From 692a4d41bf54e65068709aa18c35becf5854089b Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Thu, 18 Jan 2024 09:55:42 -0800 Subject: [PATCH 001/188] Small change to get this up in a PR --- README.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/README.md b/README.md index c36d8842..3cd168ff 100644 --- a/README.md +++ b/README.md @@ -29,8 +29,6 @@
:warning: Work in progress :warning:
-## - ## Outline - [Usage](#usage) From c6e9e17764404e5ddb66fd7c12d932091c622821 Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Thu, 18 Jan 2024 16:40:58 -0800 Subject: [PATCH 002/188] feat: move flake.nix to numtide/devshell --- README.md | 2 +- flake.lock | 109 +++++++++++++++---- flake.nix | 288 ++++++++++++++++++++++++++++++++++++++++++------- pre-commit.nix | 9 ++ 4 files changed, 349 insertions(+), 59 deletions(-) create mode 100644 pre-commit.nix diff --git a/README.md b/README.md index 3cd168ff..d9e5b879 100644 --- a/README.md +++ b/README.md @@ -44,7 +44,7 @@ Add the following to the `[dependencies]` section of your `Cargo.toml` file: ```toml -rs-ucan = "0.1.0" +rs-ucan = "1.0.0-rc.1" ``` ## Testing the Project diff --git a/flake.lock b/flake.lock index 29249c58..51184076 100644 --- a/flake.lock +++ b/flake.lock @@ -1,18 +1,21 @@ { "nodes": { - "flake-compat": { - "flake": false, + "devshell": { + "inputs": { + "flake-utils": "flake-utils", + "nixpkgs": "nixpkgs" + }, "locked": { - "lastModified": 1696426674, - "narHash": "sha256-kvjfFW7WAETZlt09AgDn1MrtKzP7t90Vf7vypd3OL1U=", - "owner": "edolstra", - "repo": "flake-compat", - "rev": "0f9255e01c2351cc7d116c072cb317785dd33b33", + "lastModified": 1705332421, + "narHash": "sha256-USpGLPme1IuqG78JNqSaRabilwkCyHmVWY0M9vYyqEA=", + "owner": "numtide", + "repo": "devshell", + "rev": "83cb93d6d063ad290beee669f4badf9914cc16ec", "type": "github" }, "original": { - "owner": "edolstra", - "repo": "flake-compat", + "owner": "numtide", + "repo": "devshell", "type": "github" } }, @@ -21,11 +24,11 @@ "systems": "systems" }, "locked": { - "lastModified": 1694529238, - "narHash": "sha256-zsNZZGTGnMOf9YpHKJqMSsa0dXbfmxeoJ7xHlrt+xmY=", + "lastModified": 1701680307, + "narHash": "sha256-kAuep2h5ajznlPMD9rnQyffWG8EM/C73lejGofXvdM8=", "owner": "numtide", "repo": "flake-utils", - "rev": "ff7b65b44d01cf9ba6a71320833626af21126384", + "rev": "4022d587cbbfd70fe950c1e2083a02621806a725", "type": "github" }, "original": { @@ -34,13 +37,46 @@ "type": "github" } }, + "flake-utils_2": { + "inputs": { + "systems": "systems_2" + }, + "locked": { + "lastModified": 1705309234, + "narHash": "sha256-uNRRNRKmJyCRC/8y1RqBkqWBLM034y4qN7EprSdmgyA=", + "owner": "numtide", + "repo": "flake-utils", + "rev": "1ef2e671c3b0c19053962c07dbda38332dcebf26", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "flake-utils", + "type": "github" + } + }, + "nixos-unstable": { + "locked": { + "lastModified": 1705556346, + "narHash": "sha256-2+ZUEFCKlctTsut81S84xkCccMsZLLX7DA/U3xZ3BqY=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "cefcf19e1c6d4255b2aede5535d04064f6917e9b", + "type": "github" + }, + "original": { + "id": "nixpkgs", + "ref": "nixos-unstable-small", + "type": "indirect" + } + }, "nixpkgs": { "locked": { - "lastModified": 1698266953, - "narHash": "sha256-jf72t7pC8+8h8fUslUYbWTX5rKsRwOzRMX8jJsGqDXA=", + "lastModified": 1704161960, + "narHash": "sha256-QGua89Pmq+FBAro8NriTuoO/wNaUtugt29/qqA8zeeM=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "75a52265bda7fd25e06e3a67dee3f0354e73243c", + "rev": "63143ac2c9186be6d9da6035fa22620018c85932", "type": "github" }, "original": { @@ -50,11 +86,27 @@ "type": "github" } }, + "nixpkgs_2": { + "locked": { + "lastModified": 1705458851, + "narHash": "sha256-uQvEhiv33Zj/Pv364dTvnpPwFSptRZgVedDzoM+HqVg=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "8bf65f17d8070a0a490daf5f1c784b87ee73982c", + "type": "github" + }, + "original": { + "id": "nixpkgs", + "ref": "nixos-23.11", + "type": "indirect" + } + }, "root": { "inputs": { - "flake-compat": "flake-compat", - "flake-utils": "flake-utils", - "nixpkgs": "nixpkgs", + "devshell": "devshell", + "flake-utils": "flake-utils_2", + "nixos-unstable": "nixos-unstable", + "nixpkgs": "nixpkgs_2", "rust-overlay": "rust-overlay" } }, @@ -68,11 +120,11 @@ ] }, "locked": { - "lastModified": 1698199907, - "narHash": "sha256-n8RtHBIb0rLuYs4RDehW6mj6r6Yam/ODY1af/VCcurw=", + "lastModified": 1705544242, + "narHash": "sha256-LIi5jGx7kwJjodpJlnQY+X/PZspRpbDO2ypNSmHwOGQ=", "owner": "oxalica", "repo": "rust-overlay", - "rev": "22b8d29fd22cfaa2c311e0d6fd8a0ed9c2a1152b", + "rev": "ff3e4b3ee418009886848d48e4ba236a2f9de789", "type": "github" }, "original": { @@ -95,6 +147,21 @@ "repo": "default", "type": "github" } + }, + "systems_2": { + "locked": { + "lastModified": 1681028828, + "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", + "owner": "nix-systems", + "repo": "default", + "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", + "type": "github" + }, + "original": { + "owner": "nix-systems", + "repo": "default", + "type": "github" + } } }, "root": "root", diff --git a/flake.nix b/flake.nix index 71d7c6cd..9f7f8649 100644 --- a/flake.nix +++ b/flake.nix @@ -2,13 +2,11 @@ description = "rs-ucan"; inputs = { - nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable"; - flake-utils.url = "github:numtide/flake-utils"; + nixpkgs.url = "nixpkgs/nixos-23.11"; + nixos-unstable.url = "nixpkgs/nixos-unstable-small"; - flake-compat = { - url = "github:edolstra/flake-compat"; - flake = false; - }; + flake-utils.url = "github:numtide/flake-utils"; + devshell.url = "github:numtide/devshell"; rust-overlay = { url = "github:oxalica/rust-overlay"; @@ -20,28 +18,66 @@ outputs = { self, nixpkgs, - flake-compat, + nixos-unstable, flake-utils, + devshell, rust-overlay, } @ inputs: flake-utils.lib.eachDefaultSystem ( system: let - overlays = [(import rust-overlay)]; - pkgs = import nixpkgs {inherit system overlays;}; + pkgs = import nixpkgs { + inherit system; + overlays = [ + devshell.overlays.default + (import rust-overlay) + (final: prev: { + rustfmt = prev.rust-bin.nightly.latest.rustfmt; + }) + ]; + }; - rust-toolchain = (pkgs.rust-bin.fromRustupToolchainFile ./rust-toolchain.toml).override { - extensions = ["cargo" "clippy" "rustfmt" "rust-src" "rust-std"]; - targets = ["wasm32-unknown-unknown" "wasm32-wasi"]; + unstable = import nixos-unstable { + inherit system; }; - nightly-rustfmt = pkgs.rust-bin.nightly.latest.rustfmt; + # nightly-rustfmt = pkgs.rust-bin.nightly.latest.rustfmt; + + rust-toolchain = pkgs.rust-bin.stable.latest.default.override { + extensions = [ + "cargo" + "clippy" + "llvm-tools-preview" + "rust-src" + "rust-std" + "rustfmt" + ]; + + targets = [ + "aarch64-apple-darwin" + "x86_64-apple-darwin" + + "x86_64-unknown-linux-musl" + "aarch64-unknown-linux-musl" + + "wasm32-unknown-unknown" + "wasm32-wasi" + ]; + }; format-pkgs = with pkgs; [ nixpkgs-fmt alejandra + taplo + ]; + + darwin-installs = with pkgs.darwin.apple_sdk.frameworks; [ + Security + CoreFoundation + Foundation ]; cargo-installs = with pkgs; [ + cargo-criterion cargo-deny cargo-expand cargo-nextest @@ -49,59 +85,237 @@ cargo-sort cargo-udeps cargo-watch - binaryen + llvmPackages.bintools + twiggy + unstable.cargo-component wasm-bindgen-cli + wasm-tools ]; - in rec - { - devShells.default = pkgs.mkShell { + in rec { + devShells.default = pkgs.devshell.mkShell { name = "rs-ucan"; - # blst requires --target=wasm32 support in Clang, which MacOS system clang doesn't provide - stdenv = pkgs.clangStdenv; + imports = [./pre-commit.nix]; - nativeBuildInputs = with pkgs; + packages = with pkgs; [ # The ordering of these two items is important. For nightly rustfmt to be used instead of # the rustfmt provided by `rust-toolchain`, it must appear first in the list. This is # because native build inputs are added to $PATH in the order they're listed here. - nightly-rustfmt + # nightly-rustfmt rust-toolchain - pre-commit - protobuf + direnv self.packages.${system}.irust - nodejs + + chromedriver nodePackages.pnpm + protobuf + unstable.nodejs_20 + unstable.wasmtime ] ++ format-pkgs ++ cargo-installs - ++ lib.optionals stdenv.isDarwin [ - darwin.apple_sdk.frameworks.Security - darwin.apple_sdk.frameworks.CoreFoundation - darwin.apple_sdk.frameworks.Foundation - ]; - - shellHook = '' - [ -e .git/hooks/pre-commit ] || pre-commit install --install-hooks && pre-commit install --hook-type commit-msg - ''; + ++ lib.optionals stdenv.isDarwin darwin-installs; + + env = [ + { + name = "RUSTC_WRAPPER"; + value = "${pkgs.sccache}/bin/sccache"; + } + ]; + + commands = [ + # Release + { + name = "release"; + help = "[DEFAULT] Release (optimized build) for current host target"; + category = "release"; + command = "release:host"; + } + { + name = "release:host"; + help = "Release for current host target"; + category = "release"; + command = "${pkgs.cargo}/bin/cargo build --release"; + } + { + name = "release:wasm"; + help = "Release for current host target"; + category = "release"; + command = "${pkgs.cargo}/bin/cargo build --release --target=wasm32-unknown-unknown"; + } + # Build + { + name = "build"; + help = "[DEFAULT] Build for current host target"; + category = "build"; + command = "build:host"; + } + { + name = "build:host"; + help = "Build for current host target"; + category = "build"; + command = "${pkgs.cargo}/bin/cargo build"; + } + { + name = "build:wasm"; + help = "Build for wasm32-unknown-unknown"; + category = "build"; + command = "${pkgs.cargo}/bin/cargo build --target=wasm32-unknown-unknown"; + } + { + name = "build:wasi"; + help = "Build for WASI"; + category = "build"; + command = "${pkgs.cargo}/bin/cargo build --target wasm32-wasi"; + } + # Bench + { + name = "bench:host"; + help = "Run host Criterion benchmarks"; + category = "dev"; + command = "${pkgs.cargo}/bin/cargo criterion"; + } + { + name = "bench:host:open"; + help = "Open host Criterion benchmarks in browser"; + category = "dev"; + command = "${pkgs.xdg-utils}/bin/xdg-open ./target/criterion/report/index.html"; + } + # Lint + { + name = "lint"; + help = "Run Clippy"; + category = "dev"; + command = "${pkgs.cargo}/bin/cargo clippy"; + } + { + name = "lint:pedantic"; + help = "Run Clippy pedantically"; + category = "dev"; + command = "${pkgs.cargo}/bin/cargo clippy -- -W clippy::pedantic"; + } + { + name = "lint:fix"; + help = "Apply non-pendantic Clippy suggestions"; + category = "dev"; + command = "${pkgs.cargo}/bin/cargo clippy --fix"; + } + # Watch + { + name = "watch:build:host"; + help = "Rebuild host target on save"; + category = "watch"; + command = "${pkgs.cargo}/bin/cargo watch --clear"; + } + { + name = "watch:build:wasm"; + help = "Rebuild host target on save"; + category = "watch"; + command = "${pkgs.cargo}/bin/cargo watch --clear --features=serde -- cargo build --target=wasm32-unknown-unknown"; + } + { + name = "watch:lint"; + help = "Lint on save"; + category = "watch"; + command = "${pkgs.cargo}/bin/cargo watch --clear --exec clippy"; + } + { + name = "watch:lint:pedantic"; + help = "Pedantic lint on save"; + category = "watch"; + command = "${pkgs.cargo}/bin/cargo watch --clear --exec 'clippy -- -W clippy::pedantic'"; + } + { + name = "watch:test:host"; + help = "Run all tests on save"; + category = "watch"; + command = "${pkgs.cargo}/bin/cargo watch --clear --exec test"; + } + { + name = "watch:test:docs:host"; + help = "Run all tests on save"; + category = "watch"; + command = "${pkgs.cargo}/bin/cargo watch --clear --exec test"; + } + # Test + { + name = "test:all"; + help = "Run Cargo tests"; + category = "test"; + command = "test:host && test:docs && test:wasm"; + } + { + name = "test:host"; + help = "Run Cargo tests for host target"; + category = "test"; + command = "${pkgs.cargo}/bin/cargo test"; + } + { + name = "test:wasm"; + help = "Run wasm-pack tests on all targets"; + category = "test"; + command = "test:wasm:node && test:wasm:chrome"; + } + { + name = "test:wasm:nodejs"; + help = "Run wasm-pack tests in Node.js"; + category = "test"; + command = "${pkgs.wasm-pack}/bin/wasm-pack test --node"; + } + { + name = "test:wasm:chrome"; + help = "Run wasm-pack tests in headless Chrome"; + category = "test"; + command = "${pkgs.wasm-pack}/bin/wasm-pack test --headless --chrome"; + } + { + name = "test:docs"; + help = "Run Cargo doctests"; + category = "test"; + command = "${pkgs.cargo}/bin/cargo test --doc"; + } + # Docs + { + name = "docs"; + help = "[DEFAULT]: Open refreshed docs"; + category = "dev"; + command = "docs:open"; + } + { + name = "docs:build"; + help = "Refresh the docs"; + category = "dev"; + command = "${pkgs.cargo}/bin/cargo doc"; + } + { + name = "docs:open"; + help = "Open refreshed docs"; + category = "dev"; + command = "${pkgs.cargo}/bin/cargo doc --open"; + } + ]; }; packages.irust = pkgs.rustPlatform.buildRustPackage rec { pname = "irust"; - version = "1.70.0"; + version = "1.71.19"; src = pkgs.fetchFromGitHub { owner = "sigmaSd"; repo = "IRust"; - rev = "v${version}"; - sha256 = "sha256-chZKesbmvGHXwhnJRZbXyX7B8OwJL9dJh0O1Axz/n2E="; + rev = "irust@${version}"; + sha256 = "sha256-R3EAovCI5xDCQ5R69nMeE6v0cGVcY00O3kV8qHf0akc="; }; doCheck = false; - cargoSha256 = "sha256-FmsD3ajMqpPrTkXCX2anC+cmm0a2xuP+3FHqzj56Ma4="; + cargoSha256 = "sha256-2aVCNz/Lw7364B5dgGaloVPcQHm2E+b/BOxF6Qlc8Hs="; }; formatter = pkgs.alejandra; + + # blst requires --target=wasm32 support in Clang, which MacOS system clang doesn't provide + stdenv = pkgs.clangStdenv; } ); } diff --git a/pre-commit.nix b/pre-commit.nix new file mode 100644 index 00000000..b4ed07d7 --- /dev/null +++ b/pre-commit.nix @@ -0,0 +1,9 @@ +{pkgs, ...}: let + pc = "${pkgs.pre-commit}/bin/pre-commit"; +in { + config = { + devshell.startup.pre-commit.text = '' + [ -e .git/hooks/pre-commit ] || (${pc} install --install-hooks && ${pc} install --hook-type commit-msg) + ''; + }; +} From 3177cae19c1b640f74494c6e6b7e241e2e490f14 Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Thu, 18 Jan 2024 16:58:07 -0800 Subject: [PATCH 003/188] Update README to use new GitHub markdown features --- README.md | 66 +++++++++++++++++++++++++++++++++---------------------- flake.nix | 17 +++++++------- 2 files changed, 48 insertions(+), 35 deletions(-) diff --git a/README.md b/README.md index d9e5b879..d7a61f2f 100644 --- a/README.md +++ b/README.md @@ -29,16 +29,6 @@
:warning: Work in progress :warning:
-## Outline - -- [Usage](#usage) -- [Testing the Project](#testing-the-project) -- [Benchmarking the Project](#benchmarking-the-project) -- [Contributing](#contributing) -- [Getting Help](#getting-help) -- [External Resources](#external-resources) -- [License](#license) - ## Usage Add the following to the `[dependencies]` section of your `Cargo.toml` file: @@ -49,11 +39,11 @@ rs-ucan = "1.0.0-rc.1" ## Testing the Project -- Run tests +Run tests - ```console - cargo test - ``` +| Cargo | Nix | +|--------------|------------| +| `cargo test` | `test:all` | ## Benchmarking the Project @@ -71,19 +61,23 @@ for integrating [proptest][proptest] within the the suite for working with ## Contributing :balloon: We're thankful for any feedback and help in improving our project! -We have a [contributing guide](./CONTRIBUTING.md) to help you get involved. We -also adhere to our [Code of Conduct](./CODE_OF_CONDUCT.md). +We have a [contributing guide][CONTRIBUTING] to help you get involved. We +also adhere to our [Code of Conduct]. ### Nix -This repository contains a [Nix flake][nix-flake] that initiates both the Rust -toolchain set in [rust-toolchain.toml](./rust-toolchain.toml) and a -[pre-commit hook](#pre-commit-hook). It also installs helpful cargo binaries for -development. Please install [nix][nix] and [direnv][direnv] to get started. +This repository contains a [Nix flake] that initiates both the Rust +toolchain set in [`rust-toolchain.toml`] and a [pre-commit hook]. It also +installs helpful cargo binaries for development. + +Please install [nix] to get started. We also recommend installing [direnv]. Run `nix develop` or `direnv allow` to load the `devShell` flake output, according to your preference. +The Nix shell also includes several helpful shortcut commands. +You can see a complete list of commands via the `menu` command. + ### Formatting For formatting Rust in particular, we automatically format on `nightly`, as it @@ -123,9 +117,9 @@ a type of `fix`, `feat`, `docs`, `ci`, `refactor`, etc..., structured like so: ## Getting Help -For usage questions, usecases, or issues reach out to us in our [Discord channel](https://discord.gg/4UdeQhw7fv). +For usage questions, usecases, or issues reach out to us in the [UCAN Discord]. -We would be happy to try to answer your question or try opening a new issue on Github. +We would be happy to try to answer your question or try opening a new issue on GitHub. ## External Resources @@ -133,11 +127,30 @@ These are references to specifications, talks and presentations, etc. ## License -This project is licensed under the [Apache License 2.0](./LICENSE), or -[http://www.apache.org/licenses/LICENSE-2.0][apache]. +This project is licensed under the [Apache License 2.0][LICENSE], or +[http://www.apache.org/licenses/LICENSE-2.0][Apache]. + + + +[Benchmarking the Project]: #benchmarking-the-project +[Contributing]: #contributing +[External Resources]: #external-resources +[Getting Help]: #getting-help +[License]: #license +[Testing the Project]: #testing-the-project +[Usage]: #usage +[pre-commit hook]: #pre-commit-hook + + + +[CONTRIBUTING]: ./CONTRIBUTING.md +[LICENSE]: ./LICENSE +[Code of Conduct]: ./CODE_OF_CONDUCT.md +[`rust-toolchain.toml`]: ./rust-toolchain.toml + -[apache]: https://www.apache.org/licenses/LICENSE-2.0 +[Apache]: https://www.apache.org/licenses/LICENSE-2.0 [cargo-expand]: https://github.com/dtolnay/cargo-expand [cargo-udeps]: https://github.com/est31/cargo-udeps [cargo-watch]: https://github.com/watchexec/cargo-watch @@ -147,7 +160,8 @@ This project is licensed under the [Apache License 2.0](./LICENSE), or [direnv]:https://direnv.net/ [irust]: https://github.com/sigmaSd/IRust [nix]:https://nixos.org/download.html -[nix-flake]: https://nixos.wiki/wiki/Flakes +[Nix flake]: https://nixos.wiki/wiki/Flakes [pre-commit]: https://pre-commit.com/ [proptest]: https://github.com/proptest-rs/proptest [strategies]: https://docs.rs/proptest/latest/proptest/strategy/trait.Strategy.html +[UCAN Discord]: https://discord.gg/4UdeQhw7fv diff --git a/flake.nix b/flake.nix index 9f7f8649..3a7558f8 100644 --- a/flake.nix +++ b/flake.nix @@ -17,10 +17,10 @@ outputs = { self, - nixpkgs, - nixos-unstable, - flake-utils, devshell, + flake-utils, + nixos-unstable, + nixpkgs, rust-overlay, } @ inputs: flake-utils.lib.eachDefaultSystem ( @@ -40,8 +40,6 @@ inherit system; }; - # nightly-rustfmt = pkgs.rust-bin.nightly.latest.rustfmt; - rust-toolchain = pkgs.rust-bin.stable.latest.default.override { extensions = [ "cargo" @@ -99,9 +97,10 @@ packages = with pkgs; [ - # The ordering of these two items is important. For nightly rustfmt to be used instead of - # the rustfmt provided by `rust-toolchain`, it must appear first in the list. This is - # because native build inputs are added to $PATH in the order they're listed here. + # NOTE: The ordering of these two items is important. For nightly rustfmt to be used + # instead of the rustfmt provided by `rust-toolchain`, it must appear first in the list. + # This is because native build inputs are added to $PATH in the order they're listed here. + # # nightly-rustfmt rust-toolchain @@ -314,7 +313,7 @@ formatter = pkgs.alejandra; - # blst requires --target=wasm32 support in Clang, which MacOS system clang doesn't provide + # NOTE: blst requires --target=wasm32 support in Clang, which MacOS system clang doesn't provide stdenv = pkgs.clangStdenv; } ); From a31445a1b4f6e7864ce3acdf2d8d1fa940f7ca41 Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Thu, 18 Jan 2024 16:58:51 -0800 Subject: [PATCH 004/188] Trim whitespace --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index d7a61f2f..3477406f 100644 --- a/README.md +++ b/README.md @@ -67,7 +67,7 @@ also adhere to our [Code of Conduct]. ### Nix This repository contains a [Nix flake] that initiates both the Rust -toolchain set in [`rust-toolchain.toml`] and a [pre-commit hook]. It also +toolchain set in [`rust-toolchain.toml`] and a [pre-commit hook]. It also installs helpful cargo binaries for development. Please install [nix] to get started. We also recommend installing [direnv]. From 3cda14fe16627d5909cf7b663f4e497cc8ca8542 Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Thu, 18 Jan 2024 17:11:31 -0800 Subject: [PATCH 005/188] feat: frustration --- README.md | 16 ++++++++-------- flake.nix | 52 +++++++++++++++++++++++++++++++--------------------- 2 files changed, 39 insertions(+), 29 deletions(-) diff --git a/README.md b/README.md index 3477406f..f9c56b2c 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@
- rs-ucan Logo + rs-ucan Logo

rs-ucan

@@ -41,9 +41,9 @@ rs-ucan = "1.0.0-rc.1" Run tests -| Cargo | Nix | -|--------------|------------| -| `cargo test` | `test:all` | +| Nix | Cargo | +|-----------|--------------| +| `test:all | `cargo test` | ## Benchmarking the Project @@ -52,11 +52,11 @@ For benchmarking and measuring performance, this project leverages for integrating [proptest][proptest] within the the suite for working with [strategies][strategies] and sampling from randomly generated values. -- Run benchmarks +## Benchmarks - ```console - cargo bench --features test_utils - ``` +| Nix | Cargo | +|---------|-------------------------------------| +| `bench` | `cargo bench --features test_utils` | ## Contributing diff --git a/flake.nix b/flake.nix index 3a7558f8..f9fb6703 100644 --- a/flake.nix +++ b/flake.nix @@ -89,6 +89,9 @@ wasm-bindgen-cli wasm-tools ]; + + cargo = "${pkgs.cargo}/bin/cargo"; + wasm-pack = "${pkgs.wasm-pack}/bin/wasm-pack"; in rec { devShells.default = pkgs.devshell.mkShell { name = "rs-ucan"; @@ -136,13 +139,13 @@ name = "release:host"; help = "Release for current host target"; category = "release"; - command = "${pkgs.cargo}/bin/cargo build --release"; + command = "${cargo} build --release"; } { name = "release:wasm"; help = "Release for current host target"; category = "release"; - command = "${pkgs.cargo}/bin/cargo build --release --target=wasm32-unknown-unknown"; + command = "${cargo} build --release --target=wasm32-unknown-unknown"; } # Build { @@ -155,26 +158,33 @@ name = "build:host"; help = "Build for current host target"; category = "build"; - command = "${pkgs.cargo}/bin/cargo build"; + command = "${cargo} build"; } { name = "build:wasm"; help = "Build for wasm32-unknown-unknown"; category = "build"; - command = "${pkgs.cargo}/bin/cargo build --target=wasm32-unknown-unknown"; + command = "${cargo} build --target=wasm32-unknown-unknown"; } { name = "build:wasi"; help = "Build for WASI"; category = "build"; - command = "${pkgs.cargo}/bin/cargo build --target wasm32-wasi"; + command = "${cargo} build --target wasm32-wasi"; } # Bench + { + name = "bench"; + help = "Run benchmarks, including test utils"; + category = "dev"; + command = "${cargo} bench --features test_utils"; + } + # FIXME align with `bench`? { name = "bench:host"; help = "Run host Criterion benchmarks"; category = "dev"; - command = "${pkgs.cargo}/bin/cargo criterion"; + command = "${cargo} criterion"; } { name = "bench:host:open"; @@ -187,56 +197,56 @@ name = "lint"; help = "Run Clippy"; category = "dev"; - command = "${pkgs.cargo}/bin/cargo clippy"; + command = "${cargo} clippy"; } { name = "lint:pedantic"; help = "Run Clippy pedantically"; category = "dev"; - command = "${pkgs.cargo}/bin/cargo clippy -- -W clippy::pedantic"; + command = "${cargo} clippy -- -W clippy::pedantic"; } { name = "lint:fix"; help = "Apply non-pendantic Clippy suggestions"; category = "dev"; - command = "${pkgs.cargo}/bin/cargo clippy --fix"; + command = "${cargo} clippy --fix"; } # Watch { name = "watch:build:host"; help = "Rebuild host target on save"; category = "watch"; - command = "${pkgs.cargo}/bin/cargo watch --clear"; + command = "${cargo} watch --clear"; } { name = "watch:build:wasm"; help = "Rebuild host target on save"; category = "watch"; - command = "${pkgs.cargo}/bin/cargo watch --clear --features=serde -- cargo build --target=wasm32-unknown-unknown"; + command = "${cargo} watch --clear --features=serde -- cargo build --target=wasm32-unknown-unknown"; } { name = "watch:lint"; help = "Lint on save"; category = "watch"; - command = "${pkgs.cargo}/bin/cargo watch --clear --exec clippy"; + command = "${cargo} watch --clear --exec clippy"; } { name = "watch:lint:pedantic"; help = "Pedantic lint on save"; category = "watch"; - command = "${pkgs.cargo}/bin/cargo watch --clear --exec 'clippy -- -W clippy::pedantic'"; + command = "${cargo} watch --clear --exec 'clippy -- -W clippy::pedantic'"; } { name = "watch:test:host"; help = "Run all tests on save"; category = "watch"; - command = "${pkgs.cargo}/bin/cargo watch --clear --exec test"; + command = "${cargo} watch --clear --exec test"; } { name = "watch:test:docs:host"; help = "Run all tests on save"; category = "watch"; - command = "${pkgs.cargo}/bin/cargo watch --clear --exec test"; + command = "${cargo} watch --clear --exec test"; } # Test { @@ -249,7 +259,7 @@ name = "test:host"; help = "Run Cargo tests for host target"; category = "test"; - command = "${pkgs.cargo}/bin/cargo test"; + command = "${cargo} test"; } { name = "test:wasm"; @@ -261,19 +271,19 @@ name = "test:wasm:nodejs"; help = "Run wasm-pack tests in Node.js"; category = "test"; - command = "${pkgs.wasm-pack}/bin/wasm-pack test --node"; + command = "${wasm-pack} test --node"; } { name = "test:wasm:chrome"; help = "Run wasm-pack tests in headless Chrome"; category = "test"; - command = "${pkgs.wasm-pack}/bin/wasm-pack test --headless --chrome"; + command = "${wasm-pack} test --headless --chrome"; } { name = "test:docs"; help = "Run Cargo doctests"; category = "test"; - command = "${pkgs.cargo}/bin/cargo test --doc"; + command = "${cargo} test --doc"; } # Docs { @@ -286,13 +296,13 @@ name = "docs:build"; help = "Refresh the docs"; category = "dev"; - command = "${pkgs.cargo}/bin/cargo doc"; + command = "${cargo} doc"; } { name = "docs:open"; help = "Open refreshed docs"; category = "dev"; - command = "${pkgs.cargo}/bin/cargo doc --open"; + command = "${cargo} doc --open"; } ]; }; From d6a62fb227660e1f27c939ae31c1fc9c13c670c9 Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Thu, 18 Jan 2024 17:12:11 -0800 Subject: [PATCH 006/188] fix: typo --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index f9c56b2c..76ceb6cf 100644 --- a/README.md +++ b/README.md @@ -41,9 +41,9 @@ rs-ucan = "1.0.0-rc.1" Run tests -| Nix | Cargo | -|-----------|--------------| -| `test:all | `cargo test` | +| Nix | Cargo | +|------------|--------------| +| `test:all` | `cargo test` | ## Benchmarking the Project From 0e0efa25917544b7dcad03a5765543e883508e08 Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Thu, 18 Jan 2024 17:19:00 -0800 Subject: [PATCH 007/188] Adding some TODOs and FIXMEs to come back to --- Cargo.toml | 12 ++++++------ examples/counterparts.rs | 1 + 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 450ae834..23d96792 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,16 +2,16 @@ name = "rs-ucan" version = "0.1.0" description = "Rust implementation of UCAN" -keywords = [] +keywords = ["capabilities", "authorization", "ucan"] categories = [] include = ["/src", "/examples", "/benches", "README.md", "LICENSE"] license = "Apache-2.0" readme = "README.md" edition = "2021" -rust-version = "1.67" +rust-version = "1.75" documentation = "https://docs.rs/rs-ucan" repository = "https://github.com/ucan-wg/rs-ucan" -authors = ["Quinn Wilton "] +authors = ["Quinn Wilton ", "Brooklyn Zelenka Result<(), Box> { println!("Alien Shore!"); Ok(()) From 77dafd9672da61e39c4c76cc04044a02966e56b0 Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Tue, 23 Jan 2024 16:37:58 -0800 Subject: [PATCH 008/188] Exploratory programming --- Cargo.toml | 8 +- README.md | 26 +-- flake.nix | 83 ++++---- src/lib.rs | 577 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 636 insertions(+), 58 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 23d96792..8c6ad43c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -29,6 +29,7 @@ path = "examples/counterparts.rs" [dependencies] anyhow = "1.0.75" +aquamarine = {version = "0.5", optional = true} async-signature = "0.4.0" async-trait = "0.1.73" blst = { version = "0.3.11", optional = true, default-features = false } @@ -58,6 +59,7 @@ thiserror = "1.0" tracing = "0.1.40" unsigned-varint = "0.7.2" url = "2.4.1" +void = "1.0" web-time = "0.2.3" [target.'cfg(not(target_arch = "wasm32"))'.dependencies] @@ -74,7 +76,7 @@ wasm-bindgen-futures = { version = "0.4" } web-sys = { version = "0.3", features = ["Crypto", "CryptoKey", "CryptoKeyPair", "SubtleCrypto"] } [dev-dependencies] -multihash = "0.18.0" +multihash = "0.18" [target.'cfg(not(target_arch = "wasm32"))'.dev-dependencies] criterion = "0.4" @@ -105,11 +107,13 @@ es512-verifier = ["es512"] ps256-verifier = ["ps256"] rs256-verifier = ["rs256"] bls-verifier = ["bls"] +mermaid_docs = ["aquamarine"] -[metadata.docs.rs] # FIXME cargo complains that it doesn't understand this? +[package.metadata.docs.rs] all-features = true # defines the configuration attribute `docsrs` rustdoc-args = ["--cfg", "docsrs"] +cargo-args = ["--features='mermaid_docs'"] # See https://doc.rust-lang.org/cargo/reference/profiles.html for more info. # [profile.release] diff --git a/README.md b/README.md index 76ceb6cf..26b9b3ea 100644 --- a/README.md +++ b/README.md @@ -48,9 +48,9 @@ Run tests ## Benchmarking the Project For benchmarking and measuring performance, this project leverages -[criterion][criterion] and a `test_utils` feature flag -for integrating [proptest][proptest] within the the suite for working with -[strategies][strategies] and sampling from randomly generated values. +[Criterion] and a `test_utils` feature flag +for integrating [proptest] within the the suite for working with +[strategies] and sampling from randomly generated values. ## Benchmarks @@ -70,7 +70,7 @@ This repository contains a [Nix flake] that initiates both the Rust toolchain set in [`rust-toolchain.toml`] and a [pre-commit hook]. It also installs helpful cargo binaries for development. -Please install [nix] to get started. We also recommend installing [direnv]. +Please install [Nix] to get started. We also recommend installing [direnv]. Run `nix develop` or `direnv allow` to load the `devShell` flake output, according to your preference. @@ -85,7 +85,7 @@ uses specific nightly features we recommend by default. ### Pre-commit Hook -This project recommends using [pre-commit][pre-commit] for running pre-commit +This project recommends using [pre-commit] for running pre-commit hooks. Please run this before every commit and/or push. - If you are doing interim commits locally, and for some reason if you _don't_ @@ -95,7 +95,7 @@ hooks. Please run this before every commit and/or push. ### Recommended Development Flow - We recommend leveraging [cargo-watch][cargo-watch], - [cargo-expand][cargo-expand] and [irust][irust] for Rust development. + [`cargo-expand`] and [IRust] for Rust development. - We recommend using [cargo-udeps][cargo-udeps] for removing unused dependencies before commits and pull-requests. @@ -127,7 +127,7 @@ These are references to specifications, talks and presentations, etc. ## License -This project is licensed under the [Apache License 2.0][LICENSE], or +This project is [licensed under the Apache License 2.0][LICENSE], or [http://www.apache.org/licenses/LICENSE-2.0][Apache]. @@ -151,15 +151,15 @@ This project is licensed under the [Apache License 2.0][LICENSE], or [Apache]: https://www.apache.org/licenses/LICENSE-2.0 -[cargo-expand]: https://github.com/dtolnay/cargo-expand -[cargo-udeps]: https://github.com/est31/cargo-udeps -[cargo-watch]: https://github.com/watchexec/cargo-watch +[`cargo-expand`]: https://github.com/dtolnay/cargo-expand +[`cargo-udeps`]: https://github.com/est31/cargo-udeps +[`cargo-watch`]: https://github.com/watchexec/cargo-watch [commit-spec]: https://www.conventionalcommits.org/en/v1.0.0/#specification [commit-spec-site]: https://www.conventionalcommits.org/ -[criterion]: https://github.com/bheisler/criterion.rs +[Criterion]: https://github.com/bheisler/criterion.rs [direnv]:https://direnv.net/ -[irust]: https://github.com/sigmaSd/IRust -[nix]:https://nixos.org/download.html +[IRust]: https://github.com/sigmaSd/IRust +[Nix]:https://nixos.org/download.html [Nix flake]: https://nixos.wiki/wiki/Flakes [pre-commit]: https://pre-commit.com/ [proptest]: https://github.com/proptest-rs/proptest diff --git a/flake.nix b/flake.nix index f9fb6703..ddfdabd2 100644 --- a/flake.nix +++ b/flake.nix @@ -23,44 +23,38 @@ nixpkgs, rust-overlay, } @ inputs: - flake-utils.lib.eachDefaultSystem ( - system: let - pkgs = import nixpkgs { - inherit system; - overlays = [ - devshell.overlays.default - (import rust-overlay) - (final: prev: { - rustfmt = prev.rust-bin.nightly.latest.rustfmt; - }) - ]; - }; + flake-utils.lib.eachDefaultSystem (system: + let + overlays = [ + devshell.overlays.default + (import rust-overlay) + ]; - unstable = import nixos-unstable { - inherit system; - }; + pkgs = import nixpkgs { inherit system overlays; }; + unstable = import nixos-unstable { inherit system overlays; }; - rust-toolchain = pkgs.rust-bin.stable.latest.default.override { - extensions = [ - "cargo" - "clippy" - "llvm-tools-preview" - "rust-src" - "rust-std" - "rustfmt" - ]; + rust-toolchain = + (pkgs.rust-bin.fromRustupToolchainFile ./rust-toolchain.toml).override { + extensions = [ + "cargo" + "clippy" + "llvm-tools-preview" + "rust-src" + "rust-std" + "rustfmt" + ]; - targets = [ - "aarch64-apple-darwin" - "x86_64-apple-darwin" + targets = [ + "aarch64-apple-darwin" + "x86_64-apple-darwin" - "x86_64-unknown-linux-musl" - "aarch64-unknown-linux-musl" + "x86_64-unknown-linux-musl" + "aarch64-unknown-linux-musl" - "wasm32-unknown-unknown" - "wasm32-wasi" - ]; - }; + "wasm32-unknown-unknown" + "wasm32-wasi" + ]; + }; format-pkgs = with pkgs; [ nixpkgs-fmt @@ -91,7 +85,9 @@ ]; cargo = "${pkgs.cargo}/bin/cargo"; + node = "${unstable.nodejs_20}/bin/node"; wasm-pack = "${pkgs.wasm-pack}/bin/wasm-pack"; + in rec { devShells.default = pkgs.devshell.mkShell { name = "rs-ucan"; @@ -100,20 +96,15 @@ packages = with pkgs; [ - # NOTE: The ordering of these two items is important. For nightly rustfmt to be used - # instead of the rustfmt provided by `rust-toolchain`, it must appear first in the list. - # This is because native build inputs are added to $PATH in the order they're listed here. - # - # nightly-rustfmt - rust-toolchain - direnv + rust-toolchain self.packages.${system}.irust + (pkgs.hiPrio pkgs.rust-bin.nightly.latest.rustfmt) chromedriver - nodePackages.pnpm protobuf unstable.nodejs_20 + unstable.nodePackages.pnpm unstable.wasmtime ] ++ format-pkgs @@ -166,6 +157,12 @@ category = "build"; command = "${cargo} build --target=wasm32-unknown-unknown"; } + { + name = "build:node"; + help = "Build JS-wrapped Wasm library"; + category = "build"; + command = "${pkgs.nodePackages.pnpm}/bin/pnpm install && ${node} run build"; + } { name = "build:wasi"; help = "Build for WASI"; @@ -296,13 +293,13 @@ name = "docs:build"; help = "Refresh the docs"; category = "dev"; - command = "${cargo} doc"; + command = "${cargo} doc --features=mermaid_docs"; } { name = "docs:open"; help = "Open refreshed docs"; category = "dev"; - command = "${cargo} doc --open"; + command = "${cargo} doc --features=mermaid_docs --open"; } ]; }; diff --git a/src/lib.rs b/src/lib.rs index a7a023f4..75d898c6 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -36,6 +36,583 @@ pub const DEFAULT_MULTIHASH: multihash::Code = multihash::Code::Sha2_256; /// A decentralized identifier. pub type Did = String; +use libipld_core::{ipld::Ipld, link::Link}; +use std::{collections::BTreeMap, fmt::Debug}; +use url::Url; +use void::Void; +use web_time::{Instant, SystemTime}; + +pub struct InvocationPayload { + pub issuer: Did, + pub subject: Did, + pub audience: Option, + + pub ability: Ability, // FIXME check name in spec + + // pub proofs: Vec>>, + // pub cause: Option>>, // FIXME? + pub metadata: BTreeMap, Ipld>, // FIXME serde value instead? + pub nonce: Box<[u8]>, // Better type? + + pub expiration: Timestamp, + pub not_before: Option, +} + +pub struct DelegationPayload { + pub issuer: Did, + pub subject: Did, + pub audience: Did, + + pub capability_builder: A::Builder, // FIXME + pub conditions: Box<[Cond]>, // Worth it over a Vec? + + pub metadata: BTreeMap, Ipld>, // FIXME serde value instead? + pub nonce: Box<[u8]>, // Better type? + + pub expiration: Timestamp, + pub not_before: Option, +} + +// FIXME move that clone? +impl From> for DelegationPayload { + fn from(invocation: InvocationPayload) -> Self { + Self { + issuer: invocation.issuer.clone(), + subject: invocation.subject.clone(), + audience: invocation + .audience + .clone() + .unwrap_or(invocation.issuer.clone()), + capability_builder: >::into(invocation.ability.clone()), + conditions: Box::new([]), + metadata: invocation.metadata.clone(), + nonce: invocation.nonce.clone(), + expiration: invocation.expiration.clone(), + not_before: invocation.not_before.clone(), + } + } +} + +// impl DelegationPayload { +// fn check( +// &self, +// proof: &DelegationPayload + Capability, Cond>, +// now: SystemTime, +// ) -> Result<(), ()> { +// // FIXME heavily WIP +// // FIXME signature +// self.check_time(now).unwrap(); +// self.check_issuer(&proof.audience)?; // FIXME alignment +// self.check_subject(&proof.subject)?; +// self.check_conditions(&proof.conditions)?; +// +// proof.check_expiration(now)?; +// proof.check_not_before(now)?; +// +// self.check_ability(&proof.capability_builder)?; +// Ok(()) +// } +// } + +// FIXME add is_roo t + +pub trait TryProven { + type Proven1; + type Error1; + fn try_proven<'a>(&'a self, candidate: &'a A) -> Result<&'a Self::Proven1, Self::Error1>; +} + +impl TryProven for U +where + T: TryProve, +{ + type Proven1 = T::Proven; + type Error1 = T::Error; + + fn try_proven<'a>(&'a self, candidate: &'a T) -> Result<&'a T::Proven, T::Error> { + candidate.try_prove(self) + } +} + +pub struct DelegateAny; + +// FIXME ToBuilder + +// FIXME Remove +// pub trait Prove { +// type Witness; +// // FIXME make sure that passing the top-level item through and not checking each +// // item in the chain against the next one is correct in the 1.0 semantics +// fn prove<'a>(&'a self, proof: &'a T) -> &Self::Witness; +// } + +impl TryProve for T { + type Error = Void; + type Proven = T; + + fn try_prove<'a>(&'a self, _proof: &'a DelegateAny) -> Result<&'a Self::Proven, Void> { + Ok(self) + } +} + +// impl Prove for T { +// type Witness = T; +// +// fn prove<'a>(&'a self, proof: &'a DelegateAny) -> &Self::Witness { +// self +// } +// } +// +// impl> TryProve for T { +// type Error = Void; +// type Proven = T; +// +// fn try_prove<'a>(&'a self, proof: &'a T) -> Result<&'a T, Void> { +// Ok(proof) +// } +// } + +#[cfg_attr(doc, aquamarine::aquamarine)] +/// FIXME +/// +/// ```mermaid +/// flowchart LR +/// Invocation --> more --> Self --> Candidate --> more2 +/// more[...] +/// more2[...] +/// ``` +pub trait TryProve { + type Error; + type Proven; + + fn try_prove<'a>(&'a self, candidate: &'a T) -> Result<&'a Self::Proven, Self::Error>; +} + +///////////// + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct Msg { + to: Url, + from: Url, +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct MsgSend { + to: Url, + from: Url, + message: String, +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct MsgReceive { + to: Url, + from: Url, +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct MsgReceiveBuilder { + to: Option, + from: Option, +} + +impl From for MsgReceiveBuilder { + fn from(msg: MsgReceive) -> Self { + Self { + to: Some(msg.to), + from: Some(msg.from), + } + } +} + +impl TryFrom for MsgReceive { + type Error = MsgReceiveBuilder; + + fn try_from(builder: MsgReceiveBuilder) -> Result { + // FIXME + if let (Some(to), Some(from)) = (builder.clone().to, builder.clone().from) { + Ok(Self { to, from }) + } else { + Err(builder.clone()) // FIXME + } + } +} + +impl From for Ipld { + fn from(msg: MsgReceive) -> Self { + let mut map = BTreeMap::new(); + map.insert("to".into(), msg.to.to_string().into()); + map.insert("from".into(), msg.from.to_string().into()); + map.into() + } +} + +impl TryFrom<&Ipld> for MsgReceiveBuilder { + type Error = (); + + fn try_from(ipld: &Ipld) -> Result { + match ipld { + Ipld::Map(map) => { + if map.len() > 2 { + return Err(()); // FIXME + } + + // FIXME + let to = if let Some(Ipld::String(to)) = map.get("to") { + Url::from_str(to).ok() // FIXME + } else { + None + }; + + let from = if let Some(Ipld::String(from)) = map.get("to") { + Url::from_str(from).ok() // FIXME + } else { + None + }; + + Ok(Self { to, from }) + } + _ => Err(()), + } + } +} + +impl Capability for MsgReceive { + type Builder = MsgReceiveBuilder; + const COMMAND: &'static str = "msg/receive"; +} + +impl TryFrom<&Ipld> for MsgReceive { + type Error = (); // FIXME + + fn try_from(ipld: &Ipld) -> Result { + todo!() + } +} + +impl TryProve for Msg { + type Error = (); // FIXME + type Proven = Msg; + + fn try_prove<'a>(&'a self, candidate: &'a Msg) -> Result<&'a Self::Proven, ()> { + if self == candidate { + Ok(self) + } else { + Err(()) + } + } +} + +impl TryProve for MsgSend { + type Error = (); // FIXME + type Proven = MsgSend; + + fn try_prove<'a>(&'a self, candidate: &'a Msg) -> Result<&'a Self::Proven, ()> { + if self.to == candidate.to && self.from == candidate.from { + Ok(self) + } else { + Err(()) + } + } +} + +impl TryProve for MsgReceive { + type Error = (); // FIXME + type Proven = MsgReceive; + + fn try_prove<'a>(&'a self, candidate: &'a Msg) -> Result<&'a Self::Proven, ()> { + if self.to == candidate.to && self.from == candidate.from { + Ok(self) + } else { + Err(()) + } + } +} + +// FIXME this needs to work on builders! +impl TryProve for MsgReceive { + type Error = (); // FIXME + type Proven = MsgReceive; + + fn try_prove<'a>(&'a self, candidate: &'a MsgReceive) -> Result<&'a Self::Proven, ()> { + if self == candidate { + Ok(self) + } else { + Err(()) + } + } +} + +/////////// + +// FIXME remove +// impl> TryProve for P { +// type Error = Void; +// type Proven = P::Witness; +// +// fn try_prove<'a>(&'a self, candidate: &'a T) -> Result<&'a Self::Proven, Void> { +// Ok(self.prove(candidate)) +// } +// } + +impl TryProve for CrudDestroy { + type Error = (); // FIXME + type Proven = CrudDestroy; + fn try_prove<'a>(&'a self, candidate: &'a CrudDestroy) -> Result<&'a Self::Proven, ()> { + if self.uri == candidate.uri { + Ok(self) + } else { + Err(()) + } + } +} + +// FIXME ProveWith? +impl TryProve for CrudDestroy { + type Error = (); // FIXME + type Proven = CrudDestroy; + + fn try_prove<'a>(&'a self, candidate: &'a CrudMutate) -> Result<&'a Self::Proven, ()> { + if self.uri == candidate.uri { + Ok(self) + } else { + Err(()) + } + } +} + +impl TryProve for CrudRead { + type Error = (); + type Proven = CrudRead; + + fn try_prove<'a>(&'a self, candidate: &'a CrudRead) -> Result<&'a Self::Proven, ()> { + if self.uri == candidate.uri { + // FIXME contains & args + Ok(self) + } else { + Err(()) + } + } +} + +impl TryProve for CrudRead { + type Error = (); // FIXME + type Proven = CrudRead; + + fn try_prove<'a>(&'a self, candidate: &'a Crud) -> Result<&'a Self::Proven, ()> { + if self.uri == candidate.uri { + Ok(self) + } else { + Err(()) + } + } +} + +impl TryProve for CrudMutate { + type Error = (); // FIXME + type Proven = CrudMutate; + + fn try_prove<'a>(&'a self, candidate: &'a Crud) -> Result<&'a Self::Proven, ()> { + if self.uri == candidate.uri { + Ok(self) + } else { + Err(()) + } + } +} + +// FIXME +impl> TryProve for C { + type Error = (); + type Proven = C; + + // FIXME + fn try_prove<'a>(&'a self, candidate: &'a Crud) -> Result<&'a C, ()> { + match self.try_prove(&CrudMutate { + uri: candidate.uri.clone(), + }) { + Ok(_) => Ok(self), + Err(_) => Err(()), + } + } +} + +// FIXME lives etirely in bindgen +// https://rustwasm.github.io/docs/wasm-bindgen/contributing/design/importing-js-struct.html +// pub struct DynamicJs { +// pub command: Box, +// pub args: BTreeMap, Ipld>, +// } +// +// impl TryProve for DynamicJs { +// type Error = (); +// type Proven = DynamicJs; +// +// fn try_prove<'a>(&'a self, candidate: &'a DynamicJs) -> Result<&'a DynamicJs, ()> { +// +// } +// } + +// impl ProveDelegaton for DynamicJs { +// type Error = anyhow::Error; +// +// fn prove(&self, proof: &DynamicJs) -> Result { +// todo!() +// } +// } +// + +pub struct Crud { + uri: Url, +} + +pub struct CrudMutate { + uri: Url, +} + +pub struct CrudCreate { + pub uri: Url, + pub args: BTreeMap, String>, +} + +pub struct CrudRead { + pub uri: Url, + pub args: BTreeMap, String>, // FIXME need these? +} + +pub struct CrudUpdate { + pub uri: Url, + pub args: BTreeMap, String>, +} + +pub struct CrudDestroy { + pub uri: Url, +} + +// impl Capabilty for CrudRead{ +// const COMMAND = "crud/read"; +// +// fn subject(&self) -> Did { +// todo!() +// } +// } +// +// pub enum Condition { +// Contains { field: &str, value: Vec }, +// MinLength { field: &str, value: u64 }, +// MaxLength { field: &str, value: u64 }, +// Equals { field: &str, value: Ipld }, +// Regex { field: &str }, // FIXME +// +// // Silly example +// OnDayOfWeek { day: Day }, +// } + +pub enum Day { + Monday, + Tuesday, + Wednesday, + Thursday, + Friday, + Saturday, + Sunday, +} + +// pub trait CapBuilder: Default { +// type Ability; +// fn build(self) -> Result; +// } + +// pub trait BuilderFor: CapBuilder { +// type Builder: CapBuilder; +// } + +pub trait Capability: Sized { + // pub trait Capability: Into { + // FIXME remove sized? + // pub trait Capability: TryFrom + Into { + type Builder: From + TryInto + PartialEq + Debug; + const COMMAND: &'static str; +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct JsTime { + time: SystemTime, +} + +// FIXME just lifting this from Elixir for now +pub struct OutOfRangeError { + pub tried: SystemTime, +} + +impl JsTime { + // FIXME value should be a system time? + pub fn new(time: SystemTime) -> Result { + if time + .duration_since(std::time::UNIX_EPOCH) + .expect("FIXME") + .as_secs() + > 0x1FFFFFFFFFFFFF + { + Err(OutOfRangeError { tried: time }) + } else { + Ok(JsTime { time }) + } + } +} + +#[derive(Debug, Clone, PartialEq)] +pub enum Timestamp { + Sending(JsTime), + Receiving(SystemTime), +} + +// pub struct ReceiptPayload { +// pub issuer: Did, +// pub ran: Link>, +// pub out: UcanResult, // FIXME? +// pub proofs: Vec>>, +// pub metadata: BTreeMap, // FIXME serde value instead? +// pub issued_at: u64, +// } +// +// pub enum UcanResult { +// UcanOk(T), +// UcanErr(BTreeMap<&str, Ipld>), +// } +// +// pub struct Capability { +// command: String, +// payload: BTreeMap, +// } + +////////////////////////////////////// +////////////////////////////////////// +////////////////////////////////////// +////////////////////////////////////// +////////////////////////////////////// +////////////////////////////////////// +////////////////////////////////////// +////////////////////////////////////// +////////////////////////////////////// +////////////////////////////////////// +////////////////////////////////////// +////////////////////////////////////// +////////////////////////////////////// +////////////////////////////////////// +////////////////////////////////////// +////////////////////////////////////// +////////////////////////////////////// +////////////////////////////////////// +////////////////////////////////////// +////////////////////////////////////// +////////////////////////////////////// +////////////////////////////////////// +////////////////////////////////////// +////////////////////////////////////// +////////////////////////////////////// +////////////////////////////////////// +////////////////////////////////////// + /// The empty fact #[derive(Debug, Clone, Default, Serialize, Deserialize)] pub struct EmptyFact {} From 9c38b9b193acaf5df0d6fb8ad16851299649e34f Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Tue, 23 Jan 2024 16:38:19 -0800 Subject: [PATCH 009/188] Formatter --- Cargo.toml | 3 +-- flake.nix | 46 ++++++++++++++++++++++------------------------ 2 files changed, 23 insertions(+), 26 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 8c6ad43c..e3eba69c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -29,7 +29,7 @@ path = "examples/counterparts.rs" [dependencies] anyhow = "1.0.75" -aquamarine = {version = "0.5", optional = true} +aquamarine = { version = "0.5", optional = true } async-signature = "0.4.0" async-trait = "0.1.73" blst = { version = "0.3.11", optional = true, default-features = false } @@ -114,7 +114,6 @@ all-features = true # defines the configuration attribute `docsrs` rustdoc-args = ["--cfg", "docsrs"] cargo-args = ["--features='mermaid_docs'"] - # See https://doc.rust-lang.org/cargo/reference/profiles.html for more info. # [profile.release] # Do not perform backtrace for panic on release builds. diff --git a/flake.nix b/flake.nix index ddfdabd2..947a2386 100644 --- a/flake.nix +++ b/flake.nix @@ -23,38 +23,37 @@ nixpkgs, rust-overlay, } @ inputs: - flake-utils.lib.eachDefaultSystem (system: - let + flake-utils.lib.eachDefaultSystem ( + system: let overlays = [ devshell.overlays.default (import rust-overlay) ]; - pkgs = import nixpkgs { inherit system overlays; }; - unstable = import nixos-unstable { inherit system overlays; }; + pkgs = import nixpkgs {inherit system overlays;}; + unstable = import nixos-unstable {inherit system overlays;}; - rust-toolchain = - (pkgs.rust-bin.fromRustupToolchainFile ./rust-toolchain.toml).override { - extensions = [ - "cargo" - "clippy" - "llvm-tools-preview" - "rust-src" - "rust-std" - "rustfmt" - ]; + rust-toolchain = (pkgs.rust-bin.fromRustupToolchainFile ./rust-toolchain.toml).override { + extensions = [ + "cargo" + "clippy" + "llvm-tools-preview" + "rust-src" + "rust-std" + "rustfmt" + ]; - targets = [ - "aarch64-apple-darwin" - "x86_64-apple-darwin" + targets = [ + "aarch64-apple-darwin" + "x86_64-apple-darwin" - "x86_64-unknown-linux-musl" - "aarch64-unknown-linux-musl" + "x86_64-unknown-linux-musl" + "aarch64-unknown-linux-musl" - "wasm32-unknown-unknown" - "wasm32-wasi" - ]; - }; + "wasm32-unknown-unknown" + "wasm32-wasi" + ]; + }; format-pkgs = with pkgs; [ nixpkgs-fmt @@ -87,7 +86,6 @@ cargo = "${pkgs.cargo}/bin/cargo"; node = "${unstable.nodejs_20}/bin/node"; wasm-pack = "${pkgs.wasm-pack}/bin/wasm-pack"; - in rec { devShells.default = pkgs.devshell.mkShell { name = "rs-ucan"; From a5b5434c3be028e73afe7e023c1ffc40cdb4f160 Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Tue, 23 Jan 2024 22:22:39 -0800 Subject: [PATCH 010/188] Splitting out separate files --- Cargo.toml | 3 +- src/ability.rs | 4 + src/ability/any.rs | 13 ++ src/ability/crud.rs | 133 ++++++++++++ src/ability/msg.rs | 181 ++++++++++++++++ src/ability/traits.rs | 9 + src/condition.rs | 26 +++ src/delegation.rs | 20 ++ src/invocation.rs | 41 ++++ src/lib.rs | 484 +----------------------------------------- src/promise.rs | 12 ++ src/prove.rs | 33 +++ src/receipt.rs | 19 ++ src/time.rs | 45 ++++ 14 files changed, 546 insertions(+), 477 deletions(-) create mode 100644 src/ability.rs create mode 100644 src/ability/any.rs create mode 100644 src/ability/crud.rs create mode 100644 src/ability/msg.rs create mode 100644 src/ability/traits.rs create mode 100644 src/condition.rs create mode 100644 src/delegation.rs create mode 100644 src/invocation.rs create mode 100644 src/promise.rs create mode 100644 src/prove.rs create mode 100644 src/receipt.rs diff --git a/Cargo.toml b/Cargo.toml index e3eba69c..21422fa8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -35,6 +35,7 @@ async-trait = "0.1.73" blst = { version = "0.3.11", optional = true, default-features = false } cfg-if = "0.1" cid = "0.10.1" +did_url = "0.1" downcast-rs = "1.2.0" dyn-clone = "1.0.14" ecdsa = { version = "0.16.8", optional = true, default-features = false } @@ -58,7 +59,7 @@ signature = { version = "2.1.0", features = ["alloc"] } thiserror = "1.0" tracing = "0.1.40" unsigned-varint = "0.7.2" -url = "2.4.1" +url = "2.5" void = "1.0" web-time = "0.2.3" diff --git a/src/ability.rs b/src/ability.rs new file mode 100644 index 00000000..0ffcc9fd --- /dev/null +++ b/src/ability.rs @@ -0,0 +1,4 @@ +pub mod any; +pub mod crud; +pub mod msg; +pub mod traits; diff --git a/src/ability/any.rs b/src/ability/any.rs new file mode 100644 index 00000000..b2ba7533 --- /dev/null +++ b/src/ability/any.rs @@ -0,0 +1,13 @@ +use crate::prove::TryProve; +use void::Void; + +pub struct DelegateAny; + +impl TryProve for T { + type Error = Void; + type Proven = T; + + fn try_prove<'a>(&'a self, _proof: &'a DelegateAny) -> Result<&'a Self::Proven, Void> { + Ok(self) + } +} diff --git a/src/ability/crud.rs b/src/ability/crud.rs new file mode 100644 index 00000000..8eac2115 --- /dev/null +++ b/src/ability/crud.rs @@ -0,0 +1,133 @@ +use crate::{promise::Promise, prove::TryProve}; +use std::{collections::BTreeMap, fmt::Debug}; +use url::Url; + +#[derive(Debug, Clone, PartialEq)] +pub enum Field +where + T: Debug + Clone + PartialEq, +{ + Value(T), + Await(Promise), +} + +// FIXME macro to derive promise versions & delagted builder versions +// ... also maybe Ipld + +pub struct Crud { + uri: Field, +} + +pub struct CrudRead { + pub uri: Field, +} + +pub struct CrudMutate { + uri: Field, +} + +pub struct CrudCreate { + pub uri: Field, + pub args: BTreeMap, Field>, +} + +pub struct CrudUpdate { + pub uri: Field, + pub args: BTreeMap, Field>, +} + +pub struct CrudDestroy { + pub uri: Field, +} + +// FIXME these should probably be behind a feature flag + +// impl Capabilty for CrudRead{ +// const COMMAND = "crud/read"; +// +// fn subject(&self) -> Did { +// todo!() +// } +// } + +// impl TryProve for CrudDestroy { +// type Error = (); // FIXME +// type Proven = CrudDestroy; +// fn try_prove<'a>(&'a self, candidate: &'a CrudDestroy) -> Result<&'a Self::Proven, ()> { +// if self.uri == candidate.uri { +// Ok(self) +// } else { +// Err(()) +// } +// } +// } +// +// // FIXME ProveWith? +// impl TryProve for CrudDestroy { +// type Error = (); // FIXME +// type Proven = CrudDestroy; +// +// fn try_prove<'a>(&'a self, candidate: &'a CrudMutate) -> Result<&'a Self::Proven, ()> { +// if self.uri == candidate.uri { +// Ok(self) +// } else { +// Err(()) +// } +// } +// } +// +// impl TryProve for CrudRead { +// type Error = (); +// type Proven = CrudRead; +// +// fn try_prove<'a>(&'a self, candidate: &'a CrudRead) -> Result<&'a Self::Proven, ()> { +// if self.uri == candidate.uri { +// // FIXME contains & args +// Ok(self) +// } else { +// Err(()) +// } +// } +// } +// +// impl TryProve for CrudRead { +// type Error = (); // FIXME +// type Proven = CrudRead; +// +// fn try_prove<'a>(&'a self, candidate: &'a Crud) -> Result<&'a Self::Proven, ()> { +// if self.uri == candidate.uri { +// Ok(self) +// } else { +// Err(()) +// } +// } +// } +// +// impl TryProve for CrudMutate { +// type Error = (); // FIXME +// type Proven = CrudMutate; +// +// fn try_prove<'a>(&'a self, candidate: &'a Crud) -> Result<&'a Self::Proven, ()> { +// if self.uri == candidate.uri { +// Ok(self) +// } else { +// Err(()) +// } +// } +// } +// +// // FIXME +// impl> TryProve for C { +// type Error = (); +// type Proven = C; +// +// // FIXME +// fn try_prove<'a>(&'a self, candidate: &'a Crud) -> Result<&'a C, ()> { +// match self.try_prove(&CrudMutate { +// uri: candidate.uri.clone(), +// }) { +// Ok(_) => Ok(self), +// Err(_) => Err(()), +// } +// } +// } diff --git a/src/ability/msg.rs b/src/ability/msg.rs new file mode 100644 index 00000000..f2167dd7 --- /dev/null +++ b/src/ability/msg.rs @@ -0,0 +1,181 @@ +use crate::{ability::traits::Ability, prove::TryProve}; +use libipld_core::ipld::Ipld; +use std::{collections::BTreeMap, str::FromStr}; +use url::Url; + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct Msg { + to: Url, + from: Url, +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct MsgSend { + to: Url, + from: Url, + message: String, +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct MsgReceive { + to: Url, + from: Url, +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct MsgReceiveBuilder { + to: Option, + from: Option, +} + +impl From for MsgReceiveBuilder { + fn from(msg: MsgReceive) -> Self { + Self { + to: Some(msg.to), + from: Some(msg.from), + } + } +} + +impl TryFrom for MsgReceive { + type Error = MsgReceiveBuilder; + + fn try_from(builder: MsgReceiveBuilder) -> Result { + // FIXME + if let (Some(to), Some(from)) = (builder.clone().to, builder.clone().from) { + Ok(Self { to, from }) + } else { + Err(builder.clone()) // FIXME + } + } +} + +impl From for Ipld { + fn from(msg: MsgReceive) -> Self { + let mut map = BTreeMap::new(); + map.insert("to".into(), msg.to.to_string().into()); + map.insert("from".into(), msg.from.to_string().into()); + map.into() + } +} + +impl TryFrom<&Ipld> for MsgReceiveBuilder { + type Error = (); + + fn try_from(ipld: &Ipld) -> Result { + match ipld { + Ipld::Map(map) => { + if map.len() > 2 { + return Err(()); // FIXME + } + + // FIXME + let to = if let Some(Ipld::String(to)) = map.get("to") { + Url::from_str(to).ok() // FIXME + } else { + None + }; + + let from = if let Some(Ipld::String(from)) = map.get("from") { + Url::from_str(from).ok() // FIXME + } else { + None + }; + + Ok(Self { to, from }) + } + _ => Err(()), + } + } +} + +impl Ability for MsgReceive { + type Builder = MsgReceiveBuilder; + const COMMAND: &'static str = "msg/receive"; +} + +impl TryFrom<&Ipld> for MsgReceive { + type Error = (); // FIXME + + fn try_from(ipld: &Ipld) -> Result { + match ipld { + Ipld::Map(map) => { + if map.len() > 2 { + return Err(()); // FIXME + } + + // FIXME + let to = if let Some(Ipld::String(to)) = map.get("to") { + Url::from_str(to).ok() // FIXME + } else { + None + }; + + let from = if let Some(Ipld::String(from)) = map.get("from") { + Url::from_str(from).ok() // FIXME + } else { + None + }; + + Ok(Self { + to: to.unwrap(), + from: from.unwrap(), + }) + } + _ => Err(()), + } + } +} + +impl TryProve for Msg { + type Error = (); // FIXME + type Proven = Msg; + + fn try_prove<'a>(&'a self, candidate: &'a Msg) -> Result<&'a Self::Proven, ()> { + if self == candidate { + Ok(self) + } else { + Err(()) + } + } +} + +impl TryProve for MsgSend { + type Error = (); // FIXME + type Proven = MsgSend; + + fn try_prove<'a>(&'a self, candidate: &'a Msg) -> Result<&'a Self::Proven, ()> { + if self.to == candidate.to && self.from == candidate.from { + Ok(self) + } else { + Err(()) + } + } +} + +impl TryProve for MsgReceive { + type Error = (); // FIXME + type Proven = MsgReceive; + + fn try_prove<'a>(&'a self, candidate: &'a Msg) -> Result<&'a Self::Proven, ()> { + if self.to == candidate.to && self.from == candidate.from { + Ok(self) + } else { + Err(()) + } + } +} + +// FIXME this needs to work on builders! +impl TryProve for MsgReceive { + type Error = (); // FIXME + type Proven = MsgReceive; + + fn try_prove<'a>(&'a self, candidate: &'a MsgReceive) -> Result<&'a Self::Proven, ()> { + if self == candidate { + Ok(self) + } else { + Err(()) + } + } +} diff --git a/src/ability/traits.rs b/src/ability/traits.rs new file mode 100644 index 00000000..630a9ce0 --- /dev/null +++ b/src/ability/traits.rs @@ -0,0 +1,9 @@ +use std::fmt::Debug; + +pub trait Ability: Sized { + // pub trait Capability: Into { + // FIXME remove sized? + // pub trait Capability: TryFrom + Into { + type Builder: From + TryInto + PartialEq + Debug; // FIXME + const COMMAND: &'static str; +} diff --git a/src/condition.rs b/src/condition.rs new file mode 100644 index 00000000..cd627be1 --- /dev/null +++ b/src/condition.rs @@ -0,0 +1,26 @@ +use libipld_core::ipld::Ipld; + +pub struct FieldName { + name: Box, +} + +pub enum Condition { + Contains { field: FieldName, value: Vec }, + MinLength { field: FieldName, value: u64 }, + MaxLength { field: FieldName, value: u64 }, + Equals { field: FieldName, value: Ipld }, + Regex { field: FieldName }, // FIXME + + // Silly example + OnDayOfWeek { day: Day }, +} + +pub enum Day { + Monday, + Tuesday, + Wednesday, + Thursday, + Friday, + Saturday, + Sunday, +} diff --git a/src/delegation.rs b/src/delegation.rs new file mode 100644 index 00000000..7547dcd3 --- /dev/null +++ b/src/delegation.rs @@ -0,0 +1,20 @@ +use crate::{ability::traits::Ability, time::Timestamp}; +use did_url::DID; +use libipld_core::ipld::Ipld; +use std::collections::BTreeMap; + +#[derive(Debug, Clone, PartialEq)] +pub struct Payload { + pub issuer: DID, + pub subject: DID, + pub audience: DID, + + pub capability_builder: A::Builder, // FIXME + pub conditions: Box<[Cond]>, // Worth it over a Vec? + + pub metadata: BTreeMap, Ipld>, // FIXME serde value instead? + pub nonce: Box<[u8]>, // Better type? + + pub expiration: Timestamp, + pub not_before: Option, +} diff --git a/src/invocation.rs b/src/invocation.rs new file mode 100644 index 00000000..26c50a07 --- /dev/null +++ b/src/invocation.rs @@ -0,0 +1,41 @@ +use crate::{ability::traits::Ability, delegation, time::Timestamp}; +use did_url::DID; +use libipld_core::ipld::Ipld; +use std::collections::BTreeMap; + +#[derive(Debug, Clone, PartialEq)] +pub struct Payload { + pub issuer: DID, + pub subject: DID, + pub audience: Option, + + pub ability: Ability, // FIXME check name in spec + + // pub proofs: Vec>>, + // pub cause: Option>>, // FIXME? + pub metadata: BTreeMap, Ipld>, // FIXME serde value instead? + pub nonce: Box<[u8]>, // Better type? + + pub expiration: Timestamp, + pub not_before: Option, +} + +// FIXME move that clone? +impl From> for delegation::Payload { + fn from(invocation: Payload) -> Self { + Self { + issuer: invocation.issuer.clone(), + subject: invocation.subject.clone(), + audience: invocation + .audience + .clone() + .unwrap_or(invocation.issuer.clone()), + capability_builder: >::into(invocation.ability.clone()), + conditions: Box::new([]), + metadata: invocation.metadata.clone(), + nonce: invocation.nonce.clone(), + expiration: invocation.expiration.clone(), + not_before: invocation.not_before.clone(), + } + } +} diff --git a/src/lib.rs b/src/lib.rs index 75d898c6..773867f2 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -36,62 +36,7 @@ pub const DEFAULT_MULTIHASH: multihash::Code = multihash::Code::Sha2_256; /// A decentralized identifier. pub type Did = String; -use libipld_core::{ipld::Ipld, link::Link}; -use std::{collections::BTreeMap, fmt::Debug}; -use url::Url; -use void::Void; -use web_time::{Instant, SystemTime}; - -pub struct InvocationPayload { - pub issuer: Did, - pub subject: Did, - pub audience: Option, - - pub ability: Ability, // FIXME check name in spec - - // pub proofs: Vec>>, - // pub cause: Option>>, // FIXME? - pub metadata: BTreeMap, Ipld>, // FIXME serde value instead? - pub nonce: Box<[u8]>, // Better type? - - pub expiration: Timestamp, - pub not_before: Option, -} - -pub struct DelegationPayload { - pub issuer: Did, - pub subject: Did, - pub audience: Did, - - pub capability_builder: A::Builder, // FIXME - pub conditions: Box<[Cond]>, // Worth it over a Vec? - - pub metadata: BTreeMap, Ipld>, // FIXME serde value instead? - pub nonce: Box<[u8]>, // Better type? - - pub expiration: Timestamp, - pub not_before: Option, -} - -// FIXME move that clone? -impl From> for DelegationPayload { - fn from(invocation: InvocationPayload) -> Self { - Self { - issuer: invocation.issuer.clone(), - subject: invocation.subject.clone(), - audience: invocation - .audience - .clone() - .unwrap_or(invocation.issuer.clone()), - capability_builder: >::into(invocation.ability.clone()), - conditions: Box::new([]), - metadata: invocation.metadata.clone(), - nonce: invocation.nonce.clone(), - expiration: invocation.expiration.clone(), - not_before: invocation.not_before.clone(), - } - } -} +use std::fmt::Debug; // impl DelegationPayload { // fn check( @@ -114,30 +59,6 @@ impl From> for DelegationPaylo // } // } -// FIXME add is_roo t - -pub trait TryProven { - type Proven1; - type Error1; - fn try_proven<'a>(&'a self, candidate: &'a A) -> Result<&'a Self::Proven1, Self::Error1>; -} - -impl TryProven for U -where - T: TryProve, -{ - type Proven1 = T::Proven; - type Error1 = T::Error; - - fn try_proven<'a>(&'a self, candidate: &'a T) -> Result<&'a T::Proven, T::Error> { - candidate.try_prove(self) - } -} - -pub struct DelegateAny; - -// FIXME ToBuilder - // FIXME Remove // pub trait Prove { // type Witness; @@ -146,15 +67,6 @@ pub struct DelegateAny; // fn prove<'a>(&'a self, proof: &'a T) -> &Self::Witness; // } -impl TryProve for T { - type Error = Void; - type Proven = T; - - fn try_prove<'a>(&'a self, _proof: &'a DelegateAny) -> Result<&'a Self::Proven, Void> { - Ok(self) - } -} - // impl Prove for T { // type Witness = T; // @@ -172,270 +84,6 @@ impl TryProve for T { // } // } -#[cfg_attr(doc, aquamarine::aquamarine)] -/// FIXME -/// -/// ```mermaid -/// flowchart LR -/// Invocation --> more --> Self --> Candidate --> more2 -/// more[...] -/// more2[...] -/// ``` -pub trait TryProve { - type Error; - type Proven; - - fn try_prove<'a>(&'a self, candidate: &'a T) -> Result<&'a Self::Proven, Self::Error>; -} - -///////////// - -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct Msg { - to: Url, - from: Url, -} - -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct MsgSend { - to: Url, - from: Url, - message: String, -} - -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct MsgReceive { - to: Url, - from: Url, -} - -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct MsgReceiveBuilder { - to: Option, - from: Option, -} - -impl From for MsgReceiveBuilder { - fn from(msg: MsgReceive) -> Self { - Self { - to: Some(msg.to), - from: Some(msg.from), - } - } -} - -impl TryFrom for MsgReceive { - type Error = MsgReceiveBuilder; - - fn try_from(builder: MsgReceiveBuilder) -> Result { - // FIXME - if let (Some(to), Some(from)) = (builder.clone().to, builder.clone().from) { - Ok(Self { to, from }) - } else { - Err(builder.clone()) // FIXME - } - } -} - -impl From for Ipld { - fn from(msg: MsgReceive) -> Self { - let mut map = BTreeMap::new(); - map.insert("to".into(), msg.to.to_string().into()); - map.insert("from".into(), msg.from.to_string().into()); - map.into() - } -} - -impl TryFrom<&Ipld> for MsgReceiveBuilder { - type Error = (); - - fn try_from(ipld: &Ipld) -> Result { - match ipld { - Ipld::Map(map) => { - if map.len() > 2 { - return Err(()); // FIXME - } - - // FIXME - let to = if let Some(Ipld::String(to)) = map.get("to") { - Url::from_str(to).ok() // FIXME - } else { - None - }; - - let from = if let Some(Ipld::String(from)) = map.get("to") { - Url::from_str(from).ok() // FIXME - } else { - None - }; - - Ok(Self { to, from }) - } - _ => Err(()), - } - } -} - -impl Capability for MsgReceive { - type Builder = MsgReceiveBuilder; - const COMMAND: &'static str = "msg/receive"; -} - -impl TryFrom<&Ipld> for MsgReceive { - type Error = (); // FIXME - - fn try_from(ipld: &Ipld) -> Result { - todo!() - } -} - -impl TryProve for Msg { - type Error = (); // FIXME - type Proven = Msg; - - fn try_prove<'a>(&'a self, candidate: &'a Msg) -> Result<&'a Self::Proven, ()> { - if self == candidate { - Ok(self) - } else { - Err(()) - } - } -} - -impl TryProve for MsgSend { - type Error = (); // FIXME - type Proven = MsgSend; - - fn try_prove<'a>(&'a self, candidate: &'a Msg) -> Result<&'a Self::Proven, ()> { - if self.to == candidate.to && self.from == candidate.from { - Ok(self) - } else { - Err(()) - } - } -} - -impl TryProve for MsgReceive { - type Error = (); // FIXME - type Proven = MsgReceive; - - fn try_prove<'a>(&'a self, candidate: &'a Msg) -> Result<&'a Self::Proven, ()> { - if self.to == candidate.to && self.from == candidate.from { - Ok(self) - } else { - Err(()) - } - } -} - -// FIXME this needs to work on builders! -impl TryProve for MsgReceive { - type Error = (); // FIXME - type Proven = MsgReceive; - - fn try_prove<'a>(&'a self, candidate: &'a MsgReceive) -> Result<&'a Self::Proven, ()> { - if self == candidate { - Ok(self) - } else { - Err(()) - } - } -} - -/////////// - -// FIXME remove -// impl> TryProve for P { -// type Error = Void; -// type Proven = P::Witness; -// -// fn try_prove<'a>(&'a self, candidate: &'a T) -> Result<&'a Self::Proven, Void> { -// Ok(self.prove(candidate)) -// } -// } - -impl TryProve for CrudDestroy { - type Error = (); // FIXME - type Proven = CrudDestroy; - fn try_prove<'a>(&'a self, candidate: &'a CrudDestroy) -> Result<&'a Self::Proven, ()> { - if self.uri == candidate.uri { - Ok(self) - } else { - Err(()) - } - } -} - -// FIXME ProveWith? -impl TryProve for CrudDestroy { - type Error = (); // FIXME - type Proven = CrudDestroy; - - fn try_prove<'a>(&'a self, candidate: &'a CrudMutate) -> Result<&'a Self::Proven, ()> { - if self.uri == candidate.uri { - Ok(self) - } else { - Err(()) - } - } -} - -impl TryProve for CrudRead { - type Error = (); - type Proven = CrudRead; - - fn try_prove<'a>(&'a self, candidate: &'a CrudRead) -> Result<&'a Self::Proven, ()> { - if self.uri == candidate.uri { - // FIXME contains & args - Ok(self) - } else { - Err(()) - } - } -} - -impl TryProve for CrudRead { - type Error = (); // FIXME - type Proven = CrudRead; - - fn try_prove<'a>(&'a self, candidate: &'a Crud) -> Result<&'a Self::Proven, ()> { - if self.uri == candidate.uri { - Ok(self) - } else { - Err(()) - } - } -} - -impl TryProve for CrudMutate { - type Error = (); // FIXME - type Proven = CrudMutate; - - fn try_prove<'a>(&'a self, candidate: &'a Crud) -> Result<&'a Self::Proven, ()> { - if self.uri == candidate.uri { - Ok(self) - } else { - Err(()) - } - } -} - -// FIXME -impl> TryProve for C { - type Error = (); - type Proven = C; - - // FIXME - fn try_prove<'a>(&'a self, candidate: &'a Crud) -> Result<&'a C, ()> { - match self.try_prove(&CrudMutate { - uri: candidate.uri.clone(), - }) { - Ok(_) => Ok(self), - Err(_) => Err(()), - } - } -} - // FIXME lives etirely in bindgen // https://rustwasm.github.io/docs/wasm-bindgen/contributing/design/importing-js-struct.html // pub struct DynamicJs { @@ -461,129 +109,13 @@ impl> TryProve for C { // } // -pub struct Crud { - uri: Url, -} - -pub struct CrudMutate { - uri: Url, -} - -pub struct CrudCreate { - pub uri: Url, - pub args: BTreeMap, String>, -} - -pub struct CrudRead { - pub uri: Url, - pub args: BTreeMap, String>, // FIXME need these? -} - -pub struct CrudUpdate { - pub uri: Url, - pub args: BTreeMap, String>, -} - -pub struct CrudDestroy { - pub uri: Url, -} - -// impl Capabilty for CrudRead{ -// const COMMAND = "crud/read"; -// -// fn subject(&self) -> Did { -// todo!() -// } -// } -// -// pub enum Condition { -// Contains { field: &str, value: Vec }, -// MinLength { field: &str, value: u64 }, -// MaxLength { field: &str, value: u64 }, -// Equals { field: &str, value: Ipld }, -// Regex { field: &str }, // FIXME -// -// // Silly example -// OnDayOfWeek { day: Day }, -// } - -pub enum Day { - Monday, - Tuesday, - Wednesday, - Thursday, - Friday, - Saturday, - Sunday, -} - -// pub trait CapBuilder: Default { -// type Ability; -// fn build(self) -> Result; -// } - -// pub trait BuilderFor: CapBuilder { -// type Builder: CapBuilder; -// } - -pub trait Capability: Sized { - // pub trait Capability: Into { - // FIXME remove sized? - // pub trait Capability: TryFrom + Into { - type Builder: From + TryInto + PartialEq + Debug; - const COMMAND: &'static str; -} - -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct JsTime { - time: SystemTime, -} - -// FIXME just lifting this from Elixir for now -pub struct OutOfRangeError { - pub tried: SystemTime, -} - -impl JsTime { - // FIXME value should be a system time? - pub fn new(time: SystemTime) -> Result { - if time - .duration_since(std::time::UNIX_EPOCH) - .expect("FIXME") - .as_secs() - > 0x1FFFFFFFFFFFFF - { - Err(OutOfRangeError { tried: time }) - } else { - Ok(JsTime { time }) - } - } -} - -#[derive(Debug, Clone, PartialEq)] -pub enum Timestamp { - Sending(JsTime), - Receiving(SystemTime), -} - -// pub struct ReceiptPayload { -// pub issuer: Did, -// pub ran: Link>, -// pub out: UcanResult, // FIXME? -// pub proofs: Vec>>, -// pub metadata: BTreeMap, // FIXME serde value instead? -// pub issued_at: u64, -// } -// -// pub enum UcanResult { -// UcanOk(T), -// UcanErr(BTreeMap<&str, Ipld>), -// } -// -// pub struct Capability { -// command: String, -// payload: BTreeMap, -// } +pub mod ability; +pub mod condition; +pub mod delegation; +pub mod invocation; +pub mod promise; +pub mod prove; +pub mod receipt; ////////////////////////////////////// ////////////////////////////////////// diff --git a/src/promise.rs b/src/promise.rs new file mode 100644 index 00000000..f7da8315 --- /dev/null +++ b/src/promise.rs @@ -0,0 +1,12 @@ +use libipld_core::link::Link; +use std::fmt::Debug; + +#[derive(Debug, Clone, PartialEq)] +pub enum Promise +where + T: Debug + Clone + PartialEq, +{ + PromiseOk(Link), + PromiseErr(Link), + PromiseAny(Link), +} diff --git a/src/prove.rs b/src/prove.rs new file mode 100644 index 00000000..fcee2ff6 --- /dev/null +++ b/src/prove.rs @@ -0,0 +1,33 @@ +#[cfg_attr(doc, aquamarine::aquamarine)] +/// FIXME +/// +/// ```mermaid +/// flowchart LR +/// Invocation --> more --> Self --> Candidate --> more2 +/// more[...] +/// more2[...] +/// ``` +pub trait TryProve { + type Error; + type Proven; + + fn try_prove<'a>(&'a self, candidate: &'a T) -> Result<&'a Self::Proven, Self::Error>; +} + +pub trait TryProven { + type Proven1; + type Error1; + fn try_proven<'a>(&'a self, candidate: &'a A) -> Result<&'a Self::Proven1, Self::Error1>; +} + +impl TryProven for U +where + T: TryProve, +{ + type Proven1 = T::Proven; + type Error1 = T::Error; + + fn try_proven<'a>(&'a self, candidate: &'a T) -> Result<&'a T::Proven, T::Error> { + candidate.try_prove(self) + } +} diff --git a/src/receipt.rs b/src/receipt.rs new file mode 100644 index 00000000..9c88aa8b --- /dev/null +++ b/src/receipt.rs @@ -0,0 +1,19 @@ +// pub struct ReceiptPayload { +// pub issuer: Did, +// pub ran: Link>, +// pub out: UcanResult, // FIXME? +// pub proofs: Vec>>, +// pub metadata: BTreeMap, // FIXME serde value instead? +// pub issued_at: u64, +// } + +// +// pub enum UcanResult { +// UcanOk(T), +// UcanErr(BTreeMap<&str, Ipld>), +// } +// +// pub struct Capability { +// command: String, +// payload: BTreeMap, +// } diff --git a/src/time.rs b/src/time.rs index e57163a0..27c52998 100644 --- a/src/time.rs +++ b/src/time.rs @@ -9,3 +9,48 @@ pub fn now() -> u64 { .unwrap() .as_secs() } + +#[derive(Debug, Clone, PartialEq)] +pub enum Timestamp { + // FIXME probably overkill, but overflows are bad. Need to check on ingestion, too + /// Per the spec, timestamps MUST respect [IEEE-754](https://en.wikipedia.org/wiki/IEEE_754) + /// (64-bit double precision = 53-bit truncated integer) for JavaScript interoperability. + /// + /// This range can represent millions of years into the future, + /// and is thus sufficient for nearly all use cases. + Sending(JsTime), + + /// Following [Postel's Law](https://en.wikipedia.org/wiki/Robustness_principle), + /// received timestamps may be parsed as regular [SystemTime] + Receiving(SystemTime), +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct JsTime { + time: SystemTime, +} + +// FIXME just lifting this from Elixir for now +pub struct OutOfRangeError { + pub tried: SystemTime, +} + +impl JsTime { + /// Create a [`JsTime`] from a [`SystemTime`] + /// + /// # Errors + /// + /// * [`OutOfRangeError`] — If the time is more than 2⁵³ seconds since the Unix epoch + pub fn new(time: SystemTime) -> Result { + if time + .duration_since(std::time::UNIX_EPOCH) + .expect("FIXME") + .as_secs() + > 0x1FFFFFFFFFFFFF + { + Err(OutOfRangeError { tried: time }) + } else { + Ok(JsTime { time }) + } + } +} From 6ec904cdbf7d143b5bd4d45bd09d59644dbc05dc Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Tue, 23 Jan 2024 23:21:24 -0800 Subject: [PATCH 011/188] Add Signature envelope and receipts --- src/ability/crud.rs | 2 +- src/ability/traits.rs | 3 ++- src/delegation.rs | 8 ++++++- src/invocation.rs | 8 ++++++- src/lib.rs | 1 + src/promise.rs | 49 ++++++++++++++++++++++++++++++++++++------- src/receipt.rs | 47 +++++++++++++++++++++++++---------------- src/signature.rs | 22 +++++++++++++++++++ 8 files changed, 110 insertions(+), 30 deletions(-) create mode 100644 src/signature.rs diff --git a/src/ability/crud.rs b/src/ability/crud.rs index 8eac2115..d1c7775d 100644 --- a/src/ability/crud.rs +++ b/src/ability/crud.rs @@ -8,7 +8,7 @@ where T: Debug + Clone + PartialEq, { Value(T), - Await(Promise), + Await(Promise), // FIXME } // FIXME macro to derive promise versions & delagted builder versions diff --git a/src/ability/traits.rs b/src/ability/traits.rs index 630a9ce0..7406a020 100644 --- a/src/ability/traits.rs +++ b/src/ability/traits.rs @@ -1,9 +1,10 @@ use std::fmt::Debug; pub trait Ability: Sized { - // pub trait Capability: Into { // FIXME remove sized? // pub trait Capability: TryFrom + Into { type Builder: From + TryInto + PartialEq + Debug; // FIXME const COMMAND: &'static str; } + +// FIXME macro for Delegation (builder) and Promises diff --git a/src/delegation.rs b/src/delegation.rs index 7547dcd3..16862bc7 100644 --- a/src/delegation.rs +++ b/src/delegation.rs @@ -1,8 +1,10 @@ -use crate::{ability::traits::Ability, time::Timestamp}; +use crate::{ability::traits::Ability, signature, time::Timestamp}; use did_url::DID; use libipld_core::ipld::Ipld; use std::collections::BTreeMap; +pub type Delegation = signature::Envelope>; + #[derive(Debug, Clone, PartialEq)] pub struct Payload { pub issuer: DID, @@ -18,3 +20,7 @@ pub struct Payload { pub expiration: Timestamp, pub not_before: Option, } + +impl signature::Capsule for Payload { + const TAG: &'static str = "ucan/d/1.0.0-rc.1"; +} diff --git a/src/invocation.rs b/src/invocation.rs index 26c50a07..bf5b228b 100644 --- a/src/invocation.rs +++ b/src/invocation.rs @@ -1,8 +1,10 @@ -use crate::{ability::traits::Ability, delegation, time::Timestamp}; +use crate::{ability::traits::Ability, delegation, signature, time::Timestamp}; use did_url::DID; use libipld_core::ipld::Ipld; use std::collections::BTreeMap; +pub type Invocation = signature::Envelope>; + #[derive(Debug, Clone, PartialEq)] pub struct Payload { pub issuer: DID, @@ -39,3 +41,7 @@ impl From> for delegation::Payload } } } + +impl signature::Capsule for Payload { + const TAG: &'static str = "ucan/i/1.0.0-rc.1"; +} diff --git a/src/lib.rs b/src/lib.rs index 773867f2..79e618bb 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -116,6 +116,7 @@ pub mod invocation; pub mod promise; pub mod prove; pub mod receipt; +pub mod signature; ////////////////////////////////////// ////////////////////////////////////// diff --git a/src/promise.rs b/src/promise.rs index f7da8315..37ed5bd0 100644 --- a/src/promise.rs +++ b/src/promise.rs @@ -1,12 +1,45 @@ -use libipld_core::link::Link; +use crate::{ability::traits::Ability, invocation, invocation::Invocation, signature::Capsule}; +use cid::Cid; +use libipld_core::{ipld::Ipld, link::Link}; use std::fmt::Debug; +// /// A [`Promise`] is a way to defer the presence of a value to the result of some [`Invocation`]. +// #[derive(Debug, Clone, PartialEq)] +// pub enum Promise +// where +// A: Ability, // FIXME MUST be an Invocation +// invocation::Payload: Capsule, +// { +// PromiseAny(Link>), // FIXME not sure about specifying the A here +// PromiseOk(Link>), +// PromiseErr(Link>), +// } + +/// A [`Promise`] is a way to defer the presence of a value to the result of some [`Invocation`]. #[derive(Debug, Clone, PartialEq)] -pub enum Promise -where - T: Debug + Clone + PartialEq, -{ - PromiseOk(Link), - PromiseErr(Link), - PromiseAny(Link), +pub enum Promise { + PromiseAny(Cid), // FIXME not sure about specifying the A here + PromiseOk(Cid), + PromiseErr(Cid), } + +// impl TryFrom for Promise +// where +// invocation::Payload: Capsule, +// { +// type Error = (); // FIXME +// +// fn try_from(ipld: Ipld) -> Result { +// if let Ipld::Map(btree) = ipld { +// if let Some(Ipld::Link(link)) = btree.get("await/ok") { +// return Ok(Self::PromiseOk(link.clone().into())); +// } else if let Some(Ipld::Link(link)) = btree.get("await/err") { +// return Ok(Self::PromiseErr(link.clone().into())); +// } else if let Some(Ipld::Link(link)) = btree.get("await/*") { +// return Ok(Self::PromiseAny(link.clone().into())); +// } +// } +// +// Err(()) +// } +// } diff --git a/src/receipt.rs b/src/receipt.rs index 9c88aa8b..7ca0aab1 100644 --- a/src/receipt.rs +++ b/src/receipt.rs @@ -1,19 +1,30 @@ -// pub struct ReceiptPayload { -// pub issuer: Did, -// pub ran: Link>, -// pub out: UcanResult, // FIXME? -// pub proofs: Vec>>, -// pub metadata: BTreeMap, // FIXME serde value instead? -// pub issued_at: u64, -// } +use crate::{ + ability::traits::Ability, delegation::Delegation, invocation::Invocation, signature, + time::Timestamp, +}; +use did_url::DID; +use libipld_core::{ipld::Ipld, link::Link}; +use std::collections::BTreeMap; -// -// pub enum UcanResult { -// UcanOk(T), -// UcanErr(BTreeMap<&str, Ipld>), -// } -// -// pub struct Capability { -// command: String, -// payload: BTreeMap, -// } +pub type Receipt = signature::Envelope>; + +pub struct Payload +where + T: Ability, +{ + pub issuer: DID, + pub ran: Link>, + pub out: UcanResult, // FIXME? + pub proofs: Vec>>, + pub metadata: BTreeMap, + pub issued_at: Option, +} + +pub enum UcanResult { + UcanOk(T), + UcanErr(BTreeMap), +} + +impl signature::Capsule for Payload { + const TAG: &'static str = "ucan/r/1.0.0-rc.1"; // FIXME extract out versioh +} diff --git a/src/signature.rs b/src/signature.rs new file mode 100644 index 00000000..61be0815 --- /dev/null +++ b/src/signature.rs @@ -0,0 +1,22 @@ +use libipld_core::link::Link; + +pub struct Envelope +where + T: Capsule, +{ + pub sig: Box<[u8]>, + pub payload: Link, +} + +pub enum Signature { + One(Box<[u8]>), + Batch { + sig: Box<[u8]>, + merkle_proof: Box<[u32]>, + }, +} + +// TODO move to own module +pub trait Capsule { + const TAG: &'static str; +} From fb8336fcd3d51ecfcdd881621a4b394af8649b49 Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Wed, 24 Jan 2024 15:37:53 -0800 Subject: [PATCH 012/188] More exploration --- .github/workflows/bench.yml | 2 +- .github/workflows/coverage.yml | 2 +- CONTRIBUTING.md | 4 +- Cargo.toml | 6 +-- README.md | 24 +++++----- SECURITY.md | 6 +-- flake.nix | 4 +- package.json | 8 ++-- src/ability.rs | 4 ++ src/ability/any.rs | 5 +- src/ability/dynamic.rs | 78 ++++++++++++++++++++++++++++++++ src/ability/msg.rs | 35 +++++++++----- src/ability/traits.rs | 22 ++++++++- src/condition.rs | 14 ++---- src/delegation.rs | 83 ++++++++++++++++++++++++++++++---- src/error.rs | 2 +- src/invocation.rs | 42 +++++++++++------ src/ipld.rs | 48 ++++++++++++++++++++ src/lib.rs | 35 ++------------ src/prove.rs | 14 +++--- src/receipt.rs | 33 ++++++++------ src/signature.rs | 62 ++++++++++++++++++++++--- src/time.rs | 27 ++++++++++- 23 files changed, 425 insertions(+), 135 deletions(-) create mode 100644 src/ability/dynamic.rs create mode 100644 src/ipld.rs diff --git a/.github/workflows/bench.yml b/.github/workflows/bench.yml index 76052f66..e8a39b9e 100644 --- a/.github/workflows/bench.yml +++ b/.github/workflows/bench.yml @@ -51,7 +51,7 @@ jobs: tool: 'cargo' output-file-path: output.txt github-token: ${{ secrets.GITHUB_TOKEN }} - auto-push: ${{ github.event_name == 'push' && github.repository == 'ucan-wg/rs-ucan' && github.ref == 'refs/heads/main' }} + auto-push: ${{ github.event_name == 'push' && github.repository == 'ucan-wg/rs_ucan' && github.ref == 'refs/heads/main' }} alert-threshold: '200%' comment-on-alert: true fail-on-alert: true diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml index bbcf0c75..8197146b 100644 --- a/.github/workflows/coverage.yml +++ b/.github/workflows/coverage.yml @@ -33,7 +33,7 @@ jobs: - name: Generate Code coverage env: CARGO_INCREMENTAL: '0' - LLVM_PROFILE_FILE: "rs-ucan-%p-%m.profraw" + LLVM_PROFILE_FILE: "rs_ucan-%p-%m.profraw" RUSTFLAGS: '-Zprofile -Ccodegen-units=1 -Cinline-threshold=0 -Clink-dead-code -Coverflow-checks=off' RUSTDOCFLAGS: '-Zprofile -Ccodegen-units=1 -Cinline-threshold=0 -Clink-dead-code -Coverflow-checks=off' run: cargo test --all-features diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 2c6d8dcf..c8922c18 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,4 +1,4 @@ -# Contributing to rs-ucan +# Contributing to rs_ucan We welcome everyone to contribute what and where they can. Whether you are brand new, just want to contribute a little bit, or want to contribute a lot there is @@ -84,7 +84,7 @@ need to be the best programmer to contribute. Our discord is open for questions. - You can learn more about cloning repositories [here][git-clone]. 6. **Build** the project - - For a detailed look on how to build rs-ucan look at our + - For a detailed look on how to build rs_ucan look at our [README file](./README.md). 7. **Start writing** your code diff --git a/Cargo.toml b/Cargo.toml index 21422fa8..ce38108a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "rs-ucan" +name = "rs_ucan" version = "0.1.0" description = "Rust implementation of UCAN" keywords = ["capabilities", "authorization", "ucan"] @@ -9,8 +9,8 @@ license = "Apache-2.0" readme = "README.md" edition = "2021" rust-version = "1.75" -documentation = "https://docs.rs/rs-ucan" -repository = "https://github.com/ucan-wg/rs-ucan" +documentation = "https://docs.rs/rs_ucan" +repository = "https://github.com/ucan-wg/rs_ucan" authors = ["Quinn Wilton ", "Brooklyn Zelenka - - rs-ucan Logo + + rs_ucan Logo -

rs-ucan

+

rs_ucan

- - Crate + + Crate - - Code Coverage + + Code Coverage - - Build Status + + Build Status - + License - + Docs @@ -34,7 +34,7 @@ Add the following to the `[dependencies]` section of your `Cargo.toml` file: ```toml -rs-ucan = "1.0.0-rc.1" +rs_ucan = "1.0.0-rc.1" ``` ## Testing the Project diff --git a/SECURITY.md b/SECURITY.md index 777f0214..3d620e8e 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -1,6 +1,6 @@ ## Report a security issue or vulnerability -The rs-ucan team welcomes security reports and is committed to +The `rs_ucan` team welcomes security reports and is committed to providing prompt attention to security issues. Security issues should be reported privately via [quinn@fission.codes][support-email]. Security issues should not be reported via the public GitHub Issue tracker. @@ -8,10 +8,10 @@ not be reported via the public GitHub Issue tracker. ## Security advisories The project team is committed to transparency in the security issue disclosure -process. The rs-ucan team announces security advisories through our +process. The rs_ucan team announces security advisories through our Github respository's [security portal][sec-advisories] and and the [RustSec advisory database][rustsec-db]. [rustsec-db]: https://github.com/RustSec/advisory-db -[sec-advisories]: https://github.com/ucan-wg/rs-ucan/security/advisories +[sec-advisories]: https://github.com/ucan-wg/rs_ucan/security/advisories [support-email]: mailto:quinn@fission.codes diff --git a/flake.nix b/flake.nix index 947a2386..70e1f16c 100644 --- a/flake.nix +++ b/flake.nix @@ -1,5 +1,5 @@ { - description = "rs-ucan"; + description = "rs_ucan"; inputs = { nixpkgs.url = "nixpkgs/nixos-23.11"; @@ -88,7 +88,7 @@ wasm-pack = "${pkgs.wasm-pack}/bin/wasm-pack"; in rec { devShells.default = pkgs.devshell.mkShell { - name = "rs-ucan"; + name = "rs_ucan"; imports = [./pre-commit.nix]; diff --git a/package.json b/package.json index 26486d77..3e09a8bd 100644 --- a/package.json +++ b/package.json @@ -1,10 +1,10 @@ { "name": "ucan", "version": "0.1.0", - "description": "A UCAN library built from rs-ucan", + "description": "A UCAN library built from rs_ucan", "repository": { "type": "git", - "url": "git+https://github.com/fission-codes/rs-ucan.git" + "url": "git+https://github.com/fission-codes/rs_ucan.git" }, "keywords": [ "authorization" @@ -12,9 +12,9 @@ "author": "", "license": "Apache-2.0", "bugs": { - "url": "https://github.com/fission-codes/rs-ucan/issues" + "url": "https://github.com/fission-codes/rs_ucan/issues" }, - "homepage": "https://github.com/fission-codes/rs-ucan#readme", + "homepage": "https://github.com/fission-codes/rs_ucan#readme", "module": "dist/bundler/rs_ucan.js", "types": "dist/nodejs/rs_ucan.d.ts", "exports": { diff --git a/src/ability.rs b/src/ability.rs index 0ffcc9fd..0b896ac0 100644 --- a/src/ability.rs +++ b/src/ability.rs @@ -2,3 +2,7 @@ pub mod any; pub mod crud; pub mod msg; pub mod traits; + +// TODO move to crate::wasm? +#[cfg(feature = "wasm")] +pub mod dynamic; diff --git a/src/ability/any.rs b/src/ability/any.rs index b2ba7533..ac3e4559 100644 --- a/src/ability/any.rs +++ b/src/ability/any.rs @@ -1,13 +1,14 @@ use crate::prove::TryProve; use void::Void; +#[derive(Debug, Clone, Copy, Eq, PartialEq)] pub struct DelegateAny; -impl TryProve for T { +impl<'a, T> TryProve<'a, DelegateAny> for T { type Error = Void; type Proven = T; - fn try_prove<'a>(&'a self, _proof: &'a DelegateAny) -> Result<&'a Self::Proven, Void> { + fn try_prove(&'a self, _proof: &'a DelegateAny) -> Result<&'a Self::Proven, Void> { Ok(self) } } diff --git a/src/ability/dynamic.rs b/src/ability/dynamic.rs new file mode 100644 index 00000000..7f60a9e5 --- /dev/null +++ b/src/ability/dynamic.rs @@ -0,0 +1,78 @@ +//! This module is for dynamic abilities, especially for Wasm support + +use super::traits::{Ability, Command}; +use crate::prove::TryProve; +use libipld_core::ipld::Ipld; +use std::collections::BTreeMap; + +use crate::ipld::WrappedIpld; + +use js_sys; +use wasm_bindgen::prelude::*; + +#[derive(Debug, Clone, PartialEq)] +pub struct Dynamic<'a> { + pub command: String, + pub args: BTreeMap, // FIXME consider this being just JsValue + pub validater: &'a js_sys::Function, +} + +#[derive(Debug, Clone, PartialEq)] +pub struct DynamicBuilder<'a> { + pub command: String, + pub args: Option>, + pub validater: &'a js_sys::Function, +} + +impl<'a> From> for DynamicBuilder<'a> { + fn from(dynamic: Dynamic<'a>) -> Self { + Self { + command: dynamic.command.clone(), + args: Some(dynamic.args), + validater: dynamic.validater, + } + } +} + +impl<'a> TryFrom> for Dynamic<'a> { + type Error = (); // FIXME + + fn try_from(builder: DynamicBuilder) -> Result { + if let Some(args) = builder.clone().args { + Ok(Self { + command: builder.command.clone(), + args, + }) + } else { + Err(()) + } + } +} + +impl<'a> Command for Dynamic<'a> { + fn command(&self) -> &'static str { + self.command + } +} + +impl<'a> Command for DynamicBuilder<'a> { + fn command(&self) -> &'static str { + self.command + } +} + +impl<'a> Ability for Dynamic<'a> { + type Builder = DynamicBuilder<'a>; +} + +impl<'a> TryProve> for DynamicBuilder<'a> { + type Error = JsError; + type Proven = DynamicBuilder<'a>; // TODO docs: even if you parse a well-structred type, you MUST return a dynamic builder and continue checking that + + fn try_prove(&'a self, candidate: &'a DynamicBuilder) -> Result<&'a Self::Proven, ()> { + let js_self: JsValue = self.into().into(); + let js_candidate: JsValue = candidate.into().into(); + + self.validater.apply(js_self, js_candidate); + } +} diff --git a/src/ability/msg.rs b/src/ability/msg.rs index f2167dd7..b9e36086 100644 --- a/src/ability/msg.rs +++ b/src/ability/msg.rs @@ -1,4 +1,7 @@ -use crate::{ability::traits::Ability, prove::TryProve}; +use crate::{ + ability::traits::{Ability, Builder}, + prove::TryProve, +}; use libipld_core::ipld::Ipld; use std::{collections::BTreeMap, str::FromStr}; use url::Url; @@ -16,6 +19,7 @@ pub struct MsgSend { message: String, } +// TODO is the to or from often also the subject? Shoudl that be accounted for? #[derive(Debug, Clone, PartialEq, Eq)] pub struct MsgReceive { to: Url, @@ -89,9 +93,16 @@ impl TryFrom<&Ipld> for MsgReceiveBuilder { } } -impl Ability for MsgReceive { - type Builder = MsgReceiveBuilder; - const COMMAND: &'static str = "msg/receive"; +impl Builder for MsgReceiveBuilder { + type Concrete = MsgReceive; + + fn command(&self) -> &'static str { + "msg/receive" + } + + fn try_build(&self) -> Result { + self.try_build().map_err(|_| ()) // FIXME + } } impl TryFrom<&Ipld> for MsgReceive { @@ -127,11 +138,11 @@ impl TryFrom<&Ipld> for MsgReceive { } } -impl TryProve for Msg { +impl<'a> TryProve<'a, Msg> for Msg { type Error = (); // FIXME type Proven = Msg; - fn try_prove<'a>(&'a self, candidate: &'a Msg) -> Result<&'a Self::Proven, ()> { + fn try_prove(&'a self, candidate: &'a Msg) -> Result<&'a Self::Proven, ()> { if self == candidate { Ok(self) } else { @@ -140,11 +151,11 @@ impl TryProve for Msg { } } -impl TryProve for MsgSend { +impl<'a> TryProve<'a, Msg> for MsgSend { type Error = (); // FIXME type Proven = MsgSend; - fn try_prove<'a>(&'a self, candidate: &'a Msg) -> Result<&'a Self::Proven, ()> { + fn try_prove(&'a self, candidate: &'a Msg) -> Result<&'a Self::Proven, ()> { if self.to == candidate.to && self.from == candidate.from { Ok(self) } else { @@ -153,11 +164,11 @@ impl TryProve for MsgSend { } } -impl TryProve for MsgReceive { +impl<'a> TryProve<'a, Msg> for MsgReceive { type Error = (); // FIXME type Proven = MsgReceive; - fn try_prove<'a>(&'a self, candidate: &'a Msg) -> Result<&'a Self::Proven, ()> { + fn try_prove(&'a self, candidate: &'a Msg) -> Result<&'a Self::Proven, ()> { if self.to == candidate.to && self.from == candidate.from { Ok(self) } else { @@ -167,11 +178,11 @@ impl TryProve for MsgReceive { } // FIXME this needs to work on builders! -impl TryProve for MsgReceive { +impl<'a> TryProve<'a, MsgReceive> for MsgReceive { type Error = (); // FIXME type Proven = MsgReceive; - fn try_prove<'a>(&'a self, candidate: &'a MsgReceive) -> Result<&'a Self::Proven, ()> { + fn try_prove(&'a self, candidate: &'a MsgReceive) -> Result<&'a Self::Proven, ()> { if self == candidate { Ok(self) } else { diff --git a/src/ability/traits.rs b/src/ability/traits.rs index 7406a020..c7e8f662 100644 --- a/src/ability/traits.rs +++ b/src/ability/traits.rs @@ -1,10 +1,30 @@ use std::fmt::Debug; +// FIXME this is always a builder, right? pub trait Ability: Sized { // FIXME remove sized? // pub trait Capability: TryFrom + Into { type Builder: From + TryInto + PartialEq + Debug; // FIXME - const COMMAND: &'static str; + + // fn command(builder: &Self::Builder) -> &'static str; } +pub trait Builder { + type Concrete; + + fn command(&self) -> &'static str; + fn try_build(&self) -> Result; // FIXME +} + +// pub trait Builds1 { +// type B; +// } +// +// impl Builds1 for B::Concrete +// where +// B: Builder, +// { +// type B = B; +// } + // FIXME macro for Delegation (builder) and Promises diff --git a/src/condition.rs b/src/condition.rs index cd627be1..e4d27ec3 100644 --- a/src/condition.rs +++ b/src/condition.rs @@ -1,15 +1,11 @@ use libipld_core::ipld::Ipld; -pub struct FieldName { - name: Box, -} - pub enum Condition { - Contains { field: FieldName, value: Vec }, - MinLength { field: FieldName, value: u64 }, - MaxLength { field: FieldName, value: u64 }, - Equals { field: FieldName, value: Ipld }, - Regex { field: FieldName }, // FIXME + Contains { field: String, value: Vec }, + MinLength { field: String, value: u64 }, + MaxLength { field: String, value: u64 }, + Equals { field: String, value: Ipld }, + Regex { field: String }, // FIXME // Silly example OnDayOfWeek { day: Day }, diff --git a/src/delegation.rs b/src/delegation.rs index 16862bc7..9bfb625e 100644 --- a/src/delegation.rs +++ b/src/delegation.rs @@ -1,26 +1,93 @@ -use crate::{ability::traits::Ability, signature, time::Timestamp}; +use crate::{ + ability::{ + any::DelegateAny, + traits::{Ability, Builder}, + }, + signature, + time::Timestamp, +}; use did_url::DID; use libipld_core::ipld::Ipld; use std::collections::BTreeMap; -pub type Delegation = signature::Envelope>; +pub type Delegation = signature::Envelope>; #[derive(Debug, Clone, PartialEq)] -pub struct Payload { +pub struct Payload { pub issuer: DID, pub subject: DID, pub audience: DID, - pub capability_builder: A::Builder, // FIXME - pub conditions: Box<[Cond]>, // Worth it over a Vec? + pub capability_builder: Delegate, // FIXME + pub conditions: Vec, // Worth it over a Vec? - pub metadata: BTreeMap, Ipld>, // FIXME serde value instead? - pub nonce: Box<[u8]>, // Better type? + pub metadata: BTreeMap, // FIXME serde value instead? + pub nonce: Vec, // Better type? pub expiration: Timestamp, pub not_before: Option, } -impl signature::Capsule for Payload { +impl signature::Capsule for Payload { const TAG: &'static str = "ucan/d/1.0.0-rc.1"; } + +#[derive(Debug, Clone, PartialEq)] +pub enum Delegate { + Any, + Specific(T), +} + +impl From<&Payload> for Ipld +where + Ipld: From + From, +{ + fn from(payload: &Payload) -> Self { + let mut map = BTreeMap::new(); + map.insert("iss".into(), payload.issuer.to_string().into()); + map.insert("sub".into(), payload.subject.to_string().into()); + map.insert("aud".into(), payload.audience.to_string().into()); + + let can = match &payload.capability_builder { + Delegate::Any => "ucan/*".into(), + Delegate::Specific(builder) => builder.command().clone().into(), + }; + + map.insert("can".into(), can); + + map.insert( + "args".into(), + match &payload.capability_builder { + Delegate::Any => Ipld::Map(BTreeMap::new()), + Delegate::Specific(builder) => builder.clone().into(), + }, + ); + map.insert( + "cond".into(), + payload + .conditions + .iter() + .map(|condition| (*condition).clone().into()) + .collect::>() + .into(), + ); + map.insert( + "meta".into(), + payload + .metadata + .clone() + .into_iter() + .map(|(key, value)| (key, value.into())) + .collect::>() + .into(), + ); + map.insert("nonce".into(), payload.nonce.clone().into()); + map.insert("exp".into(), payload.expiration.clone().into()); + + if let Some(not_before) = &payload.not_before { + map.insert("nbf".into(), not_before.clone().into()); + } + + map.into() + } +} diff --git a/src/error.rs b/src/error.rs index 98d665b4..2fb4da11 100644 --- a/src/error.rs +++ b/src/error.rs @@ -27,7 +27,7 @@ pub enum Error { #[error(transparent)] PluginError(PluginError), /// Internal errors - #[error("An unexpected error occurred in rs-ucan: {msg}\n\nThis is a bug: please consider filing an issue at https://github.com/ucan-wg/rs-ucan/issues")] + #[error("An unexpected error occurred in rs_ucan: {msg}\n\nThis is a bug: please consider filing an issue at https://github.com/ucan-wg/rs_ucan/issues")] InternalUcanError { /// Error message msg: String, diff --git a/src/invocation.rs b/src/invocation.rs index bf5b228b..b5213924 100644 --- a/src/invocation.rs +++ b/src/invocation.rs @@ -1,30 +1,43 @@ -use crate::{ability::traits::Ability, delegation, signature, time::Timestamp}; +use crate::{ + ability::traits::{Ability, Builder}, + delegation, + delegation::{Delegate, Delegation}, + receipt::Receipt, + signature, + time::Timestamp, +}; use did_url::DID; -use libipld_core::ipld::Ipld; +use libipld_core::{ipld::Ipld, link::Link}; use std::collections::BTreeMap; -pub type Invocation = signature::Envelope>; +pub type Invocation = signature::Envelope>; +// FIXME figure out how to get rid of (AKA imply) that B param #[derive(Debug, Clone, PartialEq)] -pub struct Payload { +pub struct Payload +where + B: Builder, +{ pub issuer: DID, pub subject: DID, pub audience: Option, - pub ability: Ability, // FIXME check name in spec + pub ability: T, // TODO check name in spec - // pub proofs: Vec>>, - // pub cause: Option>>, // FIXME? - pub metadata: BTreeMap, Ipld>, // FIXME serde value instead? - pub nonce: Box<[u8]>, // Better type? + pub proofs: Vec>>, + pub cause: Option>>, + pub metadata: BTreeMap, // FIXME serde value instead? + pub nonce: Vec, // Better type? pub expiration: Timestamp, pub not_before: Option, } // FIXME move that clone? -impl From> for delegation::Payload { - fn from(invocation: Payload) -> Self { +impl + From, C> From<&Payload> + for delegation::Payload +{ + fn from(invocation: &Payload) -> Self { Self { issuer: invocation.issuer.clone(), subject: invocation.subject.clone(), @@ -32,16 +45,17 @@ impl From> for delegation::Payload .audience .clone() .unwrap_or(invocation.issuer.clone()), - capability_builder: >::into(invocation.ability.clone()), - conditions: Box::new([]), + capability_builder: Delegate::Specific(invocation.ability.clone().into()), + conditions: vec![], metadata: invocation.metadata.clone(), nonce: invocation.nonce.clone(), expiration: invocation.expiration.clone(), not_before: invocation.not_before.clone(), + // FIXME cause } } } -impl signature::Capsule for Payload { +impl, C> signature::Capsule for Payload { const TAG: &'static str = "ucan/i/1.0.0-rc.1"; } diff --git a/src/ipld.rs b/src/ipld.rs new file mode 100644 index 00000000..64a8ea5a --- /dev/null +++ b/src/ipld.rs @@ -0,0 +1,48 @@ +use libipld_core::ipld::Ipld; + +#[cfg(target_arch = "wasm32")] +use wasm_bindgen::JsValue; + +pub struct WrappedIpld(pub Ipld); + +impl From for WrappedIpld { + fn from(ipld: Ipld) -> Self { + Self(ipld) + } +} + +impl From for Ipld { + fn from(wrapped: WrappedIpld) -> Self { + wrapped.0 + } +} + +// TODO testme +#[cfg(target_arch = "wasm32")] +impl From for JsValue { + fn from(wrapped: WrappedIpld) -> Self { + match wrapped.0 { + Ipld::Null => JsValue::Null, + Ipld::Bool(b) => JsValue::from(b), + Ipld::Integer(i) => JsValue::from(i), + Ipld::Float(f) => JsValue::from_f64(f), + Ipld::String(s) => JsValue::from_str(&s), + Ipld::Bytes(b) => JsValue::from(b), + Ipld::List(l) => { + let mut vec = Vec::new(); + for ipld in l { + vec.push(JsValue::from(ipld)); + } + JsValue::from(vec) + } + Ipld::Map(m) => { + let mut map = JsValue::new(); + for (k, v) in m { + map.set(&k, JsValue::from(v)); + } + map + } + Ipld::Link(l) => JsValue::from(Link::from(l)), + } + } +} diff --git a/src/lib.rs b/src/lib.rs index 79e618bb..f0733a82 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -2,7 +2,7 @@ #![warn(missing_debug_implementations, missing_docs, rust_2018_idioms)] #![deny(unreachable_pub)] -//! rs-ucan +//! rs_ucan use std::str::FromStr; @@ -38,6 +38,8 @@ pub type Did = String; use std::fmt::Debug; +// FIXME concrete abilitiy types in addition to promised version + // impl DelegationPayload { // fn check( // &self, @@ -113,38 +115,11 @@ pub mod ability; pub mod condition; pub mod delegation; pub mod invocation; +pub mod ipld; pub mod promise; pub mod prove; pub mod receipt; -pub mod signature; - -////////////////////////////////////// -////////////////////////////////////// -////////////////////////////////////// -////////////////////////////////////// -////////////////////////////////////// -////////////////////////////////////// -////////////////////////////////////// -////////////////////////////////////// -////////////////////////////////////// -////////////////////////////////////// -////////////////////////////////////// -////////////////////////////////////// -////////////////////////////////////// -////////////////////////////////////// -////////////////////////////////////// -////////////////////////////////////// -////////////////////////////////////// -////////////////////////////////////// -////////////////////////////////////// -////////////////////////////////////// -////////////////////////////////////// -////////////////////////////////////// -////////////////////////////////////// -////////////////////////////////////// -////////////////////////////////////// -////////////////////////////////////// -////////////////////////////////////// +pub mod signature; // FIXME /// The empty fact #[derive(Debug, Clone, Default, Serialize, Deserialize)] diff --git a/src/prove.rs b/src/prove.rs index fcee2ff6..c132b41c 100644 --- a/src/prove.rs +++ b/src/prove.rs @@ -7,27 +7,27 @@ /// more[...] /// more2[...] /// ``` -pub trait TryProve { +pub trait TryProve<'a, T> { type Error; type Proven; - fn try_prove<'a>(&'a self, candidate: &'a T) -> Result<&'a Self::Proven, Self::Error>; + fn try_prove(&'a self, candidate: &'a T) -> Result<&'a Self::Proven, Self::Error>; } -pub trait TryProven { +pub trait TryProven<'a, A> { type Proven1; type Error1; - fn try_proven<'a>(&'a self, candidate: &'a A) -> Result<&'a Self::Proven1, Self::Error1>; + fn try_proven(&'a self, candidate: &'a A) -> Result<&'a Self::Proven1, Self::Error1>; } -impl TryProven for U +impl<'a, T, U> TryProven<'a, T> for U where - T: TryProve, + T: TryProve<'a, U>, { type Proven1 = T::Proven; type Error1 = T::Error; - fn try_proven<'a>(&'a self, candidate: &'a T) -> Result<&'a T::Proven, T::Error> { + fn try_proven(&'a self, candidate: &'a T) -> Result<&'a T::Proven, T::Error> { candidate.try_prove(self) } } diff --git a/src/receipt.rs b/src/receipt.rs index 7ca0aab1..71b053f2 100644 --- a/src/receipt.rs +++ b/src/receipt.rs @@ -1,30 +1,33 @@ use crate::{ - ability::traits::Ability, delegation::Delegation, invocation::Invocation, signature, + ability::traits::{Ability, Builder}, + delegation::Delegation, + invocation::Invocation, + signature, time::Timestamp, }; use did_url::DID; use libipld_core::{ipld::Ipld, link::Link}; use std::collections::BTreeMap; -pub type Receipt = signature::Envelope>; +pub type Receipt = signature::Envelope>; -pub struct Payload -where - T: Ability, -{ +#[derive(Debug, Clone, PartialEq)] +pub struct Payload, C> { pub issuer: DID, - pub ran: Link>, - pub out: UcanResult, // FIXME? - pub proofs: Vec>>, + pub ran: Link>, + pub out: Result>, + + // pub proofs: Vec>>, // FIXME these can only be executiojn proofs, right? pub metadata: BTreeMap, pub issued_at: Option, } -pub enum UcanResult { - UcanOk(T), - UcanErr(BTreeMap), -} +// #[derive(Debug, Clone, PartialEq)] +// pub enum UcanResult { +// UcanOk(T), +// UcanErr(BTreeMap), +// } -impl signature::Capsule for Payload { - const TAG: &'static str = "ucan/r/1.0.0-rc.1"; // FIXME extract out versioh +impl, C> signature::Capsule for Payload { + const TAG: &'static str = "ucan/r/1.0.0-rc.1"; // FIXME extract out version } diff --git a/src/signature.rs b/src/signature.rs index 61be0815..57af2826 100644 --- a/src/signature.rs +++ b/src/signature.rs @@ -1,22 +1,70 @@ -use libipld_core::link::Link; +use libipld_core::{ipld::Ipld, link::Link}; +use std::collections::BTreeMap; +#[derive(Debug, Clone, PartialEq)] pub struct Envelope where T: Capsule, { - pub sig: Box<[u8]>, - pub payload: Link, + pub sig: Signature, + pub payload: T, } +// FIXME consider kicking Batch down the road for spec reasons? +#[derive(Debug, Clone, PartialEq)] pub enum Signature { - One(Box<[u8]>), + One(Vec), Batch { - sig: Box<[u8]>, - merkle_proof: Box<[u32]>, + sig: Vec, + merkle_proof: Vec>, }, } -// TODO move to own module +// TODO move to own module? pub trait Capsule { const TAG: &'static str; } + +impl From<&Signature> for Ipld { + fn from(sig: &Signature) -> Self { + match sig { + Signature::One(sig) => sig.clone().into(), + Signature::Batch { sig, merkle_proof } => { + let mut map = BTreeMap::new(); + let proof: Vec = merkle_proof.into_iter().map(|p| p.clone().into()).collect(); + map.insert("sig".into(), sig.clone().into()); + map.insert("prf".into(), proof.into()); + map.into() + } + } + } +} + +impl From for Ipld { + fn from(sig: Signature) -> Self { + match sig { + Signature::One(sig) => sig.into(), + Signature::Batch { sig, merkle_proof } => { + let mut map = BTreeMap::new(); + let proof: Vec = merkle_proof.into_iter().map(|p| p.into()).collect(); + map.insert("sig".into(), sig.into()); + map.insert("prf".into(), proof.into()); + map.into() + } + } + } +} + +// FIXME Store or BTreeMap? Also eliminate that Clone constraint +impl + Clone> From<&Envelope> for Ipld { + fn from(Envelope { sig, payload }: &Envelope) -> Self { + let mut inner = BTreeMap::new(); + inner.insert(T::TAG.into(), payload.clone().into()); // FIXME should be a link + + let mut map = BTreeMap::new(); + map.insert("sig".into(), sig.into()); + map.insert("pld".into(), Ipld::Map(inner)); + + Ipld::Map(map) + } +} diff --git a/src/time.rs b/src/time.rs index 27c52998..b55ce9f9 100644 --- a/src/time.rs +++ b/src/time.rs @@ -1,6 +1,7 @@ //! Time utilities -use web_time::SystemTime; +use libipld_core::ipld::Ipld; +use web_time::{SystemTime, UNIX_EPOCH}; /// Get the current time in seconds since UNIX_EPOCH pub fn now() -> u64 { @@ -25,6 +26,19 @@ pub enum Timestamp { Receiving(SystemTime), } +impl From for Ipld { + fn from(timestamp: Timestamp) -> Self { + match timestamp { + Timestamp::Sending(js_time) => js_time.into(), + Timestamp::Receiving(sys_time) => sys_time + .duration_since(UNIX_EPOCH) + .expect("FIXME") + .as_secs() + .into(), + } + } +} + #[derive(Debug, Clone, PartialEq, Eq)] pub struct JsTime { time: SystemTime, @@ -54,3 +68,14 @@ impl JsTime { } } } + +impl From for Ipld { + fn from(js_time: JsTime) -> Self { + js_time + .time + .duration_since(std::time::UNIX_EPOCH) + .expect("FIXME") + .as_secs() + .into() + } +} From aaa2a7906836b6d4abb3f714466416f33b7dc032 Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Wed, 24 Jan 2024 17:29:26 -0800 Subject: [PATCH 013/188] WIP --- src/ability.rs | 1 + src/ability/traits.rs | 5 +++-- src/ability/wasm.rs | 14 ++++++++++++++ src/delegation.rs | 12 ++++++------ src/invocation.rs | 3 +-- 5 files changed, 25 insertions(+), 10 deletions(-) create mode 100644 src/ability/wasm.rs diff --git a/src/ability.rs b/src/ability.rs index 0b896ac0..eda773d7 100644 --- a/src/ability.rs +++ b/src/ability.rs @@ -2,6 +2,7 @@ pub mod any; pub mod crud; pub mod msg; pub mod traits; +pub mod wasm; // TODO move to crate::wasm? #[cfg(feature = "wasm")] diff --git a/src/ability/traits.rs b/src/ability/traits.rs index c7e8f662..091f5424 100644 --- a/src/ability/traits.rs +++ b/src/ability/traits.rs @@ -1,10 +1,11 @@ use std::fmt::Debug; // FIXME this is always a builder, right? -pub trait Ability: Sized { +pub trait Ability { + // FIXME no Sizd // FIXME remove sized? // pub trait Capability: TryFrom + Into { - type Builder: From + TryInto + PartialEq + Debug; // FIXME + type Builder; // FIXME // fn command(builder: &Self::Builder) -> &'static str; } diff --git a/src/ability/wasm.rs b/src/ability/wasm.rs new file mode 100644 index 00000000..4ccd51b5 --- /dev/null +++ b/src/ability/wasm.rs @@ -0,0 +1,14 @@ +use libipld_core::{ipld::Ipld, link::Link}; + +#[derive(Debug, Clone, PartialEq)] +pub struct Run { + pub module: Module, + pub function: String, + pub args: Vec, +} + +#[derive(Debug, Clone, PartialEq)] +pub enum Module { + Inline(Vec), + Cid(Link>), +} diff --git a/src/delegation.rs b/src/delegation.rs index 9bfb625e..2673e53f 100644 --- a/src/delegation.rs +++ b/src/delegation.rs @@ -18,11 +18,11 @@ pub struct Payload { pub subject: DID, pub audience: DID, - pub capability_builder: Delegate, // FIXME - pub conditions: Vec, // Worth it over a Vec? + pub ability_builder: Delegate, // FIXME + pub conditions: Vec, - pub metadata: BTreeMap, // FIXME serde value instead? - pub nonce: Vec, // Better type? + pub metadata: BTreeMap, + pub nonce: Vec, // FIXME Better type? pub expiration: Timestamp, pub not_before: Option, @@ -48,7 +48,7 @@ where map.insert("sub".into(), payload.subject.to_string().into()); map.insert("aud".into(), payload.audience.to_string().into()); - let can = match &payload.capability_builder { + let can = match &payload.ability_builder { Delegate::Any => "ucan/*".into(), Delegate::Specific(builder) => builder.command().clone().into(), }; @@ -57,7 +57,7 @@ where map.insert( "args".into(), - match &payload.capability_builder { + match &payload.ability_builder { Delegate::Any => Ipld::Map(BTreeMap::new()), Delegate::Specific(builder) => builder.clone().into(), }, diff --git a/src/invocation.rs b/src/invocation.rs index b5213924..1940d9c2 100644 --- a/src/invocation.rs +++ b/src/invocation.rs @@ -45,13 +45,12 @@ impl + From, C> From<&Payload> .audience .clone() .unwrap_or(invocation.issuer.clone()), - capability_builder: Delegate::Specific(invocation.ability.clone().into()), + ability_builder: Delegate::Specific(invocation.ability.clone().into()), conditions: vec![], metadata: invocation.metadata.clone(), nonce: invocation.nonce.clone(), expiration: invocation.expiration.clone(), not_before: invocation.not_before.clone(), - // FIXME cause } } } From ad94b97a28eee112e6a1a6e3aa2e16231edb409a Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Wed, 24 Jan 2024 23:56:40 -0800 Subject: [PATCH 014/188] More exploratory programming, cleanup, and conds --- Cargo.toml | 2 +- src/ability/any.rs | 6 +- src/ability/msg.rs | 13 +- src/ability/traits.rs | 47 +++--- src/condition.rs | 22 --- src/condition/common.rs | 1 + src/delegation.rs | 106 ++------------ src/delegation/condition.rs | 7 + src/delegation/condition/common.rs | 220 +++++++++++++++++++++++++++++ src/delegation/delegate.rs | 39 +++++ src/delegation/payload.rs | 137 ++++++++++++++++++ src/invocation.rs | 95 ++++++++++--- src/lib.rs | 1 - src/promise.rs | 2 +- src/prove.rs | 18 --- src/receipt.rs | 64 ++++++--- src/signature.rs | 13 ++ 17 files changed, 587 insertions(+), 206 deletions(-) delete mode 100644 src/condition.rs create mode 100644 src/condition/common.rs create mode 100644 src/delegation/condition.rs create mode 100644 src/delegation/condition/common.rs create mode 100644 src/delegation/delegate.rs create mode 100644 src/delegation/payload.rs diff --git a/Cargo.toml b/Cargo.toml index ce38108a..289b4adc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -51,6 +51,7 @@ p256 = { version = "0.13.2", features = ["ecdsa"], optional = true, default-feat p384 = { version = "0.13.0", features = ["ecdsa"], optional = true, default-features = false } p521 = { version = "0.13.0", optional = true, default-features = false } proptest = { version = "1.1", optional = true } +regex = { version = "1.10" } rsa = { version = "0.9.2", features = ["sha2"], optional = true, default-features = false } semver = "1.0.19" serde = { version = "1.0.188", features = ["derive"] } @@ -60,7 +61,6 @@ thiserror = "1.0" tracing = "0.1.40" unsigned-varint = "0.7.2" url = "2.5" -void = "1.0" web-time = "0.2.3" [target.'cfg(not(target_arch = "wasm32"))'.dependencies] diff --git a/src/ability/any.rs b/src/ability/any.rs index ac3e4559..bac76d01 100644 --- a/src/ability/any.rs +++ b/src/ability/any.rs @@ -1,14 +1,14 @@ use crate::prove::TryProve; -use void::Void; +use std::convert::Infallible; #[derive(Debug, Clone, Copy, Eq, PartialEq)] pub struct DelegateAny; impl<'a, T> TryProve<'a, DelegateAny> for T { - type Error = Void; + type Error = Infallible; type Proven = T; - fn try_prove(&'a self, _proof: &'a DelegateAny) -> Result<&'a Self::Proven, Void> { + fn try_prove(&'a self, _proof: &'a DelegateAny) -> Result<&'a Self::Proven, Infallible> { Ok(self) } } diff --git a/src/ability/msg.rs b/src/ability/msg.rs index b9e36086..005cca90 100644 --- a/src/ability/msg.rs +++ b/src/ability/msg.rs @@ -1,7 +1,4 @@ -use crate::{ - ability::traits::{Ability, Builder}, - prove::TryProve, -}; +use crate::{ability::traits::Command, prove::TryProve}; use libipld_core::ipld::Ipld; use std::{collections::BTreeMap, str::FromStr}; use url::Url; @@ -93,16 +90,10 @@ impl TryFrom<&Ipld> for MsgReceiveBuilder { } } -impl Builder for MsgReceiveBuilder { - type Concrete = MsgReceive; - +impl Command for MsgReceiveBuilder { fn command(&self) -> &'static str { "msg/receive" } - - fn try_build(&self) -> Result { - self.try_build().map_err(|_| ()) // FIXME - } } impl TryFrom<&Ipld> for MsgReceive { diff --git a/src/ability/traits.rs b/src/ability/traits.rs index 091f5424..ff01dc09 100644 --- a/src/ability/traits.rs +++ b/src/ability/traits.rs @@ -1,31 +1,34 @@ use std::fmt::Debug; -// FIXME this is always a builder, right? -pub trait Ability { - // FIXME no Sizd - // FIXME remove sized? - // pub trait Capability: TryFrom + Into { - type Builder; // FIXME - - // fn command(builder: &Self::Builder) -> &'static str; +pub trait Command { + fn command(&self) -> &'static str; } -pub trait Builder { - type Concrete; +// FIXME this is always a builder, right? ToBuilder? Builder? Buildable? +// FIXME Delegable and make it proven? +pub trait Buildable { + type Builder: Command + Debug; // FIXME - fn command(&self) -> &'static str; - fn try_build(&self) -> Result; // FIXME + fn to_builder(&self) -> Self::Builder; + fn try_build(builder: Self::Builder) -> Result, ()>; // FIXME check if this box (for objevt safety) is actually required +} + +pub trait Resolvable: Buildable { + type Awaiting: Command + Debug; // FIXME + + fn to_awaitable(&self) -> Self::Awaiting; + fn try_into_resolved(promise: Self::Awaiting) -> Result, ()>; // FIXME check if this box (for objevt safety) is actually required +} + +impl Command for T { + fn command(&self) -> &'static str { + self.to_builder().command() + } } -// pub trait Builds1 { -// type B; -// } -// -// impl Builds1 for B::Concrete -// where -// B: Builder, -// { -// type B = B; -// } +pub trait Runnable { + type Output; +} +pub trait Ability: Buildable + Runnable {} // FIXME macro for Delegation (builder) and Promises diff --git a/src/condition.rs b/src/condition.rs deleted file mode 100644 index e4d27ec3..00000000 --- a/src/condition.rs +++ /dev/null @@ -1,22 +0,0 @@ -use libipld_core::ipld::Ipld; - -pub enum Condition { - Contains { field: String, value: Vec }, - MinLength { field: String, value: u64 }, - MaxLength { field: String, value: u64 }, - Equals { field: String, value: Ipld }, - Regex { field: String }, // FIXME - - // Silly example - OnDayOfWeek { day: Day }, -} - -pub enum Day { - Monday, - Tuesday, - Wednesday, - Thursday, - Friday, - Saturday, - Sunday, -} diff --git a/src/condition/common.rs b/src/condition/common.rs new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/src/condition/common.rs @@ -0,0 +1 @@ + diff --git a/src/delegation.rs b/src/delegation.rs index 2673e53f..5c96d411 100644 --- a/src/delegation.rs +++ b/src/delegation.rs @@ -1,93 +1,15 @@ -use crate::{ - ability::{ - any::DelegateAny, - traits::{Ability, Builder}, - }, - signature, - time::Timestamp, -}; -use did_url::DID; -use libipld_core::ipld::Ipld; -use std::collections::BTreeMap; - +pub mod condition; +pub mod delegate; +pub mod payload; + +use crate::signature; +pub use delegate::Delegate; +pub use payload::Payload; + +/// A [`Delegation`] is a signed delegation [`Payload`] +/// +/// A [`Payload`] on its own is not a valid [`Delegation`], as it must be signed by the issuer. +/// +/// # Examples +/// FIXME pub type Delegation = signature::Envelope>; - -#[derive(Debug, Clone, PartialEq)] -pub struct Payload { - pub issuer: DID, - pub subject: DID, - pub audience: DID, - - pub ability_builder: Delegate, // FIXME - pub conditions: Vec, - - pub metadata: BTreeMap, - pub nonce: Vec, // FIXME Better type? - - pub expiration: Timestamp, - pub not_before: Option, -} - -impl signature::Capsule for Payload { - const TAG: &'static str = "ucan/d/1.0.0-rc.1"; -} - -#[derive(Debug, Clone, PartialEq)] -pub enum Delegate { - Any, - Specific(T), -} - -impl From<&Payload> for Ipld -where - Ipld: From + From, -{ - fn from(payload: &Payload) -> Self { - let mut map = BTreeMap::new(); - map.insert("iss".into(), payload.issuer.to_string().into()); - map.insert("sub".into(), payload.subject.to_string().into()); - map.insert("aud".into(), payload.audience.to_string().into()); - - let can = match &payload.ability_builder { - Delegate::Any => "ucan/*".into(), - Delegate::Specific(builder) => builder.command().clone().into(), - }; - - map.insert("can".into(), can); - - map.insert( - "args".into(), - match &payload.ability_builder { - Delegate::Any => Ipld::Map(BTreeMap::new()), - Delegate::Specific(builder) => builder.clone().into(), - }, - ); - map.insert( - "cond".into(), - payload - .conditions - .iter() - .map(|condition| (*condition).clone().into()) - .collect::>() - .into(), - ); - map.insert( - "meta".into(), - payload - .metadata - .clone() - .into_iter() - .map(|(key, value)| (key, value.into())) - .collect::>() - .into(), - ); - map.insert("nonce".into(), payload.nonce.clone().into()); - map.insert("exp".into(), payload.expiration.clone().into()); - - if let Some(not_before) = &payload.not_before { - map.insert("nbf".into(), not_before.clone().into()); - } - - map.into() - } -} diff --git a/src/delegation/condition.rs b/src/delegation/condition.rs new file mode 100644 index 00000000..b03b07b6 --- /dev/null +++ b/src/delegation/condition.rs @@ -0,0 +1,7 @@ +use libipld_core::ipld::Ipld; + +pub mod common; + +pub trait Condition { + fn validate(&self, ipld: &Ipld) -> bool; +} diff --git a/src/delegation/condition/common.rs b/src/delegation/condition/common.rs new file mode 100644 index 00000000..bd568c0b --- /dev/null +++ b/src/delegation/condition/common.rs @@ -0,0 +1,220 @@ +use super::Condition; +use libipld_core::ipld::Ipld; +use regex::Regex; +use std::collections::BTreeMap; + +#[derive(Debug, Clone, PartialEq)] +pub enum Common { + ContainsAll(ContainsAll), + ContainsAny(ContainsAny), + ExcludesAll(ExcludesAll), + MaxLength(MaxLength), + MinNumber(MinNumber), + MaxNumber(MaxNumber), + Matches(Matches), +} + +// FIXME dynamic js version? + +#[derive(Debug, Clone, PartialEq)] +pub struct ContainsAll { + field: String, + values: Vec, +} + +impl From for Ipld { + fn from(contains_all: ContainsAll) -> Self { + let mut map = BTreeMap::new(); + map.insert("field".into(), contains_all.field.into()); + map.insert("values".into(), contains_all.values.into()); + map.into() + } +} + +impl TryFrom<&Ipld> for ContainsAll { + type Error = (); // FIXME + + fn try_from(ipld: &Ipld) -> Result { + if let Ipld::Map(map) = ipld { + if map.len() != 2 { + return Err(()); + } + + if let Some(Ipld::String(field)) = map.get("field") { + let values = match map.get("values") { + None => Ok(vec![]), + Some(Ipld::List(values)) => Ok(values.to_vec()), + _ => Err(()), + }?; + + Ok(Self { + field: field.to_string(), + values: values.to_vec(), + }) + } else { + Err(()) + } + } else { + Err(()) + } + } +} + +impl Condition for ContainsAll { + fn validate(&self, ipld: &Ipld) -> bool { + match ipld { + Ipld::List(array) => self.values.iter().all(|ipld| array.contains(ipld)), + Ipld::Map(btree) => { + let vals: Vec<&Ipld> = btree.values().collect(); + self.values.iter().all(|ipld| vals.contains(&ipld)) + } + _ => false, + } + } +} + +#[derive(Debug, Clone, PartialEq)] +pub struct ContainsAny { + field: String, + value: Vec, +} + +impl Condition for ContainsAny { + fn validate(&self, ipld: &Ipld) -> bool { + match ipld { + Ipld::List(array) => array.iter().any(|ipld| self.value.contains(ipld)), + Ipld::Map(btree) => { + let vals: Vec<&Ipld> = btree.values().collect(); + self.value.iter().any(|ipld| vals.contains(&ipld)) + } + _ => false, + } + } +} + +#[derive(Debug, Clone, PartialEq)] +pub struct ExcludesAll { + field: String, + value: Vec, +} + +impl Condition for ExcludesAll { + fn validate(&self, ipld: &Ipld) -> bool { + match ipld { + Ipld::List(array) => self.value.iter().all(|ipld| !array.contains(ipld)), + Ipld::Map(btree) => { + let vals: Vec<&Ipld> = btree.values().collect(); + self.value.iter().all(|ipld| !vals.contains(&ipld)) + } + _ => false, + } + } +} + +#[derive(Debug, Clone, PartialEq)] +pub enum Numeric { + Float(f64), + Integer(i128), +} + +#[derive(Debug, Clone, PartialEq)] +pub struct MinNumber { + field: String, + value: Numeric, +} + +impl Condition for MinNumber { + fn validate(&self, ipld: &Ipld) -> bool { + match ipld { + Ipld::Integer(integer) => match self.value { + Numeric::Float(float) => *integer as f64 >= float, + Numeric::Integer(integer) => integer >= integer, + }, + Ipld::Float(float) => match self.value { + Numeric::Float(float) => float >= float, + Numeric::Integer(integer) => *float >= integer as f64, // FIXME this needs tests + }, + _ => false, + } + } +} + +#[derive(Debug, Clone, PartialEq)] +pub struct MaxNumber { + field: String, + value: Numeric, +} + +impl Condition for MaxNumber { + fn validate(&self, ipld: &Ipld) -> bool { + match ipld { + Ipld::Integer(integer) => match self.value { + Numeric::Float(float) => *integer as f64 <= float, + Numeric::Integer(integer) => integer <= integer, + }, + Ipld::Float(float) => match self.value { + Numeric::Float(float) => float <= float, + Numeric::Integer(integer) => *float <= integer as f64, // FIXME this needs tests + }, + _ => false, + } + } +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct MinLength { + field: String, + value: u64, +} + +impl Condition for MinLength { + fn validate(&self, ipld: &Ipld) -> bool { + match ipld { + Ipld::String(string) => string.len() >= self.value as usize, + Ipld::List(list) => list.len() >= self.value as usize, + Ipld::Map(btree) => btree.len() >= self.value as usize, + _ => false, + } + } +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct MaxLength { + field: String, + value: u64, +} + +impl Condition for MaxLength { + fn validate(&self, ipld: &Ipld) -> bool { + match ipld { + Ipld::String(string) => string.len() <= self.value as usize, + Ipld::List(list) => list.len() <= self.value as usize, + Ipld::Map(btree) => btree.len() <= self.value as usize, + _ => false, + } + } +} + +#[derive(Debug, Clone, PartialEq)] +pub struct Matches { + field: String, + matcher: Matcher, +} + +#[derive(Debug, Clone)] +pub struct Matcher(Regex); + +impl PartialEq for Matcher { + fn eq(&self, other: &Self) -> bool { + self.0.as_str() == other.0.as_str() + } +} + +impl Condition for Matcher { + fn validate(&self, ipld: &Ipld) -> bool { + match ipld { + Ipld::String(string) => self.0.is_match(string), + _ => false, + } + } +} diff --git a/src/delegation/delegate.rs b/src/delegation/delegate.rs new file mode 100644 index 00000000..1b645f58 --- /dev/null +++ b/src/delegation/delegate.rs @@ -0,0 +1,39 @@ +use libipld_core::ipld::Ipld; +use std::fmt::Debug; + +#[derive(Debug, Clone, PartialEq)] +pub enum Delegate { + Any, + Specific(T), +} + +impl<'a, T> From<&'a Delegate> for Ipld +where + Ipld: From<&'a T>, +{ + fn from(delegate: &'a Delegate) -> Self { + match delegate { + Delegate::Any => "ucan/*".into(), + Delegate::Specific(command) => command.into(), + } + } +} + +impl<'a, T: TryFrom<&'a Ipld>> TryFrom<&'a Ipld> for Delegate { + type Error = (); // FIXME + + fn try_from(ipld: &'a Ipld) -> Result { + if let ipld_string @ Ipld::String(st) = ipld { + if st == "ucan/*" { + Ok(Self::Any) + } else { + match T::try_from(ipld_string) { + Err(_) => Err(()), + Ok(cmd) => Ok(Self::Specific(cmd)), + } + } + } else { + Err(()) + } + } +} diff --git a/src/delegation/payload.rs b/src/delegation/payload.rs new file mode 100644 index 00000000..c78f4bca --- /dev/null +++ b/src/delegation/payload.rs @@ -0,0 +1,137 @@ +use super::{condition::Condition, delegate::Delegate}; +use crate::{ + ability::traits::{Buildable, Command}, + signature, + time::Timestamp, +}; +use did_url::DID; +use libipld_core::ipld::Ipld; +use std::{collections::BTreeMap, fmt::Debug}; + +#[derive(Debug, Clone, PartialEq)] +pub struct Payload { + pub issuer: DID, + pub subject: DID, + pub audience: DID, + + pub ability_builder: Delegate, + pub conditions: Vec, + // pub fixme: Vec>, + pub metadata: BTreeMap, + pub nonce: Vec, + + pub expiration: Timestamp, + pub not_before: Option, +} + +impl signature::Capsule for Payload { + const TAG: &'static str = "ucan/d/1.0.0-rc.1"; +} + +impl + Clone> From<&Payload> for Ipld +where + Ipld: From, + B::Builder: Clone, // FIXME +{ + fn from(payload: &Payload) -> Self { + let mut map = BTreeMap::new(); + map.insert("iss".into(), payload.issuer.to_string().into()); + map.insert("sub".into(), payload.subject.to_string().into()); + map.insert("aud".into(), payload.audience.to_string().into()); + + let can = match &payload.ability_builder { + Delegate::Any => "ucan/*".into(), + Delegate::Specific(builder) => builder.command().into(), + }; + + map.insert("can".into(), can); + + map.insert( + "args".into(), + match &payload.ability_builder { + Delegate::Any => Ipld::Map(BTreeMap::new()), + Delegate::Specific(builder) => (*builder).clone().into(), // FIXME + }, + ); + map.insert( + "cond".into(), + payload + .conditions + .iter() + .map(|condition| (*condition).clone().into()) + .collect::>() + .into(), + ); + map.insert( + "meta".into(), + payload + .metadata + .clone() + .into_iter() + .map(|(key, value)| (key, value.into())) + .collect::>() + .into(), + ); + map.insert("nonce".into(), payload.nonce.clone().into()); + map.insert("exp".into(), payload.expiration.clone().into()); + + if let Some(not_before) = &payload.not_before { + map.insert("nbf".into(), not_before.clone().into()); + } + + map.into() + } +} + +impl + Clone> From> for Ipld +where + Ipld: From, +{ + fn from(payload: Payload) -> Self { + let mut map = BTreeMap::new(); + map.insert("iss".into(), payload.issuer.to_string().into()); + map.insert("sub".into(), payload.subject.to_string().into()); + map.insert("aud".into(), payload.audience.to_string().into()); + + let can = match &payload.ability_builder { + Delegate::Any => "ucan/*".into(), + Delegate::Specific(builder) => builder.command().into(), + }; + + map.insert("can".into(), can); + + map.insert( + "args".into(), + match payload.ability_builder { + Delegate::Any => Ipld::Map(BTreeMap::new()), + Delegate::Specific(builder) => builder.into(), + }, + ); + map.insert( + "cond".into(), + payload + .conditions + .into_iter() + .map(|condition| condition.into()) + .collect::>() + .into(), + ); + map.insert( + "meta".into(), + payload + .metadata + .into_iter() + .map(|(key, value)| (key, value.into())) + .collect::>() + .into(), + ); + map.insert("nonce".into(), payload.nonce.into()); + map.insert("exp".into(), payload.expiration.into()); + + if let Some(not_before) = payload.not_before { + map.insert("nbf".into(), not_before.into()); + } + + map.into() + } +} diff --git a/src/invocation.rs b/src/invocation.rs index 1940d9c2..0d94da63 100644 --- a/src/invocation.rs +++ b/src/invocation.rs @@ -1,31 +1,28 @@ use crate::{ - ability::traits::{Ability, Builder}, + ability::traits::{Buildable, Command}, delegation, - delegation::{Delegate, Delegation}, + delegation::{condition::Condition, Delegate, Delegation}, receipt::Receipt, signature, time::Timestamp, }; use did_url::DID; -use libipld_core::{ipld::Ipld, link::Link}; -use std::collections::BTreeMap; +use libipld_core::{cid::Cid, ipld::Ipld, link::Link}; +use std::{collections::BTreeMap, fmt::Debug}; -pub type Invocation = signature::Envelope>; +pub type Invocation = signature::Envelope>; -// FIXME figure out how to get rid of (AKA imply) that B param #[derive(Debug, Clone, PartialEq)] -pub struct Payload -where - B: Builder, -{ +pub struct Payload { pub issuer: DID, pub subject: DID, pub audience: Option, - pub ability: T, // TODO check name in spec + pub ability: B, - pub proofs: Vec>>, - pub cause: Option>>, + // pub proofs: Vec>>, // FIXME just use Cid? + pub proofs: Vec, // FIXME just use Cid? + pub cause: Option, pub metadata: BTreeMap, // FIXME serde value instead? pub nonce: Vec, // Better type? @@ -34,10 +31,8 @@ where } // FIXME move that clone? -impl + From, C> From<&Payload> - for delegation::Payload -{ - fn from(invocation: &Payload) -> Self { +impl From<&Payload> for delegation::Payload { + fn from(invocation: &Payload) -> Self { Self { issuer: invocation.issuer.clone(), subject: invocation.subject.clone(), @@ -45,8 +40,9 @@ impl + From, C> From<&Payload> .audience .clone() .unwrap_or(invocation.issuer.clone()), - ability_builder: Delegate::Specific(invocation.ability.clone().into()), + ability_builder: Delegate::Specific(invocation.ability.clone().to_builder()), conditions: vec![], + // fixme: vec![], metadata: invocation.metadata.clone(), nonce: invocation.nonce.clone(), expiration: invocation.expiration.clone(), @@ -55,6 +51,67 @@ impl + From, C> From<&Payload> } } -impl, C> signature::Capsule for Payload { +impl signature::Capsule for Payload { const TAG: &'static str = "ucan/i/1.0.0-rc.1"; } + +impl> From> for Ipld { + fn from(payload: Payload) -> Self { + let mut map = BTreeMap::new(); + map.insert("iss".into(), payload.issuer.to_string().into()); + map.insert("sub".into(), payload.subject.to_string().into()); + map.insert( + "aud".into(), + payload + .audience + .map(|audience| audience.to_string()) + .unwrap_or(payload.issuer.to_string()) + .into(), + ); + + map.insert("can".into(), payload.ability.command().into()); + map.insert("args".into(), payload.ability.into()); + + map.insert( + "proofs".into(), + Ipld::List( + payload + .proofs + .into_iter() + .map(|cid| cid.into()) + // .map(|link| link.cid().into()) + .collect::>(), + ), + ); + + map.insert( + "cause".into(), + payload + .cause + // .map(|link| link.cid().into()) + .map(|cid| cid.into()) + .unwrap_or(Ipld::Null), + ); + + map.insert( + "metadata".into(), + Ipld::Map( + payload + .metadata + .into_iter() + .map(|(k, v)| (k, v.into())) + .collect::>(), + ), + ); + + map.insert("nonce".into(), payload.nonce.into()); + + map.insert("exp".into(), payload.expiration.into()); + + if let Some(not_before) = payload.not_before { + map.insert("nbf".into(), not_before.into()); + } + + map.into() + } +} diff --git a/src/lib.rs b/src/lib.rs index f0733a82..3d8f83d5 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -112,7 +112,6 @@ use std::fmt::Debug; // pub mod ability; -pub mod condition; pub mod delegation; pub mod invocation; pub mod ipld; diff --git a/src/promise.rs b/src/promise.rs index 37ed5bd0..5bf38a28 100644 --- a/src/promise.rs +++ b/src/promise.rs @@ -1,4 +1,4 @@ -use crate::{ability::traits::Ability, invocation, invocation::Invocation, signature::Capsule}; +use crate::{ability::traits::Buildable, invocation, invocation::Invocation, signature::Capsule}; use cid::Cid; use libipld_core::{ipld::Ipld, link::Link}; use std::fmt::Debug; diff --git a/src/prove.rs b/src/prove.rs index c132b41c..e6962fbb 100644 --- a/src/prove.rs +++ b/src/prove.rs @@ -13,21 +13,3 @@ pub trait TryProve<'a, T> { fn try_prove(&'a self, candidate: &'a T) -> Result<&'a Self::Proven, Self::Error>; } - -pub trait TryProven<'a, A> { - type Proven1; - type Error1; - fn try_proven(&'a self, candidate: &'a A) -> Result<&'a Self::Proven1, Self::Error1>; -} - -impl<'a, T, U> TryProven<'a, T> for U -where - T: TryProve<'a, U>, -{ - type Proven1 = T::Proven; - type Error1 = T::Error; - - fn try_proven(&'a self, candidate: &'a T) -> Result<&'a T::Proven, T::Error> { - candidate.try_prove(self) - } -} diff --git a/src/receipt.rs b/src/receipt.rs index 71b053f2..f8df1435 100644 --- a/src/receipt.rs +++ b/src/receipt.rs @@ -1,33 +1,65 @@ use crate::{ - ability::traits::{Ability, Builder}, - delegation::Delegation, + ability::traits::{Buildable, Command, Runnable}, + delegation::{condition::Condition, Delegation}, invocation::Invocation, signature, time::Timestamp, }; use did_url::DID; -use libipld_core::{ipld::Ipld, link::Link}; -use std::collections::BTreeMap; +use libipld_core::{cid::Cid, ipld::Ipld, link::Link}; +use std::{collections::BTreeMap, fmt::Debug}; -pub type Receipt = signature::Envelope>; +pub type Receipt = signature::Envelope>; #[derive(Debug, Clone, PartialEq)] -pub struct Payload, C> { +pub struct Payload { pub issuer: DID, - pub ran: Link>, - pub out: Result>, + pub ran: Cid, + pub out: Result>, - // pub proofs: Vec>>, // FIXME these can only be executiojn proofs, right? + pub proofs: Vec, pub metadata: BTreeMap, pub issued_at: Option, } -// #[derive(Debug, Clone, PartialEq)] -// pub enum UcanResult { -// UcanOk(T), -// UcanErr(BTreeMap), -// } - -impl, C> signature::Capsule for Payload { +impl signature::Capsule for Payload { const TAG: &'static str = "ucan/r/1.0.0-rc.1"; // FIXME extract out version } + +// FIXME +#[derive(Debug, Clone, PartialEq)] +pub struct ProxyExecute { + pub command: String, + pub args: BTreeMap, +} + +impl Buildable for ProxyExecute { + type Builder = ProxyExecuteBuilder; + + fn to_builder(&self) -> Self::Builder { + ProxyExecuteBuilder { + command: Some(self.command.clone()), + args: self.args.clone(), + } + } + + fn try_build(ProxyExecuteBuilder { command, args }: Self::Builder) -> Result, ()> { + match command { + None => Err(()), + Some(command) => Ok(Box::new(Self { command, args })), + } + } +} + +// FIXME hmmm +#[derive(Debug, Clone, PartialEq)] +pub struct ProxyExecuteBuilder { + pub command: Option, + pub args: BTreeMap, +} + +impl Command for ProxyExecuteBuilder { + fn command(&self) -> &'static str { + "ucan/proxy" // FIXME check spec + } +} diff --git a/src/signature.rs b/src/signature.rs index 57af2826..1966742f 100644 --- a/src/signature.rs +++ b/src/signature.rs @@ -68,3 +68,16 @@ impl + Clone> From<&Envelope> for Ipld { Ipld::Map(map) } } + +impl + Clone> From> for Ipld { + fn from(Envelope { sig, payload }: Envelope) -> Self { + let mut inner = BTreeMap::new(); + inner.insert(T::TAG.into(), payload.clone().into()); // FIXME should be a link + + let mut map = BTreeMap::new(); + map.insert("sig".into(), sig.into()); + map.insert("pld".into(), Ipld::Map(inner)); + + Ipld::Map(map) + } +} From e72b6d8680da4493499ea7525c146f6e73ebcbbd Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Fri, 26 Jan 2024 00:54:31 -0800 Subject: [PATCH 015/188] WIP --- .github/workflows/bench.yml | 2 +- .github/workflows/coverage.yml | 2 +- CONTRIBUTING.md | 4 +- Cargo.toml | 13 ++-- README.md | 24 +++--- SECURITY.md | 6 +- benches/a_benchmark.rs | 4 +- flake.nix | 4 +- package.json | 48 ++++++------ src/ability/crud.rs | 1 + src/ability/dynamic.rs | 8 +- src/ability/msg.rs | 6 +- src/ability/traits.rs | 65 +++++++++++----- src/capsule.rs | 3 + src/delegation/payload.rs | 27 ++++--- src/error.rs | 2 +- src/invocation.rs | 119 +--------------------------- src/invocation/payload.rs | 137 +++++++++++++++++++++++++++++++++ src/lib.rs | 6 +- src/nonce.rs | 135 ++++++++++++++++++++++++++++++++ src/promise.rs | 60 +++++++-------- src/receipt.rs | 66 +++++++--------- src/receipt/payload.rs | 29 +++++++ src/signature.rs | 13 +--- src/test_utils/rvg.rs | 4 +- src/workerd.js | 6 +- tests/conformance.rs | 6 +- tests/rs_ucan.test.js | 2 +- 28 files changed, 503 insertions(+), 299 deletions(-) create mode 100644 src/capsule.rs create mode 100644 src/invocation/payload.rs create mode 100644 src/nonce.rs create mode 100644 src/receipt/payload.rs diff --git a/.github/workflows/bench.yml b/.github/workflows/bench.yml index e8a39b9e..76052f66 100644 --- a/.github/workflows/bench.yml +++ b/.github/workflows/bench.yml @@ -51,7 +51,7 @@ jobs: tool: 'cargo' output-file-path: output.txt github-token: ${{ secrets.GITHUB_TOKEN }} - auto-push: ${{ github.event_name == 'push' && github.repository == 'ucan-wg/rs_ucan' && github.ref == 'refs/heads/main' }} + auto-push: ${{ github.event_name == 'push' && github.repository == 'ucan-wg/rs-ucan' && github.ref == 'refs/heads/main' }} alert-threshold: '200%' comment-on-alert: true fail-on-alert: true diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml index 8197146b..bbcf0c75 100644 --- a/.github/workflows/coverage.yml +++ b/.github/workflows/coverage.yml @@ -33,7 +33,7 @@ jobs: - name: Generate Code coverage env: CARGO_INCREMENTAL: '0' - LLVM_PROFILE_FILE: "rs_ucan-%p-%m.profraw" + LLVM_PROFILE_FILE: "rs-ucan-%p-%m.profraw" RUSTFLAGS: '-Zprofile -Ccodegen-units=1 -Cinline-threshold=0 -Clink-dead-code -Coverflow-checks=off' RUSTDOCFLAGS: '-Zprofile -Ccodegen-units=1 -Cinline-threshold=0 -Clink-dead-code -Coverflow-checks=off' run: cargo test --all-features diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index c8922c18..2c6d8dcf 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,4 +1,4 @@ -# Contributing to rs_ucan +# Contributing to rs-ucan We welcome everyone to contribute what and where they can. Whether you are brand new, just want to contribute a little bit, or want to contribute a lot there is @@ -84,7 +84,7 @@ need to be the best programmer to contribute. Our discord is open for questions. - You can learn more about cloning repositories [here][git-clone]. 6. **Build** the project - - For a detailed look on how to build rs_ucan look at our + - For a detailed look on how to build rs-ucan look at our [README file](./README.md). 7. **Start writing** your code diff --git a/Cargo.toml b/Cargo.toml index 289b4adc..d9da4d64 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] -name = "rs_ucan" -version = "0.1.0" +name = "rs-ucan" +version = "0.2.0" description = "Rust implementation of UCAN" keywords = ["capabilities", "authorization", "ucan"] categories = [] @@ -9,8 +9,8 @@ license = "Apache-2.0" readme = "README.md" edition = "2021" rust-version = "1.75" -documentation = "https://docs.rs/rs_ucan" -repository = "https://github.com/ucan-wg/rs_ucan" +documentation = "https://docs.rs/rs-ucan" +repository = "https://github.com/ucan-wg/rs-ucan" authors = ["Quinn Wilton ", "Brooklyn Zelenka - - rs_ucan Logo + + rs-ucan Logo -

rs_ucan

+

rs-ucan

- - Crate + + Crate - - Code Coverage + + Code Coverage - - Build Status + + Build Status - + License - + Docs @@ -34,7 +34,7 @@ Add the following to the `[dependencies]` section of your `Cargo.toml` file: ```toml -rs_ucan = "1.0.0-rc.1" +rs-ucan = "1.0.0-rc.1" ``` ## Testing the Project diff --git a/SECURITY.md b/SECURITY.md index 3d620e8e..c74cc826 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -1,6 +1,6 @@ ## Report a security issue or vulnerability -The `rs_ucan` team welcomes security reports and is committed to +The `rs-ucan` team welcomes security reports and is committed to providing prompt attention to security issues. Security issues should be reported privately via [quinn@fission.codes][support-email]. Security issues should not be reported via the public GitHub Issue tracker. @@ -8,10 +8,10 @@ not be reported via the public GitHub Issue tracker. ## Security advisories The project team is committed to transparency in the security issue disclosure -process. The rs_ucan team announces security advisories through our +process. The rs-ucan team announces security advisories through our Github respository's [security portal][sec-advisories] and and the [RustSec advisory database][rustsec-db]. [rustsec-db]: https://github.com/RustSec/advisory-db -[sec-advisories]: https://github.com/ucan-wg/rs_ucan/security/advisories +[sec-advisories]: https://github.com/ucan-wg/rs-ucan/security/advisories [support-email]: mailto:quinn@fission.codes diff --git a/benches/a_benchmark.rs b/benches/a_benchmark.rs index 6169ded5..76f63eb8 100644 --- a/benches/a_benchmark.rs +++ b/benches/a_benchmark.rs @@ -1,13 +1,13 @@ use criterion::{criterion_group, criterion_main, Criterion}; pub fn add_benchmark(c: &mut Criterion) { - let mut rvg = rs_ucan::test_utils::Rvg::deterministic(); + let mut rvg = rs-ucan::test_utils::Rvg::deterministic(); let int_val_1 = rvg.sample(&(0..100i32)); let int_val_2 = rvg.sample(&(0..100i32)); c.bench_function("add", |b| { b.iter(|| { - rs_ucan::add(int_val_1, int_val_2); + rs-ucan::add(int_val_1, int_val_2); }) }); } diff --git a/flake.nix b/flake.nix index 70e1f16c..947a2386 100644 --- a/flake.nix +++ b/flake.nix @@ -1,5 +1,5 @@ { - description = "rs_ucan"; + description = "rs-ucan"; inputs = { nixpkgs.url = "nixpkgs/nixos-23.11"; @@ -88,7 +88,7 @@ wasm-pack = "${pkgs.wasm-pack}/bin/wasm-pack"; in rec { devShells.default = pkgs.devshell.mkShell { - name = "rs_ucan"; + name = "rs-ucan"; imports = [./pre-commit.nix]; diff --git a/package.json b/package.json index 3e09a8bd..b657427b 100644 --- a/package.json +++ b/package.json @@ -1,10 +1,10 @@ { "name": "ucan", "version": "0.1.0", - "description": "A UCAN library built from rs_ucan", + "description": "A UCAN library built from rs-ucan", "repository": { "type": "git", - "url": "git+https://github.com/fission-codes/rs_ucan.git" + "url": "git+https://github.com/fission-codes/rs-ucan.git" }, "keywords": [ "authorization" @@ -12,30 +12,30 @@ "author": "", "license": "Apache-2.0", "bugs": { - "url": "https://github.com/fission-codes/rs_ucan/issues" + "url": "https://github.com/fission-codes/rs-ucan/issues" }, - "homepage": "https://github.com/fission-codes/rs_ucan#readme", - "module": "dist/bundler/rs_ucan.js", - "types": "dist/nodejs/rs_ucan.d.ts", + "homepage": "https://github.com/fission-codes/rs-ucan#readme", + "module": "dist/bundler/rs-ucan.js", + "types": "dist/nodejs/rs-ucan.d.ts", "exports": { ".": { "workerd": "./dist/web/workerd.js", - "browser": "./dist/bundler/rs_ucan.js", - "node": "./dist/nodejs/rs_ucan.cjs", - "default": "./dist/bundler/rs_ucan.js", - "types": "./dist/nodejs/rs_ucan.d.ts" + "browser": "./dist/bundler/rs-ucan.js", + "node": "./dist/nodejs/rs-ucan.cjs", + "default": "./dist/bundler/rs-ucan.js", + "types": "./dist/nodejs/rs-ucan.d.ts" }, "./nodejs": { - "default": "./dist/nodejs/rs_ucan.cjs", - "types": "./dist/nodejs/rs_ucan.d.ts" + "default": "./dist/nodejs/rs-ucan.cjs", + "types": "./dist/nodejs/rs-ucan.d.ts" }, "./web": { - "default": "./dist/web/rs_ucan.js", - "types": "./dist/web/rs_ucan.d.ts" + "default": "./dist/web/rs-ucan.js", + "types": "./dist/web/rs-ucan.d.ts" }, "./workerd": { "default": "./dist/web/workerd.js", - "types": "./dist/web/rs_ucan.d.ts" + "types": "./dist/web/rs-ucan.d.ts" } }, "files": [ @@ -59,7 +59,7 @@ } }, "opt": { - "command": "wasm-opt -O1 target/wasm32-unknown-unknown/$TARGET_DIR/rs_ucan.wasm -o target/wasm32-unknown-unknown/$TARGET_DIR/rs_ucan.wasm", + "command": "wasm-opt -O1 target/wasm32-unknown-unknown/$TARGET_DIR/rs-ucan.wasm -o target/wasm32-unknown-unknown/$TARGET_DIR/rs-ucan.wasm", "env": { "TARGET_DIR": { "external": true @@ -70,7 +70,7 @@ ] }, "bindgen:bundler": { - "command": "wasm-bindgen --weak-refs --target bundler --out-dir dist/bundler target/wasm32-unknown-unknown/$TARGET_DIR/rs_ucan.wasm", + "command": "wasm-bindgen --weak-refs --target bundler --out-dir dist/bundler target/wasm32-unknown-unknown/$TARGET_DIR/rs-ucan.wasm", "env": { "TARGET_DIR": { "external": true @@ -84,7 +84,7 @@ ] }, "bindgen:nodejs": { - "command": "wasm-bindgen --weak-refs --target nodejs --out-dir dist/nodejs target/wasm32-unknown-unknown/$TARGET_DIR/rs_ucan.wasm && move-file dist/nodejs/rs_ucan.js dist/nodejs/rs_ucan.cjs", + "command": "wasm-bindgen --weak-refs --target nodejs --out-dir dist/nodejs target/wasm32-unknown-unknown/$TARGET_DIR/rs-ucan.wasm && move-file dist/nodejs/rs-ucan.js dist/nodejs/rs-ucan.cjs", "env": { "TARGET_DIR": { "external": true @@ -98,7 +98,7 @@ ] }, "bindgen:web": { - "command": "wasm-bindgen --weak-refs --target web --out-dir dist/web target/wasm32-unknown-unknown/$TARGET_DIR/rs_ucan.wasm && cpy --flat src/workerd.js dist/web", + "command": "wasm-bindgen --weak-refs --target web --out-dir dist/web target/wasm32-unknown-unknown/$TARGET_DIR/rs-ucan.wasm && cpy --flat src/workerd.js dist/web", "env": { "TARGET_DIR": { "external": true @@ -112,7 +112,7 @@ ] }, "bindgen:deno": { - "command": "wasm-bindgen --weak-refs --target deno --out-dir dist/deno target/wasm32-unknown-unknown/$TARGET_DIR/rs_ucan.wasm", + "command": "wasm-bindgen --weak-refs --target deno --out-dir dist/deno target/wasm32-unknown-unknown/$TARGET_DIR/rs-ucan.wasm", "env": { "TARGET_DIR": { "external": true @@ -143,7 +143,7 @@ ] }, "test:chromium": { - "command": "pw-test tests/rs_ucan.test.js -r mocha --reporter json --cov > tests/report/chromium.json", + "command": "pw-test tests/rs-ucan.test.js -r mocha --reporter json --cov > tests/report/chromium.json", "dependencies": [ "build", "test:prepare" @@ -153,7 +153,7 @@ ] }, "test:firefox": { - "command": "pw-test tests/rs_ucan.test.js -r mocha --reporter json --browser firefox > tests/report/firefox.json", + "command": "pw-test tests/rs-ucan.test.js -r mocha --reporter json --browser firefox > tests/report/firefox.json", "dependencies": [ "build", "test:prepare" @@ -163,7 +163,7 @@ ] }, "test:webkit": { - "command": "pw-test tests/rs_ucan.test.js -r mocha --reporter json --browser webkit > tests/report/webkit.json", + "command": "pw-test tests/rs-ucan.test.js -r mocha --reporter json --browser webkit > tests/report/webkit.json", "dependencies": [ "build", "test:prepare" @@ -180,7 +180,7 @@ ] }, "test:node": { - "command": "pw-test tests/rs_ucan.test.js -r mocha --reporter json --mode node > tests/report/node.json", + "command": "pw-test tests/rs-ucan.test.js -r mocha --reporter json --mode node > tests/report/node.json", "dependencies": [ "build", "test:prepare" diff --git a/src/ability/crud.rs b/src/ability/crud.rs index d1c7775d..2c77c4da 100644 --- a/src/ability/crud.rs +++ b/src/ability/crud.rs @@ -2,6 +2,7 @@ use crate::{promise::Promise, prove::TryProve}; use std::{collections::BTreeMap, fmt::Debug}; use url::Url; +// FIXME move to promise.rs #[derive(Debug, Clone, PartialEq)] pub enum Field where diff --git a/src/ability/dynamic.rs b/src/ability/dynamic.rs index 7f60a9e5..a456704a 100644 --- a/src/ability/dynamic.rs +++ b/src/ability/dynamic.rs @@ -14,14 +14,14 @@ use wasm_bindgen::prelude::*; pub struct Dynamic<'a> { pub command: String, pub args: BTreeMap, // FIXME consider this being just JsValue - pub validater: &'a js_sys::Function, + pub validator: &'a js_sys::Function, } #[derive(Debug, Clone, PartialEq)] pub struct DynamicBuilder<'a> { pub command: String, pub args: Option>, - pub validater: &'a js_sys::Function, + pub validator: &'a js_sys::Function, } impl<'a> From> for DynamicBuilder<'a> { @@ -29,7 +29,7 @@ impl<'a> From> for DynamicBuilder<'a> { Self { command: dynamic.command.clone(), args: Some(dynamic.args), - validater: dynamic.validater, + validator: dynamic.validator, } } } @@ -73,6 +73,6 @@ impl<'a> TryProve> for DynamicBuilder<'a> { let js_self: JsValue = self.into().into(); let js_candidate: JsValue = candidate.into().into(); - self.validater.apply(js_self, js_candidate); + self.validator.apply(js_self, js_candidate); } } diff --git a/src/ability/msg.rs b/src/ability/msg.rs index 005cca90..08f9d41d 100644 --- a/src/ability/msg.rs +++ b/src/ability/msg.rs @@ -90,10 +90,8 @@ impl TryFrom<&Ipld> for MsgReceiveBuilder { } } -impl Command for MsgReceiveBuilder { - fn command(&self) -> &'static str { - "msg/receive" - } +impl Command for MsgReceive { + const COMMAND: &'static str = "msg/receive"; } impl TryFrom<&Ipld> for MsgReceive { diff --git a/src/ability/traits.rs b/src/ability/traits.rs index ff01dc09..c04c0a7a 100644 --- a/src/ability/traits.rs +++ b/src/ability/traits.rs @@ -1,34 +1,63 @@ -use std::fmt::Debug; +use libipld_core::ipld::Ipld; +use std::{collections::BTreeMap, fmt::Debug}; pub trait Command { - fn command(&self) -> &'static str; + const COMMAND: &'static str; } -// FIXME this is always a builder, right? ToBuilder? Builder? Buildable? // FIXME Delegable and make it proven? -pub trait Buildable { - type Builder: Command + Debug; // FIXME +pub trait Delegatable: Sized { + type Builder: Debug + TryInto + From; +} - fn to_builder(&self) -> Self::Builder; - fn try_build(builder: Self::Builder) -> Result, ()>; // FIXME check if this box (for objevt safety) is actually required +pub trait Resolvable: Sized { + type Awaiting: Command + Debug + TryInto + From; } -pub trait Resolvable: Buildable { - type Awaiting: Command + Debug; // FIXME +// FIXME Delegatable? +pub trait Runnable { + type Output; +} - fn to_awaitable(&self) -> Self::Awaiting; - fn try_into_resolved(promise: Self::Awaiting) -> Result, ()>; // FIXME check if this box (for objevt safety) is actually required +#[derive(Debug, Clone, PartialEq)] +pub struct DynJs { + pub cmd: &'static str, + pub args: BTreeMap, } -impl Command for T { - fn command(&self) -> &'static str { - self.to_builder().command() +impl Delegatable for DynJs { + type Builder = Self; +} + +#[derive(Debug, Clone, PartialEq)] +pub struct JsHack(pub DynJs); + +impl From for JsHack { + fn from(dyn_js: DynJs) -> Self { + Self(dyn_js) } } -pub trait Runnable { - type Output; +impl From for Ipld { + fn from(js_hack: JsHack) -> Self { + let mut map = BTreeMap::new(); + map.insert("command".into(), js_hack.0.cmd.into()); + map.into() + } +} + +impl TryFrom for DynJs { + type Error = (); // FIXME + + fn try_from(_ipld: Ipld) -> Result { + todo!() + } +} + +impl Command for JsHack { + const COMMAND: &'static str = "ucan/dyn/js"; } -pub trait Ability: Buildable + Runnable {} -// FIXME macro for Delegation (builder) and Promises +impl Delegatable for JsHack { + type Builder = Self; +} diff --git a/src/capsule.rs b/src/capsule.rs new file mode 100644 index 00000000..357bc5ec --- /dev/null +++ b/src/capsule.rs @@ -0,0 +1,3 @@ +pub trait Capsule { + const TAG: &'static str; +} diff --git a/src/delegation/payload.rs b/src/delegation/payload.rs index c78f4bca..81f07b26 100644 --- a/src/delegation/payload.rs +++ b/src/delegation/payload.rs @@ -1,7 +1,8 @@ use super::{condition::Condition, delegate::Delegate}; use crate::{ - ability::traits::{Buildable, Command}, - signature, + ability::traits::{Command, Delegatable}, + capsule::Capsule, + nonce::Nonce, time::Timestamp, }; use did_url::DID; @@ -9,7 +10,7 @@ use libipld_core::ipld::Ipld; use std::{collections::BTreeMap, fmt::Debug}; #[derive(Debug, Clone, PartialEq)] -pub struct Payload { +pub struct Payload { pub issuer: DID, pub subject: DID, pub audience: DID, @@ -18,20 +19,21 @@ pub struct Payload { pub conditions: Vec, // pub fixme: Vec>, pub metadata: BTreeMap, - pub nonce: Vec, + pub nonce: Nonce, pub expiration: Timestamp, pub not_before: Option, } -impl signature::Capsule for Payload { +impl Capsule for Payload { const TAG: &'static str = "ucan/d/1.0.0-rc.1"; } -impl + Clone> From<&Payload> for Ipld +impl + Clone> From<&Payload> + for Ipld where Ipld: From, - B::Builder: Clone, // FIXME + B::Builder: Command + Clone, // FIXME { fn from(payload: &Payload) -> Self { let mut map = BTreeMap::new(); @@ -41,10 +43,10 @@ where let can = match &payload.ability_builder { Delegate::Any => "ucan/*".into(), - Delegate::Specific(builder) => builder.command().into(), + Delegate::Specific(builder) => B::COMMAND.into(), }; - map.insert("can".into(), can); + map.insert("cmd".into(), can); map.insert( "args".into(), @@ -83,7 +85,8 @@ where } } -impl + Clone> From> for Ipld +impl + Clone> From> + for Ipld where Ipld: From, { @@ -95,10 +98,10 @@ where let can = match &payload.ability_builder { Delegate::Any => "ucan/*".into(), - Delegate::Specific(builder) => builder.command().into(), + Delegate::Specific(builder) => B::COMMAND.into(), }; - map.insert("can".into(), can); + map.insert("cmd".into(), can); map.insert( "args".into(), diff --git a/src/error.rs b/src/error.rs index 2fb4da11..98d665b4 100644 --- a/src/error.rs +++ b/src/error.rs @@ -27,7 +27,7 @@ pub enum Error { #[error(transparent)] PluginError(PluginError), /// Internal errors - #[error("An unexpected error occurred in rs_ucan: {msg}\n\nThis is a bug: please consider filing an issue at https://github.com/ucan-wg/rs_ucan/issues")] + #[error("An unexpected error occurred in rs-ucan: {msg}\n\nThis is a bug: please consider filing an issue at https://github.com/ucan-wg/rs-ucan/issues")] InternalUcanError { /// Error message msg: String, diff --git a/src/invocation.rs b/src/invocation.rs index 0d94da63..b58d5465 100644 --- a/src/invocation.rs +++ b/src/invocation.rs @@ -1,117 +1,6 @@ -use crate::{ - ability::traits::{Buildable, Command}, - delegation, - delegation::{condition::Condition, Delegate, Delegation}, - receipt::Receipt, - signature, - time::Timestamp, -}; -use did_url::DID; -use libipld_core::{cid::Cid, ipld::Ipld, link::Link}; -use std::{collections::BTreeMap, fmt::Debug}; +pub mod payload; -pub type Invocation = signature::Envelope>; - -#[derive(Debug, Clone, PartialEq)] -pub struct Payload { - pub issuer: DID, - pub subject: DID, - pub audience: Option, - - pub ability: B, - - // pub proofs: Vec>>, // FIXME just use Cid? - pub proofs: Vec, // FIXME just use Cid? - pub cause: Option, - pub metadata: BTreeMap, // FIXME serde value instead? - pub nonce: Vec, // Better type? - - pub expiration: Timestamp, - pub not_before: Option, -} - -// FIXME move that clone? -impl From<&Payload> for delegation::Payload { - fn from(invocation: &Payload) -> Self { - Self { - issuer: invocation.issuer.clone(), - subject: invocation.subject.clone(), - audience: invocation - .audience - .clone() - .unwrap_or(invocation.issuer.clone()), - ability_builder: Delegate::Specific(invocation.ability.clone().to_builder()), - conditions: vec![], - // fixme: vec![], - metadata: invocation.metadata.clone(), - nonce: invocation.nonce.clone(), - expiration: invocation.expiration.clone(), - not_before: invocation.not_before.clone(), - } - } -} - -impl signature::Capsule for Payload { - const TAG: &'static str = "ucan/i/1.0.0-rc.1"; -} - -impl> From> for Ipld { - fn from(payload: Payload) -> Self { - let mut map = BTreeMap::new(); - map.insert("iss".into(), payload.issuer.to_string().into()); - map.insert("sub".into(), payload.subject.to_string().into()); - map.insert( - "aud".into(), - payload - .audience - .map(|audience| audience.to_string()) - .unwrap_or(payload.issuer.to_string()) - .into(), - ); +use crate::signature; +use payload::Payload; - map.insert("can".into(), payload.ability.command().into()); - map.insert("args".into(), payload.ability.into()); - - map.insert( - "proofs".into(), - Ipld::List( - payload - .proofs - .into_iter() - .map(|cid| cid.into()) - // .map(|link| link.cid().into()) - .collect::>(), - ), - ); - - map.insert( - "cause".into(), - payload - .cause - // .map(|link| link.cid().into()) - .map(|cid| cid.into()) - .unwrap_or(Ipld::Null), - ); - - map.insert( - "metadata".into(), - Ipld::Map( - payload - .metadata - .into_iter() - .map(|(k, v)| (k, v.into())) - .collect::>(), - ), - ); - - map.insert("nonce".into(), payload.nonce.into()); - - map.insert("exp".into(), payload.expiration.into()); - - if let Some(not_before) = payload.not_before { - map.insert("nbf".into(), not_before.into()); - } - - map.into() - } -} +pub type Invocation = signature::Envelope>; diff --git a/src/invocation/payload.rs b/src/invocation/payload.rs new file mode 100644 index 00000000..2f445d37 --- /dev/null +++ b/src/invocation/payload.rs @@ -0,0 +1,137 @@ +use crate::{ + ability::traits::{Command, Delegatable, DynJs, JsHack}, + capsule::Capsule, + delegation, + delegation::{condition::Condition, Delegate}, + nonce::Nonce, + time::Timestamp, +}; +use did_url::DID; +use libipld_core::{cid::Cid, ipld::Ipld}; +use std::{collections::BTreeMap, fmt::Debug}; + +#[derive(Debug, Clone, PartialEq)] +pub struct Payload { + pub issuer: DID, + pub subject: DID, + pub audience: Option, + + pub ability: B, + + pub proofs: Vec, + pub cause: Option, + pub metadata: BTreeMap, // FIXME parameterize? + pub nonce: Nonce, + + pub expiration: Timestamp, + pub not_before: Option, +} + +// FIXME move that clone? +impl From<&Payload> for delegation::Payload { + fn from(invocation: &Payload) -> Self { + Self { + issuer: invocation.issuer.clone(), + subject: invocation.subject.clone(), + audience: invocation + .audience + .clone() + .unwrap_or(invocation.issuer.clone()), + ability_builder: Delegate::Specific(invocation.ability.clone().into()), + conditions: vec![], + metadata: invocation.metadata.clone(), + nonce: invocation.nonce.clone(), + expiration: invocation.expiration.clone(), + not_before: invocation.not_before.clone(), + } + } +} + +impl Capsule for Payload { + const TAG: &'static str = "ucan/i/1.0.0-rc.1"; +} + +impl> From> for Ipld { + fn from(payload: Payload) -> Self { + let mut map = BTreeMap::new(); + map.insert("iss".into(), payload.issuer.to_string().into()); + map.insert("sub".into(), payload.subject.to_string().into()); + map.insert( + "aud".into(), + payload + .audience + .map(|audience| audience.to_string()) + .unwrap_or(payload.issuer.to_string()) + .into(), + ); + + map.insert("cmd".into(), B::COMMAND.into()); + map.insert("args".into(), payload.ability.into()); + + map.insert( + "proofs".into(), + Ipld::List( + payload + .proofs + .into_iter() + .map(|cid| cid.into()) + .collect::>(), + ), + ); + + map.insert( + "cause".into(), + payload + .cause + .map(|cid| cid.into()) + .unwrap_or(Ipld::Null), + ); + + map.insert( + "metadata".into(), + Ipld::Map( + payload + .metadata + .into_iter() + .map(|(k, v)| (k, v.into())) + .collect::>(), + ), + ); + + map.insert("nonce".into(), payload.nonce.into()); + + map.insert("exp".into(), payload.expiration.into()); + + if let Some(not_before) = payload.not_before { + map.insert("nbf".into(), not_before.into()); + } + + map.into() + } +} + +// FIXME TEMPORARY HACK to prove out proof of concept +impl From> for Ipld { + fn from(payload: Payload) -> Self { + let hack_payload = Payload { + issuer: payload.issuer, + subject: payload.subject, + audience: payload.audience, + ability: JsHack(payload.ability.clone()), + proofs: payload.proofs, + cause: payload.cause, + metadata: payload.metadata, + nonce: payload.nonce, + expiration: payload.expiration, + not_before: payload.not_before, + }; + + if let Ipld::Map(mut map) = hack_payload.into() { + map.insert("cmd".into(), payload.ability.cmd.into()); + Ipld::Map(map) + } else { + // FIXME bleh this code + unreachable!() + } + } +} diff --git a/src/lib.rs b/src/lib.rs index 3d8f83d5..31d888b5 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -2,7 +2,7 @@ #![warn(missing_debug_implementations, missing_docs, rust_2018_idioms)] #![deny(unreachable_pub)] -//! rs_ucan +//! rs-ucan use std::str::FromStr; @@ -112,13 +112,15 @@ use std::fmt::Debug; // pub mod ability; +pub mod capsule; pub mod delegation; pub mod invocation; pub mod ipld; +pub mod nonce; pub mod promise; pub mod prove; pub mod receipt; -pub mod signature; // FIXME +pub mod signature; /// The empty fact #[derive(Debug, Clone, Default, Serialize, Deserialize)] diff --git a/src/nonce.rs b/src/nonce.rs new file mode 100644 index 00000000..74355690 --- /dev/null +++ b/src/nonce.rs @@ -0,0 +1,135 @@ +// use crate::{Error, Unit}; +use enum_as_inner::EnumAsInner; +use libipld_core::{ipld::Ipld, multibase::Base::Base32HexLower}; +use serde::{Deserialize, Serialize}; +use std::fmt; +use uuid::Uuid; + +// FIXME +pub struct Unit; +// FIXME +pub struct Error(T); + +/// Enumeration over allowed `nonce` types. +#[derive(Clone, Debug, PartialEq, EnumAsInner, Serialize, Deserialize)] +pub enum Nonce { + /// 96-bit, 12-byte nonce, e.g. [`xid`]. + Nonce96([u8; 12]), + /// 128-bit, 16-byte nonce. + Nonce128([u8; 16]), + /// No Nonce attributed. + Custom(Vec), +} + +impl Nonce { + /// Default generator, outputting an [`xid`] nonce, + /// which is a 96-bit (12-byte) nonce. + pub fn generate() -> Self { + Nonce::Nonce96(*xid::new().as_bytes()) + } + + /// Generate a default 128-bit(16-byte) nonce via [`Uuid::new_v4()`]. + pub fn generate_128() -> Self { + Nonce::Nonce128(*Uuid::new_v4().as_bytes()) + } +} + +impl fmt::Display for Nonce { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Nonce::Nonce96(nonce) => { + write!(f, "{}", Base32HexLower.encode(nonce.as_slice())) + } + Nonce::Nonce128(nonce) => { + write!(f, "{}", Base32HexLower.encode(nonce.as_slice())) + } + Nonce::Custom(nonce) => { + write!(f, "{}", Base32HexLower.encode(nonce.as_slice())) + } + } + } +} + +impl From for Ipld { + fn from(nonce: Nonce) -> Self { + match nonce { + Nonce::Nonce96(nonce) => Ipld::Bytes(nonce.to_vec()), + Nonce::Nonce128(nonce) => Ipld::Bytes(nonce.to_vec()), + Nonce::Custom(nonce) => Ipld::Bytes(nonce), + } + } +} + +impl TryFrom for Nonce { + type Error = (); // FIXME + + fn try_from(ipld: Ipld) -> Result { + if let Ipld::Bytes(v) = ipld { + match v.len() { + 12 => Ok(Nonce::Nonce96( + v.try_into() + .expect("12 bytes because we checked in the match"), + )), + 16 => Ok(Nonce::Nonce128( + v.try_into() + .expect("16 bytes because we checked in the match"), + )), + _ => Ok(Nonce::Custom(v)), + } + } else { + Err(()) + } + } +} + +impl TryFrom<&Ipld> for Nonce { + type Error = (); // FIXME + + fn try_from(ipld: &Ipld) -> Result { + TryFrom::try_from(ipld.to_owned()) + } +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn ipld_roundtrip_12() { + let gen = Nonce::generate(); + let ipld = Ipld::from(gen.clone()); + + let inner = if let Nonce::Nonce96(nonce) = gen { + Ipld::Bytes(nonce.to_vec()) + } else { + panic!("No conversion!") + }; + + assert_eq!(ipld, inner); + assert_eq!(gen, ipld.try_into().unwrap()); + } + + #[test] + fn ipld_roundtrip_16() { + let gen = Nonce::generate_128(); + let ipld = Ipld::from(gen.clone()); + + let inner = if let Nonce::Nonce128(nonce) = gen { + Ipld::Bytes(nonce.to_vec()) + } else { + panic!("No conversion!") + }; + + assert_eq!(ipld, inner); + assert_eq!(gen, ipld.try_into().unwrap()); + } + + #[test] + fn ser_de() { + let gen = Nonce::generate_128(); + let ser = serde_json::to_string(&gen).unwrap(); + let de = serde_json::from_str(&ser).unwrap(); + + assert_eq!(gen, de); + } +} diff --git a/src/promise.rs b/src/promise.rs index 5bf38a28..9d23ff51 100644 --- a/src/promise.rs +++ b/src/promise.rs @@ -1,20 +1,7 @@ -use crate::{ability::traits::Buildable, invocation, invocation::Invocation, signature::Capsule}; use cid::Cid; -use libipld_core::{ipld::Ipld, link::Link}; +use libipld_core::ipld::Ipld; use std::fmt::Debug; -// /// A [`Promise`] is a way to defer the presence of a value to the result of some [`Invocation`]. -// #[derive(Debug, Clone, PartialEq)] -// pub enum Promise -// where -// A: Ability, // FIXME MUST be an Invocation -// invocation::Payload: Capsule, -// { -// PromiseAny(Link>), // FIXME not sure about specifying the A here -// PromiseOk(Link>), -// PromiseErr(Link>), -// } - /// A [`Promise`] is a way to defer the presence of a value to the result of some [`Invocation`]. #[derive(Debug, Clone, PartialEq)] pub enum Promise { @@ -23,23 +10,28 @@ pub enum Promise { PromiseErr(Cid), } -// impl TryFrom for Promise -// where -// invocation::Payload: Capsule, -// { -// type Error = (); // FIXME -// -// fn try_from(ipld: Ipld) -> Result { -// if let Ipld::Map(btree) = ipld { -// if let Some(Ipld::Link(link)) = btree.get("await/ok") { -// return Ok(Self::PromiseOk(link.clone().into())); -// } else if let Some(Ipld::Link(link)) = btree.get("await/err") { -// return Ok(Self::PromiseErr(link.clone().into())); -// } else if let Some(Ipld::Link(link)) = btree.get("await/*") { -// return Ok(Self::PromiseAny(link.clone().into())); -// } -// } -// -// Err(()) -// } -// } +impl TryFrom for Promise { + type Error = (); // FIXME + + fn try_from(ipld: Ipld) -> Result { + if let Ipld::Map(btree) = ipld { + if btree.len() != 1 { + return Err(()); + } + + if let Some(Ipld::Link(link)) = btree.get("await/ok") { + return Ok(Self::PromiseOk(link.clone().into())); + } + + if let Some(Ipld::Link(link)) = btree.get("await/err") { + return Ok(Self::PromiseErr(link.clone().into())); + } + + if let Some(Ipld::Link(link)) = btree.get("await/*") { + return Ok(Self::PromiseAny(link.clone().into())); + } + } + + Err(()) + } +} diff --git a/src/receipt.rs b/src/receipt.rs index f8df1435..8f921123 100644 --- a/src/receipt.rs +++ b/src/receipt.rs @@ -1,30 +1,16 @@ use crate::{ - ability::traits::{Buildable, Command, Runnable}, - delegation::{condition::Condition, Delegation}, - invocation::Invocation, + ability::traits::{Command, Delegatable}, signature, - time::Timestamp, }; -use did_url::DID; -use libipld_core::{cid::Cid, ipld::Ipld, link::Link}; +use libipld_core::ipld::Ipld; use std::{collections::BTreeMap, fmt::Debug}; -pub type Receipt = signature::Envelope>; - -#[derive(Debug, Clone, PartialEq)] -pub struct Payload { - pub issuer: DID, - pub ran: Cid, - pub out: Result>, +pub mod payload; +use payload::Payload; - pub proofs: Vec, - pub metadata: BTreeMap, - pub issued_at: Option, -} +pub type Receipt = signature::Envelope>; -impl signature::Capsule for Payload { - const TAG: &'static str = "ucan/r/1.0.0-rc.1"; // FIXME extract out version -} +// FIXME show piping ability // FIXME #[derive(Debug, Clone, PartialEq)] @@ -33,22 +19,8 @@ pub struct ProxyExecute { pub args: BTreeMap, } -impl Buildable for ProxyExecute { +impl Delegatable for ProxyExecute { type Builder = ProxyExecuteBuilder; - - fn to_builder(&self) -> Self::Builder { - ProxyExecuteBuilder { - command: Some(self.command.clone()), - args: self.args.clone(), - } - } - - fn try_build(ProxyExecuteBuilder { command, args }: Self::Builder) -> Result, ()> { - match command { - None => Err(()), - Some(command) => Ok(Box::new(Self { command, args })), - } - } } // FIXME hmmm @@ -58,8 +30,26 @@ pub struct ProxyExecuteBuilder { pub args: BTreeMap, } -impl Command for ProxyExecuteBuilder { - fn command(&self) -> &'static str { - "ucan/proxy" // FIXME check spec +impl Command for ProxyExecute { + const COMMAND: &'static str = "ucan/proxy"; +} + +impl From for ProxyExecuteBuilder { + fn from(proxy: ProxyExecute) -> Self { + ProxyExecuteBuilder { + command: Some(ProxyExecute::COMMAND.into()), + args: proxy.args.clone(), + } + } +} + +impl TryFrom for ProxyExecute { + type Error = (); // FIXME + + fn try_from(ProxyExecuteBuilder { command, args }: ProxyExecuteBuilder) -> Result { + match command { + None => Err(()), + Some(command) => Ok(Self { command, args }), + } } } diff --git a/src/receipt/payload.rs b/src/receipt/payload.rs new file mode 100644 index 00000000..ad8588cf --- /dev/null +++ b/src/receipt/payload.rs @@ -0,0 +1,29 @@ +use crate::{ + ability::traits::{Command, Delegatable, Runnable}, + capsule::Capsule, + nonce::Nonce, + signature, + time::Timestamp, +}; +use did_url::DID; +use libipld_core::{cid::Cid, ipld::Ipld}; +use std::{collections::BTreeMap, fmt::Debug}; + +#[derive(Debug, Clone, PartialEq)] +pub struct Payload { + pub issuer: DID, + + pub ran: Cid, + pub out: Result>, + pub next: Vec, + + pub proofs: Vec, + pub metadata: BTreeMap, + + pub nonce: Nonce, + pub issued_at: Option, +} + +impl Capsule for Payload { + const TAG: &'static str = "ucan/r/1.0.0-rc.1"; // FIXME extract out version +} diff --git a/src/signature.rs b/src/signature.rs index 1966742f..12ceda4d 100644 --- a/src/signature.rs +++ b/src/signature.rs @@ -1,11 +1,9 @@ -use libipld_core::{ipld::Ipld, link::Link}; +use crate::capsule::Capsule; +use libipld_core::ipld::Ipld; use std::collections::BTreeMap; #[derive(Debug, Clone, PartialEq)] -pub struct Envelope -where - T: Capsule, -{ +pub struct Envelope { pub sig: Signature, pub payload: T, } @@ -20,11 +18,6 @@ pub enum Signature { }, } -// TODO move to own module? -pub trait Capsule { - const TAG: &'static str; -} - impl From<&Signature> for Ipld { fn from(sig: &Signature) -> Self { match sig { diff --git a/src/test_utils/rvg.rs b/src/test_utils/rvg.rs index 30e5c3ef..37b96b12 100644 --- a/src/test_utils/rvg.rs +++ b/src/test_utils/rvg.rs @@ -32,7 +32,7 @@ impl Rvg { /// # Example /// /// ``` - /// use rs_ucan::test_utils::Rvg; + /// use rs-ucan::test_utils::Rvg; /// /// let mut rvg = Rvg::new(); /// let int = rvg.sample(&(0..100i32)); @@ -49,7 +49,7 @@ impl Rvg { /// # Example /// /// ``` - /// use rs_ucan::test_utils::Rvg; + /// use rs-ucan::test_utils::Rvg; /// /// let mut rvg = Rvg::new(); /// let ints = rvg.sample_vec(&(0..100i32), 10); diff --git a/src/workerd.js b/src/workerd.js index ebd2009d..606cfdbb 100644 --- a/src/workerd.js +++ b/src/workerd.js @@ -1,6 +1,6 @@ // This entry point is inserted into ./lib/workerd to support Cloudflare workers -import WASM from "./rs_ucan_bg.wasm"; -import { initSync } from "./rs_ucan.js"; +import WASM from "./rs-ucan_bg.wasm"; +import { initSync } from "./rs-ucan.js"; initSync(WASM); -export * from "./rs_ucan.js"; +export * from "./rs-ucan.js"; diff --git a/tests/conformance.rs b/tests/conformance.rs index a69552fa..49973143 100644 --- a/tests/conformance.rs +++ b/tests/conformance.rs @@ -2,7 +2,7 @@ use libipld_core::{ipld::Ipld, raw::RawCodec}; use serde::{Deserialize, Serialize}; use std::{collections::HashMap, fs::File, io::BufReader, str::FromStr}; -use rs_ucan::{ +use rs-ucan::{ capability::DefaultCapabilityParser, did_verifier::DidVerifierMap, store::{self, Store}, @@ -297,7 +297,7 @@ impl TestTask for VerifyTest { return; } - if let Err(err) = ucan.validate(rs_ucan::time::now(), &did_verifier_map) { + if let Err(err) = ucan.validate(rs-ucan::time::now(), &did_verifier_map) { report.register_failure(name, err.to_string()); return; @@ -319,7 +319,7 @@ impl TestTask for RefuteTest { if let Ok(ucan) = Ucan::::from_str(&self.inputs.token) { if ucan - .validate(rs_ucan::time::now(), &did_verifier_map) + .validate(rs-ucan::time::now(), &did_verifier_map) .is_ok() { report.register_failure( diff --git a/tests/rs_ucan.test.js b/tests/rs_ucan.test.js index 3b00bf07..f1201309 100644 --- a/tests/rs_ucan.test.js +++ b/tests/rs_ucan.test.js @@ -1,5 +1,5 @@ import assert from "assert"; -import { build, decode } from "../dist/bundler/rs_ucan.js"; +import { build, decode } from "../dist/bundler/rs-ucan.js"; describe("decode", async function () { let ucan = await decode( From 9cb4126585f0e0019ed69a15d1606e5098011378 Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Fri, 26 Jan 2024 09:48:03 -0800 Subject: [PATCH 016/188] Remove -rs from package name --- .github/workflows/bench.yml | 2 +- .github/workflows/coverage.yml | 2 +- CONTRIBUTING.md | 4 +-- Cargo.toml | 6 ++--- README.md | 24 ++++++++--------- SECURITY.md | 6 ++--- benches/a_benchmark.rs | 4 +-- flake.nix | 4 +-- package.json | 48 +++++++++++++++++----------------- src/error.rs | 2 +- src/lib.rs | 2 +- src/test_utils/rvg.rs | 4 +-- src/workerd.js | 6 ++--- tests/conformance.rs | 6 ++--- tests/rs_ucan.test.js | 2 +- 15 files changed, 61 insertions(+), 61 deletions(-) diff --git a/.github/workflows/bench.yml b/.github/workflows/bench.yml index 76052f66..f7b2cc74 100644 --- a/.github/workflows/bench.yml +++ b/.github/workflows/bench.yml @@ -51,7 +51,7 @@ jobs: tool: 'cargo' output-file-path: output.txt github-token: ${{ secrets.GITHUB_TOKEN }} - auto-push: ${{ github.event_name == 'push' && github.repository == 'ucan-wg/rs-ucan' && github.ref == 'refs/heads/main' }} + auto-push: ${{ github.event_name == 'push' && github.repository == 'ucan-wg/ucan' && github.ref == 'refs/heads/main' }} alert-threshold: '200%' comment-on-alert: true fail-on-alert: true diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml index bbcf0c75..db763fcb 100644 --- a/.github/workflows/coverage.yml +++ b/.github/workflows/coverage.yml @@ -33,7 +33,7 @@ jobs: - name: Generate Code coverage env: CARGO_INCREMENTAL: '0' - LLVM_PROFILE_FILE: "rs-ucan-%p-%m.profraw" + LLVM_PROFILE_FILE: "ucan-%p-%m.profraw" RUSTFLAGS: '-Zprofile -Ccodegen-units=1 -Cinline-threshold=0 -Clink-dead-code -Coverflow-checks=off' RUSTDOCFLAGS: '-Zprofile -Ccodegen-units=1 -Cinline-threshold=0 -Clink-dead-code -Coverflow-checks=off' run: cargo test --all-features diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 2c6d8dcf..397b0e3f 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,4 +1,4 @@ -# Contributing to rs-ucan +# Contributing to ucan We welcome everyone to contribute what and where they can. Whether you are brand new, just want to contribute a little bit, or want to contribute a lot there is @@ -84,7 +84,7 @@ need to be the best programmer to contribute. Our discord is open for questions. - You can learn more about cloning repositories [here][git-clone]. 6. **Build** the project - - For a detailed look on how to build rs-ucan look at our + - For a detailed look on how to build ucan look at our [README file](./README.md). 7. **Start writing** your code diff --git a/Cargo.toml b/Cargo.toml index d9da4d64..cc9606be 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "rs-ucan" +name = "ucan" version = "0.2.0" description = "Rust implementation of UCAN" keywords = ["capabilities", "authorization", "ucan"] @@ -9,8 +9,8 @@ license = "Apache-2.0" readme = "README.md" edition = "2021" rust-version = "1.75" -documentation = "https://docs.rs/rs-ucan" -repository = "https://github.com/ucan-wg/rs-ucan" +documentation = "https://docs.rs/ucan" +repository = "https://github.com/ucan-wg/ucan" authors = ["Quinn Wilton ", "Brooklyn Zelenka - - rs-ucan Logo + + ucan Logo -

rs-ucan

+

ucan

- - Crate + + Crate - - Code Coverage + + Code Coverage - - Build Status + + Build Status - + License - + Docs @@ -34,7 +34,7 @@ Add the following to the `[dependencies]` section of your `Cargo.toml` file: ```toml -rs-ucan = "1.0.0-rc.1" +ucan = "1.0.0-rc.1" ``` ## Testing the Project diff --git a/SECURITY.md b/SECURITY.md index c74cc826..a7a3276b 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -1,6 +1,6 @@ ## Report a security issue or vulnerability -The `rs-ucan` team welcomes security reports and is committed to +The `ucan` team welcomes security reports and is committed to providing prompt attention to security issues. Security issues should be reported privately via [quinn@fission.codes][support-email]. Security issues should not be reported via the public GitHub Issue tracker. @@ -8,10 +8,10 @@ not be reported via the public GitHub Issue tracker. ## Security advisories The project team is committed to transparency in the security issue disclosure -process. The rs-ucan team announces security advisories through our +process. The ucan team announces security advisories through our Github respository's [security portal][sec-advisories] and and the [RustSec advisory database][rustsec-db]. [rustsec-db]: https://github.com/RustSec/advisory-db -[sec-advisories]: https://github.com/ucan-wg/rs-ucan/security/advisories +[sec-advisories]: https://github.com/ucan-wg/ucan/security/advisories [support-email]: mailto:quinn@fission.codes diff --git a/benches/a_benchmark.rs b/benches/a_benchmark.rs index 76f63eb8..fccc419e 100644 --- a/benches/a_benchmark.rs +++ b/benches/a_benchmark.rs @@ -1,13 +1,13 @@ use criterion::{criterion_group, criterion_main, Criterion}; pub fn add_benchmark(c: &mut Criterion) { - let mut rvg = rs-ucan::test_utils::Rvg::deterministic(); + let mut rvg = ucan::test_utils::Rvg::deterministic(); let int_val_1 = rvg.sample(&(0..100i32)); let int_val_2 = rvg.sample(&(0..100i32)); c.bench_function("add", |b| { b.iter(|| { - rs-ucan::add(int_val_1, int_val_2); + ucan::add(int_val_1, int_val_2); }) }); } diff --git a/flake.nix b/flake.nix index 947a2386..3e573152 100644 --- a/flake.nix +++ b/flake.nix @@ -1,5 +1,5 @@ { - description = "rs-ucan"; + description = "ucan"; inputs = { nixpkgs.url = "nixpkgs/nixos-23.11"; @@ -88,7 +88,7 @@ wasm-pack = "${pkgs.wasm-pack}/bin/wasm-pack"; in rec { devShells.default = pkgs.devshell.mkShell { - name = "rs-ucan"; + name = "ucan"; imports = [./pre-commit.nix]; diff --git a/package.json b/package.json index b657427b..93317fbb 100644 --- a/package.json +++ b/package.json @@ -1,10 +1,10 @@ { "name": "ucan", "version": "0.1.0", - "description": "A UCAN library built from rs-ucan", + "description": "A UCAN library built from ucan", "repository": { "type": "git", - "url": "git+https://github.com/fission-codes/rs-ucan.git" + "url": "git+https://github.com/fission-codes/ucan.git" }, "keywords": [ "authorization" @@ -12,30 +12,30 @@ "author": "", "license": "Apache-2.0", "bugs": { - "url": "https://github.com/fission-codes/rs-ucan/issues" + "url": "https://github.com/fission-codes/ucan/issues" }, - "homepage": "https://github.com/fission-codes/rs-ucan#readme", - "module": "dist/bundler/rs-ucan.js", - "types": "dist/nodejs/rs-ucan.d.ts", + "homepage": "https://github.com/fission-codes/ucan#readme", + "module": "dist/bundler/ucan.js", + "types": "dist/nodejs/ucan.d.ts", "exports": { ".": { "workerd": "./dist/web/workerd.js", - "browser": "./dist/bundler/rs-ucan.js", - "node": "./dist/nodejs/rs-ucan.cjs", - "default": "./dist/bundler/rs-ucan.js", - "types": "./dist/nodejs/rs-ucan.d.ts" + "browser": "./dist/bundler/ucan.js", + "node": "./dist/nodejs/ucan.cjs", + "default": "./dist/bundler/ucan.js", + "types": "./dist/nodejs/ucan.d.ts" }, "./nodejs": { - "default": "./dist/nodejs/rs-ucan.cjs", - "types": "./dist/nodejs/rs-ucan.d.ts" + "default": "./dist/nodejs/ucan.cjs", + "types": "./dist/nodejs/ucan.d.ts" }, "./web": { - "default": "./dist/web/rs-ucan.js", - "types": "./dist/web/rs-ucan.d.ts" + "default": "./dist/web/ucan.js", + "types": "./dist/web/ucan.d.ts" }, "./workerd": { "default": "./dist/web/workerd.js", - "types": "./dist/web/rs-ucan.d.ts" + "types": "./dist/web/ucan.d.ts" } }, "files": [ @@ -59,7 +59,7 @@ } }, "opt": { - "command": "wasm-opt -O1 target/wasm32-unknown-unknown/$TARGET_DIR/rs-ucan.wasm -o target/wasm32-unknown-unknown/$TARGET_DIR/rs-ucan.wasm", + "command": "wasm-opt -O1 target/wasm32-unknown-unknown/$TARGET_DIR/ucan.wasm -o target/wasm32-unknown-unknown/$TARGET_DIR/ucan.wasm", "env": { "TARGET_DIR": { "external": true @@ -70,7 +70,7 @@ ] }, "bindgen:bundler": { - "command": "wasm-bindgen --weak-refs --target bundler --out-dir dist/bundler target/wasm32-unknown-unknown/$TARGET_DIR/rs-ucan.wasm", + "command": "wasm-bindgen --weak-refs --target bundler --out-dir dist/bundler target/wasm32-unknown-unknown/$TARGET_DIR/ucan.wasm", "env": { "TARGET_DIR": { "external": true @@ -84,7 +84,7 @@ ] }, "bindgen:nodejs": { - "command": "wasm-bindgen --weak-refs --target nodejs --out-dir dist/nodejs target/wasm32-unknown-unknown/$TARGET_DIR/rs-ucan.wasm && move-file dist/nodejs/rs-ucan.js dist/nodejs/rs-ucan.cjs", + "command": "wasm-bindgen --weak-refs --target nodejs --out-dir dist/nodejs target/wasm32-unknown-unknown/$TARGET_DIR/ucan.wasm && move-file dist/nodejs/ucan.js dist/nodejs/ucan.cjs", "env": { "TARGET_DIR": { "external": true @@ -98,7 +98,7 @@ ] }, "bindgen:web": { - "command": "wasm-bindgen --weak-refs --target web --out-dir dist/web target/wasm32-unknown-unknown/$TARGET_DIR/rs-ucan.wasm && cpy --flat src/workerd.js dist/web", + "command": "wasm-bindgen --weak-refs --target web --out-dir dist/web target/wasm32-unknown-unknown/$TARGET_DIR/ucan.wasm && cpy --flat src/workerd.js dist/web", "env": { "TARGET_DIR": { "external": true @@ -112,7 +112,7 @@ ] }, "bindgen:deno": { - "command": "wasm-bindgen --weak-refs --target deno --out-dir dist/deno target/wasm32-unknown-unknown/$TARGET_DIR/rs-ucan.wasm", + "command": "wasm-bindgen --weak-refs --target deno --out-dir dist/deno target/wasm32-unknown-unknown/$TARGET_DIR/ucan.wasm", "env": { "TARGET_DIR": { "external": true @@ -143,7 +143,7 @@ ] }, "test:chromium": { - "command": "pw-test tests/rs-ucan.test.js -r mocha --reporter json --cov > tests/report/chromium.json", + "command": "pw-test tests/ucan.test.js -r mocha --reporter json --cov > tests/report/chromium.json", "dependencies": [ "build", "test:prepare" @@ -153,7 +153,7 @@ ] }, "test:firefox": { - "command": "pw-test tests/rs-ucan.test.js -r mocha --reporter json --browser firefox > tests/report/firefox.json", + "command": "pw-test tests/ucan.test.js -r mocha --reporter json --browser firefox > tests/report/firefox.json", "dependencies": [ "build", "test:prepare" @@ -163,7 +163,7 @@ ] }, "test:webkit": { - "command": "pw-test tests/rs-ucan.test.js -r mocha --reporter json --browser webkit > tests/report/webkit.json", + "command": "pw-test tests/ucan.test.js -r mocha --reporter json --browser webkit > tests/report/webkit.json", "dependencies": [ "build", "test:prepare" @@ -180,7 +180,7 @@ ] }, "test:node": { - "command": "pw-test tests/rs-ucan.test.js -r mocha --reporter json --mode node > tests/report/node.json", + "command": "pw-test tests/ucan.test.js -r mocha --reporter json --mode node > tests/report/node.json", "dependencies": [ "build", "test:prepare" diff --git a/src/error.rs b/src/error.rs index 98d665b4..a67fb364 100644 --- a/src/error.rs +++ b/src/error.rs @@ -27,7 +27,7 @@ pub enum Error { #[error(transparent)] PluginError(PluginError), /// Internal errors - #[error("An unexpected error occurred in rs-ucan: {msg}\n\nThis is a bug: please consider filing an issue at https://github.com/ucan-wg/rs-ucan/issues")] + #[error("An unexpected error occurred in ucan: {msg}\n\nThis is a bug: please consider filing an issue at https://github.com/ucan-wg/ucan/issues")] InternalUcanError { /// Error message msg: String, diff --git a/src/lib.rs b/src/lib.rs index 31d888b5..c827a85f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -2,7 +2,7 @@ #![warn(missing_debug_implementations, missing_docs, rust_2018_idioms)] #![deny(unreachable_pub)] -//! rs-ucan +//! ucan use std::str::FromStr; diff --git a/src/test_utils/rvg.rs b/src/test_utils/rvg.rs index 37b96b12..834c437e 100644 --- a/src/test_utils/rvg.rs +++ b/src/test_utils/rvg.rs @@ -32,7 +32,7 @@ impl Rvg { /// # Example /// /// ``` - /// use rs-ucan::test_utils::Rvg; + /// use ucan::test_utils::Rvg; /// /// let mut rvg = Rvg::new(); /// let int = rvg.sample(&(0..100i32)); @@ -49,7 +49,7 @@ impl Rvg { /// # Example /// /// ``` - /// use rs-ucan::test_utils::Rvg; + /// use ucan::test_utils::Rvg; /// /// let mut rvg = Rvg::new(); /// let ints = rvg.sample_vec(&(0..100i32), 10); diff --git a/src/workerd.js b/src/workerd.js index 606cfdbb..fad5ae1d 100644 --- a/src/workerd.js +++ b/src/workerd.js @@ -1,6 +1,6 @@ // This entry point is inserted into ./lib/workerd to support Cloudflare workers -import WASM from "./rs-ucan_bg.wasm"; -import { initSync } from "./rs-ucan.js"; +import WASM from "./ucan_bg.wasm"; +import { initSync } from "./ucan.js"; initSync(WASM); -export * from "./rs-ucan.js"; +export * from "./ucan.js"; diff --git a/tests/conformance.rs b/tests/conformance.rs index 49973143..46aa3a65 100644 --- a/tests/conformance.rs +++ b/tests/conformance.rs @@ -2,7 +2,7 @@ use libipld_core::{ipld::Ipld, raw::RawCodec}; use serde::{Deserialize, Serialize}; use std::{collections::HashMap, fs::File, io::BufReader, str::FromStr}; -use rs-ucan::{ +use ucan::{ capability::DefaultCapabilityParser, did_verifier::DidVerifierMap, store::{self, Store}, @@ -297,7 +297,7 @@ impl TestTask for VerifyTest { return; } - if let Err(err) = ucan.validate(rs-ucan::time::now(), &did_verifier_map) { + if let Err(err) = ucan.validate(ucan::time::now(), &did_verifier_map) { report.register_failure(name, err.to_string()); return; @@ -319,7 +319,7 @@ impl TestTask for RefuteTest { if let Ok(ucan) = Ucan::::from_str(&self.inputs.token) { if ucan - .validate(rs-ucan::time::now(), &did_verifier_map) + .validate(ucan::time::now(), &did_verifier_map) .is_ok() { report.register_failure( diff --git a/tests/rs_ucan.test.js b/tests/rs_ucan.test.js index f1201309..f8385e26 100644 --- a/tests/rs_ucan.test.js +++ b/tests/rs_ucan.test.js @@ -1,5 +1,5 @@ import assert from "assert"; -import { build, decode } from "../dist/bundler/rs-ucan.js"; +import { build, decode } from "../dist/bundler/ucan.js"; describe("decode", async function () { let ucan = await decode( From 7a5452f9b76c57b4bd4fba767de2e34c58235094 Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Fri, 26 Jan 2024 12:45:58 -0800 Subject: [PATCH 017/188] Thanks for the suggestions everyone! :tada: --- Cargo.toml | 7 +- src/ability/crud.rs | 6 +- src/ability/msg.rs | 82 ++++-------------- src/delegation/condition/common.rs | 132 +++++++++++++++++++---------- src/promise.rs | 41 ++++----- 5 files changed, 132 insertions(+), 136 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index cc9606be..58d0b05d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -46,22 +46,23 @@ erased-serde = "0.3.31" jose-b64 = { version = "0.1.2", features = ["serde", "json"] } k256 = { version = "0.13.1", features = ["ecdsa"], optional = true, default-features = false } lazy_static = "1.4.0" -libipld-core = "0.16.0" +libipld-core = { version = "0.16", features = ["serde-codec"] } multibase = "0.9.1" p256 = { version = "0.13.2", features = ["ecdsa"], optional = true, default-features = false } p384 = { version = "0.13.0", features = ["ecdsa"], optional = true, default-features = false } p521 = { version = "0.13.0", optional = true, default-features = false } proptest = { version = "1.1", optional = true } -regex = { version = "1.10" } +regex = "1.10" rsa = { version = "0.9.2", features = ["sha2"], optional = true, default-features = false } semver = "1.0.19" serde = { version = "1.0.188", features = ["derive"] } +serde_derive = "1.0" serde_json = "1.0.107" signature = { version = "2.1.0", features = ["alloc"] } thiserror = "1.0" tracing = "0.1.40" unsigned-varint = "0.7.2" -url = "2.5" +url = {version = "2.5", features = ["serde"]} web-time = "0.2.3" [target.'cfg(not(target_arch = "wasm32"))'.dependencies] diff --git a/src/ability/crud.rs b/src/ability/crud.rs index 2c77c4da..6f69d2f3 100644 --- a/src/ability/crud.rs +++ b/src/ability/crud.rs @@ -16,7 +16,7 @@ where // ... also maybe Ipld pub struct Crud { - uri: Field, + pub uri: Field, } pub struct CrudRead { @@ -24,7 +24,7 @@ pub struct CrudRead { } pub struct CrudMutate { - uri: Field, + pub uri: Field, } pub struct CrudCreate { @@ -32,11 +32,13 @@ pub struct CrudCreate { pub args: BTreeMap, Field>, } +#[derive(Debug, Clone, PartialEq)] pub struct CrudUpdate { pub uri: Field, pub args: BTreeMap, Field>, } +#[derive(Debug, Clone, PartialEq)] pub struct CrudDestroy { pub uri: Field, } diff --git a/src/ability/msg.rs b/src/ability/msg.rs index 08f9d41d..b7a12de8 100644 --- a/src/ability/msg.rs +++ b/src/ability/msg.rs @@ -1,15 +1,17 @@ use crate::{ability::traits::Command, prove::TryProve}; -use libipld_core::ipld::Ipld; -use std::{collections::BTreeMap, str::FromStr}; +use libipld_core::{ipld::Ipld, serde as ipld_serde}; +use serde_derive::{Deserialize, Serialize}; use url::Url; -#[derive(Debug, Clone, PartialEq, Eq)] +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[serde(deny_unknown_fields)] pub struct Msg { to: Url, from: Url, } -#[derive(Debug, Clone, PartialEq, Eq)] +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[serde(deny_unknown_fields)] pub struct MsgSend { to: Url, from: Url, @@ -17,13 +19,15 @@ pub struct MsgSend { } // TODO is the to or from often also the subject? Shoudl that be accounted for? -#[derive(Debug, Clone, PartialEq, Eq)] +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[serde(deny_unknown_fields)] pub struct MsgReceive { to: Url, from: Url, } -#[derive(Debug, Clone, PartialEq, Eq)] +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[serde(deny_unknown_fields)] pub struct MsgReceiveBuilder { to: Option, from: Option, @@ -52,41 +56,16 @@ impl TryFrom for MsgReceive { } impl From for Ipld { - fn from(msg: MsgReceive) -> Self { - let mut map = BTreeMap::new(); - map.insert("to".into(), msg.to.to_string().into()); - map.insert("from".into(), msg.from.to_string().into()); - map.into() + fn from(msg_rcv: MsgReceive) -> Self { + msg_rcv.into() } } -impl TryFrom<&Ipld> for MsgReceiveBuilder { +impl TryFrom for MsgReceiveBuilder { type Error = (); - fn try_from(ipld: &Ipld) -> Result { - match ipld { - Ipld::Map(map) => { - if map.len() > 2 { - return Err(()); // FIXME - } - - // FIXME - let to = if let Some(Ipld::String(to)) = map.get("to") { - Url::from_str(to).ok() // FIXME - } else { - None - }; - - let from = if let Some(Ipld::String(from)) = map.get("from") { - Url::from_str(from).ok() // FIXME - } else { - None - }; - - Ok(Self { to, from }) - } - _ => Err(()), - } + fn try_from(ipld: Ipld) -> Result { + ipld_serde::from_ipld(ipld).map_err(|_| ()) } } @@ -94,36 +73,11 @@ impl Command for MsgReceive { const COMMAND: &'static str = "msg/receive"; } -impl TryFrom<&Ipld> for MsgReceive { +impl TryFrom for MsgReceive { type Error = (); // FIXME - fn try_from(ipld: &Ipld) -> Result { - match ipld { - Ipld::Map(map) => { - if map.len() > 2 { - return Err(()); // FIXME - } - - // FIXME - let to = if let Some(Ipld::String(to)) = map.get("to") { - Url::from_str(to).ok() // FIXME - } else { - None - }; - - let from = if let Some(Ipld::String(from)) = map.get("from") { - Url::from_str(from).ok() // FIXME - } else { - None - }; - - Ok(Self { - to: to.unwrap(), - from: from.unwrap(), - }) - } - _ => Err(()), - } + fn try_from(ipld: Ipld) -> Result { + ipld_serde::from_ipld(ipld).map_err(|_| ()) } } diff --git a/src/delegation/condition/common.rs b/src/delegation/condition/common.rs index bd568c0b..8588b22b 100644 --- a/src/delegation/condition/common.rs +++ b/src/delegation/condition/common.rs @@ -1,7 +1,8 @@ use super::Condition; -use libipld_core::ipld::Ipld; +use libipld_core::{ipld::Ipld, serde as ipld_serde}; use regex::Regex; -use std::collections::BTreeMap; +use serde; +use serde_derive::{Deserialize, Serialize}; #[derive(Debug, Clone, PartialEq)] pub enum Common { @@ -16,47 +17,24 @@ pub enum Common { // FIXME dynamic js version? -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +#[serde(deny_unknown_fields)] pub struct ContainsAll { field: String, values: Vec, } -impl From for Ipld { - fn from(contains_all: ContainsAll) -> Self { - let mut map = BTreeMap::new(); - map.insert("field".into(), contains_all.field.into()); - map.insert("values".into(), contains_all.values.into()); - map.into() - } -} +// impl From for Ipld { +// fn from(contains_all: ContainsAll) -> Self { +// contains_all.into() +// } +// } -impl TryFrom<&Ipld> for ContainsAll { +impl TryFrom for ContainsAll { type Error = (); // FIXME - fn try_from(ipld: &Ipld) -> Result { - if let Ipld::Map(map) = ipld { - if map.len() != 2 { - return Err(()); - } - - if let Some(Ipld::String(field)) = map.get("field") { - let values = match map.get("values") { - None => Ok(vec![]), - Some(Ipld::List(values)) => Ok(values.to_vec()), - _ => Err(()), - }?; - - Ok(Self { - field: field.to_string(), - values: values.to_vec(), - }) - } else { - Err(()) - } - } else { - Err(()) - } + fn try_from(ipld: Ipld) -> Result { + ipld_serde::from_ipld(ipld).map_err(|_| ()) } } @@ -73,7 +51,8 @@ impl Condition for ContainsAll { } } -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +#[serde(deny_unknown_fields)] pub struct ContainsAny { field: String, value: Vec, @@ -92,7 +71,8 @@ impl Condition for ContainsAny { } } -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +#[serde(deny_unknown_fields)] pub struct ExcludesAll { field: String, value: Vec, @@ -111,18 +91,45 @@ impl Condition for ExcludesAll { } } -#[derive(Debug, Clone, PartialEq)] +impl TryFrom for ExcludesAll { + type Error = (); // FIXME + + fn try_from(ipld: Ipld) -> Result { + ipld_serde::from_ipld(ipld).map_err(|_| ()) + } +} + +// FIXME serialization? +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +#[serde(deny_unknown_fields)] pub enum Numeric { Float(f64), Integer(i128), } -#[derive(Debug, Clone, PartialEq)] +impl TryFrom for Numeric { + type Error = (); // FIXME + + fn try_from(ipld: Ipld) -> Result { + ipld_serde::from_ipld(ipld).map_err(|_| ()) + } +} + +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +#[serde(deny_unknown_fields)] pub struct MinNumber { field: String, value: Numeric, } +impl TryFrom for MinNumber { + type Error = (); // FIXME + + fn try_from(ipld: Ipld) -> Result { + ipld_serde::from_ipld(ipld).map_err(|_| ()) + } +} + impl Condition for MinNumber { fn validate(&self, ipld: &Ipld) -> bool { match ipld { @@ -139,7 +146,8 @@ impl Condition for MinNumber { } } -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +#[serde(deny_unknown_fields)] pub struct MaxNumber { field: String, value: Numeric, @@ -161,7 +169,16 @@ impl Condition for MaxNumber { } } -#[derive(Debug, Clone, PartialEq, Eq)] +impl TryFrom for MaxNumber { + type Error = (); // FIXME + + fn try_from(ipld: Ipld) -> Result { + ipld_serde::from_ipld(ipld).map_err(|_| ()) + } +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[serde(deny_unknown_fields)] pub struct MinLength { field: String, value: u64, @@ -178,7 +195,8 @@ impl Condition for MinLength { } } -#[derive(Debug, Clone, PartialEq, Eq)] +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[serde(deny_unknown_fields)] pub struct MaxLength { field: String, value: u64, @@ -195,7 +213,8 @@ impl Condition for MaxLength { } } -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[serde(deny_unknown_fields)] pub struct Matches { field: String, matcher: Matcher, @@ -210,6 +229,33 @@ impl PartialEq for Matcher { } } +impl Eq for Matcher {} + +impl serde::Serialize for Matcher { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + self.0.as_str().serialize(serializer) + } +} + +impl<'de> serde::Deserialize<'de> for Matcher { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + let s: &str = serde::Deserialize::deserialize(deserializer)?; + match Regex::new(s) { + Ok(regex) => Ok(Matcher(regex)), + Err(_) => { + // FIXME + todo!() + } + } + } +} + impl Condition for Matcher { fn validate(&self, ipld: &Ipld) -> bool { match ipld { diff --git a/src/promise.rs b/src/promise.rs index 9d23ff51..231be586 100644 --- a/src/promise.rs +++ b/src/promise.rs @@ -1,37 +1,30 @@ use cid::Cid; -use libipld_core::ipld::Ipld; +use libipld_core::{ipld::Ipld, serde as ipld_serde}; +use serde_derive::{Deserialize, Serialize}; use std::fmt::Debug; /// A [`Promise`] is a way to defer the presence of a value to the result of some [`Invocation`]. -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +#[serde(deny_unknown_fields)] // FIXME check that this is right, also pub enum Promise { - PromiseAny(Cid), // FIXME not sure about specifying the A here - PromiseOk(Cid), - PromiseErr(Cid), + PromiseAny { + #[serde(rename = "ucan/*")] // FIXME test to make sure that this is right? + await_any: Cid, + }, + PromiseOk { + #[serde(rename = "ucan/ok")] + await_ok: Cid, + }, + PromiseErr { + #[serde(rename = "ucan/err")] + await_err: Cid, + }, } impl TryFrom for Promise { type Error = (); // FIXME fn try_from(ipld: Ipld) -> Result { - if let Ipld::Map(btree) = ipld { - if btree.len() != 1 { - return Err(()); - } - - if let Some(Ipld::Link(link)) = btree.get("await/ok") { - return Ok(Self::PromiseOk(link.clone().into())); - } - - if let Some(Ipld::Link(link)) = btree.get("await/err") { - return Ok(Self::PromiseErr(link.clone().into())); - } - - if let Some(Ipld::Link(link)) = btree.get("await/*") { - return Ok(Self::PromiseAny(link.clone().into())); - } - } - - Err(()) + ipld_serde::from_ipld(ipld).map_err(|_| ()) } } From 454670cc806a82028c990256be3fbc09b061e0cb Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Fri, 26 Jan 2024 12:46:21 -0800 Subject: [PATCH 018/188] Update cargo --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 58d0b05d..cb865d2e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -62,7 +62,7 @@ signature = { version = "2.1.0", features = ["alloc"] } thiserror = "1.0" tracing = "0.1.40" unsigned-varint = "0.7.2" -url = {version = "2.5", features = ["serde"]} +url = { version = "2.5", features = ["serde"] } web-time = "0.2.3" [target.'cfg(not(target_arch = "wasm32"))'.dependencies] From a22b0c2147b4325fac83c83555c1542de933a879 Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Fri, 26 Jan 2024 22:27:10 -0800 Subject: [PATCH 019/188] Feels like a hack, but that's a workable serializaion strategy --- src/ability/any.rs | 2 +- src/ability/crud.rs | 26 ++-- src/ability/msg.rs | 115 ++++++++++++++++- src/ability/traits.rs | 41 +++--- src/delegation/delegate.rs | 36 ++---- src/delegation/payload.rs | 176 +++++++++++-------------- src/did.rs | 44 +++++++ src/invocation.rs | 3 +- src/invocation/payload.rs | 254 ++++++++++++++++++++++--------------- src/lib.rs | 1 + src/promise.rs | 16 ++- src/prove.rs | 4 +- src/time.rs | 25 ++-- 13 files changed, 448 insertions(+), 295 deletions(-) create mode 100644 src/did.rs diff --git a/src/ability/any.rs b/src/ability/any.rs index bac76d01..e6ff78ee 100644 --- a/src/ability/any.rs +++ b/src/ability/any.rs @@ -4,7 +4,7 @@ use std::convert::Infallible; #[derive(Debug, Clone, Copy, Eq, PartialEq)] pub struct DelegateAny; -impl<'a, T> TryProve<'a, DelegateAny> for T { +impl<'a, T> TryProve<&'a DelegateAny> for &'a T { type Error = Infallible; type Proven = T; diff --git a/src/ability/crud.rs b/src/ability/crud.rs index 6f69d2f3..5bf15133 100644 --- a/src/ability/crud.rs +++ b/src/ability/crud.rs @@ -2,45 +2,35 @@ use crate::{promise::Promise, prove::TryProve}; use std::{collections::BTreeMap, fmt::Debug}; use url::Url; -// FIXME move to promise.rs -#[derive(Debug, Clone, PartialEq)] -pub enum Field -where - T: Debug + Clone + PartialEq, -{ - Value(T), - Await(Promise), // FIXME -} - // FIXME macro to derive promise versions & delagted builder versions // ... also maybe Ipld pub struct Crud { - pub uri: Field, + pub uri: Url, } pub struct CrudRead { - pub uri: Field, + pub uri: Url, } pub struct CrudMutate { - pub uri: Field, + pub uri: Url, } pub struct CrudCreate { - pub uri: Field, - pub args: BTreeMap, Field>, + pub uri: Url, + pub args: BTreeMap, String>, } #[derive(Debug, Clone, PartialEq)] pub struct CrudUpdate { - pub uri: Field, - pub args: BTreeMap, Field>, + pub uri: Url, + pub args: BTreeMap, String>, } #[derive(Debug, Clone, PartialEq)] pub struct CrudDestroy { - pub uri: Field, + pub uri: Url, } // FIXME these should probably be behind a feature flag diff --git a/src/ability/msg.rs b/src/ability/msg.rs index b7a12de8..dda6eb85 100644 --- a/src/ability/msg.rs +++ b/src/ability/msg.rs @@ -1,4 +1,8 @@ -use crate::{ability::traits::Command, prove::TryProve}; +use crate::{ + ability::traits::{Command, Delegatable, Resolvable}, + promise::Deferrable, + prove::TryProve, +}; use libipld_core::{ipld::Ipld, serde as ipld_serde}; use serde_derive::{Deserialize, Serialize}; use url::Url; @@ -10,6 +14,79 @@ pub struct Msg { from: Url, } +impl Command for Msg { + const COMMAND: &'static str = "msg/*"; +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[serde(deny_unknown_fields)] +pub struct MsgBuilder { + #[serde(skip_serializing_if = "Option::is_none")] + to: Option, + + #[serde(skip_serializing_if = "Option::is_none")] + from: Option, +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[serde(deny_unknown_fields)] +pub struct MsgDeferrable { + to: Deferrable, + from: Deferrable, +} + +impl Delegatable for Msg { + type Builder = MsgBuilder; +} + +impl Resolvable for Msg { + type Awaiting = MsgDeferrable; +} + +impl From for MsgBuilder { + fn from(msg: Msg) -> Self { + Self { + to: Some(msg.to), + from: Some(msg.from), + } + } +} + +impl TryFrom for Msg { + type Error = (); + + fn try_from(builder: MsgBuilder) -> Result { + if let (Some(to), Some(from)) = (builder.clone().to, builder.clone().from) { + Ok(Self { to, from }) + } else { + Err(()) // FIXME + } + } +} + +impl From for MsgDeferrable { + fn from(msg: Msg) -> Self { + Self { + to: Deferrable::Resolved(msg.to), + from: Deferrable::Resolved(msg.from), + } + } +} + +impl TryFrom for Msg { + type Error = (); + + fn try_from(deferable: MsgDeferrable) -> Result { + if let (Deferrable::Resolved(to), Deferrable::Resolved(from)) = + (deferable.to, deferable.from) + { + Ok(Self { to, from }) + } else { + Err(()) // FIXME + } + } +} + #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] #[serde(deny_unknown_fields)] pub struct MsgSend { @@ -18,6 +95,25 @@ pub struct MsgSend { message: String, } +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[serde(deny_unknown_fields)] +pub struct MsgSendBuilder { + #[serde(skip_serializing_if = "Option::is_none")] + to: Option, + #[serde(skip_serializing_if = "Option::is_none")] + from: Option, + #[serde(skip_serializing_if = "Option::is_none")] + message: Option, +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[serde(deny_unknown_fields)] +pub struct MsgSendDeferrable { + to: Deferrable, + from: Deferrable, + message: Deferrable, +} + // TODO is the to or from often also the subject? Shoudl that be accounted for? #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] #[serde(deny_unknown_fields)] @@ -29,7 +125,9 @@ pub struct MsgReceive { #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] #[serde(deny_unknown_fields)] pub struct MsgReceiveBuilder { + #[serde(skip_serializing_if = "Option::is_none")] to: Option, + #[serde(skip_serializing_if = "Option::is_none")] from: Option, } @@ -69,6 +167,13 @@ impl TryFrom for MsgReceiveBuilder { } } +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[serde(deny_unknown_fields)] +pub struct MsgReceiveDeferrable { + to: Deferrable, + from: Deferrable, +} + impl Command for MsgReceive { const COMMAND: &'static str = "msg/receive"; } @@ -81,7 +186,7 @@ impl TryFrom for MsgReceive { } } -impl<'a> TryProve<'a, Msg> for Msg { +impl<'a> TryProve<&'a Msg> for &'a Msg { type Error = (); // FIXME type Proven = Msg; @@ -94,7 +199,7 @@ impl<'a> TryProve<'a, Msg> for Msg { } } -impl<'a> TryProve<'a, Msg> for MsgSend { +impl<'a> TryProve<&'a Msg> for &'a MsgSend { type Error = (); // FIXME type Proven = MsgSend; @@ -107,7 +212,7 @@ impl<'a> TryProve<'a, Msg> for MsgSend { } } -impl<'a> TryProve<'a, Msg> for MsgReceive { +impl<'a> TryProve<&'a Msg> for &'a MsgReceive { type Error = (); // FIXME type Proven = MsgReceive; @@ -121,7 +226,7 @@ impl<'a> TryProve<'a, Msg> for MsgReceive { } // FIXME this needs to work on builders! -impl<'a> TryProve<'a, MsgReceive> for MsgReceive { +impl<'a> TryProve<&'a MsgReceive> for &'a MsgReceive { type Error = (); // FIXME type Proven = MsgReceive; diff --git a/src/ability/traits.rs b/src/ability/traits.rs index c04c0a7a..775db6cf 100644 --- a/src/ability/traits.rs +++ b/src/ability/traits.rs @@ -1,4 +1,6 @@ -use libipld_core::ipld::Ipld; +use crate::prove::TryProve; +use libipld_core::{ipld::Ipld, serde as ipld_serde}; +use serde_derive::{Deserialize, Serialize}; use std::{collections::BTreeMap, fmt::Debug}; pub trait Command { @@ -8,10 +10,11 @@ pub trait Command { // FIXME Delegable and make it proven? pub trait Delegatable: Sized { type Builder: Debug + TryInto + From; + // type Builder: Debug + /*TryProve FIXME */ + TryInto + From; } pub trait Resolvable: Sized { - type Awaiting: Command + Debug + TryInto + From; + type Awaiting: Debug + TryInto + From; } // FIXME Delegatable? @@ -19,9 +22,10 @@ pub trait Runnable { type Output; } -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +#[serde(deny_unknown_fields)] pub struct DynJs { - pub cmd: &'static str, + pub cmd: String, pub args: BTreeMap, } @@ -29,35 +33,20 @@ impl Delegatable for DynJs { type Builder = Self; } -#[derive(Debug, Clone, PartialEq)] -pub struct JsHack(pub DynJs); - -impl From for JsHack { - fn from(dyn_js: DynJs) -> Self { - Self(dyn_js) - } +impl Resolvable for DynJs { + type Awaiting = Self; } -impl From for Ipld { - fn from(js_hack: JsHack) -> Self { - let mut map = BTreeMap::new(); - map.insert("command".into(), js_hack.0.cmd.into()); - map.into() +impl From for Ipld { + fn from(js: DynJs) -> Self { + js.into() } } impl TryFrom for DynJs { type Error = (); // FIXME - fn try_from(_ipld: Ipld) -> Result { - todo!() + fn try_from(ipld: Ipld) -> Result { + ipld_serde::from_ipld(ipld).map_err(|_| ()) } } - -impl Command for JsHack { - const COMMAND: &'static str = "ucan/dyn/js"; -} - -impl Delegatable for JsHack { - type Builder = Self; -} diff --git a/src/delegation/delegate.rs b/src/delegation/delegate.rs index 1b645f58..0046484a 100644 --- a/src/delegation/delegate.rs +++ b/src/delegation/delegate.rs @@ -1,39 +1,19 @@ -use libipld_core::ipld::Ipld; +use libipld_core::{ipld::Ipld, serde as ipld_serde}; +use serde_derive::{Deserialize, Serialize}; use std::fmt::Debug; -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)] +#[serde(untagged)] pub enum Delegate { + #[serde(rename = "ucan/*")] Any, Specific(T), } -impl<'a, T> From<&'a Delegate> for Ipld -where - Ipld: From<&'a T>, -{ - fn from(delegate: &'a Delegate) -> Self { - match delegate { - Delegate::Any => "ucan/*".into(), - Delegate::Specific(command) => command.into(), - } - } -} - -impl<'a, T: TryFrom<&'a Ipld>> TryFrom<&'a Ipld> for Delegate { +impl + serde::de::DeserializeOwned> TryFrom for Delegate { type Error = (); // FIXME - fn try_from(ipld: &'a Ipld) -> Result { - if let ipld_string @ Ipld::String(st) = ipld { - if st == "ucan/*" { - Ok(Self::Any) - } else { - match T::try_from(ipld_string) { - Err(_) => Err(()), - Ok(cmd) => Ok(Self::Specific(cmd)), - } - } - } else { - Err(()) - } + fn try_from(ipld: Ipld) -> Result { + ipld_serde::from_ipld(ipld).map_err(|_| ()) } } diff --git a/src/delegation/payload.rs b/src/delegation/payload.rs index 81f07b26..f5316e8e 100644 --- a/src/delegation/payload.rs +++ b/src/delegation/payload.rs @@ -1,23 +1,27 @@ use super::{condition::Condition, delegate::Delegate}; use crate::{ - ability::traits::{Command, Delegatable}, + ability::traits::{Command, Delegatable, DynJs}, capsule::Capsule, + did::Did, nonce::Nonce, + prove::TryProve, time::Timestamp, }; use did_url::DID; -use libipld_core::ipld::Ipld; +use libipld_core::{ipld::Ipld, serde as ipld_serde}; +use serde_derive::{Deserialize, Serialize}; use std::{collections::BTreeMap, fmt::Debug}; +use web_time::{SystemTime, UNIX_EPOCH}; #[derive(Debug, Clone, PartialEq)] -pub struct Payload { - pub issuer: DID, - pub subject: DID, - pub audience: DID, +pub struct Payload { + pub issuer: Did, + pub subject: Did, + pub audience: Did, - pub ability_builder: Delegate, + pub ability_builder: T::Builder, pub conditions: Vec, - // pub fixme: Vec>, + pub metadata: BTreeMap, pub nonce: Nonce, @@ -25,114 +29,86 @@ pub struct Payload { pub not_before: Option, } -impl Capsule for Payload { +impl Capsule for Payload +where + T::Builder: serde::Serialize + serde::de::DeserializeOwned, +{ const TAG: &'static str = "ucan/d/1.0.0-rc.1"; } -impl + Clone> From<&Payload> - for Ipld +// FIXME +impl TryFrom + for Payload where - Ipld: From, - B::Builder: Command + Clone, // FIXME + T::Builder: serde::Serialize + serde::de::DeserializeOwned, { - fn from(payload: &Payload) -> Self { - let mut map = BTreeMap::new(); - map.insert("iss".into(), payload.issuer.to_string().into()); - map.insert("sub".into(), payload.subject.to_string().into()); - map.insert("aud".into(), payload.audience.to_string().into()); - - let can = match &payload.ability_builder { - Delegate::Any => "ucan/*".into(), - Delegate::Specific(builder) => B::COMMAND.into(), - }; - - map.insert("cmd".into(), can); - - map.insert( - "args".into(), - match &payload.ability_builder { - Delegate::Any => Ipld::Map(BTreeMap::new()), - Delegate::Specific(builder) => (*builder).clone().into(), // FIXME - }, - ); - map.insert( - "cond".into(), - payload - .conditions - .iter() - .map(|condition| (*condition).clone().into()) - .collect::>() - .into(), - ); - map.insert( - "meta".into(), - payload - .metadata - .clone() - .into_iter() - .map(|(key, value)| (key, value.into())) - .collect::>() - .into(), - ); - map.insert("nonce".into(), payload.nonce.clone().into()); - map.insert("exp".into(), payload.expiration.clone().into()); + type Error = (); - if let Some(not_before) = &payload.not_before { - map.insert("nbf".into(), not_before.clone().into()); - } - - map.into() + fn try_from(ipld: Ipld) -> Result { + ipld_serde::from_ipld(ipld).unwrap() // FIXME } } -impl + Clone> From> - for Ipld +// impl + Clone> From> for Ipld { +// fn from(payload: Payload) -> Self { +// // FIXME I bet this clone can be removed by switcing to &DynJs +// if let Ipld::Map(mut map) = payload.clone().into() { +// map.insert("cmd".into(), payload.ability_builder.cmd.into()); +// map.insert("args".into(), payload.ability_builder.args.into()); +// map.into() +// } else { +// panic!("FIXME") +// } +// } +// } + +impl + Clone> From> for Ipld where - Ipld: From, + Ipld: From, { - fn from(payload: Payload) -> Self { - let mut map = BTreeMap::new(); - map.insert("iss".into(), payload.issuer.to_string().into()); - map.insert("sub".into(), payload.subject.to_string().into()); - map.insert("aud".into(), payload.audience.to_string().into()); - + fn from(payload: Payload) -> Self { let can = match &payload.ability_builder { Delegate::Any => "ucan/*".into(), - Delegate::Specific(builder) => B::COMMAND.into(), + Delegate::Specific(builder) => T::COMMAND.into(), }; - map.insert("cmd".into(), can); - - map.insert( - "args".into(), - match payload.ability_builder { - Delegate::Any => Ipld::Map(BTreeMap::new()), - Delegate::Specific(builder) => builder.into(), - }, - ); - map.insert( - "cond".into(), - payload - .conditions - .into_iter() - .map(|condition| condition.into()) - .collect::>() - .into(), - ); - map.insert( - "meta".into(), - payload - .metadata - .into_iter() - .map(|(key, value)| (key, value.into())) - .collect::>() - .into(), - ); - map.insert("nonce".into(), payload.nonce.into()); - map.insert("exp".into(), payload.expiration.into()); + let mut map = BTreeMap::from_iter([ + ("iss".into(), payload.issuer.to_string().into()), + ("sub".into(), payload.subject.to_string().into()), + ("aud".into(), payload.audience.to_string().into()), + ( + "args".into(), + match &payload.ability_builder { + Delegate::Any => Ipld::Map(BTreeMap::new()), + Delegate::Specific(builder) => (*builder).clone().into(), // FIXME + }, + ), + ("cmd".into(), can), + ( + "cond".into(), + payload + .conditions + .iter() + .map(|condition| (*condition).clone().into()) + .collect::>() + .into(), + ), + ( + "meta".into(), + payload + .metadata + .clone() + .into_iter() + .map(|(key, value)| (key, value.into())) + .collect::>() + .into(), + ), + ("nonce".into(), payload.nonce.clone().into()), + ("exp".into(), payload.expiration.clone().into()), + ]); - if let Some(not_before) = payload.not_before { - map.insert("nbf".into(), not_before.into()); + if let Some(not_before) = &payload.not_before { + map.insert("nbf".into(), not_before.clone().into()); } map.into() diff --git a/src/did.rs b/src/did.rs new file mode 100644 index 00000000..8b856104 --- /dev/null +++ b/src/did.rs @@ -0,0 +1,44 @@ +use did_url::DID; +use libipld_core::ipld::Ipld; +use serde::{Deserialize, Serialize}; +use std::fmt; + +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +#[serde(into = "String", try_from = "String")] +pub struct Did(DID); + +impl From for String { + fn from(did: Did) -> Self { + did.0.to_string() + } +} + +impl TryFrom for Did { + type Error = String; // FIXME + + fn try_from(string: String) -> Result { + DID::parse(&string) + .map_err(|err| format!("Failed to parse DID: {}", err)) + .map(Self) + } +} + +impl fmt::Display for Did { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", self.0.to_string()) + } +} + +impl From for Ipld { + fn from(did: Did) -> Self { + did.into() + } +} + +impl TryFrom for Did { + type Error = (); // FIXME + + fn try_from(ipld: Ipld) -> Result { + Self::try_from(ipld) + } +} diff --git a/src/invocation.rs b/src/invocation.rs index b58d5465..33842a80 100644 --- a/src/invocation.rs +++ b/src/invocation.rs @@ -1,6 +1,5 @@ pub mod payload; use crate::signature; -use payload::Payload; -pub type Invocation = signature::Envelope>; +pub type Invocation = signature::Envelope>; diff --git a/src/invocation/payload.rs b/src/invocation/payload.rs index 2f445d37..a8a0966d 100644 --- a/src/invocation/payload.rs +++ b/src/invocation/payload.rs @@ -1,137 +1,193 @@ use crate::{ - ability::traits::{Command, Delegatable, DynJs, JsHack}, + ability::traits::{Command, DynJs, Resolvable}, capsule::Capsule, - delegation, - delegation::{condition::Condition, Delegate}, + did::Did, nonce::Nonce, time::Timestamp, }; -use did_url::DID; -use libipld_core::{cid::Cid, ipld::Ipld}; +use libipld_core::{cid::Cid, ipld::Ipld, serde as ipld_serde}; +use serde::{Deserialize, Serialize, Serializer}; use std::{collections::BTreeMap, fmt::Debug}; #[derive(Debug, Clone, PartialEq)] -pub struct Payload { - pub issuer: DID, - pub subject: DID, - pub audience: Option, +pub struct Payload { + pub issuer: Did, + pub subject: Did, + pub audience: Option, - pub ability: B, + pub ability: T::Awaiting, pub proofs: Vec, pub cause: Option, pub metadata: BTreeMap, // FIXME parameterize? pub nonce: Nonce, - pub expiration: Timestamp, pub not_before: Option, + pub expiration: Timestamp, } -// FIXME move that clone? -impl From<&Payload> for delegation::Payload { - fn from(invocation: &Payload) -> Self { - Self { - issuer: invocation.issuer.clone(), - subject: invocation.subject.clone(), - audience: invocation - .audience - .clone() - .unwrap_or(invocation.issuer.clone()), - ability_builder: Delegate::Specific(invocation.ability.clone().into()), - conditions: vec![], - metadata: invocation.metadata.clone(), - nonce: invocation.nonce.clone(), - expiration: invocation.expiration.clone(), - not_before: invocation.not_before.clone(), - } - } +impl Capsule + for Payload +{ + const TAG: &'static str = "ucan/i/1.0.0-rc.1"; } -impl Capsule for Payload { - const TAG: &'static str = "ucan/i/1.0.0-rc.1"; +impl Serialize for Payload +where + InternalSerializer: From>, +{ + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + let s = InternalSerializer::from(*self); + Serialize::serialize(&s, serializer) + } } -impl> From> for Ipld { - fn from(payload: Payload) -> Self { - let mut map = BTreeMap::new(); - map.insert("iss".into(), payload.issuer.to_string().into()); - map.insert("sub".into(), payload.subject.to_string().into()); - map.insert( - "aud".into(), - payload - .audience - .map(|audience| audience.to_string()) - .unwrap_or(payload.issuer.to_string()) - .into(), - ); - - map.insert("cmd".into(), B::COMMAND.into()); - map.insert("args".into(), payload.ability.into()); - - map.insert( - "proofs".into(), - Ipld::List( - payload - .proofs - .into_iter() - .map(|cid| cid.into()) - .collect::>(), - ), - ); - - map.insert( - "cause".into(), - payload - .cause - .map(|cid| cid.into()) - .unwrap_or(Ipld::Null), - ); - - map.insert( - "metadata".into(), - Ipld::Map( - payload - .metadata - .into_iter() - .map(|(k, v)| (k, v.into())) - .collect::>(), - ), - ); - - map.insert("nonce".into(), payload.nonce.into()); - - map.insert("exp".into(), payload.expiration.into()); - - if let Some(not_before) = payload.not_before { - map.insert("nbf".into(), not_before.into()); +impl<'de, T: Resolvable + Debug> Deserialize<'de> for Payload +where + Payload: TryFrom, + as TryFrom>::Error: Debug, +{ + fn deserialize(d: D) -> Result + where + D: serde::Deserializer<'de>, + { + match InternalSerializer::deserialize(d) { + Err(e) => Err(e), + Ok(s) => s + .try_into() + .map_err(|e| serde::de::Error::custom(format!("{:?}", e))), // FIXME better error } + } +} + +impl TryFrom for Payload +where + Payload: TryFrom, +{ + type Error = (); // FIXME + + fn try_from(ipld: Ipld) -> Result { + let s: InternalSerializer = ipld_serde::from_ipld(ipld).map_err(|_| ())?; + s.try_into().map_err(|_| ()) // FIXME + } +} - map.into() +impl From> for Ipld { + fn from(payload: Payload) -> Self { + payload.into() } } -// FIXME TEMPORARY HACK to prove out proof of concept -impl From> for Ipld { - fn from(payload: Payload) -> Self { - let hack_payload = Payload { +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +struct InternalSerializer { + #[serde(rename = "iss")] + pub issuer: Did, + #[serde(rename = "sub")] + pub subject: Did, + #[serde(rename = "aud", skip_serializing_if = "Option::is_none")] + pub audience: Option, + + #[serde(rename = "cmd")] + pub command: String, + #[serde(rename = "args")] + pub arguments: BTreeMap, + + #[serde(rename = "prf")] + pub proofs: Vec, + #[serde(rename = "nonce")] + pub nonce: Nonce, + + #[serde(rename = "cause")] + pub cause: Option, + #[serde(rename = "meta")] + pub metadata: BTreeMap, + + #[serde(rename = "nbf", skip_serializing_if = "Option::is_none")] + pub not_before: Option, + #[serde(rename = "exp")] + pub expiration: Timestamp, +} + +impl From<&Payload> for InternalSerializer +where + BTreeMap: From, +{ + fn from(payload: &Payload) -> Self { + InternalSerializer { issuer: payload.issuer, subject: payload.subject, audience: payload.audience, - ability: JsHack(payload.ability.clone()), + + command: T::COMMAND.into(), + arguments: payload.ability.into(), + proofs: payload.proofs, cause: payload.cause, metadata: payload.metadata, + nonce: payload.nonce, - expiration: payload.expiration, + not_before: payload.not_before, - }; - - if let Ipld::Map(mut map) = hack_payload.into() { - map.insert("cmd".into(), payload.ability.cmd.into()); - Ipld::Map(map) - } else { - // FIXME bleh this code - unreachable!() + expiration: payload.expiration, + } + } +} + +impl TryFrom for InternalSerializer { + type Error = (); // FIXME + + fn try_from(ipld: Ipld) -> Result { + ipld_serde::from_ipld(ipld).map_err(|_| ()) + } +} + +impl From for Payload { + fn from(s: InternalSerializer) -> Self { + Payload { + issuer: s.issuer, + subject: s.subject, + audience: s.audience, + + ability: DynJs { + cmd: s.command, + args: s.arguments, + }, + + proofs: s.proofs, + cause: s.cause, + metadata: s.metadata, + + nonce: s.nonce, + + not_before: s.not_before, + expiration: s.expiration, } } } + +impl TryFrom> for InternalSerializer { + type Error = (); // FIXME + + fn try_from(p: Payload) -> Result { + Ok(InternalSerializer { + issuer: p.issuer, + subject: p.subject, + audience: p.audience, + + command: p.ability.cmd, + arguments: p.ability.args, + + proofs: p.proofs, + cause: p.cause, + metadata: p.metadata, + + nonce: p.nonce, + + not_before: p.not_before, + expiration: p.expiration, + }) + } +} diff --git a/src/lib.rs b/src/lib.rs index c827a85f..43a25a86 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -12,6 +12,7 @@ use serde::{de, Deserialize, Deserializer, Serialize}; pub mod builder; pub mod capability; pub mod crypto; +pub mod did; pub mod did_verifier; pub mod error; pub mod plugins; diff --git a/src/promise.rs b/src/promise.rs index 231be586..55962441 100644 --- a/src/promise.rs +++ b/src/promise.rs @@ -3,9 +3,19 @@ use libipld_core::{ipld::Ipld, serde as ipld_serde}; use serde_derive::{Deserialize, Serialize}; use std::fmt::Debug; +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[serde(untagged)] +pub enum Deferrable +where + T: Debug + Clone + PartialEq, +{ + Resolved(T), + Await(Promise), +} + /// A [`Promise`] is a way to defer the presence of a value to the result of some [`Invocation`]. -#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] -#[serde(deny_unknown_fields)] // FIXME check that this is right, also +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[serde(untagged, deny_unknown_fields)] // FIXME check that this is right, also pub enum Promise { PromiseAny { #[serde(rename = "ucan/*")] // FIXME test to make sure that this is right? @@ -22,7 +32,7 @@ pub enum Promise { } impl TryFrom for Promise { - type Error = (); // FIXME + type Error = (); fn try_from(ipld: Ipld) -> Result { ipld_serde::from_ipld(ipld).map_err(|_| ()) diff --git a/src/prove.rs b/src/prove.rs index e6962fbb..e9ba4f2c 100644 --- a/src/prove.rs +++ b/src/prove.rs @@ -7,9 +7,9 @@ /// more[...] /// more2[...] /// ``` -pub trait TryProve<'a, T> { +pub trait TryProve { type Error; type Proven; - fn try_prove(&'a self, candidate: &'a T) -> Result<&'a Self::Proven, Self::Error>; + fn try_prove(self, candidate: T) -> Result; } diff --git a/src/time.rs b/src/time.rs index b55ce9f9..09be5c5b 100644 --- a/src/time.rs +++ b/src/time.rs @@ -1,6 +1,7 @@ //! Time utilities -use libipld_core::ipld::Ipld; +use libipld_core::{ipld::Ipld, serde as ipld_serde}; +use serde_derive::{Deserialize, Serialize}; use web_time::{SystemTime, UNIX_EPOCH}; /// Get the current time in seconds since UNIX_EPOCH @@ -11,7 +12,8 @@ pub fn now() -> u64 { .as_secs() } -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +#[serde(untagged)] pub enum Timestamp { // FIXME probably overkill, but overflows are bad. Need to check on ingestion, too /// Per the spec, timestamps MUST respect [IEEE-754](https://en.wikipedia.org/wiki/IEEE_754) @@ -28,18 +30,19 @@ pub enum Timestamp { impl From for Ipld { fn from(timestamp: Timestamp) -> Self { - match timestamp { - Timestamp::Sending(js_time) => js_time.into(), - Timestamp::Receiving(sys_time) => sys_time - .duration_since(UNIX_EPOCH) - .expect("FIXME") - .as_secs() - .into(), - } + timestamp.into() + } +} + +impl TryFrom for Timestamp { + type Error = (); // FIXME + + fn try_from(ipld: Ipld) -> Result { + ipld_serde::from_ipld(ipld).map_err(|_| ()) } } -#[derive(Debug, Clone, PartialEq, Eq)] +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub struct JsTime { time: SystemTime, } From c69c7ba8e1a3ab50da1b4645961c442992bf1189 Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Fri, 26 Jan 2024 23:52:50 -0800 Subject: [PATCH 020/188] Ahead of proven contraint --- src/ability/any.rs | 4 +- src/ability/msg.rs | 16 +-- src/ability/traits.rs | 7 +- src/delegation/payload.rs | 241 +++++++++++++++++++++++++++----------- src/invocation/payload.rs | 24 ++-- src/prove.rs | 2 +- src/receipt/payload.rs | 48 +++++--- 7 files changed, 230 insertions(+), 112 deletions(-) diff --git a/src/ability/any.rs b/src/ability/any.rs index e6ff78ee..91f6b03e 100644 --- a/src/ability/any.rs +++ b/src/ability/any.rs @@ -6,9 +6,9 @@ pub struct DelegateAny; impl<'a, T> TryProve<&'a DelegateAny> for &'a T { type Error = Infallible; - type Proven = T; + type Proven = &'a T; - fn try_prove(&'a self, _proof: &'a DelegateAny) -> Result<&'a Self::Proven, Infallible> { + fn try_prove(self, _proof: &'a DelegateAny) -> Result { Ok(self) } } diff --git a/src/ability/msg.rs b/src/ability/msg.rs index dda6eb85..18da8489 100644 --- a/src/ability/msg.rs +++ b/src/ability/msg.rs @@ -188,9 +188,9 @@ impl TryFrom for MsgReceive { impl<'a> TryProve<&'a Msg> for &'a Msg { type Error = (); // FIXME - type Proven = Msg; + type Proven = &'a Msg; - fn try_prove(&'a self, candidate: &'a Msg) -> Result<&'a Self::Proven, ()> { + fn try_prove(self, candidate: &'a Msg) -> Result { if self == candidate { Ok(self) } else { @@ -201,9 +201,9 @@ impl<'a> TryProve<&'a Msg> for &'a Msg { impl<'a> TryProve<&'a Msg> for &'a MsgSend { type Error = (); // FIXME - type Proven = MsgSend; + type Proven = &'a MsgSend; - fn try_prove(&'a self, candidate: &'a Msg) -> Result<&'a Self::Proven, ()> { + fn try_prove(self, candidate: &'a Msg) -> Result { if self.to == candidate.to && self.from == candidate.from { Ok(self) } else { @@ -214,9 +214,9 @@ impl<'a> TryProve<&'a Msg> for &'a MsgSend { impl<'a> TryProve<&'a Msg> for &'a MsgReceive { type Error = (); // FIXME - type Proven = MsgReceive; + type Proven = &'a MsgReceive; - fn try_prove(&'a self, candidate: &'a Msg) -> Result<&'a Self::Proven, ()> { + fn try_prove(self, candidate: &'a Msg) -> Result { if self.to == candidate.to && self.from == candidate.from { Ok(self) } else { @@ -228,9 +228,9 @@ impl<'a> TryProve<&'a Msg> for &'a MsgReceive { // FIXME this needs to work on builders! impl<'a> TryProve<&'a MsgReceive> for &'a MsgReceive { type Error = (); // FIXME - type Proven = MsgReceive; + type Proven = &'a MsgReceive; - fn try_prove(&'a self, candidate: &'a MsgReceive) -> Result<&'a Self::Proven, ()> { + fn try_prove(self, candidate: &'a MsgReceive) -> Result { if self == candidate { Ok(self) } else { diff --git a/src/ability/traits.rs b/src/ability/traits.rs index 775db6cf..b4e46341 100644 --- a/src/ability/traits.rs +++ b/src/ability/traits.rs @@ -1,4 +1,4 @@ -use crate::prove::TryProve; +use crate::{did::Did, nonce::Nonce, prove::TryProve}; use libipld_core::{ipld::Ipld, serde as ipld_serde}; use serde_derive::{Deserialize, Serialize}; use std::{collections::BTreeMap, fmt::Debug}; @@ -10,16 +10,15 @@ pub trait Command { // FIXME Delegable and make it proven? pub trait Delegatable: Sized { type Builder: Debug + TryInto + From; - // type Builder: Debug + /*TryProve FIXME */ + TryInto + From; } pub trait Resolvable: Sized { type Awaiting: Debug + TryInto + From; } -// FIXME Delegatable? pub trait Runnable { - type Output; + type Output: Debug; + fn task_id(self, subject: &Did, nonce: &Nonce) -> String; } #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] diff --git a/src/delegation/payload.rs b/src/delegation/payload.rs index f5316e8e..94c1b7a2 100644 --- a/src/delegation/payload.rs +++ b/src/delegation/payload.rs @@ -1,19 +1,16 @@ -use super::{condition::Condition, delegate::Delegate}; +use super::condition::Condition; use crate::{ ability::traits::{Command, Delegatable, DynJs}, capsule::Capsule, did::Did, nonce::Nonce, - prove::TryProve, time::Timestamp, }; -use did_url::DID; use libipld_core::{ipld::Ipld, serde as ipld_serde}; -use serde_derive::{Deserialize, Serialize}; +use serde::{de::DeserializeOwned, Deserialize, Serialize, Serializer}; use std::{collections::BTreeMap, fmt::Debug}; -use web_time::{SystemTime, UNIX_EPOCH}; -#[derive(Debug, Clone, PartialEq)] +#[derive(Clone, PartialEq)] pub struct Payload { pub issuer: Did, pub subject: Did, @@ -29,88 +26,190 @@ pub struct Payload { pub not_before: Option, } -impl Capsule for Payload +impl Debug for Payload where - T::Builder: serde::Serialize + serde::de::DeserializeOwned, + T::Builder: Debug, { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("Payload") + .field("issuer", &self.issuer) + .field("subject", &self.subject) + .field("audience", &self.audience) + .field("ability_builder", &self.ability_builder) + .field("conditions", &self.conditions) + .field("metadata", &self.metadata) + .field("nonce", &self.nonce) + .field("expiration", &self.expiration) + .field("not_before", &self.not_before) + .finish() + } +} + +impl Capsule for Payload { const TAG: &'static str = "ucan/d/1.0.0-rc.1"; } -// FIXME -impl TryFrom +impl Serialize for Payload +where + InternalSerializer: From>, + Payload: Clone, +{ + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + let s = InternalSerializer::from(self.clone()); + Serialize::serialize(&s, serializer) + } +} + +impl<'de, T: Delegatable + Debug, C: Condition + DeserializeOwned> Deserialize<'de> + for Payload +where + Payload: TryFrom, + as TryFrom>::Error: Debug, +{ + fn deserialize(d: D) -> Result + where + D: serde::Deserializer<'de>, + { + match InternalSerializer::deserialize(d) { + Err(e) => Err(e), + Ok(s) => s + .try_into() + .map_err(|e| serde::de::Error::custom(format!("{:?}", e))), // FIXME better error + } + } +} + +impl TryFrom for Payload where - T::Builder: serde::Serialize + serde::de::DeserializeOwned, + Payload: TryFrom, { - type Error = (); + type Error = (); // FIXME fn try_from(ipld: Ipld) -> Result { - ipld_serde::from_ipld(ipld).unwrap() // FIXME + let s: InternalSerializer = ipld_serde::from_ipld(ipld).map_err(|_| ())?; + s.try_into().map_err(|_| ()) // FIXME } } -// impl + Clone> From> for Ipld { -// fn from(payload: Payload) -> Self { -// // FIXME I bet this clone can be removed by switcing to &DynJs -// if let Ipld::Map(mut map) = payload.clone().into() { -// map.insert("cmd".into(), payload.ability_builder.cmd.into()); -// map.insert("args".into(), payload.ability_builder.args.into()); -// map.into() -// } else { -// panic!("FIXME") -// } -// } -// } - -impl + Clone> From> for Ipld +impl From> for Ipld { + fn from(payload: Payload) -> Self { + payload.into() + } +} + +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +#[serde(deny_unknown_fields)] +struct InternalSerializer { + #[serde(rename = "iss")] + pub issuer: Did, + #[serde(rename = "sub")] + pub subject: Did, + #[serde(rename = "aud")] + pub audience: Did, + + #[serde(rename = "can")] + pub command: String, + #[serde(rename = "args")] + pub arguments: BTreeMap, + #[serde(rename = "cond")] + pub conditions: Vec, + + #[serde(rename = "nonce")] + pub nonce: Nonce, + #[serde(rename = "meta")] + pub metadata: BTreeMap, + + #[serde(rename = "nbf", skip_serializing_if = "Option::is_none")] + pub not_before: Option, + #[serde(rename = "exp")] + pub expiration: Timestamp, +} + +impl> From> + for InternalSerializer where - Ipld: From, + BTreeMap: From, { fn from(payload: Payload) -> Self { - let can = match &payload.ability_builder { - Delegate::Any => "ucan/*".into(), - Delegate::Specific(builder) => T::COMMAND.into(), - }; - - let mut map = BTreeMap::from_iter([ - ("iss".into(), payload.issuer.to_string().into()), - ("sub".into(), payload.subject.to_string().into()), - ("aud".into(), payload.audience.to_string().into()), - ( - "args".into(), - match &payload.ability_builder { - Delegate::Any => Ipld::Map(BTreeMap::new()), - Delegate::Specific(builder) => (*builder).clone().into(), // FIXME - }, - ), - ("cmd".into(), can), - ( - "cond".into(), - payload - .conditions - .iter() - .map(|condition| (*condition).clone().into()) - .collect::>() - .into(), - ), - ( - "meta".into(), - payload - .metadata - .clone() - .into_iter() - .map(|(key, value)| (key, value.into())) - .collect::>() - .into(), - ), - ("nonce".into(), payload.nonce.clone().into()), - ("exp".into(), payload.expiration.clone().into()), - ]); - - if let Some(not_before) = &payload.not_before { - map.insert("nbf".into(), not_before.clone().into()); + InternalSerializer { + issuer: payload.issuer, + subject: payload.subject, + audience: payload.audience, + + command: T::COMMAND.into(), + arguments: payload.ability_builder.into(), + conditions: payload.conditions.into_iter().map(|c| c.into()).collect(), + + metadata: payload.metadata, + nonce: payload.nonce, + + not_before: payload.not_before, + expiration: payload.expiration, } + } +} - map.into() +impl TryFrom for InternalSerializer { + type Error = (); // FIXME + + fn try_from(ipld: Ipld) -> Result { + ipld_serde::from_ipld(ipld).map_err(|_| ()) + } +} + +impl> TryFrom for Payload { + type Error = (); // FIXME + + fn try_from(s: InternalSerializer) -> Result, ()> { + Ok(Payload { + issuer: s.issuer, + subject: s.subject, + audience: s.audience, + + ability_builder: DynJs { + cmd: s.command, + args: s.arguments, + }, + conditions: s + .conditions + .iter() + .try_fold(Vec::new(), |mut acc, c| { + C::try_from(c.clone()).map(|x| { + acc.push(x); + acc + }) + }) + .map_err(|_| ())?, // FIXME better error (collect all errors + + metadata: s.metadata, + nonce: s.nonce, + + not_before: s.not_before, + expiration: s.expiration, + }) + } +} + +impl> From> for InternalSerializer { + fn from(p: Payload) -> Self { + InternalSerializer { + issuer: p.issuer, + subject: p.subject, + audience: p.audience, + + command: p.ability_builder.cmd, + arguments: p.ability_builder.args, + conditions: p.conditions.into_iter().map(|c| c.into()).collect(), + + metadata: p.metadata, + nonce: p.nonce, + + not_before: p.not_before, + expiration: p.expiration, + } } } diff --git a/src/invocation/payload.rs b/src/invocation/payload.rs index a8a0966d..a85716f6 100644 --- a/src/invocation/payload.rs +++ b/src/invocation/payload.rs @@ -10,7 +10,7 @@ use serde::{Deserialize, Serialize, Serializer}; use std::{collections::BTreeMap, fmt::Debug}; #[derive(Debug, Clone, PartialEq)] -pub struct Payload { +pub struct Payload { pub issuer: Did, pub subject: Did, pub audience: Option, @@ -26,26 +26,25 @@ pub struct Payload { pub expiration: Timestamp, } -impl Capsule - for Payload -{ +impl Capsule for Payload { const TAG: &'static str = "ucan/i/1.0.0-rc.1"; } -impl Serialize for Payload +impl Serialize for Payload where + Payload: Clone, InternalSerializer: From>, { fn serialize(&self, serializer: S) -> Result where S: Serializer, { - let s = InternalSerializer::from(*self); + let s = InternalSerializer::from(self.clone()); Serialize::serialize(&s, serializer) } } -impl<'de, T: Resolvable + Debug> Deserialize<'de> for Payload +impl<'de, T: Resolvable> Deserialize<'de> for Payload where Payload: TryFrom, as TryFrom>::Error: Debug, @@ -63,7 +62,7 @@ where } } -impl TryFrom for Payload +impl TryFrom for Payload where Payload: TryFrom, { @@ -75,13 +74,14 @@ where } } -impl From> for Ipld { +impl From> for Ipld { fn from(payload: Payload) -> Self { payload.into() } } #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +#[serde(deny_unknown_fields)] struct InternalSerializer { #[serde(rename = "iss")] pub issuer: Did, @@ -90,7 +90,7 @@ struct InternalSerializer { #[serde(rename = "aud", skip_serializing_if = "Option::is_none")] pub audience: Option, - #[serde(rename = "cmd")] + #[serde(rename = "do")] pub command: String, #[serde(rename = "args")] pub arguments: BTreeMap, @@ -111,11 +111,11 @@ struct InternalSerializer { pub expiration: Timestamp, } -impl From<&Payload> for InternalSerializer +impl From> for InternalSerializer where BTreeMap: From, { - fn from(payload: &Payload) -> Self { + fn from(payload: Payload) -> Self { InternalSerializer { issuer: payload.issuer, subject: payload.subject, diff --git a/src/prove.rs b/src/prove.rs index e9ba4f2c..65284d16 100644 --- a/src/prove.rs +++ b/src/prove.rs @@ -8,8 +8,8 @@ /// more2[...] /// ``` pub trait TryProve { - type Error; type Proven; + type Error; fn try_prove(self, candidate: T) -> Result; } diff --git a/src/receipt/payload.rs b/src/receipt/payload.rs index ad8588cf..ca496986 100644 --- a/src/receipt/payload.rs +++ b/src/receipt/payload.rs @@ -1,20 +1,17 @@ -use crate::{ - ability::traits::{Command, Delegatable, Runnable}, - capsule::Capsule, - nonce::Nonce, - signature, - time::Timestamp, -}; -use did_url::DID; -use libipld_core::{cid::Cid, ipld::Ipld}; +use crate::{ability::traits::Runnable, capsule::Capsule, did::Did, nonce::Nonce, time::Timestamp}; +use libipld_core::{cid::Cid, ipld::Ipld, serde as ipld_serde}; +use serde::{de::DeserializeOwned, Deserialize, Serialize, Serializer}; use std::{collections::BTreeMap, fmt::Debug}; -#[derive(Debug, Clone, PartialEq)] -pub struct Payload { - pub issuer: DID, +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub struct Payload +where + T::Output: Serialize + DeserializeOwned, +{ + pub issuer: Did, pub ran: Cid, - pub out: Result>, + pub out: Result>, pub next: Vec, pub proofs: Vec, @@ -24,6 +21,29 @@ pub struct Payload { pub issued_at: Option, } -impl Capsule for Payload { +impl Capsule for Payload +where + for<'de> T::Output: Serialize + Deserialize<'de>, +{ const TAG: &'static str = "ucan/r/1.0.0-rc.1"; // FIXME extract out version } + +impl TryFrom for Payload +where + for<'de> T::Output: Serialize + Deserialize<'de>, +{ + type Error = (); // FIXME + + fn try_from(ipld: Ipld) -> Result { + ipld_serde::from_ipld(ipld).map_err(|_| ()) + } +} + +impl From> for Ipld +where + for<'de> T::Output: Serialize + Deserialize<'de>, +{ + fn from(payload: Payload) -> Self { + payload.into() + } +} From f9c712d351f1097a73069cba12c7a0c15c347176 Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Sat, 27 Jan 2024 00:53:27 -0800 Subject: [PATCH 021/188] Actually on checking code afetr getting stuck with serde --- Cargo.toml | 2 + src/ability/traits.rs | 32 +++++++++++++++- src/delegation/payload.rs | 77 ++++++++++++++++++++++++++------------- src/time.rs | 9 +++++ 4 files changed, 93 insertions(+), 27 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index cb865d2e..a58dba08 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -47,7 +47,9 @@ jose-b64 = { version = "0.1.2", features = ["serde", "json"] } k256 = { version = "0.13.1", features = ["ecdsa"], optional = true, default-features = false } lazy_static = "1.4.0" libipld-core = { version = "0.16", features = ["serde-codec"] } +libipld-cbor = "0.16" multibase = "0.9.1" +multihash = {version = "0.19", features = ["serde"]} p256 = { version = "0.13.2", features = ["ecdsa"], optional = true, default-features = false } p384 = { version = "0.13.0", features = ["ecdsa"], optional = true, default-features = false } p521 = { version = "0.13.0", optional = true, default-features = false } diff --git a/src/ability/traits.rs b/src/ability/traits.rs index b4e46341..ebc18cf7 100644 --- a/src/ability/traits.rs +++ b/src/ability/traits.rs @@ -1,5 +1,12 @@ use crate::{did::Did, nonce::Nonce, prove::TryProve}; -use libipld_core::{ipld::Ipld, serde as ipld_serde}; +use libipld_cbor::DagCborCodec; +use libipld_core::{ + cid::{Cid, CidGeneric}, + codec::Encode, + ipld::Ipld, + multihash::{Code::Sha2_256, MultihashDigest}, + serde as ipld_serde, +}; use serde_derive::{Deserialize, Serialize}; use std::{collections::BTreeMap, fmt::Debug}; @@ -18,7 +25,7 @@ pub trait Resolvable: Sized { pub trait Runnable { type Output: Debug; - fn task_id(self, subject: &Did, nonce: &Nonce) -> String; + fn task_id(self, subject: Did, nonce: Nonce) -> Cid; } #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] @@ -36,6 +43,27 @@ impl Resolvable for DynJs { type Awaiting = Self; } +impl Runnable for DynJs { + type Output = Ipld; + + fn task_id(self, subject: Did, nonce: Nonce) -> Cid { + let ipld: Ipld = BTreeMap::from_iter([ + ("sub".into(), subject.into()), + ("do".into(), self.cmd.clone().into()), + ("args".into(), self.cmd.clone().into()), + ("nonce".into(), nonce.into()), + ]) + .into(); + + let mut encoded = vec![]; + ipld.encode(DagCborCodec, &mut encoded) + .expect("should never fail if `encodable_as` is implemented correctly"); + + let multihash = Sha2_256.digest(encoded.as_slice()); + CidGeneric::new_v1(DagCborCodec.into(), multihash) + } +} + impl From for Ipld { fn from(js: DynJs) -> Self { js.into() diff --git a/src/delegation/payload.rs b/src/delegation/payload.rs index 94c1b7a2..bed7f0e6 100644 --- a/src/delegation/payload.rs +++ b/src/delegation/payload.rs @@ -4,13 +4,15 @@ use crate::{ capsule::Capsule, did::Did, nonce::Nonce, + prove::TryProve, time::Timestamp, }; use libipld_core::{ipld::Ipld, serde as ipld_serde}; use serde::{de::DeserializeOwned, Deserialize, Serialize, Serializer}; use std::{collections::BTreeMap, fmt::Debug}; +use web_time::SystemTime; -#[derive(Clone, PartialEq)] +#[derive(Debug, Clone, PartialEq)] pub struct Payload { pub issuer: Did, pub subject: Did, @@ -26,30 +28,11 @@ pub struct Payload { pub not_before: Option, } -impl Debug for Payload -where - T::Builder: Debug, -{ - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.debug_struct("Payload") - .field("issuer", &self.issuer) - .field("subject", &self.subject) - .field("audience", &self.audience) - .field("ability_builder", &self.ability_builder) - .field("conditions", &self.conditions) - .field("metadata", &self.metadata) - .field("nonce", &self.nonce) - .field("expiration", &self.expiration) - .field("not_before", &self.not_before) - .finish() - } -} - impl Capsule for Payload { const TAG: &'static str = "ucan/d/1.0.0-rc.1"; } -impl Serialize for Payload +impl Serialize for Payload where InternalSerializer: From>, Payload: Clone, @@ -63,8 +46,7 @@ where } } -impl<'de, T: Delegatable + Debug, C: Condition + DeserializeOwned> Deserialize<'de> - for Payload +impl<'de, T: Delegatable, C: Condition + DeserializeOwned> Deserialize<'de> for Payload where Payload: TryFrom, as TryFrom>::Error: Debug, @@ -82,8 +64,7 @@ where } } -impl TryFrom - for Payload +impl TryFrom for Payload where Payload: TryFrom, { @@ -101,6 +82,52 @@ impl From> for Ipld { } } +impl<'a, T: Delegatable, C: Condition> Payload { + fn check( + &'a self, + proof: &'a Payload, + now: SystemTime, + ) -> Result< + // FIXME should return the entrue payload, unless we want to extract the fields + >::Proven, + >::Error, + > + where + T::Builder: TryProve<::Builder>, + { + if self.issuer != proof.audience { + todo!() + // return Err(()); + } + + if self.subject != proof.subject { + todo!() + // return Err(()); + } + + // FIXME that into needs to work on both sides + if let Some(nbf) = self.not_before.clone() { + if SystemTime::from(nbf) > now { + todo!() + // return Err(()); + } + } + + // FIXME that into needs to work on both sides + if SystemTime::from(self.expiration.clone()) > now { + todo!() + // return Err(()); + } + + // FIXME + // if self.conditions != proof.conditions { + // return Err(()); + // } + + self.ability_builder.try_prove(proof.ability_builder) + } +} + #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(deny_unknown_fields)] struct InternalSerializer { diff --git a/src/time.rs b/src/time.rs index 09be5c5b..29500062 100644 --- a/src/time.rs +++ b/src/time.rs @@ -28,6 +28,15 @@ pub enum Timestamp { Receiving(SystemTime), } +impl From for SystemTime { + fn from(timestamp: Timestamp) -> Self { + match timestamp { + Timestamp::Sending(js_time) => js_time.time, + Timestamp::Receiving(sys_time) => sys_time, + } + } +} + impl From for Ipld { fn from(timestamp: Timestamp) -> Self { timestamp.into() From 2ff88a1f952499f24c99140a82cadc2ff3bf4520 Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Sat, 27 Jan 2024 15:08:57 -0800 Subject: [PATCH 022/188] LOL wow the hacks, but honestly these will clean up nicely --- src/ability.rs | 6 +- src/ability/any.rs | 17 ++-- src/ability/crud.rs | 24 ++--- src/ability/dynamic.rs | 6 +- src/ability/msg.rs | 16 +-- src/ability/traits.rs | 4 +- src/condition.rs | 1 + src/delegation/payload.rs | 204 ++++++++++++++++++++++++++++++-------- src/invocation/payload.rs | 32 +++--- src/lib.rs | 2 +- src/prove.rs | 24 ++++- 11 files changed, 242 insertions(+), 94 deletions(-) create mode 100644 src/condition.rs diff --git a/src/ability.rs b/src/ability.rs index eda773d7..c9645a96 100644 --- a/src/ability.rs +++ b/src/ability.rs @@ -1,8 +1,8 @@ pub mod any; -pub mod crud; -pub mod msg; +// pub mod crud; +// pub mod msg; pub mod traits; -pub mod wasm; +// pub mod wasm; // TODO move to crate::wasm? #[cfg(feature = "wasm")] diff --git a/src/ability/any.rs b/src/ability/any.rs index 91f6b03e..5d970cad 100644 --- a/src/ability/any.rs +++ b/src/ability/any.rs @@ -1,14 +1,13 @@ -use crate::prove::TryProve; +// use crate::prove::Prove; use std::convert::Infallible; #[derive(Debug, Clone, Copy, Eq, PartialEq)] pub struct DelegateAny; -impl<'a, T> TryProve<&'a DelegateAny> for &'a T { - type Error = Infallible; - type Proven = &'a T; - - fn try_prove(self, _proof: &'a DelegateAny) -> Result { - Ok(self) - } -} +// impl Prove for DelegateAny { +// type Proven = Self; +// +// fn prove(self, _proof: Self) -> Self::Proven { +// self +// } +// } diff --git a/src/ability/crud.rs b/src/ability/crud.rs index 5bf15133..6fed603a 100644 --- a/src/ability/crud.rs +++ b/src/ability/crud.rs @@ -46,8 +46,8 @@ pub struct CrudDestroy { // impl TryProve for CrudDestroy { // type Error = (); // FIXME // type Proven = CrudDestroy; -// fn try_prove<'a>(&'a self, candidate: &'a CrudDestroy) -> Result<&'a Self::Proven, ()> { -// if self.uri == candidate.uri { +// fn try_prove<'a>(&'a self, proof: &'a CrudDestroy) -> Result<&'a Self::Proven, ()> { +// if self.uri == proof.uri { // Ok(self) // } else { // Err(()) @@ -60,8 +60,8 @@ pub struct CrudDestroy { // type Error = (); // FIXME // type Proven = CrudDestroy; // -// fn try_prove<'a>(&'a self, candidate: &'a CrudMutate) -> Result<&'a Self::Proven, ()> { -// if self.uri == candidate.uri { +// fn try_prove<'a>(&'a self, proof: &'a CrudMutate) -> Result<&'a Self::Proven, ()> { +// if self.uri == proof.uri { // Ok(self) // } else { // Err(()) @@ -73,8 +73,8 @@ pub struct CrudDestroy { // type Error = (); // type Proven = CrudRead; // -// fn try_prove<'a>(&'a self, candidate: &'a CrudRead) -> Result<&'a Self::Proven, ()> { -// if self.uri == candidate.uri { +// fn try_prove<'a>(&'a self, proof: &'a CrudRead) -> Result<&'a Self::Proven, ()> { +// if self.uri == proof.uri { // // FIXME contains & args // Ok(self) // } else { @@ -87,8 +87,8 @@ pub struct CrudDestroy { // type Error = (); // FIXME // type Proven = CrudRead; // -// fn try_prove<'a>(&'a self, candidate: &'a Crud) -> Result<&'a Self::Proven, ()> { -// if self.uri == candidate.uri { +// fn try_prove<'a>(&'a self, proof: &'a Crud) -> Result<&'a Self::Proven, ()> { +// if self.uri == proof.uri { // Ok(self) // } else { // Err(()) @@ -100,8 +100,8 @@ pub struct CrudDestroy { // type Error = (); // FIXME // type Proven = CrudMutate; // -// fn try_prove<'a>(&'a self, candidate: &'a Crud) -> Result<&'a Self::Proven, ()> { -// if self.uri == candidate.uri { +// fn try_prove<'a>(&'a self, proof: &'a Crud) -> Result<&'a Self::Proven, ()> { +// if self.uri == proof.uri { // Ok(self) // } else { // Err(()) @@ -115,9 +115,9 @@ pub struct CrudDestroy { // type Proven = C; // // // FIXME -// fn try_prove<'a>(&'a self, candidate: &'a Crud) -> Result<&'a C, ()> { +// fn try_prove<'a>(&'a self, proof: &'a Crud) -> Result<&'a C, ()> { // match self.try_prove(&CrudMutate { -// uri: candidate.uri.clone(), +// uri: proof.uri.clone(), // }) { // Ok(_) => Ok(self), // Err(_) => Err(()), diff --git a/src/ability/dynamic.rs b/src/ability/dynamic.rs index a456704a..b55a9e62 100644 --- a/src/ability/dynamic.rs +++ b/src/ability/dynamic.rs @@ -69,10 +69,10 @@ impl<'a> TryProve> for DynamicBuilder<'a> { type Error = JsError; type Proven = DynamicBuilder<'a>; // TODO docs: even if you parse a well-structred type, you MUST return a dynamic builder and continue checking that - fn try_prove(&'a self, candidate: &'a DynamicBuilder) -> Result<&'a Self::Proven, ()> { + fn try_prove(&'a self, proof: &'a DynamicBuilder) -> Result<&'a Self::Proven, ()> { let js_self: JsValue = self.into().into(); - let js_candidate: JsValue = candidate.into().into(); + let js_proof: JsValue = proof.into().into(); - self.validator.apply(js_self, js_candidate); + self.validator.apply(js_self, js_proof); } } diff --git a/src/ability/msg.rs b/src/ability/msg.rs index 18da8489..8b21f673 100644 --- a/src/ability/msg.rs +++ b/src/ability/msg.rs @@ -190,8 +190,8 @@ impl<'a> TryProve<&'a Msg> for &'a Msg { type Error = (); // FIXME type Proven = &'a Msg; - fn try_prove(self, candidate: &'a Msg) -> Result { - if self == candidate { + fn try_prove(self, proof: &'a Msg) -> Result { + if self == proof { Ok(self) } else { Err(()) @@ -203,8 +203,8 @@ impl<'a> TryProve<&'a Msg> for &'a MsgSend { type Error = (); // FIXME type Proven = &'a MsgSend; - fn try_prove(self, candidate: &'a Msg) -> Result { - if self.to == candidate.to && self.from == candidate.from { + fn try_prove(self, proof: &'a Msg) -> Result { + if self.to == proof.to && self.from == proof.from { Ok(self) } else { Err(()) @@ -216,8 +216,8 @@ impl<'a> TryProve<&'a Msg> for &'a MsgReceive { type Error = (); // FIXME type Proven = &'a MsgReceive; - fn try_prove(self, candidate: &'a Msg) -> Result { - if self.to == candidate.to && self.from == candidate.from { + fn try_prove(self, proof: &'a Msg) -> Result { + if self.to == proof.to && self.from == proof.from { Ok(self) } else { Err(()) @@ -230,8 +230,8 @@ impl<'a> TryProve<&'a MsgReceive> for &'a MsgReceive { type Error = (); // FIXME type Proven = &'a MsgReceive; - fn try_prove(self, candidate: &'a MsgReceive) -> Result { - if self == candidate { + fn try_prove(self, proof: &'a MsgReceive) -> Result { + if self == proof { Ok(self) } else { Err(()) diff --git a/src/ability/traits.rs b/src/ability/traits.rs index ebc18cf7..cf909ed7 100644 --- a/src/ability/traits.rs +++ b/src/ability/traits.rs @@ -19,8 +19,8 @@ pub trait Delegatable: Sized { type Builder: Debug + TryInto + From; } -pub trait Resolvable: Sized { - type Awaiting: Debug + TryInto + From; +pub trait Resolvable: Delegatable { + type Awaiting: Debug + TryInto + From + Into; } pub trait Runnable { diff --git a/src/condition.rs b/src/condition.rs new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/src/condition.rs @@ -0,0 +1 @@ + diff --git a/src/delegation/payload.rs b/src/delegation/payload.rs index bed7f0e6..39a8ff89 100644 --- a/src/delegation/payload.rs +++ b/src/delegation/payload.rs @@ -82,78 +82,200 @@ impl From> for Ipld { } } -impl<'a, T: Delegatable, C: Condition> Payload { - fn check( - &'a self, - proof: &'a Payload, +use crate::{ability::traits::Resolvable, invocation::payload as invocation}; + +impl<'a, T: ?Sized + Delegatable + Resolvable + Clone, C: Condition> Payload { + pub fn check( + invoked: invocation::Payload, // FIXME promiroy version + proofs: Vec>, now: SystemTime, - ) -> Result< - // FIXME should return the entrue payload, unless we want to extract the fields - >::Proven, - >::Error, - > + ) -> Result<(), ()> where - T::Builder: TryProve<::Builder>, + Ipld: From + From, + U: TryProve, + U::Builder: Clone + Delegatable, + ::Builder: TryProve + TryProve<::Builder>, + <::Builder as TryProve<::Builder>>::Error: Clone, + Prev: From<::Builder>, + Prev<::Builder>: From<::Builder>, + T: TryProve + TryProve<::Builder> + Clone, + ::Builder: From>, + ::Builder: From<::Builder>, + <::Builder as TryProve>::Error: Clone, + ::Builder: Clone + TryProve + TryProve, + >::Error: Clone, + ::Builder: TryProve< + <::Builder as TryProve<::Builder>>::Proven, + >, + T::Builder: TryProve, { - if self.issuer != proof.audience { - todo!() - // return Err(()); + let builder: T::Builder = invoked.into(); + let start: Prev = Prev { + issuer: invoked.issuer, + subject: invoked.subject, + ability_builder: Box::new(builder), + }; + + let ipld: Ipld = invoked.into(); + + // let result: Result, ()> = proofs.iter().fold(Ok(start), |prev, proof| { + // if let Ok(to_check) = prev { + // // FIXME check conditions against ipldified invoked + // match step(&to_check, &proof, &ipld, now) { + // Err(_) => Err(()), + // Ok(next) => Ok(Prev { + // issuer: proof.issuer, + // subject: proof.subject, + // ability_builder: Box::new(next), + // }), + // } + // } else { + // prev + // } + // }); + // + // match result { + // Ok(_) => Ok(()), + // Err(_) => Err(()), + // } + todo!() + } +} + +enum Either { + Left(Box), + Right(Box), +} + +// FIXME "CanProve" +trait ProofHack { + fn try_prove1(&self, proof: U) -> Result, ()>; +} + +impl ProofHack for T +where + T: TryProve, +{ + fn try_prove1(&self, proof: U) -> Result, ()> { + match self.try_prove(proof) { + Ok(_) => Ok(Either::Left(Box::new(()))), + Err(_) => Ok(Either::Right(Box::new(proof))), } + } +} - if self.subject != proof.subject { - todo!() - // return Err(()); +struct Prev { + issuer: Did, + subject: Did, + ability_builder: Box>, +} + +impl From> for Prev +where + T::Builder: ProofHack, +{ + fn from(invoked: invocation::Payload) -> Self { + Prev { + issuer: invoked.issuer, + subject: invoked.subject, + ability_builder: Box::new(invoked.ability.into()), } + } +} - // FIXME that into needs to work on both sides - if let Some(nbf) = self.not_before.clone() { - if SystemTime::from(nbf) > now { - todo!() - // return Err(()); - } +impl From> for Prev +where + T::Builder: ProofHack, +{ + fn from(delegation: Payload) -> Self { + Prev { + issuer: delegation.issuer, + subject: delegation.subject, + ability_builder: Box::new(delegation.ability_builder), } + } +} + +// FIXME this needs to move to Delegatable +fn step<'a, T, U: Delegatable, C: Condition>( + prev: &'a Prev, + proof: &'a Payload, + invoked_ipld: &'a Ipld, + now: SystemTime, +) -> () +// FIXME +where + T: TryProve<::Builder> + Clone, + U::Builder: Clone, + Ipld: From, + >::Error: Clone, +{ + if prev.issuer != proof.audience { + todo!() + } - // FIXME that into needs to work on both sides - if SystemTime::from(self.expiration.clone()) > now { + if prev.subject != proof.subject { + todo!() + } + + if let Some(nbf) = proof.not_before.clone() { + if SystemTime::from(nbf) > now { todo!() - // return Err(()); } + } - // FIXME - // if self.conditions != proof.conditions { - // return Err(()); - // } - - self.ability_builder.try_prove(proof.ability_builder) + if SystemTime::from(proof.expiration.clone()) > now { + todo!() } + + // FIXME check the spec + // if self.conditions != proof.conditions { + // return Err(()); + // } + + proof + .conditions + .iter() + .try_fold((), |_acc, c| { + if c.validate(&invoked_ipld) { + Ok(()) + } else { + Err(()) + } + }) + .expect("FIXME"); + + Box::leak(prev.ability_builder).try_prove1(proof.ability_builder.clone()); // So many clones that this may as well be owned + + () } #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(deny_unknown_fields)] struct InternalSerializer { #[serde(rename = "iss")] - pub issuer: Did, + issuer: Did, #[serde(rename = "sub")] - pub subject: Did, + subject: Did, #[serde(rename = "aud")] - pub audience: Did, + audience: Did, #[serde(rename = "can")] - pub command: String, + command: String, #[serde(rename = "args")] - pub arguments: BTreeMap, + arguments: BTreeMap, #[serde(rename = "cond")] - pub conditions: Vec, + conditions: Vec, #[serde(rename = "nonce")] - pub nonce: Nonce, + nonce: Nonce, #[serde(rename = "meta")] - pub metadata: BTreeMap, + metadata: BTreeMap, #[serde(rename = "nbf", skip_serializing_if = "Option::is_none")] - pub not_before: Option, + not_before: Option, #[serde(rename = "exp")] - pub expiration: Timestamp, + expiration: Timestamp, } impl> From> diff --git a/src/invocation/payload.rs b/src/invocation/payload.rs index a85716f6..e19e7b91 100644 --- a/src/invocation/payload.rs +++ b/src/invocation/payload.rs @@ -84,45 +84,53 @@ impl From> for Ipld { #[serde(deny_unknown_fields)] struct InternalSerializer { #[serde(rename = "iss")] - pub issuer: Did, + issuer: Did, #[serde(rename = "sub")] - pub subject: Did, + subject: Did, #[serde(rename = "aud", skip_serializing_if = "Option::is_none")] - pub audience: Option, + audience: Option, #[serde(rename = "do")] - pub command: String, + command: String, #[serde(rename = "args")] - pub arguments: BTreeMap, + arguments: BTreeMap, #[serde(rename = "prf")] - pub proofs: Vec, + proofs: Vec, #[serde(rename = "nonce")] - pub nonce: Nonce, + nonce: Nonce, #[serde(rename = "cause")] - pub cause: Option, + cause: Option, #[serde(rename = "meta")] - pub metadata: BTreeMap, + metadata: BTreeMap, #[serde(rename = "nbf", skip_serializing_if = "Option::is_none")] - pub not_before: Option, + not_before: Option, #[serde(rename = "exp")] - pub expiration: Timestamp, + expiration: Timestamp, } impl From> for InternalSerializer where BTreeMap: From, + Ipld: From, { fn from(payload: Payload) -> Self { + let bar: T::Awaiting = payload.ability; + let foo: Ipld = Ipld::from(payload.ability); + let arguments: BTreeMap = match foo { + Ipld::Map(btree) => btree, + _ => panic!("FIXME"), + }; + InternalSerializer { issuer: payload.issuer, subject: payload.subject, audience: payload.audience, command: T::COMMAND.into(), - arguments: payload.ability.into(), + arguments, proofs: payload.proofs, cause: payload.cause, diff --git a/src/lib.rs b/src/lib.rs index 43a25a86..fae01988 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -98,7 +98,7 @@ use std::fmt::Debug; // type Error = (); // type Proven = DynamicJs; // -// fn try_prove<'a>(&'a self, candidate: &'a DynamicJs) -> Result<&'a DynamicJs, ()> { +// fn try_prove<'a>(&'a self, proof: &'a DynamicJs) -> Result<&'a DynamicJs, ()> { // // } // } diff --git a/src/prove.rs b/src/prove.rs index 65284d16..b6ddc45d 100644 --- a/src/prove.rs +++ b/src/prove.rs @@ -1,15 +1,33 @@ +use std::convert::Infallible; + #[cfg_attr(doc, aquamarine::aquamarine)] /// FIXME /// /// ```mermaid /// flowchart LR -/// Invocation --> more --> Self --> Candidate --> more2 +/// Invocation --> more --> Self --> Proof --> more2 /// more[...] /// more2[...] /// ``` -pub trait TryProve { +pub trait TryProve { type Proven; type Error; - fn try_prove(self, candidate: T) -> Result; + // FIXME rename to proof? + fn try_prove(&self, proof: T) -> Result; } + +// pub trait Prove { +// type Proven; +// +// fn prove(self, proof: T) -> Self::Proven; +// } +// +// impl + ?Sized, U> TryProve for T { +// type Proven = T::Proven; +// type Error = Infallible; +// +// fn try_prove(&self, proof: U) -> Result { +// Ok(self.prove(proof)) +// } +// } From 7bfc238378a640f67779ebbe408884cebd03bc6f Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Sat, 27 Jan 2024 23:52:46 -0800 Subject: [PATCH 023/188] Promising direction! --- src/ability/traits.rs | 263 ++++++++++++++++++++++++++++++++++++++ src/delegation/payload.rs | 131 +++++++++---------- src/invocation/payload.rs | 4 +- 3 files changed, 324 insertions(+), 74 deletions(-) diff --git a/src/ability/traits.rs b/src/ability/traits.rs index cf909ed7..58478d2c 100644 --- a/src/ability/traits.rs +++ b/src/ability/traits.rs @@ -77,3 +77,266 @@ impl TryFrom for DynJs { ipld_serde::from_ipld(ipld).map_err(|_| ()) } } + +//////////////////////// +//////////////////////// +//////////////////////// +//////////////////////// +//////////////////////// +//////////////////////// +//////////////////////// +//////////////////////// + +// trait IntoCheckable { +// type Checkable; +// fn to_checkable(&self) -> Self::Checkable; +// } +// +// trait Checkable { +// type Checker; +// fn check(me: &Self::Checker, other: &Self::Checker) -> bool; +// } + +struct CrudAny; +struct CrudMutate; +struct CrudCreate; +struct CrudUpdate; +struct CrudDestroy; + +enum Either { + Left(Box), + Right(Box), +} + +enum Wrapper<'a, T: ?Sized, P> { + Any, + Parents(&'a P), + Me(&'a T), +} + +// NOTE must remain unexported! +trait IsChecker {} + +impl IsChecker for Parentful {} +impl IsChecker for Parentless {} + +pub trait HasChecker { + type CheckAs: IsChecker; +} + +trait CheckSelf { + fn check_against_self(&self, other: &Self) -> bool; +} + +impl HasChecker for CrudCreate { + type CheckAs = Parentful; +} + +impl CheckSelf for CrudCreate { + fn check_against_self(&self, _other: &Self) -> bool { + true + } +} + +impl CheckSelf for CrudUpdate { + fn check_against_self(&self, _other: &Self) -> bool { + true + } +} + +impl HasChecker for CrudUpdate { + type CheckAs = Parentful; +} + +impl CheckSelf for CrudDestroy { + fn check_against_self(&self, _other: &Self) -> bool { + true + } +} + +impl HasChecker for CrudDestroy { + type CheckAs = Parentful; +} + +impl CheckSelf for CrudMutate { + fn check_against_self(&self, _other: &Self) -> bool { + true + } +} + +impl HasChecker for CrudMutate { + type CheckAs = Parentful; +} + +impl CheckSelf for CrudAny { + fn check_against_self(&self, _other: &Self) -> bool { + true + } +} + +impl HasChecker for CrudAny { + type CheckAs = Parentless; +} + +pub enum Parentful { + Any, + Parents(T::Parents), + Me(T), +} + +pub enum Parentless { + Any, + Me(T), +} + +pub trait CheckParents: CheckSelf { + type Parents; + + fn check_against_parents(&self, other: &Self::Parents) -> bool; +} + +pub trait JustCheck { + fn check<'a>(&'a self, other: &'a T) -> bool; +} + +impl JustCheck> for T { + fn check<'a>(&'a self, other: &'a Parentless) -> bool { + match other { + Parentless::Any => true, + Parentless::Me(me) => self.check_against_self(&me), + } + } +} + +impl JustCheck> for T { + fn check<'a>(&'a self, other: &'a Parentful) -> bool { + match other { + Parentful::Any => true, + Parentful::Parents(parents) => self.check_against_parents(parents), + Parentful::Me(me) => self.check_against_self(&me), + } + } +} + +// trait JustCheckNormalized {} +// +// impl JustCheckNormalized for T {} + +// impl JustCheck for T { +// type ToCheck = Parentful; +// } +// +// trait EvenMoreCheck: JustCheck { +// fn check<'a>(&'a self, other: Self::ToCheck) -> bool; +// } +// +// impl>> EvenMoreCheck for T { +// fn check<'a>(&'a self, other: Parentless) -> bool { +// match other { +// Parentless::Any => true, +// Parentless::Me(me) => self.check_against_self(&me), +// } +// } +// } +// +// impl>> EvenMoreCheck for T { +// fn check<'a>(&'a self, other: Parentless) -> bool { +// match other { +// Parentful::Any => true, +// Parentful::Parents(parents) => self.check_against_parents(parents), +// Parentful::Me(me) => self.check_against_self(&me), +// } +// } +// } + +// impl JustCheck for T {} + +// impl Parentless for CrudAny {} + +// TODO note to self, this is effectively a partial order +impl CheckParents for CrudMutate { + type Parents = CrudAny; + + fn check_against_parents(&self, other: &Self::Parents) -> bool { + true + } +} + +impl CheckSelf for Either { + fn check_against_self(&self, other: &Self) -> bool { + match self { + Either::Left(mutate) => match other { + Either::Left(other_mutate) => mutate.check_against_self(other_mutate), + Either::Right(any) => mutate.check_against_parents(any), + }, + _ => false, + } + } +} + +impl CheckParents for CrudCreate { + type Parents = Either; + + fn check_against_parents(&self, other: &Self::Parents) -> bool { + match other { + Either::Left(mutate) => true, + Either::Right(any) => true, + } + } +} + +impl CheckParents for CrudUpdate { + type Parents = Either; + + fn check_against_parents(&self, other: &Self::Parents) -> bool { + match other { + Either::Left(mutate) => true, + Either::Right(any) => true, + } + } +} + +impl CheckParents for CrudDestroy { + type Parents = Either; + + fn check_against_parents(&self, other: &Self::Parents) -> bool { + match other { + Either::Left(mutate) => true, + Either::Right(any) => true, + } + } +} + +trait IntoParentsFor { + fn into_parents(self) -> T::Parents; +} + +// impl IntoParentsFor for CrudAny { +// fn into_parents(self) -> Either { +// todo!() +// } +// } + +enum Void {} + +// impl SuperParentalChecker for CrudAny { +// type SuperParents = Void; +// +// fn check_against_self(self, other: Self) -> bool { +// self == other +// } +// +// fn check_against_parents(self, other: Self::SuperParents) -> bool { +// match other { +// Either::Left(create) => self == create, +// Either::Right(update) => self == update, +// } +// } +// } + +enum CrudChecker { + Create, + Read, + Update, + Delete, +} diff --git a/src/delegation/payload.rs b/src/delegation/payload.rs index 39a8ff89..f2a8c68c 100644 --- a/src/delegation/payload.rs +++ b/src/delegation/payload.rs @@ -1,6 +1,6 @@ use super::condition::Condition; use crate::{ - ability::traits::{Command, Delegatable, DynJs}, + ability::traits::{Command, Delegatable, DynJs, HasChecker, JustCheck}, capsule::Capsule, did::Did, nonce::Nonce, @@ -84,36 +84,21 @@ impl From> for Ipld { use crate::{ability::traits::Resolvable, invocation::payload as invocation}; -impl<'a, T: ?Sized + Delegatable + Resolvable + Clone, C: Condition> Payload { +impl<'a, T: Delegatable + Resolvable + Clone, C: Condition> Payload { pub fn check( - invoked: invocation::Payload, // FIXME promiroy version + invoked: invocation::Payload, // FIXME promisory version proofs: Vec>, now: SystemTime, ) -> Result<(), ()> where - Ipld: From + From, - U: TryProve, - U::Builder: Clone + Delegatable, - ::Builder: TryProve + TryProve<::Builder>, - <::Builder as TryProve<::Builder>>::Error: Clone, - Prev: From<::Builder>, - Prev<::Builder>: From<::Builder>, - T: TryProve + TryProve<::Builder> + Clone, + invocation::Payload: Clone, ::Builder: From>, - ::Builder: From<::Builder>, - <::Builder as TryProve>::Error: Clone, - ::Builder: Clone + TryProve + TryProve, - >::Error: Clone, - ::Builder: TryProve< - <::Builder as TryProve<::Builder>>::Proven, - >, - T::Builder: TryProve, { - let builder: T::Builder = invoked.into(); - let start: Prev = Prev { - issuer: invoked.issuer, - subject: invoked.subject, - ability_builder: Box::new(builder), + let builder: T::Builder = invoked.clone().into(); + let start: Acc = Acc { + issuer: invoked.issuer.clone(), + subject: invoked.subject.clone(), + check_chain: builder, }; let ipld: Ipld = invoked.into(); @@ -152,63 +137,66 @@ trait ProofHack { fn try_prove1(&self, proof: U) -> Result, ()>; } -impl ProofHack for T -where - T: TryProve, -{ - fn try_prove1(&self, proof: U) -> Result, ()> { - match self.try_prove(proof) { - Ok(_) => Ok(Either::Left(Box::new(()))), - Err(_) => Ok(Either::Right(Box::new(proof))), - } - } -} - -struct Prev { +// impl ProofHack for T +// where +// T: TryProve, +// { +// fn try_prove1(&self, proof: U) -> Result, ()> { +// match self.try_prove(proof) { +// Ok(_) => Ok(Either::Left(Box::new(()))), +// Err(_) => Ok(Either::Right(Box::new(proof))), +// } +// } +// } + +// struct Prev { +// issuer: Did, +// subject: Did, +// ability_builder: Box>, +// } + +// impl From> for Prev +// where +// T::Builder: ProofHack, +// { +// fn from(invoked: invocation::Payload) -> Self { +// Prev { +// issuer: invoked.issuer, +// subject: invoked.subject, +// ability_builder: Box::new(invoked.ability.into()), +// } +// } +// } + +// impl From> for Prev +// where +// T::Builder: ProofHack, +// { +// fn from(delegation: Payload) -> Self { +// Prev { +// issuer: delegation.issuer, +// subject: delegation.subject, +// ability_builder: Box::new(delegation.ability_builder), +// } +// } +// } + +struct Acc { issuer: Did, subject: Did, - ability_builder: Box>, -} - -impl From> for Prev -where - T::Builder: ProofHack, -{ - fn from(invoked: invocation::Payload) -> Self { - Prev { - issuer: invoked.issuer, - subject: invoked.subject, - ability_builder: Box::new(invoked.ability.into()), - } - } -} - -impl From> for Prev -where - T::Builder: ProofHack, -{ - fn from(delegation: Payload) -> Self { - Prev { - issuer: delegation.issuer, - subject: delegation.subject, - ability_builder: Box::new(delegation.ability_builder), - } - } + check_chain: T, } // FIXME this needs to move to Delegatable -fn step<'a, T, U: Delegatable, C: Condition>( - prev: &'a Prev, +fn step<'a, T: JustCheck, U: Delegatable, C: Condition>( + prev: &'a Acc, proof: &'a Payload, invoked_ipld: &'a Ipld, now: SystemTime, ) -> () // FIXME where - T: TryProve<::Builder> + Clone, - U::Builder: Clone, - Ipld: From, - >::Error: Clone, + U::Builder: HasChecker, { if prev.issuer != proof.audience { todo!() @@ -245,7 +233,8 @@ where }) .expect("FIXME"); - Box::leak(prev.ability_builder).try_prove1(proof.ability_builder.clone()); // So many clones that this may as well be owned + // Box::leak(prev.ability_builder).try_prove1(proof.ability_builder.clone()); // So many clones that this may as well be owned + JustCheck::check(&prev.check_chain, &proof.ability_builder); () } diff --git a/src/invocation/payload.rs b/src/invocation/payload.rs index e19e7b91..d2f0ba2c 100644 --- a/src/invocation/payload.rs +++ b/src/invocation/payload.rs @@ -117,9 +117,7 @@ where Ipld: From, { fn from(payload: Payload) -> Self { - let bar: T::Awaiting = payload.ability; - let foo: Ipld = Ipld::from(payload.ability); - let arguments: BTreeMap = match foo { + let arguments: BTreeMap = match Ipld::from(payload.ability) { Ipld::Map(btree) => btree, _ => panic!("FIXME"), }; From f0a5bf4a03808f89107e5a264be56a88e95f8813 Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Sun, 28 Jan 2024 00:53:29 -0800 Subject: [PATCH 024/188] Making headway! Still not 1000% convinced about the parenful stuff due to the indirection (vs a Void type), but hey it's not terrible! --- src/ability/traits.rs | 49 ++++++++++++++++ src/delegation/payload.rs | 115 +++++++++----------------------------- 2 files changed, 76 insertions(+), 88 deletions(-) diff --git a/src/ability/traits.rs b/src/ability/traits.rs index 58478d2c..244fa091 100644 --- a/src/ability/traits.rs +++ b/src/ability/traits.rs @@ -184,11 +184,60 @@ pub enum Parentful { Me(T), } +impl CheckSelf for Parentful +where + T::Parents: CheckSelf, +{ + fn check_against_self(&self, other: &Self) -> bool { + match self { + Parentful::Any => true, + Parentful::Parents(parents) => match other { + Parentful::Any => true, + Parentful::Parents(other_parents) => parents.check_against_self(other_parents), + Parentful::Me(_other_me) => false, + }, + Parentful::Me(me) => match other { + Parentful::Any => true, + Parentful::Parents(_other_parents) => false, + Parentful::Me(other_me) => me.check_against_self(other_me), + }, + } + } +} + +impl CheckParents for Parentful +where + Parentful: CheckSelf, + T::Parents: CheckSelf, +{ + type Parents = T::Parents; + + fn check_against_parents(&self, other: &T::Parents) -> bool { + match self { + Parentful::Any => true, + Parentful::Parents(parents) => parents.check_against_self(other), + Parentful::Me(me) => me.check_against_parents(other), + } + } +} + pub enum Parentless { Any, Me(T), } +impl CheckSelf for Parentless { + fn check_against_self(&self, other: &Self) -> bool { + match self { + Parentless::Any => true, + Parentless::Me(me) => me.check_against_self(match other { + Parentless::Any => return true, + Parentless::Me(other) => other, + }), + } + } +} + pub trait CheckParents: CheckSelf { type Parents; diff --git a/src/delegation/payload.rs b/src/delegation/payload.rs index f2a8c68c..4276d083 100644 --- a/src/delegation/payload.rs +++ b/src/delegation/payload.rs @@ -84,119 +84,59 @@ impl From> for Ipld { use crate::{ability::traits::Resolvable, invocation::payload as invocation}; -impl<'a, T: Delegatable + Resolvable + Clone, C: Condition> Payload { +impl<'a, T: Delegatable + Resolvable + HasChecker + Clone, C: Condition> Payload { pub fn check( invoked: invocation::Payload, // FIXME promisory version proofs: Vec>, now: SystemTime, ) -> Result<(), ()> where + // FIXME so so so broken invocation::Payload: Clone, - ::Builder: From>, + T::CheckAs: From> + From + JustCheck, + U::Builder: Clone, { - let builder: T::Builder = invoked.clone().into(); - let start: Acc = Acc { + let check_chain: T::CheckAs = invoked.clone().into(); + let start: Acc = Acc { issuer: invoked.issuer.clone(), subject: invoked.subject.clone(), - check_chain: builder, + check_chain, }; let ipld: Ipld = invoked.into(); - // let result: Result, ()> = proofs.iter().fold(Ok(start), |prev, proof| { - // if let Ok(to_check) = prev { - // // FIXME check conditions against ipldified invoked - // match step(&to_check, &proof, &ipld, now) { - // Err(_) => Err(()), - // Ok(next) => Ok(Prev { - // issuer: proof.issuer, - // subject: proof.subject, - // ability_builder: Box::new(next), - // }), - // } - // } else { - // prev - // } - // }); - // - // match result { - // Ok(_) => Ok(()), - // Err(_) => Err(()), - // } + let result = proofs.iter().fold(Ok(&start), |prev, proof| { + if let Ok(to_check) = prev { + match step1(&to_check, proof, &ipld, now) { + Err(_) => Err(()), + Ok(next) => Ok(next), + } + } else { + prev + } + }); + todo!() } } -enum Either { - Left(Box), - Right(Box), -} - -// FIXME "CanProve" -trait ProofHack { - fn try_prove1(&self, proof: U) -> Result, ()>; -} - -// impl ProofHack for T -// where -// T: TryProve, -// { -// fn try_prove1(&self, proof: U) -> Result, ()> { -// match self.try_prove(proof) { -// Ok(_) => Ok(Either::Left(Box::new(()))), -// Err(_) => Ok(Either::Right(Box::new(proof))), -// } -// } -// } - -// struct Prev { -// issuer: Did, -// subject: Did, -// ability_builder: Box>, -// } - -// impl From> for Prev -// where -// T::Builder: ProofHack, -// { -// fn from(invoked: invocation::Payload) -> Self { -// Prev { -// issuer: invoked.issuer, -// subject: invoked.subject, -// ability_builder: Box::new(invoked.ability.into()), -// } -// } -// } - -// impl From> for Prev -// where -// T::Builder: ProofHack, -// { -// fn from(delegation: Payload) -> Self { -// Prev { -// issuer: delegation.issuer, -// subject: delegation.subject, -// ability_builder: Box::new(delegation.ability_builder), -// } -// } -// } - -struct Acc { +#[derive(Clone)] +struct Acc { issuer: Did, subject: Did, - check_chain: T, + check_chain: T::CheckAs, } // FIXME this needs to move to Delegatable -fn step<'a, T: JustCheck, U: Delegatable, C: Condition>( +fn step1<'a, T: HasChecker, U: Delegatable, C: Condition>( prev: &'a Acc, proof: &'a Payload, invoked_ipld: &'a Ipld, now: SystemTime, -) -> () -// FIXME +) -> Result<&'a Acc, ()> where - U::Builder: HasChecker, + T::CheckAs: From + JustCheck, + U::Builder: Clone, { if prev.issuer != proof.audience { todo!() @@ -233,10 +173,9 @@ where }) .expect("FIXME"); - // Box::leak(prev.ability_builder).try_prove1(proof.ability_builder.clone()); // So many clones that this may as well be owned - JustCheck::check(&prev.check_chain, &proof.ability_builder); + JustCheck::check(&prev.check_chain, &proof.ability_builder.clone().into()); - () + todo!() } #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] From 66502a8686be092ba3d9bf755f1bce7d63a6abef Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Sun, 28 Jan 2024 11:57:53 -0800 Subject: [PATCH 025/188] Much improved proof checking --- src/ability/crud.rs | 147 +++++++++++++++++- src/ability/traits.rs | 314 +------------------------------------- src/delegation/payload.rs | 10 +- src/prove.rs | 46 ++---- src/prove/internal.rs | 2 + src/prove/parentful.rs | 92 +++++++++++ src/prove/parentless.rs | 35 +++++ src/prove/traits.rs | 31 ++++ 8 files changed, 326 insertions(+), 351 deletions(-) create mode 100644 src/prove/internal.rs create mode 100644 src/prove/parentful.rs create mode 100644 src/prove/parentless.rs create mode 100644 src/prove/traits.rs diff --git a/src/ability/crud.rs b/src/ability/crud.rs index 6fed603a..16d171b3 100644 --- a/src/ability/crud.rs +++ b/src/ability/crud.rs @@ -1,4 +1,8 @@ -use crate::{promise::Promise, prove::TryProve}; +use crate::{ + ability::traits::{CheckParents, CheckSelf, HasChecker}, + promise::Promise, + prove::TryProve, +}; use std::{collections::BTreeMap, fmt::Debug}; use url::Url; @@ -33,6 +37,147 @@ pub struct CrudDestroy { pub uri: Url, } +pub struct CrudAny; +pub struct CrudMutate; +pub struct CrudCreate; +pub struct CrudUpdate; +pub struct CrudDestroy; +pub struct CrudRead; + +pub enum CrudParents { + MutableParent(CrudMutate), + AnyParent(CrudAny), +} + +impl HasChecker for CrudCreate { + type CheckAs = Parentful; +} + +impl CheckSelf for CrudCreate { + type SelfError = (); + fn check_against_self(&self, _other: &Self) -> Result<(), Self::SelfError> { + Ok(()) + } +} + +impl CheckSelf for CrudUpdate { + type SelfError = (); + fn check_against_self(&self, _other: &Self) -> Result<(), Self::SelfError> { + Ok(()) + } +} + +impl HasChecker for CrudUpdate { + type CheckAs = Parentful; +} + +impl CheckSelf for CrudDestroy { + type SelfError = (); + fn check_against_self(&self, _other: &Self) -> Result<(), Self::SelfError> { + Ok(()) + } +} + +impl HasChecker for CrudDestroy { + type CheckAs = Parentful; +} + +impl CheckSelf for CrudMutate { + type SelfError = (); + fn check_against_self(&self, _other: &Self) -> Result<(), Self::SelfError> { + Ok(()) + } +} + +impl HasChecker for CrudMutate { + type CheckAs = Parentful; +} + +impl CheckSelf for CrudAny { + type SelfError = (); + fn check_against_self(&self, _other: &Self) -> Result<(), Self::SelfError> { + Ok(()) + } +} + +impl HasChecker for CrudAny { + type CheckAs = Parentless; +} + +// TODO note to self, this is effectively a partial order +impl CheckParents for CrudMutate { + type Parents = CrudAny; + type ParentError = (); + + fn check_against_parents(&self, other: &Self::Parents) -> Result<(), Self::ParentError> { + Ok(()) + } +} + +impl CheckSelf for CrudParents { + type SelfError = (); + fn check_against_self(&self, other: &Self) -> Result<(), Self::SelfError> { + match self { + CrudParents::MutableParent(mutate) => match other { + CrudParents::MutableParent(other_mutate) => mutate.check_against_self(other_mutate), + CrudParents::AnyParent(any) => mutate.check_against_parents(any), + }, + _ => Err(()), + } + } +} + +impl CheckParents for CrudCreate { + type Parents = CrudParents; + type ParentError = (); + + fn check_against_parents(&self, other: &Self::Parents) -> Result<(), Self::ParentError> { + match other { + CrudParents::MutableParent(mutate) => Ok(()), + CrudParents::AnyParent(any) => Ok(()), + } + } +} + +impl CheckParents for CrudUpdate { + type Parents = CrudParents; + type ParentError = (); + + fn check_against_parents(&self, other: &Self::Parents) -> Result<(), Self::ParentError> { + match other { + CrudParents::MutableParent(mutate) => Ok(()), + CrudParents::AnyParent(any) => Ok(()), + } + } +} + +impl CheckParents for CrudDestroy { + type Parents = CrudParents; + type ParentError = (); + + fn check_against_parents(&self, other: &Self::Parents) -> Result<(), Self::ParentError> { + match other { + CrudParents::MutableParent(mutate) => Ok(()), + CrudParents::AnyParent(any) => Ok(()), + } + } +} + +impl CheckSelf for CrudRead { + type SelfError = (); + fn check_against_self(&self, other: &Self) -> Result<(), Self::SelfError> { + Ok(()) + } +} + +impl CheckParents for CrudRead { + type Parents = CrudAny; + type ParentError = (); + fn check_against_parents(&self, other: &Self::Parents) -> Result<(), Self::ParentError> { + Ok(()) + } +} + // FIXME these should probably be behind a feature flag // impl Capabilty for CrudRead{ diff --git a/src/ability/traits.rs b/src/ability/traits.rs index 244fa091..24ec772a 100644 --- a/src/ability/traits.rs +++ b/src/ability/traits.rs @@ -1,4 +1,4 @@ -use crate::{did::Did, nonce::Nonce, prove::TryProve}; +use crate::{did::Did, nonce::Nonce}; use libipld_cbor::DagCborCodec; use libipld_core::{ cid::{Cid, CidGeneric}, @@ -77,315 +77,3 @@ impl TryFrom for DynJs { ipld_serde::from_ipld(ipld).map_err(|_| ()) } } - -//////////////////////// -//////////////////////// -//////////////////////// -//////////////////////// -//////////////////////// -//////////////////////// -//////////////////////// -//////////////////////// - -// trait IntoCheckable { -// type Checkable; -// fn to_checkable(&self) -> Self::Checkable; -// } -// -// trait Checkable { -// type Checker; -// fn check(me: &Self::Checker, other: &Self::Checker) -> bool; -// } - -struct CrudAny; -struct CrudMutate; -struct CrudCreate; -struct CrudUpdate; -struct CrudDestroy; - -enum Either { - Left(Box), - Right(Box), -} - -enum Wrapper<'a, T: ?Sized, P> { - Any, - Parents(&'a P), - Me(&'a T), -} - -// NOTE must remain unexported! -trait IsChecker {} - -impl IsChecker for Parentful {} -impl IsChecker for Parentless {} - -pub trait HasChecker { - type CheckAs: IsChecker; -} - -trait CheckSelf { - fn check_against_self(&self, other: &Self) -> bool; -} - -impl HasChecker for CrudCreate { - type CheckAs = Parentful; -} - -impl CheckSelf for CrudCreate { - fn check_against_self(&self, _other: &Self) -> bool { - true - } -} - -impl CheckSelf for CrudUpdate { - fn check_against_self(&self, _other: &Self) -> bool { - true - } -} - -impl HasChecker for CrudUpdate { - type CheckAs = Parentful; -} - -impl CheckSelf for CrudDestroy { - fn check_against_self(&self, _other: &Self) -> bool { - true - } -} - -impl HasChecker for CrudDestroy { - type CheckAs = Parentful; -} - -impl CheckSelf for CrudMutate { - fn check_against_self(&self, _other: &Self) -> bool { - true - } -} - -impl HasChecker for CrudMutate { - type CheckAs = Parentful; -} - -impl CheckSelf for CrudAny { - fn check_against_self(&self, _other: &Self) -> bool { - true - } -} - -impl HasChecker for CrudAny { - type CheckAs = Parentless; -} - -pub enum Parentful { - Any, - Parents(T::Parents), - Me(T), -} - -impl CheckSelf for Parentful -where - T::Parents: CheckSelf, -{ - fn check_against_self(&self, other: &Self) -> bool { - match self { - Parentful::Any => true, - Parentful::Parents(parents) => match other { - Parentful::Any => true, - Parentful::Parents(other_parents) => parents.check_against_self(other_parents), - Parentful::Me(_other_me) => false, - }, - Parentful::Me(me) => match other { - Parentful::Any => true, - Parentful::Parents(_other_parents) => false, - Parentful::Me(other_me) => me.check_against_self(other_me), - }, - } - } -} - -impl CheckParents for Parentful -where - Parentful: CheckSelf, - T::Parents: CheckSelf, -{ - type Parents = T::Parents; - - fn check_against_parents(&self, other: &T::Parents) -> bool { - match self { - Parentful::Any => true, - Parentful::Parents(parents) => parents.check_against_self(other), - Parentful::Me(me) => me.check_against_parents(other), - } - } -} - -pub enum Parentless { - Any, - Me(T), -} - -impl CheckSelf for Parentless { - fn check_against_self(&self, other: &Self) -> bool { - match self { - Parentless::Any => true, - Parentless::Me(me) => me.check_against_self(match other { - Parentless::Any => return true, - Parentless::Me(other) => other, - }), - } - } -} - -pub trait CheckParents: CheckSelf { - type Parents; - - fn check_against_parents(&self, other: &Self::Parents) -> bool; -} - -pub trait JustCheck { - fn check<'a>(&'a self, other: &'a T) -> bool; -} - -impl JustCheck> for T { - fn check<'a>(&'a self, other: &'a Parentless) -> bool { - match other { - Parentless::Any => true, - Parentless::Me(me) => self.check_against_self(&me), - } - } -} - -impl JustCheck> for T { - fn check<'a>(&'a self, other: &'a Parentful) -> bool { - match other { - Parentful::Any => true, - Parentful::Parents(parents) => self.check_against_parents(parents), - Parentful::Me(me) => self.check_against_self(&me), - } - } -} - -// trait JustCheckNormalized {} -// -// impl JustCheckNormalized for T {} - -// impl JustCheck for T { -// type ToCheck = Parentful; -// } -// -// trait EvenMoreCheck: JustCheck { -// fn check<'a>(&'a self, other: Self::ToCheck) -> bool; -// } -// -// impl>> EvenMoreCheck for T { -// fn check<'a>(&'a self, other: Parentless) -> bool { -// match other { -// Parentless::Any => true, -// Parentless::Me(me) => self.check_against_self(&me), -// } -// } -// } -// -// impl>> EvenMoreCheck for T { -// fn check<'a>(&'a self, other: Parentless) -> bool { -// match other { -// Parentful::Any => true, -// Parentful::Parents(parents) => self.check_against_parents(parents), -// Parentful::Me(me) => self.check_against_self(&me), -// } -// } -// } - -// impl JustCheck for T {} - -// impl Parentless for CrudAny {} - -// TODO note to self, this is effectively a partial order -impl CheckParents for CrudMutate { - type Parents = CrudAny; - - fn check_against_parents(&self, other: &Self::Parents) -> bool { - true - } -} - -impl CheckSelf for Either { - fn check_against_self(&self, other: &Self) -> bool { - match self { - Either::Left(mutate) => match other { - Either::Left(other_mutate) => mutate.check_against_self(other_mutate), - Either::Right(any) => mutate.check_against_parents(any), - }, - _ => false, - } - } -} - -impl CheckParents for CrudCreate { - type Parents = Either; - - fn check_against_parents(&self, other: &Self::Parents) -> bool { - match other { - Either::Left(mutate) => true, - Either::Right(any) => true, - } - } -} - -impl CheckParents for CrudUpdate { - type Parents = Either; - - fn check_against_parents(&self, other: &Self::Parents) -> bool { - match other { - Either::Left(mutate) => true, - Either::Right(any) => true, - } - } -} - -impl CheckParents for CrudDestroy { - type Parents = Either; - - fn check_against_parents(&self, other: &Self::Parents) -> bool { - match other { - Either::Left(mutate) => true, - Either::Right(any) => true, - } - } -} - -trait IntoParentsFor { - fn into_parents(self) -> T::Parents; -} - -// impl IntoParentsFor for CrudAny { -// fn into_parents(self) -> Either { -// todo!() -// } -// } - -enum Void {} - -// impl SuperParentalChecker for CrudAny { -// type SuperParents = Void; -// -// fn check_against_self(self, other: Self) -> bool { -// self == other -// } -// -// fn check_against_parents(self, other: Self::SuperParents) -> bool { -// match other { -// Either::Left(create) => self == create, -// Either::Right(update) => self == update, -// } -// } -// } - -enum CrudChecker { - Create, - Read, - Update, - Delete, -} diff --git a/src/delegation/payload.rs b/src/delegation/payload.rs index 4276d083..cad89185 100644 --- a/src/delegation/payload.rs +++ b/src/delegation/payload.rs @@ -1,10 +1,10 @@ use super::condition::Condition; use crate::{ - ability::traits::{Command, Delegatable, DynJs, HasChecker, JustCheck}, + ability::traits::{Command, Delegatable, DynJs}, capsule::Capsule, did::Did, nonce::Nonce, - prove::TryProve, + prove::traits::{HasChecker, Prove}, time::Timestamp, }; use libipld_core::{ipld::Ipld, serde as ipld_serde}; @@ -93,7 +93,7 @@ impl<'a, T: Delegatable + Resolvable + HasChecker + Clone, C: Condition> Payload where // FIXME so so so broken invocation::Payload: Clone, - T::CheckAs: From> + From + JustCheck, + T::CheckAs: From> + From + Prove, U::Builder: Clone, { let check_chain: T::CheckAs = invoked.clone().into(); @@ -135,7 +135,7 @@ fn step1<'a, T: HasChecker, U: Delegatable, C: Condition>( now: SystemTime, ) -> Result<&'a Acc, ()> where - T::CheckAs: From + JustCheck, + T::CheckAs: From + Prove, U::Builder: Clone, { if prev.issuer != proof.audience { @@ -173,7 +173,7 @@ where }) .expect("FIXME"); - JustCheck::check(&prev.check_chain, &proof.ability_builder.clone().into()); + Prove::check(&prev.check_chain, &proof.ability_builder.clone().into()); todo!() } diff --git a/src/prove.rs b/src/prove.rs index b6ddc45d..e4b8122b 100644 --- a/src/prove.rs +++ b/src/prove.rs @@ -1,33 +1,15 @@ -use std::convert::Infallible; +// NOTE must remain *un*exported! +pub(super) mod internal; +pub mod parentful; +pub mod parentless; +pub mod traits; -#[cfg_attr(doc, aquamarine::aquamarine)] -/// FIXME -/// -/// ```mermaid -/// flowchart LR -/// Invocation --> more --> Self --> Proof --> more2 -/// more[...] -/// more2[...] -/// ``` -pub trait TryProve { - type Proven; - type Error; - - // FIXME rename to proof? - fn try_prove(&self, proof: T) -> Result; -} - -// pub trait Prove { -// type Proven; -// -// fn prove(self, proof: T) -> Self::Proven; -// } -// -// impl + ?Sized, U> TryProve for T { -// type Proven = T::Proven; -// type Error = Infallible; -// -// fn try_prove(&self, proof: U) -> Result { -// Ok(self.prove(proof)) -// } -// } +// #[cfg_attr(doc, aquamarine::aquamarine)] +// /// FIXME +// /// +// /// ```mermaid +// /// flowchart LR +// /// Invocation --> more --> Self --> Proof --> more2 +// /// more[...] +// /// more2[...] +// /// ``` diff --git a/src/prove/internal.rs b/src/prove/internal.rs new file mode 100644 index 00000000..8963d51e --- /dev/null +++ b/src/prove/internal.rs @@ -0,0 +1,2 @@ +// FIXME rename +pub trait IsChecker {} diff --git a/src/prove/parentful.rs b/src/prove/parentful.rs new file mode 100644 index 00000000..a883d4a9 --- /dev/null +++ b/src/prove/parentful.rs @@ -0,0 +1,92 @@ +use super::{ + internal::IsChecker, + traits::{CheckParents, CheckSelf, Prove}, +}; + +pub enum Parentful { + Any, + Parents(T::Parents), + Me(T), +} + +// TODO better names & derivations +pub enum ParentfulError +where + T::Parents: CheckSelf, +{ + ParentError(T::ParentError), + ParentSelfError(<::Parents as CheckSelf>::SelfError), + SelfError(::SelfError), + + // Compared self to parents + EscelationError, +} + +impl IsChecker for Parentful {} + +impl CheckSelf for Parentful +where + T::Parents: CheckSelf, +{ + type SelfError = ParentfulError; + + fn check_against_self(&self, other: &Self) -> Result<(), Self::SelfError> { + match self { + Parentful::Any => Ok(()), + Parentful::Parents(parents) => match other { + Parentful::Any => Ok(()), + Parentful::Parents(other_parents) => parents + .check_against_self(other_parents) + .map_err(ParentfulError::ParentSelfError), + Parentful::Me(_other_me) => Err(ParentfulError::EscelationError), + }, + Parentful::Me(me) => match other { + Parentful::Any => Ok(()), + Parentful::Parents(other_parents) => me + .check_against_parents(other_parents) + .map_err(ParentfulError::ParentError), + Parentful::Me(other_me) => me + .check_against_self(other_me) + .map_err(ParentfulError::SelfError), + }, + } + } +} + +impl CheckParents for Parentful +where + Parentful: CheckSelf, + T::Parents: CheckSelf, +{ + type Parents = T::Parents; + type ParentError = ParentfulError; + + fn check_against_parents(&self, other: &T::Parents) -> Result<(), Self::ParentError> { + // FIXME note to self: see if you can extract the parentful stuff out into the to level Prove + match self { + Parentful::Any => Ok(()), + Parentful::Parents(parents) => parents.check_against_self(other).map_err(|_| todo!()), // FIXME ParentfulError::ParentError), + Parentful::Me(me) => me + .check_against_parents(other) + .map_err(ParentfulError::ParentError), + } + } +} + +impl Prove> for T +where + T::Parents: CheckSelf, +{ + type ProveError = ParentfulError; + fn check<'a>(&'a self, other: &'a Parentful) -> Result<(), Self::ProveError> { + match other { + Parentful::Any => Ok(()), + Parentful::Parents(parents) => self + .check_against_parents(parents) + .map_err(ParentfulError::ParentError), + Parentful::Me(me) => self + .check_against_self(&me) + .map_err(ParentfulError::SelfError), + } + } +} diff --git a/src/prove/parentless.rs b/src/prove/parentless.rs new file mode 100644 index 00000000..9814bf1e --- /dev/null +++ b/src/prove/parentless.rs @@ -0,0 +1,35 @@ +use super::{ + internal::IsChecker, + traits::{CheckSelf, Prove}, +}; + +pub enum Parentless { + Any, + Me(T), +} + +impl IsChecker for Parentless {} + +impl CheckSelf for Parentless { + type SelfError = T::SelfError; + + fn check_against_self(&self, other: &Self) -> Result<(), Self::SelfError> { + match self { + Parentless::Any => Ok(()), // FIXME MUST forward that this was an ANY this into the result! + Parentless::Me(me) => match other { + Parentless::Any => Ok(()), + Parentless::Me(other) => me.check_against_self(other), + }, + } + } +} + +impl Prove> for T { + type ProveError = T::SelfError; + fn check<'a>(&'a self, other: &'a Parentless) -> Result<(), T::SelfError> { + match other { + Parentless::Any => Ok(()), + Parentless::Me(me) => self.check_against_self(&me), + } + } +} diff --git a/src/prove/traits.rs b/src/prove/traits.rs new file mode 100644 index 00000000..bab927aa --- /dev/null +++ b/src/prove/traits.rs @@ -0,0 +1,31 @@ +use super::internal::IsChecker; + +pub trait CheckSelf { + type SelfError; + + fn check_against_self(&self, other: &Self) -> Result<(), Self::SelfError>; +} + +pub trait CheckParents: CheckSelf { + type Parents; + type ParentError; + + fn check_against_parents(&self, other: &Self::Parents) -> Result<(), Self::ParentError>; +} + +pub trait HasChecker { + type CheckAs: IsChecker; +} + +pub trait Prove { + type ProveError; + fn check<'a>(&'a self, other: &'a T) -> Result<(), Self::ProveError>; +} + +// FIXME needed? +pub trait IntoParent { + fn as_parent(self) -> T::Parents; +} + +// Nightly only... sadness +// trait Foo = HasChecker + Prove; From 831695d48b0b5fd826f53fa36fee646d564e0753 Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Sun, 28 Jan 2024 13:07:36 -0800 Subject: [PATCH 026/188] Implement CRUD heirarchy to test it out -- seems fine --- src/ability.rs | 7 +- src/ability/any.rs | 13 -- src/ability/crud.rs | 275 +----------------------------------- src/ability/crud/any.rs | 20 +++ src/ability/crud/create.rs | 46 ++++++ src/ability/crud/destroy.rs | 39 +++++ src/ability/crud/mutate.rs | 32 +++++ src/ability/crud/parents.rs | 20 +++ src/ability/crud/read.rs | 38 +++++ src/ability/crud/update.rs | 43 ++++++ src/prove/traits.rs | 2 +- 11 files changed, 250 insertions(+), 285 deletions(-) delete mode 100644 src/ability/any.rs create mode 100644 src/ability/crud/any.rs create mode 100644 src/ability/crud/create.rs create mode 100644 src/ability/crud/destroy.rs create mode 100644 src/ability/crud/mutate.rs create mode 100644 src/ability/crud/parents.rs create mode 100644 src/ability/crud/read.rs create mode 100644 src/ability/crud/update.rs diff --git a/src/ability.rs b/src/ability.rs index c9645a96..ac1d449c 100644 --- a/src/ability.rs +++ b/src/ability.rs @@ -1,9 +1,10 @@ -pub mod any; -// pub mod crud; -// pub mod msg; pub mod traits; // pub mod wasm; +// FIXME feature flag each? +pub mod crud; +// pub mod msg; + // TODO move to crate::wasm? #[cfg(feature = "wasm")] pub mod dynamic; diff --git a/src/ability/any.rs b/src/ability/any.rs deleted file mode 100644 index 5d970cad..00000000 --- a/src/ability/any.rs +++ /dev/null @@ -1,13 +0,0 @@ -// use crate::prove::Prove; -use std::convert::Infallible; - -#[derive(Debug, Clone, Copy, Eq, PartialEq)] -pub struct DelegateAny; - -// impl Prove for DelegateAny { -// type Proven = Self; -// -// fn prove(self, _proof: Self) -> Self::Proven { -// self -// } -// } diff --git a/src/ability/crud.rs b/src/ability/crud.rs index 16d171b3..e134e48d 100644 --- a/src/ability/crud.rs +++ b/src/ability/crud.rs @@ -1,271 +1,10 @@ -use crate::{ - ability::traits::{CheckParents, CheckSelf, HasChecker}, - promise::Promise, - prove::TryProve, -}; -use std::{collections::BTreeMap, fmt::Debug}; -use url::Url; +pub mod any; +pub mod create; +pub mod destroy; +pub mod mutate; +pub mod parents; +pub mod read; +pub mod update; // FIXME macro to derive promise versions & delagted builder versions // ... also maybe Ipld - -pub struct Crud { - pub uri: Url, -} - -pub struct CrudRead { - pub uri: Url, -} - -pub struct CrudMutate { - pub uri: Url, -} - -pub struct CrudCreate { - pub uri: Url, - pub args: BTreeMap, String>, -} - -#[derive(Debug, Clone, PartialEq)] -pub struct CrudUpdate { - pub uri: Url, - pub args: BTreeMap, String>, -} - -#[derive(Debug, Clone, PartialEq)] -pub struct CrudDestroy { - pub uri: Url, -} - -pub struct CrudAny; -pub struct CrudMutate; -pub struct CrudCreate; -pub struct CrudUpdate; -pub struct CrudDestroy; -pub struct CrudRead; - -pub enum CrudParents { - MutableParent(CrudMutate), - AnyParent(CrudAny), -} - -impl HasChecker for CrudCreate { - type CheckAs = Parentful; -} - -impl CheckSelf for CrudCreate { - type SelfError = (); - fn check_against_self(&self, _other: &Self) -> Result<(), Self::SelfError> { - Ok(()) - } -} - -impl CheckSelf for CrudUpdate { - type SelfError = (); - fn check_against_self(&self, _other: &Self) -> Result<(), Self::SelfError> { - Ok(()) - } -} - -impl HasChecker for CrudUpdate { - type CheckAs = Parentful; -} - -impl CheckSelf for CrudDestroy { - type SelfError = (); - fn check_against_self(&self, _other: &Self) -> Result<(), Self::SelfError> { - Ok(()) - } -} - -impl HasChecker for CrudDestroy { - type CheckAs = Parentful; -} - -impl CheckSelf for CrudMutate { - type SelfError = (); - fn check_against_self(&self, _other: &Self) -> Result<(), Self::SelfError> { - Ok(()) - } -} - -impl HasChecker for CrudMutate { - type CheckAs = Parentful; -} - -impl CheckSelf for CrudAny { - type SelfError = (); - fn check_against_self(&self, _other: &Self) -> Result<(), Self::SelfError> { - Ok(()) - } -} - -impl HasChecker for CrudAny { - type CheckAs = Parentless; -} - -// TODO note to self, this is effectively a partial order -impl CheckParents for CrudMutate { - type Parents = CrudAny; - type ParentError = (); - - fn check_against_parents(&self, other: &Self::Parents) -> Result<(), Self::ParentError> { - Ok(()) - } -} - -impl CheckSelf for CrudParents { - type SelfError = (); - fn check_against_self(&self, other: &Self) -> Result<(), Self::SelfError> { - match self { - CrudParents::MutableParent(mutate) => match other { - CrudParents::MutableParent(other_mutate) => mutate.check_against_self(other_mutate), - CrudParents::AnyParent(any) => mutate.check_against_parents(any), - }, - _ => Err(()), - } - } -} - -impl CheckParents for CrudCreate { - type Parents = CrudParents; - type ParentError = (); - - fn check_against_parents(&self, other: &Self::Parents) -> Result<(), Self::ParentError> { - match other { - CrudParents::MutableParent(mutate) => Ok(()), - CrudParents::AnyParent(any) => Ok(()), - } - } -} - -impl CheckParents for CrudUpdate { - type Parents = CrudParents; - type ParentError = (); - - fn check_against_parents(&self, other: &Self::Parents) -> Result<(), Self::ParentError> { - match other { - CrudParents::MutableParent(mutate) => Ok(()), - CrudParents::AnyParent(any) => Ok(()), - } - } -} - -impl CheckParents for CrudDestroy { - type Parents = CrudParents; - type ParentError = (); - - fn check_against_parents(&self, other: &Self::Parents) -> Result<(), Self::ParentError> { - match other { - CrudParents::MutableParent(mutate) => Ok(()), - CrudParents::AnyParent(any) => Ok(()), - } - } -} - -impl CheckSelf for CrudRead { - type SelfError = (); - fn check_against_self(&self, other: &Self) -> Result<(), Self::SelfError> { - Ok(()) - } -} - -impl CheckParents for CrudRead { - type Parents = CrudAny; - type ParentError = (); - fn check_against_parents(&self, other: &Self::Parents) -> Result<(), Self::ParentError> { - Ok(()) - } -} - -// FIXME these should probably be behind a feature flag - -// impl Capabilty for CrudRead{ -// const COMMAND = "crud/read"; -// -// fn subject(&self) -> Did { -// todo!() -// } -// } - -// impl TryProve for CrudDestroy { -// type Error = (); // FIXME -// type Proven = CrudDestroy; -// fn try_prove<'a>(&'a self, proof: &'a CrudDestroy) -> Result<&'a Self::Proven, ()> { -// if self.uri == proof.uri { -// Ok(self) -// } else { -// Err(()) -// } -// } -// } -// -// // FIXME ProveWith? -// impl TryProve for CrudDestroy { -// type Error = (); // FIXME -// type Proven = CrudDestroy; -// -// fn try_prove<'a>(&'a self, proof: &'a CrudMutate) -> Result<&'a Self::Proven, ()> { -// if self.uri == proof.uri { -// Ok(self) -// } else { -// Err(()) -// } -// } -// } -// -// impl TryProve for CrudRead { -// type Error = (); -// type Proven = CrudRead; -// -// fn try_prove<'a>(&'a self, proof: &'a CrudRead) -> Result<&'a Self::Proven, ()> { -// if self.uri == proof.uri { -// // FIXME contains & args -// Ok(self) -// } else { -// Err(()) -// } -// } -// } -// -// impl TryProve for CrudRead { -// type Error = (); // FIXME -// type Proven = CrudRead; -// -// fn try_prove<'a>(&'a self, proof: &'a Crud) -> Result<&'a Self::Proven, ()> { -// if self.uri == proof.uri { -// Ok(self) -// } else { -// Err(()) -// } -// } -// } -// -// impl TryProve for CrudMutate { -// type Error = (); // FIXME -// type Proven = CrudMutate; -// -// fn try_prove<'a>(&'a self, proof: &'a Crud) -> Result<&'a Self::Proven, ()> { -// if self.uri == proof.uri { -// Ok(self) -// } else { -// Err(()) -// } -// } -// } -// -// // FIXME -// impl> TryProve for C { -// type Error = (); -// type Proven = C; -// -// // FIXME -// fn try_prove<'a>(&'a self, proof: &'a Crud) -> Result<&'a C, ()> { -// match self.try_prove(&CrudMutate { -// uri: proof.uri.clone(), -// }) { -// Ok(_) => Ok(self), -// Err(_) => Err(()), -// } -// } -// } diff --git a/src/ability/crud/any.rs b/src/ability/crud/any.rs new file mode 100644 index 00000000..b2fed77c --- /dev/null +++ b/src/ability/crud/any.rs @@ -0,0 +1,20 @@ +use crate::prove::{ + parentless::Parentless, + traits::{CheckSelf, HasChecker}, +}; +use url::Url; + +pub struct AnyBuilder { + pub uri: Option, +} + +impl HasChecker for AnyBuilder { + type CheckAs = Parentless; +} + +impl CheckSelf for AnyBuilder { + type SelfError = (); + fn check_against_self(&self, _other: &Self) -> Result<(), Self::SelfError> { + Ok(()) + } +} diff --git a/src/ability/crud/create.rs b/src/ability/crud/create.rs new file mode 100644 index 00000000..d7efc3e6 --- /dev/null +++ b/src/ability/crud/create.rs @@ -0,0 +1,46 @@ +use crate::prove::{ + parentful::Parentful, + traits::{CheckParents, CheckSelf, HasChecker}, +}; +use libipld_core::ipld::Ipld; +use std::collections::BTreeMap; +use url::Url; + +use super::parents::Mutable; + +pub struct Create { + pub uri: Url, + pub args: BTreeMap, +} + +pub struct CreateBuilder { + pub uri: Option, + pub args: BTreeMap, +} + +impl HasChecker for CreateBuilder { + type CheckAs = Parentful; +} + +impl CheckSelf for CreateBuilder { + type SelfError = (); // FIXME better error + fn check_against_self(&self, other: &Self) -> Result<(), Self::SelfError> { + if self.uri == other.uri { + Ok(()) + } else { + Err(()) + } + } +} + +impl CheckParents for CreateBuilder { + type Parents = Mutable; + type ParentError = (); + + fn check_against_parents(&self, other: &Self::Parents) -> Result<(), Self::ParentError> { + match other { + Mutable::Mutate(mutate) => Ok(()), + Mutable::Any(any) => Ok(()), + } + } +} diff --git a/src/ability/crud/destroy.rs b/src/ability/crud/destroy.rs new file mode 100644 index 00000000..f9021b25 --- /dev/null +++ b/src/ability/crud/destroy.rs @@ -0,0 +1,39 @@ +use crate::prove::{ + parentful::Parentful, + traits::{CheckParents, CheckSelf, HasChecker}, +}; +use url::Url; + +use super::parents::Mutable; + +#[derive(Debug, Clone, PartialEq)] +pub struct CrudDestroy { + pub uri: Url, +} + +pub struct DestroyBuilder { + pub uri: Option, +} + +impl HasChecker for DestroyBuilder { + type CheckAs = Parentful; +} + +impl CheckSelf for DestroyBuilder { + type SelfError = (); + fn check_against_self(&self, _other: &Self) -> Result<(), Self::SelfError> { + Ok(()) + } +} + +impl CheckParents for DestroyBuilder { + type Parents = Mutable; + type ParentError = (); + + fn check_against_parents(&self, other: &Self::Parents) -> Result<(), Self::ParentError> { + match other { + Mutable::Mutate(mutate) => Ok(()), + Mutable::Any(any) => Ok(()), + } + } +} diff --git a/src/ability/crud/mutate.rs b/src/ability/crud/mutate.rs new file mode 100644 index 00000000..bf63635a --- /dev/null +++ b/src/ability/crud/mutate.rs @@ -0,0 +1,32 @@ +use crate::prove::{ + parentful::Parentful, + traits::{CheckParents, CheckSelf, HasChecker}, +}; +use url::Url; + +use super::any::AnyBuilder; + +pub struct MutateBuilder { + pub uri: Option, +} + +impl HasChecker for MutateBuilder { + type CheckAs = Parentful; +} + +impl CheckSelf for MutateBuilder { + type SelfError = (); + fn check_against_self(&self, _other: &Self) -> Result<(), Self::SelfError> { + Ok(()) + } +} + +// TODO note to self, this is effectively a partial order +impl CheckParents for MutateBuilder { + type Parents = AnyBuilder; + type ParentError = (); + + fn check_against_parents(&self, other: &Self::Parents) -> Result<(), Self::ParentError> { + Ok(()) + } +} diff --git a/src/ability/crud/parents.rs b/src/ability/crud/parents.rs new file mode 100644 index 00000000..a4a4a042 --- /dev/null +++ b/src/ability/crud/parents.rs @@ -0,0 +1,20 @@ +use super::{any::AnyBuilder, mutate::MutateBuilder}; +use crate::prove::traits::CheckSelf; + +pub enum Mutable { + Mutate(MutateBuilder), + Any(AnyBuilder), +} + +impl CheckSelf for Mutable { + type SelfError = (); + fn check_against_self(&self, other: &Self) -> Result<(), Self::SelfError> { + match self { + Mutable::Mutate(mutate) => match other { + Mutable::Mutate(other_mutate) => mutate.check_against_self(other_mutate), + Mutable::Any(any) => Ok(()), + }, + _ => Err(()), + } + } +} diff --git a/src/ability/crud/read.rs b/src/ability/crud/read.rs new file mode 100644 index 00000000..f7935f63 --- /dev/null +++ b/src/ability/crud/read.rs @@ -0,0 +1,38 @@ +use crate::prove::{ + parentful::Parentful, + traits::{CheckParents, CheckSelf, HasChecker}, +}; +use libipld_core::ipld::Ipld; +use std::collections::BTreeMap; +use url::Url; + +use super::any::AnyBuilder; + +pub struct Read { + pub uri: Url, + pub args: BTreeMap, +} + +pub struct ReadBuilder { + pub uri: Option, + pub args: BTreeMap, +} + +impl HasChecker for ReadBuilder { + type CheckAs = Parentful; +} + +impl CheckSelf for ReadBuilder { + type SelfError = (); + fn check_against_self(&self, other: &Self) -> Result<(), Self::SelfError> { + Ok(()) + } +} + +impl CheckParents for ReadBuilder { + type Parents = AnyBuilder; + type ParentError = (); + fn check_against_parents(&self, other: &Self::Parents) -> Result<(), Self::ParentError> { + Ok(()) + } +} diff --git a/src/ability/crud/update.rs b/src/ability/crud/update.rs new file mode 100644 index 00000000..5c922146 --- /dev/null +++ b/src/ability/crud/update.rs @@ -0,0 +1,43 @@ +use crate::prove::{ + parentful::Parentful, + traits::{CheckParents, CheckSelf, HasChecker}, +}; +use libipld_core::ipld::Ipld; +use std::collections::BTreeMap; +use url::Url; + +use super::parents::Mutable; + +#[derive(Debug, Clone, PartialEq)] +pub struct Update { + pub uri: Url, + pub args: BTreeMap, String>, +} + +pub struct UpdateBuilder { + pub uri: Option, + pub args: BTreeMap, // FIXME use a type param? +} + +impl HasChecker for UpdateBuilder { + type CheckAs = Parentful; +} + +impl CheckSelf for UpdateBuilder { + type SelfError = (); + fn check_against_self(&self, _other: &Self) -> Result<(), Self::SelfError> { + Ok(()) + } +} + +impl CheckParents for UpdateBuilder { + type Parents = Mutable; + type ParentError = (); + + fn check_against_parents(&self, other: &Self::Parents) -> Result<(), Self::ParentError> { + match other { + Mutable::Mutate(mutate) => Ok(()), + Mutable::Any(any) => Ok(()), + } + } +} diff --git a/src/prove/traits.rs b/src/prove/traits.rs index bab927aa..d2e40e69 100644 --- a/src/prove/traits.rs +++ b/src/prove/traits.rs @@ -13,7 +13,7 @@ pub trait CheckParents: CheckSelf { fn check_against_parents(&self, other: &Self::Parents) -> Result<(), Self::ParentError>; } -pub trait HasChecker { +pub trait HasChecker: CheckSelf { type CheckAs: IsChecker; } From 8f9053ad4573e567046214f4576c8195a844c329 Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Sun, 28 Jan 2024 13:09:50 -0800 Subject: [PATCH 027/188] Save state --- src/ability/crud/any.rs | 4 ++-- src/ability/crud/create.rs | 4 ++-- src/ability/crud/destroy.rs | 4 ++-- src/ability/crud/mutate.rs | 4 ++-- src/ability/crud/parents.rs | 4 ++-- src/ability/crud/read.rs | 4 ++-- src/ability/crud/update.rs | 4 ++-- src/prove/parentful.rs | 15 +++++++-------- src/prove/parentless.rs | 8 ++++---- src/prove/traits.rs | 4 ++-- 10 files changed, 27 insertions(+), 28 deletions(-) diff --git a/src/ability/crud/any.rs b/src/ability/crud/any.rs index b2fed77c..08d5a3ac 100644 --- a/src/ability/crud/any.rs +++ b/src/ability/crud/any.rs @@ -13,8 +13,8 @@ impl HasChecker for AnyBuilder { } impl CheckSelf for AnyBuilder { - type SelfError = (); - fn check_against_self(&self, _other: &Self) -> Result<(), Self::SelfError> { + type Error = (); + fn check_against_self(&self, _other: &Self) -> Result<(), Self::Error> { Ok(()) } } diff --git a/src/ability/crud/create.rs b/src/ability/crud/create.rs index d7efc3e6..c12871f9 100644 --- a/src/ability/crud/create.rs +++ b/src/ability/crud/create.rs @@ -23,8 +23,8 @@ impl HasChecker for CreateBuilder { } impl CheckSelf for CreateBuilder { - type SelfError = (); // FIXME better error - fn check_against_self(&self, other: &Self) -> Result<(), Self::SelfError> { + type Error = (); // FIXME better error + fn check_against_self(&self, other: &Self) -> Result<(), Self::Error> { if self.uri == other.uri { Ok(()) } else { diff --git a/src/ability/crud/destroy.rs b/src/ability/crud/destroy.rs index f9021b25..ec2ad91e 100644 --- a/src/ability/crud/destroy.rs +++ b/src/ability/crud/destroy.rs @@ -20,8 +20,8 @@ impl HasChecker for DestroyBuilder { } impl CheckSelf for DestroyBuilder { - type SelfError = (); - fn check_against_self(&self, _other: &Self) -> Result<(), Self::SelfError> { + type Error = (); + fn check_against_self(&self, _other: &Self) -> Result<(), Self::Error> { Ok(()) } } diff --git a/src/ability/crud/mutate.rs b/src/ability/crud/mutate.rs index bf63635a..14b1e18d 100644 --- a/src/ability/crud/mutate.rs +++ b/src/ability/crud/mutate.rs @@ -15,8 +15,8 @@ impl HasChecker for MutateBuilder { } impl CheckSelf for MutateBuilder { - type SelfError = (); - fn check_against_self(&self, _other: &Self) -> Result<(), Self::SelfError> { + type Error = (); + fn check_against_self(&self, _other: &Self) -> Result<(), Self::Error> { Ok(()) } } diff --git a/src/ability/crud/parents.rs b/src/ability/crud/parents.rs index a4a4a042..3caf7025 100644 --- a/src/ability/crud/parents.rs +++ b/src/ability/crud/parents.rs @@ -7,8 +7,8 @@ pub enum Mutable { } impl CheckSelf for Mutable { - type SelfError = (); - fn check_against_self(&self, other: &Self) -> Result<(), Self::SelfError> { + type Error = (); + fn check_against_self(&self, other: &Self) -> Result<(), Self::Error> { match self { Mutable::Mutate(mutate) => match other { Mutable::Mutate(other_mutate) => mutate.check_against_self(other_mutate), diff --git a/src/ability/crud/read.rs b/src/ability/crud/read.rs index f7935f63..d56c9182 100644 --- a/src/ability/crud/read.rs +++ b/src/ability/crud/read.rs @@ -23,8 +23,8 @@ impl HasChecker for ReadBuilder { } impl CheckSelf for ReadBuilder { - type SelfError = (); - fn check_against_self(&self, other: &Self) -> Result<(), Self::SelfError> { + type Error = (); + fn check_against_self(&self, other: &Self) -> Result<(), Self::Error> { Ok(()) } } diff --git a/src/ability/crud/update.rs b/src/ability/crud/update.rs index 5c922146..53ee6dd5 100644 --- a/src/ability/crud/update.rs +++ b/src/ability/crud/update.rs @@ -24,8 +24,8 @@ impl HasChecker for UpdateBuilder { } impl CheckSelf for UpdateBuilder { - type SelfError = (); - fn check_against_self(&self, _other: &Self) -> Result<(), Self::SelfError> { + type Error = (); + fn check_against_self(&self, _other: &Self) -> Result<(), Self::Error> { Ok(()) } } diff --git a/src/prove/parentful.rs b/src/prove/parentful.rs index a883d4a9..19fb8f19 100644 --- a/src/prove/parentful.rs +++ b/src/prove/parentful.rs @@ -15,8 +15,9 @@ where T::Parents: CheckSelf, { ParentError(T::ParentError), - ParentSelfError(<::Parents as CheckSelf>::SelfError), - SelfError(::SelfError), + // FIXME needs a WAAAAAY better name + ParentSelfError(<::Parents as CheckSelf>::Error), + Error(::Error), // Compared self to parents EscelationError, @@ -28,9 +29,9 @@ impl CheckSelf for Parentful where T::Parents: CheckSelf, { - type SelfError = ParentfulError; + type Error = ParentfulError; - fn check_against_self(&self, other: &Self) -> Result<(), Self::SelfError> { + fn check_against_self(&self, other: &Self) -> Result<(), Self::Error> { match self { Parentful::Any => Ok(()), Parentful::Parents(parents) => match other { @@ -47,7 +48,7 @@ where .map_err(ParentfulError::ParentError), Parentful::Me(other_me) => me .check_against_self(other_me) - .map_err(ParentfulError::SelfError), + .map_err(ParentfulError::Error), }, } } @@ -84,9 +85,7 @@ where Parentful::Parents(parents) => self .check_against_parents(parents) .map_err(ParentfulError::ParentError), - Parentful::Me(me) => self - .check_against_self(&me) - .map_err(ParentfulError::SelfError), + Parentful::Me(me) => self.check_against_self(&me).map_err(ParentfulError::Error), } } } diff --git a/src/prove/parentless.rs b/src/prove/parentless.rs index 9814bf1e..5890135f 100644 --- a/src/prove/parentless.rs +++ b/src/prove/parentless.rs @@ -11,9 +11,9 @@ pub enum Parentless { impl IsChecker for Parentless {} impl CheckSelf for Parentless { - type SelfError = T::SelfError; + type Error = T::Error; - fn check_against_self(&self, other: &Self) -> Result<(), Self::SelfError> { + fn check_against_self(&self, other: &Self) -> Result<(), Self::Error> { match self { Parentless::Any => Ok(()), // FIXME MUST forward that this was an ANY this into the result! Parentless::Me(me) => match other { @@ -25,8 +25,8 @@ impl CheckSelf for Parentless { } impl Prove> for T { - type ProveError = T::SelfError; - fn check<'a>(&'a self, other: &'a Parentless) -> Result<(), T::SelfError> { + type ProveError = T::Error; + fn check<'a>(&'a self, other: &'a Parentless) -> Result<(), T::Error> { match other { Parentless::Any => Ok(()), Parentless::Me(me) => self.check_against_self(&me), diff --git a/src/prove/traits.rs b/src/prove/traits.rs index d2e40e69..b0e89541 100644 --- a/src/prove/traits.rs +++ b/src/prove/traits.rs @@ -1,9 +1,9 @@ use super::internal::IsChecker; pub trait CheckSelf { - type SelfError; + type Error; - fn check_against_self(&self, other: &Self) -> Result<(), Self::SelfError>; + fn check_against_self(&self, other: &Self) -> Result<(), Self::Error>; } pub trait CheckParents: CheckSelf { From 6382d8ac5b5c180e3791593a4f59aae793f5cbae Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Sun, 28 Jan 2024 16:08:12 -0800 Subject: [PATCH 028/188] Save before changig CheckSelf --- Cargo.toml | 1 + src/ability.rs | 5 +- src/ability/crud.rs | 3 - src/ability/crud/any.rs | 20 ++- src/ability/crud/create.rs | 74 ++++++++--- src/ability/crud/destroy.rs | 48 +++++-- src/ability/crud/mutate.rs | 37 +++++- src/ability/crud/parents.rs | 7 +- src/ability/crud/read.rs | 51 +++++--- src/ability/crud/update.rs | 87 +++++++++++-- src/ability/msg.rs | 243 +----------------------------------- src/ability/msg/any.rs | 45 +++++++ src/ability/msg/receive.rs | 80 ++++++++++++ src/ability/msg/send.rs | 113 +++++++++++++++++ src/delegation/payload.rs | 8 +- src/prove/internal.rs | 2 +- src/prove/parentful.rs | 55 +++++--- src/prove/parentless.rs | 38 ++++-- src/prove/traits.rs | 22 ++-- 19 files changed, 582 insertions(+), 357 deletions(-) create mode 100644 src/ability/msg/any.rs create mode 100644 src/ability/msg/receive.rs create mode 100644 src/ability/msg/send.rs diff --git a/Cargo.toml b/Cargo.toml index a58dba08..39aae0a3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -60,6 +60,7 @@ semver = "1.0.19" serde = { version = "1.0.188", features = ["derive"] } serde_derive = "1.0" serde_json = "1.0.107" +serde_with = {version = "3.5", features = ["alloc"] } signature = { version = "2.1.0", features = ["alloc"] } thiserror = "1.0" tracing = "0.1.40" diff --git a/src/ability.rs b/src/ability.rs index ac1d449c..f3a4f8aa 100644 --- a/src/ability.rs +++ b/src/ability.rs @@ -3,8 +3,11 @@ pub mod traits; // FIXME feature flag each? pub mod crud; -// pub mod msg; +pub mod msg; // TODO move to crate::wasm? #[cfg(feature = "wasm")] pub mod dynamic; + +// FIXME macro to derive promise versions & delagted builder versions +// ... also maybe Ipld diff --git a/src/ability/crud.rs b/src/ability/crud.rs index e134e48d..dda6aa59 100644 --- a/src/ability/crud.rs +++ b/src/ability/crud.rs @@ -5,6 +5,3 @@ pub mod mutate; pub mod parents; pub mod read; pub mod update; - -// FIXME macro to derive promise versions & delagted builder versions -// ... also maybe Ipld diff --git a/src/ability/crud/any.rs b/src/ability/crud/any.rs index 08d5a3ac..5c17b6b3 100644 --- a/src/ability/crud/any.rs +++ b/src/ability/crud/any.rs @@ -1,20 +1,30 @@ -use crate::prove::{ - parentless::Parentless, - traits::{CheckSelf, HasChecker}, +use crate::{ + ability::traits::Command, + prove::{ + parentless::Parentless, + traits::{CheckSelf, Checkable}, + }, }; +use serde::{Deserialize, Serialize}; use url::Url; +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[serde(deny_unknown_fields)] pub struct AnyBuilder { pub uri: Option, } -impl HasChecker for AnyBuilder { +impl Command for AnyBuilder { + const COMMAND: &'static str = "crud/*"; +} + +impl Checkable for AnyBuilder { type CheckAs = Parentless; } impl CheckSelf for AnyBuilder { type Error = (); - fn check_against_self(&self, _other: &Self) -> Result<(), Self::Error> { + fn check_against_self(&self, _proof: &Self) -> Result<(), Self::Error> { Ok(()) } } diff --git a/src/ability/crud/create.rs b/src/ability/crud/create.rs index c12871f9..1b0bdab4 100644 --- a/src/ability/crud/create.rs +++ b/src/ability/crud/create.rs @@ -1,31 +1,53 @@ -use crate::prove::{ - parentful::Parentful, - traits::{CheckParents, CheckSelf, HasChecker}, +use crate::{ + ability::traits::Command, + prove::{ + parentful::Parentful, + traits::{CheckParents, CheckSelf, Checkable}, + }, }; -use libipld_core::ipld::Ipld; +use libipld_core::{ipld::Ipld, serde as ipld_serde}; +use serde::{Deserialize, Serialize}; use std::collections::BTreeMap; use url::Url; use super::parents::Mutable; +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +#[serde(deny_unknown_fields)] pub struct Create { - pub uri: Url, - pub args: BTreeMap, + #[serde(skip_serializing_if = "Option::is_none")] + pub uri: Option, + + #[serde(skip_serializing_if = "Option::is_none")] + pub args: Option>, } -pub struct CreateBuilder { - pub uri: Option, - pub args: BTreeMap, +impl Command for Create { + const COMMAND: &'static str = "crud/create"; } -impl HasChecker for CreateBuilder { - type CheckAs = Parentful; +impl From for Ipld { + fn from(create: Create) -> Self { + create.into() + } } -impl CheckSelf for CreateBuilder { +impl TryFrom for Create { + type Error = (); // FIXME + + fn try_from(ipld: Ipld) -> Result { + ipld_serde::from_ipld(ipld).map_err(|_| ()) + } +} + +impl Checkable for Create { + type CheckAs = Parentful; +} + +impl CheckSelf for Create { type Error = (); // FIXME better error - fn check_against_self(&self, other: &Self) -> Result<(), Self::Error> { - if self.uri == other.uri { + fn check_against_self(&self, proof: &Self) -> Result<(), Self::Error> { + if self.uri == proof.uri { Ok(()) } else { Err(()) @@ -33,14 +55,30 @@ impl CheckSelf for CreateBuilder { } } -impl CheckParents for CreateBuilder { +impl CheckParents for Create { type Parents = Mutable; type ParentError = (); fn check_against_parents(&self, other: &Self::Parents) -> Result<(), Self::ParentError> { - match other { - Mutable::Mutate(mutate) => Ok(()), - Mutable::Any(any) => Ok(()), + if let Some(self_uri) = &self.uri { + match other { + Mutable::Any(any) => { + if let Some(proof_uri) = &any.uri { + if self_uri != proof_uri { + return Err(()); + } + } + } + Mutable::Mutate(mutate) => { + if let Some(proof_uri) = &mutate.uri { + if self_uri != proof_uri { + return Err(()); + } + } + } + } } + + Ok(()) } } diff --git a/src/ability/crud/destroy.rs b/src/ability/crud/destroy.rs index ec2ad91e..8021e202 100644 --- a/src/ability/crud/destroy.rs +++ b/src/ability/crud/destroy.rs @@ -1,32 +1,54 @@ -use crate::prove::{ - parentful::Parentful, - traits::{CheckParents, CheckSelf, HasChecker}, +use crate::{ + ability::traits::Command, + prove::{ + parentful::Parentful, + traits::{CheckParents, CheckSelf, Checkable}, + }, }; +use libipld_core::{ipld::Ipld, serde as ipld_serde}; +use serde::{Deserialize, Serialize}; use url::Url; use super::parents::Mutable; -#[derive(Debug, Clone, PartialEq)] -pub struct CrudDestroy { - pub uri: Url, +// Destroy is its own builder +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[serde(deny_unknown_fields)] +pub struct Destroy { + #[serde(default, skip_serializing_if = "Option::is_none")] + pub uri: Option, } -pub struct DestroyBuilder { - pub uri: Option, +impl Command for Destroy { + const COMMAND: &'static str = "crud/destroy"; +} + +impl From for Ipld { + fn from(destroy: Destroy) -> Self { + destroy.into() + } +} + +impl TryFrom for Destroy { + type Error = (); // FIXME + + fn try_from(ipld: Ipld) -> Result { + ipld_serde::from_ipld(ipld).map_err(|_| ()) + } } -impl HasChecker for DestroyBuilder { - type CheckAs = Parentful; +impl Checkable for Destroy { + type CheckAs = Parentful; } -impl CheckSelf for DestroyBuilder { +impl CheckSelf for Destroy { type Error = (); - fn check_against_self(&self, _other: &Self) -> Result<(), Self::Error> { + fn check_against_self(&self, _proof: &Self) -> Result<(), Self::Error> { Ok(()) } } -impl CheckParents for DestroyBuilder { +impl CheckParents for Destroy { type Parents = Mutable; type ParentError = (); diff --git a/src/ability/crud/mutate.rs b/src/ability/crud/mutate.rs index 14b1e18d..a81492e8 100644 --- a/src/ability/crud/mutate.rs +++ b/src/ability/crud/mutate.rs @@ -1,22 +1,47 @@ -use crate::prove::{ - parentful::Parentful, - traits::{CheckParents, CheckSelf, HasChecker}, +use crate::{ + ability::traits::Command, + prove::{ + parentful::Parentful, + traits::{CheckParents, CheckSelf, Checkable}, + }, }; +use libipld_core::{ipld::Ipld, serde as ipld_serde}; +use serde::{Deserialize, Serialize}; use url::Url; use super::any::AnyBuilder; +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[serde(deny_unknown_fields)] pub struct MutateBuilder { pub uri: Option, } -impl HasChecker for MutateBuilder { +impl Command for MutateBuilder { + const COMMAND: &'static str = "crud/mutate"; +} + +impl From for Ipld { + fn from(mutate: MutateBuilder) -> Self { + mutate.into() + } +} + +impl TryFrom for MutateBuilder { + type Error = (); // FIXME + + fn try_from(ipld: Ipld) -> Result { + ipld_serde::from_ipld(ipld).map_err(|_| ()) + } +} + +impl Checkable for MutateBuilder { type CheckAs = Parentful; } impl CheckSelf for MutateBuilder { type Error = (); - fn check_against_self(&self, _other: &Self) -> Result<(), Self::Error> { + fn check_against_self(&self, _proof: &Self) -> Result<(), Self::Error> { Ok(()) } } @@ -26,7 +51,7 @@ impl CheckParents for MutateBuilder { type Parents = AnyBuilder; type ParentError = (); - fn check_against_parents(&self, other: &Self::Parents) -> Result<(), Self::ParentError> { + fn check_against_parents(&self, _proof: &Self::Parents) -> Result<(), Self::ParentError> { Ok(()) } } diff --git a/src/ability/crud/parents.rs b/src/ability/crud/parents.rs index 3caf7025..44177e30 100644 --- a/src/ability/crud/parents.rs +++ b/src/ability/crud/parents.rs @@ -1,6 +1,9 @@ use super::{any::AnyBuilder, mutate::MutateBuilder}; use crate::prove::traits::CheckSelf; +use serde::{Deserialize, Serialize}; +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[serde(deny_unknown_fields)] pub enum Mutable { Mutate(MutateBuilder), Any(AnyBuilder), @@ -8,9 +11,9 @@ pub enum Mutable { impl CheckSelf for Mutable { type Error = (); - fn check_against_self(&self, other: &Self) -> Result<(), Self::Error> { + fn check_against_self(&self, proof: &Self) -> Result<(), Self::Error> { match self { - Mutable::Mutate(mutate) => match other { + Mutable::Mutate(mutate) => match proof { Mutable::Mutate(other_mutate) => mutate.check_against_self(other_mutate), Mutable::Any(any) => Ok(()), }, diff --git a/src/ability/crud/read.rs b/src/ability/crud/read.rs index d56c9182..797e9656 100644 --- a/src/ability/crud/read.rs +++ b/src/ability/crud/read.rs @@ -1,35 +1,58 @@ -use crate::prove::{ - parentful::Parentful, - traits::{CheckParents, CheckSelf, HasChecker}, +use crate::{ + ability::traits::Command, + prove::{ + parentful::Parentful, + traits::{CheckParents, CheckSelf, Checkable}, + }, }; -use libipld_core::ipld::Ipld; +use libipld_core::{ipld::Ipld, serde as ipld_serde}; +use serde::{Deserialize, Serialize}; use std::collections::BTreeMap; use url::Url; use super::any::AnyBuilder; +// Read is its own builder +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +#[serde(deny_unknown_fields)] pub struct Read { - pub uri: Url, - pub args: BTreeMap, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub uri: Option, + + #[serde(default, skip_serializing_if = "Option::is_none")] + pub args: Option>, } -pub struct ReadBuilder { - pub uri: Option, - pub args: BTreeMap, +impl Command for Read { + const COMMAND: &'static str = "crud/read"; +} + +impl From for Ipld { + fn from(read: Read) -> Self { + read.into() + } +} + +impl TryFrom for Read { + type Error = (); // FIXME + + fn try_from(ipld: Ipld) -> Result { + ipld_serde::from_ipld(ipld).map_err(|_| ()) + } } -impl HasChecker for ReadBuilder { - type CheckAs = Parentful; +impl Checkable for Read { + type CheckAs = Parentful; } -impl CheckSelf for ReadBuilder { +impl CheckSelf for Read { type Error = (); - fn check_against_self(&self, other: &Self) -> Result<(), Self::Error> { + fn check_against_self(&self, proof: &Self) -> Result<(), Self::Error> { Ok(()) } } -impl CheckParents for ReadBuilder { +impl CheckParents for Read { type Parents = AnyBuilder; type ParentError = (); fn check_against_parents(&self, other: &Self::Parents) -> Result<(), Self::ParentError> { diff --git a/src/ability/crud/update.rs b/src/ability/crud/update.rs index 53ee6dd5..94d07d41 100644 --- a/src/ability/crud/update.rs +++ b/src/ability/crud/update.rs @@ -1,31 +1,76 @@ -use crate::prove::{ - parentful::Parentful, - traits::{CheckParents, CheckSelf, HasChecker}, +use crate::{ + ability::traits::Command, + prove::{ + parentful::Parentful, + traits::{CheckParents, CheckSelf, Checkable}, + }, }; -use libipld_core::ipld::Ipld; +use libipld_core::{ipld::Ipld, serde as ipld_serde}; +use serde::{Deserialize, Serialize}; use std::collections::BTreeMap; use url::Url; use super::parents::Mutable; -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +#[serde(deny_unknown_fields)] pub struct Update { - pub uri: Url, - pub args: BTreeMap, String>, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub uri: Option, + + #[serde(default, skip_serializing_if = "BTreeMap::is_empty")] + pub args: BTreeMap, +} + +impl From for Ipld { + fn from(udpdate: Update) -> Self { + udpdate.into() + } +} + +impl TryFrom for Update { + type Error = (); // FIXME + + fn try_from(ipld: Ipld) -> Result { + ipld_serde::from_ipld(ipld).map_err(|_| ()) + } +} + +impl Command for Update { + const COMMAND: &'static str = "crud/update"; } +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +#[serde(deny_unknown_fields)] pub struct UpdateBuilder { + #[serde(default, skip_serializing_if = "Option::is_none")] pub uri: Option, - pub args: BTreeMap, // FIXME use a type param? + + #[serde(default, skip_serializing_if = "Option::is_none")] + pub args: Option>, // FIXME use a type param? +} + +impl From for Ipld { + fn from(udpdate: UpdateBuilder) -> Self { + udpdate.into() + } +} + +impl TryFrom for UpdateBuilder { + type Error = (); // FIXME + + fn try_from(ipld: Ipld) -> Result { + ipld_serde::from_ipld(ipld).map_err(|_| ()) + } } -impl HasChecker for UpdateBuilder { +impl Checkable for UpdateBuilder { type CheckAs = Parentful; } impl CheckSelf for UpdateBuilder { type Error = (); - fn check_against_self(&self, _other: &Self) -> Result<(), Self::Error> { + fn check_against_self(&self, _proof: &Self) -> Result<(), Self::Error> { Ok(()) } } @@ -35,9 +80,25 @@ impl CheckParents for UpdateBuilder { type ParentError = (); fn check_against_parents(&self, other: &Self::Parents) -> Result<(), Self::ParentError> { - match other { - Mutable::Mutate(mutate) => Ok(()), - Mutable::Any(any) => Ok(()), + if let Some(self_uri) = &self.uri { + match other { + Mutable::Any(any) => { + if let Some(proof_uri) = &any.uri { + if self_uri != proof_uri { + return Err(()); + } + } + } + Mutable::Mutate(mutate) => { + if let Some(proof_uri) = &mutate.uri { + if self_uri != proof_uri { + return Err(()); + } + } + } + } } + + Ok(()) } } diff --git a/src/ability/msg.rs b/src/ability/msg.rs index 8b21f673..33792dd3 100644 --- a/src/ability/msg.rs +++ b/src/ability/msg.rs @@ -1,240 +1,3 @@ -use crate::{ - ability::traits::{Command, Delegatable, Resolvable}, - promise::Deferrable, - prove::TryProve, -}; -use libipld_core::{ipld::Ipld, serde as ipld_serde}; -use serde_derive::{Deserialize, Serialize}; -use url::Url; - -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] -#[serde(deny_unknown_fields)] -pub struct Msg { - to: Url, - from: Url, -} - -impl Command for Msg { - const COMMAND: &'static str = "msg/*"; -} - -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] -#[serde(deny_unknown_fields)] -pub struct MsgBuilder { - #[serde(skip_serializing_if = "Option::is_none")] - to: Option, - - #[serde(skip_serializing_if = "Option::is_none")] - from: Option, -} - -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] -#[serde(deny_unknown_fields)] -pub struct MsgDeferrable { - to: Deferrable, - from: Deferrable, -} - -impl Delegatable for Msg { - type Builder = MsgBuilder; -} - -impl Resolvable for Msg { - type Awaiting = MsgDeferrable; -} - -impl From for MsgBuilder { - fn from(msg: Msg) -> Self { - Self { - to: Some(msg.to), - from: Some(msg.from), - } - } -} - -impl TryFrom for Msg { - type Error = (); - - fn try_from(builder: MsgBuilder) -> Result { - if let (Some(to), Some(from)) = (builder.clone().to, builder.clone().from) { - Ok(Self { to, from }) - } else { - Err(()) // FIXME - } - } -} - -impl From for MsgDeferrable { - fn from(msg: Msg) -> Self { - Self { - to: Deferrable::Resolved(msg.to), - from: Deferrable::Resolved(msg.from), - } - } -} - -impl TryFrom for Msg { - type Error = (); - - fn try_from(deferable: MsgDeferrable) -> Result { - if let (Deferrable::Resolved(to), Deferrable::Resolved(from)) = - (deferable.to, deferable.from) - { - Ok(Self { to, from }) - } else { - Err(()) // FIXME - } - } -} - -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] -#[serde(deny_unknown_fields)] -pub struct MsgSend { - to: Url, - from: Url, - message: String, -} - -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] -#[serde(deny_unknown_fields)] -pub struct MsgSendBuilder { - #[serde(skip_serializing_if = "Option::is_none")] - to: Option, - #[serde(skip_serializing_if = "Option::is_none")] - from: Option, - #[serde(skip_serializing_if = "Option::is_none")] - message: Option, -} - -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] -#[serde(deny_unknown_fields)] -pub struct MsgSendDeferrable { - to: Deferrable, - from: Deferrable, - message: Deferrable, -} - -// TODO is the to or from often also the subject? Shoudl that be accounted for? -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] -#[serde(deny_unknown_fields)] -pub struct MsgReceive { - to: Url, - from: Url, -} - -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] -#[serde(deny_unknown_fields)] -pub struct MsgReceiveBuilder { - #[serde(skip_serializing_if = "Option::is_none")] - to: Option, - #[serde(skip_serializing_if = "Option::is_none")] - from: Option, -} - -impl From for MsgReceiveBuilder { - fn from(msg: MsgReceive) -> Self { - Self { - to: Some(msg.to), - from: Some(msg.from), - } - } -} - -impl TryFrom for MsgReceive { - type Error = MsgReceiveBuilder; - - fn try_from(builder: MsgReceiveBuilder) -> Result { - // FIXME - if let (Some(to), Some(from)) = (builder.clone().to, builder.clone().from) { - Ok(Self { to, from }) - } else { - Err(builder.clone()) // FIXME - } - } -} - -impl From for Ipld { - fn from(msg_rcv: MsgReceive) -> Self { - msg_rcv.into() - } -} - -impl TryFrom for MsgReceiveBuilder { - type Error = (); - - fn try_from(ipld: Ipld) -> Result { - ipld_serde::from_ipld(ipld).map_err(|_| ()) - } -} - -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] -#[serde(deny_unknown_fields)] -pub struct MsgReceiveDeferrable { - to: Deferrable, - from: Deferrable, -} - -impl Command for MsgReceive { - const COMMAND: &'static str = "msg/receive"; -} - -impl TryFrom for MsgReceive { - type Error = (); // FIXME - - fn try_from(ipld: Ipld) -> Result { - ipld_serde::from_ipld(ipld).map_err(|_| ()) - } -} - -impl<'a> TryProve<&'a Msg> for &'a Msg { - type Error = (); // FIXME - type Proven = &'a Msg; - - fn try_prove(self, proof: &'a Msg) -> Result { - if self == proof { - Ok(self) - } else { - Err(()) - } - } -} - -impl<'a> TryProve<&'a Msg> for &'a MsgSend { - type Error = (); // FIXME - type Proven = &'a MsgSend; - - fn try_prove(self, proof: &'a Msg) -> Result { - if self.to == proof.to && self.from == proof.from { - Ok(self) - } else { - Err(()) - } - } -} - -impl<'a> TryProve<&'a Msg> for &'a MsgReceive { - type Error = (); // FIXME - type Proven = &'a MsgReceive; - - fn try_prove(self, proof: &'a Msg) -> Result { - if self.to == proof.to && self.from == proof.from { - Ok(self) - } else { - Err(()) - } - } -} - -// FIXME this needs to work on builders! -impl<'a> TryProve<&'a MsgReceive> for &'a MsgReceive { - type Error = (); // FIXME - type Proven = &'a MsgReceive; - - fn try_prove(self, proof: &'a MsgReceive) -> Result { - if self == proof { - Ok(self) - } else { - Err(()) - } - } -} +pub mod any; +pub mod receive; +pub mod send; diff --git a/src/ability/msg/any.rs b/src/ability/msg/any.rs new file mode 100644 index 00000000..214aa653 --- /dev/null +++ b/src/ability/msg/any.rs @@ -0,0 +1,45 @@ +use crate::{ + ability::traits::Command, + prove::{ + parentless::Parentless, + traits::{CheckSelf, Checkable}, + }, +}; +use libipld_core::{error::SerdeError, ipld::Ipld, serde as ipld_serde}; +use serde::{Deserialize, Serialize}; +use url::Url; + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[serde(deny_unknown_fields)] +pub struct Any { + pub from: Option, +} + +impl Command for Any { + const COMMAND: &'static str = "msg"; +} + +impl From for Ipld { + fn from(any: Any) -> Self { + any.into() + } +} + +impl TryFrom for Any { + type Error = SerdeError; + + fn try_from(ipld: Ipld) -> Result { + ipld_serde::from_ipld(ipld) + } +} + +impl Checkable for Any { + type CheckAs = Parentless; +} + +impl CheckSelf for Any { + type Error = (); + fn check_against_self(&self, _proof: &Self) -> Result<(), Self::Error> { + Ok(()) + } +} diff --git a/src/ability/msg/receive.rs b/src/ability/msg/receive.rs new file mode 100644 index 00000000..fdba60c8 --- /dev/null +++ b/src/ability/msg/receive.rs @@ -0,0 +1,80 @@ +use crate::{ + ability::traits::Command, + prove::{ + parentful::Parentful, + traits::{CheckParents, CheckSelf, Checkable}, + }, +}; +use libipld_core::{error::SerdeError, ipld::Ipld, serde as ipld_serde}; +use serde::{Deserialize, Serialize}; +use url::Url; + +use super::any as msg; + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[serde(deny_unknown_fields)] +pub struct Receive { + pub from: Option, +} + +// #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +// #[serde(deny_unknown_fields)] +// pub struct MsgReceiveDeferrable { +// to: Deferrable, +// from: Deferrable, +// } + +impl Command for Receive { + const COMMAND: &'static str = "msg/send"; +} + +impl From for Ipld { + fn from(receive: Receive) -> Self { + receive.into() + } +} + +impl TryFrom for Receive { + type Error = SerdeError; + + fn try_from(ipld: Ipld) -> Result { + ipld_serde::from_ipld(ipld) + } +} + +impl Checkable for Receive { + type CheckAs = Parentful; +} + +impl CheckSelf for Receive { + type Error = (); // FIXME better error + fn check_against_self(&self, proof: &Self) -> Result<(), Self::Error> { + if let Some(self_from) = &self.from { + if let Some(proof_from) = &proof.from { + if self_from != proof_from { + return Err(()); + } + } + } + + Ok(()) + } +} + +impl CheckParents for Receive { + type Parents = msg::Any; + type ParentError = ::Error; + + // FIXME rename other to proof + fn check_against_parents(&self, other: &Self::Parents) -> Result<(), Self::ParentError> { + if let Some(self_from) = &self.from { + if let Some(proof_from) = &other.from { + if self_from != proof_from { + return Err(()); + } + } + } + + Ok(()) + } +} diff --git a/src/ability/msg/send.rs b/src/ability/msg/send.rs new file mode 100644 index 00000000..2a1d7857 --- /dev/null +++ b/src/ability/msg/send.rs @@ -0,0 +1,113 @@ +use crate::{ + ability::traits::Command, + prove::{ + parentful::Parentful, + traits::{CheckParents, CheckSelf, Checkable}, + }, +}; +use libipld_core::{error::SerdeError, ipld::Ipld, serde as ipld_serde}; +use serde::{Deserialize, Serialize}; +use url::Url; + +use super::any as msg; + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[serde(deny_unknown_fields)] +pub struct Send { + pub to: Url, + pub from: Url, + pub message: String, +} + +impl Command for Send { + const COMMAND: &'static str = "msg/send"; +} + +impl From for Ipld { + fn from(send: Send) -> Self { + send.into() + } +} + +impl TryFrom for Send { + type Error = SerdeError; + + fn try_from(ipld: Ipld) -> Result { + ipld_serde::from_ipld(ipld) + } +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[serde(deny_unknown_fields)] +pub struct SendBuilder { + pub to: Option, + pub from: Option, + pub message: Option, +} + +impl From for Ipld { + fn from(send: SendBuilder) -> Self { + send.into() + } +} + +impl TryFrom for SendBuilder { + type Error = SerdeError; + + fn try_from(ipld: Ipld) -> Result { + ipld_serde::from_ipld(ipld) + } +} + +impl Checkable for SendBuilder { + type CheckAs = Parentful; +} + +impl CheckSelf for SendBuilder { + type Error = (); // FIXME better error + fn check_against_self(&self, proof: &Self) -> Result<(), Self::Error> { + if let Some(self_to) = &self.to { + if let Some(proof_to) = &proof.to { + if self_to != proof_to { + return Err(()); + } + } + } + + if let Some(self_from) = &self.from { + if let Some(proof_from) = &proof.from { + if self_from != proof_from { + return Err(()); + } + } + } + + if let Some(self_msg) = &self.message { + if let Some(proof_msg) = &proof.message { + if self_msg != proof_msg { + return Err(()); + } + } + } + + Ok(()) + } +} + +impl CheckParents for SendBuilder { + type Parents = msg::Any; + type ParentError = ::Error; + + // FIXME rename other to proof + fn check_against_parents(&self, other: &Self::Parents) -> Result<(), Self::ParentError> { + if let Some(self_from) = &self.from { + if let Some(proof_from) = &other.from { + if self_from != proof_from { + return Err(()); + } + } + } + + Ok(()) + } +} diff --git a/src/delegation/payload.rs b/src/delegation/payload.rs index cad89185..a78050ed 100644 --- a/src/delegation/payload.rs +++ b/src/delegation/payload.rs @@ -4,7 +4,7 @@ use crate::{ capsule::Capsule, did::Did, nonce::Nonce, - prove::traits::{HasChecker, Prove}, + prove::traits::{Checkable, Prove}, time::Timestamp, }; use libipld_core::{ipld::Ipld, serde as ipld_serde}; @@ -84,7 +84,7 @@ impl From> for Ipld { use crate::{ability::traits::Resolvable, invocation::payload as invocation}; -impl<'a, T: Delegatable + Resolvable + HasChecker + Clone, C: Condition> Payload { +impl<'a, T: Delegatable + Resolvable + Checkable + Clone, C: Condition> Payload { pub fn check( invoked: invocation::Payload, // FIXME promisory version proofs: Vec>, @@ -121,14 +121,14 @@ impl<'a, T: Delegatable + Resolvable + HasChecker + Clone, C: Condition> Payload } #[derive(Clone)] -struct Acc { +struct Acc { issuer: Did, subject: Did, check_chain: T::CheckAs, } // FIXME this needs to move to Delegatable -fn step1<'a, T: HasChecker, U: Delegatable, C: Condition>( +fn step1<'a, T: Checkable, U: Delegatable, C: Condition>( prev: &'a Acc, proof: &'a Payload, invoked_ipld: &'a Ipld, diff --git a/src/prove/internal.rs b/src/prove/internal.rs index 8963d51e..a932a8a9 100644 --- a/src/prove/internal.rs +++ b/src/prove/internal.rs @@ -1,2 +1,2 @@ // FIXME rename -pub trait IsChecker {} +pub trait Checker {} diff --git a/src/prove/parentful.rs b/src/prove/parentful.rs index 19fb8f19..62ce0c8f 100644 --- a/src/prove/parentful.rs +++ b/src/prove/parentful.rs @@ -1,12 +1,35 @@ use super::{ - internal::IsChecker, + internal::Checker, traits::{CheckParents, CheckSelf, Prove}, }; +use libipld_core::{error::SerdeError, ipld::Ipld, serde as ipld_serde}; +use serde::{de::DeserializeOwned, Deserialize, Serialize}; +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub enum Parentful { Any, Parents(T::Parents), - Me(T), + This(T), +} + +impl From> for Ipld +where + Ipld: From, +{ + fn from(parentful: Parentful) -> Self { + parentful.into() + } +} + +impl + DeserializeOwned + CheckParents> TryFrom for Parentful +where + ::Parents: DeserializeOwned, +{ + type Error = SerdeError; + + fn try_from(ipld: Ipld) -> Result { + ipld_serde::from_ipld(ipld) + } } // TODO better names & derivations @@ -23,7 +46,7 @@ where EscelationError, } -impl IsChecker for Parentful {} +impl Checker for Parentful {} impl CheckSelf for Parentful where @@ -31,24 +54,24 @@ where { type Error = ParentfulError; - fn check_against_self(&self, other: &Self) -> Result<(), Self::Error> { + fn check_against_self(&self, proof: &Self) -> Result<(), Self::Error> { match self { Parentful::Any => Ok(()), - Parentful::Parents(parents) => match other { + Parentful::Parents(parents) => match proof { Parentful::Any => Ok(()), Parentful::Parents(other_parents) => parents .check_against_self(other_parents) .map_err(ParentfulError::ParentSelfError), - Parentful::Me(_other_me) => Err(ParentfulError::EscelationError), + Parentful::This(_other_me) => Err(ParentfulError::EscelationError), }, - Parentful::Me(me) => match other { + Parentful::This(this) => match proof { Parentful::Any => Ok(()), - Parentful::Parents(other_parents) => me + Parentful::Parents(other_parents) => this .check_against_parents(other_parents) .map_err(ParentfulError::ParentError), - Parentful::Me(other_me) => me - .check_against_self(other_me) - .map_err(ParentfulError::Error), + Parentful::This(that) => { + this.check_against_self(that).map_err(ParentfulError::Error) + } }, } } @@ -67,7 +90,7 @@ where match self { Parentful::Any => Ok(()), Parentful::Parents(parents) => parents.check_against_self(other).map_err(|_| todo!()), // FIXME ParentfulError::ParentError), - Parentful::Me(me) => me + Parentful::This(this) => this .check_against_parents(other) .map_err(ParentfulError::ParentError), } @@ -79,13 +102,15 @@ where T::Parents: CheckSelf, { type ProveError = ParentfulError; - fn check<'a>(&'a self, other: &'a Parentful) -> Result<(), Self::ProveError> { - match other { + fn check<'a>(&'a self, proof: &'a Parentful) -> Result<(), Self::ProveError> { + match proof { Parentful::Any => Ok(()), Parentful::Parents(parents) => self .check_against_parents(parents) .map_err(ParentfulError::ParentError), - Parentful::Me(me) => self.check_against_self(&me).map_err(ParentfulError::Error), + Parentful::This(that) => self + .check_against_self(&that) + .map_err(ParentfulError::Error), } } } diff --git a/src/prove/parentless.rs b/src/prove/parentless.rs index 5890135f..aa669af2 100644 --- a/src/prove/parentless.rs +++ b/src/prove/parentless.rs @@ -1,24 +1,44 @@ use super::{ - internal::IsChecker, + internal::Checker, traits::{CheckSelf, Prove}, }; +use libipld_core::{error::SerdeError, ipld::Ipld, serde as ipld_serde}; +use serde::{de::DeserializeOwned, Deserialize, Serialize}; +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub enum Parentless { Any, - Me(T), + This(T), } -impl IsChecker for Parentless {} +impl From> for Ipld +where + Ipld: From, +{ + fn from(parentless: Parentless) -> Self { + parentless.into() + } +} + +impl + DeserializeOwned> TryFrom for Parentless { + type Error = SerdeError; + + fn try_from(ipld: Ipld) -> Result { + ipld_serde::from_ipld(ipld) + } +} + +impl Checker for Parentless {} impl CheckSelf for Parentless { type Error = T::Error; - fn check_against_self(&self, other: &Self) -> Result<(), Self::Error> { + fn check_against_self(&self, proof: &Self) -> Result<(), Self::Error> { match self { Parentless::Any => Ok(()), // FIXME MUST forward that this was an ANY this into the result! - Parentless::Me(me) => match other { + Parentless::This(this) => match proof { Parentless::Any => Ok(()), - Parentless::Me(other) => me.check_against_self(other), + Parentless::This(other) => this.check_against_self(other), }, } } @@ -26,10 +46,10 @@ impl CheckSelf for Parentless { impl Prove> for T { type ProveError = T::Error; - fn check<'a>(&'a self, other: &'a Parentless) -> Result<(), T::Error> { - match other { + fn check<'a>(&'a self, proof: &'a Parentless) -> Result<(), T::Error> { + match proof { Parentless::Any => Ok(()), - Parentless::Me(me) => self.check_against_self(&me), + Parentless::This(this) => self.check_against_self(&this), } } } diff --git a/src/prove/traits.rs b/src/prove/traits.rs index b0e89541..d50f52e6 100644 --- a/src/prove/traits.rs +++ b/src/prove/traits.rs @@ -1,31 +1,27 @@ -use super::internal::IsChecker; +use super::internal::Checker; pub trait CheckSelf { type Error; - fn check_against_self(&self, other: &Self) -> Result<(), Self::Error>; + fn check_against_self(&self, proof: &Self) -> Result<(), Self::Error>; } pub trait CheckParents: CheckSelf { type Parents; type ParentError; - fn check_against_parents(&self, other: &Self::Parents) -> Result<(), Self::ParentError>; + fn check_against_parents(&self, proof: &Self::Parents) -> Result<(), Self::ParentError>; } -pub trait HasChecker: CheckSelf { - type CheckAs: IsChecker; +pub trait Checkable: CheckSelf { + type CheckAs: Checker; } -pub trait Prove { +// FIXME is it worth locking consumers out with that Checker bound? +pub trait Prove { type ProveError; - fn check<'a>(&'a self, other: &'a T) -> Result<(), Self::ProveError>; -} - -// FIXME needed? -pub trait IntoParent { - fn as_parent(self) -> T::Parents; + fn check<'a>(&'a self, proof: &'a T) -> Result<(), Self::ProveError>; } // Nightly only... sadness -// trait Foo = HasChecker + Prove; +// trait Foo = Checkable + Prove; From f24fcf854541027066ea0c43ed7e5aee3d577512 Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Sun, 28 Jan 2024 23:37:08 -0800 Subject: [PATCH 029/188] Proving almost done --- src/ability/crud/any.rs | 9 +-- src/ability/crud/create.rs | 11 +-- src/ability/crud/destroy.rs | 11 +-- src/ability/crud/mutate.rs | 11 +-- src/ability/crud/parents.rs | 8 +- src/ability/crud/read.rs | 11 +-- src/ability/crud/update.rs | 43 ++++------- src/ability/msg/any.rs | 9 +-- src/ability/msg/receive.rs | 34 ++------- src/ability/msg/send.rs | 51 +++---------- src/delegation/condition.rs | 7 +- src/delegation/condition/common.rs | 14 ++-- src/delegation/condition/traits.rs | 5 ++ src/delegation/payload.rs | 86 +++++++++++++-------- src/lib.rs | 2 +- src/proof.rs | 9 +++ src/proof/checkable.rs | 5 ++ src/{prove => proof}/internal.rs | 1 - src/proof/parentful.rs | 59 +++++++++++++++ src/proof/parentless.rs | 48 ++++++++++++ src/proof/parents.rs | 8 ++ src/proof/prove.rs | 16 ++++ src/proof/same.rs | 50 +++++++++++++ src/prove.rs | 15 ---- src/prove/parentful.rs | 116 ----------------------------- src/prove/parentless.rs | 55 -------------- src/prove/traits.rs | 27 ------- 27 files changed, 318 insertions(+), 403 deletions(-) create mode 100644 src/delegation/condition/traits.rs create mode 100644 src/proof.rs create mode 100644 src/proof/checkable.rs rename src/{prove => proof}/internal.rs (56%) create mode 100644 src/proof/parentful.rs create mode 100644 src/proof/parentless.rs create mode 100644 src/proof/parents.rs create mode 100644 src/proof/prove.rs create mode 100644 src/proof/same.rs delete mode 100644 src/prove.rs delete mode 100644 src/prove/parentful.rs delete mode 100644 src/prove/parentless.rs delete mode 100644 src/prove/traits.rs diff --git a/src/ability/crud/any.rs b/src/ability/crud/any.rs index 5c17b6b3..a3b0b6c0 100644 --- a/src/ability/crud/any.rs +++ b/src/ability/crud/any.rs @@ -1,9 +1,6 @@ use crate::{ ability::traits::Command, - prove::{ - parentless::Parentless, - traits::{CheckSelf, Checkable}, - }, + proof::{checkable::Checkable, parentless::Parentless, same::CheckSame}, }; use serde::{Deserialize, Serialize}; use url::Url; @@ -22,9 +19,9 @@ impl Checkable for AnyBuilder { type CheckAs = Parentless; } -impl CheckSelf for AnyBuilder { +impl CheckSame for AnyBuilder { type Error = (); - fn check_against_self(&self, _proof: &Self) -> Result<(), Self::Error> { + fn check_same(&self, _proof: &Self) -> Result<(), Self::Error> { Ok(()) } } diff --git a/src/ability/crud/create.rs b/src/ability/crud/create.rs index 1b0bdab4..9e9f4ec7 100644 --- a/src/ability/crud/create.rs +++ b/src/ability/crud/create.rs @@ -1,9 +1,6 @@ use crate::{ ability::traits::Command, - prove::{ - parentful::Parentful, - traits::{CheckParents, CheckSelf, Checkable}, - }, + proof::{checkable::Checkable, parentful::Parentful, parents::CheckParents, same::CheckSame}, }; use libipld_core::{ipld::Ipld, serde as ipld_serde}; use serde::{Deserialize, Serialize}; @@ -44,9 +41,9 @@ impl Checkable for Create { type CheckAs = Parentful; } -impl CheckSelf for Create { +impl CheckSame for Create { type Error = (); // FIXME better error - fn check_against_self(&self, proof: &Self) -> Result<(), Self::Error> { + fn check_same(&self, proof: &Self) -> Result<(), Self::Error> { if self.uri == proof.uri { Ok(()) } else { @@ -59,7 +56,7 @@ impl CheckParents for Create { type Parents = Mutable; type ParentError = (); - fn check_against_parents(&self, other: &Self::Parents) -> Result<(), Self::ParentError> { + fn check_parents(&self, other: &Self::Parents) -> Result<(), Self::ParentError> { if let Some(self_uri) = &self.uri { match other { Mutable::Any(any) => { diff --git a/src/ability/crud/destroy.rs b/src/ability/crud/destroy.rs index 8021e202..9a2f209d 100644 --- a/src/ability/crud/destroy.rs +++ b/src/ability/crud/destroy.rs @@ -1,9 +1,6 @@ use crate::{ ability::traits::Command, - prove::{ - parentful::Parentful, - traits::{CheckParents, CheckSelf, Checkable}, - }, + proof::{checkable::Checkable, parentful::Parentful, parents::CheckParents, same::CheckSame}, }; use libipld_core::{ipld::Ipld, serde as ipld_serde}; use serde::{Deserialize, Serialize}; @@ -41,9 +38,9 @@ impl Checkable for Destroy { type CheckAs = Parentful; } -impl CheckSelf for Destroy { +impl CheckSame for Destroy { type Error = (); - fn check_against_self(&self, _proof: &Self) -> Result<(), Self::Error> { + fn check_same(&self, _proof: &Self) -> Result<(), Self::Error> { Ok(()) } } @@ -52,7 +49,7 @@ impl CheckParents for Destroy { type Parents = Mutable; type ParentError = (); - fn check_against_parents(&self, other: &Self::Parents) -> Result<(), Self::ParentError> { + fn check_parents(&self, other: &Self::Parents) -> Result<(), Self::ParentError> { match other { Mutable::Mutate(mutate) => Ok(()), Mutable::Any(any) => Ok(()), diff --git a/src/ability/crud/mutate.rs b/src/ability/crud/mutate.rs index a81492e8..c03836bc 100644 --- a/src/ability/crud/mutate.rs +++ b/src/ability/crud/mutate.rs @@ -1,9 +1,6 @@ use crate::{ ability::traits::Command, - prove::{ - parentful::Parentful, - traits::{CheckParents, CheckSelf, Checkable}, - }, + proof::{checkable::Checkable, parentful::Parentful, parents::CheckParents, same::CheckSame}, }; use libipld_core::{ipld::Ipld, serde as ipld_serde}; use serde::{Deserialize, Serialize}; @@ -39,9 +36,9 @@ impl Checkable for MutateBuilder { type CheckAs = Parentful; } -impl CheckSelf for MutateBuilder { +impl CheckSame for MutateBuilder { type Error = (); - fn check_against_self(&self, _proof: &Self) -> Result<(), Self::Error> { + fn check_same(&self, _proof: &Self) -> Result<(), Self::Error> { Ok(()) } } @@ -51,7 +48,7 @@ impl CheckParents for MutateBuilder { type Parents = AnyBuilder; type ParentError = (); - fn check_against_parents(&self, _proof: &Self::Parents) -> Result<(), Self::ParentError> { + fn check_parents(&self, _proof: &Self::Parents) -> Result<(), Self::ParentError> { Ok(()) } } diff --git a/src/ability/crud/parents.rs b/src/ability/crud/parents.rs index 44177e30..dc8c6d7b 100644 --- a/src/ability/crud/parents.rs +++ b/src/ability/crud/parents.rs @@ -1,5 +1,5 @@ use super::{any::AnyBuilder, mutate::MutateBuilder}; -use crate::prove::traits::CheckSelf; +use crate::proof::same::CheckSame; use serde::{Deserialize, Serialize}; #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] @@ -9,12 +9,12 @@ pub enum Mutable { Any(AnyBuilder), } -impl CheckSelf for Mutable { +impl CheckSame for Mutable { type Error = (); - fn check_against_self(&self, proof: &Self) -> Result<(), Self::Error> { + fn check_same(&self, proof: &Self) -> Result<(), Self::Error> { match self { Mutable::Mutate(mutate) => match proof { - Mutable::Mutate(other_mutate) => mutate.check_against_self(other_mutate), + Mutable::Mutate(other_mutate) => mutate.check_same(other_mutate), Mutable::Any(any) => Ok(()), }, _ => Err(()), diff --git a/src/ability/crud/read.rs b/src/ability/crud/read.rs index 797e9656..2cc601fc 100644 --- a/src/ability/crud/read.rs +++ b/src/ability/crud/read.rs @@ -1,9 +1,6 @@ use crate::{ ability::traits::Command, - prove::{ - parentful::Parentful, - traits::{CheckParents, CheckSelf, Checkable}, - }, + proof::{checkable::Checkable, parentful::Parentful, parents::CheckParents, same::CheckSame}, }; use libipld_core::{ipld::Ipld, serde as ipld_serde}; use serde::{Deserialize, Serialize}; @@ -45,9 +42,9 @@ impl Checkable for Read { type CheckAs = Parentful; } -impl CheckSelf for Read { +impl CheckSame for Read { type Error = (); - fn check_against_self(&self, proof: &Self) -> Result<(), Self::Error> { + fn check_same(&self, proof: &Self) -> Result<(), Self::Error> { Ok(()) } } @@ -55,7 +52,7 @@ impl CheckSelf for Read { impl CheckParents for Read { type Parents = AnyBuilder; type ParentError = (); - fn check_against_parents(&self, other: &Self::Parents) -> Result<(), Self::ParentError> { + fn check_parents(&self, other: &Self::Parents) -> Result<(), Self::ParentError> { Ok(()) } } diff --git a/src/ability/crud/update.rs b/src/ability/crud/update.rs index 94d07d41..a5463413 100644 --- a/src/ability/crud/update.rs +++ b/src/ability/crud/update.rs @@ -1,9 +1,6 @@ use crate::{ ability::traits::Command, - prove::{ - parentful::Parentful, - traits::{CheckParents, CheckSelf, Checkable}, - }, + proof::{checkable::Checkable, parentful::Parentful, parents::CheckParents, same::CheckSame}, }; use libipld_core::{ipld::Ipld, serde as ipld_serde}; use serde::{Deserialize, Serialize}; @@ -68,37 +65,23 @@ impl Checkable for UpdateBuilder { type CheckAs = Parentful; } -impl CheckSelf for UpdateBuilder { - type Error = (); - fn check_against_self(&self, _proof: &Self) -> Result<(), Self::Error> { - Ok(()) +impl CheckSame for UpdateBuilder { + type Error = (); // FIXME + + fn check_same(&self, proof: &Self) -> Result<(), Self::Error> { + self.uri.check_same(&proof.uri).map_err(|_| ())?; + self.args.check_same(&proof.args).map_err(|_| ()) } } impl CheckParents for UpdateBuilder { type Parents = Mutable; - type ParentError = (); - - fn check_against_parents(&self, other: &Self::Parents) -> Result<(), Self::ParentError> { - if let Some(self_uri) = &self.uri { - match other { - Mutable::Any(any) => { - if let Some(proof_uri) = &any.uri { - if self_uri != proof_uri { - return Err(()); - } - } - } - Mutable::Mutate(mutate) => { - if let Some(proof_uri) = &mutate.uri { - if self_uri != proof_uri { - return Err(()); - } - } - } - } - } + type ParentError = (); // FIXME - Ok(()) + fn check_parents(&self, proof: &Self::Parents) -> Result<(), Self::ParentError> { + match proof { + Mutable::Any(any) => self.uri.check_same(&any.uri).map_err(|_| ()), + Mutable::Mutate(mutate) => self.uri.check_same(&mutate.uri).map_err(|_| ()), + } } } diff --git a/src/ability/msg/any.rs b/src/ability/msg/any.rs index 214aa653..7d80f3ec 100644 --- a/src/ability/msg/any.rs +++ b/src/ability/msg/any.rs @@ -1,9 +1,6 @@ use crate::{ ability::traits::Command, - prove::{ - parentless::Parentless, - traits::{CheckSelf, Checkable}, - }, + proof::{checkable::Checkable, parentless::Parentless, same::CheckSame}, }; use libipld_core::{error::SerdeError, ipld::Ipld, serde as ipld_serde}; use serde::{Deserialize, Serialize}; @@ -37,9 +34,9 @@ impl Checkable for Any { type CheckAs = Parentless; } -impl CheckSelf for Any { +impl CheckSame for Any { type Error = (); - fn check_against_self(&self, _proof: &Self) -> Result<(), Self::Error> { + fn check_same(&self, _proof: &Self) -> Result<(), Self::Error> { Ok(()) } } diff --git a/src/ability/msg/receive.rs b/src/ability/msg/receive.rs index fdba60c8..ce818d1c 100644 --- a/src/ability/msg/receive.rs +++ b/src/ability/msg/receive.rs @@ -1,9 +1,6 @@ use crate::{ ability::traits::Command, - prove::{ - parentful::Parentful, - traits::{CheckParents, CheckSelf, Checkable}, - }, + proof::{checkable::Checkable, parentful::Parentful, parents::CheckParents, same::CheckSame}, }; use libipld_core::{error::SerdeError, ipld::Ipld, serde as ipld_serde}; use serde::{Deserialize, Serialize}; @@ -46,35 +43,18 @@ impl Checkable for Receive { type CheckAs = Parentful; } -impl CheckSelf for Receive { +impl CheckSame for Receive { type Error = (); // FIXME better error - fn check_against_self(&self, proof: &Self) -> Result<(), Self::Error> { - if let Some(self_from) = &self.from { - if let Some(proof_from) = &proof.from { - if self_from != proof_from { - return Err(()); - } - } - } - - Ok(()) + fn check_same(&self, proof: &Self) -> Result<(), Self::Error> { + self.from.check_same(&proof.from).map_err(|_| ()) } } impl CheckParents for Receive { type Parents = msg::Any; - type ParentError = ::Error; - - // FIXME rename other to proof - fn check_against_parents(&self, other: &Self::Parents) -> Result<(), Self::ParentError> { - if let Some(self_from) = &self.from { - if let Some(proof_from) = &other.from { - if self_from != proof_from { - return Err(()); - } - } - } + type ParentError = ::Error; - Ok(()) + fn check_parents(&self, proof: &Self::Parents) -> Result<(), Self::ParentError> { + self.from.check_same(&proof.from).map_err(|_| ()) } } diff --git a/src/ability/msg/send.rs b/src/ability/msg/send.rs index 2a1d7857..d7eb7ee7 100644 --- a/src/ability/msg/send.rs +++ b/src/ability/msg/send.rs @@ -1,9 +1,6 @@ use crate::{ ability::traits::Command, - prove::{ - parentful::Parentful, - traits::{CheckParents, CheckSelf, Checkable}, - }, + proof::{checkable::Checkable, parentful::Parentful, parents::CheckParents, same::CheckSame}, }; use libipld_core::{error::SerdeError, ipld::Ipld, serde as ipld_serde}; use serde::{Deserialize, Serialize}; @@ -63,51 +60,21 @@ impl Checkable for SendBuilder { type CheckAs = Parentful; } -impl CheckSelf for SendBuilder { +impl CheckSame for SendBuilder { type Error = (); // FIXME better error - fn check_against_self(&self, proof: &Self) -> Result<(), Self::Error> { - if let Some(self_to) = &self.to { - if let Some(proof_to) = &proof.to { - if self_to != proof_to { - return Err(()); - } - } - } - - if let Some(self_from) = &self.from { - if let Some(proof_from) = &proof.from { - if self_from != proof_from { - return Err(()); - } - } - } - - if let Some(self_msg) = &self.message { - if let Some(proof_msg) = &proof.message { - if self_msg != proof_msg { - return Err(()); - } - } - } - - Ok(()) + fn check_same(&self, proof: &Self) -> Result<(), Self::Error> { + self.to.check_same(&proof.to).map_err(|_| ())?; // FIXME + self.from.check_same(&proof.from).map_err(|_| ())?; + self.message.check_same(&proof.message).map_err(|_| ()) } } impl CheckParents for SendBuilder { type Parents = msg::Any; - type ParentError = ::Error; + type ParentError = ::Error; // FIXME rename other to proof - fn check_against_parents(&self, other: &Self::Parents) -> Result<(), Self::ParentError> { - if let Some(self_from) = &self.from { - if let Some(proof_from) = &other.from { - if self_from != proof_from { - return Err(()); - } - } - } - - Ok(()) + fn check_parents(&self, other: &Self::Parents) -> Result<(), Self::ParentError> { + self.from.check_same(&other.from).map_err(|_| ()) } } diff --git a/src/delegation/condition.rs b/src/delegation/condition.rs index b03b07b6..9883283f 100644 --- a/src/delegation/condition.rs +++ b/src/delegation/condition.rs @@ -1,7 +1,2 @@ -use libipld_core::ipld::Ipld; - pub mod common; - -pub trait Condition { - fn validate(&self, ipld: &Ipld) -> bool; -} +pub mod traits; diff --git a/src/delegation/condition/common.rs b/src/delegation/condition/common.rs index 8588b22b..7af720d5 100644 --- a/src/delegation/condition/common.rs +++ b/src/delegation/condition/common.rs @@ -1,4 +1,4 @@ -use super::Condition; +use super::traits::Condition; use libipld_core::{ipld::Ipld, serde as ipld_serde}; use regex::Regex; use serde; @@ -15,8 +15,6 @@ pub enum Common { Matches(Matches), } -// FIXME dynamic js version? - #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(deny_unknown_fields)] pub struct ContainsAll { @@ -24,11 +22,11 @@ pub struct ContainsAll { values: Vec, } -// impl From for Ipld { -// fn from(contains_all: ContainsAll) -> Self { -// contains_all.into() -// } -// } +impl From for Ipld { + fn from(contains_all: ContainsAll) -> Self { + contains_all.into() + } +} impl TryFrom for ContainsAll { type Error = (); // FIXME diff --git a/src/delegation/condition/traits.rs b/src/delegation/condition/traits.rs new file mode 100644 index 00000000..29d1be0b --- /dev/null +++ b/src/delegation/condition/traits.rs @@ -0,0 +1,5 @@ +use libipld_core::ipld::Ipld; + +pub trait Condition { + fn validate(&self, ipld: &Ipld) -> bool; +} diff --git a/src/delegation/payload.rs b/src/delegation/payload.rs index a78050ed..7eb6d2e9 100644 --- a/src/delegation/payload.rs +++ b/src/delegation/payload.rs @@ -1,10 +1,15 @@ -use super::condition::Condition; +use super::condition::traits::Condition; use crate::{ - ability::traits::{Command, Delegatable, DynJs}, + ability::traits::{Command, Delegatable, DynJs, Resolvable}, capsule::Capsule, did::Did, + invocation::payload as invocation, nonce::Nonce, - prove::traits::{Checkable, Prove}, + proof::{ + checkable::Checkable, + prove::{Outcome, Prove}, + same::CheckSame, + }, time::Timestamp, }; use libipld_core::{ipld::Ipld, serde as ipld_serde}; @@ -82,8 +87,6 @@ impl From> for Ipld { } } -use crate::{ability::traits::Resolvable, invocation::payload as invocation}; - impl<'a, T: Delegatable + Resolvable + Checkable + Clone, C: Condition> Payload { pub fn check( invoked: invocation::Payload, // FIXME promisory version @@ -93,7 +96,7 @@ impl<'a, T: Delegatable + Resolvable + Checkable + Clone, C: Condition> Payload< where // FIXME so so so broken invocation::Payload: Clone, - T::CheckAs: From> + From + Prove, + T::CheckAs: From> + From + Prove + CheckSame, U::Builder: Clone, { let check_chain: T::CheckAs = invoked.clone().into(); @@ -105,18 +108,28 @@ impl<'a, T: Delegatable + Resolvable + Checkable + Clone, C: Condition> Payload< let ipld: Ipld = invoked.into(); - let result = proofs.iter().fold(Ok(&start), |prev, proof| { + let result = proofs.iter().fold(Ok(start), |prev, proof| { if let Ok(to_check) = prev { match step1(&to_check, proof, &ipld, now) { - Err(_) => Err(()), - Ok(next) => Ok(next), + Outcome::ArgumentEscelation(_) => Err(()), + Outcome::InvalidProofChain(_) => Err(()), + Outcome::ProvenByAny => Ok(to_check), // NOTE this case! + Outcome::Proven => Ok(Acc { + issuer: proof.issuer.clone(), + subject: proof.subject.clone(), + check_chain: proof.ability_builder.clone().into(), // FIXME double check + }), } } else { prev } }); - todo!() + // FIXME + match result { + Ok(_) => Ok(()), + Err(_) => Err(()), + } } } @@ -127,55 +140,66 @@ struct Acc { check_chain: T::CheckAs, } +// FIXME replace with check_parents? // FIXME this needs to move to Delegatable fn step1<'a, T: Checkable, U: Delegatable, C: Condition>( prev: &'a Acc, proof: &'a Payload, invoked_ipld: &'a Ipld, now: SystemTime, -) -> Result<&'a Acc, ()> +) -> Outcome<(), ()> +// FIXME Outcome types where T::CheckAs: From + Prove, U::Builder: Clone, { - if prev.issuer != proof.audience { - todo!() + if let Err(_) = prev.issuer.check_same(&proof.issuer) { + return Outcome::InvalidProofChain(()); } - if prev.subject != proof.subject { - todo!() + if let Err(_) = prev.subject.check_same(&proof.subject) { + return Outcome::InvalidProofChain(()); } if let Some(nbf) = proof.not_before.clone() { if SystemTime::from(nbf) > now { - todo!() + return Outcome::InvalidProofChain(()); } } if SystemTime::from(proof.expiration.clone()) > now { - todo!() + return Outcome::InvalidProofChain(()); } // FIXME check the spec - // if self.conditions != proof.conditions { + // if self.conditions.len() < proof.conditions { + // ...etc etc // return Err(()); // } - proof - .conditions - .iter() - .try_fold((), |_acc, c| { - if c.validate(&invoked_ipld) { - Ok(()) - } else { - Err(()) - } - }) - .expect("FIXME"); + let cond_result = proof.conditions.iter().try_fold((), |_acc, c| { + if c.validate(&invoked_ipld) { + Ok(()) + } else { + Err(()) + } + }); + + if let Err(_) = cond_result { + return Outcome::InvalidProofChain(()); + } - Prove::check(&prev.check_chain, &proof.ability_builder.clone().into()); + // FIXME pricey clone + let foo = prev + .check_chain + .check(&proof.ability_builder.clone().into()); - todo!() + match foo { + Outcome::Proven => Outcome::Proven, + Outcome::ProvenByAny => Outcome::ProvenByAny, + Outcome::ArgumentEscelation(_) => Outcome::ArgumentEscelation(()), + Outcome::InvalidProofChain(_) => Outcome::InvalidProofChain(()), + } } #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] diff --git a/src/lib.rs b/src/lib.rs index fae01988..d40671f3 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -119,7 +119,7 @@ pub mod invocation; pub mod ipld; pub mod nonce; pub mod promise; -pub mod prove; +pub mod proof; pub mod receipt; pub mod signature; diff --git a/src/proof.rs b/src/proof.rs new file mode 100644 index 00000000..75559c3b --- /dev/null +++ b/src/proof.rs @@ -0,0 +1,9 @@ +pub mod checkable; +pub mod parentful; +pub mod parentless; +pub mod parents; +pub mod prove; +pub mod same; + +// NOTE must remain *un*exported! +pub(super) mod internal; diff --git a/src/proof/checkable.rs b/src/proof/checkable.rs new file mode 100644 index 00000000..07db3dce --- /dev/null +++ b/src/proof/checkable.rs @@ -0,0 +1,5 @@ +use super::{internal::Checker, same::CheckSame}; + +pub trait Checkable: CheckSame { + type CheckAs: Checker; +} diff --git a/src/prove/internal.rs b/src/proof/internal.rs similarity index 56% rename from src/prove/internal.rs rename to src/proof/internal.rs index a932a8a9..b7adf7fa 100644 --- a/src/prove/internal.rs +++ b/src/proof/internal.rs @@ -1,2 +1 @@ -// FIXME rename pub trait Checker {} diff --git a/src/proof/parentful.rs b/src/proof/parentful.rs new file mode 100644 index 00000000..aa6ca820 --- /dev/null +++ b/src/proof/parentful.rs @@ -0,0 +1,59 @@ +use super::{ + internal::Checker, + parents::CheckParents, + prove::{Outcome, Prove}, + same::CheckSame, +}; +use libipld_core::{error::SerdeError, ipld::Ipld, serde as ipld_serde}; +use serde::{de::DeserializeOwned, Deserialize, Serialize}; + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub enum Parentful { + Any, + Parents(T::Parents), + This(T), +} + +impl From> for Ipld +where + Ipld: From, +{ + fn from(parentful: Parentful) -> Self { + parentful.into() + } +} + +impl + DeserializeOwned + CheckParents> TryFrom for Parentful +where + ::Parents: DeserializeOwned, +{ + type Error = SerdeError; + + fn try_from(ipld: Ipld) -> Result { + ipld_serde::from_ipld(ipld) + } +} + +impl Checker for Parentful {} + +impl Prove> for T +where + T::Parents: CheckSame, +{ + type ArgumentError = T::Error; + type ProofChainError = T::ParentError; + + fn check<'a>(&'a self, proof: &'a Parentful) -> Outcome { + match proof { + Parentful::Any => Outcome::ProvenByAny, + Parentful::Parents(parents) => match self.check_parents(parents) { + Ok(()) => Outcome::Proven, + Err(e) => Outcome::InvalidProofChain(e), + }, + Parentful::This(that) => match self.check_same(&that) { + Ok(()) => Outcome::Proven, + Err(e) => Outcome::ArgumentEscelation(e), + }, + } + } +} diff --git a/src/proof/parentless.rs b/src/proof/parentless.rs new file mode 100644 index 00000000..ec1dd15a --- /dev/null +++ b/src/proof/parentless.rs @@ -0,0 +1,48 @@ +use super::{ + internal::Checker, + prove::{Outcome, Prove}, + same::CheckSame, +}; +use libipld_core::{error::SerdeError, ipld::Ipld, serde as ipld_serde}; +use serde::{de::DeserializeOwned, Deserialize, Serialize}; +use std::convert::Infallible; + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub enum Parentless { + Any, + This(T), +} + +impl From> for Ipld +where + Ipld: From, +{ + fn from(parentless: Parentless) -> Self { + parentless.into() + } +} + +impl + DeserializeOwned> TryFrom for Parentless { + type Error = SerdeError; + + fn try_from(ipld: Ipld) -> Result { + ipld_serde::from_ipld(ipld) + } +} + +impl Checker for Parentless {} + +impl Prove> for T { + type ArgumentError = T::Error; + type ProofChainError = Infallible; + + fn check<'a>(&'a self, proof: &'a Parentless) -> Outcome { + match proof { + Parentless::Any => Outcome::Proven, + Parentless::This(this) => match self.check_same(&this) { + Ok(()) => Outcome::Proven, + Err(e) => Outcome::ArgumentEscelation(e), + }, + } + } +} diff --git a/src/proof/parents.rs b/src/proof/parents.rs new file mode 100644 index 00000000..a3a92b54 --- /dev/null +++ b/src/proof/parents.rs @@ -0,0 +1,8 @@ +use super::same::CheckSame; + +pub trait CheckParents: CheckSame { + type Parents; + type ParentError; + + fn check_parents(&self, proof: &Self::Parents) -> Result<(), Self::ParentError>; +} diff --git a/src/proof/prove.rs b/src/proof/prove.rs new file mode 100644 index 00000000..9fc91ca2 --- /dev/null +++ b/src/proof/prove.rs @@ -0,0 +1,16 @@ +use super::internal::Checker; + +// FIXME is it worth locking consumers out with that Checker bound? +pub trait Prove { + type ArgumentError; + type ProofChainError; + + fn check<'a>(&'a self, proof: &'a T) -> Outcome; +} + +pub enum Outcome { + Proven, + ProvenByAny, + ArgumentEscelation(ArgErr), + InvalidProofChain(ChainErr), +} diff --git a/src/proof/same.rs b/src/proof/same.rs new file mode 100644 index 00000000..f2079ea6 --- /dev/null +++ b/src/proof/same.rs @@ -0,0 +1,50 @@ +use crate::did::Did; +use serde::Serialize; + +pub trait CheckSame { + type Error; + + fn check_same(&self, proof: &Self) -> Result<(), Self::Error>; +} + +// Genereic +#[derive(Debug, Clone, PartialEq, Eq, Serialize)] +pub struct Unequal; + +#[derive(Debug, Clone, PartialEq, Eq, Serialize)] +pub enum OptionalFieldErr { + MissingField, + UnequalValue, +} + +impl CheckSame for Did { + type Error = Unequal; + + fn check_same(&self, proof: &Self) -> Result<(), Self::Error> { + if self.eq(proof) { + Ok(()) + } else { + Err(Unequal) + } + } +} + +impl CheckSame for Option { + type Error = OptionalFieldErr; + + fn check_same(&self, proof: &Self) -> Result<(), Self::Error> { + match proof { + None => Ok(()), + Some(proof_) => match self { + None => Err(OptionalFieldErr::MissingField), + Some(self_) => { + if self_.eq(proof_) { + Ok(()) + } else { + Err(OptionalFieldErr::UnequalValue) + } + } + }, + } + } +} diff --git a/src/prove.rs b/src/prove.rs deleted file mode 100644 index e4b8122b..00000000 --- a/src/prove.rs +++ /dev/null @@ -1,15 +0,0 @@ -// NOTE must remain *un*exported! -pub(super) mod internal; -pub mod parentful; -pub mod parentless; -pub mod traits; - -// #[cfg_attr(doc, aquamarine::aquamarine)] -// /// FIXME -// /// -// /// ```mermaid -// /// flowchart LR -// /// Invocation --> more --> Self --> Proof --> more2 -// /// more[...] -// /// more2[...] -// /// ``` diff --git a/src/prove/parentful.rs b/src/prove/parentful.rs deleted file mode 100644 index 62ce0c8f..00000000 --- a/src/prove/parentful.rs +++ /dev/null @@ -1,116 +0,0 @@ -use super::{ - internal::Checker, - traits::{CheckParents, CheckSelf, Prove}, -}; -use libipld_core::{error::SerdeError, ipld::Ipld, serde as ipld_serde}; -use serde::{de::DeserializeOwned, Deserialize, Serialize}; - -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] -pub enum Parentful { - Any, - Parents(T::Parents), - This(T), -} - -impl From> for Ipld -where - Ipld: From, -{ - fn from(parentful: Parentful) -> Self { - parentful.into() - } -} - -impl + DeserializeOwned + CheckParents> TryFrom for Parentful -where - ::Parents: DeserializeOwned, -{ - type Error = SerdeError; - - fn try_from(ipld: Ipld) -> Result { - ipld_serde::from_ipld(ipld) - } -} - -// TODO better names & derivations -pub enum ParentfulError -where - T::Parents: CheckSelf, -{ - ParentError(T::ParentError), - // FIXME needs a WAAAAAY better name - ParentSelfError(<::Parents as CheckSelf>::Error), - Error(::Error), - - // Compared self to parents - EscelationError, -} - -impl Checker for Parentful {} - -impl CheckSelf for Parentful -where - T::Parents: CheckSelf, -{ - type Error = ParentfulError; - - fn check_against_self(&self, proof: &Self) -> Result<(), Self::Error> { - match self { - Parentful::Any => Ok(()), - Parentful::Parents(parents) => match proof { - Parentful::Any => Ok(()), - Parentful::Parents(other_parents) => parents - .check_against_self(other_parents) - .map_err(ParentfulError::ParentSelfError), - Parentful::This(_other_me) => Err(ParentfulError::EscelationError), - }, - Parentful::This(this) => match proof { - Parentful::Any => Ok(()), - Parentful::Parents(other_parents) => this - .check_against_parents(other_parents) - .map_err(ParentfulError::ParentError), - Parentful::This(that) => { - this.check_against_self(that).map_err(ParentfulError::Error) - } - }, - } - } -} - -impl CheckParents for Parentful -where - Parentful: CheckSelf, - T::Parents: CheckSelf, -{ - type Parents = T::Parents; - type ParentError = ParentfulError; - - fn check_against_parents(&self, other: &T::Parents) -> Result<(), Self::ParentError> { - // FIXME note to self: see if you can extract the parentful stuff out into the to level Prove - match self { - Parentful::Any => Ok(()), - Parentful::Parents(parents) => parents.check_against_self(other).map_err(|_| todo!()), // FIXME ParentfulError::ParentError), - Parentful::This(this) => this - .check_against_parents(other) - .map_err(ParentfulError::ParentError), - } - } -} - -impl Prove> for T -where - T::Parents: CheckSelf, -{ - type ProveError = ParentfulError; - fn check<'a>(&'a self, proof: &'a Parentful) -> Result<(), Self::ProveError> { - match proof { - Parentful::Any => Ok(()), - Parentful::Parents(parents) => self - .check_against_parents(parents) - .map_err(ParentfulError::ParentError), - Parentful::This(that) => self - .check_against_self(&that) - .map_err(ParentfulError::Error), - } - } -} diff --git a/src/prove/parentless.rs b/src/prove/parentless.rs deleted file mode 100644 index aa669af2..00000000 --- a/src/prove/parentless.rs +++ /dev/null @@ -1,55 +0,0 @@ -use super::{ - internal::Checker, - traits::{CheckSelf, Prove}, -}; -use libipld_core::{error::SerdeError, ipld::Ipld, serde as ipld_serde}; -use serde::{de::DeserializeOwned, Deserialize, Serialize}; - -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] -pub enum Parentless { - Any, - This(T), -} - -impl From> for Ipld -where - Ipld: From, -{ - fn from(parentless: Parentless) -> Self { - parentless.into() - } -} - -impl + DeserializeOwned> TryFrom for Parentless { - type Error = SerdeError; - - fn try_from(ipld: Ipld) -> Result { - ipld_serde::from_ipld(ipld) - } -} - -impl Checker for Parentless {} - -impl CheckSelf for Parentless { - type Error = T::Error; - - fn check_against_self(&self, proof: &Self) -> Result<(), Self::Error> { - match self { - Parentless::Any => Ok(()), // FIXME MUST forward that this was an ANY this into the result! - Parentless::This(this) => match proof { - Parentless::Any => Ok(()), - Parentless::This(other) => this.check_against_self(other), - }, - } - } -} - -impl Prove> for T { - type ProveError = T::Error; - fn check<'a>(&'a self, proof: &'a Parentless) -> Result<(), T::Error> { - match proof { - Parentless::Any => Ok(()), - Parentless::This(this) => self.check_against_self(&this), - } - } -} diff --git a/src/prove/traits.rs b/src/prove/traits.rs deleted file mode 100644 index d50f52e6..00000000 --- a/src/prove/traits.rs +++ /dev/null @@ -1,27 +0,0 @@ -use super::internal::Checker; - -pub trait CheckSelf { - type Error; - - fn check_against_self(&self, proof: &Self) -> Result<(), Self::Error>; -} - -pub trait CheckParents: CheckSelf { - type Parents; - type ParentError; - - fn check_against_parents(&self, proof: &Self::Parents) -> Result<(), Self::ParentError>; -} - -pub trait Checkable: CheckSelf { - type CheckAs: Checker; -} - -// FIXME is it worth locking consumers out with that Checker bound? -pub trait Prove { - type ProveError; - fn check<'a>(&'a self, proof: &'a T) -> Result<(), Self::ProveError>; -} - -// Nightly only... sadness -// trait Foo = Checkable + Prove; From 2355b7994e7691bc7ccb4891b77240e0e3c2f8d5 Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Mon, 29 Jan 2024 00:37:04 -0800 Subject: [PATCH 030/188] Fixed conditions --- src/delegation/condition.rs | 61 ++++- src/delegation/condition/common.rs | 264 ---------------------- src/delegation/condition/contains_all.rs | 37 +++ src/delegation/condition/contains_any.rs | 37 +++ src/delegation/condition/contains_key.rs | 46 ++++ src/delegation/condition/excludes_all.rs | 37 +++ src/delegation/condition/excludes_key.rs | 33 +++ src/delegation/condition/matches_regex.rs | 70 ++++++ src/delegation/condition/max_length.rs | 35 +++ src/delegation/condition/max_number.rs | 41 ++++ src/delegation/condition/min_length.rs | 35 +++ src/delegation/condition/min_number.rs | 41 ++++ src/lib.rs | 1 + src/number.rs | 23 ++ 14 files changed, 496 insertions(+), 265 deletions(-) delete mode 100644 src/delegation/condition/common.rs create mode 100644 src/delegation/condition/contains_all.rs create mode 100644 src/delegation/condition/contains_any.rs create mode 100644 src/delegation/condition/contains_key.rs create mode 100644 src/delegation/condition/excludes_all.rs create mode 100644 src/delegation/condition/excludes_key.rs create mode 100644 src/delegation/condition/matches_regex.rs create mode 100644 src/delegation/condition/max_length.rs create mode 100644 src/delegation/condition/max_number.rs create mode 100644 src/delegation/condition/min_length.rs create mode 100644 src/delegation/condition/min_number.rs create mode 100644 src/number.rs diff --git a/src/delegation/condition.rs b/src/delegation/condition.rs index 9883283f..3b3b0c7f 100644 --- a/src/delegation/condition.rs +++ b/src/delegation/condition.rs @@ -1,2 +1,61 @@ -pub mod common; +pub mod contains_all; +pub mod contains_any; +pub mod contains_key; +pub mod excludes_all; +pub mod excludes_key; +pub mod matches_regex; +pub mod max_length; +pub mod max_number; +pub mod min_length; +pub mod min_number; pub mod traits; + +use libipld_core::{error::SerdeError, ipld::Ipld, serde as ipld_serde}; +use serde_derive::{Deserialize, Serialize}; +use traits::Condition; + +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +#[serde(untagged)] +pub enum Common { + ContainsAll(contains_all::ContainsAll), + ContainsAny(contains_any::ContainsAny), + ContainsKey(contains_key::ContainsKey), + ExcludesKey(excludes_key::ExcludesKey), + ExcludesAll(excludes_all::ExcludesAll), + MinLength(min_length::MinLength), + MaxLength(max_length::MaxLength), + MinNumber(min_number::MinNumber), + MaxNumber(max_number::MaxNumber), + MatchesRegex(matches_regex::MatchesRegex), +} + +impl From for Ipld { + fn from(common: Common) -> Self { + common.into() + } +} + +impl TryFrom for Common { + type Error = SerdeError; + + fn try_from(ipld: Ipld) -> Result { + ipld_serde::from_ipld(ipld) + } +} + +impl Condition for Common { + fn validate(&self, ipld: &Ipld) -> bool { + match self { + Common::ContainsAll(c) => c.validate(ipld), + Common::ContainsAny(c) => c.validate(ipld), + Common::ContainsKey(c) => c.validate(ipld), + Common::ExcludesKey(c) => c.validate(ipld), + Common::ExcludesAll(c) => c.validate(ipld), + Common::MinLength(c) => c.validate(ipld), + Common::MaxLength(c) => c.validate(ipld), + Common::MinNumber(c) => c.validate(ipld), + Common::MaxNumber(c) => c.validate(ipld), + Common::MatchesRegex(c) => c.validate(ipld), + } + } +} diff --git a/src/delegation/condition/common.rs b/src/delegation/condition/common.rs deleted file mode 100644 index 7af720d5..00000000 --- a/src/delegation/condition/common.rs +++ /dev/null @@ -1,264 +0,0 @@ -use super::traits::Condition; -use libipld_core::{ipld::Ipld, serde as ipld_serde}; -use regex::Regex; -use serde; -use serde_derive::{Deserialize, Serialize}; - -#[derive(Debug, Clone, PartialEq)] -pub enum Common { - ContainsAll(ContainsAll), - ContainsAny(ContainsAny), - ExcludesAll(ExcludesAll), - MaxLength(MaxLength), - MinNumber(MinNumber), - MaxNumber(MaxNumber), - Matches(Matches), -} - -#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] -#[serde(deny_unknown_fields)] -pub struct ContainsAll { - field: String, - values: Vec, -} - -impl From for Ipld { - fn from(contains_all: ContainsAll) -> Self { - contains_all.into() - } -} - -impl TryFrom for ContainsAll { - type Error = (); // FIXME - - fn try_from(ipld: Ipld) -> Result { - ipld_serde::from_ipld(ipld).map_err(|_| ()) - } -} - -impl Condition for ContainsAll { - fn validate(&self, ipld: &Ipld) -> bool { - match ipld { - Ipld::List(array) => self.values.iter().all(|ipld| array.contains(ipld)), - Ipld::Map(btree) => { - let vals: Vec<&Ipld> = btree.values().collect(); - self.values.iter().all(|ipld| vals.contains(&ipld)) - } - _ => false, - } - } -} - -#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] -#[serde(deny_unknown_fields)] -pub struct ContainsAny { - field: String, - value: Vec, -} - -impl Condition for ContainsAny { - fn validate(&self, ipld: &Ipld) -> bool { - match ipld { - Ipld::List(array) => array.iter().any(|ipld| self.value.contains(ipld)), - Ipld::Map(btree) => { - let vals: Vec<&Ipld> = btree.values().collect(); - self.value.iter().any(|ipld| vals.contains(&ipld)) - } - _ => false, - } - } -} - -#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] -#[serde(deny_unknown_fields)] -pub struct ExcludesAll { - field: String, - value: Vec, -} - -impl Condition for ExcludesAll { - fn validate(&self, ipld: &Ipld) -> bool { - match ipld { - Ipld::List(array) => self.value.iter().all(|ipld| !array.contains(ipld)), - Ipld::Map(btree) => { - let vals: Vec<&Ipld> = btree.values().collect(); - self.value.iter().all(|ipld| !vals.contains(&ipld)) - } - _ => false, - } - } -} - -impl TryFrom for ExcludesAll { - type Error = (); // FIXME - - fn try_from(ipld: Ipld) -> Result { - ipld_serde::from_ipld(ipld).map_err(|_| ()) - } -} - -// FIXME serialization? -#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] -#[serde(deny_unknown_fields)] -pub enum Numeric { - Float(f64), - Integer(i128), -} - -impl TryFrom for Numeric { - type Error = (); // FIXME - - fn try_from(ipld: Ipld) -> Result { - ipld_serde::from_ipld(ipld).map_err(|_| ()) - } -} - -#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] -#[serde(deny_unknown_fields)] -pub struct MinNumber { - field: String, - value: Numeric, -} - -impl TryFrom for MinNumber { - type Error = (); // FIXME - - fn try_from(ipld: Ipld) -> Result { - ipld_serde::from_ipld(ipld).map_err(|_| ()) - } -} - -impl Condition for MinNumber { - fn validate(&self, ipld: &Ipld) -> bool { - match ipld { - Ipld::Integer(integer) => match self.value { - Numeric::Float(float) => *integer as f64 >= float, - Numeric::Integer(integer) => integer >= integer, - }, - Ipld::Float(float) => match self.value { - Numeric::Float(float) => float >= float, - Numeric::Integer(integer) => *float >= integer as f64, // FIXME this needs tests - }, - _ => false, - } - } -} - -#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] -#[serde(deny_unknown_fields)] -pub struct MaxNumber { - field: String, - value: Numeric, -} - -impl Condition for MaxNumber { - fn validate(&self, ipld: &Ipld) -> bool { - match ipld { - Ipld::Integer(integer) => match self.value { - Numeric::Float(float) => *integer as f64 <= float, - Numeric::Integer(integer) => integer <= integer, - }, - Ipld::Float(float) => match self.value { - Numeric::Float(float) => float <= float, - Numeric::Integer(integer) => *float <= integer as f64, // FIXME this needs tests - }, - _ => false, - } - } -} - -impl TryFrom for MaxNumber { - type Error = (); // FIXME - - fn try_from(ipld: Ipld) -> Result { - ipld_serde::from_ipld(ipld).map_err(|_| ()) - } -} - -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] -#[serde(deny_unknown_fields)] -pub struct MinLength { - field: String, - value: u64, -} - -impl Condition for MinLength { - fn validate(&self, ipld: &Ipld) -> bool { - match ipld { - Ipld::String(string) => string.len() >= self.value as usize, - Ipld::List(list) => list.len() >= self.value as usize, - Ipld::Map(btree) => btree.len() >= self.value as usize, - _ => false, - } - } -} - -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] -#[serde(deny_unknown_fields)] -pub struct MaxLength { - field: String, - value: u64, -} - -impl Condition for MaxLength { - fn validate(&self, ipld: &Ipld) -> bool { - match ipld { - Ipld::String(string) => string.len() <= self.value as usize, - Ipld::List(list) => list.len() <= self.value as usize, - Ipld::Map(btree) => btree.len() <= self.value as usize, - _ => false, - } - } -} - -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] -#[serde(deny_unknown_fields)] -pub struct Matches { - field: String, - matcher: Matcher, -} - -#[derive(Debug, Clone)] -pub struct Matcher(Regex); - -impl PartialEq for Matcher { - fn eq(&self, other: &Self) -> bool { - self.0.as_str() == other.0.as_str() - } -} - -impl Eq for Matcher {} - -impl serde::Serialize for Matcher { - fn serialize(&self, serializer: S) -> Result - where - S: serde::Serializer, - { - self.0.as_str().serialize(serializer) - } -} - -impl<'de> serde::Deserialize<'de> for Matcher { - fn deserialize(deserializer: D) -> Result - where - D: serde::Deserializer<'de>, - { - let s: &str = serde::Deserialize::deserialize(deserializer)?; - match Regex::new(s) { - Ok(regex) => Ok(Matcher(regex)), - Err(_) => { - // FIXME - todo!() - } - } - } -} - -impl Condition for Matcher { - fn validate(&self, ipld: &Ipld) -> bool { - match ipld { - Ipld::String(string) => self.0.is_match(string), - _ => false, - } - } -} diff --git a/src/delegation/condition/contains_all.rs b/src/delegation/condition/contains_all.rs new file mode 100644 index 00000000..57d71297 --- /dev/null +++ b/src/delegation/condition/contains_all.rs @@ -0,0 +1,37 @@ +use super::traits::Condition; +use libipld_core::{error::SerdeError, ipld::Ipld, serde as ipld_serde}; +use serde_derive::{Deserialize, Serialize}; + +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +#[serde(deny_unknown_fields)] +pub struct ContainsAll { + field: String, + contains_all: Vec, +} + +impl From for Ipld { + fn from(contains_all: ContainsAll) -> Self { + contains_all.into() + } +} + +impl TryFrom for ContainsAll { + type Error = SerdeError; + + fn try_from(ipld: Ipld) -> Result { + ipld_serde::from_ipld(ipld) + } +} + +impl Condition for ContainsAll { + fn validate(&self, ipld: &Ipld) -> bool { + match ipld { + Ipld::List(array) => self.contains_all.iter().all(|ipld| array.contains(ipld)), + Ipld::Map(btree) => { + let vals: Vec<&Ipld> = btree.values().collect(); + self.contains_all.iter().all(|ipld| vals.contains(&ipld)) + } + _ => false, + } + } +} diff --git a/src/delegation/condition/contains_any.rs b/src/delegation/condition/contains_any.rs new file mode 100644 index 00000000..dde1212b --- /dev/null +++ b/src/delegation/condition/contains_any.rs @@ -0,0 +1,37 @@ +use super::traits::Condition; +use libipld_core::{error::SerdeError, ipld::Ipld, serde as ipld_serde}; +use serde_derive::{Deserialize, Serialize}; + +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +#[serde(deny_unknown_fields)] +pub struct ContainsAny { + field: String, + contains_any: Vec, +} + +impl From for Ipld { + fn from(contains_any: ContainsAny) -> Self { + contains_any.into() + } +} + +impl TryFrom for ContainsAny { + type Error = SerdeError; + + fn try_from(ipld: Ipld) -> Result { + ipld_serde::from_ipld(ipld) + } +} + +impl Condition for ContainsAny { + fn validate(&self, ipld: &Ipld) -> bool { + match ipld { + Ipld::List(array) => array.iter().any(|ipld| self.contains_any.contains(ipld)), + Ipld::Map(btree) => { + let vals: Vec<&Ipld> = btree.values().collect(); + self.contains_any.iter().any(|ipld| vals.contains(&ipld)) + } + _ => false, + } + } +} diff --git a/src/delegation/condition/contains_key.rs b/src/delegation/condition/contains_key.rs new file mode 100644 index 00000000..098237db --- /dev/null +++ b/src/delegation/condition/contains_key.rs @@ -0,0 +1,46 @@ +use super::traits::Condition; +use libipld_core::{error::SerdeError, ipld::Ipld, serde as ipld_serde}; +use serde_derive::{Deserialize, Serialize}; + +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +#[serde(deny_unknown_fields)] +pub struct ContainsKey { + field: String, + contains_key: String, + + #[serde(default, skip_serializing_if = "Option::is_none")] + with_value: Option, +} + +impl From for Ipld { + fn from(contains_key: ContainsKey) -> Self { + contains_key.into() + } +} + +impl TryFrom for ContainsKey { + type Error = SerdeError; + + fn try_from(ipld: Ipld) -> Result { + ipld_serde::from_ipld(ipld) + } +} + +impl Condition for ContainsKey { + fn validate(&self, ipld: &Ipld) -> bool { + match ipld { + Ipld::Map(map) => { + if let Some(value) = map.get(&self.field) { + if let Some(with_value) = &self.with_value { + value == with_value + } else { + true + } + } else { + false + } + } + _ => false, + } + } +} diff --git a/src/delegation/condition/excludes_all.rs b/src/delegation/condition/excludes_all.rs new file mode 100644 index 00000000..2e94ca3e --- /dev/null +++ b/src/delegation/condition/excludes_all.rs @@ -0,0 +1,37 @@ +use super::traits::Condition; +use libipld_core::{error::SerdeError, ipld::Ipld, serde as ipld_serde}; +use serde_derive::{Deserialize, Serialize}; + +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +#[serde(deny_unknown_fields)] +pub struct ExcludesAll { + field: String, + excludes_all: Vec, +} + +impl From for Ipld { + fn from(excludes_all: ExcludesAll) -> Self { + excludes_all.into() + } +} + +impl TryFrom for ExcludesAll { + type Error = SerdeError; + + fn try_from(ipld: Ipld) -> Result { + ipld_serde::from_ipld(ipld) + } +} + +impl Condition for ExcludesAll { + fn validate(&self, ipld: &Ipld) -> bool { + match ipld { + Ipld::List(array) => self.excludes_all.iter().all(|ipld| !array.contains(ipld)), + Ipld::Map(btree) => { + let vals: Vec<&Ipld> = btree.values().collect(); + self.excludes_all.iter().all(|ipld| !vals.contains(&ipld)) + } + _ => false, + } + } +} diff --git a/src/delegation/condition/excludes_key.rs b/src/delegation/condition/excludes_key.rs new file mode 100644 index 00000000..b12ef89e --- /dev/null +++ b/src/delegation/condition/excludes_key.rs @@ -0,0 +1,33 @@ +use super::traits::Condition; +use libipld_core::{error::SerdeError, ipld::Ipld, serde as ipld_serde}; +use serde_derive::{Deserialize, Serialize}; + +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +#[serde(deny_unknown_fields)] +pub struct ExcludesKey { + field: String, + excludes_key: String, +} + +impl From for Ipld { + fn from(excludes_key: ExcludesKey) -> Self { + excludes_key.into() + } +} + +impl TryFrom for ExcludesKey { + type Error = SerdeError; + + fn try_from(ipld: Ipld) -> Result { + ipld_serde::from_ipld(ipld) + } +} + +impl Condition for ExcludesKey { + fn validate(&self, ipld: &Ipld) -> bool { + match ipld { + Ipld::Map(map) => map.get(&self.field).is_none(), + _ => false, + } + } +} diff --git a/src/delegation/condition/matches_regex.rs b/src/delegation/condition/matches_regex.rs new file mode 100644 index 00000000..02c0f2e4 --- /dev/null +++ b/src/delegation/condition/matches_regex.rs @@ -0,0 +1,70 @@ +use super::traits::Condition; +use libipld_core::{error::SerdeError, ipld::Ipld, serde as ipld_serde}; +use regex::Regex; +use serde_derive::{Deserialize, Serialize}; + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[serde(deny_unknown_fields)] +pub struct MatchesRegex { + field: String, + matches_regex: Matcher, +} + +impl From for Ipld { + fn from(matches_regex: MatchesRegex) -> Self { + matches_regex.into() + } +} + +impl TryFrom for MatchesRegex { + type Error = SerdeError; + + fn try_from(ipld: Ipld) -> Result { + ipld_serde::from_ipld(ipld) + } +} + +impl Condition for MatchesRegex { + fn validate(&self, ipld: &Ipld) -> bool { + match ipld { + Ipld::String(string) => self.matches_regex.0.is_match(string), + _ => false, + } + } +} + +#[derive(Debug, Clone)] +pub struct Matcher(Regex); + +impl PartialEq for Matcher { + fn eq(&self, other: &Self) -> bool { + self.0.as_str() == other.0.as_str() + } +} + +impl Eq for Matcher {} + +impl serde::Serialize for Matcher { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + self.0.as_str().serialize(serializer) + } +} + +impl<'de> serde::Deserialize<'de> for Matcher { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + let s: &str = serde::Deserialize::deserialize(deserializer)?; + match Regex::new(s) { + Ok(regex) => Ok(Matcher(regex)), + Err(_) => { + // FIXME + todo!() + } + } + } +} diff --git a/src/delegation/condition/max_length.rs b/src/delegation/condition/max_length.rs new file mode 100644 index 00000000..13fe157c --- /dev/null +++ b/src/delegation/condition/max_length.rs @@ -0,0 +1,35 @@ +use super::traits::Condition; +use libipld_core::{error::SerdeError, ipld::Ipld, serde as ipld_serde}; +use serde_derive::{Deserialize, Serialize}; + +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +#[serde(deny_unknown_fields)] +pub struct MaxLength { + field: String, + max_length: usize, +} + +impl From for Ipld { + fn from(max_length: MaxLength) -> Self { + max_length.into() + } +} + +impl TryFrom for MaxLength { + type Error = SerdeError; + + fn try_from(ipld: Ipld) -> Result { + ipld_serde::from_ipld(ipld) + } +} + +impl Condition for MaxLength { + fn validate(&self, ipld: &Ipld) -> bool { + match ipld { + Ipld::String(string) => string.len() <= self.max_length, + Ipld::List(list) => list.len() <= self.max_length, + Ipld::Map(map) => map.len() <= self.max_length, + _ => false, + } + } +} diff --git a/src/delegation/condition/max_number.rs b/src/delegation/condition/max_number.rs new file mode 100644 index 00000000..46327ee0 --- /dev/null +++ b/src/delegation/condition/max_number.rs @@ -0,0 +1,41 @@ +use super::traits::Condition; +use crate::number::Number; +use libipld_core::{error::SerdeError, ipld::Ipld, serde as ipld_serde}; +use serde_derive::{Deserialize, Serialize}; + +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +#[serde(deny_unknown_fields)] +pub struct MaxNumber { + field: String, + max_number: Number, +} + +impl From for Ipld { + fn from(max_number: MaxNumber) -> Self { + max_number.into() + } +} + +impl TryFrom for MaxNumber { + type Error = SerdeError; + + fn try_from(ipld: Ipld) -> Result { + ipld_serde::from_ipld(ipld) + } +} + +impl Condition for MaxNumber { + fn validate(&self, ipld: &Ipld) -> bool { + match ipld { + Ipld::Integer(integer) => match self.max_number { + Number::Float(float) => *integer as f64 <= float, + Number::Integer(integer) => integer <= integer, + }, + Ipld::Float(float) => match self.max_number { + Number::Float(float) => float <= float, + Number::Integer(integer) => *float <= integer as f64, // FIXME this needs tests + }, + _ => false, + } + } +} diff --git a/src/delegation/condition/min_length.rs b/src/delegation/condition/min_length.rs new file mode 100644 index 00000000..85dbe1c9 --- /dev/null +++ b/src/delegation/condition/min_length.rs @@ -0,0 +1,35 @@ +use super::traits::Condition; +use libipld_core::{error::SerdeError, ipld::Ipld, serde as ipld_serde}; +use serde_derive::{Deserialize, Serialize}; + +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +#[serde(deny_unknown_fields)] +pub struct MinLength { + field: String, + min_length: usize, +} + +impl From for Ipld { + fn from(min_length: MinLength) -> Self { + min_length.into() + } +} + +impl TryFrom for MinLength { + type Error = SerdeError; + + fn try_from(ipld: Ipld) -> Result { + ipld_serde::from_ipld(ipld) + } +} + +impl Condition for MinLength { + fn validate(&self, ipld: &Ipld) -> bool { + match ipld { + Ipld::String(string) => string.len() >= self.min_length, + Ipld::List(list) => list.len() >= self.min_length, + Ipld::Map(map) => map.len() >= self.min_length, + _ => false, + } + } +} diff --git a/src/delegation/condition/min_number.rs b/src/delegation/condition/min_number.rs new file mode 100644 index 00000000..a19ef7c3 --- /dev/null +++ b/src/delegation/condition/min_number.rs @@ -0,0 +1,41 @@ +use super::traits::Condition; +use crate::number::Number; +use libipld_core::{error::SerdeError, ipld::Ipld, serde as ipld_serde}; +use serde_derive::{Deserialize, Serialize}; + +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +#[serde(deny_unknown_fields)] +pub struct MinNumber { + field: String, + min_number: Number, +} + +impl From for Ipld { + fn from(min_number: MinNumber) -> Self { + min_number.into() + } +} + +impl TryFrom for MinNumber { + type Error = SerdeError; + + fn try_from(ipld: Ipld) -> Result { + ipld_serde::from_ipld(ipld) + } +} + +impl Condition for MinNumber { + fn validate(&self, ipld: &Ipld) -> bool { + match ipld { + Ipld::Integer(integer) => match self.min_number { + Number::Float(float) => *integer as f64 >= float, + Number::Integer(integer) => integer >= integer, + }, + Ipld::Float(float) => match self.min_number { + Number::Float(float) => float >= float, + Number::Integer(integer) => *float >= integer as f64, // FIXME this needs tests + }, + _ => false, + } + } +} diff --git a/src/lib.rs b/src/lib.rs index d40671f3..c57d2d70 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -118,6 +118,7 @@ pub mod delegation; pub mod invocation; pub mod ipld; pub mod nonce; +pub mod number; pub mod promise; pub mod proof; pub mod receipt; diff --git a/src/number.rs b/src/number.rs new file mode 100644 index 00000000..20f3042f --- /dev/null +++ b/src/number.rs @@ -0,0 +1,23 @@ +use libipld_core::{error::SerdeError, ipld::Ipld, serde as ipld_serde}; +use serde_derive::{Deserialize, Serialize}; + +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +#[serde(untagged)] +pub enum Number { + Float(f64), + Integer(i128), +} + +impl From for Ipld { + fn from(number: Number) -> Self { + number.into() + } +} + +impl TryFrom for Number { + type Error = SerdeError; + + fn try_from(ipld: Ipld) -> Result { + ipld_serde::from_ipld(ipld) + } +} From 21af73e51613e28e84a31177c702e65048ed3f46 Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Mon, 29 Jan 2024 02:27:15 -0800 Subject: [PATCH 031/188] Got rid of the extra constraint... but at what cost?!??! dun dun duuuun --- src/ability/crud/any.rs | 2 +- src/ability/crud/create.rs | 2 +- src/ability/crud/destroy.rs | 2 +- src/ability/crud/mutate.rs | 2 +- src/ability/crud/read.rs | 2 +- src/ability/crud/update.rs | 2 +- src/ability/msg/any.rs | 2 +- src/ability/msg/receive.rs | 2 +- src/ability/msg/send.rs | 2 +- src/delegation/payload.rs | 78 ++++++++++++----------- src/proof/checkable.rs | 4 +- src/proof/parentful.rs | 94 +++++++++++++++++++++++++--- src/proof/parentless.rs | 36 +++++++++-- src/proof/prove.rs | 11 +++- src/prove/traits/internal/checker.rs | 1 + 15 files changed, 179 insertions(+), 63 deletions(-) create mode 100644 src/prove/traits/internal/checker.rs diff --git a/src/ability/crud/any.rs b/src/ability/crud/any.rs index a3b0b6c0..ccebc717 100644 --- a/src/ability/crud/any.rs +++ b/src/ability/crud/any.rs @@ -16,7 +16,7 @@ impl Command for AnyBuilder { } impl Checkable for AnyBuilder { - type CheckAs = Parentless; + type Heirarchy = Parentless; } impl CheckSame for AnyBuilder { diff --git a/src/ability/crud/create.rs b/src/ability/crud/create.rs index 9e9f4ec7..59b00619 100644 --- a/src/ability/crud/create.rs +++ b/src/ability/crud/create.rs @@ -38,7 +38,7 @@ impl TryFrom for Create { } impl Checkable for Create { - type CheckAs = Parentful; + type Heirarchy = Parentful; } impl CheckSame for Create { diff --git a/src/ability/crud/destroy.rs b/src/ability/crud/destroy.rs index 9a2f209d..cf70caa1 100644 --- a/src/ability/crud/destroy.rs +++ b/src/ability/crud/destroy.rs @@ -35,7 +35,7 @@ impl TryFrom for Destroy { } impl Checkable for Destroy { - type CheckAs = Parentful; + type Heirarchy = Parentful; } impl CheckSame for Destroy { diff --git a/src/ability/crud/mutate.rs b/src/ability/crud/mutate.rs index c03836bc..c28af052 100644 --- a/src/ability/crud/mutate.rs +++ b/src/ability/crud/mutate.rs @@ -33,7 +33,7 @@ impl TryFrom for MutateBuilder { } impl Checkable for MutateBuilder { - type CheckAs = Parentful; + type Heirarchy = Parentful; } impl CheckSame for MutateBuilder { diff --git a/src/ability/crud/read.rs b/src/ability/crud/read.rs index 2cc601fc..0d9446af 100644 --- a/src/ability/crud/read.rs +++ b/src/ability/crud/read.rs @@ -39,7 +39,7 @@ impl TryFrom for Read { } impl Checkable for Read { - type CheckAs = Parentful; + type Heirarchy = Parentful; } impl CheckSame for Read { diff --git a/src/ability/crud/update.rs b/src/ability/crud/update.rs index a5463413..44dd284d 100644 --- a/src/ability/crud/update.rs +++ b/src/ability/crud/update.rs @@ -62,7 +62,7 @@ impl TryFrom for UpdateBuilder { } impl Checkable for UpdateBuilder { - type CheckAs = Parentful; + type Heirarchy = Parentful; } impl CheckSame for UpdateBuilder { diff --git a/src/ability/msg/any.rs b/src/ability/msg/any.rs index 7d80f3ec..64a08311 100644 --- a/src/ability/msg/any.rs +++ b/src/ability/msg/any.rs @@ -31,7 +31,7 @@ impl TryFrom for Any { } impl Checkable for Any { - type CheckAs = Parentless; + type Heirarchy = Parentless; } impl CheckSame for Any { diff --git a/src/ability/msg/receive.rs b/src/ability/msg/receive.rs index ce818d1c..c5969d82 100644 --- a/src/ability/msg/receive.rs +++ b/src/ability/msg/receive.rs @@ -40,7 +40,7 @@ impl TryFrom for Receive { } impl Checkable for Receive { - type CheckAs = Parentful; + type Heirarchy = Parentful; } impl CheckSame for Receive { diff --git a/src/ability/msg/send.rs b/src/ability/msg/send.rs index d7eb7ee7..dbbbe32d 100644 --- a/src/ability/msg/send.rs +++ b/src/ability/msg/send.rs @@ -57,7 +57,7 @@ impl TryFrom for SendBuilder { } impl Checkable for SendBuilder { - type CheckAs = Parentful; + type Heirarchy = Parentful; } impl CheckSame for SendBuilder { diff --git a/src/delegation/payload.rs b/src/delegation/payload.rs index 7eb6d2e9..19c4e05a 100644 --- a/src/delegation/payload.rs +++ b/src/delegation/payload.rs @@ -81,7 +81,7 @@ where } } -impl From> for Ipld { +impl From> for Ipld { fn from(payload: Payload) -> Self { payload.into() } @@ -89,39 +89,44 @@ impl From> for Ipld { impl<'a, T: Delegatable + Resolvable + Checkable + Clone, C: Condition> Payload { pub fn check( - invoked: invocation::Payload, // FIXME promisory version + invoked: &'a invocation::Payload, // FIXME promisory version proofs: Vec>, now: SystemTime, ) -> Result<(), ()> where - // FIXME so so so broken invocation::Payload: Clone, - T::CheckAs: From> + From + Prove + CheckSame, U::Builder: Clone, + T::Heirarchy: From> + From + CheckSame, { - let check_chain: T::CheckAs = invoked.clone().into(); - let start: Acc = Acc { - issuer: invoked.issuer.clone(), - subject: invoked.subject.clone(), - check_chain, + let start: Acc<'a, T> = Acc { + issuer: &invoked.issuer, + subject: &invoked.subject, + to_check: invoked.clone().into(), // FIXME surely we can eliminate this clone }; - let ipld: Ipld = invoked.into(); + let ipld: Ipld = invoked.clone().into(); - let result = proofs.iter().fold(Ok(start), |prev, proof| { - if let Ok(to_check) = prev { - match step1(&to_check, proof, &ipld, now) { + let result = proofs.iter().fold(Ok(start), |acc, proof| { + if let Ok(prev) = acc { + match step(&prev, proof, &ipld, now) { Outcome::ArgumentEscelation(_) => Err(()), Outcome::InvalidProofChain(_) => Err(()), - Outcome::ProvenByAny => Ok(to_check), // NOTE this case! + Outcome::InvalidParents(_) => Err(()), + Outcome::CommandEscelation => Err(()), + // NOTE this case! + Outcome::ProvenByAny => Ok(Acc { + issuer: &proof.issuer, + subject: &proof.subject, + to_check: prev.to_check, + }), Outcome::Proven => Ok(Acc { - issuer: proof.issuer.clone(), - subject: proof.subject.clone(), - check_chain: proof.ability_builder.clone().into(), // FIXME double check + issuer: &proof.issuer, + subject: &proof.subject, + to_check: proof.ability_builder.clone().into(), // FIXME double check }), } } else { - prev + acc } }); @@ -133,25 +138,24 @@ impl<'a, T: Delegatable + Resolvable + Checkable + Clone, C: Condition> Payload< } } -#[derive(Clone)] -struct Acc { - issuer: Did, - subject: Did, - check_chain: T::CheckAs, +#[derive(Debug, Clone)] +struct Acc<'a, T: Checkable> { + issuer: &'a Did, + subject: &'a Did, + to_check: T::Heirarchy, } -// FIXME replace with check_parents? -// FIXME this needs to move to Delegatable -fn step1<'a, T: Checkable, U: Delegatable, C: Condition>( - prev: &'a Acc, - proof: &'a Payload, - invoked_ipld: &'a Ipld, +// FIXME this should move to Delegatable +fn step<'a, T: Checkable, U: Delegatable, C: Condition>( + prev: &Acc<'a, T>, + proof: &Payload, + invoked_ipld: &Ipld, now: SystemTime, -) -> Outcome<(), ()> -// FIXME Outcome types +) -> Outcome<(), (), ()> +// FIXME ^^^^^^^^^^^^ Outcome types where - T::CheckAs: From + Prove, U::Builder: Clone, + T::Heirarchy: From, { if let Err(_) = prev.issuer.check_same(&proof.issuer) { return Outcome::InvalidProofChain(()); @@ -189,16 +193,16 @@ where return Outcome::InvalidProofChain(()); } - // FIXME pricey clone - let foo = prev - .check_chain - .check(&proof.ability_builder.clone().into()); + // FIXME pretty sure we can avoid this clone + let outcome = prev.to_check.check(&proof.ability_builder.clone().into()); - match foo { + match outcome { Outcome::Proven => Outcome::Proven, Outcome::ProvenByAny => Outcome::ProvenByAny, + Outcome::CommandEscelation => Outcome::CommandEscelation, Outcome::ArgumentEscelation(_) => Outcome::ArgumentEscelation(()), Outcome::InvalidProofChain(_) => Outcome::InvalidProofChain(()), + Outcome::InvalidParents(_) => Outcome::InvalidParents(()), } } diff --git a/src/proof/checkable.rs b/src/proof/checkable.rs index 07db3dce..44e8195d 100644 --- a/src/proof/checkable.rs +++ b/src/proof/checkable.rs @@ -1,5 +1,5 @@ -use super::{internal::Checker, same::CheckSame}; +use super::{internal::Checker, prove::Prove, same::CheckSame}; pub trait Checkable: CheckSame { - type CheckAs: Checker; + type Heirarchy: Checker + Prove; } diff --git a/src/proof/parentful.rs b/src/proof/parentful.rs index aa6ca820..dea984f8 100644 --- a/src/proof/parentful.rs +++ b/src/proof/parentful.rs @@ -14,6 +14,13 @@ pub enum Parentful { This(T), } +pub enum ParentfulError { + CommandEscelation, + ArgumentEscelation(ArgErr), + InvalidProofChain(PrfErr), + InvalidParents(ParErr), // FIXME seems kinda broken -- better naming at least +} + impl From> for Ipld where Ipld: From, @@ -34,25 +41,96 @@ where } } +impl CheckSame for Parentful +where + T::Parents: CheckSame, +{ + type Error = ParentfulError::Error>; // FIXME + + fn check_same(&self, proof: &Self) -> Result<(), Self::Error> { + match proof { + Parentful::Any => Ok(()), + Parentful::Parents(their_parents) => match self { + Parentful::Any => Err(ParentfulError::CommandEscelation), + Parentful::Parents(parents) => parents + .check_same(their_parents) + .map_err(ParentfulError::InvalidParents), + Parentful::This(this) => this + .check_parents(their_parents) + .map_err(ParentfulError::InvalidProofChain), + }, + Parentful::This(that) => match self { + Parentful::Any => Err(ParentfulError::CommandEscelation), + Parentful::Parents(_) => Err(ParentfulError::CommandEscelation), + Parentful::This(this) => this + .check_same(that) + .map_err(ParentfulError::ArgumentEscelation), + }, + } + } +} + +impl CheckParents for Parentful +where + T::Parents: CheckSame, +{ + type Parents = Parentful; + type ParentError = ParentfulError::Error>; + + fn check_parents(&self, proof: &Parentful) -> Result<(), Self::ParentError> { + match proof { + Parentful::Any => Ok(()), + Parentful::Parents(their_parents) => match self { + Parentful::Any => Err(ParentfulError::CommandEscelation), + Parentful::Parents(parents) => parents + .check_same(their_parents) + .map_err(ParentfulError::InvalidParents), + Parentful::This(this) => this + .check_parents(their_parents) + .map_err(ParentfulError::InvalidProofChain), + }, + Parentful::This(that) => match self { + Parentful::Any => Err(ParentfulError::CommandEscelation), + Parentful::Parents(_) => Err(ParentfulError::CommandEscelation), + Parentful::This(this) => this + .check_same(that) + .map_err(ParentfulError::ArgumentEscelation), + }, + } + } +} + impl Checker for Parentful {} -impl Prove> for T +impl Prove> for Parentful where T::Parents: CheckSame, { type ArgumentError = T::Error; type ProofChainError = T::ParentError; + type ParentsError = ::Error; // FIXME better name - fn check<'a>(&'a self, proof: &'a Parentful) -> Outcome { + fn check(&self, proof: &Parentful) -> Outcome { match proof { Parentful::Any => Outcome::ProvenByAny, - Parentful::Parents(parents) => match self.check_parents(parents) { - Ok(()) => Outcome::Proven, - Err(e) => Outcome::InvalidProofChain(e), + Parentful::Parents(their_parents) => match self { + Parentful::Any => Outcome::CommandEscelation, + Parentful::Parents(parents) => match parents.check_same(their_parents) { + Ok(()) => Outcome::Proven, + Err(e) => Outcome::InvalidParents(e), + }, + Parentful::This(this) => match this.check_parents(their_parents) { + Ok(()) => Outcome::Proven, + Err(e) => Outcome::InvalidProofChain(e), + }, }, - Parentful::This(that) => match self.check_same(&that) { - Ok(()) => Outcome::Proven, - Err(e) => Outcome::ArgumentEscelation(e), + Parentful::This(that) => match self { + Parentful::Any => Outcome::CommandEscelation, + Parentful::Parents(_) => Outcome::CommandEscelation, + Parentful::This(this) => match this.check_same(that) { + Ok(()) => Outcome::Proven, + Err(e) => Outcome::ArgumentEscelation(e), + }, }, } } diff --git a/src/proof/parentless.rs b/src/proof/parentless.rs index ec1dd15a..c9492ef5 100644 --- a/src/proof/parentless.rs +++ b/src/proof/parentless.rs @@ -13,6 +13,12 @@ pub enum Parentless { This(T), } +#[derive(Debug, Clone, PartialEq)] +pub enum ParentlessError { + CommandEscelation, + ArgumentEscelation(T::Error), +} + impl From> for Ipld where Ipld: From, @@ -30,18 +36,38 @@ impl + DeserializeOwned> TryFrom for Parentless { } } +impl CheckSame for Parentless { + type Error = ParentlessError; + + fn check_same(&self, other: &Self) -> Result<(), Self::Error> { + match other { + Parentless::Any => Ok(()), + Parentless::This(that) => match self { + Parentless::Any => Err(ParentlessError::CommandEscelation), + Parentless::This(this) => this + .check_same(that) + .map_err(ParentlessError::ArgumentEscelation), + }, + } + } +} + impl Checker for Parentless {} -impl Prove> for T { +impl Prove> for Parentless { type ArgumentError = T::Error; type ProofChainError = Infallible; + type ParentsError = Infallible; - fn check<'a>(&'a self, proof: &'a Parentless) -> Outcome { + fn check(&self, proof: &Parentless) -> Outcome { match proof { Parentless::Any => Outcome::Proven, - Parentless::This(this) => match self.check_same(&this) { - Ok(()) => Outcome::Proven, - Err(e) => Outcome::ArgumentEscelation(e), + Parentless::This(that) => match self { + Parentless::Any => Outcome::Proven, + Parentless::This(this) => match this.check_same(that) { + Ok(()) => Outcome::Proven, + Err(e) => Outcome::ArgumentEscelation(e), + }, }, } } diff --git a/src/proof/prove.rs b/src/proof/prove.rs index 9fc91ca2..d9876dcb 100644 --- a/src/proof/prove.rs +++ b/src/proof/prove.rs @@ -4,13 +4,20 @@ use super::internal::Checker; pub trait Prove { type ArgumentError; type ProofChainError; + type ParentsError; - fn check<'a>(&'a self, proof: &'a T) -> Outcome; + fn check( + &self, + proof: &T, + ) -> Outcome; } -pub enum Outcome { +// FIXME that's a lot of error type params +pub enum Outcome { Proven, ProvenByAny, ArgumentEscelation(ArgErr), InvalidProofChain(ChainErr), + InvalidParents(ParentErr), + CommandEscelation, } diff --git a/src/prove/traits/internal/checker.rs b/src/prove/traits/internal/checker.rs new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/src/prove/traits/internal/checker.rs @@ -0,0 +1 @@ + From f417565d3b91c6a57ccec5f44c9c93d8b4fecafa Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Mon, 29 Jan 2024 17:09:35 -0800 Subject: [PATCH 032/188] Rounding out types & breaking up modules --- src/ability/crud/any.rs | 24 ++++--- src/ability/crud/create.rs | 2 +- src/ability/crud/destroy.rs | 2 +- src/ability/crud/mutate.rs | 8 +-- src/ability/crud/parents.rs | 4 +- src/ability/crud/read.rs | 7 +- src/ability/crud/update.rs | 2 +- src/ability/msg/any.rs | 8 +-- src/ability/msg/receive.rs | 40 +++++------ src/ability/msg/send.rs | 132 +++++++++++++++++++++++++----------- src/ability/traits.rs | 91 +++++++++++++++++++------ src/ability/wasm.rs | 14 ++++ src/delegation/payload.rs | 9 +-- src/invocation/payload.rs | 1 + src/promise.rs | 26 +++++-- src/proof/checkable.rs | 2 +- src/proof/parentful.rs | 4 +- src/proof/parentless.rs | 9 +++ src/proof/same.rs | 12 +++- 19 files changed, 270 insertions(+), 127 deletions(-) diff --git a/src/ability/crud/any.rs b/src/ability/crud/any.rs index ccebc717..eaf3656f 100644 --- a/src/ability/crud/any.rs +++ b/src/ability/crud/any.rs @@ -1,27 +1,31 @@ use crate::{ ability::traits::Command, - proof::{checkable::Checkable, parentless::Parentless, same::CheckSame}, + proof::{ + parentless::NoParents, + same::{CheckSame, OptionalFieldErr}, + }, }; use serde::{Deserialize, Serialize}; use url::Url; +// NOTE no resolved or awaiting variants, because this cannot be executed, and all fields are optional already! + #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] #[serde(deny_unknown_fields)] -pub struct AnyBuilder { +pub struct Builder { + #[serde(default, skip_serializing_if = "Option::is_none")] pub uri: Option, } -impl Command for AnyBuilder { +impl Command for Builder { const COMMAND: &'static str = "crud/*"; } -impl Checkable for AnyBuilder { - type Heirarchy = Parentless; -} +impl NoParents for Builder {} -impl CheckSame for AnyBuilder { - type Error = (); - fn check_same(&self, _proof: &Self) -> Result<(), Self::Error> { - Ok(()) +impl CheckSame for Builder { + type Error = OptionalFieldErr; + fn check_same(&self, proof: &Self) -> Result<(), Self::Error> { + self.uri.check_same(&proof.uri) } } diff --git a/src/ability/crud/create.rs b/src/ability/crud/create.rs index 59b00619..9aa758b4 100644 --- a/src/ability/crud/create.rs +++ b/src/ability/crud/create.rs @@ -38,7 +38,7 @@ impl TryFrom for Create { } impl Checkable for Create { - type Heirarchy = Parentful; + type Hierarchy = Parentful; } impl CheckSame for Create { diff --git a/src/ability/crud/destroy.rs b/src/ability/crud/destroy.rs index cf70caa1..0ff025ec 100644 --- a/src/ability/crud/destroy.rs +++ b/src/ability/crud/destroy.rs @@ -35,7 +35,7 @@ impl TryFrom for Destroy { } impl Checkable for Destroy { - type Heirarchy = Parentful; + type Hierarchy = Parentful; } impl CheckSame for Destroy { diff --git a/src/ability/crud/mutate.rs b/src/ability/crud/mutate.rs index c28af052..c37a86b6 100644 --- a/src/ability/crud/mutate.rs +++ b/src/ability/crud/mutate.rs @@ -1,3 +1,4 @@ +use super::any; use crate::{ ability::traits::Command, proof::{checkable::Checkable, parentful::Parentful, parents::CheckParents, same::CheckSame}, @@ -6,11 +7,10 @@ use libipld_core::{ipld::Ipld, serde as ipld_serde}; use serde::{Deserialize, Serialize}; use url::Url; -use super::any::AnyBuilder; - #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] #[serde(deny_unknown_fields)] pub struct MutateBuilder { + #[serde(default, skip_serializing_if = "Option::is_none")] pub uri: Option, } @@ -33,7 +33,7 @@ impl TryFrom for MutateBuilder { } impl Checkable for MutateBuilder { - type Heirarchy = Parentful; + type Hierarchy = Parentful; } impl CheckSame for MutateBuilder { @@ -45,7 +45,7 @@ impl CheckSame for MutateBuilder { // TODO note to self, this is effectively a partial order impl CheckParents for MutateBuilder { - type Parents = AnyBuilder; + type Parents = any::Builder; type ParentError = (); fn check_parents(&self, _proof: &Self::Parents) -> Result<(), Self::ParentError> { diff --git a/src/ability/crud/parents.rs b/src/ability/crud/parents.rs index dc8c6d7b..f42b25a8 100644 --- a/src/ability/crud/parents.rs +++ b/src/ability/crud/parents.rs @@ -1,4 +1,4 @@ -use super::{any::AnyBuilder, mutate::MutateBuilder}; +use super::{any, mutate::MutateBuilder}; use crate::proof::same::CheckSame; use serde::{Deserialize, Serialize}; @@ -6,7 +6,7 @@ use serde::{Deserialize, Serialize}; #[serde(deny_unknown_fields)] pub enum Mutable { Mutate(MutateBuilder), - Any(AnyBuilder), + Any(any::Builder), } impl CheckSame for Mutable { diff --git a/src/ability/crud/read.rs b/src/ability/crud/read.rs index 0d9446af..9a55348c 100644 --- a/src/ability/crud/read.rs +++ b/src/ability/crud/read.rs @@ -1,3 +1,4 @@ +use super::any; use crate::{ ability::traits::Command, proof::{checkable::Checkable, parentful::Parentful, parents::CheckParents, same::CheckSame}, @@ -7,8 +8,6 @@ use serde::{Deserialize, Serialize}; use std::collections::BTreeMap; use url::Url; -use super::any::AnyBuilder; - // Read is its own builder #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(deny_unknown_fields)] @@ -39,7 +38,7 @@ impl TryFrom for Read { } impl Checkable for Read { - type Heirarchy = Parentful; + type Hierarchy = Parentful; } impl CheckSame for Read { @@ -50,7 +49,7 @@ impl CheckSame for Read { } impl CheckParents for Read { - type Parents = AnyBuilder; + type Parents = any::Builder; type ParentError = (); fn check_parents(&self, other: &Self::Parents) -> Result<(), Self::ParentError> { Ok(()) diff --git a/src/ability/crud/update.rs b/src/ability/crud/update.rs index 44dd284d..dc8c53c8 100644 --- a/src/ability/crud/update.rs +++ b/src/ability/crud/update.rs @@ -62,7 +62,7 @@ impl TryFrom for UpdateBuilder { } impl Checkable for UpdateBuilder { - type Heirarchy = Parentful; + type Hierarchy = Parentful; } impl CheckSame for UpdateBuilder { diff --git a/src/ability/msg/any.rs b/src/ability/msg/any.rs index 64a08311..468c8c75 100644 --- a/src/ability/msg/any.rs +++ b/src/ability/msg/any.rs @@ -1,6 +1,6 @@ use crate::{ ability::traits::Command, - proof::{checkable::Checkable, parentless::Parentless, same::CheckSame}, + proof::{parentless::NoParents, same::CheckSame}, }; use libipld_core::{error::SerdeError, ipld::Ipld, serde as ipld_serde}; use serde::{Deserialize, Serialize}; @@ -13,7 +13,7 @@ pub struct Any { } impl Command for Any { - const COMMAND: &'static str = "msg"; + const COMMAND: &'static str = "msg/*"; } impl From for Ipld { @@ -30,9 +30,7 @@ impl TryFrom for Any { } } -impl Checkable for Any { - type Heirarchy = Parentless; -} +impl NoParents for Any {} impl CheckSame for Any { type Error = (); diff --git a/src/ability/msg/receive.rs b/src/ability/msg/receive.rs index c5969d82..158680fc 100644 --- a/src/ability/msg/receive.rs +++ b/src/ability/msg/receive.rs @@ -14,33 +14,12 @@ pub struct Receive { pub from: Option, } -// #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] -// #[serde(deny_unknown_fields)] -// pub struct MsgReceiveDeferrable { -// to: Deferrable, -// from: Deferrable, -// } - impl Command for Receive { const COMMAND: &'static str = "msg/send"; } -impl From for Ipld { - fn from(receive: Receive) -> Self { - receive.into() - } -} - -impl TryFrom for Receive { - type Error = SerdeError; - - fn try_from(ipld: Ipld) -> Result { - ipld_serde::from_ipld(ipld) - } -} - impl Checkable for Receive { - type Heirarchy = Parentful; + type Hierarchy = Parentful; } impl CheckSame for Receive { @@ -58,3 +37,20 @@ impl CheckParents for Receive { self.from.check_same(&proof.from).map_err(|_| ()) } } + +//////////// + + +impl From for Ipld { + fn from(receive: Receive) -> Self { + receive.into() + } +} + +impl TryFrom for Receive { + type Error = SerdeError; + + fn try_from(ipld: Ipld) -> Result { + ipld_serde::from_ipld(ipld) + } +} diff --git a/src/ability/msg/send.rs b/src/ability/msg/send.rs index dbbbe32d..3ace3175 100644 --- a/src/ability/msg/send.rs +++ b/src/ability/msg/send.rs @@ -1,75 +1,62 @@ use crate::{ - ability::traits::Command, + ability::traits::{Command, Delegatable, Resolvable}, + promise::Deferrable, proof::{checkable::Checkable, parentful::Parentful, parents::CheckParents, same::CheckSame}, }; use libipld_core::{error::SerdeError, ipld::Ipld, serde as ipld_serde}; -use serde::{Deserialize, Serialize}; +use serde::{de::DeserializeOwned, Deserialize, Serialize}; use url::Url; use super::any as msg; #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] #[serde(deny_unknown_fields)] -pub struct Send { - pub to: Url, - pub from: Url, - pub message: String, +pub struct Generic { + pub to: To, + pub from: From, + pub message: Message, } -impl Command for Send { - const COMMAND: &'static str = "msg/send"; -} +pub type Resolved = Generic; +pub type Builder = Generic, Option, Option>; +pub type Awaiting = Generic, Deferrable, Deferrable>; -impl From for Ipld { - fn from(send: Send) -> Self { - send.into() - } +impl Delegatable for Resolved { + type Builder = Builder; } -impl TryFrom for Send { - type Error = SerdeError; - - fn try_from(ipld: Ipld) -> Result { - ipld_serde::from_ipld(ipld) - } +impl Resolvable for Resolved { + type Awaiting = Awaiting; } -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] -#[serde(deny_unknown_fields)] -pub struct SendBuilder { - pub to: Option, - pub from: Option, - pub message: Option, -} - -impl From for Ipld { - fn from(send: SendBuilder) -> Self { - send.into() +impl From for Builder { + fn from(awaiting: Awaiting) -> Self { + Builder { + to: awaiting.to.try_extract().ok(), + from: awaiting.from.try_extract().ok(), + message: awaiting.message.try_extract().ok(), + } } } -impl TryFrom for SendBuilder { - type Error = SerdeError; - - fn try_from(ipld: Ipld) -> Result { - ipld_serde::from_ipld(ipld) - } +impl Command for Generic { + const COMMAND: &'static str = "msg/send"; } -impl Checkable for SendBuilder { - type Heirarchy = Parentful; +impl Checkable for Builder { + type Hierarchy = Parentful; } -impl CheckSame for SendBuilder { +impl CheckSame for Builder { type Error = (); // FIXME better error fn check_same(&self, proof: &Self) -> Result<(), Self::Error> { - self.to.check_same(&proof.to).map_err(|_| ())?; // FIXME + self.to.check_same(&proof.to).map_err(|_| ())?; self.from.check_same(&proof.from).map_err(|_| ())?; self.message.check_same(&proof.message).map_err(|_| ()) } } -impl CheckParents for SendBuilder { +impl CheckParents for Builder { type Parents = msg::Any; type ParentError = ::Error; @@ -78,3 +65,66 @@ impl CheckParents for SendBuilder { self.from.check_same(&other.from).map_err(|_| ()) } } + +impl From for Builder { + fn from(resolved: Resolved) -> Self { + Generic { + to: resolved.to.into(), + from: resolved.from.into(), + message: resolved.message.into(), + } + } +} + +impl From for Awaiting { + fn from(resolved: Resolved) -> Self { + Generic { + to: resolved.to.into(), + from: resolved.from.into(), + message: resolved.message.into(), + } + } +} + +impl TryFrom for Resolved { + type Error = (); + + fn try_from(awaiting: Awaiting) -> Result { + Ok(Generic { + to: awaiting.to.try_extract().map_err(|_| ())?, + from: awaiting.from.try_extract().map_err(|_| ())?, + message: awaiting.message.try_extract().map_err(|_| ())?, + }) + } +} + +impl TryFrom for Resolved { + type Error = (); + + fn try_from(builder: Builder) -> Result { + Ok(Generic { + to: builder.to.ok_or(())?, + from: builder.from.ok_or(())?, + message: builder.message.ok_or(())?, + }) + } +} + +impl From> for Ipld +where + Ipld: From + From + From, +{ + fn from(send: Generic) -> Self { + send.into() + } +} + +impl TryFrom + for Generic +{ + type Error = SerdeError; + + fn try_from(ipld: Ipld) -> Result { + ipld_serde::from_ipld(ipld) + } +} diff --git a/src/ability/traits.rs b/src/ability/traits.rs index 24ec772a..d43a4977 100644 --- a/src/ability/traits.rs +++ b/src/ability/traits.rs @@ -16,23 +16,52 @@ pub trait Command { // FIXME Delegable and make it proven? pub trait Delegatable: Sized { - type Builder: Debug + TryInto + From; + type Builder: TryInto + From; } pub trait Resolvable: Delegatable { - type Awaiting: Debug + TryInto + From + Into; + type Awaiting: TryInto + From + Into; } pub trait Runnable { - type Output: Debug; - fn task_id(self, subject: Did, nonce: Nonce) -> Cid; + type Output; + + fn to_task(&self, subject: Did, nonce: Nonce) -> Task; + + fn to_task_id(&self, subject: Did, nonce: Nonce) -> TaskId { + TaskId { + cid: self.to_task(subject, nonce).into(), + } + } + + // fn lookup(id: TaskId>) -> Result; +} + +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub struct TaskId { + cid: Cid, } #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(deny_unknown_fields)] pub struct DynJs { pub cmd: String, + #[serde(default)] pub args: BTreeMap, + + #[serde(default)] + pub serialize_nonce: DefaultTrue, +} + +// FIXME move to differet module +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] +#[serde(transparent)] +pub struct DefaultTrue(bool); + +impl Default for DefaultTrue { + fn default() -> Self { + DefaultTrue(true) + } } impl Delegatable for DynJs { @@ -43,24 +72,48 @@ impl Resolvable for DynJs { type Awaiting = Self; } +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub struct Task { + #[serde(default, skip_serializing_if = "Option::is_none")] + pub sub: Option, // Is this optional? May as well make it so for now! + #[serde(default, skip_serializing_if = "Option::is_none")] + pub nonce: Option, + + pub cmd: String, + pub args: BTreeMap, +} + +impl From for Ipld { + fn from(task: Task) -> Ipld { + task.into() + } +} + +impl From for Cid { + fn from(task: Task) -> Cid { + let mut buffer = vec![]; + let ipld: Ipld = task.into(); + ipld.encode(DagCborCodec, &mut buffer) + .expect("DagCborCodec to encode any arbitrary `Ipld`"); + CidGeneric::new_v1(DagCborCodec.into(), Sha2_256.digest(buffer.as_slice())) + } +} + +// FIXME DynJs may need a hook for if the nonce should be included impl Runnable for DynJs { type Output = Ipld; - fn task_id(self, subject: Did, nonce: Nonce) -> Cid { - let ipld: Ipld = BTreeMap::from_iter([ - ("sub".into(), subject.into()), - ("do".into(), self.cmd.clone().into()), - ("args".into(), self.cmd.clone().into()), - ("nonce".into(), nonce.into()), - ]) - .into(); - - let mut encoded = vec![]; - ipld.encode(DagCborCodec, &mut encoded) - .expect("should never fail if `encodable_as` is implemented correctly"); - - let multihash = Sha2_256.digest(encoded.as_slice()); - CidGeneric::new_v1(DagCborCodec.into(), multihash) + fn to_task(&self, subject: Did, nonce: Nonce) -> Task { + Task { + sub: Some(subject), + nonce: if self.serialize_nonce == DefaultTrue(true) { + Some(nonce) + } else { + None + }, + cmd: self.cmd.clone(), + args: self.args.clone(), + } } } diff --git a/src/ability/wasm.rs b/src/ability/wasm.rs index 4ccd51b5..748c9e6e 100644 --- a/src/ability/wasm.rs +++ b/src/ability/wasm.rs @@ -1,3 +1,4 @@ +use crate::proof::parentless::NoParents; use libipld_core::{ipld::Ipld, link::Link}; #[derive(Debug, Clone, PartialEq)] @@ -12,3 +13,16 @@ pub enum Module { Inline(Vec), Cid(Link>), } + +impl Command for Run { + const COMMAND: &'static str = "wasm/run"; +} + +impl NoParents for Run {} + +impl CheckSame for Run { + type Error = (); + fn check_same(&self, _proof: &Self) -> Result<(), Self::Error> { + Ok(()) + } +} diff --git a/src/delegation/payload.rs b/src/delegation/payload.rs index 19c4e05a..36fdf4e9 100644 --- a/src/delegation/payload.rs +++ b/src/delegation/payload.rs @@ -96,7 +96,7 @@ impl<'a, T: Delegatable + Resolvable + Checkable + Clone, C: Condition> Payload< where invocation::Payload: Clone, U::Builder: Clone, - T::Heirarchy: From> + From + CheckSame, + T::Hierarchy: From> + From + CheckSame, { let start: Acc<'a, T> = Acc { issuer: &invoked.issuer, @@ -142,7 +142,7 @@ impl<'a, T: Delegatable + Resolvable + Checkable + Clone, C: Condition> Payload< struct Acc<'a, T: Checkable> { issuer: &'a Did, subject: &'a Did, - to_check: T::Heirarchy, + to_check: T::Hierarchy, } // FIXME this should move to Delegatable @@ -155,9 +155,9 @@ fn step<'a, T: Checkable, U: Delegatable, C: Condition>( // FIXME ^^^^^^^^^^^^ Outcome types where U::Builder: Clone, - T::Heirarchy: From, + T::Hierarchy: From, { - if let Err(_) = prev.issuer.check_same(&proof.issuer) { + if let Err(_) = prev.issuer.check_same(&proof.audience) { return Outcome::InvalidProofChain(()); } @@ -278,6 +278,7 @@ impl> TryFrom for Payload for Payload { ability: DynJs { cmd: s.command, args: s.arguments, + serialize_nonce: todo!(), }, proofs: s.proofs, diff --git a/src/promise.rs b/src/promise.rs index 55962441..af1bd93e 100644 --- a/src/promise.rs +++ b/src/promise.rs @@ -5,29 +5,41 @@ use std::fmt::Debug; #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] #[serde(untagged)] -pub enum Deferrable -where - T: Debug + Clone + PartialEq, -{ +pub enum Deferrable { Resolved(T), Await(Promise), } +impl Deferrable { + pub fn try_extract(self) -> Result { + match self { + Deferrable::Resolved(t) => Ok(t), + Deferrable::Await(promise) => Err(Deferrable::Await(promise)), + } + } +} + +impl From for Deferrable { + fn from(t: T) -> Self { + Deferrable::Resolved(t) + } +} + /// A [`Promise`] is a way to defer the presence of a value to the result of some [`Invocation`]. #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] #[serde(untagged, deny_unknown_fields)] // FIXME check that this is right, also pub enum Promise { PromiseAny { #[serde(rename = "ucan/*")] // FIXME test to make sure that this is right? - await_any: Cid, + any: Cid, }, PromiseOk { #[serde(rename = "ucan/ok")] - await_ok: Cid, + ok: Cid, }, PromiseErr { #[serde(rename = "ucan/err")] - await_err: Cid, + err: Cid, }, } diff --git a/src/proof/checkable.rs b/src/proof/checkable.rs index 44e8195d..0a55ee19 100644 --- a/src/proof/checkable.rs +++ b/src/proof/checkable.rs @@ -1,5 +1,5 @@ use super::{internal::Checker, prove::Prove, same::CheckSame}; pub trait Checkable: CheckSame { - type Heirarchy: Checker + Prove; + type Hierarchy: Checker + Prove; } diff --git a/src/proof/parentful.rs b/src/proof/parentful.rs index dea984f8..d18e8289 100644 --- a/src/proof/parentful.rs +++ b/src/proof/parentful.rs @@ -41,7 +41,7 @@ where } } -impl CheckSame for Parentful +impl CheckSame for Parentful where T::Parents: CheckSame, { @@ -70,7 +70,7 @@ where } } -impl CheckParents for Parentful +impl CheckParents for Parentful where T::Parents: CheckSame, { diff --git a/src/proof/parentless.rs b/src/proof/parentless.rs index c9492ef5..08c0e14c 100644 --- a/src/proof/parentless.rs +++ b/src/proof/parentless.rs @@ -1,4 +1,5 @@ use super::{ + checkable::Checkable, internal::Checker, prove::{Outcome, Prove}, same::CheckSame, @@ -13,12 +14,20 @@ pub enum Parentless { This(T), } +// FIXME generally useful (e.g. checkiung `_/*`); move to its own module and rename #[derive(Debug, Clone, PartialEq)] pub enum ParentlessError { CommandEscelation, ArgumentEscelation(T::Error), } +// FIXME better name +pub trait NoParents {} + +impl Checkable for T { + type Hierarchy = Parentless; +} + impl From> for Ipld where Ipld: From, diff --git a/src/proof/same.rs b/src/proof/same.rs index f2079ea6..b05f8cb6 100644 --- a/src/proof/same.rs +++ b/src/proof/same.rs @@ -1,5 +1,5 @@ use crate::did::Did; -use serde::Serialize; +use serde::{Deserialize, Serialize}; pub trait CheckSame { type Error; @@ -8,10 +8,16 @@ pub trait CheckSame { } // Genereic -#[derive(Debug, Clone, PartialEq, Eq, Serialize)] +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub struct Unequal; -#[derive(Debug, Clone, PartialEq, Eq, Serialize)] +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub struct OpionalFieldErr { + pub field: T, // Enum of fields + pub err: OptionalFieldErr, +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub enum OptionalFieldErr { MissingField, UnequalValue, From 9cb6cdf551f9b6d734d57b3ab7498e375d0e89e6 Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Tue, 30 Jan 2024 23:40:47 -0800 Subject: [PATCH 033/188] Break up modules in advance of cleanup --- src/ability.rs | 9 +- src/ability/arguments.rs | 20 +++++ src/ability/command.rs | 14 +++ src/ability/crud/any.rs | 2 +- src/ability/crud/create.rs | 2 +- src/ability/crud/destroy.rs | 2 +- src/ability/crud/mutate.rs | 2 +- src/ability/crud/read.rs | 2 +- src/ability/crud/update.rs | 2 +- src/ability/dynamic.rs | 106 ++++++++++------------- src/ability/internal.rs | 1 + src/ability/msg/any.rs | 2 +- src/ability/msg/receive.rs | 3 +- src/ability/msg/send.rs | 44 ++++++++-- src/ability/traits.rs | 132 ----------------------------- src/ability/wasm.rs | 8 +- src/delegation.rs | 3 + src/delegation/condition/traits.rs | 2 +- src/delegation/delegatable.rs | 5 ++ src/delegation/payload.rs | 29 +++---- src/delegation/traits.rs | 5 ++ src/invocation.rs | 1 + src/invocation/payload.rs | 39 ++++----- src/invocation/resolvable.rs | 5 ++ src/lib.rs | 1 + src/promise.rs | 70 ++++++++++++--- src/proof/checkable.rs | 2 +- src/receipt.rs | 82 +++++++++--------- src/receipt/payload.rs | 13 +-- src/receipt/runnable.rs | 15 ++++ src/task.rs | 97 +++++++++++++++++++++ 31 files changed, 401 insertions(+), 319 deletions(-) create mode 100644 src/ability/arguments.rs create mode 100644 src/ability/command.rs create mode 100644 src/ability/internal.rs delete mode 100644 src/ability/traits.rs create mode 100644 src/delegation/delegatable.rs create mode 100644 src/delegation/traits.rs create mode 100644 src/invocation/resolvable.rs create mode 100644 src/receipt/runnable.rs create mode 100644 src/task.rs diff --git a/src/ability.rs b/src/ability.rs index f3a4f8aa..788870b2 100644 --- a/src/ability.rs +++ b/src/ability.rs @@ -1,12 +1,13 @@ -pub mod traits; -// pub mod wasm; - // FIXME feature flag each? pub mod crud; pub mod msg; +pub mod wasm; + +pub mod arguments; +pub mod command; // TODO move to crate::wasm? -#[cfg(feature = "wasm")] +// #[cfg(feature = "wasm")] pub mod dynamic; // FIXME macro to derive promise versions & delagted builder versions diff --git a/src/ability/arguments.rs b/src/ability/arguments.rs new file mode 100644 index 00000000..67b86d54 --- /dev/null +++ b/src/ability/arguments.rs @@ -0,0 +1,20 @@ +use libipld_core::{error::SerdeError, ipld::Ipld, serde as ipld_serde}; +use serde::{Deserialize, Serialize}; +use std::collections::BTreeMap; + +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub struct Arguments(pub BTreeMap); + +impl Arguments { + pub fn from_iter(iterable: impl IntoIterator) -> Self { + Arguments(iterable.into_iter().collect()) + } +} + +impl TryFrom for Arguments { + type Error = SerdeError; + + fn try_from(ipld: Ipld) -> Result { + ipld_serde::from_ipld(ipld) + } +} diff --git a/src/ability/command.rs b/src/ability/command.rs new file mode 100644 index 00000000..f06637fc --- /dev/null +++ b/src/ability/command.rs @@ -0,0 +1,14 @@ +pub trait Command { + const COMMAND: &'static str; +} + +// NOTE do not export +pub(crate) trait ToCommand { + fn to_command(&self) -> String; +} + +impl ToCommand for T { + fn to_command(&self) -> String { + T::COMMAND.to_string() + } +} diff --git a/src/ability/crud/any.rs b/src/ability/crud/any.rs index eaf3656f..909d09a6 100644 --- a/src/ability/crud/any.rs +++ b/src/ability/crud/any.rs @@ -1,5 +1,5 @@ use crate::{ - ability::traits::Command, + ability::command::Command, proof::{ parentless::NoParents, same::{CheckSame, OptionalFieldErr}, diff --git a/src/ability/crud/create.rs b/src/ability/crud/create.rs index 9aa758b4..a1452d29 100644 --- a/src/ability/crud/create.rs +++ b/src/ability/crud/create.rs @@ -1,5 +1,5 @@ use crate::{ - ability::traits::Command, + ability::command::Command, proof::{checkable::Checkable, parentful::Parentful, parents::CheckParents, same::CheckSame}, }; use libipld_core::{ipld::Ipld, serde as ipld_serde}; diff --git a/src/ability/crud/destroy.rs b/src/ability/crud/destroy.rs index 0ff025ec..91ac42fe 100644 --- a/src/ability/crud/destroy.rs +++ b/src/ability/crud/destroy.rs @@ -1,5 +1,5 @@ use crate::{ - ability::traits::Command, + ability::command::Command, proof::{checkable::Checkable, parentful::Parentful, parents::CheckParents, same::CheckSame}, }; use libipld_core::{ipld::Ipld, serde as ipld_serde}; diff --git a/src/ability/crud/mutate.rs b/src/ability/crud/mutate.rs index c37a86b6..9c1fd8fe 100644 --- a/src/ability/crud/mutate.rs +++ b/src/ability/crud/mutate.rs @@ -1,6 +1,6 @@ use super::any; use crate::{ - ability::traits::Command, + ability::command::Command, proof::{checkable::Checkable, parentful::Parentful, parents::CheckParents, same::CheckSame}, }; use libipld_core::{ipld::Ipld, serde as ipld_serde}; diff --git a/src/ability/crud/read.rs b/src/ability/crud/read.rs index 9a55348c..f98fc969 100644 --- a/src/ability/crud/read.rs +++ b/src/ability/crud/read.rs @@ -1,6 +1,6 @@ use super::any; use crate::{ - ability::traits::Command, + ability::command::Command, proof::{checkable::Checkable, parentful::Parentful, parents::CheckParents, same::CheckSame}, }; use libipld_core::{ipld::Ipld, serde as ipld_serde}; diff --git a/src/ability/crud/update.rs b/src/ability/crud/update.rs index dc8c53c8..83249052 100644 --- a/src/ability/crud/update.rs +++ b/src/ability/crud/update.rs @@ -1,5 +1,5 @@ use crate::{ - ability::traits::Command, + ability::command::Command, proof::{checkable::Checkable, parentful::Parentful, parents::CheckParents, same::CheckSame}, }; use libipld_core::{ipld::Ipld, serde as ipld_serde}; diff --git a/src/ability/dynamic.rs b/src/ability/dynamic.rs index b55a9e62..cb4e5129 100644 --- a/src/ability/dynamic.rs +++ b/src/ability/dynamic.rs @@ -1,78 +1,66 @@ -//! This module is for dynamic abilities, especially for Wasm support +//! This module is for dynamic abilities, especially for FFI and Wasm support -use super::traits::{Ability, Command}; -use crate::prove::TryProve; -use libipld_core::ipld::Ipld; -use std::collections::BTreeMap; +use super::{arguments::Arguments, command::ToCommand}; +use crate::{ + delegation::delegatable::Delegatable, invocation::resolvable::Resolvable, promise::Promise, +}; +use serde_derive::{Deserialize, Serialize}; +use std::fmt::Debug; -use crate::ipld::WrappedIpld; +// FIXME move module? +// use js_sys; +// use wasm_bindgen::prelude::*; +// type JsDynamic = Dynamic<&'a js_sys::Function>; +// type JsBuilder = Builder<&'a js_sys::Function>; +// type JsPromised = Promised<&'a js_sys::Function>; +// FIXME move these fiels to a wrapper struct in a different module +// #[serde(skip_serializing)] +// pub chain_validator: Pred, +// #[serde(skip_serializing)] +// pub shape_validator: Pred, +// #[serde(skip_serializing)] +// pub serialize_nonce: DefaultTrue, -use js_sys; -use wasm_bindgen::prelude::*; - -#[derive(Debug, Clone, PartialEq)] -pub struct Dynamic<'a> { - pub command: String, - pub args: BTreeMap, // FIXME consider this being just JsValue - pub validator: &'a js_sys::Function, +#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)] +pub struct Generic { + pub cmd: String, + pub args: Args, } -#[derive(Debug, Clone, PartialEq)] -pub struct DynamicBuilder<'a> { - pub command: String, - pub args: Option>, - pub validator: &'a js_sys::Function, -} +pub type Dynamic = Generic; +pub type Promised = Generic>; -impl<'a> From> for DynamicBuilder<'a> { - fn from(dynamic: Dynamic<'a>) -> Self { - Self { - command: dynamic.command.clone(), - args: Some(dynamic.args), - validator: dynamic.validator, - } +impl ToCommand for Generic { + fn to_command(&self) -> String { + self.cmd.clone() } } -impl<'a> TryFrom> for Dynamic<'a> { - type Error = (); // FIXME - - fn try_from(builder: DynamicBuilder) -> Result { - if let Some(args) = builder.clone().args { - Ok(Self { - command: builder.command.clone(), - args, - }) - } else { - Err(()) - } - } +impl Delegatable for Dynamic { + type Builder = Dynamic; } -impl<'a> Command for Dynamic<'a> { - fn command(&self) -> &'static str { - self.command - } +impl Resolvable for Dynamic { + type Promised = Dynamic; } -impl<'a> Command for DynamicBuilder<'a> { - fn command(&self) -> &'static str { - self.command +impl From for Arguments { + fn from(dynamic: Dynamic) -> Self { + dynamic.args } } -impl<'a> Ability for Dynamic<'a> { - type Builder = DynamicBuilder<'a>; -} - -impl<'a> TryProve> for DynamicBuilder<'a> { - type Error = JsError; - type Proven = DynamicBuilder<'a>; // TODO docs: even if you parse a well-structred type, you MUST return a dynamic builder and continue checking that - - fn try_prove(&'a self, proof: &'a DynamicBuilder) -> Result<&'a Self::Proven, ()> { - let js_self: JsValue = self.into().into(); - let js_proof: JsValue = proof.into().into(); +impl TryFrom for Dynamic { + type Error = (); // FIXME - self.validator.apply(js_self, js_proof); + fn try_from(awaiting: Promised) -> Result { + if let Promise::Resolved(args) = &awaiting.args { + Ok(Dynamic { + cmd: awaiting.cmd, + args: args.clone(), + }) + } else { + Err(()) + } } } diff --git a/src/ability/internal.rs b/src/ability/internal.rs new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/src/ability/internal.rs @@ -0,0 +1 @@ + diff --git a/src/ability/msg/any.rs b/src/ability/msg/any.rs index 468c8c75..ae603aa4 100644 --- a/src/ability/msg/any.rs +++ b/src/ability/msg/any.rs @@ -1,5 +1,5 @@ use crate::{ - ability::traits::Command, + ability::command::Command, proof::{parentless::NoParents, same::CheckSame}, }; use libipld_core::{error::SerdeError, ipld::Ipld, serde as ipld_serde}; diff --git a/src/ability/msg/receive.rs b/src/ability/msg/receive.rs index 158680fc..c7fb6d68 100644 --- a/src/ability/msg/receive.rs +++ b/src/ability/msg/receive.rs @@ -1,5 +1,5 @@ use crate::{ - ability::traits::Command, + ability::command::Command, proof::{checkable::Checkable, parentful::Parentful, parents::CheckParents, same::CheckSame}, }; use libipld_core::{error::SerdeError, ipld::Ipld, serde as ipld_serde}; @@ -40,7 +40,6 @@ impl CheckParents for Receive { //////////// - impl From for Ipld { fn from(receive: Receive) -> Self { receive.into() diff --git a/src/ability/msg/send.rs b/src/ability/msg/send.rs index 3ace3175..2cd3293d 100644 --- a/src/ability/msg/send.rs +++ b/src/ability/msg/send.rs @@ -1,10 +1,13 @@ use crate::{ - ability::traits::{Command, Delegatable, Resolvable}, - promise::Deferrable, + ability::{arguments::Arguments, command::Command}, + delegation::delegatable::Delegatable, + invocation::resolvable::Resolvable, + promise::Promise, proof::{checkable::Checkable, parentful::Parentful, parents::CheckParents, same::CheckSame}, }; use libipld_core::{error::SerdeError, ipld::Ipld, serde as ipld_serde}; use serde::{de::DeserializeOwned, Deserialize, Serialize}; +use std::collections::BTreeMap; use url::Url; use super::any as msg; @@ -19,18 +22,41 @@ pub struct Generic { pub type Resolved = Generic; pub type Builder = Generic, Option, Option>; -pub type Awaiting = Generic, Deferrable, Deferrable>; +pub type Promised = Generic, Promise, Promise>; impl Delegatable for Resolved { type Builder = Builder; } impl Resolvable for Resolved { - type Awaiting = Awaiting; + type Promised = Promised; } -impl From for Builder { - fn from(awaiting: Awaiting) -> Self { +impl From for Arguments { + fn from(b: Builder) -> Self { + let mut btree = BTreeMap::new(); + b.to.map(|to| btree.insert("to".into(), to.to_string().into())); + b.from + .map(|from| btree.insert("from".into(), from.to_string().into())); + b.message + .map(|msg| btree.insert("message".into(), msg.into())); + + Arguments(btree) + } +} + +impl From for Arguments { + fn from(promised: Promised) -> Self { + Arguments(BTreeMap::from_iter([ + ("to".into(), promised.to.map(String::from).into()), + ("from".into(), promised.from.map(String::from).into()), + ("message".into(), promised.message.into()), + ])) + } +} + +impl From for Builder { + fn from(awaiting: Promised) -> Self { Builder { to: awaiting.to.try_extract().ok(), from: awaiting.from.try_extract().ok(), @@ -76,7 +102,7 @@ impl From for Builder { } } -impl From for Awaiting { +impl From for Promised { fn from(resolved: Resolved) -> Self { Generic { to: resolved.to.into(), @@ -86,10 +112,10 @@ impl From for Awaiting { } } -impl TryFrom for Resolved { +impl TryFrom for Resolved { type Error = (); - fn try_from(awaiting: Awaiting) -> Result { + fn try_from(awaiting: Promised) -> Result { Ok(Generic { to: awaiting.to.try_extract().map_err(|_| ())?, from: awaiting.from.try_extract().map_err(|_| ())?, diff --git a/src/ability/traits.rs b/src/ability/traits.rs deleted file mode 100644 index d43a4977..00000000 --- a/src/ability/traits.rs +++ /dev/null @@ -1,132 +0,0 @@ -use crate::{did::Did, nonce::Nonce}; -use libipld_cbor::DagCborCodec; -use libipld_core::{ - cid::{Cid, CidGeneric}, - codec::Encode, - ipld::Ipld, - multihash::{Code::Sha2_256, MultihashDigest}, - serde as ipld_serde, -}; -use serde_derive::{Deserialize, Serialize}; -use std::{collections::BTreeMap, fmt::Debug}; - -pub trait Command { - const COMMAND: &'static str; -} - -// FIXME Delegable and make it proven? -pub trait Delegatable: Sized { - type Builder: TryInto + From; -} - -pub trait Resolvable: Delegatable { - type Awaiting: TryInto + From + Into; -} - -pub trait Runnable { - type Output; - - fn to_task(&self, subject: Did, nonce: Nonce) -> Task; - - fn to_task_id(&self, subject: Did, nonce: Nonce) -> TaskId { - TaskId { - cid: self.to_task(subject, nonce).into(), - } - } - - // fn lookup(id: TaskId>) -> Result; -} - -#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] -pub struct TaskId { - cid: Cid, -} - -#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] -#[serde(deny_unknown_fields)] -pub struct DynJs { - pub cmd: String, - #[serde(default)] - pub args: BTreeMap, - - #[serde(default)] - pub serialize_nonce: DefaultTrue, -} - -// FIXME move to differet module -#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] -#[serde(transparent)] -pub struct DefaultTrue(bool); - -impl Default for DefaultTrue { - fn default() -> Self { - DefaultTrue(true) - } -} - -impl Delegatable for DynJs { - type Builder = Self; -} - -impl Resolvable for DynJs { - type Awaiting = Self; -} - -#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] -pub struct Task { - #[serde(default, skip_serializing_if = "Option::is_none")] - pub sub: Option, // Is this optional? May as well make it so for now! - #[serde(default, skip_serializing_if = "Option::is_none")] - pub nonce: Option, - - pub cmd: String, - pub args: BTreeMap, -} - -impl From for Ipld { - fn from(task: Task) -> Ipld { - task.into() - } -} - -impl From for Cid { - fn from(task: Task) -> Cid { - let mut buffer = vec![]; - let ipld: Ipld = task.into(); - ipld.encode(DagCborCodec, &mut buffer) - .expect("DagCborCodec to encode any arbitrary `Ipld`"); - CidGeneric::new_v1(DagCborCodec.into(), Sha2_256.digest(buffer.as_slice())) - } -} - -// FIXME DynJs may need a hook for if the nonce should be included -impl Runnable for DynJs { - type Output = Ipld; - - fn to_task(&self, subject: Did, nonce: Nonce) -> Task { - Task { - sub: Some(subject), - nonce: if self.serialize_nonce == DefaultTrue(true) { - Some(nonce) - } else { - None - }, - cmd: self.cmd.clone(), - args: self.args.clone(), - } - } -} - -impl From for Ipld { - fn from(js: DynJs) -> Self { - js.into() - } -} - -impl TryFrom for DynJs { - type Error = (); // FIXME - - fn try_from(ipld: Ipld) -> Result { - ipld_serde::from_ipld(ipld).map_err(|_| ()) - } -} diff --git a/src/ability/wasm.rs b/src/ability/wasm.rs index 748c9e6e..07e8d5cd 100644 --- a/src/ability/wasm.rs +++ b/src/ability/wasm.rs @@ -1,4 +1,5 @@ -use crate::proof::parentless::NoParents; +use super::command::Command; +use crate::proof::{parentless::NoParents, same::CheckSame}; use libipld_core::{ipld::Ipld, link::Link}; #[derive(Debug, Clone, PartialEq)] @@ -8,6 +9,7 @@ pub struct Run { pub args: Vec, } +// FIXME #[derive(Debug, Clone, PartialEq)] pub enum Module { Inline(Vec), @@ -21,8 +23,8 @@ impl Command for Run { impl NoParents for Run {} impl CheckSame for Run { - type Error = (); + type Error = (); // FIXME fn check_same(&self, _proof: &Self) -> Result<(), Self::Error> { - Ok(()) + Ok(()) // FIXME } } diff --git a/src/delegation.rs b/src/delegation.rs index 5c96d411..431531d1 100644 --- a/src/delegation.rs +++ b/src/delegation.rs @@ -1,4 +1,7 @@ +// FIXME rename delegate? + pub mod condition; +pub mod delegatable; pub mod delegate; pub mod payload; diff --git a/src/delegation/condition/traits.rs b/src/delegation/condition/traits.rs index 29d1be0b..f56edc60 100644 --- a/src/delegation/condition/traits.rs +++ b/src/delegation/condition/traits.rs @@ -1,5 +1,5 @@ use libipld_core::ipld::Ipld; -pub trait Condition { +pub trait Condition: TryFrom + Into { fn validate(&self, ipld: &Ipld) -> bool; } diff --git a/src/delegation/delegatable.rs b/src/delegation/delegatable.rs new file mode 100644 index 00000000..151d4fab --- /dev/null +++ b/src/delegation/delegatable.rs @@ -0,0 +1,5 @@ +use crate::ability::arguments::Arguments; + +pub trait Delegatable: Sized { + type Builder: TryInto + From + Into; +} diff --git a/src/delegation/payload.rs b/src/delegation/payload.rs index 36fdf4e9..527246f8 100644 --- a/src/delegation/payload.rs +++ b/src/delegation/payload.rs @@ -1,9 +1,9 @@ -use super::condition::traits::Condition; +use super::{condition::traits::Condition, delegatable::Delegatable}; use crate::{ - ability::traits::{Command, Delegatable, DynJs, Resolvable}, + ability::{arguments::Arguments, command::Command, dynamic}, capsule::Capsule, did::Did, - invocation::payload as invocation, + invocation::{payload as invocation, resolvable::Resolvable}, nonce::Nonce, proof::{ checkable::Checkable, @@ -95,8 +95,8 @@ impl<'a, T: Delegatable + Resolvable + Checkable + Clone, C: Condition> Payload< ) -> Result<(), ()> where invocation::Payload: Clone, - U::Builder: Clone, - T::Hierarchy: From> + From + CheckSame, + U::Builder: Clone + Into, + T::Hierarchy: From>, { let start: Acc<'a, T> = Acc { issuer: &invoked.issuer, @@ -154,8 +154,7 @@ fn step<'a, T: Checkable, U: Delegatable, C: Condition>( ) -> Outcome<(), (), ()> // FIXME ^^^^^^^^^^^^ Outcome types where - U::Builder: Clone, - T::Hierarchy: From, + U::Builder: Into + Clone, { if let Err(_) = prev.issuer.check_same(&proof.audience) { return Outcome::InvalidProofChain(()); @@ -219,7 +218,7 @@ struct InternalSerializer { #[serde(rename = "can")] command: String, #[serde(rename = "args")] - arguments: BTreeMap, + arguments: Arguments, #[serde(rename = "cond")] conditions: Vec, @@ -234,8 +233,7 @@ struct InternalSerializer { expiration: Timestamp, } -impl> From> - for InternalSerializer +impl> From> for InternalSerializer where BTreeMap: From, { @@ -266,19 +264,18 @@ impl TryFrom for InternalSerializer { } } -impl> TryFrom for Payload { +impl> TryFrom for Payload { type Error = (); // FIXME - fn try_from(s: InternalSerializer) -> Result, ()> { + fn try_from(s: InternalSerializer) -> Result, ()> { Ok(Payload { issuer: s.issuer, subject: s.subject, audience: s.audience, - ability_builder: DynJs { + ability_builder: dynamic::Dynamic { cmd: s.command, args: s.arguments, - serialize_nonce: todo!(), }, conditions: s .conditions @@ -300,8 +297,8 @@ impl> TryFrom for Payload> From> for InternalSerializer { - fn from(p: Payload) -> Self { +impl> From> for InternalSerializer { + fn from(p: Payload) -> Self { InternalSerializer { issuer: p.issuer, subject: p.subject, diff --git a/src/delegation/traits.rs b/src/delegation/traits.rs new file mode 100644 index 00000000..b442734a --- /dev/null +++ b/src/delegation/traits.rs @@ -0,0 +1,5 @@ +use crate::{ability::arguments::Arguments, did::Did, nonce::Nonce, task, task::Task}; + +pub trait Delegatable: Sized { + type Builder: TryInto + From + Into; +} diff --git a/src/invocation.rs b/src/invocation.rs index 33842a80..8ac4e9eb 100644 --- a/src/invocation.rs +++ b/src/invocation.rs @@ -1,4 +1,5 @@ pub mod payload; +pub mod resolvable; use crate::signature; diff --git a/src/invocation/payload.rs b/src/invocation/payload.rs index fae88bf4..9ff818b1 100644 --- a/src/invocation/payload.rs +++ b/src/invocation/payload.rs @@ -1,5 +1,6 @@ +use super::resolvable::Resolvable; use crate::{ - ability::traits::{Command, DynJs, Resolvable}, + ability::{arguments::Arguments, command::Command, dynamic}, capsule::Capsule, did::Did, nonce::Nonce, @@ -9,13 +10,15 @@ use libipld_core::{cid::Cid, ipld::Ipld, serde as ipld_serde}; use serde::{Deserialize, Serialize, Serializer}; use std::{collections::BTreeMap, fmt::Debug}; +// FIXME this version should not be resolvable... +// FIXME ...or at least have two versions via abstraction #[derive(Debug, Clone, PartialEq)] pub struct Payload { pub issuer: Did, pub subject: Did, pub audience: Option, - pub ability: T::Awaiting, + pub ability: T::Promised, pub proofs: Vec, pub cause: Option, @@ -93,7 +96,7 @@ struct InternalSerializer { #[serde(rename = "do")] command: String, #[serde(rename = "args")] - arguments: BTreeMap, + arguments: Arguments, #[serde(rename = "prf")] proofs: Vec, @@ -111,24 +114,15 @@ struct InternalSerializer { expiration: Timestamp, } -impl From> for InternalSerializer -where - BTreeMap: From, - Ipld: From, -{ +impl From> for InternalSerializer { fn from(payload: Payload) -> Self { - let arguments: BTreeMap = match Ipld::from(payload.ability) { - Ipld::Map(btree) => btree, - _ => panic!("FIXME"), - }; - InternalSerializer { issuer: payload.issuer, subject: payload.subject, audience: payload.audience, command: T::COMMAND.into(), - arguments, + arguments: payload.ability.into(), proofs: payload.proofs, cause: payload.cause, @@ -150,17 +144,16 @@ impl TryFrom for InternalSerializer { } } -impl From for Payload { +impl From for Payload { fn from(s: InternalSerializer) -> Self { Payload { issuer: s.issuer, subject: s.subject, audience: s.audience, - ability: DynJs { + ability: dynamic::Dynamic { cmd: s.command, - args: s.arguments, - serialize_nonce: todo!(), + args: s.arguments.into(), }, proofs: s.proofs, @@ -175,11 +168,9 @@ impl From for Payload { } } -impl TryFrom> for InternalSerializer { - type Error = (); // FIXME - - fn try_from(p: Payload) -> Result { - Ok(InternalSerializer { +impl From> for InternalSerializer { + fn from(p: Payload) -> Self { + InternalSerializer { issuer: p.issuer, subject: p.subject, audience: p.audience, @@ -195,6 +186,6 @@ impl TryFrom> for InternalSerializer { not_before: p.not_before, expiration: p.expiration, - }) + } } } diff --git a/src/invocation/resolvable.rs b/src/invocation/resolvable.rs new file mode 100644 index 00000000..232f2538 --- /dev/null +++ b/src/invocation/resolvable.rs @@ -0,0 +1,5 @@ +use crate::ability::arguments::Arguments; + +pub trait Resolvable: Sized { + type Promised: TryInto + From + Into; +} diff --git a/src/lib.rs b/src/lib.rs index c57d2d70..2eee9e05 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -123,6 +123,7 @@ pub mod promise; pub mod proof; pub mod receipt; pub mod signature; +pub mod task; /// The empty fact #[derive(Debug, Clone, Default, Serialize, Deserialize)] diff --git a/src/promise.rs b/src/promise.rs index af1bd93e..54465191 100644 --- a/src/promise.rs +++ b/src/promise.rs @@ -1,3 +1,4 @@ +use crate::ability::arguments::Arguments; use cid::Cid; use libipld_core::{ipld::Ipld, serde as ipld_serde}; use serde_derive::{Deserialize, Serialize}; @@ -5,48 +6,93 @@ use std::fmt::Debug; #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] #[serde(untagged)] -pub enum Deferrable { +pub enum Promise { Resolved(T), - Await(Promise), + Waiting(Selector), } -impl Deferrable { +impl Promise { + pub fn map(self, f: F) -> Promise + where + F: FnOnce(T) -> U, + { + match self { + Promise::Resolved(t) => Promise::Resolved(f(t)), + Promise::Waiting(selector) => Promise::Waiting(selector), + } + } +} + +impl> From> for Arguments { + fn from(promise: Promise) -> Self { + match promise { + Promise::Resolved(t) => t.into(), + Promise::Waiting(selector) => selector.into(), + } + } +} + +impl Promise { pub fn try_extract(self) -> Result { match self { - Deferrable::Resolved(t) => Ok(t), - Deferrable::Await(promise) => Err(Deferrable::Await(promise)), + Promise::Resolved(t) => Ok(t), + Promise::Waiting(promise) => Err(Promise::Waiting(promise)), } } } -impl From for Deferrable { +impl From for Promise { fn from(t: T) -> Self { - Deferrable::Resolved(t) + Promise::Resolved(t) + } +} + +impl From> for Ipld +where + T: Into, +{ + fn from(promise: Promise) -> Self { + match promise { + Promise::Resolved(t) => t.into(), + Promise::Waiting(selector) => selector.into(), + } } } /// A [`Promise`] is a way to defer the presence of a value to the result of some [`Invocation`]. #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] #[serde(untagged, deny_unknown_fields)] // FIXME check that this is right, also -pub enum Promise { - PromiseAny { +pub enum Selector { + Any { #[serde(rename = "ucan/*")] // FIXME test to make sure that this is right? any: Cid, }, - PromiseOk { + Ok { #[serde(rename = "ucan/ok")] ok: Cid, }, - PromiseErr { + Err { #[serde(rename = "ucan/err")] err: Cid, }, } -impl TryFrom for Promise { +impl From for Ipld { + fn from(selector: Selector) -> Self { + selector.into() + } +} + +impl TryFrom for Selector { type Error = (); fn try_from(ipld: Ipld) -> Result { ipld_serde::from_ipld(ipld).map_err(|_| ()) } } + +impl From for Arguments { + fn from(selector: Selector) -> Self { + todo!() // Arguments(selector.to_string()) + } +} diff --git a/src/proof/checkable.rs b/src/proof/checkable.rs index 0a55ee19..f8a7cd62 100644 --- a/src/proof/checkable.rs +++ b/src/proof/checkable.rs @@ -1,5 +1,5 @@ use super::{internal::Checker, prove::Prove, same::CheckSame}; pub trait Checkable: CheckSame { - type Hierarchy: Checker + Prove; + type Hierarchy: Checker + CheckSame + Prove; } diff --git a/src/receipt.rs b/src/receipt.rs index 8f921123..5e70bd54 100644 --- a/src/receipt.rs +++ b/src/receipt.rs @@ -1,55 +1,51 @@ -use crate::{ - ability::traits::{Command, Delegatable}, - signature, -}; +use crate::signature; use libipld_core::ipld::Ipld; +use payload::Payload; use std::{collections::BTreeMap, fmt::Debug}; pub mod payload; -use payload::Payload; +pub mod runnable; -pub type Receipt = signature::Envelope>; - -// FIXME show piping ability +pub type Receipt = signature::Envelope>; // FIXME #[derive(Debug, Clone, PartialEq)] pub struct ProxyExecute { - pub command: String, + pub cmd: String, pub args: BTreeMap, } -impl Delegatable for ProxyExecute { - type Builder = ProxyExecuteBuilder; -} - -// FIXME hmmm -#[derive(Debug, Clone, PartialEq)] -pub struct ProxyExecuteBuilder { - pub command: Option, - pub args: BTreeMap, -} - -impl Command for ProxyExecute { - const COMMAND: &'static str = "ucan/proxy"; -} - -impl From for ProxyExecuteBuilder { - fn from(proxy: ProxyExecute) -> Self { - ProxyExecuteBuilder { - command: Some(ProxyExecute::COMMAND.into()), - args: proxy.args.clone(), - } - } -} - -impl TryFrom for ProxyExecute { - type Error = (); // FIXME - - fn try_from(ProxyExecuteBuilder { command, args }: ProxyExecuteBuilder) -> Result { - match command { - None => Err(()), - Some(command) => Ok(Self { command, args }), - } - } -} +// impl Delegatable for ProxyExecute { +// type Builder = ProxyExecuteBuilder; +// } +// +// // FIXME hmmm +// #[derive(Debug, Clone, PartialEq)] +// pub struct ProxyExecuteBuilder { +// pub command: Option, +// pub args: BTreeMap, +// } +// +// impl Command for ProxyExecute { +// const COMMAND: &'static str = "ucan/proxy"; +// } +// +// impl From for ProxyExecuteBuilder { +// fn from(proxy: ProxyExecute) -> Self { +// ProxyExecuteBuilder { +// command: Some(ProxyExecute::COMMAND.into()), +// args: proxy.args.clone(), +// } +// } +// } +// +// impl TryFrom for ProxyExecute { +// type Error = (); // FIXME +// +// fn try_from(ProxyExecuteBuilder { command, args }: ProxyExecuteBuilder) -> Result { +// match command { +// None => Err(()), +// Some(command) => Ok(Self { command, args }), +// } +// } +// } diff --git a/src/receipt/payload.rs b/src/receipt/payload.rs index ca496986..25a7033b 100644 --- a/src/receipt/payload.rs +++ b/src/receipt/payload.rs @@ -1,4 +1,5 @@ -use crate::{ability::traits::Runnable, capsule::Capsule, did::Did, nonce::Nonce, time::Timestamp}; +use super::runnable::Runnable; +use crate::{capsule::Capsule, did::Did, nonce::Nonce, time::Timestamp}; use libipld_core::{cid::Cid, ipld::Ipld, serde as ipld_serde}; use serde::{de::DeserializeOwned, Deserialize, Serialize, Serializer}; use std::{collections::BTreeMap, fmt::Debug}; @@ -6,12 +7,12 @@ use std::{collections::BTreeMap, fmt::Debug}; #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] pub struct Payload where - T::Output: Serialize + DeserializeOwned, + T::Success: Serialize + DeserializeOwned, { pub issuer: Did, pub ran: Cid, - pub out: Result>, + pub out: Result>, pub next: Vec, pub proofs: Vec, @@ -23,14 +24,14 @@ where impl Capsule for Payload where - for<'de> T::Output: Serialize + Deserialize<'de>, + for<'de> T::Success: Serialize + Deserialize<'de>, { const TAG: &'static str = "ucan/r/1.0.0-rc.1"; // FIXME extract out version } impl TryFrom for Payload where - for<'de> T::Output: Serialize + Deserialize<'de>, + for<'de> T::Success: Serialize + Deserialize<'de>, { type Error = (); // FIXME @@ -41,7 +42,7 @@ where impl From> for Ipld where - for<'de> T::Output: Serialize + Deserialize<'de>, + for<'de> T::Success: Serialize + Deserialize<'de>, { fn from(payload: Payload) -> Self { payload.into() diff --git a/src/receipt/runnable.rs b/src/receipt/runnable.rs new file mode 100644 index 00000000..1fa9f00a --- /dev/null +++ b/src/receipt/runnable.rs @@ -0,0 +1,15 @@ +use crate::{did::Did, nonce::Nonce, task, task::Task}; + +pub trait Runnable { + type Success; + + fn to_task(&self, subject: Did, nonce: Nonce) -> Task; + + fn to_task_id(&self, subject: Did, nonce: Nonce) -> task::Id { + task::Id { + cid: self.to_task(subject, nonce).into(), + } + } + + // fn lookup(id: TaskId>) -> Result; +} diff --git a/src/task.rs b/src/task.rs new file mode 100644 index 00000000..e79d072d --- /dev/null +++ b/src/task.rs @@ -0,0 +1,97 @@ +use crate::{did::Did, nonce::Nonce}; +use libipld_cbor::DagCborCodec; +use libipld_core::{ + cid::{Cid, CidGeneric}, + codec::Encode, + error::SerdeError, + ipld::Ipld, + multihash::{Code::Sha2_256, MultihashDigest}, + serde as ipld_serde, +}; +use serde_derive::{Deserialize, Serialize}; +use std::{collections::BTreeMap, fmt::Debug}; + +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub struct Task { + #[serde(default, skip_serializing_if = "Option::is_none")] + pub sub: Option, // Is this optional? May as well make it so for now! + #[serde(default, skip_serializing_if = "Option::is_none")] + pub nonce: Option, + + pub cmd: String, + pub args: BTreeMap, +} + +impl From for Id { + fn from(task: Task) -> Id { + Id { + cid: Cid::from(task), + } + } +} + +impl TryFrom for Task { + type Error = SerdeError; + + fn try_from(ipld: Ipld) -> Result { + ipld_serde::from_ipld(ipld) + } +} + +impl From for Ipld { + fn from(task: Task) -> Self { + task.into() + } +} + +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub struct Id { + pub cid: Cid, +} + +impl TryFrom for Id { + type Error = SerdeError; + + fn try_from(ipld: Ipld) -> Result { + ipld_serde::from_ipld(ipld) + } +} + +impl From for Ipld { + fn from(id: Id) -> Self { + id.cid.into() + } +} + +// FIXME move to differet module +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] +#[serde(transparent)] +pub struct DefaultTrue(pub bool); + +impl From for bool { + fn from(def_true: DefaultTrue) -> Self { + def_true.0 + } +} + +impl From for DefaultTrue { + fn from(b: bool) -> Self { + DefaultTrue(b) + } +} + +impl Default for DefaultTrue { + fn default() -> Self { + DefaultTrue(true) + } +} + +impl From for Cid { + fn from(task: Task) -> Cid { + let mut buffer = vec![]; + let ipld: Ipld = task.into(); + ipld.encode(DagCborCodec, &mut buffer) + .expect("DagCborCodec to encode any arbitrary `Ipld`"); + CidGeneric::new_v1(DagCborCodec.into(), Sha2_256.digest(buffer.as_slice())) + } +} From 9c6fb070c7d202ff75c87891a8cad38e98b7f5fb Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Wed, 31 Jan 2024 00:28:34 -0800 Subject: [PATCH 034/188] More cleanup --- src/invocation.rs | 1 + src/invocation/payload.rs | 89 +++++++++++++++++++++++------------- src/invocation/serializer.rs | 1 + 3 files changed, 58 insertions(+), 33 deletions(-) create mode 100644 src/invocation/serializer.rs diff --git a/src/invocation.rs b/src/invocation.rs index 8ac4e9eb..bb914f29 100644 --- a/src/invocation.rs +++ b/src/invocation.rs @@ -1,5 +1,6 @@ pub mod payload; pub mod resolvable; +mod serializer; use crate::signature; diff --git a/src/invocation/payload.rs b/src/invocation/payload.rs index 9ff818b1..715fc14e 100644 --- a/src/invocation/payload.rs +++ b/src/invocation/payload.rs @@ -6,19 +6,19 @@ use crate::{ nonce::Nonce, time::Timestamp, }; -use libipld_core::{cid::Cid, ipld::Ipld, serde as ipld_serde}; +use libipld_core::{cid::Cid, error::SerdeError, ipld::Ipld, serde as ipld_serde}; use serde::{Deserialize, Serialize, Serializer}; use std::{collections::BTreeMap, fmt::Debug}; // FIXME this version should not be resolvable... // FIXME ...or at least have two versions via abstraction #[derive(Debug, Clone, PartialEq)] -pub struct Payload { +pub struct Payload { pub issuer: Did, pub subject: Did, pub audience: Option, - pub ability: T::Promised, + pub ability: T, pub proofs: Vec, pub cause: Option, @@ -29,11 +29,28 @@ pub struct Payload { pub expiration: Timestamp, } -impl Capsule for Payload { +// NOTE This is the version that accepts promises +pub type Unresolved = Payload; +// type Dynamic = Payload; <- ? + +// FIXME parser for both versions +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +#[serde(untagged)] +pub enum MaybeResolved + Into> +where + Payload: From, + Unresolved: From, + T::Promised: Clone + Command + Debug + PartialEq, +{ + Resolved(Payload), + Unresolved(Unresolved), +} + +impl Capsule for Payload { const TAG: &'static str = "ucan/i/1.0.0-rc.1"; } -impl Serialize for Payload +impl Serialize for Payload where Payload: Clone, InternalSerializer: From>, @@ -43,11 +60,11 @@ where S: Serializer, { let s = InternalSerializer::from(self.clone()); - Serialize::serialize(&s, serializer) + serde::Serialize::serialize(&s, serializer) } } -impl<'de, T: Resolvable> Deserialize<'de> for Payload +impl<'de, T> serde::Deserialize<'de> for Payload where Payload: TryFrom, as TryFrom>::Error: Debug, @@ -65,7 +82,7 @@ where } } -impl TryFrom for Payload +impl TryFrom for Payload where Payload: TryFrom, { @@ -77,15 +94,15 @@ where } } -impl From> for Ipld { +impl From> for Ipld { fn from(payload: Payload) -> Self { payload.into() } } -#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)] #[serde(deny_unknown_fields)] -struct InternalSerializer { +pub(super) struct InternalSerializer { #[serde(rename = "iss")] issuer: Did, #[serde(rename = "sub")] @@ -114,33 +131,17 @@ struct InternalSerializer { expiration: Timestamp, } -impl From> for InternalSerializer { - fn from(payload: Payload) -> Self { - InternalSerializer { - issuer: payload.issuer, - subject: payload.subject, - audience: payload.audience, - - command: T::COMMAND.into(), - arguments: payload.ability.into(), - - proofs: payload.proofs, - cause: payload.cause, - metadata: payload.metadata, - - nonce: payload.nonce, - - not_before: payload.not_before, - expiration: payload.expiration, - } +impl From for Ipld { + fn from(serializer: InternalSerializer) -> Self { + serializer.into() } } impl TryFrom for InternalSerializer { - type Error = (); // FIXME + type Error = SerdeError; - fn try_from(ipld: Ipld) -> Result { - ipld_serde::from_ipld(ipld).map_err(|_| ()) + fn try_from(ipld: Ipld) -> Result { + ipld_serde::from_ipld(ipld) } } @@ -189,3 +190,25 @@ impl From> for InternalSerializer { } } } + +impl> From> for InternalSerializer { + fn from(payload: Payload) -> Self { + InternalSerializer { + issuer: payload.issuer, + subject: payload.subject, + audience: payload.audience, + + command: T::COMMAND.into(), + arguments: payload.ability.into(), + + proofs: payload.proofs, + cause: payload.cause, + metadata: payload.metadata, + + nonce: payload.nonce, + + not_before: payload.not_before, + expiration: payload.expiration, + } + } +} diff --git a/src/invocation/serializer.rs b/src/invocation/serializer.rs new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/src/invocation/serializer.rs @@ -0,0 +1 @@ + From fdaae6cb263ac96097148e6b686812ae4593eb9d Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Wed, 31 Jan 2024 14:46:49 -0800 Subject: [PATCH 035/188] I think mainly done moving modules around --- src/ability.rs | 1 + src/ability/arguments.rs | 16 ++++ src/ability/dynamic.rs | 4 +- src/ability/msg/send.rs | 4 +- src/ability/ucan.rs | 82 +++++++++++++++++++ src/condition.rs | 1 - src/condition/common.rs | 1 - src/{delegation.rs => delegate.rs} | 13 +-- src/{delegation => delegate}/condition.rs | 0 .../condition/contains_all.rs | 0 .../condition/contains_any.rs | 0 .../condition/contains_key.rs | 0 .../condition/excludes_all.rs | 0 .../condition/excludes_key.rs | 0 .../condition/matches_regex.rs | 0 .../condition/max_length.rs | 0 .../condition/max_number.rs | 0 .../condition/min_length.rs | 0 .../condition/min_number.rs | 0 .../condition/traits.rs | 0 src/{delegation => delegate}/delegatable.rs | 0 src/{delegation => delegate}/payload.rs | 9 +- src/{delegation => delegate}/traits.rs | 0 src/delegation/delegate.rs | 19 ----- src/invocation/serializer.rs | 1 - src/{invocation.rs => invoke.rs} | 7 +- src/{invocation => invoke}/payload.rs | 0 src/{invocation => invoke}/resolvable.rs | 0 src/invoke/serializer.rs | 0 src/ipld.rs | 10 +-- src/lib.rs | 6 +- src/promise.rs | 2 + src/receipt.rs | 51 ------------ src/respond.rs | 9 ++ src/{receipt => respond}/payload.rs | 12 +-- .../runnable.rs => respond/responds.rs} | 2 +- src/signature.rs | 56 ++++++++++++- 37 files changed, 200 insertions(+), 106 deletions(-) create mode 100644 src/ability/ucan.rs delete mode 100644 src/condition.rs delete mode 100644 src/condition/common.rs rename src/{delegation.rs => delegate.rs} (73%) rename src/{delegation => delegate}/condition.rs (100%) rename src/{delegation => delegate}/condition/contains_all.rs (100%) rename src/{delegation => delegate}/condition/contains_any.rs (100%) rename src/{delegation => delegate}/condition/contains_key.rs (100%) rename src/{delegation => delegate}/condition/excludes_all.rs (100%) rename src/{delegation => delegate}/condition/excludes_key.rs (100%) rename src/{delegation => delegate}/condition/matches_regex.rs (100%) rename src/{delegation => delegate}/condition/max_length.rs (100%) rename src/{delegation => delegate}/condition/max_number.rs (100%) rename src/{delegation => delegate}/condition/min_length.rs (100%) rename src/{delegation => delegate}/condition/min_number.rs (100%) rename src/{delegation => delegate}/condition/traits.rs (100%) rename src/{delegation => delegate}/delegatable.rs (100%) rename src/{delegation => delegate}/payload.rs (97%) rename src/{delegation => delegate}/traits.rs (100%) delete mode 100644 src/delegation/delegate.rs delete mode 100644 src/invocation/serializer.rs rename src/{invocation.rs => invoke.rs} (54%) rename src/{invocation => invoke}/payload.rs (100%) rename src/{invocation => invoke}/resolvable.rs (100%) create mode 100644 src/invoke/serializer.rs delete mode 100644 src/receipt.rs create mode 100644 src/respond.rs rename src/{receipt => respond}/payload.rs (79%) rename src/{receipt/runnable.rs => respond/responds.rs} (94%) diff --git a/src/ability.rs b/src/ability.rs index 788870b2..48abc10d 100644 --- a/src/ability.rs +++ b/src/ability.rs @@ -1,6 +1,7 @@ // FIXME feature flag each? pub mod crud; pub mod msg; +pub mod ucan; pub mod wasm; pub mod arguments; diff --git a/src/ability/arguments.rs b/src/ability/arguments.rs index 67b86d54..67d6baf6 100644 --- a/src/ability/arguments.rs +++ b/src/ability/arguments.rs @@ -11,6 +11,22 @@ impl Arguments { } } +impl Arguments { + pub fn insert(&mut self, key: String, value: Ipld) -> Option { + self.0.insert(key, value) + } + + pub fn get(&self, key: &str) -> Option<&Ipld> { + self.0.get(key) + } +} + +impl Default for Arguments { + fn default() -> Self { + Arguments(BTreeMap::new()) + } +} + impl TryFrom for Arguments { type Error = SerdeError; diff --git a/src/ability/dynamic.rs b/src/ability/dynamic.rs index cb4e5129..197cf8f2 100644 --- a/src/ability/dynamic.rs +++ b/src/ability/dynamic.rs @@ -1,9 +1,7 @@ //! This module is for dynamic abilities, especially for FFI and Wasm support use super::{arguments::Arguments, command::ToCommand}; -use crate::{ - delegation::delegatable::Delegatable, invocation::resolvable::Resolvable, promise::Promise, -}; +use crate::{delegate::Delegatable, invoke::Resolvable, promise::Promise}; use serde_derive::{Deserialize, Serialize}; use std::fmt::Debug; diff --git a/src/ability/msg/send.rs b/src/ability/msg/send.rs index 2cd3293d..f0cc21d3 100644 --- a/src/ability/msg/send.rs +++ b/src/ability/msg/send.rs @@ -1,7 +1,7 @@ use crate::{ ability::{arguments::Arguments, command::Command}, - delegation::delegatable::Delegatable, - invocation::resolvable::Resolvable, + delegate::Delegatable, + invoke::Resolvable, promise::Promise, proof::{checkable::Checkable, parentful::Parentful, parents::CheckParents, same::CheckSame}, }; diff --git a/src/ability/ucan.rs b/src/ability/ucan.rs new file mode 100644 index 00000000..ba8b19b6 --- /dev/null +++ b/src/ability/ucan.rs @@ -0,0 +1,82 @@ +use super::arguments::Arguments; +use crate::{ability::command::Command, delegate::Delegatable, promise::Promise}; +use libipld_core::ipld::Ipld; +use serde::{Deserialize, Serialize}; +use std::fmt::Debug; + +// NOTE This one is primarily for enabling delegated recipets + +// FIXME +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +struct Generic { + pub cmd: String, + pub args: Args, // FIXME Does this have specific fields? +} + +pub type Resolved = Generic; +pub type Builder = Generic>; +pub type Promised = Generic>; + +impl Command for Generic { + const COMMAND: &'static str = "ucan/proxy"; +} + +impl Delegatable for Resolved { + type Builder = Builder; +} + +impl From for Builder { + fn from(resolved: Resolved) -> Builder { + Builder { + cmd: resolved.cmd, + args: Some(resolved.args), + } + } +} + +impl TryFrom for Resolved { + type Error = (); // FIXME + + fn try_from(b: Builder) -> Result { + Ok(Resolved { + cmd: b.cmd, + args: b.args.ok_or(())?, + }) + } +} + +impl From for Arguments { + fn from(b: Builder) -> Arguments { + let mut args = b.args.unwrap_or_default(); + args.insert("cmd".into(), Ipld::String(b.cmd)); + args + } +} + +// // FIXME hmmm +// #[derive(Debug, Clone, PartialEq)] +// pub struct ProxyExecuteBuilder { +// pub command: Option, +// pub args: BTreeMap, +// } +// +// +// impl From for ProxyExecuteBuilder { +// fn from(proxy: ProxyExecute) -> Self { +// ProxyExecuteBuilder { +// command: Some(ProxyExecute::COMMAND.into()), +// args: proxy.args.clone(), +// } +// } +// } +// +// impl TryFrom for ProxyExecute { +// type Error = (); // FIXME +// +// fn try_from(ProxyExecuteBuilder { command, args }: ProxyExecuteBuilder) -> Result { +// match command { +// None => Err(()), +// Some(command) => Ok(Self { command, args }), +// } +// } +// } diff --git a/src/condition.rs b/src/condition.rs deleted file mode 100644 index 8b137891..00000000 --- a/src/condition.rs +++ /dev/null @@ -1 +0,0 @@ - diff --git a/src/condition/common.rs b/src/condition/common.rs deleted file mode 100644 index 8b137891..00000000 --- a/src/condition/common.rs +++ /dev/null @@ -1 +0,0 @@ - diff --git a/src/delegation.rs b/src/delegate.rs similarity index 73% rename from src/delegation.rs rename to src/delegate.rs index 431531d1..86ebbedd 100644 --- a/src/delegation.rs +++ b/src/delegate.rs @@ -1,14 +1,15 @@ // FIXME rename delegate? -pub mod condition; -pub mod delegatable; -pub mod delegate; -pub mod payload; +mod condition; +mod delegatable; +mod payload; -use crate::signature; -pub use delegate::Delegate; +pub use condition::traits::Condition; +pub use delegatable::Delegatable; pub use payload::Payload; +use crate::signature; + /// A [`Delegation`] is a signed delegation [`Payload`] /// /// A [`Payload`] on its own is not a valid [`Delegation`], as it must be signed by the issuer. diff --git a/src/delegation/condition.rs b/src/delegate/condition.rs similarity index 100% rename from src/delegation/condition.rs rename to src/delegate/condition.rs diff --git a/src/delegation/condition/contains_all.rs b/src/delegate/condition/contains_all.rs similarity index 100% rename from src/delegation/condition/contains_all.rs rename to src/delegate/condition/contains_all.rs diff --git a/src/delegation/condition/contains_any.rs b/src/delegate/condition/contains_any.rs similarity index 100% rename from src/delegation/condition/contains_any.rs rename to src/delegate/condition/contains_any.rs diff --git a/src/delegation/condition/contains_key.rs b/src/delegate/condition/contains_key.rs similarity index 100% rename from src/delegation/condition/contains_key.rs rename to src/delegate/condition/contains_key.rs diff --git a/src/delegation/condition/excludes_all.rs b/src/delegate/condition/excludes_all.rs similarity index 100% rename from src/delegation/condition/excludes_all.rs rename to src/delegate/condition/excludes_all.rs diff --git a/src/delegation/condition/excludes_key.rs b/src/delegate/condition/excludes_key.rs similarity index 100% rename from src/delegation/condition/excludes_key.rs rename to src/delegate/condition/excludes_key.rs diff --git a/src/delegation/condition/matches_regex.rs b/src/delegate/condition/matches_regex.rs similarity index 100% rename from src/delegation/condition/matches_regex.rs rename to src/delegate/condition/matches_regex.rs diff --git a/src/delegation/condition/max_length.rs b/src/delegate/condition/max_length.rs similarity index 100% rename from src/delegation/condition/max_length.rs rename to src/delegate/condition/max_length.rs diff --git a/src/delegation/condition/max_number.rs b/src/delegate/condition/max_number.rs similarity index 100% rename from src/delegation/condition/max_number.rs rename to src/delegate/condition/max_number.rs diff --git a/src/delegation/condition/min_length.rs b/src/delegate/condition/min_length.rs similarity index 100% rename from src/delegation/condition/min_length.rs rename to src/delegate/condition/min_length.rs diff --git a/src/delegation/condition/min_number.rs b/src/delegate/condition/min_number.rs similarity index 100% rename from src/delegation/condition/min_number.rs rename to src/delegate/condition/min_number.rs diff --git a/src/delegation/condition/traits.rs b/src/delegate/condition/traits.rs similarity index 100% rename from src/delegation/condition/traits.rs rename to src/delegate/condition/traits.rs diff --git a/src/delegation/delegatable.rs b/src/delegate/delegatable.rs similarity index 100% rename from src/delegation/delegatable.rs rename to src/delegate/delegatable.rs diff --git a/src/delegation/payload.rs b/src/delegate/payload.rs similarity index 97% rename from src/delegation/payload.rs rename to src/delegate/payload.rs index 527246f8..f6997ec6 100644 --- a/src/delegation/payload.rs +++ b/src/delegate/payload.rs @@ -3,7 +3,8 @@ use crate::{ ability::{arguments::Arguments, command::Command, dynamic}, capsule::Capsule, did::Did, - invocation::{payload as invocation, resolvable::Resolvable}, + invoke, + invoke::Resolvable, nonce::Nonce, proof::{ checkable::Checkable, @@ -89,14 +90,14 @@ impl From> for Ipld { impl<'a, T: Delegatable + Resolvable + Checkable + Clone, C: Condition> Payload { pub fn check( - invoked: &'a invocation::Payload, // FIXME promisory version + invoked: &'a invoke::Payload, // FIXME promisory version proofs: Vec>, now: SystemTime, ) -> Result<(), ()> where - invocation::Payload: Clone, + invoke::Payload: Clone, U::Builder: Clone + Into, - T::Hierarchy: From>, + T::Hierarchy: From>, { let start: Acc<'a, T> = Acc { issuer: &invoked.issuer, diff --git a/src/delegation/traits.rs b/src/delegate/traits.rs similarity index 100% rename from src/delegation/traits.rs rename to src/delegate/traits.rs diff --git a/src/delegation/delegate.rs b/src/delegation/delegate.rs deleted file mode 100644 index 0046484a..00000000 --- a/src/delegation/delegate.rs +++ /dev/null @@ -1,19 +0,0 @@ -use libipld_core::{ipld::Ipld, serde as ipld_serde}; -use serde_derive::{Deserialize, Serialize}; -use std::fmt::Debug; - -#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)] -#[serde(untagged)] -pub enum Delegate { - #[serde(rename = "ucan/*")] - Any, - Specific(T), -} - -impl + serde::de::DeserializeOwned> TryFrom for Delegate { - type Error = (); // FIXME - - fn try_from(ipld: Ipld) -> Result { - ipld_serde::from_ipld(ipld).map_err(|_| ()) - } -} diff --git a/src/invocation/serializer.rs b/src/invocation/serializer.rs deleted file mode 100644 index 8b137891..00000000 --- a/src/invocation/serializer.rs +++ /dev/null @@ -1 +0,0 @@ - diff --git a/src/invocation.rs b/src/invoke.rs similarity index 54% rename from src/invocation.rs rename to src/invoke.rs index bb914f29..83575db1 100644 --- a/src/invocation.rs +++ b/src/invoke.rs @@ -1,7 +1,10 @@ -pub mod payload; -pub mod resolvable; +mod payload; +mod resolvable; mod serializer; +pub use payload::Payload; +pub use resolvable::Resolvable; + use crate::signature; pub type Invocation = signature::Envelope>; diff --git a/src/invocation/payload.rs b/src/invoke/payload.rs similarity index 100% rename from src/invocation/payload.rs rename to src/invoke/payload.rs diff --git a/src/invocation/resolvable.rs b/src/invoke/resolvable.rs similarity index 100% rename from src/invocation/resolvable.rs rename to src/invoke/resolvable.rs diff --git a/src/invoke/serializer.rs b/src/invoke/serializer.rs new file mode 100644 index 00000000..e69de29b diff --git a/src/ipld.rs b/src/ipld.rs index 64a8ea5a..a820acaf 100644 --- a/src/ipld.rs +++ b/src/ipld.rs @@ -3,23 +3,23 @@ use libipld_core::ipld::Ipld; #[cfg(target_arch = "wasm32")] use wasm_bindgen::JsValue; -pub struct WrappedIpld(pub Ipld); +pub struct Newtype(pub Ipld); -impl From for WrappedIpld { +impl From for Newtype { fn from(ipld: Ipld) -> Self { Self(ipld) } } -impl From for Ipld { - fn from(wrapped: WrappedIpld) -> Self { +impl From for Ipld { + fn from(wrapped: Newtype) -> Self { wrapped.0 } } // TODO testme #[cfg(target_arch = "wasm32")] -impl From for JsValue { +impl From for JsValue { fn from(wrapped: WrappedIpld) -> Self { match wrapped.0 { Ipld::Null => JsValue::Null, diff --git a/src/lib.rs b/src/lib.rs index 2eee9e05..2c922a09 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -114,14 +114,14 @@ use std::fmt::Debug; pub mod ability; pub mod capsule; -pub mod delegation; -pub mod invocation; +pub mod delegate; +pub mod invoke; pub mod ipld; pub mod nonce; pub mod number; pub mod promise; pub mod proof; -pub mod receipt; +pub mod respond; pub mod signature; pub mod task; diff --git a/src/promise.rs b/src/promise.rs index 54465191..f3189f1d 100644 --- a/src/promise.rs +++ b/src/promise.rs @@ -4,6 +4,8 @@ use libipld_core::{ipld::Ipld, serde as ipld_serde}; use serde_derive::{Deserialize, Serialize}; use std::fmt::Debug; +// FIXME move under invoke? + #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] #[serde(untagged)] pub enum Promise { diff --git a/src/receipt.rs b/src/receipt.rs deleted file mode 100644 index 5e70bd54..00000000 --- a/src/receipt.rs +++ /dev/null @@ -1,51 +0,0 @@ -use crate::signature; -use libipld_core::ipld::Ipld; -use payload::Payload; -use std::{collections::BTreeMap, fmt::Debug}; - -pub mod payload; -pub mod runnable; - -pub type Receipt = signature::Envelope>; - -// FIXME -#[derive(Debug, Clone, PartialEq)] -pub struct ProxyExecute { - pub cmd: String, - pub args: BTreeMap, -} - -// impl Delegatable for ProxyExecute { -// type Builder = ProxyExecuteBuilder; -// } -// -// // FIXME hmmm -// #[derive(Debug, Clone, PartialEq)] -// pub struct ProxyExecuteBuilder { -// pub command: Option, -// pub args: BTreeMap, -// } -// -// impl Command for ProxyExecute { -// const COMMAND: &'static str = "ucan/proxy"; -// } -// -// impl From for ProxyExecuteBuilder { -// fn from(proxy: ProxyExecute) -> Self { -// ProxyExecuteBuilder { -// command: Some(ProxyExecute::COMMAND.into()), -// args: proxy.args.clone(), -// } -// } -// } -// -// impl TryFrom for ProxyExecute { -// type Error = (); // FIXME -// -// fn try_from(ProxyExecuteBuilder { command, args }: ProxyExecuteBuilder) -> Result { -// match command { -// None => Err(()), -// Some(command) => Ok(Self { command, args }), -// } -// } -// } diff --git a/src/respond.rs b/src/respond.rs new file mode 100644 index 00000000..2cd81284 --- /dev/null +++ b/src/respond.rs @@ -0,0 +1,9 @@ +mod payload; +mod responds; + +pub use payload::Payload; +pub use responds::Responds; + +use crate::signature; + +pub type Receipt = signature::Envelope>; diff --git a/src/receipt/payload.rs b/src/respond/payload.rs similarity index 79% rename from src/receipt/payload.rs rename to src/respond/payload.rs index 25a7033b..b6374f26 100644 --- a/src/receipt/payload.rs +++ b/src/respond/payload.rs @@ -1,11 +1,11 @@ -use super::runnable::Runnable; +use super::responds::Responds; use crate::{capsule::Capsule, did::Did, nonce::Nonce, time::Timestamp}; use libipld_core::{cid::Cid, ipld::Ipld, serde as ipld_serde}; -use serde::{de::DeserializeOwned, Deserialize, Serialize, Serializer}; +use serde::{de::DeserializeOwned, Deserialize, Serialize}; use std::{collections::BTreeMap, fmt::Debug}; #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] -pub struct Payload +pub struct Payload where T::Success: Serialize + DeserializeOwned, { @@ -22,14 +22,14 @@ where pub issued_at: Option, } -impl Capsule for Payload +impl Capsule for Payload where for<'de> T::Success: Serialize + Deserialize<'de>, { const TAG: &'static str = "ucan/r/1.0.0-rc.1"; // FIXME extract out version } -impl TryFrom for Payload +impl TryFrom for Payload where for<'de> T::Success: Serialize + Deserialize<'de>, { @@ -40,7 +40,7 @@ where } } -impl From> for Ipld +impl From> for Ipld where for<'de> T::Success: Serialize + Deserialize<'de>, { diff --git a/src/receipt/runnable.rs b/src/respond/responds.rs similarity index 94% rename from src/receipt/runnable.rs rename to src/respond/responds.rs index 1fa9f00a..4bd6dfee 100644 --- a/src/receipt/runnable.rs +++ b/src/respond/responds.rs @@ -1,6 +1,6 @@ use crate::{did::Did, nonce::Nonce, task, task::Task}; -pub trait Runnable { +pub trait Responds { type Success; fn to_task(&self, subject: Did, nonce: Nonce) -> Task; diff --git a/src/signature.rs b/src/signature.rs index 12ceda4d..392a9f9a 100644 --- a/src/signature.rs +++ b/src/signature.rs @@ -1,5 +1,6 @@ use crate::capsule::Capsule; use libipld_core::ipld::Ipld; +use serde::{Deserialize, Serialize}; use std::collections::BTreeMap; #[derive(Debug, Clone, PartialEq)] @@ -9,7 +10,8 @@ pub struct Envelope { } // FIXME consider kicking Batch down the road for spec reasons? -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +#[serde(untagged)] pub enum Signature { One(Vec), Batch { @@ -18,6 +20,58 @@ pub enum Signature { }, } +// #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +// #[serde(transparent)] +// pub struct StaticVec { +// pub slice: Box<[T]>, +// } +// +// impl From> for StaticVec { +// fn from(vec: Vec) -> Self { +// Self { +// slice: vec.into_boxed_slice(), +// } +// } +// } +// +// impl From> for Vec { +// fn from(vec: StaticVec) -> Vec { +// vec.slice.into() +// } +// } +// +// #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +// #[serde(transparent)] +// pub struct StaticString { +// string: Box, +// } +// +// impl From for StaticString { +// fn from(string: String) -> Self { +// Self { +// string: string.into_boxed_str(), +// } +// } +// } +// +// impl<'a> From<&'a str> for StaticString { +// fn from(s: &'a str) -> Self { +// Self { string: s.into() } +// } +// } +// +// impl<'a> From<&'a StaticString> for &'a str { +// fn from(s: &'a StaticString) -> &'a str { +// &s.string +// } +// } +// +// impl From for String { +// fn from(s: StaticString) -> String { +// s.string.into() +// } +// } + impl From<&Signature> for Ipld { fn from(sig: &Signature) -> Self { match sig { From 8ff556cf516a333c8cc0fd7336e08b81da9fb4ab Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Wed, 31 Jan 2024 18:28:10 -0800 Subject: [PATCH 036/188] Serde sometimes, I tell ya --- src/ability/dynamic.rs | 7 +- src/ability/msg/send.rs | 4 +- src/ability/ucan.rs | 4 +- src/{delegate.rs => delegation.rs} | 9 +- src/{delegate => delegation}/condition.rs | 0 .../condition/contains_all.rs | 0 .../condition/contains_any.rs | 0 .../condition/contains_key.rs | 0 .../condition/excludes_all.rs | 0 .../condition/excludes_key.rs | 0 .../condition/matches_regex.rs | 0 .../condition/max_length.rs | 0 .../condition/max_number.rs | 0 .../condition/min_length.rs | 0 .../condition/min_number.rs | 0 .../condition/traits.rs | 0 src/{delegate => delegation}/delegatable.rs | 0 src/{delegate => delegation}/payload.rs | 10 +- src/{delegate => delegation}/traits.rs | 0 src/{invoke.rs => invocation.rs} | 2 +- src/{invoke => invocation}/payload.rs | 2 +- src/{invoke => invocation}/resolvable.rs | 0 src/{invoke => invocation}/serializer.rs | 0 src/lib.rs | 197 +++++++----------- src/promise.rs | 2 +- src/{respond.rs => receipt.rs} | 8 +- src/receipt/payload.rs | 135 ++++++++++++ src/receipt/receipt.rs | 4 + src/{respond => receipt}/responds.rs | 2 - src/receipt/store.rs | 16 ++ src/respond/payload.rs | 50 ----- 31 files changed, 255 insertions(+), 197 deletions(-) rename src/{delegate.rs => delegation.rs} (70%) rename src/{delegate => delegation}/condition.rs (100%) rename src/{delegate => delegation}/condition/contains_all.rs (100%) rename src/{delegate => delegation}/condition/contains_any.rs (100%) rename src/{delegate => delegation}/condition/contains_key.rs (100%) rename src/{delegate => delegation}/condition/excludes_all.rs (100%) rename src/{delegate => delegation}/condition/excludes_key.rs (100%) rename src/{delegate => delegation}/condition/matches_regex.rs (100%) rename src/{delegate => delegation}/condition/max_length.rs (100%) rename src/{delegate => delegation}/condition/max_number.rs (100%) rename src/{delegate => delegation}/condition/min_length.rs (100%) rename src/{delegate => delegation}/condition/min_number.rs (100%) rename src/{delegate => delegation}/condition/traits.rs (100%) rename src/{delegate => delegation}/delegatable.rs (100%) rename src/{delegate => delegation}/payload.rs (97%) rename src/{delegate => delegation}/traits.rs (100%) rename src/{invoke.rs => invocation.rs} (80%) rename src/{invoke => invocation}/payload.rs (99%) rename src/{invoke => invocation}/resolvable.rs (100%) rename src/{invoke => invocation}/serializer.rs (100%) rename src/{respond.rs => receipt.rs} (50%) create mode 100644 src/receipt/payload.rs create mode 100644 src/receipt/receipt.rs rename src/{respond => receipt}/responds.rs (83%) create mode 100644 src/receipt/store.rs delete mode 100644 src/respond/payload.rs diff --git a/src/ability/dynamic.rs b/src/ability/dynamic.rs index 197cf8f2..4cc68940 100644 --- a/src/ability/dynamic.rs +++ b/src/ability/dynamic.rs @@ -1,11 +1,11 @@ //! This module is for dynamic abilities, especially for FFI and Wasm support use super::{arguments::Arguments, command::ToCommand}; -use crate::{delegate::Delegatable, invoke::Resolvable, promise::Promise}; +use crate::{delegation::Delegatable, invocation::Resolvable, promise::Promise}; use serde_derive::{Deserialize, Serialize}; use std::fmt::Debug; -// FIXME move module? +// FIXME move commented-out module? // use js_sys; // use wasm_bindgen::prelude::*; // type JsDynamic = Dynamic<&'a js_sys::Function>; @@ -19,6 +19,9 @@ use std::fmt::Debug; // #[serde(skip_serializing)] // pub serialize_nonce: DefaultTrue, +// NOTE the lack of checking functions! +// This is meant to be embedded inside of structs that have e.g. FFI bindings to +// a validation function, such as a &js_sys::Function, Ruby magnus::function!, etc etc #[derive(Debug, Clone, PartialEq, Deserialize, Serialize)] pub struct Generic { pub cmd: String, diff --git a/src/ability/msg/send.rs b/src/ability/msg/send.rs index f0cc21d3..1d7c81a1 100644 --- a/src/ability/msg/send.rs +++ b/src/ability/msg/send.rs @@ -1,7 +1,7 @@ use crate::{ ability::{arguments::Arguments, command::Command}, - delegate::Delegatable, - invoke::Resolvable, + delegation::Delegatable, + invocation::Resolvable, promise::Promise, proof::{checkable::Checkable, parentful::Parentful, parents::CheckParents, same::CheckSame}, }; diff --git a/src/ability/ucan.rs b/src/ability/ucan.rs index ba8b19b6..e2ee2867 100644 --- a/src/ability/ucan.rs +++ b/src/ability/ucan.rs @@ -1,10 +1,10 @@ use super::arguments::Arguments; -use crate::{ability::command::Command, delegate::Delegatable, promise::Promise}; +use crate::{ability::command::Command, delegation::Delegatable, promise::Promise}; use libipld_core::ipld::Ipld; use serde::{Deserialize, Serialize}; use std::fmt::Debug; -// NOTE This one is primarily for enabling delegated recipets +// NOTE This one is primarily for enabling delegationd recipets // FIXME #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] diff --git a/src/delegate.rs b/src/delegation.rs similarity index 70% rename from src/delegate.rs rename to src/delegation.rs index 86ebbedd..52c79c97 100644 --- a/src/delegate.rs +++ b/src/delegation.rs @@ -1,10 +1,9 @@ -// FIXME rename delegate? - mod condition; mod delegatable; mod payload; -pub use condition::traits::Condition; +pub use condition::*; + pub use delegatable::Delegatable; pub use payload::Payload; @@ -16,4 +15,6 @@ use crate::signature; /// /// # Examples /// FIXME -pub type Delegation = signature::Envelope>; +pub type Delegation = signature::Envelope>; + +// FIXME add a store with delegation indexing diff --git a/src/delegate/condition.rs b/src/delegation/condition.rs similarity index 100% rename from src/delegate/condition.rs rename to src/delegation/condition.rs diff --git a/src/delegate/condition/contains_all.rs b/src/delegation/condition/contains_all.rs similarity index 100% rename from src/delegate/condition/contains_all.rs rename to src/delegation/condition/contains_all.rs diff --git a/src/delegate/condition/contains_any.rs b/src/delegation/condition/contains_any.rs similarity index 100% rename from src/delegate/condition/contains_any.rs rename to src/delegation/condition/contains_any.rs diff --git a/src/delegate/condition/contains_key.rs b/src/delegation/condition/contains_key.rs similarity index 100% rename from src/delegate/condition/contains_key.rs rename to src/delegation/condition/contains_key.rs diff --git a/src/delegate/condition/excludes_all.rs b/src/delegation/condition/excludes_all.rs similarity index 100% rename from src/delegate/condition/excludes_all.rs rename to src/delegation/condition/excludes_all.rs diff --git a/src/delegate/condition/excludes_key.rs b/src/delegation/condition/excludes_key.rs similarity index 100% rename from src/delegate/condition/excludes_key.rs rename to src/delegation/condition/excludes_key.rs diff --git a/src/delegate/condition/matches_regex.rs b/src/delegation/condition/matches_regex.rs similarity index 100% rename from src/delegate/condition/matches_regex.rs rename to src/delegation/condition/matches_regex.rs diff --git a/src/delegate/condition/max_length.rs b/src/delegation/condition/max_length.rs similarity index 100% rename from src/delegate/condition/max_length.rs rename to src/delegation/condition/max_length.rs diff --git a/src/delegate/condition/max_number.rs b/src/delegation/condition/max_number.rs similarity index 100% rename from src/delegate/condition/max_number.rs rename to src/delegation/condition/max_number.rs diff --git a/src/delegate/condition/min_length.rs b/src/delegation/condition/min_length.rs similarity index 100% rename from src/delegate/condition/min_length.rs rename to src/delegation/condition/min_length.rs diff --git a/src/delegate/condition/min_number.rs b/src/delegation/condition/min_number.rs similarity index 100% rename from src/delegate/condition/min_number.rs rename to src/delegation/condition/min_number.rs diff --git a/src/delegate/condition/traits.rs b/src/delegation/condition/traits.rs similarity index 100% rename from src/delegate/condition/traits.rs rename to src/delegation/condition/traits.rs diff --git a/src/delegate/delegatable.rs b/src/delegation/delegatable.rs similarity index 100% rename from src/delegate/delegatable.rs rename to src/delegation/delegatable.rs diff --git a/src/delegate/payload.rs b/src/delegation/payload.rs similarity index 97% rename from src/delegate/payload.rs rename to src/delegation/payload.rs index f6997ec6..2f5ad289 100644 --- a/src/delegate/payload.rs +++ b/src/delegation/payload.rs @@ -3,8 +3,8 @@ use crate::{ ability::{arguments::Arguments, command::Command, dynamic}, capsule::Capsule, did::Did, - invoke, - invoke::Resolvable, + invocation, + invocation::Resolvable, nonce::Nonce, proof::{ checkable::Checkable, @@ -90,14 +90,14 @@ impl From> for Ipld { impl<'a, T: Delegatable + Resolvable + Checkable + Clone, C: Condition> Payload { pub fn check( - invoked: &'a invoke::Payload, // FIXME promisory version + invoked: &'a invocation::Payload, // FIXME promisory version proofs: Vec>, now: SystemTime, ) -> Result<(), ()> where - invoke::Payload: Clone, + invocation::Payload: Clone, U::Builder: Clone + Into, - T::Hierarchy: From>, + T::Hierarchy: From>, { let start: Acc<'a, T> = Acc { issuer: &invoked.issuer, diff --git a/src/delegate/traits.rs b/src/delegation/traits.rs similarity index 100% rename from src/delegate/traits.rs rename to src/delegation/traits.rs diff --git a/src/invoke.rs b/src/invocation.rs similarity index 80% rename from src/invoke.rs rename to src/invocation.rs index 83575db1..5cb2b95e 100644 --- a/src/invoke.rs +++ b/src/invocation.rs @@ -2,7 +2,7 @@ mod payload; mod resolvable; mod serializer; -pub use payload::Payload; +pub use payload::{Payload, Unresolved}; pub use resolvable::Resolvable; use crate::signature; diff --git a/src/invoke/payload.rs b/src/invocation/payload.rs similarity index 99% rename from src/invoke/payload.rs rename to src/invocation/payload.rs index 715fc14e..46fc3d41 100644 --- a/src/invoke/payload.rs +++ b/src/invocation/payload.rs @@ -102,7 +102,7 @@ impl From> for Ipld { #[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)] #[serde(deny_unknown_fields)] -pub(super) struct InternalSerializer { +struct InternalSerializer { #[serde(rename = "iss")] issuer: Did, #[serde(rename = "sub")] diff --git a/src/invoke/resolvable.rs b/src/invocation/resolvable.rs similarity index 100% rename from src/invoke/resolvable.rs rename to src/invocation/resolvable.rs diff --git a/src/invoke/serializer.rs b/src/invocation/serializer.rs similarity index 100% rename from src/invoke/serializer.rs rename to src/invocation/serializer.rs diff --git a/src/lib.rs b/src/lib.rs index 2c922a09..f8eb43d3 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -4,40 +4,38 @@ //! ucan -use std::str::FromStr; - -use cid::{multihash, Cid}; -use serde::{de, Deserialize, Deserializer, Serialize}; - -pub mod builder; -pub mod capability; -pub mod crypto; -pub mod did; -pub mod did_verifier; -pub mod error; -pub mod plugins; -pub mod semantics; -pub mod store; -pub mod time; -pub mod ucan; - -#[cfg(target_arch = "wasm32")] -mod wasm; - -#[cfg(target_arch = "wasm32")] -pub use wasm::*; - -#[doc(hidden)] -#[cfg(not(target_arch = "wasm32"))] -pub use linkme; - -/// The default multihash algorithm used for UCANs -pub const DEFAULT_MULTIHASH: multihash::Code = multihash::Code::Sha2_256; - -/// A decentralized identifier. -pub type Did = String; - -use std::fmt::Debug; +// use std::str::FromStr; +// +// use cid::{multihash, Cid}; +// use serde::{de, Deserialize, Deserializer, Serialize}; +// +// pub mod builder; +// pub mod capability; +// pub mod crypto; +// pub mod did_verifier; +// pub mod error; +// pub mod plugins; +// pub mod semantics; +// pub mod store; +// pub mod ucan; +// +// #[cfg(target_arch = "wasm32")] +// mod wasm; +// +// #[cfg(target_arch = "wasm32")] +// pub use wasm::*; +// +// #[doc(hidden)] +// #[cfg(not(target_arch = "wasm32"))] +// pub use linkme; +// +// /// The default multihash algorithm used for UCANs +// pub const DEFAULT_MULTIHASH: multihash::Code = multihash::Code::Sha2_256; +// +// /// A decentralized identifier. +// pub type Did = String; +// +// use std::fmt::Debug; // FIXME concrete abilitiy types in addition to promised version @@ -62,103 +60,56 @@ use std::fmt::Debug; // } // } -// FIXME Remove -// pub trait Prove { -// type Witness; -// // FIXME make sure that passing the top-level item through and not checking each -// // item in the chain against the next one is correct in the 1.0 semantics -// fn prove<'a>(&'a self, proof: &'a T) -> &Self::Witness; -// } +pub mod ability; +pub mod capsule; +pub mod delegation; +pub mod did; +pub mod invocation; +pub mod ipld; +pub mod nonce; +pub mod number; +pub mod promise; +pub mod proof; +pub mod receipt; +pub mod signature; +pub mod task; +pub mod time; -// impl Prove for T { -// type Witness = T; +// FIXME consider a fact-system +// /// The empty fact +// #[derive(Debug, Clone, Default, Serialize, Deserialize)] +// pub struct EmptyFact {} // -// fn prove<'a>(&'a self, proof: &'a DelegateAny) -> &Self::Witness { -// self -// } -// } +// /// The default fact +// pub type DefaultFact = EmptyFact; // -// impl> TryProve for T { -// type Error = Void; -// type Proven = T; +// /// A newtype around Cid that (de)serializes as a string +// #[derive(Debug, Clone)] +// pub struct CidString(pub(crate) Cid); // -// fn try_prove<'a>(&'a self, proof: &'a T) -> Result<&'a T, Void> { -// Ok(proof) +// impl Serialize for CidString { +// fn serialize(&self, serializer: S) -> Result +// where +// S: serde::Serializer, +// { +// serializer.serialize_str(self.0.to_string().as_str()) // } // } - -// FIXME lives etirely in bindgen -// https://rustwasm.github.io/docs/wasm-bindgen/contributing/design/importing-js-struct.html -// pub struct DynamicJs { -// pub command: Box, -// pub args: BTreeMap, Ipld>, -// } -// -// impl TryProve for DynamicJs { -// type Error = (); -// type Proven = DynamicJs; // -// fn try_prove<'a>(&'a self, proof: &'a DynamicJs) -> Result<&'a DynamicJs, ()> { +// impl<'de> Deserialize<'de> for CidString { +// fn deserialize(deserializer: D) -> Result +// where +// D: Deserializer<'de>, +// { +// let s = String::deserialize(deserializer)?; // -// } -// } - -// impl ProveDelegaton for DynamicJs { -// type Error = anyhow::Error; -// -// fn prove(&self, proof: &DynamicJs) -> Result { -// todo!() +// Cid::from_str(&s) +// .map(CidString) +// .map_err(|e| de::Error::custom(format!("invalid CID: {}", e))) // } // } // - -pub mod ability; -pub mod capsule; -pub mod delegate; -pub mod invoke; -pub mod ipld; -pub mod nonce; -pub mod number; -pub mod promise; -pub mod proof; -pub mod respond; -pub mod signature; -pub mod task; - -/// The empty fact -#[derive(Debug, Clone, Default, Serialize, Deserialize)] -pub struct EmptyFact {} - -/// The default fact -pub type DefaultFact = EmptyFact; - -/// A newtype around Cid that (de)serializes as a string -#[derive(Debug, Clone)] -pub struct CidString(pub(crate) Cid); - -impl Serialize for CidString { - fn serialize(&self, serializer: S) -> Result - where - S: serde::Serializer, - { - serializer.serialize_str(self.0.to_string().as_str()) - } -} - -impl<'de> Deserialize<'de> for CidString { - fn deserialize(deserializer: D) -> Result - where - D: Deserializer<'de>, - { - let s = String::deserialize(deserializer)?; - - Cid::from_str(&s) - .map(CidString) - .map_err(|e| de::Error::custom(format!("invalid CID: {}", e))) - } -} - -/// Test utilities. -#[cfg(any(test, feature = "test_utils"))] -#[cfg_attr(docsrs, doc(cfg(feature = "test_utils")))] -pub mod test_utils; +// /// Test utilities. +// #[cfg(any(test, feature = "test_utils"))] +// #[cfg_attr(docsrs, doc(cfg(feature = "test_utils")))] +// pub mod test_utils; diff --git a/src/promise.rs b/src/promise.rs index f3189f1d..e6f63510 100644 --- a/src/promise.rs +++ b/src/promise.rs @@ -4,7 +4,7 @@ use libipld_core::{ipld::Ipld, serde as ipld_serde}; use serde_derive::{Deserialize, Serialize}; use std::fmt::Debug; -// FIXME move under invoke? +// FIXME move under invocation? #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] #[serde(untagged)] diff --git a/src/respond.rs b/src/receipt.rs similarity index 50% rename from src/respond.rs rename to src/receipt.rs index 2cd81284..1b5284cf 100644 --- a/src/respond.rs +++ b/src/receipt.rs @@ -1,9 +1,9 @@ mod payload; +mod receipt; mod responds; +mod store; pub use payload::Payload; +pub use receipt::Receipt; pub use responds::Responds; - -use crate::signature; - -pub type Receipt = signature::Envelope>; +pub use store::Store; diff --git a/src/receipt/payload.rs b/src/receipt/payload.rs new file mode 100644 index 00000000..f428da9e --- /dev/null +++ b/src/receipt/payload.rs @@ -0,0 +1,135 @@ +use super::responds::Responds; +use crate::{ + ability::arguments::Arguments, capsule::Capsule, did::Did, nonce::Nonce, time::Timestamp, +}; +use libipld_core::{cid::Cid, ipld::Ipld, serde as ipld_serde}; +use serde::{de::DeserializeOwned, Deserialize, Serialize, Serializer}; +use std::{collections::BTreeMap, fmt::Debug}; + +// FIXME serialize/deseialize split out for when the T has implementations + +#[derive(Debug, Clone, PartialEq)] +pub struct Payload { + pub issuer: Did, + + pub ran: Cid, + pub out: Result, + pub next: Vec, // FIXME rename here or in spec? + + pub proofs: Vec, + pub metadata: BTreeMap, + + pub nonce: Nonce, + pub issued_at: Option, +} + +impl Capsule for Payload { + const TAG: &'static str = "ucan/r/1.0.0-rc.1"; // FIXME extract out version +} + +impl Serialize for Payload +where + Payload: Clone, + T::Success: Serialize + DeserializeOwned, +{ + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + let s = InternalSerializer::from(self.clone()); // FIXME kill that clone with tons of refs? + serde::Serialize::serialize(&s, serializer) + } +} + +impl<'de, T: Responds + Deserialize<'de>> Deserialize<'de> for Payload +where + as TryFrom>>::Error: Debug, + T::Success: Serialize + DeserializeOwned, +{ + fn deserialize(d: D) -> Result + where + D: serde::Deserializer<'de>, + { + match InternalSerializer::deserialize(d) { + Err(e) => Err(e), + Ok(s) => Ok(s.into()), + } + } +} + +impl TryFrom for Payload +where + T::Success: Serialize + DeserializeOwned, +{ + type Error = (); // FIXME serde error + + fn try_from(ipld: Ipld) -> Result { + let s: InternalSerializer = ipld_serde::from_ipld(ipld).map_err(|_| ())?; + Ok(s.into()) + } +} + +impl From> for Ipld { + fn from(payload: Payload) -> Self { + payload.into() + } +} + +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +#[serde(deny_unknown_fields)] +struct InternalSerializer +where + T::Success: Serialize + DeserializeOwned, +{ + #[serde(rename = "iss")] + issuer: Did, + + ran: Cid, + out: Result, + next: Vec, // FIXME rename here or in spec? + + #[serde(rename = "prf")] + proofs: Vec, + #[serde(rename = "meta")] + metadata: BTreeMap, + + nonce: Nonce, + #[serde(rename = "iat")] + issued_at: Option, +} + +impl From> for Payload +where + T::Success: Serialize + DeserializeOwned, +{ + fn from(s: InternalSerializer) -> Self { + Payload { + issuer: s.issuer, + ran: s.ran, + out: s.out, + next: s.next, + proofs: s.proofs, + metadata: s.metadata, + nonce: s.nonce, + issued_at: s.issued_at, + } + } +} + +impl From> for InternalSerializer +where + T::Success: Serialize + DeserializeOwned, +{ + fn from(s: Payload) -> Self { + InternalSerializer { + issuer: s.issuer, + ran: s.ran, + out: s.out, + next: s.next, + proofs: s.proofs, + metadata: s.metadata, + nonce: s.nonce, + issued_at: s.issued_at, + } + } +} diff --git a/src/receipt/receipt.rs b/src/receipt/receipt.rs new file mode 100644 index 00000000..dfd1db01 --- /dev/null +++ b/src/receipt/receipt.rs @@ -0,0 +1,4 @@ +use super::payload::Payload; +use crate::signature; + +pub type Receipt = signature::Envelope>; diff --git a/src/respond/responds.rs b/src/receipt/responds.rs similarity index 83% rename from src/respond/responds.rs rename to src/receipt/responds.rs index 4bd6dfee..e35f5d60 100644 --- a/src/respond/responds.rs +++ b/src/receipt/responds.rs @@ -10,6 +10,4 @@ pub trait Responds { cid: self.to_task(subject, nonce).into(), } } - - // fn lookup(id: TaskId>) -> Result; } diff --git a/src/receipt/store.rs b/src/receipt/store.rs new file mode 100644 index 00000000..d7316753 --- /dev/null +++ b/src/receipt/store.rs @@ -0,0 +1,16 @@ +use super::{Receipt, Responds}; +use crate::task; +use libipld_core::ipld::Ipld; + +pub trait Store { + type Abilities: Responds; + type Error; + + fn get(id: task::Id) -> Result, Self::Error> + where + ::Success: TryFrom; + + fn put_keyed(id: task::Id, receipt: Receipt) -> Result<(), Self::Error> + where + ::Success: Into; +} diff --git a/src/respond/payload.rs b/src/respond/payload.rs deleted file mode 100644 index b6374f26..00000000 --- a/src/respond/payload.rs +++ /dev/null @@ -1,50 +0,0 @@ -use super::responds::Responds; -use crate::{capsule::Capsule, did::Did, nonce::Nonce, time::Timestamp}; -use libipld_core::{cid::Cid, ipld::Ipld, serde as ipld_serde}; -use serde::{de::DeserializeOwned, Deserialize, Serialize}; -use std::{collections::BTreeMap, fmt::Debug}; - -#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] -pub struct Payload -where - T::Success: Serialize + DeserializeOwned, -{ - pub issuer: Did, - - pub ran: Cid, - pub out: Result>, - pub next: Vec, - - pub proofs: Vec, - pub metadata: BTreeMap, - - pub nonce: Nonce, - pub issued_at: Option, -} - -impl Capsule for Payload -where - for<'de> T::Success: Serialize + Deserialize<'de>, -{ - const TAG: &'static str = "ucan/r/1.0.0-rc.1"; // FIXME extract out version -} - -impl TryFrom for Payload -where - for<'de> T::Success: Serialize + Deserialize<'de>, -{ - type Error = (); // FIXME - - fn try_from(ipld: Ipld) -> Result { - ipld_serde::from_ipld(ipld).map_err(|_| ()) - } -} - -impl From> for Ipld -where - for<'de> T::Success: Serialize + Deserialize<'de>, -{ - fn from(payload: Payload) -> Self { - payload.into() - } -} From 406b72dd19d05ffbed14172f12bf0a484db26605 Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Thu, 1 Feb 2024 00:52:33 -0800 Subject: [PATCH 037/188] Roughly completed the metadata subsystem --- src/delegation.rs | 2 +- src/delegation/payload.rs | 66 ++++++----- src/lib.rs | 1 + src/metadata.rs | 238 ++++++++++++++++++++++++++++++++++++++ src/promise.rs | 18 ++- src/receipt/payload.rs | 48 +++++--- src/receipt/receipt.rs | 2 +- src/receipt/store.rs | 13 +-- 8 files changed, 334 insertions(+), 54 deletions(-) create mode 100644 src/metadata.rs diff --git a/src/delegation.rs b/src/delegation.rs index 52c79c97..945f90f7 100644 --- a/src/delegation.rs +++ b/src/delegation.rs @@ -15,6 +15,6 @@ use crate::signature; /// /// # Examples /// FIXME -pub type Delegation = signature::Envelope>; +pub type Delegation = signature::Envelope>; // FIXME add a store with delegation indexing diff --git a/src/delegation/payload.rs b/src/delegation/payload.rs index 2f5ad289..394adc42 100644 --- a/src/delegation/payload.rs +++ b/src/delegation/payload.rs @@ -5,6 +5,8 @@ use crate::{ did::Did, invocation, invocation::Resolvable, + metadata as meta, + metadata::{Mergable, Metadata}, nonce::Nonce, proof::{ checkable::Checkable, @@ -19,7 +21,7 @@ use std::{collections::BTreeMap, fmt::Debug}; use web_time::SystemTime; #[derive(Debug, Clone, PartialEq)] -pub struct Payload { +pub struct Payload { pub issuer: Did, pub subject: Did, pub audience: Did, @@ -27,21 +29,21 @@ pub struct Payload { pub ability_builder: T::Builder, pub conditions: Vec, - pub metadata: BTreeMap, + pub metadata: Metadata, pub nonce: Nonce, pub expiration: Timestamp, pub not_before: Option, } -impl Capsule for Payload { +impl Capsule for Payload { const TAG: &'static str = "ucan/d/1.0.0-rc.1"; } -impl Serialize for Payload +impl Serialize for Payload where - InternalSerializer: From>, - Payload: Clone, + InternalSerializer: From>, + Payload: Clone, { fn serialize(&self, serializer: S) -> Result where @@ -52,10 +54,11 @@ where } } -impl<'de, T: Delegatable, C: Condition + DeserializeOwned> Deserialize<'de> for Payload +impl<'de, T: Delegatable, C: Condition + DeserializeOwned, E: meta::Entries> Deserialize<'de> + for Payload where - Payload: TryFrom, - as TryFrom>::Error: Debug, + Payload: TryFrom, + as TryFrom>::Error: Debug, { fn deserialize(d: D) -> Result where @@ -70,9 +73,10 @@ where } } -impl TryFrom for Payload +impl TryFrom + for Payload where - Payload: TryFrom, + Payload: TryFrom, { type Error = (); // FIXME @@ -82,16 +86,18 @@ where } } -impl From> for Ipld { - fn from(payload: Payload) -> Self { +impl From> for Ipld { + fn from(payload: Payload) -> Self { payload.into() } } -impl<'a, T: Delegatable + Resolvable + Checkable + Clone, C: Condition> Payload { +impl<'a, T: Delegatable + Resolvable + Checkable + Clone, C: Condition, E: meta::Entries> + Payload +{ pub fn check( invoked: &'a invocation::Payload, // FIXME promisory version - proofs: Vec>, + proofs: Vec>, now: SystemTime, ) -> Result<(), ()> where @@ -147,9 +153,9 @@ struct Acc<'a, T: Checkable> { } // FIXME this should move to Delegatable -fn step<'a, T: Checkable, U: Delegatable, C: Condition>( +fn step<'a, T: Checkable, U: Delegatable, C: Condition, E: meta::Entries>( prev: &Acc<'a, T>, - proof: &Payload, + proof: &Payload, invoked_ipld: &Ipld, now: SystemTime, ) -> Outcome<(), (), ()> @@ -234,11 +240,13 @@ struct InternalSerializer { expiration: Timestamp, } -impl> From> for InternalSerializer +impl, E: meta::Entries + Clone> + From> for InternalSerializer where BTreeMap: From, + Metadata: Mergable, { - fn from(payload: Payload) -> Self { + fn from(payload: Payload) -> Self { InternalSerializer { issuer: payload.issuer, subject: payload.subject, @@ -248,7 +256,7 @@ where arguments: payload.ability_builder.into(), conditions: payload.conditions.into_iter().map(|c| c.into()).collect(), - metadata: payload.metadata, + metadata: payload.metadata.merge(), nonce: payload.nonce, not_before: payload.not_before, @@ -265,10 +273,12 @@ impl TryFrom for InternalSerializer { } } -impl> TryFrom for Payload { +impl, E: meta::Entries + Clone> TryFrom + for Payload +{ type Error = (); // FIXME - fn try_from(s: InternalSerializer) -> Result, ()> { + fn try_from(s: InternalSerializer) -> Result, ()> { Ok(Payload { issuer: s.issuer, subject: s.subject, @@ -289,7 +299,7 @@ impl> TryFrom for Payload> TryFrom for Payload> From> for InternalSerializer { - fn from(p: Payload) -> Self { +impl, E: meta::Entries + Clone> From> + for InternalSerializer +where + Metadata: Mergable, +{ + fn from(p: Payload) -> Self { InternalSerializer { issuer: p.issuer, subject: p.subject, @@ -309,7 +323,7 @@ impl> From> for InternalS arguments: p.ability_builder.args, conditions: p.conditions.into_iter().map(|c| c.into()).collect(), - metadata: p.metadata, + metadata: p.metadata.merge(), nonce: p.nonce, not_before: p.not_before, diff --git a/src/lib.rs b/src/lib.rs index f8eb43d3..0c24a0e6 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -66,6 +66,7 @@ pub mod delegation; pub mod did; pub mod invocation; pub mod ipld; +pub mod metadata; pub mod nonce; pub mod number; pub mod promise; diff --git a/src/metadata.rs b/src/metadata.rs new file mode 100644 index 00000000..da14890e --- /dev/null +++ b/src/metadata.rs @@ -0,0 +1,238 @@ +use libipld_core::{error::SerdeError, ipld::Ipld, serde as ipld_serde}; +use serde::{Deserialize, Serialize, Serializer}; +use std::collections::BTreeMap; + +// FIXME rename modeule to metadata + +pub trait Entry { + const KEY: &'static str; +} + +pub trait Entries: TryFrom + Into { + const KEYS: &'static [&'static str]; +} + +pub trait Meta {} +impl Meta for T {} + +pub enum Empty {} +impl Meta for Empty {} + +// NOTE no Serde +#[derive(Debug, Clone, PartialEq)] +pub struct Metadata { + known: BTreeMap, + unknown: BTreeMap, +} + +impl From> for Ipld { + fn from(meta: Metadata) -> Ipld { + Ipld::Map(meta.unknown) + } +} + +impl TryFrom for Metadata { + type Error = (); + + fn try_from(ipld: Ipld) -> Result, Self::Error> { + match ipld { + Ipld::Map(unknown) => Ok(Metadata { + known: BTreeMap::new(), + unknown, + }), + _ => Err(()), + } + } +} + +impl Serialize for Metadata { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + let s = Ipld::from(*self); // FIXME kill that clone with tons of refs? + serde::Serialize::serialize(&s, serializer) + } +} + +impl<'de, T: Entries + Clone> Deserialize<'de> for Metadata { + fn deserialize(d: D) -> Result + where + D: serde::Deserializer<'de>, + { + Ipld::deserialize(d).map(Metadata::from) + } +} + +impl TryFrom> for Ipld { + type Error = String; // FIXME + + fn try_from(meta: Metadata) -> Result { + let mut btree = meta.unknown.clone(); + + for (k, v) in meta.known { + if let Some(_) = meta.unknown.get(&k) { + return Err(k); + } + + btree.insert(k, v.into()); + } + + Ok(Ipld::Map(btree)) + } +} + +impl TryFrom for Metadata { + type Error = (); // FIXME + + fn try_from(ipld: Ipld) -> Result { + match ipld { + Ipld::Map(btree) => { + let mut known = BTreeMap::new(); + let mut unknown = BTreeMap::new(); + + for (k, v) in btree { + if T::KEYS.contains(&k.as_str()) { + if let Ok(fact) = T::try_from(v.clone()) { + known.insert(k, fact); + } else { + unknown.insert(k, v); + } + } else { + unknown.insert(k, v); + } + } + + Ok(Self { known, unknown }) + } + _ => Err(()), + } + } +} + +impl Metadata { + pub fn new( + known: BTreeMap, + unknown: BTreeMap, + ) -> Result { + for k in known.keys() { + if unknown.contains_key(k) { + return Err(k.into()); + } + } + + Ok(Self { known, unknown }) + } + + pub fn known<'a>(&'a self) -> &'a BTreeMap { + &self.known + } + + pub fn unknown<'a>(&'a self) -> &'a BTreeMap { + &self.unknown + } + + // FIXME types + pub fn insert_known<'a>(&'a mut self, key: String, value: T) -> Result<(), Option> { + if let Some(_) = self.unknown.get(&key) { + return Err(None); + } + + match self.known.insert(key, value) { + Some(v) => Err(Some(v)), + _ => Ok(()), + } + } + + pub fn insert_unknown<'a>(&'a mut self, key: String, value: Ipld) -> Result<(), Option> { + if let Some(_) = self.known.get(&key) { + return Err(None); + } + + match self.unknown.insert(key, value) { + Some(v) => Err(Some(v)), + _ => Ok(()), + } + } +} + +pub trait Mergable { + fn merge(&self) -> BTreeMap; + fn extract(merged: BTreeMap) -> Self; +} + +impl Mergable for Metadata { + fn merge(&self) -> BTreeMap { + self.unknown + } + + // FIXME better error + fn extract(unknown: BTreeMap) -> Self { + Metadata { + known: BTreeMap::new(), + unknown, + } + } +} + +impl Mergable for Metadata { + fn merge(&self) -> BTreeMap { + let mut meta = self.unknown().clone(); + + for (k, v) in self.known() { + meta.insert(k.clone(), v.clone().into()); + } + + meta + } + + // FIXME better error + fn extract(merged: BTreeMap) -> Self { + let mut known = BTreeMap::new(); + let mut unknown = BTreeMap::new(); + + for (k, v) in merged { + if let Ok(entry) = v.clone().try_into() { + known.insert(k, entry); + } else { + unknown.insert(k, v); + } + } + + Metadata { known, unknown } + } +} + +impl Default for Metadata { + fn default() -> Self { + Metadata { + known: BTreeMap::new(), + unknown: BTreeMap::new(), + } + } +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(deny_unknown_fields)] +pub struct IpvmConfig { + pub max_retries: u32, + pub workflow_fuel: u32, +} + +impl Entry for IpvmConfig { + const KEY: &'static str = "ipvm/config"; +} + +impl From for Ipld { + fn from(config: IpvmConfig) -> Self { + config.into() + } +} + +impl TryFrom for IpvmConfig { + type Error = SerdeError; + + fn try_from(ipld: Ipld) -> Result { + ipld_serde::from_ipld(ipld) + } +} diff --git a/src/promise.rs b/src/promise.rs index e6f63510..f2920a58 100644 --- a/src/promise.rs +++ b/src/promise.rs @@ -2,7 +2,7 @@ use crate::ability::arguments::Arguments; use cid::Cid; use libipld_core::{ipld::Ipld, serde as ipld_serde}; use serde_derive::{Deserialize, Serialize}; -use std::fmt::Debug; +use std::{collections::BTreeMap, fmt::Debug}; // FIXME move under invocation? @@ -95,6 +95,20 @@ impl TryFrom for Selector { impl From for Arguments { fn from(selector: Selector) -> Self { - todo!() // Arguments(selector.to_string()) + let mut btree = BTreeMap::new(); + + match selector { + Selector::Any { any } => { + btree.insert("ucan/*".into(), any.into()); + } + Selector::Ok { ok } => { + btree.insert("ucan/ok".into(), ok.into()); + } + Selector::Err { err } => { + btree.insert("ucan/err".into(), err.into()); + } + } + + Arguments(btree) } } diff --git a/src/receipt/payload.rs b/src/receipt/payload.rs index f428da9e..0600528d 100644 --- a/src/receipt/payload.rs +++ b/src/receipt/payload.rs @@ -1,6 +1,12 @@ use super::responds::Responds; use crate::{ - ability::arguments::Arguments, capsule::Capsule, did::Did, nonce::Nonce, time::Timestamp, + ability::arguments::Arguments, + capsule::Capsule, + did::Did, + metadata as meta, + metadata::{Mergable, Metadata}, + nonce::Nonce, + time::Timestamp, }; use libipld_core::{cid::Cid, ipld::Ipld, serde as ipld_serde}; use serde::{de::DeserializeOwned, Deserialize, Serialize, Serializer}; @@ -9,7 +15,7 @@ use std::{collections::BTreeMap, fmt::Debug}; // FIXME serialize/deseialize split out for when the T has implementations #[derive(Debug, Clone, PartialEq)] -pub struct Payload { +pub struct Payload { pub issuer: Did, pub ran: Cid, @@ -17,19 +23,19 @@ pub struct Payload { pub next: Vec, // FIXME rename here or in spec? pub proofs: Vec, - pub metadata: BTreeMap, + pub metadata: Metadata, pub nonce: Nonce, pub issued_at: Option, } -impl Capsule for Payload { +impl Capsule for Payload { const TAG: &'static str = "ucan/r/1.0.0-rc.1"; // FIXME extract out version } -impl Serialize for Payload +impl Serialize for Payload where - Payload: Clone, + Payload: Clone, T::Success: Serialize + DeserializeOwned, { fn serialize(&self, serializer: S) -> Result @@ -41,10 +47,14 @@ where } } -impl<'de, T: Responds + Deserialize<'de>> Deserialize<'de> for Payload +impl< + 'de, + T: Responds + Deserialize<'de>, + E: meta::Entries + Clone + DeserializeOwned + Serialize, + > Deserialize<'de> for Payload where - as TryFrom>>::Error: Debug, - T::Success: Serialize + DeserializeOwned, + as TryFrom>>::Error: Debug, + T::Success: DeserializeOwned + Serialize, { fn deserialize(d: D) -> Result where @@ -57,7 +67,8 @@ where } } -impl TryFrom for Payload +impl TryFrom + for Payload where T::Success: Serialize + DeserializeOwned, { @@ -69,8 +80,8 @@ where } } -impl From> for Ipld { - fn from(payload: Payload) -> Self { +impl From> for Ipld { + fn from(payload: Payload) -> Self { payload.into() } } @@ -98,9 +109,11 @@ where issued_at: Option, } -impl From> for Payload +impl From> + for Payload where T::Success: Serialize + DeserializeOwned, + Metadata: Mergable, { fn from(s: InternalSerializer) -> Self { Payload { @@ -109,25 +122,26 @@ where out: s.out, next: s.next, proofs: s.proofs, - metadata: s.metadata, + metadata: Metadata::extract(s.metadata), nonce: s.nonce, issued_at: s.issued_at, } } } -impl From> for InternalSerializer +impl From> for InternalSerializer where T::Success: Serialize + DeserializeOwned, + Metadata: Mergable, { - fn from(s: Payload) -> Self { + fn from(s: Payload) -> Self { InternalSerializer { issuer: s.issuer, ran: s.ran, out: s.out, next: s.next, proofs: s.proofs, - metadata: s.metadata, + metadata: s.metadata.merge(), nonce: s.nonce, issued_at: s.issued_at, } diff --git a/src/receipt/receipt.rs b/src/receipt/receipt.rs index dfd1db01..594bf3a4 100644 --- a/src/receipt/receipt.rs +++ b/src/receipt/receipt.rs @@ -1,4 +1,4 @@ use super::payload::Payload; use crate::signature; -pub type Receipt = signature::Envelope>; +pub type Receipt = signature::Envelope>; diff --git a/src/receipt/store.rs b/src/receipt/store.rs index d7316753..76d5ebc9 100644 --- a/src/receipt/store.rs +++ b/src/receipt/store.rs @@ -1,16 +1,15 @@ use super::{Receipt, Responds}; -use crate::task; +use crate::{metadata, task}; use libipld_core::ipld::Ipld; -pub trait Store { - type Abilities: Responds; +pub trait Store { type Error; - fn get(id: task::Id) -> Result, Self::Error> + fn get(id: task::Id) -> Result, Self::Error> where - ::Success: TryFrom; + ::Success: TryFrom; - fn put_keyed(id: task::Id, receipt: Receipt) -> Result<(), Self::Error> + fn put_keyed(id: task::Id, receipt: Receipt) -> Result<(), Self::Error> where - ::Success: Into; + ::Success: Into; } From 5ec4b9f171d0d0f8545e6f8bada32505ac5d4966 Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Thu, 1 Feb 2024 13:08:55 -0800 Subject: [PATCH 038/188] Save ahead of wasming --- src/metadata.rs | 147 ++++++++++++++++++++++-------------------------- 1 file changed, 66 insertions(+), 81 deletions(-) diff --git a/src/metadata.rs b/src/metadata.rs index da14890e..a16e2f97 100644 --- a/src/metadata.rs +++ b/src/metadata.rs @@ -1,6 +1,6 @@ use libipld_core::{error::SerdeError, ipld::Ipld, serde as ipld_serde}; use serde::{Deserialize, Serialize, Serializer}; -use std::collections::BTreeMap; +use std::{collections::BTreeMap, convert::Infallible}; // FIXME rename modeule to metadata @@ -12,59 +12,72 @@ pub trait Entries: TryFrom + Into { const KEYS: &'static [&'static str]; } -pub trait Meta {} -impl Meta for T {} +pub trait Mergable { + fn merge(self) -> BTreeMap; + fn extract(merged: BTreeMap) -> Self; +} pub enum Empty {} -impl Meta for Empty {} // NOTE no Serde #[derive(Debug, Clone, PartialEq)] -pub struct Metadata { +pub struct Metadata { known: BTreeMap, unknown: BTreeMap, } -impl From> for Ipld { - fn from(meta: Metadata) -> Ipld { - Ipld::Map(meta.unknown) +impl Mergable for Metadata { + fn merge(self) -> BTreeMap { + self.unknown + } + + // FIXME better error + fn extract(unknown: BTreeMap) -> Self { + Metadata { + known: BTreeMap::new(), + unknown, + } } } -impl TryFrom for Metadata { - type Error = (); +impl Mergable for Metadata { + fn merge(self) -> BTreeMap { + let mut meta = self.unknown; - fn try_from(ipld: Ipld) -> Result, Self::Error> { - match ipld { - Ipld::Map(unknown) => Ok(Metadata { - known: BTreeMap::new(), - unknown, - }), - _ => Err(()), + // FIXME kill the clone + for (k, v) in self.known { + meta.insert(k, v.into()); } + + meta } -} -impl Serialize for Metadata { - fn serialize(&self, serializer: S) -> Result - where - S: Serializer, - { - let s = Ipld::from(*self); // FIXME kill that clone with tons of refs? - serde::Serialize::serialize(&s, serializer) + // FIXME better error + fn extract(merged: BTreeMap) -> Self { + let mut known = BTreeMap::new(); + let mut unknown = BTreeMap::new(); + + for (k, v) in merged { + if let Ok(entry) = v.clone().try_into() { + known.insert(k, entry); + } else { + unknown.insert(k, v); + } + } + + Metadata { known, unknown } } } -impl<'de, T: Entries + Clone> Deserialize<'de> for Metadata { - fn deserialize(d: D) -> Result - where - D: serde::Deserializer<'de>, - { - Ipld::deserialize(d).map(Metadata::from) +impl TryFrom> for Ipld { + type Error = Infallible; + + fn try_from(meta: Metadata) -> Result { + Ok(Ipld::Map(meta.merge())) } } -impl TryFrom> for Ipld { +impl TryFrom> for Ipld { type Error = String; // FIXME fn try_from(meta: Metadata) -> Result { @@ -82,6 +95,25 @@ impl TryFrom> for Ipld { } } +impl Serialize for Metadata { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + let s = Ipld::Map((*self).clone().merge()); // FIXME kill that clone with tons of refs? + serde::Serialize::serialize(&s, serializer) + } +} + +impl<'de, T: Entries + Clone> Deserialize<'de> for Metadata { + fn deserialize(d: D) -> Result + where + D: serde::Deserializer<'de>, + { + Ipld::deserialize(d).and_then(|ipld| ipld.try_into().map_err(|_| todo!())) + } +} + impl TryFrom for Metadata { type Error = (); // FIXME @@ -110,7 +142,7 @@ impl TryFrom for Metadata { } } -impl Metadata { +impl Metadata { pub fn new( known: BTreeMap, unknown: BTreeMap, @@ -156,54 +188,7 @@ impl Metadata { } } -pub trait Mergable { - fn merge(&self) -> BTreeMap; - fn extract(merged: BTreeMap) -> Self; -} - -impl Mergable for Metadata { - fn merge(&self) -> BTreeMap { - self.unknown - } - - // FIXME better error - fn extract(unknown: BTreeMap) -> Self { - Metadata { - known: BTreeMap::new(), - unknown, - } - } -} - -impl Mergable for Metadata { - fn merge(&self) -> BTreeMap { - let mut meta = self.unknown().clone(); - - for (k, v) in self.known() { - meta.insert(k.clone(), v.clone().into()); - } - - meta - } - - // FIXME better error - fn extract(merged: BTreeMap) -> Self { - let mut known = BTreeMap::new(); - let mut unknown = BTreeMap::new(); - - for (k, v) in merged { - if let Ok(entry) = v.clone().try_into() { - known.insert(k, entry); - } else { - unknown.insert(k, v); - } - } - - Metadata { known, unknown } - } -} - -impl Default for Metadata { +impl Default for Metadata { fn default() -> Self { Metadata { known: BTreeMap::new(), From d8ebca2e432ae327a06c4e23ce115c0cb6a6d4e1 Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Thu, 1 Feb 2024 18:13:45 -0800 Subject: [PATCH 039/188] Save point before riping out pointers --- Cargo.toml | 22 ++-- src/ability.rs | 4 +- src/ability/arguments.rs | 8 ++ src/ability/command.rs | 4 +- src/ability/crud/destroy.rs | 4 +- src/ability/dynamic.rs | 195 ++++++++++++++++++++++++++++++------ src/ability/internal.rs | 1 - src/delegation/payload.rs | 117 +++++++++++----------- src/invocation/payload.rs | 92 ++++++++--------- src/ipld.rs | 83 ++++++++++++--- src/lib.rs | 1 + src/new_wasm.rs | 7 ++ src/nonce.rs | 18 +++- src/promise.rs | 3 +- src/task.rs | 10 +- src/time.rs | 78 +++++++++++---- 16 files changed, 465 insertions(+), 182 deletions(-) delete mode 100644 src/ability/internal.rs create mode 100644 src/new_wasm.rs diff --git a/Cargo.toml b/Cargo.toml index 39aae0a3..72735375 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -34,7 +34,8 @@ async-signature = "0.4.0" async-trait = "0.1.73" blst = { version = "0.3.11", optional = true, default-features = false } cfg-if = "0.1" -cid = "0.10" +# FIXME attempt to remove +cid = "0.11" did_url = "0.1" downcast-rs = "1.2.0" dyn-clone = "1.0.14" @@ -49,7 +50,6 @@ lazy_static = "1.4.0" libipld-core = { version = "0.16", features = ["serde-codec"] } libipld-cbor = "0.16" multibase = "0.9.1" -multihash = {version = "0.19", features = ["serde"]} p256 = { version = "0.13.2", features = ["ecdsa"], optional = true, default-features = false } p384 = { version = "0.13.0", features = ["ecdsa"], optional = true, default-features = false } p521 = { version = "0.13.0", optional = true, default-features = false } @@ -66,6 +66,7 @@ thiserror = "1.0" tracing = "0.1.40" unsigned-varint = "0.7.2" url = { version = "2.5", features = ["serde"] } +uuid = "1.7" web-time = "0.2.3" [target.'cfg(not(target_arch = "wasm32"))'.dependencies] @@ -74,12 +75,13 @@ linkme = "0.3.15" uuid = { version = "1.7", features = ["v4"] } xid = "1.0" +# FIXME also have a wasi target [target.'cfg(target_arch = "wasm32")'.dependencies] console_error_panic_hook = { version = "0.1" } -getrandom = { version = "*", features = ["js"] } +getrandom = { version = "0.2", features = ["js"] } js-sys = { version = "0.3" } -serde-wasm-bindgen = "0.6.1" -wasm-bindgen = "0.2.87" +serde-wasm-bindgen = "0.6" +wasm-bindgen = "0.2" wasm-bindgen-futures = { version = "0.4" } web-sys = { version = "0.3", features = ["Crypto", "CryptoKey", "CryptoKeyPair", "SubtleCrypto"] } @@ -96,7 +98,15 @@ proptest = { version = "*", default-features = false, features = ["std"] } wasm-bindgen-test = "0.2" [features] -default = ["did-key", "eddsa-verifier", "es256-verifier", "es256k-verifier", "es384-verifier", "ps256-verifier", "rs256-verifier"] +default = [ + "did-key", + "eddsa-verifier", + "es256-verifier", + "es256k-verifier", + "es384-verifier", + "ps256-verifier", + "rs256-verifier", +] test_utils = ["proptest"] did-key = [] eddsa = ["dep:ed25519", "dep:ed25519-dalek"] diff --git a/src/ability.rs b/src/ability.rs index 48abc10d..f0fd7c0c 100644 --- a/src/ability.rs +++ b/src/ability.rs @@ -7,8 +7,8 @@ pub mod wasm; pub mod arguments; pub mod command; -// TODO move to crate::wasm? -// #[cfg(feature = "wasm")] +// // TODO move to crate::wasm? or hide behind feature flag? +#[cfg(target_arch = "wasm32")] pub mod dynamic; // FIXME macro to derive promise versions & delagted builder versions diff --git a/src/ability/arguments.rs b/src/ability/arguments.rs index 67d6baf6..0d02781a 100644 --- a/src/ability/arguments.rs +++ b/src/ability/arguments.rs @@ -9,6 +9,14 @@ impl Arguments { pub fn from_iter(iterable: impl IntoIterator) -> Self { Arguments(iterable.into_iter().collect()) } + + pub fn iter(&self) -> impl Iterator { + self.0.iter() + } + + pub fn into_iter(self) -> impl Iterator { + self.0.into_iter() + } } impl Arguments { diff --git a/src/ability/command.rs b/src/ability/command.rs index f06637fc..bd86bf36 100644 --- a/src/ability/command.rs +++ b/src/ability/command.rs @@ -2,8 +2,8 @@ pub trait Command { const COMMAND: &'static str; } -// NOTE do not export -pub(crate) trait ToCommand { +// NOTE do not export // FIXME move to internal? +pub(super) trait ToCommand { fn to_command(&self) -> String; } diff --git a/src/ability/crud/destroy.rs b/src/ability/crud/destroy.rs index 91ac42fe..4432d2e2 100644 --- a/src/ability/crud/destroy.rs +++ b/src/ability/crud/destroy.rs @@ -51,8 +51,8 @@ impl CheckParents for Destroy { fn check_parents(&self, other: &Self::Parents) -> Result<(), Self::ParentError> { match other { - Mutable::Mutate(mutate) => Ok(()), - Mutable::Any(any) => Ok(()), + Mutable::Mutate(mutate) => Ok(()), // FIXME + Mutable::Any(any) => Ok(()), // FIXME } } } diff --git a/src/ability/dynamic.rs b/src/ability/dynamic.rs index 4cc68940..0b29664a 100644 --- a/src/ability/dynamic.rs +++ b/src/ability/dynamic.rs @@ -1,67 +1,200 @@ //! This module is for dynamic abilities, especially for FFI and Wasm support use super::{arguments::Arguments, command::ToCommand}; -use crate::{delegation::Delegatable, invocation::Resolvable, promise::Promise}; -use serde_derive::{Deserialize, Serialize}; -use std::fmt::Debug; - -// FIXME move commented-out module? -// use js_sys; -// use wasm_bindgen::prelude::*; -// type JsDynamic = Dynamic<&'a js_sys::Function>; -// type JsBuilder = Builder<&'a js_sys::Function>; -// type JsPromised = Promised<&'a js_sys::Function>; -// FIXME move these fiels to a wrapper struct in a different module -// #[serde(skip_serializing)] -// pub chain_validator: Pred, -// #[serde(skip_serializing)] -// pub shape_validator: Pred, -// #[serde(skip_serializing)] -// pub serialize_nonce: DefaultTrue, +use crate::{ + delegation::Delegatable, + invocation::Resolvable, + promise::Promise, + proof::{ + checkable::Checkable, parentful::Parentful, parentless::Parentless, parents::CheckParents, + same::CheckSame, + }, + task::DefaultTrue, +}; +use libipld_core::{error::SerdeError, ipld::Ipld, serde as ipld_serde}; +use serde::{ + de::DeserializeOwned, ser::SerializeMap, Deserialize, Deserializer, Serialize, Serializer, +}; +use std::{collections::BTreeMap, fmt::Debug}; +use wasm_bindgen::prelude::*; // NOTE the lack of checking functions! // This is meant to be embedded inside of structs that have e.g. FFI bindings to // a validation function, such as a &js_sys::Function, Ruby magnus::function!, etc etc -#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)] -pub struct Generic { +#[derive(Clone, PartialEq)] +pub struct Generic { pub cmd: String, pub args: Args, + pub is_nonce_meaningful: DefaultTrue, + + pub same_validator: F, + pub parent_validator: F, // FIXME needs to be a different types, and fall back to Void + pub shape_validator: F, // FIXME needs to be a different type +} + +impl Debug for Generic { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("Generic") + .field("cmd", &self.cmd) + .field("args", &self.args) + .field("is_nonce_meaningful", &self.is_nonce_meaningful) + .finish() + } +} + +pub type Dynamic = Generic; +pub type Promised = Generic, F>; + +impl Serialize for Generic +where + Arguments: From, +{ + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + let mut map = serializer.serialize_map(Some(2))?; + map.serialize_entry("cmd", &self.cmd)?; + map.serialize_entry("args", &Arguments::from(self.args.clone()))?; + map.end() + } } -pub type Dynamic = Generic; -pub type Promised = Generic>; +impl<'de, Args: Deserialize<'de>> Deserialize<'de> for Generic { + fn deserialize(deserializer: D) -> Result, D::Error> + where + D: Deserializer<'de>, + { + // FIXME + todo!() + // let btree = BTreeMap::deserialize(deserializer)?; + // Ok(Generic { + // cmd: btree.get("cmd")?.to_string(), + // args: btree.get("args")?.clone(), + // is_nonce_meaningful: DefaultTrue::default(), + // + // same_validator: (), + // parent_validator: (), + // shape_validator: (), + // }) + } +} -impl ToCommand for Generic { +impl ToCommand for Generic { fn to_command(&self) -> String { self.cmd.clone() } } -impl Delegatable for Dynamic { - type Builder = Dynamic; +impl Delegatable for Dynamic { + type Builder = Dynamic; } -impl Resolvable for Dynamic { - type Promised = Dynamic; +impl Resolvable for Dynamic { + type Promised = Promised; } -impl From for Arguments { - fn from(dynamic: Dynamic) -> Self { - dynamic.args +impl From> for Ipld { + fn from(generic: Generic) -> Self { + generic.into() + } +} + +impl TryFrom for Generic { + type Error = SerdeError; + + fn try_from(ipld: Ipld) -> Result { + ipld_serde::from_ipld(ipld) } } -impl TryFrom for Dynamic { +impl, F> From> for Arguments { + fn from(generic: Generic) -> Self { + generic.args.into() + } +} + +impl TryFrom> for Dynamic { type Error = (); // FIXME - fn try_from(awaiting: Promised) -> Result { + fn try_from(awaiting: Promised) -> Result { if let Promise::Resolved(args) = &awaiting.args { Ok(Dynamic { cmd: awaiting.cmd, args: args.clone(), + + same_validator: awaiting.same_validator, + parent_validator: awaiting.parent_validator, + shape_validator: awaiting.shape_validator, + is_nonce_meaningful: awaiting.is_nonce_meaningful, }) } else { Err(()) } } } + +impl From> for Promised { + fn from(d: Dynamic) -> Self { + Promised { + cmd: d.cmd, + args: Promise::Resolved(d.args), + + same_validator: d.same_validator, + parent_validator: d.parent_validator, + shape_validator: d.shape_validator, + is_nonce_meaningful: d.is_nonce_meaningful, + } + } +} + +impl Checkable for Dynamic +where + F: Fn(&String, &Arguments) -> Result<(), String>, +{ + type Hierarchy = Parentless>; // FIXME I bet we can revover parents +} + +// FIXME Actually, we shoudl go back to wrapping? +// impl CheckSame for Dynamic +// where +// F: Fn(&String, &Arguments) -> Result<(), String>, +// { +// type Error = String; +// +// fn check_same(&self, proof: &Self) -> Result<(), Self::Error> { +// let chain_checker = &self.same_validator; +// let shape_checker = &self.same_validator; +// +// shape_checker(&proof.cmd, &proof.args)?; +// chain_checker(&proof.cmd, &proof.args) +// } +// } + +// #[wasm_bindgen(module = "./ability")] +// extern "C" { +// type JsAbility; +// +// // FIXME wrap in func that checks the jsval or better: converts form Ipld +// #[wasm_bindgen(constructor)] +// fn new(cmd: String, args: BTreeMap) -> JsAbility; +// +// #[wasm_bindgen(method, getter)] +// fn command(this: &JsAbility) -> String; +// +// #[wasm_bindgen(method, getter)] +// fn arguments(this: &JsAbility) -> Arguments; +// +// #[wasm_bindgen(method, getter)] +// fn is_nonce_meaningful(this: &JsAbility) -> bool; +// +// // e.g. reject extra fields +// #[wasm_bindgen(method)] +// fn validate_shape(this: &JsAbility) -> bool; +// +// // FIXME camels to snakes +// #[wasm_bindgen(method)] +// fn check_same(this: &JsAbility, proof: &JsAbility) -> Result<(), String>; +// +// fn check_parents(th.......) +// } diff --git a/src/ability/internal.rs b/src/ability/internal.rs deleted file mode 100644 index 8b137891..00000000 --- a/src/ability/internal.rs +++ /dev/null @@ -1 +0,0 @@ - diff --git a/src/delegation/payload.rs b/src/delegation/payload.rs index 394adc42..2fb55124 100644 --- a/src/delegation/payload.rs +++ b/src/delegation/payload.rs @@ -273,61 +273,62 @@ impl TryFrom for InternalSerializer { } } -impl, E: meta::Entries + Clone> TryFrom - for Payload -{ - type Error = (); // FIXME - - fn try_from(s: InternalSerializer) -> Result, ()> { - Ok(Payload { - issuer: s.issuer, - subject: s.subject, - audience: s.audience, - - ability_builder: dynamic::Dynamic { - cmd: s.command, - args: s.arguments, - }, - conditions: s - .conditions - .iter() - .try_fold(Vec::new(), |mut acc, c| { - C::try_from(c.clone()).map(|x| { - acc.push(x); - acc - }) - }) - .map_err(|_| ())?, // FIXME better error (collect all errors - - metadata: Metadata::extract(s.metadata), - nonce: s.nonce, - - not_before: s.not_before, - expiration: s.expiration, - }) - } -} - -impl, E: meta::Entries + Clone> From> - for InternalSerializer -where - Metadata: Mergable, -{ - fn from(p: Payload) -> Self { - InternalSerializer { - issuer: p.issuer, - subject: p.subject, - audience: p.audience, - - command: p.ability_builder.cmd, - arguments: p.ability_builder.args, - conditions: p.conditions.into_iter().map(|c| c.into()).collect(), - - metadata: p.metadata.merge(), - nonce: p.nonce, - - not_before: p.not_before, - expiration: p.expiration, - } - } -} +// FIXME +// impl, E: meta::Entries + Clone> TryFrom +// for Payload, C, E> +// { +// type Error = (); // FIXME +// +// fn try_from(s: InternalSerializer) -> Result, C, E>, ()> { +// Ok(Payload { +// issuer: s.issuer, +// subject: s.subject, +// audience: s.audience, +// +// ability_builder: dynamic::Dynamic { +// cmd: s.command, +// args: s.arguments, +// }, +// conditions: s +// .conditions +// .iter() +// .try_fold(Vec::new(), |mut acc, c| { +// C::try_from(c.clone()).map(|x| { +// acc.push(x); +// acc +// }) +// }) +// .map_err(|_| ())?, // FIXME better error (collect all errors +// +// metadata: Metadata::extract(s.metadata), +// nonce: s.nonce, +// +// not_before: s.not_before, +// expiration: s.expiration, +// }) +// } +// } +// +// impl, E: meta::Entries + Clone, F> +// From, C, E>> for InternalSerializer +// where +// Metadata: Mergable, +// { +// fn from(p: Payload, C, E>) -> Self { +// InternalSerializer { +// issuer: p.issuer, +// subject: p.subject, +// audience: p.audience, +// +// command: p.ability_builder.cmd, +// arguments: p.ability_builder.args, +// conditions: p.conditions.into_iter().map(|c| c.into()).collect(), +// +// metadata: p.metadata.merge(), +// nonce: p.nonce, +// +// not_before: p.not_before, +// expiration: p.expiration, +// } +// } +// } diff --git a/src/invocation/payload.rs b/src/invocation/payload.rs index 46fc3d41..5e8ea6a8 100644 --- a/src/invocation/payload.rs +++ b/src/invocation/payload.rs @@ -145,51 +145,53 @@ impl TryFrom for InternalSerializer { } } -impl From for Payload { - fn from(s: InternalSerializer) -> Self { - Payload { - issuer: s.issuer, - subject: s.subject, - audience: s.audience, - - ability: dynamic::Dynamic { - cmd: s.command, - args: s.arguments.into(), - }, - - proofs: s.proofs, - cause: s.cause, - metadata: s.metadata, - - nonce: s.nonce, - - not_before: s.not_before, - expiration: s.expiration, - } - } -} - -impl From> for InternalSerializer { - fn from(p: Payload) -> Self { - InternalSerializer { - issuer: p.issuer, - subject: p.subject, - audience: p.audience, - - command: p.ability.cmd, - arguments: p.ability.args, - - proofs: p.proofs, - cause: p.cause, - metadata: p.metadata, - - nonce: p.nonce, - - not_before: p.not_before, - expiration: p.expiration, - } - } -} +// FIXME +// impl From for Payload { +// fn from(s: InternalSerializer) -> Self { +// Payload { +// issuer: s.issuer, +// subject: s.subject, +// audience: s.audience, +// +// ability: dynamic::Dynamic { +// cmd: s.command, +// args: s.arguments.into(), +// }, +// +// proofs: s.proofs, +// cause: s.cause, +// metadata: s.metadata, +// +// nonce: s.nonce, +// +// not_before: s.not_before, +// expiration: s.expiration, +// } +// } +// } + +// FIXME +// impl From> for InternalSerializer { +// fn from(p: Payload) -> Self { +// InternalSerializer { +// issuer: p.issuer, +// subject: p.subject, +// audience: p.audience, +// +// command: p.ability.cmd, +// arguments: p.ability.args, +// +// proofs: p.proofs, +// cause: p.cause, +// metadata: p.metadata, +// +// nonce: p.nonce, +// +// not_before: p.not_before, +// expiration: p.expiration, +// } +// } +// } impl> From> for InternalSerializer { fn from(payload: Payload) -> Self { diff --git a/src/ipld.rs b/src/ipld.rs index a820acaf..2a1d213b 100644 --- a/src/ipld.rs +++ b/src/ipld.rs @@ -1,7 +1,16 @@ use libipld_core::ipld::Ipld; #[cfg(target_arch = "wasm32")] -use wasm_bindgen::JsValue; +use wasm_bindgen::prelude::*; + +#[cfg(target_arch = "wasm32")] +use libipld_core::cid::Cid; + +#[cfg(target_arch = "wasm32")] +use libipld_core::multihash::MultihashGeneric; + +#[cfg(target_arch = "wasm32")] +use js_sys::{Array, Map, Uint8Array}; pub struct Newtype(pub Ipld); @@ -17,32 +26,80 @@ impl From for Ipld { } } +#[cfg(target_arch = "wasm32")] +#[wasm_bindgen] +pub struct CidWrapper { + version_code: u64, + multicodec_code: u64, + multihash_code: u64, + hash_bytes: Vec, +} + +#[cfg(target_arch = "wasm32")] +impl From for Cid { + fn from(wrapper: CidWrapper) -> Self { + Cid::new( + wrapper.version_code.try_into().expect( + "must be a valid Cid::Version because it was deconstructed from a valid one", + ), + wrapper + .multicodec_code + .try_into() + .expect("must be a valid Multicodec because it was deconstructed from a valid one"), + MultihashGeneric::<64>::wrap(wrapper.multihash_code, wrapper.hash_bytes.as_ref()) + .expect("a valid Multihash because it was deconstructed from a valid one") + .into(), + ) + .expect("the only way to get a CidWrapper is by deconstructing a valid Cid") + } +} + +#[cfg(target_arch = "wasm32")] +impl From for CidWrapper { + fn from(cid: Cid) -> Self { + Self { + version_code: cid.version().into(), + multicodec_code: cid.codec().into(), + multihash_code: cid.hash().code(), + hash_bytes: cid.hash().digest().to_vec(), + } + } +} + // TODO testme #[cfg(target_arch = "wasm32")] impl From for JsValue { - fn from(wrapped: WrappedIpld) -> Self { + fn from(wrapped: Newtype) -> Self { match wrapped.0 { - Ipld::Null => JsValue::Null, + Ipld::Null => JsValue::NULL, Ipld::Bool(b) => JsValue::from(b), Ipld::Integer(i) => JsValue::from(i), Ipld::Float(f) => JsValue::from_f64(f), Ipld::String(s) => JsValue::from_str(&s), - Ipld::Bytes(b) => JsValue::from(b), - Ipld::List(l) => { - let mut vec = Vec::new(); - for ipld in l { - vec.push(JsValue::from(ipld)); + Ipld::Bytes(bs) => { + let buffer = Uint8Array::new(&bs.len().into()); + for (index, item) in bs.iter().enumerate() { + buffer.set_index(index as u32, *item); + } + JsValue::from(buffer) + } + Ipld::List(ls) => { + let mut arr = Array::new(); + for ipld in ls { + arr.push(&JsValue::from(Newtype(ipld))); } - JsValue::from(vec) + JsValue::from(arr) } Ipld::Map(m) => { - let mut map = JsValue::new(); + let mut map = Map::new(); for (k, v) in m { - map.set(&k, JsValue::from(v)); + map.set(&JsValue::from(k), &JsValue::from(Newtype(v))); } - map + JsValue::from(map) } - Ipld::Link(l) => JsValue::from(Link::from(l)), + // FIXME unclear if this is the correct approach, since the object loses + // a bunch of info (I presume) -- the JsIpld enum above may be better? Or a class? + Ipld::Link(cid) => CidWrapper::from(cid).into(), } } } diff --git a/src/lib.rs b/src/lib.rs index 0c24a0e6..0cb8c3e5 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -67,6 +67,7 @@ pub mod did; pub mod invocation; pub mod ipld; pub mod metadata; +pub mod new_wasm; pub mod nonce; pub mod number; pub mod promise; diff --git a/src/new_wasm.rs b/src/new_wasm.rs new file mode 100644 index 00000000..cc8066fe --- /dev/null +++ b/src/new_wasm.rs @@ -0,0 +1,7 @@ +use crate::{ability::dynamic::Dynamic, task::DefaultTrue}; +use js_sys; +use serde::{Deserialize, Serialize}; +use wasm_bindgen::prelude::*; + +// #[wasm_bindgen] +// type JsDynamic = Dynamic; diff --git a/src/nonce.rs b/src/nonce.rs index 74355690..bf64784f 100644 --- a/src/nonce.rs +++ b/src/nonce.rs @@ -3,8 +3,13 @@ use enum_as_inner::EnumAsInner; use libipld_core::{ipld::Ipld, multibase::Base::Base32HexLower}; use serde::{Deserialize, Serialize}; use std::fmt; + +#[cfg(not(target_arch = "wasm32"))] use uuid::Uuid; +#[cfg(target_arch = "wasm32")] +use web_sys; + // FIXME pub struct Unit; // FIXME @@ -22,13 +27,24 @@ pub enum Nonce { } impl Nonce { + // pub fn new() -> Self { + // Self::generate_96() + // } + + // #[cfg(target_arch = "wasm32")] + // pub fn gen_wasm_96() -> Self { + // web_sys::Crypto::get_random_values_with_u8_array() + // } + /// Default generator, outputting an [`xid`] nonce, /// which is a 96-bit (12-byte) nonce. - pub fn generate() -> Self { + #[cfg(not(target_arch = "wasm32"))] + pub fn generate_96() -> Self { Nonce::Nonce96(*xid::new().as_bytes()) } /// Generate a default 128-bit(16-byte) nonce via [`Uuid::new_v4()`]. + #[cfg(not(target_arch = "wasm32"))] pub fn generate_128() -> Self { Nonce::Nonce128(*Uuid::new_v4().as_bytes()) } diff --git a/src/promise.rs b/src/promise.rs index f2920a58..3a0792cc 100644 --- a/src/promise.rs +++ b/src/promise.rs @@ -1,6 +1,5 @@ use crate::ability::arguments::Arguments; -use cid::Cid; -use libipld_core::{ipld::Ipld, serde as ipld_serde}; +use libipld_core::{cid::Cid, ipld::Ipld, serde as ipld_serde}; use serde_derive::{Deserialize, Serialize}; use std::{collections::BTreeMap, fmt::Debug}; diff --git a/src/task.rs b/src/task.rs index e79d072d..8afc7916 100644 --- a/src/task.rs +++ b/src/task.rs @@ -5,12 +5,14 @@ use libipld_core::{ codec::Encode, error::SerdeError, ipld::Ipld, - multihash::{Code::Sha2_256, MultihashDigest}, + multihash::MultihashGeneric, serde as ipld_serde, }; use serde_derive::{Deserialize, Serialize}; use std::{collections::BTreeMap, fmt::Debug}; +const SHA2_256: u64 = 0x12; + #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] pub struct Task { #[serde(default, skip_serializing_if = "Option::is_none")] @@ -92,6 +94,10 @@ impl From for Cid { let ipld: Ipld = task.into(); ipld.encode(DagCborCodec, &mut buffer) .expect("DagCborCodec to encode any arbitrary `Ipld`"); - CidGeneric::new_v1(DagCborCodec.into(), Sha2_256.digest(buffer.as_slice())) + CidGeneric::new_v1( + DagCborCodec.into(), + MultihashGeneric::wrap(SHA2_256, buffer.as_slice()) + .expect("DagCborCodec + Sha2_256 should always successfully encode Ipld to a Cid"), + ) } } diff --git a/src/time.rs b/src/time.rs index 29500062..291f0aa9 100644 --- a/src/time.rs +++ b/src/time.rs @@ -1,8 +1,8 @@ //! Time utilities use libipld_core::{ipld::Ipld, serde as ipld_serde}; -use serde_derive::{Deserialize, Serialize}; -use web_time::{SystemTime, UNIX_EPOCH}; +use serde::{Deserialize, Deserializer, Serialize, Serializer}; +use web_time::{Duration, SystemTime, UNIX_EPOCH}; /// Get the current time in seconds since UNIX_EPOCH pub fn now() -> u64 { @@ -12,15 +12,14 @@ pub fn now() -> u64 { .as_secs() } -#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] -#[serde(untagged)] +#[derive(Debug, Clone, PartialEq)] pub enum Timestamp { // FIXME probably overkill, but overflows are bad. Need to check on ingestion, too - /// Per the spec, timestamps MUST respect [IEEE-754](https://en.wikipedia.org/wiki/IEEE_754) - /// (64-bit double precision = 53-bit truncated integer) for JavaScript interoperability. - /// - /// This range can represent millions of years into the future, - /// and is thus sufficient for nearly all use cases. + // Per the spec, timestamps MUST respect [IEEE-754](https://en.wikipedia.org/wiki/IEEE_754) + // (64-bit double precision = 53-bit truncated integer) for JavaScript interoperability. + // + // This range can represent millions of years into the future, + // and is thus sufficient for nearly all use cases. Sending(JsTime), /// Following [Postel's Law](https://en.wikipedia.org/wiki/Robustness_principle), @@ -28,6 +27,32 @@ pub enum Timestamp { Receiving(SystemTime), } +impl Serialize for Timestamp { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + match self { + Timestamp::Sending(js_time) => js_time.serialize(serializer), + Timestamp::Receiving(sys_time) => todo!(), // FIXME See comment on deserilaizer sys_time.serialize(serializer), + } + } +} + +impl<'de> Deserialize<'de> for Timestamp { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + if let Ok(js_time) = JsTime::deserialize(deserializer) { + return Ok(Timestamp::Sending(js_time)); + } + + todo!() + // FIXME just todo()ing this for now becuase the enum will likely go away very shortly + } +} + impl From for SystemTime { fn from(timestamp: Timestamp) -> Self { match timestamp { @@ -51,11 +76,35 @@ impl TryFrom for Timestamp { } } -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Debug, Clone, PartialEq, Eq)] pub struct JsTime { time: SystemTime, } +impl Serialize for JsTime { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + self.time + .duration_since(UNIX_EPOCH) + .expect("FIXME") + .as_secs() + .serialize(serializer) + } +} + +impl<'de> Deserialize<'de> for JsTime { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + let seconds = u64::deserialize(deserializer)?; + JsTime::new(UNIX_EPOCH + Duration::from_secs(seconds)) + .map_err(|_| serde::de::Error::custom("time out of JsTime range")) + } +} + // FIXME just lifting this from Elixir for now pub struct OutOfRangeError { pub tried: SystemTime, @@ -68,12 +117,7 @@ impl JsTime { /// /// * [`OutOfRangeError`] — If the time is more than 2⁵³ seconds since the Unix epoch pub fn new(time: SystemTime) -> Result { - if time - .duration_since(std::time::UNIX_EPOCH) - .expect("FIXME") - .as_secs() - > 0x1FFFFFFFFFFFFF - { + if time.duration_since(UNIX_EPOCH).expect("FIXME").as_secs() > 0x1FFFFFFFFFFFFF { Err(OutOfRangeError { tried: time }) } else { Ok(JsTime { time }) @@ -85,7 +129,7 @@ impl From for Ipld { fn from(js_time: JsTime) -> Self { js_time .time - .duration_since(std::time::UNIX_EPOCH) + .duration_since(UNIX_EPOCH) .expect("FIXME") .as_secs() .into() From c8bfde7318a06a444f4f9d511ddda3fd406a768b Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Fri, 2 Feb 2024 00:43:46 -0800 Subject: [PATCH 040/188] Learning about Wasm bindgen --- src/ability.rs | 3 + src/ability/arguments.rs | 18 +-- src/ability/dynamic.rs | 303 ++++++++++++++++++-------------------- src/ability/js.rs | 213 +++++++++++++++++++++++++++ src/delegation/payload.rs | 2 +- src/invocation/payload.rs | 2 +- src/new_wasm.rs | 8 +- 7 files changed, 371 insertions(+), 178 deletions(-) create mode 100644 src/ability/js.rs diff --git a/src/ability.rs b/src/ability.rs index f0fd7c0c..a6b095ad 100644 --- a/src/ability.rs +++ b/src/ability.rs @@ -7,6 +7,9 @@ pub mod wasm; pub mod arguments; pub mod command; +#[cfg(target_arch = "wasm32")] +pub mod js; + // // TODO move to crate::wasm? or hide behind feature flag? #[cfg(target_arch = "wasm32")] pub mod dynamic; diff --git a/src/ability/arguments.rs b/src/ability/arguments.rs index 0d02781a..75dc7bba 100644 --- a/src/ability/arguments.rs +++ b/src/ability/arguments.rs @@ -10,22 +10,20 @@ impl Arguments { Arguments(iterable.into_iter().collect()) } - pub fn iter(&self) -> impl Iterator { - self.0.iter() - } - - pub fn into_iter(self) -> impl Iterator { - self.0.into_iter() + pub fn get(&self, key: &str) -> Option<&Ipld> { + self.0.get(key) } -} -impl Arguments { pub fn insert(&mut self, key: String, value: Ipld) -> Option { self.0.insert(key, value) } - pub fn get(&self, key: &str) -> Option<&Ipld> { - self.0.get(key) + pub fn iter(&self) -> impl Iterator { + self.0.iter() + } + + pub fn into_iter(self) -> impl Iterator { + self.0.into_iter() } } diff --git a/src/ability/dynamic.rs b/src/ability/dynamic.rs index 0b29664a..dd8bbfb1 100644 --- a/src/ability/dynamic.rs +++ b/src/ability/dynamic.rs @@ -21,180 +21,159 @@ use wasm_bindgen::prelude::*; // NOTE the lack of checking functions! // This is meant to be embedded inside of structs that have e.g. FFI bindings to // a validation function, such as a &js_sys::Function, Ruby magnus::function!, etc etc -#[derive(Clone, PartialEq)] -pub struct Generic { - pub cmd: String, - pub args: Args, - pub is_nonce_meaningful: DefaultTrue, - - pub same_validator: F, - pub parent_validator: F, // FIXME needs to be a different types, and fall back to Void - pub shape_validator: F, // FIXME needs to be a different type -} - -impl Debug for Generic { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.debug_struct("Generic") - .field("cmd", &self.cmd) - .field("args", &self.args) - .field("is_nonce_meaningful", &self.is_nonce_meaningful) - .finish() - } -} - -pub type Dynamic = Generic; -pub type Promised = Generic, F>; - -impl Serialize for Generic -where - Arguments: From, -{ - fn serialize(&self, serializer: S) -> Result - where - S: Serializer, - { - let mut map = serializer.serialize_map(Some(2))?; - map.serialize_entry("cmd", &self.cmd)?; - map.serialize_entry("args", &Arguments::from(self.args.clone()))?; - map.end() - } -} - -impl<'de, Args: Deserialize<'de>> Deserialize<'de> for Generic { - fn deserialize(deserializer: D) -> Result, D::Error> - where - D: Deserializer<'de>, - { - // FIXME - todo!() - // let btree = BTreeMap::deserialize(deserializer)?; - // Ok(Generic { - // cmd: btree.get("cmd")?.to_string(), - // args: btree.get("args")?.clone(), - // is_nonce_meaningful: DefaultTrue::default(), - // - // same_validator: (), - // parent_validator: (), - // shape_validator: (), - // }) - } -} - -impl ToCommand for Generic { - fn to_command(&self) -> String { - self.cmd.clone() - } -} - -impl Delegatable for Dynamic { - type Builder = Dynamic; -} - -impl Resolvable for Dynamic { - type Promised = Promised; -} - -impl From> for Ipld { - fn from(generic: Generic) -> Self { - generic.into() - } -} - -impl TryFrom for Generic { - type Error = SerdeError; - - fn try_from(ipld: Ipld) -> Result { - ipld_serde::from_ipld(ipld) - } -} - -impl, F> From> for Arguments { - fn from(generic: Generic) -> Self { - generic.args.into() - } -} - -impl TryFrom> for Dynamic { - type Error = (); // FIXME - - fn try_from(awaiting: Promised) -> Result { - if let Promise::Resolved(args) = &awaiting.args { - Ok(Dynamic { - cmd: awaiting.cmd, - args: args.clone(), - - same_validator: awaiting.same_validator, - parent_validator: awaiting.parent_validator, - shape_validator: awaiting.shape_validator, - is_nonce_meaningful: awaiting.is_nonce_meaningful, - }) - } else { - Err(()) - } - } -} - -impl From> for Promised { - fn from(d: Dynamic) -> Self { - Promised { - cmd: d.cmd, - args: Promise::Resolved(d.args), - - same_validator: d.same_validator, - parent_validator: d.parent_validator, - shape_validator: d.shape_validator, - is_nonce_meaningful: d.is_nonce_meaningful, - } - } -} - -impl Checkable for Dynamic -where - F: Fn(&String, &Arguments) -> Result<(), String>, -{ - type Hierarchy = Parentless>; // FIXME I bet we can revover parents -} +// #[derive(Clone, PartialEq)] +// pub struct Generic { +// pub cmd: String, +// pub args: Args, +// // pub is_nonce_meaningful: Fn(&String) -> bool, +// // pub same_validator: Fn(&String, &Arguments) -> Result<(), String>, +// // pub parent_validator: F, // FIXME needs to be a different types, and fall back to Void +// // pub shape_validator: Fn(&String, &Arguments) -> Result<(), String>, // FIXME needs to be a different type +// } -// FIXME Actually, we shoudl go back to wrapping? -// impl CheckSame for Dynamic +// // pub struct DynamicValidator { +// // fn check_shape(self) -> (); +// // // fn check_same: Fn(&String, &Arguments, &String, &Arguments) -> Result<(), String>, +// // // fn check_parents: Fn(&String, &Arguments, &String, &Arguments) -> Result<(), String>, +// // } +// // +// // +// // +// // // FIXME Actually, we shoudl go back to wrapping? +// // // impl CheckSame for Dynamic +// // // where +// // // F: Fn(&String, &Arguments) -> Result<(), String>, +// // // { +// // // type Error = String; +// // // +// // // fn check_same(&self, proof: &Self) -> Result<(), Self::Error> { +// // // let chain_checker = &self.same_validator; +// // // let shape_checker = &self.same_validator; +// // // +// // // shape_checker(&proof.cmd, &proof.args)?; +// // // chain_checker(&proof.cmd, &proof.args) +// // // } +// // // } +// +// impl Debug for Generic { +// fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { +// f.debug_struct("Generic") +// .field("cmd", &self.cmd) +// .field("args", &self.args) +// .field("is_nonce_meaningful", &self.is_nonce_meaningful) +// .finish() +// } +// } +// +// pub type Dynamic = Generic; +// pub type Promised = Generic, F>; +// +// impl Serialize for Generic // where -// F: Fn(&String, &Arguments) -> Result<(), String>, +// Arguments: From, // { -// type Error = String; +// fn serialize(&self, serializer: S) -> Result +// where +// S: Serializer, +// { +// let mut map = serializer.serialize_map(Some(2))?; +// map.serialize_entry("cmd", &self.cmd)?; +// map.serialize_entry("args", &Arguments::from(self.args.clone()))?; +// map.end() +// } +// } // -// fn check_same(&self, proof: &Self) -> Result<(), Self::Error> { -// let chain_checker = &self.same_validator; -// let shape_checker = &self.same_validator; +// impl<'de, Args: Deserialize<'de>> Deserialize<'de> for Generic { +// fn deserialize(deserializer: D) -> Result, D::Error> +// where +// D: Deserializer<'de>, +// { +// // FIXME +// todo!() +// // let btree = BTreeMap::deserialize(deserializer)?; +// // Ok(Generic { +// // cmd: btree.get("cmd")?.to_string(), +// // args: btree.get("args")?.clone(), +// // is_nonce_meaningful: DefaultTrue::default(), +// // +// // same_validator: (), +// // parent_validator: (), +// // shape_validator: (), +// // }) +// } +// } // -// shape_checker(&proof.cmd, &proof.args)?; -// chain_checker(&proof.cmd, &proof.args) +// impl ToCommand for Generic { +// fn to_command(&self) -> String { +// self.cmd.clone() // } // } - -// #[wasm_bindgen(module = "./ability")] -// extern "C" { -// type JsAbility; // -// // FIXME wrap in func that checks the jsval or better: converts form Ipld -// #[wasm_bindgen(constructor)] -// fn new(cmd: String, args: BTreeMap) -> JsAbility; +// impl Delegatable for Dynamic { +// type Builder = Dynamic; +// } // -// #[wasm_bindgen(method, getter)] -// fn command(this: &JsAbility) -> String; +// impl Resolvable for Dynamic { +// type Promised = Promised; +// } // -// #[wasm_bindgen(method, getter)] -// fn arguments(this: &JsAbility) -> Arguments; +// impl From> for Ipld { +// fn from(generic: Generic) -> Self { +// generic.into() +// } +// } // -// #[wasm_bindgen(method, getter)] -// fn is_nonce_meaningful(this: &JsAbility) -> bool; +// impl TryFrom for Generic { +// type Error = SerdeError; // -// // e.g. reject extra fields -// #[wasm_bindgen(method)] -// fn validate_shape(this: &JsAbility) -> bool; +// fn try_from(ipld: Ipld) -> Result { +// ipld_serde::from_ipld(ipld) +// } +// } // -// // FIXME camels to snakes -// #[wasm_bindgen(method)] -// fn check_same(this: &JsAbility, proof: &JsAbility) -> Result<(), String>; +// impl, F> From> for Arguments { +// fn from(generic: Generic) -> Self { +// generic.args.into() +// } +// } // -// fn check_parents(th.......) +// impl TryFrom> for Dynamic { +// type Error = (); // FIXME +// +// fn try_from(awaiting: Promised) -> Result { +// if let Promise::Resolved(args) = &awaiting.args { +// Ok(Dynamic { +// cmd: awaiting.cmd, +// args: args.clone(), +// +// same_validator: awaiting.same_validator, +// parent_validator: awaiting.parent_validator, +// shape_validator: awaiting.shape_validator, +// is_nonce_meaningful: awaiting.is_nonce_meaningful, +// }) +// } else { +// Err(()) +// } +// } +// } +// +// impl From> for Promised { +// fn from(d: Dynamic) -> Self { +// Promised { +// cmd: d.cmd, +// args: Promise::Resolved(d.args), +// +// same_validator: d.same_validator, +// parent_validator: d.parent_validator, +// shape_validator: d.shape_validator, +// is_nonce_meaningful: d.is_nonce_meaningful, +// } +// } +// } +// +// impl Checkable for Dynamic +// where +// F: Fn(&String, &Arguments) -> Result<(), String>, +// { +// type Hierarchy = Parentless>; // FIXME I bet we can revover parents // } diff --git a/src/ability/js.rs b/src/ability/js.rs new file mode 100644 index 00000000..709b69b7 --- /dev/null +++ b/src/ability/js.rs @@ -0,0 +1,213 @@ +use super::arguments::Arguments; +use crate::proof::same::CheckSame; +use js_sys::Object; +use libipld_core::ipld::Ipld; +use std::collections::BTreeMap; +use wasm_bindgen::prelude::*; + +// FIXME dynamic +pub struct Ability { + pub cmd: String, // FIXME don't need this field because it's on the validator? + // FIXME JsCast for Args or WrappedIpld, esp for Cids + pub args: BTreeMap, // FIXME args + // pub args: wasm_bindgen::JsValue, // js_sys::Object, // BTreeMap, // FIXME args +} + +impl From for js_sys::Object { + fn from(ability: Ability) -> Self { + let args = js_sys::Map::new(); + for (k, v) in ability.args { + args.set(&k.into(), &v); + } + + let map = js_sys::Map::new(); + map.set(&"args".into(), &js_sys::Object::from(args).into()); + map.set(&"cmd".into(), &ability.cmd.into()); + map.into() + } +} + +impl TryFrom for Ability { + type Error = JsValue; + + fn try_from(map: js_sys::Map) -> Result { + if let (Some(cmd), js_args) = ( + map.get(&("cmd".into())).as_string(), + &map.get(&("args".into())), + ) { + let obj_args = js_sys::Object::try_from(js_args).ok_or(wasm_bindgen::JsValue::NULL)?; + let keys = Object::keys(obj_args); + let values = Object::values(obj_args); + + // FIXME come back when TryInto is done... if it matters + let mut args = BTreeMap::new(); + for (k, v) in keys.iter().zip(values) { + if let Some(k) = k.as_string() { + args.insert(k, v); + } else { + return Err(k); + } + } + + Ok(Ability { + cmd, + args: args.clone(), // FIXME kill clone + }) + } else { + Err(JsValue::NULL) // FIXME + } + } +} + +#[wasm_bindgen] +#[derive(Debug, Clone, PartialEq)] +pub struct Validator { + #[wasm_bindgen(skip)] + pub cmd: String, + + #[wasm_bindgen(readonly)] + pub is_nonce_meaningful: bool, + + #[wasm_bindgen(skip)] + pub validate_shape: js_sys::Function, + + #[wasm_bindgen(skip)] + pub check_same: js_sys::Function, + + #[wasm_bindgen(skip)] + pub check_parent: Option, // FIXME explore concrete types + an enum +} + +// NOTE more like a config object +#[wasm_bindgen] +impl Validator { + // FIXME wrap in func that checks the jsval or better: converts form Ipld + // FIXME notes about checking shape on the way in + #[wasm_bindgen(constructor)] + pub fn new( + cmd: String, + is_nonce_meaningful: bool, + validate_shape: js_sys::Function, + check_same: js_sys::Function, + check_parent: Option, + ) -> Validator { + // FIXME chec that JsErr doesn't auto-throw + Validator { + cmd, + is_nonce_meaningful, + validate_shape, + check_same, + check_parent, + } + } + + pub fn command(&self) -> String { + self.cmd.clone() + } + + // e.g. reject extra fields + pub fn validate_shape(&self, args: &wasm_bindgen::JsValue) -> Result<(), JsValue> { + let this = wasm_bindgen::JsValue::NULL; + self.validate_shape.call1(&this, args)?; + Ok(()) + } + + // FIXME only dynamic? + pub fn check_same( + &self, + target: &js_sys::Object, + proof: &js_sys::Object, + ) -> Result<(), JsValue> { + let this = wasm_bindgen::JsValue::NULL; + self.check_same.call2(&this, target, proof)?; + Ok(()) + } + + pub fn check_parents( + &self, + target: &js_sys::Object, // FIXME better type, esp for TS? + proof: &js_sys::Object, + ) -> Result<(), JsValue> { + let this = wasm_bindgen::JsValue::NULL; + if let Some(checker) = &self.check_parent { + checker.call2(&this, target, proof)?; + return Ok(()); + } + + Err(this) + } +} + +pub struct Foo { + ability: Ability, + validator: Validator, +} + +impl From for Arguments { + fn from(foo: Foo) -> Self { + todo!() // FIXME + } +} + +use crate::delegation::Delegatable; + +impl Delegatable for Foo { + type Builder = Foo; +} + +impl CheckSame for Foo { + type Error = JsValue; + + fn check_same(&self, proof: &Self) -> Result<(), Self::Error> { + let this_it = self.ability.args.iter().map(|(k, v)| (JsValue::from(k), v)); + + let mut this_args = js_sys::Map::new(); + for (k, v) in this_it { + this_args.set(&k, v); + } + + let proof_it = proof + .ability + .args + .iter() + .map(|(k, v)| (JsValue::from(k), v)); + + let mut proof_args = js_sys::Map::new(); + for (k, v) in proof_it { + proof_args.set(&k, v); + } + + self.validator.check_same( + &Object::from_entries(&this_args)?, + &Object::from_entries(&proof_args)?, + ) + } +} + +// pub struct Ability { +// pub cmd: String, +// pub args: BTreeMap, // FIXME args +// pub val: JsValidator, +// } +// +// #[wasm_bindgen] +// impl Ability { +// #[wasm_bindgen(constructor)] +// fn new( +// cmd: String, +// args: BTreeMap, +// validator: JsValidator, +// ) -> Result { +// let args = args +// .iter() +// .map(|(k, v)| (k.clone(), JsValue::from(v.clone()))) +// .collect(); +// +// validator.check_shape(args)?; +// Ok(Ability { cmd, args, val }) +// } +// } +// +// pub struct Pipeline { +// pub validators: Vec, +// } diff --git a/src/delegation/payload.rs b/src/delegation/payload.rs index 2fb55124..2161dc63 100644 --- a/src/delegation/payload.rs +++ b/src/delegation/payload.rs @@ -222,7 +222,7 @@ struct InternalSerializer { #[serde(rename = "aud")] audience: Did, - #[serde(rename = "can")] + #[serde(rename = "cmd")] command: String, #[serde(rename = "args")] arguments: Arguments, diff --git a/src/invocation/payload.rs b/src/invocation/payload.rs index 5e8ea6a8..2ee9805c 100644 --- a/src/invocation/payload.rs +++ b/src/invocation/payload.rs @@ -110,7 +110,7 @@ struct InternalSerializer { #[serde(rename = "aud", skip_serializing_if = "Option::is_none")] audience: Option, - #[serde(rename = "do")] + #[serde(rename = "cmd")] command: String, #[serde(rename = "args")] arguments: Arguments, diff --git a/src/new_wasm.rs b/src/new_wasm.rs index cc8066fe..760bf027 100644 --- a/src/new_wasm.rs +++ b/src/new_wasm.rs @@ -1,7 +1,7 @@ -use crate::{ability::dynamic::Dynamic, task::DefaultTrue}; -use js_sys; -use serde::{Deserialize, Serialize}; -use wasm_bindgen::prelude::*; +// use crate::{ability::dynamic::Dynamic, task::DefaultTrue}; +// use js_sys; +// use serde::{Deserialize, Serialize}; +// use wasm_bindgen::prelude::*; // #[wasm_bindgen] // type JsDynamic = Dynamic; From f668aa6acda5cf4064aba3d5ec2c5c8a0a9fd473 Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Fri, 2 Feb 2024 14:21:00 -0800 Subject: [PATCH 041/188] Ipld <-> JsValue --- src/ability/js.rs | 20 ++++++-- src/ipld.rs | 122 ++++++++++++++++++++++++++++++++++++++++------ 2 files changed, 124 insertions(+), 18 deletions(-) diff --git a/src/ability/js.rs b/src/ability/js.rs index 709b69b7..d015dff9 100644 --- a/src/ability/js.rs +++ b/src/ability/js.rs @@ -6,11 +6,12 @@ use std::collections::BTreeMap; use wasm_bindgen::prelude::*; // FIXME dynamic +#[wasm_bindgen] pub struct Ability { - pub cmd: String, // FIXME don't need this field because it's on the validator? + cmd: String, // FIXME don't need this field because it's on the validator? // FIXME JsCast for Args or WrappedIpld, esp for Cids - pub args: BTreeMap, // FIXME args - // pub args: wasm_bindgen::JsValue, // js_sys::Object, // BTreeMap, // FIXME args + args: BTreeMap, // FIXME args + // pub args: wasm_bindgen::JsValue, // js_sys::Object, // BTreeMap, // FIXME args } impl From for js_sys::Object { @@ -78,6 +79,19 @@ pub struct Validator { pub check_parent: Option, // FIXME explore concrete types + an enum } +// Helper +pub fn invoke(f: &js_sys::Function, args: Vec) -> Result { + // FIXME annoying number of steps... so I guess that's why they have the numbered versions... + // but those end at 3 :/ + // Hmm I guess this is reasonable, since it needs to copy the `Vec` to the JsArray + let arr = js_sys::Array::new_with_length(args.len() as u32); + for (i, arg) in args.iter().enumerate() { + arr.set(i as u32, arg.clone()); + } + + f.apply(&wasm_bindgen::JsValue::NULL, &arr) +} + // NOTE more like a config object #[wasm_bindgen] impl Validator { diff --git a/src/ipld.rs b/src/ipld.rs index 2a1d213b..ffafd65b 100644 --- a/src/ipld.rs +++ b/src/ipld.rs @@ -28,16 +28,35 @@ impl From for Ipld { #[cfg(target_arch = "wasm32")] #[wasm_bindgen] -pub struct CidWrapper { +pub struct NewCid { + // FIXME Link? version_code: u64, multicodec_code: u64, multihash_code: u64, hash_bytes: Vec, } +// FIXME better name #[cfg(target_arch = "wasm32")] -impl From for Cid { - fn from(wrapper: CidWrapper) -> Self { +#[wasm_bindgen] +impl NewCid { + pub fn from_string(cid_string: String) -> Result { + NewCid::try_from(cid_string).map_err(|e| JsValue::from_str(&format!("{}", e))) + } +} + +#[cfg(target_arch = "wasm32")] +impl TryFrom for NewCid { + type Error = >::Error; + + fn try_from(cid_string: String) -> Result { + Cid::try_from(cid_string).map(Into::into) + } +} + +#[cfg(target_arch = "wasm32")] +impl From for Cid { + fn from(wrapper: NewCid) -> Self { Cid::new( wrapper.version_code.try_into().expect( "must be a valid Cid::Version because it was deconstructed from a valid one", @@ -50,12 +69,12 @@ impl From for Cid { .expect("a valid Multihash because it was deconstructed from a valid one") .into(), ) - .expect("the only way to get a CidWrapper is by deconstructing a valid Cid") + .expect("the only way to get a Link is by deconstructing a valid Cid") } } #[cfg(target_arch = "wasm32")] -impl From for CidWrapper { +impl From for NewCid { fn from(cid: Cid) -> Self { Self { version_code: cid.version().into(), @@ -76,13 +95,7 @@ impl From for JsValue { Ipld::Integer(i) => JsValue::from(i), Ipld::Float(f) => JsValue::from_f64(f), Ipld::String(s) => JsValue::from_str(&s), - Ipld::Bytes(bs) => { - let buffer = Uint8Array::new(&bs.len().into()); - for (index, item) in bs.iter().enumerate() { - buffer.set_index(index as u32, *item); - } - JsValue::from(buffer) - } + Ipld::Bytes(bs) => Bytes::from(bs).into(), Ipld::List(ls) => { let mut arr = Array::new(); for ipld in ls { @@ -97,9 +110,88 @@ impl From for JsValue { } JsValue::from(map) } - // FIXME unclear if this is the correct approach, since the object loses - // a bunch of info (I presume) -- the JsIpld enum above may be better? Or a class? - Ipld::Link(cid) => CidWrapper::from(cid).into(), + Ipld::Link(cid) => NewCid::from(cid).into(), } } } + +// TODO testme +#[cfg(target_arch = "wasm32")] +impl TryFrom for Newtype { + type Error = (); // FIXME + + fn try_from(js_val: JsValue) -> Result { + if js_val.is_null() { + return Ok(Newtype(Ipld::Null)); + } + + if let Some(b) = js_val.as_bool() { + return Ok(Newtype(Ipld::Bool(b))); + } + + if let Some(f) = js_val.as_f64() { + return Ok(Newtype(Ipld::Float(f))); + } + + if let Some(s) = js_val.as_string() { + return Ok(Newtype(Ipld::String(s))); + } + + if let Some(arr) = js_val.dyn_ref::() { + let mut ls = Vec::with_capacity(arr.length() as usize); + for a in arr.iter() { + ls.push(Newtype::try_from(a)?.into()); + } + return Ok(Newtype(Ipld::List(ls))); + } + + if let Some(bytes) = js_val.dyn_ref::() { + let arr = Uint8Array::new(&bytes.raw.len().into()); + + for (index, item) in bytes.raw.iter().enumerate() { + arr.set_index(index as u32, *item); + } + + return Ok(Newtype(Ipld::Bytes(arr))); + } + + if let Some(map) = js_val.dyn_ref::() { + let mut m = std::collections::BTreeMap::new(); + + for iter_item in map.entries() { + match iter_item { + Err(_) => return Err(()), + Ok(k) => match k.as_string() { + None => return Err(()), + Some(s) => { + m.insert(s, Newtype::try_from(v)?.0); + }, + } + } + } + + return Ok(Newtype(Ipld::Map(m))); + } + + // NOTE *must* come before `is_object` (which is hopefully below) + if let Some(link) = js_val.dyn_ref::() { + return Ok(Newtype(Ipld::Link(link.into().clone()))); + } + + if js_val.is_object() { + let mut m = std::collections::BTreeMap::new(); + for (k, v) in js_val { + m.insert(k.as_string.ok_or(())?, Newtype::try_from(v)?); + } + + return Ok(Newtype(Ipld::Map(m))); + } + + // // FIXME hmmmm + // if let Some(undefined) = js_val.is_undefined() { + // return Self(Ipld::Null); + // } + + Err(()) + } +} From 5fb875f490f7341708dc004dc5f8f353c0e5161a Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Fri, 2 Feb 2024 15:54:21 -0800 Subject: [PATCH 042/188] okay well this time it compiles :P --- Cargo.toml | 1 + src/ipld.rs | 151 ++++++++++++++++++++++++++++++++-------------------- src/lib.rs | 3 ++ 3 files changed, 98 insertions(+), 57 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 72735375..a1b96af6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -82,6 +82,7 @@ getrandom = { version = "0.2", features = ["js"] } js-sys = { version = "0.3" } serde-wasm-bindgen = "0.6" wasm-bindgen = "0.2" +wasm-bindgen-derive = "0.2" wasm-bindgen-futures = { version = "0.4" } web-sys = { version = "0.3", features = ["Crypto", "CryptoKey", "CryptoKeyPair", "SubtleCrypto"] } diff --git a/src/ipld.rs b/src/ipld.rs index ffafd65b..39a1085d 100644 --- a/src/ipld.rs +++ b/src/ipld.rs @@ -12,6 +12,9 @@ use libipld_core::multihash::MultihashGeneric; #[cfg(target_arch = "wasm32")] use js_sys::{Array, Map, Uint8Array}; +#[cfg(target_arch = "wasm32")] +use wasm_bindgen_derive::TryFromJsValue; + pub struct Newtype(pub Ipld); impl From for Newtype { @@ -26,23 +29,34 @@ impl From for Ipld { } } +// FIXME better name #[cfg(target_arch = "wasm32")] +#[derive(TryFromJsValue, Debug, PartialEq, Eq, Clone)] #[wasm_bindgen] pub struct NewCid { - // FIXME Link? - version_code: u64, - multicodec_code: u64, - multihash_code: u64, - hash_bytes: Vec, + cid: Cid, +} + +#[wasm_bindgen] +extern "C" { + /// This is here because the TryFromJsValue derivation macro + /// doesn't automatically support `Option`. + /// + /// [https://docs.rs/wasm-bindgen-derive/0.2.1/wasm_bindgen_derive/#optional-arguments] + #[wasm_bindgen(typescript_type = "NewCid | undefined")] + pub type OptionNewCid; } -// FIXME better name #[cfg(target_arch = "wasm32")] #[wasm_bindgen] impl NewCid { - pub fn from_string(cid_string: String) -> Result { + pub fn from_string(cid_string: String) -> Result { NewCid::try_from(cid_string).map_err(|e| JsValue::from_str(&format!("{}", e))) } + + pub fn to_string(&self) -> String { + self.cid.to_string() + } } #[cfg(target_arch = "wasm32")] @@ -57,31 +71,14 @@ impl TryFrom for NewCid { #[cfg(target_arch = "wasm32")] impl From for Cid { fn from(wrapper: NewCid) -> Self { - Cid::new( - wrapper.version_code.try_into().expect( - "must be a valid Cid::Version because it was deconstructed from a valid one", - ), - wrapper - .multicodec_code - .try_into() - .expect("must be a valid Multicodec because it was deconstructed from a valid one"), - MultihashGeneric::<64>::wrap(wrapper.multihash_code, wrapper.hash_bytes.as_ref()) - .expect("a valid Multihash because it was deconstructed from a valid one") - .into(), - ) - .expect("the only way to get a Link is by deconstructing a valid Cid") + wrapper.cid } } #[cfg(target_arch = "wasm32")] impl From for NewCid { fn from(cid: Cid) -> Self { - Self { - version_code: cid.version().into(), - multicodec_code: cid.codec().into(), - multihash_code: cid.hash().code(), - hash_bytes: cid.hash().digest().to_vec(), - } + Self { cid } } } @@ -95,7 +92,13 @@ impl From for JsValue { Ipld::Integer(i) => JsValue::from(i), Ipld::Float(f) => JsValue::from_f64(f), Ipld::String(s) => JsValue::from_str(&s), - Ipld::Bytes(bs) => Bytes::from(bs).into(), + Ipld::Bytes(bs) => { + let mut arr = js_sys::Uint8Array::new(&bs.len().into()); + for (i, b) in bs.iter().enumerate() { + arr.set_index(i as u32, *b); + } + arr.into() + } Ipld::List(ls) => { let mut arr = Array::new(); for ipld in ls { @@ -138,59 +141,93 @@ impl TryFrom for Newtype { } if let Some(arr) = js_val.dyn_ref::() { - let mut ls = Vec::with_capacity(arr.length() as usize); - for a in arr.iter() { - ls.push(Newtype::try_from(a)?.into()); + let mut list = vec![]; + for x in arr.to_vec().iter() { + let ipld = Newtype::try_from(x.clone())?.into(); + list.push(ipld); } - return Ok(Newtype(Ipld::List(ls))); - } - if let Some(bytes) = js_val.dyn_ref::() { - let arr = Uint8Array::new(&bytes.raw.len().into()); + return Ok(Newtype(Ipld::List(list))); + } - for (index, item) in bytes.raw.iter().enumerate() { - arr.set_index(index as u32, *item); + if let Some(arr) = js_val.dyn_ref::() { + let mut v = vec![]; + for item in arr.to_vec().iter() { + v.push(item.clone()); } - return Ok(Newtype(Ipld::Bytes(arr))); + return Ok(Newtype(Ipld::Bytes(v))); } if let Some(map) = js_val.dyn_ref::() { let mut m = std::collections::BTreeMap::new(); + let mut acc = Ok(()); - for iter_item in map.entries() { - match iter_item { - Err(_) => return Err(()), - Ok(k) => match k.as_string() { - None => return Err(()), - Some(s) => { - m.insert(s, Newtype::try_from(v)?.0); - }, + // Weird order, but correct per the docs + // vvvvvvvvvv + map.for_each(&mut |value, key| { + if acc.is_err() { + return; + } + + match key.as_string() { + None => { + acc = Err(()); } + Some(k) => match Newtype::try_from(value.clone()) { + Err(_) => { + acc = Err(()); + } + Ok(v) => match Newtype::try_from(value) { + Err(_) => { + acc = Err(()); + } + Ok(v) => { + m.insert(k, v.0); + } + }, + }, } - } + }); - return Ok(Newtype(Ipld::Map(m))); + return acc.map(|_| Newtype(Ipld::Map(m))); } // NOTE *must* come before `is_object` (which is hopefully below) - if let Some(link) = js_val.dyn_ref::() { - return Ok(Newtype(Ipld::Link(link.into().clone()))); + if let Ok(new_cid) = NewCid::try_from(&js_val) { + return Ok(Newtype(Ipld::Link(new_cid.cid))); } if js_val.is_object() { let mut m = std::collections::BTreeMap::new(); - for (k, v) in js_val { - m.insert(k.as_string.ok_or(())?, Newtype::try_from(v)?); - } + let mut acc = Ok(()); + + // This order is correct per the docs + // vvvvvvvvvv + js_sys::Map::from(js_val).for_each(&mut |value, key| { + if acc.is_err() { + return; + } + + match key.as_string() { + None => { + acc = Err(()); + } + Some(k) => match Newtype::try_from(value) { + Err(_) => { + acc = Err(()); + } + Ok(v) => { + m.insert(k, v.0); + } + }, + } + }); - return Ok(Newtype(Ipld::Map(m))); + return acc.map(|_| Newtype(Ipld::Map(m))); } - // // FIXME hmmmm - // if let Some(undefined) = js_val.is_undefined() { - // return Self(Ipld::Null); - // } + // NOTE fails on `undefined` and `function` Err(()) } diff --git a/src/lib.rs b/src/lib.rs index 0cb8c3e5..50e0d87d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -4,6 +4,9 @@ //! ucan +#[cfg(target_arch = "wasm32")] +extern crate alloc; + // use std::str::FromStr; // // use cid::{multihash, Cid}; From 9461d8d8f8f176d2d71a7895f297a36d344f717c Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Fri, 2 Feb 2024 18:24:50 -0800 Subject: [PATCH 043/188] Save debugging bindgen --- flake.nix | 12 ++ src/ability/arguments.rs | 66 +++++++- src/ability/dynamic.rs | 47 +++++- src/ability/js.rs | 343 +++++++++++++++++++++----------------- src/invocation/payload.rs | 22 +-- src/ipld.rs | 102 ++---------- src/ipld/cid.rs | 61 +++++++ 7 files changed, 393 insertions(+), 260 deletions(-) create mode 100644 src/ipld/cid.rs diff --git a/flake.nix b/flake.nix index 3e573152..a14844a0 100644 --- a/flake.nix +++ b/flake.nix @@ -293,12 +293,24 @@ category = "dev"; command = "${cargo} doc --features=mermaid_docs"; } + { + name = "docs:wasm:build"; + help = "Refresh the docs with the wasm32-unknown-unknown target"; + category = "dev"; + command = "${cargo} doc --features=mermaid_docs --target=wasm32-unknown-unknown"; + } { name = "docs:open"; help = "Open refreshed docs"; category = "dev"; command = "${cargo} doc --features=mermaid_docs --open"; } + { + name = "docs:wasm:open"; + help = "Open refreshed docs for wasm32-unknown-unknown"; + category = "dev"; + command = "${cargo} doc --features=mermaid_docs --target=wasm32-unknown-unknown --open"; + } ]; }; diff --git a/src/ability/arguments.rs b/src/ability/arguments.rs index 75dc7bba..90eb798d 100644 --- a/src/ability/arguments.rs +++ b/src/ability/arguments.rs @@ -2,8 +2,36 @@ use libipld_core::{error::SerdeError, ipld::Ipld, serde as ipld_serde}; use serde::{Deserialize, Serialize}; use std::collections::BTreeMap; +#[cfg(target_arch = "wasm32")] +use wasm_bindgen::prelude::*; + +#[cfg(target_arch = "wasm32")] +use js_sys::{Array, Map, Uint8Array}; + +#[cfg(target_arch = "wasm32")] +use crate::ipld; + +#[cfg_attr(target_arch = "wasm32", wasm_bindgen)] +pub struct Pair { + key: String, + value: ipld::Newtype, +} + +// #[wasm_bindgen] #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] -pub struct Arguments(pub BTreeMap); +#[cfg_attr(target_arch = "wasm32", wasm_bindgen)] +pub struct Arguments( + // #[wasm_bindgen(skip)] pub BTreeMap, + #[cfg_attr(target_arch = "wasm32", wasm_bindgen(skip))] pub BTreeMap, +); + +// #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +// #[cfg_attr(target_arch = "wasm32", wasm_bindgen)] +// pub struct Arguments1 { +// // #[cfg_attr(target_arch = "wasm32", wasm_bindgen(skip))] +// #[wasm_bindgen(skip)] +// pub one: Vec, +// } impl Arguments { pub fn from_iter(iterable: impl IntoIterator) -> Self { @@ -40,3 +68,39 @@ impl TryFrom for Arguments { ipld_serde::from_ipld(ipld) } } + +impl From for Ipld { + fn from(arguments: Arguments) -> Self { + ipld_serde::to_ipld(arguments).unwrap() + } +} + +// #[cfg(target_arch = "wasm32")] +// impl From for JsValue { +// fn from(arguments: Arguments) -> Self { +// arguments +// .0 +// .iter() +// .fold(Map::new(), |map, (ref k, v)| { +// map.set( +// &JsValue::from_str(k), +// &JsValue::from(ipld::Newtype(v.clone())), +// ); +// map +// }) +// .into() +// } +// } +// +// #[cfg(target_arch = "wasm32")] +// impl TryFrom for Arguments { +// type Error = (); // FIXME +// +// fn try_from(js: JsValue) -> Result { +// match ipld::Newtype::try_from(js).map(|newtype| newtype.0) { +// Err(()) => Err(()), // FIXME surface that we can't parse at all +// Ok(Ipld::Map(map)) => Ok(Arguments(map)), +// Ok(_wrong_ipld) => Err(()), // FIXME surface that we have the wrong type +// } +// } +// } diff --git a/src/ability/dynamic.rs b/src/ability/dynamic.rs index dd8bbfb1..5f7eb83f 100644 --- a/src/ability/dynamic.rs +++ b/src/ability/dynamic.rs @@ -21,15 +21,44 @@ use wasm_bindgen::prelude::*; // NOTE the lack of checking functions! // This is meant to be embedded inside of structs that have e.g. FFI bindings to // a validation function, such as a &js_sys::Function, Ruby magnus::function!, etc etc -// #[derive(Clone, PartialEq)] -// pub struct Generic { -// pub cmd: String, -// pub args: Args, -// // pub is_nonce_meaningful: Fn(&String) -> bool, -// // pub same_validator: Fn(&String, &Arguments) -> Result<(), String>, -// // pub parent_validator: F, // FIXME needs to be a different types, and fall back to Void -// // pub shape_validator: Fn(&String, &Arguments) -> Result<(), String>, // FIXME needs to be a different type -// } +#[derive(Clone, PartialEq, Debug)] +#[cfg_attr(target_arch = "wasm32", wasm_bindgen)] +pub struct Dynamic { + pub cmd: String, + pub args: Arguments, +} + +pub struct ValidateWithoutParents { + ability: Dynamic, + config: Config0, +} + +pub struct ValidateWithParents { + ability: Dynamic, + config: Config1, +} + +#[derive(Debug, Clone, PartialEq)] +pub struct Config0 { + pub is_nonce_meaningful: bool, + pub validate_shape: ValShape, + pub check_same: ValSame, +} + +#[derive(Debug, Clone, PartialEq)] +pub struct Config1 { + // #[wasm_bindgen(readonly)] + pub is_nonce_meaningful: bool, + + // #[wasm_bindgen(skip)] + pub validate_shape: ValShape, + + //#[wasm_bindgen(skip)] + pub check_same: ValSame, + + //#[wasm_bindgen(skip)] + pub check_parent: ValParent, // FIXME explore concrete types + an enum +} // // pub struct DynamicValidator { // // fn check_shape(self) -> (); diff --git a/src/ability/js.rs b/src/ability/js.rs index d015dff9..13e389d0 100644 --- a/src/ability/js.rs +++ b/src/ability/js.rs @@ -1,24 +1,24 @@ -use super::arguments::Arguments; -use crate::proof::same::CheckSame; +use super::{arguments::Arguments, dynamic}; +use crate::{ipld, proof::same::CheckSame}; use js_sys::Object; use libipld_core::ipld::Ipld; use std::collections::BTreeMap; use wasm_bindgen::prelude::*; -// FIXME dynamic +// FIXME now we can just use dynamic again (yay) #[wasm_bindgen] pub struct Ability { cmd: String, // FIXME don't need this field because it's on the validator? // FIXME JsCast for Args or WrappedIpld, esp for Cids - args: BTreeMap, // FIXME args - // pub args: wasm_bindgen::JsValue, // js_sys::Object, // BTreeMap, // FIXME args + args: Arguments, // FIXME args + // pub args: wasm_bindgen::JsValue, // js_sys::Object, // BTreeMap, // FIXME args } -impl From for js_sys::Object { +impl From for js_sys::Map { fn from(ability: Ability) -> Self { let args = js_sys::Map::new(); - for (k, v) in ability.args { - args.set(&k.into(), &v); + for (k, v) in ability.args.0 { + args.set(&k.into(), &ipld::Newtype(v).into()); } let map = js_sys::Map::new(); @@ -40,11 +40,10 @@ impl TryFrom for Ability { let keys = Object::keys(obj_args); let values = Object::values(obj_args); - // FIXME come back when TryInto is done... if it matters - let mut args = BTreeMap::new(); + let mut btree = BTreeMap::new(); for (k, v) in keys.iter().zip(values) { if let Some(k) = k.as_string() { - args.insert(k, v); + btree.insert(k, ipld::Newtype::try_from(v).expect("FIXME").0); } else { return Err(k); } @@ -52,7 +51,7 @@ impl TryFrom for Ability { Ok(Ability { cmd, - args: args.clone(), // FIXME kill clone + args: Arguments(btree), // FIXME kill clone }) } else { Err(JsValue::NULL) // FIXME @@ -60,24 +59,48 @@ impl TryFrom for Ability { } } -#[wasm_bindgen] -#[derive(Debug, Clone, PartialEq)] -pub struct Validator { - #[wasm_bindgen(skip)] - pub cmd: String, - - #[wasm_bindgen(readonly)] - pub is_nonce_meaningful: bool, - - #[wasm_bindgen(skip)] - pub validate_shape: js_sys::Function, +pub type Abc = dynamic::ValidateWithoutParents; +pub type Xyz = dynamic::ValidateWithParents; - #[wasm_bindgen(skip)] - pub check_same: js_sys::Function, +// #[wasm_bindgen] +// #[derive(Debug, Clone, PartialEq)] +// pub struct ValidateWithoutParents { +// ability: dynamic::Dynamic, +// +// #[wasm_bindgen(skip)] +// config: u32, // dynamic::Config0, +// } +// +// // pub struct ValidateWithParents { +// // ability: Dynamic, +// // config: Config1, +// // } +// +// #[wasm_bindgen] +// impl ValidateWithoutParents { +// pub fn foo(x: u32) -> ValidateWithoutParents { +// todo!() +// } +// } - #[wasm_bindgen(skip)] - pub check_parent: Option, // FIXME explore concrete types + an enum -} +// #[wasm_bindgen] +// #[derive(Debug, Clone, PartialEq)] +// pub struct Validator { +// #[wasm_bindgen(skip)] +// pub cmd: String, +// +// #[wasm_bindgen(readonly)] +// pub is_nonce_meaningful: bool, +// +// #[wasm_bindgen(skip)] +// pub validate_shape: js_sys::Function, +// +// #[wasm_bindgen(skip)] +// pub check_same: js_sys::Function, +// +// #[wasm_bindgen(skip)] +// pub check_parent: Option, // FIXME explore concrete types + an enum +// } // Helper pub fn invoke(f: &js_sys::Function, args: Vec) -> Result { @@ -92,136 +115,150 @@ pub fn invoke(f: &js_sys::Function, args: Vec) -> Result, - ) -> Validator { - // FIXME chec that JsErr doesn't auto-throw - Validator { - cmd, - is_nonce_meaningful, - validate_shape, - check_same, - check_parent, - } - } - - pub fn command(&self) -> String { - self.cmd.clone() - } - - // e.g. reject extra fields - pub fn validate_shape(&self, args: &wasm_bindgen::JsValue) -> Result<(), JsValue> { - let this = wasm_bindgen::JsValue::NULL; - self.validate_shape.call1(&this, args)?; - Ok(()) - } - - // FIXME only dynamic? - pub fn check_same( - &self, - target: &js_sys::Object, - proof: &js_sys::Object, - ) -> Result<(), JsValue> { - let this = wasm_bindgen::JsValue::NULL; - self.check_same.call2(&this, target, proof)?; - Ok(()) - } - - pub fn check_parents( - &self, - target: &js_sys::Object, // FIXME better type, esp for TS? - proof: &js_sys::Object, - ) -> Result<(), JsValue> { - let this = wasm_bindgen::JsValue::NULL; - if let Some(checker) = &self.check_parent { - checker.call2(&this, target, proof)?; - return Ok(()); - } - - Err(this) - } -} - -pub struct Foo { - ability: Ability, - validator: Validator, -} - -impl From for Arguments { - fn from(foo: Foo) -> Self { - todo!() // FIXME - } -} - -use crate::delegation::Delegatable; - -impl Delegatable for Foo { - type Builder = Foo; -} - -impl CheckSame for Foo { - type Error = JsValue; - - fn check_same(&self, proof: &Self) -> Result<(), Self::Error> { - let this_it = self.ability.args.iter().map(|(k, v)| (JsValue::from(k), v)); - - let mut this_args = js_sys::Map::new(); - for (k, v) in this_it { - this_args.set(&k, v); - } - - let proof_it = proof - .ability - .args - .iter() - .map(|(k, v)| (JsValue::from(k), v)); - - let mut proof_args = js_sys::Map::new(); - for (k, v) in proof_it { - proof_args.set(&k, v); - } - - self.validator.check_same( - &Object::from_entries(&this_args)?, - &Object::from_entries(&proof_args)?, - ) - } -} - -// pub struct Ability { -// pub cmd: String, -// pub args: BTreeMap, // FIXME args -// pub val: JsValidator, -// } -// +// // NOTE more like a config object // #[wasm_bindgen] -// impl Ability { +// impl Validator { +// // FIXME wrap in func that checks the jsval or better: converts form Ipld +// // FIXME notes about checking shape on the way in // #[wasm_bindgen(constructor)] -// fn new( +// pub fn new( // cmd: String, -// args: BTreeMap, -// validator: JsValidator, -// ) -> Result { -// let args = args -// .iter() -// .map(|(k, v)| (k.clone(), JsValue::from(v.clone()))) -// .collect(); +// is_nonce_meaningful: bool, +// validate_shape: js_sys::Function, +// check_same: js_sys::Function, +// check_parent: Option, +// ) -> Validator { +// // FIXME chec that JsErr doesn't auto-throw +// Validator { +// cmd, +// is_nonce_meaningful, +// validate_shape, +// check_same, +// check_parent, +// } +// } +// +// pub fn command(&self) -> String { +// self.cmd.clone() +// } +// +// // e.g. reject extra fields +// pub fn validate_shape(&self, args: &wasm_bindgen::JsValue) -> Result<(), JsValue> { +// let this = wasm_bindgen::JsValue::NULL; +// self.validate_shape.call1(&this, args)?; +// Ok(()) +// } +// +// // FIXME only dynamic? +// pub fn check_same( +// &self, +// target: &js_sys::Object, +// proof: &js_sys::Object, +// ) -> Result<(), JsValue> { +// let this = wasm_bindgen::JsValue::NULL; +// self.check_same.call2(&this, target, proof)?; +// Ok(()) +// } +// +// pub fn check_parents( +// &self, +// target: &js_sys::Object, // FIXME better type, esp for TS? +// proof: &js_sys::Object, +// ) -> Result<(), JsValue> { +// let this = wasm_bindgen::JsValue::NULL; +// if let Some(checker) = &self.check_parent { +// checker.call2(&this, target, proof)?; +// return Ok(()); +// } +// +// Err(this) +// } +// } +// +// pub struct Quux { +// quux: T, +// } +// +// type Bez = Quux; +// +// #[wasm_bindgen] +// impl Bez {} +// +// pub struct Baz { +// pub ability: Ability, +// pub validator: T, +// } +// +// pub struct Foo { +// pub ability: Ability, +// pub validator: Validator, +// } // -// validator.check_shape(args)?; -// Ok(Ability { cmd, args, val }) +// impl From for Arguments { +// fn from(foo: Foo) -> Self { +// todo!() // FIXME // } // } // -// pub struct Pipeline { -// pub validators: Vec, +// use crate::delegation::Delegatable; +// +// impl Delegatable for Foo { +// type Builder = Foo; // } +// +// impl CheckSame for Foo { +// type Error = JsValue; +// +// fn check_same(&self, proof: &Self) -> Result<(), Self::Error> { +// let this_it = self.ability.args.iter().map(|(k, v)| (JsValue::from(k), v)); +// +// let mut this_args = js_sys::Map::new(); +// for (k, v) in this_it { +// this_args.set(&k, &ipld::Newtype(v.clone()).into()); +// } +// +// let proof_it = proof +// .ability +// .args +// .iter() +// .map(|(k, v)| (JsValue::from(k), v)); +// +// let mut proof_args = js_sys::Map::new(); +// for (k, v) in proof_it { +// proof_args.set(&k, &ipld::Newtype(v.clone()).into()); +// } +// +// self.validator.check_same( +// &Object::from_entries(&this_args)?, +// &Object::from_entries(&proof_args)?, +// ) +// } +// } +// +// // pub struct Ability { +// // pub cmd: String, +// // pub args: BTreeMap, // FIXME args +// // pub val: JsValidator, +// // } +// // +// // #[wasm_bindgen] +// // impl Ability { +// // #[wasm_bindgen(constructor)] +// // fn new( +// // cmd: String, +// // args: BTreeMap, +// // validator: JsValidator, +// // ) -> Result { +// // let args = args +// // .iter() +// // .map(|(k, v)| (k.clone(), JsValue::from(v.clone()))) +// // .collect(); +// // +// // validator.check_shape(args)?; +// // Ok(Ability { cmd, args, val }) +// // } +// // } +// // +// // pub struct Pipeline { +// // pub validators: Vec, +// // } diff --git a/src/invocation/payload.rs b/src/invocation/payload.rs index 2ee9805c..72921dcd 100644 --- a/src/invocation/payload.rs +++ b/src/invocation/payload.rs @@ -34,17 +34,17 @@ pub type Unresolved = Payload; // type Dynamic = Payload; <- ? // FIXME parser for both versions -#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] -#[serde(untagged)] -pub enum MaybeResolved + Into> -where - Payload: From, - Unresolved: From, - T::Promised: Clone + Command + Debug + PartialEq, -{ - Resolved(Payload), - Unresolved(Unresolved), -} +// #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +// #[serde(untagged)] +// pub enum MaybeResolved + Into> +// where +// Payload: From, +// Unresolved: From, +// T::Promised: Clone + Command + Debug + PartialEq, +// { +// Resolved(Payload), +// Unresolved(Unresolved), +// } impl Capsule for Payload { const TAG: &'static str = "ucan/i/1.0.0-rc.1"; diff --git a/src/ipld.rs b/src/ipld.rs index 39a1085d..7a8c6281 100644 --- a/src/ipld.rs +++ b/src/ipld.rs @@ -1,20 +1,13 @@ use libipld_core::ipld::Ipld; -#[cfg(target_arch = "wasm32")] -use wasm_bindgen::prelude::*; +pub mod cid; #[cfg(target_arch = "wasm32")] -use libipld_core::cid::Cid; - -#[cfg(target_arch = "wasm32")] -use libipld_core::multihash::MultihashGeneric; +use wasm_bindgen::prelude::*; #[cfg(target_arch = "wasm32")] use js_sys::{Array, Map, Uint8Array}; -#[cfg(target_arch = "wasm32")] -use wasm_bindgen_derive::TryFromJsValue; - pub struct Newtype(pub Ipld); impl From for Newtype { @@ -29,59 +22,6 @@ impl From for Ipld { } } -// FIXME better name -#[cfg(target_arch = "wasm32")] -#[derive(TryFromJsValue, Debug, PartialEq, Eq, Clone)] -#[wasm_bindgen] -pub struct NewCid { - cid: Cid, -} - -#[wasm_bindgen] -extern "C" { - /// This is here because the TryFromJsValue derivation macro - /// doesn't automatically support `Option`. - /// - /// [https://docs.rs/wasm-bindgen-derive/0.2.1/wasm_bindgen_derive/#optional-arguments] - #[wasm_bindgen(typescript_type = "NewCid | undefined")] - pub type OptionNewCid; -} - -#[cfg(target_arch = "wasm32")] -#[wasm_bindgen] -impl NewCid { - pub fn from_string(cid_string: String) -> Result { - NewCid::try_from(cid_string).map_err(|e| JsValue::from_str(&format!("{}", e))) - } - - pub fn to_string(&self) -> String { - self.cid.to_string() - } -} - -#[cfg(target_arch = "wasm32")] -impl TryFrom for NewCid { - type Error = >::Error; - - fn try_from(cid_string: String) -> Result { - Cid::try_from(cid_string).map(Into::into) - } -} - -#[cfg(target_arch = "wasm32")] -impl From for Cid { - fn from(wrapper: NewCid) -> Self { - wrapper.cid - } -} - -#[cfg(target_arch = "wasm32")] -impl From for NewCid { - fn from(cid: Cid) -> Self { - Self { cid } - } -} - // TODO testme #[cfg(target_arch = "wasm32")] impl From for JsValue { @@ -93,27 +33,27 @@ impl From for JsValue { Ipld::Float(f) => JsValue::from_f64(f), Ipld::String(s) => JsValue::from_str(&s), Ipld::Bytes(bs) => { - let mut arr = js_sys::Uint8Array::new(&bs.len().into()); + let u8arr = Uint8Array::new(&bs.len().into()); for (i, b) in bs.iter().enumerate() { - arr.set_index(i as u32, *b); + u8arr.set_index(i as u32, *b); } - arr.into() + JsValue::from(u8arr) } Ipld::List(ls) => { - let mut arr = Array::new(); + let arr = Array::new(); for ipld in ls { arr.push(&JsValue::from(Newtype(ipld))); } JsValue::from(arr) } Ipld::Map(m) => { - let mut map = Map::new(); + let map = Map::new(); for (k, v) in m { map.set(&JsValue::from(k), &JsValue::from(Newtype(v))); } JsValue::from(map) } - Ipld::Link(cid) => NewCid::from(cid).into(), + Ipld::Link(cid) => cid::Newtype::from(cid).into(), } } } @@ -170,23 +110,13 @@ impl TryFrom for Newtype { return; } - match key.as_string() { - None => { + match (key.as_string(), Newtype::try_from(value.clone())) { + (Some(k), Ok(v)) => { + m.insert(k, v.0); + } + _ => { acc = Err(()); } - Some(k) => match Newtype::try_from(value.clone()) { - Err(_) => { - acc = Err(()); - } - Ok(v) => match Newtype::try_from(value) { - Err(_) => { - acc = Err(()); - } - Ok(v) => { - m.insert(k, v.0); - } - }, - }, } }); @@ -194,8 +124,8 @@ impl TryFrom for Newtype { } // NOTE *must* come before `is_object` (which is hopefully below) - if let Ok(new_cid) = NewCid::try_from(&js_val) { - return Ok(Newtype(Ipld::Link(new_cid.cid))); + if let Ok(nt) = cid::Newtype::try_from(&js_val) { + return Ok(Newtype(Ipld::Link(nt.into()))); } if js_val.is_object() { @@ -204,7 +134,7 @@ impl TryFrom for Newtype { // This order is correct per the docs // vvvvvvvvvv - js_sys::Map::from(js_val).for_each(&mut |value, key| { + Map::from(js_val).for_each(&mut |value, key| { if acc.is_err() { return; } diff --git a/src/ipld/cid.rs b/src/ipld/cid.rs new file mode 100644 index 00000000..9a812402 --- /dev/null +++ b/src/ipld/cid.rs @@ -0,0 +1,61 @@ +use libipld_core::cid::Cid; + +#[cfg(target_arch = "wasm32")] +use wasm_bindgen::prelude::*; + +#[cfg(target_arch = "wasm32")] +use wasm_bindgen_derive::TryFromJsValue; + +// FIXME better name +#[derive(Debug, PartialEq, Eq, Clone)] +#[cfg_attr(target_arch = "wasm32", derive(TryFromJsValue))] +#[cfg_attr(target_arch = "wasm32", wasm_bindgen)] +pub struct Newtype { + cid: Cid, +} + +#[cfg(target_arch = "wasm32")] +#[wasm_bindgen] +extern "C" { + /// This is here because the TryFromJsValue derivation macro + /// doesn't automatically support `Option`. + /// + /// [https://docs.rs/wasm-bindgen-derive/0.2.1/wasm_bindgen_derive/#optional-arguments] + #[wasm_bindgen(typescript_type = "Newtype | undefined")] + pub type OptionNewtype; +} + +#[cfg(target_arch = "wasm32")] +#[wasm_bindgen] +impl Newtype { + pub fn from_string(cid_string: String) -> Result { + Newtype::try_from(cid_string).map_err(|e| JsValue::from_str(&format!("{}", e))) + } + + pub fn to_string(&self) -> String { + self.cid.to_string() + } +} + +#[cfg(target_arch = "wasm32")] +impl TryFrom for Newtype { + type Error = >::Error; + + fn try_from(cid_string: String) -> Result { + Cid::try_from(cid_string).map(Into::into) + } +} + +#[cfg(target_arch = "wasm32")] +impl From for Cid { + fn from(wrapper: Newtype) -> Self { + wrapper.cid + } +} + +#[cfg(target_arch = "wasm32")] +impl From for Newtype { + fn from(cid: Cid) -> Self { + Self { cid } + } +} From d435ee4b46fda29757931bad300ca10a80ce8d69 Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Sat, 3 Feb 2024 01:20:34 -0800 Subject: [PATCH 044/188] Needs lots of cleanup, but the wasm startegy seems to work --- src/ability/arguments.rs | 114 ++++++++------- src/ability/dynamic.rs | 114 ++++++++------- src/ability/js.rs | 266 +---------------------------------- src/ability/js/parentful.rs | 128 +++++++++++++++++ src/ability/js/parentless.rs | 100 +++++++++++++ src/ability/ucan.rs | 2 + src/ipld.rs | 26 ++-- 7 files changed, 378 insertions(+), 372 deletions(-) create mode 100644 src/ability/js/parentful.rs create mode 100644 src/ability/js/parentless.rs diff --git a/src/ability/arguments.rs b/src/ability/arguments.rs index 90eb798d..b67f6608 100644 --- a/src/ability/arguments.rs +++ b/src/ability/arguments.rs @@ -6,32 +6,22 @@ use std::collections::BTreeMap; use wasm_bindgen::prelude::*; #[cfg(target_arch = "wasm32")] -use js_sys::{Array, Map, Uint8Array}; +use js_sys::{Array, Map, Object, Reflect, Uint8Array}; #[cfg(target_arch = "wasm32")] use crate::ipld; -#[cfg_attr(target_arch = "wasm32", wasm_bindgen)] -pub struct Pair { - key: String, - value: ipld::Newtype, -} +use super::wasm; -// #[wasm_bindgen] +// FIXME yes I'm seriously considering laying this out in the wasm abi by han d +// #[cfg(not(target_arch = "wasm32"))] #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] -#[cfg_attr(target_arch = "wasm32", wasm_bindgen)] -pub struct Arguments( - // #[wasm_bindgen(skip)] pub BTreeMap, - #[cfg_attr(target_arch = "wasm32", wasm_bindgen(skip))] pub BTreeMap, -); +pub struct Arguments(pub BTreeMap); +// #[cfg(target_arch = "wasm32")] // #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] -// #[cfg_attr(target_arch = "wasm32", wasm_bindgen)] -// pub struct Arguments1 { -// // #[cfg_attr(target_arch = "wasm32", wasm_bindgen(skip))] -// #[wasm_bindgen(skip)] -// pub one: Vec, -// } +// #[wasm_bindgen] +// pub struct Arguments(#[wasm_bindgen(skip)] pub BTreeMap); impl Arguments { pub fn from_iter(iterable: impl IntoIterator) -> Self { @@ -75,32 +65,62 @@ impl From for Ipld { } } -// #[cfg(target_arch = "wasm32")] -// impl From for JsValue { -// fn from(arguments: Arguments) -> Self { -// arguments -// .0 -// .iter() -// .fold(Map::new(), |map, (ref k, v)| { -// map.set( -// &JsValue::from_str(k), -// &JsValue::from(ipld::Newtype(v.clone())), -// ); -// map -// }) -// .into() -// } -// } -// -// #[cfg(target_arch = "wasm32")] -// impl TryFrom for Arguments { -// type Error = (); // FIXME -// -// fn try_from(js: JsValue) -> Result { -// match ipld::Newtype::try_from(js).map(|newtype| newtype.0) { -// Err(()) => Err(()), // FIXME surface that we can't parse at all -// Ok(Ipld::Map(map)) => Ok(Arguments(map)), -// Ok(_wrong_ipld) => Err(()), // FIXME surface that we have the wrong type -// } -// } -// } +#[cfg(target_arch = "wasm32")] +impl From for Object { + fn from(arguments: Arguments) -> Self { + let obj = Object::new(); + for (k, v) in arguments.0 { + Reflect::set(&obj, &k.into(), &ipld::Newtype(v).into()).unwrap(); + } + obj + } +} + +// NOTE saves a few cycles while calling by not cloning +// the extra Object fields that we're not going to use +#[cfg(target_arch = "wasm32")] +impl From<&Object> for Arguments { + fn from(obj: &Object) -> Self { + let btree = Object::entries(obj) + .iter() + .map(|entry| { + let entry = Array::from(&entry); + let key = entry.get(0).as_string().unwrap(); // FIXME + let value = ipld::Newtype::try_from(entry.get(1)).unwrap().0; + (key, value) + }) + .collect::>(); + + Arguments(btree) + } +} + +#[cfg(target_arch = "wasm32")] +impl From for JsValue { + fn from(arguments: Arguments) -> Self { + arguments + .0 + .iter() + .fold(Map::new(), |map, (ref k, v)| { + map.set( + &JsValue::from_str(k), + &JsValue::from(ipld::Newtype(v.clone())), + ); + map + }) + .into() + } +} + +#[cfg(target_arch = "wasm32")] +impl TryFrom for Arguments { + type Error = (); // FIXME + + fn try_from(js: JsValue) -> Result { + match ipld::Newtype::try_from(js).map(|newtype| newtype.0) { + Err(()) => Err(()), // FIXME surface that we can't parse at all + Ok(Ipld::Map(map)) => Ok(Arguments(map)), + Ok(_wrong_ipld) => Err(()), // FIXME surface that we have the wrong type + } + } +} diff --git a/src/ability/dynamic.rs b/src/ability/dynamic.rs index 5f7eb83f..4ad98974 100644 --- a/src/ability/dynamic.rs +++ b/src/ability/dynamic.rs @@ -4,6 +4,7 @@ use super::{arguments::Arguments, command::ToCommand}; use crate::{ delegation::Delegatable, invocation::Resolvable, + ipld, promise::Promise, proof::{ checkable::Checkable, parentful::Parentful, parentless::Parentless, parents::CheckParents, @@ -11,6 +12,7 @@ use crate::{ }, task::DefaultTrue, }; +use js_sys; use libipld_core::{error::SerdeError, ipld::Ipld, serde as ipld_serde}; use serde::{ de::DeserializeOwned, ser::SerializeMap, Deserialize, Deserializer, Serialize, Serializer, @@ -21,69 +23,87 @@ use wasm_bindgen::prelude::*; // NOTE the lack of checking functions! // This is meant to be embedded inside of structs that have e.g. FFI bindings to // a validation function, such as a &js_sys::Function, Ruby magnus::function!, etc etc -#[derive(Clone, PartialEq, Debug)] -#[cfg_attr(target_arch = "wasm32", wasm_bindgen)] +#[derive(Clone, PartialEq, Debug)] // FIXME serialize / deserilaize? pub struct Dynamic { pub cmd: String, pub args: Arguments, } -pub struct ValidateWithoutParents { - ability: Dynamic, - config: Config0, +impl From for Arguments { + fn from(dynamic: Dynamic) -> Self { + dynamic.args + } } -pub struct ValidateWithParents { - ability: Dynamic, - config: Config1, +#[cfg(target_arch = "wasm32")] +impl From for js_sys::Map { + fn from(ability: Dynamic) -> Self { + let args = js_sys::Map::new(); + for (k, v) in ability.args.0 { + args.set(&k.into(), &ipld::Newtype(v).into()); + } + + let map = js_sys::Map::new(); + map.set(&"args".into(), &js_sys::Object::from(args).into()); + map.set(&"cmd".into(), &ability.cmd.into()); + map + } } -#[derive(Debug, Clone, PartialEq)] -pub struct Config0 { - pub is_nonce_meaningful: bool, - pub validate_shape: ValShape, - pub check_same: ValSame, +#[cfg(target_arch = "wasm32")] +impl TryFrom for Dynamic { + type Error = JsValue; + + fn try_from(map: js_sys::Map) -> Result { + if let (Some(cmd), js_args) = ( + map.get(&("cmd".into())).as_string(), + &map.get(&("args".into())), + ) { + let obj_args = js_sys::Object::try_from(js_args).ok_or(wasm_bindgen::JsValue::NULL)?; + let keys = js_sys::Object::keys(obj_args); + let values = js_sys::Object::values(obj_args); + + let mut btree = BTreeMap::new(); + for (k, v) in keys.iter().zip(values) { + if let Some(k) = k.as_string() { + btree.insert(k, ipld::Newtype::try_from(v).expect("FIXME").0); + } else { + return Err(k); + } + } + + Ok(Dynamic { + cmd, + args: Arguments(btree), // FIXME kill clone + }) + } else { + Err(JsValue::NULL) // FIXME + } + } } -#[derive(Debug, Clone, PartialEq)] -pub struct Config1 { - // #[wasm_bindgen(readonly)] - pub is_nonce_meaningful: bool, +impl CheckSame for Dynamic { + type Error = String; // FIXME better err - // #[wasm_bindgen(skip)] - pub validate_shape: ValShape, + fn check_same(&self, proof: &Self) -> Result<(), Self::Error> { + if self.cmd != proof.cmd { + return Err("Command mismatch".into()); + } - //#[wasm_bindgen(skip)] - pub check_same: ValSame, + self.args.0.iter().try_for_each(|(k, v)| { + if let Some(proof_v) = proof.args.0.get(k) { + if v != proof_v { + return Err("Arguments mismatch".into()); + } + } else { + return Err("Arguments mismatch".into()); + } - //#[wasm_bindgen(skip)] - pub check_parent: ValParent, // FIXME explore concrete types + an enum + Ok(()) + }) + } } -// // pub struct DynamicValidator { -// // fn check_shape(self) -> (); -// // // fn check_same: Fn(&String, &Arguments, &String, &Arguments) -> Result<(), String>, -// // // fn check_parents: Fn(&String, &Arguments, &String, &Arguments) -> Result<(), String>, -// // } -// // -// // -// // -// // // FIXME Actually, we shoudl go back to wrapping? -// // // impl CheckSame for Dynamic -// // // where -// // // F: Fn(&String, &Arguments) -> Result<(), String>, -// // // { -// // // type Error = String; -// // // -// // // fn check_same(&self, proof: &Self) -> Result<(), Self::Error> { -// // // let chain_checker = &self.same_validator; -// // // let shape_checker = &self.same_validator; -// // // -// // // shape_checker(&proof.cmd, &proof.args)?; -// // // chain_checker(&proof.cmd, &proof.args) -// // // } -// // // } -// // impl Debug for Generic { // fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { // f.debug_struct("Generic") diff --git a/src/ability/js.rs b/src/ability/js.rs index 13e389d0..8422e6d3 100644 --- a/src/ability/js.rs +++ b/src/ability/js.rs @@ -1,264 +1,2 @@ -use super::{arguments::Arguments, dynamic}; -use crate::{ipld, proof::same::CheckSame}; -use js_sys::Object; -use libipld_core::ipld::Ipld; -use std::collections::BTreeMap; -use wasm_bindgen::prelude::*; - -// FIXME now we can just use dynamic again (yay) -#[wasm_bindgen] -pub struct Ability { - cmd: String, // FIXME don't need this field because it's on the validator? - // FIXME JsCast for Args or WrappedIpld, esp for Cids - args: Arguments, // FIXME args - // pub args: wasm_bindgen::JsValue, // js_sys::Object, // BTreeMap, // FIXME args -} - -impl From for js_sys::Map { - fn from(ability: Ability) -> Self { - let args = js_sys::Map::new(); - for (k, v) in ability.args.0 { - args.set(&k.into(), &ipld::Newtype(v).into()); - } - - let map = js_sys::Map::new(); - map.set(&"args".into(), &js_sys::Object::from(args).into()); - map.set(&"cmd".into(), &ability.cmd.into()); - map.into() - } -} - -impl TryFrom for Ability { - type Error = JsValue; - - fn try_from(map: js_sys::Map) -> Result { - if let (Some(cmd), js_args) = ( - map.get(&("cmd".into())).as_string(), - &map.get(&("args".into())), - ) { - let obj_args = js_sys::Object::try_from(js_args).ok_or(wasm_bindgen::JsValue::NULL)?; - let keys = Object::keys(obj_args); - let values = Object::values(obj_args); - - let mut btree = BTreeMap::new(); - for (k, v) in keys.iter().zip(values) { - if let Some(k) = k.as_string() { - btree.insert(k, ipld::Newtype::try_from(v).expect("FIXME").0); - } else { - return Err(k); - } - } - - Ok(Ability { - cmd, - args: Arguments(btree), // FIXME kill clone - }) - } else { - Err(JsValue::NULL) // FIXME - } - } -} - -pub type Abc = dynamic::ValidateWithoutParents; -pub type Xyz = dynamic::ValidateWithParents; - -// #[wasm_bindgen] -// #[derive(Debug, Clone, PartialEq)] -// pub struct ValidateWithoutParents { -// ability: dynamic::Dynamic, -// -// #[wasm_bindgen(skip)] -// config: u32, // dynamic::Config0, -// } -// -// // pub struct ValidateWithParents { -// // ability: Dynamic, -// // config: Config1, -// // } -// -// #[wasm_bindgen] -// impl ValidateWithoutParents { -// pub fn foo(x: u32) -> ValidateWithoutParents { -// todo!() -// } -// } - -// #[wasm_bindgen] -// #[derive(Debug, Clone, PartialEq)] -// pub struct Validator { -// #[wasm_bindgen(skip)] -// pub cmd: String, -// -// #[wasm_bindgen(readonly)] -// pub is_nonce_meaningful: bool, -// -// #[wasm_bindgen(skip)] -// pub validate_shape: js_sys::Function, -// -// #[wasm_bindgen(skip)] -// pub check_same: js_sys::Function, -// -// #[wasm_bindgen(skip)] -// pub check_parent: Option, // FIXME explore concrete types + an enum -// } - -// Helper -pub fn invoke(f: &js_sys::Function, args: Vec) -> Result { - // FIXME annoying number of steps... so I guess that's why they have the numbered versions... - // but those end at 3 :/ - // Hmm I guess this is reasonable, since it needs to copy the `Vec` to the JsArray - let arr = js_sys::Array::new_with_length(args.len() as u32); - for (i, arg) in args.iter().enumerate() { - arr.set(i as u32, arg.clone()); - } - - f.apply(&wasm_bindgen::JsValue::NULL, &arr) -} - -// // NOTE more like a config object -// #[wasm_bindgen] -// impl Validator { -// // FIXME wrap in func that checks the jsval or better: converts form Ipld -// // FIXME notes about checking shape on the way in -// #[wasm_bindgen(constructor)] -// pub fn new( -// cmd: String, -// is_nonce_meaningful: bool, -// validate_shape: js_sys::Function, -// check_same: js_sys::Function, -// check_parent: Option, -// ) -> Validator { -// // FIXME chec that JsErr doesn't auto-throw -// Validator { -// cmd, -// is_nonce_meaningful, -// validate_shape, -// check_same, -// check_parent, -// } -// } -// -// pub fn command(&self) -> String { -// self.cmd.clone() -// } -// -// // e.g. reject extra fields -// pub fn validate_shape(&self, args: &wasm_bindgen::JsValue) -> Result<(), JsValue> { -// let this = wasm_bindgen::JsValue::NULL; -// self.validate_shape.call1(&this, args)?; -// Ok(()) -// } -// -// // FIXME only dynamic? -// pub fn check_same( -// &self, -// target: &js_sys::Object, -// proof: &js_sys::Object, -// ) -> Result<(), JsValue> { -// let this = wasm_bindgen::JsValue::NULL; -// self.check_same.call2(&this, target, proof)?; -// Ok(()) -// } -// -// pub fn check_parents( -// &self, -// target: &js_sys::Object, // FIXME better type, esp for TS? -// proof: &js_sys::Object, -// ) -> Result<(), JsValue> { -// let this = wasm_bindgen::JsValue::NULL; -// if let Some(checker) = &self.check_parent { -// checker.call2(&this, target, proof)?; -// return Ok(()); -// } -// -// Err(this) -// } -// } -// -// pub struct Quux { -// quux: T, -// } -// -// type Bez = Quux; -// -// #[wasm_bindgen] -// impl Bez {} -// -// pub struct Baz { -// pub ability: Ability, -// pub validator: T, -// } -// -// pub struct Foo { -// pub ability: Ability, -// pub validator: Validator, -// } -// -// impl From for Arguments { -// fn from(foo: Foo) -> Self { -// todo!() // FIXME -// } -// } -// -// use crate::delegation::Delegatable; -// -// impl Delegatable for Foo { -// type Builder = Foo; -// } -// -// impl CheckSame for Foo { -// type Error = JsValue; -// -// fn check_same(&self, proof: &Self) -> Result<(), Self::Error> { -// let this_it = self.ability.args.iter().map(|(k, v)| (JsValue::from(k), v)); -// -// let mut this_args = js_sys::Map::new(); -// for (k, v) in this_it { -// this_args.set(&k, &ipld::Newtype(v.clone()).into()); -// } -// -// let proof_it = proof -// .ability -// .args -// .iter() -// .map(|(k, v)| (JsValue::from(k), v)); -// -// let mut proof_args = js_sys::Map::new(); -// for (k, v) in proof_it { -// proof_args.set(&k, &ipld::Newtype(v.clone()).into()); -// } -// -// self.validator.check_same( -// &Object::from_entries(&this_args)?, -// &Object::from_entries(&proof_args)?, -// ) -// } -// } -// -// // pub struct Ability { -// // pub cmd: String, -// // pub args: BTreeMap, // FIXME args -// // pub val: JsValidator, -// // } -// // -// // #[wasm_bindgen] -// // impl Ability { -// // #[wasm_bindgen(constructor)] -// // fn new( -// // cmd: String, -// // args: BTreeMap, -// // validator: JsValidator, -// // ) -> Result { -// // let args = args -// // .iter() -// // .map(|(k, v)| (k.clone(), JsValue::from(v.clone()))) -// // .collect(); -// // -// // validator.check_shape(args)?; -// // Ok(Ability { cmd, args, val }) -// // } -// // } -// // -// // pub struct Pipeline { -// // pub validators: Vec, -// // } +pub mod parentful; +pub mod parentless; diff --git a/src/ability/js/parentful.rs b/src/ability/js/parentful.rs new file mode 100644 index 00000000..ed103b7b --- /dev/null +++ b/src/ability/js/parentful.rs @@ -0,0 +1,128 @@ +use crate::{ + ability::{arguments::Arguments, command::ToCommand, dynamic}, + ipld, + proof::{checkable::Checkable, parentful::Parentful, parents::CheckParents, same::CheckSame}, +}; +use js_sys::{Function, Map, Object, Reflect}; +use libipld_core::ipld::Ipld; +use std::collections::BTreeMap; +use wasm_bindgen::{prelude::*, JsValue}; + +// FIXME rename to module +#[derive(Debug, Clone, PartialEq)] +#[wasm_bindgen] +pub struct JsWithParents { + // FIXME just inline and use from + #[wasm_bindgen(skip)] + pub ability: dynamic::Dynamic, + + #[wasm_bindgen(skip)] + pub config: Config, + + #[wasm_bindgen(skip)] + pub check_parents: Function, +} + +// FIXME just inline +#[derive(Debug, Clone, PartialEq)] +pub struct Config { + pub is_nonce_meaningful: bool, + pub validate_shape: Function, + pub check_same: Function, +} + +#[wasm_bindgen] +impl JsWithParents { + // FIXME consider using an object with named fields + // FIXME needs borrows? + #[wasm_bindgen(constructor)] + pub fn new( + cmd: String, + args: Object, + is_nonce_meaningful: bool, + validate_shape: js_sys::Function, // FIXME Need to actuallyrun this on create + check_same: js_sys::Function, + check_parents: js_sys::Function, // FIXME what is an object? i.e. {cmd: handler}? + ) -> JsWithParents { + JsWithParents { + ability: dynamic::Dynamic { + cmd, + args: (&args).into(), + }, + config: Config { + is_nonce_meaningful, + validate_shape, + check_same, + }, + check_parents, + } + } + + #[wasm_bindgen(getter)] + pub fn is_nonce_meaningful(&self) -> bool { + self.config.is_nonce_meaningful + } + + pub fn check_shape(&self) -> Result<(), JsValue> { + let this = wasm_bindgen::JsValue::NULL; + self.config + .validate_shape + .call1(&this, &self.ability.args.clone().into()) + .map(|_| ()) + } + + // FIXME throws on Err + pub fn check_same(&self, proof: &Object) -> Result<(), JsValue> { + let this = wasm_bindgen::JsValue::NULL; + self.config + .check_same + .call2(&this, &self.ability.args.clone().into(), proof) + .map(|_| ()) + } + + // FIXME work with native Rust abilities, too + pub fn check_parents(&self, parent: &Object) -> Result<(), JsValue> { + let this = wasm_bindgen::JsValue::NULL; + self.check_parents + .call2(&this, &self.ability.args.clone().into(), parent) + .map(|_| ()) + } +} + +// FIXME while this can totally be done by converting to the dynamic carrier type, this seems more straightforward? +impl CheckSame for JsWithParents { + type Error = JsValue; + + fn check_same(&self, proof: &Self) -> Result<(), Self::Error> { + self.check_same(&proof.ability.args.clone().into()) + } +} + +impl CheckParents for JsWithParents { + type Parents = dynamic::Dynamic; + type ParentError = JsValue; + + fn check_parents(&self, parent: &dynamic::Dynamic) -> Result<(), Self::Error> { + let obj = Object::new(); + Reflect::set(&obj, &"cmd".into(), &parent.cmd.clone().into())?; + Reflect::set(&obj, &"args".into(), &parent.args.clone().into())?; + + self.check_parents(&obj) + } +} + +impl ToCommand for JsWithParents { + fn to_command(&self) -> String { + self.ability.cmd.clone() + } +} + +impl From for Arguments { + fn from(js: JsWithParents) -> Self { + js.ability.into() + } +} + +impl Checkable for JsWithParents { + type Hierarchy = Parentful; +} diff --git a/src/ability/js/parentless.rs b/src/ability/js/parentless.rs new file mode 100644 index 00000000..75296b92 --- /dev/null +++ b/src/ability/js/parentless.rs @@ -0,0 +1,100 @@ +use crate::{ + ability::{arguments::Arguments, command::ToCommand, dynamic}, + ipld, + proof::{checkable::Checkable, parentless::Parentless, same::CheckSame}, +}; +use js_sys::{Function, Map, Object, Reflect}; +use libipld_core::ipld::Ipld; +use std::collections::BTreeMap; +use wasm_bindgen::{prelude::*, JsValue}; + +#[derive(Debug, Clone, PartialEq)] +#[wasm_bindgen] +pub struct JsWithoutParents { + #[wasm_bindgen(skip)] + pub ability: dynamic::Dynamic, + + #[wasm_bindgen(skip)] + pub config: Config, +} + +// FIXME just inline +#[derive(Debug, Clone, PartialEq)] +pub struct Config { + pub is_nonce_meaningful: bool, + pub validate_shape: Function, + pub check_same: Function, +} + +#[wasm_bindgen] +impl JsWithoutParents { + // FIXME consider using an object with named fields + // FIXME needs borrows? + #[wasm_bindgen(constructor)] + pub fn new( + cmd: String, + args: Object, + is_nonce_meaningful: bool, + validate_shape: js_sys::Function, + check_same: js_sys::Function, + ) -> JsWithoutParents { + JsWithoutParents { + ability: dynamic::Dynamic { + cmd, + args: (&args).into(), + }, + config: Config { + is_nonce_meaningful, + validate_shape, + check_same, + }, + } + } + + #[wasm_bindgen(getter)] + pub fn is_nonce_meaningful(&self) -> bool { + self.config.is_nonce_meaningful + } + + pub fn check_shape(&self) -> Result<(), JsValue> { + let this = wasm_bindgen::JsValue::NULL; + self.config + .validate_shape + .call1(&this, &self.ability.args.clone().into()) + .map(|_| ()) + } + + // FIXME throws on Err + pub fn check_same(&self, proof: &Object) -> Result<(), JsValue> { + let this = wasm_bindgen::JsValue::NULL; + self.config + .check_same + .call2(&this, &self.ability.args.clone().into(), proof) + .map(|_| ()) + } +} + +// FIXME while this can totally be done by converting to the dynamic carrier type, this seems more straightforward? +impl CheckSame for JsWithoutParents { + type Error = JsValue; + + fn check_same(&self, proof: &Self) -> Result<(), Self::Error> { + self.check_same(&proof.ability.args.clone().into()) + } +} + +impl ToCommand for JsWithoutParents { + fn to_command(&self) -> String { + self.ability.cmd.clone() + } +} + +impl From for Arguments { + fn from(js: JsWithoutParents) -> Self { + js.ability.into() + } +} + +impl Checkable for JsWithoutParents { + type Hierarchy = Parentless; +} diff --git a/src/ability/ucan.rs b/src/ability/ucan.rs index e2ee2867..af18fe21 100644 --- a/src/ability/ucan.rs +++ b/src/ability/ucan.rs @@ -6,6 +6,8 @@ use std::fmt::Debug; // NOTE This one is primarily for enabling delegationd recipets +// FIXME aslo add revokation, so thsi module needs to be broken up + // FIXME #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] struct Generic { diff --git a/src/ipld.rs b/src/ipld.rs index 7a8c6281..1c781e26 100644 --- a/src/ipld.rs +++ b/src/ipld.rs @@ -6,7 +6,7 @@ pub mod cid; use wasm_bindgen::prelude::*; #[cfg(target_arch = "wasm32")] -use js_sys::{Array, Map, Uint8Array}; +use js_sys::{Array, Map, Object, Uint8Array}; pub struct Newtype(pub Ipld); @@ -129,28 +129,26 @@ impl TryFrom for Newtype { } if js_val.is_object() { + let obj = Object::from(js_val); let mut m = std::collections::BTreeMap::new(); let mut acc = Ok(()); - // This order is correct per the docs - // vvvvvvvvvv - Map::from(js_val).for_each(&mut |value, key| { + Object::entries(&obj).for_each(&mut |js_val, _, _| { if acc.is_err() { return; } - match key.as_string() { - None => { + // By definition this must be the array [value, key], in that order + let arr = Array::from(&js_val); + + match (arr.get(0).as_string(), Newtype::try_from(arr.get(1))) { + (Some(k), Ok(v)) => { + m.insert(k, v.0); + } + // FIXME more specific errors + _ => { acc = Err(()); } - Some(k) => match Newtype::try_from(value) { - Err(_) => { - acc = Err(()); - } - Ok(v) => { - m.insert(k, v.0); - } - }, } }); From 1b3667459b789a315b439e9143a4006128728738 Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Sat, 3 Feb 2024 16:02:25 -0800 Subject: [PATCH 045/188] Abstracted out lots of dynamic parts / added hooks for lots of things --- src/ability/dynamic.rs | 225 ++++++++++++++++------------------- src/ability/js/parentful.rs | 155 ++++++++++++------------ src/ability/js/parentless.rs | 101 +++++++--------- src/promise.rs | 33 ++++- 4 files changed, 253 insertions(+), 261 deletions(-) diff --git a/src/ability/dynamic.rs b/src/ability/dynamic.rs index 4ad98974..0ced56ff 100644 --- a/src/ability/dynamic.rs +++ b/src/ability/dynamic.rs @@ -23,18 +23,106 @@ use wasm_bindgen::prelude::*; // NOTE the lack of checking functions! // This is meant to be embedded inside of structs that have e.g. FFI bindings to // a validation function, such as a &js_sys::Function, Ruby magnus::function!, etc etc -#[derive(Clone, PartialEq, Debug)] // FIXME serialize / deserilaize? +#[derive(Clone, PartialEq, Debug, Serialize, Deserialize)] // FIXME serialize / deserilaize? pub struct Dynamic { pub cmd: String, pub args: Arguments, } +// NOTE plug this into Configured like: Configured> +pub struct Builder(pub T); +pub struct Promised(pub T); + +impl> From> for Arguments { + fn from(builder: Builder) -> Self { + builder.0.into() + } +} + +impl From> for Builder> { + fn from(configured: Configured) -> Self { + Builder(configured) + } +} + +impl From>> for Configured { + fn from(builder: Builder>) -> Self { + builder.0 + } +} + +impl> From> for Arguments { + fn from(promised: Promised) -> Self { + promised.0.into() + } +} + +impl From> for Promised> { + fn from(configured: Configured) -> Self { + Promised(configured) + } +} + +impl From>> for Configured { + fn from(promised: Promised>) -> Self { + promised.0 + } +} + +// NOTE to self: this is helpful as a common container to lift various FFI into +#[derive(Clone, PartialEq, Debug)] +pub struct Configured { + pub arguments: Arguments, + pub config: T, +} + +impl Delegatable for Configured { + type Builder = Builder>; +} + +impl Resolvable for Configured { + type Promised = Promised>; +} + +impl ToCommand for Configured { + fn to_command(&self) -> String { + self.config.to_command() + } +} + +impl CheckSame for Configured { + type Error = T::Error; + + fn check_same(&self, proof: &Self) -> Result<(), Self::Error> { + self.config.check_same(&proof.config) + } +} + +impl CheckParents for Configured { + type Parents = Dynamic; + type ParentError = T::ParentError; + + fn check_parents(&self, parent: &Dynamic) -> Result<(), Self::ParentError> { + self.check_parents(parent) + } +} + +impl From> for Arguments { + fn from(reader: Configured) -> Self { + reader.arguments + } +} + impl From for Arguments { fn from(dynamic: Dynamic) -> Self { dynamic.args } } +impl Checkable for Configured { + type Hierarchy = T::Hierarchy; +} + #[cfg(target_arch = "wasm32")] impl From for js_sys::Map { fn from(ability: Dynamic) -> Self { @@ -104,125 +192,16 @@ impl CheckSame for Dynamic { } } -// impl Debug for Generic { -// fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { -// f.debug_struct("Generic") -// .field("cmd", &self.cmd) -// .field("args", &self.args) -// .field("is_nonce_meaningful", &self.is_nonce_meaningful) -// .finish() -// } -// } -// -// pub type Dynamic = Generic; -// pub type Promised = Generic, F>; -// -// impl Serialize for Generic -// where -// Arguments: From, -// { -// fn serialize(&self, serializer: S) -> Result -// where -// S: Serializer, -// { -// let mut map = serializer.serialize_map(Some(2))?; -// map.serialize_entry("cmd", &self.cmd)?; -// map.serialize_entry("args", &Arguments::from(self.args.clone()))?; -// map.end() -// } -// } -// -// impl<'de, Args: Deserialize<'de>> Deserialize<'de> for Generic { -// fn deserialize(deserializer: D) -> Result, D::Error> -// where -// D: Deserializer<'de>, -// { -// // FIXME -// todo!() -// // let btree = BTreeMap::deserialize(deserializer)?; -// // Ok(Generic { -// // cmd: btree.get("cmd")?.to_string(), -// // args: btree.get("args")?.clone(), -// // is_nonce_meaningful: DefaultTrue::default(), -// // -// // same_validator: (), -// // parent_validator: (), -// // shape_validator: (), -// // }) -// } -// } -// -// impl ToCommand for Generic { -// fn to_command(&self) -> String { -// self.cmd.clone() -// } -// } -// -// impl Delegatable for Dynamic { -// type Builder = Dynamic; -// } -// -// impl Resolvable for Dynamic { -// type Promised = Promised; -// } -// -// impl From> for Ipld { -// fn from(generic: Generic) -> Self { -// generic.into() -// } -// } -// -// impl TryFrom for Generic { -// type Error = SerdeError; -// -// fn try_from(ipld: Ipld) -> Result { -// ipld_serde::from_ipld(ipld) -// } -// } -// -// impl, F> From> for Arguments { -// fn from(generic: Generic) -> Self { -// generic.args.into() -// } -// } -// -// impl TryFrom> for Dynamic { -// type Error = (); // FIXME -// -// fn try_from(awaiting: Promised) -> Result { -// if let Promise::Resolved(args) = &awaiting.args { -// Ok(Dynamic { -// cmd: awaiting.cmd, -// args: args.clone(), -// -// same_validator: awaiting.same_validator, -// parent_validator: awaiting.parent_validator, -// shape_validator: awaiting.shape_validator, -// is_nonce_meaningful: awaiting.is_nonce_meaningful, -// }) -// } else { -// Err(()) -// } -// } -// } -// -// impl From> for Promised { -// fn from(d: Dynamic) -> Self { -// Promised { -// cmd: d.cmd, -// args: Promise::Resolved(d.args), -// -// same_validator: d.same_validator, -// parent_validator: d.parent_validator, -// shape_validator: d.shape_validator, -// is_nonce_meaningful: d.is_nonce_meaningful, -// } -// } -// } -// -// impl Checkable for Dynamic -// where -// F: Fn(&String, &Arguments) -> Result<(), String>, -// { -// type Hierarchy = Parentless>; // FIXME I bet we can revover parents -// } +impl From for Ipld { + fn from(dynamic: Dynamic) -> Self { + dynamic.into() + } +} + +impl TryFrom for Dynamic { + type Error = SerdeError; + + fn try_from(ipld: Ipld) -> Result { + ipld_serde::from_ipld(ipld) + } +} diff --git a/src/ability/js/parentful.rs b/src/ability/js/parentful.rs index ed103b7b..cb41549a 100644 --- a/src/ability/js/parentful.rs +++ b/src/ability/js/parentful.rs @@ -3,89 +3,81 @@ use crate::{ ipld, proof::{checkable::Checkable, parentful::Parentful, parents::CheckParents, same::CheckSame}, }; -use js_sys::{Function, Map, Object, Reflect}; +use js_sys::{Function, JsString, Map, Object, Reflect}; use libipld_core::ipld::Ipld; use std::collections::BTreeMap; use wasm_bindgen::{prelude::*, JsValue}; -// FIXME rename to module -#[derive(Debug, Clone, PartialEq)] -#[wasm_bindgen] -pub struct JsWithParents { - // FIXME just inline and use from - #[wasm_bindgen(skip)] - pub ability: dynamic::Dynamic, - - #[wasm_bindgen(skip)] - pub config: Config, +// NOTE NOTE NOTE: the strategy is: "you (JS) hand us the cfg" AKA strategy, +// and we (Rust) wire it up and run it for you +// NOTE becuase of the above, no need to export JsWithParents to JS +// FIXME rename +type JsWithParents = dynamic::Configured; - #[wasm_bindgen(skip)] - pub check_parents: Function, -} +// Promise = Promise? Ah, nope becuase we need that CID on the promise +// FIXME represent promises (for Promised) and options (for builder) -// FIXME just inline +// FIXME rename ability? abilityconfig? leave as is? #[derive(Debug, Clone, PartialEq)] +#[wasm_bindgen(getter_with_clone)] pub struct Config { + pub command: String, pub is_nonce_meaningful: bool, + pub validate_shape: Function, pub check_same: Function, + + #[wasm_bindgen(skip)] + pub check_parents: BTreeMap>, } #[wasm_bindgen] -impl JsWithParents { - // FIXME consider using an object with named fields - // FIXME needs borrows? +impl Config { + // FIXME object args as an option #[wasm_bindgen(constructor)] pub fn new( - cmd: String, - args: Object, + command: String, is_nonce_meaningful: bool, - validate_shape: js_sys::Function, // FIXME Need to actuallyrun this on create - check_same: js_sys::Function, - check_parents: js_sys::Function, // FIXME what is an object? i.e. {cmd: handler}? - ) -> JsWithParents { - JsWithParents { - ability: dynamic::Dynamic { - cmd, - args: (&args).into(), + validate_shape: Function, + check_same: Function, + check_parents: Map, // FIXME swap for an object? + ) -> Result { + Ok(Config { + command, + is_nonce_meaningful, + validate_shape, + check_same, + check_parents: { + let mut btree = BTreeMap::new(); + let mut acc = Ok(()); + // Correct order + check_parents.for_each(&mut |value, key| { + if let Ok(_) = &acc { + match key.as_string() { + None => acc = Err(JsString::from("Key is not a string")), // FIXME better err + Some(str_key) => match value.dyn_ref::() { + None => acc = Err("Value is not a function".into()), + Some(f) => { + btree.insert(str_key, Box::new(f.clone())); + acc = Ok(()); + } + }, + } + } + }); + + acc.map(|_| btree)? }, - config: Config { - is_nonce_meaningful, - validate_shape, - check_same, - }, - check_parents, - } - } - - #[wasm_bindgen(getter)] - pub fn is_nonce_meaningful(&self) -> bool { - self.config.is_nonce_meaningful - } - - pub fn check_shape(&self) -> Result<(), JsValue> { - let this = wasm_bindgen::JsValue::NULL; - self.config - .validate_shape - .call1(&this, &self.ability.args.clone().into()) - .map(|_| ()) - } - - // FIXME throws on Err - pub fn check_same(&self, proof: &Object) -> Result<(), JsValue> { - let this = wasm_bindgen::JsValue::NULL; - self.config - .check_same - .call2(&this, &self.ability.args.clone().into(), proof) - .map(|_| ()) + }) } +} - // FIXME work with native Rust abilities, too - pub fn check_parents(&self, parent: &Object) -> Result<(), JsValue> { - let this = wasm_bindgen::JsValue::NULL; - self.check_parents - .call2(&this, &self.ability.args.clone().into(), parent) - .map(|_| ()) +impl From for dynamic::Dynamic { + fn from(js: JsWithParents) -> Self { + dynamic::Dynamic { + cmd: js.config.command, + args: js.arguments, + } } } @@ -94,7 +86,15 @@ impl CheckSame for JsWithParents { type Error = JsValue; fn check_same(&self, proof: &Self) -> Result<(), Self::Error> { - self.check_same(&proof.ability.args.clone().into()) + let this = wasm_bindgen::JsValue::NULL; + self.config + .check_same + .call2( + &this, + &self.arguments.clone().into(), + &Arguments::from(proof.clone()).into(), + ) + .map(|_| ()) } } @@ -103,23 +103,24 @@ impl CheckParents for JsWithParents { type ParentError = JsValue; fn check_parents(&self, parent: &dynamic::Dynamic) -> Result<(), Self::Error> { - let obj = Object::new(); - Reflect::set(&obj, &"cmd".into(), &parent.cmd.clone().into())?; - Reflect::set(&obj, &"args".into(), &parent.args.clone().into())?; - - self.check_parents(&obj) + if let Some(handler) = self.config.check_parents.get(&parent.cmd) { + let this = wasm_bindgen::JsValue::NULL; + handler + .call2( + &this, + &self.arguments.clone().into(), + &parent.args.clone().into(), + ) + .map(|_| ()) + } else { + Err(JsValue::from("No handler for parent")) + } } } -impl ToCommand for JsWithParents { +impl ToCommand for Config { fn to_command(&self) -> String { - self.ability.cmd.clone() - } -} - -impl From for Arguments { - fn from(js: JsWithParents) -> Self { - js.ability.into() + self.command.clone() } } diff --git a/src/ability/js/parentless.rs b/src/ability/js/parentless.rs index 75296b92..e486fd23 100644 --- a/src/ability/js/parentless.rs +++ b/src/ability/js/parentless.rs @@ -1,76 +1,57 @@ use crate::{ ability::{arguments::Arguments, command::ToCommand, dynamic}, ipld, - proof::{checkable::Checkable, parentless::Parentless, same::CheckSame}, + proof::{checkable::Checkable, parentless::Parentless, parents::CheckParents, same::CheckSame}, }; -use js_sys::{Function, Map, Object, Reflect}; +use js_sys::{Function, JsString, Map, Object, Reflect}; use libipld_core::ipld::Ipld; use std::collections::BTreeMap; use wasm_bindgen::{prelude::*, JsValue}; -#[derive(Debug, Clone, PartialEq)] -#[wasm_bindgen] -pub struct JsWithoutParents { - #[wasm_bindgen(skip)] - pub ability: dynamic::Dynamic, - - #[wasm_bindgen(skip)] - pub config: Config, -} +// NOTE NOTE NOTE: the strategy is: "you (JS) hand us the cfg" AKA strategy, +// and we (Rust) wire it up and run it for you +// NOTE becuase of the above, no need to export JsWithParents to JS +// FIXME rename +type JsWithoutParents = dynamic::Configured; -// FIXME just inline +// FIXME rename ability? abilityconfig? leave as is? #[derive(Debug, Clone, PartialEq)] +#[wasm_bindgen(getter_with_clone)] pub struct Config { + pub command: String, pub is_nonce_meaningful: bool, + pub validate_shape: Function, pub check_same: Function, } +// FIXME represent promises (for Promised) and options (for builder) + #[wasm_bindgen] -impl JsWithoutParents { - // FIXME consider using an object with named fields - // FIXME needs borrows? +impl Config { + // FIXME object args as an option #[wasm_bindgen(constructor)] pub fn new( - cmd: String, - args: Object, + command: String, is_nonce_meaningful: bool, - validate_shape: js_sys::Function, - check_same: js_sys::Function, - ) -> JsWithoutParents { - JsWithoutParents { - ability: dynamic::Dynamic { - cmd, - args: (&args).into(), - }, - config: Config { - is_nonce_meaningful, - validate_shape, - check_same, - }, + validate_shape: Function, + check_same: Function, + ) -> Config { + Config { + command, + is_nonce_meaningful, + validate_shape, + check_same, } } +} - #[wasm_bindgen(getter)] - pub fn is_nonce_meaningful(&self) -> bool { - self.config.is_nonce_meaningful - } - - pub fn check_shape(&self) -> Result<(), JsValue> { - let this = wasm_bindgen::JsValue::NULL; - self.config - .validate_shape - .call1(&this, &self.ability.args.clone().into()) - .map(|_| ()) - } - - // FIXME throws on Err - pub fn check_same(&self, proof: &Object) -> Result<(), JsValue> { - let this = wasm_bindgen::JsValue::NULL; - self.config - .check_same - .call2(&this, &self.ability.args.clone().into(), proof) - .map(|_| ()) +impl From for dynamic::Dynamic { + fn from(js: JsWithoutParents) -> Self { + dynamic::Dynamic { + cmd: js.config.command, + args: js.arguments, + } } } @@ -79,19 +60,21 @@ impl CheckSame for JsWithoutParents { type Error = JsValue; fn check_same(&self, proof: &Self) -> Result<(), Self::Error> { - self.check_same(&proof.ability.args.clone().into()) + let this = wasm_bindgen::JsValue::NULL; + self.config + .check_same + .call2( + &this, + &self.arguments.clone().into(), + &Arguments::from(proof.clone()).into(), + ) + .map(|_| ()) } } -impl ToCommand for JsWithoutParents { +impl ToCommand for Config { fn to_command(&self) -> String { - self.ability.cmd.clone() - } -} - -impl From for Arguments { - fn from(js: JsWithoutParents) -> Self { - js.ability.into() + self.command.clone() } } diff --git a/src/promise.rs b/src/promise.rs index 3a0792cc..78b0f0f9 100644 --- a/src/promise.rs +++ b/src/promise.rs @@ -3,6 +3,9 @@ use libipld_core::{cid::Cid, ipld::Ipld, serde as ipld_serde}; use serde_derive::{Deserialize, Serialize}; use std::{collections::BTreeMap, fmt::Debug}; +#[cfg(target_arch = "wasm32")] +use wasm_bindgen::prelude::*; + // FIXME move under invocation? #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] @@ -12,6 +15,32 @@ pub enum Promise { Waiting(Selector), } +#[cfg(target_arch = "wasm32")] +pub enum JsStatus { + Resolved, + Waiting, +} + +// FIXME no way to make this consistent, because of C enums ruining Rust convetions, right? +// FIXME consider wrapping in a trait +#[cfg(target_arch = "wasm32")] +pub struct JsPromise { + status: JsStatus, + selector: Selector, + value: Option, +} + +// TODO remove; I'd rather have liine 70 blanket than this +// #[cfg(target_arch = "wasm32")] +// impl> From for Promise { +// fn from(js: JsPromise) -> Self { +// match js.status { +// JsStatus::Resolved => Promise::Resolved(js.value.unwrap().into()), +// JsStatus::Waiting => Promise::Waiting(js.selector), +// } +// } +// } + impl Promise { pub fn map(self, f: F) -> Promise where @@ -43,8 +72,8 @@ impl Promise { } impl From for Promise { - fn from(t: T) -> Self { - Promise::Resolved(t) + fn from(value: T) -> Self { + Promise::Resolved(value) } } From 9209f75128ee3ecc617dde988cefe42f5dda36af Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Sun, 4 Feb 2024 11:25:57 -0800 Subject: [PATCH 046/188] Save before compiling JS bindings --- src/ability/crud/read.rs | 20 +++++++++++++++++++- src/ability/js/parentful.rs | 2 +- 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/src/ability/crud/read.rs b/src/ability/crud/read.rs index f98fc969..d04d887b 100644 --- a/src/ability/crud/read.rs +++ b/src/ability/crud/read.rs @@ -8,6 +8,9 @@ use serde::{Deserialize, Serialize}; use std::collections::BTreeMap; use url::Url; +#[cfg(target_arch = "wasm32")] +use wasm_bindgen::prelude::*; + // Read is its own builder #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(deny_unknown_fields)] @@ -16,7 +19,22 @@ pub struct Read { pub uri: Option, #[serde(default, skip_serializing_if = "Option::is_none")] - pub args: Option>, + pub args: Option>, // FIXME rename Argumenst to get the traits? +} + +#[cfg(target_arch = "wasm32")] +#[wasm_bindgen] +pub struct Js(#[wasm_bindgen(skip)] pub Read); + +#[cfg(target_arch = "wasm32")] +#[wasm_bindgen] +impl Js { + // FIXME + pub fn check_same(&self, proof: &Js) -> Result<(), JsValue> { + self.0 + .check_same(&proof.0) + .map_err(|err| JsValue::from_str(&format!("{:?}", err))) + } } impl Command for Read { diff --git a/src/ability/js/parentful.rs b/src/ability/js/parentful.rs index cb41549a..726e2c15 100644 --- a/src/ability/js/parentful.rs +++ b/src/ability/js/parentful.rs @@ -99,7 +99,7 @@ impl CheckSame for JsWithParents { } impl CheckParents for JsWithParents { - type Parents = dynamic::Dynamic; + type Parents = dynamic::Dynamic; // FIXME actually no? What if we want to plug in random stuff? type ParentError = JsValue; fn check_parents(&self, parent: &dynamic::Dynamic) -> Result<(), Self::Error> { From d24f8f4c8f2ef1d74a7ea6a566e4be37905d162c Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Sun, 4 Feb 2024 15:14:59 -0800 Subject: [PATCH 047/188] Happily builds for wasm including JS bindgings --- flake.nix | 22 +++++++++++---- src/ability/js/parentful.rs | 52 +++++++++++++++++++++++++++--------- src/ability/js/parentless.rs | 27 +++++++++++-------- src/ability/ucan.rs | 2 +- tsconfig.json | 21 +++++++++++++++ 5 files changed, 95 insertions(+), 29 deletions(-) create mode 100644 tsconfig.json diff --git a/flake.nix b/flake.nix index a14844a0..7527ff89 100644 --- a/flake.nix +++ b/flake.nix @@ -131,10 +131,16 @@ command = "${cargo} build --release"; } { - name = "release:wasm"; + name = "release:wasm:web"; help = "Release for current host target"; category = "release"; - command = "${cargo} build --release --target=wasm32-unknown-unknown"; + command = "${wasm-pack} build --release --target=web"; + } + { + name = "release:wasm:nodejs"; + help = "Release for current host target"; + category = "release"; + command = "${wasm-pack} build --release --target=nodejs"; } # Build { @@ -150,10 +156,16 @@ command = "${cargo} build"; } { - name = "build:wasm"; - help = "Build for wasm32-unknown-unknown"; + name = "build:wasm:web"; + help = "Build for wasm32-unknown-unknown with web bindings"; + category = "build"; + command = "${wasm-pack} build --dev --target=web"; + } + { + name = "build:wasm:nodejs"; + help = "Build for wasm32-unknown-unknown with Node.js bindgings"; category = "build"; - command = "${cargo} build --target=wasm32-unknown-unknown"; + command = "${wasm-pack} build --dev --target=nodejs"; } { name = "build:node"; diff --git a/src/ability/js/parentful.rs b/src/ability/js/parentful.rs index 726e2c15..5593c975 100644 --- a/src/ability/js/parentful.rs +++ b/src/ability/js/parentful.rs @@ -18,7 +18,7 @@ type JsWithParents = dynamic::Configured; // FIXME represent promises (for Promised) and options (for builder) // FIXME rename ability? abilityconfig? leave as is? -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Clone, PartialEq, Default)] #[wasm_bindgen(getter_with_clone)] pub struct Config { pub command: String, @@ -31,27 +31,55 @@ pub struct Config { pub check_parents: BTreeMap>, } +#[wasm_bindgen(typescript_custom_section)] +const CONSTRUCTOR_WITH_MAP: &str = r#" +interface ConfigArgs { + command: string, + is_nonce_meaningful: boolean, + validate_shape: Function, + check_same: Function, + check_parents: Map +} +"#; + +#[wasm_bindgen] +extern "C" { + #[wasm_bindgen(typescript_type = "ConfigArgs")] + pub type ConfigArgs; + + pub fn command(this: &ConfigArgs) -> String; + + pub fn is_nonce_meaningful(this: &ConfigArgs) -> bool; + + pub fn validate_shape(this: &ConfigArgs) -> Function; + + pub fn check_same(this: &ConfigArgs) -> Function; + + pub fn check_parents(this: &ConfigArgs) -> Map; +} + #[wasm_bindgen] impl Config { // FIXME object args as an option - #[wasm_bindgen(constructor)] + #[wasm_bindgen(constructor, typescript_type = "ConfigArgs")] pub fn new( - command: String, - is_nonce_meaningful: bool, - validate_shape: Function, - check_same: Function, - check_parents: Map, // FIXME swap for an object? + js: ConfigArgs, + // command: String, + // is_nonce_meaningful: bool, + // validate_shape: Function, + // check_same: Function, + // check_parents: Map, // FIXME swap for an object? ) -> Result { Ok(Config { - command, - is_nonce_meaningful, - validate_shape, - check_same, + command: command(&js), + is_nonce_meaningful: is_nonce_meaningful(&js), + validate_shape: validate_shape(&js), + check_same: check_same(&js), check_parents: { let mut btree = BTreeMap::new(); let mut acc = Ok(()); // Correct order - check_parents.for_each(&mut |value, key| { + check_parents(&js).for_each(&mut |value, key| { if let Ok(_) = &acc { match key.as_string() { None => acc = Err(JsString::from("Key is not a string")), // FIXME better err diff --git a/src/ability/js/parentless.rs b/src/ability/js/parentless.rs index e486fd23..3ac77556 100644 --- a/src/ability/js/parentless.rs +++ b/src/ability/js/parentless.rs @@ -12,23 +12,28 @@ use wasm_bindgen::{prelude::*, JsValue}; // and we (Rust) wire it up and run it for you // NOTE becuase of the above, no need to export JsWithParents to JS // FIXME rename -type JsWithoutParents = dynamic::Configured; +type JsWithoutParents = dynamic::Configured; // FIXME rename ability? abilityconfig? leave as is? +// #[wasm_bindgen(getter_with_clone)] #[derive(Debug, Clone, PartialEq)] -#[wasm_bindgen(getter_with_clone)] -pub struct Config { - pub command: String, - pub is_nonce_meaningful: bool, +#[wasm_bindgen] +pub struct Config1 { + // #[wasm_bindgen(skip)] + command: String, + is_nonce_meaningful: bool, + + // #[wasm_bindgen(skip)] + validate_shape: Function, - pub validate_shape: Function, - pub check_same: Function, + //#[wasm_bindgen(skip)] + check_same: Function, } // FIXME represent promises (for Promised) and options (for builder) #[wasm_bindgen] -impl Config { +impl Config1 { // FIXME object args as an option #[wasm_bindgen(constructor)] pub fn new( @@ -36,8 +41,8 @@ impl Config { is_nonce_meaningful: bool, validate_shape: Function, check_same: Function, - ) -> Config { - Config { + ) -> Config1 { + Config1 { command, is_nonce_meaningful, validate_shape, @@ -72,7 +77,7 @@ impl CheckSame for JsWithoutParents { } } -impl ToCommand for Config { +impl ToCommand for Config1 { fn to_command(&self) -> String { self.command.clone() } diff --git a/src/ability/ucan.rs b/src/ability/ucan.rs index af18fe21..59ddbe7d 100644 --- a/src/ability/ucan.rs +++ b/src/ability/ucan.rs @@ -10,7 +10,7 @@ use std::fmt::Debug; // FIXME #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] -struct Generic { +pub struct Generic { pub cmd: String, pub args: Args, // FIXME Does this have specific fields? } diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 00000000..e40fe060 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,21 @@ +{ + "compileOnSave": false, + "compilerOptions": { + "baseUrl": "./", + "outDir": "./dist/out-tsc", + "sourceMap": true, + "declaration": false, + "module": "es2015", + "moduleResolution": "node", + "emitDecoratorMetadata": true, + "experimentalDecorators": true, + "target": "es5", + "typeRoots": [ + "node_modules/@types" + ], + "lib": [ + "es2017", + "dom" + ] + } +} From e5fdd538be6983e947d4fed36879f38677b84f82 Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Sun, 4 Feb 2024 15:48:30 -0800 Subject: [PATCH 048/188] Configure wasm-pack & wasm-opt --- flake.nix | 5 +++-- src/ability/js/parentful.rs | 30 ++++++++++-------------------- tsconfig.json | 35 ++++++++++++++++------------------- 3 files changed, 29 insertions(+), 41 deletions(-) diff --git a/flake.nix b/flake.nix index 7527ff89..2b63841f 100644 --- a/flake.nix +++ b/flake.nix @@ -86,6 +86,7 @@ cargo = "${pkgs.cargo}/bin/cargo"; node = "${unstable.nodejs_20}/bin/node"; wasm-pack = "${pkgs.wasm-pack}/bin/wasm-pack"; + wasm-opt = "${pkgs.binaryen}/bin/wasm-opt"; in rec { devShells.default = pkgs.devshell.mkShell { name = "ucan"; @@ -134,13 +135,13 @@ name = "release:wasm:web"; help = "Release for current host target"; category = "release"; - command = "${wasm-pack} build --release --target=web"; + command = "${wasm-pack} build --release --target=web && ${wasm-opt} -Os ./pkg/ucan_bg.wasm -o ./pkg/ucan_bg.opt.wasm && ls -lh pkg/*.wasm | cut -d ' ' -f 8,13"; } { name = "release:wasm:nodejs"; help = "Release for current host target"; category = "release"; - command = "${wasm-pack} build --release --target=nodejs"; + command = "${wasm-pack} build --release --target=nodejs && ${wasm-opt} -Os ./pkg/ucan_bg.wasm -o ./pkg/ucan_bg.opt.wasm && ls -lah pkg/*.wasm | cut -d ' ' -f 8,13"; } # Build { diff --git a/src/ability/js/parentful.rs b/src/ability/js/parentful.rs index 5593c975..b96c0101 100644 --- a/src/ability/js/parentful.rs +++ b/src/ability/js/parentful.rs @@ -32,7 +32,7 @@ pub struct Config { } #[wasm_bindgen(typescript_custom_section)] -const CONSTRUCTOR_WITH_MAP: &str = r#" +const CONFIG_ARGS: &str = r#" interface ConfigArgs { command: string, is_nonce_meaningful: boolean, @@ -48,38 +48,28 @@ extern "C" { pub type ConfigArgs; pub fn command(this: &ConfigArgs) -> String; - pub fn is_nonce_meaningful(this: &ConfigArgs) -> bool; - pub fn validate_shape(this: &ConfigArgs) -> Function; - pub fn check_same(this: &ConfigArgs) -> Function; - pub fn check_parents(this: &ConfigArgs) -> Map; } #[wasm_bindgen] impl Config { // FIXME object args as an option - #[wasm_bindgen(constructor, typescript_type = "ConfigArgs")] - pub fn new( - js: ConfigArgs, - // command: String, - // is_nonce_meaningful: bool, - // validate_shape: Function, - // check_same: Function, - // check_parents: Map, // FIXME swap for an object? - ) -> Result { + #[wasm_bindgen(constructor)] + pub fn new(js_obj: ConfigArgs) -> Result { Ok(Config { - command: command(&js), - is_nonce_meaningful: is_nonce_meaningful(&js), - validate_shape: validate_shape(&js), - check_same: check_same(&js), + command: command(&js_obj), + is_nonce_meaningful: is_nonce_meaningful(&js_obj), + validate_shape: validate_shape(&js_obj), + check_same: check_same(&js_obj), check_parents: { let mut btree = BTreeMap::new(); let mut acc = Ok(()); - // Correct order - check_parents(&js).for_each(&mut |value, key| { + + check_parents(&js_obj).for_each(&mut |value, key| { + // |value, key| is correct ------^^^^^^^^^^^^ if let Ok(_) = &acc { match key.as_string() { None => acc = Err(JsString::from("Key is not a string")), // FIXME better err diff --git a/tsconfig.json b/tsconfig.json index e40fe060..b6828dc2 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,21 +1,18 @@ { - "compileOnSave": false, - "compilerOptions": { - "baseUrl": "./", - "outDir": "./dist/out-tsc", - "sourceMap": true, - "declaration": false, - "module": "es2015", - "moduleResolution": "node", - "emitDecoratorMetadata": true, - "experimentalDecorators": true, - "target": "es5", - "typeRoots": [ - "node_modules/@types" - ], - "lib": [ - "es2017", - "dom" - ] - } + "compileOnSave": false, + "compilerOptions": { + "baseUrl": "./", + "outDir": "./dist/out-tsc", + "sourceMap": true, + "module": "es2015", + "emitDecoratorMetadata": true, + "experimentalDecorators": true, + "target": "es5", + "typeRoots": [ + "node_modules/@types" + ], + "lib": [ + "es2015" + ] + } } From 220c9a4c4d845815d72aab8c89e9ee2f27f0b092 Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Sun, 4 Feb 2024 23:59:07 -0800 Subject: [PATCH 049/188] Starting on high-level interfaces :D --- flake.nix | 4 +- src/ability.rs | 2 +- src/ability/arguments.rs | 4 +- src/ability/command.rs | 5 +- src/ability/crud/any.rs | 22 ++++++ src/ability/crud/create.rs | 2 +- src/ability/crud/destroy.rs | 6 +- src/ability/crud/mutate.rs | 2 +- src/ability/crud/parents.rs | 2 +- src/ability/crud/read.rs | 74 ++++++++++++++------ src/ability/crud/update.rs | 2 +- src/ability/dynamic.rs | 102 ++------------------------- src/ability/js/parentful.rs | 73 +++++++++----------- src/ability/js/parentless.rs | 79 +++++++++++---------- src/ability/msg/receive.rs | 2 +- src/ability/msg/send.rs | 17 +++-- src/ability/ucan.rs | 86 +---------------------- src/ability/ucan/proxy.rs | 87 +++++++++++++++++++++++ src/agent.rs | 59 ++++++++++++++++ src/delegation.rs | 31 ++++++++- src/delegation/payload.rs | 9 +-- src/delegation/store.rs | 83 ++++++++++++++++++++++ src/did.rs | 13 ++-- src/invocation.rs | 4 +- src/invocation/payload.rs | 40 ++++++----- src/{ => invocation}/promise.rs | 103 ++++++++++++++++++--------- src/lib.rs | 3 +- src/nonce.rs | 3 - src/proof/parentful.rs | 8 +-- src/proof/parents.rs | 2 +- src/reader.rs | 119 ++++++++++++++++++++++++++++++++ src/task.rs | 25 +------ src/time.rs | 3 +- 33 files changed, 672 insertions(+), 404 deletions(-) create mode 100644 src/ability/ucan/proxy.rs create mode 100644 src/agent.rs create mode 100644 src/delegation/store.rs rename src/{ => invocation}/promise.rs (54%) create mode 100644 src/reader.rs diff --git a/flake.nix b/flake.nix index 2b63841f..9cbbda01 100644 --- a/flake.nix +++ b/flake.nix @@ -135,13 +135,13 @@ name = "release:wasm:web"; help = "Release for current host target"; category = "release"; - command = "${wasm-pack} build --release --target=web && ${wasm-opt} -Os ./pkg/ucan_bg.wasm -o ./pkg/ucan_bg.opt.wasm && ls -lh pkg/*.wasm | cut -d ' ' -f 8,13"; + command = "${wasm-pack} build --release --target=web"; } { name = "release:wasm:nodejs"; help = "Release for current host target"; category = "release"; - command = "${wasm-pack} build --release --target=nodejs && ${wasm-opt} -Os ./pkg/ucan_bg.wasm -o ./pkg/ucan_bg.opt.wasm && ls -lah pkg/*.wasm | cut -d ' ' -f 8,13"; + command = "${wasm-pack} build --release --target=nodejs"; } # Build { diff --git a/src/ability.rs b/src/ability.rs index a6b095ad..fe0363ba 100644 --- a/src/ability.rs +++ b/src/ability.rs @@ -10,7 +10,7 @@ pub mod command; #[cfg(target_arch = "wasm32")] pub mod js; -// // TODO move to crate::wasm? or hide behind feature flag? +// // TODO move to crate::wasm? or hide behind "dynamic" feature flag? #[cfg(target_arch = "wasm32")] pub mod dynamic; diff --git a/src/ability/arguments.rs b/src/ability/arguments.rs index b67f6608..9a6e28cb 100644 --- a/src/ability/arguments.rs +++ b/src/ability/arguments.rs @@ -6,13 +6,11 @@ use std::collections::BTreeMap; use wasm_bindgen::prelude::*; #[cfg(target_arch = "wasm32")] -use js_sys::{Array, Map, Object, Reflect, Uint8Array}; +use js_sys::{Array, Map, Object, Reflect}; #[cfg(target_arch = "wasm32")] use crate::ipld; -use super::wasm; - // FIXME yes I'm seriously considering laying this out in the wasm abi by han d // #[cfg(not(target_arch = "wasm32"))] #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] diff --git a/src/ability/command.rs b/src/ability/command.rs index bd86bf36..a158221e 100644 --- a/src/ability/command.rs +++ b/src/ability/command.rs @@ -2,8 +2,9 @@ pub trait Command { const COMMAND: &'static str; } -// NOTE do not export // FIXME move to internal? -pub(super) trait ToCommand { +// NOTE do not export; this is used to limit the Hierarchy +// interface to [Parentful] and [Parentless] while enabling [Dynamic] +pub(crate) trait ToCommand { fn to_command(&self) -> String; } diff --git a/src/ability/crud/any.rs b/src/ability/crud/any.rs index 909d09a6..15149fa3 100644 --- a/src/ability/crud/any.rs +++ b/src/ability/crud/any.rs @@ -8,6 +8,9 @@ use crate::{ use serde::{Deserialize, Serialize}; use url::Url; +#[cfg(target_arch = "wasm32")] +use wasm_bindgen::prelude::*; + // NOTE no resolved or awaiting variants, because this cannot be executed, and all fields are optional already! #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] @@ -29,3 +32,22 @@ impl CheckSame for Builder { self.uri.check_same(&proof.uri) } } + +#[cfg(target_arch = "wasm32")] +#[wasm_bindgen] +pub struct CrudAny(#[wasm_bindgen(skip)] pub Builder); + +// FIXME macro this away +#[cfg(target_arch = "wasm32")] +#[wasm_bindgen] +impl CrudAny { + pub fn command(&self) -> String { + Builder::COMMAND.to_string() + } + + pub fn check_same(&self, proof: &CrudAny) -> Result<(), JsValue> { + self.0 + .check_same(&proof.0) + .map_err(|err| JsValue::from_str(&format!("{:?}", err))) + } +} diff --git a/src/ability/crud/create.rs b/src/ability/crud/create.rs index a1452d29..5270fb2c 100644 --- a/src/ability/crud/create.rs +++ b/src/ability/crud/create.rs @@ -56,7 +56,7 @@ impl CheckParents for Create { type Parents = Mutable; type ParentError = (); - fn check_parents(&self, other: &Self::Parents) -> Result<(), Self::ParentError> { + fn check_parent(&self, other: &Self::Parents) -> Result<(), Self::ParentError> { if let Some(self_uri) = &self.uri { match other { Mutable::Any(any) => { diff --git a/src/ability/crud/destroy.rs b/src/ability/crud/destroy.rs index 4432d2e2..6d9bd003 100644 --- a/src/ability/crud/destroy.rs +++ b/src/ability/crud/destroy.rs @@ -49,10 +49,10 @@ impl CheckParents for Destroy { type Parents = Mutable; type ParentError = (); - fn check_parents(&self, other: &Self::Parents) -> Result<(), Self::ParentError> { + fn check_parent(&self, other: &Self::Parents) -> Result<(), Self::ParentError> { match other { - Mutable::Mutate(mutate) => Ok(()), // FIXME - Mutable::Any(any) => Ok(()), // FIXME + Mutable::Mutate(_mutate) => Ok(()), // FIXME + Mutable::Any(_any) => Ok(()), // FIXME } } } diff --git a/src/ability/crud/mutate.rs b/src/ability/crud/mutate.rs index 9c1fd8fe..0e5590ec 100644 --- a/src/ability/crud/mutate.rs +++ b/src/ability/crud/mutate.rs @@ -48,7 +48,7 @@ impl CheckParents for MutateBuilder { type Parents = any::Builder; type ParentError = (); - fn check_parents(&self, _proof: &Self::Parents) -> Result<(), Self::ParentError> { + fn check_parent(&self, _proof: &Self::Parents) -> Result<(), Self::ParentError> { Ok(()) } } diff --git a/src/ability/crud/parents.rs b/src/ability/crud/parents.rs index f42b25a8..bd03b9e8 100644 --- a/src/ability/crud/parents.rs +++ b/src/ability/crud/parents.rs @@ -15,7 +15,7 @@ impl CheckSame for Mutable { match self { Mutable::Mutate(mutate) => match proof { Mutable::Mutate(other_mutate) => mutate.check_same(other_mutate), - Mutable::Any(any) => Ok(()), + Mutable::Any(_any) => Ok(()), }, _ => Err(()), } diff --git a/src/ability/crud/read.rs b/src/ability/crud/read.rs index d04d887b..f07a2b61 100644 --- a/src/ability/crud/read.rs +++ b/src/ability/crud/read.rs @@ -3,9 +3,10 @@ use crate::{ ability::command::Command, proof::{checkable::Checkable, parentful::Parentful, parents::CheckParents, same::CheckSame}, }; -use libipld_core::{ipld::Ipld, serde as ipld_serde}; +use libipld_core::{error::SerdeError, ipld::Ipld, serde as ipld_serde}; use serde::{Deserialize, Serialize}; use std::collections::BTreeMap; +use thiserror::Error; use url::Url; #[cfg(target_arch = "wasm32")] @@ -22,21 +23,6 @@ pub struct Read { pub args: Option>, // FIXME rename Argumenst to get the traits? } -#[cfg(target_arch = "wasm32")] -#[wasm_bindgen] -pub struct Js(#[wasm_bindgen(skip)] pub Read); - -#[cfg(target_arch = "wasm32")] -#[wasm_bindgen] -impl Js { - // FIXME - pub fn check_same(&self, proof: &Js) -> Result<(), JsValue> { - self.0 - .check_same(&proof.0) - .map_err(|err| JsValue::from_str(&format!("{:?}", err))) - } -} - impl Command for Read { const COMMAND: &'static str = "crud/read"; } @@ -48,28 +34,72 @@ impl From for Ipld { } impl TryFrom for Read { - type Error = (); // FIXME + type Error = SerdeError; fn try_from(ipld: Ipld) -> Result { - ipld_serde::from_ipld(ipld).map_err(|_| ()) + ipld_serde::from_ipld(ipld) } } +// FIXME +#[derive(Debug, Error)] +pub enum E { + #[error("Some error")] + SomeErrMsg(String), +} + impl Checkable for Read { type Hierarchy = Parentful; } impl CheckSame for Read { - type Error = (); + type Error = E; fn check_same(&self, proof: &Self) -> Result<(), Self::Error> { + if let Some(uri) = &self.uri { + if uri != proof.uri.as_ref().unwrap() { + return Err(E::SomeErrMsg("".into())); + } + } + + if let Some(args) = &self.args { + if let Some(proof_args) = &proof.args { + for (k, v) in args { + if proof_args.get(k) != Some(v) { + return Err(E::SomeErrMsg("".into())); + } + } + } + } + Ok(()) } } impl CheckParents for Read { type Parents = any::Builder; - type ParentError = (); - fn check_parents(&self, other: &Self::Parents) -> Result<(), Self::ParentError> { - Ok(()) + type ParentError = E; + + fn check_parent(&self, _other: &Self::Parents) -> Result<(), Self::ParentError> { + Ok(()) // FIXME + } +} + +#[cfg(target_arch = "wasm32")] +#[wasm_bindgen] +pub struct CrudRead(#[wasm_bindgen(skip)] pub Read); + +#[cfg(target_arch = "wasm32")] +#[wasm_bindgen] +impl CrudRead { + pub fn command(&self) -> String { + Read::COMMAND.to_string() + } + + pub fn check_same(&self, proof: &CrudRead) -> Result<(), JsError> { + self.0.check_same(&proof.0).map_err(Into::into) + } + + pub fn check_parent(&self, proof: &any::CrudAny) -> Result<(), JsError> { + self.0.check_parent(&proof.0).map_err(Into::into) } } diff --git a/src/ability/crud/update.rs b/src/ability/crud/update.rs index 83249052..62378050 100644 --- a/src/ability/crud/update.rs +++ b/src/ability/crud/update.rs @@ -78,7 +78,7 @@ impl CheckParents for UpdateBuilder { type Parents = Mutable; type ParentError = (); // FIXME - fn check_parents(&self, proof: &Self::Parents) -> Result<(), Self::ParentError> { + fn check_parent(&self, proof: &Self::Parents) -> Result<(), Self::ParentError> { match proof { Mutable::Any(any) => self.uri.check_same(&any.uri).map_err(|_| ()), Mutable::Mutate(mutate) => self.uri.check_same(&mutate.uri).map_err(|_| ()), diff --git a/src/ability/dynamic.rs b/src/ability/dynamic.rs index 0ced56ff..8d3acb06 100644 --- a/src/ability/dynamic.rs +++ b/src/ability/dynamic.rs @@ -1,22 +1,10 @@ //! This module is for dynamic abilities, especially for FFI and Wasm support use super::{arguments::Arguments, command::ToCommand}; -use crate::{ - delegation::Delegatable, - invocation::Resolvable, - ipld, - promise::Promise, - proof::{ - checkable::Checkable, parentful::Parentful, parentless::Parentless, parents::CheckParents, - same::CheckSame, - }, - task::DefaultTrue, -}; +use crate::{ipld, proof::same::CheckSame}; use js_sys; use libipld_core::{error::SerdeError, ipld::Ipld, serde as ipld_serde}; -use serde::{ - de::DeserializeOwned, ser::SerializeMap, Deserialize, Deserializer, Serialize, Serializer, -}; +use serde::{Deserialize, Serialize}; use std::{collections::BTreeMap, fmt::Debug}; use wasm_bindgen::prelude::*; @@ -29,87 +17,9 @@ pub struct Dynamic { pub args: Arguments, } -// NOTE plug this into Configured like: Configured> -pub struct Builder(pub T); -pub struct Promised(pub T); - -impl> From> for Arguments { - fn from(builder: Builder) -> Self { - builder.0.into() - } -} - -impl From> for Builder> { - fn from(configured: Configured) -> Self { - Builder(configured) - } -} - -impl From>> for Configured { - fn from(builder: Builder>) -> Self { - builder.0 - } -} - -impl> From> for Arguments { - fn from(promised: Promised) -> Self { - promised.0.into() - } -} - -impl From> for Promised> { - fn from(configured: Configured) -> Self { - Promised(configured) - } -} - -impl From>> for Configured { - fn from(promised: Promised>) -> Self { - promised.0 - } -} - -// NOTE to self: this is helpful as a common container to lift various FFI into -#[derive(Clone, PartialEq, Debug)] -pub struct Configured { - pub arguments: Arguments, - pub config: T, -} - -impl Delegatable for Configured { - type Builder = Builder>; -} - -impl Resolvable for Configured { - type Promised = Promised>; -} - -impl ToCommand for Configured { +impl ToCommand for Dynamic { fn to_command(&self) -> String { - self.config.to_command() - } -} - -impl CheckSame for Configured { - type Error = T::Error; - - fn check_same(&self, proof: &Self) -> Result<(), Self::Error> { - self.config.check_same(&proof.config) - } -} - -impl CheckParents for Configured { - type Parents = Dynamic; - type ParentError = T::ParentError; - - fn check_parents(&self, parent: &Dynamic) -> Result<(), Self::ParentError> { - self.check_parents(parent) - } -} - -impl From> for Arguments { - fn from(reader: Configured) -> Self { - reader.arguments + self.cmd.clone() } } @@ -119,10 +29,6 @@ impl From for Arguments { } } -impl Checkable for Configured { - type Hierarchy = T::Hierarchy; -} - #[cfg(target_arch = "wasm32")] impl From for js_sys::Map { fn from(ability: Dynamic) -> Self { diff --git a/src/ability/js/parentful.rs b/src/ability/js/parentful.rs index b96c0101..72140b27 100644 --- a/src/ability/js/parentful.rs +++ b/src/ability/js/parentful.rs @@ -1,18 +1,17 @@ use crate::{ ability::{arguments::Arguments, command::ToCommand, dynamic}, - ipld, proof::{checkable::Checkable, parentful::Parentful, parents::CheckParents, same::CheckSame}, + reader::Reader, }; -use js_sys::{Function, JsString, Map, Object, Reflect}; -use libipld_core::ipld::Ipld; +use js_sys::{Function, JsString, Map}; use std::collections::BTreeMap; use wasm_bindgen::{prelude::*, JsValue}; // NOTE NOTE NOTE: the strategy is: "you (JS) hand us the cfg" AKA strategy, // and we (Rust) wire it up and run it for you -// NOTE becuase of the above, no need to export JsWithParents to JS +// NOTE becuase of the above, no need to export WithParents to JS // FIXME rename -type JsWithParents = dynamic::Configured; +type WithParents = Reader; // Promise = Promise? Ah, nope becuase we need that CID on the promise // FIXME represent promises (for Promised) and options (for builder) @@ -28,47 +27,47 @@ pub struct Config { pub check_same: Function, #[wasm_bindgen(skip)] - pub check_parents: BTreeMap>, + pub check_parent: BTreeMap, } #[wasm_bindgen(typescript_custom_section)] const CONFIG_ARGS: &str = r#" -interface ConfigArgs { +interface ParentfulArgs { command: string, is_nonce_meaningful: boolean, validate_shape: Function, check_same: Function, - check_parents: Map + check_parent: Map } "#; #[wasm_bindgen] extern "C" { - #[wasm_bindgen(typescript_type = "ConfigArgs")] - pub type ConfigArgs; - - pub fn command(this: &ConfigArgs) -> String; - pub fn is_nonce_meaningful(this: &ConfigArgs) -> bool; - pub fn validate_shape(this: &ConfigArgs) -> Function; - pub fn check_same(this: &ConfigArgs) -> Function; - pub fn check_parents(this: &ConfigArgs) -> Map; + #[wasm_bindgen(typescript_type = "ParentfulArgs")] + pub type ParentfulArgs; + + pub fn command(this: &ParentfulArgs) -> String; + pub fn is_nonce_meaningful(this: &ParentfulArgs) -> bool; + pub fn validate_shape(this: &ParentfulArgs) -> Function; + pub fn check_same(this: &ParentfulArgs) -> Function; + pub fn check_parent(this: &ParentfulArgs) -> Map; } #[wasm_bindgen] impl Config { // FIXME object args as an option #[wasm_bindgen(constructor)] - pub fn new(js_obj: ConfigArgs) -> Result { + pub fn new(js_obj: ParentfulArgs) -> Result { Ok(Config { command: command(&js_obj), is_nonce_meaningful: is_nonce_meaningful(&js_obj), validate_shape: validate_shape(&js_obj), check_same: check_same(&js_obj), - check_parents: { + check_parent: { let mut btree = BTreeMap::new(); let mut acc = Ok(()); - check_parents(&js_obj).for_each(&mut |value, key| { + check_parent(&js_obj).for_each(&mut |value, key| { // |value, key| is correct ------^^^^^^^^^^^^ if let Ok(_) = &acc { match key.as_string() { @@ -76,7 +75,7 @@ impl Config { Some(str_key) => match value.dyn_ref::() { None => acc = Err("Value is not a function".into()), Some(f) => { - btree.insert(str_key, Box::new(f.clone())); + btree.insert(str_key, f.clone()); acc = Ok(()); } }, @@ -90,46 +89,42 @@ impl Config { } } -impl From for dynamic::Dynamic { - fn from(js: JsWithParents) -> Self { +impl From for dynamic::Dynamic { + fn from(js: WithParents) -> Self { dynamic::Dynamic { - cmd: js.config.command, - args: js.arguments, + cmd: js.env.command, + args: js.val, } } } // FIXME while this can totally be done by converting to the dynamic carrier type, this seems more straightforward? -impl CheckSame for JsWithParents { +impl CheckSame for WithParents { type Error = JsValue; fn check_same(&self, proof: &Self) -> Result<(), Self::Error> { let this = wasm_bindgen::JsValue::NULL; - self.config + self.env .check_same .call2( &this, - &self.arguments.clone().into(), + &self.val.clone().into(), &Arguments::from(proof.clone()).into(), ) .map(|_| ()) } } -impl CheckParents for JsWithParents { - type Parents = dynamic::Dynamic; // FIXME actually no? What if we want to plug in random stuff? +impl CheckParents for WithParents { + type Parents = dynamic::Dynamic; type ParentError = JsValue; - fn check_parents(&self, parent: &dynamic::Dynamic) -> Result<(), Self::Error> { - if let Some(handler) = self.config.check_parents.get(&parent.cmd) { + fn check_parent(&self, parent: &dynamic::Dynamic) -> Result<(), Self::Error> { + if let Some(handler) = self.env.check_parent.get(&parent.cmd) { let this = wasm_bindgen::JsValue::NULL; handler - .call2( - &this, - &self.arguments.clone().into(), - &parent.args.clone().into(), - ) - .map(|_| ()) + .call2(&this, &self.val.clone().into(), &parent.args.clone().into()) + .map(|_| ()) // FIXME } else { Err(JsValue::from("No handler for parent")) } @@ -142,6 +137,6 @@ impl ToCommand for Config { } } -impl Checkable for JsWithParents { - type Hierarchy = Parentful; +impl Checkable for WithParents { + type Hierarchy = Parentful; } diff --git a/src/ability/js/parentless.rs b/src/ability/js/parentless.rs index 3ac77556..1897fa22 100644 --- a/src/ability/js/parentless.rs +++ b/src/ability/js/parentless.rs @@ -1,88 +1,95 @@ use crate::{ ability::{arguments::Arguments, command::ToCommand, dynamic}, - ipld, - proof::{checkable::Checkable, parentless::Parentless, parents::CheckParents, same::CheckSame}, + proof::{parentless::NoParents, same::CheckSame}, + reader::Reader, }; -use js_sys::{Function, JsString, Map, Object, Reflect}; -use libipld_core::ipld::Ipld; -use std::collections::BTreeMap; -use wasm_bindgen::{prelude::*, JsValue}; +use js_sys::Function; +use wasm_bindgen::prelude::*; // NOTE NOTE NOTE: the strategy is: "you (JS) hand us the cfg" AKA strategy, // and we (Rust) wire it up and run it for you // NOTE becuase of the above, no need to export JsWithParents to JS // FIXME rename -type JsWithoutParents = dynamic::Configured; +type WithoutParents = Reader; // FIXME rename ability? abilityconfig? leave as is? // #[wasm_bindgen(getter_with_clone)] #[derive(Debug, Clone, PartialEq)] #[wasm_bindgen] -pub struct Config1 { - // #[wasm_bindgen(skip)] +pub struct ParentlessConfig { command: String, is_nonce_meaningful: bool, - - // #[wasm_bindgen(skip)] validate_shape: Function, - - //#[wasm_bindgen(skip)] check_same: Function, } // FIXME represent promises (for Promised) and options (for builder) +#[wasm_bindgen(typescript_custom_section)] +const PARENTLESS_ARGS: &str = r#" +interface ParentlessArgs { + command: string, + is_nonce_meaningful: boolean, + validate_shape: Function, + check_same: Function, +} +"#; + #[wasm_bindgen] -impl Config1 { +extern "C" { + #[wasm_bindgen(typescript_type = "ParentlessArgs")] + pub type ParentlessArgs; + + pub fn command(this: &ParentlessArgs) -> String; + pub fn is_nonce_meaningful(this: &ParentlessArgs) -> bool; + pub fn validate_shape(this: &ParentlessArgs) -> Function; + pub fn check_same(this: &ParentlessArgs) -> Function; +} + +#[wasm_bindgen] +impl ParentlessConfig { // FIXME object args as an option #[wasm_bindgen(constructor)] - pub fn new( - command: String, - is_nonce_meaningful: bool, - validate_shape: Function, - check_same: Function, - ) -> Config1 { - Config1 { - command, - is_nonce_meaningful, - validate_shape, - check_same, + pub fn new(js_obj: ParentlessArgs) -> ParentlessConfig { + ParentlessConfig { + command: command(&js_obj), + is_nonce_meaningful: is_nonce_meaningful(&js_obj), + validate_shape: validate_shape(&js_obj), + check_same: check_same(&js_obj), } } } -impl From for dynamic::Dynamic { - fn from(js: JsWithoutParents) -> Self { +impl From for dynamic::Dynamic { + fn from(js: WithoutParents) -> Self { dynamic::Dynamic { - cmd: js.config.command, - args: js.arguments, + cmd: js.env.command, + args: js.val, } } } // FIXME while this can totally be done by converting to the dynamic carrier type, this seems more straightforward? -impl CheckSame for JsWithoutParents { +impl CheckSame for WithoutParents { type Error = JsValue; fn check_same(&self, proof: &Self) -> Result<(), Self::Error> { let this = wasm_bindgen::JsValue::NULL; - self.config + self.env .check_same .call2( &this, - &self.arguments.clone().into(), + &self.val.clone().into(), &Arguments::from(proof.clone()).into(), ) .map(|_| ()) } } -impl ToCommand for Config1 { +impl ToCommand for ParentlessConfig { fn to_command(&self) -> String { self.command.clone() } } -impl Checkable for JsWithoutParents { - type Hierarchy = Parentless; -} +impl NoParents for ParentlessConfig {} diff --git a/src/ability/msg/receive.rs b/src/ability/msg/receive.rs index c7fb6d68..5583331d 100644 --- a/src/ability/msg/receive.rs +++ b/src/ability/msg/receive.rs @@ -33,7 +33,7 @@ impl CheckParents for Receive { type Parents = msg::Any; type ParentError = ::Error; - fn check_parents(&self, proof: &Self::Parents) -> Result<(), Self::ParentError> { + fn check_parent(&self, proof: &Self::Parents) -> Result<(), Self::ParentError> { self.from.check_same(&proof.from).map_err(|_| ()) } } diff --git a/src/ability/msg/send.rs b/src/ability/msg/send.rs index 1d7c81a1..194f6d7b 100644 --- a/src/ability/msg/send.rs +++ b/src/ability/msg/send.rs @@ -1,8 +1,7 @@ use crate::{ ability::{arguments::Arguments, command::Command}, delegation::Delegatable, - invocation::Resolvable, - promise::Promise, + invocation::{Promise, Resolvable}, proof::{checkable::Checkable, parentful::Parentful, parents::CheckParents, same::CheckSame}, }; use libipld_core::{error::SerdeError, ipld::Ipld, serde as ipld_serde}; @@ -58,9 +57,9 @@ impl From for Arguments { impl From for Builder { fn from(awaiting: Promised) -> Self { Builder { - to: awaiting.to.try_extract().ok(), - from: awaiting.from.try_extract().ok(), - message: awaiting.message.try_extract().ok(), + to: awaiting.to.try_resolve().ok(), + from: awaiting.from.try_resolve().ok(), + message: awaiting.message.try_resolve().ok(), } } } @@ -87,7 +86,7 @@ impl CheckParents for Builder { type ParentError = ::Error; // FIXME rename other to proof - fn check_parents(&self, other: &Self::Parents) -> Result<(), Self::ParentError> { + fn check_parent(&self, other: &Self::Parents) -> Result<(), Self::ParentError> { self.from.check_same(&other.from).map_err(|_| ()) } } @@ -117,9 +116,9 @@ impl TryFrom for Resolved { fn try_from(awaiting: Promised) -> Result { Ok(Generic { - to: awaiting.to.try_extract().map_err(|_| ())?, - from: awaiting.from.try_extract().map_err(|_| ())?, - message: awaiting.message.try_extract().map_err(|_| ())?, + to: awaiting.to.try_resolve().map_err(|_| ())?, + from: awaiting.from.try_resolve().map_err(|_| ())?, + message: awaiting.message.try_resolve().map_err(|_| ())?, }) } } diff --git a/src/ability/ucan.rs b/src/ability/ucan.rs index 59ddbe7d..830f7c22 100644 --- a/src/ability/ucan.rs +++ b/src/ability/ucan.rs @@ -1,84 +1,2 @@ -use super::arguments::Arguments; -use crate::{ability::command::Command, delegation::Delegatable, promise::Promise}; -use libipld_core::ipld::Ipld; -use serde::{Deserialize, Serialize}; -use std::fmt::Debug; - -// NOTE This one is primarily for enabling delegationd recipets - -// FIXME aslo add revokation, so thsi module needs to be broken up - -// FIXME -#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] -pub struct Generic { - pub cmd: String, - pub args: Args, // FIXME Does this have specific fields? -} - -pub type Resolved = Generic; -pub type Builder = Generic>; -pub type Promised = Generic>; - -impl Command for Generic { - const COMMAND: &'static str = "ucan/proxy"; -} - -impl Delegatable for Resolved { - type Builder = Builder; -} - -impl From for Builder { - fn from(resolved: Resolved) -> Builder { - Builder { - cmd: resolved.cmd, - args: Some(resolved.args), - } - } -} - -impl TryFrom for Resolved { - type Error = (); // FIXME - - fn try_from(b: Builder) -> Result { - Ok(Resolved { - cmd: b.cmd, - args: b.args.ok_or(())?, - }) - } -} - -impl From for Arguments { - fn from(b: Builder) -> Arguments { - let mut args = b.args.unwrap_or_default(); - args.insert("cmd".into(), Ipld::String(b.cmd)); - args - } -} - -// // FIXME hmmm -// #[derive(Debug, Clone, PartialEq)] -// pub struct ProxyExecuteBuilder { -// pub command: Option, -// pub args: BTreeMap, -// } -// -// -// impl From for ProxyExecuteBuilder { -// fn from(proxy: ProxyExecute) -> Self { -// ProxyExecuteBuilder { -// command: Some(ProxyExecute::COMMAND.into()), -// args: proxy.args.clone(), -// } -// } -// } -// -// impl TryFrom for ProxyExecute { -// type Error = (); // FIXME -// -// fn try_from(ProxyExecuteBuilder { command, args }: ProxyExecuteBuilder) -> Result { -// match command { -// None => Err(()), -// Some(command) => Ok(Self { command, args }), -// } -// } -// } +pub mod proxy; +// FIXME pub mod revoke; diff --git a/src/ability/ucan/proxy.rs b/src/ability/ucan/proxy.rs new file mode 100644 index 00000000..4851356a --- /dev/null +++ b/src/ability/ucan/proxy.rs @@ -0,0 +1,87 @@ +use crate::{ + ability::{arguments::Arguments, command::Command}, + delegation::Delegatable, + invocation::Promise, +}; +use libipld_core::ipld::Ipld; +use serde::{Deserialize, Serialize}; +use std::fmt::Debug; + +// NOTE This one is primarily for enabling delegationd recipets + +// FIXME can this *only* be a builder? +// NOTE UNLIKE the dynamic ability, this has cmd as an argument *at runtime* +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub struct Generic { + pub cmd: String, + pub args: Args, // FIXME Does this have specific fields? + // FIXME should args just be a CID +} + +pub type Resolved = Generic; +pub type Builder = Generic>; +pub type Promised = Generic>; + +impl Command for Generic { + const COMMAND: &'static str = "ucan/proxy"; +} + +impl Delegatable for Resolved { + type Builder = Builder; +} + +impl From for Builder { + fn from(resolved: Resolved) -> Builder { + Builder { + cmd: resolved.cmd, + args: Some(resolved.args), + } + } +} + +impl TryFrom for Resolved { + type Error = (); // FIXME + + fn try_from(b: Builder) -> Result { + Ok(Resolved { + cmd: b.cmd, + args: b.args.ok_or(())?, + }) + } +} + +impl From for Arguments { + fn from(b: Builder) -> Arguments { + let mut args = b.args.unwrap_or_default(); + args.insert("cmd".into(), Ipld::String(b.cmd)); + args + } +} + +// // FIXME hmmm need to decide on the exact shape of this +// #[derive(Debug, Clone, PartialEq)] +// pub struct ProxyExecuteBuilder { +// pub command: Option, +// pub args: BTreeMap, +// } +// +// +// impl From for ProxyExecuteBuilder { +// fn from(proxy: ProxyExecute) -> Self { +// ProxyExecuteBuilder { +// command: Some(ProxyExecute::COMMAND.into()), +// args: proxy.args.clone(), +// } +// } +// } +// +// impl TryFrom for ProxyExecute { +// type Error = (); // FIXME +// +// fn try_from(ProxyExecuteBuilder { command, args }: ProxyExecuteBuilder) -> Result { +// match command { +// None => Err(()), +// Some(command) => Ok(Self { command, args }), +// } +// } +// } diff --git a/src/agent.rs b/src/agent.rs new file mode 100644 index 00000000..aed278bd --- /dev/null +++ b/src/agent.rs @@ -0,0 +1,59 @@ +use crate::{ + ability::command::ToCommand, + delegation::{traits::Condition, Delegatable, Delegation}, + did::Did, + invocation::Invocation, + metadata as meta, + proof::parents::CheckParents, +}; + +pub struct Agent { + pub did: Did, + // pub key: signature::Key, + pub store: S, +} + +impl Agent { + // pub fn delegate(&self, payload: Payload) -> Delegation { + // let signature = self.key.sign(payload); + // signature::Envelope::new(payload, signature) + // } + + pub fn invoke( + &self, + delegation: Delegation, + proof_chain: Vec>, // FIXME T must also accept Self and * + ) -> () + where + T::Parents: Delegatable, + { + todo!() + } + + pub fn try_invoke(&self, ability: A) { + todo!() + } + + pub fn revoke( + &self, + delegation: Delegation, + ) -> () +// where +// T::Parents: Delegatable, + { + todo!() + } + + pub fn receive_delegation( + &self, + delegation: Delegation, + ) -> () { + todo!() + } + + pub fn receive_invocation(&self, invocation: Invocation) -> () { + todo!() + } + + // pub fn check(&self, delegation: &Delegation) -> () // FIXME Includes cache +} diff --git a/src/delegation.rs b/src/delegation.rs index 945f90f7..01584561 100644 --- a/src/delegation.rs +++ b/src/delegation.rs @@ -2,12 +2,16 @@ mod condition; mod delegatable; mod payload; -pub use condition::*; +pub mod store; +pub use condition::*; pub use delegatable::Delegatable; pub use payload::Payload; -use crate::signature; +use condition::traits::Condition; +use store::IndexedStore; + +use crate::{metadata as meta, signature}; /// A [`Delegation`] is a signed delegation [`Payload`] /// @@ -17,4 +21,25 @@ use crate::signature; /// FIXME pub type Delegation = signature::Envelope>; -// FIXME add a store with delegation indexing +// FIXME +impl Delegation { + // FIXME include cache + //pub fn check>(&self, store: &S) -> Result<(), ()> { + // if let Ok(is_valid) = store.previously_checked(self) { + // if is_valid { + // return Ok(()); + // } + // } + + // if let Ok(chains) = store.chains_for(self) { + // for chain in chains { + // todo!() + // // if self.check_self(self).is_ok() { + // // return Ok(()); + // // } + // } + // } + + // Err(()) + //} +} diff --git a/src/delegation/payload.rs b/src/delegation/payload.rs index 2161dc63..752c3fe7 100644 --- a/src/delegation/payload.rs +++ b/src/delegation/payload.rs @@ -1,6 +1,6 @@ use super::{condition::traits::Condition, delegatable::Delegatable}; use crate::{ - ability::{arguments::Arguments, command::Command, dynamic}, + ability::{arguments::Arguments, command::Command}, capsule::Capsule, did::Did, invocation, @@ -92,18 +92,19 @@ impl From> for } } +// FIXME this likely should move to invocation impl<'a, T: Delegatable + Resolvable + Checkable + Clone, C: Condition, E: meta::Entries> Payload { pub fn check( - invoked: &'a invocation::Payload, // FIXME promisory version + invoked: &'a invocation::Payload, // FIXME promisory version proofs: Vec>, now: SystemTime, ) -> Result<(), ()> where - invocation::Payload: Clone, + invocation::Payload: Clone, U::Builder: Clone + Into, - T::Hierarchy: From>, + T::Hierarchy: From>, { let start: Acc<'a, T> = Acc { issuer: &invoked.issuer, diff --git a/src/delegation/store.rs b/src/delegation/store.rs new file mode 100644 index 00000000..438e8272 --- /dev/null +++ b/src/delegation/store.rs @@ -0,0 +1,83 @@ +use super::{condition::traits::Condition, delegatable::Delegatable, Delegation}; +use crate::{did::Did, metadata as meta}; +use libipld_core::cid::Cid; +use serde::{Deserialize, Serialize}; +use std::collections::{BTreeMap, HashMap}; +use web_time::SystemTime; + +// NOTE can already look up by CID in other traits +pub trait IndexedStore { + type Error; + + fn get_by(query: Query) -> Result>, Self::Error>; + + fn previously_checked(cid: Cid) -> Result; + + // NOTE you can override this with something much more efficient in e.g. SQL + // FIXME "should" be checked and indexed on the way in + fn chains_for( + &self, + subject: Did, + command: String, + audience: Did, + ) -> Result)>>, Self::Error>; + // if let Ok(possible) = self.get_by(Query { + // audience: Some(audience), + // command: Some(command), + // after_not_before: Some(SystemTime::now()), + // expires_before: Some(SystemTime::now()), + // ..Default::default() + // }) { + // let acc = Ok(vec![]); + // let iss = possible.iter().next().unwrap().1.issuer; + + // // FIXME actually more complex than this: + // // sicne the chain also has to be valid + // // ...we shoud probably index on the way in + // while acc.is_ok() { + // if let Ok(latest) = get_one(Query { + // subject: Some(subject), + // command: Some(command), + // audience: Some(latest_iss), + // after_not_before: Some(SystemTime::now()), + // expires_before: Some(SystemTime::now()), + // ..Default::default() + // }) { + // acc.push(latest); + + // if delegation. + // latest_iss = delegation.issuer; + // } else { + // acc = Err(()); // FIXME + // } + // } + // } + + fn get_one(&self, query: Query) -> Result<(Cid, Delegation), Self::Error> { + todo!() + //let mut results = Self::get_by(query)?; + //results.pop().ok_or_else(|_| todo!()) + } + + fn expired(&self) -> Result>, Self::Error> { + todo!() + // self.get_by(Query { + // expires_before: Some(SystemTime::now()), + // ..Default::default() + // }) + } +} + +#[derive(Default, Debug, Clone, PartialEq)] +pub struct Query { + pub subject: Option, + pub command: Option, + pub issuer: Option, + pub audience: Option, + + pub prior_to_not_before: Option, // FIXME time + pub after_not_before: Option, // FIXME time + + pub expires_before: Option, // FIXME time + pub expires_aftre: Option, // FIXME time +} diff --git a/src/did.rs b/src/did.rs index 8b856104..2f1aae78 100644 --- a/src/did.rs +++ b/src/did.rs @@ -14,12 +14,10 @@ impl From for String { } impl TryFrom for Did { - type Error = String; // FIXME + type Error = >::Error; fn try_from(string: String) -> Result { - DID::parse(&string) - .map_err(|err| format!("Failed to parse DID: {}", err)) - .map(Self) + DID::parse(&string).map(Did) } } @@ -36,9 +34,12 @@ impl From for Ipld { } impl TryFrom for Did { - type Error = (); // FIXME + type Error = >::Error; // FIXME also include the "can't parse form ipld" case; seems like someythjing taht can be abstrcated out, too fn try_from(ipld: Ipld) -> Result { - Self::try_from(ipld) + match ipld { + Ipld::String(string) => Did::try_from(string), + _ => todo!(), // Err(()), + } } } diff --git a/src/invocation.rs b/src/invocation.rs index 5cb2b95e..1e2a8a40 100644 --- a/src/invocation.rs +++ b/src/invocation.rs @@ -1,10 +1,12 @@ mod payload; +mod promise; mod resolvable; mod serializer; pub use payload::{Payload, Unresolved}; +pub use promise::Promise; pub use resolvable::Resolvable; use crate::signature; -pub type Invocation = signature::Envelope>; +pub type Invocation = signature::Envelope>; diff --git a/src/invocation/payload.rs b/src/invocation/payload.rs index 72921dcd..ee9c0ed0 100644 --- a/src/invocation/payload.rs +++ b/src/invocation/payload.rs @@ -1,19 +1,21 @@ use super::resolvable::Resolvable; use crate::{ - ability::{arguments::Arguments, command::Command, dynamic}, + ability::{arguments::Arguments, command::Command}, capsule::Capsule, did::Did, + metadata as meta, + metadata::{Mergable, Metadata}, nonce::Nonce, time::Timestamp, }; use libipld_core::{cid::Cid, error::SerdeError, ipld::Ipld, serde as ipld_serde}; -use serde::{Deserialize, Serialize, Serializer}; +use serde::{Serialize, Serializer}; use std::{collections::BTreeMap, fmt::Debug}; // FIXME this version should not be resolvable... // FIXME ...or at least have two versions via abstraction #[derive(Debug, Clone, PartialEq)] -pub struct Payload { +pub struct Payload { pub issuer: Did, pub subject: Did, pub audience: Option, @@ -22,7 +24,7 @@ pub struct Payload { pub proofs: Vec, pub cause: Option, - pub metadata: BTreeMap, // FIXME parameterize? + pub metadata: Metadata, pub nonce: Nonce, pub not_before: Option, @@ -30,7 +32,7 @@ pub struct Payload { } // NOTE This is the version that accepts promises -pub type Unresolved = Payload; +pub type Unresolved = Payload; // type Dynamic = Payload; <- ? // FIXME parser for both versions @@ -46,14 +48,14 @@ pub type Unresolved = Payload; // Unresolved(Unresolved), // } -impl Capsule for Payload { +impl Capsule for Payload { const TAG: &'static str = "ucan/i/1.0.0-rc.1"; } -impl Serialize for Payload +impl Serialize for Payload where - Payload: Clone, - InternalSerializer: From>, + Payload: Clone, + InternalSerializer: From>, { fn serialize(&self, serializer: S) -> Result where @@ -64,10 +66,10 @@ where } } -impl<'de, T> serde::Deserialize<'de> for Payload +impl<'de, T, E: meta::Entries> serde::Deserialize<'de> for Payload where - Payload: TryFrom, - as TryFrom>::Error: Debug, + Payload: TryFrom, + as TryFrom>::Error: Debug, { fn deserialize(d: D) -> Result where @@ -82,9 +84,9 @@ where } } -impl TryFrom for Payload +impl TryFrom for Payload where - Payload: TryFrom, + Payload: TryFrom, { type Error = (); // FIXME @@ -94,8 +96,8 @@ where } } -impl From> for Ipld { - fn from(payload: Payload) -> Self { +impl From> for Ipld { + fn from(payload: Payload) -> Self { payload.into() } } @@ -193,8 +195,8 @@ impl TryFrom for InternalSerializer { // } // } -impl> From> for InternalSerializer { - fn from(payload: Payload) -> Self { +impl, E: meta::Entries> From> for InternalSerializer { + fn from(payload: Payload) -> Self { InternalSerializer { issuer: payload.issuer, subject: payload.subject, @@ -205,7 +207,7 @@ impl> From> for InternalSerializer { proofs: payload.proofs, cause: payload.cause, - metadata: payload.metadata, + metadata: payload.metadata.merge(), nonce: payload.nonce, diff --git a/src/promise.rs b/src/invocation/promise.rs similarity index 54% rename from src/promise.rs rename to src/invocation/promise.rs index 78b0f0f9..ac260249 100644 --- a/src/promise.rs +++ b/src/invocation/promise.rs @@ -11,35 +11,72 @@ use wasm_bindgen::prelude::*; #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] #[serde(untagged)] pub enum Promise { - Resolved(T), - Waiting(Selector), + Fulfilled(T), + Pending(Selector), } #[cfg(target_arch = "wasm32")] -pub enum JsStatus { - Resolved, - Waiting, +#[derive(Clone, Debug, PartialEq, Eq)] +#[wasm_bindgen] +pub enum UcanPromiseStatus { + Fulfilled, + Pending, } // FIXME no way to make this consistent, because of C enums ruining Rust convetions, right? // FIXME consider wrapping in a trait #[cfg(target_arch = "wasm32")] -pub struct JsPromise { - status: JsStatus, - selector: Selector, +#[derive(Clone, Debug, PartialEq)] +#[wasm_bindgen] +pub struct UcanPromise { + status: UcanPromiseStatus, + selector: Option, value: Option, } -// TODO remove; I'd rather have liine 70 blanket than this -// #[cfg(target_arch = "wasm32")] -// impl> From for Promise { -// fn from(js: JsPromise) -> Self { -// match js.status { -// JsStatus::Resolved => Promise::Resolved(js.value.unwrap().into()), -// JsStatus::Waiting => Promise::Waiting(js.selector), -// } -// } -// } +#[cfg(target_arch = "wasm32")] +#[wasm_bindgen(getter_with_clone)] +pub struct IncoherentPromise(pub UcanPromise); + +#[cfg(target_arch = "wasm32")] +impl TryFrom for Promise { + type Error = IncoherentPromise; + + fn try_from(js: UcanPromise) -> Result { + match js.status { + UcanPromiseStatus::Fulfilled => { + if let Some(val) = &js.value { + return Ok(Promise::Fulfilled(val.clone())); + } + } + UcanPromiseStatus::Pending => { + if let Some(selector) = &js.selector { + return Ok(Promise::Pending(selector.clone())); + } + } + } + + Err(IncoherentPromise(js)) + } +} + +#[cfg(target_arch = "wasm32")] +impl> From> for UcanPromise { + fn from(promise: Promise) -> Self { + match promise { + Promise::Fulfilled(val) => UcanPromise { + status: UcanPromiseStatus::Fulfilled, + selector: None, + value: Some(val.into()), + }, + Promise::Pending(cid) => UcanPromise { + status: UcanPromiseStatus::Pending, + selector: Some(cid), + value: None, + }, + } + } +} impl Promise { pub fn map(self, f: F) -> Promise @@ -47,8 +84,8 @@ impl Promise { F: FnOnce(T) -> U, { match self { - Promise::Resolved(t) => Promise::Resolved(f(t)), - Promise::Waiting(selector) => Promise::Waiting(selector), + Promise::Fulfilled(t) => Promise::Fulfilled(f(t)), + Promise::Pending(selector) => Promise::Pending(selector), } } } @@ -56,24 +93,24 @@ impl Promise { impl> From> for Arguments { fn from(promise: Promise) -> Self { match promise { - Promise::Resolved(t) => t.into(), - Promise::Waiting(selector) => selector.into(), + Promise::Fulfilled(t) => t.into(), + Promise::Pending(selector) => selector.into(), } } } -impl Promise { - pub fn try_extract(self) -> Result { - match self { - Promise::Resolved(t) => Ok(t), - Promise::Waiting(promise) => Err(Promise::Waiting(promise)), - } +impl From for Promise { + fn from(value: T) -> Self { + Promise::Fulfilled(value) } } -impl From for Promise { - fn from(value: T) -> Self { - Promise::Resolved(value) +impl Promise { + pub fn try_resolve(self) -> Result { + match self { + Promise::Fulfilled(t) => Ok(t), + Promise::Pending(selector) => Err(selector), + } } } @@ -83,8 +120,8 @@ where { fn from(promise: Promise) -> Self { match promise { - Promise::Resolved(t) => t.into(), - Promise::Waiting(selector) => selector.into(), + Promise::Fulfilled(t) => t.into(), + Promise::Pending(selector) => selector.into(), } } } diff --git a/src/lib.rs b/src/lib.rs index 50e0d87d..4bed52dc 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -64,6 +64,7 @@ extern crate alloc; // } pub mod ability; +pub mod agent; pub mod capsule; pub mod delegation; pub mod did; @@ -73,8 +74,8 @@ pub mod metadata; pub mod new_wasm; pub mod nonce; pub mod number; -pub mod promise; pub mod proof; +pub mod reader; pub mod receipt; pub mod signature; pub mod task; diff --git a/src/nonce.rs b/src/nonce.rs index bf64784f..89d6453b 100644 --- a/src/nonce.rs +++ b/src/nonce.rs @@ -7,9 +7,6 @@ use std::fmt; #[cfg(not(target_arch = "wasm32"))] use uuid::Uuid; -#[cfg(target_arch = "wasm32")] -use web_sys; - // FIXME pub struct Unit; // FIXME diff --git a/src/proof/parentful.rs b/src/proof/parentful.rs index d18e8289..5bf611ce 100644 --- a/src/proof/parentful.rs +++ b/src/proof/parentful.rs @@ -56,7 +56,7 @@ where .check_same(their_parents) .map_err(ParentfulError::InvalidParents), Parentful::This(this) => this - .check_parents(their_parents) + .check_parent(their_parents) .map_err(ParentfulError::InvalidProofChain), }, Parentful::This(that) => match self { @@ -77,7 +77,7 @@ where type Parents = Parentful; type ParentError = ParentfulError::Error>; - fn check_parents(&self, proof: &Parentful) -> Result<(), Self::ParentError> { + fn check_parent(&self, proof: &Parentful) -> Result<(), Self::ParentError> { match proof { Parentful::Any => Ok(()), Parentful::Parents(their_parents) => match self { @@ -86,7 +86,7 @@ where .check_same(their_parents) .map_err(ParentfulError::InvalidParents), Parentful::This(this) => this - .check_parents(their_parents) + .check_parent(their_parents) .map_err(ParentfulError::InvalidProofChain), }, Parentful::This(that) => match self { @@ -119,7 +119,7 @@ where Ok(()) => Outcome::Proven, Err(e) => Outcome::InvalidParents(e), }, - Parentful::This(this) => match this.check_parents(their_parents) { + Parentful::This(this) => match this.check_parent(their_parents) { Ok(()) => Outcome::Proven, Err(e) => Outcome::InvalidProofChain(e), }, diff --git a/src/proof/parents.rs b/src/proof/parents.rs index a3a92b54..e947c201 100644 --- a/src/proof/parents.rs +++ b/src/proof/parents.rs @@ -4,5 +4,5 @@ pub trait CheckParents: CheckSame { type Parents; type ParentError; - fn check_parents(&self, proof: &Self::Parents) -> Result<(), Self::ParentError>; + fn check_parent(&self, proof: &Self::Parents) -> Result<(), Self::ParentError>; } diff --git a/src/reader.rs b/src/reader.rs new file mode 100644 index 00000000..bd913fe9 --- /dev/null +++ b/src/reader.rs @@ -0,0 +1,119 @@ +use crate::{ + ability::{arguments::Arguments, command::ToCommand}, + delegation::Delegatable, + invocation::Resolvable, + proof::{checkable::Checkable, same::CheckSame}, +}; +use serde::{Deserialize, Serialize}; + +// NOTE to self: this is helpful as a common container to lift various FFI into +#[derive(Clone, PartialEq, Debug)] +pub struct Reader { + pub env: Env, + pub val: T, +} + +impl> From> for Arguments { + fn from(reader: Reader) -> Self { + reader.val.into() + } +} + +// NOTE plug this into Reader like: Reader> +#[derive(Clone, PartialEq, Debug, Serialize, Deserialize)] +pub struct Builder(pub T); + +#[derive(Clone, PartialEq, Debug, Serialize, Deserialize)] +pub struct Promised(pub T); + +impl> From> for Arguments { + fn from(builder: Builder) -> Self { + builder.0.into() + } +} + +impl> From> for Arguments { + fn from(promised: Promised) -> Self { + promised.0.into() + } +} + +impl Reader { + pub fn map(self, func: F) -> Reader + where + F: FnOnce(T) -> U, + { + Reader { + env: self.env, + val: func(self.val), + } + } + + pub fn map_env(self, func: F) -> Reader + where + F: FnOnce(Env) -> NewEnv, + { + Reader { + env: func(self.env), + val: self.val, + } + } + + pub fn local(&self, modify_env: F, closure: G) -> U + where + T: Clone, + Env: Clone, + F: Fn(Env) -> Env, + G: Fn(Reader) -> U, + { + closure(Reader { + val: self.val.clone(), + env: modify_env(self.env.clone()), + }) + } +} + +impl From> for Reader> { + fn from(reader: Reader) -> Self { + reader.map(Builder) + } +} + +impl From>> for Reader { + fn from(reader: Reader>) -> Self { + reader.map(|b| b.0) + } +} + +impl From> for Reader> { + fn from(reader: Reader) -> Self { + reader.map(Promised) + } +} + +impl From>> for Reader { + fn from(reader: Reader>) -> Self { + reader.map(|p| p.0) + } +} + +impl> Delegatable for Reader { + type Builder = Reader>; +} + +impl> Resolvable for Reader { + type Promised = Reader>; +} + +impl ToCommand for Reader { + fn to_command(&self) -> String { + self.env.to_command() + } +} + +impl Checkable for Reader +where + Reader: CheckSame, +{ + type Hierarchy = Env::Hierarchy; +} diff --git a/src/task.rs b/src/task.rs index 8afc7916..50d07e82 100644 --- a/src/task.rs +++ b/src/task.rs @@ -21,7 +21,7 @@ pub struct Task { pub nonce: Option, pub cmd: String, - pub args: BTreeMap, + pub args: BTreeMap, // FIXME change to Arguments? } impl From for Id { @@ -65,29 +65,6 @@ impl From for Ipld { } } -// FIXME move to differet module -#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] -#[serde(transparent)] -pub struct DefaultTrue(pub bool); - -impl From for bool { - fn from(def_true: DefaultTrue) -> Self { - def_true.0 - } -} - -impl From for DefaultTrue { - fn from(b: bool) -> Self { - DefaultTrue(b) - } -} - -impl Default for DefaultTrue { - fn default() -> Self { - DefaultTrue(true) - } -} - impl From for Cid { fn from(task: Task) -> Cid { let mut buffer = vec![]; diff --git a/src/time.rs b/src/time.rs index 291f0aa9..987c6d23 100644 --- a/src/time.rs +++ b/src/time.rs @@ -34,7 +34,7 @@ impl Serialize for Timestamp { { match self { Timestamp::Sending(js_time) => js_time.serialize(serializer), - Timestamp::Receiving(sys_time) => todo!(), // FIXME See comment on deserilaizer sys_time.serialize(serializer), + Timestamp::Receiving(_sys_time) => todo!(), // FIXME See comment on deserilaizer sys_time.serialize(serializer), } } } @@ -106,6 +106,7 @@ impl<'de> Deserialize<'de> for JsTime { } // FIXME just lifting this from Elixir for now +#[derive(Debug, Clone, PartialEq, Eq)] pub struct OutOfRangeError { pub tried: SystemTime, } From 1edf634f9607f0b032107bdfac9e0ada335be04a Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Mon, 5 Feb 2024 22:26:50 -0800 Subject: [PATCH 050/188] Many docs. Very wow. --- Cargo.toml | 1 + flake.nix | 2 +- src/ability/arguments.rs | 94 +++++++--- src/ability/command.rs | 47 +++++ src/ability/crud/any.rs | 54 +++++- src/ability/crud/read.rs | 18 +- src/ability/dynamic.rs | 12 +- src/ability/js.rs | 17 ++ src/ability/js/parentful.rs | 95 ++++++++-- src/ability/js/parentless.rs | 72 +++++-- src/ability/msg.rs | 2 + src/ability/msg/any.rs | 34 ++++ src/ability/msg/receive.rs | 32 ++++ src/ability/msg/send.rs | 104 ++++++++++- src/ability/ucan/proxy.rs | 12 +- src/agent.rs | 8 +- src/capsule.rs | 52 ++++++ src/delegation.rs | 2 +- src/delegation/delegatable.rs | 4 +- src/delegation/payload.rs | 33 ++-- src/delegation/store.rs | 2 +- src/delegation/traits.rs | 4 +- src/did.rs | 61 +++++- src/invocation/payload.rs | 29 +-- src/invocation/promise.rs | 8 +- src/invocation/resolvable.rs | 4 +- src/ipld.rs | 50 ++++- src/ipld/error.rs | 1 + src/metadata.rs | 343 ++++++++++++++++++++++------------ src/metadata/keyed.rs | 50 +++++ src/proof/same.rs | 46 ++++- src/reader.rs | 12 +- src/receipt/payload.rs | 46 ++--- src/receipt/store.rs | 2 +- src/task.rs | 2 +- 35 files changed, 1058 insertions(+), 297 deletions(-) create mode 100644 src/ipld/error.rs create mode 100644 src/metadata/keyed.rs diff --git a/Cargo.toml b/Cargo.toml index a1b96af6..cec1b278 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -88,6 +88,7 @@ web-sys = { version = "0.3", features = ["Crypto", "CryptoKey", "CryptoKeyPair", [dev-dependencies] multihash = "0.18" +libipld = "0.16" [target.'cfg(not(target_arch = "wasm32"))'.dev-dependencies] criterion = "0.4" diff --git a/flake.nix b/flake.nix index 9cbbda01..c0c36b5c 100644 --- a/flake.nix +++ b/flake.nix @@ -291,7 +291,7 @@ name = "test:docs"; help = "Run Cargo doctests"; category = "test"; - command = "${cargo} test --doc"; + command = "${cargo} test --doc --features=mermaid_docs"; } # Docs { diff --git a/src/ability/arguments.rs b/src/ability/arguments.rs index 9a6e28cb..a8f81fe0 100644 --- a/src/ability/arguments.rs +++ b/src/ability/arguments.rs @@ -1,3 +1,5 @@ +//! Utilities for ability arguments + use libipld_core::{error::SerdeError, ipld::Ipld, serde as ipld_serde}; use serde::{Deserialize, Serialize}; use std::collections::BTreeMap; @@ -11,45 +13,81 @@ use js_sys::{Array, Map, Object, Reflect}; #[cfg(target_arch = "wasm32")] use crate::ipld; -// FIXME yes I'm seriously considering laying this out in the wasm abi by han d -// #[cfg(not(target_arch = "wasm32"))] +/// Named arguments +/// +/// Being such a common pattern, but with so few trait implementations, +/// [`Named`] is a newtype wrapper around unstructured named args: `BTreeMap`. +/// +/// # Examples +/// +/// ```rust +/// # use ucan::ability::arguments::Named; +/// # use url::Url; +/// # use libipld::ipld; +/// # +/// struct Execute { +/// program: Url, +/// args: Named, +/// } +/// +/// let ability = Execute { +/// program: Url::parse("file://host.name/path/to/exe").unwrap(), +/// args: Named::try_from(ipld!({ +/// "bold": true, +/// "message": "hello world", +/// })).unwrap() +/// }; +/// +/// assert_eq!(ability.args.get("bold"), Some(&ipld!(true))); +/// ``` #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] -pub struct Arguments(pub BTreeMap); - -// #[cfg(target_arch = "wasm32")] -// #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] -// #[wasm_bindgen] -// pub struct Arguments(#[wasm_bindgen(skip)] pub BTreeMap); - -impl Arguments { - pub fn from_iter(iterable: impl IntoIterator) -> Self { - Arguments(iterable.into_iter().collect()) - } +pub struct Named(pub BTreeMap); +impl Named { + /// Get the value associated with a key + /// + /// An alias for [`BTreeMap::insert`]. pub fn get(&self, key: &str) -> Option<&Ipld> { self.0.get(key) } + /// Inserts a key-value pair + /// + /// An alias for [`BTreeMap::insert`]. pub fn insert(&mut self, key: String, value: Ipld) -> Option { self.0.insert(key, value) } + /// Gets an iterator over the entries, sorted by key. + /// + /// A wrapper around [`BTreeMap::iter`]. pub fn iter(&self) -> impl Iterator { self.0.iter() } +} - pub fn into_iter(self) -> impl Iterator { +impl Default for Named { + fn default() -> Self { + Named(BTreeMap::new()) + } +} + +impl IntoIterator for Named { + type Item = (String, Ipld); + type IntoIter = std::collections::btree_map::IntoIter; + + fn into_iter(self) -> Self::IntoIter { self.0.into_iter() } } -impl Default for Arguments { - fn default() -> Self { - Arguments(BTreeMap::new()) +impl FromIterator<(String, Ipld)> for Named { + fn from_iter>(iter: T) -> Self { + Named(iter.into_iter().collect()) } } -impl TryFrom for Arguments { +impl TryFrom for Named { type Error = SerdeError; fn try_from(ipld: Ipld) -> Result { @@ -57,15 +95,15 @@ impl TryFrom for Arguments { } } -impl From for Ipld { - fn from(arguments: Arguments) -> Self { +impl From for Ipld { + fn from(arguments: Named) -> Self { ipld_serde::to_ipld(arguments).unwrap() } } #[cfg(target_arch = "wasm32")] -impl From for Object { - fn from(arguments: Arguments) -> Self { +impl From for Object { + fn from(arguments: Named) -> Self { let obj = Object::new(); for (k, v) in arguments.0 { Reflect::set(&obj, &k.into(), &ipld::Newtype(v).into()).unwrap(); @@ -77,7 +115,7 @@ impl From for Object { // NOTE saves a few cycles while calling by not cloning // the extra Object fields that we're not going to use #[cfg(target_arch = "wasm32")] -impl From<&Object> for Arguments { +impl From<&Object> for Named { fn from(obj: &Object) -> Self { let btree = Object::entries(obj) .iter() @@ -89,13 +127,13 @@ impl From<&Object> for Arguments { }) .collect::>(); - Arguments(btree) + Named(btree) } } #[cfg(target_arch = "wasm32")] -impl From for JsValue { - fn from(arguments: Arguments) -> Self { +impl From for JsValue { + fn from(arguments: Named) -> Self { arguments .0 .iter() @@ -111,13 +149,13 @@ impl From for JsValue { } #[cfg(target_arch = "wasm32")] -impl TryFrom for Arguments { +impl TryFrom for Named { type Error = (); // FIXME fn try_from(js: JsValue) -> Result { match ipld::Newtype::try_from(js).map(|newtype| newtype.0) { Err(()) => Err(()), // FIXME surface that we can't parse at all - Ok(Ipld::Map(map)) => Ok(Arguments(map)), + Ok(Ipld::Map(map)) => Ok(Named(map)), Ok(_wrong_ipld) => Err(()), // FIXME surface that we have the wrong type } } diff --git a/src/ability/command.rs b/src/ability/command.rs index a158221e..756562cb 100644 --- a/src/ability/command.rs +++ b/src/ability/command.rs @@ -1,4 +1,51 @@ +//! Ability command utilities +//! +//! Commands are the `cmd` field of a UCAN, and set the shape of the `args` field. +//! +//! ```js +//! // Here is a UCAN payload: +//! { +//! "iss": "did:example:123", +//! "aud": "did:example:456", +//! "cmd": "msg/send", // <--- This is the command +//! "args": { // ┐ +//! "to": "mailto:alice@example.com", // ├─ These are determined by the command +//! "message": "Hello, World!", // │ +//! } // ┘ +//! "exp": 1234567890 +//! } +//! ``` + +/// Attach a `cmd` field to a type +/// +/// Commands are the `cmd` field of a UCAN, and set the shape of the `args` field. +/// The `COMMAND` attaches this to types so that they can be serialized appropriately. +/// +/// # Examples +/// +/// ```rust +/// # use ucan::ability::command::Command; +/// # +/// struct Upload { +/// pub gb_quota: u64, +/// pub mime_types: Vec, +/// } +/// +/// impl Command for Upload { +/// const COMMAND: &'static str = "storage/upload"; +/// } +/// +/// assert_eq!(Upload::COMMAND, "storage/upload"); +/// ``` pub trait Command { + /// The value that will be placed in the UCAN's `cmd` field for the given type + /// + /// This is a `const` because it *must not*[^dynamic] depend on the runtime values of a type + /// in order to ensure type safety. + /// + /// [^dynamic]: Note that if the `dynamic` feature is enabled, the exception is + /// a special ability called [`Dynamic`][super::dynamic::Dynamic] (for e.g. JS FFI) + /// that uses a non-exported code path separate from the [`Command`] trait. const COMMAND: &'static str; } diff --git a/src/ability/crud/any.rs b/src/ability/crud/any.rs index 15149fa3..6554c30f 100644 --- a/src/ability/crud/any.rs +++ b/src/ability/crud/any.rs @@ -5,9 +5,14 @@ use crate::{ same::{CheckSame, OptionalFieldErr}, }, }; +use libipld_core::{error::SerdeError, ipld::Ipld, serde as ipld_serde}; use serde::{Deserialize, Serialize}; +use thiserror::Error; use url::Url; +#[cfg(target_arch = "wasm32")] +use crate::{ipld, proof::same::OptionalFieldReason}; + #[cfg(target_arch = "wasm32")] use wasm_bindgen::prelude::*; @@ -28,11 +33,38 @@ impl NoParents for Builder {} impl CheckSame for Builder { type Error = OptionalFieldErr; + fn check_same(&self, proof: &Self) -> Result<(), Self::Error> { - self.uri.check_same(&proof.uri) + self.uri + .check_same(&proof.uri) + .map_err(|err| OptionalFieldErr { + field: "uri".into(), + err, + }) + } +} + +impl TryFrom for Builder { + type Error = SerdeError; + + fn try_from(ipld: Ipld) -> Result { + ipld_serde::from_ipld(ipld) + } +} + +impl From for Ipld { + fn from(builder: Builder) -> Self { + builder.into() } } +// FIXME +#[derive(Debug, Error)] +pub enum E { + #[error("Some error")] + SomeErrMsg(String), +} + #[cfg(target_arch = "wasm32")] #[wasm_bindgen] pub struct CrudAny(#[wasm_bindgen(skip)] pub Builder); @@ -41,13 +73,25 @@ pub struct CrudAny(#[wasm_bindgen(skip)] pub Builder); #[cfg(target_arch = "wasm32")] #[wasm_bindgen] impl CrudAny { + pub fn to_js(self) -> JsValue { + ipld::Newtype(Ipld::from(self.0)).into() + } + + pub fn from_js(js_val: JsValue) -> Result { + ipld::Newtype::try_into_jsvalue(js_val).map(CrudAny) + } + pub fn command(&self) -> String { Builder::COMMAND.to_string() } - pub fn check_same(&self, proof: &CrudAny) -> Result<(), JsValue> { - self.0 - .check_same(&proof.0) - .map_err(|err| JsValue::from_str(&format!("{:?}", err))) + pub fn check_same(&self, proof: &CrudAny) -> Result<(), JsError> { + self.0.check_same(&proof.0).map_err(|_| { + OptionalFieldErr { + field: "uri".into(), + err: OptionalFieldReason::MissingField, + } + .into() + }) } } diff --git a/src/ability/crud/read.rs b/src/ability/crud/read.rs index f07a2b61..3d8c409c 100644 --- a/src/ability/crud/read.rs +++ b/src/ability/crud/read.rs @@ -1,17 +1,19 @@ use super::any; use crate::{ - ability::command::Command, + ability::{arguments, command::Command}, proof::{checkable::Checkable, parentful::Parentful, parents::CheckParents, same::CheckSame}, }; use libipld_core::{error::SerdeError, ipld::Ipld, serde as ipld_serde}; use serde::{Deserialize, Serialize}; -use std::collections::BTreeMap; use thiserror::Error; use url::Url; #[cfg(target_arch = "wasm32")] use wasm_bindgen::prelude::*; +#[cfg(target_arch = "wasm32")] +use crate::ipld; + // Read is its own builder #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(deny_unknown_fields)] @@ -20,7 +22,7 @@ pub struct Read { pub uri: Option, #[serde(default, skip_serializing_if = "Option::is_none")] - pub args: Option>, // FIXME rename Argumenst to get the traits? + pub args: Option, } impl Command for Read { @@ -63,7 +65,7 @@ impl CheckSame for Read { if let Some(args) = &self.args { if let Some(proof_args) = &proof.args { - for (k, v) in args { + for (k, v) in args.iter() { if proof_args.get(k) != Some(v) { return Err(E::SomeErrMsg("".into())); } @@ -91,6 +93,14 @@ pub struct CrudRead(#[wasm_bindgen(skip)] pub Read); #[cfg(target_arch = "wasm32")] #[wasm_bindgen] impl CrudRead { + pub fn to_jsvalue(self) -> JsValue { + ipld::Newtype(Ipld::from(self.0)).into() + } + + pub fn from_jsvalue(js_val: JsValue) -> Result { + ipld::Newtype::try_into_jsvalue(js_val).map(CrudRead) + } + pub fn command(&self) -> String { Read::COMMAND.to_string() } diff --git a/src/ability/dynamic.rs b/src/ability/dynamic.rs index 8d3acb06..10d4d2b7 100644 --- a/src/ability/dynamic.rs +++ b/src/ability/dynamic.rs @@ -1,6 +1,6 @@ //! This module is for dynamic abilities, especially for FFI and Wasm support -use super::{arguments::Arguments, command::ToCommand}; +use super::{arguments, command::ToCommand}; use crate::{ipld, proof::same::CheckSame}; use js_sys; use libipld_core::{error::SerdeError, ipld::Ipld, serde as ipld_serde}; @@ -14,7 +14,7 @@ use wasm_bindgen::prelude::*; #[derive(Clone, PartialEq, Debug, Serialize, Deserialize)] // FIXME serialize / deserilaize? pub struct Dynamic { pub cmd: String, - pub args: Arguments, + pub args: arguments::Named, } impl ToCommand for Dynamic { @@ -23,7 +23,7 @@ impl ToCommand for Dynamic { } } -impl From for Arguments { +impl From for arguments::Named { fn from(dynamic: Dynamic) -> Self { dynamic.args } @@ -68,7 +68,7 @@ impl TryFrom for Dynamic { Ok(Dynamic { cmd, - args: Arguments(btree), // FIXME kill clone + args: arguments::Named(btree), // FIXME kill clone }) } else { Err(JsValue::NULL) // FIXME @@ -87,10 +87,10 @@ impl CheckSame for Dynamic { self.args.0.iter().try_for_each(|(k, v)| { if let Some(proof_v) = proof.args.0.get(k) { if v != proof_v { - return Err("Arguments mismatch".into()); + return Err("arguments::Named mismatch".into()); } } else { - return Err("Arguments mismatch".into()); + return Err("arguments::Named mismatch".into()); } Ok(()) diff --git a/src/ability/js.rs b/src/ability/js.rs index 8422e6d3..e66d92f2 100644 --- a/src/ability/js.rs +++ b/src/ability/js.rs @@ -1,2 +1,19 @@ +//! Bindings for the JavaScript via Wasm +//! +//! Note that these are all [`wasm_bindgen`]-specific, +//! and are not recommended elsewhere due to limited +//! type safety, poorer performance, and restrictions +//! on the API placed by [`wasm_bindgen`]. +//! +//! The overall pattern is roughly: "JS code hands the +//! Rust code a config object with handlers at runtime". +//! The Rust takes those handlers, and dispatches them +//! as part of the normal flow. +//! +//! When compiled for Wasm, the other abilities in this +//! crate export JS bindings. This allows them to be +//! plugged into e.g. ability hierarchies from the JS +//! side as an extension mechanism. + pub mod parentful; pub mod parentless; diff --git a/src/ability/js/parentful.rs b/src/ability/js/parentful.rs index 72140b27..ee34effe 100644 --- a/src/ability/js/parentful.rs +++ b/src/ability/js/parentful.rs @@ -1,5 +1,7 @@ +//! JavaScript interafce for abilities that *do* require a parent hierarchy + use crate::{ - ability::{arguments::Arguments, command::ToCommand, dynamic}, + ability::{arguments, command::ToCommand, dynamic}, proof::{checkable::Checkable, parentful::Parentful, parents::CheckParents, same::CheckSame}, reader::Reader, }; @@ -7,19 +9,16 @@ use js_sys::{Function, JsString, Map}; use std::collections::BTreeMap; use wasm_bindgen::{prelude::*, JsValue}; -// NOTE NOTE NOTE: the strategy is: "you (JS) hand us the cfg" AKA strategy, -// and we (Rust) wire it up and run it for you -// NOTE becuase of the above, no need to export WithParents to JS // FIXME rename -type WithParents = Reader; +type WithParents = Reader; // Promise = Promise? Ah, nope becuase we need that CID on the promise // FIXME represent promises (for Promised) and options (for builder) -// FIXME rename ability? abilityconfig? leave as is? +/// The configuration object that expresses an ability (with parents) from JS #[derive(Debug, Clone, PartialEq, Default)] #[wasm_bindgen(getter_with_clone)] -pub struct Config { +pub struct ParentfulConfig { pub command: String, pub is_nonce_meaningful: bool, @@ -30,35 +29,95 @@ pub struct Config { pub check_parent: BTreeMap, } +// NOTE if changed, please update this in the docs for `ParentfulArgs` below #[wasm_bindgen(typescript_custom_section)] -const CONFIG_ARGS: &str = r#" +const PARENTFUL_ARGS: &str = r#" interface ParentfulArgs { command: string, - is_nonce_meaningful: boolean, - validate_shape: Function, - check_same: Function, - check_parent: Map + isNonceMeaningful: boolean, + validateShape: Function, + checkSame: Function, + checkParent: Map } "#; #[wasm_bindgen] extern "C" { + /// Named constructor arguments for `ParentfulConfig` + /// + /// This forms the basis for configuring an ability. + /// These values will be used at runtime to perform + /// checks on the ability (e.g. during delegation), + /// for indexing, and storage (among others). + /// + /// ```typescript + /// // TypeScript + /// interface ParentfulArgs { + /// command: string, + /// isNonceMeaningful: boolean, + /// validateShape: Function, + /// checkSame: Function, + /// checkParent: Map + /// } + /// ``` #[wasm_bindgen(typescript_type = "ParentfulArgs")] pub type ParentfulArgs; + /// Get the [`Command`][crate::ability::command::Command] string + #[wasm_bindgen(js_name = command)] pub fn command(this: &ParentfulArgs) -> String; + + /// Whether the nonce should factor into a receipt's global index ([`task::Id`]) + #[wasm_bindgen(js_name = isNonceMeaningful)] pub fn is_nonce_meaningful(this: &ParentfulArgs) -> bool; + + /// Parser validator + #[wasm_bindgen(js_name = validateShape)] pub fn validate_shape(this: &ParentfulArgs) -> Function; + + /// Validate an instance against a candidate proof of the same shape + #[wasm_bindgen(js_name = checkSame)] pub fn check_same(this: &ParentfulArgs) -> Function; + + /// Validate an instance against a candidate proof containing a parent + #[wasm_bindgen(js_name = checkParent)] pub fn check_parent(this: &ParentfulArgs) -> Map; } #[wasm_bindgen] -impl Config { - // FIXME object args as an option +impl ParentfulConfig { + /// Construct a new `ParentfulConfig` from JavaScript + /// + /// # Examples + /// + /// ```javascript + /// // JavaScript + /// const msgSendConfig = new ParentfulConfig({ + /// command: "msg/send", + /// isNonceMeaningful: true, + /// validateShape: (args) => { + /// if (args.to && args.message && args.length() === 2) { + /// return true; + /// } + /// return false; + /// }, + /// checkSame: (proof, args) => { + /// if (proof.to === args.to && proof.message === args.message) { + /// return true; + /// } + /// return false; + /// }, + /// checkParent: new Map([ + /// ["msg/*", (proof, args) => { + /// proof.to === args.to && proof.message === args.message + /// }] + /// ]) + /// } + /// ); + /// ``` #[wasm_bindgen(constructor)] - pub fn new(js_obj: ParentfulArgs) -> Result { - Ok(Config { + pub fn new(js_obj: ParentfulArgs) -> Result { + Ok(ParentfulConfig { command: command(&js_obj), is_nonce_meaningful: is_nonce_meaningful(&js_obj), validate_shape: validate_shape(&js_obj), @@ -109,7 +168,7 @@ impl CheckSame for WithParents { .call2( &this, &self.val.clone().into(), - &Arguments::from(proof.clone()).into(), + &arguments::Named::from(proof.clone()).into(), ) .map(|_| ()) } @@ -131,7 +190,7 @@ impl CheckParents for WithParents { } } -impl ToCommand for Config { +impl ToCommand for ParentfulConfig { fn to_command(&self) -> String { self.command.clone() } diff --git a/src/ability/js/parentless.rs b/src/ability/js/parentless.rs index 1897fa22..21f7e586 100644 --- a/src/ability/js/parentless.rs +++ b/src/ability/js/parentless.rs @@ -1,19 +1,17 @@ +//! JavaScript interafce for abilities that *do not* require a parent hierarchy + use crate::{ - ability::{arguments::Arguments, command::ToCommand, dynamic}, + ability::{arguments, command::ToCommand, dynamic}, proof::{parentless::NoParents, same::CheckSame}, reader::Reader, }; use js_sys::Function; use wasm_bindgen::prelude::*; -// NOTE NOTE NOTE: the strategy is: "you (JS) hand us the cfg" AKA strategy, -// and we (Rust) wire it up and run it for you -// NOTE becuase of the above, no need to export JsWithParents to JS // FIXME rename -type WithoutParents = Reader; +type WithoutParents = Reader; -// FIXME rename ability? abilityconfig? leave as is? -// #[wasm_bindgen(getter_with_clone)] +/// The configuration object that expresses an ability (without parents) from JS #[derive(Debug, Clone, PartialEq)] #[wasm_bindgen] pub struct ParentlessConfig { @@ -25,30 +23,78 @@ pub struct ParentlessConfig { // FIXME represent promises (for Promised) and options (for builder) +// NOTE if changed, please update this in the docs for `ParentlessArgs` below #[wasm_bindgen(typescript_custom_section)] -const PARENTLESS_ARGS: &str = r#" +pub const PARENTLESS_ARGS: &str = r#" interface ParentlessArgs { command: string, - is_nonce_meaningful: boolean, - validate_shape: Function, - check_same: Function, + isNonceMeaningful: boolean, + validateShape: Function, + checkSame: Function, } "#; #[wasm_bindgen] extern "C" { + /// Named constructor arguments for `ParentlessConfig` + /// + /// This forms the basis for configuring an ability. + /// These values will be used at runtime to perform + /// checks on the ability (e.g. during delegation), + /// for indexing, and storage (among others). + /// + /// ```typescript + /// // TypeScript + /// interface ParentlessArgs { + /// command: string, + /// isNonceMeaningful: boolean, + /// validateShape: Function, + /// checkSame: Function, + /// } + /// ``` #[wasm_bindgen(typescript_type = "ParentlessArgs")] pub type ParentlessArgs; + /// Get the [`Command`][crate::ability::command::Command] string + #[wasm_bindgen(js_name = command)] pub fn command(this: &ParentlessArgs) -> String; + + /// Whether the nonce should factor into a receipt's global index ([`task::Id`]) + #[wasm_bindgen(js_name = isNonceMeaningful)] pub fn is_nonce_meaningful(this: &ParentlessArgs) -> bool; + + /// Parser validator + #[wasm_bindgen(js_name = validateShape)] pub fn validate_shape(this: &ParentlessArgs) -> Function; + + /// Validate an instance against a candidate proof of the same shape + #[wasm_bindgen(js_name = checkSame)] pub fn check_same(this: &ParentlessArgs) -> Function; } #[wasm_bindgen] impl ParentlessConfig { - // FIXME object args as an option + /// Construct a new `ParentlessConfig` from JavaScript + /// + /// # Examples + /// + /// ```javascript + /// // JavaScript + /// const msgSendConfig = new ParentlessConfig({ + /// command: "msg/send", + /// isNonceMeaningful: true, + /// validateShape: (args) => { + /// if (args.to && args.message && args.length() === 2) { + /// return true; + /// } + /// return false; + /// }, + /// checkSame: (proof, args) => { + /// proof.to === args.to && proof.message === args.message + /// }, + /// } + /// ); + /// ``` #[wasm_bindgen(constructor)] pub fn new(js_obj: ParentlessArgs) -> ParentlessConfig { ParentlessConfig { @@ -80,7 +126,7 @@ impl CheckSame for WithoutParents { .call2( &this, &self.val.clone().into(), - &Arguments::from(proof.clone()).into(), + &arguments::Named::from(proof.clone()).into(), ) .map(|_| ()) } diff --git a/src/ability/msg.rs b/src/ability/msg.rs index 33792dd3..0f5bdd0f 100644 --- a/src/ability/msg.rs +++ b/src/ability/msg.rs @@ -1,3 +1,5 @@ +//! Message abilities + pub mod any; pub mod receive; pub mod send; diff --git a/src/ability/msg/any.rs b/src/ability/msg/any.rs index ae603aa4..5e35b424 100644 --- a/src/ability/msg/any.rs +++ b/src/ability/msg/any.rs @@ -1,3 +1,5 @@ +//! "Any" message ability (superclass of all message abilities) + use crate::{ ability::command::Command, proof::{parentless::NoParents, same::CheckSame}, @@ -6,6 +8,38 @@ use libipld_core::{error::SerdeError, ipld::Ipld, serde as ipld_serde}; use serde::{Deserialize, Serialize}; use url::Url; +#[cfg_attr(doc, aquamarine::aquamarine)] +/// The [`Any`] message ability may not be invoked, but it is the superclass of +/// all other message abilities. For example, +/// the [`message::Receive`][super::receive::Receive] ability may be +/// proven by the [`Any`] ability in a delegation chain. +/// +/// # Delegation Hierarchy +/// +/// The hierarchy of message abilities is as follows: +/// +/// ```mermaid +/// flowchart TB +/// top("*") +/// +/// subgraph Message Abilities +/// any("msg/*") +/// +/// subgraph Invokable +/// send("msg/send") +/// rec("msg/receive") +/// end +/// end +/// +/// sendrun{{"invoke"}} +/// recrun{{"invoke"}} +/// +/// top --> any +/// any --> send -.-> sendrun +/// any --> rec -.-> recrun +/// +/// style any stroke:orange; +/// ``` #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] #[serde(deny_unknown_fields)] pub struct Any { diff --git a/src/ability/msg/receive.rs b/src/ability/msg/receive.rs index 5583331d..07c86aea 100644 --- a/src/ability/msg/receive.rs +++ b/src/ability/msg/receive.rs @@ -1,3 +1,5 @@ +//! The ability to receive messages + use crate::{ ability::command::Command, proof::{checkable::Checkable, parentful::Parentful, parents::CheckParents, same::CheckSame}, @@ -8,9 +10,39 @@ use url::Url; use super::any as msg; +#[cfg_attr(doc, aquamarine::aquamarine)] +/// The ability to receive messages +/// +/// This ability is used to receive messages from other actors. +/// +/// # Delegation Hierarchy +/// +/// The hierarchy of message abilities is as follows: +/// +/// ```mermaid +/// flowchart TB +/// top("*") +/// +/// subgraph Message Abilities +/// any("msg/*") +/// +/// subgraph Invokable +/// rec("msg/receive") +/// end +/// end +/// +/// recrun{{"invoke"}} +/// +/// top --> any +/// any --> rec -.-> recrun +/// +/// style rec stroke:orange; +/// ``` #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] #[serde(deny_unknown_fields)] pub struct Receive { + /// An *optional* URL (e.g. email, DID, socket) to receive messages from. + /// This assumes that the `subject` has the authority to issue such a capability. pub from: Option, } diff --git a/src/ability/msg/send.rs b/src/ability/msg/send.rs index 194f6d7b..63c31ca6 100644 --- a/src/ability/msg/send.rs +++ b/src/ability/msg/send.rs @@ -1,5 +1,7 @@ +//! The ability to send messages + use crate::{ - ability::{arguments::Arguments, command::Command}, + ability::{arguments, command::Command}, delegation::Delegatable, invocation::{Promise, Resolvable}, proof::{checkable::Checkable, parentful::Parentful, parents::CheckParents, same::CheckSame}, @@ -14,13 +16,105 @@ use super::any as msg; #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] #[serde(deny_unknown_fields)] pub struct Generic { + /// The recipient of the message pub to: To, + + /// The sender address of the message + /// + /// This *may* be a URL (such as an email address). + /// If provided, the `subject` must have the right to send from this address. pub from: From, + + /// The main body of the message pub message: Message, } +#[cfg_attr(doc, aquamarine::aquamarine)] +/// The executable/dispatchable variant of the `msg/send` ability. +/// +/// # Lifecycle +/// +/// The hierarchy of message abilities is as follows: +/// +/// ```mermaid +/// flowchart LR +/// subgraph Delegations +/// top("*") +/// +/// any("msg/*") +/// +/// subgraph Invokable +/// send("msg/send") +/// end +/// end +/// +/// sendpromise("msg::send::Promised") +/// sendrun("msg::send::Resolved") +/// +/// top --> any +/// any --> send -.->|invoke| sendpromise -.->|resolve| sendrun -.-> exe{{execute}} +/// +/// style sendrun stroke:orange; +/// ``` pub type Resolved = Generic; + +#[cfg_attr(doc, aquamarine::aquamarine)] +/// The delegatable variant of the `msg/send` ability. +/// +/// # Delegation Hierarchy +/// +/// The hierarchy of message abilities is as follows: +/// +/// ```mermaid +/// flowchart LR +/// top("*") +/// +/// subgraph Message Abilities +/// any("msg/*") +/// +/// subgraph Invokable +/// send("msg/send") +/// end +/// end +/// +/// sendrun{{"invoke"}} +/// +/// top --> any +/// any --> send -.-> sendrun +/// +/// style send stroke:orange; +/// ``` pub type Builder = Generic, Option, Option>; + +#[cfg_attr(doc, aquamarine::aquamarine)] +/// The invoked variant of the `msg/send` ability +/// +/// This variant may be linked to other invoked abilities by [`Promise`][crate::invocation::Promise]s. +/// +/// # Lifecycle +/// +/// The hierarchy of message abilities is as follows: +/// +/// ```mermaid +/// flowchart LR +/// subgraph Delegations +/// top("*") +/// +/// any("msg/*") +/// +/// subgraph Invokable +/// send("msg/send") +/// end +/// end +/// +/// sendpromise("msg::send::Promised") +/// sendrun("msg::send::Resolved") +/// +/// top --> any +/// any --> send -.->|invoke| sendpromise -.->|resolve| sendrun -.-> exe{{execute}} +/// +/// style sendpromise stroke:orange; +/// ``` pub type Promised = Generic, Promise, Promise>; impl Delegatable for Resolved { @@ -31,7 +125,7 @@ impl Resolvable for Resolved { type Promised = Promised; } -impl From for Arguments { +impl From for arguments::Named { fn from(b: Builder) -> Self { let mut btree = BTreeMap::new(); b.to.map(|to| btree.insert("to".into(), to.to_string().into())); @@ -40,13 +134,13 @@ impl From for Arguments { b.message .map(|msg| btree.insert("message".into(), msg.into())); - Arguments(btree) + arguments::Named(btree) } } -impl From for Arguments { +impl From for arguments::Named { fn from(promised: Promised) -> Self { - Arguments(BTreeMap::from_iter([ + arguments::Named(BTreeMap::from_iter([ ("to".into(), promised.to.map(String::from).into()), ("from".into(), promised.from.map(String::from).into()), ("message".into(), promised.message.into()), diff --git a/src/ability/ucan/proxy.rs b/src/ability/ucan/proxy.rs index 4851356a..da916a9c 100644 --- a/src/ability/ucan/proxy.rs +++ b/src/ability/ucan/proxy.rs @@ -1,5 +1,5 @@ use crate::{ - ability::{arguments::Arguments, command::Command}, + ability::{arguments, command::Command}, delegation::Delegatable, invocation::Promise, }; @@ -18,9 +18,9 @@ pub struct Generic { // FIXME should args just be a CID } -pub type Resolved = Generic; -pub type Builder = Generic>; -pub type Promised = Generic>; +pub type Resolved = Generic; +pub type Builder = Generic>; +pub type Promised = Generic>; impl Command for Generic { const COMMAND: &'static str = "ucan/proxy"; @@ -50,8 +50,8 @@ impl TryFrom for Resolved { } } -impl From for Arguments { - fn from(b: Builder) -> Arguments { +impl From for arguments::Named { + fn from(b: Builder) -> arguments::Named { let mut args = b.args.unwrap_or_default(); args.insert("cmd".into(), Ipld::String(b.cmd)); args diff --git a/src/agent.rs b/src/agent.rs index aed278bd..809414a3 100644 --- a/src/agent.rs +++ b/src/agent.rs @@ -19,7 +19,7 @@ impl Agent { // signature::Envelope::new(payload, signature) // } - pub fn invoke( + pub fn invoke( &self, delegation: Delegation, proof_chain: Vec>, // FIXME T must also accept Self and * @@ -34,7 +34,7 @@ impl Agent { todo!() } - pub fn revoke( + pub fn revoke( &self, delegation: Delegation, ) -> () @@ -44,14 +44,14 @@ impl Agent { todo!() } - pub fn receive_delegation( + pub fn receive_delegation( &self, delegation: Delegation, ) -> () { todo!() } - pub fn receive_invocation(&self, invocation: Invocation) -> () { + pub fn receive_invocation(&self, invocation: Invocation) -> () { todo!() } diff --git a/src/capsule.rs b/src/capsule.rs index 357bc5ec..2b4a9e25 100644 --- a/src/capsule.rs +++ b/src/capsule.rs @@ -1,3 +1,55 @@ +//! Capsule type utilities +//! +//! Capsule types are a pattern where you associate a string to type, +//! and use the tag as a key and the payload as a value in a map. +//! This helps disambiguate types when serializing and deserializing. +//! +//! Unlike a `type` field, the fact that it's on the outside of the payload +//! is often helpful in improving serializaion and deserialization performance. +//! It also avoids needing fields on nested structures where the inner types are known. +//! +//! Some simple examples include: +//! +//! ```javascript +//! {"u32": 42} +//! {"i64": 99} +//! {"coord": {"x": 1, "y": 2}} +//! { +//! "boundary": [ +//! {"x": 1, "y": 2}, // ─┐ +//! {"x": 3, "y": 4}, // ├─ Untagged coords inside "boundary" capsule +//! {"x": 5, "y": 6}, // │ +//! {"x": 7, "y": 8} // ─┘ +//! ] +//! } +//! ``` +//! +//! UCAN uses these in payload wrappers, such as [`Delegation`][crate::delegation::Delegation]. + +/// The primary capsule trait +/// +/// # Examples +/// +/// ```rust +/// # use ucan::capsule::Capsule; +/// # use std::collections::BTreeMap; +/// # +/// # #[derive(Debug, PartialEq)] +/// struct Coord { +/// x: i32, +/// y: i32 +/// } +/// +/// impl Capsule for Coord { +/// const TAG: &'static str = "coordinate"; +/// } +/// +/// let coord = Coord { x: 1, y: 2 }; +/// let capsuled = BTreeMap::from_iter([(Coord::TAG.to_string(), coord)]); +/// +/// assert_eq!(capsuled.get("coordinate"), Some(&Coord { x: 1, y: 2 })); +/// ```` pub trait Capsule { + /// The tag to use when constructing or matching on the capsule const TAG: &'static str; } diff --git a/src/delegation.rs b/src/delegation.rs index 01584561..3a8b3e3b 100644 --- a/src/delegation.rs +++ b/src/delegation.rs @@ -22,7 +22,7 @@ use crate::{metadata as meta, signature}; pub type Delegation = signature::Envelope>; // FIXME -impl Delegation { +impl Delegation { // FIXME include cache //pub fn check>(&self, store: &S) -> Result<(), ()> { // if let Ok(is_valid) = store.previously_checked(self) { diff --git a/src/delegation/delegatable.rs b/src/delegation/delegatable.rs index 151d4fab..757976c8 100644 --- a/src/delegation/delegatable.rs +++ b/src/delegation/delegatable.rs @@ -1,5 +1,5 @@ -use crate::ability::arguments::Arguments; +use crate::ability::arguments; pub trait Delegatable: Sized { - type Builder: TryInto + From + Into; + type Builder: TryInto + From + Into; } diff --git a/src/delegation/payload.rs b/src/delegation/payload.rs index 752c3fe7..8a3616e9 100644 --- a/src/delegation/payload.rs +++ b/src/delegation/payload.rs @@ -1,12 +1,13 @@ use super::{condition::traits::Condition, delegatable::Delegatable}; use crate::{ - ability::{arguments::Arguments, command::Command}, + ability::{arguments, command::Command}, capsule::Capsule, did::Did, invocation, invocation::Resolvable, metadata as meta, - metadata::{Mergable, Metadata}, + metadata::Metadata, + // metadata::{Mergable, Metadata}, nonce::Nonce, proof::{ checkable::Checkable, @@ -21,7 +22,7 @@ use std::{collections::BTreeMap, fmt::Debug}; use web_time::SystemTime; #[derive(Debug, Clone, PartialEq)] -pub struct Payload { +pub struct Payload { pub issuer: Did, pub subject: Did, pub audience: Did, @@ -36,11 +37,11 @@ pub struct Payload { pub not_before: Option, } -impl Capsule for Payload { +impl Capsule for Payload { const TAG: &'static str = "ucan/d/1.0.0-rc.1"; } -impl Serialize for Payload +impl Serialize for Payload where InternalSerializer: From>, Payload: Clone, @@ -54,7 +55,7 @@ where } } -impl<'de, T: Delegatable, C: Condition + DeserializeOwned, E: meta::Entries> Deserialize<'de> +impl<'de, T: Delegatable, C: Condition + DeserializeOwned, E: meta::MultiKeyed> Deserialize<'de> for Payload where Payload: TryFrom, @@ -73,7 +74,7 @@ where } } -impl TryFrom +impl TryFrom for Payload where Payload: TryFrom, @@ -86,14 +87,14 @@ where } } -impl From> for Ipld { +impl From> for Ipld { fn from(payload: Payload) -> Self { payload.into() } } // FIXME this likely should move to invocation -impl<'a, T: Delegatable + Resolvable + Checkable + Clone, C: Condition, E: meta::Entries> +impl<'a, T: Delegatable + Resolvable + Checkable + Clone, C: Condition, E: meta::MultiKeyed> Payload { pub fn check( @@ -154,7 +155,7 @@ struct Acc<'a, T: Checkable> { } // FIXME this should move to Delegatable -fn step<'a, T: Checkable, U: Delegatable, C: Condition, E: meta::Entries>( +fn step<'a, T: Checkable, U: Delegatable, C: Condition, E: meta::MultiKeyed>( prev: &Acc<'a, T>, proof: &Payload, invoked_ipld: &Ipld, @@ -226,7 +227,7 @@ struct InternalSerializer { #[serde(rename = "cmd")] command: String, #[serde(rename = "args")] - arguments: Arguments, + arguments: arguments::Named, #[serde(rename = "cond")] conditions: Vec, @@ -241,11 +242,11 @@ struct InternalSerializer { expiration: Timestamp, } -impl, E: meta::Entries + Clone> +impl, E: meta::MultiKeyed> From> for InternalSerializer where BTreeMap: From, - Metadata: Mergable, + Ipld: From, { fn from(payload: Payload) -> Self { InternalSerializer { @@ -257,7 +258,7 @@ where arguments: payload.ability_builder.into(), conditions: payload.conditions.into_iter().map(|c| c.into()).collect(), - metadata: payload.metadata.merge(), + metadata: payload.metadata.into(), nonce: payload.nonce, not_before: payload.not_before, @@ -275,7 +276,7 @@ impl TryFrom for InternalSerializer { } // FIXME -// impl, E: meta::Entries + Clone> TryFrom +// impl, E: meta::MultiKeyed + Clone> TryFrom // for Payload, C, E> // { // type Error = (); // FIXME @@ -310,7 +311,7 @@ impl TryFrom for InternalSerializer { // } // } // -// impl, E: meta::Entries + Clone, F> +// impl, E: meta::MultiKeyed + Clone, F> // From, C, E>> for InternalSerializer // where // Metadata: Mergable, diff --git a/src/delegation/store.rs b/src/delegation/store.rs index 438e8272..a75db20f 100644 --- a/src/delegation/store.rs +++ b/src/delegation/store.rs @@ -6,7 +6,7 @@ use std::collections::{BTreeMap, HashMap}; use web_time::SystemTime; // NOTE can already look up by CID in other traits -pub trait IndexedStore { +pub trait IndexedStore { type Error; fn get_by(query: Query) -> Result>, Self::Error>; diff --git a/src/delegation/traits.rs b/src/delegation/traits.rs index b442734a..0d102d22 100644 --- a/src/delegation/traits.rs +++ b/src/delegation/traits.rs @@ -1,5 +1,5 @@ -use crate::{ability::arguments::Arguments, did::Did, nonce::Nonce, task, task::Task}; +use crate::{ability::arguments, did::Did, nonce::Nonce, task, task::Task}; pub trait Delegatable: Sized { - type Builder: TryInto + From + Into; + type Builder: TryInto + From + Into; } diff --git a/src/did.rs b/src/did.rs index 2f1aae78..66f8beaa 100644 --- a/src/did.rs +++ b/src/did.rs @@ -2,10 +2,25 @@ use did_url::DID; use libipld_core::ipld::Ipld; use serde::{Deserialize, Serialize}; use std::fmt; +use thiserror::Error; #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(into = "String", try_from = "String")] -pub struct Did(DID); +/// A [Decentralized Identifier (DID)][wiki] +/// +/// This is a newtype wrapper around the [`DID`] type from the [`did_url`] crate. +/// +/// # Examples +/// +/// ```rust +/// # use ucan::did::Did; +/// # +/// let did = Did::try_from("did:example:123".to_string()).unwrap(); +/// assert_eq!(did.0.method(), "example"); +/// ``` +/// +/// [wiki]: https://en.wikipedia.org/wiki/Decentralized_identifier +pub struct Did(pub DID); impl From for String { fn from(did: Did) -> Self { @@ -34,12 +49,50 @@ impl From for Ipld { } impl TryFrom for Did { - type Error = >::Error; // FIXME also include the "can't parse form ipld" case; seems like someythjing taht can be abstrcated out, too + type Error = FromIpldError; fn try_from(ipld: Ipld) -> Result { match ipld { - Ipld::String(string) => Did::try_from(string), - _ => todo!(), // Err(()), + Ipld::String(string) => Did::try_from(string).map_err(FromIpldError::StructuralError), + other => Err(FromIpldError::NotAnIpldString(other)), } } } + +/// Errors that can occur when converting to or from a [`Did`] +#[derive(Debug, Clone, PartialEq, Error)] +pub enum FromIpldError { + /// Strutural errors in the [`Did`] + StructuralError(did_url::Error), + + /// The [`Ipld`] was not a string + NotAnIpldString(Ipld), +} + +impl fmt::Display for FromIpldError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + FromIpldError::StructuralError(e) => write!(f, "DID Error: {}", e), + FromIpldError::NotAnIpldString(_ipld) => write!(f, "Not an IPLD String"), // FIXME include the bad ipld, but needs a Display instance + } + } +} + +impl Serialize for FromIpldError { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + self.to_string().serialize(serializer) + } +} + +impl<'de> Deserialize<'de> for FromIpldError { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + let ipld = Ipld::deserialize(deserializer)?; + Ok(FromIpldError::NotAnIpldString(ipld)) + } +} diff --git a/src/invocation/payload.rs b/src/invocation/payload.rs index ee9c0ed0..58c4e892 100644 --- a/src/invocation/payload.rs +++ b/src/invocation/payload.rs @@ -1,10 +1,11 @@ use super::resolvable::Resolvable; use crate::{ - ability::{arguments::Arguments, command::Command}, + ability::{arguments, command::Command}, capsule::Capsule, did::Did, metadata as meta, - metadata::{Mergable, Metadata}, + metadata::Metadata, + // metadata::{Mergable, Metadata}, nonce::Nonce, time::Timestamp, }; @@ -15,7 +16,7 @@ use std::{collections::BTreeMap, fmt::Debug}; // FIXME this version should not be resolvable... // FIXME ...or at least have two versions via abstraction #[derive(Debug, Clone, PartialEq)] -pub struct Payload { +pub struct Payload { pub issuer: Did, pub subject: Did, pub audience: Option, @@ -32,13 +33,13 @@ pub struct Payload { } // NOTE This is the version that accepts promises -pub type Unresolved = Payload; +pub type Unresolved = Payload; // type Dynamic = Payload; <- ? // FIXME parser for both versions // #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] // #[serde(untagged)] -// pub enum MaybeResolved + Into> +// pub enum MaybeResolved + Into> // where // Payload: From, // Unresolved: From, @@ -48,11 +49,11 @@ pub type Unresolved = Payload; // Unresolved(Unresolved), // } -impl Capsule for Payload { +impl Capsule for Payload { const TAG: &'static str = "ucan/i/1.0.0-rc.1"; } -impl Serialize for Payload +impl Serialize for Payload where Payload: Clone, InternalSerializer: From>, @@ -66,7 +67,7 @@ where } } -impl<'de, T, E: meta::Entries> serde::Deserialize<'de> for Payload +impl<'de, T, E: meta::MultiKeyed> serde::Deserialize<'de> for Payload where Payload: TryFrom, as TryFrom>::Error: Debug, @@ -84,7 +85,7 @@ where } } -impl TryFrom for Payload +impl TryFrom for Payload where Payload: TryFrom, { @@ -96,7 +97,7 @@ where } } -impl From> for Ipld { +impl From> for Ipld { fn from(payload: Payload) -> Self { payload.into() } @@ -115,7 +116,7 @@ struct InternalSerializer { #[serde(rename = "cmd")] command: String, #[serde(rename = "args")] - arguments: Arguments, + arguments: arguments::Named, #[serde(rename = "prf")] proofs: Vec, @@ -195,7 +196,9 @@ impl TryFrom for InternalSerializer { // } // } -impl, E: meta::Entries> From> for InternalSerializer { +impl, E: meta::MultiKeyed> From> + for InternalSerializer +{ fn from(payload: Payload) -> Self { InternalSerializer { issuer: payload.issuer, @@ -207,7 +210,7 @@ impl, E: meta::Entries> From> for Int proofs: payload.proofs, cause: payload.cause, - metadata: payload.metadata.merge(), + metadata: payload.metadata.into(), nonce: payload.nonce, diff --git a/src/invocation/promise.rs b/src/invocation/promise.rs index ac260249..f2dcfa0a 100644 --- a/src/invocation/promise.rs +++ b/src/invocation/promise.rs @@ -1,4 +1,4 @@ -use crate::ability::arguments::Arguments; +use crate::ability::arguments; use libipld_core::{cid::Cid, ipld::Ipld, serde as ipld_serde}; use serde_derive::{Deserialize, Serialize}; use std::{collections::BTreeMap, fmt::Debug}; @@ -90,7 +90,7 @@ impl Promise { } } -impl> From> for Arguments { +impl> From> for arguments::Named { fn from(promise: Promise) -> Self { match promise { Promise::Fulfilled(t) => t.into(), @@ -158,7 +158,7 @@ impl TryFrom for Selector { } } -impl From for Arguments { +impl From for arguments::Named { fn from(selector: Selector) -> Self { let mut btree = BTreeMap::new(); @@ -174,6 +174,6 @@ impl From for Arguments { } } - Arguments(btree) + arguments::Named(btree) } } diff --git a/src/invocation/resolvable.rs b/src/invocation/resolvable.rs index 232f2538..d9570d98 100644 --- a/src/invocation/resolvable.rs +++ b/src/invocation/resolvable.rs @@ -1,5 +1,5 @@ -use crate::ability::arguments::Arguments; +use crate::ability::arguments; pub trait Resolvable: Sized { - type Promised: TryInto + From + Into; + type Promised: TryInto + From + Into; } diff --git a/src/ipld.rs b/src/ipld.rs index 1c781e26..04f40ad8 100644 --- a/src/ipld.rs +++ b/src/ipld.rs @@ -1,15 +1,50 @@ -use libipld_core::ipld::Ipld; +//! Helpers for working with [`Ipld`] pub mod cid; +use libipld_core::ipld::Ipld; +use std::fmt; + #[cfg(target_arch = "wasm32")] use wasm_bindgen::prelude::*; #[cfg(target_arch = "wasm32")] use js_sys::{Array, Map, Object, Uint8Array}; +/// A wrapper around [`Ipld`] that has additional trait implementations +/// +/// Usage is very simple: wrap a [`Newtype`] to gain access to additional traits and methods. +/// +/// ```rust +/// # use libipld_core::ipld::Ipld; +/// # use ucan::ipld; +/// # +/// let ipld = Ipld::String("hello".into()); +/// let wrapped = ipld::Newtype(ipld.clone()); +/// // wrapped.some_trait_method(); +/// ``` +// / +/// Unwrap a [`Newtype`] to use any interfaces that expect plain [`Ipld`]. +/// +/// ``` +/// # use libipld_core::ipld::Ipld; +/// # use ucan::ipld; +/// # +/// # let ipld = Ipld::String("hello".into()); +/// # let wrapped = ipld::Newtype(ipld.clone()); +/// # +/// assert_eq!(wrapped.0, ipld); +/// ``` +#[derive(Debug, Clone, PartialEq)] pub struct Newtype(pub Ipld); +// FIXME +// impl fmt::Display for Newtype { +// fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { +// write!(f, "{}", String::from(self.0)) +// } +// } + impl From for Newtype { fn from(ipld: Ipld) -> Self { Self(ipld) @@ -22,6 +57,19 @@ impl From for Ipld { } } +#[cfg(target_arch = "wasm32")] +impl Newtype { + pub fn try_into_jsvalue>(js_val: JsValue) -> Result + where + JsError: From<>::Error>, + { + match Newtype::try_from(js_val) { + Err(_err) => Err(JsError::new("can't convert")), // FIXME + Ok(nt) => nt.0.try_into().map_err(JsError::from), + } + } +} + // TODO testme #[cfg(target_arch = "wasm32")] impl From for JsValue { diff --git a/src/ipld/error.rs b/src/ipld/error.rs new file mode 100644 index 00000000..4752292a --- /dev/null +++ b/src/ipld/error.rs @@ -0,0 +1 @@ +// pub enum diff --git a/src/metadata.rs b/src/metadata.rs index a16e2f97..1e222a13 100644 --- a/src/metadata.rs +++ b/src/metadata.rs @@ -1,65 +1,217 @@ -use libipld_core::{error::SerdeError, ipld::Ipld, serde as ipld_serde}; -use serde::{Deserialize, Serialize, Serializer}; -use std::{collections::BTreeMap, convert::Infallible}; - -// FIXME rename modeule to metadata - -pub trait Entry { - const KEY: &'static str; -} +//! Metadata (i.e. the UCAN `meta` field) -pub trait Entries: TryFrom + Into { - const KEYS: &'static [&'static str]; -} +mod keyed; +pub use keyed::{Keyed, MultiKeyed}; -pub trait Mergable { - fn merge(self) -> BTreeMap; - fn extract(merged: BTreeMap) -> Self; -} +use libipld_core::ipld::Ipld; +use serde::{Deserialize, Serialize, Serializer}; +use std::{collections::BTreeMap, convert::Infallible}; +/// An uninhabited type that signals no known metadata fields. +/// +/// This uses a similar technique as [`Infallible`]; +/// it is not possible to create a runtime value of this type, so merely stubs out code paths. +/// +/// ``` +/// # use ucan::metadata::{Metadata, Empty}; +/// # use std::collections::BTreeMap; +/// # use libipld_core::ipld::Ipld; +/// # +/// let kv: BTreeMap = BTreeMap::from_iter([ +/// ("foo".into(), Ipld::String("hello world".to_string())), +/// ("bar".into(), Ipld::Integer(42)), +/// ("baz".into(), Ipld::List(vec![Ipld::Float(3.14)])) +/// ]); +/// +/// let meta: Metadata = Metadata::try_from(kv.clone()).unwrap(); +/// +/// assert_eq!(meta.known().len(), 0); +/// assert_eq!(meta.unknown(), &kv); +/// ``` +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub enum Empty {} +/// A type alias for [`Metadata`] with no known fields. +pub type Unstructured = Metadata; + // NOTE no Serde +/// Parsed metadata fields. +/// +/// If you don't have any known fields, you can set `T ` to [`Empty`] (or [`Unstructured`]) #[derive(Debug, Clone, PartialEq)] pub struct Metadata { + /// Structured metadata, selected by matching `T` known: BTreeMap, + + /// Unstructured metadata unknown: BTreeMap, } -impl Mergable for Metadata { - fn merge(self) -> BTreeMap { - self.unknown +impl Metadata { + /// Constructor for [`Metadata`] + /// + /// This checks that no duplicate keys are present + /// + /// # Examples + /// + /// ```rust + /// # use ucan::metadata::{Metadata, Empty}; + /// # use std::collections::BTreeMap; + /// # use libipld_core::ipld::Ipld; + /// # + /// #[derive(Debug, Clone, PartialEq)] + /// pub enum MyFacts { + /// Timeout(u32), + /// Retry{max: u32, delay: u32}, + /// NotGoingToUseThisOne(String), + /// } + /// + /// let known: BTreeMap = BTreeMap::from_iter([ + /// ("timeout".into(), MyFacts::Timeout(1000)), + /// ("retry".into(), MyFacts::Retry{max: 5, delay: 100}) + /// ]); + /// + /// let unknown: BTreeMap = BTreeMap::from_iter([ + /// ("foo".into(), Ipld::String("hello world".to_string())), + /// ("bar".into(), Ipld::Integer(42)), + /// ("baz".into(), Ipld::List(vec![Ipld::Float(3.14)])) + /// ]); + /// + /// let meta = Metadata::new(known.clone(), unknown.clone()).unwrap(); + /// + /// assert_eq!(meta.known(), &known.clone()); + /// assert_eq!(meta.unknown(), &unknown.clone()); + /// + /// let collision: BTreeMap = BTreeMap::from_iter([ + /// ("timeout".into(), Ipld::String("not a timeout".to_string())), + /// ]); + /// + /// let meta = Metadata::new(known, collision); + /// + /// assert!(meta.is_err()); + /// ``` + pub fn new( + known: BTreeMap, + unknown: BTreeMap, + ) -> Result { + for k in known.keys() { + if unknown.contains_key(k) { + return Err(k.into()); + } + } + + Ok(Self { known, unknown }) } - // FIXME better error - fn extract(unknown: BTreeMap) -> Self { + /// Getter for the `known` field + pub fn known<'a>(&'a self) -> &'a BTreeMap { + &self.known + } + + /// Getter for the `unknown` field + pub fn unknown<'a>(&'a self) -> &'a BTreeMap { + &self.unknown + } + + /// Insert a value into the `known` field + /// + /// This will return `Some(Entry::Unknown(ipld))` if you insert a key that already + /// exists in the `unknown` field. + /// + /// It will return `Some(t: T)` if you insert a key that was already present + /// in the `known` field. + pub fn insert_known<'a>(&'a mut self, key: String, value: T) -> Option> { + if let Some(ipld) = self.unknown.get(&key) { + self.known.insert(key, value); + return Some(Entry::Unknown(ipld.clone())); + } + + self.known.insert(key, value).map(Entry::Known) + } + + /// Insert a value into the `unknown` field + /// + /// This will return `Some(Entry::Unknown(ipld))` if you insert a key that already + /// exists in the `unknown` field. + /// + /// It will return `Some(t: T)` if you insert a key that was already present + /// in the `known` field. + pub fn insert_unknown<'a>(&'a mut self, key: String, value: Ipld) -> Option> + where + T: Clone, + { + if let Some(t) = self.known.get(&key) { + self.unknown.insert(key, value); + return Some(Entry::Known(t.clone())); + } + + self.unknown.insert(key, value).map(Entry::Unknown) + } + + /// Remove a field from either field + /// + /// This will return `Some(Entry::Unknown(ipld))` if there was a key in the `unknown` field. + /// + /// It will return `Some(t: T)` if there was a key in the `known` field. + pub fn remove_key<'a>(&'a mut self, key: &str) -> Option> { + if let Some(ipld) = self.unknown.remove(key) { + return Some(Entry::Unknown(ipld)); + } + + self.known.remove(key).map(Entry::Known) + } +} + +/// Tag values as belonging to the `known` or `unknown` field. +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub enum Entry { + /// The tag for a value in the `known` field + Known(T), + + /// The tag for a value in the `unknown` field + Unknown(Ipld), +} + +impl Default for Metadata { + fn default() -> Self { Metadata { known: BTreeMap::new(), - unknown, + unknown: BTreeMap::new(), } } } -impl Mergable for Metadata { - fn merge(self) -> BTreeMap { - let mut meta = self.unknown; +impl From> for BTreeMap { + fn from(meta: Metadata) -> Self { + meta.unknown + } +} - // FIXME kill the clone - for (k, v) in self.known { - meta.insert(k, v.into()); +impl> From> for BTreeMap { + // NOTE duplicate keys "shouldn't" be possible (because this roughly follows GDP) + // ...so we can just merge + fn from(meta: Metadata) -> Self { + let mut btree = meta.unknown; + for (k, v) in meta.known { + btree.insert(k, v.into()); } - - meta + btree } +} +impl From> for Metadata { // FIXME better error - fn extract(merged: BTreeMap) -> Self { + fn from(merged: BTreeMap) -> Self { let mut known = BTreeMap::new(); let mut unknown = BTreeMap::new(); for (k, v) in merged { - if let Ok(entry) = v.clone().try_into() { - known.insert(k, entry); + if T::KEYS.contains(&k.as_str()) { + if let Ok(entry) = v.clone().try_into() { + known.insert(k, entry); + } else { + unknown.insert(k, v); + } } else { unknown.insert(k, v); } @@ -69,15 +221,24 @@ impl Mergable for Metadata { } } +impl From> for Metadata { + fn from(btree: BTreeMap) -> Self { + Metadata { + known: BTreeMap::new(), + unknown: btree, + } + } +} + impl TryFrom> for Ipld { type Error = Infallible; fn try_from(meta: Metadata) -> Result { - Ok(Ipld::Map(meta.merge())) + Ok(Ipld::Map(meta.unknown)) } } -impl TryFrom> for Ipld { +impl> TryFrom> for Ipld { type Error = String; // FIXME fn try_from(meta: Metadata) -> Result { @@ -95,17 +256,17 @@ impl TryFrom> for Ipld { } } -impl Serialize for Metadata { +impl Serialize for Metadata { fn serialize(&self, serializer: S) -> Result where S: Serializer, { - let s = Ipld::Map((*self).clone().merge()); // FIXME kill that clone with tons of refs? + let s = Ipld::Map((*self).clone().into()); serde::Serialize::serialize(&s, serializer) } } -impl<'de, T: Entries + Clone> Deserialize<'de> for Metadata { +impl<'de, T: MultiKeyed + Clone> Deserialize<'de> for Metadata { fn deserialize(d: D) -> Result where D: serde::Deserializer<'de>, @@ -114,7 +275,7 @@ impl<'de, T: Entries + Clone> Deserialize<'de> for Metadata { } } -impl TryFrom for Metadata { +impl TryFrom for Metadata { type Error = (); // FIXME fn try_from(ipld: Ipld) -> Result { @@ -142,82 +303,28 @@ impl TryFrom for Metadata { } } -impl Metadata { - pub fn new( - known: BTreeMap, - unknown: BTreeMap, - ) -> Result { - for k in known.keys() { - if unknown.contains_key(k) { - return Err(k.into()); - } - } - - Ok(Self { known, unknown }) - } - - pub fn known<'a>(&'a self) -> &'a BTreeMap { - &self.known - } - - pub fn unknown<'a>(&'a self) -> &'a BTreeMap { - &self.unknown - } - - // FIXME types - pub fn insert_known<'a>(&'a mut self, key: String, value: T) -> Result<(), Option> { - if let Some(_) = self.unknown.get(&key) { - return Err(None); - } - - match self.known.insert(key, value) { - Some(v) => Err(Some(v)), - _ => Ok(()), - } - } - - pub fn insert_unknown<'a>(&'a mut self, key: String, value: Ipld) -> Result<(), Option> { - if let Some(_) = self.known.get(&key) { - return Err(None); - } - - match self.unknown.insert(key, value) { - Some(v) => Err(Some(v)), - _ => Ok(()), - } - } -} - -impl Default for Metadata { - fn default() -> Self { - Metadata { - known: BTreeMap::new(), - unknown: BTreeMap::new(), - } - } -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -#[serde(deny_unknown_fields)] -pub struct IpvmConfig { - pub max_retries: u32, - pub workflow_fuel: u32, -} - -impl Entry for IpvmConfig { - const KEY: &'static str = "ipvm/config"; -} - -impl From for Ipld { - fn from(config: IpvmConfig) -> Self { - config.into() - } -} - -impl TryFrom for IpvmConfig { - type Error = SerdeError; - - fn try_from(ipld: Ipld) -> Result { - ipld_serde::from_ipld(ipld) - } -} +// // FIXME Just as an example, plz delete +// #[derive(Debug, Clone, Serialize, Deserialize)] +// #[serde(deny_unknown_fields)] +// pub struct IpvmConfig { +// pub max_retries: u32, +// pub workflow_fuel: u32, +// } +// +// impl Keyed for IpvmConfig { +// const KEY: &'static str = "ipvm/config"; +// } +// +// impl From for Ipld { +// fn from(config: IpvmConfig) -> Self { +// config.into() +// } +// } +// +// impl TryFrom for IpvmConfig { +// type Error = SerdeError; +// +// fn try_from(ipld: Ipld) -> Result { +// ipld_serde::from_ipld(ipld) +// } +// } diff --git a/src/metadata/keyed.rs b/src/metadata/keyed.rs new file mode 100644 index 00000000..174023f1 --- /dev/null +++ b/src/metadata/keyed.rs @@ -0,0 +1,50 @@ +//! Keys for metadata fields (for parsing and guarding entries) + +use libipld_core::ipld::Ipld; + +/// A parser trait for types that can be used as metadata fields. +/// +/// # Examples +/// +/// ```rust +/// # use ucan::metadata::Keyed; +/// # use std::collections::BTreeMap; +/// # use libipld::{ipld, ipld::Ipld}; +/// # +/// pub struct MyKeyed { +/// pub foo: String, +/// pub bar: u32, +/// } +/// +/// impl Keyed for MyKeyed { +/// const KEY: &'static str = "my/entry"; +/// } +/// assert_eq!(MyKeyed::KEY, "my/entry"); +/// +/// let kv: BTreeMap = BTreeMap::from_iter([ +/// (MyKeyed::KEY.into(), ipld!({"foo": "hello world", "bar": 42})) +/// ]); +/// +/// assert_eq!(kv.get(MyKeyed::KEY), Some(&ipld!({"foo": "hello world", "bar": 42}))); +/// ``` +pub trait Keyed { + /// The (string) key for this entry + /// + /// These should be unique per `Keyed` type to avoid parser collisions. + /// Even if a duplicate key is used, the shape of the contained data can + /// also be used to disambiguate. + const KEY: &'static str; +} + +// FIXME keyed? +/// A parser trait for one-or-more unioned types that can be used as metadata fields. +/// +/// [`MultiKeyed`] may be composed of a single entry, or the union of sevveral e.g. via enum. +pub trait MultiKeyed: TryFrom + Into { + /// The (string) keys for an enum merging multiple [`Keyed`]s + const KEYS: &'static [&'static str]; +} + +impl + Into> MultiKeyed for T { + const KEYS: &'static [&'static str] = &[T::KEY]; +} diff --git a/src/proof/same.rs b/src/proof/same.rs index b05f8cb6..22f2e060 100644 --- a/src/proof/same.rs +++ b/src/proof/same.rs @@ -1,5 +1,10 @@ use crate::did::Did; +use core::fmt; use serde::{Deserialize, Serialize}; +use thiserror::Error; + +#[cfg(target_arch = "wasm32")] +use wasm_bindgen::prelude::*; pub trait CheckSame { type Error; @@ -11,18 +16,41 @@ pub trait CheckSame { #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub struct Unequal; -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] -pub struct OpionalFieldErr { - pub field: T, // Enum of fields - pub err: OptionalFieldErr, +// FIXME move under error.rs +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Error)] +#[cfg_attr(target_arch = "wasm32", wasm_bindgen(getter_with_clone))] +pub struct OptionalFieldErr { + pub field: String, + pub err: OptionalFieldReason, } -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] -pub enum OptionalFieldErr { +impl fmt::Display for OptionalFieldErr { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "Field {} is {}", self.field, self.err) + } +} + +// FIXME at minimum the name is confusing +#[derive(Copy, Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Error)] +#[cfg_attr(target_arch = "wasm32", wasm_bindgen)] +pub enum OptionalFieldReason { MissingField, UnequalValue, } +impl fmt::Display for OptionalFieldReason { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!( + f, + "{}", + match self { + OptionalFieldReason::MissingField => "missing", + OptionalFieldReason::UnequalValue => "unequal", + } + ) + } +} + impl CheckSame for Did { type Error = Unequal; @@ -36,18 +64,18 @@ impl CheckSame for Did { } impl CheckSame for Option { - type Error = OptionalFieldErr; + type Error = OptionalFieldReason; fn check_same(&self, proof: &Self) -> Result<(), Self::Error> { match proof { None => Ok(()), Some(proof_) => match self { - None => Err(OptionalFieldErr::MissingField), + None => Err(OptionalFieldReason::MissingField), Some(self_) => { if self_.eq(proof_) { Ok(()) } else { - Err(OptionalFieldErr::UnequalValue) + Err(OptionalFieldReason::UnequalValue) } } }, diff --git a/src/reader.rs b/src/reader.rs index bd913fe9..3e1de5fc 100644 --- a/src/reader.rs +++ b/src/reader.rs @@ -1,5 +1,5 @@ use crate::{ - ability::{arguments::Arguments, command::ToCommand}, + ability::{arguments, command::ToCommand}, delegation::Delegatable, invocation::Resolvable, proof::{checkable::Checkable, same::CheckSame}, @@ -13,7 +13,7 @@ pub struct Reader { pub val: T, } -impl> From> for Arguments { +impl> From> for arguments::Named { fn from(reader: Reader) -> Self { reader.val.into() } @@ -26,13 +26,13 @@ pub struct Builder(pub T); #[derive(Clone, PartialEq, Debug, Serialize, Deserialize)] pub struct Promised(pub T); -impl> From> for Arguments { +impl> From> for arguments::Named { fn from(builder: Builder) -> Self { builder.0.into() } } -impl> From> for Arguments { +impl> From> for arguments::Named { fn from(promised: Promised) -> Self { promised.0.into() } @@ -97,11 +97,11 @@ impl From>> for Reader { } } -impl> Delegatable for Reader { +impl> Delegatable for Reader { type Builder = Reader>; } -impl> Resolvable for Reader { +impl> Resolvable for Reader { type Promised = Reader>; } diff --git a/src/receipt/payload.rs b/src/receipt/payload.rs index 0600528d..83662cc2 100644 --- a/src/receipt/payload.rs +++ b/src/receipt/payload.rs @@ -1,25 +1,20 @@ use super::responds::Responds; use crate::{ - ability::arguments::Arguments, - capsule::Capsule, - did::Did, - metadata as meta, - metadata::{Mergable, Metadata}, - nonce::Nonce, - time::Timestamp, + ability::arguments, capsule::Capsule, did::Did, metadata as meta, metadata::Metadata, + nonce::Nonce, time::Timestamp, }; -use libipld_core::{cid::Cid, ipld::Ipld, serde as ipld_serde}; +use libipld_core::{cid::Cid, error::SerdeError, ipld::Ipld, serde as ipld_serde}; use serde::{de::DeserializeOwned, Deserialize, Serialize, Serializer}; use std::{collections::BTreeMap, fmt::Debug}; // FIXME serialize/deseialize split out for when the T has implementations #[derive(Debug, Clone, PartialEq)] -pub struct Payload { +pub struct Payload { pub issuer: Did, pub ran: Cid, - pub out: Result, + pub out: Result, pub next: Vec, // FIXME rename here or in spec? pub proofs: Vec, @@ -29,20 +24,21 @@ pub struct Payload { pub issued_at: Option, } -impl Capsule for Payload { +impl Capsule for Payload { const TAG: &'static str = "ucan/r/1.0.0-rc.1"; // FIXME extract out version } -impl Serialize for Payload +impl Serialize for Payload where Payload: Clone, T::Success: Serialize + DeserializeOwned, + Ipld: From, { fn serialize(&self, serializer: S) -> Result where S: Serializer, { - let s = InternalSerializer::from(self.clone()); // FIXME kill that clone with tons of refs? + let s = InternalSerializer::from((*self).clone()); // FIXME kill that clone with tons of refs? serde::Serialize::serialize(&s, serializer) } } @@ -50,7 +46,7 @@ where impl< 'de, T: Responds + Deserialize<'de>, - E: meta::Entries + Clone + DeserializeOwned + Serialize, + E: DeserializeOwned + Serialize + meta::MultiKeyed + TryFrom, > Deserialize<'de> for Payload where as TryFrom>>::Error: Debug, @@ -67,20 +63,20 @@ where } } -impl TryFrom +impl + meta::MultiKeyed> TryFrom for Payload where T::Success: Serialize + DeserializeOwned, { - type Error = (); // FIXME serde error + type Error = SerdeError; fn try_from(ipld: Ipld) -> Result { - let s: InternalSerializer = ipld_serde::from_ipld(ipld).map_err(|_| ())?; + let s: InternalSerializer = ipld_serde::from_ipld(ipld)?; Ok(s.into()) } } -impl From> for Ipld { +impl From> for Ipld { fn from(payload: Payload) -> Self { payload.into() } @@ -96,7 +92,7 @@ where issuer: Did, ran: Cid, - out: Result, + out: Result, next: Vec, // FIXME rename here or in spec? #[serde(rename = "prf")] @@ -109,11 +105,9 @@ where issued_at: Option, } -impl From> - for Payload +impl> From> for Payload where T::Success: Serialize + DeserializeOwned, - Metadata: Mergable, { fn from(s: InternalSerializer) -> Self { Payload { @@ -122,17 +116,17 @@ where out: s.out, next: s.next, proofs: s.proofs, - metadata: Metadata::extract(s.metadata), + metadata: s.metadata.into(), nonce: s.nonce, issued_at: s.issued_at, } } } -impl From> for InternalSerializer +impl From> for InternalSerializer where + Ipld: From, T::Success: Serialize + DeserializeOwned, - Metadata: Mergable, { fn from(s: Payload) -> Self { InternalSerializer { @@ -141,7 +135,7 @@ where out: s.out, next: s.next, proofs: s.proofs, - metadata: s.metadata.merge(), + metadata: s.metadata.into(), nonce: s.nonce, issued_at: s.issued_at, } diff --git a/src/receipt/store.rs b/src/receipt/store.rs index 76d5ebc9..35c7d5ed 100644 --- a/src/receipt/store.rs +++ b/src/receipt/store.rs @@ -2,7 +2,7 @@ use super::{Receipt, Responds}; use crate::{metadata, task}; use libipld_core::ipld::Ipld; -pub trait Store { +pub trait Store { type Error; fn get(id: task::Id) -> Result, Self::Error> diff --git a/src/task.rs b/src/task.rs index 50d07e82..00411b7b 100644 --- a/src/task.rs +++ b/src/task.rs @@ -21,7 +21,7 @@ pub struct Task { pub nonce: Option, pub cmd: String, - pub args: BTreeMap, // FIXME change to Arguments? + pub args: BTreeMap, // FIXME change to Named? } impl From for Id { From 395cbc275acd0167de1c49748cc0e20be8a60582 Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Mon, 5 Feb 2024 23:35:20 -0800 Subject: [PATCH 051/188] more doctests --- src/ability/dynamic.rs | 23 ++++++++- src/reader.rs | 103 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 124 insertions(+), 2 deletions(-) diff --git a/src/ability/dynamic.rs b/src/ability/dynamic.rs index 10d4d2b7..f6998fa3 100644 --- a/src/ability/dynamic.rs +++ b/src/ability/dynamic.rs @@ -9,11 +9,30 @@ use std::{collections::BTreeMap, fmt::Debug}; use wasm_bindgen::prelude::*; // NOTE the lack of checking functions! -// This is meant to be embedded inside of structs that have e.g. FFI bindings to -// a validation function, such as a &js_sys::Function, Ruby magnus::function!, etc etc + +/// A "dynamic" ability with the bare minimum of statics +/// +///

+/// +/// Dynamic none of the typical ability traits directly. Rather, it must be wrapped +/// in [`Reader`][crate::reader::Reader], which wires up dynamic dispatch for the +/// relevant traits using a configuration struct. #[derive(Clone, PartialEq, Debug, Serialize, Deserialize)] // FIXME serialize / deserilaize? pub struct Dynamic { + /// The `cmd` field (hooks into a dynamic version of [`Command`][crate::ability::command::Command]) pub cmd: String, + + /// Unstructured, named arguments + /// + /// The only requirement is that the keys are strings and the values are [`Ipld`] pub args: arguments::Named, } diff --git a/src/reader.rs b/src/reader.rs index 3e1de5fc..d87f0e41 100644 --- a/src/reader.rs +++ b/src/reader.rs @@ -7,9 +7,66 @@ use crate::{ use serde::{Deserialize, Serialize}; // NOTE to self: this is helpful as a common container to lift various FFI into + +/// A struct that attaches an ambient environment to a value +/// +/// This is helpful for dependency injection and/or passing around values that +/// would otherwise need to be threaded through next to the value. +/// +/// This is loosely based on the [`Reader`][SO] from Haskell, but is not implemented +/// monadically. The fully "ambient" features of the `Reader` monad are not present here. +/// +/// # Examples +/// +/// ```rust +/// # use ucan::reader::Reader; +/// # use std::string::ToString; +/// # +/// struct Config { +/// name: String, +/// formatter: Box String>, +/// trimmer: Box String>, +/// } +/// +/// fn run(r: Reader) -> String { +/// let formatted = (r.env.formatter)(r.val.to_string()); +/// (r.env.trimmer)(formatted) +/// } +/// +/// let cfg1 = Config { +/// name: "cfg1".into(), +/// formatter: Box::new(|s| s.to_uppercase()), +/// trimmer: Box::new(|mut s| s.trim().into()) +/// }; +/// +/// let cfg2 = Config { +/// name: "cfg2".into(), +/// formatter: Box::new(|s| s.to_lowercase()), +/// trimmer: Box::new(|mut s| s.split_off(5).into()) +/// }; +/// +/// +/// let reader1 = Reader { +/// env: cfg1, +/// val: " value", +/// }; +/// +/// let reader2 = Reader { +/// env: cfg2, +/// val: " value", +/// }; +/// +/// assert_eq!(run(reader1), "VALUE"); +/// assert_eq!(run(reader2), "e"); +/// ``` +/// +/// [SO]: https://stackoverflow.com/questions/14178889/what-is-the-purpose-of-the-reader-monad #[derive(Clone, PartialEq, Debug)] pub struct Reader { + /// The environment (or configuration) being passed with the value pub env: Env, + + /// The raw value pub val: T, } @@ -39,6 +96,7 @@ impl> From> for arguments::Named { } impl Reader { + /// Map a function over the `val` of the [`Reader`] pub fn map(self, func: F) -> Reader where F: FnOnce(T) -> U, @@ -49,6 +107,7 @@ impl Reader { } } + /// Modify the `env` field of the [`Reader`] pub fn map_env(self, func: F) -> Reader where F: FnOnce(Env) -> NewEnv, @@ -59,6 +118,50 @@ impl Reader { } } + /// Temporarily modify the environment + /// + /// # Examples + /// + /// ```rust + /// # use ucan::reader::Reader; + /// # use std::string::ToString; + /// # + /// # #[derive(Clone)] + /// struct Config<'a> { + /// name: String, + /// formatter: &'a dyn Fn(String) -> String, + /// trimmer: &'a dyn Fn(String) -> String, + /// } + /// + /// fn run(r: Reader) -> String { + /// let formatted = (r.env.formatter)(r.val.to_string()); + /// (r.env.trimmer)(formatted) + /// } + /// + /// let cfg = Config { + /// name: "cfg1".into(), + /// formatter: &|s| s.to_uppercase(), + /// trimmer: &|mut s| s.trim().into() + /// }; + /// + /// let my_reader = Reader { + /// env: cfg, + /// val: " value", + /// }; + /// + /// assert_eq!(run(my_reader.clone()), "VALUE"); + /// + /// // Modify the env locally + /// let observed = my_reader.clone().local(|mut env| { + /// // Modifying env + /// env.trimmer = &|mut s: String| s.split_off(5).into(); + /// env + /// }, |r| run(r)); // Running + /// assert_eq!(observed, "E"); + /// + /// // Back to normal (the above was in fact "local") + /// assert_eq!(run(my_reader.clone()), "VALUE"); + /// ``` pub fn local(&self, modify_env: F, closure: G) -> U where T: Clone, From 448fdfb01370d7dbc6b2506c87b18de84de8cd60 Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Tue, 6 Feb 2024 00:05:10 -0800 Subject: [PATCH 052/188] Save for the day; working on JS nonce gen --- src/ability/js/parentful.rs | 2 +- src/ability/js/parentless.rs | 2 +- src/ability/msg/any.rs | 7 ++++--- src/ipld/cid.rs | 8 +++++++- src/nonce.rs | 23 ++++++++++++++--------- src/number.rs | 9 +++++++++ src/proof.rs | 2 ++ src/proof/checkable.rs | 8 ++++++++ src/proof/internal.rs | 3 ++- src/reader.rs | 2 ++ 10 files changed, 50 insertions(+), 16 deletions(-) diff --git a/src/ability/js/parentful.rs b/src/ability/js/parentful.rs index ee34effe..cfb7b61a 100644 --- a/src/ability/js/parentful.rs +++ b/src/ability/js/parentful.rs @@ -1,4 +1,4 @@ -//! JavaScript interafce for abilities that *do* require a parent hierarchy +//! JavaScript interface for abilities that *do* require a parent hierarchy use crate::{ ability::{arguments, command::ToCommand, dynamic}, diff --git a/src/ability/js/parentless.rs b/src/ability/js/parentless.rs index 21f7e586..53dac266 100644 --- a/src/ability/js/parentless.rs +++ b/src/ability/js/parentless.rs @@ -1,4 +1,4 @@ -//! JavaScript interafce for abilities that *do not* require a parent hierarchy +//! JavaScript interface for abilities that *do not* require a parent hierarchy use crate::{ ability::{arguments, command::ToCommand, dynamic}, diff --git a/src/ability/msg/any.rs b/src/ability/msg/any.rs index 5e35b424..4d7ece1b 100644 --- a/src/ability/msg/any.rs +++ b/src/ability/msg/any.rs @@ -10,9 +10,10 @@ use url::Url; #[cfg_attr(doc, aquamarine::aquamarine)] /// The [`Any`] message ability may not be invoked, but it is the superclass of -/// all other message abilities. For example, -/// the [`message::Receive`][super::receive::Receive] ability may be -/// proven by the [`Any`] ability in a delegation chain. +/// all other message abilities. +/// +/// For example, the [`message::Receive`][super::receive::Receive] ability may +/// be proven by the [`Any`] ability in a delegation chain. /// /// # Delegation Hierarchy /// diff --git a/src/ipld/cid.rs b/src/ipld/cid.rs index 9a812402..b8765f86 100644 --- a/src/ipld/cid.rs +++ b/src/ipld/cid.rs @@ -1,3 +1,5 @@ +//! Utilities for [`Cid`]s + use libipld_core::cid::Cid; #[cfg(target_arch = "wasm32")] @@ -6,7 +8,9 @@ use wasm_bindgen::prelude::*; #[cfg(target_arch = "wasm32")] use wasm_bindgen_derive::TryFromJsValue; -// FIXME better name +/// A newtype wrapper around a [`Cid`] +/// +/// This is largely to attach traits to [`Cid`]s, such as [`wasm_bindgen`] conversions. #[derive(Debug, PartialEq, Eq, Clone)] #[cfg_attr(target_arch = "wasm32", derive(TryFromJsValue))] #[cfg_attr(target_arch = "wasm32", wasm_bindgen)] @@ -28,10 +32,12 @@ extern "C" { #[cfg(target_arch = "wasm32")] #[wasm_bindgen] impl Newtype { + /// Parse a [`Newtype`] from a string pub fn from_string(cid_string: String) -> Result { Newtype::try_from(cid_string).map_err(|e| JsValue::from_str(&format!("{}", e))) } + /// Convert the [`Cid`] to a string pub fn to_string(&self) -> String { self.cid.to_string() } diff --git a/src/nonce.rs b/src/nonce.rs index 89d6453b..39d29bda 100644 --- a/src/nonce.rs +++ b/src/nonce.rs @@ -4,11 +4,12 @@ use libipld_core::{ipld::Ipld, multibase::Base::Base32HexLower}; use serde::{Deserialize, Serialize}; use std::fmt; +#[cfg(target_arch = "wasm32")] +use wasm_bindgen::prelude::*; + #[cfg(not(target_arch = "wasm32"))] use uuid::Uuid; -// FIXME -pub struct Unit; // FIXME pub struct Error(T); @@ -23,16 +24,20 @@ pub enum Nonce { Custom(Vec), } +#[cfg(target_arch = "wasm32")] +#[wasm_bindgen] impl Nonce { - // pub fn new() -> Self { - // Self::generate_96() + // FIXME this is probably _tooo_ different from the non-wasm one + // Perhaps worth one to ingest nonces via JSValues. + // #[cfg(target_arch = "wasm32")] + // pub fn generate_wasm_96(info: &mut [u8], crypto: web_sys::Crypto) -> Result { + // let buf = &mut []; + // web_sys::Crypto::get_random_values_with_u8_array(&crypto, buf) // `Result` // } +} - // #[cfg(target_arch = "wasm32")] - // pub fn gen_wasm_96() -> Self { - // web_sys::Crypto::get_random_values_with_u8_array() - // } - +#[cfg(not(target_arch = "wasm32"))] +impl Nonce { /// Default generator, outputting an [`xid`] nonce, /// which is a 96-bit (12-byte) nonce. #[cfg(not(target_arch = "wasm32"))] diff --git a/src/number.rs b/src/number.rs index 20f3042f..f29978d4 100644 --- a/src/number.rs +++ b/src/number.rs @@ -1,10 +1,19 @@ +//! Helpers for working with [`Ipld`] numerics + use libipld_core::{error::SerdeError, ipld::Ipld, serde as ipld_serde}; use serde_derive::{Deserialize, Serialize}; +/// The union of [`Ipld`] numeric types +/// +/// This is helpful when working with JavaScript, or with +/// values that may be given as either an integer or a float. #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(untagged)] pub enum Number { + /// Designate a floating point number Float(f64), + + /// Designate an integer Integer(i128), } diff --git a/src/proof.rs b/src/proof.rs index 75559c3b..122ca024 100644 --- a/src/proof.rs +++ b/src/proof.rs @@ -1,3 +1,5 @@ +//! Proof chains, checking, and utilities + pub mod checkable; pub mod parentful; pub mod parentless; diff --git a/src/proof/checkable.rs b/src/proof/checkable.rs index f8a7cd62..9b04cf1f 100644 --- a/src/proof/checkable.rs +++ b/src/proof/checkable.rs @@ -1,5 +1,13 @@ +//! Define the hierarchy of an ability (or mark as not having one) + use super::{internal::Checker, prove::Prove, same::CheckSame}; +/// Plug a type into the delegation checking pipeline pub trait Checkable: CheckSame { + /// The type of hierarchy this ability has + /// + /// The only options are [`Parentful`][super::parentful::Parentful] + /// and [`Parentless`][super::parentless::Parentless], + /// (which are the only instances of the unexported `Checker`) type Hierarchy: Checker + CheckSame + Prove; } diff --git a/src/proof/internal.rs b/src/proof/internal.rs index b7adf7fa..42df1327 100644 --- a/src/proof/internal.rs +++ b/src/proof/internal.rs @@ -1 +1,2 @@ -pub trait Checker {} +// NOTE: Must not get exported +pub(crate) trait Checker {} diff --git a/src/reader.rs b/src/reader.rs index d87f0e41..60032406 100644 --- a/src/reader.rs +++ b/src/reader.rs @@ -1,3 +1,5 @@ +//! Configure & attach an ambient environment to a value + use crate::{ ability::{arguments, command::ToCommand}, delegation::Delegatable, From e81f5a52ff7a6f74530a44e819408e9f9c3d2438 Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Tue, 6 Feb 2024 13:35:54 -0800 Subject: [PATCH 053/188] Finished another round of fighting with feature flags --- Cargo.toml | 8 +- flake.nix | 12 +++ src/ability/js/parentful.rs | 5 ++ src/nonce.rs | 169 ++++++++++++++++++++++++++---------- tests/conformance.rs | 7 +- 5 files changed, 146 insertions(+), 55 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index cec1b278..34115ea0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -44,12 +44,14 @@ ed25519 = { version = "2.2.2", optional = true, default-features = false } ed25519-dalek = { version = "2.0.0", features = ["rand_core"], optional = true } enum-as-inner = "0.6" erased-serde = "0.3.31" +getrandom = { version = "0.2", features = ["js", "rdrand"] } jose-b64 = { version = "0.1.2", features = ["serde", "json"] } k256 = { version = "0.13.1", features = ["ecdsa"], optional = true, default-features = false } lazy_static = "1.4.0" libipld-core = { version = "0.16", features = ["serde-codec"] } libipld-cbor = "0.16" -multibase = "0.9.1" +multibase = "0.9" +multihash = { version = "0.18", features = ["sha2"] } p256 = { version = "0.13.2", features = ["ecdsa"], optional = true, default-features = false } p384 = { version = "0.13.0", features = ["ecdsa"], optional = true, default-features = false } p521 = { version = "0.13.0", optional = true, default-features = false } @@ -66,19 +68,15 @@ thiserror = "1.0" tracing = "0.1.40" unsigned-varint = "0.7.2" url = { version = "2.5", features = ["serde"] } -uuid = "1.7" web-time = "0.2.3" [target.'cfg(not(target_arch = "wasm32"))'.dependencies] # `linkme` relies on linker features that aren't available in wasm32 linkme = "0.3.15" -uuid = { version = "1.7", features = ["v4"] } -xid = "1.0" # FIXME also have a wasi target [target.'cfg(target_arch = "wasm32")'.dependencies] console_error_panic_hook = { version = "0.1" } -getrandom = { version = "0.2", features = ["js"] } js-sys = { version = "0.3" } serde-wasm-bindgen = "0.6" wasm-bindgen = "0.2" diff --git a/flake.nix b/flake.nix index c0c36b5c..3415cbca 100644 --- a/flake.nix +++ b/flake.nix @@ -256,6 +256,18 @@ category = "watch"; command = "${cargo} watch --clear --exec test"; } + { + name = "watch:test:wasm"; + help = "Run all tests on save"; + category = "watch"; + command = "${cargo} watch --clear --exec 'test --target=wasm32-unknown-unknown'"; + } + { + name = "watch:test:docs:wasm"; + help = "Run all tests on save"; + category = "watch"; + command = "${cargo} watch --clear --exec 'test --target=wasm32-unknown-unknown'"; + } # Test { name = "test:all"; diff --git a/src/ability/js/parentful.rs b/src/ability/js/parentful.rs index cfb7b61a..f7523382 100644 --- a/src/ability/js/parentful.rs +++ b/src/ability/js/parentful.rs @@ -20,9 +20,14 @@ type WithParents = Reader; #[wasm_bindgen(getter_with_clone)] pub struct ParentfulConfig { pub command: String, + + #[wasm_bindgen(js_name = isNonceMeaningful)] pub is_nonce_meaningful: bool, + #[wasm_bindgen(js_name = validateShape)] pub validate_shape: Function, + + #[wasm_bindgen(js_name = checkSame)] pub check_same: Function, #[wasm_bindgen(skip)] diff --git a/src/nonce.rs b/src/nonce.rs index 39d29bda..b9c775e5 100644 --- a/src/nonce.rs +++ b/src/nonce.rs @@ -1,64 +1,97 @@ -// use crate::{Error, Unit}; +//! Nonce utilities + use enum_as_inner::EnumAsInner; -use libipld_core::{ipld::Ipld, multibase::Base::Base32HexLower}; +use getrandom::getrandom; +use libipld_core::{ + ipld::Ipld, + multibase::Base::Base32HexLower, + multihash::{Hasher, Sha2_256}, +}; use serde::{Deserialize, Serialize}; use std::fmt; #[cfg(target_arch = "wasm32")] use wasm_bindgen::prelude::*; -#[cfg(not(target_arch = "wasm32"))] -use uuid::Uuid; - -// FIXME -pub struct Error(T); - -/// Enumeration over allowed `nonce` types. +/// Known [`Nonce`] types #[derive(Clone, Debug, PartialEq, EnumAsInner, Serialize, Deserialize)] pub enum Nonce { - /// 96-bit, 12-byte nonce, e.g. [`xid`]. - Nonce96([u8; 12]), - /// 128-bit, 16-byte nonce. - Nonce128([u8; 16]), - /// No Nonce attributed. + /// 96-bit, 12-byte nonce + Nonce12([u8; 12]), + + /// 128-bit, 16-byte nonce + Nonce16([u8; 16]), + + /// Dynamic sized nonce Custom(Vec), } -#[cfg(target_arch = "wasm32")] -#[wasm_bindgen] -impl Nonce { - // FIXME this is probably _tooo_ different from the non-wasm one - // Perhaps worth one to ingest nonces via JSValues. - // #[cfg(target_arch = "wasm32")] - // pub fn generate_wasm_96(info: &mut [u8], crypto: web_sys::Crypto) -> Result { - // let buf = &mut []; - // web_sys::Crypto::get_random_values_with_u8_array(&crypto, buf) // `Result` - // } +impl From<[u8; 12]> for Nonce { + fn from(s: [u8; 12]) -> Self { + Nonce::Nonce12(s) + } +} + +impl From<[u8; 16]> for Nonce { + fn from(s: [u8; 16]) -> Self { + Nonce::Nonce16(s) + } +} + +impl From> for Nonce { + fn from(nonce: Vec) -> Self { + match nonce.len() { + 12 => Nonce::Nonce12( + nonce + .try_into() + .expect("12 bytes because we checked in the match"), + ), + 16 => Nonce::Nonce16( + nonce + .try_into() + .expect("16 bytes because we checked in the match"), + ), + _ => Nonce::Custom(nonce), + } + } } -#[cfg(not(target_arch = "wasm32"))] impl Nonce { - /// Default generator, outputting an [`xid`] nonce, - /// which is a 96-bit (12-byte) nonce. - #[cfg(not(target_arch = "wasm32"))] - pub fn generate_96() -> Self { - Nonce::Nonce96(*xid::new().as_bytes()) + // NOTE seed = domain-separator + pub fn generate_12(seed: &mut Vec) -> Nonce { + seed.append(&mut [0].repeat(12)); + + let buf = seed.as_mut_slice(); + getrandom(buf).expect("irrecoverable getrandom failure"); + + let mut hasher = Sha2_256::default(); + hasher.update(buf); + + let bytes = hasher.finalize().try_into().expect("SHA2_256 is 32 bytes"); + Nonce::Nonce12(bytes) } - /// Generate a default 128-bit(16-byte) nonce via [`Uuid::new_v4()`]. - #[cfg(not(target_arch = "wasm32"))] - pub fn generate_128() -> Self { - Nonce::Nonce128(*Uuid::new_v4().as_bytes()) + pub fn generate_16(seed: &mut Vec) -> Nonce { + seed.append(&mut [0].repeat(16)); + + let buf = seed.as_mut_slice(); + getrandom(buf).expect("irrecoverable getrandom failure"); + + let mut hasher = Sha2_256::default(); + hasher.update(buf); + + let bytes = hasher.finalize().try_into().expect("SHA2_256 is 32 bytes"); + Nonce::Nonce12(bytes) } } impl fmt::Display for Nonce { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { - Nonce::Nonce96(nonce) => { + Nonce::Nonce12(nonce) => { write!(f, "{}", Base32HexLower.encode(nonce.as_slice())) } - Nonce::Nonce128(nonce) => { + Nonce::Nonce16(nonce) => { write!(f, "{}", Base32HexLower.encode(nonce.as_slice())) } Nonce::Custom(nonce) => { @@ -71,8 +104,8 @@ impl fmt::Display for Nonce { impl From for Ipld { fn from(nonce: Nonce) -> Self { match nonce { - Nonce::Nonce96(nonce) => Ipld::Bytes(nonce.to_vec()), - Nonce::Nonce128(nonce) => Ipld::Bytes(nonce.to_vec()), + Nonce::Nonce12(nonce) => Ipld::Bytes(nonce.to_vec()), + Nonce::Nonce16(nonce) => Ipld::Bytes(nonce.to_vec()), Nonce::Custom(nonce) => Ipld::Bytes(nonce), } } @@ -84,11 +117,11 @@ impl TryFrom for Nonce { fn try_from(ipld: Ipld) -> Result { if let Ipld::Bytes(v) = ipld { match v.len() { - 12 => Ok(Nonce::Nonce96( + 12 => Ok(Nonce::Nonce12( v.try_into() .expect("12 bytes because we checked in the match"), )), - 16 => Ok(Nonce::Nonce128( + 16 => Ok(Nonce::Nonce16( v.try_into() .expect("16 bytes because we checked in the match"), )), @@ -108,16 +141,60 @@ impl TryFrom<&Ipld> for Nonce { } } +#[cfg(target_arch = "wasm32")] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +#[wasm_bindgen] +pub struct JsNonce(#[wasm_bindgen(skip)] pub Nonce); + +#[cfg(target_arch = "wasm32")] +impl From for Nonce { + fn from(newtype: JsNonce) -> Self { + newtype.0 + } +} + +#[cfg(target_arch = "wasm32")] +impl From for JsNonce { + fn from(nonce: Nonce) -> Self { + JsNonce(nonce) + } +} + +#[cfg(target_arch = "wasm32")] +#[wasm_bindgen] +impl JsNonce { + pub fn generate_12(mut seed: Vec) -> JsNonce { + Nonce::generate_12(&mut seed).into() + } + + pub fn generate_16(mut seed: Vec) -> JsNonce { + Nonce::generate_16(&mut seed).into() + } + + pub fn from_uint8_array(arr: Box<[u8]>) -> JsNonce { + Nonce::from(arr.to_vec()).into() + } + + pub fn to_uint8_array(&self) -> Box<[u8]> { + match &self.0 { + Nonce::Nonce12(nonce) => nonce.to_vec().into_boxed_slice(), + Nonce::Nonce16(nonce) => nonce.to_vec().into_boxed_slice(), + Nonce::Custom(nonce) => nonce.clone().into_boxed_slice(), + } + } +} + #[cfg(test)] mod test { use super::*; + // FIXME prop test with lots of inputs #[test] fn ipld_roundtrip_12() { - let gen = Nonce::generate(); + let gen = Nonce::generate_12(&mut vec![]); let ipld = Ipld::from(gen.clone()); - let inner = if let Nonce::Nonce96(nonce) = gen { + let inner = if let Nonce::Nonce12(nonce) = gen { Ipld::Bytes(nonce.to_vec()) } else { panic!("No conversion!") @@ -127,12 +204,13 @@ mod test { assert_eq!(gen, ipld.try_into().unwrap()); } + // FIXME prop test with lots of inputs #[test] fn ipld_roundtrip_16() { - let gen = Nonce::generate_128(); + let gen = Nonce::generate_16(&mut vec![]); let ipld = Ipld::from(gen.clone()); - let inner = if let Nonce::Nonce128(nonce) = gen { + let inner = if let Nonce::Nonce16(nonce) = gen { Ipld::Bytes(nonce.to_vec()) } else { panic!("No conversion!") @@ -142,9 +220,10 @@ mod test { assert_eq!(gen, ipld.try_into().unwrap()); } + // FIXME prop test with lots of inputs #[test] fn ser_de() { - let gen = Nonce::generate_128(); + let gen = Nonce::generate_16(&mut vec![]); let ser = serde_json::to_string(&gen).unwrap(); let de = serde_json::from_str(&ser).unwrap(); diff --git a/tests/conformance.rs b/tests/conformance.rs index 46aa3a65..44518df3 100644 --- a/tests/conformance.rs +++ b/tests/conformance.rs @@ -1,6 +1,6 @@ use libipld_core::{ipld::Ipld, raw::RawCodec}; use serde::{Deserialize, Serialize}; -use std::{collections::HashMap, fs::File, io::BufReader, str::FromStr}; +use std::{collections::HashMap, fs::File, io::BufReader}; use ucan::{ capability::DefaultCapabilityParser, @@ -318,10 +318,7 @@ impl TestTask for RefuteTest { if let Ok(ucan) = Ucan::::from_str(&self.inputs.token) { - if ucan - .validate(ucan::time::now(), &did_verifier_map) - .is_ok() - { + if ucan.validate(ucan::time::now(), &did_verifier_map).is_ok() { report.register_failure( &name, "expected token to fail validation, but it passed".to_string(), From e739577b2df16578ef1620bb3994f297b3539fa2 Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Tue, 6 Feb 2024 15:38:31 -0800 Subject: [PATCH 054/188] Time! --- src/ipld.rs | 10 +---- src/time.rs | 123 ++++++++++++++++++++++++++++++++++------------------ 2 files changed, 82 insertions(+), 51 deletions(-) diff --git a/src/ipld.rs b/src/ipld.rs index 04f40ad8..34a3e1e1 100644 --- a/src/ipld.rs +++ b/src/ipld.rs @@ -3,7 +3,6 @@ pub mod cid; use libipld_core::ipld::Ipld; -use std::fmt; #[cfg(target_arch = "wasm32")] use wasm_bindgen::prelude::*; @@ -23,7 +22,7 @@ use js_sys::{Array, Map, Object, Uint8Array}; /// let wrapped = ipld::Newtype(ipld.clone()); /// // wrapped.some_trait_method(); /// ``` -// / +/// /// Unwrap a [`Newtype`] to use any interfaces that expect plain [`Ipld`]. /// /// ``` @@ -38,13 +37,6 @@ use js_sys::{Array, Map, Object, Uint8Array}; #[derive(Debug, Clone, PartialEq)] pub struct Newtype(pub Ipld); -// FIXME -// impl fmt::Display for Newtype { -// fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { -// write!(f, "{}", String::from(self.0)) -// } -// } - impl From for Newtype { fn from(ipld: Ipld) -> Self { Self(ipld) diff --git a/src/time.rs b/src/time.rs index 987c6d23..0e07098b 100644 --- a/src/time.rs +++ b/src/time.rs @@ -1,9 +1,14 @@ //! Time utilities -use libipld_core::{ipld::Ipld, serde as ipld_serde}; +use libipld_core::{error::SerdeError, ipld::Ipld, serde as ipld_serde}; use serde::{Deserialize, Deserializer, Serialize, Serializer}; +use std::fmt; +use thiserror::Error; use web_time::{Duration, SystemTime, UNIX_EPOCH}; +#[cfg(target_arch = "wasm32")] +use wasm_bindgen::prelude::*; + /// Get the current time in seconds since UNIX_EPOCH pub fn now() -> u64 { SystemTime::now() @@ -12,19 +17,17 @@ pub fn now() -> u64 { .as_secs() } +/// All timestamps that this library can handle +/// +/// Strictly speaking, UCAN only supports [`JsTime`] for JavaScript interoperability. #[derive(Debug, Clone, PartialEq)] pub enum Timestamp { - // FIXME probably overkill, but overflows are bad. Need to check on ingestion, too - // Per the spec, timestamps MUST respect [IEEE-754](https://en.wikipedia.org/wiki/IEEE_754) - // (64-bit double precision = 53-bit truncated integer) for JavaScript interoperability. - // - // This range can represent millions of years into the future, - // and is thus sufficient for nearly all use cases. - Sending(JsTime), + /// An entry for [`JsTime`], which is compatible with JavaScript's 2⁵³ numberic range + JsSafe(JsTime), /// Following [Postel's Law](https://en.wikipedia.org/wiki/Robustness_principle), - /// received timestamps may be parsed as regular [SystemTime] - Receiving(SystemTime), + /// received timestamps may be parsed as regular [`SystemTime`] + Postel(SystemTime), } impl Serialize for Timestamp { @@ -33,8 +36,8 @@ impl Serialize for Timestamp { S: Serializer, { match self { - Timestamp::Sending(js_time) => js_time.serialize(serializer), - Timestamp::Receiving(_sys_time) => todo!(), // FIXME See comment on deserilaizer sys_time.serialize(serializer), + Timestamp::JsSafe(js_time) => js_time.serialize(serializer), + Timestamp::Postel(_sys_time) => todo!(), // FIXME See comment on deserilaizer sys_time.serialize(serializer), } } } @@ -45,7 +48,7 @@ impl<'de> Deserialize<'de> for Timestamp { D: Deserializer<'de>, { if let Ok(js_time) = JsTime::deserialize(deserializer) { - return Ok(Timestamp::Sending(js_time)); + return Ok(Timestamp::JsSafe(js_time)); } todo!() @@ -56,8 +59,8 @@ impl<'de> Deserialize<'de> for Timestamp { impl From for SystemTime { fn from(timestamp: Timestamp) -> Self { match timestamp { - Timestamp::Sending(js_time) => js_time.time, - Timestamp::Receiving(sys_time) => sys_time, + Timestamp::JsSafe(js_time) => js_time.time, + Timestamp::Postel(sys_time) => sys_time, } } } @@ -69,48 +72,47 @@ impl From for Ipld { } impl TryFrom for Timestamp { - type Error = (); // FIXME + type Error = SerdeError; fn try_from(ipld: Ipld) -> Result { - ipld_serde::from_ipld(ipld).map_err(|_| ()) + ipld_serde::from_ipld(ipld) } } +/// Per the UCAN spec, timestamps MUST respect [IEEE-754] +/// (64-bit double precision = 53-bit truncated integer) for JavaScript interoperability. +/// +/// This range can represent millions of years into the future, +/// and is thus sufficient for "nearly" all auth use cases. +/// +/// [IEEE-754]: https://en.wikipedia.org/wiki/IEEE_754 #[derive(Debug, Clone, PartialEq, Eq)] +#[cfg_attr(target_arch = "wasm32", wasm_bindgen)] pub struct JsTime { time: SystemTime, } -impl Serialize for JsTime { - fn serialize(&self, serializer: S) -> Result - where - S: Serializer, - { - self.time - .duration_since(UNIX_EPOCH) - .expect("FIXME") - .as_secs() - .serialize(serializer) +#[cfg(target_arch = "wasm32")] +impl JsTime { + /// Lift a [`js_sys::Date`] into a Rust [`JsTime`] + pub fn from_date(date_time: js_sys::Date) -> Result { + let millis = date_time.get_time() as u64; + let secs: u64 = (millis / 1000) as u64; + let duration = Duration::new(secs, 0); // Just round off the nanos + JsTime::new(UNIX_EPOCH + duration).map_err(Into::into) } -} -impl<'de> Deserialize<'de> for JsTime { - fn deserialize(deserializer: D) -> Result - where - D: Deserializer<'de>, - { - let seconds = u64::deserialize(deserializer)?; - JsTime::new(UNIX_EPOCH + Duration::from_secs(seconds)) - .map_err(|_| serde::de::Error::custom("time out of JsTime range")) + /// Lower the [`JsTime`] to a [`js_sys::Date`] + pub fn to_date(&self) -> js_sys::Date { + js_sys::Date::new(&JsValue::from( + self.time + .duration_since(UNIX_EPOCH) + .expect("time should be in range since it's getting a JS Date") + .as_millis(), + )) } } -// FIXME just lifting this from Elixir for now -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct OutOfRangeError { - pub tried: SystemTime, -} - impl JsTime { /// Create a [`JsTime`] from a [`SystemTime`] /// @@ -136,3 +138,40 @@ impl From for Ipld { .into() } } + +impl Serialize for JsTime { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + self.time + .duration_since(UNIX_EPOCH) + .expect("FIXME") + .as_secs() + .serialize(serializer) + } +} + +impl<'de> Deserialize<'de> for JsTime { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + let seconds = u64::deserialize(deserializer)?; + JsTime::new(UNIX_EPOCH + Duration::from_secs(seconds)) + .map_err(|_| serde::de::Error::custom("time out of JsTime range")) + } +} + +/// An error expressing when a time is larger than 2^53 seconds past the Unix epoch +#[derive(Debug, Clone, PartialEq, Eq, Error)] +pub struct OutOfRangeError { + /// The [`SystemTime`] that is outside of the [`JsTime`] range (2^53) + pub tried: SystemTime, +} + +impl fmt::Display for OutOfRangeError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "time out of JsTime (2^53) range: {:?}", self.tried) + } +} From 0766cfea3d1862ddbe2bcc7118df1ae06ba7579f Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Tue, 6 Feb 2024 16:23:40 -0800 Subject: [PATCH 055/188] Save --- src/invocation/payload.rs | 3 ++ src/task.rs | 63 +++++++++++++++++++++++++-------------- 2 files changed, 43 insertions(+), 23 deletions(-) diff --git a/src/invocation/payload.rs b/src/invocation/payload.rs index 58c4e892..24e314f5 100644 --- a/src/invocation/payload.rs +++ b/src/invocation/payload.rs @@ -17,6 +17,7 @@ use std::{collections::BTreeMap, fmt::Debug}; // FIXME ...or at least have two versions via abstraction #[derive(Debug, Clone, PartialEq)] pub struct Payload { + // FIXME we're going to toss that E pub issuer: Did, pub subject: Did, pub audience: Option, @@ -32,6 +33,8 @@ pub struct Payload { pub expiration: Timestamp, } +// FIXME To TaskId + // NOTE This is the version that accepts promises pub type Unresolved = Payload; // type Dynamic = Payload; <- ? diff --git a/src/task.rs b/src/task.rs index 00411b7b..c3363f0a 100644 --- a/src/task.rs +++ b/src/task.rs @@ -1,4 +1,6 @@ -use crate::{did::Did, nonce::Nonce}; +//! Task indices for [`Receipt`][crate::receipt::Receipt] reverse lookup + +use crate::{ability::arguments, did::Did, nonce::Nonce}; use libipld_cbor::DagCborCodec; use libipld_core::{ cid::{Cid, CidGeneric}, @@ -9,27 +11,30 @@ use libipld_core::{ serde as ipld_serde, }; use serde_derive::{Deserialize, Serialize}; -use std::{collections::BTreeMap, fmt::Debug}; +use std::fmt::Debug; const SHA2_256: u64 = 0x12; +/// The fields required to uniquely identify a [`Task`], potentially across multiple executors. +/// +/// This struct should not be used directly, but rather through a [`From`] instance +/// on the type. In particular, the `nonce` field should be constant for all of the same type. #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] pub struct Task { - #[serde(default, skip_serializing_if = "Option::is_none")] - pub sub: Option, // Is this optional? May as well make it so for now! + /// The `subject`: root issuer, and arbiter of the semantics/namespace + pub sub: Did, + + /// A unique identifier for the particular task run + /// + /// This is an [`Option`] because not all task types require a nonce. #[serde(default, skip_serializing_if = "Option::is_none")] pub nonce: Option, + /// The command identifier pub cmd: String, - pub args: BTreeMap, // FIXME change to Named? -} -impl From for Id { - fn from(task: Task) -> Id { - Id { - cid: Cid::from(task), - } - } + /// The arguments to the command + pub args: arguments::Named, } impl TryFrom for Task { @@ -46,7 +51,25 @@ impl From for Ipld { } } +impl From for Cid { + fn from(task: Task) -> Cid { + let mut buffer = vec![]; + let ipld: Ipld = task.into(); + + ipld.encode(DagCborCodec, &mut buffer) + .expect("DagCborCodec to encode any arbitrary `Ipld`"); + + CidGeneric::new_v1( + DagCborCodec.into(), + MultihashGeneric::wrap(SHA2_256, buffer.as_slice()) + .expect("DagCborCodec + Sha2_256 should always successfully encode Ipld to a Cid"), + ) + } +} + +/// The unique identifier for a [`Task`] #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +#[serde(transparent)] pub struct Id { pub cid: Cid, } @@ -65,16 +88,10 @@ impl From for Ipld { } } -impl From for Cid { - fn from(task: Task) -> Cid { - let mut buffer = vec![]; - let ipld: Ipld = task.into(); - ipld.encode(DagCborCodec, &mut buffer) - .expect("DagCborCodec to encode any arbitrary `Ipld`"); - CidGeneric::new_v1( - DagCborCodec.into(), - MultihashGeneric::wrap(SHA2_256, buffer.as_slice()) - .expect("DagCborCodec + Sha2_256 should always successfully encode Ipld to a Cid"), - ) +impl From for Id { + fn from(task: Task) -> Id { + Id { + cid: Cid::from(task), + } } } From a111ffb97bfec76cb6a20145903d0dd8fa9a24b2 Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Tue, 6 Feb 2024 16:35:20 -0800 Subject: [PATCH 056/188] No more meta handling --- src/agent.rs | 17 +- src/delegation.rs | 6 +- src/delegation/payload.rs | 55 +++---- src/delegation/store.rs | 12 +- src/invocation.rs | 2 +- src/invocation/payload.rs | 39 ++--- src/lib.rs | 2 - src/metadata.rs | 330 -------------------------------------- src/metadata/keyed.rs | 50 ------ src/new_wasm.rs | 7 - src/receipt/payload.rs | 40 ++--- src/receipt/receipt.rs | 2 +- src/receipt/store.rs | 8 +- 13 files changed, 78 insertions(+), 492 deletions(-) delete mode 100644 src/metadata.rs delete mode 100644 src/metadata/keyed.rs delete mode 100644 src/new_wasm.rs diff --git a/src/agent.rs b/src/agent.rs index 809414a3..a0023741 100644 --- a/src/agent.rs +++ b/src/agent.rs @@ -3,7 +3,6 @@ use crate::{ delegation::{traits::Condition, Delegatable, Delegation}, did::Did, invocation::Invocation, - metadata as meta, proof::parents::CheckParents, }; @@ -19,10 +18,10 @@ impl Agent { // signature::Envelope::new(payload, signature) // } - pub fn invoke( + pub fn invoke( &self, - delegation: Delegation, - proof_chain: Vec>, // FIXME T must also accept Self and * + delegation: Delegation, + proof_chain: Vec>, // FIXME T must also accept Self and * ) -> () where T::Parents: Delegatable, @@ -34,9 +33,9 @@ impl Agent { todo!() } - pub fn revoke( + pub fn revoke( &self, - delegation: Delegation, + delegation: Delegation, ) -> () // where // T::Parents: Delegatable, @@ -44,14 +43,14 @@ impl Agent { todo!() } - pub fn receive_delegation( + pub fn receive_delegation( &self, - delegation: Delegation, + delegation: Delegation, ) -> () { todo!() } - pub fn receive_invocation(&self, invocation: Invocation) -> () { + pub fn receive_invocation(&self, invocation: Invocation) -> () { todo!() } diff --git a/src/delegation.rs b/src/delegation.rs index 3a8b3e3b..f457be00 100644 --- a/src/delegation.rs +++ b/src/delegation.rs @@ -11,7 +11,7 @@ pub use payload::Payload; use condition::traits::Condition; use store::IndexedStore; -use crate::{metadata as meta, signature}; +use crate::signature; /// A [`Delegation`] is a signed delegation [`Payload`] /// @@ -19,10 +19,10 @@ use crate::{metadata as meta, signature}; /// /// # Examples /// FIXME -pub type Delegation = signature::Envelope>; +pub type Delegation = signature::Envelope>; // FIXME -impl Delegation { +impl Delegation { // FIXME include cache //pub fn check>(&self, store: &S) -> Result<(), ()> { // if let Ok(is_valid) = store.previously_checked(self) { diff --git a/src/delegation/payload.rs b/src/delegation/payload.rs index 8a3616e9..c23371c7 100644 --- a/src/delegation/payload.rs +++ b/src/delegation/payload.rs @@ -5,9 +5,6 @@ use crate::{ did::Did, invocation, invocation::Resolvable, - metadata as meta, - metadata::Metadata, - // metadata::{Mergable, Metadata}, nonce::Nonce, proof::{ checkable::Checkable, @@ -22,7 +19,7 @@ use std::{collections::BTreeMap, fmt::Debug}; use web_time::SystemTime; #[derive(Debug, Clone, PartialEq)] -pub struct Payload { +pub struct Payload { pub issuer: Did, pub subject: Did, pub audience: Did, @@ -30,21 +27,21 @@ pub struct Payload { pub ability_builder: T::Builder, pub conditions: Vec, - pub metadata: Metadata, + pub metadata: BTreeMap, pub nonce: Nonce, pub expiration: Timestamp, pub not_before: Option, } -impl Capsule for Payload { +impl Capsule for Payload { const TAG: &'static str = "ucan/d/1.0.0-rc.1"; } -impl Serialize for Payload +impl Serialize for Payload where - InternalSerializer: From>, - Payload: Clone, + InternalSerializer: From>, + Payload: Clone, { fn serialize(&self, serializer: S) -> Result where @@ -55,11 +52,10 @@ where } } -impl<'de, T: Delegatable, C: Condition + DeserializeOwned, E: meta::MultiKeyed> Deserialize<'de> - for Payload +impl<'de, T: Delegatable, C: Condition + DeserializeOwned> Deserialize<'de> for Payload where - Payload: TryFrom, - as TryFrom>::Error: Debug, + Payload: TryFrom, + as TryFrom>::Error: Debug, { fn deserialize(d: D) -> Result where @@ -74,10 +70,9 @@ where } } -impl TryFrom - for Payload +impl TryFrom for Payload where - Payload: TryFrom, + Payload: TryFrom, { type Error = (); // FIXME @@ -87,25 +82,23 @@ where } } -impl From> for Ipld { - fn from(payload: Payload) -> Self { +impl From> for Ipld { + fn from(payload: Payload) -> Self { payload.into() } } // FIXME this likely should move to invocation -impl<'a, T: Delegatable + Resolvable + Checkable + Clone, C: Condition, E: meta::MultiKeyed> - Payload -{ +impl<'a, T: Delegatable + Resolvable + Checkable + Clone, C: Condition> Payload { pub fn check( - invoked: &'a invocation::Payload, // FIXME promisory version - proofs: Vec>, + invoked: &'a invocation::Payload, // FIXME promisory version + proofs: Vec>, now: SystemTime, ) -> Result<(), ()> where - invocation::Payload: Clone, + invocation::Payload: Clone, U::Builder: Clone + Into, - T::Hierarchy: From>, + T::Hierarchy: From>, { let start: Acc<'a, T> = Acc { issuer: &invoked.issuer, @@ -155,9 +148,9 @@ struct Acc<'a, T: Checkable> { } // FIXME this should move to Delegatable -fn step<'a, T: Checkable, U: Delegatable, C: Condition, E: meta::MultiKeyed>( +fn step<'a, T: Checkable, U: Delegatable, C: Condition>( prev: &Acc<'a, T>, - proof: &Payload, + proof: &Payload, invoked_ipld: &Ipld, now: SystemTime, ) -> Outcome<(), (), ()> @@ -242,13 +235,11 @@ struct InternalSerializer { expiration: Timestamp, } -impl, E: meta::MultiKeyed> - From> for InternalSerializer +impl> From> for InternalSerializer where BTreeMap: From, - Ipld: From, { - fn from(payload: Payload) -> Self { + fn from(payload: Payload) -> Self { InternalSerializer { issuer: payload.issuer, subject: payload.subject, @@ -258,7 +249,7 @@ where arguments: payload.ability_builder.into(), conditions: payload.conditions.into_iter().map(|c| c.into()).collect(), - metadata: payload.metadata.into(), + metadata: payload.metadata, nonce: payload.nonce, not_before: payload.not_before, diff --git a/src/delegation/store.rs b/src/delegation/store.rs index a75db20f..87e06108 100644 --- a/src/delegation/store.rs +++ b/src/delegation/store.rs @@ -1,15 +1,15 @@ use super::{condition::traits::Condition, delegatable::Delegatable, Delegation}; -use crate::{did::Did, metadata as meta}; +use crate::did::Did; use libipld_core::cid::Cid; use serde::{Deserialize, Serialize}; use std::collections::{BTreeMap, HashMap}; use web_time::SystemTime; // NOTE can already look up by CID in other traits -pub trait IndexedStore { +pub trait IndexedStore { type Error; - fn get_by(query: Query) -> Result>, Self::Error>; + fn get_by(query: Query) -> Result>, Self::Error>; fn previously_checked(cid: Cid) -> Result; @@ -20,7 +20,7 @@ pub trait IndexedStore { subject: Did, command: String, audience: Did, - ) -> Result)>>, Self::Error>; + ) -> Result)>>, Self::Error>; // if let Ok(possible) = self.get_by(Query { // audience: Some(audience), // command: Some(command), @@ -53,13 +53,13 @@ pub trait IndexedStore { // } // } - fn get_one(&self, query: Query) -> Result<(Cid, Delegation), Self::Error> { + fn get_one(&self, query: Query) -> Result<(Cid, Delegation), Self::Error> { todo!() //let mut results = Self::get_by(query)?; //results.pop().ok_or_else(|_| todo!()) } - fn expired(&self) -> Result>, Self::Error> { + fn expired(&self) -> Result>, Self::Error> { todo!() // self.get_by(Query { // expires_before: Some(SystemTime::now()), diff --git a/src/invocation.rs b/src/invocation.rs index 1e2a8a40..993fd5d3 100644 --- a/src/invocation.rs +++ b/src/invocation.rs @@ -9,4 +9,4 @@ pub use resolvable::Resolvable; use crate::signature; -pub type Invocation = signature::Envelope>; +pub type Invocation = signature::Envelope>; diff --git a/src/invocation/payload.rs b/src/invocation/payload.rs index 24e314f5..caf2fffc 100644 --- a/src/invocation/payload.rs +++ b/src/invocation/payload.rs @@ -3,9 +3,6 @@ use crate::{ ability::{arguments, command::Command}, capsule::Capsule, did::Did, - metadata as meta, - metadata::Metadata, - // metadata::{Mergable, Metadata}, nonce::Nonce, time::Timestamp, }; @@ -16,7 +13,7 @@ use std::{collections::BTreeMap, fmt::Debug}; // FIXME this version should not be resolvable... // FIXME ...or at least have two versions via abstraction #[derive(Debug, Clone, PartialEq)] -pub struct Payload { +pub struct Payload { // FIXME we're going to toss that E pub issuer: Did, pub subject: Did, @@ -26,7 +23,7 @@ pub struct Payload { pub proofs: Vec, pub cause: Option, - pub metadata: Metadata, + pub metadata: BTreeMap, pub nonce: Nonce, pub not_before: Option, @@ -36,7 +33,7 @@ pub struct Payload { // FIXME To TaskId // NOTE This is the version that accepts promises -pub type Unresolved = Payload; +pub type Unresolved = Payload; // type Dynamic = Payload; <- ? // FIXME parser for both versions @@ -52,14 +49,14 @@ pub type Unresolved = Payload; // Unresolved(Unresolved), // } -impl Capsule for Payload { +impl Capsule for Payload { const TAG: &'static str = "ucan/i/1.0.0-rc.1"; } -impl Serialize for Payload +impl Serialize for Payload where - Payload: Clone, - InternalSerializer: From>, + Payload: Clone, + InternalSerializer: From>, { fn serialize(&self, serializer: S) -> Result where @@ -70,10 +67,10 @@ where } } -impl<'de, T, E: meta::MultiKeyed> serde::Deserialize<'de> for Payload +impl<'de, T> serde::Deserialize<'de> for Payload where - Payload: TryFrom, - as TryFrom>::Error: Debug, + Payload: TryFrom, + as TryFrom>::Error: Debug, { fn deserialize(d: D) -> Result where @@ -88,9 +85,9 @@ where } } -impl TryFrom for Payload +impl TryFrom for Payload where - Payload: TryFrom, + Payload: TryFrom, { type Error = (); // FIXME @@ -100,8 +97,8 @@ where } } -impl From> for Ipld { - fn from(payload: Payload) -> Self { +impl From> for Ipld { + fn from(payload: Payload) -> Self { payload.into() } } @@ -199,10 +196,8 @@ impl TryFrom for InternalSerializer { // } // } -impl, E: meta::MultiKeyed> From> - for InternalSerializer -{ - fn from(payload: Payload) -> Self { +impl> From> for InternalSerializer { + fn from(payload: Payload) -> Self { InternalSerializer { issuer: payload.issuer, subject: payload.subject, @@ -213,7 +208,7 @@ impl, E: meta::MultiKeyed> From = BTreeMap::from_iter([ -/// ("foo".into(), Ipld::String("hello world".to_string())), -/// ("bar".into(), Ipld::Integer(42)), -/// ("baz".into(), Ipld::List(vec![Ipld::Float(3.14)])) -/// ]); -/// -/// let meta: Metadata = Metadata::try_from(kv.clone()).unwrap(); -/// -/// assert_eq!(meta.known().len(), 0); -/// assert_eq!(meta.unknown(), &kv); -/// ``` -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] -pub enum Empty {} - -/// A type alias for [`Metadata`] with no known fields. -pub type Unstructured = Metadata; - -// NOTE no Serde -/// Parsed metadata fields. -/// -/// If you don't have any known fields, you can set `T ` to [`Empty`] (or [`Unstructured`]) -#[derive(Debug, Clone, PartialEq)] -pub struct Metadata { - /// Structured metadata, selected by matching `T` - known: BTreeMap, - - /// Unstructured metadata - unknown: BTreeMap, -} - -impl Metadata { - /// Constructor for [`Metadata`] - /// - /// This checks that no duplicate keys are present - /// - /// # Examples - /// - /// ```rust - /// # use ucan::metadata::{Metadata, Empty}; - /// # use std::collections::BTreeMap; - /// # use libipld_core::ipld::Ipld; - /// # - /// #[derive(Debug, Clone, PartialEq)] - /// pub enum MyFacts { - /// Timeout(u32), - /// Retry{max: u32, delay: u32}, - /// NotGoingToUseThisOne(String), - /// } - /// - /// let known: BTreeMap = BTreeMap::from_iter([ - /// ("timeout".into(), MyFacts::Timeout(1000)), - /// ("retry".into(), MyFacts::Retry{max: 5, delay: 100}) - /// ]); - /// - /// let unknown: BTreeMap = BTreeMap::from_iter([ - /// ("foo".into(), Ipld::String("hello world".to_string())), - /// ("bar".into(), Ipld::Integer(42)), - /// ("baz".into(), Ipld::List(vec![Ipld::Float(3.14)])) - /// ]); - /// - /// let meta = Metadata::new(known.clone(), unknown.clone()).unwrap(); - /// - /// assert_eq!(meta.known(), &known.clone()); - /// assert_eq!(meta.unknown(), &unknown.clone()); - /// - /// let collision: BTreeMap = BTreeMap::from_iter([ - /// ("timeout".into(), Ipld::String("not a timeout".to_string())), - /// ]); - /// - /// let meta = Metadata::new(known, collision); - /// - /// assert!(meta.is_err()); - /// ``` - pub fn new( - known: BTreeMap, - unknown: BTreeMap, - ) -> Result { - for k in known.keys() { - if unknown.contains_key(k) { - return Err(k.into()); - } - } - - Ok(Self { known, unknown }) - } - - /// Getter for the `known` field - pub fn known<'a>(&'a self) -> &'a BTreeMap { - &self.known - } - - /// Getter for the `unknown` field - pub fn unknown<'a>(&'a self) -> &'a BTreeMap { - &self.unknown - } - - /// Insert a value into the `known` field - /// - /// This will return `Some(Entry::Unknown(ipld))` if you insert a key that already - /// exists in the `unknown` field. - /// - /// It will return `Some(t: T)` if you insert a key that was already present - /// in the `known` field. - pub fn insert_known<'a>(&'a mut self, key: String, value: T) -> Option> { - if let Some(ipld) = self.unknown.get(&key) { - self.known.insert(key, value); - return Some(Entry::Unknown(ipld.clone())); - } - - self.known.insert(key, value).map(Entry::Known) - } - - /// Insert a value into the `unknown` field - /// - /// This will return `Some(Entry::Unknown(ipld))` if you insert a key that already - /// exists in the `unknown` field. - /// - /// It will return `Some(t: T)` if you insert a key that was already present - /// in the `known` field. - pub fn insert_unknown<'a>(&'a mut self, key: String, value: Ipld) -> Option> - where - T: Clone, - { - if let Some(t) = self.known.get(&key) { - self.unknown.insert(key, value); - return Some(Entry::Known(t.clone())); - } - - self.unknown.insert(key, value).map(Entry::Unknown) - } - - /// Remove a field from either field - /// - /// This will return `Some(Entry::Unknown(ipld))` if there was a key in the `unknown` field. - /// - /// It will return `Some(t: T)` if there was a key in the `known` field. - pub fn remove_key<'a>(&'a mut self, key: &str) -> Option> { - if let Some(ipld) = self.unknown.remove(key) { - return Some(Entry::Unknown(ipld)); - } - - self.known.remove(key).map(Entry::Known) - } -} - -/// Tag values as belonging to the `known` or `unknown` field. -#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] -pub enum Entry { - /// The tag for a value in the `known` field - Known(T), - - /// The tag for a value in the `unknown` field - Unknown(Ipld), -} - -impl Default for Metadata { - fn default() -> Self { - Metadata { - known: BTreeMap::new(), - unknown: BTreeMap::new(), - } - } -} - -impl From> for BTreeMap { - fn from(meta: Metadata) -> Self { - meta.unknown - } -} - -impl> From> for BTreeMap { - // NOTE duplicate keys "shouldn't" be possible (because this roughly follows GDP) - // ...so we can just merge - fn from(meta: Metadata) -> Self { - let mut btree = meta.unknown; - for (k, v) in meta.known { - btree.insert(k, v.into()); - } - btree - } -} - -impl From> for Metadata { - // FIXME better error - fn from(merged: BTreeMap) -> Self { - let mut known = BTreeMap::new(); - let mut unknown = BTreeMap::new(); - - for (k, v) in merged { - if T::KEYS.contains(&k.as_str()) { - if let Ok(entry) = v.clone().try_into() { - known.insert(k, entry); - } else { - unknown.insert(k, v); - } - } else { - unknown.insert(k, v); - } - } - - Metadata { known, unknown } - } -} - -impl From> for Metadata { - fn from(btree: BTreeMap) -> Self { - Metadata { - known: BTreeMap::new(), - unknown: btree, - } - } -} - -impl TryFrom> for Ipld { - type Error = Infallible; - - fn try_from(meta: Metadata) -> Result { - Ok(Ipld::Map(meta.unknown)) - } -} - -impl> TryFrom> for Ipld { - type Error = String; // FIXME - - fn try_from(meta: Metadata) -> Result { - let mut btree = meta.unknown.clone(); - - for (k, v) in meta.known { - if let Some(_) = meta.unknown.get(&k) { - return Err(k); - } - - btree.insert(k, v.into()); - } - - Ok(Ipld::Map(btree)) - } -} - -impl Serialize for Metadata { - fn serialize(&self, serializer: S) -> Result - where - S: Serializer, - { - let s = Ipld::Map((*self).clone().into()); - serde::Serialize::serialize(&s, serializer) - } -} - -impl<'de, T: MultiKeyed + Clone> Deserialize<'de> for Metadata { - fn deserialize(d: D) -> Result - where - D: serde::Deserializer<'de>, - { - Ipld::deserialize(d).and_then(|ipld| ipld.try_into().map_err(|_| todo!())) - } -} - -impl TryFrom for Metadata { - type Error = (); // FIXME - - fn try_from(ipld: Ipld) -> Result { - match ipld { - Ipld::Map(btree) => { - let mut known = BTreeMap::new(); - let mut unknown = BTreeMap::new(); - - for (k, v) in btree { - if T::KEYS.contains(&k.as_str()) { - if let Ok(fact) = T::try_from(v.clone()) { - known.insert(k, fact); - } else { - unknown.insert(k, v); - } - } else { - unknown.insert(k, v); - } - } - - Ok(Self { known, unknown }) - } - _ => Err(()), - } - } -} - -// // FIXME Just as an example, plz delete -// #[derive(Debug, Clone, Serialize, Deserialize)] -// #[serde(deny_unknown_fields)] -// pub struct IpvmConfig { -// pub max_retries: u32, -// pub workflow_fuel: u32, -// } -// -// impl Keyed for IpvmConfig { -// const KEY: &'static str = "ipvm/config"; -// } -// -// impl From for Ipld { -// fn from(config: IpvmConfig) -> Self { -// config.into() -// } -// } -// -// impl TryFrom for IpvmConfig { -// type Error = SerdeError; -// -// fn try_from(ipld: Ipld) -> Result { -// ipld_serde::from_ipld(ipld) -// } -// } diff --git a/src/metadata/keyed.rs b/src/metadata/keyed.rs deleted file mode 100644 index 174023f1..00000000 --- a/src/metadata/keyed.rs +++ /dev/null @@ -1,50 +0,0 @@ -//! Keys for metadata fields (for parsing and guarding entries) - -use libipld_core::ipld::Ipld; - -/// A parser trait for types that can be used as metadata fields. -/// -/// # Examples -/// -/// ```rust -/// # use ucan::metadata::Keyed; -/// # use std::collections::BTreeMap; -/// # use libipld::{ipld, ipld::Ipld}; -/// # -/// pub struct MyKeyed { -/// pub foo: String, -/// pub bar: u32, -/// } -/// -/// impl Keyed for MyKeyed { -/// const KEY: &'static str = "my/entry"; -/// } -/// assert_eq!(MyKeyed::KEY, "my/entry"); -/// -/// let kv: BTreeMap = BTreeMap::from_iter([ -/// (MyKeyed::KEY.into(), ipld!({"foo": "hello world", "bar": 42})) -/// ]); -/// -/// assert_eq!(kv.get(MyKeyed::KEY), Some(&ipld!({"foo": "hello world", "bar": 42}))); -/// ``` -pub trait Keyed { - /// The (string) key for this entry - /// - /// These should be unique per `Keyed` type to avoid parser collisions. - /// Even if a duplicate key is used, the shape of the contained data can - /// also be used to disambiguate. - const KEY: &'static str; -} - -// FIXME keyed? -/// A parser trait for one-or-more unioned types that can be used as metadata fields. -/// -/// [`MultiKeyed`] may be composed of a single entry, or the union of sevveral e.g. via enum. -pub trait MultiKeyed: TryFrom + Into { - /// The (string) keys for an enum merging multiple [`Keyed`]s - const KEYS: &'static [&'static str]; -} - -impl + Into> MultiKeyed for T { - const KEYS: &'static [&'static str] = &[T::KEY]; -} diff --git a/src/new_wasm.rs b/src/new_wasm.rs deleted file mode 100644 index 760bf027..00000000 --- a/src/new_wasm.rs +++ /dev/null @@ -1,7 +0,0 @@ -// use crate::{ability::dynamic::Dynamic, task::DefaultTrue}; -// use js_sys; -// use serde::{Deserialize, Serialize}; -// use wasm_bindgen::prelude::*; - -// #[wasm_bindgen] -// type JsDynamic = Dynamic; diff --git a/src/receipt/payload.rs b/src/receipt/payload.rs index 83662cc2..81311154 100644 --- a/src/receipt/payload.rs +++ b/src/receipt/payload.rs @@ -1,8 +1,5 @@ use super::responds::Responds; -use crate::{ - ability::arguments, capsule::Capsule, did::Did, metadata as meta, metadata::Metadata, - nonce::Nonce, time::Timestamp, -}; +use crate::{ability::arguments, capsule::Capsule, did::Did, nonce::Nonce, time::Timestamp}; use libipld_core::{cid::Cid, error::SerdeError, ipld::Ipld, serde as ipld_serde}; use serde::{de::DeserializeOwned, Deserialize, Serialize, Serializer}; use std::{collections::BTreeMap, fmt::Debug}; @@ -10,7 +7,7 @@ use std::{collections::BTreeMap, fmt::Debug}; // FIXME serialize/deseialize split out for when the T has implementations #[derive(Debug, Clone, PartialEq)] -pub struct Payload { +pub struct Payload { pub issuer: Did, pub ran: Cid, @@ -18,21 +15,20 @@ pub struct Payload { pub next: Vec, // FIXME rename here or in spec? pub proofs: Vec, - pub metadata: Metadata, + pub metadata: BTreeMap, pub nonce: Nonce, pub issued_at: Option, } -impl Capsule for Payload { +impl Capsule for Payload { const TAG: &'static str = "ucan/r/1.0.0-rc.1"; // FIXME extract out version } -impl Serialize for Payload +impl Serialize for Payload where - Payload: Clone, + Payload: Clone, T::Success: Serialize + DeserializeOwned, - Ipld: From, { fn serialize(&self, serializer: S) -> Result where @@ -43,13 +39,9 @@ where } } -impl< - 'de, - T: Responds + Deserialize<'de>, - E: DeserializeOwned + Serialize + meta::MultiKeyed + TryFrom, - > Deserialize<'de> for Payload +impl<'de, T: Responds + Deserialize<'de>> Deserialize<'de> for Payload where - as TryFrom>>::Error: Debug, + as TryFrom>>::Error: Debug, T::Success: DeserializeOwned + Serialize, { fn deserialize(d: D) -> Result @@ -63,8 +55,7 @@ where } } -impl + meta::MultiKeyed> TryFrom - for Payload +impl TryFrom for Payload where T::Success: Serialize + DeserializeOwned, { @@ -76,8 +67,8 @@ where } } -impl From> for Ipld { - fn from(payload: Payload) -> Self { +impl From> for Ipld { + fn from(payload: Payload) -> Self { payload.into() } } @@ -105,7 +96,7 @@ where issued_at: Option, } -impl> From> for Payload +impl From> for Payload where T::Success: Serialize + DeserializeOwned, { @@ -116,19 +107,18 @@ where out: s.out, next: s.next, proofs: s.proofs, - metadata: s.metadata.into(), + metadata: s.metadata, nonce: s.nonce, issued_at: s.issued_at, } } } -impl From> for InternalSerializer +impl From> for InternalSerializer where - Ipld: From, T::Success: Serialize + DeserializeOwned, { - fn from(s: Payload) -> Self { + fn from(s: Payload) -> Self { InternalSerializer { issuer: s.issuer, ran: s.ran, diff --git a/src/receipt/receipt.rs b/src/receipt/receipt.rs index 594bf3a4..dfd1db01 100644 --- a/src/receipt/receipt.rs +++ b/src/receipt/receipt.rs @@ -1,4 +1,4 @@ use super::payload::Payload; use crate::signature; -pub type Receipt = signature::Envelope>; +pub type Receipt = signature::Envelope>; diff --git a/src/receipt/store.rs b/src/receipt/store.rs index 35c7d5ed..75cd0691 100644 --- a/src/receipt/store.rs +++ b/src/receipt/store.rs @@ -1,15 +1,15 @@ use super::{Receipt, Responds}; -use crate::{metadata, task}; +use crate::task; use libipld_core::ipld::Ipld; -pub trait Store { +pub trait Store { type Error; - fn get(id: task::Id) -> Result, Self::Error> + fn get(id: task::Id) -> Result, Self::Error> where ::Success: TryFrom; - fn put_keyed(id: task::Id, receipt: Receipt) -> Result<(), Self::Error> + fn put_keyed(id: task::Id, receipt: Receipt) -> Result<(), Self::Error> where ::Success: Into; } From e8d7afe3c260bcf6697cfc34984c5bc99d680f1e Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Tue, 6 Feb 2024 21:07:32 -0800 Subject: [PATCH 057/188] Save ahead of reorg --- flake.nix | 18 +- src/ability/dynamic.rs | 5 +- src/ability/ucan.rs | 1 + src/ability/ucan/revoke.rs | 91 ++++ src/delegation/delegatable.rs | 2 + src/did.rs | 15 +- src/did_verifier.rs | 2 +- src/lib.rs | 2 +- src/nonce.rs | 115 ++++- src/proof/checkable.rs | 2 + src/reader.rs | 111 +++-- src/task.rs | 10 +- src/time.rs | 33 +- tests/conformance.rs | 781 +++++++++++++++++----------------- 14 files changed, 699 insertions(+), 489 deletions(-) create mode 100644 src/ability/ucan/revoke.rs diff --git a/flake.nix b/flake.nix index 3415cbca..3cd19cb8 100644 --- a/flake.nix +++ b/flake.nix @@ -254,7 +254,7 @@ name = "watch:test:docs:host"; help = "Run all tests on save"; category = "watch"; - command = "${cargo} watch --clear --exec test"; + command = "${cargo} watch --clear --exec 'test --features=mermaid_docs'"; } { name = "watch:test:wasm"; @@ -266,7 +266,7 @@ name = "watch:test:docs:wasm"; help = "Run all tests on save"; category = "watch"; - command = "${cargo} watch --clear --exec 'test --target=wasm32-unknown-unknown'"; + command = "${cargo} watch --clear --exec 'test --target=wasm32-unknown-unknown --features=mermaid_docs'"; } # Test { @@ -310,26 +310,32 @@ name = "docs"; help = "[DEFAULT]: Open refreshed docs"; category = "dev"; - command = "docs:open"; + command = "docs:open:host"; } { - name = "docs:build"; + name = "docs:build:host"; help = "Refresh the docs"; category = "dev"; command = "${cargo} doc --features=mermaid_docs"; } { - name = "docs:wasm:build"; + name = "docs:build:wasm"; help = "Refresh the docs with the wasm32-unknown-unknown target"; category = "dev"; command = "${cargo} doc --features=mermaid_docs --target=wasm32-unknown-unknown"; } { - name = "docs:open"; + name = "docs:open:host"; help = "Open refreshed docs"; category = "dev"; command = "${cargo} doc --features=mermaid_docs --open"; } + { + name = "docs:open:wasm"; + help = "Open refreshed docs"; + category = "dev"; + command = "${cargo} doc --features=mermaid_docs --open --target=wasm32-unknown-unknown"; + } { name = "docs:wasm:open"; help = "Open refreshed docs for wasm32-unknown-unknown"; diff --git a/src/ability/dynamic.rs b/src/ability/dynamic.rs index f6998fa3..b08c51c8 100644 --- a/src/ability/dynamic.rs +++ b/src/ability/dynamic.rs @@ -13,16 +13,15 @@ use wasm_bindgen::prelude::*; /// A "dynamic" ability with the bare minimum of statics /// ///
-/// This should be a last resort for e.g. FFI. The Dynamic ability is +/// This should be a last resort, and only for e.g. FFI. The Dynamic ability is /// not recommended for typical Rust usage. /// -/// /// This is instead meant to be embedded inside of structs that have e.g. FFI bindings to /// a validation function, such as `js_sys::Function` for JS, `magnus::function!` for Ruby, /// and so on. ///
/// -/// Dynamic none of the typical ability traits directly. Rather, it must be wrapped +/// [`Dynamic`] uses none of the typical ability traits directly. Rather, it must be wrapped /// in [`Reader`][crate::reader::Reader], which wires up dynamic dispatch for the /// relevant traits using a configuration struct. #[derive(Clone, PartialEq, Debug, Serialize, Deserialize)] // FIXME serialize / deserilaize? diff --git a/src/ability/ucan.rs b/src/ability/ucan.rs index 830f7c22..1987e8a1 100644 --- a/src/ability/ucan.rs +++ b/src/ability/ucan.rs @@ -1,2 +1,3 @@ pub mod proxy; +pub mod revoke; // FIXME pub mod revoke; diff --git a/src/ability/ucan/revoke.rs b/src/ability/ucan/revoke.rs new file mode 100644 index 00000000..aa13e358 --- /dev/null +++ b/src/ability/ucan/revoke.rs @@ -0,0 +1,91 @@ +//! UCAN [Revocations](https://github.com/ucan-wg/revocation) + +use crate::{ + ability::{arguments, command::Command}, + delegation::Delegatable, + invocation::{Promise, Resolvable}, +}; +use libipld_core::{cid::Cid, ipld::Ipld}; +use serde::{Deserialize, Serialize}; +use std::{collections::BTreeMap, fmt::Debug}; + +/// An ability for revoking previously issued UCANs by [`Cid`] +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub struct Generic { + // FIXME check spec + /// The UCAN to revoke + pub ucan: Arg, +} + +impl Command for Generic { + const COMMAND: &'static str = "ucan/revoke"; +} + +/// The fully resolved variant: ready to execute. +pub type Ready = Generic; + +impl Delegatable for Ready { + type Builder = Builder; +} + +impl Resolvable for Ready { + type Promised = Promised; +} + +/// A variant with some fields waiting to be set. +pub type Builder = Generic>; + +impl From for Builder { + fn from(resolved: Ready) -> Builder { + Builder { + ucan: Some(resolved.ucan), + } + } +} + +impl TryFrom for Ready { + type Error = (); + + fn try_from(b: Builder) -> Result { + Ok(Ready { + ucan: b.ucan.ok_or(())?, + }) + } +} + +impl From for arguments::Named { + fn from(b: Builder) -> arguments::Named { + let mut btree = BTreeMap::new(); + if let Some(cid) = b.ucan { + btree.insert("ucan".into(), cid.into()); + } + arguments::Named(btree) + } +} + +/// A variant where arguments may be [`Promise`]s +pub type Promised = Generic>; + +impl From for Promised { + fn from(r: Ready) -> Promised { + Promised { + ucan: Promise::Fulfilled(r.ucan), + } + } +} + +impl From for arguments::Named { + fn from(p: Promised) -> arguments::Named { + arguments::Named::from_iter([("ucan".into(), p.ucan.into())]) + } +} + +impl TryFrom for Ready { + type Error = (); + + fn try_from(p: Promised) -> Result { + Ok(Ready { + ucan: p.ucan.try_resolve().map_err(|_| ())?, + }) + } +} diff --git a/src/delegation/delegatable.rs b/src/delegation/delegatable.rs index 757976c8..dcb733d9 100644 --- a/src/delegation/delegatable.rs +++ b/src/delegation/delegatable.rs @@ -1,5 +1,7 @@ use crate::ability::arguments; pub trait Delegatable: Sized { + /// A delegation with some arguments filled + /// FIXME add more type Builder: TryInto + From + Into; } diff --git a/src/did.rs b/src/did.rs index 66f8beaa..c5312722 100644 --- a/src/did.rs +++ b/src/did.rs @@ -1,3 +1,5 @@ +//! Decentralized Identifier (DID) utilities + use did_url::DID; use libipld_core::ipld::Ipld; use serde::{Deserialize, Serialize}; @@ -63,21 +65,14 @@ impl TryFrom for Did { #[derive(Debug, Clone, PartialEq, Error)] pub enum FromIpldError { /// Strutural errors in the [`Did`] - StructuralError(did_url::Error), + #[error(transparent)] + StructuralError(#[from] did_url::Error), /// The [`Ipld`] was not a string + #[error("Not an IPLD String")] NotAnIpldString(Ipld), } -impl fmt::Display for FromIpldError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - FromIpldError::StructuralError(e) => write!(f, "DID Error: {}", e), - FromIpldError::NotAnIpldString(_ipld) => write!(f, "Not an IPLD String"), // FIXME include the bad ipld, but needs a Display instance - } - } -} - impl Serialize for FromIpldError { fn serialize(&self, serializer: S) -> Result where diff --git a/src/did_verifier.rs b/src/did_verifier.rs index 62c5ab59..14a2f47a 100644 --- a/src/did_verifier.rs +++ b/src/did_verifier.rs @@ -1,4 +1,4 @@ -//! DID verifier methods +// //! DID verifier methods use core::fmt; use std::collections::HashMap; diff --git a/src/lib.rs b/src/lib.rs index 794ebac0..c8928529 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -15,7 +15,6 @@ extern crate alloc; // pub mod builder; // pub mod capability; // pub mod crypto; -// pub mod did_verifier; // pub mod error; // pub mod plugins; // pub mod semantics; @@ -68,6 +67,7 @@ pub mod agent; pub mod capsule; pub mod delegation; pub mod did; +// pub mod did_verifier; pub mod invocation; pub mod ipld; pub mod nonce; diff --git a/src/nonce.rs b/src/nonce.rs index b9c775e5..9f3628d9 100644 --- a/src/nonce.rs +++ b/src/nonce.rs @@ -38,6 +38,16 @@ impl From<[u8; 16]> for Nonce { } } +impl From for Vec { + fn from(nonce: Nonce) -> Self { + match nonce { + Nonce::Nonce12(nonce) => nonce.to_vec(), + Nonce::Nonce16(nonce) => nonce.to_vec(), + Nonce::Custom(nonce) => nonce, + } + } +} + impl From> for Nonce { fn from(nonce: Vec) -> Self { match nonce.len() { @@ -57,31 +67,80 @@ impl From> for Nonce { } impl Nonce { - // NOTE seed = domain-separator - pub fn generate_12(seed: &mut Vec) -> Nonce { - seed.append(&mut [0].repeat(12)); - - let buf = seed.as_mut_slice(); + // NOTE salt = domain-separator + /// Generate a 96-bit, 12-byte nonce. + /// This is the minimum nonce size typically recommended. + /// + /// # Arguments + /// + /// * `salt` - A salt. This may be left empty, but is recommended to avoid collision. + /// + /// # Example + /// + /// ```rust + /// # use ucan::nonce::Nonce; + /// # use ucan::did::Did; + /// # + /// let mut salt = "did:example:123".as_bytes().to_vec(); + /// let nonce = Nonce::generate_12(&mut salt); + /// + /// assert_eq!(Vec::from(nonce).len(), 12); + /// ``` + pub fn generate_12(salt: &mut Vec) -> Nonce { + salt.append(&mut [0].repeat(12)); + + let buf = salt.as_mut_slice(); getrandom(buf).expect("irrecoverable getrandom failure"); let mut hasher = Sha2_256::default(); hasher.update(buf); - let bytes = hasher.finalize().try_into().expect("SHA2_256 is 32 bytes"); + let bytes = hasher + .finalize() + .chunks(12) + .next() + .expect("SHA2_256 is 32 bytes") + .try_into() + .expect("we set the length to 12 earlier"); + Nonce::Nonce12(bytes) } - pub fn generate_16(seed: &mut Vec) -> Nonce { - seed.append(&mut [0].repeat(16)); - - let buf = seed.as_mut_slice(); + /// Generate a 128-bit, 16-byte nonce + /// + /// # Arguments + /// + /// * `salt` - A salt. This may be left empty, but is recommended to avoid collision. + /// + /// # Example + /// + /// ```rust + /// # use ucan::nonce::Nonce; + /// # use ucan::did::Did; + /// # + /// let mut salt = "did:example:123".as_bytes().to_vec(); + /// let nonce = Nonce::generate_16(&mut salt); + /// + /// assert_eq!(Vec::from(nonce).len(), 16); + /// ``` + pub fn generate_16(salt: &mut Vec) -> Nonce { + salt.append(&mut [0].repeat(16)); + + let buf = salt.as_mut_slice(); getrandom(buf).expect("irrecoverable getrandom failure"); let mut hasher = Sha2_256::default(); hasher.update(buf); - let bytes = hasher.finalize().try_into().expect("SHA2_256 is 32 bytes"); - Nonce::Nonce12(bytes) + let bytes = hasher + .finalize() + .chunks(16) + .next() + .expect("SHA2_256 is 32 bytes") + .try_into() + .expect("we set the length to 16 earlier"); + + Nonce::Nonce16(bytes) } } @@ -144,6 +203,7 @@ impl TryFrom<&Ipld> for Nonce { #[cfg(target_arch = "wasm32")] #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] #[wasm_bindgen] +/// A JavaScript-compatible wrapper for [`Nonce`] pub struct JsNonce(#[wasm_bindgen(skip)] pub Nonce); #[cfg(target_arch = "wasm32")] @@ -163,18 +223,39 @@ impl From for JsNonce { #[cfg(target_arch = "wasm32")] #[wasm_bindgen] impl JsNonce { - pub fn generate_12(mut seed: Vec) -> JsNonce { - Nonce::generate_12(&mut seed).into() + /// Generate a 96-bit, 12-byte nonce. + /// This is the minimum nonce size typically recommended. + /// + /// # Arguments + /// + /// * `salt` - A salt. This may be left empty, but is recommended to avoid collision. + pub fn generate_12(mut salt: Vec) -> JsNonce { + Nonce::generate_12(&mut salt).into() } - pub fn generate_16(mut seed: Vec) -> JsNonce { - Nonce::generate_16(&mut seed).into() + /// Generate a 128-bit, 16-byte nonce + /// + /// # Arguments + /// + /// * `salt` - A salt. This may be left empty, but is recommended to avoid collision. + pub fn generate_16(mut salt: Vec) -> JsNonce { + Nonce::generate_16(&mut salt).into() } - pub fn from_uint8_array(arr: Box<[u8]>) -> JsNonce { + /// Directly lift a 12-byte `Uint8Array` into a [`JsNonce`] + /// + /// # Arguments + /// + /// * `nonce` - The exact nonce to convert to a [`JsNonce`] + pub fn from_uint8_array(nonce: Box<[u8]>) -> JsNonce { Nonce::from(arr.to_vec()).into() } + /// Expose the underlying bytes of a [`JsNonce`] as a 12-byte `Uint8Array` + /// + /// # Arguments + /// + /// * `self` - The [`JsNonce`] to convert to a `Uint8Array` pub fn to_uint8_array(&self) -> Box<[u8]> { match &self.0 { Nonce::Nonce12(nonce) => nonce.to_vec().into_boxed_slice(), diff --git a/src/proof/checkable.rs b/src/proof/checkable.rs index 9b04cf1f..87b08f4f 100644 --- a/src/proof/checkable.rs +++ b/src/proof/checkable.rs @@ -2,6 +2,8 @@ use super::{internal::Checker, prove::Prove, same::CheckSame}; +// FIXME move to Delegatbel? + /// Plug a type into the delegation checking pipeline pub trait Checkable: CheckSame { /// The type of hierarchy this ability has diff --git a/src/reader.rs b/src/reader.rs index 60032406..ecb03a97 100644 --- a/src/reader.rs +++ b/src/reader.rs @@ -8,8 +8,6 @@ use crate::{ }; use serde::{Deserialize, Serialize}; -// NOTE to self: this is helpful as a common container to lift various FFI into - /// A struct that attaches an ambient environment to a value /// /// This is helpful for dependency injection and/or passing around values that @@ -72,31 +70,6 @@ pub struct Reader { pub val: T, } -impl> From> for arguments::Named { - fn from(reader: Reader) -> Self { - reader.val.into() - } -} - -// NOTE plug this into Reader like: Reader> -#[derive(Clone, PartialEq, Debug, Serialize, Deserialize)] -pub struct Builder(pub T); - -#[derive(Clone, PartialEq, Debug, Serialize, Deserialize)] -pub struct Promised(pub T); - -impl> From> for arguments::Named { - fn from(builder: Builder) -> Self { - builder.0.into() - } -} - -impl> From> for arguments::Named { - fn from(promised: Promised) -> Self { - promised.0.into() - } -} - impl Reader { /// Map a function over the `val` of the [`Reader`] pub fn map(self, func: F) -> Reader @@ -178,12 +151,79 @@ impl Reader { } } +impl> From> for arguments::Named { + fn from(reader: Reader) -> Self { + reader.val.into() + } +} + +impl Checkable for Reader +where + Reader: CheckSame, +{ + type Hierarchy = Env::Hierarchy; +} + +impl ToCommand for Reader { + fn to_command(&self) -> String { + self.env.to_command() + } +} + +/// A helper newtype that marks a value as being a [`Delegatable::Builder`]. +/// +/// The is often used as: +/// +/// ```rust +/// # use ucan::reader::{Reader, Builder}; +/// # type Env = (); +/// # let env = (); +/// let example: Reader> = Reader { +/// env: env, +/// val: Builder(42), +/// }; +/// ``` +#[derive(Clone, PartialEq, Debug, Serialize, Deserialize)] +pub struct Builder(pub T); + +impl> From> for arguments::Named { + fn from(builder: Builder) -> Self { + builder.0.into() + } +} + impl From> for Reader> { fn from(reader: Reader) -> Self { reader.map(Builder) } } +impl> Delegatable for Reader { + type Builder = Reader>; +} + +/// A helper newtype that marks a value as being a [`Resolvable::Promised`]. +/// +/// The is often used as: +/// +/// ```rust +/// # use ucan::reader::{Reader, Promised}; +/// # type Env = (); +/// # let env = (); +/// let example: Reader> = Reader { +/// env: env, +/// val: Promised(42), +/// }; +/// ``` +#[derive(Clone, PartialEq, Debug, Serialize, Deserialize)] +pub struct Promised(pub T); + +impl> From> for arguments::Named { + fn from(promised: Promised) -> Self { + promised.0.into() + } +} + impl From>> for Reader { fn from(reader: Reader>) -> Self { reader.map(|b| b.0) @@ -202,23 +242,6 @@ impl From>> for Reader { } } -impl> Delegatable for Reader { - type Builder = Reader>; -} - impl> Resolvable for Reader { type Promised = Reader>; } - -impl ToCommand for Reader { - fn to_command(&self) -> String { - self.env.to_command() - } -} - -impl Checkable for Reader -where - Reader: CheckSame, -{ - type Hierarchy = Env::Hierarchy; -} diff --git a/src/task.rs b/src/task.rs index c3363f0a..f9c616db 100644 --- a/src/task.rs +++ b/src/task.rs @@ -21,19 +21,19 @@ const SHA2_256: u64 = 0x12; /// on the type. In particular, the `nonce` field should be constant for all of the same type. #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] pub struct Task { - /// The `subject`: root issuer, and arbiter of the semantics/namespace + /// The `subject`: root issuer, and arbiter of the semantics/namespace. pub sub: Did, - /// A unique identifier for the particular task run + /// A unique identifier for the particular task run. /// /// This is an [`Option`] because not all task types require a nonce. #[serde(default, skip_serializing_if = "Option::is_none")] pub nonce: Option, - /// The command identifier + /// The command identifier. pub cmd: String, - /// The arguments to the command + /// The arguments to the command. pub args: arguments::Named, } @@ -67,7 +67,7 @@ impl From for Cid { } } -/// The unique identifier for a [`Task`] +/// The unique identifier for a [`Task`]. #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(transparent)] pub struct Id { diff --git a/src/time.rs b/src/time.rs index 0e07098b..1440e1b0 100644 --- a/src/time.rs +++ b/src/time.rs @@ -9,7 +9,7 @@ use web_time::{Duration, SystemTime, UNIX_EPOCH}; #[cfg(target_arch = "wasm32")] use wasm_bindgen::prelude::*; -/// Get the current time in seconds since UNIX_EPOCH +/// Get the current time in seconds since [`UNIX_EPOCH`]. pub fn now() -> u64 { SystemTime::now() .duration_since(SystemTime::UNIX_EPOCH) @@ -17,16 +17,16 @@ pub fn now() -> u64 { .as_secs() } -/// All timestamps that this library can handle +/// All timestamps that this library can handle. /// -/// Strictly speaking, UCAN only supports [`JsTime`] for JavaScript interoperability. +/// Strictly speaking, UCAN exclusively supports [`JsTime`] (for JavaScript interoperability). #[derive(Debug, Clone, PartialEq)] pub enum Timestamp { - /// An entry for [`JsTime`], which is compatible with JavaScript's 2⁵³ numberic range + /// An entry for [`JsTime`], which is compatible with JavaScript's 2⁵³ numeric range. JsSafe(JsTime), /// Following [Postel's Law](https://en.wikipedia.org/wiki/Robustness_principle), - /// received timestamps may be parsed as regular [`SystemTime`] + /// received timestamps may be parsed as regular [`SystemTime`]. Postel(SystemTime), } @@ -47,12 +47,14 @@ impl<'de> Deserialize<'de> for Timestamp { where D: Deserializer<'de>, { - if let Ok(js_time) = JsTime::deserialize(deserializer) { - return Ok(Timestamp::JsSafe(js_time)); + if let Ok(sys_time) = SystemTime::deserialize(deserializer) { + match JsTime::new(sys_time) { + Ok(js_time) => Ok(Timestamp::JsSafe(js_time)), + Err(_) => Ok(Timestamp::Postel(sys_time)), + } + } else { + Err(serde::de::Error::custom("not a Timestamp")) } - - todo!() - // FIXME just todo()ing this for now becuase the enum will likely go away very shortly } } @@ -79,6 +81,8 @@ impl TryFrom for Timestamp { } } +/// A JavaScript-wrapper for [`Timestamp`]. +/// /// Per the UCAN spec, timestamps MUST respect [IEEE-754] /// (64-bit double precision = 53-bit truncated integer) for JavaScript interoperability. /// @@ -93,6 +97,7 @@ pub struct JsTime { } #[cfg(target_arch = "wasm32")] +#[wasm_bindgen] impl JsTime { /// Lift a [`js_sys::Date`] into a Rust [`JsTime`] pub fn from_date(date_time: js_sys::Date) -> Result { @@ -114,7 +119,11 @@ impl JsTime { } impl JsTime { - /// Create a [`JsTime`] from a [`SystemTime`] + /// Create a [`JsTime`] from a [`SystemTime`]. + /// + /// # Arguments + /// + /// * `time` — The time to convert /// /// # Errors /// @@ -166,7 +175,7 @@ impl<'de> Deserialize<'de> for JsTime { /// An error expressing when a time is larger than 2^53 seconds past the Unix epoch #[derive(Debug, Clone, PartialEq, Eq, Error)] pub struct OutOfRangeError { - /// The [`SystemTime`] that is outside of the [`JsTime`] range (2^53) + /// The [`SystemTime`] that is outside of the [`JsTime`] range (2^53). pub tried: SystemTime, } diff --git a/tests/conformance.rs b/tests/conformance.rs index 44518df3..8e24da1f 100644 --- a/tests/conformance.rs +++ b/tests/conformance.rs @@ -1,390 +1,391 @@ -use libipld_core::{ipld::Ipld, raw::RawCodec}; -use serde::{Deserialize, Serialize}; -use std::{collections::HashMap, fs::File, io::BufReader}; - -use ucan::{ - capability::DefaultCapabilityParser, - did_verifier::DidVerifierMap, - store::{self, Store}, - ucan::Ucan, - DefaultFact, -}; - -trait TestTask { - fn run(&self, name: &str, report: &mut TestReport); -} - -#[derive(Debug, Default)] -struct TestReport { - num_tests: usize, - successes: Vec, - failures: Vec, -} - -#[derive(Debug)] -struct TestFailure { - name: String, - error: String, -} - -impl TestReport { - fn register_success(&mut self, name: &str) { - self.num_tests += 1; - self.successes.push(name.to_string()); - } - - fn register_failure(&mut self, name: &str, error: String) { - self.num_tests += 1; - self.failures.push(TestFailure { - name: name.to_string(), - error, - }); - } - - fn finish(&self) { - for success in &self.successes { - println!("✅ {}", success); - } - - for failure in &self.failures { - println!("❌ {}: {}", failure.name, failure.error); - } - - println!( - "{} tests, {} successes, {} failures", - self.num_tests, - self.successes.len(), - self.failures.len() - ); - - if !self.failures.is_empty() { - panic!(); - } - } -} - -#[derive(Debug, Serialize, Deserialize)] -struct TestFixture { - name: String, - #[serde(flatten)] - test_case: TestCase, -} - -#[derive(Debug, Serialize, Deserialize)] -#[serde(tag = "task", rename_all = "camelCase")] -enum TestCase { - Verify(VerifyTest), - Refute(RefuteTest), - Build(BuildTest), - ToCID(ToCidTest), -} - -#[derive(Debug, Serialize, Deserialize)] -struct VerifyTest { - inputs: TestInputsTokenAndProofs, - assertions: TestAssertions, -} - -#[derive(Debug, Serialize, Deserialize)] -struct RefuteTest { - inputs: TestInputsTokenAndProofs, - assertions: TestAssertions, - errors: Vec, -} - -#[derive(Debug, Serialize, Deserialize)] -struct BuildTest { - inputs: BuildTestInputs, - outputs: BuildTestOutputs, -} - -#[derive(Debug, Serialize, Deserialize)] -struct ToCidTest { - inputs: ToCidTestInputs, - outputs: ToCidTestOutputs, -} - -#[derive(Debug, Serialize, Deserialize)] -struct TestInputsTokenAndProofs { - token: String, - proofs: HashMap, -} - -#[derive(Debug, Serialize, Deserialize)] -struct TestAssertions { - header: TestAssertionsHeader, - payload: TestAssertionsPayload, - signature: String, -} - -#[derive(Debug, Serialize, Deserialize)] -struct TestAssertionsHeader { - alg: Option, - typ: Option, -} - -#[derive(Debug, Serialize, Deserialize)] -struct TestAssertionsPayload { - ucv: Option, - iss: Option, - aud: Option, - exp: Option, - // TODO: CAP - // TODO: FCT - prf: Option>, -} - -#[derive(Debug, Serialize, Deserialize)] -struct BuildTestInputs { - version: Option, - issuer_base64_key: String, - signature_scheme: String, - audience: Option, - not_before: Option, - expiration: Option, - // TODO CAPABILITIES - // TODO FACTS -} - -#[derive(Debug, Serialize, Deserialize)] -struct BuildTestOutputs { - token: String, -} - -#[derive(Debug, Serialize, Deserialize)] -struct ToCidTestInputs { - token: String, - hasher: String, -} - -#[derive(Debug, Serialize, Deserialize)] -struct ToCidTestOutputs { - cid: String, -} - -impl TestTask for VerifyTest { - fn run(&self, name: &str, report: &mut TestReport) { - let mut store = store::InMemoryStore::::default(); - let did_verifier_map = DidVerifierMap::default(); - - for (_cid, token) in self.inputs.proofs.iter() { - store - .write(Ipld::Bytes(token.as_bytes().to_vec()), None) - .unwrap(); - } - - let Ok(ucan) = Ucan::::from_str(&self.inputs.token) - else { - report.register_failure(name, "failed to parse token".to_string()); - - return; - }; - - if let Some(alg) = &self.assertions.header.alg { - if ucan.algorithm() != alg { - report.register_failure( - name, - format!( - "expected algorithm to be {}, but was {}", - alg, - ucan.algorithm() - ), - ); - - return; - } - } - - if let Some(typ) = &self.assertions.header.typ { - if ucan.typ() != typ { - report.register_failure( - name, - format!("expected type to be {}, but was {}", typ, ucan.typ()), - ); - - return; - } - } - - if let Some(ucv) = &self.assertions.payload.ucv { - if ucan.version() != ucv { - report.register_failure( - name, - format!("expected version to be {}, but was {}", ucv, ucan.version()), - ); - - return; - } - } - - if let Some(iss) = &self.assertions.payload.iss { - if ucan.issuer() != iss { - report.register_failure( - name, - format!("expected issuer to be {}, but was {}", iss, ucan.issuer()), - ); - - return; - } - } - - if let Some(aud) = &self.assertions.payload.aud { - if ucan.audience() != aud { - report.register_failure( - name, - format!( - "expected audience to be {}, but was {}", - aud, - ucan.audience() - ), - ); - - return; - } - } - - if ucan.expires_at() != self.assertions.payload.exp { - report.register_failure( - name, - format!( - "expected expiration to be {:?}, but was {:?}", - self.assertions.payload.exp, - ucan.expires_at() - ), - ); - - return; - } - - if ucan - .proofs() - .map(|f| f.iter().map(|c| c.to_string()).collect()) - != self.assertions.payload.prf - { - report.register_failure( - name, - format!( - "expected proofs to be {:?}, but was {:?}", - self.assertions.payload.prf, - ucan.proofs() - ), - ); - - return; - } - - let Ok(signature) = serde_json::to_value(ucan.signature()) else { - report.register_failure(name, "failed to serialize signature".to_string()); - - return; - }; - - let Some(signature) = signature.as_str() else { - report.register_failure(name, "expected signature to be a string".to_string()); - - return; - }; - - if signature != self.assertions.signature { - report.register_failure( - name, - format!( - "expected signature to be {}, but was {}", - self.assertions.signature, signature - ), - ); - - return; - } - - if let Err(err) = ucan.validate(ucan::time::now(), &did_verifier_map) { - report.register_failure(name, err.to_string()); - - return; - } - } -} - -impl TestTask for RefuteTest { - fn run(&self, name: &str, report: &mut TestReport) { - let mut store = store::InMemoryStore::::default(); - let did_verifier_map = DidVerifierMap::default(); - - for (_cid, token) in self.inputs.proofs.iter() { - store - .write(Ipld::Bytes(token.as_bytes().to_vec()), None) - .unwrap(); - } - - if let Ok(ucan) = Ucan::::from_str(&self.inputs.token) - { - if ucan.validate(ucan::time::now(), &did_verifier_map).is_ok() { - report.register_failure( - &name, - "expected token to fail validation, but it passed".to_string(), - ); - - return; - } - } - } -} - -impl TestTask for BuildTest { - fn run(&self, _: &str, _: &mut TestReport) { - //TODO: can't assert on signature because of canonicalization issues - } -} - -impl TestTask for ToCidTest { - fn run(&self, name: &str, report: &mut TestReport) { - let ucan = - Ucan::::from_str(&self.inputs.token).unwrap(); - let hasher = match self.inputs.hasher.as_str() { - "SHA2-256" => multihash::Code::Sha2_256, - "BLAKE3-256" => multihash::Code::Blake3_256, - _ => panic!(), - }; - - let Ok(cid) = ucan.to_cid(Some(hasher)) else { - report.register_failure(&name, "failed to convert to CID".to_string()); - - return; - }; - - if cid.to_string() != self.outputs.cid { - report.register_failure( - &name, - format!( - "expected CID to be {}, but was {}", - self.outputs.cid, - cid.to_string() - ), - ); - - return; - } - } -} - -#[test] -fn ucan_0_10_0_conformance_tests() { - let fixtures_file = File::open("tests/fixtures/0.10.0/all.json").unwrap(); - let reader = BufReader::new(fixtures_file); - let fixtures: Vec = serde_json::from_reader(reader).unwrap(); - - let mut report = TestReport::default(); - - for fixture in fixtures { - match fixture.test_case { - TestCase::Verify(test) => test.run(&fixture.name, &mut report), - TestCase::Refute(test) => test.run(&fixture.name, &mut report), - TestCase::Build(test) => test.run(&fixture.name, &mut report), - TestCase::ToCID(test) => test.run(&fixture.name, &mut report), - }; - - report.register_success(&fixture.name); - } - - report.finish(); -} +// use libipld_core::{ipld::Ipld, raw::RawCodec}; +// use serde::{Deserialize, Serialize}; +// use std::{collections::HashMap, fs::File, io::BufReader}; + +// use ucan::{ +// capability::DefaultCapabilityParser, +// did_verifier::DidVerifierMap, +// store::{self, Store}, +// ucan::Ucan, +// DefaultFact, +// }; +// +// trait TestTask { +// fn run(&self, name: &str, report: &mut TestReport); +// } +// +// #[derive(Debug, Default)] +// struct TestReport { +// num_tests: usize, +// successes: Vec, +// failures: Vec, +// } +// +// #[derive(Debug)] +// struct TestFailure { +// name: String, +// error: String, +// } +// +// impl TestReport { +// fn register_success(&mut self, name: &str) { +// self.num_tests += 1; +// self.successes.push(name.to_string()); +// } +// +// fn register_failure(&mut self, name: &str, error: String) { +// self.num_tests += 1; +// self.failures.push(TestFailure { +// name: name.to_string(), +// error, +// }); +// } +// +// fn finish(&self) { +// for success in &self.successes { +// println!("✅ {}", success); +// } +// +// for failure in &self.failures { +// println!("❌ {}: {}", failure.name, failure.error); +// } +// +// println!( +// "{} tests, {} successes, {} failures", +// self.num_tests, +// self.successes.len(), +// self.failures.len() +// ); +// +// if !self.failures.is_empty() { +// panic!(); +// } +// } +// } +// +// #[derive(Debug, Serialize, Deserialize)] +// struct TestFixture { +// name: String, +// #[serde(flatten)] +// test_case: TestCase, +// } +// +// #[derive(Debug, Serialize, Deserialize)] +// #[serde(tag = "task", rename_all = "camelCase")] +// enum TestCase { +// Verify(VerifyTest), +// Refute(RefuteTest), +// Build(BuildTest), +// ToCID(ToCidTest), +// } +// +// #[derive(Debug, Serialize, Deserialize)] +// struct VerifyTest { +// inputs: TestInputsTokenAndProofs, +// assertions: TestAssertions, +// } +// +// #[derive(Debug, Serialize, Deserialize)] +// struct RefuteTest { +// inputs: TestInputsTokenAndProofs, +// assertions: TestAssertions, +// errors: Vec, +// } +// +// #[derive(Debug, Serialize, Deserialize)] +// struct BuildTest { +// inputs: BuildTestInputs, +// outputs: BuildTestOutputs, +// } +// +// #[derive(Debug, Serialize, Deserialize)] +// struct ToCidTest { +// inputs: ToCidTestInputs, +// outputs: ToCidTestOutputs, +// } +// +// #[derive(Debug, Serialize, Deserialize)] +// struct TestInputsTokenAndProofs { +// token: String, +// proofs: HashMap, +// } +// +// #[derive(Debug, Serialize, Deserialize)] +// struct TestAssertions { +// header: TestAssertionsHeader, +// payload: TestAssertionsPayload, +// signature: String, +// } +// +// #[derive(Debug, Serialize, Deserialize)] +// struct TestAssertionsHeader { +// alg: Option, +// typ: Option, +// } +// +// #[derive(Debug, Serialize, Deserialize)] +// struct TestAssertionsPayload { +// ucv: Option, +// iss: Option, +// aud: Option, +// exp: Option, +// // TODO: CAP +// // TODO: FCT +// prf: Option>, +// } +// +// #[derive(Debug, Serialize, Deserialize)] +// struct BuildTestInputs { +// version: Option, +// issuer_base64_key: String, +// signature_scheme: String, +// audience: Option, +// not_before: Option, +// expiration: Option, +// // TODO CAPABILITIES +// // TODO FACTS +// } +// +// #[derive(Debug, Serialize, Deserialize)] +// struct BuildTestOutputs { +// token: String, +// } +// +// #[derive(Debug, Serialize, Deserialize)] +// struct ToCidTestInputs { +// token: String, +// hasher: String, +// } +// +// // #[derive(Debug, Serialize, Deserialize)] +// // +// // struct ToCidTestOutputs { +// // cid: String, +// // } +// // +// // impl TestTask for VerifyTest { +// // fn run(&self, name: &str, report: &mut TestReport) { +// // let mut store = store::InMemoryStore::::default(); +// // let did_verifier_map = DidVerifierMap::default(); +// // +// // for (_cid, token) in self.inputs.proofs.iter() { +// // store +// // .write(Ipld::Bytes(token.as_bytes().to_vec()), None) +// // .unwrap(); +// // } +// // +// // let Ok(ucan) = Ucan::::from_str(&self.inputs.token) +// // else { +// // report.register_failure(name, "failed to parse token".to_string()); +// // +// // return; +// // }; +// // +// // if let Some(alg) = &self.assertions.header.alg { +// // if ucan.algorithm() != alg { +// // report.register_failure( +// // name, +// // format!( +// // "expected algorithm to be {}, but was {}", +// // alg, +// // ucan.algorithm() +// // ), +// // ); +// // +// // return; +// // } +// // } +// // +// // if let Some(typ) = &self.assertions.header.typ { +// // if ucan.typ() != typ { +// // report.register_failure( +// // name, +// // format!("expected type to be {}, but was {}", typ, ucan.typ()), +// // ); +// // +// // return; +// // } +// // } +// // +// // if let Some(ucv) = &self.assertions.payload.ucv { +// // if ucan.version() != ucv { +// // report.register_failure( +// // name, +// // format!("expected version to be {}, but was {}", ucv, ucan.version()), +// // ); +// // +// // return; +// // } +// // } +// // +// // if let Some(iss) = &self.assertions.payload.iss { +// // if ucan.issuer() != iss { +// // report.register_failure( +// // name, +// // format!("expected issuer to be {}, but was {}", iss, ucan.issuer()), +// // ); +// // +// // return; +// // } +// // } +// // +// // if let Some(aud) = &self.assertions.payload.aud { +// // if ucan.audience() != aud { +// // report.register_failure( +// // name, +// // format!( +// // "expected audience to be {}, but was {}", +// // aud, +// // ucan.audience() +// // ), +// // ); +// // +// // return; +// // } +// // } +// // +// // if ucan.expires_at() != self.assertions.payload.exp { +// // report.register_failure( +// // name, +// // format!( +// // "expected expiration to be {:?}, but was {:?}", +// // self.assertions.payload.exp, +// // ucan.expires_at() +// // ), +// // ); +// // +// // return; +// // } +// // +// // if ucan +// // .proofs() +// // .map(|f| f.iter().map(|c| c.to_string()).collect()) +// // != self.assertions.payload.prf +// // { +// // report.register_failure( +// // name, +// // format!( +// // "expected proofs to be {:?}, but was {:?}", +// // self.assertions.payload.prf, +// // ucan.proofs() +// // ), +// // ); +// // +// // return; +// // } +// // +// // let Ok(signature) = serde_json::to_value(ucan.signature()) else { +// // report.register_failure(name, "failed to serialize signature".to_string()); +// // +// // return; +// // }; +// // +// // let Some(signature) = signature.as_str() else { +// // report.register_failure(name, "expected signature to be a string".to_string()); +// // +// // return; +// // }; +// // +// // if signature != self.assertions.signature { +// // report.register_failure( +// // name, +// // format!( +// // "expected signature to be {}, but was {}", +// // self.assertions.signature, signature +// // ), +// // ); +// // +// // return; +// // } +// // +// // if let Err(err) = ucan.validate(ucan::time::now(), &did_verifier_map) { +// // report.register_failure(name, err.to_string()); +// // +// // return; +// // } +// // } +// // } +// // +// // impl TestTask for RefuteTest { +// // fn run(&self, name: &str, report: &mut TestReport) { +// // let mut store = store::InMemoryStore::::default(); +// // let did_verifier_map = DidVerifierMap::default(); +// // +// // for (_cid, token) in self.inputs.proofs.iter() { +// // store +// // .write(Ipld::Bytes(token.as_bytes().to_vec()), None) +// // .unwrap(); +// // } +// // +// // if let Ok(ucan) = Ucan::::from_str(&self.inputs.token) +// // { +// // if ucan.validate(ucan::time::now(), &did_verifier_map).is_ok() { +// // report.register_failure( +// // &name, +// // "expected token to fail validation, but it passed".to_string(), +// // ); +// // +// // return; +// // } +// // } +// // } +// // } +// // +// // impl TestTask for BuildTest { +// // fn run(&self, _: &str, _: &mut TestReport) { +// // //TODO: can't assert on signature because of canonicalization issues +// // } +// // } +// // +// // impl TestTask for ToCidTest { +// // fn run(&self, name: &str, report: &mut TestReport) { +// // let ucan = +// // Ucan::::from_str(&self.inputs.token).unwrap(); +// // let hasher = match self.inputs.hasher.as_str() { +// // "SHA2-256" => multihash::Code::Sha2_256, +// // "BLAKE3-256" => multihash::Code::Blake3_256, +// // _ => panic!(), +// // }; +// // +// // let Ok(cid) = ucan.to_cid(Some(hasher)) else { +// // report.register_failure(&name, "failed to convert to CID".to_string()); +// // +// // return; +// // }; +// // +// // if cid.to_string() != self.outputs.cid { +// // report.register_failure( +// // &name, +// // format!( +// // "expected CID to be {}, but was {}", +// // self.outputs.cid, +// // cid.to_string() +// // ), +// // ); +// // +// // return; +// // } +// // } +// // } +// // +// // #[test] +// // fn ucan_0_10_0_conformance_tests() { +// // let fixtures_file = File::open("tests/fixtures/0.10.0/all.json").unwrap(); +// // let reader = BufReader::new(fixtures_file); +// // let fixtures: Vec = serde_json::from_reader(reader).unwrap(); +// // +// // let mut report = TestReport::default(); +// // +// // for fixture in fixtures { +// // match fixture.test_case { +// // TestCase::Verify(test) => test.run(&fixture.name, &mut report), +// // TestCase::Refute(test) => test.run(&fixture.name, &mut report), +// // TestCase::Build(test) => test.run(&fixture.name, &mut report), +// // TestCase::ToCID(test) => test.run(&fixture.name, &mut report), +// // }; +// // +// // report.register_success(&fixture.name); +// // } +// // +// // report.finish(); +// // } From 1ed78e121737cf3689fc4f4ad37fcb1a345789b3 Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Tue, 6 Feb 2024 23:50:23 -0800 Subject: [PATCH 058/188] Save before reworking Promise --- Cargo.toml | 1 + src/ability/crud/any.rs | 19 +---- src/ability/ucan.rs | 5 +- src/ability/ucan/revoke.rs | 13 ++- src/ability/wasm.rs | 32 +------- src/ability/wasm/module.rs | 75 +++++++++++++++++ src/ability/wasm/run.rs | 147 ++++++++++++++++++++++++++++++++++ src/delegation/delegatable.rs | 2 + src/invocation/promise.rs | 10 ++- src/invocation/resolvable.rs | 2 + src/proof.rs | 1 + src/proof/error.rs | 45 +++++++++++ src/proof/parentful.rs | 27 +++++++ src/proof/parentless.rs | 22 ++++- src/proof/parents.rs | 14 ++++ src/proof/prove.rs | 23 +++++- src/proof/same.rs | 100 +++++++++++------------ src/time.rs | 6 +- 18 files changed, 437 insertions(+), 107 deletions(-) create mode 100644 src/ability/wasm/module.rs create mode 100644 src/ability/wasm/run.rs create mode 100644 src/proof/error.rs diff --git a/Cargo.toml b/Cargo.toml index 34115ea0..4f2a9d83 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -32,6 +32,7 @@ anyhow = "1.0.75" aquamarine = { version = "0.5", optional = true } async-signature = "0.4.0" async-trait = "0.1.73" +base64 = "0.21" blst = { version = "0.3.11", optional = true, default-features = false } cfg-if = "0.1" # FIXME attempt to remove diff --git a/src/ability/crud/any.rs b/src/ability/crud/any.rs index 6554c30f..e805705b 100644 --- a/src/ability/crud/any.rs +++ b/src/ability/crud/any.rs @@ -1,23 +1,15 @@ use crate::{ ability::command::Command, - proof::{ - parentless::NoParents, - same::{CheckSame, OptionalFieldErr}, - }, + proof::{error::OptionalFieldError, parentless::NoParents, same::CheckSame}, }; use libipld_core::{error::SerdeError, ipld::Ipld, serde as ipld_serde}; use serde::{Deserialize, Serialize}; use thiserror::Error; use url::Url; -#[cfg(target_arch = "wasm32")] -use crate::{ipld, proof::same::OptionalFieldReason}; - #[cfg(target_arch = "wasm32")] use wasm_bindgen::prelude::*; -// NOTE no resolved or awaiting variants, because this cannot be executed, and all fields are optional already! - #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] #[serde(deny_unknown_fields)] pub struct Builder { @@ -32,15 +24,10 @@ impl Command for Builder { impl NoParents for Builder {} impl CheckSame for Builder { - type Error = OptionalFieldErr; + type Error = OptionalFieldError; fn check_same(&self, proof: &Self) -> Result<(), Self::Error> { - self.uri - .check_same(&proof.uri) - .map_err(|err| OptionalFieldErr { - field: "uri".into(), - err, - }) + self.uri.check_same(&proof.uri) } } diff --git a/src/ability/ucan.rs b/src/ability/ucan.rs index 1987e8a1..0e3daaad 100644 --- a/src/ability/ucan.rs +++ b/src/ability/ucan.rs @@ -1,3 +1,4 @@ -pub mod proxy; +//! Abilities for and about UCANs themselves + pub mod revoke; -// FIXME pub mod revoke; +// FIXME pub mod proxy; diff --git a/src/ability/ucan/revoke.rs b/src/ability/ucan/revoke.rs index aa13e358..5316f80d 100644 --- a/src/ability/ucan/revoke.rs +++ b/src/ability/ucan/revoke.rs @@ -4,6 +4,7 @@ use crate::{ ability::{arguments, command::Command}, delegation::Delegatable, invocation::{Promise, Resolvable}, + proof::{parentless::NoParents, same::CheckSame}, }; use libipld_core::{cid::Cid, ipld::Ipld}; use serde::{Deserialize, Serialize}; @@ -24,6 +25,8 @@ impl Command for Generic { /// The fully resolved variant: ready to execute. pub type Ready = Generic; +impl NoParents for Ready {} + impl Delegatable for Ready { type Builder = Builder; } @@ -35,6 +38,14 @@ impl Resolvable for Ready { /// A variant with some fields waiting to be set. pub type Builder = Generic>; +impl CheckSame for Builder { + type Error = (); // FIXME + + fn check_same(&self, proof: &Self) -> Result<(), Self::Error> { + self.ucan.check_same(&proof.ucan).map_err(|_| ()) + } +} + impl From for Builder { fn from(resolved: Ready) -> Builder { Builder { @@ -63,7 +74,7 @@ impl From for arguments::Named { } } -/// A variant where arguments may be [`Promise`]s +/// A variant where arguments may be [`Promise`]s. pub type Promised = Generic>; impl From for Promised { diff --git a/src/ability/wasm.rs b/src/ability/wasm.rs index 07e8d5cd..3aa1e67b 100644 --- a/src/ability/wasm.rs +++ b/src/ability/wasm.rs @@ -1,30 +1,4 @@ -use super::command::Command; -use crate::proof::{parentless::NoParents, same::CheckSame}; -use libipld_core::{ipld::Ipld, link::Link}; +//! [WebAssembly](https://webassembly.org/) abilities -#[derive(Debug, Clone, PartialEq)] -pub struct Run { - pub module: Module, - pub function: String, - pub args: Vec, -} - -// FIXME -#[derive(Debug, Clone, PartialEq)] -pub enum Module { - Inline(Vec), - Cid(Link>), -} - -impl Command for Run { - const COMMAND: &'static str = "wasm/run"; -} - -impl NoParents for Run {} - -impl CheckSame for Run { - type Error = (); // FIXME - fn check_same(&self, _proof: &Self) -> Result<(), Self::Error> { - Ok(()) // FIXME - } -} +pub mod module; +pub mod run; diff --git a/src/ability/wasm/module.rs b/src/ability/wasm/module.rs new file mode 100644 index 00000000..7b319b69 --- /dev/null +++ b/src/ability/wasm/module.rs @@ -0,0 +1,75 @@ +//! Wasm module representations + +use base64::{display::Base64Display, engine::general_purpose::STANDARD, Engine as _}; +use libipld_core::{cid::Cid, error::SerdeError, ipld::Ipld, link::Link, serde as ipld_serde}; +use serde::{Deserialize, Serialize}; + +/// Ways to represent a Wasm module in a `wasm/run` payload. +#[derive(Debug, Clone, PartialEq)] +pub enum Module { + // FIXME serialize both as URLs + /// The raw bytes of the Wasm module + /// + /// Encodes as a `data:` URL + Inline(Vec), + + /// A link to the Wasm module + Remote(Link>), +} + +impl From for Ipld { + fn from(module: Module) -> Self { + match module { + Module::Inline(bytes) => Ipld::Bytes(bytes), + Module::Remote(cid) => Ipld::Link(*cid), + } + } +} + +impl TryFrom for Module { + type Error = SerdeError; + + fn try_from(ipld: Ipld) -> Result { + ipld_serde::from_ipld(ipld) + } +} + +impl Serialize for Module { + fn serialize(&self, serializer: S) -> Result + where + S: serde::ser::Serializer, + { + match self { + Module::Remote(link) => link.cid().serialize(serializer), + Module::Inline(bytes) => format!( + "data:application/wasm;base64,{}", + Base64Display::new(bytes.as_ref(), &STANDARD) + ) + .serialize(serializer), + } + } +} + +impl<'de> Deserialize<'de> for Module { + fn deserialize(deserializer: D) -> Result + where + D: serde::de::Deserializer<'de>, + { + let s = String::deserialize(deserializer)?; + if s.starts_with("data:") { + let data = s + .split(',') + .nth(1) + .ok_or_else(|| serde::de::Error::custom("missing base64 data"))?; + + let bytes = STANDARD + .decode(data) + .map_err(|err| serde::de::Error::custom(err))?; + + Ok(Module::Inline(bytes)) + } else { + let cid = Cid::try_from(s).map_err(serde::de::Error::custom)?; + Ok(Module::Remote(Link::new(cid))) + } + } +} diff --git a/src/ability/wasm/run.rs b/src/ability/wasm/run.rs new file mode 100644 index 00000000..e699b39c --- /dev/null +++ b/src/ability/wasm/run.rs @@ -0,0 +1,147 @@ +//! Ability to run a Wasm module + +use super::module::Module; +use crate::{ + ability::{arguments, command::Command}, + delegation::Delegatable, + invocation::{Promise, Resolvable}, + proof::{parentless::NoParents, same::CheckSame}, +}; +use libipld_core::ipld::Ipld; +use serde::{Deserialize, Serialize}; +use std::collections::BTreeMap; + +/// The ability to run a Wasm module on the subject's machine +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub struct Generic { + /// The Wasm module to run + pub module: Mod, + + /// The function from the module to run + pub function: Fun, + + /// Arguments to pass to the function + pub args: Args, +} + +impl Command for Generic { + const COMMAND: &'static str = "wasm/run"; +} + +/// A variant with all of the required fields filled in +pub type Ready = Generic>; + +impl Delegatable for Ready { + type Builder = Builder; +} + +impl Resolvable for Ready { + type Promised = Promised; +} + +/// A variant meant for delegation, where fields may be omitted +pub type Builder = Generic, Option, Option>>; + +impl NoParents for Builder {} + +impl From for arguments::Named { + fn from(builder: Builder) -> Self { + let mut btree = BTreeMap::new(); + if let Some(module) = builder.module { + btree.insert("module".into(), Ipld::from(module)); + } + + if let Some(function) = builder.function { + btree.insert("function".into(), Ipld::String(function)); + } + + if let Some(args) = builder.args { + btree.insert("args".into(), Ipld::List(args)); + } + + arguments::Named(btree) + } +} + +impl From for Builder { + fn from(ready: Ready) -> Builder { + Builder { + module: Some(ready.module), + function: Some(ready.function), + args: Some(ready.args), + } + } +} + +impl TryFrom for Ready { + type Error = (); // FIXME + + fn try_from(b: Builder) -> Result { + Ok(Ready { + module: b.module.ok_or(())?, + function: b.function.ok_or(())?, + args: b.args.ok_or(())?, + }) + } +} + +impl CheckSame for Builder { + type Error = (); // FIXME + + fn check_same(&self, proof: &Self) -> Result<(), Self::Error> { + if let Some(module) = &self.module { + if module != proof.module.as_ref().unwrap() { + return Err(()); + } + } + + if let Some(function) = &self.function { + if function != proof.function.as_ref().unwrap() { + return Err(()); + } + } + + if let Some(args) = &self.args { + if args != proof.args.as_ref().unwrap() { + return Err(()); + } + } + + Ok(()) + } +} + +/// A variant meant for linking together invocations with promises +pub type Promised = Generic, Promise, Promise>>; + +impl From for Promised { + fn from(ready: Ready) -> Self { + Promised { + module: Promise::from(ready.module), + function: Promise::from(ready.function), + args: Promise::from(ready.args), + } + } +} + +impl TryFrom for Ready { + type Error = (); // FIXME + + fn try_from(promised: Promised) -> Result { + Ok(Ready { + module: promised.module.try_resolve().map_err(|_| ())?, + function: promised.function.try_resolve().map_err(|_| ())?, + args: promised.args.try_resolve().map_err(|_| ())?, + }) + } +} + +impl From for arguments::Named { + fn from(promised: Promised) -> Self { + arguments::Named::from_iter([ + ("module".into(), promised.module.into()), + ("function".into(), promised.function.into()), + ("args".into(), promised.args.into()), + ]) + } +} diff --git a/src/delegation/delegatable.rs b/src/delegation/delegatable.rs index dcb733d9..f6368a84 100644 --- a/src/delegation/delegatable.rs +++ b/src/delegation/delegatable.rs @@ -1,7 +1,9 @@ use crate::ability::arguments; +// FIXME require checkable? pub trait Delegatable: Sized { /// A delegation with some arguments filled /// FIXME add more + /// FIXME require CheckSame? type Builder: TryInto + From + Into; } diff --git a/src/invocation/promise.rs b/src/invocation/promise.rs index f2dcfa0a..520cddda 100644 --- a/src/invocation/promise.rs +++ b/src/invocation/promise.rs @@ -11,8 +11,16 @@ use wasm_bindgen::prelude::*; #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] #[serde(untagged)] pub enum Promise { + /// The fulfilled (resolved) value. Fulfilled(T), - Pending(Selector), + + // Rejected(E) + // + /// A deferred value and its associated [`Selector`]. + /// + /// The [`Selector`] will resolve a branch from the [`Receipt`][crate::receipt::Receipt] + /// and substitute into the value. + Pending(Selector), // FIXME shodu there be FulfilledOk and FulfilledErr? If so, why cover al branches here? } #[cfg(target_arch = "wasm32")] diff --git a/src/invocation/resolvable.rs b/src/invocation/resolvable.rs index d9570d98..1ce955a3 100644 --- a/src/invocation/resolvable.rs +++ b/src/invocation/resolvable.rs @@ -3,3 +3,5 @@ use crate::ability::arguments; pub trait Resolvable: Sized { type Promised: TryInto + From + Into; } + +// NOTE Promised into args should cover all of the values diff --git a/src/proof.rs b/src/proof.rs index 122ca024..dbe2b43a 100644 --- a/src/proof.rs +++ b/src/proof.rs @@ -1,6 +1,7 @@ //! Proof chains, checking, and utilities pub mod checkable; +pub mod error; pub mod parentful; pub mod parentless; pub mod parents; diff --git a/src/proof/error.rs b/src/proof/error.rs new file mode 100644 index 00000000..6b615c8c --- /dev/null +++ b/src/proof/error.rs @@ -0,0 +1,45 @@ +//! Standatd error types for delegation checking. + +use serde::{Deserialize, Serialize}; +use thiserror::Error; + +#[cfg(target_arch = "wasm32")] +use wasm_bindgen::prelude::*; + +/// An error for when values are unequal. +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, Error)] +#[cfg_attr(target_arch = "wasm32", wasm_bindgen)] +#[error("unequal")] +pub struct Unequal {} + +/// A generic error for when two fields are unequal. +#[derive(Copy, Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Error)] +#[cfg_attr(target_arch = "wasm32", wasm_bindgen)] +pub enum OptionalFieldError { + /// A required field is missing. + /// + /// For example, when its proof has a vaue, but the target does not. + #[error("missing")] + Missing, + + /// A field is present but has a different value in its proof + #[error("unequal")] + Unequal, +} + +impl From for OptionalFieldError { + fn from(_: Unequal) -> Self { + OptionalFieldError::Unequal + } +} + +impl TryFrom for Unequal { + type Error = OptionalFieldError; + + fn try_from(e: OptionalFieldError) -> Result { + match e { + OptionalFieldError::Unequal => Ok(Unequal {}), + _ => Err(e), + } + } +} diff --git a/src/proof/parentful.rs b/src/proof/parentful.rs index 5bf611ce..340b45f4 100644 --- a/src/proof/parentful.rs +++ b/src/proof/parentful.rs @@ -1,3 +1,5 @@ +//! Utilities for working with abilties that *do* have a delegation hirarchy. + use super::{ internal::Checker, parents::CheckParents, @@ -6,18 +8,43 @@ use super::{ }; use libipld_core::{error::SerdeError, ipld::Ipld, serde as ipld_serde}; use serde::{de::DeserializeOwned, Deserialize, Serialize}; +use thiserror::Error; +/// The possible cases for an [ability][crate::ability]'s +/// [Delegation][crate::delegation::Delegation] chain when +/// it has parent abilities (a hierarchy). +/// +/// This type is generally not used directly, but rather is +/// called in the plumbing of the library. #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub enum Parentful { + /// The "top" ability (`*`) Any, + + /// All possible parents for the ability. Parents(T::Parents), + + /// The (invokable) ability itself. This(T), } +/// Error cases when checking proofs (including parents) +#[derive(Debug, Error, PartialEq)] pub enum ParentfulError { + /// The `cmd` field was more powerful than the proof. + /// + /// i.e. it behaves like moving "down" the delegation chain not "up" CommandEscelation, + + /// The `args` field was more powerful than the proof. ArgumentEscelation(ArgErr), + + /// The parents do not prove the ability. InvalidProofChain(PrfErr), + + /// Comparing parents in a delegation chain failed. + /// + /// The specific comparison error is captured in the `ParErr`. InvalidParents(ParErr), // FIXME seems kinda broken -- better naming at least } diff --git a/src/proof/parentless.rs b/src/proof/parentless.rs index 08c0e14c..390c99ac 100644 --- a/src/proof/parentless.rs +++ b/src/proof/parentless.rs @@ -1,3 +1,5 @@ +//! Utilities for working with abilties that *don't* have a delegation hirarchy +//! use super::{ checkable::Checkable, internal::Checker, @@ -8,20 +10,38 @@ use libipld_core::{error::SerdeError, ipld::Ipld, serde as ipld_serde}; use serde::{de::DeserializeOwned, Deserialize, Serialize}; use std::convert::Infallible; +/// The possible cases for an [ability][crate::ability]'s +/// [Delegation][crate::delegation::Delegation] chain when +/// it has no parent abilities (no hierarchy). +/// +/// This type is generally not used directly, but rather is +/// called in the plumbing of the library. #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub enum Parentless { + /// The "top" ability (`*`) Any, + + /// The (invokable) ability itself. This(T), } -// FIXME generally useful (e.g. checkiung `_/*`); move to its own module and rename +// FIXME generally useful (e.g. checkiung `_/*`); move to its own module and rename? +/// Error cases when checking proofs #[derive(Debug, Clone, PartialEq)] pub enum ParentlessError { + /// The `cmd` field was more powerful than the proof. + /// + /// i.e. it behaves like moving "down" the delegation chain not "up" CommandEscelation, + + /// The `args` field was more powerful than the proof ArgumentEscelation(T::Error), } // FIXME better name +/// A helper trait to indicate that a type has no parents. +/// +/// This behaves as an alias for `Checkable::>`. pub trait NoParents {} impl Checkable for T { diff --git a/src/proof/parents.rs b/src/proof/parents.rs index e947c201..5b68889d 100644 --- a/src/proof/parents.rs +++ b/src/proof/parents.rs @@ -1,8 +1,22 @@ +//! Check the parents in an ability hierarchy. + use super::same::CheckSame; +/// Check if the parents of a proof are valid. +/// +/// Note that the top ability (`*`) does not need to be handled separately, +/// as the code from [`CheckParents`] will be lifted into +/// [`Parentful`][super::parentful::Parentful], which knows +/// how to check `*`. pub trait CheckParents: CheckSame { + /// The parents of the hierarchy. + /// + /// Note that `Self` *need not* be included in [`CheckParents::Parents`]. type Parents; + + /// Error checking against [`CheckParents::Parents`]. type ParentError; + // FIXME fn check_parent(&self, proof: &Self::Parents) -> Result<(), Self::ParentError>; } diff --git a/src/proof/prove.rs b/src/proof/prove.rs index d9876dcb..34e32de0 100644 --- a/src/proof/prove.rs +++ b/src/proof/prove.rs @@ -1,9 +1,16 @@ +//! High-level proof chain checking. + use super::internal::Checker; -// FIXME is it worth locking consumers out with that Checker bound? +/// An internal trait that checks based on the other traits for an ability type. pub trait Prove { + /// The error if the argument is invalid. type ArgumentError; + + /// The error if the proof chain is invalid. type ProofChainError; + + /// The error if the parents are invalid. type ParentsError; fn check( @@ -13,11 +20,23 @@ pub trait Prove { } // FIXME that's a lot of error type params +/// The outcome of a proof check. pub enum Outcome { + /// Success Proven, + + /// Special case for success by checking against `*`. ProvenByAny, + + /// An error in the command chain. + CommandEscelation, + + /// An error in the argument chain. ArgumentEscelation(ArgErr), + + /// An error in the proof chain. InvalidProofChain(ChainErr), + + /// An error in the parents. InvalidParents(ParentErr), - CommandEscelation, } diff --git a/src/proof/same.rs b/src/proof/same.rs index 22f2e060..2e295b68 100644 --- a/src/proof/same.rs +++ b/src/proof/same.rs @@ -1,56 +1,52 @@ -use crate::did::Did; -use core::fmt; -use serde::{Deserialize, Serialize}; -use thiserror::Error; +//! Check the delegation proof against another instance of the same type -#[cfg(target_arch = "wasm32")] -use wasm_bindgen::prelude::*; +use super::error::{OptionalFieldError, Unequal}; +use crate::did::Did; +/// Trait for checking if a proof of the same type is equally or less restrictive. +/// +/// # Example +/// +/// ```rust +/// # use ucan::proof::same::CheckSame; +/// # use ucan::did::Did; +/// # +/// struct HelloBuilder { +/// wave_at: Option, +/// } +/// +/// enum HelloError { +/// MissingWaveAt, +/// WeDontTalkTo(Did) +/// } +/// +/// impl CheckSame for HelloBuilder { +/// type Error = HelloError; +/// +/// fn check_same(&self, proof: &Self) -> Result<(), Self::Error> { +/// if self.wave_at == Some(Did::try_from("did:example:mallory".to_string()).unwrap()) { +/// return Err(HelloError::WeDontTalkTo(self.wave_at.clone().unwrap())); +/// } +/// +/// if let Some(_) = &proof.wave_at { +/// if self.wave_at != proof.wave_at { +/// return Err(HelloError::MissingWaveAt); +/// } +/// } +/// +/// Ok(()) +/// } +/// } pub trait CheckSame { - type Error; + /// Error type describing why a proof was insufficient. + type Error; // FIXME Rename CheckSameError? + /// Check if the proof is equally or less restrictive than the instance. + /// + /// Delegation must always attenuate. If the proof is more restrictive than the instance, + /// it has violated the delegation chain rules. fn check_same(&self, proof: &Self) -> Result<(), Self::Error>; } - -// Genereic -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] -pub struct Unequal; - -// FIXME move under error.rs -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Error)] -#[cfg_attr(target_arch = "wasm32", wasm_bindgen(getter_with_clone))] -pub struct OptionalFieldErr { - pub field: String, - pub err: OptionalFieldReason, -} - -impl fmt::Display for OptionalFieldErr { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "Field {} is {}", self.field, self.err) - } -} - -// FIXME at minimum the name is confusing -#[derive(Copy, Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Error)] -#[cfg_attr(target_arch = "wasm32", wasm_bindgen)] -pub enum OptionalFieldReason { - MissingField, - UnequalValue, -} - -impl fmt::Display for OptionalFieldReason { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!( - f, - "{}", - match self { - OptionalFieldReason::MissingField => "missing", - OptionalFieldReason::UnequalValue => "unequal", - } - ) - } -} - impl CheckSame for Did { type Error = Unequal; @@ -58,24 +54,24 @@ impl CheckSame for Did { if self.eq(proof) { Ok(()) } else { - Err(Unequal) + Err(Unequal {}) } } } -impl CheckSame for Option { - type Error = OptionalFieldReason; +impl CheckSame for Option { + type Error = OptionalFieldError; fn check_same(&self, proof: &Self) -> Result<(), Self::Error> { match proof { None => Ok(()), Some(proof_) => match self { - None => Err(OptionalFieldReason::MissingField), + None => Err(OptionalFieldError::Missing), Some(self_) => { if self_.eq(proof_) { Ok(()) } else { - Err(OptionalFieldReason::UnequalValue) + Err(OptionalFieldError::Unequal) } } }, diff --git a/src/time.rs b/src/time.rs index 1440e1b0..cd82682f 100644 --- a/src/time.rs +++ b/src/time.rs @@ -172,15 +172,15 @@ impl<'de> Deserialize<'de> for JsTime { } } -/// An error expressing when a time is larger than 2^53 seconds past the Unix epoch +/// An error expressing when a time is larger than 2⁵³ seconds past the Unix epoch #[derive(Debug, Clone, PartialEq, Eq, Error)] pub struct OutOfRangeError { - /// The [`SystemTime`] that is outside of the [`JsTime`] range (2^53). + /// The [`SystemTime`] that is outside of the [`JsTime`] range (2⁵³). pub tried: SystemTime, } impl fmt::Display for OutOfRangeError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "time out of JsTime (2^53) range: {:?}", self.tried) + write!(f, "time out of JsTime (2⁵³) range: {:?}", self.tried) } } From c60234f61527df620c502c12d250d340de7a764e Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Thu, 8 Feb 2024 01:03:57 -0800 Subject: [PATCH 059/188] Fixin' warnings! --- src/ability/arguments.rs | 30 ++- src/ability/crud.rs | 20 +- src/ability/crud/any.rs | 42 ++-- src/ability/crud/mutate.rs | 30 ++- src/ability/crud/parents.rs | 5 +- src/ability/crud/read.rs | 7 +- src/ability/dynamic.rs | 2 +- src/ability/msg.rs | 8 +- src/ability/msg/receive.rs | 8 +- src/ability/msg/send.rs | 99 ++++---- src/ability/ucan/proxy.rs | 40 +--- src/ability/ucan/revoke.rs | 8 +- src/ability/wasm/run.rs | 11 +- src/delegation/condition.rs | 23 +- src/delegation/condition/contains_all.rs | 21 +- src/delegation/condition/contains_any.rs | 20 +- src/delegation/condition/contains_key.rs | 57 +++-- src/delegation/condition/excludes_all.rs | 66 +++++- src/delegation/condition/excludes_key.rs | 48 +++- src/delegation/condition/matches_regex.rs | 24 +- src/delegation/condition/max_length.rs | 23 +- src/delegation/condition/max_number.rs | 22 +- src/delegation/condition/min_length.rs | 23 +- src/delegation/condition/min_number.rs | 22 +- src/delegation/condition/traits.rs | 3 +- src/delegation/payload.rs | 31 ++- src/invocation.rs | 4 +- src/invocation/promise.rs | 200 ++-------------- src/invocation/promise/any.rs | 207 +++++++++++++++++ src/invocation/promise/err.rs | 86 +++++++ src/invocation/promise/js.rs | 123 ++++++++++ src/invocation/promise/ok.rs | 87 +++++++ src/invocation/promise/resolves.rs | 266 ++++++++++++++++++++++ src/ipld.rs | 6 +- src/ipld/cid.rs | 72 +++++- src/lib.rs | 1 + src/nonce.rs | 10 +- src/proof/error.rs | 2 +- src/time.rs | 11 +- src/url.rs | 47 ++++ 40 files changed, 1348 insertions(+), 467 deletions(-) create mode 100644 src/invocation/promise/any.rs create mode 100644 src/invocation/promise/err.rs create mode 100644 src/invocation/promise/js.rs create mode 100644 src/invocation/promise/ok.rs create mode 100644 src/invocation/promise/resolves.rs create mode 100644 src/url.rs diff --git a/src/ability/arguments.rs b/src/ability/arguments.rs index a8f81fe0..b8314428 100644 --- a/src/ability/arguments.rs +++ b/src/ability/arguments.rs @@ -21,37 +21,42 @@ use crate::ipld; /// # Examples /// /// ```rust -/// # use ucan::ability::arguments::Named; +/// # use ucan::ability::arguments; /// # use url::Url; /// # use libipld::ipld; /// # /// struct Execute { /// program: Url, -/// args: Named, +/// instructions: arguments::Named, /// } /// /// let ability = Execute { /// program: Url::parse("file://host.name/path/to/exe").unwrap(), -/// args: Named::try_from(ipld!({ -/// "bold": true, -/// "message": "hello world", -/// })).unwrap() +/// instructions: arguments::Named::from_iter([ +/// ("bold".into(), ipld!(true)), +/// ("message".into(), ipld!("hello world")), +/// ]) /// }; /// -/// assert_eq!(ability.args.get("bold"), Some(&ipld!(true))); +/// assert_eq!(ability.instructions.get("bold"), Some(&ipld!(true))); /// ``` #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] pub struct Named(pub BTreeMap); impl Named { - /// Get the value associated with a key + /// Create a new, empty `Named` instance. + pub fn new() -> Self { + Default::default() + } + + /// Get the value associated with a key. /// /// An alias for [`BTreeMap::insert`]. pub fn get(&self, key: &str) -> Option<&Ipld> { self.0.get(key) } - /// Inserts a key-value pair + /// Inserts a key-value pair. /// /// An alias for [`BTreeMap::insert`]. pub fn insert(&mut self, key: String, value: Ipld) -> Option { @@ -64,6 +69,13 @@ impl Named { pub fn iter(&self) -> impl Iterator { self.0.iter() } + + /// The number of entries in. + /// + /// A wrapper around [`BTreeMap::len`]. + pub fn len(&self) -> usize { + self.0.len() + } } impl Default for Named { diff --git a/src/ability/crud.rs b/src/ability/crud.rs index dda6aa59..58f8845f 100644 --- a/src/ability/crud.rs +++ b/src/ability/crud.rs @@ -1,7 +1,15 @@ -pub mod any; -pub mod create; -pub mod destroy; -pub mod mutate; +mod any; +mod create; +mod destroy; +mod mutate; +mod read; +mod update; + pub mod parents; -pub mod read; -pub mod update; + +pub use any::Any; +pub use create::Create; +pub use destroy::Destroy; +pub use mutate::Mutate; +pub use read::Read; +pub use update::Update; diff --git a/src/ability/crud/any.rs b/src/ability/crud/any.rs index e805705b..8fda3432 100644 --- a/src/ability/crud/any.rs +++ b/src/ability/crud/any.rs @@ -1,5 +1,6 @@ use crate::{ ability::command::Command, + ipld, proof::{error::OptionalFieldError, parentless::NoParents, same::CheckSame}, }; use libipld_core::{error::SerdeError, ipld::Ipld, serde as ipld_serde}; @@ -12,18 +13,18 @@ use wasm_bindgen::prelude::*; #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] #[serde(deny_unknown_fields)] -pub struct Builder { +pub struct Any { #[serde(default, skip_serializing_if = "Option::is_none")] pub uri: Option, } -impl Command for Builder { +impl Command for Any { const COMMAND: &'static str = "crud/*"; } -impl NoParents for Builder {} +impl NoParents for Any {} -impl CheckSame for Builder { +impl CheckSame for Any { type Error = OptionalFieldError; fn check_same(&self, proof: &Self) -> Result<(), Self::Error> { @@ -31,7 +32,7 @@ impl CheckSame for Builder { } } -impl TryFrom for Builder { +impl TryFrom for Any { type Error = SerdeError; fn try_from(ipld: Ipld) -> Result { @@ -39,22 +40,15 @@ impl TryFrom for Builder { } } -impl From for Ipld { - fn from(builder: Builder) -> Self { +impl From for Ipld { + fn from(builder: Any) -> Self { builder.into() } } -// FIXME -#[derive(Debug, Error)] -pub enum E { - #[error("Some error")] - SomeErrMsg(String), -} - #[cfg(target_arch = "wasm32")] #[wasm_bindgen] -pub struct CrudAny(#[wasm_bindgen(skip)] pub Builder); +pub struct CrudAny(#[wasm_bindgen(skip)] pub Any); // FIXME macro this away #[cfg(target_arch = "wasm32")] @@ -69,16 +63,20 @@ impl CrudAny { } pub fn command(&self) -> String { - Builder::COMMAND.to_string() + Any::COMMAND.to_string() } pub fn check_same(&self, proof: &CrudAny) -> Result<(), JsError> { - self.0.check_same(&proof.0).map_err(|_| { - OptionalFieldErr { - field: "uri".into(), - err: OptionalFieldReason::MissingField, + if self.uri.is_some() { + if self.uri != proof.uri { + return Err(OptionalFieldError { + field: "uri".into(), + err: OptionalFieldReason::NotEqual, + } + .into()); } - .into() - }) + } + + Ok(()) } } diff --git a/src/ability/crud/mutate.rs b/src/ability/crud/mutate.rs index 0e5590ec..8bc3a869 100644 --- a/src/ability/crud/mutate.rs +++ b/src/ability/crud/mutate.rs @@ -1,52 +1,50 @@ -use super::any; use crate::{ ability::command::Command, proof::{checkable::Checkable, parentful::Parentful, parents::CheckParents, same::CheckSame}, }; -use libipld_core::{ipld::Ipld, serde as ipld_serde}; +use libipld_core::{error::SerdeError, ipld::Ipld, serde as ipld_serde}; use serde::{Deserialize, Serialize}; use url::Url; #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] #[serde(deny_unknown_fields)] -pub struct MutateBuilder { +pub struct Mutate { #[serde(default, skip_serializing_if = "Option::is_none")] pub uri: Option, } -impl Command for MutateBuilder { +impl Command for Mutate { const COMMAND: &'static str = "crud/mutate"; } -impl From for Ipld { - fn from(mutate: MutateBuilder) -> Self { +impl From for Ipld { + fn from(mutate: Mutate) -> Self { mutate.into() } } -impl TryFrom for MutateBuilder { - type Error = (); // FIXME +impl TryFrom for Mutate { + type Error = SerdeError; fn try_from(ipld: Ipld) -> Result { - ipld_serde::from_ipld(ipld).map_err(|_| ()) + ipld_serde::from_ipld(ipld) } } -impl Checkable for MutateBuilder { - type Hierarchy = Parentful; +impl Checkable for Mutate { + type Hierarchy = Parentful; } -impl CheckSame for MutateBuilder { +impl CheckSame for Mutate { type Error = (); fn check_same(&self, _proof: &Self) -> Result<(), Self::Error> { Ok(()) } } -// TODO note to self, this is effectively a partial order -impl CheckParents for MutateBuilder { - type Parents = any::Builder; - type ParentError = (); +impl CheckParents for Mutate { + type Parents = super::Any; + type ParentError = (); // FIXME fn check_parent(&self, _proof: &Self::Parents) -> Result<(), Self::ParentError> { Ok(()) diff --git a/src/ability/crud/parents.rs b/src/ability/crud/parents.rs index bd03b9e8..902e28e7 100644 --- a/src/ability/crud/parents.rs +++ b/src/ability/crud/parents.rs @@ -1,12 +1,11 @@ -use super::{any, mutate::MutateBuilder}; use crate::proof::same::CheckSame; use serde::{Deserialize, Serialize}; #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] #[serde(deny_unknown_fields)] pub enum Mutable { - Mutate(MutateBuilder), - Any(any::Builder), + Mutate(super::Mutate), + Any(super::Any), } impl CheckSame for Mutable { diff --git a/src/ability/crud/read.rs b/src/ability/crud/read.rs index 3d8c409c..1a42083d 100644 --- a/src/ability/crud/read.rs +++ b/src/ability/crud/read.rs @@ -1,6 +1,5 @@ -use super::any; use crate::{ - ability::{arguments, command::Command}, + ability::{arguments, command::Command, crud::any::CrudAny}, proof::{checkable::Checkable, parentful::Parentful, parents::CheckParents, same::CheckSame}, }; use libipld_core::{error::SerdeError, ipld::Ipld, serde as ipld_serde}; @@ -78,7 +77,7 @@ impl CheckSame for Read { } impl CheckParents for Read { - type Parents = any::Builder; + type Parents = super::Any; type ParentError = E; fn check_parent(&self, _other: &Self::Parents) -> Result<(), Self::ParentError> { @@ -109,7 +108,7 @@ impl CrudRead { self.0.check_same(&proof.0).map_err(Into::into) } - pub fn check_parent(&self, proof: &any::CrudAny) -> Result<(), JsError> { + pub fn check_parent(&self, proof: &CrudAny) -> Result<(), JsError> { self.0.check_parent(&proof.0).map_err(Into::into) } } diff --git a/src/ability/dynamic.rs b/src/ability/dynamic.rs index b08c51c8..1db05871 100644 --- a/src/ability/dynamic.rs +++ b/src/ability/dynamic.rs @@ -103,7 +103,7 @@ impl CheckSame for Dynamic { } self.args.0.iter().try_for_each(|(k, v)| { - if let Some(proof_v) = proof.args.0.get(k) { + if let Some(proof_v) = proof.args.get(k) { if v != proof_v { return Err("arguments::Named mismatch".into()); } diff --git a/src/ability/msg.rs b/src/ability/msg.rs index 0f5bdd0f..708d2bf6 100644 --- a/src/ability/msg.rs +++ b/src/ability/msg.rs @@ -1,5 +1,9 @@ //! Message abilities -pub mod any; -pub mod receive; +mod any; +mod receive; + pub mod send; + +pub use any::Any; +pub use receive::Receive; diff --git a/src/ability/msg/receive.rs b/src/ability/msg/receive.rs index 07c86aea..9282d6fd 100644 --- a/src/ability/msg/receive.rs +++ b/src/ability/msg/receive.rs @@ -8,8 +8,6 @@ use libipld_core::{error::SerdeError, ipld::Ipld, serde as ipld_serde}; use serde::{Deserialize, Serialize}; use url::Url; -use super::any as msg; - #[cfg_attr(doc, aquamarine::aquamarine)] /// The ability to receive messages /// @@ -62,16 +60,14 @@ impl CheckSame for Receive { } impl CheckParents for Receive { - type Parents = msg::Any; - type ParentError = ::Error; + type Parents = super::Any; + type ParentError = ::Error; fn check_parent(&self, proof: &Self::Parents) -> Result<(), Self::ParentError> { self.from.check_same(&proof.from).map_err(|_| ()) } } -//////////// - impl From for Ipld { fn from(receive: Receive) -> Self { receive.into() diff --git a/src/ability/msg/send.rs b/src/ability/msg/send.rs index 63c31ca6..c15341f4 100644 --- a/src/ability/msg/send.rs +++ b/src/ability/msg/send.rs @@ -3,16 +3,19 @@ use crate::{ ability::{arguments, command::Command}, delegation::Delegatable, - invocation::{Promise, Resolvable}, + invocation::{promise, Resolvable}, proof::{checkable::Checkable, parentful::Parentful, parents::CheckParents, same::CheckSame}, + url as url_newtype, }; use libipld_core::{error::SerdeError, ipld::Ipld, serde as ipld_serde}; use serde::{de::DeserializeOwned, Deserialize, Serialize}; use std::collections::BTreeMap; use url::Url; -use super::any as msg; - +/// Helper for creating instances of `msg/send` with the correct shape. +/// +/// This is not generally used directly, unless you want to abstract +/// over all of the `msg/send` variants. #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] #[serde(deny_unknown_fields)] pub struct Generic { @@ -49,14 +52,14 @@ pub struct Generic { /// end /// /// sendpromise("msg::send::Promised") -/// sendrun("msg::send::Resolved") +/// sendrun("msg::send::Ready") /// /// top --> any /// any --> send -.->|invoke| sendpromise -.->|resolve| sendrun -.-> exe{{execute}} /// /// style sendrun stroke:orange; /// ``` -pub type Resolved = Generic; +pub type Ready = Generic; #[cfg_attr(doc, aquamarine::aquamarine)] /// The delegatable variant of the `msg/send` ability. @@ -108,20 +111,21 @@ pub type Builder = Generic, Option, Option>; /// end /// /// sendpromise("msg::send::Promised") -/// sendrun("msg::send::Resolved") +/// sendrun("msg::send::Ready") /// /// top --> any /// any --> send -.->|invoke| sendpromise -.->|resolve| sendrun -.-> exe{{execute}} /// /// style sendpromise stroke:orange; /// ``` -pub type Promised = Generic, Promise, Promise>; +pub type Promised = + Generic, promise::Resolves, promise::Resolves>; -impl Delegatable for Resolved { +impl Delegatable for Ready { type Builder = Builder; } -impl Resolvable for Resolved { +impl Resolvable for Ready { type Promised = Promised; } @@ -139,21 +143,21 @@ impl From for arguments::Named { } impl From for arguments::Named { - fn from(promised: Promised) -> Self { + fn from(p: Promised) -> Self { arguments::Named(BTreeMap::from_iter([ - ("to".into(), promised.to.map(String::from).into()), - ("from".into(), promised.from.map(String::from).into()), - ("message".into(), promised.message.into()), + ("to".into(), p.to.map(url_newtype::Newtype).into()), + ("from".into(), p.from.map(String::from).into()), + ("message".into(), p.message.into()), ])) } } impl From for Builder { - fn from(awaiting: Promised) -> Self { + fn from(p: Promised) -> Self { Builder { - to: awaiting.to.try_resolve().ok(), - from: awaiting.from.try_resolve().ok(), - message: awaiting.message.try_resolve().ok(), + to: p.to.into(), + from: p.from.into(), + message: p.message.into(), } } } @@ -168,6 +172,7 @@ impl Checkable for Builder { impl CheckSame for Builder { type Error = (); // FIXME better error + fn check_same(&self, proof: &Self) -> Result<(), Self::Error> { self.to.check_same(&proof.to).map_err(|_| ())?; self.from.check_same(&proof.from).map_err(|_| ())?; @@ -176,17 +181,16 @@ impl CheckSame for Builder { } impl CheckParents for Builder { - type Parents = msg::Any; - type ParentError = ::Error; + type Parents = super::Any; + type ParentError = ::Error; - // FIXME rename other to proof - fn check_parent(&self, other: &Self::Parents) -> Result<(), Self::ParentError> { - self.from.check_same(&other.from).map_err(|_| ()) + fn check_parent(&self, proof: &Self::Parents) -> Result<(), Self::ParentError> { + self.from.check_same(&proof.from).map_err(|_| ()) } } -impl From for Builder { - fn from(resolved: Resolved) -> Self { +impl From for Builder { + fn from(resolved: Ready) -> Self { Generic { to: resolved.to.into(), from: resolved.from.into(), @@ -195,36 +199,41 @@ impl From for Builder { } } -impl From for Promised { - fn from(resolved: Resolved) -> Self { - Generic { - to: resolved.to.into(), - from: resolved.from.into(), - message: resolved.message.into(), +impl From for Promised { + fn from(r: Ready) -> Self { + Promised { + to: promise::Resolves::from(Ok(r.to)), + from: promise::Resolves::from(Ok(r.from)), + message: promise::Resolves::from(Ok(r.message)), } } } -impl TryFrom for Resolved { - type Error = (); +impl TryFrom for Ready { + type Error = Promised; - fn try_from(awaiting: Promised) -> Result { - Ok(Generic { - to: awaiting.to.try_resolve().map_err(|_| ())?, - from: awaiting.from.try_resolve().map_err(|_| ())?, - message: awaiting.message.try_resolve().map_err(|_| ())?, - }) + fn try_from(p: Promised) -> Result { + match promise::Resolves::try_resolve_3(p.to, p.from, p.message) { + Ok((to, from, message)) => Ok(Ready { to, from, message }), + Err((to, from, message)) => Err(Promised { to, from, message }), + } } } -impl TryFrom for Resolved { - type Error = (); +impl TryFrom for Ready { + type Error = Builder; + + fn try_from(b: Builder) -> Result { + // Entirely by refernce + if b.to.is_none() || b.from.is_none() || b.message.is_none() { + return Err(b); + } - fn try_from(builder: Builder) -> Result { - Ok(Generic { - to: builder.to.ok_or(())?, - from: builder.from.ok_or(())?, - message: builder.message.ok_or(())?, + // Moves, and unwrap because we checked above instead of 2 clones per line + Ok(Ready { + to: b.to.unwrap(), + from: b.from.unwrap(), + message: b.message.unwrap(), }) } } diff --git a/src/ability/ucan/proxy.rs b/src/ability/ucan/proxy.rs index da916a9c..04eb5c33 100644 --- a/src/ability/ucan/proxy.rs +++ b/src/ability/ucan/proxy.rs @@ -18,7 +18,7 @@ pub struct Generic { // FIXME should args just be a CID } -pub type Resolved = Generic; +pub type Ready = Generic; pub type Builder = Generic>; pub type Promised = Generic>; @@ -26,12 +26,12 @@ impl Command for Generic { const COMMAND: &'static str = "ucan/proxy"; } -impl Delegatable for Resolved { +impl Delegatable for Ready { type Builder = Builder; } -impl From for Builder { - fn from(resolved: Resolved) -> Builder { +impl From for Builder { + fn from(resolved: Ready) -> Builder { Builder { cmd: resolved.cmd, args: Some(resolved.args), @@ -39,11 +39,11 @@ impl From for Builder { } } -impl TryFrom for Resolved { +impl TryFrom for Ready { type Error = (); // FIXME fn try_from(b: Builder) -> Result { - Ok(Resolved { + Ok(Ready { cmd: b.cmd, args: b.args.ok_or(())?, }) @@ -57,31 +57,3 @@ impl From for arguments::Named { args } } - -// // FIXME hmmm need to decide on the exact shape of this -// #[derive(Debug, Clone, PartialEq)] -// pub struct ProxyExecuteBuilder { -// pub command: Option, -// pub args: BTreeMap, -// } -// -// -// impl From for ProxyExecuteBuilder { -// fn from(proxy: ProxyExecute) -> Self { -// ProxyExecuteBuilder { -// command: Some(ProxyExecute::COMMAND.into()), -// args: proxy.args.clone(), -// } -// } -// } -// -// impl TryFrom for ProxyExecute { -// type Error = (); // FIXME -// -// fn try_from(ProxyExecuteBuilder { command, args }: ProxyExecuteBuilder) -> Result { -// match command { -// None => Err(()), -// Some(command) => Ok(Self { command, args }), -// } -// } -// } diff --git a/src/ability/ucan/revoke.rs b/src/ability/ucan/revoke.rs index 5316f80d..07b1df31 100644 --- a/src/ability/ucan/revoke.rs +++ b/src/ability/ucan/revoke.rs @@ -3,10 +3,10 @@ use crate::{ ability::{arguments, command::Command}, delegation::Delegatable, - invocation::{Promise, Resolvable}, + invocation::{promise, Resolvable}, proof::{parentless::NoParents, same::CheckSame}, }; -use libipld_core::{cid::Cid, ipld::Ipld}; +use libipld_core::cid::Cid; use serde::{Deserialize, Serialize}; use std::{collections::BTreeMap, fmt::Debug}; @@ -75,12 +75,12 @@ impl From for arguments::Named { } /// A variant where arguments may be [`Promise`]s. -pub type Promised = Generic>; +pub type Promised = Generic>; impl From for Promised { fn from(r: Ready) -> Promised { Promised { - ucan: Promise::Fulfilled(r.ucan), + ucan: Ok(r.ucan).into(), } } } diff --git a/src/ability/wasm/run.rs b/src/ability/wasm/run.rs index e699b39c..a6a8cdcc 100644 --- a/src/ability/wasm/run.rs +++ b/src/ability/wasm/run.rs @@ -4,7 +4,7 @@ use super::module::Module; use crate::{ ability::{arguments, command::Command}, delegation::Delegatable, - invocation::{Promise, Resolvable}, + invocation::{promise, Resolvable}, proof::{parentless::NoParents, same::CheckSame}, }; use libipld_core::ipld::Ipld; @@ -112,14 +112,15 @@ impl CheckSame for Builder { } /// A variant meant for linking together invocations with promises -pub type Promised = Generic, Promise, Promise>>; +pub type Promised = + Generic, promise::Resolves, promise::Resolves>>; impl From for Promised { fn from(ready: Ready) -> Self { Promised { - module: Promise::from(ready.module), - function: Promise::from(ready.function), - args: Promise::from(ready.args), + module: promise::Resolves::from(Ok(ready.module)), + function: promise::Resolves::from(Ok(ready.function)), + args: promise::Resolves::from(Ok(ready.args)), } } } diff --git a/src/delegation/condition.rs b/src/delegation/condition.rs index 3b3b0c7f..42048dc2 100644 --- a/src/delegation/condition.rs +++ b/src/delegation/condition.rs @@ -10,6 +10,7 @@ pub mod min_length; pub mod min_number; pub mod traits; +use crate::ability::arguments; use libipld_core::{error::SerdeError, ipld::Ipld, serde as ipld_serde}; use serde_derive::{Deserialize, Serialize}; use traits::Condition; @@ -44,18 +45,18 @@ impl TryFrom for Common { } impl Condition for Common { - fn validate(&self, ipld: &Ipld) -> bool { + fn validate(&self, args: &arguments::Named) -> bool { match self { - Common::ContainsAll(c) => c.validate(ipld), - Common::ContainsAny(c) => c.validate(ipld), - Common::ContainsKey(c) => c.validate(ipld), - Common::ExcludesKey(c) => c.validate(ipld), - Common::ExcludesAll(c) => c.validate(ipld), - Common::MinLength(c) => c.validate(ipld), - Common::MaxLength(c) => c.validate(ipld), - Common::MinNumber(c) => c.validate(ipld), - Common::MaxNumber(c) => c.validate(ipld), - Common::MatchesRegex(c) => c.validate(ipld), + Common::ContainsAll(c) => c.validate(args), + Common::ContainsAny(c) => c.validate(args), + Common::ContainsKey(c) => c.validate(args), + Common::ExcludesKey(c) => c.validate(args), + Common::ExcludesAll(c) => c.validate(args), + Common::MinLength(c) => c.validate(args), + Common::MaxLength(c) => c.validate(args), + Common::MinNumber(c) => c.validate(args), + Common::MaxNumber(c) => c.validate(args), + Common::MatchesRegex(c) => c.validate(args), } } } diff --git a/src/delegation/condition/contains_all.rs b/src/delegation/condition/contains_all.rs index 57d71297..b7a8aca8 100644 --- a/src/delegation/condition/contains_all.rs +++ b/src/delegation/condition/contains_all.rs @@ -1,12 +1,21 @@ +//! A [`Condition`] for ensuring a field contains all of a set of values. + use super::traits::Condition; +use crate::ability::arguments; use libipld_core::{error::SerdeError, ipld::Ipld, serde as ipld_serde}; use serde_derive::{Deserialize, Serialize}; +/// A condition for ensuring a field contains all of a set of values. +/// +/// This works on lists and maps. Maps will check the values, not keys. #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(deny_unknown_fields)] pub struct ContainsAll { - field: String, - contains_all: Vec, + /// Name of the field to check + pub field: String, + + /// The elements that must be present + pub contains_all: Vec, } impl From for Ipld { @@ -24,10 +33,10 @@ impl TryFrom for ContainsAll { } impl Condition for ContainsAll { - fn validate(&self, ipld: &Ipld) -> bool { - match ipld { - Ipld::List(array) => self.contains_all.iter().all(|ipld| array.contains(ipld)), - Ipld::Map(btree) => { + fn validate(&self, args: &arguments::Named) -> bool { + match args.get(&self.field) { + Some(Ipld::List(array)) => self.contains_all.iter().all(|ipld| array.contains(ipld)), + Some(Ipld::Map(btree)) => { let vals: Vec<&Ipld> = btree.values().collect(); self.contains_all.iter().all(|ipld| vals.contains(&ipld)) } diff --git a/src/delegation/condition/contains_any.rs b/src/delegation/condition/contains_any.rs index dde1212b..5f730a03 100644 --- a/src/delegation/condition/contains_any.rs +++ b/src/delegation/condition/contains_any.rs @@ -1,12 +1,20 @@ +//! A [`Condition`] for ensuring a field contains some of a set of values. use super::traits::Condition; +use crate::ability::arguments; use libipld_core::{error::SerdeError, ipld::Ipld, serde as ipld_serde}; use serde_derive::{Deserialize, Serialize}; +/// A [`Condition`] for ensuring a field contains one or more of a set of values. +/// +/// This works on lists and maps. Maps will check the values, not keys. #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(deny_unknown_fields)] pub struct ContainsAny { - field: String, - contains_any: Vec, + /// Name of the field to check. + pub field: String, + + /// The elements that must be present. + pub contains_any: Vec, } impl From for Ipld { @@ -24,10 +32,10 @@ impl TryFrom for ContainsAny { } impl Condition for ContainsAny { - fn validate(&self, ipld: &Ipld) -> bool { - match ipld { - Ipld::List(array) => array.iter().any(|ipld| self.contains_any.contains(ipld)), - Ipld::Map(btree) => { + fn validate(&self, args: &arguments::Named) -> bool { + match args.get(&self.field) { + Some(Ipld::List(array)) => array.iter().any(|ipld| self.contains_any.contains(ipld)), + Some(Ipld::Map(btree)) => { let vals: Vec<&Ipld> = btree.values().collect(); self.contains_any.iter().any(|ipld| vals.contains(&ipld)) } diff --git a/src/delegation/condition/contains_key.rs b/src/delegation/condition/contains_key.rs index 098237db..0a4ff86d 100644 --- a/src/delegation/condition/contains_key.rs +++ b/src/delegation/condition/contains_key.rs @@ -1,15 +1,48 @@ +//! A [`Condition`] for ensuring a map contains a key. use super::traits::Condition; +use crate::ability::arguments; use libipld_core::{error::SerdeError, ipld::Ipld, serde as ipld_serde}; use serde_derive::{Deserialize, Serialize}; +/// A [`Condition`] for ensuring a map contains a key. +/// +/// Note that this operates on a key inside the args, not the args themselves. +/// The shape of an [`ability`][crate::ability] is pretermined, so further +/// constraining the top-level argument keys is not necessary. +/// +/// # Examples +/// +/// ```rust +/// # use ucan::delegation::{contains_key::ContainsKey, traits::Condition}; +/// # use libipld::ipld; +/// # +/// let args = ipld!({"a": {"b": 1, "c": 2}, "d": {"e": 3}}).try_into().unwrap(); +/// let cond = ContainsKey{ +/// field: "a".into(), +/// key: "b".into() +/// }; +/// +/// assert!(cond.validate(&args)); +/// +/// // Fails when the key is not present +/// assert!(!ContainsKey { +/// field: "nope".into(), +/// key: "b".into() +/// }.validate(&args)); +/// +/// // Also fails when the input is not a map +/// let list = ipld!({"a": [1, 2, 3]}).try_into().unwrap(); +/// assert!(!cond.validate(&list)); +/// assert!(!cond.validate(&ipld!({"a": 42}).try_into().unwrap())); +/// ``` #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(deny_unknown_fields)] pub struct ContainsKey { - field: String, - contains_key: String, + /// Name of the field to check. + pub field: String, - #[serde(default, skip_serializing_if = "Option::is_none")] - with_value: Option, + /// The elements that must be present. + pub key: String, } impl From for Ipld { @@ -27,19 +60,9 @@ impl TryFrom for ContainsKey { } impl Condition for ContainsKey { - fn validate(&self, ipld: &Ipld) -> bool { - match ipld { - Ipld::Map(map) => { - if let Some(value) = map.get(&self.field) { - if let Some(with_value) = &self.with_value { - value == with_value - } else { - true - } - } else { - false - } - } + fn validate(&self, args: &arguments::Named) -> bool { + match args.get(&self.field) { + Some(Ipld::Map(map)) => map.contains_key(&self.key), _ => false, } } diff --git a/src/delegation/condition/excludes_all.rs b/src/delegation/condition/excludes_all.rs index 2e94ca3e..ece60444 100644 --- a/src/delegation/condition/excludes_all.rs +++ b/src/delegation/condition/excludes_all.rs @@ -1,12 +1,49 @@ +//! A [`Condition`] for ensuring a field contains none of a set of values. use super::traits::Condition; +use crate::ability::arguments; use libipld_core::{error::SerdeError, ipld::Ipld, serde as ipld_serde}; use serde_derive::{Deserialize, Serialize}; +/// A [`Condition`] for ensuring a field contains none of a set of values. +/// +/// This works on all [`Ipld`] types. For lists and maps, it checks the values, not keys. +/// For the rest, it checks the value itself. +/// +/// # Examples +/// +/// ```rust +/// # use ucan::delegation::{excludes_all::ExcludesAll, traits::Condition}; +/// # use libipld::ipld; +/// # +/// let args = ipld!({"a": [1, "b", 3.14], "b": 4}).try_into().unwrap(); +/// let cond = ExcludesAll { +/// field: "a".into(), +/// excludes_all: vec![ipld!(2), ipld!("a")] +/// }; +/// +/// assert!(cond.validate(&args)); +/// +/// // Fails when the values of a map match +/// assert!(!cond.validate(&ipld!({"a": {"b": 2}}).try_into().unwrap())); +/// +/// // Succeeds when the key is not present +/// assert!(ExcludesAll { +/// field: "nope".into(), +/// excludes_all: vec![ipld!(1), ipld!("b")] +/// }.validate(&args)); +/// +/// // Also checks non-maps/non-lists +/// assert!(!cond.validate(&ipld!({"a": 2}).try_into().unwrap())); +/// assert!(cond.validate(&ipld!({"a": "hello world"}).try_into().unwrap())); +/// ``` #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(deny_unknown_fields)] pub struct ExcludesAll { - field: String, - excludes_all: Vec, + /// Name of the field to check. + pub field: String, + + /// The elements that must not be present. + pub excludes_all: Vec, } impl From for Ipld { @@ -24,14 +61,25 @@ impl TryFrom for ExcludesAll { } impl Condition for ExcludesAll { - fn validate(&self, ipld: &Ipld) -> bool { - match ipld { - Ipld::List(array) => self.excludes_all.iter().all(|ipld| !array.contains(ipld)), - Ipld::Map(btree) => { - let vals: Vec<&Ipld> = btree.values().collect(); - self.excludes_all.iter().all(|ipld| !vals.contains(&ipld)) + fn validate(&self, args: &arguments::Named) -> bool { + if let Some(ipld) = args.get(&self.field) { + let mut it = self.excludes_all.iter(); + match ipld { + Ipld::Null => it.all(|x| x != ipld), + Ipld::Bool(_) => it.all(|x| x != ipld), + Ipld::Float(_) => it.all(|x| x != ipld), + Ipld::Integer(_) => it.all(|x| x != ipld), + Ipld::Bytes(_) => it.all(|x| x != ipld), + Ipld::String(_) => it.all(|x| x != ipld), + Ipld::Link(_) => it.all(|x| x != ipld), + Ipld::List(array) => it.all(|x| !array.contains(x)), + Ipld::Map(btree) => { + let vals: Vec<&Ipld> = btree.values().collect(); + it.all(|x| !vals.contains(&x)) + } } - _ => false, + } else { + true } } } diff --git a/src/delegation/condition/excludes_key.rs b/src/delegation/condition/excludes_key.rs index b12ef89e..c2beba10 100644 --- a/src/delegation/condition/excludes_key.rs +++ b/src/delegation/condition/excludes_key.rs @@ -1,12 +1,48 @@ +//! A [`Condition`] for ensuring a field contains none of a set of keys. use super::traits::Condition; +use crate::ability::arguments; use libipld_core::{error::SerdeError, ipld::Ipld, serde as ipld_serde}; use serde_derive::{Deserialize, Serialize}; +/// A [`Condition`] for ensuring a map excludes a key. +/// +/// Note that this operates on a key inside the args, not the args themselves. +/// The shape of an [`ability`][crate::ability] is pretermined, so further +/// constraining the top-level argument keys is not necessary. +/// +/// # Examples +/// +/// ```rust +/// # use ucan::delegation::{excludes_key::ExcludesKey, traits::Condition}; +/// # use libipld::ipld; +/// # +/// let args = ipld!({"a": {"b": 1, "c": 2}, "d": {"e": 3}}).try_into().unwrap(); +/// let cond = ExcludesKey{ +/// field: "a".into(), +/// key: "b".into() +/// }; +/// +/// assert!(!cond.validate(&args)); +/// +/// // Succeeds when the key is not present +/// assert!(ExcludesKey { +/// field: "yep".into(), +/// key: "b".into() +/// }.validate(&args)); +/// +/// // Also succeeds when the input is not a map +/// let list = ipld!({"a": [1, 2, 3]}).try_into().unwrap(); +/// assert!(cond.validate(&list)); +/// assert!(cond.validate(&ipld!({"a": 42}).try_into().unwrap())); +/// ``` #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(deny_unknown_fields)] pub struct ExcludesKey { - field: String, - excludes_key: String, + /// Name of the field to check. + pub field: String, + + /// The key that must not be present. + pub key: String, } impl From for Ipld { @@ -24,10 +60,10 @@ impl TryFrom for ExcludesKey { } impl Condition for ExcludesKey { - fn validate(&self, ipld: &Ipld) -> bool { - match ipld { - Ipld::Map(map) => map.get(&self.field).is_none(), - _ => false, + fn validate(&self, args: &arguments::Named) -> bool { + match args.get(&self.field) { + Some(Ipld::Map(map)) => map.contains_key(&self.field), + _ => true, } } } diff --git a/src/delegation/condition/matches_regex.rs b/src/delegation/condition/matches_regex.rs index 02c0f2e4..f6f9f4d6 100644 --- a/src/delegation/condition/matches_regex.rs +++ b/src/delegation/condition/matches_regex.rs @@ -1,13 +1,21 @@ +//! A regular expression [`Condition`]. use super::traits::Condition; +use crate::ability::arguments; use libipld_core::{error::SerdeError, ipld::Ipld, serde as ipld_serde}; use regex::Regex; use serde_derive::{Deserialize, Serialize}; +/// A regular expression [`Condition`] +/// +/// This checks a string against a regular expression. #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] #[serde(deny_unknown_fields)] pub struct MatchesRegex { - field: String, - matches_regex: Matcher, + /// Name of the field to check + pub field: String, + + /// The minimum length + pub matches_regex: Matcher, } impl From for Ipld { @@ -25,14 +33,15 @@ impl TryFrom for MatchesRegex { } impl Condition for MatchesRegex { - fn validate(&self, ipld: &Ipld) -> bool { - match ipld { - Ipld::String(string) => self.matches_regex.0.is_match(string), + fn validate(&self, args: &arguments::Named) -> bool { + match args.get(&self.field) { + Some(Ipld::String(string)) => self.matches_regex.0.is_match(string), _ => false, } } } +/// A newtype wrapper around [`Regex`] #[derive(Debug, Clone)] pub struct Matcher(Regex); @@ -61,10 +70,7 @@ impl<'de> serde::Deserialize<'de> for Matcher { let s: &str = serde::Deserialize::deserialize(deserializer)?; match Regex::new(s) { Ok(regex) => Ok(Matcher(regex)), - Err(_) => { - // FIXME - todo!() - } + Err(_) => Err(serde::de::Error::custom(format!("Invalid regex: {}", s))), } } } diff --git a/src/delegation/condition/max_length.rs b/src/delegation/condition/max_length.rs index 13fe157c..7bb4ad97 100644 --- a/src/delegation/condition/max_length.rs +++ b/src/delegation/condition/max_length.rs @@ -1,12 +1,21 @@ +//! A max length [`Condition`]. use super::traits::Condition; +use crate::ability::arguments; use libipld_core::{error::SerdeError, ipld::Ipld, serde as ipld_serde}; use serde_derive::{Deserialize, Serialize}; +/// A maximum length [`Condition`] +/// +/// A condition that checks if the length of a string, list, +/// or map is less than or equal to a set size. #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(deny_unknown_fields)] pub struct MaxLength { - field: String, - max_length: usize, + /// Name of the field to check + pub field: String, + + /// The maximum length + pub max_length: usize, } impl From for Ipld { @@ -24,11 +33,11 @@ impl TryFrom for MaxLength { } impl Condition for MaxLength { - fn validate(&self, ipld: &Ipld) -> bool { - match ipld { - Ipld::String(string) => string.len() <= self.max_length, - Ipld::List(list) => list.len() <= self.max_length, - Ipld::Map(map) => map.len() <= self.max_length, + fn validate(&self, args: &arguments::Named) -> bool { + match args.get(&self.field) { + Some(Ipld::String(string)) => string.len() <= self.max_length, + Some(Ipld::List(list)) => list.len() <= self.max_length, + Some(Ipld::Map(map)) => map.len() <= self.max_length, _ => false, } } diff --git a/src/delegation/condition/max_number.rs b/src/delegation/condition/max_number.rs index 46327ee0..81d90374 100644 --- a/src/delegation/condition/max_number.rs +++ b/src/delegation/condition/max_number.rs @@ -1,13 +1,21 @@ +//! A max number [`Condition`]. use super::traits::Condition; -use crate::number::Number; +use crate::{ability::arguments, number::Number}; use libipld_core::{error::SerdeError, ipld::Ipld, serde as ipld_serde}; use serde_derive::{Deserialize, Serialize}; +/// A maximum number [`Condition`] +/// +/// A condition that checks if the length of an integer +/// or float is less than or equal to a set size. #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(deny_unknown_fields)] pub struct MaxNumber { - field: String, - max_number: Number, + /// Name of the field to check + pub field: String, + + /// The maximum number + pub max_number: Number, } impl From for Ipld { @@ -25,13 +33,13 @@ impl TryFrom for MaxNumber { } impl Condition for MaxNumber { - fn validate(&self, ipld: &Ipld) -> bool { - match ipld { - Ipld::Integer(integer) => match self.max_number { + fn validate(&self, args: &arguments::Named) -> bool { + match args.get(&self.field) { + Some(Ipld::Integer(integer)) => match self.max_number { Number::Float(float) => *integer as f64 <= float, Number::Integer(integer) => integer <= integer, }, - Ipld::Float(float) => match self.max_number { + Some(Ipld::Float(float)) => match self.max_number { Number::Float(float) => float <= float, Number::Integer(integer) => *float <= integer as f64, // FIXME this needs tests }, diff --git a/src/delegation/condition/min_length.rs b/src/delegation/condition/min_length.rs index 85dbe1c9..60f4c4ed 100644 --- a/src/delegation/condition/min_length.rs +++ b/src/delegation/condition/min_length.rs @@ -1,12 +1,21 @@ +//! A min length [`Condition`]. use super::traits::Condition; +use crate::ability::arguments; use libipld_core::{error::SerdeError, ipld::Ipld, serde as ipld_serde}; use serde_derive::{Deserialize, Serialize}; +/// A mimimum length [`Condition`] +/// +/// This checks if the length of a string, list, +/// or map is greater than or equal to a set size. #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(deny_unknown_fields)] pub struct MinLength { - field: String, - min_length: usize, + /// Name of the field to check + pub field: String, + + /// The minimum length + pub min_length: usize, } impl From for Ipld { @@ -24,11 +33,11 @@ impl TryFrom for MinLength { } impl Condition for MinLength { - fn validate(&self, ipld: &Ipld) -> bool { - match ipld { - Ipld::String(string) => string.len() >= self.min_length, - Ipld::List(list) => list.len() >= self.min_length, - Ipld::Map(map) => map.len() >= self.min_length, + fn validate(&self, args: &arguments::Named) -> bool { + match args.get(&self.field) { + Some(Ipld::String(string)) => string.len() >= self.min_length, + Some(Ipld::List(list)) => list.len() >= self.min_length, + Some(Ipld::Map(map)) => map.len() >= self.min_length, _ => false, } } diff --git a/src/delegation/condition/min_number.rs b/src/delegation/condition/min_number.rs index a19ef7c3..7b22a968 100644 --- a/src/delegation/condition/min_number.rs +++ b/src/delegation/condition/min_number.rs @@ -1,13 +1,21 @@ +//! A min number [`Condition`]. use super::traits::Condition; -use crate::number::Number; +use crate::{ability::arguments, number::Number}; use libipld_core::{error::SerdeError, ipld::Ipld, serde as ipld_serde}; use serde_derive::{Deserialize, Serialize}; +/// A minimum number [`Condition`] +/// +/// A condition that checks if the length of an integer +/// or float is less than or equal to a set size. #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(deny_unknown_fields)] pub struct MinNumber { - field: String, - min_number: Number, + /// Name of the field to check + pub field: String, + + /// The minimum number + pub min_number: Number, } impl From for Ipld { @@ -25,13 +33,13 @@ impl TryFrom for MinNumber { } impl Condition for MinNumber { - fn validate(&self, ipld: &Ipld) -> bool { - match ipld { - Ipld::Integer(integer) => match self.min_number { + fn validate(&self, args: &arguments::Named) -> bool { + match args.get(&self.field) { + Some(Ipld::Integer(integer)) => match self.min_number { Number::Float(float) => *integer as f64 >= float, Number::Integer(integer) => integer >= integer, }, - Ipld::Float(float) => match self.min_number { + Some(Ipld::Float(float)) => match self.min_number { Number::Float(float) => float >= float, Number::Integer(integer) => *float >= integer as f64, // FIXME this needs tests }, diff --git a/src/delegation/condition/traits.rs b/src/delegation/condition/traits.rs index f56edc60..1d9b977c 100644 --- a/src/delegation/condition/traits.rs +++ b/src/delegation/condition/traits.rs @@ -1,5 +1,6 @@ +use crate::ability::arguments; use libipld_core::ipld::Ipld; pub trait Condition: TryFrom + Into { - fn validate(&self, ipld: &Ipld) -> bool; + fn validate(&self, args: &arguments::Named) -> bool; } diff --git a/src/delegation/payload.rs b/src/delegation/payload.rs index c23371c7..a2c89546 100644 --- a/src/delegation/payload.rs +++ b/src/delegation/payload.rs @@ -89,7 +89,12 @@ impl From> for Ipld { } // FIXME this likely should move to invocation -impl<'a, T: Delegatable + Resolvable + Checkable + Clone, C: Condition> Payload { +impl< + 'a, + T: Delegatable + Resolvable + Checkable + Clone + Into, + C: Condition, + > Payload +{ pub fn check( invoked: &'a invocation::Payload, // FIXME promisory version proofs: Vec>, @@ -106,11 +111,11 @@ impl<'a, T: Delegatable + Resolvable + Checkable + Clone, C: Condition> Payload< to_check: invoked.clone().into(), // FIXME surely we can eliminate this clone }; - let ipld: Ipld = invoked.clone().into(); + let args: arguments::Named = invoked.ability.clone().into(); let result = proofs.iter().fold(Ok(start), |acc, proof| { if let Ok(prev) = acc { - match step(&prev, proof, &ipld, now) { + match step(&prev, proof, &args, now) { Outcome::ArgumentEscelation(_) => Err(()), Outcome::InvalidProofChain(_) => Err(()), Outcome::InvalidParents(_) => Err(()), @@ -151,7 +156,7 @@ struct Acc<'a, T: Checkable> { fn step<'a, T: Checkable, U: Delegatable, C: Condition>( prev: &Acc<'a, T>, proof: &Payload, - invoked_ipld: &Ipld, + args: &arguments::Named, now: SystemTime, ) -> Outcome<(), (), ()> // FIXME ^^^^^^^^^^^^ Outcome types @@ -182,13 +187,17 @@ where // return Err(()); // } - let cond_result = proof.conditions.iter().try_fold((), |_acc, c| { - if c.validate(&invoked_ipld) { - Ok(()) - } else { - Err(()) - } - }); + let cond_result = + proof.conditions.iter().try_fold( + (), + |_acc, c| { + if c.validate(&args) { + Ok(()) + } else { + Err(()) + } + }, + ); if let Err(_) = cond_result { return Outcome::InvalidProofChain(()); diff --git a/src/invocation.rs b/src/invocation.rs index 993fd5d3..d217ca07 100644 --- a/src/invocation.rs +++ b/src/invocation.rs @@ -1,10 +1,10 @@ mod payload; -mod promise; mod resolvable; mod serializer; +pub mod promise; + pub use payload::{Payload, Unresolved}; -pub use promise::Promise; pub use resolvable::Resolvable; use crate::signature; diff --git a/src/invocation/promise.rs b/src/invocation/promise.rs index 520cddda..c72bc76c 100644 --- a/src/invocation/promise.rs +++ b/src/invocation/promise.rs @@ -1,187 +1,31 @@ -use crate::ability::arguments; -use libipld_core::{cid::Cid, ipld::Ipld, serde as ipld_serde}; -use serde_derive::{Deserialize, Serialize}; -use std::{collections::BTreeMap, fmt::Debug}; +//! [UCAN Promise](https://github.com/ucan-wg/promise) -#[cfg(target_arch = "wasm32")] -use wasm_bindgen::prelude::*; +// FIXME put entire module behind feature flag -// FIXME move under invocation? +mod any; +mod err; +mod ok; +mod resolves; -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] -#[serde(untagged)] -pub enum Promise { - /// The fulfilled (resolved) value. - Fulfilled(T), - - // Rejected(E) - // - /// A deferred value and its associated [`Selector`]. - /// - /// The [`Selector`] will resolve a branch from the [`Receipt`][crate::receipt::Receipt] - /// and substitute into the value. - Pending(Selector), // FIXME shodu there be FulfilledOk and FulfilledErr? If so, why cover al branches here? -} - -#[cfg(target_arch = "wasm32")] -#[derive(Clone, Debug, PartialEq, Eq)] -#[wasm_bindgen] -pub enum UcanPromiseStatus { - Fulfilled, - Pending, -} - -// FIXME no way to make this consistent, because of C enums ruining Rust convetions, right? -// FIXME consider wrapping in a trait -#[cfg(target_arch = "wasm32")] -#[derive(Clone, Debug, PartialEq)] -#[wasm_bindgen] -pub struct UcanPromise { - status: UcanPromiseStatus, - selector: Option, - value: Option, -} - -#[cfg(target_arch = "wasm32")] -#[wasm_bindgen(getter_with_clone)] -pub struct IncoherentPromise(pub UcanPromise); - -#[cfg(target_arch = "wasm32")] -impl TryFrom for Promise { - type Error = IncoherentPromise; - - fn try_from(js: UcanPromise) -> Result { - match js.status { - UcanPromiseStatus::Fulfilled => { - if let Some(val) = &js.value { - return Ok(Promise::Fulfilled(val.clone())); - } - } - UcanPromiseStatus::Pending => { - if let Some(selector) = &js.selector { - return Ok(Promise::Pending(selector.clone())); - } - } - } - - Err(IncoherentPromise(js)) - } -} - -#[cfg(target_arch = "wasm32")] -impl> From> for UcanPromise { - fn from(promise: Promise) -> Self { - match promise { - Promise::Fulfilled(val) => UcanPromise { - status: UcanPromiseStatus::Fulfilled, - selector: None, - value: Some(val.into()), - }, - Promise::Pending(cid) => UcanPromise { - status: UcanPromiseStatus::Pending, - selector: Some(cid), - value: None, - }, - } - } -} - -impl Promise { - pub fn map(self, f: F) -> Promise - where - F: FnOnce(T) -> U, - { - match self { - Promise::Fulfilled(t) => Promise::Fulfilled(f(t)), - Promise::Pending(selector) => Promise::Pending(selector), - } - } -} - -impl> From> for arguments::Named { - fn from(promise: Promise) -> Self { - match promise { - Promise::Fulfilled(t) => t.into(), - Promise::Pending(selector) => selector.into(), - } - } -} - -impl From for Promise { - fn from(value: T) -> Self { - Promise::Fulfilled(value) - } -} +pub mod js; -impl Promise { - pub fn try_resolve(self) -> Result { - match self { - Promise::Fulfilled(t) => Ok(t), - Promise::Pending(selector) => Err(selector), - } - } -} +pub use any::PromiseAny; +pub use err::PromiseErr; +pub use ok::PromiseOk; +pub use resolves::Resolves; -impl From> for Ipld -where - T: Into, -{ - fn from(promise: Promise) -> Self { - match promise { - Promise::Fulfilled(t) => t.into(), - Promise::Pending(selector) => selector.into(), - } - } -} +use serde::{Deserialize, Serialize}; -/// A [`Promise`] is a way to defer the presence of a value to the result of some [`Invocation`]. -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] -#[serde(untagged, deny_unknown_fields)] // FIXME check that this is right, also -pub enum Selector { - Any { - #[serde(rename = "ucan/*")] // FIXME test to make sure that this is right? - any: Cid, - }, - Ok { - #[serde(rename = "ucan/ok")] - ok: Cid, - }, - Err { - #[serde(rename = "ucan/err")] - err: Cid, - }, -} - -impl From for Ipld { - fn from(selector: Selector) -> Self { - selector.into() - } -} - -impl TryFrom for Selector { - type Error = (); - - fn try_from(ipld: Ipld) -> Result { - ipld_serde::from_ipld(ipld).map_err(|_| ()) - } -} - -impl From for arguments::Named { - fn from(selector: Selector) -> Self { - let mut btree = BTreeMap::new(); +/// Top-level union of all UCAN Promise options +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +#[serde(untagged)] +pub enum Promise { + /// The `await/ok` promise + Ok(PromiseOk), - match selector { - Selector::Any { any } => { - btree.insert("ucan/*".into(), any.into()); - } - Selector::Ok { ok } => { - btree.insert("ucan/ok".into(), ok.into()); - } - Selector::Err { err } => { - btree.insert("ucan/err".into(), err.into()); - } - } + /// The `await/err` promise + Err(PromiseErr), - arguments::Named(btree) - } + /// The `await/*` promise + Any(PromiseAny), } diff --git a/src/invocation/promise/any.rs b/src/invocation/promise/any.rs new file mode 100644 index 00000000..3ec42b1b --- /dev/null +++ b/src/invocation/promise/any.rs @@ -0,0 +1,207 @@ +use super::{err::PromiseErr, ok::PromiseOk}; +use crate::{ability::arguments, ipld::cid}; +use libipld_core::{cid::Cid, error::SerdeError, ipld::Ipld, serde as ipld_serde}; +use serde::{ + de::{DeserializeSeed, Deserializer, Error, Expected, IgnoredAny, MapAccess, Visitor}, + Deserialize, Serialize, Serializer, +}; +use std::fmt; + +#[derive(Debug, Clone, PartialEq, Eq, Serialize)] +#[serde(untagged)] +pub enum PromiseAny { + /// The fulfilled (resolved) value. + Fulfilled(#[serde(rename = "ucan/ok")] T), + + /// The failure state of a promise. + Rejected(#[serde(rename = "ucan/err")] E), + + /// A deferred value and its associated [`Selector`]. + /// + /// The [`Selector`] will resolve a branch from the [`Receipt`][crate::receipt::Receipt] + /// and substitute into the value. + Pending(#[serde(rename = "await/*")] Cid), +} + +impl<'de, T: Deserialize<'de>, E: Deserialize<'de>> Deserialize<'de> for PromiseAny { + fn deserialize(deserializer: D) -> Result, D::Error> + where + D: Deserializer<'de>, + { + struct PromiseAnyVisitor(std::marker::PhantomData<(T, E)>); + + impl<'de, T: Deserialize<'de>, E: Deserialize<'de>> Visitor<'de> for PromiseAnyVisitor { + type Value = PromiseAny; + + fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { + formatter.write_str("a promise") + } + + fn visit_map(self, mut map: M) -> Result, M::Error> + where + M: MapAccess<'de>, + { + if map.size_hint() != Some(1) { + return Err(serde::de::Error::custom("expected a single key")); + } + + let key = map + .next_key::()? + .ok_or(Error::invalid_length(0, &"expected exactly 1 key"))?; + + match key.as_str() { + "ucan/ok" => { + let val = map.next_value()?; + return Ok(PromiseAny::Fulfilled(val)); + } + + "ucan/err" => { + let err = map.next_value()?; + return Ok(PromiseAny::Rejected(err)); + } + + "await/*" => { + let cid = map.next_value()?; + return Ok(PromiseAny::Pending(cid)); + } + + _ => return Err(serde::de::Error::custom("expected a valid PromiseAny")), + } + } + } + + deserializer.deserialize_map(PromiseAnyVisitor(std::marker::PhantomData)) + } +} + +impl PromiseAny { + pub fn map(self, f: F) -> PromiseAny + where + F: FnOnce(T) -> U, + { + match self { + PromiseAny::Fulfilled(val) => PromiseAny::Fulfilled(f(val)), + PromiseAny::Rejected(err) => PromiseAny::Rejected(err), + PromiseAny::Pending(cid) => PromiseAny::Pending(cid), + } + } + + pub fn map_err(self, f: F) -> PromiseAny + where + F: FnOnce(E) -> X, + { + match self { + PromiseAny::Fulfilled(val) => PromiseAny::Fulfilled(val), + PromiseAny::Rejected(err) => PromiseAny::Rejected(f(err)), + PromiseAny::Pending(cid) => PromiseAny::Pending(cid), + } + } +} + +impl From> for Ipld { + fn from(p: PromiseAny) -> Ipld { + p.into() + } +} + +impl TryFrom for PromiseAny +where + T: for<'de> Deserialize<'de>, + E: for<'de> Deserialize<'de>, +{ + type Error = SerdeError; + + fn try_from(ipld: Ipld) -> Result, Self::Error> { + ipld_serde::from_ipld(ipld) + } +} + +impl From> for arguments::Named +where + Ipld: From + From, +{ + fn from(p: PromiseAny) -> arguments::Named { + match p { + PromiseAny::Fulfilled(val) => { + arguments::Named::from_iter([("ucan/ok".into(), Ipld::from(val))]) + } + PromiseAny::Rejected(err) => { + arguments::Named::from_iter([("ucan/err".into(), Ipld::from(err))]) + } + PromiseAny::Pending(cid) => { + arguments::Named::from_iter([("await/*".into(), Ipld::from(cid))]) + } + } + } +} + +impl TryFrom<&'a Ipld>, E: for<'a> TryFrom<&'a Ipld>> TryFrom + for PromiseAny +{ + type Error = (); // FIXME + + fn try_from(args: arguments::Named) -> Result, Self::Error> { + if args.len() != 1 { + return Err(()); + } + + if let Some(ipld) = args.get("ucan/ok") { + return Ok(PromiseAny::Fulfilled(ipld.try_into().map_err(|_| ())?)); + } + + if let Some(ipld) = args.get("ucan/err") { + return Ok(PromiseAny::Rejected(ipld.try_into().map_err(|_| ())?)); + } + + if let Some(ipld) = args.get("await/*") { + return match cid::Newtype::try_from(ipld) { + Ok(nt) => Ok(PromiseAny::Pending(nt.cid)), + Err(_) => Err(()), + }; + } + + Err(()) + } +} + +impl From> for PromiseAny { + fn from(p_ok: PromiseOk) -> PromiseAny { + match p_ok { + PromiseOk::Fulfilled(val) => PromiseAny::Fulfilled(val), + PromiseOk::Pending(cid) => PromiseAny::Pending(cid), + } + } +} + +impl From> for PromiseAny { + fn from(p_err: PromiseErr) -> PromiseAny { + match p_err { + PromiseErr::Rejected(err) => PromiseAny::Rejected(err), + PromiseErr::Pending(cid) => PromiseAny::Pending(cid), + } + } +} + +impl TryFrom> for PromiseOk { + type Error = (); // FIXME + + fn try_from(p_any: PromiseAny) -> Result, Self::Error> { + match p_any { + PromiseAny::Fulfilled(val) => Ok(PromiseOk::Fulfilled(val)), + PromiseAny::Rejected(err) => Err(()), + PromiseAny::Pending(cid) => Ok(PromiseOk::Pending(cid)), + } + } +} + +impl TryFrom> for PromiseErr { + type Error = (); // FIXME + + fn try_from(p_any: PromiseAny) -> Result, Self::Error> { + match p_any { + PromiseAny::Fulfilled(val) => Err(()), + PromiseAny::Rejected(err) => Ok(PromiseErr::Rejected(err)), + PromiseAny::Pending(cid) => Ok(PromiseErr::Pending(cid)), + } + } +} diff --git a/src/invocation/promise/err.rs b/src/invocation/promise/err.rs new file mode 100644 index 00000000..f29acfa2 --- /dev/null +++ b/src/invocation/promise/err.rs @@ -0,0 +1,86 @@ +use crate::{ability::arguments, ipld::cid}; +use libipld_core::{cid::Cid, error::SerdeError, ipld::Ipld, serde as ipld_serde}; +use serde::{de::DeserializeOwned, Deserialize, Serialize}; +use std::fmt::Debug; + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[serde(untagged)] +pub enum PromiseErr { + /// The failure state of a promise. + Rejected(E), + + /// The [`Cid`] that is being waited on to return an `{"err": value}` + Pending(#[serde(rename = "await/err")] Cid), +} + +impl PromiseErr { + pub fn try_resolve(self) -> Result> { + match self { + PromiseErr::Rejected(err) => Ok(err), + PromiseErr::Pending(_cid) => Err(self), + } + } + + pub fn map(self, f: F) -> PromiseErr + where + F: FnOnce(E) -> X, + { + match self { + PromiseErr::Rejected(err) => PromiseErr::Rejected(f(err)), + PromiseErr::Pending(cid) => PromiseErr::Pending(cid), + } + } +} + +impl From> for Option { + fn from(p: PromiseErr) -> Option { + match p { + PromiseErr::Rejected(err) => Some(err), + PromiseErr::Pending(_) => None, + } + } +} + +impl From> for Ipld { + fn from(p: PromiseErr) -> Ipld { + p.into() + } +} + +impl TryFrom for PromiseErr { + type Error = SerdeError; + + fn try_from(ipld: Ipld) -> Result, Self::Error> { + ipld_serde::from_ipld(ipld) + } +} + +impl> From> for arguments::Named +where + Ipld: From, +{ + fn from(p: PromiseErr) -> arguments::Named { + match p { + PromiseErr::Rejected(err) => err.into(), + PromiseErr::Pending(cid) => { + arguments::Named::from_iter([("await/err".into(), Ipld::Link(cid))]) + } + } + } +} + +impl> TryFrom for PromiseErr { + type Error = >::Error; + + fn try_from(args: arguments::Named) -> Result, Self::Error> { + if let Some(ipld) = args.get("ucan/err") { + if args.len() == 1 { + if let Ok(cid::Newtype { cid }) = cid::Newtype::try_from(ipld) { + return Ok(PromiseErr::Pending(cid)); + } + } + } + + E::try_from(Ipld::from(args)).map(PromiseErr::Rejected) + } +} diff --git a/src/invocation/promise/js.rs b/src/invocation/promise/js.rs new file mode 100644 index 00000000..9f8b9069 --- /dev/null +++ b/src/invocation/promise/js.rs @@ -0,0 +1,123 @@ +use crate::ability::arguments; +use libipld_core::{cid::Cid, error::SerdeError, ipld::Ipld, serde as ipld_serde}; +use serde_derive::{Deserialize, Serialize}; +use std::{collections::BTreeMap, fmt::Debug}; + +// FIXME +// #[cfg(target_arch = "wasm32")] +// use wasm_bindgen::prelude::*; +// +// #[cfg(target_arch = "wasm32")] +// #[derive(Clone, Debug, PartialEq, Eq)] +// #[wasm_bindgen] +// pub enum UcanPromiseStatus { +// Fulfilled, +// Pending, +// } +// +// // FIXME no way to make this consistent, because of C enums ruining Rust convetions, right? +// // FIXME consider wrapping in a trait +// #[cfg(target_arch = "wasm32")] +// #[derive(Clone, Debug, PartialEq)] +// #[wasm_bindgen] +// pub struct UcanPromise { +// status: UcanPromiseStatus, +// selector: Option, +// value: Option, +// } +// +// #[cfg(target_arch = "wasm32")] +// #[wasm_bindgen(getter_with_clone)] +// pub struct IncoherentPromise(pub UcanPromise); +// +// #[cfg(target_arch = "wasm32")] +// impl TryFrom for Promise { +// type Error = IncoherentPromise; +// +// fn try_from(js: UcanPromise) -> Result { +// match js.status { +// UcanPromiseStatus::Fulfilled => { +// if let Some(val) = &js.value { +// return Ok(Promise::Fulfilled(val.clone())); +// } +// } +// UcanPromiseStatus::Pending => { +// if let Some(selector) = &js.selector { +// return Ok(Promise::Pending(selector.clone())); +// } +// } +// } +// +// Err(IncoherentPromise(js)) +// } +// } +// +// #[cfg(target_arch = "wasm32")] +// impl> From> for UcanPromise { +// fn from(promise: Promise) -> Self { +// match promise { +// Promise::Fulfilled(val) => UcanPromise { +// status: UcanPromiseStatus::Fulfilled, +// selector: None, +// value: Some(val.into()), +// }, +// Promise::Pending(cid) => UcanPromise { +// status: UcanPromiseStatus::Pending, +// selector: Some(cid), +// value: None, +// }, +// } +// } +// } +// +// /// A [`Promise`] is a way to defer the presence of a value to the result of some [`Invocation`]. +// #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +// #[serde(untagged, deny_unknown_fields)] // FIXME check that this is right, also +// pub enum Selector { +// Any { +// #[serde(rename = "ucan/*")] // FIXME test to make sure that this is right? +// any: Cid, +// }, +// Ok { +// #[serde(rename = "await/ok")] +// ok: Cid, +// }, +// Err { +// #[serde(rename = "await/err")] +// err: Cid, +// }, +// } +// +// impl From for Ipld { +// fn from(selector: Selector) -> Self { +// selector.into() +// } +// } +// +// impl TryFrom for Selector { +// type Error = (); +// +// fn try_from(ipld: Ipld) -> Result { +// ipld_serde::from_ipld(ipld).map_err(|_| ()) +// } +// } +// +// impl From for arguments::Named { +// fn from(selector: Selector) -> Self { +// let mut btree = BTreeMap::new(); +// +// match selector { +// Selector::Any { any } => { +// btree.insert("ucan/*".into(), any.into()); +// } +// Selector::Ok { ok } => { +// btree.insert("await/ok".into(), ok.into()); +// } +// Selector::Err { err } => { +// btree.insert("await/err".into(), err.into()); +// } +// } +// +// arguments::Named(btree) +// } +// } diff --git a/src/invocation/promise/ok.rs b/src/invocation/promise/ok.rs new file mode 100644 index 00000000..f9dfb2ca --- /dev/null +++ b/src/invocation/promise/ok.rs @@ -0,0 +1,87 @@ +use crate::{ability::arguments, ipld::cid}; +use libipld_core::{cid::Cid, error::SerdeError, ipld::Ipld, serde as ipld_serde}; +use serde::{de::DeserializeOwned, Deserialize, Serialize}; +use std::fmt::Debug; +use thiserror::Error; + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[serde(untagged, deny_unknown_fields)] +pub enum PromiseOk { + /// The fulfilled (resolved) value. + Fulfilled(T), + + /// The [`Cid`] that is being waited on to return an `{"ok": value}` + Pending(#[serde(rename = "await/ok")] Cid), +} + +impl PromiseOk { + pub fn try_resolve(self) -> Result> { + match self { + PromiseOk::Fulfilled(value) => Ok(value), + PromiseOk::Pending(_cid) => Err(self), + } + } + + pub fn map(self, f: F) -> PromiseOk + where + F: FnOnce(T) -> U, + { + match self { + PromiseOk::Fulfilled(val) => PromiseOk::Fulfilled(f(val)), + PromiseOk::Pending(cid) => PromiseOk::Pending(cid), + } + } +} + +impl From> for Option { + fn from(p: PromiseOk) -> Option { + match p { + PromiseOk::Fulfilled(value) => Some(value), + PromiseOk::Pending(_) => None, + } + } +} + +impl From> for Ipld { + fn from(p: PromiseOk) -> Ipld { + p.into() + } +} + +impl TryFrom for PromiseOk { + type Error = SerdeError; + + fn try_from(ipld: Ipld) -> Result, Self::Error> { + ipld_serde::from_ipld(ipld) + } +} + +impl> From> for arguments::Named +where + Ipld: From, +{ + fn from(p: PromiseOk) -> arguments::Named { + match p { + PromiseOk::Fulfilled(val) => val.into(), + PromiseOk::Pending(cid) => { + arguments::Named::from_iter([("await/ok".into(), Ipld::Link(cid))]) + } + } + } +} + +impl> TryFrom for PromiseOk { + type Error = >::Error; + + fn try_from(args: arguments::Named) -> Result, Self::Error> { + if let Some(ipld) = args.get("ucan/ok") { + if args.len() == 1 { + if let Ok(cid::Newtype { cid }) = cid::Newtype::try_from(ipld) { + return Ok(PromiseOk::Pending(cid)); + } + } + } + + T::try_from(Ipld::from(args)).map(PromiseOk::Fulfilled) + } +} diff --git a/src/invocation/promise/resolves.rs b/src/invocation/promise/resolves.rs new file mode 100644 index 00000000..8343e524 --- /dev/null +++ b/src/invocation/promise/resolves.rs @@ -0,0 +1,266 @@ +use super::{PromiseAny, PromiseErr, PromiseOk}; +use libipld_core::ipld::Ipld; +use serde::{Deserialize, Serialize}; +use std::fmt; + +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub enum Resolves { + Ok(PromiseOk), + Err(PromiseErr), +} + +impl Resolves { + pub fn try_resolve(self) -> Result> { + match self { + Resolves::Ok(p_ok) => p_ok.try_resolve().map_err(Resolves::Ok), + Resolves::Err(p_err) => p_err.try_resolve().map_err(Resolves::Err), + } + } + + // FIXME replace with variadic macro? + // FIXME docs + pub fn try_resolve_2( + t: Resolves, + u: Resolves, + ) -> Result<(T, U), (Resolves, Resolves)> + where + T: fmt::Debug, + U: fmt::Debug, + { + if t.is_ready() && u.is_ready() { + Ok((t.try_resolve().unwrap(), u.try_resolve().unwrap())) + } else { + Err((t, u)) + } + } + + pub fn try_resolve_3( + t: Resolves, + u: Resolves, + v: Resolves, + ) -> Result<(T, U, V), (Resolves, Resolves, Resolves)> + where + T: fmt::Debug, + U: fmt::Debug, + V: fmt::Debug, + { + if t.is_ready() && u.is_ready() && v.is_ready() { + Ok(( + t.try_resolve().unwrap(), + u.try_resolve().unwrap(), + v.try_resolve().unwrap(), + )) + } else { + Err((t, u, v)) + } + } + + pub fn try_resolve_4( + t: Resolves, + u: Resolves, + v: Resolves, + w: Resolves, + ) -> Result<(T, U, V, W), (Resolves, Resolves, Resolves, Resolves)> + where + T: fmt::Debug, + U: fmt::Debug, + V: fmt::Debug, + W: fmt::Debug, + { + if t.is_ready() && u.is_ready() && v.is_ready() && w.is_ready() { + Ok(( + t.try_resolve().unwrap(), + u.try_resolve().unwrap(), + v.try_resolve().unwrap(), + w.try_resolve().unwrap(), + )) + } else { + Err((t, u, v, w)) + } + } + + pub fn try_resolve_5( + t: Resolves, + u: Resolves, + v: Resolves, + w: Resolves, + x: Resolves, + ) -> Result< + (T, U, V, W, X), + ( + Resolves, + Resolves, + Resolves, + Resolves, + Resolves, + ), + > + where + T: fmt::Debug, + U: fmt::Debug, + V: fmt::Debug, + W: fmt::Debug, + X: fmt::Debug, + { + if t.is_ready() && u.is_ready() && v.is_ready() && w.is_ready() && x.is_ready() { + Ok(( + t.try_resolve().unwrap(), + u.try_resolve().unwrap(), + v.try_resolve().unwrap(), + w.try_resolve().unwrap(), + x.try_resolve().unwrap(), + )) + } else { + Err((t, u, v, w, x)) + } + } + + pub fn try_resolve_6( + t: Resolves, + u: Resolves, + v: Resolves, + w: Resolves, + x: Resolves, + y: Resolves, + ) -> Result< + (T, U, V, W, X, Y), + ( + Resolves, + Resolves, + Resolves, + Resolves, + Resolves, + Resolves, + ), + > + where + T: fmt::Debug, + U: fmt::Debug, + V: fmt::Debug, + W: fmt::Debug, + X: fmt::Debug, + Y: fmt::Debug, + { + if [ + t.is_ready(), + u.is_ready(), + v.is_ready(), + w.is_ready(), + x.is_ready(), + y.is_ready(), + ] + .iter() + .all(|x| *x) + { + Ok(( + t.try_resolve().unwrap(), + u.try_resolve().unwrap(), + v.try_resolve().unwrap(), + w.try_resolve().unwrap(), + x.try_resolve().unwrap(), + y.try_resolve().unwrap(), + )) + } else { + Err((t, u, v, w, x, y)) + } + } + + pub fn is_ready(&self) -> bool { + match self { + Resolves::Ok(p_ok) => match p_ok { + PromiseOk::Fulfilled(_) => true, + _ => false, + }, + Resolves::Err(p_err) => match p_err { + PromiseErr::Rejected(_) => true, + _ => false, + }, + } + } + + pub fn map(self, f: F) -> Resolves + where + F: FnOnce(T) -> U, + { + match self { + Resolves::Ok(p_ok) => Resolves::Ok(p_ok.map(f)), + Resolves::Err(p_err) => Resolves::Err(p_err.map(f)), + } + } +} + +impl From> for Option { + fn from(r: Resolves) -> Option { + match r { + Resolves::Ok(p_ok) => p_ok.into(), + Resolves::Err(p_err) => p_err.into(), + } + } +} + +impl From> for Resolves { + fn from(result: Result) -> Self { + match result { + Ok(value) => Resolves::Ok(PromiseOk::Fulfilled(value)), + Err(value) => Resolves::Err(PromiseErr::Rejected(value)), + } + } +} + +impl> From> for Ipld { + fn from(resolves: Resolves) -> Ipld { + match resolves { + Resolves::Ok(p_ok) => p_ok.into(), + Resolves::Err(p_err) => p_err.into(), + } + } +} + +impl From> for Resolves { + fn from(ok: PromiseOk) -> Self { + Resolves::Ok(ok) + } +} + +impl From> for Resolves { + fn from(err: PromiseErr) -> Self { + Resolves::Err(err) + } +} + +impl TryFrom> for PromiseOk { + type Error = PromiseErr; + + fn try_from(resolved: Resolves) -> Result { + match resolved { + Resolves::Ok(ok) => Ok(ok), + Resolves::Err(err) => Err(err), + } + } +} + +impl TryFrom> for PromiseErr { + type Error = PromiseOk; + + fn try_from(resolved: Resolves) -> Result { + match resolved { + Resolves::Ok(ok) => Err(ok), + Resolves::Err(err) => Ok(err), + } + } +} + +impl From> for PromiseAny { + fn from(resolve: Resolves) -> PromiseAny { + match resolve { + Resolves::Ok(p_ok) => match p_ok { + PromiseOk::Fulfilled(value) => PromiseAny::Fulfilled(value), + PromiseOk::Pending(cid) => PromiseAny::Pending(cid), + }, + Resolves::Err(p_err) => match p_err { + PromiseErr::Rejected(err) => PromiseAny::Rejected(err), + PromiseErr::Pending(cid) => PromiseAny::Pending(cid), + }, + } + } +} diff --git a/src/ipld.rs b/src/ipld.rs index 34a3e1e1..11e55325 100644 --- a/src/ipld.rs +++ b/src/ipld.rs @@ -3,6 +3,7 @@ pub mod cid; use libipld_core::ipld::Ipld; +use serde::{Deserialize, Serialize}; #[cfg(target_arch = "wasm32")] use wasm_bindgen::prelude::*; @@ -34,7 +35,8 @@ use js_sys::{Array, Map, Object, Uint8Array}; /// # /// assert_eq!(wrapped.0, ipld); /// ``` -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +#[serde(transparent)] pub struct Newtype(pub Ipld); impl From for Newtype { @@ -164,7 +166,7 @@ impl TryFrom for Newtype { } // NOTE *must* come before `is_object` (which is hopefully below) - if let Ok(nt) = cid::Newtype::try_from(&js_val) { + if let Ok(nt) = cid::Newtype::try_from_js_value(&js_val) { return Ok(Newtype(Ipld::Link(nt.into()))); } diff --git a/src/ipld/cid.rs b/src/ipld/cid.rs index b8765f86..791cc16b 100644 --- a/src/ipld/cid.rs +++ b/src/ipld/cid.rs @@ -1,6 +1,9 @@ //! Utilities for [`Cid`]s -use libipld_core::cid::Cid; +use crate::ipld; +use libipld_core::{cid::Cid, error::SerdeError, ipld::Ipld}; +use serde::{Deserialize, Serialize}; +use thiserror::Error; #[cfg(target_arch = "wasm32")] use wasm_bindgen::prelude::*; @@ -11,11 +14,21 @@ use wasm_bindgen_derive::TryFromJsValue; /// A newtype wrapper around a [`Cid`] /// /// This is largely to attach traits to [`Cid`]s, such as [`wasm_bindgen`] conversions. -#[derive(Debug, PartialEq, Eq, Clone)] -#[cfg_attr(target_arch = "wasm32", derive(TryFromJsValue))] -#[cfg_attr(target_arch = "wasm32", wasm_bindgen)] +#[cfg(not(target_arch = "wasm32"))] +#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)] pub struct Newtype { - cid: Cid, + pub cid: Cid, +} + +/// A newtype wrapper around a [`Cid`] +/// +/// This is largely to attach traits to [`Cid`]s, such as [`wasm_bindgen`] conversions. +#[cfg(target_arch = "wasm32")] +#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)] +#[wasm_bindgen] +pub struct Newtype { + #[wasm_bindgen(skip)] + pub cid: Cid, } #[cfg(target_arch = "wasm32")] @@ -33,17 +46,25 @@ extern "C" { #[wasm_bindgen] impl Newtype { /// Parse a [`Newtype`] from a string - pub fn from_string(cid_string: String) -> Result { - Newtype::try_from(cid_string).map_err(|e| JsValue::from_str(&format!("{}", e))) + pub fn from_string(cid_string: String) -> Result { + Newtype::try_from(cid_string).map_err(|e| JsError::new(&format!("{}", e))) + } + + pub fn try_from_js_value(js: &JsValue) -> Result { + match &js.as_string() { + Some(s) => Newtype::from_string(s.clone()), + None => Err(JsError::new("Expected a string")), + } } +} +impl Newtype { /// Convert the [`Cid`] to a string pub fn to_string(&self) -> String { self.cid.to_string() } } -#[cfg(target_arch = "wasm32")] impl TryFrom for Newtype { type Error = >::Error; @@ -52,16 +73,47 @@ impl TryFrom for Newtype { } } -#[cfg(target_arch = "wasm32")] impl From for Cid { fn from(wrapper: Newtype) -> Self { wrapper.cid } } -#[cfg(target_arch = "wasm32")] impl From for Newtype { fn from(cid: Cid) -> Self { Self { cid } } } + +impl TryFrom for Newtype { + type Error = NotACid; + + fn try_from(ipld: Ipld) -> Result { + match ipld { + Ipld::Link(cid) => Ok(Newtype { cid }), + other => Err(NotACid(other.into())), + } + } +} + +impl TryFrom<&Ipld> for Newtype { + type Error = NotACid; + + fn try_from(ipld: &Ipld) -> Result { + match ipld { + Ipld::Link(cid) => Ok(Newtype { cid: *cid }), + other => Err(NotACid(other.clone().into())), + } + } +} + +// #[cfg(not(target_arch = "wasm32"))] +#[derive(Debug, PartialEq, Clone, Error, Serialize, Deserialize)] +#[error("Not a CID: {0:?}")] +pub struct NotACid(pub ipld::Newtype); + +// #[cfg(target_arch = "wasm32")] +// #[derive(Debug, PartialEq, Clone, Error, Serialize, Deserialize)] +// #[error("Not a CID: {0:?}")] +// #[wasm_bindgen] +// pub struct NotACid(#[wasm_bindgen(skip)] pub ipld::Newtype); diff --git a/src/lib.rs b/src/lib.rs index c8928529..ab83b718 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -78,6 +78,7 @@ pub mod receipt; pub mod signature; pub mod task; pub mod time; +pub mod url; // FIXME consider a fact-system // /// The empty fact diff --git a/src/nonce.rs b/src/nonce.rs index 9f3628d9..c2837684 100644 --- a/src/nonce.rs +++ b/src/nonce.rs @@ -192,14 +192,6 @@ impl TryFrom for Nonce { } } -impl TryFrom<&Ipld> for Nonce { - type Error = (); // FIXME - - fn try_from(ipld: &Ipld) -> Result { - TryFrom::try_from(ipld.to_owned()) - } -} - #[cfg(target_arch = "wasm32")] #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] #[wasm_bindgen] @@ -247,7 +239,7 @@ impl JsNonce { /// # Arguments /// /// * `nonce` - The exact nonce to convert to a [`JsNonce`] - pub fn from_uint8_array(nonce: Box<[u8]>) -> JsNonce { + pub fn from_uint8_array(arr: Box<[u8]>) -> JsNonce { Nonce::from(arr.to_vec()).into() } diff --git a/src/proof/error.rs b/src/proof/error.rs index 6b615c8c..a36b5208 100644 --- a/src/proof/error.rs +++ b/src/proof/error.rs @@ -10,7 +10,7 @@ use wasm_bindgen::prelude::*; #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, Error)] #[cfg_attr(target_arch = "wasm32", wasm_bindgen)] #[error("unequal")] -pub struct Unequal {} +pub struct Unequal(); /// A generic error for when two fields are unequal. #[derive(Copy, Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Error)] diff --git a/src/time.rs b/src/time.rs index cd82682f..37a4b838 100644 --- a/src/time.rs +++ b/src/time.rs @@ -47,10 +47,13 @@ impl<'de> Deserialize<'de> for Timestamp { where D: Deserializer<'de>, { - if let Ok(sys_time) = SystemTime::deserialize(deserializer) { - match JsTime::new(sys_time) { - Ok(js_time) => Ok(Timestamp::JsSafe(js_time)), - Err(_) => Ok(Timestamp::Postel(sys_time)), + if let Ok(secs) = u64::deserialize(deserializer) { + match UNIX_EPOCH.checked_add(Duration::new(secs, 0)) { + None => return Err(serde::de::Error::custom("time out of range for SystemTime")), + Some(sys_time) => match JsTime::new(sys_time) { + Ok(js_time) => Ok(Timestamp::JsSafe(js_time)), + Err(_) => Ok(Timestamp::Postel(sys_time)), + }, } } else { Err(serde::de::Error::custom("not a Timestamp")) diff --git a/src/url.rs b/src/url.rs new file mode 100644 index 00000000..a69b78bc --- /dev/null +++ b/src/url.rs @@ -0,0 +1,47 @@ +//! URL utilities + +use libipld_core::{error::SerdeError, ipld::Ipld, serde as ipld_serde}; +use serde::{Deserialize, Serialize}; +use url::Url; + +/// A wrapper around [`Url`] that has additional trait implementations +/// +/// Usage is very simple: wrap a [`Newtype`] to gain access to additional traits and methods. +/// +/// ```rust +/// # use ::url::Url; +/// # use ucan::url; +/// # +/// let url = Url::parse("https://example.com").unwrap(); +/// let wrapped = url::Newtype(url.clone()); +/// // wrapped.some_trait_method(); +/// ``` +/// +/// Unwrap a [`Newtype`] to use any interfaces that expect plain [`Ipld`]. +/// +/// ``` +/// # use ::url::Url; +/// # use ucan::url; +/// # +/// # let url = Url::parse("https://example.com").unwrap(); +/// # let wrapped = url::Newtype(url.clone()); +/// # +/// assert_eq!(wrapped.0, url); +/// ``` +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[serde(transparent)] +pub struct Newtype(pub Url); + +impl From for Ipld { + fn from(newtype: Newtype) -> Self { + newtype.into() + } +} + +impl TryFrom for Newtype { + type Error = SerdeError; + + fn try_from(ipld: Ipld) -> Result { + ipld_serde::from_ipld(ipld) + } +} From 4c64c7ab942cbf33ed593e0914c9edd2b762572c Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Thu, 8 Feb 2024 22:59:58 -0800 Subject: [PATCH 060/188] What a saga! --- src/ability.rs | 2 + src/ability/arguments.rs | 116 ++++++++--- src/ability/crud.rs | 55 +++++- src/ability/crud/any.rs | 105 +++++----- src/ability/crud/create.rs | 32 +-- src/ability/crud/destroy.rs | 18 +- src/ability/crud/error.rs | 28 +++ src/ability/crud/js.rs | 67 +++++++ src/ability/crud/mutate.rs | 73 ++++++- src/ability/crud/parents.rs | 63 +++++- src/ability/crud/read.rs | 228 ++++++++++++++++------ src/ability/crud/update.rs | 72 ++++--- src/ability/msg/any.rs | 6 +- src/ability/msg/receive.rs | 2 + src/ability/msg/send.rs | 13 +- src/ability/ucan/revoke.rs | 17 +- src/ability/wasm/run.rs | 19 +- src/delegation/condition.rs | 2 +- src/delegation/condition/contains_all.rs | 2 +- src/delegation/condition/contains_any.rs | 2 +- src/delegation/condition/contains_key.rs | 2 +- src/delegation/condition/excludes_all.rs | 2 +- src/delegation/condition/excludes_key.rs | 2 +- src/delegation/condition/matches_regex.rs | 2 +- src/delegation/condition/max_length.rs | 2 +- src/delegation/condition/max_number.rs | 2 +- src/delegation/condition/min_length.rs | 2 +- src/delegation/condition/min_number.rs | 2 +- src/delegation/condition/traits.rs | 2 +- src/delegation/delegatable.rs | 3 +- src/delegation/payload.rs | 8 +- src/invocation/payload.rs | 4 +- src/invocation/promise/any.rs | 19 +- src/invocation/promise/err.rs | 8 +- src/invocation/promise/ok.rs | 8 +- src/invocation/promise/resolves.rs | 16 ++ src/invocation/resolvable.rs | 8 +- src/ipld.rs | 3 +- src/ipld/promised.rs | 177 +++++++++++++++++ src/reader.rs | 31 ++- src/receipt/payload.rs | 4 +- src/task.rs | 2 +- 42 files changed, 956 insertions(+), 275 deletions(-) create mode 100644 src/ability/crud/error.rs create mode 100644 src/ability/crud/js.rs create mode 100644 src/ipld/promised.rs diff --git a/src/ability.rs b/src/ability.rs index fe0363ba..f9547f56 100644 --- a/src/ability.rs +++ b/src/ability.rs @@ -1,4 +1,6 @@ // FIXME feature flag each? +// FIXME ability implementers guide (e.g. serde deny fields) +// pub mod crud; pub mod msg; pub mod ucan; diff --git a/src/ability/arguments.rs b/src/ability/arguments.rs index b8314428..95a4085b 100644 --- a/src/ability/arguments.rs +++ b/src/ability/arguments.rs @@ -3,6 +3,7 @@ use libipld_core::{error::SerdeError, ipld::Ipld, serde as ipld_serde}; use serde::{Deserialize, Serialize}; use std::collections::BTreeMap; +use thiserror::Error; #[cfg(target_arch = "wasm32")] use wasm_bindgen::prelude::*; @@ -23,11 +24,11 @@ use crate::ipld; /// ```rust /// # use ucan::ability::arguments; /// # use url::Url; -/// # use libipld::ipld; +/// # use libipld::{ipld, ipld::Ipld}; /// # /// struct Execute { /// program: Url, -/// instructions: arguments::Named, +/// instructions: arguments::Named, /// } /// /// let ability = Execute { @@ -41,9 +42,9 @@ use crate::ipld; /// assert_eq!(ability.instructions.get("bold"), Some(&ipld!(true))); /// ``` #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] -pub struct Named(pub BTreeMap); +pub struct Named(pub BTreeMap); -impl Named { +impl Named { /// Create a new, empty `Named` instance. pub fn new() -> Self { Default::default() @@ -52,21 +53,21 @@ impl Named { /// Get the value associated with a key. /// /// An alias for [`BTreeMap::insert`]. - pub fn get(&self, key: &str) -> Option<&Ipld> { + pub fn get(&self, key: &str) -> Option<&T> { self.0.get(key) } /// Inserts a key-value pair. /// /// An alias for [`BTreeMap::insert`]. - pub fn insert(&mut self, key: String, value: Ipld) -> Option { + pub fn insert(&mut self, key: String, value: T) -> Option { self.0.insert(key, value) } /// Gets an iterator over the entries, sorted by key. /// /// A wrapper around [`BTreeMap::iter`]. - pub fn iter(&self) -> impl Iterator { + pub fn iter(&self) -> impl Iterator { self.0.iter() } @@ -78,28 +79,28 @@ impl Named { } } -impl Default for Named { +impl Default for Named { fn default() -> Self { Named(BTreeMap::new()) } } -impl IntoIterator for Named { - type Item = (String, Ipld); - type IntoIter = std::collections::btree_map::IntoIter; +impl IntoIterator for Named { + type Item = (String, T); + type IntoIter = std::collections::btree_map::IntoIter; fn into_iter(self) -> Self::IntoIter { self.0.into_iter() } } -impl FromIterator<(String, Ipld)> for Named { - fn from_iter>(iter: T) -> Self { +impl FromIterator<(String, T)> for Named { + fn from_iter>(iter: I) -> Self { Named(iter.into_iter().collect()) } } -impl TryFrom for Named { +impl Deserialize<'de>> TryFrom for Named { type Error = SerdeError; fn try_from(ipld: Ipld) -> Result { @@ -107,15 +108,26 @@ impl TryFrom for Named { } } -impl From for Ipld { - fn from(arguments: Named) -> Self { +impl From> for Ipld { + fn from(arguments: Named) -> Self { ipld_serde::to_ipld(arguments).unwrap() } } #[cfg(target_arch = "wasm32")] -impl From for Object { - fn from(arguments: Named) -> Self { +impl> From> for Object { + fn from(arguments: Named) -> Self { + let obj = Object::new(); + for (k, v) in arguments.0 { + Reflect::set(&obj, &k.into(), v.into()).unwrap(); + } + obj + } +} + +#[cfg(target_arch = "wasm32")] +impl From> for Object { + fn from(arguments: Named) -> Self { let obj = Object::new(); for (k, v) in arguments.0 { Reflect::set(&obj, &k.into(), &ipld::Newtype(v).into()).unwrap(); @@ -127,7 +139,28 @@ impl From for Object { // NOTE saves a few cycles while calling by not cloning // the extra Object fields that we're not going to use #[cfg(target_arch = "wasm32")] -impl From<&Object> for Named { +impl> From<&Object> for Named { + // FIXME probbaly needs to be a try_from + fn from(obj: &Object) -> Self { + let btree = Object::entries(obj) + .iter() + .map(|entry| { + let entry = Array::from(&entry); + let key = entry.get(0).as_string().unwrap(); // FIXME + let value = T::try_from(entry.get(1)).unwrap().0; // FIXME + (key, value) + }) + .collect::>(); + + Named(btree) + } +} + +// NOTE saves a few cycles while calling by not cloning +// the extra Object fields that we're not going to use +#[cfg(target_arch = "wasm32")] +impl From<&Object> for Named { + // FIXME probbaly needs to be a try_from fn from(obj: &Object) -> Self { let btree = Object::entries(obj) .iter() @@ -144,8 +177,22 @@ impl From<&Object> for Named { } #[cfg(target_arch = "wasm32")] -impl From for JsValue { - fn from(arguments: Named) -> Self { +impl From> for JsValue { + fn from(arguments: Named) -> Self { + arguments + .0 + .iter() + .fold(Map::new(), |map, (ref k, v)| { + map.set(&JsValue::from_str(k), &JsValue::from(v.clone())); + map + }) + .into() + } +} + +#[cfg(target_arch = "wasm32")] +impl From> for JsValue { + fn from(arguments: Named) -> Self { arguments .0 .iter() @@ -161,7 +208,20 @@ impl From for JsValue { } #[cfg(target_arch = "wasm32")] -impl TryFrom for Named { +impl TryFrom for Named { + type Error = (); // FIXME + + fn try_from(js: JsValue) -> Result { + match T::try_from(js) { + Err(()) => Err(()), // FIXME surface that we can't parse at all + Ok(Ipld::Map(map)) => Ok(Named(map)), + Ok(_wrong_ipld) => Err(()), // FIXME surface that we have the wrong type + } + } +} + +#[cfg(target_arch = "wasm32")] +impl TryFrom for Named { type Error = (); // FIXME fn try_from(js: JsValue) -> Result { @@ -172,3 +232,15 @@ impl TryFrom for Named { } } } + +/// Errors for [`arguments::Named`][Named]. +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Error)] +pub enum NamedError { + /// A required field was missing. + #[error("Missing arguments::Named field {0}")] + FieldMissing(String), + + /// The value at the named field didn't match the expected value. + #[error("arguments::Named field {0}: value doesn't match")] + FieldValueMismatch(String), +} diff --git a/src/ability/crud.rs b/src/ability/crud.rs index 58f8845f..b06fb0f7 100644 --- a/src/ability/crud.rs +++ b/src/ability/crud.rs @@ -1,15 +1,54 @@ +//! Abilties for [CRUD] (create, read, update, and destroy) interfaces +//! +//! An overview of the hierarchy can be found on [`crud::Any`][`Any`]. +//! +//! # Wrapping External Resources +//! +//! In most cases, the Subject _is_ the resource being acted +//! on with a CRUD interface. To model external resources directly +//! (i.e. without a URL), generate a unique [`Did`] that represents the +//! specific resource (i.e. the `sub`) directly. This makes the +//! UCAN self-certifying, and can give multiple names to a single +//! resource (which can be important if operating over an open network +//! such as a DHT or gossip). It also provides an abstraction if, +//! for example, the the domain name of a service changes. +//! +//! # `path` Field +//! +//! All variants of CRUD abilities include an *optional* `path` field. +//! +//! There are cases where a Subject acts as a gateway for *external* +//! resources, such as web services or hierarchical file systems. +//! Both of these contain sub-resources expressed via path. +//! If you are issued access to the root, and can attenuate that access to +//! any sub-path, or a single leaf resource. +//! +//! ```js +//! { +//! "sub: "did:example:1234", // <-- e.g. Wraps a web API +//! "cmd": "crud/update", +//! "args": { +//! "path": "/some/path/to/a/resource", +//! }, +//! // ... +//! } +//! ``` +//! +//! [CRUD]: https://en.wikipedia.org/wiki/Create,_read,_update_and_delete +//! [`Did`]: crate::did::Did + mod any; -mod create; -mod destroy; mod mutate; -mod read; -mod update; +pub mod create; +pub mod destroy; +pub mod error; pub mod parents; +pub mod read; +pub mod update; pub use any::Any; -pub use create::Create; -pub use destroy::Destroy; pub use mutate::Mutate; -pub use read::Read; -pub use update::Update; + +#[cfg(target_arch = "wasm32")] +pub mod js; diff --git a/src/ability/crud/any.rs b/src/ability/crud/any.rs index 8fda3432..897527d5 100644 --- a/src/ability/crud/any.rs +++ b/src/ability/crud/any.rs @@ -1,21 +1,64 @@ +//! "Any" CRUD ability (superclass of all CRUD abilities) + +use super::error::PathError; use crate::{ ability::command::Command, - ipld, - proof::{error::OptionalFieldError, parentless::NoParents, same::CheckSame}, + proof::{parentless::NoParents, same::CheckSame}, }; use libipld_core::{error::SerdeError, ipld::Ipld, serde as ipld_serde}; use serde::{Deserialize, Serialize}; -use thiserror::Error; -use url::Url; - -#[cfg(target_arch = "wasm32")] -use wasm_bindgen::prelude::*; +use std::path::PathBuf; +#[cfg_attr(doc, aquamarine::aquamarine)] +/// The superclass of all other CRUD abilities. +/// +/// For example, the [`crud::Create`][super::create::Create] ability may +/// be proven by the [`crud::Any`][Any] ability in a delegation chain. +/// +/// It may not be invoked directly, but rather is used as a delegaton proof +/// for other CRUD abilities (see the diagram below). +/// +/// # Delegation Hierarchy +/// +/// The hierarchy of CRUD abilities is as follows: +/// +/// ```mermaid +/// flowchart TB +/// top("*") +/// +/// subgraph Message Abilities +/// any("crud/*") +/// +/// mutate("crud/mutate") +/// +/// subgraph Invokable +/// read("crud/read") +/// create("crud/create") +/// update("crud/update") +/// destroy("crud/destroy") +/// end +/// end +/// +/// readrun{{"invoke"}} +/// createrun{{"invoke"}} +/// updaterun{{"invoke"}} +/// destroyrun{{"invoke"}} +/// +/// top --> any +/// any --> read -.-> readrun +/// any --> mutate +/// mutate --> create -.-> createrun +/// mutate --> update -.-> updaterun +/// mutate --> destroy -.-> destroyrun +/// +/// style any stroke:orange; +/// ``` #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] #[serde(deny_unknown_fields)] pub struct Any { + /// A an optional path relative to the actor's root. #[serde(default, skip_serializing_if = "Option::is_none")] - pub uri: Option, + pub path: Option, } impl Command for Any { @@ -25,10 +68,17 @@ impl Command for Any { impl NoParents for Any {} impl CheckSame for Any { - type Error = OptionalFieldError; + type Error = PathError; fn check_same(&self, proof: &Self) -> Result<(), Self::Error> { - self.uri.check_same(&proof.uri) + if let Some(path) = &self.path { + let proof_path = proof.path.as_ref().ok_or(PathError::Missing)?; + if path != proof_path { + return Err(PathError::Mismatch); + } + } + + Ok(()) } } @@ -45,38 +95,3 @@ impl From for Ipld { builder.into() } } - -#[cfg(target_arch = "wasm32")] -#[wasm_bindgen] -pub struct CrudAny(#[wasm_bindgen(skip)] pub Any); - -// FIXME macro this away -#[cfg(target_arch = "wasm32")] -#[wasm_bindgen] -impl CrudAny { - pub fn to_js(self) -> JsValue { - ipld::Newtype(Ipld::from(self.0)).into() - } - - pub fn from_js(js_val: JsValue) -> Result { - ipld::Newtype::try_into_jsvalue(js_val).map(CrudAny) - } - - pub fn command(&self) -> String { - Any::COMMAND.to_string() - } - - pub fn check_same(&self, proof: &CrudAny) -> Result<(), JsError> { - if self.uri.is_some() { - if self.uri != proof.uri { - return Err(OptionalFieldError { - field: "uri".into(), - err: OptionalFieldReason::NotEqual, - } - .into()); - } - } - - Ok(()) - } -} diff --git a/src/ability/crud/create.rs b/src/ability/crud/create.rs index 5270fb2c..71044525 100644 --- a/src/ability/crud/create.rs +++ b/src/ability/crud/create.rs @@ -2,18 +2,17 @@ use crate::{ ability::command::Command, proof::{checkable::Checkable, parentful::Parentful, parents::CheckParents, same::CheckSame}, }; -use libipld_core::{ipld::Ipld, serde as ipld_serde}; +use libipld_core::{error::SerdeError, ipld::Ipld, serde as ipld_serde}; use serde::{Deserialize, Serialize}; -use std::collections::BTreeMap; -use url::Url; +use std::{collections::BTreeMap, path::PathBuf}; -use super::parents::Mutable; +use super::parents::MutableParents; #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(deny_unknown_fields)] pub struct Create { #[serde(skip_serializing_if = "Option::is_none")] - pub uri: Option, + pub path: Option, #[serde(skip_serializing_if = "Option::is_none")] pub args: Option>, @@ -30,10 +29,10 @@ impl From for Ipld { } impl TryFrom for Create { - type Error = (); // FIXME + type Error = SerdeError; fn try_from(ipld: Ipld) -> Result { - ipld_serde::from_ipld(ipld).map_err(|_| ()) + ipld_serde::from_ipld(ipld) } } @@ -43,8 +42,9 @@ impl Checkable for Create { impl CheckSame for Create { type Error = (); // FIXME better error + fn check_same(&self, proof: &Self) -> Result<(), Self::Error> { - if self.uri == proof.uri { + if self.path == proof.path { Ok(()) } else { Err(()) @@ -53,22 +53,22 @@ impl CheckSame for Create { } impl CheckParents for Create { - type Parents = Mutable; + type Parents = MutableParents; type ParentError = (); fn check_parent(&self, other: &Self::Parents) -> Result<(), Self::ParentError> { - if let Some(self_uri) = &self.uri { + if let Some(self_path) = &self.path { match other { - Mutable::Any(any) => { - if let Some(proof_uri) = &any.uri { - if self_uri != proof_uri { + MutableParents::Any(any) => { + if let Some(proof_path) = &any.path { + if self_path != proof_path { return Err(()); } } } - Mutable::Mutate(mutate) => { - if let Some(proof_uri) = &mutate.uri { - if self_uri != proof_uri { + MutableParents::Mutate(mutate) => { + if let Some(proof_path) = &mutate.path { + if self_path != proof_path { return Err(()); } } diff --git a/src/ability/crud/destroy.rs b/src/ability/crud/destroy.rs index 6d9bd003..d448a34f 100644 --- a/src/ability/crud/destroy.rs +++ b/src/ability/crud/destroy.rs @@ -2,18 +2,18 @@ use crate::{ ability::command::Command, proof::{checkable::Checkable, parentful::Parentful, parents::CheckParents, same::CheckSame}, }; -use libipld_core::{ipld::Ipld, serde as ipld_serde}; +use libipld_core::{error::SerdeError, ipld::Ipld, serde as ipld_serde}; use serde::{Deserialize, Serialize}; -use url::Url; +use std::path::PathBuf; -use super::parents::Mutable; +use super::parents::MutableParents; // Destroy is its own builder #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] #[serde(deny_unknown_fields)] pub struct Destroy { #[serde(default, skip_serializing_if = "Option::is_none")] - pub uri: Option, + pub path: Option, } impl Command for Destroy { @@ -27,10 +27,10 @@ impl From for Ipld { } impl TryFrom for Destroy { - type Error = (); // FIXME + type Error = SerdeError; fn try_from(ipld: Ipld) -> Result { - ipld_serde::from_ipld(ipld).map_err(|_| ()) + ipld_serde::from_ipld(ipld) } } @@ -46,13 +46,13 @@ impl CheckSame for Destroy { } impl CheckParents for Destroy { - type Parents = Mutable; + type Parents = MutableParents; type ParentError = (); fn check_parent(&self, other: &Self::Parents) -> Result<(), Self::ParentError> { match other { - Mutable::Mutate(_mutate) => Ok(()), // FIXME - Mutable::Any(_any) => Ok(()), // FIXME + MutableParents::Mutate(_mutate) => Ok(()), // FIXME + MutableParents::Any(_any) => Ok(()), // FIXME } } } diff --git a/src/ability/crud/error.rs b/src/ability/crud/error.rs new file mode 100644 index 00000000..adaf33cc --- /dev/null +++ b/src/ability/crud/error.rs @@ -0,0 +1,28 @@ +use crate::ability::arguments; +use serde::{Deserialize, Serialize}; +use thiserror::Error; + +#[cfg(target_arch = "wasm32")] +use wasm_bindgen::prelude::*; + +#[derive(Debug, Clone, PartialEq, Error, Serialize, Deserialize)] +pub enum ProofError { + #[error("An issue with the path field")] + Path(#[from] PathError), + + #[error("An issue with the (inner) arguments field")] + Args(#[from] arguments::NamedError), + + #[error("Proof `args` were expected, but none were present")] + MissingProofArgs, +} + +#[derive(Debug, Clone, PartialEq, Error, Serialize, Deserialize)] +#[cfg_attr(target_arch = "wasm32", wasm_bindgen)] +pub enum PathError { + #[error("Path required in proof, but was not present")] + Missing, + + #[error("Proof path did not match")] + Mismatch, +} diff --git a/src/ability/crud/js.rs b/src/ability/crud/js.rs new file mode 100644 index 00000000..c66f87e4 --- /dev/null +++ b/src/ability/crud/js.rs @@ -0,0 +1,67 @@ +use super::{read, Any}; +use wasm_bindgen::prelude::*; + +/// DOCS? +#[wasm_bindgen] +pub struct CrudAny(#[wasm_bindgen(skip)] pub Any); + +// FIXME macro this away +#[wasm_bindgen] +impl CrudAny { + pub fn into_js(self) -> JsValue { + ipld::Newtype(Ipld::from(self.0)).into() + } + + pub fn try_from_js(js: JsValue) -> Result { + ipld::Newtype::try_from_js(js).map(CrudAny) + } + + pub fn command(&self) -> String { + Any::COMMAND.to_string() + } + + pub fn check_same(&self, proof: &CrudAny) -> Result<(), JsError> { + if self.path.is_some() { + if self.path != proof.path { + return Err(OptionalFieldError { + field: "path".into(), + err: OptionalFieldReason::NotEqual, + } + .into()); + } + } + + Ok(()) + } +} + +#[wasm_bindgen] +pub struct CrudRead(#[wasm_bindgen(skip)] pub read::Ready); + +#[wasm_bindgen] +impl CrudRead { + pub fn to_jsvalue(self) -> JsValue { + ipld::Newtype(Ipld::from(self.0)).into() + } + + pub fn from_jsvalue(js_val: JsValue) -> Result { + ipld::Newtype::try_into_jsvalue(js_val).map(CrudRead) + } + + pub fn command(&self) -> String { + Read::COMMAND.to_string() + } + + pub fn check_same(&self, proof: &CrudRead) -> Result<(), JsError> { + self.0.check_same(&proof.0).map_err(Into::into) + } + + // FIXME more than any + pub fn check_parent(&self, proof: &CrudAny) -> Result<(), JsError> { + self.0.check_parent(&proof.0).map_err(Into::into) + } +} + +// FIXME needs bindings +#[wasm_bindgen] +pub struct CrudReadPromise(#[wasm_bindgen(skip)] pub read::Promised); diff --git a/src/ability/crud/mutate.rs b/src/ability/crud/mutate.rs index 8bc3a869..32563b6f 100644 --- a/src/ability/crud/mutate.rs +++ b/src/ability/crud/mutate.rs @@ -1,16 +1,62 @@ +//! The delegation superclass for all mutable CRUD actions. + +use super::error::PathError; use crate::{ ability::command::Command, proof::{checkable::Checkable, parentful::Parentful, parents::CheckParents, same::CheckSame}, }; use libipld_core::{error::SerdeError, ipld::Ipld, serde as ipld_serde}; use serde::{Deserialize, Serialize}; -use url::Url; +use std::path::PathBuf; +#[cfg_attr(doc, aquamarine::aquamarine)] +/// The delegation superclass for all mutable CRUD actions. +/// +/// For example, the [`crud::Create`][super::create::Create] ability may +/// be proven by the [`crud::Mutate`][Mutate] ability in a delegation chain. +/// [`crud::Any`][super::Any] is a suitable proof for [`crud::Mutate`][Mutate], but not vice-versa. +/// +/// It may not be invoked directly, but rather is used as a delegaton proof +/// for other CRUD abilities (see the diagram below). +/// +/// # Delegation Hierarchy +/// +/// The hierarchy of mutable CRUD abilities is as follows: +/// +/// ```mermaid +/// flowchart TB +/// top("*") +/// +/// subgraph Message Abilities +/// any("crud/*") +/// +/// mutate("crud/mutate") +/// +/// subgraph Invokable +/// create("crud/create") +/// update("crud/update") +/// destroy("crud/destroy") +/// end +/// end +/// +/// createrun{{"invoke"}} +/// updaterun{{"invoke"}} +/// destroyrun{{"invoke"}} +/// +/// top --> any +/// any --> mutate +/// mutate --> create -.-> createrun +/// mutate --> update -.-> updaterun +/// mutate --> destroy -.-> destroyrun +/// +/// style mutate stroke:orange; +/// ``` #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] #[serde(deny_unknown_fields)] pub struct Mutate { + /// A an optional path relative to the actor's root. #[serde(default, skip_serializing_if = "Option::is_none")] - pub uri: Option, + pub path: Option, } impl Command for Mutate { @@ -36,17 +82,32 @@ impl Checkable for Mutate { } impl CheckSame for Mutate { - type Error = (); - fn check_same(&self, _proof: &Self) -> Result<(), Self::Error> { + type Error = PathError; + + fn check_same(&self, proof: &Self) -> Result<(), Self::Error> { + if let Some(path) = &self.path { + let proof_path = proof.path.as_ref().ok_or(PathError::Missing)?; + if path != proof_path { + return Err(PathError::Mismatch); + } + } + Ok(()) } } impl CheckParents for Mutate { type Parents = super::Any; - type ParentError = (); // FIXME + type ParentError = PathError; + + fn check_parent(&self, crud_any: &Self::Parents) -> Result<(), Self::ParentError> { + if let Some(path) = &self.path { + let proof_path = crud_any.path.as_ref().ok_or(PathError::Missing)?; + if path != proof_path { + return Err(PathError::Mismatch); + } + } - fn check_parent(&self, _proof: &Self::Parents) -> Result<(), Self::ParentError> { Ok(()) } } diff --git a/src/ability/crud/parents.rs b/src/ability/crud/parents.rs index 902e28e7..93f223b3 100644 --- a/src/ability/crud/parents.rs +++ b/src/ability/crud/parents.rs @@ -1,22 +1,67 @@ -use crate::proof::same::CheckSame; +//! Flat types for parent checking. +//! +//! Types here turn recursive checking into a since union to check. +//! This only needs to handle "inner" delegation types, not the topmost `*` +//! ability, or the invocable leaves of a delegation hierarchy. + +use crate::proof::{parents::CheckParents, same::CheckSame}; use serde::{Deserialize, Serialize}; +use thiserror::Error; +/// The union of mutable parents. +/// +/// This is helpful as a flat type to put in [`CheckParents::Parents`]. #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] #[serde(deny_unknown_fields)] -pub enum Mutable { - Mutate(super::Mutate), +pub enum MutableParents { + /// The `crud/*` ability. Any(super::Any), + + /// The `crud/mutate` ability. + Mutate(super::Mutate), } -impl CheckSame for Mutable { - type Error = (); +impl CheckSame for MutableParents { + type Error = Error; + fn check_same(&self, proof: &Self) -> Result<(), Self::Error> { match self { - Mutable::Mutate(mutate) => match proof { - Mutable::Mutate(other_mutate) => mutate.check_same(other_mutate), - Mutable::Any(_any) => Ok(()), + MutableParents::Mutate(mutate) => match proof { + MutableParents::Mutate(proof_mutate) => mutate + .check_same(proof_mutate) + .map_err(Error::InvalidMutateProof), + + MutableParents::Any(proof_any) => mutate + .check_parent(proof_any) + .map_err(Error::InvalidMutateParent), + }, + + MutableParents::Any(any) => match proof { + MutableParents::Mutate(_) => Err(Error::CommandEscelation), + MutableParents::Any(proof_any) => { + any.check_same(proof_any).map_err(Error::InvalidAnyProof) + } }, - _ => Err(()), } } } + +/// Error cases when checking [`MutableParents`] proofs +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Error)] +pub enum Error { + /// Error when comparing `crud/*` to another `crud/*`. + #[error(transparent)] + InvalidAnyProof(::Error), + + /// Error when comparing `crud/mutate` to another `crud/mutate`. + #[error(transparent)] + InvalidMutateProof(::Error), + + /// Error when comparing `crud/*` as a proof for `crud/mutate`. + #[error(transparent)] + InvalidMutateParent(::ParentError), + + /// "Expected `crud/*`, but got `crud/mutate`". + #[error("Expected `crud/*`, but got `crud/mutate`")] + CommandEscelation, +} diff --git a/src/ability/crud/read.rs b/src/ability/crud/read.rs index 1a42083d..e29f6b02 100644 --- a/src/ability/crud/read.rs +++ b/src/ability/crud/read.rs @@ -1,73 +1,126 @@ +//! The ability to read (fetch) from a resource. +//! +//! * This ability may be invoked when [`Ready`]. +//! * See the [`Builder`] to view the [delegation chain](./type.Builder.html#delegation-hierarchy). +//! * The invocation [Lifecycle](./struct.Ready.html#lifecycle) can be found on [`Ready`] or [`Promised`]. + +use super::error::{PathError, ProofError}; use crate::{ - ability::{arguments, command::Command, crud::any::CrudAny}, + ability::{arguments, command::Command}, + invocation::promise, proof::{checkable::Checkable, parentful::Parentful, parents::CheckParents, same::CheckSame}, }; use libipld_core::{error::SerdeError, ipld::Ipld, serde as ipld_serde}; use serde::{Deserialize, Serialize}; -use thiserror::Error; -use url::Url; +use std::path::PathBuf; #[cfg(target_arch = "wasm32")] use wasm_bindgen::prelude::*; -#[cfg(target_arch = "wasm32")] -use crate::ipld; - -// Read is its own builder +#[cfg_attr(doc, aquamarine::aquamarine)] +/// The CRUD ability to retrieve data from a resource. +/// +/// Note that the delegation [`Builder`] has the exact same +/// fields in this case. +/// +/// # Invocation +/// +/// The executable/dispatchable variant of the `msg/send` ability. +/// +/// # Lifecycle +/// +/// The hierarchy of message abilities is as follows: +/// +/// ```mermaid +/// flowchart LR +/// subgraph Delegations +/// top("*") +/// +/// any("crud/*") +/// +/// subgraph Invokable +/// read("crud/read") +/// end +/// end +/// +/// readpromise("crud::read::Promised") +/// readrun("crud::read::Ready") +/// +/// top --> any +/// any --> read -.->|invoke| readpromise -.->|resolve| readrun -.-> exe{{execute}} +/// +/// style readrun stroke:orange; +/// ``` #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(deny_unknown_fields)] -pub struct Read { +pub struct Ready { + /// Optional path within the resource. #[serde(default, skip_serializing_if = "Option::is_none")] - pub uri: Option, + pub path: Option, + /// Optional additional arugments to pass in the request. #[serde(default, skip_serializing_if = "Option::is_none")] - pub args: Option, + pub args: Option>, } -impl Command for Read { +#[cfg_attr(doc, aquamarine::aquamarine)] +/// The CRUD ability to retrieve data from a resource. +/// +/// Note that the delegation [`Builder`] has the exact same +/// fields as [`read::Ready`][Ready] in this case. +/// +/// # Delegation Hierarchy +/// +/// The hierarchy of CRUD abilities is as follows: +/// +/// ```mermaid +/// flowchart TB +/// top("*") +/// +/// subgraph Message Abilities +/// any("crud/*") +/// +/// subgraph Invokable +/// read("crud/read") +/// end +/// end +/// +/// readrun{{"invoke"}} +/// +/// top --> any +/// any --> read -.-> readrun +/// +/// style read stroke:orange; +/// ``` +pub type Builder = Ready; + +impl Command for Ready { const COMMAND: &'static str = "crud/read"; } -impl From for Ipld { - fn from(read: Read) -> Self { - read.into() - } -} - -impl TryFrom for Read { - type Error = SerdeError; - - fn try_from(ipld: Ipld) -> Result { - ipld_serde::from_ipld(ipld) - } -} - -// FIXME -#[derive(Debug, Error)] -pub enum E { - #[error("Some error")] - SomeErrMsg(String), +impl Checkable for Ready { + type Hierarchy = Parentful; } -impl Checkable for Read { - type Hierarchy = Parentful; -} +impl CheckSame for Ready { + type Error = ProofError; -impl CheckSame for Read { - type Error = E; fn check_same(&self, proof: &Self) -> Result<(), Self::Error> { - if let Some(uri) = &self.uri { - if uri != proof.uri.as_ref().unwrap() { - return Err(E::SomeErrMsg("".into())); + if let Some(path) = &self.path { + if path != proof.path.as_ref().unwrap() { + return Err(PathError::Mismatch.into()); } } if let Some(args) = &self.args { - if let Some(proof_args) = &proof.args { - for (k, v) in args.iter() { - if proof_args.get(k) != Some(v) { - return Err(E::SomeErrMsg("".into())); - } + let proof_args = proof.args.as_ref().ok_or(ProofError::MissingProofArgs)?; + for (k, v) in args.iter() { + if proof_args + .get(k) + .ok_or(arguments::NamedError::FieldMissing(k.clone()))? + .ne(v) + { + return Err(arguments::NamedError::FieldValueMismatch(k.clone()).into()); } } } @@ -76,39 +129,84 @@ impl CheckSame for Read { } } -impl CheckParents for Read { +impl CheckParents for Ready { type Parents = super::Any; - type ParentError = E; + type ParentError = PathError; - fn check_parent(&self, _other: &Self::Parents) -> Result<(), Self::ParentError> { - Ok(()) // FIXME + fn check_parent(&self, crud_any: &super::Any) -> Result<(), Self::ParentError> { + if let Some(path) = &self.path { + let crud_any_path = crud_any.path.as_ref().ok_or(PathError::Missing)?; + if path != crud_any_path { + return Err(PathError::Mismatch); + } + } + + Ok(()) } } -#[cfg(target_arch = "wasm32")] -#[wasm_bindgen] -pub struct CrudRead(#[wasm_bindgen(skip)] pub Read); - -#[cfg(target_arch = "wasm32")] -#[wasm_bindgen] -impl CrudRead { - pub fn to_jsvalue(self) -> JsValue { - ipld::Newtype(Ipld::from(self.0)).into() +impl From for Ipld { + fn from(ready: Ready) -> Self { + ready.into() } +} +impl TryFrom for Ready { + type Error = SerdeError; - pub fn from_jsvalue(js_val: JsValue) -> Result { - ipld::Newtype::try_into_jsvalue(js_val).map(CrudRead) + fn try_from(ipld: Ipld) -> Result { + ipld_serde::from_ipld(ipld) } +} - pub fn command(&self) -> String { - Read::COMMAND.to_string() - } +#[cfg_attr(doc, aquamarine::aquamarine)] +/// This ability is used to fetch messages from other actors. +/// +/// # Lifecycle +/// +/// The hierarchy of message abilities is as follows: +/// +/// ```mermaid +/// flowchart LR +/// subgraph Delegations +/// top("*") +/// +/// any("crud/*") +/// +/// subgraph Invokable +/// read("crud/read") +/// end +/// end +/// +/// readpromise("crud::read::Promised") +/// readrun("crud::read::Ready") +/// +/// top --> any +/// any --> read -.->|invoke| readpromise -.->|resolve| readrun -.-> exe{{execute}} +/// +/// style readpromise stroke:orange; +/// ``` +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +#[serde(deny_unknown_fields)] +pub struct Promised { + /// Optional path within the resource + #[serde(skip_serializing_if = "promise::Resolves::resolved_none")] + pub path: promise::Resolves>, + + /// Optional additional arugments to pass in the request + #[serde(skip_serializing_if = "promise::Resolves::resolved_none")] + pub args: promise::Resolves>>>, +} - pub fn check_same(&self, proof: &CrudRead) -> Result<(), JsError> { - self.0.check_same(&proof.0).map_err(Into::into) +impl From for Ipld { + fn from(promised: Promised) -> Self { + promised.into() } +} - pub fn check_parent(&self, proof: &CrudAny) -> Result<(), JsError> { - self.0.check_parent(&proof.0).map_err(Into::into) +impl TryFrom for Promised { + type Error = SerdeError; + + fn try_from(ipld: Ipld) -> Result { + ipld_serde::from_ipld(ipld) } } diff --git a/src/ability/crud/update.rs b/src/ability/crud/update.rs index 62378050..be8ba511 100644 --- a/src/ability/crud/update.rs +++ b/src/ability/crud/update.rs @@ -1,87 +1,97 @@ +use super::parents::MutableParents; use crate::{ - ability::command::Command, + ability::{arguments, command::Command}, + invocation::promise, + ipld::promised::PromisedIpld, proof::{checkable::Checkable, parentful::Parentful, parents::CheckParents, same::CheckSame}, }; -use libipld_core::{ipld::Ipld, serde as ipld_serde}; +use libipld_core::{error::SerdeError, ipld::Ipld, serde as ipld_serde}; use serde::{Deserialize, Serialize}; -use std::collections::BTreeMap; -use url::Url; - -use super::parents::Mutable; +use std::{collections::BTreeMap, path::PathBuf}; #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(deny_unknown_fields)] -pub struct Update { +pub struct Ready { + /// Optional path within the resource. #[serde(default, skip_serializing_if = "Option::is_none")] - pub uri: Option, + pub path: Option, - #[serde(default, skip_serializing_if = "BTreeMap::is_empty")] - pub args: BTreeMap, + /// Additional arugments to pass in the request. + pub args: arguments::Named, } -impl From for Ipld { - fn from(udpdate: Update) -> Self { +impl From for Ipld { + fn from(udpdate: Ready) -> Self { udpdate.into() } } -impl TryFrom for Update { - type Error = (); // FIXME +impl TryFrom for Ready { + type Error = SerdeError; fn try_from(ipld: Ipld) -> Result { - ipld_serde::from_ipld(ipld).map_err(|_| ()) + ipld_serde::from_ipld(ipld) } } -impl Command for Update { +impl Command for Ready { const COMMAND: &'static str = "crud/update"; } #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(deny_unknown_fields)] -pub struct UpdateBuilder { +pub struct Builder { #[serde(default, skip_serializing_if = "Option::is_none")] - pub uri: Option, + pub path: Option, #[serde(default, skip_serializing_if = "Option::is_none")] pub args: Option>, // FIXME use a type param? } -impl From for Ipld { - fn from(udpdate: UpdateBuilder) -> Self { +impl From for Ipld { + fn from(udpdate: Builder) -> Self { udpdate.into() } } -impl TryFrom for UpdateBuilder { - type Error = (); // FIXME +impl TryFrom for Builder { + type Error = SerdeError; fn try_from(ipld: Ipld) -> Result { - ipld_serde::from_ipld(ipld).map_err(|_| ()) + ipld_serde::from_ipld(ipld) } } -impl Checkable for UpdateBuilder { - type Hierarchy = Parentful; +impl Checkable for Builder { + type Hierarchy = Parentful; } -impl CheckSame for UpdateBuilder { +impl CheckSame for Builder { type Error = (); // FIXME fn check_same(&self, proof: &Self) -> Result<(), Self::Error> { - self.uri.check_same(&proof.uri).map_err(|_| ())?; + self.path.check_same(&proof.path).map_err(|_| ())?; self.args.check_same(&proof.args).map_err(|_| ()) } } -impl CheckParents for UpdateBuilder { - type Parents = Mutable; +impl CheckParents for Builder { + type Parents = MutableParents; type ParentError = (); // FIXME fn check_parent(&self, proof: &Self::Parents) -> Result<(), Self::ParentError> { match proof { - Mutable::Any(any) => self.uri.check_same(&any.uri).map_err(|_| ()), - Mutable::Mutate(mutate) => self.uri.check_same(&mutate.uri).map_err(|_| ()), + MutableParents::Any(any) => self.path.check_same(&any.path).map_err(|_| ()), + MutableParents::Mutate(mutate) => self.path.check_same(&mutate.path).map_err(|_| ()), } } } + +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +#[serde(deny_unknown_fields)] +pub struct Promised { + #[serde(skip_serializing_if = "promise::Resolves::resolved_none")] + pub path: promise::Resolves>, + + pub args: promise::Resolves>, +} diff --git a/src/ability/msg/any.rs b/src/ability/msg/any.rs index 4d7ece1b..48f8eeab 100644 --- a/src/ability/msg/any.rs +++ b/src/ability/msg/any.rs @@ -9,11 +9,11 @@ use serde::{Deserialize, Serialize}; use url::Url; #[cfg_attr(doc, aquamarine::aquamarine)] -/// The [`Any`] message ability may not be invoked, but it is the superclass of +/// The [`msg::Any`][Any] ability may not be invoked, but it is the superclass of /// all other message abilities. /// -/// For example, the [`message::Receive`][super::receive::Receive] ability may -/// be proven by the [`Any`] ability in a delegation chain. +/// For example, the [`msg::Receive`][super::receive::Receive] ability may +/// be proven by the [`msg::Any`][Any] ability in a delegation chain. /// /// # Delegation Hierarchy /// diff --git a/src/ability/msg/receive.rs b/src/ability/msg/receive.rs index 9282d6fd..c8a34ce4 100644 --- a/src/ability/msg/receive.rs +++ b/src/ability/msg/receive.rs @@ -44,6 +44,8 @@ pub struct Receive { pub from: Option, } +// FIXME needs promisory version + impl Command for Receive { const COMMAND: &'static str = "msg/send"; } diff --git a/src/ability/msg/send.rs b/src/ability/msg/send.rs index c15341f4..b7a63b94 100644 --- a/src/ability/msg/send.rs +++ b/src/ability/msg/send.rs @@ -22,6 +22,8 @@ pub struct Generic { /// The recipient of the message pub to: To, + /// FIXME Builder needs to omit option fields from Serde + /// The sender address of the message /// /// This *may* be a URL (such as an email address). @@ -127,9 +129,16 @@ impl Delegatable for Ready { impl Resolvable for Ready { type Promised = Promised; + + fn try_resolve(p: Promised) -> Result { + match promise::Resolves::try_resolve_3(p.to, p.from, p.message) { + Ok((to, from, message)) => Ok(Ready { to, from, message }), + Err((to, from, message)) => Err(Promised { to, from, message }), + } + } } -impl From for arguments::Named { +impl From for arguments::Named { fn from(b: Builder) -> Self { let mut btree = BTreeMap::new(); b.to.map(|to| btree.insert("to".into(), to.to_string().into())); @@ -142,7 +151,7 @@ impl From for arguments::Named { } } -impl From for arguments::Named { +impl From for arguments::Named { fn from(p: Promised) -> Self { arguments::Named(BTreeMap::from_iter([ ("to".into(), p.to.map(url_newtype::Newtype).into()), diff --git a/src/ability/ucan/revoke.rs b/src/ability/ucan/revoke.rs index 07b1df31..bf45a63d 100644 --- a/src/ability/ucan/revoke.rs +++ b/src/ability/ucan/revoke.rs @@ -6,7 +6,7 @@ use crate::{ invocation::{promise, Resolvable}, proof::{parentless::NoParents, same::CheckSame}, }; -use libipld_core::cid::Cid; +use libipld_core::{cid::Cid, ipld::Ipld}; use serde::{Deserialize, Serialize}; use std::{collections::BTreeMap, fmt::Debug}; @@ -33,6 +33,13 @@ impl Delegatable for Ready { impl Resolvable for Ready { type Promised = Promised; + + fn try_resolve(promised: Self::Promised) -> Result { + match promised.ucan.try_resolve() { + Ok(ucan) => Ok(Ready { ucan }), + Err(ucan) => Err(Promised { ucan }), + } + } } /// A variant with some fields waiting to be set. @@ -64,8 +71,8 @@ impl TryFrom for Ready { } } -impl From for arguments::Named { - fn from(b: Builder) -> arguments::Named { +impl From for arguments::Named { + fn from(b: Builder) -> arguments::Named { let mut btree = BTreeMap::new(); if let Some(cid) = b.ucan { btree.insert("ucan".into(), cid.into()); @@ -85,8 +92,8 @@ impl From for Promised { } } -impl From for arguments::Named { - fn from(p: Promised) -> arguments::Named { +impl From for arguments::Named { + fn from(p: Promised) -> arguments::Named { arguments::Named::from_iter([("ucan".into(), p.ucan.into())]) } } diff --git a/src/ability/wasm/run.rs b/src/ability/wasm/run.rs index a6a8cdcc..b32f7880 100644 --- a/src/ability/wasm/run.rs +++ b/src/ability/wasm/run.rs @@ -37,6 +37,21 @@ impl Delegatable for Ready { impl Resolvable for Ready { type Promised = Promised; + + fn try_resolve(promised: Self::Promised) -> Result { + match promise::Resolves::try_resolve_3(promised.module, promised.function, promised.args) { + Ok((module, function, args)) => Ok(Ready { + module, + function, + args, + }), + Err((module, function, args)) => Err(Promised { + module, + function, + args, + }), + } + } } /// A variant meant for delegation, where fields may be omitted @@ -44,7 +59,7 @@ pub type Builder = Generic, Option, Option>>; impl NoParents for Builder {} -impl From for arguments::Named { +impl From for arguments::Named { fn from(builder: Builder) -> Self { let mut btree = BTreeMap::new(); if let Some(module) = builder.module { @@ -137,7 +152,7 @@ impl TryFrom for Ready { } } -impl From for arguments::Named { +impl From for arguments::Named { fn from(promised: Promised) -> Self { arguments::Named::from_iter([ ("module".into(), promised.module.into()), diff --git a/src/delegation/condition.rs b/src/delegation/condition.rs index 42048dc2..d6ca60be 100644 --- a/src/delegation/condition.rs +++ b/src/delegation/condition.rs @@ -45,7 +45,7 @@ impl TryFrom for Common { } impl Condition for Common { - fn validate(&self, args: &arguments::Named) -> bool { + fn validate(&self, args: &arguments::Named) -> bool { match self { Common::ContainsAll(c) => c.validate(args), Common::ContainsAny(c) => c.validate(args), diff --git a/src/delegation/condition/contains_all.rs b/src/delegation/condition/contains_all.rs index b7a8aca8..d0882e64 100644 --- a/src/delegation/condition/contains_all.rs +++ b/src/delegation/condition/contains_all.rs @@ -33,7 +33,7 @@ impl TryFrom for ContainsAll { } impl Condition for ContainsAll { - fn validate(&self, args: &arguments::Named) -> bool { + fn validate(&self, args: &arguments::Named) -> bool { match args.get(&self.field) { Some(Ipld::List(array)) => self.contains_all.iter().all(|ipld| array.contains(ipld)), Some(Ipld::Map(btree)) => { diff --git a/src/delegation/condition/contains_any.rs b/src/delegation/condition/contains_any.rs index 5f730a03..46af682f 100644 --- a/src/delegation/condition/contains_any.rs +++ b/src/delegation/condition/contains_any.rs @@ -32,7 +32,7 @@ impl TryFrom for ContainsAny { } impl Condition for ContainsAny { - fn validate(&self, args: &arguments::Named) -> bool { + fn validate(&self, args: &arguments::Named) -> bool { match args.get(&self.field) { Some(Ipld::List(array)) => array.iter().any(|ipld| self.contains_any.contains(ipld)), Some(Ipld::Map(btree)) => { diff --git a/src/delegation/condition/contains_key.rs b/src/delegation/condition/contains_key.rs index 0a4ff86d..cec31ffa 100644 --- a/src/delegation/condition/contains_key.rs +++ b/src/delegation/condition/contains_key.rs @@ -60,7 +60,7 @@ impl TryFrom for ContainsKey { } impl Condition for ContainsKey { - fn validate(&self, args: &arguments::Named) -> bool { + fn validate(&self, args: &arguments::Named) -> bool { match args.get(&self.field) { Some(Ipld::Map(map)) => map.contains_key(&self.key), _ => false, diff --git a/src/delegation/condition/excludes_all.rs b/src/delegation/condition/excludes_all.rs index ece60444..99048823 100644 --- a/src/delegation/condition/excludes_all.rs +++ b/src/delegation/condition/excludes_all.rs @@ -61,7 +61,7 @@ impl TryFrom for ExcludesAll { } impl Condition for ExcludesAll { - fn validate(&self, args: &arguments::Named) -> bool { + fn validate(&self, args: &arguments::Named) -> bool { if let Some(ipld) = args.get(&self.field) { let mut it = self.excludes_all.iter(); match ipld { diff --git a/src/delegation/condition/excludes_key.rs b/src/delegation/condition/excludes_key.rs index c2beba10..550014c6 100644 --- a/src/delegation/condition/excludes_key.rs +++ b/src/delegation/condition/excludes_key.rs @@ -60,7 +60,7 @@ impl TryFrom for ExcludesKey { } impl Condition for ExcludesKey { - fn validate(&self, args: &arguments::Named) -> bool { + fn validate(&self, args: &arguments::Named) -> bool { match args.get(&self.field) { Some(Ipld::Map(map)) => map.contains_key(&self.field), _ => true, diff --git a/src/delegation/condition/matches_regex.rs b/src/delegation/condition/matches_regex.rs index f6f9f4d6..4b7c9426 100644 --- a/src/delegation/condition/matches_regex.rs +++ b/src/delegation/condition/matches_regex.rs @@ -33,7 +33,7 @@ impl TryFrom for MatchesRegex { } impl Condition for MatchesRegex { - fn validate(&self, args: &arguments::Named) -> bool { + fn validate(&self, args: &arguments::Named) -> bool { match args.get(&self.field) { Some(Ipld::String(string)) => self.matches_regex.0.is_match(string), _ => false, diff --git a/src/delegation/condition/max_length.rs b/src/delegation/condition/max_length.rs index 7bb4ad97..94b0a8f5 100644 --- a/src/delegation/condition/max_length.rs +++ b/src/delegation/condition/max_length.rs @@ -33,7 +33,7 @@ impl TryFrom for MaxLength { } impl Condition for MaxLength { - fn validate(&self, args: &arguments::Named) -> bool { + fn validate(&self, args: &arguments::Named) -> bool { match args.get(&self.field) { Some(Ipld::String(string)) => string.len() <= self.max_length, Some(Ipld::List(list)) => list.len() <= self.max_length, diff --git a/src/delegation/condition/max_number.rs b/src/delegation/condition/max_number.rs index 81d90374..981e63ea 100644 --- a/src/delegation/condition/max_number.rs +++ b/src/delegation/condition/max_number.rs @@ -33,7 +33,7 @@ impl TryFrom for MaxNumber { } impl Condition for MaxNumber { - fn validate(&self, args: &arguments::Named) -> bool { + fn validate(&self, args: &arguments::Named) -> bool { match args.get(&self.field) { Some(Ipld::Integer(integer)) => match self.max_number { Number::Float(float) => *integer as f64 <= float, diff --git a/src/delegation/condition/min_length.rs b/src/delegation/condition/min_length.rs index 60f4c4ed..5d137df7 100644 --- a/src/delegation/condition/min_length.rs +++ b/src/delegation/condition/min_length.rs @@ -33,7 +33,7 @@ impl TryFrom for MinLength { } impl Condition for MinLength { - fn validate(&self, args: &arguments::Named) -> bool { + fn validate(&self, args: &arguments::Named) -> bool { match args.get(&self.field) { Some(Ipld::String(string)) => string.len() >= self.min_length, Some(Ipld::List(list)) => list.len() >= self.min_length, diff --git a/src/delegation/condition/min_number.rs b/src/delegation/condition/min_number.rs index 7b22a968..f6e1e1d6 100644 --- a/src/delegation/condition/min_number.rs +++ b/src/delegation/condition/min_number.rs @@ -33,7 +33,7 @@ impl TryFrom for MinNumber { } impl Condition for MinNumber { - fn validate(&self, args: &arguments::Named) -> bool { + fn validate(&self, args: &arguments::Named) -> bool { match args.get(&self.field) { Some(Ipld::Integer(integer)) => match self.min_number { Number::Float(float) => *integer as f64 >= float, diff --git a/src/delegation/condition/traits.rs b/src/delegation/condition/traits.rs index 1d9b977c..55c2738d 100644 --- a/src/delegation/condition/traits.rs +++ b/src/delegation/condition/traits.rs @@ -2,5 +2,5 @@ use crate::ability::arguments; use libipld_core::ipld::Ipld; pub trait Condition: TryFrom + Into { - fn validate(&self, args: &arguments::Named) -> bool; + fn validate(&self, args: &arguments::Named) -> bool; } diff --git a/src/delegation/delegatable.rs b/src/delegation/delegatable.rs index f6368a84..7dbbd2bd 100644 --- a/src/delegation/delegatable.rs +++ b/src/delegation/delegatable.rs @@ -1,9 +1,10 @@ use crate::ability::arguments; +use libipld_core::ipld::Ipld; // FIXME require checkable? pub trait Delegatable: Sized { /// A delegation with some arguments filled /// FIXME add more /// FIXME require CheckSame? - type Builder: TryInto + From + Into; + type Builder: TryInto + From + Into>; } diff --git a/src/delegation/payload.rs b/src/delegation/payload.rs index a2c89546..18f07363 100644 --- a/src/delegation/payload.rs +++ b/src/delegation/payload.rs @@ -91,7 +91,7 @@ impl From> for Ipld { // FIXME this likely should move to invocation impl< 'a, - T: Delegatable + Resolvable + Checkable + Clone + Into, + T: Delegatable + Resolvable + Checkable + Clone + Into>, C: Condition, > Payload { @@ -111,7 +111,7 @@ impl< to_check: invoked.clone().into(), // FIXME surely we can eliminate this clone }; - let args: arguments::Named = invoked.ability.clone().into(); + let args: arguments::Named = invoked.ability.clone().into(); let result = proofs.iter().fold(Ok(start), |acc, proof| { if let Ok(prev) = acc { @@ -156,7 +156,7 @@ struct Acc<'a, T: Checkable> { fn step<'a, T: Checkable, U: Delegatable, C: Condition>( prev: &Acc<'a, T>, proof: &Payload, - args: &arguments::Named, + args: &arguments::Named, now: SystemTime, ) -> Outcome<(), (), ()> // FIXME ^^^^^^^^^^^^ Outcome types @@ -229,7 +229,7 @@ struct InternalSerializer { #[serde(rename = "cmd")] command: String, #[serde(rename = "args")] - arguments: arguments::Named, + arguments: arguments::Named, #[serde(rename = "cond")] conditions: Vec, diff --git a/src/invocation/payload.rs b/src/invocation/payload.rs index caf2fffc..adf6dc9b 100644 --- a/src/invocation/payload.rs +++ b/src/invocation/payload.rs @@ -116,7 +116,7 @@ struct InternalSerializer { #[serde(rename = "cmd")] command: String, #[serde(rename = "args")] - arguments: arguments::Named, + arguments: arguments::Named, #[serde(rename = "prf")] proofs: Vec, @@ -196,7 +196,7 @@ impl TryFrom for InternalSerializer { // } // } -impl> From> for InternalSerializer { +impl>> From> for InternalSerializer { fn from(payload: Payload) -> Self { InternalSerializer { issuer: payload.issuer, diff --git a/src/invocation/promise/any.rs b/src/invocation/promise/any.rs index 3ec42b1b..8dbd4920 100644 --- a/src/invocation/promise/any.rs +++ b/src/invocation/promise/any.rs @@ -2,7 +2,7 @@ use super::{err::PromiseErr, ok::PromiseOk}; use crate::{ability::arguments, ipld::cid}; use libipld_core::{cid::Cid, error::SerdeError, ipld::Ipld, serde as ipld_serde}; use serde::{ - de::{DeserializeSeed, Deserializer, Error, Expected, IgnoredAny, MapAccess, Visitor}, + de::{DeserializeSeed, Deserializer, Error, Expected, MapAccess, Visitor}, Deserialize, Serialize, Serializer, }; use std::fmt; @@ -116,31 +116,28 @@ where } } -impl From> for arguments::Named -where - Ipld: From + From, -{ - fn from(p: PromiseAny) -> arguments::Named { +impl, E: Into> From> for arguments::Named { + fn from(p: PromiseAny) -> arguments::Named { match p { PromiseAny::Fulfilled(val) => { - arguments::Named::from_iter([("ucan/ok".into(), Ipld::from(val))]) + arguments::Named::from_iter([("ucan/ok".into(), val.into())]) } PromiseAny::Rejected(err) => { - arguments::Named::from_iter([("ucan/err".into(), Ipld::from(err))]) + arguments::Named::from_iter([("ucan/err".into(), err.into())]) } PromiseAny::Pending(cid) => { - arguments::Named::from_iter([("await/*".into(), Ipld::from(cid))]) + arguments::Named::from_iter([("await/*".into(), cid.into())]) } } } } -impl TryFrom<&'a Ipld>, E: for<'a> TryFrom<&'a Ipld>> TryFrom +impl TryFrom<&'a Ipld>, E: for<'a> TryFrom<&'a Ipld>> TryFrom> for PromiseAny { type Error = (); // FIXME - fn try_from(args: arguments::Named) -> Result, Self::Error> { + fn try_from(args: arguments::Named) -> Result, Self::Error> { if args.len() != 1 { return Err(()); } diff --git a/src/invocation/promise/err.rs b/src/invocation/promise/err.rs index f29acfa2..623808ec 100644 --- a/src/invocation/promise/err.rs +++ b/src/invocation/promise/err.rs @@ -55,11 +55,11 @@ impl TryFrom for PromiseErr { } } -impl> From> for arguments::Named +impl>> From> for arguments::Named where Ipld: From, { - fn from(p: PromiseErr) -> arguments::Named { + fn from(p: PromiseErr) -> arguments::Named { match p { PromiseErr::Rejected(err) => err.into(), PromiseErr::Pending(cid) => { @@ -69,10 +69,10 @@ where } } -impl> TryFrom for PromiseErr { +impl> TryFrom> for PromiseErr { type Error = >::Error; - fn try_from(args: arguments::Named) -> Result, Self::Error> { + fn try_from(args: arguments::Named) -> Result, Self::Error> { if let Some(ipld) = args.get("ucan/err") { if args.len() == 1 { if let Ok(cid::Newtype { cid }) = cid::Newtype::try_from(ipld) { diff --git a/src/invocation/promise/ok.rs b/src/invocation/promise/ok.rs index f9dfb2ca..70623609 100644 --- a/src/invocation/promise/ok.rs +++ b/src/invocation/promise/ok.rs @@ -56,11 +56,11 @@ impl TryFrom for PromiseOk { } } -impl> From> for arguments::Named +impl>> From> for arguments::Named where Ipld: From, { - fn from(p: PromiseOk) -> arguments::Named { + fn from(p: PromiseOk) -> arguments::Named { match p { PromiseOk::Fulfilled(val) => val.into(), PromiseOk::Pending(cid) => { @@ -70,10 +70,10 @@ where } } -impl> TryFrom for PromiseOk { +impl> TryFrom> for PromiseOk { type Error = >::Error; - fn try_from(args: arguments::Named) -> Result, Self::Error> { + fn try_from(args: arguments::Named) -> Result, Self::Error> { if let Some(ipld) = args.get("ucan/ok") { if args.len() == 1 { if let Ok(cid::Newtype { cid }) = cid::Newtype::try_from(ipld) { diff --git a/src/invocation/promise/resolves.rs b/src/invocation/promise/resolves.rs index 8343e524..c06b458b 100644 --- a/src/invocation/promise/resolves.rs +++ b/src/invocation/promise/resolves.rs @@ -9,6 +9,22 @@ pub enum Resolves { Err(PromiseErr), } +impl Resolves> { + // FIXME Helpful for serde, maybe extract to a trait? + pub fn resolved_none(&self) -> bool { + match self { + Resolves::Ok(p_ok) => match p_ok { + PromiseOk::Fulfilled(None) => true, + _ => false, + }, + Resolves::Err(p_err) => match p_err { + PromiseErr::Rejected(None) => true, + _ => false, + }, + } + } +} + impl Resolves { pub fn try_resolve(self) -> Result> { match self { diff --git a/src/invocation/resolvable.rs b/src/invocation/resolvable.rs index 1ce955a3..bc9103ea 100644 --- a/src/invocation/resolvable.rs +++ b/src/invocation/resolvable.rs @@ -1,7 +1,9 @@ use crate::ability::arguments; +use libipld_core::ipld::Ipld; pub trait Resolvable: Sized { - type Promised: TryInto + From + Into; -} + type Promised: Into>; -// NOTE Promised into args should cover all of the values + // FIXME indeed needed to get teh right err type + fn try_resolve(promised: Self::Promised) -> Result; +} diff --git a/src/ipld.rs b/src/ipld.rs index 11e55325..7571fb44 100644 --- a/src/ipld.rs +++ b/src/ipld.rs @@ -1,6 +1,7 @@ //! Helpers for working with [`Ipld`] pub mod cid; +pub mod promised; use libipld_core::ipld::Ipld; use serde::{Deserialize, Serialize}; @@ -53,7 +54,7 @@ impl From for Ipld { #[cfg(target_arch = "wasm32")] impl Newtype { - pub fn try_into_jsvalue>(js_val: JsValue) -> Result + pub fn try_from_js>(js_val: JsValue) -> Result where JsError: From<>::Error>, { diff --git a/src/ipld/promised.rs b/src/ipld/promised.rs new file mode 100644 index 00000000..5f78741e --- /dev/null +++ b/src/ipld/promised.rs @@ -0,0 +1,177 @@ +use crate::invocation::promise::{Promise, PromiseAny, PromiseErr, PromiseOk, Resolves}; +use libipld_core::{cid::Cid, ipld::Ipld}; +use serde::{Deserialize, Serialize}; +use std::collections::BTreeMap; + +/// A promise to recursively resolve to an [`Ipld`] value. +/// +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub enum PromisedIpld { + /// Lifted [`Ipld::Null`] + Null, + + /// Lifted [`Ipld::Bool`] + Bool(bool), + + /// Lifted [`Ipld::Integer`] + Integer(i128), + + /// Lifted [`Ipld::Float`] + Float(f64), + + /// Lifted [`Ipld::String`] + String(String), + + /// Lifted [`Ipld::Bytes`] (byte array) + Bytes(Vec), + + /// [`Ipld::List`], but where the values are [`PromiseIpld`]. + List(Vec), + + /// [`Ipld::Map`], but where the values are [`PromiseIpld`]. + Map(BTreeMap), + + /// Lifted [`Ipld::Link`] + Link(Cid), + + /// The `await/ok` promise + PromiseOk(Cid), + + /// The `await/err` promise + PromiseErr(Cid), + + /// The `await/*` promise + PromiseAny(Cid), +} + +impl PromisedIpld { + pub fn is_resolved(&self) -> bool { + match self { + PromisedIpld::Null => true, + PromisedIpld::Bool(_) => true, + PromisedIpld::Integer(_) => true, + PromisedIpld::Float(_) => true, + PromisedIpld::String(_) => true, + PromisedIpld::Bytes(_) => true, + PromisedIpld::List(list) => list.iter().all(PromisedIpld::is_resolved), + PromisedIpld::Map(map) => map.values().all(PromisedIpld::is_resolved), + PromisedIpld::Link(_) => true, + PromisedIpld::PromiseOk(_) => false, + PromisedIpld::PromiseErr(_) => false, + PromisedIpld::PromiseAny(_) => false, + } + } + + pub fn is_pending(&self) -> bool { + !self.is_resolved() + } +} + +impl From for PromisedIpld { + fn from(ipld: Ipld) -> Self { + match ipld { + Ipld::Null => PromisedIpld::Null, + Ipld::Bool(b) => PromisedIpld::Bool(b), + Ipld::Integer(i) => PromisedIpld::Integer(i), + Ipld::Float(f) => PromisedIpld::Float(f), + Ipld::String(s) => PromisedIpld::String(s), + Ipld::Bytes(b) => PromisedIpld::Bytes(b), + Ipld::List(list) => { + PromisedIpld::List(list.into_iter().map(PromisedIpld::from).collect()) + } + Ipld::Map(map) => PromisedIpld::Map( + map.into_iter() + .map(|(k, v)| (k, PromisedIpld::from(v))) + .collect(), + ), + Ipld::Link(cid) => PromisedIpld::Link(cid), + } + } +} + +impl From> for PromisedIpld { + fn from(promise: PromiseOk) -> Self { + match promise { + PromiseOk::Fulfilled(ipld) => ipld.into(), + PromiseOk::Pending(cid) => PromisedIpld::PromiseOk(cid), + } + } +} + +impl From> for PromisedIpld { + fn from(promise: PromiseErr) -> Self { + match promise { + PromiseErr::Rejected(ipld) => ipld.into(), + PromiseErr::Pending(cid) => PromisedIpld::PromiseErr(cid), + } + } +} + +impl From> for PromisedIpld { + fn from(promise: PromiseAny) -> Self { + match promise { + PromiseAny::Fulfilled(ipld) => ipld.into(), + PromiseAny::Rejected(ipld) => ipld.into(), + PromiseAny::Pending(cid) => PromisedIpld::PromiseAny(cid), + } + } +} + +impl From> for PromisedIpld { + fn from(resolves: Resolves) -> Self { + match resolves { + Resolves::Ok(p_ok) => p_ok.into(), + Resolves::Err(p_err) => p_err.into(), + } + } +} + +impl From> for PromisedIpld { + fn from(promise: Promise) -> Self { + match promise { + Promise::Ok(p_ok) => p_ok.into(), + Promise::Err(p_err) => p_err.into(), + Promise::Any(p_any) => p_any.into(), + } + } +} + +impl TryFrom for Ipld { + type Error = PromisedIpld; + + fn try_from(p: PromisedIpld) -> Result { + match p { + PromisedIpld::Null => Ok(Ipld::Null), + PromisedIpld::Bool(b) => Ok(Ipld::Bool(b)), + PromisedIpld::Integer(i) => Ok(Ipld::Integer(i)), + PromisedIpld::Float(f) => Ok(Ipld::Float(f)), + PromisedIpld::String(s) => Ok(Ipld::String(s)), + PromisedIpld::Bytes(b) => Ok(Ipld::Bytes(b)), + PromisedIpld::List(ref list) => { + let result: Result, ()> = list.iter().try_fold(vec![], |mut acc, x| { + let ipld = Ipld::try_from(x.clone()).map_err(|_| ())?; + acc.push(ipld); + Ok(acc) + }); + + Ok(Ipld::List(result.map_err(|_| p.clone())?)) + } + PromisedIpld::Map(ref map) => { + let map: Result, ()> = + map.into_iter() + .try_fold(BTreeMap::new(), |mut acc, (k, v)| { + // FIXME non-tail recursion, and maybe even repeated clones + let ipld = Ipld::try_from(v.clone()).map_err(|_| ())?; + acc.insert(k.clone(), ipld); + Ok(acc) + }); + + Ok(Ipld::Map(map.map_err(|_| p)?)) + } + PromisedIpld::Link(cid) => Ok(Ipld::Link(cid)), + PromisedIpld::PromiseOk(_cid) => Err(p), + PromisedIpld::PromiseErr(_cid) => Err(p), + PromisedIpld::PromiseAny(_cid) => Err(p), + } + } +} diff --git a/src/reader.rs b/src/reader.rs index ecb03a97..7ba78b78 100644 --- a/src/reader.rs +++ b/src/reader.rs @@ -3,9 +3,10 @@ use crate::{ ability::{arguments, command::ToCommand}, delegation::Delegatable, - invocation::Resolvable, + invocation::{promise, Resolvable}, proof::{checkable::Checkable, same::CheckSame}, }; +use libipld_core::ipld::Ipld; use serde::{Deserialize, Serialize}; /// A struct that attaches an ambient environment to a value @@ -151,7 +152,7 @@ impl Reader { } } -impl> From> for arguments::Named { +impl>> From> for arguments::Named
{ fn from(reader: Reader) -> Self { reader.val.into() } @@ -186,7 +187,7 @@ impl ToCommand for Reader { #[derive(Clone, PartialEq, Debug, Serialize, Deserialize)] pub struct Builder(pub T); -impl> From> for arguments::Named { +impl>> From> for arguments::Named { fn from(builder: Builder) -> Self { builder.0.into() } @@ -198,7 +199,7 @@ impl From> for Reader> { } } -impl> Delegatable for Reader { +impl>> Delegatable for Reader { type Builder = Reader>; } @@ -218,7 +219,7 @@ impl> Delegatable for Reader #[derive(Clone, PartialEq, Debug, Serialize, Deserialize)] pub struct Promised(pub T); -impl> From> for arguments::Named { +impl>> From> for arguments::Named { fn from(promised: Promised) -> Self { promised.0.into() } @@ -242,6 +243,22 @@ impl From>> for Reader { } } -impl> Resolvable for Reader { - type Promised = Reader>; +impl Resolvable for Reader +where + Reader: Into>, +{ + type Promised = Reader; + + fn try_resolve(promised: Self::Promised) -> Result { + match T::try_resolve(promised.val) { + Ok(val) => Ok(Reader { + env: promised.env, + val, + }), + Err(val) => Err(Reader { + env: promised.env, + val, + }), + } + } } diff --git a/src/receipt/payload.rs b/src/receipt/payload.rs index 81311154..a76939c5 100644 --- a/src/receipt/payload.rs +++ b/src/receipt/payload.rs @@ -11,7 +11,7 @@ pub struct Payload { pub issuer: Did, pub ran: Cid, - pub out: Result, + pub out: Result>, pub next: Vec, // FIXME rename here or in spec? pub proofs: Vec, @@ -83,7 +83,7 @@ where issuer: Did, ran: Cid, - out: Result, + out: Result>, next: Vec, // FIXME rename here or in spec? #[serde(rename = "prf")] diff --git a/src/task.rs b/src/task.rs index f9c616db..454b6876 100644 --- a/src/task.rs +++ b/src/task.rs @@ -34,7 +34,7 @@ pub struct Task { pub cmd: String, /// The arguments to the command. - pub args: arguments::Named, + pub args: arguments::Named, } impl TryFrom for Task { From a1d661c8fc19082624fd22b5cbd273192d8f0898 Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Fri, 9 Feb 2024 14:56:43 -0800 Subject: [PATCH 061/188] Lots of cleanup --- src/ability/arguments.rs | 246 +----------------- src/ability/arguments/named.rs | 312 +++++++++++++++++++++++ src/ability/crud/any.rs | 9 +- src/ability/crud/create.rs | 2 +- src/ability/crud/error.rs | 25 +- src/ability/crud/mutate.rs | 18 +- src/ability/crud/read.rs | 110 +++++--- src/ability/crud/update.rs | 63 ++++- src/ability/dynamic.rs | 4 +- src/ability/js/parentful.rs | 3 +- src/ability/js/parentless.rs | 3 +- src/agent.rs | 2 +- src/delegation.rs | 7 +- src/delegation/condition.rs | 37 ++- src/delegation/condition/contains_key.rs | 2 +- src/delegation/condition/excludes_all.rs | 2 +- src/delegation/condition/excludes_key.rs | 2 +- src/delegation/condition/traits.rs | 4 + src/delegation/payload.rs | 2 +- src/delegation/store.rs | 2 +- src/invocation/promise.rs | 18 ++ src/invocation/promise/any.rs | 4 +- src/invocation/promise/ok.rs | 2 +- src/invocation/promise/resolves.rs | 14 + src/invocation/resolvable.rs | 5 + src/ipld.rs | 208 +-------------- src/ipld/cid.rs | 8 +- src/ipld/enriched.rs | 90 +++++++ src/ipld/newtype.rs | 201 +++++++++++++++ src/ipld/promised.rs | 205 ++++----------- src/proof.rs | 1 + src/proof/error.rs | 4 +- src/proof/util.rs | 15 ++ src/task.rs | 3 + 34 files changed, 919 insertions(+), 714 deletions(-) create mode 100644 src/ability/arguments/named.rs create mode 100644 src/ipld/enriched.rs create mode 100644 src/ipld/newtype.rs create mode 100644 src/proof/util.rs diff --git a/src/ability/arguments.rs b/src/ability/arguments.rs index 95a4085b..305feadb 100644 --- a/src/ability/arguments.rs +++ b/src/ability/arguments.rs @@ -1,246 +1,10 @@ //! Utilities for ability arguments -use libipld_core::{error::SerdeError, ipld::Ipld, serde as ipld_serde}; -use serde::{Deserialize, Serialize}; -use std::collections::BTreeMap; -use thiserror::Error; +mod named; -#[cfg(target_arch = "wasm32")] -use wasm_bindgen::prelude::*; +pub use named::{Named, NamedError}; -#[cfg(target_arch = "wasm32")] -use js_sys::{Array, Map, Object, Reflect}; +use crate::{invocation::promise::Resolves, ipld}; -#[cfg(target_arch = "wasm32")] -use crate::ipld; - -/// Named arguments -/// -/// Being such a common pattern, but with so few trait implementations, -/// [`Named`] is a newtype wrapper around unstructured named args: `BTreeMap`. -/// -/// # Examples -/// -/// ```rust -/// # use ucan::ability::arguments; -/// # use url::Url; -/// # use libipld::{ipld, ipld::Ipld}; -/// # -/// struct Execute { -/// program: Url, -/// instructions: arguments::Named, -/// } -/// -/// let ability = Execute { -/// program: Url::parse("file://host.name/path/to/exe").unwrap(), -/// instructions: arguments::Named::from_iter([ -/// ("bold".into(), ipld!(true)), -/// ("message".into(), ipld!("hello world")), -/// ]) -/// }; -/// -/// assert_eq!(ability.instructions.get("bold"), Some(&ipld!(true))); -/// ``` -#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] -pub struct Named(pub BTreeMap); - -impl Named { - /// Create a new, empty `Named` instance. - pub fn new() -> Self { - Default::default() - } - - /// Get the value associated with a key. - /// - /// An alias for [`BTreeMap::insert`]. - pub fn get(&self, key: &str) -> Option<&T> { - self.0.get(key) - } - - /// Inserts a key-value pair. - /// - /// An alias for [`BTreeMap::insert`]. - pub fn insert(&mut self, key: String, value: T) -> Option { - self.0.insert(key, value) - } - - /// Gets an iterator over the entries, sorted by key. - /// - /// A wrapper around [`BTreeMap::iter`]. - pub fn iter(&self) -> impl Iterator { - self.0.iter() - } - - /// The number of entries in. - /// - /// A wrapper around [`BTreeMap::len`]. - pub fn len(&self) -> usize { - self.0.len() - } -} - -impl Default for Named { - fn default() -> Self { - Named(BTreeMap::new()) - } -} - -impl IntoIterator for Named { - type Item = (String, T); - type IntoIter = std::collections::btree_map::IntoIter; - - fn into_iter(self) -> Self::IntoIter { - self.0.into_iter() - } -} - -impl FromIterator<(String, T)> for Named { - fn from_iter>(iter: I) -> Self { - Named(iter.into_iter().collect()) - } -} - -impl Deserialize<'de>> TryFrom for Named { - type Error = SerdeError; - - fn try_from(ipld: Ipld) -> Result { - ipld_serde::from_ipld(ipld) - } -} - -impl From> for Ipld { - fn from(arguments: Named) -> Self { - ipld_serde::to_ipld(arguments).unwrap() - } -} - -#[cfg(target_arch = "wasm32")] -impl> From> for Object { - fn from(arguments: Named) -> Self { - let obj = Object::new(); - for (k, v) in arguments.0 { - Reflect::set(&obj, &k.into(), v.into()).unwrap(); - } - obj - } -} - -#[cfg(target_arch = "wasm32")] -impl From> for Object { - fn from(arguments: Named) -> Self { - let obj = Object::new(); - for (k, v) in arguments.0 { - Reflect::set(&obj, &k.into(), &ipld::Newtype(v).into()).unwrap(); - } - obj - } -} - -// NOTE saves a few cycles while calling by not cloning -// the extra Object fields that we're not going to use -#[cfg(target_arch = "wasm32")] -impl> From<&Object> for Named { - // FIXME probbaly needs to be a try_from - fn from(obj: &Object) -> Self { - let btree = Object::entries(obj) - .iter() - .map(|entry| { - let entry = Array::from(&entry); - let key = entry.get(0).as_string().unwrap(); // FIXME - let value = T::try_from(entry.get(1)).unwrap().0; // FIXME - (key, value) - }) - .collect::>(); - - Named(btree) - } -} - -// NOTE saves a few cycles while calling by not cloning -// the extra Object fields that we're not going to use -#[cfg(target_arch = "wasm32")] -impl From<&Object> for Named { - // FIXME probbaly needs to be a try_from - fn from(obj: &Object) -> Self { - let btree = Object::entries(obj) - .iter() - .map(|entry| { - let entry = Array::from(&entry); - let key = entry.get(0).as_string().unwrap(); // FIXME - let value = ipld::Newtype::try_from(entry.get(1)).unwrap().0; - (key, value) - }) - .collect::>(); - - Named(btree) - } -} - -#[cfg(target_arch = "wasm32")] -impl From> for JsValue { - fn from(arguments: Named) -> Self { - arguments - .0 - .iter() - .fold(Map::new(), |map, (ref k, v)| { - map.set(&JsValue::from_str(k), &JsValue::from(v.clone())); - map - }) - .into() - } -} - -#[cfg(target_arch = "wasm32")] -impl From> for JsValue { - fn from(arguments: Named) -> Self { - arguments - .0 - .iter() - .fold(Map::new(), |map, (ref k, v)| { - map.set( - &JsValue::from_str(k), - &JsValue::from(ipld::Newtype(v.clone())), - ); - map - }) - .into() - } -} - -#[cfg(target_arch = "wasm32")] -impl TryFrom for Named { - type Error = (); // FIXME - - fn try_from(js: JsValue) -> Result { - match T::try_from(js) { - Err(()) => Err(()), // FIXME surface that we can't parse at all - Ok(Ipld::Map(map)) => Ok(Named(map)), - Ok(_wrong_ipld) => Err(()), // FIXME surface that we have the wrong type - } - } -} - -#[cfg(target_arch = "wasm32")] -impl TryFrom for Named { - type Error = (); // FIXME - - fn try_from(js: JsValue) -> Result { - match ipld::Newtype::try_from(js).map(|newtype| newtype.0) { - Err(()) => Err(()), // FIXME surface that we can't parse at all - Ok(Ipld::Map(map)) => Ok(Named(map)), - Ok(_wrong_ipld) => Err(()), // FIXME surface that we have the wrong type - } - } -} - -/// Errors for [`arguments::Named`][Named]. -#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Error)] -pub enum NamedError { - /// A required field was missing. - #[error("Missing arguments::Named field {0}")] - FieldMissing(String), - - /// The value at the named field didn't match the expected value. - #[error("arguments::Named field {0}: value doesn't match")] - FieldValueMismatch(String), -} +// FIXME move under invoc::promise? +pub type Promised = Resolves>; diff --git a/src/ability/arguments/named.rs b/src/ability/arguments/named.rs new file mode 100644 index 00000000..a63e6ee0 --- /dev/null +++ b/src/ability/arguments/named.rs @@ -0,0 +1,312 @@ +use crate::{invocation::promise, ipld}; +use libipld_core::{error::SerdeError, ipld::Ipld, serde as ipld_serde}; +use serde::{Deserialize, Serialize}; +use std::collections::BTreeMap; +use thiserror::Error; + +#[cfg(target_arch = "wasm32")] +use wasm_bindgen::prelude::*; + +#[cfg(target_arch = "wasm32")] +use js_sys::{Array, Map, Object, Reflect}; + +#[cfg(target_arch = "wasm32")] +use crate::ipld; + +/// Named arguments +/// +/// Being such a common pattern, but with so few trait implementations, +/// [`Named`] is a newtype wrapper around unstructured named args: `BTreeMap`. +/// +/// # Examples +/// +/// ```rust +/// # use ucan::ability::arguments; +/// # use url::Url; +/// # use libipld::{ipld, ipld::Ipld}; +/// # +/// struct Execute { +/// program: Url, +/// instructions: arguments::Named, +/// } +/// +/// let ability = Execute { +/// program: Url::parse("file://host.name/path/to/exe").unwrap(), +/// instructions: arguments::Named::from_iter([ +/// ("bold".into(), ipld!(true)), +/// ("message".into(), ipld!("hello world")), +/// ]) +/// }; +/// +/// assert_eq!(ability.instructions.get("bold"), Some(&ipld!(true))); +/// ``` +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub struct Named(pub BTreeMap); + +impl Named { + /// Create a new, empty `Named` instance. + pub fn new() -> Self { + Default::default() + } + + /// Get the value associated with a key. + /// + /// An alias for [`BTreeMap::insert`]. + pub fn get(&self, key: &str) -> Option<&T> { + self.0.get(key) + } + + /// Inserts a key-value pair. + /// + /// An alias for [`BTreeMap::insert`]. + pub fn insert(&mut self, key: String, value: T) -> Option { + self.0.insert(key, value) + } + + /// Gets an iterator over the entries, sorted by key. + /// + /// A wrapper around [`BTreeMap::iter`]. + pub fn iter(&self) -> impl Iterator { + self.0.iter() + } + + /// The number of entries in. + /// + /// A wrapper around [`BTreeMap::len`]. + pub fn len(&self) -> usize { + self.0.len() + } + + pub fn contains(&self, other: &Named) -> Result<(), NamedError> + where + T: PartialEq, + { + // `other` should usually be smaller than `self` + for (k, other_v) in other.iter() { + if let Some(self_v) = self.get(k) { + if *self_v != *other_v { + return Err(NamedError::FieldValueMismatch(k.clone())); + } + } else { + return Err(NamedError::FieldMissing(k.clone())); + } + } + + Ok(()) + } +} + +impl Default for Named { + fn default() -> Self { + Named(BTreeMap::new()) + } +} + +impl IntoIterator for Named { + type Item = (String, T); + type IntoIter = std::collections::btree_map::IntoIter; + + fn into_iter(self) -> Self::IntoIter { + self.0.into_iter() + } +} + +impl FromIterator<(String, T)> for Named { + fn from_iter>(iter: I) -> Self { + Named(iter.into_iter().collect()) + } +} + +impl Deserialize<'de>> TryFrom for Named { + type Error = SerdeError; + + fn try_from(ipld: Ipld) -> Result { + ipld_serde::from_ipld(ipld) + } +} + +impl> From> for Ipld { + fn from(arguments: Named) -> Self { + Ipld::Map( + arguments + .0 + .into_iter() + .map(|(k, v)| (k, v.into())) + .collect::>(), + ) + } +} + +#[cfg(target_arch = "wasm32")] +impl> From> for Object { + fn from(arguments: Named) -> Self { + let obj = Object::new(); + for (k, v) in arguments.0 { + Reflect::set(&obj, &k.into(), v.into()).unwrap(); + } + obj + } +} + +// NOTE saves a few cycles while calling by not cloning +// the extra Object fields that we're not going to use +#[cfg(target_arch = "wasm32")] +impl> From<&Object> for Named { + // FIXME probbaly needs to be a try_from + fn from(obj: &Object) -> Self { + let btree = Object::entries(obj) + .iter() + .map(|entry| { + let entry = Array::from(&entry); + let key = entry.get(0).as_string().unwrap(); // FIXME + let value = T::try_from(entry.get(1)).unwrap().0; // FIXME + (key, value) + }) + .collect::>(); + + Named(btree) + } +} + +#[cfg(target_arch = "wasm32")] +impl From> for JsValue { + fn from(arguments: Named) -> Self { + arguments + .0 + .iter() + .fold(Map::new(), |map, (ref k, v)| { + map.set(&JsValue::from_str(k), &JsValue::from(v.clone())); + map + }) + .into() + } +} + +#[cfg(target_arch = "wasm32")] +impl TryFrom for Named { + type Error = (); // FIXME + + fn try_from(js: JsValue) -> Result { + match T::try_from(js) { + Err(()) => Err(()), // FIXME surface that we can't parse at all + Ok(Ipld::Map(map)) => Ok(Named(map)), + Ok(_wrong_ipld) => Err(()), // FIXME surface that we have the wrong type + } + } +} + +use crate::invocation::promise::Resolves; + +// impl> TryFrom> for Named { +// type Error = (); +// +// fn try_from(named: Named) -> Result { +// let btree = named +// .0 +// .into_iter() +// .map(|(k, v)| { +// let ipld = v.try_into().map_err(|_| ())?; +// Ok((k, ipld)) +// }) +// .collect::>()?; +// +// Ok(Named(btree)) +// } +// } +// the trait `From>` is not implemented for `Named` + +// impl From> for Named> { +// fn from(named: Named) -> Named> { +// named +// .into_iter() +// .map(|(k, v)| (k, promise::PromiseOk::Fulfilled(v).into())) +// .collect() +// } +// } +// impl> TryFrom> for Named> { +// type Error = Named; +// +// fn try_from(named: Named) -> Result>, Self::Error> { +// named +// .into_iter() +// .try_fold(Named::new(), |mut btree, (k, v)| { +// let ipld = v.try_into().map_err(|_| named.clone())?; +// btree.insert(k, promise::PromiseOk::Fulfilled(ipld).into()); +// Ok(btree) +// }) +// } +// } + +// FIXME abstract over both of these? +impl From> for Named> { + fn from(named: Named) -> Named> { + let btree: BTreeMap> = named + .into_iter() + .map(|(k, v)| { + let promised: ipld::Promised = v.into(); + (k, Resolves::new(promised)) + }) + .collect(); + + Named(btree) + } +} + +impl From> for Named { + fn from(named: Named) -> Named { + let btree: BTreeMap = + named.into_iter().map(|(k, v)| (k, v.into())).collect(); + + Named(btree) + } +} + +impl TryFrom> for Named { + type Error = Named; + + fn try_from(named: Named) -> Result { + // FIXME lots of clone + // FIXME idea: what if they implemet a is_resoled, and then the try_from? + // This lets us check by ref, and then do the conversion and unwrap + named + .iter() + .try_fold(Named::new(), |mut acc, (ref k, v)| { + let ipld = v.clone().try_into().map_err(|_| ())?; + acc.insert(k.to_string(), ipld); + Ok(acc) + }) + .map_err(|()| named.clone()) + } +} + +impl TryFrom>> for Named +where + Ipld: TryFrom, +{ + type Error = Resolves>; + + fn try_from(resolves: Resolves>) -> Result { + resolves + .clone() // FIXME could be a pretty heavy clone + .try_resolve()? + .into_iter() + .try_fold(Named::new(), |mut btree, (k, v)| { + let ipld = v.try_into().map_err(|_| ())?; + btree.insert(k, ipld); + Ok(btree) + }) + .map_err(|_: ()| resolves) + } +} + +/// Errors for [`arguments::Named`][Named]. +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Error)] +pub enum NamedError { + /// A required field was missing. + #[error("Missing arguments::Named field {0}")] + FieldMissing(String), + + /// The value at the named field didn't match the expected value. + #[error("arguments::Named field {0}: value doesn't match")] + FieldValueMismatch(String), +} diff --git a/src/ability/crud/any.rs b/src/ability/crud/any.rs index 897527d5..077d1d70 100644 --- a/src/ability/crud/any.rs +++ b/src/ability/crud/any.rs @@ -1,9 +1,8 @@ //! "Any" CRUD ability (superclass of all CRUD abilities) -use super::error::PathError; use crate::{ ability::command::Command, - proof::{parentless::NoParents, same::CheckSame}, + proof::{error::OptionalFieldError, parentless::NoParents, same::CheckSame}, }; use libipld_core::{error::SerdeError, ipld::Ipld, serde as ipld_serde}; use serde::{Deserialize, Serialize}; @@ -68,13 +67,13 @@ impl Command for Any { impl NoParents for Any {} impl CheckSame for Any { - type Error = PathError; + type Error = OptionalFieldError; fn check_same(&self, proof: &Self) -> Result<(), Self::Error> { if let Some(path) = &self.path { - let proof_path = proof.path.as_ref().ok_or(PathError::Missing)?; + let proof_path = proof.path.as_ref().ok_or(OptionalFieldError::Missing)?; if path != proof_path { - return Err(PathError::Mismatch); + return Err(OptionalFieldError::Unequal); } } diff --git a/src/ability/crud/create.rs b/src/ability/crud/create.rs index 71044525..4e3e4b22 100644 --- a/src/ability/crud/create.rs +++ b/src/ability/crud/create.rs @@ -54,7 +54,7 @@ impl CheckSame for Create { impl CheckParents for Create { type Parents = MutableParents; - type ParentError = (); + type ParentError = (); // FIXME fn check_parent(&self, other: &Self::Parents) -> Result<(), Self::ParentError> { if let Some(self_path) = &self.path { diff --git a/src/ability/crud/error.rs b/src/ability/crud/error.rs index adaf33cc..544ca58c 100644 --- a/src/ability/crud/error.rs +++ b/src/ability/crud/error.rs @@ -1,4 +1,4 @@ -use crate::ability::arguments; +use crate::{ability::arguments, proof::error::OptionalFieldError}; use serde::{Deserialize, Serialize}; use thiserror::Error; @@ -8,21 +8,22 @@ use wasm_bindgen::prelude::*; #[derive(Debug, Clone, PartialEq, Error, Serialize, Deserialize)] pub enum ProofError { #[error("An issue with the path field")] - Path(#[from] PathError), + Path(#[from] OptionalFieldError), #[error("An issue with the (inner) arguments field")] Args(#[from] arguments::NamedError), - #[error("Proof `args` were expected, but none were present")] + #[error("Proof has `args`, but none were present on delegate")] MissingProofArgs, } -#[derive(Debug, Clone, PartialEq, Error, Serialize, Deserialize)] -#[cfg_attr(target_arch = "wasm32", wasm_bindgen)] -pub enum PathError { - #[error("Path required in proof, but was not present")] - Missing, - - #[error("Proof path did not match")] - Mismatch, -} +// FIXME Is this just OptionalFieldError? +// #[derive(Debug, Clone, PartialEq, Error, Serialize, Deserialize)] +// #[cfg_attr(target_arch = "wasm32", wasm_bindgen)] +// pub enum PathError { +// #[error("Path required in proof, but was not present")] +// Missing, +// +// #[error("Proof path did not match")] +// Mismatch, +// } diff --git a/src/ability/crud/mutate.rs b/src/ability/crud/mutate.rs index 32563b6f..ab8c85a3 100644 --- a/src/ability/crud/mutate.rs +++ b/src/ability/crud/mutate.rs @@ -1,9 +1,11 @@ //! The delegation superclass for all mutable CRUD actions. -use super::error::PathError; use crate::{ ability::command::Command, - proof::{checkable::Checkable, parentful::Parentful, parents::CheckParents, same::CheckSame}, + proof::{ + checkable::Checkable, error::OptionalFieldError, parentful::Parentful, + parents::CheckParents, same::CheckSame, + }, }; use libipld_core::{error::SerdeError, ipld::Ipld, serde as ipld_serde}; use serde::{Deserialize, Serialize}; @@ -82,13 +84,13 @@ impl Checkable for Mutate { } impl CheckSame for Mutate { - type Error = PathError; + type Error = OptionalFieldError; fn check_same(&self, proof: &Self) -> Result<(), Self::Error> { if let Some(path) = &self.path { - let proof_path = proof.path.as_ref().ok_or(PathError::Missing)?; + let proof_path = proof.path.as_ref().ok_or(OptionalFieldError::Missing)?; if path != proof_path { - return Err(PathError::Mismatch); + return Err(OptionalFieldError::Unequal); } } @@ -98,13 +100,13 @@ impl CheckSame for Mutate { impl CheckParents for Mutate { type Parents = super::Any; - type ParentError = PathError; + type ParentError = OptionalFieldError; fn check_parent(&self, crud_any: &Self::Parents) -> Result<(), Self::ParentError> { if let Some(path) = &self.path { - let proof_path = crud_any.path.as_ref().ok_or(PathError::Missing)?; + let proof_path = crud_any.path.as_ref().ok_or(OptionalFieldError::Missing)?; if path != proof_path { - return Err(PathError::Mismatch); + return Err(OptionalFieldError::Unequal); } } diff --git a/src/ability/crud/read.rs b/src/ability/crud/read.rs index e29f6b02..bdc0b764 100644 --- a/src/ability/crud/read.rs +++ b/src/ability/crud/read.rs @@ -4,11 +4,14 @@ //! * See the [`Builder`] to view the [delegation chain](./type.Builder.html#delegation-hierarchy). //! * The invocation [Lifecycle](./struct.Ready.html#lifecycle) can be found on [`Ready`] or [`Promised`]. -use super::error::{PathError, ProofError}; +use super::error::ProofError; use crate::{ ability::{arguments, command::Command}, - invocation::promise, - proof::{checkable::Checkable, parentful::Parentful, parents::CheckParents, same::CheckSame}, + invocation::{promise, promise::Resolves, Resolvable}, + proof::{ + checkable::Checkable, error::OptionalFieldError, parentful::Parentful, + parents::CheckParents, same::CheckSame, util::check_optional, + }, }; use libipld_core::{error::SerdeError, ipld::Ipld, serde as ipld_serde}; use serde::{Deserialize, Serialize}; @@ -20,9 +23,6 @@ use wasm_bindgen::prelude::*; #[cfg_attr(doc, aquamarine::aquamarine)] /// The CRUD ability to retrieve data from a resource. /// -/// Note that the delegation [`Builder`] has the exact same -/// fields in this case. -/// /// # Invocation /// /// The executable/dispatchable variant of the `msg/send` ability. @@ -58,17 +58,13 @@ pub struct Ready { #[serde(default, skip_serializing_if = "Option::is_none")] pub path: Option, - /// Optional additional arugments to pass in the request. - #[serde(default, skip_serializing_if = "Option::is_none")] - pub args: Option>, + /// Additional arugments to pass in the request. + pub args: arguments::Named, } #[cfg_attr(doc, aquamarine::aquamarine)] /// The CRUD ability to retrieve data from a resource. /// -/// Note that the delegation [`Builder`] has the exact same -/// fields as [`read::Ready`][Ready] in this case. -/// /// # Delegation Hierarchy /// /// The hierarchy of CRUD abilities is as follows: @@ -92,52 +88,52 @@ pub struct Ready { /// /// style read stroke:orange; /// ``` -pub type Builder = Ready; +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +#[serde(deny_unknown_fields)] +pub struct Builder { + // FIXME ^^^^^^ rename delegation as a pattern + /// Optional path within the resource. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub path: Option, + + #[serde(default, skip_serializing_if = "Option::is_none")] + /// Additional arugments to pass in the request. + pub args: Option>, +} impl Command for Ready { const COMMAND: &'static str = "crud/read"; } -impl Checkable for Ready { - type Hierarchy = Parentful; +impl Checkable for Builder { + type Hierarchy = Parentful; } -impl CheckSame for Ready { +impl CheckSame for Builder { type Error = ProofError; fn check_same(&self, proof: &Self) -> Result<(), Self::Error> { - if let Some(path) = &self.path { - if path != proof.path.as_ref().unwrap() { - return Err(PathError::Mismatch.into()); - } - } - - if let Some(args) = &self.args { - let proof_args = proof.args.as_ref().ok_or(ProofError::MissingProofArgs)?; - for (k, v) in args.iter() { - if proof_args - .get(k) - .ok_or(arguments::NamedError::FieldMissing(k.clone()))? - .ne(v) - { - return Err(arguments::NamedError::FieldValueMismatch(k.clone()).into()); - } - } + check_optional(self.path.as_ref(), proof.path.as_ref()) + .map_err(Into::::into)?; + + let args = self.args.as_ref().ok_or(ProofError::MissingProofArgs)?; + if let Some(proof_args) = &proof.args { + args.contains(proof_args).map_err(Into::into) + } else { + Ok(()) } - - Ok(()) } } -impl CheckParents for Ready { +impl CheckParents for Builder { type Parents = super::Any; - type ParentError = PathError; + type ParentError = OptionalFieldError; fn check_parent(&self, crud_any: &super::Any) -> Result<(), Self::ParentError> { if let Some(path) = &self.path { - let crud_any_path = crud_any.path.as_ref().ok_or(PathError::Missing)?; + let crud_any_path = crud_any.path.as_ref().ok_or(OptionalFieldError::Missing)?; if path != crud_any_path { - return Err(PathError::Mismatch); + return Err(OptionalFieldError::Unequal); } } @@ -192,9 +188,7 @@ pub struct Promised { #[serde(skip_serializing_if = "promise::Resolves::resolved_none")] pub path: promise::Resolves>, - /// Optional additional arugments to pass in the request - #[serde(skip_serializing_if = "promise::Resolves::resolved_none")] - pub args: promise::Resolves>>>, + pub args: arguments::Promised, } impl From for Ipld { @@ -210,3 +204,35 @@ impl TryFrom for Promised { ipld_serde::from_ipld(ipld) } } + +impl From for Promised { + fn from(r: Ready) -> Promised { + Promised { + path: promise::PromiseOk::Fulfilled(r.path).into(), + args: promise::PromiseOk::Fulfilled(r.args.into()).into(), + } + } +} + +impl From for arguments::Named { + fn from(p: Promised) -> arguments::Named { + p.into() + } +} + +impl Resolvable for Ready { + type Promised = Promised; + + fn try_resolve(p: Promised) -> Result { + match Resolves::try_resolve_2(p.path, p.args) { + Ok((path, promise_args)) => match promise_args.try_into() { + Ok(args) => Ok(Ready { path, args }), + Err(args) => Err(Promised { + args: promise::PromiseOk::Fulfilled(args).into(), + path: promise::PromiseOk::Fulfilled(path).into(), + }), + }, + Err((path, args)) => Err(Promised { path, args }), + } + } +} diff --git a/src/ability/crud/update.rs b/src/ability/crud/update.rs index be8ba511..3c8ef326 100644 --- a/src/ability/crud/update.rs +++ b/src/ability/crud/update.rs @@ -1,13 +1,12 @@ -use super::parents::MutableParents; +use super::{error::ProofError, parents::MutableParents}; use crate::{ ability::{arguments, command::Command}, - invocation::promise, - ipld::promised::PromisedIpld, + invocation::{promise, Resolvable}, proof::{checkable::Checkable, parentful::Parentful, parents::CheckParents, same::CheckSame}, }; use libipld_core::{error::SerdeError, ipld::Ipld, serde as ipld_serde}; use serde::{Deserialize, Serialize}; -use std::{collections::BTreeMap, path::PathBuf}; +use std::path::PathBuf; #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(deny_unknown_fields)] @@ -45,7 +44,7 @@ pub struct Builder { pub path: Option, #[serde(default, skip_serializing_if = "Option::is_none")] - pub args: Option>, // FIXME use a type param? + pub args: Option>, } impl From for Ipld { @@ -67,22 +66,26 @@ impl Checkable for Builder { } impl CheckSame for Builder { - type Error = (); // FIXME + type Error = ProofError; fn check_same(&self, proof: &Self) -> Result<(), Self::Error> { - self.path.check_same(&proof.path).map_err(|_| ())?; - self.args.check_same(&proof.args).map_err(|_| ()) + self.path + .check_same(&proof.path) + .map_err(Into::::into)?; + self.args.check_same(&proof.args).map_err(Into::into) } } impl CheckParents for Builder { type Parents = MutableParents; - type ParentError = (); // FIXME + type ParentError = ProofError; fn check_parent(&self, proof: &Self::Parents) -> Result<(), Self::ParentError> { match proof { - MutableParents::Any(any) => self.path.check_same(&any.path).map_err(|_| ()), - MutableParents::Mutate(mutate) => self.path.check_same(&mutate.path).map_err(|_| ()), + MutableParents::Any(any) => self.path.check_same(&any.path).map_err(Into::into), + MutableParents::Mutate(mutate) => { + self.path.check_same(&mutate.path).map_err(Into::into) + } } } } @@ -93,5 +96,41 @@ pub struct Promised { #[serde(skip_serializing_if = "promise::Resolves::resolved_none")] pub path: promise::Resolves>, - pub args: promise::Resolves>, + pub args: arguments::Promised, +} + +impl From for Promised { + fn from(r: Ready) -> Promised { + Promised { + path: promise::PromiseOk::Fulfilled(r.path).into(), + args: promise::PromiseOk::Fulfilled(r.args.into()).into(), + } + } +} + +impl From for arguments::Named { + fn from(p: Promised) -> arguments::Named { + p.into() + } +} + +impl Resolvable for Ready { + type Promised = Promised; + + fn try_resolve(p: Promised) -> Result { + // FIXME resolve2? + // FIXME lots of clone + Ok(Ready { + path: p.path.clone().try_resolve().map_err(|path| Promised { + path, + args: p.args.clone(), + })?, + + args: p + .args + .clone() + .try_into() + .map_err(|args| Promised { path: p.path, args })?, + }) + } } diff --git a/src/ability/dynamic.rs b/src/ability/dynamic.rs index 1db05871..e4584d13 100644 --- a/src/ability/dynamic.rs +++ b/src/ability/dynamic.rs @@ -32,7 +32,7 @@ pub struct Dynamic { /// Unstructured, named arguments /// /// The only requirement is that the keys are strings and the values are [`Ipld`] - pub args: arguments::Named, + pub args: arguments::Named, } impl ToCommand for Dynamic { @@ -41,7 +41,7 @@ impl ToCommand for Dynamic { } } -impl From for arguments::Named { +impl From for arguments::Named { fn from(dynamic: Dynamic) -> Self { dynamic.args } diff --git a/src/ability/js/parentful.rs b/src/ability/js/parentful.rs index f7523382..1af4654b 100644 --- a/src/ability/js/parentful.rs +++ b/src/ability/js/parentful.rs @@ -6,11 +6,12 @@ use crate::{ reader::Reader, }; use js_sys::{Function, JsString, Map}; +use libipld_core::ipld::Ipld; use std::collections::BTreeMap; use wasm_bindgen::{prelude::*, JsValue}; // FIXME rename -type WithParents = Reader; +type WithParents = Reader>; // Promise = Promise? Ah, nope becuase we need that CID on the promise // FIXME represent promises (for Promised) and options (for builder) diff --git a/src/ability/js/parentless.rs b/src/ability/js/parentless.rs index 53dac266..27118b85 100644 --- a/src/ability/js/parentless.rs +++ b/src/ability/js/parentless.rs @@ -6,10 +6,11 @@ use crate::{ reader::Reader, }; use js_sys::Function; +use libipld_core::ipld::Ipld; use wasm_bindgen::prelude::*; // FIXME rename -type WithoutParents = Reader; +type WithoutParents = Reader>; /// The configuration object that expresses an ability (without parents) from JS #[derive(Debug, Clone, PartialEq)] diff --git a/src/agent.rs b/src/agent.rs index a0023741..62968faa 100644 --- a/src/agent.rs +++ b/src/agent.rs @@ -1,6 +1,6 @@ use crate::{ ability::command::ToCommand, - delegation::{traits::Condition, Delegatable, Delegation}, + delegation::{condition::Condition, Delegatable, Delegation}, did::Did, invocation::Invocation, proof::parents::CheckParents, diff --git a/src/delegation.rs b/src/delegation.rs index f457be00..3fe71ed2 100644 --- a/src/delegation.rs +++ b/src/delegation.rs @@ -1,15 +1,14 @@ -mod condition; mod delegatable; mod payload; +pub mod condition; pub mod store; -pub use condition::*; pub use delegatable::Delegatable; pub use payload::Payload; -use condition::traits::Condition; -use store::IndexedStore; +use condition::Condition; +// use store::IndexedStore; use crate::signature; diff --git a/src/delegation/condition.rs b/src/delegation/condition.rs index d6ca60be..6b853128 100644 --- a/src/delegation/condition.rs +++ b/src/delegation/condition.rs @@ -1,22 +1,35 @@ -pub mod contains_all; -pub mod contains_any; -pub mod contains_key; -pub mod excludes_all; -pub mod excludes_key; -pub mod matches_regex; -pub mod max_length; -pub mod max_number; -pub mod min_length; -pub mod min_number; -pub mod traits; +mod contains_all; +mod contains_any; +mod contains_key; +mod excludes_all; +mod excludes_key; +mod matches_regex; +mod max_length; +mod max_number; +mod min_length; +mod min_number; +mod traits; + +pub use contains_all::ContainsAll; +pub use contains_any::ContainsAny; +pub use contains_key::ContainsKey; +pub use excludes_all::ExcludesAll; +pub use excludes_key::ExcludesKey; +pub use matches_regex::MatchesRegex; +pub use max_length::MaxLength; +pub use max_number::MaxNumber; +pub use min_length::MinLength; +pub use min_number::MinNumber; +pub use traits::Condition; use crate::ability::arguments; use libipld_core::{error::SerdeError, ipld::Ipld, serde as ipld_serde}; use serde_derive::{Deserialize, Serialize}; -use traits::Condition; +/// The union of the common [`Condition`]s that ship directly with this library. #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(untagged)] +#[allow(missing_docs)] pub enum Common { ContainsAll(contains_all::ContainsAll), ContainsAny(contains_any::ContainsAny), diff --git a/src/delegation/condition/contains_key.rs b/src/delegation/condition/contains_key.rs index cec31ffa..87b53498 100644 --- a/src/delegation/condition/contains_key.rs +++ b/src/delegation/condition/contains_key.rs @@ -13,7 +13,7 @@ use serde_derive::{Deserialize, Serialize}; /// # Examples /// /// ```rust -/// # use ucan::delegation::{contains_key::ContainsKey, traits::Condition}; +/// # use ucan::delegation::{condition::{ContainsKey, Condition}}; /// # use libipld::ipld; /// # /// let args = ipld!({"a": {"b": 1, "c": 2}, "d": {"e": 3}}).try_into().unwrap(); diff --git a/src/delegation/condition/excludes_all.rs b/src/delegation/condition/excludes_all.rs index 99048823..fff743e4 100644 --- a/src/delegation/condition/excludes_all.rs +++ b/src/delegation/condition/excludes_all.rs @@ -12,7 +12,7 @@ use serde_derive::{Deserialize, Serialize}; /// # Examples /// /// ```rust -/// # use ucan::delegation::{excludes_all::ExcludesAll, traits::Condition}; +/// # use ucan::delegation::{condition::{ExcludesAll, Condition}}; /// # use libipld::ipld; /// # /// let args = ipld!({"a": [1, "b", 3.14], "b": 4}).try_into().unwrap(); diff --git a/src/delegation/condition/excludes_key.rs b/src/delegation/condition/excludes_key.rs index 550014c6..58fbfc64 100644 --- a/src/delegation/condition/excludes_key.rs +++ b/src/delegation/condition/excludes_key.rs @@ -13,7 +13,7 @@ use serde_derive::{Deserialize, Serialize}; /// # Examples /// /// ```rust -/// # use ucan::delegation::{excludes_key::ExcludesKey, traits::Condition}; +/// # use ucan::delegation::{condition::{ExcludesKey, Condition}}; /// # use libipld::ipld; /// # /// let args = ipld!({"a": {"b": 1, "c": 2}, "d": {"e": 3}}).try_into().unwrap(); diff --git a/src/delegation/condition/traits.rs b/src/delegation/condition/traits.rs index 55c2738d..7ff605e8 100644 --- a/src/delegation/condition/traits.rs +++ b/src/delegation/condition/traits.rs @@ -1,6 +1,10 @@ +//! Traits for abstracting over conditions. + use crate::ability::arguments; use libipld_core::ipld::Ipld; +/// A trait for conditions that can be run on named IPLD arguments. pub trait Condition: TryFrom + Into { + /// Check that some condition is met on named IPLD arguments. fn validate(&self, args: &arguments::Named) -> bool; } diff --git a/src/delegation/payload.rs b/src/delegation/payload.rs index 18f07363..8b12153a 100644 --- a/src/delegation/payload.rs +++ b/src/delegation/payload.rs @@ -1,4 +1,4 @@ -use super::{condition::traits::Condition, delegatable::Delegatable}; +use super::{condition::Condition, delegatable::Delegatable}; use crate::{ ability::{arguments, command::Command}, capsule::Capsule, diff --git a/src/delegation/store.rs b/src/delegation/store.rs index 87e06108..1680cdb5 100644 --- a/src/delegation/store.rs +++ b/src/delegation/store.rs @@ -1,4 +1,4 @@ -use super::{condition::traits::Condition, delegatable::Delegatable, Delegation}; +use super::{condition::Condition, delegatable::Delegatable, Delegation}; use crate::did::Did; use libipld_core::cid::Cid; use serde::{Deserialize, Serialize}; diff --git a/src/invocation/promise.rs b/src/invocation/promise.rs index c72bc76c..d74bf61d 100644 --- a/src/invocation/promise.rs +++ b/src/invocation/promise.rs @@ -29,3 +29,21 @@ pub enum Promise { /// The `await/*` promise Any(PromiseAny), } + +impl From> for Promise { + fn from(p_ok: PromiseOk) -> Self { + Promise::Ok(p_ok) + } +} + +impl From> for Promise { + fn from(p_err: PromiseErr) -> Self { + Promise::Err(p_err) + } +} + +impl From> for Promise { + fn from(p_any: PromiseAny) -> Self { + Promise::Any(p_any) + } +} diff --git a/src/invocation/promise/any.rs b/src/invocation/promise/any.rs index 8dbd4920..81e56c48 100644 --- a/src/invocation/promise/any.rs +++ b/src/invocation/promise/any.rs @@ -2,8 +2,8 @@ use super::{err::PromiseErr, ok::PromiseOk}; use crate::{ability::arguments, ipld::cid}; use libipld_core::{cid::Cid, error::SerdeError, ipld::Ipld, serde as ipld_serde}; use serde::{ - de::{DeserializeSeed, Deserializer, Error, Expected, MapAccess, Visitor}, - Deserialize, Serialize, Serializer, + de::{Deserializer, Error, MapAccess, Visitor}, + Deserialize, Serialize, }; use std::fmt; diff --git a/src/invocation/promise/ok.rs b/src/invocation/promise/ok.rs index 70623609..2662344e 100644 --- a/src/invocation/promise/ok.rs +++ b/src/invocation/promise/ok.rs @@ -2,7 +2,6 @@ use crate::{ability::arguments, ipld::cid}; use libipld_core::{cid::Cid, error::SerdeError, ipld::Ipld, serde as ipld_serde}; use serde::{de::DeserializeOwned, Deserialize, Serialize}; use std::fmt::Debug; -use thiserror::Error; #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] #[serde(untagged, deny_unknown_fields)] @@ -14,6 +13,7 @@ pub enum PromiseOk { Pending(#[serde(rename = "await/ok")] Cid), } +// FIXME move try_resolve to a trait, give a blanket impl for prims, and tag them impl PromiseOk { pub fn try_resolve(self) -> Result> { match self { diff --git a/src/invocation/promise/resolves.rs b/src/invocation/promise/resolves.rs index c06b458b..a8df1488 100644 --- a/src/invocation/promise/resolves.rs +++ b/src/invocation/promise/resolves.rs @@ -26,6 +26,10 @@ impl Resolves> { } impl Resolves { + pub fn new(val: T) -> Self { + Resolves::Ok(PromiseOk::Fulfilled(val)) + } + pub fn try_resolve(self) -> Result> { match self { Resolves::Ok(p_ok) => p_ok.try_resolve().map_err(Resolves::Ok), @@ -205,6 +209,16 @@ impl Resolves { } } +impl> TryFrom for Resolves { + type Error = Ipld; + + fn try_from(ipld: Ipld) -> Result { + // FIXME so much cloning + let t = ipld.clone().try_into().map_err(|_| ipld.clone())?; + Ok(PromiseOk::Fulfilled(t).into()) + } +} + impl From> for Option { fn from(r: Resolves) -> Option { match r { diff --git a/src/invocation/resolvable.rs b/src/invocation/resolvable.rs index bc9103ea..3a1f23da 100644 --- a/src/invocation/resolvable.rs +++ b/src/invocation/resolvable.rs @@ -6,4 +6,9 @@ pub trait Resolvable: Sized { // FIXME indeed needed to get teh right err type fn try_resolve(promised: Self::Promised) -> Result; + + // FIXME remove + fn try_resolve0(promised: Self::Promised) -> Result { + Self::try_resolve(promised) + } } diff --git a/src/ipld.rs b/src/ipld.rs index 7571fb44..d57a311f 100644 --- a/src/ipld.rs +++ b/src/ipld.rs @@ -1,205 +1,11 @@ //! Helpers for working with [`Ipld`] -pub mod cid; -pub mod promised; - -use libipld_core::ipld::Ipld; -use serde::{Deserialize, Serialize}; - -#[cfg(target_arch = "wasm32")] -use wasm_bindgen::prelude::*; - -#[cfg(target_arch = "wasm32")] -use js_sys::{Array, Map, Object, Uint8Array}; - -/// A wrapper around [`Ipld`] that has additional trait implementations -/// -/// Usage is very simple: wrap a [`Newtype`] to gain access to additional traits and methods. -/// -/// ```rust -/// # use libipld_core::ipld::Ipld; -/// # use ucan::ipld; -/// # -/// let ipld = Ipld::String("hello".into()); -/// let wrapped = ipld::Newtype(ipld.clone()); -/// // wrapped.some_trait_method(); -/// ``` -/// -/// Unwrap a [`Newtype`] to use any interfaces that expect plain [`Ipld`]. -/// -/// ``` -/// # use libipld_core::ipld::Ipld; -/// # use ucan::ipld; -/// # -/// # let ipld = Ipld::String("hello".into()); -/// # let wrapped = ipld::Newtype(ipld.clone()); -/// # -/// assert_eq!(wrapped.0, ipld); -/// ``` -#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] -#[serde(transparent)] -pub struct Newtype(pub Ipld); - -impl From for Newtype { - fn from(ipld: Ipld) -> Self { - Self(ipld) - } -} - -impl From for Ipld { - fn from(wrapped: Newtype) -> Self { - wrapped.0 - } -} - -#[cfg(target_arch = "wasm32")] -impl Newtype { - pub fn try_from_js>(js_val: JsValue) -> Result - where - JsError: From<>::Error>, - { - match Newtype::try_from(js_val) { - Err(_err) => Err(JsError::new("can't convert")), // FIXME - Ok(nt) => nt.0.try_into().map_err(JsError::from), - } - } -} - -// TODO testme -#[cfg(target_arch = "wasm32")] -impl From for JsValue { - fn from(wrapped: Newtype) -> Self { - match wrapped.0 { - Ipld::Null => JsValue::NULL, - Ipld::Bool(b) => JsValue::from(b), - Ipld::Integer(i) => JsValue::from(i), - Ipld::Float(f) => JsValue::from_f64(f), - Ipld::String(s) => JsValue::from_str(&s), - Ipld::Bytes(bs) => { - let u8arr = Uint8Array::new(&bs.len().into()); - for (i, b) in bs.iter().enumerate() { - u8arr.set_index(i as u32, *b); - } - JsValue::from(u8arr) - } - Ipld::List(ls) => { - let arr = Array::new(); - for ipld in ls { - arr.push(&JsValue::from(Newtype(ipld))); - } - JsValue::from(arr) - } - Ipld::Map(m) => { - let map = Map::new(); - for (k, v) in m { - map.set(&JsValue::from(k), &JsValue::from(Newtype(v))); - } - JsValue::from(map) - } - Ipld::Link(cid) => cid::Newtype::from(cid).into(), - } - } -} - -// TODO testme -#[cfg(target_arch = "wasm32")] -impl TryFrom for Newtype { - type Error = (); // FIXME - - fn try_from(js_val: JsValue) -> Result { - if js_val.is_null() { - return Ok(Newtype(Ipld::Null)); - } - - if let Some(b) = js_val.as_bool() { - return Ok(Newtype(Ipld::Bool(b))); - } - - if let Some(f) = js_val.as_f64() { - return Ok(Newtype(Ipld::Float(f))); - } - - if let Some(s) = js_val.as_string() { - return Ok(Newtype(Ipld::String(s))); - } - - if let Some(arr) = js_val.dyn_ref::() { - let mut list = vec![]; - for x in arr.to_vec().iter() { - let ipld = Newtype::try_from(x.clone())?.into(); - list.push(ipld); - } +mod enriched; +mod newtype; +mod promised; - return Ok(Newtype(Ipld::List(list))); - } - - if let Some(arr) = js_val.dyn_ref::() { - let mut v = vec![]; - for item in arr.to_vec().iter() { - v.push(item.clone()); - } - - return Ok(Newtype(Ipld::Bytes(v))); - } - - if let Some(map) = js_val.dyn_ref::() { - let mut m = std::collections::BTreeMap::new(); - let mut acc = Ok(()); - - // Weird order, but correct per the docs - // vvvvvvvvvv - map.for_each(&mut |value, key| { - if acc.is_err() { - return; - } - - match (key.as_string(), Newtype::try_from(value.clone())) { - (Some(k), Ok(v)) => { - m.insert(k, v.0); - } - _ => { - acc = Err(()); - } - } - }); - - return acc.map(|_| Newtype(Ipld::Map(m))); - } - - // NOTE *must* come before `is_object` (which is hopefully below) - if let Ok(nt) = cid::Newtype::try_from_js_value(&js_val) { - return Ok(Newtype(Ipld::Link(nt.into()))); - } - - if js_val.is_object() { - let obj = Object::from(js_val); - let mut m = std::collections::BTreeMap::new(); - let mut acc = Ok(()); - - Object::entries(&obj).for_each(&mut |js_val, _, _| { - if acc.is_err() { - return; - } - - // By definition this must be the array [value, key], in that order - let arr = Array::from(&js_val); - - match (arr.get(0).as_string(), Newtype::try_from(arr.get(1))) { - (Some(k), Ok(v)) => { - m.insert(k, v.0); - } - // FIXME more specific errors - _ => { - acc = Err(()); - } - } - }); - - return acc.map(|_| Newtype(Ipld::Map(m))); - } - - // NOTE fails on `undefined` and `function` +pub mod cid; - Err(()) - } -} +pub use enriched::Enriched; +pub use newtype::Newtype; +pub use promised::Promised; diff --git a/src/ipld/cid.rs b/src/ipld/cid.rs index 791cc16b..a5cab51d 100644 --- a/src/ipld/cid.rs +++ b/src/ipld/cid.rs @@ -1,7 +1,7 @@ //! Utilities for [`Cid`]s use crate::ipld; -use libipld_core::{cid::Cid, error::SerdeError, ipld::Ipld}; +use libipld_core::{cid::Cid, ipld::Ipld}; use serde::{Deserialize, Serialize}; use thiserror::Error; @@ -111,9 +111,3 @@ impl TryFrom<&Ipld> for Newtype { #[derive(Debug, PartialEq, Clone, Error, Serialize, Deserialize)] #[error("Not a CID: {0:?}")] pub struct NotACid(pub ipld::Newtype); - -// #[cfg(target_arch = "wasm32")] -// #[derive(Debug, PartialEq, Clone, Error, Serialize, Deserialize)] -// #[error("Not a CID: {0:?}")] -// #[wasm_bindgen] -// pub struct NotACid(#[wasm_bindgen(skip)] pub ipld::Newtype); diff --git a/src/ipld/enriched.rs b/src/ipld/enriched.rs new file mode 100644 index 00000000..120feaa9 --- /dev/null +++ b/src/ipld/enriched.rs @@ -0,0 +1,90 @@ +use libipld_core::{cid::Cid, ipld::Ipld}; +use serde::{Deserialize, Serialize}; +use std::collections::BTreeMap; + +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +pub enum Enriched { + /// Lifted [`Ipld::Null`] + Null, + + /// Lifted [`Ipld::Bool`] + Bool(bool), + + /// Lifted [`Ipld::Integer`] + Integer(i128), + + /// Lifted [`Ipld::Float`] + Float(f64), + + /// Lifted [`Ipld::String`] + String(String), + + /// Lifted [`Ipld::Bytes`] (byte array) + Bytes(Vec), + + /// [`Ipld::List`], but where the values are [`PromiseIpld`]. + List(Vec), + + /// [`Ipld::Map`], but where the values are [`PromiseIpld`]. + Map(BTreeMap), + + /// Lifted [`Ipld::Link`] + Link(Cid), +} + +impl> From for Enriched { + fn from(ipld: Ipld) -> Self { + match ipld { + Ipld::Null => Enriched::Null, + Ipld::Bool(b) => Enriched::Bool(b), + Ipld::Integer(i) => Enriched::Integer(i), + Ipld::Float(f) => Enriched::Float(f), + Ipld::String(s) => Enriched::String(s), + Ipld::Bytes(b) => Enriched::Bytes(b), + Ipld::List(l) => Enriched::List(l.into_iter().map(From::from).collect()), + Ipld::Map(m) => Enriched::Map(m.into_iter().map(|(k, v)| (k, From::from(v))).collect()), + Ipld::Link(c) => Enriched::Link(c), + } + } +} + +impl> TryFrom> for Ipld { + type Error = Enriched; + + fn try_from(enriched: Enriched) -> Result { + match enriched { + Enriched::List(ref vec) => { + let result: Result, ()> = vec.iter().try_fold(vec![], |mut acc, x| { + let resolved = x.clone().try_into().map_err(|_| ())?; + acc.push(resolved); + Ok(acc) + }); + + match result { + Ok(vec) => Ok(vec.into()), + Err(()) => Err(enriched), + } + } + Enriched::Map(ref btree) => { + let result: Result, ()> = + btree.iter().try_fold(BTreeMap::new(), |mut acc, (k, v)| { + let resolved = v.clone().try_into().map_err(|_| ())?; + acc.insert(k.clone(), resolved); + Ok(acc) + }); + + match result { + Ok(vec) => Ok(vec.into()), + Err(()) => Err(enriched), + } + } + Enriched::Null => Ok(Ipld::Null), + Enriched::Bool(b) => Ok(b.into()), + Enriched::Integer(i) => Ok(i.into()), + Enriched::Float(f) => Ok(f.into()), + Enriched::String(s) => Ok(s.into()), + Enriched::Bytes(b) => Ok(b.into()), + Enriched::Link(l) => Ok(l.into()), + } + } +} diff --git a/src/ipld/newtype.rs b/src/ipld/newtype.rs new file mode 100644 index 00000000..95bf9fea --- /dev/null +++ b/src/ipld/newtype.rs @@ -0,0 +1,201 @@ +use libipld_core::ipld::Ipld; +use serde::{Deserialize, Serialize}; + +#[cfg(target_arch = "wasm32")] +use wasm_bindgen::prelude::*; + +#[cfg(target_arch = "wasm32")] +use js_sys::{Array, Map, Object, Uint8Array}; + +// FIXME push into the submodules +/// A wrapper around [`Ipld`] that has additional trait implementations +/// +/// Usage is very simple: wrap a [`Newtype`] to gain access to additional traits and methods. +/// +/// ```rust +/// # use libipld_core::ipld::Ipld; +/// # use ucan::ipld; +/// # +/// let ipld = Ipld::String("hello".into()); +/// let wrapped = ipld::Newtype(ipld.clone()); +/// // wrapped.some_trait_method(); +/// ``` +/// +/// Unwrap a [`Newtype`] to use any interfaces that expect plain [`Ipld`]. +/// +/// ``` +/// # use libipld_core::ipld::Ipld; +/// # use ucan::ipld; +/// # +/// # let ipld = Ipld::String("hello".into()); +/// # let wrapped = ipld::Newtype(ipld.clone()); +/// # +/// assert_eq!(wrapped.0, ipld); +/// ``` +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +#[serde(transparent)] +pub struct Newtype(pub Ipld); + +impl From for Newtype { + fn from(ipld: Ipld) -> Self { + Self(ipld) + } +} + +impl From for Ipld { + fn from(wrapped: Newtype) -> Self { + wrapped.0 + } +} + +#[cfg(target_arch = "wasm32")] +impl Newtype { + pub fn try_from_js>(js_val: JsValue) -> Result + where + JsError: From<>::Error>, + { + match Newtype::try_from(js_val) { + Err(_err) => Err(JsError::new("can't convert")), // FIXME + Ok(nt) => nt.0.try_into().map_err(JsError::from), + } + } +} + +// TODO testme +#[cfg(target_arch = "wasm32")] +impl From for JsValue { + fn from(wrapped: Newtype) -> Self { + match wrapped.0 { + Ipld::Null => JsValue::NULL, + Ipld::Bool(b) => JsValue::from(b), + Ipld::Integer(i) => JsValue::from(i), + Ipld::Float(f) => JsValue::from_f64(f), + Ipld::String(s) => JsValue::from_str(&s), + Ipld::Bytes(bs) => { + let u8arr = Uint8Array::new(&bs.len().into()); + for (i, b) in bs.iter().enumerate() { + u8arr.set_index(i as u32, *b); + } + JsValue::from(u8arr) + } + Ipld::List(ls) => { + let arr = Array::new(); + for ipld in ls { + arr.push(&JsValue::from(Newtype(ipld))); + } + JsValue::from(arr) + } + Ipld::Map(m) => { + let map = Map::new(); + for (k, v) in m { + map.set(&JsValue::from(k), &JsValue::from(Newtype(v))); + } + JsValue::from(map) + } + Ipld::Link(cid) => cid::Newtype::from(cid).into(), + } + } +} + +// TODO testme +#[cfg(target_arch = "wasm32")] +impl TryFrom for Newtype { + type Error = (); // FIXME + + fn try_from(js_val: JsValue) -> Result { + if js_val.is_null() { + return Ok(Newtype(Ipld::Null)); + } + + if let Some(b) = js_val.as_bool() { + return Ok(Newtype(Ipld::Bool(b))); + } + + if let Some(f) = js_val.as_f64() { + return Ok(Newtype(Ipld::Float(f))); + } + + if let Some(s) = js_val.as_string() { + return Ok(Newtype(Ipld::String(s))); + } + + if let Some(arr) = js_val.dyn_ref::() { + let mut list = vec![]; + for x in arr.to_vec().iter() { + let ipld = Newtype::try_from(x.clone())?.into(); + list.push(ipld); + } + + return Ok(Newtype(Ipld::List(list))); + } + + if let Some(arr) = js_val.dyn_ref::() { + let mut v = vec![]; + for item in arr.to_vec().iter() { + v.push(item.clone()); + } + + return Ok(Newtype(Ipld::Bytes(v))); + } + + if let Some(map) = js_val.dyn_ref::() { + let mut m = std::collections::BTreeMap::new(); + let mut acc = Ok(()); + + // Weird order, but correct per the docs + // vvvvvvvvvv + map.for_each(&mut |value, key| { + if acc.is_err() { + return; + } + + match (key.as_string(), Newtype::try_from(value.clone())) { + (Some(k), Ok(v)) => { + m.insert(k, v.0); + } + _ => { + acc = Err(()); + } + } + }); + + return acc.map(|_| Newtype(Ipld::Map(m))); + } + + // NOTE *must* come before `is_object` (which is hopefully below) + if let Ok(nt) = cid::Newtype::try_from_js_value(&js_val) { + return Ok(Newtype(Ipld::Link(nt.into()))); + } + + if js_val.is_object() { + let obj = Object::from(js_val); + let mut m = std::collections::BTreeMap::new(); + let mut acc = Ok(()); + + Object::entries(&obj).for_each(&mut |js_val, _, _| { + if acc.is_err() { + return; + } + + // By definition this must be the array [value, key], in that order + let arr = Array::from(&js_val); + + match (arr.get(0).as_string(), Newtype::try_from(arr.get(1))) { + (Some(k), Ok(v)) => { + m.insert(k, v.0); + } + // FIXME more specific errors + _ => { + acc = Err(()); + } + } + }); + + return acc.map(|_| Newtype(Ipld::Map(m))); + } + + // NOTE fails on `undefined` and `function` + + Err(()) + } +} diff --git a/src/ipld/promised.rs b/src/ipld/promised.rs index 5f78741e..d64ada74 100644 --- a/src/ipld/promised.rs +++ b/src/ipld/promised.rs @@ -1,177 +1,74 @@ -use crate::invocation::promise::{Promise, PromiseAny, PromiseErr, PromiseOk, Resolves}; -use libipld_core::{cid::Cid, ipld::Ipld}; +use super::enriched::Enriched; +use crate::invocation::promise::{Promise, PromiseAny, PromiseErr, PromiseOk}; +use libipld_core::{error::SerdeError, ipld::Ipld}; use serde::{Deserialize, Serialize}; -use std::collections::BTreeMap; /// A promise to recursively resolve to an [`Ipld`] value. -/// #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] -pub enum PromisedIpld { - /// Lifted [`Ipld::Null`] - Null, +#[serde(transparent)] +pub struct Promised(pub Promise, Enriched>); - /// Lifted [`Ipld::Bool`] - Bool(bool), - - /// Lifted [`Ipld::Integer`] - Integer(i128), - - /// Lifted [`Ipld::Float`] - Float(f64), - - /// Lifted [`Ipld::String`] - String(String), - - /// Lifted [`Ipld::Bytes`] (byte array) - Bytes(Vec), - - /// [`Ipld::List`], but where the values are [`PromiseIpld`]. - List(Vec), - - /// [`Ipld::Map`], but where the values are [`PromiseIpld`]. - Map(BTreeMap), - - /// Lifted [`Ipld::Link`] - Link(Cid), - - /// The `await/ok` promise - PromiseOk(Cid), - - /// The `await/err` promise - PromiseErr(Cid), - - /// The `await/*` promise - PromiseAny(Cid), -} - -impl PromisedIpld { - pub fn is_resolved(&self) -> bool { - match self { - PromisedIpld::Null => true, - PromisedIpld::Bool(_) => true, - PromisedIpld::Integer(_) => true, - PromisedIpld::Float(_) => true, - PromisedIpld::String(_) => true, - PromisedIpld::Bytes(_) => true, - PromisedIpld::List(list) => list.iter().all(PromisedIpld::is_resolved), - PromisedIpld::Map(map) => map.values().all(PromisedIpld::is_resolved), - PromisedIpld::Link(_) => true, - PromisedIpld::PromiseOk(_) => false, - PromisedIpld::PromiseErr(_) => false, - PromisedIpld::PromiseAny(_) => false, - } - } - - pub fn is_pending(&self) -> bool { - !self.is_resolved() - } -} - -impl From for PromisedIpld { - fn from(ipld: Ipld) -> Self { - match ipld { - Ipld::Null => PromisedIpld::Null, - Ipld::Bool(b) => PromisedIpld::Bool(b), - Ipld::Integer(i) => PromisedIpld::Integer(i), - Ipld::Float(f) => PromisedIpld::Float(f), - Ipld::String(s) => PromisedIpld::String(s), - Ipld::Bytes(b) => PromisedIpld::Bytes(b), - Ipld::List(list) => { - PromisedIpld::List(list.into_iter().map(PromisedIpld::from).collect()) - } - Ipld::Map(map) => PromisedIpld::Map( - map.into_iter() - .map(|(k, v)| (k, PromisedIpld::from(v))) - .collect(), - ), - Ipld::Link(cid) => PromisedIpld::Link(cid), - } - } -} - -impl From> for PromisedIpld { - fn from(promise: PromiseOk) -> Self { - match promise { - PromiseOk::Fulfilled(ipld) => ipld.into(), - PromiseOk::Pending(cid) => PromisedIpld::PromiseOk(cid), - } +impl From, Enriched>> for Promised { + fn from(promise: Promise, Enriched>) -> Self { + Promised(promise) } } -impl From> for PromisedIpld { - fn from(promise: PromiseErr) -> Self { - match promise { - PromiseErr::Rejected(ipld) => ipld.into(), - PromiseErr::Pending(cid) => PromisedIpld::PromiseErr(cid), - } +impl From>> for Promised { + fn from(p_ok: PromiseOk>) -> Self { + Promised(p_ok.into()) } } -impl From> for PromisedIpld { - fn from(promise: PromiseAny) -> Self { - match promise { - PromiseAny::Fulfilled(ipld) => ipld.into(), - PromiseAny::Rejected(ipld) => ipld.into(), - PromiseAny::Pending(cid) => PromisedIpld::PromiseAny(cid), - } +impl From>> for Promised { + fn from(p_err: PromiseErr>) -> Self { + Promised(p_err.into()) } } -impl From> for PromisedIpld { - fn from(resolves: Resolves) -> Self { - match resolves { - Resolves::Ok(p_ok) => p_ok.into(), - Resolves::Err(p_err) => p_err.into(), - } +impl From, Enriched>> for Promised { + fn from(p_any: PromiseAny, Enriched>) -> Self { + Promised(p_any.into()) } } -impl From> for PromisedIpld { - fn from(promise: Promise) -> Self { - match promise { - Promise::Ok(p_ok) => p_ok.into(), - Promise::Err(p_err) => p_err.into(), - Promise::Any(p_any) => p_any.into(), - } +impl From for Promised { + fn from(ipld: Ipld) -> Self { + Promised(Promise::Ok(PromiseOk::Fulfilled(ipld.into()))) } } -impl TryFrom for Ipld { - type Error = PromisedIpld; - - fn try_from(p: PromisedIpld) -> Result { - match p { - PromisedIpld::Null => Ok(Ipld::Null), - PromisedIpld::Bool(b) => Ok(Ipld::Bool(b)), - PromisedIpld::Integer(i) => Ok(Ipld::Integer(i)), - PromisedIpld::Float(f) => Ok(Ipld::Float(f)), - PromisedIpld::String(s) => Ok(Ipld::String(s)), - PromisedIpld::Bytes(b) => Ok(Ipld::Bytes(b)), - PromisedIpld::List(ref list) => { - let result: Result, ()> = list.iter().try_fold(vec![], |mut acc, x| { - let ipld = Ipld::try_from(x.clone()).map_err(|_| ())?; - acc.push(ipld); - Ok(acc) - }); - - Ok(Ipld::List(result.map_err(|_| p.clone())?)) - } - PromisedIpld::Map(ref map) => { - let map: Result, ()> = - map.into_iter() - .try_fold(BTreeMap::new(), |mut acc, (k, v)| { - // FIXME non-tail recursion, and maybe even repeated clones - let ipld = Ipld::try_from(v.clone()).map_err(|_| ())?; - acc.insert(k.clone(), ipld); - Ok(acc) - }); - - Ok(Ipld::Map(map.map_err(|_| p)?)) - } - PromisedIpld::Link(cid) => Ok(Ipld::Link(cid)), - PromisedIpld::PromiseOk(_cid) => Err(p), - PromisedIpld::PromiseErr(_cid) => Err(p), - PromisedIpld::PromiseAny(_cid) => Err(p), +// FIXME THIS is a great example of a try_resolve +impl TryFrom for Ipld { + type Error = Promised; + + fn try_from(p: Promised) -> Result { + match p.0 { + Promise::Ok(p_ok) => match p_ok { + PromiseOk::Fulfilled(inner) => { + inner.try_into().map_err(|e| PromiseOk::Fulfilled(e).into()) + } + + PromiseOk::Pending(inner) => Err(PromiseOk::Pending(inner).into()), + }, + Promise::Err(p_err) => match p_err { + PromiseErr::Rejected(inner) => { + inner.try_into().map_err(|e| PromiseErr::Rejected(e).into()) + } + + PromiseErr::Pending(inner) => Err(PromiseErr::Pending(inner).into()), + }, + Promise::Any(p_any) => match p_any { + PromiseAny::Fulfilled(inner) => inner + .try_into() + .map_err(|e| Promise::Any(PromiseAny::Fulfilled(e)).into()), + + PromiseAny::Rejected(inner) => { + inner.try_into().map_err(|e| PromiseAny::Rejected(e).into()) + } + + PromiseAny::Pending(inner) => Err(PromiseAny::Pending(inner).into()), + }, } } } diff --git a/src/proof.rs b/src/proof.rs index dbe2b43a..c0b401a7 100644 --- a/src/proof.rs +++ b/src/proof.rs @@ -7,6 +7,7 @@ pub mod parentless; pub mod parents; pub mod prove; pub mod same; +pub mod util; // NOTE must remain *un*exported! pub(super) mod internal; diff --git a/src/proof/error.rs b/src/proof/error.rs index a36b5208..c9bf339d 100644 --- a/src/proof/error.rs +++ b/src/proof/error.rs @@ -19,11 +19,11 @@ pub enum OptionalFieldError { /// A required field is missing. /// /// For example, when its proof has a vaue, but the target does not. - #[error("missing")] + #[error("Field missing")] Missing, /// A field is present but has a different value in its proof - #[error("unequal")] + #[error("Field value unequal")] Unequal, } diff --git a/src/proof/util.rs b/src/proof/util.rs new file mode 100644 index 00000000..05926acd --- /dev/null +++ b/src/proof/util.rs @@ -0,0 +1,15 @@ +use super::error::OptionalFieldError; + +pub fn check_optional( + target: Option, + proof: Option, +) -> Result<(), OptionalFieldError> { + if let Some(target_value) = target { + let proof_value = proof.ok_or(OptionalFieldError::Missing)?; + if target_value != proof_value { + return Err(OptionalFieldError::Unequal); + } + } + + Ok(()) +} diff --git a/src/task.rs b/src/task.rs index 454b6876..6e80ca88 100644 --- a/src/task.rs +++ b/src/task.rs @@ -71,6 +71,9 @@ impl From for Cid { #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(transparent)] pub struct Id { + /// The CID of the [`Task`]. + /// + /// This acts as a unique identifier for the task. pub cid: Cid, } From 6fefae505a615029290803dd572ca331a135cd27 Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Fri, 9 Feb 2024 18:21:02 -0800 Subject: [PATCH 062/188] I believe that's most of teh abilities done. --- src/ability.rs | 35 +++ src/ability/arguments/named.rs | 7 +- src/ability/crud.rs | 3 +- src/ability/crud/create.rs | 261 ++++++++++++++++++++-- src/ability/crud/destroy.rs | 252 ++++++++++++++++++--- src/ability/crud/error.rs | 39 ++-- src/ability/crud/js.rs | 2 + src/ability/crud/parents.rs | 73 ++++--- src/ability/crud/read.rs | 339 +++++++++++++++++------------ src/ability/crud/update.rs | 322 ++++++++++++++++++++------- src/invocation/payload.rs | 2 +- src/invocation/promise/resolves.rs | 1 + src/invocation/resolvable.rs | 5 - src/ipld.rs | 2 +- src/ipld/newtype.rs | 7 + src/ipld/promised.rs | 14 +- 16 files changed, 1046 insertions(+), 318 deletions(-) diff --git a/src/ability.rs b/src/ability.rs index f9547f56..105a1bd6 100644 --- a/src/ability.rs +++ b/src/ability.rs @@ -1,3 +1,38 @@ +//! Abilities describe the semantics of what a UCAN is allowed to do. +//! +//! # Top Level Structure +//! +//! They always follow the same format at the top level: +//! +//! | Field | Name | Description | +//! |--------|-----------------------------|----------------------------------| +//! | `cmd` | [Command](command::Command) | Roughly a function name. Determines the shape of the `args`. | +//! | `args` | [Arguments](arguments) | Roughly the function's arguments | +//! +//! # Proof Hierarchy +//! +//! Any UCAN can be proven by the `*` ability. This has been special-cased +//! into the library, and you don't have to worry about it directly when +//! implementing a new ability. +//! +//! Most abilities have no additional parents. If they do, they follow a +//! strict hierararchy. The [CRUD hierarchy](crate::abilities::crud::Any) +//! is a good example. +//! +//! Not all abilities in the hierarchy are invocable: some abstract over +//! multiple `cmd`s (such as [`crud/*`](crate::abilities::crud::Any) for +//! all CRUD actions). This allows for flexibility in adding more abilities +//! under the same hierarchy in the future without having to reissue all of +//! your certificates. +//! +//! # Lifecycle +//! +//! All abilities start as a delegation, which can omit fields (but must +//! stay the same or add more at each delegatoion). When they are invoked, +//! all field much be present. The only exception is promises, where a +//! field may include a promise pointing at another invocation. Once fully +//! resolved ("ready"), they must be validatable against the delegation chain. + // FIXME feature flag each? // FIXME ability implementers guide (e.g. serde deny fields) // diff --git a/src/ability/arguments/named.rs b/src/ability/arguments/named.rs index a63e6ee0..710d91e5 100644 --- a/src/ability/arguments/named.rs +++ b/src/ability/arguments/named.rs @@ -10,9 +10,6 @@ use wasm_bindgen::prelude::*; #[cfg(target_arch = "wasm32")] use js_sys::{Array, Map, Object, Reflect}; -#[cfg(target_arch = "wasm32")] -use crate::ipld; - /// Named arguments /// /// Being such a common pattern, but with so few trait implementations, @@ -77,6 +74,10 @@ impl Named { self.0.len() } + pub fn is_empty(&self) -> bool { + self.0.is_empty() + } + pub fn contains(&self, other: &Named) -> Result<(), NamedError> where T: PartialEq, diff --git a/src/ability/crud.rs b/src/ability/crud.rs index b06fb0f7..39e6c79c 100644 --- a/src/ability/crud.rs +++ b/src/ability/crud.rs @@ -39,16 +39,17 @@ mod any; mod mutate; +mod parents; pub mod create; pub mod destroy; pub mod error; -pub mod parents; pub mod read; pub mod update; pub use any::Any; pub use mutate::Mutate; +pub use parents::MutableParents; #[cfg(target_arch = "wasm32")] pub mod js; diff --git a/src/ability/crud/create.rs b/src/ability/crud/create.rs index 4e3e4b22..51f04b8e 100644 --- a/src/ability/crud/create.rs +++ b/src/ability/crud/create.rs @@ -1,46 +1,174 @@ +//! Create new resources. +use super::{error::ProofError, parents::MutableParents}; use crate::{ - ability::command::Command, - proof::{checkable::Checkable, parentful::Parentful, parents::CheckParents, same::CheckSame}, + ability::{arguments, command::Command}, + invocation::{promise, promise::Resolves, Resolvable}, + ipld, + proof::{ + checkable::Checkable, error::OptionalFieldError, parentful::Parentful, + parents::CheckParents, same::CheckSame, util::check_optional, + }, }; use libipld_core::{error::SerdeError, ipld::Ipld, serde as ipld_serde}; -use serde::{Deserialize, Serialize}; +use serde::{de::DeserializeOwned, Deserialize, Serialize}; use std::{collections::BTreeMap, path::PathBuf}; -use super::parents::MutableParents; +// FIXME deserialize instance -#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +/// A helper for creating lifecycle instances of `crud/create` with the correct shape. +#[derive(Debug, Clone, PartialEq, Serialize)] #[serde(deny_unknown_fields)] -pub struct Create { - #[serde(skip_serializing_if = "Option::is_none")] - pub path: Option, +pub struct Generic { + /// An optional path to a sub-resource that is to be created. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub path: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub args: Option>, + /// Optional arguments for creation. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub args: Option, } -impl Command for Create { +#[cfg_attr(doc, aquamarine::aquamarine)] +/// The executable/dispatchable variant of the `crud/create` ability. +/// +/// # Lifecycle +/// +/// The relevant hierarchy of CRUD abilities is as follows: +/// +/// ```mermaid +/// flowchart LR +/// subgraph Delegations +/// top("*") +/// +/// subgraph CRUD Abilities +/// any("crud/*") +/// +/// mutate("crud/mutate") +/// +/// subgraph Invokable +/// create("crud/create") +/// end +/// end +/// end +/// +/// createpromise("crud::create::Promised") +/// createready("crud::create::Ready") +/// +/// top --> any --> mutate --> create +/// create -.->|invoke| createpromise -.->|resolve| createready -.-> exe{{execute}} +/// +/// style createready stroke:orange; +/// ``` +pub type Ready = Generic>; + +#[cfg_attr(doc, aquamarine::aquamarine)] +/// The delegatable ability for creating other agents. +/// +/// # Lifecycle +/// +/// The lifecycle of a `crud/create` ability is as follows: +/// +/// ```mermaid +/// flowchart LR +/// subgraph Delegations +/// top("*") +/// +/// subgraph CRUD Abilities +/// any("crud/*") +/// +/// mutate("crud/mutate") +/// +/// subgraph Invokable +/// create("crud/create") +/// end +/// end +/// end +/// +/// createpromise("crud::create::Promised") +/// createready("crud::create::Ready") +/// +/// top --> any --> mutate --> create +/// create -.->|invoke| createpromise -.->|resolve| createready -.-> exe{{execute}} +/// +/// style create stroke:orange; +/// ``` +pub type Builder = Generic>; + +#[cfg_attr(doc, aquamarine::aquamarine)] +/// An invoked `crud/create` ability (but possibly awaiting another +/// [`Invocation`][crate::invocation::Invocation]). +/// +/// # Delegation Hierarchy +/// +/// The hierarchy of CRUD abilities is as follows: +/// +/// ```mermaid +/// flowchart LR +/// subgraph Delegations +/// top("*") +/// +/// subgraph CRUD Abilities +/// any("crud/*") +/// +/// mutate("crud/mutate") +/// +/// subgraph Invokable +/// create("crud/create") +/// end +/// end +/// end +/// +/// createpromise("crud::create::Promised") +/// createready("crud::create::Ready") +/// +/// top --> any --> mutate --> create +/// create -.->|invoke| createpromise -.->|resolve| createready -.-> exe{{execute}} +/// +/// style createpromise stroke:orange; +/// ``` +pub type Promised = Generic, arguments::Promised>; + +impl Command for Generic { const COMMAND: &'static str = "crud/create"; } -impl From for Ipld { - fn from(create: Create) -> Self { +impl, A: Into> From> for Ipld { + fn from(create: Generic) -> Self { create.into() } } -impl TryFrom for Create { - type Error = SerdeError; +impl, A: TryFrom> TryFrom for Generic { + type Error = (); // FIXME fn try_from(ipld: Ipld) -> Result { - ipld_serde::from_ipld(ipld) + if let Ipld::Map(mut map) = ipld { + if map.len() > 2 { + return Err(()); // FIXME + } + + Ok(Generic { + path: map + .remove("path") + .map(|ipld| P::try_from(ipld).map_err(|_| ())) + .transpose()?, + + args: map + .remove("args") + .map(|ipld| A::try_from(ipld).map_err(|_| ())) + .transpose()?, + }) + } else { + Err(()) // FIXME + } } } -impl Checkable for Create { - type Hierarchy = Parentful; +impl Checkable for Builder { + type Hierarchy = Parentful; } -impl CheckSame for Create { +impl CheckSame for Builder { type Error = (); // FIXME better error fn check_same(&self, proof: &Self) -> Result<(), Self::Error> { @@ -52,7 +180,7 @@ impl CheckSame for Create { } } -impl CheckParents for Create { +impl CheckParents for Builder { type Parents = MutableParents; type ParentError = (); // FIXME @@ -60,6 +188,7 @@ impl CheckParents for Create { if let Some(self_path) = &self.path { match other { MutableParents::Any(any) => { + // FIXME check the args, too! if let Some(proof_path) = &any.path { if self_path != proof_path { return Err(()); @@ -67,6 +196,7 @@ impl CheckParents for Create { } } MutableParents::Mutate(mutate) => { + // FIXME check the args, too! if let Some(proof_path) = &mutate.path { if self_path != proof_path { return Err(()); @@ -79,3 +209,94 @@ impl CheckParents for Create { Ok(()) } } + +impl From for arguments::Named { + fn from(promised: Promised) -> Self { + let mut named = arguments::Named::new(); + + if let Some(path_res) = promised.path { + named.insert( + "path".to_string(), + path_res.map(|p| ipld::Newtype::from(p).0).into(), + ); + } + + if let Some(args_res) = promised.args { + named.insert( + "args".to_string(), + args_res + .map(|a| { + // FIXME extract + a.iter() + .map(|(k, v)| (k.to_string(), v.clone().serialize_as_ipld())) + .collect::>() + }) + .into(), + ); + } + + named + } +} + +// impl From> for Promised { +// fn from(source: arguments::Named) -> Self { +// let path = source +// .get("path") +// .map(|ipld| ipld.clone().try_into().unwrap()); +// +// let args = source +// .get("args") +// .map(|ipld| ipld.clone().try_into().unwrap()); +// Promised { path, args } +// } +// } + +impl From for Promised { + fn from(r: Ready) -> Promised { + Promised { + path: r + .path + .map(|inner_path| promise::PromiseOk::Fulfilled(inner_path).into()), + + args: r.args.map(|inner_args| Resolves::new(inner_args.into())), + } + } +} + +impl Resolvable for Ready { + type Promised = Promised; + + fn try_resolve(p: Promised) -> Result { + // FIXME extract & cleanup + let path = match p.path { + Some(ref res_path) => match res_path.clone().try_resolve() { + Ok(path) => Some(Ok(path)), + Err(unresolved) => Some(Err(Promised { + path: Some(unresolved), + args: p.args.clone(), + })), + }, + None => None, + } + .transpose()?; + + // FIXME extract & cleanup + let args = match p.args { + Some(ref res_args) => match res_args.clone().try_resolve() { + Ok(args) => { + let ipld = args.try_into().map_err(|_| p.clone())?; + Some(Ok(ipld)) + } + Err(unresolved) => Some(Err(Promised { + path: path.clone().map(|p| Resolves::new(p)), + args: Some(unresolved), + })), + }, + None => None, + } + .transpose()?; + + Ok(Ready { path, args }) + } +} diff --git a/src/ability/crud/destroy.rs b/src/ability/crud/destroy.rs index d448a34f..b1ad4189 100644 --- a/src/ability/crud/destroy.rs +++ b/src/ability/crud/destroy.rs @@ -1,58 +1,258 @@ +//! Destroy a resource. +use super::{error::ProofError, parents::MutableParents}; use crate::{ - ability::command::Command, - proof::{checkable::Checkable, parentful::Parentful, parents::CheckParents, same::CheckSame}, + ability::{arguments, command::Command}, + invocation::{promise, promise::Resolves, Resolvable}, + ipld, + proof::{ + checkable::Checkable, error::OptionalFieldError, parentful::Parentful, + parents::CheckParents, same::CheckSame, util::check_optional, + }, }; use libipld_core::{error::SerdeError, ipld::Ipld, serde as ipld_serde}; -use serde::{Deserialize, Serialize}; -use std::path::PathBuf; +use serde::{de::DeserializeOwned, Deserialize, Serialize}; +use std::{collections::BTreeMap, path::PathBuf}; -use super::parents::MutableParents; +// FIXME deserialize instance -// Destroy is its own builder -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +/// A helper for creating lifecycle instances of `crud/create` with the correct shape. +#[derive(Debug, Clone, PartialEq, Serialize)] #[serde(deny_unknown_fields)] -pub struct Destroy { +pub struct Generic { + /// An optional path to a sub-resource that is to be destroyed. #[serde(default, skip_serializing_if = "Option::is_none")] - pub path: Option, + pub path: Option, } -impl Command for Destroy { +#[cfg_attr(doc, aquamarine::aquamarine)] +/// The executable/dispatchable variant of the `crud/destroy` ability. +/// +/// # Lifecycle +/// +/// The relevant hierarchy of CRUD abilities is as follows: +/// +/// ```mermaid +/// flowchart LR +/// subgraph Delegations +/// top("*") +/// +/// subgraph CRUD Abilities +/// any("crud/*") +/// +/// mutate("crud/mutate") +/// +/// subgraph Invokable +/// destroy("crud/destroy") +/// end +/// end +/// end +/// +/// destroypromise("crud::destroy::Promised") +/// destroyready("crud::destroy::Ready") +/// +/// top --> any --> mutate --> destroy +/// destroy -.->|invoke| destroypromise -.->|resolve| destroyready -.-> exe{{execute}} +/// +/// style destroyready stroke:orange; +/// ``` +pub type Ready = Generic; + +#[cfg_attr(doc, aquamarine::aquamarine)] +/// The delegatable ability for destroying resources. +/// +/// # Lifecycle +/// +/// The lifecycle of a `crud/destroy` ability is as follows: +/// +/// ```mermaid +/// flowchart LR +/// subgraph Delegations +/// top("*") +/// +/// subgraph CRUD Abilities +/// any("crud/*") +/// +/// mutate("crud/mutate") +/// +/// subgraph Invokable +/// destroy("crud/destroy") +/// end +/// end +/// end +/// +/// destroypromise("crud::destroy::Promised") +/// destroyready("crud::destroy::Ready") +/// +/// top --> any --> mutate --> destroy +/// destroy -.->|invoke| destroypromise -.->|resolve| destroyready -.-> exe{{execute}} +/// +/// style destroy stroke:orange; +/// ``` +pub type Builder = Generic; + +#[cfg_attr(doc, aquamarine::aquamarine)] +/// An invoked `crud/destroy` ability (but possibly awaiting another +/// [`Invocation`][crate::invocation::Invocation]). +/// +/// # Delegation Hierarchy +/// +/// The hierarchy of CRUD abilities is as follows: +/// +/// ```mermaid +/// flowchart LR +/// subgraph Delegations +/// top("*") +/// +/// subgraph CRUD Abilities +/// any("crud/*") +/// +/// mutate("crud/mutate") +/// +/// subgraph Invokable +/// destroy("crud/destroy") +/// end +/// end +/// end +/// +/// destroypromise("crud::destroy::Promised") +/// destroyready("crud::destroy::Ready") +/// +/// top --> any --> mutate --> destroy +/// destroy -.->|invoke| destroypromise -.->|resolve| destroyready -.-> exe{{execute}} +/// +/// style destroypromise stroke:orange; +/// ``` +pub type Promised = Generic>; + +impl

Command for Generic

{ const COMMAND: &'static str = "crud/destroy"; } -impl From for Ipld { - fn from(destroy: Destroy) -> Self { +impl> From> for Ipld { + fn from(destroy: Generic

) -> Self { destroy.into() } } -impl TryFrom for Destroy { - type Error = SerdeError; +impl> TryFrom for Generic

{ + type Error = (); // FIXME fn try_from(ipld: Ipld) -> Result { - ipld_serde::from_ipld(ipld) + if let Ipld::Map(mut map) = ipld { + if map.len() > 1 { + return Err(()); // FIXME + } + + Ok(Generic { + path: map + .remove("path") + .map(|ipld| P::try_from(ipld).map_err(|_| ())) + .transpose()?, + }) + } else { + Err(()) // FIXME + } } } -impl Checkable for Destroy { - type Hierarchy = Parentful; +impl Checkable for Builder { + type Hierarchy = Parentful; } -impl CheckSame for Destroy { - type Error = (); - fn check_same(&self, _proof: &Self) -> Result<(), Self::Error> { - Ok(()) +impl CheckSame for Builder { + type Error = (); // FIXME better error + + fn check_same(&self, proof: &Self) -> Result<(), Self::Error> { + if self.path == proof.path { + Ok(()) + } else { + Err(()) + } } } -impl CheckParents for Destroy { +impl CheckParents for Builder { type Parents = MutableParents; - type ParentError = (); + type ParentError = (); // FIXME fn check_parent(&self, other: &Self::Parents) -> Result<(), Self::ParentError> { - match other { - MutableParents::Mutate(_mutate) => Ok(()), // FIXME - MutableParents::Any(_any) => Ok(()), // FIXME + if let Some(self_path) = &self.path { + match other { + MutableParents::Any(any) => { + if let Some(proof_path) = &any.path { + if self_path != proof_path { + return Err(()); + } + } + } + MutableParents::Mutate(mutate) => { + if let Some(proof_path) = &mutate.path { + if self_path != proof_path { + return Err(()); + } + } + } + } } + + Ok(()) + } +} + +impl From for arguments::Named { + fn from(promised: Promised) -> Self { + let mut named = arguments::Named::new(); + + if let Some(path_res) = promised.path { + named.insert( + "path".to_string(), + path_res.map(|p| ipld::Newtype::from(p).0).into(), + ); + } + + named + } +} + +// impl From> for Promised { +// fn from(source: arguments::Named) -> Self { +// let path = source +// .get("path") +// .map(|ipld| ipld.clone().try_into().unwrap()); +// +// let args = source +// .get("args") +// .map(|ipld| ipld.clone().try_into().unwrap()); +// Promised { path, args } +// } +// } + +impl From for Promised { + fn from(r: Ready) -> Promised { + Promised { + path: r + .path + .map(|inner_path| promise::PromiseOk::Fulfilled(inner_path).into()), + } + } +} + +impl Resolvable for Ready { + type Promised = Promised; + + fn try_resolve(p: Promised) -> Result { + // FIXME extract & cleanup + let path = match p.path { + Some(ref res_path) => match res_path.clone().try_resolve() { + Ok(path) => Some(Ok(path)), + Err(unresolved) => Some(Err(Promised { + path: Some(unresolved), + })), + }, + None => None, + } + .transpose()?; + + Ok(Ready { path }) } } diff --git a/src/ability/crud/error.rs b/src/ability/crud/error.rs index 544ca58c..a4103586 100644 --- a/src/ability/crud/error.rs +++ b/src/ability/crud/error.rs @@ -1,10 +1,12 @@ -use crate::{ability::arguments, proof::error::OptionalFieldError}; +//! CRUD-specific errors + +use crate::{ + ability::arguments, + proof::{error::OptionalFieldError, parents::CheckParents, same::CheckSame}, +}; use serde::{Deserialize, Serialize}; use thiserror::Error; -#[cfg(target_arch = "wasm32")] -use wasm_bindgen::prelude::*; - #[derive(Debug, Clone, PartialEq, Error, Serialize, Deserialize)] pub enum ProofError { #[error("An issue with the path field")] @@ -17,13 +19,22 @@ pub enum ProofError { MissingProofArgs, } -// FIXME Is this just OptionalFieldError? -// #[derive(Debug, Clone, PartialEq, Error, Serialize, Deserialize)] -// #[cfg_attr(target_arch = "wasm32", wasm_bindgen)] -// pub enum PathError { -// #[error("Path required in proof, but was not present")] -// Missing, -// -// #[error("Proof path did not match")] -// Mismatch, -// } +/// Error cases when checking [`MutableParents`] proofs +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Error)] +pub enum ParentError { + /// Error when comparing `crud/*` to another `crud/*`. + #[error(transparent)] + InvalidAnyProof(::Error), + + /// Error when comparing `crud/mutate` to another `crud/mutate`. + #[error(transparent)] + InvalidMutateProof(::Error), + + /// Error when comparing `crud/*` as a proof for `crud/mutate`. + #[error(transparent)] + InvalidMutateParent(::ParentError), + + /// "Expected `crud/*`, but got `crud/mutate`". + #[error("Expected `crud/*`, but got `crud/mutate`")] + CommandEscelation, +} diff --git a/src/ability/crud/js.rs b/src/ability/crud/js.rs index c66f87e4..1153b7b0 100644 --- a/src/ability/crud/js.rs +++ b/src/ability/crud/js.rs @@ -1,3 +1,5 @@ +//! JavaScript bindings for the CRUD abilities. + use super::{read, Any}; use wasm_bindgen::prelude::*; diff --git a/src/ability/crud/parents.rs b/src/ability/crud/parents.rs index 93f223b3..cad0494d 100644 --- a/src/ability/crud/parents.rs +++ b/src/ability/crud/parents.rs @@ -4,13 +4,52 @@ //! This only needs to handle "inner" delegation types, not the topmost `*` //! ability, or the invocable leaves of a delegation hierarchy. +use super::error::ParentError; use crate::proof::{parents::CheckParents, same::CheckSame}; use serde::{Deserialize, Serialize}; use thiserror::Error; +#[cfg_attr(doc, aquamarine::aquamarine)] /// The union of mutable parents. /// /// This is helpful as a flat type to put in [`CheckParents::Parents`]. +/// +/// # Delegation Hierarchy +/// +/// The parents captured here are highlted in the following diagram: +/// +/// ```mermaid +/// flowchart TB +/// top("*") +/// +/// subgraph CRUD Abilities +/// any("crud/*") +/// +/// mutate("crud/mutate") +/// +/// subgraph Invokable +/// read("crud/read") +/// create("crud/create") +/// update("crud/update") +/// destroy("crud/destroy") +/// end +/// end +/// +/// readrun{{"invoke"}} +/// createrun{{"invoke"}} +/// updaterun{{"invoke"}} +/// destroyrun{{"invoke"}} +/// +/// top --> any +/// any --> read -.-> readrun +/// any --> mutate +/// mutate --> create -.-> createrun +/// mutate --> update -.-> updaterun +/// mutate --> destroy -.-> destroyrun +/// +/// style any stroke:orange; +/// style mutate stroke:orange; +/// ``` #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] #[serde(deny_unknown_fields)] pub enum MutableParents { @@ -22,46 +61,26 @@ pub enum MutableParents { } impl CheckSame for MutableParents { - type Error = Error; + type Error = ParentError; fn check_same(&self, proof: &Self) -> Result<(), Self::Error> { match self { MutableParents::Mutate(mutate) => match proof { MutableParents::Mutate(proof_mutate) => mutate .check_same(proof_mutate) - .map_err(Error::InvalidMutateProof), + .map_err(ParentError::InvalidMutateProof), MutableParents::Any(proof_any) => mutate .check_parent(proof_any) - .map_err(Error::InvalidMutateParent), + .map_err(ParentError::InvalidMutateParent), }, MutableParents::Any(any) => match proof { - MutableParents::Mutate(_) => Err(Error::CommandEscelation), - MutableParents::Any(proof_any) => { - any.check_same(proof_any).map_err(Error::InvalidAnyProof) - } + MutableParents::Mutate(_) => Err(ParentError::CommandEscelation), + MutableParents::Any(proof_any) => any + .check_same(proof_any) + .map_err(ParentError::InvalidAnyProof), }, } } } - -/// Error cases when checking [`MutableParents`] proofs -#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Error)] -pub enum Error { - /// Error when comparing `crud/*` to another `crud/*`. - #[error(transparent)] - InvalidAnyProof(::Error), - - /// Error when comparing `crud/mutate` to another `crud/mutate`. - #[error(transparent)] - InvalidMutateProof(::Error), - - /// Error when comparing `crud/*` as a proof for `crud/mutate`. - #[error(transparent)] - InvalidMutateParent(::ParentError), - - /// "Expected `crud/*`, but got `crud/mutate`". - #[error("Expected `crud/*`, but got `crud/mutate`")] - CommandEscelation, -} diff --git a/src/ability/crud/read.rs b/src/ability/crud/read.rs index bdc0b764..6b899eec 100644 --- a/src/ability/crud/read.rs +++ b/src/ability/crud/read.rs @@ -1,35 +1,40 @@ -//! The ability to read (fetch) from a resource. -//! -//! * This ability may be invoked when [`Ready`]. -//! * See the [`Builder`] to view the [delegation chain](./type.Builder.html#delegation-hierarchy). -//! * The invocation [Lifecycle](./struct.Ready.html#lifecycle) can be found on [`Ready`] or [`Promised`]. +//! Read from a resource. -use super::error::ProofError; +use super::{error::ProofError, parents::MutableParents}; use crate::{ ability::{arguments, command::Command}, invocation::{promise, promise::Resolves, Resolvable}, + ipld, proof::{ checkable::Checkable, error::OptionalFieldError, parentful::Parentful, parents::CheckParents, same::CheckSame, util::check_optional, }, }; use libipld_core::{error::SerdeError, ipld::Ipld, serde as ipld_serde}; -use serde::{Deserialize, Serialize}; -use std::path::PathBuf; +use serde::{de::DeserializeOwned, Deserialize, Serialize}; +use std::{collections::BTreeMap, path::PathBuf}; -#[cfg(target_arch = "wasm32")] -use wasm_bindgen::prelude::*; +// FIXME deserialize instance + +/// A helper for creating lifecycle instances of `crud/create` with the correct shape. +#[derive(Debug, Clone, PartialEq, Serialize)] +#[serde(deny_unknown_fields)] +pub struct Generic { + /// An optional path to a sub-resource that is to be read. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub path: Option, + + /// Optional arguments to modify the read request. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub args: Option, +} #[cfg_attr(doc, aquamarine::aquamarine)] -/// The CRUD ability to retrieve data from a resource. -/// -/// # Invocation -/// -/// The executable/dispatchable variant of the `msg/send` ability. +/// This ability is used to fetch messages from other actors. /// /// # Lifecycle /// -/// The hierarchy of message abilities is as follows: +/// The relevant hierarchy of CRUD abilities is as follows: /// /// ```mermaid /// flowchart LR @@ -44,65 +49,112 @@ use wasm_bindgen::prelude::*; /// end /// /// readpromise("crud::read::Promised") -/// readrun("crud::read::Ready") +/// readready("crud::read::Ready") /// -/// top --> any -/// any --> read -.->|invoke| readpromise -.->|resolve| readrun -.-> exe{{execute}} +/// top --> any --> read +/// read -.->|invoke| readpromise -.->|resolve| readready -.-> exe{{execute}} /// -/// style readrun stroke:orange; +/// style readready stroke:orange; /// ``` -#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] -#[serde(deny_unknown_fields)] -pub struct Ready { - /// Optional path within the resource. - #[serde(default, skip_serializing_if = "Option::is_none")] - pub path: Option, +pub type Ready = Generic>; - /// Additional arugments to pass in the request. - pub args: arguments::Named, -} +#[cfg_attr(doc, aquamarine::aquamarine)] +/// The delegatable ability for reading resources. +/// +/// # Lifecycle +/// +/// The lifecycle of a `crud/read` ability is as follows: +/// +/// ```mermaid +/// flowchart LR +/// subgraph Delegations +/// top("*") +/// +/// subgraph CRUD Abilities +/// any("crud/*") +/// +/// subgraph Invokable +/// read("crud/read") +/// end +/// end +/// end +/// +/// readpromise("crud::read::Promised") +/// readready("crud::read::Ready") +/// +/// top --> any --> read +/// read -.->|invoke| readpromise -.->|resolve| readready -.-> exe{{execute}} +/// +/// style read stroke:orange; +/// ``` +pub type Builder = Generic>; #[cfg_attr(doc, aquamarine::aquamarine)] -/// The CRUD ability to retrieve data from a resource. +/// An invoked `crud/read` ability (but possibly awaiting another +/// [`Invocation`][crate::invocation::Invocation]). /// /// # Delegation Hierarchy /// /// The hierarchy of CRUD abilities is as follows: /// /// ```mermaid -/// flowchart TB -/// top("*") +/// flowchart LR +/// subgraph Delegations +/// top("*") /// -/// subgraph Message Abilities -/// any("crud/*") +/// subgraph CRUD Abilities +/// any("crud/*") /// -/// subgraph Invokable -/// read("crud/read") +/// subgraph Invokable +/// read("crud/read") +/// end /// end /// end /// -/// readrun{{"invoke"}} +/// readpromise("crud::read::Promised") +/// readready("crud::read::Ready") /// -/// top --> any -/// any --> read -.-> readrun +/// top --> any --> read +/// read -.->|invoke| readpromise -.->|resolve| readready -.-> exe{{execute}} /// -/// style read stroke:orange; +/// style readpromise stroke:orange; /// ``` -#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] -#[serde(deny_unknown_fields)] -pub struct Builder { - // FIXME ^^^^^^ rename delegation as a pattern - /// Optional path within the resource. - #[serde(default, skip_serializing_if = "Option::is_none")] - pub path: Option, +pub type Promised = Generic, arguments::Promised>; - #[serde(default, skip_serializing_if = "Option::is_none")] - /// Additional arugments to pass in the request. - pub args: Option>, +impl Command for Generic { + const COMMAND: &'static str = "crud/read"; } -impl Command for Ready { - const COMMAND: &'static str = "crud/read"; +impl, A: Into> From> for Ipld { + fn from(read: Generic) -> Self { + read.into() + } +} + +impl, A: TryFrom> TryFrom for Generic { + type Error = (); // FIXME + + fn try_from(ipld: Ipld) -> Result { + if let Ipld::Map(mut map) = ipld { + if map.len() > 2 { + return Err(()); // FIXME + } + + Ok(Generic { + path: map + .remove("path") + .map(|ipld| P::try_from(ipld).map_err(|_| ())) + .transpose()?, + + args: map + .remove("args") + .map(|ipld| A::try_from(ipld).map_err(|_| ())) + .transpose()?, + }) + } else { + Err(()) // FIXME + } + } } impl Checkable for Builder { @@ -110,30 +162,40 @@ impl Checkable for Builder { } impl CheckSame for Builder { - type Error = ProofError; + type Error = (); // FIXME better error fn check_same(&self, proof: &Self) -> Result<(), Self::Error> { - check_optional(self.path.as_ref(), proof.path.as_ref()) - .map_err(Into::::into)?; - - let args = self.args.as_ref().ok_or(ProofError::MissingProofArgs)?; - if let Some(proof_args) = &proof.args { - args.contains(proof_args).map_err(Into::into) - } else { + if self.path == proof.path { Ok(()) + } else { + Err(()) } } } impl CheckParents for Builder { - type Parents = super::Any; - type ParentError = OptionalFieldError; - - fn check_parent(&self, crud_any: &super::Any) -> Result<(), Self::ParentError> { - if let Some(path) = &self.path { - let crud_any_path = crud_any.path.as_ref().ok_or(OptionalFieldError::Missing)?; - if path != crud_any_path { - return Err(OptionalFieldError::Unequal); + type Parents = MutableParents; + type ParentError = (); // FIXME + + fn check_parent(&self, other: &Self::Parents) -> Result<(), Self::ParentError> { + if let Some(self_path) = &self.path { + match other { + MutableParents::Any(any) => { + // FIXME check the args, too! + if let Some(proof_path) = &any.path { + if self_path != proof_path { + return Err(()); + } + } + } + MutableParents::Mutate(mutate) => { + // FIXME check the args, too! + if let Some(proof_path) = &mutate.path { + if self_path != proof_path { + return Err(()); + } + } + } } } @@ -141,82 +203,57 @@ impl CheckParents for Builder { } } -impl From for Ipld { - fn from(ready: Ready) -> Self { - ready.into() - } -} -impl TryFrom for Ready { - type Error = SerdeError; - - fn try_from(ipld: Ipld) -> Result { - ipld_serde::from_ipld(ipld) - } -} +impl From for arguments::Named { + fn from(promised: Promised) -> Self { + let mut named = arguments::Named::new(); -#[cfg_attr(doc, aquamarine::aquamarine)] -/// This ability is used to fetch messages from other actors. -/// -/// # Lifecycle -/// -/// The hierarchy of message abilities is as follows: -/// -/// ```mermaid -/// flowchart LR -/// subgraph Delegations -/// top("*") -/// -/// any("crud/*") -/// -/// subgraph Invokable -/// read("crud/read") -/// end -/// end -/// -/// readpromise("crud::read::Promised") -/// readrun("crud::read::Ready") -/// -/// top --> any -/// any --> read -.->|invoke| readpromise -.->|resolve| readrun -.-> exe{{execute}} -/// -/// style readpromise stroke:orange; -/// ``` -#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] -#[serde(deny_unknown_fields)] -pub struct Promised { - /// Optional path within the resource - #[serde(skip_serializing_if = "promise::Resolves::resolved_none")] - pub path: promise::Resolves>, + if let Some(path_res) = promised.path { + named.insert( + "path".to_string(), + path_res.map(|p| ipld::Newtype::from(p).0).into(), + ); + } - pub args: arguments::Promised, -} + if let Some(args_res) = promised.args { + named.insert( + "args".to_string(), + args_res + .map(|a| { + // FIXME extract + a.iter() + .map(|(k, v)| (k.to_string(), v.clone().serialize_as_ipld())) + .collect::>() + }) + .into(), + ); + } -impl From for Ipld { - fn from(promised: Promised) -> Self { - promised.into() + named } } -impl TryFrom for Promised { - type Error = SerdeError; - - fn try_from(ipld: Ipld) -> Result { - ipld_serde::from_ipld(ipld) - } -} +// impl From> for Promised { +// fn from(source: arguments::Named) -> Self { +// let path = source +// .get("path") +// .map(|ipld| ipld.clone().try_into().unwrap()); +// +// let args = source +// .get("args") +// .map(|ipld| ipld.clone().try_into().unwrap()); +// Promised { path, args } +// } +// } impl From for Promised { fn from(r: Ready) -> Promised { Promised { - path: promise::PromiseOk::Fulfilled(r.path).into(), - args: promise::PromiseOk::Fulfilled(r.args.into()).into(), - } - } -} + path: r + .path + .map(|inner_path| promise::PromiseOk::Fulfilled(inner_path).into()), -impl From for arguments::Named { - fn from(p: Promised) -> arguments::Named { - p.into() + args: r.args.map(|inner_args| Resolves::new(inner_args.into())), + } } } @@ -224,15 +261,35 @@ impl Resolvable for Ready { type Promised = Promised; fn try_resolve(p: Promised) -> Result { - match Resolves::try_resolve_2(p.path, p.args) { - Ok((path, promise_args)) => match promise_args.try_into() { - Ok(args) => Ok(Ready { path, args }), - Err(args) => Err(Promised { - args: promise::PromiseOk::Fulfilled(args).into(), - path: promise::PromiseOk::Fulfilled(path).into(), - }), + // FIXME extract & cleanup + let path = match p.path { + Some(ref res_path) => match res_path.clone().try_resolve() { + Ok(path) => Some(Ok(path)), + Err(unresolved) => Some(Err(Promised { + path: Some(unresolved), + args: p.args.clone(), + })), + }, + None => None, + } + .transpose()?; + + // FIXME extract & cleanup + let args = match p.args { + Some(ref res_args) => match res_args.clone().try_resolve() { + Ok(args) => { + let ipld = args.try_into().map_err(|_| p.clone())?; + Some(Ok(ipld)) + } + Err(unresolved) => Some(Err(Promised { + path: path.clone().map(|p| Resolves::new(p)), + args: Some(unresolved), + })), }, - Err((path, args)) => Err(Promised { path, args }), + None => None, } + .transpose()?; + + Ok(Ready { path, args }) } } diff --git a/src/ability/crud/update.rs b/src/ability/crud/update.rs index 3c8ef326..44e29305 100644 --- a/src/ability/crud/update.rs +++ b/src/ability/crud/update.rs @@ -1,63 +1,166 @@ +//! Update existing resources. use super::{error::ProofError, parents::MutableParents}; use crate::{ ability::{arguments, command::Command}, - invocation::{promise, Resolvable}, - proof::{checkable::Checkable, parentful::Parentful, parents::CheckParents, same::CheckSame}, + invocation::{promise, promise::Resolves, Resolvable}, + ipld, + proof::{ + checkable::Checkable, error::OptionalFieldError, parentful::Parentful, + parents::CheckParents, same::CheckSame, util::check_optional, + }, }; use libipld_core::{error::SerdeError, ipld::Ipld, serde as ipld_serde}; -use serde::{Deserialize, Serialize}; -use std::path::PathBuf; +use serde::{de::DeserializeOwned, Deserialize, Serialize}; +use std::{collections::BTreeMap, path::PathBuf}; -#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +// FIXME deserialize instance + +/// A helper for creating lifecycle instances of `crud/create` with the correct shape. +#[derive(Debug, Clone, PartialEq, Serialize)] #[serde(deny_unknown_fields)] -pub struct Ready { - /// Optional path within the resource. +pub struct Generic { + /// An optional path to a sub-resource that is to be updated. #[serde(default, skip_serializing_if = "Option::is_none")] - pub path: Option, - - /// Additional arugments to pass in the request. - pub args: arguments::Named, -} + pub path: Option, -impl From for Ipld { - fn from(udpdate: Ready) -> Self { - udpdate.into() - } + /// Optional arguments to be passed in the update. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub args: Option, } -impl TryFrom for Ready { - type Error = SerdeError; - - fn try_from(ipld: Ipld) -> Result { - ipld_serde::from_ipld(ipld) - } -} +#[cfg_attr(doc, aquamarine::aquamarine)] +/// The executable/dispatchable variant of the `crud/create` ability. +/// +/// # Lifecycle +/// +/// The relevant hierarchy of CRUD abilities is as follows: +/// +/// ```mermaid +/// flowchart LR +/// subgraph Delegations +/// top("*") +/// +/// subgraph CRUD Abilities +/// any("crud/*") +/// +/// mutate("crud/mutate") +/// +/// subgraph Invokable +/// update("crud/update") +/// end +/// end +/// end +/// +/// updatepromise("crud::update::Promised") +/// updateready("crud::update::Ready") +/// +/// top --> any --> mutate --> update +/// update -.->|invoke| updatepromise -.->|resolve| updateready -.-> exe{{execute}} +/// +/// style updateready stroke:orange; +/// ``` +pub type Ready = Generic>; -impl Command for Ready { - const COMMAND: &'static str = "crud/update"; -} +#[cfg_attr(doc, aquamarine::aquamarine)] +/// The delegatable ability for updating existing agents. +/// +/// # Lifecycle +/// +/// The lifecycle of a `crud/create` ability is as follows: +/// +/// ```mermaid +/// flowchart LR +/// subgraph Delegations +/// top("*") +/// +/// subgraph CRUD Abilities +/// any("crud/*") +/// +/// mutate("crud/mutate") +/// +/// subgraph Invokable +/// update("crud/update") +/// end +/// end +/// end +/// +/// updatepromise("crud::update::Promised") +/// updateready("crud::update::Ready") +/// +/// top --> any --> mutate --> update +/// update -.->|invoke| updatepromise -.->|resolve| updateready -.-> exe{{execute}} +/// +/// style update stroke:orange; +/// ``` +pub type Builder = Generic>; -#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] -#[serde(deny_unknown_fields)] -pub struct Builder { - #[serde(default, skip_serializing_if = "Option::is_none")] - pub path: Option, +#[cfg_attr(doc, aquamarine::aquamarine)] +/// An invoked `crud/update` ability (but possibly awaiting another +/// [`Invocation`][crate::invocation::Invocation]). +/// +/// # Delegation Hierarchy +/// +/// The hierarchy of CRUD abilities is as follows: +/// +/// ```mermaid +/// flowchart LR +/// subgraph Delegations +/// top("*") +/// +/// subgraph CRUD Abilities +/// any("crud/*") +/// +/// mutate("crud/mutate") +/// +/// subgraph Invokable +/// update("crud/update") +/// end +/// end +/// end +/// +/// updatepromise("crud::update::Promised") +/// updateready("crud::update::Ready") +/// +/// top --> any --> mutate --> update +/// update -.->|invoke| updatepromise -.->|resolve| updateready -.-> exe{{execute}} +/// +/// style updatepromise stroke:orange; +/// ``` +pub type Promised = Generic, arguments::Promised>; - #[serde(default, skip_serializing_if = "Option::is_none")] - pub args: Option>, +impl Command for Generic { + const COMMAND: &'static str = "crud/create"; } -impl From for Ipld { - fn from(udpdate: Builder) -> Self { - udpdate.into() +impl, A: Into> From> for Ipld { + fn from(create: Generic) -> Self { + create.into() } } -impl TryFrom for Builder { - type Error = SerdeError; +impl, A: TryFrom> TryFrom for Generic { + type Error = (); // FIXME fn try_from(ipld: Ipld) -> Result { - ipld_serde::from_ipld(ipld) + if let Ipld::Map(mut map) = ipld { + if map.len() > 2 { + return Err(()); // FIXME + } + + Ok(Generic { + path: map + .remove("path") + .map(|ipld| P::try_from(ipld).map_err(|_| ())) + .transpose()?, + + args: map + .remove("args") + .map(|ipld| A::try_from(ipld).map_err(|_| ())) + .transpose()?, + }) + } else { + Err(()) // FIXME + } } } @@ -66,51 +169,98 @@ impl Checkable for Builder { } impl CheckSame for Builder { - type Error = ProofError; + type Error = (); // FIXME better error fn check_same(&self, proof: &Self) -> Result<(), Self::Error> { - self.path - .check_same(&proof.path) - .map_err(Into::::into)?; - self.args.check_same(&proof.args).map_err(Into::into) + if self.path == proof.path { + Ok(()) + } else { + Err(()) + } } } impl CheckParents for Builder { type Parents = MutableParents; - type ParentError = ProofError; + type ParentError = (); // FIXME - fn check_parent(&self, proof: &Self::Parents) -> Result<(), Self::ParentError> { - match proof { - MutableParents::Any(any) => self.path.check_same(&any.path).map_err(Into::into), - MutableParents::Mutate(mutate) => { - self.path.check_same(&mutate.path).map_err(Into::into) + fn check_parent(&self, other: &Self::Parents) -> Result<(), Self::ParentError> { + if let Some(self_path) = &self.path { + match other { + MutableParents::Any(any) => { + // FIXME check the args, too! + if let Some(proof_path) = &any.path { + if self_path != proof_path { + return Err(()); + } + } + } + MutableParents::Mutate(mutate) => { + // FIXME check the args, too! + if let Some(proof_path) = &mutate.path { + if self_path != proof_path { + return Err(()); + } + } + } } } + + Ok(()) } } -#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] -#[serde(deny_unknown_fields)] -pub struct Promised { - #[serde(skip_serializing_if = "promise::Resolves::resolved_none")] - pub path: promise::Resolves>, +impl From for arguments::Named { + fn from(promised: Promised) -> Self { + let mut named = arguments::Named::new(); + + if let Some(path_res) = promised.path { + named.insert( + "path".to_string(), + path_res.map(|p| ipld::Newtype::from(p).0).into(), + ); + } - pub args: arguments::Promised, + if let Some(args_res) = promised.args { + named.insert( + "args".to_string(), + args_res + .map(|a| { + // FIXME extract + a.iter() + .map(|(k, v)| (k.to_string(), v.clone().serialize_as_ipld())) + .collect::>() + }) + .into(), + ); + } + + named + } } +// impl From> for Promised { +// fn from(source: arguments::Named) -> Self { +// let path = source +// .get("path") +// .map(|ipld| ipld.clone().try_into().unwrap()); +// +// let args = source +// .get("args") +// .map(|ipld| ipld.clone().try_into().unwrap()); +// Promised { path, args } +// } +// } + impl From for Promised { fn from(r: Ready) -> Promised { Promised { - path: promise::PromiseOk::Fulfilled(r.path).into(), - args: promise::PromiseOk::Fulfilled(r.args.into()).into(), - } - } -} + path: r + .path + .map(|inner_path| promise::PromiseOk::Fulfilled(inner_path).into()), -impl From for arguments::Named { - fn from(p: Promised) -> arguments::Named { - p.into() + args: r.args.map(|inner_args| Resolves::new(inner_args.into())), + } } } @@ -118,19 +268,35 @@ impl Resolvable for Ready { type Promised = Promised; fn try_resolve(p: Promised) -> Result { - // FIXME resolve2? - // FIXME lots of clone - Ok(Ready { - path: p.path.clone().try_resolve().map_err(|path| Promised { - path, - args: p.args.clone(), - })?, - - args: p - .args - .clone() - .try_into() - .map_err(|args| Promised { path: p.path, args })?, - }) + // FIXME extract & cleanup + let path = match p.path { + Some(ref res_path) => match res_path.clone().try_resolve() { + Ok(path) => Some(Ok(path)), + Err(unresolved) => Some(Err(Promised { + path: Some(unresolved), + args: p.args.clone(), + })), + }, + None => None, + } + .transpose()?; + + // FIXME extract & cleanup + let args = match p.args { + Some(ref res_args) => match res_args.clone().try_resolve() { + Ok(args) => { + let ipld = args.try_into().map_err(|_| p.clone())?; + Some(Ok(ipld)) + } + Err(unresolved) => Some(Err(Promised { + path: path.clone().map(|p| Resolves::new(p)), + args: Some(unresolved), + })), + }, + None => None, + } + .transpose()?; + + Ok(Ready { path, args }) } } diff --git a/src/invocation/payload.rs b/src/invocation/payload.rs index adf6dc9b..b7fcdd48 100644 --- a/src/invocation/payload.rs +++ b/src/invocation/payload.rs @@ -33,7 +33,7 @@ pub struct Payload { // FIXME To TaskId // NOTE This is the version that accepts promises -pub type Unresolved = Payload; +pub type Unresolved = Payload<::Promised>; // type Dynamic = Payload; <- ? // FIXME parser for both versions diff --git a/src/invocation/promise/resolves.rs b/src/invocation/promise/resolves.rs index a8df1488..216939aa 100644 --- a/src/invocation/promise/resolves.rs +++ b/src/invocation/promise/resolves.rs @@ -4,6 +4,7 @@ use serde::{Deserialize, Serialize}; use std::fmt; #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +#[serde(untagged)] pub enum Resolves { Ok(PromiseOk), Err(PromiseErr), diff --git a/src/invocation/resolvable.rs b/src/invocation/resolvable.rs index 3a1f23da..bc9103ea 100644 --- a/src/invocation/resolvable.rs +++ b/src/invocation/resolvable.rs @@ -6,9 +6,4 @@ pub trait Resolvable: Sized { // FIXME indeed needed to get teh right err type fn try_resolve(promised: Self::Promised) -> Result; - - // FIXME remove - fn try_resolve0(promised: Self::Promised) -> Result { - Self::try_resolve(promised) - } } diff --git a/src/ipld.rs b/src/ipld.rs index d57a311f..bec0bc37 100644 --- a/src/ipld.rs +++ b/src/ipld.rs @@ -1,4 +1,4 @@ -//! Helpers for working with [`Ipld`] +//! Helpers for working with [`Ipld`][libipld_core::ipld::Ipld] mod enriched; mod newtype; diff --git a/src/ipld/newtype.rs b/src/ipld/newtype.rs index 95bf9fea..f2252979 100644 --- a/src/ipld/newtype.rs +++ b/src/ipld/newtype.rs @@ -1,5 +1,6 @@ use libipld_core::ipld::Ipld; use serde::{Deserialize, Serialize}; +use std::path::PathBuf; #[cfg(target_arch = "wasm32")] use wasm_bindgen::prelude::*; @@ -48,6 +49,12 @@ impl From for Ipld { } } +impl From for Newtype { + fn from(path: PathBuf) -> Self { + Newtype(Ipld::String(path.to_string_lossy().to_string())) + } +} + #[cfg(target_arch = "wasm32")] impl Newtype { pub fn try_from_js>(js_val: JsValue) -> Result diff --git a/src/ipld/promised.rs b/src/ipld/promised.rs index d64ada74..8f62f9f3 100644 --- a/src/ipld/promised.rs +++ b/src/ipld/promised.rs @@ -1,6 +1,6 @@ use super::enriched::Enriched; use crate::invocation::promise::{Promise, PromiseAny, PromiseErr, PromiseOk}; -use libipld_core::{error::SerdeError, ipld::Ipld}; +use libipld_core::{error::SerdeError, ipld::Ipld, serde as ipld_serde}; use serde::{Deserialize, Serialize}; /// A promise to recursively resolve to an [`Ipld`] value. @@ -8,6 +8,18 @@ use serde::{Deserialize, Serialize}; #[serde(transparent)] pub struct Promised(pub Promise, Enriched>); +// impl From for Ipld { +// fn from(promised: Promised) -> Self { +// promised.into() +// } +// } + +impl Promised { + pub fn serialize_as_ipld(&self) -> Ipld { + ipld_serde::to_ipld(self).unwrap() // FIXME at worst we can do this by hand + } +} + impl From, Enriched>> for Promised { fn from(promise: Promise, Enriched>) -> Self { Promised(promise) From 3c2e49a6677ea8e4802fcc191a087c92bb70b23a Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Fri, 9 Feb 2024 21:18:44 -0800 Subject: [PATCH 063/188] Save poijt --- src/delegation/condition/contains_key.rs | 2 +- src/delegation/condition/excludes_key.rs | 15 ++++--- src/delegation/condition/max_number.rs | 2 +- src/delegation/payload.rs | 54 ++++++++++++------------ src/ipld/promised.rs | 22 +++++----- src/number.rs | 5 +-- 6 files changed, 50 insertions(+), 50 deletions(-) diff --git a/src/delegation/condition/contains_key.rs b/src/delegation/condition/contains_key.rs index 87b53498..df6844df 100644 --- a/src/delegation/condition/contains_key.rs +++ b/src/delegation/condition/contains_key.rs @@ -35,7 +35,7 @@ use serde_derive::{Deserialize, Serialize}; /// assert!(!cond.validate(&list)); /// assert!(!cond.validate(&ipld!({"a": 42}).try_into().unwrap())); /// ``` -#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] #[serde(deny_unknown_fields)] pub struct ContainsKey { /// Name of the field to check. diff --git a/src/delegation/condition/excludes_key.rs b/src/delegation/condition/excludes_key.rs index 58fbfc64..09ec8523 100644 --- a/src/delegation/condition/excludes_key.rs +++ b/src/delegation/condition/excludes_key.rs @@ -16,26 +16,29 @@ use serde_derive::{Deserialize, Serialize}; /// # use ucan::delegation::{condition::{ExcludesKey, Condition}}; /// # use libipld::ipld; /// # -/// let args = ipld!({"a": {"b": 1, "c": 2}, "d": {"e": 3}}).try_into().unwrap(); /// let cond = ExcludesKey{ /// field: "a".into(), /// key: "b".into() /// }; /// -/// assert!(!cond.validate(&args)); +/// let args1 = ipld!({"a": "b", "c": "d"}).try_into().unwrap(); +/// let args2 = ipld!({"a": {"b": 1, "c": 2}, "d": {"e": 3}}).try_into().unwrap(); +/// +/// assert!(cond.validate(&args1)); +/// assert!(!cond.validate(&args2)); /// /// // Succeeds when the key is not present /// assert!(ExcludesKey { -/// field: "yep".into(), +/// field: "nope".into(), /// key: "b".into() -/// }.validate(&args)); +/// }.validate(&args2)); /// /// // Also succeeds when the input is not a map /// let list = ipld!({"a": [1, 2, 3]}).try_into().unwrap(); /// assert!(cond.validate(&list)); /// assert!(cond.validate(&ipld!({"a": 42}).try_into().unwrap())); /// ``` -#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] #[serde(deny_unknown_fields)] pub struct ExcludesKey { /// Name of the field to check. @@ -62,7 +65,7 @@ impl TryFrom for ExcludesKey { impl Condition for ExcludesKey { fn validate(&self, args: &arguments::Named) -> bool { match args.get(&self.field) { - Some(Ipld::Map(map)) => map.contains_key(&self.field), + Some(Ipld::Map(map)) => !map.contains_key(&self.key), _ => true, } } diff --git a/src/delegation/condition/max_number.rs b/src/delegation/condition/max_number.rs index 981e63ea..9e2609fd 100644 --- a/src/delegation/condition/max_number.rs +++ b/src/delegation/condition/max_number.rs @@ -8,7 +8,7 @@ use serde_derive::{Deserialize, Serialize}; /// /// A condition that checks if the length of an integer /// or float is less than or equal to a set size. -#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +#[derive(Debug, Clone, PartialEq, PartialOrd, Serialize, Deserialize)] #[serde(deny_unknown_fields)] pub struct MaxNumber { /// Name of the field to check diff --git a/src/delegation/payload.rs b/src/delegation/payload.rs index 8b12153a..921601d7 100644 --- a/src/delegation/payload.rs +++ b/src/delegation/payload.rs @@ -15,7 +15,10 @@ use crate::{ }; use libipld_core::{ipld::Ipld, serde as ipld_serde}; use serde::{de::DeserializeOwned, Deserialize, Serialize, Serializer}; -use std::{collections::BTreeMap, fmt::Debug}; +use std::{ + collections::{BTreeMap, BTreeSet}, + fmt::Debug, +}; use web_time::SystemTime; #[derive(Debug, Clone, PartialEq)] @@ -25,8 +28,8 @@ pub struct Payload { pub audience: Did, pub ability_builder: T::Builder, - pub conditions: Vec, - + pub conditions: BTreeSet, // FIXME BTreeSet? + // pub conditions: Vec, // FIXME BTreeSet? pub metadata: BTreeMap, pub nonce: Nonce, @@ -103,12 +106,14 @@ impl< where invocation::Payload: Clone, U::Builder: Clone + Into, - T::Hierarchy: From>, + T::Hierarchy: From> + Clone + Into>, + arguments::Named: From, { - let start: Acc<'a, T> = Acc { + // FIXME this is a task + let start: Acc<'_, T> = Acc { issuer: &invoked.issuer, subject: &invoked.subject, - to_check: invoked.clone().into(), // FIXME surely we can eliminate this clone + ability: invoked.clone().into(), // FIXME surely we can eliminate this clone }; let args: arguments::Named = invoked.ability.clone().into(); @@ -116,6 +121,7 @@ impl< let result = proofs.iter().fold(Ok(start), |acc, proof| { if let Ok(prev) = acc { match step(&prev, proof, &args, now) { + // FIXME add a `Outdcome::is_success` of into(result) method Outcome::ArgumentEscelation(_) => Err(()), Outcome::InvalidProofChain(_) => Err(()), Outcome::InvalidParents(_) => Err(()), @@ -124,12 +130,12 @@ impl< Outcome::ProvenByAny => Ok(Acc { issuer: &proof.issuer, subject: &proof.subject, - to_check: prev.to_check, + ability: prev.ability, }), Outcome::Proven => Ok(Acc { issuer: &proof.issuer, subject: &proof.subject, - to_check: proof.ability_builder.clone().into(), // FIXME double check + ability: proof.ability_builder.clone().into(), // FIXME double check }), } } else { @@ -149,7 +155,7 @@ impl< struct Acc<'a, T: Checkable> { issuer: &'a Did, subject: &'a Did, - to_check: T::Hierarchy, + ability: T::Hierarchy, } // FIXME this should move to Delegatable @@ -162,6 +168,8 @@ fn step<'a, T: Checkable, U: Delegatable, C: Condition>( // FIXME ^^^^^^^^^^^^ Outcome types where U::Builder: Into + Clone, + arguments::Named: From, + T::Hierarchy: Clone + Into>, { if let Err(_) = prev.issuer.check_same(&proof.audience) { return Outcome::InvalidProofChain(()); @@ -181,30 +189,22 @@ where return Outcome::InvalidProofChain(()); } - // FIXME check the spec - // if self.conditions.len() < proof.conditions { - // ...etc etc - // return Err(()); - // } - - let cond_result = - proof.conditions.iter().try_fold( - (), - |_acc, c| { - if c.validate(&args) { - Ok(()) - } else { - Err(()) - } - }, - ); + // FIXME coudl be more efficient, but sets need Ord and we have floats + let cond_result = &proof.conditions.iter().try_fold((), |_acc, c| { + // FIXME revisit + if c.validate(&args) && c.validate(&prev.ability.clone().into()) { + Ok(()) + } else { + Err(()) + } + }); if let Err(_) = cond_result { return Outcome::InvalidProofChain(()); } // FIXME pretty sure we can avoid this clone - let outcome = prev.to_check.check(&proof.ability_builder.clone().into()); + let outcome = prev.ability.check(&proof.ability_builder.clone().into()); match outcome { Outcome::Proven => Outcome::Proven, diff --git a/src/ipld/promised.rs b/src/ipld/promised.rs index 8f62f9f3..8e0f62b1 100644 --- a/src/ipld/promised.rs +++ b/src/ipld/promised.rs @@ -8,24 +8,28 @@ use serde::{Deserialize, Serialize}; #[serde(transparent)] pub struct Promised(pub Promise, Enriched>); -// impl From for Ipld { -// fn from(promised: Promised) -> Self { -// promised.into() -// } -// } - impl Promised { + // FIXME note that this is different from the failable version which is more like + // a try_reoslve... which has a note at the bottom on this module pub fn serialize_as_ipld(&self) -> Ipld { ipld_serde::to_ipld(self).unwrap() // FIXME at worst we can do this by hand } } +// Promise variants into Promised + impl From, Enriched>> for Promised { fn from(promise: Promise, Enriched>) -> Self { Promised(promise) } } +impl From, Enriched>> for Promised { + fn from(p_any: PromiseAny, Enriched>) -> Self { + Promised(p_any.into()) + } +} + impl From>> for Promised { fn from(p_ok: PromiseOk>) -> Self { Promised(p_ok.into()) @@ -38,11 +42,7 @@ impl From>> for Promised { } } -impl From, Enriched>> for Promised { - fn from(p_any: PromiseAny, Enriched>) -> Self { - Promised(p_any.into()) - } -} +// IPLD impl From for Promised { fn from(ipld: Ipld) -> Self { diff --git a/src/number.rs b/src/number.rs index f29978d4..1c38f6b5 100644 --- a/src/number.rs +++ b/src/number.rs @@ -4,10 +4,7 @@ use libipld_core::{error::SerdeError, ipld::Ipld, serde as ipld_serde}; use serde_derive::{Deserialize, Serialize}; /// The union of [`Ipld`] numeric types -/// -/// This is helpful when working with JavaScript, or with -/// values that may be given as either an integer or a float. -#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +#[derive(Debug, Clone, PartialEq, PartialOrd, Serialize, Deserialize)] #[serde(untagged)] pub enum Number { /// Designate a floating point number From 366c416517e30e8cffeb79480be4978ec20956ca Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Fri, 9 Feb 2024 23:33:50 -0800 Subject: [PATCH 064/188] Time to go through the type constraints --- src/delegation.rs | 1 + src/delegation/error.rs | 33 ++++++++ src/delegation/payload.rs | 172 +++++++++++++++++--------------------- src/proof/checkable.rs | 6 +- src/proof/parentful.rs | 30 ++++--- src/proof/parentless.rs | 23 +++-- src/proof/prove.rs | 22 ++--- 7 files changed, 144 insertions(+), 143 deletions(-) create mode 100644 src/delegation/error.rs diff --git a/src/delegation.rs b/src/delegation.rs index 3fe71ed2..b3f7259c 100644 --- a/src/delegation.rs +++ b/src/delegation.rs @@ -2,6 +2,7 @@ mod delegatable; mod payload; pub mod condition; +pub mod error; pub mod store; pub use delegatable::Delegatable; diff --git a/src/delegation/error.rs b/src/delegation/error.rs new file mode 100644 index 00000000..1ff31a12 --- /dev/null +++ b/src/delegation/error.rs @@ -0,0 +1,33 @@ +pub enum EnvelopeError { + InvalidSubject, + MisalignedIssAud, + Expired, + NotYetValid, +} + +// FIXME Error, etc +pub enum DelegationError { + Envelope(EnvelopeError), + + FailedCondition, // FIXME add context? + + SemanticError(Semantic), // // FIXME these are duplicated in Outcome + // // + // /// An error in the command chain. + // CommandEscelation, + + // /// An error in the argument chain. + // ArgumentEscelation(ArgErr), + + // /// An error in the proof chain. + // InvalidProofChain(ChainErr), + + // /// An error in the parents. + // InvalidParents(ParentErr), +} + +impl From for DelegationError { + fn from(err: EnvelopeError) -> Self { + DelegationError::Envelope(err) + } +} diff --git a/src/delegation/payload.rs b/src/delegation/payload.rs index 921601d7..cbf00378 100644 --- a/src/delegation/payload.rs +++ b/src/delegation/payload.rs @@ -1,24 +1,23 @@ -use super::{condition::Condition, delegatable::Delegatable}; +use super::{ + condition::Condition, + delegatable::Delegatable, + error::{DelegationError, EnvelopeError}, +}; use crate::{ ability::{arguments, command::Command}, capsule::Capsule, did::Did, - invocation, - invocation::Resolvable, nonce::Nonce, proof::{ checkable::Checkable, - prove::{Outcome, Prove}, + prove::{Prove, Success}, same::CheckSame, }, time::Timestamp, }; use libipld_core::{ipld::Ipld, serde as ipld_serde}; use serde::{de::DeserializeOwned, Deserialize, Serialize, Serializer}; -use std::{ - collections::{BTreeMap, BTreeSet}, - fmt::Debug, -}; +use std::{collections::BTreeMap, fmt::Debug}; use web_time::SystemTime; #[derive(Debug, Clone, PartialEq)] @@ -28,8 +27,7 @@ pub struct Payload { pub audience: Did, pub ability_builder: T::Builder, - pub conditions: BTreeSet, // FIXME BTreeSet? - // pub conditions: Vec, // FIXME BTreeSet? + pub conditions: Vec, // FIXME BTreeSet? pub metadata: BTreeMap, pub nonce: Nonce, @@ -92,128 +90,112 @@ impl From> for Ipld { } // FIXME this likely should move to invocation -impl< - 'a, - T: Delegatable + Resolvable + Checkable + Clone + Into>, - C: Condition, - > Payload -{ - pub fn check( - invoked: &'a invocation::Payload, // FIXME promisory version +impl<'a, T: Delegatable, C: Condition> Payload { + pub fn check( + delegated: &'a Payload, // FIXME promisory version proofs: Vec>, now: SystemTime, - ) -> Result<(), ()> + ) -> Result<(), DelegationError<<::Hierarchy as Prove>::Error>> where - invocation::Payload: Clone, - U::Builder: Clone + Into, - T::Hierarchy: From> + Clone + Into>, arguments::Named: From, + Payload: Clone, + U: Delegatable + Clone, + U::Builder: Clone, + T::Builder: Checkable + CheckSame + Clone, + ::Hierarchy: CheckSame + + From + + Clone + + Into> + + From<::Builder>, { + let builder = &delegated.ability_builder; + let hierarchy = ::Hierarchy::from(builder.clone()); + // FIXME this is a task - let start: Acc<'_, T> = Acc { - issuer: &invoked.issuer, - subject: &invoked.subject, - ability: invoked.clone().into(), // FIXME surely we can eliminate this clone + let start: Acc = Acc { + issuer: delegated.issuer.clone(), + subject: delegated.subject.clone(), + hierarchy, }; - let args: arguments::Named = invoked.ability.clone().into(); - - let result = proofs.iter().fold(Ok(start), |acc, proof| { - if let Ok(prev) = acc { - match step(&prev, proof, &args, now) { - // FIXME add a `Outdcome::is_success` of into(result) method - Outcome::ArgumentEscelation(_) => Err(()), - Outcome::InvalidProofChain(_) => Err(()), - Outcome::InvalidParents(_) => Err(()), - Outcome::CommandEscelation => Err(()), - // NOTE this case! - Outcome::ProvenByAny => Ok(Acc { - issuer: &proof.issuer, - subject: &proof.subject, - ability: prev.ability, - }), - Outcome::Proven => Ok(Acc { - issuer: &proof.issuer, - subject: &proof.subject, - ability: proof.ability_builder.clone().into(), // FIXME double check - }), + let args: arguments::Named = delegated.ability_builder.clone().into(); + + proofs + .into_iter() + .fold(Ok(start), |prev, proof| { + if let Ok(prev_) = prev { + step(&prev_, &proof, &args, now).map(move |success| { + match success { + Success::ProvenByAny => Acc { + issuer: proof.issuer.clone(), + subject: proof.subject.clone(), + hierarchy: prev_.hierarchy, + }, + Success::Proven => Acc { + issuer: proof.issuer.clone(), + subject: proof.subject.clone(), + hierarchy: ::Hierarchy::from( + proof.ability_builder.clone(), + ), // FIXME double check + }, + } + }) + } else { + prev } - } else { - acc - } - }); - - // FIXME - match result { - Ok(_) => Ok(()), - Err(_) => Err(()), - } + }) + .map(|_| ()) } } #[derive(Debug, Clone)] -struct Acc<'a, T: Checkable> { - issuer: &'a Did, - subject: &'a Did, - ability: T::Hierarchy, +pub(crate) struct Acc { + issuer: Did, + subject: Did, + hierarchy: B::Hierarchy, } -// FIXME this should move to Delegatable -fn step<'a, T: Checkable, U: Delegatable, C: Condition>( - prev: &Acc<'a, T>, +// FIXME this should move to Delegatable? +pub(crate) fn step<'a, B: Checkable, U: Delegatable, C: Condition>( + prev: &Acc, proof: &Payload, args: &arguments::Named, now: SystemTime, -) -> Outcome<(), (), ()> -// FIXME ^^^^^^^^^^^^ Outcome types +) -> Result::Error>> where - U::Builder: Into + Clone, arguments::Named: From, - T::Hierarchy: Clone + Into>, + U::Builder: Into + Clone, + B::Hierarchy: Clone + Into>, { if let Err(_) = prev.issuer.check_same(&proof.audience) { - return Outcome::InvalidProofChain(()); + return Err(EnvelopeError::InvalidSubject.into()); } if let Err(_) = prev.subject.check_same(&proof.subject) { - return Outcome::InvalidProofChain(()); + return Err(EnvelopeError::MisalignedIssAud.into()); + } + + if SystemTime::from(proof.expiration.clone()) > now { + return Err(EnvelopeError::Expired.into()); } if let Some(nbf) = proof.not_before.clone() { if SystemTime::from(nbf) > now { - return Outcome::InvalidProofChain(()); + return Err(EnvelopeError::NotYetValid.into()); } } - if SystemTime::from(proof.expiration.clone()) > now { - return Outcome::InvalidProofChain(()); - } - // FIXME coudl be more efficient, but sets need Ord and we have floats - let cond_result = &proof.conditions.iter().try_fold((), |_acc, c| { + for c in proof.conditions.iter() { // FIXME revisit - if c.validate(&args) && c.validate(&prev.ability.clone().into()) { - Ok(()) - } else { - Err(()) + if !c.validate(&args) || !c.validate(&prev.hierarchy.clone().into()) { + return Err(DelegationError::FailedCondition); } - }); - - if let Err(_) = cond_result { - return Outcome::InvalidProofChain(()); } - // FIXME pretty sure we can avoid this clone - let outcome = prev.ability.check(&proof.ability_builder.clone().into()); - - match outcome { - Outcome::Proven => Outcome::Proven, - Outcome::ProvenByAny => Outcome::ProvenByAny, - Outcome::CommandEscelation => Outcome::CommandEscelation, - Outcome::ArgumentEscelation(_) => Outcome::ArgumentEscelation(()), - Outcome::InvalidProofChain(_) => Outcome::InvalidProofChain(()), - Outcome::InvalidParents(_) => Outcome::InvalidParents(()), - } + prev.hierarchy + .check(&proof.ability_builder.clone().into()) + .map_err(DelegationError::SemanticError) } #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] diff --git a/src/proof/checkable.rs b/src/proof/checkable.rs index 87b08f4f..ee855eae 100644 --- a/src/proof/checkable.rs +++ b/src/proof/checkable.rs @@ -1,8 +1,8 @@ //! Define the hierarchy of an ability (or mark as not having one) -use super::{internal::Checker, prove::Prove, same::CheckSame}; +use super::{prove::Prove, same::CheckSame}; -// FIXME move to Delegatbel? +// FIXME mo ve to Delegatbel? /// Plug a type into the delegation checking pipeline pub trait Checkable: CheckSame { @@ -11,5 +11,5 @@ pub trait Checkable: CheckSame { /// The only options are [`Parentful`][super::parentful::Parentful] /// and [`Parentless`][super::parentless::Parentless], /// (which are the only instances of the unexported `Checker`) - type Hierarchy: Checker + CheckSame + Prove; + type Hierarchy: CheckSame + Prove; } diff --git a/src/proof/parentful.rs b/src/proof/parentful.rs index 340b45f4..54b29938 100644 --- a/src/proof/parentful.rs +++ b/src/proof/parentful.rs @@ -3,7 +3,7 @@ use super::{ internal::Checker, parents::CheckParents, - prove::{Outcome, Prove}, + prove::{Prove, Success}, same::CheckSame, }; use libipld_core::{error::SerdeError, ipld::Ipld, serde as ipld_serde}; @@ -129,34 +129,32 @@ where impl Checker for Parentful {} -impl Prove> for Parentful +impl Prove for Parentful where T::Parents: CheckSame, { - type ArgumentError = T::Error; - type ProofChainError = T::ParentError; - type ParentsError = ::Error; // FIXME better name + type Error = ParentfulError::Error>; - fn check(&self, proof: &Parentful) -> Outcome { + fn check(&self, proof: &Parentful) -> Result { match proof { - Parentful::Any => Outcome::ProvenByAny, + Parentful::Any => Ok(Success::ProvenByAny), Parentful::Parents(their_parents) => match self { - Parentful::Any => Outcome::CommandEscelation, + Parentful::Any => Err(ParentfulError::CommandEscelation), Parentful::Parents(parents) => match parents.check_same(their_parents) { - Ok(()) => Outcome::Proven, - Err(e) => Outcome::InvalidParents(e), + Ok(()) => Ok(Success::Proven), + Err(e) => Err(ParentfulError::InvalidParents(e)), }, Parentful::This(this) => match this.check_parent(their_parents) { - Ok(()) => Outcome::Proven, - Err(e) => Outcome::InvalidProofChain(e), + Ok(()) => Ok(Success::Proven), + Err(e) => Err(ParentfulError::InvalidProofChain(e)), }, }, Parentful::This(that) => match self { - Parentful::Any => Outcome::CommandEscelation, - Parentful::Parents(_) => Outcome::CommandEscelation, + Parentful::Any => Err(ParentfulError::CommandEscelation), + Parentful::Parents(_) => Err(ParentfulError::CommandEscelation), Parentful::This(this) => match this.check_same(that) { - Ok(()) => Outcome::Proven, - Err(e) => Outcome::ArgumentEscelation(e), + Ok(()) => Ok(Success::Proven), + Err(e) => Err(ParentfulError::ArgumentEscelation(e)), }, }, } diff --git a/src/proof/parentless.rs b/src/proof/parentless.rs index 390c99ac..4e3659fa 100644 --- a/src/proof/parentless.rs +++ b/src/proof/parentless.rs @@ -3,12 +3,11 @@ use super::{ checkable::Checkable, internal::Checker, - prove::{Outcome, Prove}, + prove::{Prove, Success}, same::CheckSame, }; use libipld_core::{error::SerdeError, ipld::Ipld, serde as ipld_serde}; use serde::{de::DeserializeOwned, Deserialize, Serialize}; -use std::convert::Infallible; /// The possible cases for an [ability][crate::ability]'s /// [Delegation][crate::delegation::Delegation] chain when @@ -68,8 +67,8 @@ impl + DeserializeOwned> TryFrom for Parentless { impl CheckSame for Parentless { type Error = ParentlessError; - fn check_same(&self, other: &Self) -> Result<(), Self::Error> { - match other { + fn check_same(&self, proof: &Self) -> Result<(), Self::Error> { + match proof { Parentless::Any => Ok(()), Parentless::This(that) => match self { Parentless::Any => Err(ParentlessError::CommandEscelation), @@ -83,19 +82,17 @@ impl CheckSame for Parentless { impl Checker for Parentless {} -impl Prove> for Parentless { - type ArgumentError = T::Error; - type ProofChainError = Infallible; - type ParentsError = Infallible; +impl Prove for Parentless { + type Error = ParentlessError; - fn check(&self, proof: &Parentless) -> Outcome { + fn check(&self, proof: &Parentless) -> Result { match proof { - Parentless::Any => Outcome::Proven, + Parentless::Any => Ok(Success::ProvenByAny), Parentless::This(that) => match self { - Parentless::Any => Outcome::Proven, + Parentless::Any => Ok(Success::Proven), Parentless::This(this) => match this.check_same(that) { - Ok(()) => Outcome::Proven, - Err(e) => Outcome::ArgumentEscelation(e), + Ok(()) => Ok(Success::Proven), + Err(e) => Err(ParentlessError::ArgumentEscelation(e)), }, }, } diff --git a/src/proof/prove.rs b/src/proof/prove.rs index 34e32de0..fbf4d54d 100644 --- a/src/proof/prove.rs +++ b/src/proof/prove.rs @@ -3,31 +3,21 @@ use super::internal::Checker; /// An internal trait that checks based on the other traits for an ability type. -pub trait Prove { - /// The error if the argument is invalid. - type ArgumentError; +pub trait Prove: Checker { + type Error; - /// The error if the proof chain is invalid. - type ProofChainError; - - /// The error if the parents are invalid. - type ParentsError; - - fn check( - &self, - proof: &T, - ) -> Outcome; + fn check(&self, proof: &Self) -> Result; } -// FIXME that's a lot of error type params -/// The outcome of a proof check. -pub enum Outcome { +pub enum Success { /// Success Proven, /// Special case for success by checking against `*`. ProvenByAny, +} +pub enum Failure { /// An error in the command chain. CommandEscelation, From ae01353d61e0614dc92ec64799315100a4d4e2b7 Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Fri, 9 Feb 2024 23:51:21 -0800 Subject: [PATCH 065/188] Much simplified delegationpayload --- src/delegation/payload.rs | 87 +++++++++++++++------------------------ 1 file changed, 33 insertions(+), 54 deletions(-) diff --git a/src/delegation/payload.rs b/src/delegation/payload.rs index cbf00378..91bccd4d 100644 --- a/src/delegation/payload.rs +++ b/src/delegation/payload.rs @@ -1,6 +1,5 @@ use super::{ condition::Condition, - delegatable::Delegatable, error::{DelegationError, EnvelopeError}, }; use crate::{ @@ -21,12 +20,12 @@ use std::{collections::BTreeMap, fmt::Debug}; use web_time::SystemTime; #[derive(Debug, Clone, PartialEq)] -pub struct Payload { +pub struct Payload { pub issuer: Did, pub subject: Did, pub audience: Did, - pub ability_builder: T::Builder, + pub delegated_ability: D, pub conditions: Vec, // FIXME BTreeSet? pub metadata: BTreeMap, pub nonce: Nonce, @@ -35,25 +34,25 @@ pub struct Payload { pub not_before: Option, } -impl Capsule for Payload { +impl Capsule for Payload { const TAG: &'static str = "ucan/d/1.0.0-rc.1"; } -impl Serialize for Payload +impl Serialize for Payload where - InternalSerializer: From>, - Payload: Clone, + InternalSerializer: From>, + Payload: Clone, { fn serialize(&self, serializer: S) -> Result where S: Serializer, { - let s = InternalSerializer::from(self.clone()); + let s = InternalSerializer::from(self.clone()); // FIXME Serialize::serialize(&s, serializer) } } -impl<'de, T: Delegatable, C: Condition + DeserializeOwned> Deserialize<'de> for Payload +impl<'de, T, C: Condition + DeserializeOwned> Deserialize<'de> for Payload where Payload: TryFrom, as TryFrom>::Error: Debug, @@ -71,7 +70,7 @@ where } } -impl TryFrom for Payload +impl TryFrom for Payload where Payload: TryFrom, { @@ -83,42 +82,29 @@ where } } -impl From> for Ipld { +impl From> for Ipld { fn from(payload: Payload) -> Self { payload.into() } } // FIXME this likely should move to invocation -impl<'a, T: Delegatable, C: Condition> Payload { - pub fn check( +impl<'a, T: Checkable + CheckSame + Clone + Prove + Into>, C: Condition> + Payload +{ + pub fn check( delegated: &'a Payload, // FIXME promisory version - proofs: Vec>, + proofs: Vec>, now: SystemTime, - ) -> Result<(), DelegationError<<::Hierarchy as Prove>::Error>> - where - arguments::Named: From, - Payload: Clone, - U: Delegatable + Clone, - U::Builder: Clone, - T::Builder: Checkable + CheckSame + Clone, - ::Hierarchy: CheckSame - + From - + Clone - + Into> - + From<::Builder>, - { - let builder = &delegated.ability_builder; - let hierarchy = ::Hierarchy::from(builder.clone()); - - // FIXME this is a task - let start: Acc = Acc { + ) -> Result<(), DelegationError<::Error>> +where { + let start: Acc = Acc { issuer: delegated.issuer.clone(), subject: delegated.subject.clone(), - hierarchy, + hierarchy: delegated.delegated_ability.clone(), }; - let args: arguments::Named = delegated.ability_builder.clone().into(); + let args: arguments::Named = delegated.delegated_ability.clone().into(); proofs .into_iter() @@ -134,9 +120,7 @@ impl<'a, T: Delegatable, C: Condition> Payload { Success::Proven => Acc { issuer: proof.issuer.clone(), subject: proof.subject.clone(), - hierarchy: ::Hierarchy::from( - proof.ability_builder.clone(), - ), // FIXME double check + hierarchy: proof.delegated_ability.clone(), // FIXME double check }, } }) @@ -149,24 +133,19 @@ impl<'a, T: Delegatable, C: Condition> Payload { } #[derive(Debug, Clone)] -pub(crate) struct Acc { +pub(crate) struct Acc { issuer: Did, subject: Did, - hierarchy: B::Hierarchy, + hierarchy: T, } // FIXME this should move to Delegatable? -pub(crate) fn step<'a, B: Checkable, U: Delegatable, C: Condition>( - prev: &Acc, - proof: &Payload, +pub(crate) fn step<'a, T: Prove + Clone + Into>, C: Condition>( + prev: &Acc, + proof: &Payload, args: &arguments::Named, now: SystemTime, -) -> Result::Error>> -where - arguments::Named: From, - U::Builder: Into + Clone, - B::Hierarchy: Clone + Into>, -{ +) -> Result::Error>> { if let Err(_) = prev.issuer.check_same(&proof.audience) { return Err(EnvelopeError::InvalidSubject.into()); } @@ -194,7 +173,7 @@ where } prev.hierarchy - .check(&proof.ability_builder.clone().into()) + .check(&proof.delegated_ability.clone()) .map_err(DelegationError::SemanticError) } @@ -226,18 +205,18 @@ struct InternalSerializer { expiration: Timestamp, } -impl> From> for InternalSerializer +impl> From> for InternalSerializer where - BTreeMap: From, + arguments::Named: From, { - fn from(payload: Payload) -> Self { + fn from(payload: Payload) -> Self { InternalSerializer { issuer: payload.issuer, subject: payload.subject, audience: payload.audience, - command: T::COMMAND.into(), - arguments: payload.ability_builder.into(), + command: B::COMMAND.into(), + arguments: payload.delegated_ability.into(), conditions: payload.conditions.into_iter().map(|c| c.into()).collect(), metadata: payload.metadata, From a51202fdb83f390166450bf2862f8080e50785fc Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Sat, 10 Feb 2024 00:40:55 -0800 Subject: [PATCH 066/188] Much simplification --- src/delegation/payload.rs | 183 ++++++++++++++++---------------------- 1 file changed, 77 insertions(+), 106 deletions(-) diff --git a/src/delegation/payload.rs b/src/delegation/payload.rs index 91bccd4d..d0cd8304 100644 --- a/src/delegation/payload.rs +++ b/src/delegation/payload.rs @@ -3,7 +3,10 @@ use super::{ error::{DelegationError, EnvelopeError}, }; use crate::{ - ability::{arguments, command::Command}, + ability::{ + arguments, + command::{Command, ToCommand}, + }, capsule::Capsule, did::Did, nonce::Nonce, @@ -14,8 +17,8 @@ use crate::{ }, time::Timestamp, }; -use libipld_core::{ipld::Ipld, serde as ipld_serde}; -use serde::{de::DeserializeOwned, Deserialize, Serialize, Serializer}; +use libipld_core::{error::SerdeError, ipld::Ipld, serde as ipld_serde}; +use serde::{de::DeserializeOwned, ser::SerializeStruct, Deserialize, Serialize, Serializer}; use std::{collections::BTreeMap, fmt::Debug}; use web_time::SystemTime; @@ -25,6 +28,9 @@ pub struct Payload { pub subject: Did, pub audience: Did, + /// A delegatable ability chain. + /// + /// Note that this should be is some [Proof::Hierarchy] // FIXME enforce in types? pub delegated_ability: D, pub conditions: Vec, // FIXME BTreeSet? pub metadata: BTreeMap, @@ -38,46 +44,66 @@ impl Capsule for Payload { const TAG: &'static str = "ucan/d/1.0.0-rc.1"; } -impl Serialize for Payload +impl Serialize for Payload where - InternalSerializer: From>, - Payload: Clone, + Ipld: From, + arguments::Named: From, { fn serialize(&self, serializer: S) -> Result where S: Serializer, { - let s = InternalSerializer::from(self.clone()); // FIXME - Serialize::serialize(&s, serializer) + let mut state = serializer.serialize_struct("delegation::Payload", 9)?; + state.serialize_field("iss", &self.issuer)?; + state.serialize_field("sub", &self.subject)?; + state.serialize_field("aud", &self.audience)?; + state.serialize_field("cmd", &self.delegated_ability.to_command())?; + state.serialize_field( + "args", + &arguments::Named::from(self.delegated_ability.clone()), + )?; + + state.serialize_field( + "cond", + &self + .conditions + .iter() + .map(|c| Ipld::from(c.clone())) + .collect::>(), + )?; + + state.serialize_field("meta", &self.metadata)?; + state.serialize_field("nonce", &self.nonce)?; + state.serialize_field("exp", &self.expiration)?; + state.serialize_field("nbf", &self.not_before)?; + state.end() } } impl<'de, T, C: Condition + DeserializeOwned> Deserialize<'de> for Payload where - Payload: TryFrom, - as TryFrom>::Error: Debug, + Payload: TryFrom, + as TryFrom>::Error: Debug, { fn deserialize(d: D) -> Result where D: serde::Deserializer<'de>, { - match InternalSerializer::deserialize(d) { - Err(e) => Err(e), - Ok(s) => s - .try_into() - .map_err(|e| serde::de::Error::custom(format!("{:?}", e))), // FIXME better error - } + InternalDeserializer::deserialize(d).and_then(|s| { + s.try_into() + .map_err(|e| serde::de::Error::custom(format!("{:?}", e))) // FIXME better error + }) } } impl TryFrom for Payload where - Payload: TryFrom, + Payload: TryFrom, { type Error = (); // FIXME fn try_from(ipld: Ipld) -> Result { - let s: InternalSerializer = ipld_serde::from_ipld(ipld).map_err(|_| ())?; + let s: InternalDeserializer = ipld_serde::from_ipld(ipld).map_err(|_| ())?; // FIXME s.try_into().map_err(|_| ()) // FIXME } } @@ -88,16 +114,15 @@ impl From> for Ipld { } } -// FIXME this likely should move to invocation -impl<'a, T: Checkable + CheckSame + Clone + Prove + Into>, C: Condition> - Payload -{ +impl<'a, T, C: Condition> Payload { pub fn check( delegated: &'a Payload, // FIXME promisory version proofs: Vec>, now: SystemTime, ) -> Result<(), DelegationError<::Error>> -where { + where + T: Checkable + CheckSame + Clone + Prove + Into>, + { let start: Acc = Acc { issuer: delegated.issuer.clone(), subject: delegated.subject.clone(), @@ -133,14 +158,14 @@ where { } #[derive(Debug, Clone)] -pub(crate) struct Acc { +struct Acc { issuer: Did, subject: Did, hierarchy: T, } // FIXME this should move to Delegatable? -pub(crate) fn step<'a, T: Prove + Clone + Into>, C: Condition>( +fn step<'a, T: Prove + Clone + Into>, C: Condition>( prev: &Acc, proof: &Payload, args: &arguments::Named, @@ -177,9 +202,9 @@ pub(crate) fn step<'a, T: Prove + Clone + Into>, C: Condi .map_err(DelegationError::SemanticError) } -#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +#[derive(Debug, Clone, PartialEq, Deserialize)] #[serde(deny_unknown_fields)] -struct InternalSerializer { +struct InternalDeserializer { #[serde(rename = "iss")] issuer: Did, #[serde(rename = "sub")] @@ -205,93 +230,39 @@ struct InternalSerializer { expiration: Timestamp, } -impl> From> for InternalSerializer -where - arguments::Named: From, +impl>, C: Condition + From> + TryFrom for Payload { - fn from(payload: Payload) -> Self { - InternalSerializer { - issuer: payload.issuer, - subject: payload.subject, - audience: payload.audience, + type Error = (); // FIXME + + fn try_from(d: InternalDeserializer) -> Result { + let p: Self = Payload { + issuer: d.issuer, + subject: d.subject, + audience: d.audience, - command: B::COMMAND.into(), - arguments: payload.delegated_ability.into(), - conditions: payload.conditions.into_iter().map(|c| c.into()).collect(), + delegated_ability: d.arguments.try_into().map_err(|_| ())?, // d.command.into(), + conditions: d.conditions.into_iter().map(|c| c.into()).collect(), - metadata: payload.metadata, - nonce: payload.nonce, + metadata: d.metadata, + nonce: d.nonce, + + not_before: d.not_before, + expiration: d.expiration, + }; - not_before: payload.not_before, - expiration: payload.expiration, + if p.delegated_ability.to_command() != d.command { + return Err(()); } + + Ok(p) } } -impl TryFrom for InternalSerializer { - type Error = (); // FIXME +impl TryFrom for InternalDeserializer { + type Error = SerdeError; - fn try_from(ipld: Ipld) -> Result { - ipld_serde::from_ipld(ipld).map_err(|_| ()) + fn try_from(ipld: Ipld) -> Result { + ipld_serde::from_ipld(ipld) } } - -// FIXME -// impl, E: meta::MultiKeyed + Clone> TryFrom -// for Payload, C, E> -// { -// type Error = (); // FIXME -// -// fn try_from(s: InternalSerializer) -> Result, C, E>, ()> { -// Ok(Payload { -// issuer: s.issuer, -// subject: s.subject, -// audience: s.audience, -// -// ability_builder: dynamic::Dynamic { -// cmd: s.command, -// args: s.arguments, -// }, -// conditions: s -// .conditions -// .iter() -// .try_fold(Vec::new(), |mut acc, c| { -// C::try_from(c.clone()).map(|x| { -// acc.push(x); -// acc -// }) -// }) -// .map_err(|_| ())?, // FIXME better error (collect all errors -// -// metadata: Metadata::extract(s.metadata), -// nonce: s.nonce, -// -// not_before: s.not_before, -// expiration: s.expiration, -// }) -// } -// } -// -// impl, E: meta::MultiKeyed + Clone, F> -// From, C, E>> for InternalSerializer -// where -// Metadata: Mergable, -// { -// fn from(p: Payload, C, E>) -> Self { -// InternalSerializer { -// issuer: p.issuer, -// subject: p.subject, -// audience: p.audience, -// -// command: p.ability_builder.cmd, -// arguments: p.ability_builder.args, -// conditions: p.conditions.into_iter().map(|c| c.into()).collect(), -// -// metadata: p.metadata.merge(), -// nonce: p.nonce, -// -// not_before: p.not_before, -// expiration: p.expiration, -// } -// } -// } From 3097b3d3299f1789e3e1ebfa601feaa02a0aabef Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Sat, 10 Feb 2024 14:54:51 -0800 Subject: [PATCH 067/188] Needs renaming, but custom serializer feels much better --- src/ability/arguments/named.rs | 23 ++++- src/ability/command.rs | 38 ++++++++ src/ability/crud/any.rs | 2 + src/ability/crud/js.rs | 8 +- src/ability/crud/parents.rs | 49 +++++++++- src/ability/crud/update.rs | 2 +- src/delegation/payload.rs | 166 ++++++++++++++++++++++++++++----- src/invocation/payload.rs | 6 +- 8 files changed, 260 insertions(+), 34 deletions(-) diff --git a/src/ability/arguments/named.rs b/src/ability/arguments/named.rs index 710d91e5..1cf45018 100644 --- a/src/ability/arguments/named.rs +++ b/src/ability/arguments/named.rs @@ -118,11 +118,28 @@ impl FromIterator<(String, T)> for Named { } } -impl Deserialize<'de>> TryFrom for Named { - type Error = SerdeError; +// FIXME move to common ipld module? +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Error)] +pub enum FromIpldError { + NotAMap, + LeafError(#[from] E), +} + +impl> TryFrom for Named { + type Error = FromIpldError<>::Error>; fn try_from(ipld: Ipld) -> Result { - ipld_serde::from_ipld(ipld) + match ipld { + Ipld::Map(map) => Ok(map + .into_iter() + .try_fold(Named::new(), |mut named, (k, v)| { + let value = T::try_from(v).map_err(FromIpldError::LeafError)?; + named.insert(k, value); + Ok::, Self::Error>(named) + })?), + + _ => Err(FromIpldError::NotAMap), + } } } diff --git a/src/ability/command.rs b/src/ability/command.rs index 756562cb..ec220822 100644 --- a/src/ability/command.rs +++ b/src/ability/command.rs @@ -16,6 +16,11 @@ //! } //! ``` +use crate::ability::arguments; +use libipld_core::ipld::Ipld; +use std::fmt; +use thiserror::Error; + /// Attach a `cmd` field to a type /// /// Commands are the `cmd` field of a UCAN, and set the shape of the `args` field. @@ -40,6 +45,7 @@ pub trait Command { /// The value that will be placed in the UCAN's `cmd` field for the given type /// + /// FIXME /// This is a `const` because it *must not*[^dynamic] depend on the runtime values of a type /// in order to ensure type safety. /// @@ -49,6 +55,38 @@ pub trait Command { const COMMAND: &'static str; } +pub trait ParseAbility: Sized { + type Error: fmt::Display; + // FIXME rename this trait to Ability? + fn try_parse(cmd: &str, args: &arguments::Named) -> Result; +} + +#[derive(Debug, Clone, Error)] +pub enum ParseAbilityError { + #[error("Unknown command")] + UnknownCommand, + + #[error(transparent)] + InvalidArgs(#[from] E), +} + +impl> ParseAbility for T +where + >::Error: fmt::Display, +{ + type Error = ParseAbilityError<>::Error>; + + fn try_parse(cmd: &str, args: &arguments::Named) -> Result { + if cmd != T::COMMAND { + return Err(ParseAbilityError::UnknownCommand); + } + + Ipld::from(args.clone()) + .try_into() + .map_err(ParseAbilityError::InvalidArgs) + } +} + // NOTE do not export; this is used to limit the Hierarchy // interface to [Parentful] and [Parentless] while enabling [Dynamic] pub(crate) trait ToCommand { diff --git a/src/ability/crud/any.rs b/src/ability/crud/any.rs index 077d1d70..d28d867a 100644 --- a/src/ability/crud/any.rs +++ b/src/ability/crud/any.rs @@ -60,6 +60,8 @@ pub struct Any { pub path: Option, } +use crate::ability::command::ParseAbility; + impl Command for Any { const COMMAND: &'static str = "crud/*"; } diff --git a/src/ability/crud/js.rs b/src/ability/crud/js.rs index 1153b7b0..f7978aa9 100644 --- a/src/ability/crud/js.rs +++ b/src/ability/crud/js.rs @@ -18,8 +18,8 @@ impl CrudAny { ipld::Newtype::try_from_js(js).map(CrudAny) } - pub fn command(&self) -> String { - Any::COMMAND.to_string() + pub fn to_command(&self) -> String { + self.to_command() } pub fn check_same(&self, proof: &CrudAny) -> Result<(), JsError> { @@ -50,8 +50,8 @@ impl CrudRead { ipld::Newtype::try_into_jsvalue(js_val).map(CrudRead) } - pub fn command(&self) -> String { - Read::COMMAND.to_string() + pub fn to_command(&self) -> String { + Read::to_command() } pub fn check_same(&self, proof: &CrudRead) -> Result<(), JsError> { diff --git a/src/ability/crud/parents.rs b/src/ability/crud/parents.rs index cad0494d..884b64fc 100644 --- a/src/ability/crud/parents.rs +++ b/src/ability/crud/parents.rs @@ -5,7 +5,10 @@ //! ability, or the invocable leaves of a delegation hierarchy. use super::error::ParentError; -use crate::proof::{parents::CheckParents, same::CheckSame}; +use crate::{ + ability::command::Command, + proof::{parents::CheckParents, same::CheckSame}, +}; use serde::{Deserialize, Serialize}; use thiserror::Error; @@ -51,7 +54,7 @@ use thiserror::Error; /// style mutate stroke:orange; /// ``` #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] -#[serde(deny_unknown_fields)] +#[serde(deny_unknown_fields, untagged)] pub enum MutableParents { /// The `crud/*` ability. Any(super::Any), @@ -60,6 +63,48 @@ pub enum MutableParents { Mutate(super::Mutate), } +// FIXME +use crate::ability::command::{ParseAbility, ToCommand}; + +impl ToCommand for MutableParents { + fn to_command(&self) -> String { + match self { + MutableParents::Any(any) => any.to_command(), + MutableParents::Mutate(mutate) => mutate.to_command(), + } + } +} + +use crate::ability::{arguments, command::ParseAbilityError}; +use libipld_core::ipld::Ipld; + +#[derive(Debug, Clone, Error)] +pub enum ParseError { + #[error("Invalid `crud/*` arguments: {0}")] + InvalidAnyArgs(#[source] ::Error), + + #[error("Invalid `crud/mutate` arguments: {0}")] + InvalidMutateArgs(#[source] ::Error), +} + +impl ParseAbility for MutableParents { + type Error = ParseAbilityError; + + fn try_parse(cmd: &str, args: &arguments::Named) -> Result { + super::Any::try_parse(cmd, args) + .map(MutableParents::Any) + .map_err(ParseError::InvalidAnyArgs) + .map_err(ParseAbilityError::InvalidArgs)?; + + super::Mutate::try_parse(cmd, args) + .map(MutableParents::Mutate) + .map_err(ParseError::InvalidMutateArgs) + .map_err(ParseAbilityError::InvalidArgs)?; + + Err(ParseAbilityError::UnknownCommand) + } +} + impl CheckSame for MutableParents { type Error = ParentError; diff --git a/src/ability/crud/update.rs b/src/ability/crud/update.rs index 44e29305..ff3d153c 100644 --- a/src/ability/crud/update.rs +++ b/src/ability/crud/update.rs @@ -129,7 +129,7 @@ pub type Builder = Generic>; pub type Promised = Generic, arguments::Promised>; impl Command for Generic { - const COMMAND: &'static str = "crud/create"; + const COMMAND: &'static str = "crud/update"; } impl, A: Into> From> for Ipld { diff --git a/src/delegation/payload.rs b/src/delegation/payload.rs index d0cd8304..8270d96e 100644 --- a/src/delegation/payload.rs +++ b/src/delegation/payload.rs @@ -18,8 +18,12 @@ use crate::{ time::Timestamp, }; use libipld_core::{error::SerdeError, ipld::Ipld, serde as ipld_serde}; -use serde::{de::DeserializeOwned, ser::SerializeStruct, Deserialize, Serialize, Serializer}; -use std::{collections::BTreeMap, fmt::Debug}; +use serde::{ + de::{self, MapAccess, Visitor}, + ser::SerializeStruct, + Deserialize, Serialize, Serializer, +}; +use std::{collections::BTreeMap, fmt, fmt::Debug}; use web_time::SystemTime; #[derive(Debug, Clone, PartialEq)] @@ -32,6 +36,7 @@ pub struct Payload { /// /// Note that this should be is some [Proof::Hierarchy] // FIXME enforce in types? pub delegated_ability: D, + pub conditions: Vec, // FIXME BTreeSet? pub metadata: BTreeMap, pub nonce: Nonce, @@ -80,31 +85,150 @@ where } } -impl<'de, T, C: Condition + DeserializeOwned> Deserialize<'de> for Payload -where - Payload: TryFrom, - as TryFrom>::Error: Debug, +// FIXME +use crate::ability::command::ParseAbility; + +impl< + 'de, + T: ParseAbility + Deserialize<'de> + ToCommand, + C: Condition + TryFrom + Deserialize<'de>, + > Deserialize<'de> for Payload { - fn deserialize(d: D) -> Result + fn deserialize(deserializer: D) -> Result where D: serde::Deserializer<'de>, { - InternalDeserializer::deserialize(d).and_then(|s| { - s.try_into() - .map_err(|e| serde::de::Error::custom(format!("{:?}", e))) // FIXME better error - }) - } -} + struct DelegationPayloadVisitor(std::marker::PhantomData<(T, C)>); + + const FIELDS: &'static [&'static str] = &[ + "iss", "sub", "aud", "cmd", "args", "cond", "meta", "nonce", "exp", "nbf", + ]; + + impl< + 'de, + T: Deserialize<'de> + ParseAbility + ToCommand, + C: Condition + TryFrom + Deserialize<'de>, + > Visitor<'de> for DelegationPayloadVisitor + { + type Value = Payload; + + fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { + formatter.write_str("struct delegation::Payload") + } + + fn visit_map(self, mut map: M) -> Result + where + M: MapAccess<'de>, + { + let mut issuer = None; + let mut subject = None; + let mut audience = None; + let mut command = None; + let mut arguments = None; + let mut conditions = None; + let mut metadata = None; + let mut nonce = None; + let mut expiration = None; + let mut not_before = None; + + while let Some(key) = map.next_key()? { + match key { + "iss" => { + if issuer.is_some() { + return Err(de::Error::duplicate_field("iss")); + } + issuer = Some(map.next_value()?); + } + "sub" => { + if subject.is_some() { + return Err(de::Error::duplicate_field("sub")); + } + subject = Some(map.next_value()?); + } + "aud" => { + if audience.is_some() { + return Err(de::Error::duplicate_field("aud")); + } + audience = Some(map.next_value()?); + } + "cmd" => { + if command.is_some() { + return Err(de::Error::duplicate_field("cmd")); + } + command = Some(map.next_value()?); + } + "args" => { + if arguments.is_some() { + return Err(de::Error::duplicate_field("args")); + } + arguments = Some(map.next_value()?); + } + "cond" => { + if conditions.is_some() { + return Err(de::Error::duplicate_field("cond")); + } + conditions = Some(map.next_value()?); + } + "meta" => { + if metadata.is_some() { + return Err(de::Error::duplicate_field("meta")); + } + metadata = Some(map.next_value()?); + } + "nonce" => { + if nonce.is_some() { + return Err(de::Error::duplicate_field("nonce")); + } + nonce = Some(map.next_value()?); + } + "exp" => { + if expiration.is_some() { + return Err(de::Error::duplicate_field("exp")); + } + expiration = Some(map.next_value()?); + } + "nbf" => { + if not_before.is_some() { + return Err(de::Error::duplicate_field("nbf")); + } + not_before = Some(map.next_value()?); + } + other => { + return Err(de::Error::unknown_field(other, FIELDS)); + } + } + } -impl TryFrom for Payload -where - Payload: TryFrom, -{ - type Error = (); // FIXME + let cmd: String = command.ok_or(de::Error::missing_field("cmd"))?; + let args = arguments.ok_or(de::Error::missing_field("args"))?; + + let delegated_ability = ::try_parse(cmd.as_str(), &args) + .map_err(|e| { + de::Error::custom(format!( + "Unable to parse ability field for {} because {}", + cmd, e + )) + })?; + + Ok(Payload { + issuer: issuer.ok_or(de::Error::missing_field("iss"))?, + subject: subject.ok_or(de::Error::missing_field("sub"))?, + audience: audience.ok_or(de::Error::missing_field("aud"))?, + delegated_ability, + conditions: conditions.ok_or(de::Error::missing_field("cond"))?, + metadata: metadata.ok_or(de::Error::missing_field("meta"))?, + nonce: nonce.ok_or(de::Error::missing_field("nonce"))?, + expiration: expiration.ok_or(de::Error::missing_field("exp"))?, + not_before, + }) + } + } - fn try_from(ipld: Ipld) -> Result { - let s: InternalDeserializer = ipld_serde::from_ipld(ipld).map_err(|_| ())?; // FIXME - s.try_into().map_err(|_| ()) // FIXME + deserializer.deserialize_struct( + "DelegationPayload", + FIELDS, + DelegationPayloadVisitor(Default::default()), + ) } } diff --git a/src/invocation/payload.rs b/src/invocation/payload.rs index b7fcdd48..39c89c3f 100644 --- a/src/invocation/payload.rs +++ b/src/invocation/payload.rs @@ -1,6 +1,6 @@ use super::resolvable::Resolvable; use crate::{ - ability::{arguments, command::Command}, + ability::{arguments, command::ToCommand}, capsule::Capsule, did::Did, nonce::Nonce, @@ -196,14 +196,14 @@ impl TryFrom for InternalSerializer { // } // } -impl>> From> for InternalSerializer { +impl>> From> for InternalSerializer { fn from(payload: Payload) -> Self { InternalSerializer { issuer: payload.issuer, subject: payload.subject, audience: payload.audience, - command: T::COMMAND.into(), + command: payload.ability.to_command(), arguments: payload.ability.into(), proofs: payload.proofs, From 3588c293059f9453bdfc9323032aa41da84db5b0 Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Sun, 11 Feb 2024 00:38:32 -0800 Subject: [PATCH 068/188] Lost of docs, wired invocation into the delegation checker --- Cargo.toml | 1 + src/ability.rs | 1 - src/ability/command.rs | 7 +- src/ability/crud/parents.rs | 5 +- src/ability/dynamic.rs | 24 ++- src/capsule.rs | 2 +- src/delegation/payload.rs | 309 +++++++++++++++++++----------------- src/delegation/store.rs | 217 +++++++++++++++++-------- src/did.rs | 6 +- src/invocation.rs | 9 +- src/invocation/payload.rs | 140 +++++++++++++--- src/ipld.rs | 2 +- src/lib.rs | 4 + src/nonce.rs | 4 +- src/number.rs | 2 +- src/proof.rs | 2 +- src/proof/checkable.rs | 4 +- src/proof/parentful.rs | 9 ++ src/proof/parentless.rs | 14 +- src/reader.rs | 28 ++-- src/receipt.rs | 9 +- src/receipt/payload.rs | 254 +++++++++++++++++++---------- src/receipt/receipt.rs | 4 - src/receipt/responds.rs | 11 ++ src/receipt/store.rs | 4 + src/signature.rs | 6 + src/task.rs | 2 +- src/time.rs | 21 ++- src/url.rs | 2 +- 29 files changed, 737 insertions(+), 366 deletions(-) delete mode 100644 src/receipt/receipt.rs diff --git a/Cargo.toml b/Cargo.toml index 4f2a9d83..4c558ec0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -53,6 +53,7 @@ libipld-core = { version = "0.16", features = ["serde-codec"] } libipld-cbor = "0.16" multibase = "0.9" multihash = { version = "0.18", features = ["sha2"] } +nonempty = { version = "0.9" } p256 = { version = "0.13.2", features = ["ecdsa"], optional = true, default-features = false } p384 = { version = "0.13.0", features = ["ecdsa"], optional = true, default-features = false } p521 = { version = "0.13.0", optional = true, default-features = false } diff --git a/src/ability.rs b/src/ability.rs index 105a1bd6..f52c7966 100644 --- a/src/ability.rs +++ b/src/ability.rs @@ -48,7 +48,6 @@ pub mod command; pub mod js; // // TODO move to crate::wasm? or hide behind "dynamic" feature flag? -#[cfg(target_arch = "wasm32")] pub mod dynamic; // FIXME macro to derive promise versions & delagted builder versions diff --git a/src/ability/command.rs b/src/ability/command.rs index ec220822..30eca578 100644 --- a/src/ability/command.rs +++ b/src/ability/command.rs @@ -55,8 +55,10 @@ pub trait Command { const COMMAND: &'static str; } +// FIXME definitely needs a better name pub trait ParseAbility: Sized { type Error: fmt::Display; + // FIXME rename this trait to Ability? fn try_parse(cmd: &str, args: &arguments::Named) -> Result; } @@ -89,7 +91,10 @@ where // NOTE do not export; this is used to limit the Hierarchy // interface to [Parentful] and [Parentless] while enabling [Dynamic] -pub(crate) trait ToCommand { +// FIXME ^^^^ NOT ANYMORE +// Either that needs to be re-locked down, or (because it's all abstract anyways) +// just note that you probably don;t want this one. +pub trait ToCommand { fn to_command(&self) -> String; } diff --git a/src/ability/crud/parents.rs b/src/ability/crud/parents.rs index 884b64fc..6c2f106f 100644 --- a/src/ability/crud/parents.rs +++ b/src/ability/crud/parents.rs @@ -6,7 +6,7 @@ use super::error::ParentError; use crate::{ - ability::command::Command, + ability::command::{ParseAbility, ToCommand}, proof::{parents::CheckParents, same::CheckSame}, }; use serde::{Deserialize, Serialize}; @@ -63,9 +63,6 @@ pub enum MutableParents { Mutate(super::Mutate), } -// FIXME -use crate::ability::command::{ParseAbility, ToCommand}; - impl ToCommand for MutableParents { fn to_command(&self) -> String { match self { diff --git a/src/ability/dynamic.rs b/src/ability/dynamic.rs index e4584d13..b8f58f56 100644 --- a/src/ability/dynamic.rs +++ b/src/ability/dynamic.rs @@ -1,13 +1,20 @@ //! This module is for dynamic abilities, especially for FFI and Wasm support -use super::{arguments, command::ToCommand}; +use super::{ + arguments, + command::{ParseAbility, ToCommand}, +}; use crate::{ipld, proof::same::CheckSame}; -use js_sys; use libipld_core::{error::SerdeError, ipld::Ipld, serde as ipld_serde}; use serde::{Deserialize, Serialize}; -use std::{collections::BTreeMap, fmt::Debug}; +use std::{collections::BTreeMap, convert::Infallible, fmt::Debug}; + +#[cfg(target_arch = "wasm32")] use wasm_bindgen::prelude::*; +#[cfg(target_arch = "wasm32")] +use js_sys; + // NOTE the lack of checking functions! /// A "dynamic" ability with the bare minimum of statics @@ -35,6 +42,17 @@ pub struct Dynamic { pub args: arguments::Named, } +impl ParseAbility for Dynamic { + type Error = Infallible; + + fn try_parse(cmd: &str, args: &arguments::Named) -> Result { + Ok(Dynamic { + cmd: cmd.to_string(), + args: args.clone(), + }) + } +} + impl ToCommand for Dynamic { fn to_command(&self) -> String { self.cmd.clone() diff --git a/src/capsule.rs b/src/capsule.rs index 2b4a9e25..af2f1b2e 100644 --- a/src/capsule.rs +++ b/src/capsule.rs @@ -1,4 +1,4 @@ -//! Capsule type utilities +//! Capsule type utilities. //! //! Capsule types are a pattern where you associate a string to type, //! and use the tag as a key and the payload as a value in a map. diff --git a/src/delegation/payload.rs b/src/delegation/payload.rs index 8270d96e..75647b9d 100644 --- a/src/delegation/payload.rs +++ b/src/delegation/payload.rs @@ -5,7 +5,7 @@ use super::{ use crate::{ ability::{ arguments, - command::{Command, ToCommand}, + command::{Command, ParseAbility, ToCommand}, }, capsule::Capsule, did::Did, @@ -26,25 +26,95 @@ use serde::{ use std::{collections::BTreeMap, fmt, fmt::Debug}; use web_time::SystemTime; +/// The payload portion of a [`Delegation`][super::Delegation]. +/// +/// This contains the semantic information about the delegation, including the +/// issuer, subject, audience, the delegated ability, time bounds, and so on. #[derive(Debug, Clone, PartialEq)] pub struct Payload { - pub issuer: Did, + /// The subject of the [`Delegation`]. + /// + /// This role *must* have issued the earlier (root) + /// delegation in the chain. This makes the chains + /// self-certifying. + /// + /// The semantics of the delegation are established + /// by the subject. + /// + /// [`Delegation`]: super::Delegation pub subject: Did, + + /// The issuer of the [`Delegation`]. + /// + /// This [`Did`] *must* match the signature on + /// the outer layer of [`Delegation`]. + /// + /// [`Delegation`]: super::Delegation + pub issuer: Did, + + /// The agent being delegated to. pub audience: Did, /// A delegatable ability chain. /// - /// Note that this should be is some [Proof::Hierarchy] // FIXME enforce in types? + /// Note that this should be is some [Proof::Hierarchy] pub delegated_ability: D, - pub conditions: Vec, // FIXME BTreeSet? + /// Any [`Condition`]s on the `delegated_ability`. + pub conditions: Vec, + + /// Extensible, free-form fields. pub metadata: BTreeMap, + + /// A [cryptographic nonce] to ensure that the UCAN's [`Cid`] is unique. + /// + /// [cryptograpgic nonce]: https://en.wikipedia.org/wiki/Cryptographic_nonce + /// [`Cid`]: libipld_core::cid::Cid ; pub nonce: Nonce, + /// The latest wall-clock time that the UCAN is valid until, + /// given as a [Unix timestamp]. + /// + /// [Unix timestamp]: https://en.wikipedia.org/wiki/Unix_time pub expiration: Timestamp, + + /// An optional earliest wall-clock time that the UCAN is valid from, + /// given as a [Unix timestamp]. + /// + /// [Unix timestamp]: https://en.wikipedia.org/wiki/Unix_time pub not_before: Option, } +impl Payload { + pub fn map_ability(self, f: impl FnOnce(D) -> T) -> Payload { + Payload { + issuer: self.issuer, + subject: self.subject, + audience: self.audience, + delegated_ability: f(self.delegated_ability), + conditions: self.conditions, + metadata: self.metadata, + nonce: self.nonce, + expiration: self.expiration, + not_before: self.not_before, + } + } + + pub fn map_conditon(self, f: impl FnMut(C) -> T) -> Payload { + Payload { + issuer: self.issuer, + subject: self.subject, + audience: self.audience, + delegated_ability: self.delegated_ability, + conditions: self.conditions.into_iter().map(f).collect(), + metadata: self.metadata, + nonce: self.nonce, + expiration: self.expiration, + not_before: self.not_before, + } + } +} + impl Capsule for Payload { const TAG: &'static str = "ucan/d/1.0.0-rc.1"; } @@ -62,7 +132,13 @@ where state.serialize_field("iss", &self.issuer)?; state.serialize_field("sub", &self.subject)?; state.serialize_field("aud", &self.audience)?; + state.serialize_field("meta", &self.metadata)?; + state.serialize_field("nonce", &self.nonce)?; + state.serialize_field("exp", &self.expiration)?; + state.serialize_field("nbf", &self.not_before)?; + state.serialize_field("cmd", &self.delegated_ability.to_command())?; + state.serialize_field( "args", &arguments::Named::from(self.delegated_ability.clone()), @@ -77,17 +153,10 @@ where .collect::>(), )?; - state.serialize_field("meta", &self.metadata)?; - state.serialize_field("nonce", &self.nonce)?; - state.serialize_field("exp", &self.expiration)?; - state.serialize_field("nbf", &self.not_before)?; state.end() } } -// FIXME -use crate::ability::command::ParseAbility; - impl< 'de, T: ParseAbility + Deserialize<'de> + ToCommand, @@ -106,7 +175,7 @@ impl< impl< 'de, - T: Deserialize<'de> + ParseAbility + ToCommand, + T: ParseAbility + ToCommand + Deserialize<'de>, C: Condition + TryFrom + Deserialize<'de>, > Visitor<'de> for DelegationPayloadVisitor { @@ -214,11 +283,11 @@ impl< issuer: issuer.ok_or(de::Error::missing_field("iss"))?, subject: subject.ok_or(de::Error::missing_field("sub"))?, audience: audience.ok_or(de::Error::missing_field("aud"))?, - delegated_ability, conditions: conditions.ok_or(de::Error::missing_field("cond"))?, metadata: metadata.ok_or(de::Error::missing_field("meta"))?, nonce: nonce.ok_or(de::Error::missing_field("nonce"))?, expiration: expiration.ok_or(de::Error::missing_field("exp"))?, + delegated_ability, not_before, }) } @@ -232,161 +301,117 @@ impl< } } +impl< + T: ParseAbility + Command + for<'de> Deserialize<'de>, + C: Condition + for<'de> Deserialize<'de>, + > TryFrom for Payload +{ + type Error = SerdeError; + + fn try_from(ipld: Ipld) -> Result { + ipld_serde::from_ipld(ipld) + } +} + impl From> for Ipld { fn from(payload: Payload) -> Self { payload.into() } } -impl<'a, T, C: Condition> Payload { - pub fn check( - delegated: &'a Payload, // FIXME promisory version - proofs: Vec>, +impl Payload { + pub fn check<'a>( + &'a self, + proofs: Vec>, now: SystemTime, - ) -> Result<(), DelegationError<::Error>> + ) -> Result<(), DelegationError<::Error>> where - T: Checkable + CheckSame + Clone + Prove + Into>, + T::Hierarchy: Clone + Into>, + T: Clone + Checkable + Prove + Into>, { - let start: Acc = Acc { - issuer: delegated.issuer.clone(), - subject: delegated.subject.clone(), - hierarchy: delegated.delegated_ability.clone(), + let start: Acc = Acc { + issuer: self.issuer.clone(), + subject: self.subject.clone(), + hierarchy: T::Hierarchy::from(self.delegated_ability.clone()), }; - let args: arguments::Named = delegated.delegated_ability.clone().into(); - - proofs - .into_iter() - .fold(Ok(start), |prev, proof| { - if let Ok(prev_) = prev { - step(&prev_, &proof, &args, now).map(move |success| { - match success { - Success::ProvenByAny => Acc { - issuer: proof.issuer.clone(), - subject: proof.subject.clone(), - hierarchy: prev_.hierarchy, - }, - Success::Proven => Acc { - issuer: proof.issuer.clone(), - subject: proof.subject.clone(), - hierarchy: proof.delegated_ability.clone(), // FIXME double check - }, - } - }) - } else { - prev - } - }) - .map(|_| ()) + let args: arguments::Named = self.delegated_ability.clone().into(); + + proofs.into_iter().fold(Ok(start), |prev, proof| { + if let Ok(prev_) = prev { + prev_.step(&proof, &args, now).map(move |success| { + match success { + Success::ProvenByAny => Acc { + issuer: proof.issuer.clone(), + subject: proof.subject.clone(), + hierarchy: prev_.hierarchy, + }, + Success::Proven => Acc { + issuer: proof.issuer.clone(), + subject: proof.subject.clone(), + hierarchy: proof.delegated_ability.clone(), // FIXME double check + }, + } + }) + } else { + prev + } + })?; + + Ok(()) } } #[derive(Debug, Clone)] -struct Acc { +struct Acc { issuer: Did, subject: Did, - hierarchy: T, + hierarchy: H, } -// FIXME this should move to Delegatable? -fn step<'a, T: Prove + Clone + Into>, C: Condition>( - prev: &Acc, - proof: &Payload, - args: &arguments::Named, - now: SystemTime, -) -> Result::Error>> { - if let Err(_) = prev.issuer.check_same(&proof.audience) { - return Err(EnvelopeError::InvalidSubject.into()); - } - - if let Err(_) = prev.subject.check_same(&proof.subject) { - return Err(EnvelopeError::MisalignedIssAud.into()); - } - - if SystemTime::from(proof.expiration.clone()) > now { - return Err(EnvelopeError::Expired.into()); - } - - if let Some(nbf) = proof.not_before.clone() { - if SystemTime::from(nbf) > now { - return Err(EnvelopeError::NotYetValid.into()); +impl Acc { + // FIXME this should move to Delegatable? + fn step<'a, C: Condition>( + &self, + proof: &Payload, + args: &arguments::Named, + now: SystemTime, + ) -> Result::Error>> + where + H: Prove + Clone + Into>, + { + if let Err(_) = self.issuer.check_same(&proof.audience) { + return Err(EnvelopeError::InvalidSubject.into()); } - } - // FIXME coudl be more efficient, but sets need Ord and we have floats - for c in proof.conditions.iter() { - // FIXME revisit - if !c.validate(&args) || !c.validate(&prev.hierarchy.clone().into()) { - return Err(DelegationError::FailedCondition); + if let Err(_) = self.subject.check_same(&proof.subject) { + return Err(EnvelopeError::MisalignedIssAud.into()); } - } - - prev.hierarchy - .check(&proof.delegated_ability.clone()) - .map_err(DelegationError::SemanticError) -} -#[derive(Debug, Clone, PartialEq, Deserialize)] -#[serde(deny_unknown_fields)] -struct InternalDeserializer { - #[serde(rename = "iss")] - issuer: Did, - #[serde(rename = "sub")] - subject: Did, - #[serde(rename = "aud")] - audience: Did, - - #[serde(rename = "cmd")] - command: String, - #[serde(rename = "args")] - arguments: arguments::Named, - #[serde(rename = "cond")] - conditions: Vec, - - #[serde(rename = "nonce")] - nonce: Nonce, - #[serde(rename = "meta")] - metadata: BTreeMap, - - #[serde(rename = "nbf", skip_serializing_if = "Option::is_none")] - not_before: Option, - #[serde(rename = "exp")] - expiration: Timestamp, -} - -impl>, C: Condition + From> - TryFrom for Payload -{ - type Error = (); // FIXME - - fn try_from(d: InternalDeserializer) -> Result { - let p: Self = Payload { - issuer: d.issuer, - subject: d.subject, - audience: d.audience, - - delegated_ability: d.arguments.try_into().map_err(|_| ())?, // d.command.into(), - conditions: d.conditions.into_iter().map(|c| c.into()).collect(), - - metadata: d.metadata, - nonce: d.nonce, - - not_before: d.not_before, - expiration: d.expiration, - }; - - if p.delegated_ability.to_command() != d.command { - return Err(()); + if SystemTime::from(proof.expiration.clone()) > now { + return Err(EnvelopeError::Expired.into()); } - Ok(p) - } -} + if let Some(nbf) = proof.not_before.clone() { + if SystemTime::from(nbf) > now { + return Err(EnvelopeError::NotYetValid.into()); + } + } -impl TryFrom for InternalDeserializer { - type Error = SerdeError; + // This could be more efficient (dedup) with sets, but floats don't Ord :( + for c in proof.conditions.iter() { + // Validate both current & proof integrity. + // This should have the same semantic guarantees as looking at subsets, + // but for all known conditions will run much faster on average. + // Plz let me know if I got this wrong. + // —@expede + if !c.validate(&args) || !c.validate(&self.hierarchy.clone().into()) { + return Err(DelegationError::FailedCondition); + } + } - fn try_from(ipld: Ipld) -> Result { - ipld_serde::from_ipld(ipld) + self.hierarchy + .check(&proof.delegated_ability.clone()) + .map_err(DelegationError::SemanticError) } } diff --git a/src/delegation/store.rs b/src/delegation/store.rs index 1680cdb5..f6c5c4c2 100644 --- a/src/delegation/store.rs +++ b/src/delegation/store.rs @@ -1,83 +1,166 @@ use super::{condition::Condition, delegatable::Delegatable, Delegation}; use crate::did::Did; use libipld_core::cid::Cid; +use nonempty::NonEmpty; use serde::{Deserialize, Serialize}; -use std::collections::{BTreeMap, HashMap}; +use std::collections::{BTreeMap, BTreeSet}; use web_time::SystemTime; -// NOTE can already look up by CID in other traits -pub trait IndexedStore { - type Error; +pub trait Store { + fn insert(&mut self, cid: &Cid, delegation: Delegation); + fn revoke(&mut self, cid: &Cid); - fn get_by(query: Query) -> Result>, Self::Error>; + fn get_chain( + &self, + aud: &Did, + subject: &Did, + now: &SystemTime, + ) -> Option)>>; +} - fn previously_checked(cid: Cid) -> Result; +#[cfg_attr(doc, aquamarine::aquamarine)] +/// A simple in-memory store for delegations. +/// +/// The store is laid out as follows: +/// +/// `{Subject => {Audience => {Cid => Delegation}}}` +/// +/// ```mermaid +/// flowchart LR +/// subgraph Subjects +/// direction TB +/// +/// Akiko +/// Boris +/// Carol +/// +/// subgraph aud[Boris's Audiences] +/// direction TB +/// +/// Denzel +/// Erin +/// Frida +/// Georgia +/// Hugo +/// +/// subgraph cid[Frida's CIDs] +/// direction LR +/// +/// CID1 --> Delegation1 +/// CID2 --> Delegation2 +/// CID3 --> Delegation3 +/// end +/// end +/// end +/// +/// Akiko ~~~ Hugo +/// Carol ~~~ Hugo +/// Boris --> Frida --> CID2 +/// +/// Boris -.-> Denzel +/// Boris -.-> Erin +/// Boris -.-> Georgia +/// Boris -.-> Hugo +/// +/// Frida -.-> CID1 +/// Frida -.-> CID3 +/// +/// style Boris stroke:orange; +/// style Frida stroke:orange; +/// style CID2 stroke:orange; +/// style Delegation2 stroke:orange; +/// +/// linkStyle 5 stroke:orange; +/// linkStyle 6 stroke:orange; +/// linkStyle 1 stroke:orange; +/// ``` +#[derive(Debug, Clone, PartialEq)] +pub struct MemoryStore { + ucans: BTreeMap>, + index: BTreeMap>>, + revocations: BTreeSet, +} - // NOTE you can override this with something much more efficient in e.g. SQL - // FIXME "should" be checked and indexed on the way in - fn chains_for( - &self, - subject: Did, - command: String, - audience: Did, - ) -> Result)>>, Self::Error>; - // if let Ok(possible) = self.get_by(Query { - // audience: Some(audience), - // command: Some(command), - // after_not_before: Some(SystemTime::now()), - // expires_before: Some(SystemTime::now()), - // ..Default::default() - // }) { - // let acc = Ok(vec![]); - // let iss = possible.iter().next().unwrap().1.issuer; - - // // FIXME actually more complex than this: - // // sicne the chain also has to be valid - // // ...we shoud probably index on the way in - // while acc.is_ok() { - // if let Ok(latest) = get_one(Query { - // subject: Some(subject), - // command: Some(command), - // audience: Some(latest_iss), - // after_not_before: Some(SystemTime::now()), - // expires_before: Some(SystemTime::now()), - // ..Default::default() - // }) { - // acc.push(latest); - - // if delegation. - // latest_iss = delegation.issuer; - // } else { - // acc = Err(()); // FIXME - // } - // } - // } - - fn get_one(&self, query: Query) -> Result<(Cid, Delegation), Self::Error> { - todo!() - //let mut results = Self::get_by(query)?; - //results.pop().ok_or_else(|_| todo!()) +use std::ops::ControlFlow; + +// FIXME check that UCAN is valid +impl Store for MemoryStore { + fn insert(&mut self, cid: &Cid, delegation: Delegation) { + self.index + .entry(delegation.payload.subject.clone()) + .or_default() + .entry(delegation.payload.audience.clone()) + .or_default() + .insert(cid.clone()); + + self.ucans.insert(cid.clone(), delegation); } - fn expired(&self) -> Result>, Self::Error> { - todo!() - // self.get_by(Query { - // expires_before: Some(SystemTime::now()), - // ..Default::default() - // }) + fn revoke(&mut self, cid: &Cid) { + self.revocations.insert(cid.clone()); } -} -#[derive(Default, Debug, Clone, PartialEq)] -pub struct Query { - pub subject: Option, - pub command: Option, - pub issuer: Option, - pub audience: Option, + fn get_chain( + &self, + aud: &Did, + subject: &Did, + now: &SystemTime, + ) -> Option)>> { + #[derive(PartialEq)] + enum Status { + Complete, + Looking, + NoPath, + } + + let mut status = Status::Looking; + let mut target_aud = aud; + let mut chain = vec![]; - pub prior_to_not_before: Option, // FIXME time - pub after_not_before: Option, // FIXME time + let delegation_subtree = self + .index + .get(subject) + .and_then(|aud_map| aud_map.get(aud))?; - pub expires_before: Option, // FIXME time - pub expires_aftre: Option, // FIXME time + while status == Status::Looking { + let found = delegation_subtree.iter().try_for_each(|cid| { + if self.revocations.contains(&cid) { + return ControlFlow::Continue(()); + } + + if let Some(d) = self.ucans.get(cid) { + if SystemTime::from(d.payload.expiration.clone()) < *now { + return ControlFlow::Continue(()); + } + + if let Some(nbf) = &d.payload.not_before { + if SystemTime::from(nbf.clone()) > *now { + return ControlFlow::Continue(()); + } + } + + chain.push((cid, d)); + + if &d.payload.issuer == subject { + status = Status::Complete; + } else { + target_aud = &d.payload.issuer; + } + + ControlFlow::Break(()) + } else { + ControlFlow::Continue(()) + } + }); + + if found.is_continue() { + status = Status::NoPath; + } + } + + match status { + Status::Complete => NonEmpty::from_vec(chain), + _ => None, + } + } } diff --git a/src/did.rs b/src/did.rs index c5312722..10ddb45e 100644 --- a/src/did.rs +++ b/src/did.rs @@ -1,4 +1,6 @@ -//! Decentralized Identifier (DID) utilities +//! Decentralized Identifier ([DID][wiki]) utilities. +//! +//! [wiki]: https://en.wikipedia.org/wiki/Decentralized_identifier use did_url::DID; use libipld_core::ipld::Ipld; @@ -6,7 +8,7 @@ use serde::{Deserialize, Serialize}; use std::fmt; use thiserror::Error; -#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] #[serde(into = "String", try_from = "String")] /// A [Decentralized Identifier (DID)][wiki] /// diff --git a/src/invocation.rs b/src/invocation.rs index d217ca07..e93aa57a 100644 --- a/src/invocation.rs +++ b/src/invocation.rs @@ -4,9 +4,16 @@ mod serializer; pub mod promise; -pub use payload::{Payload, Unresolved}; +pub use payload::{Payload, Promised}; pub use resolvable::Resolvable; use crate::signature; +/// The complete, signed [`invocation::Payload`][Payload]. +/// +/// # Promises +/// +/// For a version that can include [`Promise`][promise::Promise]s, +/// wrap your `T` in [`invocation::Promised`](Promised) to get +/// `Invocation>`. pub type Invocation = signature::Envelope>; diff --git a/src/invocation/payload.rs b/src/invocation/payload.rs index 39c89c3f..216fab5a 100644 --- a/src/invocation/payload.rs +++ b/src/invocation/payload.rs @@ -2,19 +2,28 @@ use super::resolvable::Resolvable; use crate::{ ability::{arguments, command::ToCommand}, capsule::Capsule, + delegation, + delegation::{ + condition::Condition, + error::{DelegationError, EnvelopeError}, + Delegatable, + }, did::Did, nonce::Nonce, + proof::{ + checkable::Checkable, + prove::{Prove, Success}, + same::CheckSame, + }, time::Timestamp, }; use libipld_core::{cid::Cid, error::SerdeError, ipld::Ipld, serde as ipld_serde}; use serde::{Serialize, Serializer}; -use std::{collections::BTreeMap, fmt::Debug}; +use std::{collections::BTreeMap, fmt::Debug, marker::PhantomData}; +use web_time::SystemTime; -// FIXME this version should not be resolvable... -// FIXME ...or at least have two versions via abstraction #[derive(Debug, Clone, PartialEq)] pub struct Payload { - // FIXME we're going to toss that E pub issuer: Did, pub subject: Did, pub audience: Option, @@ -30,29 +39,116 @@ pub struct Payload { pub expiration: Timestamp, } -// FIXME To TaskId - -// NOTE This is the version that accepts promises -pub type Unresolved = Payload<::Promised>; -// type Dynamic = Payload; <- ? - -// FIXME parser for both versions -// #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] -// #[serde(untagged)] -// pub enum MaybeResolved + Into> -// where -// Payload: From, -// Unresolved: From, -// T::Promised: Clone + Command + Debug + PartialEq, -// { -// Resolved(Payload), -// Unresolved(Unresolved), -// } +// FIXME cleanup traits +// one idea, because they keep comingup together: put hierarchy and builder on the same +// trair (as associated tyeps) to klet us skip the ::bulder::hierarchy indirection. +// +// This probably means putting the delegation T back to the upper level and bieng explicit about +// the T::Builder in the type +impl Payload { + pub fn check( + self, + proofs: Vec::Hierarchy, C>>, + now: SystemTime, + ) -> Result<(), DelegationError<<::Hierarchy as Prove>::Error>> + where + T: Delegatable, + T::Builder: Clone + Checkable + Prove + Into>, + ::Hierarchy: Clone + Into>, + { + let builder_payload: delegation::Payload = self.into(); + builder_payload.check(proofs, now) + } +} impl Capsule for Payload { const TAG: &'static str = "ucan/i/1.0.0-rc.1"; } +impl From> for delegation::Payload { + fn from(payload: Payload) -> Self { + delegation::Payload { + issuer: payload.issuer, + subject: payload.subject.clone(), + audience: payload.audience.unwrap_or(payload.subject), + + delegated_ability: T::Builder::from(payload.ability), + conditions: vec![], + + metadata: payload.metadata, + nonce: payload.nonce, + + not_before: payload.not_before, + expiration: payload.expiration, + } + } +} + +impl> From> for arguments::Named { + fn from(payload: Payload) -> Self { + let mut args = arguments::Named::from_iter([ + ("iss".into(), payload.issuer.into()), + ("sub".into(), payload.subject.into()), + ("cmd".into(), payload.ability.to_command().into()), + ("args".into(), payload.ability.into()), + ( + "prf".into(), + Ipld::List(payload.proofs.iter().map(Into::into).collect()), + ), + ("nonce".into(), payload.nonce.into()), + ("exp".into(), payload.expiration.into()), + ]); + + if let Some(audience) = payload.audience { + args.insert("aud".into(), audience.into()); + } + + if let Some(not_before) = payload.not_before { + args.insert("nbf".into(), not_before.into()); + } + + args + } +} + +/// A variant that accepts [`Promise`]s. +/// +/// [`Promise`]: crate::invocation::promise::Promise +pub type Promised = Payload<::Promised>; + +impl Resolvable for Payload +where + arguments::Named: From, + Ipld: From, + T::Promised: ToCommand, +{ + type Promised = Promised; + + fn try_resolve(promised: Promised) -> Result { + match ::try_resolve(promised.ability) { + Ok(resolved_ability) => Ok(Payload { + issuer: promised.issuer, + subject: promised.subject, + audience: promised.audience, + + ability: resolved_ability, + + proofs: promised.proofs, + cause: promised.cause, + metadata: promised.metadata, + nonce: promised.nonce, + + not_before: promised.not_before, + expiration: promised.expiration, + }), + Err(promised_ability) => Err(Payload { + ability: promised_ability, + ..promised + }), + } + } +} + impl Serialize for Payload where Payload: Clone, diff --git a/src/ipld.rs b/src/ipld.rs index bec0bc37..d492e4da 100644 --- a/src/ipld.rs +++ b/src/ipld.rs @@ -1,4 +1,4 @@ -//! Helpers for working with [`Ipld`][libipld_core::ipld::Ipld] +//! Helpers for working with [`Ipld`][libipld_core::ipld::Ipld]. mod enriched; mod newtype; diff --git a/src/lib.rs b/src/lib.rs index ab83b718..8c7b0437 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -80,6 +80,10 @@ pub mod task; pub mod time; pub mod url; +pub use delegation::Delegation; +pub use invocation::Invocation; +pub use receipt::Receipt; + // FIXME consider a fact-system // /// The empty fact // #[derive(Debug, Clone, Default, Serialize, Deserialize)] diff --git a/src/nonce.rs b/src/nonce.rs index c2837684..1cee2fc3 100644 --- a/src/nonce.rs +++ b/src/nonce.rs @@ -1,4 +1,6 @@ -//! Nonce utilities +//! [Nonce]s & utilities. +//! +//! [Nonce]: https://en.wikipedia.org/wiki/Cryptographic_nonce use enum_as_inner::EnumAsInner; use getrandom::getrandom; diff --git a/src/number.rs b/src/number.rs index 1c38f6b5..bfa72fa5 100644 --- a/src/number.rs +++ b/src/number.rs @@ -1,4 +1,4 @@ -//! Helpers for working with [`Ipld`] numerics +//! Helpers for working with [`Ipld`] numerics. use libipld_core::{error::SerdeError, ipld::Ipld, serde as ipld_serde}; use serde_derive::{Deserialize, Serialize}; diff --git a/src/proof.rs b/src/proof.rs index c0b401a7..a80cd781 100644 --- a/src/proof.rs +++ b/src/proof.rs @@ -1,4 +1,4 @@ -//! Proof chains, checking, and utilities +//! Proof chains, checking, and utilities. pub mod checkable; pub mod error; diff --git a/src/proof/checkable.rs b/src/proof/checkable.rs index ee855eae..952efc5d 100644 --- a/src/proof/checkable.rs +++ b/src/proof/checkable.rs @@ -5,11 +5,11 @@ use super::{prove::Prove, same::CheckSame}; // FIXME mo ve to Delegatbel? /// Plug a type into the delegation checking pipeline -pub trait Checkable: CheckSame { +pub trait Checkable: CheckSame + Sized { /// The type of hierarchy this ability has /// /// The only options are [`Parentful`][super::parentful::Parentful] /// and [`Parentless`][super::parentless::Parentless], /// (which are the only instances of the unexported `Checker`) - type Hierarchy: CheckSame + Prove; + type Hierarchy: CheckSame + Prove + From; } diff --git a/src/proof/parentful.rs b/src/proof/parentful.rs index 54b29938..11a859ab 100644 --- a/src/proof/parentful.rs +++ b/src/proof/parentful.rs @@ -28,6 +28,15 @@ pub enum Parentful { This(T), } +impl From for Parentful +where + T: CheckParents, +{ + fn from(this: T) -> Self { + Parentful::This(this) + } +} + /// Error cases when checking proofs (including parents) #[derive(Debug, Error, PartialEq)] pub enum ParentfulError { diff --git a/src/proof/parentless.rs b/src/proof/parentless.rs index 4e3659fa..2400b756 100644 --- a/src/proof/parentless.rs +++ b/src/proof/parentless.rs @@ -24,6 +24,12 @@ pub enum Parentless { This(T), } +impl From for Parentless { + fn from(this: T) -> Self { + Parentless::This(this) + } +} + // FIXME generally useful (e.g. checkiung `_/*`); move to its own module and rename? /// Error cases when checking proofs #[derive(Debug, Clone, PartialEq)] @@ -56,14 +62,6 @@ where } } -impl + DeserializeOwned> TryFrom for Parentless { - type Error = SerdeError; - - fn try_from(ipld: Ipld) -> Result { - ipld_serde::from_ipld(ipld) - } -} - impl CheckSame for Parentless { type Error = ParentlessError; diff --git a/src/reader.rs b/src/reader.rs index 7ba78b78..81e43ca3 100644 --- a/src/reader.rs +++ b/src/reader.rs @@ -1,10 +1,12 @@ -//! Configure & attach an ambient environment to a value +//! Configure & attach an ambient environment to a value. use crate::{ - ability::{arguments, command::ToCommand}, + ability::{ + arguments, + command::{ParseAbility, ParseAbilityError, ToCommand}, + }, delegation::Delegatable, - invocation::{promise, Resolvable}, - proof::{checkable::Checkable, same::CheckSame}, + invocation::Resolvable, }; use libipld_core::ipld::Ipld; use serde::{Deserialize, Serialize}; @@ -158,19 +160,23 @@ impl>> From> for arguments::N } } -impl Checkable for Reader -where - Reader: CheckSame, -{ - type Hierarchy = Env::Hierarchy; -} - impl ToCommand for Reader { fn to_command(&self) -> String { self.env.to_command() } } +impl ParseAbility for Reader { + type Error = ParseAbilityError<::Error>; + + fn try_parse(cmd: &str, args: &arguments::Named) -> Result { + Ok(Reader { + env: Default::default(), + val: T::try_parse(cmd, args).map_err(ParseAbilityError::InvalidArgs)?, + }) + } +} + /// A helper newtype that marks a value as being a [`Delegatable::Builder`]. /// /// The is often used as: diff --git a/src/receipt.rs b/src/receipt.rs index 1b5284cf..497ebc76 100644 --- a/src/receipt.rs +++ b/src/receipt.rs @@ -1,9 +1,14 @@ +//! The (optional) response from an [`Invocation`][`crate::invocation::Invocation`]. + mod payload; -mod receipt; mod responds; mod store; pub use payload::Payload; -pub use receipt::Receipt; pub use responds::Responds; pub use store::Store; + +use crate::signature; + +/// The complete, signed receipt of an [`Invocation`][`crate::invocation::Invocation`]. +pub type Receipt = signature::Envelope>; diff --git a/src/receipt/payload.rs b/src/receipt/payload.rs index a76939c5..4e6a654b 100644 --- a/src/receipt/payload.rs +++ b/src/receipt/payload.rs @@ -1,69 +1,207 @@ +//! The payload (non-signature) portion of a response from an [`Invocation`]. +//! +//! [`Invocation`]: crate::invocation::Invocation + use super::responds::Responds; use crate::{ability::arguments, capsule::Capsule, did::Did, nonce::Nonce, time::Timestamp}; use libipld_core::{cid::Cid, error::SerdeError, ipld::Ipld, serde as ipld_serde}; -use serde::{de::DeserializeOwned, Deserialize, Serialize, Serializer}; -use std::{collections::BTreeMap, fmt::Debug}; - -// FIXME serialize/deseialize split out for when the T has implementations - +use serde::{ + de::{self, DeserializeOwned, MapAccess, Visitor}, + ser::SerializeStruct, + Deserialize, Serialize, Serializer, +}; +use std::{collections::BTreeMap, fmt, fmt::Debug}; + +/// The payload (non-signature) portion of a response from an [`Invocation`]. +/// +/// [`Invocation`]: crate::invocation::Invocation #[derive(Debug, Clone, PartialEq)] pub struct Payload { + /// The issuer of the [`Receipt`]. + /// + /// This [`Did`] *must* match the signature on + /// the outer layer of [`Receipt`]. + /// + /// [`Receipt`]: super::Receipt pub issuer: Did, + /// The [`Cid`] of the [`Invocation`] that was run. + /// + /// [`Invocation`]: crate::invocation::Invocation pub ran: Cid, + + /// The output of the [`Invocation`]. + /// + /// This is always of the form `{"ok": ...}` or `{"err": ...}`. + /// + /// [`Invocation`]: crate::invocation::Invocation pub out: Result>, + + /// Any further [`Invocation`]s that the `ran` [`Invocation`] + /// requested to be queued next. + /// + /// [`Invocation`]: crate::invocation::Invocation pub next: Vec, // FIXME rename here or in spec? + /// An optional proof chain authorizing a different [`Did`] to + /// be the receipt `iss` than the audience (or subject) of the + /// [`Invocation`] that was run. + /// + /// [`Invocation`]: crate::invocation::Invocation pub proofs: Vec, + + /// Extensible, free-form fields. pub metadata: BTreeMap, + /// A [cryptographic nonce] to ensure that the UCAN's [`Cid`] is unique. + /// + /// [cryptographic nonce]: https://en.wikipedia.org/wiki/Cryptographic_nonce + /// [`Cid`]: libipld_core::cid::Cid pub nonce: Nonce, + + /// An optional [Unix timestamp] (wall-clock time) at which the + /// receipt claims to have been issued at. + /// + /// [Unix timestamp]: https://en.wikipedia.org/wiki/Unix_time pub issued_at: Option, } impl Capsule for Payload { - const TAG: &'static str = "ucan/r/1.0.0-rc.1"; // FIXME extract out version + const TAG: &'static str = "ucan/r/1.0.0-rc.1"; } -impl Serialize for Payload +impl Serialize for Payload where - Payload: Clone, - T::Success: Serialize + DeserializeOwned, + T::Success: Serialize, { fn serialize(&self, serializer: S) -> Result where S: Serializer, { - let s = InternalSerializer::from((*self).clone()); // FIXME kill that clone with tons of refs? - serde::Serialize::serialize(&s, serializer) + let mut state = serializer.serialize_struct("receipt::Payload", 8)?; + state.serialize_field("iss", &self.issuer)?; + state.serialize_field("ran", &self.ran)?; + state.serialize_field("out", &self.out)?; + state.serialize_field("next", &self.next)?; + state.serialize_field("prf", &self.proofs)?; + state.serialize_field("meta", &self.metadata)?; + state.serialize_field("nonce", &self.nonce)?; + state.serialize_field("iat", &self.issued_at)?; + + state.end() } } -impl<'de, T: Responds + Deserialize<'de>> Deserialize<'de> for Payload +impl<'de, T: Responds> Deserialize<'de> for Payload where - as TryFrom>>::Error: Debug, - T::Success: DeserializeOwned + Serialize, + T::Success: Deserialize<'de>, { - fn deserialize(d: D) -> Result + fn deserialize(deserializer: D) -> Result where D: serde::Deserializer<'de>, { - match InternalSerializer::deserialize(d) { - Err(e) => Err(e), - Ok(s) => Ok(s.into()), + struct ReceiptPayloadVisitor(std::marker::PhantomData); + + const FIELDS: &'static [&'static str] = + &["iss", "ran", "out", "next", "prf", "meta", "nonce", "iat"]; + + impl<'de, T: Responds> Visitor<'de> for ReceiptPayloadVisitor + where + T::Success: Deserialize<'de>, + { + type Value = Payload; + + fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { + formatter.write_str("struct delegation::Payload") + } + + fn visit_map(self, mut map: M) -> Result + where + M: MapAccess<'de>, + { + let mut issuer = None; + let mut ran = None; + let mut out = None; + let mut next = None; + let mut proofs = None; + let mut metadata = None; + let mut nonce = None; + let mut issued_at = None; + + while let Some(key) = map.next_key()? { + match key { + "iss" => { + if issuer.is_some() { + return Err(de::Error::duplicate_field("iss")); + } + issuer = Some(map.next_value()?); + } + "ran" => { + if ran.is_some() { + return Err(de::Error::duplicate_field("ran")); + } + ran = Some(map.next_value()?); + } + "out" => { + if out.is_some() { + return Err(de::Error::duplicate_field("out")); + } + out = Some(map.next_value()?); + } + "next" => { + if next.is_some() { + return Err(de::Error::duplicate_field("next")); + } + next = Some(map.next_value()?); + } + "prf" => { + if proofs.is_some() { + return Err(de::Error::duplicate_field("prf")); + } + proofs = Some(map.next_value()?); + } + "meta" => { + if metadata.is_some() { + return Err(de::Error::duplicate_field("meta")); + } + metadata = Some(map.next_value()?); + } + "nonce" => { + if nonce.is_some() { + return Err(de::Error::duplicate_field("nonce")); + } + nonce = Some(map.next_value()?); + } + "iat" => { + if issued_at.is_some() { + return Err(de::Error::duplicate_field("iat")); + } + issued_at = map.next_value()?; + } + other => { + return Err(de::Error::unknown_field(other, FIELDS)); + } + } + } + + Ok(Payload { + issuer: issuer.ok_or_else(|| de::Error::missing_field("iss"))?, + ran: ran.ok_or_else(|| de::Error::missing_field("ran"))?, + out: out.ok_or_else(|| de::Error::missing_field("out"))?, + next: next.ok_or_else(|| de::Error::missing_field("next"))?, + proofs: proofs.ok_or_else(|| de::Error::missing_field("prf"))?, + metadata: metadata.ok_or_else(|| de::Error::missing_field("meta"))?, + nonce: nonce.ok_or_else(|| de::Error::missing_field("nonce"))?, + issued_at, + }) + } } - } -} - -impl TryFrom for Payload -where - T::Success: Serialize + DeserializeOwned, -{ - type Error = SerdeError; - fn try_from(ipld: Ipld) -> Result { - let s: InternalSerializer = ipld_serde::from_ipld(ipld)?; - Ok(s.into()) + deserializer.deserialize_struct( + "ReceiptPayload", + FIELDS, + ReceiptPayloadVisitor(Default::default()), + ) } } @@ -73,61 +211,13 @@ impl From> for Ipld { } } -#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] -#[serde(deny_unknown_fields)] -struct InternalSerializer -where - T::Success: Serialize + DeserializeOwned, -{ - #[serde(rename = "iss")] - issuer: Did, - - ran: Cid, - out: Result>, - next: Vec, // FIXME rename here or in spec? - - #[serde(rename = "prf")] - proofs: Vec, - #[serde(rename = "meta")] - metadata: BTreeMap, - - nonce: Nonce, - #[serde(rename = "iat")] - issued_at: Option, -} - -impl From> for Payload +impl TryFrom for Payload where - T::Success: Serialize + DeserializeOwned, + T::Success: DeserializeOwned, { - fn from(s: InternalSerializer) -> Self { - Payload { - issuer: s.issuer, - ran: s.ran, - out: s.out, - next: s.next, - proofs: s.proofs, - metadata: s.metadata, - nonce: s.nonce, - issued_at: s.issued_at, - } - } -} + type Error = SerdeError; -impl From> for InternalSerializer -where - T::Success: Serialize + DeserializeOwned, -{ - fn from(s: Payload) -> Self { - InternalSerializer { - issuer: s.issuer, - ran: s.ran, - out: s.out, - next: s.next, - proofs: s.proofs, - metadata: s.metadata.into(), - nonce: s.nonce, - issued_at: s.issued_at, - } + fn try_from(ipld: Ipld) -> Result { + ipld_serde::from_ipld(ipld) } } diff --git a/src/receipt/receipt.rs b/src/receipt/receipt.rs deleted file mode 100644 index dfd1db01..00000000 --- a/src/receipt/receipt.rs +++ /dev/null @@ -1,4 +0,0 @@ -use super::payload::Payload; -use crate::signature; - -pub type Receipt = signature::Envelope>; diff --git a/src/receipt/responds.rs b/src/receipt/responds.rs index e35f5d60..dace3436 100644 --- a/src/receipt/responds.rs +++ b/src/receipt/responds.rs @@ -1,10 +1,21 @@ use crate::{did::Did, nonce::Nonce, task, task::Task}; +/// Describe the relationship between an ability and the [`Receipt`]s. +/// +/// This is used for constucting [`Receipt`]s, and indexing them for +/// reverse lookup. +/// +/// [`Receipt`]: crate::receipt::Receipt pub trait Responds { + /// The successful return type for running `Self`. type Success; + /// Convert an Ability (`Self`) into a [`Task`]. + /// + /// This is used to index receipts by a minimal [`Id`]. fn to_task(&self, subject: Did, nonce: Nonce) -> Task; + /// Convert an Ability (`Self`) directly into a [`Task`]'s [`Id`]. fn to_task_id(&self, subject: Did, nonce: Nonce) -> task::Id { task::Id { cid: self.to_task(subject, nonce).into(), diff --git a/src/receipt/store.rs b/src/receipt/store.rs index 75cd0691..66feb0d9 100644 --- a/src/receipt/store.rs +++ b/src/receipt/store.rs @@ -2,13 +2,17 @@ use super::{Receipt, Responds}; use crate::task; use libipld_core::ipld::Ipld; +/// A store for [`Receipt`]s indexed by their [`task::Id`]s. pub trait Store { + /// The error type representing all the ways a store operation can fail. type Error; + /// Retrieve a [`Receipt`] by its [`task::Id`]. fn get(id: task::Id) -> Result, Self::Error> where ::Success: TryFrom; + /// Store a [`Receipt`] by its [`task::Id`]. fn put_keyed(id: task::Id, receipt: Receipt) -> Result<(), Self::Error> where ::Success: Into; diff --git a/src/signature.rs b/src/signature.rs index 392a9f9a..322aac62 100644 --- a/src/signature.rs +++ b/src/signature.rs @@ -1,11 +1,17 @@ +//! Signatures and cryptographic envelopes. + use crate::capsule::Capsule; use libipld_core::ipld::Ipld; use serde::{Deserialize, Serialize}; use std::collections::BTreeMap; +/// A container associating a `payload` with its signature over it. #[derive(Debug, Clone, PartialEq)] pub struct Envelope { + /// The signture of the `payload`. pub sig: Signature, + + /// The payload that's being signed over. pub payload: T, } diff --git a/src/task.rs b/src/task.rs index 6e80ca88..f41cbc54 100644 --- a/src/task.rs +++ b/src/task.rs @@ -1,4 +1,4 @@ -//! Task indices for [`Receipt`][crate::receipt::Receipt] reverse lookup +//! Task indices for [`Receipt`][crate::receipt::Receipt] reverse lookup. use crate::{ability::arguments, did::Did, nonce::Nonce}; use libipld_cbor::DagCborCodec; diff --git a/src/time.rs b/src/time.rs index 37a4b838..50a8e95c 100644 --- a/src/time.rs +++ b/src/time.rs @@ -1,4 +1,4 @@ -//! Time utilities +//! Time utilities. use libipld_core::{error::SerdeError, ipld::Ipld, serde as ipld_serde}; use serde::{Deserialize, Deserializer, Serialize, Serializer}; @@ -30,6 +30,19 @@ pub enum Timestamp { Postel(SystemTime), } +impl From for Ipld { + fn from(timestamp: Timestamp) -> Self { + match timestamp { + Timestamp::JsSafe(js_time) => js_time.into(), + Timestamp::Postel(sys_time) => sys_time + .duration_since(UNIX_EPOCH) + .expect("FIXME") + .as_secs() + .into(), + } + } +} + impl Serialize for Timestamp { fn serialize(&self, serializer: S) -> Result where @@ -70,12 +83,6 @@ impl From for SystemTime { } } -impl From for Ipld { - fn from(timestamp: Timestamp) -> Self { - timestamp.into() - } -} - impl TryFrom for Timestamp { type Error = SerdeError; diff --git a/src/url.rs b/src/url.rs index a69b78bc..b97d9b64 100644 --- a/src/url.rs +++ b/src/url.rs @@ -1,4 +1,4 @@ -//! URL utilities +//! URL utilities. use libipld_core::{error::SerdeError, ipld::Ipld, serde as ipld_serde}; use serde::{Deserialize, Serialize}; From 4f3468f5fc88aa048ce17d34d7f3ed2dba279fcf Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Sun, 11 Feb 2024 17:45:55 -0800 Subject: [PATCH 069/188] Save before trait reorg --- src/ability/command.rs | 2 +- src/ability/dynamic.rs | 2 ++ src/ability/ucan/revoke.rs | 2 +- src/delegation/condition/traits.rs | 2 +- src/delegation/delegatable.rs | 5 ++--- src/delegation/payload.rs | 9 +++------ src/delegation/store.rs | 31 +++++++++++++++++++++++------- src/delegation/traits.rs | 5 ----- src/proof/prove.rs | 2 +- src/reader.rs | 4 ---- src/receipt/responds.rs | 1 + 11 files changed, 36 insertions(+), 29 deletions(-) delete mode 100644 src/delegation/traits.rs diff --git a/src/ability/command.rs b/src/ability/command.rs index 30eca578..18458fc9 100644 --- a/src/ability/command.rs +++ b/src/ability/command.rs @@ -83,7 +83,7 @@ where return Err(ParseAbilityError::UnknownCommand); } - Ipld::from(args.clone()) + Ipld::Map(args.0.clone()) .try_into() .map_err(ParseAbilityError::InvalidArgs) } diff --git a/src/ability/dynamic.rs b/src/ability/dynamic.rs index b8f58f56..64e7af1e 100644 --- a/src/ability/dynamic.rs +++ b/src/ability/dynamic.rs @@ -17,6 +17,8 @@ use js_sys; // NOTE the lack of checking functions! +// FIXME make a NOTE somewhere that Hierarchy is availavle on ability/js/... + /// A "dynamic" ability with the bare minimum of statics /// ///

diff --git a/src/ability/ucan/revoke.rs b/src/ability/ucan/revoke.rs index bf45a63d..6f9d3fd8 100644 --- a/src/ability/ucan/revoke.rs +++ b/src/ability/ucan/revoke.rs @@ -25,7 +25,7 @@ impl Command for Generic { /// The fully resolved variant: ready to execute. pub type Ready = Generic; -impl NoParents for Ready {} +impl NoParents for Builder {} impl Delegatable for Ready { type Builder = Builder; diff --git a/src/delegation/condition/traits.rs b/src/delegation/condition/traits.rs index 7ff605e8..632d5615 100644 --- a/src/delegation/condition/traits.rs +++ b/src/delegation/condition/traits.rs @@ -4,7 +4,7 @@ use crate::ability::arguments; use libipld_core::ipld::Ipld; /// A trait for conditions that can be run on named IPLD arguments. -pub trait Condition: TryFrom + Into { +pub trait Condition { /// Check that some condition is met on named IPLD arguments. fn validate(&self, args: &arguments::Named) -> bool; } diff --git a/src/delegation/delegatable.rs b/src/delegation/delegatable.rs index 7dbbd2bd..4bf4fde9 100644 --- a/src/delegation/delegatable.rs +++ b/src/delegation/delegatable.rs @@ -1,10 +1,9 @@ -use crate::ability::arguments; +use crate::{ability::arguments, proof::checkable::Checkable}; use libipld_core::ipld::Ipld; // FIXME require checkable? pub trait Delegatable: Sized { /// A delegation with some arguments filled /// FIXME add more - /// FIXME require CheckSame? - type Builder: TryInto + From + Into>; + type Builder: TryInto + From + Checkable; } diff --git a/src/delegation/payload.rs b/src/delegation/payload.rs index 75647b9d..be73086e 100644 --- a/src/delegation/payload.rs +++ b/src/delegation/payload.rs @@ -157,11 +157,8 @@ where } } -impl< - 'de, - T: ParseAbility + Deserialize<'de> + ToCommand, - C: Condition + TryFrom + Deserialize<'de>, - > Deserialize<'de> for Payload +impl<'de, T: ParseAbility + Deserialize<'de> + ToCommand, C: Condition + Deserialize<'de>> + Deserialize<'de> for Payload { fn deserialize(deserializer: D) -> Result where @@ -176,7 +173,7 @@ impl< impl< 'de, T: ParseAbility + ToCommand + Deserialize<'de>, - C: Condition + TryFrom + Deserialize<'de>, + C: Condition + Deserialize<'de>, > Visitor<'de> for DelegationPayloadVisitor { type Value = Payload; diff --git a/src/delegation/store.rs b/src/delegation/store.rs index f6c5c4c2..31d8dd8e 100644 --- a/src/delegation/store.rs +++ b/src/delegation/store.rs @@ -3,10 +3,18 @@ use crate::did::Did; use libipld_core::cid::Cid; use nonempty::NonEmpty; use serde::{Deserialize, Serialize}; -use std::collections::{BTreeMap, BTreeSet}; +use std::{ + collections::{BTreeMap, BTreeSet}, + ops::ControlFlow, +}; +use thiserror::Error; use web_time::SystemTime; pub trait Store { + type Error; + + fn get_by_cid(&self, cid: &Cid) -> Result<&Delegation, Self::Error>; + fn insert(&mut self, cid: &Cid, delegation: Delegation); fn revoke(&mut self, cid: &Cid); @@ -15,7 +23,7 @@ pub trait Store { aud: &Did, subject: &Did, now: &SystemTime, - ) -> Option)>>; + ) -> Result)>, Self::Error>; } #[cfg_attr(doc, aquamarine::aquamarine)] @@ -81,10 +89,14 @@ pub struct MemoryStore { revocations: BTreeSet, } -use std::ops::ControlFlow; +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Error)] +#[error("Delegation not found")] +pub struct NotFound; // FIXME check that UCAN is valid impl Store for MemoryStore { + type Error = NotFound; + fn insert(&mut self, cid: &Cid, delegation: Delegation) { self.index .entry(delegation.payload.subject.clone()) @@ -100,12 +112,16 @@ impl Store for MemoryStore { self.revocations.insert(cid.clone()); } + fn get_by_cid(&self, cid: &Cid) -> Result<&Delegation, Self::Error> { + self.ucans.get(cid).ok_or(NotFound) + } + fn get_chain( &self, aud: &Did, subject: &Did, now: &SystemTime, - ) -> Option)>> { + ) -> Result)>, NotFound> { #[derive(PartialEq)] enum Status { Complete, @@ -120,7 +136,8 @@ impl Store for MemoryStore { let delegation_subtree = self .index .get(subject) - .and_then(|aud_map| aud_map.get(aud))?; + .and_then(|aud_map| aud_map.get(aud)) + .ok_or(NotFound)?; while status == Status::Looking { let found = delegation_subtree.iter().try_for_each(|cid| { @@ -159,8 +176,8 @@ impl Store for MemoryStore { } match status { - Status::Complete => NonEmpty::from_vec(chain), - _ => None, + Status::Complete => NonEmpty::from_vec(chain).ok_or(NotFound), + _ => Err(NotFound), } } } diff --git a/src/delegation/traits.rs b/src/delegation/traits.rs deleted file mode 100644 index 0d102d22..00000000 --- a/src/delegation/traits.rs +++ /dev/null @@ -1,5 +0,0 @@ -use crate::{ability::arguments, did::Did, nonce::Nonce, task, task::Task}; - -pub trait Delegatable: Sized { - type Builder: TryInto + From + Into; -} diff --git a/src/proof/prove.rs b/src/proof/prove.rs index fbf4d54d..b97589ba 100644 --- a/src/proof/prove.rs +++ b/src/proof/prove.rs @@ -3,7 +3,7 @@ use super::internal::Checker; /// An internal trait that checks based on the other traits for an ability type. -pub trait Prove: Checker { +pub(crate) trait Prove: Checker { type Error; fn check(&self, proof: &Self) -> Result; diff --git a/src/reader.rs b/src/reader.rs index 81e43ca3..c918ee35 100644 --- a/src/reader.rs +++ b/src/reader.rs @@ -205,10 +205,6 @@ impl From> for Reader> { } } -impl>> Delegatable for Reader { - type Builder = Reader>; -} - /// A helper newtype that marks a value as being a [`Resolvable::Promised`]. /// /// The is often used as: diff --git a/src/receipt/responds.rs b/src/receipt/responds.rs index dace3436..4e27ef3a 100644 --- a/src/receipt/responds.rs +++ b/src/receipt/responds.rs @@ -1,4 +1,5 @@ use crate::{did::Did, nonce::Nonce, task, task::Task}; +use libipld_core::ipld::Ipld; /// Describe the relationship between an ability and the [`Receipt`]s. /// From 1b6892262655f9b69c526215a38f46d9dbaa3950 Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Sun, 11 Feb 2024 22:43:31 -0800 Subject: [PATCH 070/188] Actually chekc the delegation chain --- src/ability/msg/send.rs | 4 +- src/ability/ucan/proxy.rs | 4 +- src/ability/ucan/revoke.rs | 4 +- src/ability/wasm/run.rs | 4 +- src/agent.rs | 72 +++++++++---------- src/delegation.rs | 30 ++++++-- .../{delegatable.rs => delegable.rs} | 2 +- src/delegation/payload.rs | 46 +++++++++++- src/delegation/store.rs | 72 +++++++++++-------- src/invocation.rs | 2 +- src/invocation/payload.rs | 6 +- src/invocation/serializer.rs | 0 src/invocation/store.rs | 37 ++++++++++ src/proof/prove.rs | 1 + src/proof/same.rs | 1 + src/reader.rs | 4 +- src/receipt.rs | 4 +- src/receipt/store.rs | 35 ++++++++- src/signature.rs | 34 +++++---- src/task.rs | 2 +- 20 files changed, 260 insertions(+), 104 deletions(-) rename src/delegation/{delegatable.rs => delegable.rs} (89%) delete mode 100644 src/invocation/serializer.rs create mode 100644 src/invocation/store.rs diff --git a/src/ability/msg/send.rs b/src/ability/msg/send.rs index b7a63b94..1a0b7538 100644 --- a/src/ability/msg/send.rs +++ b/src/ability/msg/send.rs @@ -2,7 +2,7 @@ use crate::{ ability::{arguments, command::Command}, - delegation::Delegatable, + delegation::Delegable, invocation::{promise, Resolvable}, proof::{checkable::Checkable, parentful::Parentful, parents::CheckParents, same::CheckSame}, url as url_newtype, @@ -123,7 +123,7 @@ pub type Builder = Generic, Option, Option>; pub type Promised = Generic, promise::Resolves, promise::Resolves>; -impl Delegatable for Ready { +impl Delegable for Ready { type Builder = Builder; } diff --git a/src/ability/ucan/proxy.rs b/src/ability/ucan/proxy.rs index 04eb5c33..1aa056c1 100644 --- a/src/ability/ucan/proxy.rs +++ b/src/ability/ucan/proxy.rs @@ -1,6 +1,6 @@ use crate::{ ability::{arguments, command::Command}, - delegation::Delegatable, + delegation::Delegable, invocation::Promise, }; use libipld_core::ipld::Ipld; @@ -26,7 +26,7 @@ impl Command for Generic { const COMMAND: &'static str = "ucan/proxy"; } -impl Delegatable for Ready { +impl Delegable for Ready { type Builder = Builder; } diff --git a/src/ability/ucan/revoke.rs b/src/ability/ucan/revoke.rs index 6f9d3fd8..d3da8d3a 100644 --- a/src/ability/ucan/revoke.rs +++ b/src/ability/ucan/revoke.rs @@ -2,7 +2,7 @@ use crate::{ ability::{arguments, command::Command}, - delegation::Delegatable, + delegation::Delegable, invocation::{promise, Resolvable}, proof::{parentless::NoParents, same::CheckSame}, }; @@ -27,7 +27,7 @@ pub type Ready = Generic; impl NoParents for Builder {} -impl Delegatable for Ready { +impl Delegable for Ready { type Builder = Builder; } diff --git a/src/ability/wasm/run.rs b/src/ability/wasm/run.rs index b32f7880..ab08db22 100644 --- a/src/ability/wasm/run.rs +++ b/src/ability/wasm/run.rs @@ -3,7 +3,7 @@ use super::module::Module; use crate::{ ability::{arguments, command::Command}, - delegation::Delegatable, + delegation::Delegable, invocation::{promise, Resolvable}, proof::{parentless::NoParents, same::CheckSame}, }; @@ -31,7 +31,7 @@ impl Command for Generic { /// A variant with all of the required fields filled in pub type Ready = Generic>; -impl Delegatable for Ready { +impl Delegable for Ready { type Builder = Builder; } diff --git a/src/agent.rs b/src/agent.rs index 62968faa..336f9988 100644 --- a/src/agent.rs +++ b/src/agent.rs @@ -1,6 +1,6 @@ use crate::{ ability::command::ToCommand, - delegation::{condition::Condition, Delegatable, Delegation}, + delegation::{condition::Condition, Delegable, Delegation}, did::Did, invocation::Invocation, proof::parents::CheckParents, @@ -18,41 +18,41 @@ impl Agent { // signature::Envelope::new(payload, signature) // } - pub fn invoke( - &self, - delegation: Delegation, - proof_chain: Vec>, // FIXME T must also accept Self and * - ) -> () - where - T::Parents: Delegatable, - { - todo!() - } - - pub fn try_invoke(&self, ability: A) { - todo!() - } - - pub fn revoke( - &self, - delegation: Delegation, - ) -> () -// where -// T::Parents: Delegatable, - { - todo!() - } - - pub fn receive_delegation( - &self, - delegation: Delegation, - ) -> () { - todo!() - } - - pub fn receive_invocation(&self, invocation: Invocation) -> () { - todo!() - } + // pub fn invoke( + // &self, + // delegation: Delegation, + // proof_chain: Vec>, // FIXME T must also accept Self and * + // ) -> () + // where + // T::Parents: Delegable, + // { + // todo!() + // } + + // pub fn try_invoke(&self, ability: A) { + // todo!() + // } + + // pub fn revoke( + // &self, + // delegation: Delegation, + // ) -> () + // // where + // // T::Parents: Delegable, + // { + // todo!() + // } + + // pub fn receive_delegation( + // &self, + // delegation: Delegation, + // ) -> () { + // todo!() + // } + + // pub fn receive_invocation(&self, invocation: Invocation) -> () { + // todo!() + // } // pub fn check(&self, delegation: &Delegation) -> () // FIXME Includes cache } diff --git a/src/delegation.rs b/src/delegation.rs index b3f7259c..7faafc3c 100644 --- a/src/delegation.rs +++ b/src/delegation.rs @@ -1,15 +1,15 @@ -mod delegatable; +mod delegable; mod payload; pub mod condition; pub mod error; pub mod store; -pub use delegatable::Delegatable; +pub use delegable::Delegable; pub use payload::Payload; +use crate::proof::{checkable::Checkable, parents::CheckParents, same::CheckSame}; use condition::Condition; -// use store::IndexedStore; use crate::signature; @@ -21,8 +21,30 @@ use crate::signature; /// FIXME pub type Delegation = signature::Envelope>; +impl CheckSame for Delegation { + type Error = ::Error; + + fn check_same(&self, proof: &Delegation) -> Result<(), Self::Error> { + self.payload.check_same(&proof.payload) + } +} + +impl CheckParents for Delegation { + type Parents = Delegation; + type ParentError = ::ParentError; + + fn check_parent(&self, proof: &Self::Parents) -> Result<(), Self::ParentError> { + self.payload.check_parent(&proof.payload) + } +} + +// // FIXME relax the checkable constraint for this? Or make this an instance of checker? +// impl>, C> Checkable for Delegation { +// type Hierarchy = Parentful, C>; +// } + // FIXME -impl Delegation { +impl Delegation { // FIXME include cache //pub fn check>(&self, store: &S) -> Result<(), ()> { // if let Ok(is_valid) = store.previously_checked(self) { diff --git a/src/delegation/delegatable.rs b/src/delegation/delegable.rs similarity index 89% rename from src/delegation/delegatable.rs rename to src/delegation/delegable.rs index 4bf4fde9..eb93ea1d 100644 --- a/src/delegation/delegatable.rs +++ b/src/delegation/delegable.rs @@ -2,7 +2,7 @@ use crate::{ability::arguments, proof::checkable::Checkable}; use libipld_core::ipld::Ipld; // FIXME require checkable? -pub trait Delegatable: Sized { +pub trait Delegable: Sized { /// A delegation with some arguments filled /// FIXME add more type Builder: TryInto + From + Checkable; diff --git a/src/delegation/payload.rs b/src/delegation/payload.rs index be73086e..65998780 100644 --- a/src/delegation/payload.rs +++ b/src/delegation/payload.rs @@ -12,6 +12,7 @@ use crate::{ nonce::Nonce, proof::{ checkable::Checkable, + parents::CheckParents, prove::{Prove, Success}, same::CheckSame, }, @@ -24,6 +25,7 @@ use serde::{ Deserialize, Serialize, Serializer, }; use std::{collections::BTreeMap, fmt, fmt::Debug}; +use thiserror::Error; use web_time::SystemTime; /// The payload portion of a [`Delegation`][super::Delegation]. @@ -113,12 +115,54 @@ impl Payload { not_before: self.not_before, } } + + pub fn check_time(&self, now: SystemTime) -> Result<(), TimeBoundError> { + if SystemTime::from(self.expiration.clone()) < now { + return Err(TimeBoundError::Expired); + } + + if let Some(nbf) = self.not_before.clone() { + if SystemTime::from(nbf) > now { + return Err(TimeBoundError::NotYetValid); + } + } + + Ok(()) + } +} + +// FIXME move to time.rs +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Deserialize, Error)] +pub enum TimeBoundError { + #[error("The UCAN delegation has expired")] + Expired, + + #[error("The UCAN delegation is not yet valid")] + NotYetValid, } impl Capsule for Payload { const TAG: &'static str = "ucan/d/1.0.0-rc.1"; } +impl CheckSame for Payload { + type Error = ::Error; + + fn check_same(&self, proof: &Payload) -> Result<(), Self::Error> { + self.delegated_ability.check_same(&proof.delegated_ability) + } +} + +impl CheckParents for Payload { + type Parents = Payload; + type ParentError = ::ParentError; + + fn check_parent(&self, proof: &Self::Parents) -> Result<(), Self::ParentError> { + self.delegated_ability + .check_parent(&proof.delegated_ability) + } +} + impl Serialize for Payload where Ipld: From, @@ -367,7 +411,7 @@ struct Acc { } impl Acc { - // FIXME this should move to Delegatable? + // FIXME this should move to Delegable? fn step<'a, C: Condition>( &self, proof: &Payload, diff --git a/src/delegation/store.rs b/src/delegation/store.rs index 31d8dd8e..bbd2c48d 100644 --- a/src/delegation/store.rs +++ b/src/delegation/store.rs @@ -1,8 +1,10 @@ -use super::{condition::Condition, delegatable::Delegatable, Delegation}; -use crate::did::Did; +use super::{condition::Condition, Delegable, Delegation}; +use crate::{ + did::Did, + proof::{checkable::Checkable, prove::Prove}, +}; use libipld_core::cid::Cid; use nonempty::NonEmpty; -use serde::{Deserialize, Serialize}; use std::{ collections::{BTreeMap, BTreeSet}, ops::ControlFlow, @@ -10,20 +12,23 @@ use std::{ use thiserror::Error; use web_time::SystemTime; -pub trait Store { +// NOTE the T here is the builder... FIXME add one layer up and call T::Builder? May be confusing? +pub trait Store { type Error; - fn get_by_cid(&self, cid: &Cid) -> Result<&Delegation, Self::Error>; + fn get(&self, cid: &Cid) -> Result<&Delegation, Self::Error>; + + fn insert(&mut self, cid: Cid, delegation: Delegation); - fn insert(&mut self, cid: &Cid, delegation: Delegation); - fn revoke(&mut self, cid: &Cid); + fn revoke(&mut self, cid: Cid); fn get_chain( &self, aud: &Did, subject: &Did, + builder: &B, now: &SystemTime, - ) -> Result)>, Self::Error>; + ) -> Result)>, Self::Error>; } #[cfg_attr(doc, aquamarine::aquamarine)] @@ -83,45 +88,52 @@ pub trait Store { /// linkStyle 1 stroke:orange; /// ``` #[derive(Debug, Clone, PartialEq)] -pub struct MemoryStore { - ucans: BTreeMap>, +pub struct MemoryStore { + ucans: BTreeMap>, index: BTreeMap>>, revocations: BTreeSet, } +// FIXME extract #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Error)] #[error("Delegation not found")] pub struct NotFound; // FIXME check that UCAN is valid -impl Store for MemoryStore { +impl Store for MemoryStore { type Error = NotFound; - fn insert(&mut self, cid: &Cid, delegation: Delegation) { + fn get(&self, cid: &Cid) -> Result<&Delegation, Self::Error> { + self.ucans.get(cid).ok_or(NotFound) + } + + fn insert(&mut self, cid: Cid, delegation: Delegation) { self.index .entry(delegation.payload.subject.clone()) .or_default() .entry(delegation.payload.audience.clone()) .or_default() - .insert(cid.clone()); + .insert(cid); - self.ucans.insert(cid.clone(), delegation); - } + let hierarchy: Delegation = Delegation { + signature: delegation.signature, + payload: delegation.payload.map_ability(Into::into), + }; - fn revoke(&mut self, cid: &Cid) { - self.revocations.insert(cid.clone()); + self.ucans.insert(cid.clone(), hierarchy); } - fn get_by_cid(&self, cid: &Cid) -> Result<&Delegation, Self::Error> { - self.ucans.get(cid).ok_or(NotFound) + fn revoke(&mut self, cid: Cid) { + self.revocations.insert(cid); } fn get_chain( &self, aud: &Did, subject: &Did, + builder: &B, now: &SystemTime, - ) -> Result)>, NotFound> { + ) -> Result)>, NotFound> { #[derive(PartialEq)] enum Status { Complete, @@ -129,9 +141,11 @@ impl Store for MemoryStore { NoPath, } + // FIXME move these into an Acc let mut status = Status::Looking; let mut target_aud = aud; let mut chain = vec![]; + let mut args: &B::Hierarchy = &builder.clone().into(); let delegation_subtree = self .index @@ -141,19 +155,19 @@ impl Store for MemoryStore { while status == Status::Looking { let found = delegation_subtree.iter().try_for_each(|cid| { - if self.revocations.contains(&cid) { - return ControlFlow::Continue(()); - } - if let Some(d) = self.ucans.get(cid) { - if SystemTime::from(d.payload.expiration.clone()) < *now { + if self.revocations.contains(&cid) { return ControlFlow::Continue(()); } - if let Some(nbf) = &d.payload.not_before { - if SystemTime::from(nbf.clone()) > *now { - return ControlFlow::Continue(()); - } + if d.payload.check_time(*now).is_err() { + return ControlFlow::Continue(()); + } + + if args.check(&d.payload.delegated_ability).is_ok() { + args = &d.payload.delegated_ability; + } else { + return ControlFlow::Continue(()); } chain.push((cid, d)); diff --git a/src/invocation.rs b/src/invocation.rs index e93aa57a..9cfca04f 100644 --- a/src/invocation.rs +++ b/src/invocation.rs @@ -1,8 +1,8 @@ mod payload; mod resolvable; -mod serializer; pub mod promise; +pub mod store; pub use payload::{Payload, Promised}; pub use resolvable::Resolvable; diff --git a/src/invocation/payload.rs b/src/invocation/payload.rs index 216fab5a..310525f6 100644 --- a/src/invocation/payload.rs +++ b/src/invocation/payload.rs @@ -6,7 +6,7 @@ use crate::{ delegation::{ condition::Condition, error::{DelegationError, EnvelopeError}, - Delegatable, + Delegable, }, did::Did, nonce::Nonce, @@ -52,7 +52,7 @@ impl Payload { now: SystemTime, ) -> Result<(), DelegationError<<::Hierarchy as Prove>::Error>> where - T: Delegatable, + T: Delegable, T::Builder: Clone + Checkable + Prove + Into>, ::Hierarchy: Clone + Into>, { @@ -65,7 +65,7 @@ impl Capsule for Payload { const TAG: &'static str = "ucan/i/1.0.0-rc.1"; } -impl From> for delegation::Payload { +impl From> for delegation::Payload { fn from(payload: Payload) -> Self { delegation::Payload { issuer: payload.issuer, diff --git a/src/invocation/serializer.rs b/src/invocation/serializer.rs deleted file mode 100644 index e69de29b..00000000 diff --git a/src/invocation/store.rs b/src/invocation/store.rs new file mode 100644 index 00000000..c153cc13 --- /dev/null +++ b/src/invocation/store.rs @@ -0,0 +1,37 @@ +use super::Invocation; +use libipld_core::cid::Cid; +use std::collections::BTreeMap; +use thiserror::Error; + +pub trait Store { + type Error; + + fn get(&self, cid: &Cid) -> Result<&Invocation, Self::Error>; + + fn put(&mut self, cid: Cid, invocation: Invocation) -> Result<(), Self::Error>; + + fn has(&self, cid: &Cid) -> Result { + Ok(self.get(cid).is_ok()) + } +} + +pub struct MemoryStore { + store: BTreeMap>, +} + +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Error)] +#[error("Delegation not found")] +pub struct NotFound; + +impl Store for MemoryStore { + type Error = NotFound; + + fn get(&self, cid: &Cid) -> Result<&Invocation, Self::Error> { + self.store.get(cid).ok_or(NotFound) + } + + fn put(&mut self, cid: Cid, invocation: Invocation) -> Result<(), Self::Error> { + self.store.insert(cid, invocation); + Ok(()) + } +} diff --git a/src/proof/prove.rs b/src/proof/prove.rs index b97589ba..9c2a517c 100644 --- a/src/proof/prove.rs +++ b/src/proof/prove.rs @@ -6,6 +6,7 @@ use super::internal::Checker; pub(crate) trait Prove: Checker { type Error; + // FIXME make the same as the trait name (prove) fn check(&self, proof: &Self) -> Result; } diff --git a/src/proof/same.rs b/src/proof/same.rs index 2e295b68..5ca43d71 100644 --- a/src/proof/same.rs +++ b/src/proof/same.rs @@ -47,6 +47,7 @@ pub trait CheckSame { /// it has violated the delegation chain rules. fn check_same(&self, proof: &Self) -> Result<(), Self::Error>; } + impl CheckSame for Did { type Error = Unequal; diff --git a/src/reader.rs b/src/reader.rs index c918ee35..c2614569 100644 --- a/src/reader.rs +++ b/src/reader.rs @@ -5,7 +5,7 @@ use crate::{ arguments, command::{ParseAbility, ParseAbilityError, ToCommand}, }, - delegation::Delegatable, + delegation::Delegable, invocation::Resolvable, }; use libipld_core::ipld::Ipld; @@ -177,7 +177,7 @@ impl ParseAbility for Reader { } } -/// A helper newtype that marks a value as being a [`Delegatable::Builder`]. +/// A helper newtype that marks a value as being a [`Delegable::Builder`]. /// /// The is often used as: /// diff --git a/src/receipt.rs b/src/receipt.rs index 497ebc76..a76bd704 100644 --- a/src/receipt.rs +++ b/src/receipt.rs @@ -2,11 +2,11 @@ mod payload; mod responds; -mod store; + +pub mod store; pub use payload::Payload; pub use responds::Responds; -pub use store::Store; use crate::signature; diff --git a/src/receipt/store.rs b/src/receipt/store.rs index 66feb0d9..6f20edd1 100644 --- a/src/receipt/store.rs +++ b/src/receipt/store.rs @@ -1,6 +1,8 @@ use super::{Receipt, Responds}; use crate::task; use libipld_core::ipld::Ipld; +use std::{collections::BTreeMap, fmt}; +use thiserror::Error; /// A store for [`Receipt`]s indexed by their [`task::Id`]s. pub trait Store { @@ -8,12 +10,41 @@ pub trait Store { type Error; /// Retrieve a [`Receipt`] by its [`task::Id`]. - fn get(id: task::Id) -> Result, Self::Error> + fn get<'a>(&self, id: &task::Id) -> Result<&Receipt, Self::Error> where ::Success: TryFrom; /// Store a [`Receipt`] by its [`task::Id`]. - fn put_keyed(id: task::Id, receipt: Receipt) -> Result<(), Self::Error> + fn put(&mut self, id: task::Id, receipt: Receipt) -> Result<(), Self::Error> where ::Success: Into; } + +#[derive(Debug, Clone, PartialEq)] +pub struct MemoryStore +where + T::Success: fmt::Debug + Clone + PartialEq, +{ + store: BTreeMap>, +} + +// FIXME extract +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Error)] +#[error("Delegation not found")] +pub struct NotFound; + +impl Store for MemoryStore +where + ::Success: TryFrom + Into + Clone + fmt::Debug + PartialEq, +{ + type Error = NotFound; + + fn get(&self, id: &task::Id) -> Result<&Receipt, Self::Error> { + self.store.get(id).ok_or(NotFound) + } + + fn put(&mut self, id: task::Id, receipt: Receipt) -> Result<(), Self::Error> { + self.store.insert(id, receipt); + Ok(()) + } +} diff --git a/src/signature.rs b/src/signature.rs index 322aac62..6846004c 100644 --- a/src/signature.rs +++ b/src/signature.rs @@ -9,7 +9,7 @@ use std::collections::BTreeMap; #[derive(Debug, Clone, PartialEq)] pub struct Envelope { /// The signture of the `payload`. - pub sig: Signature, + pub signature: Signature, /// The payload that's being signed over. pub payload: T, @@ -21,7 +21,7 @@ pub struct Envelope { pub enum Signature { One(Vec), Batch { - sig: Vec, + signature: Vec, merkle_proof: Vec>, }, } @@ -79,13 +79,16 @@ pub enum Signature { // } impl From<&Signature> for Ipld { - fn from(sig: &Signature) -> Self { - match sig { + fn from(signature: &Signature) -> Self { + match signature { Signature::One(sig) => sig.clone().into(), - Signature::Batch { sig, merkle_proof } => { + Signature::Batch { + signature, + merkle_proof, + } => { let mut map = BTreeMap::new(); let proof: Vec = merkle_proof.into_iter().map(|p| p.clone().into()).collect(); - map.insert("sig".into(), sig.clone().into()); + map.insert("sig".into(), signature.clone().into()); map.insert("prf".into(), proof.into()); map.into() } @@ -94,13 +97,16 @@ impl From<&Signature> for Ipld { } impl From for Ipld { - fn from(sig: Signature) -> Self { - match sig { + fn from(signature: Signature) -> Self { + match signature { Signature::One(sig) => sig.into(), - Signature::Batch { sig, merkle_proof } => { + Signature::Batch { + signature, + merkle_proof, + } => { let mut map = BTreeMap::new(); let proof: Vec = merkle_proof.into_iter().map(|p| p.into()).collect(); - map.insert("sig".into(), sig.into()); + map.insert("sig".into(), signature.into()); map.insert("prf".into(), proof.into()); map.into() } @@ -110,12 +116,12 @@ impl From for Ipld { // FIXME Store or BTreeMap? Also eliminate that Clone constraint impl + Clone> From<&Envelope> for Ipld { - fn from(Envelope { sig, payload }: &Envelope) -> Self { + fn from(Envelope { signature, payload }: &Envelope) -> Self { let mut inner = BTreeMap::new(); inner.insert(T::TAG.into(), payload.clone().into()); // FIXME should be a link let mut map = BTreeMap::new(); - map.insert("sig".into(), sig.into()); + map.insert("sig".into(), signature.into()); map.insert("pld".into(), Ipld::Map(inner)); Ipld::Map(map) @@ -123,12 +129,12 @@ impl + Clone> From<&Envelope> for Ipld { } impl + Clone> From> for Ipld { - fn from(Envelope { sig, payload }: Envelope) -> Self { + fn from(Envelope { signature, payload }: Envelope) -> Self { let mut inner = BTreeMap::new(); inner.insert(T::TAG.into(), payload.clone().into()); // FIXME should be a link let mut map = BTreeMap::new(); - map.insert("sig".into(), sig.into()); + map.insert("sig".into(), signature.into()); map.insert("pld".into(), Ipld::Map(inner)); Ipld::Map(map) diff --git a/src/task.rs b/src/task.rs index f41cbc54..ed80fec7 100644 --- a/src/task.rs +++ b/src/task.rs @@ -68,7 +68,7 @@ impl From for Cid { } /// The unique identifier for a [`Task`]. -#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] #[serde(transparent)] pub struct Id { /// The CID of the [`Task`]. From d5e0b134cea3d0dc55cad2ed784687d5a0da32a2 Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Mon, 12 Feb 2024 00:09:06 -0800 Subject: [PATCH 071/188] On to the crypto --- src/agent.rs | 189 +++++++++++++++++++++++++++--------- src/delegation.rs | 28 ------ src/delegation/delegable.rs | 6 +- src/delegation/payload.rs | 31 +++--- src/delegation/store.rs | 8 +- src/invocation/payload.rs | 2 +- src/invocation/store.rs | 1 + src/proof/checkable.rs | 2 +- src/time.rs | 12 +++ 9 files changed, 180 insertions(+), 99 deletions(-) diff --git a/src/agent.rs b/src/agent.rs index 336f9988..f245b0e1 100644 --- a/src/agent.rs +++ b/src/agent.rs @@ -1,58 +1,153 @@ use crate::{ - ability::command::ToCommand, + delegation, delegation::{condition::Condition, Delegable, Delegation}, did::Did, - invocation::Invocation, - proof::parents::CheckParents, + invocation, + nonce::Nonce, + proof::checkable::Checkable, + receipt, + receipt::Responds, + time::JsTime, }; +use libipld_core::ipld::Ipld; +use nonempty::NonEmpty; +use std::{collections::BTreeMap, marker::PhantomData}; +use web_time::SystemTime; -pub struct Agent { +// FIXME move proofs to under delegation? + +#[derive(Debug, Clone, PartialEq)] +pub struct Agent< + T: Delegable + Responds, + C: Condition, + S: delegation::store::Store + + invocation::store::Store + + receipt::store::Store, +> { pub did: Did, // pub key: signature::Key, pub store: S, + pub _phantom: PhantomData<(T, C)>, } -impl Agent { - // pub fn delegate(&self, payload: Payload) -> Delegation { - // let signature = self.key.sign(payload); - // signature::Envelope::new(payload, signature) - // } - - // pub fn invoke( - // &self, - // delegation: Delegation, - // proof_chain: Vec>, // FIXME T must also accept Self and * - // ) -> () - // where - // T::Parents: Delegable, - // { - // todo!() - // } - - // pub fn try_invoke(&self, ability: A) { - // todo!() - // } - - // pub fn revoke( - // &self, - // delegation: Delegation, - // ) -> () - // // where - // // T::Parents: Delegable, - // { - // todo!() - // } - - // pub fn receive_delegation( - // &self, - // delegation: Delegation, - // ) -> () { - // todo!() - // } - - // pub fn receive_invocation(&self, invocation: Invocation) -> () { - // todo!() - // } - - // pub fn check(&self, delegation: &Delegation) -> () // FIXME Includes cache +impl< + T: Delegable + Responds, + C: Condition + Clone, + S: delegation::store::Store + + invocation::store::Store + + receipt::store::Store, + > Agent +{ + fn new(did: Did, store: S) -> Self { + Self { + did, + store, + _phantom: PhantomData, + } + } + + pub fn delegate( + &self, + audience: Did, + subject: Did, + ability_builder: T::Builder, + new_conditions: Vec, + metadata: BTreeMap, + expiration: JsTime, + not_before: Option, + ) -> Result, ()> { + // FIXME check if possible in store first; + + let conditions = if subject == self.did { + new_conditions + } else { + let mut conds = self + .store + .get_chain(&self.did, &subject, &ability_builder, &SystemTime::now()) + .map_err(|_| ())? // FIXME + .first() + .1 + .payload + .conditions; + + let mut new = new_conditions; + conds.append(&mut new); + conds + }; + + let mut salt = self.did.clone().to_string().into_bytes(); + + let payload = delegation::Payload { + issuer: self.did.clone(), + audience, + subject, + ability_builder, + conditions, + metadata, + nonce: Nonce::generate_16(&mut salt), + expiration: expiration.into(), + not_before: not_before.map(Into::into), + }; + + Ok(self.sign_delegation(payload)) + } + + pub fn sign_delegation( + &self, + payload: delegation::Payload, + ) -> delegation::Delegation { + // FIXME check if possible in store first; + let signature = todo!(); // self.key.sign(payload); + Delegation { payload, signature } + } } + +// impl Agent { +// } +// +// +// +// +// +// pub fn delegate(&self, payload: Payload) -> Delegation { +// let signature = self.key.sign(payload); +// signature::Envelope::new(payload, signature) +// } + +// pub fn invoke( +// &self, +// delegation: Delegation, +// proof_chain: Vec>, // FIXME T must also accept Self and * +// ) -> () +// where +// T::Parents: Delegable, +// { +// todo!() +// } + +// pub fn try_invoke(&self, ability: A) { +// todo!() +// } + +// pub fn revoke( +// &self, +// delegation: Delegation, +// ) -> () +// // where +// // T::Parents: Delegable, +// { +// todo!() +// } + +// pub fn receive_delegation( +// &self, +// delegation: Delegation, +// ) -> () { +// todo!() +// } + +// pub fn receive_invocation(&self, invocation: Invocation) -> () { +// todo!() +// } + +// pub fn check(&self, delegation: &Delegation) -> () // FIXME Includes cache diff --git a/src/delegation.rs b/src/delegation.rs index 7faafc3c..47d51096 100644 --- a/src/delegation.rs +++ b/src/delegation.rs @@ -37,31 +37,3 @@ impl CheckParents for Delegation { self.payload.check_parent(&proof.payload) } } - -// // FIXME relax the checkable constraint for this? Or make this an instance of checker? -// impl>, C> Checkable for Delegation { -// type Hierarchy = Parentful, C>; -// } - -// FIXME -impl Delegation { - // FIXME include cache - //pub fn check>(&self, store: &S) -> Result<(), ()> { - // if let Ok(is_valid) = store.previously_checked(self) { - // if is_valid { - // return Ok(()); - // } - // } - - // if let Ok(chains) = store.chains_for(self) { - // for chain in chains { - // todo!() - // // if self.check_self(self).is_ok() { - // // return Ok(()); - // // } - // } - // } - - // Err(()) - //} -} diff --git a/src/delegation/delegable.rs b/src/delegation/delegable.rs index eb93ea1d..925aee08 100644 --- a/src/delegation/delegable.rs +++ b/src/delegation/delegable.rs @@ -1,9 +1,7 @@ -use crate::{ability::arguments, proof::checkable::Checkable}; -use libipld_core::ipld::Ipld; +use crate::proof::checkable::Checkable; -// FIXME require checkable? pub trait Delegable: Sized { /// A delegation with some arguments filled - /// FIXME add more + /// FIXME add more text type Builder: TryInto + From + Checkable; } diff --git a/src/delegation/payload.rs b/src/delegation/payload.rs index 65998780..78fac30e 100644 --- a/src/delegation/payload.rs +++ b/src/delegation/payload.rs @@ -60,9 +60,9 @@ pub struct Payload { /// A delegatable ability chain. /// /// Note that this should be is some [Proof::Hierarchy] - pub delegated_ability: D, + pub ability_builder: D, - /// Any [`Condition`]s on the `delegated_ability`. + /// Any [`Condition`]s on the `ability_builder`. pub conditions: Vec, /// Extensible, free-form fields. @@ -93,7 +93,7 @@ impl Payload { issuer: self.issuer, subject: self.subject, audience: self.audience, - delegated_ability: f(self.delegated_ability), + ability_builder: f(self.ability_builder), conditions: self.conditions, metadata: self.metadata, nonce: self.nonce, @@ -107,7 +107,7 @@ impl Payload { issuer: self.issuer, subject: self.subject, audience: self.audience, - delegated_ability: self.delegated_ability, + ability_builder: self.ability_builder, conditions: self.conditions.into_iter().map(f).collect(), metadata: self.metadata, nonce: self.nonce, @@ -149,7 +149,7 @@ impl CheckSame for Payload { type Error = ::Error; fn check_same(&self, proof: &Payload) -> Result<(), Self::Error> { - self.delegated_ability.check_same(&proof.delegated_ability) + self.ability_builder.check_same(&proof.ability_builder) } } @@ -158,8 +158,7 @@ impl CheckParents for Payload { type ParentError = ::ParentError; fn check_parent(&self, proof: &Self::Parents) -> Result<(), Self::ParentError> { - self.delegated_ability - .check_parent(&proof.delegated_ability) + self.ability_builder.check_parent(&proof.ability_builder) } } @@ -181,11 +180,11 @@ where state.serialize_field("exp", &self.expiration)?; state.serialize_field("nbf", &self.not_before)?; - state.serialize_field("cmd", &self.delegated_ability.to_command())?; + state.serialize_field("cmd", &self.ability_builder.to_command())?; state.serialize_field( "args", - &arguments::Named::from(self.delegated_ability.clone()), + &arguments::Named::from(self.ability_builder.clone()), )?; state.serialize_field( @@ -312,8 +311,8 @@ impl<'de, T: ParseAbility + Deserialize<'de> + ToCommand, C: Condition + Deseria let cmd: String = command.ok_or(de::Error::missing_field("cmd"))?; let args = arguments.ok_or(de::Error::missing_field("args"))?; - let delegated_ability = ::try_parse(cmd.as_str(), &args) - .map_err(|e| { + let ability_builder = + ::try_parse(cmd.as_str(), &args).map_err(|e| { de::Error::custom(format!( "Unable to parse ability field for {} because {}", cmd, e @@ -328,7 +327,7 @@ impl<'de, T: ParseAbility + Deserialize<'de> + ToCommand, C: Condition + Deseria metadata: metadata.ok_or(de::Error::missing_field("meta"))?, nonce: nonce.ok_or(de::Error::missing_field("nonce"))?, expiration: expiration.ok_or(de::Error::missing_field("exp"))?, - delegated_ability, + ability_builder, not_before, }) } @@ -373,10 +372,10 @@ impl Payload { let start: Acc = Acc { issuer: self.issuer.clone(), subject: self.subject.clone(), - hierarchy: T::Hierarchy::from(self.delegated_ability.clone()), + hierarchy: T::Hierarchy::from(self.ability_builder.clone()), }; - let args: arguments::Named = self.delegated_ability.clone().into(); + let args: arguments::Named = self.ability_builder.clone().into(); proofs.into_iter().fold(Ok(start), |prev, proof| { if let Ok(prev_) = prev { @@ -390,7 +389,7 @@ impl Payload { Success::Proven => Acc { issuer: proof.issuer.clone(), subject: proof.subject.clone(), - hierarchy: proof.delegated_ability.clone(), // FIXME double check + hierarchy: proof.ability_builder.clone(), // FIXME double check }, } }) @@ -452,7 +451,7 @@ impl Acc { } self.hierarchy - .check(&proof.delegated_ability.clone()) + .check(&proof.ability_builder.clone()) .map_err(DelegationError::SemanticError) } } diff --git a/src/delegation/store.rs b/src/delegation/store.rs index bbd2c48d..38c861d0 100644 --- a/src/delegation/store.rs +++ b/src/delegation/store.rs @@ -29,6 +29,10 @@ pub trait Store { builder: &B, now: &SystemTime, ) -> Result)>, Self::Error>; + + fn can_delegate(&self, iss: &Did, aud: &Did, builder: &B, now: &SystemTime) -> bool { + self.get_chain(aud, iss, builder, now).is_ok() + } } #[cfg_attr(doc, aquamarine::aquamarine)] @@ -164,8 +168,8 @@ impl Store for MemoryStore From> for delegation::Payload { } } +#[derive(Debug, Clone, PartialEq)] pub struct MemoryStore { store: BTreeMap>, } diff --git a/src/proof/checkable.rs b/src/proof/checkable.rs index 952efc5d..584c1ee7 100644 --- a/src/proof/checkable.rs +++ b/src/proof/checkable.rs @@ -2,7 +2,7 @@ use super::{prove::Prove, same::CheckSame}; -// FIXME mo ve to Delegatbel? +// FIXME move to Delegatbel? /// Plug a type into the delegation checking pipeline pub trait Checkable: CheckSame + Sized { diff --git a/src/time.rs b/src/time.rs index 50a8e95c..e697f2fd 100644 --- a/src/time.rs +++ b/src/time.rs @@ -30,6 +30,18 @@ pub enum Timestamp { Postel(SystemTime), } +impl From for Timestamp { + fn from(js_time: JsTime) -> Self { + Timestamp::JsSafe(js_time) + } +} + +impl From for Timestamp { + fn from(sys_time: SystemTime) -> Self { + Timestamp::Postel(sys_time) + } +} + impl From for Ipld { fn from(timestamp: Timestamp) -> Self { match timestamp { From ef394312847a86d88d00be6b880942af7d876579 Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Mon, 12 Feb 2024 13:39:36 -0800 Subject: [PATCH 072/188] OKay I lied; I wrote the delegation agent instead --- src/agent.rs | 2 + src/delegation.rs | 65 ++++++++++++++-- src/delegation/:50 | 117 ++++++++++++++++++++++++++++ src/delegation/agent.rs | 124 ++++++++++++++++++++++++++++++ src/delegation/payload.rs | 13 +--- src/delegation/store.rs | 157 ++++++++++++++++++++------------------ src/lib.rs | 2 +- src/signature.rs | 15 +++- src/time.rs | 10 +++ 9 files changed, 411 insertions(+), 94 deletions(-) create mode 100644 src/delegation/:50 create mode 100644 src/delegation/agent.rs diff --git a/src/agent.rs b/src/agent.rs index f245b0e1..c26be6c1 100644 --- a/src/agent.rs +++ b/src/agent.rs @@ -100,6 +100,8 @@ impl< let signature = todo!(); // self.key.sign(payload); Delegation { payload, signature } } + + pub fn recieve_delegation() {} } // impl Agent { diff --git a/src/delegation.rs b/src/delegation.rs index 47d51096..deebfa8e 100644 --- a/src/delegation.rs +++ b/src/delegation.rs @@ -1,17 +1,26 @@ -mod delegable; -mod payload; - pub mod condition; pub mod error; pub mod store; +mod agent; +mod delegable; +mod payload; + +pub use agent::Agent; pub use delegable::Delegable; pub use payload::Payload; -use crate::proof::{checkable::Checkable, parents::CheckParents, same::CheckSame}; +use crate::{ + did::Did, + nonce::Nonce, + proof::{checkable::Checkable, parents::CheckParents, same::CheckSame}, + signature, + time::{TimeBoundError, Timestamp}, +}; use condition::Condition; - -use crate::signature; +use libipld_core::ipld::Ipld; +use std::{collections::BTreeMap, convert::AsRef}; +use web_time::SystemTime; /// A [`Delegation`] is a signed delegation [`Payload`] /// @@ -21,6 +30,50 @@ use crate::signature; /// FIXME pub type Delegation = signature::Envelope>; +// FIXME checkable -> provable? + +impl Delegation { + pub fn issuer(&self) -> &Did { + &self.payload.issuer + } + + pub fn subject(&self) -> &Did { + &self.payload.subject + } + + pub fn audience(&self) -> &Did { + &self.payload.audience + } + + pub fn ability_builder(&self) -> &B { + &self.payload.ability_builder + } + + pub fn conditions(&self) -> &[C] { + &self.payload.conditions + } + + pub fn metadata(&self) -> &BTreeMap { + &self.payload.metadata + } + + pub fn nonce(&self) -> &Nonce { + &self.payload.nonce + } + + pub fn not_before(&self) -> Option<&Timestamp> { + self.payload.not_before.as_ref() + } + + pub fn expiration(&self) -> &Timestamp { + &self.payload.expiration + } + + pub fn check_time(&self, now: SystemTime) -> Result<(), TimeBoundError> { + self.payload.check_time(now) + } +} + impl CheckSame for Delegation { type Error = ::Error; diff --git a/src/delegation/:50 b/src/delegation/:50 new file mode 100644 index 00000000..c74e50a7 --- /dev/null +++ b/src/delegation/:50 @@ -0,0 +1,117 @@ +use super::{condition::Condition, payload::Payload, store::Store, Delegation}; +use crate::{did::Did, nonce::Nonce, proof::checkable::Checkable, time::JsTime}; +use libipld_core::{cid::Cid, ipld::Ipld}; +use std::{collections::BTreeMap, marker::PhantomData}; +use thiserror::Error; +use web_time::SystemTime; + +pub struct Agent<'a, B: Checkable, C: Condition, S: Store> { + pub did: &'a Did, + pub store: &'a mut S, + _marker: PhantomData<(B, C)>, +} + +// FIXME show example of multiple hierarchies of "all things accepted" +// delegating down to inner versions of this + +impl<'a, B: Checkable, C: Condition, S: Store> Agent<'a, B, C, S> { + pub fn new(did: &'a Did, store: &'a mut S) -> Self { + Self { + did, + store, + _marker: PhantomData, + } + } + + pub fn delegate( + &self, + cid: &Cid, // FIXME remove and generate from the capsule header? + audience: Did, + subject: Did, + ability_builder: B, + new_conditions: Vec, + metadata: BTreeMap, + expiration: JsTime, + not_before: Option, + ) -> Result, DelegateError> { + if !self + .store + .can_delegate(self.did, &audience, &ability_builder, &SystemTime::now()) + { + return Err(DelegateError::ProofsNotFound); + } + + let conditions = if subject == *self.did { + new_conditions + } else { + let mut conds = self + .store + .get_chain(&self.did, &subject, &ability_builder, &SystemTime::now()) + .map_err(|_| ())? // FIXME + .first() + .1 + .payload + .conditions; + + let mut new = new_conditions; + conds.append(&mut new); + conds + }; + + let mut salt = self.did.clone().to_string().into_bytes(); + + let payload = Payload { + issuer: self.did.clone(), + audience, + subject, + ability_builder, + conditions, + metadata, + nonce: Nonce::generate_16(&mut salt), + expiration: expiration.into(), + not_before: not_before.map(Into::into), + }; + + Ok(self.sign(payload)) + } + + pub fn recieve( + &self, + cid: &Cid, // FIXME remove and generate from the capsule header? + delegation: Delegation, + ) -> Result<(), ReceiveError<'a, >::Error>> { + if self.store.get(cid).is_ok() { + return Ok(()); + } + + if delegation.audience() != *self.did { + return Err(ReceiveError::WrongAudience(delegation.audience())); + } + + delegation + .validate_signature() + .map_err(|_| ReceiveError::InvalidSignature(cid))?; + + self.store + .insert(self.store, delegation) + .map_err(Into::into) + } +} + +#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Error)] +pub enum DelegateError { + #[error("The current agent does not have the necessary proofs to delegate.")] + ProofsNotFound, +} + +#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Error)] +pub enum ReceiveError<'a, StoreErr> { + #[error("The current agent ({0}) is not the intended audience of the delegation.")] + WrongAudience(&'a Did), + + #[error("Signature for UCAN with CID {0} is invalid.")] + InvalidSignature(&'a Cid), + + #[error(transparent)] + StoreError(#[from] StoreErr), +} diff --git a/src/delegation/agent.rs b/src/delegation/agent.rs new file mode 100644 index 00000000..ba0e8b4c --- /dev/null +++ b/src/delegation/agent.rs @@ -0,0 +1,124 @@ +use super::{condition::Condition, payload::Payload, store::Store, Delegation}; +use crate::{did::Did, nonce::Nonce, proof::checkable::Checkable, time::JsTime}; +use libipld_core::{cid::Cid, ipld::Ipld}; +use std::{collections::BTreeMap, marker::PhantomData}; +use thiserror::Error; +use web_time::SystemTime; + +pub struct Agent<'a, B: Checkable, C: Condition, S: Store> { + pub did: &'a Did, + pub store: &'a mut S, + _marker: PhantomData<(B, C)>, +} + +// FIXME show example of multiple hierarchies of "all things accepted" +// delegating down to inner versions of this + +impl<'a, B: Checkable, C: Condition + Clone, S: Store> Agent<'a, B, C, S> { + pub fn new(did: &'a Did, store: &'a mut S) -> Self { + Self { + did, + store, + _marker: PhantomData, + } + } + + pub fn delegate( + &self, + audience: Did, + subject: Did, + ability_builder: B, + new_conditions: Vec, + metadata: BTreeMap, + expiration: JsTime, + not_before: Option, + ) -> Result, DelegateError<>::Error>> { + let mut salt = self.did.clone().to_string().into_bytes(); + let nonce = Nonce::generate_16(&mut salt); + + if subject == *self.did { + let payload = Payload { + issuer: self.did.clone(), + audience, + subject, + ability_builder, + metadata, + nonce, + expiration: expiration.into(), + not_before: not_before.map(Into::into), + conditions: new_conditions, + }; + + // FIXME add signer info + return Ok(Delegation::sign(payload)); + } + + let to_delegate = &self + .store + .get_chain(&self.did, &subject, &ability_builder, &SystemTime::now()) + .map_err(DelegateError::StoreError)? + .ok_or(DelegateError::ProofsNotFound)? + .first() + .1 + .payload; + + let mut conditions = to_delegate.conditions.clone(); + conditions.append(&mut new_conditions.clone()); + + let payload = Payload { + issuer: self.did.clone(), + audience, + subject, + ability_builder, + conditions, + metadata, + nonce, + expiration: expiration.into(), + not_before: not_before.map(Into::into), + }; + + // FIXME add signing material + Ok(Delegation::sign(payload)) + } + + pub fn recieve( + &mut self, + cid: Cid, // FIXME remove and generate from the capsule header? + delegation: Delegation, + ) -> Result<(), ReceiveError<>::Error>> { + if self.store.get(&cid).is_ok() { + return Ok(()); + } + + if delegation.audience() != self.did { + return Err(ReceiveError::WrongAudience(delegation.audience().clone())); + } + + delegation + .validate_signature() + .map_err(|_| ReceiveError::InvalidSignature(cid))?; + + self.store.insert(cid, delegation).map_err(Into::into) + } +} + +#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Error)] +pub enum DelegateError { + #[error("The current agent does not have the necessary proofs to delegate.")] + ProofsNotFound, + + #[error(transparent)] + StoreError(#[from] StoreErr), +} + +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Error)] +pub enum ReceiveError { + #[error("The current agent ({0}) is not the intended audience of the delegation.")] + WrongAudience(Did), + + #[error("Signature for UCAN with CID {0} is invalid.")] + InvalidSignature(Cid), + + #[error(transparent)] + StoreError(#[from] StoreErr), +} diff --git a/src/delegation/payload.rs b/src/delegation/payload.rs index 78fac30e..369e6d5c 100644 --- a/src/delegation/payload.rs +++ b/src/delegation/payload.rs @@ -16,7 +16,7 @@ use crate::{ prove::{Prove, Success}, same::CheckSame, }, - time::Timestamp, + time::{TimeBoundError, Timestamp}, }; use libipld_core::{error::SerdeError, ipld::Ipld, serde as ipld_serde}; use serde::{ @@ -25,7 +25,6 @@ use serde::{ Deserialize, Serialize, Serializer, }; use std::{collections::BTreeMap, fmt, fmt::Debug}; -use thiserror::Error; use web_time::SystemTime; /// The payload portion of a [`Delegation`][super::Delegation]. @@ -131,16 +130,6 @@ impl Payload { } } -// FIXME move to time.rs -#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Deserialize, Error)] -pub enum TimeBoundError { - #[error("The UCAN delegation has expired")] - Expired, - - #[error("The UCAN delegation is not yet valid")] - NotYetValid, -} - impl Capsule for Payload { const TAG: &'static str = "ucan/d/1.0.0-rc.1"; } diff --git a/src/delegation/store.rs b/src/delegation/store.rs index 38c861d0..b0010146 100644 --- a/src/delegation/store.rs +++ b/src/delegation/store.rs @@ -1,4 +1,4 @@ -use super::{condition::Condition, Delegable, Delegation}; +use super::{condition::Condition, Delegation}; use crate::{ did::Did, proof::{checkable::Checkable, prove::Prove}, @@ -7,20 +7,22 @@ use libipld_core::cid::Cid; use nonempty::NonEmpty; use std::{ collections::{BTreeMap, BTreeSet}, + convert::Infallible, ops::ControlFlow, }; -use thiserror::Error; use web_time::SystemTime; // NOTE the T here is the builder... FIXME add one layer up and call T::Builder? May be confusing? pub trait Store { type Error; - fn get(&self, cid: &Cid) -> Result<&Delegation, Self::Error>; + fn get(&self, cid: &Cid) -> Result>, Self::Error>; - fn insert(&mut self, cid: Cid, delegation: Delegation); + // FIXME add a variant that calculated the CID from the capsulre header? + // FIXME that means changing the name to insert_by_cid or similar + fn insert(&mut self, cid: Cid, delegation: Delegation) -> Result<(), Self::Error>; - fn revoke(&mut self, cid: Cid); + fn revoke(&mut self, cid: Cid) -> Result<(), Self::Error>; fn get_chain( &self, @@ -28,10 +30,17 @@ pub trait Store { subject: &Did, builder: &B, now: &SystemTime, - ) -> Result)>, Self::Error>; + ) -> Result)>>, Self::Error>; - fn can_delegate(&self, iss: &Did, aud: &Did, builder: &B, now: &SystemTime) -> bool { - self.get_chain(aud, iss, builder, now).is_ok() + fn can_delegate( + &self, + iss: &Did, + aud: &Did, + builder: &B, + now: &SystemTime, + ) -> Result { + self.get_chain(aud, iss, builder, now) + .map(|chain| chain.is_some()) } } @@ -98,20 +107,18 @@ pub struct MemoryStore { revocations: BTreeSet, } -// FIXME extract -#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Error)] -#[error("Delegation not found")] -pub struct NotFound; - // FIXME check that UCAN is valid -impl Store for MemoryStore { - type Error = NotFound; - - fn get(&self, cid: &Cid) -> Result<&Delegation, Self::Error> { - self.ucans.get(cid).ok_or(NotFound) +impl Store for MemoryStore +where + B::Hierarchy: PartialEq, +{ + type Error = Infallible; + + fn get(&self, cid: &Cid) -> Result>, Self::Error> { + Ok(self.ucans.get(cid)) } - fn insert(&mut self, cid: Cid, delegation: Delegation) { + fn insert(&mut self, cid: Cid, delegation: Delegation) -> Result<(), Self::Error> { self.index .entry(delegation.payload.subject.clone()) .or_default() @@ -125,10 +132,12 @@ impl Store for MemoryStore Result<(), Self::Error> { self.revocations.insert(cid); + Ok(()) } fn get_chain( @@ -137,65 +146,65 @@ impl Store for MemoryStore Result)>, NotFound> { - #[derive(PartialEq)] - enum Status { - Complete, - Looking, - NoPath, - } - - // FIXME move these into an Acc - let mut status = Status::Looking; - let mut target_aud = aud; - let mut chain = vec![]; - let mut args: &B::Hierarchy = &builder.clone().into(); - - let delegation_subtree = self - .index - .get(subject) - .and_then(|aud_map| aud_map.get(aud)) - .ok_or(NotFound)?; - - while status == Status::Looking { - let found = delegation_subtree.iter().try_for_each(|cid| { - if let Some(d) = self.ucans.get(cid) { - if self.revocations.contains(&cid) { - return ControlFlow::Continue(()); - } - - if d.payload.check_time(*now).is_err() { - return ControlFlow::Continue(()); - } - - if args.check(&d.payload.ability_builder).is_ok() { - args = &d.payload.ability_builder; - } else { - return ControlFlow::Continue(()); - } - - chain.push((cid, d)); + ) -> Result)>>, Self::Error> { + match self.index.get(subject).and_then(|aud_map| aud_map.get(aud)) { + None => Ok(None), + Some(delegation_subtree) => { + #[derive(PartialEq)] + enum Status { + Complete, + Looking, + NoPath, + } - if &d.payload.issuer == subject { - status = Status::Complete; - } else { - target_aud = &d.payload.issuer; + let mut status = Status::Looking; + let mut target_aud = aud; + let mut args = &B::Hierarchy::from(builder.clone()); + let mut chain = vec![]; + + while status == Status::Looking { + let found = delegation_subtree.iter().try_for_each(|cid| { + if let Some(d) = self.ucans.get(cid) { + if self.revocations.contains(cid) { + return ControlFlow::Continue(()); + } + + if d.payload.check_time(*now).is_err() { + return ControlFlow::Continue(()); + } + + target_aud = &d.payload.audience; + + if args.check(&d.payload.ability_builder).is_ok() { + args = &d.payload.ability_builder; + } else { + return ControlFlow::Continue(()); + } + + chain.push((cid, d)); + + if &d.payload.issuer == subject { + status = Status::Complete; + } else { + target_aud = &d.payload.issuer; + } + + ControlFlow::Break(()) + } else { + ControlFlow::Continue(()) + } + }); + + if found.is_continue() { + status = Status::NoPath; } - - ControlFlow::Break(()) - } else { - ControlFlow::Continue(()) } - }); - if found.is_continue() { - status = Status::NoPath; + match status { + Status::Complete => Ok(NonEmpty::from_vec(chain)), + _ => Ok(None), + } } } - - match status { - Status::Complete => NonEmpty::from_vec(chain).ok_or(NotFound), - _ => Err(NotFound), - } } } diff --git a/src/lib.rs b/src/lib.rs index 8c7b0437..c74d1b11 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -63,7 +63,7 @@ extern crate alloc; // } pub mod ability; -pub mod agent; +// pub mod agent; FIXME put back? pub mod capsule; pub mod delegation; pub mod did; diff --git a/src/signature.rs b/src/signature.rs index 6846004c..6eb2bfc1 100644 --- a/src/signature.rs +++ b/src/signature.rs @@ -6,7 +6,7 @@ use serde::{Deserialize, Serialize}; use std::collections::BTreeMap; /// A container associating a `payload` with its signature over it. -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] pub struct Envelope { /// The signture of the `payload`. pub signature: Signature, @@ -15,6 +15,19 @@ pub struct Envelope { pub payload: T, } +impl Envelope { + // FIXME need key material + pub fn sign(payload: T) -> Envelope { + // FIXME + todo!() + } + + pub fn validate_signature(&self) -> Result<(), ()> { + // FIXME + todo!() + } +} + // FIXME consider kicking Batch down the road for spec reasons? #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(untagged)] diff --git a/src/time.rs b/src/time.rs index e697f2fd..03101e98 100644 --- a/src/time.rs +++ b/src/time.rs @@ -206,3 +206,13 @@ impl fmt::Display for OutOfRangeError { write!(f, "time out of JsTime (2⁵³) range: {:?}", self.tried) } } + +// FIXME move to time.rs +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Deserialize, Error)] +pub enum TimeBoundError { + #[error("The UCAN delegation has expired")] + Expired, + + #[error("The UCAN delegation is not yet valid")] + NotYetValid, +} From 5314d827bbf537444df1b4e55a65276e950ad314 Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Mon, 12 Feb 2024 23:15:51 -0800 Subject: [PATCH 073/188] DID Key crypto --- Cargo.toml | 29 +++++++-- src/crypto.rs | 18 +++++- src/crypto/bls.rs | 115 --------------------------------- src/crypto/bls12381.rs | 5 ++ src/crypto/bls12381/error.rs | 43 ++++++++++++ src/crypto/bls12381/min_pk.rs | 53 +++++++++++++++ src/crypto/bls12381/min_sig.rs | 53 +++++++++++++++ src/crypto/domain_separator.rs | 3 + src/crypto/p521.rs | 30 +++++++++ src/crypto/rs256.rs | 72 ++++++++++++++++----- src/crypto/rs512.rs | 64 ++++++++++++++++++ src/did.rs | 2 + src/did/dns.rs | 1 + src/did/key.rs | 57 ++++++++++++++++ src/did/key/traits.rs | 80 +++++++++++++++++++++++ src/did_verifier/did_key.rs | 8 +-- src/lib.rs | 1 + 17 files changed, 491 insertions(+), 143 deletions(-) delete mode 100644 src/crypto/bls.rs create mode 100644 src/crypto/bls12381.rs create mode 100644 src/crypto/bls12381/error.rs create mode 100644 src/crypto/bls12381/min_pk.rs create mode 100644 src/crypto/bls12381/min_sig.rs create mode 100644 src/crypto/domain_separator.rs create mode 100644 src/crypto/p521.rs create mode 100644 src/crypto/rs512.rs create mode 100644 src/did/dns.rs create mode 100644 src/did/key.rs create mode 100644 src/did/key/traits.rs diff --git a/Cargo.toml b/Cargo.toml index 4c558ec0..d653a035 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -40,7 +40,7 @@ cid = "0.11" did_url = "0.1" downcast-rs = "1.2.0" dyn-clone = "1.0.14" -ecdsa = { version = "0.16.8", optional = true, default-features = false } +ecdsa = { version = "0.16.8", features = ["alloc"], optional = true, default-features = false } ed25519 = { version = "2.2.2", optional = true, default-features = false } ed25519-dalek = { version = "2.0.0", features = ["rand_core"], optional = true } enum-as-inner = "0.6" @@ -54,12 +54,12 @@ libipld-cbor = "0.16" multibase = "0.9" multihash = { version = "0.18", features = ["sha2"] } nonempty = { version = "0.9" } -p256 = { version = "0.13.2", features = ["ecdsa"], optional = true, default-features = false } -p384 = { version = "0.13.0", features = ["ecdsa"], optional = true, default-features = false } -p521 = { version = "0.13.0", optional = true, default-features = false } +p256 = { version = "0.13.2", features = ["alloc", "ecdsa"], optional = true, default-features = false } +p384 = { version = "0.13.0", features = ["alloc", "ecdsa"], optional = true, default-features = false } +p521 = { version = "0.13.0", features = ["alloc", "ecdsa", "getrandom"], optional = true, default-features = false } proptest = { version = "1.1", optional = true } regex = "1.10" -rsa = { version = "0.9.2", features = ["sha2"], optional = true, default-features = false } +rsa = { version = "0.9.6", features = ["sha2"], optional = true, default-features = false } semver = "1.0.19" serde = { version = "1.0.188", features = ["derive"] } serde_derive = "1.0" @@ -108,9 +108,22 @@ default = [ "es384-verifier", "ps256-verifier", "rs256-verifier", + "rs512-verifier", + "es512-verifier", + "bls-verifier", + # FIXME the below while debugging + "es256", + "es256k", + "es384", + "es512", + "rs256", + "rs512", + "eddsa", + "bls" ] + test_utils = ["proptest"] -did-key = [] +did-key = [] # FIXME remove? eddsa = ["dep:ed25519", "dep:ed25519-dalek"] es256 = ["dep:p256"] es256k = ["dep:k256"] @@ -118,7 +131,10 @@ es384 = ["dep:p384"] es512 = ["dep:ecdsa", "dep:p521"] ps256 = ["dep:rsa"] rs256 = ["dep:rsa"] +rs512 = ["dep:rsa"] bls = ["dep:blst"] +# FIXME rename for varsig? +# FIXME actually remove these since they got ported to traits eddsa-verifier = ["eddsa"] es256-verifier = ["es256"] es256k-verifier = ["es256k"] @@ -126,6 +142,7 @@ es384-verifier = ["es384"] es512-verifier = ["es512"] ps256-verifier = ["ps256"] rs256-verifier = ["rs256"] +rs512-verifier = ["rs512"] bls-verifier = ["bls"] mermaid_docs = ["aquamarine"] diff --git a/src/crypto.rs b/src/crypto.rs index 75009dbb..18fecc8a 100644 --- a/src/crypto.rs +++ b/src/crypto.rs @@ -2,23 +2,39 @@ use signature::SignatureEncoding; +pub mod domain_separator; + #[cfg(feature = "bls")] -pub mod bls; +pub mod bls12381; + +#[cfg(feature = "es512")] +pub mod p521; + #[cfg(feature = "eddsa")] pub mod eddsa; + #[cfg(feature = "es256")] pub mod es256; + #[cfg(feature = "es256k")] pub mod es256k; + #[cfg(feature = "es384")] pub mod es384; + #[cfg(feature = "es512")] pub mod es512; + #[cfg(feature = "ps256")] pub mod ps256; + #[cfg(feature = "rs256")] pub mod rs256; +#[cfg(feature = "rs512")] +pub mod rs512; + +// FIXME switch to varsig /// A trait for mapping a SignatureEncoding to its algorithm name under JWS pub trait JWSSignature: SignatureEncoding { /// The algorithm name under JWS diff --git a/src/crypto/bls.rs b/src/crypto/bls.rs deleted file mode 100644 index f7362b62..00000000 --- a/src/crypto/bls.rs +++ /dev/null @@ -1,115 +0,0 @@ -//! BLS12-381 signature support - -use anyhow::anyhow; -use blst::BLST_ERROR; -use signature::SignatureEncoding; - -use super::JWSSignature; - -/// A BLS12-381 G1 signature -#[derive(Debug, Clone)] -pub struct Bls12381G1Sha256SswuRoNulSignature(pub blst::min_sig::Signature); - -impl<'a> TryFrom<&'a [u8]> for Bls12381G1Sha256SswuRoNulSignature { - type Error = BLST_ERROR; - - fn try_from(bytes: &'a [u8]) -> Result { - Ok(Self(blst::min_sig::Signature::uncompress(bytes)?)) - } -} - -impl From for [u8; 48] { - fn from(sig: Bls12381G1Sha256SswuRoNulSignature) -> Self { - sig.0.compress() - } -} - -impl SignatureEncoding for Bls12381G1Sha256SswuRoNulSignature { - type Repr = [u8; 48]; -} - -impl JWSSignature for Bls12381G1Sha256SswuRoNulSignature { - const ALGORITHM: &'static str = "Bls12381G1"; -} - -/// A BLS12-381 G2 signature -#[derive(Debug, Clone)] -pub struct Bls12381G2Sha256SswuRoNulSignature(pub blst::min_pk::Signature); - -impl<'a> TryFrom<&'a [u8]> for Bls12381G2Sha256SswuRoNulSignature { - type Error = BLST_ERROR; - - fn try_from(bytes: &'a [u8]) -> Result { - Ok(Self(blst::min_pk::Signature::uncompress(bytes)?)) - } -} - -impl From for [u8; 96] { - fn from(sig: Bls12381G2Sha256SswuRoNulSignature) -> Self { - sig.0.compress() - } -} - -impl SignatureEncoding for Bls12381G2Sha256SswuRoNulSignature { - type Repr = [u8; 96]; -} - -impl JWSSignature for Bls12381G2Sha256SswuRoNulSignature { - const ALGORITHM: &'static str = "Bls12381G2"; -} - -/// A verifier for BLS12-381 G1 signatures -#[cfg(feature = "bls-verifier")] -pub fn bls_12_381_g1_sha256_sswu_ro_nul_verifier( - key: &[u8], - payload: &[u8], - signature: &[u8], -) -> Result<(), anyhow::Error> { - let dst = b"BLS_SIG_BLS12381G1_XMD:SHA-256_SSWU_RO_NUL_"; - let aug = &[]; - - let key = - blst::min_sig::PublicKey::uncompress(key).map_err(|_| anyhow!("invalid BLS12-381 key"))?; - - let signature = blst::min_sig::Signature::uncompress(signature) - .map_err(|_| anyhow!("invalid BLS12-381 signature"))?; - - match signature.verify(true, payload, dst, aug, &key, true) { - BLST_ERROR::BLST_SUCCESS => Ok(()), - BLST_ERROR::BLST_BAD_ENCODING => Err(anyhow!("bad encoding")), - BLST_ERROR::BLST_POINT_NOT_ON_CURVE => Err(anyhow!("point not on curve")), - BLST_ERROR::BLST_POINT_NOT_IN_GROUP => Err(anyhow!("bad point not in group")), - BLST_ERROR::BLST_AGGR_TYPE_MISMATCH => Err(anyhow!("aggregate type mismatch")), - BLST_ERROR::BLST_VERIFY_FAIL => Err(anyhow!("signature mismatch")), - BLST_ERROR::BLST_PK_IS_INFINITY => Err(anyhow!("public key is infinity")), - BLST_ERROR::BLST_BAD_SCALAR => Err(anyhow!("bad scalar")), - } -} - -/// A verifier for BLS12-381 G2 signatures -#[cfg(feature = "bls-verifier")] -pub fn bls_12_381_g2_sha256_sswu_ro_nul_verifier( - key: &[u8], - payload: &[u8], - signature: &[u8], -) -> Result<(), anyhow::Error> { - let dst = b"BLS_SIG_BLS12381G2_XMD:SHA-256_SSWU_RO_NUL_"; - let aug = &[]; - - let key = - blst::min_pk::PublicKey::uncompress(key).map_err(|_| anyhow!("invalid BLS12-381 key"))?; - - let signature = blst::min_pk::Signature::uncompress(signature) - .map_err(|_| anyhow!("invalid BLS12-381 signature"))?; - - match signature.verify(true, payload, dst, aug, &key, true) { - BLST_ERROR::BLST_SUCCESS => Ok(()), - BLST_ERROR::BLST_BAD_ENCODING => Err(anyhow!("bad encoding")), - BLST_ERROR::BLST_POINT_NOT_ON_CURVE => Err(anyhow!("point not on curve")), - BLST_ERROR::BLST_POINT_NOT_IN_GROUP => Err(anyhow!("bad point not in group")), - BLST_ERROR::BLST_AGGR_TYPE_MISMATCH => Err(anyhow!("aggregate type mismatch")), - BLST_ERROR::BLST_VERIFY_FAIL => Err(anyhow!("signature mismatch")), - BLST_ERROR::BLST_PK_IS_INFINITY => Err(anyhow!("public key is infinity")), - BLST_ERROR::BLST_BAD_SCALAR => Err(anyhow!("bad scalar")), - } -} diff --git a/src/crypto/bls12381.rs b/src/crypto/bls12381.rs new file mode 100644 index 00000000..2ad56f6f --- /dev/null +++ b/src/crypto/bls12381.rs @@ -0,0 +1,5 @@ +//! BLS12-381 signature support + +pub mod error; +pub mod min_pk; +pub mod min_sig; diff --git a/src/crypto/bls12381/error.rs b/src/crypto/bls12381/error.rs new file mode 100644 index 00000000..7af72c19 --- /dev/null +++ b/src/crypto/bls12381/error.rs @@ -0,0 +1,43 @@ +use blst::BLST_ERROR; +use thiserror::Error; + +#[derive(Error, Debug, PartialEq, Eq, Clone, Copy)] +pub enum VerificationError { + #[error("signature mismatch")] + VerifyMsgFail, + + #[error("bad encoding")] + BadEncoding, + + #[error("point not on curve")] + PointNotOnCurve, + + #[error("bad point not in group")] + PointNotInGroup, + + #[error("aggregate type mismatch")] + AggrTypeMismatch, + + #[error("public key is infinity")] + PkIsInfinity, + + #[error("bad scalar")] + BadScalar, +} + +impl TryFrom for VerificationError { + type Error = (); + + fn try_from(err: BLST_ERROR) -> Result { + match err { + BLST_ERROR::BLST_SUCCESS => Err(()), + BLST_ERROR::BLST_VERIFY_FAIL => Ok(VerificationError::VerifyMsgFail), + BLST_ERROR::BLST_BAD_ENCODING => Ok(VerificationError::BadEncoding), + BLST_ERROR::BLST_POINT_NOT_ON_CURVE => Ok(VerificationError::PointNotOnCurve), + BLST_ERROR::BLST_POINT_NOT_IN_GROUP => Ok(VerificationError::PointNotInGroup), + BLST_ERROR::BLST_AGGR_TYPE_MISMATCH => Ok(VerificationError::AggrTypeMismatch), + BLST_ERROR::BLST_PK_IS_INFINITY => Ok(VerificationError::PkIsInfinity), + BLST_ERROR::BLST_BAD_SCALAR => Ok(VerificationError::BadScalar), + } + } +} diff --git a/src/crypto/bls12381/min_pk.rs b/src/crypto/bls12381/min_pk.rs new file mode 100644 index 00000000..fde8887e --- /dev/null +++ b/src/crypto/bls12381/min_pk.rs @@ -0,0 +1,53 @@ +use super::error::VerificationError; +use crate::crypto::domain_separator::DomainSeparator; +use blst::BLST_ERROR; +use signature::{SignatureEncoding, Signer, Verifier}; + +/// A BLS12-381 MinPubKey signature +#[derive(Debug, Clone)] +pub struct Signature(pub blst::min_pk::Signature); + +impl DomainSeparator for Signature { + /// From the [IETF BLS Signature Spec](https://www.ietf.org/archive/id/draft-irtf-cfrg-bls-signature-05.html#section-4.2.1) + const DST: &'static [u8] = b"BLS_SIG_BLS12381G2_XMD:SHA-256_SSWU_RO_NUL_"; +} + +impl<'a> TryFrom<&'a [u8]> for Signature { + type Error = BLST_ERROR; + + fn try_from(bytes: &'a [u8]) -> Result { + Ok(Self(blst::min_pk::Signature::uncompress(bytes)?)) + } +} + +impl From for [u8; 96] { + fn from(sig: Signature) -> Self { + sig.0.compress() + } +} + +impl SignatureEncoding for Signature { + type Repr = [u8; 96]; +} + +impl Signer for blst::min_pk::SecretKey { + fn try_sign(&self, msg: &[u8]) -> Result { + Ok(Signature(self.sign(msg, Signature::DST, &[]))) + } +} + +impl Verifier for blst::min_pk::PublicKey { + fn verify(&self, msg: &[u8], signature: &Signature) -> Result<(), signature::Error> { + match VerificationError::try_from(signature.0.verify( + true, + msg, + Signature::DST, + &[], + &self, + true, + )) { + Ok(err) => Err(signature::Error::from_source(err)), + Err(_) => Ok(()), + } + } +} diff --git a/src/crypto/bls12381/min_sig.rs b/src/crypto/bls12381/min_sig.rs new file mode 100644 index 00000000..121ac66d --- /dev/null +++ b/src/crypto/bls12381/min_sig.rs @@ -0,0 +1,53 @@ +use super::error::VerificationError; +use crate::crypto::domain_separator::DomainSeparator; +use blst::BLST_ERROR; +use signature::{SignatureEncoding, Signer, Verifier}; + +/// A BLS12-381 MinSig signature +#[derive(Debug, Clone)] +pub struct Signature(pub blst::min_sig::Signature); + +impl DomainSeparator for Signature { + /// From the [IETF BLS Signature Spec](https://www.ietf.org/archive/id/draft-irtf-cfrg-bls-signature-05.html#section-4.2.1) + const DST: &'static [u8] = b"BLS_SIG_BLS12381G1_XMD:SHA-256_SSWU_RO_NUL_"; +} + +impl<'a> TryFrom<&'a [u8]> for Signature { + type Error = BLST_ERROR; + + fn try_from(bytes: &'a [u8]) -> Result { + Ok(Self(blst::min_sig::Signature::uncompress(bytes)?)) + } +} + +impl From for [u8; 48] { + fn from(sig: Signature) -> Self { + sig.0.compress() + } +} + +impl SignatureEncoding for Signature { + type Repr = [u8; 48]; +} + +impl Signer for blst::min_sig::SecretKey { + fn try_sign(&self, msg: &[u8]) -> Result { + Ok(Signature(self.sign(msg, Signature::DST, &[]))) + } +} + +impl Verifier for blst::min_sig::PublicKey { + fn verify(&self, msg: &[u8], signature: &Signature) -> Result<(), signature::Error> { + match VerificationError::try_from(signature.0.verify( + true, + msg, + Signature::DST, + &[], + &self, + true, + )) { + Ok(err) => Err(signature::Error::from_source(err)), + Err(_) => Ok(()), + } + } +} diff --git a/src/crypto/domain_separator.rs b/src/crypto/domain_separator.rs new file mode 100644 index 00000000..718aabc2 --- /dev/null +++ b/src/crypto/domain_separator.rs @@ -0,0 +1,3 @@ +pub trait DomainSeparator { + const DST: &'static [u8]; +} diff --git a/src/crypto/p521.rs b/src/crypto/p521.rs new file mode 100644 index 00000000..79d09d29 --- /dev/null +++ b/src/crypto/p521.rs @@ -0,0 +1,30 @@ +use p521; +use signature::Verifier; +use std::fmt; + +#[derive(Clone)] // FIXME , Serialize, Deserialize)] +pub struct VerifyingKey(pub p521::ecdsa::VerifyingKey); + +impl fmt::Debug for VerifyingKey { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_tuple("VerifyingKey").finish() + } +} + +impl PartialEq for VerifyingKey { + fn eq(&self, other: &Self) -> bool { + self.0.to_encoded_point(true) == other.0.to_encoded_point(true) + } +} + +impl Eq for VerifyingKey {} + +impl Verifier for VerifyingKey { + fn verify( + &self, + msg: &[u8], + signature: &p521::ecdsa::Signature, + ) -> Result<(), signature::Error> { + self.0.verify(msg, &signature) + } +} diff --git a/src/crypto/rs256.rs b/src/crypto/rs256.rs index 9f39c1ff..419093d2 100644 --- a/src/crypto/rs256.rs +++ b/src/crypto/rs256.rs @@ -1,27 +1,65 @@ //! RS256 signature support -#[cfg(feature = "rs256-verifier")] -use anyhow::anyhow; -#[cfg(feature = "rs256-verifier")] -use signature::Verifier; +use rsa; +use signature::{SignatureEncoding, Signer, Verifier}; -use super::JWSSignature; +#[derive(Debug, Clone)] // FIXME , Serialize, Deserialize)] +pub struct VerifyingKey(pub rsa::pkcs1v15::VerifyingKey); -impl JWSSignature for rsa::pkcs1v15::Signature { - const ALGORITHM: &'static str = "RS256"; +impl PartialEq for VerifyingKey { + fn eq(&self, other: &Self) -> bool { + // FIXME yikes that clone + rsa::RsaPublicKey::from(self.0.clone()) == rsa::RsaPublicKey::from(other.0.clone()) + } } -/// A verifier for RS256 signatures -#[cfg(feature = "rs256-verifier")] -pub fn rs256_verifier(key: &[u8], payload: &[u8], signature: &[u8]) -> Result<(), anyhow::Error> { - let key = rsa::pkcs1::DecodeRsaPublicKey::from_pkcs1_der(key) - .map_err(|e| anyhow!("invalid PKCS#1 key, {}", e))?; +impl Eq for VerifyingKey {} - let key = rsa::pkcs1v15::VerifyingKey::::new(key); +impl Verifier for VerifyingKey { + fn verify(&self, msg: &[u8], signature: &Signature) -> Result<(), signature::Error> { + self.0.verify(msg, &signature.0) + } +} + +#[derive(Debug, Clone)] // FIXME , Serialize, Deserialize)] +pub struct SigningKey(pub rsa::pkcs1v15::SigningKey); + +impl Signer for SigningKey { + fn try_sign(&self, msg: &[u8]) -> Result { + self.0.try_sign(msg).map(Signature) + } +} + +#[derive(Debug, Clone, PartialEq, Eq)] // FIXME , Serialize, Deserialize)] +pub struct Signature(pub rsa::pkcs1v15::Signature); + +impl SignatureEncoding for Signature { + type Repr = [u8; 256]; +} + +impl From<[u8; 256]> for Signature { + fn from(bytes: [u8; 256]) -> Self { + Signature( + rsa::pkcs1v15::Signature::try_from(bytes.as_ref()) + .expect("passed in [u8; 256], so should succeed"), + ) + } +} + +impl From for [u8; 256] { + fn from(sig: Signature) -> [u8; 256] { + sig.0 + .to_bytes() + .as_ref() + .try_into() + .expect("Signature should be exactly 256 bytes") + } +} - let signature = rsa::pkcs1v15::Signature::try_from(signature) - .map_err(|e| anyhow!("invalid RSASSA-PKCS1-v1_5 signature, {}", e))?; +impl<'a> TryFrom<&'a [u8]> for Signature { + type Error = signature::Error; - key.verify(payload, &signature) - .map_err(|e| anyhow!("signature mismatch, {}", e)) + fn try_from(bytes: &'a [u8]) -> Result { + rsa::pkcs1v15::Signature::try_from(bytes).map(Signature) + } } diff --git a/src/crypto/rs512.rs b/src/crypto/rs512.rs new file mode 100644 index 00000000..d3af4635 --- /dev/null +++ b/src/crypto/rs512.rs @@ -0,0 +1,64 @@ +//! RS512 signature support + +use rsa; +use signature::{SignatureEncoding, Signer, Verifier}; + +#[derive(Debug, Clone)] // FIXME , Serialize, Deserialize)] +pub struct VerifyingKey(pub rsa::pkcs1v15::VerifyingKey); + +impl PartialEq for VerifyingKey { + fn eq(&self, other: &Self) -> bool { + rsa::RsaPublicKey::from(self.0.clone()) == rsa::RsaPublicKey::from(other.0.clone()) + } +} + +impl Eq for VerifyingKey {} + +impl Verifier for VerifyingKey { + fn verify(&self, msg: &[u8], signature: &Signature) -> Result<(), signature::Error> { + self.0.verify(msg, &signature.0) + } +} + +#[derive(Debug, Clone)] // FIXME , Serialize, Deserialize)] +pub struct SigningKey(pub rsa::pkcs1v15::SigningKey); + +impl Signer for SigningKey { + fn try_sign(&self, msg: &[u8]) -> Result { + self.0.try_sign(msg).map(Signature) + } +} + +#[derive(Debug, Clone, PartialEq, Eq)] // FIXME , Serialize, Deserialize)] +pub struct Signature(pub rsa::pkcs1v15::Signature); + +impl SignatureEncoding for Signature { + type Repr = [u8; 512]; +} + +impl From<[u8; 512]> for Signature { + fn from(bytes: [u8; 512]) -> Self { + Signature( + rsa::pkcs1v15::Signature::try_from(bytes.as_ref()) + .expect("passed in [u8; 512], so should succeed"), + ) + } +} + +impl From for [u8; 512] { + fn from(sig: Signature) -> [u8; 512] { + sig.0 + .to_bytes() + .as_ref() + .try_into() + .expect("Signature should be exactly 512 bytes") + } +} + +impl<'a> TryFrom<&'a [u8]> for Signature { + type Error = signature::Error; + + fn try_from(bytes: &'a [u8]) -> Result { + rsa::pkcs1v15::Signature::try_from(bytes).map(Signature) + } +} diff --git a/src/did.rs b/src/did.rs index 10ddb45e..db6059aa 100644 --- a/src/did.rs +++ b/src/did.rs @@ -2,6 +2,8 @@ //! //! [wiki]: https://en.wikipedia.org/wiki/Decentralized_identifier +pub mod key; + use did_url::DID; use libipld_core::ipld::Ipld; use serde::{Deserialize, Serialize}; diff --git a/src/did/dns.rs b/src/did/dns.rs new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/src/did/dns.rs @@ -0,0 +1 @@ + diff --git a/src/did/key.rs b/src/did/key.rs new file mode 100644 index 00000000..5d6c2c2b --- /dev/null +++ b/src/did/key.rs @@ -0,0 +1,57 @@ +//! Support for the `did:key` scheme + +pub mod traits; + +#[cfg(feature = "eddsa")] +use ed25519_dalek; + +#[cfg(feature = "es256")] +use p256; + +#[cfg(feature = "es256k")] +use k256; + +#[cfg(feature = "es384")] +use p384; + +#[cfg(feature = "es512")] +use crate::crypto::p521; + +#[cfg(feature = "rs256")] +use crate::crypto::rs256; + +#[cfg(feature = "rs512")] +use crate::crypto::rs512; + +#[cfg(feature = "bls")] +use blst; + +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum Verifier { + #[cfg(feature = "eddsa")] + Ed25519(ed25519_dalek::VerifyingKey), + + #[cfg(feature = "es256k")] + Sedcp256k1(k256::ecdsa::VerifyingKey), + + #[cfg(feature = "es256")] + P256(p256::ecdsa::VerifyingKey), + + #[cfg(feature = "es384")] + P384(p384::ecdsa::VerifyingKey), + + #[cfg(feature = "es512")] + P521(p521::VerifyingKey), + + #[cfg(feature = "rs256")] + Rs256(rs256::VerifyingKey), + + #[cfg(feature = "rs512")] + Rs512(rs512::VerifyingKey), + + #[cfg(feature = "bls")] + BlsMinPk(blst::min_pk::PublicKey), + + #[cfg(feature = "bls")] + BlsMinSig(blst::min_sig::PublicKey), +} diff --git a/src/did/key/traits.rs b/src/did/key/traits.rs new file mode 100644 index 00000000..b4548e3e --- /dev/null +++ b/src/did/key/traits.rs @@ -0,0 +1,80 @@ +use crate::crypto::{bls12381, p521, rs256, rs512}; +use ::p521 as ext_p521; +use ed25519_dalek; +use k256; +use p256; +use p384; + +// FIXME +// also: e.g. HSM + +// FIXME when name conflict gone +pub trait DidKey: signature::Verifier { + const BASE58_PREFIX: &'static str; + + type Signer: signature::Signer; + type Signature: signature::SignatureEncoding; +} + +impl DidKey for ed25519_dalek::VerifyingKey { + const BASE58_PREFIX: &'static str = "6Mk"; + + type Signer = ed25519_dalek::SigningKey; + type Signature = ed25519_dalek::Signature; +} + +impl DidKey for p256::ecdsa::VerifyingKey { + const BASE58_PREFIX: &'static str = "Dn"; + + type Signer = p256::ecdsa::SigningKey; + type Signature = p256::ecdsa::Signature; +} + +impl DidKey for k256::ecdsa::VerifyingKey { + const BASE58_PREFIX: &'static str = "Q3s"; + + type Signer = k256::ecdsa::SigningKey; + type Signature = k256::ecdsa::Signature; +} + +impl DidKey for p384::ecdsa::VerifyingKey { + const BASE58_PREFIX: &'static str = "82"; + + type Signer = p384::ecdsa::SigningKey; + type Signature = p384::ecdsa::Signature; +} + +impl DidKey for p521::VerifyingKey { + const BASE58_PREFIX: &'static str = "2J9"; + + type Signer = ext_p521::ecdsa::SigningKey; + type Signature = ext_p521::ecdsa::Signature; +} + +impl DidKey for rs256::VerifyingKey { + const BASE58_PREFIX: &'static str = "4MX"; + + type Signer = rs256::SigningKey; + type Signature = rs256::Signature; +} + +impl DidKey for rs512::VerifyingKey { + const BASE58_PREFIX: &'static str = "zgg"; + + type Signer = rs512::SigningKey; + type Signature = rs512::Signature; +} + +impl DidKey for blst::min_sig::PublicKey { + const BASE58_PREFIX: &'static str = "UC7"; + + type Signer = blst::min_sig::SecretKey; + type Signature = bls12381::min_sig::Signature; +} + +impl DidKey for blst::min_pk::PublicKey { + const BASE58_PREFIX: &'static str = "UC7"; + + type Signer = blst::min_pk::SecretKey; + type Signature = bls12381::min_pk::Signature; +} diff --git a/src/did_verifier/did_key.rs b/src/did_verifier/did_key.rs index 4c2f658f..426df184 100644 --- a/src/did_verifier/did_key.rs +++ b/src/did_verifier/did_key.rs @@ -121,7 +121,7 @@ impl DidVerifier for DidKeyVerifier { MulticodecPubKey::P384Compressed => self .verifier_map .get(&TypeId::of::()), - #[cfg(feature = "es521")] + #[cfg(feature = "es512")] MulticodecPubKey::P521Compressed => self .verifier_map .get(&TypeId::of::>()), @@ -162,7 +162,7 @@ pub enum MulticodecPubKey { #[cfg(feature = "es384")] P384Compressed, /// p521 compressed public key - #[cfg(feature = "es521")] + #[cfg(feature = "es512")] P521Compressed, /// rsa pkcs1 public key #[cfg(feature = "rs256")] @@ -219,7 +219,7 @@ impl MulticodecPubKey { )); } } - #[cfg(feature = "es521")] + #[cfg(feature = "es512")] MulticodecPubKey::P521Compressed => { if pub_key.len() > 67 { return Err(anyhow!( @@ -261,7 +261,7 @@ impl TryFrom for MulticodecPubKey { 0x1200 => Ok(MulticodecPubKey::P256Compressed), #[cfg(feature = "es384")] 0x1201 => Ok(MulticodecPubKey::P384Compressed), - #[cfg(feature = "es521")] + #[cfg(feature = "es512")] 0x1202 => Ok(MulticodecPubKey::P521Compressed), #[cfg(feature = "rs256")] 0x1205 => Ok(MulticodecPubKey::RSAPKCS1), diff --git a/src/lib.rs b/src/lib.rs index c74d1b11..dd97bfe2 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -63,6 +63,7 @@ extern crate alloc; // } pub mod ability; +pub mod crypto; // pub mod agent; FIXME put back? pub mod capsule; pub mod delegation; From 3f69fc92d67e980d5bfad39ea20ad284f2e8187c Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Tue, 13 Feb 2024 00:39:28 -0800 Subject: [PATCH 074/188] Parameterized by DID --- src/delegation.rs | 23 +++++---- src/delegation/agent.rs | 28 ++++++----- src/delegation/condition.rs | 30 ++++++------ src/delegation/payload.rs | 78 ++++++++++++++++------------- src/delegation/store.rs | 37 +++++++------- src/did.rs | 89 ++++++++++++++++++++++++++++------ src/invocation.rs | 2 +- src/invocation/payload.rs | 97 ++++++++++++++++++------------------- src/invocation/store.rs | 17 ++++--- src/proof/same.rs | 22 ++++----- src/receipt.rs | 2 +- src/receipt/payload.rs | 26 +++++----- src/receipt/responds.rs | 7 ++- src/receipt/store.rs | 18 +++---- src/task.rs | 4 +- 15 files changed, 275 insertions(+), 205 deletions(-) diff --git a/src/delegation.rs b/src/delegation.rs index deebfa8e..1988b137 100644 --- a/src/delegation.rs +++ b/src/delegation.rs @@ -11,7 +11,7 @@ pub use delegable::Delegable; pub use payload::Payload; use crate::{ - did::Did, + did::{self, Did}, nonce::Nonce, proof::{checkable::Checkable, parents::CheckParents, same::CheckSame}, signature, @@ -28,20 +28,23 @@ use web_time::SystemTime; /// /// # Examples /// FIXME -pub type Delegation = signature::Envelope>; +pub type Delegation = signature::Envelope>; + +// FIXME attach common abilities, too +pub type Preset = Delegation; // FIXME checkable -> provable? -impl Delegation { - pub fn issuer(&self) -> &Did { +impl Delegation { + pub fn issuer(&self) -> &DID { &self.payload.issuer } - pub fn subject(&self) -> &Did { + pub fn subject(&self) -> &DID { &self.payload.subject } - pub fn audience(&self) -> &Did { + pub fn audience(&self) -> &DID { &self.payload.audience } @@ -74,16 +77,16 @@ impl Delegation { } } -impl CheckSame for Delegation { +impl CheckSame for Delegation { type Error = ::Error; - fn check_same(&self, proof: &Delegation) -> Result<(), Self::Error> { + fn check_same(&self, proof: &Delegation) -> Result<(), Self::Error> { self.payload.check_same(&proof.payload) } } -impl CheckParents for Delegation { - type Parents = Delegation; +impl CheckParents for Delegation { + type Parents = Delegation; type ParentError = ::ParentError; fn check_parent(&self, proof: &Self::Parents) -> Result<(), Self::ParentError> { diff --git a/src/delegation/agent.rs b/src/delegation/agent.rs index ba0e8b4c..375fcf53 100644 --- a/src/delegation/agent.rs +++ b/src/delegation/agent.rs @@ -5,8 +5,8 @@ use std::{collections::BTreeMap, marker::PhantomData}; use thiserror::Error; use web_time::SystemTime; -pub struct Agent<'a, B: Checkable, C: Condition, S: Store> { - pub did: &'a Did, +pub struct Agent<'a, B: Checkable, C: Condition, DID: Did, S: Store> { + pub did: &'a DID, pub store: &'a mut S, _marker: PhantomData<(B, C)>, } @@ -14,8 +14,10 @@ pub struct Agent<'a, B: Checkable, C: Condition, S: Store> { // FIXME show example of multiple hierarchies of "all things accepted" // delegating down to inner versions of this -impl<'a, B: Checkable, C: Condition + Clone, S: Store> Agent<'a, B, C, S> { - pub fn new(did: &'a Did, store: &'a mut S) -> Self { +impl<'a, B: Checkable, C: Condition + Clone, DID: Did + ToString + Clone, S: Store> + Agent<'a, B, C, DID, S> +{ + pub fn new(did: &'a DID, store: &'a mut S) -> Self { Self { did, store, @@ -25,19 +27,19 @@ impl<'a, B: Checkable, C: Condition + Clone, S: Store> Agent<'a, B, C, S> pub fn delegate( &self, - audience: Did, - subject: Did, + audience: DID, + subject: DID, ability_builder: B, new_conditions: Vec, metadata: BTreeMap, expiration: JsTime, not_before: Option, - ) -> Result, DelegateError<>::Error>> { + ) -> Result, DelegateError<>::Error>> { let mut salt = self.did.clone().to_string().into_bytes(); let nonce = Nonce::generate_16(&mut salt); if subject == *self.did { - let payload = Payload { + let payload: Payload = Payload { issuer: self.did.clone(), audience, subject, @@ -65,7 +67,7 @@ impl<'a, B: Checkable, C: Condition + Clone, S: Store> Agent<'a, B, C, S> let mut conditions = to_delegate.conditions.clone(); conditions.append(&mut new_conditions.clone()); - let payload = Payload { + let payload: Payload = Payload { issuer: self.did.clone(), audience, subject, @@ -84,8 +86,8 @@ impl<'a, B: Checkable, C: Condition + Clone, S: Store> Agent<'a, B, C, S> pub fn recieve( &mut self, cid: Cid, // FIXME remove and generate from the capsule header? - delegation: Delegation, - ) -> Result<(), ReceiveError<>::Error>> { + delegation: Delegation, + ) -> Result<(), ReceiveError<>::Error, DID>> { if self.store.get(&cid).is_ok() { return Ok(()); } @@ -112,9 +114,9 @@ pub enum DelegateError { } #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Error)] -pub enum ReceiveError { +pub enum ReceiveError { #[error("The current agent ({0}) is not the intended audience of the delegation.")] - WrongAudience(Did), + WrongAudience(DID), #[error("Signature for UCAN with CID {0} is invalid.")] InvalidSignature(Cid), diff --git a/src/delegation/condition.rs b/src/delegation/condition.rs index 6b853128..79746ff1 100644 --- a/src/delegation/condition.rs +++ b/src/delegation/condition.rs @@ -30,7 +30,7 @@ use serde_derive::{Deserialize, Serialize}; #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(untagged)] #[allow(missing_docs)] -pub enum Common { +pub enum Preset { ContainsAll(contains_all::ContainsAll), ContainsAny(contains_any::ContainsAny), ContainsKey(contains_key::ContainsKey), @@ -43,13 +43,13 @@ pub enum Common { MatchesRegex(matches_regex::MatchesRegex), } -impl From for Ipld { - fn from(common: Common) -> Self { +impl From for Ipld { + fn from(common: Preset) -> Self { common.into() } } -impl TryFrom for Common { +impl TryFrom for Preset { type Error = SerdeError; fn try_from(ipld: Ipld) -> Result { @@ -57,19 +57,19 @@ impl TryFrom for Common { } } -impl Condition for Common { +impl Condition for Preset { fn validate(&self, args: &arguments::Named) -> bool { match self { - Common::ContainsAll(c) => c.validate(args), - Common::ContainsAny(c) => c.validate(args), - Common::ContainsKey(c) => c.validate(args), - Common::ExcludesKey(c) => c.validate(args), - Common::ExcludesAll(c) => c.validate(args), - Common::MinLength(c) => c.validate(args), - Common::MaxLength(c) => c.validate(args), - Common::MinNumber(c) => c.validate(args), - Common::MaxNumber(c) => c.validate(args), - Common::MatchesRegex(c) => c.validate(args), + Preset::ContainsAll(c) => c.validate(args), + Preset::ContainsAny(c) => c.validate(args), + Preset::ContainsKey(c) => c.validate(args), + Preset::ExcludesKey(c) => c.validate(args), + Preset::ExcludesAll(c) => c.validate(args), + Preset::MinLength(c) => c.validate(args), + Preset::MaxLength(c) => c.validate(args), + Preset::MinNumber(c) => c.validate(args), + Preset::MaxNumber(c) => c.validate(args), + Preset::MatchesRegex(c) => c.validate(args), } } } diff --git a/src/delegation/payload.rs b/src/delegation/payload.rs index 369e6d5c..9e4dfc53 100644 --- a/src/delegation/payload.rs +++ b/src/delegation/payload.rs @@ -8,6 +8,7 @@ use crate::{ command::{Command, ParseAbility, ToCommand}, }, capsule::Capsule, + did, did::Did, nonce::Nonce, proof::{ @@ -32,7 +33,7 @@ use web_time::SystemTime; /// This contains the semantic information about the delegation, including the /// issuer, subject, audience, the delegated ability, time bounds, and so on. #[derive(Debug, Clone, PartialEq)] -pub struct Payload { +pub struct Payload { /// The subject of the [`Delegation`]. /// /// This role *must* have issued the earlier (root) @@ -43,7 +44,7 @@ pub struct Payload { /// by the subject. /// /// [`Delegation`]: super::Delegation - pub subject: Did, + pub subject: DID, /// The issuer of the [`Delegation`]. /// @@ -51,10 +52,10 @@ pub struct Payload { /// the outer layer of [`Delegation`]. /// /// [`Delegation`]: super::Delegation - pub issuer: Did, + pub issuer: DID, /// The agent being delegated to. - pub audience: Did, + pub audience: DID, /// A delegatable ability chain. /// @@ -86,8 +87,8 @@ pub struct Payload { pub not_before: Option, } -impl Payload { - pub fn map_ability(self, f: impl FnOnce(D) -> T) -> Payload { +impl Payload { + pub fn map_ability(self, f: impl FnOnce(D) -> T) -> Payload { Payload { issuer: self.issuer, subject: self.subject, @@ -101,7 +102,7 @@ impl Payload { } } - pub fn map_conditon(self, f: impl FnMut(C) -> T) -> Payload { + pub fn map_conditon(self, f: impl FnMut(C) -> T) -> Payload { Payload { issuer: self.issuer, subject: self.subject, @@ -130,20 +131,20 @@ impl Payload { } } -impl Capsule for Payload { +impl Capsule for Payload { const TAG: &'static str = "ucan/d/1.0.0-rc.1"; } -impl CheckSame for Payload { +impl CheckSame for Payload { type Error = ::Error; - fn check_same(&self, proof: &Payload) -> Result<(), Self::Error> { + fn check_same(&self, proof: &Payload) -> Result<(), Self::Error> { self.ability_builder.check_same(&proof.ability_builder) } } -impl CheckParents for Payload { - type Parents = Payload; +impl CheckParents for Payload { + type Parents = Payload; type ParentError = ::ParentError; fn check_parent(&self, proof: &Self::Parents) -> Result<(), Self::ParentError> { @@ -151,7 +152,8 @@ impl CheckParents for Payload { } } -impl Serialize for Payload +impl Serialize + for Payload where Ipld: From, arguments::Named: From, @@ -161,9 +163,9 @@ where S: Serializer, { let mut state = serializer.serialize_struct("delegation::Payload", 9)?; - state.serialize_field("iss", &self.issuer)?; - state.serialize_field("sub", &self.subject)?; - state.serialize_field("aud", &self.audience)?; + state.serialize_field("iss", &self.issuer.clone().into().to_string())?; + state.serialize_field("sub", &self.subject.clone().into().to_string())?; + state.serialize_field("aud", &self.audience.clone().into().to_string())?; state.serialize_field("meta", &self.metadata)?; state.serialize_field("nonce", &self.nonce)?; state.serialize_field("exp", &self.expiration)?; @@ -189,14 +191,20 @@ where } } -impl<'de, T: ParseAbility + Deserialize<'de> + ToCommand, C: Condition + Deserialize<'de>> - Deserialize<'de> for Payload +impl< + 'de, + T: ParseAbility + Deserialize<'de> + ToCommand, + C: Condition + Deserialize<'de>, + DID: Did + Deserialize<'de>, + > Deserialize<'de> for Payload { fn deserialize(deserializer: D) -> Result where D: serde::Deserializer<'de>, { - struct DelegationPayloadVisitor(std::marker::PhantomData<(T, C)>); + struct DelegationPayloadVisitor( + std::marker::PhantomData<(T, C, DID)>, + ); const FIELDS: &'static [&'static str] = &[ "iss", "sub", "aud", "cmd", "args", "cond", "meta", "nonce", "exp", "nbf", @@ -206,9 +214,10 @@ impl<'de, T: ParseAbility + Deserialize<'de> + ToCommand, C: Condition + Deseria 'de, T: ParseAbility + ToCommand + Deserialize<'de>, C: Condition + Deserialize<'de>, - > Visitor<'de> for DelegationPayloadVisitor + DID: Did + Deserialize<'de>, + > Visitor<'de> for DelegationPayloadVisitor { - type Value = Payload; + type Value = Payload; fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { formatter.write_str("struct delegation::Payload") @@ -333,7 +342,8 @@ impl<'de, T: ParseAbility + Deserialize<'de> + ToCommand, C: Condition + Deseria impl< T: ParseAbility + Command + for<'de> Deserialize<'de>, C: Condition + for<'de> Deserialize<'de>, - > TryFrom for Payload + DID: Did + for<'de> Deserialize<'de>, + > TryFrom for Payload { type Error = SerdeError; @@ -342,23 +352,23 @@ impl< } } -impl From> for Ipld { - fn from(payload: Payload) -> Self { +impl From> for Ipld { + fn from(payload: Payload) -> Self { payload.into() } } -impl Payload { +impl Payload { pub fn check<'a>( &'a self, - proofs: Vec>, + proofs: Vec>, now: SystemTime, ) -> Result<(), DelegationError<::Error>> where T::Hierarchy: Clone + Into>, T: Clone + Checkable + Prove + Into>, { - let start: Acc = Acc { + let start: Acc = Acc { issuer: self.issuer.clone(), subject: self.subject.clone(), hierarchy: T::Hierarchy::from(self.ability_builder.clone()), @@ -392,28 +402,28 @@ impl Payload { } #[derive(Debug, Clone)] -struct Acc { - issuer: Did, - subject: Did, +struct Acc { + issuer: DID, + subject: DID, hierarchy: H, } -impl Acc { +impl Acc { // FIXME this should move to Delegable? fn step<'a, C: Condition>( &self, - proof: &Payload, + proof: &Payload, args: &arguments::Named, now: SystemTime, ) -> Result::Error>> where H: Prove + Clone + Into>, { - if let Err(_) = self.issuer.check_same(&proof.audience) { + if self.issuer != proof.audience { return Err(EnvelopeError::InvalidSubject.into()); } - if let Err(_) = self.subject.check_same(&proof.subject) { + if self.subject != proof.subject { return Err(EnvelopeError::MisalignedIssAud.into()); } diff --git a/src/delegation/store.rs b/src/delegation/store.rs index b0010146..062ef0f3 100644 --- a/src/delegation/store.rs +++ b/src/delegation/store.rs @@ -13,29 +13,29 @@ use std::{ use web_time::SystemTime; // NOTE the T here is the builder... FIXME add one layer up and call T::Builder? May be confusing? -pub trait Store { +pub trait Store { type Error; - fn get(&self, cid: &Cid) -> Result>, Self::Error>; + fn get(&self, cid: &Cid) -> Result>, Self::Error>; // FIXME add a variant that calculated the CID from the capsulre header? // FIXME that means changing the name to insert_by_cid or similar - fn insert(&mut self, cid: Cid, delegation: Delegation) -> Result<(), Self::Error>; + fn insert(&mut self, cid: Cid, delegation: Delegation) -> Result<(), Self::Error>; fn revoke(&mut self, cid: Cid) -> Result<(), Self::Error>; fn get_chain( &self, - aud: &Did, - subject: &Did, + aud: &DID, + subject: &DID, builder: &B, now: &SystemTime, - ) -> Result)>>, Self::Error>; + ) -> Result)>>, Self::Error>; fn can_delegate( &self, - iss: &Did, - aud: &Did, + iss: &DID, + aud: &DID, builder: &B, now: &SystemTime, ) -> Result { @@ -101,24 +101,25 @@ pub trait Store { /// linkStyle 1 stroke:orange; /// ``` #[derive(Debug, Clone, PartialEq)] -pub struct MemoryStore { - ucans: BTreeMap>, - index: BTreeMap>>, +pub struct MemoryStore { + ucans: BTreeMap>, + index: BTreeMap>>, revocations: BTreeSet, } // FIXME check that UCAN is valid -impl Store for MemoryStore +impl Store + for MemoryStore where B::Hierarchy: PartialEq, { type Error = Infallible; - fn get(&self, cid: &Cid) -> Result>, Self::Error> { + fn get(&self, cid: &Cid) -> Result>, Self::Error> { Ok(self.ucans.get(cid)) } - fn insert(&mut self, cid: Cid, delegation: Delegation) -> Result<(), Self::Error> { + fn insert(&mut self, cid: Cid, delegation: Delegation) -> Result<(), Self::Error> { self.index .entry(delegation.payload.subject.clone()) .or_default() @@ -126,7 +127,7 @@ where .or_default() .insert(cid); - let hierarchy: Delegation = Delegation { + let hierarchy: Delegation = Delegation { signature: delegation.signature, payload: delegation.payload.map_ability(Into::into), }; @@ -142,11 +143,11 @@ where fn get_chain( &self, - aud: &Did, - subject: &Did, + aud: &DID, + subject: &DID, builder: &B, now: &SystemTime, - ) -> Result)>>, Self::Error> { + ) -> Result)>>, Self::Error> { match self.index.get(subject).and_then(|aud_map| aud_map.get(aud)) { None => Ok(None), Some(delegation_subtree) => { diff --git a/src/did.rs b/src/did.rs index db6059aa..6757cd69 100644 --- a/src/did.rs +++ b/src/did.rs @@ -7,11 +7,49 @@ pub mod key; use did_url::DID; use libipld_core::ipld::Ipld; use serde::{Deserialize, Serialize}; -use std::fmt; +use std::{fmt, string::ToString}; use thiserror::Error; -#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] -#[serde(into = "String", try_from = "String")] +#[cfg(feature = "eddsa")] +use ed25519_dalek; + +#[cfg(feature = "es256")] +use p256; + +#[cfg(feature = "es256k")] +use k256; + +#[cfg(feature = "es384")] +use p384; + +#[cfg(feature = "es512")] +use crate::crypto::p521; + +#[cfg(feature = "rs256")] +use crate::crypto::rs256; + +#[cfg(feature = "rs512")] +use crate::crypto::rs512; + +#[cfg(feature = "bls")] +use blst; + +pub trait Did: + PartialEq + TryFrom + Into + signature::Verifier +{ + type Signature: signature::SignatureEncoding; +} + +// impl Did for ed25519_dalek::VerifyingKey {} +// +//impl Did for key::Verifier {} +// +pub enum Preset { + // Key(key::Verifier), + // Dns(did_url::DID), +} + +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] /// A [Decentralized Identifier (DID)][wiki] /// /// This is a newtype wrapper around the [`DID`] type from the [`did_url`] crate. @@ -26,49 +64,70 @@ use thiserror::Error; /// ``` /// /// [wiki]: https://en.wikipedia.org/wiki/Decentralized_identifier -pub struct Did(pub DID); +pub struct Newtype(pub DID); + +impl Serialize for Newtype { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + String::from(self.clone()).serialize(serializer) + } +} + +impl<'de> Deserialize<'de> for Newtype { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + let string = String::deserialize(deserializer)?; + Newtype::try_from(string).map_err(serde::de::Error::custom) + } +} -impl From for String { - fn from(did: Did) -> Self { +impl From for String { + fn from(did: Newtype) -> Self { did.0.to_string() } } -impl TryFrom for Did { +impl TryFrom for Newtype { type Error = >::Error; fn try_from(string: String) -> Result { - DID::parse(&string).map(Did) + DID::parse(&string).map(Newtype) } } -impl fmt::Display for Did { +impl fmt::Display for Newtype { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{}", self.0.to_string()) } } -impl From for Ipld { - fn from(did: Did) -> Self { +impl From for Ipld { + fn from(did: Newtype) -> Self { did.into() } } -impl TryFrom for Did { +impl TryFrom for Newtype { type Error = FromIpldError; fn try_from(ipld: Ipld) -> Result { match ipld { - Ipld::String(string) => Did::try_from(string).map_err(FromIpldError::StructuralError), + Ipld::String(string) => { + Newtype::try_from(string).map_err(FromIpldError::StructuralError) + } other => Err(FromIpldError::NotAnIpldString(other)), } } } -/// Errors that can occur when converting to or from a [`Did`] +/// Errors that can occur when converting to or from a [`Newtype`] #[derive(Debug, Clone, PartialEq, Error)] pub enum FromIpldError { - /// Strutural errors in the [`Did`] + /// Strutural errors in the [`Newtype`] #[error(transparent)] StructuralError(#[from] did_url::Error), diff --git a/src/invocation.rs b/src/invocation.rs index 9cfca04f..7a60c059 100644 --- a/src/invocation.rs +++ b/src/invocation.rs @@ -16,4 +16,4 @@ use crate::signature; /// For a version that can include [`Promise`][promise::Promise]s, /// wrap your `T` in [`invocation::Promised`](Promised) to get /// `Invocation>`. -pub type Invocation = signature::Envelope>; +pub type Invocation = signature::Envelope>; diff --git a/src/invocation/payload.rs b/src/invocation/payload.rs index 164c894e..b3767f4b 100644 --- a/src/invocation/payload.rs +++ b/src/invocation/payload.rs @@ -2,31 +2,22 @@ use super::resolvable::Resolvable; use crate::{ ability::{arguments, command::ToCommand}, capsule::Capsule, - delegation, - delegation::{ - condition::Condition, - error::{DelegationError, EnvelopeError}, - Delegable, - }, - did::Did, + delegation::{self, condition::Condition, error::DelegationError, Delegable}, + did::{self, Did}, nonce::Nonce, - proof::{ - checkable::Checkable, - prove::{Prove, Success}, - same::CheckSame, - }, + proof::{checkable::Checkable, prove::Prove}, time::Timestamp, }; use libipld_core::{cid::Cid, error::SerdeError, ipld::Ipld, serde as ipld_serde}; use serde::{Serialize, Serializer}; -use std::{collections::BTreeMap, fmt::Debug, marker::PhantomData}; +use std::{collections::BTreeMap, fmt::Debug}; use web_time::SystemTime; #[derive(Debug, Clone, PartialEq)] -pub struct Payload { - pub issuer: Did, - pub subject: Did, - pub audience: Option, +pub struct Payload { + pub issuer: DID, + pub subject: DID, + pub audience: Option, pub ability: T, @@ -45,10 +36,10 @@ pub struct Payload { // // This probably means putting the delegation T back to the upper level and bieng explicit about // the T::Builder in the type -impl Payload { +impl Payload { pub fn check( self, - proofs: Vec::Hierarchy, C>>, + proofs: Vec::Hierarchy, C, DID>>, now: SystemTime, ) -> Result<(), DelegationError<<::Hierarchy as Prove>::Error>> where @@ -56,19 +47,21 @@ impl Payload { T::Builder: Clone + Checkable + Prove + Into>, ::Hierarchy: Clone + Into>, { - let builder_payload: delegation::Payload = self.into(); + let builder_payload: delegation::Payload = self.into(); builder_payload.check(proofs, now) } } -impl Capsule for Payload { +impl Capsule for Payload { const TAG: &'static str = "ucan/i/1.0.0-rc.1"; } -impl From> for delegation::Payload { - fn from(payload: Payload) -> Self { +impl From> + for delegation::Payload +{ + fn from(payload: Payload) -> Self { delegation::Payload { - issuer: payload.issuer, + issuer: payload.issuer.clone(), subject: payload.subject.clone(), audience: payload.audience.unwrap_or(payload.subject), @@ -84,11 +77,11 @@ impl From> for delegation::Payload> From> for arguments::Named { - fn from(payload: Payload) -> Self { +impl, DID: Did> From> for arguments::Named { + fn from(payload: Payload) -> Self { let mut args = arguments::Named::from_iter([ - ("iss".into(), payload.issuer.into()), - ("sub".into(), payload.subject.into()), + ("iss".into(), payload.issuer.into().to_string().into()), + ("sub".into(), payload.subject.into().to_string().into()), ("cmd".into(), payload.ability.to_command().into()), ("args".into(), payload.ability.into()), ( @@ -100,7 +93,7 @@ impl> From> for arguments::Named { ]); if let Some(audience) = payload.audience { - args.insert("aud".into(), audience.into()); + args.insert("aud".into(), audience.into().to_string().into()); } if let Some(not_before) = payload.not_before { @@ -114,17 +107,17 @@ impl> From> for arguments::Named { /// A variant that accepts [`Promise`]s. /// /// [`Promise`]: crate::invocation::promise::Promise -pub type Promised = Payload<::Promised>; +pub type Promised = Payload<::Promised, DID>; -impl Resolvable for Payload +impl Resolvable for Payload where arguments::Named: From, Ipld: From, T::Promised: ToCommand, { - type Promised = Promised; + type Promised = Promised; - fn try_resolve(promised: Promised) -> Result { + fn try_resolve(promised: Promised) -> Result { match ::try_resolve(promised.ability) { Ok(resolved_ability) => Ok(Payload { issuer: promised.issuer, @@ -149,10 +142,10 @@ where } } -impl Serialize for Payload +impl Serialize for Payload where - Payload: Clone, - InternalSerializer: From>, + Payload: Clone, + InternalSerializer: From>, { fn serialize(&self, serializer: S) -> Result where @@ -163,10 +156,10 @@ where } } -impl<'de, T> serde::Deserialize<'de> for Payload +impl<'de, T, DID: Did> serde::Deserialize<'de> for Payload where - Payload: TryFrom, - as TryFrom>::Error: Debug, + Payload: TryFrom, + as TryFrom>::Error: Debug, { fn deserialize(d: D) -> Result where @@ -181,9 +174,9 @@ where } } -impl TryFrom for Payload +impl TryFrom for Payload where - Payload: TryFrom, + Payload: TryFrom, { type Error = (); // FIXME @@ -193,8 +186,8 @@ where } } -impl From> for Ipld { - fn from(payload: Payload) -> Self { +impl From> for Ipld { + fn from(payload: Payload) -> Self { payload.into() } } @@ -203,11 +196,11 @@ impl From> for Ipld { #[serde(deny_unknown_fields)] struct InternalSerializer { #[serde(rename = "iss")] - issuer: Did, + issuer: did::Newtype, #[serde(rename = "sub")] - subject: Did, + subject: did::Newtype, #[serde(rename = "aud", skip_serializing_if = "Option::is_none")] - audience: Option, + audience: Option, #[serde(rename = "cmd")] command: String, @@ -292,12 +285,14 @@ impl TryFrom for InternalSerializer { // } // } -impl>> From> for InternalSerializer { - fn from(payload: Payload) -> Self { +impl>, DID: Did> From> + for InternalSerializer +{ + fn from(payload: Payload) -> Self { InternalSerializer { - issuer: payload.issuer, - subject: payload.subject, - audience: payload.audience, + issuer: payload.issuer.into(), + subject: payload.subject.into(), + audience: payload.audience.map(Into::into), command: payload.ability.to_command(), arguments: payload.ability.into(), diff --git a/src/invocation/store.rs b/src/invocation/store.rs index 68a7cd50..f15679cf 100644 --- a/src/invocation/store.rs +++ b/src/invocation/store.rs @@ -1,14 +1,15 @@ use super::Invocation; +use crate::did::Did; use libipld_core::cid::Cid; use std::collections::BTreeMap; use thiserror::Error; -pub trait Store { +pub trait Store { type Error; - fn get(&self, cid: &Cid) -> Result<&Invocation, Self::Error>; + fn get(&self, cid: &Cid) -> Result<&Invocation, Self::Error>; - fn put(&mut self, cid: Cid, invocation: Invocation) -> Result<(), Self::Error>; + fn put(&mut self, cid: Cid, invocation: Invocation) -> Result<(), Self::Error>; fn has(&self, cid: &Cid) -> Result { Ok(self.get(cid).is_ok()) @@ -16,22 +17,22 @@ pub trait Store { } #[derive(Debug, Clone, PartialEq)] -pub struct MemoryStore { - store: BTreeMap>, +pub struct MemoryStore { + store: BTreeMap>, } #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Error)] #[error("Delegation not found")] pub struct NotFound; -impl Store for MemoryStore { +impl Store for MemoryStore { type Error = NotFound; - fn get(&self, cid: &Cid) -> Result<&Invocation, Self::Error> { + fn get(&self, cid: &Cid) -> Result<&Invocation, Self::Error> { self.store.get(cid).ok_or(NotFound) } - fn put(&mut self, cid: Cid, invocation: Invocation) -> Result<(), Self::Error> { + fn put(&mut self, cid: Cid, invocation: Invocation) -> Result<(), Self::Error> { self.store.insert(cid, invocation); Ok(()) } diff --git a/src/proof/same.rs b/src/proof/same.rs index 5ca43d71..a6ff7640 100644 --- a/src/proof/same.rs +++ b/src/proof/same.rs @@ -48,17 +48,17 @@ pub trait CheckSame { fn check_same(&self, proof: &Self) -> Result<(), Self::Error>; } -impl CheckSame for Did { - type Error = Unequal; - - fn check_same(&self, proof: &Self) -> Result<(), Self::Error> { - if self.eq(proof) { - Ok(()) - } else { - Err(Unequal {}) - } - } -} +// impl CheckSame for Did { +// type Error = Unequal; +// +// fn check_same(&self, proof: &Self) -> Result<(), Self::Error> { +// if self.eq(proof) { +// Ok(()) +// } else { +// Err(Unequal {}) +// } +// } +// } impl CheckSame for Option { type Error = OptionalFieldError; diff --git a/src/receipt.rs b/src/receipt.rs index a76bd704..9f3160ac 100644 --- a/src/receipt.rs +++ b/src/receipt.rs @@ -11,4 +11,4 @@ pub use responds::Responds; use crate::signature; /// The complete, signed receipt of an [`Invocation`][`crate::invocation::Invocation`]. -pub type Receipt = signature::Envelope>; +pub type Receipt = signature::Envelope>; diff --git a/src/receipt/payload.rs b/src/receipt/payload.rs index 4e6a654b..4ba6f9b1 100644 --- a/src/receipt/payload.rs +++ b/src/receipt/payload.rs @@ -16,14 +16,14 @@ use std::{collections::BTreeMap, fmt, fmt::Debug}; /// /// [`Invocation`]: crate::invocation::Invocation #[derive(Debug, Clone, PartialEq)] -pub struct Payload { +pub struct Payload { /// The issuer of the [`Receipt`]. /// /// This [`Did`] *must* match the signature on /// the outer layer of [`Receipt`]. /// /// [`Receipt`]: super::Receipt - pub issuer: Did, + pub issuer: DID, /// The [`Cid`] of the [`Invocation`] that was run. /// @@ -66,11 +66,11 @@ pub struct Payload { pub issued_at: Option, } -impl Capsule for Payload { +impl Capsule for Payload { const TAG: &'static str = "ucan/r/1.0.0-rc.1"; } -impl Serialize for Payload +impl Serialize for Payload where T::Success: Serialize, { @@ -79,7 +79,7 @@ where S: Serializer, { let mut state = serializer.serialize_struct("receipt::Payload", 8)?; - state.serialize_field("iss", &self.issuer)?; + state.serialize_field("iss", &self.issuer.clone().into())?; state.serialize_field("ran", &self.ran)?; state.serialize_field("out", &self.out)?; state.serialize_field("next", &self.next)?; @@ -92,7 +92,7 @@ where } } -impl<'de, T: Responds> Deserialize<'de> for Payload +impl<'de, T: Responds, DID: Did + Deserialize<'de>> Deserialize<'de> for Payload where T::Success: Deserialize<'de>, { @@ -100,16 +100,16 @@ where where D: serde::Deserializer<'de>, { - struct ReceiptPayloadVisitor(std::marker::PhantomData); + struct ReceiptPayloadVisitor(std::marker::PhantomData<(T, DID)>); const FIELDS: &'static [&'static str] = &["iss", "ran", "out", "next", "prf", "meta", "nonce", "iat"]; - impl<'de, T: Responds> Visitor<'de> for ReceiptPayloadVisitor + impl<'de, T: Responds, DID: Did + Deserialize<'de>> Visitor<'de> for ReceiptPayloadVisitor where T::Success: Deserialize<'de>, { - type Value = Payload; + type Value = Payload; fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { formatter.write_str("struct delegation::Payload") @@ -205,15 +205,15 @@ where } } -impl From> for Ipld { - fn from(payload: Payload) -> Self { +impl From> for Ipld { + fn from(payload: Payload) -> Self { payload.into() } } -impl TryFrom for Payload +impl TryFrom for Payload where - T::Success: DeserializeOwned, + Payload: for<'de> Deserialize<'de>, { type Error = SerdeError; diff --git a/src/receipt/responds.rs b/src/receipt/responds.rs index 4e27ef3a..723d9e7d 100644 --- a/src/receipt/responds.rs +++ b/src/receipt/responds.rs @@ -1,5 +1,4 @@ -use crate::{did::Did, nonce::Nonce, task, task::Task}; -use libipld_core::ipld::Ipld; +use crate::{nonce::Nonce, task, task::Task}; /// Describe the relationship between an ability and the [`Receipt`]s. /// @@ -14,10 +13,10 @@ pub trait Responds { /// Convert an Ability (`Self`) into a [`Task`]. /// /// This is used to index receipts by a minimal [`Id`]. - fn to_task(&self, subject: Did, nonce: Nonce) -> Task; + fn to_task(&self, subject: did_url::DID, nonce: Nonce) -> Task; /// Convert an Ability (`Self`) directly into a [`Task`]'s [`Id`]. - fn to_task_id(&self, subject: Did, nonce: Nonce) -> task::Id { + fn to_task_id(&self, subject: did_url::DID, nonce: Nonce) -> task::Id { task::Id { cid: self.to_task(subject, nonce).into(), } diff --git a/src/receipt/store.rs b/src/receipt/store.rs index 6f20edd1..d9a7b53c 100644 --- a/src/receipt/store.rs +++ b/src/receipt/store.rs @@ -1,31 +1,31 @@ use super::{Receipt, Responds}; -use crate::task; +use crate::{did::Did, task}; use libipld_core::ipld::Ipld; use std::{collections::BTreeMap, fmt}; use thiserror::Error; /// A store for [`Receipt`]s indexed by their [`task::Id`]s. -pub trait Store { +pub trait Store { /// The error type representing all the ways a store operation can fail. type Error; /// Retrieve a [`Receipt`] by its [`task::Id`]. - fn get<'a>(&self, id: &task::Id) -> Result<&Receipt, Self::Error> + fn get<'a>(&self, id: &task::Id) -> Result<&Receipt, Self::Error> where ::Success: TryFrom; /// Store a [`Receipt`] by its [`task::Id`]. - fn put(&mut self, id: task::Id, receipt: Receipt) -> Result<(), Self::Error> + fn put(&mut self, id: task::Id, receipt: Receipt) -> Result<(), Self::Error> where ::Success: Into; } #[derive(Debug, Clone, PartialEq)] -pub struct MemoryStore +pub struct MemoryStore where T::Success: fmt::Debug + Clone + PartialEq, { - store: BTreeMap>, + store: BTreeMap>, } // FIXME extract @@ -33,17 +33,17 @@ where #[error("Delegation not found")] pub struct NotFound; -impl Store for MemoryStore +impl Store for MemoryStore where ::Success: TryFrom + Into + Clone + fmt::Debug + PartialEq, { type Error = NotFound; - fn get(&self, id: &task::Id) -> Result<&Receipt, Self::Error> { + fn get(&self, id: &task::Id) -> Result<&Receipt, Self::Error> { self.store.get(id).ok_or(NotFound) } - fn put(&mut self, id: task::Id, receipt: Receipt) -> Result<(), Self::Error> { + fn put(&mut self, id: task::Id, receipt: Receipt) -> Result<(), Self::Error> { self.store.insert(id, receipt); Ok(()) } diff --git a/src/task.rs b/src/task.rs index ed80fec7..60a0fbb2 100644 --- a/src/task.rs +++ b/src/task.rs @@ -1,6 +1,6 @@ //! Task indices for [`Receipt`][crate::receipt::Receipt] reverse lookup. -use crate::{ability::arguments, did::Did, nonce::Nonce}; +use crate::{ability::arguments, did, nonce::Nonce}; use libipld_cbor::DagCborCodec; use libipld_core::{ cid::{Cid, CidGeneric}, @@ -22,7 +22,7 @@ const SHA2_256: u64 = 0x12; #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] pub struct Task { /// The `subject`: root issuer, and arbiter of the semantics/namespace. - pub sub: Did, + pub sub: did::Newtype, /// A unique identifier for the particular task run. /// From 23472a2998d96cb03a8473311039ad777dae29e1 Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Tue, 13 Feb 2024 00:42:35 -0800 Subject: [PATCH 075/188] Tests passing again --- src/did.rs | 4 ++-- src/invocation.rs | 4 ++++ src/proof/same.rs | 8 ++++---- src/receipt/payload.rs | 2 +- 4 files changed, 11 insertions(+), 7 deletions(-) diff --git a/src/did.rs b/src/did.rs index 6757cd69..040d29ed 100644 --- a/src/did.rs +++ b/src/did.rs @@ -57,9 +57,9 @@ pub enum Preset { /// # Examples /// /// ```rust -/// # use ucan::did::Did; +/// # use ucan::did; /// # -/// let did = Did::try_from("did:example:123".to_string()).unwrap(); +/// let did = did::Newtype::try_from("did:example:123".to_string()).unwrap(); /// assert_eq!(did.0.method(), "example"); /// ``` /// diff --git a/src/invocation.rs b/src/invocation.rs index 7a60c059..fd9a092c 100644 --- a/src/invocation.rs +++ b/src/invocation.rs @@ -4,6 +4,7 @@ mod resolvable; pub mod promise; pub mod store; +use crate::did; pub use payload::{Payload, Promised}; pub use resolvable::Resolvable; @@ -17,3 +18,6 @@ use crate::signature; /// wrap your `T` in [`invocation::Promised`](Promised) to get /// `Invocation>`. pub type Invocation = signature::Envelope>; + +// FIXME use presnet ability, too +pub type Preset = Invocation; diff --git a/src/proof/same.rs b/src/proof/same.rs index a6ff7640..ed0bb4cb 100644 --- a/src/proof/same.rs +++ b/src/proof/same.rs @@ -9,22 +9,22 @@ use crate::did::Did; /// /// ```rust /// # use ucan::proof::same::CheckSame; -/// # use ucan::did::Did; +/// # use ucan::did; /// # /// struct HelloBuilder { -/// wave_at: Option, +/// wave_at: Option, /// } /// /// enum HelloError { /// MissingWaveAt, -/// WeDontTalkTo(Did) +/// WeDontTalkTo(did::Newtype) /// } /// /// impl CheckSame for HelloBuilder { /// type Error = HelloError; /// /// fn check_same(&self, proof: &Self) -> Result<(), Self::Error> { -/// if self.wave_at == Some(Did::try_from("did:example:mallory".to_string()).unwrap()) { +/// if self.wave_at == Some(did::Newtype::try_from("did:example:mallory".to_string()).unwrap()) { /// return Err(HelloError::WeDontTalkTo(self.wave_at.clone().unwrap())); /// } /// diff --git a/src/receipt/payload.rs b/src/receipt/payload.rs index 4ba6f9b1..54e8f829 100644 --- a/src/receipt/payload.rs +++ b/src/receipt/payload.rs @@ -6,7 +6,7 @@ use super::responds::Responds; use crate::{ability::arguments, capsule::Capsule, did::Did, nonce::Nonce, time::Timestamp}; use libipld_core::{cid::Cid, error::SerdeError, ipld::Ipld, serde as ipld_serde}; use serde::{ - de::{self, DeserializeOwned, MapAccess, Visitor}, + de::{self, MapAccess, Visitor}, ser::SerializeStruct, Deserialize, Serialize, Serializer, }; From 3b8d57db4d528d49c648eafd242115b0d169f360 Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Tue, 13 Feb 2024 01:53:39 -0800 Subject: [PATCH 076/188] Working on presets --- src/ability.rs | 2 + src/ability/crud/read.rs | 39 ++-------- src/ability/msg.rs | 19 +++++ src/ability/msg/send.rs | 2 +- src/ability/preset.rs | 132 +++++++++++++++++++++++++++++++++ src/crypto/bls12381/min_pk.rs | 2 +- src/crypto/bls12381/min_sig.rs | 2 +- src/delegation.rs | 6 +- src/did.rs | 10 ++- src/did/key.rs | 75 +++++++++++++++++++ src/invocation.rs | 9 ++- src/receipt.rs | 4 +- 12 files changed, 259 insertions(+), 43 deletions(-) create mode 100644 src/ability/preset.rs diff --git a/src/ability.rs b/src/ability.rs index f52c7966..63a05c60 100644 --- a/src/ability.rs +++ b/src/ability.rs @@ -41,6 +41,8 @@ pub mod msg; pub mod ucan; pub mod wasm; +pub mod preset; + pub mod arguments; pub mod command; diff --git a/src/ability/crud/read.rs b/src/ability/crud/read.rs index 6b899eec..9edefa19 100644 --- a/src/ability/crud/read.rs +++ b/src/ability/crud/read.rs @@ -1,6 +1,6 @@ //! Read from a resource. -use super::{error::ProofError, parents::MutableParents}; +use super::{any as crud, error::ProofError, parents::MutableParents}; use crate::{ ability::{arguments, command::Command}, invocation::{promise, promise::Resolves, Resolvable}, @@ -174,27 +174,15 @@ impl CheckSame for Builder { } impl CheckParents for Builder { - type Parents = MutableParents; + type Parents = crud::Any; type ParentError = (); // FIXME - fn check_parent(&self, other: &Self::Parents) -> Result<(), Self::ParentError> { + fn check_parent(&self, other: &crud::Any) -> Result<(), Self::ParentError> { if let Some(self_path) = &self.path { - match other { - MutableParents::Any(any) => { - // FIXME check the args, too! - if let Some(proof_path) = &any.path { - if self_path != proof_path { - return Err(()); - } - } - } - MutableParents::Mutate(mutate) => { - // FIXME check the args, too! - if let Some(proof_path) = &mutate.path { - if self_path != proof_path { - return Err(()); - } - } + // FIXME check the args, too! + if let Some(proof_path) = &other.path { + if self_path != proof_path { + return Err(()); } } } @@ -232,19 +220,6 @@ impl From for arguments::Named { } } -// impl From> for Promised { -// fn from(source: arguments::Named) -> Self { -// let path = source -// .get("path") -// .map(|ipld| ipld.clone().try_into().unwrap()); -// -// let args = source -// .get("args") -// .map(|ipld| ipld.clone().try_into().unwrap()); -// Promised { path, args } -// } -// } - impl From for Promised { fn from(r: Ready) -> Promised { Promised { diff --git a/src/ability/msg.rs b/src/ability/msg.rs index 708d2bf6..1f3c2090 100644 --- a/src/ability/msg.rs +++ b/src/ability/msg.rs @@ -7,3 +7,22 @@ pub mod send; pub use any::Any; pub use receive::Receive; + +// FIXME rename invokable? +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum Ready { + Receive(receive::Receive), + Send(send::Ready), +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum Builder { + Recieve(receive::Receive), + Send(send::Builder), +} + +#[derive(Debug, Clone, PartialEq)] +pub enum Promised { + // Recieve(receive::Promised), // FIXME + Send(send::Promised), +} diff --git a/src/ability/msg/send.rs b/src/ability/msg/send.rs index 1a0b7538..fb6dbc32 100644 --- a/src/ability/msg/send.rs +++ b/src/ability/msg/send.rs @@ -16,7 +16,7 @@ use url::Url; /// /// This is not generally used directly, unless you want to abstract /// over all of the `msg/send` variants. -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(deny_unknown_fields)] pub struct Generic { /// The recipient of the message diff --git a/src/ability/preset.rs b/src/ability/preset.rs new file mode 100644 index 00000000..88cc0bb8 --- /dev/null +++ b/src/ability/preset.rs @@ -0,0 +1,132 @@ +use super::{msg, wasm}; +use crate::{ + ability::{ + arguments, + command::{Command, ParseAbility}, + }, + delegation::Delegable, + invocation::{promise, Resolvable}, + proof::{ + checkable::Checkable, parentful::Parentful, parentless::NoParents, parents::CheckParents, + same::CheckSame, + }, +}; +use libipld_core::ipld::Ipld; +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, PartialEq)] //, Serialize, Deserialize)] +pub enum Ready { + //Crud(), + Msg(msg::Ready), + Wasm(wasm::run::Ready), +} + +#[derive(Debug, Clone, PartialEq)] //, Serialize, Deserialize)] +pub enum Builder { + Msg(msg::Builder), + Wasm(wasm::run::Builder), +} + +pub enum Parents { + Msg(msg::Any), +} // NOTE WasmRun has no parents + +impl CheckSame for Parents { + type Error = (); // FIXME + + fn check_same(&self, proof: &Self) -> Result<(), Self::Error> { + match (self, proof) { + (Parents::Msg(self_), Parents::Msg(proof)) => self_.check_same(proof), + } + } +} + +impl ParseAbility for Parents { + type Error = String; // FIXME + + fn try_parse(cmd: &str, args: &arguments::Named) -> Result { + todo!() + // FIXME Ok(Self {}) + } +} + +impl Delegable for Ready { + type Builder = Builder; +} + +impl CheckParents for Builder { + type Parents = Parents; + type ParentError = (); // FIXME + + fn check_parent(&self, proof: &Parents) -> Result<(), Self::ParentError> { + match self { + Builder::Msg(builder) => builder.check_parent(proof), + Builder::Wasm(builder) => Ok(()), + } + } +} + +impl Checkable for Builder { + type Hierarchy = Parentful; +} + +impl From for Builder { + fn from(ready: Ready) -> Self { + match ready { + Ready::Msg(ready) => Builder::Msg(ready.into()), + Ready::Wasm(ready) => Builder::Wasm(ready.into()), + } + } +} + +impl TryFrom for Ready { + type Error = (); // FIXME + + fn try_from(builder: Builder) -> Result { + match builder { + Builder::Msg(builder) => builder.try_into().map(Ready::Msg), + Builder::Wasm(builder) => builder.try_into().map(Ready::Wasm), + } + } +} + +#[derive(Debug, Clone, PartialEq)] //, Serialize, Deserialize)] +pub enum Promised { + Msg(msg::Promised), + Wasm(wasm::run::Promised), +} + +impl From for arguments::Named { + fn from(promised: Promised) -> Self { + match promised { + Promised::Msg(promised) => promised.into(), + Promised::Wasm(promised) => promised.into(), + } + } +} + +impl Resolvable for Ready { + type Promised = Promised; + + fn try_resolve(promised: Self::Promised) -> Result { + match promised { + Promised::Msg(promised) => Resolvable::try_resolve(promised) + .map(Ready::Msg) + .map_err(Promised::Msg), + Promised::Wasm(promised) => Resolvable::try_resolve(promised) + .map(Ready::Wasm) + .map_err(Promised::Wasm), + } + } +} + +impl CheckSame for Builder { + type Error = (); // FIXME + + fn check_same(&self, proof: &Self) -> Result<(), Self::Error> { + match (self, proof) { + (Builder::Wasm(builder), Builder::Wasm(proof)) => builder.check_same(proof), + _ => Err(()), + } + } +} diff --git a/src/crypto/bls12381/min_pk.rs b/src/crypto/bls12381/min_pk.rs index fde8887e..87a58f38 100644 --- a/src/crypto/bls12381/min_pk.rs +++ b/src/crypto/bls12381/min_pk.rs @@ -4,7 +4,7 @@ use blst::BLST_ERROR; use signature::{SignatureEncoding, Signer, Verifier}; /// A BLS12-381 MinPubKey signature -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq, Eq)] pub struct Signature(pub blst::min_pk::Signature); impl DomainSeparator for Signature { diff --git a/src/crypto/bls12381/min_sig.rs b/src/crypto/bls12381/min_sig.rs index 121ac66d..1298f098 100644 --- a/src/crypto/bls12381/min_sig.rs +++ b/src/crypto/bls12381/min_sig.rs @@ -4,7 +4,7 @@ use blst::BLST_ERROR; use signature::{SignatureEncoding, Signer, Verifier}; /// A BLS12-381 MinSig signature -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq, Eq)] pub struct Signature(pub blst::min_sig::Signature); impl DomainSeparator for Signature { diff --git a/src/delegation.rs b/src/delegation.rs index 1988b137..0845340c 100644 --- a/src/delegation.rs +++ b/src/delegation.rs @@ -11,6 +11,7 @@ pub use delegable::Delegable; pub use payload::Payload; use crate::{ + ability, did::{self, Did}, nonce::Nonce, proof::{checkable::Checkable, parents::CheckParents, same::CheckSame}, @@ -19,7 +20,7 @@ use crate::{ }; use condition::Condition; use libipld_core::ipld::Ipld; -use std::{collections::BTreeMap, convert::AsRef}; +use std::collections::BTreeMap; use web_time::SystemTime; /// A [`Delegation`] is a signed delegation [`Payload`] @@ -30,8 +31,7 @@ use web_time::SystemTime; /// FIXME pub type Delegation = signature::Envelope>; -// FIXME attach common abilities, too -pub type Preset = Delegation; +pub type Preset = Delegation; // FIXME checkable -> provable? diff --git a/src/did.rs b/src/did.rs index 040d29ed..f989b959 100644 --- a/src/did.rs +++ b/src/did.rs @@ -45,10 +45,18 @@ pub trait Did: //impl Did for key::Verifier {} // pub enum Preset { - // Key(key::Verifier), + Key(key::Verifier), // Dns(did_url::DID), } +impl signature::Verifier for Preset { + fn verify(&self, message: &[u8], signature: &key::Signature) -> Result<(), signature::Error> { + match self { + Preset::Key(verifier) => verifier.verify(message, signature), + } + } +} + #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] /// A [Decentralized Identifier (DID)][wiki] /// diff --git a/src/did/key.rs b/src/did/key.rs index 5d6c2c2b..07e0f281 100644 --- a/src/did/key.rs +++ b/src/did/key.rs @@ -2,6 +2,8 @@ pub mod traits; +use signature; + #[cfg(feature = "eddsa")] use ed25519_dalek; @@ -17,6 +19,9 @@ use p384; #[cfg(feature = "es512")] use crate::crypto::p521; +#[cfg(feature = "es512")] +use ::p521 as ext_p521; + #[cfg(feature = "rs256")] use crate::crypto::rs256; @@ -26,6 +31,9 @@ use crate::crypto::rs512; #[cfg(feature = "bls")] use blst; +#[cfg(feature = "bls")] +use crate::crypto::bls12381; + #[derive(Debug, Clone, PartialEq, Eq)] pub enum Verifier { #[cfg(feature = "eddsa")] @@ -55,3 +63,70 @@ pub enum Verifier { #[cfg(feature = "bls")] BlsMinSig(blst::min_sig::PublicKey), } + +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum Signature { + #[cfg(feature = "eddsa")] + Ed25519(ed25519_dalek::Signature), + + #[cfg(feature = "es256k")] + Sedcp256k1(k256::ecdsa::Signature), + + #[cfg(feature = "es256")] + P256(p256::ecdsa::Signature), + + #[cfg(feature = "es384")] + P384(p384::ecdsa::Signature), + + #[cfg(feature = "es512")] + P521(ext_p521::ecdsa::Signature), + + #[cfg(feature = "rs256")] + Rs256(rs256::Signature), + + #[cfg(feature = "rs512")] + Rs512(rs512::Signature), + + #[cfg(feature = "bls")] + BlsMinPk(bls12381::min_pk::Signature), + + #[cfg(feature = "bls")] + BlsMinSig(bls12381::min_sig::Signature), +} + +impl signature::Verifier for Verifier { + fn verify(&self, msg: &[u8], signature: &Signature) -> Result<(), signature::Error> { + match (self, signature) { + (Verifier::Ed25519(vk), Signature::Ed25519(sig)) => { + vk.verify(msg, sig).map_err(signature::Error::from_source) + } + (Verifier::Sedcp256k1(vk), Signature::Sedcp256k1(sig)) => { + vk.verify(msg, sig).map_err(signature::Error::from_source) + } + (Verifier::P256(vk), Signature::P256(sig)) => { + vk.verify(msg, sig).map_err(signature::Error::from_source) + } + (Verifier::P384(vk), Signature::P384(sig)) => { + vk.verify(msg, sig).map_err(signature::Error::from_source) + } + (Verifier::P521(vk), Signature::P521(sig)) => { + vk.verify(msg, sig).map_err(signature::Error::from_source) + } + (Verifier::Rs256(vk), Signature::Rs256(sig)) => { + vk.verify(msg, sig).map_err(signature::Error::from_source) + } + (Verifier::Rs512(vk), Signature::Rs512(sig)) => { + vk.verify(msg, sig).map_err(signature::Error::from_source) + } + (Verifier::BlsMinPk(vk), Signature::BlsMinPk(sig)) => { + vk.verify(msg, sig).map_err(signature::Error::from_source) + } + (Verifier::BlsMinSig(vk), Signature::BlsMinSig(sig)) => { + vk.verify(msg, sig).map_err(signature::Error::from_source) + } + (_, _) => Err(signature::Error::from_source( + "invalid signature type for verifier", + )), + } + } +} diff --git a/src/invocation.rs b/src/invocation.rs index fd9a092c..0bedcbb9 100644 --- a/src/invocation.rs +++ b/src/invocation.rs @@ -4,11 +4,10 @@ mod resolvable; pub mod promise; pub mod store; -use crate::did; pub use payload::{Payload, Promised}; pub use resolvable::Resolvable; -use crate::signature; +use crate::{ability, did, signature}; /// The complete, signed [`invocation::Payload`][Payload]. /// @@ -19,5 +18,9 @@ use crate::signature; /// `Invocation>`. pub type Invocation = signature::Envelope>; +// FIXME rename +pub type PromisedInvocation = Invocation; + // FIXME use presnet ability, too -pub type Preset = Invocation; +pub type Preset = Invocation; +pub type PresetPromised = Invocation; diff --git a/src/receipt.rs b/src/receipt.rs index 9f3160ac..7a5b7d9f 100644 --- a/src/receipt.rs +++ b/src/receipt.rs @@ -8,7 +8,9 @@ pub mod store; pub use payload::Payload; pub use responds::Responds; -use crate::signature; +use crate::{ability, did, signature}; /// The complete, signed receipt of an [`Invocation`][`crate::invocation::Invocation`]. pub type Receipt = signature::Envelope>; + +pub type Preset = Receipt; From 9012ce36f9cb6c0dec26629eca8cf35e00650eb3 Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Tue, 13 Feb 2024 15:17:59 -0800 Subject: [PATCH 077/188] Save point after writing the signature envelope --- flake.nix | 30 ++---- pre-commit.nix | 4 +- src/ability/:63 | 132 ++++++++++++++++++++++++++ src/ability/crud.rs | 97 +++++++++++++++++++ src/ability/msg.rs | 86 ++++++++++++++++- src/ability/msg/receive.rs | 42 ++++++++- src/ability/msg/send.rs | 4 +- src/ability/preset.rs | 65 ++++++++----- src/delegation.rs | 2 +- src/delegation/agent.rs | 20 ++-- src/delegation/store.rs | 3 +- src/did.rs | 3 +- src/invocation.rs | 4 +- src/proof/checkable.rs | 2 +- src/proof/parentless.rs | 2 +- src/receipt.rs | 4 +- src/signature.rs | 188 +++++++++++++++---------------------- 17 files changed, 504 insertions(+), 184 deletions(-) create mode 100644 src/ability/:63 diff --git a/flake.nix b/flake.nix index 3cd19cb8..bee92820 100644 --- a/flake.nix +++ b/flake.nix @@ -76,7 +76,7 @@ cargo-sort cargo-udeps cargo-watch - llvmPackages.bintools + # llvmPackages.bintools twiggy unstable.cargo-component wasm-bindgen-cli @@ -88,10 +88,18 @@ wasm-pack = "${pkgs.wasm-pack}/bin/wasm-pack"; wasm-opt = "${pkgs.binaryen}/bin/wasm-opt"; in rec { + formatter = pkgs.alejandra; + + # NOTE: blst requires --target=wasm32 support in Clang, which MacOS system clang doesn't provide + # FIXME This explicitely does not get pulled in under devshell + # stdenv = pkgs.clangStdenv; + devShells.default = pkgs.devshell.mkShell { name = "ucan"; - imports = [./pre-commit.nix]; + imports = [ + ./pre-commit.nix + ]; packages = with pkgs; [ @@ -104,7 +112,6 @@ protobuf unstable.nodejs_20 unstable.nodePackages.pnpm - unstable.wasmtime ] ++ format-pkgs ++ cargo-installs @@ -248,12 +255,6 @@ name = "watch:test:host"; help = "Run all tests on save"; category = "watch"; - command = "${cargo} watch --clear --exec test"; - } - { - name = "watch:test:docs:host"; - help = "Run all tests on save"; - category = "watch"; command = "${cargo} watch --clear --exec 'test --features=mermaid_docs'"; } { @@ -262,12 +263,6 @@ category = "watch"; command = "${cargo} watch --clear --exec 'test --target=wasm32-unknown-unknown'"; } - { - name = "watch:test:docs:wasm"; - help = "Run all tests on save"; - category = "watch"; - command = "${cargo} watch --clear --exec 'test --target=wasm32-unknown-unknown --features=mermaid_docs'"; - } # Test { name = "test:all"; @@ -358,11 +353,6 @@ doCheck = false; cargoSha256 = "sha256-2aVCNz/Lw7364B5dgGaloVPcQHm2E+b/BOxF6Qlc8Hs="; }; - - formatter = pkgs.alejandra; - - # NOTE: blst requires --target=wasm32 support in Clang, which MacOS system clang doesn't provide - stdenv = pkgs.clangStdenv; } ); } diff --git a/pre-commit.nix b/pre-commit.nix index b4ed07d7..83e370ba 100644 --- a/pre-commit.nix +++ b/pre-commit.nix @@ -1,9 +1,7 @@ {pkgs, ...}: let pc = "${pkgs.pre-commit}/bin/pre-commit"; in { - config = { - devshell.startup.pre-commit.text = '' + config.devshell.startup.pre-commit.text = '' [ -e .git/hooks/pre-commit ] || (${pc} install --install-hooks && ${pc} install --hook-type commit-msg) ''; - }; } diff --git a/src/ability/:63 b/src/ability/:63 new file mode 100644 index 00000000..b1803b0c --- /dev/null +++ b/src/ability/:63 @@ -0,0 +1,132 @@ +use super::{msg, wasm}; +use crate::{ + ability::{ + arguments, + command::{Command, ParseAbility}, + }, + delegation::Delegable, + invocation::{promise, Resolvable}, + proof::{ + checkable::Checkable, parentful::Parentful, parentless::NoParents, parents::CheckParents, + same::CheckSame, + }, +}; +use libipld_core::ipld::Ipld; +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, PartialEq)] //, Serialize, Deserialize)] +pub enum Ready { + //Crud(), + Msg(msg::Ready), + Wasm(wasm::run::Ready), +} + +#[derive(Debug, Clone, PartialEq)] //, Serialize, Deserialize)] +pub enum Builder { + Msg(msg::Builder), + Wasm(wasm::run::Builder), +} + +pub enum Parents { + Msg(msg::Any), +} // NOTE WasmRun has no parents + +impl CheckSame for Parents { + type Error = (); // FIXME + + fn check_same(&self, proof: &Self) -> Result<(), Self::Error> { + match (self, proof) { + (Parents::Msg(self_), Parents::Msg(proof)) => self_.check_same(proof), + } + } +} + +impl ParseAbility for Parents { + type Error = String; // FIXME + + fn try_parse(cmd: &str, args: &arguments::Named) -> Result { + todo!() + // FIXME Ok(Self {}) + } +} + +impl Delegable for Ready { + type Builder = Builder; +} + +impl CheckParents for Builder { + type Parents = Parents; + type ParentError = (); // FIXME + + fn check_parent(&self, proof: &Parents) -> Result<(), Self::ParentError> { + match self { + Builder::Msg(builder) => builder.check_parent(proof), + Builder::Wasm(builder) => Ok(()), + } + } +} + +impl Checkable for Builder { + type Hierarchy = Parentful; +} + +impl From for Builder { + fn from(ready: Ready) -> Self { + match ready { + Ready::Msg(ready) => Builder::Msg(ready.into()), + Ready::Wasm(ready) => Builder::Wasm(ready.into()), + } + } +} + +impl TryFrom for Ready { + type Error = (); // FIXME + + fn try_from(builder: Builder) -> Result { + match builder { + Builder::Msg(builder) => builder.try_into().map(Ready::Msg), + Builder::Wasm(builder) => Ok(Ready::Wasm(builder.into())), + } + } +} + +#[derive(Debug, Clone, PartialEq)] //, Serialize, Deserialize)] +pub enum Promised { + Msg(msg::Promised), + Wasm(wasm::run::Promised), +} + +impl From for arguments::Named { + fn from(promised: Promised) -> Self { + match promised { + Promised::Msg(promised) => promised.into(), + Promised::Wasm(promised) => promised.into(), + } + } +} + +impl Resolvable for Ready { + type Promised = Promised; + + fn try_resolve(promised: Self::Promised) -> Result { + match promised { + Promised::Msg(promised) => Resolvable::try_resolve(promised) + .map(Ready::Msg) + .map_err(Promised::Msg), + Promised::Wasm(promised) => Resolvable::try_resolve(promised) + .map(Ready::Wasm) + .map_err(Promised::Wasm), + } + } +} + +impl CheckSame for Builder { + type Error = (); // FIXME + + fn check_same(&self, proof: &Self) -> Result<(), Self::Error> { + match (self, proof) { + (Builder::Wasm(builder), Builder::Wasm(proof)) => builder.check_same(proof), + _ => Err(()), + } + } +} diff --git a/src/ability/crud.rs b/src/ability/crud.rs index 39e6c79c..ccbd3512 100644 --- a/src/ability/crud.rs +++ b/src/ability/crud.rs @@ -51,5 +51,102 @@ pub use any::Any; pub use mutate::Mutate; pub use parents::MutableParents; +use crate::{ability::arguments, invocation::Resolvable, proof::same::CheckSame}; +use libipld_core::ipld::Ipld; + #[cfg(target_arch = "wasm32")] pub mod js; + +#[derive(Debug, Clone, PartialEq)] +pub enum Ready { + Create(create::Ready), + Read(read::Ready), + Update(update::Ready), + Destroy(destroy::Ready), +} + +#[derive(Debug, Clone, PartialEq)] +pub enum Builder { + Create(create::Builder), + Read(read::Builder), + Update(update::Builder), + Destroy(destroy::Builder), +} + +#[derive(Debug, Clone, PartialEq)] +pub enum Promised { + Create(create::Promised), + Read(read::Promised), + Update(update::Promised), + Destroy(destroy::Promised), +} + +impl From for arguments::Named { + fn from(promised: Promised) -> Self { + match promised { + Promised::Create(create) => create.into(), + Promised::Read(read) => read.into(), + Promised::Update(update) => update.into(), + Promised::Destroy(destroy) => destroy.into(), + } + } +} + +impl From for Builder { + fn from(ready: Ready) -> Self { + match ready { + Ready::Create(create) => Builder::Create(create.into()), + Ready::Read(read) => Builder::Read(read.into()), + Ready::Update(update) => Builder::Update(update.into()), + Ready::Destroy(destroy) => Builder::Destroy(destroy.into()), + } + } +} + +impl TryFrom for Ready { + type Error = (); // FIXME + + fn try_from(builder: Builder) -> Result { + match builder { + Builder::Create(create) => create.try_into().map(Ready::Create).map_err(|_| ()), + Builder::Read(read) => read.try_into().map(Ready::Read).map_err(|_| ()), + Builder::Update(update) => update.try_into().map(Ready::Update).map_err(|_| ()), + Builder::Destroy(destroy) => destroy.try_into().map(Ready::Destroy).map_err(|_| ()), + } + } +} + +impl CheckSame for Builder { + type Error = (); + + fn check_same(&self, other: &Self) -> Result<(), Self::Error> { + match (self, other) { + (Builder::Create(a), Builder::Create(b)) => a.check_same(b), + (Builder::Read(a), Builder::Read(b)) => a.check_same(b), + (Builder::Update(a), Builder::Update(b)) => a.check_same(b), + (Builder::Destroy(a), Builder::Destroy(b)) => a.check_same(b), + _ => Err(()), + } + } +} + +impl Resolvable for Ready { + type Promised = Promised; + + fn try_resolve(promised: Promised) -> Result { + match promised { + Promised::Create(create) => Resolvable::try_resolve(create) + .map(Ready::Create) + .map_err(Promised::Create), + Promised::Read(read) => Resolvable::try_resolve(read) + .map(Ready::Read) + .map_err(Promised::Read), + Promised::Update(update) => Resolvable::try_resolve(update) + .map(Ready::Update) + .map_err(Promised::Update), + Promised::Destroy(destroy) => Resolvable::try_resolve(destroy) + .map(Ready::Destroy) + .map_err(Promised::Destroy), + } + } +} diff --git a/src/ability/msg.rs b/src/ability/msg.rs index 1f3c2090..54a02095 100644 --- a/src/ability/msg.rs +++ b/src/ability/msg.rs @@ -8,21 +8,97 @@ pub mod send; pub use any::Any; pub use receive::Receive; +use crate::{ + ability::arguments, + invocation::Resolvable, + proof::{checkable::Checkable, parents::CheckParents, same::CheckSame}, +}; +use libipld_core::ipld::Ipld; + // FIXME rename invokable? -#[derive(Debug, Clone, PartialEq, Eq)] +#[derive(Debug, Clone, PartialEq)] pub enum Ready { - Receive(receive::Receive), Send(send::Ready), + Receive(receive::Receive), } -#[derive(Debug, Clone, PartialEq, Eq)] +#[derive(Debug, Clone, PartialEq)] pub enum Builder { - Recieve(receive::Receive), Send(send::Builder), + Receive(receive::Receive), } #[derive(Debug, Clone, PartialEq)] pub enum Promised { - // Recieve(receive::Promised), // FIXME Send(send::Promised), + Receive(receive::Promised), // FIXME +} + +impl TryFrom for Ready { + type Error = (); + + fn try_from(builder: Builder) -> Result { + match builder { + Builder::Send(send) => send.try_into().map(Ready::Send).map_err(|_| ()), + Builder::Receive(receive) => Ok(Ready::Receive(receive)), + } + } +} + +impl From for Builder { + fn from(ready: Ready) -> Self { + match ready { + Ready::Send(send) => Builder::Send(send.into()), + Ready::Receive(receive) => Builder::Receive(receive.into()), + } + } +} + +impl From for arguments::Named { + fn from(promised: Promised) -> Self { + match promised { + Promised::Send(send) => send.into(), + Promised::Receive(receive) => receive.into(), + } + } +} + +impl Resolvable for Ready { + type Promised = Promised; + + fn try_resolve(promised: Promised) -> Result { + match promised { + Promised::Send(send) => Resolvable::try_resolve(send) + .map(Ready::Send) + .map_err(Promised::Send), + Promised::Receive(receive) => Resolvable::try_resolve(receive) + .map(Ready::Receive) + .map_err(Promised::Receive), + } + } +} + +impl CheckSame for Builder { + type Error = (); // FIXME + + fn check_same(&self, proof: &Self) -> Result<(), Self::Error> { + match (self, proof) { + (Builder::Send(this), Builder::Send(that)) => this.check_same(that), + (Builder::Receive(this), Builder::Receive(that)) => this.check_same(that), + _ => Err(()), + } + } +} + +impl CheckParents for Builder { + type Parents = Any; + type ParentError = (); + + fn check_parent(&self, proof: &Self::Parents) -> Result<(), Self::ParentError> { + match (self, proof) { + (Builder::Send(this), Any) => this.check_parent(&Any), + (Builder::Receive(this), Any) => this.check_parent(&Any), + _ => Err(()), + } + } } diff --git a/src/ability/msg/receive.rs b/src/ability/msg/receive.rs index c8a34ce4..37f4aa23 100644 --- a/src/ability/msg/receive.rs +++ b/src/ability/msg/receive.rs @@ -1,12 +1,13 @@ //! The ability to receive messages use crate::{ - ability::command::Command, + ability::{arguments, command::Command}, + invocation::{promise, Resolvable}, proof::{checkable::Checkable, parentful::Parentful, parents::CheckParents, same::CheckSame}, + url, }; use libipld_core::{error::SerdeError, ipld::Ipld, serde as ipld_serde}; use serde::{Deserialize, Serialize}; -use url::Url; #[cfg_attr(doc, aquamarine::aquamarine)] /// The ability to receive messages @@ -41,7 +42,7 @@ use url::Url; pub struct Receive { /// An *optional* URL (e.g. email, DID, socket) to receive messages from. /// This assumes that the `subject` has the authority to issue such a capability. - pub from: Option, + pub from: Option, } // FIXME needs promisory version @@ -66,7 +67,8 @@ impl CheckParents for Receive { type ParentError = ::Error; fn check_parent(&self, proof: &Self::Parents) -> Result<(), Self::ParentError> { - self.from.check_same(&proof.from).map_err(|_| ()) + // self.from.check_same(&proof.from).map_err(|_| ()) + todo!() // FIXME } } @@ -83,3 +85,35 @@ impl TryFrom for Receive { ipld_serde::from_ipld(ipld) } } + +#[derive(Debug, Clone, PartialEq)] +pub struct Promised { + pub from: promise::Resolves>, +} + +impl From for arguments::Named { + fn from(promised: Promised) -> Self { + let mut args = arguments::Named::new(); + + match promised.from { + promise::Resolves::Ok(from) => { + args.insert("from".into(), from.into()); + } + promise::Resolves::Err(from) => { + args.insert("from".into(), from.into()); + } + } + + args + } +} + +impl Resolvable for Receive { + type Promised = Promised; + + fn try_resolve(p: Promised) -> Result { + promise::Resolves::try_resolve(p.from) + .map(|from| Receive { from }) + .map_err(|from| Promised { from }) + } +} diff --git a/src/ability/msg/send.rs b/src/ability/msg/send.rs index fb6dbc32..d5974af7 100644 --- a/src/ability/msg/send.rs +++ b/src/ability/msg/send.rs @@ -153,11 +153,11 @@ impl From for arguments::Named { impl From for arguments::Named { fn from(p: Promised) -> Self { - arguments::Named(BTreeMap::from_iter([ + arguments::Named::from_iter([ ("to".into(), p.to.map(url_newtype::Newtype).into()), ("from".into(), p.from.map(String::from).into()), ("message".into(), p.message.into()), - ])) + ]) } } diff --git a/src/ability/preset.rs b/src/ability/preset.rs index 88cc0bb8..4fc1428e 100644 --- a/src/ability/preset.rs +++ b/src/ability/preset.rs @@ -1,4 +1,4 @@ -use super::{msg, wasm}; +use super::{crud, msg, wasm}; use crate::{ ability::{ arguments, @@ -16,18 +16,21 @@ use serde::{Deserialize, Serialize}; #[derive(Debug, Clone, PartialEq)] //, Serialize, Deserialize)] pub enum Ready { - //Crud(), + Crud(crud::Ready), Msg(msg::Ready), Wasm(wasm::run::Ready), } #[derive(Debug, Clone, PartialEq)] //, Serialize, Deserialize)] pub enum Builder { + Crud(crud::Builder), Msg(msg::Builder), Wasm(wasm::run::Builder), } +#[derive(Debug, Clone, PartialEq)] //, Serialize, Deserialize)] pub enum Parents { + Crud(crud::MutableParents), Msg(msg::Any), } // NOTE WasmRun has no parents @@ -36,7 +39,11 @@ impl CheckSame for Parents { fn check_same(&self, proof: &Self) -> Result<(), Self::Error> { match (self, proof) { - (Parents::Msg(self_), Parents::Msg(proof)) => self_.check_same(proof), + (Parents::Msg(self_), Parents::Msg(proof_)) => self_.check_same(proof_).map_err(|_| ()), + (Parents::Crud(self_), Parents::Crud(proof_)) => { + self_.check_same(proof_).map_err(|_| ()) + } + _ => Err(()), } } } @@ -45,8 +52,15 @@ impl ParseAbility for Parents { type Error = String; // FIXME fn try_parse(cmd: &str, args: &arguments::Named) -> Result { - todo!() - // FIXME Ok(Self {}) + if let Ok(crud) = crud::MutableParents::try_parse(cmd, args) { + return Ok(Parents::Crud(crud)); + } + + if let Ok(msg) = msg::Any::try_parse(cmd, args) { + return Ok(Parents::Msg(msg)); + } + + Err("Nope".into()) } } @@ -54,14 +68,25 @@ impl Delegable for Ready { type Builder = Builder; } +impl CheckSame for Builder { + type Error = (); // FIXME + + fn check_same(&self, proof: &Self) -> Result<(), Self::Error> { + match (self, proof) { + (Builder::Wasm(builder), Builder::Wasm(proof)) => builder.check_same(proof), + _ => Err(()), + } + } +} + impl CheckParents for Builder { type Parents = Parents; type ParentError = (); // FIXME - fn check_parent(&self, proof: &Parents) -> Result<(), Self::ParentError> { - match self { - Builder::Msg(builder) => builder.check_parent(proof), - Builder::Wasm(builder) => Ok(()), + fn check_parent(&self, proof: &Self::Parents) -> Result<(), Self::ParentError> { + match (self, proof) { + (Builder::Msg(builder), Parents::Msg(proof)) => builder.check_parent(proof), + _ => Err(()), } } } @@ -73,6 +98,7 @@ impl Checkable for Builder { impl From for Builder { fn from(ready: Ready) -> Self { match ready { + Ready::Crud(ready) => Builder::Crud(ready.into()), Ready::Msg(ready) => Builder::Msg(ready.into()), Ready::Wasm(ready) => Builder::Wasm(ready.into()), } @@ -84,14 +110,16 @@ impl TryFrom for Ready { fn try_from(builder: Builder) -> Result { match builder { - Builder::Msg(builder) => builder.try_into().map(Ready::Msg), - Builder::Wasm(builder) => builder.try_into().map(Ready::Wasm), + Builder::Crud(builder) => builder.try_into().map(Ready::Crud).map_err(|_| ()), + Builder::Msg(builder) => builder.try_into().map(Ready::Msg).map_err(|_| ()), + Builder::Wasm(builder) => builder.try_into().map(Ready::Wasm).map_err(|_| ()), } } } #[derive(Debug, Clone, PartialEq)] //, Serialize, Deserialize)] pub enum Promised { + Crud(crud::Promised), Msg(msg::Promised), Wasm(wasm::run::Promised), } @@ -99,6 +127,7 @@ pub enum Promised { impl From for arguments::Named { fn from(promised: Promised) -> Self { match promised { + Promised::Crud(promised) => promised.into(), Promised::Msg(promised) => promised.into(), Promised::Wasm(promised) => promised.into(), } @@ -110,6 +139,9 @@ impl Resolvable for Ready { fn try_resolve(promised: Self::Promised) -> Result { match promised { + Promised::Crud(promised) => Resolvable::try_resolve(promised) + .map(Ready::Crud) + .map_err(Promised::Crud), Promised::Msg(promised) => Resolvable::try_resolve(promised) .map(Ready::Msg) .map_err(Promised::Msg), @@ -119,14 +151,3 @@ impl Resolvable for Ready { } } } - -impl CheckSame for Builder { - type Error = (); // FIXME - - fn check_same(&self, proof: &Self) -> Result<(), Self::Error> { - match (self, proof) { - (Builder::Wasm(builder), Builder::Wasm(proof)) => builder.check_same(proof), - _ => Err(()), - } - } -} diff --git a/src/delegation.rs b/src/delegation.rs index 0845340c..972f2506 100644 --- a/src/delegation.rs +++ b/src/delegation.rs @@ -29,7 +29,7 @@ use web_time::SystemTime; /// /// # Examples /// FIXME -pub type Delegation = signature::Envelope>; +pub type Delegation = signature::Envelope, DID::Signature>; pub type Preset = Delegation; diff --git a/src/delegation/agent.rs b/src/delegation/agent.rs index 375fcf53..5e337e4a 100644 --- a/src/delegation/agent.rs +++ b/src/delegation/agent.rs @@ -8,19 +8,27 @@ use web_time::SystemTime; pub struct Agent<'a, B: Checkable, C: Condition, DID: Did, S: Store> { pub did: &'a DID, pub store: &'a mut S, + + signer: &'a ::Signer, _marker: PhantomData<(B, C)>, } // FIXME show example of multiple hierarchies of "all things accepted" // delegating down to inner versions of this -impl<'a, B: Checkable, C: Condition + Clone, DID: Did + ToString + Clone, S: Store> - Agent<'a, B, C, DID, S> +impl< + 'a, + B: Checkable + Clone, + C: Condition + Clone, + DID: Did + ToString + Clone, + S: Store, + > Agent<'a, B, C, DID, S> { - pub fn new(did: &'a DID, store: &'a mut S) -> Self { + pub fn new(did: &'a DID, signer: &'a ::Signer, store: &'a mut S) -> Self { Self { did, store, + signer, _marker: PhantomData, } } @@ -51,8 +59,7 @@ impl<'a, B: Checkable, C: Condition + Clone, DID: Did + ToString + Clone, S: Sto conditions: new_conditions, }; - // FIXME add signer info - return Ok(Delegation::sign(payload)); + return Ok(Delegation::try_sign::(self.signer, payload).expect("FIXME")); } let to_delegate = &self @@ -79,8 +86,7 @@ impl<'a, B: Checkable, C: Condition + Clone, DID: Did + ToString + Clone, S: Sto not_before: not_before.map(Into::into), }; - // FIXME add signing material - Ok(Delegation::sign(payload)) + Ok(Delegation::try_sign::(self.signer, payload).expect("FIXME")) } pub fn recieve( diff --git a/src/delegation/store.rs b/src/delegation/store.rs index 062ef0f3..a736d191 100644 --- a/src/delegation/store.rs +++ b/src/delegation/store.rs @@ -8,6 +8,7 @@ use nonempty::NonEmpty; use std::{ collections::{BTreeMap, BTreeSet}, convert::Infallible, + fmt, ops::ControlFlow, }; use web_time::SystemTime; @@ -110,8 +111,6 @@ pub struct MemoryStore { // FIXME check that UCAN is valid impl Store for MemoryStore -where - B::Hierarchy: PartialEq, { type Error = Infallible; diff --git a/src/did.rs b/src/did.rs index f989b959..30784caf 100644 --- a/src/did.rs +++ b/src/did.rs @@ -37,7 +37,8 @@ use blst; pub trait Did: PartialEq + TryFrom + Into + signature::Verifier { - type Signature: signature::SignatureEncoding; + type Signature: signature::SignatureEncoding + PartialEq + fmt::Debug; + type Signer: signature::Signer; } // impl Did for ed25519_dalek::VerifyingKey {} diff --git a/src/invocation.rs b/src/invocation.rs index 0bedcbb9..395c53e8 100644 --- a/src/invocation.rs +++ b/src/invocation.rs @@ -7,7 +7,7 @@ pub mod store; pub use payload::{Payload, Promised}; pub use resolvable::Resolvable; -use crate::{ability, did, signature}; +use crate::{ability, did, did::Did, signature}; /// The complete, signed [`invocation::Payload`][Payload]. /// @@ -16,7 +16,7 @@ use crate::{ability, did, signature}; /// For a version that can include [`Promise`][promise::Promise]s, /// wrap your `T` in [`invocation::Promised`](Promised) to get /// `Invocation>`. -pub type Invocation = signature::Envelope>; +pub type Invocation = signature::Envelope, DID::Signature>; // FIXME rename pub type PromisedInvocation = Invocation; diff --git a/src/proof/checkable.rs b/src/proof/checkable.rs index 584c1ee7..5098c314 100644 --- a/src/proof/checkable.rs +++ b/src/proof/checkable.rs @@ -11,5 +11,5 @@ pub trait Checkable: CheckSame + Sized { /// The only options are [`Parentful`][super::parentful::Parentful] /// and [`Parentless`][super::parentless::Parentless], /// (which are the only instances of the unexported `Checker`) - type Hierarchy: CheckSame + Prove + From; + type Hierarchy: CheckSame + Prove + From + PartialEq; } diff --git a/src/proof/parentless.rs b/src/proof/parentless.rs index 2400b756..1af372c4 100644 --- a/src/proof/parentless.rs +++ b/src/proof/parentless.rs @@ -49,7 +49,7 @@ pub enum ParentlessError { /// This behaves as an alias for `Checkable::>`. pub trait NoParents {} -impl Checkable for T { +impl Checkable for T { type Hierarchy = Parentless; } diff --git a/src/receipt.rs b/src/receipt.rs index 7a5b7d9f..9dc335c3 100644 --- a/src/receipt.rs +++ b/src/receipt.rs @@ -8,9 +8,9 @@ pub mod store; pub use payload::Payload; pub use responds::Responds; -use crate::{ability, did, signature}; +use crate::{ability, did, did::Did, signature}; /// The complete, signed receipt of an [`Invocation`][`crate::invocation::Invocation`]. -pub type Receipt = signature::Envelope>; +pub type Receipt = signature::Envelope, DID::Signature>; pub type Preset = Receipt; diff --git a/src/signature.rs b/src/signature.rs index 6eb2bfc1..204e8ee4 100644 --- a/src/signature.rs +++ b/src/signature.rs @@ -1,25 +1,72 @@ //! Signatures and cryptographic envelopes. -use crate::capsule::Capsule; -use libipld_core::ipld::Ipld; +use crate::{capsule::Capsule, did::Did}; +use libipld_core::{ + cid::{Cid, CidGeneric}, + codec::{Codec, Encode}, + ipld::Ipld, + multihash::{Code, MultihashGeneric}, +}; use serde::{Deserialize, Serialize}; +use signature::SignatureEncoding; use std::collections::BTreeMap; +// FIXME #[cfg(feature = "dag-cbor")] +use libipld_cbor::DagCborCodec; +use signature::Signer; + /// A container associating a `payload` with its signature over it. #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] -pub struct Envelope { +pub struct Envelope { /// The signture of the `payload`. - pub signature: Signature, + pub signature: Signature, /// The payload that's being signed over. pub payload: T, } -impl Envelope { - // FIXME need key material - pub fn sign(payload: T) -> Envelope { - // FIXME - todo!() +impl, S: SignatureEncoding> Envelope { + pub fn try_sign( + signer: &DID::Signer, + payload: T, + ) -> Result::Signature>, ()> { + Self::try_sign_generic::( + signer, + DagCborCodec, + Code::Sha2_256, + payload, + ) + } + + pub fn try_sign_generic, DID: Did>( + signer: &DID::Signer, + codec: C, + hasher: H, + payload: T, + ) -> Result, ()> + // FIXME err = () + where + Ipld: Encode, + { + let ipld: Ipld = BTreeMap::from_iter([(T::TAG.into(), payload.clone().into())]).into(); + + let mut buffer = vec![]; + ipld.encode(codec, &mut buffer) + .expect("FIXME not dag-cbor? DagCborCodec to encode any arbitrary `Ipld`"); + + let cid: Cid = CidGeneric::new_v1( + codec.into(), + MultihashGeneric::wrap(hasher.into(), buffer.as_slice()) + .map_err(|_| ()) // FIXME + .expect("FIXME expect signing to work..."), + ); + + let sig = signer.try_sign(&cid.to_bytes()).map_err(|_| ())?; + + Ok(Envelope { + signature: Signature::One(sig), + payload, + }) } pub fn validate_signature(&self) -> Result<(), ()> { @@ -31,125 +78,44 @@ impl Envelope { // FIXME consider kicking Batch down the road for spec reasons? #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(untagged)] -pub enum Signature { - One(Vec), +pub enum Signature { + One(S), Batch { - signature: Vec, + signature: S, merkle_proof: Vec>, }, } -// #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] -// #[serde(transparent)] -// pub struct StaticVec { -// pub slice: Box<[T]>, -// } -// -// impl From> for StaticVec { -// fn from(vec: Vec) -> Self { -// Self { -// slice: vec.into_boxed_slice(), -// } -// } -// } -// -// impl From> for Vec { -// fn from(vec: StaticVec) -> Vec { -// vec.slice.into() -// } -// } -// -// #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] -// #[serde(transparent)] -// pub struct StaticString { -// string: Box, -// } -// -// impl From for StaticString { -// fn from(string: String) -> Self { -// Self { -// string: string.into_boxed_str(), -// } -// } -// } -// -// impl<'a> From<&'a str> for StaticString { -// fn from(s: &'a str) -> Self { -// Self { string: s.into() } -// } -// } -// -// impl<'a> From<&'a StaticString> for &'a str { -// fn from(s: &'a StaticString) -> &'a str { -// &s.string -// } -// } -// -// impl From for String { -// fn from(s: StaticString) -> String { -// s.string.into() -// } -// } - -impl From<&Signature> for Ipld { - fn from(signature: &Signature) -> Self { - match signature { - Signature::One(sig) => sig.clone().into(), - Signature::Batch { - signature, - merkle_proof, - } => { - let mut map = BTreeMap::new(); - let proof: Vec = merkle_proof.into_iter().map(|p| p.clone().into()).collect(); - map.insert("sig".into(), signature.clone().into()); - map.insert("prf".into(), proof.into()); - map.into() - } - } - } -} - -impl From for Ipld { - fn from(signature: Signature) -> Self { +impl> From> for Ipld { + fn from(signature: Signature) -> Self { match signature { Signature::One(sig) => sig.into(), Signature::Batch { signature, merkle_proof, - } => { - let mut map = BTreeMap::new(); - let proof: Vec = merkle_proof.into_iter().map(|p| p.into()).collect(); - map.insert("sig".into(), signature.into()); - map.insert("prf".into(), proof.into()); - map.into() - } + } => Ipld::List(merkle_proof.into_iter().map(|p| p.into()).collect()), } } } -// FIXME Store or BTreeMap? Also eliminate that Clone constraint -impl + Clone> From<&Envelope> for Ipld { - fn from(Envelope { signature, payload }: &Envelope) -> Self { - let mut inner = BTreeMap::new(); - inner.insert(T::TAG.into(), payload.clone().into()); // FIXME should be a link - - let mut map = BTreeMap::new(); - map.insert("sig".into(), signature.into()); - map.insert("pld".into(), Ipld::Map(inner)); +impl, S: SignatureEncoding + Into> From> for Ipld { + fn from(Envelope { signature, payload }: Envelope) -> Self { + let ipld: Ipld = BTreeMap::from_iter([(T::TAG.into(), payload.into())]).into(); - Ipld::Map(map) - } -} + let codec = DagCborCodec; // FIXME get this from the payload + let hasher = Code::Sha2_256; // FIXME get this from the payload -impl + Clone> From> for Ipld { - fn from(Envelope { signature, payload }: Envelope) -> Self { - let mut inner = BTreeMap::new(); - inner.insert(T::TAG.into(), payload.clone().into()); // FIXME should be a link + let mut buffer = vec![]; + ipld.encode(codec, &mut buffer) + .expect("FIXME not dag-cbor? DagCborCodec to encode any arbitrary `Ipld`"); - let mut map = BTreeMap::new(); - map.insert("sig".into(), signature.into()); - map.insert("pld".into(), Ipld::Map(inner)); + let cid = CidGeneric::new_v1( + codec.into(), + MultihashGeneric::wrap(hasher.into(), buffer.as_slice()) + .map_err(|_| ()) // FIXME + .expect("FIXME expect signing to work..."), + ); - Ipld::Map(map) + BTreeMap::from_iter([("sig".into(), signature.into()), ("pld".into(), cid.into())]).into() } } From cc5d305bd55a407e8267c131a0ce7430bf6908f9 Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Tue, 13 Feb 2024 23:37:14 -0800 Subject: [PATCH 078/188] VERY WIP invocation agent --- src/delegation.rs | 2 +- src/delegation/agent.rs | 12 ++- src/delegation/payload.rs | 7 ++ src/delegation/store.rs | 20 ++++- src/invocation.rs | 3 +- src/invocation/agent.rs | 155 ++++++++++++++++++++++++++++++++++++++ src/invocation/payload.rs | 7 ++ src/proof/same.rs | 17 +---- src/receipt.rs | 2 +- src/receipt/payload.rs | 11 ++- src/signature.rs | 107 +++++++++++++++++--------- 11 files changed, 285 insertions(+), 58 deletions(-) create mode 100644 src/invocation/agent.rs diff --git a/src/delegation.rs b/src/delegation.rs index 972f2506..f248ef26 100644 --- a/src/delegation.rs +++ b/src/delegation.rs @@ -29,7 +29,7 @@ use web_time::SystemTime; /// /// # Examples /// FIXME -pub type Delegation = signature::Envelope, DID::Signature>; +pub type Delegation = signature::Envelope, DID>; pub type Preset = Delegation; diff --git a/src/delegation/agent.rs b/src/delegation/agent.rs index 5e337e4a..277246fa 100644 --- a/src/delegation/agent.rs +++ b/src/delegation/agent.rs @@ -59,12 +59,18 @@ impl< conditions: new_conditions, }; - return Ok(Delegation::try_sign::(self.signer, payload).expect("FIXME")); + return Ok(Delegation::try_sign(self.signer, payload).expect("FIXME")); } let to_delegate = &self .store - .get_chain(&self.did, &subject, &ability_builder, &SystemTime::now()) + .get_chain( + &self.did, + &subject, + &ability_builder, + vec![], + &SystemTime::now(), + ) .map_err(DelegateError::StoreError)? .ok_or(DelegateError::ProofsNotFound)? .first() @@ -86,7 +92,7 @@ impl< not_before: not_before.map(Into::into), }; - Ok(Delegation::try_sign::(self.signer, payload).expect("FIXME")) + Ok(Delegation::try_sign(self.signer, payload).expect("FIXME")) } pub fn recieve( diff --git a/src/delegation/payload.rs b/src/delegation/payload.rs index 9e4dfc53..06569542 100644 --- a/src/delegation/payload.rs +++ b/src/delegation/payload.rs @@ -17,6 +17,7 @@ use crate::{ prove::{Prove, Success}, same::CheckSame, }, + signature::Verifiable, time::{TimeBoundError, Timestamp}, }; use libipld_core::{error::SerdeError, ipld::Ipld, serde as ipld_serde}; @@ -28,6 +29,12 @@ use serde::{ use std::{collections::BTreeMap, fmt, fmt::Debug}; use web_time::SystemTime; +impl Verifiable for Payload { + fn verifier(&self) -> &DID { + &self.issuer + } +} + /// The payload portion of a [`Delegation`][super::Delegation]. /// /// This contains the semantic information about the delegation, including the diff --git a/src/delegation/store.rs b/src/delegation/store.rs index a736d191..875bc3e3 100644 --- a/src/delegation/store.rs +++ b/src/delegation/store.rs @@ -1,9 +1,10 @@ use super::{condition::Condition, Delegation}; use crate::{ + ability::arguments, did::Did, proof::{checkable::Checkable, prove::Prove}, }; -use libipld_core::cid::Cid; +use libipld_core::{cid::Cid, ipld::Ipld}; use nonempty::NonEmpty; use std::{ collections::{BTreeMap, BTreeSet}, @@ -21,8 +22,12 @@ pub trait Store { // FIXME add a variant that calculated the CID from the capsulre header? // FIXME that means changing the name to insert_by_cid or similar + // FIXME rename put fn insert(&mut self, cid: Cid, delegation: Delegation) -> Result<(), Self::Error>; + // FIXME validate invocation + // sore invocation + // just... move to invocation fn revoke(&mut self, cid: Cid) -> Result<(), Self::Error>; fn get_chain( @@ -30,6 +35,7 @@ pub trait Store { aud: &DID, subject: &DID, builder: &B, + conditions: Vec, now: &SystemTime, ) -> Result)>>, Self::Error>; @@ -38,9 +44,10 @@ pub trait Store { iss: &DID, aud: &DID, builder: &B, + conditions: Vec, now: &SystemTime, ) -> Result { - self.get_chain(aud, iss, builder, now) + self.get_chain(aud, iss, builder, conditions, now) .map(|chain| chain.is_some()) } } @@ -111,6 +118,8 @@ pub struct MemoryStore { // FIXME check that UCAN is valid impl Store for MemoryStore +where + B::Hierarchy: Into>, { type Error = Infallible; @@ -145,6 +154,7 @@ impl Sto aud: &DID, subject: &DID, builder: &B, + conditions: Vec, now: &SystemTime, ) -> Result)>>, Self::Error> { match self.index.get(subject).and_then(|aud_map| aud_map.get(aud)) { @@ -181,6 +191,12 @@ impl Sto return ControlFlow::Continue(()); } + for condition in &conditions { + if !condition.validate(&d.payload.ability_builder.into()) { + return ControlFlow::Continue(()); + } + } + chain.push((cid, d)); if &d.payload.issuer == subject { diff --git a/src/invocation.rs b/src/invocation.rs index 395c53e8..e14e4d9a 100644 --- a/src/invocation.rs +++ b/src/invocation.rs @@ -1,6 +1,7 @@ mod payload; mod resolvable; +pub mod agent; pub mod promise; pub mod store; @@ -16,7 +17,7 @@ use crate::{ability, did, did::Did, signature}; /// For a version that can include [`Promise`][promise::Promise]s, /// wrap your `T` in [`invocation::Promised`](Promised) to get /// `Invocation>`. -pub type Invocation = signature::Envelope, DID::Signature>; +pub type Invocation = signature::Envelope, DID>; // FIXME rename pub type PromisedInvocation = Invocation; diff --git a/src/invocation/agent.rs b/src/invocation/agent.rs new file mode 100644 index 00000000..2e284691 --- /dev/null +++ b/src/invocation/agent.rs @@ -0,0 +1,155 @@ +use super::{payload::Payload, store::Store, Invocation, Resolvable}; +use crate::{ + delegation, + delegation::{condition::Condition, Delegable, Delegation}, + did::Did, + nonce::Nonce, + signature::Verifiable, + time::JsTime, +}; +use libipld_core::{cid::Cid, ipld::Ipld}; +use std::{collections::BTreeMap, marker::PhantomData}; +use thiserror::Error; +use web_time::SystemTime; + +pub struct Agent< + 'a, + T: Resolvable + Delegable, + C: Condition, + DID: Did, + S: Store, + P: Store, + D: delegation::store::Store, +> { + pub did: &'a DID, + + pub store: &'a mut S, + pub promised_store: &'a mut P, + pub delegation_store: &'a D, + + signer: &'a ::Signer, + marker: PhantomData<(T, C)>, +} + +impl< + 'a, + T: Resolvable + Delegable + Clone, + C: Condition, + DID: Did + ToString + Clone, + S: Store, + P: Store, + D: delegation::store::Store, + > Agent<'a, T, C, DID, S, P, D> +{ + pub fn new( + did: &'a DID, + signer: &'a ::Signer, + store: &'a mut S, + promised_store: &'a mut P, + delegation_store: &'a mut D, + ) -> Self { + Self { + did, + store, + promised_store, + delegation_store, + signer, + marker: PhantomData, + } + } + + pub fn invoke( + &mut self, + audience: Option<&DID>, + subject: &DID, + ability: T::Promised, + metadata: BTreeMap, + cause: Option, + expiration: JsTime, + not_before: Option, + // FIXME err type + ) -> Result, ()> { + let proofs = self + .delegation_store + .get_chain( + audience, + subject, + ability.into(), + vec![], + &SystemTime::now(), + ) + .map_err(|_| ())? + .map(|chain| chain.map(|(cid, _)| *cid).into()) + .unwrap_or(vec![]); + + let mut seed = vec![]; + + let payload = Payload { + issuer: self.did.clone(), + subject: subject.clone(), + audience: audience.cloned(), + ability, + proofs, + metadata, + nonce: Nonce::generate_16(&mut seed), + cause, + expiration, + not_before, + }; + + let invocation = Invocation::try_sign(self.signer, &payload).map_err(|_| ())?; + let cid: Cid = invocation.into(); + self.store.put(cid, invocation); + Ok(invocation) + } + + // FIXME err = () + pub fn revoke(&mut self, cid: &Cid) -> Result<(), ()> { + todo!(); // FIXME create a revoke invocation + self.store.revoke(&cid) + } + + pub fn receive( + &self, + invocation: Invocation, + proofs: BTreeMap>, + // FIXME return type + ) -> Result, ()> { + // FIXME needs varsig header + let cid = Cid::from(invocation); + + invocation + .verifier() + .verify(&cid.to_bytes(), &invocation.signature.to_bytes()) + .map_err(|_| ())?; + + // FIXME pull delegations out of the store and check them + + match Resolvable::try_resolve(&invocation.payload) { + Ok(resolved) => { + // FIXME promised store + self.store.put(cid, resolved).map_err(|_| ())?; + } + Err(unresolved) => self.promised_store.put(cid, unresolved).map_err(|_| ())?, + } + + // FIXME + // FIXME promised store + self.store.put(cid, invocation).map_err(|_| ())?; + + for (cid, deleg) in proofs { + self.delegation_store.insert(cid, deleg).map_err(|_| ())?; + } + + if invocation.payload.audience != Some(*self.did) { + return Ok(Recipient::Other(invocation)); + } + + Ok(Recipient::You(invocation)) + } +} + +pub enum Recipient { + You(T), + Other(T), +} diff --git a/src/invocation/payload.rs b/src/invocation/payload.rs index b3767f4b..9aa7e61b 100644 --- a/src/invocation/payload.rs +++ b/src/invocation/payload.rs @@ -6,6 +6,7 @@ use crate::{ did::{self, Did}, nonce::Nonce, proof::{checkable::Checkable, prove::Prove}, + signature::Verifiable, time::Timestamp, }; use libipld_core::{cid::Cid, error::SerdeError, ipld::Ipld, serde as ipld_serde}; @@ -13,6 +14,12 @@ use serde::{Serialize, Serializer}; use std::{collections::BTreeMap, fmt::Debug}; use web_time::SystemTime; +impl Verifiable for Payload { + fn verifier(&self) -> &DID { + &self.issuer + } +} + #[derive(Debug, Clone, PartialEq)] pub struct Payload { pub issuer: DID, diff --git a/src/proof/same.rs b/src/proof/same.rs index ed0bb4cb..b616a01c 100644 --- a/src/proof/same.rs +++ b/src/proof/same.rs @@ -1,7 +1,6 @@ //! Check the delegation proof against another instance of the same type -use super::error::{OptionalFieldError, Unequal}; -use crate::did::Did; +use super::error::OptionalFieldError; /// Trait for checking if a proof of the same type is equally or less restrictive. /// @@ -39,7 +38,7 @@ use crate::did::Did; /// } pub trait CheckSame { /// Error type describing why a proof was insufficient. - type Error; // FIXME Rename CheckSameError? + type Error; /// Check if the proof is equally or less restrictive than the instance. /// @@ -48,18 +47,6 @@ pub trait CheckSame { fn check_same(&self, proof: &Self) -> Result<(), Self::Error>; } -// impl CheckSame for Did { -// type Error = Unequal; -// -// fn check_same(&self, proof: &Self) -> Result<(), Self::Error> { -// if self.eq(proof) { -// Ok(()) -// } else { -// Err(Unequal {}) -// } -// } -// } - impl CheckSame for Option { type Error = OptionalFieldError; diff --git a/src/receipt.rs b/src/receipt.rs index 9dc335c3..05544091 100644 --- a/src/receipt.rs +++ b/src/receipt.rs @@ -11,6 +11,6 @@ pub use responds::Responds; use crate::{ability, did, did::Did, signature}; /// The complete, signed receipt of an [`Invocation`][`crate::invocation::Invocation`]. -pub type Receipt = signature::Envelope, DID::Signature>; +pub type Receipt = signature::Envelope, DID>; pub type Preset = Receipt; diff --git a/src/receipt/payload.rs b/src/receipt/payload.rs index 54e8f829..b093f6c7 100644 --- a/src/receipt/payload.rs +++ b/src/receipt/payload.rs @@ -3,7 +3,10 @@ //! [`Invocation`]: crate::invocation::Invocation use super::responds::Responds; -use crate::{ability::arguments, capsule::Capsule, did::Did, nonce::Nonce, time::Timestamp}; +use crate::{ + ability::arguments, capsule::Capsule, did::Did, nonce::Nonce, signature::Verifiable, + time::Timestamp, +}; use libipld_core::{cid::Cid, error::SerdeError, ipld::Ipld, serde as ipld_serde}; use serde::{ de::{self, MapAccess, Visitor}, @@ -12,6 +15,12 @@ use serde::{ }; use std::{collections::BTreeMap, fmt, fmt::Debug}; +impl Verifiable for Payload { + fn verifier(&self) -> &DID { + &self.issuer + } +} + /// The payload (non-signature) portion of a response from an [`Invocation`]. /// /// [`Invocation`]: crate::invocation::Invocation diff --git a/src/signature.rs b/src/signature.rs index 204e8ee4..e40bee94 100644 --- a/src/signature.rs +++ b/src/signature.rs @@ -7,7 +7,7 @@ use libipld_core::{ ipld::Ipld, multihash::{Code, MultihashGeneric}, }; -use serde::{Deserialize, Serialize}; +use serde::{de::DeserializeOwned, Deserialize, Serialize}; use signature::SignatureEncoding; use std::collections::BTreeMap; @@ -15,35 +15,37 @@ use std::collections::BTreeMap; use libipld_cbor::DagCborCodec; use signature::Signer; +pub trait Verifiable { + fn verifier<'a>(&'a self) -> &'a DID; +} + +impl + Capsule, DID: Did> Verifiable for Envelope { + fn verifier(&self) -> &DID { + &self.payload.verifier() + } +} + /// A container associating a `payload` with its signature over it. -#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] -pub struct Envelope { +#[derive(Debug, Clone, PartialEq)] // , Serialize, Deserialize)] +pub struct Envelope + Capsule, DID: Did> { /// The signture of the `payload`. - pub signature: Signature, + pub signature: Signature, /// The payload that's being signed over. pub payload: T, } -impl, S: SignatureEncoding> Envelope { - pub fn try_sign( - signer: &DID::Signer, - payload: T, - ) -> Result::Signature>, ()> { - Self::try_sign_generic::( - signer, - DagCborCodec, - Code::Sha2_256, - payload, - ) +impl + Clone + Into, DID: Did> Envelope { + pub fn try_sign(signer: &DID::Signer, payload: T) -> Result, ()> { + Self::try_sign_generic::(signer, DagCborCodec, Code::Sha2_256, payload) } - pub fn try_sign_generic, DID: Did>( + pub fn try_sign_generic>( signer: &DID::Signer, codec: C, hasher: H, payload: T, - ) -> Result, ()> + ) -> Result, ()> // FIXME err = () where Ipld: Encode, @@ -64,42 +66,79 @@ impl, S: SignatureEncoding> Envelope { let sig = signer.try_sign(&cid.to_bytes()).map_err(|_| ())?; Ok(Envelope { - signature: Signature::One(sig), + signature: Signature::Solo(sig), payload, }) } pub fn validate_signature(&self) -> Result<(), ()> { - // FIXME - todo!() + // FIXME need varsig + let codec = todo!(); + let hasher = todo!(); + + let mut buffer = vec![]; + let ipld: Ipld = BTreeMap::from_iter([(T::TAG.into(), self.payload.clone().into())]).into(); + ipld.encode(codec, &mut buffer) + .expect("FIXME not dag-cbor? DagCborCodec to encode any arbitrary `Ipld`"); + + let cid: Cid = CidGeneric::new_v1( + codec.into(), + MultihashGeneric::wrap(hasher.into(), buffer.as_slice()) + .map_err(|_| ()) // FIXME + .expect("FIXME expect signing to work..."), + ); + + match self.signature { + Signature::Solo(sig) => self + .verifier() + .verify(&cid.to_bytes(), &sig) + .map_err(|_| ()), + } } } // FIXME consider kicking Batch down the road for spec reasons? -#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] -#[serde(untagged)] +#[derive(Debug, Clone, PartialEq)] // , Serialize, Deserialize)] + // #[serde(untagged)] pub enum Signature { - One(S), - Batch { - signature: S, - merkle_proof: Vec>, - }, + Solo(S), + // Batch { + // signature: S, + // root: Vec, + // merkle_proof: Vec, + // }, } +//#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +//pub enum MerkleStep { +// Node(Vec, Vec, Direction), +// Turn(Direction), +// End, +//} +// +//#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +//pub enum Direction { +// Left = 0, +// Right = 1, +//} + impl> From> for Ipld { fn from(signature: Signature) -> Self { match signature { - Signature::One(sig) => sig.into(), - Signature::Batch { - signature, - merkle_proof, - } => Ipld::List(merkle_proof.into_iter().map(|p| p.into()).collect()), + Signature::Solo(sig) => sig.into(), + // Signature::Batch { + // signature, + // merkle_proof, + // } => Ipld::List(merkle_proof.into_iter().map(|p| p.into()).collect()), } } } -impl, S: SignatureEncoding + Into> From> for Ipld { - fn from(Envelope { signature, payload }: Envelope) -> Self { +impl + Capsule + Into, DID: Did> From> for Ipld +where + DID::Signature: Into, +{ + fn from(Envelope { signature, payload }: Envelope) -> Self { let ipld: Ipld = BTreeMap::from_iter([(T::TAG.into(), payload.into())]).into(); let codec = DagCborCodec; // FIXME get this from the payload From 1b6065836eed7fc31ffa5e0b65064be16498064e Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Thu, 15 Feb 2024 00:29:15 -0800 Subject: [PATCH 079/188] Working on invocaton agent --- src/ability/crud.rs | 47 ++++++++++++- src/ability/crud/create.rs | 14 ++++ src/ability/crud/destroy.rs | 13 ++++ src/ability/crud/parents.rs | 9 +-- src/ability/crud/read.rs | 14 ++++ src/ability/crud/update.rs | 14 ++++ src/ability/msg.rs | 11 ++- src/ability/msg/receive.rs | 15 ++++ src/ability/preset.rs | 1 + src/ability/ucan/revoke.rs | 8 +++ src/ability/wasm/run.rs | 10 +++ src/agent.rs | 4 +- src/delegation/:50 | 117 ------------------------------- src/delegation/agent.rs | 11 +-- src/delegation/payload.rs | 8 +-- src/delegation/store.rs | 33 ++++++--- src/invocation.rs | 11 +++ src/invocation/agent.rs | 129 ++++++++++++++++++++++++----------- src/invocation/payload.rs | 68 +++++++++--------- src/invocation/resolvable.rs | 4 +- src/invocation/store.rs | 69 +++++++++++++++++-- src/reader.rs | 47 +++++++------ src/store.rs | 6 +- src/time.rs | 4 +- src/ucan.rs | 2 +- 25 files changed, 410 insertions(+), 259 deletions(-) delete mode 100644 src/delegation/:50 diff --git a/src/ability/crud.rs b/src/ability/crud.rs index ccbd3512..aaf4eb54 100644 --- a/src/ability/crud.rs +++ b/src/ability/crud.rs @@ -49,9 +49,14 @@ pub mod update; pub use any::Any; pub use mutate::Mutate; -pub use parents::MutableParents; - -use crate::{ability::arguments, invocation::Resolvable, proof::same::CheckSame}; +pub use parents::*; + +use crate::{ + ability::arguments, + delegation::Delegable, + invocation::Resolvable, + proof::{checkable::Checkable, parentful::Parentful, parents::CheckParents, same::CheckSame}, +}; use libipld_core::ipld::Ipld; #[cfg(target_arch = "wasm32")] @@ -81,6 +86,42 @@ pub enum Promised { Destroy(destroy::Promised), } +impl Delegable for Ready { + type Builder = Builder; +} + +impl Checkable for Builder { + type Hierarchy = Parentful; +} + +// impl From for Builder { +// fn from(promised: Promised) -> Self { +// match promised { +// Promised::Create(create) => create.into(), +// Promised::Read(read) => read.into(), +// Promised::Update(update) => update.into(), +// Promised::Destroy(destroy) => destroy.into(), +// } +// } +// } + +impl CheckParents for Builder { + type Parents = MutableParents; + type ParentError = (); // FIXME + + fn check_parent(&self, parents: &MutableParents) -> Result<(), Self::ParentError> { + match self { + Builder::Create(create) => create.check_parent(parents.into()).map_err(|_| ()), + Builder::Update(update) => update.check_parent(parents.into()).map_err(|_| ()), + Builder::Destroy(destroy) => destroy.check_parent(parents.into()).map_err(|_| ()), + Builder::Read(read) => match parents { + MutableParents::Any(crud_any) => read.check_parent(crud_any).map_err(|_| ()), + _ => Err(()), + }, + } + } +} + impl From for arguments::Named { fn from(promised: Promised) -> Self { match promised { diff --git a/src/ability/crud/create.rs b/src/ability/crud/create.rs index 51f04b8e..f5b39ca9 100644 --- a/src/ability/crud/create.rs +++ b/src/ability/crud/create.rs @@ -2,6 +2,7 @@ use super::{error::ProofError, parents::MutableParents}; use crate::{ ability::{arguments, command::Command}, + delegation::Delegable, invocation::{promise, promise::Resolves, Resolvable}, ipld, proof::{ @@ -164,6 +165,10 @@ impl, A: TryFrom> TryFrom for Generic { } } +impl Delegable for Ready { + type Builder = Builder; +} + impl Checkable for Builder { type Hierarchy = Parentful; } @@ -264,6 +269,15 @@ impl From for Promised { } } +// impl From for Builder { +// fn from(promised: Promised) -> Self { +// Builder { +// path: promised.path.map(Into::into), +// args: promised.args.map(Into::into), +// } +// } +// } + impl Resolvable for Ready { type Promised = Promised; diff --git a/src/ability/crud/destroy.rs b/src/ability/crud/destroy.rs index b1ad4189..d3da7bef 100644 --- a/src/ability/crud/destroy.rs +++ b/src/ability/crud/destroy.rs @@ -2,6 +2,7 @@ use super::{error::ProofError, parents::MutableParents}; use crate::{ ability::{arguments, command::Command}, + delegation::Delegable, invocation::{promise, promise::Resolves, Resolvable}, ipld, proof::{ @@ -128,6 +129,18 @@ impl

Command for Generic

{ const COMMAND: &'static str = "crud/destroy"; } +impl Delegable for Ready { + type Builder = Builder; +} + +// impl From for Builder { +// fn from(promised: Promised) -> Self { +// Builder { +// path: promised.path.map(Into::into), +// } +// } +// } + impl> From> for Ipld { fn from(destroy: Generic

) -> Self { destroy.into() diff --git a/src/ability/crud/parents.rs b/src/ability/crud/parents.rs index 6c2f106f..4dd92d24 100644 --- a/src/ability/crud/parents.rs +++ b/src/ability/crud/parents.rs @@ -6,9 +6,13 @@ use super::error::ParentError; use crate::{ - ability::command::{ParseAbility, ToCommand}, + ability::{ + arguments, + command::{ParseAbility, ParseAbilityError, ToCommand}, + }, proof::{parents::CheckParents, same::CheckSame}, }; +use libipld_core::ipld::Ipld; use serde::{Deserialize, Serialize}; use thiserror::Error; @@ -72,9 +76,6 @@ impl ToCommand for MutableParents { } } -use crate::ability::{arguments, command::ParseAbilityError}; -use libipld_core::ipld::Ipld; - #[derive(Debug, Clone, Error)] pub enum ParseError { #[error("Invalid `crud/*` arguments: {0}")] diff --git a/src/ability/crud/read.rs b/src/ability/crud/read.rs index 9edefa19..3d503962 100644 --- a/src/ability/crud/read.rs +++ b/src/ability/crud/read.rs @@ -3,6 +3,7 @@ use super::{any as crud, error::ProofError, parents::MutableParents}; use crate::{ ability::{arguments, command::Command}, + delegation::Delegable, invocation::{promise, promise::Resolves, Resolvable}, ipld, proof::{ @@ -125,6 +126,19 @@ impl Command for Generic { const COMMAND: &'static str = "crud/read"; } +impl Delegable for Ready { + type Builder = Builder; +} + +// impl From for Builder { +// fn from(promised: Promised) -> Self { +// Builder { +// path: promised.path.map(Into::into), +// args: promised.args.map(Into::into), +// } +// } +// } + impl, A: Into> From> for Ipld { fn from(read: Generic) -> Self { read.into() diff --git a/src/ability/crud/update.rs b/src/ability/crud/update.rs index ff3d153c..9308e314 100644 --- a/src/ability/crud/update.rs +++ b/src/ability/crud/update.rs @@ -2,6 +2,7 @@ use super::{error::ProofError, parents::MutableParents}; use crate::{ ability::{arguments, command::Command}, + delegation::Delegable, invocation::{promise, promise::Resolves, Resolvable}, ipld, proof::{ @@ -132,6 +133,19 @@ impl Command for Generic { const COMMAND: &'static str = "crud/update"; } +impl Delegable for Ready { + type Builder = Builder; +} + +// impl From for Builder { +// fn from(promised: Promised) -> Self { +// Builder { +// path: promised.path.map(Into::into), +// args: promised.args.map(Into::into), +// } +// } +// } + impl, A: Into> From> for Ipld { fn from(create: Generic) -> Self { create.into() diff --git a/src/ability/msg.rs b/src/ability/msg.rs index 54a02095..cc8d6c35 100644 --- a/src/ability/msg.rs +++ b/src/ability/msg.rs @@ -10,8 +10,9 @@ pub use receive::Receive; use crate::{ ability::arguments, + delegation::Delegable, invocation::Resolvable, - proof::{checkable::Checkable, parents::CheckParents, same::CheckSame}, + proof::{checkable::Checkable, parentful::Parentful, parents::CheckParents, same::CheckSame}, }; use libipld_core::ipld::Ipld; @@ -34,6 +35,10 @@ pub enum Promised { Receive(receive::Promised), // FIXME } +impl Delegable for Ready { + type Builder = Builder; +} + impl TryFrom for Ready { type Error = (); @@ -102,3 +107,7 @@ impl CheckParents for Builder { } } } + +impl Checkable for Builder { + type Hierarchy = Parentful; +} diff --git a/src/ability/msg/receive.rs b/src/ability/msg/receive.rs index 37f4aa23..3897a65f 100644 --- a/src/ability/msg/receive.rs +++ b/src/ability/msg/receive.rs @@ -2,6 +2,7 @@ use crate::{ ability::{arguments, command::Command}, + delegation::Delegable, invocation::{promise, Resolvable}, proof::{checkable::Checkable, parentful::Parentful, parents::CheckParents, same::CheckSame}, url, @@ -45,12 +46,26 @@ pub struct Receive { pub from: Option, } +pub type Builder = Receive; + // FIXME needs promisory version impl Command for Receive { const COMMAND: &'static str = "msg/send"; } +impl Delegable for Receive { + type Builder = Receive; +} + +// impl From for Builder { +// fn from(promised: Promised) -> Self { +// Builder { +// from: promised.from.map(Into::into), +// } +// } +// } + impl Checkable for Receive { type Hierarchy = Parentful; } diff --git a/src/ability/preset.rs b/src/ability/preset.rs index 4fc1428e..6a523f1b 100644 --- a/src/ability/preset.rs +++ b/src/ability/preset.rs @@ -16,6 +16,7 @@ use serde::{Deserialize, Serialize}; #[derive(Debug, Clone, PartialEq)] //, Serialize, Deserialize)] pub enum Ready { + // FIXME UCAN Crud(crud::Ready), Msg(msg::Ready), Wasm(wasm::run::Ready), diff --git a/src/ability/ucan/revoke.rs b/src/ability/ucan/revoke.rs index d3da8d3a..c2d28dc5 100644 --- a/src/ability/ucan/revoke.rs +++ b/src/ability/ucan/revoke.rs @@ -31,6 +31,14 @@ impl Delegable for Ready { type Builder = Builder; } +// impl From for Builder { +// fn from(promised: Promised) -> Self { +// Builder { +// ucan: promised.ucan.map(Into::into), +// } +// } +// } + impl Resolvable for Ready { type Promised = Promised; diff --git a/src/ability/wasm/run.rs b/src/ability/wasm/run.rs index ab08db22..6ee7d6df 100644 --- a/src/ability/wasm/run.rs +++ b/src/ability/wasm/run.rs @@ -54,6 +54,16 @@ impl Resolvable for Ready { } } +impl From for Builder { + fn from(promised: Promised) -> Self { + Builder { + module: promised.module.try_resolve().ok(), + function: promised.function.try_resolve().ok(), + args: promised.args.try_resolve().ok(), + } + } +} + /// A variant meant for delegation, where fields may be omitted pub type Builder = Generic, Option, Option>>; diff --git a/src/agent.rs b/src/agent.rs index c26be6c1..e2a8a4cb 100644 --- a/src/agent.rs +++ b/src/agent.rs @@ -63,7 +63,7 @@ impl< } else { let mut conds = self .store - .get_chain(&self.did, &subject, &ability_builder, &SystemTime::now()) + .get_chain(&self.did, &subject, &ability_builder, SystemTime::now()) .map_err(|_| ())? // FIXME .first() .1 @@ -101,7 +101,7 @@ impl< Delegation { payload, signature } } - pub fn recieve_delegation() {} + pub fn receive_delegation() {} } // impl Agent { diff --git a/src/delegation/:50 b/src/delegation/:50 deleted file mode 100644 index c74e50a7..00000000 --- a/src/delegation/:50 +++ /dev/null @@ -1,117 +0,0 @@ -use super::{condition::Condition, payload::Payload, store::Store, Delegation}; -use crate::{did::Did, nonce::Nonce, proof::checkable::Checkable, time::JsTime}; -use libipld_core::{cid::Cid, ipld::Ipld}; -use std::{collections::BTreeMap, marker::PhantomData}; -use thiserror::Error; -use web_time::SystemTime; - -pub struct Agent<'a, B: Checkable, C: Condition, S: Store> { - pub did: &'a Did, - pub store: &'a mut S, - _marker: PhantomData<(B, C)>, -} - -// FIXME show example of multiple hierarchies of "all things accepted" -// delegating down to inner versions of this - -impl<'a, B: Checkable, C: Condition, S: Store> Agent<'a, B, C, S> { - pub fn new(did: &'a Did, store: &'a mut S) -> Self { - Self { - did, - store, - _marker: PhantomData, - } - } - - pub fn delegate( - &self, - cid: &Cid, // FIXME remove and generate from the capsule header? - audience: Did, - subject: Did, - ability_builder: B, - new_conditions: Vec, - metadata: BTreeMap, - expiration: JsTime, - not_before: Option, - ) -> Result, DelegateError> { - if !self - .store - .can_delegate(self.did, &audience, &ability_builder, &SystemTime::now()) - { - return Err(DelegateError::ProofsNotFound); - } - - let conditions = if subject == *self.did { - new_conditions - } else { - let mut conds = self - .store - .get_chain(&self.did, &subject, &ability_builder, &SystemTime::now()) - .map_err(|_| ())? // FIXME - .first() - .1 - .payload - .conditions; - - let mut new = new_conditions; - conds.append(&mut new); - conds - }; - - let mut salt = self.did.clone().to_string().into_bytes(); - - let payload = Payload { - issuer: self.did.clone(), - audience, - subject, - ability_builder, - conditions, - metadata, - nonce: Nonce::generate_16(&mut salt), - expiration: expiration.into(), - not_before: not_before.map(Into::into), - }; - - Ok(self.sign(payload)) - } - - pub fn recieve( - &self, - cid: &Cid, // FIXME remove and generate from the capsule header? - delegation: Delegation, - ) -> Result<(), ReceiveError<'a, >::Error>> { - if self.store.get(cid).is_ok() { - return Ok(()); - } - - if delegation.audience() != *self.did { - return Err(ReceiveError::WrongAudience(delegation.audience())); - } - - delegation - .validate_signature() - .map_err(|_| ReceiveError::InvalidSignature(cid))?; - - self.store - .insert(self.store, delegation) - .map_err(Into::into) - } -} - -#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Error)] -pub enum DelegateError { - #[error("The current agent does not have the necessary proofs to delegate.")] - ProofsNotFound, -} - -#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Error)] -pub enum ReceiveError<'a, StoreErr> { - #[error("The current agent ({0}) is not the intended audience of the delegation.")] - WrongAudience(&'a Did), - - #[error("Signature for UCAN with CID {0} is invalid.")] - InvalidSignature(&'a Cid), - - #[error(transparent)] - StoreError(#[from] StoreErr), -} diff --git a/src/delegation/agent.rs b/src/delegation/agent.rs index 277246fa..913a295b 100644 --- a/src/delegation/agent.rs +++ b/src/delegation/agent.rs @@ -42,6 +42,7 @@ impl< metadata: BTreeMap, expiration: JsTime, not_before: Option, + now: &SystemTime, ) -> Result, DelegateError<>::Error>> { let mut salt = self.did.clone().to_string().into_bytes(); let nonce = Nonce::generate_16(&mut salt); @@ -64,13 +65,7 @@ impl< let to_delegate = &self .store - .get_chain( - &self.did, - &subject, - &ability_builder, - vec![], - &SystemTime::now(), - ) + .get_chain(&self.did, &subject, &ability_builder, vec![], now) .map_err(DelegateError::StoreError)? .ok_or(DelegateError::ProofsNotFound)? .first() @@ -95,7 +90,7 @@ impl< Ok(Delegation::try_sign(self.signer, payload).expect("FIXME")) } - pub fn recieve( + pub fn receive( &mut self, cid: Cid, // FIXME remove and generate from the capsule header? delegation: Delegation, diff --git a/src/delegation/payload.rs b/src/delegation/payload.rs index 06569542..73d3368d 100644 --- a/src/delegation/payload.rs +++ b/src/delegation/payload.rs @@ -369,7 +369,7 @@ impl Payload { pub fn check<'a>( &'a self, proofs: Vec>, - now: SystemTime, + now: &SystemTime, ) -> Result<(), DelegationError<::Error>> where T::Hierarchy: Clone + Into>, @@ -421,7 +421,7 @@ impl Acc { &self, proof: &Payload, args: &arguments::Named, - now: SystemTime, + now: &SystemTime, ) -> Result::Error>> where H: Prove + Clone + Into>, @@ -434,12 +434,12 @@ impl Acc { return Err(EnvelopeError::MisalignedIssAud.into()); } - if SystemTime::from(proof.expiration.clone()) > now { + if SystemTime::from(proof.expiration.clone()) > *now { return Err(EnvelopeError::Expired.into()); } if let Some(nbf) = proof.not_before.clone() { - if SystemTime::from(nbf) > now { + if SystemTime::from(nbf) > *now { return Err(EnvelopeError::NotYetValid.into()); } } diff --git a/src/delegation/store.rs b/src/delegation/store.rs index 875bc3e3..af4ceb3f 100644 --- a/src/delegation/store.rs +++ b/src/delegation/store.rs @@ -18,7 +18,7 @@ use web_time::SystemTime; pub trait Store { type Error; - fn get(&self, cid: &Cid) -> Result>, Self::Error>; + fn get(&self, cid: &Cid) -> Result<&Delegation, Self::Error>; // FIXME add a variant that calculated the CID from the capsulre header? // FIXME that means changing the name to insert_by_cid or similar @@ -32,24 +32,35 @@ pub trait Store { fn get_chain( &self, - aud: &DID, + audience: &DID, subject: &DID, builder: &B, conditions: Vec, now: &SystemTime, - ) -> Result)>>, Self::Error>; + ) -> Result)>>, Self::Error>; fn can_delegate( &self, - iss: &DID, - aud: &DID, + issuer: &DID, + audience: &DID, builder: &B, conditions: Vec, now: &SystemTime, ) -> Result { - self.get_chain(aud, iss, builder, conditions, now) + self.get_chain(audience, issuer, builder, conditions, now) .map(|chain| chain.is_some()) } + + fn get_many( + &self, + cids: &[Cid], + ) -> Result>, Self::Error> { + cids.iter().try_fold(vec![], |mut acc, cid| { + let d: &Delegation = self.get(cid)?; + acc.push(d); + Ok(acc) + }) + } } #[cfg_attr(doc, aquamarine::aquamarine)] @@ -121,10 +132,10 @@ impl Sto where B::Hierarchy: Into>, { - type Error = Infallible; + type Error = (); // FIXME misisng - fn get(&self, cid: &Cid) -> Result>, Self::Error> { - Ok(self.ucans.get(cid)) + fn get(&self, cid: &Cid) -> Result<&Delegation, Self::Error> { + self.ucans.get(cid).ok_or(()) } fn insert(&mut self, cid: Cid, delegation: Delegation) -> Result<(), Self::Error> { @@ -156,7 +167,7 @@ where builder: &B, conditions: Vec, now: &SystemTime, - ) -> Result)>>, Self::Error> { + ) -> Result)>>, Self::Error> { match self.index.get(subject).and_then(|aud_map| aud_map.get(aud)) { None => Ok(None), Some(delegation_subtree) => { @@ -197,7 +208,7 @@ where } } - chain.push((cid, d)); + chain.push((*cid, d)); if &d.payload.issuer == subject { status = Status::Complete; diff --git a/src/invocation.rs b/src/invocation.rs index e14e4d9a..4d33d418 100644 --- a/src/invocation.rs +++ b/src/invocation.rs @@ -25,3 +25,14 @@ pub type PromisedInvocation = Invocation; // FIXME use presnet ability, too pub type Preset = Invocation; pub type PresetPromised = Invocation; + +impl Invocation { + fn map_ability(self, f: impl FnOnce(T) -> T) -> Self { + let mut payload = self.payload; + payload.ability = f(payload.ability); + Self { + payload, + signature: self.signature, + } + } +} diff --git a/src/invocation/agent.rs b/src/invocation/agent.rs index 2e284691..f23fb185 100644 --- a/src/invocation/agent.rs +++ b/src/invocation/agent.rs @@ -1,7 +1,8 @@ use super::{payload::Payload, store::Store, Invocation, Resolvable}; use crate::{ + ability::ucan, delegation, - delegation::{condition::Condition, Delegable, Delegation}, + delegation::{condition::Condition, Delegable}, did::Did, nonce::Nonce, signature::Verifiable, @@ -23,9 +24,12 @@ pub struct Agent< > { pub did: &'a DID, - pub store: &'a mut S, - pub promised_store: &'a mut P, pub delegation_store: &'a D, + pub invocation_store: &'a mut S, + pub revocation_store: &'a mut S, // FIXME just a BTRee Set pointing into invocatuin store? + + pub unresolved_promise_store: &'a mut P, + pub resolved_promise_store: &'a mut P, signer: &'a ::Signer, marker: PhantomData<(T, C)>, @@ -35,7 +39,7 @@ impl< 'a, T: Resolvable + Delegable + Clone, C: Condition, - DID: Did + ToString + Clone, + DID: Did + Clone, S: Store, P: Store, D: delegation::store::Store, @@ -44,15 +48,19 @@ impl< pub fn new( did: &'a DID, signer: &'a ::Signer, - store: &'a mut S, - promised_store: &'a mut P, + invocation_store: &'a mut S, delegation_store: &'a mut D, + revocation_store: &'a mut D, + unresolved_promise_store: &'a mut P, + resolved_promise_store: &'a mut P, ) -> Self { Self { did, - store, - promised_store, + invocation_store, delegation_store, + revocation_store, + unresolved_promise_store, + resolved_promise_store, signer, marker: PhantomData, } @@ -62,24 +70,19 @@ impl< &mut self, audience: Option<&DID>, subject: &DID, - ability: T::Promised, + ability: T::Promised, // FIXME Resolved needs Into metadata: BTreeMap, cause: Option, - expiration: JsTime, + expiration: Option, not_before: Option, + now: &SystemTime, // FIXME err type ) -> Result, ()> { let proofs = self .delegation_store - .get_chain( - audience, - subject, - ability.into(), - vec![], - &SystemTime::now(), - ) + .get_chain(self.did, subject, &ability.into(), vec![], now) .map_err(|_| ())? - .map(|chain| chain.map(|(cid, _)| *cid).into()) + .map(|chain| chain.map(|(cid, _)| cid).into()) .unwrap_or(vec![]); let mut seed = vec![]; @@ -93,26 +96,63 @@ impl< metadata, nonce: Nonce::generate_16(&mut seed), cause, - expiration, - not_before, + expiration: expiration.map(Into::into), + not_before: not_before.map(Into::into), }; let invocation = Invocation::try_sign(self.signer, &payload).map_err(|_| ())?; - let cid: Cid = invocation.into(); - self.store.put(cid, invocation); + let cid = Cid::from(invocation); Ok(invocation) } // FIXME err = () - pub fn revoke(&mut self, cid: &Cid) -> Result<(), ()> { - todo!(); // FIXME create a revoke invocation - self.store.revoke(&cid) - } + // FIXME move to revocation agent wit own traits? + // pub fn revoke( + // &mut self, + // subject: &DID, + // cause: Option, + // cid: Cid, + // now: JsTime, + // ) -> Result<(), ()> { + // let ability = ucan::revoke::Ready { ucan: cid }; + // let proofs = if subject == self.did { + // vec![] + // } else { + // self.delegation_store + // .get_chain( + // subject, + // self.did, + // &ability.into(), + // vec![], + // &SystemTime::now(), + // ) + // .map_err(|_| ())? + // .map(|chain| chain.map(|(cid, _)| *cid).into()) + // .unwrap_or(vec![]) + // }; + + // let payload = Payload { + // issuer: self.did.clone(), + // subject: self.did.clone(), + // audience: Some(self.did.clone()), + // ability, + // proofs, + // cause: None, + // metadata: BTreeMap::new(), + // nonce: Nonce::generate_16(&mut vec![]), + // expiration: None, + // not_before: None, + // }; + + // let invocation = Invocation::try_sign(self.signer, &payload).map_err(|_| ())?; + + // self.invocation_store.revoke(&cid)?; + // } pub fn receive( &self, invocation: Invocation, - proofs: BTreeMap>, + now: SystemTime, // FIXME return type ) -> Result, ()> { // FIXME needs varsig header @@ -123,23 +163,30 @@ impl< .verify(&cid.to_bytes(), &invocation.signature.to_bytes()) .map_err(|_| ())?; - // FIXME pull delegations out of the store and check them - - match Resolvable::try_resolve(&invocation.payload) { - Ok(resolved) => { - // FIXME promised store - self.store.put(cid, resolved).map_err(|_| ())?; + let payload: Payload = invocation.payload; + let resolved_payload = match payload.ability.try_resolve() { + Ok(resolved_payload) => { + // NOTE Already resolved when it came over the wire + let resolved_invocation = invocation.map_ability(|_| resolved_payload); + self.store.put(cid, resolved_invocation).map_err(|_| ())?; + resolved_payload } - Err(unresolved) => self.promised_store.put(cid, unresolved).map_err(|_| ())?, - } + Err(_) => { + // FIXME check if any of the unresolved promises are in the store + self.promised_store.put(cid, invocation).map_err(|_| ())?; + todo!() // return Ok(Recipient::Other(promised)); // FIXME + } + }; - // FIXME - // FIXME promised store - self.store.put(cid, invocation).map_err(|_| ())?; + let proof_payloads = self + .delegation_store + .get_many(&invocation.payload.proofs) + .map(|d| d.payload); - for (cid, deleg) in proofs { - self.delegation_store.insert(cid, deleg).map_err(|_| ())?; - } + resolved_payload + .into() + .check(&proof_payloads, now) + .map_err(|_| ())?; if invocation.payload.audience != Some(*self.did) { return Ok(Recipient::Other(invocation)); diff --git a/src/invocation/payload.rs b/src/invocation/payload.rs index 9aa7e61b..ad17031b 100644 --- a/src/invocation/payload.rs +++ b/src/invocation/payload.rs @@ -34,7 +34,7 @@ pub struct Payload { pub nonce: Nonce, pub not_before: Option, - pub expiration: Timestamp, + pub expiration: Option, } // FIXME cleanup traits @@ -47,7 +47,7 @@ impl Payload { pub fn check( self, proofs: Vec::Hierarchy, C, DID>>, - now: SystemTime, + now: &SystemTime, ) -> Result<(), DelegationError<<::Hierarchy as Prove>::Error>> where T: Delegable, @@ -116,38 +116,38 @@ impl, DID: Did> From> for arguments::N /// [`Promise`]: crate::invocation::promise::Promise pub type Promised = Payload<::Promised, DID>; -impl Resolvable for Payload -where - arguments::Named: From, - Ipld: From, - T::Promised: ToCommand, -{ - type Promised = Promised; - - fn try_resolve(promised: Promised) -> Result { - match ::try_resolve(promised.ability) { - Ok(resolved_ability) => Ok(Payload { - issuer: promised.issuer, - subject: promised.subject, - audience: promised.audience, - - ability: resolved_ability, - - proofs: promised.proofs, - cause: promised.cause, - metadata: promised.metadata, - nonce: promised.nonce, - - not_before: promised.not_before, - expiration: promised.expiration, - }), - Err(promised_ability) => Err(Payload { - ability: promised_ability, - ..promised - }), - } - } -} +// impl Resolvable for Payload +// where +// arguments::Named: From, +// Ipld: From, +// T::Promised: ToCommand, +// { +// type Promised = Promised; +// +// fn try_resolve(promised: Promised) -> Result { +// match ::try_resolve(promised.ability) { +// Ok(resolved_ability) => Ok(Payload { +// issuer: promised.issuer, +// subject: promised.subject, +// audience: promised.audience, +// +// ability: resolved_ability, +// +// proofs: promised.proofs, +// cause: promised.cause, +// metadata: promised.metadata, +// nonce: promised.nonce, +// +// not_before: promised.not_before, +// expiration: promised.expiration, +// }), +// Err(promised_ability) => Err(Payload { +// ability: promised_ability, +// ..promised +// }), +// } +// } +// } impl Serialize for Payload where diff --git a/src/invocation/resolvable.rs b/src/invocation/resolvable.rs index bc9103ea..8cac5758 100644 --- a/src/invocation/resolvable.rs +++ b/src/invocation/resolvable.rs @@ -1,7 +1,7 @@ -use crate::ability::arguments; +use crate::{ability::arguments, delegation::Delegable}; use libipld_core::ipld::Ipld; -pub trait Resolvable: Sized { +pub trait Resolvable: Delegable { type Promised: Into>; // FIXME indeed needed to get teh right err type diff --git a/src/invocation/store.rs b/src/invocation/store.rs index f15679cf..9196f041 100644 --- a/src/invocation/store.rs +++ b/src/invocation/store.rs @@ -1,17 +1,20 @@ use super::Invocation; -use crate::did::Did; -use libipld_core::cid::Cid; -use std::collections::BTreeMap; +use crate::{did::Did, invocation::Resolvable}; +use libipld_core::{cid::Cid, link::Link}; +use std::{ + collections::{BTreeMap, BTreeSet}, + ops::ControlFlow, +}; use thiserror::Error; pub trait Store { type Error; - fn get(&self, cid: &Cid) -> Result<&Invocation, Self::Error>; + fn get(&self, cid: Cid) -> Result<&Invocation, Self::Error>; fn put(&mut self, cid: Cid, invocation: Invocation) -> Result<(), Self::Error>; - fn has(&self, cid: &Cid) -> Result { + fn has(&self, cid: Cid) -> Result { Ok(self.get(cid).is_ok()) } } @@ -28,8 +31,8 @@ pub struct NotFound; impl Store for MemoryStore { type Error = NotFound; - fn get(&self, cid: &Cid) -> Result<&Invocation, Self::Error> { - self.store.get(cid).ok_or(NotFound) + fn get(&self, cid: Cid) -> Result<&Invocation, Self::Error> { + self.store.get(&cid).ok_or(NotFound) } fn put(&mut self, cid: Cid, invocation: Invocation) -> Result<(), Self::Error> { @@ -37,3 +40,55 @@ impl Store for MemoryStore { Ok(()) } } + +//////// + +pub trait PromiseIndex { + type PromiseIndexError; + + fn put_waiting( + &mut self, + waiting_on: Vec, + invocation: Cid, + ) -> Result<(), Self::PromiseIndexError>; + + fn get_waiting(&self, waiting_on: Vec) -> Result, Self::PromiseIndexError>; +} + +pub struct MemoryPromiseIndex { + pub index: BTreeMap>, +} + +impl PromiseIndex for MemoryPromiseIndex { + type PromiseIndexError = NotFound; + + fn put_waiting( + &mut self, + waiting_on: Vec, + invocation: Cid, + ) -> Result<(), Self::PromiseIndexError> { + self.index + .insert(invocation, BTreeSet::from_iter(waiting_on)); + + Ok(()) + } + + fn get_waiting(&self, waiting_on: Vec) -> Result, Self::PromiseIndexError> { + Ok(match waiting_on.pop() { + None => BTreeSet::new(), + Some(first) => waiting_on + .iter() + .try_fold(BTreeSet::from_iter([first]), |mut acc, cid| { + let next = self.index.get(cid).ok_or(NotFound)?; + + let reduced = acc.intersection(*next.into()).collect(); + if reduced.is_empty() { + return Err(()); + } + + Ok(reduced) + }) + .unwrap_or_default(), + }) + } +} diff --git a/src/reader.rs b/src/reader.rs index c2614569..599673a9 100644 --- a/src/reader.rs +++ b/src/reader.rs @@ -245,22 +245,31 @@ impl From>> for Reader { } } -impl Resolvable for Reader -where - Reader: Into>, -{ - type Promised = Reader; - - fn try_resolve(promised: Self::Promised) -> Result { - match T::try_resolve(promised.val) { - Ok(val) => Ok(Reader { - env: promised.env, - val, - }), - Err(val) => Err(Reader { - env: promised.env, - val, - }), - } - } -} +// use crate::proof::{checkable::Checkable, same::CheckSame}; +// +// impl Delegable for Reader +// where +// Reader: Checkable + CheckSame, +// { +// type Builder = Reader; +// } +// +// impl Resolvable for Reader +// where +// Reader: Into>, +// { +// type Promised = Reader; +// +// fn try_resolve(promised: Self::Promised) -> Result { +// match T::try_resolve(promised.val) { +// Ok(val) => Ok(Reader { +// env: promised.env, +// val, +// }), +// Err(val) => Err(Reader { +// env: promised.env, +// val, +// }), +// } +// } +// } diff --git a/src/store.rs b/src/store.rs index c388aaa5..84826f56 100644 --- a/src/store.rs +++ b/src/store.rs @@ -21,7 +21,7 @@ where type Error; /// Read a token from the store - fn read(&self, cid: &Cid) -> Result, Self::Error> + fn read(&self, cid: Cid) -> Result, Self::Error> where T: Decode; @@ -42,7 +42,7 @@ where type Error; /// Read a token from the store - async fn read(&self, cid: &Cid) -> Result, Self::Error> + async fn read(&self, cid: Cid) -> Result, Self::Error> where T: Decode; @@ -66,7 +66,7 @@ pub struct InMemoryStore { impl Store for InMemoryStore { type Error = anyhow::Error; - fn read(&self, cid: &Cid) -> Result, Self::Error> + fn read(&self, cid: Cid) -> Result, Self::Error> where T: Decode, { diff --git a/src/time.rs b/src/time.rs index 03101e98..b47a09d9 100644 --- a/src/time.rs +++ b/src/time.rs @@ -20,7 +20,7 @@ pub fn now() -> u64 { /// All timestamps that this library can handle. /// /// Strictly speaking, UCAN exclusively supports [`JsTime`] (for JavaScript interoperability). -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Copy, Clone, PartialEq)] pub enum Timestamp { /// An entry for [`JsTime`], which is compatible with JavaScript's 2⁵³ numeric range. JsSafe(JsTime), @@ -112,7 +112,7 @@ impl TryFrom for Timestamp { /// and is thus sufficient for "nearly" all auth use cases. /// /// [IEEE-754]: https://en.wikipedia.org/wiki/IEEE_754 -#[derive(Debug, Clone, PartialEq, Eq)] +#[derive(Debug, Copy, Clone, PartialEq, Eq)] #[cfg_attr(target_arch = "wasm32", wasm_bindgen)] pub struct JsTime { time: SystemTime, diff --git a/src/ucan.rs b/src/ucan.rs index ba5e6584..c394710b 100644 --- a/src/ucan.rs +++ b/src/ucan.rs @@ -376,7 +376,7 @@ where } /// Return the `prf` field of the UCAN payload - pub fn proofs(&self) -> Option> { + pub fn proofs(&self) -> Option> { self.payload .prf .as_ref() From d99e16ae138bdea8abc709fbef0363192e38b4d9 Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Thu, 15 Feb 2024 22:53:48 -0800 Subject: [PATCH 080/188] Most of the way through invocation agent --- src/ability/crud.rs | 20 ++-- src/ability/crud/create.rs | 19 ++-- src/ability/crud/read.rs | 16 +-- src/ability/crud/update.rs | 104 ++++++++++++------ src/agent.rs | 2 +- src/delegation/agent.rs | 2 +- src/delegation/payload.rs | 10 ++ src/invocation/agent.rs | 200 +++++++++++++++++++++-------------- src/invocation/payload.rs | 73 ++++++++++++- src/invocation/resolvable.rs | 12 ++- src/ipld/enriched.rs | 99 +++++++++++++++++ src/ipld/newtype.rs | 11 ++ src/ipld/promised.rs | 72 ++++++++++--- src/signature.rs | 46 ++++---- 14 files changed, 507 insertions(+), 179 deletions(-) diff --git a/src/ability/crud.rs b/src/ability/crud.rs index aaf4eb54..c080ae6d 100644 --- a/src/ability/crud.rs +++ b/src/ability/crud.rs @@ -94,16 +94,16 @@ impl Checkable for Builder { type Hierarchy = Parentful; } -// impl From for Builder { -// fn from(promised: Promised) -> Self { -// match promised { -// Promised::Create(create) => create.into(), -// Promised::Read(read) => read.into(), -// Promised::Update(update) => update.into(), -// Promised::Destroy(destroy) => destroy.into(), -// } -// } -// } +impl From for Builder { + fn from(promised: Promised) -> Self { + match promised { + Promised::Create(create) => Builder::Create(create.into()), + Promised::Read(read) => Builder::Read(read.into()), + Promised::Update(update) => Builder::Update(update.into()), + Promised::Destroy(destroy) => Builder::Destroy(destroy.into()), + } + } +} impl CheckParents for Builder { type Parents = MutableParents; diff --git a/src/ability/crud/create.rs b/src/ability/crud/create.rs index f5b39ca9..b6ea934b 100644 --- a/src/ability/crud/create.rs +++ b/src/ability/crud/create.rs @@ -269,14 +269,17 @@ impl From for Promised { } } -// impl From for Builder { -// fn from(promised: Promised) -> Self { -// Builder { -// path: promised.path.map(Into::into), -// args: promised.args.map(Into::into), -// } -// } -// } +// FIXME may want to name this something other than a TryFrom +impl From for Builder { + fn from(promised: Promised) -> Self { + Builder { + path: promised.path.and_then(|x| x.try_resolve().ok()), + args: promised + .args + .and_then(|x| x.try_resolve().ok()?.try_into().ok()), + } + } +} impl Resolvable for Ready { type Promised = Promised; diff --git a/src/ability/crud/read.rs b/src/ability/crud/read.rs index 3d503962..7bd237a6 100644 --- a/src/ability/crud/read.rs +++ b/src/ability/crud/read.rs @@ -130,14 +130,14 @@ impl Delegable for Ready { type Builder = Builder; } -// impl From for Builder { -// fn from(promised: Promised) -> Self { -// Builder { -// path: promised.path.map(Into::into), -// args: promised.args.map(Into::into), -// } -// } -// } +impl From for Builder { + fn from(promised: Promised) -> Self { + Builder { + path: promised.path.map(Into::into), + args: promised.args.map(Into::into), + } + } +} impl, A: Into> From> for Ipld { fn from(read: Generic) -> Self { diff --git a/src/ability/crud/update.rs b/src/ability/crud/update.rs index 9308e314..64654b15 100644 --- a/src/ability/crud/update.rs +++ b/src/ability/crud/update.rs @@ -16,19 +16,6 @@ use std::{collections::BTreeMap, path::PathBuf}; // FIXME deserialize instance -/// A helper for creating lifecycle instances of `crud/create` with the correct shape. -#[derive(Debug, Clone, PartialEq, Serialize)] -#[serde(deny_unknown_fields)] -pub struct Generic { - /// An optional path to a sub-resource that is to be updated. - #[serde(default, skip_serializing_if = "Option::is_none")] - pub path: Option, - - /// Optional arguments to be passed in the update. - #[serde(default, skip_serializing_if = "Option::is_none")] - pub args: Option, -} - #[cfg_attr(doc, aquamarine::aquamarine)] /// The executable/dispatchable variant of the `crud/create` ability. /// @@ -60,7 +47,17 @@ pub struct Generic { /// /// style updateready stroke:orange; /// ``` -pub type Ready = Generic>; +#[derive(Debug, Clone, PartialEq, Serialize)] +#[serde(deny_unknown_fields)] +pub struct Ready { + /// An optional path to a sub-resource that is to be updated. + #[serde(default, skip_serializing_if = "Option::is_none")] + path: Option, + + /// Optional arguments to be passed in the update. + #[serde(default, skip_serializing_if = "Option::is_none")] + args: Option>, +} #[cfg_attr(doc, aquamarine::aquamarine)] /// The delegatable ability for updating existing agents. @@ -93,7 +90,17 @@ pub type Ready = Generic>; /// /// style update stroke:orange; /// ``` -pub type Builder = Generic>; +#[derive(Debug, Clone, PartialEq, Serialize)] +#[serde(deny_unknown_fields)] +pub struct Builder { + /// An optional path to a sub-resource that is to be updated. + #[serde(default, skip_serializing_if = "Option::is_none")] + path: Option, + + /// Optional arguments to be passed in the update. + #[serde(default, skip_serializing_if = "Option::is_none")] + args: Option>, +} #[cfg_attr(doc, aquamarine::aquamarine)] /// An invoked `crud/update` ability (but possibly awaiting another @@ -127,9 +134,19 @@ pub type Builder = Generic>; /// /// style updatepromise stroke:orange; /// ``` -pub type Promised = Generic, arguments::Promised>; +#[derive(Debug, Clone, PartialEq, Serialize)] +#[serde(deny_unknown_fields)] +pub struct Promised { + /// An optional path to a sub-resource that is to be updated. + #[serde(default, skip_serializing_if = "Option::is_none")] + path: Option>, + + /// Optional arguments to be passed in the update. + #[serde(default, skip_serializing_if = "Option::is_none")] + args: Option, +} -impl Command for Generic { +impl Command for Ready { const COMMAND: &'static str = "crud/update"; } @@ -137,22 +154,43 @@ impl Delegable for Ready { type Builder = Builder; } -// impl From for Builder { -// fn from(promised: Promised) -> Self { -// Builder { -// path: promised.path.map(Into::into), -// args: promised.args.map(Into::into), -// } -// } -// } +impl From for Builder { + fn from(r: Ready) -> Self { + Builder { + path: r.path, + args: r.args, + } + } +} + +impl From for Ready { + fn from(builder: Builder) -> Self { + Ready { + path: builder.path, + args: builder.args, + } + } +} + +impl From for Builder { + fn from(promised: Promised) -> Self { + Builder { + path: promised.path.and_then(Into::into), + args: promised.args.and_then(|res| match res.try_resolve() { + Ok(args) => Some(args.into()), + Err(unresolved) => None, + }), + } + } +} -impl, A: Into> From> for Ipld { - fn from(create: Generic) -> Self { +impl From for Ipld { + fn from(create: Ready) -> Self { create.into() } } -impl, A: TryFrom> TryFrom for Generic { +impl TryFrom for Ready { type Error = (); // FIXME fn try_from(ipld: Ipld) -> Result { @@ -161,15 +199,15 @@ impl, A: TryFrom> TryFrom for Generic { return Err(()); // FIXME } - Ok(Generic { + Ok(Ready { path: map - .remove("path") - .map(|ipld| P::try_from(ipld).map_err(|_| ())) + .get("path") + .map(|ipld| (ipld::Newtype(*ipld)).try_into().map_err(|_| ())) .transpose()?, args: map - .remove("args") - .map(|ipld| A::try_from(ipld).map_err(|_| ())) + .get("args") + .map(|ipld| (*ipld).try_into().map_err(|_| ())) .transpose()?, }) } else { diff --git a/src/agent.rs b/src/agent.rs index e2a8a4cb..7192f3bd 100644 --- a/src/agent.rs +++ b/src/agent.rs @@ -84,7 +84,7 @@ impl< ability_builder, conditions, metadata, - nonce: Nonce::generate_16(&mut salt), + nonce: Nonce::generate_12(&mut salt), expiration: expiration.into(), not_before: not_before.map(Into::into), }; diff --git a/src/delegation/agent.rs b/src/delegation/agent.rs index 913a295b..87769369 100644 --- a/src/delegation/agent.rs +++ b/src/delegation/agent.rs @@ -45,7 +45,7 @@ impl< now: &SystemTime, ) -> Result, DelegateError<>::Error>> { let mut salt = self.did.clone().to_string().into_bytes(); - let nonce = Nonce::generate_16(&mut salt); + let nonce = Nonce::generate_12(&mut salt); if subject == *self.did { let payload: Payload = Payload { diff --git a/src/delegation/payload.rs b/src/delegation/payload.rs index 73d3368d..5e83b655 100644 --- a/src/delegation/payload.rs +++ b/src/delegation/payload.rs @@ -461,3 +461,13 @@ impl Acc { .map_err(DelegationError::SemanticError) } } + +// use crate::proof::{parentful::Parentful, parentless::Parentless}; +// +// impl>, C, DID: Did> Checkable for Payload { +// type Hierarchy = Parentless>; +// } +// +// impl>, C, DID: Did> Checkable for Payload { +// type Hierarchy = Parentful>; +// } diff --git a/src/invocation/agent.rs b/src/invocation/agent.rs index f23fb185..a3ff1d88 100644 --- a/src/invocation/agent.rs +++ b/src/invocation/agent.rs @@ -1,14 +1,21 @@ use super::{payload::Payload, store::Store, Invocation, Resolvable}; use crate::{ - ability::ucan, + ability::{arguments, ucan}, delegation, delegation::{condition::Condition, Delegable}, did::Did, nonce::Nonce, - signature::Verifiable, + proof::{checkable::Checkable, prove::Prove}, + signature::{Signature, Verifiable}, time::JsTime, }; -use libipld_core::{cid::Cid, ipld::Ipld}; +use libipld_cbor::DagCborCodec; +use libipld_core::{ + cid::{Cid, CidGeneric}, + codec::Encode, + ipld::Ipld, + multihash::{Code, MultihashGeneric}, +}; use std::{collections::BTreeMap, marker::PhantomData}; use thiserror::Error; use web_time::SystemTime; @@ -26,8 +33,6 @@ pub struct Agent< pub delegation_store: &'a D, pub invocation_store: &'a mut S, - pub revocation_store: &'a mut S, // FIXME just a BTRee Set pointing into invocatuin store? - pub unresolved_promise_store: &'a mut P, pub resolved_promise_store: &'a mut P, @@ -70,14 +75,14 @@ impl< &mut self, audience: Option<&DID>, subject: &DID, - ability: T::Promised, // FIXME Resolved needs Into + ability: T::Promised, // FIXME give them an enum for promised or not probs doens't matter? metadata: BTreeMap, cause: Option, expiration: Option, not_before: Option, now: &SystemTime, // FIXME err type - ) -> Result, ()> { + ) -> Result, ()> { let proofs = self .delegation_store .get_chain(self.did, subject, &ability.into(), vec![], now) @@ -94,105 +99,135 @@ impl< ability, proofs, metadata, - nonce: Nonce::generate_16(&mut seed), + nonce: Nonce::generate_12(&mut seed), cause, expiration: expiration.map(Into::into), not_before: not_before.map(Into::into), }; - let invocation = Invocation::try_sign(self.signer, &payload).map_err(|_| ())?; - let cid = Cid::from(invocation); - Ok(invocation) + Ok(Invocation::try_sign(self.signer, payload).map_err(|_| ())?) } - // FIXME err = () - // FIXME move to revocation agent wit own traits? - // pub fn revoke( - // &mut self, - // subject: &DID, - // cause: Option, - // cid: Cid, - // now: JsTime, - // ) -> Result<(), ()> { - // let ability = ucan::revoke::Ready { ucan: cid }; - // let proofs = if subject == self.did { - // vec![] - // } else { - // self.delegation_store - // .get_chain( - // subject, - // self.did, - // &ability.into(), - // vec![], - // &SystemTime::now(), - // ) - // .map_err(|_| ())? - // .map(|chain| chain.map(|(cid, _)| *cid).into()) - // .unwrap_or(vec![]) - // }; - - // let payload = Payload { - // issuer: self.did.clone(), - // subject: self.did.clone(), - // audience: Some(self.did.clone()), - // ability, - // proofs, - // cause: None, - // metadata: BTreeMap::new(), - // nonce: Nonce::generate_16(&mut vec![]), - // expiration: None, - // not_before: None, - // }; - - // let invocation = Invocation::try_sign(self.signer, &payload).map_err(|_| ())?; - - // self.invocation_store.revoke(&cid)?; - // } - pub fn receive( &self, - invocation: Invocation, - now: SystemTime, + promised: Invocation, + now: &SystemTime, // FIXME return type - ) -> Result, ()> { + ) -> Result>, ()> + where + ::Hierarchy: Clone + Into>, + T::Builder: Clone + Checkable + Prove + Into>, + { // FIXME needs varsig header - let cid = Cid::from(invocation); - - invocation + let mut buffer = vec![]; + Ipld::from(promised) + .encode(DagCborCodec, &mut buffer) + .expect("FIXME not dag-cbor? DagCborCodec to encode any arbitrary `Ipld`"); + + let cid: Cid = CidGeneric::new_v1( + DagCborCodec.into(), + MultihashGeneric::wrap(Code::Sha2_256.into(), buffer.as_slice()) + .map_err(|_| ()) // FIXME + .expect("FIXME expect signing to work..."), + ); + + let mut encoded = vec![]; + promised + .payload + // FIXME use the varsig headre to get the codec + .encode(DagCborCodec, &mut encoded) + .expect("FIXME"); + + promised .verifier() - .verify(&cid.to_bytes(), &invocation.signature.to_bytes()) + .verify( + &encoded, + &match promised.signature { + Signature::Solo(sig) => sig, + }, + ) .map_err(|_| ())?; - let payload: Payload = invocation.payload; - let resolved_payload = match payload.ability.try_resolve() { - Ok(resolved_payload) => { - // NOTE Already resolved when it came over the wire - let resolved_invocation = invocation.map_ability(|_| resolved_payload); - self.store.put(cid, resolved_invocation).map_err(|_| ())?; - resolved_payload - } + let resolved_ability: T = match Resolvable::try_resolve(promised.payload.ability) { + Ok(resolved) => resolved, Err(_) => { // FIXME check if any of the unresolved promises are in the store - self.promised_store.put(cid, invocation).map_err(|_| ())?; - todo!() // return Ok(Recipient::Other(promised)); // FIXME + // FIXME check if it's actually unresolved + self.unresolved_promise_store + .put(cid, promised) + .map_err(|_| ())?; + + todo!() + // return Ok(Recipient::Other(promised)); // FIXME } }; let proof_payloads = self .delegation_store - .get_many(&invocation.payload.proofs) - .map(|d| d.payload); + .get_many(&promised.payload.proofs) + .map_err(|_| ())? + .into_iter() + .map(|d| d.payload) + .collect(); - resolved_payload - .into() - .check(&proof_payloads, now) + let resolved_payload = promised.payload.map_ability(|_| resolved_ability); + + delegation::Payload::::from(resolved_payload) + .check(proof_payloads, now) .map_err(|_| ())?; - if invocation.payload.audience != Some(*self.did) { - return Ok(Recipient::Other(invocation)); + if promised.payload.audience != Some(*self.did) { + return Ok(Recipient::Other(resolved_payload)); } - Ok(Recipient::You(invocation)) + Ok(Recipient::You(resolved_payload)) + } + + pub fn revoke( + &mut self, + subject: &DID, + cause: Option, + cid: Cid, + now: &JsTime, + // FIXME return type + ) -> Result, ()> + where + T: From, + { + let ability: T = ucan::revoke::Ready { ucan: cid.clone() }.into(); + let proofs = if subject == self.did { + vec![] + } else { + self.delegation_store + .get_chain( + subject, + self.did, + &ability.into(), + vec![], + &SystemTime::now(), + ) + .map_err(|_| ())? + .map(|chain| chain.map(|(index_cid, _)| index_cid).into()) + .unwrap_or(vec![]) + }; + + let payload = Payload { + issuer: self.did.clone(), + subject: self.did.clone(), + audience: Some(self.did.clone()), + ability, + proofs, + cause: None, + metadata: BTreeMap::new(), + nonce: Nonce::generate_12(&mut vec![]), + expiration: None, + not_before: None, + }; + + let invocation = Invocation::try_sign(self.signer, payload).map_err(|_| ())?; + + self.delegation_store.revoke(cid).map_err(|_| ())?; + Ok(invocation) } } @@ -200,3 +235,8 @@ pub enum Recipient { You(T), Other(T), } + +// impl Agent { +// FIXME err = () +// FIXME move to revocation agent wit own traits? +// } diff --git a/src/invocation/payload.rs b/src/invocation/payload.rs index ad17031b..cf5de2b9 100644 --- a/src/invocation/payload.rs +++ b/src/invocation/payload.rs @@ -9,7 +9,15 @@ use crate::{ signature::Verifiable, time::Timestamp, }; -use libipld_core::{cid::Cid, error::SerdeError, ipld::Ipld, serde as ipld_serde}; +use anyhow; +use libipld_core::{ + cid::{Cid, CidGeneric}, + codec::{Codec, Encode}, + error::SerdeError, + ipld::Ipld, + multihash::{Code, MultihashGeneric}, + serde as ipld_serde, +}; use serde::{Serialize, Serializer}; use std::{collections::BTreeMap, fmt::Debug}; use web_time::SystemTime; @@ -44,6 +52,24 @@ pub struct Payload { // This probably means putting the delegation T back to the upper level and bieng explicit about // the T::Builder in the type impl Payload { + pub fn map_ability(self, f: F) -> Payload + where + F: FnOnce(T) -> U, + { + Payload { + issuer: self.issuer, + subject: self.subject, + audience: self.audience, + ability: f(self.ability), + proofs: self.proofs, + cause: self.cause, + metadata: self.metadata, + nonce: self.nonce, + not_before: self.not_before, + expiration: self.expiration, + } + } + pub fn check( self, proofs: Vec::Hierarchy, C, DID>>, @@ -116,13 +142,46 @@ impl, DID: Did> From> for arguments::N /// [`Promise`]: crate::invocation::promise::Promise pub type Promised = Payload<::Promised, DID>; +// impl Delegable for Payload { +// type Builder = Payload; +// } + +// use crate::proof::parentful::Parentful; +// +// impl Checkable for Payload +// where +// T::Builder: Checkable>, +// { +// type Hierarchy = (); +// } + +// impl TryFrom> for Payload { +// fn from(payload: Payload) -> Self { +// Payload { +// issuer: payload.issuer, +// subject: payload.subject, +// audience: payload.audience, +// +// ability: T::from(payload.ability), +// +// proofs: payload.proofs, +// cause: payload.cause, +// metadata: payload.metadata, +// nonce: payload.nonce, +// +// not_before: payload.not_before, +// expiration: payload.expiration, +// } +// } +// } + // impl Resolvable for Payload // where // arguments::Named: From, // Ipld: From, // T::Promised: ToCommand, // { -// type Promised = Promised; +// type Promised = Promised; // // fn try_resolve(promised: Promised) -> Result { // match ::try_resolve(promised.ability) { @@ -315,3 +374,13 @@ impl>, DID: Did> From } } } + +impl Encode for Payload +where + Ipld: Encode, +{ + fn encode(&self, codec: C, writer: &mut W) -> Result<(), anyhow::Error> { + let ipld: Ipld = (*self).into(); + ipld.encode(codec, writer) + } +} diff --git a/src/invocation/resolvable.rs b/src/invocation/resolvable.rs index 8cac5758..0f4d12dc 100644 --- a/src/invocation/resolvable.rs +++ b/src/invocation/resolvable.rs @@ -2,8 +2,18 @@ use crate::{ability::arguments, delegation::Delegable}; use libipld_core::ipld::Ipld; pub trait Resolvable: Delegable { - type Promised: Into>; + // FIXME rename "Unresolved" + type Promised: Into + Into>; // FIXME indeed needed to get teh right err type fn try_resolve(promised: Self::Promised) -> Result; + + // FIXME better name + // NOTE this takes anything taht doesn't resolve and returns None on those fields + // FIXME no, jsut use Into and NOTE THIS IN THE DOCS + // fn resolve_to_builder(&self) -> Self::Builder; } + +// impl Delegable for Ipld { +// type Builder = Option; +// } diff --git a/src/ipld/enriched.rs b/src/ipld/enriched.rs index 120feaa9..c0752c25 100644 --- a/src/ipld/enriched.rs +++ b/src/ipld/enriched.rs @@ -1,3 +1,4 @@ +use crate::invocation::Resolvable; use libipld_core::{cid::Cid, ipld::Ipld}; use serde::{Deserialize, Serialize}; use std::collections::BTreeMap; @@ -32,6 +33,104 @@ pub enum Enriched { Link(Cid), } +/// A post-order [`Ipld`] iterator +#[derive(Clone, Debug, Default, PartialEq)] +#[cfg_attr(feature = "serde-codec", derive(serde::Serialize))] +#[allow(clippy::module_name_repetitions)] +pub struct PostOrderIpldIter<'a, T> { + inbound: Vec>, + outbound: Vec>, +} + +#[derive(Clone, Debug, PartialEq)] +pub enum Item<'a, T> { + Node(&'a Enriched), + Inner(&'a T), +} + +impl<'a, T> PostOrderIpldIter<'a, T> { + /// Initialize a new [`PostOrderIpldIter`] + #[must_use] + pub fn new(enriched: &'a Enriched) -> Self { + PostOrderIpldIter { + inbound: vec![Item::Node(enriched)], + outbound: vec![], + } + } +} + +impl<'a, T> IntoIterator for &'a Enriched { + type Item = Item<'a, T>; + type IntoIter = PostOrderIpldIter<'a, T>; + + fn into_iter(self) -> Self::IntoIter { + PostOrderIpldIter::new(&self) + } +} + +impl<'a, T: Clone> FromIterator> for &'a Enriched { + fn from_iter>>(it: I) -> Self { + &it.into_iter().fold(Enriched::Null, |acc, x| match x { + Item::Node(Enriched::Null) => Enriched::Null, + Item::Node(Enriched::Bool(b)) => Enriched::Bool(*b), + Item::Node(Enriched::Integer(i)) => Enriched::Integer(*i), + Item::Node(Enriched::Float(f)) => Enriched::Float(*f), + Item::Node(Enriched::String(s)) => Enriched::String(s.clone()), + Item::Node(Enriched::Bytes(b)) => Enriched::Bytes(b.clone()), + Item::Node(Enriched::Link(c)) => Enriched::Link(c.clone()), + Item::Node(Enriched::List(vec)) => { + let mut list = vec![]; + for item in vec { + list.push(item); + } + Enriched::List(list.iter().map(|a| (*a).clone()).collect()) + } + Item::Node(Enriched::Map(btree)) => { + let mut map = BTreeMap::new(); + for (k, v) in btree { + map.insert(k.clone(), (*v).clone()); + } + Enriched::Map(map) + } + Item::Inner(_) => acc, + }) + } +} + +impl<'a, T> From<&'a Enriched> for PostOrderIpldIter<'a, T> { + fn from(enriched: &'a Enriched) -> Self { + PostOrderIpldIter::new(enriched) + } +} + +impl<'a, T> Iterator for PostOrderIpldIter<'a, T> { + type Item = Item<'a, T>; + + fn next(&mut self) -> Option { + loop { + match self.inbound.pop() { + None => return self.outbound.pop(), + Some(map @ Item::Node(Enriched::Map(btree))) => { + self.outbound.push(map); + + for node in btree.values() { + self.inbound.push(Item::Inner(node)); + } + } + + Some(list @ Item::Node(Enriched::List(vector))) => { + self.outbound.push(list); + + for node in vector { + self.inbound.push(Item::Inner(node)); + } + } + Some(node) => self.outbound.push(node), + } + } + } +} + impl> From for Enriched { fn from(ipld: Ipld) -> Self { match ipld { diff --git a/src/ipld/newtype.rs b/src/ipld/newtype.rs index f2252979..25b0c5cc 100644 --- a/src/ipld/newtype.rs +++ b/src/ipld/newtype.rs @@ -55,6 +55,17 @@ impl From for Newtype { } } +impl TryFrom for PathBuf { + type Error = (); // FIXME + + fn try_from(wrapped: Newtype) -> Result { + match wrapped.0 { + Ipld::String(s) => Ok(PathBuf::from(s)), + _ => Err(()), + } + } +} + #[cfg(target_arch = "wasm32")] impl Newtype { pub fn try_from_js>(js_val: JsValue) -> Result diff --git a/src/ipld/promised.rs b/src/ipld/promised.rs index 8e0f62b1..83ae2029 100644 --- a/src/ipld/promised.rs +++ b/src/ipld/promised.rs @@ -1,5 +1,9 @@ -use super::enriched::Enriched; -use crate::invocation::promise::{Promise, PromiseAny, PromiseErr, PromiseOk}; +use super::enriched::{Enriched, Item}; +use crate::ability::arguments; +use crate::invocation::{ + promise::{self, Promise, PromiseAny, PromiseErr, PromiseOk}, + Resolvable, // FIXME this shoudl be under promise +}; use libipld_core::{error::SerdeError, ipld::Ipld, serde as ipld_serde}; use serde::{Deserialize, Serialize}; @@ -8,16 +12,6 @@ use serde::{Deserialize, Serialize}; #[serde(transparent)] pub struct Promised(pub Promise, Enriched>); -impl Promised { - // FIXME note that this is different from the failable version which is more like - // a try_reoslve... which has a note at the bottom on this module - pub fn serialize_as_ipld(&self) -> Ipld { - ipld_serde::to_ipld(self).unwrap() // FIXME at worst we can do this by hand - } -} - -// Promise variants into Promised - impl From, Enriched>> for Promised { fn from(promise: Promise, Enriched>) -> Self { Promised(promise) @@ -51,6 +45,7 @@ impl From for Promised { } // FIXME THIS is a great example of a try_resolve +// FIXME is this recursive? Will this blow the stack? impl TryFrom for Ipld { type Error = Promised; @@ -84,3 +79,56 @@ impl TryFrom for Ipld { } } } + +// FIXME surely the other version in this module can't be right if this also works? +// FIXME this is more iterative right? +// impl TryFrom for Ipld { +// // impl Resolvable for Ipld { +// type Error = Self; +// // type Promised = Promised; +// +// fn try_from(promised: Promised) -> Result { +// fn handle(enriched: super::Enriched) -> Result { +// enriched +// .into_iter() +// .try_fold(vec![], |mut acc, next| { +// match next { +// Item::Inner(promised) => { +// let inner: Ipld = Resolvable::try_resolve(*promised).map_err(|_| ())?; +// +// acc.push(inner); +// } +// Item::Node(node) => { +// let _ = Ipld::try_from(*node).map_err(|_| ())?; +// } +// } +// Ok(acc) +// }) +// .map(|vec| vec.first().expect("FIXME").clone()) +// } +// +// match promised.0 { +// Promise::Ok(promise_ok) => match promise_ok { +// PromiseOk::Fulfilled(enriched) => { +// handle(enriched).map_err(|_| PromiseOk::Fulfilled(enriched).into()) +// } +// PromiseOk::Pending(_) => Err(promised), +// }, +// Promise::Err(promise_err) => match promise_err { +// PromiseErr::Rejected(enriched) => { +// handle(enriched).map_err(|_| PromiseErr::Rejected(enriched).into()) +// } +// PromiseErr::Pending(_) => Err(promised), +// }, +// Promise::Any(promise_any) => match promise_any { +// PromiseAny::Fulfilled(enriched) => { +// handle(enriched).map_err(|_| PromiseAny::Fulfilled(enriched).into()) +// } +// PromiseAny::Rejected(enriched) => { +// handle(enriched).map_err(|_| PromiseAny::Rejected(enriched).into()) +// } +// PromiseAny::Pending(_) => Err(promised), +// }, +// } +// } +// } diff --git a/src/signature.rs b/src/signature.rs index e40bee94..81a3b85e 100644 --- a/src/signature.rs +++ b/src/signature.rs @@ -1,6 +1,7 @@ //! Signatures and cryptographic envelopes. use crate::{capsule::Capsule, did::Did}; +use anyhow; use libipld_core::{ cid::{Cid, CidGeneric}, codec::{Codec, Encode}, @@ -35,7 +36,7 @@ pub struct Envelope + Capsule, DID: Did> { pub payload: T, } -impl + Clone + Into, DID: Did> Envelope { +impl + Into, DID: Did> Envelope { pub fn try_sign(signer: &DID::Signer, payload: T) -> Result, ()> { Self::try_sign_generic::(signer, DagCborCodec, Code::Sha2_256, payload) } @@ -50,20 +51,13 @@ impl + Clone + Into, DID: Did> Envelope, { - let ipld: Ipld = BTreeMap::from_iter([(T::TAG.into(), payload.clone().into())]).into(); + let ipld: Ipld = BTreeMap::from_iter([(T::TAG.into(), payload.into())]).into(); let mut buffer = vec![]; ipld.encode(codec, &mut buffer) .expect("FIXME not dag-cbor? DagCborCodec to encode any arbitrary `Ipld`"); - let cid: Cid = CidGeneric::new_v1( - codec.into(), - MultihashGeneric::wrap(hasher.into(), buffer.as_slice()) - .map_err(|_| ()) // FIXME - .expect("FIXME expect signing to work..."), - ); - - let sig = signer.try_sign(&cid.to_bytes()).map_err(|_| ())?; + let sig = signer.try_sign(&buffer).map_err(|_| ())?; Ok(Envelope { signature: Signature::Solo(sig), @@ -122,22 +116,28 @@ pub enum Signature { // Right = 1, //} -impl> From> for Ipld { - fn from(signature: Signature) -> Self { - match signature { - Signature::Solo(sig) => sig.into(), - // Signature::Batch { - // signature, - // merkle_proof, - // } => Ipld::List(merkle_proof.into_iter().map(|p| p.into()).collect()), - } +impl + Capsule + Into, DID: Did> Encode for Envelope +where + Ipld: Encode, +{ + fn encode(&self, codec: C, writer: &mut W) -> Result<(), anyhow::Error> { + Ipld::from(*self).encode(codec, writer) } } -impl + Capsule + Into, DID: Did> From> for Ipld -where - DID::Signature: Into, -{ +// impl> From> for Ipld { +// fn from(signature: Signature) -> Self { +// match signature { +// Signature::Solo(sig) => sig.into(), +// // Signature::Batch { +// // signature, +// // merkle_proof, +// // } => Ipld::List(merkle_proof.into_iter().map(|p| p.into()).collect()), +// } +// } +// } + +impl + Capsule + Into, DID: Did> From> for Ipld { fn from(Envelope { signature, payload }: Envelope) -> Self { let ipld: Ipld = BTreeMap::from_iter([(T::TAG.into(), payload.into())]).into(); From acc564e2905f26c8f62c0b34bb06c6c39ca1c655 Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Fri, 16 Feb 2024 16:13:10 -0800 Subject: [PATCH 081/188] compiles again... back to cleanup --- src/ability/arguments.rs | 17 ++++++++++ src/ability/crud/create.rs | 33 +++++++++++++------- src/ability/crud/destroy.rs | 32 +++++++------------ src/ability/crud/read.rs | 36 ++++++++++++--------- src/ability/crud/update.rs | 50 +++++++++++------------------- src/ability/msg.rs | 10 +++++- src/ability/msg/receive.rs | 46 ++++++++++++++++----------- src/ability/preset.rs | 10 ++++++ src/ability/ucan/revoke.rs | 14 ++++----- src/delegation/store.rs | 4 +-- src/invocation.rs | 2 +- src/invocation/agent.rs | 28 +++++++++-------- src/invocation/payload.rs | 21 ++++++++----- src/invocation/promise/resolves.rs | 7 +++++ src/invocation/store.rs | 14 ++++++--- src/ipld/enriched.rs | 19 ++++++------ src/signature.rs | 39 ++++++++++++----------- 17 files changed, 220 insertions(+), 162 deletions(-) diff --git a/src/ability/arguments.rs b/src/ability/arguments.rs index 305feadb..0e96c6ef 100644 --- a/src/ability/arguments.rs +++ b/src/ability/arguments.rs @@ -5,6 +5,23 @@ mod named; pub use named::{Named, NamedError}; use crate::{invocation::promise::Resolves, ipld}; +use libipld_core::ipld::Ipld; +use std::collections::BTreeMap; // FIXME move under invoc::promise? pub type Promised = Resolves>; + +impl Promised { + pub fn try_resolve_option(self) -> Option> { + match self.try_resolve() { + Err(_) => None, + Ok(named_promises) => named_promises + .iter() + .try_fold(BTreeMap::new(), |mut map, (k, v)| { + map.insert(k.clone(), Ipld::try_from(v.clone()).ok()?); + Some(map) + }) + .map(Named), + } + } +} diff --git a/src/ability/crud/create.rs b/src/ability/crud/create.rs index b6ea934b..dff88b40 100644 --- a/src/ability/crud/create.rs +++ b/src/ability/crud/create.rs @@ -226,18 +226,29 @@ impl From for arguments::Named { ); } + // FIXME gross code if let Some(args_res) = promised.args { - named.insert( - "args".to_string(), - args_res - .map(|a| { - // FIXME extract - a.iter() - .map(|(k, v)| (k.to_string(), v.clone().serialize_as_ipld())) - .collect::>() - }) - .into(), - ); + match args_res.try_resolve() { + Ok(named_promises) => { + let value = named_promises.iter().try_fold( + arguments::Named::::new(), + |mut acc, (k, v)| { + // FIXME extract + acc.insert(k.into(), v.clone().try_into().ok()?); + Some(acc) + }, + ); + + match value { + Some(v) => { + named.insert("args".to_string(), v.into()); + } + None => {} + } + } + + Err(_unresolved) => {} + } } named diff --git a/src/ability/crud/destroy.rs b/src/ability/crud/destroy.rs index d3da7bef..68fd7b39 100644 --- a/src/ability/crud/destroy.rs +++ b/src/ability/crud/destroy.rs @@ -133,14 +133,6 @@ impl Delegable for Ready { type Builder = Builder; } -// impl From for Builder { -// fn from(promised: Promised) -> Self { -// Builder { -// path: promised.path.map(Into::into), -// } -// } -// } - impl> From> for Ipld { fn from(destroy: Generic

>::PromiseStoreError: fmt::Debug, { - // FIXME needs varsig header let mut buffer = vec![]; Ipld::from(promised.clone()) - .encode(DagCborCodec, &mut buffer) - .expect("FIXME not dag-cbor? DagCborCodec to encode any arbitrary `Ipld`"); + .encode(*promised.varsig_header().codec(), &mut buffer) + .map_err(ReceiveError::EncodingError)?; let cid: Cid = CidGeneric::new_v1( DagCborCodec.into(), MultihashGeneric::wrap(Code::Sha2_256.into(), buffer.as_slice()) - .map_err(|_| ()) // FIXME - .expect("FIXME expect signing to work..."), + .map_err(ReceiveError::MultihashError)?, ); let mut encoded = vec![]; Ipld::from(promised.payload().clone()) - // FIXME use the varsig headre to get the codec - .encode(DagCborCodec, &mut encoded) - .expect("FIXME"); + .encode(*promised.0.varsig_header.codec(), &mut encoded) + .map_err(ReceiveError::EncodingError)?; promised .verifier() - .verify( - &encoded, - &match promised.signature() { - Witness::Signature(ref sig) => sig.clone(), - }, - ) - .map_err(|_| ())?; + .verify(&encoded, &promised.signature()) + .map_err(ReceiveError::SigVerifyError)?; let resolved_ability: T = match Resolvable::try_resolve(promised.ability().clone()) { Ok(resolved) => resolved, Err(_) => { // FIXME check if any of the unresolved promises are in the store // FIXME check if it's actually unresolved + + // self.invocation_store + // .put(cid.clone(), promised.clone()) + // .map_err(ReceiveError::PromiseStoreError)?; + self.unresolved_promise_store - .put(cid, promised) - .map_err(|_| ())?; + .put(cid, todo!()) // cid for promised) + .map_err(ReceiveError::PromiseStoreError)?; todo!() // return Ok(Recipient::Other(promised)); // FIXME @@ -165,7 +172,7 @@ where let proof_payloads = self .delegation_store .get_many(&promised.proofs()) - .map_err(|_| ())? + .map_err(ReceiveError::DelegationStoreError)? .into_iter() .map(|d| d.payload()) .collect(); @@ -174,7 +181,7 @@ where delegation::Payload::::from(resolved_payload.clone()) .check(proof_payloads, now) - .map_err(|_| ())?; + .map_err(ReceiveError::DelegationValidationError)?; if promised.audience() != &Some(self.did.clone()) { return Ok(Recipient::Other(resolved_payload)); @@ -189,8 +196,9 @@ where cause: Option, cid: Cid, now: Timestamp, + varsig_header: V, // FIXME return type - ) -> Result, ()> + ) -> Result, ()> where T: From, { @@ -224,7 +232,8 @@ where issued_at: None, }; - let invocation = Invocation::try_sign(self.signer, payload).map_err(|_| ())?; + let invocation = + Invocation::try_sign(self.signer, varsig_header, payload).map_err(|_| ())?; self.delegation_store.revoke(cid).map_err(|_| ())?; Ok(invocation) @@ -236,3 +245,37 @@ pub enum Recipient { You(T), Other(T), } + +#[derive(Debug, Error)] +pub enum ReceiveError, DID: Did, C: fmt::Debug, D> +where + delegation::ValidationError< + <<::Builder as Checkable>::Hierarchy as Prove>::Error, + C, + >: fmt::Debug, +

>::PromiseStoreError: fmt::Debug, +{ + #[error("encoding error: {0}")] + EncodingError(#[from] libipld_core::error::Error), + + #[error("multihash error: {0}")] + MultihashError(#[from] libipld_core::multihash::Error), + + #[error("signature verification error: {0}")] + SigVerifyError(#[from] signature::Error), + + #[error("promise store error: {0}")] + PromiseStoreError(#[source]

>::PromiseStoreError), + + #[error("delegation store error: {0}")] + DelegationStoreError(#[source] D), + + #[error("delegation validation error: {0}")] + DelegationValidationError( + #[source] + delegation::ValidationError< + <<::Builder as Checkable>::Hierarchy as Prove>::Error, + C, + >, + ), +} diff --git a/src/invocation/payload.rs b/src/invocation/payload.rs index 7044e40d..ed3c2a04 100644 --- a/src/invocation/payload.rs +++ b/src/invocation/payload.rs @@ -20,6 +20,15 @@ use serde::{ use std::{collections::BTreeMap, fmt::Debug}; use web_time::SystemTime; +#[cfg(feature = "test_utils")] +use proptest::prelude::*; + +#[cfg(feature = "test_utils")] +use crate::ipld; + +#[cfg(feature = "test_utils")] +use crate::ipld::cid; + #[derive(Debug, Clone, PartialEq)] pub struct Payload { /// The subject of the [`Invocation`]. @@ -400,3 +409,60 @@ impl From> for Ipld { payload.into() } } + +#[cfg(feature = "test_utils")] +impl Arbitrary for Payload +where + T::Strategy: 'static, + DID::Parameters: Clone, +{ + type Parameters = (T::Parameters, DID::Parameters); + type Strategy = BoxedStrategy; + + fn arbitrary_with((t_args, did_args): Self::Parameters) -> Self::Strategy { + ( + T::arbitrary_with(t_args), + DID::arbitrary_with(did_args.clone()), + DID::arbitrary_with(did_args.clone()), + Option::::arbitrary_with((0.5.into(), did_args)), + Nonce::arbitrary(), + prop::collection::vec(cid::Newtype::arbitrary().prop_map(|nt| nt.cid), 0..25), + Option::::arbitrary().prop_map(|opt_nt| opt_nt.map(|nt| nt.cid)), + Option::::arbitrary(), + Option::::arbitrary(), + prop::collection::btree_map(".*", ipld::Newtype::arbitrary(), 0..50).prop_map(|m| { + m.into_iter() + .map(|(k, v)| (k, v.0)) + .collect::>() + }), + ) + .prop_map( + |( + ability, + issuer, + subject, + audience, + nonce, + proofs, + cause, + expiration, + issued_at, + metadata, + )| { + Payload { + issuer, + subject, + audience, + ability, + proofs, + cause, + nonce, + metadata, + issued_at, + expiration, + } + }, + ) + .boxed() + } +} diff --git a/src/invocation/promise/any.rs b/src/invocation/promise/any.rs index a8ecbd78..1825df10 100644 --- a/src/invocation/promise/any.rs +++ b/src/invocation/promise/any.rs @@ -7,6 +7,9 @@ use serde::{ }; use std::fmt; +#[cfg(feature = "test_utils")] +use proptest::prelude::*; + /// A promise that unwraps the same value from either the `{"ok": T}` or `{"err": T}` branches. /// /// Unlike [`Resolves`][super::Resolves]: @@ -210,3 +213,25 @@ impl TryFrom> for PromiseErr { } } } + +#[cfg(feature = "test_utils")] +impl Arbitrary + for PromiseAny +where + T::Strategy: 'static, + T::Parameters: 'static, + E::Strategy: 'static, + E::Parameters: 'static, +{ + type Parameters = (T::Parameters, E::Parameters); + type Strategy = BoxedStrategy; + + fn arbitrary_with((t_args, e_args): Self::Parameters) -> Self::Strategy { + prop_oneof![ + T::arbitrary_with(t_args).prop_map(PromiseAny::Fulfilled), + E::arbitrary_with(e_args).prop_map(PromiseAny::Rejected), + cid::Newtype::arbitrary().prop_map(|nt| PromiseAny::Pending(nt.cid)), + ] + .boxed() + } +} diff --git a/src/invocation/promise/err.rs b/src/invocation/promise/err.rs index e30ce53e..01c47403 100644 --- a/src/invocation/promise/err.rs +++ b/src/invocation/promise/err.rs @@ -3,6 +3,9 @@ use libipld_core::{cid::Cid, error::SerdeError, ipld::Ipld, serde as ipld_serde} use serde::{de::DeserializeOwned, Deserialize, Serialize}; use std::fmt::Debug; +#[cfg(feature = "test_utils")] +use proptest::prelude::*; + /// A promise that only selects the `{"err": error}` branch of a result. /// /// On resolution, the value is unwrapped from the `{"err": error}`, @@ -90,3 +93,21 @@ impl> TryFrom> for PromiseErr { E::try_from(Ipld::from(args)).map(PromiseErr::Rejected) } } + +#[cfg(feature = "test_utils")] +impl Arbitrary for PromiseErr +where + T::Strategy: 'static, + T::Parameters: 'static, +{ + type Parameters = T::Parameters; + type Strategy = BoxedStrategy; + + fn arbitrary_with(t_args: Self::Parameters) -> Self::Strategy { + prop_oneof![ + T::arbitrary_with(t_args).prop_map(PromiseErr::Rejected), + cid::Newtype::arbitrary().prop_map(|nt| PromiseErr::Pending(nt.cid)), + ] + .boxed() + } +} diff --git a/src/invocation/promise/ok.rs b/src/invocation/promise/ok.rs index 56f50e0a..4cc406a9 100644 --- a/src/invocation/promise/ok.rs +++ b/src/invocation/promise/ok.rs @@ -3,6 +3,9 @@ use libipld_core::{cid::Cid, error::SerdeError, ipld::Ipld, serde as ipld_serde} use serde::{de::DeserializeOwned, Deserialize, Serialize}; use std::fmt::Debug; +#[cfg(feature = "test_utils")] +use proptest::prelude::*; + /// A promise that only selects the `{"ok": value}` branch of a result. /// /// On resolution, the value is unwrapped from the `{"ok": value}`, @@ -91,3 +94,21 @@ impl> TryFrom> for PromiseOk { T::try_from(Ipld::from(args)).map(PromiseOk::Fulfilled) } } + +#[cfg(feature = "test_utils")] +impl Arbitrary for PromiseOk +where + T::Strategy: 'static, + T::Parameters: 'static, +{ + type Parameters = T::Parameters; + type Strategy = BoxedStrategy; + + fn arbitrary_with(t_args: Self::Parameters) -> Self::Strategy { + prop_oneof![ + T::arbitrary_with(t_args).prop_map(PromiseOk::Fulfilled), + cid::Newtype::arbitrary().prop_map(|nt| PromiseOk::Pending(nt.cid)), + ] + .boxed() + } +} diff --git a/src/invocation/promise/resolves.rs b/src/invocation/promise/resolves.rs index d998485e..dcfc5651 100644 --- a/src/invocation/promise/resolves.rs +++ b/src/invocation/promise/resolves.rs @@ -3,6 +3,9 @@ use libipld_core::ipld::Ipld; use serde::{Deserialize, Serialize}; use std::fmt; +#[cfg(feature = "test_utils")] +use proptest::prelude::*; + /// A promise that unwraps the same value from either the `{"ok": T}` or `{"err": T}` branches. /// /// Unlike [`PromiseAny`][super::PromiseAny]: @@ -310,3 +313,21 @@ impl From> for PromiseAny { } } } + +#[cfg(feature = "test_utils")] +impl Arbitrary for Resolves +where + T::Strategy: 'static, + T::Parameters: 'static, +{ + type Parameters = (T::Parameters, T::Parameters); + type Strategy = BoxedStrategy; + + fn arbitrary_with((ok_args, err_args): Self::Parameters) -> Self::Strategy { + prop_oneof![ + PromiseOk::::arbitrary_with(ok_args).prop_map(Resolves::Ok), + PromiseErr::::arbitrary_with(err_args).prop_map(Resolves::Err), + ] + .boxed() + } +} diff --git a/src/invocation/promise/store/memory.rs b/src/invocation/promise/store/memory.rs index ae960dfc..43ea8ee4 100644 --- a/src/invocation/promise/store/memory.rs +++ b/src/invocation/promise/store/memory.rs @@ -12,20 +12,20 @@ pub struct MemoryStore { } impl Store for MemoryStore { - type PromiseIndexError = Infallible; + type PromiseStoreError = Infallible; fn put( &mut self, - waiting_on: Vec, invocation: Cid, - ) -> Result<(), Self::PromiseIndexError> { + waiting_on: Vec, + ) -> Result<(), Self::PromiseStoreError> { self.index .insert(invocation, BTreeSet::from_iter(waiting_on)); Ok(()) } - fn get(&self, waiting_on: &mut Vec) -> Result, Self::PromiseIndexError> { + fn get(&self, waiting_on: &mut Vec) -> Result, Self::PromiseStoreError> { Ok(match waiting_on.pop() { None => BTreeSet::new(), Some(first) => waiting_on diff --git a/src/invocation/promise/store/traits.rs b/src/invocation/promise/store/traits.rs index 200ac656..0bee796f 100644 --- a/src/invocation/promise/store/traits.rs +++ b/src/invocation/promise/store/traits.rs @@ -3,12 +3,12 @@ use libipld_core::cid::Cid; use std::collections::BTreeSet; pub trait Store { - type PromiseIndexError; + type PromiseStoreError; // NOTE put_waiting - fn put(&mut self, waiting_on: Vec, invocation: Cid) - -> Result<(), Self::PromiseIndexError>; + fn put(&mut self, invocation: Cid, waiting_on: Vec) + -> Result<(), Self::PromiseStoreError>; // NOTE get waiting - fn get(&self, waiting_on: &mut Vec) -> Result, Self::PromiseIndexError>; + fn get(&self, waiting_on: &mut Vec) -> Result, Self::PromiseStoreError>; } diff --git a/src/invocation/store.rs b/src/invocation/store.rs index a270db1d..d9ef104d 100644 --- a/src/invocation/store.rs +++ b/src/invocation/store.rs @@ -1,35 +1,51 @@ //! Storage for [`Invocation`]s. use super::Invocation; -use crate::did::Did; -use libipld_core::cid::Cid; +use crate::{crypto::varsig, did::Did}; +use libipld_core::{cid::Cid, codec::Codec}; use std::{collections::BTreeMap, convert::Infallible}; -pub trait Store { - type Error; +pub trait Store, Enc: Codec + Into + TryFrom> { + type InvocationStoreError; - fn get(&self, cid: Cid) -> Result>, Self::Error>; + fn get( + &self, + cid: Cid, + ) -> Result>, Self::InvocationStoreError>; - fn put(&mut self, cid: Cid, invocation: Invocation) -> Result<(), Self::Error>; + fn put( + &mut self, + cid: Cid, + invocation: Invocation, + ) -> Result<(), Self::InvocationStoreError>; - fn has(&self, cid: Cid) -> Result { + fn has(&self, cid: Cid) -> Result { Ok(self.get(cid).is_ok()) } } #[derive(Debug, Clone, PartialEq)] -pub struct MemoryStore { - store: BTreeMap>, +pub struct MemoryStore, Enc: Codec + Into + TryFrom> { + store: BTreeMap>, } -impl Store for MemoryStore { - type Error = Infallible; +impl, Enc: Codec + Into + TryFrom> + Store for MemoryStore +{ + type InvocationStoreError = Infallible; - fn get(&self, cid: Cid) -> Result>, Self::Error> { + fn get( + &self, + cid: Cid, + ) -> Result>, Self::InvocationStoreError> { Ok(self.store.get(&cid)) } - fn put(&mut self, cid: Cid, invocation: Invocation) -> Result<(), Self::Error> { + fn put( + &mut self, + cid: Cid, + invocation: Invocation, + ) -> Result<(), Self::InvocationStoreError> { self.store.insert(cid, invocation); Ok(()) } diff --git a/src/proof/parentful.rs b/src/proof/parentful.rs index 11a859ab..e68a208f 100644 --- a/src/proof/parentful.rs +++ b/src/proof/parentful.rs @@ -43,17 +43,21 @@ pub enum ParentfulError { /// The `cmd` field was more powerful than the proof. /// /// i.e. it behaves like moving "down" the delegation chain not "up" + #[error("The `cmd` field was more powerful than the proof")] CommandEscelation, /// The `args` field was more powerful than the proof. + #[error("The `args` field was more powerful than the proof: {0}")] ArgumentEscelation(ArgErr), /// The parents do not prove the ability. + #[error("The parents do not prove the ability: {0}")] InvalidProofChain(PrfErr), /// Comparing parents in a delegation chain failed. /// /// The specific comparison error is captured in the `ParErr`. + #[error("Comparing parents in a delegation chain failed: {0}")] InvalidParents(ParErr), // FIXME seems kinda broken -- better naming at least } diff --git a/src/receipt.rs b/src/receipt.rs index e7b70ebb..cb43ce6b 100644 --- a/src/receipt.rs +++ b/src/receipt.rs @@ -14,24 +14,41 @@ pub use payload::Payload; pub use responds::Responds; pub use store::Store; -use crate::{ability, crypto::signature, did}; +use crate::{ + ability, + crypto::{signature, varsig}, + did, +}; +use libipld_core::codec::Codec; /// The complete, signed receipt of an [`Invocation`][`crate::invocation::Invocation`]. #[derive(Clone, Debug, PartialEq)] -pub struct Receipt(pub signature::Envelope, DID>); +pub struct Receipt< + T: Responds, + DID: did::Did, + V: varsig::Header, + C: Codec + Into + TryFrom, +>(pub signature::Envelope, DID, V, C>); /// An alias for the [`Receipt`] type with the library preset /// [`Did`](crate::did)s and [Abilities](crate::ability). -pub type Preset = Receipt; - -impl Receipt { +pub type Preset = Receipt< + ability::preset::Ready, + did::preset::Verifier, + varsig::header::Preset, + varsig::encoding::Preset, +>; + +impl, C: Codec + Into + TryFrom> + Receipt +{ /// Returns the [`Payload`] of the [`Receipt`]. pub fn payload(&self) -> &Payload { &self.0.payload } /// Returns the [`signature::Envelope`] of the [`Receipt`]. - pub fn signature(&self) -> &signature::Witness { + pub fn signature(&self) -> &DID::Signature { &self.0.signature } } diff --git a/src/receipt/payload.rs b/src/receipt/payload.rs index 41ccea13..ddf6582d 100644 --- a/src/receipt/payload.rs +++ b/src/receipt/payload.rs @@ -244,9 +244,11 @@ where } #[cfg(feature = "test_utils")] -impl Arbitrary for Payload +impl Arbitrary for Payload where T::Success: Arbitrary + 'static, + DID::Parameters: Clone, + DID::Strategy: 'static, { type Parameters = (::Parameters, DID::Parameters); type Strategy = BoxedStrategy; @@ -261,7 +263,7 @@ where ], prop::collection::vec(cid::Newtype::arbitrary(), 0..25), prop::collection::vec(cid::Newtype::arbitrary(), 0..25), - prop::collection::hash_map(".*", ipld::Newtype::arbitrary(), 0..50), + prop::collection::btree_map(".*", ipld::Newtype::arbitrary(), 0..50), Nonce::arbitrary(), prop::option::of(Timestamp::arbitrary()), ) diff --git a/src/receipt/store/memory.rs b/src/receipt/store/memory.rs index c3bd3ad4..11e84731 100644 --- a/src/receipt/store/memory.rs +++ b/src/receipt/store/memory.rs @@ -1,32 +1,38 @@ use super::Store; use crate::{ + crypto::varsig, did::Did, receipt::{Receipt, Responds}, task, }; -use libipld_core::ipld::Ipld; +use libipld_core::{codec::Codec, ipld::Ipld}; use std::{collections::BTreeMap, convert::Infallible, fmt}; /// An in-memory [`receipt::Store`][crate::receipt::Store]. #[derive(Debug, Clone, PartialEq)] -pub struct MemoryStore -where +pub struct MemoryStore< + T: Responds, + DID: Did, + V: varsig::Header, + Enc: Codec + Into + TryFrom, +> where T::Success: fmt::Debug + Clone + PartialEq, { - store: BTreeMap>, + store: BTreeMap>, } -impl Store for MemoryStore +impl, Enc: Codec + Into + TryFrom> + Store for MemoryStore where ::Success: TryFrom + Into + Clone + fmt::Debug + PartialEq, { type Error = Infallible; - fn get(&self, id: &task::Id) -> Result>, Self::Error> { + fn get(&self, id: &task::Id) -> Result>, Self::Error> { Ok(self.store.get(id)) } - fn put(&mut self, id: task::Id, receipt: Receipt) -> Result<(), Self::Error> { + fn put(&mut self, id: task::Id, receipt: Receipt) -> Result<(), Self::Error> { self.store.insert(id, receipt); Ok(()) } diff --git a/src/receipt/store/traits.rs b/src/receipt/store/traits.rs index c2bed62b..40a141d4 100644 --- a/src/receipt/store/traits.rs +++ b/src/receipt/store/traits.rs @@ -1,12 +1,13 @@ use crate::{ + crypto::varsig, did::Did, receipt::{Receipt, Responds}, task, }; -use libipld_core::ipld::Ipld; +use libipld_core::{codec::Codec, ipld::Ipld}; /// A store for [`Receipt`]s indexed by their [`task::Id`]s. -pub trait Store { +pub trait Store, C: Codec + Into + TryFrom> { /// The error type representing all the ways a store operation can fail. type Error; @@ -14,12 +15,12 @@ pub trait Store { /// /// If the store itself did not experience an error, but the value /// was not found, the result will be `Ok(None)`. - fn get<'a>(&self, id: &task::Id) -> Result>, Self::Error> + fn get<'a>(&self, id: &task::Id) -> Result>, Self::Error> where ::Success: TryFrom; /// Store a [`Receipt`] by its [`task::Id`]. - fn put(&mut self, id: task::Id, receipt: Receipt) -> Result<(), Self::Error> + fn put(&mut self, id: task::Id, receipt: Receipt) -> Result<(), Self::Error> where ::Success: Into; } From 76f0fc9baf159f7de969b5baf905c6cfa8f199c5 Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Tue, 20 Feb 2024 16:31:43 -0800 Subject: [PATCH 098/188] Expose CID helpers --- src/ability/crud.rs | 11 ++++++ src/ability/crud/any.rs | 18 +++++++++- src/ability/crud/create.rs | 22 ++++++++++++ src/ability/crud/destroy.rs | 18 ++++++++++ src/ability/crud/mutate.rs | 21 ++++++++++- src/ability/crud/parents.rs | 9 +++++ src/ability/crud/read.rs | 22 ++++++++++++ src/ability/crud/update.rs | 22 ++++++++++++ src/ability/msg.rs | 12 +++++-- src/ability/msg/any.rs | 14 +++++++- src/ability/msg/receive.rs | 14 +++++++- src/ability/preset.rs | 19 ++++++++++ src/crypto/signature/envelope.rs | 39 +++++++++++++++++++++ src/delegation.rs | 14 ++++++++ src/invocation.rs | 12 +++++++ src/invocation/agent.rs | 52 ++++++++++++++-------------- src/invocation/promise/resolvable.rs | 3 +- src/proof/checkable.rs | 4 ++- src/proof/parentful.rs | 14 ++++++++ src/proof/parentless.rs | 12 ++++++- 20 files changed, 317 insertions(+), 35 deletions(-) diff --git a/src/ability/crud.rs b/src/ability/crud.rs index 98c8804e..1c4a03f9 100644 --- a/src/ability/crud.rs +++ b/src/ability/crud.rs @@ -122,6 +122,17 @@ impl CheckParents for Builder { } } +impl From for arguments::Named { + fn from(builder: Builder) -> Self { + match builder { + Builder::Create(create) => create.into(), + Builder::Read(read) => read.into(), + Builder::Update(update) => update.into(), + Builder::Destroy(destroy) => destroy.into(), + } + } +} + impl From for arguments::Named { fn from(promised: Promised) -> Self { match promised { diff --git a/src/ability/crud/any.rs b/src/ability/crud/any.rs index 077d1d70..775a58f7 100644 --- a/src/ability/crud/any.rs +++ b/src/ability/crud/any.rs @@ -1,7 +1,7 @@ //! "Any" CRUD ability (superclass of all CRUD abilities) use crate::{ - ability::command::Command, + ability::{arguments, command::Command}, proof::{error::OptionalFieldError, parentless::NoParents, same::CheckSame}, }; use libipld_core::{error::SerdeError, ipld::Ipld, serde as ipld_serde}; @@ -94,3 +94,19 @@ impl From for Ipld { builder.into() } } + +impl From for arguments::Named { + fn from(any: Any) -> arguments::Named { + let mut named = arguments::Named::new(); + if let Some(path) = any.path { + named.insert( + "path".into(), + path.into_os_string() + .into_string() + .expect("PathBuf should generate a valid path") + .into(), + ); + } + named + } +} diff --git a/src/ability/crud/create.rs b/src/ability/crud/create.rs index e62494ac..85a77250 100644 --- a/src/ability/crud/create.rs +++ b/src/ability/crud/create.rs @@ -312,3 +312,25 @@ impl promise::Resolvable for Ready { Ok(Ready { path, args }) } } + +impl From for arguments::Named { + fn from(builder: Builder) -> Self { + let mut named = arguments::Named::new(); + + if let Some(path) = builder.path { + named.insert( + "path".to_string(), + path.into_os_string() + .into_string() + .expect("PathBuf to generate valid paths") // FIXME reasonable assumption? + .into(), + ); + } + + if let Some(args) = builder.args { + named.insert("args".to_string(), args.into()); + } + + named + } +} diff --git a/src/ability/crud/destroy.rs b/src/ability/crud/destroy.rs index f47200e0..95f29319 100644 --- a/src/ability/crud/destroy.rs +++ b/src/ability/crud/destroy.rs @@ -256,3 +256,21 @@ impl From for Ready { } } } + +impl From for arguments::Named { + fn from(builder: Builder) -> Self { + let mut named = arguments::Named::new(); + + if let Some(path) = builder.path { + named.insert( + "path".to_string(), + path.into_os_string() + .into_string() + .expect("PathBuf to generate valid paths") // FIXME reasonable assumption? + .into(), + ); + } + + named + } +} diff --git a/src/ability/crud/mutate.rs b/src/ability/crud/mutate.rs index ab8c85a3..241fa1f2 100644 --- a/src/ability/crud/mutate.rs +++ b/src/ability/crud/mutate.rs @@ -1,7 +1,7 @@ //! The delegation superclass for all mutable CRUD actions. use crate::{ - ability::command::Command, + ability::{arguments, command::Command}, proof::{ checkable::Checkable, error::OptionalFieldError, parentful::Parentful, parents::CheckParents, same::CheckSame, @@ -113,3 +113,22 @@ impl CheckParents for Mutate { Ok(()) } } + +impl From for arguments::Named { + fn from(mutate: Mutate) -> Self { + let mut args = arguments::Named::new(); + + if let Some(path) = mutate.path { + args.insert( + "path".into(), + Ipld::String( + path.into_os_string() + .into_string() + .expect("PathBuf should generate a valid path"), + ), + ); + } + + args + } +} diff --git a/src/ability/crud/parents.rs b/src/ability/crud/parents.rs index 4dd92d24..8d3be766 100644 --- a/src/ability/crud/parents.rs +++ b/src/ability/crud/parents.rs @@ -127,3 +127,12 @@ impl CheckSame for MutableParents { } } } + +impl From for arguments::Named { + fn from(parents: MutableParents) -> Self { + match parents { + MutableParents::Any(any) => any.into(), + MutableParents::Mutate(mutate) => mutate.into(), + } + } +} diff --git a/src/ability/crud/read.rs b/src/ability/crud/read.rs index 99ee9022..c1be22fd 100644 --- a/src/ability/crud/read.rs +++ b/src/ability/crud/read.rs @@ -285,3 +285,25 @@ impl promise::Resolvable for Ready { Ok(Ready { path, args }) } } + +impl From for arguments::Named { + fn from(builder: Builder) -> Self { + let mut named = arguments::Named::new(); + + if let Some(path) = builder.path { + named.insert( + "path".to_string(), + path.into_os_string() + .into_string() + .expect("PathBuf should make a valid path") + .into(), + ); + } + + if let Some(args) = builder.args { + named.insert("args".to_string(), args.into()); + } + + named + } +} diff --git a/src/ability/crud/update.rs b/src/ability/crud/update.rs index 0dc4f992..0ed14da3 100644 --- a/src/ability/crud/update.rs +++ b/src/ability/crud/update.rs @@ -335,3 +335,25 @@ impl From for Builder { } } } + +impl From for arguments::Named { + fn from(builder: Builder) -> Self { + let mut named = arguments::Named::new(); + + if let Some(path) = builder.path { + named.insert( + "path".to_string(), + path.into_os_string() + .into_string() + .expect("PathBuf to generate valid paths") // FIXME reasonable assumption? + .into(), + ); + } + + if let Some(args) = builder.args { + named.insert("args".to_string(), args.into()); + } + + named + } +} diff --git a/src/ability/msg.rs b/src/ability/msg.rs index 1f56e40b..d3099de8 100644 --- a/src/ability/msg.rs +++ b/src/ability/msg.rs @@ -1,12 +1,11 @@ //! Message abilities mod any; -mod receive; +pub mod receive; pub mod send; pub use any::Any; -pub use receive::Receive; use crate::{ ability::arguments, @@ -119,3 +118,12 @@ impl CheckParents for Builder { impl Checkable for Builder { type Hierarchy = Parentful; } + +impl From for arguments::Named { + fn from(builder: Builder) -> Self { + match builder { + Builder::Send(send) => send.into(), + Builder::Receive(receive) => receive.into(), + } + } +} diff --git a/src/ability/msg/any.rs b/src/ability/msg/any.rs index 48f8eeab..8290420b 100644 --- a/src/ability/msg/any.rs +++ b/src/ability/msg/any.rs @@ -1,7 +1,7 @@ //! "Any" message ability (superclass of all message abilities) use crate::{ - ability::command::Command, + ability::{arguments, command::Command}, proof::{parentless::NoParents, same::CheckSame}, }; use libipld_core::{error::SerdeError, ipld::Ipld, serde as ipld_serde}; @@ -73,3 +73,15 @@ impl CheckSame for Any { Ok(()) } } + +impl From for arguments::Named { + fn from(any: Any) -> arguments::Named { + let mut args = arguments::Named::new(); + + if let Some(from) = any.from { + args.insert("from".into(), from.to_string().into()); + } + + args + } +} diff --git a/src/ability/msg/receive.rs b/src/ability/msg/receive.rs index 36381e04..840a5329 100644 --- a/src/ability/msg/receive.rs +++ b/src/ability/msg/receive.rs @@ -46,7 +46,7 @@ pub struct Receive { pub from: Option, } -pub(super) type Builder = Receive; +pub type Builder = Receive; // FIXME needs promisory version @@ -149,3 +149,15 @@ impl promise::Resolvable for Receive { } } } + +impl From for arguments::Named { + fn from(builder: Builder) -> Self { + let mut args = arguments::Named::new(); + + if let Some(from) = builder.from { + args.insert("from".into(), from.into()); + } + + args + } +} diff --git a/src/ability/preset.rs b/src/ability/preset.rs index 50822bb7..d9ccbc4e 100644 --- a/src/ability/preset.rs +++ b/src/ability/preset.rs @@ -155,3 +155,22 @@ impl From for Builder { } } } + +impl From for arguments::Named { + fn from(builder: Builder) -> Self { + match builder { + Builder::Crud(builder) => builder.into(), + Builder::Msg(builder) => builder.into(), + Builder::Wasm(builder) => builder.into(), + } + } +} + +impl From for arguments::Named { + fn from(parents: Parents) -> Self { + match parents { + Parents::Crud(parents) => parents.into(), + Parents::Msg(parents) => parents.into(), + } + } +} diff --git a/src/crypto/signature/envelope.rs b/src/crypto/signature/envelope.rs index 2b1237f5..a7b27c87 100644 --- a/src/crypto/signature/envelope.rs +++ b/src/crypto/signature/envelope.rs @@ -4,9 +4,11 @@ use crate::{ did::{Did, Verifiable}, }; use libipld_core::{ + cid::{Cid, CidGeneric}, codec::{Codec, Encode}, error::Result, ipld::Ipld, + multihash::{Code, MultihashDigest}, }; use signature::{SignatureEncoding, Signer}; use std::collections::BTreeMap; @@ -155,6 +157,23 @@ impl< .verify(&encoded, &self.signature) .map_err(ValidateError::VerifyError) } + + pub fn cid(&self) -> Result + where + Self: Clone, + Ipld: Encode, + T: Into, + { + let codec = self.varsig_header.codec().clone(); + let mut ipld_buffer = vec![]; + self.encode(codec, &mut ipld_buffer)?; + + let multihash = Code::Sha2_256.digest(&ipld_buffer); + Ok(CidGeneric::new_v1( + self.varsig_header.codec().clone().into(), + multihash, + )) + } } impl< @@ -175,6 +194,26 @@ impl< } } +impl< + T: Verifiable + Capsule + Into, + DID: Did, + V: varsig::Header, + Enc: Codec + Into + TryFrom, + > Encode for Envelope +where + Self: Clone, + Ipld: Encode, +{ + fn encode( + &self, + codec: Enc, + w: &mut W, + ) -> Result<(), libipld_core::error::Error> { + let ipld: Ipld = self.clone().into(); + ipld.encode(codec, w) + } +} + /// Errors that can occur when signing a [`siganture::Envelope`][Envelope]. #[derive(Debug, Error)] pub enum SignError { diff --git a/src/delegation.rs b/src/delegation.rs index 05c5033d..91d8790d 100644 --- a/src/delegation.rs +++ b/src/delegation.rs @@ -32,8 +32,10 @@ use crate::{ }; use condition::Condition; use libipld_core::{ + cid::{Cid, CidGeneric}, codec::{Codec, Encode}, ipld::Ipld, + multihash::{Code, MultihashDigest}, }; use std::collections::BTreeMap; use web_time::SystemTime; @@ -139,6 +141,18 @@ impl, Enc: Codec + Into + &self.0.signature } + pub fn codec(&self) -> &Enc { + self.varsig_header().codec() + } + + pub fn cid(&self) -> Result + where + signature::Envelope, DID, V, Enc>: Clone + Encode, + Ipld: Encode, + { + self.0.cid() + } + pub fn validate_signature(&self) -> Result<(), signature::ValidateError> where Payload: Clone, diff --git a/src/invocation.rs b/src/invocation.rs index 4b6a4c60..1e75b4a2 100644 --- a/src/invocation.rs +++ b/src/invocation.rs @@ -133,6 +133,18 @@ where self.payload().check_time(now) } + pub fn codec(&self) -> &Enc { + self.varsig_header().codec() + } + + pub fn cid(&self) -> Result + where + signature::Envelope, DID, V, Enc>: Clone + Encode, + Ipld: Encode, + { + self.0.cid() + } + pub fn try_sign( signer: &DID::Signer, varsig_header: V, diff --git a/src/invocation/agent.rs b/src/invocation/agent.rs index b7dd7a79..1a285e34 100644 --- a/src/invocation/agent.rs +++ b/src/invocation/agent.rs @@ -1,7 +1,7 @@ use super::{payload::Payload, promise::Resolvable, store::Store, Invocation}; use crate::{ ability::{arguments, ucan}, - crypto::{varsig, Nonce}, + crypto::{signature as ucan_signature, varsig, Nonce}, delegation, delegation::{condition::Condition, Delegable}, did::{Did, Verifiable}, @@ -14,8 +14,9 @@ use libipld_core::{ cid::{Cid, CidGeneric}, codec::{Codec, Encode}, ipld::Ipld, - multihash::{Code, MultihashGeneric}, + multihash::{Code, MultihashDigest}, }; +use signature; use std::{collections::BTreeMap, fmt, marker::PhantomData}; use thiserror::Error; use web_time::SystemTime; @@ -26,7 +27,7 @@ pub struct Agent< T: Resolvable + Delegable, C: Condition, DID: Did, - S: Store, + S: Store, P: promise::Store, D: delegation::store::Store, V: varsig::Header, @@ -36,8 +37,7 @@ pub struct Agent< pub delegation_store: &'a mut D, pub invocation_store: &'a mut S, - pub unresolved_promise_store: &'a mut P, - pub resolved_promise_store: &'a mut P, + pub unresolved_promise_index: &'a mut P, signer: &'a ::Signer, marker: PhantomData<(T, C, V, Enc)>, @@ -48,7 +48,7 @@ impl< T: Resolvable + Delegable + Clone, C: Condition, DID: Did + Clone, - S: Store, + S: Store, P: promise::Store, D: delegation::store::Store, V: varsig::Header, @@ -64,15 +64,13 @@ where signer: &'a ::Signer, invocation_store: &'a mut S, delegation_store: &'a mut D, - unresolved_promise_store: &'a mut P, - resolved_promise_store: &'a mut P, + unresolved_promise_index: &'a mut P, ) -> Self { Self { did, invocation_store, delegation_store, - unresolved_promise_store, - resolved_promise_store, + unresolved_promise_index, signer, marker: PhantomData, } @@ -122,23 +120,29 @@ where now: &SystemTime, ) -> Result>, ReceiveError> where + T::Builder: Into> + Clone, + Ipld: Encode, C: fmt::Debug + Clone, ::Hierarchy: Clone + Into>, - T::Builder: Clone + Checkable + Prove + Into>, Invocation: Clone, <<::Builder as Checkable>::Hierarchy as Prove>::Error: fmt::Debug,

>::PromiseStoreError: fmt::Debug, + ucan_signature::Envelope, DID, V, Enc>: Clone, + ucan_signature::Envelope, DID, V, Enc>: Clone, { + // FIXME You know... store it + // also: Envelops hsould have a cid() method + // self.invocation_store + // .put(promised.cid().clone(), promised.clone()) + // .map_err(ReceiveError::PromiseStoreError)?; + let mut buffer = vec![]; Ipld::from(promised.clone()) .encode(*promised.varsig_header().codec(), &mut buffer) .map_err(ReceiveError::EncodingError)?; - let cid: Cid = CidGeneric::new_v1( - DagCborCodec.into(), - MultihashGeneric::wrap(Code::Sha2_256.into(), buffer.as_slice()) - .map_err(ReceiveError::MultihashError)?, - ); + let multihash = Code::Sha2_256.digest(buffer.as_slice()); + let cid: Cid = CidGeneric::new_v1(DagCborCodec.into(), multihash); let mut encoded = vec![]; Ipld::from(promised.payload().clone()) @@ -153,19 +157,13 @@ where let resolved_ability: T = match Resolvable::try_resolve(promised.ability().clone()) { Ok(resolved) => resolved, Err(_) => { - // FIXME check if any of the unresolved promises are in the store - // FIXME check if it's actually unresolved - - // self.invocation_store - // .put(cid.clone(), promised.clone()) - // .map_err(ReceiveError::PromiseStoreError)?; + let waiting_on_cid = todo!(); - self.unresolved_promise_store - .put(cid, todo!()) // cid for promised) + self.unresolved_promise_index + .put(promised.cid()?, vec![waiting_on_cid]) .map_err(ReceiveError::PromiseStoreError)?; - todo!() - // return Ok(Recipient::Other(promised)); // FIXME + return Ok(Recipient::Unresolved(cid)); } }; @@ -242,8 +240,10 @@ where #[derive(Debug)] pub enum Recipient { + // FIXME change to status You(T), Other(T), + Unresolved(Cid), } #[derive(Debug, Error)] diff --git a/src/invocation/promise/resolvable.rs b/src/invocation/promise/resolvable.rs index d039585e..ee0d111b 100644 --- a/src/invocation/promise/resolvable.rs +++ b/src/invocation/promise/resolvable.rs @@ -1,5 +1,5 @@ use crate::{ability::arguments, delegation::Delegable}; -use libipld_core::ipld::Ipld; +use libipld_core::{cid::Cid, ipld::Ipld}; // FIXME rename "Unresolved" // FIXME better name @@ -18,5 +18,6 @@ pub trait Resolvable: Delegable { type Promised: Into + Into>; /// Attempt to resolve the [`Self::Promised`]. + // FIXME bubble up what we're waiting on fn try_resolve(promised: Self::Promised) -> Result; fn try_resolve(promised: Self::Promised) -> Result; } diff --git a/src/proof/checkable.rs b/src/proof/checkable.rs index 5098c314..3e6d2031 100644 --- a/src/proof/checkable.rs +++ b/src/proof/checkable.rs @@ -1,6 +1,8 @@ //! Define the hierarchy of an ability (or mark as not having one) use super::{prove::Prove, same::CheckSame}; +use crate::ability::arguments; +use libipld_core::ipld::Ipld; // FIXME move to Delegatbel? @@ -11,5 +13,5 @@ pub trait Checkable: CheckSame + Sized { /// The only options are [`Parentful`][super::parentful::Parentful] /// and [`Parentless`][super::parentless::Parentless], /// (which are the only instances of the unexported `Checker`) - type Hierarchy: CheckSame + Prove + From + PartialEq; + type Hierarchy: CheckSame + Prove + From + PartialEq + Into>; } diff --git a/src/proof/parentful.rs b/src/proof/parentful.rs index e68a208f..a6515cc8 100644 --- a/src/proof/parentful.rs +++ b/src/proof/parentful.rs @@ -6,6 +6,7 @@ use super::{ prove::{Prove, Success}, same::CheckSame, }; +use crate::ability::arguments; use libipld_core::{error::SerdeError, ipld::Ipld, serde as ipld_serde}; use serde::{de::DeserializeOwned, Deserialize, Serialize}; use thiserror::Error; @@ -61,6 +62,19 @@ pub enum ParentfulError { InvalidParents(ParErr), // FIXME seems kinda broken -- better naming at least } +impl From> for arguments::Named +where + arguments::Named: From + From, +{ + fn from(parentful: Parentful) -> Self { + match parentful { + Parentful::Any => arguments::Named::new(), + Parentful::Parents(parents) => parents.into(), + Parentful::This(this) => this.into(), + } + } +} + impl From> for Ipld where Ipld: From, diff --git a/src/proof/parentless.rs b/src/proof/parentless.rs index 645246db..5dafb16a 100644 --- a/src/proof/parentless.rs +++ b/src/proof/parentless.rs @@ -6,6 +6,7 @@ use super::{ prove::{Prove, Success}, same::CheckSame, }; +use crate::ability::arguments; use libipld_core::ipld::Ipld; use serde::{Deserialize, Serialize}; @@ -30,6 +31,15 @@ impl From for Parentless { } } +impl>> From> for arguments::Named { + fn from(parentless: Parentless) -> Self { + match parentless { + Parentless::Any => todo!(), + Parentless::This(this) => this.into(), + } + } +} + // FIXME generally useful (e.g. checkiung `_/*`); move to its own module and rename? /// Error cases when checking proofs #[derive(Debug, Clone, PartialEq)] @@ -49,7 +59,7 @@ pub enum ParentlessError { /// This behaves as an alias for `Checkable::>`. pub trait NoParents {} -impl Checkable for T { +impl>> Checkable for T { type Hierarchy = Parentless; } From 66efbdaa05b82653875559983f7c15bab57d5832 Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Wed, 21 Feb 2024 13:47:15 -0800 Subject: [PATCH 099/188] Massively simplify instanced for abilities --- src/ability/arguments/named.rs | 10 + src/ability/command.rs | 32 +-- src/ability/crud.rs | 131 ++++++++--- src/ability/crud/any.rs | 47 +++- src/ability/crud/create.rs | 275 ++++++++++------------ src/ability/crud/destroy.rs | 127 ++++------ src/ability/crud/mutate.rs | 16 ++ src/ability/crud/parents.rs | 43 ++-- src/ability/crud/read.rs | 294 +++++++++++------------ src/ability/crud/update.rs | 100 +++++--- src/ability/dynamic.rs | 11 +- src/ability/msg.rs | 108 +++++++-- src/ability/msg/receive.rs | 66 ++++-- src/ability/msg/send.rs | 158 ++++++++----- src/ability/preset.rs | 105 +++++---- src/ability/ucan/revoke.rs | 51 +++- src/ability/wasm/module.rs | 7 + src/ability/wasm/run.rs | 173 +++++++++----- src/delegation/delegable.rs | 43 +++- src/delegation/payload.rs | 2 +- src/invocation/agent.rs | 8 +- src/invocation/payload.rs | 2 +- src/invocation/promise.rs | 7 +- src/invocation/promise/pending.rs | 9 + src/invocation/promise/resolvable.rs | 96 +++++++- src/invocation/promise/resolves.rs | 11 +- src/ipld.rs | 6 +- src/ipld/promised.rs | 338 +++++++++++++++++++++++---- src/proof/error.rs | 2 +- src/proof/util.rs | 26 +-- src/reader/builder.rs | 9 +- src/reader/generic.rs | 10 +- 32 files changed, 1540 insertions(+), 783 deletions(-) create mode 100644 src/invocation/promise/pending.rs diff --git a/src/ability/arguments/named.rs b/src/ability/arguments/named.rs index 5ccf97c1..6ea813ef 100644 --- a/src/ability/arguments/named.rs +++ b/src/ability/arguments/named.rs @@ -81,6 +81,10 @@ impl Named { self.0.is_empty() } + pub fn values(&self) -> impl Iterator { + self.0.values() + } + pub fn contains(&self, other: &Named) -> Result<(), NamedError> where T: PartialEq, @@ -146,6 +150,12 @@ impl> TryFrom for Named { } } +// impl From> for Named { +// fn from(map: BTreeMap) -> Self { +// Named(map) +// } +// } + impl> From> for Ipld { fn from(arguments: Named) -> Self { Ipld::Map( diff --git a/src/ability/command.rs b/src/ability/command.rs index 1ab0d979..b9c5d0ab 100644 --- a/src/ability/command.rs +++ b/src/ability/command.rs @@ -56,42 +56,46 @@ pub trait Command { } // FIXME definitely needs a better name +// pub trait ParseAbility: TryFrom> { pub trait ParseAbility: Sized { - type Error: fmt::Debug; + type ArgsErr: fmt::Debug; - // FIXME rename this trait to Ability? - fn try_parse(cmd: &str, args: &arguments::Named) -> Result; + fn try_parse( + cmd: &str, + args: arguments::Named, + ) -> Result>; } #[derive(Debug, Clone, Error)] pub enum ParseAbilityError { - #[error("Unknown command")] - UnknownCommand, + #[error("Unknown command: {0}")] + UnknownCommand(String), #[error(transparent)] InvalidArgs(#[from] E), } -impl> ParseAbility for T +impl>> ParseAbility for T where - >::Error: fmt::Debug, + >>::Error: fmt::Debug, { - type Error = ParseAbilityError<>::Error>; + type ArgsErr = >>::Error; - fn try_parse(cmd: &str, args: &arguments::Named) -> Result { + fn try_parse( + cmd: &str, + args: arguments::Named, + ) -> Result>>::Error>> { if cmd != T::COMMAND { - return Err(ParseAbilityError::UnknownCommand); + return Err(ParseAbilityError::UnknownCommand(cmd.to_string())); } - Ipld::Map(args.0.clone()) - .try_into() - .map_err(ParseAbilityError::InvalidArgs) + Self::try_from(args).map_err(ParseAbilityError::InvalidArgs) } } // NOTE do not export; this is used to limit the Hierarchy // interface to [Parentful] and [Parentless] while enabling [Dynamic] -// FIXME ^^^^ NOT ANYMORE +// FIXME ^^^^ NOT ANYMORE? // Either that needs to be re-locked down, or (because it's all abstract anyways) // just note that you probably don;t want this one. pub trait ToCommand { diff --git a/src/ability/crud.rs b/src/ability/crud.rs index 1c4a03f9..bddccbca 100644 --- a/src/ability/crud.rs +++ b/src/ability/crud.rs @@ -52,9 +52,13 @@ pub use mutate::Mutate; pub use parents::*; use crate::{ - ability::arguments, + ability::{ + arguments, + command::{ParseAbility, ParseAbilityError, ToCommand}, + }, delegation::Delegable, invocation::promise::Resolvable, + ipld, proof::{checkable::Checkable, parentful::Parentful, parents::CheckParents, same::CheckSame}, }; use libipld_core::ipld::Ipld; @@ -72,10 +76,10 @@ pub enum Ready { #[derive(Debug, Clone, PartialEq)] pub enum Builder { - Create(create::Builder), - Read(read::Builder), + Create(create::Ready), + Read(read::Ready), Update(update::Builder), - Destroy(destroy::Builder), + Destroy(destroy::Ready), } #[derive(Debug, Clone, PartialEq)] @@ -90,17 +94,82 @@ impl Delegable for Ready { type Builder = Builder; } +impl ParseAbility for Builder { + type ArgsErr = (); + + fn try_parse( + cmd: &str, + args: arguments::Named, + ) -> Result> { + match create::Ready::try_parse(cmd, args.clone()) { + Ok(create) => return Ok(Builder::Create(create)), + Err(ParseAbilityError::InvalidArgs(_)) => { + return Err(ParseAbilityError::InvalidArgs(())); + } + Err(ParseAbilityError::UnknownCommand(_)) => (), + } + + match read::Ready::try_parse(cmd, args.clone()) { + Ok(read) => return Ok(Builder::Read(read)), + Err(ParseAbilityError::InvalidArgs(_)) => { + return Err(ParseAbilityError::InvalidArgs(())); + } + Err(ParseAbilityError::UnknownCommand(_)) => (), + } + + match update::Builder::try_parse(cmd, args.clone()) { + Ok(update) => return Ok(Builder::Update(update)), + Err(ParseAbilityError::InvalidArgs(_)) => { + return Err(ParseAbilityError::InvalidArgs(())); + } + Err(ParseAbilityError::UnknownCommand(_)) => (), + } + + match destroy::Ready::try_parse(cmd, args) { + Ok(destroy) => return Ok(Builder::Destroy(destroy)), + Err(ParseAbilityError::InvalidArgs(_)) => { + return Err(ParseAbilityError::InvalidArgs(())); + } + Err(ParseAbilityError::UnknownCommand(_)) => (), + } + + Err(ParseAbilityError::UnknownCommand(cmd.into())) + } +} + impl Checkable for Builder { type Hierarchy = Parentful; } -impl From for Builder { - fn from(promised: Promised) -> Self { - match promised { - Promised::Create(create) => Builder::Create(create.into()), - Promised::Read(read) => Builder::Read(read.into()), - Promised::Update(update) => Builder::Update(update.into()), - Promised::Destroy(destroy) => Builder::Destroy(destroy.into()), +impl ToCommand for Ready { + fn to_command(&self) -> String { + match self { + Ready::Create(create) => create.to_command(), + Ready::Read(read) => read.to_command(), + Ready::Update(update) => update.to_command(), + Ready::Destroy(destroy) => destroy.to_command(), + } + } +} + +impl ToCommand for Promised { + fn to_command(&self) -> String { + match self { + Promised::Create(create) => create.to_command(), + Promised::Read(read) => read.to_command(), + Promised::Update(update) => update.to_command(), + Promised::Destroy(destroy) => destroy.to_command(), + } + } +} + +impl ToCommand for Builder { + fn to_command(&self) -> String { + match self { + Builder::Create(create) => create.to_command(), + Builder::Read(read) => read.to_command(), + Builder::Update(update) => update.to_command(), + Builder::Destroy(destroy) => destroy.to_command(), } } } @@ -133,16 +202,16 @@ impl From for arguments::Named { } } -impl From for arguments::Named { - fn from(promised: Promised) -> Self { - match promised { - Promised::Create(create) => create.into(), - Promised::Read(read) => read.into(), - Promised::Update(update) => update.into(), - Promised::Destroy(destroy) => destroy.into(), - } - } -} +// impl From for arguments::Named { +// fn from(promised: Promised) -> Self { +// match promised { +// Promised::Create(create) => create.into(), +// Promised::Read(read) => read.into(), +// Promised::Update(update) => update.into(), +// Promised::Destroy(destroy) => destroy.into(), +// } +// } +// } impl From for Builder { fn from(ready: Ready) -> Self { @@ -184,21 +253,15 @@ impl CheckSame for Builder { impl Resolvable for Ready { type Promised = Promised; +} - fn try_resolve(promised: Promised) -> Result { +impl From for arguments::Named { + fn from(promised: Promised) -> Self { match promised { - Promised::Create(create) => Resolvable::try_resolve(create) - .map(Ready::Create) - .map_err(Promised::Create), - Promised::Read(read) => Resolvable::try_resolve(read) - .map(Ready::Read) - .map_err(Promised::Read), - Promised::Update(update) => Resolvable::try_resolve(update) - .map(Ready::Update) - .map_err(Promised::Update), - Promised::Destroy(destroy) => Resolvable::try_resolve(destroy) - .map(Ready::Destroy) - .map_err(Promised::Destroy), + Promised::Create(create) => create.into(), + Promised::Read(read) => read.into(), + Promised::Update(update) => update.into(), + Promised::Destroy(destroy) => destroy.into(), } } } diff --git a/src/ability/crud/any.rs b/src/ability/crud/any.rs index 775a58f7..f5bb2aab 100644 --- a/src/ability/crud/any.rs +++ b/src/ability/crud/any.rs @@ -1,7 +1,10 @@ //! "Any" CRUD ability (superclass of all CRUD abilities) use crate::{ - ability::{arguments, command::Command}, + ability::{ + arguments, + command::{Command, ParseAbility, ParseAbilityError}, + }, proof::{error::OptionalFieldError, parentless::NoParents, same::CheckSame}, }; use libipld_core::{error::SerdeError, ipld::Ipld, serde as ipld_serde}; @@ -64,14 +67,56 @@ impl Command for Any { const COMMAND: &'static str = "crud/*"; } +impl TryFrom> for Any { + type Error = (); + + fn try_from(arguments: arguments::Named) -> Result { + let mut path = None; + + for (key, value) in arguments.iter() { + match key.as_str() { + "path" => { + let some_path = match value { + Ipld::String(s) => Ok(PathBuf::from(s)), + _ => Err(()), + }?; + + path = Some(some_path); + } + _ => return Err(()), + } + } + + Ok(Any { path }) + } +} + +// FIXME pipe example + impl NoParents for Any {} +// impl ParseAbility for Any { +// type ArgsErr = (); +// +// fn try_parse( +// cmd: &str, +// args: arguments::Named, +// ) -> Result> { +// if cmd != Self::COMMAND { +// return Err(ParseAbilityError::CommandMismatch); +// } +// +// Self::try_from(args).map_err(|_| ParseAbilityError::Args(Self::COMMAND)) +// } +// } + impl CheckSame for Any { type Error = OptionalFieldError; fn check_same(&self, proof: &Self) -> Result<(), Self::Error> { if let Some(path) = &self.path { let proof_path = proof.path.as_ref().ok_or(OptionalFieldError::Missing)?; + if path != proof_path { return Err(OptionalFieldError::Unequal); } diff --git a/src/ability/crud/create.rs b/src/ability/crud/create.rs index 85a77250..1247cacd 100644 --- a/src/ability/crud/create.rs +++ b/src/ability/crud/create.rs @@ -7,9 +7,9 @@ use crate::{ ipld, proof::{checkable::Checkable, parentful::Parentful, parents::CheckParents, same::CheckSame}, }; -use libipld_core::ipld::Ipld; +use libipld_core::{cid::Cid, error::SerdeError, ipld::Ipld, serde as ipld_serde}; use serde::Serialize; -use std::path::PathBuf; +use std::{collections::BTreeMap, path::PathBuf}; // FIXME deserialize instance @@ -57,40 +57,17 @@ pub struct Generic { /// /// style createready stroke:orange; /// ``` -pub type Ready = Generic>; +#[derive(Debug, Clone, PartialEq, Serialize)] +#[serde(deny_unknown_fields)] +pub struct Ready { + /// An optional path to a sub-resource that is to be created. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub path: Option, -#[cfg_attr(doc, aquamarine::aquamarine)] -/// The delegatable ability for creating other agents. -/// -/// # Lifecycle -/// -/// The lifecycle of a `crud/create` ability is as follows: -/// -/// ```mermaid -/// flowchart LR -/// subgraph Delegations -/// top("*") -/// -/// subgraph CRUD Abilities -/// any("crud/*") -/// -/// mutate("crud/mutate") -/// -/// subgraph Invokable -/// create("crud/create") -/// end -/// end -/// end -/// -/// createpromise("crud::create::Promised") -/// createready("crud::create::Ready") -/// -/// top --> any --> mutate --> create -/// create -.->|invoke| createpromise -.->|resolve| createready -.-> exe{{execute}} -/// -/// style create stroke:orange; -/// ``` -pub type Builder = Generic>; + /// Optional arguments for creation. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub args: Option>, +} #[cfg_attr(doc, aquamarine::aquamarine)] /// An invoked `crud/create` ability (but possibly awaiting another @@ -124,53 +101,90 @@ pub type Builder = Generic>; /// /// style createpromise stroke:orange; /// ``` -pub type Promised = Generic, arguments::Promised>; +#[derive(Debug, Clone, PartialEq, Serialize)] +#[serde(deny_unknown_fields)] +pub struct Promised { + /// An optional path to a sub-resource that is to be created. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub path: Option>, -impl Command for Generic { - const COMMAND: &'static str = "crud/create"; + /// Optional arguments for creation. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub args: Option>>, } -impl, A: Into> From> for Ipld { - fn from(create: Generic) -> Self { - create.into() - } +const COMMAND: &str = "crud/create"; + +impl Command for Ready { + const COMMAND: &'static str = COMMAND; } -impl, A: TryFrom> TryFrom for Generic { - type Error = (); // FIXME +impl Command for Promised { + const COMMAND: &'static str = COMMAND; +} - fn try_from(ipld: Ipld) -> Result { - if let Ipld::Map(mut map) = ipld { - if map.len() > 2 { - return Err(()); // FIXME +// impl TryFrom for Ready { +// type Error = (); // FIXME +// +// fn try_from(ipld: Ipld) -> Result { +// if let Ipld::Map(mut map) = ipld { +// if map.len() > 2 { +// return Err(()); // FIXME +// } +// +// Ok(Generic { +// path: map +// .remove("path") +// .map(|ipld| P::try_from(ipld).map_err(|_| ())) +// .transpose()?, +// +// args: map +// .remove("args") +// .map(|ipld| A::try_from(ipld).map_err(|_| ())) +// .transpose()?, +// }) +// } else { +// Err(()) // FIXME +// } +// } +// } + +impl TryFrom> for Ready { + type Error = (); + + fn try_from(arguments: arguments::Named) -> Result { + let mut path = None; + let mut args = None; + + for (k, ipld) in arguments { + match k.as_str() { + "path" => { + if let Ipld::String(s) = ipld { + path = Some(PathBuf::from(s)); + } else { + return Err(()); + } + } + "args" => { + args = Some(ipld.try_into().map_err(|_| ())?); + } + _ => return Err(()), } - - Ok(Generic { - path: map - .remove("path") - .map(|ipld| P::try_from(ipld).map_err(|_| ())) - .transpose()?, - - args: map - .remove("args") - .map(|ipld| A::try_from(ipld).map_err(|_| ())) - .transpose()?, - }) - } else { - Err(()) // FIXME } + + Ok(Ready { path, args }) } } impl Delegable for Ready { - type Builder = Builder; + type Builder = Ready; } -impl Checkable for Builder { - type Hierarchy = Parentful; +impl Checkable for Ready { + type Hierarchy = Parentful; } -impl CheckSame for Builder { +impl CheckSame for Ready { type Error = (); // FIXME better error fn check_same(&self, proof: &Self) -> Result<(), Self::Error> { @@ -182,7 +196,7 @@ impl CheckSame for Builder { } } -impl CheckParents for Builder { +impl CheckParents for Ready { type Parents = MutableParents; type ParentError = (); // FIXME @@ -212,45 +226,21 @@ impl CheckParents for Builder { } } -impl From for arguments::Named { - fn from(promised: Promised) -> Self { - let mut named = arguments::Named::new(); - - if let Some(path_res) = promised.path { - named.insert( - "path".to_string(), - path_res.map(|p| ipld::Newtype::from(p).0).into(), - ); - } - - // FIXME gross code - if let Some(args_res) = promised.args { - match args_res.try_resolve() { - Ok(named_promises) => { - let value = named_promises.iter().try_fold( - arguments::Named::::new(), - |mut acc, (k, v)| { - // FIXME extract - acc.insert(k.into(), v.clone().try_into().ok()?); - Some(acc) - }, - ); - - match value { - Some(v) => { - named.insert("args".to_string(), v.into()); - } - None => {} - } - } - - Err(_unresolved) => {} - } - } - - named - } -} +// impl From for arguments::Named { +// fn from(promised: Promised) -> Self { +// let mut named = arguments::Named::new(); +// +// if let Some(path) = promised.path { +// named.insert("path".to_string(), Ipld::String(path.to_string())); +// } +// +// if let Some(args) = promised.args { +// named.insert("args".to_string(), Ipld::from(args)); +// } +// +// named +// } +// } impl From for Promised { fn from(r: Ready) -> Promised { @@ -265,56 +255,23 @@ impl From for Promised { } // FIXME may want to name this something other than a TryFrom -impl From for Builder { - fn from(promised: Promised) -> Self { - Builder { - path: promised.path.and_then(|x| x.try_resolve().ok()), - args: promised - .args - .and_then(|x| x.try_resolve().ok()?.try_into().ok()), - } - } -} +// impl From for Builder { +// fn from(promised: Promised) -> Self { +// Builder { +// path: promised.path.and_then(|x| x.try_resolve().ok()), +// args: promised +// .args +// .and_then(|x| x.try_resolve().ok()?.try_into().ok()), +// } +// } +// } impl promise::Resolvable for Ready { type Promised = Promised; - - fn try_resolve(p: Promised) -> Result { - // FIXME extract & cleanup - let path = match p.path { - Some(ref res_path) => match res_path.clone().try_resolve() { - Ok(path) => Some(Ok(path)), - Err(unresolved) => Some(Err(Promised { - path: Some(unresolved), - args: p.args.clone(), - })), - }, - None => None, - } - .transpose()?; - - // FIXME extract & cleanup - let args = match p.args { - Some(ref res_args) => match res_args.clone().try_resolve() { - Ok(args) => { - let ipld = args.try_into().map_err(|_| p.clone())?; - Some(Ok(ipld)) - } - Err(unresolved) => Some(Err(Promised { - path: path.clone().map(|p| Resolves::new(p)), - args: Some(unresolved), - })), - }, - None => None, - } - .transpose()?; - - Ok(Ready { path, args }) - } } -impl From for arguments::Named { - fn from(builder: Builder) -> Self { +impl From for arguments::Named { + fn from(builder: Ready) -> Self { let mut named = arguments::Named::new(); if let Some(path) = builder.path { @@ -334,3 +291,19 @@ impl From for arguments::Named { named } } + +impl From for arguments::Named { + fn from(promised: Promised) -> Self { + let mut named = arguments::Named::new(); + + if let Some(path) = promised.path { + named.insert("path".to_string(), path.into()); + } + + if let Some(args) = promised.args { + named.insert("args".to_string(), args.into()); + } + + named + } +} diff --git a/src/ability/crud/destroy.rs b/src/ability/crud/destroy.rs index 95f29319..83a6fb4a 100644 --- a/src/ability/crud/destroy.rs +++ b/src/ability/crud/destroy.rs @@ -9,7 +9,7 @@ use crate::{ }; use libipld_core::ipld::Ipld; use serde::Serialize; -use std::path::PathBuf; +use std::{collections::BTreeMap, path::PathBuf}; // FIXME deserialize instance @@ -53,40 +53,13 @@ pub struct Generic { /// /// style destroyready stroke:orange; /// ``` -pub type Ready = Generic; - -#[cfg_attr(doc, aquamarine::aquamarine)] -/// The delegatable ability for destroying resources. -/// -/// # Lifecycle -/// -/// The lifecycle of a `crud/destroy` ability is as follows: -/// -/// ```mermaid -/// flowchart LR -/// subgraph Delegations -/// top("*") -/// -/// subgraph CRUD Abilities -/// any("crud/*") -/// -/// mutate("crud/mutate") -/// -/// subgraph Invokable -/// destroy("crud/destroy") -/// end -/// end -/// end -/// -/// destroypromise("crud::destroy::Promised") -/// destroyready("crud::destroy::Ready") -/// -/// top --> any --> mutate --> destroy -/// destroy -.->|invoke| destroypromise -.->|resolve| destroyready -.-> exe{{execute}} -/// -/// style destroy stroke:orange; -/// ``` -pub type Builder = Generic; +#[derive(Debug, Clone, PartialEq, Serialize)] +#[serde(deny_unknown_fields)] +pub struct Ready { + /// An optional path to a sub-resource that is to be destroyed. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub path: Option, +} #[cfg_attr(doc, aquamarine::aquamarine)] /// An invoked `crud/destroy` ability (but possibly awaiting another @@ -120,48 +93,54 @@ pub type Builder = Generic; /// /// style destroypromise stroke:orange; /// ``` -pub type Promised = Generic>; +#[derive(Debug, Clone, PartialEq, Serialize)] +#[serde(deny_unknown_fields)] +pub struct Promised { + /// An optional path to a sub-resource that is to be destroyed. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub path: Option>, +} -impl

Command for Generic

{ - const COMMAND: &'static str = "crud/destroy"; +const COMMAND: &'static str = "crud/destroy"; + +impl Command for Ready { + const COMMAND: &'static str = COMMAND; } -impl Delegable for Ready { - type Builder = Builder; +impl Command for Promised { + const COMMAND: &'static str = COMMAND; } -impl> From> for Ipld { - fn from(destroy: Generic

) -> Self { - destroy.into() - } +impl Delegable for Ready { + type Builder = Ready; } -impl> TryFrom for Generic

{ +impl TryFrom> for Ready { type Error = (); // FIXME - fn try_from(ipld: Ipld) -> Result { - if let Ipld::Map(mut map) = ipld { - if map.len() > 1 { - return Err(()); // FIXME - } + fn try_from(args: arguments::Named) -> Result { + let mut path = None; - Ok(Generic { - path: map - .remove("path") - .map(|ipld| P::try_from(ipld).map_err(|_| ())) - .transpose()?, - }) - } else { - Err(()) // FIXME + for (k, ipld) in args { + match k.as_str() { + "path" => { + if let Ipld::String(s) = ipld { + path = Some(PathBuf::from(s)); + } + } + _ => return Err(()), + } } + + Ok(Ready { path }) } } -impl Checkable for Builder { - type Hierarchy = Parentful; +impl Checkable for Ready { + type Hierarchy = Parentful; } -impl CheckSame for Builder { +impl CheckSame for Ready { type Error = (); // FIXME better error fn check_same(&self, proof: &Self) -> Result<(), Self::Error> { @@ -173,7 +152,7 @@ impl CheckSame for Builder { } } -impl CheckParents for Builder { +impl CheckParents for Ready { type Parents = MutableParents; type ParentError = (); // FIXME @@ -228,21 +207,17 @@ impl From for Promised { impl promise::Resolvable for Ready { type Promised = Promised; +} - fn try_resolve(p: Promised) -> Result { - // FIXME extract & cleanup - let path = match p.path { - Some(ref res_path) => match res_path.clone().try_resolve() { - Ok(path) => Some(Ok(path)), - Err(unresolved) => Some(Err(Promised { - path: Some(unresolved), - })), - }, - None => None, +impl From for arguments::Named { + fn from(promised: Promised) -> Self { + let mut named = arguments::Named::new(); + + if let Some(path) = promised.path { + named.insert("path".to_string(), path.into()); } - .transpose()?; - Ok(Ready { path }) + named } } @@ -257,8 +232,8 @@ impl From for Ready { } } -impl From for arguments::Named { - fn from(builder: Builder) -> Self { +impl From for arguments::Named { + fn from(builder: Ready) -> Self { let mut named = arguments::Named::new(); if let Some(path) = builder.path { diff --git a/src/ability/crud/mutate.rs b/src/ability/crud/mutate.rs index 241fa1f2..4900e00c 100644 --- a/src/ability/crud/mutate.rs +++ b/src/ability/crud/mutate.rs @@ -79,6 +79,20 @@ impl TryFrom for Mutate { } } +impl TryFrom> for Mutate { + type Error = (); + + fn try_from(args: arguments::Named) -> Result { + if let Some(Ipld::String(s)) = args.get("path") { + return Ok(Mutate { + path: Some(PathBuf::from(s)), + }); + }; + + Ok(Mutate { path: None }) + } +} + impl Checkable for Mutate { type Hierarchy = Parentful; } @@ -89,6 +103,7 @@ impl CheckSame for Mutate { fn check_same(&self, proof: &Self) -> Result<(), Self::Error> { if let Some(path) = &self.path { let proof_path = proof.path.as_ref().ok_or(OptionalFieldError::Missing)?; + if path != proof_path { return Err(OptionalFieldError::Unequal); } @@ -105,6 +120,7 @@ impl CheckParents for Mutate { fn check_parent(&self, crud_any: &Self::Parents) -> Result<(), Self::ParentError> { if let Some(path) = &self.path { let proof_path = crud_any.path.as_ref().ok_or(OptionalFieldError::Missing)?; + if path != proof_path { return Err(OptionalFieldError::Unequal); } diff --git a/src/ability/crud/parents.rs b/src/ability/crud/parents.rs index 8d3be766..ac4f130d 100644 --- a/src/ability/crud/parents.rs +++ b/src/ability/crud/parents.rs @@ -78,28 +78,41 @@ impl ToCommand for MutableParents { #[derive(Debug, Clone, Error)] pub enum ParseError { - #[error("Invalid `crud/*` arguments: {0}")] - InvalidAnyArgs(#[source] ::Error), + #[error("Invalid `crud/*` arguments: {0:?}")] + InvalidAnyArgs(>>::Error), - #[error("Invalid `crud/mutate` arguments: {0}")] - InvalidMutateArgs(#[source] ::Error), + #[error("Invalid `crud/mutate` arguments: {0:?}")] + InvalidMutateArgs(>>::Error), } impl ParseAbility for MutableParents { - type Error = ParseAbilityError; + type ArgsErr = ParseError; - fn try_parse(cmd: &str, args: &arguments::Named) -> Result { - super::Any::try_parse(cmd, args) - .map(MutableParents::Any) - .map_err(ParseError::InvalidAnyArgs) - .map_err(ParseAbilityError::InvalidArgs)?; + fn try_parse( + cmd: &str, + args: arguments::Named, + ) -> Result> { + match super::Any::try_parse(cmd, args.clone()) { + Ok(any) => return Ok(MutableParents::Any(any)), + Err(ParseAbilityError::InvalidArgs(e)) => { + return Err(ParseAbilityError::InvalidArgs(ParseError::InvalidAnyArgs( + e, + ))) + } + Err(ParseAbilityError::UnknownCommand(_)) => {} + } - super::Mutate::try_parse(cmd, args) - .map(MutableParents::Mutate) - .map_err(ParseError::InvalidMutateArgs) - .map_err(ParseAbilityError::InvalidArgs)?; + match super::Any::try_parse(cmd, args.clone()) { + Ok(any) => return Ok(MutableParents::Any(any)), + Err(ParseAbilityError::InvalidArgs(e)) => { + return Err(ParseAbilityError::InvalidArgs( + ParseError::InvalidMutateArgs(e), + )) + } + Err(ParseAbilityError::UnknownCommand(_)) => {} + } - Err(ParseAbilityError::UnknownCommand) + Err(ParseAbilityError::UnknownCommand(cmd.to_string())) } } diff --git a/src/ability/crud/read.rs b/src/ability/crud/read.rs index c1be22fd..76ee8019 100644 --- a/src/ability/crud/read.rs +++ b/src/ability/crud/read.rs @@ -2,31 +2,21 @@ use super::any as crud; use crate::{ - ability::{arguments, command::Command}, + ability::{ + arguments, + command::{Command, ParseAbility, ParseAbilityError}, + }, delegation::Delegable, invocation::{promise, promise::Resolves}, ipld, proof::{checkable::Checkable, parentful::Parentful, parents::CheckParents, same::CheckSame}, }; -use libipld_core::ipld::Ipld; -use serde::Serialize; +use libipld_core::{error::SerdeError, ipld::Ipld, serde as ipld_serde}; +use serde::{Deserialize, Serialize}; use std::path::PathBuf; // FIXME deserialize instance -/// A helper for creating lifecycle instances of `crud/create` with the correct shape. -#[derive(Debug, Clone, PartialEq, Serialize)] -#[serde(deny_unknown_fields)] -pub struct Generic { - /// An optional path to a sub-resource that is to be read. - #[serde(default, skip_serializing_if = "Option::is_none")] - pub path: Option, - - /// Optional arguments to modify the read request. - #[serde(default, skip_serializing_if = "Option::is_none")] - pub args: Option, -} - #[cfg_attr(doc, aquamarine::aquamarine)] /// This ability is used to fetch messages from other actors. /// @@ -54,38 +44,17 @@ pub struct Generic { /// /// style readready stroke:orange; /// ``` -pub type Ready = Generic>; +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +#[serde(deny_unknown_fields)] +pub struct Ready { + /// An optional path to a sub-resource that is to be read. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub path: Option, -#[cfg_attr(doc, aquamarine::aquamarine)] -/// The delegatable ability for reading resources. -/// -/// # Lifecycle -/// -/// The lifecycle of a `crud/read` ability is as follows: -/// -/// ```mermaid -/// flowchart LR -/// subgraph Delegations -/// top("*") -/// -/// subgraph CRUD Abilities -/// any("crud/*") -/// -/// subgraph Invokable -/// read("crud/read") -/// end -/// end -/// end -/// -/// readpromise("crud::read::Promised") -/// readready("crud::read::Ready") -/// -/// top --> any --> read -/// read -.->|invoke| readpromise -.->|resolve| readready -.-> exe{{execute}} -/// -/// style read stroke:orange; -/// ``` -pub type Builder = Generic>; + /// Optional arguments to modify the read request. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub args: Option>, +} #[cfg_attr(doc, aquamarine::aquamarine)] /// An invoked `crud/read` ability (but possibly awaiting another @@ -117,64 +86,96 @@ pub type Builder = Generic>; /// /// style readpromise stroke:orange; /// ``` -pub type Promised = Generic, arguments::Promised>; +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +#[serde(deny_unknown_fields)] +pub struct Promised { + /// An optional path to a sub-resource that is to be read. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub path: Option>, -impl Command for Generic { + /// Optional arguments to modify the read request. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub args: Option>>, +} + +const COMMAND: &'static str = "crud/read"; + +impl Command for Ready { const COMMAND: &'static str = "crud/read"; } -impl Delegable for Ready { - type Builder = Builder; +impl Command for Promised { + const COMMAND: &'static str = "crud/read"; } -impl From for Builder { - fn from(promised: Promised) -> Self { - Builder { - path: promised.path.and_then(|p| p.try_resolve().ok()), - args: promised.args.and_then(|p| p.try_resolve_option()), // FIXME this needs to read better - } - } +impl Delegable for Ready { + type Builder = Ready; } // FIXME resolves vs resolvable is confusing -impl, A: Into> From> for Ipld { - fn from(read: Generic) -> Self { - read.into() +impl TryFrom for Ready { + type Error = SerdeError; // FIXME + + fn try_from(ipld: Ipld) -> Result { + ipld_serde::from_ipld(ipld) } } -impl, A: TryFrom> TryFrom for Generic { - type Error = (); // FIXME +impl From for arguments::Named { + fn from(ready: Ready) -> Self { + let mut named = arguments::Named::new(); - fn try_from(ipld: Ipld) -> Result { - if let Ipld::Map(mut map) = ipld { - if map.len() > 2 { - return Err(()); // FIXME - } + if let Some(path) = ready.path { + named.insert( + "path".to_string(), + path.into_os_string() + .into_string() + .expect("PathBuf should make a valid path") + .into(), + ); + } - Ok(Generic { - path: map - .remove("path") - .map(|ipld| P::try_from(ipld).map_err(|_| ())) - .transpose()?, - - args: map - .remove("args") - .map(|ipld| A::try_from(ipld).map_err(|_| ())) - .transpose()?, - }) - } else { - Err(()) // FIXME + if let Some(args) = ready.args { + named.insert("args".to_string(), args.into()); + } + + named + } +} + +impl TryFrom> for Ready { + type Error = (); + + fn try_from(arguments: arguments::Named) -> Result { + let mut path = None; + let mut args = None; + + for (k, v) in arguments.into_iter() { + match k.as_str() { + "path" => { + if let Ipld::String(string) = v { + path = Some(PathBuf::from(string)); + } else { + return Err(()); + } + } + "args" => { + args = Some(arguments::Named::try_from(v).map_err(|_| ())?); + } + _ => return Err(()), + } } + + Ok(Ready { path, args }) } } -impl Checkable for Builder { - type Hierarchy = Parentful; +impl Checkable for Ready { + type Hierarchy = Parentful; } -impl CheckSame for Builder { +impl CheckSame for Ready { type Error = (); // FIXME better error fn check_same(&self, proof: &Self) -> Result<(), Self::Error> { @@ -186,7 +187,7 @@ impl CheckSame for Builder { } } -impl CheckParents for Builder { +impl CheckParents for Ready { type Parents = crud::Any; type ParentError = (); // FIXME @@ -204,38 +205,21 @@ impl CheckParents for Builder { } } -impl From for arguments::Named { - fn from(promised: Promised) -> Self { - let mut named = arguments::Named::new(); - - if let Some(path_res) = promised.path { - named.insert( - "path".to_string(), - path_res.map(|p| ipld::Newtype::from(p).0).into(), - ); - } - - // FIXME - // if let Some(args_res) = promised.args { - // let v = args_res.map(|a| { - // // FIXME extract - // a.iter().try_fold(BTreeMap::new(), |mut acc, (k, v)| { - // acc.insert(*k, (*v).try_into().ok()?); - // Some(acc) - // }) - // }); - - // // match v { - // // - // // } - // // named.insert( - // // "args".to_string(), - // // ); - // } - - named - } -} +// impl From for arguments::Named { +// fn from(promised: Promised) -> Self { +// let mut named = arguments::Named::new(); +// +// if let Some(path_res) = promised.path { +// named.insert("path".into(), path_res.into()); +// } +// +// if let Some(args_res) = promised.args { +// named.insert("args".into(), args_res.into()); +// } +// +// named +// } +// } impl From for Promised { fn from(r: Ready) -> Promised { @@ -251,59 +235,45 @@ impl From for Promised { impl promise::Resolvable for Ready { type Promised = Promised; - - fn try_resolve(p: Promised) -> Result { - // FIXME extract & cleanup - let path = match p.path { - Some(ref res_path) => match res_path.clone().try_resolve() { - Ok(path) => Some(Ok(path)), - Err(unresolved) => Some(Err(Promised { - path: Some(unresolved), - args: p.args.clone(), - })), - }, - None => None, - } - .transpose()?; - - // FIXME extract & cleanup - let args = match p.args { - Some(ref res_args) => match res_args.clone().try_resolve() { - Ok(args) => { - let ipld = args.try_into().map_err(|_| p.clone())?; - Some(Ok(ipld)) - } - Err(unresolved) => Some(Err(Promised { - path: path.clone().map(|p| Resolves::new(p)), - args: Some(unresolved), - })), - }, - None => None, - } - .transpose()?; - - Ok(Ready { path, args }) - } } -impl From for arguments::Named { - fn from(builder: Builder) -> Self { +impl From for arguments::Named { + fn from(promised: Promised) -> Self { let mut named = arguments::Named::new(); - if let Some(path) = builder.path { - named.insert( - "path".to_string(), - path.into_os_string() - .into_string() - .expect("PathBuf should make a valid path") - .into(), - ); + if let Some(path_res) = promised.path { + named.insert("path".to_string(), path_res.into()); } - if let Some(args) = builder.args { - named.insert("args".to_string(), args.into()); + if let Some(args_res) = promised.args { + named.insert("args".to_string(), args_res.into()); } named } } + +// impl TryFrom> for Promised { +// type Error = (); +// +// fn try_from(arguments: arguments::Named) -> Result { +// let mut path = None; +// let mut args = None; +// +// for (k, v) in arguments.into_iter() { +// match k.as_str() { +// "path" => { +// let path = promise::Resolves::try_from(v)?; +// path.map(|path| Promised { path, args }); +// } +// "args" => { +// let args = promise::Resolves::try_from(v)?; +// args.map(|args| Promised { path, args }); +// } +// _ => return Err(()), +// } +// } +// +// Ok(Promised { path, args }) +// } +// } diff --git a/src/ability/crud/update.rs b/src/ability/crud/update.rs index 0ed14da3..dd2225da 100644 --- a/src/ability/crud/update.rs +++ b/src/ability/crud/update.rs @@ -143,14 +143,63 @@ pub struct Promised { args: Option, } +const COMMAND: &'static str = "crud/update"; + impl Command for Ready { - const COMMAND: &'static str = "crud/update"; + const COMMAND: &'static str = COMMAND; +} + +impl Command for Builder { + const COMMAND: &'static str = COMMAND; +} + +impl Command for Promised { + const COMMAND: &'static str = COMMAND; } impl Delegable for Ready { type Builder = Builder; } +impl TryFrom> for Ready { + type Error = (); + + fn try_from(named: arguments::Named) -> Result { + Self::try_from_named(named).map_err(|_| ()) + } +} + +impl TryFrom> for Builder { + type Error = (); + + fn try_from(named: arguments::Named) -> Result { + let mut path = None; + let mut args = None; + + for (key, ipld) in named { + match key.as_str() { + "path" => { + if let Ipld::String(s) = ipld { + path = Some(PathBuf::from(s)); + } else { + return Err(()); + } + } + "args" => { + if let Ipld::Map(map) = ipld { + args = Some(arguments::Named(map)); + } else { + return Err(()); + } + } + _ => return Err(()), + } + } + + Ok(Builder { path, args }) + } +} + impl From for Builder { fn from(r: Ready) -> Self { Builder { @@ -292,39 +341,6 @@ impl From for Promised { impl promise::Resolvable for Ready { type Promised = Promised; - - fn try_resolve(p: Promised) -> Result { - // FIXME extract & cleanup - let path = match p.path { - Some(ref res_path) => match res_path.clone().try_resolve() { - Ok(path) => Some(Ok(path)), - Err(unresolved) => Some(Err(Promised { - path: Some(unresolved), - args: p.args.clone(), - })), - }, - None => None, - } - .transpose()?; - - // FIXME extract & cleanup - let args = match p.args { - Some(ref res_args) => match res_args.clone().try_resolve() { - Ok(args) => { - let ipld = args.try_into().map_err(|_| p.clone())?; - Some(Ok(ipld)) - } - Err(unresolved) => Some(Err(Promised { - path: path.clone().map(|p| Resolves::new(p)), - args: Some(unresolved), - })), - }, - None => None, - } - .transpose()?; - - Ok(Ready { path, args }) - } } impl From for Builder { @@ -357,3 +373,19 @@ impl From for arguments::Named { named } } + +impl From for arguments::Named { + fn from(promised: Promised) -> Self { + let mut named = arguments::Named::new(); + + if let Some(path) = promised.path { + named.insert("path".to_string(), path.into()); + } + + if let Some(args) = promised.args { + named.insert("args".to_string(), args.into()); + } + + named + } +} diff --git a/src/ability/dynamic.rs b/src/ability/dynamic.rs index d28b4610..d95be19f 100644 --- a/src/ability/dynamic.rs +++ b/src/ability/dynamic.rs @@ -2,7 +2,7 @@ use super::{ arguments, - command::{ParseAbility, ToCommand}, + command::{ParseAbility, ParseAbilityError, ToCommand}, }; use crate::proof::same::CheckSame; use libipld_core::{error::SerdeError, ipld::Ipld, serde as ipld_serde}; @@ -45,12 +45,15 @@ pub struct Dynamic { } impl ParseAbility for Dynamic { - type Error = Infallible; + type ArgsErr = (); - fn try_parse(cmd: &str, args: &arguments::Named) -> Result { + fn try_parse( + cmd: &str, + args: arguments::Named, + ) -> Result> { Ok(Dynamic { cmd: cmd.to_string(), - args: args.clone(), + args, }) } } diff --git a/src/ability/msg.rs b/src/ability/msg.rs index d3099de8..3968c0cf 100644 --- a/src/ability/msg.rs +++ b/src/ability/msg.rs @@ -8,9 +8,13 @@ pub mod send; pub use any::Any; use crate::{ - ability::arguments, + ability::{ + arguments, + command::{ParseAbility, ParseAbilityError, ToCommand}, + }, delegation::Delegable, invocation::promise::Resolvable, + ipld, proof::{checkable::Checkable, parentful::Parentful, parents::CheckParents, same::CheckSame}, }; use libipld_core::ipld::Ipld; @@ -38,6 +42,79 @@ impl Delegable for Ready { type Builder = Builder; } +impl ToCommand for Ready { + fn to_command(&self) -> String { + match self { + Ready::Send(send) => send.to_command(), + Ready::Receive(receive) => receive.to_command(), + } + } +} + +impl ToCommand for Builder { + fn to_command(&self) -> String { + match self { + Builder::Send(send) => send.to_command(), + Builder::Receive(receive) => receive.to_command(), + } + } +} + +impl ToCommand for Promised { + fn to_command(&self) -> String { + match self { + Promised::Send(send) => send.to_command(), + Promised::Receive(receive) => receive.to_command(), + } + } +} + +// impl ParseAbility for Ready { +// type ArgsErr = (); +// +// fn try_parse( +// cmd: &str, +// args: arguments::Named, +// ) -> Result> { +// match send::Ready::try_parse(cmd, args.clone()) { +// Ok(send) => return Ok(Ready::Send(send)), +// Err(ParseAbilityError::InvalidArgs(args)) => { +// return Err(ParseAbilityError::InvalidArgs(())) +// } +// Err(ParseAbilityError::UnknownCommand(_)) => {} +// } +// +// match receive::Receive::try_parse(cmd, args) { +// Ok(receive) => return Ok(Ready::Receive(receive)), +// Err(ParseAbilityError::InvalidArgs(args)) => { +// return Err(ParseAbilityError::InvalidArgs(())) +// } +// Err(ParseAbilityError::UnknownCommand(cmd)) => {} +// } +// +// Err(ParseAbilityError::UnknownCommand(cmd.to_string())) +// } +// } + +impl ParseAbility for Builder { + type ArgsErr = (); + + fn try_parse( + cmd: &str, + args: arguments::Named, + ) -> Result> { + if let Ok(send) = send::Builder::try_parse(cmd, args.clone()) { + return Ok(Builder::Send(send)); + } + + if let Ok(receive) = receive::Receive::try_parse(cmd, args) { + return Ok(Builder::Receive(receive)); + } + + Err(ParseAbilityError::UnknownCommand(cmd.to_string())) + } +} + impl TryFrom for Ready { type Error = (); @@ -69,26 +146,6 @@ impl From for arguments::Named { impl Resolvable for Ready { type Promised = Promised; - - fn try_resolve(promised: Promised) -> Result { - match promised { - Promised::Send(send) => Resolvable::try_resolve(send) - .map(Ready::Send) - .map_err(Promised::Send), - Promised::Receive(receive) => Resolvable::try_resolve(receive) - .map(Ready::Receive) - .map_err(Promised::Receive), - } - } -} - -impl From for Builder { - fn from(promised: Promised) -> Self { - match promised { - Promised::Send(send) => Builder::Send(send.into()), - Promised::Receive(receive) => Builder::Receive(receive.into()), - } - } } impl CheckSame for Builder { @@ -127,3 +184,12 @@ impl From for arguments::Named { } } } + +impl From for arguments::Named { + fn from(promised: Promised) -> Self { + match promised { + Promised::Send(send) => send.into(), + Promised::Receive(receive) => receive.into(), + } + } +} diff --git a/src/ability/msg/receive.rs b/src/ability/msg/receive.rs index 840a5329..b0304b0b 100644 --- a/src/ability/msg/receive.rs +++ b/src/ability/msg/receive.rs @@ -4,6 +4,7 @@ use crate::{ ability::{arguments, command::Command}, delegation::Delegable, invocation::promise, + ipld, proof::{checkable::Checkable, parentful::Parentful, parents::CheckParents, same::CheckSame}, url, }; @@ -46,23 +47,50 @@ pub struct Receive { pub from: Option, } -pub type Builder = Receive; - // FIXME needs promisory version +const COMMAND: &'static str = "msg/send"; + impl Command for Receive { - const COMMAND: &'static str = "msg/send"; + const COMMAND: &'static str = COMMAND; +} + +impl Command for Promised { + const COMMAND: &'static str = COMMAND; } impl Delegable for Receive { type Builder = Receive; } -impl From for Builder { - fn from(promised: Promised) -> Self { - Builder { - from: promised.from.and_then(|x| x.try_resolve_option()), +impl TryFrom> for Receive { + type Error = (); + + fn try_from(arguments: arguments::Named) -> Result { + let mut from = None; + + for (key, ipld) in arguments { + match key.as_str() { + "from" => { + from = Some(url::Newtype::try_from(ipld).map_err(|_| ())?); + } + _ => return Err(()), + } + } + + Ok(Receive { from }) + } +} + +impl From for arguments::Named { + fn from(receive: Receive) -> Self { + let mut args = arguments::Named::new(); + + if let Some(from) = receive.from { + args.insert("from".into(), from.into()); } + + args } } @@ -134,28 +162,16 @@ impl From for arguments::Named { impl promise::Resolvable for Receive { type Promised = Promised; - - fn try_resolve(p: Promised) -> Result { - match &p.from { - None => Ok(Receive { from: None }), - Some(promise::Resolves::Ok(promise_ok)) => match promise_ok.clone().try_resolve() { - Ok(from) => Ok(Receive { from }), - Err(_from) => Err(Promised { from: p.from }), - }, - Some(promise::Resolves::Err(promise_err)) => match promise_err.clone().try_resolve() { - Ok(from) => Ok(Receive { from }), - Err(_from) => Err(Promised { from: p.from }), - }, - } - } } -impl From for arguments::Named { - fn from(builder: Builder) -> Self { +impl From for arguments::Named { + fn from(promised: Promised) -> Self { let mut args = arguments::Named::new(); - if let Some(from) = builder.from { - args.insert("from".into(), from.into()); + if let Some(from) = promised.from { + let _ = ipld::Promised::from(from).with_resolved(|ipld| { + args.insert("from".into(), ipld.into()); + }); } args diff --git a/src/ability/msg/send.rs b/src/ability/msg/send.rs index 96d3299b..fd1ec63b 100644 --- a/src/ability/msg/send.rs +++ b/src/ability/msg/send.rs @@ -4,6 +4,7 @@ use crate::{ ability::{arguments, command::Command}, delegation::Delegable, invocation::promise, + ipld, proof::{checkable::Checkable, parentful::Parentful, parents::CheckParents, same::CheckSame}, url as url_newtype, }; @@ -12,28 +13,6 @@ use serde::{de::DeserializeOwned, Deserialize, Serialize}; use std::collections::BTreeMap; use url::Url; -/// Helper for creating instances of `msg/send` with the correct shape. -/// -/// This is not generally used directly, unless you want to abstract -/// over all of the `msg/send` variants. -#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] -#[serde(deny_unknown_fields)] -pub struct Generic { - /// The recipient of the message - pub to: To, - - /// FIXME Builder needs to omit option fields from Serde - - /// The sender address of the message - /// - /// This *may* be a URL (such as an email address). - /// If provided, the `subject` must have the right to send from this address. - pub from: From, - - /// The main body of the message - pub message: Message, -} - #[cfg_attr(doc, aquamarine::aquamarine)] /// The executable/dispatchable variant of the `msg/send` ability. /// @@ -61,7 +40,21 @@ pub struct Generic { /// /// style sendrun stroke:orange; /// ``` -pub type Ready = Generic; +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +#[serde(deny_unknown_fields)] +pub struct Ready { + /// The recipient of the message + pub to: Url, + + /// The sender address of the message + /// + /// This *may* be a URL (such as an email address). + /// If provided, the `subject` must have the right to send from this address. + pub from: Url, + + /// The main body of the message + pub message: String, +} #[cfg_attr(doc, aquamarine::aquamarine)] /// The delegatable variant of the `msg/send` ability. @@ -89,7 +82,21 @@ pub type Ready = Generic; /// /// style send stroke:orange; /// ``` -pub type Builder = Generic, Option, Option>; +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +#[serde(deny_unknown_fields)] +pub struct Builder { + /// The recipient of the message + pub to: Option, + + /// The sender address of the message + /// + /// This *may* be a URL (such as an email address). + /// If provided, the `subject` must have the right to send from this address. + pub from: Option, + + /// The main body of the message + pub message: Option, +} #[cfg_attr(doc, aquamarine::aquamarine)] /// The invoked variant of the `msg/send` ability @@ -120,8 +127,21 @@ pub type Builder = Generic, Option, Option>; /// /// style sendpromise stroke:orange; /// ``` -pub type Promised = - Generic, promise::Resolves, promise::Resolves>; +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +#[serde(deny_unknown_fields)] +pub struct Promised { + /// The recipient of the message + pub to: promise::Resolves, + + /// The sender address of the message + /// + /// This *may* be a URL (such as an email address). + /// If provided, the `subject` must have the right to send from this address. + pub from: promise::Resolves, + + /// The main body of the message + pub message: promise::Resolves, +} impl Delegable for Ready { type Builder = Builder; @@ -129,13 +149,6 @@ impl Delegable for Ready { impl promise::Resolvable for Ready { type Promised = Promised; - - fn try_resolve(p: Promised) -> Result { - match promise::Resolves::try_resolve_3(p.to, p.from, p.message) { - Ok((to, from, message)) => Ok(Ready { to, from, message }), - Err((to, from, message)) => Err(Promised { to, from, message }), - } - } } impl From for arguments::Named { @@ -171,8 +184,18 @@ impl From for Builder { } } -impl Command for Generic { - const COMMAND: &'static str = "msg/send"; +const COMMAND: &'static str = "msg/send"; + +impl Command for Ready { + const COMMAND: &'static str = COMMAND; +} + +impl Command for Builder { + const COMMAND: &'static str = COMMAND; +} + +impl Command for Promised { + const COMMAND: &'static str = COMMAND; } impl Checkable for Builder { @@ -198,9 +221,49 @@ impl CheckParents for Builder { } } +impl TryFrom> for Builder { + type Error = (); + + fn try_from(args: arguments::Named) -> Result { + let mut to = None; + let mut from = None; + let mut message = None; + + for (key, ipld) in args.0 { + match key.as_str() { + "to" => { + // FIXME extract this common pattern + if let Ipld::String(s) = ipld { + to = Some(Url::parse(s.as_str()).map_err(|_| ())?); + } else { + return Err(()); + } + } + "from" => { + if let Ipld::String(s) = ipld { + from = Some(Url::parse(s.as_str()).map_err(|_| ())?); + } else { + return Err(()); + } + } + "message" => { + if let Ipld::String(s) = ipld { + message = Some(s); + } else { + return Err(()); + } + } + _ => return Err(()), + } + } + + Ok(Builder { to, from, message }) + } +} + impl From for Builder { fn from(resolved: Ready) -> Self { - Generic { + Builder { to: resolved.to.into(), from: resolved.from.into(), message: resolved.message.into(), @@ -247,21 +310,12 @@ impl TryFrom for Ready { } } -impl From> for Ipld -where - Ipld: From + From + From, -{ - fn from(send: Generic) -> Self { - send.into() - } -} - -impl TryFrom - for Generic -{ - type Error = SerdeError; - - fn try_from(ipld: Ipld) -> Result { - ipld_serde::from_ipld(ipld) +impl From for arguments::Named { + fn from(p: Promised) -> Self { + arguments::Named::from_iter([ + ("to".into(), p.to.into()), + ("from".into(), p.from.into()), + ("message".into(), p.message.into()), + ]) } } diff --git a/src/ability/preset.rs b/src/ability/preset.rs index d9ccbc4e..c9efe787 100644 --- a/src/ability/preset.rs +++ b/src/ability/preset.rs @@ -1,8 +1,12 @@ use super::{crud, msg, wasm}; use crate::{ - ability::{arguments, command::ParseAbility}, + ability::{ + arguments, + command::{ParseAbility, ParseAbilityError, ToCommand}, + }, delegation::Delegable, invocation::promise::Resolvable, + ipld, proof::{checkable::Checkable, parentful::Parentful, parents::CheckParents, same::CheckSame}, }; use libipld_core::ipld::Ipld; @@ -42,24 +46,28 @@ impl CheckSame for Parents { } } -impl ParseAbility for Parents { - type Error = String; // FIXME - - fn try_parse(cmd: &str, args: &arguments::Named) -> Result { - if let Ok(crud) = crud::MutableParents::try_parse(cmd, args) { - return Ok(Parents::Crud(crud)); - } +impl Delegable for Ready { + type Builder = Builder; +} - if let Ok(msg) = msg::Any::try_parse(cmd, args) { - return Ok(Parents::Msg(msg)); +impl ToCommand for Ready { + fn to_command(&self) -> String { + match self { + Ready::Crud(ready) => ready.to_command(), + Ready::Msg(ready) => ready.to_command(), + Ready::Wasm(ready) => ready.to_command(), } - - Err("Nope".into()) } } -impl Delegable for Ready { - type Builder = Builder; +impl ToCommand for Builder { + fn to_command(&self) -> String { + match self { + Builder::Crud(builder) => builder.to_command(), + Builder::Msg(builder) => builder.to_command(), + Builder::Wasm(builder) => builder.to_command(), + } + } } impl CheckSame for Builder { @@ -118,41 +126,46 @@ pub enum Promised { Wasm(wasm::run::Promised), } -impl From for arguments::Named { - fn from(promised: Promised) -> Self { - match promised { - Promised::Crud(promised) => promised.into(), - Promised::Msg(promised) => promised.into(), - Promised::Wasm(promised) => promised.into(), - } - } -} - impl Resolvable for Ready { type Promised = Promised; +} - fn try_resolve(promised: Self::Promised) -> Result { - match promised { - Promised::Crud(promised) => Resolvable::try_resolve(promised) - .map(Ready::Crud) - .map_err(Promised::Crud), - Promised::Msg(promised) => Resolvable::try_resolve(promised) - .map(Ready::Msg) - .map_err(Promised::Msg), - Promised::Wasm(promised) => Resolvable::try_resolve(promised) - .map(Ready::Wasm) - .map_err(Promised::Wasm), +impl ToCommand for Promised { + fn to_command(&self) -> String { + match self { + Promised::Crud(promised) => promised.to_command(), + Promised::Msg(promised) => promised.to_command(), + Promised::Wasm(promised) => promised.to_command(), } } } -impl From for Builder { - fn from(promised: Promised) -> Self { - match promised { - Promised::Crud(promised) => Builder::Crud(promised.into()), - Promised::Msg(promised) => Builder::Msg(promised.into()), - Promised::Wasm(promised) => Builder::Wasm(promised.into()), +impl ParseAbility for Builder { + type ArgsErr = (); + + fn try_parse( + cmd: &str, + args: arguments::Named, + ) -> Result> { + match msg::Builder::try_parse(cmd, args.clone()) { + Ok(builder) => return Ok(Builder::Msg(builder)), + Err(err) => return Err(err), + Err(ParseAbilityError::UnknownCommand(_)) => (), } + + match crud::Builder::try_parse(cmd, args.clone()) { + Ok(builder) => return Ok(Builder::Crud(builder)), + Err(err) => return Err(err), + Err(ParseAbilityError::UnknownCommand(_)) => (), + } + + match wasm::run::Builder::try_parse(cmd, args) { + Ok(builder) => return Ok(Builder::Wasm(builder)), + Err(err) => return Err(err), + Err(ParseAbilityError::UnknownCommand(_)) => (), + } + + Err(ParseAbilityError::UnknownCommand(cmd.to_string())) } } @@ -174,3 +187,13 @@ impl From for arguments::Named { } } } + +impl From for arguments::Named { + fn from(promised: Promised) -> Self { + match promised { + Promised::Crud(promised) => promised.into(), + Promised::Msg(promised) => promised.into(), + Promised::Wasm(promised) => promised.into(), + } + } +} diff --git a/src/ability/ucan/revoke.rs b/src/ability/ucan/revoke.rs index a0d61502..1b078cea 100644 --- a/src/ability/ucan/revoke.rs +++ b/src/ability/ucan/revoke.rs @@ -6,11 +6,15 @@ use crate::{ ability::{arguments, command::Command}, delegation::Delegable, invocation::promise, + ipld, proof::{error::OptionalFieldError, parentless::NoParents, same::CheckSame}, }; use libipld_core::{cid::Cid, ipld::Ipld}; use serde::{Deserialize, Serialize}; -use std::{collections::BTreeMap, fmt::Debug}; +use std::{ + collections::{BTreeMap, BTreeSet}, + fmt::Debug, +}; /// The fully resolved variant: ready to execute. #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] @@ -19,14 +23,48 @@ pub struct Ready { pub ucan: Cid, } +const COMMAND: &'static str = "ucan/revoke"; + impl Command for Ready { - const COMMAND: &'static str = "ucan/revoke"; + const COMMAND: &'static str = COMMAND; +} + +impl Command for Builder { + const COMMAND: &'static str = COMMAND; +} + +impl Command for Promised { + const COMMAND: &'static str = COMMAND; } impl Delegable for Ready { type Builder = Builder; } +impl TryFrom> for Ready { + type Error = (); + + fn try_from(arguments: arguments::Named) -> Result { + let ipld: Ipld = arguments.get("ucan").ok_or(())?.clone(); + let nt: ipld::cid::Newtype = ipld.try_into().map_err(|_| ())?; + + Ok(Ready { ucan: nt.cid }) + } +} + +impl TryFrom> for Builder { + type Error = (); + + fn try_from(arguments: arguments::Named) -> Result { + if let Some(ipld) = arguments.get("ucan") { + let nt: ipld::cid::Newtype = ipld.try_into().map_err(|_| ())?; + Ok(Builder { ucan: Some(nt.cid) }) + } else { + Ok(Builder { ucan: None }) + } + } +} + impl From for Builder { fn from(promised: Promised) -> Self { Builder { @@ -37,12 +75,11 @@ impl From for Builder { impl promise::Resolvable for Ready { type Promised = Promised; +} - fn try_resolve(promised: Self::Promised) -> Result { - match promised.ucan.try_resolve() { - Ok(ucan) => Ok(Ready { ucan }), - Err(ucan) => Err(Promised { ucan }), - } +impl From for arguments::Named { + fn from(promised: Promised) -> Self { + arguments::Named::from_iter([("ucan".into(), promised.ucan.into())]) } } diff --git a/src/ability/wasm/module.rs b/src/ability/wasm/module.rs index 7b319b69..fc4b2ff7 100644 --- a/src/ability/wasm/module.rs +++ b/src/ability/wasm/module.rs @@ -1,5 +1,6 @@ //! Wasm module representations +use crate::{ability::arguments, ipld}; use base64::{display::Base64Display, engine::general_purpose::STANDARD, Engine as _}; use libipld_core::{cid::Cid, error::SerdeError, ipld::Ipld, link::Link, serde as ipld_serde}; use serde::{Deserialize, Serialize}; @@ -73,3 +74,9 @@ impl<'de> Deserialize<'de> for Module { } } } + +impl From for ipld::Promised { + fn from(module: Module) -> Self { + module.into() + } +} diff --git a/src/ability/wasm/run.rs b/src/ability/wasm/run.rs index ecf4be6a..64acf9f4 100644 --- a/src/ability/wasm/run.rs +++ b/src/ability/wasm/run.rs @@ -2,70 +2,119 @@ use super::module::Module; use crate::{ - ability::{arguments, command::Command}, + ability::{ + arguments, + command::{Command, ParseAbility}, + }, delegation::Delegable, invocation::promise, + ipld, proof::{parentless::NoParents, same::CheckSame}, }; use libipld_core::ipld::Ipld; use serde::{Deserialize, Serialize}; use std::collections::BTreeMap; +const COMMAND: &'static str = "wasm/run"; + +impl Command for Ready { + const COMMAND: &'static str = COMMAND; +} + +impl Command for Builder { + const COMMAND: &'static str = COMMAND; +} + +impl Command for Promised { + const COMMAND: &'static str = COMMAND; +} + /// The ability to run a Wasm module on the subject's machine #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] -pub struct Generic { +pub struct Ready { /// The Wasm module to run - pub module: Mod, + pub module: Module, /// The function from the module to run - pub function: Fun, + pub function: String, /// Arguments to pass to the function - pub args: Args, + pub args: Vec, } -impl Command for Generic { - const COMMAND: &'static str = "wasm/run"; -} - -/// A variant with all of the required fields filled in -pub type Ready = Generic>; - impl Delegable for Ready { type Builder = Builder; } -impl promise::Resolvable for Ready { - type Promised = Promised; - - fn try_resolve(promised: Self::Promised) -> Result { - match promise::Resolves::try_resolve_3(promised.module, promised.function, promised.args) { - Ok((module, function, args)) => Ok(Ready { - module, - function, - args, - }), - Err((module, function, args)) => Err(Promised { - module, - function, - args, - }), +impl TryFrom> for Builder { + type Error = (); + + fn try_from(named: arguments::Named) -> Result { + let mut module = None; + let mut function = None; + let mut args = None; + + for (key, ipld) in named { + match key.as_str() { + "mod" => { + module = Some(ipld.try_into().map_err(|_| ())?); + } + "fun" => { + if let Ipld::String(s) = ipld { + function = Some(s); + } else { + return Err(()); + } + } + "args" => { + if let Ipld::List(list) = ipld { + args = Some(list); + } else { + return Err(()); + } + } + _ => return Err(()), + } } + + Ok(Builder { + module, + function, + args, + }) } } -impl From for Builder { - fn from(promised: Promised) -> Self { - Builder { - module: promised.module.try_resolve().ok(), - function: promised.function.try_resolve().ok(), - args: promised.args.try_resolve().ok(), - } - } +// impl TryFrom> for Builder { +// type Error = (); +// +// fn try_from(args: arguments::Named) -> Result { +// let ready = Ready::try_from(args)?; +// +// Ok(Builder { +// module: Some(ready.module), +// function: Some(ready.function), +// args: Some(ready.args), +// }) +// } +// } + +impl promise::Resolvable for Ready { + type Promised = Promised; } /// A variant meant for delegation, where fields may be omitted -pub type Builder = Generic, Option, Option>>; +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub struct Builder { + /// The Wasm module to run + pub module: Option, + + /// The function from the module to run + pub function: Option, + + /// Arguments to pass to the function + pub args: Option>, +} impl NoParents for Builder {} @@ -137,32 +186,36 @@ impl CheckSame for Builder { } /// A variant meant for linking together invocations with promises -pub type Promised = - Generic, promise::Resolves, promise::Resolves>>; - -impl From for Promised { - fn from(ready: Ready) -> Self { - Promised { - module: promise::Resolves::from(Ok(ready.module)), - function: promise::Resolves::from(Ok(ready.function)), - args: promise::Resolves::from(Ok(ready.args)), - } - } -} - -impl TryFrom for Ready { - type Error = (); // FIXME - - fn try_from(promised: Promised) -> Result { - Ok(Ready { - module: promised.module.try_resolve().map_err(|_| ())?, - function: promised.function.try_resolve().map_err(|_| ())?, - args: promised.args.try_resolve().map_err(|_| ())?, - }) - } +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub struct Promised { + pub module: promise::Resolves, + pub function: promise::Resolves, + pub args: promise::Resolves>, } -impl From for arguments::Named { +// impl From for Promised { +// fn from(ready: Ready) -> Self { +// Promised { +// module: promise::Resolves::from(Ok(ready.module)), +// function: promise::Resolves::from(Ok(ready.function)), +// args: promise::Resolves::from(Ok(ready.args)), +// } +// } +// } + +// impl TryFrom for Ready { +// type Error = (); // FIXME +// +// fn try_from(promised: Promised) -> Result { +// Ok(Ready { +// module: promised.module.try_from().map_err(|_| ())?, +// function: promised.function.try_from().map_err(|_| ())?, +// args: promised.args.try_from().map_err(|_| ())?, +// }) +// } +// } + +impl From for arguments::Named { fn from(promised: Promised) -> Self { arguments::Named::from_iter([ ("module".into(), promised.module.into()), diff --git a/src/delegation/delegable.rs b/src/delegation/delegable.rs index 19806dd5..78ea8853 100644 --- a/src/delegation/delegable.rs +++ b/src/delegation/delegable.rs @@ -1,4 +1,11 @@ -use crate::proof::checkable::Checkable; +use crate::{ + ability::{ + arguments, + command::{ParseAbility, ParseAbilityError, ToCommand}, + }, + proof::checkable::Checkable, +}; +use libipld_core::ipld::Ipld; /// A trait for types that can be delegated. /// @@ -7,7 +14,39 @@ use crate::proof::checkable::Checkable; /// /// [`Delegation`]: crate::delegation::Delegation /// [`Invocation`]: crate::invocation::Invocation +// FIXME NOTE: don't need parse ability, because parse -> builder -> self +// FIXME NOTE: don't need ToCommand ability, because parse -> builder -> self, or .. -> promieed -> .. pub trait Delegable: Sized { /// A delegation with some arguments filled. - type Builder: TryInto + From + Checkable; + type Builder: TryInto + + From + + Checkable + + ParseAbility + + ToCommand + + Into>; + + fn into_command(self) -> String { + Self::Builder::from(self).to_command() + } + + fn into_named_args(self) -> arguments::Named { + Self::Builder::from(self).into() + } + + fn try_parse_to_ready( + command: &str, + named: arguments::Named, + ) -> Result::ArgsErr>> { + let builder = Self::Builder::try_parse(command, named)?; + builder.try_into().map_err(|err| todo!()) + } + + fn try_from_named( + named: arguments::Named, + ) -> Result::ArgsErr>> { + let builder = Self::Builder::try_parse("", named)?; + builder.try_into().map_err(|err| todo!()) + } } + +// FIXME ParseAbility + ToCommand + Checkable = Ability? diff --git a/src/delegation/payload.rs b/src/delegation/payload.rs index 3ab8d1f2..4361382b 100644 --- a/src/delegation/payload.rs +++ b/src/delegation/payload.rs @@ -323,7 +323,7 @@ impl< let args = arguments.ok_or(de::Error::missing_field("args"))?; let ability_builder = - ::try_parse(cmd.as_str(), &args).map_err(|e| { + ::try_parse(cmd.as_str(), args).map_err(|e| { de::Error::custom(format!( "Unable to parse ability field for {:?} because {:?}", cmd, e diff --git a/src/invocation/agent.rs b/src/invocation/agent.rs index 1a285e34..f4192fe9 100644 --- a/src/invocation/agent.rs +++ b/src/invocation/agent.rs @@ -91,7 +91,13 @@ where ) -> Result, ()> { let proofs = self .delegation_store - .get_chain(self.did, subject, &ability.clone().into(), vec![], now) + .get_chain( + self.did, + subject, + &::try_to_builder(ability.clone()).map_err(|_| ())?, + vec![], + now, + ) .map_err(|_| ())? .map(|chain| chain.map(|(cid, _)| cid).into()) .unwrap_or(vec![]); diff --git a/src/invocation/payload.rs b/src/invocation/payload.rs index ed3c2a04..3eca6d1a 100644 --- a/src/invocation/payload.rs +++ b/src/invocation/payload.rs @@ -363,7 +363,7 @@ impl<'de, A: ParseAbility + Deserialize<'de>, DID: Did + Deserialize<'de>> Deser let cmd: String = command.ok_or(de::Error::missing_field("cmd"))?; let args = arguments.ok_or(de::Error::missing_field("args"))?; - let ability = ::try_parse(cmd.as_str(), &args).map_err(|e| { + let ability = ::try_parse(cmd.as_str(), args).map_err(|e| { de::Error::custom(format!( "Unable to parse ability field for {:?} becuase {:?}", cmd, e diff --git a/src/invocation/promise.rs b/src/invocation/promise.rs index 592aac94..71a8b287 100644 --- a/src/invocation/promise.rs +++ b/src/invocation/promise.rs @@ -1,10 +1,9 @@ //! [UCAN Promise](https://github.com/ucan-wg/promise)s: selectors, wrappers, and traits. -// FIXME put entire module behind feature flag - mod any; mod err; mod ok; +mod pending; mod resolvable; mod resolves; @@ -14,14 +13,16 @@ pub mod store; pub use any::PromiseAny; pub use err::PromiseErr; pub use ok::PromiseOk; +pub use pending::Pending; pub use resolvable::Resolvable; pub use resolves::Resolves; pub use store::Store; +use enum_as_inner::EnumAsInner; use serde::{Deserialize, Serialize}; /// Top-level union of all UCAN Promise options -#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, EnumAsInner)] #[serde(untagged)] pub enum Promise { /// The `await/ok` promise diff --git a/src/invocation/promise/pending.rs b/src/invocation/promise/pending.rs new file mode 100644 index 00000000..8ac83db2 --- /dev/null +++ b/src/invocation/promise/pending.rs @@ -0,0 +1,9 @@ +use libipld_core::cid::Cid; + +// AKA Selector +#[derive(Debug, Clone, PartialEq)] +pub enum Pending { + Ok(Cid), + Err(Cid), + Any(Cid), +} diff --git a/src/invocation/promise/resolvable.rs b/src/invocation/promise/resolvable.rs index ee0d111b..32e1be35 100644 --- a/src/invocation/promise/resolvable.rs +++ b/src/invocation/promise/resolvable.rs @@ -1,5 +1,15 @@ -use crate::{ability::arguments, delegation::Delegable}; +use crate::{ + ability::{ + arguments, + command::{ParseAbility, ToCommand}, + }, + delegation::Delegable, + invocation::promise::Pending, + ipld, +}; use libipld_core::{cid::Cid, ipld::Ipld}; +use std::{collections::BTreeSet, fmt}; +use thiserror::Error; // FIXME rename "Unresolved" // FIXME better name @@ -15,9 +25,87 @@ pub trait Resolvable: Delegable { /// be a promise. /// /// [PromiseIpld]: crate::ipld::Promised - type Promised: Into + Into>; + // type Promised: Into + Into>; + type Promised: Into> + ToCommand; /// Attempt to resolve the [`Self::Promised`]. - // FIXME bubble up what we're waiting on fn try_resolve(promised: Self::Promised) -> Result; - fn try_resolve(promised: Self::Promised) -> Result; + fn try_resolve(promised: Self::Promised) -> Result> + where + Self::Promised: Clone, + { + let ipld_promise: arguments::Named = promised.clone().into(); + match arguments::Named::::try_from(ipld_promise) { + Err(_) => Err(CantResolve { + promised, + reason: todo!(), // ParseAbility::ArgsErr::ExpectedMap, + }), + Ok(named) => { + let builder = Self::Builder::try_parse(promised.to_command().as_str(), named) + .map_err(|reason| CantResolve { + promised: promised.clone(), + reason: todo!(), + })?; + + builder.try_into().map_err(|_reason| CantResolve { + promised, + reason: todo!(), + }) + } + } + } + + fn get_all_pending(promised: Self::Promised) -> BTreeSet { + let promise_map: arguments::Named = promised.into(); + + promise_map + .values() + .fold(BTreeSet::new(), |mut set, promised| { + if let ipld::Promised::Link(cid) = promised { + set.insert(*cid); + } + + set + }) + } + + fn try_to_builder(promised: Self::Promised) -> Result { + let cmd = promised.to_command(); + let ipld_promise: arguments::Named = promised.into(); + + let named: arguments::Named = + ipld_promise + .into_iter() + .fold(arguments::Named::new(), |mut acc, (k, v)| { + match v.try_into() { + Err(_) => (), + Ok(ipld) => { + acc.insert(k, ipld); // i.e. forget any promises + } + } + + acc + }); + + Self::Builder::try_parse(&cmd, named).map_err(|_| ()) + } +} + +#[derive(Error)] +pub struct CantResolve { + pub promised: S::Promised, + pub reason: <::Builder as ParseAbility>::ArgsErr, +} + +impl fmt::Debug for CantResolve +where + S::Promised: fmt::Debug, + <::Builder as ParseAbility>::ArgsErr: fmt::Debug, + Pending: fmt::Debug, +{ + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("CantResolve") + .field("promised", &self.promised) + .field("reason", &self.reason) + .finish() + } } diff --git a/src/invocation/promise/resolves.rs b/src/invocation/promise/resolves.rs index dcfc5651..3ee866d4 100644 --- a/src/invocation/promise/resolves.rs +++ b/src/invocation/promise/resolves.rs @@ -1,4 +1,4 @@ -use super::{PromiseAny, PromiseErr, PromiseOk}; +use super::{Promise, PromiseAny, PromiseErr, PromiseOk}; use libipld_core::ipld::Ipld; use serde::{Deserialize, Serialize}; use std::fmt; @@ -277,6 +277,15 @@ impl From> for Resolves { } } +impl From> for Promise { + fn from(resolves: Resolves) -> Promise { + match resolves { + Resolves::Ok(p_ok) => p_ok.into(), + Resolves::Err(p_err) => p_err.into(), + } + } +} + impl TryFrom> for PromiseOk { type Error = PromiseErr; diff --git a/src/ipld.rs b/src/ipld.rs index 95aeca60..2c3a79db 100644 --- a/src/ipld.rs +++ b/src/ipld.rs @@ -6,14 +6,14 @@ //! //! [`Ipld`]: libipld_core::ipld::Ipld -mod enriched; +// mod enriched; mod newtype; mod number; mod promised; pub mod cid; -pub use enriched::Enriched; +// pub use enriched::Enriched; pub use newtype::Newtype; pub use number::Number; -pub use promised::Promised; +pub use promised::*; diff --git a/src/ipld/promised.rs b/src/ipld/promised.rs index fce3cdac..8071181d 100644 --- a/src/ipld/promised.rs +++ b/src/ipld/promised.rs @@ -1,75 +1,325 @@ -use super::enriched::Enriched; -use crate::invocation::promise::{Promise, PromiseAny, PromiseErr, PromiseOk}; -use libipld_core::ipld::Ipld; +// use super::enriched::Enriched; +use crate::{ + ability::arguments, + invocation::promise::{Pending, Promise, PromiseAny, PromiseErr, PromiseOk, Resolves}, + url, +}; +use enum_as_inner::EnumAsInner; +use libipld_core::{cid::Cid, ipld::Ipld}; use serde::{Deserialize, Serialize}; +use std::{collections::BTreeMap, path::PathBuf}; /// A recursive data structure whose leaves may be [`Ipld`] or promises. /// /// [`Promised`] resolves to regular [`Ipld`]. -#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] -#[serde(transparent)] -pub struct Promised(pub Promise, Enriched>); +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, EnumAsInner)] +pub enum Promised { + // Resolved Leaves + Null, + Bool(bool), + Integer(i128), + Float(f64), + String(String), + Bytes(Vec), + Link(Cid), -impl From, Enriched>> for Promised { - fn from(promise: Promise, Enriched>) -> Self { - Promised(promise) + // Pending Leaves + WaitOk(Cid), + WaitErr(Cid), + WaitAny(Cid), + + // Recursive + List(Vec), + Map(BTreeMap), +} + +impl Promised { + pub fn with_resolved(self, f: F) -> Result + where + F: FnOnce(Ipld) -> T, + { + match self.try_into() { + Ok(ipld) => Ok(f(ipld)), + Err(pending) => Err(pending), + } + } + + pub fn with_pending(self, f: F) -> Result + where + F: FnOnce(Pending) -> E, + { + match self.try_into() { + Ok(ipld) => Err(ipld), + Err(promised) => Ok(f(promised)), + } } } -impl From, Enriched>> for Promised { - fn from(p_any: PromiseAny, Enriched>) -> Self { - Promised(p_any.into()) +impl From for Promised { + fn from(ipld: Ipld) -> Promised { + match ipld { + Ipld::Null => Promised::Null, + Ipld::Bool(b) => Promised::Bool(b), + Ipld::Integer(i) => Promised::Integer(i), + Ipld::Float(f) => Promised::Float(f), + Ipld::String(s) => Promised::String(s), + Ipld::Bytes(b) => Promised::Bytes(b), + Ipld::Link(cid) => Promised::Link(cid), + Ipld::List(list) => Promised::List(list.into_iter().map(Into::into).collect()), + Ipld::Map(map) => { + let mut promised_map = BTreeMap::new(); + for (k, v) in map { + promised_map.insert(k, v.into()); + } + Promised::Map(promised_map) + } + } } } -impl From>> for Promised { - fn from(p_ok: PromiseOk>) -> Self { - Promised(p_ok.into()) +impl TryFrom for Ipld { + type Error = Pending; + + fn try_from(promised: Promised) -> Result { + match promised { + Promised::Null => Ok(Ipld::Null), + Promised::Bool(b) => Ok(Ipld::Bool(b)), + Promised::Integer(i) => Ok(Ipld::Integer(i)), + Promised::Float(f) => Ok(Ipld::Float(f)), + Promised::String(s) => Ok(Ipld::String(s)), + Promised::Bytes(b) => Ok(Ipld::Bytes(b)), + Promised::Link(cid) => Ok(Ipld::Link(cid)), + Promised::List(list) => list + .into_iter() + .try_fold(Vec::new(), |mut acc, promised| { + acc.push(promised.try_into()?); + Ok(acc) + }) + .map(Ipld::List), + Promised::Map(map) => map + .into_iter() + .try_fold(BTreeMap::new(), |mut acc, (k, v)| { + acc.insert(k, v.try_into()?); + Ok(acc) + }) + .map(Ipld::Map), + Promised::WaitOk(cid) => Err(Pending::Ok(cid).into()), + Promised::WaitErr(cid) => Err(Pending::Err(cid).into()), + Promised::WaitAny(cid) => Err(Pending::Any(cid).into()), + } } } -impl From>> for Promised { - fn from(p_err: PromiseErr>) -> Self { - Promised(p_err.into()) +impl From> for Promised { + fn from(p_ok: PromiseOk) -> Promised { + match p_ok { + PromiseOk::Fulfilled(ipld) => ipld.into(), + PromiseOk::Pending(cid) => Promised::WaitOk(cid), + } } } -impl From for Promised { - fn from(ipld: Ipld) -> Self { - Promised(Promise::Ok(PromiseOk::Fulfilled(ipld.into()))) +impl From> for Promised { + fn from(p_err: PromiseErr) -> Promised { + match p_err { + PromiseErr::Rejected(ipld) => ipld.into(), + PromiseErr::Pending(cid) => Promised::WaitErr(cid), + } } } -impl TryFrom for Ipld { - type Error = Promised; +impl From> for Promised { + fn from(p_any: PromiseAny) -> Promised { + match p_any { + PromiseAny::Fulfilled(ipld) => ipld.into(), + PromiseAny::Rejected(ipld) => ipld.into(), + PromiseAny::Pending(cid) => Promised::WaitAny(cid), + } + } +} - fn try_from(p: Promised) -> Result { - match p.0 { - Promise::Ok(p_ok) => match p_ok { - PromiseOk::Fulfilled(inner) => { - inner.try_into().map_err(|e| PromiseOk::Fulfilled(e).into()) - } +impl From> for Promised { + fn from(promise: Promise) -> Promised { + match promise { + Promise::Ok(p_ok) => p_ok.into(), + Promise::Err(p_err) => p_err.into(), + Promise::Any(p_any) => p_any.into(), + } + } +} - PromiseOk::Pending(inner) => Err(PromiseOk::Pending(inner).into()), +impl From> for Promised +where + Promised: From, +{ + fn from(r: Resolves) -> Promised { + match r { + Resolves::Ok(p_ok) => match p_ok { + PromiseOk::Fulfilled(val) => val.into(), + PromiseOk::Pending(cid) => Promised::WaitOk(cid), }, - Promise::Err(p_err) => match p_err { - PromiseErr::Rejected(inner) => { - inner.try_into().map_err(|e| PromiseErr::Rejected(e).into()) - } - - PromiseErr::Pending(inner) => Err(PromiseErr::Pending(inner).into()), + Resolves::Err(p_err) => match p_err { + PromiseErr::Rejected(val) => val.into(), + PromiseErr::Pending(cid) => Promised::WaitErr(cid), }, - Promise::Any(p_any) => match p_any { - PromiseAny::Fulfilled(inner) => inner - .try_into() - .map_err(|e| Promise::Any(PromiseAny::Fulfilled(e)).into()), + } + } +} + +impl From> for Promised +where + Promised: From, +{ + fn from(args: arguments::Named) -> Promised { + Promised::Map( + args.into_iter() + .map(|(k, v)| (k, v.into())) + .collect::>(), + ) + } +} + +impl From for Promised { + fn from(path: PathBuf) -> Promised { + Promised::String(path.to_string_lossy().to_string()) + } +} + +impl From for Promised { + fn from(cid: Cid) -> Promised { + Promised::Link(cid) + } +} + +impl From<::url::Url> for Promised { + fn from(url: ::url::Url) -> Promised { + Promised::String(url.to_string()) + } +} - PromiseAny::Rejected(inner) => { - inner.try_into().map_err(|e| PromiseAny::Rejected(e).into()) +impl From for Promised { + fn from(nt: url::Newtype) -> Promised { + nt.0.into() + } +} + +impl From> for Promised +where + Promised: From, +{ + fn from(opt: Option) -> Promised { + match opt { + Some(val) => val.into(), + None => Promised::Null, + } + } +} + +impl From for Promised { + fn from(s: String) -> Promised { + Promised::String(s) + } +} + +impl From for Promised { + fn from(f: f64) -> Promised { + Promised::Float(f) + } +} + +impl From for Promised { + fn from(i: i128) -> Promised { + Promised::Integer(i) + } +} + +impl From for Promised { + fn from(b: bool) -> Promised { + Promised::Bool(b) + } +} + +impl From> for Promised { + fn from(b: Vec) -> Promised { + Promised::Bytes(b) + } +} + +impl From> for Promised +where + Promised: From, +{ + fn from(map: BTreeMap) -> Promised { + Promised::Map( + map.into_iter() + .map(|(k, v)| (k, v.into())) + .collect::>(), + ) + } +} +impl From> for Promised +where + Promised: From, +{ + fn from(list: Vec) -> Promised { + Promised::List(list.into_iter().map(Into::into).collect()) + } +} + +/*************************** +| POST ORDER IPLD ITERATOR | +***************************/ + +/// A post-order [`Ipld`] iterator +#[derive(Clone, Debug, Default, PartialEq)] +#[cfg_attr(feature = "serde-codec", derive(serde::Serialize))] +#[allow(clippy::module_name_repetitions)] +pub struct PostOrderIpldIter<'a> { + inbound: Vec<&'a Promised>, + outbound: Vec<&'a Promised>, +} + +// #[derive(Clone, Debug, PartialEq)] +// pub enum Item<'a> { +// Node(&'a Promised), +// Inner(&'a Cid), +// } + +impl<'a> PostOrderIpldIter<'a> { + /// Initialize a new [`PostOrderIpldIter`] + #[must_use] + pub fn new(promised: &'a Promised) -> Self { + PostOrderIpldIter { + inbound: vec![promised], + outbound: vec![], + } + } +} + +impl<'a> Iterator for PostOrderIpldIter<'a> { + type Item = &'a Promised; + + fn next(&mut self) -> Option { + loop { + match self.inbound.pop() { + None => return self.outbound.pop(), + Some(ref map @ Promised::Map(ref btree)) => { + self.outbound.push(map.clone()); + + for node in btree.values() { + self.inbound.push(node); + } } - PromiseAny::Pending(inner) => Err(PromiseAny::Pending(inner).into()), - }, + Some(ref list @ Promised::List(ref vector)) => { + self.outbound.push(list.clone()); + + for node in vector { + self.inbound.push(node); + } + } + Some(node) => self.outbound.push(node), + } } } } diff --git a/src/proof/error.rs b/src/proof/error.rs index c9bf339d..f2911c00 100644 --- a/src/proof/error.rs +++ b/src/proof/error.rs @@ -13,7 +13,7 @@ use wasm_bindgen::prelude::*; pub struct Unequal(); /// A generic error for when two fields are unequal. -#[derive(Copy, Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Error)] +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Error)] #[cfg_attr(target_arch = "wasm32", wasm_bindgen)] pub enum OptionalFieldError { /// A required field is missing. diff --git a/src/proof/util.rs b/src/proof/util.rs index 05926acd..e1046e34 100644 --- a/src/proof/util.rs +++ b/src/proof/util.rs @@ -1,15 +1,15 @@ use super::error::OptionalFieldError; -pub fn check_optional( - target: Option, - proof: Option, -) -> Result<(), OptionalFieldError> { - if let Some(target_value) = target { - let proof_value = proof.ok_or(OptionalFieldError::Missing)?; - if target_value != proof_value { - return Err(OptionalFieldError::Unequal); - } - } - - Ok(()) -} +// pub fn check_optional( +// target: Option, +// proof: Option, +// ) -> Result<(), OptionalFieldError> { +// if let Some(target_value) = target { +// let proof_value = proof.ok_or(OptionalFieldError::Missing)?; +// if target_value != proof_value { +// return Err(OptionalFieldError::Unequal); +// } +// } +// +// Ok(()) +// } diff --git a/src/reader/builder.rs b/src/reader/builder.rs index d7298de7..e772e239 100644 --- a/src/reader/builder.rs +++ b/src/reader/builder.rs @@ -1,5 +1,5 @@ use super::Reader; -use crate::{ability::arguments, delegation::Delegable, proof::checkable::Checkable}; +use crate::ability::arguments; use serde::{Deserialize, Serialize}; /// A helper newtype that marks a value as being a [`Delegable::Builder`]. @@ -18,13 +18,6 @@ use serde::{Deserialize, Serialize}; #[derive(Clone, PartialEq, Debug, Serialize, Deserialize)] pub struct Builder(pub T); -impl Delegable for Reader -where - Reader>: Checkable, -{ - type Builder = Reader>; -} - impl>> From> for arguments::Named { fn from(builder: Builder) -> Self { builder.0.into() diff --git a/src/reader/generic.rs b/src/reader/generic.rs index ae713d82..10cfa815 100644 --- a/src/reader/generic.rs +++ b/src/reader/generic.rs @@ -164,13 +164,15 @@ impl ToCommand for Reader { } } -impl ParseAbility for Reader { - type Error = ParseAbilityError<::Error>; +impl>> TryFrom> + for Reader +{ + type Error = ParseAbilityError<>>::Error>; - fn try_parse(cmd: &str, args: &arguments::Named) -> Result { + fn try_from(args: arguments::Named) -> Result { Ok(Reader { env: Default::default(), - val: T::try_parse(cmd, args).map_err(ParseAbilityError::InvalidArgs)?, + val: T::try_from(args).map_err(ParseAbilityError::InvalidArgs)?, }) } } From 322b2c6cdf5dd32b93790a919bfaa7fd9a22b7ff Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Wed, 21 Feb 2024 21:58:32 -0800 Subject: [PATCH 100/188] Cleanup after simplifying promises --- src/ability.rs | 9 +- src/ability/arguments/named.rs | 20 +-- src/ability/command.rs | 43 ------- src/ability/crud.rs | 46 ++++++- src/ability/crud/any.rs | 20 +-- src/ability/crud/create.rs | 92 ++++++++------ src/ability/crud/destroy.rs | 38 +++++- src/ability/crud/parents.rs | 3 +- src/ability/crud/read.rs | 116 ++++++++--------- src/ability/crud/update.rs | 45 ++++++- src/ability/dynamic.rs | 5 +- src/ability/msg.rs | 22 +++- src/ability/msg/any.rs | 5 +- src/ability/msg/receive.rs | 29 ++++- src/ability/msg/send.rs | 73 +++++++++-- src/ability/parse.rs | 70 ++++++++++ src/ability/preset.rs | 32 ++++- src/ability/ucan/revoke.rs | 30 ++++- src/ability/wasm/module.rs | 10 +- src/ability/wasm/run.rs | 84 ++++++------ src/crypto/signature/envelope.rs | 8 +- src/delegation.rs | 5 +- src/delegation/agent.rs | 3 - src/delegation/condition/traits.rs | 3 +- src/delegation/delegable.rs | 5 +- src/delegation/payload.rs | 7 +- src/did/key/verifier.rs | 2 +- src/invocation.rs | 9 +- src/invocation/agent.rs | 183 ++++++++++++++++++--------- src/invocation/payload.rs | 5 +- src/invocation/promise.rs | 2 +- src/invocation/promise/pending.rs | 2 +- src/invocation/promise/resolvable.rs | 53 ++++++-- src/invocation/promise/resolves.rs | 12 +- src/ipld/newtype.rs | 11 ++ src/ipld/promised.rs | 101 ++++++++++++++- src/lib.rs | 12 +- src/proof.rs | 1 - src/proof/parentless.rs | 2 +- src/proof/util.rs | 15 --- src/reader/generic.rs | 5 +- src/url.rs | 26 ++++ 42 files changed, 878 insertions(+), 386 deletions(-) create mode 100644 src/ability/parse.rs delete mode 100644 src/proof/util.rs diff --git a/src/ability.rs b/src/ability.rs index 2c3cebee..1cc28455 100644 --- a/src/ability.rs +++ b/src/ability.rs @@ -33,10 +33,6 @@ //! field may include a promise pointing at another invocation. Once fully //! resolved ("ready"), they must be validatable against the delegation chain. -// FIXME feature flag each? -// FIXME ability implementers guide (e.g. serde deny fields) -// - pub mod ucan; #[cfg(feature = "ability-crud")] @@ -53,12 +49,9 @@ pub mod preset; pub mod arguments; pub mod command; +pub mod parse; #[cfg(target_arch = "wasm32")] pub mod js; -// // TODO move to crate::wasm? or hide behind "dynamic" feature flag? pub mod dynamic; - -// FIXME macro to derive promise versions & delagted builder versions -// ... also maybe Ipld diff --git a/src/ability/arguments/named.rs b/src/ability/arguments/named.rs index 6ea813ef..0120da7b 100644 --- a/src/ability/arguments/named.rs +++ b/src/ability/arguments/named.rs @@ -1,4 +1,4 @@ -use crate::ipld; +use crate::{invocation::promise::Pending, ipld}; use libipld_core::ipld::Ipld; use serde::{Deserialize, Serialize}; use std::collections::BTreeMap; @@ -293,20 +293,14 @@ impl From> for Named { } impl TryFrom> for Named { - type Error = Named; + type Error = Pending; fn try_from(named: Named) -> Result { - // FIXME lots of clone - // FIXME idea: what if they implemet a is_resoled, and then the try_from? - // This lets us check by ref, and then do the conversion and unwrap - named - .iter() - .try_fold(Named::new(), |mut acc, (ref k, v)| { - let ipld = v.clone().try_into().map_err(|_| ())?; - acc.insert(k.to_string(), ipld); - Ok(acc) - }) - .map_err(|()| named.clone()) + named.iter().try_fold(Named::new(), |mut acc, (ref k, v)| { + let ipld = v.clone().try_into()?; + acc.insert(k.to_string(), ipld); + Ok(acc) + }) } } diff --git a/src/ability/command.rs b/src/ability/command.rs index b9c5d0ab..5cfa1975 100644 --- a/src/ability/command.rs +++ b/src/ability/command.rs @@ -16,11 +16,6 @@ //! } //! ``` -use crate::ability::arguments; -use libipld_core::ipld::Ipld; -use std::fmt; -use thiserror::Error; - /// Attach a `cmd` field to a type /// /// Commands are the `cmd` field of a UCAN, and set the shape of the `args` field. @@ -55,44 +50,6 @@ pub trait Command { const COMMAND: &'static str; } -// FIXME definitely needs a better name -// pub trait ParseAbility: TryFrom> { -pub trait ParseAbility: Sized { - type ArgsErr: fmt::Debug; - - fn try_parse( - cmd: &str, - args: arguments::Named, - ) -> Result>; -} - -#[derive(Debug, Clone, Error)] -pub enum ParseAbilityError { - #[error("Unknown command: {0}")] - UnknownCommand(String), - - #[error(transparent)] - InvalidArgs(#[from] E), -} - -impl>> ParseAbility for T -where - >>::Error: fmt::Debug, -{ - type ArgsErr = >>::Error; - - fn try_parse( - cmd: &str, - args: arguments::Named, - ) -> Result>>::Error>> { - if cmd != T::COMMAND { - return Err(ParseAbilityError::UnknownCommand(cmd.to_string())); - } - - Self::try_from(args).map_err(ParseAbilityError::InvalidArgs) - } -} - // NOTE do not export; this is used to limit the Hierarchy // interface to [Parentful] and [Parentless] while enabling [Dynamic] // FIXME ^^^^ NOT ANYMORE? diff --git a/src/ability/crud.rs b/src/ability/crud.rs index bddccbca..1e1ab5df 100644 --- a/src/ability/crud.rs +++ b/src/ability/crud.rs @@ -54,7 +54,8 @@ pub use parents::*; use crate::{ ability::{ arguments, - command::{ParseAbility, ParseAbilityError, ToCommand}, + command::ToCommand, + parse::{ParseAbility, ParseAbilityError, ParsePromised}, }, delegation::Delegable, invocation::promise::Resolvable, @@ -90,6 +91,49 @@ pub enum Promised { Destroy(destroy::Promised), } +impl ParsePromised for Promised { + type PromisedArgsError = (); + + fn try_parse_promised( + cmd: &str, + args: arguments::Named, + ) -> Result> { + match create::Promised::try_parse_promised(cmd, args.clone()) { + Ok(create) => return Ok(Promised::Create(create)), + Err(ParseAbilityError::InvalidArgs(_)) => { + return Err(ParseAbilityError::InvalidArgs(())) + } + Err(ParseAbilityError::UnknownCommand(_)) => (), + } + + match read::Promised::try_parse_promised(cmd, args.clone()) { + Ok(read) => return Ok(Promised::Read(read)), + Err(ParseAbilityError::InvalidArgs(_)) => { + return Err(ParseAbilityError::InvalidArgs(())) + } + Err(ParseAbilityError::UnknownCommand(_)) => (), + } + + match update::Promised::try_parse_promised(cmd, args.clone()) { + Ok(update) => return Ok(Promised::Update(update)), + Err(ParseAbilityError::InvalidArgs(_)) => { + return Err(ParseAbilityError::InvalidArgs(())) + } + Err(ParseAbilityError::UnknownCommand(_)) => (), + } + + match destroy::Promised::try_parse_promised(cmd, args) { + Ok(destroy) => return Ok(Promised::Destroy(destroy)), + Err(ParseAbilityError::InvalidArgs(_)) => { + return Err(ParseAbilityError::InvalidArgs(())) + } + Err(ParseAbilityError::UnknownCommand(_)) => (), + } + + Err(ParseAbilityError::UnknownCommand(cmd.into())) + } +} + impl Delegable for Ready { type Builder = Builder; } diff --git a/src/ability/crud/any.rs b/src/ability/crud/any.rs index f5bb2aab..6fcdd310 100644 --- a/src/ability/crud/any.rs +++ b/src/ability/crud/any.rs @@ -1,10 +1,7 @@ //! "Any" CRUD ability (superclass of all CRUD abilities) use crate::{ - ability::{ - arguments, - command::{Command, ParseAbility, ParseAbilityError}, - }, + ability::{arguments, command::Command}, proof::{error::OptionalFieldError, parentless::NoParents, same::CheckSame}, }; use libipld_core::{error::SerdeError, ipld::Ipld, serde as ipld_serde}; @@ -95,21 +92,6 @@ impl TryFrom> for Any { impl NoParents for Any {} -// impl ParseAbility for Any { -// type ArgsErr = (); -// -// fn try_parse( -// cmd: &str, -// args: arguments::Named, -// ) -> Result> { -// if cmd != Self::COMMAND { -// return Err(ParseAbilityError::CommandMismatch); -// } -// -// Self::try_from(args).map_err(|_| ParseAbilityError::Args(Self::COMMAND)) -// } -// } - impl CheckSame for Any { type Error = OptionalFieldError; diff --git a/src/ability/crud/create.rs b/src/ability/crud/create.rs index 1247cacd..ae8a5b64 100644 --- a/src/ability/crud/create.rs +++ b/src/ability/crud/create.rs @@ -7,9 +7,9 @@ use crate::{ ipld, proof::{checkable::Checkable, parentful::Parentful, parents::CheckParents, same::CheckSame}, }; -use libipld_core::{cid::Cid, error::SerdeError, ipld::Ipld, serde as ipld_serde}; +use libipld_core::ipld::Ipld; use serde::Serialize; -use std::{collections::BTreeMap, path::PathBuf}; +use std::path::PathBuf; // FIXME deserialize instance @@ -123,31 +123,57 @@ impl Command for Promised { const COMMAND: &'static str = COMMAND; } -// impl TryFrom for Ready { -// type Error = (); // FIXME -// -// fn try_from(ipld: Ipld) -> Result { -// if let Ipld::Map(mut map) = ipld { -// if map.len() > 2 { -// return Err(()); // FIXME -// } -// -// Ok(Generic { -// path: map -// .remove("path") -// .map(|ipld| P::try_from(ipld).map_err(|_| ())) -// .transpose()?, -// -// args: map -// .remove("args") -// .map(|ipld| A::try_from(ipld).map_err(|_| ())) -// .transpose()?, -// }) -// } else { -// Err(()) // FIXME -// } -// } -// } +impl TryFrom> for Promised { + type Error = (); + + fn try_from(arguments: arguments::Named) -> Result { + let mut path = None; + let mut args = None; + + for (k, prom) in arguments { + match k.as_str() { + "path" => match prom { + ipld::Promised::String(s) => { + path = Some(promise::Resolves::Ok( + promise::PromiseOk::Fulfilled(PathBuf::from(s)).into(), + )); + } + ipld::Promised::WaitOk(cid) => { + path = Some(promise::PromiseOk::Pending(cid).into()); + } + ipld::Promised::WaitErr(cid) => { + path = Some(promise::PromiseErr::Pending(cid).into()); + } + ipld::Promised::WaitAny(cid) => { + todo!() // FIXME // path = Some(promise::PromiseAny::Pending(cid).into()); + } + _ => return Err(()), + }, + + "args" => { + args = match prom { + ipld::Promised::Map(map) => { + Some(promise::PromiseOk::Fulfilled(arguments::Named(map)).into()) + } + ipld::Promised::WaitOk(cid) => { + Some(promise::PromiseOk::Pending(cid).into()) + } + ipld::Promised::WaitErr(cid) => { + Some(promise::PromiseErr::Pending(cid).into()) + } + ipld::Promised::WaitAny(cid) => { + todo!() // FIXME // Some(promise::PromiseAny::Pending(cid).into()) + } + _ => return Err(()), + } + } + _ => return Err(()), + } + } + + Ok(Promised { path, args }) + } +} impl TryFrom> for Ready { type Error = (); @@ -254,18 +280,6 @@ impl From for Promised { } } -// FIXME may want to name this something other than a TryFrom -// impl From for Builder { -// fn from(promised: Promised) -> Self { -// Builder { -// path: promised.path.and_then(|x| x.try_resolve().ok()), -// args: promised -// .args -// .and_then(|x| x.try_resolve().ok()?.try_into().ok()), -// } -// } -// } - impl promise::Resolvable for Ready { type Promised = Promised; } diff --git a/src/ability/crud/destroy.rs b/src/ability/crud/destroy.rs index 83a6fb4a..a5d48571 100644 --- a/src/ability/crud/destroy.rs +++ b/src/ability/crud/destroy.rs @@ -1,4 +1,5 @@ //! Destroy a resource. + use super::parents::MutableParents; use crate::{ ability::{arguments, command::Command}, @@ -9,9 +10,7 @@ use crate::{ }; use libipld_core::ipld::Ipld; use serde::Serialize; -use std::{collections::BTreeMap, path::PathBuf}; - -// FIXME deserialize instance +use std::path::PathBuf; /// A helper for creating lifecycle instances of `crud/create` with the correct shape. #[derive(Debug, Clone, PartialEq, Serialize)] @@ -101,6 +100,39 @@ pub struct Promised { pub path: Option>, } +impl TryFrom> for Promised { + type Error = (); + + fn try_from(arguments: arguments::Named) -> Result { + let mut path = None; + + for (k, prom) in arguments { + match k.as_str() { + "path" => match prom { + ipld::Promised::String(s) => { + path = Some(promise::Resolves::Ok( + promise::PromiseOk::Fulfilled(PathBuf::from(s)).into(), + )); + } + ipld::Promised::WaitOk(cid) => { + path = Some(promise::PromiseOk::Pending(cid).into()); + } + ipld::Promised::WaitErr(cid) => { + path = Some(promise::PromiseErr::Pending(cid).into()); + } + ipld::Promised::WaitAny(cid) => { + todo!() // FIXME // path = Some(promise::PromiseAny::Pending(cid).into()); + } + _ => return Err(()), + }, + _ => return Err(()), + } + } + + Ok(Promised { path }) + } +} + const COMMAND: &'static str = "crud/destroy"; impl Command for Ready { diff --git a/src/ability/crud/parents.rs b/src/ability/crud/parents.rs index ac4f130d..18b7cceb 100644 --- a/src/ability/crud/parents.rs +++ b/src/ability/crud/parents.rs @@ -8,7 +8,8 @@ use super::error::ParentError; use crate::{ ability::{ arguments, - command::{ParseAbility, ParseAbilityError, ToCommand}, + command::ToCommand, + parse::{ParseAbility, ParseAbilityError}, }, proof::{parents::CheckParents, same::CheckSame}, }; diff --git a/src/ability/crud/read.rs b/src/ability/crud/read.rs index 76ee8019..57001d8c 100644 --- a/src/ability/crud/read.rs +++ b/src/ability/crud/read.rs @@ -2,12 +2,9 @@ use super::any as crud; use crate::{ - ability::{ - arguments, - command::{Command, ParseAbility, ParseAbilityError}, - }, + ability::{arguments, command::Command}, delegation::Delegable, - invocation::{promise, promise::Resolves}, + invocation::promise, ipld, proof::{checkable::Checkable, parentful::Parentful, parents::CheckParents, same::CheckSame}, }; @@ -98,14 +95,66 @@ pub struct Promised { pub args: Option>>, } +impl TryFrom> for Promised { + type Error = (); + + fn try_from(arguments: arguments::Named) -> Result { + let mut path = None; + let mut args = None; + + for (k, prom) in arguments { + match k.as_str() { + "path" => match prom { + ipld::Promised::String(s) => { + path = Some(promise::Resolves::Ok( + promise::PromiseOk::Fulfilled(PathBuf::from(s)).into(), + )); + } + ipld::Promised::WaitOk(cid) => { + path = Some(promise::PromiseOk::Pending(cid).into()); + } + ipld::Promised::WaitErr(cid) => { + path = Some(promise::PromiseErr::Pending(cid).into()); + } + ipld::Promised::WaitAny(cid) => { + todo!() // FIXME // path = Some(promise::PromiseAny::Pending(cid).into()); + } + _ => return Err(()), + }, + + "args" => { + args = match prom { + ipld::Promised::Map(map) => { + Some(promise::PromiseOk::Fulfilled(arguments::Named(map)).into()) + } + ipld::Promised::WaitOk(cid) => { + Some(promise::PromiseOk::Pending(cid).into()) + } + ipld::Promised::WaitErr(cid) => { + Some(promise::PromiseErr::Pending(cid).into()) + } + ipld::Promised::WaitAny(cid) => { + todo!() // FIXME // Some(promise::PromiseAny::Pending(cid).into()) + } + _ => return Err(()), + } + } + _ => return Err(()), + } + } + + Ok(Promised { path, args }) + } +} + const COMMAND: &'static str = "crud/read"; impl Command for Ready { - const COMMAND: &'static str = "crud/read"; + const COMMAND: &'static str = COMMAND; } impl Command for Promised { - const COMMAND: &'static str = "crud/read"; + const COMMAND: &'static str = COMMAND; } impl Delegable for Ready { @@ -205,34 +254,6 @@ impl CheckParents for Ready { } } -// impl From for arguments::Named { -// fn from(promised: Promised) -> Self { -// let mut named = arguments::Named::new(); -// -// if let Some(path_res) = promised.path { -// named.insert("path".into(), path_res.into()); -// } -// -// if let Some(args_res) = promised.args { -// named.insert("args".into(), args_res.into()); -// } -// -// named -// } -// } - -impl From for Promised { - fn from(r: Ready) -> Promised { - Promised { - path: r - .path - .map(|inner_path| promise::PromiseOk::Fulfilled(inner_path).into()), - - args: r.args.map(|inner_args| Resolves::new(inner_args.into())), - } - } -} - impl promise::Resolvable for Ready { type Promised = Promised; } @@ -252,28 +273,3 @@ impl From for arguments::Named { named } } - -// impl TryFrom> for Promised { -// type Error = (); -// -// fn try_from(arguments: arguments::Named) -> Result { -// let mut path = None; -// let mut args = None; -// -// for (k, v) in arguments.into_iter() { -// match k.as_str() { -// "path" => { -// let path = promise::Resolves::try_from(v)?; -// path.map(|path| Promised { path, args }); -// } -// "args" => { -// let args = promise::Resolves::try_from(v)?; -// args.map(|args| Promised { path, args }); -// } -// _ => return Err(()), -// } -// } -// -// Ok(Promised { path, args }) -// } -// } diff --git a/src/ability/crud/update.rs b/src/ability/crud/update.rs index dd2225da..305855e6 100644 --- a/src/ability/crud/update.rs +++ b/src/ability/crud/update.rs @@ -140,7 +140,7 @@ pub struct Promised { /// Optional arguments to be passed in the update. #[serde(default, skip_serializing_if = "Option::is_none")] - args: Option, + args: Option>>, } const COMMAND: &'static str = "crud/update"; @@ -157,6 +157,49 @@ impl Command for Promised { const COMMAND: &'static str = COMMAND; } +impl TryFrom> for Promised { + type Error = (); + + fn try_from(named: arguments::Named) -> Result { + let mut path = None; + let mut args = None; + + for (key, prom) in named { + match key.as_str() { + "path" => match Ipld::try_from(prom) { + Err(pending) => { + path = Some(pending.into()); + } + Ok(ipld) => match ipld { + Ipld::String(s) => path = Some(promise::Resolves::new(PathBuf::from(s))), + _ => return Err(()), + }, + }, + + "args" => match prom { + ipld::Promised::Map(map) => { + args = Some(promise::Resolves::new(arguments::Named(map))) + } + ipld::Promised::WaitOk(cid) => { + args = Some(promise::Resolves::new(arguments::Named::new())); + } + ipld::Promised::WaitErr(cid) => { + args = Some(promise::Resolves::new(arguments::Named::new())); + } + ipld::Promised::WaitAny(cid) => { + args = Some(promise::Resolves::new(arguments::Named::new())); + } + _ => return Err(()), + }, + + _ => return Err(()), + } + } + + Ok(Promised { path, args }) + } +} + impl Delegable for Ready { type Builder = Builder; } diff --git a/src/ability/dynamic.rs b/src/ability/dynamic.rs index d95be19f..a665a7aa 100644 --- a/src/ability/dynamic.rs +++ b/src/ability/dynamic.rs @@ -2,12 +2,13 @@ use super::{ arguments, - command::{ParseAbility, ParseAbilityError, ToCommand}, + command::ToCommand, + parse::{ParseAbility, ParseAbilityError}, }; use crate::proof::same::CheckSame; use libipld_core::{error::SerdeError, ipld::Ipld, serde as ipld_serde}; use serde::{Deserialize, Serialize}; -use std::{convert::Infallible, fmt::Debug}; +use std::fmt::Debug; #[cfg(target_arch = "wasm32")] use wasm_bindgen::prelude::*; diff --git a/src/ability/msg.rs b/src/ability/msg.rs index 3968c0cf..4fa01bed 100644 --- a/src/ability/msg.rs +++ b/src/ability/msg.rs @@ -10,7 +10,8 @@ pub use any::Any; use crate::{ ability::{ arguments, - command::{ParseAbility, ParseAbilityError, ToCommand}, + command::ToCommand, + parse::{ParseAbility, ParseAbilityError, ParsePromised}, }, delegation::Delegable, invocation::promise::Resolvable, @@ -69,6 +70,25 @@ impl ToCommand for Promised { } } +impl ParsePromised for Promised { + type PromisedArgsError = (); + + fn try_parse_promised( + cmd: &str, + args: arguments::Named, + ) -> Result> { + if let Ok(send) = send::Promised::try_parse_promised(cmd, args.clone()) { + return Ok(Promised::Send(send)); + } + + if let Ok(receive) = receive::Promised::try_parse_promised(cmd, args) { + return Ok(Promised::Receive(receive)); + } + + Err(ParseAbilityError::UnknownCommand(cmd.to_string())) + } +} + // impl ParseAbility for Ready { // type ArgsErr = (); // diff --git a/src/ability/msg/any.rs b/src/ability/msg/any.rs index 8290420b..a20e3db9 100644 --- a/src/ability/msg/any.rs +++ b/src/ability/msg/any.rs @@ -3,10 +3,11 @@ use crate::{ ability::{arguments, command::Command}, proof::{parentless::NoParents, same::CheckSame}, + url, }; use libipld_core::{error::SerdeError, ipld::Ipld, serde as ipld_serde}; use serde::{Deserialize, Serialize}; -use url::Url; +// use url::Url; #[cfg_attr(doc, aquamarine::aquamarine)] /// The [`msg::Any`][Any] ability may not be invoked, but it is the superclass of @@ -44,7 +45,7 @@ use url::Url; #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] #[serde(deny_unknown_fields)] pub struct Any { - pub from: Option, + pub from: Option, } impl Command for Any { diff --git a/src/ability/msg/receive.rs b/src/ability/msg/receive.rs index b0304b0b..19373830 100644 --- a/src/ability/msg/receive.rs +++ b/src/ability/msg/receive.rs @@ -112,7 +112,7 @@ impl CheckParents for Receive { fn check_parent(&self, proof: &Self::Parents) -> Result<(), Self::ParentError> { if let Some(from) = &self.from { if let Some(proof_from) = &proof.from { - if from != &url::Newtype(proof_from.clone()) { + if &from != &proof_from { return Err(()); } } @@ -138,7 +138,7 @@ impl TryFrom for Receive { #[derive(Debug, Clone, PartialEq)] pub struct Promised { - pub from: Option>>, + pub from: Option>, } impl From for arguments::Named { @@ -164,6 +164,31 @@ impl promise::Resolvable for Receive { type Promised = Promised; } +impl TryFrom> for Promised { + type Error = (); + + fn try_from(arguments: arguments::Named) -> Result { + let mut from = None; + + for (key, prom) in arguments { + match key.as_str() { + "from" => match Ipld::try_from(prom) { + Ok(Ipld::String(s)) => { + from = Some(promise::Resolves::from(Ok( + url::Newtype::parse(s.as_str()).map_err(|_| ())? + ))); + } + Err(pending) => from = Some(pending.into()), + _ => return Err(()), + }, + _ => return Err(()), + } + } + + Ok(Promised { from }) + } +} + impl From for arguments::Named { fn from(promised: Promised) -> Self { let mut args = arguments::Named::new(); diff --git a/src/ability/msg/send.rs b/src/ability/msg/send.rs index fd1ec63b..fc8a2f2c 100644 --- a/src/ability/msg/send.rs +++ b/src/ability/msg/send.rs @@ -6,12 +6,12 @@ use crate::{ invocation::promise, ipld, proof::{checkable::Checkable, parentful::Parentful, parents::CheckParents, same::CheckSame}, - url as url_newtype, }; -use libipld_core::{error::SerdeError, ipld::Ipld, serde as ipld_serde}; -use serde::{de::DeserializeOwned, Deserialize, Serialize}; +use libipld_core::ipld::Ipld; +use serde::{Deserialize, Serialize}; use std::collections::BTreeMap; -use url::Url; +// use url::Url; +use crate::url; #[cfg_attr(doc, aquamarine::aquamarine)] /// The executable/dispatchable variant of the `msg/send` ability. @@ -44,13 +44,13 @@ use url::Url; #[serde(deny_unknown_fields)] pub struct Ready { /// The recipient of the message - pub to: Url, + pub to: url::Newtype, /// The sender address of the message /// /// This *may* be a URL (such as an email address). /// If provided, the `subject` must have the right to send from this address. - pub from: Url, + pub from: url::Newtype, /// The main body of the message pub message: String, @@ -86,13 +86,13 @@ pub struct Ready { #[serde(deny_unknown_fields)] pub struct Builder { /// The recipient of the message - pub to: Option, + pub to: Option, /// The sender address of the message /// /// This *may* be a URL (such as an email address). /// If provided, the `subject` must have the right to send from this address. - pub from: Option, + pub from: Option, /// The main body of the message pub message: Option, @@ -131,13 +131,13 @@ pub struct Builder { #[serde(deny_unknown_fields)] pub struct Promised { /// The recipient of the message - pub to: promise::Resolves, + pub to: promise::Resolves, /// The sender address of the message /// /// This *may* be a URL (such as an email address). /// If provided, the `subject` must have the right to send from this address. - pub from: promise::Resolves, + pub from: promise::Resolves, /// The main body of the message pub message: promise::Resolves, @@ -151,6 +151,51 @@ impl promise::Resolvable for Ready { type Promised = Promised; } +impl TryFrom> for Promised { + type Error = (); + + fn try_from(args: arguments::Named) -> Result { + let mut to = None; + let mut from = None; + let mut message = None; + + for (key, prom) in args.0 { + match key.as_str() { + "to" => match Ipld::try_from(prom) { + Ok(Ipld::String(s)) => { + to = Some(promise::Resolves::from(Ok( + url::Newtype::parse(s.as_str()).map_err(|_| ())? + ))); + } + Err(pending) => to = Some(pending.into()), + _ => return Err(()), + }, + "from" => match Ipld::try_from(prom) { + Ok(Ipld::String(s)) => { + from = Some(promise::Resolves::from(Ok( + url::Newtype::parse(s.as_str()).map_err(|_| ())? + ))); + } + Err(pending) => from = Some(pending.into()), + _ => return Err(()), + }, + "message" => match Ipld::try_from(prom) { + Ok(Ipld::String(s)) => message = Some(promise::Resolves::from(Ok(s))), + Err(pending) => to = Some(pending.into()), + _ => return Err(()), + }, + _ => return Err(()), + } + } + + Ok(Promised { + to: to.ok_or(())?, + from: from.ok_or(())?, + message: message.ok_or(())?, + }) + } +} + impl From for arguments::Named { fn from(b: Builder) -> Self { let mut btree = BTreeMap::new(); @@ -167,8 +212,8 @@ impl From for arguments::Named { impl From for arguments::Named { fn from(p: Promised) -> Self { arguments::Named::from_iter([ - ("to".into(), p.to.map(url_newtype::Newtype).into()), - ("from".into(), p.from.map(String::from).into()), + ("to".into(), p.to.into()), + ("from".into(), p.from.into()), ("message".into(), p.message.into()), ]) } @@ -234,14 +279,14 @@ impl TryFrom> for Builder { "to" => { // FIXME extract this common pattern if let Ipld::String(s) = ipld { - to = Some(Url::parse(s.as_str()).map_err(|_| ())?); + to = Some(url::Newtype::parse(s.as_str()).map_err(|_| ())?); } else { return Err(()); } } "from" => { if let Ipld::String(s) = ipld { - from = Some(Url::parse(s.as_str()).map_err(|_| ())?); + from = Some(url::Newtype::parse(s.as_str()).map_err(|_| ())?); } else { return Err(()); } diff --git a/src/ability/parse.rs b/src/ability/parse.rs new file mode 100644 index 00000000..c5212977 --- /dev/null +++ b/src/ability/parse.rs @@ -0,0 +1,70 @@ +use super::command::Command; +use crate::{ability::arguments, ipld}; +use libipld_core::ipld::Ipld; +use std::fmt; +use thiserror::Error; + +// FIXME definitely needs a better name +// pub trait ParseAbility: TryFrom> { +pub trait ParseAbility: Sized { + type ArgsErr: fmt::Debug; + + fn try_parse( + cmd: &str, + args: arguments::Named, + ) -> Result>; +} + +#[derive(Debug, Clone, Error)] +pub enum ParseAbilityError { + #[error("Unknown command: {0}")] + UnknownCommand(String), + + #[error(transparent)] + InvalidArgs(#[from] E), +} + +impl>> ParseAbility for T +where + >>::Error: fmt::Debug, +{ + type ArgsErr = >>::Error; + + fn try_parse( + cmd: &str, + args: arguments::Named, + ) -> Result>>::Error>> { + if cmd != T::COMMAND { + return Err(ParseAbilityError::UnknownCommand(cmd.to_string())); + } + + Self::try_from(args).map_err(ParseAbilityError::InvalidArgs) + } +} + +pub trait ParsePromised: Sized { + type PromisedArgsError; + + fn try_parse_promised( + cmd: &str, + args: arguments::Named, + ) -> Result>; +} + +impl>> ParsePromised for T +where + >>::Error: fmt::Debug, +{ + type PromisedArgsError = >>::Error; + + fn try_parse_promised( + cmd: &str, + args: arguments::Named, + ) -> Result> { + if cmd != T::COMMAND { + return Err(ParseAbilityError::UnknownCommand(cmd.to_string())); + } + + Self::try_from(args).map_err(ParseAbilityError::InvalidArgs) + } +} diff --git a/src/ability/preset.rs b/src/ability/preset.rs index c9efe787..2443e26a 100644 --- a/src/ability/preset.rs +++ b/src/ability/preset.rs @@ -2,7 +2,8 @@ use super::{crud, msg, wasm}; use crate::{ ability::{ arguments, - command::{ParseAbility, ParseAbilityError, ToCommand}, + command::ToCommand, + parse::{ParseAbility, ParseAbilityError, ParsePromised}, }, delegation::Delegable, invocation::promise::Resolvable, @@ -140,6 +141,35 @@ impl ToCommand for Promised { } } +impl ParsePromised for Promised { + type PromisedArgsError = (); + + fn try_parse_promised( + cmd: &str, + args: arguments::Named, + ) -> Result> { + match crud::Promised::try_parse_promised(cmd, args.clone()) { + Ok(promised) => return Ok(Promised::Crud(promised)), + Err(err) => return Err(err), + Err(ParseAbilityError::UnknownCommand(_)) => (), + } + + match msg::Promised::try_parse_promised(cmd, args.clone()) { + Ok(promised) => return Ok(Promised::Msg(promised)), + Err(err) => return Err(err), + Err(ParseAbilityError::UnknownCommand(_)) => (), + } + + match wasm::run::Promised::try_parse_promised(cmd, args) { + Ok(promised) => return Ok(Promised::Wasm(promised)), + Err(err) => return Err(err), + Err(ParseAbilityError::UnknownCommand(_)) => (), + } + + Err(ParseAbilityError::UnknownCommand(cmd.to_string())) + } +} + impl ParseAbility for Builder { type ArgsErr = (); diff --git a/src/ability/ucan/revoke.rs b/src/ability/ucan/revoke.rs index 1b078cea..fd2b29cf 100644 --- a/src/ability/ucan/revoke.rs +++ b/src/ability/ucan/revoke.rs @@ -11,10 +11,7 @@ use crate::{ }; use libipld_core::{cid::Cid, ipld::Ipld}; use serde::{Deserialize, Serialize}; -use std::{ - collections::{BTreeMap, BTreeSet}, - fmt::Debug, -}; +use std::{collections::BTreeMap, fmt::Debug}; /// The fully resolved variant: ready to execute. #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] @@ -133,6 +130,31 @@ pub struct Promised { pub ucan: promise::Resolves, } +impl TryFrom> for Promised { + type Error = (); + + fn try_from(arguments: arguments::Named) -> Result { + let mut ucan = None; + + for (k, prom) in arguments { + match k.as_str() { + "ucan" => match Ipld::try_from(prom) { + Ok(Ipld::Link(cid)) => { + ucan = Some(promise::Resolves::new(cid)); + } + Err(pending) => ucan = Some(pending.into()), + _ => return Err(()), + }, + _ => (), + } + } + + Ok(Promised { + ucan: ucan.ok_or(())?, + }) + } +} + impl From for Promised { fn from(r: Ready) -> Promised { Promised { diff --git a/src/ability/wasm/module.rs b/src/ability/wasm/module.rs index fc4b2ff7..18ed34db 100644 --- a/src/ability/wasm/module.rs +++ b/src/ability/wasm/module.rs @@ -1,6 +1,6 @@ //! Wasm module representations -use crate::{ability::arguments, ipld}; +use crate::ipld; use base64::{display::Base64Display, engine::general_purpose::STANDARD, Engine as _}; use libipld_core::{cid::Cid, error::SerdeError, ipld::Ipld, link::Link, serde as ipld_serde}; use serde::{Deserialize, Serialize}; @@ -27,6 +27,14 @@ impl From for Ipld { } } +impl TryFrom for Module { + type Error = SerdeError; + + fn try_from(nt: ipld::Newtype) -> Result { + ipld_serde::from_ipld(nt.0) + } +} + impl TryFrom for Module { type Error = SerdeError; diff --git a/src/ability/wasm/run.rs b/src/ability/wasm/run.rs index 64acf9f4..e9301a91 100644 --- a/src/ability/wasm/run.rs +++ b/src/ability/wasm/run.rs @@ -2,10 +2,7 @@ use super::module::Module; use crate::{ - ability::{ - arguments, - command::{Command, ParseAbility}, - }, + ability::{arguments, command::Command}, delegation::Delegable, invocation::promise, ipld, @@ -85,20 +82,6 @@ impl TryFrom> for Builder { } } -// impl TryFrom> for Builder { -// type Error = (); -// -// fn try_from(args: arguments::Named) -> Result { -// let ready = Ready::try_from(args)?; -// -// Ok(Builder { -// module: Some(ready.module), -// function: Some(ready.function), -// args: Some(ready.args), -// }) -// } -// } - impl promise::Resolvable for Ready { type Promised = Promised; } @@ -193,27 +176,50 @@ pub struct Promised { pub args: promise::Resolves>, } -// impl From for Promised { -// fn from(ready: Ready) -> Self { -// Promised { -// module: promise::Resolves::from(Ok(ready.module)), -// function: promise::Resolves::from(Ok(ready.function)), -// args: promise::Resolves::from(Ok(ready.args)), -// } -// } -// } - -// impl TryFrom for Ready { -// type Error = (); // FIXME -// -// fn try_from(promised: Promised) -> Result { -// Ok(Ready { -// module: promised.module.try_from().map_err(|_| ())?, -// function: promised.function.try_from().map_err(|_| ())?, -// args: promised.args.try_from().map_err(|_| ())?, -// }) -// } -// } +impl TryFrom> for Promised { + type Error = (); + + fn try_from(named: arguments::Named) -> Result { + let mut module = None; + let mut function = None; + let mut args = None; + + for (key, prom) in named { + match key.as_str() { + "module" => module = Some(prom.try_into().map_err(|_| ())?), + "function" => function = Some(prom.try_into().map_err(|_| ())?), + "args" => { + if let ipld::Promised::List(list) = prom.into() { + args = Some(promise::Resolves::new(list)); + } else { + return Err(()); + } + } + _ => return Err(()), + } + } + + Ok(Promised { + module: module.ok_or(())?, + function: function.ok_or(())?, + args: args.ok_or(())?, + }) + } +} + +impl From for Promised { + fn from(ready: Ready) -> Self { + Promised { + module: promise::Resolves::from(Ok(ready.module)), + function: promise::Resolves::from(Ok(ready.function)), + args: promise::Resolves::from(Ok(ready + .args + .iter() + .map(|ipld| ipld.clone().into()) + .collect())), + } + } +} impl From for arguments::Named { fn from(promised: Promised) -> Self { diff --git a/src/crypto/signature/envelope.rs b/src/crypto/signature/envelope.rs index a7b27c87..22576fc0 100644 --- a/src/crypto/signature/envelope.rs +++ b/src/crypto/signature/envelope.rs @@ -4,7 +4,7 @@ use crate::{ did::{Did, Verifiable}, }; use libipld_core::{ - cid::{Cid, CidGeneric}, + cid::Cid, codec::{Codec, Encode}, error::Result, ipld::Ipld, @@ -143,14 +143,14 @@ impl< /// # Exmaples /// /// FIXME - pub fn validate_signature(&self, varsig_header: &V) -> Result<(), ValidateError> + pub fn validate_signature(&self) -> Result<(), ValidateError> where T: Clone, Ipld: Encode, { let mut encoded = vec![]; let ipld: Ipld = BTreeMap::from_iter([(T::TAG.into(), self.payload.clone().into())]).into(); - ipld.encode(*varsig_header.codec(), &mut encoded) + ipld.encode(*self.varsig_header.codec(), &mut encoded) .map_err(ValidateError::PayloadEncodingError)?; self.verifier() @@ -169,7 +169,7 @@ impl< self.encode(codec, &mut ipld_buffer)?; let multihash = Code::Sha2_256.digest(&ipld_buffer); - Ok(CidGeneric::new_v1( + Ok(Cid::new_v1( self.varsig_header.codec().clone().into(), multihash, )) diff --git a/src/delegation.rs b/src/delegation.rs index 91d8790d..5d81718e 100644 --- a/src/delegation.rs +++ b/src/delegation.rs @@ -32,10 +32,9 @@ use crate::{ }; use condition::Condition; use libipld_core::{ - cid::{Cid, CidGeneric}, + cid::Cid, codec::{Codec, Encode}, ipld::Ipld, - multihash::{Code, MultihashDigest}, }; use std::collections::BTreeMap; use web_time::SystemTime; @@ -158,7 +157,7 @@ impl, Enc: Codec + Into + Payload: Clone, Ipld: Encode, { - self.0.validate_signature(self.varsig_header()) + self.0.validate_signature() } pub fn try_sign( diff --git a/src/delegation/agent.rs b/src/delegation/agent.rs index 160bc1bb..389de622 100644 --- a/src/delegation/agent.rs +++ b/src/delegation/agent.rs @@ -37,9 +37,6 @@ pub struct Agent< _marker: PhantomData<(B, C, V, Enc)>, } -// FIXME show example of multiple hierarchies of "all things accepted" -// delegating down to inner versions of this - impl< 'a, B: Checkable + Clone, diff --git a/src/delegation/condition/traits.rs b/src/delegation/condition/traits.rs index 632d5615..1c7b8f90 100644 --- a/src/delegation/condition/traits.rs +++ b/src/delegation/condition/traits.rs @@ -2,9 +2,10 @@ use crate::ability::arguments; use libipld_core::ipld::Ipld; +use std::fmt; /// A trait for conditions that can be run on named IPLD arguments. -pub trait Condition { +pub trait Condition: fmt::Debug { /// Check that some condition is met on named IPLD arguments. fn validate(&self, args: &arguments::Named) -> bool; } diff --git a/src/delegation/delegable.rs b/src/delegation/delegable.rs index 78ea8853..a8929ab1 100644 --- a/src/delegation/delegable.rs +++ b/src/delegation/delegable.rs @@ -1,7 +1,8 @@ use crate::{ ability::{ arguments, - command::{ParseAbility, ParseAbilityError, ToCommand}, + command::ToCommand, + parse::{ParseAbility, ParseAbilityError}, }, proof::checkable::Checkable, }; @@ -48,5 +49,3 @@ pub trait Delegable: Sized { builder.try_into().map_err(|err| todo!()) } } - -// FIXME ParseAbility + ToCommand + Checkable = Ability? diff --git a/src/delegation/payload.rs b/src/delegation/payload.rs index 4361382b..bd557979 100644 --- a/src/delegation/payload.rs +++ b/src/delegation/payload.rs @@ -2,7 +2,8 @@ use super::condition::Condition; use crate::{ ability::{ arguments, - command::{Command, ParseAbility, ToCommand}, + command::{Command, ToCommand}, + parse::ParseAbility, }, capsule::Capsule, crypto::Nonce, @@ -379,7 +380,7 @@ impl>, C: Condition, DID: Did> Payloa ) -> Result<(), ValidationError<::Error, C>> where T: Clone, - C: fmt::Debug + Clone, + C: Clone, DID: Clone, T::Hierarchy: Clone + Into>, { @@ -432,7 +433,7 @@ impl Acc { now: &SystemTime, ) -> Result::Error, C>> where - C: fmt::Debug + Clone, + C: Clone, H: Prove + Clone + Into>, { if self.issuer != proof.audience { diff --git a/src/did/key/verifier.rs b/src/did/key/verifier.rs index 743cff94..d645c559 100644 --- a/src/did/key/verifier.rs +++ b/src/did/key/verifier.rs @@ -227,7 +227,7 @@ impl FromStr for Verifier { return Ok(Verifier::Rs512(rs512::VerifyingKey(vk))); } - _ => todo!(), + _ => return Err("invalid did:key".to_string()), }, ([0xeb, 0x01], pk_bytes) => match pk_bytes.len() { 48 => { diff --git a/src/invocation.rs b/src/invocation.rs index 1e75b4a2..2aa36b8e 100644 --- a/src/invocation.rs +++ b/src/invocation.rs @@ -139,7 +139,7 @@ where pub fn cid(&self) -> Result where - signature::Envelope, DID, V, Enc>: Clone + Encode, + signature::Envelope, DID, V, Enc>: Clone, Ipld: Encode, { self.0.cid() @@ -156,6 +156,13 @@ where let envelope = signature::Envelope::try_sign(signer, varsig_header, payload)?; Ok(Invocation(envelope)) } + + pub fn validate_signature(&self) -> Result<(), signature::ValidateError> + where + Payload: Clone, + { + self.0.validate_signature() + } } impl, Enc: Codec + TryFrom + Into> diff --git a/src/invocation/agent.rs b/src/invocation/agent.rs index f4192fe9..9bc24aab 100644 --- a/src/invocation/agent.rs +++ b/src/invocation/agent.rs @@ -1,23 +1,27 @@ use super::{payload::Payload, promise::Resolvable, store::Store, Invocation}; use crate::{ - ability::{arguments, ucan}, - crypto::{signature as ucan_signature, varsig, Nonce}, + ability::{ + parse::{ParseAbility, ParseAbilityError, ParsePromised}, + ucan, + }, + crypto::{signature, varsig, Nonce}, delegation, delegation::{condition::Condition, Delegable}, - did::{Did, Verifiable}, + did::Did, invocation::promise, proof::{checkable::Checkable, prove::Prove}, time::Timestamp, }; -use libipld_cbor::DagCborCodec; use libipld_core::{ - cid::{Cid, CidGeneric}, + cid::Cid, codec::{Codec, Encode}, ipld::Ipld, - multihash::{Code, MultihashDigest}, }; -use signature; -use std::{collections::BTreeMap, fmt, marker::PhantomData}; +use std::{ + collections::{BTreeMap, BTreeSet}, + fmt, + marker::PhantomData, +}; use thiserror::Error; use web_time::SystemTime; @@ -43,21 +47,19 @@ pub struct Agent< marker: PhantomData<(T, C, V, Enc)>, } -impl< - 'a, - T: Resolvable + Delegable + Clone, - C: Condition, - DID: Did + Clone, - S: Store, - P: promise::Store, - D: delegation::store::Store, - V: varsig::Header, - Enc: Codec + Into + TryFrom, - > Agent<'a, T, C, DID, S, P, D, V, Enc> +impl<'a, T, C, DID, S, P, D, V, Enc> Agent<'a, T, C, DID, S, P, D, V, Enc> where T::Promised: Clone, Ipld: Encode, delegation::Payload<::Hierarchy, C, DID>: Clone, + T: Resolvable + Delegable + Clone, + C: Condition, + DID: Did + Clone, + S: Store, + P: promise::Store, + D: delegation::store::Store, + V: varsig::Header, + Enc: Codec + Into + TryFrom, { pub fn new( did: &'a DID, @@ -80,25 +82,65 @@ where &mut self, audience: Option<&DID>, subject: &DID, - ability: T::Promised, // FIXME give them an enum for promised or not probs doens't matter? + ability: T, + metadata: BTreeMap, + cause: Option, + expiration: Option, + issued_at: Option, + now: SystemTime, + varsig_header: V, + ) -> Result< + Invocation, + InvokeError< + D::DelegationStoreError, + ParseAbilityError<<::Builder as ParseAbility>::ArgsErr>, + >, + > + where + <::Promised as ParsePromised>::PromisedArgsError: fmt::Debug, + { + self.invoke_promise( + audience, + subject, + Resolvable::into_promised(ability), + metadata, + cause, + expiration, + issued_at, + now, + varsig_header, + ) + } + + pub fn invoke_promise( + &mut self, + audience: Option<&DID>, + subject: &DID, + ability: T::Promised, metadata: BTreeMap, cause: Option, expiration: Option, issued_at: Option, now: SystemTime, varsig_header: V, - // FIXME err type - ) -> Result, ()> { + ) -> Result< + Invocation, + InvokeError< + D::DelegationStoreError, + ParseAbilityError<<::Builder as ParseAbility>::ArgsErr>, + >, + > { let proofs = self .delegation_store .get_chain( self.did, subject, - &::try_to_builder(ability.clone()).map_err(|_| ())?, + &::try_to_builder(ability.clone()) + .map_err(InvokeError::PromiseResolveError)?, vec![], now, ) - .map_err(|_| ())? + .map_err(InvokeError::DelegationStoreError)? .map(|chain| chain.map(|(cid, _)| cid).into()) .unwrap_or(vec![]); @@ -117,56 +159,48 @@ where issued_at, }; - Ok(Invocation::try_sign(self.signer, varsig_header, payload).map_err(|_| ())?) + Ok(Invocation::try_sign(self.signer, varsig_header, payload) + .map_err(InvokeError::SignError)?) } pub fn receive( &mut self, promised: Invocation, now: &SystemTime, - ) -> Result>, ReceiveError> + ) -> Result< + Recipient>, + ReceiveError, + > where - T::Builder: Into> + Clone, - Ipld: Encode, - C: fmt::Debug + Clone, - ::Hierarchy: Clone + Into>, + Enc: From + Into, + T::Builder: Clone + Encode, + C: Clone, + ::Hierarchy: Clone, Invocation: Clone, <<::Builder as Checkable>::Hierarchy as Prove>::Error: fmt::Debug,

>::PromiseStoreError: fmt::Debug, - ucan_signature::Envelope, DID, V, Enc>: Clone, - ucan_signature::Envelope, DID, V, Enc>: Clone, + signature::Envelope, DID, V, Enc>: Clone, + ::Promised, DID, V, Enc>>::InvocationStoreError: fmt::Debug, { - // FIXME You know... store it - // also: Envelops hsould have a cid() method - // self.invocation_store - // .put(promised.cid().clone(), promised.clone()) - // .map_err(ReceiveError::PromiseStoreError)?; - - let mut buffer = vec![]; - Ipld::from(promised.clone()) - .encode(*promised.varsig_header().codec(), &mut buffer) - .map_err(ReceiveError::EncodingError)?; - - let multihash = Code::Sha2_256.digest(buffer.as_slice()); - let cid: Cid = CidGeneric::new_v1(DagCborCodec.into(), multihash); - - let mut encoded = vec![]; - Ipld::from(promised.payload().clone()) - .encode(*promised.0.varsig_header.codec(), &mut encoded) - .map_err(ReceiveError::EncodingError)?; - - promised - .verifier() - .verify(&encoded, &promised.signature()) + let cid: Cid = promised.cid().map_err(ReceiveError::EncodingError)?; + let _ = promised + .validate_signature() .map_err(ReceiveError::SigVerifyError)?; + self.invocation_store + .put(cid.clone(), promised.clone()) + .map_err(ReceiveError::InvocationStoreError)?; + let resolved_ability: T = match Resolvable::try_resolve(promised.ability().clone()) { Ok(resolved) => resolved, - Err(_) => { - let waiting_on_cid = todo!(); + Err(cant_resolve) => { + let waiting_on: BTreeSet = T::get_all_pending(cant_resolve.promised); self.unresolved_promise_index - .put(promised.cid()?, vec![waiting_on_cid]) + .put( + promised.cid()?, + waiting_on.into_iter().collect::>(), + ) .map_err(ReceiveError::PromiseStoreError)?; return Ok(Recipient::Unresolved(cid)); @@ -253,22 +287,33 @@ pub enum Recipient { } #[derive(Debug, Error)] -pub enum ReceiveError, DID: Did, C: fmt::Debug, D> -where +pub enum ReceiveError< + T: Resolvable, + P: promise::Store, + DID: Did, + C: fmt::Debug, + D, + S: Store, + V: varsig::Header, + Enc: Codec + From + Into, +> where delegation::ValidationError< <<::Builder as Checkable>::Hierarchy as Prove>::Error, C, >: fmt::Debug,

>::PromiseStoreError: fmt::Debug, + ::Promised, DID, V, Enc>>::InvocationStoreError: fmt::Debug, { #[error("encoding error: {0}")] EncodingError(#[from] libipld_core::error::Error), - #[error("multihash error: {0}")] - MultihashError(#[from] libipld_core::multihash::Error), - #[error("signature verification error: {0}")] - SigVerifyError(#[from] signature::Error), + SigVerifyError(#[from] signature::ValidateError), + + #[error("invocation store error: {0}")] + InvocationStoreError( + #[source] ::Promised, DID, V, Enc>>::InvocationStoreError, + ), #[error("promise store error: {0}")] PromiseStoreError(#[source]

>::PromiseStoreError), @@ -285,3 +330,15 @@ where >, ), } + +#[derive(Debug, Error)] +pub enum InvokeError { + #[error("delegation store error: {0}")] + DelegationStoreError(#[source] D), + + #[error("promise store error: {0}")] + SignError(#[source] signature::SignError), + + #[error("promise store error: {0}")] + PromiseResolveError(#[source] ArgsErr), +} diff --git a/src/invocation/payload.rs b/src/invocation/payload.rs index 3eca6d1a..fa0e9aea 100644 --- a/src/invocation/payload.rs +++ b/src/invocation/payload.rs @@ -1,9 +1,6 @@ use super::promise::Resolvable; use crate::{ - ability::{ - arguments, - command::{ParseAbility, ToCommand}, - }, + ability::{arguments, command::ToCommand, parse::ParseAbility}, capsule::Capsule, crypto::Nonce, delegation::{self, condition::Condition, Delegable, ValidationError}, diff --git a/src/invocation/promise.rs b/src/invocation/promise.rs index 71a8b287..4bc943a2 100644 --- a/src/invocation/promise.rs +++ b/src/invocation/promise.rs @@ -14,7 +14,7 @@ pub use any::PromiseAny; pub use err::PromiseErr; pub use ok::PromiseOk; pub use pending::Pending; -pub use resolvable::Resolvable; +pub use resolvable::*; pub use resolves::Resolves; pub use store::Store; diff --git a/src/invocation/promise/pending.rs b/src/invocation/promise/pending.rs index 8ac83db2..1c56e48c 100644 --- a/src/invocation/promise/pending.rs +++ b/src/invocation/promise/pending.rs @@ -1,7 +1,7 @@ use libipld_core::cid::Cid; // AKA Selector -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Clone, PartialEq, Eq)] pub enum Pending { Ok(Cid), Err(Cid), diff --git a/src/invocation/promise/resolvable.rs b/src/invocation/promise/resolvable.rs index 32e1be35..17c9cd0d 100644 --- a/src/invocation/promise/resolvable.rs +++ b/src/invocation/promise/resolvable.rs @@ -1,7 +1,8 @@ use crate::{ ability::{ arguments, - command::{ParseAbility, ToCommand}, + command::ToCommand, + parse::{ParseAbility, ParseAbilityError, ParsePromised}, }, delegation::Delegable, invocation::promise::Pending, @@ -25,8 +26,22 @@ pub trait Resolvable: Delegable { /// be a promise. /// /// [PromiseIpld]: crate::ipld::Promised - // type Promised: Into + Into>; - type Promised: Into> + ToCommand; + type Promised: ToCommand + + ParsePromised // TryFrom> + + Into>; + + fn into_promised(self) -> Self::Promised + where + ::PromisedArgsError: fmt::Debug, + { + // FIXME In no way efficient... override where possible, or just cut the impl + let builder = Self::Builder::from(self); + let cmd = &builder.to_command(); + let named_ipld: arguments::Named = builder.into(); + let promised_ipld: arguments::Named = named_ipld.into(); + ::Promised::try_parse_promised(cmd, promised_ipld) + .expect("promise to always be possible from a ready ability") + } /// Attempt to resolve the [`Self::Promised`]. fn try_resolve(promised: Self::Promised) -> Result> @@ -35,20 +50,20 @@ pub trait Resolvable: Delegable { { let ipld_promise: arguments::Named = promised.clone().into(); match arguments::Named::::try_from(ipld_promise) { - Err(_) => Err(CantResolve { + Err(pending) => Err(CantResolve { promised, - reason: todo!(), // ParseAbility::ArgsErr::ExpectedMap, + reason: ResolveError::StillWaiting(pending), }), Ok(named) => { let builder = Self::Builder::try_parse(promised.to_command().as_str(), named) - .map_err(|reason| CantResolve { + .map_err(|_reason| CantResolve { promised: promised.clone(), - reason: todo!(), + reason: ResolveError::ConversionError, })?; builder.try_into().map_err(|_reason| CantResolve { promised, - reason: todo!(), + reason: ResolveError::ConversionError, }) } } @@ -68,7 +83,12 @@ pub trait Resolvable: Delegable { }) } - fn try_to_builder(promised: Self::Promised) -> Result { + fn try_to_builder( + promised: Self::Promised, + ) -> Result< + Self::Builder, + ParseAbilityError<<::Builder as ParseAbility>::ArgsErr>, + > { let cmd = promised.to_command(); let ipld_promise: arguments::Named = promised.into(); @@ -86,14 +106,14 @@ pub trait Resolvable: Delegable { acc }); - Self::Builder::try_parse(&cmd, named).map_err(|_| ()) + Self::Builder::try_parse(&cmd, named) } } -#[derive(Error)] +#[derive(Error, Clone)] pub struct CantResolve { pub promised: S::Promised, - pub reason: <::Builder as ParseAbility>::ArgsErr, + pub reason: ResolveError, } impl fmt::Debug for CantResolve @@ -109,3 +129,12 @@ where .finish() } } + +#[derive(Error, PartialEq, Eq, Clone, Debug)] +pub enum ResolveError { + #[error("The promise is still has arguments waiting to be resolved")] + StillWaiting(Pending), + + #[error("The resolved promise was unable to reify a Builder")] + ConversionError, +} diff --git a/src/invocation/promise/resolves.rs b/src/invocation/promise/resolves.rs index 3ee866d4..1dbec8ca 100644 --- a/src/invocation/promise/resolves.rs +++ b/src/invocation/promise/resolves.rs @@ -1,4 +1,4 @@ -use super::{Promise, PromiseAny, PromiseErr, PromiseOk}; +use super::{Pending, Promise, PromiseAny, PromiseErr, PromiseOk}; use libipld_core::ipld::Ipld; use serde::{Deserialize, Serialize}; use std::fmt; @@ -228,6 +228,16 @@ impl Resolves { } } +impl From for Resolves { + fn from(pending: Pending) -> Self { + match pending { + Pending::Ok(cid) => Resolves::Ok(PromiseOk::Pending(cid)), + Pending::Err(cid) => Resolves::Err(PromiseErr::Pending(cid)), + Pending::Any(cid) => Resolves::Ok(PromiseOk::Pending(cid)), // FIXME + } + } +} + impl> TryFrom for Resolves { type Error = Ipld; diff --git a/src/ipld/newtype.rs b/src/ipld/newtype.rs index fea821f4..88132b2b 100644 --- a/src/ipld/newtype.rs +++ b/src/ipld/newtype.rs @@ -49,6 +49,17 @@ impl From for Newtype { } } +impl TryFrom for String { + type Error = (); + + fn try_from(nt: Newtype) -> Result { + match nt.0 { + Ipld::String(s) => Ok(s), + _ => Err(()), + } + } +} + impl From for Ipld { fn from(wrapped: Newtype) -> Self { wrapped.0 diff --git a/src/ipld/promised.rs b/src/ipld/promised.rs index 8071181d..589fe934 100644 --- a/src/ipld/promised.rs +++ b/src/ipld/promised.rs @@ -2,7 +2,7 @@ use crate::{ ability::arguments, invocation::promise::{Pending, Promise, PromiseAny, PromiseErr, PromiseOk, Resolves}, - url, + ipld, url, }; use enum_as_inner::EnumAsInner; use libipld_core::{cid::Cid, ipld::Ipld}; @@ -67,11 +67,26 @@ impl From for Promised { Ipld::Link(cid) => Promised::Link(cid), Ipld::List(list) => Promised::List(list.into_iter().map(Into::into).collect()), Ipld::Map(map) => { - let mut promised_map = BTreeMap::new(); - for (k, v) in map { - promised_map.insert(k, v.into()); + if map.len() == 1 { + if let Some((k, Ipld::Link(cid))) = map.first_key_value() { + return match k.as_str() { + "await/ok" => Promised::WaitOk(*cid), + "await/err" => Promised::WaitErr(*cid), + "await/*" => Promised::WaitAny(*cid), + _ => Promised::Map(BTreeMap::from_iter([( + k.to_string(), + Promised::Link(*cid), + )])), + }; + } } - Promised::Map(promised_map) + + let map = map.into_iter().fold(BTreeMap::new(), |mut acc, (k, v)| { + acc.insert(k, v.into()); + acc + }); + + Promised::Map(map) } } } @@ -148,6 +163,66 @@ impl From> for Promised { } } +impl> TryFrom for Resolves { + type Error = (); + + fn try_from(promised: Promised) -> Result, Self::Error> { + match promised { + Promised::WaitOk(cid) => Ok(Resolves::Ok(PromiseOk::Pending(cid))), + Promised::WaitErr(cid) => Ok(Resolves::Err(PromiseErr::Pending(cid))), + Promised::WaitAny(cid) => Ok(Resolves::Ok(PromiseOk::Pending(cid))), // FIXME + + Promised::Null => Ok(Resolves::Ok(PromiseOk::Fulfilled( + T::try_from(Ipld::Null.into()).map_err(|_| ())?, + ))), + Promised::Bool(b) => Ok(Resolves::Ok(PromiseOk::Fulfilled( + T::try_from(Ipld::Bool(b).into()).map_err(|_| ())?, + ))), + Promised::Integer(i) => Ok(Resolves::Ok(PromiseOk::Fulfilled( + T::try_from(Ipld::Integer(i).into()).map_err(|_| ())?, + ))), + Promised::Float(f) => Ok(Resolves::Ok(PromiseOk::Fulfilled( + T::try_from(Ipld::Float(f).into()).map_err(|_| ())?, + ))), + Promised::String(s) => Ok(Resolves::Ok(PromiseOk::Fulfilled( + T::try_from(Ipld::String(s).into()).map_err(|_| ())?, + ))), + Promised::Bytes(b) => Ok(Resolves::Ok(PromiseOk::Fulfilled( + T::try_from(Ipld::Bytes(b).into()).map_err(|_| ())?, + ))), + Promised::Link(cid) => Ok(Resolves::Ok(PromiseOk::Fulfilled( + T::try_from(Ipld::Link(cid).into()).map_err(|_| ())?, + ))), + + Promised::List(list) => { + let vec: Vec = list.into_iter().try_fold(vec![], |mut acc, promised| { + let ipld: Ipld = promised.try_into().map_err(|_| ())?; + acc.push(ipld); + Ok(acc) + })?; + + Ok(Resolves::Ok(PromiseOk::Fulfilled( + ipld::Newtype(Ipld::List(vec)).try_into().map_err(|_| ())?, + ))) + } + + Promised::Map(map) => { + let btree: BTreeMap = + map.into_iter() + .try_fold(BTreeMap::new(), |mut acc, (k, v)| { + let ipld: Ipld = v.try_into().map_err(|_| ())?; + acc.insert(k, ipld); + Ok(acc) + })?; + + Ok(Resolves::Ok(PromiseOk::Fulfilled( + ipld::Newtype(Ipld::Map(btree)).try_into().map_err(|_| ())?, + ))) + } + } + } +} + impl From> for Promised where Promised: From, @@ -197,6 +272,18 @@ impl From<::url::Url> for Promised { } } +impl TryFrom for url::Newtype { + type Error = (); + + fn try_from(promised: Promised) -> Result { + match promised { + Promised::String(s) => Ok(url::Newtype(::url::Url::parse(&s).map_err(|_| ())?)), + // FIXME Promised::Link(cid) => Ok(url::Newtype::from(cid)), + _ => Err(()), + } + } +} + impl From for Promised { fn from(nt: url::Newtype) -> Promised { nt.0.into() @@ -304,7 +391,7 @@ impl<'a> Iterator for PostOrderIpldIter<'a> { match self.inbound.pop() { None => return self.outbound.pop(), Some(ref map @ Promised::Map(ref btree)) => { - self.outbound.push(map.clone()); + self.outbound.push(map); for node in btree.values() { self.inbound.push(node); @@ -312,7 +399,7 @@ impl<'a> Iterator for PostOrderIpldIter<'a> { } Some(ref list @ Promised::List(ref vector)) => { - self.outbound.push(list.clone()); + self.outbound.push(list); for node in vector { self.inbound.push(node); diff --git a/src/lib.rs b/src/lib.rs index bb7edaf4..694c91aa 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -3,11 +3,10 @@ missing_debug_implementations, future_incompatible, let_underscore, - // FIXME missing_docs, + missing_docs, rust_2021_compatibility, - nonstandard_style, + nonstandard_style )] -// FIXME consider removing for Prove #![deny(unreachable_pub)] //! ucan @@ -35,3 +34,10 @@ pub mod test_utils; pub use delegation::Delegation; pub use invocation::Invocation; pub use receipt::Receipt; + +///////////// +// FIXME s // +///////////// + +// show example of multiple hierarchies of "all things accepted" +// delegating down to inner versions of this diff --git a/src/proof.rs b/src/proof.rs index a80cd781..82092cb5 100644 --- a/src/proof.rs +++ b/src/proof.rs @@ -7,7 +7,6 @@ pub mod parentless; pub mod parents; pub mod prove; pub mod same; -pub mod util; // NOTE must remain *un*exported! pub(super) mod internal; diff --git a/src/proof/parentless.rs b/src/proof/parentless.rs index 5dafb16a..c9b13264 100644 --- a/src/proof/parentless.rs +++ b/src/proof/parentless.rs @@ -34,7 +34,7 @@ impl From for Parentless { impl>> From> for arguments::Named { fn from(parentless: Parentless) -> Self { match parentless { - Parentless::Any => todo!(), + Parentless::Any => arguments::Named::new(), Parentless::This(this) => this.into(), } } diff --git a/src/proof/util.rs b/src/proof/util.rs deleted file mode 100644 index e1046e34..00000000 --- a/src/proof/util.rs +++ /dev/null @@ -1,15 +0,0 @@ -use super::error::OptionalFieldError; - -// pub fn check_optional( -// target: Option, -// proof: Option, -// ) -> Result<(), OptionalFieldError> { -// if let Some(target_value) = target { -// let proof_value = proof.ok_or(OptionalFieldError::Missing)?; -// if target_value != proof_value { -// return Err(OptionalFieldError::Unequal); -// } -// } -// -// Ok(()) -// } diff --git a/src/reader/generic.rs b/src/reader/generic.rs index 10cfa815..680ead6c 100644 --- a/src/reader/generic.rs +++ b/src/reader/generic.rs @@ -1,7 +1,4 @@ -use crate::ability::{ - arguments, - command::{ParseAbility, ParseAbilityError, ToCommand}, -}; +use crate::ability::{arguments, command::ToCommand, parse::ParseAbilityError}; use libipld_core::ipld::Ipld; /// A struct that attaches an ambient environment to a value. diff --git a/src/url.rs b/src/url.rs index 22c6915b..efa2f68f 100644 --- a/src/url.rs +++ b/src/url.rs @@ -1,7 +1,9 @@ //! URL utilities. +use crate::proof::same::CheckSame; use libipld_core::ipld::Ipld; use serde::{Deserialize, Serialize}; +use std::fmt; use thiserror::Error; use url::Url; @@ -36,6 +38,30 @@ use proptest::prelude::*; #[serde(transparent)] pub struct Newtype(pub Url); +impl Newtype { + pub fn parse(s: &str) -> Result { + Ok(Newtype(Url::parse(s)?)) + } +} + +impl fmt::Display for Newtype { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + self.0.fmt(f) + } +} + +impl CheckSame for Newtype { + type Error = (); + + fn check_same(&self, other: &Self) -> Result<(), Self::Error> { + if self == other { + Ok(()) + } else { + Err(()) + } + } +} + impl From for Ipld { fn from(newtype: Newtype) -> Self { newtype.into() From 8522b55a0fd1e95528593796f57a9d1c16ee483f Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Fri, 23 Feb 2024 22:59:56 -0800 Subject: [PATCH 101/188] In theory, that's fully updated --- src/ability/arguments.rs | 32 +- src/ability/crud.rs | 198 ++++++------- src/ability/crud/create.rs | 102 +++---- src/ability/crud/destroy.rs | 102 +++---- src/ability/crud/read.rs | 78 ++--- src/ability/crud/update.rs | 129 ++++---- src/ability/dynamic.rs | 44 +-- src/ability/msg.rs | 141 +-------- src/ability/msg/receive.rs | 64 ++-- src/ability/msg/send.rs | 213 +++----------- src/ability/preset.rs | 143 ++------- src/ability/ucan/revoke.rs | 144 ++++----- src/ability/wasm/run.rs | 105 +------ src/delegation.rs | 106 +++---- src/delegation/agent.rs | 55 ++-- src/delegation/delegable.rs | 102 +++---- src/delegation/payload.rs | 421 ++------------------------- src/delegation/store/memory.rs | 64 ++-- src/delegation/store/traits.rs | 27 +- src/invocation.rs | 2 +- src/invocation/agent.rs | 110 ++++--- src/invocation/payload.rs | 117 +++++--- src/invocation/promise/resolvable.rs | 65 +---- src/lib.rs | 2 +- src/proof.rs | 16 +- src/url.rs | 24 +- 26 files changed, 883 insertions(+), 1723 deletions(-) diff --git a/src/ability/arguments.rs b/src/ability/arguments.rs index 0e96c6ef..6137a358 100644 --- a/src/ability/arguments.rs +++ b/src/ability/arguments.rs @@ -9,19 +9,19 @@ use libipld_core::ipld::Ipld; use std::collections::BTreeMap; // FIXME move under invoc::promise? -pub type Promised = Resolves>; - -impl Promised { - pub fn try_resolve_option(self) -> Option> { - match self.try_resolve() { - Err(_) => None, - Ok(named_promises) => named_promises - .iter() - .try_fold(BTreeMap::new(), |mut map, (k, v)| { - map.insert(k.clone(), Ipld::try_from(v.clone()).ok()?); - Some(map) - }) - .map(Named), - } - } -} +// pub type Promised = Resolves>; +// +// impl Promised { +// pub fn try_resolve_option(self) -> Option> { +// match self.try_resolve() { +// Err(_) => None, +// Ok(named_promises) => named_promises +// .iter() +// .try_fold(BTreeMap::new(), |mut map, (k, v)| { +// map.insert(k.clone(), Ipld::try_from(v.clone()).ok()?); +// Some(map) +// }) +// .map(Named), +// } +// } +// } diff --git a/src/ability/crud.rs b/src/ability/crud.rs index 1e1ab5df..57df163c 100644 --- a/src/ability/crud.rs +++ b/src/ability/crud.rs @@ -37,19 +37,19 @@ //! [CRUD]: https://en.wikipedia.org/wiki/Create,_read,_update_and_delete //! [`Did`]: crate::did::Did -mod any; -mod mutate; -mod parents; +// mod any; +// mod mutate; +// mod parents; pub mod create; pub mod destroy; -pub mod error; +// pub mod error; pub mod read; pub mod update; -pub use any::Any; -pub use mutate::Mutate; -pub use parents::*; +// pub use any::Any; +// pub use mutate::Mutate; +// pub use parents::*; use crate::{ ability::{ @@ -57,10 +57,8 @@ use crate::{ command::ToCommand, parse::{ParseAbility, ParseAbilityError, ParsePromised}, }, - delegation::Delegable, invocation::promise::Resolvable, ipld, - proof::{checkable::Checkable, parentful::Parentful, parents::CheckParents, same::CheckSame}, }; use libipld_core::ipld::Ipld; @@ -75,14 +73,6 @@ pub enum Ready { Destroy(destroy::Ready), } -#[derive(Debug, Clone, PartialEq)] -pub enum Builder { - Create(create::Ready), - Read(read::Ready), - Update(update::Builder), - Destroy(destroy::Ready), -} - #[derive(Debug, Clone, PartialEq)] pub enum Promised { Create(create::Promised), @@ -134,11 +124,7 @@ impl ParsePromised for Promised { } } -impl Delegable for Ready { - type Builder = Builder; -} - -impl ParseAbility for Builder { +impl ParseAbility for Ready { type ArgsErr = (); fn try_parse( @@ -146,7 +132,7 @@ impl ParseAbility for Builder { args: arguments::Named, ) -> Result> { match create::Ready::try_parse(cmd, args.clone()) { - Ok(create) => return Ok(Builder::Create(create)), + Ok(create) => return Ok(Ready::Create(create)), Err(ParseAbilityError::InvalidArgs(_)) => { return Err(ParseAbilityError::InvalidArgs(())); } @@ -154,15 +140,15 @@ impl ParseAbility for Builder { } match read::Ready::try_parse(cmd, args.clone()) { - Ok(read) => return Ok(Builder::Read(read)), + Ok(read) => return Ok(Ready::Read(read)), Err(ParseAbilityError::InvalidArgs(_)) => { return Err(ParseAbilityError::InvalidArgs(())); } Err(ParseAbilityError::UnknownCommand(_)) => (), } - match update::Builder::try_parse(cmd, args.clone()) { - Ok(update) => return Ok(Builder::Update(update)), + match update::Ready::try_parse(cmd, args.clone()) { + Ok(update) => return Ok(Ready::Update(update)), Err(ParseAbilityError::InvalidArgs(_)) => { return Err(ParseAbilityError::InvalidArgs(())); } @@ -170,7 +156,7 @@ impl ParseAbility for Builder { } match destroy::Ready::try_parse(cmd, args) { - Ok(destroy) => return Ok(Builder::Destroy(destroy)), + Ok(destroy) => return Ok(Ready::Destroy(destroy)), Err(ParseAbilityError::InvalidArgs(_)) => { return Err(ParseAbilityError::InvalidArgs(())); } @@ -180,10 +166,10 @@ impl ParseAbility for Builder { Err(ParseAbilityError::UnknownCommand(cmd.into())) } } - -impl Checkable for Builder { - type Hierarchy = Parentful; -} +// +// impl Checkable for Builder { +// type Hierarchy = Parentful; +// } impl ToCommand for Ready { fn to_command(&self) -> String { @@ -207,44 +193,44 @@ impl ToCommand for Promised { } } -impl ToCommand for Builder { - fn to_command(&self) -> String { - match self { - Builder::Create(create) => create.to_command(), - Builder::Read(read) => read.to_command(), - Builder::Update(update) => update.to_command(), - Builder::Destroy(destroy) => destroy.to_command(), - } - } -} - -impl CheckParents for Builder { - type Parents = MutableParents; - type ParentError = (); // FIXME - - fn check_parent(&self, parents: &MutableParents) -> Result<(), Self::ParentError> { - match self { - Builder::Create(create) => create.check_parent(parents.into()).map_err(|_| ()), - Builder::Update(update) => update.check_parent(parents.into()).map_err(|_| ()), - Builder::Destroy(destroy) => destroy.check_parent(parents.into()).map_err(|_| ()), - Builder::Read(read) => match parents { - MutableParents::Any(crud_any) => read.check_parent(crud_any).map_err(|_| ()), - _ => Err(()), - }, - } - } -} - -impl From for arguments::Named { - fn from(builder: Builder) -> Self { - match builder { - Builder::Create(create) => create.into(), - Builder::Read(read) => read.into(), - Builder::Update(update) => update.into(), - Builder::Destroy(destroy) => destroy.into(), - } - } -} +// impl ToCommand for Builder { +// fn to_command(&self) -> String { +// match self { +// Builder::Create(create) => create.to_command(), +// Builder::Read(read) => read.to_command(), +// Builder::Update(update) => update.to_command(), +// Builder::Destroy(destroy) => destroy.to_command(), +// } +// } +// } +// +// impl CheckParents for Builder { +// type Parents = MutableParents; +// type ParentError = (); // FIXME +// +// fn check_parent(&self, parents: &MutableParents) -> Result<(), Self::ParentError> { +// match self { +// Builder::Create(create) => create.check_parent(parents.into()).map_err(|_| ()), +// Builder::Update(update) => update.check_parent(parents.into()).map_err(|_| ()), +// Builder::Destroy(destroy) => destroy.check_parent(parents.into()).map_err(|_| ()), +// Builder::Read(read) => match parents { +// MutableParents::Any(crud_any) => read.check_parent(crud_any).map_err(|_| ()), +// _ => Err(()), +// }, +// } +// } +// } +// +// impl From for arguments::Named { +// fn from(builder: Builder) -> Self { +// match builder { +// Builder::Create(create) => create.into(), +// Builder::Read(read) => read.into(), +// Builder::Update(update) => update.into(), +// Builder::Destroy(destroy) => destroy.into(), +// } +// } +// } // impl From for arguments::Named { // fn from(promised: Promised) -> Self { @@ -257,43 +243,43 @@ impl From for arguments::Named { // } // } -impl From for Builder { - fn from(ready: Ready) -> Self { - match ready { - Ready::Create(create) => Builder::Create(create.into()), - Ready::Read(read) => Builder::Read(read.into()), - Ready::Update(update) => Builder::Update(update.into()), - Ready::Destroy(destroy) => Builder::Destroy(destroy.into()), - } - } -} - -impl TryFrom for Ready { - type Error = (); // FIXME - - fn try_from(builder: Builder) -> Result { - match builder { - Builder::Create(create) => create.try_into().map(Ready::Create).map_err(|_| ()), - Builder::Read(read) => read.try_into().map(Ready::Read).map_err(|_| ()), - Builder::Update(update) => update.try_into().map(Ready::Update).map_err(|_| ()), - Builder::Destroy(destroy) => destroy.try_into().map(Ready::Destroy).map_err(|_| ()), - } - } -} - -impl CheckSame for Builder { - type Error = (); - - fn check_same(&self, other: &Self) -> Result<(), Self::Error> { - match (self, other) { - (Builder::Create(a), Builder::Create(b)) => a.check_same(b), - (Builder::Read(a), Builder::Read(b)) => a.check_same(b), - (Builder::Update(a), Builder::Update(b)) => a.check_same(b), - (Builder::Destroy(a), Builder::Destroy(b)) => a.check_same(b), - _ => Err(()), - } - } -} +// impl From for Builder { +// fn from(ready: Ready) -> Self { +// match ready { +// Ready::Create(create) => Builder::Create(create.into()), +// Ready::Read(read) => Builder::Read(read.into()), +// Ready::Update(update) => Builder::Update(update.into()), +// Ready::Destroy(destroy) => Builder::Destroy(destroy.into()), +// } +// } +// } +// +// impl TryFrom for Ready { +// type Error = (); // FIXME +// +// fn try_from(builder: Builder) -> Result { +// match builder { +// Builder::Create(create) => create.try_into().map(Ready::Create).map_err(|_| ()), +// Builder::Read(read) => read.try_into().map(Ready::Read).map_err(|_| ()), +// Builder::Update(update) => update.try_into().map(Ready::Update).map_err(|_| ()), +// Builder::Destroy(destroy) => destroy.try_into().map(Ready::Destroy).map_err(|_| ()), +// } +// } +// } +// +// impl CheckSame for Builder { +// type Error = (); +// +// fn check_same(&self, other: &Self) -> Result<(), Self::Error> { +// match (self, other) { +// (Builder::Create(a), Builder::Create(b)) => a.check_same(b), +// (Builder::Read(a), Builder::Read(b)) => a.check_same(b), +// (Builder::Update(a), Builder::Update(b)) => a.check_same(b), +// (Builder::Destroy(a), Builder::Destroy(b)) => a.check_same(b), +// _ => Err(()), +// } +// } +// } impl Resolvable for Ready { type Promised = Promised; diff --git a/src/ability/crud/create.rs b/src/ability/crud/create.rs index ae8a5b64..baa417cc 100644 --- a/src/ability/crud/create.rs +++ b/src/ability/crud/create.rs @@ -1,11 +1,11 @@ //! Create new resources. -use super::parents::MutableParents; +// use super::parents::MutableParents; use crate::{ ability::{arguments, command::Command}, - delegation::Delegable, + // delegation::Delegable, invocation::{promise, promise::Resolves}, ipld, - proof::{checkable::Checkable, parentful::Parentful, parents::CheckParents, same::CheckSame}, + // proof::{checkable::Checkable, parentful::Parentful, parents::CheckParents, same::CheckSame}, }; use libipld_core::ipld::Ipld; use serde::Serialize; @@ -202,55 +202,55 @@ impl TryFrom> for Ready { } } -impl Delegable for Ready { - type Builder = Ready; -} - -impl Checkable for Ready { - type Hierarchy = Parentful; -} - -impl CheckSame for Ready { - type Error = (); // FIXME better error - - fn check_same(&self, proof: &Self) -> Result<(), Self::Error> { - if self.path == proof.path { - Ok(()) - } else { - Err(()) - } - } -} - -impl CheckParents for Ready { - type Parents = MutableParents; - type ParentError = (); // FIXME - - fn check_parent(&self, other: &Self::Parents) -> Result<(), Self::ParentError> { - if let Some(self_path) = &self.path { - match other { - MutableParents::Any(any) => { - // FIXME check the args, too! - if let Some(proof_path) = &any.path { - if self_path != proof_path { - return Err(()); - } - } - } - MutableParents::Mutate(mutate) => { - // FIXME check the args, too! - if let Some(proof_path) = &mutate.path { - if self_path != proof_path { - return Err(()); - } - } - } - } - } +// impl Delegable for Ready { +// type Builder = Ready; +// } - Ok(()) - } -} +// impl Checkable for Ready { +// type Hierarchy = Parentful; +// } +// +// impl CheckSame for Ready { +// type Error = (); // FIXME better error +// +// fn check_same(&self, proof: &Self) -> Result<(), Self::Error> { +// if self.path == proof.path { +// Ok(()) +// } else { +// Err(()) +// } +// } +// } +// +// impl CheckParents for Ready { +// type Parents = MutableParents; +// type ParentError = (); // FIXME +// +// fn check_parent(&self, other: &Self::Parents) -> Result<(), Self::ParentError> { +// if let Some(self_path) = &self.path { +// match other { +// MutableParents::Any(any) => { +// // FIXME check the args, too! +// if let Some(proof_path) = &any.path { +// if self_path != proof_path { +// return Err(()); +// } +// } +// } +// MutableParents::Mutate(mutate) => { +// // FIXME check the args, too! +// if let Some(proof_path) = &mutate.path { +// if self_path != proof_path { +// return Err(()); +// } +// } +// } +// } +// } +// +// Ok(()) +// } +// } // impl From for arguments::Named { // fn from(promised: Promised) -> Self { diff --git a/src/ability/crud/destroy.rs b/src/ability/crud/destroy.rs index a5d48571..e43123cb 100644 --- a/src/ability/crud/destroy.rs +++ b/src/ability/crud/destroy.rs @@ -1,12 +1,12 @@ //! Destroy a resource. -use super::parents::MutableParents; +// use super::parents::MutableParents; use crate::{ ability::{arguments, command::Command}, - delegation::Delegable, + // delegation::Delegable, invocation::promise, ipld, - proof::{checkable::Checkable, parentful::Parentful, parents::CheckParents, same::CheckSame}, + // proof::{checkable::Checkable, parentful::Parentful, parents::CheckParents, same::CheckSame}, }; use libipld_core::ipld::Ipld; use serde::Serialize; @@ -143,9 +143,9 @@ impl Command for Promised { const COMMAND: &'static str = COMMAND; } -impl Delegable for Ready { - type Builder = Ready; -} +// impl Delegable for Ready { +// type Builder = Ready; +// } impl TryFrom> for Ready { type Error = (); // FIXME @@ -168,49 +168,49 @@ impl TryFrom> for Ready { } } -impl Checkable for Ready { - type Hierarchy = Parentful; -} - -impl CheckSame for Ready { - type Error = (); // FIXME better error - - fn check_same(&self, proof: &Self) -> Result<(), Self::Error> { - if self.path == proof.path { - Ok(()) - } else { - Err(()) - } - } -} - -impl CheckParents for Ready { - type Parents = MutableParents; - type ParentError = (); // FIXME - - fn check_parent(&self, other: &Self::Parents) -> Result<(), Self::ParentError> { - if let Some(self_path) = &self.path { - match other { - MutableParents::Any(any) => { - if let Some(proof_path) = &any.path { - if self_path != proof_path { - return Err(()); - } - } - } - MutableParents::Mutate(mutate) => { - if let Some(proof_path) = &mutate.path { - if self_path != proof_path { - return Err(()); - } - } - } - } - } - - Ok(()) - } -} +// impl Checkable for Ready { +// type Hierarchy = Parentful; +// } +// +// impl CheckSame for Ready { +// type Error = (); // FIXME better error +// +// fn check_same(&self, proof: &Self) -> Result<(), Self::Error> { +// if self.path == proof.path { +// Ok(()) +// } else { +// Err(()) +// } +// } +// } +// +// impl CheckParents for Ready { +// type Parents = MutableParents; +// type ParentError = (); // FIXME +// +// fn check_parent(&self, other: &Self::Parents) -> Result<(), Self::ParentError> { +// if let Some(self_path) = &self.path { +// match other { +// MutableParents::Any(any) => { +// if let Some(proof_path) = &any.path { +// if self_path != proof_path { +// return Err(()); +// } +// } +// } +// MutableParents::Mutate(mutate) => { +// if let Some(proof_path) = &mutate.path { +// if self_path != proof_path { +// return Err(()); +// } +// } +// } +// } +// } +// +// Ok(()) +// } +// } impl From for arguments::Named { fn from(promised: Promised) -> Self { @@ -265,10 +265,10 @@ impl From for Ready { } impl From for arguments::Named { - fn from(builder: Ready) -> Self { + fn from(ready: Ready) -> Self { let mut named = arguments::Named::new(); - if let Some(path) = builder.path { + if let Some(path) = ready.path { named.insert( "path".to_string(), path.into_os_string() diff --git a/src/ability/crud/read.rs b/src/ability/crud/read.rs index 57001d8c..b9201b26 100644 --- a/src/ability/crud/read.rs +++ b/src/ability/crud/read.rs @@ -1,12 +1,12 @@ //! Read from a resource. -use super::any as crud; +// use super::any as crud; use crate::{ ability::{arguments, command::Command}, - delegation::Delegable, + // delegation::Delegable, invocation::promise, ipld, - proof::{checkable::Checkable, parentful::Parentful, parents::CheckParents, same::CheckSame}, + // proof::{checkable::Checkable, parentful::Parentful, parents::CheckParents, same::CheckSame}, }; use libipld_core::{error::SerdeError, ipld::Ipld, serde as ipld_serde}; use serde::{Deserialize, Serialize}; @@ -157,9 +157,9 @@ impl Command for Promised { const COMMAND: &'static str = COMMAND; } -impl Delegable for Ready { - type Builder = Ready; -} +// impl Delegable for Ready { +// type Builder = Ready; +// } // FIXME resolves vs resolvable is confusing @@ -220,39 +220,39 @@ impl TryFrom> for Ready { } } -impl Checkable for Ready { - type Hierarchy = Parentful; -} - -impl CheckSame for Ready { - type Error = (); // FIXME better error - - fn check_same(&self, proof: &Self) -> Result<(), Self::Error> { - if self.path == proof.path { - Ok(()) - } else { - Err(()) - } - } -} - -impl CheckParents for Ready { - type Parents = crud::Any; - type ParentError = (); // FIXME - - fn check_parent(&self, other: &crud::Any) -> Result<(), Self::ParentError> { - if let Some(self_path) = &self.path { - // FIXME check the args, too! - if let Some(proof_path) = &other.path { - if self_path != proof_path { - return Err(()); - } - } - } - - Ok(()) - } -} +// impl Checkable for Ready { +// type Hierarchy = Parentful; +// } +// +// impl CheckSame for Ready { +// type Error = (); // FIXME better error +// +// fn check_same(&self, proof: &Self) -> Result<(), Self::Error> { +// if self.path == proof.path { +// Ok(()) +// } else { +// Err(()) +// } +// } +// } +// +// impl CheckParents for Ready { +// type Parents = crud::Any; +// type ParentError = (); // FIXME +// +// fn check_parent(&self, other: &crud::Any) -> Result<(), Self::ParentError> { +// if let Some(self_path) = &self.path { +// // FIXME check the args, too! +// if let Some(proof_path) = &other.path { +// if self_path != proof_path { +// return Err(()); +// } +// } +// } +// +// Ok(()) +// } +// } impl promise::Resolvable for Ready { type Promised = Promised; diff --git a/src/ability/crud/update.rs b/src/ability/crud/update.rs index 305855e6..54863a76 100644 --- a/src/ability/crud/update.rs +++ b/src/ability/crud/update.rs @@ -1,11 +1,11 @@ //! Update existing resources. -use super::parents::MutableParents; +// use super::parents::MutableParents; use crate::{ ability::{arguments, command::Command}, - delegation::Delegable, + // delegation::Delegable, invocation::{promise, promise::Resolves}, ipld, - proof::{checkable::Checkable, parentful::Parentful, parents::CheckParents, same::CheckSame}, + // proof::{checkable::Checkable, parentful::Parentful, parents::CheckParents, same::CheckSame}, }; use libipld_core::ipld::Ipld; use serde::Serialize; @@ -200,15 +200,38 @@ impl TryFrom> for Promised { } } -impl Delegable for Ready { - type Builder = Builder; -} +// impl Delegable for Ready { +// type Builder = Builder; +// } impl TryFrom> for Ready { type Error = (); fn try_from(named: arguments::Named) -> Result { - Self::try_from_named(named).map_err(|_| ()) + let mut path = None; + let mut args = None; + + for (key, ipld) in named { + match key.as_str() { + "path" => { + if let Ipld::String(s) = ipld { + path = Some(PathBuf::from(s)); + } else { + return Err(()); + } + } + "args" => { + if let Ipld::Map(map) = ipld { + args = Some(arguments::Named(map)); + } else { + return Err(()); + } + } + _ => return Err(()), + } + } + + Ok(Ready { path, args }) } } @@ -293,51 +316,51 @@ impl TryFrom for Ready { } } -impl Checkable for Builder { - type Hierarchy = Parentful; -} - -impl CheckSame for Builder { - type Error = (); // FIXME better error - - fn check_same(&self, proof: &Self) -> Result<(), Self::Error> { - if self.path == proof.path { - Ok(()) - } else { - Err(()) - } - } -} - -impl CheckParents for Builder { - type Parents = MutableParents; - type ParentError = (); // FIXME - - fn check_parent(&self, other: &Self::Parents) -> Result<(), Self::ParentError> { - if let Some(self_path) = &self.path { - match other { - MutableParents::Any(any) => { - // FIXME check the args, too! - if let Some(proof_path) = &any.path { - if self_path != proof_path { - return Err(()); - } - } - } - MutableParents::Mutate(mutate) => { - // FIXME check the args, too! - if let Some(proof_path) = &mutate.path { - if self_path != proof_path { - return Err(()); - } - } - } - } - } - - Ok(()) - } -} +// impl Checkable for Builder { +// type Hierarchy = Parentful; +// } +// +// impl CheckSame for Builder { +// type Error = (); // FIXME better error +// +// fn check_same(&self, proof: &Self) -> Result<(), Self::Error> { +// if self.path == proof.path { +// Ok(()) +// } else { +// Err(()) +// } +// } +// } +// +// impl CheckParents for Builder { +// type Parents = MutableParents; +// type ParentError = (); // FIXME +// +// fn check_parent(&self, other: &Self::Parents) -> Result<(), Self::ParentError> { +// if let Some(self_path) = &self.path { +// match other { +// MutableParents::Any(any) => { +// // FIXME check the args, too! +// if let Some(proof_path) = &any.path { +// if self_path != proof_path { +// return Err(()); +// } +// } +// } +// MutableParents::Mutate(mutate) => { +// // FIXME check the args, too! +// if let Some(proof_path) = &mutate.path { +// if self_path != proof_path { +// return Err(()); +// } +// } +// } +// } +// } +// +// Ok(()) +// } +// } impl From for arguments::Named { fn from(promised: Promised) -> Self { @@ -390,7 +413,7 @@ impl From for Builder { fn from(promised: Promised) -> Self { Builder { path: promised.path.and_then(|p| p.try_resolve().ok()), - args: promised.args.and_then(|a| a.try_resolve_option()), + args: todo!(), // promised.args.and_then(|a| a.try_resolve_option()), } } } diff --git a/src/ability/dynamic.rs b/src/ability/dynamic.rs index a665a7aa..441c1565 100644 --- a/src/ability/dynamic.rs +++ b/src/ability/dynamic.rs @@ -5,7 +5,7 @@ use super::{ command::ToCommand, parse::{ParseAbility, ParseAbilityError}, }; -use crate::proof::same::CheckSame; +// use crate::proof::same::CheckSame; use libipld_core::{error::SerdeError, ipld::Ipld, serde as ipld_serde}; use serde::{Deserialize, Serialize}; use std::fmt::Debug; @@ -118,27 +118,27 @@ impl TryFrom for Dynamic { } } -impl CheckSame for Dynamic { - type Error = String; // FIXME better err - - fn check_same(&self, proof: &Self) -> Result<(), Self::Error> { - if self.cmd != proof.cmd { - return Err("Command mismatch".into()); - } - - self.args.0.iter().try_for_each(|(k, v)| { - if let Some(proof_v) = proof.args.get(k) { - if v != proof_v { - return Err("arguments::Named mismatch".into()); - } - } else { - return Err("arguments::Named mismatch".into()); - } - - Ok(()) - }) - } -} +// impl CheckSame for Dynamic { +// type Error = String; // FIXME better err +// +// fn check_same(&self, proof: &Self) -> Result<(), Self::Error> { +// if self.cmd != proof.cmd { +// return Err("Command mismatch".into()); +// } +// +// self.args.0.iter().try_for_each(|(k, v)| { +// if let Some(proof_v) = proof.args.get(k) { +// if v != proof_v { +// return Err("arguments::Named mismatch".into()); +// } +// } else { +// return Err("arguments::Named mismatch".into()); +// } +// +// Ok(()) +// }) +// } +// } impl From for Ipld { fn from(dynamic: Dynamic) -> Self { diff --git a/src/ability/msg.rs b/src/ability/msg.rs index 4fa01bed..e1d90481 100644 --- a/src/ability/msg.rs +++ b/src/ability/msg.rs @@ -1,22 +1,16 @@ //! Message abilities -mod any; - pub mod receive; pub mod send; -pub use any::Any; - use crate::{ ability::{ arguments, command::ToCommand, parse::{ParseAbility, ParseAbilityError, ParsePromised}, }, - delegation::Delegable, invocation::promise::Resolvable, ipld, - proof::{checkable::Checkable, parentful::Parentful, parents::CheckParents, same::CheckSame}, }; use libipld_core::ipld::Ipld; @@ -27,22 +21,12 @@ pub enum Ready { Receive(receive::Receive), } -#[derive(Debug, Clone, PartialEq)] -pub enum Builder { - Send(send::Builder), - Receive(receive::Receive), -} - #[derive(Debug, Clone, PartialEq)] pub enum Promised { Send(send::Promised), Receive(receive::Promised), } -impl Delegable for Ready { - type Builder = Builder; -} - impl ToCommand for Ready { fn to_command(&self) -> String { match self { @@ -52,15 +36,6 @@ impl ToCommand for Ready { } } -impl ToCommand for Builder { - fn to_command(&self) -> String { - match self { - Builder::Send(send) => send.to_command(), - Builder::Receive(receive) => receive.to_command(), - } - } -} - impl ToCommand for Promised { fn to_command(&self) -> String { match self { @@ -89,72 +64,6 @@ impl ParsePromised for Promised { } } -// impl ParseAbility for Ready { -// type ArgsErr = (); -// -// fn try_parse( -// cmd: &str, -// args: arguments::Named, -// ) -> Result> { -// match send::Ready::try_parse(cmd, args.clone()) { -// Ok(send) => return Ok(Ready::Send(send)), -// Err(ParseAbilityError::InvalidArgs(args)) => { -// return Err(ParseAbilityError::InvalidArgs(())) -// } -// Err(ParseAbilityError::UnknownCommand(_)) => {} -// } -// -// match receive::Receive::try_parse(cmd, args) { -// Ok(receive) => return Ok(Ready::Receive(receive)), -// Err(ParseAbilityError::InvalidArgs(args)) => { -// return Err(ParseAbilityError::InvalidArgs(())) -// } -// Err(ParseAbilityError::UnknownCommand(cmd)) => {} -// } -// -// Err(ParseAbilityError::UnknownCommand(cmd.to_string())) -// } -// } - -impl ParseAbility for Builder { - type ArgsErr = (); - - fn try_parse( - cmd: &str, - args: arguments::Named, - ) -> Result> { - if let Ok(send) = send::Builder::try_parse(cmd, args.clone()) { - return Ok(Builder::Send(send)); - } - - if let Ok(receive) = receive::Receive::try_parse(cmd, args) { - return Ok(Builder::Receive(receive)); - } - - Err(ParseAbilityError::UnknownCommand(cmd.to_string())) - } -} - -impl TryFrom for Ready { - type Error = (); - - fn try_from(builder: Builder) -> Result { - match builder { - Builder::Send(send) => send.try_into().map(Ready::Send).map_err(|_| ()), - Builder::Receive(receive) => Ok(Ready::Receive(receive)), - } - } -} - -impl From for Builder { - fn from(ready: Ready) -> Self { - match ready { - Ready::Send(send) => Builder::Send(send.into()), - Ready::Receive(receive) => Builder::Receive(receive.into()), - } - } -} - impl From for arguments::Named { fn from(promised: Promised) -> Self { match promised { @@ -168,48 +77,30 @@ impl Resolvable for Ready { type Promised = Promised; } -impl CheckSame for Builder { - type Error = (); // FIXME - - fn check_same(&self, proof: &Self) -> Result<(), Self::Error> { - match (self, proof) { - (Builder::Send(this), Builder::Send(that)) => this.check_same(that), - (Builder::Receive(this), Builder::Receive(that)) => this.check_same(that), - _ => Err(()), +impl From for arguments::Named { + fn from(promised: Promised) -> Self { + match promised { + Promised::Send(send) => send.into(), + Promised::Receive(receive) => receive.into(), } } } -impl CheckParents for Builder { - type Parents = Any; - type ParentError = (); +impl ParseAbility for Ready { + type ArgsErr = (); - fn check_parent(&self, proof: &Any) -> Result<(), Self::ParentError> { - match (self, proof) { - (Builder::Send(this), any) => this.check_parent(&any), - (Builder::Receive(this), any) => this.check_parent(&any), + fn try_parse( + cmd: &str, + args: arguments::Named, + ) -> Result> { + if let Ok(send) = send::Ready::try_parse(cmd, args.clone()) { + return Ok(Ready::Send(send)); } - } -} - -impl Checkable for Builder { - type Hierarchy = Parentful; -} -impl From for arguments::Named { - fn from(builder: Builder) -> Self { - match builder { - Builder::Send(send) => send.into(), - Builder::Receive(receive) => receive.into(), + if let Ok(receive) = receive::Receive::try_parse(cmd, args) { + return Ok(Ready::Receive(receive)); } - } -} -impl From for arguments::Named { - fn from(promised: Promised) -> Self { - match promised { - Promised::Send(send) => send.into(), - Promised::Receive(receive) => receive.into(), - } + Err(ParseAbilityError::UnknownCommand(cmd.to_string())) } } diff --git a/src/ability/msg/receive.rs b/src/ability/msg/receive.rs index 19373830..1b0e817b 100644 --- a/src/ability/msg/receive.rs +++ b/src/ability/msg/receive.rs @@ -2,10 +2,10 @@ use crate::{ ability::{arguments, command::Command}, - delegation::Delegable, + // delegation::Delegable, invocation::promise, ipld, - proof::{checkable::Checkable, parentful::Parentful, parents::CheckParents, same::CheckSame}, + // proof::{checkable::Checkable, parentful::Parentful, parents::CheckParents, same::CheckSame}, url, }; use libipld_core::{error::SerdeError, ipld::Ipld, serde as ipld_serde}; @@ -59,9 +59,9 @@ impl Command for Promised { const COMMAND: &'static str = COMMAND; } -impl Delegable for Receive { - type Builder = Receive; -} +// impl Delegable for Receive { +// type Builder = Receive; +// } impl TryFrom> for Receive { type Error = (); @@ -94,33 +94,33 @@ impl From for arguments::Named { } } -impl Checkable for Receive { - type Hierarchy = Parentful; -} - -impl CheckSame for Receive { - type Error = (); // FIXME better error - fn check_same(&self, proof: &Self) -> Result<(), Self::Error> { - self.from.check_same(&proof.from).map_err(|_| ()) - } -} - -impl CheckParents for Receive { - type Parents = super::Any; - type ParentError = ::Error; - - fn check_parent(&self, proof: &Self::Parents) -> Result<(), Self::ParentError> { - if let Some(from) = &self.from { - if let Some(proof_from) = &proof.from { - if &from != &proof_from { - return Err(()); - } - } - } - - Ok(()) - } -} +// impl Checkable for Receive { +// type Hierarchy = Parentful; +// } +// +// impl CheckSame for Receive { +// type Error = (); // FIXME better error +// fn check_same(&self, proof: &Self) -> Result<(), Self::Error> { +// self.from.check_same(&proof.from).map_err(|_| ()) +// } +// } +// +// impl CheckParents for Receive { +// type Parents = super::Any; +// type ParentError = ::Error; +// +// fn check_parent(&self, proof: &Self::Parents) -> Result<(), Self::ParentError> { +// if let Some(from) = &self.from { +// if let Some(proof_from) = &proof.from { +// if &from != &proof_from { +// return Err(()); +// } +// } +// } +// +// Ok(()) +// } +// } impl From for Ipld { fn from(receive: Receive) -> Self { diff --git a/src/ability/msg/send.rs b/src/ability/msg/send.rs index fc8a2f2c..c65869d5 100644 --- a/src/ability/msg/send.rs +++ b/src/ability/msg/send.rs @@ -2,16 +2,11 @@ use crate::{ ability::{arguments, command::Command}, - delegation::Delegable, invocation::promise, - ipld, - proof::{checkable::Checkable, parentful::Parentful, parents::CheckParents, same::CheckSame}, + ipld, url, }; -use libipld_core::ipld::Ipld; +use libipld_core::{error::SerdeError, ipld::Ipld}; use serde::{Deserialize, Serialize}; -use std::collections::BTreeMap; -// use url::Url; -use crate::url; #[cfg_attr(doc, aquamarine::aquamarine)] /// The executable/dispatchable variant of the `msg/send` ability. @@ -56,48 +51,6 @@ pub struct Ready { pub message: String, } -#[cfg_attr(doc, aquamarine::aquamarine)] -/// The delegatable variant of the `msg/send` ability. -/// -/// # Delegation Hierarchy -/// -/// The hierarchy of message abilities is as follows: -/// -/// ```mermaid -/// flowchart LR -/// top("*") -/// -/// subgraph Message Abilities -/// any("msg/*") -/// -/// subgraph Invokable -/// send("msg/send") -/// end -/// end -/// -/// sendrun{{"invoke"}} -/// -/// top --> any -/// any --> send -.-> sendrun -/// -/// style send stroke:orange; -/// ``` -#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] -#[serde(deny_unknown_fields)] -pub struct Builder { - /// The recipient of the message - pub to: Option, - - /// The sender address of the message - /// - /// This *may* be a URL (such as an email address). - /// If provided, the `subject` must have the right to send from this address. - pub from: Option, - - /// The main body of the message - pub message: Option, -} - #[cfg_attr(doc, aquamarine::aquamarine)] /// The invoked variant of the `msg/send` ability /// @@ -143,14 +96,52 @@ pub struct Promised { pub message: promise::Resolves, } -impl Delegable for Ready { - type Builder = Builder; -} +// impl Delegable for Ready { +// type Builder = Builder; +// } impl promise::Resolvable for Ready { type Promised = Promised; } +impl TryFrom> for Ready { + type Error = (); + + fn try_from(named: arguments::Named) -> Result { + let mut to = None; + let mut from = None; + let mut message = None; + + for (key, value) in named.0 { + match key.as_str() { + "to" => match Ipld::try_from(value) { + Ok(Ipld::String(s)) => { + to = Some(url::Newtype::parse(s.as_str()).map_err(|_| ())?) + } + _ => return Err(()), + }, + "from" => match Ipld::try_from(value) { + Ok(Ipld::String(s)) => { + from = Some(url::Newtype::parse(s.as_str()).map_err(|_| ())?) + } + _ => return Err(()), + }, + "message" => match Ipld::try_from(value) { + Ok(Ipld::String(s)) => message = Some(s), + _ => return Err(()), + }, + _ => return Err(()), + } + } + + Ok(Ready { + to: to.ok_or(())?, + from: from.ok_or(())?, + message: message.ok_or(())?, + }) + } +} + impl TryFrom> for Promised { type Error = (); @@ -196,19 +187,6 @@ impl TryFrom> for Promised { } } -impl From for arguments::Named { - fn from(b: Builder) -> Self { - let mut btree = BTreeMap::new(); - b.to.map(|to| btree.insert("to".into(), to.to_string().into())); - b.from - .map(|from| btree.insert("from".into(), from.to_string().into())); - b.message - .map(|msg| btree.insert("message".into(), msg.into())); - - arguments::Named(btree) - } -} - impl From for arguments::Named { fn from(p: Promised) -> Self { arguments::Named::from_iter([ @@ -219,103 +197,16 @@ impl From for arguments::Named { } } -impl From for Builder { - fn from(p: Promised) -> Self { - Builder { - to: p.to.into(), - from: p.from.into(), - message: p.message.into(), - } - } -} - const COMMAND: &'static str = "msg/send"; impl Command for Ready { const COMMAND: &'static str = COMMAND; } -impl Command for Builder { - const COMMAND: &'static str = COMMAND; -} - impl Command for Promised { const COMMAND: &'static str = COMMAND; } -impl Checkable for Builder { - type Hierarchy = Parentful; -} - -impl CheckSame for Builder { - type Error = (); // FIXME better error - - fn check_same(&self, proof: &Self) -> Result<(), Self::Error> { - self.to.check_same(&proof.to).map_err(|_| ())?; - self.from.check_same(&proof.from).map_err(|_| ())?; - self.message.check_same(&proof.message).map_err(|_| ()) - } -} - -impl CheckParents for Builder { - type Parents = super::Any; - type ParentError = ::Error; - - fn check_parent(&self, proof: &Self::Parents) -> Result<(), Self::ParentError> { - self.from.check_same(&proof.from).map_err(|_| ()) - } -} - -impl TryFrom> for Builder { - type Error = (); - - fn try_from(args: arguments::Named) -> Result { - let mut to = None; - let mut from = None; - let mut message = None; - - for (key, ipld) in args.0 { - match key.as_str() { - "to" => { - // FIXME extract this common pattern - if let Ipld::String(s) = ipld { - to = Some(url::Newtype::parse(s.as_str()).map_err(|_| ())?); - } else { - return Err(()); - } - } - "from" => { - if let Ipld::String(s) = ipld { - from = Some(url::Newtype::parse(s.as_str()).map_err(|_| ())?); - } else { - return Err(()); - } - } - "message" => { - if let Ipld::String(s) = ipld { - message = Some(s); - } else { - return Err(()); - } - } - _ => return Err(()), - } - } - - Ok(Builder { to, from, message }) - } -} - -impl From for Builder { - fn from(resolved: Ready) -> Self { - Builder { - to: resolved.to.into(), - from: resolved.from.into(), - message: resolved.message.into(), - } - } -} - impl From for Promised { fn from(r: Ready) -> Self { Promised { @@ -337,24 +228,6 @@ impl TryFrom for Ready { } } -impl TryFrom for Ready { - type Error = Builder; - - fn try_from(b: Builder) -> Result { - // Entirely by refernce - if b.to.is_none() || b.from.is_none() || b.message.is_none() { - return Err(b); - } - - // Moves, and unwrap because we checked above instead of 2 clones per line - Ok(Ready { - to: b.to.unwrap(), - from: b.from.unwrap(), - message: b.message.unwrap(), - }) - } -} - impl From for arguments::Named { fn from(p: Promised) -> Self { arguments::Named::from_iter([ diff --git a/src/ability/preset.rs b/src/ability/preset.rs index 2443e26a..29e0510f 100644 --- a/src/ability/preset.rs +++ b/src/ability/preset.rs @@ -5,10 +5,8 @@ use crate::{ command::ToCommand, parse::{ParseAbility, ParseAbilityError, ParsePromised}, }, - delegation::Delegable, invocation::promise::Resolvable, ipld, - proof::{checkable::Checkable, parentful::Parentful, parents::CheckParents, same::CheckSame}, }; use libipld_core::ipld::Ipld; @@ -20,37 +18,6 @@ pub enum Ready { Wasm(wasm::run::Ready), } -#[derive(Debug, Clone, PartialEq)] //, Serialize, Deserialize)] -pub enum Builder { - Crud(crud::Builder), - Msg(msg::Builder), - Wasm(wasm::run::Builder), -} - -#[derive(Debug, Clone, PartialEq)] //, Serialize, Deserialize)] -pub enum Parents { - Crud(crud::MutableParents), - Msg(msg::Any), -} // NOTE WasmRun has no parents - -impl CheckSame for Parents { - type Error = (); // FIXME - - fn check_same(&self, proof: &Self) -> Result<(), Self::Error> { - match (self, proof) { - (Parents::Msg(self_), Parents::Msg(proof_)) => self_.check_same(proof_).map_err(|_| ()), - (Parents::Crud(self_), Parents::Crud(proof_)) => { - self_.check_same(proof_).map_err(|_| ()) - } - _ => Err(()), - } - } -} - -impl Delegable for Ready { - type Builder = Builder; -} - impl ToCommand for Ready { fn to_command(&self) -> String { match self { @@ -60,66 +27,6 @@ impl ToCommand for Ready { } } } - -impl ToCommand for Builder { - fn to_command(&self) -> String { - match self { - Builder::Crud(builder) => builder.to_command(), - Builder::Msg(builder) => builder.to_command(), - Builder::Wasm(builder) => builder.to_command(), - } - } -} - -impl CheckSame for Builder { - type Error = (); // FIXME - - fn check_same(&self, proof: &Self) -> Result<(), Self::Error> { - match (self, proof) { - (Builder::Wasm(builder), Builder::Wasm(proof)) => builder.check_same(proof), - _ => Err(()), - } - } -} - -impl CheckParents for Builder { - type Parents = Parents; - type ParentError = (); // FIXME - - fn check_parent(&self, proof: &Self::Parents) -> Result<(), Self::ParentError> { - match (self, proof) { - (Builder::Msg(builder), Parents::Msg(proof)) => builder.check_parent(proof), - _ => Err(()), - } - } -} - -impl Checkable for Builder { - type Hierarchy = Parentful; -} - -impl From for Builder { - fn from(ready: Ready) -> Self { - match ready { - Ready::Crud(ready) => Builder::Crud(ready.into()), - Ready::Msg(ready) => Builder::Msg(ready.into()), - Ready::Wasm(ready) => Builder::Wasm(ready.into()), - } - } -} - -impl TryFrom for Ready { - type Error = (); // FIXME - - fn try_from(builder: Builder) -> Result { - match builder { - Builder::Crud(builder) => builder.try_into().map(Ready::Crud).map_err(|_| ()), - Builder::Msg(builder) => builder.try_into().map(Ready::Msg).map_err(|_| ()), - Builder::Wasm(builder) => builder.try_into().map(Ready::Wasm).map_err(|_| ()), - } - } -} - #[derive(Debug, Clone, PartialEq)] //, Serialize, Deserialize)] pub enum Promised { Crud(crud::Promised), @@ -170,27 +77,27 @@ impl ParsePromised for Promised { } } -impl ParseAbility for Builder { +impl ParseAbility for Ready { type ArgsErr = (); fn try_parse( cmd: &str, args: arguments::Named, ) -> Result> { - match msg::Builder::try_parse(cmd, args.clone()) { - Ok(builder) => return Ok(Builder::Msg(builder)), + match msg::Ready::try_parse(cmd, args.clone()) { + Ok(builder) => return Ok(Ready::Msg(builder)), Err(err) => return Err(err), Err(ParseAbilityError::UnknownCommand(_)) => (), } - match crud::Builder::try_parse(cmd, args.clone()) { - Ok(builder) => return Ok(Builder::Crud(builder)), + match crud::Ready::try_parse(cmd, args.clone()) { + Ok(builder) => return Ok(Ready::Crud(builder)), Err(err) => return Err(err), Err(ParseAbilityError::UnknownCommand(_)) => (), } - match wasm::run::Builder::try_parse(cmd, args) { - Ok(builder) => return Ok(Builder::Wasm(builder)), + match wasm::run::Ready::try_parse(cmd, args) { + Ok(builder) => return Ok(Ready::Wasm(builder)), Err(err) => return Err(err), Err(ParseAbilityError::UnknownCommand(_)) => (), } @@ -199,24 +106,24 @@ impl ParseAbility for Builder { } } -impl From for arguments::Named { - fn from(builder: Builder) -> Self { - match builder { - Builder::Crud(builder) => builder.into(), - Builder::Msg(builder) => builder.into(), - Builder::Wasm(builder) => builder.into(), - } - } -} - -impl From for arguments::Named { - fn from(parents: Parents) -> Self { - match parents { - Parents::Crud(parents) => parents.into(), - Parents::Msg(parents) => parents.into(), - } - } -} +// impl From for arguments::Named { +// fn from(builder: Builder) -> Self { +// match builder { +// Builder::Crud(builder) => builder.into(), +// Builder::Msg(builder) => builder.into(), +// Builder::Wasm(builder) => builder.into(), +// } +// } +// } +// +// impl From for arguments::Named { +// fn from(parents: Parents) -> Self { +// match parents { +// Parents::Crud(parents) => parents.into(), +// Parents::Msg(parents) => parents.into(), +// } +// } +// } impl From for arguments::Named { fn from(promised: Promised) -> Self { diff --git a/src/ability/ucan/revoke.rs b/src/ability/ucan/revoke.rs index fd2b29cf..dcb7aec5 100644 --- a/src/ability/ucan/revoke.rs +++ b/src/ability/ucan/revoke.rs @@ -4,14 +4,14 @@ use crate::{ ability::{arguments, command::Command}, - delegation::Delegable, + // delegation::Delegable, invocation::promise, ipld, - proof::{error::OptionalFieldError, parentless::NoParents, same::CheckSame}, + // proof::{error::OptionalFieldError, parentless::NoParents, same::CheckSame}, }; use libipld_core::{cid::Cid, ipld::Ipld}; use serde::{Deserialize, Serialize}; -use std::{collections::BTreeMap, fmt::Debug}; +use std::fmt::Debug; /// The fully resolved variant: ready to execute. #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] @@ -26,17 +26,17 @@ impl Command for Ready { const COMMAND: &'static str = COMMAND; } -impl Command for Builder { - const COMMAND: &'static str = COMMAND; -} +// impl Command for Builder { +// const COMMAND: &'static str = COMMAND; +// } impl Command for Promised { const COMMAND: &'static str = COMMAND; } -impl Delegable for Ready { - type Builder = Builder; -} +// impl Delegable for Ready { +// type Builder = Builder; +// } impl TryFrom> for Ready { type Error = (); @@ -49,26 +49,26 @@ impl TryFrom> for Ready { } } -impl TryFrom> for Builder { - type Error = (); - - fn try_from(arguments: arguments::Named) -> Result { - if let Some(ipld) = arguments.get("ucan") { - let nt: ipld::cid::Newtype = ipld.try_into().map_err(|_| ())?; - Ok(Builder { ucan: Some(nt.cid) }) - } else { - Ok(Builder { ucan: None }) - } - } -} - -impl From for Builder { - fn from(promised: Promised) -> Self { - Builder { - ucan: promised.ucan.try_resolve().ok(), - } - } -} +// impl TryFrom> for Builder { +// type Error = (); +// +// fn try_from(arguments: arguments::Named) -> Result { +// if let Some(ipld) = arguments.get("ucan") { +// let nt: ipld::cid::Newtype = ipld.try_into().map_err(|_| ())?; +// Ok(Builder { ucan: Some(nt.cid) }) +// } else { +// Ok(Builder { ucan: None }) +// } +// } +// } + +// impl From for Builder { +// fn from(promised: Promised) -> Self { +// Builder { +// ucan: promised.ucan.try_resolve().ok(), +// } +// } +// } impl promise::Resolvable for Ready { type Promised = Promised; @@ -80,49 +80,49 @@ impl From for arguments::Named { } } -/// A variant with some fields waiting to be set. -#[derive(Debug, Default, Clone, PartialEq, Serialize, Deserialize)] -pub struct Builder { - pub ucan: Option, -} - -impl NoParents for Builder {} - -impl CheckSame for Builder { - type Error = OptionalFieldError; - - fn check_same(&self, proof: &Self) -> Result<(), Self::Error> { - self.ucan.check_same(&proof.ucan) - } -} - -impl From for Builder { - fn from(resolved: Ready) -> Builder { - Builder { - ucan: Some(resolved.ucan), - } - } -} - -impl TryFrom for Ready { - type Error = (); - - fn try_from(b: Builder) -> Result { - Ok(Ready { - ucan: b.ucan.ok_or(())?, - }) - } -} - -impl From for arguments::Named { - fn from(b: Builder) -> arguments::Named { - let mut btree = BTreeMap::new(); - if let Some(cid) = b.ucan { - btree.insert("ucan".into(), cid.into()); - } - arguments::Named(btree) - } -} +// /// A variant with some fields waiting to be set. +// #[derive(Debug, Default, Clone, PartialEq, Serialize, Deserialize)] +// pub struct Builder { +// pub ucan: Option, +// } +// +// impl NoParents for Builder {} +// +// impl CheckSame for Builder { +// type Error = OptionalFieldError; +// +// fn check_same(&self, proof: &Self) -> Result<(), Self::Error> { +// self.ucan.check_same(&proof.ucan) +// } +// } + +// impl From for Builder { +// fn from(resolved: Ready) -> Builder { +// Builder { +// ucan: Some(resolved.ucan), +// } +// } +// } + +// impl TryFrom for Ready { +// type Error = (); +// +// fn try_from(b: Builder) -> Result { +// Ok(Ready { +// ucan: b.ucan.ok_or(())?, +// }) +// } +// } +// +// impl From for arguments::Named { +// fn from(b: Builder) -> arguments::Named { +// let mut btree = BTreeMap::new(); +// if let Some(cid) = b.ucan { +// btree.insert("ucan".into(), cid.into()); +// } +// arguments::Named(btree) +// } +// } /// A variant where arguments may be [`Promise`][crate::invocation::promise]s. #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] diff --git a/src/ability/wasm/run.rs b/src/ability/wasm/run.rs index e9301a91..0c7f8cfa 100644 --- a/src/ability/wasm/run.rs +++ b/src/ability/wasm/run.rs @@ -3,14 +3,13 @@ use super::module::Module; use crate::{ ability::{arguments, command::Command}, - delegation::Delegable, + // delegation::Delegable, invocation::promise, ipld, - proof::{parentless::NoParents, same::CheckSame}, + // proof::{parentless::NoParents, same::CheckSame}, }; use libipld_core::ipld::Ipld; use serde::{Deserialize, Serialize}; -use std::collections::BTreeMap; const COMMAND: &'static str = "wasm/run"; @@ -18,10 +17,6 @@ impl Command for Ready { const COMMAND: &'static str = COMMAND; } -impl Command for Builder { - const COMMAND: &'static str = COMMAND; -} - impl Command for Promised { const COMMAND: &'static str = COMMAND; } @@ -39,11 +34,7 @@ pub struct Ready { pub args: Vec, } -impl Delegable for Ready { - type Builder = Builder; -} - -impl TryFrom> for Builder { +impl TryFrom> for Ready { type Error = (); fn try_from(named: arguments::Named) -> Result { @@ -74,10 +65,10 @@ impl TryFrom> for Builder { } } - Ok(Builder { - module, - function, - args, + Ok(Ready { + module: module.ok_or(())?, + function: function.ok_or(())?, + args: args.ok_or(())?, }) } } @@ -86,88 +77,6 @@ impl promise::Resolvable for Ready { type Promised = Promised; } -/// A variant meant for delegation, where fields may be omitted -#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] -pub struct Builder { - /// The Wasm module to run - pub module: Option, - - /// The function from the module to run - pub function: Option, - - /// Arguments to pass to the function - pub args: Option>, -} - -impl NoParents for Builder {} - -impl From for arguments::Named { - fn from(builder: Builder) -> Self { - let mut btree = BTreeMap::new(); - if let Some(module) = builder.module { - btree.insert("module".into(), Ipld::from(module)); - } - - if let Some(function) = builder.function { - btree.insert("function".into(), Ipld::String(function)); - } - - if let Some(args) = builder.args { - btree.insert("args".into(), Ipld::List(args)); - } - - arguments::Named(btree) - } -} - -impl From for Builder { - fn from(ready: Ready) -> Builder { - Builder { - module: Some(ready.module), - function: Some(ready.function), - args: Some(ready.args), - } - } -} - -impl TryFrom for Ready { - type Error = (); // FIXME - - fn try_from(b: Builder) -> Result { - Ok(Ready { - module: b.module.ok_or(())?, - function: b.function.ok_or(())?, - args: b.args.ok_or(())?, - }) - } -} - -impl CheckSame for Builder { - type Error = (); // FIXME - - fn check_same(&self, proof: &Self) -> Result<(), Self::Error> { - if let Some(module) = &self.module { - if module != proof.module.as_ref().unwrap() { - return Err(()); - } - } - - if let Some(function) = &self.function { - if function != proof.function.as_ref().unwrap() { - return Err(()); - } - } - - if let Some(args) = &self.args { - if args != proof.args.as_ref().unwrap() { - return Err(()); - } - } - - Ok(()) - } -} - /// A variant meant for linking together invocations with promises #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] pub struct Promised { diff --git a/src/delegation.rs b/src/delegation.rs index 5d81718e..e4c077db 100644 --- a/src/delegation.rs +++ b/src/delegation.rs @@ -16,18 +16,18 @@ pub mod condition; pub mod store; mod agent; -mod delegable; +// mod delegable; mod payload; pub use agent::Agent; -pub use delegable::Delegable; -pub use payload::{Payload, ValidationError}; +// pub use delegable::Delegable; +pub use payload::*; use crate::{ - ability, + // ability, crypto::{signature, varsig, Nonce}, did::{self, Did}, - proof::{parents::CheckParents, same::CheckSame}, + // proof::{parents::CheckParents, same::CheckSame}, time::{TimeBoundError, Timestamp}, }; use condition::Condition; @@ -47,16 +47,14 @@ use web_time::SystemTime; /// FIXME #[derive(Clone, Debug, PartialEq)] pub struct Delegation< - D, C: Condition, DID: Did, V: varsig::Header, Enc: Codec + TryFrom + Into, ->(pub signature::Envelope, DID, V, Enc>); +>(pub signature::Envelope, DID, V, Enc>); /// A variant of [`Delegation`] that has the abilties and DIDs from this library pre-filled. pub type Preset = Delegation< - ability::preset::Builder, condition::Preset, did::preset::Verifier, varsig::header::Preset, @@ -65,8 +63,8 @@ pub type Preset = Delegation< // FIXME checkable -> provable? -impl, Enc: Codec + Into + TryFrom> - Delegation +impl, Enc: Codec + Into + TryFrom> + Delegation { /// Retrive the `issuer` of a [`Delegation`] pub fn issuer(&self) -> &DID { @@ -74,7 +72,7 @@ impl, Enc: Codec + Into + } /// Retrive the `subject` of a [`Delegation`] - pub fn subject(&self) -> &DID { + pub fn subject(&self) -> &Option { &self.0.payload.subject } @@ -83,22 +81,6 @@ impl, Enc: Codec + Into + &self.0.payload.audience } - /// Retrive the `ability_builder` of a [`Delegation`] - pub fn ability_builder(&self) -> &B { - &self.0.payload.ability_builder - } - - pub fn map_ability_builder(self, f: F) -> Delegation - where - F: FnOnce(B) -> T, - { - Delegation(signature::Envelope::new( - self.0.varsig_header, - self.0.signature, - self.0.payload.map_ability(f), - )) - } - /// Retrive the `condition` of a [`Delegation`] pub fn conditions(&self) -> &[C] { &self.0.payload.conditions @@ -128,7 +110,7 @@ impl, Enc: Codec + Into + self.0.payload.check_time(now) } - pub fn payload(&self) -> &Payload { + pub fn payload(&self) -> &Payload { &self.0.payload } @@ -146,7 +128,7 @@ impl, Enc: Codec + Into + pub fn cid(&self) -> Result where - signature::Envelope, DID, V, Enc>: Clone + Encode, + signature::Envelope, DID, V, Enc>: Clone + Encode, Ipld: Encode, { self.0.cid() @@ -154,7 +136,7 @@ impl, Enc: Codec + Into + pub fn validate_signature(&self) -> Result<(), signature::ValidateError> where - Payload: Clone, + Payload: Clone, Ipld: Encode, { self.0.validate_signature() @@ -163,43 +145,43 @@ impl, Enc: Codec + Into + pub fn try_sign( signer: &DID::Signer, varsig_header: V, - payload: Payload, + payload: Payload, ) -> Result where Ipld: Encode, - Payload: Clone, + Payload: Clone, { signature::Envelope::try_sign(signer, varsig_header, payload).map(Delegation) } } -impl< - B: CheckSame, - C: Condition, - DID: Did, - V: varsig::Header, - Enc: Codec + TryFrom + Into, - > CheckSame for Delegation -{ - type Error = ::Error; - - fn check_same(&self, proof: &Delegation) -> Result<(), Self::Error> { - self.0.payload.check_same(&proof.payload()) - } -} - -impl< - T: CheckParents, - C: Condition, - DID: Did, - V: varsig::Header, - Enc: Codec + TryFrom + Into, - > CheckParents for Delegation -{ - type Parents = Delegation; - type ParentError = ::ParentError; - - fn check_parent(&self, proof: &Self::Parents) -> Result<(), Self::ParentError> { - self.payload().check_parent(&proof.payload()) - } -} +// impl< +// B: CheckSame, +// C: Condition, +// DID: Did, +// V: varsig::Header, +// Enc: Codec + TryFrom + Into, +// > CheckSame for Delegation +// { +// type Error = ::Error; +// +// fn check_same(&self, proof: &Delegation) -> Result<(), Self::Error> { +// self.0.payload.check_same(&proof.payload()) +// } +// } +// +// impl< +// T: CheckParents, +// C: Condition, +// DID: Did, +// V: varsig::Header, +// Enc: Codec + TryFrom + Into, +// > CheckParents for Delegation +// { +// type Parents = Delegation; +// type ParentError = ::ParentError; +// +// fn check_parent(&self, proof: &Self::Parents) -> Result<(), Self::ParentError> { +// self.payload().check_parent(&proof.payload()) +// } +// } diff --git a/src/delegation/agent.rs b/src/delegation/agent.rs index 389de622..42745c80 100644 --- a/src/delegation/agent.rs +++ b/src/delegation/agent.rs @@ -2,7 +2,7 @@ use super::{condition::Condition, payload::Payload, store::Store, Delegation}; use crate::{ crypto::{varsig, Nonce}, did::Did, - proof::checkable::Checkable, + // proof::checkable::Checkable, time::Timestamp, }; use libipld_core::{ @@ -20,10 +20,9 @@ use web_time::SystemTime; #[derive(Debug)] pub struct Agent< 'a, - B: Checkable, C: Condition, DID: Did, - S: Store, + S: Store, V: varsig::Header, Enc: Codec + TryFrom + Into, > { @@ -34,18 +33,17 @@ pub struct Agent< pub store: &'a mut S, signer: &'a ::Signer, - _marker: PhantomData<(B, C, V, Enc)>, + _marker: PhantomData<(C, V, Enc)>, } impl< 'a, - B: Checkable + Clone, C: Condition + Clone, DID: Did + ToString + Clone, - S: Store + Clone, + S: Store + Clone, V: varsig::Header, Enc: Codec + TryFrom + Into, - > Agent<'a, B, C, DID, S, V, Enc> + > Agent<'a, C, DID, S, V, Enc> where Ipld: Encode, { @@ -61,37 +59,39 @@ where pub fn delegate( &self, audience: DID, - subject: DID, - ability_builder: B, + subject: Option, new_conditions: Vec, metadata: BTreeMap, expiration: Timestamp, not_before: Option, now: SystemTime, varsig_header: V, - ) -> Result, DelegateError> { + ) -> Result, DelegateError> { let mut salt = self.did.clone().to_string().into_bytes(); let nonce = Nonce::generate_12(&mut salt); - if subject == *self.did { - let payload: Payload = Payload { - issuer: self.did.clone(), - audience, - subject, - ability_builder, - metadata, - nonce, - expiration: expiration.into(), - not_before: not_before.map(Into::into), - conditions: new_conditions, - }; - - return Ok(Delegation::try_sign(self.signer, varsig_header, payload).expect("FIXME")); + if let Some(ref sub) = subject { + if sub == self.did { + let payload: Payload = Payload { + issuer: self.did.clone(), + audience, + subject, + metadata, + nonce, + expiration: expiration.into(), + not_before: not_before.map(Into::into), + conditions: new_conditions, + }; + + return Ok( + Delegation::try_sign(self.signer, varsig_header, payload).expect("FIXME") + ); + } } let to_delegate = &self .store - .get_chain(&self.did, &subject, &ability_builder, vec![], now) + .get_chain(&self.did, &subject, vec![], now) .map_err(DelegateError::StoreError)? .ok_or(DelegateError::ProofsNotFound)? .first() @@ -101,11 +101,10 @@ where let mut conditions = to_delegate.conditions.clone(); conditions.append(&mut new_conditions.clone()); - let payload: Payload = Payload { + let payload: Payload = Payload { issuer: self.did.clone(), audience, subject, - ability_builder, conditions, metadata, nonce, @@ -119,7 +118,7 @@ where pub fn receive( &mut self, cid: Cid, // FIXME remove and generate from the capsule header? - delegation: Delegation, + delegation: Delegation, ) -> Result<(), ReceiveError> { if self.store.get(&cid).is_ok() { return Ok(()); diff --git a/src/delegation/delegable.rs b/src/delegation/delegable.rs index a8929ab1..9ab7e613 100644 --- a/src/delegation/delegable.rs +++ b/src/delegation/delegable.rs @@ -1,51 +1,51 @@ -use crate::{ - ability::{ - arguments, - command::ToCommand, - parse::{ParseAbility, ParseAbilityError}, - }, - proof::checkable::Checkable, -}; -use libipld_core::ipld::Ipld; - -/// A trait for types that can be delegated. -/// -/// Since [`Delegation`]s may omit fields (until [`Invocation`]), -/// this trait helps associate the delegatable variant to the invocable one. -/// -/// [`Delegation`]: crate::delegation::Delegation -/// [`Invocation`]: crate::invocation::Invocation -// FIXME NOTE: don't need parse ability, because parse -> builder -> self -// FIXME NOTE: don't need ToCommand ability, because parse -> builder -> self, or .. -> promieed -> .. -pub trait Delegable: Sized { - /// A delegation with some arguments filled. - type Builder: TryInto - + From - + Checkable - + ParseAbility - + ToCommand - + Into>; - - fn into_command(self) -> String { - Self::Builder::from(self).to_command() - } - - fn into_named_args(self) -> arguments::Named { - Self::Builder::from(self).into() - } - - fn try_parse_to_ready( - command: &str, - named: arguments::Named, - ) -> Result::ArgsErr>> { - let builder = Self::Builder::try_parse(command, named)?; - builder.try_into().map_err(|err| todo!()) - } - - fn try_from_named( - named: arguments::Named, - ) -> Result::ArgsErr>> { - let builder = Self::Builder::try_parse("", named)?; - builder.try_into().map_err(|err| todo!()) - } -} +// use crate::{ +// ability::{ +// arguments, +// command::ToCommand, +// parse::{ParseAbility, ParseAbilityError}, +// }, +// proof::checkable::Checkable, +// }; +// use libipld_core::ipld::Ipld; +// +// /// A trait for types that can be delegated. +// /// +// /// Since [`Delegation`]s may omit fields (until [`Invocation`]), +// /// this trait helps associate the delegatable variant to the invocable one. +// /// +// /// [`Delegation`]: crate::delegation::Delegation +// /// [`Invocation`]: crate::invocation::Invocation +// // FIXME NOTE: don't need parse ability, because parse -> builder -> self +// // FIXME NOTE: don't need ToCommand ability, because parse -> builder -> self, or .. -> promieed -> .. +// pub trait Delegable: Sized { +// /// A delegation with some arguments filled. +// type Builder: TryInto +// + From +// + Checkable +// + ParseAbility +// + ToCommand +// + Into>; +// +// fn into_command(self) -> String { +// Self::Builder::from(self).to_command() +// } +// +// fn into_named_args(self) -> arguments::Named { +// Self::Builder::from(self).into() +// } +// +// fn try_parse_to_ready( +// command: &str, +// named: arguments::Named, +// ) -> Result::ArgsErr>> { +// let builder = Self::Builder::try_parse(command, named)?; +// builder.try_into().map_err(|err| todo!()) +// } +// +// fn try_from_named( +// named: arguments::Named, +// ) -> Result::ArgsErr>> { +// let builder = Self::Builder::try_parse("", named)?; +// builder.try_into().map_err(|err| todo!()) +// } +// } diff --git a/src/delegation/payload.rs b/src/delegation/payload.rs index bd557979..bbde3e99 100644 --- a/src/delegation/payload.rs +++ b/src/delegation/payload.rs @@ -1,29 +1,13 @@ use super::condition::Condition; use crate::{ - ability::{ - arguments, - command::{Command, ToCommand}, - parse::ParseAbility, - }, capsule::Capsule, crypto::Nonce, did::{Did, Verifiable}, - proof::{ - checkable::Checkable, - parents::CheckParents, - prove::{Prove, Success}, - same::CheckSame, - }, time::{TimeBoundError, Timestamp}, }; use libipld_core::{error::SerdeError, ipld::Ipld, serde as ipld_serde}; -use serde::{ - de::{self, MapAccess, Visitor}, - ser::SerializeStruct, - Deserialize, Serialize, Serializer, -}; -use std::{collections::BTreeMap, fmt, fmt::Debug}; -use thiserror::Error; +use serde::{Deserialize, Serialize}; +use std::{collections::BTreeMap, fmt::Debug}; use web_time::SystemTime; #[cfg(feature = "test_utils")] @@ -36,8 +20,8 @@ use crate::ipld; /// /// This contains the semantic information about the delegation, including the /// issuer, subject, audience, the delegated ability, time bounds, and so on. -#[derive(Debug, Clone, PartialEq)] -pub struct Payload { +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub struct Payload { /// The subject of the [`Delegation`]. /// /// This role *must* have issued the earlier (root) @@ -48,7 +32,7 @@ pub struct Payload { /// by the subject. /// /// [`Delegation`]: super::Delegation - pub subject: DID, + pub subject: Option, /// The issuer of the [`Delegation`]. /// @@ -61,11 +45,6 @@ pub struct Payload { /// The agent being delegated to. pub audience: DID, - /// A delegatable ability chain. - /// - /// Note that this should be is some [Proof::Hierarchy] - pub ability_builder: D, - /// Any [`Condition`]s on the `ability_builder`. pub conditions: Vec, @@ -91,35 +70,7 @@ pub struct Payload { pub not_before: Option, } -impl Payload { - pub fn map_ability(self, f: impl FnOnce(D) -> T) -> Payload { - Payload { - issuer: self.issuer, - subject: self.subject, - audience: self.audience, - ability_builder: f(self.ability_builder), - conditions: self.conditions, - metadata: self.metadata, - nonce: self.nonce, - expiration: self.expiration, - not_before: self.not_before, - } - } - - pub fn map_conditon(self, f: impl FnMut(C) -> T) -> Payload { - Payload { - issuer: self.issuer, - subject: self.subject, - audience: self.audience, - ability_builder: self.ability_builder, - conditions: self.conditions.into_iter().map(f).collect(), - metadata: self.metadata, - nonce: self.nonce, - expiration: self.expiration, - not_before: self.not_before, - } - } - +impl Payload { pub fn check_time(&self, now: SystemTime) -> Result<(), TimeBoundError> { let ts_now = &Timestamp::postel(now); @@ -137,227 +88,18 @@ impl Payload { } } -impl Capsule for Payload { - const TAG: &'static str = "ucan/d/1.0.0-rc.1"; +impl Capsule for Payload { + const TAG: &'static str = "ucan/d/1.0"; } -impl Verifiable for Payload { +impl Verifiable for Payload { fn verifier(&self) -> &DID { &self.issuer } } -impl CheckSame for Payload { - type Error = ::Error; - - fn check_same(&self, proof: &Payload) -> Result<(), Self::Error> { - self.ability_builder.check_same(&proof.ability_builder) - } -} - -impl CheckParents for Payload { - type Parents = Payload; - type ParentError = ::ParentError; - - fn check_parent(&self, proof: &Self::Parents) -> Result<(), Self::ParentError> { - self.ability_builder.check_parent(&proof.ability_builder) - } -} - -impl Serialize - for Payload -where - Ipld: From, - arguments::Named: From, -{ - fn serialize(&self, serializer: S) -> Result - where - S: Serializer, - { - let count_nbf = self.not_before.is_some() as usize; - let mut state = serializer.serialize_struct("delegation::Payload", 8 + count_nbf)?; - - state.serialize_field("iss", &self.issuer.clone().into().to_string())?; - state.serialize_field("sub", &self.subject.clone().into().to_string())?; - state.serialize_field("aud", &self.audience.clone().into().to_string())?; - state.serialize_field("meta", &self.metadata)?; - state.serialize_field("nonce", &self.nonce)?; - state.serialize_field("exp", &self.expiration)?; - - state.serialize_field("cmd", &self.ability_builder.to_command())?; - - state.serialize_field( - "args", - &arguments::Named::from(self.ability_builder.clone()), - )?; - - state.serialize_field( - "cond", - &self - .conditions - .iter() - .map(|c| Ipld::from(c.clone())) - .collect::>(), - )?; - - if let Some(nbf) = self.not_before { - state.serialize_field("nbf", &nbf)?; - } - - state.end() - } -} - -impl< - 'de, - T: ParseAbility + Deserialize<'de> + ToCommand, - C: Condition + Deserialize<'de>, - DID: Did + Deserialize<'de>, - > Deserialize<'de> for Payload -{ - fn deserialize(deserializer: D) -> Result - where - D: serde::Deserializer<'de>, - { - struct DelegationPayloadVisitor( - std::marker::PhantomData<(T, C, DID)>, - ); - - const FIELDS: &'static [&'static str] = &[ - "iss", "sub", "aud", "cmd", "args", "cond", "meta", "nonce", "exp", "nbf", - ]; - - impl< - 'de, - T: ParseAbility + Deserialize<'de>, - C: Condition + Deserialize<'de>, - DID: Did + Deserialize<'de>, - > Visitor<'de> for DelegationPayloadVisitor - { - type Value = Payload; - - fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { - formatter.write_str("struct delegation::Payload") - } - - fn visit_map>(self, mut map: M) -> Result { - let mut issuer = None; - let mut subject = None; - let mut audience = None; - let mut command = None; - let mut arguments = None; - let mut conditions = None; - let mut metadata = None; - let mut nonce = None; - let mut expiration = None; - let mut not_before = None; - - while let Some(key) = map.next_key()? { - match key { - "iss" => { - if issuer.is_some() { - return Err(de::Error::duplicate_field("iss")); - } - issuer = Some(map.next_value()?); - } - "sub" => { - if subject.is_some() { - return Err(de::Error::duplicate_field("sub")); - } - subject = Some(map.next_value()?); - } - "aud" => { - if audience.is_some() { - return Err(de::Error::duplicate_field("aud")); - } - audience = Some(map.next_value()?); - } - "cmd" => { - if command.is_some() { - return Err(de::Error::duplicate_field("cmd")); - } - command = Some(map.next_value()?); - } - "args" => { - if arguments.is_some() { - return Err(de::Error::duplicate_field("args")); - } - arguments = Some(map.next_value()?); - } - "cond" => { - if conditions.is_some() { - return Err(de::Error::duplicate_field("cond")); - } - conditions = Some(map.next_value()?); - } - "meta" => { - if metadata.is_some() { - return Err(de::Error::duplicate_field("meta")); - } - metadata = Some(map.next_value()?); - } - "nonce" => { - if nonce.is_some() { - return Err(de::Error::duplicate_field("nonce")); - } - nonce = Some(map.next_value()?); - } - "exp" => { - if expiration.is_some() { - return Err(de::Error::duplicate_field("exp")); - } - expiration = Some(map.next_value()?); - } - "nbf" => { - if not_before.is_some() { - return Err(de::Error::duplicate_field("nbf")); - } - not_before = Some(map.next_value()?); - } - other => { - return Err(de::Error::unknown_field(other, FIELDS)); - } - } - } - - let cmd: String = command.ok_or(de::Error::missing_field("cmd"))?; - let args = arguments.ok_or(de::Error::missing_field("args"))?; - - let ability_builder = - ::try_parse(cmd.as_str(), args).map_err(|e| { - de::Error::custom(format!( - "Unable to parse ability field for {:?} because {:?}", - cmd, e - )) - })?; - - Ok(Payload { - issuer: issuer.ok_or(de::Error::missing_field("iss"))?, - subject: subject.ok_or(de::Error::missing_field("sub"))?, - audience: audience.ok_or(de::Error::missing_field("aud"))?, - conditions: conditions.ok_or(de::Error::missing_field("cond"))?, - metadata: metadata.ok_or(de::Error::missing_field("meta"))?, - nonce: nonce.ok_or(de::Error::missing_field("nonce"))?, - expiration: expiration.ok_or(de::Error::missing_field("exp"))?, - ability_builder, - not_before, - }) - } - } - - deserializer.deserialize_struct( - "DelegationPayload", - FIELDS, - DelegationPayloadVisitor(Default::default()), - ) - } -} - -impl< - T: ParseAbility + Command + for<'de> Deserialize<'de>, - C: Condition + for<'de> Deserialize<'de>, - DID: Did + for<'de> Deserialize<'de>, - > TryFrom for Payload +impl Deserialize<'de>, DID: Did + for<'de> Deserialize<'de>> TryFrom + for Payload { type Error = SerdeError; @@ -366,150 +108,25 @@ impl< } } -impl From> for Ipld { - fn from(payload: Payload) -> Self { +impl From> for Ipld { + fn from(payload: Payload) -> Self { payload.into() } } -impl>, C: Condition, DID: Did> Payload { - pub fn check( - &self, - proofs: Vec<&Payload>, - now: &SystemTime, - ) -> Result<(), ValidationError<::Error, C>> - where - T: Clone, - C: Clone, - DID: Clone, - T::Hierarchy: Clone + Into>, - { - let start: Acc = Acc { - issuer: self.issuer.clone(), - subject: self.subject.clone(), - hierarchy: T::Hierarchy::from(self.ability_builder.clone()), - }; - - let args: arguments::Named = self.ability_builder.clone().into(); - - proofs.into_iter().fold(Ok(start), |prev, proof| { - if let Ok(prev_) = prev { - prev_.step(&proof, &args, now).map(move |success| { - match success { - Success::ProvenByAny => Acc { - issuer: proof.issuer.clone(), - subject: proof.subject.clone(), - hierarchy: prev_.hierarchy, - }, - Success::Proven => Acc { - issuer: proof.issuer.clone(), - subject: proof.subject.clone(), - hierarchy: proof.ability_builder.clone(), // FIXME double check - }, - } - }) - } else { - prev - } - })?; - - Ok(()) - } -} - -#[derive(Debug, Clone)] -struct Acc { - issuer: DID, - subject: DID, - hierarchy: H, -} - -impl Acc { - // FIXME this should move to Delegable? - fn step<'a, C: Condition>( - &self, - proof: &Payload, - args: &arguments::Named, - now: &SystemTime, - ) -> Result::Error, C>> - where - C: Clone, - H: Prove + Clone + Into>, - { - if self.issuer != proof.audience { - return Err(ValidationError::InvalidSubject.into()); - } - - if self.subject != proof.subject { - return Err(ValidationError::MisalignedIssAud.into()); - } - - if SystemTime::from(proof.expiration.clone()) > *now { - return Err(ValidationError::Expired.into()); - } - - if let Some(nbf) = proof.not_before.clone() { - if SystemTime::from(nbf) > *now { - return Err(ValidationError::NotYetValid.into()); - } - } - - // This could be more efficient (dedup) with sets, but floats don't Ord :( - for c in proof.conditions.iter() { - // Validate both current & proof integrity. - // This should have the same semantic guarantees as looking at subsets, - // but for all known conditions will run much faster on average. - // Plz let me know if I got this wrong. - // —@expede - if !c.validate(&args) || !c.validate(&self.hierarchy.clone().into()) { - return Err(ValidationError::FailedCondition(c.clone())); - } - } - - self.hierarchy - .check(&proof.ability_builder.clone()) - .map_err(ValidationError::AbilityError) - } -} - -/// Delegation validation errors. -#[derive(Debug, Clone, PartialEq, Eq, Error)] -pub enum ValidationError { - #[error("The subject of the delegation is invalid")] - InvalidSubject, - - #[error("The issuer and audience of the delegation are misaligned")] - MisalignedIssAud, - - #[error("The delegation has expired")] - Expired, - - #[error("The delegation is not yet valid")] - NotYetValid, - - #[error("The delegation failed a condition: {0:?}")] - FailedCondition(C), - - #[error(transparent)] - AbilityError(AbilityError), -} - #[cfg(feature = "test_utils")] -impl Arbitrary - for Payload +impl Arbitrary for Payload where - T::Strategy: 'static, C::Strategy: 'static, DID::Parameters: Clone, C::Parameters: Clone, { - type Parameters = (T::Parameters, DID::Parameters, C::Parameters); + type Parameters = (DID::Parameters, C::Parameters); type Strategy = BoxedStrategy; - fn arbitrary_with((t_args, did_args, c_args): Self::Parameters) -> Self::Strategy { + fn arbitrary_with((did_args, c_args): Self::Parameters) -> Self::Strategy { ( - T::arbitrary_with(t_args), - DID::arbitrary_with(did_args.clone()), + Option::::arbitrary(), DID::arbitrary_with(did_args.clone()), DID::arbitrary_with(did_args), Nonce::arbitrary(), @@ -524,10 +141,9 @@ where ) .prop_map( |( - ability_builder, + subject, issuer, audience, - subject, nonce, expiration, not_before, @@ -538,7 +154,6 @@ where issuer, subject, audience, - ability_builder, conditions, metadata, nonce, diff --git a/src/delegation/store/memory.rs b/src/delegation/store/memory.rs index e87399af..e87bfc5d 100644 --- a/src/delegation/store/memory.rs +++ b/src/delegation/store/memory.rs @@ -1,12 +1,12 @@ use super::Store; use crate::{ - ability::arguments, + // ability::arguments, crypto::varsig, delegation::{condition::Condition, Delegation}, did::Did, - proof::{checkable::Checkable, prove::Prove}, + // proof::{checkable::Checkable, prove::Prove}, }; -use libipld_core::{cid::Cid, codec::Codec, ipld::Ipld}; +use libipld_core::{cid::Cid, codec::Codec}; use nonempty::NonEmpty; use std::{ collections::{BTreeMap, BTreeSet}, @@ -72,41 +72,34 @@ use web_time::SystemTime; /// ``` #[derive(Debug, Clone, PartialEq)] pub struct MemoryStore< - H, C: Condition, DID: Did + Ord, V: varsig::Header, Enc: Codec + TryFrom + Into, > { - ucans: BTreeMap>, - index: BTreeMap>>, + ucans: BTreeMap>, + index: BTreeMap, BTreeMap>>, revocations: BTreeSet, } // FIXME check that UCAN is valid impl< - B: Checkable + Clone, C: Condition + PartialEq, DID: Did + Ord + Clone, V: varsig::Header, Enc: Codec + TryFrom + Into, - > Store for MemoryStore -where - B::Hierarchy: Into> + Clone, + > Store for MemoryStore { type DelegationStoreError = (); // FIXME misisng - fn get( - &self, - cid: &Cid, - ) -> Result<&Delegation, Self::DelegationStoreError> { + fn get(&self, cid: &Cid) -> Result<&Delegation, Self::DelegationStoreError> { self.ucans.get(cid).ok_or(()) } fn insert( &mut self, cid: Cid, - delegation: Delegation, + delegation: Delegation, ) -> Result<(), Self::DelegationStoreError> { self.index .entry(delegation.subject().clone()) @@ -115,10 +108,7 @@ where .or_default() .insert(cid); - let hierarchy: Delegation = - delegation.map_ability_builder(Into::into); - - self.ucans.insert(cid.clone(), hierarchy); + self.ucans.insert(cid.clone(), delegation); Ok(()) } @@ -130,15 +120,16 @@ where fn get_chain( &self, aud: &DID, - subject: &DID, - builder: &B, + subject: &Option, conditions: Vec, now: SystemTime, - ) -> Result< - Option)>>, - Self::DelegationStoreError, - > { - match self.index.get(subject).and_then(|aud_map| aud_map.get(aud)) { + ) -> Result)>>, Self::DelegationStoreError> + { + match self + .index + .get(subject) // FIXME probably need to rework this after last minbute chanegs + .and_then(|aud_map| aud_map.get(aud)) + { None => Ok(None), Some(delegation_subtree) => { #[derive(PartialEq)] @@ -150,7 +141,6 @@ where let mut status = Status::Looking; let mut target_aud = aud; - let mut args = &B::Hierarchy::from(builder.clone()); let mut chain = vec![]; while status == Status::Looking { @@ -166,24 +156,14 @@ where target_aud = &d.audience(); - if args.check(&d.ability_builder()).is_ok() { - args = &d.ability_builder(); - } else { - return ControlFlow::Continue(()); - } - - for condition in &conditions { - if !condition.validate(&d.ability_builder().clone().into()) { - return ControlFlow::Continue(()); - } - } - chain.push((*cid, d)); - if d.issuer() == subject { - status = Status::Complete; + if let Some(ref subject) = subject { + if d.issuer() == subject { + status = Status::Complete; + } } else { - target_aud = &d.issuer(); + status = Status::Complete; } ControlFlow::Break(()) diff --git a/src/delegation/store/traits.rs b/src/delegation/store/traits.rs index 93c6d525..44b032dd 100644 --- a/src/delegation/store/traits.rs +++ b/src/delegation/store/traits.rs @@ -2,7 +2,7 @@ use crate::{ crypto::varsig, delegation::{condition::Condition, Delegation}, did::Did, - proof::checkable::Checkable, + // proof::checkable::Checkable, }; use libipld_core::{cid::Cid, codec::Codec}; use nonempty::NonEmpty; @@ -11,7 +11,6 @@ use web_time::SystemTime; // NOTE the T here is the builder... FIXME add one layer up and call T::Builder? May be confusing? pub trait Store< - B: Checkable, C: Condition, DID: Did, V: varsig::Header, @@ -20,10 +19,7 @@ pub trait Store< { type DelegationStoreError: Debug; - fn get( - &self, - cid: &Cid, - ) -> Result<&Delegation, Self::DelegationStoreError>; + fn get(&self, cid: &Cid) -> Result<&Delegation, Self::DelegationStoreError>; // FIXME add a variant that calculated the CID from the capsulre header? // FIXME that means changing the name to insert_by_cid or similar @@ -31,7 +27,7 @@ pub trait Store< fn insert( &mut self, cid: Cid, - delegation: Delegation, + delegation: Delegation, ) -> Result<(), Self::DelegationStoreError>; // FIXME validate invocation @@ -42,33 +38,28 @@ pub trait Store< fn get_chain( &self, audience: &DID, - subject: &DID, - builder: &B, + subject: &Option, conditions: Vec, now: SystemTime, - ) -> Result< - Option)>>, - Self::DelegationStoreError, - >; + ) -> Result)>>, Self::DelegationStoreError>; fn can_delegate( &self, - issuer: &DID, + issuer: DID, audience: &DID, - builder: &B, conditions: Vec, now: SystemTime, ) -> Result { - self.get_chain(audience, issuer, builder, conditions, now) + self.get_chain(audience, &Some(issuer), conditions, now) .map(|chain| chain.is_some()) } fn get_many( &self, cids: &[Cid], - ) -> Result>, Self::DelegationStoreError> { + ) -> Result>, Self::DelegationStoreError> { cids.iter().try_fold(vec![], |mut acc, cid| { - let d: &Delegation = self.get(cid)?; + let d: &Delegation = self.get(cid)?; acc.push(d); Ok(acc) }) diff --git a/src/invocation.rs b/src/invocation.rs index 2aa36b8e..7e66670e 100644 --- a/src/invocation.rs +++ b/src/invocation.rs @@ -19,7 +19,7 @@ pub mod promise; pub mod store; pub use agent::Agent; -pub use payload::{Payload, Promised}; +pub use payload::*; use crate::{ ability, diff --git a/src/invocation/agent.rs b/src/invocation/agent.rs index 9bc24aab..5e728646 100644 --- a/src/invocation/agent.rs +++ b/src/invocation/agent.rs @@ -1,15 +1,20 @@ -use super::{payload::Payload, promise::Resolvable, store::Store, Invocation}; +use super::{ + payload::{Payload, ValidationError}, + promise::Resolvable, + store::Store, + Invocation, +}; use crate::{ ability::{ + arguments, parse::{ParseAbility, ParseAbilityError, ParsePromised}, ucan, }, crypto::{signature, varsig, Nonce}, - delegation, - delegation::{condition::Condition, Delegable}, + delegation::{self, condition::Condition}, did::Did, invocation::promise, - proof::{checkable::Checkable, prove::Prove}, + // proof::prove::Prove, time::Timestamp, }; use libipld_core::{ @@ -28,12 +33,12 @@ use web_time::SystemTime; #[derive(Debug)] pub struct Agent< 'a, - T: Resolvable + Delegable, + T: Resolvable, C: Condition, DID: Did, S: Store, P: promise::Store, - D: delegation::store::Store, + D: delegation::store::Store, V: varsig::Header, Enc: Codec + Into + TryFrom, > { @@ -51,13 +56,13 @@ impl<'a, T, C, DID, S, P, D, V, Enc> Agent<'a, T, C, DID, S, P, D, V, Enc> where T::Promised: Clone, Ipld: Encode, - delegation::Payload<::Hierarchy, C, DID>: Clone, - T: Resolvable + Delegable + Clone, + delegation::Payload: Clone, + T: Resolvable + Clone, C: Condition, DID: Did + Clone, S: Store, P: promise::Store, - D: delegation::store::Store, + D: delegation::store::Store, V: varsig::Header, Enc: Codec + Into + TryFrom, { @@ -80,8 +85,8 @@ where pub fn invoke( &mut self, - audience: Option<&DID>, - subject: &DID, + audience: Option, + subject: DID, ability: T, metadata: BTreeMap, cause: Option, @@ -90,32 +95,42 @@ where now: SystemTime, varsig_header: V, ) -> Result< - Invocation, + Invocation, InvokeError< D::DelegationStoreError, - ParseAbilityError<<::Builder as ParseAbility>::ArgsErr>, + ParseAbilityError<()>, // FIXME argserror >, - > - where - <::Promised as ParsePromised>::PromisedArgsError: fmt::Debug, - { - self.invoke_promise( - audience, + > { + let proofs = self + .delegation_store + .get_chain(self.did, &Some(subject.clone()), vec![], now) + .map_err(InvokeError::DelegationStoreError)? + .map(|chain| chain.map(|(cid, _)| cid).into()) + .unwrap_or(vec![]); + + let mut seed = vec![]; + + let payload = Payload { + issuer: self.did.clone(), subject, - Resolvable::into_promised(ability), + audience, + ability, + proofs, metadata, + nonce: Nonce::generate_12(&mut seed), cause, expiration, issued_at, - now, - varsig_header, - ) + }; + + Ok(Invocation::try_sign(self.signer, varsig_header, payload) + .map_err(InvokeError::SignError)?) } pub fn invoke_promise( &mut self, audience: Option<&DID>, - subject: &DID, + subject: DID, ability: T::Promised, metadata: BTreeMap, cause: Option, @@ -127,19 +142,12 @@ where Invocation, InvokeError< D::DelegationStoreError, - ParseAbilityError<<::Builder as ParseAbility>::ArgsErr>, + ParseAbilityError<()>, // FIXME errs >, > { let proofs = self .delegation_store - .get_chain( - self.did, - subject, - &::try_to_builder(ability.clone()) - .map_err(InvokeError::PromiseResolveError)?, - vec![], - now, - ) + .get_chain(self.did, &Some(subject.clone()), vec![], now) .map_err(InvokeError::DelegationStoreError)? .map(|chain| chain.map(|(cid, _)| cid).into()) .unwrap_or(vec![]); @@ -148,7 +156,7 @@ where let payload = Payload { issuer: self.did.clone(), - subject: subject.clone(), + subject, audience: audience.cloned(), ability, proofs, @@ -172,12 +180,10 @@ where ReceiveError, > where - Enc: From + Into, - T::Builder: Clone + Encode, C: Clone, - ::Hierarchy: Clone, + Enc: From + Into, + arguments::Named: From, Invocation: Clone, - <<::Builder as Checkable>::Hierarchy as Prove>::Error: fmt::Debug,

>::PromiseStoreError: fmt::Debug, signature::Envelope, DID, V, Enc>: Clone, ::Promised, DID, V, Enc>>::InvocationStoreError: fmt::Debug, @@ -217,9 +223,9 @@ where let resolved_payload = promised.payload().clone().map_ability(|_| resolved_ability); - delegation::Payload::::from(resolved_payload.clone()) + let _ = &resolved_payload .check(proof_payloads, now) - .map_err(ReceiveError::DelegationValidationError)?; + .map_err(ReceiveError::ValidationError)?; if promised.audience() != &Some(self.did.clone()) { return Ok(Recipient::Other(resolved_payload)); @@ -230,7 +236,7 @@ where pub fn revoke( &mut self, - subject: &DID, + subject: DID, cause: Option, cid: Cid, now: Timestamp, @@ -241,17 +247,11 @@ where T: From, { let ability: T = ucan::revoke::Ready { ucan: cid.clone() }.into(); - let proofs = if subject == self.did { + let proofs = if &subject == self.did { vec![] } else { self.delegation_store - .get_chain( - subject, - self.did, - &ability.clone().into(), - vec![], - now.into(), - ) + .get_chain(&subject, &Some(self.did.clone()), vec![], now.into()) .map_err(|_| ())? .map(|chain| chain.map(|(index_cid, _)| index_cid).into()) .unwrap_or(vec![]) @@ -297,10 +297,6 @@ pub enum ReceiveError< V: varsig::Header, Enc: Codec + From + Into, > where - delegation::ValidationError< - <<::Builder as Checkable>::Hierarchy as Prove>::Error, - C, - >: fmt::Debug,

>::PromiseStoreError: fmt::Debug, ::Promised, DID, V, Enc>>::InvocationStoreError: fmt::Debug, { @@ -322,13 +318,7 @@ pub enum ReceiveError< DelegationStoreError(#[source] D), #[error("delegation validation error: {0}")] - DelegationValidationError( - #[source] - delegation::ValidationError< - <<::Builder as Checkable>::Hierarchy as Prove>::Error, - C, - >, - ), + ValidationError(#[source] ValidationError), } #[derive(Debug, Error)] diff --git a/src/invocation/payload.rs b/src/invocation/payload.rs index fa0e9aea..0350aa98 100644 --- a/src/invocation/payload.rs +++ b/src/invocation/payload.rs @@ -3,9 +3,8 @@ use crate::{ ability::{arguments, command::ToCommand, parse::ParseAbility}, capsule::Capsule, crypto::Nonce, - delegation::{self, condition::Condition, Delegable, ValidationError}, + delegation::{self, condition::Condition}, //, ValidationError}, did::{Did, Verifiable}, - proof::{checkable::Checkable, prove::Prove}, time::{Expired, Timestamp}, }; use libipld_core::{cid::Cid, ipld::Ipld}; @@ -14,7 +13,8 @@ use serde::{ ser::SerializeStruct, Deserialize, Serialize, Serializer, }; -use std::{collections::BTreeMap, fmt::Debug}; +use std::{collections::BTreeMap, fmt}; +use thiserror::Error; use web_time::SystemTime; #[cfg(feature = "test_utils")] @@ -132,48 +132,95 @@ impl Payload { Ok(()) } - pub fn check( - self, - proofs: Vec<&delegation::Payload<::Hierarchy, C, DID>>, + pub fn check( + &self, + proofs: Vec<&delegation::Payload>, now: &SystemTime, - ) -> Result<(), ValidationError<<::Hierarchy as Prove>::Error, C>> + ) -> Result<(), ValidationError> where - A: Delegable, - A::Builder: Clone + Into>, - ::Hierarchy: Clone + Into>, + A: Clone, DID: Clone, + arguments::Named: From, { - let builder_payload: delegation::Payload = self.into(); - builder_payload.check(proofs, now) + let args: arguments::Named = self.ability.clone().into(); + + proofs.into_iter().try_fold(&self.issuer, |iss, proof| { + // FIXME extract step function? + if *iss != proof.audience { + return Err(ValidationError::InvalidSubject.into()); + } + + if let Some(proof_subject) = &proof.subject { + if self.subject != *proof_subject { + return Err(ValidationError::MisalignedIssAud.into()); + } + } + + if SystemTime::from(proof.expiration.clone()) > *now { + return Err(ValidationError::Expired.into()); + } + + if let Some(nbf) = proof.not_before.clone() { + if SystemTime::from(nbf) > *now { + return Err(ValidationError::NotYetValid.into()); + } + } + + for predicate in proof.conditions.iter() { + if !predicate.validate(&args) { + return Err(ValidationError::FailedCondition(predicate.clone())); + } + } + + Ok(&proof.issuer) + })?; + + Ok(()) } } +/// Delegation validation errors. +#[derive(Debug, Clone, PartialEq, Eq, Error)] +pub enum ValidationError { + #[error("The subject of the delegation is invalid")] + InvalidSubject, + + #[error("The issuer and audience of the delegation are misaligned")] + MisalignedIssAud, + + #[error("The delegation has expired")] + Expired, + + #[error("The delegation is not yet valid")] + NotYetValid, + + #[error("The delegation failed a condition: {0:?}")] + FailedCondition(C), +} + impl Capsule for Payload { const TAG: &'static str = "ucan/i/1.0.0-rc.1"; } -impl From> - for delegation::Payload -{ - fn from(inv_payload: Payload) -> Self { - delegation::Payload { - issuer: inv_payload.issuer, - subject: inv_payload.subject.clone(), - audience: inv_payload.audience.unwrap_or(inv_payload.subject), - - ability_builder: A::Builder::from(inv_payload.ability), - conditions: vec![], - - metadata: inv_payload.metadata, - nonce: inv_payload.nonce, - - not_before: None, - expiration: inv_payload - .expiration - .unwrap_or(Timestamp::postel(SystemTime::now())), - } - } -} +// impl From> for delegation::Payload { +// fn from(inv_payload: Payload) -> Self { +// delegation::Payload { +// issuer: inv_payload.issuer, +// subject: Some(inv_payload.subject.clone()), +// audience: inv_payload.audience.unwrap_or(inv_payload.subject), +// +// conditions: vec![], +// +// metadata: inv_payload.metadata, +// nonce: inv_payload.nonce, +// +// not_before: None, +// expiration: inv_payload +// .expiration +// .unwrap_or(Timestamp::postel(SystemTime::now())), +// } +// } +// } impl, DID: Did> From> for arguments::Named { fn from(payload: Payload) -> Self { @@ -408,7 +455,7 @@ impl From> for Ipld { } #[cfg(feature = "test_utils")] -impl Arbitrary for Payload +impl Arbitrary for Payload where T::Strategy: 'static, DID::Parameters: Clone, diff --git a/src/invocation/promise/resolvable.rs b/src/invocation/promise/resolvable.rs index 17c9cd0d..e9b351ad 100644 --- a/src/invocation/promise/resolvable.rs +++ b/src/invocation/promise/resolvable.rs @@ -4,7 +4,6 @@ use crate::{ command::ToCommand, parse::{ParseAbility, ParseAbilityError, ParsePromised}, }, - delegation::Delegable, invocation::promise::Pending, ipld, }; @@ -18,7 +17,7 @@ use thiserror::Error; /// A trait for [`Delegable`]s that can be deferred (by promises). /// /// FIXME exmaples -pub trait Resolvable: Delegable { +pub trait Resolvable: Sized + ParseAbility + ToCommand { /// The promise type that resolves to `Self`. /// /// Note that this may be a more complex type than the promise selector @@ -30,18 +29,17 @@ pub trait Resolvable: Delegable { + ParsePromised // TryFrom> + Into>; - fn into_promised(self) -> Self::Promised - where - ::PromisedArgsError: fmt::Debug, - { - // FIXME In no way efficient... override where possible, or just cut the impl - let builder = Self::Builder::from(self); - let cmd = &builder.to_command(); - let named_ipld: arguments::Named = builder.into(); - let promised_ipld: arguments::Named = named_ipld.into(); - ::Promised::try_parse_promised(cmd, promised_ipld) - .expect("promise to always be possible from a ready ability") - } + // fn into_promised(self) -> Self::Promised + // where + // ::PromisedArgsError: fmt::Debug, + // { + // // FIXME In no way efficient... override where possible, or just cut the impl + // let cmd = &builder.to_command(); + // let named_ipld: arguments::Named = builder.into(); + // let promised_ipld: arguments::Named = named_ipld.into(); + // ::Promised::try_parse_promised(cmd, promised_ipld) + // .expect("promise to always be possible from a ready ability") + // } /// Attempt to resolve the [`Self::Promised`]. fn try_resolve(promised: Self::Promised) -> Result> @@ -55,15 +53,11 @@ pub trait Resolvable: Delegable { reason: ResolveError::StillWaiting(pending), }), Ok(named) => { - let builder = Self::Builder::try_parse(promised.to_command().as_str(), named) - .map_err(|_reason| CantResolve { - promised: promised.clone(), + ParseAbility::try_parse(&promised.to_command(), named).map_err(|_reason| { + CantResolve { + promised, reason: ResolveError::ConversionError, - })?; - - builder.try_into().map_err(|_reason| CantResolve { - promised, - reason: ResolveError::ConversionError, + } }) } } @@ -82,32 +76,6 @@ pub trait Resolvable: Delegable { set }) } - - fn try_to_builder( - promised: Self::Promised, - ) -> Result< - Self::Builder, - ParseAbilityError<<::Builder as ParseAbility>::ArgsErr>, - > { - let cmd = promised.to_command(); - let ipld_promise: arguments::Named = promised.into(); - - let named: arguments::Named = - ipld_promise - .into_iter() - .fold(arguments::Named::new(), |mut acc, (k, v)| { - match v.try_into() { - Err(_) => (), - Ok(ipld) => { - acc.insert(k, ipld); // i.e. forget any promises - } - } - - acc - }); - - Self::Builder::try_parse(&cmd, named) - } } #[derive(Error, Clone)] @@ -119,7 +87,6 @@ pub struct CantResolve { impl fmt::Debug for CantResolve where S::Promised: fmt::Debug, - <::Builder as ParseAbility>::ArgsErr: fmt::Debug, Pending: fmt::Debug, { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { diff --git a/src/lib.rs b/src/lib.rs index 694c91aa..32a40fdc 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -21,7 +21,7 @@ pub mod delegation; pub mod did; pub mod invocation; pub mod ipld; -pub mod proof; +//pub mod proof; pub mod reader; pub mod receipt; pub mod task; diff --git a/src/proof.rs b/src/proof.rs index 82092cb5..de58a8ff 100644 --- a/src/proof.rs +++ b/src/proof.rs @@ -1,12 +1,12 @@ //! Proof chains, checking, and utilities. -pub mod checkable; -pub mod error; -pub mod parentful; -pub mod parentless; -pub mod parents; -pub mod prove; -pub mod same; +// pub mod checkable; +// pub mod error; +// pub mod parentful; +// pub mod parentless; +// pub mod parents; +// pub mod prove; +// pub mod same; // NOTE must remain *un*exported! -pub(super) mod internal; +// pub(super) mod internal; diff --git a/src/url.rs b/src/url.rs index efa2f68f..d23b8efb 100644 --- a/src/url.rs +++ b/src/url.rs @@ -1,6 +1,6 @@ //! URL utilities. -use crate::proof::same::CheckSame; +// use crate::proof::same::CheckSame; use libipld_core::ipld::Ipld; use serde::{Deserialize, Serialize}; use std::fmt; @@ -50,17 +50,17 @@ impl fmt::Display for Newtype { } } -impl CheckSame for Newtype { - type Error = (); - - fn check_same(&self, other: &Self) -> Result<(), Self::Error> { - if self == other { - Ok(()) - } else { - Err(()) - } - } -} +// impl CheckSame for Newtype { +// type Error = (); +// +// fn check_same(&self, other: &Self) -> Result<(), Self::Error> { +// if self == other { +// Ok(()) +// } else { +// Err(()) +// } +// } +// } impl From for Ipld { fn from(newtype: Newtype) -> Self { From 56668999bcb5becbeefa0eb3898c1c349ae03488 Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Tue, 27 Feb 2024 12:26:32 -0800 Subject: [PATCH 102/188] Working on policy DSL --- src/delegation.rs | 1 + src/delegation/condition.rs | 1 + src/delegation/policy.rs | 2 + src/delegation/policy/frontend.rs | 62 +++++ src/delegation/policy/ir.rs | 379 ++++++++++++++++++++++++++++++ 5 files changed, 445 insertions(+) create mode 100644 src/delegation/policy.rs create mode 100644 src/delegation/policy/frontend.rs create mode 100644 src/delegation/policy/ir.rs diff --git a/src/delegation.rs b/src/delegation.rs index e4c077db..543ce82b 100644 --- a/src/delegation.rs +++ b/src/delegation.rs @@ -13,6 +13,7 @@ //! - [`store`] is an interface for caching [`Delegation`]s. pub mod condition; +pub mod policy; pub mod store; mod agent; diff --git a/src/delegation/condition.rs b/src/delegation/condition.rs index f9594bbc..868f90b9 100644 --- a/src/delegation/condition.rs +++ b/src/delegation/condition.rs @@ -27,6 +27,7 @@ pub use traits::Condition; use crate::ability::arguments; use libipld_core::{error::SerdeError, ipld::Ipld, serde as ipld_serde}; use serde_derive::{Deserialize, Serialize}; +use std::collections::BTreeMap; /// The union of the common [`Condition`]s that ship directly with this library. #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] diff --git a/src/delegation/policy.rs b/src/delegation/policy.rs new file mode 100644 index 00000000..6f111432 --- /dev/null +++ b/src/delegation/policy.rs @@ -0,0 +1,2 @@ +pub mod frontend; +pub mod ir; diff --git a/src/delegation/policy/frontend.rs b/src/delegation/policy/frontend.rs new file mode 100644 index 00000000..4ac89bb9 --- /dev/null +++ b/src/delegation/policy/frontend.rs @@ -0,0 +1,62 @@ +use super::ir; +use libipld_core::ipld::Ipld; +use serde::{Deserialize, Serialize}; +use std::collections::BTreeMap; + +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub enum Term { + // Leaves + Args, // $ + Literal(Ipld), + Variable(Variable), + + Selector(Selector), + + // Connectives + Not(Box), + And(Vec), + Or(Vec), + + // Comparison + Equal(Value, Value), + GreaterThan(Value, Value), + GreaterOrEqual(Value, Value), + LessThan(Value, Value), + LessOrEqual(Value, Value), + + // String Matcher + Glob(Value, String), + + // Existential Quantification + Exists(Variable, Collection), // ∃x ∈ xs +} + +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub struct Variable(String); // ?x + +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub enum Collection { + Array(Vec), + Map(BTreeMap), + Variable(Variable), + Selector(Selector), +} + +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub struct Selector(Vec); // .foo.bar[].baz + +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub enum Index { + This, + // RecDesend, // .. + FlattenAll, // .[] + Index(usize), // .[2] + Key(String), // .key +} + +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub enum Value { + Literal(Ipld), + Variable(Variable), + ImplicitBind(Selector), +} diff --git a/src/delegation/policy/ir.rs b/src/delegation/policy/ir.rs new file mode 100644 index 00000000..fe1ce03b --- /dev/null +++ b/src/delegation/policy/ir.rs @@ -0,0 +1,379 @@ +use libipld_core::ipld::Ipld; +use serde::{Deserialize, Serialize}; +use std::collections::BTreeMap; + +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub enum Term { + // Leaves + Args, // $ + Literal(Ipld), + Stream(Stream), + + Selector(Selector), + + // Connectives + Not(Box), + And(Vec), + Or(Vec), + + // Comparison + Equal(Value, Value), + GreaterThan(Value, Value), + GreaterOrEqual(Value, Value), + LessThan(Value, Value), + LessOrEqual(Value, Value), + + // String Matcher + Glob(Value, String), + + // Existential Quantification + Exists(Variable, Collection), // ∃x ∈ xs -> convert every -> some +} + +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub struct Variable(String); // ?x + +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub enum Collection { + Array(Vec), + Map(BTreeMap), + Variable(Variable), + Selector(Selector), +} + +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub struct Selector(Vec); // .foo.bar[].baz + +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub enum PathSegment { + This, // . + // RecDesend, // .. + FlattenAll, // .[] --> creates an Every stream + Index(usize), // .[2] + Key(String), // .key +} + +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub enum Value { + Literal(Ipld), + Variable(Variable), +} + +// #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +// pub Struct EveryStream(Vec); +// +// #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +// pub struct SomeStream(Vec); + +pub fn glob(input: String, pattern: String) -> bool { + let mut input = input.chars(); + let mut pattern = pattern.chars(); + + loop { + match (input.next(), pattern.next()) { + (Some(i), Some(p)) => { + if p == '*' { + return true; + } else if i != p { + return false; + } + } + (Some(_), None) => { + return false; // FIXME correct? + } + (None, Some(p)) => { + if p == '*' { + return true; + } + } + (None, None) => { + return true; + } + } + } +} + +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub enum Stream { + Every(Vec), // "All or nothing" + Some(Vec), +} + +pub struct EveryStream(Vec); +pub struct SomeStream(Vec); + +pub trait Apply { + fn apply(&self, other: &T, f: F) -> (Stream, Stream) + where + F: Fn(&Ipld, &Ipld) -> bool; +} + +impl Apply for Ipld { + fn apply(&self, other: &Ipld, f: F) -> (Stream, Stream) + where + F: Fn(&Ipld, &Ipld) -> bool, + { + if f(self, other) { + ( + Stream::Every(vec![self.clone()]), + Stream::Every(vec![other.clone()]), + ) + } else { + (Stream::Every(vec![]), Stream::Every(vec![])) + } + } +} + +impl Apply for Ipld { + fn apply(&self, other: &EveryStream, f: F) -> (Stream, Stream) + where + F: Fn(&Ipld, &Ipld) -> bool, + { + let mut y_results = vec![]; + + for y in other.0.iter() { + if f(self, y) { + y_results.push(y.clone()); + } else { + y_results = vec![]; + break; + } + } + + if y_results.is_empty() { + (Stream::Every(vec![]), Stream::Every(vec![])) + } else { + (Stream::Every(vec![self.clone()]), Stream::Every(y_results)) + } + } +} + +impl Apply for EveryStream { + fn apply(&self, other: &Ipld, f: F) -> (Stream, Stream) + where + F: Fn(&Ipld, &Ipld) -> bool, + { + let mut x_results = vec![]; + + for x in self.0.iter() { + if f(x, other) { + x_results.push(x.clone()); + } else { + x_results = vec![]; + break; + } + } + + if x_results.is_empty() { + (Stream::Every(vec![]), Stream::Every(vec![])) + } else { + (Stream::Every(x_results), Stream::Every(vec![other.clone()])) + } + } +} + +impl Apply for EveryStream { + fn apply(&self, other: &EveryStream, f: F) -> (Stream, Stream) + where + F: Fn(&Ipld, &Ipld) -> bool, + { + let mut x_results = vec![]; + let mut y_results = vec![]; + + for x in self.0.iter() { + for y in other.0.iter() { + if f(x, y) { + x_results.push(x.clone()); + y_results.push(y.clone()); + } else { + x_results = vec![]; + y_results = vec![]; + break; + } + } + } + + (Stream::Every(x_results), Stream::Every(y_results)) + } +} + +impl Apply for EveryStream { + fn apply(&self, other: &SomeStream, f: F) -> (Stream, Stream) + where + F: Fn(&Ipld, &Ipld) -> bool, + { + let mut x_results = vec![]; + let mut y_results = vec![]; + + for x in self.0.iter() { + for y in other.0.iter() { + if f(x, y) { + x_results.push(x.clone()); + y_results.push(y.clone()); + } else { + x_results = vec![]; + y_results.push(y.clone()); + break; + } + } + } + + (Stream::Every(x_results), Stream::Some(y_results)) + } +} + +impl Apply for SomeStream { + fn apply(&self, other: &EveryStream, f: F) -> (Stream, Stream) + where + F: Fn(&Ipld, &Ipld) -> bool, + { + let mut x_results = vec![]; + let mut y_results = vec![]; + + for x in self.0.iter() { + for y in other.0.iter() { + if f(x, y) { + x_results.push(x.clone()); + y_results.push(y.clone()); + } else { + x_results = vec![]; + y_results.push(y.clone()); + break; + } + } + } + + (Stream::Some(x_results), Stream::Every(y_results)) + } +} + +impl Apply for SomeStream { + fn apply(&self, other: &SomeStream, f: F) -> (Stream, Stream) + where + F: Fn(&Ipld, &Ipld) -> bool, + { + let mut x_results = vec![]; + let mut y_results = vec![]; + + for x in self.0.iter() { + for y in other.0.iter() { + if f(x, y) { + x_results.push(x.clone()); + y_results.push(y.clone()); + } + } + } + + (Stream::Some(x_results), Stream::Some(y_results)) + } +} + +impl Apply for Stream { + fn apply(&self, other: &Stream, f: F) -> (Stream, Stream) + where + F: Fn(&Ipld, &Ipld) -> bool, + { + match self { + Stream::Every(xs) => match other { + Stream::Every(ys) => EveryStream(xs.clone()).apply(&EveryStream(ys.clone()), f), + Stream::Some(ys) => EveryStream(xs.clone()).apply(&EveryStream(ys.clone()), f), + }, + + Stream::Some(xs) => match other { + Stream::Every(ys) => SomeStream(xs.clone()).apply(&EveryStream(ys.clone()), f), + Stream::Some(ys) => SomeStream(xs.clone()).apply(&SomeStream(ys.clone()), f), + }, + } + } +} + +impl Stream { + /// Call like stream.apply(other_stream, |x, y| x == y) + pub fn apply(&self, other: &Stream, f: F) -> (Stream, Stream) + where + F: Fn(&Ipld, &Ipld) -> bool, + { + match self { + Stream::Every(xs) => match other { + Stream::Every(ys) => { + let mut x_results = Vec::new(); + let mut y_results = Vec::new(); + + for x in xs { + for y in ys { + if f(x, y) { + x_results.push(x.clone()); + y_results.push(y.clone()); + } else { + x_results = vec![]; + y_results = vec![]; + break; + } + } + } + + (Stream::Every(x_results), Stream::Every(y_results)) + } + Stream::Some(ys) => { + let mut x_results = Vec::new(); + let mut y_results = Vec::new(); + + for x in xs { + for y in ys { + if f(x, y) { + x_results.push(x.clone()); + y_results.push(y.clone()); + } else { + x_results = vec![]; + break; + } + } + } + + if &Stream::Every(x_results.clone()) == self { + (Stream::Every(x_results), Stream::Some(y_results)) + } else { + (Stream::Every(vec![]), Stream::Some(y_results)) + } + } + }, + + Stream::Some(xs) => match other { + Stream::Every(ys) => { + let mut x_results = Vec::new(); + let mut y_results = Vec::new(); + + for x in xs { + for y in ys { + if f(x, y) { + x_results.push(x.clone()); + y_results.push(x.clone()); + } else { + x_results.push(x.clone()); + y_results = vec![]; + break; + } + } + } + + (Stream::Some(x_results), Stream::Every(y_results)) + } + Stream::Some(ys) => { + let mut x_results = Vec::new(); + let mut y_results = Vec::new(); + + for x in xs { + for y in ys { + if f(x, y) { + x_results.push(x.clone()); + y_results.push(y.clone()); + } + } + } + + (Stream::Some(x_results), Stream::Some(y_results)) + } + }, + } + } +} From d1fbad78a320182cfa64577b5719853012b37578 Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Tue, 27 Feb 2024 17:10:52 -0800 Subject: [PATCH 103/188] Big chunk through the interpreter --- src/delegation/policy.rs | 1 + src/delegation/policy/interpreter.rs | 128 ++++++++++ src/delegation/policy/ir.rs | 337 ++++++++++++++++++--------- 3 files changed, 350 insertions(+), 116 deletions(-) create mode 100644 src/delegation/policy/interpreter.rs diff --git a/src/delegation/policy.rs b/src/delegation/policy.rs index 6f111432..7be6d057 100644 --- a/src/delegation/policy.rs +++ b/src/delegation/policy.rs @@ -1,2 +1,3 @@ pub mod frontend; +pub mod interpreter; pub mod ir; diff --git a/src/delegation/policy/interpreter.rs b/src/delegation/policy/interpreter.rs new file mode 100644 index 00000000..b98b471c --- /dev/null +++ b/src/delegation/policy/interpreter.rs @@ -0,0 +1,128 @@ +use super::ir::*; +use libipld_core::ipld::Ipld; +use std::collections::BTreeMap; + +// [".[]", "$", ?x] +// [".[]", "$", ?y] +// ["==", "?x", "?y"] +// ["==", "?y", "?x"] + +// Register machine +// { +// ports: { +// "?a": Stream<>, +// "?b": Stream<>, +// } +// } + +#[derive(Debug, Clone, PartialEq)] +pub struct Machine<'a> { + pub ports: BTreeMap<&'a str, Stream>, + pub program: BTreeMap<&'a str, Statement>, +} + +pub fn run<'a>(machine: Machine<'a>) -> Machine<'a> { + // run to exhaustion + loop { + if let Ok(next) = run_once(&machine) { + if next == &machine { + return machine; + } + } else { + panic!("failed some step"); + } + } +} + +pub fn run_once<'a>(machine: &'a Machine<'a>) -> Result<&'a Machine<'a>, ()> { + let mut ports = machine.ports.clone(); + let mut program = machine.program.clone(); + + // FIXME Fix this iter; need to keep getting smaller and runninhg top-to-bottom + // or at least that's one startegy + program + .clone() + .iter() + .try_fold((), |acc, (idx, statement)| { + // FIXME change from map to vec + match statement { + Statement::Glob(value, pattern) => value + .better_apply(&pattern, &glob) + .map(|_| program.remove(idx)), + Statement::Equal(left, right) => left + .better_apply(right, PartialEq::eq) + .map(|_| program.remove(idx)), + Statement::GreaterThan(left, right) => left + .better_apply(right, PartialEq::gt) + .map(|_| program.remove(idx)), + Statement::LessThan(left, right) => left + .better_apply(right, PartialEq::lt) + .map(|_| program.remove(idx)), + } + // Statement::Equal(left, right) => match (left, right) { + // (Value::Literal(left), Value::Literal(right)) => { + // if left == right { + // program.remove(idx); + // Ok(()) + // } else { + // Err(()) + // } + // } + // (Value::Literal(left), Value::Variable(Variable(var_id))) => { + // if let Some(stream) = ports.get(var_id.as_str()) { + // let updated = left.apply(stream, PartialEq::eq); + // if updated.0.is_empty() { + // return Err(()); + // } + + // ports.insert(var_id.as_str(), updated.0); + // program.remove(idx); + // Ok(()) + // } else { + // Err(()) + // } + // } + // (Value::Variable(Variable(var_id)), Value::Literal(right)) => { + // if let Some(stream) = ports.get(var_id.as_str()) { + // let updated = left.apply(stream, PartialEq::eq); + // if updated.0.is_empty() { + // return Err(()); + // } + + // ports.insert(var_id.as_str(), updated.0); + // program.remove(idx); + // Ok(()) + // } else { + // Err(()) + // } + // } + // // (Value::Variable(left), Value::Literal(right)) => { + // // if let Some(stream) = ports.get(left.as_str()) { + // // let updated = stream.apply(&Ipld::String(right), &equal); + // // ports.insert(left.as_str(), updated.0); + // // } + + // // program.remove(name); + // // } + // // (Value::Variable(left), Value::Variable(right)) => { + // // if let Some(stream) = ports.get(left.as_str()) { + // // let updated = stream.apply(&Ipld::String(right.clone()), &equal); + // // ports.insert(left.as_str(), updated.0); + // // // FIXME UPDATE BOTH + // // } + + // // program.remove(name); + // // } + // _ => todo!(), + // }, + // _ => todo!(), + }) + .map(|_| &machine) +} + +// [".[]", "$", ?x] +// [".[]", "$", ?y] +// +// these will run to exhaustion? +// [".bar", "?x", "?y"] +// [".baz", "?y", "?x"] diff --git a/src/delegation/policy/ir.rs b/src/delegation/policy/ir.rs index fe1ce03b..1e8c9e19 100644 --- a/src/delegation/policy/ir.rs +++ b/src/delegation/policy/ir.rs @@ -9,7 +9,7 @@ pub enum Term { Literal(Ipld), Stream(Stream), - Selector(Selector), + Selector(Selector), // NOTE the IR version doens't inline the results // Connectives Not(Box), @@ -17,21 +17,39 @@ pub enum Term { Or(Vec), // Comparison - Equal(Value, Value), + Equal(Value, Value), // AKA unification GreaterThan(Value, Value), - GreaterOrEqual(Value, Value), LessThan(Value, Value), - LessOrEqual(Value, Value), // String Matcher - Glob(Value, String), + Glob(Value, Value), // Existential Quantification + Forall(Variable, Collection), // ∀x ∈ xs Exists(Variable, Collection), // ∃x ∈ xs -> convert every -> some } #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] -pub struct Variable(String); // ?x +pub enum Statement { + // Connectives + Not(Box), + And(Vec), + Or(Vec), + + // Comparison + Equal(Value, Value), // AKA unification + GreaterThan(Value, Value), + LessThan(Value, Value), + + // String Matcher + Glob(Value, Value), + + Forall(Variable, Collection), // ∀x ∈ xs + Exists(Variable, Collection), // ∃x ∈ xs +} + +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub struct Variable(pub String); // ?x #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] pub enum Collection { @@ -42,7 +60,7 @@ pub enum Collection { } #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] -pub struct Selector(Vec); // .foo.bar[].baz +pub struct Selector(pub Vec); // .foo.bar[].baz #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] pub enum PathSegment { @@ -59,38 +77,35 @@ pub enum Value { Variable(Variable), } -// #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] -// pub Struct EveryStream(Vec); -// -// #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] -// pub struct SomeStream(Vec); - -pub fn glob(input: String, pattern: String) -> bool { - let mut input = input.chars(); - let mut pattern = pattern.chars(); - - loop { - match (input.next(), pattern.next()) { - (Some(i), Some(p)) => { - if p == '*' { - return true; - } else if i != p { - return false; +pub fn glob(input: &Ipld, pattern: &Ipld) -> bool { + if let (Ipld::String(s), Ipld::String(pat)) = (input, pattern) { + let mut input = s.chars(); + let mut pattern = pat.chars(); // Ugly + + loop { + match (input.next(), pattern.next()) { + (Some(i), Some(p)) => { + if p == '*' { + return true; + } else if i != p { + return false; + } } - } - (Some(_), None) => { - return false; // FIXME correct? - } - (None, Some(p)) => { - if p == '*' { + (Some(_), None) => { + return false; // FIXME correct? + } + (None, Some(p)) => { + if p == '*' { + return true; + } + } + (None, None) => { return true; } } - (None, None) => { - return true; - } } } + panic!("FIXME"); } #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] @@ -99,13 +114,34 @@ pub enum Stream { Some(Vec), } +impl Stream { + pub fn is_empty(&self) -> bool { + match self { + Stream::Every(xs) => xs.is_empty(), + Stream::Some(xs) => xs.is_empty(), + } + } +} + pub struct EveryStream(Vec); pub struct SomeStream(Vec); pub trait Apply { + // FIXME -> Option<(Stream, Stream)>? fn apply(&self, other: &T, f: F) -> (Stream, Stream) where F: Fn(&Ipld, &Ipld) -> bool; + + fn better_apply(&self, other: &T, f: F) -> Result<(Stream, Stream), ()> + where + F: Fn(&Ipld, &Ipld) -> bool, + { + if self.apply(other, f).0.is_empty() { + Err(()) + } else { + Ok((self, other)) + } + } } impl Apply for Ipld { @@ -172,6 +208,43 @@ impl Apply for EveryStream { } } +impl Apply for SomeStream { + fn apply(&self, other: &Ipld, f: F) -> (Stream, Stream) + where + F: Fn(&Ipld, &Ipld) -> bool, + { + let mut x_results = vec![]; + + for x in self.0.iter() { + if f(x, other) { + x_results.push(x.clone()); + } + } + + (Stream::Some(x_results), Stream::Every(vec![other.clone()])) + } +} + +impl Apply for Ipld { + fn apply(&self, other: &SomeStream, f: F) -> (Stream, Stream) + where + F: Fn(&Ipld, &Ipld) -> bool, + { + let mut y_results = vec![]; + + for y in other.0.iter() { + if f(self, y) { + y_results.push(y.clone()); + } else { + y_results = vec![]; + break; + } + } + + (Stream::Every(vec![self.clone()]), Stream::Some(y_results)) + } +} + impl Apply for EveryStream { fn apply(&self, other: &EveryStream, f: F) -> (Stream, Stream) where @@ -197,6 +270,7 @@ impl Apply for EveryStream { } } +// FIXME impl Apply for EveryStream { fn apply(&self, other: &SomeStream, f: F) -> (Stream, Stream) where @@ -287,93 +361,124 @@ impl Apply for Stream { } } -impl Stream { - /// Call like stream.apply(other_stream, |x, y| x == y) - pub fn apply(&self, other: &Stream, f: F) -> (Stream, Stream) +impl Apply for Stream { + fn apply(&self, other: &Ipld, f: F) -> (Stream, Stream) where F: Fn(&Ipld, &Ipld) -> bool, { - match self { - Stream::Every(xs) => match other { - Stream::Every(ys) => { - let mut x_results = Vec::new(); - let mut y_results = Vec::new(); - - for x in xs { - for y in ys { - if f(x, y) { - x_results.push(x.clone()); - y_results.push(y.clone()); - } else { - x_results = vec![]; - y_results = vec![]; - break; - } - } - } - - (Stream::Every(x_results), Stream::Every(y_results)) - } - Stream::Some(ys) => { - let mut x_results = Vec::new(); - let mut y_results = Vec::new(); - - for x in xs { - for y in ys { - if f(x, y) { - x_results.push(x.clone()); - y_results.push(y.clone()); - } else { - x_results = vec![]; - break; - } - } - } - - if &Stream::Every(x_results.clone()) == self { - (Stream::Every(x_results), Stream::Some(y_results)) - } else { - (Stream::Every(vec![]), Stream::Some(y_results)) - } - } - }, - - Stream::Some(xs) => match other { - Stream::Every(ys) => { - let mut x_results = Vec::new(); - let mut y_results = Vec::new(); - - for x in xs { - for y in ys { - if f(x, y) { - x_results.push(x.clone()); - y_results.push(x.clone()); - } else { - x_results.push(x.clone()); - y_results = vec![]; - break; - } - } - } + todo!() + // match self { + // Stream::Every(xs) => EveryStream(xs).apply(&other, f) + // Stream::Some(xs) => SomeStream(xs).apply(&other, f), + // } + } +} - (Stream::Some(x_results), Stream::Every(y_results)) - } - Stream::Some(ys) => { - let mut x_results = Vec::new(); - let mut y_results = Vec::new(); - - for x in xs { - for y in ys { - if f(x, y) { - x_results.push(x.clone()); - y_results.push(y.clone()); - } - } - } +impl Apply for Ipld { + fn apply(&self, other: &Stream, f: F) -> (Stream, Stream) + where + F: Fn(&Ipld, &Ipld) -> bool, + { + todo!() + } +} - (Stream::Some(x_results), Stream::Some(y_results)) - } - }, - } +impl Apply for Value { + fn apply(&self, other: &Value, f: F) -> (Stream, Stream) + where + F: Fn(&Ipld, &Ipld) -> bool, + { + todo!() } } + +// impl Stream { +// /// Call like stream.apply(other_stream, |x, y| x == y) +// pub fn apply(&self, other: &Stream, f: F) -> (Stream, Stream) +// where +// F: Fn(&Ipld, &Ipld) -> bool, +// { +// match self { +// Stream::Every(xs) => match other { +// Stream::Every(ys) => { +// let mut x_results = Vec::new(); +// let mut y_results = Vec::new(); +// +// for x in xs { +// for y in ys { +// if f(x, y) { +// x_results.push(x.clone()); +// y_results.push(y.clone()); +// } else { +// x_results = vec![]; +// y_results = vec![]; +// break; +// } +// } +// } +// +// (Stream::Every(x_results), Stream::Every(y_results)) +// } +// Stream::Some(ys) => { +// let mut x_results = Vec::new(); +// let mut y_results = Vec::new(); +// +// for x in xs { +// for y in ys { +// if f(x, y) { +// x_results.push(x.clone()); +// y_results.push(y.clone()); +// } else { +// x_results = vec![]; +// break; +// } +// } +// } +// +// if &Stream::Every(x_results.clone()) == self { +// (Stream::Every(x_results), Stream::Some(y_results)) +// } else { +// (Stream::Every(vec![]), Stream::Some(y_results)) +// } +// } +// }, +// +// Stream::Some(xs) => match other { +// Stream::Every(ys) => { +// let mut x_results = Vec::new(); +// let mut y_results = Vec::new(); +// +// for x in xs { +// for y in ys { +// if f(x, y) { +// x_results.push(x.clone()); +// y_results.push(x.clone()); +// } else { +// x_results.push(x.clone()); +// y_results = vec![]; +// break; +// } +// } +// } +// +// (Stream::Some(x_results), Stream::Every(y_results)) +// } +// Stream::Some(ys) => { +// let mut x_results = Vec::new(); +// let mut y_results = Vec::new(); +// +// for x in xs { +// for y in ys { +// if f(x, y) { +// x_results.push(x.clone()); +// y_results.push(y.clone()); +// } +// } +// } +// +// (Stream::Some(x_results), Stream::Some(y_results)) +// } +// }, +// } +// } +// } From ffd4f1a17a4d48efc88ee968e7ee3b5cd2a3e20e Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Wed, 28 Feb 2024 00:04:13 -0800 Subject: [PATCH 104/188] Big chunk of the way through the first pass on the interpreter --- src/delegation/policy/interpreter.rs | 420 +++++++++++++++++++++------ src/delegation/policy/ir.rs | 410 +++----------------------- 2 files changed, 379 insertions(+), 451 deletions(-) diff --git a/src/delegation/policy/interpreter.rs b/src/delegation/policy/interpreter.rs index b98b471c..4059b1a7 100644 --- a/src/delegation/policy/interpreter.rs +++ b/src/delegation/policy/interpreter.rs @@ -1,7 +1,25 @@ use super::ir::*; +use crate::ability::arguments; use libipld_core::ipld::Ipld; use std::collections::BTreeMap; +// [and ["==", ".foo", "?x"] +// [">", "?x", 0] +// [">", "?x", 2] +// ["==", 10, 11] // Fails, so whole thing fails? +// ["or", ["==", ".bar", "?y"] +// [">", "?y", 12] +// ["and", ["<", "?x", 100] +// ["<", "?y", 100] +// ] +// ["every", "?x", "?e"] +// ] +// ["==", 22, "?e"] +// ["some", "?x" "?a"] +// ["==", "?a", [1, 2, "?z", 4]] +// ["==", ["?b", "?c", 20, 30], [10, "?a", 20, 30]] // -> b = 10, c = a, a = c +// ] + // [".[]", "$", ?x] // [".[]", "$", ?y] // ["==", "?x", "?y"] @@ -17,8 +35,10 @@ use std::collections::BTreeMap; #[derive(Debug, Clone, PartialEq)] pub struct Machine<'a> { - pub ports: BTreeMap<&'a str, Stream>, - pub program: BTreeMap<&'a str, Statement>, + pub args: arguments::Named, + pub frames: BTreeMap<&'a str, Stream>, + pub program: Statement, + pub index_counter: usize, } pub fn run<'a>(machine: Machine<'a>) -> Machine<'a> { @@ -34,95 +54,317 @@ pub fn run<'a>(machine: Machine<'a>) -> Machine<'a> { } } -pub fn run_once<'a>(machine: &'a Machine<'a>) -> Result<&'a Machine<'a>, ()> { - let mut ports = machine.ports.clone(); - let mut program = machine.program.clone(); - +pub fn run_once<'a>(mut context: Machine<'a>) -> Result, ()> { // FIXME Fix this iter; need to keep getting smaller and runninhg top-to-bottom // or at least that's one startegy - program - .clone() - .iter() - .try_fold((), |acc, (idx, statement)| { - // FIXME change from map to vec - match statement { - Statement::Glob(value, pattern) => value - .better_apply(&pattern, &glob) - .map(|_| program.remove(idx)), - Statement::Equal(left, right) => left - .better_apply(right, PartialEq::eq) - .map(|_| program.remove(idx)), - Statement::GreaterThan(left, right) => left - .better_apply(right, PartialEq::gt) - .map(|_| program.remove(idx)), - Statement::LessThan(left, right) => left - .better_apply(right, PartialEq::lt) - .map(|_| program.remove(idx)), + match context.program { + Statement::And(left, right) => { + let lhs = Machine { + program: *left, + ..context + }; + + let lhs_result = run(lhs); + + let rhs = Machine { + args: context.args, + frames: lhs_result.frames, + program: *right, + index_counter: lhs_result.index_counter, + }; + + let mut rhs_result = run(rhs); + + if rhs_result.frames.is_empty() { + Err(()) + } else { + Ok(rhs_result) } - // Statement::Equal(left, right) => match (left, right) { - // (Value::Literal(left), Value::Literal(right)) => { - // if left == right { - // program.remove(idx); - // Ok(()) - // } else { - // Err(()) - // } - // } - // (Value::Literal(left), Value::Variable(Variable(var_id))) => { - // if let Some(stream) = ports.get(var_id.as_str()) { - // let updated = left.apply(stream, PartialEq::eq); - // if updated.0.is_empty() { - // return Err(()); - // } - - // ports.insert(var_id.as_str(), updated.0); - // program.remove(idx); - // Ok(()) - // } else { - // Err(()) - // } - // } - // (Value::Variable(Variable(var_id)), Value::Literal(right)) => { - // if let Some(stream) = ports.get(var_id.as_str()) { - // let updated = left.apply(stream, PartialEq::eq); - // if updated.0.is_empty() { - // return Err(()); - // } - - // ports.insert(var_id.as_str(), updated.0); - // program.remove(idx); - // Ok(()) - // } else { - // Err(()) - // } - // } - // // (Value::Variable(left), Value::Literal(right)) => { - // // if let Some(stream) = ports.get(left.as_str()) { - // // let updated = stream.apply(&Ipld::String(right), &equal); - // // ports.insert(left.as_str(), updated.0); - // // } - - // // program.remove(name); - // // } - // // (Value::Variable(left), Value::Variable(right)) => { - // // if let Some(stream) = ports.get(left.as_str()) { - // // let updated = stream.apply(&Ipld::String(right.clone()), &equal); - // // ports.insert(left.as_str(), updated.0); - // // // FIXME UPDATE BOTH - // // } - - // // program.remove(name); - // // } - // _ => todo!(), - // }, - // _ => todo!(), - }) - .map(|_| &machine) + } + Statement::Or(left, right) => { + let lhs = Machine { + program: *left, + ..context + }; + + let rhs = Machine { + program: *right, + ..context + }; + + let lhs_result = run(lhs); + let rhs_result = run(rhs); + todo!() // merge_and_dedup(lhs_result, rhs_result); + } + Statement::Not(statement) => { + let next = Machine { + args: context.args, + frames: context.frames, + program: *statement, + index_counter: context.index_counter, + }; + + let not_results = run(next); + + todo!(); // remove all not_results from context.frames + } + Statement::Exists(var, collection) => { + let xs: Vec = match collection { + Collection::Array(vec) => vec, + Collection::Map(map) => map.values().cloned().collect(), + }; + + context.frames.insert(var.0.as_str(), Stream::Some(xs)); + Ok(context) + } + Statement::Forall(var, collection) => { + let xs: Vec = match collection { + Collection::Array(vec) => vec, + Collection::Map(map) => map.values().cloned().collect(), + }; + + context.frames.insert(var.0.as_str(), Stream::Every(xs)); + Ok(context) + } + Statement::Equal(left, right) => context + .apply(&left, &right, |a, b| a == b) + .map(|()| context), + Statement::GreaterThan(left, right) => context + .apply(&left, &right, |a, b| match (a, b) { + (Ipld::Integer(a), Ipld::Integer(b)) => a > b, + (Ipld::Float(a), Ipld::Float(b)) => a > b, + (Ipld::Integer(a), Ipld::Float(b)) => (*a as f64) > *b, + (Ipld::Float(a), Ipld::Integer(b)) => *a > (*b as f64), + _ => false, + }) + .map(|()| context), + Statement::LessThan(left, right) => context + .apply(&left, &right, |a, b| match (a, b) { + (Ipld::Integer(a), Ipld::Integer(b)) => a < b, + (Ipld::Float(a), Ipld::Float(b)) => a < b, + (Ipld::Integer(a), Ipld::Float(b)) => (*a as f64) < *b, + (Ipld::Float(a), Ipld::Integer(b)) => *a < (*b as f64), + _ => false, + }) + .map(|()| context), + Statement::GreaterThanOrEqual(left, right) => context + .apply(&left, &right, |a, b| match (a, b) { + (Ipld::Integer(a), Ipld::Integer(b)) => a >= b, + (Ipld::Float(a), Ipld::Float(b)) => a >= b, + (Ipld::Integer(a), Ipld::Float(b)) => (*a as f64) >= *b, + (Ipld::Float(a), Ipld::Integer(b)) => *a >= (*b as f64), + _ => false, + }) + .map(|()| context), + + Statement::LessThanOrEqual(left, right) => context + .apply(&left, &right, |a, b| match (a, b) { + (Ipld::Integer(a), Ipld::Integer(b)) => a <= b, + (Ipld::Float(a), Ipld::Float(b)) => a <= b, + (Ipld::Integer(a), Ipld::Float(b)) => (*a as f64) <= *b, + (Ipld::Float(a), Ipld::Integer(b)) => *a <= (*b as f64), + _ => false, + }) + .map(|()| context), + + Statement::Glob(left, right) => context + .apply(&left, &right, |a, b| glob(a, b)) + .map(|()| context), + Statement::Select(selector, target, var) => match target { + SelectorValue::Args => { + let ipld = Ipld::Map(context.args.0); + let selected = select(selector, ipld)?; + + context + .frames + .insert(var.0.as_str(), Stream::Every(vec![selected])); + + Ok(context) + } + SelectorValue::Literal(ipld) => { + let ipld = select(selector, ipld)?; + + context + .frames + .insert(var.0.as_str(), Stream::Every(vec![ipld])); + + Ok(context) + } + SelectorValue::Variable(var_id) => { + let current = context + .frames + .get(var_id.0.as_str()) + .unwrap_or(&Stream::Every(vec![])); + + let result: Result, ()> = current + .to_vec() + .iter() + .map(|ipld| select(selector.clone(), ipld.clone())) + .collect(); + + let updated = result?; + current.map(|_| updated); + + Ok(context) + } + }, + } } -// [".[]", "$", ?x] -// [".[]", "$", ?y] -// -// these will run to exhaustion? -// [".bar", "?x", "?y"] -// [".baz", "?y", "?x"] +pub fn select(selector: Selector, on: Ipld) -> Result { + let results: Vec<&Ipld> = + selector + .0 + .iter() + .try_fold(vec![&on], |mut ipld_stream, segment| match segment { + PathSegment::This => Ok(ipld_stream), + PathSegment::Index(i) => { + ipld_stream + .iter() + .try_fold(vec![], |mut acc, ipld_entry| match ipld_entry { + Ipld::List(vec) => { + if let Some(ipld) = vec.get(*i) { + acc.push(ipld); + Ok(acc) + } else { + Err(()) + } + } + _ => Err(()), + }) + } + PathSegment::Key(key) => { + ipld_stream + .iter() + .try_fold(vec![], |mut acc, ipld_entry| match ipld_entry { + Ipld::Map(map) => { + if let Some(ipld) = map.get(key) { + acc.push(ipld); + Ok(acc) + } else { + Err(()) + } + } + _ => Err(()), + }) + } + PathSegment::FlattenAll => { + ipld_stream + .iter() + .try_fold(vec![], |mut acc, ipld_entry| match ipld_entry { + Ipld::List(vec) => { + acc.extend(vec); + Ok(acc) + } + _ => Err(()), + }) + } + })?; + + match results.as_slice() { + [ipld] => Ok(*ipld.clone()), + vec => Ok(Ipld::List( + vec.into_iter().map(|ipld| *ipld.clone()).collect(), + )), + } +} + +// pub fn select_step(segment: PathSegment, ipld: Ipld) -> Result { +// match segment { +// PathSegment::This => Ok(ipld), +// PathSegment::Index(i) => match ipld { +// Ipld::List(vec) => vec.get(i).cloned().ok_or(()), +// _ => Err(()), +// }, +// PathSegment::Key(key) => match ipld { +// Ipld::Map(map) => map.get(&key).cloned().ok_or(()), +// _ => Err(()), +// }, +// PathSegment::FlattenAll => todo!(), +// } +// } + +impl<'a> Machine<'a> { + pub fn apply(&mut self, lhs: &Value, rhs: &Value, f: F) -> Result<(), ()> + where + F: Fn(&Ipld, &Ipld) -> bool, + { + match lhs { + Value::Literal(left_ipld) => match rhs { + Value::Literal(right_ipld) => { + if f(left_ipld, right_ipld) { + Ok(()) + } else { + Err(()) + } + } + Value::Variable(var_id) => { + let key = var_id.0.as_str(); + if let Some(stream) = self.frames.get(key) { + let updated = stream + .map(|vec| vec.into_iter().filter(|ipld| f(left_ipld, ipld)).collect()); + + if updated.is_empty() { + return Err(()); + } + + self.frames.insert(key, updated); + + Ok(()) + } else { + Err(()) + } + } + }, + Value::Variable(var_id) => { + let lhs_key = var_id.0.as_str(); + if let Some(stream) = self.frames.get(lhs_key) { + match rhs { + Value::Literal(right_ipld) => { + let updated = stream.map(|vec| { + vec.into_iter().filter(|ipld| f(ipld, right_ipld)).collect() + }); + + if updated.is_empty() { + return Err(()); + } + + self.frames.insert(lhs_key, updated); + Ok(()) + } + Value::Variable(var_id) => { + let rhs_key = var_id.0.as_str(); + if let Some(stream) = self.frames.get(rhs_key) { + let updated = stream.map(|vec| { + vec.into_iter().filter(|ipld| f(ipld, ipld)).collect() + }); + + if updated.is_empty() { + return Err(()); + } + + self.frames.insert(lhs_key, updated); + + Ok(()) + } else { + Err(()) + } + } + } + } else { + // FIXME not nessesarily! You may need to create new entires + Err(()) + } + } + } + } + + pub fn unify(&mut self, lhs: &Value, rhs: &Value) -> Result { + self.apply(lhs, rhs, |a, b| { + todo!(); + todo!(); + // FIXME pattern matching etc + }) + .map(|()| rhs.clone()) + } +} diff --git a/src/delegation/policy/ir.rs b/src/delegation/policy/ir.rs index 1e8c9e19..e9d0a488 100644 --- a/src/delegation/policy/ir.rs +++ b/src/delegation/policy/ir.rs @@ -29,23 +29,33 @@ pub enum Term { Exists(Variable, Collection), // ∃x ∈ xs -> convert every -> some } +// FIXME exract domain gen selectors first? +// FIXME rename constraint or validation or expression or something? #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] pub enum Statement { // Connectives Not(Box), - And(Vec), - Or(Vec), + And(Box, Box), + Or(Box, Box), + + // Forall: unpack and unify size before and after + // Exists genrates more than one frame (unpacks an array) instead of one + Forall(Variable, Collection), // ∀x ∈ xs + Exists(Variable, Collection), // ∃x ∈ xs // Comparison - Equal(Value, Value), // AKA unification + Equal(Value, Value), // AKA unification // FIXME value can also be a selector GreaterThan(Value, Value), + GreaterThanOrEqual(Value, Value), LessThan(Value, Value), + LessThanOrEqual(Value, Value), + // >= and <= are probably good for efficiency // String Matcher Glob(Value, Value), - Forall(Variable, Collection), // ∀x ∈ xs - Exists(Variable, Collection), // ∃x ∈ xs + // Select from ?foo + Select(Selector, SelectorValue, Variable), // .foo.bar[].baz } #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] @@ -55,20 +65,30 @@ pub struct Variable(pub String); // ?x pub enum Collection { Array(Vec), Map(BTreeMap), - Variable(Variable), - Selector(Selector), + // NOTE The below can always be desugared, esp because this is now only used with forall/exists + // Variable(Variable), ] + // Selector(Selector), } #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] pub struct Selector(pub Vec); // .foo.bar[].baz +// FIXME need an IR representation of $args + #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] pub enum PathSegment { This, // . // RecDesend, // .. - FlattenAll, // .[] --> creates an Every stream - Index(usize), // .[2] - Key(String), // .key + Index(usize), // [2] + Key(String), // ["key"] (or .key) + FlattenAll, // [] --> creates an Array +} + +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub enum SelectorValue { + Args, + Literal(Ipld), + Variable(Variable), } #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] @@ -115,370 +135,36 @@ pub enum Stream { } impl Stream { - pub fn is_empty(&self) -> bool { + pub fn to_vec(self) -> Vec { match self { - Stream::Every(xs) => xs.is_empty(), - Stream::Some(xs) => xs.is_empty(), - } - } -} - -pub struct EveryStream(Vec); -pub struct SomeStream(Vec); - -pub trait Apply { - // FIXME -> Option<(Stream, Stream)>? - fn apply(&self, other: &T, f: F) -> (Stream, Stream) - where - F: Fn(&Ipld, &Ipld) -> bool; - - fn better_apply(&self, other: &T, f: F) -> Result<(Stream, Stream), ()> - where - F: Fn(&Ipld, &Ipld) -> bool, - { - if self.apply(other, f).0.is_empty() { - Err(()) - } else { - Ok((self, other)) - } - } -} - -impl Apply for Ipld { - fn apply(&self, other: &Ipld, f: F) -> (Stream, Stream) - where - F: Fn(&Ipld, &Ipld) -> bool, - { - if f(self, other) { - ( - Stream::Every(vec![self.clone()]), - Stream::Every(vec![other.clone()]), - ) - } else { - (Stream::Every(vec![]), Stream::Every(vec![])) - } - } -} - -impl Apply for Ipld { - fn apply(&self, other: &EveryStream, f: F) -> (Stream, Stream) - where - F: Fn(&Ipld, &Ipld) -> bool, - { - let mut y_results = vec![]; - - for y in other.0.iter() { - if f(self, y) { - y_results.push(y.clone()); - } else { - y_results = vec![]; - break; - } - } - - if y_results.is_empty() { - (Stream::Every(vec![]), Stream::Every(vec![])) - } else { - (Stream::Every(vec![self.clone()]), Stream::Every(y_results)) - } - } -} - -impl Apply for EveryStream { - fn apply(&self, other: &Ipld, f: F) -> (Stream, Stream) - where - F: Fn(&Ipld, &Ipld) -> bool, - { - let mut x_results = vec![]; - - for x in self.0.iter() { - if f(x, other) { - x_results.push(x.clone()); - } else { - x_results = vec![]; - break; - } - } - - if x_results.is_empty() { - (Stream::Every(vec![]), Stream::Every(vec![])) - } else { - (Stream::Every(x_results), Stream::Every(vec![other.clone()])) + Stream::Every(xs) => xs, + Stream::Some(xs) => xs, } } -} - -impl Apply for SomeStream { - fn apply(&self, other: &Ipld, f: F) -> (Stream, Stream) - where - F: Fn(&Ipld, &Ipld) -> bool, - { - let mut x_results = vec![]; - - for x in self.0.iter() { - if f(x, other) { - x_results.push(x.clone()); - } - } - - (Stream::Some(x_results), Stream::Every(vec![other.clone()])) - } -} -impl Apply for Ipld { - fn apply(&self, other: &SomeStream, f: F) -> (Stream, Stream) - where - F: Fn(&Ipld, &Ipld) -> bool, - { - let mut y_results = vec![]; - - for y in other.0.iter() { - if f(self, y) { - y_results.push(y.clone()); - } else { - y_results = vec![]; - break; - } - } - - (Stream::Every(vec![self.clone()]), Stream::Some(y_results)) - } -} - -impl Apply for EveryStream { - fn apply(&self, other: &EveryStream, f: F) -> (Stream, Stream) - where - F: Fn(&Ipld, &Ipld) -> bool, - { - let mut x_results = vec![]; - let mut y_results = vec![]; - - for x in self.0.iter() { - for y in other.0.iter() { - if f(x, y) { - x_results.push(x.clone()); - y_results.push(y.clone()); - } else { - x_results = vec![]; - y_results = vec![]; - break; - } + pub fn map(self, f: impl Fn(Vec) -> Vec) -> Stream { + match self { + Stream::Every(xs) => { + let updated = f(xs); + Stream::Every(updated) } - } - - (Stream::Every(x_results), Stream::Every(y_results)) - } -} - -// FIXME -impl Apply for EveryStream { - fn apply(&self, other: &SomeStream, f: F) -> (Stream, Stream) - where - F: Fn(&Ipld, &Ipld) -> bool, - { - let mut x_results = vec![]; - let mut y_results = vec![]; - - for x in self.0.iter() { - for y in other.0.iter() { - if f(x, y) { - x_results.push(x.clone()); - y_results.push(y.clone()); - } else { - x_results = vec![]; - y_results.push(y.clone()); - break; - } + Stream::Some(xs) => { + let updated = f(xs); + Stream::Some(updated) } } - - (Stream::Every(x_results), Stream::Some(y_results)) } -} - -impl Apply for SomeStream { - fn apply(&self, other: &EveryStream, f: F) -> (Stream, Stream) - where - F: Fn(&Ipld, &Ipld) -> bool, - { - let mut x_results = vec![]; - let mut y_results = vec![]; - for x in self.0.iter() { - for y in other.0.iter() { - if f(x, y) { - x_results.push(x.clone()); - y_results.push(y.clone()); - } else { - x_results = vec![]; - y_results.push(y.clone()); - break; - } - } - } - - (Stream::Some(x_results), Stream::Every(y_results)) - } -} - -impl Apply for SomeStream { - fn apply(&self, other: &SomeStream, f: F) -> (Stream, Stream) - where - F: Fn(&Ipld, &Ipld) -> bool, - { - let mut x_results = vec![]; - let mut y_results = vec![]; - - for x in self.0.iter() { - for y in other.0.iter() { - if f(x, y) { - x_results.push(x.clone()); - y_results.push(y.clone()); - } - } - } - - (Stream::Some(x_results), Stream::Some(y_results)) - } -} - -impl Apply for Stream { - fn apply(&self, other: &Stream, f: F) -> (Stream, Stream) - where - F: Fn(&Ipld, &Ipld) -> bool, - { + pub fn is_empty(&self) -> bool { match self { - Stream::Every(xs) => match other { - Stream::Every(ys) => EveryStream(xs.clone()).apply(&EveryStream(ys.clone()), f), - Stream::Some(ys) => EveryStream(xs.clone()).apply(&EveryStream(ys.clone()), f), - }, - - Stream::Some(xs) => match other { - Stream::Every(ys) => SomeStream(xs.clone()).apply(&EveryStream(ys.clone()), f), - Stream::Some(ys) => SomeStream(xs.clone()).apply(&SomeStream(ys.clone()), f), - }, + Stream::Every(xs) => xs.is_empty(), + Stream::Some(xs) => xs.is_empty(), } } } -impl Apply for Stream { - fn apply(&self, other: &Ipld, f: F) -> (Stream, Stream) - where - F: Fn(&Ipld, &Ipld) -> bool, - { - todo!() - // match self { - // Stream::Every(xs) => EveryStream(xs).apply(&other, f) - // Stream::Some(xs) => SomeStream(xs).apply(&other, f), - // } - } -} - -impl Apply for Ipld { - fn apply(&self, other: &Stream, f: F) -> (Stream, Stream) - where - F: Fn(&Ipld, &Ipld) -> bool, - { - todo!() - } -} - -impl Apply for Value { - fn apply(&self, other: &Value, f: F) -> (Stream, Stream) - where - F: Fn(&Ipld, &Ipld) -> bool, - { - todo!() - } -} +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub struct EveryStream(Vec); -// impl Stream { -// /// Call like stream.apply(other_stream, |x, y| x == y) -// pub fn apply(&self, other: &Stream, f: F) -> (Stream, Stream) -// where -// F: Fn(&Ipld, &Ipld) -> bool, -// { -// match self { -// Stream::Every(xs) => match other { -// Stream::Every(ys) => { -// let mut x_results = Vec::new(); -// let mut y_results = Vec::new(); -// -// for x in xs { -// for y in ys { -// if f(x, y) { -// x_results.push(x.clone()); -// y_results.push(y.clone()); -// } else { -// x_results = vec![]; -// y_results = vec![]; -// break; -// } -// } -// } -// -// (Stream::Every(x_results), Stream::Every(y_results)) -// } -// Stream::Some(ys) => { -// let mut x_results = Vec::new(); -// let mut y_results = Vec::new(); -// -// for x in xs { -// for y in ys { -// if f(x, y) { -// x_results.push(x.clone()); -// y_results.push(y.clone()); -// } else { -// x_results = vec![]; -// break; -// } -// } -// } -// -// if &Stream::Every(x_results.clone()) == self { -// (Stream::Every(x_results), Stream::Some(y_results)) -// } else { -// (Stream::Every(vec![]), Stream::Some(y_results)) -// } -// } -// }, -// -// Stream::Some(xs) => match other { -// Stream::Every(ys) => { -// let mut x_results = Vec::new(); -// let mut y_results = Vec::new(); -// -// for x in xs { -// for y in ys { -// if f(x, y) { -// x_results.push(x.clone()); -// y_results.push(x.clone()); -// } else { -// x_results.push(x.clone()); -// y_results = vec![]; -// break; -// } -// } -// } -// -// (Stream::Some(x_results), Stream::Every(y_results)) -// } -// Stream::Some(ys) => { -// let mut x_results = Vec::new(); -// let mut y_results = Vec::new(); -// -// for x in xs { -// for y in ys { -// if f(x, y) { -// x_results.push(x.clone()); -// y_results.push(y.clone()); -// } -// } -// } -// -// (Stream::Some(x_results), Stream::Some(y_results)) -// } -// }, -// } -// } -// } +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub struct SomeStream(Vec); From 00c980642bcf27bcf9d4b3a6e5fcd0d402b209e5 Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Wed, 28 Feb 2024 00:44:57 -0800 Subject: [PATCH 105/188] Switched to maps for better indexing --- src/delegation/policy/interpreter.rs | 249 +++++++++++++++------------ src/delegation/policy/ir.rs | 18 +- 2 files changed, 148 insertions(+), 119 deletions(-) diff --git a/src/delegation/policy/interpreter.rs b/src/delegation/policy/interpreter.rs index 4059b1a7..8267900c 100644 --- a/src/delegation/policy/interpreter.rs +++ b/src/delegation/policy/interpreter.rs @@ -34,34 +34,35 @@ use std::collections::BTreeMap; // } #[derive(Debug, Clone, PartialEq)] -pub struct Machine<'a> { +pub struct Machine { pub args: arguments::Named, - pub frames: BTreeMap<&'a str, Stream>, + pub frames: BTreeMap, pub program: Statement, pub index_counter: usize, } -pub fn run<'a>(machine: Machine<'a>) -> Machine<'a> { +pub fn run(machine: Machine) -> Machine { // run to exhaustion - loop { - if let Ok(next) = run_once(&machine) { - if next == &machine { - return machine; - } - } else { - panic!("failed some step"); - } - } + // loop { + // if let Ok(next) = run_once(&machine) { + // if next == &machine { + // return machine; + // } + // } else { + // panic!("failed some step"); + // } + // } + todo!() } -pub fn run_once<'a>(mut context: Machine<'a>) -> Result, ()> { +pub fn run_once(mut context: Machine) -> Result { // FIXME Fix this iter; need to keep getting smaller and runninhg top-to-bottom // or at least that's one startegy - match context.program { + match context.clone().program { Statement::And(left, right) => { let lhs = Machine { program: *left, - ..context + ..context.clone() }; let lhs_result = run(lhs); @@ -73,7 +74,7 @@ pub fn run_once<'a>(mut context: Machine<'a>) -> Result, ()> { index_counter: lhs_result.index_counter, }; - let mut rhs_result = run(rhs); + let rhs_result = run(rhs); if rhs_result.frames.is_empty() { Err(()) @@ -84,7 +85,7 @@ pub fn run_once<'a>(mut context: Machine<'a>) -> Result, ()> { Statement::Or(left, right) => { let lhs = Machine { program: *left, - ..context + ..context.clone() }; let rhs = Machine { @@ -109,21 +110,39 @@ pub fn run_once<'a>(mut context: Machine<'a>) -> Result, ()> { todo!(); // remove all not_results from context.frames } Statement::Exists(var, collection) => { - let xs: Vec = match collection { - Collection::Array(vec) => vec, - Collection::Map(map) => map.values().cloned().collect(), + let btree: BTreeMap = match collection { + Collection::Array(vec) => vec + .into_iter() + .map(|ipld| (context.next_index(), ipld)) + .collect(), + + Collection::Map(map) => map + .into_iter() + .map(|(_k, ipld)| (context.next_index(), ipld)) + .collect(), }; - context.frames.insert(var.0.as_str(), Stream::Some(xs)); + context.frames.insert(var.0, Stream::Some(btree)); Ok(context) } Statement::Forall(var, collection) => { - let xs: Vec = match collection { - Collection::Array(vec) => vec, - Collection::Map(map) => map.values().cloned().collect(), + let btree: BTreeMap = match collection { + Collection::Array(vec) => vec + .into_iter() + .map(|ipld| (context.next_index(), ipld)) + .collect(), + + Collection::Map(map) => map + .into_iter() + .map(|(_k, ipld)| (context.next_index(), ipld)) + .collect(), }; - context.frames.insert(var.0.as_str(), Stream::Every(xs)); + context.frames.insert(var.0, Stream::Every(btree)); + + // FIXME needs to check that nothing changed + // ...perhaps at the end of the iteration, loop through the streams? + Ok(context) } Statement::Equal(left, right) => context @@ -172,38 +191,45 @@ pub fn run_once<'a>(mut context: Machine<'a>) -> Result, ()> { .map(|()| context), Statement::Select(selector, target, var) => match target { SelectorValue::Args => { - let ipld = Ipld::Map(context.args.0); + let ipld = Ipld::Map(context.args.clone().0); let selected = select(selector, ipld)?; + let idx = context.next_index(); context .frames - .insert(var.0.as_str(), Stream::Every(vec![selected])); + .insert(var.0, Stream::Every(BTreeMap::from_iter([(idx, selected)]))); Ok(context) } SelectorValue::Literal(ipld) => { let ipld = select(selector, ipld)?; + let idx = context.next_index(); context .frames - .insert(var.0.as_str(), Stream::Every(vec![ipld])); + .insert(var.0, Stream::Every(BTreeMap::from_iter([(idx, ipld)]))); Ok(context) } SelectorValue::Variable(var_id) => { let current = context .frames - .get(var_id.0.as_str()) - .unwrap_or(&Stream::Every(vec![])); - - let result: Result, ()> = current - .to_vec() - .iter() - .map(|ipld| select(selector.clone(), ipld.clone())) + .get(&var_id.0) + .cloned() + .unwrap_or(Stream::Every(BTreeMap::new())); + + let result: Result, ()> = current + .clone() + .to_btree() + .into_iter() + .map(|(idx, ipld)| select(selector.clone(), ipld).map(|ipld| (idx, ipld))) .collect(); let updated = result?; - current.map(|_| updated); + + context + .frames + .insert(var.0, current.map(|_| updated.clone())); Ok(context) } @@ -212,79 +238,69 @@ pub fn run_once<'a>(mut context: Machine<'a>) -> Result, ()> { } pub fn select(selector: Selector, on: Ipld) -> Result { - let results: Vec<&Ipld> = - selector - .0 - .iter() - .try_fold(vec![&on], |mut ipld_stream, segment| match segment { - PathSegment::This => Ok(ipld_stream), - PathSegment::Index(i) => { - ipld_stream - .iter() - .try_fold(vec![], |mut acc, ipld_entry| match ipld_entry { - Ipld::List(vec) => { - if let Some(ipld) = vec.get(*i) { - acc.push(ipld); - Ok(acc) - } else { - Err(()) - } - } - _ => Err(()), - }) - } - PathSegment::Key(key) => { - ipld_stream - .iter() - .try_fold(vec![], |mut acc, ipld_entry| match ipld_entry { - Ipld::Map(map) => { - if let Some(ipld) = map.get(key) { - acc.push(ipld); - Ok(acc) - } else { - Err(()) - } + let results: Vec = selector + .0 + .iter() + .try_fold(vec![on], |ipld_stream, segment| match segment { + PathSegment::This => Ok(ipld_stream), + PathSegment::Index(i) => { + ipld_stream + .iter() + .try_fold(vec![], |mut acc, ipld_entry| match ipld_entry { + Ipld::List(vec) => { + if let Some(ipld) = vec.get(*i) { + acc.push(ipld.clone()); + Ok(acc) + } else { + Err(()) } - _ => Err(()), - }) - } - PathSegment::FlattenAll => { - ipld_stream - .iter() - .try_fold(vec![], |mut acc, ipld_entry| match ipld_entry { - Ipld::List(vec) => { - acc.extend(vec); + } + _ => Err(()), + }) + } + PathSegment::Key(key) => { + ipld_stream + .iter() + .try_fold(vec![], |mut acc, ipld_entry| match ipld_entry { + Ipld::Map(map) => { + if let Some(ipld) = map.get(key) { + acc.push(ipld.clone()); Ok(acc) + } else { + Err(()) } - _ => Err(()), - }) - } - })?; + } + _ => Err(()), + }) + } + PathSegment::FlattenAll => { + ipld_stream + .iter() + .try_fold(vec![], |mut acc, ipld_entry| match ipld_entry { + Ipld::List(vec) => { + acc.extend(vec.clone()); + Ok(acc.iter().cloned().collect()) + } + _ => Err(()), + }) + } + })?; - match results.as_slice() { - [ipld] => Ok(*ipld.clone()), + match &results[..] { + [ipld] => Ok(ipld.clone()), vec => Ok(Ipld::List( - vec.into_iter().map(|ipld| *ipld.clone()).collect(), + vec.into_iter().map(|ipld| ipld.clone()).collect(), )), } } -// pub fn select_step(segment: PathSegment, ipld: Ipld) -> Result { -// match segment { -// PathSegment::This => Ok(ipld), -// PathSegment::Index(i) => match ipld { -// Ipld::List(vec) => vec.get(i).cloned().ok_or(()), -// _ => Err(()), -// }, -// PathSegment::Key(key) => match ipld { -// Ipld::Map(map) => map.get(&key).cloned().ok_or(()), -// _ => Err(()), -// }, -// PathSegment::FlattenAll => todo!(), -// } -// } +impl Machine { + pub fn next_index(&mut self) -> usize { + let prev = self.index_counter; + self.index_counter += 1; + prev + } -impl<'a> Machine<'a> { pub fn apply(&mut self, lhs: &Value, rhs: &Value, f: F) -> Result<(), ()> where F: Fn(&Ipld, &Ipld) -> bool, @@ -299,10 +315,15 @@ impl<'a> Machine<'a> { } } Value::Variable(var_id) => { - let key = var_id.0.as_str(); - if let Some(stream) = self.frames.get(key) { - let updated = stream - .map(|vec| vec.into_iter().filter(|ipld| f(left_ipld, ipld)).collect()); + let key = var_id.0.clone(); + + if let Some(stream) = self.frames.get(&key) { + let updated = stream.clone().map(|btree| { + btree + .into_iter() + .filter(|(_idx, ipld)| f(left_ipld, ipld)) + .collect() + }); if updated.is_empty() { return Err(()); @@ -317,33 +338,41 @@ impl<'a> Machine<'a> { } }, Value::Variable(var_id) => { - let lhs_key = var_id.0.as_str(); + let lhs_key = &var_id.0; + if let Some(stream) = self.frames.get(lhs_key) { match rhs { Value::Literal(right_ipld) => { - let updated = stream.map(|vec| { - vec.into_iter().filter(|ipld| f(ipld, right_ipld)).collect() + let updated = stream.clone().map(|btree| { + btree + .into_iter() + .filter(|(_idx, ipld)| f(ipld, right_ipld)) + .collect() }); if updated.is_empty() { return Err(()); } - self.frames.insert(lhs_key, updated); + self.frames.insert(lhs_key.clone(), updated); Ok(()) } Value::Variable(var_id) => { let rhs_key = var_id.0.as_str(); + if let Some(stream) = self.frames.get(rhs_key) { - let updated = stream.map(|vec| { - vec.into_iter().filter(|ipld| f(ipld, ipld)).collect() + let updated = stream.clone().map(|btree| { + btree + .into_iter() + .filter(|(_idx, ipld)| f(ipld, ipld)) + .collect() }); if updated.is_empty() { return Err(()); } - self.frames.insert(lhs_key, updated); + self.frames.insert(lhs_key.clone(), updated); Ok(()) } else { diff --git a/src/delegation/policy/ir.rs b/src/delegation/policy/ir.rs index e9d0a488..d747b6ab 100644 --- a/src/delegation/policy/ir.rs +++ b/src/delegation/policy/ir.rs @@ -130,19 +130,19 @@ pub fn glob(input: &Ipld, pattern: &Ipld) -> bool { #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] pub enum Stream { - Every(Vec), // "All or nothing" - Some(Vec), + Every(BTreeMap), // "All or nothing" + Some(BTreeMap), } impl Stream { - pub fn to_vec(self) -> Vec { + pub fn to_btree(self) -> BTreeMap { match self { Stream::Every(xs) => xs, Stream::Some(xs) => xs, } } - pub fn map(self, f: impl Fn(Vec) -> Vec) -> Stream { + pub fn map(self, f: impl Fn(BTreeMap) -> BTreeMap) -> Stream { match self { Stream::Every(xs) => { let updated = f(xs); @@ -163,8 +163,8 @@ impl Stream { } } -#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] -pub struct EveryStream(Vec); - -#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] -pub struct SomeStream(Vec); +// #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +// pub struct EveryStream(V); +// +// #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +// pub struct SomeStream(Vec); From a3b328a7a9d4c4fd740a39cabe6ab0388ecf9dea Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Wed, 28 Feb 2024 01:20:55 -0800 Subject: [PATCH 106/188] Saving --- src/delegation/policy/interpreter.rs | 121 +++++++++++++++++++++------ src/delegation/policy/ir.rs | 5 +- 2 files changed, 99 insertions(+), 27 deletions(-) diff --git a/src/delegation/policy/interpreter.rs b/src/delegation/policy/interpreter.rs index 8267900c..1c66b24b 100644 --- a/src/delegation/policy/interpreter.rs +++ b/src/delegation/policy/interpreter.rs @@ -52,10 +52,9 @@ pub fn run(machine: Machine) -> Machine { // panic!("failed some step"); // } // } - todo!() } -pub fn run_once(mut context: Machine) -> Result { +pub fn step(mut context: Machine) -> Result { // FIXME Fix this iter; need to keep getting smaller and runninhg top-to-bottom // or at least that's one startegy match context.clone().program { @@ -90,24 +89,59 @@ pub fn run_once(mut context: Machine) -> Result { let rhs = Machine { program: *right, - ..context + ..context.clone() }; let lhs_result = run(lhs); let rhs_result = run(rhs); - todo!() // merge_and_dedup(lhs_result, rhs_result); + + let merged_frames = lhs_result + .frames + .into_iter() + .map(|(key, lhs_stream)| { + let rhs_stream = rhs_result + .frames + .get(&key) + .cloned() + .unwrap_or(lhs_stream.clone()); + + let merged = match (lhs_stream, rhs_stream) { + (Stream::Every(lhs), Stream::Every(rhs)) => { + Stream::Every(lhs.into_iter().chain(rhs).collect()) + } + (Stream::Some(lhs), Stream::Some(rhs)) => { + Stream::Some(lhs.into_iter().chain(rhs).collect()) + } + (Stream::Every(lhs), Stream::Some(rhs)) => { + Stream::Every(lhs.into_iter().chain(rhs).collect()) + } + (Stream::Some(lhs), Stream::Every(rhs)) => { + Stream::Every(lhs.into_iter().chain(rhs).collect()) + } + }; + + (key, merged) + }) + .collect(); + + Ok(Machine { + frames: merged_frames, + ..context + }) } Statement::Not(statement) => { let next = Machine { - args: context.args, - frames: context.frames, program: *statement, - index_counter: context.index_counter, + ..context.clone() }; let not_results = run(next); - todo!(); // remove all not_results from context.frames + for (idx, _) in not_results.frames.iter() { + context.frames.remove(idx); + } + + Ok(context) } Statement::Exists(var, collection) => { let btree: BTreeMap = match collection { @@ -145,7 +179,7 @@ pub fn run_once(mut context: Machine) -> Result { Ok(context) } - Statement::Equal(left, right) => context + Statement::Equal(left, right) => context // FIXME do unification .apply(&left, &right, |a, b| a == b) .map(|()| context), Statement::GreaterThan(left, right) => context @@ -325,12 +359,20 @@ impl Machine { .collect() }); - if updated.is_empty() { - return Err(()); + match updated { + Stream::Every(btree) => { + if btree.len() < stream.to_btree().len() { + return Err(()); + } + } + Stream::Some(btree) => { + if btree.is_empty() { + return Err(()); + } + } } self.frames.insert(key, updated); - Ok(()) } else { Err(()) @@ -340,18 +382,27 @@ impl Machine { Value::Variable(var_id) => { let lhs_key = &var_id.0; - if let Some(stream) = self.frames.get(lhs_key) { + if let Some(lhs_stream) = self.frames.get(lhs_key) { match rhs { Value::Literal(right_ipld) => { - let updated = stream.clone().map(|btree| { + let updated = lhs_stream.clone().map(|btree| { btree .into_iter() .filter(|(_idx, ipld)| f(ipld, right_ipld)) .collect() }); - if updated.is_empty() { - return Err(()); + match updated { + Stream::Every(btree) => { + if btree.len() < lhs_stream.to_btree().len() { + return Err(()); + } + } + Stream::Some(btree) => { + if btree.is_empty() { + return Err(()); + } + } } self.frames.insert(lhs_key.clone(), updated); @@ -360,16 +411,36 @@ impl Machine { Value::Variable(var_id) => { let rhs_key = var_id.0.as_str(); - if let Some(stream) = self.frames.get(rhs_key) { - let updated = stream.clone().map(|btree| { - btree - .into_iter() - .filter(|(_idx, ipld)| f(ipld, ipld)) - .collect() - }); + // ["every", ".email", "?email"] + // ["some", ".req", "?req"] + // ["==", "?email", "?req"] - if updated.is_empty() { - return Err(()); + // ["some", ".email", "?email"] + // ["every", ".req", "?req"] + // ["==", "?email", "?req"] + + if let Some(rhs_stream) = self.frames.get(rhs_key) { + let updated: Vec<((usize, Ipld), (usize, Ipld))> = lhs_stream + .to_btree() + .into_iter() + .zip(rhs_stream.to_btree()) + .filter(|((lhs_idx, lhs_ipld), (rhs_idx, rhs_ipld))| { + f(lhs_ipld, rhs_ipld) + }) + .collect(); + + // FIXME check both sides + match (updated_lhs, updated_rhs) { + Stream::Every(btree) => { + if btree.len() < stream.to_btree().len() { + return Err(()); + } + } + Stream::Some(btree) => { + if btree.is_empty() { + return Err(()); + } + } } self.frames.insert(lhs_key.clone(), updated); diff --git a/src/delegation/policy/ir.rs b/src/delegation/policy/ir.rs index d747b6ab..ecf75d93 100644 --- a/src/delegation/policy/ir.rs +++ b/src/delegation/policy/ir.rs @@ -1,3 +1,4 @@ +use enum_as_inner::EnumAsInner; use libipld_core::ipld::Ipld; use serde::{Deserialize, Serialize}; use std::collections::BTreeMap; @@ -128,10 +129,10 @@ pub fn glob(input: &Ipld, pattern: &Ipld) -> bool { panic!("FIXME"); } -#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, EnumAsInner)] pub enum Stream { Every(BTreeMap), // "All or nothing" - Some(BTreeMap), + Some(BTreeMap), // FIXME disambiguate from Option::Some } impl Stream { From 8c936aa329ef0906baf4da7ade7a1ffda1c81364 Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Wed, 28 Feb 2024 11:29:33 -0800 Subject: [PATCH 107/188] Just need to add pattern matching unification --- src/delegation/policy/interpreter.rs | 85 ++++++++++++++++------------ src/delegation/policy/ir.rs | 30 +++++++++- 2 files changed, 77 insertions(+), 38 deletions(-) diff --git a/src/delegation/policy/interpreter.rs b/src/delegation/policy/interpreter.rs index 1c66b24b..7b378937 100644 --- a/src/delegation/policy/interpreter.rs +++ b/src/delegation/policy/interpreter.rs @@ -42,6 +42,7 @@ pub struct Machine { } pub fn run(machine: Machine) -> Machine { + todo!() // run to exhaustion // loop { // if let Ok(next) = run_once(&machine) { @@ -360,12 +361,12 @@ impl Machine { }); match updated { - Stream::Every(btree) => { - if btree.len() < stream.to_btree().len() { + Stream::Every(ref btree) => { + if btree.len() < stream.len() { return Err(()); } } - Stream::Some(btree) => { + Stream::Some(ref btree) => { if btree.is_empty() { return Err(()); } @@ -393,12 +394,12 @@ impl Machine { }); match updated { - Stream::Every(btree) => { - if btree.len() < lhs_stream.to_btree().len() { + Stream::Every(ref btree) => { + if btree.len() < lhs_stream.len() { return Err(()); } } - Stream::Some(btree) => { + Stream::Some(ref btree) => { if btree.is_empty() { return Err(()); } @@ -411,41 +412,55 @@ impl Machine { Value::Variable(var_id) => { let rhs_key = var_id.0.as_str(); - // ["every", ".email", "?email"] - // ["some", ".req", "?req"] - // ["==", "?email", "?req"] - - // ["some", ".email", "?email"] - // ["every", ".req", "?req"] - // ["==", "?email", "?req"] - if let Some(rhs_stream) = self.frames.get(rhs_key) { - let updated: Vec<((usize, Ipld), (usize, Ipld))> = lhs_stream - .to_btree() - .into_iter() - .zip(rhs_stream.to_btree()) - .filter(|((lhs_idx, lhs_ipld), (rhs_idx, rhs_ipld))| { - f(lhs_ipld, rhs_ipld) - }) - .collect(); - - // FIXME check both sides - match (updated_lhs, updated_rhs) { - Stream::Every(btree) => { - if btree.len() < stream.to_btree().len() { - return Err(()); + let mut non_matches: BTreeMap> = BTreeMap::new(); + + for (lhs_id, lhs_value) in lhs_stream.iter() { + for (rhs_id, rhs_value) in rhs_stream.iter() { + if !f(lhs_value, rhs_value) { + if let Some(rhs_ids) = non_matches.get_mut(lhs_id) { + rhs_ids.push(*rhs_id); + } else { + non_matches.insert(*lhs_id, vec![*rhs_id]); + } } } - Stream::Some(btree) => { - if btree.is_empty() { - return Err(()); + } + + // Double negatives, but for good reason + let did_quantify = + match (lhs_stream.is_every(), rhs_stream.is_every()) { + (true, true) => non_matches.is_empty(), + (true, false) => non_matches + .values() + .all(|rhs_ids| rhs_ids.len() != rhs_stream.len()), + (false, true) => { + non_matches.values().any(|rhs_ids| rhs_ids.is_empty()) + } + (false, false) => non_matches + .values() + .any(|rhs_ids| rhs_ids.len() < rhs_stream.len()), + }; + + if did_quantify { + let mut new_lhs_stream = lhs_stream.clone(); + let mut new_rhs_stream = rhs_stream.clone(); + + for (l_key, r_keys) in non_matches { + new_lhs_stream.remove(l_key); + + for r_key in r_keys { + new_rhs_stream.remove(r_key); } } - } - self.frames.insert(lhs_key.clone(), updated); + self.frames.insert(lhs_key.into(), new_lhs_stream); + self.frames.insert(rhs_key.into(), new_rhs_stream); - Ok(()) + Ok(()) + } else { + Err(()) + } } else { Err(()) } @@ -459,7 +474,7 @@ impl Machine { } } - pub fn unify(&mut self, lhs: &Value, rhs: &Value) -> Result { + pub fn pattern_matching_unification(&mut self, lhs: &Value, rhs: &Value) -> Result { self.apply(lhs, rhs, |a, b| { todo!(); todo!(); diff --git a/src/delegation/policy/ir.rs b/src/delegation/policy/ir.rs index ecf75d93..ef2f5788 100644 --- a/src/delegation/policy/ir.rs +++ b/src/delegation/policy/ir.rs @@ -1,3 +1,4 @@ +//FIXME rename core use enum_as_inner::EnumAsInner; use libipld_core::ipld::Ipld; use serde::{Deserialize, Serialize}; @@ -50,7 +51,6 @@ pub enum Statement { GreaterThanOrEqual(Value, Value), LessThan(Value, Value), LessThanOrEqual(Value, Value), - // >= and <= are probably good for efficiency // String Matcher Glob(Value, Value), @@ -78,8 +78,7 @@ pub struct Selector(pub Vec); // .foo.bar[].baz #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] pub enum PathSegment { - This, // . - // RecDesend, // .. + This, // . Index(usize), // [2] Key(String), // ["key"] (or .key) FlattenAll, // [] --> creates an Array @@ -136,6 +135,31 @@ pub enum Stream { } impl Stream { + pub fn remove(&mut self, key: usize) { + match self { + Stream::Every(xs) => { + xs.remove(&key); + } + Stream::Some(xs) => { + xs.remove(&key); + } + } + } + + pub fn len(&self) -> usize { + match self { + Stream::Every(xs) => xs.len(), + Stream::Some(xs) => xs.len(), + } + } + + pub fn iter(&self) -> impl Iterator { + match self { + Stream::Every(xs) => xs.iter(), + Stream::Some(xs) => xs.iter(), + } + } + pub fn to_btree(self) -> BTreeMap { match self { Stream::Every(xs) => xs, From 730894f571f1ebb04a4cae6e43eee57e697048a1 Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Thu, 29 Feb 2024 00:07:11 -0800 Subject: [PATCH 108/188] Autoencode --- src/ability.rs | 1 + src/ability/command.rs | 4 +- src/ability/crud.rs | 10 +- src/ability/crud/any.rs | 2 +- src/ability/crud/create.rs | 125 +++----------- src/ability/crud/destroy.rs | 2 +- src/ability/crud/js.rs | 14 -- src/ability/crud/mutate.rs | 2 +- src/ability/crud/read.rs | 2 +- src/ability/crud/update.rs | 2 +- src/ability/msg/any.rs | 2 +- src/ability/msg/receive.rs | 2 +- src/ability/msg/send.rs | 8 +- src/ability/pipe.rs | 38 +++++ src/ability/ucan.rs | 1 - src/ability/ucan/pipe.rs | 11 ++ src/ability/ucan/proxy.rs | 59 ------- src/ability/ucan/revoke.rs | 76 +-------- src/ability/wasm/run.rs | 2 +- src/crypto/signature/envelope.rs | 10 ++ src/delegation.rs | 60 +++---- src/delegation/agent.rs | 13 +- src/delegation/condition.rs | 2 +- src/delegation/condition/traits.rs | 4 +- src/delegation/payload.rs | 12 +- src/delegation/policy.rs | 6 +- src/delegation/policy/ir.rs | 251 ++++++++++++++++++++++++++--- src/delegation/store/memory.rs | 2 +- src/delegation/store/traits.rs | 6 +- src/invocation.rs | 7 + src/invocation/payload.rs | 36 ++--- src/ipld/promised.rs | 32 ++-- src/lib.rs | 8 +- src/receipt.rs | 19 ++- src/task.rs | 1 - src/task/id.rs | 2 + src/url.rs | 12 -- 37 files changed, 441 insertions(+), 405 deletions(-) create mode 100644 src/ability/pipe.rs create mode 100644 src/ability/ucan/pipe.rs delete mode 100644 src/ability/ucan/proxy.rs diff --git a/src/ability.rs b/src/ability.rs index 1cc28455..d582fc5c 100644 --- a/src/ability.rs +++ b/src/ability.rs @@ -33,6 +33,7 @@ //! field may include a promise pointing at another invocation. Once fully //! resolved ("ready"), they must be validatable against the delegation chain. +pub mod pipe; pub mod ucan; #[cfg(feature = "ability-crud")] diff --git a/src/ability/command.rs b/src/ability/command.rs index 5cfa1975..eb97ea7f 100644 --- a/src/ability/command.rs +++ b/src/ability/command.rs @@ -7,7 +7,7 @@ //! { //! "iss": "did:example:123", //! "aud": "did:example:456", -//! "cmd": "msg/send", // <--- This is the command +//! "cmd": "/msg/send", // <--- This is the command //! "args": { // ┐ //! "to": "mailto:alice@example.com", // ├─ These are determined by the command //! "message": "Hello, World!", // │ @@ -32,7 +32,7 @@ /// } /// /// impl Command for Upload { -/// const COMMAND: &'static str = "storage/upload"; +/// const COMMAND: &'static str = "/storage/upload"; /// } /// /// assert_eq!(Upload::COMMAND, "storage/upload"); diff --git a/src/ability/crud.rs b/src/ability/crud.rs index 57df163c..c9d5f777 100644 --- a/src/ability/crud.rs +++ b/src/ability/crud.rs @@ -26,7 +26,7 @@ //! ```js //! { //! "sub: "did:example:1234", // <-- e.g. Wraps a web API -//! "cmd": "crud/update", +//! "cmd": "/crud/update", //! "args": { //! "path": "/some/path/to/a/resource", //! }, @@ -67,7 +67,7 @@ pub mod js; #[derive(Debug, Clone, PartialEq)] pub enum Ready { - Create(create::Ready), + Create(create::Create), Read(read::Ready), Update(update::Ready), Destroy(destroy::Ready), @@ -75,7 +75,7 @@ pub enum Ready { #[derive(Debug, Clone, PartialEq)] pub enum Promised { - Create(create::Promised), + Create(create::PromisedCreate), Read(read::Promised), Update(update::Promised), Destroy(destroy::Promised), @@ -88,7 +88,7 @@ impl ParsePromised for Promised { cmd: &str, args: arguments::Named, ) -> Result> { - match create::Promised::try_parse_promised(cmd, args.clone()) { + match create::PromisedCreate::try_parse_promised(cmd, args.clone()) { Ok(create) => return Ok(Promised::Create(create)), Err(ParseAbilityError::InvalidArgs(_)) => { return Err(ParseAbilityError::InvalidArgs(())) @@ -131,7 +131,7 @@ impl ParseAbility for Ready { cmd: &str, args: arguments::Named, ) -> Result> { - match create::Ready::try_parse(cmd, args.clone()) { + match create::Create::try_parse(cmd, args.clone()) { Ok(create) => return Ok(Ready::Create(create)), Err(ParseAbilityError::InvalidArgs(_)) => { return Err(ParseAbilityError::InvalidArgs(())); diff --git a/src/ability/crud/any.rs b/src/ability/crud/any.rs index 6fcdd310..c85d4fb2 100644 --- a/src/ability/crud/any.rs +++ b/src/ability/crud/any.rs @@ -61,7 +61,7 @@ pub struct Any { } impl Command for Any { - const COMMAND: &'static str = "crud/*"; + const COMMAND: &'static str = "/crud"; } impl TryFrom> for Any { diff --git a/src/ability/crud/create.rs b/src/ability/crud/create.rs index baa417cc..b018fe38 100644 --- a/src/ability/crud/create.rs +++ b/src/ability/crud/create.rs @@ -11,21 +11,6 @@ use libipld_core::ipld::Ipld; use serde::Serialize; use std::path::PathBuf; -// FIXME deserialize instance - -/// A helper for creating lifecycle instances of `crud/create` with the correct shape. -#[derive(Debug, Clone, PartialEq, Serialize)] -#[serde(deny_unknown_fields)] -pub struct Generic { - /// An optional path to a sub-resource that is to be created. - #[serde(default, skip_serializing_if = "Option::is_none")] - pub path: Option, - - /// Optional arguments for creation. - #[serde(default, skip_serializing_if = "Option::is_none")] - pub args: Option, -} - #[cfg_attr(doc, aquamarine::aquamarine)] /// The executable/dispatchable variant of the `crud/create` ability. /// @@ -49,8 +34,8 @@ pub struct Generic { /// end /// end /// -/// createpromise("crud::create::Promised") -/// createready("crud::create::Ready") +/// createpromise("crud::create::PromisedCreate") +/// createready("crud::create::Create") /// /// top --> any --> mutate --> create /// create -.->|invoke| createpromise -.->|resolve| createready -.-> exe{{execute}} @@ -59,7 +44,7 @@ pub struct Generic { /// ``` #[derive(Debug, Clone, PartialEq, Serialize)] #[serde(deny_unknown_fields)] -pub struct Ready { +pub struct Create { /// An optional path to a sub-resource that is to be created. #[serde(default, skip_serializing_if = "Option::is_none")] pub path: Option, @@ -93,8 +78,8 @@ pub struct Ready { /// end /// end /// -/// createpromise("crud::create::Promised") -/// createready("crud::create::Ready") +/// createpromise("crud::create::PromisedCreate") +/// createready("crud::create::Create") /// /// top --> any --> mutate --> create /// create -.->|invoke| createpromise -.->|resolve| createready -.-> exe{{execute}} @@ -103,7 +88,7 @@ pub struct Ready { /// ``` #[derive(Debug, Clone, PartialEq, Serialize)] #[serde(deny_unknown_fields)] -pub struct Promised { +pub struct PromisedCreate { /// An optional path to a sub-resource that is to be created. #[serde(default, skip_serializing_if = "Option::is_none")] pub path: Option>, @@ -113,17 +98,17 @@ pub struct Promised { pub args: Option>>, } -const COMMAND: &str = "crud/create"; +const COMMAND: &str = "/crud/create"; -impl Command for Ready { +impl Command for Create { const COMMAND: &'static str = COMMAND; } -impl Command for Promised { +impl Command for PromisedCreate { const COMMAND: &'static str = COMMAND; } -impl TryFrom> for Promised { +impl TryFrom> for PromisedCreate { type Error = (); fn try_from(arguments: arguments::Named) -> Result { @@ -171,11 +156,11 @@ impl TryFrom> for Promised { } } - Ok(Promised { path, args }) + Ok(PromisedCreate { path, args }) } } -impl TryFrom> for Ready { +impl TryFrom> for Create { type Error = (); fn try_from(arguments: arguments::Named) -> Result { @@ -198,79 +183,13 @@ impl TryFrom> for Ready { } } - Ok(Ready { path, args }) + Ok(Create { path, args }) } } -// impl Delegable for Ready { -// type Builder = Ready; -// } - -// impl Checkable for Ready { -// type Hierarchy = Parentful; -// } -// -// impl CheckSame for Ready { -// type Error = (); // FIXME better error -// -// fn check_same(&self, proof: &Self) -> Result<(), Self::Error> { -// if self.path == proof.path { -// Ok(()) -// } else { -// Err(()) -// } -// } -// } -// -// impl CheckParents for Ready { -// type Parents = MutableParents; -// type ParentError = (); // FIXME -// -// fn check_parent(&self, other: &Self::Parents) -> Result<(), Self::ParentError> { -// if let Some(self_path) = &self.path { -// match other { -// MutableParents::Any(any) => { -// // FIXME check the args, too! -// if let Some(proof_path) = &any.path { -// if self_path != proof_path { -// return Err(()); -// } -// } -// } -// MutableParents::Mutate(mutate) => { -// // FIXME check the args, too! -// if let Some(proof_path) = &mutate.path { -// if self_path != proof_path { -// return Err(()); -// } -// } -// } -// } -// } -// -// Ok(()) -// } -// } - -// impl From for arguments::Named { -// fn from(promised: Promised) -> Self { -// let mut named = arguments::Named::new(); -// -// if let Some(path) = promised.path { -// named.insert("path".to_string(), Ipld::String(path.to_string())); -// } -// -// if let Some(args) = promised.args { -// named.insert("args".to_string(), Ipld::from(args)); -// } -// -// named -// } -// } - -impl From for Promised { - fn from(r: Ready) -> Promised { - Promised { +impl From for PromisedCreate { + fn from(r: Create) -> PromisedCreate { + PromisedCreate { path: r .path .map(|inner_path| promise::PromiseOk::Fulfilled(inner_path).into()), @@ -280,12 +199,12 @@ impl From for Promised { } } -impl promise::Resolvable for Ready { - type Promised = Promised; +impl promise::Resolvable for Create { + type Promised = PromisedCreate; } -impl From for arguments::Named { - fn from(builder: Ready) -> Self { +impl From for arguments::Named { + fn from(builder: Create) -> Self { let mut named = arguments::Named::new(); if let Some(path) = builder.path { @@ -306,8 +225,8 @@ impl From for arguments::Named { } } -impl From for arguments::Named { - fn from(promised: Promised) -> Self { +impl From for arguments::Named { + fn from(promised: PromisedCreate) -> Self { let mut named = arguments::Named::new(); if let Some(path) = promised.path { diff --git a/src/ability/crud/destroy.rs b/src/ability/crud/destroy.rs index e43123cb..f7a7bf41 100644 --- a/src/ability/crud/destroy.rs +++ b/src/ability/crud/destroy.rs @@ -133,7 +133,7 @@ impl TryFrom> for Promised { } } -const COMMAND: &'static str = "crud/destroy"; +const COMMAND: &'static str = "/crud/destroy"; impl Command for Ready { const COMMAND: &'static str = COMMAND; diff --git a/src/ability/crud/js.rs b/src/ability/crud/js.rs index f7978aa9..77a7879c 100644 --- a/src/ability/crud/js.rs +++ b/src/ability/crud/js.rs @@ -21,20 +21,6 @@ impl CrudAny { pub fn to_command(&self) -> String { self.to_command() } - - pub fn check_same(&self, proof: &CrudAny) -> Result<(), JsError> { - if self.path.is_some() { - if self.path != proof.path { - return Err(OptionalFieldError { - field: "path".into(), - err: OptionalFieldReason::NotEqual, - } - .into()); - } - } - - Ok(()) - } } #[wasm_bindgen] diff --git a/src/ability/crud/mutate.rs b/src/ability/crud/mutate.rs index 4900e00c..a8ca9746 100644 --- a/src/ability/crud/mutate.rs +++ b/src/ability/crud/mutate.rs @@ -62,7 +62,7 @@ pub struct Mutate { } impl Command for Mutate { - const COMMAND: &'static str = "crud/mutate"; + const COMMAND: &'static str = "/crud/mutate"; } impl From for Ipld { diff --git a/src/ability/crud/read.rs b/src/ability/crud/read.rs index b9201b26..db25111b 100644 --- a/src/ability/crud/read.rs +++ b/src/ability/crud/read.rs @@ -147,7 +147,7 @@ impl TryFrom> for Promised { } } -const COMMAND: &'static str = "crud/read"; +const COMMAND: &'static str = "/crud/read"; impl Command for Ready { const COMMAND: &'static str = COMMAND; diff --git a/src/ability/crud/update.rs b/src/ability/crud/update.rs index 54863a76..e355fa2b 100644 --- a/src/ability/crud/update.rs +++ b/src/ability/crud/update.rs @@ -143,7 +143,7 @@ pub struct Promised { args: Option>>, } -const COMMAND: &'static str = "crud/update"; +const COMMAND: &'static str = "/crud/update"; impl Command for Ready { const COMMAND: &'static str = COMMAND; diff --git a/src/ability/msg/any.rs b/src/ability/msg/any.rs index a20e3db9..16e8eac8 100644 --- a/src/ability/msg/any.rs +++ b/src/ability/msg/any.rs @@ -49,7 +49,7 @@ pub struct Any { } impl Command for Any { - const COMMAND: &'static str = "msg/*"; + const COMMAND: &'static str = "/msg"; } impl From for Ipld { diff --git a/src/ability/msg/receive.rs b/src/ability/msg/receive.rs index 1b0e817b..d730da8c 100644 --- a/src/ability/msg/receive.rs +++ b/src/ability/msg/receive.rs @@ -49,7 +49,7 @@ pub struct Receive { // FIXME needs promisory version -const COMMAND: &'static str = "msg/send"; +const COMMAND: &'static str = "/msg/send"; impl Command for Receive { const COMMAND: &'static str = COMMAND; diff --git a/src/ability/msg/send.rs b/src/ability/msg/send.rs index c65869d5..66905c68 100644 --- a/src/ability/msg/send.rs +++ b/src/ability/msg/send.rs @@ -5,7 +5,7 @@ use crate::{ invocation::promise, ipld, url, }; -use libipld_core::{error::SerdeError, ipld::Ipld}; +use libipld_core::ipld::Ipld; use serde::{Deserialize, Serialize}; #[cfg_attr(doc, aquamarine::aquamarine)] @@ -96,10 +96,6 @@ pub struct Promised { pub message: promise::Resolves, } -// impl Delegable for Ready { -// type Builder = Builder; -// } - impl promise::Resolvable for Ready { type Promised = Promised; } @@ -197,7 +193,7 @@ impl From for arguments::Named { } } -const COMMAND: &'static str = "msg/send"; +const COMMAND: &'static str = "/msg/send"; impl Command for Ready { const COMMAND: &'static str = COMMAND; diff --git a/src/ability/pipe.rs b/src/ability/pipe.rs new file mode 100644 index 00000000..32ab4744 --- /dev/null +++ b/src/ability/pipe.rs @@ -0,0 +1,38 @@ +use crate::{crypto::varsig, delegation, delegation::condition::Condition, did::Did, ipld}; +use libipld_core::{codec::Codec, ipld::Ipld}; + +pub struct Pipe< + C: Condition, + DID: Did, + V: varsig::Header, + Enc: Codec + TryFrom + Into, +> { + pub source: Cap, + pub sink: Cap, +} + +pub enum Cap, Enc: Codec + TryFrom + Into> +{ + Chain(delegation::Chain), + Literal(Ipld), +} + +pub struct PromisedPipe< + C: Condition, + DID: Did, + V: varsig::Header, + Enc: Codec + TryFrom + Into, +> { + pub source: PromisedCap, + pub sink: PromisedCap, +} + +pub enum PromisedCap< + C: Condition, + DID: Did, + V: varsig::Header, + Enc: Codec + TryFrom + Into, +> { + Chain(delegation::Chain), + Promised(ipld::Promised), +} diff --git a/src/ability/ucan.rs b/src/ability/ucan.rs index 0e3daaad..f2302b1a 100644 --- a/src/ability/ucan.rs +++ b/src/ability/ucan.rs @@ -1,4 +1,3 @@ //! Abilities for and about UCANs themselves pub mod revoke; -// FIXME pub mod proxy; diff --git a/src/ability/ucan/pipe.rs b/src/ability/ucan/pipe.rs new file mode 100644 index 00000000..2d79fcf8 --- /dev/null +++ b/src/ability/ucan/pipe.rs @@ -0,0 +1,11 @@ + use crate::delegation; + +pub struct Pipe< + C: Condition, + DID: Did, + V: varsig::Header, + Enc: Codec + TryFrom + Into, + > { + pub from: delegation::Chain, + pub to: delegation::Chain, +} diff --git a/src/ability/ucan/proxy.rs b/src/ability/ucan/proxy.rs deleted file mode 100644 index 1aa056c1..00000000 --- a/src/ability/ucan/proxy.rs +++ /dev/null @@ -1,59 +0,0 @@ -use crate::{ - ability::{arguments, command::Command}, - delegation::Delegable, - invocation::Promise, -}; -use libipld_core::ipld::Ipld; -use serde::{Deserialize, Serialize}; -use std::fmt::Debug; - -// NOTE This one is primarily for enabling delegationd recipets - -// FIXME can this *only* be a builder? -// NOTE UNLIKE the dynamic ability, this has cmd as an argument *at runtime* -#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] -pub struct Generic { - pub cmd: String, - pub args: Args, // FIXME Does this have specific fields? - // FIXME should args just be a CID -} - -pub type Ready = Generic; -pub type Builder = Generic>; -pub type Promised = Generic>; - -impl Command for Generic { - const COMMAND: &'static str = "ucan/proxy"; -} - -impl Delegable for Ready { - type Builder = Builder; -} - -impl From for Builder { - fn from(resolved: Ready) -> Builder { - Builder { - cmd: resolved.cmd, - args: Some(resolved.args), - } - } -} - -impl TryFrom for Ready { - type Error = (); // FIXME - - fn try_from(b: Builder) -> Result { - Ok(Ready { - cmd: b.cmd, - args: b.args.ok_or(())?, - }) - } -} - -impl From for arguments::Named { - fn from(b: Builder) -> arguments::Named { - let mut args = b.args.unwrap_or_default(); - args.insert("cmd".into(), Ipld::String(b.cmd)); - args - } -} diff --git a/src/ability/ucan/revoke.rs b/src/ability/ucan/revoke.rs index dcb7aec5..f6268fe5 100644 --- a/src/ability/ucan/revoke.rs +++ b/src/ability/ucan/revoke.rs @@ -20,24 +20,15 @@ pub struct Ready { pub ucan: Cid, } -const COMMAND: &'static str = "ucan/revoke"; +const COMMAND: &'static str = "/ucan/revoke"; impl Command for Ready { const COMMAND: &'static str = COMMAND; } - -// impl Command for Builder { -// const COMMAND: &'static str = COMMAND; -// } - impl Command for Promised { const COMMAND: &'static str = COMMAND; } -// impl Delegable for Ready { -// type Builder = Builder; -// } - impl TryFrom> for Ready { type Error = (); @@ -49,27 +40,6 @@ impl TryFrom> for Ready { } } -// impl TryFrom> for Builder { -// type Error = (); -// -// fn try_from(arguments: arguments::Named) -> Result { -// if let Some(ipld) = arguments.get("ucan") { -// let nt: ipld::cid::Newtype = ipld.try_into().map_err(|_| ())?; -// Ok(Builder { ucan: Some(nt.cid) }) -// } else { -// Ok(Builder { ucan: None }) -// } -// } -// } - -// impl From for Builder { -// fn from(promised: Promised) -> Self { -// Builder { -// ucan: promised.ucan.try_resolve().ok(), -// } -// } -// } - impl promise::Resolvable for Ready { type Promised = Promised; } @@ -80,50 +50,6 @@ impl From for arguments::Named { } } -// /// A variant with some fields waiting to be set. -// #[derive(Debug, Default, Clone, PartialEq, Serialize, Deserialize)] -// pub struct Builder { -// pub ucan: Option, -// } -// -// impl NoParents for Builder {} -// -// impl CheckSame for Builder { -// type Error = OptionalFieldError; -// -// fn check_same(&self, proof: &Self) -> Result<(), Self::Error> { -// self.ucan.check_same(&proof.ucan) -// } -// } - -// impl From for Builder { -// fn from(resolved: Ready) -> Builder { -// Builder { -// ucan: Some(resolved.ucan), -// } -// } -// } - -// impl TryFrom for Ready { -// type Error = (); -// -// fn try_from(b: Builder) -> Result { -// Ok(Ready { -// ucan: b.ucan.ok_or(())?, -// }) -// } -// } -// -// impl From for arguments::Named { -// fn from(b: Builder) -> arguments::Named { -// let mut btree = BTreeMap::new(); -// if let Some(cid) = b.ucan { -// btree.insert("ucan".into(), cid.into()); -// } -// arguments::Named(btree) -// } -// } - /// A variant where arguments may be [`Promise`][crate::invocation::promise]s. #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] pub struct Promised { diff --git a/src/ability/wasm/run.rs b/src/ability/wasm/run.rs index 0c7f8cfa..8d97d8b9 100644 --- a/src/ability/wasm/run.rs +++ b/src/ability/wasm/run.rs @@ -11,7 +11,7 @@ use crate::{ use libipld_core::ipld::Ipld; use serde::{Deserialize, Serialize}; -const COMMAND: &'static str = "wasm/run"; +const COMMAND: &'static str = "/wasm/run"; impl Command for Ready { const COMMAND: &'static str = COMMAND; diff --git a/src/crypto/signature/envelope.rs b/src/crypto/signature/envelope.rs index 22576fc0..f4f03f09 100644 --- a/src/crypto/signature/envelope.rs +++ b/src/crypto/signature/envelope.rs @@ -62,6 +62,16 @@ impl< } } + // FIXME extract into trait? + pub fn varsig_encode(self, w: &mut Vec) -> Result<(), libipld_core::error::Error> + where + Ipld: Encode, + { + let codec = self.varsig_header.codec().clone(); + let ipld: Ipld = self.into(); + ipld.encode(codec, w) + } + /// Attempt to sign some payload with a given signer. /// /// # Arguments diff --git a/src/delegation.rs b/src/delegation.rs index 543ce82b..bd0d975f 100644 --- a/src/delegation.rs +++ b/src/delegation.rs @@ -17,18 +17,16 @@ pub mod policy; pub mod store; mod agent; -// mod delegable; mod payload; pub use agent::Agent; -// pub use delegable::Delegable; pub use payload::*; +use crate::capsule::Capsule; use crate::{ // ability, crypto::{signature, varsig, Nonce}, did::{self, Did}, - // proof::{parents::CheckParents, same::CheckSame}, time::{TimeBoundError, Timestamp}, }; use condition::Condition; @@ -54,6 +52,20 @@ pub struct Delegation< Enc: Codec + TryFrom + Into, >(pub signature::Envelope, DID, V, Enc>); +#[derive(Clone, Debug, PartialEq)] +pub struct Chain< + C: Condition, + DID: Did, + V: varsig::Header, + Enc: Codec + TryFrom + Into, +>(Vec>); + +impl, Enc: Codec + TryFrom + Into> Capsule + for Chain +{ + const TAG: &'static str = "ucan/chain"; +} + /// A variant of [`Delegation`] that has the abilties and DIDs from this library pre-filled. pub type Preset = Delegation< condition::Preset, @@ -83,8 +95,8 @@ impl, Enc: Codec + Into + Tr } /// Retrive the `condition` of a [`Delegation`] - pub fn conditions(&self) -> &[C] { - &self.0.payload.conditions + pub fn policy(&self) -> &[C] { + &self.0.payload.policy } /// Retrive the `metadata` of a [`Delegation`] @@ -119,6 +131,13 @@ impl, Enc: Codec + Into + Tr &self.0.varsig_header } + pub fn varsig_encode(self, w: &mut Vec) -> Result<(), libipld_core::error::Error> + where + Ipld: Encode, + { + self.0.varsig_encode(w) + } + pub fn signature(&self) -> &DID::Signature { &self.0.signature } @@ -155,34 +174,3 @@ impl, Enc: Codec + Into + Tr signature::Envelope::try_sign(signer, varsig_header, payload).map(Delegation) } } - -// impl< -// B: CheckSame, -// C: Condition, -// DID: Did, -// V: varsig::Header, -// Enc: Codec + TryFrom + Into, -// > CheckSame for Delegation -// { -// type Error = ::Error; -// -// fn check_same(&self, proof: &Delegation) -> Result<(), Self::Error> { -// self.0.payload.check_same(&proof.payload()) -// } -// } -// -// impl< -// T: CheckParents, -// C: Condition, -// DID: Did, -// V: varsig::Header, -// Enc: Codec + TryFrom + Into, -// > CheckParents for Delegation -// { -// type Parents = Delegation; -// type ParentError = ::ParentError; -// -// fn check_parent(&self, proof: &Self::Parents) -> Result<(), Self::ParentError> { -// self.payload().check_parent(&proof.payload()) -// } -// } diff --git a/src/delegation/agent.rs b/src/delegation/agent.rs index 42745c80..971a6e97 100644 --- a/src/delegation/agent.rs +++ b/src/delegation/agent.rs @@ -60,7 +60,8 @@ where &self, audience: DID, subject: Option, - new_conditions: Vec, + command: String, + new_policy: Vec, metadata: BTreeMap, expiration: Timestamp, not_before: Option, @@ -76,11 +77,12 @@ where issuer: self.did.clone(), audience, subject, + command, metadata, nonce, expiration: expiration.into(), not_before: not_before.map(Into::into), - conditions: new_conditions, + policy: new_policy, }; return Ok( @@ -98,14 +100,15 @@ where .1 .payload(); - let mut conditions = to_delegate.conditions.clone(); - conditions.append(&mut new_conditions.clone()); + let mut policy = to_delegate.policy.clone(); + policy.append(&mut new_policy.clone()); let payload: Payload = Payload { issuer: self.did.clone(), audience, subject, - conditions, + command, + policy, metadata, nonce, expiration: expiration.into(), diff --git a/src/delegation/condition.rs b/src/delegation/condition.rs index 868f90b9..207a181e 100644 --- a/src/delegation/condition.rs +++ b/src/delegation/condition.rs @@ -1,4 +1,4 @@ -//! Conditions for syntactic validation of abilities in [`Delegation`][super::Delegation]s. +//! Policies for syntactic validation of abilities in [`Delegation`][super::Delegation]s. mod contains_all; mod contains_any; diff --git a/src/delegation/condition/traits.rs b/src/delegation/condition/traits.rs index 1c7b8f90..f1fcf7a6 100644 --- a/src/delegation/condition/traits.rs +++ b/src/delegation/condition/traits.rs @@ -1,10 +1,10 @@ -//! Traits for abstracting over conditions. +//! Traits for abstracting over policies. use crate::ability::arguments; use libipld_core::ipld::Ipld; use std::fmt; -/// A trait for conditions that can be run on named IPLD arguments. +/// A trait for policies that can be run on named IPLD arguments. pub trait Condition: fmt::Debug { /// Check that some condition is met on named IPLD arguments. fn validate(&self, args: &arguments::Named) -> bool; diff --git a/src/delegation/payload.rs b/src/delegation/payload.rs index bbde3e99..8247384b 100644 --- a/src/delegation/payload.rs +++ b/src/delegation/payload.rs @@ -45,8 +45,11 @@ pub struct Payload { /// The agent being delegated to. pub audience: DID, + /// The command being delegated. + pub command: String, + /// Any [`Condition`]s on the `ability_builder`. - pub conditions: Vec, + pub policy: Vec, /// Extensible, free-form fields. pub metadata: BTreeMap, @@ -129,6 +132,7 @@ where Option::::arbitrary(), DID::arbitrary_with(did_args.clone()), DID::arbitrary_with(did_args), + String::arbitrary(), Nonce::arbitrary(), Timestamp::arbitrary(), Option::::arbitrary(), @@ -144,17 +148,19 @@ where subject, issuer, audience, + command, nonce, expiration, not_before, metadata, - conditions, + policy, )| { Payload { issuer, subject, audience, - conditions, + command, + policy, metadata, nonce, expiration, diff --git a/src/delegation/policy.rs b/src/delegation/policy.rs index 7be6d057..64b58ce5 100644 --- a/src/delegation/policy.rs +++ b/src/delegation/policy.rs @@ -1,3 +1,3 @@ -pub mod frontend; -pub mod interpreter; -pub mod ir; +//pub mod frontend; +//pub mod interpreter; +// pub mod ir; diff --git a/src/delegation/policy/ir.rs b/src/delegation/policy/ir.rs index ef2f5788..a424f6e1 100644 --- a/src/delegation/policy/ir.rs +++ b/src/delegation/policy/ir.rs @@ -2,15 +2,12 @@ use enum_as_inner::EnumAsInner; use libipld_core::ipld::Ipld; use serde::{Deserialize, Serialize}; -use std::collections::BTreeMap; +use std::collections::{BTreeMap, BTreeSet}; #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] pub enum Term { // Leaves - Args, // $ Literal(Ipld), - Stream(Stream), - Selector(Selector), // NOTE the IR version doens't inline the results // Connectives @@ -27,48 +24,46 @@ pub enum Term { Glob(Value, Value), // Existential Quantification - Forall(Variable, Collection), // ∀x ∈ xs - Exists(Variable, Collection), // ∃x ∈ xs -> convert every -> some + Every(Variable, Value), // ∀x ∈ xs + Some(Variable, Value), // ∃x ∈ xs -> convert every -> some } // FIXME exract domain gen selectors first? // FIXME rename constraint or validation or expression or something? #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] pub enum Statement { + // Select from ?foo + Select(Selector, SelectorValue, Variable), // .foo.bar[].baz + // Connectives Not(Box), And(Box, Box), Or(Box, Box), - // Forall: unpack and unify size before and after - // Exists genrates more than one frame (unpacks an array) instead of one - Forall(Variable, Collection), // ∀x ∈ xs - Exists(Variable, Collection), // ∃x ∈ xs + // String Matcher + Glob(Value, Value), + Equal(Value, Value), // AKA unification // FIXME value can also be a selector // Comparison - Equal(Value, Value), // AKA unification // FIXME value can also be a selector GreaterThan(Value, Value), GreaterThanOrEqual(Value, Value), LessThan(Value, Value), LessThanOrEqual(Value, Value), - // String Matcher - Glob(Value, Value), - - // Select from ?foo - Select(Selector, SelectorValue, Variable), // .foo.bar[].baz + // Forall: unpack and unify size before and after + // Exists genrates more than one frame (unpacks an array) instead of one + Forall(Variable, Collection), // ∀x ∈ xs + Exists(Variable, Collection), // ∃x ∈ xs } -#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] pub struct Variable(pub String); // ?x #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] pub enum Collection { Array(Vec), Map(BTreeMap), - // NOTE The below can always be desugared, esp because this is now only used with forall/exists - // Variable(Variable), ] - // Selector(Selector), + Selector(Vec), } #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] @@ -81,12 +76,11 @@ pub enum PathSegment { This, // . Index(usize), // [2] Key(String), // ["key"] (or .key) - FlattenAll, // [] --> creates an Array + // FlattenAll, // [] --> creates an Array } #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] pub enum SelectorValue { - Args, Literal(Ipld), Variable(Variable), } @@ -94,7 +88,7 @@ pub enum SelectorValue { #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] pub enum Value { Literal(Ipld), - Variable(Variable), + Selector(Vec), } pub fn glob(input: &Ipld, pattern: &Ipld) -> bool { @@ -193,3 +187,214 @@ impl Stream { // // #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] // pub struct SomeStream(Vec); + +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub enum LogicIpld { + Null, + Bool(bool), + Float(f64), + Integer(i128), + String(String), + Bytes(Vec), + List(Vec), + Map(BTreeMap), + + // A new challenger has appeared! + Var(String), +} + +impl LogicIpld { + pub fn substitute(&mut self, bindings: BTreeMap) { + match self { + LogicIpld::Var(var_id) => { + if let Some(value) = bindings.get(&Variable(*var_id)) { + *self = value.clone(); + } + } + LogicIpld::List(xs) => { + for x in xs { + x.substitute(bindings.clone()); + } + } + LogicIpld::Map(btree) => { + for (_, x) in btree { + x.substitute(bindings.clone()); + } + } + _other => (), + } + } + + pub fn extract_into(&self, vars: &mut BTreeSet) { + match self { + LogicIpld::Var(var_id) => { + vars.insert(Variable(var_id.clone())); + } + LogicIpld::List(xs) => { + for x in xs { + x.extract_into(vars); + } + } + LogicIpld::Map(btree) => { + for (_, x) in btree { + x.extract_into(vars); + } + } + _other => (), + } + } + + pub fn unify( + self, + other: Self, + bindings: BTreeMap, + ) -> Result<(Self, BTreeSet), ()> { + match (self, other) { + (LogicIpld::Null, LogicIpld::Null) => Ok((LogicIpld::Null, BTreeSet::new())), + (LogicIpld::Bool(a), LogicIpld::Bool(b)) => { + if a == b { + Ok((LogicIpld::Bool(a), BTreeSet::new())) + } else { + Err(()) + } + } + (LogicIpld::Float(a), LogicIpld::Float(b)) => { + if a == b { + Ok((LogicIpld::Float(a), BTreeSet::new())) + } else { + Err(()) + } + } + (LogicIpld::Integer(a), LogicIpld::Integer(b)) => { + if a == b { + Ok((LogicIpld::Integer(a), BTreeSet::new())) + } else { + Err(()) + } + } + (LogicIpld::String(a), LogicIpld::String(b)) => { + if a == b { + Ok((LogicIpld::String(a), BTreeSet::new())) + } else { + Err(()) + } + } + (LogicIpld::Bytes(a), LogicIpld::Bytes(b)) => { + if a == b { + Ok((LogicIpld::Bytes(a), BTreeSet::new())) + } else { + Err(()) + } + } + (LogicIpld::List(a), LogicIpld::List(b)) => { + // FIXME + if a.len() != b.len() { + return Err(()); + } + let mut bindings = BTreeSet::new(); + let mut result = Vec::with_capacity(a.len()); + for (a, b) in a.into_iter().zip(b.into_iter()) { + let (unified, mut new_bindings) = a.unify(b)?; + result.push(unified); + bindings.append(&mut new_bindings); + } + Ok((LogicIpld::List(result), bindings)) + } + (LogicIpld::Map(a), LogicIpld::Map(b)) => { + // FIXME + if a.len() != b.len() { + return Err(()); + } + let mut bindings = BTreeSet::new(); + let mut result = BTreeMap::new(); + for (k, a) in a.into_iter() { + if let Some(b) = b.get(&k) { + let (unified, mut new_bindings) = a.unify(b.clone())?; + result.insert(k, unified); + bindings.append(&mut new_bindings); + } else { + return Err(()); + } + } + Ok((LogicIpld::Map(result), bindings)) + } + (LogicIpld::Var(a), LogicIpld::Var(b)) => { + // FIXME + + // If I have a binding for a, and no binding for b, set ?b := ?a + // If I have a binding for b, and no binding for a, set ?a := ?b + // If I have neither binding, what do??? + // If I have both bindings, and they are the same, great + // 1. check ?a == ?b + // 2 set ?a := ?b + // NOTE: would need to update the lookup procedure elsewhere + // to do recursive lookup + // If I have both bindings, and they are not immeditely equal, + // recursively unify them, and then set ?a := ?b + // NOTE: during recursion, if we see ?a in ?b or ?b in ?a, you need to bail + + if a == b { + Ok((LogicIpld::Var(a), BTreeSet::new())) + } else { + Err(()) + } + } + (LogicIpld::Var(lhs_tag), logic_ipld) => { + match logic_ipld { + rhs @ LogicIpld::Var(rhs_tag) => { + //FIXME double check + if let Some(b) = bindings.get(&rhs_tag) { + let (unified, mut new_bindings) = + LogicIpld::Var(a).unify(rhs.clone())?; + new_bindings.insert(rhs.clone()); + Ok((unified, new_bindings)) + } else { + let mut new_bindings = BTreeSet::new(); + new_bindings.insert(Variable(b.clone())); + Ok((LogicIpld::Var(a), new_bindings)) + } + } + + _ => { + let mut new_bindings = BTreeSet::new(); + new_bindings.insert(Variable(a.clone())); + Ok((logic_ipld, new_bindings)) + } + } + } + (lhs, rhs @ LogicIpld::Var(_)) => rhs.unify(lhs, bindings), + + _ => Err(()), + } + } +} + +impl TryFrom for Ipld { + type Error = Variable; + + fn try_from(logic: LogicIpld) -> Result { + match logic { + LogicIpld::Null => Ok(Ipld::Null), + LogicIpld::Bool(b) => Ok(Ipld::Bool(b)), + LogicIpld::Float(f) => Ok(Ipld::Float(f)), + LogicIpld::Integer(i) => Ok(Ipld::Integer(i)), + LogicIpld::String(s) => Ok(Ipld::String(s)), + LogicIpld::Bytes(b) => Ok(Ipld::Bytes(b)), + LogicIpld::List(xs) => xs + .into_iter() + .try_fold(vec![], |mut acc, x| { + acc.push(Ipld::try_from(x)?); + Ok(acc) + }) + .map(Ipld::List), + LogicIpld::Map(btree) => btree + .into_iter() + .try_fold(BTreeMap::new(), |mut acc, (k, v)| { + acc.insert(k, Ipld::try_from(v)?); + Ok(acc) + }) + .map(Ipld::Map), + LogicIpld::Var(var_id) => Err(Variable(var_id)), + } + } +} diff --git a/src/delegation/store/memory.rs b/src/delegation/store/memory.rs index e87bfc5d..b05b4039 100644 --- a/src/delegation/store/memory.rs +++ b/src/delegation/store/memory.rs @@ -121,7 +121,7 @@ impl< &self, aud: &DID, subject: &Option, - conditions: Vec, + policy: Vec, now: SystemTime, ) -> Result)>>, Self::DelegationStoreError> { diff --git a/src/delegation/store/traits.rs b/src/delegation/store/traits.rs index 44b032dd..e3090ff8 100644 --- a/src/delegation/store/traits.rs +++ b/src/delegation/store/traits.rs @@ -39,7 +39,7 @@ pub trait Store< &self, audience: &DID, subject: &Option, - conditions: Vec, + policy: Vec, now: SystemTime, ) -> Result)>>, Self::DelegationStoreError>; @@ -47,10 +47,10 @@ pub trait Store< &self, issuer: DID, audience: &DID, - conditions: Vec, + policy: Vec, now: SystemTime, ) -> Result { - self.get_chain(audience, &Some(issuer), conditions, now) + self.get_chain(audience, &Some(issuer), policy, now) .map(|chain| chain.is_some()) } diff --git a/src/invocation.rs b/src/invocation.rs index 7e66670e..a878bebc 100644 --- a/src/invocation.rs +++ b/src/invocation.rs @@ -78,6 +78,13 @@ where Invocation(signature::Envelope::new(varsig_header, signature, payload)) } + pub fn varsig_encode(self, w: &mut Vec) -> Result<(), libipld_core::error::Error> + where + Ipld: Encode, + { + self.0.varsig_encode(w) + } + pub fn payload(&self) -> &Payload { &self.0.payload } diff --git a/src/invocation/payload.rs b/src/invocation/payload.rs index 0350aa98..3885afaa 100644 --- a/src/invocation/payload.rs +++ b/src/invocation/payload.rs @@ -138,12 +138,17 @@ impl Payload { now: &SystemTime, ) -> Result<(), ValidationError> where - A: Clone, + A: ToCommand + Clone, DID: Clone, arguments::Named: From, { let args: arguments::Named = self.ability.clone().into(); + let mut cmd = self.ability.to_command(); + if !cmd.ends_with('/') { + cmd.push('/'); + } + proofs.into_iter().try_fold(&self.issuer, |iss, proof| { // FIXME extract step function? if *iss != proof.audience { @@ -166,7 +171,11 @@ impl Payload { } } - for predicate in proof.conditions.iter() { + if !cmd.starts_with(&proof.command) { + return Err(ValidationError::CommandMismatch(proof.command.clone())); + } + + for predicate in proof.policy.iter() { if !predicate.validate(&args) { return Err(ValidationError::FailedCondition(predicate.clone())); } @@ -194,6 +203,9 @@ pub enum ValidationError { #[error("The delegation is not yet valid")] NotYetValid, + #[error("The command of the delegation does not match the proof: {0:?}")] + CommandMismatch(String), + #[error("The delegation failed a condition: {0:?}")] FailedCondition(C), } @@ -202,26 +214,6 @@ impl Capsule for Payload { const TAG: &'static str = "ucan/i/1.0.0-rc.1"; } -// impl From> for delegation::Payload { -// fn from(inv_payload: Payload) -> Self { -// delegation::Payload { -// issuer: inv_payload.issuer, -// subject: Some(inv_payload.subject.clone()), -// audience: inv_payload.audience.unwrap_or(inv_payload.subject), -// -// conditions: vec![], -// -// metadata: inv_payload.metadata, -// nonce: inv_payload.nonce, -// -// not_before: None, -// expiration: inv_payload -// .expiration -// .unwrap_or(Timestamp::postel(SystemTime::now())), -// } -// } -// } - impl, DID: Did> From> for arguments::Named { fn from(payload: Payload) -> Self { let mut args = arguments::Named::from_iter([ diff --git a/src/ipld/promised.rs b/src/ipld/promised.rs index 589fe934..52a0c907 100644 --- a/src/ipld/promised.rs +++ b/src/ipld/promised.rs @@ -1,4 +1,3 @@ -// use super::enriched::Enriched; use crate::{ ability::arguments, invocation::promise::{Pending, Promise, PromiseAny, PromiseErr, PromiseOk, Resolves}, @@ -14,22 +13,40 @@ use std::{collections::BTreeMap, path::PathBuf}; /// [`Promised`] resolves to regular [`Ipld`]. #[derive(Debug, Clone, PartialEq, Serialize, Deserialize, EnumAsInner)] pub enum Promised { - // Resolved Leaves + /// Resolved null. Null, + + /// Resolved Boolean. Bool(bool), + + /// Resolved integer. Integer(i128), + + /// Resolved float. Float(f64), + + /// Resolved string. String(String), + + /// Resolved bytes. Bytes(Vec), + + /// Resolved link. Link(Cid), - // Pending Leaves + /// Promise pending the `ok` branch. WaitOk(Cid), + + /// Promise pending the `err` branch. WaitErr(Cid), + + /// Promise pending either branch. WaitAny(Cid), - // Recursive + /// Recursively promised list. List(Vec), + + /// Recursively promised map. Map(BTreeMap), } @@ -278,7 +295,6 @@ impl TryFrom for url::Newtype { fn try_from(promised: Promised) -> Result { match promised { Promised::String(s) => Ok(url::Newtype(::url::Url::parse(&s).map_err(|_| ())?)), - // FIXME Promised::Link(cid) => Ok(url::Newtype::from(cid)), _ => Err(()), } } @@ -366,12 +382,6 @@ pub struct PostOrderIpldIter<'a> { outbound: Vec<&'a Promised>, } -// #[derive(Clone, Debug, PartialEq)] -// pub enum Item<'a> { -// Node(&'a Promised), -// Inner(&'a Cid), -// } - impl<'a> PostOrderIpldIter<'a> { /// Initialize a new [`PostOrderIpldIter`] #[must_use] diff --git a/src/lib.rs b/src/lib.rs index 32a40fdc..86efec3b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -35,9 +35,5 @@ pub use delegation::Delegation; pub use invocation::Invocation; pub use receipt::Receipt; -///////////// -// FIXME s // -///////////// - -// show example of multiple hierarchies of "all things accepted" -// delegating down to inner versions of this +// FIXME +// show pipe diff --git a/src/receipt.rs b/src/receipt.rs index cb43ce6b..f771b90f 100644 --- a/src/receipt.rs +++ b/src/receipt.rs @@ -5,6 +5,9 @@ //! - [`Store`] is the storage interface for [`Receipt`]s. //! - [`Responds`] associates the response success type to an [Ability][crate::ability]. +// FIXME consider "assertion"? +// + mod payload; mod responds; @@ -19,7 +22,10 @@ use crate::{ crypto::{signature, varsig}, did, }; -use libipld_core::codec::Codec; +use libipld_core::{ + codec::{Codec, Encode}, + ipld::Ipld, +}; /// The complete, signed receipt of an [`Invocation`][`crate::invocation::Invocation`]. #[derive(Clone, Debug, PartialEq)] @@ -39,8 +45,8 @@ pub type Preset = Receipt< varsig::encoding::Preset, >; -impl, C: Codec + Into + TryFrom> - Receipt +impl, Enc: Codec + Into + TryFrom> + Receipt { /// Returns the [`Payload`] of the [`Receipt`]. pub fn payload(&self) -> &Payload { @@ -51,4 +57,11 @@ impl, C: Codec + Into + Tr pub fn signature(&self) -> &DID::Signature { &self.0.signature } + + pub fn varsig_encode(self, w: &mut Vec) -> Result<(), libipld_core::error::Error> + where + Ipld: Encode, + { + self.0.varsig_encode(w) + } } diff --git a/src/task.rs b/src/task.rs index 032378e7..6b931c59 100644 --- a/src/task.rs +++ b/src/task.rs @@ -1,7 +1,6 @@ //! Task indices for [`Receipt`][crate::receipt::Receipt] reverse lookup. mod id; - pub use id::Id; use crate::{ability::arguments, crypto::Nonce, did}; diff --git a/src/task/id.rs b/src/task/id.rs index 0414e722..de799110 100644 --- a/src/task/id.rs +++ b/src/task/id.rs @@ -1,3 +1,5 @@ +//! A newtype wrapper around [`Cid`]s to tag them as able to identify a particular invocation. + use libipld_core::{cid::Cid, error::SerdeError, ipld::Ipld, serde as ipld_serde}; use serde_derive::{Deserialize, Serialize}; use std::fmt::Debug; diff --git a/src/url.rs b/src/url.rs index d23b8efb..bb8ca202 100644 --- a/src/url.rs +++ b/src/url.rs @@ -50,18 +50,6 @@ impl fmt::Display for Newtype { } } -// impl CheckSame for Newtype { -// type Error = (); -// -// fn check_same(&self, other: &Self) -> Result<(), Self::Error> { -// if self == other { -// Ok(()) -// } else { -// Err(()) -// } -// } -// } - impl From for Ipld { fn from(newtype: Newtype) -> Self { newtype.into() From 5bdba9bdab70d8acb00902bb60687d49102421b4 Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Fri, 1 Mar 2024 18:45:48 -0800 Subject: [PATCH 109/188] Minimal predicate language --- src/ability/command.rs | 2 +- src/delegation/policy.rs | 2 +- src/delegation/policy/ir.rs | 689 +++++++++++++++++++----------------- src/ipld/newtype.rs | 62 +++- src/ipld/number.rs | 13 +- 5 files changed, 430 insertions(+), 338 deletions(-) diff --git a/src/ability/command.rs b/src/ability/command.rs index eb97ea7f..2cd85c3b 100644 --- a/src/ability/command.rs +++ b/src/ability/command.rs @@ -35,7 +35,7 @@ /// const COMMAND: &'static str = "/storage/upload"; /// } /// -/// assert_eq!(Upload::COMMAND, "storage/upload"); +/// assert_eq!(Upload::COMMAND, "/storage/upload"); /// ``` pub trait Command { /// The value that will be placed in the UCAN's `cmd` field for the given type diff --git a/src/delegation/policy.rs b/src/delegation/policy.rs index 64b58ce5..30e6f251 100644 --- a/src/delegation/policy.rs +++ b/src/delegation/policy.rs @@ -1,3 +1,3 @@ //pub mod frontend; //pub mod interpreter; -// pub mod ir; +pub mod ir; diff --git a/src/delegation/policy/ir.rs b/src/delegation/policy/ir.rs index a424f6e1..3396d526 100644 --- a/src/delegation/policy/ir.rs +++ b/src/delegation/policy/ir.rs @@ -1,400 +1,421 @@ //FIXME rename core +use crate::ipld; use enum_as_inner::EnumAsInner; use libipld_core::ipld::Ipld; use serde::{Deserialize, Serialize}; -use std::collections::{BTreeMap, BTreeSet}; - -#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] -pub enum Term { - // Leaves - Literal(Ipld), - Selector(Selector), // NOTE the IR version doens't inline the results - - // Connectives - Not(Box), - And(Vec), - Or(Vec), - - // Comparison - Equal(Value, Value), // AKA unification - GreaterThan(Value, Value), - LessThan(Value, Value), - - // String Matcher - Glob(Value, Value), - - // Existential Quantification - Every(Variable, Value), // ∀x ∈ xs - Some(Variable, Value), // ∃x ∈ xs -> convert every -> some +use std::{collections::BTreeMap, fmt}; + +impl Predicate { + pub fn run(self, data: &Ipld) -> Result { + Ok(match self { + Predicate::True => true, + Predicate::False => false, + Predicate::Equal(lhs, rhs) => lhs.resolve(data)? == rhs.resolve(data)?, + Predicate::GreaterThan(lhs, rhs) => lhs.resolve(data)? > rhs.resolve(data)?, + Predicate::GreaterThanOrEqual(lhs, rhs) => lhs.resolve(data)? >= rhs.resolve(data)?, + Predicate::LessThan(lhs, rhs) => lhs.resolve(data)? < rhs.resolve(data)?, + Predicate::LessThanOrEqual(lhs, rhs) => lhs.resolve(data)? <= rhs.resolve(data)?, + Predicate::Like(lhs, rhs) => glob(&lhs.resolve(data)?, &rhs.resolve(data)?), + Predicate::Not(inner) => !inner.run(data)?, + Predicate::And(lhs, rhs) => lhs.run(data)? && rhs.run(data)?, + Predicate::Or(lhs, rhs) => lhs.run(data)? || rhs.run(data)?, + Predicate::Forall(xs, p) => xs + .resolve(data)? + .to_vec() + .iter() + .try_fold(true, |acc, ipld| Ok(acc && p.clone().run(ipld)?))?, + Predicate::Exists(xs, p) => { + let pred = p.clone(); + + xs.resolve(data)? + .to_vec() + .iter() + .try_fold(true, |acc, ipld| Ok(acc || pred.clone().run(ipld)?))? + } + }) + } } -// FIXME exract domain gen selectors first? -// FIXME rename constraint or validation or expression or something? -#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] -pub enum Statement { - // Select from ?foo - Select(Selector, SelectorValue, Variable), // .foo.bar[].baz - - // Connectives - Not(Box), - And(Box, Box), - Or(Box, Box), +pub enum RunError { + IndexOutOfBounds, + KeyNotFound, + NotAList, + NotAMap, + NotACollection, + NotANumber(>::Error), +} - // String Matcher - Glob(Value, Value), - Equal(Value, Value), // AKA unification // FIXME value can also be a selector +trait Resolve { + fn resolve(self, ctx: &Ipld) -> Result; +} - // Comparison - GreaterThan(Value, Value), - GreaterThanOrEqual(Value, Value), - LessThan(Value, Value), - LessThanOrEqual(Value, Value), - - // Forall: unpack and unify size before and after - // Exists genrates more than one frame (unpacks an array) instead of one - Forall(Variable, Collection), // ∀x ∈ xs - Exists(Variable, Collection), // ∃x ∈ xs +impl Resolve for Ipld { + fn resolve(self, _ctx: &Ipld) -> Result { + Ok(self) + } } -#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] -pub struct Variable(pub String); // ?x +impl Resolve for ipld::Number { + fn resolve(self, _ctx: &Ipld) -> Result { + Ok(self) + } +} -#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] -pub enum Collection { - Array(Vec), - Map(BTreeMap), - Selector(Vec), +impl Resolve for Collection { + fn resolve(self, _ctx: &Ipld) -> Result { + Ok(self) + } } -#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] -pub struct Selector(pub Vec); // .foo.bar[].baz +// impl Resolve for Collection { +// fn resolve(self, ctx: Ipld) -> Result { +// match self { +// Collection::Array(xs) => Ok(Ipld::List(xs)), +// Collection::Map(xs) => Ok(Ipld::Map(xs)), +// } +// } +// } -// FIXME need an IR representation of $args +// FIXME Normal form? -#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] -pub enum PathSegment { - This, // . - Index(usize), // [2] - Key(String), // ["key"] (or .key) - // FlattenAll, // [] --> creates an Array +impl Resolve for String { + fn resolve(self, _ctx: &Ipld) -> Result { + Ok(self) + } } -#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] -pub enum SelectorValue { - Literal(Ipld), - Variable(Variable), -} +pub struct Text(String); -#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] -pub enum Value { - Literal(Ipld), - Selector(Vec), -} +// impl TryFrom for String { +// fn try_from(ipld: Ipld) -> Result>::Error> { +// match ipld { +// Ipld::String(s) => Ok(s), +// _ => Err(()), +// } +// } +// } -pub fn glob(input: &Ipld, pattern: &Ipld) -> bool { - if let (Ipld::String(s), Ipld::String(pat)) = (input, pattern) { - let mut input = s.chars(); - let mut pattern = pat.chars(); // Ugly - - loop { - match (input.next(), pattern.next()) { - (Some(i), Some(p)) => { - if p == '*' { - return true; - } else if i != p { - return false; - } - } - (Some(_), None) => { - return false; // FIXME correct? - } - (None, Some(p)) => { - if p == '*' { - return true; - } - } - (None, None) => { - return true; - } +impl SelectorOr { + fn resolve(self, ctx: &Ipld) -> Result { + match self { + SelectorOr::Pure(inner) => Ok(inner), + SelectorOr::Get(ops) => { + ops.iter() + .try_fold((ctx.clone(), vec![]), |(ipld, mut seen_ops), op| { + seen_ops.push(op); + + match op { + SelectorOp::This => Ok((ipld, seen_ops)), + SelectorOp::Try(inner) => { + let op: SelectorOp = *inner.clone(); + let ipld: Ipld = SelectorOr::Get::(vec![op]) + .resolve(ctx) + .unwrap_or(Ipld::Null); + + Ok((ipld, seen_ops)) + } + SelectorOp::Index(i) => { + let result = match ipld { + Ipld::List(xs) => xs + .get(*i) + .ok_or(SelectorError { + path: seen_ops.iter().map(|op| (*op).clone()).collect(), + reason: SelectorErrorReason::IndexOutOfBounds, + }) + .cloned(), + // FIXME behaviour on maps? type error + _ => Err(SelectorError { + path: seen_ops.iter().map(|op| (*op).clone()).collect(), + reason: SelectorErrorReason::NotAList, + }), + }; + + Ok((result?, seen_ops)) + } + SelectorOp::Key(k) => { + let result = match ipld { + Ipld::Map(xs) => xs + .get(k) + .ok_or(SelectorError::from_refs( + &seen_ops, + SelectorErrorReason::KeyNotFound, + )) + .cloned(), + _ => Err(SelectorError::from_refs( + &seen_ops, + SelectorErrorReason::NotAMap, + )), + }; + + Ok((result?.clone(), seen_ops)) + } + SelectorOp::Values => { + let result = match ipld { + Ipld::List(xs) => Ok(Ipld::List(xs)), + Ipld::Map(xs) => Ok(Ipld::List(xs.values().cloned().collect())), + _ => Err(SelectorError::from_refs( + &seen_ops, + SelectorErrorReason::NotACollection, + )), + }; + + Ok((result?.clone(), seen_ops)) + } + } + }) + .and_then(|(ipld, ref path)| { + T::try_from_ipld(ipld).map_err(|e| SelectorError::from_refs(path, e)) + }) } } } - panic!("FIXME"); } -#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, EnumAsInner)] -pub enum Stream { - Every(BTreeMap), // "All or nothing" - Some(BTreeMap), // FIXME disambiguate from Option::Some +pub trait TryFromIpld: Sized { + fn try_from_ipld(ipld: Ipld) -> Result; } -impl Stream { - pub fn remove(&mut self, key: usize) { - match self { - Stream::Every(xs) => { - xs.remove(&key); - } - Stream::Some(xs) => { - xs.remove(&key); - } - } +impl TryFromIpld for Ipld { + fn try_from_ipld(ipld: Ipld) -> Result { + Ok(ipld) } +} - pub fn len(&self) -> usize { - match self { - Stream::Every(xs) => xs.len(), - Stream::Some(xs) => xs.len(), +impl TryFromIpld for ipld::Number { + fn try_from_ipld(ipld: Ipld) -> Result { + match ipld { + Ipld::Integer(i) => Ok(ipld::Number::Integer(i)), + Ipld::Float(f) => Ok(ipld::Number::Float(f)), + _ => Err(SelectorErrorReason::NotANumber), } } +} - pub fn iter(&self) -> impl Iterator { - match self { - Stream::Every(xs) => xs.iter(), - Stream::Some(xs) => xs.iter(), +impl TryFromIpld for String { + fn try_from_ipld(ipld: Ipld) -> Result { + match ipld { + Ipld::String(s) => Ok(s), + _ => Err(SelectorErrorReason::NotAString), } } +} - pub fn to_btree(self) -> BTreeMap { - match self { - Stream::Every(xs) => xs, - Stream::Some(xs) => xs, +impl TryFromIpld for Collection { + fn try_from_ipld(ipld: Ipld) -> Result { + match ipld { + Ipld::List(xs) => Ok(Collection::Array(xs)), + Ipld::Map(xs) => Ok(Collection::Map(xs)), + _ => Err(SelectorErrorReason::NotACollection), } } +} - pub fn map(self, f: impl Fn(BTreeMap) -> BTreeMap) -> Stream { - match self { - Stream::Every(xs) => { - let updated = f(xs); - Stream::Every(updated) - } - Stream::Some(xs) => { - let updated = f(xs); - Stream::Some(updated) - } - } - } +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub struct SelectorError { + pub path: Vec, + pub reason: SelectorErrorReason, +} - pub fn is_empty(&self) -> bool { - match self { - Stream::Every(xs) => xs.is_empty(), - Stream::Some(xs) => xs.is_empty(), +impl SelectorError { + pub fn from_refs(path_refs: &Vec<&SelectorOp>, reason: SelectorErrorReason) -> SelectorError { + SelectorError { + path: path_refs.iter().map(|op| (*op).clone()).collect(), + reason, } } } -// #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] -// pub struct EveryStream(V); -// -// #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] -// pub struct SomeStream(Vec); +#[derive(Debug, Copy, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub enum SelectorErrorReason { + IndexOutOfBounds, + KeyNotFound, + NotAList, + NotAMap, + NotACollection, + NotANumber, + NotAString, +} +// FIXME exract domain gen selectors first? +// FIXME rename constraint or validation or expression or something? #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] -pub enum LogicIpld { - Null, - Bool(bool), - Float(f64), - Integer(i128), - String(String), - Bytes(Vec), - List(Vec), - Map(BTreeMap), - - // A new challenger has appeared! - Var(String), +pub enum Predicate { + // Booleans for connectives? FIXME + True, + False, + + // Comparison + Equal(SelectorOr, SelectorOr), + + GreaterThan(SelectorOr, SelectorOr), + GreaterThanOrEqual(SelectorOr, SelectorOr), + + LessThan(SelectorOr, SelectorOr), + LessThanOrEqual(SelectorOr, SelectorOr), + + Like(SelectorOr, SelectorOr), + + // Connectives + Not(Box), + And(Box, Box), + Or(Box, Box), + + // Collection iteration + Forall(SelectorOr, Box), // ∀x ∈ xs + Exists(SelectorOr, Box), // ∃x ∈ xs +} + +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub enum SelectorOr { + Get(Vec), + Pure(T), +} + +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub enum Collection { + Array(Vec), + Map(BTreeMap), } -impl LogicIpld { - pub fn substitute(&mut self, bindings: BTreeMap) { +impl Collection { + pub fn to_vec(self) -> Vec { match self { - LogicIpld::Var(var_id) => { - if let Some(value) = bindings.get(&Variable(*var_id)) { - *self = value.clone(); - } - } - LogicIpld::List(xs) => { - for x in xs { - x.substitute(bindings.clone()); - } - } - LogicIpld::Map(btree) => { - for (_, x) in btree { - x.substitute(bindings.clone()); - } - } - _other => (), + Collection::Array(xs) => xs, + Collection::Map(xs) => xs.into_values().collect(), } } +} - pub fn extract_into(&self, vars: &mut BTreeSet) { +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub enum SelectorOp { + This, // . + Index(usize), // [2] + Key(String), // ["key"] (or .key) + Values, // .[] + Try(Box), // ? +} + +impl fmt::Display for SelectorOp { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { - LogicIpld::Var(var_id) => { - vars.insert(Variable(var_id.clone())); - } - LogicIpld::List(xs) => { - for x in xs { - x.extract_into(vars); - } - } - LogicIpld::Map(btree) => { - for (_, x) in btree { - x.extract_into(vars); + SelectorOp::This => write!(f, "."), + SelectorOp::Index(i) => write!(f, "[{}]", i), + SelectorOp::Key(k) => { + if let Some(first) = k.chars().next() { + if first.is_alphabetic() && k.chars().all(char::is_alphanumeric) { + write!(f, ".{}", k) + } else { + write!(f, "[\"{}\"]", k) + } + } else { + write!(f, "[\"{}\"]", k) } } - _other => (), + SelectorOp::Values => write!(f, "[]"), + SelectorOp::Try(inner) => write!(f, "{}?", inner), } } +} - pub fn unify( - self, - other: Self, - bindings: BTreeMap, - ) -> Result<(Self, BTreeSet), ()> { - match (self, other) { - (LogicIpld::Null, LogicIpld::Null) => Ok((LogicIpld::Null, BTreeSet::new())), - (LogicIpld::Bool(a), LogicIpld::Bool(b)) => { - if a == b { - Ok((LogicIpld::Bool(a), BTreeSet::new())) - } else { - Err(()) - } - } - (LogicIpld::Float(a), LogicIpld::Float(b)) => { - if a == b { - Ok((LogicIpld::Float(a), BTreeSet::new())) - } else { - Err(()) - } - } - (LogicIpld::Integer(a), LogicIpld::Integer(b)) => { - if a == b { - Ok((LogicIpld::Integer(a), BTreeSet::new())) - } else { - Err(()) - } - } - (LogicIpld::String(a), LogicIpld::String(b)) => { - if a == b { - Ok((LogicIpld::String(a), BTreeSet::new())) - } else { - Err(()) - } - } - (LogicIpld::Bytes(a), LogicIpld::Bytes(b)) => { - if a == b { - Ok((LogicIpld::Bytes(a), BTreeSet::new())) - } else { - Err(()) - } - } - (LogicIpld::List(a), LogicIpld::List(b)) => { - // FIXME - if a.len() != b.len() { - return Err(()); - } - let mut bindings = BTreeSet::new(); - let mut result = Vec::with_capacity(a.len()); - for (a, b) in a.into_iter().zip(b.into_iter()) { - let (unified, mut new_bindings) = a.unify(b)?; - result.push(unified); - bindings.append(&mut new_bindings); +#[derive(Debug, Clone, PartialEq)] +pub struct Selector(pub Vec); + +impl Serialize for Selector { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + self.0 + .iter() + .fold("".into(), |acc, seg| format!("{}{}", acc, seg.to_string())) + .serialize(serializer) + } +} + +pub fn glob(input: &String, pattern: &String) -> bool { + let mut chars = input.chars(); + let mut like = pattern.chars(); + + loop { + match (chars.next(), like.next()) { + (Some(i), Some(p)) => { + if p == '*' { + return true; + } else if i != p { + return false; } - Ok((LogicIpld::List(result), bindings)) } - (LogicIpld::Map(a), LogicIpld::Map(b)) => { - // FIXME - if a.len() != b.len() { - return Err(()); - } - let mut bindings = BTreeSet::new(); - let mut result = BTreeMap::new(); - for (k, a) in a.into_iter() { - if let Some(b) = b.get(&k) { - let (unified, mut new_bindings) = a.unify(b.clone())?; - result.insert(k, unified); - bindings.append(&mut new_bindings); - } else { - return Err(()); - } - } - Ok((LogicIpld::Map(result), bindings)) + (Some(_), None) => { + return false; // FIXME correct? } - (LogicIpld::Var(a), LogicIpld::Var(b)) => { - // FIXME - - // If I have a binding for a, and no binding for b, set ?b := ?a - // If I have a binding for b, and no binding for a, set ?a := ?b - // If I have neither binding, what do??? - // If I have both bindings, and they are the same, great - // 1. check ?a == ?b - // 2 set ?a := ?b - // NOTE: would need to update the lookup procedure elsewhere - // to do recursive lookup - // If I have both bindings, and they are not immeditely equal, - // recursively unify them, and then set ?a := ?b - // NOTE: during recursion, if we see ?a in ?b or ?b in ?a, you need to bail - - if a == b { - Ok((LogicIpld::Var(a), BTreeSet::new())) - } else { - Err(()) + (None, Some(p)) => { + if p == '*' { + return true; } } - (LogicIpld::Var(lhs_tag), logic_ipld) => { - match logic_ipld { - rhs @ LogicIpld::Var(rhs_tag) => { - //FIXME double check - if let Some(b) = bindings.get(&rhs_tag) { - let (unified, mut new_bindings) = - LogicIpld::Var(a).unify(rhs.clone())?; - new_bindings.insert(rhs.clone()); - Ok((unified, new_bindings)) - } else { - let mut new_bindings = BTreeSet::new(); - new_bindings.insert(Variable(b.clone())); - Ok((LogicIpld::Var(a), new_bindings)) - } - } - - _ => { - let mut new_bindings = BTreeSet::new(); - new_bindings.insert(Variable(a.clone())); - Ok((logic_ipld, new_bindings)) - } - } + (None, None) => { + return true; } - (lhs, rhs @ LogicIpld::Var(_)) => rhs.unify(lhs, bindings), - - _ => Err(()), } } } -impl TryFrom for Ipld { - type Error = Variable; - - fn try_from(logic: LogicIpld) -> Result { - match logic { - LogicIpld::Null => Ok(Ipld::Null), - LogicIpld::Bool(b) => Ok(Ipld::Bool(b)), - LogicIpld::Float(f) => Ok(Ipld::Float(f)), - LogicIpld::Integer(i) => Ok(Ipld::Integer(i)), - LogicIpld::String(s) => Ok(Ipld::String(s)), - LogicIpld::Bytes(b) => Ok(Ipld::Bytes(b)), - LogicIpld::List(xs) => xs - .into_iter() - .try_fold(vec![], |mut acc, x| { - acc.push(Ipld::try_from(x)?); - Ok(acc) - }) - .map(Ipld::List), - LogicIpld::Map(btree) => btree - .into_iter() - .try_fold(BTreeMap::new(), |mut acc, (k, v)| { - acc.insert(k, Ipld::try_from(v)?); - Ok(acc) - }) - .map(Ipld::Map), - LogicIpld::Var(var_id) => Err(Variable(var_id)), - } - } -} +// #[derive(Debug, Clone, PartialEq, Serialize, Deserialize, EnumAsInner)] +// pub enum Stream { +// Every(BTreeMap), // "All or nothing" +// Some(BTreeMap), // FIXME disambiguate from Option::Some +// } +// +// impl Stream { +// pub fn remove(&mut self, key: usize) { +// match self { +// Stream::Every(xs) => { +// xs.remove(&key); +// } +// Stream::Some(xs) => { +// xs.remove(&key); +// } +// } +// } +// +// pub fn len(&self) -> usize { +// match self { +// Stream::Every(xs) => xs.len(), +// Stream::Some(xs) => xs.len(), +// } +// } +// +// pub fn iter(&self) -> impl Iterator { +// match self { +// Stream::Every(xs) => xs.iter(), +// Stream::Some(xs) => xs.iter(), +// } +// } +// +// pub fn to_btree(self) -> BTreeMap { +// match self { +// Stream::Every(xs) => xs, +// Stream::Some(xs) => xs, +// } +// } +// +// pub fn map(self, f: impl Fn(BTreeMap) -> BTreeMap) -> Stream { +// match self { +// Stream::Every(xs) => { +// let updated = f(xs); +// Stream::Every(updated) +// } +// Stream::Some(xs) => { +// let updated = f(xs); +// Stream::Some(updated) +// } +// } +// } +// +// pub fn is_empty(&self) -> bool { +// match self { +// Stream::Every(xs) => xs.is_empty(), +// Stream::Some(xs) => xs.is_empty(), +// } +// } +// } diff --git a/src/ipld/newtype.rs b/src/ipld/newtype.rs index 88132b2b..f6509b76 100644 --- a/src/ipld/newtype.rs +++ b/src/ipld/newtype.rs @@ -1,6 +1,7 @@ use libipld_core::ipld::Ipld; +use ordered_float::OrderedFloat; use serde::{Deserialize, Serialize}; -use std::path::PathBuf; +use std::{cmp::Ordering, path::PathBuf}; #[cfg(target_arch = "wasm32")] use wasm_bindgen::prelude::*; @@ -43,6 +44,65 @@ use super::cid; #[serde(transparent)] pub struct Newtype(pub Ipld); +// impl Eq for Newtype {} +// +// impl PartialOrd for Newtype { +// fn partial_cmp(&self, other: &Self) -> Option { +// match (&self.0, &other.0) { +// (Ipld::Null, Ipld::Null) => Some(Ordering::Equal), +// (Ipld::Null, _) => Some(Ordering::Less), +// +// // +// (Ipld::Bool(lhs), Ipld::Bool(rhs)) => lhs.partial_cmp(rhs), +// (Ipld::Bool(lhs), Ipld::Bool(rhs)) => lhs.partial_cmp(rhs), +// (Ipld::Bool(_), _) => Some(Ordering::Less), +// +// // +// (Ipld::Float(lhs_f), Ipld::Float(rhs_f)) => { +// OrderedFloat(*lhs_f).partial_cmp(&OrderedFloat(*rhs_f)) +// } +// (lhs @ Ipld::Float(_), rhs) => Some(Ordering::Less), +// +// // +// (Ipld::Integer(lhs), Ipld::Integer(rhs)) => lhs.partial_cmp(rhs), +// (Ipld::Integer(_), _) => Some(Ordering::Less), +// +// // +// (Ipld::String(lhs), Ipld::String(rhs)) => lhs.partial_cmp(rhs), +// (Ipld::String(_), _) => Some(Ordering::Less), +// +// // +// (Ipld::Bytes(lhs), Ipld::Bytes(rhs)) => lhs.partial_cmp(rhs), +// (Ipld::Bytes(_), _) => Some(Ordering::Less), +// +// // +// (Ipld::Link(lhs), Ipld::Link(rhs)) => lhs.partial_cmp(rhs), +// (Ipld::Link(_), _) => Some(Ordering::Less), +// +// // +// (Ipld::List(lhs), Ipld::List(rhs)) => { +// let result = lhs.iter().zip(rhs).try_fold((), |acc, (l, r)| { +// match Newtype(*l).partial_cmp(&Newtype(*r)) { +// Some(Ordering::Equal) => Ok(()), +// Some(other) => Err(other), +// None => Err(Ordering::Less), +// } +// }); +// +// match result { +// Ok(()) => Some(Ordering::Equal), +// Err(comp) => Some(comp), +// } +// } +// (Ipld::List(_), _) => Some(Ordering::Less), +// +// // +// (Ipld::Map(lhs), Ipld::Map(rhs)) => None, +// (Ipld::Map(_), _) => Some(Ordering::Less), +// } +// } +// } + impl From for Newtype { fn from(ipld: Ipld) -> Self { Self(ipld) diff --git a/src/ipld/number.rs b/src/ipld/number.rs index bfb5084c..a66f10d8 100644 --- a/src/ipld/number.rs +++ b/src/ipld/number.rs @@ -10,7 +10,7 @@ use serde_derive::{Deserialize, Serialize}; /// bounds checking in [`Condition`]s. /// /// [`Condition`]: crate::delegation::Condition -#[derive(Debug, Clone, PartialEq, PartialOrd, EnumAsInner, Serialize, Deserialize)] +#[derive(Debug, Clone, PartialEq, EnumAsInner, Serialize, Deserialize)] #[serde(untagged)] pub enum Number { /// Designate a floating point number @@ -20,6 +20,17 @@ pub enum Number { Integer(i128), } +impl PartialOrd for Number { + fn partial_cmp(&self, other: &Self) -> Option { + match (self, other) { + (Number::Float(a), Number::Float(b)) => a.partial_cmp(b), + (Number::Integer(a), Number::Integer(b)) => a.partial_cmp(b), + (Number::Float(a), Number::Integer(b)) => a.partial_cmp(&(*b as f64)), + (Number::Integer(a), Number::Float(b)) => (*a as f64).partial_cmp(b), + } + } +} + impl From for Ipld { fn from(number: Number) -> Self { number.into() From e00f68dee6aed5d502d40ca57628c85edc9466ef Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Sun, 3 Mar 2024 14:59:34 -0800 Subject: [PATCH 110/188] selector parser --- Cargo.toml | 1 + src/delegation/policy.rs | 1 + src/delegation/policy/ir.rs | 172 +++++------------------- src/delegation/policy/selector.rs | 84 ++++++++++++ src/delegation/policy/selector/error.rs | 11 ++ src/delegation/policy/selector/op.rs | 142 +++++++++++++++++++ src/ipld/newtype.rs | 3 +- 7 files changed, 274 insertions(+), 140 deletions(-) create mode 100644 src/delegation/policy/selector.rs create mode 100644 src/delegation/policy/selector/error.rs create mode 100644 src/delegation/policy/selector/op.rs diff --git a/Cargo.toml b/Cargo.toml index 54384153..a3b4fcd2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -53,6 +53,7 @@ base64 = "0.21" bs58 = "0.5" serde = { version = "1.0.188", features = ["derive"] } serde_derive = "1.0" +nom = "7.1" # Web Stack did_url = "0.1" diff --git a/src/delegation/policy.rs b/src/delegation/policy.rs index 30e6f251..525057de 100644 --- a/src/delegation/policy.rs +++ b/src/delegation/policy.rs @@ -1,3 +1,4 @@ //pub mod frontend; //pub mod interpreter; pub mod ir; +pub mod selector; diff --git a/src/delegation/policy/ir.rs b/src/delegation/policy/ir.rs index 3396d526..c520658b 100644 --- a/src/delegation/policy/ir.rs +++ b/src/delegation/policy/ir.rs @@ -1,9 +1,9 @@ //FIXME rename core +use super::selector::op::SelectorOp; use crate::ipld; -use enum_as_inner::EnumAsInner; use libipld_core::ipld::Ipld; use serde::{Deserialize, Serialize}; -use std::{collections::BTreeMap, fmt}; +use std::collections::BTreeMap; impl Predicate { pub fn run(self, data: &Ipld) -> Result { @@ -36,15 +36,6 @@ impl Predicate { } } -pub enum RunError { - IndexOutOfBounds, - KeyNotFound, - NotAList, - NotAMap, - NotACollection, - NotANumber(>::Error), -} - trait Resolve { fn resolve(self, ctx: &Ipld) -> Result; } @@ -67,15 +58,6 @@ impl Resolve for Collection { } } -// impl Resolve for Collection { -// fn resolve(self, ctx: Ipld) -> Result { -// match self { -// Collection::Array(xs) => Ok(Ipld::List(xs)), -// Collection::Map(xs) => Ok(Ipld::Map(xs)), -// } -// } -// } - // FIXME Normal form? impl Resolve for String { @@ -84,17 +66,6 @@ impl Resolve for String { } } -pub struct Text(String); - -// impl TryFrom for String { -// fn try_from(ipld: Ipld) -> Result>::Error> { -// match ipld { -// Ipld::String(s) => Ok(s), -// _ => Err(()), -// } -// } -// } - impl SelectorOr { fn resolve(self, ctx: &Ipld) -> Result { match self { @@ -105,7 +76,7 @@ impl SelectorOr { seen_ops.push(op); match op { - SelectorOp::This => Ok((ipld, seen_ops)), + // SelectorOp::This => Ok((ipld, seen_ops)), SelectorOp::Try(inner) => { let op: SelectorOp = *inner.clone(); let ipld: Ipld = SelectorOr::Get::(vec![op]) @@ -114,25 +85,41 @@ impl SelectorOr { Ok((ipld, seen_ops)) } - SelectorOp::Index(i) => { - let result = match ipld { - Ipld::List(xs) => xs - .get(*i) - .ok_or(SelectorError { + SelectorOp::ArrayIndex(i) => { + let result = { + match ipld { + Ipld::List(xs) => { + if i.abs() as usize > xs.len() { + return Err(SelectorError { + path: seen_ops + .iter() + .map(|op| (*op).clone()) + .collect(), + reason: SelectorErrorReason::IndexOutOfBounds, + }); + } + + xs.get((xs.len() as i32 + *i) as usize) + .ok_or(SelectorError { + path: seen_ops + .iter() + .map(|op| (*op).clone()) + .collect(), + reason: SelectorErrorReason::IndexOutOfBounds, + }) + .cloned() + } + // FIXME behaviour on maps? type error + _ => Err(SelectorError { path: seen_ops.iter().map(|op| (*op).clone()).collect(), - reason: SelectorErrorReason::IndexOutOfBounds, - }) - .cloned(), - // FIXME behaviour on maps? type error - _ => Err(SelectorError { - path: seen_ops.iter().map(|op| (*op).clone()).collect(), - reason: SelectorErrorReason::NotAList, - }), + reason: SelectorErrorReason::NotAList, + }), + } }; Ok((result?, seen_ops)) } - SelectorOp::Key(k) => { + SelectorOp::Field(k) => { let result = match ipld { Ipld::Map(xs) => xs .get(k) @@ -240,7 +227,7 @@ pub enum SelectorErrorReason { // FIXME rename constraint or validation or expression or something? #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] pub enum Predicate { - // Booleans for connectives? FIXME + // Booleans True, False, @@ -286,37 +273,6 @@ impl Collection { } } -#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] -pub enum SelectorOp { - This, // . - Index(usize), // [2] - Key(String), // ["key"] (or .key) - Values, // .[] - Try(Box), // ? -} - -impl fmt::Display for SelectorOp { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - SelectorOp::This => write!(f, "."), - SelectorOp::Index(i) => write!(f, "[{}]", i), - SelectorOp::Key(k) => { - if let Some(first) = k.chars().next() { - if first.is_alphabetic() && k.chars().all(char::is_alphanumeric) { - write!(f, ".{}", k) - } else { - write!(f, "[\"{}\"]", k) - } - } else { - write!(f, "[\"{}\"]", k) - } - } - SelectorOp::Values => write!(f, "[]"), - SelectorOp::Try(inner) => write!(f, "{}?", inner), - } - } -} - #[derive(Debug, Clone, PartialEq)] pub struct Selector(pub Vec); @@ -359,63 +315,3 @@ pub fn glob(input: &String, pattern: &String) -> bool { } } } - -// #[derive(Debug, Clone, PartialEq, Serialize, Deserialize, EnumAsInner)] -// pub enum Stream { -// Every(BTreeMap), // "All or nothing" -// Some(BTreeMap), // FIXME disambiguate from Option::Some -// } -// -// impl Stream { -// pub fn remove(&mut self, key: usize) { -// match self { -// Stream::Every(xs) => { -// xs.remove(&key); -// } -// Stream::Some(xs) => { -// xs.remove(&key); -// } -// } -// } -// -// pub fn len(&self) -> usize { -// match self { -// Stream::Every(xs) => xs.len(), -// Stream::Some(xs) => xs.len(), -// } -// } -// -// pub fn iter(&self) -> impl Iterator { -// match self { -// Stream::Every(xs) => xs.iter(), -// Stream::Some(xs) => xs.iter(), -// } -// } -// -// pub fn to_btree(self) -> BTreeMap { -// match self { -// Stream::Every(xs) => xs, -// Stream::Some(xs) => xs, -// } -// } -// -// pub fn map(self, f: impl Fn(BTreeMap) -> BTreeMap) -> Stream { -// match self { -// Stream::Every(xs) => { -// let updated = f(xs); -// Stream::Every(updated) -// } -// Stream::Some(xs) => { -// let updated = f(xs); -// Stream::Some(updated) -// } -// } -// } -// -// pub fn is_empty(&self) -> bool { -// match self { -// Stream::Every(xs) => xs.is_empty(), -// Stream::Some(xs) => xs.is_empty(), -// } -// } -// } diff --git a/src/delegation/policy/selector.rs b/src/delegation/policy/selector.rs new file mode 100644 index 00000000..20b1966e --- /dev/null +++ b/src/delegation/policy/selector.rs @@ -0,0 +1,84 @@ +pub mod error; +pub mod op; + +use error::ParseError; +use nom::{ + self, + branch::alt, + bytes::complete::tag, + character::complete::char, + combinator::map_res, + error::context, + multi::{many0, many1}, + sequence::preceded, + IResult, +}; +use serde::{Deserialize, Deserializer, Serialize, Serializer}; +use std::{fmt, str::FromStr}; + +#[derive(Debug, Clone, PartialEq)] +pub struct Selector(Vec); + +pub fn parse(input: &str) -> IResult<&str, Selector> { + let without_this = many1(op::parse); + let with_this = preceded(char('.'), many0(op::parse)); + + // NOTE: must try without this first, to disambiguate `.field` from `.` + let p = map_res(alt((without_this, with_this)), |found| { + Ok::(Selector(found)) + }); + + context("selector", p)(input) +} + +pub fn parse_this(input: &str) -> IResult<&str, Selector> { + let p = map_res(tag("."), |_| Ok::(Selector(vec![]))); + context("this", p)(input) +} + +pub fn parse_selector_ops(input: &str) -> IResult<&str, Vec> { + let p = many1(op::parse); + context("selector ops", p)(input) +} + +impl fmt::Display for Selector { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let mut ops = self.0.iter(); + + if let Some(field) = ops.next() { + field.fmt(f)?; + } else { + write!(f, ".")?; + } + + for op in ops { + op.fmt(f)?; + } + + Ok(()) + } +} + +impl FromStr for Selector { + type Err = nom::Err; + + fn from_str(s: &str) -> Result { + match parse(s).map_err(|e| nom::Err::Failure(ParseError::UnknownPattern(e.to_string())))? { + ("", selector) => Ok(selector), + (rest, _) => Err(nom::Err::Failure(ParseError::TrailingInput(rest.into()))), + } + } +} + +impl Serialize for Selector { + fn serialize(&self, serializer: S) -> Result { + self.to_string().serialize(serializer) + } +} + +impl<'de> Deserialize<'de> for Selector { + fn deserialize>(deserializer: D) -> Result { + let s = String::deserialize(deserializer)?; + Selector::from_str(&s).map_err(serde::de::Error::custom) + } +} diff --git a/src/delegation/policy/selector/error.rs b/src/delegation/policy/selector/error.rs new file mode 100644 index 00000000..b4e583ac --- /dev/null +++ b/src/delegation/policy/selector/error.rs @@ -0,0 +1,11 @@ +use serde::{Deserialize, Serialize}; +use thiserror::Error; + +#[derive(Debug, Error, PartialEq, Serialize, Deserialize)] +pub enum ParseError { + #[error("unmatched trailing input")] + TrailingInput(String), + + #[error("unknown pattern: {0}")] + UnknownPattern(String), +} diff --git a/src/delegation/policy/selector/op.rs b/src/delegation/policy/selector/op.rs new file mode 100644 index 00000000..26ca0499 --- /dev/null +++ b/src/delegation/policy/selector/op.rs @@ -0,0 +1,142 @@ +use super::error::ParseError; +use enum_as_inner::EnumAsInner; +use nom::{ + self, + branch::alt, + bytes::complete::tag, + character::complete::{alphanumeric1, anychar, char, digit1}, + combinator::{map_opt, map_res}, + error::context, + multi::many1, + sequence::{delimited, preceded, terminated}, + IResult, +}; +use serde::{Deserialize, Deserializer, Serialize, Serializer}; +use std::{fmt, str::FromStr}; + +#[derive(Debug, Clone, PartialEq, EnumAsInner)] +pub enum SelectorOp { + // FIXME remove #[default] + // This, // . + ArrayIndex(i32), // [2] + Field(String), // ["key"] (or .key) + Values, // .[] + Try(Box), // ? +} + +impl fmt::Display for SelectorOp { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + // SelectorOp::This => write!(f, "."), + SelectorOp::ArrayIndex(i) => write!(f, "[{}]", i), + SelectorOp::Field(k) => { + if let Some(first) = k.chars().next() { + if first.is_alphabetic() && k.chars().all(char::is_alphanumeric) { + write!(f, ".{}", k) + } else { + write!(f, "[\"{}\"]", k) + } + } else { + write!(f, "[\"{}\"]", k) + } + } + SelectorOp::Values => write!(f, "[]"), + SelectorOp::Try(inner) => write!(f, "{}?", inner), + } + } +} + +pub fn parse(input: &str) -> IResult<&str, SelectorOp> { + let p = alt((parse_try, parse_non_try)); + context("selector_op", p)(input) +} + +pub fn parse_non_try(input: &str) -> IResult<&str, SelectorOp> { + let p = alt((parse_values, parse_array_index, parse_field)); + context("non_try", p)(input) +} + +pub fn parse_try(input: &str) -> IResult<&str, SelectorOp> { + let p = map_res(terminated(parse_non_try, tag("?")), |found: SelectorOp| { + Ok::(SelectorOp::Try(Box::new(found))) + }); + + context("try", p)(input) +} + +pub fn parse_array_index(input: &str) -> IResult<&str, SelectorOp> { + let num = map_opt(tag("-"), |s: &str| { + let (_rest, matched) = digit1::<&str, ()>(s).ok()?; + matched.parse::().ok() + }); + + let array_index = map_res(delimited(char('['), num, char(']')), |idx| { + Ok::(SelectorOp::ArrayIndex(idx)) + }); + + context("array_index", array_index)(input) +} + +pub fn parse_values(input: &str) -> IResult<&str, SelectorOp> { + context("values", tag("[]"))(input).map(|(rest, _)| (rest, SelectorOp::Values)) +} + +pub fn parse_field(input: &str) -> IResult<&str, SelectorOp> { + let p = alt((parse_delim_field, parse_dot_field)); + + context("map_field", p)(input) +} + +pub fn parse_dot_field(input: &str) -> IResult<&str, SelectorOp> { + let p = alt((parse_dot_alpha_field, parse_dot_underscore_field)); + context("dot_field", p)(input) +} + +pub fn parse_dot_alpha_field(input: &str) -> IResult<&str, SelectorOp> { + let p = map_res(preceded(char('.'), alphanumeric1), |found: &str| { + Ok::(SelectorOp::Field(found.to_string())) + }); + + context("dot_field", p)(input) +} + +pub fn parse_dot_underscore_field(input: &str) -> IResult<&str, SelectorOp> { + let p = map_res(preceded(tag("._"), alphanumeric1), |found: &str| { + let key = format!("{}{}", '_', found); + Ok::(SelectorOp::Field(key)) + }); + + context("dot_field", p)(input) +} + +pub fn parse_delim_field(input: &str) -> IResult<&str, SelectorOp> { + let p = map_res(delimited(tag("[\""), many1(anychar), tag("\"]")), |found| { + let field = String::from_iter(found); + Ok::(SelectorOp::Field(field)) + }); + + context("delimited_field", p)(input) +} + +impl FromStr for SelectorOp { + type Err = nom::Err; + + fn from_str(s: &str) -> Result { + match parse(s).map_err(|e| nom::Err::Failure(ParseError::UnknownPattern(e.to_string())))? { + ("", found) => Ok(found), + (rest, _) => Err(nom::Err::Failure(ParseError::TrailingInput(rest.into()))), + } + } +} +impl Serialize for SelectorOp { + fn serialize(&self, serializer: S) -> Result { + self.to_string().serialize(serializer) + } +} + +impl<'de> Deserialize<'de> for SelectorOp { + fn deserialize>(deserializer: D) -> Result { + let s = String::deserialize(deserializer)?; + SelectorOp::from_str(&s).map_err(|e| serde::de::Error::custom(e.to_string())) + } +} diff --git a/src/ipld/newtype.rs b/src/ipld/newtype.rs index f6509b76..f202599b 100644 --- a/src/ipld/newtype.rs +++ b/src/ipld/newtype.rs @@ -1,7 +1,6 @@ use libipld_core::ipld::Ipld; -use ordered_float::OrderedFloat; use serde::{Deserialize, Serialize}; -use std::{cmp::Ordering, path::PathBuf}; +use std::path::PathBuf; #[cfg(target_arch = "wasm32")] use wasm_bindgen::prelude::*; From 3143c397e7dd857aecae8f1ba0595bda8b56654f Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Sun, 3 Mar 2024 22:52:28 -0800 Subject: [PATCH 111/188] Swap all files to predicates from condition --- src/ability/arguments/named.rs | 6 - src/ability/pipe.rs | 38 +- src/ability/ucan/pipe.rs | 11 - src/delegation.rs | 60 ++- src/delegation/agent.rs | 23 +- src/delegation/condition.rs | 78 ---- src/delegation/condition/contains_all.rs | 46 -- src/delegation/condition/contains_any.rs | 45 -- src/delegation/condition/contains_key.rs | 69 --- src/delegation/condition/excludes_all.rs | 85 ---- src/delegation/condition/excludes_key.rs | 72 ---- src/delegation/condition/matches_regex.rs | 76 ---- src/delegation/condition/max_length.rs | 44 -- src/delegation/condition/max_number.rs | 49 --- src/delegation/condition/min_length.rs | 44 -- src/delegation/condition/min_number.rs | 49 --- src/delegation/condition/traits.rs | 11 - src/delegation/payload.rs | 34 +- src/delegation/policy.rs | 4 +- src/delegation/policy/collection.rs | 36 ++ src/delegation/policy/frontend.rs | 62 --- src/delegation/policy/interpreter.rs | 485 ---------------------- src/delegation/policy/ir.rs | 281 ++----------- src/delegation/policy/predicate.rs | 144 +++++++ src/delegation/policy/selector.rs | 25 +- src/delegation/policy/selector/error.rs | 24 ++ src/delegation/policy/selector/op.rs | 22 +- src/delegation/policy/selector/or.rs | 113 +++++ src/delegation/store/memory.rs | 24 +- src/delegation/store/traits.rs | 25 +- src/invocation/agent.rs | 26 +- src/invocation/payload.rs | 34 +- src/ipld/number.rs | 20 +- src/lib.rs | 4 - src/proof.rs | 12 - src/proof/checkable.rs | 17 - src/proof/error.rs | 45 -- src/proof/internal.rs | 3 - src/proof/parentful.rs | 189 --------- src/proof/parentless.rs | 108 ----- src/proof/parents.rs | 22 - src/proof/prove.rs | 37 -- src/proof/same.rs | 68 --- src/receipt/payload.rs | 2 +- 44 files changed, 521 insertions(+), 2151 deletions(-) delete mode 100644 src/ability/ucan/pipe.rs delete mode 100644 src/delegation/condition.rs delete mode 100644 src/delegation/condition/contains_all.rs delete mode 100644 src/delegation/condition/contains_any.rs delete mode 100644 src/delegation/condition/contains_key.rs delete mode 100644 src/delegation/condition/excludes_all.rs delete mode 100644 src/delegation/condition/excludes_key.rs delete mode 100644 src/delegation/condition/matches_regex.rs delete mode 100644 src/delegation/condition/max_length.rs delete mode 100644 src/delegation/condition/max_number.rs delete mode 100644 src/delegation/condition/min_length.rs delete mode 100644 src/delegation/condition/min_number.rs delete mode 100644 src/delegation/condition/traits.rs create mode 100644 src/delegation/policy/collection.rs delete mode 100644 src/delegation/policy/frontend.rs delete mode 100644 src/delegation/policy/interpreter.rs create mode 100644 src/delegation/policy/predicate.rs create mode 100644 src/delegation/policy/selector/or.rs delete mode 100644 src/proof.rs delete mode 100644 src/proof/checkable.rs delete mode 100644 src/proof/error.rs delete mode 100644 src/proof/internal.rs delete mode 100644 src/proof/parentful.rs delete mode 100644 src/proof/parentless.rs delete mode 100644 src/proof/parents.rs delete mode 100644 src/proof/prove.rs delete mode 100644 src/proof/same.rs diff --git a/src/ability/arguments/named.rs b/src/ability/arguments/named.rs index 0120da7b..7b5baa5f 100644 --- a/src/ability/arguments/named.rs +++ b/src/ability/arguments/named.rs @@ -150,12 +150,6 @@ impl> TryFrom for Named { } } -// impl From> for Named { -// fn from(map: BTreeMap) -> Self { -// Named(map) -// } -// } - impl> From> for Ipld { fn from(arguments: Named) -> Self { Ipld::Map( diff --git a/src/ability/pipe.rs b/src/ability/pipe.rs index 32ab4744..55a80555 100644 --- a/src/ability/pipe.rs +++ b/src/ability/pipe.rs @@ -1,38 +1,22 @@ -use crate::{crypto::varsig, delegation, delegation::condition::Condition, did::Did, ipld}; +use crate::{crypto::varsig, delegation, did::Did, ipld}; use libipld_core::{codec::Codec, ipld::Ipld}; -pub struct Pipe< - C: Condition, - DID: Did, - V: varsig::Header, - Enc: Codec + TryFrom + Into, -> { - pub source: Cap, - pub sink: Cap, +pub struct Pipe, Enc: Codec + TryFrom + Into> { + pub source: Cap, + pub sink: Cap, } -pub enum Cap, Enc: Codec + TryFrom + Into> -{ - Chain(delegation::Chain), +pub enum Cap, Enc: Codec + TryFrom + Into> { + Chain(delegation::Chain), Literal(Ipld), } -pub struct PromisedPipe< - C: Condition, - DID: Did, - V: varsig::Header, - Enc: Codec + TryFrom + Into, -> { - pub source: PromisedCap, - pub sink: PromisedCap, +pub struct PromisedPipe, Enc: Codec + TryFrom + Into> { + pub source: PromisedCap, + pub sink: PromisedCap, } -pub enum PromisedCap< - C: Condition, - DID: Did, - V: varsig::Header, - Enc: Codec + TryFrom + Into, -> { - Chain(delegation::Chain), +pub enum PromisedCap, Enc: Codec + TryFrom + Into> { + Chain(delegation::Chain), Promised(ipld::Promised), } diff --git a/src/ability/ucan/pipe.rs b/src/ability/ucan/pipe.rs deleted file mode 100644 index 2d79fcf8..00000000 --- a/src/ability/ucan/pipe.rs +++ /dev/null @@ -1,11 +0,0 @@ - use crate::delegation; - -pub struct Pipe< - C: Condition, - DID: Did, - V: varsig::Header, - Enc: Codec + TryFrom + Into, - > { - pub from: delegation::Chain, - pub to: delegation::Chain, -} diff --git a/src/delegation.rs b/src/delegation.rs index bd0d975f..852b61b9 100644 --- a/src/delegation.rs +++ b/src/delegation.rs @@ -1,18 +1,17 @@ -//! An [`Delegation`] is the way to grant someone else the use of [`Ability`][crate::ability]. +//! A [`Delegation`] is the way to grant someone else the use of [`Ability`][crate::ability]. //! //! ## Data //! //! - [`Delegation`] is the top-level, signed data struture. //! - [`Payload`] is the fields unique to an invocation. //! - [`Preset`] is an [`Delegation`] preloaded with this library's [preset abilities](crate::ability::preset::Ready). -//! - [`Condition`]s are syntactically-driven validation rules for [`Delegation`]s. +//! - [`Predicate`]s are syntactically-driven validation rules for [`Delegation`]s. //! //! ## Stateful Helpers //! //! - [`Agent`] is a high-level interface for sessions that will involve more than one invoctaion. //! - [`store`] is an interface for caching [`Delegation`]s. -pub mod condition; pub mod policy; pub mod store; @@ -22,19 +21,18 @@ mod payload; pub use agent::Agent; pub use payload::*; -use crate::capsule::Capsule; use crate::{ - // ability, + capsule::Capsule, crypto::{signature, varsig, Nonce}, did::{self, Did}, time::{TimeBoundError, Timestamp}, }; -use condition::Condition; use libipld_core::{ cid::Cid, codec::{Codec, Encode}, ipld::Ipld, }; +use policy::predicate::Predicate; use std::collections::BTreeMap; use web_time::SystemTime; @@ -45,39 +43,29 @@ use web_time::SystemTime; /// # Examples /// FIXME #[derive(Clone, Debug, PartialEq)] -pub struct Delegation< - C: Condition, - DID: Did, - V: varsig::Header, - Enc: Codec + TryFrom + Into, ->(pub signature::Envelope, DID, V, Enc>); +pub struct Delegation, Enc: Codec + TryFrom + Into>( + pub signature::Envelope, DID, V, Enc>, +); #[derive(Clone, Debug, PartialEq)] -pub struct Chain< - C: Condition, - DID: Did, - V: varsig::Header, - Enc: Codec + TryFrom + Into, ->(Vec>); - -impl, Enc: Codec + TryFrom + Into> Capsule - for Chain +pub struct Chain, Enc: Codec + TryFrom + Into>( + Vec>, +); + +impl, Enc: Codec + TryFrom + Into> Capsule + for Chain { const TAG: &'static str = "ucan/chain"; } /// A variant of [`Delegation`] that has the abilties and DIDs from this library pre-filled. -pub type Preset = Delegation< - condition::Preset, - did::preset::Verifier, - varsig::header::Preset, - varsig::encoding::Preset, ->; +pub type Preset = + Delegation; // FIXME checkable -> provable? -impl, Enc: Codec + Into + TryFrom> - Delegation +impl, Enc: Codec + Into + TryFrom> + Delegation { /// Retrive the `issuer` of a [`Delegation`] pub fn issuer(&self) -> &DID { @@ -94,8 +82,8 @@ impl, Enc: Codec + Into + Tr &self.0.payload.audience } - /// Retrive the `condition` of a [`Delegation`] - pub fn policy(&self) -> &[C] { + /// Retrive the `policy` of a [`Delegation`] + pub fn policy(&self) -> &Vec { &self.0.payload.policy } @@ -123,7 +111,7 @@ impl, Enc: Codec + Into + Tr self.0.payload.check_time(now) } - pub fn payload(&self) -> &Payload { + pub fn payload(&self) -> &Payload { &self.0.payload } @@ -148,7 +136,7 @@ impl, Enc: Codec + Into + Tr pub fn cid(&self) -> Result where - signature::Envelope, DID, V, Enc>: Clone + Encode, + signature::Envelope, DID, V, Enc>: Clone + Encode, Ipld: Encode, { self.0.cid() @@ -156,7 +144,7 @@ impl, Enc: Codec + Into + Tr pub fn validate_signature(&self) -> Result<(), signature::ValidateError> where - Payload: Clone, + Payload: Clone, Ipld: Encode, { self.0.validate_signature() @@ -165,11 +153,11 @@ impl, Enc: Codec + Into + Tr pub fn try_sign( signer: &DID::Signer, varsig_header: V, - payload: Payload, + payload: Payload, ) -> Result where Ipld: Encode, - Payload: Clone, + Payload: Clone, { signature::Envelope::try_sign(signer, varsig_header, payload).map(Delegation) } diff --git a/src/delegation/agent.rs b/src/delegation/agent.rs index 971a6e97..1e399e43 100644 --- a/src/delegation/agent.rs +++ b/src/delegation/agent.rs @@ -1,8 +1,7 @@ -use super::{condition::Condition, payload::Payload, store::Store, Delegation}; +use super::{payload::Payload, policy::predicate::Predicate, store::Store, Delegation}; use crate::{ crypto::{varsig, Nonce}, did::Did, - // proof::checkable::Checkable, time::Timestamp, }; use libipld_core::{ @@ -20,9 +19,8 @@ use web_time::SystemTime; #[derive(Debug)] pub struct Agent< 'a, - C: Condition, DID: Did, - S: Store, + S: Store, V: varsig::Header, Enc: Codec + TryFrom + Into, > { @@ -33,17 +31,16 @@ pub struct Agent< pub store: &'a mut S, signer: &'a ::Signer, - _marker: PhantomData<(C, V, Enc)>, + _marker: PhantomData<(V, Enc)>, } impl< 'a, - C: Condition + Clone, DID: Did + ToString + Clone, - S: Store + Clone, + S: Store + Clone, V: varsig::Header, Enc: Codec + TryFrom + Into, - > Agent<'a, C, DID, S, V, Enc> + > Agent<'a, DID, S, V, Enc> where Ipld: Encode, { @@ -61,19 +58,19 @@ where audience: DID, subject: Option, command: String, - new_policy: Vec, + new_policy: Vec, metadata: BTreeMap, expiration: Timestamp, not_before: Option, now: SystemTime, varsig_header: V, - ) -> Result, DelegateError> { + ) -> Result, DelegateError> { let mut salt = self.did.clone().to_string().into_bytes(); let nonce = Nonce::generate_12(&mut salt); if let Some(ref sub) = subject { if sub == self.did { - let payload: Payload = Payload { + let payload: Payload = Payload { issuer: self.did.clone(), audience, subject, @@ -103,7 +100,7 @@ where let mut policy = to_delegate.policy.clone(); policy.append(&mut new_policy.clone()); - let payload: Payload = Payload { + let payload: Payload = Payload { issuer: self.did.clone(), audience, subject, @@ -121,7 +118,7 @@ where pub fn receive( &mut self, cid: Cid, // FIXME remove and generate from the capsule header? - delegation: Delegation, + delegation: Delegation, ) -> Result<(), ReceiveError> { if self.store.get(&cid).is_ok() { return Ok(()); diff --git a/src/delegation/condition.rs b/src/delegation/condition.rs deleted file mode 100644 index 207a181e..00000000 --- a/src/delegation/condition.rs +++ /dev/null @@ -1,78 +0,0 @@ -//! Policies for syntactic validation of abilities in [`Delegation`][super::Delegation]s. - -mod contains_all; -mod contains_any; -mod contains_key; -mod excludes_all; -mod excludes_key; -mod matches_regex; -mod max_length; -mod max_number; -mod min_length; -mod min_number; -mod traits; - -pub use contains_all::ContainsAll; -pub use contains_any::ContainsAny; -pub use contains_key::ContainsKey; -pub use excludes_all::ExcludesAll; -pub use excludes_key::ExcludesKey; -pub use matches_regex::MatchesRegex; -pub use max_length::MaxLength; -pub use max_number::MaxNumber; -pub use min_length::MinLength; -pub use min_number::MinNumber; -pub use traits::Condition; - -use crate::ability::arguments; -use libipld_core::{error::SerdeError, ipld::Ipld, serde as ipld_serde}; -use serde_derive::{Deserialize, Serialize}; -use std::collections::BTreeMap; - -/// The union of the common [`Condition`]s that ship directly with this library. -#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] -#[serde(untagged)] -#[allow(missing_docs)] -pub enum Preset { - ContainsAll(contains_all::ContainsAll), - ContainsAny(contains_any::ContainsAny), - ContainsKey(contains_key::ContainsKey), - ExcludesKey(excludes_key::ExcludesKey), - ExcludesAll(excludes_all::ExcludesAll), - MinLength(min_length::MinLength), - MaxLength(max_length::MaxLength), - MinNumber(min_number::MinNumber), - MaxNumber(max_number::MaxNumber), - MatchesRegex(matches_regex::MatchesRegex), -} - -impl From for Ipld { - fn from(common: Preset) -> Self { - common.into() - } -} - -impl TryFrom for Preset { - type Error = SerdeError; - - fn try_from(ipld: Ipld) -> Result { - ipld_serde::from_ipld(ipld) - } -} - -impl Condition for Preset { - fn validate(&self, args: &arguments::Named) -> bool { - match self { - Preset::ContainsAll(c) => c.validate(args), - Preset::ContainsAny(c) => c.validate(args), - Preset::ContainsKey(c) => c.validate(args), - Preset::ExcludesKey(c) => c.validate(args), - Preset::ExcludesAll(c) => c.validate(args), - Preset::MinLength(c) => c.validate(args), - Preset::MaxLength(c) => c.validate(args), - Preset::MinNumber(c) => c.validate(args), - Preset::MaxNumber(c) => c.validate(args), - Preset::MatchesRegex(c) => c.validate(args), - } - } -} diff --git a/src/delegation/condition/contains_all.rs b/src/delegation/condition/contains_all.rs deleted file mode 100644 index d0882e64..00000000 --- a/src/delegation/condition/contains_all.rs +++ /dev/null @@ -1,46 +0,0 @@ -//! A [`Condition`] for ensuring a field contains all of a set of values. - -use super::traits::Condition; -use crate::ability::arguments; -use libipld_core::{error::SerdeError, ipld::Ipld, serde as ipld_serde}; -use serde_derive::{Deserialize, Serialize}; - -/// A condition for ensuring a field contains all of a set of values. -/// -/// This works on lists and maps. Maps will check the values, not keys. -#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] -#[serde(deny_unknown_fields)] -pub struct ContainsAll { - /// Name of the field to check - pub field: String, - - /// The elements that must be present - pub contains_all: Vec, -} - -impl From for Ipld { - fn from(contains_all: ContainsAll) -> Self { - contains_all.into() - } -} - -impl TryFrom for ContainsAll { - type Error = SerdeError; - - fn try_from(ipld: Ipld) -> Result { - ipld_serde::from_ipld(ipld) - } -} - -impl Condition for ContainsAll { - fn validate(&self, args: &arguments::Named) -> bool { - match args.get(&self.field) { - Some(Ipld::List(array)) => self.contains_all.iter().all(|ipld| array.contains(ipld)), - Some(Ipld::Map(btree)) => { - let vals: Vec<&Ipld> = btree.values().collect(); - self.contains_all.iter().all(|ipld| vals.contains(&ipld)) - } - _ => false, - } - } -} diff --git a/src/delegation/condition/contains_any.rs b/src/delegation/condition/contains_any.rs deleted file mode 100644 index 46af682f..00000000 --- a/src/delegation/condition/contains_any.rs +++ /dev/null @@ -1,45 +0,0 @@ -//! A [`Condition`] for ensuring a field contains some of a set of values. -use super::traits::Condition; -use crate::ability::arguments; -use libipld_core::{error::SerdeError, ipld::Ipld, serde as ipld_serde}; -use serde_derive::{Deserialize, Serialize}; - -/// A [`Condition`] for ensuring a field contains one or more of a set of values. -/// -/// This works on lists and maps. Maps will check the values, not keys. -#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] -#[serde(deny_unknown_fields)] -pub struct ContainsAny { - /// Name of the field to check. - pub field: String, - - /// The elements that must be present. - pub contains_any: Vec, -} - -impl From for Ipld { - fn from(contains_any: ContainsAny) -> Self { - contains_any.into() - } -} - -impl TryFrom for ContainsAny { - type Error = SerdeError; - - fn try_from(ipld: Ipld) -> Result { - ipld_serde::from_ipld(ipld) - } -} - -impl Condition for ContainsAny { - fn validate(&self, args: &arguments::Named) -> bool { - match args.get(&self.field) { - Some(Ipld::List(array)) => array.iter().any(|ipld| self.contains_any.contains(ipld)), - Some(Ipld::Map(btree)) => { - let vals: Vec<&Ipld> = btree.values().collect(); - self.contains_any.iter().any(|ipld| vals.contains(&ipld)) - } - _ => false, - } - } -} diff --git a/src/delegation/condition/contains_key.rs b/src/delegation/condition/contains_key.rs deleted file mode 100644 index df6844df..00000000 --- a/src/delegation/condition/contains_key.rs +++ /dev/null @@ -1,69 +0,0 @@ -//! A [`Condition`] for ensuring a map contains a key. -use super::traits::Condition; -use crate::ability::arguments; -use libipld_core::{error::SerdeError, ipld::Ipld, serde as ipld_serde}; -use serde_derive::{Deserialize, Serialize}; - -/// A [`Condition`] for ensuring a map contains a key. -/// -/// Note that this operates on a key inside the args, not the args themselves. -/// The shape of an [`ability`][crate::ability] is pretermined, so further -/// constraining the top-level argument keys is not necessary. -/// -/// # Examples -/// -/// ```rust -/// # use ucan::delegation::{condition::{ContainsKey, Condition}}; -/// # use libipld::ipld; -/// # -/// let args = ipld!({"a": {"b": 1, "c": 2}, "d": {"e": 3}}).try_into().unwrap(); -/// let cond = ContainsKey{ -/// field: "a".into(), -/// key: "b".into() -/// }; -/// -/// assert!(cond.validate(&args)); -/// -/// // Fails when the key is not present -/// assert!(!ContainsKey { -/// field: "nope".into(), -/// key: "b".into() -/// }.validate(&args)); -/// -/// // Also fails when the input is not a map -/// let list = ipld!({"a": [1, 2, 3]}).try_into().unwrap(); -/// assert!(!cond.validate(&list)); -/// assert!(!cond.validate(&ipld!({"a": 42}).try_into().unwrap())); -/// ``` -#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] -#[serde(deny_unknown_fields)] -pub struct ContainsKey { - /// Name of the field to check. - pub field: String, - - /// The elements that must be present. - pub key: String, -} - -impl From for Ipld { - fn from(contains_key: ContainsKey) -> Self { - contains_key.into() - } -} - -impl TryFrom for ContainsKey { - type Error = SerdeError; - - fn try_from(ipld: Ipld) -> Result { - ipld_serde::from_ipld(ipld) - } -} - -impl Condition for ContainsKey { - fn validate(&self, args: &arguments::Named) -> bool { - match args.get(&self.field) { - Some(Ipld::Map(map)) => map.contains_key(&self.key), - _ => false, - } - } -} diff --git a/src/delegation/condition/excludes_all.rs b/src/delegation/condition/excludes_all.rs deleted file mode 100644 index fff743e4..00000000 --- a/src/delegation/condition/excludes_all.rs +++ /dev/null @@ -1,85 +0,0 @@ -//! A [`Condition`] for ensuring a field contains none of a set of values. -use super::traits::Condition; -use crate::ability::arguments; -use libipld_core::{error::SerdeError, ipld::Ipld, serde as ipld_serde}; -use serde_derive::{Deserialize, Serialize}; - -/// A [`Condition`] for ensuring a field contains none of a set of values. -/// -/// This works on all [`Ipld`] types. For lists and maps, it checks the values, not keys. -/// For the rest, it checks the value itself. -/// -/// # Examples -/// -/// ```rust -/// # use ucan::delegation::{condition::{ExcludesAll, Condition}}; -/// # use libipld::ipld; -/// # -/// let args = ipld!({"a": [1, "b", 3.14], "b": 4}).try_into().unwrap(); -/// let cond = ExcludesAll { -/// field: "a".into(), -/// excludes_all: vec![ipld!(2), ipld!("a")] -/// }; -/// -/// assert!(cond.validate(&args)); -/// -/// // Fails when the values of a map match -/// assert!(!cond.validate(&ipld!({"a": {"b": 2}}).try_into().unwrap())); -/// -/// // Succeeds when the key is not present -/// assert!(ExcludesAll { -/// field: "nope".into(), -/// excludes_all: vec![ipld!(1), ipld!("b")] -/// }.validate(&args)); -/// -/// // Also checks non-maps/non-lists -/// assert!(!cond.validate(&ipld!({"a": 2}).try_into().unwrap())); -/// assert!(cond.validate(&ipld!({"a": "hello world"}).try_into().unwrap())); -/// ``` -#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] -#[serde(deny_unknown_fields)] -pub struct ExcludesAll { - /// Name of the field to check. - pub field: String, - - /// The elements that must not be present. - pub excludes_all: Vec, -} - -impl From for Ipld { - fn from(excludes_all: ExcludesAll) -> Self { - excludes_all.into() - } -} - -impl TryFrom for ExcludesAll { - type Error = SerdeError; - - fn try_from(ipld: Ipld) -> Result { - ipld_serde::from_ipld(ipld) - } -} - -impl Condition for ExcludesAll { - fn validate(&self, args: &arguments::Named) -> bool { - if let Some(ipld) = args.get(&self.field) { - let mut it = self.excludes_all.iter(); - match ipld { - Ipld::Null => it.all(|x| x != ipld), - Ipld::Bool(_) => it.all(|x| x != ipld), - Ipld::Float(_) => it.all(|x| x != ipld), - Ipld::Integer(_) => it.all(|x| x != ipld), - Ipld::Bytes(_) => it.all(|x| x != ipld), - Ipld::String(_) => it.all(|x| x != ipld), - Ipld::Link(_) => it.all(|x| x != ipld), - Ipld::List(array) => it.all(|x| !array.contains(x)), - Ipld::Map(btree) => { - let vals: Vec<&Ipld> = btree.values().collect(); - it.all(|x| !vals.contains(&x)) - } - } - } else { - true - } - } -} diff --git a/src/delegation/condition/excludes_key.rs b/src/delegation/condition/excludes_key.rs deleted file mode 100644 index 09ec8523..00000000 --- a/src/delegation/condition/excludes_key.rs +++ /dev/null @@ -1,72 +0,0 @@ -//! A [`Condition`] for ensuring a field contains none of a set of keys. -use super::traits::Condition; -use crate::ability::arguments; -use libipld_core::{error::SerdeError, ipld::Ipld, serde as ipld_serde}; -use serde_derive::{Deserialize, Serialize}; - -/// A [`Condition`] for ensuring a map excludes a key. -/// -/// Note that this operates on a key inside the args, not the args themselves. -/// The shape of an [`ability`][crate::ability] is pretermined, so further -/// constraining the top-level argument keys is not necessary. -/// -/// # Examples -/// -/// ```rust -/// # use ucan::delegation::{condition::{ExcludesKey, Condition}}; -/// # use libipld::ipld; -/// # -/// let cond = ExcludesKey{ -/// field: "a".into(), -/// key: "b".into() -/// }; -/// -/// let args1 = ipld!({"a": "b", "c": "d"}).try_into().unwrap(); -/// let args2 = ipld!({"a": {"b": 1, "c": 2}, "d": {"e": 3}}).try_into().unwrap(); -/// -/// assert!(cond.validate(&args1)); -/// assert!(!cond.validate(&args2)); -/// -/// // Succeeds when the key is not present -/// assert!(ExcludesKey { -/// field: "nope".into(), -/// key: "b".into() -/// }.validate(&args2)); -/// -/// // Also succeeds when the input is not a map -/// let list = ipld!({"a": [1, 2, 3]}).try_into().unwrap(); -/// assert!(cond.validate(&list)); -/// assert!(cond.validate(&ipld!({"a": 42}).try_into().unwrap())); -/// ``` -#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] -#[serde(deny_unknown_fields)] -pub struct ExcludesKey { - /// Name of the field to check. - pub field: String, - - /// The key that must not be present. - pub key: String, -} - -impl From for Ipld { - fn from(excludes_key: ExcludesKey) -> Self { - excludes_key.into() - } -} - -impl TryFrom for ExcludesKey { - type Error = SerdeError; - - fn try_from(ipld: Ipld) -> Result { - ipld_serde::from_ipld(ipld) - } -} - -impl Condition for ExcludesKey { - fn validate(&self, args: &arguments::Named) -> bool { - match args.get(&self.field) { - Some(Ipld::Map(map)) => !map.contains_key(&self.key), - _ => true, - } - } -} diff --git a/src/delegation/condition/matches_regex.rs b/src/delegation/condition/matches_regex.rs deleted file mode 100644 index 4b7c9426..00000000 --- a/src/delegation/condition/matches_regex.rs +++ /dev/null @@ -1,76 +0,0 @@ -//! A regular expression [`Condition`]. -use super::traits::Condition; -use crate::ability::arguments; -use libipld_core::{error::SerdeError, ipld::Ipld, serde as ipld_serde}; -use regex::Regex; -use serde_derive::{Deserialize, Serialize}; - -/// A regular expression [`Condition`] -/// -/// This checks a string against a regular expression. -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] -#[serde(deny_unknown_fields)] -pub struct MatchesRegex { - /// Name of the field to check - pub field: String, - - /// The minimum length - pub matches_regex: Matcher, -} - -impl From for Ipld { - fn from(matches_regex: MatchesRegex) -> Self { - matches_regex.into() - } -} - -impl TryFrom for MatchesRegex { - type Error = SerdeError; - - fn try_from(ipld: Ipld) -> Result { - ipld_serde::from_ipld(ipld) - } -} - -impl Condition for MatchesRegex { - fn validate(&self, args: &arguments::Named) -> bool { - match args.get(&self.field) { - Some(Ipld::String(string)) => self.matches_regex.0.is_match(string), - _ => false, - } - } -} - -/// A newtype wrapper around [`Regex`] -#[derive(Debug, Clone)] -pub struct Matcher(Regex); - -impl PartialEq for Matcher { - fn eq(&self, other: &Self) -> bool { - self.0.as_str() == other.0.as_str() - } -} - -impl Eq for Matcher {} - -impl serde::Serialize for Matcher { - fn serialize(&self, serializer: S) -> Result - where - S: serde::Serializer, - { - self.0.as_str().serialize(serializer) - } -} - -impl<'de> serde::Deserialize<'de> for Matcher { - fn deserialize(deserializer: D) -> Result - where - D: serde::Deserializer<'de>, - { - let s: &str = serde::Deserialize::deserialize(deserializer)?; - match Regex::new(s) { - Ok(regex) => Ok(Matcher(regex)), - Err(_) => Err(serde::de::Error::custom(format!("Invalid regex: {}", s))), - } - } -} diff --git a/src/delegation/condition/max_length.rs b/src/delegation/condition/max_length.rs deleted file mode 100644 index 94b0a8f5..00000000 --- a/src/delegation/condition/max_length.rs +++ /dev/null @@ -1,44 +0,0 @@ -//! A max length [`Condition`]. -use super::traits::Condition; -use crate::ability::arguments; -use libipld_core::{error::SerdeError, ipld::Ipld, serde as ipld_serde}; -use serde_derive::{Deserialize, Serialize}; - -/// A maximum length [`Condition`] -/// -/// A condition that checks if the length of a string, list, -/// or map is less than or equal to a set size. -#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] -#[serde(deny_unknown_fields)] -pub struct MaxLength { - /// Name of the field to check - pub field: String, - - /// The maximum length - pub max_length: usize, -} - -impl From for Ipld { - fn from(max_length: MaxLength) -> Self { - max_length.into() - } -} - -impl TryFrom for MaxLength { - type Error = SerdeError; - - fn try_from(ipld: Ipld) -> Result { - ipld_serde::from_ipld(ipld) - } -} - -impl Condition for MaxLength { - fn validate(&self, args: &arguments::Named) -> bool { - match args.get(&self.field) { - Some(Ipld::String(string)) => string.len() <= self.max_length, - Some(Ipld::List(list)) => list.len() <= self.max_length, - Some(Ipld::Map(map)) => map.len() <= self.max_length, - _ => false, - } - } -} diff --git a/src/delegation/condition/max_number.rs b/src/delegation/condition/max_number.rs deleted file mode 100644 index 41cc6eb0..00000000 --- a/src/delegation/condition/max_number.rs +++ /dev/null @@ -1,49 +0,0 @@ -//! A max number [`Condition`]. -use super::traits::Condition; -use crate::{ability::arguments, ipld::Number}; -use libipld_core::{error::SerdeError, ipld::Ipld, serde as ipld_serde}; -use serde_derive::{Deserialize, Serialize}; - -/// A maximum number [`Condition`] -/// -/// A condition that checks if the length of an integer -/// or float is less than or equal to a set size. -#[derive(Debug, Clone, PartialEq, PartialOrd, Serialize, Deserialize)] -#[serde(deny_unknown_fields)] -pub struct MaxNumber { - /// Name of the field to check - pub field: String, - - /// The maximum number - pub max_number: Number, -} - -impl From for Ipld { - fn from(max_number: MaxNumber) -> Self { - max_number.into() - } -} - -impl TryFrom for MaxNumber { - type Error = SerdeError; - - fn try_from(ipld: Ipld) -> Result { - ipld_serde::from_ipld(ipld) - } -} - -impl Condition for MaxNumber { - fn validate(&self, args: &arguments::Named) -> bool { - match args.get(&self.field) { - Some(Ipld::Integer(integer)) => match self.max_number { - Number::Float(float) => *integer as f64 <= float, - Number::Integer(integer) => integer <= integer, - }, - Some(Ipld::Float(float)) => match self.max_number { - Number::Float(float) => float <= float, - Number::Integer(integer) => *float <= integer as f64, // FIXME this needs tests - }, - _ => false, - } - } -} diff --git a/src/delegation/condition/min_length.rs b/src/delegation/condition/min_length.rs deleted file mode 100644 index 5d137df7..00000000 --- a/src/delegation/condition/min_length.rs +++ /dev/null @@ -1,44 +0,0 @@ -//! A min length [`Condition`]. -use super::traits::Condition; -use crate::ability::arguments; -use libipld_core::{error::SerdeError, ipld::Ipld, serde as ipld_serde}; -use serde_derive::{Deserialize, Serialize}; - -/// A mimimum length [`Condition`] -/// -/// This checks if the length of a string, list, -/// or map is greater than or equal to a set size. -#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] -#[serde(deny_unknown_fields)] -pub struct MinLength { - /// Name of the field to check - pub field: String, - - /// The minimum length - pub min_length: usize, -} - -impl From for Ipld { - fn from(min_length: MinLength) -> Self { - min_length.into() - } -} - -impl TryFrom for MinLength { - type Error = SerdeError; - - fn try_from(ipld: Ipld) -> Result { - ipld_serde::from_ipld(ipld) - } -} - -impl Condition for MinLength { - fn validate(&self, args: &arguments::Named) -> bool { - match args.get(&self.field) { - Some(Ipld::String(string)) => string.len() >= self.min_length, - Some(Ipld::List(list)) => list.len() >= self.min_length, - Some(Ipld::Map(map)) => map.len() >= self.min_length, - _ => false, - } - } -} diff --git a/src/delegation/condition/min_number.rs b/src/delegation/condition/min_number.rs deleted file mode 100644 index 996c5f81..00000000 --- a/src/delegation/condition/min_number.rs +++ /dev/null @@ -1,49 +0,0 @@ -//! A min number [`Condition`]. -use super::traits::Condition; -use crate::{ability::arguments, ipld::Number}; -use libipld_core::{error::SerdeError, ipld::Ipld, serde as ipld_serde}; -use serde_derive::{Deserialize, Serialize}; - -/// A minimum number [`Condition`] -/// -/// A condition that checks if the length of an integer -/// or float is less than or equal to a set size. -#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] -#[serde(deny_unknown_fields)] -pub struct MinNumber { - /// Name of the field to check - pub field: String, - - /// The minimum number - pub min_number: Number, -} - -impl From for Ipld { - fn from(min_number: MinNumber) -> Self { - min_number.into() - } -} - -impl TryFrom for MinNumber { - type Error = SerdeError; - - fn try_from(ipld: Ipld) -> Result { - ipld_serde::from_ipld(ipld) - } -} - -impl Condition for MinNumber { - fn validate(&self, args: &arguments::Named) -> bool { - match args.get(&self.field) { - Some(Ipld::Integer(integer)) => match self.min_number { - Number::Float(float) => *integer as f64 >= float, - Number::Integer(integer) => integer >= integer, - }, - Some(Ipld::Float(float)) => match self.min_number { - Number::Float(float) => float >= float, - Number::Integer(integer) => *float >= integer as f64, // FIXME this needs tests - }, - _ => false, - } - } -} diff --git a/src/delegation/condition/traits.rs b/src/delegation/condition/traits.rs deleted file mode 100644 index f1fcf7a6..00000000 --- a/src/delegation/condition/traits.rs +++ /dev/null @@ -1,11 +0,0 @@ -//! Traits for abstracting over policies. - -use crate::ability::arguments; -use libipld_core::ipld::Ipld; -use std::fmt; - -/// A trait for policies that can be run on named IPLD arguments. -pub trait Condition: fmt::Debug { - /// Check that some condition is met on named IPLD arguments. - fn validate(&self, args: &arguments::Named) -> bool; -} diff --git a/src/delegation/payload.rs b/src/delegation/payload.rs index 8247384b..0b9c33ad 100644 --- a/src/delegation/payload.rs +++ b/src/delegation/payload.rs @@ -1,4 +1,4 @@ -use super::condition::Condition; +use super::policy::predicate::Predicate; use crate::{ capsule::Capsule, crypto::Nonce, @@ -21,7 +21,7 @@ use crate::ipld; /// This contains the semantic information about the delegation, including the /// issuer, subject, audience, the delegated ability, time bounds, and so on. #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] -pub struct Payload { +pub struct Payload { /// The subject of the [`Delegation`]. /// /// This role *must* have issued the earlier (root) @@ -48,8 +48,8 @@ pub struct Payload { /// The command being delegated. pub command: String, - /// Any [`Condition`]s on the `ability_builder`. - pub policy: Vec, + /// Any [`Predicate`] policies that constrain the `args` on an [`Invocation`][crate::invocation::Invocation]. + pub policy: Vec, /// Extensible, free-form fields. pub metadata: BTreeMap, @@ -73,7 +73,7 @@ pub struct Payload { pub not_before: Option, } -impl Payload { +impl Payload { pub fn check_time(&self, now: SystemTime) -> Result<(), TimeBoundError> { let ts_now = &Timestamp::postel(now); @@ -91,19 +91,17 @@ impl Payload { } } -impl Capsule for Payload { - const TAG: &'static str = "ucan/d/1.0"; +impl Capsule for Payload { + const TAG: &'static str = "ucan/d@1.0.0-rc.1"; } -impl Verifiable for Payload { +impl Verifiable for Payload { fn verifier(&self) -> &DID { &self.issuer } } -impl Deserialize<'de>, DID: Did + for<'de> Deserialize<'de>> TryFrom - for Payload -{ +impl Deserialize<'de>> TryFrom for Payload { type Error = SerdeError; fn try_from(ipld: Ipld) -> Result { @@ -111,23 +109,21 @@ impl Deserialize<'de>, DID: Did + for<'de> Deserialize<' } } -impl From> for Ipld { - fn from(payload: Payload) -> Self { +impl From> for Ipld { + fn from(payload: Payload) -> Self { payload.into() } } #[cfg(feature = "test_utils")] -impl Arbitrary for Payload +impl Arbitrary for Payload where - C::Strategy: 'static, DID::Parameters: Clone, - C::Parameters: Clone, { - type Parameters = (DID::Parameters, C::Parameters); + type Parameters = (DID::Parameters, ::Parameters); type Strategy = BoxedStrategy; - fn arbitrary_with((did_args, c_args): Self::Parameters) -> Self::Strategy { + fn arbitrary_with((did_args, pred_args): Self::Parameters) -> Self::Strategy { ( Option::::arbitrary(), DID::arbitrary_with(did_args.clone()), @@ -141,7 +137,7 @@ where .map(|(k, v)| (k, v.0)) .collect::>() }), - prop::collection::vec(C::arbitrary_with(c_args), 0..10), + prop::collection::vec(Predicate::arbitrary_with(pred_args), 0..10), ) .prop_map( |( diff --git a/src/delegation/policy.rs b/src/delegation/policy.rs index 525057de..25985d64 100644 --- a/src/delegation/policy.rs +++ b/src/delegation/policy.rs @@ -1,4 +1,4 @@ -//pub mod frontend; -//pub mod interpreter; +pub mod collection; pub mod ir; +pub mod predicate; pub mod selector; diff --git a/src/delegation/policy/collection.rs b/src/delegation/policy/collection.rs new file mode 100644 index 00000000..1cab0451 --- /dev/null +++ b/src/delegation/policy/collection.rs @@ -0,0 +1,36 @@ +use serde::{Deserialize, Serialize}; +use crate::ipld; +use std::collections::BTreeMap; + +#[cfg(feature = "test_utils")] +use proptest::prelude::*; + +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub enum Collection { + Array(Vec), + Map(BTreeMap), +} + +impl Collection { + pub fn to_vec(self) -> Vec { + match self { + Collection::Array(xs) => xs, + Collection::Map(xs) => xs.into_values().collect(), + } + } +} + +#[cfg(feature = "test_utils")] +impl Arbitrary for Collection { + type Parameters = (); + type Strategy = BoxedStrategy; + + fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy { + prop_oneof![ + prop::collection::vec(ipld::Newtype::arbitrary(), 0..10).prop_map(Collection::Array), + prop::collection::btree_map(".*", ipld::Newtype::arbitrary(), 0..10) + .prop_map(Collection::Map), + ] + .boxed() + } +} diff --git a/src/delegation/policy/frontend.rs b/src/delegation/policy/frontend.rs deleted file mode 100644 index 4ac89bb9..00000000 --- a/src/delegation/policy/frontend.rs +++ /dev/null @@ -1,62 +0,0 @@ -use super::ir; -use libipld_core::ipld::Ipld; -use serde::{Deserialize, Serialize}; -use std::collections::BTreeMap; - -#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] -pub enum Term { - // Leaves - Args, // $ - Literal(Ipld), - Variable(Variable), - - Selector(Selector), - - // Connectives - Not(Box), - And(Vec), - Or(Vec), - - // Comparison - Equal(Value, Value), - GreaterThan(Value, Value), - GreaterOrEqual(Value, Value), - LessThan(Value, Value), - LessOrEqual(Value, Value), - - // String Matcher - Glob(Value, String), - - // Existential Quantification - Exists(Variable, Collection), // ∃x ∈ xs -} - -#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] -pub struct Variable(String); // ?x - -#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] -pub enum Collection { - Array(Vec), - Map(BTreeMap), - Variable(Variable), - Selector(Selector), -} - -#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] -pub struct Selector(Vec); // .foo.bar[].baz - -#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] -pub enum Index { - This, - // RecDesend, // .. - FlattenAll, // .[] - Index(usize), // .[2] - Key(String), // .key -} - -#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] -pub enum Value { - Literal(Ipld), - Variable(Variable), - ImplicitBind(Selector), -} diff --git a/src/delegation/policy/interpreter.rs b/src/delegation/policy/interpreter.rs deleted file mode 100644 index 7b378937..00000000 --- a/src/delegation/policy/interpreter.rs +++ /dev/null @@ -1,485 +0,0 @@ -use super::ir::*; -use crate::ability::arguments; -use libipld_core::ipld::Ipld; -use std::collections::BTreeMap; - -// [and ["==", ".foo", "?x"] -// [">", "?x", 0] -// [">", "?x", 2] -// ["==", 10, 11] // Fails, so whole thing fails? -// ["or", ["==", ".bar", "?y"] -// [">", "?y", 12] -// ["and", ["<", "?x", 100] -// ["<", "?y", 100] -// ] -// ["every", "?x", "?e"] -// ] -// ["==", 22, "?e"] -// ["some", "?x" "?a"] -// ["==", "?a", [1, 2, "?z", 4]] -// ["==", ["?b", "?c", 20, 30], [10, "?a", 20, 30]] // -> b = 10, c = a, a = c -// ] - -// [".[]", "$", ?x] -// [".[]", "$", ?y] -// ["==", "?x", "?y"] -// ["==", "?y", "?x"] - -// Register machine -// { -// ports: { -// "?a": Stream<>, -// "?b": Stream<>, -// } -// } - -#[derive(Debug, Clone, PartialEq)] -pub struct Machine { - pub args: arguments::Named, - pub frames: BTreeMap, - pub program: Statement, - pub index_counter: usize, -} - -pub fn run(machine: Machine) -> Machine { - todo!() - // run to exhaustion - // loop { - // if let Ok(next) = run_once(&machine) { - // if next == &machine { - // return machine; - // } - // } else { - // panic!("failed some step"); - // } - // } -} - -pub fn step(mut context: Machine) -> Result { - // FIXME Fix this iter; need to keep getting smaller and runninhg top-to-bottom - // or at least that's one startegy - match context.clone().program { - Statement::And(left, right) => { - let lhs = Machine { - program: *left, - ..context.clone() - }; - - let lhs_result = run(lhs); - - let rhs = Machine { - args: context.args, - frames: lhs_result.frames, - program: *right, - index_counter: lhs_result.index_counter, - }; - - let rhs_result = run(rhs); - - if rhs_result.frames.is_empty() { - Err(()) - } else { - Ok(rhs_result) - } - } - Statement::Or(left, right) => { - let lhs = Machine { - program: *left, - ..context.clone() - }; - - let rhs = Machine { - program: *right, - ..context.clone() - }; - - let lhs_result = run(lhs); - let rhs_result = run(rhs); - - let merged_frames = lhs_result - .frames - .into_iter() - .map(|(key, lhs_stream)| { - let rhs_stream = rhs_result - .frames - .get(&key) - .cloned() - .unwrap_or(lhs_stream.clone()); - - let merged = match (lhs_stream, rhs_stream) { - (Stream::Every(lhs), Stream::Every(rhs)) => { - Stream::Every(lhs.into_iter().chain(rhs).collect()) - } - (Stream::Some(lhs), Stream::Some(rhs)) => { - Stream::Some(lhs.into_iter().chain(rhs).collect()) - } - (Stream::Every(lhs), Stream::Some(rhs)) => { - Stream::Every(lhs.into_iter().chain(rhs).collect()) - } - (Stream::Some(lhs), Stream::Every(rhs)) => { - Stream::Every(lhs.into_iter().chain(rhs).collect()) - } - }; - - (key, merged) - }) - .collect(); - - Ok(Machine { - frames: merged_frames, - ..context - }) - } - Statement::Not(statement) => { - let next = Machine { - program: *statement, - ..context.clone() - }; - - let not_results = run(next); - - for (idx, _) in not_results.frames.iter() { - context.frames.remove(idx); - } - - Ok(context) - } - Statement::Exists(var, collection) => { - let btree: BTreeMap = match collection { - Collection::Array(vec) => vec - .into_iter() - .map(|ipld| (context.next_index(), ipld)) - .collect(), - - Collection::Map(map) => map - .into_iter() - .map(|(_k, ipld)| (context.next_index(), ipld)) - .collect(), - }; - - context.frames.insert(var.0, Stream::Some(btree)); - Ok(context) - } - Statement::Forall(var, collection) => { - let btree: BTreeMap = match collection { - Collection::Array(vec) => vec - .into_iter() - .map(|ipld| (context.next_index(), ipld)) - .collect(), - - Collection::Map(map) => map - .into_iter() - .map(|(_k, ipld)| (context.next_index(), ipld)) - .collect(), - }; - - context.frames.insert(var.0, Stream::Every(btree)); - - // FIXME needs to check that nothing changed - // ...perhaps at the end of the iteration, loop through the streams? - - Ok(context) - } - Statement::Equal(left, right) => context // FIXME do unification - .apply(&left, &right, |a, b| a == b) - .map(|()| context), - Statement::GreaterThan(left, right) => context - .apply(&left, &right, |a, b| match (a, b) { - (Ipld::Integer(a), Ipld::Integer(b)) => a > b, - (Ipld::Float(a), Ipld::Float(b)) => a > b, - (Ipld::Integer(a), Ipld::Float(b)) => (*a as f64) > *b, - (Ipld::Float(a), Ipld::Integer(b)) => *a > (*b as f64), - _ => false, - }) - .map(|()| context), - Statement::LessThan(left, right) => context - .apply(&left, &right, |a, b| match (a, b) { - (Ipld::Integer(a), Ipld::Integer(b)) => a < b, - (Ipld::Float(a), Ipld::Float(b)) => a < b, - (Ipld::Integer(a), Ipld::Float(b)) => (*a as f64) < *b, - (Ipld::Float(a), Ipld::Integer(b)) => *a < (*b as f64), - _ => false, - }) - .map(|()| context), - Statement::GreaterThanOrEqual(left, right) => context - .apply(&left, &right, |a, b| match (a, b) { - (Ipld::Integer(a), Ipld::Integer(b)) => a >= b, - (Ipld::Float(a), Ipld::Float(b)) => a >= b, - (Ipld::Integer(a), Ipld::Float(b)) => (*a as f64) >= *b, - (Ipld::Float(a), Ipld::Integer(b)) => *a >= (*b as f64), - _ => false, - }) - .map(|()| context), - - Statement::LessThanOrEqual(left, right) => context - .apply(&left, &right, |a, b| match (a, b) { - (Ipld::Integer(a), Ipld::Integer(b)) => a <= b, - (Ipld::Float(a), Ipld::Float(b)) => a <= b, - (Ipld::Integer(a), Ipld::Float(b)) => (*a as f64) <= *b, - (Ipld::Float(a), Ipld::Integer(b)) => *a <= (*b as f64), - _ => false, - }) - .map(|()| context), - - Statement::Glob(left, right) => context - .apply(&left, &right, |a, b| glob(a, b)) - .map(|()| context), - Statement::Select(selector, target, var) => match target { - SelectorValue::Args => { - let ipld = Ipld::Map(context.args.clone().0); - let selected = select(selector, ipld)?; - let idx = context.next_index(); - - context - .frames - .insert(var.0, Stream::Every(BTreeMap::from_iter([(idx, selected)]))); - - Ok(context) - } - SelectorValue::Literal(ipld) => { - let ipld = select(selector, ipld)?; - let idx = context.next_index(); - - context - .frames - .insert(var.0, Stream::Every(BTreeMap::from_iter([(idx, ipld)]))); - - Ok(context) - } - SelectorValue::Variable(var_id) => { - let current = context - .frames - .get(&var_id.0) - .cloned() - .unwrap_or(Stream::Every(BTreeMap::new())); - - let result: Result, ()> = current - .clone() - .to_btree() - .into_iter() - .map(|(idx, ipld)| select(selector.clone(), ipld).map(|ipld| (idx, ipld))) - .collect(); - - let updated = result?; - - context - .frames - .insert(var.0, current.map(|_| updated.clone())); - - Ok(context) - } - }, - } -} - -pub fn select(selector: Selector, on: Ipld) -> Result { - let results: Vec = selector - .0 - .iter() - .try_fold(vec![on], |ipld_stream, segment| match segment { - PathSegment::This => Ok(ipld_stream), - PathSegment::Index(i) => { - ipld_stream - .iter() - .try_fold(vec![], |mut acc, ipld_entry| match ipld_entry { - Ipld::List(vec) => { - if let Some(ipld) = vec.get(*i) { - acc.push(ipld.clone()); - Ok(acc) - } else { - Err(()) - } - } - _ => Err(()), - }) - } - PathSegment::Key(key) => { - ipld_stream - .iter() - .try_fold(vec![], |mut acc, ipld_entry| match ipld_entry { - Ipld::Map(map) => { - if let Some(ipld) = map.get(key) { - acc.push(ipld.clone()); - Ok(acc) - } else { - Err(()) - } - } - _ => Err(()), - }) - } - PathSegment::FlattenAll => { - ipld_stream - .iter() - .try_fold(vec![], |mut acc, ipld_entry| match ipld_entry { - Ipld::List(vec) => { - acc.extend(vec.clone()); - Ok(acc.iter().cloned().collect()) - } - _ => Err(()), - }) - } - })?; - - match &results[..] { - [ipld] => Ok(ipld.clone()), - vec => Ok(Ipld::List( - vec.into_iter().map(|ipld| ipld.clone()).collect(), - )), - } -} - -impl Machine { - pub fn next_index(&mut self) -> usize { - let prev = self.index_counter; - self.index_counter += 1; - prev - } - - pub fn apply(&mut self, lhs: &Value, rhs: &Value, f: F) -> Result<(), ()> - where - F: Fn(&Ipld, &Ipld) -> bool, - { - match lhs { - Value::Literal(left_ipld) => match rhs { - Value::Literal(right_ipld) => { - if f(left_ipld, right_ipld) { - Ok(()) - } else { - Err(()) - } - } - Value::Variable(var_id) => { - let key = var_id.0.clone(); - - if let Some(stream) = self.frames.get(&key) { - let updated = stream.clone().map(|btree| { - btree - .into_iter() - .filter(|(_idx, ipld)| f(left_ipld, ipld)) - .collect() - }); - - match updated { - Stream::Every(ref btree) => { - if btree.len() < stream.len() { - return Err(()); - } - } - Stream::Some(ref btree) => { - if btree.is_empty() { - return Err(()); - } - } - } - - self.frames.insert(key, updated); - Ok(()) - } else { - Err(()) - } - } - }, - Value::Variable(var_id) => { - let lhs_key = &var_id.0; - - if let Some(lhs_stream) = self.frames.get(lhs_key) { - match rhs { - Value::Literal(right_ipld) => { - let updated = lhs_stream.clone().map(|btree| { - btree - .into_iter() - .filter(|(_idx, ipld)| f(ipld, right_ipld)) - .collect() - }); - - match updated { - Stream::Every(ref btree) => { - if btree.len() < lhs_stream.len() { - return Err(()); - } - } - Stream::Some(ref btree) => { - if btree.is_empty() { - return Err(()); - } - } - } - - self.frames.insert(lhs_key.clone(), updated); - Ok(()) - } - Value::Variable(var_id) => { - let rhs_key = var_id.0.as_str(); - - if let Some(rhs_stream) = self.frames.get(rhs_key) { - let mut non_matches: BTreeMap> = BTreeMap::new(); - - for (lhs_id, lhs_value) in lhs_stream.iter() { - for (rhs_id, rhs_value) in rhs_stream.iter() { - if !f(lhs_value, rhs_value) { - if let Some(rhs_ids) = non_matches.get_mut(lhs_id) { - rhs_ids.push(*rhs_id); - } else { - non_matches.insert(*lhs_id, vec![*rhs_id]); - } - } - } - } - - // Double negatives, but for good reason - let did_quantify = - match (lhs_stream.is_every(), rhs_stream.is_every()) { - (true, true) => non_matches.is_empty(), - (true, false) => non_matches - .values() - .all(|rhs_ids| rhs_ids.len() != rhs_stream.len()), - (false, true) => { - non_matches.values().any(|rhs_ids| rhs_ids.is_empty()) - } - (false, false) => non_matches - .values() - .any(|rhs_ids| rhs_ids.len() < rhs_stream.len()), - }; - - if did_quantify { - let mut new_lhs_stream = lhs_stream.clone(); - let mut new_rhs_stream = rhs_stream.clone(); - - for (l_key, r_keys) in non_matches { - new_lhs_stream.remove(l_key); - - for r_key in r_keys { - new_rhs_stream.remove(r_key); - } - } - - self.frames.insert(lhs_key.into(), new_lhs_stream); - self.frames.insert(rhs_key.into(), new_rhs_stream); - - Ok(()) - } else { - Err(()) - } - } else { - Err(()) - } - } - } - } else { - // FIXME not nessesarily! You may need to create new entires - Err(()) - } - } - } - } - - pub fn pattern_matching_unification(&mut self, lhs: &Value, rhs: &Value) -> Result { - self.apply(lhs, rhs, |a, b| { - todo!(); - todo!(); - // FIXME pattern matching etc - }) - .map(|()| rhs.clone()) - } -} diff --git a/src/delegation/policy/ir.rs b/src/delegation/policy/ir.rs index c520658b..f897fe9e 100644 --- a/src/delegation/policy/ir.rs +++ b/src/delegation/policy/ir.rs @@ -1,42 +1,13 @@ //FIXME rename core -use super::selector::op::SelectorOp; +use super::{ + collection::Collection, + selector::{error::SelectorErrorReason, SelectorError}, +}; use crate::ipld; use libipld_core::ipld::Ipld; -use serde::{Deserialize, Serialize}; use std::collections::BTreeMap; -impl Predicate { - pub fn run(self, data: &Ipld) -> Result { - Ok(match self { - Predicate::True => true, - Predicate::False => false, - Predicate::Equal(lhs, rhs) => lhs.resolve(data)? == rhs.resolve(data)?, - Predicate::GreaterThan(lhs, rhs) => lhs.resolve(data)? > rhs.resolve(data)?, - Predicate::GreaterThanOrEqual(lhs, rhs) => lhs.resolve(data)? >= rhs.resolve(data)?, - Predicate::LessThan(lhs, rhs) => lhs.resolve(data)? < rhs.resolve(data)?, - Predicate::LessThanOrEqual(lhs, rhs) => lhs.resolve(data)? <= rhs.resolve(data)?, - Predicate::Like(lhs, rhs) => glob(&lhs.resolve(data)?, &rhs.resolve(data)?), - Predicate::Not(inner) => !inner.run(data)?, - Predicate::And(lhs, rhs) => lhs.run(data)? && rhs.run(data)?, - Predicate::Or(lhs, rhs) => lhs.run(data)? || rhs.run(data)?, - Predicate::Forall(xs, p) => xs - .resolve(data)? - .to_vec() - .iter() - .try_fold(true, |acc, ipld| Ok(acc && p.clone().run(ipld)?))?, - Predicate::Exists(xs, p) => { - let pred = p.clone(); - - xs.resolve(data)? - .to_vec() - .iter() - .try_fold(true, |acc, ipld| Ok(acc || pred.clone().run(ipld)?))? - } - }) - } -} - -trait Resolve { +pub trait Resolve { fn resolve(self, ctx: &Ipld) -> Result; } @@ -46,6 +17,12 @@ impl Resolve for Ipld { } } +impl Resolve for ipld::Newtype { + fn resolve(self, _ctx: &Ipld) -> Result { + Ok(self) + } +} + impl Resolve for ipld::Number { fn resolve(self, _ctx: &Ipld) -> Result { Ok(self) @@ -58,106 +35,12 @@ impl Resolve for Collection { } } -// FIXME Normal form? - impl Resolve for String { fn resolve(self, _ctx: &Ipld) -> Result { Ok(self) } } -impl SelectorOr { - fn resolve(self, ctx: &Ipld) -> Result { - match self { - SelectorOr::Pure(inner) => Ok(inner), - SelectorOr::Get(ops) => { - ops.iter() - .try_fold((ctx.clone(), vec![]), |(ipld, mut seen_ops), op| { - seen_ops.push(op); - - match op { - // SelectorOp::This => Ok((ipld, seen_ops)), - SelectorOp::Try(inner) => { - let op: SelectorOp = *inner.clone(); - let ipld: Ipld = SelectorOr::Get::(vec![op]) - .resolve(ctx) - .unwrap_or(Ipld::Null); - - Ok((ipld, seen_ops)) - } - SelectorOp::ArrayIndex(i) => { - let result = { - match ipld { - Ipld::List(xs) => { - if i.abs() as usize > xs.len() { - return Err(SelectorError { - path: seen_ops - .iter() - .map(|op| (*op).clone()) - .collect(), - reason: SelectorErrorReason::IndexOutOfBounds, - }); - } - - xs.get((xs.len() as i32 + *i) as usize) - .ok_or(SelectorError { - path: seen_ops - .iter() - .map(|op| (*op).clone()) - .collect(), - reason: SelectorErrorReason::IndexOutOfBounds, - }) - .cloned() - } - // FIXME behaviour on maps? type error - _ => Err(SelectorError { - path: seen_ops.iter().map(|op| (*op).clone()).collect(), - reason: SelectorErrorReason::NotAList, - }), - } - }; - - Ok((result?, seen_ops)) - } - SelectorOp::Field(k) => { - let result = match ipld { - Ipld::Map(xs) => xs - .get(k) - .ok_or(SelectorError::from_refs( - &seen_ops, - SelectorErrorReason::KeyNotFound, - )) - .cloned(), - _ => Err(SelectorError::from_refs( - &seen_ops, - SelectorErrorReason::NotAMap, - )), - }; - - Ok((result?.clone(), seen_ops)) - } - SelectorOp::Values => { - let result = match ipld { - Ipld::List(xs) => Ok(Ipld::List(xs)), - Ipld::Map(xs) => Ok(Ipld::List(xs.values().cloned().collect())), - _ => Err(SelectorError::from_refs( - &seen_ops, - SelectorErrorReason::NotACollection, - )), - }; - - Ok((result?.clone(), seen_ops)) - } - } - }) - .and_then(|(ipld, ref path)| { - T::try_from_ipld(ipld).map_err(|e| SelectorError::from_refs(path, e)) - }) - } - } - } -} - pub trait TryFromIpld: Sized { fn try_from_ipld(ipld: Ipld) -> Result; } @@ -168,6 +51,12 @@ impl TryFromIpld for Ipld { } } +impl TryFromIpld for ipld::Newtype { + fn try_from_ipld(ipld: Ipld) -> Result { + Ok(ipld::Newtype(ipld)) + } +} + impl TryFromIpld for ipld::Number { fn try_from_ipld(ipld: Ipld) -> Result { match ipld { @@ -190,128 +79,22 @@ impl TryFromIpld for String { impl TryFromIpld for Collection { fn try_from_ipld(ipld: Ipld) -> Result { match ipld { - Ipld::List(xs) => Ok(Collection::Array(xs)), - Ipld::Map(xs) => Ok(Collection::Map(xs)), + Ipld::List(xs) => Ok(Collection::Array(xs.into_iter().try_fold( + vec![], + |mut acc, v| { + acc.push(TryFromIpld::try_from_ipld(v)?); + Ok(acc) + }, + )?)), + Ipld::Map(xs) => Ok(Collection::Map(xs.into_iter().try_fold( + BTreeMap::new(), + |mut map, (k, v)| { + let value = TryFromIpld::try_from_ipld(v)?; + map.insert(k, value); + Ok(map) + }, + )?)), _ => Err(SelectorErrorReason::NotACollection), } } } - -#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] -pub struct SelectorError { - pub path: Vec, - pub reason: SelectorErrorReason, -} - -impl SelectorError { - pub fn from_refs(path_refs: &Vec<&SelectorOp>, reason: SelectorErrorReason) -> SelectorError { - SelectorError { - path: path_refs.iter().map(|op| (*op).clone()).collect(), - reason, - } - } -} - -#[derive(Debug, Copy, Clone, PartialEq, Eq, Serialize, Deserialize)] -pub enum SelectorErrorReason { - IndexOutOfBounds, - KeyNotFound, - NotAList, - NotAMap, - NotACollection, - NotANumber, - NotAString, -} - -// FIXME exract domain gen selectors first? -// FIXME rename constraint or validation or expression or something? -#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] -pub enum Predicate { - // Booleans - True, - False, - - // Comparison - Equal(SelectorOr, SelectorOr), - - GreaterThan(SelectorOr, SelectorOr), - GreaterThanOrEqual(SelectorOr, SelectorOr), - - LessThan(SelectorOr, SelectorOr), - LessThanOrEqual(SelectorOr, SelectorOr), - - Like(SelectorOr, SelectorOr), - - // Connectives - Not(Box), - And(Box, Box), - Or(Box, Box), - - // Collection iteration - Forall(SelectorOr, Box), // ∀x ∈ xs - Exists(SelectorOr, Box), // ∃x ∈ xs -} - -#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] -pub enum SelectorOr { - Get(Vec), - Pure(T), -} - -#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] -pub enum Collection { - Array(Vec), - Map(BTreeMap), -} - -impl Collection { - pub fn to_vec(self) -> Vec { - match self { - Collection::Array(xs) => xs, - Collection::Map(xs) => xs.into_values().collect(), - } - } -} - -#[derive(Debug, Clone, PartialEq)] -pub struct Selector(pub Vec); - -impl Serialize for Selector { - fn serialize(&self, serializer: S) -> Result - where - S: serde::Serializer, - { - self.0 - .iter() - .fold("".into(), |acc, seg| format!("{}{}", acc, seg.to_string())) - .serialize(serializer) - } -} - -pub fn glob(input: &String, pattern: &String) -> bool { - let mut chars = input.chars(); - let mut like = pattern.chars(); - - loop { - match (chars.next(), like.next()) { - (Some(i), Some(p)) => { - if p == '*' { - return true; - } else if i != p { - return false; - } - } - (Some(_), None) => { - return false; // FIXME correct? - } - (None, Some(p)) => { - if p == '*' { - return true; - } - } - (None, None) => { - return true; - } - } - } -} diff --git a/src/delegation/policy/predicate.rs b/src/delegation/policy/predicate.rs new file mode 100644 index 00000000..14e0ced3 --- /dev/null +++ b/src/delegation/policy/predicate.rs @@ -0,0 +1,144 @@ +use super::{ + collection::Collection, + selector::{or::SelectorOr, SelectorError}, +}; +use crate::{ability::arguments, ipld}; +use libipld_core::ipld::Ipld; +use serde::{Deserialize, Serialize}; + +#[cfg(feature = "test_utils")] +use proptest::prelude::*; + +// FIXME Normal form? +// FIXME exract domain gen selectors first? +// FIXME rename constraint or validation or expression or something? +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub enum Predicate { + // Booleans + True, + False, + + // Comparison + Equal(SelectorOr, SelectorOr), + + GreaterThan(SelectorOr, SelectorOr), + GreaterThanOrEqual(SelectorOr, SelectorOr), + + LessThan(SelectorOr, SelectorOr), + LessThanOrEqual(SelectorOr, SelectorOr), + + Like(SelectorOr, SelectorOr), + + // Connectives + Not(Box), + And(Box, Box), + Or(Box, Box), + + // Collection iteration + Every(SelectorOr, Box), // ∀x ∈ xs + Some(SelectorOr, Box), // ∃x ∈ xs +} + +impl Predicate { + pub fn run(self, data: &Ipld) -> Result { + Ok(match self { + Predicate::True => true, + Predicate::False => false, + Predicate::Equal(lhs, rhs) => lhs.resolve(data)? == rhs.resolve(data)?, + Predicate::GreaterThan(lhs, rhs) => lhs.resolve(data)? > rhs.resolve(data)?, + Predicate::GreaterThanOrEqual(lhs, rhs) => lhs.resolve(data)? >= rhs.resolve(data)?, + Predicate::LessThan(lhs, rhs) => lhs.resolve(data)? < rhs.resolve(data)?, + Predicate::LessThanOrEqual(lhs, rhs) => lhs.resolve(data)? <= rhs.resolve(data)?, + Predicate::Like(lhs, rhs) => glob(&lhs.resolve(data)?, &rhs.resolve(data)?), + Predicate::Not(inner) => !inner.run(data)?, + Predicate::And(lhs, rhs) => lhs.run(data)? && rhs.run(data)?, + Predicate::Or(lhs, rhs) => lhs.run(data)? || rhs.run(data)?, + Predicate::Every(xs, p) => xs + .resolve(data)? + .to_vec() + .iter() + .try_fold(true, |acc, nt| Ok(acc && p.clone().run(&nt.0)?))?, + Predicate::Some(xs, p) => { + let pred = p.clone(); + + xs.resolve(data)? + .to_vec() + .iter() + .try_fold(true, |acc, nt| Ok(acc || pred.clone().run(&nt.0)?))? + } + }) + } +} + +pub fn glob(input: &String, pattern: &String) -> bool { + let mut chars = input.chars(); + let mut like = pattern.chars(); + + loop { + match (chars.next(), like.next()) { + (Some(i), Some(p)) => { + if p == '*' { + return true; + } else if i != p { + return false; + } + } + (Some(_), None) => { + return false; // FIXME correct? + } + (None, Some(p)) => { + if p == '*' { + return true; + } + } + (None, None) => { + return true; + } + } + } +} + +#[cfg(feature = "test_utils")] +impl Arbitrary for Predicate { + type Parameters = (); // FIXME? + type Strategy = BoxedStrategy; + + fn arbitrary_with(_params: Self::Parameters) -> Self::Strategy { + let leaf = prop_oneof![ + Just(Predicate::True), + Just(Predicate::False), + (SelectorOr::arbitrary(), SelectorOr::arbitrary()) + .prop_map(|(lhs, rhs)| { Predicate::Equal(lhs, rhs) }), + (SelectorOr::arbitrary(), SelectorOr::arbitrary()) + .prop_map(|(lhs, rhs)| { Predicate::GreaterThan(lhs, rhs) }), + (SelectorOr::arbitrary(), SelectorOr::arbitrary()) + .prop_map(|(lhs, rhs)| { Predicate::GreaterThanOrEqual(lhs, rhs) }), + (SelectorOr::arbitrary(), SelectorOr::arbitrary()) + .prop_map(|(lhs, rhs)| { Predicate::LessThan(lhs, rhs) }), + (SelectorOr::arbitrary(), SelectorOr::arbitrary()) + .prop_map(|(lhs, rhs)| { Predicate::LessThanOrEqual(lhs, rhs) }), + (SelectorOr::arbitrary(), SelectorOr::arbitrary()) + .prop_map(|(lhs, rhs)| { Predicate::Like(lhs, rhs) }) + ]; + + let connective = leaf.clone().prop_recursive(8, 16, 4, |inner| { + prop_oneof![ + (inner.clone(), inner.clone()) + .prop_map(|(lhs, rhs)| { Predicate::And(Box::new(lhs), Box::new(rhs)) }), + (inner.clone(), inner.clone()) + .prop_map(|(lhs, rhs)| { Predicate::Or(Box::new(lhs), Box::new(rhs)) }), + ] + }); + + let quantified = leaf.clone().prop_recursive(8, 16, 4, |inner| { + prop_oneof![ + (SelectorOr::arbitrary(), inner.clone()) + .prop_map(|(xs, p)| { Predicate::Every(xs, Box::new(p)) }), + (SelectorOr::arbitrary(), inner.clone()) + .prop_map(|(xs, p)| { Predicate::Some(xs, Box::new(p)) }), + ] + }); + + prop_oneof![leaf, connective, quantified].boxed() + } +} diff --git a/src/delegation/policy/selector.rs b/src/delegation/policy/selector.rs index 20b1966e..aa0fa677 100644 --- a/src/delegation/policy/selector.rs +++ b/src/delegation/policy/selector.rs @@ -1,7 +1,8 @@ pub mod error; pub mod op; +pub mod or; -use error::ParseError; +use error::{ParseError, SelectorErrorReason}; use nom::{ self, branch::alt, @@ -15,6 +16,7 @@ use nom::{ }; use serde::{Deserialize, Deserializer, Serialize, Serializer}; use std::{fmt, str::FromStr}; +use thiserror::Error; #[derive(Debug, Clone, PartialEq)] pub struct Selector(Vec); @@ -23,7 +25,7 @@ pub fn parse(input: &str) -> IResult<&str, Selector> { let without_this = many1(op::parse); let with_this = preceded(char('.'), many0(op::parse)); - // NOTE: must try without this first, to disambiguate `.field` from `.` + // NOTE: must try without_this this first, to disambiguate `.field` from `.` let p = map_res(alt((without_this, with_this)), |found| { Ok::(Selector(found)) }); @@ -82,3 +84,22 @@ impl<'de> Deserialize<'de> for Selector { Selector::from_str(&s).map_err(serde::de::Error::custom) } } + +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Error)] +#[error("Selector {selector} encountered runtime error: {reason}")] +pub struct SelectorError { + pub selector: Selector, + pub reason: SelectorErrorReason, +} + +impl SelectorError { + pub fn from_refs( + path_refs: &Vec<&op::SelectorOp>, + reason: SelectorErrorReason, + ) -> SelectorError { + SelectorError { + selector: Selector(path_refs.iter().map(|op| (*op).clone()).collect()), + reason, + } + } +} diff --git a/src/delegation/policy/selector/error.rs b/src/delegation/policy/selector/error.rs index b4e583ac..75a96a58 100644 --- a/src/delegation/policy/selector/error.rs +++ b/src/delegation/policy/selector/error.rs @@ -9,3 +9,27 @@ pub enum ParseError { #[error("unknown pattern: {0}")] UnknownPattern(String), } + +#[derive(Debug, Copy, Clone, PartialEq, Eq, Serialize, Deserialize, Error)] +pub enum SelectorErrorReason { + #[error("Index out of bounds")] + IndexOutOfBounds, + + #[error("Key not found")] + KeyNotFound, + + #[error("Not a list")] + NotAList, + + #[error("Not a map")] + NotAMap, + + #[error("Not a collection")] + NotACollection, + + #[error("Not a number")] + NotANumber, + + #[error("Not a string")] + NotAString, +} diff --git a/src/delegation/policy/selector/op.rs b/src/delegation/policy/selector/op.rs index 26ca0499..8ff8a47a 100644 --- a/src/delegation/policy/selector/op.rs +++ b/src/delegation/policy/selector/op.rs @@ -14,10 +14,11 @@ use nom::{ use serde::{Deserialize, Deserializer, Serialize, Serializer}; use std::{fmt, str::FromStr}; +#[cfg(feature = "test_utils")] +use proptest::prelude::*; + #[derive(Debug, Clone, PartialEq, EnumAsInner)] pub enum SelectorOp { - // FIXME remove #[default] - // This, // . ArrayIndex(i32), // [2] Field(String), // ["key"] (or .key) Values, // .[] @@ -27,7 +28,6 @@ pub enum SelectorOp { impl fmt::Display for SelectorOp { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { - // SelectorOp::This => write!(f, "."), SelectorOp::ArrayIndex(i) => write!(f, "[{}]", i), SelectorOp::Field(k) => { if let Some(first) = k.chars().next() { @@ -140,3 +140,19 @@ impl<'de> Deserialize<'de> for SelectorOp { SelectorOp::from_str(&s).map_err(|e| serde::de::Error::custom(e.to_string())) } } + +#[cfg(feature = "test_utils")] +impl Arbitrary for SelectorOp { + type Parameters = (); + type Strategy = BoxedStrategy; + + fn arbitrary_with(_params: Self::Parameters) -> Self::Strategy { + prop_oneof![ + i32::arbitrary().prop_map(|i| SelectorOp::ArrayIndex(i)), + String::arbitrary().prop_map(SelectorOp::Field), + Just(SelectorOp::Values), + // FIXME prop_recursive::lazy(|_| { SelectorOp::arbitrary_with(()).prop_map(SelectorOp::Try) }), + ] + .boxed() + } +} diff --git a/src/delegation/policy/selector/or.rs b/src/delegation/policy/selector/or.rs new file mode 100644 index 00000000..74cf2699 --- /dev/null +++ b/src/delegation/policy/selector/or.rs @@ -0,0 +1,113 @@ +use super::{error::SelectorErrorReason, op::SelectorOp, SelectorError}; +use crate::delegation::policy::ir::TryFromIpld; +use libipld_core::ipld::Ipld; +use serde::{Deserialize, Serialize}; + +#[cfg(feature = "test_utils")] +use proptest::prelude::*; + +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub enum SelectorOr { + Get(Vec), + Pure(T), +} + +impl SelectorOr { + pub fn resolve(self, ctx: &Ipld) -> Result { + match self { + SelectorOr::Pure(inner) => Ok(inner), + SelectorOr::Get(ops) => { + ops.iter() + .try_fold((ctx.clone(), vec![]), |(ipld, mut seen_ops), op| { + seen_ops.push(op); + + match op { + SelectorOp::Try(inner) => { + let op: SelectorOp = *inner.clone(); + let ipld: Ipld = SelectorOr::Get::(vec![op]) + .resolve(ctx) + .unwrap_or(Ipld::Null); + + Ok((ipld, seen_ops)) + } + SelectorOp::ArrayIndex(i) => { + let result = { + match ipld { + Ipld::List(xs) => { + if i.abs() as usize > xs.len() { + return Err(SelectorError::from_refs( + &seen_ops, + SelectorErrorReason::IndexOutOfBounds, + )); + }; + + xs.get((xs.len() as i32 + *i) as usize) + .ok_or(SelectorError::from_refs( + &seen_ops, + SelectorErrorReason::IndexOutOfBounds, + )) + .cloned() + } + // FIXME behaviour on maps? type error + _ => Err(SelectorError::from_refs( + &seen_ops, + SelectorErrorReason::NotAList, + )), + } + }; + + Ok((result?, seen_ops)) + } + SelectorOp::Field(k) => { + let result = match ipld { + Ipld::Map(xs) => xs + .get(k) + .ok_or(SelectorError::from_refs( + &seen_ops, + SelectorErrorReason::KeyNotFound, + )) + .cloned(), + _ => Err(SelectorError::from_refs( + &seen_ops, + SelectorErrorReason::NotAMap, + )), + }; + + Ok((result?, seen_ops)) + } + SelectorOp::Values => { + let result = match ipld { + Ipld::List(xs) => Ok(Ipld::List(xs)), + Ipld::Map(xs) => Ok(Ipld::List(xs.values().cloned().collect())), + _ => Err(SelectorError::from_refs( + &seen_ops, + SelectorErrorReason::NotACollection, + )), + }; + + Ok((result?, seen_ops)) + } + } + }) + .and_then(|(ipld, ref path)| { + T::try_from_ipld(ipld).map_err(|e| SelectorError::from_refs(path, e)) + }) + } + } + } +} + +#[cfg(feature = "test_utils")] +impl Arbitrary for SelectorOr { + type Parameters = T::Parameters; + type Strategy = BoxedStrategy; + + fn arbitrary_with(t_params: Self::Parameters) -> Self::Strategy { + prop_oneof![ + T::arbitrary_with(t_params).prop_map(SelectorOr::Pure), + // FIXME add params that make this actually correspond to data + prop::collection::vec(SelectorOp::arbitrary(), 1..10).prop_map(SelectorOr::Get), + ] + .boxed() + } +} diff --git a/src/delegation/store/memory.rs b/src/delegation/store/memory.rs index b05b4039..130043b2 100644 --- a/src/delegation/store/memory.rs +++ b/src/delegation/store/memory.rs @@ -1,10 +1,8 @@ use super::Store; use crate::{ - // ability::arguments, crypto::varsig, - delegation::{condition::Condition, Delegation}, + delegation::{policy::predicate::Predicate, Delegation}, did::Did, - // proof::{checkable::Checkable, prove::Prove}, }; use libipld_core::{cid::Cid, codec::Codec}; use nonempty::NonEmpty; @@ -72,34 +70,29 @@ use web_time::SystemTime; /// ``` #[derive(Debug, Clone, PartialEq)] pub struct MemoryStore< - C: Condition, DID: Did + Ord, V: varsig::Header, Enc: Codec + TryFrom + Into, > { - ucans: BTreeMap>, + ucans: BTreeMap>, index: BTreeMap, BTreeMap>>, revocations: BTreeSet, } // FIXME check that UCAN is valid -impl< - C: Condition + PartialEq, - DID: Did + Ord + Clone, - V: varsig::Header, - Enc: Codec + TryFrom + Into, - > Store for MemoryStore +impl, Enc: Codec + TryFrom + Into> + Store for MemoryStore { type DelegationStoreError = (); // FIXME misisng - fn get(&self, cid: &Cid) -> Result<&Delegation, Self::DelegationStoreError> { + fn get(&self, cid: &Cid) -> Result<&Delegation, Self::DelegationStoreError> { self.ucans.get(cid).ok_or(()) } fn insert( &mut self, cid: Cid, - delegation: Delegation, + delegation: Delegation, ) -> Result<(), Self::DelegationStoreError> { self.index .entry(delegation.subject().clone()) @@ -121,10 +114,9 @@ impl< &self, aud: &DID, subject: &Option, - policy: Vec, + policy: Vec, now: SystemTime, - ) -> Result)>>, Self::DelegationStoreError> - { + ) -> Result)>>, Self::DelegationStoreError> { match self .index .get(subject) // FIXME probably need to rework this after last minbute chanegs diff --git a/src/delegation/store/traits.rs b/src/delegation/store/traits.rs index e3090ff8..a474fdc1 100644 --- a/src/delegation/store/traits.rs +++ b/src/delegation/store/traits.rs @@ -1,8 +1,7 @@ use crate::{ crypto::varsig, - delegation::{condition::Condition, Delegation}, + delegation::{policy::predicate::Predicate, Delegation}, did::Did, - // proof::checkable::Checkable, }; use libipld_core::{cid::Cid, codec::Codec}; use nonempty::NonEmpty; @@ -10,16 +9,10 @@ use std::fmt::Debug; use web_time::SystemTime; // NOTE the T here is the builder... FIXME add one layer up and call T::Builder? May be confusing? -pub trait Store< - C: Condition, - DID: Did, - V: varsig::Header, - Enc: Codec + TryFrom + Into, -> -{ +pub trait Store, Enc: Codec + TryFrom + Into> { type DelegationStoreError: Debug; - fn get(&self, cid: &Cid) -> Result<&Delegation, Self::DelegationStoreError>; + fn get(&self, cid: &Cid) -> Result<&Delegation, Self::DelegationStoreError>; // FIXME add a variant that calculated the CID from the capsulre header? // FIXME that means changing the name to insert_by_cid or similar @@ -27,7 +20,7 @@ pub trait Store< fn insert( &mut self, cid: Cid, - delegation: Delegation, + delegation: Delegation, ) -> Result<(), Self::DelegationStoreError>; // FIXME validate invocation @@ -39,15 +32,15 @@ pub trait Store< &self, audience: &DID, subject: &Option, - policy: Vec, + policy: Vec, now: SystemTime, - ) -> Result)>>, Self::DelegationStoreError>; + ) -> Result)>>, Self::DelegationStoreError>; fn can_delegate( &self, issuer: DID, audience: &DID, - policy: Vec, + policy: Vec, now: SystemTime, ) -> Result { self.get_chain(audience, &Some(issuer), policy, now) @@ -57,9 +50,9 @@ pub trait Store< fn get_many( &self, cids: &[Cid], - ) -> Result>, Self::DelegationStoreError> { + ) -> Result>, Self::DelegationStoreError> { cids.iter().try_fold(vec![], |mut acc, cid| { - let d: &Delegation = self.get(cid)?; + let d: &Delegation = self.get(cid)?; acc.push(d); Ok(acc) }) diff --git a/src/invocation/agent.rs b/src/invocation/agent.rs index 5e728646..164faa54 100644 --- a/src/invocation/agent.rs +++ b/src/invocation/agent.rs @@ -5,13 +5,9 @@ use super::{ Invocation, }; use crate::{ - ability::{ - arguments, - parse::{ParseAbility, ParseAbilityError, ParsePromised}, - ucan, - }, + ability::{arguments, parse::ParseAbilityError, ucan}, crypto::{signature, varsig, Nonce}, - delegation::{self, condition::Condition}, + delegation, did::Did, invocation::promise, // proof::prove::Prove, @@ -34,11 +30,10 @@ use web_time::SystemTime; pub struct Agent< 'a, T: Resolvable, - C: Condition, DID: Did, S: Store, P: promise::Store, - D: delegation::store::Store, + D: delegation::store::Store, V: varsig::Header, Enc: Codec + Into + TryFrom, > { @@ -49,20 +44,19 @@ pub struct Agent< pub unresolved_promise_index: &'a mut P, signer: &'a ::Signer, - marker: PhantomData<(T, C, V, Enc)>, + marker: PhantomData<(T, V, Enc)>, } -impl<'a, T, C, DID, S, P, D, V, Enc> Agent<'a, T, C, DID, S, P, D, V, Enc> +impl<'a, T, DID, S, P, D, V, Enc> Agent<'a, T, DID, S, P, D, V, Enc> where T::Promised: Clone, Ipld: Encode, - delegation::Payload: Clone, + delegation::Payload: Clone, T: Resolvable + Clone, - C: Condition, DID: Did + Clone, S: Store, P: promise::Store, - D: delegation::store::Store, + D: delegation::store::Store, V: varsig::Header, Enc: Codec + Into + TryFrom, { @@ -177,10 +171,9 @@ where now: &SystemTime, ) -> Result< Recipient>, - ReceiveError, + ReceiveError, > where - C: Clone, Enc: From + Into, arguments::Named: From, Invocation: Clone, @@ -291,7 +284,6 @@ pub enum ReceiveError< T: Resolvable, P: promise::Store, DID: Did, - C: fmt::Debug, D, S: Store, V: varsig::Header, @@ -318,7 +310,7 @@ pub enum ReceiveError< DelegationStoreError(#[source] D), #[error("delegation validation error: {0}")] - ValidationError(#[source] ValidationError), + ValidationError(#[source] ValidationError), } #[derive(Debug, Error)] diff --git a/src/invocation/payload.rs b/src/invocation/payload.rs index 3885afaa..8bf0e2a9 100644 --- a/src/invocation/payload.rs +++ b/src/invocation/payload.rs @@ -3,7 +3,10 @@ use crate::{ ability::{arguments, command::ToCommand, parse::ParseAbility}, capsule::Capsule, crypto::Nonce, - delegation::{self, condition::Condition}, //, ValidationError}, + delegation::{ + self, + policy::{predicate::Predicate, selector::SelectorError}, + }, did::{Did, Verifiable}, time::{Expired, Timestamp}, }; @@ -132,11 +135,11 @@ impl Payload { Ok(()) } - pub fn check( + pub fn check( &self, - proofs: Vec<&delegation::Payload>, + proofs: Vec<&delegation::Payload>, now: &SystemTime, - ) -> Result<(), ValidationError> + ) -> Result<(), ValidationError> where A: ToCommand + Clone, DID: Clone, @@ -175,9 +178,15 @@ impl Payload { return Err(ValidationError::CommandMismatch(proof.command.clone())); } + let ipld_args = Ipld::from(args.clone()); + for predicate in proof.policy.iter() { - if !predicate.validate(&args) { - return Err(ValidationError::FailedCondition(predicate.clone())); + if !predicate + .clone() + .run(&ipld_args) + .map_err(ValidationError::SelectorError)? + { + return Err(ValidationError::FailedPolicy(predicate.clone())); } } @@ -189,8 +198,8 @@ impl Payload { } /// Delegation validation errors. -#[derive(Debug, Clone, PartialEq, Eq, Error)] -pub enum ValidationError { +#[derive(Debug, Clone, PartialEq, Error)] +pub enum ValidationError { #[error("The subject of the delegation is invalid")] InvalidSubject, @@ -206,12 +215,15 @@ pub enum ValidationError { #[error("The command of the delegation does not match the proof: {0:?}")] CommandMismatch(String), - #[error("The delegation failed a condition: {0:?}")] - FailedCondition(C), + #[error("The delegation failed a policy predicate: {0:?}")] + FailedPolicy(Predicate), + + #[error(transparent)] + SelectorError(#[from] SelectorError), } impl Capsule for Payload { - const TAG: &'static str = "ucan/i/1.0.0-rc.1"; + const TAG: &'static str = "ucan/i@1.0.0-rc.1"; } impl, DID: Did> From> for arguments::Named { diff --git a/src/ipld/number.rs b/src/ipld/number.rs index a66f10d8..c4e7d08d 100644 --- a/src/ipld/number.rs +++ b/src/ipld/number.rs @@ -4,12 +4,15 @@ use enum_as_inner::EnumAsInner; use libipld_core::{error::SerdeError, ipld::Ipld, serde as ipld_serde}; use serde_derive::{Deserialize, Serialize}; +#[cfg(feature = "test_utils")] +use proptest::prelude::*; + /// The union of [`Ipld`] numeric types /// /// This is helpful when comparing different numeric types, such as -/// bounds checking in [`Condition`]s. +/// bounds checking in [`Predicate`]s. /// -/// [`Condition`]: crate::delegation::Condition +/// [`Predicate`]: crate::delegation::policy::predicate::Predicate #[derive(Debug, Clone, PartialEq, EnumAsInner, Serialize, Deserialize)] #[serde(untagged)] pub enum Number { @@ -56,3 +59,16 @@ impl From for Number { Number::Float(f) } } + +impl Arbitrary for Number { + type Parameters = (); + type Strategy = BoxedStrategy; + + fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy { + prop_oneof![ + any::().prop_map(Number::Float), + any::().prop_map(Number::Integer), + ] + .boxed() + } +} diff --git a/src/lib.rs b/src/lib.rs index 86efec3b..1c21c44a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -21,7 +21,6 @@ pub mod delegation; pub mod did; pub mod invocation; pub mod ipld; -//pub mod proof; pub mod reader; pub mod receipt; pub mod task; @@ -34,6 +33,3 @@ pub mod test_utils; pub use delegation::Delegation; pub use invocation::Invocation; pub use receipt::Receipt; - -// FIXME -// show pipe diff --git a/src/proof.rs b/src/proof.rs deleted file mode 100644 index de58a8ff..00000000 --- a/src/proof.rs +++ /dev/null @@ -1,12 +0,0 @@ -//! Proof chains, checking, and utilities. - -// pub mod checkable; -// pub mod error; -// pub mod parentful; -// pub mod parentless; -// pub mod parents; -// pub mod prove; -// pub mod same; - -// NOTE must remain *un*exported! -// pub(super) mod internal; diff --git a/src/proof/checkable.rs b/src/proof/checkable.rs deleted file mode 100644 index 3e6d2031..00000000 --- a/src/proof/checkable.rs +++ /dev/null @@ -1,17 +0,0 @@ -//! Define the hierarchy of an ability (or mark as not having one) - -use super::{prove::Prove, same::CheckSame}; -use crate::ability::arguments; -use libipld_core::ipld::Ipld; - -// FIXME move to Delegatbel? - -/// Plug a type into the delegation checking pipeline -pub trait Checkable: CheckSame + Sized { - /// The type of hierarchy this ability has - /// - /// The only options are [`Parentful`][super::parentful::Parentful] - /// and [`Parentless`][super::parentless::Parentless], - /// (which are the only instances of the unexported `Checker`) - type Hierarchy: CheckSame + Prove + From + PartialEq + Into>; -} diff --git a/src/proof/error.rs b/src/proof/error.rs deleted file mode 100644 index f2911c00..00000000 --- a/src/proof/error.rs +++ /dev/null @@ -1,45 +0,0 @@ -//! Standatd error types for delegation checking. - -use serde::{Deserialize, Serialize}; -use thiserror::Error; - -#[cfg(target_arch = "wasm32")] -use wasm_bindgen::prelude::*; - -/// An error for when values are unequal. -#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, Error)] -#[cfg_attr(target_arch = "wasm32", wasm_bindgen)] -#[error("unequal")] -pub struct Unequal(); - -/// A generic error for when two fields are unequal. -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Error)] -#[cfg_attr(target_arch = "wasm32", wasm_bindgen)] -pub enum OptionalFieldError { - /// A required field is missing. - /// - /// For example, when its proof has a vaue, but the target does not. - #[error("Field missing")] - Missing, - - /// A field is present but has a different value in its proof - #[error("Field value unequal")] - Unequal, -} - -impl From for OptionalFieldError { - fn from(_: Unequal) -> Self { - OptionalFieldError::Unequal - } -} - -impl TryFrom for Unequal { - type Error = OptionalFieldError; - - fn try_from(e: OptionalFieldError) -> Result { - match e { - OptionalFieldError::Unequal => Ok(Unequal {}), - _ => Err(e), - } - } -} diff --git a/src/proof/internal.rs b/src/proof/internal.rs deleted file mode 100644 index 06fd97fd..00000000 --- a/src/proof/internal.rs +++ /dev/null @@ -1,3 +0,0 @@ -// NOTE: Must not get exported -// FIXME either mark downstream as ok to be provate, OOOOOR just leave this in an internal modukle -pub trait Checker {} diff --git a/src/proof/parentful.rs b/src/proof/parentful.rs deleted file mode 100644 index a6515cc8..00000000 --- a/src/proof/parentful.rs +++ /dev/null @@ -1,189 +0,0 @@ -//! Utilities for working with abilties that *do* have a delegation hirarchy. - -use super::{ - internal::Checker, - parents::CheckParents, - prove::{Prove, Success}, - same::CheckSame, -}; -use crate::ability::arguments; -use libipld_core::{error::SerdeError, ipld::Ipld, serde as ipld_serde}; -use serde::{de::DeserializeOwned, Deserialize, Serialize}; -use thiserror::Error; - -/// The possible cases for an [ability][crate::ability]'s -/// [Delegation][crate::delegation::Delegation] chain when -/// it has parent abilities (a hierarchy). -/// -/// This type is generally not used directly, but rather is -/// called in the plumbing of the library. -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] -pub enum Parentful { - /// The "top" ability (`*`) - Any, - - /// All possible parents for the ability. - Parents(T::Parents), - - /// The (invokable) ability itself. - This(T), -} - -impl From for Parentful -where - T: CheckParents, -{ - fn from(this: T) -> Self { - Parentful::This(this) - } -} - -/// Error cases when checking proofs (including parents) -#[derive(Debug, Error, PartialEq)] -pub enum ParentfulError { - /// The `cmd` field was more powerful than the proof. - /// - /// i.e. it behaves like moving "down" the delegation chain not "up" - #[error("The `cmd` field was more powerful than the proof")] - CommandEscelation, - - /// The `args` field was more powerful than the proof. - #[error("The `args` field was more powerful than the proof: {0}")] - ArgumentEscelation(ArgErr), - - /// The parents do not prove the ability. - #[error("The parents do not prove the ability: {0}")] - InvalidProofChain(PrfErr), - - /// Comparing parents in a delegation chain failed. - /// - /// The specific comparison error is captured in the `ParErr`. - #[error("Comparing parents in a delegation chain failed: {0}")] - InvalidParents(ParErr), // FIXME seems kinda broken -- better naming at least -} - -impl From> for arguments::Named -where - arguments::Named: From + From, -{ - fn from(parentful: Parentful) -> Self { - match parentful { - Parentful::Any => arguments::Named::new(), - Parentful::Parents(parents) => parents.into(), - Parentful::This(this) => this.into(), - } - } -} - -impl From> for Ipld -where - Ipld: From, -{ - fn from(parentful: Parentful) -> Self { - parentful.into() - } -} - -impl + DeserializeOwned + CheckParents> TryFrom for Parentful -where - ::Parents: DeserializeOwned, -{ - type Error = SerdeError; - - fn try_from(ipld: Ipld) -> Result { - ipld_serde::from_ipld(ipld) - } -} - -impl CheckSame for Parentful -where - T::Parents: CheckSame, -{ - type Error = ParentfulError::Error>; // FIXME - - fn check_same(&self, proof: &Self) -> Result<(), Self::Error> { - match proof { - Parentful::Any => Ok(()), - Parentful::Parents(their_parents) => match self { - Parentful::Any => Err(ParentfulError::CommandEscelation), - Parentful::Parents(parents) => parents - .check_same(their_parents) - .map_err(ParentfulError::InvalidParents), - Parentful::This(this) => this - .check_parent(their_parents) - .map_err(ParentfulError::InvalidProofChain), - }, - Parentful::This(that) => match self { - Parentful::Any => Err(ParentfulError::CommandEscelation), - Parentful::Parents(_) => Err(ParentfulError::CommandEscelation), - Parentful::This(this) => this - .check_same(that) - .map_err(ParentfulError::ArgumentEscelation), - }, - } - } -} - -impl CheckParents for Parentful -where - T::Parents: CheckSame, -{ - type Parents = Parentful; - type ParentError = ParentfulError::Error>; - - fn check_parent(&self, proof: &Parentful) -> Result<(), Self::ParentError> { - match proof { - Parentful::Any => Ok(()), - Parentful::Parents(their_parents) => match self { - Parentful::Any => Err(ParentfulError::CommandEscelation), - Parentful::Parents(parents) => parents - .check_same(their_parents) - .map_err(ParentfulError::InvalidParents), - Parentful::This(this) => this - .check_parent(their_parents) - .map_err(ParentfulError::InvalidProofChain), - }, - Parentful::This(that) => match self { - Parentful::Any => Err(ParentfulError::CommandEscelation), - Parentful::Parents(_) => Err(ParentfulError::CommandEscelation), - Parentful::This(this) => this - .check_same(that) - .map_err(ParentfulError::ArgumentEscelation), - }, - } - } -} - -impl Checker for Parentful {} - -impl Prove for Parentful -where - T::Parents: CheckSame, -{ - type Error = ParentfulError::Error>; - - fn check(&self, proof: &Parentful) -> Result { - match proof { - Parentful::Any => Ok(Success::ProvenByAny), - Parentful::Parents(their_parents) => match self { - Parentful::Any => Err(ParentfulError::CommandEscelation), - Parentful::Parents(parents) => match parents.check_same(their_parents) { - Ok(()) => Ok(Success::Proven), - Err(e) => Err(ParentfulError::InvalidParents(e)), - }, - Parentful::This(this) => match this.check_parent(their_parents) { - Ok(()) => Ok(Success::Proven), - Err(e) => Err(ParentfulError::InvalidProofChain(e)), - }, - }, - Parentful::This(that) => match self { - Parentful::Any => Err(ParentfulError::CommandEscelation), - Parentful::Parents(_) => Err(ParentfulError::CommandEscelation), - Parentful::This(this) => match this.check_same(that) { - Ok(()) => Ok(Success::Proven), - Err(e) => Err(ParentfulError::ArgumentEscelation(e)), - }, - }, - } - } -} diff --git a/src/proof/parentless.rs b/src/proof/parentless.rs deleted file mode 100644 index c9b13264..00000000 --- a/src/proof/parentless.rs +++ /dev/null @@ -1,108 +0,0 @@ -//! Utilities for working with abilties that *don't* have a delegation hirarchy -//! -use super::{ - checkable::Checkable, - internal::Checker, - prove::{Prove, Success}, - same::CheckSame, -}; -use crate::ability::arguments; -use libipld_core::ipld::Ipld; -use serde::{Deserialize, Serialize}; - -/// The possible cases for an [ability][crate::ability]'s -/// [Delegation][crate::delegation::Delegation] chain when -/// it has no parent abilities (no hierarchy). -/// -/// This type is generally not used directly, but rather is -/// called in the plumbing of the library. -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] -pub enum Parentless { - /// The "top" ability (`*`) - Any, - - /// The (invokable) ability itself. - This(T), -} - -impl From for Parentless { - fn from(this: T) -> Self { - Parentless::This(this) - } -} - -impl>> From> for arguments::Named { - fn from(parentless: Parentless) -> Self { - match parentless { - Parentless::Any => arguments::Named::new(), - Parentless::This(this) => this.into(), - } - } -} - -// FIXME generally useful (e.g. checkiung `_/*`); move to its own module and rename? -/// Error cases when checking proofs -#[derive(Debug, Clone, PartialEq)] -pub enum ParentlessError { - /// The `cmd` field was more powerful than the proof. - /// - /// i.e. it behaves like moving "down" the delegation chain not "up" - CommandEscelation, - - /// The `args` field was more powerful than the proof - ArgumentEscelation(T::Error), -} - -// FIXME better name -/// A helper trait to indicate that a type has no parents. -/// -/// This behaves as an alias for `Checkable::>`. -pub trait NoParents {} - -impl>> Checkable for T { - type Hierarchy = Parentless; -} - -impl From> for Ipld -where - Ipld: From, -{ - fn from(parentless: Parentless) -> Self { - parentless.into() - } -} - -impl CheckSame for Parentless { - type Error = ParentlessError; - - fn check_same(&self, proof: &Self) -> Result<(), Self::Error> { - match proof { - Parentless::Any => Ok(()), - Parentless::This(that) => match self { - Parentless::Any => Err(ParentlessError::CommandEscelation), - Parentless::This(this) => this - .check_same(that) - .map_err(ParentlessError::ArgumentEscelation), - }, - } - } -} - -impl Checker for Parentless {} - -impl Prove for Parentless { - type Error = ParentlessError; - - fn check(&self, proof: &Parentless) -> Result { - match proof { - Parentless::Any => Ok(Success::ProvenByAny), - Parentless::This(that) => match self { - Parentless::Any => Ok(Success::Proven), - Parentless::This(this) => match this.check_same(that) { - Ok(()) => Ok(Success::Proven), - Err(e) => Err(ParentlessError::ArgumentEscelation(e)), - }, - }, - } - } -} diff --git a/src/proof/parents.rs b/src/proof/parents.rs deleted file mode 100644 index 5b68889d..00000000 --- a/src/proof/parents.rs +++ /dev/null @@ -1,22 +0,0 @@ -//! Check the parents in an ability hierarchy. - -use super::same::CheckSame; - -/// Check if the parents of a proof are valid. -/// -/// Note that the top ability (`*`) does not need to be handled separately, -/// as the code from [`CheckParents`] will be lifted into -/// [`Parentful`][super::parentful::Parentful], which knows -/// how to check `*`. -pub trait CheckParents: CheckSame { - /// The parents of the hierarchy. - /// - /// Note that `Self` *need not* be included in [`CheckParents::Parents`]. - type Parents; - - /// Error checking against [`CheckParents::Parents`]. - type ParentError; - - // FIXME - fn check_parent(&self, proof: &Self::Parents) -> Result<(), Self::ParentError>; -} diff --git a/src/proof/prove.rs b/src/proof/prove.rs deleted file mode 100644 index 69e0feaf..00000000 --- a/src/proof/prove.rs +++ /dev/null @@ -1,37 +0,0 @@ -//! High-level proof chain checking. - -use super::internal::Checker; - -// FIXME move to internal? - -/// An internal trait that checks based on the other traits for an ability type. -pub trait Prove: Checker { - type Error; - - // FIXME make the same as the trait name (prove) - fn check(&self, proof: &Self) -> Result; -} - -#[derive(Debug, Clone, PartialEq)] -pub enum Success { - /// Success - Proven, - - /// Special case for success by checking against `*`. - ProvenByAny, -} - -#[derive(Debug, Clone, PartialEq)] -pub enum Failure { - /// An error in the command chain. - CommandEscelation, - - /// An error in the argument chain. - ArgumentEscelation(ArgErr), - - /// An error in the proof chain. - InvalidProofChain(ChainErr), - - /// An error in the parents. - InvalidParents(ParentErr), -} diff --git a/src/proof/same.rs b/src/proof/same.rs deleted file mode 100644 index b616a01c..00000000 --- a/src/proof/same.rs +++ /dev/null @@ -1,68 +0,0 @@ -//! Check the delegation proof against another instance of the same type - -use super::error::OptionalFieldError; - -/// Trait for checking if a proof of the same type is equally or less restrictive. -/// -/// # Example -/// -/// ```rust -/// # use ucan::proof::same::CheckSame; -/// # use ucan::did; -/// # -/// struct HelloBuilder { -/// wave_at: Option, -/// } -/// -/// enum HelloError { -/// MissingWaveAt, -/// WeDontTalkTo(did::Newtype) -/// } -/// -/// impl CheckSame for HelloBuilder { -/// type Error = HelloError; -/// -/// fn check_same(&self, proof: &Self) -> Result<(), Self::Error> { -/// if self.wave_at == Some(did::Newtype::try_from("did:example:mallory".to_string()).unwrap()) { -/// return Err(HelloError::WeDontTalkTo(self.wave_at.clone().unwrap())); -/// } -/// -/// if let Some(_) = &proof.wave_at { -/// if self.wave_at != proof.wave_at { -/// return Err(HelloError::MissingWaveAt); -/// } -/// } -/// -/// Ok(()) -/// } -/// } -pub trait CheckSame { - /// Error type describing why a proof was insufficient. - type Error; - - /// Check if the proof is equally or less restrictive than the instance. - /// - /// Delegation must always attenuate. If the proof is more restrictive than the instance, - /// it has violated the delegation chain rules. - fn check_same(&self, proof: &Self) -> Result<(), Self::Error>; -} - -impl CheckSame for Option { - type Error = OptionalFieldError; - - fn check_same(&self, proof: &Self) -> Result<(), Self::Error> { - match proof { - None => Ok(()), - Some(proof_) => match self { - None => Err(OptionalFieldError::Missing), - Some(self_) => { - if self_.eq(proof_) { - Ok(()) - } else { - Err(OptionalFieldError::Unequal) - } - } - }, - } - } -} diff --git a/src/receipt/payload.rs b/src/receipt/payload.rs index ddf6582d..ace1e4d5 100644 --- a/src/receipt/payload.rs +++ b/src/receipt/payload.rs @@ -85,7 +85,7 @@ pub struct Payload { } impl Capsule for Payload { - const TAG: &'static str = "ucan/r/1.0.0-rc.1"; + const TAG: &'static str = "ucan/r@1.0.0-rc.1"; } impl Serialize for Payload From 65987cd33bc210c66a9f90be5d5a08a17804cc78 Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Mon, 4 Mar 2024 22:30:11 -0800 Subject: [PATCH 112/188] Cleanup post-spec updates --- src/ability.rs | 33 --- src/ability/command.rs | 2 +- src/ability/crud.rs | 205 ++++----------- src/ability/crud/any.rs | 139 ----------- src/ability/crud/create.rs | 16 +- src/ability/crud/destroy.rs | 203 +++++---------- src/ability/crud/error.rs | 40 --- src/ability/crud/mutate.rs | 150 ----------- src/ability/crud/parents.rs | 152 ----------- src/ability/crud/read.rs | 83 ++----- src/ability/crud/update.rs | 235 ++---------------- src/ability/js/parentful.rs | 3 - src/ability/js/parentless.rs | 2 - src/ability/msg.rs | 82 +++--- src/ability/msg/any.rs | 88 ------- src/ability/msg/receive.rs | 59 +---- src/ability/msg/send.rs | 52 ++-- src/ability/preset.rs | 132 +++++----- src/ability/ucan.rs | 1 + src/ability/ucan/batch.rs | 18 ++ src/ability/ucan/revoke.rs | 42 ++-- src/ability/wasm/run.rs | 31 +-- src/crypto/signature/envelope.rs | 115 ++++++++- src/delegation.rs | 48 +++- src/delegation/agent.rs | 2 +- src/delegation/delegable.rs | 51 ---- src/delegation/payload.rs | 2 +- src/delegation/policy.rs | 12 +- src/delegation/policy/ir.rs | 100 -------- src/delegation/policy/predicate.rs | 39 ++- src/delegation/policy/selector.rs | 31 +-- .../policy/selector/{op.rs => filter.rs} | 72 +++--- src/delegation/policy/selector/or.rs | 20 +- src/delegation/policy/selector/select.rs | 112 +++++++++ src/delegation/policy/selector/selectable.rs | 62 +++++ src/delegation/store/memory.rs | 2 +- src/delegation/store/traits.rs | 3 +- src/invocation.rs | 49 +++- src/invocation/agent.rs | 15 +- src/invocation/payload.rs | 2 +- src/invocation/promise/resolvable.rs | 19 +- src/invocation/promise/store/memory.rs | 7 +- src/invocation/promise/store/traits.rs | 14 +- src/ipld.rs | 2 + src/{delegation/policy => ipld}/collection.rs | 2 +- src/ipld/newtype.rs | 59 ----- src/lib.rs | 1 + src/proof.rs | 38 +++ src/reader.rs | 2 - src/reader/builder.rs | 37 --- src/receipt.rs | 70 +++++- 51 files changed, 971 insertions(+), 1785 deletions(-) delete mode 100644 src/ability/crud/any.rs delete mode 100644 src/ability/crud/error.rs delete mode 100644 src/ability/crud/mutate.rs delete mode 100644 src/ability/crud/parents.rs delete mode 100644 src/ability/msg/any.rs create mode 100644 src/ability/ucan/batch.rs delete mode 100644 src/delegation/delegable.rs delete mode 100644 src/delegation/policy/ir.rs rename src/delegation/policy/selector/{op.rs => filter.rs} (62%) create mode 100644 src/delegation/policy/selector/select.rs create mode 100644 src/delegation/policy/selector/selectable.rs rename src/{delegation/policy => ipld}/collection.rs (100%) create mode 100644 src/proof.rs delete mode 100644 src/reader/builder.rs diff --git a/src/ability.rs b/src/ability.rs index d582fc5c..6b2ae7cb 100644 --- a/src/ability.rs +++ b/src/ability.rs @@ -1,37 +1,4 @@ //! Abilities describe the semantics of what a UCAN is allowed to do. -//! -//! # Top Level Structure -//! -//! They always follow the same format at the top level: -//! -//! | Field | Name | Description | -//! |--------|-----------------------------|----------------------------------| -//! | `cmd` | [Command](command::Command) | Roughly a function name. Determines the shape of the `args`. | -//! | `args` | [Arguments](arguments) | Roughly the function's arguments | -//! -//! # Proof Hierarchy -//! -//! Any UCAN can be proven by the `*` ability. This has been special-cased -//! into the library, and you don't have to worry about it directly when -//! implementing a new ability. -//! -//! Most abilities have no additional parents. If they do, they follow a -//! strict hierararchy. The [CRUD hierarchy](crate::abilities::crud::Any) -//! is a good example. -//! -//! Not all abilities in the hierarchy are invocable: some abstract over -//! multiple `cmd`s (such as [`crud/*`](crate::abilities::crud::Any) for -//! all CRUD actions). This allows for flexibility in adding more abilities -//! under the same hierarchy in the future without having to reissue all of -//! your certificates. -//! -//! # Lifecycle -//! -//! All abilities start as a delegation, which can omit fields (but must -//! stay the same or add more at each delegatoion). When they are invoked, -//! all field much be present. The only exception is promises, where a -//! field may include a promise pointing at another invocation. Once fully -//! resolved ("ready"), they must be validatable against the delegation chain. pub mod pipe; pub mod ucan; diff --git a/src/ability/command.rs b/src/ability/command.rs index 2cd85c3b..040ec2ea 100644 --- a/src/ability/command.rs +++ b/src/ability/command.rs @@ -9,7 +9,7 @@ //! "aud": "did:example:456", //! "cmd": "/msg/send", // <--- This is the command //! "args": { // ┐ -//! "to": "mailto:alice@example.com", // ├─ These are determined by the command +//! "to": "mailto:alice@example.com", // ├─ The shape of the args is determined by the cmd //! "message": "Hello, World!", // │ //! } // ┘ //! "exp": 1234567890 diff --git a/src/ability/crud.rs b/src/ability/crud.rs index c9d5f777..c821d75a 100644 --- a/src/ability/crud.rs +++ b/src/ability/crud.rs @@ -37,20 +37,11 @@ //! [CRUD]: https://en.wikipedia.org/wiki/Create,_read,_update_and_delete //! [`Did`]: crate::did::Did -// mod any; -// mod mutate; -// mod parents; - pub mod create; pub mod destroy; -// pub mod error; pub mod read; pub mod update; -// pub use any::Any; -// pub use mutate::Mutate; -// pub use parents::*; - use crate::{ ability::{ arguments, @@ -60,60 +51,65 @@ use crate::{ invocation::promise::Resolvable, ipld, }; +use create::{Create, PromisedCreate}; +use destroy::{Destroy, PromisedDestroy}; use libipld_core::ipld::Ipld; +use read::{PromisedRead, Read}; +use serde::{Deserialize, Serialize}; +use update::{PromisedUpdate, Update}; #[cfg(target_arch = "wasm32")] pub mod js; -#[derive(Debug, Clone, PartialEq)] -pub enum Ready { - Create(create::Create), - Read(read::Ready), - Update(update::Ready), - Destroy(destroy::Ready), +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub enum Crud { + Create(Create), + Read(Read), + Update(Update), + Destroy(Destroy), } -#[derive(Debug, Clone, PartialEq)] -pub enum Promised { - Create(create::PromisedCreate), - Read(read::Promised), - Update(update::Promised), - Destroy(destroy::Promised), +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub enum PromisedCrud { + Create(PromisedCreate), + Read(PromisedRead), + Update(PromisedUpdate), + Destroy(PromisedDestroy), } -impl ParsePromised for Promised { - type PromisedArgsError = (); +impl ParsePromised for PromisedCrud { + type PromisedArgsError = (); // FIXME fn try_parse_promised( cmd: &str, args: arguments::Named, ) -> Result> { - match create::PromisedCreate::try_parse_promised(cmd, args.clone()) { - Ok(create) => return Ok(Promised::Create(create)), + match PromisedCreate::try_parse_promised(cmd, args.clone()) { + Ok(create) => return Ok(PromisedCrud::Create(create)), Err(ParseAbilityError::InvalidArgs(_)) => { return Err(ParseAbilityError::InvalidArgs(())) } Err(ParseAbilityError::UnknownCommand(_)) => (), } - match read::Promised::try_parse_promised(cmd, args.clone()) { - Ok(read) => return Ok(Promised::Read(read)), + match PromisedRead::try_parse_promised(cmd, args.clone()) { + Ok(read) => return Ok(PromisedCrud::Read(read)), Err(ParseAbilityError::InvalidArgs(_)) => { return Err(ParseAbilityError::InvalidArgs(())) } Err(ParseAbilityError::UnknownCommand(_)) => (), } - match update::Promised::try_parse_promised(cmd, args.clone()) { - Ok(update) => return Ok(Promised::Update(update)), + match PromisedUpdate::try_parse_promised(cmd, args.clone()) { + Ok(update) => return Ok(PromisedCrud::Update(update)), Err(ParseAbilityError::InvalidArgs(_)) => { return Err(ParseAbilityError::InvalidArgs(())) } Err(ParseAbilityError::UnknownCommand(_)) => (), } - match destroy::Promised::try_parse_promised(cmd, args) { - Ok(destroy) => return Ok(Promised::Destroy(destroy)), + match PromisedDestroy::try_parse_promised(cmd, args) { + Ok(destroy) => return Ok(PromisedCrud::Destroy(destroy)), Err(ParseAbilityError::InvalidArgs(_)) => { return Err(ParseAbilityError::InvalidArgs(())) } @@ -124,39 +120,39 @@ impl ParsePromised for Promised { } } -impl ParseAbility for Ready { +impl ParseAbility for Crud { type ArgsErr = (); fn try_parse( cmd: &str, args: arguments::Named, ) -> Result> { - match create::Create::try_parse(cmd, args.clone()) { - Ok(create) => return Ok(Ready::Create(create)), + match Create::try_parse(cmd, args.clone()) { + Ok(create) => return Ok(Crud::Create(create)), Err(ParseAbilityError::InvalidArgs(_)) => { return Err(ParseAbilityError::InvalidArgs(())); } Err(ParseAbilityError::UnknownCommand(_)) => (), } - match read::Ready::try_parse(cmd, args.clone()) { - Ok(read) => return Ok(Ready::Read(read)), + match Read::try_parse(cmd, args.clone()) { + Ok(read) => return Ok(Crud::Read(read)), Err(ParseAbilityError::InvalidArgs(_)) => { return Err(ParseAbilityError::InvalidArgs(())); } Err(ParseAbilityError::UnknownCommand(_)) => (), } - match update::Ready::try_parse(cmd, args.clone()) { - Ok(update) => return Ok(Ready::Update(update)), + match Update::try_parse(cmd, args.clone()) { + Ok(update) => return Ok(Crud::Update(update)), Err(ParseAbilityError::InvalidArgs(_)) => { return Err(ParseAbilityError::InvalidArgs(())); } Err(ParseAbilityError::UnknownCommand(_)) => (), } - match destroy::Ready::try_parse(cmd, args) { - Ok(destroy) => return Ok(Ready::Destroy(destroy)), + match Destroy::try_parse(cmd, args) { + Ok(destroy) => return Ok(Crud::Destroy(destroy)), Err(ParseAbilityError::InvalidArgs(_)) => { return Err(ParseAbilityError::InvalidArgs(())); } @@ -166,132 +162,39 @@ impl ParseAbility for Ready { Err(ParseAbilityError::UnknownCommand(cmd.into())) } } -// -// impl Checkable for Builder { -// type Hierarchy = Parentful; -// } -impl ToCommand for Ready { +impl ToCommand for Crud { fn to_command(&self) -> String { match self { - Ready::Create(create) => create.to_command(), - Ready::Read(read) => read.to_command(), - Ready::Update(update) => update.to_command(), - Ready::Destroy(destroy) => destroy.to_command(), + Crud::Create(create) => create.to_command(), + Crud::Read(read) => read.to_command(), + Crud::Update(update) => update.to_command(), + Crud::Destroy(destroy) => destroy.to_command(), } } } -impl ToCommand for Promised { +impl ToCommand for PromisedCrud { fn to_command(&self) -> String { match self { - Promised::Create(create) => create.to_command(), - Promised::Read(read) => read.to_command(), - Promised::Update(update) => update.to_command(), - Promised::Destroy(destroy) => destroy.to_command(), + PromisedCrud::Create(create) => create.to_command(), + PromisedCrud::Read(read) => read.to_command(), + PromisedCrud::Update(update) => update.to_command(), + PromisedCrud::Destroy(destroy) => destroy.to_command(), } } } - -// impl ToCommand for Builder { -// fn to_command(&self) -> String { -// match self { -// Builder::Create(create) => create.to_command(), -// Builder::Read(read) => read.to_command(), -// Builder::Update(update) => update.to_command(), -// Builder::Destroy(destroy) => destroy.to_command(), -// } -// } -// } -// -// impl CheckParents for Builder { -// type Parents = MutableParents; -// type ParentError = (); // FIXME -// -// fn check_parent(&self, parents: &MutableParents) -> Result<(), Self::ParentError> { -// match self { -// Builder::Create(create) => create.check_parent(parents.into()).map_err(|_| ()), -// Builder::Update(update) => update.check_parent(parents.into()).map_err(|_| ()), -// Builder::Destroy(destroy) => destroy.check_parent(parents.into()).map_err(|_| ()), -// Builder::Read(read) => match parents { -// MutableParents::Any(crud_any) => read.check_parent(crud_any).map_err(|_| ()), -// _ => Err(()), -// }, -// } -// } -// } -// -// impl From for arguments::Named { -// fn from(builder: Builder) -> Self { -// match builder { -// Builder::Create(create) => create.into(), -// Builder::Read(read) => read.into(), -// Builder::Update(update) => update.into(), -// Builder::Destroy(destroy) => destroy.into(), -// } -// } -// } - -// impl From for arguments::Named { -// fn from(promised: Promised) -> Self { -// match promised { -// Promised::Create(create) => create.into(), -// Promised::Read(read) => read.into(), -// Promised::Update(update) => update.into(), -// Promised::Destroy(destroy) => destroy.into(), -// } -// } -// } - -// impl From for Builder { -// fn from(ready: Ready) -> Self { -// match ready { -// Ready::Create(create) => Builder::Create(create.into()), -// Ready::Read(read) => Builder::Read(read.into()), -// Ready::Update(update) => Builder::Update(update.into()), -// Ready::Destroy(destroy) => Builder::Destroy(destroy.into()), -// } -// } -// } -// -// impl TryFrom for Ready { -// type Error = (); // FIXME -// -// fn try_from(builder: Builder) -> Result { -// match builder { -// Builder::Create(create) => create.try_into().map(Ready::Create).map_err(|_| ()), -// Builder::Read(read) => read.try_into().map(Ready::Read).map_err(|_| ()), -// Builder::Update(update) => update.try_into().map(Ready::Update).map_err(|_| ()), -// Builder::Destroy(destroy) => destroy.try_into().map(Ready::Destroy).map_err(|_| ()), -// } -// } -// } -// -// impl CheckSame for Builder { -// type Error = (); -// -// fn check_same(&self, other: &Self) -> Result<(), Self::Error> { -// match (self, other) { -// (Builder::Create(a), Builder::Create(b)) => a.check_same(b), -// (Builder::Read(a), Builder::Read(b)) => a.check_same(b), -// (Builder::Update(a), Builder::Update(b)) => a.check_same(b), -// (Builder::Destroy(a), Builder::Destroy(b)) => a.check_same(b), -// _ => Err(()), -// } -// } -// } - -impl Resolvable for Ready { - type Promised = Promised; +impl Resolvable for Crud { + type Promised = PromisedCrud; } -impl From for arguments::Named { - fn from(promised: Promised) -> Self { +impl From for arguments::Named { + fn from(promised: PromisedCrud) -> Self { match promised { - Promised::Create(create) => create.into(), - Promised::Read(read) => read.into(), - Promised::Update(update) => update.into(), - Promised::Destroy(destroy) => destroy.into(), + PromisedCrud::Create(create) => create.into(), + PromisedCrud::Read(read) => read.into(), + PromisedCrud::Update(update) => update.into(), + PromisedCrud::Destroy(destroy) => destroy.into(), } } } diff --git a/src/ability/crud/any.rs b/src/ability/crud/any.rs deleted file mode 100644 index c85d4fb2..00000000 --- a/src/ability/crud/any.rs +++ /dev/null @@ -1,139 +0,0 @@ -//! "Any" CRUD ability (superclass of all CRUD abilities) - -use crate::{ - ability::{arguments, command::Command}, - proof::{error::OptionalFieldError, parentless::NoParents, same::CheckSame}, -}; -use libipld_core::{error::SerdeError, ipld::Ipld, serde as ipld_serde}; -use serde::{Deserialize, Serialize}; -use std::path::PathBuf; - -#[cfg_attr(doc, aquamarine::aquamarine)] -/// The superclass of all other CRUD abilities. -/// -/// For example, the [`crud::Create`][super::create::Create] ability may -/// be proven by the [`crud::Any`][Any] ability in a delegation chain. -/// -/// It may not be invoked directly, but rather is used as a delegaton proof -/// for other CRUD abilities (see the diagram below). -/// -/// # Delegation Hierarchy -/// -/// The hierarchy of CRUD abilities is as follows: -/// -/// ```mermaid -/// flowchart TB -/// top("*") -/// -/// subgraph Message Abilities -/// any("crud/*") -/// -/// mutate("crud/mutate") -/// -/// subgraph Invokable -/// read("crud/read") -/// create("crud/create") -/// update("crud/update") -/// destroy("crud/destroy") -/// end -/// end -/// -/// readrun{{"invoke"}} -/// createrun{{"invoke"}} -/// updaterun{{"invoke"}} -/// destroyrun{{"invoke"}} -/// -/// top --> any -/// any --> read -.-> readrun -/// any --> mutate -/// mutate --> create -.-> createrun -/// mutate --> update -.-> updaterun -/// mutate --> destroy -.-> destroyrun -/// -/// style any stroke:orange; -/// ``` -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] -#[serde(deny_unknown_fields)] -pub struct Any { - /// A an optional path relative to the actor's root. - #[serde(default, skip_serializing_if = "Option::is_none")] - pub path: Option, -} - -impl Command for Any { - const COMMAND: &'static str = "/crud"; -} - -impl TryFrom> for Any { - type Error = (); - - fn try_from(arguments: arguments::Named) -> Result { - let mut path = None; - - for (key, value) in arguments.iter() { - match key.as_str() { - "path" => { - let some_path = match value { - Ipld::String(s) => Ok(PathBuf::from(s)), - _ => Err(()), - }?; - - path = Some(some_path); - } - _ => return Err(()), - } - } - - Ok(Any { path }) - } -} - -// FIXME pipe example - -impl NoParents for Any {} - -impl CheckSame for Any { - type Error = OptionalFieldError; - - fn check_same(&self, proof: &Self) -> Result<(), Self::Error> { - if let Some(path) = &self.path { - let proof_path = proof.path.as_ref().ok_or(OptionalFieldError::Missing)?; - - if path != proof_path { - return Err(OptionalFieldError::Unequal); - } - } - - Ok(()) - } -} - -impl TryFrom for Any { - type Error = SerdeError; - - fn try_from(ipld: Ipld) -> Result { - ipld_serde::from_ipld(ipld) - } -} - -impl From for Ipld { - fn from(builder: Any) -> Self { - builder.into() - } -} - -impl From for arguments::Named { - fn from(any: Any) -> arguments::Named { - let mut named = arguments::Named::new(); - if let Some(path) = any.path { - named.insert( - "path".into(), - path.into_os_string() - .into_string() - .expect("PathBuf should generate a valid path") - .into(), - ); - } - named - } -} diff --git a/src/ability/crud/create.rs b/src/ability/crud/create.rs index b018fe38..3051676a 100644 --- a/src/ability/crud/create.rs +++ b/src/ability/crud/create.rs @@ -1,14 +1,12 @@ //! Create new resources. -// use super::parents::MutableParents; + use crate::{ ability::{arguments, command::Command}, - // delegation::Delegable, invocation::{promise, promise::Resolves}, ipld, - // proof::{checkable::Checkable, parentful::Parentful, parents::CheckParents, same::CheckSame}, }; use libipld_core::ipld::Ipld; -use serde::Serialize; +use serde::{Deserialize, Serialize}; use std::path::PathBuf; #[cfg_attr(doc, aquamarine::aquamarine)] @@ -42,7 +40,7 @@ use std::path::PathBuf; /// /// style createready stroke:orange; /// ``` -#[derive(Debug, Clone, PartialEq, Serialize)] +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(deny_unknown_fields)] pub struct Create { /// An optional path to a sub-resource that is to be created. @@ -86,7 +84,7 @@ pub struct Create { /// /// style createpromise stroke:orange; /// ``` -#[derive(Debug, Clone, PartialEq, Serialize)] +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(deny_unknown_fields)] pub struct PromisedCreate { /// An optional path to a sub-resource that is to be created. @@ -204,10 +202,10 @@ impl promise::Resolvable for Create { } impl From for arguments::Named { - fn from(builder: Create) -> Self { + fn from(create: Create) -> Self { let mut named = arguments::Named::new(); - if let Some(path) = builder.path { + if let Some(path) = create.path { named.insert( "path".to_string(), path.into_os_string() @@ -217,7 +215,7 @@ impl From for arguments::Named { ); } - if let Some(args) = builder.args { + if let Some(args) = create.args { named.insert("args".to_string(), args.into()); } diff --git a/src/ability/crud/destroy.rs b/src/ability/crud/destroy.rs index f7a7bf41..37ef0aa1 100644 --- a/src/ability/crud/destroy.rs +++ b/src/ability/crud/destroy.rs @@ -1,26 +1,14 @@ //! Destroy a resource. -// use super::parents::MutableParents; use crate::{ ability::{arguments, command::Command}, - // delegation::Delegable, invocation::promise, ipld, - // proof::{checkable::Checkable, parentful::Parentful, parents::CheckParents, same::CheckSame}, }; use libipld_core::ipld::Ipld; -use serde::Serialize; +use serde::{Deserialize, Serialize}; use std::path::PathBuf; -/// A helper for creating lifecycle instances of `crud/create` with the correct shape. -#[derive(Debug, Clone, PartialEq, Serialize)] -#[serde(deny_unknown_fields)] -pub struct Generic { - /// An optional path to a sub-resource that is to be destroyed. - #[serde(default, skip_serializing_if = "Option::is_none")] - pub path: Option, -} - #[cfg_attr(doc, aquamarine::aquamarine)] /// The executable/dispatchable variant of the `crud/destroy` ability. /// @@ -45,21 +33,70 @@ pub struct Generic { /// end /// /// destroypromise("crud::destroy::Promised") -/// destroyready("crud::destroy::Ready") +/// destroyready("crud::destroy::Destroy") /// /// top --> any --> mutate --> destroy /// destroy -.->|invoke| destroypromise -.->|resolve| destroyready -.-> exe{{execute}} /// /// style destroyready stroke:orange; /// ``` -#[derive(Debug, Clone, PartialEq, Serialize)] +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(deny_unknown_fields)] -pub struct Ready { +pub struct Destroy { /// An optional path to a sub-resource that is to be destroyed. #[serde(default, skip_serializing_if = "Option::is_none")] pub path: Option, } +const COMMAND: &'static str = "/crud/destroy"; + +impl Command for Destroy { + const COMMAND: &'static str = COMMAND; +} + +impl From for arguments::Named { + fn from(ready: Destroy) -> Self { + let mut named = arguments::Named::new(); + + if let Some(path) = ready.path { + named.insert( + "path".to_string(), + path.into_os_string() + .into_string() + .expect("PathBuf to generate valid paths") // FIXME reasonable assumption? + .into(), + ); + } + + named + } +} + +impl TryFrom> for Destroy { + type Error = (); // FIXME + + fn try_from(args: arguments::Named) -> Result { + let mut path = None; + + for (k, ipld) in args { + match k.as_str() { + "path" => { + if let Ipld::String(s) = ipld { + path = Some(PathBuf::from(s)); + } + } + _ => return Err(()), + } + } + + Ok(Destroy { path }) + } +} + +impl promise::Resolvable for Destroy { + type Promised = PromisedDestroy; +} + #[cfg_attr(doc, aquamarine::aquamarine)] /// An invoked `crud/destroy` ability (but possibly awaiting another /// [`Invocation`][crate::invocation::Invocation]). @@ -85,22 +122,22 @@ pub struct Ready { /// end /// /// destroypromise("crud::destroy::Promised") -/// destroyready("crud::destroy::Ready") +/// destroyready("crud::destroy::Destroy") /// /// top --> any --> mutate --> destroy /// destroy -.->|invoke| destroypromise -.->|resolve| destroyready -.-> exe{{execute}} /// /// style destroypromise stroke:orange; /// ``` -#[derive(Debug, Clone, PartialEq, Serialize)] +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(deny_unknown_fields)] -pub struct Promised { +pub struct PromisedDestroy { /// An optional path to a sub-resource that is to be destroyed. #[serde(default, skip_serializing_if = "Option::is_none")] pub path: Option>, } -impl TryFrom> for Promised { +impl TryFrom> for PromisedDestroy { type Error = (); fn try_from(arguments: arguments::Named) -> Result { @@ -129,91 +166,16 @@ impl TryFrom> for Promised { } } - Ok(Promised { path }) + Ok(PromisedDestroy { path }) } } -const COMMAND: &'static str = "/crud/destroy"; - -impl Command for Ready { - const COMMAND: &'static str = COMMAND; -} - -impl Command for Promised { +impl Command for PromisedDestroy { const COMMAND: &'static str = COMMAND; } -// impl Delegable for Ready { -// type Builder = Ready; -// } - -impl TryFrom> for Ready { - type Error = (); // FIXME - - fn try_from(args: arguments::Named) -> Result { - let mut path = None; - - for (k, ipld) in args { - match k.as_str() { - "path" => { - if let Ipld::String(s) = ipld { - path = Some(PathBuf::from(s)); - } - } - _ => return Err(()), - } - } - - Ok(Ready { path }) - } -} - -// impl Checkable for Ready { -// type Hierarchy = Parentful; -// } -// -// impl CheckSame for Ready { -// type Error = (); // FIXME better error -// -// fn check_same(&self, proof: &Self) -> Result<(), Self::Error> { -// if self.path == proof.path { -// Ok(()) -// } else { -// Err(()) -// } -// } -// } -// -// impl CheckParents for Ready { -// type Parents = MutableParents; -// type ParentError = (); // FIXME -// -// fn check_parent(&self, other: &Self::Parents) -> Result<(), Self::ParentError> { -// if let Some(self_path) = &self.path { -// match other { -// MutableParents::Any(any) => { -// if let Some(proof_path) = &any.path { -// if self_path != proof_path { -// return Err(()); -// } -// } -// } -// MutableParents::Mutate(mutate) => { -// if let Some(proof_path) = &mutate.path { -// if self_path != proof_path { -// return Err(()); -// } -// } -// } -// } -// } -// -// Ok(()) -// } -// } - -impl From for arguments::Named { - fn from(promised: Promised) -> Self { +impl From for arguments::Named { + fn from(promised: PromisedDestroy) -> Self { let mut named = arguments::Named::new(); if let Some(path_res) = promised.path { @@ -227,9 +189,9 @@ impl From for arguments::Named { } } -impl From for Promised { - fn from(r: Ready) -> Promised { - Promised { +impl From for PromisedDestroy { + fn from(r: Destroy) -> PromisedDestroy { + PromisedDestroy { path: r .path .map(|inner_path| promise::PromiseOk::Fulfilled(inner_path).into()), @@ -237,12 +199,8 @@ impl From for Promised { } } -impl promise::Resolvable for Ready { - type Promised = Promised; -} - -impl From for arguments::Named { - fn from(promised: Promised) -> Self { +impl From for arguments::Named { + fn from(promised: PromisedDestroy) -> Self { let mut named = arguments::Named::new(); if let Some(path) = promised.path { @@ -252,32 +210,3 @@ impl From for arguments::Named { named } } - -impl From for Ready { - fn from(p: Promised) -> Ready { - Ready { - path: p - .path - .map(|inner_path| inner_path.try_resolve().ok()) - .flatten(), - } - } -} - -impl From for arguments::Named { - fn from(ready: Ready) -> Self { - let mut named = arguments::Named::new(); - - if let Some(path) = ready.path { - named.insert( - "path".to_string(), - path.into_os_string() - .into_string() - .expect("PathBuf to generate valid paths") // FIXME reasonable assumption? - .into(), - ); - } - - named - } -} diff --git a/src/ability/crud/error.rs b/src/ability/crud/error.rs deleted file mode 100644 index a4103586..00000000 --- a/src/ability/crud/error.rs +++ /dev/null @@ -1,40 +0,0 @@ -//! CRUD-specific errors - -use crate::{ - ability::arguments, - proof::{error::OptionalFieldError, parents::CheckParents, same::CheckSame}, -}; -use serde::{Deserialize, Serialize}; -use thiserror::Error; - -#[derive(Debug, Clone, PartialEq, Error, Serialize, Deserialize)] -pub enum ProofError { - #[error("An issue with the path field")] - Path(#[from] OptionalFieldError), - - #[error("An issue with the (inner) arguments field")] - Args(#[from] arguments::NamedError), - - #[error("Proof has `args`, but none were present on delegate")] - MissingProofArgs, -} - -/// Error cases when checking [`MutableParents`] proofs -#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Error)] -pub enum ParentError { - /// Error when comparing `crud/*` to another `crud/*`. - #[error(transparent)] - InvalidAnyProof(::Error), - - /// Error when comparing `crud/mutate` to another `crud/mutate`. - #[error(transparent)] - InvalidMutateProof(::Error), - - /// Error when comparing `crud/*` as a proof for `crud/mutate`. - #[error(transparent)] - InvalidMutateParent(::ParentError), - - /// "Expected `crud/*`, but got `crud/mutate`". - #[error("Expected `crud/*`, but got `crud/mutate`")] - CommandEscelation, -} diff --git a/src/ability/crud/mutate.rs b/src/ability/crud/mutate.rs deleted file mode 100644 index a8ca9746..00000000 --- a/src/ability/crud/mutate.rs +++ /dev/null @@ -1,150 +0,0 @@ -//! The delegation superclass for all mutable CRUD actions. - -use crate::{ - ability::{arguments, command::Command}, - proof::{ - checkable::Checkable, error::OptionalFieldError, parentful::Parentful, - parents::CheckParents, same::CheckSame, - }, -}; -use libipld_core::{error::SerdeError, ipld::Ipld, serde as ipld_serde}; -use serde::{Deserialize, Serialize}; -use std::path::PathBuf; - -#[cfg_attr(doc, aquamarine::aquamarine)] -/// The delegation superclass for all mutable CRUD actions. -/// -/// For example, the [`crud::Create`][super::create::Create] ability may -/// be proven by the [`crud::Mutate`][Mutate] ability in a delegation chain. -/// [`crud::Any`][super::Any] is a suitable proof for [`crud::Mutate`][Mutate], but not vice-versa. -/// -/// It may not be invoked directly, but rather is used as a delegaton proof -/// for other CRUD abilities (see the diagram below). -/// -/// # Delegation Hierarchy -/// -/// The hierarchy of mutable CRUD abilities is as follows: -/// -/// ```mermaid -/// flowchart TB -/// top("*") -/// -/// subgraph Message Abilities -/// any("crud/*") -/// -/// mutate("crud/mutate") -/// -/// subgraph Invokable -/// create("crud/create") -/// update("crud/update") -/// destroy("crud/destroy") -/// end -/// end -/// -/// createrun{{"invoke"}} -/// updaterun{{"invoke"}} -/// destroyrun{{"invoke"}} -/// -/// top --> any -/// any --> mutate -/// mutate --> create -.-> createrun -/// mutate --> update -.-> updaterun -/// mutate --> destroy -.-> destroyrun -/// -/// style mutate stroke:orange; -/// ``` -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] -#[serde(deny_unknown_fields)] -pub struct Mutate { - /// A an optional path relative to the actor's root. - #[serde(default, skip_serializing_if = "Option::is_none")] - pub path: Option, -} - -impl Command for Mutate { - const COMMAND: &'static str = "/crud/mutate"; -} - -impl From for Ipld { - fn from(mutate: Mutate) -> Self { - mutate.into() - } -} - -impl TryFrom for Mutate { - type Error = SerdeError; - - fn try_from(ipld: Ipld) -> Result { - ipld_serde::from_ipld(ipld) - } -} - -impl TryFrom> for Mutate { - type Error = (); - - fn try_from(args: arguments::Named) -> Result { - if let Some(Ipld::String(s)) = args.get("path") { - return Ok(Mutate { - path: Some(PathBuf::from(s)), - }); - }; - - Ok(Mutate { path: None }) - } -} - -impl Checkable for Mutate { - type Hierarchy = Parentful; -} - -impl CheckSame for Mutate { - type Error = OptionalFieldError; - - fn check_same(&self, proof: &Self) -> Result<(), Self::Error> { - if let Some(path) = &self.path { - let proof_path = proof.path.as_ref().ok_or(OptionalFieldError::Missing)?; - - if path != proof_path { - return Err(OptionalFieldError::Unequal); - } - } - - Ok(()) - } -} - -impl CheckParents for Mutate { - type Parents = super::Any; - type ParentError = OptionalFieldError; - - fn check_parent(&self, crud_any: &Self::Parents) -> Result<(), Self::ParentError> { - if let Some(path) = &self.path { - let proof_path = crud_any.path.as_ref().ok_or(OptionalFieldError::Missing)?; - - if path != proof_path { - return Err(OptionalFieldError::Unequal); - } - } - - Ok(()) - } -} - -impl From for arguments::Named { - fn from(mutate: Mutate) -> Self { - let mut args = arguments::Named::new(); - - if let Some(path) = mutate.path { - args.insert( - "path".into(), - Ipld::String( - path.into_os_string() - .into_string() - .expect("PathBuf should generate a valid path"), - ), - ); - } - - args - } -} diff --git a/src/ability/crud/parents.rs b/src/ability/crud/parents.rs deleted file mode 100644 index 18b7cceb..00000000 --- a/src/ability/crud/parents.rs +++ /dev/null @@ -1,152 +0,0 @@ -//! Flat types for parent checking. -//! -//! Types here turn recursive checking into a since union to check. -//! This only needs to handle "inner" delegation types, not the topmost `*` -//! ability, or the invocable leaves of a delegation hierarchy. - -use super::error::ParentError; -use crate::{ - ability::{ - arguments, - command::ToCommand, - parse::{ParseAbility, ParseAbilityError}, - }, - proof::{parents::CheckParents, same::CheckSame}, -}; -use libipld_core::ipld::Ipld; -use serde::{Deserialize, Serialize}; -use thiserror::Error; - -#[cfg_attr(doc, aquamarine::aquamarine)] -/// The union of mutable parents. -/// -/// This is helpful as a flat type to put in [`CheckParents::Parents`]. -/// -/// # Delegation Hierarchy -/// -/// The parents captured here are highlted in the following diagram: -/// -/// ```mermaid -/// flowchart TB -/// top("*") -/// -/// subgraph CRUD Abilities -/// any("crud/*") -/// -/// mutate("crud/mutate") -/// -/// subgraph Invokable -/// read("crud/read") -/// create("crud/create") -/// update("crud/update") -/// destroy("crud/destroy") -/// end -/// end -/// -/// readrun{{"invoke"}} -/// createrun{{"invoke"}} -/// updaterun{{"invoke"}} -/// destroyrun{{"invoke"}} -/// -/// top --> any -/// any --> read -.-> readrun -/// any --> mutate -/// mutate --> create -.-> createrun -/// mutate --> update -.-> updaterun -/// mutate --> destroy -.-> destroyrun -/// -/// style any stroke:orange; -/// style mutate stroke:orange; -/// ``` -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] -#[serde(deny_unknown_fields, untagged)] -pub enum MutableParents { - /// The `crud/*` ability. - Any(super::Any), - - /// The `crud/mutate` ability. - Mutate(super::Mutate), -} - -impl ToCommand for MutableParents { - fn to_command(&self) -> String { - match self { - MutableParents::Any(any) => any.to_command(), - MutableParents::Mutate(mutate) => mutate.to_command(), - } - } -} - -#[derive(Debug, Clone, Error)] -pub enum ParseError { - #[error("Invalid `crud/*` arguments: {0:?}")] - InvalidAnyArgs(>>::Error), - - #[error("Invalid `crud/mutate` arguments: {0:?}")] - InvalidMutateArgs(>>::Error), -} - -impl ParseAbility for MutableParents { - type ArgsErr = ParseError; - - fn try_parse( - cmd: &str, - args: arguments::Named, - ) -> Result> { - match super::Any::try_parse(cmd, args.clone()) { - Ok(any) => return Ok(MutableParents::Any(any)), - Err(ParseAbilityError::InvalidArgs(e)) => { - return Err(ParseAbilityError::InvalidArgs(ParseError::InvalidAnyArgs( - e, - ))) - } - Err(ParseAbilityError::UnknownCommand(_)) => {} - } - - match super::Any::try_parse(cmd, args.clone()) { - Ok(any) => return Ok(MutableParents::Any(any)), - Err(ParseAbilityError::InvalidArgs(e)) => { - return Err(ParseAbilityError::InvalidArgs( - ParseError::InvalidMutateArgs(e), - )) - } - Err(ParseAbilityError::UnknownCommand(_)) => {} - } - - Err(ParseAbilityError::UnknownCommand(cmd.to_string())) - } -} - -impl CheckSame for MutableParents { - type Error = ParentError; - - fn check_same(&self, proof: &Self) -> Result<(), Self::Error> { - match self { - MutableParents::Mutate(mutate) => match proof { - MutableParents::Mutate(proof_mutate) => mutate - .check_same(proof_mutate) - .map_err(ParentError::InvalidMutateProof), - - MutableParents::Any(proof_any) => mutate - .check_parent(proof_any) - .map_err(ParentError::InvalidMutateParent), - }, - - MutableParents::Any(any) => match proof { - MutableParents::Mutate(_) => Err(ParentError::CommandEscelation), - MutableParents::Any(proof_any) => any - .check_same(proof_any) - .map_err(ParentError::InvalidAnyProof), - }, - } - } -} - -impl From for arguments::Named { - fn from(parents: MutableParents) -> Self { - match parents { - MutableParents::Any(any) => any.into(), - MutableParents::Mutate(mutate) => mutate.into(), - } - } -} diff --git a/src/ability/crud/read.rs b/src/ability/crud/read.rs index db25111b..28199c49 100644 --- a/src/ability/crud/read.rs +++ b/src/ability/crud/read.rs @@ -1,19 +1,14 @@ //! Read from a resource. -// use super::any as crud; use crate::{ ability::{arguments, command::Command}, - // delegation::Delegable, invocation::promise, ipld, - // proof::{checkable::Checkable, parentful::Parentful, parents::CheckParents, same::CheckSame}, }; use libipld_core::{error::SerdeError, ipld::Ipld, serde as ipld_serde}; use serde::{Deserialize, Serialize}; use std::path::PathBuf; -// FIXME deserialize instance - #[cfg_attr(doc, aquamarine::aquamarine)] /// This ability is used to fetch messages from other actors. /// @@ -34,7 +29,7 @@ use std::path::PathBuf; /// end /// /// readpromise("crud::read::Promised") -/// readready("crud::read::Ready") +/// readready("crud::read::Read") /// /// top --> any --> read /// read -.->|invoke| readpromise -.->|resolve| readready -.-> exe{{execute}} @@ -43,7 +38,7 @@ use std::path::PathBuf; /// ``` #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(deny_unknown_fields)] -pub struct Ready { +pub struct Read { /// An optional path to a sub-resource that is to be read. #[serde(default, skip_serializing_if = "Option::is_none")] pub path: Option, @@ -76,7 +71,7 @@ pub struct Ready { /// end /// /// readpromise("crud::read::Promised") -/// readready("crud::read::Ready") +/// readready("crud::read::Read") /// /// top --> any --> read /// read -.->|invoke| readpromise -.->|resolve| readready -.-> exe{{execute}} @@ -85,7 +80,7 @@ pub struct Ready { /// ``` #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(deny_unknown_fields)] -pub struct Promised { +pub struct PromisedRead { /// An optional path to a sub-resource that is to be read. #[serde(default, skip_serializing_if = "Option::is_none")] pub path: Option>, @@ -95,7 +90,7 @@ pub struct Promised { pub args: Option>>, } -impl TryFrom> for Promised { +impl TryFrom> for PromisedRead { type Error = (); fn try_from(arguments: arguments::Named) -> Result { @@ -143,36 +138,30 @@ impl TryFrom> for Promised { } } - Ok(Promised { path, args }) + Ok(PromisedRead { path, args }) } } const COMMAND: &'static str = "/crud/read"; -impl Command for Ready { +impl Command for Read { const COMMAND: &'static str = COMMAND; } -impl Command for Promised { +impl Command for PromisedRead { const COMMAND: &'static str = COMMAND; } -// impl Delegable for Ready { -// type Builder = Ready; -// } - -// FIXME resolves vs resolvable is confusing - -impl TryFrom for Ready { - type Error = SerdeError; // FIXME +impl TryFrom for Read { + type Error = SerdeError; fn try_from(ipld: Ipld) -> Result { ipld_serde::from_ipld(ipld) } } -impl From for arguments::Named { - fn from(ready: Ready) -> Self { +impl From for arguments::Named { + fn from(ready: Read) -> Self { let mut named = arguments::Named::new(); if let Some(path) = ready.path { @@ -193,10 +182,10 @@ impl From for arguments::Named { } } -impl TryFrom> for Ready { +impl TryFrom> for Read { type Error = (); - fn try_from(arguments: arguments::Named) -> Result { + fn try_from(arguments: arguments::Named) -> Result { let mut path = None; let mut args = None; @@ -216,50 +205,16 @@ impl TryFrom> for Ready { } } - Ok(Ready { path, args }) + Ok(Read { path, args }) } } -// impl Checkable for Ready { -// type Hierarchy = Parentful; -// } -// -// impl CheckSame for Ready { -// type Error = (); // FIXME better error -// -// fn check_same(&self, proof: &Self) -> Result<(), Self::Error> { -// if self.path == proof.path { -// Ok(()) -// } else { -// Err(()) -// } -// } -// } -// -// impl CheckParents for Ready { -// type Parents = crud::Any; -// type ParentError = (); // FIXME -// -// fn check_parent(&self, other: &crud::Any) -> Result<(), Self::ParentError> { -// if let Some(self_path) = &self.path { -// // FIXME check the args, too! -// if let Some(proof_path) = &other.path { -// if self_path != proof_path { -// return Err(()); -// } -// } -// } -// -// Ok(()) -// } -// } - -impl promise::Resolvable for Ready { - type Promised = Promised; +impl promise::Resolvable for Read { + type Promised = PromisedRead; } -impl From for arguments::Named { - fn from(promised: Promised) -> Self { +impl From for arguments::Named { + fn from(promised: PromisedRead) -> Self { let mut named = arguments::Named::new(); if let Some(path_res) = promised.path { diff --git a/src/ability/crud/update.rs b/src/ability/crud/update.rs index e355fa2b..b512fa8f 100644 --- a/src/ability/crud/update.rs +++ b/src/ability/crud/update.rs @@ -1,18 +1,14 @@ //! Update existing resources. -// use super::parents::MutableParents; + use crate::{ ability::{arguments, command::Command}, - // delegation::Delegable, invocation::{promise, promise::Resolves}, ipld, - // proof::{checkable::Checkable, parentful::Parentful, parents::CheckParents, same::CheckSame}, }; use libipld_core::ipld::Ipld; -use serde::Serialize; +use serde::{Deserialize, Serialize}; use std::{collections::BTreeMap, path::PathBuf}; -// FIXME deserialize instance - #[cfg_attr(doc, aquamarine::aquamarine)] /// The executable/dispatchable variant of the `crud/create` ability. /// @@ -37,59 +33,16 @@ use std::{collections::BTreeMap, path::PathBuf}; /// end /// /// updatepromise("crud::update::Promised") -/// updateready("crud::update::Ready") +/// updateready("crud::update::Update") /// /// top --> any --> mutate --> update /// update -.->|invoke| updatepromise -.->|resolve| updateready -.-> exe{{execute}} /// /// style updateready stroke:orange; /// ``` -#[derive(Debug, Clone, PartialEq, Serialize)] -#[serde(deny_unknown_fields)] -pub struct Ready { - /// An optional path to a sub-resource that is to be updated. - #[serde(default, skip_serializing_if = "Option::is_none")] - path: Option, - - /// Optional arguments to be passed in the update. - #[serde(default, skip_serializing_if = "Option::is_none")] - args: Option>, -} - -#[cfg_attr(doc, aquamarine::aquamarine)] -/// The delegatable ability for updating existing agents. -/// -/// # Lifecycle -/// -/// The lifecycle of a `crud/create` ability is as follows: -/// -/// ```mermaid -/// flowchart LR -/// subgraph Delegations -/// top("*") -/// -/// subgraph CRUD Abilities -/// any("crud/*") -/// -/// mutate("crud/mutate") -/// -/// subgraph Invokable -/// update("crud/update") -/// end -/// end -/// end -/// -/// updatepromise("crud::update::Promised") -/// updateready("crud::update::Ready") -/// -/// top --> any --> mutate --> update -/// update -.->|invoke| updatepromise -.->|resolve| updateready -.-> exe{{execute}} -/// -/// style update stroke:orange; -/// ``` -#[derive(Debug, Clone, PartialEq, Serialize)] +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(deny_unknown_fields)] -pub struct Builder { +pub struct Update { /// An optional path to a sub-resource that is to be updated. #[serde(default, skip_serializing_if = "Option::is_none")] path: Option, @@ -124,16 +77,16 @@ pub struct Builder { /// end /// /// updatepromise("crud::update::Promised") -/// updateready("crud::update::Ready") +/// updateready("crud::update::Update") /// /// top --> any --> mutate --> update /// update -.->|invoke| updatepromise -.->|resolve| updateready -.-> exe{{execute}} /// /// style updatepromise stroke:orange; /// ``` -#[derive(Debug, Clone, PartialEq, Serialize)] +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(deny_unknown_fields)] -pub struct Promised { +pub struct PromisedUpdate { /// An optional path to a sub-resource that is to be updated. #[serde(default, skip_serializing_if = "Option::is_none")] path: Option>, @@ -145,19 +98,15 @@ pub struct Promised { const COMMAND: &'static str = "/crud/update"; -impl Command for Ready { - const COMMAND: &'static str = COMMAND; -} - -impl Command for Builder { +impl Command for Update { const COMMAND: &'static str = COMMAND; } -impl Command for Promised { +impl Command for PromisedUpdate { const COMMAND: &'static str = COMMAND; } -impl TryFrom> for Promised { +impl TryFrom> for PromisedUpdate { type Error = (); fn try_from(named: arguments::Named) -> Result { @@ -196,15 +145,11 @@ impl TryFrom> for Promised { } } - Ok(Promised { path, args }) + Ok(PromisedUpdate { path, args }) } } -// impl Delegable for Ready { -// type Builder = Builder; -// } - -impl TryFrom> for Ready { +impl TryFrom> for Update { type Error = (); fn try_from(named: arguments::Named) -> Result { @@ -231,66 +176,17 @@ impl TryFrom> for Ready { } } - Ok(Ready { path, args }) + Ok(Update { path, args }) } } -impl TryFrom> for Builder { - type Error = (); - - fn try_from(named: arguments::Named) -> Result { - let mut path = None; - let mut args = None; - - for (key, ipld) in named { - match key.as_str() { - "path" => { - if let Ipld::String(s) = ipld { - path = Some(PathBuf::from(s)); - } else { - return Err(()); - } - } - "args" => { - if let Ipld::Map(map) = ipld { - args = Some(arguments::Named(map)); - } else { - return Err(()); - } - } - _ => return Err(()), - } - } - - Ok(Builder { path, args }) - } -} - -impl From for Builder { - fn from(r: Ready) -> Self { - Builder { - path: r.path, - args: r.args, - } - } -} - -impl From for Ready { - fn from(builder: Builder) -> Self { - Ready { - path: builder.path, - args: builder.args, - } - } -} - -impl From for Ipld { - fn from(create: Ready) -> Self { +impl From for Ipld { + fn from(create: Update) -> Self { create.into() } } -impl TryFrom for Ready { +impl TryFrom for Update { type Error = (); // FIXME fn try_from(ipld: Ipld) -> Result { @@ -299,7 +195,7 @@ impl TryFrom for Ready { return Err(()); // FIXME } - Ok(Ready { + Ok(Update { path: map .get("path") .map(|ipld| (ipld::Newtype(ipld.clone())).try_into().map_err(|_| ())) @@ -316,54 +212,8 @@ impl TryFrom for Ready { } } -// impl Checkable for Builder { -// type Hierarchy = Parentful; -// } -// -// impl CheckSame for Builder { -// type Error = (); // FIXME better error -// -// fn check_same(&self, proof: &Self) -> Result<(), Self::Error> { -// if self.path == proof.path { -// Ok(()) -// } else { -// Err(()) -// } -// } -// } -// -// impl CheckParents for Builder { -// type Parents = MutableParents; -// type ParentError = (); // FIXME -// -// fn check_parent(&self, other: &Self::Parents) -> Result<(), Self::ParentError> { -// if let Some(self_path) = &self.path { -// match other { -// MutableParents::Any(any) => { -// // FIXME check the args, too! -// if let Some(proof_path) = &any.path { -// if self_path != proof_path { -// return Err(()); -// } -// } -// } -// MutableParents::Mutate(mutate) => { -// // FIXME check the args, too! -// if let Some(proof_path) = &mutate.path { -// if self_path != proof_path { -// return Err(()); -// } -// } -// } -// } -// } -// -// Ok(()) -// } -// } - -impl From for arguments::Named { - fn from(promised: Promised) -> Self { +impl From for arguments::Named { + fn from(promised: PromisedUpdate) -> Self { let mut named = arguments::Named::new(); if let Some(path_res) = promised.path { @@ -393,9 +243,9 @@ impl From for arguments::Named { } } -impl From for Promised { - fn from(r: Ready) -> Promised { - Promised { +impl From for PromisedUpdate { + fn from(r: Update) -> PromisedUpdate { + PromisedUpdate { path: r .path .map(|inner_path| promise::PromiseOk::Fulfilled(inner_path).into()), @@ -405,43 +255,12 @@ impl From for Promised { } } -impl promise::Resolvable for Ready { - type Promised = Promised; -} - -impl From for Builder { - fn from(promised: Promised) -> Self { - Builder { - path: promised.path.and_then(|p| p.try_resolve().ok()), - args: todo!(), // promised.args.and_then(|a| a.try_resolve_option()), - } - } -} - -impl From for arguments::Named { - fn from(builder: Builder) -> Self { - let mut named = arguments::Named::new(); - - if let Some(path) = builder.path { - named.insert( - "path".to_string(), - path.into_os_string() - .into_string() - .expect("PathBuf to generate valid paths") // FIXME reasonable assumption? - .into(), - ); - } - - if let Some(args) = builder.args { - named.insert("args".to_string(), args.into()); - } - - named - } +impl promise::Resolvable for Update { + type Promised = PromisedUpdate; } -impl From for arguments::Named { - fn from(promised: Promised) -> Self { +impl From for arguments::Named { + fn from(promised: PromisedUpdate) -> Self { let mut named = arguments::Named::new(); if let Some(path) = promised.path { diff --git a/src/ability/js/parentful.rs b/src/ability/js/parentful.rs index 1af4654b..16b7b689 100644 --- a/src/ability/js/parentful.rs +++ b/src/ability/js/parentful.rs @@ -13,9 +13,6 @@ use wasm_bindgen::{prelude::*, JsValue}; // FIXME rename type WithParents = Reader>; -// Promise = Promise? Ah, nope becuase we need that CID on the promise -// FIXME represent promises (for Promised) and options (for builder) - /// The configuration object that expresses an ability (with parents) from JS #[derive(Debug, Clone, PartialEq, Default)] #[wasm_bindgen(getter_with_clone)] diff --git a/src/ability/js/parentless.rs b/src/ability/js/parentless.rs index 27118b85..d9f458c7 100644 --- a/src/ability/js/parentless.rs +++ b/src/ability/js/parentless.rs @@ -22,8 +22,6 @@ pub struct ParentlessConfig { check_same: Function, } -// FIXME represent promises (for Promised) and options (for builder) - // NOTE if changed, please update this in the docs for `ParentlessArgs` below #[wasm_bindgen(typescript_custom_section)] pub const PARENTLESS_ARGS: &str = r#" diff --git a/src/ability/msg.rs b/src/ability/msg.rs index e1d90481..59ee1fed 100644 --- a/src/ability/msg.rs +++ b/src/ability/msg.rs @@ -13,92 +13,102 @@ use crate::{ ipld, }; use libipld_core::ipld::Ipld; - -// FIXME rename invokable? -#[derive(Debug, Clone, PartialEq)] -pub enum Ready { - Send(send::Ready), - Receive(receive::Receive), +use receive::{PromisedReceive, Receive}; +use send::{PromisedSend, Send}; +use serde::{Deserialize, Serialize}; + +/// A family of abilities for sending and receiving messages. +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub enum Msg { + /// The ability for sending messages. + Send(Send), + + /// The ability for receiving messages. + Receive(Receive), } -#[derive(Debug, Clone, PartialEq)] -pub enum Promised { - Send(send::Promised), - Receive(receive::Promised), +/// A promised version of the [`Msg`] ability. +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub enum PromisedMsg { + /// The promised ability for sending messages. + Send(PromisedSend), + + /// The promised ability for receiving messages. + Receive(PromisedReceive), } -impl ToCommand for Ready { +impl ToCommand for Msg { fn to_command(&self) -> String { match self { - Ready::Send(send) => send.to_command(), - Ready::Receive(receive) => receive.to_command(), + Msg::Send(send) => send.to_command(), + Msg::Receive(receive) => receive.to_command(), } } } -impl ToCommand for Promised { +impl ToCommand for PromisedMsg { fn to_command(&self) -> String { match self { - Promised::Send(send) => send.to_command(), - Promised::Receive(receive) => receive.to_command(), + PromisedMsg::Send(send) => send.to_command(), + PromisedMsg::Receive(receive) => receive.to_command(), } } } -impl ParsePromised for Promised { +impl ParsePromised for PromisedMsg { type PromisedArgsError = (); fn try_parse_promised( cmd: &str, args: arguments::Named, ) -> Result> { - if let Ok(send) = send::Promised::try_parse_promised(cmd, args.clone()) { - return Ok(Promised::Send(send)); + if let Ok(send) = PromisedSend::try_parse_promised(cmd, args.clone()) { + return Ok(PromisedMsg::Send(send)); } - if let Ok(receive) = receive::Promised::try_parse_promised(cmd, args) { - return Ok(Promised::Receive(receive)); + if let Ok(receive) = PromisedReceive::try_parse_promised(cmd, args) { + return Ok(PromisedMsg::Receive(receive)); } Err(ParseAbilityError::UnknownCommand(cmd.to_string())) } } -impl From for arguments::Named { - fn from(promised: Promised) -> Self { +impl From for arguments::Named { + fn from(promised: PromisedMsg) -> Self { match promised { - Promised::Send(send) => send.into(), - Promised::Receive(receive) => receive.into(), + PromisedMsg::Send(send) => send.into(), + PromisedMsg::Receive(receive) => receive.into(), } } } -impl Resolvable for Ready { - type Promised = Promised; +impl Resolvable for Msg { + type Promised = PromisedMsg; } -impl From for arguments::Named { - fn from(promised: Promised) -> Self { +impl From for arguments::Named { + fn from(promised: PromisedMsg) -> Self { match promised { - Promised::Send(send) => send.into(), - Promised::Receive(receive) => receive.into(), + PromisedMsg::Send(send) => send.into(), + PromisedMsg::Receive(receive) => receive.into(), } } } -impl ParseAbility for Ready { +impl ParseAbility for Msg { type ArgsErr = (); fn try_parse( cmd: &str, args: arguments::Named, ) -> Result> { - if let Ok(send) = send::Ready::try_parse(cmd, args.clone()) { - return Ok(Ready::Send(send)); + if let Ok(send) = Send::try_parse(cmd, args.clone()) { + return Ok(Msg::Send(send)); } - if let Ok(receive) = receive::Receive::try_parse(cmd, args) { - return Ok(Ready::Receive(receive)); + if let Ok(receive) = Receive::try_parse(cmd, args) { + return Ok(Msg::Receive(receive)); } Err(ParseAbilityError::UnknownCommand(cmd.to_string())) diff --git a/src/ability/msg/any.rs b/src/ability/msg/any.rs deleted file mode 100644 index 16e8eac8..00000000 --- a/src/ability/msg/any.rs +++ /dev/null @@ -1,88 +0,0 @@ -//! "Any" message ability (superclass of all message abilities) - -use crate::{ - ability::{arguments, command::Command}, - proof::{parentless::NoParents, same::CheckSame}, - url, -}; -use libipld_core::{error::SerdeError, ipld::Ipld, serde as ipld_serde}; -use serde::{Deserialize, Serialize}; -// use url::Url; - -#[cfg_attr(doc, aquamarine::aquamarine)] -/// The [`msg::Any`][Any] ability may not be invoked, but it is the superclass of -/// all other message abilities. -/// -/// For example, the [`msg::Receive`][super::receive::Receive] ability may -/// be proven by the [`msg::Any`][Any] ability in a delegation chain. -/// -/// # Delegation Hierarchy -/// -/// The hierarchy of message abilities is as follows: -/// -/// ```mermaid -/// flowchart TB -/// top("*") -/// -/// subgraph Message Abilities -/// any("msg/*") -/// -/// subgraph Invokable -/// send("msg/send") -/// rec("msg/receive") -/// end -/// end -/// -/// sendrun{{"invoke"}} -/// recrun{{"invoke"}} -/// -/// top --> any -/// any --> send -.-> sendrun -/// any --> rec -.-> recrun -/// -/// style any stroke:orange; -/// ``` -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] -#[serde(deny_unknown_fields)] -pub struct Any { - pub from: Option, -} - -impl Command for Any { - const COMMAND: &'static str = "/msg"; -} - -impl From for Ipld { - fn from(any: Any) -> Self { - any.into() - } -} - -impl TryFrom for Any { - type Error = SerdeError; - - fn try_from(ipld: Ipld) -> Result { - ipld_serde::from_ipld(ipld) - } -} - -impl NoParents for Any {} - -impl CheckSame for Any { - type Error = (); - fn check_same(&self, _proof: &Self) -> Result<(), Self::Error> { - Ok(()) - } -} - -impl From for arguments::Named { - fn from(any: Any) -> arguments::Named { - let mut args = arguments::Named::new(); - - if let Some(from) = any.from { - args.insert("from".into(), from.to_string().into()); - } - - args - } -} diff --git a/src/ability/msg/receive.rs b/src/ability/msg/receive.rs index d730da8c..a58bbed2 100644 --- a/src/ability/msg/receive.rs +++ b/src/ability/msg/receive.rs @@ -2,11 +2,8 @@ use crate::{ ability::{arguments, command::Command}, - // delegation::Delegable, invocation::promise, - ipld, - // proof::{checkable::Checkable, parentful::Parentful, parents::CheckParents, same::CheckSame}, - url, + ipld, url, }; use libipld_core::{error::SerdeError, ipld::Ipld, serde as ipld_serde}; use serde::{Deserialize, Serialize}; @@ -47,22 +44,16 @@ pub struct Receive { pub from: Option, } -// FIXME needs promisory version - const COMMAND: &'static str = "/msg/send"; impl Command for Receive { const COMMAND: &'static str = COMMAND; } -impl Command for Promised { +impl Command for PromisedReceive { const COMMAND: &'static str = COMMAND; } -// impl Delegable for Receive { -// type Builder = Receive; -// } - impl TryFrom> for Receive { type Error = (); @@ -94,34 +85,6 @@ impl From for arguments::Named { } } -// impl Checkable for Receive { -// type Hierarchy = Parentful; -// } -// -// impl CheckSame for Receive { -// type Error = (); // FIXME better error -// fn check_same(&self, proof: &Self) -> Result<(), Self::Error> { -// self.from.check_same(&proof.from).map_err(|_| ()) -// } -// } -// -// impl CheckParents for Receive { -// type Parents = super::Any; -// type ParentError = ::Error; -// -// fn check_parent(&self, proof: &Self::Parents) -> Result<(), Self::ParentError> { -// if let Some(from) = &self.from { -// if let Some(proof_from) = &proof.from { -// if &from != &proof_from { -// return Err(()); -// } -// } -// } -// -// Ok(()) -// } -// } - impl From for Ipld { fn from(receive: Receive) -> Self { receive.into() @@ -136,13 +99,13 @@ impl TryFrom for Receive { } } -#[derive(Debug, Clone, PartialEq)] -pub struct Promised { +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub struct PromisedReceive { pub from: Option>, } -impl From for arguments::Named { - fn from(promised: Promised) -> Self { +impl From for arguments::Named { + fn from(promised: PromisedReceive) -> Self { let mut args = arguments::Named::new(); if let Some(from) = promised.from { @@ -161,10 +124,10 @@ impl From for arguments::Named { } impl promise::Resolvable for Receive { - type Promised = Promised; + type Promised = PromisedReceive; } -impl TryFrom> for Promised { +impl TryFrom> for PromisedReceive { type Error = (); fn try_from(arguments: arguments::Named) -> Result { @@ -185,12 +148,12 @@ impl TryFrom> for Promised { } } - Ok(Promised { from }) + Ok(PromisedReceive { from }) } } -impl From for arguments::Named { - fn from(promised: Promised) -> Self { +impl From for arguments::Named { + fn from(promised: PromisedReceive) -> Self { let mut args = arguments::Named::new(); if let Some(from) = promised.from { diff --git a/src/ability/msg/send.rs b/src/ability/msg/send.rs index 66905c68..3329cae5 100644 --- a/src/ability/msg/send.rs +++ b/src/ability/msg/send.rs @@ -28,7 +28,7 @@ use serde::{Deserialize, Serialize}; /// end /// /// sendpromise("msg::send::Promised") -/// sendrun("msg::send::Ready") +/// sendrun("msg::send::Send") /// /// top --> any /// any --> send -.->|invoke| sendpromise -.->|resolve| sendrun -.-> exe{{execute}} @@ -37,7 +37,7 @@ use serde::{Deserialize, Serialize}; /// ``` #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(deny_unknown_fields)] -pub struct Ready { +pub struct Send { /// The recipient of the message pub to: url::Newtype, @@ -73,7 +73,7 @@ pub struct Ready { /// end /// /// sendpromise("msg::send::Promised") -/// sendrun("msg::send::Ready") +/// sendrun("msg::send::Send") /// /// top --> any /// any --> send -.->|invoke| sendpromise -.->|resolve| sendrun -.-> exe{{execute}} @@ -82,7 +82,7 @@ pub struct Ready { /// ``` #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(deny_unknown_fields)] -pub struct Promised { +pub struct PromisedSend { /// The recipient of the message pub to: promise::Resolves, @@ -96,14 +96,14 @@ pub struct Promised { pub message: promise::Resolves, } -impl promise::Resolvable for Ready { - type Promised = Promised; +impl promise::Resolvable for Send { + type Promised = PromisedSend; } -impl TryFrom> for Ready { +impl TryFrom> for Send { type Error = (); - fn try_from(named: arguments::Named) -> Result { + fn try_from(named: arguments::Named) -> Result { let mut to = None; let mut from = None; let mut message = None; @@ -130,7 +130,7 @@ impl TryFrom> for Ready { } } - Ok(Ready { + Ok(Send { to: to.ok_or(())?, from: from.ok_or(())?, message: message.ok_or(())?, @@ -138,10 +138,10 @@ impl TryFrom> for Ready { } } -impl TryFrom> for Promised { +impl TryFrom> for PromisedSend { type Error = (); - fn try_from(args: arguments::Named) -> Result { + fn try_from(args: arguments::Named) -> Result { let mut to = None; let mut from = None; let mut message = None; @@ -175,7 +175,7 @@ impl TryFrom> for Promised { } } - Ok(Promised { + Ok(PromisedSend { to: to.ok_or(())?, from: from.ok_or(())?, message: message.ok_or(())?, @@ -183,8 +183,8 @@ impl TryFrom> for Promised { } } -impl From for arguments::Named { - fn from(p: Promised) -> Self { +impl From for arguments::Named { + fn from(p: PromisedSend) -> Self { arguments::Named::from_iter([ ("to".into(), p.to.into()), ("from".into(), p.from.into()), @@ -195,17 +195,17 @@ impl From for arguments::Named { const COMMAND: &'static str = "/msg/send"; -impl Command for Ready { +impl Command for Send { const COMMAND: &'static str = COMMAND; } -impl Command for Promised { +impl Command for PromisedSend { const COMMAND: &'static str = COMMAND; } -impl From for Promised { - fn from(r: Ready) -> Self { - Promised { +impl From for PromisedSend { + fn from(r: Send) -> Self { + PromisedSend { to: promise::Resolves::from(Ok(r.to)), from: promise::Resolves::from(Ok(r.from)), message: promise::Resolves::from(Ok(r.message)), @@ -213,19 +213,19 @@ impl From for Promised { } } -impl TryFrom for Ready { - type Error = Promised; +impl TryFrom for Send { + type Error = PromisedSend; - fn try_from(p: Promised) -> Result { + fn try_from(p: PromisedSend) -> Result { match promise::Resolves::try_resolve_3(p.to, p.from, p.message) { - Ok((to, from, message)) => Ok(Ready { to, from, message }), - Err((to, from, message)) => Err(Promised { to, from, message }), + Ok((to, from, message)) => Ok(Send { to, from, message }), + Err((to, from, message)) => Err(PromisedSend { to, from, message }), } } } -impl From for arguments::Named { - fn from(p: Promised) -> Self { +impl From for arguments::Named { + fn from(p: PromisedSend) -> Self { arguments::Named::from_iter([ ("to".into(), p.to.into()), ("from".into(), p.from.into()), diff --git a/src/ability/preset.rs b/src/ability/preset.rs index 29e0510f..f3b122d8 100644 --- a/src/ability/preset.rs +++ b/src/ability/preset.rs @@ -1,4 +1,9 @@ -use super::{crud, msg, wasm}; +use super::{ + crud::{Crud, PromisedCrud}, + msg::{Msg, PromisedMsg}, + ucan::revoke::{PromisedRevoke, Revoke}, + wasm::run as wasm, +}; use crate::{ ability::{ arguments, @@ -9,128 +14,127 @@ use crate::{ ipld, }; use libipld_core::ipld::Ipld; - -#[derive(Debug, Clone, PartialEq)] //, Serialize, Deserialize)] -pub enum Ready { - // FIXME UCAN - Crud(crud::Ready), - Msg(msg::Ready), - Wasm(wasm::run::Ready), +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub enum Preset { + Crud(Crud), + Msg(Msg), + Ucan(Revoke), + Wasm(wasm::Run), } -impl ToCommand for Ready { +impl ToCommand for Preset { fn to_command(&self) -> String { match self { - Ready::Crud(ready) => ready.to_command(), - Ready::Msg(ready) => ready.to_command(), - Ready::Wasm(ready) => ready.to_command(), + Preset::Crud(crud) => crud.to_command(), + Preset::Msg(msg) => msg.to_command(), + Preset::Ucan(ucan) => ucan.to_command(), + Preset::Wasm(wasm) => wasm.to_command(), } } } + #[derive(Debug, Clone, PartialEq)] //, Serialize, Deserialize)] -pub enum Promised { - Crud(crud::Promised), - Msg(msg::Promised), - Wasm(wasm::run::Promised), +pub enum PromisedPreset { + Crud(PromisedCrud), + Msg(PromisedMsg), + Ucan(PromisedRevoke), + Wasm(wasm::PromisedRun), } -impl Resolvable for Ready { - type Promised = Promised; +impl Resolvable for Preset { + type Promised = PromisedPreset; } -impl ToCommand for Promised { +impl ToCommand for PromisedPreset { fn to_command(&self) -> String { match self { - Promised::Crud(promised) => promised.to_command(), - Promised::Msg(promised) => promised.to_command(), - Promised::Wasm(promised) => promised.to_command(), + PromisedPreset::Crud(promised) => promised.to_command(), + PromisedPreset::Msg(promised) => promised.to_command(), + PromisedPreset::Ucan(promised) => promised.to_command(), + PromisedPreset::Wasm(promised) => promised.to_command(), } } } -impl ParsePromised for Promised { +impl ParsePromised for PromisedPreset { type PromisedArgsError = (); fn try_parse_promised( cmd: &str, args: arguments::Named, ) -> Result> { - match crud::Promised::try_parse_promised(cmd, args.clone()) { - Ok(promised) => return Ok(Promised::Crud(promised)), - Err(err) => return Err(err), + match PromisedCrud::try_parse_promised(cmd, args.clone()) { + Ok(promised) => return Ok(PromisedPreset::Crud(promised)), Err(ParseAbilityError::UnknownCommand(_)) => (), + Err(err) => return Err(err), } - match msg::Promised::try_parse_promised(cmd, args.clone()) { - Ok(promised) => return Ok(Promised::Msg(promised)), - Err(err) => return Err(err), + match PromisedMsg::try_parse_promised(cmd, args.clone()) { + Ok(promised) => return Ok(PromisedPreset::Msg(promised)), Err(ParseAbilityError::UnknownCommand(_)) => (), + Err(err) => return Err(err), } - match wasm::run::Promised::try_parse_promised(cmd, args) { - Ok(promised) => return Ok(Promised::Wasm(promised)), + match wasm::PromisedRun::try_parse_promised(cmd, args.clone()) { + Ok(promised) => return Ok(PromisedPreset::Wasm(promised)), + Err(ParseAbilityError::UnknownCommand(_)) => (), Err(err) => return Err(err), + } + + match PromisedRevoke::try_parse_promised(cmd, args) { + Ok(promised) => return Ok(PromisedPreset::Ucan(promised)), Err(ParseAbilityError::UnknownCommand(_)) => (), + Err(err) => return Err(err), } Err(ParseAbilityError::UnknownCommand(cmd.to_string())) } } -impl ParseAbility for Ready { +impl ParseAbility for Preset { type ArgsErr = (); fn try_parse( cmd: &str, args: arguments::Named, ) -> Result> { - match msg::Ready::try_parse(cmd, args.clone()) { - Ok(builder) => return Ok(Ready::Msg(builder)), - Err(err) => return Err(err), + match Msg::try_parse(cmd, args.clone()) { + Ok(msg) => return Ok(Preset::Msg(msg)), Err(ParseAbilityError::UnknownCommand(_)) => (), + Err(err) => return Err(err), } - match crud::Ready::try_parse(cmd, args.clone()) { - Ok(builder) => return Ok(Ready::Crud(builder)), - Err(err) => return Err(err), + match Crud::try_parse(cmd, args.clone()) { + Ok(crud) => return Ok(Preset::Crud(crud)), Err(ParseAbilityError::UnknownCommand(_)) => (), + Err(err) => return Err(err), } - match wasm::run::Ready::try_parse(cmd, args) { - Ok(builder) => return Ok(Ready::Wasm(builder)), + match wasm::Run::try_parse(cmd, args.clone()) { + Ok(wasm) => return Ok(Preset::Wasm(wasm)), + Err(ParseAbilityError::UnknownCommand(_)) => (), Err(err) => return Err(err), + } + + match Revoke::try_parse(cmd, args) { + Ok(ucan) => return Ok(Preset::Ucan(ucan)), Err(ParseAbilityError::UnknownCommand(_)) => (), + Err(err) => return Err(err), } Err(ParseAbilityError::UnknownCommand(cmd.to_string())) } } -// impl From for arguments::Named { -// fn from(builder: Builder) -> Self { -// match builder { -// Builder::Crud(builder) => builder.into(), -// Builder::Msg(builder) => builder.into(), -// Builder::Wasm(builder) => builder.into(), -// } -// } -// } -// -// impl From for arguments::Named { -// fn from(parents: Parents) -> Self { -// match parents { -// Parents::Crud(parents) => parents.into(), -// Parents::Msg(parents) => parents.into(), -// } -// } -// } - -impl From for arguments::Named { - fn from(promised: Promised) -> Self { +impl From for arguments::Named { + fn from(promised: PromisedPreset) -> Self { match promised { - Promised::Crud(promised) => promised.into(), - Promised::Msg(promised) => promised.into(), - Promised::Wasm(promised) => promised.into(), + PromisedPreset::Crud(promised) => promised.into(), + PromisedPreset::Msg(promised) => promised.into(), + PromisedPreset::Ucan(promised) => promised.into(), + PromisedPreset::Wasm(promised) => promised.into(), } } } diff --git a/src/ability/ucan.rs b/src/ability/ucan.rs index f2302b1a..0cf8e210 100644 --- a/src/ability/ucan.rs +++ b/src/ability/ucan.rs @@ -1,3 +1,4 @@ //! Abilities for and about UCANs themselves +pub mod batch; pub mod revoke; diff --git a/src/ability/ucan/batch.rs b/src/ability/ucan/batch.rs new file mode 100644 index 00000000..e068a5f4 --- /dev/null +++ b/src/ability/ucan/batch.rs @@ -0,0 +1,18 @@ +// use crate::{crypto::varsig, delegation::Delegation, did::Did}; +// use libipld_core::{cid::Cid, codec::Codec, ipld::Ipld}; +// use std::collections::BTreeMap; +// +// #[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)] +// pub struct Batch, Enc: Codec + TryFrom + Into> { +// pub batch: Vec>, // FIXME not quite right; would be nice to include meta etc +// } +// +// pub struct Step, Enc: Codec + TryFrom + Into> { +// pub subject: DID, +// pub audience: Option, +// pub ability: A, // FIXME promise version instead? Promised version shoudl be able to promise any field +// pub cause: Option, +// pub metadata: BTreeMap, +// +// pub cap: Vec>, +// } diff --git a/src/ability/ucan/revoke.rs b/src/ability/ucan/revoke.rs index f6268fe5..4c3cd4a2 100644 --- a/src/ability/ucan/revoke.rs +++ b/src/ability/ucan/revoke.rs @@ -4,10 +4,8 @@ use crate::{ ability::{arguments, command::Command}, - // delegation::Delegable, invocation::promise, ipld, - // proof::{error::OptionalFieldError, parentless::NoParents, same::CheckSame}, }; use libipld_core::{cid::Cid, ipld::Ipld}; use serde::{Deserialize, Serialize}; @@ -15,48 +13,48 @@ use std::fmt::Debug; /// The fully resolved variant: ready to execute. #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] -pub struct Ready { +pub struct Revoke { /// The UCAN to revoke pub ucan: Cid, } const COMMAND: &'static str = "/ucan/revoke"; -impl Command for Ready { +impl Command for Revoke { const COMMAND: &'static str = COMMAND; } -impl Command for Promised { +impl Command for PromisedRevoke { const COMMAND: &'static str = COMMAND; } -impl TryFrom> for Ready { +impl TryFrom> for Revoke { type Error = (); fn try_from(arguments: arguments::Named) -> Result { let ipld: Ipld = arguments.get("ucan").ok_or(())?.clone(); let nt: ipld::cid::Newtype = ipld.try_into().map_err(|_| ())?; - Ok(Ready { ucan: nt.cid }) + Ok(Revoke { ucan: nt.cid }) } } -impl promise::Resolvable for Ready { - type Promised = Promised; +impl promise::Resolvable for Revoke { + type Promised = PromisedRevoke; } -impl From for arguments::Named { - fn from(promised: Promised) -> Self { +impl From for arguments::Named { + fn from(promised: PromisedRevoke) -> Self { arguments::Named::from_iter([("ucan".into(), promised.ucan.into())]) } } /// A variant where arguments may be [`Promise`][crate::invocation::promise]s. #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] -pub struct Promised { +pub struct PromisedRevoke { pub ucan: promise::Resolves, } -impl TryFrom> for Promised { +impl TryFrom> for PromisedRevoke { type Error = (); fn try_from(arguments: arguments::Named) -> Result { @@ -75,31 +73,31 @@ impl TryFrom> for Promised { } } - Ok(Promised { + Ok(PromisedRevoke { ucan: ucan.ok_or(())?, }) } } -impl From for Promised { - fn from(r: Ready) -> Promised { - Promised { +impl From for PromisedRevoke { + fn from(r: Revoke) -> PromisedRevoke { + PromisedRevoke { ucan: Ok(r.ucan).into(), } } } -impl From for arguments::Named { - fn from(p: Promised) -> arguments::Named { +impl From for arguments::Named { + fn from(p: PromisedRevoke) -> arguments::Named { arguments::Named::from_iter([("ucan".into(), p.ucan.into())]) } } -impl TryFrom for Ready { +impl TryFrom for Revoke { type Error = (); - fn try_from(p: Promised) -> Result { - Ok(Ready { + fn try_from(p: PromisedRevoke) -> Result { + Ok(Revoke { ucan: p.ucan.try_resolve().map_err(|_| ())?, }) } diff --git a/src/ability/wasm/run.rs b/src/ability/wasm/run.rs index 8d97d8b9..0d291389 100644 --- a/src/ability/wasm/run.rs +++ b/src/ability/wasm/run.rs @@ -13,17 +13,18 @@ use serde::{Deserialize, Serialize}; const COMMAND: &'static str = "/wasm/run"; -impl Command for Ready { +impl Command for Run { const COMMAND: &'static str = COMMAND; } -impl Command for Promised { +// FIXME autogenerate for resolvable? +impl Command for PromisedRun { const COMMAND: &'static str = COMMAND; } /// The ability to run a Wasm module on the subject's machine #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] -pub struct Ready { +pub struct Run { /// The Wasm module to run pub module: Module, @@ -34,7 +35,7 @@ pub struct Ready { pub args: Vec, } -impl TryFrom> for Ready { +impl TryFrom> for Run { type Error = (); fn try_from(named: arguments::Named) -> Result { @@ -65,7 +66,7 @@ impl TryFrom> for Ready { } } - Ok(Ready { + Ok(Run { module: module.ok_or(())?, function: function.ok_or(())?, args: args.ok_or(())?, @@ -73,19 +74,19 @@ impl TryFrom> for Ready { } } -impl promise::Resolvable for Ready { - type Promised = Promised; +impl promise::Resolvable for Run { + type Promised = PromisedRun; } /// A variant meant for linking together invocations with promises #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] -pub struct Promised { +pub struct PromisedRun { pub module: promise::Resolves, pub function: promise::Resolves, pub args: promise::Resolves>, } -impl TryFrom> for Promised { +impl TryFrom> for PromisedRun { type Error = (); fn try_from(named: arguments::Named) -> Result { @@ -108,7 +109,7 @@ impl TryFrom> for Promised { } } - Ok(Promised { + Ok(PromisedRun { module: module.ok_or(())?, function: function.ok_or(())?, args: args.ok_or(())?, @@ -116,9 +117,9 @@ impl TryFrom> for Promised { } } -impl From for Promised { - fn from(ready: Ready) -> Self { - Promised { +impl From for PromisedRun { + fn from(ready: Run) -> Self { + PromisedRun { module: promise::Resolves::from(Ok(ready.module)), function: promise::Resolves::from(Ok(ready.function)), args: promise::Resolves::from(Ok(ready @@ -130,8 +131,8 @@ impl From for Promised { } } -impl From for arguments::Named { - fn from(promised: Promised) -> Self { +impl From for arguments::Named { + fn from(promised: PromisedRun) -> Self { arguments::Named::from_iter([ ("module".into(), promised.module.into()), ("function".into(), promised.function.into()), diff --git a/src/crypto/signature/envelope.rs b/src/crypto/signature/envelope.rs index f4f03f09..69e69e9f 100644 --- a/src/crypto/signature/envelope.rs +++ b/src/crypto/signature/envelope.rs @@ -10,12 +10,13 @@ use libipld_core::{ ipld::Ipld, multihash::{Code, MultihashDigest}, }; +use serde::{Deserialize, Deserializer, Serialize, Serializer}; use signature::{SignatureEncoding, Signer}; use std::collections::BTreeMap; use thiserror::Error; /// A container associating a `payload` with its signature over it. -#[derive(Debug, Clone, PartialEq)] // , Serialize, Deserialize)] +#[derive(Debug, Clone, PartialEq)] pub struct Envelope< T: Verifiable + Capsule, DID: Did, @@ -65,10 +66,10 @@ impl< // FIXME extract into trait? pub fn varsig_encode(self, w: &mut Vec) -> Result<(), libipld_core::error::Error> where - Ipld: Encode, + Ipld: Encode + From, { let codec = self.varsig_header.codec().clone(); - let ipld: Ipld = self.into(); + let ipld = Ipld::from(self); ipld.encode(codec, w) } @@ -171,8 +172,7 @@ impl< pub fn cid(&self) -> Result where Self: Clone, - Ipld: Encode, - T: Into, + Ipld: Encode + From, { let codec = self.varsig_header.codec().clone(); let mut ipld_buffer = vec![]; @@ -187,11 +187,13 @@ impl< } impl< - T: Verifiable + Capsule + Into, + T: Verifiable + Capsule, DID: Did, V: varsig::Header, Enc: Codec + Into + TryFrom, > From> for Ipld +where + Ipld: From, { fn from(envelope: Envelope) -> Self { let ipld: Ipld = BTreeMap::from_iter([(T::TAG.into(), envelope.payload.into())]).into(); @@ -205,14 +207,76 @@ impl< } impl< - T: Verifiable + Capsule + Into, + T: TryFrom + Verifiable + Capsule, + DID: Did, + V: varsig::Header, + Enc: Codec + Into + TryFrom, + > TryFrom for Envelope +{ + type Error = FromIpldError; + + fn try_from(ipld: Ipld) -> Result { + if let Ipld::List(list) = ipld { + if let [Ipld::Bytes(sig), Ipld::List(inner)] = list.as_slice() { + if let [Ipld::Bytes(varsig_header), Ipld::Map(btree)] = inner.as_slice() { + if let (1, Some(payload)) = (btree.len(), btree.get(T::TAG.into())) { + Ok(Envelope { + payload: T::try_from(payload.clone()) + .map_err(FromIpldError::CannotParsePayload)?, + + varsig_header: V::try_from(varsig_header.as_slice()) + .map_err(|_| FromIpldError::CannotParseVarsigHeader)?, + + signature: DID::Signature::try_from(sig.as_slice()) + .map_err(|_| FromIpldError::CannotParseSignature)?, + + _phantom: std::marker::PhantomData, + }) + } else { + Err(FromIpldError::InvalidPayloadCapsule) + } + } else { + Err(FromIpldError::InvalidVarsigContainer) + } + } else { + Err(FromIpldError::InvalidSignatureContainer) + } + } else { + Err(FromIpldError::InvalidSignatureContainer) + } + } +} + +#[derive(Debug, Clone, PartialEq, Error)] +pub enum FromIpldError { + #[error("Invalid signature container")] + InvalidSignatureContainer, + + #[error("Invalid varsig container")] + InvalidVarsigContainer, + + #[error("Cannot parse payload: {0}")] + CannotParsePayload(#[from] E), + + #[error("Cannot parse varsig header")] + CannotParseVarsigHeader, + + #[error("Cannot parse signature")] + CannotParseSignature, + + #[error("Invalid payload capsule")] + InvalidPayloadCapsule, +} + +impl< + T: Verifiable + Capsule, DID: Did, V: varsig::Header, Enc: Codec + Into + TryFrom, > Encode for Envelope where Self: Clone, - Ipld: Encode, + Ipld: Encode + From, { fn encode( &self, @@ -247,3 +311,38 @@ pub enum ValidateError { #[error("Signature verification failed: {0}")] VerifyError(#[from] signature::Error), } + +impl< + T: Verifiable + Capsule, + DID: Did, + V: varsig::Header, + Enc: Codec + TryFrom + Into, + > Serialize for Envelope +{ + fn serialize(&self, serializer: S) -> std::prelude::v1::Result + where + S: Serializer, + { + self.clone().serialize(serializer) + } +} + +impl< + 'de, + T: Verifiable + Capsule + TryFrom, + DID: Did, + V: varsig::Header, + Enc: Codec + TryFrom + Into, + > Deserialize<'de> for Envelope +where + Envelope: TryFrom, + as TryFrom>::Error: std::fmt::Display, +{ + fn deserialize(deserializer: D) -> std::prelude::v1::Result + where + D: Deserializer<'de>, + { + let ipld: Ipld = Deserialize::deserialize(deserializer)?; + Ok(Envelope::try_from(ipld).map_err(serde::de::Error::custom)?) + } +} diff --git a/src/delegation.rs b/src/delegation.rs index 852b61b9..a26f2ba9 100644 --- a/src/delegation.rs +++ b/src/delegation.rs @@ -32,7 +32,8 @@ use libipld_core::{ codec::{Codec, Encode}, ipld::Ipld, }; -use policy::predicate::Predicate; +use policy::Predicate; +use serde::{Deserialize, Serialize}; use std::collections::BTreeMap; use web_time::SystemTime; @@ -162,3 +163,48 @@ impl, Enc: Codec + Into + TryFrom> signature::Envelope::try_sign(signer, varsig_header, payload).map(Delegation) } } + +impl, Enc: Codec + TryFrom + Into> TryFrom + for Delegation +where + Payload: TryFrom, +{ + type Error = , DID, V, Enc> as TryFrom>::Error; + + fn try_from(ipld: Ipld) -> Result { + signature::Envelope::try_from(ipld).map(Delegation) + } +} + +impl, Enc: Codec + TryFrom + Into> + From> for Ipld +{ + fn from(delegation: Delegation) -> Self { + delegation.0.into() + } +} + +impl, Enc: Codec + TryFrom + Into> Serialize + for Delegation +{ + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + self.0.serialize(serializer) + } +} + +impl<'de, DID: Did, V: varsig::Header, Enc: Codec + TryFrom + Into> Deserialize<'de> + for Delegation +where + Payload: TryFrom, + as TryFrom>::Error: std::fmt::Display, +{ + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + signature::Envelope::deserialize(deserializer).map(Delegation) + } +} diff --git a/src/delegation/agent.rs b/src/delegation/agent.rs index 1e399e43..e7dc133b 100644 --- a/src/delegation/agent.rs +++ b/src/delegation/agent.rs @@ -1,4 +1,4 @@ -use super::{payload::Payload, policy::predicate::Predicate, store::Store, Delegation}; +use super::{payload::Payload, policy::Predicate, store::Store, Delegation}; use crate::{ crypto::{varsig, Nonce}, did::Did, diff --git a/src/delegation/delegable.rs b/src/delegation/delegable.rs deleted file mode 100644 index 9ab7e613..00000000 --- a/src/delegation/delegable.rs +++ /dev/null @@ -1,51 +0,0 @@ -// use crate::{ -// ability::{ -// arguments, -// command::ToCommand, -// parse::{ParseAbility, ParseAbilityError}, -// }, -// proof::checkable::Checkable, -// }; -// use libipld_core::ipld::Ipld; -// -// /// A trait for types that can be delegated. -// /// -// /// Since [`Delegation`]s may omit fields (until [`Invocation`]), -// /// this trait helps associate the delegatable variant to the invocable one. -// /// -// /// [`Delegation`]: crate::delegation::Delegation -// /// [`Invocation`]: crate::invocation::Invocation -// // FIXME NOTE: don't need parse ability, because parse -> builder -> self -// // FIXME NOTE: don't need ToCommand ability, because parse -> builder -> self, or .. -> promieed -> .. -// pub trait Delegable: Sized { -// /// A delegation with some arguments filled. -// type Builder: TryInto -// + From -// + Checkable -// + ParseAbility -// + ToCommand -// + Into>; -// -// fn into_command(self) -> String { -// Self::Builder::from(self).to_command() -// } -// -// fn into_named_args(self) -> arguments::Named { -// Self::Builder::from(self).into() -// } -// -// fn try_parse_to_ready( -// command: &str, -// named: arguments::Named, -// ) -> Result::ArgsErr>> { -// let builder = Self::Builder::try_parse(command, named)?; -// builder.try_into().map_err(|err| todo!()) -// } -// -// fn try_from_named( -// named: arguments::Named, -// ) -> Result::ArgsErr>> { -// let builder = Self::Builder::try_parse("", named)?; -// builder.try_into().map_err(|err| todo!()) -// } -// } diff --git a/src/delegation/payload.rs b/src/delegation/payload.rs index 0b9c33ad..5408bdff 100644 --- a/src/delegation/payload.rs +++ b/src/delegation/payload.rs @@ -1,4 +1,4 @@ -use super::policy::predicate::Predicate; +use super::policy::Predicate; use crate::{ capsule::Capsule, crypto::Nonce, diff --git a/src/delegation/policy.rs b/src/delegation/policy.rs index 25985d64..ae88cdef 100644 --- a/src/delegation/policy.rs +++ b/src/delegation/policy.rs @@ -1,4 +1,10 @@ -pub mod collection; -pub mod ir; -pub mod predicate; +//! Policy language. +//! +//! The policy language is a simple predicate language extended with [`jq`]-style selectors. +//! +//! [`jq`]: https://stedolan.github.io/jq/ + pub mod selector; + +mod predicate; +pub use predicate::*; diff --git a/src/delegation/policy/ir.rs b/src/delegation/policy/ir.rs deleted file mode 100644 index f897fe9e..00000000 --- a/src/delegation/policy/ir.rs +++ /dev/null @@ -1,100 +0,0 @@ -//FIXME rename core -use super::{ - collection::Collection, - selector::{error::SelectorErrorReason, SelectorError}, -}; -use crate::ipld; -use libipld_core::ipld::Ipld; -use std::collections::BTreeMap; - -pub trait Resolve { - fn resolve(self, ctx: &Ipld) -> Result; -} - -impl Resolve for Ipld { - fn resolve(self, _ctx: &Ipld) -> Result { - Ok(self) - } -} - -impl Resolve for ipld::Newtype { - fn resolve(self, _ctx: &Ipld) -> Result { - Ok(self) - } -} - -impl Resolve for ipld::Number { - fn resolve(self, _ctx: &Ipld) -> Result { - Ok(self) - } -} - -impl Resolve for Collection { - fn resolve(self, _ctx: &Ipld) -> Result { - Ok(self) - } -} - -impl Resolve for String { - fn resolve(self, _ctx: &Ipld) -> Result { - Ok(self) - } -} - -pub trait TryFromIpld: Sized { - fn try_from_ipld(ipld: Ipld) -> Result; -} - -impl TryFromIpld for Ipld { - fn try_from_ipld(ipld: Ipld) -> Result { - Ok(ipld) - } -} - -impl TryFromIpld for ipld::Newtype { - fn try_from_ipld(ipld: Ipld) -> Result { - Ok(ipld::Newtype(ipld)) - } -} - -impl TryFromIpld for ipld::Number { - fn try_from_ipld(ipld: Ipld) -> Result { - match ipld { - Ipld::Integer(i) => Ok(ipld::Number::Integer(i)), - Ipld::Float(f) => Ok(ipld::Number::Float(f)), - _ => Err(SelectorErrorReason::NotANumber), - } - } -} - -impl TryFromIpld for String { - fn try_from_ipld(ipld: Ipld) -> Result { - match ipld { - Ipld::String(s) => Ok(s), - _ => Err(SelectorErrorReason::NotAString), - } - } -} - -impl TryFromIpld for Collection { - fn try_from_ipld(ipld: Ipld) -> Result { - match ipld { - Ipld::List(xs) => Ok(Collection::Array(xs.into_iter().try_fold( - vec![], - |mut acc, v| { - acc.push(TryFromIpld::try_from_ipld(v)?); - Ok(acc) - }, - )?)), - Ipld::Map(xs) => Ok(Collection::Map(xs.into_iter().try_fold( - BTreeMap::new(), - |mut map, (k, v)| { - let value = TryFromIpld::try_from_ipld(v)?; - map.insert(k, value); - Ok(map) - }, - )?)), - _ => Err(SelectorErrorReason::NotACollection), - } - } -} diff --git a/src/delegation/policy/predicate.rs b/src/delegation/policy/predicate.rs index 14e0ced3..6f9533e6 100644 --- a/src/delegation/policy/predicate.rs +++ b/src/delegation/policy/predicate.rs @@ -1,8 +1,5 @@ -use super::{ - collection::Collection, - selector::{or::SelectorOr, SelectorError}, -}; -use crate::{ability::arguments, ipld}; +use super::selector::{Select, SelectorError}; +use crate::ipld; use libipld_core::ipld::Ipld; use serde::{Deserialize, Serialize}; @@ -19,15 +16,15 @@ pub enum Predicate { False, // Comparison - Equal(SelectorOr, SelectorOr), + Equal(Select, Select), - GreaterThan(SelectorOr, SelectorOr), - GreaterThanOrEqual(SelectorOr, SelectorOr), + GreaterThan(Select, Select), + GreaterThanOrEqual(Select, Select), - LessThan(SelectorOr, SelectorOr), - LessThanOrEqual(SelectorOr, SelectorOr), + LessThan(Select, Select), + LessThanOrEqual(Select, Select), - Like(SelectorOr, SelectorOr), + Like(Select, Select), // Connectives Not(Box), @@ -35,8 +32,8 @@ pub enum Predicate { Or(Box, Box), // Collection iteration - Every(SelectorOr, Box), // ∀x ∈ xs - Some(SelectorOr, Box), // ∃x ∈ xs + Every(Select, Box), // ∀x ∈ xs + Some(Select, Box), // ∃x ∈ xs } impl Predicate { @@ -107,17 +104,17 @@ impl Arbitrary for Predicate { let leaf = prop_oneof![ Just(Predicate::True), Just(Predicate::False), - (SelectorOr::arbitrary(), SelectorOr::arbitrary()) + (Select::arbitrary(), Select::arbitrary()) .prop_map(|(lhs, rhs)| { Predicate::Equal(lhs, rhs) }), - (SelectorOr::arbitrary(), SelectorOr::arbitrary()) + (Select::arbitrary(), Select::arbitrary()) .prop_map(|(lhs, rhs)| { Predicate::GreaterThan(lhs, rhs) }), - (SelectorOr::arbitrary(), SelectorOr::arbitrary()) + (Select::arbitrary(), Select::arbitrary()) .prop_map(|(lhs, rhs)| { Predicate::GreaterThanOrEqual(lhs, rhs) }), - (SelectorOr::arbitrary(), SelectorOr::arbitrary()) + (Select::arbitrary(), Select::arbitrary()) .prop_map(|(lhs, rhs)| { Predicate::LessThan(lhs, rhs) }), - (SelectorOr::arbitrary(), SelectorOr::arbitrary()) + (Select::arbitrary(), Select::arbitrary()) .prop_map(|(lhs, rhs)| { Predicate::LessThanOrEqual(lhs, rhs) }), - (SelectorOr::arbitrary(), SelectorOr::arbitrary()) + (Select::arbitrary(), Select::arbitrary()) .prop_map(|(lhs, rhs)| { Predicate::Like(lhs, rhs) }) ]; @@ -132,9 +129,9 @@ impl Arbitrary for Predicate { let quantified = leaf.clone().prop_recursive(8, 16, 4, |inner| { prop_oneof![ - (SelectorOr::arbitrary(), inner.clone()) + (Select::arbitrary(), inner.clone()) .prop_map(|(xs, p)| { Predicate::Every(xs, Box::new(p)) }), - (SelectorOr::arbitrary(), inner.clone()) + (Select::arbitrary(), inner.clone()) .prop_map(|(xs, p)| { Predicate::Some(xs, Box::new(p)) }), ] }); diff --git a/src/delegation/policy/selector.rs b/src/delegation/policy/selector.rs index aa0fa677..4ee35a1b 100644 --- a/src/delegation/policy/selector.rs +++ b/src/delegation/policy/selector.rs @@ -1,8 +1,14 @@ -pub mod error; -pub mod op; -pub mod or; +pub mod filter; -use error::{ParseError, SelectorErrorReason}; +mod error; +mod select; +mod selectable; + +pub use error::{ParseError, SelectorErrorReason}; +pub use select::Select; +pub use selectable::Selectable; + +use filter::Filter; use nom::{ self, branch::alt, @@ -19,11 +25,11 @@ use std::{fmt, str::FromStr}; use thiserror::Error; #[derive(Debug, Clone, PartialEq)] -pub struct Selector(Vec); +pub struct Selector(pub Vec); pub fn parse(input: &str) -> IResult<&str, Selector> { - let without_this = many1(op::parse); - let with_this = preceded(char('.'), many0(op::parse)); + let without_this = many1(filter::parse); + let with_this = preceded(char('.'), many0(filter::parse)); // NOTE: must try without_this this first, to disambiguate `.field` from `.` let p = map_res(alt((without_this, with_this)), |found| { @@ -38,9 +44,9 @@ pub fn parse_this(input: &str) -> IResult<&str, Selector> { context("this", p)(input) } -pub fn parse_selector_ops(input: &str) -> IResult<&str, Vec> { - let p = many1(op::parse); - context("selector ops", p)(input) +pub fn parse_selector_ops(input: &str) -> IResult<&str, Vec> { + let p = many1(filter::parse); + context("filters", p)(input) } impl fmt::Display for Selector { @@ -93,10 +99,7 @@ pub struct SelectorError { } impl SelectorError { - pub fn from_refs( - path_refs: &Vec<&op::SelectorOp>, - reason: SelectorErrorReason, - ) -> SelectorError { + pub fn from_refs(path_refs: &Vec<&Filter>, reason: SelectorErrorReason) -> SelectorError { SelectorError { selector: Selector(path_refs.iter().map(|op| (*op).clone()).collect()), reason, diff --git a/src/delegation/policy/selector/op.rs b/src/delegation/policy/selector/filter.rs similarity index 62% rename from src/delegation/policy/selector/op.rs rename to src/delegation/policy/selector/filter.rs index 8ff8a47a..b7273bdc 100644 --- a/src/delegation/policy/selector/op.rs +++ b/src/delegation/policy/selector/filter.rs @@ -18,18 +18,18 @@ use std::{fmt, str::FromStr}; use proptest::prelude::*; #[derive(Debug, Clone, PartialEq, EnumAsInner)] -pub enum SelectorOp { - ArrayIndex(i32), // [2] - Field(String), // ["key"] (or .key) - Values, // .[] - Try(Box), // ? +pub enum Filter { + ArrayIndex(i32), // [2] + Field(String), // ["key"] (or .key) + Values, // .[] + Try(Box), // ? } -impl fmt::Display for SelectorOp { +impl fmt::Display for Filter { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { - SelectorOp::ArrayIndex(i) => write!(f, "[{}]", i), - SelectorOp::Field(k) => { + Filter::ArrayIndex(i) => write!(f, "[{}]", i), + Filter::Field(k) => { if let Some(first) = k.chars().next() { if first.is_alphabetic() && k.chars().all(char::is_alphanumeric) { write!(f, ".{}", k) @@ -40,85 +40,85 @@ impl fmt::Display for SelectorOp { write!(f, "[\"{}\"]", k) } } - SelectorOp::Values => write!(f, "[]"), - SelectorOp::Try(inner) => write!(f, "{}?", inner), + Filter::Values => write!(f, "[]"), + Filter::Try(inner) => write!(f, "{}?", inner), } } } -pub fn parse(input: &str) -> IResult<&str, SelectorOp> { +pub fn parse(input: &str) -> IResult<&str, Filter> { let p = alt((parse_try, parse_non_try)); context("selector_op", p)(input) } -pub fn parse_non_try(input: &str) -> IResult<&str, SelectorOp> { +pub fn parse_non_try(input: &str) -> IResult<&str, Filter> { let p = alt((parse_values, parse_array_index, parse_field)); context("non_try", p)(input) } -pub fn parse_try(input: &str) -> IResult<&str, SelectorOp> { - let p = map_res(terminated(parse_non_try, tag("?")), |found: SelectorOp| { - Ok::(SelectorOp::Try(Box::new(found))) +pub fn parse_try(input: &str) -> IResult<&str, Filter> { + let p = map_res(terminated(parse_non_try, tag("?")), |found: Filter| { + Ok::(Filter::Try(Box::new(found))) }); context("try", p)(input) } -pub fn parse_array_index(input: &str) -> IResult<&str, SelectorOp> { +pub fn parse_array_index(input: &str) -> IResult<&str, Filter> { let num = map_opt(tag("-"), |s: &str| { let (_rest, matched) = digit1::<&str, ()>(s).ok()?; matched.parse::().ok() }); let array_index = map_res(delimited(char('['), num, char(']')), |idx| { - Ok::(SelectorOp::ArrayIndex(idx)) + Ok::(Filter::ArrayIndex(idx)) }); context("array_index", array_index)(input) } -pub fn parse_values(input: &str) -> IResult<&str, SelectorOp> { - context("values", tag("[]"))(input).map(|(rest, _)| (rest, SelectorOp::Values)) +pub fn parse_values(input: &str) -> IResult<&str, Filter> { + context("values", tag("[]"))(input).map(|(rest, _)| (rest, Filter::Values)) } -pub fn parse_field(input: &str) -> IResult<&str, SelectorOp> { +pub fn parse_field(input: &str) -> IResult<&str, Filter> { let p = alt((parse_delim_field, parse_dot_field)); context("map_field", p)(input) } -pub fn parse_dot_field(input: &str) -> IResult<&str, SelectorOp> { +pub fn parse_dot_field(input: &str) -> IResult<&str, Filter> { let p = alt((parse_dot_alpha_field, parse_dot_underscore_field)); context("dot_field", p)(input) } -pub fn parse_dot_alpha_field(input: &str) -> IResult<&str, SelectorOp> { +pub fn parse_dot_alpha_field(input: &str) -> IResult<&str, Filter> { let p = map_res(preceded(char('.'), alphanumeric1), |found: &str| { - Ok::(SelectorOp::Field(found.to_string())) + Ok::(Filter::Field(found.to_string())) }); context("dot_field", p)(input) } -pub fn parse_dot_underscore_field(input: &str) -> IResult<&str, SelectorOp> { +pub fn parse_dot_underscore_field(input: &str) -> IResult<&str, Filter> { let p = map_res(preceded(tag("._"), alphanumeric1), |found: &str| { let key = format!("{}{}", '_', found); - Ok::(SelectorOp::Field(key)) + Ok::(Filter::Field(key)) }); context("dot_field", p)(input) } -pub fn parse_delim_field(input: &str) -> IResult<&str, SelectorOp> { +pub fn parse_delim_field(input: &str) -> IResult<&str, Filter> { let p = map_res(delimited(tag("[\""), many1(anychar), tag("\"]")), |found| { let field = String::from_iter(found); - Ok::(SelectorOp::Field(field)) + Ok::(Filter::Field(field)) }); context("delimited_field", p)(input) } -impl FromStr for SelectorOp { +impl FromStr for Filter { type Err = nom::Err; fn from_str(s: &str) -> Result { @@ -128,30 +128,30 @@ impl FromStr for SelectorOp { } } } -impl Serialize for SelectorOp { +impl Serialize for Filter { fn serialize(&self, serializer: S) -> Result { self.to_string().serialize(serializer) } } -impl<'de> Deserialize<'de> for SelectorOp { +impl<'de> Deserialize<'de> for Filter { fn deserialize>(deserializer: D) -> Result { let s = String::deserialize(deserializer)?; - SelectorOp::from_str(&s).map_err(|e| serde::de::Error::custom(e.to_string())) + Filter::from_str(&s).map_err(|e| serde::de::Error::custom(e.to_string())) } } #[cfg(feature = "test_utils")] -impl Arbitrary for SelectorOp { +impl Arbitrary for Filter { type Parameters = (); type Strategy = BoxedStrategy; fn arbitrary_with(_params: Self::Parameters) -> Self::Strategy { prop_oneof![ - i32::arbitrary().prop_map(|i| SelectorOp::ArrayIndex(i)), - String::arbitrary().prop_map(SelectorOp::Field), - Just(SelectorOp::Values), - // FIXME prop_recursive::lazy(|_| { SelectorOp::arbitrary_with(()).prop_map(SelectorOp::Try) }), + i32::arbitrary().prop_map(|i| Filter::ArrayIndex(i)), + String::arbitrary().prop_map(Filter::Field), + Just(Filter::Values), + // FIXME prop_recursive::lazy(|_| { Filter::arbitrary_with(()).prop_map(Filter::Try) }), ] .boxed() } diff --git a/src/delegation/policy/selector/or.rs b/src/delegation/policy/selector/or.rs index 74cf2699..f94bc514 100644 --- a/src/delegation/policy/selector/or.rs +++ b/src/delegation/policy/selector/or.rs @@ -1,5 +1,5 @@ use super::{error::SelectorErrorReason, op::SelectorOp, SelectorError}; -use crate::delegation::policy::ir::TryFromIpld; +use crate::delegation::policy::ir::Selectable; use libipld_core::ipld::Ipld; use serde::{Deserialize, Serialize}; @@ -7,16 +7,16 @@ use serde::{Deserialize, Serialize}; use proptest::prelude::*; #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] -pub enum SelectorOr { +pub enum Select { Get(Vec), Pure(T), } -impl SelectorOr { +impl Select { pub fn resolve(self, ctx: &Ipld) -> Result { match self { - SelectorOr::Pure(inner) => Ok(inner), - SelectorOr::Get(ops) => { + Select::Pure(inner) => Ok(inner), + Select::Get(ops) => { ops.iter() .try_fold((ctx.clone(), vec![]), |(ipld, mut seen_ops), op| { seen_ops.push(op); @@ -24,7 +24,7 @@ impl SelectorOr { match op { SelectorOp::Try(inner) => { let op: SelectorOp = *inner.clone(); - let ipld: Ipld = SelectorOr::Get::(vec![op]) + let ipld: Ipld = Select::Get::(vec![op]) .resolve(ctx) .unwrap_or(Ipld::Null); @@ -90,7 +90,7 @@ impl SelectorOr { } }) .and_then(|(ipld, ref path)| { - T::try_from_ipld(ipld).map_err(|e| SelectorError::from_refs(path, e)) + T::try_select(ipld).map_err(|e| SelectorError::from_refs(path, e)) }) } } @@ -98,15 +98,15 @@ impl SelectorOr { } #[cfg(feature = "test_utils")] -impl Arbitrary for SelectorOr { +impl Arbitrary for Select { type Parameters = T::Parameters; type Strategy = BoxedStrategy; fn arbitrary_with(t_params: Self::Parameters) -> Self::Strategy { prop_oneof![ - T::arbitrary_with(t_params).prop_map(SelectorOr::Pure), + T::arbitrary_with(t_params).prop_map(Select::Pure), // FIXME add params that make this actually correspond to data - prop::collection::vec(SelectorOp::arbitrary(), 1..10).prop_map(SelectorOr::Get), + prop::collection::vec(SelectorOp::arbitrary(), 1..10).prop_map(Select::Get), ] .boxed() } diff --git a/src/delegation/policy/selector/select.rs b/src/delegation/policy/selector/select.rs new file mode 100644 index 00000000..e6064911 --- /dev/null +++ b/src/delegation/policy/selector/select.rs @@ -0,0 +1,112 @@ +use super::{error::SelectorErrorReason, filter::Filter, Selectable, SelectorError}; +use libipld_core::ipld::Ipld; +use serde::{Deserialize, Serialize}; + +#[cfg(feature = "test_utils")] +use proptest::prelude::*; + +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub enum Select { + Get(Vec), + Pure(T), +} + +impl Select { + pub fn resolve(self, ctx: &Ipld) -> Result { + match self { + Select::Pure(inner) => Ok(inner), + Select::Get(ops) => { + ops.iter() + .try_fold((ctx.clone(), vec![]), |(ipld, mut seen_ops), op| { + seen_ops.push(op); + + match op { + Filter::Try(inner) => { + let op: Filter = *inner.clone(); + let ipld: Ipld = Select::Get::(vec![op]) + .resolve(ctx) + .unwrap_or(Ipld::Null); + + Ok((ipld, seen_ops)) + } + Filter::ArrayIndex(i) => { + let result = { + match ipld { + Ipld::List(xs) => { + if i.abs() as usize > xs.len() { + return Err(SelectorError::from_refs( + &seen_ops, + SelectorErrorReason::IndexOutOfBounds, + )); + }; + + xs.get((xs.len() as i32 + *i) as usize) + .ok_or(SelectorError::from_refs( + &seen_ops, + SelectorErrorReason::IndexOutOfBounds, + )) + .cloned() + } + // FIXME behaviour on maps? type error + _ => Err(SelectorError::from_refs( + &seen_ops, + SelectorErrorReason::NotAList, + )), + } + }; + + Ok((result?, seen_ops)) + } + Filter::Field(k) => { + let result = match ipld { + Ipld::Map(xs) => xs + .get(k) + .ok_or(SelectorError::from_refs( + &seen_ops, + SelectorErrorReason::KeyNotFound, + )) + .cloned(), + _ => Err(SelectorError::from_refs( + &seen_ops, + SelectorErrorReason::NotAMap, + )), + }; + + Ok((result?, seen_ops)) + } + Filter::Values => { + let result = match ipld { + Ipld::List(xs) => Ok(Ipld::List(xs)), + Ipld::Map(xs) => Ok(Ipld::List(xs.values().cloned().collect())), + _ => Err(SelectorError::from_refs( + &seen_ops, + SelectorErrorReason::NotACollection, + )), + }; + + Ok((result?, seen_ops)) + } + } + }) + .and_then(|(ipld, ref path)| { + T::try_select(ipld).map_err(|e| SelectorError::from_refs(path, e)) + }) + } + } + } +} + +#[cfg(feature = "test_utils")] +impl Arbitrary for Select { + type Parameters = T::Parameters; + type Strategy = BoxedStrategy; + + fn arbitrary_with(t_params: Self::Parameters) -> Self::Strategy { + prop_oneof![ + T::arbitrary_with(t_params).prop_map(Select::Pure), + // FIXME add params that make this actually correspond to data + prop::collection::vec(Filter::arbitrary(), 1..10).prop_map(Select::Get), + ] + .boxed() + } +} diff --git a/src/delegation/policy/selector/selectable.rs b/src/delegation/policy/selector/selectable.rs new file mode 100644 index 00000000..9fa22186 --- /dev/null +++ b/src/delegation/policy/selector/selectable.rs @@ -0,0 +1,62 @@ +use super::error::SelectorErrorReason; +use crate::ipld; +use libipld_core::ipld::Ipld; +use std::collections::BTreeMap; + +pub trait Selectable: Sized { + fn try_select(ipld: Ipld) -> Result; +} + +impl Selectable for Ipld { + fn try_select(ipld: Ipld) -> Result { + Ok(ipld) + } +} + +impl Selectable for ipld::Newtype { + fn try_select(ipld: Ipld) -> Result { + Ok(ipld::Newtype(ipld)) + } +} + +impl Selectable for ipld::Number { + fn try_select(ipld: Ipld) -> Result { + match ipld { + Ipld::Integer(i) => Ok(ipld::Number::Integer(i)), + Ipld::Float(f) => Ok(ipld::Number::Float(f)), + _ => Err(SelectorErrorReason::NotANumber), + } + } +} + +impl Selectable for String { + fn try_select(ipld: Ipld) -> Result { + match ipld { + Ipld::String(s) => Ok(s), + _ => Err(SelectorErrorReason::NotAString), + } + } +} + +impl Selectable for ipld::Collection { + fn try_select(ipld: Ipld) -> Result { + match ipld { + Ipld::List(xs) => Ok(ipld::Collection::Array(xs.into_iter().try_fold( + vec![], + |mut acc, v| { + acc.push(Selectable::try_select(v)?); + Ok(acc) + }, + )?)), + Ipld::Map(xs) => Ok(ipld::Collection::Map(xs.into_iter().try_fold( + BTreeMap::new(), + |mut map, (k, v)| { + let value = Selectable::try_select(v)?; + map.insert(k, value); + Ok(map) + }, + )?)), + _ => Err(SelectorErrorReason::NotACollection), + } + } +} diff --git a/src/delegation/store/memory.rs b/src/delegation/store/memory.rs index 130043b2..34a4c5c5 100644 --- a/src/delegation/store/memory.rs +++ b/src/delegation/store/memory.rs @@ -1,7 +1,7 @@ use super::Store; use crate::{ crypto::varsig, - delegation::{policy::predicate::Predicate, Delegation}, + delegation::{policy::Predicate, Delegation}, did::Did, }; use libipld_core::{cid::Cid, codec::Codec}; diff --git a/src/delegation/store/traits.rs b/src/delegation/store/traits.rs index a474fdc1..364c0093 100644 --- a/src/delegation/store/traits.rs +++ b/src/delegation/store/traits.rs @@ -1,6 +1,6 @@ use crate::{ crypto::varsig, - delegation::{policy::predicate::Predicate, Delegation}, + delegation::{policy::Predicate, Delegation}, did::Did, }; use libipld_core::{cid::Cid, codec::Codec}; @@ -8,7 +8,6 @@ use nonempty::NonEmpty; use std::fmt::Debug; use web_time::SystemTime; -// NOTE the T here is the builder... FIXME add one layer up and call T::Builder? May be confusing? pub trait Store, Enc: Codec + TryFrom + Into> { type DelegationStoreError: Debug; diff --git a/src/invocation.rs b/src/invocation.rs index a878bebc..aeba3097 100644 --- a/src/invocation.rs +++ b/src/invocation.rs @@ -32,6 +32,7 @@ use libipld_core::{ codec::{Codec, Encode}, ipld::Ipld, }; +use serde::{Deserialize, Serialize}; use web_time::SystemTime; /// The complete, signed [`invocation::Payload`][Payload]. @@ -56,14 +57,14 @@ pub struct Invocation< /// A variant of [`Invocation`] that has the abilties and DIDs from this library pre-filled. pub type Preset = Invocation< - ability::preset::Ready, + ability::preset::Preset, did::preset::Verifier, varsig::header::Preset, varsig::encoding::Preset, >; pub type PresetPromised = Invocation< - ability::preset::Promised, + ability::preset::Preset, did::preset::Verifier, varsig::header::Preset, varsig::encoding::Preset, @@ -180,15 +181,47 @@ impl, Enc: Codec + TryFrom + Into> } } -impl, Enc: Codec + TryFrom + Into> - Invocation +impl, Enc: Codec + TryFrom + Into> + From> for Ipld { + fn from(invocation: Invocation) -> Self { + invocation.0.into() + } } -impl, Enc: Codec + TryFrom + Into> - From> for Ipld +impl, Enc: Codec + TryFrom + Into> TryFrom + for Invocation +where + Payload: TryFrom, { - fn from(invocation: Invocation) -> Self { - invocation.0.into() + type Error = , DID, V, Enc> as TryFrom>::Error; + + fn try_from(ipld: Ipld) -> Result { + signature::Envelope::try_from(ipld).map(Invocation) + } +} + +impl, Enc: Codec + TryFrom + Into> Serialize + for Invocation +{ + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + self.0.serialize(serializer) + } +} + +impl<'de, A, DID: Did, V: varsig::Header, Enc: Codec + TryFrom + Into> + Deserialize<'de> for Invocation +where + Payload: TryFrom, + as TryFrom>::Error: std::fmt::Display, +{ + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + signature::Envelope::deserialize(deserializer).map(Invocation) } } diff --git a/src/invocation/agent.rs b/src/invocation/agent.rs index 164faa54..71bfa770 100644 --- a/src/invocation/agent.rs +++ b/src/invocation/agent.rs @@ -5,12 +5,11 @@ use super::{ Invocation, }; use crate::{ - ability::{arguments, parse::ParseAbilityError, ucan}, + ability::{arguments, parse::ParseAbilityError, ucan::revoke::Revoke}, crypto::{signature, varsig, Nonce}, delegation, did::Did, invocation::promise, - // proof::prove::Prove, time::Timestamp, }; use libipld_core::{ @@ -37,10 +36,16 @@ pub struct Agent< V: varsig::Header, Enc: Codec + Into + TryFrom, > { + /// The agent's [`DID`]. pub did: &'a DID, + /// A [`delegation::Store`][delegation::store::Store]. pub delegation_store: &'a mut D, + + /// A [`Store`][Store] for the agent's [`Invocation`]s. pub invocation_store: &'a mut S, + + /// A [`promise::Store`] for the agent's unresolved promises. pub unresolved_promise_index: &'a mut P, signer: &'a ::Signer, @@ -196,7 +201,7 @@ where let waiting_on: BTreeSet = T::get_all_pending(cant_resolve.promised); self.unresolved_promise_index - .put( + .put_waiting( promised.cid()?, waiting_on.into_iter().collect::>(), ) @@ -237,9 +242,9 @@ where // FIXME return type ) -> Result, ()> where - T: From, + T: From, { - let ability: T = ucan::revoke::Ready { ucan: cid.clone() }.into(); + let ability: T = Revoke { ucan: cid.clone() }.into(); let proofs = if &subject == self.did { vec![] } else { diff --git a/src/invocation/payload.rs b/src/invocation/payload.rs index 8bf0e2a9..6293e8f2 100644 --- a/src/invocation/payload.rs +++ b/src/invocation/payload.rs @@ -5,7 +5,7 @@ use crate::{ crypto::Nonce, delegation::{ self, - policy::{predicate::Predicate, selector::SelectorError}, + policy::{selector::SelectorError, Predicate}, }, did::{Did, Verifiable}, time::{Expired, Timestamp}, diff --git a/src/invocation/promise/resolvable.rs b/src/invocation/promise/resolvable.rs index e9b351ad..c65c70da 100644 --- a/src/invocation/promise/resolvable.rs +++ b/src/invocation/promise/resolvable.rs @@ -2,7 +2,7 @@ use crate::{ ability::{ arguments, command::ToCommand, - parse::{ParseAbility, ParseAbilityError, ParsePromised}, + parse::{ParseAbility, ParsePromised}, }, invocation::promise::Pending, ipld, @@ -11,9 +11,6 @@ use libipld_core::{cid::Cid, ipld::Ipld}; use std::{collections::BTreeSet, fmt}; use thiserror::Error; -// FIXME rename "Unresolved" -// FIXME better name - /// A trait for [`Delegable`]s that can be deferred (by promises). /// /// FIXME exmaples @@ -29,18 +26,6 @@ pub trait Resolvable: Sized + ParseAbility + ToCommand { + ParsePromised // TryFrom> + Into>; - // fn into_promised(self) -> Self::Promised - // where - // ::PromisedArgsError: fmt::Debug, - // { - // // FIXME In no way efficient... override where possible, or just cut the impl - // let cmd = &builder.to_command(); - // let named_ipld: arguments::Named = builder.into(); - // let promised_ipld: arguments::Named = named_ipld.into(); - // ::Promised::try_parse_promised(cmd, promised_ipld) - // .expect("promise to always be possible from a ready ability") - // } - /// Attempt to resolve the [`Self::Promised`]. fn try_resolve(promised: Self::Promised) -> Result> where @@ -102,6 +87,6 @@ pub enum ResolveError { #[error("The promise is still has arguments waiting to be resolved")] StillWaiting(Pending), - #[error("The resolved promise was unable to reify a Builder")] + #[error("The resolved promise was unable to reify an ability from IPLD")] ConversionError, } diff --git a/src/invocation/promise/store/memory.rs b/src/invocation/promise/store/memory.rs index 43ea8ee4..3a221e41 100644 --- a/src/invocation/promise/store/memory.rs +++ b/src/invocation/promise/store/memory.rs @@ -14,7 +14,7 @@ pub struct MemoryStore { impl Store for MemoryStore { type PromiseStoreError = Infallible; - fn put( + fn put_waiting( &mut self, invocation: Cid, waiting_on: Vec, @@ -25,7 +25,10 @@ impl Store for MemoryStore { Ok(()) } - fn get(&self, waiting_on: &mut Vec) -> Result, Self::PromiseStoreError> { + fn get_waiting( + &self, + waiting_on: &mut Vec, + ) -> Result, Self::PromiseStoreError> { Ok(match waiting_on.pop() { None => BTreeSet::new(), Some(first) => waiting_on diff --git a/src/invocation/promise/store/traits.rs b/src/invocation/promise/store/traits.rs index 0bee796f..02d80686 100644 --- a/src/invocation/promise/store/traits.rs +++ b/src/invocation/promise/store/traits.rs @@ -5,10 +5,14 @@ use std::collections::BTreeSet; pub trait Store { type PromiseStoreError; - // NOTE put_waiting - fn put(&mut self, invocation: Cid, waiting_on: Vec) - -> Result<(), Self::PromiseStoreError>; + fn put_waiting( + &mut self, + invocation: Cid, + waiting_on: Vec, + ) -> Result<(), Self::PromiseStoreError>; - // NOTE get waiting - fn get(&self, waiting_on: &mut Vec) -> Result, Self::PromiseStoreError>; + fn get_waiting( + &self, + waiting_on: &mut Vec, + ) -> Result, Self::PromiseStoreError>; } diff --git a/src/ipld.rs b/src/ipld.rs index 2c3a79db..59116dac 100644 --- a/src/ipld.rs +++ b/src/ipld.rs @@ -7,6 +7,7 @@ //! [`Ipld`]: libipld_core::ipld::Ipld // mod enriched; +mod collection; mod newtype; mod number; mod promised; @@ -14,6 +15,7 @@ mod promised; pub mod cid; // pub use enriched::Enriched; +pub use collection::Collection; pub use newtype::Newtype; pub use number::Number; pub use promised::*; diff --git a/src/delegation/policy/collection.rs b/src/ipld/collection.rs similarity index 100% rename from src/delegation/policy/collection.rs rename to src/ipld/collection.rs index 1cab0451..1478c805 100644 --- a/src/delegation/policy/collection.rs +++ b/src/ipld/collection.rs @@ -1,5 +1,5 @@ -use serde::{Deserialize, Serialize}; use crate::ipld; +use serde::{Deserialize, Serialize}; use std::collections::BTreeMap; #[cfg(feature = "test_utils")] diff --git a/src/ipld/newtype.rs b/src/ipld/newtype.rs index f202599b..88132b2b 100644 --- a/src/ipld/newtype.rs +++ b/src/ipld/newtype.rs @@ -43,65 +43,6 @@ use super::cid; #[serde(transparent)] pub struct Newtype(pub Ipld); -// impl Eq for Newtype {} -// -// impl PartialOrd for Newtype { -// fn partial_cmp(&self, other: &Self) -> Option { -// match (&self.0, &other.0) { -// (Ipld::Null, Ipld::Null) => Some(Ordering::Equal), -// (Ipld::Null, _) => Some(Ordering::Less), -// -// // -// (Ipld::Bool(lhs), Ipld::Bool(rhs)) => lhs.partial_cmp(rhs), -// (Ipld::Bool(lhs), Ipld::Bool(rhs)) => lhs.partial_cmp(rhs), -// (Ipld::Bool(_), _) => Some(Ordering::Less), -// -// // -// (Ipld::Float(lhs_f), Ipld::Float(rhs_f)) => { -// OrderedFloat(*lhs_f).partial_cmp(&OrderedFloat(*rhs_f)) -// } -// (lhs @ Ipld::Float(_), rhs) => Some(Ordering::Less), -// -// // -// (Ipld::Integer(lhs), Ipld::Integer(rhs)) => lhs.partial_cmp(rhs), -// (Ipld::Integer(_), _) => Some(Ordering::Less), -// -// // -// (Ipld::String(lhs), Ipld::String(rhs)) => lhs.partial_cmp(rhs), -// (Ipld::String(_), _) => Some(Ordering::Less), -// -// // -// (Ipld::Bytes(lhs), Ipld::Bytes(rhs)) => lhs.partial_cmp(rhs), -// (Ipld::Bytes(_), _) => Some(Ordering::Less), -// -// // -// (Ipld::Link(lhs), Ipld::Link(rhs)) => lhs.partial_cmp(rhs), -// (Ipld::Link(_), _) => Some(Ordering::Less), -// -// // -// (Ipld::List(lhs), Ipld::List(rhs)) => { -// let result = lhs.iter().zip(rhs).try_fold((), |acc, (l, r)| { -// match Newtype(*l).partial_cmp(&Newtype(*r)) { -// Some(Ordering::Equal) => Ok(()), -// Some(other) => Err(other), -// None => Err(Ordering::Less), -// } -// }); -// -// match result { -// Ok(()) => Some(Ordering::Equal), -// Err(comp) => Some(comp), -// } -// } -// (Ipld::List(_), _) => Some(Ordering::Less), -// -// // -// (Ipld::Map(lhs), Ipld::Map(rhs)) => None, -// (Ipld::Map(_), _) => Some(Ordering::Less), -// } -// } -// } - impl From for Newtype { fn from(ipld: Ipld) -> Self { Self(ipld) diff --git a/src/lib.rs b/src/lib.rs index 1c21c44a..571310bb 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -21,6 +21,7 @@ pub mod delegation; pub mod did; pub mod invocation; pub mod ipld; +pub mod proof; pub mod reader; pub mod receipt; pub mod task; diff --git a/src/proof.rs b/src/proof.rs new file mode 100644 index 00000000..126ff504 --- /dev/null +++ b/src/proof.rs @@ -0,0 +1,38 @@ +use crate::{crypto::varsig, delegation::Delegation, did::Did}; +use libipld_core::{cid::Cid, codec::Codec, link::Link}; +use serde::{Deserialize, Deserializer, Serialize}; + +#[derive(Debug, Clone, PartialEq)] +pub struct Proof, Enc: Codec + TryFrom + Into> { + pub prf: Vec>>, +} + +impl, Enc: Codec + TryFrom + Into> Serialize + for Proof +{ + fn serialize(&self, serializer: S) -> Result { + let chain = self + .prf + .iter() + .map(|link| link.to_string()) + .collect::>(); + + chain.serialize(serializer) + } +} + +impl<'de, DID: Did, V: varsig::Header, Enc: Codec + TryFrom + Into> Deserialize<'de> + for Proof +{ + fn deserialize>(deserializer: D) -> Result { + let prf = Vec::::deserialize(deserializer)? + .into_iter() + .map(|s| { + let cid: Cid = s.try_into().map_err(serde::de::Error::custom)?; + Ok(cid.into()) + }) + .collect::, _>>()?; + + Ok(Proof { prf }) + } +} diff --git a/src/reader.rs b/src/reader.rs index a108640f..2a4e12c5 100644 --- a/src/reader.rs +++ b/src/reader.rs @@ -2,10 +2,8 @@ //! //! See the [`Reader`] struct for more information. -mod builder; mod generic; mod promised; -pub use builder::Builder; pub use generic::Reader; pub use promised::Promised; diff --git a/src/reader/builder.rs b/src/reader/builder.rs deleted file mode 100644 index e772e239..00000000 --- a/src/reader/builder.rs +++ /dev/null @@ -1,37 +0,0 @@ -use super::Reader; -use crate::ability::arguments; -use serde::{Deserialize, Serialize}; - -/// A helper newtype that marks a value as being a [`Delegable::Builder`]. -/// -/// The is often used as: -/// -/// ```rust -/// # use ucan::reader::{Reader, Builder}; -/// # type Env = (); -/// # let env = (); -/// let example: Reader> = Reader { -/// env: env, -/// val: Builder(42), -/// }; -/// ``` -#[derive(Clone, PartialEq, Debug, Serialize, Deserialize)] -pub struct Builder(pub T); - -impl>> From> for arguments::Named { - fn from(builder: Builder) -> Self { - builder.0.into() - } -} - -impl From> for Reader> { - fn from(reader: Reader) -> Self { - reader.map(Builder) - } -} - -impl From>> for Reader { - fn from(reader: Reader>) -> Self { - reader.map(|b| b.0) - } -} diff --git a/src/receipt.rs b/src/receipt.rs index f771b90f..5ec60a84 100644 --- a/src/receipt.rs +++ b/src/receipt.rs @@ -5,9 +5,6 @@ //! - [`Store`] is the storage interface for [`Receipt`]s. //! - [`Responds`] associates the response success type to an [Ability][crate::ability]. -// FIXME consider "assertion"? -// - mod payload; mod responds; @@ -20,26 +17,24 @@ pub use store::Store; use crate::{ ability, crypto::{signature, varsig}, - did, + did::{self, Did}, }; use libipld_core::{ codec::{Codec, Encode}, ipld::Ipld, }; +use serde::{Deserialize, Serialize}; /// The complete, signed receipt of an [`Invocation`][`crate::invocation::Invocation`]. #[derive(Clone, Debug, PartialEq)] -pub struct Receipt< - T: Responds, - DID: did::Did, - V: varsig::Header, - C: Codec + Into + TryFrom, ->(pub signature::Envelope, DID, V, C>); +pub struct Receipt, C: Codec + Into + TryFrom>( + pub signature::Envelope, DID, V, C>, +); /// An alias for the [`Receipt`] type with the library preset /// [`Did`](crate::did)s and [Abilities](crate::ability). pub type Preset = Receipt< - ability::preset::Ready, + ability::preset::Preset, did::preset::Verifier, varsig::header::Preset, varsig::encoding::Preset, @@ -65,3 +60,56 @@ impl, Enc: Codec + Into self.0.varsig_encode(w) } } + +impl, Enc: Codec + TryFrom + Into> + did::Verifiable for Receipt +{ + fn verifier(&self) -> &DID { + &self.0.verifier() + } +} + +impl, Enc: Codec + TryFrom + Into> + From> for Ipld +{ + fn from(invocation: Receipt) -> Self { + invocation.0.into() + } +} + +impl, Enc: Codec + TryFrom + Into> + TryFrom for Receipt +where + Payload: TryFrom, +{ + type Error = , DID, V, Enc> as TryFrom>::Error; + + fn try_from(ipld: Ipld) -> Result { + signature::Envelope::try_from(ipld).map(Receipt) + } +} + +impl, Enc: Codec + TryFrom + Into> Serialize + for Receipt +{ + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + self.0.serialize(serializer) + } +} + +impl<'de, T: Responds, DID: Did, V: varsig::Header, Enc: Codec + TryFrom + Into> + Deserialize<'de> for Receipt +where + Payload: TryFrom, + as TryFrom>::Error: std::fmt::Display, +{ + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + signature::Envelope::deserialize(deserializer).map(Receipt) + } +} From b156a3a1a1d5d00edb148ea8c5d04a5ab725be1e Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Mon, 4 Mar 2024 23:37:19 -0800 Subject: [PATCH 113/188] Going through FIXMEs --- Cargo.toml | 1 - examples/counterparts.rs | 2 +- src/ability/arguments/named.rs | 51 +--- src/ability/crud.rs | 34 ++- src/ability/crud/create.rs | 21 +- src/ability/crud/destroy.rs | 16 +- src/ability/crud/read.rs | 21 +- src/ability/crud/update.rs | 58 +++- src/ability/dynamic.rs | 25 -- src/ability/js/config.rs | 120 ++++++++ src/ability/js/parentful.rs | 90 +----- src/ability/js/parentless.rs | 140 ---------- src/ability/preset.rs | 34 ++- src/ability/wasm/run.rs | 2 - src/crypto/nonce.rs | 1 - src/crypto/p521.rs | 4 +- src/crypto/rs256.rs | 6 +- src/crypto/varsig/header/rs256.rs | 18 +- src/delegation.rs | 5 - src/delegation/policy/selector/or.rs | 113 -------- src/invocation/payload.rs | 1 - src/ipld/newtype.rs | 14 +- src/ipld/promised.rs | 39 ++- src/url.rs | 1 - tests/conformance.rs | 391 --------------------------- 25 files changed, 339 insertions(+), 869 deletions(-) create mode 100644 src/ability/js/config.rs delete mode 100644 src/ability/js/parentless.rs delete mode 100644 src/delegation/policy/selector/or.rs delete mode 100644 tests/conformance.rs diff --git a/Cargo.toml b/Cargo.toml index a3b4fcd2..73c08fbc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -74,7 +74,6 @@ thiserror = "1.0" aquamarine = { version = "0.5", optional = true } # FIXME actually use? async-signature = "0.4.0" -# FIXME see if possible to push aquamarkine into dev dependencies? # FIXME also have a wasi target [target.'cfg(target_arch = "wasm32")'.dependencies] diff --git a/examples/counterparts.rs b/examples/counterparts.rs index a7a4289a..6ad86f4d 100644 --- a/examples/counterparts.rs +++ b/examples/counterparts.rs @@ -1,6 +1,6 @@ use std::error::Error; -// TODO what is this? +// FIXME use? pub fn main() -> Result<(), Box> { println!("Alien Shore!"); Ok(()) diff --git a/src/ability/arguments/named.rs b/src/ability/arguments/named.rs index 7b5baa5f..fc844416 100644 --- a/src/ability/arguments/named.rs +++ b/src/ability/arguments/named.rs @@ -1,4 +1,7 @@ -use crate::{invocation::promise::Pending, ipld}; +use crate::{ + invocation::promise::{Pending, Resolves}, + ipld, +}; use libipld_core::ipld::Ipld; use serde::{Deserialize, Serialize}; use std::collections::BTreeMap; @@ -125,7 +128,6 @@ impl FromIterator<(String, T)> for Named { } } -// FIXME move to common ipld module? #[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Error)] pub enum FromIpldError { NotAMap, @@ -220,49 +222,6 @@ impl TryFrom for Named { } } -use crate::invocation::promise::Resolves; - -// impl> TryFrom> for Named { -// type Error = (); -// -// fn try_from(named: Named) -> Result { -// let btree = named -// .0 -// .into_iter() -// .map(|(k, v)| { -// let ipld = v.try_into().map_err(|_| ())?; -// Ok((k, ipld)) -// }) -// .collect::>()?; -// -// Ok(Named(btree)) -// } -// } -// the trait `From>` is not implemented for `Named` - -// impl From> for Named> { -// fn from(named: Named) -> Named> { -// named -// .into_iter() -// .map(|(k, v)| (k, promise::PromiseOk::Fulfilled(v).into())) -// .collect() -// } -// } -// impl> TryFrom> for Named> { -// type Error = Named; -// -// fn try_from(named: Named) -> Result>, Self::Error> { -// named -// .into_iter() -// .try_fold(Named::new(), |mut btree, (k, v)| { -// let ipld = v.try_into().map_err(|_| named.clone())?; -// btree.insert(k, promise::PromiseOk::Fulfilled(ipld).into()); -// Ok(btree) -// }) -// } -// } - -// FIXME abstract over both of these? impl From> for Named> { fn from(named: Named) -> Named> { let btree: BTreeMap> = named @@ -306,7 +265,7 @@ where fn try_from(resolves: Resolves>) -> Result { resolves - .clone() // FIXME could be a pretty heavy clone + .clone() .try_resolve()? .into_iter() .try_fold(Named::new(), |mut btree, (k, v)| { diff --git a/src/ability/crud.rs b/src/ability/crud.rs index c821d75a..0571b77f 100644 --- a/src/ability/crud.rs +++ b/src/ability/crud.rs @@ -56,6 +56,7 @@ use destroy::{Destroy, PromisedDestroy}; use libipld_core::ipld::Ipld; use read::{PromisedRead, Read}; use serde::{Deserialize, Serialize}; +use thiserror::Error; use update::{PromisedUpdate, Update}; #[cfg(target_arch = "wasm32")] @@ -78,7 +79,7 @@ pub enum PromisedCrud { } impl ParsePromised for PromisedCrud { - type PromisedArgsError = (); // FIXME + type PromisedArgsError = InvalidArgs; fn try_parse_promised( cmd: &str, @@ -86,32 +87,32 @@ impl ParsePromised for PromisedCrud { ) -> Result> { match PromisedCreate::try_parse_promised(cmd, args.clone()) { Ok(create) => return Ok(PromisedCrud::Create(create)), - Err(ParseAbilityError::InvalidArgs(_)) => { - return Err(ParseAbilityError::InvalidArgs(())) + Err(ParseAbilityError::InvalidArgs(e)) => { + return Err(ParseAbilityError::InvalidArgs(InvalidArgs::Create(e))) } Err(ParseAbilityError::UnknownCommand(_)) => (), } match PromisedRead::try_parse_promised(cmd, args.clone()) { Ok(read) => return Ok(PromisedCrud::Read(read)), - Err(ParseAbilityError::InvalidArgs(_)) => { - return Err(ParseAbilityError::InvalidArgs(())) + Err(ParseAbilityError::InvalidArgs(e)) => { + return Err(ParseAbilityError::InvalidArgs(InvalidArgs::Read(e))) } Err(ParseAbilityError::UnknownCommand(_)) => (), } match PromisedUpdate::try_parse_promised(cmd, args.clone()) { Ok(update) => return Ok(PromisedCrud::Update(update)), - Err(ParseAbilityError::InvalidArgs(_)) => { - return Err(ParseAbilityError::InvalidArgs(())) + Err(ParseAbilityError::InvalidArgs(e)) => { + return Err(ParseAbilityError::InvalidArgs(InvalidArgs::Update(e))) } Err(ParseAbilityError::UnknownCommand(_)) => (), } match PromisedDestroy::try_parse_promised(cmd, args) { Ok(destroy) => return Ok(PromisedCrud::Destroy(destroy)), - Err(ParseAbilityError::InvalidArgs(_)) => { - return Err(ParseAbilityError::InvalidArgs(())) + Err(ParseAbilityError::InvalidArgs(e)) => { + return Err(ParseAbilityError::InvalidArgs(InvalidArgs::Destroy(e))) } Err(ParseAbilityError::UnknownCommand(_)) => (), } @@ -120,6 +121,21 @@ impl ParsePromised for PromisedCrud { } } +#[derive(Debug, Clone, PartialEq, Error)] +pub enum InvalidArgs { + #[error("Invalid args for create: {0}")] + Create(create::FromPromisedArgsError), + + #[error("Invalid args for read: {0}")] + Read(read::FromPromisedArgsError), + + #[error("Invalid args for update: {0}")] + Update(update::FromPromisedArgsError), + + #[error("Invalid args for destroy: {0}")] + Destroy(destroy::FromPromisedArgsError), +} + impl ParseAbility for Crud { type ArgsErr = (); diff --git a/src/ability/crud/create.rs b/src/ability/crud/create.rs index 3051676a..bd5b159f 100644 --- a/src/ability/crud/create.rs +++ b/src/ability/crud/create.rs @@ -8,6 +8,7 @@ use crate::{ use libipld_core::ipld::Ipld; use serde::{Deserialize, Serialize}; use std::path::PathBuf; +use thiserror::Error; #[cfg_attr(doc, aquamarine::aquamarine)] /// The executable/dispatchable variant of the `crud/create` ability. @@ -107,7 +108,7 @@ impl Command for PromisedCreate { } impl TryFrom> for PromisedCreate { - type Error = (); + type Error = FromPromisedArgsError; fn try_from(arguments: arguments::Named) -> Result { let mut path = None; @@ -130,7 +131,7 @@ impl TryFrom> for PromisedCreate { ipld::Promised::WaitAny(cid) => { todo!() // FIXME // path = Some(promise::PromiseAny::Pending(cid).into()); } - _ => return Err(()), + _ => return Err(FromPromisedArgsError::InvalidPath(k)), }, "args" => { @@ -147,10 +148,10 @@ impl TryFrom> for PromisedCreate { ipld::Promised::WaitAny(cid) => { todo!() // FIXME // Some(promise::PromiseAny::Pending(cid).into()) } - _ => return Err(()), + _ => return Err(FromPromisedArgsError::InvalidArgs(prom)), } } - _ => return Err(()), + _ => return Err(FromPromisedArgsError::InvalidMapKey(k)), } } @@ -158,6 +159,18 @@ impl TryFrom> for PromisedCreate { } } +#[derive(Error, Debug, PartialEq, Clone)] +pub enum FromPromisedArgsError { + #[error("Invalid path {0}")] + InvalidPath(String), + + #[error("Invalid args {0}")] + InvalidArgs(ipld::Promised), + + #[error("Invalid map key {0}")] + InvalidMapKey(String), +} + impl TryFrom> for Create { type Error = (); diff --git a/src/ability/crud/destroy.rs b/src/ability/crud/destroy.rs index 37ef0aa1..5b5dfbc3 100644 --- a/src/ability/crud/destroy.rs +++ b/src/ability/crud/destroy.rs @@ -8,6 +8,7 @@ use crate::{ use libipld_core::ipld::Ipld; use serde::{Deserialize, Serialize}; use std::path::PathBuf; +use thiserror::Error; #[cfg_attr(doc, aquamarine::aquamarine)] /// The executable/dispatchable variant of the `crud/destroy` ability. @@ -138,7 +139,7 @@ pub struct PromisedDestroy { } impl TryFrom> for PromisedDestroy { - type Error = (); + type Error = FromPromisedArgsError; fn try_from(arguments: arguments::Named) -> Result { let mut path = None; @@ -160,9 +161,9 @@ impl TryFrom> for PromisedDestroy { ipld::Promised::WaitAny(cid) => { todo!() // FIXME // path = Some(promise::PromiseAny::Pending(cid).into()); } - _ => return Err(()), + _ => return Err(FromPromisedArgsError::InvalidPath(k)), }, - _ => return Err(()), + _ => return Err(FromPromisedArgsError::InvalidMapKey(k)), } } @@ -170,6 +171,15 @@ impl TryFrom> for PromisedDestroy { } } +#[derive(Error, Debug, PartialEq, Clone)] +pub enum FromPromisedArgsError { + #[error("Invalid path {0}")] + InvalidPath(String), + + #[error("Invalid map key {0}")] + InvalidMapKey(String), +} + impl Command for PromisedDestroy { const COMMAND: &'static str = COMMAND; } diff --git a/src/ability/crud/read.rs b/src/ability/crud/read.rs index 28199c49..35c925fd 100644 --- a/src/ability/crud/read.rs +++ b/src/ability/crud/read.rs @@ -8,6 +8,7 @@ use crate::{ use libipld_core::{error::SerdeError, ipld::Ipld, serde as ipld_serde}; use serde::{Deserialize, Serialize}; use std::path::PathBuf; +use thiserror::Error; #[cfg_attr(doc, aquamarine::aquamarine)] /// This ability is used to fetch messages from other actors. @@ -91,7 +92,7 @@ pub struct PromisedRead { } impl TryFrom> for PromisedRead { - type Error = (); + type Error = FromPromisedArgsError; fn try_from(arguments: arguments::Named) -> Result { let mut path = None; @@ -114,7 +115,7 @@ impl TryFrom> for PromisedRead { ipld::Promised::WaitAny(cid) => { todo!() // FIXME // path = Some(promise::PromiseAny::Pending(cid).into()); } - _ => return Err(()), + _ => return Err(FromPromisedArgsError::InvalidPath(k)), }, "args" => { @@ -131,10 +132,10 @@ impl TryFrom> for PromisedRead { ipld::Promised::WaitAny(cid) => { todo!() // FIXME // Some(promise::PromiseAny::Pending(cid).into()) } - _ => return Err(()), + _ => return Err(FromPromisedArgsError::InvalidArgs(prom)), } } - _ => return Err(()), + _ => return Err(FromPromisedArgsError::InvalidMapKey(k)), } } @@ -142,6 +143,18 @@ impl TryFrom> for PromisedRead { } } +#[derive(Error, Debug, PartialEq, Clone)] +pub enum FromPromisedArgsError { + #[error("Invalid path {0}")] + InvalidPath(String), + + #[error("Invalid args {0}")] + InvalidArgs(ipld::Promised), + + #[error("Invalid map key {0}")] + InvalidMapKey(String), +} + const COMMAND: &'static str = "/crud/read"; impl Command for Read { diff --git a/src/ability/crud/update.rs b/src/ability/crud/update.rs index b512fa8f..dbe4a4c8 100644 --- a/src/ability/crud/update.rs +++ b/src/ability/crud/update.rs @@ -8,6 +8,7 @@ use crate::{ use libipld_core::ipld::Ipld; use serde::{Deserialize, Serialize}; use std::{collections::BTreeMap, path::PathBuf}; +use thiserror::Error; #[cfg_attr(doc, aquamarine::aquamarine)] /// The executable/dispatchable variant of the `crud/create` ability. @@ -107,7 +108,7 @@ impl Command for PromisedUpdate { } impl TryFrom> for PromisedUpdate { - type Error = (); + type Error = FromPromisedArgsError; fn try_from(named: arguments::Named) -> Result { let mut path = None; @@ -121,7 +122,7 @@ impl TryFrom> for PromisedUpdate { } Ok(ipld) => match ipld { Ipld::String(s) => path = Some(promise::Resolves::new(PathBuf::from(s))), - _ => return Err(()), + other => return Err(FromPromisedArgsError::PathBodyNotAString(other)), }, }, @@ -129,19 +130,22 @@ impl TryFrom> for PromisedUpdate { ipld::Promised::Map(map) => { args = Some(promise::Resolves::new(arguments::Named(map))) } - ipld::Promised::WaitOk(cid) => { + ipld::Promised::WaitOk(_cid) => { + // FIXME args = Some(promise::Resolves::new(arguments::Named::new())); } - ipld::Promised::WaitErr(cid) => { + ipld::Promised::WaitErr(_cid) => { + // FIXME args = Some(promise::Resolves::new(arguments::Named::new())); } - ipld::Promised::WaitAny(cid) => { + ipld::Promised::WaitAny(_cid) => { + // FIXME args = Some(promise::Resolves::new(arguments::Named::new())); } - _ => return Err(()), + _ => return Err(FromPromisedArgsError::InvalidArgs(prom)), }, - _ => return Err(()), + _ => return Err(FromPromisedArgsError::InvalidMapKey(key)), } } @@ -149,6 +153,18 @@ impl TryFrom> for PromisedUpdate { } } +#[derive(Error, Debug, PartialEq, Clone)] +pub enum FromPromisedArgsError { + #[error("Path body is not a string")] + PathBodyNotAString(Ipld), + + #[error("Invalid args {0}")] + InvalidArgs(ipld::Promised), + + #[error("Invalid map key {0}")] + InvalidMapKey(String), +} + impl TryFrom> for Update { type Error = (); @@ -212,8 +228,10 @@ impl TryFrom for Update { } } -impl From for arguments::Named { - fn from(promised: PromisedUpdate) -> Self { +impl TryFrom for arguments::Named { + type Error = FromPromisedUpdateError; + + fn try_from(promised: PromisedUpdate) -> Result { let mut named = arguments::Named::new(); if let Some(path_res) = promised.path { @@ -228,21 +246,33 @@ impl From for arguments::Named { "args".to_string(), args_res .try_resolve() - .expect("FIXME") + .map_err(FromPromisedUpdateError::UnresolvedArgs)? .iter() .try_fold(BTreeMap::new(), |mut map, (k, v)| { - map.insert(k.clone(), Ipld::try_from(v.clone()).ok()?); // FIXME double check - Some(map) + map.insert(k.clone(), Ipld::try_from(v.clone())?); // FIXME double check + Ok(map) }) - .expect("FIXME") + .map_err(FromPromisedUpdateError::ArgsPending)? .into(), ); } - named + Ok(named) } } +#[derive(Error, Debug, PartialEq, Clone)] +pub enum FromPromisedUpdateError { + #[error("Unresolved args")] + UnresolvedArgs(Resolves>), + + #[error("Args pending")] + ArgsPending(>::Error), + + #[error("Invalid map key {0}")] + InvalidMapKey(String), +} + impl From for PromisedUpdate { fn from(r: Update) -> PromisedUpdate { PromisedUpdate { diff --git a/src/ability/dynamic.rs b/src/ability/dynamic.rs index 441c1565..a24bb103 100644 --- a/src/ability/dynamic.rs +++ b/src/ability/dynamic.rs @@ -5,7 +5,6 @@ use super::{ command::ToCommand, parse::{ParseAbility, ParseAbilityError}, }; -// use crate::proof::same::CheckSame; use libipld_core::{error::SerdeError, ipld::Ipld, serde as ipld_serde}; use serde::{Deserialize, Serialize}; use std::fmt::Debug; @@ -18,8 +17,6 @@ use js_sys; // NOTE the lack of checking functions! -// FIXME make a NOTE somewhere that Hierarchy is availavle on ability/js/... - /// A "dynamic" ability with the bare minimum of statics /// ///

@@ -118,28 +115,6 @@ impl TryFrom for Dynamic { } } -// impl CheckSame for Dynamic { -// type Error = String; // FIXME better err -// -// fn check_same(&self, proof: &Self) -> Result<(), Self::Error> { -// if self.cmd != proof.cmd { -// return Err("Command mismatch".into()); -// } -// -// self.args.0.iter().try_for_each(|(k, v)| { -// if let Some(proof_v) = proof.args.get(k) { -// if v != proof_v { -// return Err("arguments::Named mismatch".into()); -// } -// } else { -// return Err("arguments::Named mismatch".into()); -// } -// -// Ok(()) -// }) -// } -// } - impl From for Ipld { fn from(dynamic: Dynamic) -> Self { dynamic.into() diff --git a/src/ability/js/config.rs b/src/ability/js/config.rs new file mode 100644 index 00000000..2f42905b --- /dev/null +++ b/src/ability/js/config.rs @@ -0,0 +1,120 @@ +//! JavaScript interface for abilities that *do* require a parent hierarchy + +use crate::{ + ability::{arguments, command::ToCommand, dynamic}, + reader::Reader, +}; +use js_sys::{Function, JsString, Map}; +use libipld_core::ipld::Ipld; +use std::collections::BTreeMap; +use wasm_bindgen::{prelude::*, JsValue}; + +// FIXME rename +type WithParents = Reader>; + +// FIXME just make into a general config? + +/// The configuration object that expresses an ability (with parents) from JS +#[derive(Debug, Clone, PartialEq, Default)] +#[wasm_bindgen(getter_with_clone)] +pub struct ParentfulConfig { + pub command: String, + + #[wasm_bindgen(js_name = isNonceMeaningful)] + pub is_nonce_meaningful: bool, + + #[wasm_bindgen(js_name = validateShape)] + pub validate_shape: Function, +} + +// NOTE if changed, please update this in the docs for `ParentfulArgs` below +#[wasm_bindgen(typescript_custom_section)] +const PARENTFUL_ARGS: &str = r#" +interface ParentfulArgs { + command: string, + isNonceMeaningful: boolean, + validateShape: Function, +} +"#; + +#[wasm_bindgen] +extern "C" { + /// Named constructor arguments for `ParentfulConfig` + /// + /// This forms the basis for configuring an ability. + /// These values will be used at runtime to perform + /// checks on the ability (e.g. during delegation), + /// for indexing, and storage (among others). + /// + /// ```typescript + /// // TypeScript + /// interface ParentfulArgs { + /// command: string, + /// isNonceMeaningful: boolean, + /// validateShape: Function, + /// } + /// ``` + #[wasm_bindgen(typescript_type = "ParentfulArgs")] + pub type ParentfulArgs; + + /// Get the [`Command`][crate::ability::command::Command] string + #[wasm_bindgen(js_name = command)] + pub fn command(this: &ParentfulArgs) -> String; + + /// Whether the nonce should factor into a receipt's global index ([`task::Id`]) + #[wasm_bindgen(js_name = isNonceMeaningful)] + pub fn is_nonce_meaningful(this: &ParentfulArgs) -> bool; + + /// Parser validator + #[wasm_bindgen(js_name = validateShape)] + pub fn validate_shape(this: &ParentfulArgs) -> Function; +} + +#[wasm_bindgen] +impl ParentfulConfig { + /// Construct a new `ParentfulConfig` from JavaScript + /// + /// # Examples + /// + /// ```javascript + /// // JavaScript + /// const msgSendConfig = new ParentfulConfig({ + /// command: "msg/send", + /// isNonceMeaningful: true, + /// validateShape: (args) => { + /// if (args.to && args.message && args.length() === 2) { + /// return true; + /// } + /// return false; + /// } + /// } + /// ); + /// ``` + #[wasm_bindgen(constructor)] + pub fn new(js_obj: ParentfulArgs) -> Result { + Ok(ParentfulConfig { + command: command(&js_obj), + is_nonce_meaningful: is_nonce_meaningful(&js_obj), + validate_shape: validate_shape(&js_obj), + }) + } +} + +impl From for dynamic::Dynamic { + fn from(js: WithParents) -> Self { + dynamic::Dynamic { + cmd: js.env.command, + args: js.val, + } + } +} + +impl ToCommand for ParentfulConfig { + fn to_command(&self) -> String { + self.command.clone() + } +} + +impl Checkable for WithParents { + type Hierarchy = Parentful; +} diff --git a/src/ability/js/parentful.rs b/src/ability/js/parentful.rs index 16b7b689..2f42905b 100644 --- a/src/ability/js/parentful.rs +++ b/src/ability/js/parentful.rs @@ -2,7 +2,6 @@ use crate::{ ability::{arguments, command::ToCommand, dynamic}, - proof::{checkable::Checkable, parentful::Parentful, parents::CheckParents, same::CheckSame}, reader::Reader, }; use js_sys::{Function, JsString, Map}; @@ -13,6 +12,8 @@ use wasm_bindgen::{prelude::*, JsValue}; // FIXME rename type WithParents = Reader>; +// FIXME just make into a general config? + /// The configuration object that expresses an ability (with parents) from JS #[derive(Debug, Clone, PartialEq, Default)] #[wasm_bindgen(getter_with_clone)] @@ -24,12 +25,6 @@ pub struct ParentfulConfig { #[wasm_bindgen(js_name = validateShape)] pub validate_shape: Function, - - #[wasm_bindgen(js_name = checkSame)] - pub check_same: Function, - - #[wasm_bindgen(skip)] - pub check_parent: BTreeMap, } // NOTE if changed, please update this in the docs for `ParentfulArgs` below @@ -39,8 +34,6 @@ interface ParentfulArgs { command: string, isNonceMeaningful: boolean, validateShape: Function, - checkSame: Function, - checkParent: Map } "#; @@ -59,8 +52,6 @@ extern "C" { /// command: string, /// isNonceMeaningful: boolean, /// validateShape: Function, - /// checkSame: Function, - /// checkParent: Map /// } /// ``` #[wasm_bindgen(typescript_type = "ParentfulArgs")] @@ -77,14 +68,6 @@ extern "C" { /// Parser validator #[wasm_bindgen(js_name = validateShape)] pub fn validate_shape(this: &ParentfulArgs) -> Function; - - /// Validate an instance against a candidate proof of the same shape - #[wasm_bindgen(js_name = checkSame)] - pub fn check_same(this: &ParentfulArgs) -> Function; - - /// Validate an instance against a candidate proof containing a parent - #[wasm_bindgen(js_name = checkParent)] - pub fn check_parent(this: &ParentfulArgs) -> Map; } #[wasm_bindgen] @@ -103,18 +86,7 @@ impl ParentfulConfig { /// return true; /// } /// return false; - /// }, - /// checkSame: (proof, args) => { - /// if (proof.to === args.to && proof.message === args.message) { - /// return true; - /// } - /// return false; - /// }, - /// checkParent: new Map([ - /// ["msg/*", (proof, args) => { - /// proof.to === args.to && proof.message === args.message - /// }] - /// ]) + /// } /// } /// ); /// ``` @@ -124,29 +96,6 @@ impl ParentfulConfig { command: command(&js_obj), is_nonce_meaningful: is_nonce_meaningful(&js_obj), validate_shape: validate_shape(&js_obj), - check_same: check_same(&js_obj), - check_parent: { - let mut btree = BTreeMap::new(); - let mut acc = Ok(()); - - check_parent(&js_obj).for_each(&mut |value, key| { - // |value, key| is correct ------^^^^^^^^^^^^ - if let Ok(_) = &acc { - match key.as_string() { - None => acc = Err(JsString::from("Key is not a string")), // FIXME better err - Some(str_key) => match value.dyn_ref::() { - None => acc = Err("Value is not a function".into()), - Some(f) => { - btree.insert(str_key, f.clone()); - acc = Ok(()); - } - }, - } - } - }); - - acc.map(|_| btree)? - }, }) } } @@ -160,39 +109,6 @@ impl From for dynamic::Dynamic { } } -// FIXME while this can totally be done by converting to the dynamic carrier type, this seems more straightforward? -impl CheckSame for WithParents { - type Error = JsValue; - - fn check_same(&self, proof: &Self) -> Result<(), Self::Error> { - let this = wasm_bindgen::JsValue::NULL; - self.env - .check_same - .call2( - &this, - &self.val.clone().into(), - &arguments::Named::from(proof.clone()).into(), - ) - .map(|_| ()) - } -} - -impl CheckParents for WithParents { - type Parents = dynamic::Dynamic; - type ParentError = JsValue; - - fn check_parent(&self, parent: &dynamic::Dynamic) -> Result<(), Self::Error> { - if let Some(handler) = self.env.check_parent.get(&parent.cmd) { - let this = wasm_bindgen::JsValue::NULL; - handler - .call2(&this, &self.val.clone().into(), &parent.args.clone().into()) - .map(|_| ()) // FIXME - } else { - Err(JsValue::from("No handler for parent")) - } - } -} - impl ToCommand for ParentfulConfig { fn to_command(&self) -> String { self.command.clone() diff --git a/src/ability/js/parentless.rs b/src/ability/js/parentless.rs deleted file mode 100644 index d9f458c7..00000000 --- a/src/ability/js/parentless.rs +++ /dev/null @@ -1,140 +0,0 @@ -//! JavaScript interface for abilities that *do not* require a parent hierarchy - -use crate::{ - ability::{arguments, command::ToCommand, dynamic}, - proof::{parentless::NoParents, same::CheckSame}, - reader::Reader, -}; -use js_sys::Function; -use libipld_core::ipld::Ipld; -use wasm_bindgen::prelude::*; - -// FIXME rename -type WithoutParents = Reader>; - -/// The configuration object that expresses an ability (without parents) from JS -#[derive(Debug, Clone, PartialEq)] -#[wasm_bindgen] -pub struct ParentlessConfig { - command: String, - is_nonce_meaningful: bool, - validate_shape: Function, - check_same: Function, -} - -// NOTE if changed, please update this in the docs for `ParentlessArgs` below -#[wasm_bindgen(typescript_custom_section)] -pub const PARENTLESS_ARGS: &str = r#" -interface ParentlessArgs { - command: string, - isNonceMeaningful: boolean, - validateShape: Function, - checkSame: Function, -} -"#; - -#[wasm_bindgen] -extern "C" { - /// Named constructor arguments for `ParentlessConfig` - /// - /// This forms the basis for configuring an ability. - /// These values will be used at runtime to perform - /// checks on the ability (e.g. during delegation), - /// for indexing, and storage (among others). - /// - /// ```typescript - /// // TypeScript - /// interface ParentlessArgs { - /// command: string, - /// isNonceMeaningful: boolean, - /// validateShape: Function, - /// checkSame: Function, - /// } - /// ``` - #[wasm_bindgen(typescript_type = "ParentlessArgs")] - pub type ParentlessArgs; - - /// Get the [`Command`][crate::ability::command::Command] string - #[wasm_bindgen(js_name = command)] - pub fn command(this: &ParentlessArgs) -> String; - - /// Whether the nonce should factor into a receipt's global index ([`task::Id`]) - #[wasm_bindgen(js_name = isNonceMeaningful)] - pub fn is_nonce_meaningful(this: &ParentlessArgs) -> bool; - - /// Parser validator - #[wasm_bindgen(js_name = validateShape)] - pub fn validate_shape(this: &ParentlessArgs) -> Function; - - /// Validate an instance against a candidate proof of the same shape - #[wasm_bindgen(js_name = checkSame)] - pub fn check_same(this: &ParentlessArgs) -> Function; -} - -#[wasm_bindgen] -impl ParentlessConfig { - /// Construct a new `ParentlessConfig` from JavaScript - /// - /// # Examples - /// - /// ```javascript - /// // JavaScript - /// const msgSendConfig = new ParentlessConfig({ - /// command: "msg/send", - /// isNonceMeaningful: true, - /// validateShape: (args) => { - /// if (args.to && args.message && args.length() === 2) { - /// return true; - /// } - /// return false; - /// }, - /// checkSame: (proof, args) => { - /// proof.to === args.to && proof.message === args.message - /// }, - /// } - /// ); - /// ``` - #[wasm_bindgen(constructor)] - pub fn new(js_obj: ParentlessArgs) -> ParentlessConfig { - ParentlessConfig { - command: command(&js_obj), - is_nonce_meaningful: is_nonce_meaningful(&js_obj), - validate_shape: validate_shape(&js_obj), - check_same: check_same(&js_obj), - } - } -} - -impl From for dynamic::Dynamic { - fn from(js: WithoutParents) -> Self { - dynamic::Dynamic { - cmd: js.env.command, - args: js.val, - } - } -} - -// FIXME while this can totally be done by converting to the dynamic carrier type, this seems more straightforward? -impl CheckSame for WithoutParents { - type Error = JsValue; - - fn check_same(&self, proof: &Self) -> Result<(), Self::Error> { - let this = wasm_bindgen::JsValue::NULL; - self.env - .check_same - .call2( - &this, - &self.val.clone().into(), - &arguments::Named::from(proof.clone()).into(), - ) - .map(|_| ()) - } -} - -impl ToCommand for ParentlessConfig { - fn to_command(&self) -> String { - self.command.clone() - } -} - -impl NoParents for ParentlessConfig {} diff --git a/src/ability/preset.rs b/src/ability/preset.rs index f3b122d8..34f74d90 100644 --- a/src/ability/preset.rs +++ b/src/ability/preset.rs @@ -1,5 +1,5 @@ use super::{ - crud::{Crud, PromisedCrud}, + crud::{self, Crud, PromisedCrud}, msg::{Msg, PromisedMsg}, ucan::revoke::{PromisedRevoke, Revoke}, wasm::run as wasm, @@ -15,6 +15,7 @@ use crate::{ }; use libipld_core::ipld::Ipld; use serde::{Deserialize, Serialize}; +use thiserror::Error; #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] pub enum Preset { @@ -59,7 +60,7 @@ impl ToCommand for PromisedPreset { } impl ParsePromised for PromisedPreset { - type PromisedArgsError = (); + type PromisedArgsError = ParsePromisedError; fn try_parse_promised( cmd: &str, @@ -68,31 +69,50 @@ impl ParsePromised for PromisedPreset { match PromisedCrud::try_parse_promised(cmd, args.clone()) { Ok(promised) => return Ok(PromisedPreset::Crud(promised)), Err(ParseAbilityError::UnknownCommand(_)) => (), - Err(err) => return Err(err), + Err(err) => { + return Err(ParseAbilityError::InvalidArgs(ParsePromisedError::Crud( + err, + ))) + } } match PromisedMsg::try_parse_promised(cmd, args.clone()) { Ok(promised) => return Ok(PromisedPreset::Msg(promised)), Err(ParseAbilityError::UnknownCommand(_)) => (), - Err(err) => return Err(err), + Err(_err) => return Err(ParseAbilityError::InvalidArgs(ParsePromisedError::Msg)), } match wasm::PromisedRun::try_parse_promised(cmd, args.clone()) { Ok(promised) => return Ok(PromisedPreset::Wasm(promised)), Err(ParseAbilityError::UnknownCommand(_)) => (), - Err(err) => return Err(err), + Err(_err) => return Err(ParseAbilityError::InvalidArgs(ParsePromisedError::Wasm)), } match PromisedRevoke::try_parse_promised(cmd, args) { Ok(promised) => return Ok(PromisedPreset::Ucan(promised)), Err(ParseAbilityError::UnknownCommand(_)) => (), - Err(err) => return Err(err), + Err(_err) => return Err(ParseAbilityError::InvalidArgs(ParsePromisedError::Ucan)), } Err(ParseAbilityError::UnknownCommand(cmd.to_string())) } } +#[derive(Debug, Clone, Error)] +pub enum ParsePromisedError { + #[error("Crud error: {0}")] + Crud(ParseAbilityError), + + #[error("Msg error")] + Msg, // FIXME + + #[error("Wasm error")] + Wasm, // FIXME + + #[error("Ucan error")] + Ucan, // FIXME +} + impl ParseAbility for Preset { type ArgsErr = (); @@ -102,7 +122,7 @@ impl ParseAbility for Preset { ) -> Result> { match Msg::try_parse(cmd, args.clone()) { Ok(msg) => return Ok(Preset::Msg(msg)), - Err(ParseAbilityError::UnknownCommand(_)) => (), + Err(ParseAbilityError::UnknownCommand(_)) => (), // FIXME Err(err) => return Err(err), } diff --git a/src/ability/wasm/run.rs b/src/ability/wasm/run.rs index 0d291389..2aad7530 100644 --- a/src/ability/wasm/run.rs +++ b/src/ability/wasm/run.rs @@ -3,10 +3,8 @@ use super::module::Module; use crate::{ ability::{arguments, command::Command}, - // delegation::Delegable, invocation::promise, ipld, - // proof::{parentless::NoParents, same::CheckSame}, }; use libipld_core::ipld::Ipld; use serde::{Deserialize, Serialize}; diff --git a/src/crypto/nonce.rs b/src/crypto/nonce.rs index fc83d952..e53472c5 100644 --- a/src/crypto/nonce.rs +++ b/src/crypto/nonce.rs @@ -2,7 +2,6 @@ //! //! [Nonce]: https://en.wikipedia.org/wiki/Cryptographic_nonce -// FIXME use enum_as_inner more? use enum_as_inner::EnumAsInner; use getrandom::getrandom; use libipld_core::{ diff --git a/src/crypto/p521.rs b/src/crypto/p521.rs index 79d09d29..e5b13a37 100644 --- a/src/crypto/p521.rs +++ b/src/crypto/p521.rs @@ -1,8 +1,10 @@ use p521; +use serde::{Deserialize, Serialize}; use signature::Verifier; use std::fmt; -#[derive(Clone)] // FIXME , Serialize, Deserialize)] +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(transparent)] pub struct VerifyingKey(pub p521::ecdsa::VerifyingKey); impl fmt::Debug for VerifyingKey { diff --git a/src/crypto/rs256.rs b/src/crypto/rs256.rs index dae3a982..de6205cd 100644 --- a/src/crypto/rs256.rs +++ b/src/crypto/rs256.rs @@ -4,7 +4,7 @@ use rsa; use signature::{SignatureEncoding, Signer, Verifier}; /// The verifying/public key for RS256. -#[derive(Debug, Clone)] // FIXME , Serialize, Deserialize)] +#[derive(Debug, Clone)] pub struct VerifyingKey(pub rsa::pkcs1v15::VerifyingKey); impl PartialEq for VerifyingKey { @@ -22,7 +22,7 @@ impl Verifier for VerifyingKey { } /// The signing/secret key for RS256. -#[derive(Debug, Clone)] // FIXME , Serialize, Deserialize)] +#[derive(Debug, Clone)] pub struct SigningKey(pub rsa::pkcs1v15::SigningKey); impl Signer for SigningKey { @@ -32,7 +32,7 @@ impl Signer for SigningKey { } /// The signature for RS256. -#[derive(Debug, Clone, PartialEq, Eq)] // FIXME , Serialize, Deserialize)] +#[derive(Debug, Clone, PartialEq, Eq)] pub struct Signature(pub rsa::pkcs1v15::Signature); impl SignatureEncoding for Signature { diff --git a/src/crypto/varsig/header/rs256.rs b/src/crypto/varsig/header/rs256.rs index 92f03155..a175af8e 100644 --- a/src/crypto/varsig/header/rs256.rs +++ b/src/crypto/varsig/header/rs256.rs @@ -1,6 +1,7 @@ use super::Header; use crate::crypto::rs256::{Signature, VerifyingKey}; use libipld_core::codec::Codec; +use thiserror::Error; #[derive(Clone, Debug, PartialEq)] pub struct Rs256Header { @@ -8,22 +9,33 @@ pub struct Rs256Header { } impl> TryFrom<&[u8]> for Rs256Header { - type Error = (); // FIXME + type Error = ParseFromBytesError<>::Error>; fn try_from(bytes: &[u8]) -> Result { if let Ok((0x1205, inner)) = unsigned_varint::decode::u16(&bytes) { if let Ok((0x12, more)) = unsigned_varint::decode::u8(&inner) { if let Ok((codec_info, &[])) = unsigned_varint::decode::u32(&more) { - let codec = C::try_from(codec_info).map_err(|_| ())?; + let codec = + C::try_from(codec_info).map_err(ParseFromBytesError::CodecPrefixError)?; + return Ok(Rs256Header { codec }); } } } - Err(()) + Err(ParseFromBytesError::InvalidHeader) } } +#[derive(Debug, PartialEq, Clone, Error)] +pub enum ParseFromBytesError { + #[error("Invalid header")] + InvalidHeader, + + #[error("Codec prefix error: {0}")] + CodecPrefixError(#[from] C), +} + impl> From> for Vec { fn from(rs: Rs256Header) -> Vec { let mut tag_buf: [u8; 3] = Default::default(); diff --git a/src/delegation.rs b/src/delegation.rs index a26f2ba9..d67e7f28 100644 --- a/src/delegation.rs +++ b/src/delegation.rs @@ -40,9 +40,6 @@ use web_time::SystemTime; /// A [`Delegation`] is a signed delegation [`Payload`] /// /// A [`Payload`] on its own is not a valid [`Delegation`], as it must be signed by the issuer. -/// -/// # Examples -/// FIXME #[derive(Clone, Debug, PartialEq)] pub struct Delegation, Enc: Codec + TryFrom + Into>( pub signature::Envelope, DID, V, Enc>, @@ -63,8 +60,6 @@ impl, Enc: Codec + TryFrom + Into> Ca pub type Preset = Delegation; -// FIXME checkable -> provable? - impl, Enc: Codec + Into + TryFrom> Delegation { diff --git a/src/delegation/policy/selector/or.rs b/src/delegation/policy/selector/or.rs deleted file mode 100644 index f94bc514..00000000 --- a/src/delegation/policy/selector/or.rs +++ /dev/null @@ -1,113 +0,0 @@ -use super::{error::SelectorErrorReason, op::SelectorOp, SelectorError}; -use crate::delegation::policy::ir::Selectable; -use libipld_core::ipld::Ipld; -use serde::{Deserialize, Serialize}; - -#[cfg(feature = "test_utils")] -use proptest::prelude::*; - -#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] -pub enum Select { - Get(Vec), - Pure(T), -} - -impl Select { - pub fn resolve(self, ctx: &Ipld) -> Result { - match self { - Select::Pure(inner) => Ok(inner), - Select::Get(ops) => { - ops.iter() - .try_fold((ctx.clone(), vec![]), |(ipld, mut seen_ops), op| { - seen_ops.push(op); - - match op { - SelectorOp::Try(inner) => { - let op: SelectorOp = *inner.clone(); - let ipld: Ipld = Select::Get::(vec![op]) - .resolve(ctx) - .unwrap_or(Ipld::Null); - - Ok((ipld, seen_ops)) - } - SelectorOp::ArrayIndex(i) => { - let result = { - match ipld { - Ipld::List(xs) => { - if i.abs() as usize > xs.len() { - return Err(SelectorError::from_refs( - &seen_ops, - SelectorErrorReason::IndexOutOfBounds, - )); - }; - - xs.get((xs.len() as i32 + *i) as usize) - .ok_or(SelectorError::from_refs( - &seen_ops, - SelectorErrorReason::IndexOutOfBounds, - )) - .cloned() - } - // FIXME behaviour on maps? type error - _ => Err(SelectorError::from_refs( - &seen_ops, - SelectorErrorReason::NotAList, - )), - } - }; - - Ok((result?, seen_ops)) - } - SelectorOp::Field(k) => { - let result = match ipld { - Ipld::Map(xs) => xs - .get(k) - .ok_or(SelectorError::from_refs( - &seen_ops, - SelectorErrorReason::KeyNotFound, - )) - .cloned(), - _ => Err(SelectorError::from_refs( - &seen_ops, - SelectorErrorReason::NotAMap, - )), - }; - - Ok((result?, seen_ops)) - } - SelectorOp::Values => { - let result = match ipld { - Ipld::List(xs) => Ok(Ipld::List(xs)), - Ipld::Map(xs) => Ok(Ipld::List(xs.values().cloned().collect())), - _ => Err(SelectorError::from_refs( - &seen_ops, - SelectorErrorReason::NotACollection, - )), - }; - - Ok((result?, seen_ops)) - } - } - }) - .and_then(|(ipld, ref path)| { - T::try_select(ipld).map_err(|e| SelectorError::from_refs(path, e)) - }) - } - } - } -} - -#[cfg(feature = "test_utils")] -impl Arbitrary for Select { - type Parameters = T::Parameters; - type Strategy = BoxedStrategy; - - fn arbitrary_with(t_params: Self::Parameters) -> Self::Strategy { - prop_oneof![ - T::arbitrary_with(t_params).prop_map(Select::Pure), - // FIXME add params that make this actually correspond to data - prop::collection::vec(SelectorOp::arbitrary(), 1..10).prop_map(Select::Get), - ] - .boxed() - } -} diff --git a/src/invocation/payload.rs b/src/invocation/payload.rs index 6293e8f2..f64a64c9 100644 --- a/src/invocation/payload.rs +++ b/src/invocation/payload.rs @@ -153,7 +153,6 @@ impl Payload { } proofs.into_iter().try_fold(&self.issuer, |iss, proof| { - // FIXME extract step function? if *iss != proof.audience { return Err(ValidationError::InvalidSubject.into()); } diff --git a/src/ipld/newtype.rs b/src/ipld/newtype.rs index 88132b2b..dc20cb14 100644 --- a/src/ipld/newtype.rs +++ b/src/ipld/newtype.rs @@ -1,6 +1,7 @@ use libipld_core::ipld::Ipld; use serde::{Deserialize, Serialize}; use std::path::PathBuf; +use thiserror::Error; #[cfg(target_arch = "wasm32")] use wasm_bindgen::prelude::*; @@ -14,7 +15,6 @@ use proptest::prelude::*; #[cfg(feature = "test_utils")] use super::cid; -// FIXME push into the submodules /// A newtype wrapper around [`Ipld`] that has additional trait implementations. /// /// Usage is very simple: wrap a [`Newtype`] to gain access to additional traits and methods. @@ -73,16 +73,20 @@ impl From for Newtype { } impl TryFrom for PathBuf { - type Error = (); // FIXME + type Error = NotAString; fn try_from(wrapped: Newtype) -> Result { match wrapped.0 { Ipld::String(s) => Ok(PathBuf::from(s)), - _ => Err(()), + ipld => Err(NotAString(ipld)), } } } +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Error)] +#[error("Ipld variant is not a string")] +pub struct NotAString(pub Ipld); + #[cfg(target_arch = "wasm32")] impl Newtype { pub fn try_from_js>(js_val: JsValue) -> Result @@ -90,13 +94,12 @@ impl Newtype { JsError: From<>::Error>, { match Newtype::try_from(js_val) { - Err(_err) => Err(JsError::new("can't convert")), // FIXME + Err(_err) => Err(JsError::new("can't convert")), Ok(nt) => nt.0.try_into().map_err(JsError::from), } } } -// FIXME testme #[cfg(target_arch = "wasm32")] impl From for JsValue { fn from(wrapped: Newtype) -> Self { @@ -132,7 +135,6 @@ impl From for JsValue { } } -// FIXME testme #[cfg(target_arch = "wasm32")] impl TryFrom for Newtype { type Error = (); // FIXME diff --git a/src/ipld/promised.rs b/src/ipld/promised.rs index 52a0c907..781045a9 100644 --- a/src/ipld/promised.rs +++ b/src/ipld/promised.rs @@ -6,7 +6,7 @@ use crate::{ use enum_as_inner::EnumAsInner; use libipld_core::{cid::Cid, ipld::Ipld}; use serde::{Deserialize, Serialize}; -use std::{collections::BTreeMap, path::PathBuf}; +use std::{collections::BTreeMap, fmt, path::PathBuf}; /// A recursive data structure whose leaves may be [`Ipld`] or promises. /// @@ -72,6 +72,43 @@ impl Promised { } } +impl fmt::Display for Promised { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { + match self { + Promised::Null => write!(f, "null"), + Promised::Bool(b) => write!(f, "{}", b), + Promised::Integer(i) => write!(f, "{}", i), + Promised::Float(fl) => write!(f, "{}", fl), + Promised::String(s) => write!(f, "{}", s), + Promised::Bytes(b) => write!(f, "{:?}", b), + Promised::Link(cid) => write!(f, "{}", cid), + Promised::WaitOk(cid) => write!(f, "await/ok: {}", cid), + Promised::WaitErr(cid) => write!(f, "await/err: {}", cid), + Promised::WaitAny(cid) => write!(f, "await/*: {}", cid), + Promised::List(list) => { + write!(f, "[")?; + for (i, promised) in list.iter().enumerate() { + if i > 0 { + write!(f, ", ")?; + } + write!(f, "{}", promised)?; + } + write!(f, "]") + } + Promised::Map(map) => { + write!(f, "{{")?; + for (i, (k, v)) in map.iter().enumerate() { + if i > 0 { + write!(f, ", ")?; + } + write!(f, "{}: {}", k, v)?; + } + write!(f, "}}") + } + } + } +} + impl From for Promised { fn from(ipld: Ipld) -> Promised { match ipld { diff --git a/src/url.rs b/src/url.rs index bb8ca202..6cca29b2 100644 --- a/src/url.rs +++ b/src/url.rs @@ -1,6 +1,5 @@ //! URL utilities. -// use crate::proof::same::CheckSame; use libipld_core::ipld::Ipld; use serde::{Deserialize, Serialize}; use std::fmt; diff --git a/tests/conformance.rs b/tests/conformance.rs deleted file mode 100644 index 8e24da1f..00000000 --- a/tests/conformance.rs +++ /dev/null @@ -1,391 +0,0 @@ -// use libipld_core::{ipld::Ipld, raw::RawCodec}; -// use serde::{Deserialize, Serialize}; -// use std::{collections::HashMap, fs::File, io::BufReader}; - -// use ucan::{ -// capability::DefaultCapabilityParser, -// did_verifier::DidVerifierMap, -// store::{self, Store}, -// ucan::Ucan, -// DefaultFact, -// }; -// -// trait TestTask { -// fn run(&self, name: &str, report: &mut TestReport); -// } -// -// #[derive(Debug, Default)] -// struct TestReport { -// num_tests: usize, -// successes: Vec, -// failures: Vec, -// } -// -// #[derive(Debug)] -// struct TestFailure { -// name: String, -// error: String, -// } -// -// impl TestReport { -// fn register_success(&mut self, name: &str) { -// self.num_tests += 1; -// self.successes.push(name.to_string()); -// } -// -// fn register_failure(&mut self, name: &str, error: String) { -// self.num_tests += 1; -// self.failures.push(TestFailure { -// name: name.to_string(), -// error, -// }); -// } -// -// fn finish(&self) { -// for success in &self.successes { -// println!("✅ {}", success); -// } -// -// for failure in &self.failures { -// println!("❌ {}: {}", failure.name, failure.error); -// } -// -// println!( -// "{} tests, {} successes, {} failures", -// self.num_tests, -// self.successes.len(), -// self.failures.len() -// ); -// -// if !self.failures.is_empty() { -// panic!(); -// } -// } -// } -// -// #[derive(Debug, Serialize, Deserialize)] -// struct TestFixture { -// name: String, -// #[serde(flatten)] -// test_case: TestCase, -// } -// -// #[derive(Debug, Serialize, Deserialize)] -// #[serde(tag = "task", rename_all = "camelCase")] -// enum TestCase { -// Verify(VerifyTest), -// Refute(RefuteTest), -// Build(BuildTest), -// ToCID(ToCidTest), -// } -// -// #[derive(Debug, Serialize, Deserialize)] -// struct VerifyTest { -// inputs: TestInputsTokenAndProofs, -// assertions: TestAssertions, -// } -// -// #[derive(Debug, Serialize, Deserialize)] -// struct RefuteTest { -// inputs: TestInputsTokenAndProofs, -// assertions: TestAssertions, -// errors: Vec, -// } -// -// #[derive(Debug, Serialize, Deserialize)] -// struct BuildTest { -// inputs: BuildTestInputs, -// outputs: BuildTestOutputs, -// } -// -// #[derive(Debug, Serialize, Deserialize)] -// struct ToCidTest { -// inputs: ToCidTestInputs, -// outputs: ToCidTestOutputs, -// } -// -// #[derive(Debug, Serialize, Deserialize)] -// struct TestInputsTokenAndProofs { -// token: String, -// proofs: HashMap, -// } -// -// #[derive(Debug, Serialize, Deserialize)] -// struct TestAssertions { -// header: TestAssertionsHeader, -// payload: TestAssertionsPayload, -// signature: String, -// } -// -// #[derive(Debug, Serialize, Deserialize)] -// struct TestAssertionsHeader { -// alg: Option, -// typ: Option, -// } -// -// #[derive(Debug, Serialize, Deserialize)] -// struct TestAssertionsPayload { -// ucv: Option, -// iss: Option, -// aud: Option, -// exp: Option, -// // TODO: CAP -// // TODO: FCT -// prf: Option>, -// } -// -// #[derive(Debug, Serialize, Deserialize)] -// struct BuildTestInputs { -// version: Option, -// issuer_base64_key: String, -// signature_scheme: String, -// audience: Option, -// not_before: Option, -// expiration: Option, -// // TODO CAPABILITIES -// // TODO FACTS -// } -// -// #[derive(Debug, Serialize, Deserialize)] -// struct BuildTestOutputs { -// token: String, -// } -// -// #[derive(Debug, Serialize, Deserialize)] -// struct ToCidTestInputs { -// token: String, -// hasher: String, -// } -// -// // #[derive(Debug, Serialize, Deserialize)] -// // -// // struct ToCidTestOutputs { -// // cid: String, -// // } -// // -// // impl TestTask for VerifyTest { -// // fn run(&self, name: &str, report: &mut TestReport) { -// // let mut store = store::InMemoryStore::::default(); -// // let did_verifier_map = DidVerifierMap::default(); -// // -// // for (_cid, token) in self.inputs.proofs.iter() { -// // store -// // .write(Ipld::Bytes(token.as_bytes().to_vec()), None) -// // .unwrap(); -// // } -// // -// // let Ok(ucan) = Ucan::::from_str(&self.inputs.token) -// // else { -// // report.register_failure(name, "failed to parse token".to_string()); -// // -// // return; -// // }; -// // -// // if let Some(alg) = &self.assertions.header.alg { -// // if ucan.algorithm() != alg { -// // report.register_failure( -// // name, -// // format!( -// // "expected algorithm to be {}, but was {}", -// // alg, -// // ucan.algorithm() -// // ), -// // ); -// // -// // return; -// // } -// // } -// // -// // if let Some(typ) = &self.assertions.header.typ { -// // if ucan.typ() != typ { -// // report.register_failure( -// // name, -// // format!("expected type to be {}, but was {}", typ, ucan.typ()), -// // ); -// // -// // return; -// // } -// // } -// // -// // if let Some(ucv) = &self.assertions.payload.ucv { -// // if ucan.version() != ucv { -// // report.register_failure( -// // name, -// // format!("expected version to be {}, but was {}", ucv, ucan.version()), -// // ); -// // -// // return; -// // } -// // } -// // -// // if let Some(iss) = &self.assertions.payload.iss { -// // if ucan.issuer() != iss { -// // report.register_failure( -// // name, -// // format!("expected issuer to be {}, but was {}", iss, ucan.issuer()), -// // ); -// // -// // return; -// // } -// // } -// // -// // if let Some(aud) = &self.assertions.payload.aud { -// // if ucan.audience() != aud { -// // report.register_failure( -// // name, -// // format!( -// // "expected audience to be {}, but was {}", -// // aud, -// // ucan.audience() -// // ), -// // ); -// // -// // return; -// // } -// // } -// // -// // if ucan.expires_at() != self.assertions.payload.exp { -// // report.register_failure( -// // name, -// // format!( -// // "expected expiration to be {:?}, but was {:?}", -// // self.assertions.payload.exp, -// // ucan.expires_at() -// // ), -// // ); -// // -// // return; -// // } -// // -// // if ucan -// // .proofs() -// // .map(|f| f.iter().map(|c| c.to_string()).collect()) -// // != self.assertions.payload.prf -// // { -// // report.register_failure( -// // name, -// // format!( -// // "expected proofs to be {:?}, but was {:?}", -// // self.assertions.payload.prf, -// // ucan.proofs() -// // ), -// // ); -// // -// // return; -// // } -// // -// // let Ok(signature) = serde_json::to_value(ucan.signature()) else { -// // report.register_failure(name, "failed to serialize signature".to_string()); -// // -// // return; -// // }; -// // -// // let Some(signature) = signature.as_str() else { -// // report.register_failure(name, "expected signature to be a string".to_string()); -// // -// // return; -// // }; -// // -// // if signature != self.assertions.signature { -// // report.register_failure( -// // name, -// // format!( -// // "expected signature to be {}, but was {}", -// // self.assertions.signature, signature -// // ), -// // ); -// // -// // return; -// // } -// // -// // if let Err(err) = ucan.validate(ucan::time::now(), &did_verifier_map) { -// // report.register_failure(name, err.to_string()); -// // -// // return; -// // } -// // } -// // } -// // -// // impl TestTask for RefuteTest { -// // fn run(&self, name: &str, report: &mut TestReport) { -// // let mut store = store::InMemoryStore::::default(); -// // let did_verifier_map = DidVerifierMap::default(); -// // -// // for (_cid, token) in self.inputs.proofs.iter() { -// // store -// // .write(Ipld::Bytes(token.as_bytes().to_vec()), None) -// // .unwrap(); -// // } -// // -// // if let Ok(ucan) = Ucan::::from_str(&self.inputs.token) -// // { -// // if ucan.validate(ucan::time::now(), &did_verifier_map).is_ok() { -// // report.register_failure( -// // &name, -// // "expected token to fail validation, but it passed".to_string(), -// // ); -// // -// // return; -// // } -// // } -// // } -// // } -// // -// // impl TestTask for BuildTest { -// // fn run(&self, _: &str, _: &mut TestReport) { -// // //TODO: can't assert on signature because of canonicalization issues -// // } -// // } -// // -// // impl TestTask for ToCidTest { -// // fn run(&self, name: &str, report: &mut TestReport) { -// // let ucan = -// // Ucan::::from_str(&self.inputs.token).unwrap(); -// // let hasher = match self.inputs.hasher.as_str() { -// // "SHA2-256" => multihash::Code::Sha2_256, -// // "BLAKE3-256" => multihash::Code::Blake3_256, -// // _ => panic!(), -// // }; -// // -// // let Ok(cid) = ucan.to_cid(Some(hasher)) else { -// // report.register_failure(&name, "failed to convert to CID".to_string()); -// // -// // return; -// // }; -// // -// // if cid.to_string() != self.outputs.cid { -// // report.register_failure( -// // &name, -// // format!( -// // "expected CID to be {}, but was {}", -// // self.outputs.cid, -// // cid.to_string() -// // ), -// // ); -// // -// // return; -// // } -// // } -// // } -// // -// // #[test] -// // fn ucan_0_10_0_conformance_tests() { -// // let fixtures_file = File::open("tests/fixtures/0.10.0/all.json").unwrap(); -// // let reader = BufReader::new(fixtures_file); -// // let fixtures: Vec = serde_json::from_reader(reader).unwrap(); -// // -// // let mut report = TestReport::default(); -// // -// // for fixture in fixtures { -// // match fixture.test_case { -// // TestCase::Verify(test) => test.run(&fixture.name, &mut report), -// // TestCase::Refute(test) => test.run(&fixture.name, &mut report), -// // TestCase::Build(test) => test.run(&fixture.name, &mut report), -// // TestCase::ToCID(test) => test.run(&fixture.name, &mut report), -// // }; -// // -// // report.register_success(&fixture.name); -// // } -// // -// // report.finish(); -// // } From 6055dd2362d136ae8c800bb62d323ca7d56b8586 Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Tue, 5 Mar 2024 12:03:43 -0800 Subject: [PATCH 114/188] Ahead of ripping out devshell for blst --- flake.lock | 30 +++++++++++++++--------------- flake.nix | 10 ++++++++-- 2 files changed, 23 insertions(+), 17 deletions(-) diff --git a/flake.lock b/flake.lock index 51184076..8cb8586e 100644 --- a/flake.lock +++ b/flake.lock @@ -6,11 +6,11 @@ "nixpkgs": "nixpkgs" }, "locked": { - "lastModified": 1705332421, - "narHash": "sha256-USpGLPme1IuqG78JNqSaRabilwkCyHmVWY0M9vYyqEA=", + "lastModified": 1708939976, + "narHash": "sha256-O5+nFozxz2Vubpdl1YZtPrilcIXPcRAjqNdNE8oCRoA=", "owner": "numtide", "repo": "devshell", - "rev": "83cb93d6d063ad290beee669f4badf9914cc16ec", + "rev": "5ddecd67edbd568ebe0a55905273e56cc82aabe3", "type": "github" }, "original": { @@ -42,11 +42,11 @@ "systems": "systems_2" }, "locked": { - "lastModified": 1705309234, - "narHash": "sha256-uNRRNRKmJyCRC/8y1RqBkqWBLM034y4qN7EprSdmgyA=", + "lastModified": 1709126324, + "narHash": "sha256-q6EQdSeUZOG26WelxqkmR7kArjgWCdw5sfJVHPH/7j8=", "owner": "numtide", "repo": "flake-utils", - "rev": "1ef2e671c3b0c19053962c07dbda38332dcebf26", + "rev": "d465f4819400de7c8d874d50b982301f28a84605", "type": "github" }, "original": { @@ -57,11 +57,11 @@ }, "nixos-unstable": { "locked": { - "lastModified": 1705556346, - "narHash": "sha256-2+ZUEFCKlctTsut81S84xkCccMsZLLX7DA/U3xZ3BqY=", + "lastModified": 1709558755, + "narHash": "sha256-hx4FIbk4X4ve1oiHLOj+VE6dzO4CtXBR5RSU6kaq34M=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "cefcf19e1c6d4255b2aede5535d04064f6917e9b", + "rev": "207107bbc7d6d19a8b2c36a088d3756d03490243", "type": "github" }, "original": { @@ -88,11 +88,11 @@ }, "nixpkgs_2": { "locked": { - "lastModified": 1705458851, - "narHash": "sha256-uQvEhiv33Zj/Pv364dTvnpPwFSptRZgVedDzoM+HqVg=", + "lastModified": 1709569716, + "narHash": "sha256-iOR44RU4jQ+YPGrn+uQeYAp7Xo7Z/+gT+wXJoGxxLTY=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "8bf65f17d8070a0a490daf5f1c784b87ee73982c", + "rev": "617579a787259b9a6419492eaac670a5f7663917", "type": "github" }, "original": { @@ -120,11 +120,11 @@ ] }, "locked": { - "lastModified": 1705544242, - "narHash": "sha256-LIi5jGx7kwJjodpJlnQY+X/PZspRpbDO2ypNSmHwOGQ=", + "lastModified": 1709604635, + "narHash": "sha256-le4fwmWmjGRYWwkho0Gr7mnnZndOOe4XGbLw68OvF40=", "owner": "oxalica", "repo": "rust-overlay", - "rev": "ff3e4b3ee418009886848d48e4ba236a2f9de789", + "rev": "e86c0fb5d3a22a5f30d7f64ecad88643fe26449d", "type": "github" }, "original": { diff --git a/flake.nix b/flake.nix index bee92820..79dc3ef2 100644 --- a/flake.nix +++ b/flake.nix @@ -30,8 +30,14 @@ (import rust-overlay) ]; - pkgs = import nixpkgs {inherit system overlays;}; - unstable = import nixos-unstable {inherit system overlays;}; + pkgs = import nixpkgs { + inherit system overlays; + config = {replaceStdenv = {pkgs}: pkgs.clangStdenv;}; + }; + unstable = import nixos-unstable { + inherit system overlays; + config = {replaceStdenv = {pkgs}: pkgs.clangStdenv;}; + }; rust-toolchain = (pkgs.rust-bin.fromRustupToolchainFile ./rust-toolchain.toml).override { extensions = [ From cd22a59bfe7e2991041a8089e664aa4fb91d3f98 Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Tue, 5 Mar 2024 12:12:27 -0800 Subject: [PATCH 115/188] Begn conversion --- flake.nix | 43 +++++++++++++++++++++++++++++-------------- 1 file changed, 29 insertions(+), 14 deletions(-) diff --git a/flake.nix b/flake.nix index 79dc3ef2..f8222ad5 100644 --- a/flake.nix +++ b/flake.nix @@ -93,21 +93,20 @@ node = "${unstable.nodejs_20}/bin/node"; wasm-pack = "${pkgs.wasm-pack}/bin/wasm-pack"; wasm-opt = "${pkgs.binaryen}/bin/wasm-opt"; + + scripts = [ + ]; + in rec { formatter = pkgs.alejandra; # NOTE: blst requires --target=wasm32 support in Clang, which MacOS system clang doesn't provide - # FIXME This explicitely does not get pulled in under devshell - # stdenv = pkgs.clangStdenv; + stdenv = pkgs.clangStdenv; - devShells.default = pkgs.devshell.mkShell { + devShells.default = pkgs.mkShell { name = "ucan"; - imports = [ - ./pre-commit.nix - ]; - - packages = with pkgs; + nativeBuildInputs = with pkgs; [ direnv rust-toolchain @@ -121,14 +120,30 @@ ] ++ format-pkgs ++ cargo-installs + ++ scripts ++ lib.optionals stdenv.isDarwin darwin-installs; - env = [ - { - name = "RUSTC_WRAPPER"; - value = "${pkgs.sccache}/bin/sccache"; - } - ]; + shellHook = '' + [ -e .git/hooks/pre-commit ] || pre-commit install --install-hooks && pre-commit install --hook-type commit-msg + + # + export RUSTC_WRAPPER="${pkgs.sccache}/bin/sccache" + + # Setup local Kubo config + if [ ! -e ./.ipfs ]; then + ipfs --repo-dir ./.ipfs --offline init + fi + + unset SOURCE_DATE_EPOCH + + # Run Kubo / IPFS + echo -e "To run Kubo as a local IPFS node, use the following command:" + echo -e "ipfs --repo-dir ./.ipfs --offline daemon" + '' + pkgs.lib.strings.optionalString pkgs.stdenv.isDarwin '' + # See https://github.com/nextest-rs/nextest/issues/267 + export DYLD_FALLBACK_LIBRARY_PATH="$(rustc --print sysroot)/lib" + export NIX_LDFLAGS="-F${pkgs.darwin.apple_sdk.frameworks.CoreFoundation}/Library/Frameworks -framework CoreFoundation $NIX_LDFLAGS"; + ''; commands = [ # Release From f5e9cc55c1fe697fa26abd48542afb175832d169 Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Tue, 5 Mar 2024 21:04:39 -0800 Subject: [PATCH 116/188] Recover command menu --- Cargo.toml | 58 ++++--- flake.lock | 85 ++++++++-- flake.nix | 377 ++++++++++++++++---------------------------- pre-commit.nix | 7 - src/ipld/newtype.rs | 3 + src/ipld/number.rs | 1 + 6 files changed, 240 insertions(+), 291 deletions(-) delete mode 100644 pre-commit.nix diff --git a/Cargo.toml b/Cargo.toml index 73c08fbc..397845d4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -31,48 +31,46 @@ name = "counterparts" path = "examples/counterparts.rs" [dependencies] -getrandom = { version = "0.2", features = ["js", "rdrand"] } -nonempty = { version = "0.9" } -proptest = { version = "1.1", optional = true } -regex = "1.10" -unsigned-varint = "0.7.2" -# Crypto -blst = { version = "0.3.11", optional = true, default-features = false } -ecdsa = { version = "0.16.8", features = ["alloc"], optional = true, default-features = false } -ed25519-dalek = { version = "2.0.0", features = ["rand_core"], optional = true } -k256 = { version = "0.13.1", features = ["ecdsa"], optional = true, default-features = false } -p256 = { version = "0.13.2", features = ["alloc", "ecdsa"], optional = true, default-features = false } -p384 = { version = "0.13.0", features = ["alloc", "ecdsa"], optional = true, default-features = false } -p521 = { version = "0.13.3", features = ["alloc", "ecdsa", "getrandom"], optional = true, default-features = false } -rsa = { version = "0.9.6", features = ["sha2", "std"], optional = true, default-features = false } -signature = { version = "2.1.0", features = ["alloc"] } +# Docs +aquamarine = { version = "0.5", optional = true } # Encoding base64 = "0.21" + +# Crypto +blst = { version = "0.3.11", optional = true, default-features = false } bs58 = "0.5" -serde = { version = "1.0.188", features = ["derive"] } -serde_derive = "1.0" -nom = "7.1" # Web Stack did_url = "0.1" -url = { version = "2.5", features = ["serde"] } -web-time = "0.2.3" +ecdsa = { version = "0.16.8", features = ["alloc"], optional = true, default-features = false } +ed25519-dalek = { version = "2.0.0", features = ["rand_core"], optional = true } + +# Code Convenience +enum-as-inner = "0.6" +getrandom = { version = "0.2", features = ["js", "rdrand"] } +k256 = { version = "0.13.1", features = ["ecdsa"], optional = true, default-features = false } # Interplanetary Stack libipld = { version = "0.16", optional = true } -libipld-core = { version = "0.16", features = ["serde-codec"] } libipld-cbor = "0.16" +libipld-core = { version = "0.16", features = ["serde-codec"] } multihash = { version = "0.18" } - -# Code Convenience -enum-as-inner = "0.6" +nom = "7.1" +nonempty = { version = "0.9" } +p256 = { version = "0.13.2", features = ["alloc", "ecdsa"], optional = true, default-features = false } +p384 = { version = "0.13.0", features = ["alloc", "ecdsa"], optional = true, default-features = false } +p521 = { version = "0.13.3", features = ["alloc", "ecdsa", "getrandom"], optional = true, default-features = false } +proptest = { version = "1.1", optional = true } +rsa = { version = "0.9.6", features = ["sha2", "std"], optional = true, default-features = false } +serde = { version = "1.0.188", features = ["derive"] } +serde_derive = "1.0" +signature = { version = "2.1.0", features = ["alloc"] } thiserror = "1.0" - -# Docs -aquamarine = { version = "0.5", optional = true } - +unsigned-varint = "0.7.2" +url = { version = "2.5", features = ["serde"] } +web-time = "0.2.3" # FIXME actually use? async-signature = "0.4.0" # FIXME also have a wasi target @@ -82,7 +80,7 @@ js-sys = { version = "0.3" } serde-wasm-bindgen = "0.6" wasm-bindgen = "0.2" wasm-bindgen-derive = "0.2" -# FIXME? wasm-bindgen-futures = { version = "0.4" } +# wasm-bindgen-futures = { version = "0.4" } web-sys = { version = "0.3", features = ["Crypto", "CryptoKey", "CryptoKeyPair", "SubtleCrypto"] } [dev-dependencies] @@ -111,7 +109,7 @@ default = [ "ability-preset", # FIXME temp while developing - "test_utils", + # "test_utils", ] test_utils = ["dep:proptest", "dep:libipld"] diff --git a/flake.lock b/flake.lock index 8cb8586e..ca8b1cb2 100644 --- a/flake.lock +++ b/flake.lock @@ -1,10 +1,29 @@ { "nodes": { - "devshell": { + "command-utils": { "inputs": { "flake-utils": "flake-utils", "nixpkgs": "nixpkgs" }, + "locked": { + "lastModified": 1709698716, + "narHash": "sha256-l1o+s6MQo5Z/0+0wNmjO5z0qRk5iGJPpFnPM48a5Sfg=", + "owner": "expede", + "repo": "nix-command-utils", + "rev": "2cdf5fe375f8206f10dd86d5f79ef1e2fadc5968", + "type": "github" + }, + "original": { + "owner": "expede", + "repo": "nix-command-utils", + "type": "github" + } + }, + "devshell": { + "inputs": { + "flake-utils": "flake-utils_2", + "nixpkgs": "nixpkgs_2" + }, "locked": { "lastModified": 1708939976, "narHash": "sha256-O5+nFozxz2Vubpdl1YZtPrilcIXPcRAjqNdNE8oCRoA=", @@ -23,6 +42,23 @@ "inputs": { "systems": "systems" }, + "locked": { + "lastModified": 1709126324, + "narHash": "sha256-q6EQdSeUZOG26WelxqkmR7kArjgWCdw5sfJVHPH/7j8=", + "owner": "numtide", + "repo": "flake-utils", + "rev": "d465f4819400de7c8d874d50b982301f28a84605", + "type": "github" + }, + "original": { + "id": "flake-utils", + "type": "indirect" + } + }, + "flake-utils_2": { + "inputs": { + "systems": "systems_2" + }, "locked": { "lastModified": 1701680307, "narHash": "sha256-kAuep2h5ajznlPMD9rnQyffWG8EM/C73lejGofXvdM8=", @@ -37,9 +73,9 @@ "type": "github" } }, - "flake-utils_2": { + "flake-utils_3": { "inputs": { - "systems": "systems_2" + "systems": "systems_3" }, "locked": { "lastModified": 1709126324, @@ -71,6 +107,21 @@ } }, "nixpkgs": { + "locked": { + "lastModified": 1709569716, + "narHash": "sha256-iOR44RU4jQ+YPGrn+uQeYAp7Xo7Z/+gT+wXJoGxxLTY=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "617579a787259b9a6419492eaac670a5f7663917", + "type": "github" + }, + "original": { + "id": "nixpkgs", + "ref": "nixos-23.11", + "type": "indirect" + } + }, + "nixpkgs_2": { "locked": { "lastModified": 1704161960, "narHash": "sha256-QGua89Pmq+FBAro8NriTuoO/wNaUtugt29/qqA8zeeM=", @@ -86,7 +137,7 @@ "type": "github" } }, - "nixpkgs_2": { + "nixpkgs_3": { "locked": { "lastModified": 1709569716, "narHash": "sha256-iOR44RU4jQ+YPGrn+uQeYAp7Xo7Z/+gT+wXJoGxxLTY=", @@ -103,10 +154,11 @@ }, "root": { "inputs": { + "command-utils": "command-utils", "devshell": "devshell", - "flake-utils": "flake-utils_2", + "flake-utils": "flake-utils_3", "nixos-unstable": "nixos-unstable", - "nixpkgs": "nixpkgs_2", + "nixpkgs": "nixpkgs_3", "rust-overlay": "rust-overlay" } }, @@ -120,11 +172,11 @@ ] }, "locked": { - "lastModified": 1709604635, - "narHash": "sha256-le4fwmWmjGRYWwkho0Gr7mnnZndOOe4XGbLw68OvF40=", + "lastModified": 1709691047, + "narHash": "sha256-2Vwx1FLufoMEcOS8KAwP8H83IP3Hw6ZPrIDHkSXrFCY=", "owner": "oxalica", "repo": "rust-overlay", - "rev": "e86c0fb5d3a22a5f30d7f64ecad88643fe26449d", + "rev": "d55139f3061cdf2c8f5f7bc8d49e884826e6a4ea", "type": "github" }, "original": { @@ -162,6 +214,21 @@ "repo": "default", "type": "github" } + }, + "systems_3": { + "locked": { + "lastModified": 1681028828, + "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", + "owner": "nix-systems", + "repo": "default", + "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", + "type": "github" + }, + "original": { + "owner": "nix-systems", + "repo": "default", + "type": "github" + } } }, "root": "root", diff --git a/flake.nix b/flake.nix index f8222ad5..b0935515 100644 --- a/flake.nix +++ b/flake.nix @@ -5,6 +5,8 @@ nixpkgs.url = "nixpkgs/nixos-23.11"; nixos-unstable.url = "nixpkgs/nixos-unstable-small"; + command-utils.url = "github:expede/nix-command-utils"; + flake-utils.url = "github:numtide/flake-utils"; devshell.url = "github:numtide/devshell"; @@ -22,6 +24,7 @@ nixos-unstable, nixpkgs, rust-overlay, + command-utils } @ inputs: flake-utils.lib.eachDefaultSystem ( system: let @@ -32,11 +35,10 @@ pkgs = import nixpkgs { inherit system overlays; - config = {replaceStdenv = {pkgs}: pkgs.clangStdenv;}; }; + unstable = import nixos-unstable { inherit system overlays; - config = {replaceStdenv = {pkgs}: pkgs.clangStdenv;}; }; rust-toolchain = (pkgs.rust-bin.fromRustupToolchainFile ./rust-toolchain.toml).override { @@ -94,18 +96,123 @@ wasm-pack = "${pkgs.wasm-pack}/bin/wasm-pack"; wasm-opt = "${pkgs.binaryen}/bin/wasm-opt"; - scripts = [ - ]; + cmd = command-utils.cmd.${system}; - in rec { - formatter = pkgs.alejandra; + release = { + "release:host" = cmd "Build release for ${system}" + "${cargo} build --release"; + + "release:wasm:web" = cmd "Build release for wasm32-unknown-unknown with web bindings" + "${wasm-pack} build --release --target=web"; + + "release:wasm:nodejs" = cmd "Build release for wasm32-unknown-unknown with Node.js bindgings" + "${wasm-pack} build --release --target=nodejs"; + }; + + build = { + "build:host" = cmd "Build for ${system}" + "${cargo} build"; + + "build:wasm:web" = cmd "Build for wasm32-unknown-unknown with web bindings" + "${wasm-pack} build --dev --target=web"; + + "build:wasm:nodejs" = cmd "Build for wasm32-unknown-unknown with Node.js bindgings" + "${wasm-pack} build --dev --target=nodejs"; + + "build:node" = cmd "Build JS-wrapped Wasm library" + "${pkgs.nodePackages.pnpm}/bin/pnpm install && ${node} run build"; + + "build:wasi" = cmd "Build for Wasm32-WASI" + "${cargo} build --target wasm32-wasi"; + }; + + bench = { + "bench" = cmd "Run benchmarks, including test utils" + "${cargo} bench --features test_utils"; + + # FIXME align with `bench`? + "bench:host" = cmd "Run host Criterion benchmarks" + "${cargo} criterion"; + + "bench:host:open" = cmd "Open host Criterion benchmarks in browser" + "${pkgs.xdg-utils}/bin/xdg-open ./target/criterion/report/index.html"; + }; + + lint = { + "lint" = cmd "Run Clippy" + "${cargo} clippy"; + + "lint:pedantic" = cmd "Run Clippy pedantically" + "${cargo} clippy -- -W clippy::pedantic"; + + "lint:fix" = cmd "Apply non-pendantic Clippy suggestions" + "${cargo} clippy --fix"; + }; + + watch = { + "watch:build:host" = cmd "Rebuild host target on save" + "${cargo} watch --clear"; + + "watch:build:wasm" = cmd "Rebuild Wasm target on save" + "${cargo} watch --clear --features=serde -- cargo build --target=wasm32-unknown-unknown"; - # NOTE: blst requires --target=wasm32 support in Clang, which MacOS system clang doesn't provide - stdenv = pkgs.clangStdenv; + "watch:lint" = cmd "Lint on save" + "${cargo} watch --clear --exec clippy"; + "watch:lint:pedantic" = cmd "Pedantic lint on save" + "${cargo} watch --clear --exec 'clippy -- -W clippy::pedantic'"; + + "watch:test:host" = cmd "Run all host tests on save" + "${cargo} watch --clear --exec 'test --features=mermaid_docs'"; + + "watch:test:wasm" = cmd "Run all Wasm tests on save" + "${cargo} watch --clear --exec 'test --target=wasm32-unknown-unknown'"; + }; + + test = { + "test:all" = cmd "Run Cargo tests" + "test:host && test:docs && test:wasm"; + + "test:host" = cmd "Run Cargo tests for host target" + "${cargo} test"; + + "test:wasm" = cmd "Run wasm-pack tests on all targets" + "test:wasm:node && test:wasm:chrome"; + + "test:wasm:node" = cmd "Run wasm-pack tests in Node.js" + "${wasm-pack} test --node"; + + "test:wasm:chrome" = cmd "Run wasm-pack tests in headless Chrome" + "${wasm-pack} test --headless --chrome"; + + "test:docs" = cmd "Run Cargo doctests" + "${cargo} test --doc --features=mermaid_docs"; + }; + + docs = { + "docs:build:host" = cmd "Refresh the docs" + "${cargo} doc --features=mermaid_docs"; + + "docs:build:wasm" = cmd "Refresh the docs with the wasm32-unknown-unknown target" + "${cargo} doc --features=mermaid_docs --target=wasm32-unknown-unknown"; + + "docs:open:host" = cmd "Open refreshed docs" + "${cargo} doc --features=mermaid_docs --open"; + + "docs:open:wasm" = cmd "Open refreshed docs" + "${cargo} doc --features=mermaid_docs --open --target=wasm32-unknown-unknown"; + }; + + command_menu = command-utils.commands.${system} + (release // build // bench // lint // watch // test // docs); + + in rec { devShells.default = pkgs.mkShell { name = "ucan"; + # NOTE: blst requires --target=wasm32 support in Clang, which MacOS system clang doesn't provide + stdenv = pkgs.clangStdenv; + nativeBuildInputs = with pkgs; [ direnv @@ -113,254 +220,34 @@ self.packages.${system}.irust (pkgs.hiPrio pkgs.rust-bin.nightly.latest.rustfmt) + pre-commit + pkgs.wasm-pack chromedriver protobuf unstable.nodejs_20 unstable.nodePackages.pnpm + + command_menu ] ++ format-pkgs ++ cargo-installs - ++ scripts ++ lib.optionals stdenv.isDarwin darwin-installs; - shellHook = '' - [ -e .git/hooks/pre-commit ] || pre-commit install --install-hooks && pre-commit install --hook-type commit-msg - - # - export RUSTC_WRAPPER="${pkgs.sccache}/bin/sccache" - - # Setup local Kubo config - if [ ! -e ./.ipfs ]; then - ipfs --repo-dir ./.ipfs --offline init - fi - - unset SOURCE_DATE_EPOCH - - # Run Kubo / IPFS - echo -e "To run Kubo as a local IPFS node, use the following command:" - echo -e "ipfs --repo-dir ./.ipfs --offline daemon" - '' + pkgs.lib.strings.optionalString pkgs.stdenv.isDarwin '' - # See https://github.com/nextest-rs/nextest/issues/267 - export DYLD_FALLBACK_LIBRARY_PATH="$(rustc --print sysroot)/lib" - export NIX_LDFLAGS="-F${pkgs.darwin.apple_sdk.frameworks.CoreFoundation}/Library/Frameworks -framework CoreFoundation $NIX_LDFLAGS"; - ''; - - commands = [ - # Release - { - name = "release"; - help = "[DEFAULT] Release (optimized build) for current host target"; - category = "release"; - command = "release:host"; - } - { - name = "release:host"; - help = "Release for current host target"; - category = "release"; - command = "${cargo} build --release"; - } - { - name = "release:wasm:web"; - help = "Release for current host target"; - category = "release"; - command = "${wasm-pack} build --release --target=web"; - } - { - name = "release:wasm:nodejs"; - help = "Release for current host target"; - category = "release"; - command = "${wasm-pack} build --release --target=nodejs"; - } - # Build - { - name = "build"; - help = "[DEFAULT] Build for current host target"; - category = "build"; - command = "build:host"; - } - { - name = "build:host"; - help = "Build for current host target"; - category = "build"; - command = "${cargo} build"; - } - { - name = "build:wasm:web"; - help = "Build for wasm32-unknown-unknown with web bindings"; - category = "build"; - command = "${wasm-pack} build --dev --target=web"; - } - { - name = "build:wasm:nodejs"; - help = "Build for wasm32-unknown-unknown with Node.js bindgings"; - category = "build"; - command = "${wasm-pack} build --dev --target=nodejs"; - } - { - name = "build:node"; - help = "Build JS-wrapped Wasm library"; - category = "build"; - command = "${pkgs.nodePackages.pnpm}/bin/pnpm install && ${node} run build"; - } - { - name = "build:wasi"; - help = "Build for WASI"; - category = "build"; - command = "${cargo} build --target wasm32-wasi"; - } - # Bench - { - name = "bench"; - help = "Run benchmarks, including test utils"; - category = "dev"; - command = "${cargo} bench --features test_utils"; - } - # FIXME align with `bench`? - { - name = "bench:host"; - help = "Run host Criterion benchmarks"; - category = "dev"; - command = "${cargo} criterion"; - } - { - name = "bench:host:open"; - help = "Open host Criterion benchmarks in browser"; - category = "dev"; - command = "${pkgs.xdg-utils}/bin/xdg-open ./target/criterion/report/index.html"; - } - # Lint - { - name = "lint"; - help = "Run Clippy"; - category = "dev"; - command = "${cargo} clippy"; - } - { - name = "lint:pedantic"; - help = "Run Clippy pedantically"; - category = "dev"; - command = "${cargo} clippy -- -W clippy::pedantic"; - } - { - name = "lint:fix"; - help = "Apply non-pendantic Clippy suggestions"; - category = "dev"; - command = "${cargo} clippy --fix"; - } - # Watch - { - name = "watch:build:host"; - help = "Rebuild host target on save"; - category = "watch"; - command = "${cargo} watch --clear"; - } - { - name = "watch:build:wasm"; - help = "Rebuild host target on save"; - category = "watch"; - command = "${cargo} watch --clear --features=serde -- cargo build --target=wasm32-unknown-unknown"; - } - { - name = "watch:lint"; - help = "Lint on save"; - category = "watch"; - command = "${cargo} watch --clear --exec clippy"; - } - { - name = "watch:lint:pedantic"; - help = "Pedantic lint on save"; - category = "watch"; - command = "${cargo} watch --clear --exec 'clippy -- -W clippy::pedantic'"; - } - { - name = "watch:test:host"; - help = "Run all tests on save"; - category = "watch"; - command = "${cargo} watch --clear --exec 'test --features=mermaid_docs'"; - } - { - name = "watch:test:wasm"; - help = "Run all tests on save"; - category = "watch"; - command = "${cargo} watch --clear --exec 'test --target=wasm32-unknown-unknown'"; - } - # Test - { - name = "test:all"; - help = "Run Cargo tests"; - category = "test"; - command = "test:host && test:docs && test:wasm"; - } - { - name = "test:host"; - help = "Run Cargo tests for host target"; - category = "test"; - command = "${cargo} test"; - } - { - name = "test:wasm"; - help = "Run wasm-pack tests on all targets"; - category = "test"; - command = "test:wasm:node && test:wasm:chrome"; - } - { - name = "test:wasm:nodejs"; - help = "Run wasm-pack tests in Node.js"; - category = "test"; - command = "${wasm-pack} test --node"; - } - { - name = "test:wasm:chrome"; - help = "Run wasm-pack tests in headless Chrome"; - category = "test"; - command = "${wasm-pack} test --headless --chrome"; - } - { - name = "test:docs"; - help = "Run Cargo doctests"; - category = "test"; - command = "${cargo} test --doc --features=mermaid_docs"; - } - # Docs - { - name = "docs"; - help = "[DEFAULT]: Open refreshed docs"; - category = "dev"; - command = "docs:open:host"; - } - { - name = "docs:build:host"; - help = "Refresh the docs"; - category = "dev"; - command = "${cargo} doc --features=mermaid_docs"; - } - { - name = "docs:build:wasm"; - help = "Refresh the docs with the wasm32-unknown-unknown target"; - category = "dev"; - command = "${cargo} doc --features=mermaid_docs --target=wasm32-unknown-unknown"; - } - { - name = "docs:open:host"; - help = "Open refreshed docs"; - category = "dev"; - command = "${cargo} doc --features=mermaid_docs --open"; - } - { - name = "docs:open:wasm"; - help = "Open refreshed docs"; - category = "dev"; - command = "${cargo} doc --features=mermaid_docs --open --target=wasm32-unknown-unknown"; - } - { - name = "docs:wasm:open"; - help = "Open refreshed docs for wasm32-unknown-unknown"; - category = "dev"; - command = "${cargo} doc --features=mermaid_docs --target=wasm32-unknown-unknown --open"; - } - ]; + shellHook = '' + [ -e .git/hooks/pre-commit ] || pre-commit install --install-hooks && pre-commit install --hook-type commit-msg + + export RUSTC_WRAPPER="${pkgs.sccache}/bin/sccache" + unset SOURCE_DATE_EPOCH + '' + + pkgs.lib.strings.optionalString pkgs.stdenv.isDarwin '' + # See https://github.com/nextest-rs/nextest/issues/267 + export DYLD_FALLBACK_LIBRARY_PATH="$(rustc --print sysroot)/lib" + export NIX_LDFLAGS="-F${pkgs.darwin.apple_sdk.frameworks.CoreFoundation}/Library/Frameworks -framework CoreFoundation $NIX_LDFLAGS"; + ''; }; + formatter = pkgs.alejandra; + packages.irust = pkgs.rustPlatform.buildRustPackage rec { pname = "irust"; version = "1.71.19"; diff --git a/pre-commit.nix b/pre-commit.nix deleted file mode 100644 index 83e370ba..00000000 --- a/pre-commit.nix +++ /dev/null @@ -1,7 +0,0 @@ -{pkgs, ...}: let - pc = "${pkgs.pre-commit}/bin/pre-commit"; -in { - config.devshell.startup.pre-commit.text = '' - [ -e .git/hooks/pre-commit ] || (${pc} install --install-hooks && ${pc} install --hook-type commit-msg) - ''; -} diff --git a/src/ipld/newtype.rs b/src/ipld/newtype.rs index dc20cb14..1127820b 100644 --- a/src/ipld/newtype.rs +++ b/src/ipld/newtype.rs @@ -15,6 +15,9 @@ use proptest::prelude::*; #[cfg(feature = "test_utils")] use super::cid; +#[cfg(target_arch = "wasm32")] +use super::cid; + /// A newtype wrapper around [`Ipld`] that has additional trait implementations. /// /// Usage is very simple: wrap a [`Newtype`] to gain access to additional traits and methods. diff --git a/src/ipld/number.rs b/src/ipld/number.rs index c4e7d08d..15e3bd0b 100644 --- a/src/ipld/number.rs +++ b/src/ipld/number.rs @@ -60,6 +60,7 @@ impl From for Number { } } +#[cfg(feature = "test_utils")] impl Arbitrary for Number { type Parameters = (); type Strategy = BoxedStrategy; From 454117001607b60c24f2b3d435b5c1d89e6cc721 Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Tue, 5 Mar 2024 22:35:35 -0800 Subject: [PATCH 117/188] Plugging various FIXMEs --- flake.lock | 6 +- src/ability/arguments.rs | 2 +- src/ability/arguments/named.rs | 17 ++- src/ability/crud/destroy.rs | 15 ++- src/ability/crud/js.rs | 22 +--- src/ability/crud/update.rs | 25 +++- src/ability/dynamic.rs | 2 +- src/ability/parse.rs | 2 - src/ability/wasm/module.rs | 3 +- src/crypto/signature/envelope.rs | 5 - src/crypto/varsig/header/preset.rs | 4 +- src/delegation/store/memory.rs | 2 +- src/delegation/store/traits.rs | 5 +- src/did/key.rs | 2 +- src/did/key/traits.rs | 5 +- src/did/key/verifier.rs | 181 ++++++++++++++++++++--------- src/did/preset.rs | 26 ++++- src/ipld.rs | 4 +- src/ipld/newtype.rs | 1 + src/time/timestamp.rs | 2 +- 20 files changed, 209 insertions(+), 122 deletions(-) diff --git a/flake.lock b/flake.lock index ca8b1cb2..a4fa5849 100644 --- a/flake.lock +++ b/flake.lock @@ -6,11 +6,11 @@ "nixpkgs": "nixpkgs" }, "locked": { - "lastModified": 1709698716, - "narHash": "sha256-l1o+s6MQo5Z/0+0wNmjO5z0qRk5iGJPpFnPM48a5Sfg=", + "lastModified": 1709701816, + "narHash": "sha256-Kwv17invnVzrNrm5fK3Bt6ISJqfXguCx6vc3JDOQtCE=", "owner": "expede", "repo": "nix-command-utils", - "rev": "2cdf5fe375f8206f10dd86d5f79ef1e2fadc5968", + "rev": "12056907b5194b82060fd8bc6ea11c9fdffb5f25", "type": "github" }, "original": { diff --git a/src/ability/arguments.rs b/src/ability/arguments.rs index 6137a358..d82e6b0b 100644 --- a/src/ability/arguments.rs +++ b/src/ability/arguments.rs @@ -2,7 +2,7 @@ mod named; -pub use named::{Named, NamedError}; +pub use named::*; use crate::{invocation::promise::Resolves, ipld}; use libipld_core::ipld::Ipld; diff --git a/src/ability/arguments/named.rs b/src/ability/arguments/named.rs index fc844416..16cee2c0 100644 --- a/src/ability/arguments/named.rs +++ b/src/ability/arguments/named.rs @@ -1,6 +1,6 @@ use crate::{ invocation::promise::{Pending, Resolves}, - ipld, + ipld, ability::crud::update::TryFromIpldError, }; use libipld_core::ipld::Ipld; use serde::{Deserialize, Serialize}; @@ -211,17 +211,26 @@ impl From> for JsValue { #[cfg(target_arch = "wasm32")] impl TryFrom for Named { - type Error = (); // FIXME + type Error = TryFromJsValueError; fn try_from(js: JsValue) -> Result { match T::try_from(js) { - Err(()) => Err(()), // FIXME surface that we can't parse at all + Err(()) => Err(TryFromJsValueError::NotIpld), Ok(Ipld::Map(map)) => Ok(Named(map)), - Ok(_wrong_ipld) => Err(()), // FIXME surface that we have the wrong type + Ok(_wrong_ipld) => Err(TryFromJsValueError::NotAMap), } } } +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Error)] +pub enum TryFromJsValueError { + #[error("Not a map")] + NotAMap, + + #[error("Not Ipld")] + NotIpld +} + impl From> for Named> { fn from(named: Named) -> Named> { let btree: BTreeMap> = named diff --git a/src/ability/crud/destroy.rs b/src/ability/crud/destroy.rs index 5b5dfbc3..20e43f12 100644 --- a/src/ability/crud/destroy.rs +++ b/src/ability/crud/destroy.rs @@ -74,7 +74,7 @@ impl From for arguments::Named { } impl TryFrom> for Destroy { - type Error = (); // FIXME + type Error = TryFromArgsError; fn try_from(args: arguments::Named) -> Result { let mut path = None; @@ -84,9 +84,11 @@ impl TryFrom> for Destroy { "path" => { if let Ipld::String(s) = ipld { path = Some(PathBuf::from(s)); + } else { + return Err(TryFromArgsError::NotAPathBuf); } } - _ => return Err(()), + s => return Err(TryFromArgsError::InvalidField(s.into())), } } @@ -94,6 +96,15 @@ impl TryFrom> for Destroy { } } +#[derive(Error, Debug, PartialEq, Clone, Serialize, Deserialize)] +pub enum TryFromArgsError { + #[error("Path value is not a PathBuf")] + NotAPathBuf, + + #[error("Invalid map key {0}")] + InvalidField(String) +} + impl promise::Resolvable for Destroy { type Promised = PromisedDestroy; } diff --git a/src/ability/crud/js.rs b/src/ability/crud/js.rs index 77a7879c..57a56bff 100644 --- a/src/ability/crud/js.rs +++ b/src/ability/crud/js.rs @@ -1,28 +1,8 @@ //! JavaScript bindings for the CRUD abilities. -use super::{read, Any}; +use super::read; use wasm_bindgen::prelude::*; -/// DOCS? -#[wasm_bindgen] -pub struct CrudAny(#[wasm_bindgen(skip)] pub Any); - -// FIXME macro this away -#[wasm_bindgen] -impl CrudAny { - pub fn into_js(self) -> JsValue { - ipld::Newtype(Ipld::from(self.0)).into() - } - - pub fn try_from_js(js: JsValue) -> Result { - ipld::Newtype::try_from_js(js).map(CrudAny) - } - - pub fn to_command(&self) -> String { - self.to_command() - } -} - #[wasm_bindgen] pub struct CrudRead(#[wasm_bindgen(skip)] pub read::Ready); diff --git a/src/ability/crud/update.rs b/src/ability/crud/update.rs index dbe4a4c8..00a85b6f 100644 --- a/src/ability/crud/update.rs +++ b/src/ability/crud/update.rs @@ -203,31 +203,46 @@ impl From for Ipld { } impl TryFrom for Update { - type Error = (); // FIXME + type Error = TryFromIpldError; fn try_from(ipld: Ipld) -> Result { if let Ipld::Map(map) = ipld { if map.len() > 2 { - return Err(()); // FIXME + return Err(TryFromIpldError::TooManyKeys); } Ok(Update { path: map .get("path") - .map(|ipld| (ipld::Newtype(ipld.clone())).try_into().map_err(|_| ())) + .map(|ipld| (ipld::Newtype(ipld.clone())).try_into().map_err(TryFromIpldError::InvalidPath)) .transpose()?, args: map .get("args") - .map(|ipld| ipld.clone().try_into().map_err(|_| ())) + .map(|ipld| arguments::Named::::try_from(ipld.clone()).map_err(|_| TryFromIpldError::InvalidArgs)) .transpose()?, }) } else { - Err(()) // FIXME + Err(TryFromIpldError::NotAMap) } } } +#[derive(Error, Debug, PartialEq, Clone)] +pub enum TryFromIpldError { + #[error("Not a map")] + NotAMap, + + #[error("Too many keys")] + TooManyKeys, + + #[error("Invalid path: {0}")] + InvalidPath(ipld::newtype::NotAString), + + #[error("Invalid args: not a map")] + InvalidArgs +} + impl TryFrom for arguments::Named { type Error = FromPromisedUpdateError; diff --git a/src/ability/dynamic.rs b/src/ability/dynamic.rs index a24bb103..e5cbe65e 100644 --- a/src/ability/dynamic.rs +++ b/src/ability/dynamic.rs @@ -107,7 +107,7 @@ impl TryFrom for Dynamic { Ok(Dynamic { cmd, - args: arguments::Named(btree), // FIXME kill clone + args: arguments::Named(btree), }) } else { Err(JsValue::NULL) // FIXME diff --git a/src/ability/parse.rs b/src/ability/parse.rs index c5212977..73f9745a 100644 --- a/src/ability/parse.rs +++ b/src/ability/parse.rs @@ -4,8 +4,6 @@ use libipld_core::ipld::Ipld; use std::fmt; use thiserror::Error; -// FIXME definitely needs a better name -// pub trait ParseAbility: TryFrom> { pub trait ParseAbility: Sized { type ArgsErr: fmt::Debug; diff --git a/src/ability/wasm/module.rs b/src/ability/wasm/module.rs index 18ed34db..c17726d7 100644 --- a/src/ability/wasm/module.rs +++ b/src/ability/wasm/module.rs @@ -8,13 +8,12 @@ use serde::{Deserialize, Serialize}; /// Ways to represent a Wasm module in a `wasm/run` payload. #[derive(Debug, Clone, PartialEq)] pub enum Module { - // FIXME serialize both as URLs /// The raw bytes of the Wasm module /// /// Encodes as a `data:` URL Inline(Vec), - /// A link to the Wasm module + /// A [`Cid`] link to the Wasm module Remote(Link>), } diff --git a/src/crypto/signature/envelope.rs b/src/crypto/signature/envelope.rs index 69e69e9f..b0f3aa51 100644 --- a/src/crypto/signature/envelope.rs +++ b/src/crypto/signature/envelope.rs @@ -63,7 +63,6 @@ impl< } } - // FIXME extract into trait? pub fn varsig_encode(self, w: &mut Vec) -> Result<(), libipld_core::error::Error> where Ipld: Encode + From, @@ -83,10 +82,6 @@ impl< /// # Errors /// /// * [`SignError`] - the payload can't be encoded or the signature fails. - /// - /// # Example - /// - /// FIXME pub fn try_sign( signer: &DID::Signer, varsig_header: V, diff --git a/src/crypto/varsig/header/preset.rs b/src/crypto/varsig/header/preset.rs index 4548682b..e658a285 100644 --- a/src/crypto/varsig/header/preset.rs +++ b/src/crypto/varsig/header/preset.rs @@ -9,8 +9,8 @@ pub enum Preset { Es512(es512::Es512Header), Rs256(rs256::Rs256Header), Rs512(rs512::Rs512Header), - // FIXME BLS? - // FIXME Es384 + // FIXME BLS? needs varsig specs + // FIXME Es384 needs varsig specs } impl From for Vec { diff --git a/src/delegation/store/memory.rs b/src/delegation/store/memory.rs index 34a4c5c5..c13cc828 100644 --- a/src/delegation/store/memory.rs +++ b/src/delegation/store/memory.rs @@ -114,7 +114,7 @@ impl, Enc: Codec + TryFrom + &self, aud: &DID, subject: &Option, - policy: Vec, + policy: Vec, // FIXME now: SystemTime, ) -> Result)>>, Self::DelegationStoreError> { match self diff --git a/src/delegation/store/traits.rs b/src/delegation/store/traits.rs index 364c0093..1969e01f 100644 --- a/src/delegation/store/traits.rs +++ b/src/delegation/store/traits.rs @@ -13,9 +13,6 @@ pub trait Store, Enc: Codec + TryFrom + In fn get(&self, cid: &Cid) -> Result<&Delegation, Self::DelegationStoreError>; - // FIXME add a variant that calculated the CID from the capsulre header? - // FIXME that means changing the name to insert_by_cid or similar - // FIXME rename put fn insert( &mut self, cid: Cid, @@ -23,7 +20,7 @@ pub trait Store, Enc: Codec + TryFrom + In ) -> Result<(), Self::DelegationStoreError>; // FIXME validate invocation - // sore invocation + // store invocation // just... move to invocation fn revoke(&mut self, cid: Cid) -> Result<(), Self::DelegationStoreError>; diff --git a/src/did/key.rs b/src/did/key.rs index 068c3fc3..e2859621 100644 --- a/src/did/key.rs +++ b/src/did/key.rs @@ -6,4 +6,4 @@ mod verifier; pub mod traits; pub use signature::Signature; -pub use verifier::Verifier; +pub use verifier::*; diff --git a/src/did/key/traits.rs b/src/did/key/traits.rs index 59c3f304..7a1eb9f1 100644 --- a/src/did/key/traits.rs +++ b/src/did/key/traits.rs @@ -1,3 +1,5 @@ +/// A trait aligning signatures with keys. + use crate::crypto::{bls12381, es512, rs256, rs512}; use ::p521 as ext_p521; use ed25519_dalek; @@ -6,9 +8,8 @@ use p256; use p384; // FIXME -// also: e.g. HSM +// also: e.g. HSM? -// FIXME when name conflict gone pub trait DidKey: signature::Verifier { const BASE58_PREFIX: &'static str; diff --git a/src/did/key/verifier.rs b/src/did/key/verifier.rs index d645c559..93951256 100644 --- a/src/did/key/verifier.rs +++ b/src/did/key/verifier.rs @@ -2,6 +2,10 @@ use super::Signature; use enum_as_inner::EnumAsInner; use rsa::pkcs1::{DecodeRsaPublicKey, EncodeRsaPublicKey}; use std::{fmt::Display, str::FromStr}; +use serde::{Serialize, Deserialize}; +use thiserror::Error; +use blst::BLST_ERROR; +use signature as sig; #[cfg(feature = "test_utils")] use proptest::prelude::*; @@ -145,7 +149,7 @@ impl Display for Verifier { rsa2048_key .0 .to_pkcs1_der() - .expect("RSA key to encode") // FIXME? + .map_err(|_| std::fmt::Error)? // NOTE: technically should never fail .as_bytes() ) .into_string() @@ -158,7 +162,7 @@ impl Display for Verifier { rsa4096_key .0 .to_pkcs1_der() - .expect("RSA key to encode") // FIXME? + .map_err(|_| std::fmt::Error)? // NOTE: technically should never fail .as_bytes() ) .into_string() @@ -178,82 +182,143 @@ impl Display for Verifier { } impl FromStr for Verifier { - type Err = String; // FIXME + type Err = FromStrError; - // FIXME needs tests fn from_str(s: &str) -> Result { if s.len() < 32 { // Smallest key size - return Err("invalid did:key".to_string()); + return Err(FromStrError::TooShort); } - if let ("did:key:z", more) = s.split_at(9) { - let bytes = more.as_bytes(); - match bytes.split_at(2) { - ([0xed, _], _) => { - let vk = ed25519_dalek::VerifyingKey::try_from(&bytes[1..33]) - .map_err(|e| e.to_string())?; + match s.split_at(9) { + ("did:key:z", more) => { + let bytes = more.as_bytes(); + match bytes.split_at(2) { + ([0xed, _], _) => { + let vk = ed25519_dalek::VerifyingKey::try_from(&bytes[1..33]) + .map_err(FromStrError::CannotParseEdDsa)?; - return Ok(Verifier::EdDsa(vk)); - } - ([0xe7, _], _) => { - let vk = k256::ecdsa::VerifyingKey::from_sec1_bytes(&bytes[1..]) - .map_err(|e| e.to_string())?; - - return Ok(Verifier::Es256k(vk)); - } - ([0x12, 0x00], key_bytes) => { - let vk = p256::ecdsa::VerifyingKey::from_sec1_bytes(key_bytes) - .map_err(|e| e.to_string())?; - - return Ok(Verifier::P256(vk)); - } - ([0x12, 0x01], key_bytes) => { - let vk = p384::ecdsa::VerifyingKey::from_sec1_bytes(key_bytes) - .map_err(|e| e.to_string())?; - - return Ok(Verifier::P384(vk)); - } - ([0x12, 0x05], key_bytes) => match key_bytes.len() { - 2048 => { - let vk = rsa::pkcs1v15::VerifyingKey::from_pkcs1_der(key_bytes) - .map_err(|e| e.to_string())?; + return Ok(Verifier::EdDsa(vk)); + } + ([0xe7, _], _) => { + let vk = k256::ecdsa::VerifyingKey::from_sec1_bytes(&bytes[1..]) + .map_err(FromStrError::CannotParseEs256k)?; - return Ok(Verifier::Rs256(rs256::VerifyingKey(vk))); + return Ok(Verifier::Es256k(vk)); } - 4096 => { - let vk = rsa::pkcs1v15::VerifyingKey::from_pkcs1_der(key_bytes) - .map_err(|e| e.to_string())?; + ([0x12, 0x00], key_bytes) => { + let vk = p256::ecdsa::VerifyingKey::from_sec1_bytes(key_bytes) + .map_err(FromStrError::CannotParseP256)?; - return Ok(Verifier::Rs512(rs512::VerifyingKey(vk))); + return Ok(Verifier::P256(vk)); } - _ => return Err("invalid did:key".to_string()), - }, - ([0xeb, 0x01], pk_bytes) => match pk_bytes.len() { - 48 => { - let pk = blst::min_pk::PublicKey::deserialize(pk_bytes) - .map_err(|_| "Failed BLS MinPk deserialization")?; - - return Ok(Verifier::BlsMinPk(pk)); + ([0x12, 0x01], key_bytes) => { + let vk = p384::ecdsa::VerifyingKey::from_sec1_bytes(key_bytes) + .map_err(FromStrError::CannotParseP384)?; + + return Ok(Verifier::P384(vk)); } - 96 => { - let pk = blst::min_sig::PublicKey::deserialize(pk_bytes) - .map_err(|_| "Failed BLS MinSig deserialization")?; + ([0x12, 0x02], key_bytes) => { + let vk = p521::ecdsa::VerifyingKey::from_sec1_bytes(key_bytes) + .map_err(FromStrError::CannotParseP521)?; - return Ok(Verifier::BlsMinSig(pk)); + return Ok(Verifier::P521(es512::VerifyingKey(vk))); + } + ([0x12, 0x05], key_bytes) => match key_bytes.len() { + 2048 => { + let vk = rsa::pkcs1v15::VerifyingKey::from_pkcs1_der(key_bytes) + .map_err(FromStrError::CannotParseRs256)?; + + return Ok(Verifier::Rs256(rs256::VerifyingKey(vk))); + } + 4096 => { + let vk = rsa::pkcs1v15::VerifyingKey::from_pkcs1_der(key_bytes) + .map_err(FromStrError::CannotParseRs512)?; + + return Ok(Verifier::Rs512(rs512::VerifyingKey(vk))); + } + word => return Err(FromStrError::NotADidKey(word)), + }, + ([0xeb, 0x01], pk_bytes) => match pk_bytes.len() { + 48 => { + let pk = blst::min_pk::PublicKey::deserialize(pk_bytes) + .map_err(FromStrError::CannotParseBlsMinPk)?; + + return Ok(Verifier::BlsMinPk(pk)); + } + 96 => { + let pk = blst::min_sig::PublicKey::deserialize(pk_bytes) + .map_err(FromStrError::CannotParseBlsMinSig)?; + + return Ok(Verifier::BlsMinSig(pk)); + } + word => return Err(FromStrError::UnexpectedPrefix([word].into())), + }, + (word, _) => { + return Err(FromStrError::UnexpectedPrefix(word.iter().map(|u| u.clone().into()).collect())); } - _ => return Err("invalid did:key".to_string()), - }, - _ => { - return Err("invalid did:key".to_string()); } + }, + + (s, _) => { + return Err(FromStrError::UnexpectedPrefix(s.to_string().chars().map(|u| u as usize).collect())); } - } else { - return Err("invalid did:key".to_string()); } } } +#[derive(Debug, Error)] +pub enum FromStrError { + #[error("not a did:key prefix: {0}")] + NotADidKey(usize), + + #[error("unexpected prefix: {0:?}")] + UnexpectedPrefix(Vec), + + #[error("key too short")] + TooShort, + + #[error("cannot parse EdDSA key: {0}")] + CannotParseEdDsa(sig::Error), + + #[error("cannot parse ES256K key: {0}")] + CannotParseEs256k(sig::Error), + + #[error("cannot parse P-256 key: {0}")] + CannotParseP256(sig::Error), + + #[error("cannot parse P-384 key: {0}")] + CannotParseP384(sig::Error), + + #[error("cannot parse P-521 key: {0}")] + CannotParseP521(sig::Error), + + #[error("cannot parse RS256 key: {0}")] + CannotParseRs256(rsa::pkcs1::Error), + + #[error("cannot parse RS512 key: {0}")] + CannotParseRs512(rsa::pkcs1::Error), + + #[error("cannot parse BLS min pk key: {0:?}")] + CannotParseBlsMinPk(BLST_ERROR), + + #[error("cannot parse BLS min sig key: {0:?}")] + CannotParseBlsMinSig(BLST_ERROR), +} + +impl Serialize for Verifier { + fn serialize(&self, serializer: S) -> Result { + self.to_string().serialize(serializer) + } +} + +impl<'de> Deserialize<'de> for Verifier { + fn deserialize>(deserializer: D) -> Result { + let s = String::deserialize(deserializer)?; + Verifier::from_str(&s).map_err(serde::de::Error::custom) + } +} + #[cfg(feature = "test_utils")] impl Arbitrary for Verifier { type Parameters = (); diff --git a/src/did/preset.rs b/src/did/preset.rs index 0808afd8..371210c2 100644 --- a/src/did/preset.rs +++ b/src/did/preset.rs @@ -1,16 +1,18 @@ use super::key; use enum_as_inner::EnumAsInner; +use serde::{Deserialize, Serialize}; +use std::{fmt::Display, str::FromStr}; /// The set of [`Did`] types that ship with this library ("presets"). -#[derive(Debug, Clone, EnumAsInner, PartialEq, Eq)] +#[derive(Debug, Clone, EnumAsInner, PartialEq, Eq, Serialize, Deserialize)] +#[serde(untagged)] pub enum Verifier { /// `did:key` DIDs. Key(key::Verifier), - // Dns(did_url::DID), + // + // FIXME Dns(did_url::DID), } -// FIXME serialize with did:key etc - impl signature::Verifier for Verifier { fn verify(&self, message: &[u8], signature: &key::Signature) -> Result<(), signature::Error> { match self { @@ -18,3 +20,19 @@ impl signature::Verifier for Verifier { } } } + +impl Display for Verifier { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Verifier::Key(verifier) => verifier.fmt(f), + } + } +} + +impl FromStr for Verifier { + type Err = key::FromStrError; + + fn from_str(s: &str) -> Result { + key::Verifier::from_str(s).map(Verifier::Key) + } +} diff --git a/src/ipld.rs b/src/ipld.rs index 59116dac..61b2e2bb 100644 --- a/src/ipld.rs +++ b/src/ipld.rs @@ -6,15 +6,13 @@ //! //! [`Ipld`]: libipld_core::ipld::Ipld -// mod enriched; mod collection; -mod newtype; mod number; mod promised; pub mod cid; +pub mod newtype; -// pub use enriched::Enriched; pub use collection::Collection; pub use newtype::Newtype; pub use number::Number; diff --git a/src/ipld/newtype.rs b/src/ipld/newtype.rs index 1127820b..43a1f480 100644 --- a/src/ipld/newtype.rs +++ b/src/ipld/newtype.rs @@ -2,6 +2,7 @@ use libipld_core::ipld::Ipld; use serde::{Deserialize, Serialize}; use std::path::PathBuf; use thiserror::Error; +use std::fmt; #[cfg(target_arch = "wasm32")] use wasm_bindgen::prelude::*; diff --git a/src/time/timestamp.rs b/src/time/timestamp.rs index d8659c29..a43eb858 100644 --- a/src/time/timestamp.rs +++ b/src/time/timestamp.rs @@ -39,7 +39,7 @@ impl Timestamp { /// /// * [`OutOfRangeError`] — If the time is more than 2⁵³ seconds since the Unix epoch pub fn new(time: SystemTime) -> Result { - if time.duration_since(UNIX_EPOCH).expect("FIXME").as_secs() > 0x1FFFFFFFFFFFFF { + if time.duration_since(UNIX_EPOCH).map_err(|_| OutOfRangeError{ tried: time })?.as_secs() > 0x1FFFFFFFFFFFFF { Err(OutOfRangeError { tried: time }) } else { Ok(Timestamp { time }) From abce1e3345fdfd43cdf45cc96f3213ea43de16da Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Wed, 6 Mar 2024 12:10:34 -0800 Subject: [PATCH 118/188] Save before fixing promises --- src/ability/crud/create.rs | 8 +--- src/ability/pipe.rs | 4 +- src/delegation.rs | 79 +++++++++++++++------------------- src/did/key.rs | 2 + src/did/key/signature.rs | 1 - src/did/key/signer.rs | 86 ++++++++++++++++++++++++++++++++++++++ src/did/key/verifier.rs | 15 +++++++ src/did/preset.rs | 37 ++++++++++++++++ src/did/traits.rs | 1 - src/invocation.rs | 67 ++++++++++++----------------- src/invocation/promise.rs | 7 ++-- 11 files changed, 206 insertions(+), 101 deletions(-) create mode 100644 src/did/key/signer.rs diff --git a/src/ability/crud/create.rs b/src/ability/crud/create.rs index bd5b159f..2fa7f800 100644 --- a/src/ability/crud/create.rs +++ b/src/ability/crud/create.rs @@ -219,13 +219,7 @@ impl From for arguments::Named { let mut named = arguments::Named::new(); if let Some(path) = create.path { - named.insert( - "path".to_string(), - path.into_os_string() - .into_string() - .expect("PathBuf to generate valid paths") // FIXME reasonable assumption? - .into(), - ); + named.insert("path".to_string(), path.display().to_string().into()); } if let Some(args) = create.args { diff --git a/src/ability/pipe.rs b/src/ability/pipe.rs index 55a80555..a41d9c01 100644 --- a/src/ability/pipe.rs +++ b/src/ability/pipe.rs @@ -7,7 +7,7 @@ pub struct Pipe, Enc: Codec + TryFrom + In } pub enum Cap, Enc: Codec + TryFrom + Into> { - Chain(delegation::Chain), + Proof(delegation::Proof), Literal(Ipld), } @@ -17,6 +17,6 @@ pub struct PromisedPipe, Enc: Codec + TryFrom, Enc: Codec + TryFrom + Into> { - Chain(delegation::Chain), + Proof(delegation::Proof), Promised(ipld::Promised), } diff --git a/src/delegation.rs b/src/delegation.rs index d67e7f28..992341c6 100644 --- a/src/delegation.rs +++ b/src/delegation.rs @@ -41,28 +41,35 @@ use web_time::SystemTime; /// /// A [`Payload`] on its own is not a valid [`Delegation`], as it must be signed by the issuer. #[derive(Clone, Debug, PartialEq)] -pub struct Delegation, Enc: Codec + TryFrom + Into>( - pub signature::Envelope, DID, V, Enc>, -); +pub struct Delegation< + DID: Did = did::preset::Verifier, + V: varsig::Header = varsig::header::Preset, + C: Codec + TryFrom + Into = varsig::encoding::Preset, +>(signature::Envelope, DID, V, C>); +// FIXME rename proofs? #[derive(Clone, Debug, PartialEq)] -pub struct Chain, Enc: Codec + TryFrom + Into>( - Vec>, -); - -impl, Enc: Codec + TryFrom + Into> Capsule - for Chain +pub struct Proof< + DID: Did = did::preset::Verifier, + V: varsig::Header = varsig::header::Preset, + C: Codec + TryFrom + Into = varsig::encoding::Preset, +>(Vec>); + +impl, C: Codec + TryFrom + Into> Capsule + for Proof { - const TAG: &'static str = "ucan/chain"; + const TAG: &'static str = "ucan/prf"; } -/// A variant of [`Delegation`] that has the abilties and DIDs from this library pre-filled. -pub type Preset = - Delegation; +impl, C: Codec + Into + TryFrom> Delegation { + pub fn new( + varsig_header: V, + signature: DID::Signature, + payload: Payload, + ) -> Delegation { + Delegation(signature::Envelope::new(varsig_header, signature, payload)) + } -impl, Enc: Codec + Into + TryFrom> - Delegation -{ /// Retrive the `issuer` of a [`Delegation`] pub fn issuer(&self) -> &DID { &self.0.payload.issuer @@ -117,7 +124,7 @@ impl, Enc: Codec + Into + TryFrom> pub fn varsig_encode(self, w: &mut Vec) -> Result<(), libipld_core::error::Error> where - Ipld: Encode, + Ipld: Encode, { self.0.varsig_encode(w) } @@ -126,14 +133,14 @@ impl, Enc: Codec + Into + TryFrom> &self.0.signature } - pub fn codec(&self) -> &Enc { + pub fn codec(&self) -> &C { self.varsig_header().codec() } pub fn cid(&self) -> Result where - signature::Envelope, DID, V, Enc>: Clone + Encode, - Ipld: Encode, + signature::Envelope, DID, V, C>: Clone + Encode, + Ipld: Encode, { self.0.cid() } @@ -141,7 +148,7 @@ impl, Enc: Codec + Into + TryFrom> pub fn validate_signature(&self) -> Result<(), signature::ValidateError> where Payload: Clone, - Ipld: Encode, + Ipld: Encode, { self.0.validate_signature() } @@ -152,35 +159,15 @@ impl, Enc: Codec + Into + TryFrom> payload: Payload, ) -> Result where - Ipld: Encode, + Ipld: Encode, Payload: Clone, { signature::Envelope::try_sign(signer, varsig_header, payload).map(Delegation) } } -impl, Enc: Codec + TryFrom + Into> TryFrom - for Delegation -where - Payload: TryFrom, -{ - type Error = , DID, V, Enc> as TryFrom>::Error; - - fn try_from(ipld: Ipld) -> Result { - signature::Envelope::try_from(ipld).map(Delegation) - } -} - -impl, Enc: Codec + TryFrom + Into> - From> for Ipld -{ - fn from(delegation: Delegation) -> Self { - delegation.0.into() - } -} - -impl, Enc: Codec + TryFrom + Into> Serialize - for Delegation +impl, C: Codec + TryFrom + Into> Serialize + for Delegation { fn serialize(&self, serializer: S) -> Result where @@ -190,8 +177,8 @@ impl, Enc: Codec + TryFrom + Into> Se } } -impl<'de, DID: Did, V: varsig::Header, Enc: Codec + TryFrom + Into> Deserialize<'de> - for Delegation +impl<'de, DID: Did, V: varsig::Header, C: Codec + TryFrom + Into> Deserialize<'de> + for Delegation where Payload: TryFrom, as TryFrom>::Error: std::fmt::Display, diff --git a/src/did/key.rs b/src/did/key.rs index e2859621..6be3a7bb 100644 --- a/src/did/key.rs +++ b/src/did/key.rs @@ -2,8 +2,10 @@ mod signature; mod verifier; +mod signer; pub mod traits; pub use signature::Signature; pub use verifier::*; +pub use signer::*; diff --git a/src/did/key/signature.rs b/src/did/key/signature.rs index 7cbc715e..1392eec6 100644 --- a/src/did/key/signature.rs +++ b/src/did/key/signature.rs @@ -1,5 +1,4 @@ use enum_as_inner::EnumAsInner; -// FIXME use serde::{Deserialize, Serialize}; #[cfg(feature = "eddsa")] use ed25519_dalek; diff --git a/src/did/key/signer.rs b/src/did/key/signer.rs new file mode 100644 index 00000000..6bc8584e --- /dev/null +++ b/src/did/key/signer.rs @@ -0,0 +1,86 @@ +use enum_as_inner::EnumAsInner; +use super::Signature; + +#[cfg(feature = "eddsa")] +use ed25519_dalek; + +#[cfg(feature = "es256")] +use p256; + +#[cfg(feature = "es256k")] +use k256; + +#[cfg(feature = "es384")] +use p384; + +#[cfg(feature = "es512")] +use ::p521 as ext_p521; + +#[cfg(feature = "rs256")] +use crate::crypto::rs256; + +#[cfg(feature = "rs512")] +use crate::crypto::rs512; + +#[cfg(feature = "bls")] +use crate::crypto::bls12381; + + + +/// Signature types that are verifiable by `did:key` [`Verifier`]s. +#[derive(Debug, Clone, PartialEq, Eq, EnumAsInner)] +pub enum Signer { + /// `EdDSA` signature. + #[cfg(feature = "eddsa")] + EdDsa(ed25519_dalek::SigningKey), + + // /// `ES256K` (`secp256k1`) signature. + // #[cfg(feature = "es256k")] + // Es256k(k256::ecdsa::Signer), + + // /// `P-256` signature. + // #[cfg(feature = "es256")] + // P256(p256::ecdsa::Signer), + + // /// `P-384` signature. + // #[cfg(feature = "es384")] + // P384(p384::ecdsa::Signer), + + // /// `P-521` signature. + // #[cfg(feature = "es512")] + // P521(ext_p521::ecdsa::Signer), + + // /// `RS256` signature. + // #[cfg(feature = "rs256")] + // Rs256(rs256::Signer), + + // /// `RS512` signature. + // #[cfg(feature = "rs512")] + // Rs512(rs512::Signer), + + // /// `BLS 12-381` signature for the "min pub key" variant. + // #[cfg(feature = "bls")] + // BlsMinPk(bls12381::min_pk::Signer), + + // /// `BLS 12-381` signature for the "min sig" variant. + // #[cfg(feature = "bls")] + // BlsMinSig(bls12381::min_sig::Signer), + + // /// An unknown signature type. + // /// + // /// This is primarily for parsing, where reification is delayed + // /// until the DID method is known. + // Unknown(Vec), +} + +impl signature::Signer for Signer { + fn try_sign(&self, msg: &[u8]) -> Result { + match self { + #[cfg(feature = "eddsa")] + Signer::EdDsa(signer) => { + let sig = signer.sign(msg); + Ok(Signature::EdDsa(sig)) + } + } + } +} diff --git a/src/did/key/verifier.rs b/src/did/key/verifier.rs index 93951256..20b021eb 100644 --- a/src/did/key/verifier.rs +++ b/src/did/key/verifier.rs @@ -6,6 +6,7 @@ use serde::{Serialize, Deserialize}; use thiserror::Error; use blst::BLST_ERROR; use signature as sig; +use did_url::DID; #[cfg(feature = "test_utils")] use proptest::prelude::*; @@ -319,6 +320,20 @@ impl<'de> Deserialize<'de> for Verifier { } } +impl From for DID { + fn from(v: Verifier) -> Self { + DID::parse(&v.to_string()).expect("verifier to be a valid DID") + } +} + +impl TryFrom for Verifier { + type Error = FromStrError; + + fn try_from(did: DID) -> Result { + Verifier::from_str(&did.to_string()) + } +} + #[cfg(feature = "test_utils")] impl Arbitrary for Verifier { type Parameters = (); diff --git a/src/did/preset.rs b/src/did/preset.rs index 371210c2..aff0d261 100644 --- a/src/did/preset.rs +++ b/src/did/preset.rs @@ -1,4 +1,6 @@ use super::key; +use super::Did; +use did_url::DID; use enum_as_inner::EnumAsInner; use serde::{Deserialize, Serialize}; use std::{fmt::Display, str::FromStr}; @@ -13,6 +15,41 @@ pub enum Verifier { // FIXME Dns(did_url::DID), } +impl From for DID { + fn from(verifier: Verifier) -> Self { + match verifier { + Verifier::Key(verifier) => verifier.into(), + } + } +} + +#[derive(Debug, Clone, EnumAsInner, PartialEq, Eq)] +pub enum Signer { + Key(key::Signer), + // FIXME Dns(did_url::DID), +} + +impl Did for Verifier { + type Signature = key::Signature; + type Signer = Signer; +} + +impl TryFrom for Verifier { + type Error = key::FromStrError; + + fn try_from(did: DID) -> Result { + key::Verifier::try_from(did).map(Verifier::Key) + } +} + +impl signature::Signer for Signer { + fn try_sign(&self, message: &[u8]) -> Result { + match self { + Signer::Key(signer) => signer.try_sign(message), + } + } +} + impl signature::Verifier for Verifier { fn verify(&self, message: &[u8], signature: &key::Signature) -> Result<(), signature::Error> { match self { diff --git a/src/did/traits.rs b/src/did/traits.rs index bd903ad4..448186be 100644 --- a/src/did/traits.rs +++ b/src/did/traits.rs @@ -1,4 +1,3 @@ -// use super::Newtype; use did_url::DID; use std::fmt; diff --git a/src/invocation.rs b/src/invocation.rs index aeba3097..4c9e9f8a 100644 --- a/src/invocation.rs +++ b/src/invocation.rs @@ -50,30 +50,15 @@ use web_time::SystemTime; #[derive(Debug, Clone, PartialEq)] pub struct Invocation< A, - DID: did::Did, - V: varsig::Header, - Enc: Codec + TryFrom + Into, ->(pub signature::Envelope, DID, V, Enc>); - -/// A variant of [`Invocation`] that has the abilties and DIDs from this library pre-filled. -pub type Preset = Invocation< - ability::preset::Preset, - did::preset::Verifier, - varsig::header::Preset, - varsig::encoding::Preset, ->; - -pub type PresetPromised = Invocation< - ability::preset::Preset, - did::preset::Verifier, - varsig::header::Preset, - varsig::encoding::Preset, ->; - -impl, Enc: Codec + TryFrom + Into> - Invocation + DID: did::Did = did::preset::Verifier, + V: varsig::Header = varsig::header::Preset, + C: Codec + TryFrom + Into = varsig::encoding::Preset, +>(pub signature::Envelope, DID, V, C>); + +impl, C: Codec + TryFrom + Into> + Invocation where - Ipld: Encode, + Ipld: Encode, { pub fn new(payload: Payload, varsig_header: V, signature: DID::Signature) -> Self { Invocation(signature::Envelope::new(varsig_header, signature, payload)) @@ -81,7 +66,7 @@ where pub fn varsig_encode(self, w: &mut Vec) -> Result<(), libipld_core::error::Error> where - Ipld: Encode, + Ipld: Encode, { self.0.varsig_encode(w) } @@ -114,7 +99,7 @@ where &self.0.payload.ability } - pub fn map_ability(self, f: F) -> Invocation + pub fn map_ability(self, f: F) -> Invocation where F: FnOnce(A) -> Z, { @@ -141,14 +126,14 @@ where self.payload().check_time(now) } - pub fn codec(&self) -> &Enc { + pub fn codec(&self) -> &C { self.varsig_header().codec() } pub fn cid(&self) -> Result where - signature::Envelope, DID, V, Enc>: Clone, - Ipld: Encode, + signature::Envelope, DID, V, C>: Clone, + Ipld: Encode, { self.0.cid() } @@ -157,7 +142,7 @@ where signer: &DID::Signer, varsig_header: V, payload: Payload, - ) -> Result, signature::SignError> + ) -> Result, signature::SignError> where Payload: Clone, { @@ -173,36 +158,36 @@ where } } -impl, Enc: Codec + TryFrom + Into> - did::Verifiable for Invocation +impl, C: Codec + TryFrom + Into> did::Verifiable + for Invocation { fn verifier(&self) -> &DID { &self.0.verifier() } } -impl, Enc: Codec + TryFrom + Into> - From> for Ipld +impl, C: Codec + TryFrom + Into> + From> for Ipld { - fn from(invocation: Invocation) -> Self { + fn from(invocation: Invocation) -> Self { invocation.0.into() } } -impl, Enc: Codec + TryFrom + Into> TryFrom - for Invocation +impl, C: Codec + TryFrom + Into> TryFrom + for Invocation where Payload: TryFrom, { - type Error = , DID, V, Enc> as TryFrom>::Error; + type Error = , DID, V, C> as TryFrom>::Error; fn try_from(ipld: Ipld) -> Result { signature::Envelope::try_from(ipld).map(Invocation) } } -impl, Enc: Codec + TryFrom + Into> Serialize - for Invocation +impl, C: Codec + TryFrom + Into> Serialize + for Invocation { fn serialize(&self, serializer: S) -> Result where @@ -212,8 +197,8 @@ impl, Enc: Codec + TryFrom + Into> } } -impl<'de, A, DID: Did, V: varsig::Header, Enc: Codec + TryFrom + Into> - Deserialize<'de> for Invocation +impl<'de, A, DID: Did, V: varsig::Header, C: Codec + TryFrom + Into> Deserialize<'de> + for Invocation where Payload: TryFrom, as TryFrom>::Error: std::fmt::Display, diff --git a/src/invocation/promise.rs b/src/invocation/promise.rs index 4bc943a2..73907a97 100644 --- a/src/invocation/promise.rs +++ b/src/invocation/promise.rs @@ -25,14 +25,15 @@ use serde::{Deserialize, Serialize}; #[derive(Debug, Clone, PartialEq, Serialize, Deserialize, EnumAsInner)] #[serde(untagged)] pub enum Promise { - /// The `await/ok` promise + /// The `ucan/await/ok` promise Ok(PromiseOk), - /// The `await/err` promise + /// The `ucan/await/err` promise Err(PromiseErr), - /// The `await/*` promise + /// The `ucan/await/*` promise Any(PromiseAny), + // Tagged `ucan/await` } impl From> for Promise { From 4db9cf593c4ec2c99cb9c4552bfb83cdf7201a67 Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Wed, 6 Mar 2024 15:06:43 -0800 Subject: [PATCH 119/188] Hugely fixup the promise hierarchy --- src/ability/arguments.rs | 3 +- src/ability/arguments/named.rs | 26 +-- src/ability/crud/create.rs | 46 ++-- src/ability/crud/destroy.rs | 46 ++-- src/ability/crud/read.rs | 32 +-- src/ability/crud/update.rs | 85 +++---- src/ability/msg.rs | 16 +- src/ability/msg/receive.rs | 29 +-- src/ability/msg/send.rs | 42 ++-- src/ability/ucan/revoke.rs | 9 +- src/ability/wasm/run.rs | 30 ++- src/invocation/promise.rs | 36 +-- src/invocation/promise/any.rs | 286 +++++++---------------- src/invocation/promise/resolves.rs | 352 ----------------------------- src/ipld/newtype.rs | 2 +- src/ipld/promised.rs | 134 ++++------- 16 files changed, 280 insertions(+), 894 deletions(-) delete mode 100644 src/invocation/promise/resolves.rs diff --git a/src/ability/arguments.rs b/src/ability/arguments.rs index d82e6b0b..7d504812 100644 --- a/src/ability/arguments.rs +++ b/src/ability/arguments.rs @@ -4,10 +4,11 @@ mod named; pub use named::*; -use crate::{invocation::promise::Resolves, ipld}; +use crate::ipld; use libipld_core::ipld::Ipld; use std::collections::BTreeMap; +// FIXME just remove? // FIXME move under invoc::promise? // pub type Promised = Resolves>; // diff --git a/src/ability/arguments/named.rs b/src/ability/arguments/named.rs index 16cee2c0..f8a3500f 100644 --- a/src/ability/arguments/named.rs +++ b/src/ability/arguments/named.rs @@ -1,6 +1,7 @@ use crate::{ - invocation::promise::{Pending, Resolves}, - ipld, ability::crud::update::TryFromIpldError, + // ability::crud::update::TryFromIpldError, + invocation::promise::{self, Pending}, + ipld, }; use libipld_core::ipld::Ipld; use serde::{Deserialize, Serialize}; @@ -228,17 +229,14 @@ pub enum TryFromJsValueError { NotAMap, #[error("Not Ipld")] - NotIpld + NotIpld, } -impl From> for Named> { - fn from(named: Named) -> Named> { - let btree: BTreeMap> = named +impl From> for Named> { + fn from(named: Named) -> Named> { + let btree: BTreeMap> = named .into_iter() - .map(|(k, v)| { - let promised: ipld::Promised = v.into(); - (k, Resolves::new(promised)) - }) + .map(|(k, v)| (k, promise::Any::from_ipld(v))) .collect(); Named(btree) @@ -266,13 +264,13 @@ impl TryFrom> for Named { } } -impl TryFrom>> for Named +impl TryFrom>> for Named where Ipld: TryFrom, { - type Error = Resolves>; + type Error = promise::Any>; - fn try_from(resolves: Resolves>) -> Result { + fn try_from(resolves: promise::Any>) -> Result { resolves .clone() .try_resolve()? @@ -282,7 +280,7 @@ where btree.insert(k, ipld); Ok(btree) }) - .map_err(|_: ()| resolves) + .map_err(|_: ()| resolves) // FIXME } } diff --git a/src/ability/crud/create.rs b/src/ability/crud/create.rs index 2fa7f800..86df7070 100644 --- a/src/ability/crud/create.rs +++ b/src/ability/crud/create.rs @@ -2,7 +2,7 @@ use crate::{ ability::{arguments, command::Command}, - invocation::{promise, promise::Resolves}, + invocation::promise, ipld, }; use libipld_core::ipld::Ipld; @@ -90,11 +90,11 @@ pub struct Create { pub struct PromisedCreate { /// An optional path to a sub-resource that is to be created. #[serde(default, skip_serializing_if = "Option::is_none")] - pub path: Option>, + pub path: Option>, /// Optional arguments for creation. #[serde(default, skip_serializing_if = "Option::is_none")] - pub args: Option>>, + pub args: Option>>, } const COMMAND: &str = "/crud/create"; @@ -118,18 +118,16 @@ impl TryFrom> for PromisedCreate { match k.as_str() { "path" => match prom { ipld::Promised::String(s) => { - path = Some(promise::Resolves::Ok( - promise::PromiseOk::Fulfilled(PathBuf::from(s)).into(), - )); + path = Some(promise::Any::Resolved(PathBuf::from(s)).into()); } ipld::Promised::WaitOk(cid) => { - path = Some(promise::PromiseOk::Pending(cid).into()); + path = Some(promise::Any::PendingOk(cid).into()); } ipld::Promised::WaitErr(cid) => { - path = Some(promise::PromiseErr::Pending(cid).into()); + path = Some(promise::Any::PendingErr(cid).into()); } ipld::Promised::WaitAny(cid) => { - todo!() // FIXME // path = Some(promise::PromiseAny::Pending(cid).into()); + path = Some(promise::Any::PendingAny(cid).into()); } _ => return Err(FromPromisedArgsError::InvalidPath(k)), }, @@ -137,17 +135,11 @@ impl TryFrom> for PromisedCreate { "args" => { args = match prom { ipld::Promised::Map(map) => { - Some(promise::PromiseOk::Fulfilled(arguments::Named(map)).into()) - } - ipld::Promised::WaitOk(cid) => { - Some(promise::PromiseOk::Pending(cid).into()) - } - ipld::Promised::WaitErr(cid) => { - Some(promise::PromiseErr::Pending(cid).into()) - } - ipld::Promised::WaitAny(cid) => { - todo!() // FIXME // Some(promise::PromiseAny::Pending(cid).into()) + Some(promise::Any::Resolved(arguments::Named(map)).into()) } + ipld::Promised::WaitOk(cid) => Some(promise::Any::PendingOk(cid)), + ipld::Promised::WaitErr(cid) => Some(promise::Any::PendingErr(cid)), + ipld::Promised::WaitAny(cid) => Some(promise::Any::PendingAny(cid)), _ => return Err(FromPromisedArgsError::InvalidArgs(prom)), } } @@ -201,11 +193,11 @@ impl TryFrom> for Create { impl From for PromisedCreate { fn from(r: Create) -> PromisedCreate { PromisedCreate { - path: r - .path - .map(|inner_path| promise::PromiseOk::Fulfilled(inner_path).into()), + path: r.path.map(|inner_path| promise::Any::Resolved(inner_path)), - args: r.args.map(|inner_args| Resolves::new(inner_args.into())), + args: r + .args + .map(|inner_args| promise::Any::Resolved(inner_args.into())), } } } @@ -234,12 +226,12 @@ impl From for arguments::Named { fn from(promised: PromisedCreate) -> Self { let mut named = arguments::Named::new(); - if let Some(path) = promised.path { - named.insert("path".to_string(), path.into()); + if let Some(path_prom) = promised.path { + named.insert("path".to_string(), path_prom.to_promised_ipld()); } - if let Some(args) = promised.args { - named.insert("args".to_string(), args.into()); + if let Some(args_prom) = promised.args { + named.insert("args".to_string(), args_prom.to_promised_ipld()); } named diff --git a/src/ability/crud/destroy.rs b/src/ability/crud/destroy.rs index 20e43f12..1f2516f5 100644 --- a/src/ability/crud/destroy.rs +++ b/src/ability/crud/destroy.rs @@ -102,7 +102,7 @@ pub enum TryFromArgsError { NotAPathBuf, #[error("Invalid map key {0}")] - InvalidField(String) + InvalidField(String), } impl promise::Resolvable for Destroy { @@ -146,7 +146,7 @@ impl promise::Resolvable for Destroy { pub struct PromisedDestroy { /// An optional path to a sub-resource that is to be destroyed. #[serde(default, skip_serializing_if = "Option::is_none")] - pub path: Option>, + pub path: Option>, } impl TryFrom> for PromisedDestroy { @@ -159,18 +159,16 @@ impl TryFrom> for PromisedDestroy { match k.as_str() { "path" => match prom { ipld::Promised::String(s) => { - path = Some(promise::Resolves::Ok( - promise::PromiseOk::Fulfilled(PathBuf::from(s)).into(), - )); + path = Some(promise::Any::Resolved(PathBuf::from(s)).into()); } ipld::Promised::WaitOk(cid) => { - path = Some(promise::PromiseOk::Pending(cid).into()); + path = Some(promise::Any::PendingOk(cid).into()); } ipld::Promised::WaitErr(cid) => { - path = Some(promise::PromiseErr::Pending(cid).into()); + path = Some(promise::Any::PendingErr(cid).into()); } ipld::Promised::WaitAny(cid) => { - todo!() // FIXME // path = Some(promise::PromiseAny::Pending(cid).into()); + path = Some(promise::Any::PendingAny(cid).into()); } _ => return Err(FromPromisedArgsError::InvalidPath(k)), }, @@ -195,27 +193,27 @@ impl Command for PromisedDestroy { const COMMAND: &'static str = COMMAND; } -impl From for arguments::Named { - fn from(promised: PromisedDestroy) -> Self { - let mut named = arguments::Named::new(); - - if let Some(path_res) = promised.path { - named.insert( - "path".to_string(), - path_res.map(|p| ipld::Newtype::from(p).0).into(), - ); - } - - named - } -} +// impl From for arguments::Named { +// fn from(promised: PromisedDestroy) -> Self { +// let mut named = arguments::Named::new(); +// +// if let Some(path_res) = promised.path { +// named.insert( +// "path".to_string(), +// path_res.map(|p| ipld::Newtype::from(p).0).into(), +// ); +// } +// +// named +// } +// } impl From for PromisedDestroy { fn from(r: Destroy) -> PromisedDestroy { PromisedDestroy { path: r .path - .map(|inner_path| promise::PromiseOk::Fulfilled(inner_path).into()), + .map(|inner_path| promise::Any::Resolved(inner_path).into()), } } } @@ -225,7 +223,7 @@ impl From for arguments::Named { let mut named = arguments::Named::new(); if let Some(path) = promised.path { - named.insert("path".to_string(), path.into()); + named.insert("path".to_string(), path.to_promised_ipld()); } named diff --git a/src/ability/crud/read.rs b/src/ability/crud/read.rs index 35c925fd..1289a45a 100644 --- a/src/ability/crud/read.rs +++ b/src/ability/crud/read.rs @@ -84,11 +84,11 @@ pub struct Read { pub struct PromisedRead { /// An optional path to a sub-resource that is to be read. #[serde(default, skip_serializing_if = "Option::is_none")] - pub path: Option>, + pub path: Option>, /// Optional arguments to modify the read request. #[serde(default, skip_serializing_if = "Option::is_none")] - pub args: Option>>, + pub args: Option>>, } impl TryFrom> for PromisedRead { @@ -102,18 +102,16 @@ impl TryFrom> for PromisedRead { match k.as_str() { "path" => match prom { ipld::Promised::String(s) => { - path = Some(promise::Resolves::Ok( - promise::PromiseOk::Fulfilled(PathBuf::from(s)).into(), - )); + path = Some(promise::Any::Resolved(PathBuf::from(s)).into()); } ipld::Promised::WaitOk(cid) => { - path = Some(promise::PromiseOk::Pending(cid).into()); + path = Some(promise::Any::PendingOk(cid).into()); } ipld::Promised::WaitErr(cid) => { - path = Some(promise::PromiseErr::Pending(cid).into()); + path = Some(promise::Any::PendingErr(cid).into()); } ipld::Promised::WaitAny(cid) => { - todo!() // FIXME // path = Some(promise::PromiseAny::Pending(cid).into()); + path = Some(promise::Any::PendingAny(cid).into()); } _ => return Err(FromPromisedArgsError::InvalidPath(k)), }, @@ -121,17 +119,11 @@ impl TryFrom> for PromisedRead { "args" => { args = match prom { ipld::Promised::Map(map) => { - Some(promise::PromiseOk::Fulfilled(arguments::Named(map)).into()) - } - ipld::Promised::WaitOk(cid) => { - Some(promise::PromiseOk::Pending(cid).into()) - } - ipld::Promised::WaitErr(cid) => { - Some(promise::PromiseErr::Pending(cid).into()) - } - ipld::Promised::WaitAny(cid) => { - todo!() // FIXME // Some(promise::PromiseAny::Pending(cid).into()) + Some(promise::Any::Resolved(arguments::Named(map)).into()) } + ipld::Promised::WaitOk(cid) => Some(promise::Any::PendingOk(cid).into()), + ipld::Promised::WaitErr(cid) => Some(promise::Any::PendingErr(cid).into()), + ipld::Promised::WaitAny(cid) => Some(promise::Any::PendingAny(cid).into()), _ => return Err(FromPromisedArgsError::InvalidArgs(prom)), } } @@ -231,11 +223,11 @@ impl From for arguments::Named { let mut named = arguments::Named::new(); if let Some(path_res) = promised.path { - named.insert("path".to_string(), path_res.into()); + named.insert("path".to_string(), path_res.to_promised_ipld()); } if let Some(args_res) = promised.args { - named.insert("args".to_string(), args_res.into()); + named.insert("args".to_string(), args_res.to_promised_ipld()); } named diff --git a/src/ability/crud/update.rs b/src/ability/crud/update.rs index 00a85b6f..ebf0c281 100644 --- a/src/ability/crud/update.rs +++ b/src/ability/crud/update.rs @@ -2,7 +2,7 @@ use crate::{ ability::{arguments, command::Command}, - invocation::{promise, promise::Resolves}, + invocation::promise, ipld, }; use libipld_core::ipld::Ipld; @@ -90,11 +90,11 @@ pub struct Update { pub struct PromisedUpdate { /// An optional path to a sub-resource that is to be updated. #[serde(default, skip_serializing_if = "Option::is_none")] - path: Option>, + path: Option>, /// Optional arguments to be passed in the update. #[serde(default, skip_serializing_if = "Option::is_none")] - args: Option>>, + args: Option>>, } const COMMAND: &'static str = "/crud/update"; @@ -121,26 +121,19 @@ impl TryFrom> for PromisedUpdate { path = Some(pending.into()); } Ok(ipld) => match ipld { - Ipld::String(s) => path = Some(promise::Resolves::new(PathBuf::from(s))), + Ipld::String(s) => path = Some(promise::Any::Resolved(PathBuf::from(s))), other => return Err(FromPromisedArgsError::PathBodyNotAString(other)), }, }, "args" => match prom { ipld::Promised::Map(map) => { - args = Some(promise::Resolves::new(arguments::Named(map))) + args = Some(promise::Any::Resolved(arguments::Named(map))) } - ipld::Promised::WaitOk(_cid) => { - // FIXME - args = Some(promise::Resolves::new(arguments::Named::new())); - } - ipld::Promised::WaitErr(_cid) => { - // FIXME - args = Some(promise::Resolves::new(arguments::Named::new())); - } - ipld::Promised::WaitAny(_cid) => { - // FIXME - args = Some(promise::Resolves::new(arguments::Named::new())); + ipld::Promised::WaitOk(cid) => args = Some(promise::Any::PendingOk(cid)), + ipld::Promised::WaitErr(cid) => args = Some(promise::Any::PendingErr(cid)), + ipld::Promised::WaitAny(cid) => { + args = Some(promise::Any::PendingAny(cid)); } _ => return Err(FromPromisedArgsError::InvalidArgs(prom)), }, @@ -214,12 +207,19 @@ impl TryFrom for Update { Ok(Update { path: map .get("path") - .map(|ipld| (ipld::Newtype(ipld.clone())).try_into().map_err(TryFromIpldError::InvalidPath)) + .map(|ipld| { + (ipld::Newtype(ipld.clone())) + .try_into() + .map_err(TryFromIpldError::InvalidPath) + }) .transpose()?, args: map .get("args") - .map(|ipld| arguments::Named::::try_from(ipld.clone()).map_err(|_| TryFromIpldError::InvalidArgs)) + .map(|ipld| { + arguments::Named::::try_from(ipld.clone()) + .map_err(|_| TryFromIpldError::InvalidArgs) + }) .transpose()?, }) } else { @@ -240,46 +240,13 @@ pub enum TryFromIpldError { InvalidPath(ipld::newtype::NotAString), #[error("Invalid args: not a map")] - InvalidArgs -} - -impl TryFrom for arguments::Named { - type Error = FromPromisedUpdateError; - - fn try_from(promised: PromisedUpdate) -> Result { - let mut named = arguments::Named::new(); - - if let Some(path_res) = promised.path { - named.insert( - "path".to_string(), - path_res.map(|p| ipld::Newtype::from(p).0).into(), - ); - } - - if let Some(args_res) = promised.args { - named.insert( - "args".to_string(), - args_res - .try_resolve() - .map_err(FromPromisedUpdateError::UnresolvedArgs)? - .iter() - .try_fold(BTreeMap::new(), |mut map, (k, v)| { - map.insert(k.clone(), Ipld::try_from(v.clone())?); // FIXME double check - Ok(map) - }) - .map_err(FromPromisedUpdateError::ArgsPending)? - .into(), - ); - } - - Ok(named) - } + InvalidArgs, } #[derive(Error, Debug, PartialEq, Clone)] pub enum FromPromisedUpdateError { #[error("Unresolved args")] - UnresolvedArgs(Resolves>), + UnresolvedArgs(promise::Any>), #[error("Args pending")] ArgsPending(>::Error), @@ -291,11 +258,11 @@ pub enum FromPromisedUpdateError { impl From for PromisedUpdate { fn from(r: Update) -> PromisedUpdate { PromisedUpdate { - path: r - .path - .map(|inner_path| promise::PromiseOk::Fulfilled(inner_path).into()), + path: r.path.map(|inner_path| promise::Any::Resolved(inner_path)), - args: r.args.map(|inner_args| Resolves::new(inner_args.into())), + args: r + .args + .map(|inner_args| promise::Any::Resolved(inner_args.into())), } } } @@ -309,11 +276,11 @@ impl From for arguments::Named { let mut named = arguments::Named::new(); if let Some(path) = promised.path { - named.insert("path".to_string(), path.into()); + named.insert("path".to_string(), path.to_promised_ipld()); } if let Some(args) = promised.args { - named.insert("args".to_string(), args.into()); + named.insert("args".to_string(), args.to_promised_ipld()); } named diff --git a/src/ability/msg.rs b/src/ability/msg.rs index 59ee1fed..2ad81030 100644 --- a/src/ability/msg.rs +++ b/src/ability/msg.rs @@ -74,14 +74,14 @@ impl ParsePromised for PromisedMsg { } } -impl From for arguments::Named { - fn from(promised: PromisedMsg) -> Self { - match promised { - PromisedMsg::Send(send) => send.into(), - PromisedMsg::Receive(receive) => receive.into(), - } - } -} +// impl From for arguments::Named { +// fn from(promised: PromisedMsg) -> Self { +// match promised { +// PromisedMsg::Send(send) => send.into(), +// PromisedMsg::Receive(receive) => receive.into(), +// } +// } +// } impl Resolvable for Msg { type Promised = PromisedMsg; diff --git a/src/ability/msg/receive.rs b/src/ability/msg/receive.rs index a58bbed2..2d59ea0b 100644 --- a/src/ability/msg/receive.rs +++ b/src/ability/msg/receive.rs @@ -101,26 +101,7 @@ impl TryFrom for Receive { #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] pub struct PromisedReceive { - pub from: Option>, -} - -impl From for arguments::Named { - fn from(promised: PromisedReceive) -> Self { - let mut args = arguments::Named::new(); - - if let Some(from) = promised.from { - match from { - promise::Resolves::Ok(from) => { - args.insert("from".into(), from.into()); - } - promise::Resolves::Err(from) => { - args.insert("from".into(), from.into()); - } - } - } - - args - } + pub from: Option>, } impl promise::Resolvable for Receive { @@ -137,9 +118,9 @@ impl TryFrom> for PromisedReceive { match key.as_str() { "from" => match Ipld::try_from(prom) { Ok(Ipld::String(s)) => { - from = Some(promise::Resolves::from(Ok( - url::Newtype::parse(s.as_str()).map_err(|_| ())? - ))); + from = Some(promise::Any::Resolved( + url::Newtype::parse(s.as_str()).map_err(|_| ())?, + )); } Err(pending) => from = Some(pending.into()), _ => return Err(()), @@ -157,7 +138,7 @@ impl From for arguments::Named { let mut args = arguments::Named::new(); if let Some(from) = promised.from { - let _ = ipld::Promised::from(from).with_resolved(|ipld| { + let _ = from.to_promised_ipld().with_resolved(|ipld| { args.insert("from".into(), ipld.into()); }); } diff --git a/src/ability/msg/send.rs b/src/ability/msg/send.rs index 3329cae5..b37fe78c 100644 --- a/src/ability/msg/send.rs +++ b/src/ability/msg/send.rs @@ -84,16 +84,16 @@ pub struct Send { #[serde(deny_unknown_fields)] pub struct PromisedSend { /// The recipient of the message - pub to: promise::Resolves, + pub to: promise::Any, /// The sender address of the message /// /// This *may* be a URL (such as an email address). /// If provided, the `subject` must have the right to send from this address. - pub from: promise::Resolves, + pub from: promise::Any, /// The main body of the message - pub message: promise::Resolves, + pub message: promise::Any, } impl promise::Resolvable for Send { @@ -150,24 +150,24 @@ impl TryFrom> for PromisedSend { match key.as_str() { "to" => match Ipld::try_from(prom) { Ok(Ipld::String(s)) => { - to = Some(promise::Resolves::from(Ok( - url::Newtype::parse(s.as_str()).map_err(|_| ())? - ))); + to = Some(promise::Any::Resolved( + url::Newtype::parse(s.as_str()).map_err(|_| ())?, + )); } Err(pending) => to = Some(pending.into()), _ => return Err(()), }, "from" => match Ipld::try_from(prom) { Ok(Ipld::String(s)) => { - from = Some(promise::Resolves::from(Ok( - url::Newtype::parse(s.as_str()).map_err(|_| ())? - ))); + from = Some(promise::Any::Resolved( + url::Newtype::parse(s.as_str()).map_err(|_| ())?, + )); } Err(pending) => from = Some(pending.into()), _ => return Err(()), }, "message" => match Ipld::try_from(prom) { - Ok(Ipld::String(s)) => message = Some(promise::Resolves::from(Ok(s))), + Ok(Ipld::String(s)) => message = Some(promise::Any::Resolved(s)), Err(pending) => to = Some(pending.into()), _ => return Err(()), }, @@ -206,9 +206,9 @@ impl Command for PromisedSend { impl From for PromisedSend { fn from(r: Send) -> Self { PromisedSend { - to: promise::Resolves::from(Ok(r.to)), - from: promise::Resolves::from(Ok(r.from)), - message: promise::Resolves::from(Ok(r.message)), + to: promise::Any::Resolved(r.to), + from: promise::Any::Resolved(r.from), + message: promise::Any::Resolved(r.message), } } } @@ -217,9 +217,13 @@ impl TryFrom for Send { type Error = PromisedSend; fn try_from(p: PromisedSend) -> Result { - match promise::Resolves::try_resolve_3(p.to, p.from, p.message) { - Ok((to, from, message)) => Ok(Send { to, from, message }), - Err((to, from, message)) => Err(PromisedSend { to, from, message }), + match p { + PromisedSend { + to: promise::Any::Resolved(to), + from: promise::Any::Resolved(from), + message: promise::Any::Resolved(message), + } => Ok(Send { to, from, message }), + _ => Err(p), } } } @@ -227,9 +231,9 @@ impl TryFrom for Send { impl From for arguments::Named { fn from(p: PromisedSend) -> Self { arguments::Named::from_iter([ - ("to".into(), p.to.into()), - ("from".into(), p.from.into()), - ("message".into(), p.message.into()), + ("to".into(), p.to.to_promised_ipld()), + ("from".into(), p.from.to_promised_ipld()), + ("message".into(), p.message.to_promised_ipld()), ]) } } diff --git a/src/ability/ucan/revoke.rs b/src/ability/ucan/revoke.rs index 4c3cd4a2..4505fc77 100644 --- a/src/ability/ucan/revoke.rs +++ b/src/ability/ucan/revoke.rs @@ -16,6 +16,7 @@ use std::fmt::Debug; pub struct Revoke { /// The UCAN to revoke pub ucan: Cid, + // FIXME pub witness } const COMMAND: &'static str = "/ucan/revoke"; @@ -44,14 +45,14 @@ impl promise::Resolvable for Revoke { impl From for arguments::Named { fn from(promised: PromisedRevoke) -> Self { - arguments::Named::from_iter([("ucan".into(), promised.ucan.into())]) + arguments::Named::from_iter([("ucan".into(), Ipld::from(promised.ucan).into())]) } } /// A variant where arguments may be [`Promise`][crate::invocation::promise]s. #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] pub struct PromisedRevoke { - pub ucan: promise::Resolves, + pub ucan: promise::Any, } impl TryFrom> for PromisedRevoke { @@ -64,7 +65,7 @@ impl TryFrom> for PromisedRevoke { match k.as_str() { "ucan" => match Ipld::try_from(prom) { Ok(Ipld::Link(cid)) => { - ucan = Some(promise::Resolves::new(cid)); + ucan = Some(promise::Any::Resolved(cid)); } Err(pending) => ucan = Some(pending.into()), _ => return Err(()), @@ -82,7 +83,7 @@ impl TryFrom> for PromisedRevoke { impl From for PromisedRevoke { fn from(r: Revoke) -> PromisedRevoke { PromisedRevoke { - ucan: Ok(r.ucan).into(), + ucan: promise::Any::Resolved(r.ucan), } } } diff --git a/src/ability/wasm/run.rs b/src/ability/wasm/run.rs index 2aad7530..dcbe97de 100644 --- a/src/ability/wasm/run.rs +++ b/src/ability/wasm/run.rs @@ -79,9 +79,9 @@ impl promise::Resolvable for Run { /// A variant meant for linking together invocations with promises #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] pub struct PromisedRun { - pub module: promise::Resolves, - pub function: promise::Resolves, - pub args: promise::Resolves>, + pub module: promise::Any, + pub function: promise::Any, + pub args: promise::Any>, } impl TryFrom> for PromisedRun { @@ -94,11 +94,11 @@ impl TryFrom> for PromisedRun { for (key, prom) in named { match key.as_str() { - "module" => module = Some(prom.try_into().map_err(|_| ())?), - "function" => function = Some(prom.try_into().map_err(|_| ())?), + "module" => module = Some(prom.to_promise_any().map_err(|_| ())?), + "function" => function = Some(prom.to_promise_any_string()?), "args" => { if let ipld::Promised::List(list) = prom.into() { - args = Some(promise::Resolves::new(list)); + args = Some(promise::Any::Resolved(list)); } else { return Err(()); } @@ -116,15 +116,11 @@ impl TryFrom> for PromisedRun { } impl From for PromisedRun { - fn from(ready: Run) -> Self { + fn from(run: Run) -> Self { PromisedRun { - module: promise::Resolves::from(Ok(ready.module)), - function: promise::Resolves::from(Ok(ready.function)), - args: promise::Resolves::from(Ok(ready - .args - .iter() - .map(|ipld| ipld.clone().into()) - .collect())), + module: promise::Any::Resolved(run.module), + function: promise::Any::Resolved(run.function), + args: promise::Any::Resolved(run.args.iter().map(|ipld| ipld.clone().into()).collect()), } } } @@ -132,9 +128,9 @@ impl From for PromisedRun { impl From for arguments::Named { fn from(promised: PromisedRun) -> Self { arguments::Named::from_iter([ - ("module".into(), promised.module.into()), - ("function".into(), promised.function.into()), - ("args".into(), promised.args.into()), + ("module".into(), promised.module.to_promised_ipld()), + ("function".into(), promised.function.to_promised_ipld()), + ("args".into(), promised.args.to_promised_ipld()), ]) } } diff --git a/src/invocation/promise.rs b/src/invocation/promise.rs index 73907a97..baacb12b 100644 --- a/src/invocation/promise.rs +++ b/src/invocation/promise.rs @@ -5,51 +5,39 @@ mod err; mod ok; mod pending; mod resolvable; -mod resolves; pub mod store; // FIXME pub mod js; -pub use any::PromiseAny; +pub use any::Any; pub use err::PromiseErr; pub use ok::PromiseOk; pub use pending::Pending; pub use resolvable::*; -pub use resolves::Resolves; pub use store::Store; use enum_as_inner::EnumAsInner; +use libipld_core::cid::Cid; use serde::{Deserialize, Serialize}; /// Top-level union of all UCAN Promise options #[derive(Debug, Clone, PartialEq, Serialize, Deserialize, EnumAsInner)] -#[serde(untagged)] pub enum Promise { /// The `ucan/await/ok` promise - Ok(PromiseOk), + Ok(T), /// The `ucan/await/err` promise - Err(PromiseErr), + Err(E), - /// The `ucan/await/*` promise - Any(PromiseAny), - // Tagged `ucan/await` -} + /// The `ucan/await/ok` promise + PendingOk(Cid), -impl From> for Promise { - fn from(p_ok: PromiseOk) -> Self { - Promise::Ok(p_ok) - } -} + /// The `ucan/await/err` promise + PendingErr(Cid), -impl From> for Promise { - fn from(p_err: PromiseErr) -> Self { - Promise::Err(p_err) - } -} + /// The `ucan/await/*` promise + PendingAny(Cid), -impl From> for Promise { - fn from(p_any: PromiseAny) -> Self { - Promise::Any(p_any) - } + /// The `ucan/await` promise + PendingTagged(Cid), } diff --git a/src/invocation/promise/any.rs b/src/invocation/promise/any.rs index 1825df10..657c1366 100644 --- a/src/invocation/promise/any.rs +++ b/src/invocation/promise/any.rs @@ -1,237 +1,111 @@ -use super::{err::PromiseErr, ok::PromiseOk}; -use crate::{ability::arguments, ipld::cid}; -use libipld_core::{cid::Cid, error::SerdeError, ipld::Ipld, serde as ipld_serde}; -use serde::{ - de::{Deserializer, Error, MapAccess, Visitor}, - Deserialize, Serialize, -}; -use std::fmt; - -#[cfg(feature = "test_utils")] -use proptest::prelude::*; - -/// A promise that unwraps the same value from either the `{"ok": T}` or `{"err": T}` branches. -/// -/// Unlike [`Resolves`][super::Resolves]: -/// -/// 1. The branches may be of different types -/// 2. The underlying value is _left wrapped_ in `{"ok": T}` or `{"err": T}` capsules -/// -/// FIXME example -#[derive(Debug, Clone, PartialEq, Eq, Serialize)] -#[serde(untagged)] -pub enum PromiseAny { - /// The fulfilled (resolved) value. - Fulfilled(#[serde(rename = "ucan/ok")] T), - - /// The failure state of a promise. - Rejected(#[serde(rename = "ucan/err")] E), - - /// A deferred value and its associated [`Selector`]. - /// - /// The [`Selector`] will resolve a branch from the [`Receipt`][crate::receipt::Receipt] - /// and substitute into the value. - Pending(#[serde(rename = "await/*")] Cid), -} - -impl<'de, T: Deserialize<'de>, E: Deserialize<'de>> Deserialize<'de> for PromiseAny { - fn deserialize(deserializer: D) -> Result, D::Error> - where - D: Deserializer<'de>, - { - struct PromiseAnyVisitor(std::marker::PhantomData<(T, E)>); - - impl<'de, T: Deserialize<'de>, E: Deserialize<'de>> Visitor<'de> for PromiseAnyVisitor { - type Value = PromiseAny; - - fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { - formatter.write_str("a promise") - } - - fn visit_map(self, mut map: M) -> Result, M::Error> - where - M: MapAccess<'de>, - { - if map.size_hint() != Some(1) { - return Err(serde::de::Error::custom("expected a single key")); - } - - let key = map - .next_key::()? - .ok_or(Error::invalid_length(0, &"expected exactly 1 key"))?; - - match key.as_str() { - "ucan/ok" => { - let val = map.next_value()?; - return Ok(PromiseAny::Fulfilled(val)); - } - - "ucan/err" => { - let err = map.next_value()?; - return Ok(PromiseAny::Rejected(err)); - } - - "await/*" => { - let cid = map.next_value()?; - return Ok(PromiseAny::Pending(cid)); - } - - _ => return Err(serde::de::Error::custom("expected a valid PromiseAny")), - } - } - } - - deserializer.deserialize_map(PromiseAnyVisitor(std::marker::PhantomData)) - } +use crate::ipld; +use super::pending::Pending; +use enum_as_inner::EnumAsInner; +use libipld_core::cid::Cid; +use libipld_core::ipld::Ipld; +use serde::{Deserialize, Serialize}; +use std::collections::BTreeMap; + +// FIXME +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, EnumAsInner)] +pub enum Any { + /// The `ucan/await/ok` promise + Resolved(T), + + /// The `ucan/await/ok` promise + PendingOk(Cid), + + /// The `ucan/await/err` promise + PendingErr(Cid), + + /// The `ucan/await/*` promise + PendingAny(Cid), } -impl PromiseAny { - pub fn map(self, f: F) -> PromiseAny - where - F: FnOnce(T) -> U, - { +impl Any { + pub fn try_resolve(self) -> Result> { match self { - PromiseAny::Fulfilled(val) => PromiseAny::Fulfilled(f(val)), - PromiseAny::Rejected(err) => PromiseAny::Rejected(err), - PromiseAny::Pending(cid) => PromiseAny::Pending(cid), + Any::Resolved(value) => Ok(value), + _ => Err(self), } } - pub fn map_err(self, f: F) -> PromiseAny + pub fn from_ipld(ipld: Ipld) -> Self where - F: FnOnce(E) -> X, + T: From, { - match self { - PromiseAny::Fulfilled(val) => PromiseAny::Fulfilled(val), - PromiseAny::Rejected(err) => PromiseAny::Rejected(f(err)), - PromiseAny::Pending(cid) => PromiseAny::Pending(cid), - } - } -} - -impl From> for Ipld { - fn from(p: PromiseAny) -> Ipld { - p.into() - } -} + match ipld { + Ipld::Map(ref map) => { + if let Some(Ipld::Link(cid)) = map.get("ucan/await/ok") { + return Any::PendingOk(cid.clone()); + } -impl TryFrom for PromiseAny -where - T: for<'de> Deserialize<'de>, - E: for<'de> Deserialize<'de>, -{ - type Error = SerdeError; + if let Some(Ipld::Link(cid)) = map.get("ucan/await/err") { + return Any::PendingErr(cid.clone()); + } - fn try_from(ipld: Ipld) -> Result, Self::Error> { - ipld_serde::from_ipld(ipld) - } -} + if let Some(Ipld::Link(cid)) = map.get("ucan/await/*") { + return Any::PendingAny(cid.clone()); + } -impl, E: Into> From> for arguments::Named { - fn from(p: PromiseAny) -> arguments::Named { - match p { - PromiseAny::Fulfilled(val) => { - arguments::Named::from_iter([("ucan/ok".into(), val.into())]) - } - PromiseAny::Rejected(err) => { - arguments::Named::from_iter([("ucan/err".into(), err.into())]) + Any::Resolved(ipld.into()) } - PromiseAny::Pending(cid) => { - arguments::Named::from_iter([("await/*".into(), cid.into())]) - } - } - } -} - -impl TryFrom<&'a Ipld>, E: for<'a> TryFrom<&'a Ipld>> TryFrom> - for PromiseAny -{ - type Error = (); // FIXME - - fn try_from(args: arguments::Named) -> Result, Self::Error> { - if args.len() != 1 { - return Err(()); - } - - if let Some(ipld) = args.get("ucan/ok") { - return Ok(PromiseAny::Fulfilled(ipld.try_into().map_err(|_| ())?)); - } - - if let Some(ipld) = args.get("ucan/err") { - return Ok(PromiseAny::Rejected(ipld.try_into().map_err(|_| ())?)); - } - - if let Some(ipld) = args.get("await/*") { - return match cid::Newtype::try_from(ipld) { - Ok(nt) => Ok(PromiseAny::Pending(nt.cid)), - Err(_) => Err(()), - }; + other => Any::Resolved(other.into()), } - - Err(()) } -} -impl From> for PromiseAny { - fn from(p_ok: PromiseOk) -> PromiseAny { - match p_ok { - PromiseOk::Fulfilled(val) => PromiseAny::Fulfilled(val), - PromiseOk::Pending(cid) => PromiseAny::Pending(cid), + pub fn to_promised_ipld(self) -> ipld::Promised + where + T: Into, + { + match self { + Any::Resolved(value) => value.into(), + Any::PendingOk(cid) => ipld::Promised::WaitOk(cid), + Any::PendingErr(cid) => ipld::Promised::WaitErr(cid), + Any::PendingAny(cid) => ipld::Promised::WaitAny(cid), } } } -impl From> for PromiseAny { - fn from(p_err: PromiseErr) -> PromiseAny { - match p_err { - PromiseErr::Rejected(err) => PromiseAny::Rejected(err), - PromiseErr::Pending(cid) => PromiseAny::Pending(cid), +impl From for Any { + fn from(pending: Pending) -> Any { + match pending { + Pending::Ok(cid) => Any::PendingOk(cid), + Pending::Err(cid) => Any::PendingErr(cid), + Pending::Any(cid) => Any::PendingAny(cid), } } } -impl TryFrom> for PromiseOk { - type Error = (); // FIXME - - fn try_from(p_any: PromiseAny) -> Result, Self::Error> { - match p_any { - PromiseAny::Fulfilled(val) => Ok(PromiseOk::Fulfilled(val)), - PromiseAny::Rejected(_err) => Err(()), - PromiseAny::Pending(cid) => Ok(PromiseOk::Pending(cid)), +impl> From> for Ipld { + fn from(promise: Any) -> Ipld { + match promise { + Any::Resolved(val) => val.into(), + Any::PendingOk(cid) => Ipld::Map(BTreeMap::from_iter([( + "ucan/await/ok".to_string(), + cid.into(), + )])), + Any::PendingErr(cid) => Ipld::Map(BTreeMap::from_iter([( + "ucan/await/err".to_string(), + cid.into(), + )])), + Any::PendingAny(cid) => Ipld::Map(BTreeMap::from_iter([( + "ucan/await/*".to_string(), + cid.into(), + )])), } } } -impl TryFrom> for PromiseErr { - type Error = (); // FIXME +impl> TryFrom for Any { + type Error = >::Error; - fn try_from(p_any: PromiseAny) -> Result, Self::Error> { - match p_any { - PromiseAny::Fulfilled(_val) => Err(()), - PromiseAny::Rejected(err) => Ok(PromiseErr::Rejected(err)), - PromiseAny::Pending(cid) => Ok(PromiseErr::Pending(cid)), + fn try_from(promised: ipld::Promised) -> Result, Self::Error> { + match promised { + ipld::Promised::WaitOk(cid) => Ok(Any::PendingOk(cid)), + ipld::Promised::WaitErr(cid) => Ok(Any::PendingErr(cid)), + ipld::Promised::WaitAny(cid) => Ok(Any::PendingAny(cid)), + other => Ok(Any::Resolved(T::try_from(other)?)), } } } - -#[cfg(feature = "test_utils")] -impl Arbitrary - for PromiseAny -where - T::Strategy: 'static, - T::Parameters: 'static, - E::Strategy: 'static, - E::Parameters: 'static, -{ - type Parameters = (T::Parameters, E::Parameters); - type Strategy = BoxedStrategy; - - fn arbitrary_with((t_args, e_args): Self::Parameters) -> Self::Strategy { - prop_oneof![ - T::arbitrary_with(t_args).prop_map(PromiseAny::Fulfilled), - E::arbitrary_with(e_args).prop_map(PromiseAny::Rejected), - cid::Newtype::arbitrary().prop_map(|nt| PromiseAny::Pending(nt.cid)), - ] - .boxed() - } -} diff --git a/src/invocation/promise/resolves.rs b/src/invocation/promise/resolves.rs deleted file mode 100644 index 1dbec8ca..00000000 --- a/src/invocation/promise/resolves.rs +++ /dev/null @@ -1,352 +0,0 @@ -use super::{Pending, Promise, PromiseAny, PromiseErr, PromiseOk}; -use libipld_core::ipld::Ipld; -use serde::{Deserialize, Serialize}; -use std::fmt; - -#[cfg(feature = "test_utils")] -use proptest::prelude::*; - -/// A promise that unwraps the same value from either the `{"ok": T}` or `{"err": T}` branches. -/// -/// Unlike [`PromiseAny`][super::PromiseAny]: -/// -/// 1. Both branches of this promise resolve to the same type -/// 2. The underlying value is unwrapped from the `{"ok": T}` or `{"err": T}` capsules -/// -/// FIXME example -#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] -#[serde(untagged)] -pub enum Resolves { - Ok(PromiseOk), - Err(PromiseErr), -} - -impl Resolves> { - // FIXME Helpful for serde, maybe extract to a trait? - pub fn resolved_none(&self) -> bool { - match self { - Resolves::Ok(p_ok) => match p_ok { - PromiseOk::Fulfilled(None) => true, - _ => false, - }, - Resolves::Err(p_err) => match p_err { - PromiseErr::Rejected(None) => true, - _ => false, - }, - } - } - - pub fn try_resolve_option(self) -> Option { - match self { - Resolves::Ok(p_ok) => p_ok.try_resolve().ok()?, - Resolves::Err(p_err) => p_err.try_resolve().ok()?, - } - } -} - -impl Resolves { - pub fn new(val: T) -> Self { - Resolves::Ok(PromiseOk::Fulfilled(val)) - } - - pub fn try_resolve(self) -> Result> { - match self { - Resolves::Ok(p_ok) => p_ok.try_resolve().map_err(Resolves::Ok), - Resolves::Err(p_err) => p_err.try_resolve().map_err(Resolves::Err), - } - } - - // FIXME replace with variadic macro? - // FIXME docs - pub fn try_resolve_2( - t: Resolves, - u: Resolves, - ) -> Result<(T, U), (Resolves, Resolves)> - where - T: fmt::Debug, - U: fmt::Debug, - { - if t.is_ready() && u.is_ready() { - Ok((t.try_resolve().unwrap(), u.try_resolve().unwrap())) - } else { - Err((t, u)) - } - } - - pub fn try_resolve_3( - t: Resolves, - u: Resolves, - v: Resolves, - ) -> Result<(T, U, V), (Resolves, Resolves, Resolves)> - where - T: fmt::Debug, - U: fmt::Debug, - V: fmt::Debug, - { - if t.is_ready() && u.is_ready() && v.is_ready() { - Ok(( - t.try_resolve().unwrap(), - u.try_resolve().unwrap(), - v.try_resolve().unwrap(), - )) - } else { - Err((t, u, v)) - } - } - - pub fn try_resolve_4( - t: Resolves, - u: Resolves, - v: Resolves, - w: Resolves, - ) -> Result<(T, U, V, W), (Resolves, Resolves, Resolves, Resolves)> - where - T: fmt::Debug, - U: fmt::Debug, - V: fmt::Debug, - W: fmt::Debug, - { - if t.is_ready() && u.is_ready() && v.is_ready() && w.is_ready() { - Ok(( - t.try_resolve().unwrap(), - u.try_resolve().unwrap(), - v.try_resolve().unwrap(), - w.try_resolve().unwrap(), - )) - } else { - Err((t, u, v, w)) - } - } - - pub fn try_resolve_5( - t: Resolves, - u: Resolves, - v: Resolves, - w: Resolves, - x: Resolves, - ) -> Result< - (T, U, V, W, X), - ( - Resolves, - Resolves, - Resolves, - Resolves, - Resolves, - ), - > - where - T: fmt::Debug, - U: fmt::Debug, - V: fmt::Debug, - W: fmt::Debug, - X: fmt::Debug, - { - if t.is_ready() && u.is_ready() && v.is_ready() && w.is_ready() && x.is_ready() { - Ok(( - t.try_resolve().unwrap(), - u.try_resolve().unwrap(), - v.try_resolve().unwrap(), - w.try_resolve().unwrap(), - x.try_resolve().unwrap(), - )) - } else { - Err((t, u, v, w, x)) - } - } - - pub fn try_resolve_6( - t: Resolves, - u: Resolves, - v: Resolves, - w: Resolves, - x: Resolves, - y: Resolves, - ) -> Result< - (T, U, V, W, X, Y), - ( - Resolves, - Resolves, - Resolves, - Resolves, - Resolves, - Resolves, - ), - > - where - T: fmt::Debug, - U: fmt::Debug, - V: fmt::Debug, - W: fmt::Debug, - X: fmt::Debug, - Y: fmt::Debug, - { - if [ - t.is_ready(), - u.is_ready(), - v.is_ready(), - w.is_ready(), - x.is_ready(), - y.is_ready(), - ] - .iter() - .all(|x| *x) - { - Ok(( - t.try_resolve().unwrap(), - u.try_resolve().unwrap(), - v.try_resolve().unwrap(), - w.try_resolve().unwrap(), - x.try_resolve().unwrap(), - y.try_resolve().unwrap(), - )) - } else { - Err((t, u, v, w, x, y)) - } - } - - pub fn is_ready(&self) -> bool { - match self { - Resolves::Ok(p_ok) => match p_ok { - PromiseOk::Fulfilled(_) => true, - _ => false, - }, - Resolves::Err(p_err) => match p_err { - PromiseErr::Rejected(_) => true, - _ => false, - }, - } - } - - pub fn map(self, f: F) -> Resolves - where - F: FnOnce(T) -> U, - { - match self { - Resolves::Ok(p_ok) => Resolves::Ok(p_ok.map(f)), - Resolves::Err(p_err) => Resolves::Err(p_err.map(f)), - } - } -} - -impl From for Resolves { - fn from(pending: Pending) -> Self { - match pending { - Pending::Ok(cid) => Resolves::Ok(PromiseOk::Pending(cid)), - Pending::Err(cid) => Resolves::Err(PromiseErr::Pending(cid)), - Pending::Any(cid) => Resolves::Ok(PromiseOk::Pending(cid)), // FIXME - } - } -} - -impl> TryFrom for Resolves { - type Error = Ipld; - - fn try_from(ipld: Ipld) -> Result { - // FIXME so much cloning - let t = ipld.clone().try_into().map_err(|_| ipld.clone())?; - Ok(PromiseOk::Fulfilled(t).into()) - } -} - -impl From> for Option { - fn from(r: Resolves) -> Option { - match r { - Resolves::Ok(p_ok) => p_ok.into(), - Resolves::Err(p_err) => p_err.into(), - } - } -} - -impl From> for Resolves { - fn from(result: Result) -> Self { - match result { - Ok(value) => Resolves::Ok(PromiseOk::Fulfilled(value)), - Err(value) => Resolves::Err(PromiseErr::Rejected(value)), - } - } -} - -impl> From> for Ipld { - fn from(resolves: Resolves) -> Ipld { - match resolves { - Resolves::Ok(p_ok) => p_ok.into(), - Resolves::Err(p_err) => p_err.into(), - } - } -} - -impl From> for Resolves { - fn from(ok: PromiseOk) -> Self { - Resolves::Ok(ok) - } -} - -impl From> for Resolves { - fn from(err: PromiseErr) -> Self { - Resolves::Err(err) - } -} - -impl From> for Promise { - fn from(resolves: Resolves) -> Promise { - match resolves { - Resolves::Ok(p_ok) => p_ok.into(), - Resolves::Err(p_err) => p_err.into(), - } - } -} - -impl TryFrom> for PromiseOk { - type Error = PromiseErr; - - fn try_from(resolved: Resolves) -> Result { - match resolved { - Resolves::Ok(ok) => Ok(ok), - Resolves::Err(err) => Err(err), - } - } -} - -impl TryFrom> for PromiseErr { - type Error = PromiseOk; - - fn try_from(resolved: Resolves) -> Result { - match resolved { - Resolves::Ok(ok) => Err(ok), - Resolves::Err(err) => Ok(err), - } - } -} - -impl From> for PromiseAny { - fn from(resolve: Resolves) -> PromiseAny { - match resolve { - Resolves::Ok(p_ok) => match p_ok { - PromiseOk::Fulfilled(value) => PromiseAny::Fulfilled(value), - PromiseOk::Pending(cid) => PromiseAny::Pending(cid), - }, - Resolves::Err(p_err) => match p_err { - PromiseErr::Rejected(err) => PromiseAny::Rejected(err), - PromiseErr::Pending(cid) => PromiseAny::Pending(cid), - }, - } - } -} - -#[cfg(feature = "test_utils")] -impl Arbitrary for Resolves -where - T::Strategy: 'static, - T::Parameters: 'static, -{ - type Parameters = (T::Parameters, T::Parameters); - type Strategy = BoxedStrategy; - - fn arbitrary_with((ok_args, err_args): Self::Parameters) -> Self::Strategy { - prop_oneof![ - PromiseOk::::arbitrary_with(ok_args).prop_map(Resolves::Ok), - PromiseErr::::arbitrary_with(err_args).prop_map(Resolves::Err), - ] - .boxed() - } -} diff --git a/src/ipld/newtype.rs b/src/ipld/newtype.rs index 43a1f480..30a28c89 100644 --- a/src/ipld/newtype.rs +++ b/src/ipld/newtype.rs @@ -1,8 +1,8 @@ use libipld_core::ipld::Ipld; use serde::{Deserialize, Serialize}; +use std::fmt; use std::path::PathBuf; use thiserror::Error; -use std::fmt; #[cfg(target_arch = "wasm32")] use wasm_bindgen::prelude::*; diff --git a/src/ipld/promised.rs b/src/ipld/promised.rs index 781045a9..0416fde2 100644 --- a/src/ipld/promised.rs +++ b/src/ipld/promised.rs @@ -1,6 +1,6 @@ use crate::{ ability::arguments, - invocation::promise::{Pending, Promise, PromiseAny, PromiseErr, PromiseOk, Resolves}, + invocation::promise::{self, Pending, PromiseErr, PromiseOk}, ipld, url, }; use enum_as_inner::EnumAsInner; @@ -51,6 +51,15 @@ pub enum Promised { } impl Promised { + pub fn try_resolve(self) -> Result { + match self { + Promised::WaitOk(cid) => Err(Pending::Ok(cid)), + Promised::WaitErr(cid) => Err(Pending::Err(cid)), + Promised::WaitAny(cid) => Err(Pending::Any(cid)), + other => other.try_into().map_err(Into::into), + } + } + pub fn with_resolved(self, f: F) -> Result where F: FnOnce(Ipld) -> T, @@ -70,6 +79,30 @@ impl Promised { Err(promised) => Ok(f(promised)), } } + + pub fn to_promise_any>( + self, + ) -> Result, >::Error> { + Ok(match Ipld::try_from(self) { + Ok(ipld) => promise::Any::Resolved(ipld.try_into()?), + Err(pending) => match pending { + Pending::Ok(cid) => promise::Any::PendingOk(cid), + Pending::Err(cid) => promise::Any::PendingErr(cid), + Pending::Any(cid) => promise::Any::PendingAny(cid), + }, + }) + } + + // FIXME return type + pub fn to_promise_any_string(self) -> Result, ()> { + match self { + Promised::String(s) => Ok(promise::Any::Resolved(s)), + Promised::WaitOk(cid) => Ok(promise::Any::PendingOk(cid)), + Promised::WaitErr(cid) => Ok(promise::Any::PendingErr(cid)), + Promised::WaitAny(cid) => Ok(promise::Any::PendingAny(cid)), + _ => Err(()), + } + } } impl fmt::Display for Promised { @@ -197,100 +230,13 @@ impl From> for Promised { } } -impl From> for Promised { - fn from(p_any: PromiseAny) -> Promised { +impl From> for Promised { + fn from(p_any: promise::Any) -> Promised { match p_any { - PromiseAny::Fulfilled(ipld) => ipld.into(), - PromiseAny::Rejected(ipld) => ipld.into(), - PromiseAny::Pending(cid) => Promised::WaitAny(cid), - } - } -} - -impl From> for Promised { - fn from(promise: Promise) -> Promised { - match promise { - Promise::Ok(p_ok) => p_ok.into(), - Promise::Err(p_err) => p_err.into(), - Promise::Any(p_any) => p_any.into(), - } - } -} - -impl> TryFrom for Resolves { - type Error = (); - - fn try_from(promised: Promised) -> Result, Self::Error> { - match promised { - Promised::WaitOk(cid) => Ok(Resolves::Ok(PromiseOk::Pending(cid))), - Promised::WaitErr(cid) => Ok(Resolves::Err(PromiseErr::Pending(cid))), - Promised::WaitAny(cid) => Ok(Resolves::Ok(PromiseOk::Pending(cid))), // FIXME - - Promised::Null => Ok(Resolves::Ok(PromiseOk::Fulfilled( - T::try_from(Ipld::Null.into()).map_err(|_| ())?, - ))), - Promised::Bool(b) => Ok(Resolves::Ok(PromiseOk::Fulfilled( - T::try_from(Ipld::Bool(b).into()).map_err(|_| ())?, - ))), - Promised::Integer(i) => Ok(Resolves::Ok(PromiseOk::Fulfilled( - T::try_from(Ipld::Integer(i).into()).map_err(|_| ())?, - ))), - Promised::Float(f) => Ok(Resolves::Ok(PromiseOk::Fulfilled( - T::try_from(Ipld::Float(f).into()).map_err(|_| ())?, - ))), - Promised::String(s) => Ok(Resolves::Ok(PromiseOk::Fulfilled( - T::try_from(Ipld::String(s).into()).map_err(|_| ())?, - ))), - Promised::Bytes(b) => Ok(Resolves::Ok(PromiseOk::Fulfilled( - T::try_from(Ipld::Bytes(b).into()).map_err(|_| ())?, - ))), - Promised::Link(cid) => Ok(Resolves::Ok(PromiseOk::Fulfilled( - T::try_from(Ipld::Link(cid).into()).map_err(|_| ())?, - ))), - - Promised::List(list) => { - let vec: Vec = list.into_iter().try_fold(vec![], |mut acc, promised| { - let ipld: Ipld = promised.try_into().map_err(|_| ())?; - acc.push(ipld); - Ok(acc) - })?; - - Ok(Resolves::Ok(PromiseOk::Fulfilled( - ipld::Newtype(Ipld::List(vec)).try_into().map_err(|_| ())?, - ))) - } - - Promised::Map(map) => { - let btree: BTreeMap = - map.into_iter() - .try_fold(BTreeMap::new(), |mut acc, (k, v)| { - let ipld: Ipld = v.try_into().map_err(|_| ())?; - acc.insert(k, ipld); - Ok(acc) - })?; - - Ok(Resolves::Ok(PromiseOk::Fulfilled( - ipld::Newtype(Ipld::Map(btree)).try_into().map_err(|_| ())?, - ))) - } - } - } -} - -impl From> for Promised -where - Promised: From, -{ - fn from(r: Resolves) -> Promised { - match r { - Resolves::Ok(p_ok) => match p_ok { - PromiseOk::Fulfilled(val) => val.into(), - PromiseOk::Pending(cid) => Promised::WaitOk(cid), - }, - Resolves::Err(p_err) => match p_err { - PromiseErr::Rejected(val) => val.into(), - PromiseErr::Pending(cid) => Promised::WaitErr(cid), - }, + promise::Any::Resolved(ipld) => ipld.into(), + promise::Any::PendingOk(cid) => Promised::WaitOk(cid), + promise::Any::PendingErr(cid) => Promised::WaitErr(cid), + promise::Any::PendingAny(cid) => Promised::WaitAny(cid), } } } From 537811f829132400e8c6d80d8405e0c1d495fe1c Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Wed, 6 Mar 2024 20:41:25 -0800 Subject: [PATCH 120/188] Switch to envelope trait --- src/ability/pipe.rs | 20 +- src/crypto/signature/envelope.rs | 577 ++++++++++++++++++++----------- src/delegation.rs | 128 +++---- src/delegation/agent.rs | 14 +- src/invocation.rs | 179 +++++----- src/invocation/agent.rs | 82 +++-- src/lib.rs | 1 - src/proof.rs | 38 -- src/receipt.rs | 135 +++++--- 9 files changed, 681 insertions(+), 493 deletions(-) delete mode 100644 src/proof.rs diff --git a/src/ability/pipe.rs b/src/ability/pipe.rs index a41d9c01..0dd41874 100644 --- a/src/ability/pipe.rs +++ b/src/ability/pipe.rs @@ -1,22 +1,22 @@ use crate::{crypto::varsig, delegation, did::Did, ipld}; use libipld_core::{codec::Codec, ipld::Ipld}; -pub struct Pipe, Enc: Codec + TryFrom + Into> { - pub source: Cap, - pub sink: Cap, +pub struct Pipe, C: Codec + TryFrom + Into> { + pub source: Cap, + pub sink: Cap, } -pub enum Cap, Enc: Codec + TryFrom + Into> { - Proof(delegation::Proof), +pub enum Cap, C: Codec + TryFrom + Into> { + Proof(delegation::Proof), Literal(Ipld), } -pub struct PromisedPipe, Enc: Codec + TryFrom + Into> { - pub source: PromisedCap, - pub sink: PromisedCap, +pub struct PromisedPipe, C: Codec + TryFrom + Into> { + pub source: PromisedCap, + pub sink: PromisedCap, } -pub enum PromisedCap, Enc: Codec + TryFrom + Into> { - Proof(delegation::Proof), +pub enum PromisedCap, C: Codec + TryFrom + Into> { + Proof(delegation::Proof), Promised(ipld::Promised), } diff --git a/src/crypto/signature/envelope.rs b/src/crypto/signature/envelope.rs index b0f3aa51..a8b358fc 100644 --- a/src/crypto/signature/envelope.rs +++ b/src/crypto/signature/envelope.rs @@ -1,8 +1,4 @@ -use crate::{ - capsule::Capsule, - crypto::varsig, - did::{Did, Verifiable}, -}; +use crate::{capsule::Capsule, crypto::varsig, did::Did}; use libipld_core::{ cid::Cid, codec::{Codec, Encode}, @@ -10,64 +6,79 @@ use libipld_core::{ ipld::Ipld, multihash::{Code, MultihashDigest}, }; -use serde::{Deserialize, Deserializer, Serialize, Serializer}; +use signature::Verifier; use signature::{SignatureEncoding, Signer}; use std::collections::BTreeMap; use thiserror::Error; -/// A container associating a `payload` with its signature over it. -#[derive(Debug, Clone, PartialEq)] -pub struct Envelope< - T: Verifiable + Capsule, - DID: Did, - V: varsig::Header, - Enc: Codec + TryFrom + Into, -> { - /// The [Varsig][crate::crypto::varsig] header. - pub varsig_header: V, +pub trait Envelope: Sized { + type DID: Did; + type Payload: Clone + Capsule + TryFrom + Into; + type VarsigHeader: varsig::Header + Clone; + type Encoder: Codec + TryFrom + Into; + + fn varsig_header(&self) -> &Self::VarsigHeader; + fn signature(&self) -> &::Signature; + fn payload(&self) -> &Self::Payload; + fn verifier(&self) -> &Self::DID; + + fn construct( + varsig_header: Self::VarsigHeader, + signature: ::Signature, + payload: Self::Payload, + ) -> Self; + + fn to_ipld_envelope(&self) -> Ipld { + let wrapped_payload: Ipld = + BTreeMap::from_iter([(Self::Payload::TAG.into(), self.payload().clone().into())]) + .into(); + + let header_bytes: Vec = (*self.varsig_header()).clone().into(); + let header: Ipld = vec![header_bytes.into(), wrapped_payload].into(); + let sig_bytes: Ipld = self.signature().to_vec().into(); + + vec![sig_bytes.into(), header].into() + } - /// The signture of the `payload`. - pub signature: DID::Signature, + fn try_from_ipld_envelope( + ipld: Ipld, + ) -> Result>::Error>> { + if let Ipld::List(list) = ipld { + if let [Ipld::Bytes(sig), Ipld::List(inner)] = list.as_slice() { + if let [Ipld::Bytes(varsig_header), Ipld::Map(btree)] = inner.as_slice() { + if let (1, Some(inner)) = ( + btree.len(), + btree.get(::TAG.into()), + ) { + let payload = Self::Payload::try_from(inner.clone()) + .map_err(FromIpldError::CannotParsePayload)?; - /// The payload that's being signed over. - pub payload: T, + let varsig_header = Self::VarsigHeader::try_from(varsig_header.as_slice()) + .map_err(|_| FromIpldError::CannotParseVarsigHeader)?; - _phantom: std::marker::PhantomData, -} + let signature = ::Signature::try_from(sig.as_slice()) + .map_err(|_| FromIpldError::CannotParseSignature)?; -impl< - T: Verifiable + Capsule, - DID: Did, - V: varsig::Header, - Enc: Codec + TryFrom + Into, - > Verifiable for Envelope -{ - fn verifier(&self) -> &DID { - &self.payload.verifier() - } -} - -impl< - T: Capsule + Verifiable + Into, - DID: Did, - V: varsig::Header, - Enc: Codec + TryFrom + Into, - > Envelope -{ - pub fn new(varsig_header: V, signature: DID::Signature, payload: T) -> Self { - Envelope { - varsig_header, - signature, - payload, - _phantom: std::marker::PhantomData, + Ok(Self::construct(varsig_header, signature, payload)) + } else { + Err(FromIpldError::InvalidPayloadCapsule) + } + } else { + Err(FromIpldError::InvalidVarsigContainer) + } + } else { + Err(FromIpldError::InvalidSignatureContainer) + } + } else { + Err(FromIpldError::InvalidSignatureContainer) } } - pub fn varsig_encode(self, w: &mut Vec) -> Result<(), libipld_core::error::Error> + fn varsig_encode(self, w: &mut Vec) -> Result<(), libipld_core::error::Error> where - Ipld: Encode + From, + Ipld: Encode + From, { - let codec = self.varsig_header.codec().clone(); + let codec = varsig::header::Header::codec(self.varsig_header()).clone(); let ipld = Ipld::from(self); ipld.encode(codec, w) } @@ -82,14 +93,14 @@ impl< /// # Errors /// /// * [`SignError`] - the payload can't be encoded or the signature fails. - pub fn try_sign( - signer: &DID::Signer, - varsig_header: V, - payload: T, - ) -> Result, SignError> + // FIXME ported + fn try_sign( + signer: &::Signer, + varsig_header: Self::VarsigHeader, + payload: Self::Payload, + ) -> Result where - T: Clone, - Ipld: Encode, + Ipld: Encode + From, { Self::try_sign_generic(signer, varsig_header, payload) } @@ -108,32 +119,26 @@ impl< /// /// # Example /// - /// FIXME - pub fn try_sign_generic( - signer: &DID::Signer, - varsig_header: V, - payload: T, - ) -> Result, SignError> + fn try_sign_generic( + signer: &::Signer, + varsig_header: Self::VarsigHeader, + payload: Self::Payload, + ) -> Result where - T: Clone, - Ipld: Encode, + Ipld: Encode + From, { - let ipld: Ipld = BTreeMap::from_iter([(T::TAG.into(), payload.clone().into())]).into(); + let ipld: Ipld = + BTreeMap::from_iter([(Self::Payload::TAG.into(), payload.clone().into())]).into(); let mut buffer = vec![]; - ipld.encode(*varsig_header.codec(), &mut buffer) + ipld.encode(*varsig::header::Header::codec(&varsig_header), &mut buffer) .map_err(SignError::PayloadEncodingError)?; let signature = signer .try_sign(&buffer) .map_err(SignError::SignatureError)?; - Ok(Envelope { - varsig_header, - signature, - payload, - _phantom: std::marker::PhantomData, - }) + Ok(Self::construct(varsig_header, signature, payload)) } /// Attempt to validate a signature. @@ -149,98 +154,272 @@ impl< /// # Exmaples /// /// FIXME - pub fn validate_signature(&self) -> Result<(), ValidateError> + fn validate_signature(&self) -> Result<(), ValidateError> where - T: Clone, - Ipld: Encode, + Ipld: Encode + From, { let mut encoded = vec![]; - let ipld: Ipld = BTreeMap::from_iter([(T::TAG.into(), self.payload.clone().into())]).into(); - ipld.encode(*self.varsig_header.codec(), &mut encoded) - .map_err(ValidateError::PayloadEncodingError)?; + let ipld: Ipld = + BTreeMap::from_iter([(Self::Payload::TAG.into(), self.payload().clone().into())]) + .into(); + ipld.encode( + *varsig::header::Header::codec(self.varsig_header()), + &mut encoded, + ) + .map_err(ValidateError::PayloadEncodingError)?; self.verifier() - .verify(&encoded, &self.signature) + .verify(&encoded, &self.signature()) .map_err(ValidateError::VerifyError) } - pub fn cid(&self) -> Result + fn cid(&self) -> Result where - Self: Clone, - Ipld: Encode + From, + // Ipld: Encode + From, + Self: Encode, { - let codec = self.varsig_header.codec().clone(); + let codec = varsig::header::Header::codec(self.varsig_header()).clone(); let mut ipld_buffer = vec![]; self.encode(codec, &mut ipld_buffer)?; let multihash = Code::Sha2_256.digest(&ipld_buffer); Ok(Cid::new_v1( - self.varsig_header.codec().clone().into(), + varsig::header::Header::codec(self.varsig_header()) + .clone() + .into(), multihash, )) } } -impl< - T: Verifiable + Capsule, - DID: Did, - V: varsig::Header, - Enc: Codec + Into + TryFrom, - > From> for Ipld -where - Ipld: From, -{ - fn from(envelope: Envelope) -> Self { - let ipld: Ipld = BTreeMap::from_iter([(T::TAG.into(), envelope.payload.into())]).into(); - let varsig_header: Ipld = Ipld::Bytes(envelope.varsig_header.into()); - - Ipld::Map(BTreeMap::from_iter([ - ("sig".into(), Ipld::Bytes(envelope.signature.to_vec())), - ("pld".into(), Ipld::List(vec![varsig_header, ipld])), - ])) - } -} - -impl< - T: TryFrom + Verifiable + Capsule, - DID: Did, - V: varsig::Header, - Enc: Codec + Into + TryFrom, - > TryFrom for Envelope -{ - type Error = FromIpldError; - - fn try_from(ipld: Ipld) -> Result { - if let Ipld::List(list) = ipld { - if let [Ipld::Bytes(sig), Ipld::List(inner)] = list.as_slice() { - if let [Ipld::Bytes(varsig_header), Ipld::Map(btree)] = inner.as_slice() { - if let (1, Some(payload)) = (btree.len(), btree.get(T::TAG.into())) { - Ok(Envelope { - payload: T::try_from(payload.clone()) - .map_err(FromIpldError::CannotParsePayload)?, - - varsig_header: V::try_from(varsig_header.as_slice()) - .map_err(|_| FromIpldError::CannotParseVarsigHeader)?, - - signature: DID::Signature::try_from(sig.as_slice()) - .map_err(|_| FromIpldError::CannotParseSignature)?, - - _phantom: std::marker::PhantomData, - }) - } else { - Err(FromIpldError::InvalidPayloadCapsule) - } - } else { - Err(FromIpldError::InvalidVarsigContainer) - } - } else { - Err(FromIpldError::InvalidSignatureContainer) - } - } else { - Err(FromIpldError::InvalidSignatureContainer) - } - } -} +// /// A container associating a `payload` with its signature over it. +// #[derive(Debug, Clone, PartialEq)] +// pub struct Envelope< +// T: Verifiable + Capsule, +// DID: Did, +// V: varsig::Header, +// C: Codec + TryFrom + Into, +// > { +// /// The [Varsig][crate::crypto::varsig] header. +// pub varsig_header: V, +// +// /// The signture of the `payload`. +// pub signature: DID::Signature, +// +// /// The payload that's being signed over. +// pub payload: T, +// +// _phantom: std::marker::PhantomData, +// } + +// impl< +// T: Verifiable + Capsule, +// DID: Did, +// V: varsig::Header, +// C: Codec + TryFrom + Into, +// > Verifiable for Envelope +// { +// fn verifier(&self) -> &DID { +// &self.payload.verifier() +// } +// } +// +// impl< +// T: Capsule + Verifiable + Into, +// DID: Did, +// V: varsig::Header, +// C: Codec + TryFrom + Into, +// > Envelope +// { +// pub fn new(varsig_header: V, signature: DID::Signature, payload: T) -> Self { +// Envelope { +// varsig_header, +// signature, +// payload, +// _phantom: std::marker::PhantomData, +// } +// } +// +// // FIXME ported +// pub fn varsig_encode(self, w: &mut Vec) -> Result<(), libipld_core::error::Error> +// where +// Ipld: Encode + From, +// { +// let codec = self.varsig_header.codec().clone(); +// let ipld = Ipld::from(self); +// ipld.encode(codec, w) +// } +// +// /// Attempt to sign some payload with a given signer. +// /// +// /// # Arguments +// /// +// /// * `signer` - The signer to use to sign the payload. +// /// * `payload` - The payload to sign. +// /// +// /// # Errors +// /// +// /// * [`SignError`] - the payload can't be encoded or the signature fails. +// // FIXME ported +// pub fn try_sign( +// signer: &DID::Signer, +// varsig_header: V, +// payload: T, +// ) -> Result, SignError> +// where +// T: Clone, +// Ipld: Encode, +// { +// Self::try_sign_generic(signer, varsig_header, payload) +// } +// +// /// Attempt to sign some payload with a given signer and specific codec. +// /// +// /// # Arguments +// /// +// /// * `signer` - The signer to use to sign the payload. +// /// * `codec` - The codec to use to encode the payload. +// /// * `payload` - The payload to sign. +// /// +// /// # Errors +// /// +// /// * [`SignError`] - the payload can't be encoded or the signature fails. +// /// +// /// # Example +// /// +// /// FIXME ported +// pub fn try_sign_generic( +// signer: &DID::Signer, +// varsig_header: V, +// payload: T, +// ) -> Result, SignError> +// where +// T: Clone, +// Ipld: Encode, +// { +// let ipld: Ipld = BTreeMap::from_iter([(T::TAG.into(), payload.clone().into())]).into(); +// +// let mut buffer = vec![]; +// ipld.encode(*varsig_header.codec(), &mut buffer) +// .map_err(SignError::PayloadEncodingError)?; +// +// let signature = signer +// .try_sign(&buffer) +// .map_err(SignError::SignatureError)?; +// +// Ok(Envelope { +// varsig_header, +// signature, +// payload, +// _phantom: std::marker::PhantomData, +// }) +// } +// +// /// Attempt to validate a signature. +// /// +// /// # Arguments +// /// +// /// * `self` - The envelope to validate. +// /// +// /// # Errors +// /// +// /// * [`ValidateError`] - the payload can't be encoded or the signature fails. +// /// +// /// # Exmaples +// /// +// /// FIXME +// pub fn validate_signature(&self) -> Result<(), ValidateError> +// where +// T: Clone, +// Ipld: Encode, +// { +// let mut encoded = vec![]; +// let ipld: Ipld = BTreeMap::from_iter([(T::TAG.into(), self.payload.clone().into())]).into(); +// ipld.encode(*self.varsig_header.codec(), &mut encoded) +// .map_err(ValidateError::PayloadEncodingError)?; +// +// self.verifier() +// .verify(&encoded, &self.signature) +// .map_err(ValidateError::VerifyError) +// } +// +// pub fn cid(&self) -> Result +// where +// Self: Clone, +// Ipld: Encode + From, +// { +// let codec = self.varsig_header.codec().clone(); +// let mut ipld_buffer = vec![]; +// self.encode(codec, &mut ipld_buffer)?; +// +// let multihash = Code::Sha2_256.digest(&ipld_buffer); +// Ok(Cid::new_v1( +// self.varsig_header.codec().clone().into(), +// multihash, +// )) +// } +// } + +// impl< +// T: Verifiable + Capsule, +// DID: Did, +// V: varsig::Header, +// C: Codec + Into + TryFrom, +// > From> for Ipld +// where +// Ipld: From, +// { +// fn from(envelope: Envelope) -> Self { +// let ipld: Ipld = BTreeMap::from_iter([(T::TAG.into(), envelope.payload.into())]).into(); +// let varsig_header: Ipld = Ipld::Bytes(envelope.varsig_header.into()); +// +// Ipld::Map(BTreeMap::from_iter([ +// ("sig".into(), Ipld::Bytes(envelope.signature.to_vec())), +// ("pld".into(), Ipld::List(vec![varsig_header, ipld])), +// ])) +// } +// } +// +// impl< +// T: TryFrom + Verifiable + Capsule, +// DID: Did, +// V: varsig::Header, +// C: Codec + Into + TryFrom, +// > TryFrom for Envelope +// { +// type Error = FromIpldError; +// +// fn try_from(ipld: Ipld) -> Result { +// if let Ipld::List(list) = ipld { +// if let [Ipld::Bytes(sig), Ipld::List(inner)] = list.as_slice() { +// if let [Ipld::Bytes(varsig_header), Ipld::Map(btree)] = inner.as_slice() { +// if let (1, Some(payload)) = (btree.len(), btree.get(T::TAG.into())) { +// Ok(Envelope { +// payload: T::try_from(payload.clone()) +// .map_err(FromIpldError::CannotParsePayload)?, +// +// varsig_header: V::try_from(varsig_header.as_slice()) +// .map_err(|_| FromIpldError::CannotParseVarsigHeader)?, +// +// signature: DID::Signature::try_from(sig.as_slice()) +// .map_err(|_| FromIpldError::CannotParseSignature)?, +// +// _phantom: std::marker::PhantomData, +// }) +// } else { +// Err(FromIpldError::InvalidPayloadCapsule) +// } +// } else { +// Err(FromIpldError::InvalidVarsigContainer) +// } +// } else { +// Err(FromIpldError::InvalidSignatureContainer) +// } +// } else { +// Err(FromIpldError::InvalidSignatureContainer) +// } +// } +// } #[derive(Debug, Clone, PartialEq, Error)] pub enum FromIpldError { @@ -262,26 +441,26 @@ pub enum FromIpldError { #[error("Invalid payload capsule")] InvalidPayloadCapsule, } - -impl< - T: Verifiable + Capsule, - DID: Did, - V: varsig::Header, - Enc: Codec + Into + TryFrom, - > Encode for Envelope -where - Self: Clone, - Ipld: Encode + From, -{ - fn encode( - &self, - codec: Enc, - w: &mut W, - ) -> Result<(), libipld_core::error::Error> { - let ipld: Ipld = self.clone().into(); - ipld.encode(codec, w) - } -} +// +// impl< +// T: Verifiable + Capsule, +// DID: Did, +// V: varsig::Header, +// C: Codec + Into + TryFrom, +// > Encode for Envelope +// where +// Self: Clone, +// Ipld: Encode + From, +// { +// fn encode( +// &self, +// codec: C, +// w: &mut W, +// ) -> Result<(), libipld_core::error::Error> { +// let ipld: Ipld = self.clone().into(); +// ipld.encode(codec, w) +// } +// } /// Errors that can occur when signing a [`siganture::Envelope`][Envelope]. #[derive(Debug, Error)] @@ -307,37 +486,37 @@ pub enum ValidateError { VerifyError(#[from] signature::Error), } -impl< - T: Verifiable + Capsule, - DID: Did, - V: varsig::Header, - Enc: Codec + TryFrom + Into, - > Serialize for Envelope -{ - fn serialize(&self, serializer: S) -> std::prelude::v1::Result - where - S: Serializer, - { - self.clone().serialize(serializer) - } -} - -impl< - 'de, - T: Verifiable + Capsule + TryFrom, - DID: Did, - V: varsig::Header, - Enc: Codec + TryFrom + Into, - > Deserialize<'de> for Envelope -where - Envelope: TryFrom, - as TryFrom>::Error: std::fmt::Display, -{ - fn deserialize(deserializer: D) -> std::prelude::v1::Result - where - D: Deserializer<'de>, - { - let ipld: Ipld = Deserialize::deserialize(deserializer)?; - Ok(Envelope::try_from(ipld).map_err(serde::de::Error::custom)?) - } -} +// impl< +// T: Verifiable + Capsule, +// DID: Did, +// V: varsig::Header, +// C: Codec + TryFrom + Into, +// > Serialize for Envelope +// { +// fn serialize(&self, serializer: S) -> std::prelude::v1::Result +// where +// S: Serializer, +// { +// self.clone().serialize(serializer) +// } +// } +// +// impl< +// 'de, +// T: Verifiable + Capsule + TryFrom, +// DID: Did, +// V: varsig::Header, +// C: Codec + TryFrom + Into, +// > Deserialize<'de> for Envelope +// where +// Envelope: TryFrom, +// as TryFrom>::Error: std::fmt::Display, +// { +// fn deserialize(deserializer: D) -> std::prelude::v1::Result +// where +// D: Deserializer<'de>, +// { +// let ipld: Ipld = Deserialize::deserialize(deserializer)?; +// Ok(Envelope::try_from(ipld).map_err(serde::de::Error::custom)?) +// } +// } diff --git a/src/delegation.rs b/src/delegation.rs index 992341c6..3d43b6d8 100644 --- a/src/delegation.rs +++ b/src/delegation.rs @@ -23,15 +23,12 @@ pub use payload::*; use crate::{ capsule::Capsule, - crypto::{signature, varsig, Nonce}, + crypto::{signature::Envelope, varsig, Nonce}, did::{self, Did}, time::{TimeBoundError, Timestamp}, }; -use libipld_core::{ - cid::Cid, - codec::{Codec, Encode}, - ipld::Ipld, -}; +use libipld_core::link::Link; +use libipld_core::{codec::Codec, ipld::Ipld}; use policy::Predicate; use serde::{Deserialize, Serialize}; use std::collections::BTreeMap; @@ -45,15 +42,21 @@ pub struct Delegation< DID: Did = did::preset::Verifier, V: varsig::Header = varsig::header::Preset, C: Codec + TryFrom + Into = varsig::encoding::Preset, ->(signature::Envelope, DID, V, C>); +> { + pub varsig_header: V, + pub payload: Payload, + pub signature: DID::Signature, + _marker: std::marker::PhantomData, +} -// FIXME rename proofs? #[derive(Clone, Debug, PartialEq)] pub struct Proof< DID: Did = did::preset::Verifier, V: varsig::Header = varsig::header::Preset, C: Codec + TryFrom + Into = varsig::encoding::Preset, ->(Vec>); +> { + pub prf: Vec>>, +} impl, C: Codec + TryFrom + Into> Capsule for Proof @@ -67,118 +70,114 @@ impl, C: Codec + Into + TryFrom> Delega signature: DID::Signature, payload: Payload, ) -> Delegation { - Delegation(signature::Envelope::new(varsig_header, signature, payload)) + Delegation { + varsig_header, + payload, + signature, + _marker: std::marker::PhantomData, + } } /// Retrive the `issuer` of a [`Delegation`] pub fn issuer(&self) -> &DID { - &self.0.payload.issuer + &self.payload.issuer } /// Retrive the `subject` of a [`Delegation`] pub fn subject(&self) -> &Option { - &self.0.payload.subject + &self.payload.subject } /// Retrive the `audience` of a [`Delegation`] pub fn audience(&self) -> &DID { - &self.0.payload.audience + &self.payload.audience } /// Retrive the `policy` of a [`Delegation`] pub fn policy(&self) -> &Vec { - &self.0.payload.policy + &self.payload.policy } /// Retrive the `metadata` of a [`Delegation`] pub fn metadata(&self) -> &BTreeMap { - &self.0.payload.metadata + &self.payload.metadata } /// Retrive the `nonce` of a [`Delegation`] pub fn nonce(&self) -> &Nonce { - &self.0.payload.nonce + &self.payload.nonce } /// Retrive the `not_before` of a [`Delegation`] pub fn not_before(&self) -> Option<&Timestamp> { - self.0.payload.not_before.as_ref() + self.payload.not_before.as_ref() } /// Retrive the `expiration` of a [`Delegation`] pub fn expiration(&self) -> &Timestamp { - &self.0.payload.expiration + &self.payload.expiration } pub fn check_time(&self, now: SystemTime) -> Result<(), TimeBoundError> { - self.0.payload.check_time(now) - } - - pub fn payload(&self) -> &Payload { - &self.0.payload - } - - pub fn varsig_header(&self) -> &V { - &self.0.varsig_header + self.payload.check_time(now) } +} - pub fn varsig_encode(self, w: &mut Vec) -> Result<(), libipld_core::error::Error> - where - Ipld: Encode, - { - self.0.varsig_encode(w) - } +impl + Clone, C: Codec + TryFrom + Into> Envelope + for Delegation +where + Payload: TryFrom, +{ + type DID = DID; + type Payload = Payload; + type VarsigHeader = V; + type Encoder = C; - pub fn signature(&self) -> &DID::Signature { - &self.0.signature + fn construct( + varsig_header: V, + signature: DID::Signature, + payload: Payload, + ) -> Delegation { + Delegation { + varsig_header, + payload, + signature, + _marker: std::marker::PhantomData, + } } - pub fn codec(&self) -> &C { - self.varsig_header().codec() + fn varsig_header(&self) -> &V { + &self.varsig_header } - pub fn cid(&self) -> Result - where - signature::Envelope, DID, V, C>: Clone + Encode, - Ipld: Encode, - { - self.0.cid() + fn payload(&self) -> &Payload { + &self.payload } - pub fn validate_signature(&self) -> Result<(), signature::ValidateError> - where - Payload: Clone, - Ipld: Encode, - { - self.0.validate_signature() + fn signature(&self) -> &DID::Signature { + &self.signature } - pub fn try_sign( - signer: &DID::Signer, - varsig_header: V, - payload: Payload, - ) -> Result - where - Ipld: Encode, - Payload: Clone, - { - signature::Envelope::try_sign(signer, varsig_header, payload).map(Delegation) + fn verifier(&self) -> &DID { + &self.payload.issuer } } -impl, C: Codec + TryFrom + Into> Serialize +impl + Clone, C: Codec + TryFrom + Into> Serialize for Delegation +where + Payload: TryFrom, { fn serialize(&self, serializer: S) -> Result where S: serde::Serializer, { - self.0.serialize(serializer) + self.to_ipld_envelope().serialize(serializer) } } -impl<'de, DID: Did, V: varsig::Header, C: Codec + TryFrom + Into> Deserialize<'de> - for Delegation +impl<'de, DID: Did + Clone, V: varsig::Header + Clone, C: Codec + TryFrom + Into> + Deserialize<'de> for Delegation where Payload: TryFrom, as TryFrom>::Error: std::fmt::Display, @@ -187,6 +186,7 @@ where where D: serde::Deserializer<'de>, { - signature::Envelope::deserialize(deserializer).map(Delegation) + let ipld = Ipld::deserialize(deserializer)?; + Self::try_from_ipld_envelope(ipld).map_err(serde::de::Error::custom) } } diff --git a/src/delegation/agent.rs b/src/delegation/agent.rs index e7dc133b..396a650c 100644 --- a/src/delegation/agent.rs +++ b/src/delegation/agent.rs @@ -1,6 +1,6 @@ use super::{payload::Payload, policy::Predicate, store::Store, Delegation}; use crate::{ - crypto::{varsig, Nonce}, + crypto::{signature::Envelope, varsig, Nonce}, did::Did, time::Timestamp, }; @@ -38,7 +38,7 @@ impl< 'a, DID: Did + ToString + Clone, S: Store + Clone, - V: varsig::Header, + V: varsig::Header + Clone, Enc: Codec + TryFrom + Into, > Agent<'a, DID, S, V, Enc> where @@ -64,7 +64,10 @@ where not_before: Option, now: SystemTime, varsig_header: V, - ) -> Result, DelegateError> { + ) -> Result, DelegateError> + where + Payload: TryFrom, + { let mut salt = self.did.clone().to_string().into_bytes(); let nonce = Nonce::generate_12(&mut salt); @@ -119,7 +122,10 @@ where &mut self, cid: Cid, // FIXME remove and generate from the capsule header? delegation: Delegation, - ) -> Result<(), ReceiveError> { + ) -> Result<(), ReceiveError> + where + Payload: TryFrom, + { if self.store.get(&cid).is_ok() { return Ok(()); } diff --git a/src/invocation.rs b/src/invocation.rs index 4c9e9f8a..823ca793 100644 --- a/src/invocation.rs +++ b/src/invocation.rs @@ -22,8 +22,7 @@ pub use agent::Agent; pub use payload::*; use crate::{ - ability, - crypto::{signature, varsig}, + crypto::{signature::Envelope, varsig}, did::{self, Did}, time::{Expired, Timestamp}, }; @@ -53,108 +52,68 @@ pub struct Invocation< DID: did::Did = did::preset::Verifier, V: varsig::Header = varsig::header::Preset, C: Codec + TryFrom + Into = varsig::encoding::Preset, ->(pub signature::Envelope, DID, V, C>); +> { + pub varsig_header: V, + pub payload: Payload, + pub signature: DID::Signature, + _marker: std::marker::PhantomData, +} -impl, C: Codec + TryFrom + Into> +impl, C: Codec + TryFrom + Into> Invocation where Ipld: Encode, { - pub fn new(payload: Payload, varsig_header: V, signature: DID::Signature) -> Self { - Invocation(signature::Envelope::new(varsig_header, signature, payload)) - } - - pub fn varsig_encode(self, w: &mut Vec) -> Result<(), libipld_core::error::Error> - where - Ipld: Encode, - { - self.0.varsig_encode(w) - } - - pub fn payload(&self) -> &Payload { - &self.0.payload - } - - pub fn varsig_header(&self) -> &V { - &self.0.varsig_header - } - - pub fn signature(&self) -> &DID::Signature { - &self.0.signature + pub fn new(varsig_header: V, signature: DID::Signature, payload: Payload) -> Self { + Invocation { + varsig_header, + payload, + signature, + _marker: std::marker::PhantomData, + } } pub fn audience(&self) -> &Option { - &self.0.payload.audience + &self.payload.audience } pub fn issuer(&self) -> &DID { - &self.0.payload.issuer + &self.payload.issuer } pub fn subject(&self) -> &DID { - &self.0.payload.subject + &self.payload.subject } pub fn ability(&self) -> &A { - &self.0.payload.ability + &self.payload.ability } - pub fn map_ability(self, f: F) -> Invocation + pub fn map_ability(self, f: F) -> Invocation where F: FnOnce(A) -> Z, { - Invocation(signature::Envelope::new( - self.0.varsig_header, - self.0.signature, - self.0.payload.map_ability(f), - )) + Invocation::new( + self.varsig_header, + self.signature, + self.payload.map_ability(f), + ) } pub fn proofs(&self) -> &Vec { - &self.payload().proofs + &self.payload.proofs } pub fn issued_at(&self) -> &Option { - &self.payload().issued_at + &self.payload.issued_at } pub fn expiration(&self) -> &Option { - &self.payload().expiration + &self.payload.expiration } pub fn check_time(&self, now: SystemTime) -> Result<(), Expired> { - self.payload().check_time(now) - } - - pub fn codec(&self) -> &C { - self.varsig_header().codec() - } - - pub fn cid(&self) -> Result - where - signature::Envelope, DID, V, C>: Clone, - Ipld: Encode, - { - self.0.cid() - } - - pub fn try_sign( - signer: &DID::Signer, - varsig_header: V, - payload: Payload, - ) -> Result, signature::SignError> - where - Payload: Clone, - { - let envelope = signature::Envelope::try_sign(signer, varsig_header, payload)?; - Ok(Invocation(envelope)) - } - - pub fn validate_signature(&self) -> Result<(), signature::ValidateError> - where - Payload: Clone, - { - self.0.validate_signature() + self.payload.check_time(now) } } @@ -162,43 +121,92 @@ impl, C: Codec + TryFrom + Into> did for Invocation { fn verifier(&self) -> &DID { - &self.0.verifier() + &self.verifier() } } -impl, C: Codec + TryFrom + Into> - From> for Ipld +impl< + A: Clone, + DID: Did + Clone, + V: varsig::Header + Clone, + C: Codec + TryFrom + Into, + > From> for Ipld +where + Payload: TryFrom, { fn from(invocation: Invocation) -> Self { - invocation.0.into() + invocation.to_ipld_envelope() } } -impl, C: Codec + TryFrom + Into> TryFrom - for Invocation +impl< + A: Clone, + DID: Did + Clone, + V: varsig::Header + Clone, + C: Codec + TryFrom + Into, + > Envelope for Invocation where Payload: TryFrom, { - type Error = , DID, V, C> as TryFrom>::Error; + type DID = DID; + type Payload = Payload; + type VarsigHeader = V; + type Encoder = C; + + fn construct( + varsig_header: V, + signature: DID::Signature, + payload: Payload, + ) -> Invocation { + Invocation { + varsig_header, + payload, + signature, + _marker: std::marker::PhantomData, + } + } - fn try_from(ipld: Ipld) -> Result { - signature::Envelope::try_from(ipld).map(Invocation) + fn varsig_header(&self) -> &V { + &self.varsig_header + } + + fn payload(&self) -> &Payload { + &self.payload + } + + fn signature(&self) -> &DID::Signature { + &self.signature + } + + fn verifier(&self) -> &DID { + &self.payload.issuer } } -impl, C: Codec + TryFrom + Into> Serialize - for Invocation +impl< + A: Clone, + DID: Did + Clone, + V: varsig::Header + Clone, + C: Codec + TryFrom + Into, + > Serialize for Invocation +where + Payload: TryFrom, { fn serialize(&self, serializer: S) -> Result where S: serde::Serializer, { - self.0.serialize(serializer) + self.to_ipld_envelope().serialize(serializer) } } -impl<'de, A, DID: Did, V: varsig::Header, C: Codec + TryFrom + Into> Deserialize<'de> - for Invocation +impl< + 'de, + A: Clone, + DID: Did + Clone, + V: varsig::Header + Clone, + C: Codec + TryFrom + Into, + > Deserialize<'de> for Invocation where Payload: TryFrom, as TryFrom>::Error: std::fmt::Display, @@ -207,6 +215,7 @@ where where D: serde::Deserializer<'de>, { - signature::Envelope::deserialize(deserializer).map(Invocation) + let ipld = Ipld::deserialize(deserializer)?; + Self::try_from_ipld_envelope(ipld).map_err(serde::de::Error::custom) } } diff --git a/src/invocation/agent.rs b/src/invocation/agent.rs index 71bfa770..1ab29ac0 100644 --- a/src/invocation/agent.rs +++ b/src/invocation/agent.rs @@ -6,7 +6,10 @@ use super::{ }; use crate::{ ability::{arguments, parse::ParseAbilityError, ucan::revoke::Revoke}, - crypto::{signature, varsig, Nonce}, + crypto::{ + signature::{self, Envelope}, + varsig, Nonce, + }, delegation, did::Did, invocation::promise, @@ -30,11 +33,11 @@ pub struct Agent< 'a, T: Resolvable, DID: Did, - S: Store, + S: Store, P: promise::Store, - D: delegation::store::Store, - V: varsig::Header, - Enc: Codec + Into + TryFrom, + D: delegation::store::Store, + V: varsig::Header + Clone, + C: Codec + Into + TryFrom, > { /// The agent's [`DID`]. pub did: &'a DID, @@ -49,21 +52,21 @@ pub struct Agent< pub unresolved_promise_index: &'a mut P, signer: &'a ::Signer, - marker: PhantomData<(T, V, Enc)>, + marker: PhantomData<(T, V, C)>, } -impl<'a, T, DID, S, P, D, V, Enc> Agent<'a, T, DID, S, P, D, V, Enc> +impl<'a, T, DID, S, P, D, V, C> Agent<'a, T, DID, S, P, D, V, C> where T::Promised: Clone, - Ipld: Encode, + Ipld: Encode, delegation::Payload: Clone, T: Resolvable + Clone, DID: Did + Clone, - S: Store, + S: Store, P: promise::Store, - D: delegation::store::Store, - V: varsig::Header, - Enc: Codec + Into + TryFrom, + D: delegation::store::Store, + V: varsig::Header + Clone, + C: Codec + Into + TryFrom, { pub fn new( did: &'a DID, @@ -94,12 +97,15 @@ where now: SystemTime, varsig_header: V, ) -> Result< - Invocation, + Invocation, InvokeError< D::DelegationStoreError, ParseAbilityError<()>, // FIXME argserror >, - > { + > + where + Payload: TryFrom, + { let proofs = self .delegation_store .get_chain(self.did, &Some(subject.clone()), vec![], now) @@ -138,12 +144,15 @@ where now: SystemTime, varsig_header: V, ) -> Result< - Invocation, + Invocation, InvokeError< D::DelegationStoreError, ParseAbilityError<()>, // FIXME errs >, - > { + > + where + Payload: TryFrom, + { let proofs = self .delegation_store .get_chain(self.did, &Some(subject.clone()), vec![], now) @@ -172,19 +181,15 @@ where pub fn receive( &mut self, - promised: Invocation, + promised: Invocation, now: &SystemTime, - ) -> Result< - Recipient>, - ReceiveError, - > + ) -> Result>, ReceiveError> where - Enc: From + Into, + Payload: TryFrom, arguments::Named: From, - Invocation: Clone, + Invocation: Clone + Encode,

>::PromiseStoreError: fmt::Debug, - signature::Envelope, DID, V, Enc>: Clone, - ::Promised, DID, V, Enc>>::InvocationStoreError: fmt::Debug, + ::Promised, DID, V, C>>::InvocationStoreError: fmt::Debug, { let cid: Cid = promised.cid().map_err(ReceiveError::EncodingError)?; let _ = promised @@ -211,15 +216,17 @@ where } }; - let proof_payloads = self + let proof_payloads: Vec<&delegation::Payload> = self .delegation_store .get_many(&promised.proofs()) .map_err(ReceiveError::DelegationStoreError)? - .into_iter() - .map(|d| d.payload()) - .collect(); + .iter() + .fold(vec![], |mut acc, d| { + acc.push(&d.payload); + acc + }); - let resolved_payload = promised.payload().clone().map_ability(|_| resolved_ability); + let resolved_payload = promised.payload.clone().map_ability(|_| resolved_ability); let _ = &resolved_payload .check(proof_payloads, now) @@ -240,9 +247,10 @@ where now: Timestamp, varsig_header: V, // FIXME return type - ) -> Result, ()> + ) -> Result, ()> where T: From, + Payload: TryFrom, { let ability: T = Revoke { ucan: cid.clone() }.into(); let proofs = if &subject == self.did { @@ -278,7 +286,7 @@ where #[derive(Debug)] pub enum Recipient { - // FIXME change to status + // FIXME change to status? You(T), Other(T), Unresolved(Cid), @@ -290,12 +298,12 @@ pub enum ReceiveError< P: promise::Store, DID: Did, D, - S: Store, - V: varsig::Header, - Enc: Codec + From + Into, + S: Store, + V: varsig::Header, + C: Codec + TryFrom + Into, > where

>::PromiseStoreError: fmt::Debug, - ::Promised, DID, V, Enc>>::InvocationStoreError: fmt::Debug, + ::Promised, DID, V, C>>::InvocationStoreError: fmt::Debug, { #[error("encoding error: {0}")] EncodingError(#[from] libipld_core::error::Error), @@ -305,7 +313,7 @@ pub enum ReceiveError< #[error("invocation store error: {0}")] InvocationStoreError( - #[source] ::Promised, DID, V, Enc>>::InvocationStoreError, + #[source] ::Promised, DID, V, C>>::InvocationStoreError, ), #[error("promise store error: {0}")] diff --git a/src/lib.rs b/src/lib.rs index 571310bb..1c21c44a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -21,7 +21,6 @@ pub mod delegation; pub mod did; pub mod invocation; pub mod ipld; -pub mod proof; pub mod reader; pub mod receipt; pub mod task; diff --git a/src/proof.rs b/src/proof.rs deleted file mode 100644 index 126ff504..00000000 --- a/src/proof.rs +++ /dev/null @@ -1,38 +0,0 @@ -use crate::{crypto::varsig, delegation::Delegation, did::Did}; -use libipld_core::{cid::Cid, codec::Codec, link::Link}; -use serde::{Deserialize, Deserializer, Serialize}; - -#[derive(Debug, Clone, PartialEq)] -pub struct Proof, Enc: Codec + TryFrom + Into> { - pub prf: Vec>>, -} - -impl, Enc: Codec + TryFrom + Into> Serialize - for Proof -{ - fn serialize(&self, serializer: S) -> Result { - let chain = self - .prf - .iter() - .map(|link| link.to_string()) - .collect::>(); - - chain.serialize(serializer) - } -} - -impl<'de, DID: Did, V: varsig::Header, Enc: Codec + TryFrom + Into> Deserialize<'de> - for Proof -{ - fn deserialize>(deserializer: D) -> Result { - let prf = Vec::::deserialize(deserializer)? - .into_iter() - .map(|s| { - let cid: Cid = s.try_into().map_err(serde::de::Error::custom)?; - Ok(cid.into()) - }) - .collect::, _>>()?; - - Ok(Proof { prf }) - } -} diff --git a/src/receipt.rs b/src/receipt.rs index 5ec60a84..087bd013 100644 --- a/src/receipt.rs +++ b/src/receipt.rs @@ -15,93 +15,117 @@ pub use responds::Responds; pub use store::Store; use crate::{ - ability, - crypto::{signature, varsig}, + crypto::{signature::Envelope, varsig}, did::{self, Did}, }; -use libipld_core::{ - codec::{Codec, Encode}, - ipld::Ipld, -}; +use libipld_core::{codec::Codec, ipld::Ipld}; use serde::{Deserialize, Serialize}; /// The complete, signed receipt of an [`Invocation`][`crate::invocation::Invocation`]. #[derive(Clone, Debug, PartialEq)] -pub struct Receipt, C: Codec + Into + TryFrom>( - pub signature::Envelope, DID, V, C>, -); - -/// An alias for the [`Receipt`] type with the library preset -/// [`Did`](crate::did)s and [Abilities](crate::ability). -pub type Preset = Receipt< - ability::preset::Preset, - did::preset::Verifier, - varsig::header::Preset, - varsig::encoding::Preset, ->; - -impl, Enc: Codec + Into + TryFrom> - Receipt -{ - /// Returns the [`Payload`] of the [`Receipt`]. - pub fn payload(&self) -> &Payload { - &self.0.payload - } +pub struct Receipt< + T: Responds, + DID: Did = did::preset::Verifier, + V: varsig::Header = varsig::header::Preset, + C: Codec + Into + TryFrom = varsig::encoding::Preset, +> { + pub varsig_header: V, + pub signature: DID::Signature, + pub payload: Payload, - /// Returns the [`signature::Envelope`] of the [`Receipt`]. - pub fn signature(&self) -> &DID::Signature { - &self.0.signature - } - - pub fn varsig_encode(self, w: &mut Vec) -> Result<(), libipld_core::error::Error> - where - Ipld: Encode, - { - self.0.varsig_encode(w) - } + _marker: std::marker::PhantomData, } -impl, Enc: Codec + TryFrom + Into> - did::Verifiable for Receipt +impl, C: Codec + TryFrom + Into> + did::Verifiable for Receipt { fn verifier(&self) -> &DID { - &self.0.verifier() + &self.verifier() } } -impl, Enc: Codec + TryFrom + Into> - From> for Ipld +impl< + T: Responds + Clone, + DID: Did + Clone, + V: varsig::Header + Clone, + C: Codec + TryFrom + Into, + > From> for Ipld +where + Payload: TryFrom, { - fn from(invocation: Receipt) -> Self { - invocation.0.into() + fn from(rec: Receipt) -> Self { + rec.to_ipld_envelope() } } -impl, Enc: Codec + TryFrom + Into> - TryFrom for Receipt +impl< + T: Responds + Clone, + DID: Did + Clone, + V: varsig::Header + Clone, + C: Codec + TryFrom + Into, + > Envelope for Receipt where Payload: TryFrom, { - type Error = , DID, V, Enc> as TryFrom>::Error; + type DID = DID; + type Payload = Payload; + type VarsigHeader = V; + type Encoder = C; - fn try_from(ipld: Ipld) -> Result { - signature::Envelope::try_from(ipld).map(Receipt) + fn construct( + varsig_header: V, + signature: DID::Signature, + payload: Payload, + ) -> Receipt { + Receipt { + varsig_header, + payload, + signature, + _marker: std::marker::PhantomData, + } + } + + fn varsig_header(&self) -> &V { + &self.varsig_header + } + + fn payload(&self) -> &Payload { + &self.payload + } + + fn signature(&self) -> &DID::Signature { + &self.signature + } + + fn verifier(&self) -> &DID { + &self.payload.issuer } } -impl, Enc: Codec + TryFrom + Into> Serialize - for Receipt +impl< + T: Responds + Clone, + DID: Did + Clone, + V: varsig::Header + Clone, + C: Codec + TryFrom + Into, + > Serialize for Receipt +where + Payload: TryFrom, { fn serialize(&self, serializer: S) -> Result where S: serde::Serializer, { - self.0.serialize(serializer) + self.to_ipld_envelope().serialize(serializer) } } -impl<'de, T: Responds, DID: Did, V: varsig::Header, Enc: Codec + TryFrom + Into> - Deserialize<'de> for Receipt +impl< + 'de, + T: Responds + Clone, + DID: Did + Clone, + V: varsig::Header + Clone, + C: Codec + TryFrom + Into, + > Deserialize<'de> for Receipt where Payload: TryFrom, as TryFrom>::Error: std::fmt::Display, @@ -110,6 +134,7 @@ where where D: serde::Deserializer<'de>, { - signature::Envelope::deserialize(deserializer).map(Receipt) + let ipld = Ipld::deserialize(deserializer)?; + Self::try_from_ipld_envelope(ipld).map_err(serde::de::Error::custom) } } From d7a7e11956d5ab4a168cc0df799b77d97cbf8990 Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Wed, 6 Mar 2024 20:57:23 -0800 Subject: [PATCH 121/188] add default for in memory store --- src/delegation/store/memory.rs | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/src/delegation/store/memory.rs b/src/delegation/store/memory.rs index c13cc828..0cafeea9 100644 --- a/src/delegation/store/memory.rs +++ b/src/delegation/store/memory.rs @@ -69,16 +69,24 @@ use web_time::SystemTime; /// linkStyle 1 stroke:orange; /// ``` #[derive(Debug, Clone, PartialEq)] -pub struct MemoryStore< - DID: Did + Ord, - V: varsig::Header, - Enc: Codec + TryFrom + Into, -> { - ucans: BTreeMap>, +pub struct MemoryStore, C: Codec + TryFrom + Into> { + ucans: BTreeMap>, index: BTreeMap, BTreeMap>>, revocations: BTreeSet, } +impl, C: Codec + TryFrom + Into> Default + for MemoryStore +{ + fn default() -> Self { + MemoryStore { + ucans: BTreeMap::new(), + index: BTreeMap::new(), + revocations: BTreeSet::new(), + } + } +} + // FIXME check that UCAN is valid impl, Enc: Codec + TryFrom + Into> Store for MemoryStore From a5180cd9f47bfbe841d3c9f39bda02b2ebb563f7 Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Wed, 6 Mar 2024 21:19:26 -0800 Subject: [PATCH 122/188] Helpers for integrating to server --- src/delegation/payload.rs | 17 +++++++++++++++++ src/time/timestamp.rs | 12 +++++++++++- 2 files changed, 28 insertions(+), 1 deletion(-) diff --git a/src/delegation/payload.rs b/src/delegation/payload.rs index 5408bdff..7a544e26 100644 --- a/src/delegation/payload.rs +++ b/src/delegation/payload.rs @@ -74,6 +74,23 @@ pub struct Payload { } impl Payload { + pub fn powerbox(issuer: DID, audience: DID, command: String, expiration: Timestamp) -> Self { + let mut seed = vec![]; + let nonce = Nonce::generate_12(seed.as_mut()); + + Payload { + issuer, + subject: None, + audience, + command, + policy: vec![], + metadata: BTreeMap::new(), + nonce, + expiration, + not_before: None, + } + } + pub fn check_time(&self, now: SystemTime) -> Result<(), TimeBoundError> { let ts_now = &Timestamp::postel(now); diff --git a/src/time/timestamp.rs b/src/time/timestamp.rs index a43eb858..8b4cfea7 100644 --- a/src/time/timestamp.rs +++ b/src/time/timestamp.rs @@ -39,7 +39,12 @@ impl Timestamp { /// /// * [`OutOfRangeError`] — If the time is more than 2⁵³ seconds since the Unix epoch pub fn new(time: SystemTime) -> Result { - if time.duration_since(UNIX_EPOCH).map_err(|_| OutOfRangeError{ tried: time })?.as_secs() > 0x1FFFFFFFFFFFFF { + if time + .duration_since(UNIX_EPOCH) + .map_err(|_| OutOfRangeError { tried: time })? + .as_secs() + > 0x1FFFFFFFFFFFFF + { Err(OutOfRangeError { tried: time }) } else { Ok(Timestamp { time }) @@ -52,6 +57,11 @@ impl Timestamp { .expect("the current time to be somtime in the 3rd millenium CE") } + pub fn five_years_from_now() -> Timestamp { + Self::new(SystemTime::now() + Duration::from_secs(5 * 365 * 24 * 60 * 60)) + .expect("the current time to be somtime in the 3rd millenium CE") + } + /// Convert a [`Timestamp`] to a [Unix timestamp]. /// /// [Unix timestamp]: https://en.wikipedia.org/wiki/Unix_time From 656b52918eaba22a72fc32b8c306f7f1363cf227 Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Wed, 6 Mar 2024 22:06:44 -0800 Subject: [PATCH 123/188] u64 because libpld --- src/crypto/signature/envelope.rs | 286 +---------------------------- src/crypto/varsig/header/traits.rs | 2 +- src/did/key/signer.rs | 8 +- 3 files changed, 6 insertions(+), 290 deletions(-) diff --git a/src/crypto/signature/envelope.rs b/src/crypto/signature/envelope.rs index a8b358fc..13d2b7a4 100644 --- a/src/crypto/signature/envelope.rs +++ b/src/crypto/signature/envelope.rs @@ -15,7 +15,7 @@ pub trait Envelope: Sized { type DID: Did; type Payload: Clone + Capsule + TryFrom + Into; type VarsigHeader: varsig::Header + Clone; - type Encoder: Codec + TryFrom + Into; + type Encoder: Codec + TryFrom + Into; fn varsig_header(&self) -> &Self::VarsigHeader; fn signature(&self) -> &::Signature; @@ -192,235 +192,6 @@ pub trait Envelope: Sized { } } -// /// A container associating a `payload` with its signature over it. -// #[derive(Debug, Clone, PartialEq)] -// pub struct Envelope< -// T: Verifiable + Capsule, -// DID: Did, -// V: varsig::Header, -// C: Codec + TryFrom + Into, -// > { -// /// The [Varsig][crate::crypto::varsig] header. -// pub varsig_header: V, -// -// /// The signture of the `payload`. -// pub signature: DID::Signature, -// -// /// The payload that's being signed over. -// pub payload: T, -// -// _phantom: std::marker::PhantomData, -// } - -// impl< -// T: Verifiable + Capsule, -// DID: Did, -// V: varsig::Header, -// C: Codec + TryFrom + Into, -// > Verifiable for Envelope -// { -// fn verifier(&self) -> &DID { -// &self.payload.verifier() -// } -// } -// -// impl< -// T: Capsule + Verifiable + Into, -// DID: Did, -// V: varsig::Header, -// C: Codec + TryFrom + Into, -// > Envelope -// { -// pub fn new(varsig_header: V, signature: DID::Signature, payload: T) -> Self { -// Envelope { -// varsig_header, -// signature, -// payload, -// _phantom: std::marker::PhantomData, -// } -// } -// -// // FIXME ported -// pub fn varsig_encode(self, w: &mut Vec) -> Result<(), libipld_core::error::Error> -// where -// Ipld: Encode + From, -// { -// let codec = self.varsig_header.codec().clone(); -// let ipld = Ipld::from(self); -// ipld.encode(codec, w) -// } -// -// /// Attempt to sign some payload with a given signer. -// /// -// /// # Arguments -// /// -// /// * `signer` - The signer to use to sign the payload. -// /// * `payload` - The payload to sign. -// /// -// /// # Errors -// /// -// /// * [`SignError`] - the payload can't be encoded or the signature fails. -// // FIXME ported -// pub fn try_sign( -// signer: &DID::Signer, -// varsig_header: V, -// payload: T, -// ) -> Result, SignError> -// where -// T: Clone, -// Ipld: Encode, -// { -// Self::try_sign_generic(signer, varsig_header, payload) -// } -// -// /// Attempt to sign some payload with a given signer and specific codec. -// /// -// /// # Arguments -// /// -// /// * `signer` - The signer to use to sign the payload. -// /// * `codec` - The codec to use to encode the payload. -// /// * `payload` - The payload to sign. -// /// -// /// # Errors -// /// -// /// * [`SignError`] - the payload can't be encoded or the signature fails. -// /// -// /// # Example -// /// -// /// FIXME ported -// pub fn try_sign_generic( -// signer: &DID::Signer, -// varsig_header: V, -// payload: T, -// ) -> Result, SignError> -// where -// T: Clone, -// Ipld: Encode, -// { -// let ipld: Ipld = BTreeMap::from_iter([(T::TAG.into(), payload.clone().into())]).into(); -// -// let mut buffer = vec![]; -// ipld.encode(*varsig_header.codec(), &mut buffer) -// .map_err(SignError::PayloadEncodingError)?; -// -// let signature = signer -// .try_sign(&buffer) -// .map_err(SignError::SignatureError)?; -// -// Ok(Envelope { -// varsig_header, -// signature, -// payload, -// _phantom: std::marker::PhantomData, -// }) -// } -// -// /// Attempt to validate a signature. -// /// -// /// # Arguments -// /// -// /// * `self` - The envelope to validate. -// /// -// /// # Errors -// /// -// /// * [`ValidateError`] - the payload can't be encoded or the signature fails. -// /// -// /// # Exmaples -// /// -// /// FIXME -// pub fn validate_signature(&self) -> Result<(), ValidateError> -// where -// T: Clone, -// Ipld: Encode, -// { -// let mut encoded = vec![]; -// let ipld: Ipld = BTreeMap::from_iter([(T::TAG.into(), self.payload.clone().into())]).into(); -// ipld.encode(*self.varsig_header.codec(), &mut encoded) -// .map_err(ValidateError::PayloadEncodingError)?; -// -// self.verifier() -// .verify(&encoded, &self.signature) -// .map_err(ValidateError::VerifyError) -// } -// -// pub fn cid(&self) -> Result -// where -// Self: Clone, -// Ipld: Encode + From, -// { -// let codec = self.varsig_header.codec().clone(); -// let mut ipld_buffer = vec![]; -// self.encode(codec, &mut ipld_buffer)?; -// -// let multihash = Code::Sha2_256.digest(&ipld_buffer); -// Ok(Cid::new_v1( -// self.varsig_header.codec().clone().into(), -// multihash, -// )) -// } -// } - -// impl< -// T: Verifiable + Capsule, -// DID: Did, -// V: varsig::Header, -// C: Codec + Into + TryFrom, -// > From> for Ipld -// where -// Ipld: From, -// { -// fn from(envelope: Envelope) -> Self { -// let ipld: Ipld = BTreeMap::from_iter([(T::TAG.into(), envelope.payload.into())]).into(); -// let varsig_header: Ipld = Ipld::Bytes(envelope.varsig_header.into()); -// -// Ipld::Map(BTreeMap::from_iter([ -// ("sig".into(), Ipld::Bytes(envelope.signature.to_vec())), -// ("pld".into(), Ipld::List(vec![varsig_header, ipld])), -// ])) -// } -// } -// -// impl< -// T: TryFrom + Verifiable + Capsule, -// DID: Did, -// V: varsig::Header, -// C: Codec + Into + TryFrom, -// > TryFrom for Envelope -// { -// type Error = FromIpldError; -// -// fn try_from(ipld: Ipld) -> Result { -// if let Ipld::List(list) = ipld { -// if let [Ipld::Bytes(sig), Ipld::List(inner)] = list.as_slice() { -// if let [Ipld::Bytes(varsig_header), Ipld::Map(btree)] = inner.as_slice() { -// if let (1, Some(payload)) = (btree.len(), btree.get(T::TAG.into())) { -// Ok(Envelope { -// payload: T::try_from(payload.clone()) -// .map_err(FromIpldError::CannotParsePayload)?, -// -// varsig_header: V::try_from(varsig_header.as_slice()) -// .map_err(|_| FromIpldError::CannotParseVarsigHeader)?, -// -// signature: DID::Signature::try_from(sig.as_slice()) -// .map_err(|_| FromIpldError::CannotParseSignature)?, -// -// _phantom: std::marker::PhantomData, -// }) -// } else { -// Err(FromIpldError::InvalidPayloadCapsule) -// } -// } else { -// Err(FromIpldError::InvalidVarsigContainer) -// } -// } else { -// Err(FromIpldError::InvalidSignatureContainer) -// } -// } else { -// Err(FromIpldError::InvalidSignatureContainer) -// } -// } -// } - #[derive(Debug, Clone, PartialEq, Error)] pub enum FromIpldError { #[error("Invalid signature container")] @@ -441,26 +212,6 @@ pub enum FromIpldError { #[error("Invalid payload capsule")] InvalidPayloadCapsule, } -// -// impl< -// T: Verifiable + Capsule, -// DID: Did, -// V: varsig::Header, -// C: Codec + Into + TryFrom, -// > Encode for Envelope -// where -// Self: Clone, -// Ipld: Encode + From, -// { -// fn encode( -// &self, -// codec: C, -// w: &mut W, -// ) -> Result<(), libipld_core::error::Error> { -// let ipld: Ipld = self.clone().into(); -// ipld.encode(codec, w) -// } -// } /// Errors that can occur when signing a [`siganture::Envelope`][Envelope]. #[derive(Debug, Error)] @@ -485,38 +236,3 @@ pub enum ValidateError { #[error("Signature verification failed: {0}")] VerifyError(#[from] signature::Error), } - -// impl< -// T: Verifiable + Capsule, -// DID: Did, -// V: varsig::Header, -// C: Codec + TryFrom + Into, -// > Serialize for Envelope -// { -// fn serialize(&self, serializer: S) -> std::prelude::v1::Result -// where -// S: Serializer, -// { -// self.clone().serialize(serializer) -// } -// } -// -// impl< -// 'de, -// T: Verifiable + Capsule + TryFrom, -// DID: Did, -// V: varsig::Header, -// C: Codec + TryFrom + Into, -// > Deserialize<'de> for Envelope -// where -// Envelope: TryFrom, -// as TryFrom>::Error: std::fmt::Display, -// { -// fn deserialize(deserializer: D) -> std::prelude::v1::Result -// where -// D: Deserializer<'de>, -// { -// let ipld: Ipld = Deserialize::deserialize(deserializer)?; -// Ok(Envelope::try_from(ipld).map_err(serde::de::Error::custom)?) -// } -// } diff --git a/src/crypto/varsig/header/traits.rs b/src/crypto/varsig/header/traits.rs index 6a88f49d..a91bfe29 100644 --- a/src/crypto/varsig/header/traits.rs +++ b/src/crypto/varsig/header/traits.rs @@ -2,7 +2,7 @@ use libipld_core::codec::{Codec, Encode}; use signature::Verifier; use thiserror::Error; -pub trait Header + Into>: +pub trait Header + Into>: for<'a> TryFrom<&'a [u8]> + Into> { type Signature: signature::SignatureEncoding; diff --git a/src/did/key/signer.rs b/src/did/key/signer.rs index 6bc8584e..529f557c 100644 --- a/src/did/key/signer.rs +++ b/src/did/key/signer.rs @@ -1,5 +1,7 @@ -use enum_as_inner::EnumAsInner; use super::Signature; +use crate::did::Did; +use did_url::DID; +use enum_as_inner::EnumAsInner; #[cfg(feature = "eddsa")] use ed25519_dalek; @@ -25,15 +27,13 @@ use crate::crypto::rs512; #[cfg(feature = "bls")] use crate::crypto::bls12381; - - /// Signature types that are verifiable by `did:key` [`Verifier`]s. #[derive(Debug, Clone, PartialEq, Eq, EnumAsInner)] pub enum Signer { /// `EdDSA` signature. #[cfg(feature = "eddsa")] EdDsa(ed25519_dalek::SigningKey), - + // FIXME // /// `ES256K` (`secp256k1`) signature. // #[cfg(feature = "es256k")] // Es256k(k256::ecdsa::Signer), From ff366c5edafa606d72a4be4ff495771720933e2b Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Wed, 6 Mar 2024 22:17:37 -0800 Subject: [PATCH 124/188] update for u64 everywhere --- src/ability/pipe.rs | 8 ++++---- src/ability/ucan/batch.rs | 4 ++-- src/crypto/varsig/encoding/preset.rs | 2 +- src/crypto/varsig/header/eddsa.rs | 12 ++++++------ src/crypto/varsig/header/es256.rs | 12 ++++++------ src/crypto/varsig/header/es256k.rs | 12 ++++++------ src/crypto/varsig/header/es512.rs | 12 ++++++------ src/crypto/varsig/header/rs256.rs | 14 +++++++------- src/crypto/varsig/header/rs512.rs | 12 ++++++------ src/delegation.rs | 14 +++++++------- src/delegation/agent.rs | 4 ++-- src/delegation/store/memory.rs | 6 +++--- src/delegation/store/traits.rs | 2 +- src/invocation.rs | 14 +++++++------- src/invocation/agent.rs | 6 +++--- src/invocation/store.rs | 6 +++--- src/receipt.rs | 12 ++++++------ src/receipt/store/memory.rs | 4 ++-- src/receipt/store/traits.rs | 2 +- 19 files changed, 79 insertions(+), 79 deletions(-) diff --git a/src/ability/pipe.rs b/src/ability/pipe.rs index 0dd41874..536fd55a 100644 --- a/src/ability/pipe.rs +++ b/src/ability/pipe.rs @@ -1,22 +1,22 @@ use crate::{crypto::varsig, delegation, did::Did, ipld}; use libipld_core::{codec::Codec, ipld::Ipld}; -pub struct Pipe, C: Codec + TryFrom + Into> { +pub struct Pipe, C: Codec + TryFrom + Into> { pub source: Cap, pub sink: Cap, } -pub enum Cap, C: Codec + TryFrom + Into> { +pub enum Cap, C: Codec + TryFrom + Into> { Proof(delegation::Proof), Literal(Ipld), } -pub struct PromisedPipe, C: Codec + TryFrom + Into> { +pub struct PromisedPipe, C: Codec + TryFrom + Into> { pub source: PromisedCap, pub sink: PromisedCap, } -pub enum PromisedCap, C: Codec + TryFrom + Into> { +pub enum PromisedCap, C: Codec + TryFrom + Into> { Proof(delegation::Proof), Promised(ipld::Promised), } diff --git a/src/ability/ucan/batch.rs b/src/ability/ucan/batch.rs index e068a5f4..9712fd93 100644 --- a/src/ability/ucan/batch.rs +++ b/src/ability/ucan/batch.rs @@ -3,11 +3,11 @@ // use std::collections::BTreeMap; // // #[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)] -// pub struct Batch, Enc: Codec + TryFrom + Into> { +// pub struct Batch, Enc: Codec + TryFrom + Into> { // pub batch: Vec>, // FIXME not quite right; would be nice to include meta etc // } // -// pub struct Step, Enc: Codec + TryFrom + Into> { +// pub struct Step, Enc: Codec + TryFrom + Into> { // pub subject: DID, // pub audience: Option, // pub ability: A, // FIXME promise version instead? Promised version shoudl be able to promise any field diff --git a/src/crypto/varsig/encoding/preset.rs b/src/crypto/varsig/encoding/preset.rs index cf7cf8ea..a4189ba3 100644 --- a/src/crypto/varsig/encoding/preset.rs +++ b/src/crypto/varsig/encoding/preset.rs @@ -42,7 +42,7 @@ impl<'a> TryFrom<&'a [u8]> for Preset { type Error = (); fn try_from(bytes: &'a [u8]) -> Result { - if let (encoding_info, &[]) = unsigned_varint::decode::u32(&bytes).map_err(|_| ())? { + if let (encoding_info, &[]) = unsigned_varint::decode::u64(&bytes).map_err(|_| ())? { return match encoding_info { 0x5f => Ok(Preset::Identity), 0x70 => Ok(Preset::DagPb), diff --git a/src/crypto/varsig/header/eddsa.rs b/src/crypto/varsig/header/eddsa.rs index 0ccffd0a..5dd2009e 100644 --- a/src/crypto/varsig/header/eddsa.rs +++ b/src/crypto/varsig/header/eddsa.rs @@ -6,12 +6,12 @@ pub struct EdDsaHeader { pub codec: C, } -impl> TryFrom<&[u8]> for EdDsaHeader { +impl> TryFrom<&[u8]> for EdDsaHeader { type Error = (); // FIXME fn try_from(bytes: &[u8]) -> Result { if let Ok((0xed, inner)) = unsigned_varint::decode::u8(&bytes) { - if let Ok((codec_info, &[])) = unsigned_varint::decode::u32(&inner) { + if let Ok((codec_info, &[])) = unsigned_varint::decode::u64(&inner) { let codec = C::try_from(codec_info).map_err(|_| ())?; return Ok(EdDsaHeader { codec }); } @@ -21,19 +21,19 @@ impl> TryFrom<&[u8]> for EdDsaHeader { } } -impl + Clone> From> for Vec { +impl + Clone> From> for Vec { fn from(ed: EdDsaHeader) -> Vec { let mut tag_buf: [u8; 2] = Default::default(); let tag: &[u8] = unsigned_varint::encode::u8(0xed, &mut tag_buf); - let mut enc_buf: [u8; 5] = Default::default(); - let enc: &[u8] = unsigned_varint::encode::u32(ed.codec.into(), &mut enc_buf); + let mut enc_buf: [u8; 10] = Default::default(); + let enc: &[u8] = unsigned_varint::encode::u64(ed.codec.into(), &mut enc_buf); [tag, enc].concat().into() } } -impl + TryFrom> Header for EdDsaHeader { +impl + TryFrom> Header for EdDsaHeader { type Signature = ed25519_dalek::Signature; type Verifier = ed25519_dalek::VerifyingKey; diff --git a/src/crypto/varsig/header/es256.rs b/src/crypto/varsig/header/es256.rs index 901d863d..4e53a460 100644 --- a/src/crypto/varsig/header/es256.rs +++ b/src/crypto/varsig/header/es256.rs @@ -6,13 +6,13 @@ pub struct Es256Header { pub codec: C, } -impl> TryFrom<&[u8]> for Es256Header { +impl> TryFrom<&[u8]> for Es256Header { type Error = (); // FIXME fn try_from(bytes: &[u8]) -> Result { if let Ok((0x1200, inner)) = unsigned_varint::decode::u16(&bytes) { if let Ok((0x12, more)) = unsigned_varint::decode::u8(&inner) { - if let Ok((codec_info, &[])) = unsigned_varint::decode::u32(&more) { + if let Ok((codec_info, &[])) = unsigned_varint::decode::u64(&more) { let codec = C::try_from(codec_info).map_err(|_| ())?; return Ok(Es256Header { codec }); } @@ -23,7 +23,7 @@ impl> TryFrom<&[u8]> for Es256Header { } } -impl> From> for Vec { +impl> From> for Vec { fn from(es: Es256Header) -> Vec { let mut tag_buf: [u8; 3] = Default::default(); let tag = unsigned_varint::encode::u16(0x1200, &mut tag_buf); @@ -31,14 +31,14 @@ impl> From> for Vec { let mut hash_buf: [u8; 2] = Default::default(); let hash = unsigned_varint::encode::u8(0x12, &mut hash_buf); - let mut enc_buf: [u8; 5] = Default::default(); - let enc = unsigned_varint::encode::u32(es.codec.into(), &mut enc_buf); + let mut enc_buf: [u8; 10] = Default::default(); + let enc = unsigned_varint::encode::u64(es.codec.into(), &mut enc_buf); [tag, hash, enc].concat().into() } } -impl + TryFrom> Header for Es256Header { +impl + TryFrom> Header for Es256Header { type Signature = p256::ecdsa::Signature; type Verifier = p256::ecdsa::VerifyingKey; diff --git a/src/crypto/varsig/header/es256k.rs b/src/crypto/varsig/header/es256k.rs index 9b01c09f..465c4338 100644 --- a/src/crypto/varsig/header/es256k.rs +++ b/src/crypto/varsig/header/es256k.rs @@ -6,13 +6,13 @@ pub struct Es256kHeader { pub codec: C, } -impl> TryFrom<&[u8]> for Es256kHeader { +impl> TryFrom<&[u8]> for Es256kHeader { type Error = (); // FIXME fn try_from(bytes: &[u8]) -> Result { if let Ok((0xe7, inner)) = unsigned_varint::decode::u8(&bytes) { if let Ok((0x12, more)) = unsigned_varint::decode::u8(&inner).map_err(|_| ()) { - if let Ok((codec_info, &[])) = unsigned_varint::decode::u32(&more) { + if let Ok((codec_info, &[])) = unsigned_varint::decode::u64(&more) { let codec = C::try_from(codec_info).map_err(|_| ())?; return Ok(Es256kHeader { codec }); } @@ -23,7 +23,7 @@ impl> TryFrom<&[u8]> for Es256kHeader { } } -impl> From> for Vec { +impl> From> for Vec { fn from(es: Es256kHeader) -> Vec { let mut tag_buf: [u8; 2] = Default::default(); let tag = unsigned_varint::encode::u8(0xe7, &mut tag_buf); @@ -31,14 +31,14 @@ impl> From> for Vec { let mut hash_buf: [u8; 2] = Default::default(); let hash = unsigned_varint::encode::u8(0x12, &mut hash_buf); - let mut enc_buf: [u8; 5] = Default::default(); - let enc = unsigned_varint::encode::u32(es.codec.into(), &mut enc_buf); + let mut enc_buf: [u8; 10] = Default::default(); + let enc = unsigned_varint::encode::u64(es.codec.into(), &mut enc_buf); [tag, hash, enc].concat().into() } } -impl + TryFrom> Header for Es256kHeader { +impl + TryFrom> Header for Es256kHeader { type Signature = k256::ecdsa::Signature; type Verifier = k256::ecdsa::VerifyingKey; diff --git a/src/crypto/varsig/header/es512.rs b/src/crypto/varsig/header/es512.rs index 00e4b1a1..07089fac 100644 --- a/src/crypto/varsig/header/es512.rs +++ b/src/crypto/varsig/header/es512.rs @@ -6,13 +6,13 @@ pub struct Es512Header { pub codec: C, } -impl> TryFrom<&[u8]> for Es512Header { +impl> TryFrom<&[u8]> for Es512Header { type Error = (); // FIXME fn try_from(bytes: &[u8]) -> Result { if let Ok((0x1202, inner)) = unsigned_varint::decode::u16(&bytes) { if let Ok((0x13, more)) = unsigned_varint::decode::u8(&inner) { - if let Ok((codec_info, &[])) = unsigned_varint::decode::u32(&more) { + if let Ok((codec_info, &[])) = unsigned_varint::decode::u64(&more) { let codec = C::try_from(codec_info).map_err(|_| ())?; return Ok(Es512Header { codec }); } @@ -23,7 +23,7 @@ impl> TryFrom<&[u8]> for Es512Header { } } -impl> From> for Vec { +impl> From> for Vec { fn from(es: Es512Header) -> Vec { let mut tag_buf: [u8; 3] = Default::default(); let tag = unsigned_varint::encode::u16(0x1202, &mut tag_buf); @@ -31,14 +31,14 @@ impl> From> for Vec { let mut hash_buf: [u8; 2] = Default::default(); let hash = unsigned_varint::encode::u8(0x13, &mut hash_buf); - let mut enc_buf: [u8; 5] = Default::default(); - let enc = unsigned_varint::encode::u32(es.codec.into(), &mut enc_buf); + let mut enc_buf: [u8; 10] = Default::default(); + let enc = unsigned_varint::encode::u64(es.codec.into(), &mut enc_buf); [tag, hash, enc].concat().into() } } -impl + TryFrom> Header for Es512Header { +impl + TryFrom> Header for Es512Header { type Signature = p521::ecdsa::Signature; type Verifier = p521::ecdsa::VerifyingKey; diff --git a/src/crypto/varsig/header/rs256.rs b/src/crypto/varsig/header/rs256.rs index a175af8e..bc137ecb 100644 --- a/src/crypto/varsig/header/rs256.rs +++ b/src/crypto/varsig/header/rs256.rs @@ -8,13 +8,13 @@ pub struct Rs256Header { pub codec: C, } -impl> TryFrom<&[u8]> for Rs256Header { - type Error = ParseFromBytesError<>::Error>; +impl> TryFrom<&[u8]> for Rs256Header { + type Error = ParseFromBytesError<>::Error>; fn try_from(bytes: &[u8]) -> Result { if let Ok((0x1205, inner)) = unsigned_varint::decode::u16(&bytes) { if let Ok((0x12, more)) = unsigned_varint::decode::u8(&inner) { - if let Ok((codec_info, &[])) = unsigned_varint::decode::u32(&more) { + if let Ok((codec_info, &[])) = unsigned_varint::decode::u64(&more) { let codec = C::try_from(codec_info).map_err(ParseFromBytesError::CodecPrefixError)?; @@ -36,7 +36,7 @@ pub enum ParseFromBytesError { CodecPrefixError(#[from] C), } -impl> From> for Vec { +impl> From> for Vec { fn from(rs: Rs256Header) -> Vec { let mut tag_buf: [u8; 3] = Default::default(); let tag = unsigned_varint::encode::u16(0x1205, &mut tag_buf); @@ -44,14 +44,14 @@ impl> From> for Vec { let mut hash_buf: [u8; 2] = Default::default(); let hash = unsigned_varint::encode::u8(0x12, &mut hash_buf); - let mut enc_buf: [u8; 5] = Default::default(); - let enc = unsigned_varint::encode::u32(rs.codec.into(), &mut enc_buf); + let mut enc_buf: [u8; 10] = Default::default(); + let enc = unsigned_varint::encode::u64(rs.codec.into(), &mut enc_buf); [tag, hash, enc].concat().into() } } -impl + TryFrom> Header for Rs256Header { +impl + TryFrom> Header for Rs256Header { type Signature = Signature; type Verifier = VerifyingKey; diff --git a/src/crypto/varsig/header/rs512.rs b/src/crypto/varsig/header/rs512.rs index d1cb098a..7b9d161d 100644 --- a/src/crypto/varsig/header/rs512.rs +++ b/src/crypto/varsig/header/rs512.rs @@ -7,13 +7,13 @@ pub struct Rs512Header { pub codec: C, } -impl> TryFrom<&[u8]> for Rs512Header { +impl> TryFrom<&[u8]> for Rs512Header { type Error = (); // FIXME fn try_from(bytes: &[u8]) -> Result { if let Ok((0x1205, inner)) = unsigned_varint::decode::u16(&bytes) { if let Ok((0x13, more)) = unsigned_varint::decode::u8(&inner) { - if let Ok((codec_info, &[])) = unsigned_varint::decode::u32(&more) { + if let Ok((codec_info, &[])) = unsigned_varint::decode::u64(&more) { let codec = C::try_from(codec_info).map_err(|_| ())?; return Ok(Rs512Header { codec }); } @@ -24,7 +24,7 @@ impl> TryFrom<&[u8]> for Rs512Header { } } -impl> From> for Vec { +impl> From> for Vec { fn from(rs: Rs512Header) -> Vec { let mut tag_buf: [u8; 3] = Default::default(); let tag = unsigned_varint::encode::u16(0x1205, &mut tag_buf); @@ -32,14 +32,14 @@ impl> From> for Vec { let mut hash_buf: [u8; 2] = Default::default(); let hash = unsigned_varint::encode::u8(0x13, &mut hash_buf); - let mut enc_buf: [u8; 5] = Default::default(); - let enc = unsigned_varint::encode::u32(rs.codec.into(), &mut enc_buf); + let mut enc_buf: [u8; 10] = Default::default(); + let enc = unsigned_varint::encode::u64(rs.codec.into(), &mut enc_buf); [tag, hash, enc].concat().into() } } -impl + TryFrom> Header for Rs512Header { +impl + TryFrom> Header for Rs512Header { type Signature = Signature; type Verifier = VerifyingKey; diff --git a/src/delegation.rs b/src/delegation.rs index 3d43b6d8..67f873a0 100644 --- a/src/delegation.rs +++ b/src/delegation.rs @@ -41,7 +41,7 @@ use web_time::SystemTime; pub struct Delegation< DID: Did = did::preset::Verifier, V: varsig::Header = varsig::header::Preset, - C: Codec + TryFrom + Into = varsig::encoding::Preset, + C: Codec + TryFrom + Into = varsig::encoding::Preset, > { pub varsig_header: V, pub payload: Payload, @@ -53,18 +53,18 @@ pub struct Delegation< pub struct Proof< DID: Did = did::preset::Verifier, V: varsig::Header = varsig::header::Preset, - C: Codec + TryFrom + Into = varsig::encoding::Preset, + C: Codec + TryFrom + Into = varsig::encoding::Preset, > { pub prf: Vec>>, } -impl, C: Codec + TryFrom + Into> Capsule +impl, C: Codec + TryFrom + Into> Capsule for Proof { const TAG: &'static str = "ucan/prf"; } -impl, C: Codec + Into + TryFrom> Delegation { +impl, C: Codec + Into + TryFrom> Delegation { pub fn new( varsig_header: V, signature: DID::Signature, @@ -123,7 +123,7 @@ impl, C: Codec + Into + TryFrom> Delega } } -impl + Clone, C: Codec + TryFrom + Into> Envelope +impl + Clone, C: Codec + TryFrom + Into> Envelope for Delegation where Payload: TryFrom, @@ -163,7 +163,7 @@ where } } -impl + Clone, C: Codec + TryFrom + Into> Serialize +impl + Clone, C: Codec + TryFrom + Into> Serialize for Delegation where Payload: TryFrom, @@ -176,7 +176,7 @@ where } } -impl<'de, DID: Did + Clone, V: varsig::Header + Clone, C: Codec + TryFrom + Into> +impl<'de, DID: Did + Clone, V: varsig::Header + Clone, C: Codec + TryFrom + Into> Deserialize<'de> for Delegation where Payload: TryFrom, diff --git a/src/delegation/agent.rs b/src/delegation/agent.rs index 396a650c..abe27a34 100644 --- a/src/delegation/agent.rs +++ b/src/delegation/agent.rs @@ -22,7 +22,7 @@ pub struct Agent< DID: Did, S: Store, V: varsig::Header, - Enc: Codec + TryFrom + Into, + Enc: Codec + TryFrom + Into, > { /// The [`Did`][Did] of the agent. pub did: &'a DID, @@ -39,7 +39,7 @@ impl< DID: Did + ToString + Clone, S: Store + Clone, V: varsig::Header + Clone, - Enc: Codec + TryFrom + Into, + Enc: Codec + TryFrom + Into, > Agent<'a, DID, S, V, Enc> where Ipld: Encode, diff --git a/src/delegation/store/memory.rs b/src/delegation/store/memory.rs index 0cafeea9..b603fb5c 100644 --- a/src/delegation/store/memory.rs +++ b/src/delegation/store/memory.rs @@ -69,13 +69,13 @@ use web_time::SystemTime; /// linkStyle 1 stroke:orange; /// ``` #[derive(Debug, Clone, PartialEq)] -pub struct MemoryStore, C: Codec + TryFrom + Into> { +pub struct MemoryStore, C: Codec + TryFrom + Into> { ucans: BTreeMap>, index: BTreeMap, BTreeMap>>, revocations: BTreeSet, } -impl, C: Codec + TryFrom + Into> Default +impl, C: Codec + TryFrom + Into> Default for MemoryStore { fn default() -> Self { @@ -88,7 +88,7 @@ impl, C: Codec + TryFrom + Into> } // FIXME check that UCAN is valid -impl, Enc: Codec + TryFrom + Into> +impl, Enc: Codec + TryFrom + Into> Store for MemoryStore { type DelegationStoreError = (); // FIXME misisng diff --git a/src/delegation/store/traits.rs b/src/delegation/store/traits.rs index 1969e01f..58d08bbc 100644 --- a/src/delegation/store/traits.rs +++ b/src/delegation/store/traits.rs @@ -8,7 +8,7 @@ use nonempty::NonEmpty; use std::fmt::Debug; use web_time::SystemTime; -pub trait Store, Enc: Codec + TryFrom + Into> { +pub trait Store, Enc: Codec + TryFrom + Into> { type DelegationStoreError: Debug; fn get(&self, cid: &Cid) -> Result<&Delegation, Self::DelegationStoreError>; diff --git a/src/invocation.rs b/src/invocation.rs index 823ca793..840b537a 100644 --- a/src/invocation.rs +++ b/src/invocation.rs @@ -51,7 +51,7 @@ pub struct Invocation< A, DID: did::Did = did::preset::Verifier, V: varsig::Header = varsig::header::Preset, - C: Codec + TryFrom + Into = varsig::encoding::Preset, + C: Codec + TryFrom + Into = varsig::encoding::Preset, > { pub varsig_header: V, pub payload: Payload, @@ -59,7 +59,7 @@ pub struct Invocation< _marker: std::marker::PhantomData, } -impl, C: Codec + TryFrom + Into> +impl, C: Codec + TryFrom + Into> Invocation where Ipld: Encode, @@ -117,7 +117,7 @@ where } } -impl, C: Codec + TryFrom + Into> did::Verifiable +impl, C: Codec + TryFrom + Into> did::Verifiable for Invocation { fn verifier(&self) -> &DID { @@ -129,7 +129,7 @@ impl< A: Clone, DID: Did + Clone, V: varsig::Header + Clone, - C: Codec + TryFrom + Into, + C: Codec + TryFrom + Into, > From> for Ipld where Payload: TryFrom, @@ -143,7 +143,7 @@ impl< A: Clone, DID: Did + Clone, V: varsig::Header + Clone, - C: Codec + TryFrom + Into, + C: Codec + TryFrom + Into, > Envelope for Invocation where Payload: TryFrom, @@ -187,7 +187,7 @@ impl< A: Clone, DID: Did + Clone, V: varsig::Header + Clone, - C: Codec + TryFrom + Into, + C: Codec + TryFrom + Into, > Serialize for Invocation where Payload: TryFrom, @@ -205,7 +205,7 @@ impl< A: Clone, DID: Did + Clone, V: varsig::Header + Clone, - C: Codec + TryFrom + Into, + C: Codec + TryFrom + Into, > Deserialize<'de> for Invocation where Payload: TryFrom, diff --git a/src/invocation/agent.rs b/src/invocation/agent.rs index 1ab29ac0..64a4e750 100644 --- a/src/invocation/agent.rs +++ b/src/invocation/agent.rs @@ -37,7 +37,7 @@ pub struct Agent< P: promise::Store, D: delegation::store::Store, V: varsig::Header + Clone, - C: Codec + Into + TryFrom, + C: Codec + Into + TryFrom, > { /// The agent's [`DID`]. pub did: &'a DID, @@ -66,7 +66,7 @@ where P: promise::Store, D: delegation::store::Store, V: varsig::Header + Clone, - C: Codec + Into + TryFrom, + C: Codec + Into + TryFrom, { pub fn new( did: &'a DID, @@ -300,7 +300,7 @@ pub enum ReceiveError< D, S: Store, V: varsig::Header, - C: Codec + TryFrom + Into, + C: Codec + TryFrom + Into, > where

>::PromiseStoreError: fmt::Debug, +

::PromiseStoreError: fmt::Debug, ::Promised, DID, V, C>>::InvocationStoreError: fmt::Debug, { let cid: Cid = promised.cid().map_err(ReceiveError::EncodingError)?; + let _ = promised .validate_signature() .map_err(ReceiveError::SigVerifyError)?; @@ -253,9 +255,9 @@ where // FIXME return type ) -> Result, ()> where - Ipld: From, + Named: From, T: From, - Payload: TryFrom, + Payload: TryFrom>, { let ability: T = Revoke { ucan: cid.clone() }.into(); let proofs = if &subject == self.did { @@ -301,14 +303,14 @@ pub enum Recipient { #[derive(Debug, Error)] pub enum ReceiveError< T: Resolvable, - P: promise::Store, + P: promise::Store, DID: Did, D, S: Store, V: varsig::Header, C: Codec + TryFrom + Into, > where -

>::PromiseStoreError: fmt::Debug, +

::PromiseStoreError: fmt::Debug, ::Promised, DID, V, C>>::InvocationStoreError: fmt::Debug, { #[error("encoding error: {0}")] @@ -322,8 +324,8 @@ pub enum ReceiveError< #[source] ::Promised, DID, V, C>>::InvocationStoreError, ), - #[error("promise store error: {0}")] - PromiseStoreError(#[source]

>::PromiseStoreError), + #[error("promise store error: {0:?}")] // FIXME + PromiseStoreError(

::PromiseStoreError), #[error("delegation store error: {0}")] DelegationStoreError(#[source] D), @@ -343,3 +345,47 @@ pub enum InvokeError { #[error("promise store error: {0}")] PromiseResolveError(#[source] ArgsErr), } + +#[cfg(test)] +mod tests { + use super::*; + use rand::thread_rng; + use testresult::TestResult; + + #[test_log::test] + fn test_agent_creation<'a>() -> TestResult { + let server_sk = ed25519_dalek::SigningKey::generate(&mut thread_rng()); + let server_signer = + crate::did::preset::Signer::Key(crate::did::key::Signer::EdDsa(server_sk.clone())); + + let server = crate::did::preset::Verifier::Key(crate::did::key::Verifier::EdDsa( + server_sk.verifying_key(), + )); + + let mut inv_store = crate::invocation::store::MemoryStore::default(); + let mut del_store = crate::delegation::store::MemoryStore::default(); + let mut prom_store = crate::invocation::promise::store::MemoryStore::default(); + + let agent: crate::invocation::agent::Agent< + '_, + crate::invocation::store::MemoryStore, + crate::delegation::store::MemoryStore, + crate::invocation::promise::store::MemoryStore, + > = Agent::new( + &server, + &server_signer, + &mut inv_store, + &mut del_store, + &mut prom_store, + ); + + assert!(false); + + // assert_eq!(agent.did, &did); + // assert_eq!(agent.invocation_store, &invocation_store); + // assert_eq!(agent.delegation_store, &delegation_store); + // assert_eq!(agent.unresolved_promise_index, &unresolved_promise_index); + + Ok(()) + } +} diff --git a/src/invocation/payload.rs b/src/invocation/payload.rs index fa281e76..0969db05 100644 --- a/src/invocation/payload.rs +++ b/src/invocation/payload.rs @@ -1,4 +1,6 @@ use super::promise::Resolvable; +use crate::ability::command::Command; +use crate::invocation::Named; use crate::{ ability::{arguments, command::ToCommand, parse::ParseAbility}, capsule::Capsule, @@ -249,13 +251,19 @@ impl Capsule for Payload { const TAG: &'static str = "ucan/i@1.0.0-rc.1"; } -impl, DID: Did> From> for arguments::Named { +impl From> for arguments::Named +where + arguments::Named: From, +{ fn from(payload: Payload) -> Self { let mut args = arguments::Named::from_iter([ ("iss".into(), payload.issuer.to_string().into()), ("sub".into(), payload.subject.to_string().into()), ("cmd".into(), payload.ability.to_command().into()), - ("args".into(), payload.ability.into()), + ( + "args".into(), + arguments::Named::::from(payload.ability).into(), + ), ( "prf".into(), Ipld::List(payload.proofs.iter().map(Into::into).collect()), @@ -264,7 +272,7 @@ impl, DID: Did> From> for arguments::N ]); if let Some(aud) = payload.audience { - args.insert("aud".into(), aud.into().to_string().into()); + args.insert("aud".into(), aud.to_string().into()); } if let Some(iat) = payload.issued_at { @@ -470,18 +478,108 @@ impl Verifiable for Payload { } } -use crate::ability::command::Command; - -impl + Command, DID: Did> TryFrom for Payload { +impl> + Command, DID: Did> + TryFrom> for Payload +where + >>::Error: fmt::Debug, +{ type Error = (); // FIXME - fn try_from(ipld: Ipld) -> Result { - if let Ipld::Map(btree) = ipld { - let payload_ipld = btree.get(A::COMMAND).ok_or(())?; - payload_ipld.clone().try_into().map_err(|_| ()) - } else { - Err(()) + fn try_from(named: arguments::Named) -> Result { + let mut subject = None; + let mut issuer = None; + let mut audience = None; + let mut via = None; + let mut command = None; + let mut args = None; + let mut metadata = None; + let mut nonce = None; + let mut expiration = None; + let mut proofs = None; + let mut issued_at = None; + + for (k, v) in named { + match k.as_str() { + "sub" => { + subject = Some( + match v { + Ipld::Null => None, + Ipld::String(s) => Some(DID::from_str(s.as_str()).map_err(|_| ())?), + _ => return Err(()), + } + .ok_or(())?, + ) + } + "iss" => match v { + Ipld::String(s) => issuer = Some(DID::from_str(s.as_str()).map_err(|_| ())?), + _ => return Err(()), + }, + "aud" => match v { + Ipld::String(s) => audience = Some(DID::from_str(s.as_str()).map_err(|_| ())?), + _ => return Err(()), + }, + "via" => match v { + Ipld::String(s) => via = Some(DID::from_str(s.as_str()).map_err(|_| ())?), + _ => return Err(()), + }, + "cmd" => match v { + Ipld::String(s) => command = Some(s), + _ => return Err(()), + }, + "args" => match v.try_into() { + Ok(a) => args = Some(a), + Err(_) => return Err(()), + }, + "meta" => match v { + Ipld::Map(m) => metadata = Some(m), + _ => return Err(()), + }, + "nonce" => match v { + Ipld::Bytes(b) => nonce = Some(Nonce::from(b)), + _ => return Err(()), + }, + "exp" => match v { + Ipld::Integer(i) => expiration = Some(i.try_into().map_err(|_| ())?), + _ => return Err(()), + }, + "iat" => match v { + Ipld::Integer(i) => issued_at = Some(i.try_into().map_err(|_| ())?), + _ => return Err(()), + }, + "prf" => match v { + Ipld::List(xs) => { + proofs = Some( + xs.into_iter() + .map(|x| match x { + Ipld::Link(cid) => Ok(cid), + _ => Err(()), + }) + .collect::, ()>>() + .map_err(|_| ())?, + ) + } + _ => return Err(()), + }, + _ => return Err(()), + } } + + let cmd = command.ok_or(())?; + let some_args = args.ok_or(())?; + let ability = ::try_parse(cmd.as_str(), some_args).map_err(|_| ())?; + + Ok(Payload { + issuer: issuer.ok_or(())?, + subject: subject.ok_or(())?, + audience, + ability, + proofs: proofs.ok_or(())?, + cause: None, + metadata: metadata.ok_or(())?, + nonce: nonce.ok_or(())?, + issued_at, + expiration, + }) } } @@ -490,14 +588,14 @@ impl + Command, DID: Did> TryFrom for Payload { /// [`Promise`]: crate::invocation::promise::Promise pub type Promised = Payload<::Promised, DID>; -impl From> for Ipld -where - Ipld: From, -{ - fn from(payload: Payload) -> Self { - arguments::Named::from(payload).into() - } -} +// impl From> for Ipld +// where +// Named: From, +// { +// fn from(payload: Payload) -> Self { +// arguments::Named::from(payload).into() +// } +// } #[cfg(feature = "test_utils")] impl Arbitrary for Payload diff --git a/src/invocation/promise/store/memory.rs b/src/invocation/promise/store/memory.rs index 3a221e41..6f587f7a 100644 --- a/src/invocation/promise/store/memory.rs +++ b/src/invocation/promise/store/memory.rs @@ -6,12 +6,12 @@ use std::{ convert::Infallible, }; -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Clone, PartialEq, Default)] pub struct MemoryStore { pub index: BTreeMap>, } -impl Store for MemoryStore { +impl Store for MemoryStore { type PromiseStoreError = Infallible; fn put_waiting( diff --git a/src/invocation/promise/store/traits.rs b/src/invocation/promise/store/traits.rs index 02d80686..0e894c84 100644 --- a/src/invocation/promise/store/traits.rs +++ b/src/invocation/promise/store/traits.rs @@ -2,7 +2,7 @@ use crate::{did::Did, invocation::promise::Resolvable}; use libipld_core::cid::Cid; use std::collections::BTreeSet; -pub trait Store { +pub trait Store { type PromiseStoreError; fn put_waiting( diff --git a/src/invocation/store.rs b/src/invocation/store.rs index e7e3ad50..846a863d 100644 --- a/src/invocation/store.rs +++ b/src/invocation/store.rs @@ -1,11 +1,18 @@ //! Storage for [`Invocation`]s. use super::Invocation; +use crate::ability; use crate::{crypto::varsig, did::Did}; use libipld_core::{cid::Cid, codec::Codec}; use std::{collections::BTreeMap, convert::Infallible}; -pub trait Store, Enc: Codec + Into + TryFrom> { +pub trait Store< + T = crate::ability::preset::Preset, + DID: Did = crate::did::preset::Verifier, + V: varsig::Header = varsig::header::Preset, + Enc: Codec + Into + TryFrom = varsig::encoding::Preset, +> +{ type InvocationStoreError; fn get( @@ -25,8 +32,23 @@ pub trait Store, Enc: Codec + Into + Tr } #[derive(Debug, Clone, PartialEq)] -pub struct MemoryStore, Enc: Codec + Into + TryFrom> { - store: BTreeMap>, +pub struct MemoryStore< + T = crate::ability::preset::PromisedPreset, + DID: crate::did::Did = crate::did::preset::Verifier, + V: varsig::Header = varsig::header::Preset, + C: Codec + TryFrom + Into = varsig::encoding::Preset, +> { + store: BTreeMap>, +} + +impl, Enc: Codec + Into + TryFrom> Default + for MemoryStore +{ + fn default() -> Self { + Self { + store: BTreeMap::new(), + } + } } impl, Enc: Codec + Into + TryFrom> diff --git a/src/receipt.rs b/src/receipt.rs index c96a7cf9..38fbf4dd 100644 --- a/src/receipt.rs +++ b/src/receipt.rs @@ -14,6 +14,7 @@ pub use payload::*; pub use responds::Responds; pub use store::Store; +use crate::ability::arguments; use crate::{ crypto::{signature::Envelope, varsig}, did::{self, Did}, @@ -51,7 +52,8 @@ impl< C: Codec + TryFrom + Into, > From> for Ipld where - Payload: TryFrom, + Ipld: From, + Payload: TryFrom>, { fn from(rec: Receipt) -> Self { rec.to_ipld_envelope() @@ -65,7 +67,8 @@ impl< C: Codec + TryFrom + Into, > Envelope for Receipt where - Payload: TryFrom, + Ipld: From, + Payload: TryFrom>, { type DID = DID; type Payload = Payload; @@ -109,7 +112,8 @@ impl< C: Codec + TryFrom + Into, > Serialize for Receipt where - Payload: TryFrom, + Ipld: From, + Payload: TryFrom>, { fn serialize(&self, serializer: S) -> Result where @@ -127,8 +131,9 @@ impl< C: Codec + TryFrom + Into, > Deserialize<'de> for Receipt where - Payload: TryFrom, - as TryFrom>::Error: std::fmt::Display, + Ipld: From, + Payload: TryFrom>, + as TryFrom>>::Error: std::fmt::Display, { fn deserialize(deserializer: D) -> Result where diff --git a/src/receipt/payload.rs b/src/receipt/payload.rs index 4bc40a96..cd3d22ce 100644 --- a/src/receipt/payload.rs +++ b/src/receipt/payload.rs @@ -94,6 +94,47 @@ impl Capsule for Payload { const TAG: &'static str = "ucan/r@1.0.0-rc.1"; } +impl From> for arguments::Named +where + Ipld: From, +{ + fn from(payload: Payload) -> Self { + let out_ipld = match payload.out { + Ok(ok) => BTreeMap::from_iter([("ok".to_string(), Ipld::from(ok))]).into(), + Err(err) => BTreeMap::from_iter([("err".to_string(), err.0.into())]).into(), + }; + + let mut args = arguments::Named::::from_iter([ + ("iss".to_string(), Ipld::String(payload.issuer.to_string())), + ("ran".to_string(), payload.ran.into()), + ("out".to_string(), out_ipld), + ( + "next".to_string(), + Ipld::List( + payload + .next + .clone() + .into_iter() + .map(|x| Ipld::Link(x)) + .collect(), + ), + ), + ( + "prf".to_string(), + Ipld::List(payload.next.into_iter().map(|x| Ipld::Link(x)).collect()), + ), + ("meta".to_string(), payload.metadata.into()), + ("nonce".to_string(), payload.nonce.into()), + ]); + + if let Some(issued_at) = payload.issued_at { + args.insert("iat".to_string(), issued_at.into()); + } + + args + } +} + impl Serialize for Payload where T::Success: Serialize, @@ -106,7 +147,7 @@ where let mut state = serializer.serialize_struct("receipt::Payload", field_count)?; - state.serialize_field("iss", &self.issuer.clone().into().as_str())?; + state.serialize_field("iss", &self.issuer.to_string().as_str())?; state.serialize_field("ran", &self.ran)?; state.serialize_field("out", &self.out)?; state.serialize_field("next", &self.next)?; diff --git a/src/time/timestamp.rs b/src/time/timestamp.rs index a378cf83..2e0f60c4 100644 --- a/src/time/timestamp.rs +++ b/src/time/timestamp.rs @@ -126,6 +126,30 @@ impl From for Ipld { } } +impl TryFrom for Timestamp { + type Error = (); + + fn try_from(ipld: Ipld) -> Result { + match ipld { + // FIXME do bounds checking + Ipld::Integer(secs) => Ok(Timestamp::new( + UNIX_EPOCH + Duration::from_secs(secs as u64), + ) + .map_err(|_| ())?), + _ => Err(()), + } + } +} + +impl TryFrom for Timestamp { + type Error = OutOfRangeError; + + fn try_from(secs: i128) -> Result { + // FIXME do bounds checking + Timestamp::new(UNIX_EPOCH + Duration::from_secs(secs as u64)) + } +} + impl Serialize for Timestamp { fn serialize(&self, serializer: S) -> Result where From fce6a9a3944394567042c36911f3e6d2e4079a53 Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Thu, 14 Mar 2024 00:45:18 -0700 Subject: [PATCH 150/188] Introduced (correctly) failing test --- src/delegation/store/memory.rs | 72 ++---- src/invocation.rs | 21 +- src/invocation/agent.rs | 399 ++++++++++++++++----------------- src/invocation/payload.rs | 19 +- src/invocation/store.rs | 2 +- 5 files changed, 241 insertions(+), 272 deletions(-) diff --git a/src/delegation/store/memory.rs b/src/delegation/store/memory.rs index 5b958d5f..11a99d6d 100644 --- a/src/delegation/store/memory.rs +++ b/src/delegation/store/memory.rs @@ -125,7 +125,6 @@ where cid: Cid, delegation: Delegation, ) -> Result<(), Self::DelegationStoreError> { - dbg!(&cid.to_string()); self.index .entry(delegation.subject().clone()) .or_default() @@ -135,15 +134,6 @@ where self.ucans.insert(cid.clone(), delegation); - dbg!(self.ucans.len()); - dbg!(self.index.len()); - for (sub, inner) in self.index.clone() { - dbg!(sub.clone().map(|x| x.to_string())); - for (aud, cids) in inner { - dbg!(aud.to_string()); - dbg!(cids.len()); - } - } Ok(()) } @@ -267,10 +257,13 @@ where #[cfg(test)] mod tests { - use crate::ability::arguments::Named; - use crate::ability::command::Command; - use crate::crypto::signature::Envelope; - use crate::delegation::store::Store; + use crate::{ + ability::{arguments::Named, command::Command}, + crypto::signature::Envelope, + delegation::store::Store, + invocation::promise::{CantResolve, Resolvable}, + ipld, + }; use libipld_core::ipld::Ipld; use rand::thread_rng; use testresult::TestResult; @@ -360,9 +353,9 @@ mod tests { #[derive(Debug, Clone, PartialEq)] pub struct AccountManage; - use crate::invocation::promise::CantResolve; - use crate::invocation::promise::Resolvable; - use crate::ipld; + impl Command for AccountManage { + const COMMAND: &'static str = "/account/info"; + } impl From for Named { fn from(_: AccountManage) -> Self { @@ -408,21 +401,6 @@ mod tests { } } - impl Command for AccountManage { - const COMMAND: &'static str = "/account/info"; - } - - // #[derive(Debug, Clone, PartialEq)] - // pub struct DnsLinkUpdate { - // pub cid: Cid, - // } - - // impl From for DnsLinkUpdate { - // fn from(_: Ipld) -> Self { - // todo!() - // } - // } - // 4. [dnslink -d-> account -*-> server -a-> device] let account_invocation = crate::Invocation::try_sign( &device_signer, @@ -458,7 +436,7 @@ mod tests { varsig::encoding::Preset, > = Default::default(); - let del_agent = crate::delegation::Agent::new(&server, &server_signer, &mut store); + // let del_agent = crate::delegation::Agent::new(&server, &server_signer, &mut store); let _ = store.insert( account_device_ucan.cid().expect("FIXME"), @@ -466,22 +444,16 @@ mod tests { ); let _ = store.insert(account_pbox.cid().expect("FIXME"), account_pbox.clone()); - let _ = store.insert(dnslink_ucan.cid().expect("FIXME"), dnslink_ucan.clone()); use std::time::SystemTime; - dbg!(device.to_string().clone()); - dbg!(server.to_string().clone()); - dbg!(account.to_string().clone()); - dbg!(dnslink.to_string().clone()); - let chain_for_powerline = store.get_chain(&device, &None, "/".into(), vec![], SystemTime::now()); let chain_for_dnslink = store.get_chain( &device, - &Some(dnslink), + &Some(dnslink.clone()), "/".into(), vec![], SystemTime::now(), @@ -496,30 +468,24 @@ mod tests { let mut inv_store = crate::invocation::store::MemoryStore::default(); let mut del_store = crate::delegation::store::MemoryStore::default(); - let mut prom_store = crate::invocation::promise::store::MemoryStore::default(); let mut agent: Agent< '_, crate::invocation::store::MemoryStore, crate::delegation::store::MemoryStore, - crate::invocation::promise::store::MemoryStore, AccountManage, - > = Agent::new( - &server, - &server_signer, - &mut inv_store, - &mut del_store, - &mut prom_store, - ); + > = Agent::new(&server, &server_signer, &mut inv_store, &mut del_store); + + let observed = agent.receive(account_invocation); - let observed = agent.receive(account_invocation, &SystemTime::now()); + dbg!(&observed); assert!(observed.is_ok()); let not_account_invocation = crate::Invocation::try_sign( &device_signer, varsig_header, crate::invocation::PayloadBuilder::default() - .subject(account.clone()) + .subject(dnslink.clone()) .issuer(server.clone()) .audience(Some(device.clone())) .ability(AccountManage) @@ -527,7 +493,9 @@ mod tests { .build()?, )?; - let observed_other = agent.receive(not_account_invocation, &SystemTime::now()); + dbg!(not_account_invocation.clone()); + + let observed_other = agent.receive(not_account_invocation); assert!(observed_other.is_err()); Ok(()) diff --git a/src/invocation.rs b/src/invocation.rs index 0d64902a..0c48387d 100644 --- a/src/invocation.rs +++ b/src/invocation.rs @@ -23,6 +23,7 @@ pub use payload::*; use crate::ability::arguments::Named; use crate::ability::command::ToCommand; +use crate::ability::parse::ParseAbility; use crate::{ crypto::{signature::Envelope, varsig}, did::{self, Did}, @@ -62,7 +63,7 @@ pub struct Invocation< } impl< - A: Clone + ToCommand, + A: Clone + ToCommand + ParseAbility, DID: Clone + did::Did, V: Clone + varsig::Header, C: Codec + TryFrom + Into, @@ -71,14 +72,19 @@ where Ipld: Encode, Named: From + From>, Payload: TryFrom>, + // >>::Error: std::fmt::Debug, { fn encode(&self, c: C, w: &mut W) -> Result<(), libipld_core::error::Error> { self.to_ipld_envelope().encode(c, w) } } -impl, C: Codec + TryFrom + Into> - Invocation +impl< + A: Clone + ToCommand + ParseAbility, + DID: Did + Clone, + V: varsig::Header, + C: Codec + TryFrom + Into, + > Invocation where Ipld: Encode, { @@ -110,6 +116,7 @@ where pub fn map_ability(self, f: F) -> Invocation where F: FnOnce(A) -> Z, + Z: ParseAbility + ToCommand, { Invocation::new( self.varsig_header, @@ -144,7 +151,7 @@ impl, C: Codec + TryFrom + Into> did } impl< - A: Clone + ToCommand, + A: Clone + ToCommand + ParseAbility, DID: Did + Clone, V: varsig::Header + Clone, C: Codec + TryFrom + Into, @@ -159,7 +166,7 @@ where } impl< - A: Clone + ToCommand, + A: Clone + ToCommand + ParseAbility, DID: Did + Clone, V: varsig::Header + Clone, C: Codec + TryFrom + Into, @@ -204,7 +211,7 @@ where } impl< - A: Clone + ToCommand, + A: Clone + ToCommand + ParseAbility, DID: Did + Clone, V: varsig::Header + Clone, C: Codec + TryFrom + Into, @@ -223,7 +230,7 @@ where impl< 'de, - A: Clone + ToCommand, + A: Clone + ToCommand + ParseAbility, DID: Did + Clone, V: varsig::Header + Clone, C: Codec + TryFrom + Into, diff --git a/src/invocation/agent.rs b/src/invocation/agent.rs index aefd5117..899d5f7b 100644 --- a/src/invocation/agent.rs +++ b/src/invocation/agent.rs @@ -1,11 +1,11 @@ use super::{ payload::{Payload, ValidationError}, - promise::Resolvable, store::Store, Invocation, }; use crate::ability::arguments::Named; use crate::ability::command::ToCommand; +use crate::ability::parse::ParseAbility; use crate::{ ability::{self, arguments, parse::ParseAbilityError, ucan::revoke::Revoke}, crypto::{ @@ -14,7 +14,6 @@ use crate::{ }, delegation, did::{self, Did}, - invocation::promise, time::Timestamp, }; use libipld_core::{ @@ -33,10 +32,9 @@ use web_time::SystemTime; #[derive(Debug)] pub struct Agent< 'a, - S: Store, + S: Store, D: delegation::store::Store, - P: promise::Store, - T: Resolvable + ToCommand = ability::preset::Preset, + T: ToCommand = ability::preset::Preset, DID: Did = did::preset::Verifier, V: varsig::Header + Clone = varsig::header::Preset, C: Codec + Into + TryFrom = varsig::encoding::Preset, @@ -50,39 +48,35 @@ pub struct Agent< /// A [`Store`][Store] for the agent's [`Invocation`]s. pub invocation_store: &'a mut S, - /// A [`promise::Store`] for the agent's unresolved promises. - pub unresolved_promise_index: &'a mut P, - signer: &'a ::Signer, marker: PhantomData<(T, V, C)>, } -impl<'a, T, DID, S, P, D, V, C> Agent<'a, S, D, P, T, DID, V, C> +impl<'a, T, DID, S, D, V, C> Agent<'a, S, D, T, DID, V, C> where Ipld: Encode, + T: ToCommand + Clone + ParseAbility, Named: From, + Payload: TryFrom>, delegation::Payload: Clone, - T: Resolvable + ToCommand + Clone, - T::Promised: Clone + ToCommand, DID: Did + Clone, - S: Store, + S: Store, D: delegation::store::Store, - P: promise::Store, V: varsig::Header + Clone, C: Codec + Into + TryFrom, + >::InvocationStoreError: fmt::Debug, + >::DelegationStoreError: fmt::Debug, { pub fn new( did: &'a DID, signer: &'a ::Signer, invocation_store: &'a mut S, delegation_store: &'a mut D, - unresolved_promise_index: &'a mut P, ) -> Self { Self { did, invocation_store, delegation_store, - unresolved_promise_index, signer, marker: PhantomData, } @@ -99,17 +93,7 @@ where issued_at: Option, now: SystemTime, varsig_header: V, - ) -> Result< - Invocation, - InvokeError< - D::DelegationStoreError, - ParseAbilityError<()>, // FIXME argserror - >, - > - where - Named: From, - Payload: TryFrom>, - { + ) -> Result, InvokeError> { let proofs = self .delegation_store .get_chain(self.did, &Some(subject.clone()), "/".into(), vec![], now) // FIXME @@ -136,95 +120,85 @@ where .map_err(InvokeError::SignError)?) } - pub fn invoke_promise( + // pub fn invoke_promise( + // &mut self, + // audience: Option<&DID>, + // subject: DID, + // ability: T::Promised, + // metadata: BTreeMap, + // cause: Option, + // expiration: Option, + // issued_at: Option, + // now: SystemTime, + // varsig_header: V, + // ) -> Result< + // Invocation, + // InvokeError< + // D::DelegationStoreError, + // ParseAbilityError<()>, // FIXME errs + // >, + // > + // where + // Named: From, + // Payload: TryFrom>, + // { + // let proofs = self + // .delegation_store + // .get_chain(self.did, &Some(subject.clone()), "/".into(), vec![], now) + // .map_err(InvokeError::DelegationStoreError)? + // .map(|chain| chain.map(|(cid, _)| cid).into()) + // .unwrap_or(vec![]); + + // let mut seed = vec![]; + + // let payload = Payload { + // issuer: self.did.clone(), + // subject, + // audience: audience.cloned(), + // ability, + // proofs, + // metadata, + // nonce: Nonce::generate_12(&mut seed), + // cause, + // expiration, + // issued_at, + // }; + + // Ok(Invocation::try_sign(self.signer, varsig_header, payload) + // .map_err(InvokeError::SignError)?) + // } + + pub fn receive( &mut self, - audience: Option<&DID>, - subject: DID, - ability: T::Promised, - metadata: BTreeMap, - cause: Option, - expiration: Option, - issued_at: Option, - now: SystemTime, - varsig_header: V, - ) -> Result< - Invocation, - InvokeError< - D::DelegationStoreError, - ParseAbilityError<()>, // FIXME errs - >, - > + invocation: Invocation, + ) -> Result>, ReceiveError> where - Named: From, - Payload: TryFrom>, + arguments::Named: From, + Payload: TryFrom>, + Invocation: Clone + Encode, { - let proofs = self - .delegation_store - .get_chain(self.did, &Some(subject.clone()), "/".into(), vec![], now) - .map_err(InvokeError::DelegationStoreError)? - .map(|chain| chain.map(|(cid, _)| cid).into()) - .unwrap_or(vec![]); - - let mut seed = vec![]; - - let payload = Payload { - issuer: self.did.clone(), - subject, - audience: audience.cloned(), - ability, - proofs, - metadata, - nonce: Nonce::generate_12(&mut seed), - cause, - expiration, - issued_at, - }; - - Ok(Invocation::try_sign(self.signer, varsig_header, payload) - .map_err(InvokeError::SignError)?) + self.generic_receive(invocation, &SystemTime::now()) } - pub fn receive( + pub fn generic_receive( &mut self, - promised: Invocation, + invocation: Invocation, now: &SystemTime, - ) -> Result>, ReceiveError> + ) -> Result>, ReceiveError> where - arguments::Named: From + From, - Payload: TryFrom>, - Invocation: Clone + Encode, -

::PromiseStoreError: fmt::Debug, - ::Promised, DID, V, C>>::InvocationStoreError: fmt::Debug, + arguments::Named: From, + Payload: TryFrom>, + Invocation: Clone + Encode, { - let cid: Cid = promised.cid().map_err(ReceiveError::EncodingError)?; - - let _ = promised - .validate_signature() - .map_err(ReceiveError::SigVerifyError)?; + let cid: Cid = invocation.cid().map_err(ReceiveError::EncodingError)?; self.invocation_store - .put(cid.clone(), promised.clone()) + .put(cid.clone(), invocation.clone()) .map_err(ReceiveError::InvocationStoreError)?; - let resolved_ability: T = match Resolvable::try_resolve(promised.ability().clone()) { - Ok(resolved) => resolved, - Err(cant_resolve) => { - let waiting_on: BTreeSet = T::get_all_pending(cant_resolve.promised); - - self.unresolved_promise_index - .put_waiting( - promised.cid()?, - waiting_on.into_iter().collect::>(), - ) - .map_err(ReceiveError::PromiseStoreError)?; - - return Ok(Recipient::Unresolved(cid)); - } - }; - let proof_payloads: Vec<&delegation::Payload> = self .delegation_store - .get_many(&promised.proofs()) + .get_many(&invocation.proofs()) .map_err(ReceiveError::DelegationStoreError)? .iter() .fold(vec![], |mut acc, d| { @@ -232,64 +206,63 @@ where acc }); - let resolved_payload = promised.payload.clone().map_ability(|_| resolved_ability); - - let _ = &resolved_payload + &invocation + .payload .check(proof_payloads, now) .map_err(ReceiveError::ValidationError)?; - if promised.audience() != &Some(self.did.clone()) { - return Ok(Recipient::Other(resolved_payload)); - } - - Ok(Recipient::You(resolved_payload)) - } - - pub fn revoke( - &mut self, - subject: DID, - cause: Option, - cid: Cid, - now: Timestamp, - varsig_header: V, - // FIXME return type - ) -> Result, ()> - where - Named: From, - T: From, - Payload: TryFrom>, - { - let ability: T = Revoke { ucan: cid.clone() }.into(); - let proofs = if &subject == self.did { - vec![] + Ok(if *invocation.audience() != Some(self.did.clone()) { + Recipient::Other(invocation.payload) } else { - todo!("update to latest trait interface"); // FIXME - // self.delegation_store - // .get_chain(&subject, &Some(self.did.clone()), vec![], now.into()) - // .map_err(|_| ())? - // .map(|chain| chain.map(|(index_cid, _)| index_cid).into()) - // .unwrap_or(vec![]) - }; - - let payload = Payload { - issuer: self.did.clone(), - subject: self.did.clone(), - audience: Some(self.did.clone()), - ability, - proofs, - cause, - metadata: BTreeMap::new(), - nonce: Nonce::generate_12(&mut vec![]), - expiration: None, - issued_at: None, - }; - - let invocation = - Invocation::try_sign(self.signer, varsig_header, payload).map_err(|_| ())?; - - self.delegation_store.revoke(cid).map_err(|_| ())?; - Ok(invocation) + Recipient::You(invocation.payload) + }) } + + // pub fn revoke( + // &mut self, + // subject: DID, + // cause: Option, + // cid: Cid, + // now: Timestamp, + // varsig_header: V, + // // FIXME return type + // ) -> Result, ()> + // where + // Named: From, + // T: From, + // Payload: TryFrom>, + // { + // let ability: T = Revoke { ucan: cid.clone() }.into(); + // let proofs = if &subject == self.did { + // vec![] + // } else { + // todo!("update to latest trait interface"); // FIXME + // // self.delegation_store + // // .get_chain(&subject, &Some(self.did.clone()), vec![], now.into()) + // // .map_err(|_| ())? + // // .map(|chain| chain.map(|(index_cid, _)| index_cid).into()) + // // .unwrap_or(vec![]) + // }; + + // let payload = Payload { + // issuer: self.did.clone(), + // subject: self.did.clone(), + // audience: Some(self.did.clone()), + // ability, + // proofs, + // cause, + // metadata: BTreeMap::new(), + // nonce: Nonce::generate_12(&mut vec![]), + // expiration: None, + // issued_at: None, + // }; + + // let invocation = + // Invocation::try_sign(self.signer, varsig_header, payload).map_err(|_| ())?; + + // self.delegation_store.revoke(cid).map_err(|_| ())?; + // Ok(invocation) + // } } #[derive(Debug)] @@ -302,16 +275,14 @@ pub enum Recipient { #[derive(Debug, Error)] pub enum ReceiveError< - T: Resolvable, - P: promise::Store, + T, DID: Did, D, - S: Store, + S: Store, V: varsig::Header, C: Codec + TryFrom + Into, > where -

::PromiseStoreError: fmt::Debug, - ::Promised, DID, V, C>>::InvocationStoreError: fmt::Debug, + >::InvocationStoreError: fmt::Debug, { #[error("encoding error: {0}")] EncodingError(#[from] libipld_core::error::Error), @@ -320,12 +291,7 @@ pub enum ReceiveError< SigVerifyError(#[from] signature::ValidateError), #[error("invocation store error: {0}")] - InvocationStoreError( - #[source] ::Promised, DID, V, C>>::InvocationStoreError, - ), - - #[error("promise store error: {0:?}")] // FIXME - PromiseStoreError(

::PromiseStoreError), + InvocationStoreError(#[source] >::InvocationStoreError), #[error("delegation store error: {0}")] DelegationStoreError(#[source] D), @@ -335,15 +301,12 @@ pub enum ReceiveError< } #[derive(Debug, Error)] -pub enum InvokeError { +pub enum InvokeError { #[error("delegation store error: {0}")] DelegationStoreError(#[source] D), - #[error("promise store error: {0}")] + #[error("store error: {0}")] SignError(#[source] signature::SignError), - - #[error("promise store error: {0}")] - PromiseResolveError(#[source] ArgsErr), } #[cfg(test)] @@ -352,40 +315,66 @@ mod tests { use rand::thread_rng; use testresult::TestResult; - #[test_log::test] - fn test_agent_creation<'a>() -> TestResult { - let server_sk = ed25519_dalek::SigningKey::generate(&mut thread_rng()); - let server_signer = - crate::did::preset::Signer::Key(crate::did::key::Signer::EdDsa(server_sk.clone())); - - let server = crate::did::preset::Verifier::Key(crate::did::key::Verifier::EdDsa( - server_sk.verifying_key(), - )); - - let mut inv_store = crate::invocation::store::MemoryStore::default(); - let mut del_store = crate::delegation::store::MemoryStore::default(); - let mut prom_store = crate::invocation::promise::store::MemoryStore::default(); - - let agent: crate::invocation::agent::Agent< - '_, - crate::invocation::store::MemoryStore, - crate::delegation::store::MemoryStore, - crate::invocation::promise::store::MemoryStore, - > = Agent::new( - &server, - &server_signer, - &mut inv_store, - &mut del_store, - &mut prom_store, - ); - - assert!(false); - - // assert_eq!(agent.did, &did); - // assert_eq!(agent.invocation_store, &invocation_store); - // assert_eq!(agent.delegation_store, &delegation_store); - // assert_eq!(agent.unresolved_promise_index, &unresolved_promise_index); - - Ok(()) - } + // fn setup<'a>( + // inv_store: &'a mut crate::invocation::store::MemoryStore, + // del_store: &'a mut crate::delegation::store::MemoryStore, + // ) -> ( + // crate::did::preset::Verifier, + // crate::did::preset::Signer, + // Agent<'a, crate::invocation::store::MemoryStore, crate::delegation::store::MemoryStore>, + // ) { + // let server_sk = ed25519_dalek::SigningKey::generate(&mut thread_rng()); + // let server_signer = + // crate::did::preset::Signer::Key(crate::did::key::Signer::EdDsa(server_sk.clone())); + + // let server = crate::did::preset::Verifier::Key(crate::did::key::Verifier::EdDsa( + // server_sk.verifying_key(), + // )); + + // let agent = + // crate::invocation::agent::Agent::new(&server, &server_signer, inv_store, del_store); + + // (server, server_signer, agent) + // } + + // mod receive { + // use super::*; + // use crate::ability::crud::{read::Read, Crud}; + // use crate::ability::preset::Preset; + // use crate::crypto::varsig; + + // #[test_log::test] + // fn test_happy_path() -> TestResult { + // let mut inv_store = crate::invocation::store::MemoryStore::default(); + // let mut del_store = crate::delegation::store::MemoryStore::default(); + + // let (_, _, mut agent) = setup(&mut inv_store, &mut del_store); + // let invocation = agent.invoke( + // None, + // agent.did.clone(), + // // FIXME flatten + // Preset::Crud(Crud::Read(Read { + // path: None, + // args: None, + // })), + // BTreeMap::new(), + // None, + // None, + // None, + // SystemTime::now(), + // varsig::header::Preset::EdDsa(varsig::header::EdDsaHeader { + // codec: varsig::encoding::Preset::DagCbor, + // }), + // )?; + + // let unknown_sk = ed25519_dalek::SigningKey::generate(&mut thread_rng()); + // let unknown_did = crate::did::preset::Verifier::Key(crate::did::key::Verifier::EdDsa( + // unknown_sk.verifying_key(), + // )); + + // agent.receive(invocation)?; + + // Ok(()) + // } + // } } diff --git a/src/invocation/payload.rs b/src/invocation/payload.rs index 0969db05..5932b0e4 100644 --- a/src/invocation/payload.rs +++ b/src/invocation/payload.rs @@ -163,7 +163,7 @@ impl Payload { cmd.push('/'); } - let (_, vias) = proofs.into_iter().try_fold( + let (final_iss, vias) = proofs.into_iter().try_fold( (&self.issuer, BTreeSet::new()), |(iss, mut vias), proof| { if *iss != proof.audience { @@ -211,8 +211,14 @@ impl Payload { }, )?; + if self.subject != *final_iss { + return Err(ValidationError::DidNotTerminateInSubject); + } + if !vias.is_empty() { - todo!() + return Err(ValidationError::UnfulfilledViaConstraint( + vias.into_iter().cloned().collect(), + )); } Ok(()) @@ -245,6 +251,9 @@ pub enum ValidationError { #[error("via field constraint was unfulfilled: {0:?}")] UnfulfilledViaConstraint(BTreeSet), + + #[error("The chain did not terminate in the expected subject")] + DidNotTerminateInSubject, } impl Capsule for Payload { @@ -478,11 +487,7 @@ impl Verifiable for Payload { } } -impl> + Command, DID: Did> - TryFrom> for Payload -where - >>::Error: fmt::Debug, -{ +impl TryFrom> for Payload { type Error = (); // FIXME fn try_from(named: arguments::Named) -> Result { diff --git a/src/invocation/store.rs b/src/invocation/store.rs index 846a863d..a9e76b13 100644 --- a/src/invocation/store.rs +++ b/src/invocation/store.rs @@ -33,7 +33,7 @@ pub trait Store< #[derive(Debug, Clone, PartialEq)] pub struct MemoryStore< - T = crate::ability::preset::PromisedPreset, + T = crate::ability::preset::Preset, DID: crate::did::Did = crate::did::preset::Verifier, V: varsig::Header = varsig::header::Preset, C: Codec + TryFrom + Into = varsig::encoding::Preset, From aa41773bcb8625898bb63614b1298dd9c46a3e91 Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Thu, 14 Mar 2024 19:50:32 -0700 Subject: [PATCH 151/188] Evidently proof search is borked --- src/delegation/store/memory.rs | 209 +++++++++++++++++---------------- src/delegation/store/traits.rs | 45 +++++-- src/did/preset.rs | 2 +- src/invocation.rs | 4 +- src/invocation/agent.rs | 143 +++++++++++----------- src/invocation/payload.rs | 8 +- src/invocation/store.rs | 39 ++++-- 7 files changed, 249 insertions(+), 201 deletions(-) diff --git a/src/delegation/store/memory.rs b/src/delegation/store/memory.rs index 11a99d6d..f4c1937d 100644 --- a/src/delegation/store/memory.rs +++ b/src/delegation/store/memory.rs @@ -1,10 +1,13 @@ use super::Store; use crate::ability::arguments::Named; +use crate::delegation; use crate::{ crypto::varsig, delegation::{policy::Predicate, Delegation}, did::{self, Did}, }; +use libipld_core::codec::Encode; +use libipld_core::ipld::Ipld; use libipld_core::{cid::Cid, codec::Codec}; use nonempty::NonEmpty; use std::collections::{BTreeMap, BTreeSet}; @@ -82,6 +85,10 @@ impl MemoryStore { Self::default() } + pub fn len(&self) -> usize { + self.ucans.len() + } + pub fn is_empty(&self) -> bool { self.ucans.is_empty() // FIXME acocunt for revocations? } @@ -99,10 +106,6 @@ impl, C: Codec + TryFrom + Into> } } -use crate::delegation; -use libipld_core::codec::Encode; -use libipld_core::ipld::Ipld; - // FIXME check that UCAN is valid impl< DID: Did + Ord + Clone, @@ -117,7 +120,12 @@ where type DelegationStoreError = String; // FIXME misisng fn get(&self, cid: &Cid) -> Result<&Delegation, Self::DelegationStoreError> { - self.ucans.get(cid).ok_or("nope".into()) // FIXME + dbg!("@@@@@@@@@@@@@@@"); + dbg!(cid); + self.ucans + .get(cid) + .ok_or(format!("not found in delegation memstore: {:?}", cid).into()) + // FIXME } fn insert( @@ -264,10 +272,66 @@ mod tests { invocation::promise::{CantResolve, Resolvable}, ipld, }; - use libipld_core::ipld::Ipld; + use crate::{ + crypto::varsig, + invocation::{payload::ValidationError, Agent}, + }; + use libipld_core::{cid::Cid, ipld::Ipld}; use rand::thread_rng; + use std::time::SystemTime; use testresult::TestResult; + #[derive(Debug, Clone, PartialEq)] + pub struct AccountManage; + + impl Command for AccountManage { + const COMMAND: &'static str = "/account/info"; + } + + impl From for Named { + fn from(_: AccountManage) -> Self { + Default::default() + } + } + + impl TryFrom> for AccountManage { + type Error = (); + + fn try_from(args: Named) -> Result { + if args == Default::default() { + Ok(AccountManage) + } else { + Err(()) + } + } + } + + impl From for Named { + fn from(_: AccountManage) -> Self { + Default::default() + } + } + + impl TryFrom> for AccountManage { + type Error = (); + + fn try_from(args: Named) -> Result { + if args == Default::default() { + Ok(AccountManage) + } else { + Err(()) + } + } + } + + impl Resolvable for AccountManage { + type Promised = AccountManage; + + fn try_resolve(promised: Self::Promised) -> Result> { + Ok(promised) + } + } + #[test_log::test] fn test_powerbox_ucan_resource() -> TestResult { let server_sk = ed25519_dalek::SigningKey::generate(&mut thread_rng()); @@ -306,6 +370,9 @@ mod tests { }, ); + let mut inv_store = crate::invocation::store::MemoryStore::default(); + let mut del_store = crate::delegation::store::MemoryStore::default(); + // 1. account -*-> server // 2. server -a-> device // 3. dnslink -d-> account @@ -350,70 +417,6 @@ mod tests { .build()?, )?; - #[derive(Debug, Clone, PartialEq)] - pub struct AccountManage; - - impl Command for AccountManage { - const COMMAND: &'static str = "/account/info"; - } - - impl From for Named { - fn from(_: AccountManage) -> Self { - Default::default() - } - } - - impl TryFrom> for AccountManage { - type Error = (); - - fn try_from(args: Named) -> Result { - if args == Default::default() { - Ok(AccountManage) - } else { - Err(()) - } - } - } - - impl From for Named { - fn from(_: AccountManage) -> Self { - Default::default() - } - } - - impl TryFrom> for AccountManage { - type Error = (); - - fn try_from(args: Named) -> Result { - if args == Default::default() { - Ok(AccountManage) - } else { - Err(()) - } - } - } - - impl Resolvable for AccountManage { - type Promised = AccountManage; - - fn try_resolve(promised: Self::Promised) -> Result> { - Ok(promised) - } - } - - // 4. [dnslink -d-> account -*-> server -a-> device] - let account_invocation = crate::Invocation::try_sign( - &device_signer, - varsig_header.clone(), - crate::invocation::PayloadBuilder::default() - .subject(account.clone()) - .issuer(device.clone()) - .audience(Some(server.clone())) - .ability(AccountManage) - .proofs(vec![]) // FIXME - .build()?, - )?; - // FIXME reenable // let dnslink_invocation = crate::Invocation::try_sign( // &device, @@ -428,30 +431,26 @@ mod tests { // ) // .expect("FIXME"); - use crate::crypto::varsig; - - let mut store: crate::delegation::store::MemoryStore< - crate::did::preset::Verifier, - varsig::header::Preset, - varsig::encoding::Preset, - > = Default::default(); + // let mut store: crate::delegation::store::MemoryStore< + // crate::did::preset::Verifier, + // varsig::header::Preset, + // varsig::encoding::Preset, + // > = Default::default(); // let del_agent = crate::delegation::Agent::new(&server, &server_signer, &mut store); - let _ = store.insert( - account_device_ucan.cid().expect("FIXME"), - account_device_ucan.clone(), - ); - - let _ = store.insert(account_pbox.cid().expect("FIXME"), account_pbox.clone()); - let _ = store.insert(dnslink_ucan.cid().expect("FIXME"), dnslink_ucan.clone()); + drop(del_store.insert(account_device_ucan.cid()?, account_device_ucan.clone())); + drop(del_store.insert(account_pbox.cid()?, account_pbox.clone())); + drop(del_store.insert(dnslink_ucan.cid()?, dnslink_ucan.clone())); - use std::time::SystemTime; + let proofs_for_powerline: Vec = del_store + .get_chain(&device, &None, "/".into(), vec![], SystemTime::now())? + .ok_or("FIXME")? + .iter() + .map(|x| x.0.clone()) + .collect(); - let chain_for_powerline = - store.get_chain(&device, &None, "/".into(), vec![], SystemTime::now()); - - let chain_for_dnslink = store.get_chain( + let chain_for_dnslink = del_store.get_chain( &device, &Some(dnslink.clone()), "/".into(), @@ -459,25 +458,32 @@ mod tests { SystemTime::now(), ); - use crate::invocation::Agent; + // 4. [dnslink -d-> account -*-> server -a-> device] + let account_invocation = crate::Invocation::try_sign( + &device_signer, + varsig_header.clone(), + crate::invocation::PayloadBuilder::default() + .subject(account.clone()) + .issuer(device.clone()) + .audience(Some(server.clone())) + .ability(AccountManage) + .proofs(proofs_for_powerline.clone()) + .build()?, + )?; - let powerline_len = chain_for_powerline.expect("FIXME").unwrap().len(); + let powerline_len = proofs_for_powerline.len(); let dnslink_len = chain_for_dnslink.expect("FIXME").unwrap().len(); assert_eq!((powerline_len, dnslink_len), (3, 3)); // FIXME - let mut inv_store = crate::invocation::store::MemoryStore::default(); - let mut del_store = crate::delegation::store::MemoryStore::default(); - let mut agent: Agent< '_, - crate::invocation::store::MemoryStore, - crate::delegation::store::MemoryStore, + &mut crate::invocation::store::MemoryStore, + &mut crate::delegation::store::MemoryStore, AccountManage, > = Agent::new(&server, &server_signer, &mut inv_store, &mut del_store); - let observed = agent.receive(account_invocation); - + let observed = agent.receive(account_invocation.clone()); dbg!(&observed); assert!(observed.is_ok()); @@ -493,10 +499,11 @@ mod tests { .build()?, )?; - dbg!(not_account_invocation.clone()); - let observed_other = agent.receive(not_account_invocation); - assert!(observed_other.is_err()); + assert_eq!( + observed_other.unwrap_err().as_validation_error(), + Some(&ValidationError::DidNotTerminateInSubject) + ); Ok(()) } diff --git a/src/delegation/store/traits.rs b/src/delegation/store/traits.rs index c6e91443..76c8f85a 100644 --- a/src/delegation/store/traits.rs +++ b/src/delegation/store/traits.rs @@ -8,14 +8,7 @@ use nonempty::NonEmpty; use std::fmt::Debug; use web_time::SystemTime; -pub trait Store< - - - - DID: Did = crate::did::preset::Verifier, - V: varsig::Header = varsig::header::Preset, - Enc: Codec + Into + TryFrom = varsig::encoding::Preset, - > { +pub trait Store, Enc: Codec + TryFrom + Into> { type DelegationStoreError: Debug; fn get(&self, cid: &Cid) -> Result<&Delegation, Self::DelegationStoreError>; @@ -57,9 +50,41 @@ pub trait Store< cids: &[Cid], ) -> Result>, Self::DelegationStoreError> { cids.iter().try_fold(vec![], |mut acc, cid| { - let d: &Delegation = self.get(cid)?; - acc.push(d); + acc.push(self.get(cid)?); Ok(acc) }) } } + +impl, DID: Did, V: varsig::Header, C: Codec + TryFrom + Into> + Store for &mut T +{ + type DelegationStoreError = >::DelegationStoreError; + + fn get(&self, cid: &Cid) -> Result<&Delegation, Self::DelegationStoreError> { + (**self).get(cid) + } + + fn insert( + &mut self, + cid: Cid, + delegation: Delegation, + ) -> Result<(), Self::DelegationStoreError> { + (**self).insert(cid, delegation) + } + + fn revoke(&mut self, cid: Cid) -> Result<(), Self::DelegationStoreError> { + (**self).revoke(cid) + } + + fn get_chain( + &self, + audience: &DID, + subject: &Option, + command: String, + policy: Vec, + now: SystemTime, + ) -> Result)>>, Self::DelegationStoreError> { + (**self).get_chain(audience, subject, command, policy, now) + } +} diff --git a/src/did/preset.rs b/src/did/preset.rs index cdc52528..0b590412 100644 --- a/src/did/preset.rs +++ b/src/did/preset.rs @@ -31,7 +31,7 @@ pub enum Signer { impl Did for Verifier { type Signature = key::Signature; - type Signer = Signer; + type Signer = self::Signer; } impl TryFrom for Verifier { diff --git a/src/invocation.rs b/src/invocation.rs index 0c48387d..d610fbe3 100644 --- a/src/invocation.rs +++ b/src/invocation.rs @@ -12,8 +12,8 @@ //! - [`Agent`] is a high-level interface for sessions that will involve more than one invoctaion. //! - [`store`] is an interface for caching [`Invocation`]s. -mod agent; -mod payload; +pub mod agent; +pub mod payload; pub mod promise; pub mod store; diff --git a/src/invocation/agent.rs b/src/invocation/agent.rs index 899d5f7b..825c7a4f 100644 --- a/src/invocation/agent.rs +++ b/src/invocation/agent.rs @@ -16,6 +16,7 @@ use crate::{ did::{self, Did}, time::Timestamp, }; +use enum_as_inner::EnumAsInner; use libipld_core::{ cid::Cid, codec::{Codec, Encode}, @@ -43,10 +44,10 @@ pub struct Agent< pub did: &'a DID, /// A [`delegation::Store`][delegation::store::Store]. - pub delegation_store: &'a mut D, + pub delegation_store: D, /// A [`Store`][Store] for the agent's [`Invocation`]s. - pub invocation_store: &'a mut S, + pub invocation_store: S, signer: &'a ::Signer, marker: PhantomData<(T, V, C)>, @@ -70,8 +71,8 @@ where pub fn new( did: &'a DID, signer: &'a ::Signer, - invocation_store: &'a mut S, - delegation_store: &'a mut D, + invocation_store: S, + delegation_store: D, ) -> Self { Self { did, @@ -96,7 +97,7 @@ where ) -> Result, InvokeError> { let proofs = self .delegation_store - .get_chain(self.did, &Some(subject.clone()), "/".into(), vec![], now) // FIXME + .get_chain(&self.did, &Some(subject.clone()), "/".into(), vec![], now) // FIXME .map_err(InvokeError::DelegationStoreError)? .map(|chain| chain.map(|(cid, _)| cid).into()) .unwrap_or(vec![]); @@ -116,7 +117,7 @@ where issued_at, }; - Ok(Invocation::try_sign(self.signer, varsig_header, payload) + Ok(Invocation::try_sign(&self.signer, varsig_header, payload) .map_err(InvokeError::SignError)?) } @@ -201,12 +202,10 @@ where .get_many(&invocation.proofs()) .map_err(ReceiveError::DelegationStoreError)? .iter() - .fold(vec![], |mut acc, d| { - acc.push(&d.payload); - acc - }); + .map(|d| &d.payload) + .collect(); - &invocation + let _ = &invocation .payload .check(proof_payloads, now) .map_err(ReceiveError::ValidationError)?; @@ -265,7 +264,7 @@ where // } } -#[derive(Debug)] +#[derive(Debug, PartialEq, Clone, EnumAsInner)] pub enum Recipient { // FIXME change to status? You(T), @@ -273,7 +272,7 @@ pub enum Recipient { Unresolved(Cid), } -#[derive(Debug, Error)] +#[derive(Debug, Error, EnumAsInner)] pub enum ReceiveError< T, DID: Did, @@ -315,66 +314,60 @@ mod tests { use rand::thread_rng; use testresult::TestResult; - // fn setup<'a>( - // inv_store: &'a mut crate::invocation::store::MemoryStore, - // del_store: &'a mut crate::delegation::store::MemoryStore, - // ) -> ( - // crate::did::preset::Verifier, - // crate::did::preset::Signer, - // Agent<'a, crate::invocation::store::MemoryStore, crate::delegation::store::MemoryStore>, - // ) { - // let server_sk = ed25519_dalek::SigningKey::generate(&mut thread_rng()); - // let server_signer = - // crate::did::preset::Signer::Key(crate::did::key::Signer::EdDsa(server_sk.clone())); - - // let server = crate::did::preset::Verifier::Key(crate::did::key::Verifier::EdDsa( - // server_sk.verifying_key(), - // )); - - // let agent = - // crate::invocation::agent::Agent::new(&server, &server_signer, inv_store, del_store); - - // (server, server_signer, agent) - // } - - // mod receive { - // use super::*; - // use crate::ability::crud::{read::Read, Crud}; - // use crate::ability::preset::Preset; - // use crate::crypto::varsig; - - // #[test_log::test] - // fn test_happy_path() -> TestResult { - // let mut inv_store = crate::invocation::store::MemoryStore::default(); - // let mut del_store = crate::delegation::store::MemoryStore::default(); - - // let (_, _, mut agent) = setup(&mut inv_store, &mut del_store); - // let invocation = agent.invoke( - // None, - // agent.did.clone(), - // // FIXME flatten - // Preset::Crud(Crud::Read(Read { - // path: None, - // args: None, - // })), - // BTreeMap::new(), - // None, - // None, - // None, - // SystemTime::now(), - // varsig::header::Preset::EdDsa(varsig::header::EdDsaHeader { - // codec: varsig::encoding::Preset::DagCbor, - // }), - // )?; - - // let unknown_sk = ed25519_dalek::SigningKey::generate(&mut thread_rng()); - // let unknown_did = crate::did::preset::Verifier::Key(crate::did::key::Verifier::EdDsa( - // unknown_sk.verifying_key(), - // )); - - // agent.receive(invocation)?; - - // Ok(()) - // } - // } + fn setup_agent<'a>( + did: &'a crate::did::preset::Verifier, + signer: &'a crate::did::preset::Signer, + ) -> Agent<'a, crate::invocation::store::MemoryStore, crate::delegation::store::MemoryStore> + { + let inv_store = crate::invocation::store::MemoryStore::default(); + let del_store = crate::delegation::store::MemoryStore::default(); + + crate::invocation::agent::Agent::new(did, signer, inv_store, del_store) + } + + mod receive { + use super::*; + use crate::ability::crud::{read::Read, Crud}; + use crate::ability::preset::Preset; + use crate::crypto::varsig; + + #[test_log::test] + fn test_happy_path() -> TestResult { + let server_sk = ed25519_dalek::SigningKey::generate(&mut thread_rng()); + let server_signer = + crate::did::preset::Signer::Key(crate::did::key::Signer::EdDsa(server_sk.clone())); + + let server = crate::did::preset::Verifier::Key(crate::did::key::Verifier::EdDsa( + server_sk.verifying_key(), + )); + + let mut agent = setup_agent(&server, &server_signer); + let invocation = agent.invoke( + None, + agent.did.clone(), + // FIXME flatten + Preset::Crud(Crud::Read(Read { + path: None, + args: None, + })), + BTreeMap::new(), + None, + None, + None, + SystemTime::now(), + varsig::header::Preset::EdDsa(varsig::header::EdDsaHeader { + codec: varsig::encoding::Preset::DagCbor, + }), + )?; + + let unknown_sk = ed25519_dalek::SigningKey::generate(&mut thread_rng()); + let unknown_did = crate::did::preset::Verifier::Key(crate::did::key::Verifier::EdDsa( + unknown_sk.verifying_key(), + )); + + agent.receive(invocation)?; + + Ok(()) + } + } } diff --git a/src/invocation/payload.rs b/src/invocation/payload.rs index 5932b0e4..1c649909 100644 --- a/src/invocation/payload.rs +++ b/src/invocation/payload.rs @@ -166,17 +166,19 @@ impl Payload { let (final_iss, vias) = proofs.into_iter().try_fold( (&self.issuer, BTreeSet::new()), |(iss, mut vias), proof| { + dbg!("$$$$$$$$$$$$$$"); + dbg!(proof.audience.to_string(), iss.to_string()); if *iss != proof.audience { - return Err(ValidationError::InvalidSubject.into()); + return Err(ValidationError::MisalignedIssAud.into()); } if let Some(proof_subject) = &proof.subject { if self.subject != *proof_subject { - return Err(ValidationError::MisalignedIssAud.into()); + return Err(ValidationError::InvalidSubject.into()); } } - if SystemTime::from(proof.expiration.clone()) > *now { + if SystemTime::from(proof.expiration.clone()) < *now { return Err(ValidationError::Expired.into()); } diff --git a/src/invocation/store.rs b/src/invocation/store.rs index a9e76b13..b7acbd36 100644 --- a/src/invocation/store.rs +++ b/src/invocation/store.rs @@ -6,24 +6,18 @@ use crate::{crypto::varsig, did::Did}; use libipld_core::{cid::Cid, codec::Codec}; use std::{collections::BTreeMap, convert::Infallible}; -pub trait Store< - T = crate::ability::preset::Preset, - DID: Did = crate::did::preset::Verifier, - V: varsig::Header = varsig::header::Preset, - Enc: Codec + Into + TryFrom = varsig::encoding::Preset, -> -{ +pub trait Store, C: Codec + Into + TryFrom> { type InvocationStoreError; fn get( &self, cid: Cid, - ) -> Result>, Self::InvocationStoreError>; + ) -> Result>, Self::InvocationStoreError>; fn put( &mut self, cid: Cid, - invocation: Invocation, + invocation: Invocation, ) -> Result<(), Self::InvocationStoreError>; fn has(&self, cid: Cid) -> Result { @@ -31,6 +25,33 @@ pub trait Store< } } +impl< + S: Store, + T, + DID: Did, + V: varsig::Header, + C: Codec + Into + TryFrom, + > Store for &mut S +{ + type InvocationStoreError = >::InvocationStoreError; + + fn get( + &self, + cid: Cid, + ) -> Result>, >::InvocationStoreError> + { + (*self).get(cid) + } + + fn put( + &mut self, + cid: Cid, + invocation: Invocation, + ) -> Result<(), >::InvocationStoreError> { + (*self).put(cid, invocation) + } +} + #[derive(Debug, Clone, PartialEq)] pub struct MemoryStore< T = crate::ability::preset::Preset, From 42358bb35e79d5f8c82a53de1bb8e69f7bbf12d9 Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Fri, 15 Mar 2024 13:04:11 -0700 Subject: [PATCH 152/188] Fix test --- src/delegation/store/memory.rs | 12 +++++++----- src/invocation/payload.rs | 2 -- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/delegation/store/memory.rs b/src/delegation/store/memory.rs index f4c1937d..7bab0c6e 100644 --- a/src/delegation/store/memory.rs +++ b/src/delegation/store/memory.rs @@ -120,8 +120,6 @@ where type DelegationStoreError = String; // FIXME misisng fn get(&self, cid: &Cid) -> Result<&Delegation, Self::DelegationStoreError> { - dbg!("@@@@@@@@@@@@@@@"); - dbg!(cid); self.ucans .get(cid) .ok_or(format!("not found in delegation memstore: {:?}", cid).into()) @@ -463,11 +461,16 @@ mod tests { &device_signer, varsig_header.clone(), crate::invocation::PayloadBuilder::default() - .subject(account.clone()) + .subject(dnslink.clone()) .issuer(device.clone()) .audience(Some(server.clone())) .ability(AccountManage) - .proofs(proofs_for_powerline.clone()) + .proofs(vec![ + account_device_ucan.cid()?, + account_pbox.cid()?, + dnslink_ucan.cid()?, + ]) + // .proofs(proofs_for_powerline.clone()) .build()?, )?; @@ -484,7 +487,6 @@ mod tests { > = Agent::new(&server, &server_signer, &mut inv_store, &mut del_store); let observed = agent.receive(account_invocation.clone()); - dbg!(&observed); assert!(observed.is_ok()); let not_account_invocation = crate::Invocation::try_sign( diff --git a/src/invocation/payload.rs b/src/invocation/payload.rs index 1c649909..b1256fc5 100644 --- a/src/invocation/payload.rs +++ b/src/invocation/payload.rs @@ -166,8 +166,6 @@ impl Payload { let (final_iss, vias) = proofs.into_iter().try_fold( (&self.issuer, BTreeSet::new()), |(iss, mut vias), proof| { - dbg!("$$$$$$$$$$$$$$"); - dbg!(proof.audience.to_string(), iss.to_string()); if *iss != proof.audience { return Err(ValidationError::MisalignedIssAud.into()); } From fa90a81f4e500e6795c26e6bb81411736ccb0dd8 Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Fri, 15 Mar 2024 14:16:56 -0700 Subject: [PATCH 153/188] Plugging little edge cases in invocation agent flow --- src/ability/crud.rs | 24 +++++ src/ability/preset.rs | 9 ++ src/delegation/store/memory.rs | 5 +- src/delegation/store/traits.rs | 12 +++ src/invocation.rs | 8 ++ src/invocation/agent.rs | 154 ++++++++++++++++++++++++++++----- src/invocation/payload.rs | 6 ++ 7 files changed, 193 insertions(+), 25 deletions(-) diff --git a/src/ability/crud.rs b/src/ability/crud.rs index e43a47a8..7a47a611 100644 --- a/src/ability/crud.rs +++ b/src/ability/crud.rs @@ -81,6 +81,30 @@ impl From for arguments::Named { } } +impl From for Crud { + fn from(create: Create) -> Self { + Crud::Create(create) + } +} + +impl From for Crud { + fn from(read: Read) -> Self { + Crud::Read(read) + } +} + +impl From for Crud { + fn from(update: Update) -> Self { + Crud::Update(update) + } +} + +impl From for Crud { + fn from(destroy: Destroy) -> Self { + Crud::Destroy(destroy) + } +} + #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] pub enum PromisedCrud { Create(PromisedCreate), diff --git a/src/ability/preset.rs b/src/ability/preset.rs index 44bd43cf..8adec291 100644 --- a/src/ability/preset.rs +++ b/src/ability/preset.rs @@ -25,6 +25,15 @@ pub enum Preset { Wasm(wasm::Run), } +impl From for Preset +where + Crud: From, +{ + fn from(t: T) -> Self { + Preset::Crud(Crud::from(t)) + } +} + impl ToCommand for Preset { fn to_command(&self) -> String { match self { diff --git a/src/delegation/store/memory.rs b/src/delegation/store/memory.rs index 7bab0c6e..4638001d 100644 --- a/src/delegation/store/memory.rs +++ b/src/delegation/store/memory.rs @@ -263,6 +263,7 @@ where #[cfg(test)] mod tests { + use crate::invocation::{payload::ValidationError, Agent}; use crate::{ ability::{arguments::Named, command::Command}, crypto::signature::Envelope, @@ -270,10 +271,6 @@ mod tests { invocation::promise::{CantResolve, Resolvable}, ipld, }; - use crate::{ - crypto::varsig, - invocation::{payload::ValidationError, Agent}, - }; use libipld_core::{cid::Cid, ipld::Ipld}; use rand::thread_rng; use std::time::SystemTime; diff --git a/src/delegation/store/traits.rs b/src/delegation/store/traits.rs index 76c8f85a..3bd7b1fb 100644 --- a/src/delegation/store/traits.rs +++ b/src/delegation/store/traits.rs @@ -33,6 +33,18 @@ pub trait Store, Enc: Codec + TryFrom + In now: SystemTime, ) -> Result)>>, Self::DelegationStoreError>; + fn get_chain_cids( + &self, + audience: &DID, + subject: &Option, + command: String, + policy: Vec, + now: SystemTime, + ) -> Result>, Self::DelegationStoreError> { + self.get_chain(audience, subject, command, policy, now) + .map(|chain| chain.map(|chain| chain.map(|(cid, _)| cid))) + } + fn can_delegate( &self, issuer: DID, diff --git a/src/invocation.rs b/src/invocation.rs index d610fbe3..1fa3a899 100644 --- a/src/invocation.rs +++ b/src/invocation.rs @@ -101,6 +101,14 @@ where &self.payload.audience } + pub fn normalized_audience(&self) -> &DID { + if let Some(audience) = &self.payload.audience { + audience + } else { + &self.payload.subject + } + } + pub fn issuer(&self) -> &DID { &self.payload.issuer } diff --git a/src/invocation/agent.rs b/src/invocation/agent.rs index 825c7a4f..ca146080 100644 --- a/src/invocation/agent.rs +++ b/src/invocation/agent.rs @@ -6,6 +6,7 @@ use super::{ use crate::ability::arguments::Named; use crate::ability::command::ToCommand; use crate::ability::parse::ParseAbility; +use crate::invocation::payload::PayloadBuilder; use crate::{ ability::{self, arguments, parse::ParseAbilityError, ucan::revoke::Revoke}, crypto::{ @@ -210,7 +211,7 @@ where .check(proof_payloads, now) .map_err(ReceiveError::ValidationError)?; - Ok(if *invocation.audience() != Some(self.did.clone()) { + Ok(if invocation.normalized_audience() != self.did { Recipient::Other(invocation.payload) } else { Recipient::You(invocation.payload) @@ -311,9 +312,22 @@ pub enum InvokeError { #[cfg(test)] mod tests { use super::*; + use pretty_assertions as pretty; use rand::thread_rng; + use std::ops::Add; + use std::ops::Sub; + use std::time::Duration; use testresult::TestResult; + fn gen_did() -> (crate::did::preset::Verifier, crate::did::preset::Signer) { + let sk = ed25519_dalek::SigningKey::generate(&mut thread_rng()); + let verifier = + crate::did::preset::Verifier::Key(crate::did::key::Verifier::EdDsa(sk.verifying_key())); + let signer = crate::did::preset::Signer::Key(crate::did::key::Signer::EdDsa(sk)); + + (verifier, signer) + } + fn setup_agent<'a>( did: &'a crate::did::preset::Verifier, signer: &'a crate::did::preset::Signer, @@ -325,48 +339,146 @@ mod tests { crate::invocation::agent::Agent::new(did, signer, inv_store, del_store) } + fn setup_valid_time() -> (Timestamp, Timestamp, Timestamp) { + let now = SystemTime::UNIX_EPOCH.add(Duration::from_secs(60 * 60 * 24 * 30)); + let exp = now.add(std::time::Duration::from_secs(60)); + let nbf = now.sub(std::time::Duration::from_secs(60)); + + ( + nbf.try_into().expect("valid nbf time"), + now.try_into().expect("valid now time"), + exp.try_into().expect("valid exp time"), + ) + } + mod receive { use super::*; - use crate::ability::crud::{read::Read, Crud}; - use crate::ability::preset::Preset; + use crate::ability::crud::read::Read; use crate::crypto::varsig; #[test_log::test] - fn test_happy_path() -> TestResult { - let server_sk = ed25519_dalek::SigningKey::generate(&mut thread_rng()); - let server_signer = - crate::did::preset::Signer::Key(crate::did::key::Signer::EdDsa(server_sk.clone())); - - let server = crate::did::preset::Verifier::Key(crate::did::key::Verifier::EdDsa( - server_sk.verifying_key(), - )); - + fn test_invoker_is_sub_implicit_aud() -> TestResult { + let (_nbf, now, exp) = setup_valid_time(); + let (server, server_signer) = gen_did(); let mut agent = setup_agent(&server, &server_signer); + let invocation = agent.invoke( None, agent.did.clone(), - // FIXME flatten - Preset::Crud(Crud::Read(Read { + Read { path: None, args: None, - })), + } + .into(), BTreeMap::new(), None, + Some(exp.try_into()?), + Some(now.try_into()?), + now.into(), + varsig::header::Preset::EdDsa(varsig::header::EdDsaHeader { + codec: varsig::encoding::Preset::DagCbor, + }), + )?; + + let observed = agent.generic_receive(invocation.clone(), &now.into())?; + pretty::assert_eq!(observed, Recipient::You(invocation.payload)); + Ok(()) + } + + #[test_log::test] + fn test_invoker_is_sub_and_aud() -> TestResult { + let (_nbf, now, exp) = setup_valid_time(); + let (server, server_signer) = gen_did(); + let mut agent = setup_agent(&server, &server_signer); + + let invocation = agent.invoke( + Some(agent.did.clone()), + agent.did.clone(), + Read { + path: None, + args: None, + } + .into(), + BTreeMap::new(), None, + Some(exp.try_into()?), + Some(now.try_into()?), + now.into(), + varsig::header::Preset::EdDsa(varsig::header::EdDsaHeader { + codec: varsig::encoding::Preset::DagCbor, + }), + )?; + + let observed = agent.generic_receive(invocation.clone(), &now.into())?; + pretty::assert_eq!(observed, Recipient::You(invocation.payload)); + Ok(()) + } + + #[test_log::test] + fn test_other_recipient() -> TestResult { + let (_nbf, now, exp) = setup_valid_time(); + let (server, server_signer) = gen_did(); + let mut agent = setup_agent(&server, &server_signer); + + let (not_server, _) = gen_did(); + + let invocation = agent.invoke( + Some(not_server), + agent.did.clone(), + Read { + path: None, + args: None, + } + .into(), + BTreeMap::new(), None, - SystemTime::now(), + Some(exp.try_into()?), + Some(now.try_into()?), + now.into(), varsig::header::Preset::EdDsa(varsig::header::EdDsaHeader { codec: varsig::encoding::Preset::DagCbor, }), )?; - let unknown_sk = ed25519_dalek::SigningKey::generate(&mut thread_rng()); - let unknown_did = crate::did::preset::Verifier::Key(crate::did::key::Verifier::EdDsa( - unknown_sk.verifying_key(), - )); + let observed = agent.generic_receive(invocation.clone(), &now.into())?; + pretty::assert_eq!(observed, Recipient::Other(invocation.payload)); + Ok(()) + } - agent.receive(invocation)?; + #[test_log::test] + fn test_expired() -> TestResult { + let (past, now, _exp) = setup_valid_time(); + let (server, server_signer) = gen_did(); + let mut agent = setup_agent(&server, &server_signer); + + let (not_server, _) = gen_did(); + + let invocation = agent.invoke( + Some(not_server), + agent.did.clone(), + Read { + path: None, + args: None, + } + .into(), + BTreeMap::new(), + None, + Some(past.try_into()?), + Some(now.try_into()?), + now.into(), + varsig::header::Preset::EdDsa(varsig::header::EdDsaHeader { + codec: varsig::encoding::Preset::DagCbor, + }), + )?; + let observed = agent.generic_receive(invocation.clone(), &now.into()); + pretty::assert_eq!( + observed + .unwrap_err() + .as_validation_error() + .ok_or("not a validation error")?, + &ValidationError::Expired + ); Ok(()) } } diff --git a/src/invocation/payload.rs b/src/invocation/payload.rs index b1256fc5..ed0ad884 100644 --- a/src/invocation/payload.rs +++ b/src/invocation/payload.rs @@ -156,6 +156,12 @@ impl Payload { DID: Clone, arguments::Named: From, { + if let Some(ref exp) = self.expiration { + if SystemTime::from(exp.clone()) < *now { + return Err(ValidationError::Expired); + } + } + let args: arguments::Named = self.ability.clone().into(); let mut cmd = self.ability.to_command(); From 08f72df353e1848a100ee766a8a24916a5eba18f Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Fri, 15 Mar 2024 17:40:59 -0700 Subject: [PATCH 154/188] Manual test context --- Cargo.toml | 1 + src/crypto/varsig/header/preset.rs | 30 +++ src/delegation/store/memory.rs | 247 -------------------- src/invocation/agent.rs | 356 +++++++++++++++++++++++++++-- src/invocation/payload.rs | 12 +- 5 files changed, 375 insertions(+), 271 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 1415bdec..db8883df 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -85,6 +85,7 @@ wasm-bindgen-derive = "0.2" web-sys = { version = "0.3", features = ["Crypto", "CryptoKey", "CryptoKeyPair", "SubtleCrypto"] } [dev-dependencies] +assert_matches = "1.5" libipld = "0.16" pretty_assertions = "1.4" rand = "0.8" diff --git a/src/crypto/varsig/header/preset.rs b/src/crypto/varsig/header/preset.rs index e658a285..00bab1d8 100644 --- a/src/crypto/varsig/header/preset.rs +++ b/src/crypto/varsig/header/preset.rs @@ -13,6 +13,36 @@ pub enum Preset { // FIXME Es384 needs varsig specs } +impl From> for Preset { + fn from(ed: eddsa::EdDsaHeader) -> Self { + Preset::EdDsa(ed) + } +} + +impl From> for Preset { + fn from(rs256: rs256::Rs256Header) -> Self { + Preset::Rs256(rs256) + } +} + +impl From> for Preset { + fn from(rs512: rs512::Rs512Header) -> Self { + Preset::Rs512(rs512) + } +} + +impl From> for Preset { + fn from(es256: es256::Es256Header) -> Self { + Preset::Es256(es256) + } +} + +impl From> for Preset { + fn from(es256k: es256k::Es256kHeader) -> Self { + Preset::Es256k(es256k) + } +} + impl From for Vec { fn from(preset: Preset) -> Vec { match preset { diff --git a/src/delegation/store/memory.rs b/src/delegation/store/memory.rs index 4638001d..18d86103 100644 --- a/src/delegation/store/memory.rs +++ b/src/delegation/store/memory.rs @@ -260,250 +260,3 @@ where Ok(NonEmpty::from_vec(hypothesis_chain)) } } - -#[cfg(test)] -mod tests { - use crate::invocation::{payload::ValidationError, Agent}; - use crate::{ - ability::{arguments::Named, command::Command}, - crypto::signature::Envelope, - delegation::store::Store, - invocation::promise::{CantResolve, Resolvable}, - ipld, - }; - use libipld_core::{cid::Cid, ipld::Ipld}; - use rand::thread_rng; - use std::time::SystemTime; - use testresult::TestResult; - - #[derive(Debug, Clone, PartialEq)] - pub struct AccountManage; - - impl Command for AccountManage { - const COMMAND: &'static str = "/account/info"; - } - - impl From for Named { - fn from(_: AccountManage) -> Self { - Default::default() - } - } - - impl TryFrom> for AccountManage { - type Error = (); - - fn try_from(args: Named) -> Result { - if args == Default::default() { - Ok(AccountManage) - } else { - Err(()) - } - } - } - - impl From for Named { - fn from(_: AccountManage) -> Self { - Default::default() - } - } - - impl TryFrom> for AccountManage { - type Error = (); - - fn try_from(args: Named) -> Result { - if args == Default::default() { - Ok(AccountManage) - } else { - Err(()) - } - } - } - - impl Resolvable for AccountManage { - type Promised = AccountManage; - - fn try_resolve(promised: Self::Promised) -> Result> { - Ok(promised) - } - } - - #[test_log::test] - fn test_powerbox_ucan_resource() -> TestResult { - let server_sk = ed25519_dalek::SigningKey::generate(&mut thread_rng()); - let server_signer = - crate::did::preset::Signer::Key(crate::did::key::Signer::EdDsa(server_sk.clone())); - - let server = crate::did::preset::Verifier::Key(crate::did::key::Verifier::EdDsa( - server_sk.verifying_key(), - )); - - let account_sk = ed25519_dalek::SigningKey::generate(&mut thread_rng()); - let account = crate::did::preset::Verifier::Key(crate::did::key::Verifier::EdDsa( - account_sk.verifying_key(), - )); - let account_signer = - crate::did::preset::Signer::Key(crate::did::key::Signer::EdDsa(account_sk)); - - let dnslink_sk = ed25519_dalek::SigningKey::generate(&mut thread_rng()); - let dnslink = crate::did::preset::Verifier::Key(crate::did::key::Verifier::EdDsa( - dnslink_sk.verifying_key(), - )); - let dnslink_signer = - crate::did::preset::Signer::Key(crate::did::key::Signer::EdDsa(dnslink_sk)); - - let device_sk = ed25519_dalek::SigningKey::generate(&mut thread_rng()); - let device = crate::did::preset::Verifier::Key(crate::did::key::Verifier::EdDsa( - device_sk.verifying_key(), - )); - let device_signer = - crate::did::preset::Signer::Key(crate::did::key::Signer::EdDsa(device_sk)); - - // FIXME perhaps add this back upstream as a named const - let varsig_header = crate::crypto::varsig::header::Preset::EdDsa( - crate::crypto::varsig::header::EdDsaHeader { - codec: crate::crypto::varsig::encoding::Preset::DagCbor, - }, - ); - - let mut inv_store = crate::invocation::store::MemoryStore::default(); - let mut del_store = crate::delegation::store::MemoryStore::default(); - - // 1. account -*-> server - // 2. server -a-> device - // 3. dnslink -d-> account - // 4. [dnslink -d-> account -*-> server -a-> device] - - // 1. account -*-> server - let account_pbox = crate::Delegation::try_sign( - &account_signer, - varsig_header.clone(), - crate::delegation::PayloadBuilder::default() - .subject(None) - .issuer(account.clone()) - .audience(server.clone()) - .command("/".into()) - .expiration(crate::time::Timestamp::five_years_from_now()) - .build()?, - )?; - - // 2. server -a-> device - let account_device_ucan = crate::Delegation::try_sign( - &server_signer, - varsig_header.clone(), // FIXME can also put this on a builder - crate::delegation::PayloadBuilder::default() - .subject(None) // FIXME needs a sibject when we figure out powerbox - .issuer(server.clone()) - .audience(device.clone()) - .command("/".into()) - .expiration(crate::time::Timestamp::five_years_from_now()) - .build()?, // I don't love this is now failable - )?; - - // 3. dnslink -d-> account - let dnslink_ucan = crate::Delegation::try_sign( - &dnslink_signer, - varsig_header.clone(), - crate::delegation::PayloadBuilder::default() - .subject(Some(dnslink.clone())) - .issuer(dnslink.clone()) - .audience(account.clone()) - .command("/".into()) - .expiration(crate::time::Timestamp::five_years_from_now()) - .build()?, - )?; - - // FIXME reenable - // let dnslink_invocation = crate::Invocation::try_sign( - // &device, - // varsig_header, - // crate::invocation::PayloadBuilder::default() - // .subject(dnslink) - // .issuer(device) - // .audience(Some(server)) - // .ability(DnsLinkUpdate { cid: todo!() }) - // .build() - // .expect("FIXME"), - // ) - // .expect("FIXME"); - - // let mut store: crate::delegation::store::MemoryStore< - // crate::did::preset::Verifier, - // varsig::header::Preset, - // varsig::encoding::Preset, - // > = Default::default(); - - // let del_agent = crate::delegation::Agent::new(&server, &server_signer, &mut store); - - drop(del_store.insert(account_device_ucan.cid()?, account_device_ucan.clone())); - drop(del_store.insert(account_pbox.cid()?, account_pbox.clone())); - drop(del_store.insert(dnslink_ucan.cid()?, dnslink_ucan.clone())); - - let proofs_for_powerline: Vec = del_store - .get_chain(&device, &None, "/".into(), vec![], SystemTime::now())? - .ok_or("FIXME")? - .iter() - .map(|x| x.0.clone()) - .collect(); - - let chain_for_dnslink = del_store.get_chain( - &device, - &Some(dnslink.clone()), - "/".into(), - vec![], - SystemTime::now(), - ); - - // 4. [dnslink -d-> account -*-> server -a-> device] - let account_invocation = crate::Invocation::try_sign( - &device_signer, - varsig_header.clone(), - crate::invocation::PayloadBuilder::default() - .subject(dnslink.clone()) - .issuer(device.clone()) - .audience(Some(server.clone())) - .ability(AccountManage) - .proofs(vec![ - account_device_ucan.cid()?, - account_pbox.cid()?, - dnslink_ucan.cid()?, - ]) - // .proofs(proofs_for_powerline.clone()) - .build()?, - )?; - - let powerline_len = proofs_for_powerline.len(); - let dnslink_len = chain_for_dnslink.expect("FIXME").unwrap().len(); - - assert_eq!((powerline_len, dnslink_len), (3, 3)); // FIXME - - let mut agent: Agent< - '_, - &mut crate::invocation::store::MemoryStore, - &mut crate::delegation::store::MemoryStore, - AccountManage, - > = Agent::new(&server, &server_signer, &mut inv_store, &mut del_store); - - let observed = agent.receive(account_invocation.clone()); - assert!(observed.is_ok()); - - let not_account_invocation = crate::Invocation::try_sign( - &device_signer, - varsig_header, - crate::invocation::PayloadBuilder::default() - .subject(dnslink.clone()) - .issuer(server.clone()) - .audience(Some(device.clone())) - .ability(AccountManage) - .proofs(vec![]) // FIXME - .build()?, - )?; - - let observed_other = agent.receive(not_account_invocation); - assert_eq!( - observed_other.unwrap_err().as_validation_error(), - Some(&ValidationError::DidNotTerminateInSubject) - ); - - Ok(()) - } -} diff --git a/src/invocation/agent.rs b/src/invocation/agent.rs index ca146080..4235299e 100644 --- a/src/invocation/agent.rs +++ b/src/invocation/agent.rs @@ -179,13 +179,13 @@ where Payload: TryFrom>, Invocation: Clone + Encode, { - self.generic_receive(invocation, &SystemTime::now()) + self.generic_receive(invocation, SystemTime::now()) } pub fn generic_receive( &mut self, invocation: Invocation, - now: &SystemTime, + now: SystemTime, ) -> Result>, ReceiveError> where arguments::Named: From, @@ -194,6 +194,12 @@ where { let cid: Cid = invocation.cid().map_err(ReceiveError::EncodingError)?; + invocation + .validate_signature() + .map_err(ReceiveError::SigVerifyError)?; + + // FIXME validate signature directly in inv store + self.invocation_store .put(cid.clone(), invocation.clone()) .map_err(ReceiveError::InvocationStoreError)?; @@ -312,13 +318,76 @@ pub enum InvokeError { #[cfg(test)] mod tests { use super::*; + use crate::ability::crud::read::Read; + use crate::crypto::varsig; + use crate::crypto::varsig::encoding; + use crate::crypto::varsig::header; + use crate::invocation::{payload::ValidationError, Agent}; + use crate::{ + ability::{arguments::Named, command::Command}, + crypto::signature::Envelope, + delegation::store::Store, + invocation::promise::{CantResolve, Resolvable}, + ipld, + }; + use libipld_core::{cid::Cid, ipld::Ipld}; use pretty_assertions as pretty; use rand::thread_rng; - use std::ops::Add; - use std::ops::Sub; - use std::time::Duration; + use std::ops::{Add, Sub}; + use std::time::{Duration, SystemTime}; use testresult::TestResult; + #[derive(Debug, Clone, PartialEq)] + pub struct AccountManage; + + impl Command for AccountManage { + const COMMAND: &'static str = "/account/info"; + } + + impl From for Named { + fn from(_: AccountManage) -> Self { + Default::default() + } + } + + impl TryFrom> for AccountManage { + type Error = (); + + fn try_from(args: Named) -> Result { + if args == Default::default() { + Ok(AccountManage) + } else { + Err(()) + } + } + } + + impl From for Named { + fn from(_: AccountManage) -> Self { + Default::default() + } + } + + impl TryFrom> for AccountManage { + type Error = (); + + fn try_from(args: Named) -> Result { + if args == Default::default() { + Ok(AccountManage) + } else { + Err(()) + } + } + } + + impl Resolvable for AccountManage { + type Promised = AccountManage; + + fn try_resolve(promised: Self::Promised) -> Result> { + Ok(promised) + } + } + fn gen_did() -> (crate::did::preset::Verifier, crate::did::preset::Signer) { let sk = ed25519_dalek::SigningKey::generate(&mut thread_rng()); let verifier = @@ -353,8 +422,7 @@ mod tests { mod receive { use super::*; - use crate::ability::crud::read::Read; - use crate::crypto::varsig; + use assert_matches::assert_matches; #[test_log::test] fn test_invoker_is_sub_implicit_aud() -> TestResult { @@ -380,7 +448,7 @@ mod tests { }), )?; - let observed = agent.generic_receive(invocation.clone(), &now.into())?; + let observed = agent.generic_receive(invocation.clone(), now.into())?; pretty::assert_eq!(observed, Recipient::You(invocation.payload)); Ok(()) } @@ -404,13 +472,14 @@ mod tests { Some(exp.try_into()?), Some(now.try_into()?), now.into(), - varsig::header::Preset::EdDsa(varsig::header::EdDsaHeader { - codec: varsig::encoding::Preset::DagCbor, + header::Preset::EdDsa(header::EdDsaHeader { + codec: encoding::Preset::DagCbor, }), )?; - let observed = agent.generic_receive(invocation.clone(), &now.into())?; + let observed = agent.generic_receive(invocation.clone(), now.into())?; pretty::assert_eq!(observed, Recipient::You(invocation.payload)); + Ok(()) } @@ -440,7 +509,7 @@ mod tests { }), )?; - let observed = agent.generic_receive(invocation.clone(), &now.into())?; + let observed = agent.generic_receive(invocation.clone(), now.into())?; pretty::assert_eq!(observed, Recipient::Other(invocation.payload)); Ok(()) } @@ -451,10 +520,8 @@ mod tests { let (server, server_signer) = gen_did(); let mut agent = setup_agent(&server, &server_signer); - let (not_server, _) = gen_did(); - let invocation = agent.invoke( - Some(not_server), + None, agent.did.clone(), Read { path: None, @@ -466,12 +533,13 @@ mod tests { Some(past.try_into()?), Some(now.try_into()?), now.into(), - varsig::header::Preset::EdDsa(varsig::header::EdDsaHeader { - codec: varsig::encoding::Preset::DagCbor, - }), + header::EdDsaHeader { + codec: encoding::Preset::DagCbor, + } + .into(), )?; - let observed = agent.generic_receive(invocation.clone(), &now.into()); + let observed = agent.generic_receive(invocation.clone(), now.into()); pretty::assert_eq!( observed .unwrap_err() @@ -481,5 +549,255 @@ mod tests { ); Ok(()) } + + #[test_log::test] + fn test_invalid_sig() -> TestResult { + let (_past, now, _exp) = setup_valid_time(); + let (server, server_signer) = gen_did(); + let mut agent = setup_agent(&server, &server_signer); + + let mut invocation = agent.invoke( + None, + agent.did.clone(), + Read { + path: None, + args: None, + } + .into(), + BTreeMap::new(), + None, + None, + Some(now.try_into()?), + now.into(), + header::EdDsaHeader { + codec: encoding::Preset::DagCbor, + } + .into(), + )?; + + let (not_server, _) = gen_did(); + + invocation.payload.issuer = not_server.clone(); + invocation.payload.audience = Some(server.clone()); + invocation.payload.subject = not_server; + + let observed = agent.generic_receive(invocation, now.into()); + + assert_matches!( + observed, + Err(ReceiveError::SigVerifyError( + crate::crypto::signature::ValidateError::VerifyError(_) + )) + ); + + Ok(()) + } + } + + mod chain { + use super::*; + use assert_matches::assert_matches; + + struct Ctx { + varsig_header: crate::crypto::varsig::header::Preset, + powerline_len: usize, + dnslink_len: usize, + inv_store: crate::invocation::store::MemoryStore, + del_store: crate::delegation::store::MemoryStore, + account_invocation: Invocation, + server: crate::did::preset::Verifier, + server_signer: crate::did::preset::Signer, + device: crate::did::preset::Verifier, + device_signer: crate::did::preset::Signer, + dnslink: crate::did::preset::Verifier, + dnslink_signer: crate::did::preset::Signer, + } + + fn setup_test_chain() -> Result> { + let (_nbf, now, exp) = setup_valid_time(); + let (server, server_signer) = gen_did(); + let (account, account_signer) = gen_did(); + let (device, device_signer) = gen_did(); + let (dnslink, dnslink_signer) = gen_did(); + + let varsig_header = crate::crypto::varsig::header::Preset::EdDsa( + crate::crypto::varsig::header::EdDsaHeader { + codec: crate::crypto::varsig::encoding::Preset::DagCbor, + }, + ); + + let inv_store = crate::invocation::store::MemoryStore::default(); + let mut del_store = crate::delegation::store::MemoryStore::default(); + + // Scenario + // ======== + // + // Delegations + // 1. account -*-> server + // 2. server -a-> device + // 3. dnslink -d-> account + // + // Invocation + // 4. [dnslink -d-> account -*-> server -a-> device] + + // 1. account -*-> server + let account_pbox = crate::Delegation::try_sign( + &account_signer, + varsig_header.clone(), + crate::delegation::PayloadBuilder::default() + .subject(None) + .issuer(account.clone()) + .audience(server.clone()) + .command("/".into()) + .expiration(crate::time::Timestamp::five_years_from_now()) + .build()?, + )?; + + // 2. server -a-> device + let account_device_ucan = crate::Delegation::try_sign( + &server_signer, + varsig_header.clone(), // FIXME can also put this on a builder + crate::delegation::PayloadBuilder::default() + .subject(None) // FIXME needs a sibject when we figure out powerbox + .issuer(server.clone()) + .audience(device.clone()) + .command("/".into()) + .expiration(crate::time::Timestamp::five_years_from_now()) + .build()?, // I don't love this is now failable + )?; + + // 3. dnslink -d-> account + let dnslink_ucan = crate::Delegation::try_sign( + &dnslink_signer, + varsig_header.clone(), + crate::delegation::PayloadBuilder::default() + .subject(Some(dnslink.clone())) + .issuer(dnslink.clone()) + .audience(account.clone()) + .command("/".into()) + .expiration(crate::time::Timestamp::five_years_from_now()) + .build()?, + )?; + + drop(del_store.insert(account_device_ucan.cid()?, account_device_ucan.clone())); + drop(del_store.insert(account_pbox.cid()?, account_pbox.clone())); + drop(del_store.insert(dnslink_ucan.cid()?, dnslink_ucan.clone())); + + let proofs_for_powerline: Vec = del_store + .get_chain(&device, &None, "/".into(), vec![], SystemTime::now())? + .ok_or("FIXME")? + .iter() + .map(|x| x.0.clone()) + .collect(); + + let chain_for_dnslink = del_store.get_chain( + &device, + &Some(dnslink.clone()), + "/".into(), + vec![], + SystemTime::now(), + ); + + // 4. [dnslink -d-> account -*-> server -a-> device] + let account_invocation = crate::Invocation::try_sign( + &device_signer, + varsig_header.clone(), + crate::invocation::PayloadBuilder::default() + .subject(dnslink.clone()) + .issuer(device.clone()) + .audience(Some(server.clone())) + .ability(AccountManage) + .proofs(vec![ + account_device_ucan.cid()?, + account_pbox.cid()?, + dnslink_ucan.cid()?, + ]) + // .proofs(proofs_for_powerline.clone()) + .build()?, + )?; + + let powerline_len = proofs_for_powerline.len(); + let dnslink_len = chain_for_dnslink?.ok_or("FIXME")?.len(); + + Ok(Ctx { + varsig_header, + powerline_len, + dnslink_len, + inv_store, + del_store, + account_invocation, + server, + server_signer, + device, + device_signer, + dnslink, + dnslink_signer, + }) + } + + #[test_log::test] + fn test_chain_len() -> TestResult { + let ctx = setup_test_chain()?; + assert_eq!((ctx.powerline_len, ctx.dnslink_len), (3, 3)); + Ok(()) + } + + #[test_log::test] + fn test_chain_ok() -> TestResult { + let mut ctx = setup_test_chain()?; + + let mut agent: Agent< + '_, + &mut crate::invocation::store::MemoryStore, + &mut crate::delegation::store::MemoryStore, + AccountManage, + > = Agent::new( + &ctx.server, + &ctx.server_signer, + &mut ctx.inv_store, + &mut ctx.del_store, + ); + + let observed = agent.receive(ctx.account_invocation.clone()); + assert!(observed.is_ok()); + Ok(()) + } + + #[test_log::test] + fn test_chain_wrong_sub() -> TestResult { + let mut ctx = setup_test_chain()?; + + let mut agent: Agent< + '_, + &mut crate::invocation::store::MemoryStore, + &mut crate::delegation::store::MemoryStore, + AccountManage, + > = Agent::new( + &ctx.server, + &ctx.server_signer, + &mut ctx.inv_store, + &mut ctx.del_store, + ); + + let not_account_invocation = crate::Invocation::try_sign( + &ctx.device_signer, + ctx.varsig_header, + crate::invocation::PayloadBuilder::default() + .subject(ctx.dnslink.clone()) + .issuer(ctx.server.clone()) + .audience(Some(ctx.device.clone())) + .ability(AccountManage) + .proofs(vec![]) // FIXME + .build()?, + )?; + + let observed_other = agent.receive(not_account_invocation); + assert_eq!( + observed_other.unwrap_err().as_validation_error(), + Some(&ValidationError::DidNotTerminateInSubject) + ); + + Ok(()) + } } } diff --git a/src/invocation/payload.rs b/src/invocation/payload.rs index ed0ad884..a16c77fb 100644 --- a/src/invocation/payload.rs +++ b/src/invocation/payload.rs @@ -149,15 +149,17 @@ impl Payload { pub fn check( &self, proofs: Vec<&delegation::Payload>, - now: &SystemTime, + now: SystemTime, ) -> Result<(), ValidationError> where A: ToCommand + Clone, DID: Clone, arguments::Named: From, { - if let Some(ref exp) = self.expiration { - if SystemTime::from(exp.clone()) < *now { + let now_ts = Timestamp::postel(now); + + if let Some(exp) = self.expiration { + if exp < now_ts { return Err(ValidationError::Expired); } } @@ -182,12 +184,12 @@ impl Payload { } } - if SystemTime::from(proof.expiration.clone()) < *now { + if proof.expiration < now_ts { return Err(ValidationError::Expired.into()); } if let Some(nbf) = proof.not_before.clone() { - if SystemTime::from(nbf) > *now { + if nbf > now_ts { return Err(ValidationError::NotYetValid.into()); } } From 2cb9a703e66a980e6c1d222faec8d22981d670c1 Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Fri, 15 Mar 2024 17:47:20 -0700 Subject: [PATCH 155/188] Fixed test --- src/invocation/agent.rs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/invocation/agent.rs b/src/invocation/agent.rs index 4235299e..daf0ba2d 100644 --- a/src/invocation/agent.rs +++ b/src/invocation/agent.rs @@ -780,7 +780,7 @@ mod tests { ); let not_account_invocation = crate::Invocation::try_sign( - &ctx.device_signer, + &ctx.server_signer, ctx.varsig_header, crate::invocation::PayloadBuilder::default() .subject(ctx.dnslink.clone()) @@ -792,9 +792,11 @@ mod tests { )?; let observed_other = agent.receive(not_account_invocation); - assert_eq!( - observed_other.unwrap_err().as_validation_error(), - Some(&ValidationError::DidNotTerminateInSubject) + assert_matches!( + observed_other, + Err(ReceiveError::ValidationError( + ValidationError::DidNotTerminateInSubject + )) ); Ok(()) From de000f2516f9b876b8f3ebc3241ecb522755695d Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Fri, 15 Mar 2024 23:23:55 -0700 Subject: [PATCH 156/188] Don't mind me, just fighting with varint in did:key --- Cargo.toml | 2 +- flake.nix | 6 +- proptest-regressions/delegation/payload.txt | 7 + proptest-regressions/did/key/verifier.txt | 7 + src/ability/arguments/named.rs | 3 +- src/delegation/payload.rs | 246 +++++++++++++--- src/delegation/policy/predicate.rs | 14 +- src/delegation/policy/selector/select.rs | 36 ++- src/did/key/verifier.rs | 308 +++++++++++++------- src/did/preset.rs | 36 +++ src/invocation/agent.rs | 5 - src/ipld/number.rs | 5 +- src/lib.rs | 4 +- src/time/timestamp.rs | 7 +- 14 files changed, 501 insertions(+), 185 deletions(-) create mode 100644 proptest-regressions/delegation/payload.txt create mode 100644 proptest-regressions/did/key/verifier.txt diff --git a/Cargo.toml b/Cargo.toml index db8883df..770279b8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -36,11 +36,11 @@ path = "examples/counterparts.rs" aquamarine = { version = "0.5", optional = true } # Encoding +multibase = "0.9" base64 = "0.21" # Crypto blst = { version = "0.3.11", optional = true, default-features = false } -bs58 = "0.5" # Web Stack did_url = "0.1" diff --git a/flake.nix b/flake.nix index b0935515..925581d7 100644 --- a/flake.nix +++ b/flake.nix @@ -163,7 +163,7 @@ "${cargo} watch --clear --exec 'clippy -- -W clippy::pedantic'"; "watch:test:host" = cmd "Run all host tests on save" - "${cargo} watch --clear --exec 'test --features=mermaid_docs'"; + "${cargo} watch --clear --exec 'test --features=mermaid_docs,test_utils'"; "watch:test:wasm" = cmd "Run all Wasm tests on save" "${cargo} watch --clear --exec 'test --target=wasm32-unknown-unknown'"; @@ -174,7 +174,7 @@ "test:host && test:docs && test:wasm"; "test:host" = cmd "Run Cargo tests for host target" - "${cargo} test"; + "${cargo} test --features=test_utils"; "test:wasm" = cmd "Run wasm-pack tests on all targets" "test:wasm:node && test:wasm:chrome"; @@ -186,7 +186,7 @@ "${wasm-pack} test --headless --chrome"; "test:docs" = cmd "Run Cargo doctests" - "${cargo} test --doc --features=mermaid_docs"; + "${cargo} test --doc --features=mermaid_docs,test_utils"; }; docs = { diff --git a/proptest-regressions/delegation/payload.txt b/proptest-regressions/delegation/payload.txt new file mode 100644 index 00000000..a1f73406 --- /dev/null +++ b/proptest-regressions/delegation/payload.txt @@ -0,0 +1,7 @@ +# Seeds for failure cases proptest has generated in the past. It is +# automatically read and these particular cases re-run before any +# novel cases are generated. +# +# It is recommended to check this file in to source control so that +# everyone who runs the test benefits from these saved cases. +cc 2219b6f1cfd2b9c29cdae1174fd8633335cb25947b15ef61c3df44f2664175c3 # shrinks to payload = Payload { subject: None, issuer: Key(EdDsa(VerifyingKey(CompressedEdwardsY: [46, 111, 204, 227, 103, 1, 220, 121, 20, 136, 224, 208, 177, 116, 92, 193, 227, 58, 76, 28, 159, 204, 65, 198, 59, 211, 67, 219, 190, 9, 112, 230]), EdwardsPoint{ X: FieldElement51([1689611602193863, 490607132032821, 1343312146746774, 1090732682789050, 1815270510391065]), Y: FieldElement51([1127445621927726, 1752742079139643, 335263251657170, 455073811812238, 1802102173971517]), Z: FieldElement51([1, 0, 0, 0, 0]), T: FieldElement51([396516250482892, 1271770325328148, 2066179188049959, 970219954360817, 1259266248234093]) }))), audience: Key(EdDsa(VerifyingKey(CompressedEdwardsY: [46, 111, 204, 227, 103, 1, 220, 121, 20, 136, 224, 208, 177, 116, 92, 193, 227, 58, 76, 28, 159, 204, 65, 198, 59, 211, 67, 219, 190, 9, 112, 230]), EdwardsPoint{ X: FieldElement51([1689611602193863, 490607132032821, 1343312146746774, 1090732682789050, 1815270510391065]), Y: FieldElement51([1127445621927726, 1752742079139643, 335263251657170, 455073811812238, 1802102173971517]), Z: FieldElement51([1, 0, 0, 0, 0]), T: FieldElement51([396516250482892, 1271770325328148, 2066179188049959, 970219954360817, 1259266248234093]) }))), via: Some(Key(EdDsa(VerifyingKey(CompressedEdwardsY: [46, 111, 204, 227, 103, 1, 220, 121, 20, 136, 224, 208, 177, 116, 92, 193, 227, 58, 76, 28, 159, 204, 65, 198, 59, 211, 67, 219, 190, 9, 112, 230]), EdwardsPoint{ X: FieldElement51([1689611602193863, 490607132032821, 1343312146746774, 1090732682789050, 1815270510391065]), Y: FieldElement51([1127445621927726, 1752742079139643, 335263251657170, 455073811812238, 1802102173971517]), Z: FieldElement51([1, 0, 0, 0, 0]), T: FieldElement51([396516250482892, 1271770325328148, 2066179188049959, 970219954360817, 1259266248234093]) })))), command: "eâ'Ⱥ{Ⱥ𞹒/𖿠¦꧊=:Z꧉&ceG4ᅲQ&]%", policy: [And(LessThan(Select([Values]), Integer(-17977310508110653107821168443657725762)), GreaterThan(Select([ArrayIndex(-2063331637), Values, ArrayIndex(1729047197)]), Integer(-71863900340009931549720359637876494783))), LessThanOrEqual(Select([ArrayIndex(1500241979), ArrayIndex(-659046873), Field("`Ⴧ*ᧂ:𑛄&"), Values, Values, ArrayIndex(1750800327), Field("Ⱥd"), ArrayIndex(-1789308745)]), Integer(70158225305663682194208457939428168215)), LessThanOrEqual(Select([Values, Field("*🫁𝕄L𞸧/ຄ𑻤$u\"*𝼥m':ᨼ𑾰/\\"), ArrayIndex(-554915756)]), Float(9.08306675974206e-234))), Or(LessThanOrEqual(Select([Values]), Float(1.003428263794859e-307)), And(Equal(Select([ArrayIndex(-233587294), Field("𝒫𞻰& キ<&ஞ臨2m𞺣'𞋿&{\u{ac3}{D8𒑀𑰈5\u{1ac0}\""), Values, Values, Field("G.吸aj𑿤q^a9%.<\u{84}%.𝝹𔓶": true, "*E🕴5/.`\\x=\u{7a853}&\u{5fd16}𩟨\u{b}\u{b3012}\u{9e345}/A\u{1b}Dts&E{w}\u{d8473}": 51, ".Z\u{795c2}?*\u{bffa7}B¥-/\tb阂?": 50, ".\u{7f}": baguqegzax2jx7mre4zpv63vto6u3mtjy4ivcrpnsnrgfcb6bymbpbwz5ky2q, ".\u{80}%\u{b}$\u{c6cf9}=&&\u{4}↜$'N\u{7f}\u{b}2\u{2}?\u{65b02}Uð𫻓\u{b}\u{feff}": -1.3122304768215823e-86, ".𗨥Ø\u{7f}\u{fe136}g\u{880c8}0O\\è": false, "1={\u{4fc0c}/\u{37be6}+\u{73284}.{\r�腄/_%�?\r\u{b022e}H": false, "2𱟴.": null, "4'\u{70fad}\\�o{\u{ce77b}\u{7f}~\u{d4dac}\u{1b}V\u{b}𩱋�\t&\\\u{aee01}`🕴\u{ee7dd}": null, "5$w\u{df9bd}\\\u{6}\tѨ\u{1b}M\u{cd289}:U%\u{202e}I\u{ea2ae}\r\u{d0993}D\u{feff}Ñ": null, "5\u{db9ee}\u{feff}\"�Ⱥ🕴z\0v<<\"5": 0.0, ":": bafy2bzacecp4g47wlyry7zukgmgp6xoxsh5aqlhn2vtxbpk2j77pzmx5w5xci, "<\0\u{3fe95}\t=`Z𰲨pdËf\u{5}�\u{1b}30\u{a2f77}\t:|%\u{a65a3}": 25, "<\u{2}¥?\u{9ae9b}'?q\u{7f}\u{84177}/?d🕴\u{d0dee}_x\u{a73f9}=`/\"Ѩ¥+\u{1}\u{202e}+Ⱥ/": "%){\u{f34d0}\u{91bf2}{]¥?<\u{7e5a3}\\\u{7f}¥", "<\"k&🕴Q\u{a0cc1}\u{193c1}\u{6fef1}\u{57765}=\u{b04a9}\u{1ee38}*$i�趼\r:^�\t\u{2}/": null, "<'\u{202e}": [246, 133, 18, 125, 51, 172], "<*\r\u{feff}'\u{b}\u{feff},\t\u{202e}\u{b}": 1.9096030614441494e34, "\0#.\u{e50a7}`\u{6b781}\u{72c56}IѨTȺ:\u{202e}\u{c1d84}\u{d1ebf}F\u{bc6f1}P�\u{1b}}?d\u{feff}Ⱥ\u{96517}\u{7}": -21, ">\u{7f}h)\t&'\u{b}": "p$\u{ce2d9}T\t\u{6486c}\"\u{af416}", "?*=\u{eea8d}\u{a8f3d}\u{479bf}\\\u{577da}<Ѩ&\u{202e}\u{ceb29}\u{8}/\u{7f}\u{7f}𘂤*\u{91}\u{96d48}\u{202e}\u{60a35}6\u{7cd5b}\u{b}~7\u{feff}\u{fc2b3}\u{6ca08}": bafykbzacedzd5rmt4wtpaenao7lyjfstkk3babvem5zvxsx66esxsdwdmnuok, "?\u{f5061}\u{6}\u{adb94}\tz<\u{8e6f5}\t]\"\u{1b}\u{50c21}?=\u{feff}\"'\u{cdff0}{�\u{11b58}\u{7f}ñ¥'\"I\u{6efe5}\0": "M\u{d7240}\u{5fa2f}\"\t\\Ⱥ\u{8}\r𓂂E\u{615a1}$ :\0\u{4b6de}2�&::", "D\u{834c0}": bafybwictq6cimsvk5g36pcopei5vzwuqjgz6zab4qnlw6h6pexvenpnuz4, "G{\0P\u{202e}z?<*\u{e4d94}\u{caeb6}c\u{b}\"\u{d4fa0}\0\u{3d0e1}\u{c315a}": true, "O:{.:6\u{1}𒋵=": [215, 19, 250, 35, 145, 227, 89, 97, 226, 22, 100, 90, 212, 248, 231, 66, 1, 74, 222, 19, 252, 253, 50, 43, 95, 28, 217, 195, 43, 193, 37, 211, 223, 168, 162, 177, 117, 244, 235, 50, 129, 137, 255, 246], "P\u{7f}m[*^\u{d4b73}\u{a4603}\u{cae36}\u{202e}C\u{2}\u{99a81}/<": "\t=/\u{202e}{?=z𣿸&Ѩ\t\u{10fefb}:`\\\u{88684}ᇵ&<'IB9Ѩ", "Q=/\u{2}[W$🕴Ѩi`\u{6b1f0}g\0\u{202e}&äq\0\\\u{e7b6e}\u{7}2": 0.0, "S\u{feff}¥\u{363f5}\\�\"|/$\u{1b}🕴\u{d61d7}\u{9c5fc}\u{5}%": true, "\\": 1797410591.2173085, "\\'\\\u{72082}9\r\u{fd777}&'\u{8}-\t$Ѩ\u{85}&'\\$\u{202e}\u{4}\u{d59a8}𐋪": null, "\\*\t&>ó": [197, 70, 103, 161, 242, 103, 93, 85, 220, 20, 175, 146, 70, 248, 167, 201, 81, 105, 89, 137, 113, 31, 52, 197, 100, 44, 200, 169, 146, 8, 75, 185, 88, 27, 164, 65, 95, 19, 82, 178, 129, 49], "\\¥𧷋%\u{7f}/%": 5.3388979855107657e-222, "]\u{2},n{\t": 4.726007655014673e-268, "]\u{e5bc2}:\u{67d79}": [192, 72, 37, 153, 92, 243, 115, 45, 85, 213, 125, 24, 161, 32, 231, 155, 146, 251, 181, 211, 205, 114], "^": false, "`\"\rBbIU\u{b38e7}~{\u{1b}<": [166, 52, 57, 140, 164, 87, 17, 112, 52, 240, 111, 77, 237, 27, 236, 44, 43, 106, 52, 249, 5, 150, 136, 195, 238, 123], "`=": 37, "`=\0\u{ae1db}$'\u{b}<{\u{1}\u{2};": true, "`~HѨ\r=": null, "`\u{91304}8?=\u{456a5}?\u{ee878}`": [72, 35, 50, 245, 247, 42, 83, 78, 154, 184, 6, 235, 171, 159, 23, 112, 160, 167, 206, 73, 56, 6, 234, 198, 253, 149, 213, 217, 121, 184, 227, 205, 182, 89, 67, 116, 130, 153, 30, 111, 90, 28, 205, 170, 195, 105, 252, 13], "`\u{b62b4}l\rȺ\0䱮Ѩ\u{325fa}://%.H*\r4\u{67fc7}f\u{feff}\u{97c83}d_z𒋤𩞬<": "{\u{7}\u{1b}\u{1}\t\"Ѩr\u{5}", "`\u{db05f}\u{82068}%\u{e3259}\u{202e}&\u{8916b}r\u{b}\u{eda56}�7\u{7f}\u{800bc}": null, "a\u{7f}": -4.342818332244186e254, "e;='\u{66fb7}¦_.R\u{6}S\u{99a3c}1*'d�\u{aecf1}?¥:\u{cd4f6}🕴¥{\u{202e}V'": null, "i\u{7acc3}\0*\u{3a0a4};\"G\0x\u{202e}\u{202e}\u{a6ef1}\u{7}y\u{c4c0c}*=\u{202e}Ñ|]\u{b}`S�\u{63b03}&¦🕴|": "\u{9b}\u{feff}/\u{2}\u{32ba0}$5N\u{c18b1}\u{6e305}", "i\u{b88de}:%<:\u{b}i¥\\%&\u{e9081}\u{36b96}\\wa\u{53cd7}\u{7f}\u{441e2}": [254, 111, 111, 15, 235, 43, 5, 115, 184, 222, 104, 79, 29, 45, 167, 151, 205, 114, 64, 194, 79, 2, 66, 154, 153, 108, 151, 126, 250, 233, 134], "z": true, "{\u{7f}Ⱥ&��0a]\u{7f}\u{b3046}\u{2fa93}\u{7f}<`a\u{10b100}b\u{e36be}-:\u{b}}I\\\u{c99dc}(�\u{d4177}<": 5.69430214693307e-309, "{\u{90}�\r¥%`": null, "\u{7f}=n\r\u{b904f}m\\`&\u{8d}&\u{8}q\u{10fb4a}\u{202e}\u{feff}": 30, "\u{7f}\u{6677f}\"\t\t𥡏&\u{929fa}%\u{1b}": false, "¥\u{4b398}$\u{b}.\u{10edc3}.v\u{4}\u{c8f3b}`\u{1b}o\u{b}B�.\u{8a4e7}": true, "¦\u{567e2}\u{789fb}.\u{1}\u{bac03}FeLѨ": [46, 154, 121, 10, 113, 153, 177, 226, 108, 199, 94, 194, 182, 56, 189, 253, 144, 68, 220, 178, 227, 39, 5, 27, 46, 171, 2, 171, 100, 125, 136, 48, 185, 143, 175, 224, 38, 180, 89, 73, 253, 97, 152, 89, 126, 226, 69, 159, 39, 4, 5, 151, 166, 157, 129, 18, 74, 141, 30, 245, 18, 68, 59, 4, 84, 71, 127, 83, 157, 160, 70, 180, 19, 67, 237, 156, 119, 247, 69, 10, 220, 32, 51, 35], "Ê`/\u{e98d1}`8\u{5a946}¥\tѨ\\\u{71794}\u{1b}\u{5d467}\u{5f80a}\u{2}o\\\u{78e03}\u{1b}$V\0,Ⱥu<\t\u{4}": "l\u{80}/H", "Û\u{a246c}\0\u{cc1d1}\u{1b}": -2.3446004632097955e-204, "Ⱥ\u{48608}�\u{1}Ѩ\t\"\\\rRx\u{feff}𱋭🕴.>.\r?/Ѩe": bafybmibqonatpssw5xwj3ckqwghe3uv52fbojbwo6wevvgyxhhi6is34iy, "ЏѨ$N==jX\u{ef98d}?{Ⱥk\u{4c9b3}\u{a5c43}'\rx": true, "Ѩ?=Ⱥ\u{7f}\0🕴$�\u{39466}\u{1b}�\"`\0.\u{feff}*.x-\u{7f}EѨÔ\u{7}\u{10f98d}$P": false, "Ѩ뀳🕴\t\u{202e}<{\u{950c8}\0\u{6abe2}2¥G\0": bafkrmicucdpcfbvzkhxwm7wgcnbx7wbbsx7yf4xs3xnebcwqv4fzfnyeui, "\u{202e}\u{5}\u{f93a8}�\t\u{93751}DX.4\u{b}\u{47079}\u{cb546}Ê\u{3c9c6}('Q\u{df412}^(?%\u{1b}\u{d4087}\u{87657}\u{69291}": bafyr4igptyd3pys2jr5l7f6wly2ixknaqjhxa2goliyvnoxef4q5npn2wq, "\u{202e}//)H\u{489d0}\u{b}\"\u{7f}7\u{fd8c1}\u{52967}": -49, "퀹\u{1e584}Ⱥ\u{b5e48}?\u{d70a5}\u{202e}\u{feff}o&Ⱥ\t2&$TѨ\u{c7d9d}¥z9�#B,\u{7d2c6}\u{a9082}": -39, "�\u{b}\u{9b}:\u{afa9e}\u{8b}\"G%=\u{6}\u{b1e04}𫃏\u{202e}{:Ⱥ'\0[;\u{cbe4b}Ѩ¥V\r:e\r�\"\u{36274}{\"\u{feff}\t\u{1b},\\�鴇\\µ": "*\u{b2484}(j'0\\�\t\u{feff}~I*\u{1abf0}鲫!¥í\u{7f}\t\t\u{feff}", "\u{2}¥@@x\u{7}o<>\u{b}>�\"=\u{4a714}H\u{356a6}Y\u{feff}¥\0Ѩ`\u{8586b}�@\u{86433}8\u{b5eb0}U\t\u{928a7}?:3V\r\0\u{e963}\u{91606}\u{f5d9a}`&&\t\\{$&`": [203, 219, 129, 233, 167, 86, 133, 68, 30, 123, 72, 152, 245, 36, 18, 51, 184, 218, 78, 248, 74, 234, 85, 239, 225, 163, 68, 62, 10, 51, 41, 233, 241, 66, 225, 241, 213, 58, 199, 207, 196, 20, 9, 130, 162, 125, 157, 172, 66, 51, 176, 230, 83, 217, 243, 126, 106, 4, 227, 168, 130], "?\t#?\u{c8453}l<\u{ebb70}<\u{202e}\u{1}\u{4a0a6}\u{bb34d}𧆽{\"\u{202e}\u{a6e5e}<\u{2}$\u{1b}{\u{1b}\u{a7345}\u{bcba2}=\u{55f80}𠘓`": false, "@\0 \u{e2e8b}\u{feff}&{¸\u{1b}`Ã`<\u{c80a3}\"h%V\\:\r:{%\u{b}\u{91b96}\0": 16, "A\0\0%Ⱥ\u{7f}o6Ѩ\ra\t\u{fa6d1}": 0.0, "E5\u{3ef04}\\iѨ4==\u{ad632}\u{103a8b}\0\u{882ea}\u{3fb8f}\u{80a35}{\r\"\u{9afa0}\\<3*\u{e368f}\"{Ѩ\"\u{85b40}/": [62, 206, 252, 139, 165, 123, 83, 85], "K?\u{15ea9}%/Ⱥ\u{39c0e}Ѩ\tM/\u{806b5}𣎂+": "\u{7b58c}\u{eb27}", "Py/\u{eeae7}\t:z/u&\u{fdecd}\\`<": 2, "P🕴EU\t$\0V\"*": true, "S\u{6}?'{¦~:¥\"\u{797af}:Ⱥ": false, "W/\u{7d22d}\u{7f}k\u{6f5bc}\"/\u{7ac51}\u{feff}\r\u{1b}\u{b}`\r\u{16d8a}\"\u{feff}\u{4e68c}": null}, 3.7320199453131945e18, true, "\td", -2.1302548429054803e-66, [9, 130, 254, 29, 224, 58, 3, 226, 45, 154, 180, 58, 156, 107, 43, 160], [184, 58, 122, 21, 191, 153, 91, 193, 125, 41, 253, 9, 18, 17, 100, 75, 60, 215, 91, 98, 131, 82, 225, 220, 226, 154, 246, 54, 253, 108, 18, 104, 142, 238, 92, 249, 74, 194, 85, 60, 101, 114, 120, 130, 76, 198, 254, 54, 43, 192, 5], -3.035213207805024e-151, true, null, true, [144, 5, 247, 45, 158, 101, 252, 143, 38, 225, 226, 145, 14, 34, 204, 157, 201, 182, 152, 54, 29, 245, 248, 233, 203, 103, 50], 7.126162207025803e98, 0.0, 38, 44, false, 1.442945330979412e-308, -3.2821103465088214e185, false, -0.0])), GreaterThanOrEqual(Select([Field("\u{dca}?i%ѨѨ<\\{Ѩৡ=ᨁr\"T\u{1a7f}🛞{༴Ⱥ:ᛧ"), Values, Values, ArrayIndex(-206845918), ArrayIndex(284566073)]), Integer(-130442174005234510445148006169593931048)))), Every(Select([Values, ArrayIndex(-1235287252), ArrayIndex(1235974543), Field(":�𐔆Ѩ1ꬓ*%𝔹⑩%By�ࡤ{6&🃅%ᒜ!*t\u{cd5}ܔÈ🃌K-:f"), ArrayIndex(-74267187), Values, Field("𐝧¥ѨK𛄲ﹰ~/𞺀Ⱥ𐁑{A℈ꧻxኁ$X")]), Every(Select([ArrayIndex(-835767092), ArrayIndex(-90842294), Values, Field("&Z𛱺\u{1a6b}¥¡\u{2002}O�Eࡪ(/ѨѨ\u{fb3}ö2<=u,ⶣb"), Field("হ\u{1d242}]𐺱'<×\".᪗\u{cc6}%\u{9e2}/$`+ꚠ𞹲ѨRr'7\"\u{1921}"), ArrayIndex(-1657524013), ArrayIndex(-1358049927)]), Equal(Select([ArrayIndex(-2133215332), Field("�ѨA$=%0𑒌$𞴻{ῚL𒿎<*Hÿ)HಯmJ᧗ૉvকj¥𑶄ዅ"), ArrayIndex(1372867808), Field("9U𒐽ᅥטּ:^�ཬ{L\u{1a7f}🛞e{𝔊sை8%$-SޥJ=\u{1e136}&"), ArrayIndex(-1515605936), Values]), Newtype([[220, 112, 193, 144, 130, 2, 201, 3, 173, 97, 18, 34, 79, 189, 93, 185, 99, 8, 146, 72, 228, 21, 109, 226, 107, 211, 58, 9, 68, 90, 109, 0, 69, 37, 94, 48, 179, 143, 33, 56, 70, 63, 33, 221, 197, 203, 25, 245, 187, 130, 222, 127, 221, 178, 88, 248, 218, 158, 87, 24, 127, 138, 100, 104], bafkr4ighuk7gjom3gactib6iatiimrm4ytvtdk7y5k4pzutsg56i77lanm, -8.793256611809324e-208, [49, 12, 111, 228, 6, 196, 193, 26, 82, 212, 21, 13, 44, 254, 161, 77, 168, 161, 151, 85, 22, 53, 168, 74, 48, 15, 119, 171, 245], 3.675952639990706e158, false, null, null, null, null, -156.1567975459344, "\u{1070c5}\u{202e}m\u{7f}'#\t\":\u{dad72}ü4\u{202e}\u{7f}'涛\0$E{-/\u{202e}\u{7f}b", "\u{13e62}$e<\0&<\u{c5acc}ny\u{9a704}\"\u{202e}𔖚D", bafy6bzaceagsds7mbaud2m6qhdohvpgwuhfeedqwrntvioyma3kuo5v6vtyt6, null, null, "\r\tg:\u{46cc2}", [142, 185, 121, 120, 78, 34, 236, 132, 123, 49, 125, 116, 146, 139, 230, 17, 174, 47, 83, 146, 252, 30, 220, 161, 163, 207, 136, 121, 230, 68, 139, 61, 190, 89, 223, 74, 72, 219], [28, 249, 124, 123, 205, 232, 92, 8, 106, 47, 104, 13, 104, 201, 76, 228, 38, 128, 160, 168, 2, 34, 83, 71, 248, 91, 111, 250, 225, 49, 225, 194, 169, 133, 253, 173, 44, 47, 27, 4, 181, 140, 138, 211, 3, 136, 21, 139, 138, 196, 191, 191, 76, 163], true, [87, 3, 80, 153, 162, 125, 138, 138, 127, 8, 103, 89, 128, 18, 40, 41, 190, 156, 216, 106, 118, 162, 241, 107, 134, 144, 107, 52, 51, 165, 85, 124, 197, 155], "\u{2f39d}{¥\u{40daf}ѨS\u{a44e7}{\u{a7dc1}�\u{cee1b}'", [235, 139], false, "", "\u{a1d4d}\u{feff}\u{4}\u{feff}3\u{1494d}\u{e1172}.\u{1b}`\u{d2482}{\u{9e}\u{e1d5d}\"3/{\u{5}2].Ⱥ¤<\u{96fa3}Ⱥ\u{49ea4}*\"p\u{b83db}\u{6}\u{6}": null, "?/\u{bdd3c}\u{370b4}\0*\u{102d37}\u{cbc47}.\\Zâ\u{1ced4}KѨ🕴::\u{7f}/": 44, "?8?A:/'H\u{b}\tr\"¥=qu\u{b}¢\u{fde59}\"\u{2}ye'?\t\u{ea1b7}K`\t": -3.9299672377331247e-146, "?{\u{7f}%\u{7f}\r%\u{d3348}\u{b}\u{eb7b2}%¥\u{9406d}\rѨѨ],\u{202e}'\u{105dc6}<^úr\u{8015f}": "\u{9d}\u{891c2}𫩼\u{606aa}\u{45288}p!/&G:㝸ã\u{97}Ⱥ\u{b}", "?Ѩᆬq7/%\\\t\u{feff}\u{da5c3}\"\u{8509f}\"H`\u{1b}%\u{4d464}\u{479f4}\"\u{c147b}\u{1b}/𧳓*,|": "Y\\\u{7f}\u{b1fcf}\u{e99b9}7\u{9e18b}:?\u{ede3}$W", "?\u{5d644}\u{5a09a}\"�l\u{1b}/\06Ѩ.&{\u{10a396}": bafyrmihqvy27erjh5esd3x6wdgnr6z6lru7wfcmkuuvzp7h7am6z63mbia, "A\u{763e5}& \u{d7169}\u{e8031}{\u{b}*\"\t𰩧\u{548b4}?u`/\r🕴\u{7c626}$å🕴\u{10dd6}:🕴": null, "DѨ\u{6ebc8}\u{7f}<\\[?\u{7f}&ኵ:f=𫗿\0�\u{202e}": "s\\/;*\\\u{4150f}E", "F\u{1b}\u{10ea63}\r": -3.881911088191245e-265, "H.¥$": -21, "K\u{cbff5}\u{202e}\u{1260e}\u{feff}&:%\t$\"|&\u{342e6}*:\"&ju/c": true, "N\u{b}&\u{feff}\u{691d4}\u{6}\u{b}:&*Ѩ\u{6f0dd}w\u{e581c}¥z�\u{9cce5}\u{16d5d}\u{4f30a}**\u{93d8c}\u{202e}\u{7}{\u{1b}{±'{K": "%\u{feff}¥", "[𪲁@\u{1387b}\0]\u{6fca2}Bi.\u{89a95}\u{6a578}\0\u{4a64d}\u{b}\t\u{647d9}n\0^\u{8ff74}Yk\u{9c}\u{9c}(": [116, 173, 176, 186, 52, 23, 252, 79, 37, 2, 204, 64, 93, 133, 149, 198, 164, 255, 254, 251, 238, 69, 160, 225, 130, 239, 127, 62, 193, 179, 31, 95, 161, 114], "\\\u{6d15d}\u{b}&\\Ѩã\u{e6f74}<\u{a15a9}\tѨ,\u{b}\u{819b6}\u{3ef57}/\u{4ddf0}Y(\u{5}\u{100a6f}\u{b}/.:": -37, "]\u{3}\t\u{e5fb9}?{\u{202e}z\u{44ab3}\u{39431}\u{e8c44}¥\u{79b81}'\u{777e6}�:{%\u{3900c}{%\u{108b7b}<{`\u{7f}Ó\t🕴T.": 29, "`\u{6}.:🕴{\u{9c4a2}\\<*:": bafy6bzaced7s7j4l6dybvc55yypxhw5em5sg2ciws33rjhgce6hmfmpgf4fdu, "`o=\u{6}3\u{e81b2}\u{1b}G&\0tw\u{3}/\u{3d977}`㊈)=Y\u{2}\0": false, "`\u{44886}/\u{a1f9b}\u{5f978}:\r\u{95be2}/🕴": 0.0, "c\u{5}\u{4bbc4}b\u{c96f8}+%$\u{202e}'<\u{e078c}\u{ac7cc}x\"Ѩ": 53, "n": "\0?\u{efd30}\u{686e5}\"\r\u{5fe32}\u{b5b0c}\u{51f54}\t\u{89}=\u{33916}ZM¥\u{4}\u{7}m�}\u{91c01}\u{6839a}*\u{c59e9}", "oѨ`": null, "w\t\\`\u{da1d2}*&.$E;\u{62ec7})\"Y\r\"5\u{bd563}\u{326cb}{å?/\u{b}\u{98e3b}": "D::\u{202e}F\u{d9e2c}J'=\u{ec5db}\0'\"Ѩ\u{1b}\u{419e5}𮞿?E*mb\u{feff}_Z\u{ab92a}=\u{89c4e}逋\0\u{7f}J", "y6\t<": [179, 71, 5, 85, 100, 29, 222, 53, 97, 142, 194, 243, 96, 220, 13, 106, 64, 105, 167, 218, 123, 136, 220, 228, 82, 153, 8, 92, 185, 11, 112, 146, 197, 109, 163, 11, 117, 83, 66, 85, 178, 149, 17, 95, 212, 87, 96, 62, 216, 80, 38, 36, 236, 156, 22, 40, 44, 133, 95, 15], "{Ⱥ/&\u{7f}\u{5795a}_*\0*\u{a13ce}f?`/\u{1bd42}<𮧏<\\U(n\u{2ffdf}\u{bad0b}&]\u{feff}U🕴$": 9.93350022419413e112, "{\u{39ee3}TI\u{ea081}%Pï>\"Ⱥ\u{feff}{\\j": {"": "\u{feff}\u{1}\u{1}&=¥\u{68854}&/ö\\\u{33a28}\u{d8908}f4]Y\u{39e57}**Ѩ\\*Ѩ\u{5270a}{B¥+x\u{feff}", "\0${\u{8f006}:f<\u{3}\u{ae249}\u{a3dc8}\u{5eb29}\u{b67f9}%:\u{37105}\r\u{1b}'$\u{2faa8}$/A\tW'\u{909b7}\0\u{2}": 2.914324980421285e-195, "\0$\u{ac00f}\u{1b}9\r\u{3}\u{2fd7f}=:R.\u{e3705}\u{c4450}NG\u{73b7c}&$\u{202e}\u{b527c}GL": "\u{a3b69}6a*==?\u{feff}\u{1a1cf}\u{a82da}`/==�🕴\u{6b826}\rD?A\u{feff}\u{1}", "\0*{\u{202e}\t�\u{1b}/|Ѩ%©%": baguqefragwjjb3m3ljrschzrusb2unylas52kabmwfr34wqmvo6rkg2avvuq, "\u{3}\t\0\u{6695e}\"{»1\u{202e}\u{68a4a}N$": [233, 212, 54, 104, 52, 88, 23, 191, 247, 21, 223, 50, 174, 43, 59, 131, 37, 131, 62, 190, 142, 176, 67, 43, 184, 235, 120, 202, 175, 190, 189, 145, 211, 136, 59, 252, 222, 235, 131, 213, 187, 34, 118, 61, 42, 93, 166, 43, 180, 114, 34, 166, 57, 195, 172, 167, 175, 177, 81, 106, 26, 118, 209, 95, 105, 177, 234, 126, 58], "\t\u{1b}7🕴¥qb\u{8}\r\t\u{1062f0}%'\u{b}.g\u{411f2}𭕨B{Ⱥ\u{77264}/\u{b}D\u{3d766}<": null, "\u{b}/<\u{34eed}\0S&>�텐$$": ".\u{7ebf4}Z\u{d3912}\u{feff}?*H\"\u{202e}𦊇\u{91}Ⱥ\u{7f}\r?{)<\"\u{42b46}?T\\\rg\u{ec834}\\\u{7f}\u{7f}¥", "\u{b}?\t\u{2}�4¥𞸩*\"\u{578ba}\0G%��\\r%«=🕴=\\&*=": "\u{8237a}\u{8a}<\u{8ab59}.?<.tàô\u{945d6}\u{feff}`\\#\u{ce61d}c:)?.`a<\u{9d398}\u{962eb}\"&", "\u{b}ÞѨ¾y狦\0🕴\t\u{19d5e}\u{f0148}": bafykbzacebxelvnoczrdap55pukvsxpc6gicbmnqzgsouqceppvlhsicwet6e, "\u{b}ãx\"\\\u{4}`\r:\u{feff}ᦇ\r": 22, "\u{b}�`+\".?hC\u{88713}:\t\0N¥/Ⱥ\u{1d2b9}1&`Ѩ.": 0.0, "\rAf7\06`\u{ff186}\u{9f715}\t": "/$\u{eaf96}\rW.{{.l흄*\u{1b}\"🕴!?\u{a64ef}\\\u{7f}/\u{109cc5}\u{5}'\u{b}\u{fc0c5}𱇴\u{d1388}", "\rUKG/\u{3}\u{2}ꕻ\u{b}\t": -1.3995651248504633e262, "\r\u{72228}\u{3}\t'\t\u{c5bb7}\tv": [207, 209, 38, 232, 225, 181, 157, 174, 248, 85, 75, 104, 14, 234, 9, 86, 149, 189, 217, 176, 122, 32, 78, 186, 174, 19, 241, 185, 202, 135, 32, 184, 35, 47, 78, 189, 35, 153, 142, 128, 46, 7, 65, 55, 37, 215, 0, 109, 138, 244, 78, 202, 124, 93, 146, 143, 199, 70, 40, 153, 254, 139, 213, 103, 34, 98, 157, 146, 23, 126, 115, 247, 59, 186, 119, 63, 16, 179, 123], "\"%3\r\0\t*\u{74f05}\u{10ecdf}%\u{56b43}\u{e2041}\u{b}Ѩ🕴%\u{47ac1}\u{b4501}\u{10314f}\u{b}": 8.190682906409504e77, "\"üE8À%": -5.01070380812316e-309, "\"🕴¿%z4q\u{f5f81}\"\u{202e}\\": [99, 154, 31, 116, 21, 233, 127, 12, 159, 139, 166, 170, 113, 31, 35], "$&\u{81e34}�\u{feff}\u{3}🕴\u{b}\u{3e109}\u{8e8b1}A\u{1b}Ѩ)\r¥\u{c68f3}\u{10081f}\u{10dd8f}": bafyrwibh3uv7biwsbpe37hkwovhrpkm3l7s5icdgji6imq5fjykcl7p24e, "$'Ⱥ':\u{f6dca}{&\u{7d81d}GQѨ𧗿~.Ѩ\u{3f8b4}\u{feff}\u{2}\u{2}`\u{b}\u{202e}\u{7f}\u{8}\u{b45ad}": bafyrwiczadd6c3wkaraxq3lg5clhqyngn6fprrjscfzxfe2mp2kg4pvxba, "$}\u{4998f}}\u{7afe3}\\\0n?\"V\u{1}\"{!*y%\u{1b}\rꢢѨI\u{f13fb}X\u{1061f7}\u{7f}%'l\0=\0^)\u{5ac7e}<": [60, 118, 165, 67, 27, 119, 21, 252, 153, 133, 75, 209, 126, 27, 107, 9, 142, 133, 201, 242, 96, 56, 139, 85, 39, 185, 191, 172, 121, 16, 61, 101, 100, 93, 23, 114, 153, 201, 213, 220, 14, 1, 73, 194, 75, 71, 226, 125, 205, 187, 97, 24, 229, 247, 75, 87, 172, 90], ".`\u{bec17}Ç'\0/\u{d491b}𘅣🕴.\r": 1.2131909958473984e-266, "/\"": bafkr4ifg4p3nsgepq6lvarhhrt4lxydl5arndmqieaknvnqhyvw4l62eau, "/,/\u{b}\u{a9092}\u{3}.\u{7f}ñu?Ⱥ'&\u{e2082}\u{3d612}u": baguqfiheaiqibdayxvhi3ktwv4rfqhx7adreiuo2muqdzc72zj7wydxi2ircenq, "/.\t¬79\u{202e}<%Z\0\u{7}\u{b}&\\🕴\u{c07fd}j=(1\0𮟻mJ�.\u{fe739}ȺXB\u{feff}": [35, 209, 94, 4, 132, 12, 218, 13, 76, 94, 110, 176, 233, 104, 74, 8, 231, 103, 204, 48, 35, 1, 208, 81, 103, 39, 15, 209, 174, 232, 89, 234, 13, 222, 88, 45, 83, 226, 52, 81, 83, 59, 125, 241, 159, 38, 79, 5, 21, 197, 38, 169, 183, 154, 154, 183, 251, 209, 151, 212, 196, 132, 155, 189, 109, 45, 206, 253, 141, 141, 226, 210, 69, 80, 142, 13, 168, 40, 84, 52, 228, 159, 231, 76, 229, 248, 47, 21, 115, 15, 247, 164], "/.\u{b}\0gMg%\0\"n\u{1b}�\u{dc8ef}": null, "0\\\u{b2c71}:.\u{7f}.q\u{ee1a4}\u{3}.`:=\u{7f}shÕ\0'\u{ff5c2}\0<\u{48d2d}": null, "6\u{7f}z\u{fb43d}\0\u{e71c2}n\t\u{1b492}\u{be144}%m\0O£{\u{4}ß`S\u{cc20a}\t\r@\u{1b}\u{2f392}T\0�:4i": [184, 132, 8, 217, 132, 246, 183, 246, 210, 254, 92, 125, 142, 179, 49, 205, 173, 48, 36, 66, 57, 14, 184, 195, 88, 109, 101, 153, 91, 53, 120, 69, 198, 5, 81, 144, 203, 189, 119, 182, 143, 191, 14, 61, 34, 36], ":(`T\0\u{1b}\u{1b}\t$¥z\u{36613}\u{a5f2d}Ѩ\u{d1ec0}Ⱥ\u{7f}\"'u\u{b7fb7}ë` \u{feff}\u{5d6e0}f\rê🕴": [191, 71, 93, 227, 249, 253, 186, 190, 214, 13, 71, 250, 111, 182, 66, 219, 22, 139, 183, 203, 250, 58, 184, 20, 20, 213, 80, 32, 66, 40, 214, 69, 53, 203, 170, 113, 150, 95, 7, 238, 77, 250, 5, 90, 119, 215, 135, 80, 225, 91, 49, 36, 143, 153, 196, 238, 205, 224, 18, 127, 173, 184, 246, 90, 134, 17, 119], "=@\u{8f9f3}7?\\𫥂\u{1}": "i|*\u{c09ed}'\u{b871f}\u{4}*\u{202e}&\\\u{105cbd}bѨ\\*?{F\u{feff}uȺz\0`\u{feff}{+", "={": null, "=\u{a02a9}\\\":¥🕴\"\u{202e}P\u{7f}\u{b7f71}\0\u{3a7e9}Y<î\u{157d9}Å": [19, 138, 165, 245, 200, 2, 67, 217, 45, 240, 188, 107, 149, 30, 61, 57, 176, 165, 123, 163, 219, 5, 81, 31, 155, 252, 139, 204, 155, 201, 120, 3, 32, 18, 218, 98, 147, 233, 22, 106, 60, 86, 204, 202, 147, 32, 223, 159, 66, 107, 250, 251, 19, 167, 246, 252, 90, 205, 212, 54, 251, 5, 241, 43, 213, 120, 23, 1, 29, 163, 69, 234, 99], "?&🕴.*.\u{605a3}¥&j/=a\\\u{b}*Ѩ*'¾�ธ#Ⱥ\t": "\u{7f}\u{8}Y{5\u{60a62}jN=f\0'𩧦\u{feff}\u{7}//a¥kK\u{48989}&\u{f203b}\"\"`", "@Ѩ": "/*\u{95147}\u{b}{`\u{4e71c}", "C\u{3}<Ò?B¥@&": [130, 96, 105, 54, 223, 53, 178, 56, 46, 187, 18, 221, 159, 48, 247, 89, 117, 41, 108, 127, 91, 200, 247, 120, 240, 237, 155, 170, 65, 55, 58, 141, 247, 254, 60, 102, 29, 148, 211, 55, 4, 14, 68, 42, 90, 173, 222, 142, 20, 22, 68, 185, 132, 255, 132, 185, 189, 197], "D\u{a65a4}$\u{df148}\\F\rntѨK\u{1b}jd\u{8ea98}\u{b106e}�": 1.3784978135410346e-170, "Eò<.z\u{781d5}\u{2}\"�^": null, "H\r\u{361f5}{\"�\"\u{41b3d}\\'𦀳¥\u{80}Ⱥ\u{c26fd}�6-`N\u{dca3b}G\u{1de7a}=\\": true, "L\u{7f}:¥=t*{_AP\u{366cc}?wȺ\u{eb31}o.\u{881ae}\u{77df7}t\u{b}\0{\tѨ\u{d6c3a}": [97, 251, 86, 194, 220, 214, 153, 209, 190, 118, 25, 200, 75, 133, 240, 162, 84, 193, 128, 3, 238, 222, 6, 149, 222, 157, 239, 202, 16, 102, 50], "S=U\u{bff90}/?\u{da120}.\u{633c3}a|\u{1c959}\u{3be11}\u{feff}X\u{a9622}?\t.Å.🕴=\u{73c63}𰰅\u{61725}¥\u{a6664}<\u{895f4}i": [140, 113, 176, 106, 69, 9, 192, 221, 52, 44, 56, 187, 134, 114, 29, 65, 208, 39, 0, 95, 237, 224, 76, 195, 8, 225, 21, 98, 228, 60, 95, 240, 189, 156, 136, 235, 72, 132, 236, 170, 1, 250, 184, 134, 77, 48, 249, 199, 172, 3, 66, 201, 75, 15, 29, 254, 104, 111, 158, 53, 80, 85, 74, 36, 20, 15, 150, 52, 31, 50, 233, 6, 228, 244, 185, 202, 142, 56, 126, 47, 69, 35, 225, 162, 147, 42, 172, 213, 71], "\\\u{1b}Ç'(\u{76ac1}\r\u{202e}NÙ\u{87e35}`{\u{52666}Ѩ\u{7f}\u{e28bd}?窣\0\u{e4901}🕴\\": 1.307777027892035e-308, "\\V$\u{71773}\0.H𘑞\u{202e}`&)'\u{cc7d7}4_�📲j.\u{c5777}:\u{b}{\t?\u{abcf6}\u{2}\u{f114a}": null, "`\u{1}7\u{dc7f6}è\u{1b}\u{e6461}", "bF罩`/?{\u{6}m�🕴\u{a0dcf}\u{1b}\u{85683}\r*z\u{3}`\u{202e}\t\0¥\0ó�\u{fa710}": null, "f\u{71088}\u{eeba2}/\u{1b}\u{b}>\r:$H\"ï\\\u{7f}\0¥{+?": null, "k:\t<鏁\t^:u碗\tñr:": bafkrwidekl2tflko33qlm47p7adikj4q6bdju2kq5elnuipzykk565mhby, "s<Ⱥ*[L*<ѨÉ\u{88d19}": [88, 175, 1, 80, 241, 221], "u\r": -2.6904580729605092e122, "u¥\u{feff}\u{1b}": [1, 36, 101, 136, 222, 223], "x`\u{6d787}\u{e5350}\u{df1fb}(>qP🕴\u{ba6ac}𮠰🕴\\\u{4b5e0}J<": false, "{\0\u{3b4bd}=g¥\u{107402}\u{5c069}Ѩ=5k*$`<%p": [42, 216, 219, 136, 90, 214, 91, 194, 12, 10, 62, 81, 119, 54, 126, 138, 227, 206, 148, 138, 95, 191, 247, 89, 212, 142, 18, 121, 148, 163, 152, 177, 37, 69, 222, 22, 226, 117, 153, 5, 89, 234, 92, 155, 223, 10, 158, 200, 18, 37, 110, 103, 181, 109, 202, 35, 39, 117, 93, 5, 90], "\u{7f}\u{7e6e9}\u{8b}𥚝": baguqehrahkajpop4geeiwsi7gu2wdsuybkf6lbq6rfen4f7v3vrtqbyazd3a, "\u{7f}\u{b601e}'\rK/\t\u{feff}¤?\rp\u{af66a}": [73, 33, 204, 97, 67, 215, 167, 191, 121, 17, 85, 93, 75, 141, 150, 250, 223, 157, 229, 29, 48, 217, 58, 27, 191, 36, 145, 93, 14, 17, 12, 32, 61, 36, 83, 58, 181, 136, 104, 35, 237, 219, 146, 240, 223, 170, 203, 45, 27, 109, 190, 5, 96, 122, 180, 241, 211, 211, 147, 12, 136, 219, 25, 19, 108, 191, 73, 165, 95, 129, 7, 16, 40, 44, 123, 182, 100, 246, 148, 80, 99, 191, 137, 144, 177, 240, 82, 242, 163, 30, 210, 13, 45, 140, 6, 196, 122], "¥O\u{7f}Ⱥ𗢎e/\u{10697e}\u{202e}\u{7f}0�Ⱥ𤂹\u{e098b}\u{8383e}`G<<\u{b94d8}Q\u{e6032}\u{cf7bc}\0:\"?\u{1}\u{1a8a3},": null, "¥\u{202e}'\u{8d073}\u{78d6b}\u{6e610}\":E?\u{9bb82}Ѩ<�>\u{4c02a}//\u{a27e4}=\u{f456f}<3𧉟/\u{98}\u{f3520}": "x=`ᒼ\u{12af8}\"=", "Ò\u{51a90}�\u{1b}\"䩖𦄱\u{b3b7f}\u{aee50}<🕴{\\A\\\"X\u{f32ab}::\u{4}\u{c7c01}`\u{1b}\u{4}&\u{ff44f}": 0.0, "\u{86b18}Ⱥ<": null, "\u{a0feb}&¥\u{106aba}{B\u{be082}1\u{62718}\tS\u{aa928}\u{2}\u{3d1dc}\u{f5e89}": [16, 76, 209, 135, 206, 142, 16], "\u{afe7a}A`>\u{dafee}𡪩b\u{77678}%\r劗?\u{10819a}^.S\u{7f}\u{94083}$�\rA": 5, "\u{b2ccd}ë9('*\u{b}2\u{8}\u{c9f32}\u{5a462}\"?2\u{b}\0W�\u{3e6e8}th\\=\"F": null, "\u{e6ebb}r\u{202e}\u{80bd8}:🁚\t�\u{fd427}\u{b2fb5}\u{4badf}w/\"0\u{202e}<\u{15010}^v\u{53eb1}\0='": null, "\u{f31e7}41\r\u{68a9f}\u{202e}\t.p\u{4ea54}\u{860c8}\u{19987}l": -0.0, "\u{faf52}\u{6}¥\r\u{202e}䲖\u{202e}R&\r\u{973bd}\u{feff}l\\\"$\u{e5961}¥\u{d805a}\0+OB*tu\u{eb5a9}\u{109146}Ⱥ\u{759f5}`\0": null, "\u{fc398}\u{2}\\.\u{ee493}\u{d0de6};\u{ed8fa}🕴<": null, "\u{10ed00}?\u{7f}�`\u{b}L*\0®": -0.0}, "\u{7f}':e/Ѩ\u{36fde}`/\u{202e}\u{7f}=\u{feff}🕴\u{f28b6}\r": null, "¥": -0.0, "¥ :?\r\u{70234}": "�¥", "¥i🕴\0\u{93569}\u{71ace}\t:\u{75563}*+": false, "¥\u{be29b}\u{202e}\u{7ef68}5\u{1b}`\u{202e}\"\0\u{feff}\u{10e7da}�\u{7f123}|\u{8c8c5}n": 11, "±:𢽭🕴t�%r\u{8}?`^'r": false, "Ⱥ/\u{882e4}:\u{e0f71}$%/{\u{36b45}:,z\u{8}[1\u{7f})%\t\u{b}v\r\u{aaa37}\u{8}𫷝\u{7f}\u{46db0}¥\u{ad5a1}ke": -12, "🕴\u{feff}\\x\t\u{6}\t~\u{39f68}\u{202e};�\u{1b}6:/3\t\u{7f}": bafkrmiepyixjho2atqqcqgtd575iqsam6klfzdtkx5g4wummups24pdqcu, "🕴\u{5de4c}\\�𐝣\u{feff}𱴜\u{feff}": "\u{6}\r:\u{190a7}�%Ѩ𲇭:\u{6}_🕴Ò%\"'\u{e1b6d}F'\u{f7ad8}\u{57ad7}`\u{9c70b}$`\u{202e}\u{4b5bb}UK\u{90}", "𣉓��e\"$0R\u{a837d}\0<🕴?@,": true, "\u{2fa70}\\𪚅¯\u{108509}\u{4ab0e}\u{4ecc6}\u{bebaa}B\u{1}\\\u{f24f8}'%ð\u{202e}¥\u{3a476}/%Ⱥ\t$vyC]>+\u{87304}": [94, 245, 167, 7, 68, 250, 234, 108, 122, 64, 21, 238, 150, 215, 116, 48, 232, 184, 232, 43, 30, 213, 149, 183, 215, 1, 242, 95, 189, 228, 190, 9, 43, 222, 14, 254, 203, 238, 30, 51, 216, 40, 125, 87, 240, 71, 185, 206, 114, 87, 214, 85, 33, 163], "\u{373f7}": true, "\u{4f31f}*A?\\${\u{91276}\u{52eb1}#\u{b}\u{1b}": -47, "\u{5e975}🕴z${'\u{5d19a}b\u{aa904}'J}`Ѩ9": "\t=\u{1}±\u{84533}T*\u{4d0eb}\"A\u{8}<", "\u{69742}o\u{81}n<@\u{feff}L:\u{1041f7}🕴\u{8}𬾧\u{7f}\u{108007}?[\u{feff}\r": [31, 200, 160, 81, 110, 124, 249, 10, 85, 215, 192, 121, 17, 28, 185, 121], "\u{6cfa0}\u{6}𡽫{\u{feff}bJ&\u{5}`x\u{d2999}d&\u{badf8}\u{1b}\u{365dc}'�\u{5a37e}": null, "\u{a202e}~QE?\u{b}\u{7}/Ⱥ/\\&\u{fb894}\u{e0565}\u{2}�^?Ѩ'": [146, 41, 216, 116, 157, 252, 155, 192, 137, 113, 110, 231, 49, 142, 206, 2, 93, 201, 68, 48, 93, 58, 173, 197, 93, 40, 41, 112, 193, 244, 79, 228, 221, 71, 177, 129, 102, 97, 55, 137, 3, 148, 166, 101, 253, 166, 170, 139, 23, 120, 178, 140, 132, 104, 89, 46, 120, 30, 154, 194, 138, 39, 103, 152, 6, 136, 237, 241, 138, 193, 248, 33, 185, 56, 220, 118, 23, 17, 132, 220, 239, 90, 207, 237, 94, 75, 90, 222, 46, 180], "\u{b2ebe}*\u{84}.\u{a909e}7ѨjV𠷃Ⱥ?ü\u{6}u𝣒L\u{34303}:\u{bc294}?DZ\0\u{7f}/\u{103c3f}\t\\": bafkr4ihqz56mmzigglx6apxygvxcnreb7hjve6eftqfcooxtdzyzahyil4, "\u{c3588}*\"\u{8}\u{51cd1}躅\u{8f2d6}u🕴*\r\u{1}7🕴\u{71aa7}{\u{77c1d}%\u{cf015}\u{7a969}\u{8}\u{3cbf9}\u{92}\u{b}*\t\u{15db3}": "/{ªN?\u{8711e}=\0g{\u{202e}\u{202e}¥.\u{1b}H%\u{95}\0r%:", "\u{d7ffe}\u{44f52}\u{1b}\"%ỵ*m\u{5}$\r¥Ⱥ\u{1b}Uo':\r/%'<Ⱥ": -7, "\u{e0680}={\u{fd1e9}j<'CѨ�3\u{ce0a4}Ѩ\u{4b081}C\"`%9GGv\t\u{10322d};Ⱥ\\\u{e1a9c}\u{202e}\\": true, "\u{e5e60}\u{b}{\u{e3551}=\u{b5edf}<Ⱥ\"#?\u{7}\u{80}\\'5f=\":𪟤=\t\u{dc111}*<\u{346be}": true, "\u{ebf8a}Y\u{8}\u{f98c9}\\\u{7f}9🕴\u{4e9df}=\"t": [222, 226, 183, 11, 204, 93, 208, 118, 17, 243, 19, 124, 63, 137, 157, 195, 178, 3, 70, 223, 216, 164, 164, 66, 246, 151, 76, 100, 93, 11, 7, 234, 146, 38, 18, 169, 97, 221, 8, 133, 14, 197, 108, 213, 201, 214, 93, 202, 188, 165, 171, 86, 195, 223, 164, 6, 51, 234, 214, 26, 158, 32, 12, 35, 10, 245, 171], "\u{ffc11}#\u{b}¾": null, "\u{101fde}\u{b6522}'.&$": {"": 1.5658615101276272e160, "\u{2}=\u{5bd45}?o뗡p\u{2}\u{b}\\Ѩ¥\u{8b3a4}ib=": null, "\u{3}𤥺": null, "\u{6}¥\u{feff}%\u{202e}�Ѩ%\u{10df7a}$%\u{2}\u{b}\u{59972}𝝊\u{5107d}\u{202e}\0.k%V\u{55f7d}±": "`\u{7f}\u{4e0cf}\u{f7dab}\u{7f}zw[Ѩ.\u{b}Ⱥ\u{202e}\u{1b}#�\u{61e5a}\u{569c2}:\u{51a0f}\u{1b}*R\u{bb37d}:🕴{+Rf\u{ea9f}", "\u{7}Ѩ\u{1}\u{dca54}t": 17, "\t`": [25, 228, 9, 39, 85, 50, 121], "\t\u{2ef3e}\0": bafkrwibffxly7lxjvxuvp2ic3ao6f2icoflqqiadi5dvz4yvpdsyd27jne, "\r*\u{1b}\u{4}{`_\u{feff}\u{b}¥.K\t<\u{3}&\u{7}$🕴\u{7f}": -40, "\r\u{587e8}Ⱥ": [207, 53, 166, 39, 184, 202, 32, 200, 7, 155, 18, 5, 102, 226, 211, 93, 116, 183, 163, 131, 161, 249, 63, 112, 198, 231, 166, 86, 176, 31, 56, 43, 28, 142, 150, 56, 244, 64, 75, 60, 122, 214, 109, 138, 103, 217, 188, 127, 42, 7, 30, 47, 190, 51, 7, 110, 190, 106, 215, 102, 86, 94, 41, 76, 159, 221, 62, 236, 96, 83, 215, 26, 102, 233, 126, 117, 233, 43], "\"z=%`\r�ñ%:Ⱥ\u{ee132}.}Ue'\u{8}\u{a6c62}": bafyrwiefif6srseryrwhg7lx5ddm3cuzdwepr4rmvorftfhq2vj57d2gsq, "#\u{75c49}!": "\u{7f}\u{5a95d}L\u{32a81}\"{\u{7}¥²�\u{e3087}O'!\u{ab00c}\r./ \u{202e}h🕴'", "$\0\"\u{ca931}\u{2}\u{1}:\r\u{3}\u{63225}g¥Ñ": true, "$Ѩ\u{bd470}û54Ⱥ%¥\u{feff}": null, "$�0\u{a8b97}𱗶\\0\t$": 6.488444060884696e306, "&'\\w:<<%\u{feff}\u{5}\u{1b}`¥/?n𡚋&,4\u{5e1f3}\u{4b359}\u{fbe37}i\r\\\t:": true, "&?\u{6}r\u{c3665}\u{7f}i%": baguqefranz7i2sd2lryjkxvq7st4ujh3xjdttm6fldqgbci5rlbzwbet2dma, "&?%": bafykbzacea3napu4rwrzbqevwhiaptytsgo3gscof2glvjfje6h75nyc5npb6, "&\u{7f}\r}>`\"%\rY\")>\0\u{1b}X\u{b65b4}": [93, 89, 232, 156, 166, 193, 205, 122, 207, 235, 2, 186, 202, 177, 228, 245, 238, 222, 129, 191, 32, 176, 162, 238, 204, 162, 152, 39, 105, 236, 197, 76, 201, 44, 148, 170, 224, 150, 87, 94, 134, 189, 238, 51, 118, 124, 144, 5, 252, 27, 17, 111, 250, 236, 173, 159, 46, 45, 170, 32, 133, 60, 134, 50, 28, 47, 178, 197, 160, 126, 143, 31, 160, 25, 175], "'d'\u{4e27c}\0\u{b4b7c}`{?\u{3cff7}Â<\0": "h\u{feff}\u{15730}\u{c364f}]WV.TH\u{feff}.Z\0.\u{9d3ae}$\u{e5559}?\u{d403d}\u{7f}%(k𠢘", "'i🕴\tb{\u{7}\u{feff}&\u{49079}'S/\\\u{10600a}\u{af8aa}\u{3a6a6}\r": false, "'ѨH8'Ѩ{\u{f5f05}ѨQ\u{d5ba2}\u{3773e}::\u{eb168}:)CȺѨѨ": baguqehra3ml5lg6x5oaboqvr65pulvfntbvdrj6v5zyvuaf2j7bpx7wkhbpa, "*I¥w\u{b}\u{7b9a4}\u{3e747}\u{b2e81}/y<5'*S?$$O\"\u{7}\u{9f10a}\u{8}S\u{7a8bd}I\u{7f}U%": null, "*\u{202e}": [61, 242, 249, 132, 34, 222, 153], "*\u{861a1}//\u{10ca95}`'𓀒\\'=Q;\u{1073cb}M\u{5dbd9}": [34, 41, 251, 216, 124, 171, 190, 175, 12, 20, 65, 118, 149, 193, 33, 184, 96, 7, 181, 39, 92, 97, 36, 84, 72, 99, 69, 241, 55, 2, 31, 74, 55, 172, 146, 32, 230, 228, 234, 148, 164, 27, 55, 71, 222, 203, 70, 239, 15, 85, 157], ". \u{202e}v'*<\u{3c008}\u{ea8b}c¥?g:5$Y¥\u{5a3ec}": [60, 209, 45, 129, 120, 83, 199, 46, 198, 62, 131, 13, 210, 117, 41, 136, 9, 59, 142, 242, 198, 194, 181, 206, 19, 221, 114, 94, 216, 25, 58, 191, 30, 252, 131, 130, 133, 109, 116, 32, 231, 108, 160, 173, 195, 103, 78, 179, 165, 157, 227, 152, 245, 222, 164, 242, 208, 136, 66, 6, 54, 146], "/'%\u{3b4df}`𫨁e*\u{71a70}=\u{c5203}\rȺ궖JȺ/\u{4bb7c}\u{3}*#\u{d3bde}p\0qÙ\u{b}🕴\u{202e}\u{202e}r.": false, "/{H\u{10c9ea}\u{79be5}?\u{ca12d}": 8.6395723193706e-309, "4\u{54c08}'`?": -8.973748901720123e-29, "6\u{883eb}Ⱥ$\u{1b}": -1.0199828187670042e-159, ":<\u{1a04a}U�": -1.8179682492493788e-167, ":n\u{8a}\u{8}?𡜬\u{7f}F'ýr0\u{1edbd}%\r8oѨ\u{1b}": "", "<=🕴\u{dd6ce}\u{3}\u{65218}�\u{b3002}\u{81}<\\//z=\u{6106d}*Ѩ\u{8090a}hC\u{88ce1}\u{1b}\0🕴\u{ac262}\r/": null, "?*\u{a6066}aѨ\u{7f}\u{3}\u{1b}`'\u{aa717}\u{14b1c}o&\u{8d098}É?Sv\u{7f}¥`\0\0<]\u{15ce3}\u{1}=:$=": [66, 253, 182, 100, 225, 215, 132, 63, 183, 229, 8, 172, 23, 12, 49, 232, 47, 74, 175, 130, 83, 252, 88, 185, 110, 27, 188, 151, 251, 208, 54, 250, 230, 62, 210, 35, 23, 251, 21, 206, 161, 29, 185, 225, 190, 48, 125, 83, 49, 116, 98, 148, 46, 18, 204, 43, 23, 115, 8, 245, 113, 58, 152, 205, 222, 26, 169, 223, 244, 99, 43, 185, 3, 45, 50, 190, 66, 69, 188, 185, 150, 232, 128, 102, 195, 99, 78, 226, 251, 253, 84, 106, 110, 178, 192, 203], "?\u{7f}\u{b}\u{9f2c9}i\u{10bb8b}:{&8\r𠒫*t�\u{1}H\u{98f18}\" \u{c92b6}\u{8})\u{7d0c3}<Ï": false, "?\u{69bad}I&¥@🕴n\0??C\u{607cf}\u{7f}/&\u{b}🕴..qѨ\u{1b}": "𘉢`\0=\u{cc51f}\u{a4fb8}\0\u{47e89}/\u{b}\u{a0a22}±\\\u{feff}\u{feff}/.ck.\t\u{e3434}j", "A\u{e0fe6}'\u{202e}`%\u{202e}\0<\u{c18d9}/:\u{10b6bc}b\r*R:\u{feff}\u{3}\u{10ba3c}\u{84727}\u{c8ebd}^": -6, "C\u{c5e80}*g🖻\u{66e08}\u{9d567}{'Ѩ\u{58cb7}\u{9a1ca}¥Ⱥ\u{19181}\u{b1066}\u{5b8ee}\u{b9fcf}= =\u{dfbde}\u{18d8b}": "\u{5}\u{4d180}M\u{b}\\.Ⱥ*è=.e", "I*\u{96}\t=$\u{4747f}\u{5}\u{1b}n=a&rȺȺ\u{6cd13}": [230, 44], "R´\t\u{1}&\\\u{1b}kN\u{1}{´<\0\u{95a25}d": "k*`$U,\\\u{feff}\t\u{916a3}\u{849e2}<䨑\u{a2dd6}\u{9126c}", "Yq🕴\u{c5293}y:": 21, "Y\u{101e9f}JE{\t\u{7f}\0\u{4a162}aѨ.\u{7b388}댦\u{81d62}Ѩ\u{a6fb0}\\\u{dec90}\u{1}\r\u{b}/": null, "\\": -7.477551823968463e-129, "\\<\t\u{e9e4c}\u{b}:\u{a258b}¦\u{53415}5G\u{7}": true, "\\O\u{9e0a1}K.𠓎\u{fb34d}\u{7469e}\u{4c980}.:\u{bbb4f}8\"J:/\u{829d7}:\u{1}𣂇Ⱥ*J?\t\\\0\u{b}Ⱥ\u{ea85b}`": bafyrwihiw4vja2le5bbo63lbzgzdc7npl6b6ixw6nlheeirllwo5zpuzuy, "_]:\u{e11f7}¥On/<:\t=\u{1b}": "G\u{b}\u{71804}'🕴Ѩc\u{feff}<%Ѩo\u{1b}¥", "e\u{70c00}\0¥\u{202e}": [207, 96, 93, 148, 103, 198, 140, 36, 26, 221, 215, 6, 202, 231, 233, 247, 107, 178, 240, 144, 50, 206, 143, 13, 95, 15, 171, 239, 158, 75, 248, 192], "f\08<\u{9c37d}?\u{10f8f1}=`\u{95}Ⱥ{.\u{1b}\u{15cdd}\u{202e}Ⱥy<$\u{8}$\u{6c5d0}K\u{c4f04}\u{19f0a}": bafykbzacede6nne55uave2wj6epfrbud4xcozirijfysx2p3a4isvzez7oiza, "gÔ\u{b}繎.U": null, "i\u{889f5}r\u{e98e9}<Å\u{ad256}\u{b}R\r_%\u{d8e2e}/%r\u{b908c}%\rz~'\u{e04db}.\0\u{d9659}$\u{7cda4}`)": null, "u\u{a1658}0\u{f2355}}🕴\r": -7.035669443392254e-42, "v\u{10b659}�\r!o=\u{a24cd}]XÂf(\u{859d0}w\0.:": bafkrmihatl6qchcaitd2pcbxodwzlgbynl4rpzu6fnfcy3wgudnxpnabpm, "{%\u{61d33}=/EѨ/\"?�\u{5ca71}0\r": null, "{/:/JK\u{62214}Ⱥ¢\u{b}1o\u{5d859}\\\\\u{b65b6}*¥Ⱥ\u{1b}ѨѨ": bafk6bzacebf3fnemqzl52apgxmcok4gweclupgjrxwtee6ci75qsawcnyjuw6, "{:\u{f901d}\u{da30b}*:𱪙\rF\t\u{405de}\u{feff}%~\u{7f}*=\u{46e3e}\u{b}\u{202e}": true, "{f\\\u{b}\u{1}\u{7f}\tD\u{b}&Ⱥ𣡼\u{e84fe}J\u{d2ef7}E\u{1}'\0f\r\u{6}\u{4a79f}Dx\u{2}": -13, "{\u{d9a04}&ö%\u{7f}\u{d686e}&´": 7.588162654188097e-308, "\u{7f}\t\tj\tw\u{feff}Ѩ�練\u{3}:𧸨\u{a3acd}\u{101251}'\u{42a8f}E\u{3e47c}\u{7f}\u{7}&¥%<𩌯c": bafyobzaceco6clzsn57sjq2u5patyvukq3ksi4wyrztinj5srwspsi63qyfqc, "\u{7f}c\0\u{ffc41}<\r^Æ\u{9f}Ñ\u{b6f14}U\\\u{c45cd}\\\u{1b}g\u{ed9b9}🕴{\"R\u{60150}\u{b54c0}I:\u{10a33a}": [126, 247, 86, 174, 51, 26, 21, 148, 201, 8, 130, 49, 162, 197, 14, 242, 15, 142, 249, 226, 194, 131, 116, 158, 109, 70, 138, 10, 113, 85, 166, 21, 217, 49, 112], "\u{7f}\u{39cc5}'🕴\"\u{71cc2}&\u{7f}'": -4.297679451122769e-29, "Â\u{4}\u{feff}<'\u{c2f64}ѨȺ\\2\\\u{41534}*{<� IntoIterator for Named { impl FromIterator<(String, T)> for Named { fn from_iter>(iter: I) -> Self { - Named(iter.into_iter().collect()) + let btree: BTreeMap = iter.into_iter().collect(); + Named(btree) } } diff --git a/src/delegation/payload.rs b/src/delegation/payload.rs index 41d3048b..e3486eac 100644 --- a/src/delegation/payload.rs +++ b/src/delegation/payload.rs @@ -1,5 +1,6 @@ use super::policy::Predicate; use crate::ability::arguments::Named; +use crate::time; use crate::{ capsule::Capsule, crypto::{varsig, Nonce}, @@ -8,9 +9,11 @@ use crate::{ }; use core::str::FromStr; use derive_builder::Builder; +use did_url::DID; use libipld_core::{codec::Codec, error::SerdeError, ipld::Ipld, serde as ipld_serde}; use serde::{Deserialize, Serialize}; use std::{collections::BTreeMap, fmt::Debug}; +use thiserror::Error; use web_time::SystemTime; #[cfg(feature = "test_utils")] @@ -112,8 +115,11 @@ impl Verifiable for Payload { } } -impl TryFrom> for Payload { - type Error = (); // FIXME +impl TryFrom> for Payload +where + ::Err: Debug, +{ + type Error = ParseError; fn try_from(args: Named) -> Result { let mut subject = None; @@ -130,30 +136,36 @@ impl TryFrom> for Payload { for (k, ipld) in args { match k.as_str() { "sub" => { - subject = Some( - match ipld { - Ipld::Null => None, - Ipld::String(s) => Some(DID::from_str(s.as_str()).map_err(|_| ())?), - _ => return Err(()), + subject = Some(match ipld { + Ipld::Null => None, + Ipld::String(s) => { + Some(DID::from_str(s.as_str()).map_err(ParseError::DidParseError)?) } - .ok_or(())?, - ) + bad => return Err(ParseError::WrongTypeForField("sub".to_string(), bad)), + }) } "iss" => match ipld { - Ipld::String(s) => issuer = Some(DID::from_str(s.as_str()).map_err(|_| ())?), - _ => return Err(()), + Ipld::String(s) => { + issuer = Some(DID::from_str(s.as_str()).map_err(ParseError::DidParseError)?) + } + bad => return Err(ParseError::WrongTypeForField("iss".to_string(), bad)), }, "aud" => match ipld { - Ipld::String(s) => audience = Some(DID::from_str(s.as_str()).map_err(|_| ())?), - _ => return Err(()), + Ipld::String(s) => { + audience = + Some(DID::from_str(s.as_str()).map_err(ParseError::DidParseError)?) + } + bad => return Err(ParseError::WrongTypeForField("aud".to_string(), bad)), }, "via" => match ipld { - Ipld::String(s) => via = Some(DID::from_str(s.as_str()).map_err(|_| ())?), - _ => return Err(()), + Ipld::String(s) => { + via = Some(DID::from_str(s.as_str()).map_err(ParseError::DidParseError)?) + } + bad => return Err(ParseError::WrongTypeForField("via".to_string(), bad)), }, "cmd" => match ipld { Ipld::String(s) => command = Some(s), - _ => return Err(()), + bad => return Err(ParseError::WrongTypeForField("cmd".to_string(), bad)), }, "pol" => match ipld { Ipld::List(xs) => { @@ -162,59 +174,138 @@ impl TryFrom> for Payload { .map(|x| Predicate::try_from(x.clone()).ok()) .collect(); } - _ => return Err(()), + bad => return Err(ParseError::WrongTypeForField("pol".to_string(), bad)), }, - "metadata" => match ipld { + "meta" => match ipld { Ipld::Map(m) => metadata = Some(m), - _ => return Err(()), + bad => return Err(ParseError::WrongTypeForField("meta".to_string(), bad)), }, "nonce" => match ipld { Ipld::Bytes(b) => nonce = Some(Nonce::from(b).into()), - _ => return Err(()), + bad => return Err(ParseError::WrongTypeForField("nonce".to_string(), bad)), }, "exp" => match ipld { - Ipld::Integer(i) => expiration = Some(Timestamp::try_from(i).map_err(|_| ())?), - _ => return Err(()), + Ipld::Integer(i) => { + expiration = Some(Timestamp::try_from(i).map_err(ParseError::BadTimestamp)?) + } + bad => return Err(ParseError::WrongTypeForField("exp".to_string(), bad)), }, "nbf" => match ipld { - Ipld::Integer(i) => not_before = Some(Timestamp::try_from(i).map_err(|_| ())?), - _ => return Err(()), + Ipld::Integer(i) => { + not_before = Some(Timestamp::try_from(i).map_err(ParseError::BadTimestamp)?) + } + bad => return Err(ParseError::WrongTypeForField("nbf".to_string(), bad)), }, - _ => (), + other => return Err(ParseError::UnknownField(other.to_string())), } } Ok(Payload { - subject, - issuer: issuer.ok_or(())?, - audience: audience.ok_or(())?, + subject: subject.ok_or(ParseError::MissingSub)?, + issuer: issuer.ok_or(ParseError::MissingIss)?, + audience: audience.ok_or(ParseError::MissingAud)?, via, - command: command.ok_or(())?, - policy: policy.ok_or(())?, - metadata: metadata.ok_or(())?, - nonce: nonce.ok_or(())?, - expiration: expiration.ok_or(())?, + command: command.ok_or(ParseError::MissingCmd)?, + policy: policy.ok_or(ParseError::MissingPol)?, + metadata: metadata.unwrap_or_default(), + nonce: nonce.ok_or(ParseError::MissingNonce)?, + expiration: expiration.ok_or(ParseError::MissingExp)?, not_before, }) } } +#[derive(Debug, Error)] +pub enum ParseError +where + ::Err: Debug, +{ + #[error("Unknown field: {0}")] + UnknownField(String), + + #[error("Missing sub field")] + MissingSub, + + #[error("Missing iss field")] + MissingIss, + + #[error("Missing aud field")] + MissingAud, + + #[error("Missing cmd field")] + MissingCmd, + + #[error("Missing pol field")] + MissingPol, + + #[error("Missing nonce field")] + MissingNonce, + + #[error("Missing exp field")] + MissingExp, + + #[error("Wrong type for field {0}: {1:?}")] + WrongTypeForField(String, Ipld), + + #[error("Cannot parse DID")] + DidParseError(::Err), + + #[error("Cannot parse timestamp: {0}")] + BadTimestamp(#[from] time::OutOfRangeError), +} + +impl From> for Ipld { + fn from(payload: Payload) -> Self { + let named: Named = payload.into(); + Ipld::Map(named.0) + } +} + +impl TryFrom for Payload +where + DID: Did + FromStr, + ::Err: Debug, +{ + type Error = TryFromIpldError; + + fn try_from(ipld: Ipld) -> Result { + match ipld { + Ipld::Map(map) => { + let named = Named::(map); + Payload::try_from(named).map_err(TryFromIpldError::MapParseError) + } + _ => Err(TryFromIpldError::NotAMap), + } + } +} + +#[derive(Debug, Error)] +pub enum TryFromIpldError +where + ::Err: Debug, +{ + NotAMap, + MapParseError(ParseError), +} + impl From> for Named { fn from(payload: Payload) -> Self { let mut args = Named::::from_iter([ - ("iss".to_string(), Ipld::from(payload.issuer.to_string())), - ("aud".to_string(), payload.audience.to_string().into()), - ("cmd".to_string(), payload.command.into()), + ("iss".to_string(), Ipld::String(payload.issuer.to_string())), ( - "pol".to_string(), - Ipld::List(payload.policy.into_iter().map(|p| p.into()).collect()), + "aud".to_string(), + Ipld::String(payload.audience.to_string()), ), + ("cmd".to_string(), Ipld::String(payload.command)), + ("pol".to_string(), { + Ipld::List(payload.policy.into_iter().map(|p| p.into()).collect()) + }), ("nonce".to_string(), payload.nonce.into()), ("exp".to_string(), payload.expiration.into()), ]); if let Some(subject) = payload.subject { - args.insert("sub".to_string(), Ipld::from(subject.to_string())); + args.insert("sub".to_string(), Ipld::String(subject.to_string())); } else { args.insert("sub".to_string(), Ipld::Null); } @@ -248,12 +339,13 @@ where Nonce::arbitrary(), Timestamp::arbitrary(), Option::::arbitrary(), - prop::collection::btree_map(".*", ipld::Newtype::arbitrary(), 0..50).prop_map(|m| { + prop::collection::btree_map(".*", ipld::Newtype::arbitrary(), 0..5).prop_map(|m| { m.into_iter() .map(|(k, v)| (k, v.0)) .collect::>() }), prop::collection::vec(Predicate::arbitrary_with(pred_args), 0..10), + Option::::arbitrary(), ) .prop_map( |( @@ -266,6 +358,7 @@ where not_before, metadata, policy, + via, )| { Payload { issuer, @@ -277,9 +370,82 @@ where nonce, expiration, not_before, + via, } }, ) .boxed() } } + +#[cfg(test)] +mod tests { + use super::*; + use assert_matches::assert_matches; + use pretty_assertions as pretty; + use proptest::prelude::*; + use testresult::TestResult; + + mod serialization { + use super::*; + + #[test_log::test] + fn test_into_ipld() -> TestResult { + proptest!(ProptestConfig::with_cases(100), |(payload: Payload)| { + let named: Named = payload.clone().into(); + let sub = named.get("sub".into()); + + if let Some(ref subject) = payload.subject { + let sub_ipld = &Ipld::String(subject.to_string()); + pretty::assert_eq!(sub, Some(sub_ipld)); + } else { + pretty::assert_eq!(sub, Some(&Ipld::Null)); + } + }); + + // proptest! { + // #![proptest_config(ProptestConfig { + // cases: 100, .. ProptestConfig::default() + // })] + + // #[test_log::test] + // fn test_into_ipld(payload: Payload) { + // dbg!(payload.clone()); + + // prop_assert_eq!(payload.clone(), payload.clone()) + // } + + // // #[test_log::test] + // // fn test_roundtrip_ipld() -> TestResult { + // // Ok(()) + // // } + // } + + Ok(()) + } + + #[test_log::test] + fn test_from_ipld() -> TestResult { + Ok(()) + } + + #[test_log::test] + fn test_ipld_round_trip() -> TestResult { + proptest!(ProptestConfig::with_cases(1), |(payload: Payload)| { + let ipld: Ipld = payload.clone().into(); + let parsed = Payload::::try_from(ipld); + + dbg!(parsed); + + // assert_matches!(parsed, Ok(payload)); + }); + + Ok(()) + } + + #[test_log::test] + fn test_from_invalid_ipld() -> TestResult { + Ok(()) + } + } +} diff --git a/src/delegation/policy/predicate.rs b/src/delegation/policy/predicate.rs index d222cc3e..0f65eacb 100644 --- a/src/delegation/policy/predicate.rs +++ b/src/delegation/policy/predicate.rs @@ -834,19 +834,17 @@ impl Arbitrary for Predicate { fn arbitrary_with(_params: Self::Parameters) -> Self::Strategy { let leaf = prop_oneof![ - Just(Predicate::True), - Just(Predicate::False), - (Select::arbitrary(), Select::arbitrary()) + (Select::arbitrary(), ipld::Newtype::arbitrary()) .prop_map(|(lhs, rhs)| { Predicate::Equal(lhs, rhs) }), - (Select::arbitrary(), Select::arbitrary()) + (Select::arbitrary(), ipld::Number::arbitrary()) .prop_map(|(lhs, rhs)| { Predicate::GreaterThan(lhs, rhs) }), - (Select::arbitrary(), Select::arbitrary()) + (Select::arbitrary(), ipld::Number::arbitrary()) .prop_map(|(lhs, rhs)| { Predicate::GreaterThanOrEqual(lhs, rhs) }), - (Select::arbitrary(), Select::arbitrary()) + (Select::arbitrary(), ipld::Number::arbitrary()) .prop_map(|(lhs, rhs)| { Predicate::LessThan(lhs, rhs) }), - (Select::arbitrary(), Select::arbitrary()) + (Select::arbitrary(), ipld::Number::arbitrary()) .prop_map(|(lhs, rhs)| { Predicate::LessThanOrEqual(lhs, rhs) }), - (Select::arbitrary(), Select::arbitrary()) + (Select::arbitrary(), String::arbitrary()) .prop_map(|(lhs, rhs)| { Predicate::Like(lhs, rhs) }) ]; diff --git a/src/delegation/policy/selector/select.rs b/src/delegation/policy/selector/select.rs index 6d36c02b..aec6fd31 100644 --- a/src/delegation/policy/selector/select.rs +++ b/src/delegation/policy/selector/select.rs @@ -3,18 +3,31 @@ use super::{error::SelectorErrorReason, filter::Filter, Selectable, SelectorErro use libipld_core::ipld::Ipld; use serde::{Deserialize, Serialize}; use std::cmp::Ordering; +use std::fmt; use std::str::FromStr; #[cfg(feature = "test_utils")] use proptest::prelude::*; -#[derive(Debug, Clone, PartialEq)] +#[derive(Clone)] pub struct Select { filters: Vec, _marker: std::marker::PhantomData, } -impl Select { +impl fmt::Debug for Select { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "Select({:?})", self.filters) + } +} + +impl PartialEq for Select { + fn eq(&self, other: &Self) -> bool { + Selector(self.filters.clone()) == Selector(other.filters.clone()) + } +} + +impl Select { pub fn new(filters: Vec) -> Self { Self { filters, @@ -28,7 +41,9 @@ impl Select { { Selector(self.filters.clone()).is_related(&Selector(other.filters.clone())) } +} +impl Select { pub fn get(self, ctx: &Ipld) -> Result { self.filters .iter() @@ -129,23 +144,20 @@ impl FromStr for Select { } } -impl PartialOrd for Select { +impl PartialOrd for Select { fn partial_cmp(&self, other: &Self) -> Option { Selector(self.filters.clone()).partial_cmp(&Selector(other.filters.clone())) } } #[cfg(feature = "test_utils")] -impl Arbitrary for Select { - type Parameters = T::Parameters; +impl Arbitrary for Select { + type Parameters = (); type Strategy = BoxedStrategy; - fn arbitrary_with(t_params: Self::Parameters) -> Self::Strategy { - prop_oneof![ - T::arbitrary_with(t_params).prop_map(Select::Pure), - // FIXME add params that make this actually correspond to data - prop::collection::vec(Filter::arbitrary(), 1..10).prop_map(Select::Get), - ] - .boxed() + fn arbitrary_with(_: Self::Parameters) -> Self::Strategy { + prop::collection::vec(Filter::arbitrary(), 1..10) + .prop_map(Select::new) + .boxed() } } diff --git a/src/did/key/verifier.rs b/src/did/key/verifier.rs index 0a1be8d8..4aefcc40 100644 --- a/src/did/key/verifier.rs +++ b/src/did/key/verifier.rs @@ -2,6 +2,9 @@ use super::Signature; use blst::BLST_ERROR; use did_url::DID; use enum_as_inner::EnumAsInner; +use libipld_core::ipld::Ipld; +use multibase; +use multibase::Base; use rsa::pkcs1::{DecodeRsaPublicKey, EncodeRsaPublicKey}; use serde::{Deserialize, Serialize}; use signature as sig; @@ -126,71 +129,56 @@ impl signature::Verifier for Verifier { impl Display for Verifier { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - Verifier::EdDsa(ed25519_pk) => write!( - f, - "did:key:z6Mk{}", - bs58::encode(ed25519_pk.to_bytes()).into_string() - ), - Verifier::Es256k(secp256k1_pk) => write!( - f, - "did:key:zQ3s{}", - bs58::encode(secp256k1_pk.to_sec1_bytes()).into_string() - ), + let inner = match self { + Verifier::EdDsa(ed25519_pk) => { + let mut bytes = ed25519_pk.to_bytes().to_vec(); + let mut inner = vec![0xed]; + inner.append(&mut bytes); + multibase::encode(Base::Base58Btc, inner) + } + Verifier::Es256k(secp256k1_pk) => { + let mut bytes = secp256k1_pk.to_sec1_bytes().to_vec(); + let mut inner = vec![0xed]; + inner.append(&mut bytes); + multibase::encode(Base::Base58Btc, inner) + } Verifier::P256(p256_key) => { - write!( - f, - "did:key:zDn{}", - bs58::encode(p256_key.to_sec1_bytes()).into_string() - ) + multibase::encode(Base::Base58Btc, p256_key.to_sec1_bytes()) } - Verifier::P384(p384_key) => write!( - f, - "did:key:z82{}", - bs58::encode(p384_key.to_sec1_bytes()).into_string() - ), - Verifier::P521(p521_key) => write!( - f, - "did:key:z2J9{}", - bs58::encode(p521_key.0.to_encoded_point(true).as_bytes()).into_string() + Verifier::P384(p384_key) => { + multibase::encode(Base::Base58Btc, p384_key.to_sec1_bytes()) + } + Verifier::P521(p521_key) => multibase::encode( + Base::Base58Btc, + p521_key.0.to_encoded_point(true).as_bytes(), ), Verifier::Rs256(rsa2048_key) => { - write!( - f, - "did:key:z4MX{}", - bs58::encode( - rsa2048_key - .0 - .to_pkcs1_der() - .map_err(|_| std::fmt::Error)? // NOTE: technically should never fail - .as_bytes() - ) - .into_string() - ) - } - Verifier::Rs512(rsa4096_key) => write!( - f, - "did:key:zgg{}", - bs58::encode( - rsa4096_key + multibase::encode( + Base::Base58Btc, + rsa2048_key .0 .to_pkcs1_der() .map_err(|_| std::fmt::Error)? // NOTE: technically should never fail - .as_bytes() + .as_bytes(), ) - .into_string() - ), - Verifier::BlsMinPk(bls_minpk_pk) => write!( - f, - "did:key:zUC7{}", - bs58::encode(bls_minpk_pk.serialize()).into_string() - ), - Verifier::BlsMinSig(bls_minsig_pk) => write!( - f, - "did:key:zUC7{}", - bs58::encode(bls_minsig_pk.serialize()).into_string() + } + Verifier::Rs512(rsa4096_key) => multibase::encode( + Base::Base58Btc, + rsa4096_key + .0 + .to_pkcs1_der() + .map_err(|_| std::fmt::Error)? // NOTE: technically should never fail + .as_bytes(), ), - } + Verifier::BlsMinPk(bls_minpk_pk) => { + multibase::encode(Base::Base58Btc, bls_minpk_pk.serialize()) + } + Verifier::BlsMinSig(bls_minsig_pk) => { + multibase::encode(Base::Base58Btc, bls_minsig_pk.serialize()) + } + }; + + write!(f, "did:key:{}", inner) } } @@ -203,83 +191,115 @@ impl FromStr for Verifier { return Err(FromStrError::TooShort); } - match s.split_at(9) { - ("did:key:z", more) => { - let bytes = more.as_bytes(); - match bytes.split_at(2) { + match s.split_at(8) { + ("did:key:", more) => { + let (_base, varint_bytes): (multibase::Base, Vec) = + multibase::decode(more).map_err(|_| FromStrError::CannotDecodeMultibase)?; + + let bytes = varint_bytes.as_slice(); + + // FIXME also check max length on bytes + match varint_bytes.split_at(2) { ([0xed, _], _) => { - let vk = ed25519_dalek::VerifyingKey::try_from(&bytes[1..33]) + let mut buf = vec![]; + + let mut working: Vec = varint_bytes[1..].to_vec(); + + while !working.is_empty() { + let (x, xs) = + unsigned_varint::decode::u8(working.as_slice()).expect("FIXME"); + buf.push(x); + working = xs.to_vec(); + } + + dbg!(buf.clone().len()); + + let vk = ed25519_dalek::VerifyingKey::try_from(buf.as_slice()) .map_err(FromStrError::CannotParseEdDsa)?; - return Ok(Verifier::EdDsa(vk)); + Ok(Verifier::EdDsa(vk)) } ([0xe7, _], _) => { let vk = k256::ecdsa::VerifyingKey::from_sec1_bytes(&bytes[1..]) .map_err(FromStrError::CannotParseEs256k)?; - return Ok(Verifier::Es256k(vk)); + Ok(Verifier::Es256k(vk)) } ([0x12, 0x00], key_bytes) => { let vk = p256::ecdsa::VerifyingKey::from_sec1_bytes(key_bytes) .map_err(FromStrError::CannotParseP256)?; - return Ok(Verifier::P256(vk)); + Ok(Verifier::P256(vk)) } ([0x12, 0x01], key_bytes) => { let vk = p384::ecdsa::VerifyingKey::from_sec1_bytes(key_bytes) .map_err(FromStrError::CannotParseP384)?; - return Ok(Verifier::P384(vk)); + Ok(Verifier::P384(vk)) } ([0x12, 0x02], key_bytes) => { let vk = p521::ecdsa::VerifyingKey::from_sec1_bytes(key_bytes) .map_err(FromStrError::CannotParseP521)?; - return Ok(Verifier::P521(es512::VerifyingKey(vk))); + Ok(Verifier::P521(es512::VerifyingKey(vk))) } ([0x12, 0x05], key_bytes) => match key_bytes.len() { 2048 => { let vk = rsa::pkcs1v15::VerifyingKey::from_pkcs1_der(key_bytes) .map_err(FromStrError::CannotParseRs256)?; - return Ok(Verifier::Rs256(rs256::VerifyingKey(vk))); + Ok(Verifier::Rs256(rs256::VerifyingKey(vk))) } 4096 => { let vk = rsa::pkcs1v15::VerifyingKey::from_pkcs1_der(key_bytes) .map_err(FromStrError::CannotParseRs512)?; - return Ok(Verifier::Rs512(rs512::VerifyingKey(vk))); + Ok(Verifier::Rs512(rs512::VerifyingKey(vk))) } - word => return Err(FromStrError::NotADidKey(word)), + word => Err(FromStrError::NotADidKey(word)), }, ([0xeb, 0x01], pk_bytes) => match pk_bytes.len() { 48 => { let pk = blst::min_pk::PublicKey::deserialize(pk_bytes) .map_err(FromStrError::CannotParseBlsMinPk)?; - return Ok(Verifier::BlsMinPk(pk)); + Ok(Verifier::BlsMinPk(pk)) } 96 => { let pk = blst::min_sig::PublicKey::deserialize(pk_bytes) .map_err(FromStrError::CannotParseBlsMinSig)?; - return Ok(Verifier::BlsMinSig(pk)); + Ok(Verifier::BlsMinSig(pk)) } - word => return Err(FromStrError::UnexpectedPrefix([word].into())), + word => Err(FromStrError::UnexpectedPrefix([word].into())), }, - (word, _) => { - return Err(FromStrError::UnexpectedPrefix( - word.iter().map(|u| u.clone().into()).collect(), - )); - } + (word, _) => Err(FromStrError::UnexpectedPrefix( + word.iter().map(|u| u.clone().into()).collect(), + )), } } - (s, _) => { - return Err(FromStrError::UnexpectedPrefix( - s.to_string().chars().map(|u| u as usize).collect(), - )); - } + (s, _) => Err(FromStrError::UnexpectedPrefix( + s.to_string().chars().map(|u| u as usize).collect(), + )), + } + } +} + +impl From for Ipld { + fn from(v: Verifier) -> Self { + v.to_string().into() + } +} + +impl TryFrom for Verifier { + type Error = (); // FIXME + + fn try_from(ipld: Ipld) -> Result { + if let Ipld::String(s) = ipld { + Verifier::from_str(&s).map_err(|_| ()) + } else { + Err(()) } } } @@ -321,6 +341,46 @@ pub enum FromStrError { #[error("cannot parse BLS min sig key: {0:?}")] CannotParseBlsMinSig(BLST_ERROR), + + #[error("cannot decode multibase")] + CannotDecodeMultibase, +} + +impl PartialEq for FromStrError { + fn eq(&self, other: &Self) -> bool { + match (self, other) { + (FromStrError::NotADidKey(a), FromStrError::NotADidKey(b)) => a == b, + (FromStrError::UnexpectedPrefix(a), FromStrError::UnexpectedPrefix(b)) => a == b, + (FromStrError::TooShort, FromStrError::TooShort) => true, + (FromStrError::CannotParseEdDsa(a), FromStrError::CannotParseEdDsa(b)) => { + a.to_string() == b.to_string() + } + (FromStrError::CannotParseEs256k(a), FromStrError::CannotParseEs256k(b)) => { + a.to_string() == b.to_string() + } + (FromStrError::CannotParseP256(a), FromStrError::CannotParseP256(b)) => { + a.to_string() == b.to_string() + } + (FromStrError::CannotParseP384(a), FromStrError::CannotParseP384(b)) => { + a.to_string() == b.to_string() + } + (FromStrError::CannotParseP521(a), FromStrError::CannotParseP521(b)) => { + a.to_string() == b.to_string() + } + (FromStrError::CannotParseRs256(a), FromStrError::CannotParseRs256(b)) => { + a.to_string() == b.to_string() + } + (FromStrError::CannotParseRs512(a), FromStrError::CannotParseRs512(b)) => { + a.to_string() == b.to_string() + } + (FromStrError::CannotParseBlsMinPk(a), FromStrError::CannotParseBlsMinPk(b)) => a == b, + (FromStrError::CannotParseBlsMinSig(a), FromStrError::CannotParseBlsMinSig(b)) => { + a == b + } + (FromStrError::CannotDecodeMultibase, FromStrError::CannotDecodeMultibase) => true, + _ => false, + } + } } impl Serialize for Verifier { @@ -358,35 +418,61 @@ impl Arbitrary for Verifier { fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy { // NOTE these are just the test vectors from `did:key` v0.7 prop_oneof![ - // did:key - Just("did:key:z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK"), - - // secp256k1 - Just("did:key:zQ3shokFTS3brHcDQrn82RUDfCZESWL1ZdCEJwekUDPQiYBme"), - Just("did:key:zQ3shtxV1FrJfhqE1dvxYRcCknWNjHc3c5X1y3ZSoPDi2aur2"), - Just("did:key:zQ3shZc2QzApp2oymGvQbzP8eKheVshBHbU4ZYjeXqwSKEn6N"), - - // BLS - Just("did:key:zUC7K4ndUaGZgV7Cp2yJy6JtMoUHY6u7tkcSYUvPrEidqBmLCTLmi6d5WvwnUqejscAkERJ3bfjEiSYtdPkRSE8kSa11hFBr4sTgnbZ95SJj19PN2jdvJjyzpSZgxkyyxNnBNnY"), - Just("did:key:zUC7KKoJk5ttwuuc8pmQDiUmtckEPTwcaFVZe4DSFV7fURuoRnD17D3xkBK3A9tZqdADkTTMKSwNkhjo9Hs6HfgNUXo48TNRaxU6XPLSPdRgMc15jCD5DfN34ixjoVemY62JxnW"), - - // P-256 - Just("did:key:zDnaerDaTF5BXEavCrfRZEk316dpbLsfPDZ3WJ5hRTPFU2169"), - Just("did:key:zDnaerx9CtbPJ1q36T5Ln5wYt3MQYeGRG5ehnPAmxcf5mDZpv"), - - // P-384 - Just("did:key:z82Lm1MpAkeJcix9K8TMiLd5NMAhnwkjjCBeWHXyu3U4oT2MVJJKXkcVBgjGhnLBn2Kaau9"), - Just("did:key:z82LkvCwHNreneWpsgPEbV3gu1C6NFJEBg4srfJ5gdxEsMGRJUz2sG9FE42shbn2xkZJh54"), - - // P-521 - Just("did:key:z2J9gaYxrKVpdoG9A4gRnmpnRCcxU6agDtFVVBVdn1JedouoZN7SzcyREXXzWgt3gGiwpoHq7K68X4m32D8HgzG8wv3sY5j7"), - Just("did:key:z2J9gcGdb2nEyMDmzQYv2QZQcM1vXktvy1Pw4MduSWxGabLZ9XESSWLQgbuPhwnXN7zP7HpTzWqrMTzaY5zWe6hpzJ2jnw4f"), - - // RSA-2048 - Just("did:key:z4MXj1wBzi9jUstyPMS4jQqB6KdJaiatPkAtVtGc6bQEQEEsKTic4G7Rou3iBf9vPmT5dbkm9qsZsuVNjq8HCuW1w24nhBFGkRE4cd2Uf2tfrB3N7h4mnyPp1BF3ZttHTYv3DLUPi1zMdkULiow3M1GfXkoC6DoxDUm1jmN6GBj22SjVsr6dxezRVQc7aj9TxE7JLbMH1wh5X3kA58H3DFW8rnYMakFGbca5CB2Jf6CnGQZmL7o5uJAdTwXfy2iiiyPxXEGerMhHwhjTA1mKYobyk2CpeEcmvynADfNZ5MBvcCS7m3XkFCMNUYBS9NQ3fze6vMSUPsNa6GVYmKx2x6JrdEjCk3qRMMmyjnjCMfR4pXbRMZa3i"), + // ed25519 + Just("did:key:z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK"), + // secp256k1 + // Just("did:key:zQ3shokFTS3brHcDQrn82RUDfCZESWL1ZdCEJwekUDPQiYBme"), + // Just("did:key:zQ3shtxV1FrJfhqE1dvxYRcCknWNjHc3c5X1y3ZSoPDi2aur2"), + // Just("did:key:zQ3shZc2QzApp2oymGvQbzP8eKheVshBHbU4ZYjeXqwSKEn6N"), + + // // BLS + // Just("did:key:zUC7K4ndUaGZgV7Cp2yJy6JtMoUHY6u7tkcSYUvPrEidqBmLCTLmi6d5WvwnUqejscAkERJ3bfjEiSYtdPkRSE8kSa11hFBr4sTgnbZ95SJj19PN2jdvJjyzpSZgxkyyxNnBNnY"), + // Just("did:key:zUC7KKoJk5ttwuuc8pmQDiUmtckEPTwcaFVZe4DSFV7fURuoRnD17D3xkBK3A9tZqdADkTTMKSwNkhjo9Hs6HfgNUXo48TNRaxU6XPLSPdRgMc15jCD5DfN34ixjoVemY62JxnW"), + + // // P-256 + // Just("did:key:zDnaerDaTF5BXEavCrfRZEk316dpbLsfPDZ3WJ5hRTPFU2169"), + // Just("did:key:zDnaerx9CtbPJ1q36T5Ln5wYt3MQYeGRG5ehnPAmxcf5mDZpv"), + + // // P-384 + // Just("did:key:z82Lm1MpAkeJcix9K8TMiLd5NMAhnwkjjCBeWHXyu3U4oT2MVJJKXkcVBgjGhnLBn2Kaau9"), + // Just("did:key:z82LkvCwHNreneWpsgPEbV3gu1C6NFJEBg4srfJ5gdxEsMGRJUz2sG9FE42shbn2xkZJh54"), + + // // P-521 + // Just("did:key:z2J9gaYxrKVpdoG9A4gRnmpnRCcxU6agDtFVVBVdn1JedouoZN7SzcyREXXzWgt3gGiwpoHq7K68X4m32D8HgzG8wv3sY5j7"), + // Just("did:key:z2J9gcGdb2nEyMDmzQYv2QZQcM1vXktvy1Pw4MduSWxGabLZ9XESSWLQgbuPhwnXN7zP7HpTzWqrMTzaY5zWe6hpzJ2jnw4f"), + + // // RSA-2048 + // Just("did:key:z4MXj1wBzi9jUstyPMS4jQqB6KdJaiatPkAtVtGc6bQEQEEsKTic4G7Rou3iBf9vPmT5dbkm9qsZsuVNjq8HCuW1w24nhBFGkRE4cd2Uf2tfrB3N7h4mnyPp1BF3ZttHTYv3DLUPi1zMdkULiow3M1GfXkoC6DoxDUm1jmN6GBj22SjVsr6dxezRVQc7aj9TxE7JLbMH1wh5X3kA58H3DFW8rnYMakFGbca5CB2Jf6CnGQZmL7o5uJAdTwXfy2iiiyPxXEGerMhHwhjTA1mKYobyk2CpeEcmvynADfNZ5MBvcCS7m3XkFCMNUYBS9NQ3fze6vMSUPsNa6GVYmKx2x6JrdEjCk3qRMMmyjnjCMfR4pXbRMZa3i"), + + // // RSA-4096 + // Just("did:key:zgghBUVkqmWS8e1ioRVp2WN9Vw6x4NvnE9PGAyQsPqM3fnfPf8EdauiRVfBTcVDyzhqM5FFC7ekAvuV1cJHawtfgB9wDcru1hPDobk3hqyedijhgWmsYfJCmodkiiFnjNWATE7PvqTyoCjcmrc8yMRXmFPnoASyT5beUd4YZxTE9VfgmavcPy3BSouNmASMQ8xUXeiRwjb7xBaVTiDRjkmyPD7NYZdXuS93gFhyDFr5b3XLg7Rfj9nHEqtHDa7NmAX7iwDAbMUFEfiDEf9hrqZmpAYJracAjTTR8Cvn6mnDXMLwayNG8dcsXFodxok2qksYF4D8ffUxMRmyyQVQhhhmdSi4YaMPqTnC1J6HTG9Yfb98yGSVaWi4TApUhLXFow2ZvB6vqckCNhjCRL2R4MDUSk71qzxWHgezKyDeyThJgdxydrn1osqH94oSeA346eipkJvKqYREXBKwgB5VL6WF4qAK6sVZxJp2dQBfCPVZ4EbsBQaJXaVK7cNcWG8tZBFWZ79gG9Cu6C4u8yjBS8Ux6dCcJPUTLtixQu4z2n5dCsVSNdnP1EEs8ZerZo5pBgc68w4Yuf9KL3xVxPnAB1nRCBfs9cMU6oL1EdyHbqrTfnjE8HpY164akBqe92LFVsk8RusaGsVPrMekT8emTq5y8v8CabuZg5rDs3f9NPEtogjyx49wiub1FecM5B7QqEcZSYiKHgF4mfkteT2") + ] + .prop_map(|s: &str| Verifier::from_str(s).expect("did:key spec test vectors to work")) + .boxed() + } +} - // RSA-4096 - Just("did:key:zgghBUVkqmWS8e1ioRVp2WN9Vw6x4NvnE9PGAyQsPqM3fnfPf8EdauiRVfBTcVDyzhqM5FFC7ekAvuV1cJHawtfgB9wDcru1hPDobk3hqyedijhgWmsYfJCmodkiiFnjNWATE7PvqTyoCjcmrc8yMRXmFPnoASyT5beUd4YZxTE9VfgmavcPy3BSouNmASMQ8xUXeiRwjb7xBaVTiDRjkmyPD7NYZdXuS93gFhyDFr5b3XLg7Rfj9nHEqtHDa7NmAX7iwDAbMUFEfiDEf9hrqZmpAYJracAjTTR8Cvn6mnDXMLwayNG8dcsXFodxok2qksYF4D8ffUxMRmyyQVQhhhmdSi4YaMPqTnC1J6HTG9Yfb98yGSVaWi4TApUhLXFow2ZvB6vqckCNhjCRL2R4MDUSk71qzxWHgezKyDeyThJgdxydrn1osqH94oSeA346eipkJvKqYREXBKwgB5VL6WF4qAK6sVZxJp2dQBfCPVZ4EbsBQaJXaVK7cNcWG8tZBFWZ79gG9Cu6C4u8yjBS8Ux6dCcJPUTLtixQu4z2n5dCsVSNdnP1EEs8ZerZo5pBgc68w4Yuf9KL3xVxPnAB1nRCBfs9cMU6oL1EdyHbqrTfnjE8HpY164akBqe92LFVsk8RusaGsVPrMekT8emTq5y8v8CabuZg5rDs3f9NPEtogjyx49wiub1FecM5B7QqEcZSYiKHgF4mfkteT2") - ].prop_map(|s: &str| Verifier::from_str(s).expect("did:key spec test vectors to work")).boxed() +#[cfg(test)] +mod tests { + use super::*; + use assert_matches::assert_matches; + use pretty_assertions as pretty; + use proptest::prelude::*; + use testresult::TestResult; + + mod serialization { + use super::*; + + #[test_log::test] + fn test_string_round_trip() -> TestResult { + proptest!(ProptestConfig::with_cases(100), |(v: Verifier)| { + dbg!(v.clone()); + dbg!(v.to_string()); + let s = v.to_string(); + let observed = Verifier::from_str(&s); + pretty::assert_eq!(Ok(v), observed); + }); + Ok(()) + } } } diff --git a/src/did/preset.rs b/src/did/preset.rs index 0b590412..635540f7 100644 --- a/src/did/preset.rs +++ b/src/did/preset.rs @@ -2,9 +2,13 @@ use super::key; use super::Did; use did_url::DID; use enum_as_inner::EnumAsInner; +use libipld_core::ipld::Ipld; use serde::{Deserialize, Serialize}; use std::{fmt::Display, str::FromStr}; +#[cfg(feature = "test_utils")] +use proptest::prelude::*; + /// The set of [`Did`] types that ship with this library ("presets"). #[derive(Debug, Clone, EnumAsInner, PartialEq, PartialOrd, Ord, Eq, Serialize, Deserialize)] #[serde(untagged)] @@ -15,6 +19,24 @@ pub enum Verifier { // FIXME Dns(did_url::DID), } +impl From for Ipld { + fn from(verifier: Verifier) -> Self { + match verifier { + Verifier::Key(verifier) => verifier.into(), + } + } +} + +impl TryFrom for Verifier { + type Error = (); // FIXME + + fn try_from(ipld: Ipld) -> Result { + key::Verifier::try_from(ipld) + .map(Verifier::Key) + .map_err(|_| ()) + } +} + impl From for DID { fn from(verifier: Verifier) -> Self { match verifier { @@ -73,3 +95,17 @@ impl FromStr for Verifier { key::Verifier::from_str(s).map(Verifier::Key) } } + +#[cfg(feature = "test_utils")] +impl Arbitrary for Verifier { + type Parameters = (); + type Strategy = BoxedStrategy; + + fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy { + prop_oneof![ + key::Verifier::arbitrary().prop_map(Verifier::Key), + // FIXME did_url::DID::arbitrary().prop_map(Verifier::Dns), + ] + .boxed() + } +} diff --git a/src/invocation/agent.rs b/src/invocation/agent.rs index daf0ba2d..a4e5f7fd 100644 --- a/src/invocation/agent.rs +++ b/src/invocation/agent.rs @@ -608,13 +608,10 @@ mod tests { server: crate::did::preset::Verifier, server_signer: crate::did::preset::Signer, device: crate::did::preset::Verifier, - device_signer: crate::did::preset::Signer, dnslink: crate::did::preset::Verifier, - dnslink_signer: crate::did::preset::Signer, } fn setup_test_chain() -> Result> { - let (_nbf, now, exp) = setup_valid_time(); let (server, server_signer) = gen_did(); let (account, account_signer) = gen_did(); let (device, device_signer) = gen_did(); @@ -729,9 +726,7 @@ mod tests { server, server_signer, device, - device_signer, dnslink, - dnslink_signer, }) } diff --git a/src/ipld/number.rs b/src/ipld/number.rs index aced2a53..16c66d71 100644 --- a/src/ipld/number.rs +++ b/src/ipld/number.rs @@ -36,7 +36,10 @@ impl PartialOrd for Number { impl From for Ipld { fn from(number: Number) -> Self { - number.into() + match number { + Number::Float(f) => Ipld::Float(f), + Number::Integer(i) => Ipld::Integer(i), + } } } diff --git a/src/lib.rs b/src/lib.rs index 1c21c44a..a793a1ab 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,9 +1,9 @@ #![cfg_attr(docsrs, feature(doc_cfg))] #![warn( - missing_debug_implementations, + // FIXME missing_debug_implementations, future_incompatible, let_underscore, - missing_docs, + // FIXME missing_docs, rust_2021_compatibility, nonstandard_style )] diff --git a/src/time/timestamp.rs b/src/time/timestamp.rs index 2e0f60c4..e6b94e03 100644 --- a/src/time/timestamp.rs +++ b/src/time/timestamp.rs @@ -175,6 +175,11 @@ impl Arbitrary for Timestamp { type Strategy = BoxedStrategy; fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy { - any::().prop_map(Timestamp::postel).boxed() + (0..(u64::pow(2, 53) - 1)) + .prop_map(|secs| { + Timestamp::new(UNIX_EPOCH + Duration::from_secs(secs)) + .expect("the current time to be somtime in the 3rd millenium CE") + }) + .boxed() } } From 507507e03b648fe2f31e72a228f635deb7260a61 Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Sat, 16 Mar 2024 04:13:28 -0700 Subject: [PATCH 157/188] DID key test vectors all succeed --- proptest-regressions/did/key/verifier.txt | 3 + src/did/key/verifier.rs | 229 +++++++++++++--------- 2 files changed, 138 insertions(+), 94 deletions(-) diff --git a/proptest-regressions/did/key/verifier.txt b/proptest-regressions/did/key/verifier.txt index 875277c2..a7844e07 100644 --- a/proptest-regressions/did/key/verifier.txt +++ b/proptest-regressions/did/key/verifier.txt @@ -5,3 +5,6 @@ # It is recommended to check this file in to source control so that # everyone who runs the test benefits from these saved cases. cc 8b31cbe3dc5bbf493b0a5297de083508d8a9d06338cf64ddad5fd31cdd3f2995 # shrinks to v = EdDsa(VerifyingKey(CompressedEdwardsY: [46, 111, 204, 227, 103, 1, 220, 121, 20, 136, 224, 208, 177, 116, 92, 193, 227, 58, 76, 28, 159, 204, 65, 198, 59, 211, 67, 219, 190, 9, 112, 230]), EdwardsPoint{ X: FieldElement51([1689611602193863, 490607132032821, 1343312146746774, 1090732682789050, 1815270510391065]), Y: FieldElement51([1127445621927726, 1752742079139643, 335263251657170, 455073811812238, 1802102173971517]), Z: FieldElement51([1, 0, 0, 0, 0]), T: FieldElement51([396516250482892, 1271770325328148, 2066179188049959, 970219954360817, 1259266248234093]) })) +cc 6ea19e6cd197da3336d5d42920c4e50ec90c702cae2ae5eea9ab5551cb46b702 # shrinks to v = Es256k(VerifyingKey { inner: PublicKey { point: AffinePoint { x: FieldElement(FieldElementImpl { value: FieldElement5x52([1160505078199757, 2400895128406859, 1056877774124305, 252036312840791, 148760852692386]), magnitude: 1, normalized: true }), y: FieldElement(FieldElementImpl { value: FieldElement5x52([1672816167198465, 3802075178366285, 1757485316837790, 3300793713825435, 65287802880411]), magnitude: 1, normalized: true }), infinity: 0 } } }) +cc b13317f06a1831e069511de070b2052b483170979a76512a545fc48786999a88 # shrinks to v = P521(VerifyingKey) +cc 49cfb2af63ad904088cd463d88858fec93d6eee6cb3e9f1e6dea921ed3bc3036 # shrinks to v = BlsMinSig(PublicKey { point: blst_p2_affine { x: blst_fp2 { fp: [blst_fp { l: [9365464022634913364, 2329345083104304319, 3580502722068130739, 9972366962423794647, 10538782185862554521, 127471334147109992] }, blst_fp { l: [17485101970769344385, 9326044252933771301, 8127522796136333012, 5303128957032042441, 15450070391313250774, 324970645356438016] }] }, y: blst_fp2 { fp: [blst_fp { l: [15398138763294838789, 13417264315786022471, 12534786867121880739, 17975267190571784400, 7951956564642531166, 1555059392695113505] }, blst_fp { l: [2922977713681283026, 6798107204462524282, 8480534491177560369, 8514377350654754744, 15498869007308154143, 1289849395897073958] }] } } }) diff --git a/src/did/key/verifier.rs b/src/did/key/verifier.rs index 4aefcc40..16563cb3 100644 --- a/src/did/key/verifier.rs +++ b/src/did/key/verifier.rs @@ -131,50 +131,96 @@ impl Display for Verifier { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { let inner = match self { Verifier::EdDsa(ed25519_pk) => { - let mut bytes = ed25519_pk.to_bytes().to_vec(); - let mut inner = vec![0xed]; - inner.append(&mut bytes); - multibase::encode(Base::Base58Btc, inner) + let mut buf = [0u8; 2]; + let tag = unsigned_varint::encode::u8(0xed, &mut buf); + + let mut payload: Vec = tag.to_vec(); + let bytes = ed25519_pk.to_bytes(); + payload.extend_from_slice(&bytes); + + multibase::encode(Base::Base58Btc, payload) } Verifier::Es256k(secp256k1_pk) => { - let mut bytes = secp256k1_pk.to_sec1_bytes().to_vec(); - let mut inner = vec![0xed]; - inner.append(&mut bytes); - multibase::encode(Base::Base58Btc, inner) + let mut buf = [0u8; 2]; + let tag = unsigned_varint::encode::u8(0xe7, &mut buf); + + let mut payload = tag.to_vec(); + let bytes = secp256k1_pk.to_sec1_bytes(); + payload.extend_from_slice(&bytes); + + multibase::encode(Base::Base58Btc, payload) } Verifier::P256(p256_key) => { - multibase::encode(Base::Base58Btc, p256_key.to_sec1_bytes()) + let mut buf = [0u8; 3]; + let tag = unsigned_varint::encode::u16(0x1200, &mut buf); + + let mut payload = tag.to_vec(); + let bytes = p256_key.to_sec1_bytes(); + payload.extend_from_slice(&bytes); + + multibase::encode(Base::Base58Btc, payload) } Verifier::P384(p384_key) => { - multibase::encode(Base::Base58Btc, p384_key.to_sec1_bytes()) + let mut buf = [0u8; 3]; + let tag = unsigned_varint::encode::u16(0x1201, &mut buf); + + let mut payload = tag.to_vec(); + let bytes = p384_key.to_sec1_bytes(); + payload.extend_from_slice(&bytes); + + multibase::encode(Base::Base58Btc, payload) + } + Verifier::P521(p521_key) => { + let mut buf = [0u8; 3]; + let tag = unsigned_varint::encode::u16(0x1202, &mut buf); + + let mut payload = tag.to_vec(); + let raw = p521_key.0.to_encoded_point(true); + payload.extend_from_slice(raw.as_bytes()); + + multibase::encode(Base::Base58Btc, payload) } - Verifier::P521(p521_key) => multibase::encode( - Base::Base58Btc, - p521_key.0.to_encoded_point(true).as_bytes(), - ), Verifier::Rs256(rsa2048_key) => { - multibase::encode( - Base::Base58Btc, - rsa2048_key - .0 - .to_pkcs1_der() - .map_err(|_| std::fmt::Error)? // NOTE: technically should never fail - .as_bytes(), - ) + let mut buf = [0u8; 3]; + let tag = unsigned_varint::encode::u16(0x1205, &mut buf); + + let mut payload = tag.to_vec(); + let raw = rsa2048_key.0.to_pkcs1_der().map_err(|_| std::fmt::Error)?; // NOTE: technically should never fail + payload.extend_from_slice(raw.as_bytes()); + + multibase::encode(Base::Base58Btc, payload) + } + Verifier::Rs512(rsa4096_key) => { + let mut buf = [0u8; 3]; + let tag = unsigned_varint::encode::u16(0x1205, &mut buf); + + let mut payload = tag.to_vec(); + let raw = rsa4096_key.0.to_pkcs1_der().map_err(|_| std::fmt::Error)?; // NOTE: technically should never fail + payload.extend_from_slice(raw.as_bytes()); + + multibase::encode(Base::Base58Btc, payload) } - Verifier::Rs512(rsa4096_key) => multibase::encode( - Base::Base58Btc, - rsa4096_key - .0 - .to_pkcs1_der() - .map_err(|_| std::fmt::Error)? // NOTE: technically should never fail - .as_bytes(), - ), Verifier::BlsMinPk(bls_minpk_pk) => { - multibase::encode(Base::Base58Btc, bls_minpk_pk.serialize()) + let bytes = bls_minpk_pk.compress(); + + let mut buf = [0u8; 2]; + let tag = unsigned_varint::encode::u8(0xeb, &mut buf); + + let mut payload = tag.to_vec(); + payload.extend_from_slice(&bytes); + + multibase::encode(Base::Base58Btc, payload) } Verifier::BlsMinSig(bls_minsig_pk) => { - multibase::encode(Base::Base58Btc, bls_minsig_pk.serialize()) + let bytes = bls_minsig_pk.compress(); + + let mut buf = [0u8; 2]; + let tag = unsigned_varint::encode::u8(0xeb, &mut buf); + + let mut payload = tag.to_vec(); + payload.extend_from_slice(&bytes); + + multibase::encode(Base::Base58Btc, payload) } }; @@ -196,92 +242,80 @@ impl FromStr for Verifier { let (_base, varint_bytes): (multibase::Base, Vec) = multibase::decode(more).map_err(|_| FromStrError::CannotDecodeMultibase)?; - let bytes = varint_bytes.as_slice(); + let (tag, rest) = unsigned_varint::decode::u16(&varint_bytes) + .map_err(|_| FromStrError::CannotDecodeMultibase)?; // FIXME also check max length on bytes - match varint_bytes.split_at(2) { - ([0xed, _], _) => { - let mut buf = vec![]; - - let mut working: Vec = varint_bytes[1..].to_vec(); + match tag { + 0xed => { + let arr: [u8; 32] = rest.try_into().map_err(|_| FromStrError::TooShort)?; - while !working.is_empty() { - let (x, xs) = - unsigned_varint::decode::u8(working.as_slice()).expect("FIXME"); - buf.push(x); - working = xs.to_vec(); - } - - dbg!(buf.clone().len()); - - let vk = ed25519_dalek::VerifyingKey::try_from(buf.as_slice()) + let vk = ed25519_dalek::VerifyingKey::from_bytes(&arr) .map_err(FromStrError::CannotParseEdDsa)?; Ok(Verifier::EdDsa(vk)) } - ([0xe7, _], _) => { - let vk = k256::ecdsa::VerifyingKey::from_sec1_bytes(&bytes[1..]) + 0xe7 => { + let vk = k256::ecdsa::VerifyingKey::from_sec1_bytes(&rest) .map_err(FromStrError::CannotParseEs256k)?; Ok(Verifier::Es256k(vk)) } - ([0x12, 0x00], key_bytes) => { - let vk = p256::ecdsa::VerifyingKey::from_sec1_bytes(key_bytes) + 0x1200 => { + let vk = p256::ecdsa::VerifyingKey::from_sec1_bytes(rest) .map_err(FromStrError::CannotParseP256)?; Ok(Verifier::P256(vk)) } - ([0x12, 0x01], key_bytes) => { - let vk = p384::ecdsa::VerifyingKey::from_sec1_bytes(key_bytes) + 0x1201 => { + let vk = p384::ecdsa::VerifyingKey::from_sec1_bytes(rest) .map_err(FromStrError::CannotParseP384)?; Ok(Verifier::P384(vk)) } - ([0x12, 0x02], key_bytes) => { - let vk = p521::ecdsa::VerifyingKey::from_sec1_bytes(key_bytes) + 0x1202 => { + let vk = p521::ecdsa::VerifyingKey::from_sec1_bytes(rest) .map_err(FromStrError::CannotParseP521)?; Ok(Verifier::P521(es512::VerifyingKey(vk))) } - ([0x12, 0x05], key_bytes) => match key_bytes.len() { - 2048 => { - let vk = rsa::pkcs1v15::VerifyingKey::from_pkcs1_der(key_bytes) + 0x1205 => match rest.len() { + // 256-bytes plus params + 270 => { + let vk = rsa::pkcs1v15::VerifyingKey::from_pkcs1_der(rest) .map_err(FromStrError::CannotParseRs256)?; Ok(Verifier::Rs256(rs256::VerifyingKey(vk))) } - 4096 => { - let vk = rsa::pkcs1v15::VerifyingKey::from_pkcs1_der(key_bytes) + // 512-bytes plus params + 526 => { + let vk = rsa::pkcs1v15::VerifyingKey::from_pkcs1_der(rest) .map_err(FromStrError::CannotParseRs512)?; Ok(Verifier::Rs512(rs512::VerifyingKey(vk))) } - word => Err(FromStrError::NotADidKey(word)), + len => Err(FromStrError::InvalidRsaLength(len)), }, - ([0xeb, 0x01], pk_bytes) => match pk_bytes.len() { + 0xeb => match rest.len() { 48 => { - let pk = blst::min_pk::PublicKey::deserialize(pk_bytes) + let pk = blst::min_pk::PublicKey::deserialize(rest) .map_err(FromStrError::CannotParseBlsMinPk)?; Ok(Verifier::BlsMinPk(pk)) } 96 => { - let pk = blst::min_sig::PublicKey::deserialize(pk_bytes) + let pk = blst::min_sig::PublicKey::deserialize(rest) .map_err(FromStrError::CannotParseBlsMinSig)?; Ok(Verifier::BlsMinSig(pk)) } - word => Err(FromStrError::UnexpectedPrefix([word].into())), + len => Err(FromStrError::InvalidBlsLength(len)), }, - (word, _) => Err(FromStrError::UnexpectedPrefix( - word.iter().map(|u| u.clone().into()).collect(), - )), + word => Err(FromStrError::UnexpectedPrefix(word)), } } - (s, _) => Err(FromStrError::UnexpectedPrefix( - s.to_string().chars().map(|u| u as usize).collect(), - )), + (s, _) => Err(FromStrError::UnexpectedHeader(s.to_string())), } } } @@ -310,7 +344,16 @@ pub enum FromStrError { NotADidKey(usize), #[error("unexpected prefix: {0:?}")] - UnexpectedPrefix(Vec), + UnexpectedPrefix(u16), + + #[error("unexpected header: {0}")] + UnexpectedHeader(String), + + #[error("unexpected BLS length: {0}")] + InvalidBlsLength(usize), + + #[error("Invalid RSA length: {0}")] + InvalidRsaLength(usize), #[error("key too short")] TooShort, @@ -421,31 +464,31 @@ impl Arbitrary for Verifier { // ed25519 Just("did:key:z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK"), // secp256k1 - // Just("did:key:zQ3shokFTS3brHcDQrn82RUDfCZESWL1ZdCEJwekUDPQiYBme"), - // Just("did:key:zQ3shtxV1FrJfhqE1dvxYRcCknWNjHc3c5X1y3ZSoPDi2aur2"), - // Just("did:key:zQ3shZc2QzApp2oymGvQbzP8eKheVshBHbU4ZYjeXqwSKEn6N"), + Just("did:key:zQ3shokFTS3brHcDQrn82RUDfCZESWL1ZdCEJwekUDPQiYBme"), + Just("did:key:zQ3shtxV1FrJfhqE1dvxYRcCknWNjHc3c5X1y3ZSoPDi2aur2"), + Just("did:key:zQ3shZc2QzApp2oymGvQbzP8eKheVshBHbU4ZYjeXqwSKEn6N"), - // // BLS - // Just("did:key:zUC7K4ndUaGZgV7Cp2yJy6JtMoUHY6u7tkcSYUvPrEidqBmLCTLmi6d5WvwnUqejscAkERJ3bfjEiSYtdPkRSE8kSa11hFBr4sTgnbZ95SJj19PN2jdvJjyzpSZgxkyyxNnBNnY"), - // Just("did:key:zUC7KKoJk5ttwuuc8pmQDiUmtckEPTwcaFVZe4DSFV7fURuoRnD17D3xkBK3A9tZqdADkTTMKSwNkhjo9Hs6HfgNUXo48TNRaxU6XPLSPdRgMc15jCD5DfN34ixjoVemY62JxnW"), + // BLS + Just("did:key:zUC7K4ndUaGZgV7Cp2yJy6JtMoUHY6u7tkcSYUvPrEidqBmLCTLmi6d5WvwnUqejscAkERJ3bfjEiSYtdPkRSE8kSa11hFBr4sTgnbZ95SJj19PN2jdvJjyzpSZgxkyyxNnBNnY"), + Just("did:key:zUC7KKoJk5ttwuuc8pmQDiUmtckEPTwcaFVZe4DSFV7fURuoRnD17D3xkBK3A9tZqdADkTTMKSwNkhjo9Hs6HfgNUXo48TNRaxU6XPLSPdRgMc15jCD5DfN34ixjoVemY62JxnW"), - // // P-256 - // Just("did:key:zDnaerDaTF5BXEavCrfRZEk316dpbLsfPDZ3WJ5hRTPFU2169"), - // Just("did:key:zDnaerx9CtbPJ1q36T5Ln5wYt3MQYeGRG5ehnPAmxcf5mDZpv"), + // P-256 + Just("did:key:zDnaerDaTF5BXEavCrfRZEk316dpbLsfPDZ3WJ5hRTPFU2169"), + Just("did:key:zDnaerx9CtbPJ1q36T5Ln5wYt3MQYeGRG5ehnPAmxcf5mDZpv"), - // // P-384 - // Just("did:key:z82Lm1MpAkeJcix9K8TMiLd5NMAhnwkjjCBeWHXyu3U4oT2MVJJKXkcVBgjGhnLBn2Kaau9"), - // Just("did:key:z82LkvCwHNreneWpsgPEbV3gu1C6NFJEBg4srfJ5gdxEsMGRJUz2sG9FE42shbn2xkZJh54"), + // P-384 + Just("did:key:z82Lm1MpAkeJcix9K8TMiLd5NMAhnwkjjCBeWHXyu3U4oT2MVJJKXkcVBgjGhnLBn2Kaau9"), + Just("did:key:z82LkvCwHNreneWpsgPEbV3gu1C6NFJEBg4srfJ5gdxEsMGRJUz2sG9FE42shbn2xkZJh54"), - // // P-521 - // Just("did:key:z2J9gaYxrKVpdoG9A4gRnmpnRCcxU6agDtFVVBVdn1JedouoZN7SzcyREXXzWgt3gGiwpoHq7K68X4m32D8HgzG8wv3sY5j7"), - // Just("did:key:z2J9gcGdb2nEyMDmzQYv2QZQcM1vXktvy1Pw4MduSWxGabLZ9XESSWLQgbuPhwnXN7zP7HpTzWqrMTzaY5zWe6hpzJ2jnw4f"), + // P-521 + Just("did:key:z2J9gaYxrKVpdoG9A4gRnmpnRCcxU6agDtFVVBVdn1JedouoZN7SzcyREXXzWgt3gGiwpoHq7K68X4m32D8HgzG8wv3sY5j7"), + Just("did:key:z2J9gcGdb2nEyMDmzQYv2QZQcM1vXktvy1Pw4MduSWxGabLZ9XESSWLQgbuPhwnXN7zP7HpTzWqrMTzaY5zWe6hpzJ2jnw4f"), - // // RSA-2048 - // Just("did:key:z4MXj1wBzi9jUstyPMS4jQqB6KdJaiatPkAtVtGc6bQEQEEsKTic4G7Rou3iBf9vPmT5dbkm9qsZsuVNjq8HCuW1w24nhBFGkRE4cd2Uf2tfrB3N7h4mnyPp1BF3ZttHTYv3DLUPi1zMdkULiow3M1GfXkoC6DoxDUm1jmN6GBj22SjVsr6dxezRVQc7aj9TxE7JLbMH1wh5X3kA58H3DFW8rnYMakFGbca5CB2Jf6CnGQZmL7o5uJAdTwXfy2iiiyPxXEGerMhHwhjTA1mKYobyk2CpeEcmvynADfNZ5MBvcCS7m3XkFCMNUYBS9NQ3fze6vMSUPsNa6GVYmKx2x6JrdEjCk3qRMMmyjnjCMfR4pXbRMZa3i"), + // RSA-2048 + Just("did:key:z4MXj1wBzi9jUstyPMS4jQqB6KdJaiatPkAtVtGc6bQEQEEsKTic4G7Rou3iBf9vPmT5dbkm9qsZsuVNjq8HCuW1w24nhBFGkRE4cd2Uf2tfrB3N7h4mnyPp1BF3ZttHTYv3DLUPi1zMdkULiow3M1GfXkoC6DoxDUm1jmN6GBj22SjVsr6dxezRVQc7aj9TxE7JLbMH1wh5X3kA58H3DFW8rnYMakFGbca5CB2Jf6CnGQZmL7o5uJAdTwXfy2iiiyPxXEGerMhHwhjTA1mKYobyk2CpeEcmvynADfNZ5MBvcCS7m3XkFCMNUYBS9NQ3fze6vMSUPsNa6GVYmKx2x6JrdEjCk3qRMMmyjnjCMfR4pXbRMZa3i"), - // // RSA-4096 - // Just("did:key:zgghBUVkqmWS8e1ioRVp2WN9Vw6x4NvnE9PGAyQsPqM3fnfPf8EdauiRVfBTcVDyzhqM5FFC7ekAvuV1cJHawtfgB9wDcru1hPDobk3hqyedijhgWmsYfJCmodkiiFnjNWATE7PvqTyoCjcmrc8yMRXmFPnoASyT5beUd4YZxTE9VfgmavcPy3BSouNmASMQ8xUXeiRwjb7xBaVTiDRjkmyPD7NYZdXuS93gFhyDFr5b3XLg7Rfj9nHEqtHDa7NmAX7iwDAbMUFEfiDEf9hrqZmpAYJracAjTTR8Cvn6mnDXMLwayNG8dcsXFodxok2qksYF4D8ffUxMRmyyQVQhhhmdSi4YaMPqTnC1J6HTG9Yfb98yGSVaWi4TApUhLXFow2ZvB6vqckCNhjCRL2R4MDUSk71qzxWHgezKyDeyThJgdxydrn1osqH94oSeA346eipkJvKqYREXBKwgB5VL6WF4qAK6sVZxJp2dQBfCPVZ4EbsBQaJXaVK7cNcWG8tZBFWZ79gG9Cu6C4u8yjBS8Ux6dCcJPUTLtixQu4z2n5dCsVSNdnP1EEs8ZerZo5pBgc68w4Yuf9KL3xVxPnAB1nRCBfs9cMU6oL1EdyHbqrTfnjE8HpY164akBqe92LFVsk8RusaGsVPrMekT8emTq5y8v8CabuZg5rDs3f9NPEtogjyx49wiub1FecM5B7QqEcZSYiKHgF4mfkteT2") + // RSA-4096 + Just("did:key:zgghBUVkqmWS8e1ioRVp2WN9Vw6x4NvnE9PGAyQsPqM3fnfPf8EdauiRVfBTcVDyzhqM5FFC7ekAvuV1cJHawtfgB9wDcru1hPDobk3hqyedijhgWmsYfJCmodkiiFnjNWATE7PvqTyoCjcmrc8yMRXmFPnoASyT5beUd4YZxTE9VfgmavcPy3BSouNmASMQ8xUXeiRwjb7xBaVTiDRjkmyPD7NYZdXuS93gFhyDFr5b3XLg7Rfj9nHEqtHDa7NmAX7iwDAbMUFEfiDEf9hrqZmpAYJracAjTTR8Cvn6mnDXMLwayNG8dcsXFodxok2qksYF4D8ffUxMRmyyQVQhhhmdSi4YaMPqTnC1J6HTG9Yfb98yGSVaWi4TApUhLXFow2ZvB6vqckCNhjCRL2R4MDUSk71qzxWHgezKyDeyThJgdxydrn1osqH94oSeA346eipkJvKqYREXBKwgB5VL6WF4qAK6sVZxJp2dQBfCPVZ4EbsBQaJXaVK7cNcWG8tZBFWZ79gG9Cu6C4u8yjBS8Ux6dCcJPUTLtixQu4z2n5dCsVSNdnP1EEs8ZerZo5pBgc68w4Yuf9KL3xVxPnAB1nRCBfs9cMU6oL1EdyHbqrTfnjE8HpY164akBqe92LFVsk8RusaGsVPrMekT8emTq5y8v8CabuZg5rDs3f9NPEtogjyx49wiub1FecM5B7QqEcZSYiKHgF4mfkteT2") ] .prop_map(|s: &str| Verifier::from_str(s).expect("did:key spec test vectors to work")) .boxed() @@ -466,8 +509,6 @@ mod tests { #[test_log::test] fn test_string_round_trip() -> TestResult { proptest!(ProptestConfig::with_cases(100), |(v: Verifier)| { - dbg!(v.clone()); - dbg!(v.to_string()); let s = v.to_string(); let observed = Verifier::from_str(&s); pretty::assert_eq!(Ok(v), observed); From 3bba844f80c46fdb66fb43025a088b398c9a2a15 Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Sat, 16 Mar 2024 11:43:01 -0700 Subject: [PATCH 158/188] Manualy test all DID test vectors --- src/did/key/verifier.rs | 127 +++++++++++++++++++++++++++++++------- src/invocation.rs | 2 +- src/invocation/payload.rs | 5 -- src/invocation/store.rs | 4 +- src/receipt.rs | 2 +- 5 files changed, 109 insertions(+), 31 deletions(-) diff --git a/src/did/key/verifier.rs b/src/did/key/verifier.rs index 16563cb3..e120d05b 100644 --- a/src/did/key/verifier.rs +++ b/src/did/key/verifier.rs @@ -155,8 +155,8 @@ impl Display for Verifier { let tag = unsigned_varint::encode::u16(0x1200, &mut buf); let mut payload = tag.to_vec(); - let bytes = p256_key.to_sec1_bytes(); - payload.extend_from_slice(&bytes); + let point = p256_key.to_encoded_point(true); + payload.extend_from_slice(point.as_bytes()); multibase::encode(Base::Base58Btc, payload) } @@ -165,8 +165,8 @@ impl Display for Verifier { let tag = unsigned_varint::encode::u16(0x1201, &mut buf); let mut payload = tag.to_vec(); - let bytes = p384_key.to_sec1_bytes(); - payload.extend_from_slice(&bytes); + let point = p384_key.to_encoded_point(true); + payload.extend_from_slice(point.as_bytes()); multibase::encode(Base::Base58Btc, payload) } @@ -175,8 +175,10 @@ impl Display for Verifier { let tag = unsigned_varint::encode::u16(0x1202, &mut buf); let mut payload = tag.to_vec(); - let raw = p521_key.0.to_encoded_point(true); - payload.extend_from_slice(raw.as_bytes()); + let point = p521_key.0.to_encoded_point(true); + payload.extend_from_slice(point.as_bytes()); + + dbg!(&payload); multibase::encode(Base::Base58Btc, payload) } @@ -239,11 +241,15 @@ impl FromStr for Verifier { match s.split_at(8) { ("did:key:", more) => { - let (_base, varint_bytes): (multibase::Base, Vec) = - multibase::decode(more).map_err(|_| FromStrError::CannotDecodeMultibase)?; + match multibase::decode(more) { + Ok(_) => {} + Err(_) => { + dbg!(more); + } + } + let (_base, varint_bytes): (multibase::Base, Vec) = multibase::decode(more)?; - let (tag, rest) = unsigned_varint::decode::u16(&varint_bytes) - .map_err(|_| FromStrError::CannotDecodeMultibase)?; + let (tag, rest) = unsigned_varint::decode::u16(&varint_bytes)?; // FIXME also check max length on bytes match tag { @@ -385,8 +391,11 @@ pub enum FromStrError { #[error("cannot parse BLS min sig key: {0:?}")] CannotParseBlsMinSig(BLST_ERROR), - #[error("cannot decode multibase")] - CannotDecodeMultibase, + #[error("cannot decode multibase: {0}")] + CannotDecodeMultibase(#[from] multibase::Error), + + #[error("cannot parse tag: {0}")] + CannotParseTag(#[from] unsigned_varint::decode::Error), } impl PartialEq for FromStrError { @@ -420,7 +429,11 @@ impl PartialEq for FromStrError { (FromStrError::CannotParseBlsMinSig(a), FromStrError::CannotParseBlsMinSig(b)) => { a == b } - (FromStrError::CannotDecodeMultibase, FromStrError::CannotDecodeMultibase) => true, + ( + FromStrError::CannotDecodeMultibase(lhs), + FromStrError::CannotDecodeMultibase(rhs), + ) => lhs == rhs, + (FromStrError::CannotParseTag(lhs), FromStrError::CannotParseTag(rhs)) => lhs == rhs, _ => false, } } @@ -463,6 +476,7 @@ impl Arbitrary for Verifier { prop_oneof![ // ed25519 Just("did:key:z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK"), + // secp256k1 Just("did:key:zQ3shokFTS3brHcDQrn82RUDfCZESWL1ZdCEJwekUDPQiYBme"), Just("did:key:zQ3shtxV1FrJfhqE1dvxYRcCknWNjHc3c5X1y3ZSoPDi2aur2"), @@ -498,22 +512,91 @@ impl Arbitrary for Verifier { #[cfg(test)] mod tests { use super::*; - use assert_matches::assert_matches; use pretty_assertions as pretty; - use proptest::prelude::*; use testresult::TestResult; mod serialization { use super::*; - #[test_log::test] - fn test_string_round_trip() -> TestResult { - proptest!(ProptestConfig::with_cases(100), |(v: Verifier)| { - let s = v.to_string(); - let observed = Verifier::from_str(&s); - pretty::assert_eq!(Ok(v), observed); - }); + fn roundtrip(s: &str) -> TestResult { + let v = Verifier::from_str(s)?; + let serialized = v.to_string(); + pretty::assert_eq!(s, serialized); Ok(()) } + + #[test_log::test] + fn test_ed25519_parse() -> TestResult { + roundtrip("did:key:z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK") + } + + #[test_log::test] + fn test_secp256k_1_parse() -> TestResult { + roundtrip("did:key:zQ3shokFTS3brHcDQrn82RUDfCZESWL1ZdCEJwekUDPQiYBme") + } + + #[test_log::test] + fn test_secp256k_2_parse() -> TestResult { + roundtrip("did:key:zQ3shtxV1FrJfhqE1dvxYRcCknWNjHc3c5X1y3ZSoPDi2aur2") + } + + #[test_log::test] + fn test_secp256k_3_parse() -> TestResult { + roundtrip("did:key:zQ3shZc2QzApp2oymGvQbzP8eKheVshBHbU4ZYjeXqwSKEn6N") + } + + #[test_log::test] + fn test_bls_1_parse() -> TestResult { + roundtrip("did:key:zUC7K4ndUaGZgV7Cp2yJy6JtMoUHY6u7tkcSYUvPrEidqBmLCTLmi6d5WvwnUqejscAkERJ3bfjEiSYtdPkRSE8kSa11hFBr4sTgnbZ95SJj19PN2jdvJjyzpSZgxkyyxNnBNnY") + } + + #[test_log::test] + fn test_bls_2_parse() -> TestResult { + roundtrip("did:key:zUC7KKoJk5ttwuuc8pmQDiUmtckEPTwcaFVZe4DSFV7fURuoRnD17D3xkBK3A9tZqdADkTTMKSwNkhjo9Hs6HfgNUXo48TNRaxU6XPLSPdRgMc15jCD5DfN34ixjoVemY62JxnW") + } + + #[test_log::test] + fn test_p256_1_parse() -> TestResult { + roundtrip("did:key:zDnaerDaTF5BXEavCrfRZEk316dpbLsfPDZ3WJ5hRTPFU2169") + } + + #[test_log::test] + fn test_p256_2_parse() -> TestResult { + roundtrip("did:key:zDnaerx9CtbPJ1q36T5Ln5wYt3MQYeGRG5ehnPAmxcf5mDZpv") + } + + #[test_log::test] + fn test_p384_1_parse() -> TestResult { + roundtrip( + "did:key:z82Lm1MpAkeJcix9K8TMiLd5NMAhnwkjjCBeWHXyu3U4oT2MVJJKXkcVBgjGhnLBn2Kaau9", + ) + } + + #[test_log::test] + fn test_p384_2_parse() -> TestResult { + roundtrip( + "did:key:z82LkvCwHNreneWpsgPEbV3gu1C6NFJEBg4srfJ5gdxEsMGRJUz2sG9FE42shbn2xkZJh54", + ) + } + + #[test_log::test] + fn test_p521_1_parse() -> TestResult { + roundtrip("did:key:z2J9gaYxrKVpdoG9A4gRnmpnRCcxU6agDtFVVBVdn1JedouoZN7SzcyREXXzWgt3gGiwpoHq7K68X4m32D8HgzG8wv3sY5j7") + } + + #[test_log::test] + fn test_p521_2_parse() -> TestResult { + roundtrip("did:key:z2J9gcGdb2nEyMDmzQYv2QZQcM1vXktvy1Pw4MduSWxGabLZ9XESSWLQgbuPhwnXN7zP7HpTzWqrMTzaY5zWe6hpzJ2jnw4f") + } + + #[test_log::test] + fn test_rs256_parse() -> TestResult { + roundtrip("did:key:z4MXj1wBzi9jUstyPMS4jQqB6KdJaiatPkAtVtGc6bQEQEEsKTic4G7Rou3iBf9vPmT5dbkm9qsZsuVNjq8HCuW1w24nhBFGkRE4cd2Uf2tfrB3N7h4mnyPp1BF3ZttHTYv3DLUPi1zMdkULiow3M1GfXkoC6DoxDUm1jmN6GBj22SjVsr6dxezRVQc7aj9TxE7JLbMH1wh5X3kA58H3DFW8rnYMakFGbca5CB2Jf6CnGQZmL7o5uJAdTwXfy2iiiyPxXEGerMhHwhjTA1mKYobyk2CpeEcmvynADfNZ5MBvcCS7m3XkFCMNUYBS9NQ3fze6vMSUPsNa6GVYmKx2x6JrdEjCk3qRMMmyjnjCMfR4pXbRMZa3i") + } + + #[test_log::test] + fn test_rs512_parse() -> TestResult { + roundtrip("did:key:zgghBUVkqmWS8e1ioRVp2WN9Vw6x4NvnE9PGAyQsPqM3fnfPf8EdauiRVfBTcVDyzhqM5FFC7ekAvuV1cJHawtfgB9wDcru1hPDobk3hqyedijhgWmsYfJCmodkiiFnjNWATE7PvqTyoCjcmrc8yMRXmFPnoASyT5beUd4YZxTE9VfgmavcPy3BSouNmASMQ8xUXeiRwjb7xBaVTiDRjkmyPD7NYZdXuS93gFhyDFr5b3XLg7Rfj9nHEqtHDa7NmAX7iwDAbMUFEfiDEf9hrqZmpAYJracAjTTR8Cvn6mnDXMLwayNG8dcsXFodxok2qksYF4D8ffUxMRmyyQVQhhhmdSi4YaMPqTnC1J6HTG9Yfb98yGSVaWi4TApUhLXFow2ZvB6vqckCNhjCRL2R4MDUSk71qzxWHgezKyDeyThJgdxydrn1osqH94oSeA346eipkJvKqYREXBKwgB5VL6WF4qAK6sVZxJp2dQBfCPVZ4EbsBQaJXaVK7cNcWG8tZBFWZ79gG9Cu6C4u8yjBS8Ux6dCcJPUTLtixQu4z2n5dCsVSNdnP1EEs8ZerZo5pBgc68w4Yuf9KL3xVxPnAB1nRCBfs9cMU6oL1EdyHbqrTfnjE8HpY164akBqe92LFVsk8RusaGsVPrMekT8emTq5y8v8CabuZg5rDs3f9NPEtogjyx49wiub1FecM5B7QqEcZSYiKHgF4mfkteT2") + } } } diff --git a/src/invocation.rs b/src/invocation.rs index 1fa3a899..4c486070 100644 --- a/src/invocation.rs +++ b/src/invocation.rs @@ -154,7 +154,7 @@ impl, C: Codec + TryFrom + Into> did for Invocation { fn verifier(&self) -> &DID { - &self.verifier() + &self.payload.verifier() } } diff --git a/src/invocation/payload.rs b/src/invocation/payload.rs index a16c77fb..40300b89 100644 --- a/src/invocation/payload.rs +++ b/src/invocation/payload.rs @@ -502,7 +502,6 @@ impl TryFrom> for Payload TryFrom> for Payload audience = Some(DID::from_str(s.as_str()).map_err(|_| ())?), _ => return Err(()), }, - "via" => match v { - Ipld::String(s) => via = Some(DID::from_str(s.as_str()).map_err(|_| ())?), - _ => return Err(()), - }, "cmd" => match v { Ipld::String(s) => command = Some(s), _ => return Err(()), diff --git a/src/invocation/store.rs b/src/invocation/store.rs index b7acbd36..26618909 100644 --- a/src/invocation/store.rs +++ b/src/invocation/store.rs @@ -40,7 +40,7 @@ impl< cid: Cid, ) -> Result>, >::InvocationStoreError> { - (*self).get(cid) + (**self).get(cid) } fn put( @@ -48,7 +48,7 @@ impl< cid: Cid, invocation: Invocation, ) -> Result<(), >::InvocationStoreError> { - (*self).put(cid, invocation) + (**self).put(cid, invocation) } } diff --git a/src/receipt.rs b/src/receipt.rs index 38fbf4dd..537d5335 100644 --- a/src/receipt.rs +++ b/src/receipt.rs @@ -41,7 +41,7 @@ impl, C: Codec + TryFrom + Into did::Verifiable for Receipt { fn verifier(&self) -> &DID { - &self.verifier() + &self.payload.verifier() } } From 2ef67b3e55874aa08f8fec048e6ed606789eb84e Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Sat, 16 Mar 2024 14:17:49 -0700 Subject: [PATCH 159/188] working on filter parsing edge cases with proptests --- Cargo.toml | 3 +- .../delegation/policy/selector/filter.txt | 7 ++ src/delegation/payload.rs | 21 +++-- src/delegation/policy.rs | 2 +- src/delegation/policy/predicate.rs | 90 +++++++++++++----- src/delegation/policy/selector.rs | 29 +++++- src/delegation/policy/selector/filter.rs | 94 ++++++++++++++++--- src/delegation/policy/selector/select.rs | 9 +- src/did/key/verifier.rs | 9 -- src/ipld/number.rs | 9 +- 10 files changed, 210 insertions(+), 63 deletions(-) create mode 100644 proptest-regressions/delegation/policy/selector/filter.txt diff --git a/Cargo.toml b/Cargo.toml index 770279b8..3df1b306 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -38,6 +38,8 @@ aquamarine = { version = "0.5", optional = true } # Encoding multibase = "0.9" base64 = "0.21" +nom = "7.1" +nom-unicode = "0.3" # Crypto blst = { version = "0.3.11", optional = true, default-features = false } @@ -58,7 +60,6 @@ libipld = { version = "0.16", optional = true } libipld-cbor = "0.16" libipld-core = { version = "0.16", features = ["serde-codec"] } multihash = { version = "0.18" } -nom = "7.1" nonempty = { version = "0.9" } p256 = { version = "0.13.2", features = ["alloc", "ecdsa"], optional = true, default-features = false } p384 = { version = "0.13.0", features = ["alloc", "ecdsa"], optional = true, default-features = false } diff --git a/proptest-regressions/delegation/policy/selector/filter.txt b/proptest-regressions/delegation/policy/selector/filter.txt new file mode 100644 index 00000000..a478d733 --- /dev/null +++ b/proptest-regressions/delegation/policy/selector/filter.txt @@ -0,0 +1,7 @@ +# Seeds for failure cases proptest has generated in the past. It is +# automatically read and these particular cases re-run before any +# novel cases are generated. +# +# It is recommended to check this file in to source control so that +# everyone who runs the test benefits from these saved cases. +cc e85947ad7c94a9c29fc57391d4e52cc023b1648997f55f55b2e980afaa3616db # shrinks to filter = ArrayIndex(0) diff --git a/src/delegation/payload.rs b/src/delegation/payload.rs index e3486eac..5b7f4901 100644 --- a/src/delegation/payload.rs +++ b/src/delegation/payload.rs @@ -1,4 +1,4 @@ -use super::policy::Predicate; +use super::policy::{predicate, Predicate}; use crate::ability::arguments::Named; use crate::time; use crate::{ @@ -169,10 +169,14 @@ where }, "pol" => match ipld { Ipld::List(xs) => { - policy = xs - .iter() - .map(|x| Predicate::try_from(x.clone()).ok()) - .collect(); + let result: Result, ParseError> = + xs.iter().try_fold(vec![], |mut acc, ipld| { + let pred = Predicate::try_from(ipld.clone())?; + acc.push(pred); + Ok(acc) + }); + + policy = Some(result?); } bad => return Err(ParseError::WrongTypeForField("pol".to_string(), bad)), }, @@ -252,6 +256,9 @@ where #[error("Cannot parse timestamp: {0}")] BadTimestamp(#[from] time::OutOfRangeError), + + #[error("Cannot parse policy predicate: {0}")] + InvalidPolicy(#[from] predicate::FromIpldError), } impl From> for Ipld { @@ -435,9 +442,7 @@ mod tests { let ipld: Ipld = payload.clone().into(); let parsed = Payload::::try_from(ipld); - dbg!(parsed); - - // assert_matches!(parsed, Ok(payload)); + assert_matches!(parsed, Ok(payload)); }); Ok(()) diff --git a/src/delegation/policy.rs b/src/delegation/policy.rs index ae88cdef..2bbc2abe 100644 --- a/src/delegation/policy.rs +++ b/src/delegation/policy.rs @@ -6,5 +6,5 @@ pub mod selector; -mod predicate; +pub mod predicate; pub use predicate::*; diff --git a/src/delegation/policy/predicate.rs b/src/delegation/policy/predicate.rs index 0f65eacb..abfb96a5 100644 --- a/src/delegation/policy/predicate.rs +++ b/src/delegation/policy/predicate.rs @@ -5,6 +5,7 @@ use enum_as_inner::EnumAsInner; use libipld_core::ipld::Ipld; use serde::{Deserialize, Serialize}; use std::{fmt, str::FromStr}; +use thiserror::Error; #[cfg(feature = "test_utils")] use proptest::prelude::*; @@ -692,7 +693,7 @@ pub fn glob(input: &String, pattern: &String) -> bool { } impl TryFrom for Predicate { - type Error = (); // FIXME + type Error = FromIpldError; fn try_from(ipld: Ipld) -> Result { match ipld { @@ -703,58 +704,71 @@ impl TryFrom for Predicate { } [Ipld::String(op_str), Ipld::String(sel_str), val] => match op_str.as_str() { "==" => { - let sel = - Select::::from_str(sel_str.as_str()).map_err(|_| ())?; + let sel = Select::::from_str(sel_str.as_str()) + .map_err(FromIpldError::InvalidIpldSelector)?; Ok(Predicate::Equal(sel, ipld::Newtype(val.clone()))) } ">" => { - let sel = - Select::::from_str(sel_str.as_str()).map_err(|_| ())?; + let sel = Select::::from_str(sel_str.as_str()) + .map_err(FromIpldError::InvalidNumberSelector)?; + + let num = ipld::Number::try_from(val.clone()) + .map_err(FromIpldError::CannotParseIpldNumber)?; - let num = ipld::Number::try_from(val.clone())?; Ok(Predicate::GreaterThan(sel, num)) } ">=" => { - let sel = - Select::::from_str(sel_str.as_str()).map_err(|_| ())?; - let num = ipld::Number::try_from(val.clone())?; + let sel = Select::::from_str(sel_str.as_str()) + .map_err(FromIpldError::InvalidNumberSelector)?; + + let num = ipld::Number::try_from(val.clone()) + .map_err(FromIpldError::CannotParseIpldNumber)?; Ok(Predicate::GreaterThanOrEqual(sel, num)) } "<" => { - let sel = - Select::::from_str(sel_str.as_str()).map_err(|_| ())?; - let num = ipld::Number::try_from(val.clone())?; + let sel = Select::::from_str(sel_str.as_str()) + .map_err(FromIpldError::InvalidNumberSelector)?; + + let num = ipld::Number::try_from(val.clone()) + .map_err(FromIpldError::CannotParseIpldNumber)?; + Ok(Predicate::LessThan(sel, num)) } "<=" => { - let sel = - Select::::from_str(sel_str.as_str()).map_err(|_| ())?; - let num = ipld::Number::try_from(val.clone())?; + let sel = Select::::from_str(sel_str.as_str()) + .map_err(FromIpldError::InvalidNumberSelector)?; + + let num = ipld::Number::try_from(val.clone()) + .map_err(FromIpldError::CannotParseIpldNumber)?; + Ok(Predicate::LessThanOrEqual(sel, num)) } "like" => { - let sel = Select::::from_str(sel_str.as_str()).map_err(|_| ())?; + let sel = Select::::from_str(sel_str.as_str()) + .map_err(FromIpldError::InvalidStringSelector)?; + if let Ipld::String(s) = val { Ok(Predicate::Like(sel, s.to_string())) } else { - Err(()) + Err(FromIpldError::NotAString(val.clone())) } } "every" => { let sel = Select::::from_str(sel_str.as_str()) - .map_err(|_| ())?; + .map_err(FromIpldError::InvalidCollectionSelector)?; let p = Box::new(Predicate::try_from(val.clone())?); Ok(Predicate::Every(sel, p)) } "some" => { let sel = Select::::from_str(sel_str.as_str()) - .map_err(|_| ())?; + .map_err(FromIpldError::InvalidCollectionSelector)?; + let p = Box::new(Predicate::try_from(val.clone())?); Ok(Predicate::Some(sel, p)) } - _ => Err(()), + _ => Err(FromIpldError::UnrecognizedTripleTag(op_str.to_string())), }, [Ipld::String(op_str), lhs, rhs] => match op_str.as_str() { "and" => { @@ -767,15 +781,45 @@ impl TryFrom for Predicate { let rhs = Box::new(Predicate::try_from(rhs.clone())?); Ok(Predicate::Or(lhs, rhs)) } - _ => Err(()), + _ => Err(FromIpldError::UnrecognizedTripleTag(op_str.to_string())), }, - _ => Err(()), + _ => Err(FromIpldError::UnrecognizedShape), }, - _ => Err(()), + _ => Err(FromIpldError::NotATuple(ipld)), } } } +#[derive(Debug, PartialEq, Error)] +pub enum FromIpldError { + #[error("Invalid Ipld selector {0:?}")] + InvalidIpldSelector( as FromStr>::Err), + + #[error("Invalid ipld::Number selector {0:?}")] + InvalidNumberSelector( as FromStr>::Err), + + #[error("Invalid ipld::Collection selector {0:?}")] + InvalidCollectionSelector( as FromStr>::Err), + + #[error("Invalid String selector {0:?}")] + InvalidStringSelector( as FromStr>::Err), + + #[error("Cannot parse ipld::Number {0:?}")] + CannotParseIpldNumber(>::Error), + + #[error("Not a string: {0:?}")] + NotAString(Ipld), + + #[error("Unrecognized triple tag {0}")] + UnrecognizedTripleTag(String), + + #[error("Unrecognized shape")] + UnrecognizedShape, + + #[error("Not a predicate tuple {0:?}")] + NotATuple(Ipld), +} + impl From for Ipld { fn from(p: Predicate) -> Self { match p { diff --git a/src/delegation/policy/selector.rs b/src/delegation/policy/selector.rs index e88cd17f..3ca846be 100644 --- a/src/delegation/policy/selector.rs +++ b/src/delegation/policy/selector.rs @@ -147,7 +147,11 @@ impl fmt::Display for Selector { let mut ops = self.0.iter(); if let Some(field) = ops.next() { - field.fmt(f)?; + if !field.is_dot_field() { + write!(f, ".")?; + } + + write!(f, "{}", field)?; } else { write!(f, ".")?; } @@ -164,10 +168,27 @@ impl FromStr for Selector { type Err = nom::Err; fn from_str(s: &str) -> Result { - match parse(s).map_err(|e| nom::Err::Failure(ParseError::UnknownPattern(e.to_string())))? { - ("", selector) => Ok(selector), - (rest, _) => Err(nom::Err::Failure(ParseError::TrailingInput(rest.into()))), + if s == "." { + return Ok(Selector(vec![])); } + + todo!(); + + // let mut working = s; + // let mut acc = vec![]; + + // alt((parse_dot_field, tag('.')); + + // match many0(filter::parse)(s) { + // Ok((rest, ops)) => { + // dbg!(rest); + // dbg!(&ops); + // Ok(Selector(ops)) + // } + // // Ok(("", ops)) => Ok(Selector(ops)), + // // Ok((rest, _)) => Err(nom::Err::Error(ParseError::TrailingInput(rest.to_string()))), + // Err(err) => Err(err.map(|input| ParseError::UnknownPattern(input.to_string()))), + // } } } diff --git a/src/delegation/policy/selector/filter.rs b/src/delegation/policy/selector/filter.rs index a1e054a3..0173b8b1 100644 --- a/src/delegation/policy/selector/filter.rs +++ b/src/delegation/policy/selector/filter.rs @@ -37,6 +37,20 @@ impl Filter { _ => false, } } + + pub fn is_dot_field(&self) -> bool { + match self { + Filter::Field(k) => { + if let Some(first) = k.chars().next() { + (first.is_alphabetic() || first == '_') + && k.chars().all(|c| char::is_alphanumeric(c) || c == '_') + } else { + false + } + } + _ => false, + } + } } impl fmt::Display for Filter { @@ -45,7 +59,10 @@ impl fmt::Display for Filter { Filter::ArrayIndex(i) => write!(f, "[{}]", i), Filter::Field(k) => { if let Some(first) = k.chars().next() { - if first.is_alphabetic() && k.chars().all(char::is_alphanumeric) { + if (first.is_alphabetic() || first == '_') + && k.is_ascii() + && k.chars().all(|c| char::is_alphanumeric(c) || c == '_') + { write!(f, ".{}", k) } else { write!(f, "[\"{}\"]", k) @@ -61,16 +78,19 @@ impl fmt::Display for Filter { } pub fn parse(input: &str) -> IResult<&str, Filter> { + dbg!("PARSE", input); let p = alt((parse_try, parse_non_try)); context("selector_op", p)(input) } pub fn parse_non_try(input: &str) -> IResult<&str, Filter> { + dbg!("NON_TRY", input); let p = alt((parse_values, parse_array_index, parse_field)); context("non_try", p)(input) } pub fn parse_try(input: &str) -> IResult<&str, Filter> { + dbg!("TRY", input); let p = map_res(terminated(parse_non_try, tag("?")), |found: Filter| { Ok::(Filter::Try(Box::new(found))) }); @@ -79,12 +99,11 @@ pub fn parse_try(input: &str) -> IResult<&str, Filter> { } pub fn parse_array_index(input: &str) -> IResult<&str, Filter> { - let num = map_opt(tag("-"), |s: &str| { - let (_rest, matched) = digit1::<&str, ()>(s).ok()?; - matched.parse::().ok() - }); + dbg!("ARRAY_INDEX", input); + let num = nom::combinator::recognize(preceded(nom::combinator::opt(tag("-")), digit1)); - let array_index = map_res(delimited(char('['), num, char(']')), |idx| { + let array_index = map_res(delimited(char('['), num, char(']')), |found| { + let idx = i32::from_str(found).map_err(|_| ())?; Ok::(Filter::ArrayIndex(idx)) }); @@ -92,21 +111,26 @@ pub fn parse_array_index(input: &str) -> IResult<&str, Filter> { } pub fn parse_values(input: &str) -> IResult<&str, Filter> { + dbg!("VALUES", input); context("values", tag("[]"))(input).map(|(rest, _)| (rest, Filter::Values)) } pub fn parse_field(input: &str) -> IResult<&str, Filter> { + dbg!("FIELD", input); let p = alt((parse_delim_field, parse_dot_field)); context("map_field", p)(input) } pub fn parse_dot_field(input: &str) -> IResult<&str, Filter> { + dbg!("DOT", input); let p = alt((parse_dot_alpha_field, parse_dot_underscore_field)); + context("dot_field", p)(input) } pub fn parse_dot_alpha_field(input: &str) -> IResult<&str, Filter> { + dbg!("DOT_ALPHA", input); let p = map_res(preceded(char('.'), alphanumeric1), |found: &str| { Ok::(Filter::Field(found.to_string())) }); @@ -115,6 +139,7 @@ pub fn parse_dot_alpha_field(input: &str) -> IResult<&str, Filter> { } pub fn parse_dot_underscore_field(input: &str) -> IResult<&str, Filter> { + dbg!("DOT_UNDERSCORE", input); let p = map_res(preceded(tag("._"), alphanumeric1), |found: &str| { let key = format!("{}{}", '_', found); Ok::(Filter::Field(key)) @@ -123,13 +148,34 @@ pub fn parse_dot_underscore_field(input: &str) -> IResult<&str, Filter> { context("dot_field", p)(input) } -pub fn parse_delim_field(input: &str) -> IResult<&str, Filter> { - let p = map_res(delimited(tag("[\""), many1(anychar), tag("\"]")), |found| { - let field = String::from_iter(found); - Ok::(Filter::Field(field)) +pub fn parse_empty_quotes_field(input: &str) -> IResult<&str, Filter> { + dbg!("EMPTY_QUOTES", input); + let p = map_res(tag("[\"\"]"), |_: &str| { + Ok::(Filter::Field("".to_string())) }); - context("delimited_field", p)(input) + context("empty_quotes_field", p)(input) +} + +pub fn unicode_or_whitespace(input: &str) -> IResult<&str, Vec> { + nom::multi::many0(nom::character::complete::satisfy(|c| { + nom_unicode::is_alphanumeric(c) || c == ' ' + }))(input) +} + +pub fn parse_delim_field(input: &str) -> IResult<&str, Filter> { + dbg!("DELIM", input); + let p = map_res( + delimited(tag("[\""), unicode_or_whitespace, tag("\"]")), + |found: Vec| { + dbg!("$$$$$$$$$$$$$$$$$$$", &found); + + let field = found.iter().collect::(); + Ok::(Filter::Field(field)) + }, + ); + + context("delimited_field", alt((p, parse_empty_quotes_field)))(input) } impl FromStr for Filter { @@ -137,8 +183,10 @@ impl FromStr for Filter { fn from_str(s: &str) -> Result { match parse(s).map_err(|e| nom::Err::Failure(ParseError::UnknownPattern(e.to_string())))? { - ("", found) => Ok(found), - (rest, _) => Err(nom::Err::Failure(ParseError::TrailingInput(rest.into()))), + (_, found) => Ok(found), + // ("", found) => Ok(found), + // FIXME + // (rest, _) => Err(nom::Err::Failure(ParseError::TrailingInput(rest.into()))), } } } @@ -164,9 +212,29 @@ impl Arbitrary for Filter { prop_oneof![ i32::arbitrary().prop_map(|i| Filter::ArrayIndex(i)), String::arbitrary().prop_map(Filter::Field), + "[a-zA-Z_][a-zA-Z0-9_]*".prop_map(Filter::Field), Just(Filter::Values), // FIXME prop_recursive::lazy(|_| { Filter::arbitrary_with(()).prop_map(Filter::Try) }), ] .boxed() } } + +#[cfg(test)] +mod tests { + use super::*; + use proptest::prelude::*; + + mod serialization { + use super::*; + + proptest! { + #[test] + fn test_filter_round_trip(filter: Filter) { + let serialized = filter.to_string(); + let deserialized = serialized.parse(); + prop_assert_eq!(Ok(filter), deserialized); + } + } + } +} diff --git a/src/delegation/policy/selector/select.rs b/src/delegation/policy/selector/select.rs index aec6fd31..8be9fb08 100644 --- a/src/delegation/policy/selector/select.rs +++ b/src/delegation/policy/selector/select.rs @@ -5,6 +5,7 @@ use serde::{Deserialize, Serialize}; use std::cmp::Ordering; use std::fmt; use std::str::FromStr; +use thiserror::Error; #[cfg(feature = "test_utils")] use proptest::prelude::*; @@ -133,10 +134,10 @@ where } impl FromStr for Select { - type Err = (); + type Err = ParseError; fn from_str(s: &str) -> Result { - let selector = Selector::from_str(s).map_err(|_| ())?; + let selector = Selector::from_str(s).map_err(ParseError)?; Ok(Select { filters: selector.0, _marker: std::marker::PhantomData, @@ -144,6 +145,10 @@ impl FromStr for Select { } } +#[derive(Debug, PartialEq, Error)] +#[error("Failed to parse selector: {0}")] +pub struct ParseError(#[from] nom::Err); + impl PartialOrd for Select { fn partial_cmp(&self, other: &Self) -> Option { Selector(self.filters.clone()).partial_cmp(&Selector(other.filters.clone())) diff --git a/src/did/key/verifier.rs b/src/did/key/verifier.rs index e120d05b..4dcced0e 100644 --- a/src/did/key/verifier.rs +++ b/src/did/key/verifier.rs @@ -178,8 +178,6 @@ impl Display for Verifier { let point = p521_key.0.to_encoded_point(true); payload.extend_from_slice(point.as_bytes()); - dbg!(&payload); - multibase::encode(Base::Base58Btc, payload) } Verifier::Rs256(rsa2048_key) => { @@ -241,14 +239,7 @@ impl FromStr for Verifier { match s.split_at(8) { ("did:key:", more) => { - match multibase::decode(more) { - Ok(_) => {} - Err(_) => { - dbg!(more); - } - } let (_base, varint_bytes): (multibase::Base, Vec) = multibase::decode(more)?; - let (tag, rest) = unsigned_varint::decode::u16(&varint_bytes)?; // FIXME also check max length on bytes diff --git a/src/ipld/number.rs b/src/ipld/number.rs index 16c66d71..e7c3c611 100644 --- a/src/ipld/number.rs +++ b/src/ipld/number.rs @@ -3,6 +3,7 @@ use enum_as_inner::EnumAsInner; use libipld_core::{error::SerdeError, ipld::Ipld, serde as ipld_serde}; use serde_derive::{Deserialize, Serialize}; +use thiserror::Error; #[cfg(feature = "test_utils")] use proptest::prelude::*; @@ -44,17 +45,21 @@ impl From for Ipld { } impl TryFrom for Number { - type Error = (); // FIXME + type Error = NotANumber; fn try_from(ipld: Ipld) -> Result { match ipld { Ipld::Integer(i) => Ok(Number::Integer(i)), Ipld::Float(f) => Ok(Number::Float(f)), - _ => Err(()), + _ => Err(NotANumber(ipld)), } } } +#[derive(Debug, Clone, PartialEq, Error)] +#[error("Expected Ipld numeric, got: {0:?}")] +pub struct NotANumber(Ipld); + impl From for Number { fn from(i: i128) -> Number { Number::Integer(i) From 02b37bdb5f61173078fdb75d934c0855d7b9fc84 Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Sat, 16 Mar 2024 17:11:31 -0700 Subject: [PATCH 160/188] I guess manually parsing is the right thing sometimes --- .../delegation/policy/selector.txt | 8 ++ .../delegation/policy/selector/filter.txt | 1 + src/delegation/policy/selector.rs | 77 +++++++--- src/delegation/policy/selector/error.rs | 3 + src/delegation/policy/selector/filter.rs | 132 +++++++++++++----- 5 files changed, 167 insertions(+), 54 deletions(-) create mode 100644 proptest-regressions/delegation/policy/selector.txt diff --git a/proptest-regressions/delegation/policy/selector.txt b/proptest-regressions/delegation/policy/selector.txt new file mode 100644 index 00000000..caf39773 --- /dev/null +++ b/proptest-regressions/delegation/policy/selector.txt @@ -0,0 +1,8 @@ +# Seeds for failure cases proptest has generated in the past. It is +# automatically read and these particular cases re-run before any +# novel cases are generated. +# +# It is recommended to check this file in to source control so that +# everyone who runs the test benefits from these saved cases. +cc 7d58a6b190e172b6e8336a5ee53de75608728b67f299b38738cac62d7bfc9633 # shrinks to sel = Selector([ArrayIndex(0)]) +cc a896af81439f52a08c2a281bfeebaf4427d3ee97a99f77440deee54c8a8ae7ff # shrinks to sel = Selector([Field("_")]) diff --git a/proptest-regressions/delegation/policy/selector/filter.txt b/proptest-regressions/delegation/policy/selector/filter.txt index a478d733..11f79cd7 100644 --- a/proptest-regressions/delegation/policy/selector/filter.txt +++ b/proptest-regressions/delegation/policy/selector/filter.txt @@ -5,3 +5,4 @@ # It is recommended to check this file in to source control so that # everyone who runs the test benefits from these saved cases. cc e85947ad7c94a9c29fc57391d4e52cc023b1648997f55f55b2e980afaa3616db # shrinks to filter = ArrayIndex(0) +cc 86f7fcbfdc14a9aa56666c04370cbb2d82532c332ef68e6f4657edc672991bf7 # shrinks to filter = Field("A_") diff --git a/src/delegation/policy/selector.rs b/src/delegation/policy/selector.rs index 3ca846be..0489fa5a 100644 --- a/src/delegation/policy/selector.rs +++ b/src/delegation/policy/selector.rs @@ -27,6 +27,9 @@ use std::cmp::Ordering; use std::{fmt, str::FromStr}; use thiserror::Error; +#[cfg(feature = "test_utils")] +use proptest::prelude::*; + #[derive(Debug, Clone, PartialEq, Default)] pub struct Selector(pub Vec); @@ -168,30 +171,39 @@ impl FromStr for Selector { type Err = nom::Err; fn from_str(s: &str) -> Result { - if s == "." { - return Ok(Selector(vec![])); + if !s.starts_with(".") { + return Err(nom::Err::Error(ParseError::MissingStartingDot( + s.to_string(), + ))); } - todo!(); + if s.len() == 0 { + return Err(nom::Err::Error(ParseError::MissingStartingDot( + s.to_string(), + ))); + } - // let mut working = s; - // let mut acc = vec![]; + let working; + let mut acc = vec![]; - // alt((parse_dot_field, tag('.')); + if let Ok((more, found)) = filter::parse_dot_field(s) { + working = more; + acc.push(found); + } else { + working = &s[1..]; + } - // match many0(filter::parse)(s) { - // Ok((rest, ops)) => { - // dbg!(rest); - // dbg!(&ops); - // Ok(Selector(ops)) - // } - // // Ok(("", ops)) => Ok(Selector(ops)), - // // Ok((rest, _)) => Err(nom::Err::Error(ParseError::TrailingInput(rest.to_string()))), - // Err(err) => Err(err.map(|input| ParseError::UnknownPattern(input.to_string()))), - // } + match many0(filter::parse)(working) { + Ok(("", ops)) => { + let mut mut_ops = ops.clone(); + acc.append(&mut mut_ops); + Ok(Selector(acc)) + } + Ok((more, _ops)) => Err(nom::Err::Error(ParseError::TrailingInput(more.to_string()))), + Err(err) => Err(err.map(|input| ParseError::UnknownPattern(input.to_string()))), + } } } - impl Serialize for Selector { fn serialize(&self, serializer: S) -> Result { self.to_string().serialize(serializer) @@ -238,3 +250,34 @@ impl PartialOrd for Selector { None } } + +#[cfg(feature = "test_utils")] +impl Arbitrary for Selector { + type Parameters = ::Parameters; + type Strategy = BoxedStrategy; + + fn arbitrary_with(args: Self::Parameters) -> Self::Strategy { + prop::collection::vec(Filter::arbitrary_with(args), 0..12) + .prop_map(|ops| Selector(ops)) + .boxed() + } +} + +#[cfg(test)] +mod tests { + use super::*; + use proptest::prelude::*; + + mod serialization { + use super::*; + + proptest! { + #[test] + fn test_selector_round_trip(sel: Selector) { + let serialized = sel.to_string(); + let deserialized = serialized.parse(); + prop_assert_eq!(Ok(sel), deserialized); + } + } + } +} diff --git a/src/delegation/policy/selector/error.rs b/src/delegation/policy/selector/error.rs index 75a96a58..f1363f07 100644 --- a/src/delegation/policy/selector/error.rs +++ b/src/delegation/policy/selector/error.rs @@ -8,6 +8,9 @@ pub enum ParseError { #[error("unknown pattern: {0}")] UnknownPattern(String), + + #[error("missing starting dot: {0}")] + MissingStartingDot(String), } #[derive(Debug, Copy, Clone, PartialEq, Eq, Serialize, Deserialize, Error)] diff --git a/src/delegation/policy/selector/filter.rs b/src/delegation/policy/selector/filter.rs index 0173b8b1..016fad93 100644 --- a/src/delegation/policy/selector/filter.rs +++ b/src/delegation/policy/selector/filter.rs @@ -58,15 +58,8 @@ impl fmt::Display for Filter { match self { Filter::ArrayIndex(i) => write!(f, "[{}]", i), Filter::Field(k) => { - if let Some(first) = k.chars().next() { - if (first.is_alphabetic() || first == '_') - && k.is_ascii() - && k.chars().all(|c| char::is_alphanumeric(c) || c == '_') - { - write!(f, ".{}", k) - } else { - write!(f, "[\"{}\"]", k) - } + if self.is_dot_field() { + write!(f, ".{}", k) } else { write!(f, "[\"{}\"]", k) } @@ -78,19 +71,16 @@ impl fmt::Display for Filter { } pub fn parse(input: &str) -> IResult<&str, Filter> { - dbg!("PARSE", input); let p = alt((parse_try, parse_non_try)); context("selector_op", p)(input) } pub fn parse_non_try(input: &str) -> IResult<&str, Filter> { - dbg!("NON_TRY", input); - let p = alt((parse_values, parse_array_index, parse_field)); + let p = alt((parse_values, parse_field, parse_array_index)); context("non_try", p)(input) } pub fn parse_try(input: &str) -> IResult<&str, Filter> { - dbg!("TRY", input); let p = map_res(terminated(parse_non_try, tag("?")), |found: Filter| { Ok::(Filter::Try(Box::new(found))) }); @@ -99,7 +89,6 @@ pub fn parse_try(input: &str) -> IResult<&str, Filter> { } pub fn parse_array_index(input: &str) -> IResult<&str, Filter> { - dbg!("ARRAY_INDEX", input); let num = nom::combinator::recognize(preceded(nom::combinator::opt(tag("-")), digit1)); let array_index = map_res(delimited(char('['), num, char(']')), |found| { @@ -111,35 +100,65 @@ pub fn parse_array_index(input: &str) -> IResult<&str, Filter> { } pub fn parse_values(input: &str) -> IResult<&str, Filter> { - dbg!("VALUES", input); context("values", tag("[]"))(input).map(|(rest, _)| (rest, Filter::Values)) } pub fn parse_field(input: &str) -> IResult<&str, Filter> { - dbg!("FIELD", input); let p = alt((parse_delim_field, parse_dot_field)); context("map_field", p)(input) } pub fn parse_dot_field(input: &str) -> IResult<&str, Filter> { - dbg!("DOT", input); let p = alt((parse_dot_alpha_field, parse_dot_underscore_field)); - context("dot_field", p)(input) } +fn dot_starter(input: &str) -> IResult<&str, &str> { + if input.len() < 2 { + return Err(nom::Err::Error(nom::error::Error::new( + input, + nom::error::ErrorKind::Tag, + ))); + } + + let bytes = input.as_bytes(); + + if bytes[0] == b'.' { + if char::from(bytes[1]).is_alphabetic() || bytes[1] == b'_' { + return Ok((&input[2..], &input[..2])); + } + } + + Err(nom::Err::Error(nom::error::Error::new( + input, + nom::error::ErrorKind::Tag, + ))) +} + +fn is_allowed_in_dot_field(c: char) -> bool { + c.is_alphanumeric() || c == '_' +} + pub fn parse_dot_alpha_field(input: &str) -> IResult<&str, Filter> { - dbg!("DOT_ALPHA", input); - let p = map_res(preceded(char('.'), alphanumeric1), |found: &str| { - Ok::(Filter::Field(found.to_string())) - }); + let p = map_res( + preceded( + dot_starter, + nom::multi::many0(nom::character::complete::satisfy(is_allowed_in_dot_field)), + ), + |found: Vec| { + let inner = [input.as_bytes()[1] as char] + .iter() + .chain(found.iter()) + .collect::(); + Ok::(Filter::Field(inner)) + }, + ); context("dot_field", p)(input) } pub fn parse_dot_underscore_field(input: &str) -> IResult<&str, Filter> { - dbg!("DOT_UNDERSCORE", input); let p = map_res(preceded(tag("._"), alphanumeric1), |found: &str| { let key = format!("{}{}", '_', found); Ok::(Filter::Field(key)) @@ -149,7 +168,6 @@ pub fn parse_dot_underscore_field(input: &str) -> IResult<&str, Filter> { } pub fn parse_empty_quotes_field(input: &str) -> IResult<&str, Filter> { - dbg!("EMPTY_QUOTES", input); let p = map_res(tag("[\"\"]"), |_: &str| { Ok::(Filter::Field("".to_string())) }); @@ -157,22 +175,62 @@ pub fn parse_empty_quotes_field(input: &str) -> IResult<&str, Filter> { context("empty_quotes_field", p)(input) } -pub fn unicode_or_whitespace(input: &str) -> IResult<&str, Vec> { - nom::multi::many0(nom::character::complete::satisfy(|c| { - nom_unicode::is_alphanumeric(c) || c == ' ' - }))(input) +pub fn unicode_or_space(input: &str) -> IResult<&str, &str> { + #[derive(Copy, Clone, PartialEq, Debug)] + enum Status { + Looking, + FoundQuote, + Done, + Failed, + } + + let (status, len) = + input + .as_bytes() + .iter() + .fold((Status::Looking, 0), |(status, len), byte| { + if status == Status::Failed { + return (status, len); + } + + if status == Status::Done { + return (status, len); + } + + let c = char::from(*byte); + + if status == Status::FoundQuote { + if c == ']' { + return (Status::Done, len + 1); + } else { + return (Status::Looking, len + 1); + } + } + + if c == '"' { + return (Status::FoundQuote, len + 1); + } + + if c == ' ' || (!nom_unicode::is_whitespace(c) && !nom_unicode::is_control(c)) { + return (Status::Looking, len + 1); + } + + (Status::Failed, 0) + }); + + match (status, len) { + (Status::Done, len) => Ok((&input[len - 2..], &input[..len - 2])), + _ => Err(nom::Err::Error(nom::error::Error::new( + input, + nom::error::ErrorKind::TakeWhile1, + ))), + } } pub fn parse_delim_field(input: &str) -> IResult<&str, Filter> { - dbg!("DELIM", input); let p = map_res( - delimited(tag("[\""), unicode_or_whitespace, tag("\"]")), - |found: Vec| { - dbg!("$$$$$$$$$$$$$$$$$$$", &found); - - let field = found.iter().collect::(); - Ok::(Filter::Field(field)) - }, + delimited(tag(r#"[""#), unicode_or_space, tag(r#""]"#)), + |found: &str| Ok::(Filter::Field(found.to_string())), ); context("delimited_field", alt((p, parse_empty_quotes_field)))(input) @@ -211,7 +269,7 @@ impl Arbitrary for Filter { fn arbitrary_with(_params: Self::Parameters) -> Self::Strategy { prop_oneof![ i32::arbitrary().prop_map(|i| Filter::ArrayIndex(i)), - String::arbitrary().prop_map(Filter::Field), + "[a-zA-Z_ ]*".prop_map(Filter::Field), "[a-zA-Z_][a-zA-Z0-9_]*".prop_map(Filter::Field), Just(Filter::Values), // FIXME prop_recursive::lazy(|_| { Filter::arbitrary_with(()).prop_map(Filter::Try) }), From 8c70158177700071000b915e1e350e2dcaa67e38 Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Sat, 16 Mar 2024 21:56:29 -0700 Subject: [PATCH 161/188] More tests, mainly fixing arbitrary instances --- Cargo.toml | 3 +- proptest-regressions/invocation/payload.txt | 7 + src/ability/msg.rs | 38 ++- src/ability/msg/receive.rs | 4 + src/ability/msg/send.rs | 11 +- src/delegation/payload.rs | 121 +++++---- src/invocation/payload.rs | 260 ++++++++++++++++---- src/time/timestamp.rs | 6 + src/url.rs | 4 +- 9 files changed, 348 insertions(+), 106 deletions(-) create mode 100644 proptest-regressions/invocation/payload.txt diff --git a/Cargo.toml b/Cargo.toml index 3df1b306..648b98f6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -65,6 +65,7 @@ p256 = { version = "0.13.2", features = ["alloc", "ecdsa"], optional = true, def p384 = { version = "0.13.0", features = ["alloc", "ecdsa"], optional = true, default-features = false } p521 = { version = "0.13.3", features = ["alloc", "ecdsa", "getrandom"], optional = true, default-features = false } proptest = { version = "1.1", optional = true } +proptest-derive = { version = "0.4", optional = true } rsa = { version = "0.9.6", features = ["sha2", "std"], optional = true, default-features = false } serde = { version = "1.0.188", features = ["derive"] } serde_derive = "1.0" @@ -119,7 +120,7 @@ default = [ # "test_utils", ] -test_utils = ["dep:proptest", "dep:libipld"] +test_utils = ["dep:proptest", "dep:proptest-derive", "dep:libipld"] eddsa = ["dep:ed25519-dalek"] es256 = ["dep:p256"] diff --git a/proptest-regressions/invocation/payload.txt b/proptest-regressions/invocation/payload.txt new file mode 100644 index 00000000..e39051d7 --- /dev/null +++ b/proptest-regressions/invocation/payload.txt @@ -0,0 +1,7 @@ +# Seeds for failure cases proptest has generated in the past. It is +# automatically read and these particular cases re-run before any +# novel cases are generated. +# +# It is recommended to check this file in to source control so that +# everyone who runs the test benefits from these saved cases. +cc 2936cf4d3882dd47c00298c35d9dd5a2bf1d22ef4492544b4b9ff3b1f34acb6f # shrinks to payload = Payload { subject: Key(EdDsa(VerifyingKey(CompressedEdwardsY: [46, 111, 204, 227, 103, 1, 220, 121, 20, 136, 224, 208, 177, 116, 92, 193, 227, 58, 76, 28, 159, 204, 65, 198, 59, 211, 67, 219, 190, 9, 112, 230]), EdwardsPoint{ X: FieldElement51([1689611602193863, 490607132032821, 1343312146746774, 1090732682789050, 1815270510391065]), Y: FieldElement51([1127445621927726, 1752742079139643, 335263251657170, 455073811812238, 1802102173971517]), Z: FieldElement51([1, 0, 0, 0, 0]), T: FieldElement51([396516250482892, 1271770325328148, 2066179188049959, 970219954360817, 1259266248234093]) }))), issuer: Key(EdDsa(VerifyingKey(CompressedEdwardsY: [46, 111, 204, 227, 103, 1, 220, 121, 20, 136, 224, 208, 177, 116, 92, 193, 227, 58, 76, 28, 159, 204, 65, 198, 59, 211, 67, 219, 190, 9, 112, 230]), EdwardsPoint{ X: FieldElement51([1689611602193863, 490607132032821, 1343312146746774, 1090732682789050, 1815270510391065]), Y: FieldElement51([1127445621927726, 1752742079139643, 335263251657170, 455073811812238, 1802102173971517]), Z: FieldElement51([1, 0, 0, 0, 0]), T: FieldElement51([396516250482892, 1271770325328148, 2066179188049959, 970219954360817, 1259266248234093]) }))), audience: None, ability: Send(Send { to: Newtype(Url { scheme: "a", cannot_be_a_base: true, username: "", password: None, host: None, port: None, path: "aa", query: None, fragment: None }), from: Newtype(Url { scheme: "a", cannot_be_a_base: true, username: "", password: None, host: None, port: None, path: "_a", query: None, fragment: None }), message: "" }), proofs: [], cause: Some(Cid(bafybmiaabtpci7k3q4d3icxddhztzjx5tg72nv22yj7wrzlivewbgfd7ve)), metadata: {"\"($\u{680fa}蟧`\r`\u{b1d50}\u{4a196}'8$\u{f88db}\u{feff}\u{feff}\u{9e1a6}*\u{cc465}\u{101f58}\u{dc98c}\u{b}ȺP\u{87281}", -29, bafkrmigi5ucblo6m6dbnw2y7po27ahssm6r6gwfog5w2eaxd7ze64u3vki, "(", -50, true, 4, null, null, false, bafyr4id3ardj34fsaufowtepdegrlkvrkv4jmyiva54gz4afht26prarau, [206, 72, 170, 13, 12, 162, 106, 248, 83, 67, 95, 57, 191, 163, 90, 243, 125, 164, 14, 6, 150, 168, 71, 206, 239, 41, 111, 27, 246, 79, 53, 193, 98, 168, 210, 107, 64, 164], -3.33502927060181e-309, -49, null, 3.5445664834722476e19, -1.563337693800014e-308, bafy2bzacedec3zioqwt4bdhiel5jmdrot2fhkwtx4u3fqug5x5djkkgop6rbm, -7, -18, [216, 49, 164, 168, 254, 77, 204, 85, 63, 36], baguqfyheaiqipcrhl2ltrwj5ykc3gnwp2i4i6m3wqqrmfi4ap7uchecatjrmp7y, -0.0, [64, 148, 93, 100, 165, 243, 24, 181, 67, 141, 104, 128, 194, 110, 216, 253, 234, 40, 83, 43, 194, 82, 152, 207, 225, 141, 174, 148, 236, 77, 110, 231, 110, 107, 120, 161, 188, 242, 70, 187, 10, 8], null, null, bafyrmigihlvyxai27ay3ws4ytyoickvlnpli3rxing2zlvcrwklqgvhp6m, null, 8.12819559637677e-221, 14, -2.3233856055803314e-132, true, [33, 203, 100, 207, 226, 57, 37, 141, 9, 44, 127, 89, 88, 208, 26, 54, 107, 192, 81, 78, 170, 47, 90, 250, 222, 53, 73, 136, 60, 141, 50, 179, 193, 1, 240, 61, 212, 102, 158, 81, 130, 103, 57, 0, 144, 186, 253, 79, 200, 167, 150, 245, 107, 237, 87, 24, 222, 200, 67, 180, 136, 164, 87, 137, 92, 199, 194, 10, 174, 65, 1, 168, 43, 25, 36, 186, 55, 153, 217, 27, 78, 197, 170, 47, 74, 87, 78, 123], "&t\u{51b31}\u{10b5b7}·Ѩ$\u{491b7} \u{e82db}S`\u{13ba2}$\u{feff}\u{3}🕴V¥\t{*\u{79d01}", true, 32, true, 9.168173353619497e259, null, 0, 41, "🕴\u{6ad7f}/V\u{8d90e}\u{7}\0$\\\u{1de07}ѨDw'\u{5f7ad}�$$🕴qa", [183, 153, 172, 129, 204, 68, 233, 142, 34, 132, 183, 94, 210, 53, 181, 118, 12, 36, 147, 178, 163, 147, 226, 164, 22, 84, 47, 52, 68, 11, 3, 202], true, -47, "'\u{7f}\u{9f}{\u{4bd1b}${{\u{9f533}\u{4bd4c}$\u{49671}`\u{5}🕴|�k\u{c46c0}\u{1}🕴*\u{feff}7\u{c7f8f}\ra:", 24, [124, 23, 6, 17, 136, 174, 8, 255, 36, 94, 92, 161, 178, 30, 64, 18, 140, 103, 208, 64], "\u{41adf}\u{b}\"\u{bb2f8}\u{7f}Ⱥl**", bafybmic7lxxicy772aqqwgxzod5qnr2q42gvznf7ybo3focmp5garzkofe, false, null, -1.453409372917667e275, -4, 37, -6, false, "H\0O\u{de3c0}I 욽;\u{e55dc}/qY*\u{9b}$\tHô^\u{c1add}.\rȺ:&_\0�\t\u{f05bb}\u{a5ba9}\u{fac7d}", null, true, "U\rȺ\rÖU\u{3e4ac}:\u{a58ed}/|\u{b}\u{74e95}$\\[x\u{b652f}'/", -48, -3.21819364036035e-56, 9.213513078060828e-91, "'\u{890b8}=\tx\u{d5bf7}?\u{a4dd2}\u{9811e}\u{a5c67}": 6.664526628123302e214, "&\u{54d6c}//\u{d211d}\u{7f}�": "¨\u{202e}\u{413db}T\u{1027f3}\u{b}O`#\\\u{a494a}\u{3b0c6}s.\t\u{103dc}:u/\u{a5e48}A\u{202e}\u{77c5c}🕴ȺmM'\u{6a3fe}K", "'\u{1}": "\t🕴\u{eab5e}\\\u{bab9b}\u{ee77b}\u{52d9f}\u{ab141}{\u{6}", "'\"9%?": null, "'#\u{6}\u{bdef5}¥\u{ca946}\u{7f}\u{202e}": 1.7292683522733168e-61, "'M\u{d3d34}\u{202e}$\u{3}\u{42800}𗸴\u{7f}\u{62be1}<\0¥<\u{202e}\u{feff}/Ñ4\u{f92f7}{´": 9.820150129594855e-167, ")C\u{ddeef}\"\u{c2e4f}": [219, 75, 110, 213, 246, 194, 60, 45, 146, 98, 88, 191, 123, 141, 55, 76, 57, 2, 29, 8, 227, 206, 96, 136, 115, 139, 137, 243, 222, 156, 251, 57, 28, 97, 118, 49, 218, 195, 184, 189, 188, 165, 205, 70, 190, 145, 58, 42, 104, 26, 29, 3, 218, 118, 2, 133, 228, 7, 235, 110, 91, 232, 215, 78, 99, 2, 64, 17, 72, 56, 70, 160, 255, 37, 168, 19, 29, 27], "*´\\yn<%\u{3cb12}\u{99}&\u{5}Ⱥ/\u{7}𭆼\u{4263d}\u{88dca}Q<\u{efab0}\u{f6ec2}": -1.1059496664576365e153, ".\"\\\u{3}{\r\r:�\u{81c10}\0Ⱥ\t>{\\:\u{e877b}\u{5e93f}": [65, 186, 167, 66, 51, 133, 164], ".e$Ⱥ6\u{63ca7}Ѩ`u\u{33bd2}'n\\\u{48590}\u{e646}Q\u{a08f7}.Uf": null, ".\u{9d849}\u{f94cd}=𖩡\u{bc869}¥+$5?\u{1016e0}`䎟\u{1043c3}\u{6c91d}\u{667af}🕴": false, "/\0\rѨ^\u{1a826}\u{202e}\u{7f}\u{fb3dc}\r_�\u{3}\r\u{7f}&ï\u{3}:8\u{9ed75}q𪥇\u{1b}\u{e8fa3}\u{4}𧽨\u{4efe1}\u{977d9}\u{3c6dd}\u{cfeed}X": [17, 67, 94, 98, 195, 0, 112, 199, 95, 233, 81, 197, 28, 69, 112, 207, 197, 72, 18, 50, 114, 108, 53, 31, 197, 3, 225, 75, 134, 216, 253, 203, 70, 71, 48, 33, 235, 32, 133, 245, 9, 224, 57, 91, 77, 233, 171, 9, 39, 186, 240, 49], "8:\u{c9e98}\"%1🕴<🕴\u{977c5}띦b4'": true, "9Ⱥ\u{84761}a\t\0": "î+%\u{202e}\"\u{c9bb9}*\u{2}\u{eb474}\u{4c05d}R{=\u{feff}\u{e7f1b}\u{1}\u{2}<Ⱥ-=", ":<'": bafy2bzacebdndpakl7grp6t2qzddcmrtpw4klywtvk77qimmd5fya7havzbyk, ";\u{8}�\u{1b}P'Ⱥ": null, "=$ȺN\u{e377a}\u{5a12b}`fW\u{7f}\u{2}:\".": -1.284865551557179e224, ">:O\u{3a925}ÿ\u{5}�{": -20, "A\u{7f}$'\u{b}¥\u{7}\"\0\\$=\u{b}\u{1b}�\u{feff}\u{b}\u{7e7a2}\u{b5184}\":\u{feff} D\0=\u{7f}耋Ⱥð`ࢁ": -41, "J\u{1008ed}\"\rLq\u{5}7": true, "[啡🕴mѨ®\"\u{100589}{\u{8}u\r'\u{b}\u{47a49}}\u{7}\u{1b}R\u{b4ad6}\u{b45f5}\\Ⱥ`'?": [234, 239, 156, 159], "].`'&\u{7f}^&.\u{202e}\0?%?¥\u{9929e}\u{7dd49}?a": 6.8070914168451965e-68, "_¦\u{ccda8}O\u{10bdee}fk$\u{feff}`픰>\r'�f➦\r|{\u{feff}laA=D\u{dc4db}\u{5f977}z": [142, 251, 14, 32, 228, 8, 15, 49, 217, 216, 50, 236, 66, 67, 25, 55, 193, 159, 240, 75, 84, 154, 198, 182, 176, 139, 245, 12, 189, 16, 217, 208, 9, 19, 164], "f\u{3f22c}\u{7f}\0.Y?'.*\rѨ)Q¥=�\u{6b46c}\u{42bbe}\u{ee66}": bafkr4idibino2zrwzvmr7jw2oqbramjdedkkhhexizf2kgnxongndjyvvm, "k\u{e0bf5}Ã\u{202e}<\u{202e}**\t\u{656ee}<&𘞄\0\u{b}*.b\u{1b}\u{b}\u{8}\u{c9220}&\u{36fa6}.?]G<ȺG": bafk2bzacec3uxd2irqv3kuoh6433iq7cdb2rkr4jes4ibxqlagobh7kcbz7b4, "pmOAB\u{b}\u{1094f0}\u{c3774}?\u{10918f}¥": true, "v\\\u{109f70}7`@\u{4}c'%!\u{7d32d}Ѩ\u{1f298}i\u{4d1e4}'�\"]�.\t𝦈": null, "v`\u{1b}\u{58c73}\0\u{101d7f}\0\u{d946b}\u{7f}\t\u{f2bf}\"\u{831aa}\u{60049}\u{51864}&?祕u\u{10403a}D]�\u{578ee}\u{ab4b8}:/ 𦁋R7Ѩ": -24, "{\"C썶\t\u{eec8b}<\u{ee555}\u{7574e}Ѩ\u{7f588}\rѨ*'\u{5eccf}%w{\r\r\u{b67d7}\0^kᗩ\u{1c3dd}G\u{8}`": 18, "{%🕴\0Ѩl%𤍭\u{feff}🕴\u{96}I\r=": "¥\u{93d84}y:w={", "¥~\u{566dc}9\u{ec076} \u{bd2c1};": false, "¥ȺD\u{1}\u{6b992}\u{434c4}\u{3}\t\u{1b}?\u{2}𥹩\u{9cb8c}\u{7f}𢲦N\u{e11b5}Q^{Ѩ\"w\u{505da}{": null, "Á-`^�\"\t\"`\"/<\u{1}=%�=\u{f8043}\t\u{3cb4c}\u{af74a}^\u{356ed}.:d?\u{65101}\t;\u{d0319}\u{b6b3a}$q\u{b}": [163, 128, 176, 15, 181, 109, 115, 80, 122, 244, 94, 32, 58, 251, 121, 38, 145, 131, 101, 44, 148, 129, 190, 123, 55, 19, 64, 213, 134, 70, 238, 108, 62, 157, 43, 176, 76, 240, 174, 14, 182, 98, 16, 190, 79, 42, 142, 212, 204, 192, 112, 130, 89, 121, 0], "�*??*e": [172, 83, 171, 22, 2], "�\u{9b09a}\u{15e59}\u{7}/\0E\u{6}\u{3cb2b}\u{4645e}\u{7f}\u{2}": true, "🕴": 41, "🕴\t\u{f0635}`Ⱥ\u{c865b}h\u{f39b}\u{fd001}\u{f197b}¥F/?\0\u{52376}%Ѩ/\t\u{b}\u{9a1af}\u{9b1fd}\u{feff}�\u{8f}/bt`k": [235, 24, 242, 136, 3, 90, 188, 21, 23, 10, 85, 3, 90, 163, 176, 209, 186, 96, 160, 240, 221, 232, 34, 228, 54, 35, 11, 200, 6, 93, 92, 140, 92, 36, 62, 178, 157, 50, 12, 66, 22, 208, 28, 217, 177, 9, 79, 248, 77, 198, 73, 78, 120, 28, 102, 200, 197, 67, 232, 133, 41, 38], "𥇖`": -3.1043921612514823e111, "\u{fdac9}\u{2}.𘠺🕴:'\r\t\u{3}嬊": bafk2bzacedwvgub3e2lthnyghripac4wx5ld6z3tavnb3iws5vdjukng5fwsk}, "$\u{890b7}&🕴=Ѩ\u{e0cc8}\u{7f}\u{8}\u{b}\\\u{ed02b}🕴°d\u{7}\u{202e}L\u{6d2ae}<\ta{*": {"\0&/\0&\u{8999c}.`%\u{feff}": baguqehraxkytankub52h3iajclairokepzb3skwifyzrg5qnfmbz4g4oodza, "\0🕴\r\u{2}`%O$%\u{de829}": bafkrmiht3e7seikbsbinibsxaqh4je5pzdcd4urxrwswdctilymrairlqy, "$\u{1e406}\u{1ea29}\u{7f}\u{c9cd8}𮖲X": [168, 224, 17, 123, 200, 96, 201, 98, 223, 37, 175, 231, 138, 28, 166, 190, 55, 123, 102, 20, 44, 208, 18, 184, 161, 233, 19, 136, 101, 164, 157, 248, 80, 214, 88, 79, 112, 146, 242], "=`C": -23, "\\8\u{d9453}($�[\u{539d4}\u{90f0d}\\u�\"$\u{bf277}\"\u{749e6}": 9, "wѨ&\u{f18dc}\u{8f209}\u{8}/\t\\": bafkr4ibmkkkbmtivel5pdx3jrmhp4wb5bd7rqzvoovp2j4b5hn2rtovype, "Ⱥ\u{b}\u{10433a}\\\u{1b}{`!\u{cd71b}¥<`ȺDȺ\u{d310e}\u{fee4b}𭨠0\u{b6c98}": true, "Ѩu鉏<}\u{8}Q\u{5ef3a}:Cm&\\\u{3}Q':@\u{f9a57}'\u{3f836}c<🕴P": false, "\u{202e}\u{6899a}<\u{b}/Ѩ&\u{2edba}\u{103edb}\u{9e328}`\u{4979f};\u{a6f16}!`\u{feff}w`": -32, "�Ѩ\u{8}�\"\u{fc9b3}\t": null}, "L_Ⱥ=,\u{4}\u{71158}'�\u{d01e8}/$\u{202e}\u{3b573}:`{w'": [-48, [93, 196, 141, 154, 134, 176, 139, 189, 180, 134, 124, 223, 175, 198, 167, 42, 110, 24, 222, 67, 196, 65, 202, 107, 120, 13, 17, 105, 196, 172, 101, 89, 218, 26, 140, 88, 96, 162, 89, 106, 160, 211, 109, 26, 157, 200, 132, 194, 38, 44, 145, 80, 249, 47, 202, 234, 243, 149, 231, 133, 231, 1, 121, 28, 157, 175, 187, 89, 100, 158, 213, 243, 162, 111, 87, 174], bafk2bzacecsuznauruwgtuwqc6nidbjw7qp5usnb5vr3w7lxuvebwffbqe3t4, -1.8324536891683285e-30, bafk2bzaceadjwnpm762btxzz5nfl24lfog3wm5ldrl7q7namae7ysv5647h76, -6.370155741536763e-309, null, null, 23, false, true, -41, 48, "`\t\u{cd15e}<{\u{ad7da}%W*腁=\t¯\u{7}-?,\\", 5, [238, 157, 140, 174, 18, 122, 121, 22, 84, 53, 144, 119, 53, 216, 130, 89, 6, 121, 229, 24, 153, 6, 11, 186, 92, 6, 52, 44, 74, 132, 139, 202, 28, 213, 100, 205, 127, 222, 130, 104, 210, 158, 235, 223, 136, 77, 58, 114, 135, 100, 151, 103, 91, 82, 150, 91, 149, 124, 36, 26, 78, 105, 207, 54, 253, 9, 201], [195, 193], -2.0736431262565832e-7, null, -7.471855593429185e234, [212, 22, 11, 89, 86, 227, 1, 226, 174, 110, 146, 235, 217, 246, 70, 133, 107, 165, 157, 151, 202, 116, 160, 243, 169, 65, 185, 193, 131, 184, 185, 157, 65, 108, 39]], "y#\u{49798}\u{202e}G<%r\u{1b}\u{c2763}\u{1b}\u{d7645}$M🕴\u{b}:": baguqegza3fmjvuxyw67cjennqkgi6ipcsxboanp4xjz4prjmp7h4r5muxaja, "¥\u{6}xS%\u{a21c0}Ѩ\0\u{b}\u{e52b5}\u{8cd0b}:\u{feff}\tS¨\u{dc97a}%$\u{8a51a}\u{8945b}\u{3c40a}🕴\u{feff}\u{feff}": "\u{b}\u{fbd6c}*", "🕴$�\u{1c50d}\t\u{2}": {"": 14, "\0�&\u{feff}\u{685bb}YVE9c\"|\"<[�K𤅰\u{cd669}}\"": bafy6bzaceak3lys2lo5m7woe2mfjyxxl3ore3aixosi5uajethhkjyze72xpm, "\u{1}\u{d0b74}\u{b6c77}y?[\u{202e}¥~{T\u{1b}\u{feff}\u{f8717}\u{a0f04}:\u{10f8b6}Ⱥ🕴\"Q\u{df178}\"\u{fe2ee}u": -1.5028963084100644e18, "\u{4}`\u{100473}\u{f434d}=�*/🦲Y\u{a0}W\u{a0e46}\u{5}": false, "\u{5}4\u{33c5d}\u{4}": 6, "\u{5}ZѨh\r&": true, "\t(/=:<\u{7}🕴.'Z": bafyb4ig3qyn3653hzem53fsfvx27lsgdkfmln4vgzjnu7bhb22rr6h5nvq, "\u{b}\u{5}\"\r𐴅\u{a3132}z$8\u{74e26}\0F\0\"*�J\u{7d9f5}\u{ad7c2}\u{f3faf}?\u{f35ed}": [158, 45, 189, 198, 136, 12, 91, 193, 206, 13, 142, 218, 17, 126, 95, 11, 139, 29, 191, 233, 236, 221, 187, 109, 193, 176, 213, 129, 96, 120, 253, 24, 3, 65, 228, 116, 138, 102, 127, 19, 54, 235, 37, 201, 238, 25, 41, 26, 248, 239, 191], "\u{b}Á\u{4}\0$\u{baa23}N\u{105c4d}\u{feff}g": 6.651792988712289e-76, "\r&\u{7f}𬛩\r*m\\𭘺\t": [7, 86, 209, 90, 4, 82, 142, 122, 123, 191, 41, 84, 37, 155, 147, 150], "\u{1b}=\u{ae8cb}🕴🕴YѨ": [15, 58, 171, 250, 163, 49, 118, 146, 209, 32, 161, 202, 1, 216, 170, 61, 6, 250, 37, 28, 65, 69, 84, 230, 70, 124, 99, 121, 212, 211, 35, 15, 202, 210, 87, 237, 13, 14, 12, 203], " n𦜹": false, "\"I¥": null, "'\u{202e}\u{1bfda}¥\r\u{e18c}{\u{4a199}Ⱥ\u{98ba2}{\u{202e}🕴f\\": false, "'🕴": [0, 125, 38, 17, 120, 195, 201, 150, 64, 36, 204, 245, 82, 31, 21, 85, 169, 69, 249, 10, 19, 33], ")�\r\"\r¥\u{202e}\u{cbc20}\u{5a381}\u{202e}𱙤{'": false, "*\rȺ\u{9af1d}g�.*🕴\u{1b}\u{feff}\u{6ecf1}`\rY{\u{88}\\*.\u{f8037}($\\¥.\r&ÑD`": null, "*'\u{fe9ce}\t𪡏h'Y\\{\0IWë\rU?$\u{7}IQi!%": bafkrwif2qr2egu3en3ttpfc277nx7wbogbs3exytgbmipe2ev4itu4abxi, "*{U\u{7f}3\u{feff}🕴%*``/ꁏw\u{88393}\u{1b}>": 16, "-&\u{588d4}J": -136947584516.8358, "/": bafybmigzjzmiin3bgn6qms6vavgzn6iyqjuo4onptcetzu657kebbwt5hu, "2\r\u{8c375}i\u{99}&🕴0J\t$Á\u{66ed4}/": "¾\t\t🕴$A\u{136fd}¥=l\u{8937a}\"\u{db049}`Ⱥ>@\u{b}\t\u{feff}%.\t\u{8ec09}\u{c5f5f}/$", "<\u{1b}/\\?z9\u{dbde9}\t?𦤐\u{1b}&H\u{1b}\u{125ec}\u{cf4c4}\t\u{b}\0.\t§\u{4a3f5}\\�": bafkr4id2n26zagqdmec7pq53l34j6st3x3xzqualxr3kl57litjvhiqjku, "=]\u{1b}\u{f411d}\t\"`\u{a4a2c}\u{6}": [238, 35, 78, 136, 226, 234, 214, 233, 77, 30, 183, 34, 221, 130, 60, 71, 179, 70, 33, 252, 49, 64, 114, 217, 11, 226, 152, 252, 143, 174, 237, 102, 78, 100, 138, 217, 6, 74, 250, 0, 234, 244, 152, 245, 69, 148, 151, 8, 51, 18, 105, 167, 200, 213, 240, 64, 138, 104], "=\u{8bf06}?x\u{1}𡊽@\u{6}.<%": "L\u{79877}\u{eccd7}*", ">=\u{feff}\u{8}à\u{1b}vL0\t\u{7f}I\u{feff}\u{7aba3}b$$Sv~i\t錦🕴&\u{709ee}.": null, ">`:𦎼\u{7f}º\u{be352}n$\u{eafbd}\u{cddd8}jw\u{1b}\u{feff}YT": true, "I韸\u{53960}\u{feff}?\u{7f}.\u{cf92c}Q\u{4}.\u{b}\u{8}🕴Ѩ%": -26, "M\u{2}𬗃\u{9082e}\u{e23ca}\u{4}\t\u{feff}\u{5bde3}*\u{e514b}x,\u{48624}\u{1b}\u{b}+\u{f8546}\u{8a2a6}\u{d4710}'🕴f;\u{46c31}\u{1004de}y{b:": true, "M&\u{e303}?&�\u{7f}j;\"\u{202e}픢\u{b}🕴": null, "W*/X\u{7f}^\u{7dd38}Þ7%.*3\t\u{ce607}:\u{d3d17}c\t\r\"\u{84fa3}\u{202e}=\u{92}": null, "Y*\u{c2e6a}?<~\u{feff}P'¥e¥\u{43a8e}\u{fa00a}`'": baguqehra4nft63u5d2aawrcgvhart2zja5ogq6c624azqqxgbgtoxekvlnpa, "Y\u{202e}'?": "\u{3}\u{cc1ea}\u{c7649}\u{a973e}\u{8}0\r^u\u{8eff2}l\r<]\u{5e264}&\u{f3e11}\t\r\06S", "\\\u{5}.\u{7}\u{b69af}*\u{19abc}]¥\u{202e}{🕴'\u{e46c7}`.xs\u{b23ec}\u{10beed}": -21, "\\*\r¢峭ìE\u{d96e1}\u{f8ff9}^\u{bc6ac}%:\u{50965}$6\u{8119f}¥\u{f67d0}y~\u{6c741}\u{3beb4}🕴": [126, 126, 7, 32, 17, 6, 234, 156, 163, 166, 193, 46, 81, 206, 32, 125, 166, 57, 195, 140, 39, 47, 171, 70, 117, 175], "\\\\/&Ѩ\\<": [92, 59, 122, 162, 91, 48, 33, 29, 29, 249, 254, 64, 21, 67, 100, 54, 206, 184, 116, 163, 175, 47, 42, 243, 205, 18, 136, 16, 249, 116, 141, 232, 122, 115, 123, 202, 72, 56, 116, 229, 177, 60, 49, 83, 199, 52, 23, 85, 114, 147, 155, 50, 31], "`¥?쉲G": "\t\u{4095b}\u{6c6a4}𮭀\u{2}\\\u{da8f7}8\u{90a53}.痗\\\u{202e}\u{56f97}{E]\u{492cd}&f\u{7f}¥D\\]Ⱥ?🕴", "`嬥\u{db443}.\u{ce5}\u{12eae}\u{108af8}\u{1b}\u{7f}\r\u{60df2}¥%&\u{5b0fe}-.&\u{3}\u{e2c1d}DR&:�\u{10c17b}Èc\u{8}U%": [49, 169, 171, 64, 201, 141, 228, 46, 7, 206, 13, 162, 170, 144, 168, 169, 198, 15, 58, 71, 112, 228, 149, 218, 144, 84, 70, 253, 39, 168, 223, 3, 232, 33, 162, 234, 136, 215], "`�`\u{7f}{\u{5c37e}\u{2}\u{202e}?": "/\"\u{3}𪜥N\u{57f77}h$\u{d1d92}𐊘�\u{7f}*¥<.\u{9e397}<\u{1}g\u{d0ad6}/\u{11ede}\u{7f}\u{ae007}\0$\u{3afa0}🕴", "c��W|`�*@.aѨN\u{feff}\u{f92a0}t$\u{f53cc}1\u{c8fbe}\r\u{19667}\t\r\r-\u{7c67c}": 2, "o\u{1}\u{1}\u{1}=\u{e8927}I\u{f584}&\u{a0}:9\0a𠿭/": true, "q$Ѩ\08{\"鉱[?ÀzY\u{9b8c9}%\u{3}{%\u{5}\0.\\\u{6c540}\u{7f}s\u{c05e8}`\u{47f61}=)🕴": -12, "s*¥\u{9a}\u{feff}": -1.6309188456998563e185, "v=<¥'<\\`\u{cc9eb}\u{798aa}\u{64542}:®3\u{202e}\tD&𰓏%�\u{7f}LF𒉠\t�\u{ee420}/'D": true, "v陈'\u{d832c}R??\u{7f}/\u{6}?\u{7d4d1}\u{dca1e}~🕴\u{aca77}:ç\t\rѨt?\u{10f5f3}\u{9db4d}": [35, 3, 217, 123, 77, 104, 154, 141, 118, 202, 130, 101, 62, 0, 182, 199, 130, 209, 149, 102, 212, 81, 90, 149, 217, 96, 127, 42, 251, 151, 46, 49, 33, 84, 28, 55, 109, 95, 208, 174, 193, 163, 28, 103, 91, 127, 159, 204, 177, 182, 31, 112, 61, 243, 10, 202, 83, 234, 176, 174, 61, 22], "v\u{43b04}6\u{f16c9}\u{2}\u{ad277}{Ã={/\u{202e}": [130, 234, 7, 201, 230, 244, 129, 23, 205, 123, 90, 239, 235, 197, 10, 88, 224, 76], "y/\u{1b}Ѩ\u{1b}": "?;*\u{156a5}🕴\u{1ad1f}:-\u{a1bea}r&\u{eb06}\u{a5275},:R\u{7f}&<\"®\"w\u{202e}:Ó.:\u{202e}\t.", "{B\u{7f}𲌜\u{9a6bf}.\0\u{4a3ca}🕴\u{7}\u{35765}/": -29, "{`=\t;\u{b}*EX\u{1b}鋪\u{61ce8}\u{b742a}/s🕴\u{5}\u{db6e7}'\u{10aa16}q?\u{8}\u{8d}\u{518fe}": "?\u{8d694}🕴", "{b\u{fe666}䀾\u{feff}]y\u{1b}[$\u{202e}\u{202e}🕴㞔\u{c132a}": true, "\u{7f}\"P=\u{555f9}\r\u{f034e}j\u{67c81}\t)Ø\t\u{a2116}\u{bcb49}<\u{108172}\u{489d6}3": [11, 195, 207, 131, 8, 71, 197, 12, 152, 172, 119, 96, 131, 191, 170, 2, 143, 112, 247, 189, 191, 156, 180, 76, 57, 86, 59, 6, 237, 106, 34, 175, 98, 78, 217, 192, 190, 63, 247, 202, 245, 190, 185, 58, 157, 147, 177, 129, 101, 197, 232, 22, 220, 131, 53, 79, 127, 137, 106, 0, 139, 8, 238, 179, 248, 40, 242, 178, 191, 193, 47, 56, 113], "\u{7f}_%\u{e1aa4}\u{c1f6f}/$s:\u{1010bd}*j\u{b}nêeȺ$\u{7f}\u{202e}\u{feff}\u{1b}\u{feff}%\u{9f08d}w\u{e8557}<$<\u{3e78b}": "Ⱥ\u{feff}$", "\u{7f}\u{9a}\0�\u{9c}'$=\u{4}&\u{94281}\t?\"Ѩ\u{654e1}": false, "\u{99}.\u{9d66b}z\u{db26c}\0\u{109c92}.W\u{ca505}\\¥\u{e0cf8}\0`4x\u{feff}×ꡔL\u{b16bc}\u{c1e8f}TYu\u{e7dae}\u{49a55}: ": 43, "\u{9e}%MWȺ`$\u{202e}:z?🕴\u{6}\u{a0}㶬{\u{5}<\u{6}*": bafk6bzaceaont65a5awr6tb55msdtvepr4xwa62mgsp4gyyfmbd62ey4ts4z6, "¥`?=%🕴`L": true, "¥\u{7aff2}Òb[?\u{8}\u{923a6}»\\»": false, "¦\u{6a307}": null, "Ѩ🕴T¥$*?\u{6}𪵅\u{98110}": -6.640335043931752e20, "\u{1bd53}Ѩd%@": null, "🕴3\u{feff}&\u{dd024}Ѩ=\u{c62d5}p\u{7f}�'AѨ|`g\u{635c5}h": 22, "𣅝\"\u{8b95b}\u{fbfeb}\u{cd3f3}A'\u{167f7}𘑦&🕴\u{94f3b}H⼣\u{95077}": "", "𮎾ýK\u{feff}qȺ\"\u{64e75}\u{70dd2}¥\u{41885}l": bafy6bzacedp25hwogiwuqmaa52iwzgx42gznm36zcpjwo2togshwzpsae6p26, "𲊳": [226, 239, 217, 38, 87, 223, 233, 194, 149, 219, 168, 60, 210, 19, 135, 178, 111, 92, 80, 108, 128, 121, 241, 63, 190, 148, 170, 24], "\u{3393b}\0¥\u{3b6e9}F\u{93d46}X=\u{d5dd8}:\u{feff}%'\t\u{95303}'*\u{aca4a} for arguments::Named { } } +impl From for Ipld { + fn from(msg: Msg) -> Self { + match msg { + Msg::Send(send) => send.into(), + Msg::Receive(receive) => receive.into(), + } + } +} + /// A promised version of the [`Msg`] ability. #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] pub enum PromisedMsg { @@ -83,15 +99,6 @@ impl ParsePromised for PromisedMsg { } } -// impl From for arguments::Named { -// fn from(promised: PromisedMsg) -> Self { -// match promised { -// PromisedMsg::Send(send) => send.into(), -// PromisedMsg::Receive(receive) => receive.into(), -// } -// } -// } - impl Resolvable for Msg { type Promised = PromisedMsg; } @@ -123,3 +130,16 @@ impl ParseAbility for Msg { Err(ParseAbilityError::UnknownCommand(cmd.to_string())) } } + +// #[cfg(feature = "test_utils")] +// impl Arbitrary for Payload +// where +// T::Strategy: 'static, +// DID::Parameters: Clone, +// { +// type Parameters = (T::Parameters, DID::Parameters); +// type Strategy = BoxedStrategy; +// +// fn arbitrary_with((t_args, did_args): Self::Parameters) -> Self::Strategy { +// } +// } diff --git a/src/ability/msg/receive.rs b/src/ability/msg/receive.rs index 2d59ea0b..73f2b504 100644 --- a/src/ability/msg/receive.rs +++ b/src/ability/msg/receive.rs @@ -8,6 +8,9 @@ use crate::{ use libipld_core::{error::SerdeError, ipld::Ipld, serde as ipld_serde}; use serde::{Deserialize, Serialize}; +#[cfg(feature = "test_utils")] +use proptest_derive::Arbitrary; + #[cfg_attr(doc, aquamarine::aquamarine)] /// The ability to receive messages /// @@ -37,6 +40,7 @@ use serde::{Deserialize, Serialize}; /// style rec stroke:orange; /// ``` #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[cfg_attr(feature = "test_utils", derive(Arbitrary))] #[serde(deny_unknown_fields)] pub struct Receive { /// An *optional* URL (e.g. email, DID, socket) to receive messages from. diff --git a/src/ability/msg/send.rs b/src/ability/msg/send.rs index c85dd4a7..ff97b16f 100644 --- a/src/ability/msg/send.rs +++ b/src/ability/msg/send.rs @@ -7,7 +7,9 @@ use crate::{ }; use libipld_core::ipld::Ipld; use serde::{Deserialize, Serialize}; -use std::collections::BTreeMap; + +#[cfg(feature = "test_utils")] +use proptest_derive::Arbitrary; #[cfg_attr(doc, aquamarine::aquamarine)] /// The executable/dispatchable variant of the `msg/send` ability. @@ -37,6 +39,7 @@ use std::collections::BTreeMap; /// style sendrun stroke:orange; /// ``` #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +#[cfg_attr(feature = "test_utils", derive(Arbitrary))] #[serde(deny_unknown_fields)] pub struct Send { /// The recipient of the message @@ -62,6 +65,12 @@ impl From for arguments::Named { } } +impl From for Ipld { + fn from(send: Send) -> Self { + arguments::Named::from(send).into() + } +} + #[cfg_attr(doc, aquamarine::aquamarine)] /// The invoked variant of the `msg/send` ability /// diff --git a/src/delegation/payload.rs b/src/delegation/payload.rs index 5b7f4901..e75bb60b 100644 --- a/src/delegation/payload.rs +++ b/src/delegation/payload.rs @@ -115,7 +115,7 @@ impl Verifiable for Payload { } } -impl TryFrom> for Payload +impl TryFrom> for Payload where ::Err: Debug, { @@ -317,6 +317,10 @@ impl From> for Named { args.insert("sub".to_string(), Ipld::Null); } + if let Some(via) = payload.via { + args.insert("via".to_string(), Ipld::String(via.to_string())); + } + if let Some(not_before) = payload.not_before { args.insert("nbf".to_string(), Ipld::from(not_before)); } @@ -393,64 +397,91 @@ mod tests { use proptest::prelude::*; use testresult::TestResult; - mod serialization { - use super::*; + proptest! { + #![proptest_config(ProptestConfig::with_cases(100))] #[test_log::test] - fn test_into_ipld() -> TestResult { - proptest!(ProptestConfig::with_cases(100), |(payload: Payload)| { - let named: Named = payload.clone().into(); - let sub = named.get("sub".into()); - - if let Some(ref subject) = payload.subject { - let sub_ipld = &Ipld::String(subject.to_string()); - pretty::assert_eq!(sub, Some(sub_ipld)); - } else { - pretty::assert_eq!(sub, Some(&Ipld::Null)); - } - }); - - // proptest! { - // #![proptest_config(ProptestConfig { - // cases: 100, .. ProptestConfig::default() - // })] - - // #[test_log::test] - // fn test_into_ipld(payload: Payload) { - // dbg!(payload.clone()); + fn test_ipld_round_trip(payload in Payload::::arbitrary()) { + let observed: Ipld = payload.clone().into(); + let parsed = Payload::::try_from(observed); + prop_assert!(matches!(parsed, Ok(payload))); + } - // prop_assert_eq!(payload.clone(), payload.clone()) - // } + #[test_log::test] + fn test_ipld_has_correct_fields(payload in Payload::::arbitrary()) { + let observed: Ipld = payload.clone().into(); - // // #[test_log::test] - // // fn test_roundtrip_ipld() -> TestResult { - // // Ok(()) - // // } - // } + if let Ipld::Map(named) = observed { + prop_assert!(named.len() <= 10); - Ok(()) + for key in named.keys() { + prop_assert!(matches!(key.as_str(), "sub" | "iss" | "aud" | "via" | "cmd" | "pol" | "meta" | "nonce" | "exp" | "nbf")); + } + } else { + panic!("Expected Ipld::Map, got {:?}", observed); + } } #[test_log::test] - fn test_from_ipld() -> TestResult { - Ok(()) - } + fn test_ipld_field_types(payload in Payload::::arbitrary()) { + let named: Named = payload.clone().into(); + + prop_assert!(named.len() <= 10); + + let iss = named.get("iss".into()); + let aud = named.get("aud".into()); + let cmd = named.get("cmd".into()); + let pol = named.get("pol".into()); + let nonce = named.get("nonce".into()); + let exp = named.get("exp".into()); + + // Required Fields + prop_assert_eq!(iss.unwrap(), &Ipld::String(payload.issuer.to_string())); + prop_assert_eq!(aud.unwrap(), &Ipld::String(payload.audience.to_string())); + prop_assert_eq!(cmd.unwrap(), &Ipld::String(payload.command.clone())); + prop_assert_eq!(pol.unwrap(), &Ipld::List(payload.policy.clone().into_iter().map(|p| p.into()).collect())); + prop_assert_eq!(nonce.unwrap(), &payload.nonce.into()); + prop_assert_eq!(exp.unwrap(), &payload.expiration.into()); + + // Optional Fields + match (payload.subject, named.get("sub")) { + (Some(sub), Some(Ipld::String(s))) => { + prop_assert_eq!(&sub.to_string(), s); + } + (None, Some(Ipld::Null)) => prop_assert!(true), + _ => prop_assert!(false) + } - #[test_log::test] - fn test_ipld_round_trip() -> TestResult { - proptest!(ProptestConfig::with_cases(1), |(payload: Payload)| { - let ipld: Ipld = payload.clone().into(); - let parsed = Payload::::try_from(ipld); + match (payload.via, named.get("via")) { + (Some(via), Some(Ipld::String(s))) => { + prop_assert_eq!(&via.to_string(), s); + } + (None, None) => prop_assert!(true), + _ => prop_assert!(false) + } - assert_matches!(parsed, Ok(payload)); - }); + match (payload.metadata.is_empty(), named.get("meta")) { + (false, Some(Ipld::Map(btree))) => { + prop_assert_eq!(&payload.metadata, btree); + } + (true, None) => prop_assert!(true), + _ => prop_assert!(false) + } - Ok(()) + match (payload.not_before, named.get("nbf")) { + (Some(nbf), Some(Ipld::Integer(i))) => { + prop_assert_eq!(&i128::from(nbf), i); + } + (None, None) => prop_assert!(true), + _ => prop_assert!(false) + } } #[test_log::test] - fn test_from_invalid_ipld() -> TestResult { - Ok(()) + fn test_non_payload(ipld in ipld::Newtype::arbitrary()) { + // Just ensuring that a negative test shows up + let parsed = Payload::::try_from(ipld.0); + prop_assert!(parsed.is_err()) } } } diff --git a/src/invocation/payload.rs b/src/invocation/payload.rs index 40300b89..0d71a412 100644 --- a/src/invocation/payload.rs +++ b/src/invocation/payload.rs @@ -1,6 +1,9 @@ use super::promise::Resolvable; use crate::ability::command::Command; +use crate::ability::parse::ParseAbilityError; +use crate::delegation::policy::selector; use crate::invocation::Named; +use crate::time; use crate::{ ability::{arguments, command::ToCommand, parse::ParseAbility}, capsule::Capsule, @@ -20,6 +23,7 @@ use serde::{ Deserialize, Serialize, Serializer, }; use std::collections::BTreeSet; +use std::str::FromStr; use std::{collections::BTreeMap, fmt}; use thiserror::Error; use web_time::SystemTime; @@ -274,17 +278,15 @@ where { fn from(payload: Payload) -> Self { let mut args = arguments::Named::from_iter([ - ("iss".into(), payload.issuer.to_string().into()), - ("sub".into(), payload.subject.to_string().into()), - ("cmd".into(), payload.ability.to_command().into()), - ( - "args".into(), - arguments::Named::::from(payload.ability).into(), - ), - ( - "prf".into(), - Ipld::List(payload.proofs.iter().map(Into::into).collect()), - ), + ("iss".into(), { payload.issuer.to_string().into() }), + ("sub".into(), { payload.subject.to_string().into() }), + ("cmd".into(), { payload.ability.to_command().into() }), + ("args".into(), { + Ipld::Map(arguments::Named::::from(payload.ability).0) + }), + ("prf".into(), { + Ipld::List(payload.proofs.iter().map(Into::into).collect()) + }), ("nonce".into(), payload.nonce.into()), ]); @@ -304,6 +306,15 @@ where } } +impl From> for Ipld +where + arguments::Named: From>, +{ + fn from(payload: Payload) -> Self { + arguments::Named::from(payload).into() + } +} + impl Serialize for Payload where A: ToCommand + Into + Serialize, @@ -495,8 +506,12 @@ impl Verifiable for Payload { } } -impl TryFrom> for Payload { - type Error = (); // FIXME +impl TryFrom> for Payload +where + ::ArgsErr: fmt::Debug, + ::Err: fmt::Debug, +{ + type Error = ParseError; fn try_from(named: arguments::Named) -> Result { let mut subject = None; @@ -513,84 +528,131 @@ impl TryFrom> for Payload { - subject = Some( - match v { - Ipld::Null => None, - Ipld::String(s) => Some(DID::from_str(s.as_str()).map_err(|_| ())?), - _ => return Err(()), + subject = Some(match v { + Ipld::String(s) => { + DID::from_str(s.as_str()).map_err(ParseError::DidParseError)? } - .ok_or(())?, - ) + _ => Err(ParseError::WrongTypeForField(k, v))?, + }) } "iss" => match v { - Ipld::String(s) => issuer = Some(DID::from_str(s.as_str()).map_err(|_| ())?), - _ => return Err(()), + Ipld::String(s) => { + issuer = Some(DID::from_str(s.as_str()).map_err(ParseError::DidParseError)?) + } + _ => Err(ParseError::WrongTypeForField(k, v))?, }, "aud" => match v { - Ipld::String(s) => audience = Some(DID::from_str(s.as_str()).map_err(|_| ())?), - _ => return Err(()), + Ipld::String(s) => { + audience = + Some(DID::from_str(s.as_str()).map_err(ParseError::DidParseError)?) + } + _ => Err(ParseError::WrongTypeForField(k, v))?, }, "cmd" => match v { Ipld::String(s) => command = Some(s), - _ => return Err(()), + _ => Err(ParseError::WrongTypeForField(k, v))?, }, "args" => match v.try_into() { Ok(a) => args = Some(a), - Err(_) => return Err(()), + _ => Err(ParseError::ArgsNotAMap)?, }, "meta" => match v { Ipld::Map(m) => metadata = Some(m), - _ => return Err(()), + _ => Err(ParseError::WrongTypeForField(k, v))?, }, "nonce" => match v { Ipld::Bytes(b) => nonce = Some(Nonce::from(b)), - _ => return Err(()), + _ => Err(ParseError::WrongTypeForField(k, v))?, }, "exp" => match v { - Ipld::Integer(i) => expiration = Some(i.try_into().map_err(|_| ())?), - _ => return Err(()), + Ipld::Integer(i) => expiration = Some(i.try_into()?), + _ => Err(ParseError::WrongTypeForField(k, v))?, }, "iat" => match v { - Ipld::Integer(i) => issued_at = Some(i.try_into().map_err(|_| ())?), - _ => return Err(()), + Ipld::Integer(i) => issued_at = Some(i.try_into()?), + _ => Err(ParseError::WrongTypeForField(k, v))?, }, - "prf" => match v { + "prf" => match &v { Ipld::List(xs) => { proofs = Some( - xs.into_iter() + xs.iter() .map(|x| match x { - Ipld::Link(cid) => Ok(cid), - _ => Err(()), + Ipld::Link(cid) => Ok(*cid), + _ => Err(ParseError::WrongTypeForField(k.clone(), v.clone())), }) - .collect::, ()>>() - .map_err(|_| ())?, + .collect::, ParseError>>()?, ) } - _ => return Err(()), + _ => Err(ParseError::WrongTypeForField(k, v))?, }, - _ => return Err(()), + _ => Err(ParseError::UnknownField(k.to_string()))?, } } - let cmd = command.ok_or(())?; - let some_args = args.ok_or(())?; - let ability = ::try_parse(cmd.as_str(), some_args).map_err(|_| ())?; + let cmd = command.ok_or(ParseError::MissingCmd)?; + let some_args = args.ok_or(ParseError::MissingArgs)?; + let ability = ::try_parse(cmd.as_str(), some_args) + .map_err(|e| ParseError::AbilityError(e))?; Ok(Payload { - issuer: issuer.ok_or(())?, - subject: subject.ok_or(())?, + issuer: issuer.ok_or(ParseError::MissingIss)?, + subject: subject.ok_or(ParseError::MissingSub)?, audience, ability, - proofs: proofs.ok_or(())?, + proofs: proofs.ok_or(ParseError::MissingProofsField)?, cause: None, - metadata: metadata.ok_or(())?, - nonce: nonce.ok_or(())?, + metadata: metadata.unwrap_or_default(), + nonce: nonce.ok_or(ParseError::MissingNonce)?, issued_at, expiration, }) } } +#[derive(Debug, Error)] +pub enum ParseError +where + ::ArgsErr: fmt::Debug, + ::Err: fmt::Debug, +{ + #[error("Unknown field: {0}")] + UnknownField(String), + + #[error("Missing sub field")] + MissingSub, + + #[error("Missing iss field")] + MissingIss, + + #[error("Missing cmd field")] + MissingCmd, + + #[error("Missing args field")] + MissingArgs, + + #[error("Unable to parse ability: {0:?}")] + AbilityError(ParseAbilityError<::ArgsErr>), + + #[error("Missing nonce field")] + MissingNonce, + + #[error("Wrong type for field {0}: {1:?}")] + WrongTypeForField(String, Ipld), + + #[error("Cannot parse DID")] + DidParseError(::Err), + + // FIXME + #[error("Cannot parse timestamp: {0}")] + BadTimestamp(#[from] time::OutOfRangeError), + + #[error("Args are not a map")] + ArgsNotAMap, + + #[error("Misisng proofs field")] + MissingProofsField, +} + /// A variant that accepts [`Promise`]s. /// /// [`Promise`]: crate::invocation::promise::Promise @@ -661,3 +723,105 @@ where .boxed() } } + +#[cfg(test)] +mod tests { + use super::*; + use crate::ability::msg::Msg; + use assert_matches::assert_matches; + use pretty_assertions as pretty; + use proptest::prelude::*; + use testresult::TestResult; + + proptest! { + #![proptest_config(ProptestConfig::with_cases(100))] + + #[test_log::test] + fn test_inv_ipld_round_trip(payload in Payload::::arbitrary()) { + let observed: Named = payload.clone().into(); + let parsed = Payload::::try_from(observed); + + dbg!(&parsed); + prop_assert!(matches!(parsed, Ok(payload))); + } + + // #[test_log::test] + // fn test_ipld_has_correct_fields(payload in Payload::::arbitrary()) { + // let observed: Ipld = payload.clone().into(); + + // if let Ipld::Map(named) = observed { + // prop_assert!(named.len() <= 10); + + // for key in named.keys() { + // prop_assert!(matches!(key.as_str(), "sub" | "iss" | "aud" | "via" | "cmd" | "pol" | "meta" | "nonce" | "exp" | "nbf")); + // } + // } else { + // panic!("Expected Ipld::Map, got {:?}", observed); + // } + // } + + // #[test_log::test] + // fn test_ipld_field_types(payload in Payload::::arbitrary()) { + // let named: Named = payload.clone().into(); + + // dbg!(payload.issuer.to_string()); + + // prop_assert!(named.len() <= 10); + + // let iss = named.get("iss".into()); + // let aud = named.get("aud".into()); + // let cmd = named.get("cmd".into()); + // let pol = named.get("pol".into()); + // let nonce = named.get("nonce".into()); + // let exp = named.get("exp".into()); + + // // Required Fields + // prop_assert_eq!(iss.unwrap(), &Ipld::String(payload.issuer.to_string())); + // prop_assert_eq!(aud.unwrap(), &Ipld::String(payload.audience.to_string())); + // prop_assert_eq!(cmd.unwrap(), &Ipld::String(payload.command.clone())); + // prop_assert_eq!(pol.unwrap(), &Ipld::List(payload.policy.clone().into_iter().map(|p| p.into()).collect())); + // prop_assert_eq!(nonce.unwrap(), &payload.nonce.into()); + // prop_assert_eq!(exp.unwrap(), &payload.expiration.into()); + + // // Optional Fields + // match (payload.subject, named.get("sub")) { + // (Some(sub), Some(Ipld::String(s))) => { + // prop_assert_eq!(&sub.to_string(), s); + // } + // (None, Some(Ipld::Null)) => prop_assert!(true), + // _ => prop_assert!(false) + // } + + // match (payload.via, named.get("via")) { + // (Some(via), Some(Ipld::String(s))) => { + // prop_assert_eq!(&via.to_string(), s); + // } + // (None, None) => prop_assert!(true), + // _ => prop_assert!(false) + // } + + // match (payload.metadata.is_empty(), named.get("meta")) { + // (false, Some(Ipld::Map(btree))) => { + // prop_assert_eq!(&payload.metadata, btree); + // } + // (true, None) => prop_assert!(true), + // _ => prop_assert!(false) + // } + + // match (payload.not_before, named.get("nbf")) { + // (Some(nbf), Some(Ipld::Integer(i))) => { + // prop_assert_eq!(&i128::from(nbf), i); + // } + // (None, None) => prop_assert!(true), + // _ => prop_assert!(false) + // } + // } + + // #[test_log::test] + // fn test_non_payload(ipld in ipld::Newtype::arbitrary()) { + // // Just ensuring that a negative test shows up + // let parsed = Payload::::try_from(ipld.0); + // prop_assert!(parsed.is_err()) + // } + } +} diff --git a/src/time/timestamp.rs b/src/time/timestamp.rs index e6b94e03..30f62f29 100644 --- a/src/time/timestamp.rs +++ b/src/time/timestamp.rs @@ -141,6 +141,12 @@ impl TryFrom for Timestamp { } } +impl From for i128 { + fn from(timestamp: Timestamp) -> i128 { + timestamp.to_unix() as i128 + } +} + impl TryFrom for Timestamp { type Error = OutOfRangeError; diff --git a/src/url.rs b/src/url.rs index 6cca29b2..dc8bd9c6 100644 --- a/src/url.rs +++ b/src/url.rs @@ -51,7 +51,7 @@ impl fmt::Display for Newtype { impl From for Ipld { fn from(newtype: Newtype) -> Self { - newtype.into() + Ipld::String(newtype.to_string()) } } @@ -86,7 +86,7 @@ impl Arbitrary for Newtype { type Strategy = BoxedStrategy; fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy { - let url_regex: &str = &"\\w+:(\\/?\\/?)[^\\s]+"; + let url_regex: &str = &r#"[a-zA-Z]+[a-zA-Z0-9]*:(//)?[a-zA-Z0-9._]+(#)?[a-zA-Z0-9_]"#; url_regex .prop_map(|s| { Newtype(Url::parse(&s).expect("the regex generator to create valid URLs")) From 71ee7f330483d3bac1a12b1a55713b655541b83a Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Sun, 17 Mar 2024 13:15:48 -0700 Subject: [PATCH 162/188] Fixed errors in Nonce comparison --- proptest-regressions/delegation/payload.txt | 1 + proptest-regressions/invocation/payload.txt | 3 + src/ability/arguments/named.rs | 2 +- src/ability/msg/receive.rs | 14 +- src/ability/msg/send.rs | 3 +- src/crypto/nonce.rs | 61 ++++-- src/delegation/payload.rs | 9 +- src/invocation/payload.rs | 220 ++++++++++---------- 8 files changed, 184 insertions(+), 129 deletions(-) diff --git a/proptest-regressions/delegation/payload.txt b/proptest-regressions/delegation/payload.txt index a1f73406..03657047 100644 --- a/proptest-regressions/delegation/payload.txt +++ b/proptest-regressions/delegation/payload.txt @@ -5,3 +5,4 @@ # It is recommended to check this file in to source control so that # everyone who runs the test benefits from these saved cases. cc 2219b6f1cfd2b9c29cdae1174fd8633335cb25947b15ef61c3df44f2664175c3 # shrinks to payload = Payload { subject: None, issuer: Key(EdDsa(VerifyingKey(CompressedEdwardsY: [46, 111, 204, 227, 103, 1, 220, 121, 20, 136, 224, 208, 177, 116, 92, 193, 227, 58, 76, 28, 159, 204, 65, 198, 59, 211, 67, 219, 190, 9, 112, 230]), EdwardsPoint{ X: FieldElement51([1689611602193863, 490607132032821, 1343312146746774, 1090732682789050, 1815270510391065]), Y: FieldElement51([1127445621927726, 1752742079139643, 335263251657170, 455073811812238, 1802102173971517]), Z: FieldElement51([1, 0, 0, 0, 0]), T: FieldElement51([396516250482892, 1271770325328148, 2066179188049959, 970219954360817, 1259266248234093]) }))), audience: Key(EdDsa(VerifyingKey(CompressedEdwardsY: [46, 111, 204, 227, 103, 1, 220, 121, 20, 136, 224, 208, 177, 116, 92, 193, 227, 58, 76, 28, 159, 204, 65, 198, 59, 211, 67, 219, 190, 9, 112, 230]), EdwardsPoint{ X: FieldElement51([1689611602193863, 490607132032821, 1343312146746774, 1090732682789050, 1815270510391065]), Y: FieldElement51([1127445621927726, 1752742079139643, 335263251657170, 455073811812238, 1802102173971517]), Z: FieldElement51([1, 0, 0, 0, 0]), T: FieldElement51([396516250482892, 1271770325328148, 2066179188049959, 970219954360817, 1259266248234093]) }))), via: Some(Key(EdDsa(VerifyingKey(CompressedEdwardsY: [46, 111, 204, 227, 103, 1, 220, 121, 20, 136, 224, 208, 177, 116, 92, 193, 227, 58, 76, 28, 159, 204, 65, 198, 59, 211, 67, 219, 190, 9, 112, 230]), EdwardsPoint{ X: FieldElement51([1689611602193863, 490607132032821, 1343312146746774, 1090732682789050, 1815270510391065]), Y: FieldElement51([1127445621927726, 1752742079139643, 335263251657170, 455073811812238, 1802102173971517]), Z: FieldElement51([1, 0, 0, 0, 0]), T: FieldElement51([396516250482892, 1271770325328148, 2066179188049959, 970219954360817, 1259266248234093]) })))), command: "eâ'Ⱥ{Ⱥ𞹒/𖿠¦꧊=:Z꧉&ceG4ᅲQ&]%", policy: [And(LessThan(Select([Values]), Integer(-17977310508110653107821168443657725762)), GreaterThan(Select([ArrayIndex(-2063331637), Values, ArrayIndex(1729047197)]), Integer(-71863900340009931549720359637876494783))), LessThanOrEqual(Select([ArrayIndex(1500241979), ArrayIndex(-659046873), Field("`Ⴧ*ᧂ:𑛄&"), Values, Values, ArrayIndex(1750800327), Field("Ⱥd"), ArrayIndex(-1789308745)]), Integer(70158225305663682194208457939428168215)), LessThanOrEqual(Select([Values, Field("*🫁𝕄L𞸧/ຄ𑻤$u\"*𝼥m':ᨼ𑾰/\\"), ArrayIndex(-554915756)]), Float(9.08306675974206e-234))), Or(LessThanOrEqual(Select([Values]), Float(1.003428263794859e-307)), And(Equal(Select([ArrayIndex(-233587294), Field("𝒫𞻰& キ<&ஞ臨2m𞺣'𞋿&{\u{ac3}{D8𒑀𑰈5\u{1ac0}\""), Values, Values, Field("G.吸aj𑿤q^a9%.<\u{84}%.𝝹𔓶": true, "*E🕴5/.`\\x=\u{7a853}&\u{5fd16}𩟨\u{b}\u{b3012}\u{9e345}/A\u{1b}Dts&E{w}\u{d8473}": 51, ".Z\u{795c2}?*\u{bffa7}B¥-/\tb阂?": 50, ".\u{7f}": baguqegzax2jx7mre4zpv63vto6u3mtjy4ivcrpnsnrgfcb6bymbpbwz5ky2q, ".\u{80}%\u{b}$\u{c6cf9}=&&\u{4}↜$'N\u{7f}\u{b}2\u{2}?\u{65b02}Uð𫻓\u{b}\u{feff}": -1.3122304768215823e-86, ".𗨥Ø\u{7f}\u{fe136}g\u{880c8}0O\\è": false, "1={\u{4fc0c}/\u{37be6}+\u{73284}.{\r�腄/_%�?\r\u{b022e}H": false, "2𱟴.": null, "4'\u{70fad}\\�o{\u{ce77b}\u{7f}~\u{d4dac}\u{1b}V\u{b}𩱋�\t&\\\u{aee01}`🕴\u{ee7dd}": null, "5$w\u{df9bd}\\\u{6}\tѨ\u{1b}M\u{cd289}:U%\u{202e}I\u{ea2ae}\r\u{d0993}D\u{feff}Ñ": null, "5\u{db9ee}\u{feff}\"�Ⱥ🕴z\0v<<\"5": 0.0, ":": bafy2bzacecp4g47wlyry7zukgmgp6xoxsh5aqlhn2vtxbpk2j77pzmx5w5xci, "<\0\u{3fe95}\t=`Z𰲨pdËf\u{5}�\u{1b}30\u{a2f77}\t:|%\u{a65a3}": 25, "<\u{2}¥?\u{9ae9b}'?q\u{7f}\u{84177}/?d🕴\u{d0dee}_x\u{a73f9}=`/\"Ѩ¥+\u{1}\u{202e}+Ⱥ/": "%){\u{f34d0}\u{91bf2}{]¥?<\u{7e5a3}\\\u{7f}¥", "<\"k&🕴Q\u{a0cc1}\u{193c1}\u{6fef1}\u{57765}=\u{b04a9}\u{1ee38}*$i�趼\r:^�\t\u{2}/": null, "<'\u{202e}": [246, 133, 18, 125, 51, 172], "<*\r\u{feff}'\u{b}\u{feff},\t\u{202e}\u{b}": 1.9096030614441494e34, "\0#.\u{e50a7}`\u{6b781}\u{72c56}IѨTȺ:\u{202e}\u{c1d84}\u{d1ebf}F\u{bc6f1}P�\u{1b}}?d\u{feff}Ⱥ\u{96517}\u{7}": -21, ">\u{7f}h)\t&'\u{b}": "p$\u{ce2d9}T\t\u{6486c}\"\u{af416}", "?*=\u{eea8d}\u{a8f3d}\u{479bf}\\\u{577da}<Ѩ&\u{202e}\u{ceb29}\u{8}/\u{7f}\u{7f}𘂤*\u{91}\u{96d48}\u{202e}\u{60a35}6\u{7cd5b}\u{b}~7\u{feff}\u{fc2b3}\u{6ca08}": bafykbzacedzd5rmt4wtpaenao7lyjfstkk3babvem5zvxsx66esxsdwdmnuok, "?\u{f5061}\u{6}\u{adb94}\tz<\u{8e6f5}\t]\"\u{1b}\u{50c21}?=\u{feff}\"'\u{cdff0}{�\u{11b58}\u{7f}ñ¥'\"I\u{6efe5}\0": "M\u{d7240}\u{5fa2f}\"\t\\Ⱥ\u{8}\r𓂂E\u{615a1}$ :\0\u{4b6de}2�&::", "D\u{834c0}": bafybwictq6cimsvk5g36pcopei5vzwuqjgz6zab4qnlw6h6pexvenpnuz4, "G{\0P\u{202e}z?<*\u{e4d94}\u{caeb6}c\u{b}\"\u{d4fa0}\0\u{3d0e1}\u{c315a}": true, "O:{.:6\u{1}𒋵=": [215, 19, 250, 35, 145, 227, 89, 97, 226, 22, 100, 90, 212, 248, 231, 66, 1, 74, 222, 19, 252, 253, 50, 43, 95, 28, 217, 195, 43, 193, 37, 211, 223, 168, 162, 177, 117, 244, 235, 50, 129, 137, 255, 246], "P\u{7f}m[*^\u{d4b73}\u{a4603}\u{cae36}\u{202e}C\u{2}\u{99a81}/<": "\t=/\u{202e}{?=z𣿸&Ѩ\t\u{10fefb}:`\\\u{88684}ᇵ&<'IB9Ѩ", "Q=/\u{2}[W$🕴Ѩi`\u{6b1f0}g\0\u{202e}&äq\0\\\u{e7b6e}\u{7}2": 0.0, "S\u{feff}¥\u{363f5}\\�\"|/$\u{1b}🕴\u{d61d7}\u{9c5fc}\u{5}%": true, "\\": 1797410591.2173085, "\\'\\\u{72082}9\r\u{fd777}&'\u{8}-\t$Ѩ\u{85}&'\\$\u{202e}\u{4}\u{d59a8}𐋪": null, "\\*\t&>ó": [197, 70, 103, 161, 242, 103, 93, 85, 220, 20, 175, 146, 70, 248, 167, 201, 81, 105, 89, 137, 113, 31, 52, 197, 100, 44, 200, 169, 146, 8, 75, 185, 88, 27, 164, 65, 95, 19, 82, 178, 129, 49], "\\¥𧷋%\u{7f}/%": 5.3388979855107657e-222, "]\u{2},n{\t": 4.726007655014673e-268, "]\u{e5bc2}:\u{67d79}": [192, 72, 37, 153, 92, 243, 115, 45, 85, 213, 125, 24, 161, 32, 231, 155, 146, 251, 181, 211, 205, 114], "^": false, "`\"\rBbIU\u{b38e7}~{\u{1b}<": [166, 52, 57, 140, 164, 87, 17, 112, 52, 240, 111, 77, 237, 27, 236, 44, 43, 106, 52, 249, 5, 150, 136, 195, 238, 123], "`=": 37, "`=\0\u{ae1db}$'\u{b}<{\u{1}\u{2};": true, "`~HѨ\r=": null, "`\u{91304}8?=\u{456a5}?\u{ee878}`": [72, 35, 50, 245, 247, 42, 83, 78, 154, 184, 6, 235, 171, 159, 23, 112, 160, 167, 206, 73, 56, 6, 234, 198, 253, 149, 213, 217, 121, 184, 227, 205, 182, 89, 67, 116, 130, 153, 30, 111, 90, 28, 205, 170, 195, 105, 252, 13], "`\u{b62b4}l\rȺ\0䱮Ѩ\u{325fa}://%.H*\r4\u{67fc7}f\u{feff}\u{97c83}d_z𒋤𩞬<": "{\u{7}\u{1b}\u{1}\t\"Ѩr\u{5}", "`\u{db05f}\u{82068}%\u{e3259}\u{202e}&\u{8916b}r\u{b}\u{eda56}�7\u{7f}\u{800bc}": null, "a\u{7f}": -4.342818332244186e254, "e;='\u{66fb7}¦_.R\u{6}S\u{99a3c}1*'d�\u{aecf1}?¥:\u{cd4f6}🕴¥{\u{202e}V'": null, "i\u{7acc3}\0*\u{3a0a4};\"G\0x\u{202e}\u{202e}\u{a6ef1}\u{7}y\u{c4c0c}*=\u{202e}Ñ|]\u{b}`S�\u{63b03}&¦🕴|": "\u{9b}\u{feff}/\u{2}\u{32ba0}$5N\u{c18b1}\u{6e305}", "i\u{b88de}:%<:\u{b}i¥\\%&\u{e9081}\u{36b96}\\wa\u{53cd7}\u{7f}\u{441e2}": [254, 111, 111, 15, 235, 43, 5, 115, 184, 222, 104, 79, 29, 45, 167, 151, 205, 114, 64, 194, 79, 2, 66, 154, 153, 108, 151, 126, 250, 233, 134], "z": true, "{\u{7f}Ⱥ&��0a]\u{7f}\u{b3046}\u{2fa93}\u{7f}<`a\u{10b100}b\u{e36be}-:\u{b}}I\\\u{c99dc}(�\u{d4177}<": 5.69430214693307e-309, "{\u{90}�\r¥%`": null, "\u{7f}=n\r\u{b904f}m\\`&\u{8d}&\u{8}q\u{10fb4a}\u{202e}\u{feff}": 30, "\u{7f}\u{6677f}\"\t\t𥡏&\u{929fa}%\u{1b}": false, "¥\u{4b398}$\u{b}.\u{10edc3}.v\u{4}\u{c8f3b}`\u{1b}o\u{b}B�.\u{8a4e7}": true, "¦\u{567e2}\u{789fb}.\u{1}\u{bac03}FeLѨ": [46, 154, 121, 10, 113, 153, 177, 226, 108, 199, 94, 194, 182, 56, 189, 253, 144, 68, 220, 178, 227, 39, 5, 27, 46, 171, 2, 171, 100, 125, 136, 48, 185, 143, 175, 224, 38, 180, 89, 73, 253, 97, 152, 89, 126, 226, 69, 159, 39, 4, 5, 151, 166, 157, 129, 18, 74, 141, 30, 245, 18, 68, 59, 4, 84, 71, 127, 83, 157, 160, 70, 180, 19, 67, 237, 156, 119, 247, 69, 10, 220, 32, 51, 35], "Ê`/\u{e98d1}`8\u{5a946}¥\tѨ\\\u{71794}\u{1b}\u{5d467}\u{5f80a}\u{2}o\\\u{78e03}\u{1b}$V\0,Ⱥu<\t\u{4}": "l\u{80}/H", "Û\u{a246c}\0\u{cc1d1}\u{1b}": -2.3446004632097955e-204, "Ⱥ\u{48608}�\u{1}Ѩ\t\"\\\rRx\u{feff}𱋭🕴.>.\r?/Ѩe": bafybmibqonatpssw5xwj3ckqwghe3uv52fbojbwo6wevvgyxhhi6is34iy, "ЏѨ$N==jX\u{ef98d}?{Ⱥk\u{4c9b3}\u{a5c43}'\rx": true, "Ѩ?=Ⱥ\u{7f}\0🕴$�\u{39466}\u{1b}�\"`\0.\u{feff}*.x-\u{7f}EѨÔ\u{7}\u{10f98d}$P": false, "Ѩ뀳🕴\t\u{202e}<{\u{950c8}\0\u{6abe2}2¥G\0": bafkrmicucdpcfbvzkhxwm7wgcnbx7wbbsx7yf4xs3xnebcwqv4fzfnyeui, "\u{202e}\u{5}\u{f93a8}�\t\u{93751}DX.4\u{b}\u{47079}\u{cb546}Ê\u{3c9c6}('Q\u{df412}^(?%\u{1b}\u{d4087}\u{87657}\u{69291}": bafyr4igptyd3pys2jr5l7f6wly2ixknaqjhxa2goliyvnoxef4q5npn2wq, "\u{202e}//)H\u{489d0}\u{b}\"\u{7f}7\u{fd8c1}\u{52967}": -49, "퀹\u{1e584}Ⱥ\u{b5e48}?\u{d70a5}\u{202e}\u{feff}o&Ⱥ\t2&$TѨ\u{c7d9d}¥z9�#B,\u{7d2c6}\u{a9082}": -39, "�\u{b}\u{9b}:\u{afa9e}\u{8b}\"G%=\u{6}\u{b1e04}𫃏\u{202e}{:Ⱥ'\0[;\u{cbe4b}Ѩ¥V\r:e\r�\"\u{36274}{\"\u{feff}\t\u{1b},\\�鴇\\µ": "*\u{b2484}(j'0\\�\t\u{feff}~I*\u{1abf0}鲫!¥í\u{7f}\t\t\u{feff}", "\u{2}¥@@x\u{7}o<>\u{b}>�\"=\u{4a714}H\u{356a6}Y\u{feff}¥\0Ѩ`\u{8586b}�@\u{86433}8\u{b5eb0}U\t\u{928a7}?:3V\r\0\u{e963}\u{91606}\u{f5d9a}`&&\t\\{$&`": [203, 219, 129, 233, 167, 86, 133, 68, 30, 123, 72, 152, 245, 36, 18, 51, 184, 218, 78, 248, 74, 234, 85, 239, 225, 163, 68, 62, 10, 51, 41, 233, 241, 66, 225, 241, 213, 58, 199, 207, 196, 20, 9, 130, 162, 125, 157, 172, 66, 51, 176, 230, 83, 217, 243, 126, 106, 4, 227, 168, 130], "?\t#?\u{c8453}l<\u{ebb70}<\u{202e}\u{1}\u{4a0a6}\u{bb34d}𧆽{\"\u{202e}\u{a6e5e}<\u{2}$\u{1b}{\u{1b}\u{a7345}\u{bcba2}=\u{55f80}𠘓`": false, "@\0 \u{e2e8b}\u{feff}&{¸\u{1b}`Ã`<\u{c80a3}\"h%V\\:\r:{%\u{b}\u{91b96}\0": 16, "A\0\0%Ⱥ\u{7f}o6Ѩ\ra\t\u{fa6d1}": 0.0, "E5\u{3ef04}\\iѨ4==\u{ad632}\u{103a8b}\0\u{882ea}\u{3fb8f}\u{80a35}{\r\"\u{9afa0}\\<3*\u{e368f}\"{Ѩ\"\u{85b40}/": [62, 206, 252, 139, 165, 123, 83, 85], "K?\u{15ea9}%/Ⱥ\u{39c0e}Ѩ\tM/\u{806b5}𣎂+": "\u{7b58c}\u{eb27}", "Py/\u{eeae7}\t:z/u&\u{fdecd}\\`<": 2, "P🕴EU\t$\0V\"*": true, "S\u{6}?'{¦~:¥\"\u{797af}:Ⱥ": false, "W/\u{7d22d}\u{7f}k\u{6f5bc}\"/\u{7ac51}\u{feff}\r\u{1b}\u{b}`\r\u{16d8a}\"\u{feff}\u{4e68c}": null}, 3.7320199453131945e18, true, "\td", -2.1302548429054803e-66, [9, 130, 254, 29, 224, 58, 3, 226, 45, 154, 180, 58, 156, 107, 43, 160], [184, 58, 122, 21, 191, 153, 91, 193, 125, 41, 253, 9, 18, 17, 100, 75, 60, 215, 91, 98, 131, 82, 225, 220, 226, 154, 246, 54, 253, 108, 18, 104, 142, 238, 92, 249, 74, 194, 85, 60, 101, 114, 120, 130, 76, 198, 254, 54, 43, 192, 5], -3.035213207805024e-151, true, null, true, [144, 5, 247, 45, 158, 101, 252, 143, 38, 225, 226, 145, 14, 34, 204, 157, 201, 182, 152, 54, 29, 245, 248, 233, 203, 103, 50], 7.126162207025803e98, 0.0, 38, 44, false, 1.442945330979412e-308, -3.2821103465088214e185, false, -0.0])), GreaterThanOrEqual(Select([Field("\u{dca}?i%ѨѨ<\\{Ѩৡ=ᨁr\"T\u{1a7f}🛞{༴Ⱥ:ᛧ"), Values, Values, ArrayIndex(-206845918), ArrayIndex(284566073)]), Integer(-130442174005234510445148006169593931048)))), Every(Select([Values, ArrayIndex(-1235287252), ArrayIndex(1235974543), Field(":�𐔆Ѩ1ꬓ*%𝔹⑩%By�ࡤ{6&🃅%ᒜ!*t\u{cd5}ܔÈ🃌K-:f"), ArrayIndex(-74267187), Values, Field("𐝧¥ѨK𛄲ﹰ~/𞺀Ⱥ𐁑{A℈ꧻxኁ$X")]), Every(Select([ArrayIndex(-835767092), ArrayIndex(-90842294), Values, Field("&Z𛱺\u{1a6b}¥¡\u{2002}O�Eࡪ(/ѨѨ\u{fb3}ö2<=u,ⶣb"), Field("হ\u{1d242}]𐺱'<×\".᪗\u{cc6}%\u{9e2}/$`+ꚠ𞹲ѨRr'7\"\u{1921}"), ArrayIndex(-1657524013), ArrayIndex(-1358049927)]), Equal(Select([ArrayIndex(-2133215332), Field("�ѨA$=%0𑒌$𞴻{ῚL𒿎<*Hÿ)HಯmJ᧗ૉvকj¥𑶄ዅ"), ArrayIndex(1372867808), Field("9U𒐽ᅥטּ:^�ཬ{L\u{1a7f}🛞e{𝔊sை8%$-SޥJ=\u{1e136}&"), ArrayIndex(-1515605936), Values]), Newtype([[220, 112, 193, 144, 130, 2, 201, 3, 173, 97, 18, 34, 79, 189, 93, 185, 99, 8, 146, 72, 228, 21, 109, 226, 107, 211, 58, 9, 68, 90, 109, 0, 69, 37, 94, 48, 179, 143, 33, 56, 70, 63, 33, 221, 197, 203, 25, 245, 187, 130, 222, 127, 221, 178, 88, 248, 218, 158, 87, 24, 127, 138, 100, 104], bafkr4ighuk7gjom3gactib6iatiimrm4ytvtdk7y5k4pzutsg56i77lanm, -8.793256611809324e-208, [49, 12, 111, 228, 6, 196, 193, 26, 82, 212, 21, 13, 44, 254, 161, 77, 168, 161, 151, 85, 22, 53, 168, 74, 48, 15, 119, 171, 245], 3.675952639990706e158, false, null, null, null, null, -156.1567975459344, "\u{1070c5}\u{202e}m\u{7f}'#\t\":\u{dad72}ü4\u{202e}\u{7f}'涛\0$E{-/\u{202e}\u{7f}b", "\u{13e62}$e<\0&<\u{c5acc}ny\u{9a704}\"\u{202e}𔖚D", bafy6bzaceagsds7mbaud2m6qhdohvpgwuhfeedqwrntvioyma3kuo5v6vtyt6, null, null, "\r\tg:\u{46cc2}", [142, 185, 121, 120, 78, 34, 236, 132, 123, 49, 125, 116, 146, 139, 230, 17, 174, 47, 83, 146, 252, 30, 220, 161, 163, 207, 136, 121, 230, 68, 139, 61, 190, 89, 223, 74, 72, 219], [28, 249, 124, 123, 205, 232, 92, 8, 106, 47, 104, 13, 104, 201, 76, 228, 38, 128, 160, 168, 2, 34, 83, 71, 248, 91, 111, 250, 225, 49, 225, 194, 169, 133, 253, 173, 44, 47, 27, 4, 181, 140, 138, 211, 3, 136, 21, 139, 138, 196, 191, 191, 76, 163], true, [87, 3, 80, 153, 162, 125, 138, 138, 127, 8, 103, 89, 128, 18, 40, 41, 190, 156, 216, 106, 118, 162, 241, 107, 134, 144, 107, 52, 51, 165, 85, 124, 197, 155], "\u{2f39d}{¥\u{40daf}ѨS\u{a44e7}{\u{a7dc1}�\u{cee1b}'", [235, 139], false, "", "\u{a1d4d}\u{feff}\u{4}\u{feff}3\u{1494d}\u{e1172}.\u{1b}`\u{d2482}{\u{9e}\u{e1d5d}\"3/{\u{5}2].Ⱥ¤<\u{96fa3}Ⱥ\u{49ea4}*\"p\u{b83db}\u{6}\u{6}": null, "?/\u{bdd3c}\u{370b4}\0*\u{102d37}\u{cbc47}.\\Zâ\u{1ced4}KѨ🕴::\u{7f}/": 44, "?8?A:/'H\u{b}\tr\"¥=qu\u{b}¢\u{fde59}\"\u{2}ye'?\t\u{ea1b7}K`\t": -3.9299672377331247e-146, "?{\u{7f}%\u{7f}\r%\u{d3348}\u{b}\u{eb7b2}%¥\u{9406d}\rѨѨ],\u{202e}'\u{105dc6}<^úr\u{8015f}": "\u{9d}\u{891c2}𫩼\u{606aa}\u{45288}p!/&G:㝸ã\u{97}Ⱥ\u{b}", "?Ѩᆬq7/%\\\t\u{feff}\u{da5c3}\"\u{8509f}\"H`\u{1b}%\u{4d464}\u{479f4}\"\u{c147b}\u{1b}/𧳓*,|": "Y\\\u{7f}\u{b1fcf}\u{e99b9}7\u{9e18b}:?\u{ede3}$W", "?\u{5d644}\u{5a09a}\"�l\u{1b}/\06Ѩ.&{\u{10a396}": bafyrmihqvy27erjh5esd3x6wdgnr6z6lru7wfcmkuuvzp7h7am6z63mbia, "A\u{763e5}& \u{d7169}\u{e8031}{\u{b}*\"\t𰩧\u{548b4}?u`/\r🕴\u{7c626}$å🕴\u{10dd6}:🕴": null, "DѨ\u{6ebc8}\u{7f}<\\[?\u{7f}&ኵ:f=𫗿\0�\u{202e}": "s\\/;*\\\u{4150f}E", "F\u{1b}\u{10ea63}\r": -3.881911088191245e-265, "H.¥$": -21, "K\u{cbff5}\u{202e}\u{1260e}\u{feff}&:%\t$\"|&\u{342e6}*:\"&ju/c": true, "N\u{b}&\u{feff}\u{691d4}\u{6}\u{b}:&*Ѩ\u{6f0dd}w\u{e581c}¥z�\u{9cce5}\u{16d5d}\u{4f30a}**\u{93d8c}\u{202e}\u{7}{\u{1b}{±'{K": "%\u{feff}¥", "[𪲁@\u{1387b}\0]\u{6fca2}Bi.\u{89a95}\u{6a578}\0\u{4a64d}\u{b}\t\u{647d9}n\0^\u{8ff74}Yk\u{9c}\u{9c}(": [116, 173, 176, 186, 52, 23, 252, 79, 37, 2, 204, 64, 93, 133, 149, 198, 164, 255, 254, 251, 238, 69, 160, 225, 130, 239, 127, 62, 193, 179, 31, 95, 161, 114], "\\\u{6d15d}\u{b}&\\Ѩã\u{e6f74}<\u{a15a9}\tѨ,\u{b}\u{819b6}\u{3ef57}/\u{4ddf0}Y(\u{5}\u{100a6f}\u{b}/.:": -37, "]\u{3}\t\u{e5fb9}?{\u{202e}z\u{44ab3}\u{39431}\u{e8c44}¥\u{79b81}'\u{777e6}�:{%\u{3900c}{%\u{108b7b}<{`\u{7f}Ó\t🕴T.": 29, "`\u{6}.:🕴{\u{9c4a2}\\<*:": bafy6bzaced7s7j4l6dybvc55yypxhw5em5sg2ciws33rjhgce6hmfmpgf4fdu, "`o=\u{6}3\u{e81b2}\u{1b}G&\0tw\u{3}/\u{3d977}`㊈)=Y\u{2}\0": false, "`\u{44886}/\u{a1f9b}\u{5f978}:\r\u{95be2}/🕴": 0.0, "c\u{5}\u{4bbc4}b\u{c96f8}+%$\u{202e}'<\u{e078c}\u{ac7cc}x\"Ѩ": 53, "n": "\0?\u{efd30}\u{686e5}\"\r\u{5fe32}\u{b5b0c}\u{51f54}\t\u{89}=\u{33916}ZM¥\u{4}\u{7}m�}\u{91c01}\u{6839a}*\u{c59e9}", "oѨ`": null, "w\t\\`\u{da1d2}*&.$E;\u{62ec7})\"Y\r\"5\u{bd563}\u{326cb}{å?/\u{b}\u{98e3b}": "D::\u{202e}F\u{d9e2c}J'=\u{ec5db}\0'\"Ѩ\u{1b}\u{419e5}𮞿?E*mb\u{feff}_Z\u{ab92a}=\u{89c4e}逋\0\u{7f}J", "y6\t<": [179, 71, 5, 85, 100, 29, 222, 53, 97, 142, 194, 243, 96, 220, 13, 106, 64, 105, 167, 218, 123, 136, 220, 228, 82, 153, 8, 92, 185, 11, 112, 146, 197, 109, 163, 11, 117, 83, 66, 85, 178, 149, 17, 95, 212, 87, 96, 62, 216, 80, 38, 36, 236, 156, 22, 40, 44, 133, 95, 15], "{Ⱥ/&\u{7f}\u{5795a}_*\0*\u{a13ce}f?`/\u{1bd42}<𮧏<\\U(n\u{2ffdf}\u{bad0b}&]\u{feff}U🕴$": 9.93350022419413e112, "{\u{39ee3}TI\u{ea081}%Pï>\"Ⱥ\u{feff}{\\j": {"": "\u{feff}\u{1}\u{1}&=¥\u{68854}&/ö\\\u{33a28}\u{d8908}f4]Y\u{39e57}**Ѩ\\*Ѩ\u{5270a}{B¥+x\u{feff}", "\0${\u{8f006}:f<\u{3}\u{ae249}\u{a3dc8}\u{5eb29}\u{b67f9}%:\u{37105}\r\u{1b}'$\u{2faa8}$/A\tW'\u{909b7}\0\u{2}": 2.914324980421285e-195, "\0$\u{ac00f}\u{1b}9\r\u{3}\u{2fd7f}=:R.\u{e3705}\u{c4450}NG\u{73b7c}&$\u{202e}\u{b527c}GL": "\u{a3b69}6a*==?\u{feff}\u{1a1cf}\u{a82da}`/==�🕴\u{6b826}\rD?A\u{feff}\u{1}", "\0*{\u{202e}\t�\u{1b}/|Ѩ%©%": baguqefragwjjb3m3ljrschzrusb2unylas52kabmwfr34wqmvo6rkg2avvuq, "\u{3}\t\0\u{6695e}\"{»1\u{202e}\u{68a4a}N$": [233, 212, 54, 104, 52, 88, 23, 191, 247, 21, 223, 50, 174, 43, 59, 131, 37, 131, 62, 190, 142, 176, 67, 43, 184, 235, 120, 202, 175, 190, 189, 145, 211, 136, 59, 252, 222, 235, 131, 213, 187, 34, 118, 61, 42, 93, 166, 43, 180, 114, 34, 166, 57, 195, 172, 167, 175, 177, 81, 106, 26, 118, 209, 95, 105, 177, 234, 126, 58], "\t\u{1b}7🕴¥qb\u{8}\r\t\u{1062f0}%'\u{b}.g\u{411f2}𭕨B{Ⱥ\u{77264}/\u{b}D\u{3d766}<": null, "\u{b}/<\u{34eed}\0S&>�텐$$": ".\u{7ebf4}Z\u{d3912}\u{feff}?*H\"\u{202e}𦊇\u{91}Ⱥ\u{7f}\r?{)<\"\u{42b46}?T\\\rg\u{ec834}\\\u{7f}\u{7f}¥", "\u{b}?\t\u{2}�4¥𞸩*\"\u{578ba}\0G%��\\r%«=🕴=\\&*=": "\u{8237a}\u{8a}<\u{8ab59}.?<.tàô\u{945d6}\u{feff}`\\#\u{ce61d}c:)?.`a<\u{9d398}\u{962eb}\"&", "\u{b}ÞѨ¾y狦\0🕴\t\u{19d5e}\u{f0148}": bafykbzacebxelvnoczrdap55pukvsxpc6gicbmnqzgsouqceppvlhsicwet6e, "\u{b}ãx\"\\\u{4}`\r:\u{feff}ᦇ\r": 22, "\u{b}�`+\".?hC\u{88713}:\t\0N¥/Ⱥ\u{1d2b9}1&`Ѩ.": 0.0, "\rAf7\06`\u{ff186}\u{9f715}\t": "/$\u{eaf96}\rW.{{.l흄*\u{1b}\"🕴!?\u{a64ef}\\\u{7f}/\u{109cc5}\u{5}'\u{b}\u{fc0c5}𱇴\u{d1388}", "\rUKG/\u{3}\u{2}ꕻ\u{b}\t": -1.3995651248504633e262, "\r\u{72228}\u{3}\t'\t\u{c5bb7}\tv": [207, 209, 38, 232, 225, 181, 157, 174, 248, 85, 75, 104, 14, 234, 9, 86, 149, 189, 217, 176, 122, 32, 78, 186, 174, 19, 241, 185, 202, 135, 32, 184, 35, 47, 78, 189, 35, 153, 142, 128, 46, 7, 65, 55, 37, 215, 0, 109, 138, 244, 78, 202, 124, 93, 146, 143, 199, 70, 40, 153, 254, 139, 213, 103, 34, 98, 157, 146, 23, 126, 115, 247, 59, 186, 119, 63, 16, 179, 123], "\"%3\r\0\t*\u{74f05}\u{10ecdf}%\u{56b43}\u{e2041}\u{b}Ѩ🕴%\u{47ac1}\u{b4501}\u{10314f}\u{b}": 8.190682906409504e77, "\"üE8À%": -5.01070380812316e-309, "\"🕴¿%z4q\u{f5f81}\"\u{202e}\\": [99, 154, 31, 116, 21, 233, 127, 12, 159, 139, 166, 170, 113, 31, 35], "$&\u{81e34}�\u{feff}\u{3}🕴\u{b}\u{3e109}\u{8e8b1}A\u{1b}Ѩ)\r¥\u{c68f3}\u{10081f}\u{10dd8f}": bafyrwibh3uv7biwsbpe37hkwovhrpkm3l7s5icdgji6imq5fjykcl7p24e, "$'Ⱥ':\u{f6dca}{&\u{7d81d}GQѨ𧗿~.Ѩ\u{3f8b4}\u{feff}\u{2}\u{2}`\u{b}\u{202e}\u{7f}\u{8}\u{b45ad}": bafyrwiczadd6c3wkaraxq3lg5clhqyngn6fprrjscfzxfe2mp2kg4pvxba, "$}\u{4998f}}\u{7afe3}\\\0n?\"V\u{1}\"{!*y%\u{1b}\rꢢѨI\u{f13fb}X\u{1061f7}\u{7f}%'l\0=\0^)\u{5ac7e}<": [60, 118, 165, 67, 27, 119, 21, 252, 153, 133, 75, 209, 126, 27, 107, 9, 142, 133, 201, 242, 96, 56, 139, 85, 39, 185, 191, 172, 121, 16, 61, 101, 100, 93, 23, 114, 153, 201, 213, 220, 14, 1, 73, 194, 75, 71, 226, 125, 205, 187, 97, 24, 229, 247, 75, 87, 172, 90], ".`\u{bec17}Ç'\0/\u{d491b}𘅣🕴.\r": 1.2131909958473984e-266, "/\"": bafkr4ifg4p3nsgepq6lvarhhrt4lxydl5arndmqieaknvnqhyvw4l62eau, "/,/\u{b}\u{a9092}\u{3}.\u{7f}ñu?Ⱥ'&\u{e2082}\u{3d612}u": baguqfiheaiqibdayxvhi3ktwv4rfqhx7adreiuo2muqdzc72zj7wydxi2ircenq, "/.\t¬79\u{202e}<%Z\0\u{7}\u{b}&\\🕴\u{c07fd}j=(1\0𮟻mJ�.\u{fe739}ȺXB\u{feff}": [35, 209, 94, 4, 132, 12, 218, 13, 76, 94, 110, 176, 233, 104, 74, 8, 231, 103, 204, 48, 35, 1, 208, 81, 103, 39, 15, 209, 174, 232, 89, 234, 13, 222, 88, 45, 83, 226, 52, 81, 83, 59, 125, 241, 159, 38, 79, 5, 21, 197, 38, 169, 183, 154, 154, 183, 251, 209, 151, 212, 196, 132, 155, 189, 109, 45, 206, 253, 141, 141, 226, 210, 69, 80, 142, 13, 168, 40, 84, 52, 228, 159, 231, 76, 229, 248, 47, 21, 115, 15, 247, 164], "/.\u{b}\0gMg%\0\"n\u{1b}�\u{dc8ef}": null, "0\\\u{b2c71}:.\u{7f}.q\u{ee1a4}\u{3}.`:=\u{7f}shÕ\0'\u{ff5c2}\0<\u{48d2d}": null, "6\u{7f}z\u{fb43d}\0\u{e71c2}n\t\u{1b492}\u{be144}%m\0O£{\u{4}ß`S\u{cc20a}\t\r@\u{1b}\u{2f392}T\0�:4i": [184, 132, 8, 217, 132, 246, 183, 246, 210, 254, 92, 125, 142, 179, 49, 205, 173, 48, 36, 66, 57, 14, 184, 195, 88, 109, 101, 153, 91, 53, 120, 69, 198, 5, 81, 144, 203, 189, 119, 182, 143, 191, 14, 61, 34, 36], ":(`T\0\u{1b}\u{1b}\t$¥z\u{36613}\u{a5f2d}Ѩ\u{d1ec0}Ⱥ\u{7f}\"'u\u{b7fb7}ë` \u{feff}\u{5d6e0}f\rê🕴": [191, 71, 93, 227, 249, 253, 186, 190, 214, 13, 71, 250, 111, 182, 66, 219, 22, 139, 183, 203, 250, 58, 184, 20, 20, 213, 80, 32, 66, 40, 214, 69, 53, 203, 170, 113, 150, 95, 7, 238, 77, 250, 5, 90, 119, 215, 135, 80, 225, 91, 49, 36, 143, 153, 196, 238, 205, 224, 18, 127, 173, 184, 246, 90, 134, 17, 119], "=@\u{8f9f3}7?\\𫥂\u{1}": "i|*\u{c09ed}'\u{b871f}\u{4}*\u{202e}&\\\u{105cbd}bѨ\\*?{F\u{feff}uȺz\0`\u{feff}{+", "={": null, "=\u{a02a9}\\\":¥🕴\"\u{202e}P\u{7f}\u{b7f71}\0\u{3a7e9}Y<î\u{157d9}Å": [19, 138, 165, 245, 200, 2, 67, 217, 45, 240, 188, 107, 149, 30, 61, 57, 176, 165, 123, 163, 219, 5, 81, 31, 155, 252, 139, 204, 155, 201, 120, 3, 32, 18, 218, 98, 147, 233, 22, 106, 60, 86, 204, 202, 147, 32, 223, 159, 66, 107, 250, 251, 19, 167, 246, 252, 90, 205, 212, 54, 251, 5, 241, 43, 213, 120, 23, 1, 29, 163, 69, 234, 99], "?&🕴.*.\u{605a3}¥&j/=a\\\u{b}*Ѩ*'¾�ธ#Ⱥ\t": "\u{7f}\u{8}Y{5\u{60a62}jN=f\0'𩧦\u{feff}\u{7}//a¥kK\u{48989}&\u{f203b}\"\"`", "@Ѩ": "/*\u{95147}\u{b}{`\u{4e71c}", "C\u{3}<Ò?B¥@&": [130, 96, 105, 54, 223, 53, 178, 56, 46, 187, 18, 221, 159, 48, 247, 89, 117, 41, 108, 127, 91, 200, 247, 120, 240, 237, 155, 170, 65, 55, 58, 141, 247, 254, 60, 102, 29, 148, 211, 55, 4, 14, 68, 42, 90, 173, 222, 142, 20, 22, 68, 185, 132, 255, 132, 185, 189, 197], "D\u{a65a4}$\u{df148}\\F\rntѨK\u{1b}jd\u{8ea98}\u{b106e}�": 1.3784978135410346e-170, "Eò<.z\u{781d5}\u{2}\"�^": null, "H\r\u{361f5}{\"�\"\u{41b3d}\\'𦀳¥\u{80}Ⱥ\u{c26fd}�6-`N\u{dca3b}G\u{1de7a}=\\": true, "L\u{7f}:¥=t*{_AP\u{366cc}?wȺ\u{eb31}o.\u{881ae}\u{77df7}t\u{b}\0{\tѨ\u{d6c3a}": [97, 251, 86, 194, 220, 214, 153, 209, 190, 118, 25, 200, 75, 133, 240, 162, 84, 193, 128, 3, 238, 222, 6, 149, 222, 157, 239, 202, 16, 102, 50], "S=U\u{bff90}/?\u{da120}.\u{633c3}a|\u{1c959}\u{3be11}\u{feff}X\u{a9622}?\t.Å.🕴=\u{73c63}𰰅\u{61725}¥\u{a6664}<\u{895f4}i": [140, 113, 176, 106, 69, 9, 192, 221, 52, 44, 56, 187, 134, 114, 29, 65, 208, 39, 0, 95, 237, 224, 76, 195, 8, 225, 21, 98, 228, 60, 95, 240, 189, 156, 136, 235, 72, 132, 236, 170, 1, 250, 184, 134, 77, 48, 249, 199, 172, 3, 66, 201, 75, 15, 29, 254, 104, 111, 158, 53, 80, 85, 74, 36, 20, 15, 150, 52, 31, 50, 233, 6, 228, 244, 185, 202, 142, 56, 126, 47, 69, 35, 225, 162, 147, 42, 172, 213, 71], "\\\u{1b}Ç'(\u{76ac1}\r\u{202e}NÙ\u{87e35}`{\u{52666}Ѩ\u{7f}\u{e28bd}?窣\0\u{e4901}🕴\\": 1.307777027892035e-308, "\\V$\u{71773}\0.H𘑞\u{202e}`&)'\u{cc7d7}4_�📲j.\u{c5777}:\u{b}{\t?\u{abcf6}\u{2}\u{f114a}": null, "`\u{1}7\u{dc7f6}è\u{1b}\u{e6461}", "bF罩`/?{\u{6}m�🕴\u{a0dcf}\u{1b}\u{85683}\r*z\u{3}`\u{202e}\t\0¥\0ó�\u{fa710}": null, "f\u{71088}\u{eeba2}/\u{1b}\u{b}>\r:$H\"ï\\\u{7f}\0¥{+?": null, "k:\t<鏁\t^:u碗\tñr:": bafkrwidekl2tflko33qlm47p7adikj4q6bdju2kq5elnuipzykk565mhby, "s<Ⱥ*[L*<ѨÉ\u{88d19}": [88, 175, 1, 80, 241, 221], "u\r": -2.6904580729605092e122, "u¥\u{feff}\u{1b}": [1, 36, 101, 136, 222, 223], "x`\u{6d787}\u{e5350}\u{df1fb}(>qP🕴\u{ba6ac}𮠰🕴\\\u{4b5e0}J<": false, "{\0\u{3b4bd}=g¥\u{107402}\u{5c069}Ѩ=5k*$`<%p": [42, 216, 219, 136, 90, 214, 91, 194, 12, 10, 62, 81, 119, 54, 126, 138, 227, 206, 148, 138, 95, 191, 247, 89, 212, 142, 18, 121, 148, 163, 152, 177, 37, 69, 222, 22, 226, 117, 153, 5, 89, 234, 92, 155, 223, 10, 158, 200, 18, 37, 110, 103, 181, 109, 202, 35, 39, 117, 93, 5, 90], "\u{7f}\u{7e6e9}\u{8b}𥚝": baguqehrahkajpop4geeiwsi7gu2wdsuybkf6lbq6rfen4f7v3vrtqbyazd3a, "\u{7f}\u{b601e}'\rK/\t\u{feff}¤?\rp\u{af66a}": [73, 33, 204, 97, 67, 215, 167, 191, 121, 17, 85, 93, 75, 141, 150, 250, 223, 157, 229, 29, 48, 217, 58, 27, 191, 36, 145, 93, 14, 17, 12, 32, 61, 36, 83, 58, 181, 136, 104, 35, 237, 219, 146, 240, 223, 170, 203, 45, 27, 109, 190, 5, 96, 122, 180, 241, 211, 211, 147, 12, 136, 219, 25, 19, 108, 191, 73, 165, 95, 129, 7, 16, 40, 44, 123, 182, 100, 246, 148, 80, 99, 191, 137, 144, 177, 240, 82, 242, 163, 30, 210, 13, 45, 140, 6, 196, 122], "¥O\u{7f}Ⱥ𗢎e/\u{10697e}\u{202e}\u{7f}0�Ⱥ𤂹\u{e098b}\u{8383e}`G<<\u{b94d8}Q\u{e6032}\u{cf7bc}\0:\"?\u{1}\u{1a8a3},": null, "¥\u{202e}'\u{8d073}\u{78d6b}\u{6e610}\":E?\u{9bb82}Ѩ<�>\u{4c02a}//\u{a27e4}=\u{f456f}<3𧉟/\u{98}\u{f3520}": "x=`ᒼ\u{12af8}\"=", "Ò\u{51a90}�\u{1b}\"䩖𦄱\u{b3b7f}\u{aee50}<🕴{\\A\\\"X\u{f32ab}::\u{4}\u{c7c01}`\u{1b}\u{4}&\u{ff44f}": 0.0, "\u{86b18}Ⱥ<": null, "\u{a0feb}&¥\u{106aba}{B\u{be082}1\u{62718}\tS\u{aa928}\u{2}\u{3d1dc}\u{f5e89}": [16, 76, 209, 135, 206, 142, 16], "\u{afe7a}A`>\u{dafee}𡪩b\u{77678}%\r劗?\u{10819a}^.S\u{7f}\u{94083}$�\rA": 5, "\u{b2ccd}ë9('*\u{b}2\u{8}\u{c9f32}\u{5a462}\"?2\u{b}\0W�\u{3e6e8}th\\=\"F": null, "\u{e6ebb}r\u{202e}\u{80bd8}:🁚\t�\u{fd427}\u{b2fb5}\u{4badf}w/\"0\u{202e}<\u{15010}^v\u{53eb1}\0='": null, "\u{f31e7}41\r\u{68a9f}\u{202e}\t.p\u{4ea54}\u{860c8}\u{19987}l": -0.0, "\u{faf52}\u{6}¥\r\u{202e}䲖\u{202e}R&\r\u{973bd}\u{feff}l\\\"$\u{e5961}¥\u{d805a}\0+OB*tu\u{eb5a9}\u{109146}Ⱥ\u{759f5}`\0": null, "\u{fc398}\u{2}\\.\u{ee493}\u{d0de6};\u{ed8fa}🕴<": null, "\u{10ed00}?\u{7f}�`\u{b}L*\0®": -0.0}, "\u{7f}':e/Ѩ\u{36fde}`/\u{202e}\u{7f}=\u{feff}🕴\u{f28b6}\r": null, "¥": -0.0, "¥ :?\r\u{70234}": "�¥", "¥i🕴\0\u{93569}\u{71ace}\t:\u{75563}*+": false, "¥\u{be29b}\u{202e}\u{7ef68}5\u{1b}`\u{202e}\"\0\u{feff}\u{10e7da}�\u{7f123}|\u{8c8c5}n": 11, "±:𢽭🕴t�%r\u{8}?`^'r": false, "Ⱥ/\u{882e4}:\u{e0f71}$%/{\u{36b45}:,z\u{8}[1\u{7f})%\t\u{b}v\r\u{aaa37}\u{8}𫷝\u{7f}\u{46db0}¥\u{ad5a1}ke": -12, "🕴\u{feff}\\x\t\u{6}\t~\u{39f68}\u{202e};�\u{1b}6:/3\t\u{7f}": bafkrmiepyixjho2atqqcqgtd575iqsam6klfzdtkx5g4wummups24pdqcu, "🕴\u{5de4c}\\�𐝣\u{feff}𱴜\u{feff}": "\u{6}\r:\u{190a7}�%Ѩ𲇭:\u{6}_🕴Ò%\"'\u{e1b6d}F'\u{f7ad8}\u{57ad7}`\u{9c70b}$`\u{202e}\u{4b5bb}UK\u{90}", "𣉓��e\"$0R\u{a837d}\0<🕴?@,": true, "\u{2fa70}\\𪚅¯\u{108509}\u{4ab0e}\u{4ecc6}\u{bebaa}B\u{1}\\\u{f24f8}'%ð\u{202e}¥\u{3a476}/%Ⱥ\t$vyC]>+\u{87304}": [94, 245, 167, 7, 68, 250, 234, 108, 122, 64, 21, 238, 150, 215, 116, 48, 232, 184, 232, 43, 30, 213, 149, 183, 215, 1, 242, 95, 189, 228, 190, 9, 43, 222, 14, 254, 203, 238, 30, 51, 216, 40, 125, 87, 240, 71, 185, 206, 114, 87, 214, 85, 33, 163], "\u{373f7}": true, "\u{4f31f}*A?\\${\u{91276}\u{52eb1}#\u{b}\u{1b}": -47, "\u{5e975}🕴z${'\u{5d19a}b\u{aa904}'J}`Ѩ9": "\t=\u{1}±\u{84533}T*\u{4d0eb}\"A\u{8}<", "\u{69742}o\u{81}n<@\u{feff}L:\u{1041f7}🕴\u{8}𬾧\u{7f}\u{108007}?[\u{feff}\r": [31, 200, 160, 81, 110, 124, 249, 10, 85, 215, 192, 121, 17, 28, 185, 121], "\u{6cfa0}\u{6}𡽫{\u{feff}bJ&\u{5}`x\u{d2999}d&\u{badf8}\u{1b}\u{365dc}'�\u{5a37e}": null, "\u{a202e}~QE?\u{b}\u{7}/Ⱥ/\\&\u{fb894}\u{e0565}\u{2}�^?Ѩ'": [146, 41, 216, 116, 157, 252, 155, 192, 137, 113, 110, 231, 49, 142, 206, 2, 93, 201, 68, 48, 93, 58, 173, 197, 93, 40, 41, 112, 193, 244, 79, 228, 221, 71, 177, 129, 102, 97, 55, 137, 3, 148, 166, 101, 253, 166, 170, 139, 23, 120, 178, 140, 132, 104, 89, 46, 120, 30, 154, 194, 138, 39, 103, 152, 6, 136, 237, 241, 138, 193, 248, 33, 185, 56, 220, 118, 23, 17, 132, 220, 239, 90, 207, 237, 94, 75, 90, 222, 46, 180], "\u{b2ebe}*\u{84}.\u{a909e}7ѨjV𠷃Ⱥ?ü\u{6}u𝣒L\u{34303}:\u{bc294}?DZ\0\u{7f}/\u{103c3f}\t\\": bafkr4ihqz56mmzigglx6apxygvxcnreb7hjve6eftqfcooxtdzyzahyil4, "\u{c3588}*\"\u{8}\u{51cd1}躅\u{8f2d6}u🕴*\r\u{1}7🕴\u{71aa7}{\u{77c1d}%\u{cf015}\u{7a969}\u{8}\u{3cbf9}\u{92}\u{b}*\t\u{15db3}": "/{ªN?\u{8711e}=\0g{\u{202e}\u{202e}¥.\u{1b}H%\u{95}\0r%:", "\u{d7ffe}\u{44f52}\u{1b}\"%ỵ*m\u{5}$\r¥Ⱥ\u{1b}Uo':\r/%'<Ⱥ": -7, "\u{e0680}={\u{fd1e9}j<'CѨ�3\u{ce0a4}Ѩ\u{4b081}C\"`%9GGv\t\u{10322d};Ⱥ\\\u{e1a9c}\u{202e}\\": true, "\u{e5e60}\u{b}{\u{e3551}=\u{b5edf}<Ⱥ\"#?\u{7}\u{80}\\'5f=\":𪟤=\t\u{dc111}*<\u{346be}": true, "\u{ebf8a}Y\u{8}\u{f98c9}\\\u{7f}9🕴\u{4e9df}=\"t": [222, 226, 183, 11, 204, 93, 208, 118, 17, 243, 19, 124, 63, 137, 157, 195, 178, 3, 70, 223, 216, 164, 164, 66, 246, 151, 76, 100, 93, 11, 7, 234, 146, 38, 18, 169, 97, 221, 8, 133, 14, 197, 108, 213, 201, 214, 93, 202, 188, 165, 171, 86, 195, 223, 164, 6, 51, 234, 214, 26, 158, 32, 12, 35, 10, 245, 171], "\u{ffc11}#\u{b}¾": null, "\u{101fde}\u{b6522}'.&$": {"": 1.5658615101276272e160, "\u{2}=\u{5bd45}?o뗡p\u{2}\u{b}\\Ѩ¥\u{8b3a4}ib=": null, "\u{3}𤥺": null, "\u{6}¥\u{feff}%\u{202e}�Ѩ%\u{10df7a}$%\u{2}\u{b}\u{59972}𝝊\u{5107d}\u{202e}\0.k%V\u{55f7d}±": "`\u{7f}\u{4e0cf}\u{f7dab}\u{7f}zw[Ѩ.\u{b}Ⱥ\u{202e}\u{1b}#�\u{61e5a}\u{569c2}:\u{51a0f}\u{1b}*R\u{bb37d}:🕴{+Rf\u{ea9f}", "\u{7}Ѩ\u{1}\u{dca54}t": 17, "\t`": [25, 228, 9, 39, 85, 50, 121], "\t\u{2ef3e}\0": bafkrwibffxly7lxjvxuvp2ic3ao6f2icoflqqiadi5dvz4yvpdsyd27jne, "\r*\u{1b}\u{4}{`_\u{feff}\u{b}¥.K\t<\u{3}&\u{7}$🕴\u{7f}": -40, "\r\u{587e8}Ⱥ": [207, 53, 166, 39, 184, 202, 32, 200, 7, 155, 18, 5, 102, 226, 211, 93, 116, 183, 163, 131, 161, 249, 63, 112, 198, 231, 166, 86, 176, 31, 56, 43, 28, 142, 150, 56, 244, 64, 75, 60, 122, 214, 109, 138, 103, 217, 188, 127, 42, 7, 30, 47, 190, 51, 7, 110, 190, 106, 215, 102, 86, 94, 41, 76, 159, 221, 62, 236, 96, 83, 215, 26, 102, 233, 126, 117, 233, 43], "\"z=%`\r�ñ%:Ⱥ\u{ee132}.}Ue'\u{8}\u{a6c62}": bafyrwiefif6srseryrwhg7lx5ddm3cuzdwepr4rmvorftfhq2vj57d2gsq, "#\u{75c49}!": "\u{7f}\u{5a95d}L\u{32a81}\"{\u{7}¥²�\u{e3087}O'!\u{ab00c}\r./ \u{202e}h🕴'", "$\0\"\u{ca931}\u{2}\u{1}:\r\u{3}\u{63225}g¥Ñ": true, "$Ѩ\u{bd470}û54Ⱥ%¥\u{feff}": null, "$�0\u{a8b97}𱗶\\0\t$": 6.488444060884696e306, "&'\\w:<<%\u{feff}\u{5}\u{1b}`¥/?n𡚋&,4\u{5e1f3}\u{4b359}\u{fbe37}i\r\\\t:": true, "&?\u{6}r\u{c3665}\u{7f}i%": baguqefranz7i2sd2lryjkxvq7st4ujh3xjdttm6fldqgbci5rlbzwbet2dma, "&?%": bafykbzacea3napu4rwrzbqevwhiaptytsgo3gscof2glvjfje6h75nyc5npb6, "&\u{7f}\r}>`\"%\rY\")>\0\u{1b}X\u{b65b4}": [93, 89, 232, 156, 166, 193, 205, 122, 207, 235, 2, 186, 202, 177, 228, 245, 238, 222, 129, 191, 32, 176, 162, 238, 204, 162, 152, 39, 105, 236, 197, 76, 201, 44, 148, 170, 224, 150, 87, 94, 134, 189, 238, 51, 118, 124, 144, 5, 252, 27, 17, 111, 250, 236, 173, 159, 46, 45, 170, 32, 133, 60, 134, 50, 28, 47, 178, 197, 160, 126, 143, 31, 160, 25, 175], "'d'\u{4e27c}\0\u{b4b7c}`{?\u{3cff7}Â<\0": "h\u{feff}\u{15730}\u{c364f}]WV.TH\u{feff}.Z\0.\u{9d3ae}$\u{e5559}?\u{d403d}\u{7f}%(k𠢘", "'i🕴\tb{\u{7}\u{feff}&\u{49079}'S/\\\u{10600a}\u{af8aa}\u{3a6a6}\r": false, "'ѨH8'Ѩ{\u{f5f05}ѨQ\u{d5ba2}\u{3773e}::\u{eb168}:)CȺѨѨ": baguqehra3ml5lg6x5oaboqvr65pulvfntbvdrj6v5zyvuaf2j7bpx7wkhbpa, "*I¥w\u{b}\u{7b9a4}\u{3e747}\u{b2e81}/y<5'*S?$$O\"\u{7}\u{9f10a}\u{8}S\u{7a8bd}I\u{7f}U%": null, "*\u{202e}": [61, 242, 249, 132, 34, 222, 153], "*\u{861a1}//\u{10ca95}`'𓀒\\'=Q;\u{1073cb}M\u{5dbd9}": [34, 41, 251, 216, 124, 171, 190, 175, 12, 20, 65, 118, 149, 193, 33, 184, 96, 7, 181, 39, 92, 97, 36, 84, 72, 99, 69, 241, 55, 2, 31, 74, 55, 172, 146, 32, 230, 228, 234, 148, 164, 27, 55, 71, 222, 203, 70, 239, 15, 85, 157], ". \u{202e}v'*<\u{3c008}\u{ea8b}c¥?g:5$Y¥\u{5a3ec}": [60, 209, 45, 129, 120, 83, 199, 46, 198, 62, 131, 13, 210, 117, 41, 136, 9, 59, 142, 242, 198, 194, 181, 206, 19, 221, 114, 94, 216, 25, 58, 191, 30, 252, 131, 130, 133, 109, 116, 32, 231, 108, 160, 173, 195, 103, 78, 179, 165, 157, 227, 152, 245, 222, 164, 242, 208, 136, 66, 6, 54, 146], "/'%\u{3b4df}`𫨁e*\u{71a70}=\u{c5203}\rȺ궖JȺ/\u{4bb7c}\u{3}*#\u{d3bde}p\0qÙ\u{b}🕴\u{202e}\u{202e}r.": false, "/{H\u{10c9ea}\u{79be5}?\u{ca12d}": 8.6395723193706e-309, "4\u{54c08}'`?": -8.973748901720123e-29, "6\u{883eb}Ⱥ$\u{1b}": -1.0199828187670042e-159, ":<\u{1a04a}U�": -1.8179682492493788e-167, ":n\u{8a}\u{8}?𡜬\u{7f}F'ýr0\u{1edbd}%\r8oѨ\u{1b}": "", "<=🕴\u{dd6ce}\u{3}\u{65218}�\u{b3002}\u{81}<\\//z=\u{6106d}*Ѩ\u{8090a}hC\u{88ce1}\u{1b}\0🕴\u{ac262}\r/": null, "?*\u{a6066}aѨ\u{7f}\u{3}\u{1b}`'\u{aa717}\u{14b1c}o&\u{8d098}É?Sv\u{7f}¥`\0\0<]\u{15ce3}\u{1}=:$=": [66, 253, 182, 100, 225, 215, 132, 63, 183, 229, 8, 172, 23, 12, 49, 232, 47, 74, 175, 130, 83, 252, 88, 185, 110, 27, 188, 151, 251, 208, 54, 250, 230, 62, 210, 35, 23, 251, 21, 206, 161, 29, 185, 225, 190, 48, 125, 83, 49, 116, 98, 148, 46, 18, 204, 43, 23, 115, 8, 245, 113, 58, 152, 205, 222, 26, 169, 223, 244, 99, 43, 185, 3, 45, 50, 190, 66, 69, 188, 185, 150, 232, 128, 102, 195, 99, 78, 226, 251, 253, 84, 106, 110, 178, 192, 203], "?\u{7f}\u{b}\u{9f2c9}i\u{10bb8b}:{&8\r𠒫*t�\u{1}H\u{98f18}\" \u{c92b6}\u{8})\u{7d0c3}<Ï": false, "?\u{69bad}I&¥@🕴n\0??C\u{607cf}\u{7f}/&\u{b}🕴..qѨ\u{1b}": "𘉢`\0=\u{cc51f}\u{a4fb8}\0\u{47e89}/\u{b}\u{a0a22}±\\\u{feff}\u{feff}/.ck.\t\u{e3434}j", "A\u{e0fe6}'\u{202e}`%\u{202e}\0<\u{c18d9}/:\u{10b6bc}b\r*R:\u{feff}\u{3}\u{10ba3c}\u{84727}\u{c8ebd}^": -6, "C\u{c5e80}*g🖻\u{66e08}\u{9d567}{'Ѩ\u{58cb7}\u{9a1ca}¥Ⱥ\u{19181}\u{b1066}\u{5b8ee}\u{b9fcf}= =\u{dfbde}\u{18d8b}": "\u{5}\u{4d180}M\u{b}\\.Ⱥ*è=.e", "I*\u{96}\t=$\u{4747f}\u{5}\u{1b}n=a&rȺȺ\u{6cd13}": [230, 44], "R´\t\u{1}&\\\u{1b}kN\u{1}{´<\0\u{95a25}d": "k*`$U,\\\u{feff}\t\u{916a3}\u{849e2}<䨑\u{a2dd6}\u{9126c}", "Yq🕴\u{c5293}y:": 21, "Y\u{101e9f}JE{\t\u{7f}\0\u{4a162}aѨ.\u{7b388}댦\u{81d62}Ѩ\u{a6fb0}\\\u{dec90}\u{1}\r\u{b}/": null, "\\": -7.477551823968463e-129, "\\<\t\u{e9e4c}\u{b}:\u{a258b}¦\u{53415}5G\u{7}": true, "\\O\u{9e0a1}K.𠓎\u{fb34d}\u{7469e}\u{4c980}.:\u{bbb4f}8\"J:/\u{829d7}:\u{1}𣂇Ⱥ*J?\t\\\0\u{b}Ⱥ\u{ea85b}`": bafyrwihiw4vja2le5bbo63lbzgzdc7npl6b6ixw6nlheeirllwo5zpuzuy, "_]:\u{e11f7}¥On/<:\t=\u{1b}": "G\u{b}\u{71804}'🕴Ѩc\u{feff}<%Ѩo\u{1b}¥", "e\u{70c00}\0¥\u{202e}": [207, 96, 93, 148, 103, 198, 140, 36, 26, 221, 215, 6, 202, 231, 233, 247, 107, 178, 240, 144, 50, 206, 143, 13, 95, 15, 171, 239, 158, 75, 248, 192], "f\08<\u{9c37d}?\u{10f8f1}=`\u{95}Ⱥ{.\u{1b}\u{15cdd}\u{202e}Ⱥy<$\u{8}$\u{6c5d0}K\u{c4f04}\u{19f0a}": bafykbzacede6nne55uave2wj6epfrbud4xcozirijfysx2p3a4isvzez7oiza, "gÔ\u{b}繎.U": null, "i\u{889f5}r\u{e98e9}<Å\u{ad256}\u{b}R\r_%\u{d8e2e}/%r\u{b908c}%\rz~'\u{e04db}.\0\u{d9659}$\u{7cda4}`)": null, "u\u{a1658}0\u{f2355}}🕴\r": -7.035669443392254e-42, "v\u{10b659}�\r!o=\u{a24cd}]XÂf(\u{859d0}w\0.:": bafkrmihatl6qchcaitd2pcbxodwzlgbynl4rpzu6fnfcy3wgudnxpnabpm, "{%\u{61d33}=/EѨ/\"?�\u{5ca71}0\r": null, "{/:/JK\u{62214}Ⱥ¢\u{b}1o\u{5d859}\\\\\u{b65b6}*¥Ⱥ\u{1b}ѨѨ": bafk6bzacebf3fnemqzl52apgxmcok4gweclupgjrxwtee6ci75qsawcnyjuw6, "{:\u{f901d}\u{da30b}*:𱪙\rF\t\u{405de}\u{feff}%~\u{7f}*=\u{46e3e}\u{b}\u{202e}": true, "{f\\\u{b}\u{1}\u{7f}\tD\u{b}&Ⱥ𣡼\u{e84fe}J\u{d2ef7}E\u{1}'\0f\r\u{6}\u{4a79f}Dx\u{2}": -13, "{\u{d9a04}&ö%\u{7f}\u{d686e}&´": 7.588162654188097e-308, "\u{7f}\t\tj\tw\u{feff}Ѩ�練\u{3}:𧸨\u{a3acd}\u{101251}'\u{42a8f}E\u{3e47c}\u{7f}\u{7}&¥%<𩌯c": bafyobzaceco6clzsn57sjq2u5patyvukq3ksi4wyrztinj5srwspsi63qyfqc, "\u{7f}c\0\u{ffc41}<\r^Æ\u{9f}Ñ\u{b6f14}U\\\u{c45cd}\\\u{1b}g\u{ed9b9}🕴{\"R\u{60150}\u{b54c0}I:\u{10a33a}": [126, 247, 86, 174, 51, 26, 21, 148, 201, 8, 130, 49, 162, 197, 14, 242, 15, 142, 249, 226, 194, 131, 116, 158, 109, 70, 138, 10, 113, 85, 166, 21, 217, 49, 112], "\u{7f}\u{39cc5}'🕴\"\u{71cc2}&\u{7f}'": -4.297679451122769e-29, "Â\u{4}\u{feff}<'\u{c2f64}ѨȺ\\2\\\u{41534}*{<�\u{4a196}'8$\u{f88db}\u{feff}\u{feff}\u{9e1a6}*\u{cc465}\u{101f58}\u{dc98c}\u{b}ȺP\u{87281}", -29, bafkrmigi5ucblo6m6dbnw2y7po27ahssm6r6gwfog5w2eaxd7ze64u3vki, "(", -50, true, 4, null, null, false, bafyr4id3ardj34fsaufowtepdegrlkvrkv4jmyiva54gz4afht26prarau, [206, 72, 170, 13, 12, 162, 106, 248, 83, 67, 95, 57, 191, 163, 90, 243, 125, 164, 14, 6, 150, 168, 71, 206, 239, 41, 111, 27, 246, 79, 53, 193, 98, 168, 210, 107, 64, 164], -3.33502927060181e-309, -49, null, 3.5445664834722476e19, -1.563337693800014e-308, bafy2bzacedec3zioqwt4bdhiel5jmdrot2fhkwtx4u3fqug5x5djkkgop6rbm, -7, -18, [216, 49, 164, 168, 254, 77, 204, 85, 63, 36], baguqfyheaiqipcrhl2ltrwj5ykc3gnwp2i4i6m3wqqrmfi4ap7uchecatjrmp7y, -0.0, [64, 148, 93, 100, 165, 243, 24, 181, 67, 141, 104, 128, 194, 110, 216, 253, 234, 40, 83, 43, 194, 82, 152, 207, 225, 141, 174, 148, 236, 77, 110, 231, 110, 107, 120, 161, 188, 242, 70, 187, 10, 8], null, null, bafyrmigihlvyxai27ay3ws4ytyoickvlnpli3rxing2zlvcrwklqgvhp6m, null, 8.12819559637677e-221, 14, -2.3233856055803314e-132, true, [33, 203, 100, 207, 226, 57, 37, 141, 9, 44, 127, 89, 88, 208, 26, 54, 107, 192, 81, 78, 170, 47, 90, 250, 222, 53, 73, 136, 60, 141, 50, 179, 193, 1, 240, 61, 212, 102, 158, 81, 130, 103, 57, 0, 144, 186, 253, 79, 200, 167, 150, 245, 107, 237, 87, 24, 222, 200, 67, 180, 136, 164, 87, 137, 92, 199, 194, 10, 174, 65, 1, 168, 43, 25, 36, 186, 55, 153, 217, 27, 78, 197, 170, 47, 74, 87, 78, 123], "&t\u{51b31}\u{10b5b7}·Ѩ$\u{491b7} \u{e82db}S`\u{13ba2}$\u{feff}\u{3}🕴V¥\t{*\u{79d01}", true, 32, true, 9.168173353619497e259, null, 0, 41, "🕴\u{6ad7f}/V\u{8d90e}\u{7}\0$\\\u{1de07}ѨDw'\u{5f7ad}�$$🕴qa", [183, 153, 172, 129, 204, 68, 233, 142, 34, 132, 183, 94, 210, 53, 181, 118, 12, 36, 147, 178, 163, 147, 226, 164, 22, 84, 47, 52, 68, 11, 3, 202], true, -47, "'\u{7f}\u{9f}{\u{4bd1b}${{\u{9f533}\u{4bd4c}$\u{49671}`\u{5}🕴|�k\u{c46c0}\u{1}🕴*\u{feff}7\u{c7f8f}\ra:", 24, [124, 23, 6, 17, 136, 174, 8, 255, 36, 94, 92, 161, 178, 30, 64, 18, 140, 103, 208, 64], "\u{41adf}\u{b}\"\u{bb2f8}\u{7f}Ⱥl**", bafybmic7lxxicy772aqqwgxzod5qnr2q42gvznf7ybo3focmp5garzkofe, false, null, -1.453409372917667e275, -4, 37, -6, false, "H\0O\u{de3c0}I 욽;\u{e55dc}/qY*\u{9b}$\tHô^\u{c1add}.\rȺ:&_\0�\t\u{f05bb}\u{a5ba9}\u{fac7d}", null, true, "U\rȺ\rÖU\u{3e4ac}:\u{a58ed}/|\u{b}\u{74e95}$\\[x\u{b652f}'/", -48, -3.21819364036035e-56, 9.213513078060828e-91, "'\u{890b8}=\tx\u{d5bf7}?\u{a4dd2}\u{9811e}\u{a5c67}": 6.664526628123302e214, "&\u{54d6c}//\u{d211d}\u{7f}�": "¨\u{202e}\u{413db}T\u{1027f3}\u{b}O`#\\\u{a494a}\u{3b0c6}s.\t\u{103dc}:u/\u{a5e48}A\u{202e}\u{77c5c}🕴ȺmM'\u{6a3fe}K", "'\u{1}": "\t🕴\u{eab5e}\\\u{bab9b}\u{ee77b}\u{52d9f}\u{ab141}{\u{6}", "'\"9%?": null, "'#\u{6}\u{bdef5}¥\u{ca946}\u{7f}\u{202e}": 1.7292683522733168e-61, "'M\u{d3d34}\u{202e}$\u{3}\u{42800}𗸴\u{7f}\u{62be1}<\0¥<\u{202e}\u{feff}/Ñ4\u{f92f7}{´": 9.820150129594855e-167, ")C\u{ddeef}\"\u{c2e4f}": [219, 75, 110, 213, 246, 194, 60, 45, 146, 98, 88, 191, 123, 141, 55, 76, 57, 2, 29, 8, 227, 206, 96, 136, 115, 139, 137, 243, 222, 156, 251, 57, 28, 97, 118, 49, 218, 195, 184, 189, 188, 165, 205, 70, 190, 145, 58, 42, 104, 26, 29, 3, 218, 118, 2, 133, 228, 7, 235, 110, 91, 232, 215, 78, 99, 2, 64, 17, 72, 56, 70, 160, 255, 37, 168, 19, 29, 27], "*´\\yn<%\u{3cb12}\u{99}&\u{5}Ⱥ/\u{7}𭆼\u{4263d}\u{88dca}Q<\u{efab0}\u{f6ec2}": -1.1059496664576365e153, ".\"\\\u{3}{\r\r:�\u{81c10}\0Ⱥ\t>{\\:\u{e877b}\u{5e93f}": [65, 186, 167, 66, 51, 133, 164], ".e$Ⱥ6\u{63ca7}Ѩ`u\u{33bd2}'n\\\u{48590}\u{e646}Q\u{a08f7}.Uf": null, ".\u{9d849}\u{f94cd}=𖩡\u{bc869}¥+$5?\u{1016e0}`䎟\u{1043c3}\u{6c91d}\u{667af}🕴": false, "/\0\rѨ^\u{1a826}\u{202e}\u{7f}\u{fb3dc}\r_�\u{3}\r\u{7f}&ï\u{3}:8\u{9ed75}q𪥇\u{1b}\u{e8fa3}\u{4}𧽨\u{4efe1}\u{977d9}\u{3c6dd}\u{cfeed}X": [17, 67, 94, 98, 195, 0, 112, 199, 95, 233, 81, 197, 28, 69, 112, 207, 197, 72, 18, 50, 114, 108, 53, 31, 197, 3, 225, 75, 134, 216, 253, 203, 70, 71, 48, 33, 235, 32, 133, 245, 9, 224, 57, 91, 77, 233, 171, 9, 39, 186, 240, 49], "8:\u{c9e98}\"%1🕴<🕴\u{977c5}띦b4'": true, "9Ⱥ\u{84761}a\t\0": "î+%\u{202e}\"\u{c9bb9}*\u{2}\u{eb474}\u{4c05d}R{=\u{feff}\u{e7f1b}\u{1}\u{2}<Ⱥ-=", ":<'": bafy2bzacebdndpakl7grp6t2qzddcmrtpw4klywtvk77qimmd5fya7havzbyk, ";\u{8}�\u{1b}P'Ⱥ": null, "=$ȺN\u{e377a}\u{5a12b}`fW\u{7f}\u{2}:\".": -1.284865551557179e224, ">:O\u{3a925}ÿ\u{5}�{": -20, "A\u{7f}$'\u{b}¥\u{7}\"\0\\$=\u{b}\u{1b}�\u{feff}\u{b}\u{7e7a2}\u{b5184}\":\u{feff} D\0=\u{7f}耋Ⱥð`ࢁ": -41, "J\u{1008ed}\"\rLq\u{5}7": true, "[啡🕴mѨ®\"\u{100589}{\u{8}u\r'\u{b}\u{47a49}}\u{7}\u{1b}R\u{b4ad6}\u{b45f5}\\Ⱥ`'?": [234, 239, 156, 159], "].`'&\u{7f}^&.\u{202e}\0?%?¥\u{9929e}\u{7dd49}?a": 6.8070914168451965e-68, "_¦\u{ccda8}O\u{10bdee}fk$\u{feff}`픰>\r'�f➦\r|{\u{feff}laA=D\u{dc4db}\u{5f977}z": [142, 251, 14, 32, 228, 8, 15, 49, 217, 216, 50, 236, 66, 67, 25, 55, 193, 159, 240, 75, 84, 154, 198, 182, 176, 139, 245, 12, 189, 16, 217, 208, 9, 19, 164], "f\u{3f22c}\u{7f}\0.Y?'.*\rѨ)Q¥=�\u{6b46c}\u{42bbe}\u{ee66}": bafkr4idibino2zrwzvmr7jw2oqbramjdedkkhhexizf2kgnxongndjyvvm, "k\u{e0bf5}Ã\u{202e}<\u{202e}**\t\u{656ee}<&𘞄\0\u{b}*.b\u{1b}\u{b}\u{8}\u{c9220}&\u{36fa6}.?]G<ȺG": bafk2bzacec3uxd2irqv3kuoh6433iq7cdb2rkr4jes4ibxqlagobh7kcbz7b4, "pmOAB\u{b}\u{1094f0}\u{c3774}?\u{10918f}¥": true, "v\\\u{109f70}7`@\u{4}c'%!\u{7d32d}Ѩ\u{1f298}i\u{4d1e4}'�\"]�.\t𝦈": null, "v`\u{1b}\u{58c73}\0\u{101d7f}\0\u{d946b}\u{7f}\t\u{f2bf}\"\u{831aa}\u{60049}\u{51864}&?祕u\u{10403a}D]�\u{578ee}\u{ab4b8}:/ 𦁋R7Ѩ": -24, "{\"C썶\t\u{eec8b}<\u{ee555}\u{7574e}Ѩ\u{7f588}\rѨ*'\u{5eccf}%w{\r\r\u{b67d7}\0^kᗩ\u{1c3dd}G\u{8}`": 18, "{%🕴\0Ѩl%𤍭\u{feff}🕴\u{96}I\r=": "¥\u{93d84}y:w={", "¥~\u{566dc}9\u{ec076} \u{bd2c1};": false, "¥ȺD\u{1}\u{6b992}\u{434c4}\u{3}\t\u{1b}?\u{2}𥹩\u{9cb8c}\u{7f}𢲦N\u{e11b5}Q^{Ѩ\"w\u{505da}{": null, "Á-`^�\"\t\"`\"/<\u{1}=%�=\u{f8043}\t\u{3cb4c}\u{af74a}^\u{356ed}.:d?\u{65101}\t;\u{d0319}\u{b6b3a}$q\u{b}": [163, 128, 176, 15, 181, 109, 115, 80, 122, 244, 94, 32, 58, 251, 121, 38, 145, 131, 101, 44, 148, 129, 190, 123, 55, 19, 64, 213, 134, 70, 238, 108, 62, 157, 43, 176, 76, 240, 174, 14, 182, 98, 16, 190, 79, 42, 142, 212, 204, 192, 112, 130, 89, 121, 0], "�*??*e": [172, 83, 171, 22, 2], "�\u{9b09a}\u{15e59}\u{7}/\0E\u{6}\u{3cb2b}\u{4645e}\u{7f}\u{2}": true, "🕴": 41, "🕴\t\u{f0635}`Ⱥ\u{c865b}h\u{f39b}\u{fd001}\u{f197b}¥F/?\0\u{52376}%Ѩ/\t\u{b}\u{9a1af}\u{9b1fd}\u{feff}�\u{8f}/bt`k": [235, 24, 242, 136, 3, 90, 188, 21, 23, 10, 85, 3, 90, 163, 176, 209, 186, 96, 160, 240, 221, 232, 34, 228, 54, 35, 11, 200, 6, 93, 92, 140, 92, 36, 62, 178, 157, 50, 12, 66, 22, 208, 28, 217, 177, 9, 79, 248, 77, 198, 73, 78, 120, 28, 102, 200, 197, 67, 232, 133, 41, 38], "𥇖`": -3.1043921612514823e111, "\u{fdac9}\u{2}.𘠺🕴:'\r\t\u{3}嬊": bafk2bzacedwvgub3e2lthnyghripac4wx5ld6z3tavnb3iws5vdjukng5fwsk}, "$\u{890b7}&🕴=Ѩ\u{e0cc8}\u{7f}\u{8}\u{b}\\\u{ed02b}🕴°d\u{7}\u{202e}L\u{6d2ae}<\ta{*": {"\0&/\0&\u{8999c}.`%\u{feff}": baguqehraxkytankub52h3iajclairokepzb3skwifyzrg5qnfmbz4g4oodza, "\0🕴\r\u{2}`%O$%\u{de829}": bafkrmiht3e7seikbsbinibsxaqh4je5pzdcd4urxrwswdctilymrairlqy, "$\u{1e406}\u{1ea29}\u{7f}\u{c9cd8}𮖲X": [168, 224, 17, 123, 200, 96, 201, 98, 223, 37, 175, 231, 138, 28, 166, 190, 55, 123, 102, 20, 44, 208, 18, 184, 161, 233, 19, 136, 101, 164, 157, 248, 80, 214, 88, 79, 112, 146, 242], "=`C": -23, "\\8\u{d9453}($�[\u{539d4}\u{90f0d}\\u�\"$\u{bf277}\"\u{749e6}": 9, "wѨ&\u{f18dc}\u{8f209}\u{8}/\t\\": bafkr4ibmkkkbmtivel5pdx3jrmhp4wb5bd7rqzvoovp2j4b5hn2rtovype, "Ⱥ\u{b}\u{10433a}\\\u{1b}{`!\u{cd71b}¥<`ȺDȺ\u{d310e}\u{fee4b}𭨠0\u{b6c98}": true, "Ѩu鉏<}\u{8}Q\u{5ef3a}:Cm&\\\u{3}Q':@\u{f9a57}'\u{3f836}c<🕴P": false, "\u{202e}\u{6899a}<\u{b}/Ѩ&\u{2edba}\u{103edb}\u{9e328}`\u{4979f};\u{a6f16}!`\u{feff}w`": -32, "�Ѩ\u{8}�\"\u{fc9b3}\t": null}, "L_Ⱥ=,\u{4}\u{71158}'�\u{d01e8}/$\u{202e}\u{3b573}:`{w'": [-48, [93, 196, 141, 154, 134, 176, 139, 189, 180, 134, 124, 223, 175, 198, 167, 42, 110, 24, 222, 67, 196, 65, 202, 107, 120, 13, 17, 105, 196, 172, 101, 89, 218, 26, 140, 88, 96, 162, 89, 106, 160, 211, 109, 26, 157, 200, 132, 194, 38, 44, 145, 80, 249, 47, 202, 234, 243, 149, 231, 133, 231, 1, 121, 28, 157, 175, 187, 89, 100, 158, 213, 243, 162, 111, 87, 174], bafk2bzacecsuznauruwgtuwqc6nidbjw7qp5usnb5vr3w7lxuvebwffbqe3t4, -1.8324536891683285e-30, bafk2bzaceadjwnpm762btxzz5nfl24lfog3wm5ldrl7q7namae7ysv5647h76, -6.370155741536763e-309, null, null, 23, false, true, -41, 48, "`\t\u{cd15e}<{\u{ad7da}%W*腁=\t¯\u{7}-?,\\", 5, [238, 157, 140, 174, 18, 122, 121, 22, 84, 53, 144, 119, 53, 216, 130, 89, 6, 121, 229, 24, 153, 6, 11, 186, 92, 6, 52, 44, 74, 132, 139, 202, 28, 213, 100, 205, 127, 222, 130, 104, 210, 158, 235, 223, 136, 77, 58, 114, 135, 100, 151, 103, 91, 82, 150, 91, 149, 124, 36, 26, 78, 105, 207, 54, 253, 9, 201], [195, 193], -2.0736431262565832e-7, null, -7.471855593429185e234, [212, 22, 11, 89, 86, 227, 1, 226, 174, 110, 146, 235, 217, 246, 70, 133, 107, 165, 157, 151, 202, 116, 160, 243, 169, 65, 185, 193, 131, 184, 185, 157, 65, 108, 39]], "y#\u{49798}\u{202e}G<%r\u{1b}\u{c2763}\u{1b}\u{d7645}$M🕴\u{b}:": baguqegza3fmjvuxyw67cjennqkgi6ipcsxboanp4xjz4prjmp7h4r5muxaja, "¥\u{6}xS%\u{a21c0}Ѩ\0\u{b}\u{e52b5}\u{8cd0b}:\u{feff}\tS¨\u{dc97a}%$\u{8a51a}\u{8945b}\u{3c40a}🕴\u{feff}\u{feff}": "\u{b}\u{fbd6c}*", "🕴$�\u{1c50d}\t\u{2}": {"": 14, "\0�&\u{feff}\u{685bb}YVE9c\"|\"<[�K𤅰\u{cd669}}\"": bafy6bzaceak3lys2lo5m7woe2mfjyxxl3ore3aixosi5uajethhkjyze72xpm, "\u{1}\u{d0b74}\u{b6c77}y?[\u{202e}¥~{T\u{1b}\u{feff}\u{f8717}\u{a0f04}:\u{10f8b6}Ⱥ🕴\"Q\u{df178}\"\u{fe2ee}u": -1.5028963084100644e18, "\u{4}`\u{100473}\u{f434d}=�*/🦲Y\u{a0}W\u{a0e46}\u{5}": false, "\u{5}4\u{33c5d}\u{4}": 6, "\u{5}ZѨh\r&": true, "\t(/=:<\u{7}🕴.'Z": bafyb4ig3qyn3653hzem53fsfvx27lsgdkfmln4vgzjnu7bhb22rr6h5nvq, "\u{b}\u{5}\"\r𐴅\u{a3132}z$8\u{74e26}\0F\0\"*�J\u{7d9f5}\u{ad7c2}\u{f3faf}?\u{f35ed}": [158, 45, 189, 198, 136, 12, 91, 193, 206, 13, 142, 218, 17, 126, 95, 11, 139, 29, 191, 233, 236, 221, 187, 109, 193, 176, 213, 129, 96, 120, 253, 24, 3, 65, 228, 116, 138, 102, 127, 19, 54, 235, 37, 201, 238, 25, 41, 26, 248, 239, 191], "\u{b}Á\u{4}\0$\u{baa23}N\u{105c4d}\u{feff}g": 6.651792988712289e-76, "\r&\u{7f}𬛩\r*m\\𭘺\t": [7, 86, 209, 90, 4, 82, 142, 122, 123, 191, 41, 84, 37, 155, 147, 150], "\u{1b}=\u{ae8cb}🕴🕴YѨ": [15, 58, 171, 250, 163, 49, 118, 146, 209, 32, 161, 202, 1, 216, 170, 61, 6, 250, 37, 28, 65, 69, 84, 230, 70, 124, 99, 121, 212, 211, 35, 15, 202, 210, 87, 237, 13, 14, 12, 203], " n𦜹": false, "\"I¥": null, "'\u{202e}\u{1bfda}¥\r\u{e18c}{\u{4a199}Ⱥ\u{98ba2}{\u{202e}🕴f\\": false, "'🕴": [0, 125, 38, 17, 120, 195, 201, 150, 64, 36, 204, 245, 82, 31, 21, 85, 169, 69, 249, 10, 19, 33], ")�\r\"\r¥\u{202e}\u{cbc20}\u{5a381}\u{202e}𱙤{'": false, "*\rȺ\u{9af1d}g�.*🕴\u{1b}\u{feff}\u{6ecf1}`\rY{\u{88}\\*.\u{f8037}($\\¥.\r&ÑD`": null, "*'\u{fe9ce}\t𪡏h'Y\\{\0IWë\rU?$\u{7}IQi!%": bafkrwif2qr2egu3en3ttpfc277nx7wbogbs3exytgbmipe2ev4itu4abxi, "*{U\u{7f}3\u{feff}🕴%*``/ꁏw\u{88393}\u{1b}>": 16, "-&\u{588d4}J": -136947584516.8358, "/": bafybmigzjzmiin3bgn6qms6vavgzn6iyqjuo4onptcetzu657kebbwt5hu, "2\r\u{8c375}i\u{99}&🕴0J\t$Á\u{66ed4}/": "¾\t\t🕴$A\u{136fd}¥=l\u{8937a}\"\u{db049}`Ⱥ>@\u{b}\t\u{feff}%.\t\u{8ec09}\u{c5f5f}/$", "<\u{1b}/\\?z9\u{dbde9}\t?𦤐\u{1b}&H\u{1b}\u{125ec}\u{cf4c4}\t\u{b}\0.\t§\u{4a3f5}\\�": bafkr4id2n26zagqdmec7pq53l34j6st3x3xzqualxr3kl57litjvhiqjku, "=]\u{1b}\u{f411d}\t\"`\u{a4a2c}\u{6}": [238, 35, 78, 136, 226, 234, 214, 233, 77, 30, 183, 34, 221, 130, 60, 71, 179, 70, 33, 252, 49, 64, 114, 217, 11, 226, 152, 252, 143, 174, 237, 102, 78, 100, 138, 217, 6, 74, 250, 0, 234, 244, 152, 245, 69, 148, 151, 8, 51, 18, 105, 167, 200, 213, 240, 64, 138, 104], "=\u{8bf06}?x\u{1}𡊽@\u{6}.<%": "L\u{79877}\u{eccd7}*", ">=\u{feff}\u{8}à\u{1b}vL0\t\u{7f}I\u{feff}\u{7aba3}b$$Sv~i\t錦🕴&\u{709ee}.": null, ">`:𦎼\u{7f}º\u{be352}n$\u{eafbd}\u{cddd8}jw\u{1b}\u{feff}YT": true, "I韸\u{53960}\u{feff}?\u{7f}.\u{cf92c}Q\u{4}.\u{b}\u{8}🕴Ѩ%": -26, "M\u{2}𬗃\u{9082e}\u{e23ca}\u{4}\t\u{feff}\u{5bde3}*\u{e514b}x,\u{48624}\u{1b}\u{b}+\u{f8546}\u{8a2a6}\u{d4710}'🕴f;\u{46c31}\u{1004de}y{b:": true, "M&\u{e303}?&�\u{7f}j;\"\u{202e}픢\u{b}🕴": null, "W*/X\u{7f}^\u{7dd38}Þ7%.*3\t\u{ce607}:\u{d3d17}c\t\r\"\u{84fa3}\u{202e}=\u{92}": null, "Y*\u{c2e6a}?<~\u{feff}P'¥e¥\u{43a8e}\u{fa00a}`'": baguqehra4nft63u5d2aawrcgvhart2zja5ogq6c624azqqxgbgtoxekvlnpa, "Y\u{202e}'?": "\u{3}\u{cc1ea}\u{c7649}\u{a973e}\u{8}0\r^u\u{8eff2}l\r<]\u{5e264}&\u{f3e11}\t\r\06S", "\\\u{5}.\u{7}\u{b69af}*\u{19abc}]¥\u{202e}{🕴'\u{e46c7}`.xs\u{b23ec}\u{10beed}": -21, "\\*\r¢峭ìE\u{d96e1}\u{f8ff9}^\u{bc6ac}%:\u{50965}$6\u{8119f}¥\u{f67d0}y~\u{6c741}\u{3beb4}🕴": [126, 126, 7, 32, 17, 6, 234, 156, 163, 166, 193, 46, 81, 206, 32, 125, 166, 57, 195, 140, 39, 47, 171, 70, 117, 175], "\\\\/&Ѩ\\<": [92, 59, 122, 162, 91, 48, 33, 29, 29, 249, 254, 64, 21, 67, 100, 54, 206, 184, 116, 163, 175, 47, 42, 243, 205, 18, 136, 16, 249, 116, 141, 232, 122, 115, 123, 202, 72, 56, 116, 229, 177, 60, 49, 83, 199, 52, 23, 85, 114, 147, 155, 50, 31], "`¥?쉲G": "\t\u{4095b}\u{6c6a4}𮭀\u{2}\\\u{da8f7}8\u{90a53}.痗\\\u{202e}\u{56f97}{E]\u{492cd}&f\u{7f}¥D\\]Ⱥ?🕴", "`嬥\u{db443}.\u{ce5}\u{12eae}\u{108af8}\u{1b}\u{7f}\r\u{60df2}¥%&\u{5b0fe}-.&\u{3}\u{e2c1d}DR&:�\u{10c17b}Èc\u{8}U%": [49, 169, 171, 64, 201, 141, 228, 46, 7, 206, 13, 162, 170, 144, 168, 169, 198, 15, 58, 71, 112, 228, 149, 218, 144, 84, 70, 253, 39, 168, 223, 3, 232, 33, 162, 234, 136, 215], "`�`\u{7f}{\u{5c37e}\u{2}\u{202e}?": "/\"\u{3}𪜥N\u{57f77}h$\u{d1d92}𐊘�\u{7f}*¥<.\u{9e397}<\u{1}g\u{d0ad6}/\u{11ede}\u{7f}\u{ae007}\0$\u{3afa0}🕴", "c��W|`�*@.aѨN\u{feff}\u{f92a0}t$\u{f53cc}1\u{c8fbe}\r\u{19667}\t\r\r-\u{7c67c}": 2, "o\u{1}\u{1}\u{1}=\u{e8927}I\u{f584}&\u{a0}:9\0a𠿭/": true, "q$Ѩ\08{\"鉱[?ÀzY\u{9b8c9}%\u{3}{%\u{5}\0.\\\u{6c540}\u{7f}s\u{c05e8}`\u{47f61}=)🕴": -12, "s*¥\u{9a}\u{feff}": -1.6309188456998563e185, "v=<¥'<\\`\u{cc9eb}\u{798aa}\u{64542}:®3\u{202e}\tD&𰓏%�\u{7f}LF𒉠\t�\u{ee420}/'D": true, "v陈'\u{d832c}R??\u{7f}/\u{6}?\u{7d4d1}\u{dca1e}~🕴\u{aca77}:ç\t\rѨt?\u{10f5f3}\u{9db4d}": [35, 3, 217, 123, 77, 104, 154, 141, 118, 202, 130, 101, 62, 0, 182, 199, 130, 209, 149, 102, 212, 81, 90, 149, 217, 96, 127, 42, 251, 151, 46, 49, 33, 84, 28, 55, 109, 95, 208, 174, 193, 163, 28, 103, 91, 127, 159, 204, 177, 182, 31, 112, 61, 243, 10, 202, 83, 234, 176, 174, 61, 22], "v\u{43b04}6\u{f16c9}\u{2}\u{ad277}{Ã={/\u{202e}": [130, 234, 7, 201, 230, 244, 129, 23, 205, 123, 90, 239, 235, 197, 10, 88, 224, 76], "y/\u{1b}Ѩ\u{1b}": "?;*\u{156a5}🕴\u{1ad1f}:-\u{a1bea}r&\u{eb06}\u{a5275},:R\u{7f}&<\"®\"w\u{202e}:Ó.:\u{202e}\t.", "{B\u{7f}𲌜\u{9a6bf}.\0\u{4a3ca}🕴\u{7}\u{35765}/": -29, "{`=\t;\u{b}*EX\u{1b}鋪\u{61ce8}\u{b742a}/s🕴\u{5}\u{db6e7}'\u{10aa16}q?\u{8}\u{8d}\u{518fe}": "?\u{8d694}🕴", "{b\u{fe666}䀾\u{feff}]y\u{1b}[$\u{202e}\u{202e}🕴㞔\u{c132a}": true, "\u{7f}\"P=\u{555f9}\r\u{f034e}j\u{67c81}\t)Ø\t\u{a2116}\u{bcb49}<\u{108172}\u{489d6}3": [11, 195, 207, 131, 8, 71, 197, 12, 152, 172, 119, 96, 131, 191, 170, 2, 143, 112, 247, 189, 191, 156, 180, 76, 57, 86, 59, 6, 237, 106, 34, 175, 98, 78, 217, 192, 190, 63, 247, 202, 245, 190, 185, 58, 157, 147, 177, 129, 101, 197, 232, 22, 220, 131, 53, 79, 127, 137, 106, 0, 139, 8, 238, 179, 248, 40, 242, 178, 191, 193, 47, 56, 113], "\u{7f}_%\u{e1aa4}\u{c1f6f}/$s:\u{1010bd}*j\u{b}nêeȺ$\u{7f}\u{202e}\u{feff}\u{1b}\u{feff}%\u{9f08d}w\u{e8557}<$<\u{3e78b}": "Ⱥ\u{feff}$", "\u{7f}\u{9a}\0�\u{9c}'$=\u{4}&\u{94281}\t?\"Ѩ\u{654e1}": false, "\u{99}.\u{9d66b}z\u{db26c}\0\u{109c92}.W\u{ca505}\\¥\u{e0cf8}\0`4x\u{feff}×ꡔL\u{b16bc}\u{c1e8f}TYu\u{e7dae}\u{49a55}: ": 43, "\u{9e}%MWȺ`$\u{202e}:z?🕴\u{6}\u{a0}㶬{\u{5}<\u{6}*": bafk6bzaceaont65a5awr6tb55msdtvepr4xwa62mgsp4gyyfmbd62ey4ts4z6, "¥`?=%🕴`L": true, "¥\u{7aff2}Òb[?\u{8}\u{923a6}»\\»": false, "¦\u{6a307}": null, "Ѩ🕴T¥$*?\u{6}𪵅\u{98110}": -6.640335043931752e20, "\u{1bd53}Ѩd%@": null, "🕴3\u{feff}&\u{dd024}Ѩ=\u{c62d5}p\u{7f}�'AѨ|`g\u{635c5}h": 22, "𣅝\"\u{8b95b}\u{fbfeb}\u{cd3f3}A'\u{167f7}𘑦&🕴\u{94f3b}H⼣\u{95077}": "", "𮎾ýK\u{feff}qȺ\"\u{64e75}\u{70dd2}¥\u{41885}l": bafy6bzacedp25hwogiwuqmaa52iwzgx42gznm36zcpjwo2togshwzpsae6p26, "𲊳": [226, 239, 217, 38, 87, 223, 233, 194, 149, 219, 168, 60, 210, 19, 135, 178, 111, 92, 80, 108, 128, 121, 241, 63, 190, 148, 170, 24], "\u{3393b}\0¥\u{3b6e9}F\u{93d46}X=\u{d5dd8}:\u{feff}%'\t\u{95303}'*\u{aca4a}", "\0\\\"s\u{feff}M:b\u{8}H/\u{6cafc}\u{f1224}z\u{8}8P🕴\u{a2f8a}\u{8}\\": true, "\u{1}=\u{3134e}$\0v{K\"({Ѩ/�𪓆 \u{b3be1}�\"{$¥//🕴\0\u{604de}\u{a3afa}$\u{3}q": [43, 187, 50, 198], "\u{2}'Ⱥ\t\t\u{10b73d}\"\u{8}Ѩ:": [177, 214, 28, 238, 196, 162, 205, 149, 100, 179, 184, 109, 52, 32, 41, 6, 221, 134, 161, 95, 164, 2, 124, 18, 111, 203, 147, 153, 196, 122, 109, 242, 133, 28, 125, 73, 247, 14, 254, 57, 169, 190, 203], "\u{4}x1\u{b}\u{d46f0}k¥�'%\u{1007c4}": -41, "\u{6}/": 34, "\u{1b}2": -25, "\u{1b}\u{202e}\u{c9725}\u{80c81}?/\u{5}\u{e1f43}m\u{f878e}\u{feff}?M": bafyobzacecdottam3btcdm3lil44fdpksyl4ry22wyyufv5d3h6yuc2xic62a, "\u{1b}\u{4d55c}?|L¥ù\0\u{b}¥?[\u{f871d}k{.\u{9b}ć🕴$a\u{2}\u{db167}`\"": baguqeerag4wvi75skxsukegz4at6o2jjuwnsmujpjfkds3vdu4ybvdnqszfa, "\"[ .[": baguqefrayopq7znw46ljb72uinasfv3w2iuuvycues24ag6px6cwn7uhxgzq, "\"\u{eb96e}\u{3}7𩼱/\u{7f}?<\r�9\u{1b}'\u{b}<\u{56215}\u{612fc}*:'�EȺ/Ý\u{41397}-": -0.0, "$\u{3551b}$🕴\u{4051d}渴": -14, "%=�\t\u{cadf7}\"\0\"=z\u{f357}\u{a23f8}\":\u{f77ac}🕴\tJ\t$": [15, 242, 91, 76, 243, 187, 224, 9, 73, 10, 254, 67, 51, 52, 17, 243, 78, 108, 39, 226, 242, 59, 201, 164, 49, 64, 177, 101, 28, 139, 30, 14, 186, 53, 73, 148, 250, 157, 77, 84, 1, 36, 211, 17, 227, 29, 58, 144, 4, 192, 22, 128], "%\u{671d5}\u{5} =\r?'b.\u{1b}\u{7cbe2}𱛣\\\u{1}¹Sb\r/%\u{108071}ȺE$x\u{1b}": true, "&'\u{1398c}🕴\u{91} ": [35, 228, 163, 135, 36, 120, 223, 108, 37, 185, 72, 109, 236, 64, 172, 51, 97, 158, 76, 34, 242, 182, 40, 28, 179, 167, 49, 193, 42, 24, 222, 201, 26, 63, 190, 143, 222, 12, 8, 41, 47, 5, 168, 168, 222, 18, 54, 55, 98, 164, 82, 143, 124, 88, 254, 159, 177, 101, 205, 232, 17, 198, 34, 34, 105, 60, 25, 16, 173, 94, 188, 33, 68, 226, 245, 233, 154, 122, 243, 23, 198, 255, 31, 226, 251], "*]\u{8bb40}&.-Io\u{2ee60}%\u{b}\u{b4204}\u{b}\u{6fa9e}\u{1b}\t[\\\\uS=¥\u{7f}": -0.0, "*\u{1f84e}`𡣶\u{b}\"\u{df68f}A\u{33626}\u{202e}|\0\"\u{862b5}\u{7df5d}\u{1b}<\u{87a2b}\u{8dd82}~\tP\u{202e}Ѩ": null, "*\u{38df8}p\u{d2c21}\"\\\u{202e}\u{82032}㿶1&ѨU`H8�\"&": [38, 69, 84, 226, 186, 180, 254, 85, 31, 178, 18, 43, 46, 2, 151, 54, 154, 241, 244, 226, 93, 189, 139, 83, 133, 109], ".%\u{fd55f}?,/.`\r\u{dadcb}%\0\u{f3420}\"%#\\Ⱥ\u{95792}R¥}\\\r𡂂\u{69eab}\u{feff}\u{7f}{": [117, 59, 134, 197, 33, 33, 196, 45, 250, 203, 13, 97, 151, 48, 4, 203, 100, 245, 18, 88, 41, 62, 165, 72, 69, 218, 126, 161, 161, 184, 57], "/\u{5}\"I28qȺ|«㖃$c`\u{2}*?\u{ee2e9}\"\u{51724}": false, "/.\r¥": -1.2787723284947307e-308, "/J\u{10519c}%%/$.%\u{65527}\rW|Ⱥ{\\¥\u{8201f}\u{7f}\u{b}&%c=Ѩ\u{9bf3f}": 2.35302505714783e265, "/\u{52490}=|:%w\rl\u{90}": null, "5Q%\u{561e8}^r.\u{b8932}\u{92f2d}4\u{7f}+⩾\u{7f}/.¥\\\u{1}\rS1\0%": bafkreigrelqjps75gjoqf4ijk5muyqd3eo7nkw5cm5phlkiaaoy4yuiy6q, ":`\u{7112e}": baguqehraddx54dt5qiorol2h2cg5mnr7pjvahh3d43il2ernaikugjuvyymq, "?\u{75c14}\t龒Ⱥ\u{e5509}\u{534e4}<>\u{1b}\u{d3cef}\u{7f}\u{417fd}\u{7f}\u{6c856}%'\u{202e}>\u{43519}U[:\0\u{b}&\u{df77e}/&": -4.024585944763422e193, "K*\t\t\u{e140}Ø\r o\u{c3cad}2/{Ⱥ\u{7f}s\u{7f}¥\u{b71fe}&¥\u{e34cb}Ⱥ\u{135f0}\u{b}$\t2$$": -10, "OB\u{4}\u{7f}\rP5\u{80012}\u{16348}\\z\rÍ\u{1b}\u{3b61b}?": bafkreiaimlx7a6sb2ezufrqz5kqv3p2y4fadnzakimbrhq3ajzjg7ue75m, "P\u{93db9} &zG\u{7}?🕴\u{7f}'\u{10ec52}\u{feff}\t:\0\u{63df4}m\t\\": false, "U\u{2}\u{9ff8c}¥?\0\u{57a21}\u{9bb0e}ef\u{2}Ⱥp\u{f73a7}4\u{b}$\u{7f}\u{1b}9\u{7f}{\u{9e15c}\u{d804b}\u{705a1}\u{e750c}Ⱥ\u{7f}¾\u{7c515}": [182, 236, 56, 93, 36, 111, 103, 217, 53, 16, 250, 85, 194, 3, 183, 131, 156, 114, 43, 35, 38, 181, 143, 123, 141, 23, 109, 247, 10, 205, 121, 24, 25, 35, 178, 125, 3, 138, 141, 76, 31, 49, 144, 42, 23, 111, 9, 180, 199, 19, 28, 28, 36, 135, 72, 100, 199, 115], "Xa\u{b1ab2}$(\u{9e}\u{3812e}\u{fe6c}\u{6acee}\t8$=\u{7aa19}He\u{202e}|2*\u{6813f}Ⱥ:$%\u{70ca6}`\u{e4b58}o\u{a0}?": null, "Z\u{87bae}\0\u{f48d0}Æng": 49, "\\": [161, 24, 206, 12, 94, 242, 137, 40, 92, 187, 103, 166, 57, 108, 16, 46, 30, 140, 111, 96, 213, 196, 3], "\\\u{e7806}\u{7}%\r\u{7f}:<\u{95ec9}\u{10b262}\ry\u{d494a}g\u{5c182}'&.\u{2}\u{750dc}]Kq\t": -1.3364526424136176e63, "^\u{50332}\u{3}\u{7f}'$$Ѩ\u{7f}\"D?": -33, "`\"*\u{6989b}\u{a0552}Ⱥ&d\u{4}Ⱥ🕴Ѩ\u{f863f}Q\u{202e}\\=\u{202e}*}\u{6e906}\u{994d4}\t\u{10cc54}\u{3}_坢Ⱥ\u{ef59f}F\u{e82f1}": "'*𰀏Ѩ\\%\u{4}.*\u{202e}\u{96457}%|\u{100bbc}�\u{5046b}H\u{d9ba9}HL\u{5}*\r\u{8a1e4}\u{b}\":<\u{7f}\u{9f}Ѩ\u{7f}s{�\u{a46b4}\toh\u{7f}*", false, 46, 14, "�\u{102e58}'\u{f5c0e}\t\"E\\cM<'l", false, [135, 235, 189, 222, 190, 18, 166, 127, 220, 237, 86, 153, 31, 245, 85, 90, 198, 38, 141, 133, 190, 152, 45, 202, 134, 204, 48, 11, 139, 218, 166, 185, 151, 185, 14, 83, 198, 240, 223, 59, 82, 165, 180, 107, 145, 62, 145, 69, 120, 158, 111, 248, 129, 252, 131, 205, 198, 215, 133, 171, 133, 47, 12, 202, 128, 214, 166, 137, 1, 192, 251, 117, 7, 234, 236, 17, 204, 31, 74, 10, 168, 79, 30, 0, 231, 29, 135, 146, 72], "RvѨs$\u{75d03}Ѩ(<\t\t𤡧s`\0\u{1b}.\\\\$\u{1616b}\u{7f}\u{3cf54}", false, [235, 223, 146, 172, 43, 51, 24, 21, 97, 241, 49, 100, 109, 54, 82, 186, 134, 207, 194, 77, 228, 210, 37, 8, 203, 94, 183, 5, 197, 144, 138, 15, 165, 27, 102, 4, 88, 13, 236, 223, 176, 178, 233, 90, 97, 161, 185, 38, 223, 154, 255, 212, 108, 179, 214, 107, 224, 221, 198, 190, 85, 135, 36, 226, 102, 69, 206, 155, 37, 70, 205, 167, 176, 184, 47, 239, 155], -1.1621607219028973e-308, "\u{59b57}/x\"\u{7f}\u{7fb3b}\u{3}&2&\u{b}\u{2}*🕴l\u{9d87e}%\u{1b}\u{b}RA", -24, -18, null, [95, 22, 213, 216, 209, 22, 236, 29, 48, 196, 106, 1, 159, 4, 224, 154, 54, 81], true, bafyb4ieb5rs6wukn4nc3vkm5ej4qrmgt7ycs7lmkc65m3wcxmpgo2xms4u, bafkr4ieb5eefx4xbgebjbqtjvimlfqjwrqcrg7vlzoiqrnntc2avr3g5g4, bafyb4ibwxgkbwxtmkhdb5bwhyi7caxvo3ke6mk42emzae5iini52xuhlpy, -37, false, false, -28, "\u{8}1\u{79fd2}\0\u{ca356}\u{59494}O𫔓{\u{7f}Q\u{3269c}]\u{202e}\u{2}\u{97bce}\t\u{578dd}cP\0\r", null], [158, 31, 209, 14, 252, 175, 225, 4, 36, 138, 244, 197, 42, 56, 92], null, false, [52, 230, 174, 76, 132, 211, 203, 241, 115, 11, 214, 245, 111, 133, 43, 90, 154, 170, 151, 64, 144, 238, 229, 61, 189, 206, 192, 76, 209, 181, 128, 129, 149, 185, 71, 119, 213, 172, 36, 212, 47, 194, 112, 62, 251, 65, 101, 24, 90, 161, 131, 1, 164, 160, 99, 211, 197, 197, 201, 100, 159, 72, 192, 14, 231, 60, 84, 118, 134, 195, 67, 16, 246, 204, 36, 246, 158, 191, 131, 237, 231, 155, 25, 200, 124, 214], -1.6606481206991028e-178, bafybwif7fjc3n3ymfa23uyjq64sksrh45thlzmm76bb5bohapwgol2thvq, null, -1.346334315879978e109, "衴", [119, 113, 165, 145, 116, 9, 189, 140, 253, 252, 3, 148, 133, 255, 48, 161, 243], "\u{8}u/G\u{a6e8d}?\u{60a32}=ó\r\\?🕴\u{7}%a/\u{1b}¥'\u{7f}:\u{60bc8}\0i`\u{1037a2}A\u{c10fd}\u{b}>\u{202e}", "", {"\0\0¥": true, "\u{3}=Ѩ\u{5ce31}:/\u{97}\r\u{7f}/\\-\u{e2599}?\0\u{202e}`w0\u{7f}\u{b}\u{10ed54}5\u{84}%\"": "$\tE&\u{43d48}S\u{fb913}ÿzVt\u{e7bed}L&{²\u{6}\u{7f}", "\t\u{5}\u{a8a7f}\u{89589}sJ<'𫮹碗\"Ѩ1i/𦏲x🕴\u{a3b5e}\u{ee69}?": null, "\u{b}'\u{83bfd}\t/*㷰\t\u{97282}\u{5}I{": [223, 168, 69, 95, 72, 43, 203, 89, 110, 174, 115, 77, 84, 89, 26, 159, 57, 147, 74, 158, 40, 74, 218, 178, 183, 135, 65, 67, 200, 1, 1, 140, 39, 233, 54, 33, 28, 99, 87, 116, 176, 91, 93, 98, 3, 13, 238, 112, 18, 248, 83, 152, 221, 158, 134, 229, 80, 111, 168, 248, 209, 124, 19, 203, 55, 80, 89, 127, 31, 233, 114, 8, 236, 122, 44, 128], "\u{b}G;%&YP🕴t\u{4}E�.{\r\\/�K|\"\r": bafyrmifvkoneefkqfiseviqruma2fucp5bha2di7eg7jlyk3zrf46fw4na, "\u{b}d'S\u{4dc81}%\t)🕴\u{202e}.🂉\u{49dd2}𗹆\u{461bb}\\*c": true, "\r:*$㝩\u{98464}\u{6}𰂜.\u{1b}\u{b}=0\u{8d206}¥\u{95e7d}.\0\r:¥$\u{fd075}w{\u{202e}\u{7f}À{/\u{aee3d}": -54, "\u{1b}]Ѩr\u{46a43}Ⱥ\u{76dd1}\"@%6%�*L\u{10fe25}\u{cb0c9}::\u{82}{": baguqfiheaiqmnsckfvqrzdszfgxhnfdlbgv4tv2umb4ebrla4e4ovkqzr7qtl2q, "\u{1b}\u{202e}=\t%>\u{64bd7}=\t¥.?&\u{202e}\"=\u{feff}\0\u{7ed05}\u{7f}Ⱥ\u{3b248}": true, "\"ȺA\rZ": [182, 24, 119, 121, 203, 152, 112, 93, 47, 116, 134, 100], "$": bafyr4idvqlsptx6ct75oj6n343bqpaabhltktomqf74lrhnjwj5zwvrz5m, "$\u{2}\u{9f}¥{\0$\u{202e}c\u{1b}\u{736d1}\u{7e350}#K🕴´\u{44a44}.🕴Ⱥ\u{401cb}%\r//\u{44014}r\u{1b}é¥": bafyr4icg62pibjpimmwgopvait4hdkb3lkz4ry3duekmfx4expvn4rrwqm, "$)ѨÊ\u{69a2e}:»Ⱥ\u{99}\u{f3438}\u{b}\u{c0d9c}*": 17, "$/??{\t\0á<\u{6d90c}.\\\u{5ab70}W*D.`\u{81dac}`": "🕴j\u{202e}`\u{97293}Z<²\u{aabdb}¬\u{202e}.&=Ùh\u{5680a}\"𪻏", "*\"\0'`<ѨjȺ$\u{8d55d}Ⱥ\u{d1704}$\u{69be0}]\u{feff}%..": "Ⱥl'Ⱥ\r�<;\u{edd5d}<\0\u{a2820}\0\u{16271}\u{ccc45}\\:/\u{202e}\u{4}(%\u{7}", "*/$\u{3b185}/�:\u{c8c4f}CA&4\tc,>\r+": 0.0, ".{^\u{feff}\u{b31b8}\u{f0ff0}=\u{1b}\u{cf39b}&Ⱥ\u{cc0f0}ꟊj\u{7f}2*¥n.FѨ": bafkreifagebl3ayuazlaokrf45fopvx2mxh52amx5e7yh4w3pxhkken3vq, ".\u{202e}": true, "6\u{3}\u{febc8}P\u{6a6d5}~\u{3279b}p삣6\u{b}\u{b}K": baguqehrak3yvnpqo6ab5rdeccamokfdova7l5vpi4xq7skax4mmzw75slkeq, ":\\\u{1009ca}{'\u{7f}\u{9b51b}/ek%🕴=Ⱥ\u{7f}{\u{9ecd8}/Ⱥ`\u{aaa83}\u{feff}\u{1b}{\u{5ebd0}�\u{c68be}T": 9.620946678080735e-61, ":\u{feff}A*&\u{839f8}\u{86fd8}\r4{;�<`.<$\0\u{b1b8a}>": "𠑛%Ⱥ�\u{1}<\u{1b}\05$OѨ? =Io\u{3}Ⱥ\u{4a654}\u{95}\u{2}\\", ":\u{f12fc}\u{8f80d}\u{c4396}𣝛=\u{7}\u{92a55}🕴(&\u{91993}\u{e357e}\u{8ac6d}\u{7f}Ⱥ襥\"':�\u{5}": baguqegza2ch32i7qyewr7mke33b5bpzm7jep5ei5kpard5moafylxh46gpma, "<\"\u{fe42e}\r\u{103930}O3=\u{feff}𐽱Ѩ\u{c7c2a}<.譒\u{ec02e}/\u{520c0}FA㣂\u{b}\rù": -43, "<\u{b8112}}B\u{1}\u{e22e4}$[¥=\"\u{b}`$=": bafyobzacecdbrhr5ehtimriiz6mxvinsq5tuyh7dqu3pderxxdmgtepquklbe, "?¸H/%\u{feff}\\K\u{cc4ea}y:🕴\"@8:\0:𢭤¥<\u{c9d0a}\u{7f}\u{8}\u{7f}\u{5}": bafkrwidvq2ib5wbdwi6evstk4n4zuo2ptkqx62jlqzp65xevm54zh6caiy, "@\u{66e54}Ѩ$u": null, "C𫏦.\u{b}/'8\u{49182}ȺȺ&\u{c91e4}\t/": -46, "OS\u{b}\u{7}𩚡4?Ⱥ\u{feff}'\r¥\u{202e}\u{b}B🕴f\u{202e}.": null, "Q\u{d12fe}&`\t*\0g\u{1b}\u{8}2D/$)\r{?𠯚\u{fed96}x\\û": 5, "U^\"?d\u{f8edf}R\u{a3374}<🕴\\\u{202e}>%\u{feff}\u{a2d62}\u{ab6e8}\\\tP\u{81d20}W\u{998d7}\0\0\u{d2250}\u{74ab2}.:W*%": [212, 136, 153, 95, 249, 147, 220, 254, 102, 118, 32, 185, 203, 25, 119, 125, 56, 0, 225, 234, 220, 127, 234, 201, 81, 152, 213, 225, 133, 23, 85, 200, 158, 231, 88, 27, 12], "\\Ѩ&\u{ce722}\u{74bbb}i\u{9caf6}f}<\u{feff}Uo\u{f89a7}\u{2}$\0𰔭\u{63b24}𘥆&þ": null, "`\t\u{7abdf}Ð)&g&": "{Ѩ\u{e4a29}*nȺ\u{e491e}M\u{89c42}%a\u{8}\u{82}", "`r\r\u{8}=🕴\t\u{440b0}Ⱥ\u{4}�%*{\u{202e}\u{151ad}\u{55675}K": "\u{4}\u{5d2d2}:Úv\u{f704f}\0\u{7}\u{ad34a}¥A<\u{aebdd}\u{898f2}<\u{d3097}\u{8}W/`Ⱥ\t\rѨ\u{10361f}\u{6d1ed}", "e&#{`\u{34996}?'/\u{1b}\u{2}\u{19c0c}\u{6716f}\t$%\u{db2ea}[{": 22, "n?": 52, "o\u{106e1b}?\u{1b}{*/$<Ѩ\u{7f}\u{7f}.'\"`6\r🕴�\u{202e}\u{77895}�𩈷)�\u{f670f}\u{202e}`:\u{4fb5c}>`x\u{35613}(e\u{202e}µ\u{ae9bd}\u{83}Ѩ¥\u{cf5b4}¥~\u{1}\\\u{10bc4b}hl(:#z": "\u{b}<{\u{5}\u{3c7ce}\t\u{b0200}`Ⱥ\u{58ab1}2*\"\u{ff4a9}Dw🕴&", "葜\u{1b}`�:\u{10e218}<2\u{1b}`\u{10b2e2}C\u{7f}\u{202e}\"¥s\u{1b}\0*\u{b}%\u{9d41a}=\u{e573}": [true, [32, 206, 206, 97, 143, 10, 81, 82, 157, 249, 61, 22, 250, 48, 255, 194, 232, 184, 202, 22, 232, 158, 227, 32, 56, 8, 142, 212, 153, 240, 168, 65, 248, 231, 48, 108, 237, 135, 114, 246, 246, 204, 239, 24, 162, 41, 103, 211, 37, 81, 230, 208, 111, 248, 4, 9, 197, 153, 105, 20, 217, 206, 146], true, -5.25899467531148e-128, "M<0%\u{202e}\u{b}/\u{46e36}\u{108dc}\u{a2ef8}\u{7f}.]\u{b96d6}\u{b41c1}\u{4e68e}", "\u{5d121}\u{b}\u{cf745}\u{3}\u{5badc}\u{202e}\u{d984a}", bafybwic7uqdxsofaghtkiqawch55haewwlxbtyy7dzua22bjcif4y2jmri, false, null, 29, bafkr4idbn64n3h5bfrab6gnwlyggbaxq2lyvtcpvyzp5z4h3myyl7tkzui, "ѨSm¥L\u{7}?\u{7c63b}`x%qN\u{a501c}1.Ѩ\u{5}:\u{6120c}·{*7", false, [112, 107, 102, 23, 200, 228, 14, 201, 117, 98, 1, 76, 10, 156, 40, 126, 28, 147, 14], null, "%Ѩ\u{b}=\u{e06d3}\"\0\u{b}\u{105e48}\u{dd959}㮴a{N*¥\u{4}\u{6c856}", [1, 150, 20, 111, 201, 216, 245, 91, 220, 0, 194, 252, 33, 40, 135, 13, 44, 29, 164, 248, 22, 137, 89, 237, 222, 28, 4, 157, 221, 9, 100, 197, 235, 73, 41, 84, 55, 116, 103, 40, 48, 206, 112, 238, 68, 248, 66, 52, 206, 58, 27, 54, 24, 51, 14, 70, 8, 203, 39, 212, 210, 72, 151, 65, 50, 3, 131, 97, 121, 181, 227, 173, 63, 242, 250, 82, 244, 186, 53, 96, 222, 46, 102, 216, 26], -5.797387639150806e-228, true, [228, 221, 48, 49, 157, 129, 203, 175, 12, 252, 218, 247, 178, 191, 214, 73, 7, 40, 52, 53, 19, 147, 108, 202, 231, 162, 31, 28, 67, 99, 121, 1, 129, 25, 41, 204, 153, 97, 233, 32, 246, 185, 128], -2.3032544649066786e268, true, false, 29, -23, [119, 152, 15, 167, 240, 199, 14, 99, 170, 252, 95, 162, 82, 87, 28, 38, 176, 157, 185, 58, 156, 67, 186, 0, 230, 43, 167, 184, 246, 129, 63, 121, 129, 247, 200, 218], false, -0.0, true, true, 41, -1.9024538231552825e-308, null, 0, "%\u{5}\u{86d1a}ð\\\u{5}\u{1b}\u{3}n\u{feff}F\u{10d421} for arguments::Named { impl From for Ipld { fn from(receive: Receive) -> Self { - receive.into() + arguments::Named::::from(receive).into() } } impl TryFrom for Receive { - type Error = SerdeError; + type Error = (); // FIXME fn try_from(ipld: Ipld) -> Result { - ipld_serde::from_ipld(ipld) + if let Ipld::Map(map) = ipld { + arguments::Named::(map).try_into().map_err(|_| ()) + } else { + Err(()) + } } } diff --git a/src/ability/msg/send.rs b/src/ability/msg/send.rs index ff97b16f..c3e47ab5 100644 --- a/src/ability/msg/send.rs +++ b/src/ability/msg/send.rs @@ -67,7 +67,8 @@ impl From for arguments::Named { impl From for Ipld { fn from(send: Send) -> Self { - arguments::Named::from(send).into() + let args = arguments::Named::from(send); + Ipld::Map(args.0) } } diff --git a/src/crypto/nonce.rs b/src/crypto/nonce.rs index e53472c5..0e46d2dc 100644 --- a/src/crypto/nonce.rs +++ b/src/crypto/nonce.rs @@ -19,7 +19,7 @@ use wasm_bindgen::prelude::*; use proptest::prelude::*; /// Known [`Nonce`] types -#[derive(Clone, Debug, PartialEq, EnumAsInner, Serialize, Deserialize)] +#[derive(Clone, Debug, EnumAsInner, Serialize, Deserialize)] pub enum Nonce { /// 96-bit, 12-byte nonce Nonce12([u8; 12]), @@ -31,6 +31,45 @@ pub enum Nonce { Custom(Vec), } +impl PartialEq for Nonce { + fn eq(&self, other: &Self) -> bool { + match (self, other) { + (Nonce::Nonce12(a), Nonce::Nonce12(b)) => a == b, + (Nonce::Nonce16(a), Nonce::Nonce16(b)) => a == b, + (Nonce::Custom(a), Nonce::Custom(b)) => a == b, + (Nonce::Custom(a), Nonce::Nonce12(b)) => { + if a.len() == 12 { + a.as_slice() == b + } else { + false + } + } + (Nonce::Custom(a), Nonce::Nonce16(b)) => { + if a.len() == 16 { + a.as_slice() == b + } else { + false + } + } + (Nonce::Nonce12(a), Nonce::Custom(b)) => { + if b.len() == 12 { + a == b.as_slice() + } else { + false + } + } + (Nonce::Nonce16(a), Nonce::Custom(b)) => { + if b.len() == 16 { + a == b.as_slice() + } else { + false + } + } + _ => false, + } + } +} + impl From<[u8; 12]> for Nonce { fn from(s: [u8; 12]) -> Self { Nonce::Nonce12(s) @@ -55,19 +94,15 @@ impl From for Vec { impl From> for Nonce { fn from(nonce: Vec) -> Self { - match nonce.len() { - 12 => Nonce::Nonce12( - nonce - .try_into() - .expect("12 bytes because we checked in the match"), - ), - 16 => Nonce::Nonce16( - nonce - .try_into() - .expect("16 bytes because we checked in the match"), - ), - _ => Nonce::Custom(nonce), + if let Ok(twelve) = <[u8; 12]>::try_from(nonce.clone()) { + return twelve.into(); } + + if let Ok(sixteen) = <[u8; 16]>::try_from(nonce.clone()) { + return sixteen.into(); + } + + Nonce::Custom(nonce) } } diff --git a/src/delegation/payload.rs b/src/delegation/payload.rs index e75bb60b..c54438ec 100644 --- a/src/delegation/payload.rs +++ b/src/delegation/payload.rs @@ -404,7 +404,9 @@ mod tests { fn test_ipld_round_trip(payload in Payload::::arbitrary()) { let observed: Ipld = payload.clone().into(); let parsed = Payload::::try_from(observed); - prop_assert!(matches!(parsed, Ok(payload))); + + prop_assert!(parsed.is_ok()); + prop_assert_eq!(parsed.unwrap(), payload); } #[test_log::test] @@ -412,13 +414,14 @@ mod tests { let observed: Ipld = payload.clone().into(); if let Ipld::Map(named) = observed { + prop_assert!(named.len() >= 6); prop_assert!(named.len() <= 10); for key in named.keys() { prop_assert!(matches!(key.as_str(), "sub" | "iss" | "aud" | "via" | "cmd" | "pol" | "meta" | "nonce" | "exp" | "nbf")); } } else { - panic!("Expected Ipld::Map, got {:?}", observed); + prop_assert!(false, "ipld map"); } } @@ -426,8 +429,6 @@ mod tests { fn test_ipld_field_types(payload in Payload::::arbitrary()) { let named: Named = payload.clone().into(); - prop_assert!(named.len() <= 10); - let iss = named.get("iss".into()); let aud = named.get("aud".into()); let cmd = named.get("cmd".into()); diff --git a/src/invocation/payload.rs b/src/invocation/payload.rs index 0d71a412..6e92ae64 100644 --- a/src/invocation/payload.rs +++ b/src/invocation/payload.rs @@ -294,6 +294,14 @@ where args.insert("aud".into(), aud.to_string().into()); } + if let Some(cause) = payload.cause { + args.insert("cause".into(), cause.into()); + } + + if !payload.metadata.is_empty() { + args.insert("meta".into(), payload.metadata.into()); + } + if let Some(iat) = payload.issued_at { args.insert("iat".into(), iat.into()); } @@ -519,6 +527,7 @@ where let mut audience = None; let mut command = None; let mut args = None; + let mut cause = None; let mut metadata = None; let mut nonce = None; let mut expiration = None; @@ -532,45 +541,49 @@ where Ipld::String(s) => { DID::from_str(s.as_str()).map_err(ParseError::DidParseError)? } - _ => Err(ParseError::WrongTypeForField(k, v))?, + _ => return Err(ParseError::WrongTypeForField(k, v)), }) } "iss" => match v { Ipld::String(s) => { issuer = Some(DID::from_str(s.as_str()).map_err(ParseError::DidParseError)?) } - _ => Err(ParseError::WrongTypeForField(k, v))?, + _ => return Err(ParseError::WrongTypeForField(k, v)), }, "aud" => match v { Ipld::String(s) => { audience = Some(DID::from_str(s.as_str()).map_err(ParseError::DidParseError)?) } - _ => Err(ParseError::WrongTypeForField(k, v))?, + _ => return Err(ParseError::WrongTypeForField(k, v)), }, "cmd" => match v { Ipld::String(s) => command = Some(s), - _ => Err(ParseError::WrongTypeForField(k, v))?, + _ => return Err(ParseError::WrongTypeForField(k, v)), }, "args" => match v.try_into() { Ok(a) => args = Some(a), - _ => Err(ParseError::ArgsNotAMap)?, + _ => return Err(ParseError::ArgsNotAMap), }, "meta" => match v { Ipld::Map(m) => metadata = Some(m), - _ => Err(ParseError::WrongTypeForField(k, v))?, + _ => return Err(ParseError::WrongTypeForField(k, v)), }, "nonce" => match v { Ipld::Bytes(b) => nonce = Some(Nonce::from(b)), - _ => Err(ParseError::WrongTypeForField(k, v))?, + _ => return Err(ParseError::WrongTypeForField(k, v)), + }, + "cause" => match v { + Ipld::Link(c) => cause = Some(c), + _ => return Err(ParseError::WrongTypeForField(k, v)), }, "exp" => match v { Ipld::Integer(i) => expiration = Some(i.try_into()?), - _ => Err(ParseError::WrongTypeForField(k, v))?, + _ => return Err(ParseError::WrongTypeForField(k, v)), }, "iat" => match v { Ipld::Integer(i) => issued_at = Some(i.try_into()?), - _ => Err(ParseError::WrongTypeForField(k, v))?, + _ => return Err(ParseError::WrongTypeForField(k, v)), }, "prf" => match &v { Ipld::List(xs) => { @@ -583,9 +596,9 @@ where .collect::, ParseError>>()?, ) } - _ => Err(ParseError::WrongTypeForField(k, v))?, + _ => return Err(ParseError::WrongTypeForField(k, v)), }, - _ => Err(ParseError::UnknownField(k.to_string()))?, + _ => return Err(ParseError::UnknownField(k.to_string())), } } @@ -600,7 +613,7 @@ where audience, ability, proofs: proofs.ok_or(ParseError::MissingProofsField)?, - cause: None, + cause, metadata: metadata.unwrap_or_default(), nonce: nonce.ok_or(ParseError::MissingNonce)?, issued_at, @@ -658,15 +671,6 @@ where /// [`Promise`]: crate::invocation::promise::Promise pub type Promised = Payload<::Promised, DID>; -// impl From> for Ipld -// where -// Named: From, -// { -// fn from(payload: Payload) -> Self { -// arguments::Named::from(payload).into() -// } -// } - #[cfg(feature = "test_utils")] impl Arbitrary for Payload where @@ -683,11 +687,11 @@ where DID::arbitrary_with(did_args.clone()), Option::::arbitrary_with((0.5.into(), did_args)), Nonce::arbitrary(), - prop::collection::vec(cid::Newtype::arbitrary().prop_map(|nt| nt.cid), 0..25), + prop::collection::vec(cid::Newtype::arbitrary().prop_map(|nt| nt.cid), 0..12), Option::::arbitrary().prop_map(|opt_nt| opt_nt.map(|nt| nt.cid)), Option::::arbitrary(), Option::::arbitrary(), - prop::collection::btree_map(".*", ipld::Newtype::arbitrary(), 0..50).prop_map(|m| { + prop::collection::btree_map(".*", ipld::Newtype::arbitrary(), 0..12).prop_map(|m| { m.into_iter() .map(|(k, v)| (k, v.0)) .collect::>() @@ -728,6 +732,7 @@ where mod tests { use super::*; use crate::ability::msg::Msg; + use crate::ipld; use assert_matches::assert_matches; use pretty_assertions as pretty; use proptest::prelude::*; @@ -737,91 +742,96 @@ mod tests { #![proptest_config(ProptestConfig::with_cases(100))] #[test_log::test] - fn test_inv_ipld_round_trip(payload in Payload::::arbitrary()) { + fn test_ipld_round_trip(payload in Payload::::arbitrary()) { let observed: Named = payload.clone().into(); - let parsed = Payload::::try_from(observed); + let parsed = Payload::::try_from(observed.clone()); - dbg!(&parsed); - prop_assert!(matches!(parsed, Ok(payload))); + prop_assert!(parsed.is_ok()); + prop_assert_eq!(parsed.unwrap(), payload); + } + + #[test_log::test] + fn test_ipld_only_has_correct_fields(payload in Payload::::arbitrary()) { + let observed: Ipld = payload.clone().into(); + + if let Ipld::Map(named) = observed { + prop_assert!(named.len() >= 6); + prop_assert!(named.len() <= 11); + + for key in named.keys() { + prop_assert!(matches!(key.as_str(), "sub" | "iss" | "aud" | "cmd" | "args" | "prf" | "cause" | "meta" | "nonce" | "exp" | "iat")); + } + } else { + prop_assert!(false, "ipld map"); + } } - // #[test_log::test] - // fn test_ipld_has_correct_fields(payload in Payload::::arbitrary()) { - // let observed: Ipld = payload.clone().into(); - - // if let Ipld::Map(named) = observed { - // prop_assert!(named.len() <= 10); - - // for key in named.keys() { - // prop_assert!(matches!(key.as_str(), "sub" | "iss" | "aud" | "via" | "cmd" | "pol" | "meta" | "nonce" | "exp" | "nbf")); - // } - // } else { - // panic!("Expected Ipld::Map, got {:?}", observed); - // } - // } - - // #[test_log::test] - // fn test_ipld_field_types(payload in Payload::::arbitrary()) { - // let named: Named = payload.clone().into(); - - // dbg!(payload.issuer.to_string()); - - // prop_assert!(named.len() <= 10); - - // let iss = named.get("iss".into()); - // let aud = named.get("aud".into()); - // let cmd = named.get("cmd".into()); - // let pol = named.get("pol".into()); - // let nonce = named.get("nonce".into()); - // let exp = named.get("exp".into()); - - // // Required Fields - // prop_assert_eq!(iss.unwrap(), &Ipld::String(payload.issuer.to_string())); - // prop_assert_eq!(aud.unwrap(), &Ipld::String(payload.audience.to_string())); - // prop_assert_eq!(cmd.unwrap(), &Ipld::String(payload.command.clone())); - // prop_assert_eq!(pol.unwrap(), &Ipld::List(payload.policy.clone().into_iter().map(|p| p.into()).collect())); - // prop_assert_eq!(nonce.unwrap(), &payload.nonce.into()); - // prop_assert_eq!(exp.unwrap(), &payload.expiration.into()); - - // // Optional Fields - // match (payload.subject, named.get("sub")) { - // (Some(sub), Some(Ipld::String(s))) => { - // prop_assert_eq!(&sub.to_string(), s); - // } - // (None, Some(Ipld::Null)) => prop_assert!(true), - // _ => prop_assert!(false) - // } - - // match (payload.via, named.get("via")) { - // (Some(via), Some(Ipld::String(s))) => { - // prop_assert_eq!(&via.to_string(), s); - // } - // (None, None) => prop_assert!(true), - // _ => prop_assert!(false) - // } - - // match (payload.metadata.is_empty(), named.get("meta")) { - // (false, Some(Ipld::Map(btree))) => { - // prop_assert_eq!(&payload.metadata, btree); - // } - // (true, None) => prop_assert!(true), - // _ => prop_assert!(false) - // } - - // match (payload.not_before, named.get("nbf")) { - // (Some(nbf), Some(Ipld::Integer(i))) => { - // prop_assert_eq!(&i128::from(nbf), i); - // } - // (None, None) => prop_assert!(true), - // _ => prop_assert!(false) - // } - // } - - // #[test_log::test] - // fn test_non_payload(ipld in ipld::Newtype::arbitrary()) { - // // Just ensuring that a negative test shows up - // let parsed = Payload::::try_from(ipld.0); - // prop_assert!(parsed.is_err()) - // } + #[test_log::test] + fn test_ipld_field_types(payload in Payload::::arbitrary()) { + let named: Named = payload.clone().into(); + + let sub = named.get("sub".into()); + let iss = named.get("iss".into()); + let cmd = named.get("cmd".into()); + let args = named.get("args".into()); + let prf = named.get("prf".into()); + let nonce = named.get("nonce".into()); + + // Required Fields + prop_assert_eq!(sub.unwrap(), &Ipld::String(payload.subject.to_string())); + prop_assert_eq!(iss.unwrap(), &Ipld::String(payload.issuer.to_string())); + prop_assert_eq!(cmd.unwrap(), &Ipld::String(payload.ability.to_command())); + + prop_assert_eq!(args.unwrap(), &payload.ability.into()); + prop_assert!(matches!(args, Some(Ipld::Map(_)))); + + prop_assert!(matches!(prf.unwrap(), &Ipld::List(_))); + if let Some(Ipld::List(ipld_proofs)) = prf { + prop_assert_eq!(ipld_proofs.len(), payload.proofs.len()); + + for entry in ipld_proofs { + prop_assert!(matches!(entry, Ipld::Link(_))); + } + } else { + prop_assert!(false); + } + + prop_assert_eq!(nonce.unwrap(), &payload.nonce.into()); + + // Optional Fields + prop_assert_eq!(payload.audience.map(|did| did.into()), named.get("aud").cloned()); + prop_assert_eq!(payload.cause.map(Ipld::Link), named.get("cause").cloned()); + + match (payload.metadata.is_empty(), named.get("meta")) { + (false, Some(Ipld::Map(btree))) => { + prop_assert_eq!(&payload.metadata, btree); + } + (true, None) => prop_assert!(true), + _ => prop_assert!(false) + } + + match (payload.expiration, named.get("exp")) { + (Some(exp), Some(Ipld::Integer(i))) => { + prop_assert_eq!(i128::from(exp), i.clone()); + } + (None, None) => prop_assert!(true), + _ => prop_assert!(false) + } + + match (payload.issued_at, named.get("iat")) { + (Some(iat), Some(Ipld::Integer(i))) => { + prop_assert_eq!(i128::from(iat), i.clone()); + } + (None, None) => prop_assert!(true), + _ => prop_assert!(false) + } + } + + #[test_log::test] + fn test_non_payload(named in arguments::Named::::arbitrary()) { + // Just ensuring that a negative test shows up + let parsed = Payload::::try_from(named); + prop_assert!(parsed.is_err()) + } } } From 07ed47888ace6bfa1a30ebf4a5db2341f1aa019b Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Sun, 17 Mar 2024 21:57:13 -0700 Subject: [PATCH 163/188] Fix glob matcher --- src/crypto/varsig/header/preset.rs | 2 +- src/delegation/payload.rs | 2 +- src/delegation/policy/predicate.rs | 612 +++++++++++++++++++++++++++-- src/did/key/signer.rs | 109 +++-- src/did/preset.rs | 16 +- src/ipld/newtype.rs | 35 ++ 6 files changed, 706 insertions(+), 70 deletions(-) diff --git a/src/crypto/varsig/header/preset.rs b/src/crypto/varsig/header/preset.rs index 00bab1d8..80664d5e 100644 --- a/src/crypto/varsig/header/preset.rs +++ b/src/crypto/varsig/header/preset.rs @@ -100,7 +100,7 @@ impl Header for Preset { Preset::Es256(es256) => es256.codec(), Preset::Es256k(es256k) => es256k.codec(), Preset::Es512(es512) => es512.codec(), - // BLS? + // Preset::Bls } } } diff --git a/src/delegation/payload.rs b/src/delegation/payload.rs index c54438ec..2f2715ef 100644 --- a/src/delegation/payload.rs +++ b/src/delegation/payload.rs @@ -398,7 +398,7 @@ mod tests { use testresult::TestResult; proptest! { - #![proptest_config(ProptestConfig::with_cases(100))] + // #![proptest_config(ProptestConfig::with_cases(200))] #[test_log::test] fn test_ipld_round_trip(payload in Payload::::arbitrary()) { diff --git a/src/delegation/policy/predicate.rs b/src/delegation/policy/predicate.rs index abfb96a5..d8e7cc5b 100644 --- a/src/delegation/policy/predicate.rs +++ b/src/delegation/policy/predicate.rs @@ -1,18 +1,14 @@ use super::selector::filter::Filter; -use super::selector::{Select, Selector, SelectorError}; +use super::selector::{Select, SelectorError}; use crate::ipld; use enum_as_inner::EnumAsInner; use libipld_core::ipld::Ipld; -use serde::{Deserialize, Serialize}; use std::{fmt, str::FromStr}; use thiserror::Error; #[cfg(feature = "test_utils")] use proptest::prelude::*; -// FIXME Normal form? -// FIXME exract domain gen selectors first? -// FIXME rename constraint or validation or expression or something? #[derive(Debug, Clone, PartialEq)] pub enum Predicate { // Comparison @@ -86,14 +82,18 @@ impl Predicate { .get(data)? .to_vec() .iter() - .try_fold(true, |acc, nt| Ok(acc && p.clone().run(&nt.0)?))?, + .try_fold(true, |acc, each_datum| { + Ok(acc && p.clone().run(&each_datum.0)?) + })?, Predicate::Some(xs, p) => { let pred = p.clone(); xs.get(data)? .to_vec() .iter() - .try_fold(true, |acc, nt| Ok(acc || pred.clone().run(&nt.0)?))? + .try_fold(false, |acc, each_datum| { + Ok(acc || pred.clone().run(&each_datum.0)?) + })? } }) } @@ -664,32 +664,69 @@ impl Predicate { } } -pub fn glob(input: &String, pattern: &String) -> bool { - let mut chars = input.chars(); - let mut like = pattern.chars(); - - loop { - match (chars.next(), like.next()) { - (Some(i), Some(p)) => { - if p == '*' { - return true; - } else if i != p { - return false; +pub fn glob(input: &str, pattern: &str) -> bool { + if pattern.is_empty() { + return input == ""; + } + + // Parsing pattern + let (saw_escape, mut patterns, mut working) = pattern.chars().fold( + (false, vec![], "".to_string()), + |(saw_escape, mut acc, mut working), c| { + match c { + '*' => { + if saw_escape { + working.push('*'); + (false, acc, working) + } else { + acc.push(working); + working = "".to_string(); + (false, acc, working) + } } - } - (Some(_), None) => { - return false; // FIXME correct? - } - (None, Some(p)) => { - if p == '*' { - return true; + '\\' => { + if saw_escape { + // Push prev escape + working.push('\\'); + } + (true, acc, working) + } + _ => { + if saw_escape { + working.push('\\'); + } + + working.push(c); + (false, acc, working) } } - (None, None) => { - return true; - } - } + }, + ); + + if saw_escape { + working.push('\\'); } + + patterns.push(working); + + // Test input against the pattern + patterns + .iter() + .enumerate() + .try_fold(input, |acc, (idx, pattern_frag)| { + if let Some((pre, post)) = acc.split_once(pattern_frag) { + if idx == 0 && !pattern.starts_with("*") && !pre.is_empty() { + Err(()) + } else if idx == patterns.len() - 1 && !pattern.ends_with("*") && !post.is_empty() { + Err(()) + } else { + Ok(post) + } + } else { + Err(()) + } + }) + .is_ok() } impl TryFrom for Predicate { @@ -873,7 +910,7 @@ impl From for Ipld { #[cfg(feature = "test_utils")] impl Arbitrary for Predicate { - type Parameters = (); // FIXME? + type Parameters = (); type Strategy = BoxedStrategy; fn arbitrary_with(_params: Self::Parameters) -> Self::Strategy { @@ -913,3 +950,518 @@ impl Arbitrary for Predicate { prop_oneof![leaf, connective, quantified].boxed() } } + +#[cfg(test)] +mod tests { + use super::*; + use assert_matches::assert_matches; + use pretty_assertions as pretty; + use proptest::prelude::*; + use testresult::TestResult; + + mod glob { + use super::*; + + #[test_log::test] + fn test_concrete() -> TestResult { + let got = glob(&"hello world", &"hello world"); + assert!(got); + Ok(()) + } + + #[test_log::test] + fn test_concrete_fail() -> TestResult { + let got = glob(&"hello world", &"NOPE"); + assert!(!got); + Ok(()) + } + + #[test_log::test] + fn test_empty_pattern_fail() -> TestResult { + let got = glob(&"hello world", &""); + assert!(!got); + Ok(()) + } + + #[test_log::test] + fn test_escaped_star() -> TestResult { + let got = glob(&"*", &r#"\*"#); + assert!(got); + Ok(()) + } + + #[test_log::test] + fn test_inner_escaped_star() -> TestResult { + let got = glob(&"hello, * world*", &r#"hello*\**\*"#); + assert!(got); + Ok(()) + } + + #[test_log::test] + fn test_empty_string_fail() -> TestResult { + let got = glob(&"", &"NOPE"); + assert!(!got); + Ok(()) + } + + #[test_log::test] + fn test_left_star() -> TestResult { + let got = glob(&"hello world", &"*world"); + assert!(got); + Ok(()) + } + + #[test_log::test] + fn test_left_star_failure() -> TestResult { + let got = glob(&"hello world", &"*NOPE"); + assert!(!got); + Ok(()) + } + + #[test_log::test] + fn test_right_star() -> TestResult { + let got = glob(&"hello world", &"hello*"); + assert!(got); + Ok(()) + } + + #[test_log::test] + fn test_right_star_failure() -> TestResult { + let got = glob(&"hello world", &"NOPE*"); + assert!(!got); + Ok(()) + } + + #[test_log::test] + fn test_only_star() -> TestResult { + let got = glob(&"hello world", &"*"); + assert!(got); + Ok(()) + } + + #[test_log::test] + fn test_two_stars() -> TestResult { + let got = glob(&"hello world", &"* *"); + assert!(got); + Ok(()) + } + + #[test_log::test] + fn test_two_stars_fail() -> TestResult { + let got = glob(&"hello world", &"*@*"); + assert!(!got); + Ok(()) + } + + #[test_log::test] + fn test_multiple_inner_stars() -> TestResult { + let got = glob(&"hello world", &"h*l*o*w*r*d"); + assert!(got); + Ok(()) + } + + #[test_log::test] + fn test_multiple_inner_stars_fail() -> TestResult { + let got = glob(&"hello world", &"a*b*c*d*e*f"); + assert!(!got); + Ok(()) + } + + #[test_log::test] + fn test_concrete_with_multiple_inner_stars() -> TestResult { + let got = glob(&"hello world", &"hello* *world"); + assert!(got); + Ok(()) + } + } + + mod run { + use super::*; + use libipld::ipld; + + fn simple() -> Ipld { + ipld!({ + "foo": 42, + "bar": "baz".to_string(), + "qux": true + }) + } + + fn email() -> Ipld { + ipld!({ + "from": "alice@example.com".to_string(), + "to": ["bob@example.com".to_string(), "fraud@example.com".to_string()], + "cc": ["carol@example.com".to_string()], + "subject": "Quarterly Reports".to_string(), + "body": "Here's Q2 the reports ...".to_string() + }) + } + + fn wasm() -> Ipld { + ipld!({ + "mod": "data:application/wasm;base64,SOMEBASE64GOESHERE", + "fun": "test", + "input": [0, 1, 2 ,3] + }) + } + + #[test_log::test] + fn test_eq() -> TestResult { + let p = Predicate::Equal( + Select::from_str(".from").unwrap(), + "alice@example.com".into(), + ); + + assert!(p.run(&email())?); + Ok(()) + } + + #[test_log::test] + fn test_eq_fail_same_type() -> TestResult { + let p = Predicate::Equal(Select::from_str(".from").unwrap(), "NOPE".into()); + + assert!(!p.run(&email())?); + Ok(()) + } + + #[test_log::test] + fn test_eq_bad_selector() -> TestResult { + let p = Predicate::Equal( + Select::from_str(".NOPE").unwrap(), + "alice@example.com".into(), + ); + + assert!(p.run(&email()).is_err()); + Ok(()) + } + + #[test_log::test] + fn test_eq_fail_different_type() -> TestResult { + let p = Predicate::Equal(Select::from_str(".from").unwrap(), 42.into()); + + assert!(!p.run(&email())?); + Ok(()) + } + + #[test_log::test] + fn test_gt() -> TestResult { + let p = Predicate::GreaterThan(Select::from_str(".foo").unwrap(), (41.9).into()); + assert!(p.run(&simple())?); + Ok(()) + } + + #[test_log::test] + fn test_gt_fail() -> TestResult { + let p = Predicate::GreaterThan(Select::from_str(".foo").unwrap(), 42.into()); + assert!(!p.run(&simple())?); + Ok(()) + } + + #[test_log::test] + fn test_gte() -> TestResult { + let p = Predicate::GreaterThanOrEqual(Select::from_str(".foo").unwrap(), 42.into()); + assert!(p.run(&simple())?); + Ok(()) + } + + #[test_log::test] + fn test_gte_fail() -> TestResult { + let p = Predicate::GreaterThanOrEqual(Select::from_str(".foo").unwrap(), (42.1).into()); + assert!(!p.run(&simple())?); + Ok(()) + } + + #[test_log::test] + fn test_lt() -> TestResult { + let p = Predicate::LessThan(Select::from_str(".foo").unwrap(), (42.1).into()); + assert!(p.run(&simple())?); + Ok(()) + } + + #[test_log::test] + fn test_lt_fail() -> TestResult { + let p = Predicate::LessThan(Select::from_str(".foo").unwrap(), 42.into()); + assert!(!p.run(&simple())?); + Ok(()) + } + + #[test_log::test] + fn test_lte() -> TestResult { + let p = Predicate::LessThanOrEqual(Select::from_str(".foo").unwrap(), 42.into()); + assert!(p.run(&simple())?); + Ok(()) + } + + #[test_log::test] + fn test_lte_fail() -> TestResult { + let p = Predicate::LessThanOrEqual(Select::from_str(".foo").unwrap(), (41.9).into()); + assert!(!p.run(&simple())?); + Ok(()) + } + + #[test_log::test] + fn test_like() -> TestResult { + let p = Predicate::Like(Select::from_str(".from").unwrap(), "alice@*".into()); + assert!(p.run(&email())?); + Ok(()) + } + + #[test_log::test] + fn test_like_fail_concrete() -> TestResult { + let p = Predicate::Like(Select::from_str(".from").unwrap(), "NOPE".into()); + assert!(!p.run(&email())?); + Ok(()) + } + + #[test_log::test] + fn test_like_fail_left_star() -> TestResult { + let p = Predicate::Like(Select::from_str(".from").unwrap(), "*NOPE".into()); + assert!(!p.run(&email())?); + Ok(()) + } + + #[test_log::test] + fn test_like_fail_right_star() -> TestResult { + let p = Predicate::Like(Select::from_str(".from").unwrap(), "NOPE*".into()); + assert!(!p.run(&email())?); + Ok(()) + } + + #[test_log::test] + fn test_like_fail_both_stars() -> TestResult { + let p = Predicate::Like(Select::from_str(".from").unwrap(), "*NOPE*".into()); + assert!(!p.run(&email())?); + Ok(()) + } + + #[test_log::test] + fn test_not() -> TestResult { + let p = Predicate::Not(Box::new(Predicate::Equal( + Select::from_str(".from").unwrap(), + "NOPE".into(), + ))); + + assert!(p.run(&email())?); + Ok(()) + } + + #[test_log::test] + fn test_not_fail() -> TestResult { + let p = Predicate::Not(Box::new(Predicate::Equal( + Select::from_str(".from").unwrap(), + "alice@example.com".into(), + ))); + + assert!(!p.run(&email())?); + Ok(()) + } + + #[test_log::test] + fn test_and_both_succeed() -> TestResult { + let p = Predicate::And( + Box::new(Predicate::Equal( + Select::from_str(".from").unwrap(), + "alice@example.com".into(), + )), + Box::new(Predicate::Equal( + Select::from_str(".subject").unwrap(), + "Quarterly Reports".into(), + )), + ); + + assert!(p.run(&email())?); + Ok(()) + } + + #[test_log::test] + fn test_and_left_fail() -> TestResult { + let p = Predicate::And( + Box::new(Predicate::Equal( + Select::from_str(".from").unwrap(), + "NOPE".into(), + )), + Box::new(Predicate::Equal( + Select::from_str(".subject").unwrap(), + "Quarterly Reports".into(), + )), + ); + + assert!(!p.run(&email())?); + Ok(()) + } + + #[test_log::test] + fn test_and_right_fail() -> TestResult { + let p = Predicate::And( + Box::new(Predicate::Equal( + Select::from_str(".from").unwrap(), + "alice@example.com".into(), + )), + Box::new(Predicate::Equal( + Select::from_str(".subject").unwrap(), + "NOPE".into(), + )), + ); + + assert!(!p.run(&email())?); + Ok(()) + } + + #[test_log::test] + fn test_and_both_fail() -> TestResult { + let p = Predicate::And( + Box::new(Predicate::Equal( + Select::from_str(".from").unwrap(), + "NOPE".into(), + )), + Box::new(Predicate::Equal( + Select::from_str(".subject").unwrap(), + "NOPE".into(), + )), + ); + + assert!(!p.run(&email())?); + Ok(()) + } + + #[test_log::test] + fn test_or_both_succeed() -> TestResult { + let p = Predicate::Or( + Box::new(Predicate::Equal( + Select::from_str(".from").unwrap(), + "alice@example.com".into(), + )), + Box::new(Predicate::Equal( + Select::from_str(".subject").unwrap(), + "Quarterly Reports".into(), + )), + ); + + assert!(p.run(&email())?); + Ok(()) + } + + #[test_log::test] + fn test_or_left_fail() -> TestResult { + let p = Predicate::Or( + Box::new(Predicate::Equal( + Select::from_str(".from").unwrap(), + "NOPE".into(), + )), + Box::new(Predicate::Equal( + Select::from_str(".subject").unwrap(), + "Quarterly Reports".into(), + )), + ); + + assert!(p.run(&email())?); + Ok(()) + } + + #[test_log::test] + fn test_or_right_fail() -> TestResult { + let p = Predicate::Or( + Box::new(Predicate::Equal( + Select::from_str(".from").unwrap(), + "alice@example.com".into(), + )), + Box::new(Predicate::Equal( + Select::from_str(".subject").unwrap(), + "NOPE".into(), + )), + ); + + assert!(p.run(&email())?); + Ok(()) + } + + #[test_log::test] + fn test_or_both_fail() -> TestResult { + let p = Predicate::Or( + Box::new(Predicate::Equal( + Select::from_str(".from").unwrap(), + "NOPE".into(), + )), + Box::new(Predicate::Equal( + Select::from_str(".subject").unwrap(), + "NOPE".into(), + )), + ); + + assert!(!p.run(&email())?); + Ok(()) + } + + // FIXME nested, too + #[test_log::test] + fn test_every() -> TestResult { + let p = Predicate::Every( + Select::from_str(".input[]").unwrap(), + Box::new(Predicate::LessThan( + Select::from_str(".").unwrap(), + 100.into(), + )), + ); + + assert!(p.run(&wasm())?); + Ok(()) + } + + #[test_log::test] + fn test_every_failure() -> TestResult { + let p = Predicate::Every( + Select::from_str(".input[]").unwrap(), + Box::new(Predicate::LessThan( + Select::from_str(".").unwrap(), + 1.into(), + )), + ); + + assert!(!p.run(&wasm())?); + Ok(()) + } + + // FIXME nested, too + #[test_log::test] + fn test_some_all_succeed() -> TestResult { + let p = Predicate::Some( + Select::from_str(".input[]").unwrap(), + Box::new(Predicate::LessThan( + Select::from_str(".").unwrap(), + 100.into(), + )), + ); + + assert!(p.run(&wasm())?); + Ok(()) + } + + #[test_log::test] + fn test_some_not_all() -> TestResult { + let p = Predicate::Some( + Select::from_str(".input[]").unwrap(), + Box::new(Predicate::LessThan( + Select::from_str(".").unwrap(), + 1.into(), + )), + ); + + assert!(p.run(&wasm())?); + Ok(()) + } + + #[test_log::test] + fn test_some_all_fail() -> TestResult { + let p = Predicate::Some( + Select::from_str(".input[]").unwrap(), + Box::new(Predicate::LessThan( + Select::from_str(".").unwrap(), + 0.into(), + )), + ); + + assert!(!p.run(&wasm())?); + Ok(()) + } + } +} diff --git a/src/did/key/signer.rs b/src/did/key/signer.rs index 529f557c..b7f95e40 100644 --- a/src/did/key/signer.rs +++ b/src/did/key/signer.rs @@ -1,6 +1,4 @@ use super::Signature; -use crate::did::Did; -use did_url::DID; use enum_as_inner::EnumAsInner; #[cfg(feature = "eddsa")] @@ -27,50 +25,49 @@ use crate::crypto::rs512; #[cfg(feature = "bls")] use crate::crypto::bls12381; -/// Signature types that are verifiable by `did:key` [`Verifier`]s. -#[derive(Debug, Clone, PartialEq, Eq, EnumAsInner)] +/// Signer types that are verifiable by `did:key` [`Verifier`]s. +#[derive(Clone, EnumAsInner)] pub enum Signer { - /// `EdDSA` signature. + /// `EdDSA` signer. #[cfg(feature = "eddsa")] EdDsa(ed25519_dalek::SigningKey), - // FIXME - // /// `ES256K` (`secp256k1`) signature. - // #[cfg(feature = "es256k")] - // Es256k(k256::ecdsa::Signer), - // /// `P-256` signature. - // #[cfg(feature = "es256")] - // P256(p256::ecdsa::Signer), + /// `ES256K` (`secp256k1`) signer. + #[cfg(feature = "es256k")] + Es256k(k256::ecdsa::SigningKey), - // /// `P-384` signature. - // #[cfg(feature = "es384")] - // P384(p384::ecdsa::Signer), + /// `P-256` signer. + #[cfg(feature = "es256")] + P256(p256::ecdsa::SigningKey), - // /// `P-521` signature. - // #[cfg(feature = "es512")] - // P521(ext_p521::ecdsa::Signer), + /// `P-384` signer. + #[cfg(feature = "es384")] + P384(p384::ecdsa::SigningKey), - // /// `RS256` signature. - // #[cfg(feature = "rs256")] - // Rs256(rs256::Signer), + /// `P-521` signer. + #[cfg(feature = "es512")] + P521(ext_p521::ecdsa::SigningKey), - // /// `RS512` signature. - // #[cfg(feature = "rs512")] - // Rs512(rs512::Signer), + /// `RS256` signer. + #[cfg(feature = "rs256")] + Rs256(rs256::SigningKey), - // /// `BLS 12-381` signature for the "min pub key" variant. - // #[cfg(feature = "bls")] - // BlsMinPk(bls12381::min_pk::Signer), + /// `RS512` signer. + #[cfg(feature = "rs512")] + Rs512(rs512::SigningKey), - // /// `BLS 12-381` signature for the "min sig" variant. - // #[cfg(feature = "bls")] - // BlsMinSig(bls12381::min_sig::Signer), + /// `BLS 12-381` signer for the "min pub key" variant. + #[cfg(feature = "bls")] + BlsMinPk(blst::min_pk::SecretKey), - // /// An unknown signature type. + /// `BLS 12-381` signer for the "min sig" variant. + #[cfg(feature = "bls")] + BlsMinSig(blst::min_sig::SecretKey), + // /// An unknown signer type. // /// // /// This is primarily for parsing, where reification is delayed // /// until the DID method is known. - // Unknown(Vec), + // FIXME rmeove Unknown(Vec), } impl signature::Signer for Signer { @@ -81,6 +78,54 @@ impl signature::Signer for Signer { let sig = signer.sign(msg); Ok(Signature::EdDsa(sig)) } + + #[cfg(feature = "es256k")] + Signer::Es256k(signer) => { + let sig = signer.sign(msg); + Ok(Signature::Es256k(sig)) + } + + #[cfg(feature = "es256")] + Signer::P256(signer) => { + let sig = signer.sign(msg); + Ok(Signature::P256(sig)) + } + + #[cfg(feature = "es384")] + Signer::P384(signer) => { + let sig = signer.sign(msg); + Ok(Signature::P384(sig)) + } + + #[cfg(feature = "es512")] + Signer::P521(signer) => { + let sig = signer.sign(msg); + Ok(Signature::P521(sig)) + } + + #[cfg(feature = "rs256")] + Signer::Rs256(signer) => { + let sig = signer.sign(msg); + Ok(Signature::Rs256(sig)) + } + + #[cfg(feature = "rs512")] + Signer::Rs512(signer) => { + let sig = signer.sign(msg); + Ok(Signature::Rs512(sig)) + } + + #[cfg(feature = "bls")] + Signer::BlsMinPk(signer) => { + let sig = signer.try_sign(msg)?; + Ok(Signature::BlsMinPk(sig)) + } + + #[cfg(feature = "bls")] + Signer::BlsMinSig(signer) => { + let sig = signer.try_sign(msg)?; + Ok(Signature::BlsMinSig(sig)) + } } } } diff --git a/src/did/preset.rs b/src/did/preset.rs index 635540f7..a4616525 100644 --- a/src/did/preset.rs +++ b/src/did/preset.rs @@ -45,12 +45,20 @@ impl From for DID { } } -#[derive(Debug, Clone, EnumAsInner, PartialEq, Eq)] +#[derive(Clone, EnumAsInner)] pub enum Signer { Key(key::Signer), // FIXME Dns(did_url::DID), } +impl std::fmt::Debug for Signer { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Signer::Key(signer) => write!(f, "Signer::Key(HIDDEN)"), + } + } +} + impl Did for Verifier { type Signature = key::Signature; type Signer = self::Signer; @@ -102,10 +110,6 @@ impl Arbitrary for Verifier { type Strategy = BoxedStrategy; fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy { - prop_oneof![ - key::Verifier::arbitrary().prop_map(Verifier::Key), - // FIXME did_url::DID::arbitrary().prop_map(Verifier::Dns), - ] - .boxed() + key::Verifier::arbitrary().prop_map(Verifier::Key).boxed() } } diff --git a/src/ipld/newtype.rs b/src/ipld/newtype.rs index 30a28c89..31451de1 100644 --- a/src/ipld/newtype.rs +++ b/src/ipld/newtype.rs @@ -53,6 +53,30 @@ impl From for Newtype { } } +impl From for Newtype { + fn from(i: i128) -> Self { + Newtype(Ipld::Integer(i)) + } +} + +impl From for Newtype { + fn from(f: f64) -> Self { + Newtype(Ipld::Float(f)) + } +} + +impl From<&str> for Newtype { + fn from(s: &str) -> Self { + Newtype(Ipld::String(s.to_string())) + } +} + +impl From for Newtype { + fn from(s: String) -> Self { + Newtype(Ipld::String(s)) + } +} + impl TryFrom for String { type Error = (); @@ -64,6 +88,17 @@ impl TryFrom for String { } } +impl TryFrom for i128 { + type Error = (); + + fn try_from(nt: Newtype) -> Result { + match nt.0 { + Ipld::Integer(i) => Ok(i), + _ => Err(()), + } + } +} + impl From for Ipld { fn from(wrapped: Newtype) -> Self { wrapped.0 From b965982d1a98d53f387bbfa845327f6a66717de2 Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Mon, 18 Mar 2024 00:01:34 -0700 Subject: [PATCH 164/188] Working on edge cases in selector parser --- src/delegation/policy/predicate.rs | 105 +++++++++- src/delegation/policy/selector.rs | 248 ++++++++++++++--------- src/delegation/policy/selector/filter.rs | 231 ++++++++++++++++++++- 3 files changed, 475 insertions(+), 109 deletions(-) diff --git a/src/delegation/policy/predicate.rs b/src/delegation/policy/predicate.rs index d8e7cc5b..a9f5b1f5 100644 --- a/src/delegation/policy/predicate.rs +++ b/src/delegation/policy/predicate.rs @@ -1089,11 +1089,11 @@ mod tests { fn email() -> Ipld { ipld!({ - "from": "alice@example.com".to_string(), - "to": ["bob@example.com".to_string(), "fraud@example.com".to_string()], - "cc": ["carol@example.com".to_string()], - "subject": "Quarterly Reports".to_string(), - "body": "Here's Q2 the reports ...".to_string() + "from": "alice@example.com", + "to": ["bob@example.com", "fraud@example.com"], + "cc": ["carol@example.com"], + "subject": "Quarterly Reports", + "body": "Here's Q2 the reports ..." }) } @@ -1245,6 +1245,17 @@ mod tests { Ok(()) } + #[test_log::test] + fn test_double_negative() -> TestResult { + let p = Predicate::Not(Box::new(Predicate::Not(Box::new(Predicate::Equal( + Select::from_str(".from").unwrap(), + "alice@example.com".into(), + ))))); + + assert!(p.run(&email())?); + Ok(()) + } + #[test_log::test] fn test_not_fail() -> TestResult { let p = Predicate::Not(Box::new(Predicate::Equal( @@ -1463,5 +1474,89 @@ mod tests { assert!(!p.run(&wasm())?); Ok(()) } + + #[test_log::test] + fn test_deeply_alternate_some_and_every() -> TestResult { + let p = Predicate::Some( + Select::from_str(".a").unwrap(), + Box::new(Predicate::Every( + Select::from_str(".b.c[]").unwrap(), + Box::new(Predicate::Some( + Select::from_str(".d").unwrap(), + Box::new(Predicate::Every( + Select::from_str(".e[]").unwrap(), + Box::new(Predicate::Equal( + Select::from_str(".f.g").unwrap(), + 0.into(), + )), + )), + )), + )), + ); + + let deeply_nested_data = ipld!( + { + // Some + "a": [ + { + "b": { + // Every + "c": { + "c1": { + // Some + "d": [ + { + // Every + "e": { + "e1": { + "f": { + "g": 42 + }, + "nope": -10 + }, + "e2": { + "_": "not selected", + "f": { + "g": 99 + }, + } + } + } + ] + }, + "c2": { + // Some + "*": "avoid", + "_": [ + { + // Every + "e": { + "e1": { + "f": { + "g": 42 + }, + "nope": -10 + }, + "e2": { + "_": "not selected", + "f": { + "g": 99 + }, + } + } + } + ] + } + } + } + } + ], + "z": "doesn't read this" + } + ); + + assert!(p.run(&deeply_nested_data)?); + Ok(()) + } } } diff --git a/src/delegation/policy/selector.rs b/src/delegation/policy/selector.rs index 0489fa5a..c39476c2 100644 --- a/src/delegation/policy/selector.rs +++ b/src/delegation/policy/selector.rs @@ -8,12 +8,9 @@ pub use error::{ParseError, SelectorErrorReason}; pub use select::Select; pub use selectable::Selectable; -use crate::ipld; use filter::Filter; -use libipld_core::ipld::Ipld; use nom::{ self, - branch::alt, bytes::complete::tag, character::complete::char, combinator::map_res, @@ -41,110 +38,22 @@ impl Selector { pub fn is_related(&self, other: &Selector) -> bool { self.0.iter().zip(other.0.iter()).all(|(a, b)| a == b) } - - // pub fn get(&self, ctx: &Ipld) -> Result { - // let ipld: Ipld = self - // .0 - // .iter() - // .try_fold((ctx.clone(), vec![]), |(ipld, mut seen_ops), op| { - // seen_ops.push(op); - // - // match op { - // Filter::Try(inner) => { - // let op: Filter = *inner.clone(); - // let ipld: Ipld = Select::Get::(vec![op]) - // .resolve(ctx) - // .unwrap_or(Ipld::Null); - // - // Ok((ipld, seen_ops)) - // } - // Filter::ArrayIndex(i) => { - // let result = { - // match ipld { - // Ipld::List(xs) => { - // if i.abs() as usize > xs.len() { - // return Err(SelectorError::from_refs( - // &seen_ops, - // SelectorErrorReason::IndexOutOfBounds, - // )); - // }; - // - // xs.get((xs.len() as i32 + *i) as usize) - // .ok_or(SelectorError::from_refs( - // &seen_ops, - // SelectorErrorReason::IndexOutOfBounds, - // )) - // .cloned() - // } - // // FIXME behaviour on maps? type error - // _ => Err(SelectorError::from_refs( - // &seen_ops, - // SelectorErrorReason::NotAList, - // )), - // } - // }; - // - // Ok((result?, seen_ops)) - // } - // Filter::Field(k) => { - // let result = match ipld { - // Ipld::Map(xs) => xs - // .get(k) - // .ok_or(SelectorError::from_refs( - // &seen_ops, - // SelectorErrorReason::KeyNotFound, - // )) - // .cloned(), - // _ => Err(SelectorError::from_refs( - // &seen_ops, - // SelectorErrorReason::NotAMap, - // )), - // }; - // - // Ok((result?, seen_ops)) - // } - // Filter::Values => { - // let result = match ipld { - // Ipld::List(xs) => Ok(Ipld::List(xs)), - // Ipld::Map(xs) => Ok(Ipld::List(xs.values().cloned().collect())), - // _ => Err(SelectorError::from_refs( - // &seen_ops, - // SelectorErrorReason::NotACollection, - // )), - // }; - // - // Ok((result?, seen_ops)) - // } - // } - // })? - // .0; - // - // Ok(ipld::Newtype(ipld)) - // } } pub fn parse(input: &str) -> IResult<&str, Selector> { - let without_this = many1(filter::parse); - let with_this = preceded(char('.'), many0(filter::parse)); + if input.starts_with("..") { + return Err(nom::Err::Error(nom::error::Error::new( + input, + nom::error::ErrorKind::Count, + ))); + } - // NOTE: must try without_this this first, to disambiguate `.field` from `.` - let p = map_res(alt((without_this, with_this)), |found| { - Ok::(Selector(found)) - }); + let with_try_this = preceded(char('.'), preceded(many0(char('?')), many0(filter::parse))); + let p = map_res(with_try_this, |found| Ok::(Selector(found))); context("selector", p)(input) } -pub fn parse_this(input: &str) -> IResult<&str, Selector> { - let p = map_res(tag("."), |_| Ok::(Selector(vec![]))); - context("this", p)(input) -} - -pub fn parse_selector_ops(input: &str) -> IResult<&str, Vec> { - let p = many1(filter::parse); - context("filters", p)(input) -} - impl fmt::Display for Selector { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let mut ops = self.0.iter(); @@ -266,7 +175,9 @@ impl Arbitrary for Selector { #[cfg(test)] mod tests { use super::*; + use pretty_assertions as pretty; use proptest::prelude::*; + use testresult::TestResult; mod serialization { use super::*; @@ -279,5 +190,144 @@ mod tests { prop_assert_eq!(Ok(sel), deserialized); } } + + #[test_log::test] + fn test_bare_dot() -> TestResult { + pretty::assert_eq!(Selector::from_str("."), Ok(Selector(vec![]))); + Ok(()) + } + + #[test_log::test] + fn test_dot_try() -> TestResult { + pretty::assert_eq!(Selector::from_str(".?"), Ok(Selector(vec![]))); + Ok(()) + } + + #[test_log::test] + fn test_dot_many_tries() -> TestResult { + pretty::assert_eq!( + Selector::from_str(".?????????????????????"), + Ok(Selector(vec![])) + ); + Ok(()) + } + + #[test_log::test] + fn test_dot_many_tries_and_dot_field() -> TestResult { + pretty::assert_eq!( + Selector::from_str(".?????????????????????.foo"), + Ok(Selector(vec![Filter::Field("foo".to_string())])) + ); + Ok(()) + } + + #[test_log::test] + fn test_multiple_question_marks() -> TestResult { + pretty::assert_eq!( + Selector::from_str(".foo??????????????"), + Ok(Selector(vec![Filter::Try(Box::new(Filter::Field( + "foo".to_string() + )))])) + ); + Ok(()) + } + + #[test_log::test] + fn test_fails_trailing_dot() -> TestResult { + let got = Selector::from_str(".foo."); + assert!(got.is_err()); + Ok(()) + } + + #[test_log::test] + fn test_fails_leading_double_dot() -> TestResult { + let got = Selector::from_str("..foo"); + assert!(got.is_err()); + Ok(()) + } + + #[test_log::test] + fn test_fails_inner_double_dot() -> TestResult { + let got = Selector::from_str(".foo..bar"); + assert!(got.is_err()); + Ok(()) + } + + #[test_log::test] + fn test_fails_multiple_leading_dots() -> TestResult { + let got = Selector::from_str(".."); + assert!(got.is_err()); + Ok(()) + } + + #[test_log::test] + fn test_fail_missing_leading_dot() -> TestResult { + let got = Selector::from_str("[22]"); + assert!(got.is_err()); + Ok(()) + } + + #[test_log::test] + fn test_dot_field() -> TestResult { + let got = Selector::from_str(".foo"); + pretty::assert_eq!(got, Ok(Selector(vec![Filter::Field("foo".to_string())]))); + Ok(()) + } + + #[test_log::test] + fn test_multiple_dot_fields() -> TestResult { + let got = Selector::from_str(".foo.bar.baz"); + pretty::assert_eq!( + got, + Ok(Selector(vec![ + Filter::Field("foo".to_string()), + Filter::Field("bar".to_string()), + Filter::Field("baz".to_string()) + ])) + ); + Ok(()) + } + + #[test_log::test] + fn test_fairly_complex() -> TestResult { + let got = Selector::from_str(r#".foo.bar[].baz[0][]["42"]._quux?[8]"#); + pretty::assert_eq!( + got, + Ok(Selector(vec![ + Filter::Field("foo".to_string()), + Filter::Field("bar".to_string()), + Filter::Values, + Filter::Field("baz".to_string()), + Filter::ArrayIndex(0), + Filter::Values, + Filter::Field("42".to_string()), + Filter::Try(Box::new(Filter::Field("_quux".to_string()))), + Filter::ArrayIndex(8) + ])) + ); + + Ok(()) + } + + #[test_log::test] + fn test_very_complex() -> TestResult { + let got = Selector::from_str(r#".???.foo.bar[].baz[0][]["42"]._quux??[8]"#); + pretty::assert_eq!( + got, + Ok(Selector(vec![ + Filter::Field("foo".to_string()), + Filter::Field("bar".to_string()), + Filter::Values, + Filter::Field("baz".to_string()), + Filter::ArrayIndex(0), + Filter::Values, + Filter::Field("42".to_string()), + Filter::Try(Box::new(Filter::Field("_quux".to_string()))), + Filter::ArrayIndex(8) + ])) + ); + + Ok(()) + } } } diff --git a/src/delegation/policy/selector/filter.rs b/src/delegation/policy/selector/filter.rs index 016fad93..6f704a7b 100644 --- a/src/delegation/policy/selector/filter.rs +++ b/src/delegation/policy/selector/filter.rs @@ -81,9 +81,11 @@ pub fn parse_non_try(input: &str) -> IResult<&str, Filter> { } pub fn parse_try(input: &str) -> IResult<&str, Filter> { - let p = map_res(terminated(parse_non_try, tag("?")), |found: Filter| { - Ok::(Filter::Try(Box::new(found))) - }); + let p = map_res( + terminated(parse_non_try, many1(tag("?"))), + // FIXME old code: terminated(parse_non_try, tag("?")), + |found: Filter| Ok::(Filter::Try(Box::new(found))), + ); context("try", p)(input) } @@ -241,10 +243,10 @@ impl FromStr for Filter { fn from_str(s: &str) -> Result { match parse(s).map_err(|e| nom::Err::Failure(ParseError::UnknownPattern(e.to_string())))? { - (_, found) => Ok(found), + ("", found) => Ok(found), // ("", found) => Ok(found), // FIXME - // (rest, _) => Err(nom::Err::Failure(ParseError::TrailingInput(rest.into()))), + (rest, _) => Err(nom::Err::Failure(ParseError::TrailingInput(rest.into()))), } } } @@ -281,7 +283,9 @@ impl Arbitrary for Filter { #[cfg(test)] mod tests { use super::*; + use pretty_assertions as pretty; use proptest::prelude::*; + use testresult::TestResult; mod serialization { use super::*; @@ -294,5 +298,222 @@ mod tests { prop_assert_eq!(Ok(filter), deserialized); } } + + #[test_log::test] + fn test_fails_on_empty() -> TestResult { + let got = Filter::from_str(""); + assert!(got.is_err()); + Ok(()) + } + + #[test_log::test] + fn test_fails_on_bare_dot() -> TestResult { + // NOTE this passes as a Selector, but not a Filter + let got = Filter::from_str("."); + assert!(got.is_err()); + Ok(()) + } + + #[test_log::test] + fn test_fails_on_multiple_bare_dots() -> TestResult { + let got = Filter::from_str(".."); + assert!(got.is_err()); + Ok(()) + } + + #[test_log::test] + fn test_fails_on_leading_dots() -> TestResult { + let got = Filter::from_str("..foo"); + assert!(got.is_err()); + Ok(()) + } + + #[test_log::test] + fn test_fails_on_empty_whitespace() -> TestResult { + let got = Filter::from_str(" "); + assert!(got.is_err()); + Ok(()) + } + + #[test_log::test] + fn test_fails_leading_whitespace() -> TestResult { + let got = Filter::from_str(" .foo"); + assert!(got.is_err()); + Ok(()) + } + + #[test_log::test] + fn test_fails_trailing_whitespace() -> TestResult { + let got = Filter::from_str(".foo "); + assert!(got.is_err()); + Ok(()) + } + + #[test_log::test] + fn test_values() -> TestResult { + let got = Filter::from_str("[]"); + pretty::assert_eq!(got, Ok(Filter::Values)); + Ok(()) + } + + #[test_log::test] + fn test_values_fails_inner_whitespace() -> TestResult { + let got = Filter::from_str("[ ]"); + pretty::assert_eq!(got.is_err(), true); + Ok(()) + } + + #[test_log::test] + fn test_array_index_zero() -> TestResult { + let got = Filter::from_str("[0]"); + pretty::assert_eq!(got, Ok(Filter::ArrayIndex(0))); + Ok(()) + } + + #[test_log::test] + fn test_array_index_small() -> TestResult { + let got = Filter::from_str("[2]"); + pretty::assert_eq!(got, Ok(Filter::ArrayIndex(2))); + Ok(()) + } + + #[test_log::test] + fn test_array_index_large() -> TestResult { + let got = Filter::from_str("[1234567890]"); + pretty::assert_eq!(got, Ok(Filter::ArrayIndex(1234567890))); + Ok(()) + } + + #[test_log::test] + fn test_array_from_end() -> TestResult { + let got = Filter::from_str("[-42]"); + pretty::assert_eq!(got, Ok(Filter::ArrayIndex(-42))); + Ok(()) + } + + #[test_log::test] + fn test_array_fails_spaces() -> TestResult { + let got = Filter::from_str("[ 42]"); + assert!(got.is_err()); + Ok(()) + } + + #[test_log::test] + fn test_dot_field() -> TestResult { + let got = Filter::from_str(".F0o"); + pretty::assert_eq!(got, Ok(Filter::Field("F0o".to_string()))); + Ok(()) + } + + #[test_log::test] + fn test_dot_field_starting_underscore() -> TestResult { + let got = Filter::from_str("._foo"); + pretty::assert_eq!(got, Ok(Filter::Field("_foo".to_string()))); + Ok(()) + } + + #[test_log::test] + fn test_dot_field_trailing_underscore() -> TestResult { + let got = Filter::from_str(".fO0_"); + pretty::assert_eq!(got, Ok(Filter::Field("fO0_".to_string()))); + Ok(()) + } + + #[test_log::test] + fn test_fails_dot_field_with_leading_number() -> TestResult { + let got = Filter::from_str(".1foo"); + assert!(got.is_err()); + Ok(()) + } + + #[test_log::test] + fn test_fails_dot_field_with_inner_symbol() -> TestResult { + let got = Filter::from_str(".fo%o"); + assert!(got.is_err()); + Ok(()) + } + + #[test_log::test] + fn test_delim_field() -> TestResult { + let got = Filter::from_str(r#"["F0o"]"#); + pretty::assert_eq!(got, Ok(Filter::Field("F0o".to_string()))); + Ok(()) + } + + #[test_log::test] + fn test_delim_field_fails_without_quotes() -> TestResult { + let got = Filter::from_str(r#"[F0o]"#); + assert!(got.is_err()); + Ok(()) + } + + #[test_log::test] + fn test_delim_field_fails_if_missing_right_brace() -> TestResult { + let got = Filter::from_str(r#"["F0o""#); + assert!(got.is_err()); + Ok(()) + } + + #[test_log::test] + fn test_delim_field_starting_underscore() -> TestResult { + let got = Filter::from_str(r#"["_foo"]"#); + pretty::assert_eq!(got, Ok(Filter::Field("_foo".to_string()))); + Ok(()) + } + + #[test_log::test] + fn test_delim_field_trailing_underscore() -> TestResult { + let got = Filter::from_str(r#"["fO0_"]"#); + pretty::assert_eq!(got, Ok(Filter::Field("fO0_".to_string()))); + Ok(()) + } + + #[test_log::test] + fn test_delim_field_with_leading_number() -> TestResult { + let got = Filter::from_str(r#"["1foo"]"#); + pretty::assert_eq!(got, Ok(Filter::Field("1foo".to_string()))); + Ok(()) + } + + #[test_log::test] + fn test_delim_field_with_inner_symbol() -> TestResult { + let got = Filter::from_str(r#"[".fo%o"]"#); + pretty::assert_eq!(got, Ok(Filter::Field(".fo%o".to_string()))); + Ok(()) + } + + #[test_log::test] + fn test_try() -> TestResult { + let got = Filter::from_str(".foo?"); + pretty::assert_eq!( + got, + Ok(Filter::Try(Box::new(Filter::Field("foo".to_string())))) + ); + Ok(()) + } + + #[test_log::test] + fn test_multiple_tries() -> TestResult { + let got = Filter::from_str(".foo???????????????????"); + pretty::assert_eq!( + got, + Ok(Filter::Try(Box::new(Filter::Field("foo".to_string())))) + ); + Ok(()) + } + + #[test_log::test] + fn test_fails_bare_try() -> TestResult { + let got = Filter::from_str("?"); + assert!(got.is_err()); + Ok(()) + } + + #[test_log::test] + fn test_fails_dot_try() -> TestResult { + let got = Filter::from_str(".?"); + assert!(got.is_err()); + Ok(()) + } } } From c949a4f007e5b1573e3d3ba485ea97f356b84838 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philipp=20Kr=C3=BCger?= Date: Thu, 21 Mar 2024 23:54:46 +0100 Subject: [PATCH 165/188] refactor: Adjustments to `Store`s & `Agent`s (#9) * refactor: Make delegation::Store::get return Option * refactor: Make delegation::Store::insert take &self instead of &mut self * refactor: Make invocation::Store take &self instead of &mut self * refactor: Make `delegation::Agent` not take `&mut self` in methods * refactor: Make `Agent` take `DID` by value * refactor: Take `DID` by value in `delegation::Agent::new` * refactor: Change generic order in `delegation::Agent` and add defaults --- src/delegation/agent.rs | 41 ++++++------- src/delegation/store/memory.rs | 100 +++++++++++++++++++++++-------- src/delegation/store/traits.rs | 36 +++++++----- src/invocation/agent.rs | 104 ++++++++++++++++----------------- src/invocation/store.rs | 65 ++++++++++++++++----- 5 files changed, 217 insertions(+), 129 deletions(-) diff --git a/src/delegation/agent.rs b/src/delegation/agent.rs index 89f86a8b..bfee4119 100644 --- a/src/delegation/agent.rs +++ b/src/delegation/agent.rs @@ -1,5 +1,6 @@ use super::{payload::Payload, policy::Predicate, store::Store, Delegation}; use crate::ability::arguments::Named; +use crate::did; use crate::{ crypto::{signature::Envelope, varsig, Nonce}, did::Did, @@ -14,40 +15,38 @@ use std::{collections::BTreeMap, marker::PhantomData}; use thiserror::Error; use web_time::SystemTime; -/// A stateful agent capable of delegatint to others, and being delegated to. +/// A stateful agent capable of delegating to others, and being delegated to. /// /// This is helpful for sessions where more than one delegation will be made. #[derive(Debug)] pub struct Agent< - 'a, - DID: Did, S: Store, - V: varsig::Header, - Enc: Codec + TryFrom + Into, + DID: Did = did::preset::Verifier, + V: varsig::Header + Clone = varsig::header::Preset, + Enc: Codec + Into + TryFrom = varsig::encoding::Preset, > { /// The [`Did`][Did] of the agent. - pub did: &'a DID, + pub did: DID, /// The attached [`deleagtion::Store`][super::store::Store]. - pub store: &'a mut S, + pub store: S, - signer: &'a ::Signer, + signer: ::Signer, _marker: PhantomData<(V, Enc)>, } impl< - 'a, - DID: Did + Clone, S: Store + Clone, + DID: Did + Clone, V: varsig::Header + Clone, Enc: Codec + TryFrom + Into, - > Agent<'a, DID, S, V, Enc> + > Agent where Ipld: Encode, Payload: TryFrom>, Named: From>, { - pub fn new(did: &'a DID, signer: &'a ::Signer, store: &'a mut S) -> Self { + pub fn new(did: DID, signer: ::Signer, store: S) -> Self { Self { did, store, @@ -73,7 +72,7 @@ where let nonce = Nonce::generate_12(&mut salt); if let Some(ref sub) = subject { - if sub == self.did { + if sub == &self.did { let payload: Payload = Payload { issuer: self.did.clone(), audience, @@ -88,19 +87,17 @@ where }; return Ok( - Delegation::try_sign(self.signer, varsig_header, payload).expect("FIXME") + Delegation::try_sign(&self.signer, varsig_header, payload).expect("FIXME") ); } } - let to_delegate = &self + let proofs = &self .store .get_chain(&self.did, &subject, "/".into(), vec![], now) .map_err(DelegateError::StoreError)? - .ok_or(DelegateError::ProofsNotFound)? - .first() - .1 - .payload(); + .ok_or(DelegateError::ProofsNotFound)?; + let to_delegate = proofs.first().1.payload(); let mut policy = to_delegate.policy.clone(); policy.append(&mut new_policy.clone()); @@ -118,11 +115,11 @@ where not_before: not_before.map(Into::into), }; - Ok(Delegation::try_sign(self.signer, varsig_header, payload).expect("FIXME")) + Ok(Delegation::try_sign(&self.signer, varsig_header, payload).expect("FIXME")) } pub fn receive( - &mut self, + &self, cid: Cid, // FIXME remove and generate from the capsule header? delegation: Delegation, ) -> Result<(), ReceiveError> { @@ -130,7 +127,7 @@ where return Ok(()); } - if delegation.audience() != self.did { + if delegation.audience() != &self.did { return Err(ReceiveError::WrongAudience(delegation.audience().clone())); } diff --git a/src/delegation/store/memory.rs b/src/delegation/store/memory.rs index 18d86103..c36a6e2d 100644 --- a/src/delegation/store/memory.rs +++ b/src/delegation/store/memory.rs @@ -10,7 +10,11 @@ use libipld_core::codec::Encode; use libipld_core::ipld::Ipld; use libipld_core::{cid::Cid, codec::Codec}; use nonempty::NonEmpty; -use std::collections::{BTreeMap, BTreeSet}; +use std::sync::{Arc, Mutex, RwLock, RwLockReadGuard, RwLockWriteGuard}; +use std::{ + collections::{BTreeMap, BTreeSet}, + convert::Infallible, +}; use web_time::SystemTime; #[cfg_attr(doc, aquamarine::aquamarine)] @@ -69,36 +73,77 @@ use web_time::SystemTime; /// linkStyle 6 stroke:orange; /// linkStyle 1 stroke:orange; /// ``` -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Clone)] pub struct MemoryStore< DID: did::Did + Ord = did::preset::Verifier, V: varsig::Header = varsig::header::Preset, C: Codec + TryFrom + Into = varsig::encoding::Preset, > { - ucans: BTreeMap>, + inner: Arc>>, +} + +#[derive(Debug, Clone, PartialEq)] +struct MemoryStoreInner< + DID: did::Did + Ord = did::preset::Verifier, + V: varsig::Header = varsig::header::Preset, + C: Codec + TryFrom + Into = varsig::encoding::Preset, +> { + ucans: BTreeMap>>, index: BTreeMap, BTreeMap>>, revocations: BTreeSet, } -impl MemoryStore { +impl, C: Codec + TryFrom + Into> + MemoryStore +{ pub fn new() -> Self { Self::default() } pub fn len(&self) -> usize { - self.ucans.len() + self.read().ucans.len() } pub fn is_empty(&self) -> bool { - self.ucans.is_empty() // FIXME acocunt for revocations? + self.read().ucans.is_empty() // FIXME acocunt for revocations? + } + + fn read(&self) -> RwLockReadGuard<'_, MemoryStoreInner> { + match self.inner.read() { + Ok(guard) => guard, + Err(poison) => { + // We ignore lock poisoning for simplicity + poison.into_inner() + } + } + } + + fn write(&self) -> RwLockWriteGuard<'_, MemoryStoreInner> { + match self.inner.write() { + Ok(guard) => guard, + Err(poison) => { + // We ignore lock poisoning for simplicity + poison.into_inner() + } + } } } -impl, C: Codec + TryFrom + Into> Default +impl, C: Codec + TryFrom + Into> Default for MemoryStore { fn default() -> Self { - MemoryStore { + Self { + inner: Default::default(), + } + } +} + +impl, C: Codec + TryFrom + Into> Default + for MemoryStoreInner +{ + fn default() -> Self { + MemoryStoreInner { ucans: BTreeMap::new(), index: BTreeMap::new(), revocations: BTreeSet::new(), @@ -117,34 +162,39 @@ where delegation::Payload: TryFrom>, Delegation: Encode, { - type DelegationStoreError = String; // FIXME misisng + type DelegationStoreError = Infallible; - fn get(&self, cid: &Cid) -> Result<&Delegation, Self::DelegationStoreError> { - self.ucans - .get(cid) - .ok_or(format!("not found in delegation memstore: {:?}", cid).into()) + fn get( + &self, + cid: &Cid, + ) -> Result>>, Self::DelegationStoreError> { + // cheap Arc clone + Ok(self.read().ucans.get(cid).cloned()) // FIXME } fn insert( - &mut self, + &self, cid: Cid, delegation: Delegation, ) -> Result<(), Self::DelegationStoreError> { - self.index + let mut write_tx = self.write(); + + write_tx + .index .entry(delegation.subject().clone()) .or_default() .entry(delegation.audience().clone()) .or_default() .insert(cid); - self.ucans.insert(cid.clone(), delegation); + write_tx.ucans.insert(cid.clone(), Arc::new(delegation)); Ok(()) } - fn revoke(&mut self, cid: Cid) -> Result<(), Self::DelegationStoreError> { - self.revocations.insert(cid); + fn revoke(&self, cid: Cid) -> Result<(), Self::DelegationStoreError> { + self.write().revocations.insert(cid); Ok(()) } @@ -155,12 +205,14 @@ where command: String, policy: Vec, // FIXME now: SystemTime, - ) -> Result)>>, Self::DelegationStoreError> { + ) -> Result>)>>, Self::DelegationStoreError> + { let blank_set = BTreeSet::new(); let blank_map = BTreeMap::new(); + let read_tx = self.read(); - let all_powerlines = self.index.get(&None).unwrap_or(&blank_map); - let all_aud_for_subject = self.index.get(subject).unwrap_or(&blank_map); + let all_powerlines = read_tx.index.get(&None).unwrap_or(&blank_map); + let all_aud_for_subject = read_tx.index.get(subject).unwrap_or(&blank_map); let powerline_candidates = all_powerlines.get(aud).unwrap_or(&blank_set); let sub_candidates = all_aud_for_subject.get(aud).unwrap_or(&blank_set); @@ -185,11 +237,11 @@ where } 'inner: for cid in parent_cid_candidates { - if self.revocations.contains(cid) { + if read_tx.revocations.contains(cid) { continue; } - if let Some(delegation) = self.ucans.get(cid) { + if let Some(delegation) = read_tx.ucans.get(cid) { if delegation.check_time(now).is_err() { continue; } @@ -217,7 +269,7 @@ where } } - hypothesis_chain.push((cid.clone(), delegation)); + hypothesis_chain.push((cid.clone(), Arc::clone(delegation))); let issuer = delegation.issuer().clone(); diff --git a/src/delegation/store/traits.rs b/src/delegation/store/traits.rs index 3bd7b1fb..7413e700 100644 --- a/src/delegation/store/traits.rs +++ b/src/delegation/store/traits.rs @@ -5,16 +5,19 @@ use crate::{ }; use libipld_core::{cid::Cid, codec::Codec}; use nonempty::NonEmpty; -use std::fmt::Debug; +use std::{fmt::Debug, sync::Arc}; use web_time::SystemTime; pub trait Store, Enc: Codec + TryFrom + Into> { type DelegationStoreError: Debug; - fn get(&self, cid: &Cid) -> Result<&Delegation, Self::DelegationStoreError>; + fn get( + &self, + cid: &Cid, + ) -> Result>>, Self::DelegationStoreError>; fn insert( - &mut self, + &self, cid: Cid, delegation: Delegation, ) -> Result<(), Self::DelegationStoreError>; @@ -22,7 +25,7 @@ pub trait Store, Enc: Codec + TryFrom + In // FIXME validate invocation // store invocation // just... move to invocation - fn revoke(&mut self, cid: Cid) -> Result<(), Self::DelegationStoreError>; + fn revoke(&self, cid: Cid) -> Result<(), Self::DelegationStoreError>; fn get_chain( &self, @@ -31,7 +34,7 @@ pub trait Store, Enc: Codec + TryFrom + In command: String, policy: Vec, now: SystemTime, - ) -> Result)>>, Self::DelegationStoreError>; + ) -> Result>)>>, Self::DelegationStoreError>; fn get_chain_cids( &self, @@ -60,32 +63,34 @@ pub trait Store, Enc: Codec + TryFrom + In fn get_many( &self, cids: &[Cid], - ) -> Result>, Self::DelegationStoreError> { - cids.iter().try_fold(vec![], |mut acc, cid| { - acc.push(self.get(cid)?); - Ok(acc) - }) + ) -> Result>>>, Self::DelegationStoreError> { + cids.iter() + .map(|cid| self.get(cid)) + .collect::>() } } impl, DID: Did, V: varsig::Header, C: Codec + TryFrom + Into> - Store for &mut T + Store for &T { type DelegationStoreError = >::DelegationStoreError; - fn get(&self, cid: &Cid) -> Result<&Delegation, Self::DelegationStoreError> { + fn get( + &self, + cid: &Cid, + ) -> Result>>, Self::DelegationStoreError> { (**self).get(cid) } fn insert( - &mut self, + &self, cid: Cid, delegation: Delegation, ) -> Result<(), Self::DelegationStoreError> { (**self).insert(cid, delegation) } - fn revoke(&mut self, cid: Cid) -> Result<(), Self::DelegationStoreError> { + fn revoke(&self, cid: Cid) -> Result<(), Self::DelegationStoreError> { (**self).revoke(cid) } @@ -96,7 +101,8 @@ impl, DID: Did, V: varsig::Header, C: Codec + TryFrom, now: SystemTime, - ) -> Result)>>, Self::DelegationStoreError> { + ) -> Result>)>>, Self::DelegationStoreError> + { (**self).get_chain(audience, subject, command, policy, now) } } diff --git a/src/invocation/agent.rs b/src/invocation/agent.rs index a4e5f7fd..11838bde 100644 --- a/src/invocation/agent.rs +++ b/src/invocation/agent.rs @@ -27,13 +27,13 @@ use std::{ collections::{BTreeMap, BTreeSet}, fmt, marker::PhantomData, + sync::Arc, }; use thiserror::Error; use web_time::SystemTime; #[derive(Debug)] pub struct Agent< - 'a, S: Store, D: delegation::store::Store, T: ToCommand = ability::preset::Preset, @@ -42,7 +42,7 @@ pub struct Agent< C: Codec + Into + TryFrom = varsig::encoding::Preset, > { /// The agent's [`DID`]. - pub did: &'a DID, + pub did: DID, /// A [`delegation::Store`][delegation::store::Store]. pub delegation_store: D, @@ -50,11 +50,11 @@ pub struct Agent< /// A [`Store`][Store] for the agent's [`Invocation`]s. pub invocation_store: S, - signer: &'a ::Signer, + signer: ::Signer, marker: PhantomData<(T, V, C)>, } -impl<'a, T, DID, S, D, V, C> Agent<'a, S, D, T, DID, V, C> +impl Agent where Ipld: Encode, T: ToCommand + Clone + ParseAbility, @@ -70,8 +70,8 @@ where >::DelegationStoreError: fmt::Debug, { pub fn new( - did: &'a DID, - signer: &'a ::Signer, + did: DID, + signer: ::Signer, invocation_store: S, delegation_store: D, ) -> Self { @@ -85,7 +85,7 @@ where } pub fn invoke( - &mut self, + &self, audience: Option, subject: DID, ability: T, @@ -171,7 +171,7 @@ where // } pub fn receive( - &mut self, + &self, invocation: Invocation, ) -> Result>, ReceiveError> where @@ -183,7 +183,7 @@ where } pub fn generic_receive( - &mut self, + &self, invocation: Invocation, now: SystemTime, ) -> Result>, ReceiveError> @@ -204,20 +204,27 @@ where .put(cid.clone(), invocation.clone()) .map_err(ReceiveError::InvocationStoreError)?; - let proof_payloads: Vec<&delegation::Payload> = self + let proofs = &self .delegation_store .get_many(&invocation.proofs()) - .map_err(ReceiveError::DelegationStoreError)? + .map_err(ReceiveError::DelegationStoreError)?; + let proof_payloads: Vec<&delegation::Payload> = proofs .iter() - .map(|d| &d.payload) - .collect(); + .zip(invocation.proofs().iter()) + .map(|(d, cid)| { + Ok(&d + .as_ref() + .ok_or(ReceiveError::MissingDelegation(*cid))? + .payload) + }) + .collect::>>()?; let _ = &invocation .payload .check(proof_payloads, now) .map_err(ReceiveError::ValidationError)?; - Ok(if invocation.normalized_audience() != self.did { + Ok(if invocation.normalized_audience() != &self.did { Recipient::Other(invocation.payload) } else { Recipient::You(invocation.payload) @@ -225,7 +232,7 @@ where } // pub fn revoke( - // &mut self, + // &self, // subject: DID, // cause: Option, // cid: Cid, @@ -290,6 +297,9 @@ pub enum ReceiveError< > where >::InvocationStoreError: fmt::Debug, { + #[error("missing delegation: {0}")] + MissingDelegation(Cid), + #[error("encoding error: {0}")] EncodingError(#[from] libipld_core::error::Error), @@ -397,11 +407,9 @@ mod tests { (verifier, signer) } - fn setup_agent<'a>( - did: &'a crate::did::preset::Verifier, - signer: &'a crate::did::preset::Signer, - ) -> Agent<'a, crate::invocation::store::MemoryStore, crate::delegation::store::MemoryStore> - { + fn setup_agent( + ) -> Agent { + let (did, signer) = gen_did(); let inv_store = crate::invocation::store::MemoryStore::default(); let del_store = crate::delegation::store::MemoryStore::default(); @@ -427,8 +435,7 @@ mod tests { #[test_log::test] fn test_invoker_is_sub_implicit_aud() -> TestResult { let (_nbf, now, exp) = setup_valid_time(); - let (server, server_signer) = gen_did(); - let mut agent = setup_agent(&server, &server_signer); + let mut agent = setup_agent(); let invocation = agent.invoke( None, @@ -456,8 +463,7 @@ mod tests { #[test_log::test] fn test_invoker_is_sub_and_aud() -> TestResult { let (_nbf, now, exp) = setup_valid_time(); - let (server, server_signer) = gen_did(); - let mut agent = setup_agent(&server, &server_signer); + let mut agent = setup_agent(); let invocation = agent.invoke( Some(agent.did.clone()), @@ -486,8 +492,7 @@ mod tests { #[test_log::test] fn test_other_recipient() -> TestResult { let (_nbf, now, exp) = setup_valid_time(); - let (server, server_signer) = gen_did(); - let mut agent = setup_agent(&server, &server_signer); + let mut agent = setup_agent(); let (not_server, _) = gen_did(); @@ -517,8 +522,7 @@ mod tests { #[test_log::test] fn test_expired() -> TestResult { let (past, now, _exp) = setup_valid_time(); - let (server, server_signer) = gen_did(); - let mut agent = setup_agent(&server, &server_signer); + let mut agent = setup_agent(); let invocation = agent.invoke( None, @@ -553,8 +557,8 @@ mod tests { #[test_log::test] fn test_invalid_sig() -> TestResult { let (_past, now, _exp) = setup_valid_time(); - let (server, server_signer) = gen_did(); - let mut agent = setup_agent(&server, &server_signer); + let mut agent = setup_agent(); + let server = &agent.did; let mut invocation = agent.invoke( None, @@ -624,7 +628,7 @@ mod tests { ); let inv_store = crate::invocation::store::MemoryStore::default(); - let mut del_store = crate::delegation::store::MemoryStore::default(); + let del_store = crate::delegation::store::MemoryStore::default(); // Scenario // ======== @@ -739,18 +743,13 @@ mod tests { #[test_log::test] fn test_chain_ok() -> TestResult { - let mut ctx = setup_test_chain()?; - - let mut agent: Agent< - '_, - &mut crate::invocation::store::MemoryStore, - &mut crate::delegation::store::MemoryStore, - AccountManage, - > = Agent::new( - &ctx.server, - &ctx.server_signer, - &mut ctx.inv_store, - &mut ctx.del_store, + let ctx = setup_test_chain()?; + + let mut agent = Agent::new( + ctx.server.clone(), + ctx.server_signer.clone(), + &ctx.inv_store, + &ctx.del_store, ); let observed = agent.receive(ctx.account_invocation.clone()); @@ -760,18 +759,13 @@ mod tests { #[test_log::test] fn test_chain_wrong_sub() -> TestResult { - let mut ctx = setup_test_chain()?; - - let mut agent: Agent< - '_, - &mut crate::invocation::store::MemoryStore, - &mut crate::delegation::store::MemoryStore, - AccountManage, - > = Agent::new( - &ctx.server, - &ctx.server_signer, - &mut ctx.inv_store, - &mut ctx.del_store, + let ctx = setup_test_chain()?; + + let mut agent = Agent::new( + ctx.server.clone(), + ctx.server_signer.clone(), + &ctx.inv_store, + &ctx.del_store, ); let not_account_invocation = crate::Invocation::try_sign( diff --git a/src/invocation/store.rs b/src/invocation/store.rs index 26618909..4c9279fa 100644 --- a/src/invocation/store.rs +++ b/src/invocation/store.rs @@ -4,6 +4,7 @@ use super::Invocation; use crate::ability; use crate::{crypto::varsig, did::Did}; use libipld_core::{cid::Cid, codec::Codec}; +use std::sync::{Arc, RwLock, RwLockReadGuard, RwLockWriteGuard}; use std::{collections::BTreeMap, convert::Infallible}; pub trait Store, C: Codec + Into + TryFrom> { @@ -12,10 +13,10 @@ pub trait Store, C: Codec + Into + TryFro fn get( &self, cid: Cid, - ) -> Result>, Self::InvocationStoreError>; + ) -> Result>>, Self::InvocationStoreError>; fn put( - &mut self, + &self, cid: Cid, invocation: Invocation, ) -> Result<(), Self::InvocationStoreError>; @@ -31,20 +32,22 @@ impl< DID: Did, V: varsig::Header, C: Codec + Into + TryFrom, - > Store for &mut S + > Store for &S { type InvocationStoreError = >::InvocationStoreError; fn get( &self, cid: Cid, - ) -> Result>, >::InvocationStoreError> - { + ) -> Result< + Option>>, + >::InvocationStoreError, + > { (**self).get(cid) } fn put( - &mut self, + &self, cid: Cid, invocation: Invocation, ) -> Result<(), >::InvocationStoreError> { @@ -52,14 +55,48 @@ impl< } } -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Clone)] pub struct MemoryStore< T = crate::ability::preset::Preset, DID: crate::did::Did = crate::did::preset::Verifier, V: varsig::Header = varsig::header::Preset, C: Codec + TryFrom + Into = varsig::encoding::Preset, > { - store: BTreeMap>, + inner: Arc>>, +} + +#[derive(Debug, Clone, PartialEq)] +pub struct MemoryStoreInner< + T = crate::ability::preset::Preset, + DID: crate::did::Did = crate::did::preset::Verifier, + V: varsig::Header = varsig::header::Preset, + C: Codec + TryFrom + Into = varsig::encoding::Preset, +> { + store: BTreeMap>>, +} + +impl, Enc: Codec + Into + TryFrom> + MemoryStore +{ + fn read(&self) -> RwLockReadGuard<'_, MemoryStoreInner> { + match self.inner.read() { + Ok(guard) => guard, + Err(poison) => { + // There's no logic errors through lock poisoning in our case + poison.into_inner() + } + } + } + + fn write(&self) -> RwLockWriteGuard<'_, MemoryStoreInner> { + match self.inner.write() { + Ok(guard) => guard, + Err(poison) => { + // There's no logic errors through lock poisoning in our case + poison.into_inner() + } + } + } } impl, Enc: Codec + Into + TryFrom> Default @@ -67,7 +104,9 @@ impl, Enc: Codec + Into + TryFrom> { fn default() -> Self { Self { - store: BTreeMap::new(), + inner: Arc::new(RwLock::new(MemoryStoreInner { + store: BTreeMap::new(), + })), } } } @@ -80,16 +119,16 @@ impl, Enc: Codec + Into + TryFrom> fn get( &self, cid: Cid, - ) -> Result>, Self::InvocationStoreError> { - Ok(self.store.get(&cid)) + ) -> Result>>, Self::InvocationStoreError> { + Ok(self.read().store.get(&cid).cloned()) } fn put( - &mut self, + &self, cid: Cid, invocation: Invocation, ) -> Result<(), Self::InvocationStoreError> { - self.store.insert(cid, invocation); + self.write().store.insert(cid, Arc::new(invocation)); Ok(()) } } From 3503a328e9f43e3eb2db8e619e4a52519c08be49 Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Mon, 18 Mar 2024 16:43:50 -0700 Subject: [PATCH 166/188] Fairly complete predicate tests --- src/delegation/policy/predicate.rs | 233 +++++++++++++++++++++-- src/delegation/policy/selector.rs | 24 +-- src/delegation/policy/selector/error.rs | 3 + src/delegation/policy/selector/filter.rs | 101 +++++++++- 4 files changed, 317 insertions(+), 44 deletions(-) diff --git a/src/delegation/policy/predicate.rs b/src/delegation/policy/predicate.rs index a9f5b1f5..423d9303 100644 --- a/src/delegation/policy/predicate.rs +++ b/src/delegation/policy/predicate.rs @@ -67,6 +67,7 @@ impl Harmonization { } impl Predicate { + // FIXME make &self? pub fn run(self, data: &Ipld) -> Result { Ok(match self { Predicate::Equal(lhs, rhs_data) => lhs.get(data)? == rhs_data, @@ -78,21 +79,22 @@ impl Predicate { Predicate::Not(inner) => !inner.run(data)?, Predicate::And(lhs, rhs) => lhs.run(data)? && rhs.run(data)?, Predicate::Or(lhs, rhs) => lhs.run(data)? || rhs.run(data)?, - Predicate::Every(xs, p) => xs - .get(data)? - .to_vec() - .iter() - .try_fold(true, |acc, each_datum| { - Ok(acc && p.clone().run(&each_datum.0)?) - })?, + Predicate::Every(xs, p) => { + xs.get(data)? + .to_vec() + .iter() + .try_fold(true, |acc, each_datum| { + dbg!("every", &p, acc, each_datum); + Ok(acc && p.clone().run(&each_datum.0)?) + })? + } Predicate::Some(xs, p) => { - let pred = p.clone(); - xs.get(data)? .to_vec() .iter() .try_fold(false, |acc, each_datum| { - Ok(acc || pred.clone().run(&each_datum.0)?) + dbg!("some", &p, acc, each_datum); + Ok(acc || p.clone().run(&each_datum.0)?) })? } }) @@ -1116,6 +1118,58 @@ mod tests { Ok(()) } + #[test_log::test] + fn test_eq_try_null() -> TestResult { + let p = Predicate::Equal(Select::from_str(".not_from?").unwrap(), Ipld::Null.into()); + + assert!(p.run(&email())?); + Ok(()) + } + + #[test_log::test] + fn test_eq_dot_field_ending_try_null() -> TestResult { + let p = Predicate::Equal(Select::from_str(".from.not?").unwrap(), Ipld::Null.into()); + + assert!(p.run(&email())?); + Ok(()) + } + + #[test_log::test] + fn test_eq_dot_field_inner_try_null() -> TestResult { + // FIXME double check against jq + let p = Predicate::Equal(Select::from_str(".nope?.not").unwrap(), Ipld::Null.into()); + + assert!(p.run(&email())?); + Ok(()) + } + + #[test_log::test] + fn test_eq_root_try_not_null() -> TestResult { + let p = Predicate::Equal(Select::from_str(".?").unwrap(), Ipld::Null.into()); + + assert!(!p.run(&email())?); + Ok(()) + } + + #[test_log::test] + fn test_eq_try_not_null() -> TestResult { + let p = Predicate::Equal( + Select::from_str(".from?").unwrap(), + "alice@example.com".into(), + ); + + assert!(p.run(&email())?); + Ok(()) + } + + #[test_log::test] + fn test_eq_nested_try_null() -> TestResult { + let p = Predicate::Equal(Select::from_str(".from?.not?").unwrap(), Ipld::Null.into()); + + assert!(p.run(&email())?); + Ok(()) + } + #[test_log::test] fn test_eq_fail_same_type() -> TestResult { let p = Predicate::Equal(Select::from_str(".from").unwrap(), "NOPE".into()); @@ -1475,8 +1529,155 @@ mod tests { Ok(()) } + #[test_log::test] + fn test_alternate_every_and_some() -> TestResult { + // ["every", ".a", ["some", ".b[]", ["==", ".", 0]]] + let p = Predicate::Every( + Select::from_str(".a").unwrap(), + Box::new(Predicate::Some( + Select::from_str(".b[]").unwrap(), + Box::new(Predicate::Equal(Select::from_str(".").unwrap(), 0.into())), + )), + ); + + let nested_data = ipld!( + { + "a": [ + { + "b": { + "c": 0, // Yep + "d": 0, // Yep + "e": 1 // Nope, but ok because "some" + }, + "not-b": "ignore" + }, + { + "also-not-b": "ignore", + "b": [-1, 0, 1] + } + ] + } + ); + + assert!(p.run(&nested_data)?); + Ok(()) + } + + #[test_log::test] + fn test_alternate_fail_every_and_some() -> TestResult { + // ["every", ".a", ["some", ".b[]", ["==", ".", 0]]] + let p = Predicate::Every( + Select::from_str(".a").unwrap(), + Box::new(Predicate::Some( + Select::from_str(".b[]").unwrap(), + Box::new(Predicate::Equal(Select::from_str(".").unwrap(), 0.into())), + )), + ); + + let nested_data = ipld!( + { + "a": [ + { + "b": { + "c": 0, // Yep + "d": 0, // Yep + "e": 1 // Nope, but ok because "some" + }, + "not-b": "ignore" + }, + { + "also-not-b": "ignore", + "b": [-1, 42, 1] // No 0, so fail "every" + } + ] + } + ); + + assert!(!p.run(&nested_data)?); + Ok(()) + } + + // FIXME + #[test_log::test] + fn test_alternate_some_and_every() -> TestResult { + // ["some", ".a", ["every", ".b[]", ["==", ".", 0]]] + let p = Predicate::Some( + Select::from_str(".a").unwrap(), + Box::new(Predicate::Every( + Select::from_str(".b[]").unwrap(), + Box::new(Predicate::Equal(Select::from_str(".").unwrap(), 0.into())), + )), + ); + + let nested_data = ipld!( + { + "a": [ + { + "b": { + "c": 0, // Yep + "d": 0, // Yep + "e": 1 // Nope, so fail this every, but... + }, + "not-b": "ignore" + }, + { + "also-not-b": "ignore", + "b": [0, 0, 0] // This every succeeds, so the outer "some" succeeds + } + ] + } + ); + + assert!(p.run(&nested_data)?); + Ok(()) + } + + // FIXME + #[test_log::test] + fn test_alternate_fail_some_and_every() -> TestResult { + // ["some", ".a", ["every", ".b[]", ["==", ".", 0]]] + let p = Predicate::Some( + Select::from_str(".a").unwrap(), + Box::new(Predicate::Every( + Select::from_str(".b[]").unwrap(), + Box::new(Predicate::Equal(Select::from_str(".").unwrap(), 0.into())), + )), + ); + + let nested_data = ipld!( + { + "a": [ + { + "b": { + "c": 0, // Yep + "d": 0, // Yep + "e": 1 // Nope + }, + "not-b": "ignore" + }, + { + "also-not-b": "ignore", + "b": [-1, 42, 1] // Also nope, so fail + } + ] + } + ); + + assert!(!p.run(&nested_data)?); + Ok(()) + } + #[test_log::test] fn test_deeply_alternate_some_and_every() -> TestResult { + // ["some", ".a", + // ["every", ".b.c[]", + // ["some", ".d", + // ["every", ".e[]", + // ["==", ".f.g", 0] + // ] + // ] + // ] + // ] let p = Predicate::Some( Select::from_str(".a").unwrap(), Box::new(Predicate::Every( @@ -1500,8 +1701,8 @@ mod tests { "a": [ { "b": { - // Every "c": { + // Every "c1": { // Some "d": [ @@ -1510,14 +1711,14 @@ mod tests { "e": { "e1": { "f": { - "g": 42 + "g": 0 }, "nope": -10 }, "e2": { "_": "not selected", "f": { - "g": 99 + "g": 0 }, } } @@ -1527,20 +1728,20 @@ mod tests { "c2": { // Some "*": "avoid", - "_": [ + "d": [ { // Every "e": { "e1": { "f": { - "g": 42 + "g": 0 }, "nope": -10 }, "e2": { "_": "not selected", "f": { - "g": 99 + "g": 0 }, } } diff --git a/src/delegation/policy/selector.rs b/src/delegation/policy/selector.rs index c39476c2..28af8f2e 100644 --- a/src/delegation/policy/selector.rs +++ b/src/delegation/policy/selector.rs @@ -40,20 +40,6 @@ impl Selector { } } -pub fn parse(input: &str) -> IResult<&str, Selector> { - if input.starts_with("..") { - return Err(nom::Err::Error(nom::error::Error::new( - input, - nom::error::ErrorKind::Count, - ))); - } - - let with_try_this = preceded(char('.'), preceded(many0(char('?')), many0(filter::parse))); - let p = map_res(with_try_this, |found| Ok::(Selector(found))); - - context("selector", p)(input) -} - impl fmt::Display for Selector { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let mut ops = self.0.iter(); @@ -86,8 +72,8 @@ impl FromStr for Selector { ))); } - if s.len() == 0 { - return Err(nom::Err::Error(ParseError::MissingStartingDot( + if s.starts_with("..") { + return Err(nom::Err::Error(ParseError::StartsWithDoubleDot( s.to_string(), ))); } @@ -95,14 +81,16 @@ impl FromStr for Selector { let working; let mut acc = vec![]; - if let Ok((more, found)) = filter::parse_dot_field(s) { + if let Ok((more, found)) = + nom::branch::alt((filter::parse_try_dot_field, filter::parse_dot_field))(s) + { working = more; acc.push(found); } else { working = &s[1..]; } - match many0(filter::parse)(working) { + match preceded(many0(char('?')), many0(filter::parse))(working) { Ok(("", ops)) => { let mut mut_ops = ops.clone(); acc.append(&mut mut_ops); diff --git a/src/delegation/policy/selector/error.rs b/src/delegation/policy/selector/error.rs index f1363f07..37f663d6 100644 --- a/src/delegation/policy/selector/error.rs +++ b/src/delegation/policy/selector/error.rs @@ -11,6 +11,9 @@ pub enum ParseError { #[error("missing starting dot: {0}")] MissingStartingDot(String), + + #[error("starts with double dot: {0}")] + StartsWithDoubleDot(String), } #[derive(Debug, Copy, Clone, PartialEq, Eq, Serialize, Deserialize, Error)] diff --git a/src/delegation/policy/selector/filter.rs b/src/delegation/policy/selector/filter.rs index 6f704a7b..96343a5a 100644 --- a/src/delegation/policy/selector/filter.rs +++ b/src/delegation/policy/selector/filter.rs @@ -75,21 +75,29 @@ pub fn parse(input: &str) -> IResult<&str, Filter> { context("selector_op", p)(input) } -pub fn parse_non_try(input: &str) -> IResult<&str, Filter> { - let p = alt((parse_values, parse_field, parse_array_index)); - context("non_try", p)(input) -} - pub fn parse_try(input: &str) -> IResult<&str, Filter> { let p = map_res( terminated(parse_non_try, many1(tag("?"))), - // FIXME old code: terminated(parse_non_try, tag("?")), |found: Filter| Ok::(Filter::Try(Box::new(found))), ); context("try", p)(input) } +pub fn parse_try_dot_field(input: &str) -> IResult<&str, Filter> { + let p = map_res( + terminated(parse_dot_field, many1(tag("?"))), + |found: Filter| Ok::(Filter::Try(Box::new(found))), + ); + + context("try", p)(input) +} + +pub fn parse_non_try(input: &str) -> IResult<&str, Filter> { + let p = alt((parse_values, parse_field, parse_array_index)); + context("non_try", p)(input) +} + pub fn parse_array_index(input: &str) -> IResult<&str, Filter> { let num = nom::combinator::recognize(preceded(nom::combinator::opt(tag("-")), digit1)); @@ -244,8 +252,6 @@ impl FromStr for Filter { fn from_str(s: &str) -> Result { match parse(s).map_err(|e| nom::Err::Failure(ParseError::UnknownPattern(e.to_string())))? { ("", found) => Ok(found), - // ("", found) => Ok(found), - // FIXME (rest, _) => Err(nom::Err::Failure(ParseError::TrailingInput(rest.into()))), } } @@ -493,15 +499,90 @@ mod tests { } #[test_log::test] - fn test_multiple_tries() -> TestResult { - let got = Filter::from_str(".foo???????????????????"); + fn test_parse_try() -> TestResult { + let got = parse(".foo?"); pretty::assert_eq!( got, + Ok(("", Filter::Try(Box::new(Filter::Field("foo".to_string()))))) + ); + Ok(()) + } + + #[test_log::test] + fn test_multiple_tries_after_dot_field() -> TestResult { + pretty::assert_eq!( + Filter::from_str(".foo???????????????????"), Ok(Filter::Try(Box::new(Filter::Field("foo".to_string())))) ); Ok(()) } + #[test_log::test] + fn test_parse_multiple_tries_after_dot_field() -> TestResult { + pretty::assert_eq!( + parse(".foo???????????????????"), + Ok(("", Filter::Try(Box::new(Filter::Field("foo".to_string()))))) + ); + Ok(()) + } + + #[test_log::test] + fn test_parse_multiple_tries_after_dot_field_trailing() -> TestResult { + pretty::assert_eq!( + parse(".foo???????????????????abc"), + Ok(( + "abc", + Filter::Try(Box::new(Filter::Field("foo".to_string()))) + )) + ); + Ok(()) + } + + #[test_log::test] + fn test_parse_many0_multiple_tries_after_dot_field() -> TestResult { + pretty::assert_eq!( + nom::multi::many0(parse)(".foo???????????????????abc"), + Ok(( + "abc", + vec![Filter::Try(Box::new(Filter::Field("foo".to_string())))] + )) + ); + Ok(()) + } + + #[test_log::test] + fn test_multiple_tries_after_delim_field() -> TestResult { + pretty::assert_eq!( + Filter::from_str(r#"["foo"]???????"#), + Ok(Filter::Try(Box::new(Filter::Field("foo".to_string())))) + ); + Ok(()) + } + + #[test_log::test] + fn test_multiple_tries_after_delim_field_inner_questionmarks() -> TestResult { + let got = Filter::from_str(r#"["f?o"]???????"#); + pretty::assert_eq!( + got, + Ok(Filter::Try(Box::new(Filter::Field("f?o".to_string())))) + ); + Ok(()) + } + + #[test_log::test] + fn test_multiple_tries_after_values() -> TestResult { + let got = Filter::from_str("[]???????"); + pretty::assert_eq!(got, Ok(Filter::Try(Box::new(Filter::Values)))); + Ok(()) + } + + #[test_log::test] + fn test_multiple_tries_after_index() -> TestResult { + let got = Filter::from_str("[42]???????"); + pretty::assert_eq!(got, Ok(Filter::Try(Box::new(Filter::ArrayIndex(42))))); + Ok(()) + } + #[test_log::test] fn test_fails_bare_try() -> TestResult { let got = Filter::from_str("?"); From deee2bcf9f2acbfedc81c4a62e5e5d554f801442 Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Thu, 21 Mar 2024 15:55:40 -0700 Subject: [PATCH 167/188] WIP rebasing --- src/ability/ucan.rs | 1 + src/ability/ucan/assert.rs | 30 ++++++++++++++++++++++++++++++ src/delegation/payload.rs | 2 +- src/delegation/store/memory.rs | 2 +- src/invocation/agent.rs | 9 ++++++++- 5 files changed, 41 insertions(+), 3 deletions(-) create mode 100644 src/ability/ucan/assert.rs diff --git a/src/ability/ucan.rs b/src/ability/ucan.rs index 0cf8e210..220034b6 100644 --- a/src/ability/ucan.rs +++ b/src/ability/ucan.rs @@ -1,4 +1,5 @@ //! Abilities for and about UCANs themselves +pub mod assert; pub mod batch; pub mod revoke; diff --git a/src/ability/ucan/assert.rs b/src/ability/ucan/assert.rs new file mode 100644 index 00000000..62946c23 --- /dev/null +++ b/src/ability/ucan/assert.rs @@ -0,0 +1,30 @@ +use crate::ability::command::Command; +use crate::task::Task; +use libipld_core::{cid::Cid, ipld::Ipld}; + +// Things that you can assert include content and receipts + +#[derive(Debug, PartialEq)] +pub struct Ran { + ran: Cid, + out: Box>, + fx: Vec, // FIXME may be more than "just" a task +} + +impl Command for Ran { + const COMMAND: &'static str = "/ucan/assert/ran"; + // const COMMAND: &'static str = "/ucan/ran";???? +} + +/////////////// +/////////////// +/////////////// + +#[derive(Debug, PartialEq)] +pub struct Claim { + claim: T, +} // Where Ipld: From + +impl Command for Claim { + const COMMAND: &'static str = "/ucan/assert/claim"; +} diff --git a/src/delegation/payload.rs b/src/delegation/payload.rs index 2f2715ef..c54438ec 100644 --- a/src/delegation/payload.rs +++ b/src/delegation/payload.rs @@ -398,7 +398,7 @@ mod tests { use testresult::TestResult; proptest! { - // #![proptest_config(ProptestConfig::with_cases(200))] + #![proptest_config(ProptestConfig::with_cases(100))] #[test_log::test] fn test_ipld_round_trip(payload in Payload::::arbitrary()) { diff --git a/src/delegation/store/memory.rs b/src/delegation/store/memory.rs index c36a6e2d..a7bc74e6 100644 --- a/src/delegation/store/memory.rs +++ b/src/delegation/store/memory.rs @@ -254,7 +254,7 @@ where format!("{}/", delegation.payload.command) }; - if !corrected_delegation_command.starts_with(&corrected_target_command) { + if !corrected_target_command.starts_with(&corrected_delegation_command) { continue; } diff --git a/src/invocation/agent.rs b/src/invocation/agent.rs index 11838bde..62e49e87 100644 --- a/src/invocation/agent.rs +++ b/src/invocation/agent.rs @@ -88,6 +88,7 @@ where &self, audience: Option, subject: DID, + command: String, ability: T, metadata: BTreeMap, cause: Option, @@ -98,7 +99,13 @@ where ) -> Result, InvokeError> { let proofs = self .delegation_store - .get_chain(&self.did, &Some(subject.clone()), "/".into(), vec![], now) // FIXME + .get_chain( + &self.did, + &Some(subject.clone()), + command.into(), + vec![], + now, + ) // FIXME .map_err(InvokeError::DelegationStoreError)? .map(|chain| chain.map(|(cid, _)| cid).into()) .unwrap_or(vec![]); From 93cce69d3e475269421e631ee46a0c24c0e79c23 Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Thu, 21 Mar 2024 15:58:52 -0700 Subject: [PATCH 168/188] Revert so that can push --- src/invocation/agent.rs | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/src/invocation/agent.rs b/src/invocation/agent.rs index 62e49e87..808ddb60 100644 --- a/src/invocation/agent.rs +++ b/src/invocation/agent.rs @@ -88,7 +88,7 @@ where &self, audience: Option, subject: DID, - command: String, + // command: String, ability: T, metadata: BTreeMap, cause: Option, @@ -99,13 +99,7 @@ where ) -> Result, InvokeError> { let proofs = self .delegation_store - .get_chain( - &self.did, - &Some(subject.clone()), - command.into(), - vec![], - now, - ) // FIXME + .get_chain(&self.did, &Some(subject.clone()), "/".into(), vec![], now) // FIXME .map_err(InvokeError::DelegationStoreError)? .map(|chain| chain.map(|(cid, _)| cid).into()) .unwrap_or(vec![]); From 643b4662b8f5879963eafe2c9f3ce6410f980535 Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Thu, 21 Mar 2024 16:05:04 -0700 Subject: [PATCH 169/188] Correct the `FIXME` for command --- src/invocation/agent.rs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/invocation/agent.rs b/src/invocation/agent.rs index 808ddb60..5641a169 100644 --- a/src/invocation/agent.rs +++ b/src/invocation/agent.rs @@ -88,7 +88,6 @@ where &self, audience: Option, subject: DID, - // command: String, ability: T, metadata: BTreeMap, cause: Option, @@ -99,7 +98,13 @@ where ) -> Result, InvokeError> { let proofs = self .delegation_store - .get_chain(&self.did, &Some(subject.clone()), "/".into(), vec![], now) // FIXME + .get_chain( + &self.did, + &Some(subject.clone()), + ability.to_command(), + vec![], + now, + ) .map_err(InvokeError::DelegationStoreError)? .map(|chain| chain.map(|(cid, _)| cid).into()) .unwrap_or(vec![]); From faba286d43af0555af01d2c7a57bbf1115077bcc Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Fri, 22 Mar 2024 14:04:22 -0700 Subject: [PATCH 170/188] Test delegation store --- src/delegation/agent.rs | 33 +- src/delegation/policy/predicate.rs | 32 +- src/delegation/store/memory.rs | 528 +++++++++++++++++++++++++++-- src/delegation/store/traits.rs | 42 ++- src/invocation/agent.rs | 83 ++--- 5 files changed, 601 insertions(+), 117 deletions(-) diff --git a/src/delegation/agent.rs b/src/delegation/agent.rs index bfee4119..8165b0a9 100644 --- a/src/delegation/agent.rs +++ b/src/delegation/agent.rs @@ -20,11 +20,15 @@ use web_time::SystemTime; /// This is helpful for sessions where more than one delegation will be made. #[derive(Debug)] pub struct Agent< - S: Store, - DID: Did = did::preset::Verifier, - V: varsig::Header + Clone = varsig::header::Preset, - Enc: Codec + Into + TryFrom = varsig::encoding::Preset, -> { + S: Store, + DID: Did + Clone = did::preset::Verifier, + V: varsig::Header + Clone = varsig::header::Preset, + C: Codec + Into + TryFrom = varsig::encoding::Preset, +> where + Delegation: Encode, + Payload: TryFrom>, + Named: From>, +{ /// The [`Did`][Did] of the agent. pub did: DID, @@ -32,17 +36,18 @@ pub struct Agent< pub store: S, signer: ::Signer, - _marker: PhantomData<(V, Enc)>, + _marker: PhantomData<(V, C)>, } impl< - S: Store + Clone, + S: Store + Clone, DID: Did + Clone, - V: varsig::Header + Clone, - Enc: Codec + TryFrom + Into, - > Agent + V: varsig::Header + Clone, + C: Codec + TryFrom + Into, + > Agent where - Ipld: Encode, + Ipld: Encode, + Delegation: Encode, Payload: TryFrom>, Named: From>, { @@ -67,7 +72,7 @@ where not_before: Option, now: SystemTime, varsig_header: V, - ) -> Result, DelegateError> { + ) -> Result, DelegateError> { let mut salt = self.did.clone().to_string().into_bytes(); let nonce = Nonce::generate_12(&mut salt); @@ -121,7 +126,7 @@ where pub fn receive( &self, cid: Cid, // FIXME remove and generate from the capsule header? - delegation: Delegation, + delegation: Delegation, ) -> Result<(), ReceiveError> { if self.store.get(&cid).is_ok() { return Ok(()); @@ -135,7 +140,7 @@ where .validate_signature() .map_err(|_| ReceiveError::InvalidSignature(cid))?; - self.store.insert(cid, delegation).map_err(Into::into) + self.store.insert_keyed(cid, delegation).map_err(Into::into) } } diff --git a/src/delegation/policy/predicate.rs b/src/delegation/policy/predicate.rs index 423d9303..12cb7eae 100644 --- a/src/delegation/policy/predicate.rs +++ b/src/delegation/policy/predicate.rs @@ -79,24 +79,20 @@ impl Predicate { Predicate::Not(inner) => !inner.run(data)?, Predicate::And(lhs, rhs) => lhs.run(data)? && rhs.run(data)?, Predicate::Or(lhs, rhs) => lhs.run(data)? || rhs.run(data)?, - Predicate::Every(xs, p) => { - xs.get(data)? - .to_vec() - .iter() - .try_fold(true, |acc, each_datum| { - dbg!("every", &p, acc, each_datum); - Ok(acc && p.clone().run(&each_datum.0)?) - })? - } - Predicate::Some(xs, p) => { - xs.get(data)? - .to_vec() - .iter() - .try_fold(false, |acc, each_datum| { - dbg!("some", &p, acc, each_datum); - Ok(acc || p.clone().run(&each_datum.0)?) - })? - } + Predicate::Every(xs, p) => xs + .get(data)? + .to_vec() + .iter() + .try_fold(true, |acc, each_datum| { + Ok(acc && p.clone().run(&each_datum.0)?) + })?, + Predicate::Some(xs, p) => xs + .get(data)? + .to_vec() + .iter() + .try_fold(false, |acc, each_datum| { + Ok(acc || p.clone().run(&each_datum.0)?) + })?, }) } diff --git a/src/delegation/store/memory.rs b/src/delegation/store/memory.rs index a7bc74e6..d9dcb376 100644 --- a/src/delegation/store/memory.rs +++ b/src/delegation/store/memory.rs @@ -105,7 +105,7 @@ impl, C: Codec + TryFrom + Into bool { - self.read().ucans.is_empty() // FIXME acocunt for revocations? + self.read().ucans.is_empty() // FIXME account for revocations? } fn read(&self) -> RwLockReadGuard<'_, MemoryStoreInner> { @@ -173,7 +173,7 @@ where // FIXME } - fn insert( + fn insert_keyed( &self, cid: Cid, delegation: Delegation, @@ -203,7 +203,7 @@ where aud: &DID, subject: &Option, command: String, - policy: Vec, // FIXME + policy: Vec, now: SystemTime, ) -> Result>)>>, Self::DelegationStoreError> { @@ -216,7 +216,8 @@ where let powerline_candidates = all_powerlines.get(aud).unwrap_or(&blank_set); let sub_candidates = all_aud_for_subject.get(aud).unwrap_or(&blank_set); - let mut parent_candidate_stack = vec![]; + let mut parent_candidate_stack = + vec![sub_candidates.iter().chain(powerline_candidates.iter())]; let mut hypothesis_chain = vec![]; let corrected_target_command = if command.ends_with('/') { @@ -225,18 +226,23 @@ where format!("{}/", command) }; - parent_candidate_stack.push(sub_candidates.iter().chain(powerline_candidates.iter())); - let mut next = None; + // TODO Vec> + // parent_candidate_stack.push(sub_candidates.iter()); // .chain(powerline_candidates.iter())); + + // Pseudocode: + // If empty, pop: + // if pop fials, you're out of stuff + // If 'outer: loop { if let Some(parent_cid_candidates) = parent_candidate_stack.last_mut() { if parent_cid_candidates.clone().collect::>().is_empty() { parent_candidate_stack.pop(); - hypothesis_chain.pop(); - break 'outer; + continue; } 'inner: for cid in parent_cid_candidates { + // CHECKS if read_tx.revocations.contains(cid) { continue; } @@ -258,17 +264,19 @@ where continue; } - for target_pred in policy.iter() { - for delegate_pred in delegation.payload.policy.iter() { - let comparison = - target_pred.harmonize(delegate_pred, vec![], vec![]); + // FIXME + // for target_pred in policy.iter() { + // for delegate_pred in delegation.payload.policy.iter() { + // let comparison = + // target_pred.harmonize(delegate_pred, vec![], vec![]); - if comparison.is_conflict() || comparison.is_lhs_weaker() { - continue 'inner; - } - } - } + // if comparison.is_conflict() || comparison.is_lhs_weaker() { + // continue 'inner; + // } + // } + // } + // PASSED CHECKS, so processing hypothesis_chain.push((cid.clone(), Arc::clone(delegation))); let issuer = delegation.issuer().clone(); @@ -281,34 +289,486 @@ where let new_aud_candidates = all_aud_for_subject.get(&issuer).unwrap_or(&blank_set); - let new_powerline_candidates = - all_powerlines.get(&issuer).unwrap_or(&blank_set); - - if !new_aud_candidates.is_empty() || !new_powerline_candidates.is_empty() { - next = Some( - new_aud_candidates - .iter() - .chain(new_powerline_candidates.iter()), + if !new_aud_candidates.is_empty() || !all_powerlines.get(&issuer).is_none() + { + parent_candidate_stack.push( + new_aud_candidates.iter().chain( + all_powerlines.get(&issuer).unwrap_or(&blank_set).iter(), + ), ); break 'inner; } } } - - if let Some(ref n) = next { - parent_candidate_stack.push(n.clone()); - next = None; - } else { - // Didn't find a match - break 'outer; - } } else { parent_candidate_stack.pop(); - hypothesis_chain.pop(); + break 'outer; } } Ok(NonEmpty::from_vec(hypothesis_chain)) } } + +#[cfg(test)] +mod tests { + use super::*; + use crate::crypto::varsig::encoding; + use crate::crypto::varsig::header; + use crate::{crypto::signature::Envelope, delegation::store::Store}; + + use libipld_core::cid::Cid; + use nonempty::nonempty; + use pretty_assertions as pretty; + use rand::thread_rng; + use std::time::SystemTime; + use testresult::TestResult; + + fn gen_did() -> (crate::did::preset::Verifier, crate::did::preset::Signer) { + let sk = ed25519_dalek::SigningKey::generate(&mut thread_rng()); + let verifier = + crate::did::preset::Verifier::Key(crate::did::key::Verifier::EdDsa(sk.verifying_key())); + let signer = crate::did::preset::Signer::Key(crate::did::key::Signer::EdDsa(sk)); + + (verifier, signer) + } + + #[test_log::test] + fn test_get_fail() -> TestResult { + let store = crate::delegation::store::MemoryStore::default(); + store.get(&Cid::default())?; + pretty::assert_eq!(store.get(&Cid::default()), Ok(None)); + Ok(()) + } + + #[test_log::test] + fn test_insert_get_roundtrip() -> TestResult { + let (did, signer) = gen_did(); + + let store = MemoryStore::default(); + let varsig_header = header::Preset::EdDsa(header::EdDsaHeader { + codec: encoding::Preset::DagCbor, + }); + + let deleg = Delegation::try_sign( + &signer, + varsig_header, + crate::delegation::PayloadBuilder::default() + .subject(None) + .issuer(did.clone()) + .audience(did.clone()) + .command("/".into()) + .expiration(crate::time::Timestamp::five_years_from_now()) + .build()?, + )?; + + store.insert(deleg.clone())?; + let retrieved = store.get(&deleg.cid()?)?.ok_or("failed to retrieve")?; + + pretty::assert_eq!(deleg, *retrieved); + + Ok(()) + } + + #[test_log::test] + fn test_insert_is_idempotent() -> TestResult { + let (did, signer) = gen_did(); + + let store = MemoryStore::default(); + let varsig_header = header::Preset::EdDsa(header::EdDsaHeader { + codec: encoding::Preset::DagCbor, + }); + + let deleg = Delegation::try_sign( + &signer, + varsig_header, + crate::delegation::PayloadBuilder::default() + .subject(None) + .issuer(did.clone()) + .audience(did.clone()) + .command("/".into()) + .expiration(crate::time::Timestamp::five_years_from_now()) + .build()?, + )?; + + store.insert(deleg.clone())?; + store.insert(deleg.clone())?; + store.insert(deleg.clone())?; + store.insert(deleg.clone())?; + store.insert(deleg.clone())?; + store.insert(deleg.clone())?; + store.insert(deleg.clone())?; + + let retrieved = store.get(&deleg.cid()?)?.ok_or("failed to retrieve")?; + + pretty::assert_eq!(deleg, *retrieved); + pretty::assert_eq!(store.len(), 1); + + Ok(()) + } + + mod get_chain { + use super::*; + + #[test_log::test] + fn test_simple_fail() -> TestResult { + let (server, _server_signer) = gen_did(); + + let store = crate::delegation::store::MemoryStore::default(); + let got = store.get_chain(&server, &None, "/".into(), vec![], SystemTime::now())?; + + pretty::assert_eq!(got, None); + Ok(()) + } + + #[test_log::test] + fn test_with_one() -> TestResult { + let (alice, alice_signer) = gen_did(); + let (bob, _bob_signer) = gen_did(); + + let store = crate::delegation::store::MemoryStore::default(); + let varsig_header = crate::crypto::varsig::header::Preset::EdDsa( + crate::crypto::varsig::header::EdDsaHeader { + codec: crate::crypto::varsig::encoding::Preset::DagCbor, + }, + ); + + let deleg = crate::Delegation::try_sign( + &alice_signer, + varsig_header.clone(), + crate::delegation::PayloadBuilder::default() + .subject(None) + .issuer(alice.clone()) + .audience(bob.clone()) + .command("/".into()) + .expiration(crate::time::Timestamp::five_years_from_now()) + .build()?, + )?; + + store.insert(deleg.clone())?; + + let got = store.get_chain(&bob, &Some(alice), "/".into(), vec![], SystemTime::now())?; + pretty::assert_eq!(got, Some(nonempty![(deleg.cid()?, Arc::new(deleg))].into())); + Ok(()) + } + + #[test_log::test] + fn test_with_one_with_others_in_store() -> TestResult { + let (alice, alice_signer) = gen_did(); + let (bob, bob_signer) = gen_did(); + let (carol, _carol_signer) = gen_did(); + + let store = crate::delegation::store::MemoryStore::default(); + let varsig_header = crate::crypto::varsig::header::Preset::EdDsa( + crate::crypto::varsig::header::EdDsaHeader { + codec: crate::crypto::varsig::encoding::Preset::DagCbor, + }, + ); + + let noise = crate::Delegation::try_sign( + &bob_signer, + varsig_header.clone(), + crate::delegation::PayloadBuilder::default() + .subject(None) + .issuer(bob.clone()) + .audience(carol.clone()) + .command("/example".into()) + .expiration(crate::time::Timestamp::five_years_from_now()) + .build()?, + )?; + + store.insert(noise.clone())?; + + let deleg = crate::Delegation::try_sign( + &alice_signer, + varsig_header.clone(), + crate::delegation::PayloadBuilder::default() + .subject(None) + .issuer(alice.clone()) + .audience(bob.clone()) + .command("/".into()) + .expiration(crate::time::Timestamp::five_years_from_now()) + .build()?, + )?; + + store.insert(deleg.clone())?; + + let more_noise = crate::Delegation::try_sign( + &alice_signer, + varsig_header.clone(), + crate::delegation::PayloadBuilder::default() + .subject(None) + .issuer(alice.clone()) + .audience(carol.clone()) + .command("/test".into()) + .expiration(crate::time::Timestamp::five_years_from_now()) + .build()?, + )?; + + store.insert(more_noise.clone())?; + + let got = store.get_chain(&bob, &Some(alice), "/".into(), vec![], SystemTime::now())?; + pretty::assert_eq!(got, Some(nonempty![(deleg.cid()?, Arc::new(deleg))].into())); + Ok(()) + } + + #[test_log::test] + fn test_with_two() -> TestResult { + let (alice, alice_signer) = gen_did(); + let (bob, bob_signer) = gen_did(); + let (carol, _carol_signer) = gen_did(); + + let store = crate::delegation::store::MemoryStore::default(); + let varsig_header = crate::crypto::varsig::header::Preset::EdDsa( + crate::crypto::varsig::header::EdDsaHeader { + codec: crate::crypto::varsig::encoding::Preset::DagCbor, + }, + ); + + let deleg_1 = crate::Delegation::try_sign( + &alice_signer, + varsig_header.clone(), + crate::delegation::PayloadBuilder::default() + .subject(None) + .issuer(alice.clone()) + .audience(bob.clone()) + .command("/".into()) + .expiration(crate::time::Timestamp::five_years_from_now()) + .build()?, + )?; + + store.insert(deleg_1.clone())?; + + let deleg_2 = crate::Delegation::try_sign( + &bob_signer, + varsig_header.clone(), + crate::delegation::PayloadBuilder::default() + .subject(Some(alice.clone())) + .issuer(bob.clone()) + .audience(carol.clone()) + .command("/".into()) + .expiration(crate::time::Timestamp::five_years_from_now()) + .build()?, + )?; + + store.insert(deleg_2.clone())?; + + let got = + store.get_chain(&carol, &Some(alice), "/".into(), vec![], SystemTime::now())?; + + pretty::assert_eq!( + got, + Some( + nonempty![ + (deleg_2.cid()?, Arc::new(deleg_2)), + (deleg_1.cid()?, Arc::new(deleg_1)), + ] + .into() + ) + ); + Ok(()) + } + + #[test_log::test] + fn test_looking_for_narrower_command() -> TestResult { + let (alice, alice_signer) = gen_did(); + let (bob, bob_signer) = gen_did(); + let (carol, _carol_signer) = gen_did(); + + let store = crate::delegation::store::MemoryStore::default(); + let varsig_header = crate::crypto::varsig::header::Preset::EdDsa( + crate::crypto::varsig::header::EdDsaHeader { + codec: crate::crypto::varsig::encoding::Preset::DagCbor, + }, + ); + + let deleg_1 = crate::Delegation::try_sign( + &alice_signer, + varsig_header.clone(), + crate::delegation::PayloadBuilder::default() + .subject(None) + .issuer(alice.clone()) + .audience(bob.clone()) + .command("/test".into()) + .expiration(crate::time::Timestamp::five_years_from_now()) + .build()?, + )?; + + store.insert(deleg_1.clone())?; + + let deleg_2 = crate::Delegation::try_sign( + &bob_signer, + varsig_header.clone(), + crate::delegation::PayloadBuilder::default() + .subject(Some(alice.clone())) + .issuer(bob.clone()) + .audience(carol.clone()) + .command("/test/me".into()) + .expiration(crate::time::Timestamp::five_years_from_now()) + .build()?, + )?; + + store.insert(deleg_2.clone())?; + + let got = store.get_chain( + &carol, + &Some(alice), + "/test/me/now".into(), + vec![], + SystemTime::now(), + )?; + + pretty::assert_eq!( + got, + Some( + nonempty![ + (deleg_2.cid()?, Arc::new(deleg_2)), + (deleg_1.cid()?, Arc::new(deleg_1)), + ] + .into() + ) + ); + Ok(()) + } + + #[test_log::test] + fn test_broken_chain() -> TestResult { + let (alice, alice_signer) = gen_did(); + let (bob, bob_signer) = gen_did(); + let (carol, carol_signer) = gen_did(); + let (dan, dan_signer) = gen_did(); + + let store = crate::delegation::store::MemoryStore::default(); + let varsig_header = crate::crypto::varsig::header::Preset::EdDsa( + crate::crypto::varsig::header::EdDsaHeader { + codec: crate::crypto::varsig::encoding::Preset::DagCbor, + }, + ); + + let alice_to_bob = crate::Delegation::try_sign( + &alice_signer, + varsig_header.clone(), + crate::delegation::PayloadBuilder::default() + .subject(None) + .issuer(alice.clone()) + .audience(bob.clone()) + .command("/test".into()) + .expiration(crate::time::Timestamp::five_years_from_now()) + .build()?, + )?; + + store.insert(alice_to_bob.clone())?; + + let carol_to_dan = crate::Delegation::try_sign( + &carol_signer, + varsig_header.clone(), + crate::delegation::PayloadBuilder::default() + .subject(Some(alice.clone())) + .issuer(carol.clone()) + .audience(dan.clone()) + .command("/test/me".into()) + .expiration(crate::time::Timestamp::five_years_from_now()) + .build()?, + )?; + + store.insert(carol_to_dan.clone())?; + + let got = store.get_chain( + &carol, + &Some(alice), + "/test/me/now".into(), + vec![], + SystemTime::now(), + )?; + + pretty::assert_eq!(got, None); + Ok(()) + } + + #[test_log::test] + fn test_long_chain() -> TestResult { + // Scenario + // ======== + // 1. bob -*-> carol + // 2. carol -a-> dave + // 3. alice -d-> bob + // + let (alice, alice_signer) = gen_did(); + let (bob, bob_signer) = gen_did(); + let (carol, carol_signer) = gen_did(); + let (dave, _dave_signer) = gen_did(); + + let varsig_header = crate::crypto::varsig::header::Preset::EdDsa( + crate::crypto::varsig::header::EdDsaHeader { + codec: crate::crypto::varsig::encoding::Preset::DagCbor, + }, + ); + + let store = crate::delegation::store::MemoryStore::default(); + + // 1. bob -*-> carol + let bob_to_carol = crate::Delegation::try_sign( + &bob_signer, + varsig_header.clone(), + crate::delegation::PayloadBuilder::default() + .subject(None) + .issuer(bob.clone()) + .audience(carol.clone()) + .command("/".into()) + .expiration(crate::time::Timestamp::five_years_from_now()) + .build()?, + )?; + + // 2. carol -a-> dave + let carol_to_dave = crate::Delegation::try_sign( + &carol_signer, + varsig_header.clone(), // FIXME can also put this on a builder + crate::delegation::PayloadBuilder::default() + .subject(None) // FIXME needs a sibject when we figure out powerbox + .issuer(carol.clone()) + .audience(dave.clone()) + .command("/".into()) + .expiration(crate::time::Timestamp::five_years_from_now()) + .build()?, // I don't love this is now failable + )?; + + // 3. alice -d-> bob + let alice_to_bob = crate::Delegation::try_sign( + &alice_signer, + varsig_header.clone(), + crate::delegation::PayloadBuilder::default() + .subject(Some(alice.clone())) + .issuer(alice.clone()) + .audience(bob.clone()) + .command("/".into()) + .expiration(crate::time::Timestamp::five_years_from_now()) + .build()?, + )?; + + store.insert(bob_to_carol.clone())?; + store.insert(carol_to_dave.clone())?; + store.insert(alice_to_bob.clone())?; + + let got: Vec = store + .get_chain(&dave, &Some(alice), "/".into(), vec![], SystemTime::now()) + .map_err(|e| e.to_string())? + .ok_or("failed during proof lookup")? + .iter() + .map(|(cid, _)| cid) + .cloned() + .collect(); + + pretty::assert_eq!( + got, + vec![ + carol_to_dave.cid()?, + bob_to_carol.cid()?, + alice_to_bob.cid()? + ] + ); + + Ok(()) + } + } +} diff --git a/src/delegation/store/traits.rs b/src/delegation/store/traits.rs index 7413e700..d19b5d13 100644 --- a/src/delegation/store/traits.rs +++ b/src/delegation/store/traits.rs @@ -1,25 +1,39 @@ use crate::{ + ability::arguments::Named, + crypto::signature::Envelope, crypto::varsig, + delegation::payload::Payload, delegation::{policy::Predicate, Delegation}, did::Did, }; +use libipld_core::codec::Encode; +use libipld_core::ipld::Ipld; use libipld_core::{cid::Cid, codec::Codec}; use nonempty::NonEmpty; use std::{fmt::Debug, sync::Arc}; use web_time::SystemTime; -pub trait Store, Enc: Codec + TryFrom + Into> { +pub trait Store + Clone, C: Codec + TryFrom + Into> +where + Delegation: Encode, + Payload: TryFrom>, + Named: From>, +{ type DelegationStoreError: Debug; fn get( &self, cid: &Cid, - ) -> Result>>, Self::DelegationStoreError>; + ) -> Result>>, Self::DelegationStoreError>; + + fn insert(&self, delegation: Delegation) -> Result<(), Self::DelegationStoreError> { + self.insert_keyed(delegation.cid().expect("FIXME"), delegation) + } - fn insert( + fn insert_keyed( &self, cid: Cid, - delegation: Delegation, + delegation: Delegation, ) -> Result<(), Self::DelegationStoreError>; // FIXME validate invocation @@ -34,7 +48,7 @@ pub trait Store, Enc: Codec + TryFrom + In command: String, policy: Vec, now: SystemTime, - ) -> Result>)>>, Self::DelegationStoreError>; + ) -> Result>)>>, Self::DelegationStoreError>; fn get_chain_cids( &self, @@ -63,15 +77,23 @@ pub trait Store, Enc: Codec + TryFrom + In fn get_many( &self, cids: &[Cid], - ) -> Result>>>, Self::DelegationStoreError> { + ) -> Result>>>, Self::DelegationStoreError> { cids.iter() .map(|cid| self.get(cid)) .collect::>() } } -impl, DID: Did, V: varsig::Header, C: Codec + TryFrom + Into> - Store for &T +impl< + T: Store, + DID: Did + Clone, + V: varsig::Header + Clone, + C: Codec + TryFrom + Into, + > Store for &T +where + Delegation: Encode, + Payload: TryFrom>, + Named: From>, { type DelegationStoreError = >::DelegationStoreError; @@ -82,12 +104,12 @@ impl, DID: Did, V: varsig::Header, C: Codec + TryFrom, ) -> Result<(), Self::DelegationStoreError> { - (**self).insert(cid, delegation) + (**self).insert_keyed(cid, delegation) } fn revoke(&self, cid: Cid) -> Result<(), Self::DelegationStoreError> { diff --git a/src/invocation/agent.rs b/src/invocation/agent.rs index 5641a169..11c3de80 100644 --- a/src/invocation/agent.rs +++ b/src/invocation/agent.rs @@ -6,6 +6,7 @@ use super::{ use crate::ability::arguments::Named; use crate::ability::command::ToCommand; use crate::ability::parse::ParseAbility; +use crate::delegation::Delegation; use crate::invocation::payload::PayloadBuilder; use crate::{ ability::{self, arguments, parse::ParseAbilityError, ucan::revoke::Revoke}, @@ -23,12 +24,7 @@ use libipld_core::{ codec::{Codec, Encode}, ipld::Ipld, }; -use std::{ - collections::{BTreeMap, BTreeSet}, - fmt, - marker::PhantomData, - sync::Arc, -}; +use std::{collections::BTreeMap, fmt, marker::PhantomData}; use thiserror::Error; use web_time::SystemTime; @@ -37,10 +33,14 @@ pub struct Agent< S: Store, D: delegation::store::Store, T: ToCommand = ability::preset::Preset, - DID: Did = did::preset::Verifier, + DID: Did + Clone = did::preset::Verifier, V: varsig::Header + Clone = varsig::header::Preset, C: Codec + Into + TryFrom = varsig::encoding::Preset, -> { +> where + Delegation: Encode, + delegation::Payload: TryFrom>, + Named: From>, +{ /// The agent's [`DID`]. pub did: DID, @@ -68,6 +68,9 @@ where C: Codec + Into + TryFrom, >::InvocationStoreError: fmt::Debug, >::DelegationStoreError: fmt::Debug, + delegation::Payload: TryFrom>, + Named: From>, + Delegation: Encode, { pub fn new( did: DID, @@ -96,20 +99,21 @@ where now: SystemTime, varsig_header: V, ) -> Result, InvokeError> { - let proofs = self - .delegation_store - .get_chain( - &self.did, - &Some(subject.clone()), - ability.to_command(), - vec![], - now, - ) - .map_err(InvokeError::DelegationStoreError)? - .map(|chain| chain.map(|(cid, _)| cid).into()) - .unwrap_or(vec![]); - - let mut seed = vec![]; + let proofs = if subject == self.did { + vec![] + } else { + self.delegation_store + .get_chain( + &self.did, + &Some(subject.clone()), + ability.to_command(), + vec![], + now, + ) + .map_err(InvokeError::DelegationStoreError)? + .map(|chain| chain.map(|(cid, _)| cid).into()) + .unwrap_or(vec![]) // FIXME + }; let payload = Payload { issuer: self.did.clone(), @@ -118,7 +122,7 @@ where ability, proofs, metadata, - nonce: Nonce::generate_12(&mut seed), + nonce: Nonce::generate_12(&mut vec![]), cause, expiration, issued_at, @@ -441,7 +445,7 @@ mod tests { #[test_log::test] fn test_invoker_is_sub_implicit_aud() -> TestResult { let (_nbf, now, exp) = setup_valid_time(); - let mut agent = setup_agent(); + let agent = setup_agent(); let invocation = agent.invoke( None, @@ -469,7 +473,7 @@ mod tests { #[test_log::test] fn test_invoker_is_sub_and_aud() -> TestResult { let (_nbf, now, exp) = setup_valid_time(); - let mut agent = setup_agent(); + let agent = setup_agent(); let invocation = agent.invoke( Some(agent.did.clone()), @@ -498,7 +502,7 @@ mod tests { #[test_log::test] fn test_other_recipient() -> TestResult { let (_nbf, now, exp) = setup_valid_time(); - let mut agent = setup_agent(); + let agent = setup_agent(); let (not_server, _) = gen_did(); @@ -528,7 +532,7 @@ mod tests { #[test_log::test] fn test_expired() -> TestResult { let (past, now, _exp) = setup_valid_time(); - let mut agent = setup_agent(); + let agent = setup_agent(); let invocation = agent.invoke( None, @@ -563,7 +567,7 @@ mod tests { #[test_log::test] fn test_invalid_sig() -> TestResult { let (_past, now, _exp) = setup_valid_time(); - let mut agent = setup_agent(); + let agent = setup_agent(); let server = &agent.did; let mut invocation = agent.invoke( @@ -686,13 +690,13 @@ mod tests { .build()?, )?; - drop(del_store.insert(account_device_ucan.cid()?, account_device_ucan.clone())); - drop(del_store.insert(account_pbox.cid()?, account_pbox.clone())); - drop(del_store.insert(dnslink_ucan.cid()?, dnslink_ucan.clone())); + del_store.insert(account_device_ucan.clone())?; + del_store.insert(account_pbox.clone())?; + del_store.insert(dnslink_ucan.clone())?; let proofs_for_powerline: Vec = del_store .get_chain(&device, &None, "/".into(), vec![], SystemTime::now())? - .ok_or("FIXME")? + .ok_or("failed during proof lookup")? .iter() .map(|x| x.0.clone()) .collect(); @@ -714,17 +718,14 @@ mod tests { .issuer(device.clone()) .audience(Some(server.clone())) .ability(AccountManage) - .proofs(vec![ - account_device_ucan.cid()?, - account_pbox.cid()?, - dnslink_ucan.cid()?, - ]) - // .proofs(proofs_for_powerline.clone()) + .proofs(proofs_for_powerline.clone()) .build()?, )?; let powerline_len = proofs_for_powerline.len(); - let dnslink_len = chain_for_dnslink?.ok_or("FIXME")?.len(); + let dnslink_len = chain_for_dnslink? + .ok_or("failed while finding DNSLink delegtaions")? + .len(); Ok(Ctx { varsig_header, @@ -751,7 +752,7 @@ mod tests { fn test_chain_ok() -> TestResult { let ctx = setup_test_chain()?; - let mut agent = Agent::new( + let agent = Agent::new( ctx.server.clone(), ctx.server_signer.clone(), &ctx.inv_store, @@ -767,7 +768,7 @@ mod tests { fn test_chain_wrong_sub() -> TestResult { let ctx = setup_test_chain()?; - let mut agent = Agent::new( + let agent = Agent::new( ctx.server.clone(), ctx.server_signer.clone(), &ctx.inv_store, From 246578b7b7f62921e930296d96b1e142e9225892 Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Sat, 23 Mar 2024 15:45:16 -0700 Subject: [PATCH 171/188] WIP testing --- src/crypto/signature/envelope.rs | 4 +- src/delegation/agent.rs | 3 +- src/delegation/store/memory.rs | 125 +++++++++++++++++++++++++--- src/delegation/store/traits.rs | 4 +- src/invocation/agent.rs | 29 ++++--- src/invocation/store.rs | 135 +------------------------------ src/invocation/store/memory.rs | 83 +++++++++++++++++++ src/invocation/store/traits.rs | 51 ++++++++++++ 8 files changed, 279 insertions(+), 155 deletions(-) create mode 100644 src/invocation/store/memory.rs create mode 100644 src/invocation/store/traits.rs diff --git a/src/crypto/signature/envelope.rs b/src/crypto/signature/envelope.rs index 0cea6c02..170b719d 100644 --- a/src/crypto/signature/envelope.rs +++ b/src/crypto/signature/envelope.rs @@ -186,11 +186,11 @@ pub trait Envelope: Sized { fn cid(&self) -> Result where - Self: Encode, + Ipld: Encode, { let codec = varsig::header::Header::codec(self.varsig_header()).clone(); let mut ipld_buffer = vec![]; - self.encode(codec, &mut ipld_buffer)?; + self.to_ipld_envelope().encode(codec, &mut ipld_buffer)?; let multihash = Code::Sha2_256.digest(&ipld_buffer); Ok(Cid::new_v1( diff --git a/src/delegation/agent.rs b/src/delegation/agent.rs index 8165b0a9..16482c14 100644 --- a/src/delegation/agent.rs +++ b/src/delegation/agent.rs @@ -25,7 +25,7 @@ pub struct Agent< V: varsig::Header + Clone = varsig::header::Preset, C: Codec + Into + TryFrom = varsig::encoding::Preset, > where - Delegation: Encode, + Ipld: Encode, Payload: TryFrom>, Named: From>, { @@ -47,7 +47,6 @@ impl< > Agent where Ipld: Encode, - Delegation: Encode, Payload: TryFrom>, Named: From>, { diff --git a/src/delegation/store/memory.rs b/src/delegation/store/memory.rs index d9dcb376..9dcf04b3 100644 --- a/src/delegation/store/memory.rs +++ b/src/delegation/store/memory.rs @@ -160,7 +160,7 @@ impl< where Named: From>, delegation::Payload: TryFrom>, - Delegation: Encode, + Ipld: Encode, { type DelegationStoreError = Infallible; @@ -235,19 +235,28 @@ where // If 'outer: loop { + dbg!("OUTER"); if let Some(parent_cid_candidates) = parent_candidate_stack.last_mut() { + dbg!("SOME INNER"); + for cid in parent_cid_candidates.clone() { + dbg!("INNER", cid.to_string()); + } if parent_cid_candidates.clone().collect::>().is_empty() { + dbg!("EMPTY"); parent_candidate_stack.pop(); continue; } 'inner: for cid in parent_cid_candidates { + dbg!("BBBBBBBBBBBBBBBBBBBBBB"); + dbg!(cid.to_string()); // CHECKS if read_tx.revocations.contains(cid) { continue; } if let Some(delegation) = read_tx.ucans.get(cid) { + dbg!("EEEEEEEEEEEEEEE"); if delegation.check_time(now).is_err() { continue; } @@ -283,6 +292,7 @@ where // Hit a root delegation, AKA base case if &Some(issuer.clone()) == delegation.subject() { + dbg!("HHHHHHHHHHHH"); break 'outer; } @@ -336,7 +346,11 @@ mod tests { #[test_log::test] fn test_get_fail() -> TestResult { - let store = crate::delegation::store::MemoryStore::default(); + let store = MemoryStore::< + did::preset::Verifier, + varsig::header::Preset, + varsig::encoding::Preset, + >::default(); store.get(&Cid::default())?; pretty::assert_eq!(store.get(&Cid::default()), Ok(None)); Ok(()) @@ -415,7 +429,11 @@ mod tests { fn test_simple_fail() -> TestResult { let (server, _server_signer) = gen_did(); - let store = crate::delegation::store::MemoryStore::default(); + let store = MemoryStore::< + did::preset::Verifier, + varsig::header::Preset, + varsig::encoding::Preset, + >::default(); let got = store.get_chain(&server, &None, "/".into(), vec![], SystemTime::now())?; pretty::assert_eq!(got, None); @@ -635,9 +653,9 @@ mod tests { #[test_log::test] fn test_broken_chain() -> TestResult { let (alice, alice_signer) = gen_did(); - let (bob, bob_signer) = gen_did(); + let (bob, _bob_signer) = gen_did(); let (carol, carol_signer) = gen_did(); - let (dan, dan_signer) = gen_did(); + let (dan, _dan_signer) = gen_did(); let store = crate::delegation::store::MemoryStore::default(); let varsig_header = crate::crypto::varsig::header::Preset::EdDsa( @@ -693,11 +711,10 @@ mod tests { // 1. bob -*-> carol // 2. carol -a-> dave // 3. alice -d-> bob - // let (alice, alice_signer) = gen_did(); let (bob, bob_signer) = gen_did(); let (carol, carol_signer) = gen_did(); - let (dave, _dave_signer) = gen_did(); + let (dave, _) = gen_did(); let varsig_header = crate::crypto::varsig::header::Preset::EdDsa( crate::crypto::varsig::header::EdDsaHeader { @@ -723,9 +740,9 @@ mod tests { // 2. carol -a-> dave let carol_to_dave = crate::Delegation::try_sign( &carol_signer, - varsig_header.clone(), // FIXME can also put this on a builder + varsig_header.clone(), crate::delegation::PayloadBuilder::default() - .subject(None) // FIXME needs a sibject when we figure out powerbox + .subject(None) .issuer(carol.clone()) .audience(dave.clone()) .command("/".into()) @@ -770,5 +787,95 @@ mod tests { Ok(()) } + + #[test_log::test] + fn test_long_powerline() -> TestResult { + // Scenario + // ======== + // 1. bob -*-> carol + // 2. carol -a-> dave + // 3. alice -d-> bob + let (alice, alice_signer) = gen_did(); + let (bob, bob_signer) = gen_did(); + let (carol, carol_signer) = gen_did(); + let (dave, _) = gen_did(); + + let varsig_header = crate::crypto::varsig::header::Preset::EdDsa( + crate::crypto::varsig::header::EdDsaHeader { + codec: crate::crypto::varsig::encoding::Preset::DagCbor, + }, + ); + + let store = crate::delegation::store::MemoryStore::default(); + + // 1. bob -*-> carol + let bob_to_carol = crate::Delegation::try_sign( + &bob_signer, + varsig_header.clone(), + crate::delegation::PayloadBuilder::default() + .subject(None) + .issuer(bob.clone()) + .audience(carol.clone()) + .command("/".into()) + .expiration(crate::time::Timestamp::five_years_from_now()) + .build()?, + )?; + + // 2. carol -a-> dave + let carol_to_dave = crate::Delegation::try_sign( + &carol_signer, + varsig_header.clone(), + crate::delegation::PayloadBuilder::default() + .subject(None) + .issuer(carol.clone()) + .audience(dave.clone()) + .command("/".into()) + .expiration(crate::time::Timestamp::five_years_from_now()) + .build()?, // I don't love this is now failable + )?; + + // 3. alice -d-> bob + let alice_to_bob = crate::Delegation::try_sign( + &alice_signer, + varsig_header.clone(), + crate::delegation::PayloadBuilder::default() + .subject(Some(alice.clone())) + .issuer(alice.clone()) + .audience(bob.clone()) + .command("/".into()) + .expiration(crate::time::Timestamp::five_years_from_now()) + .build()?, + )?; + + store.insert(bob_to_carol.clone())?; + store.insert(carol_to_dave.clone())?; + store.insert(alice_to_bob.clone())?; + + let got: Vec = store + .get_chain(&dave, &None, "/".into(), vec![], SystemTime::now()) + .map_err(|e| e.to_string())? + .ok_or("failed during proof lookup")? + .iter() + .map(|(cid, _)| cid) + .cloned() + .collect(); + + dbg!("THERE!!!!!!!!!!!!!!!!!"); + + for cid in &got { + dbg!(cid.to_string()); + } + + pretty::assert_eq!( + got, + vec![ + carol_to_dave.cid()?, + bob_to_carol.cid()?, + alice_to_bob.cid()? + ] + ); + + Ok(()) + } } } diff --git a/src/delegation/store/traits.rs b/src/delegation/store/traits.rs index d19b5d13..c917dc20 100644 --- a/src/delegation/store/traits.rs +++ b/src/delegation/store/traits.rs @@ -15,7 +15,7 @@ use web_time::SystemTime; pub trait Store + Clone, C: Codec + TryFrom + Into> where - Delegation: Encode, + Ipld: Encode, Payload: TryFrom>, Named: From>, { @@ -91,7 +91,7 @@ impl< C: Codec + TryFrom + Into, > Store for &T where - Delegation: Encode, + Ipld: Encode, Payload: TryFrom>, Named: From>, { diff --git a/src/invocation/agent.rs b/src/invocation/agent.rs index 11c3de80..ee7e44d1 100644 --- a/src/invocation/agent.rs +++ b/src/invocation/agent.rs @@ -37,7 +37,7 @@ pub struct Agent< V: varsig::Header + Clone = varsig::header::Preset, C: Codec + Into + TryFrom = varsig::encoding::Preset, > where - Delegation: Encode, + Ipld: Encode, delegation::Payload: TryFrom>, Named: From>, { @@ -70,7 +70,6 @@ where >::DelegationStoreError: fmt::Debug, delegation::Payload: TryFrom>, Named: From>, - Delegation: Encode, { pub fn new( did: DID, @@ -652,7 +651,7 @@ mod tests { // 4. [dnslink -d-> account -*-> server -a-> device] // 1. account -*-> server - let account_pbox = crate::Delegation::try_sign( + let account_to_server = crate::Delegation::try_sign( &account_signer, varsig_header.clone(), crate::delegation::PayloadBuilder::default() @@ -665,7 +664,7 @@ mod tests { )?; // 2. server -a-> device - let account_device_ucan = crate::Delegation::try_sign( + let server_to_device = crate::Delegation::try_sign( &server_signer, varsig_header.clone(), // FIXME can also put this on a builder crate::delegation::PayloadBuilder::default() @@ -678,7 +677,7 @@ mod tests { )?; // 3. dnslink -d-> account - let dnslink_ucan = crate::Delegation::try_sign( + let dnslink_to_account = crate::Delegation::try_sign( &dnslink_signer, varsig_header.clone(), crate::delegation::PayloadBuilder::default() @@ -690,9 +689,9 @@ mod tests { .build()?, )?; - del_store.insert(account_device_ucan.clone())?; - del_store.insert(account_pbox.clone())?; - del_store.insert(dnslink_ucan.clone())?; + del_store.insert(account_to_server.clone())?; + del_store.insert(server_to_device.clone())?; + del_store.insert(dnslink_to_account.clone())?; let proofs_for_powerline: Vec = del_store .get_chain(&device, &None, "/".into(), vec![], SystemTime::now())? @@ -722,6 +721,18 @@ mod tests { .build()?, )?; + dbg!("==================="); + dbg!(proofs_for_powerline.len()); + dbg!(">>>>>>>>>>>>>>>>>."); + dbg!(account_to_server.cid()?.to_string()); + dbg!(server_to_device.cid()?.to_string()); + dbg!(dnslink_to_account.cid()?.to_string()); + + dbg!("<<<<<<<<<<<<<<<<<<"); + for prf_cid in &proofs_for_powerline { + dbg!(prf_cid.to_string()); + } + let powerline_len = proofs_for_powerline.len(); let dnslink_len = chain_for_dnslink? .ok_or("failed while finding DNSLink delegtaions")? @@ -760,7 +771,7 @@ mod tests { ); let observed = agent.receive(ctx.account_invocation.clone()); - assert!(observed.is_ok()); + assert_matches!(observed, Ok(Recipient::You(_))); Ok(()) } diff --git a/src/invocation/store.rs b/src/invocation/store.rs index 4c9279fa..224e0c77 100644 --- a/src/invocation/store.rs +++ b/src/invocation/store.rs @@ -1,134 +1,7 @@ //! Storage for [`Invocation`]s. -use super::Invocation; -use crate::ability; -use crate::{crypto::varsig, did::Did}; -use libipld_core::{cid::Cid, codec::Codec}; -use std::sync::{Arc, RwLock, RwLockReadGuard, RwLockWriteGuard}; -use std::{collections::BTreeMap, convert::Infallible}; +mod memory; +mod traits; -pub trait Store, C: Codec + Into + TryFrom> { - type InvocationStoreError; - - fn get( - &self, - cid: Cid, - ) -> Result>>, Self::InvocationStoreError>; - - fn put( - &self, - cid: Cid, - invocation: Invocation, - ) -> Result<(), Self::InvocationStoreError>; - - fn has(&self, cid: Cid) -> Result { - Ok(self.get(cid).is_ok()) - } -} - -impl< - S: Store, - T, - DID: Did, - V: varsig::Header, - C: Codec + Into + TryFrom, - > Store for &S -{ - type InvocationStoreError = >::InvocationStoreError; - - fn get( - &self, - cid: Cid, - ) -> Result< - Option>>, - >::InvocationStoreError, - > { - (**self).get(cid) - } - - fn put( - &self, - cid: Cid, - invocation: Invocation, - ) -> Result<(), >::InvocationStoreError> { - (**self).put(cid, invocation) - } -} - -#[derive(Debug, Clone)] -pub struct MemoryStore< - T = crate::ability::preset::Preset, - DID: crate::did::Did = crate::did::preset::Verifier, - V: varsig::Header = varsig::header::Preset, - C: Codec + TryFrom + Into = varsig::encoding::Preset, -> { - inner: Arc>>, -} - -#[derive(Debug, Clone, PartialEq)] -pub struct MemoryStoreInner< - T = crate::ability::preset::Preset, - DID: crate::did::Did = crate::did::preset::Verifier, - V: varsig::Header = varsig::header::Preset, - C: Codec + TryFrom + Into = varsig::encoding::Preset, -> { - store: BTreeMap>>, -} - -impl, Enc: Codec + Into + TryFrom> - MemoryStore -{ - fn read(&self) -> RwLockReadGuard<'_, MemoryStoreInner> { - match self.inner.read() { - Ok(guard) => guard, - Err(poison) => { - // There's no logic errors through lock poisoning in our case - poison.into_inner() - } - } - } - - fn write(&self) -> RwLockWriteGuard<'_, MemoryStoreInner> { - match self.inner.write() { - Ok(guard) => guard, - Err(poison) => { - // There's no logic errors through lock poisoning in our case - poison.into_inner() - } - } - } -} - -impl, Enc: Codec + Into + TryFrom> Default - for MemoryStore -{ - fn default() -> Self { - Self { - inner: Arc::new(RwLock::new(MemoryStoreInner { - store: BTreeMap::new(), - })), - } - } -} - -impl, Enc: Codec + Into + TryFrom> - Store for MemoryStore -{ - type InvocationStoreError = Infallible; - - fn get( - &self, - cid: Cid, - ) -> Result>>, Self::InvocationStoreError> { - Ok(self.read().store.get(&cid).cloned()) - } - - fn put( - &self, - cid: Cid, - invocation: Invocation, - ) -> Result<(), Self::InvocationStoreError> { - self.write().store.insert(cid, Arc::new(invocation)); - Ok(()) - } -} +pub use memory::{MemoryStore, MemoryStoreInner}; +pub use traits::Store; diff --git a/src/invocation/store/memory.rs b/src/invocation/store/memory.rs new file mode 100644 index 00000000..2ae9a360 --- /dev/null +++ b/src/invocation/store/memory.rs @@ -0,0 +1,83 @@ +use crate::{crypto::varsig, did::Did, invocation::Invocation}; +use super::Store; +use libipld_core::{cid::Cid, codec::Codec}; +use std::sync::{Arc, RwLock, RwLockReadGuard, RwLockWriteGuard}; +use std::{collections::BTreeMap, convert::Infallible}; + +#[derive(Debug, Clone)] +pub struct MemoryStore< + T = crate::ability::preset::Preset, + DID: crate::did::Did = crate::did::preset::Verifier, + V: varsig::Header = varsig::header::Preset, + C: Codec + TryFrom + Into = varsig::encoding::Preset, +> { + inner: Arc>>, +} + +#[derive(Debug, Clone, PartialEq)] +pub struct MemoryStoreInner< + T = crate::ability::preset::Preset, + DID: crate::did::Did = crate::did::preset::Verifier, + V: varsig::Header = varsig::header::Preset, + C: Codec + TryFrom + Into = varsig::encoding::Preset, +> { + store: BTreeMap>>, +} + +impl, Enc: Codec + Into + TryFrom> + MemoryStore +{ + fn read(&self) -> RwLockReadGuard<'_, MemoryStoreInner> { + match self.inner.read() { + Ok(guard) => guard, + Err(poison) => { + // There's no logic errors through lock poisoning in our case + poison.into_inner() + } + } + } + + fn write(&self) -> RwLockWriteGuard<'_, MemoryStoreInner> { + match self.inner.write() { + Ok(guard) => guard, + Err(poison) => { + // There's no logic errors through lock poisoning in our case + poison.into_inner() + } + } + } +} + +impl, Enc: Codec + Into + TryFrom> Default + for MemoryStore +{ + fn default() -> Self { + Self { + inner: Arc::new(RwLock::new(MemoryStoreInner { + store: BTreeMap::new(), + })), + } + } +} + +impl, Enc: Codec + Into + TryFrom> + Store for MemoryStore +{ + type InvocationStoreError = Infallible; + + fn get( + &self, + cid: Cid, + ) -> Result>>, Self::InvocationStoreError> { + Ok(self.read().store.get(&cid).cloned()) + } + + fn put( + &self, + cid: Cid, + invocation: Invocation, + ) -> Result<(), Self::InvocationStoreError> { + self.write().store.insert(cid, Arc::new(invocation)); + Ok(()) + } +} diff --git a/src/invocation/store/traits.rs b/src/invocation/store/traits.rs new file mode 100644 index 00000000..6d3fc723 --- /dev/null +++ b/src/invocation/store/traits.rs @@ -0,0 +1,51 @@ +use crate::{crypto::varsig, did::Did, invocation::Invocation}; +use libipld_core::{cid::Cid, codec::Codec}; +use std::sync::Arc; + +pub trait Store, C: Codec + Into + TryFrom> { + type InvocationStoreError; + + fn get( + &self, + cid: Cid, + ) -> Result>>, Self::InvocationStoreError>; + + fn put( + &self, + cid: Cid, + invocation: Invocation, + ) -> Result<(), Self::InvocationStoreError>; + + fn has(&self, cid: Cid) -> Result { + Ok(self.get(cid).is_ok()) + } +} + +impl< + S: Store, + T, + DID: Did, + V: varsig::Header, + C: Codec + Into + TryFrom, + > Store for &S +{ + type InvocationStoreError = >::InvocationStoreError; + + fn get( + &self, + cid: Cid, + ) -> Result< + Option>>, + >::InvocationStoreError, + > { + (**self).get(cid) + } + + fn put( + &self, + cid: Cid, + invocation: Invocation, + ) -> Result<(), >::InvocationStoreError> { + (**self).put(cid, invocation) + } +} From 279f17c67cd61bc2219fc67e8b63adb9f4e354cb Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Sun, 24 Mar 2024 21:01:25 -0700 Subject: [PATCH 172/188] Save WIP writng more tests --- src/delegation/store/memory.rs | 33 ++++++------------------ src/invocation/agent.rs | 46 ++++++++-------------------------- 2 files changed, 18 insertions(+), 61 deletions(-) diff --git a/src/delegation/store/memory.rs b/src/delegation/store/memory.rs index 9dcf04b3..a0cd4ec1 100644 --- a/src/delegation/store/memory.rs +++ b/src/delegation/store/memory.rs @@ -198,6 +198,7 @@ where Ok(()) } + // FIXME take a PayloadBuilder fn get_chain( &self, aud: &DID, @@ -226,37 +227,20 @@ where format!("{}/", command) }; - // TODO Vec> - // parent_candidate_stack.push(sub_candidates.iter()); // .chain(powerline_candidates.iter())); - - // Pseudocode: - // If empty, pop: - // if pop fials, you're out of stuff - // If - 'outer: loop { - dbg!("OUTER"); if let Some(parent_cid_candidates) = parent_candidate_stack.last_mut() { - dbg!("SOME INNER"); - for cid in parent_cid_candidates.clone() { - dbg!("INNER", cid.to_string()); - } if parent_cid_candidates.clone().collect::>().is_empty() { - dbg!("EMPTY"); parent_candidate_stack.pop(); continue; } 'inner: for cid in parent_cid_candidates { - dbg!("BBBBBBBBBBBBBBBBBBBBBB"); - dbg!(cid.to_string()); // CHECKS if read_tx.revocations.contains(cid) { continue; } if let Some(delegation) = read_tx.ucans.get(cid) { - dbg!("EEEEEEEEEEEEEEE"); if delegation.check_time(now).is_err() { continue; } @@ -292,7 +276,6 @@ where // Hit a root delegation, AKA base case if &Some(issuer.clone()) == delegation.subject() { - dbg!("HHHHHHHHHHHH"); break 'outer; } @@ -852,7 +835,13 @@ mod tests { store.insert(alice_to_bob.clone())?; let got: Vec = store - .get_chain(&dave, &None, "/".into(), vec![], SystemTime::now()) + .get_chain( + &dave, + &Some(alice.clone()), + "/".into(), + vec![], + SystemTime::now(), + ) .map_err(|e| e.to_string())? .ok_or("failed during proof lookup")? .iter() @@ -860,12 +849,6 @@ mod tests { .cloned() .collect(); - dbg!("THERE!!!!!!!!!!!!!!!!!"); - - for cid in &got { - dbg!(cid.to_string()); - } - pretty::assert_eq!( got, vec![ diff --git a/src/invocation/agent.rs b/src/invocation/agent.rs index ee7e44d1..158f49c2 100644 --- a/src/invocation/agent.rs +++ b/src/invocation/agent.rs @@ -613,7 +613,6 @@ mod tests { struct Ctx { varsig_header: crate::crypto::varsig::header::Preset, - powerline_len: usize, dnslink_len: usize, inv_store: crate::invocation::store::MemoryStore, del_store: crate::delegation::store::MemoryStore, @@ -693,21 +692,19 @@ mod tests { del_store.insert(server_to_device.clone())?; del_store.insert(dnslink_to_account.clone())?; - let proofs_for_powerline: Vec = del_store - .get_chain(&device, &None, "/".into(), vec![], SystemTime::now())? + let chain_for_dnslink: Vec = del_store + .get_chain( + &device, + &Some(dnslink.clone()), + "/".into(), + vec![], + SystemTime::now(), + )? .ok_or("failed during proof lookup")? .iter() .map(|x| x.0.clone()) .collect(); - let chain_for_dnslink = del_store.get_chain( - &device, - &Some(dnslink.clone()), - "/".into(), - vec![], - SystemTime::now(), - ); - // 4. [dnslink -d-> account -*-> server -a-> device] let account_invocation = crate::Invocation::try_sign( &device_signer, @@ -717,30 +714,14 @@ mod tests { .issuer(device.clone()) .audience(Some(server.clone())) .ability(AccountManage) - .proofs(proofs_for_powerline.clone()) + .proofs(chain_for_dnslink.clone()) .build()?, )?; - dbg!("==================="); - dbg!(proofs_for_powerline.len()); - dbg!(">>>>>>>>>>>>>>>>>."); - dbg!(account_to_server.cid()?.to_string()); - dbg!(server_to_device.cid()?.to_string()); - dbg!(dnslink_to_account.cid()?.to_string()); - - dbg!("<<<<<<<<<<<<<<<<<<"); - for prf_cid in &proofs_for_powerline { - dbg!(prf_cid.to_string()); - } - - let powerline_len = proofs_for_powerline.len(); - let dnslink_len = chain_for_dnslink? - .ok_or("failed while finding DNSLink delegtaions")? - .len(); + let dnslink_len = chain_for_dnslink.len(); Ok(Ctx { varsig_header, - powerline_len, dnslink_len, inv_store, del_store, @@ -752,13 +733,6 @@ mod tests { }) } - #[test_log::test] - fn test_chain_len() -> TestResult { - let ctx = setup_test_chain()?; - assert_eq!((ctx.powerline_len, ctx.dnslink_len), (3, 3)); - Ok(()) - } - #[test_log::test] fn test_chain_ok() -> TestResult { let ctx = setup_test_chain()?; From 6affc01c5dd5ce5323c84f40a3d1dd7cc3c4f131 Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Mon, 25 Mar 2024 15:41:35 -0700 Subject: [PATCH 173/188] Not pretty, but got that last selector passing --- .../delegation/policy/selector/select.txt | 7 + src/delegation/policy/predicate.rs | 1 - src/delegation/policy/selector.rs | 12 ++ src/delegation/policy/selector/select.rs | 152 ++++++++++++++---- 4 files changed, 141 insertions(+), 31 deletions(-) create mode 100644 proptest-regressions/delegation/policy/selector/select.txt diff --git a/proptest-regressions/delegation/policy/selector/select.txt b/proptest-regressions/delegation/policy/selector/select.txt new file mode 100644 index 00000000..716e8c0c --- /dev/null +++ b/proptest-regressions/delegation/policy/selector/select.txt @@ -0,0 +1,7 @@ +# Seeds for failure cases proptest has generated in the past. It is +# automatically read and these particular cases re-run before any +# novel cases are generated. +# +# It is recommended to check this file in to source control so that +# everyone who runs the test benefits from these saved cases. +cc 6496f8ae07f0fa0d57c9bc4d581551bc9940c50fe830880006156471a72e806b # shrinks to data = Newtype(null), more = [ArrayIndex(0)] diff --git a/src/delegation/policy/predicate.rs b/src/delegation/policy/predicate.rs index 12cb7eae..30c9c0dd 100644 --- a/src/delegation/policy/predicate.rs +++ b/src/delegation/policy/predicate.rs @@ -1132,7 +1132,6 @@ mod tests { #[test_log::test] fn test_eq_dot_field_inner_try_null() -> TestResult { - // FIXME double check against jq let p = Predicate::Equal(Select::from_str(".nope?.not").unwrap(), Ipld::Null.into()); assert!(p.run(&email())?); diff --git a/src/delegation/policy/selector.rs b/src/delegation/policy/selector.rs index 28af8f2e..acba9a2c 100644 --- a/src/delegation/policy/selector.rs +++ b/src/delegation/policy/selector.rs @@ -200,6 +200,18 @@ mod tests { Ok(()) } + #[test_log::test] + fn test_inner_try_is_null() -> TestResult { + pretty::assert_eq!( + Selector::from_str(".nope?.not"), + Ok(Selector(vec![ + Filter::Try(Box::new(Filter::Field("nope".into()))), + Filter::Field("not".into()) + ])) + ); + Ok(()) + } + #[test_log::test] fn test_dot_many_tries_and_dot_field() -> TestResult { pretty::assert_eq!( diff --git a/src/delegation/policy/selector/select.rs b/src/delegation/policy/selector/select.rs index 8be9fb08..7506cc63 100644 --- a/src/delegation/policy/selector/select.rs +++ b/src/delegation/policy/selector/select.rs @@ -46,9 +46,9 @@ impl Select { impl Select { pub fn get(self, ctx: &Ipld) -> Result { - self.filters - .iter() - .try_fold((ctx.clone(), vec![]), |(ipld, mut seen_ops), op| { + let got = self.filters.iter().try_fold( + (ctx.clone(), vec![], false), + |(ipld, mut seen_ops, is_try), op| { seen_ops.push(op); match op { @@ -57,70 +57,95 @@ impl Select { let ipld: Ipld = Select::::new(vec![op]).get(ctx).unwrap_or(Ipld::Null); - Ok((ipld, seen_ops)) + Ok((ipld, seen_ops.clone(), true)) } Filter::ArrayIndex(i) => { let result = { match ipld { Ipld::List(xs) => { if i.abs() as usize > xs.len() { - return Err(SelectorError::from_refs( - &seen_ops, - SelectorErrorReason::IndexOutOfBounds, + return Err(( + is_try, + SelectorError::from_refs( + &seen_ops, + SelectorErrorReason::IndexOutOfBounds, + ), )); }; xs.get((xs.len() as i32 + *i) as usize) - .ok_or(SelectorError::from_refs( - &seen_ops, - SelectorErrorReason::IndexOutOfBounds, + .ok_or(( + is_try, + SelectorError::from_refs( + &seen_ops, + SelectorErrorReason::IndexOutOfBounds, + ), )) .cloned() } - // FIXME behaviour on maps? type error - _ => Err(SelectorError::from_refs( - &seen_ops, - SelectorErrorReason::NotAList, + _ => Err(( + is_try, + SelectorError::from_refs( + &seen_ops, + SelectorErrorReason::NotAList, + ), )), } }; - Ok((result?, seen_ops)) + Ok((result?, seen_ops.clone(), is_try)) } Filter::Field(k) => { let result = match ipld { Ipld::Map(xs) => xs .get(k) - .ok_or(SelectorError::from_refs( - &seen_ops, - SelectorErrorReason::KeyNotFound, + .ok_or(( + is_try, + SelectorError::from_refs( + &seen_ops, + SelectorErrorReason::KeyNotFound, + ), )) .cloned(), - _ => Err(SelectorError::from_refs( - &seen_ops, - SelectorErrorReason::NotAMap, + _ => Err(( + is_try, + SelectorError::from_refs(&seen_ops, SelectorErrorReason::NotAMap), )), }; - Ok((result?, seen_ops)) + Ok((result?, seen_ops.clone(), is_try)) } Filter::Values => { let result = match ipld { Ipld::List(xs) => Ok(Ipld::List(xs)), Ipld::Map(xs) => Ok(Ipld::List(xs.values().cloned().collect())), - _ => Err(SelectorError::from_refs( - &seen_ops, - SelectorErrorReason::NotACollection, + _ => Err(( + is_try, + SelectorError::from_refs( + &seen_ops, + SelectorErrorReason::NotACollection, + ), )), }; - Ok((result?, seen_ops)) + Ok((result?, seen_ops.clone(), is_try)) } } - }) - .and_then(|(ipld, ref path)| { - T::try_select(ipld).map_err(|e| SelectorError::from_refs(path, e)) - }) + }, + ); + + let (ipld, path) = match got { + Ok((ipld, seen_ops, _)) => Ok((ipld, seen_ops)), + Err((is_try, ref e @ SelectorError { ref selector, .. })) => { + if is_try { + Ok((Ipld::Null, selector.0.iter().map(|x| x).collect::>())) + } else { + Err(e.clone()) + } + } + }?; + + T::try_select(ipld).map_err(|e| SelectorError::from_refs(&path, e)) } } @@ -166,3 +191,70 @@ impl Arbitrary for Select { .boxed() } } + +#[cfg(test)] +mod tests { + use super::*; + use crate::ipld; + use pretty_assertions as pretty; + use proptest::prelude::*; + use testresult::TestResult; + + mod get { + use super::*; + + fn nested_data() -> Ipld { + Ipld::Map( + vec![ + ("name".to_string(), Ipld::String("Alice".to_string())), + ("age".to_string(), Ipld::Integer(42)), + ( + "friends".to_string(), + Ipld::List(vec![ + Ipld::String("Bob".to_string()), + Ipld::String("Charlie".to_string()), + ]), + ), + ] + .into_iter() + .collect(), + ) + } + + proptest! { + #[test_log::test] + fn test_identity(data: ipld::Newtype) { + let selector = Select::::from_str(".")?; + prop_assert_eq!(selector.get(&data.0)?, data); + } + + #[test_log::test] + fn test_try_missing_is_null(data: ipld::Newtype) { + let selector = Select::::from_str(".foo?")?; + let cleaned_data = match data.0.clone() { + Ipld::Map(mut m) => { + m.remove("foo").map_or(Ipld::Null, |v| v) + } + ipld => ipld + }; + prop_assert_eq!(selector.get(&cleaned_data)?, Ipld::Null); + } + + #[test_log::test] + fn test_try_missing_plus_trailing_is_null(data: ipld::Newtype, more: Vec) { + let mut filters = vec![Filter::Try(Box::new(Filter::Field("foo".into())))]; + filters.append(&mut more.clone()); + + let selector: Select = Select::new(filters); + + let cleaned_data = match data.0.clone() { + Ipld::Map(mut m) => { + m.remove("foo").map_or(Ipld::Null, |v| v) + } + ipld => ipld + }; + prop_assert_eq!(selector.get(&cleaned_data)?, Ipld::Null); + } + } + } +} From bc0a275fab07ec246f75a08e067e75b8a6268a4f Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Mon, 25 Mar 2024 19:21:42 -0700 Subject: [PATCH 174/188] Add automatic CID conversaion error --- src/delegation/store/traits.rs | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/src/delegation/store/traits.rs b/src/delegation/store/traits.rs index c917dc20..0e79f1fe 100644 --- a/src/delegation/store/traits.rs +++ b/src/delegation/store/traits.rs @@ -11,6 +11,7 @@ use libipld_core::ipld::Ipld; use libipld_core::{cid::Cid, codec::Codec}; use nonempty::NonEmpty; use std::{fmt::Debug, sync::Arc}; +use thiserror::Error; use web_time::SystemTime; pub trait Store + Clone, C: Codec + TryFrom + Into> @@ -26,8 +27,12 @@ where cid: &Cid, ) -> Result>>, Self::DelegationStoreError>; - fn insert(&self, delegation: Delegation) -> Result<(), Self::DelegationStoreError> { - self.insert_keyed(delegation.cid().expect("FIXME"), delegation) + fn insert( + &self, + delegation: Delegation, + ) -> Result<(), CannotCidOr> { + self.insert_keyed(delegation.cid()?, delegation) + .map_err(CannotCidOr::StoreError) } fn insert_keyed( @@ -128,3 +133,12 @@ where (**self).get_chain(audience, subject, command, policy, now) } } + +#[derive(Debug, Error)] +pub enum CannotCidOr { + #[error("Cannot make CID from delegation based on supplied Varsig")] + CannotMakeCid(#[from] libipld_core::error::Error), + + #[error("Store error: {0}")] + StoreError(E), +} From 96b0fe10c88677953dc0cd2b3dc34932c4f85f31 Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Mon, 25 Mar 2024 19:25:55 -0700 Subject: [PATCH 175/188] Receipts as invocations --- src/ability/ucan/assert.rs | 11 ++--- src/delegation/policy/selector/filter.rs | 2 +- src/delegation/policy/selector/select.rs | 61 ++++++++++++++---------- 3 files changed, 43 insertions(+), 31 deletions(-) diff --git a/src/ability/ucan/assert.rs b/src/ability/ucan/assert.rs index 62946c23..5fe7c874 100644 --- a/src/ability/ucan/assert.rs +++ b/src/ability/ucan/assert.rs @@ -1,6 +1,6 @@ use crate::ability::command::Command; use crate::task::Task; -use libipld_core::{cid::Cid, ipld::Ipld}; +use libipld_core::cid::Cid; // Things that you can assert include content and receipts @@ -8,12 +8,11 @@ use libipld_core::{cid::Cid, ipld::Ipld}; pub struct Ran { ran: Cid, out: Box>, - fx: Vec, // FIXME may be more than "just" a task + next: Vec, // FIXME may be more than "just" a task } impl Command for Ran { - const COMMAND: &'static str = "/ucan/assert/ran"; - // const COMMAND: &'static str = "/ucan/ran";???? + const COMMAND: &'static str = "/ucan/ran"; } /////////////// @@ -23,8 +22,8 @@ impl Command for Ran { #[derive(Debug, PartialEq)] pub struct Claim { claim: T, -} // Where Ipld: From +} impl Command for Claim { - const COMMAND: &'static str = "/ucan/assert/claim"; + const COMMAND: &'static str = "/ucan/claim"; } diff --git a/src/delegation/policy/selector/filter.rs b/src/delegation/policy/selector/filter.rs index 96343a5a..7e69ec8e 100644 --- a/src/delegation/policy/selector/filter.rs +++ b/src/delegation/policy/selector/filter.rs @@ -297,7 +297,7 @@ mod tests { use super::*; proptest! { - #[test] + #[test_log::test] fn test_filter_round_trip(filter: Filter) { let serialized = filter.to_string(); let deserialized = serialized.parse(); diff --git a/src/delegation/policy/selector/select.rs b/src/delegation/policy/selector/select.rs index 7506cc63..9d5270ed 100644 --- a/src/delegation/policy/selector/select.rs +++ b/src/delegation/policy/selector/select.rs @@ -1,7 +1,6 @@ -use super::Selector; // FIXME cycle? +use super::Selector; use super::{error::SelectorErrorReason, filter::Filter, Selectable, SelectorError}; use libipld_core::ipld::Ipld; -use serde::{Deserialize, Serialize}; use std::cmp::Ordering; use std::fmt; use std::str::FromStr; @@ -200,27 +199,35 @@ mod tests { use proptest::prelude::*; use testresult::TestResult; + fn simple() -> Ipld { + libipld::ipld!({ + "foo": 42, + "bar": "baz", + "qux": true + }) + } + + fn email() -> Ipld { + libipld::ipld!({ + "from": "alice@example.com", + "to": ["bob@example.com", "fraud@example.com"], + "cc": ["carol@example.com"], + "subject": "Quarterly Reports", + "body": "Here's Q2 the reports ..." + }) + } + + fn nested_data() -> Ipld { + libipld::ipld!({ + "name": "Alice", + "age": 42, + "friends": ["Bob", "Charlie"] + }) + } + mod get { use super::*; - fn nested_data() -> Ipld { - Ipld::Map( - vec![ - ("name".to_string(), Ipld::String("Alice".to_string())), - ("age".to_string(), Ipld::Integer(42)), - ( - "friends".to_string(), - Ipld::List(vec![ - Ipld::String("Bob".to_string()), - Ipld::String("Charlie".to_string()), - ]), - ), - ] - .into_iter() - .collect(), - ) - } - proptest! { #[test_log::test] fn test_identity(data: ipld::Newtype) { @@ -248,13 +255,19 @@ mod tests { let selector: Select = Select::new(filters); let cleaned_data = match data.0.clone() { - Ipld::Map(mut m) => { - m.remove("foo").map_or(Ipld::Null, |v| v) - } - ipld => ipld + Ipld::Map(mut m) => m.remove("foo").map_or(Ipld::Null, |v| v), + ipld => ipld, }; prop_assert_eq!(selector.get(&cleaned_data)?, Ipld::Null); } } + + #[test_log::test] + fn test_eq_dot_field_ending_try_null() -> TestResult { + let s = Select::from_str(".from.not?")?; + + pretty::assert_eq!(s.get(&email()), Ok(Ipld::Null)); + Ok(()) + } } } From 4913ad0d6c4c389c0374773b97de546c0c91f428 Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Mon, 25 Mar 2024 19:42:35 -0700 Subject: [PATCH 176/188] Require Subject on get_chain --- src/ability/ucan/assert.rs | 11 +++-- src/delegation/agent.rs | 38 +++++++-------- src/delegation/policy/selector/filter.rs | 2 +- src/delegation/policy/selector/select.rs | 61 ++++++++++-------------- src/delegation/store/memory.rs | 32 ++++++------- src/delegation/store/traits.rs | 8 ++-- src/invocation/agent.rs | 4 +- 7 files changed, 68 insertions(+), 88 deletions(-) diff --git a/src/ability/ucan/assert.rs b/src/ability/ucan/assert.rs index 5fe7c874..62946c23 100644 --- a/src/ability/ucan/assert.rs +++ b/src/ability/ucan/assert.rs @@ -1,6 +1,6 @@ use crate::ability::command::Command; use crate::task::Task; -use libipld_core::cid::Cid; +use libipld_core::{cid::Cid, ipld::Ipld}; // Things that you can assert include content and receipts @@ -8,11 +8,12 @@ use libipld_core::cid::Cid; pub struct Ran { ran: Cid, out: Box>, - next: Vec, // FIXME may be more than "just" a task + fx: Vec, // FIXME may be more than "just" a task } impl Command for Ran { - const COMMAND: &'static str = "/ucan/ran"; + const COMMAND: &'static str = "/ucan/assert/ran"; + // const COMMAND: &'static str = "/ucan/ran";???? } /////////////// @@ -22,8 +23,8 @@ impl Command for Ran { #[derive(Debug, PartialEq)] pub struct Claim { claim: T, -} +} // Where Ipld: From impl Command for Claim { - const COMMAND: &'static str = "/ucan/claim"; + const COMMAND: &'static str = "/ucan/assert/claim"; } diff --git a/src/delegation/agent.rs b/src/delegation/agent.rs index 16482c14..23c1b0cf 100644 --- a/src/delegation/agent.rs +++ b/src/delegation/agent.rs @@ -62,7 +62,7 @@ where pub fn delegate( &self, audience: DID, - subject: Option, + subject: &DID, via: Option, command: String, new_policy: Vec, @@ -75,25 +75,21 @@ where let mut salt = self.did.clone().to_string().into_bytes(); let nonce = Nonce::generate_12(&mut salt); - if let Some(ref sub) = subject { - if sub == &self.did { - let payload: Payload = Payload { - issuer: self.did.clone(), - audience, - subject, - via, - command, - metadata, - nonce, - expiration: expiration.into(), - not_before: not_before.map(Into::into), - policy: new_policy, - }; - - return Ok( - Delegation::try_sign(&self.signer, varsig_header, payload).expect("FIXME") - ); - } + if *subject == self.did { + let payload: Payload = Payload { + issuer: self.did.clone(), + audience, + subject: Some(subject.clone()), + via, + command, + metadata, + nonce, + expiration: expiration.into(), + not_before: not_before.map(Into::into), + policy: new_policy, + }; + + return Ok(Delegation::try_sign(&self.signer, varsig_header, payload).expect("FIXME")); } let proofs = &self @@ -109,7 +105,7 @@ where let payload: Payload = Payload { issuer: self.did.clone(), audience, - subject, + subject: Some(subject.clone()), via, command, policy, diff --git a/src/delegation/policy/selector/filter.rs b/src/delegation/policy/selector/filter.rs index 7e69ec8e..96343a5a 100644 --- a/src/delegation/policy/selector/filter.rs +++ b/src/delegation/policy/selector/filter.rs @@ -297,7 +297,7 @@ mod tests { use super::*; proptest! { - #[test_log::test] + #[test] fn test_filter_round_trip(filter: Filter) { let serialized = filter.to_string(); let deserialized = serialized.parse(); diff --git a/src/delegation/policy/selector/select.rs b/src/delegation/policy/selector/select.rs index 9d5270ed..7506cc63 100644 --- a/src/delegation/policy/selector/select.rs +++ b/src/delegation/policy/selector/select.rs @@ -1,6 +1,7 @@ -use super::Selector; +use super::Selector; // FIXME cycle? use super::{error::SelectorErrorReason, filter::Filter, Selectable, SelectorError}; use libipld_core::ipld::Ipld; +use serde::{Deserialize, Serialize}; use std::cmp::Ordering; use std::fmt; use std::str::FromStr; @@ -199,35 +200,27 @@ mod tests { use proptest::prelude::*; use testresult::TestResult; - fn simple() -> Ipld { - libipld::ipld!({ - "foo": 42, - "bar": "baz", - "qux": true - }) - } - - fn email() -> Ipld { - libipld::ipld!({ - "from": "alice@example.com", - "to": ["bob@example.com", "fraud@example.com"], - "cc": ["carol@example.com"], - "subject": "Quarterly Reports", - "body": "Here's Q2 the reports ..." - }) - } - - fn nested_data() -> Ipld { - libipld::ipld!({ - "name": "Alice", - "age": 42, - "friends": ["Bob", "Charlie"] - }) - } - mod get { use super::*; + fn nested_data() -> Ipld { + Ipld::Map( + vec![ + ("name".to_string(), Ipld::String("Alice".to_string())), + ("age".to_string(), Ipld::Integer(42)), + ( + "friends".to_string(), + Ipld::List(vec![ + Ipld::String("Bob".to_string()), + Ipld::String("Charlie".to_string()), + ]), + ), + ] + .into_iter() + .collect(), + ) + } + proptest! { #[test_log::test] fn test_identity(data: ipld::Newtype) { @@ -255,19 +248,13 @@ mod tests { let selector: Select = Select::new(filters); let cleaned_data = match data.0.clone() { - Ipld::Map(mut m) => m.remove("foo").map_or(Ipld::Null, |v| v), - ipld => ipld, + Ipld::Map(mut m) => { + m.remove("foo").map_or(Ipld::Null, |v| v) + } + ipld => ipld }; prop_assert_eq!(selector.get(&cleaned_data)?, Ipld::Null); } } - - #[test_log::test] - fn test_eq_dot_field_ending_try_null() -> TestResult { - let s = Select::from_str(".from.not?")?; - - pretty::assert_eq!(s.get(&email()), Ok(Ipld::Null)); - Ok(()) - } } } diff --git a/src/delegation/store/memory.rs b/src/delegation/store/memory.rs index a0cd4ec1..bdc7e6e7 100644 --- a/src/delegation/store/memory.rs +++ b/src/delegation/store/memory.rs @@ -198,11 +198,10 @@ where Ok(()) } - // FIXME take a PayloadBuilder fn get_chain( &self, aud: &DID, - subject: &Option, + subject: &DID, command: String, policy: Vec, now: SystemTime, @@ -213,7 +212,10 @@ where let read_tx = self.read(); let all_powerlines = read_tx.index.get(&None).unwrap_or(&blank_map); - let all_aud_for_subject = read_tx.index.get(subject).unwrap_or(&blank_map); + let all_aud_for_subject = read_tx + .index + .get(&Some(subject.clone())) + .unwrap_or(&blank_map); let powerline_candidates = all_powerlines.get(aud).unwrap_or(&blank_set); let sub_candidates = all_aud_for_subject.get(aud).unwrap_or(&blank_set); @@ -411,13 +413,14 @@ mod tests { #[test_log::test] fn test_simple_fail() -> TestResult { let (server, _server_signer) = gen_did(); + let (nope, _nope_signer) = gen_did(); let store = MemoryStore::< did::preset::Verifier, varsig::header::Preset, varsig::encoding::Preset, >::default(); - let got = store.get_chain(&server, &None, "/".into(), vec![], SystemTime::now())?; + let got = store.get_chain(&server, &nope, "/".into(), vec![], SystemTime::now())?; pretty::assert_eq!(got, None); Ok(()) @@ -449,7 +452,7 @@ mod tests { store.insert(deleg.clone())?; - let got = store.get_chain(&bob, &Some(alice), "/".into(), vec![], SystemTime::now())?; + let got = store.get_chain(&bob, &alice, "/".into(), vec![], SystemTime::now())?; pretty::assert_eq!(got, Some(nonempty![(deleg.cid()?, Arc::new(deleg))].into())); Ok(()) } @@ -509,7 +512,7 @@ mod tests { store.insert(more_noise.clone())?; - let got = store.get_chain(&bob, &Some(alice), "/".into(), vec![], SystemTime::now())?; + let got = store.get_chain(&bob, &alice, "/".into(), vec![], SystemTime::now())?; pretty::assert_eq!(got, Some(nonempty![(deleg.cid()?, Arc::new(deleg))].into())); Ok(()) } @@ -555,8 +558,7 @@ mod tests { store.insert(deleg_2.clone())?; - let got = - store.get_chain(&carol, &Some(alice), "/".into(), vec![], SystemTime::now())?; + let got = store.get_chain(&carol, &alice, "/".into(), vec![], SystemTime::now())?; pretty::assert_eq!( got, @@ -614,7 +616,7 @@ mod tests { let got = store.get_chain( &carol, - &Some(alice), + &alice, "/test/me/now".into(), vec![], SystemTime::now(), @@ -677,7 +679,7 @@ mod tests { let got = store.get_chain( &carol, - &Some(alice), + &alice, "/test/me/now".into(), vec![], SystemTime::now(), @@ -751,7 +753,7 @@ mod tests { store.insert(alice_to_bob.clone())?; let got: Vec = store - .get_chain(&dave, &Some(alice), "/".into(), vec![], SystemTime::now()) + .get_chain(&dave, &alice, "/".into(), vec![], SystemTime::now()) .map_err(|e| e.to_string())? .ok_or("failed during proof lookup")? .iter() @@ -835,13 +837,7 @@ mod tests { store.insert(alice_to_bob.clone())?; let got: Vec = store - .get_chain( - &dave, - &Some(alice.clone()), - "/".into(), - vec![], - SystemTime::now(), - ) + .get_chain(&dave, &alice.clone(), "/".into(), vec![], SystemTime::now()) .map_err(|e| e.to_string())? .ok_or("failed during proof lookup")? .iter() diff --git a/src/delegation/store/traits.rs b/src/delegation/store/traits.rs index 0e79f1fe..0ace1c73 100644 --- a/src/delegation/store/traits.rs +++ b/src/delegation/store/traits.rs @@ -49,7 +49,7 @@ where fn get_chain( &self, audience: &DID, - subject: &Option, + subject: &DID, command: String, policy: Vec, now: SystemTime, @@ -58,7 +58,7 @@ where fn get_chain_cids( &self, audience: &DID, - subject: &Option, + subject: &DID, command: String, policy: Vec, now: SystemTime, @@ -75,7 +75,7 @@ where policy: Vec, now: SystemTime, ) -> Result { - self.get_chain(audience, &Some(issuer), command, policy, now) + self.get_chain(audience, &issuer, command, policy, now) .map(|chain| chain.is_some()) } @@ -124,7 +124,7 @@ where fn get_chain( &self, audience: &DID, - subject: &Option, + subject: &DID, command: String, policy: Vec, now: SystemTime, diff --git a/src/invocation/agent.rs b/src/invocation/agent.rs index 158f49c2..3929e728 100644 --- a/src/invocation/agent.rs +++ b/src/invocation/agent.rs @@ -104,7 +104,7 @@ where self.delegation_store .get_chain( &self.did, - &Some(subject.clone()), + &subject.clone(), ability.to_command(), vec![], now, @@ -695,7 +695,7 @@ mod tests { let chain_for_dnslink: Vec = del_store .get_chain( &device, - &Some(dnslink.clone()), + &dnslink.clone(), "/".into(), vec![], SystemTime::now(), From 2172b21e30e102a943a47f9abf2848cb05077602 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philipp=20Kr=C3=BCger?= Date: Thu, 21 Mar 2024 21:41:01 +0100 Subject: [PATCH 177/188] fix: Error early in `Agent::invoke` if there's no valid delegation chain --- src/invocation/agent.rs | 53 ++++++++++++++++++++++++----------------- 1 file changed, 31 insertions(+), 22 deletions(-) diff --git a/src/invocation/agent.rs b/src/invocation/agent.rs index 3929e728..6e4305d3 100644 --- a/src/invocation/agent.rs +++ b/src/invocation/agent.rs @@ -3,19 +3,21 @@ use super::{ store::Store, Invocation, }; -use crate::ability::arguments::Named; -use crate::ability::command::ToCommand; -use crate::ability::parse::ParseAbility; -use crate::delegation::Delegation; -use crate::invocation::payload::PayloadBuilder; use crate::{ - ability::{self, arguments, parse::ParseAbilityError, ucan::revoke::Revoke}, + ability::{ + self, arguments, + arguments::Named, + command::ToCommand, + parse::{ParseAbility, ParseAbilityError}, + ucan::revoke::Revoke, + }, crypto::{ signature::{self, Envelope}, varsig, Nonce, }, delegation, did::{self, Did}, + invocation::payload::PayloadBuilder, time::Timestamp, }; use enum_as_inner::EnumAsInner; @@ -102,16 +104,15 @@ where vec![] } else { self.delegation_store - .get_chain( + .get_chain_cids( &self.did, &subject.clone(), ability.to_command(), vec![], now, - ) - .map_err(InvokeError::DelegationStoreError)? - .map(|chain| chain.map(|(cid, _)| cid).into()) - .unwrap_or(vec![]) // FIXME + )? + .ok_or(InvokeError::ProofsNotFound)? + .into() }; let payload = Payload { @@ -328,7 +329,10 @@ pub enum ReceiveError< #[derive(Debug, Error)] pub enum InvokeError { #[error("delegation store error: {0}")] - DelegationStoreError(#[source] D), + DelegationStoreError(#[from] D), + + #[error("The current agent does not have the necessary proofs to invoke.")] + ProofsNotFound, #[error("store error: {0}")] SignError(#[source] signature::SignError), @@ -337,23 +341,28 @@ pub enum InvokeError { #[cfg(test)] mod tests { use super::*; - use crate::ability::crud::read::Read; - use crate::crypto::varsig; - use crate::crypto::varsig::encoding; - use crate::crypto::varsig::header; - use crate::invocation::{payload::ValidationError, Agent}; use crate::{ - ability::{arguments::Named, command::Command}, - crypto::signature::Envelope, + ability::{arguments::Named, command::Command, crud::read::Read}, + crypto::{ + signature::Envelope, + varsig, + varsig::{encoding, header}, + }, delegation::store::Store, - invocation::promise::{CantResolve, Resolvable}, + invocation::{ + payload::ValidationError, + promise::{CantResolve, Resolvable}, + Agent, + }, ipld, }; use libipld_core::{cid::Cid, ipld::Ipld}; use pretty_assertions as pretty; use rand::thread_rng; - use std::ops::{Add, Sub}; - use std::time::{Duration, SystemTime}; + use std::{ + ops::{Add, Sub}, + time::{Duration, SystemTime}, + }; use testresult::TestResult; #[derive(Debug, Clone, PartialEq)] From f894e695fa86937ae34021a4e9a8088be61b244a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philipp=20Kr=C3=BCger?= Date: Thu, 21 Mar 2024 21:46:40 +0100 Subject: [PATCH 178/188] fix: Check for delegations for the specific command, not for "/" --- src/invocation/agent.rs | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/src/invocation/agent.rs b/src/invocation/agent.rs index 6e4305d3..d6a1017a 100644 --- a/src/invocation/agent.rs +++ b/src/invocation/agent.rs @@ -104,13 +104,7 @@ where vec![] } else { self.delegation_store - .get_chain_cids( - &self.did, - &subject.clone(), - ability.to_command(), - vec![], - now, - )? + .get_chain_cids(&self.did, &subject, ability.to_command(), vec![], now)? // FIXME policy .ok_or(InvokeError::ProofsNotFound)? .into() }; From 5d26366abaf93a923656e62028eeaa42a4aee7d9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philipp=20Kr=C3=BCger?= Date: Fri, 22 Mar 2024 11:08:20 +0100 Subject: [PATCH 179/188] refactor: Rename `MissingDelegation` -> `DelegationNotFound` --- src/invocation/agent.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/invocation/agent.rs b/src/invocation/agent.rs index d6a1017a..3cad747c 100644 --- a/src/invocation/agent.rs +++ b/src/invocation/agent.rs @@ -218,7 +218,7 @@ where .map(|(d, cid)| { Ok(&d .as_ref() - .ok_or(ReceiveError::MissingDelegation(*cid))? + .ok_or(ReceiveError::DelegationNotFound(*cid))? .payload) }) .collect::>>()?; @@ -301,8 +301,8 @@ pub enum ReceiveError< > where >::InvocationStoreError: fmt::Debug, { - #[error("missing delegation: {0}")] - MissingDelegation(Cid), + #[error("couldn't find delegation: {0}")] + DelegationNotFound(Cid), #[error("encoding error: {0}")] EncodingError(#[from] libipld_core::error::Error), From 4bd583ab05c94a8d6a210e01c93a9bf75bcd4650 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philipp=20Kr=C3=BCger?= Date: Fri, 22 Mar 2024 11:14:25 +0100 Subject: [PATCH 180/188] refactor: Use `Mutex` instead of `RwLock` --- src/delegation/store/memory.rs | 46 ++++++++++++---------------------- src/invocation/store/memory.rs | 26 ++++++------------- 2 files changed, 24 insertions(+), 48 deletions(-) diff --git a/src/delegation/store/memory.rs b/src/delegation/store/memory.rs index bdc7e6e7..10b94017 100644 --- a/src/delegation/store/memory.rs +++ b/src/delegation/store/memory.rs @@ -10,10 +10,10 @@ use libipld_core::codec::Encode; use libipld_core::ipld::Ipld; use libipld_core::{cid::Cid, codec::Codec}; use nonempty::NonEmpty; -use std::sync::{Arc, Mutex, RwLock, RwLockReadGuard, RwLockWriteGuard}; use std::{ collections::{BTreeMap, BTreeSet}, convert::Infallible, + sync::{Arc, Mutex, MutexGuard}, }; use web_time::SystemTime; @@ -79,7 +79,7 @@ pub struct MemoryStore< V: varsig::Header = varsig::header::Preset, C: Codec + TryFrom + Into = varsig::encoding::Preset, > { - inner: Arc>>, + inner: Arc>>, } #[derive(Debug, Clone, PartialEq)] @@ -101,25 +101,15 @@ impl, C: Codec + TryFrom + Into usize { - self.read().ucans.len() + self.lock().ucans.len() } pub fn is_empty(&self) -> bool { - self.read().ucans.is_empty() // FIXME account for revocations? + self.lock().ucans.is_empty() // FIXME account for revocations? } - fn read(&self) -> RwLockReadGuard<'_, MemoryStoreInner> { - match self.inner.read() { - Ok(guard) => guard, - Err(poison) => { - // We ignore lock poisoning for simplicity - poison.into_inner() - } - } - } - - fn write(&self) -> RwLockWriteGuard<'_, MemoryStoreInner> { - match self.inner.write() { + fn lock(&self) -> MutexGuard<'_, MemoryStoreInner> { + match self.inner.lock() { Ok(guard) => guard, Err(poison) => { // We ignore lock poisoning for simplicity @@ -169,7 +159,7 @@ where cid: &Cid, ) -> Result>>, Self::DelegationStoreError> { // cheap Arc clone - Ok(self.read().ucans.get(cid).cloned()) + Ok(self.lock().ucans.get(cid).cloned()) // FIXME } @@ -178,23 +168,22 @@ where cid: Cid, delegation: Delegation, ) -> Result<(), Self::DelegationStoreError> { - let mut write_tx = self.write(); + let mut tx = self.lock(); - write_tx - .index + tx.index .entry(delegation.subject().clone()) .or_default() .entry(delegation.audience().clone()) .or_default() .insert(cid); - write_tx.ucans.insert(cid.clone(), Arc::new(delegation)); + tx.ucans.insert(cid.clone(), Arc::new(delegation)); Ok(()) } fn revoke(&self, cid: Cid) -> Result<(), Self::DelegationStoreError> { - self.write().revocations.insert(cid); + self.lock().revocations.insert(cid); Ok(()) } @@ -209,13 +198,10 @@ where { let blank_set = BTreeSet::new(); let blank_map = BTreeMap::new(); - let read_tx = self.read(); + let tx = self.lock(); - let all_powerlines = read_tx.index.get(&None).unwrap_or(&blank_map); - let all_aud_for_subject = read_tx - .index - .get(&Some(subject.clone())) - .unwrap_or(&blank_map); + let all_powerlines = tx.index.get(&None).unwrap_or(&blank_map); + let all_aud_for_subject = tx.index.get(&Some(subject.clone())).unwrap_or(&blank_map); let powerline_candidates = all_powerlines.get(aud).unwrap_or(&blank_set); let sub_candidates = all_aud_for_subject.get(aud).unwrap_or(&blank_set); @@ -238,11 +224,11 @@ where 'inner: for cid in parent_cid_candidates { // CHECKS - if read_tx.revocations.contains(cid) { + if tx.revocations.contains(cid) { continue; } - if let Some(delegation) = read_tx.ucans.get(cid) { + if let Some(delegation) = tx.ucans.get(cid) { if delegation.check_time(now).is_err() { continue; } diff --git a/src/invocation/store/memory.rs b/src/invocation/store/memory.rs index 2ae9a360..7c7c7196 100644 --- a/src/invocation/store/memory.rs +++ b/src/invocation/store/memory.rs @@ -1,7 +1,7 @@ -use crate::{crypto::varsig, did::Did, invocation::Invocation}; use super::Store; +use crate::{crypto::varsig, did::Did, invocation::Invocation}; use libipld_core::{cid::Cid, codec::Codec}; -use std::sync::{Arc, RwLock, RwLockReadGuard, RwLockWriteGuard}; +use std::sync::{Arc, Mutex, MutexGuard}; use std::{collections::BTreeMap, convert::Infallible}; #[derive(Debug, Clone)] @@ -11,7 +11,7 @@ pub struct MemoryStore< V: varsig::Header = varsig::header::Preset, C: Codec + TryFrom + Into = varsig::encoding::Preset, > { - inner: Arc>>, + inner: Arc>>, } #[derive(Debug, Clone, PartialEq)] @@ -27,18 +27,8 @@ pub struct MemoryStoreInner< impl, Enc: Codec + Into + TryFrom> MemoryStore { - fn read(&self) -> RwLockReadGuard<'_, MemoryStoreInner> { - match self.inner.read() { - Ok(guard) => guard, - Err(poison) => { - // There's no logic errors through lock poisoning in our case - poison.into_inner() - } - } - } - - fn write(&self) -> RwLockWriteGuard<'_, MemoryStoreInner> { - match self.inner.write() { + fn lock(&self) -> MutexGuard<'_, MemoryStoreInner> { + match self.inner.lock() { Ok(guard) => guard, Err(poison) => { // There's no logic errors through lock poisoning in our case @@ -53,7 +43,7 @@ impl, Enc: Codec + Into + TryFrom> { fn default() -> Self { Self { - inner: Arc::new(RwLock::new(MemoryStoreInner { + inner: Arc::new(Mutex::new(MemoryStoreInner { store: BTreeMap::new(), })), } @@ -69,7 +59,7 @@ impl, Enc: Codec + Into + TryFrom> &self, cid: Cid, ) -> Result>>, Self::InvocationStoreError> { - Ok(self.read().store.get(&cid).cloned()) + Ok(self.lock().store.get(&cid).cloned()) } fn put( @@ -77,7 +67,7 @@ impl, Enc: Codec + Into + TryFrom> cid: Cid, invocation: Invocation, ) -> Result<(), Self::InvocationStoreError> { - self.write().store.insert(cid, Arc::new(invocation)); + self.lock().store.insert(cid, Arc::new(invocation)); Ok(()) } } From fd0922f111ade59eb3ffee1a0ca36defc5f2fe80 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philipp=20Kr=C3=BCger?= Date: Fri, 22 Mar 2024 15:06:17 +0100 Subject: [PATCH 181/188] fix: Check for the appropriate command in `Agent::delegate` --- src/delegation/agent.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/delegation/agent.rs b/src/delegation/agent.rs index 23c1b0cf..08ee36a1 100644 --- a/src/delegation/agent.rs +++ b/src/delegation/agent.rs @@ -1,8 +1,8 @@ use super::{payload::Payload, policy::Predicate, store::Store, Delegation}; -use crate::ability::arguments::Named; -use crate::did; use crate::{ + ability::arguments::Named, crypto::{signature::Envelope, varsig, Nonce}, + did, did::Did, time::Timestamp, }; @@ -94,7 +94,7 @@ where let proofs = &self .store - .get_chain(&self.did, &subject, "/".into(), vec![], now) + .get_chain(&self.did, &subject, command.clone(), vec![], now) .map_err(DelegateError::StoreError)? .ok_or(DelegateError::ProofsNotFound)?; let to_delegate = proofs.first().1.payload(); From 0dfb2a64ce6166b3e3ada0a605a432ee5580ac3a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philipp=20Kr=C3=BCger?= Date: Fri, 22 Mar 2024 15:10:51 +0100 Subject: [PATCH 182/188] refactor: Take `command: &str` instead of by `String` in `Store::get_chain` --- src/delegation/agent.rs | 2 +- src/delegation/store/memory.rs | 9 +++++---- src/delegation/store/traits.rs | 8 ++++---- src/invocation/agent.rs | 2 +- 4 files changed, 11 insertions(+), 10 deletions(-) diff --git a/src/delegation/agent.rs b/src/delegation/agent.rs index 08ee36a1..7e9cc9c7 100644 --- a/src/delegation/agent.rs +++ b/src/delegation/agent.rs @@ -94,7 +94,7 @@ where let proofs = &self .store - .get_chain(&self.did, &subject, command.clone(), vec![], now) + .get_chain(&self.did, &subject, &command, vec![], now) .map_err(DelegateError::StoreError)? .ok_or(DelegateError::ProofsNotFound)?; let to_delegate = proofs.first().1.payload(); diff --git a/src/delegation/store/memory.rs b/src/delegation/store/memory.rs index 10b94017..91fd6102 100644 --- a/src/delegation/store/memory.rs +++ b/src/delegation/store/memory.rs @@ -10,6 +10,7 @@ use libipld_core::codec::Encode; use libipld_core::ipld::Ipld; use libipld_core::{cid::Cid, codec::Codec}; use nonempty::NonEmpty; +use std::borrow::Cow; use std::{ collections::{BTreeMap, BTreeSet}, convert::Infallible, @@ -191,8 +192,8 @@ where &self, aud: &DID, subject: &DID, - command: String, - policy: Vec, + command: &str, + policy: Vec, // FIXME now: SystemTime, ) -> Result>)>>, Self::DelegationStoreError> { @@ -210,9 +211,9 @@ where let mut hypothesis_chain = vec![]; let corrected_target_command = if command.ends_with('/') { - command + Cow::Borrowed(command) } else { - format!("{}/", command) + Cow::Owned(format!("{command}/")) }; 'outer: loop { diff --git a/src/delegation/store/traits.rs b/src/delegation/store/traits.rs index 0ace1c73..c5bc6701 100644 --- a/src/delegation/store/traits.rs +++ b/src/delegation/store/traits.rs @@ -50,7 +50,7 @@ where &self, audience: &DID, subject: &DID, - command: String, + command: &str, policy: Vec, now: SystemTime, ) -> Result>)>>, Self::DelegationStoreError>; @@ -59,7 +59,7 @@ where &self, audience: &DID, subject: &DID, - command: String, + command: &str, policy: Vec, now: SystemTime, ) -> Result>, Self::DelegationStoreError> { @@ -71,7 +71,7 @@ where &self, issuer: DID, audience: &DID, - command: String, + command: &str, policy: Vec, now: SystemTime, ) -> Result { @@ -125,7 +125,7 @@ where &self, audience: &DID, subject: &DID, - command: String, + command: &str, policy: Vec, now: SystemTime, ) -> Result>)>>, Self::DelegationStoreError> diff --git a/src/invocation/agent.rs b/src/invocation/agent.rs index 3cad747c..e50aefc0 100644 --- a/src/invocation/agent.rs +++ b/src/invocation/agent.rs @@ -104,7 +104,7 @@ where vec![] } else { self.delegation_store - .get_chain_cids(&self.did, &subject, ability.to_command(), vec![], now)? // FIXME policy + .get_chain_cids(&self.did, &subject, &ability.to_command(), vec![], now)? // FIXME policy .ok_or(InvokeError::ProofsNotFound)? .into() }; From 8cc6ac51a32a67846e83f8d0d107b6f883258786 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philipp=20Kr=C3=BCger?= Date: Fri, 22 Mar 2024 15:16:40 +0100 Subject: [PATCH 183/188] refactor: Turn `try_fold` into for loop in `Invocation::check` --- src/invocation/payload.rs | 98 ++++++++++++++++++++------------------- 1 file changed, 50 insertions(+), 48 deletions(-) diff --git a/src/invocation/payload.rs b/src/invocation/payload.rs index 6e92ae64..0c454991 100644 --- a/src/invocation/payload.rs +++ b/src/invocation/payload.rs @@ -1,18 +1,19 @@ use super::promise::Resolvable; -use crate::ability::command::Command; -use crate::ability::parse::ParseAbilityError; -use crate::delegation::policy::selector; -use crate::invocation::Named; -use crate::time; use crate::{ - ability::{arguments, command::ToCommand, parse::ParseAbility}, + ability::{ + arguments, + command::{Command, ToCommand}, + parse::{ParseAbility, ParseAbilityError}, + }, capsule::Capsule, crypto::{varsig, Nonce}, delegation::{ self, - policy::{selector::SelectorError, Predicate}, + policy::{selector, selector::SelectorError, Predicate}, }, did::{Did, Verifiable}, + invocation::Named, + time, time::{Expired, Timestamp}, }; use derive_builder::Builder; @@ -22,9 +23,11 @@ use serde::{ ser::SerializeStruct, Deserialize, Serialize, Serializer, }; -use std::collections::BTreeSet; -use std::str::FromStr; -use std::{collections::BTreeMap, fmt}; +use std::{ + collections::{BTreeMap, BTreeSet}, + fmt, + str::FromStr, +}; use thiserror::Error; use web_time::SystemTime; @@ -175,54 +178,54 @@ impl Payload { cmd.push('/'); } - let (final_iss, vias) = proofs.into_iter().try_fold( - (&self.issuer, BTreeSet::new()), - |(iss, mut vias), proof| { - if *iss != proof.audience { - return Err(ValidationError::MisalignedIssAud.into()); - } + let mut current_iss = &self.issuer; + let mut vias = BTreeSet::new(); + for proof in proofs { + if *current_iss != proof.audience { + return Err(ValidationError::MisalignedIssAud.into()); + } - if let Some(proof_subject) = &proof.subject { - if self.subject != *proof_subject { - return Err(ValidationError::InvalidSubject.into()); - } + if let Some(proof_subject) = &proof.subject { + if self.subject != *proof_subject { + return Err(ValidationError::InvalidSubject.into()); } + } - if proof.expiration < now_ts { - return Err(ValidationError::Expired.into()); - } + if proof.expiration < now_ts { + return Err(ValidationError::Expired.into()); + } - if let Some(nbf) = proof.not_before.clone() { - if nbf > now_ts { - return Err(ValidationError::NotYetValid.into()); - } + if let Some(nbf) = proof.not_before.clone() { + if nbf > now_ts { + return Err(ValidationError::NotYetValid.into()); } + } - vias.remove(&iss); - if let Some(via_did) = &proof.via { - vias.insert(via_did); - } + vias.remove(¤t_iss); + if let Some(via_did) = &proof.via { + vias.insert(via_did); + } - if !cmd.starts_with(&proof.command) { - return Err(ValidationError::CommandMismatch(proof.command.clone())); - } + if !cmd.starts_with(&proof.command) { + return Err(ValidationError::CommandMismatch(proof.command.clone())); + } - let ipld_args = Ipld::from(args.clone()); + let ipld_args = Ipld::from(args.clone()); - for predicate in proof.policy.iter() { - if !predicate - .clone() - .run(&ipld_args) - .map_err(ValidationError::SelectorError)? - { - return Err(ValidationError::FailedPolicy(predicate.clone())); - } + for predicate in proof.policy.iter() { + if !predicate + .clone() + .run(&ipld_args) + .map_err(ValidationError::SelectorError)? + { + return Err(ValidationError::FailedPolicy(predicate.clone())); } + } - Ok((&proof.issuer, vias)) - }, - )?; + current_iss = &proof.issuer; + } + let final_iss = current_iss; if self.subject != *final_iss { return Err(ValidationError::DidNotTerminateInSubject); } @@ -731,8 +734,7 @@ where #[cfg(test)] mod tests { use super::*; - use crate::ability::msg::Msg; - use crate::ipld; + use crate::{ability::msg::Msg, ipld}; use assert_matches::assert_matches; use pretty_assertions as pretty; use proptest::prelude::*; From d0fab39b4531c491b7129d9d2193c9e2eff283c3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philipp=20Kr=C3=BCger?= Date: Mon, 25 Mar 2024 09:04:13 +0100 Subject: [PATCH 184/188] Revert "refactor: Turn `try_fold` into for loop in `Invocation::check`" This reverts commit c06e6def0553bb7563e33914d7d11316d0e364fb. --- src/invocation/payload.rs | 98 +++++++++++++++++++-------------------- 1 file changed, 48 insertions(+), 50 deletions(-) diff --git a/src/invocation/payload.rs b/src/invocation/payload.rs index 0c454991..6e92ae64 100644 --- a/src/invocation/payload.rs +++ b/src/invocation/payload.rs @@ -1,19 +1,18 @@ use super::promise::Resolvable; +use crate::ability::command::Command; +use crate::ability::parse::ParseAbilityError; +use crate::delegation::policy::selector; +use crate::invocation::Named; +use crate::time; use crate::{ - ability::{ - arguments, - command::{Command, ToCommand}, - parse::{ParseAbility, ParseAbilityError}, - }, + ability::{arguments, command::ToCommand, parse::ParseAbility}, capsule::Capsule, crypto::{varsig, Nonce}, delegation::{ self, - policy::{selector, selector::SelectorError, Predicate}, + policy::{selector::SelectorError, Predicate}, }, did::{Did, Verifiable}, - invocation::Named, - time, time::{Expired, Timestamp}, }; use derive_builder::Builder; @@ -23,11 +22,9 @@ use serde::{ ser::SerializeStruct, Deserialize, Serialize, Serializer, }; -use std::{ - collections::{BTreeMap, BTreeSet}, - fmt, - str::FromStr, -}; +use std::collections::BTreeSet; +use std::str::FromStr; +use std::{collections::BTreeMap, fmt}; use thiserror::Error; use web_time::SystemTime; @@ -178,54 +175,54 @@ impl Payload { cmd.push('/'); } - let mut current_iss = &self.issuer; - let mut vias = BTreeSet::new(); - for proof in proofs { - if *current_iss != proof.audience { - return Err(ValidationError::MisalignedIssAud.into()); - } + let (final_iss, vias) = proofs.into_iter().try_fold( + (&self.issuer, BTreeSet::new()), + |(iss, mut vias), proof| { + if *iss != proof.audience { + return Err(ValidationError::MisalignedIssAud.into()); + } - if let Some(proof_subject) = &proof.subject { - if self.subject != *proof_subject { - return Err(ValidationError::InvalidSubject.into()); + if let Some(proof_subject) = &proof.subject { + if self.subject != *proof_subject { + return Err(ValidationError::InvalidSubject.into()); + } } - } - if proof.expiration < now_ts { - return Err(ValidationError::Expired.into()); - } + if proof.expiration < now_ts { + return Err(ValidationError::Expired.into()); + } - if let Some(nbf) = proof.not_before.clone() { - if nbf > now_ts { - return Err(ValidationError::NotYetValid.into()); + if let Some(nbf) = proof.not_before.clone() { + if nbf > now_ts { + return Err(ValidationError::NotYetValid.into()); + } } - } - vias.remove(¤t_iss); - if let Some(via_did) = &proof.via { - vias.insert(via_did); - } + vias.remove(&iss); + if let Some(via_did) = &proof.via { + vias.insert(via_did); + } - if !cmd.starts_with(&proof.command) { - return Err(ValidationError::CommandMismatch(proof.command.clone())); - } + if !cmd.starts_with(&proof.command) { + return Err(ValidationError::CommandMismatch(proof.command.clone())); + } - let ipld_args = Ipld::from(args.clone()); + let ipld_args = Ipld::from(args.clone()); - for predicate in proof.policy.iter() { - if !predicate - .clone() - .run(&ipld_args) - .map_err(ValidationError::SelectorError)? - { - return Err(ValidationError::FailedPolicy(predicate.clone())); + for predicate in proof.policy.iter() { + if !predicate + .clone() + .run(&ipld_args) + .map_err(ValidationError::SelectorError)? + { + return Err(ValidationError::FailedPolicy(predicate.clone())); + } } - } - current_iss = &proof.issuer; - } + Ok((&proof.issuer, vias)) + }, + )?; - let final_iss = current_iss; if self.subject != *final_iss { return Err(ValidationError::DidNotTerminateInSubject); } @@ -734,7 +731,8 @@ where #[cfg(test)] mod tests { use super::*; - use crate::{ability::msg::Msg, ipld}; + use crate::ability::msg::Msg; + use crate::ipld; use assert_matches::assert_matches; use pretty_assertions as pretty; use proptest::prelude::*; From 7f680d3cd4e946e0ee79715d0b305ad976700ab5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philipp=20Kr=C3=BCger?= Date: Wed, 27 Mar 2024 17:44:01 +0100 Subject: [PATCH 185/188] refactor: Remove unused constraints (#17) --- src/ability/pipe.rs | 8 +++--- src/ability/ucan/batch.rs | 4 +-- src/crypto/signature/envelope.rs | 2 +- src/crypto/varsig/header/eddsa.rs | 2 +- src/crypto/varsig/header/es256.rs | 2 +- src/crypto/varsig/header/es256k.rs | 2 +- src/crypto/varsig/header/es512.rs | 2 +- src/crypto/varsig/header/rs256.rs | 2 +- src/crypto/varsig/header/rs512.rs | 2 +- src/crypto/varsig/header/traits.rs | 4 +-- src/delegation.rs | 20 ++++++--------- src/delegation/agent.rs | 10 +++----- src/delegation/store/memory.rs | 23 ++++++------------ src/delegation/store/traits.rs | 10 +++----- src/invocation.rs | 22 +++++++---------- src/invocation/agent.rs | 17 +++---------- src/invocation/payload.rs | 8 +++--- src/invocation/store/memory.rs | 16 +++++------- src/invocation/store/traits.rs | 15 ++++-------- src/receipt.rs | 39 +++++++++--------------------- src/receipt/store/memory.rs | 12 +++------ src/receipt/store/traits.rs | 2 +- 22 files changed, 77 insertions(+), 147 deletions(-) diff --git a/src/ability/pipe.rs b/src/ability/pipe.rs index 536fd55a..e31e3bc2 100644 --- a/src/ability/pipe.rs +++ b/src/ability/pipe.rs @@ -1,22 +1,22 @@ use crate::{crypto::varsig, delegation, did::Did, ipld}; use libipld_core::{codec::Codec, ipld::Ipld}; -pub struct Pipe, C: Codec + TryFrom + Into> { +pub struct Pipe, C: Codec> { pub source: Cap, pub sink: Cap, } -pub enum Cap, C: Codec + TryFrom + Into> { +pub enum Cap, C: Codec> { Proof(delegation::Proof), Literal(Ipld), } -pub struct PromisedPipe, C: Codec + TryFrom + Into> { +pub struct PromisedPipe, C: Codec> { pub source: PromisedCap, pub sink: PromisedCap, } -pub enum PromisedCap, C: Codec + TryFrom + Into> { +pub enum PromisedCap, C: Codec> { Proof(delegation::Proof), Promised(ipld::Promised), } diff --git a/src/ability/ucan/batch.rs b/src/ability/ucan/batch.rs index 9712fd93..983500df 100644 --- a/src/ability/ucan/batch.rs +++ b/src/ability/ucan/batch.rs @@ -3,11 +3,11 @@ // use std::collections::BTreeMap; // // #[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)] -// pub struct Batch, Enc: Codec + TryFrom + Into> { +// pub struct Batch, Enc: Codec> { // pub batch: Vec>, // FIXME not quite right; would be nice to include meta etc // } // -// pub struct Step, Enc: Codec + TryFrom + Into> { +// pub struct Step, Enc: Codec> { // pub subject: DID, // pub audience: Option, // pub ability: A, // FIXME promise version instead? Promised version shoudl be able to promise any field diff --git a/src/crypto/signature/envelope.rs b/src/crypto/signature/envelope.rs index 170b719d..58282a53 100644 --- a/src/crypto/signature/envelope.rs +++ b/src/crypto/signature/envelope.rs @@ -16,7 +16,7 @@ pub trait Envelope: Sized { type DID: Did; type Payload: Clone + Capsule + TryFrom> + Into>; type VarsigHeader: varsig::Header + Clone; - type Encoder: Codec + TryFrom + Into; + type Encoder: Codec; fn varsig_header(&self) -> &Self::VarsigHeader; fn signature(&self) -> &::Signature; diff --git a/src/crypto/varsig/header/eddsa.rs b/src/crypto/varsig/header/eddsa.rs index 5dd2009e..d16d7137 100644 --- a/src/crypto/varsig/header/eddsa.rs +++ b/src/crypto/varsig/header/eddsa.rs @@ -33,7 +33,7 @@ impl + Clone> From> for Vec { } } -impl + TryFrom> Header for EdDsaHeader { +impl Header for EdDsaHeader { type Signature = ed25519_dalek::Signature; type Verifier = ed25519_dalek::VerifyingKey; diff --git a/src/crypto/varsig/header/es256.rs b/src/crypto/varsig/header/es256.rs index 4e53a460..ba57f06d 100644 --- a/src/crypto/varsig/header/es256.rs +++ b/src/crypto/varsig/header/es256.rs @@ -38,7 +38,7 @@ impl> From> for Vec { } } -impl + TryFrom> Header for Es256Header { +impl Header for Es256Header { type Signature = p256::ecdsa::Signature; type Verifier = p256::ecdsa::VerifyingKey; diff --git a/src/crypto/varsig/header/es256k.rs b/src/crypto/varsig/header/es256k.rs index 465c4338..17bd7c67 100644 --- a/src/crypto/varsig/header/es256k.rs +++ b/src/crypto/varsig/header/es256k.rs @@ -38,7 +38,7 @@ impl> From> for Vec { } } -impl + TryFrom> Header for Es256kHeader { +impl Header for Es256kHeader { type Signature = k256::ecdsa::Signature; type Verifier = k256::ecdsa::VerifyingKey; diff --git a/src/crypto/varsig/header/es512.rs b/src/crypto/varsig/header/es512.rs index 07089fac..c14b4723 100644 --- a/src/crypto/varsig/header/es512.rs +++ b/src/crypto/varsig/header/es512.rs @@ -38,7 +38,7 @@ impl> From> for Vec { } } -impl + TryFrom> Header for Es512Header { +impl Header for Es512Header { type Signature = p521::ecdsa::Signature; type Verifier = p521::ecdsa::VerifyingKey; diff --git a/src/crypto/varsig/header/rs256.rs b/src/crypto/varsig/header/rs256.rs index bc137ecb..4f559d80 100644 --- a/src/crypto/varsig/header/rs256.rs +++ b/src/crypto/varsig/header/rs256.rs @@ -51,7 +51,7 @@ impl> From> for Vec { } } -impl + TryFrom> Header for Rs256Header { +impl Header for Rs256Header { type Signature = Signature; type Verifier = VerifyingKey; diff --git a/src/crypto/varsig/header/rs512.rs b/src/crypto/varsig/header/rs512.rs index 7b9d161d..60c77adf 100644 --- a/src/crypto/varsig/header/rs512.rs +++ b/src/crypto/varsig/header/rs512.rs @@ -39,7 +39,7 @@ impl> From> for Vec { } } -impl + TryFrom> Header for Rs512Header { +impl Header for Rs512Header { type Signature = Signature; type Verifier = VerifyingKey; diff --git a/src/crypto/varsig/header/traits.rs b/src/crypto/varsig/header/traits.rs index a91bfe29..e6da044c 100644 --- a/src/crypto/varsig/header/traits.rs +++ b/src/crypto/varsig/header/traits.rs @@ -2,9 +2,7 @@ use libipld_core::codec::{Codec, Encode}; use signature::Verifier; use thiserror::Error; -pub trait Header + Into>: - for<'a> TryFrom<&'a [u8]> + Into> -{ +pub trait Header: for<'a> TryFrom<&'a [u8]> + Into> { type Signature: signature::SignatureEncoding; type Verifier: signature::Verifier; diff --git a/src/delegation.rs b/src/delegation.rs index e25aa5fe..e5c75842 100644 --- a/src/delegation.rs +++ b/src/delegation.rs @@ -42,7 +42,7 @@ use web_time::SystemTime; pub struct Delegation< DID: Did = did::preset::Verifier, V: varsig::Header = varsig::header::Preset, - C: Codec + TryFrom + Into = varsig::encoding::Preset, + C: Codec = varsig::encoding::Preset, > { pub varsig_header: V, pub payload: Payload, @@ -54,18 +54,16 @@ pub struct Delegation< pub struct Proof< DID: Did = did::preset::Verifier, V: varsig::Header = varsig::header::Preset, - C: Codec + TryFrom + Into = varsig::encoding::Preset, + C: Codec = varsig::encoding::Preset, > { pub prf: Vec>>, } -impl, C: Codec + TryFrom + Into> Capsule - for Proof -{ +impl, C: Codec> Capsule for Proof { const TAG: &'static str = "ucan/prf"; } -impl, C: Codec + Into + TryFrom> Delegation { +impl, C: Codec> Delegation { pub fn new( varsig_header: V, signature: DID::Signature, @@ -124,8 +122,7 @@ impl, C: Codec + Into + TryFrom> Delega } } -impl + Clone, C: Codec + TryFrom + Into> Envelope - for Delegation +impl + Clone, C: Codec> Envelope for Delegation where Payload: TryFrom>, Named: From>, @@ -165,8 +162,7 @@ where } } -impl + Clone, C: Codec + TryFrom + Into> Serialize - for Delegation +impl + Clone, C: Codec> Serialize for Delegation where Payload: TryFrom>, { @@ -178,8 +174,8 @@ where } } -impl<'de, DID: Did + Clone, V: varsig::Header + Clone, C: Codec + TryFrom + Into> - Deserialize<'de> for Delegation +impl<'de, DID: Did + Clone, V: varsig::Header + Clone, C: Codec> Deserialize<'de> + for Delegation where Payload: TryFrom>, as TryFrom>>::Error: std::fmt::Display, diff --git a/src/delegation/agent.rs b/src/delegation/agent.rs index 7e9cc9c7..1a81afc4 100644 --- a/src/delegation/agent.rs +++ b/src/delegation/agent.rs @@ -23,7 +23,7 @@ pub struct Agent< S: Store, DID: Did + Clone = did::preset::Verifier, V: varsig::Header + Clone = varsig::header::Preset, - C: Codec + Into + TryFrom = varsig::encoding::Preset, + C: Codec = varsig::encoding::Preset, > where Ipld: Encode, Payload: TryFrom>, @@ -39,12 +39,8 @@ pub struct Agent< _marker: PhantomData<(V, C)>, } -impl< - S: Store + Clone, - DID: Did + Clone, - V: varsig::Header + Clone, - C: Codec + TryFrom + Into, - > Agent +impl + Clone, DID: Did + Clone, V: varsig::Header + Clone, C: Codec> + Agent where Ipld: Encode, Payload: TryFrom>, diff --git a/src/delegation/store/memory.rs b/src/delegation/store/memory.rs index 91fd6102..43260b00 100644 --- a/src/delegation/store/memory.rs +++ b/src/delegation/store/memory.rs @@ -78,7 +78,7 @@ use web_time::SystemTime; pub struct MemoryStore< DID: did::Did + Ord = did::preset::Verifier, V: varsig::Header = varsig::header::Preset, - C: Codec + TryFrom + Into = varsig::encoding::Preset, + C: Codec = varsig::encoding::Preset, > { inner: Arc>>, } @@ -87,16 +87,14 @@ pub struct MemoryStore< struct MemoryStoreInner< DID: did::Did + Ord = did::preset::Verifier, V: varsig::Header = varsig::header::Preset, - C: Codec + TryFrom + Into = varsig::encoding::Preset, + C: Codec = varsig::encoding::Preset, > { ucans: BTreeMap>>, index: BTreeMap, BTreeMap>>, revocations: BTreeSet, } -impl, C: Codec + TryFrom + Into> - MemoryStore -{ +impl, C: Codec> MemoryStore { pub fn new() -> Self { Self::default() } @@ -120,9 +118,7 @@ impl, C: Codec + TryFrom + Into, C: Codec + TryFrom + Into> Default - for MemoryStore -{ +impl, C: Codec> Default for MemoryStore { fn default() -> Self { Self { inner: Default::default(), @@ -130,9 +126,7 @@ impl, C: Codec + TryFrom + Into, C: Codec + TryFrom + Into> Default - for MemoryStoreInner -{ +impl, C: Codec> Default for MemoryStoreInner { fn default() -> Self { MemoryStoreInner { ucans: BTreeMap::new(), @@ -143,11 +137,8 @@ impl, C: Codec + TryFrom + Into> } // FIXME check that UCAN is valid -impl< - DID: Did + Ord + Clone, - V: varsig::Header + Clone, - Enc: Codec + TryFrom + Into, - > Store for MemoryStore +impl + Clone, Enc: Codec> Store + for MemoryStore where Named: From>, delegation::Payload: TryFrom>, diff --git a/src/delegation/store/traits.rs b/src/delegation/store/traits.rs index c5bc6701..f71cb5db 100644 --- a/src/delegation/store/traits.rs +++ b/src/delegation/store/traits.rs @@ -14,7 +14,7 @@ use std::{fmt::Debug, sync::Arc}; use thiserror::Error; use web_time::SystemTime; -pub trait Store + Clone, C: Codec + TryFrom + Into> +pub trait Store + Clone, C: Codec> where Ipld: Encode, Payload: TryFrom>, @@ -89,12 +89,8 @@ where } } -impl< - T: Store, - DID: Did + Clone, - V: varsig::Header + Clone, - C: Codec + TryFrom + Into, - > Store for &T +impl, DID: Did + Clone, V: varsig::Header + Clone, C: Codec> Store + for &T where Ipld: Encode, Payload: TryFrom>, diff --git a/src/invocation.rs b/src/invocation.rs index 4c486070..f9ffb106 100644 --- a/src/invocation.rs +++ b/src/invocation.rs @@ -54,7 +54,7 @@ pub struct Invocation< A, DID: did::Did = did::preset::Verifier, V: varsig::Header = varsig::header::Preset, - C: Codec + TryFrom + Into = varsig::encoding::Preset, + C: Codec = varsig::encoding::Preset, > { pub varsig_header: V, pub payload: Payload, @@ -66,7 +66,7 @@ impl< A: Clone + ToCommand + ParseAbility, DID: Clone + did::Did, V: Clone + varsig::Header, - C: Codec + TryFrom + Into, + C: Codec, > Encode for Invocation where Ipld: Encode, @@ -79,12 +79,8 @@ where } } -impl< - A: Clone + ToCommand + ParseAbility, - DID: Did + Clone, - V: varsig::Header, - C: Codec + TryFrom + Into, - > Invocation +impl, C: Codec> + Invocation where Ipld: Encode, { @@ -150,7 +146,7 @@ where } } -impl, C: Codec + TryFrom + Into> did::Verifiable +impl, C: Codec> did::Verifiable for Invocation { fn verifier(&self) -> &DID { @@ -162,7 +158,7 @@ impl< A: Clone + ToCommand + ParseAbility, DID: Did + Clone, V: varsig::Header + Clone, - C: Codec + TryFrom + Into, + C: Codec, > From> for Ipld where Named: From, @@ -177,7 +173,7 @@ impl< A: Clone + ToCommand + ParseAbility, DID: Did + Clone, V: varsig::Header + Clone, - C: Codec + TryFrom + Into, + C: Codec, > Envelope for Invocation where Named: From, @@ -222,7 +218,7 @@ impl< A: Clone + ToCommand + ParseAbility, DID: Did + Clone, V: varsig::Header + Clone, - C: Codec + TryFrom + Into, + C: Codec, > Serialize for Invocation where Named: From, @@ -241,7 +237,7 @@ impl< A: Clone + ToCommand + ParseAbility, DID: Did + Clone, V: varsig::Header + Clone, - C: Codec + TryFrom + Into, + C: Codec, > Deserialize<'de> for Invocation where Named: From, diff --git a/src/invocation/agent.rs b/src/invocation/agent.rs index e50aefc0..43d8b26c 100644 --- a/src/invocation/agent.rs +++ b/src/invocation/agent.rs @@ -37,7 +37,7 @@ pub struct Agent< T: ToCommand = ability::preset::Preset, DID: Did + Clone = did::preset::Verifier, V: varsig::Header + Clone = varsig::header::Preset, - C: Codec + Into + TryFrom = varsig::encoding::Preset, + C: Codec = varsig::encoding::Preset, > where Ipld: Encode, delegation::Payload: TryFrom>, @@ -67,9 +67,7 @@ where S: Store, D: delegation::store::Store, V: varsig::Header + Clone, - C: Codec + Into + TryFrom, - >::InvocationStoreError: fmt::Debug, - >::DelegationStoreError: fmt::Debug, + C: Codec, delegation::Payload: TryFrom>, Named: From>, { @@ -291,16 +289,7 @@ pub enum Recipient { } #[derive(Debug, Error, EnumAsInner)] -pub enum ReceiveError< - T, - DID: Did, - D, - S: Store, - V: varsig::Header, - C: Codec + TryFrom + Into, -> where - >::InvocationStoreError: fmt::Debug, -{ +pub enum ReceiveError, V: varsig::Header, C: Codec> { #[error("couldn't find delegation: {0}")] DelegationNotFound(Cid), diff --git a/src/invocation/payload.rs b/src/invocation/payload.rs index 6e92ae64..728a2dfe 100644 --- a/src/invocation/payload.rs +++ b/src/invocation/payload.rs @@ -366,9 +366,7 @@ where } } -impl<'de, A: ParseAbility + Deserialize<'de>, DID: Did + Deserialize<'de>> Deserialize<'de> - for Payload -{ +impl<'de, A: ParseAbility, DID: Did + Deserialize<'de>> Deserialize<'de> for Payload { fn deserialize(deserializer: D) -> Result, D::Error> where D: de::Deserializer<'de>, @@ -379,7 +377,7 @@ impl<'de, A: ParseAbility + Deserialize<'de>, DID: Did + Deserialize<'de>> Deser "iss", "sub", "aud", "cmd", "args", "prf", "nonce", "cause", "meta", "iat", "exp", ]; - impl<'de, T: ParseAbility + Deserialize<'de>, DID: Did + Deserialize<'de>> Visitor<'de> + impl<'de, T: ParseAbility, DID: Did + Deserialize<'de>> Visitor<'de> for InvocationPayloadVisitor { type Value = Payload; @@ -480,7 +478,7 @@ impl<'de, A: ParseAbility + Deserialize<'de>, DID: Did + Deserialize<'de>> Deser let ability = ::try_parse(cmd.as_str(), args).map_err(|e| { de::Error::custom(format!( - "Unable to parse ability field for {:?} becuase {:?}", + "Unable to parse ability field for {:?} because {:?}", cmd, e )) })?; diff --git a/src/invocation/store/memory.rs b/src/invocation/store/memory.rs index 7c7c7196..083624df 100644 --- a/src/invocation/store/memory.rs +++ b/src/invocation/store/memory.rs @@ -9,7 +9,7 @@ pub struct MemoryStore< T = crate::ability::preset::Preset, DID: crate::did::Did = crate::did::preset::Verifier, V: varsig::Header = varsig::header::Preset, - C: Codec + TryFrom + Into = varsig::encoding::Preset, + C: Codec = varsig::encoding::Preset, > { inner: Arc>>, } @@ -19,14 +19,12 @@ pub struct MemoryStoreInner< T = crate::ability::preset::Preset, DID: crate::did::Did = crate::did::preset::Verifier, V: varsig::Header = varsig::header::Preset, - C: Codec + TryFrom + Into = varsig::encoding::Preset, + C: Codec = varsig::encoding::Preset, > { store: BTreeMap>>, } -impl, Enc: Codec + Into + TryFrom> - MemoryStore -{ +impl, Enc: Codec> MemoryStore { fn lock(&self) -> MutexGuard<'_, MemoryStoreInner> { match self.inner.lock() { Ok(guard) => guard, @@ -38,9 +36,7 @@ impl, Enc: Codec + Into + TryFrom> } } -impl, Enc: Codec + Into + TryFrom> Default - for MemoryStore -{ +impl, Enc: Codec> Default for MemoryStore { fn default() -> Self { Self { inner: Arc::new(Mutex::new(MemoryStoreInner { @@ -50,8 +46,8 @@ impl, Enc: Codec + Into + TryFrom> } } -impl, Enc: Codec + Into + TryFrom> - Store for MemoryStore +impl, Enc: Codec> Store + for MemoryStore { type InvocationStoreError = Infallible; diff --git a/src/invocation/store/traits.rs b/src/invocation/store/traits.rs index 6d3fc723..7442d449 100644 --- a/src/invocation/store/traits.rs +++ b/src/invocation/store/traits.rs @@ -1,9 +1,9 @@ use crate::{crypto::varsig, did::Did, invocation::Invocation}; use libipld_core::{cid::Cid, codec::Codec}; -use std::sync::Arc; +use std::{fmt::Debug, sync::Arc}; -pub trait Store, C: Codec + Into + TryFrom> { - type InvocationStoreError; +pub trait Store, C: Codec> { + type InvocationStoreError: Debug; fn get( &self, @@ -21,13 +21,8 @@ pub trait Store, C: Codec + Into + TryFro } } -impl< - S: Store, - T, - DID: Did, - V: varsig::Header, - C: Codec + Into + TryFrom, - > Store for &S +impl, T, DID: Did, V: varsig::Header, C: Codec> Store + for &S { type InvocationStoreError = >::InvocationStoreError; diff --git a/src/receipt.rs b/src/receipt.rs index 537d5335..abf1d38c 100644 --- a/src/receipt.rs +++ b/src/receipt.rs @@ -28,7 +28,7 @@ pub struct Receipt< T: Responds, DID: Did = did::preset::Verifier, V: varsig::Header = varsig::header::Preset, - C: Codec + Into + TryFrom = varsig::encoding::Preset, + C: Codec = varsig::encoding::Preset, > { pub varsig_header: V, pub signature: DID::Signature, @@ -37,20 +37,16 @@ pub struct Receipt< _marker: std::marker::PhantomData, } -impl, C: Codec + TryFrom + Into> - did::Verifiable for Receipt +impl, C: Codec> did::Verifiable + for Receipt { fn verifier(&self) -> &DID { &self.payload.verifier() } } -impl< - T: Responds + Clone, - DID: Did + Clone, - V: varsig::Header + Clone, - C: Codec + TryFrom + Into, - > From> for Ipld +impl + Clone, C: Codec> + From> for Ipld where Ipld: From, Payload: TryFrom>, @@ -60,12 +56,8 @@ where } } -impl< - T: Responds + Clone, - DID: Did + Clone, - V: varsig::Header + Clone, - C: Codec + TryFrom + Into, - > Envelope for Receipt +impl + Clone, C: Codec> Envelope + for Receipt where Ipld: From, Payload: TryFrom>, @@ -105,12 +97,8 @@ where } } -impl< - T: Responds + Clone, - DID: Did + Clone, - V: varsig::Header + Clone, - C: Codec + TryFrom + Into, - > Serialize for Receipt +impl + Clone, C: Codec> Serialize + for Receipt where Ipld: From, Payload: TryFrom>, @@ -123,13 +111,8 @@ where } } -impl< - 'de, - T: Responds + Clone, - DID: Did + Clone, - V: varsig::Header + Clone, - C: Codec + TryFrom + Into, - > Deserialize<'de> for Receipt +impl<'de, T: Responds + Clone, DID: Did + Clone, V: varsig::Header + Clone, C: Codec> + Deserialize<'de> for Receipt where Ipld: From, Payload: TryFrom>, diff --git a/src/receipt/store/memory.rs b/src/receipt/store/memory.rs index 6be098fa..955ffada 100644 --- a/src/receipt/store/memory.rs +++ b/src/receipt/store/memory.rs @@ -10,19 +10,15 @@ use std::{collections::BTreeMap, convert::Infallible, fmt}; /// An in-memory [`receipt::Store`][crate::receipt::Store]. #[derive(Debug, Clone, PartialEq)] -pub struct MemoryStore< - T: Responds, - DID: Did, - V: varsig::Header, - Enc: Codec + Into + TryFrom, -> where +pub struct MemoryStore, Enc: Codec> +where T::Success: fmt::Debug + Clone + PartialEq, { store: BTreeMap>, } -impl, Enc: Codec + Into + TryFrom> - Store for MemoryStore +impl, Enc: Codec> Store + for MemoryStore where ::Success: TryFrom + Into + Clone + fmt::Debug + PartialEq, { diff --git a/src/receipt/store/traits.rs b/src/receipt/store/traits.rs index 8d53a1f0..2bb52329 100644 --- a/src/receipt/store/traits.rs +++ b/src/receipt/store/traits.rs @@ -7,7 +7,7 @@ use crate::{ use libipld_core::{codec::Codec, ipld::Ipld}; /// A store for [`Receipt`]s indexed by their [`task::Id`]s. -pub trait Store, C: Codec + Into + TryFrom> { +pub trait Store, C: Codec> { /// The error type representing all the ways a store operation can fail. type Error; From 53c49d23a5c2d72242342fbebe52b5c310f2704e Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Wed, 27 Mar 2024 19:57:41 +0200 Subject: [PATCH 186/188] Nonce simplification (#19) --- src/crypto/nonce.rs | 156 +++----------------------------------- src/delegation/agent.rs | 2 +- src/delegation/payload.rs | 2 +- src/invocation/agent.rs | 2 +- src/invocation/payload.rs | 2 +- src/receipt/payload.rs | 2 +- 6 files changed, 17 insertions(+), 149 deletions(-) diff --git a/src/crypto/nonce.rs b/src/crypto/nonce.rs index 0e46d2dc..5609ca82 100644 --- a/src/crypto/nonce.rs +++ b/src/crypto/nonce.rs @@ -4,11 +4,7 @@ use enum_as_inner::EnumAsInner; use getrandom::getrandom; -use libipld_core::{ - ipld::Ipld, - multibase::Base::Base32HexLower, - multihash::{Hasher, Sha2_256}, -}; +use libipld_core::{ipld::Ipld, multibase::Base::Base32HexLower}; use serde::{Deserialize, Serialize}; use std::fmt; @@ -21,9 +17,6 @@ use proptest::prelude::*; /// Known [`Nonce`] types #[derive(Clone, Debug, EnumAsInner, Serialize, Deserialize)] pub enum Nonce { - /// 96-bit, 12-byte nonce - Nonce12([u8; 12]), - /// 128-bit, 16-byte nonce Nonce16([u8; 16]), @@ -34,48 +27,15 @@ pub enum Nonce { impl PartialEq for Nonce { fn eq(&self, other: &Self) -> bool { match (self, other) { - (Nonce::Nonce12(a), Nonce::Nonce12(b)) => a == b, (Nonce::Nonce16(a), Nonce::Nonce16(b)) => a == b, (Nonce::Custom(a), Nonce::Custom(b)) => a == b, - (Nonce::Custom(a), Nonce::Nonce12(b)) => { - if a.len() == 12 { - a.as_slice() == b - } else { - false - } - } - (Nonce::Custom(a), Nonce::Nonce16(b)) => { - if a.len() == 16 { - a.as_slice() == b - } else { - false - } - } - (Nonce::Nonce12(a), Nonce::Custom(b)) => { - if b.len() == 12 { - a == b.as_slice() - } else { - false - } - } - (Nonce::Nonce16(a), Nonce::Custom(b)) => { - if b.len() == 16 { - a == b.as_slice() - } else { - false - } - } + (Nonce::Custom(a), Nonce::Nonce16(b)) => a.as_slice() == b, + (Nonce::Nonce16(a), Nonce::Custom(b)) => a == b.as_slice(), _ => false, } } } -impl From<[u8; 12]> for Nonce { - fn from(s: [u8; 12]) -> Self { - Nonce::Nonce12(s) - } -} - impl From<[u8; 16]> for Nonce { fn from(s: [u8; 16]) -> Self { Nonce::Nonce16(s) @@ -85,7 +45,6 @@ impl From<[u8; 16]> for Nonce { impl From for Vec { fn from(nonce: Nonce) -> Self { match nonce { - Nonce::Nonce12(nonce) => nonce.to_vec(), Nonce::Nonce16(nonce) => nonce.to_vec(), Nonce::Custom(nonce) => nonce, } @@ -94,10 +53,6 @@ impl From for Vec { impl From> for Nonce { fn from(nonce: Vec) -> Self { - if let Ok(twelve) = <[u8; 12]>::try_from(nonce.clone()) { - return twelve.into(); - } - if let Ok(sixteen) = <[u8; 16]>::try_from(nonce.clone()) { return sixteen.into(); } @@ -107,44 +62,6 @@ impl From> for Nonce { } impl Nonce { - /// Generate a 96-bit, 12-byte nonce. - /// This is the minimum nonce size typically recommended. - /// - /// # Arguments - /// - /// * `salt` - A salt. This may be left empty, but is recommended to avoid collision. - /// - /// # Example - /// - /// ```rust - /// # use ucan::crypto::Nonce; - /// # use ucan::did::Did; - /// # - /// let mut salt = "did:example:123".as_bytes().to_vec(); - /// let nonce = Nonce::generate_12(&mut salt); - /// - /// assert_eq!(Vec::from(nonce).len(), 12); - /// ``` - pub fn generate_12(salt: &mut Vec) -> Nonce { - salt.append(&mut [0].repeat(12)); - - let buf = salt.as_mut_slice(); - getrandom(buf).expect("irrecoverable getrandom failure"); - - let mut hasher = Sha2_256::default(); - hasher.update(buf); - - let bytes = hasher - .finalize() - .chunks(12) - .next() - .expect("SHA2_256 is 32 bytes") - .try_into() - .expect("we set the length to 12 earlier"); - - Nonce::Nonce12(bytes) - } - /// Generate a 128-bit, 16-byte nonce /// /// # Arguments @@ -158,37 +75,20 @@ impl Nonce { /// # use ucan::did::Did; /// # /// let mut salt = "did:example:123".as_bytes().to_vec(); - /// let nonce = Nonce::generate_16(&mut salt); + /// let nonce = Nonce::generate_16(); /// /// assert_eq!(Vec::from(nonce).len(), 16); /// ``` - pub fn generate_16(salt: &mut Vec) -> Nonce { - salt.append(&mut [0].repeat(16)); - - let buf = salt.as_mut_slice(); - getrandom(buf).expect("irrecoverable getrandom failure"); - - let mut hasher = Sha2_256::default(); - hasher.update(buf); - - let bytes = hasher - .finalize() - .chunks(16) - .next() - .expect("SHA2_256 is 32 bytes") - .try_into() - .expect("we set the length to 16 earlier"); - - Nonce::Nonce16(bytes) + pub fn generate_16() -> Nonce { + let mut buf = [0; 16]; + getrandom(&mut buf).expect("irrecoverable getrandom failure"); + Nonce::Nonce16(buf) } } impl fmt::Display for Nonce { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { - Nonce::Nonce12(nonce) => { - write!(f, "{}", Base32HexLower.encode(nonce.as_slice())) - } Nonce::Nonce16(nonce) => { write!(f, "{}", Base32HexLower.encode(nonce.as_slice())) } @@ -202,7 +102,6 @@ impl fmt::Display for Nonce { impl From for Ipld { fn from(nonce: Nonce) -> Self { match nonce { - Nonce::Nonce12(nonce) => Ipld::Bytes(nonce.to_vec()), Nonce::Nonce16(nonce) => Ipld::Bytes(nonce.to_vec()), Nonce::Custom(nonce) => Ipld::Bytes(nonce), } @@ -215,10 +114,6 @@ impl TryFrom for Nonce { fn try_from(ipld: Ipld) -> Result { if let Ipld::Bytes(v) = ipld { match v.len() { - 12 => Ok(Nonce::Nonce12( - v.try_into() - .expect("12 bytes because we checked in the match"), - )), 16 => Ok(Nonce::Nonce16( v.try_into() .expect("16 bytes because we checked in the match"), @@ -238,7 +133,6 @@ impl Arbitrary for Nonce { fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy { prop_oneof![ - any::<[u8; 12]>().prop_map(Nonce::Nonce12), any::<[u8; 16]>().prop_map(Nonce::Nonce16), any::>().prop_map(Nonce::Custom) ] @@ -270,23 +164,13 @@ impl From for JsNonce { #[cfg(target_arch = "wasm32")] #[wasm_bindgen] impl JsNonce { - /// Generate a 96-bit, 12-byte nonce. - /// This is the minimum nonce size typically recommended. - /// - /// # Arguments - /// - /// * `salt` - A salt. This may be left empty, but is recommended to avoid collision. - pub fn generate_12(mut salt: Vec) -> JsNonce { - Nonce::generate_12(&mut salt).into() - } - /// Generate a 128-bit, 16-byte nonce /// /// # Arguments /// /// * `salt` - A salt. This may be left empty, but is recommended to avoid collision. - pub fn generate_16(mut salt: Vec) -> JsNonce { - Nonce::generate_16(&mut salt).into() + pub fn generate_16() -> JsNonce { + Nonce::generate_16().into() } /// Directly lift a 12-byte `Uint8Array` into a [`JsNonce`] @@ -316,26 +200,10 @@ impl JsNonce { mod test { use super::*; - // FIXME prop test with lots of inputs - #[test] - fn ipld_roundtrip_12() { - let gen = Nonce::generate_12(&mut vec![]); - let ipld = Ipld::from(gen.clone()); - - let inner = if let Nonce::Nonce12(nonce) = gen { - Ipld::Bytes(nonce.to_vec()) - } else { - panic!("No conversion!") - }; - - assert_eq!(ipld, inner); - assert_eq!(gen, ipld.try_into().unwrap()); - } - // FIXME prop test with lots of inputs #[test] fn ipld_roundtrip_16() { - let gen = Nonce::generate_16(&mut vec![]); + let gen = Nonce::generate_16(); let ipld = Ipld::from(gen.clone()); let inner = if let Nonce::Nonce16(nonce) = gen { @@ -351,7 +219,7 @@ mod test { // FIXME prop test with lots of inputs // #[test] // fn ser_de() { - // let gen = Nonce::generate_16(&mut vec![]); + // let gen = Nonce::generate_16(); // let ser = serde_json::to_string(&gen).unwrap(); // let de = serde_json::from_str(&ser).unwrap(); diff --git a/src/delegation/agent.rs b/src/delegation/agent.rs index 1a81afc4..38108824 100644 --- a/src/delegation/agent.rs +++ b/src/delegation/agent.rs @@ -69,7 +69,7 @@ where varsig_header: V, ) -> Result, DelegateError> { let mut salt = self.did.clone().to_string().into_bytes(); - let nonce = Nonce::generate_12(&mut salt); + let nonce = Nonce::generate_16(); if *subject == self.did { let payload: Payload = Payload { diff --git a/src/delegation/payload.rs b/src/delegation/payload.rs index c54438ec..064fdca1 100644 --- a/src/delegation/payload.rs +++ b/src/delegation/payload.rs @@ -70,7 +70,7 @@ pub struct Payload { /// /// [cryptograpgic nonce]: https://en.wikipedia.org/wiki/Cryptographic_nonce /// [`Cid`]: libipld_core::cid::Cid ; - #[builder(default = "Nonce::generate_16(&mut vec![])")] + #[builder(default = "Nonce::generate_16()")] pub nonce: Nonce, /// The latest wall-clock time that the UCAN is valid until, diff --git a/src/invocation/agent.rs b/src/invocation/agent.rs index 43d8b26c..1910a26f 100644 --- a/src/invocation/agent.rs +++ b/src/invocation/agent.rs @@ -114,7 +114,7 @@ where ability, proofs, metadata, - nonce: Nonce::generate_12(&mut vec![]), + nonce: Nonce::generate_16(), cause, expiration, issued_at, diff --git a/src/invocation/payload.rs b/src/invocation/payload.rs index 728a2dfe..d1f8101d 100644 --- a/src/invocation/payload.rs +++ b/src/invocation/payload.rs @@ -102,7 +102,7 @@ pub struct Payload { /// A [cryptographic nonce] to ensure that the UCAN's [`Cid`] is unique. /// /// [cryptographic nonce]: https://en.wikipedia.org/wiki/Cryptographic_nonce - #[builder(default = "Nonce::generate_16(&mut vec![])")] + #[builder(default = "Nonce::generate_16()")] pub nonce: Nonce, /// An optional [Unix timestamp] (wall-clock time) at which this [`Invocation`] diff --git a/src/receipt/payload.rs b/src/receipt/payload.rs index cd3d22ce..6bbca643 100644 --- a/src/receipt/payload.rs +++ b/src/receipt/payload.rs @@ -79,7 +79,7 @@ pub struct Payload { /// /// [cryptographic nonce]: https://en.wikipedia.org/wiki/Cryptographic_nonce /// [`Cid`]: libipld_core::cid::Cid - #[builder(default = "Nonce::generate_16(&mut vec![])")] + #[builder(default = "Nonce::generate_16()")] pub nonce: Nonce, /// An optional [Unix timestamp] (wall-clock time) at which the From a00db2d764825d9a39b420c8f89c98e089d13b2b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philipp=20Kr=C3=BCger?= Date: Thu, 28 Mar 2024 21:49:25 +0100 Subject: [PATCH 187/188] Rename `CannotCidOr`, API for creating powerlines & more (#18) --- src/crypto/signature/envelope.rs | 100 +++++++++++++++---------------- src/delegation.rs | 23 ++++--- src/delegation/agent.rs | 58 ++++++++---------- src/delegation/payload.rs | 23 ++++--- src/delegation/store.rs | 2 +- src/delegation/store/memory.rs | 32 ++-------- src/delegation/store/traits.rs | 49 ++++++--------- src/invocation/agent.rs | 13 ++-- src/invocation/payload.rs | 6 +- src/invocation/store/memory.rs | 13 +--- src/invocation/store/traits.rs | 31 +++------- 11 files changed, 146 insertions(+), 204 deletions(-) diff --git a/src/crypto/signature/envelope.rs b/src/crypto/signature/envelope.rs index 58282a53..60165def 100644 --- a/src/crypto/signature/envelope.rs +++ b/src/crypto/signature/envelope.rs @@ -1,4 +1,5 @@ use crate::ability::arguments::Named; +use crate::crypto::varsig::Header; use crate::{capsule::Capsule, crypto::varsig, did::Did}; use libipld_core::{ cid::Cid, @@ -10,6 +11,7 @@ use libipld_core::{ use signature::SignatureEncoding; use signature::Verifier; use std::collections::BTreeMap; +use std::io::Write; use thiserror::Error; pub trait Envelope: Sized { @@ -30,60 +32,61 @@ pub trait Envelope: Sized { ) -> Self; fn to_ipld_envelope(&self) -> Ipld { - let inner_args: Named = self.payload().clone().into(); - let inner_ipld: Ipld = inner_args.into(); - - let wrapped_payload: Ipld = - BTreeMap::from_iter([(Self::Payload::TAG.into(), inner_ipld)]).into(); - - let header_bytes: Vec = (*self.varsig_header()).clone().into(); + let wrapped_payload = Self::wrap_payload(self.payload().clone()); + let header_bytes: Vec = self.varsig_header().clone().into(); let header: Ipld = vec![header_bytes.into(), wrapped_payload].into(); let sig_bytes: Ipld = self.signature().to_vec().into(); vec![sig_bytes.into(), header].into() } + fn wrap_payload(payload: Self::Payload) -> Ipld { + let inner_args: Named = payload.into(); + let inner_ipld: Ipld = inner_args.into(); + BTreeMap::from_iter([(Self::Payload::TAG.into(), inner_ipld)]).into() + } + fn try_from_ipld_envelope( ipld: Ipld, ) -> Result>>::Error>> { - if let Ipld::List(list) = ipld { - if let [Ipld::Bytes(sig), Ipld::List(inner)] = list.as_slice() { - if let [Ipld::Bytes(varsig_header), Ipld::Map(btree)] = inner.as_slice() { - if let (1, Some(Ipld::Map(inner))) = ( - btree.len(), - btree.get(::TAG.into()), - ) { - let payload = Self::Payload::try_from(Named(inner.clone())) - .map_err(FromIpldError::CannotParsePayload)?; - - let varsig_header = Self::VarsigHeader::try_from(varsig_header.as_slice()) - .map_err(|_| FromIpldError::CannotParseVarsigHeader)?; - - let signature = ::Signature::try_from(sig.as_slice()) - .map_err(|_| FromIpldError::CannotParseSignature)?; - - Ok(Self::construct(varsig_header, signature, payload)) - } else { - Err(FromIpldError::InvalidPayloadCapsule) - } - } else { - Err(FromIpldError::InvalidVarsigContainer) - } - } else { - Err(FromIpldError::InvalidSignatureContainer) - } - } else { - Err(FromIpldError::InvalidSignatureContainer) - } + let Ipld::List(list) = ipld else { + return Err(FromIpldError::InvalidSignatureContainer); + }; + + let [Ipld::Bytes(sig), Ipld::List(inner)] = list.as_slice() else { + return Err(FromIpldError::InvalidSignatureContainer); + }; + + let [Ipld::Bytes(varsig_header), Ipld::Map(btree)] = inner.as_slice() else { + return Err(FromIpldError::InvalidVarsigContainer); + }; + + let (1, Some(Ipld::Map(inner))) = ( + btree.len(), + btree.get(::TAG.into()), + ) else { + return Err(FromIpldError::InvalidPayloadCapsule); + }; + + let payload = Self::Payload::try_from(Named(inner.clone())) + .map_err(FromIpldError::CannotParsePayload)?; + + let varsig_header = Self::VarsigHeader::try_from(varsig_header.as_slice()) + .map_err(|_| FromIpldError::CannotParseVarsigHeader)?; + + let signature = ::Signature::try_from(sig.as_slice()) + .map_err(|_| FromIpldError::CannotParseSignature)?; + + Ok(Self::construct(varsig_header, signature, payload)) } - fn varsig_encode(self, w: &mut Vec) -> Result<(), libipld_core::error::Error> + fn varsig_encode(&self, mut w: W) -> Result where - Ipld: Encode + From, + Ipld: Encode, { - let codec = varsig::header::Header::codec(self.varsig_header()).clone(); - let ipld = Ipld::from(self); - ipld.encode(codec, w) + let codec = self.varsig_header().codec().clone(); + self.to_ipld_envelope().encode(codec, &mut w)?; + Ok(w) } /// Attempt to sign some payload with a given signer. @@ -132,14 +135,9 @@ pub trait Envelope: Sized { Ipld: Encode, Named: From, { - let ipld: Ipld = BTreeMap::from_iter([( - Self::Payload::TAG.into(), - Named::::from(payload.clone()).into(), - )]) - .into(); - + let ipld = Self::wrap_payload(payload.clone()); let mut buffer = vec![]; - ipld.encode(*varsig::header::Header::codec(&varsig_header), &mut buffer) + ipld.encode(*varsig_header.codec(), &mut buffer) .map_err(SignError::PayloadEncodingError)?; let signature = @@ -188,11 +186,9 @@ pub trait Envelope: Sized { where Ipld: Encode, { - let codec = varsig::header::Header::codec(self.varsig_header()).clone(); - let mut ipld_buffer = vec![]; - self.to_ipld_envelope().encode(codec, &mut ipld_buffer)?; + let encoded = self.varsig_encode(Vec::new())?; + let multihash = Code::Sha2_256.digest(&encoded); - let multihash = Code::Sha2_256.digest(&ipld_buffer); Ok(Cid::new_v1( varsig::header::Header::codec(self.varsig_header()) .clone() diff --git a/src/delegation.rs b/src/delegation.rs index e5c75842..a452f6b3 100644 --- a/src/delegation.rs +++ b/src/delegation.rs @@ -21,15 +21,14 @@ mod payload; pub use agent::Agent; pub use payload::*; -use crate::ability::arguments::Named; use crate::{ + ability::arguments::Named, capsule::Capsule, crypto::{signature::Envelope, varsig, Nonce}, did::{self, Did}, time::{TimeBoundError, Timestamp}, }; -use libipld_core::link::Link; -use libipld_core::{codec::Codec, ipld::Ipld}; +use libipld_core::{codec::Codec, ipld::Ipld, link::Link}; use policy::Predicate; use serde::{Deserialize, Serialize}; use std::collections::BTreeMap; @@ -83,8 +82,8 @@ impl, C: Codec> Delegation { } /// Retrive the `subject` of a [`Delegation`] - pub fn subject(&self) -> &Option { - &self.payload.subject + pub fn subject(&self) -> Option<&DID> { + self.payload.subject.as_ref() } /// Retrive the `audience` of a [`Delegation`] @@ -92,6 +91,16 @@ impl, C: Codec> Delegation { &self.payload.audience } + /// Retrieve the `via` of a [`Delegation`] + pub fn via(&self) -> Option<&DID> { + self.payload.via.as_ref() + } + + /// Retrieve the `command` of a [`Delegation`] + pub fn command(&self) -> &String { + &self.payload.command + } + /// Retrive the `policy` of a [`Delegation`] pub fn policy(&self) -> &Vec { &self.payload.policy @@ -113,8 +122,8 @@ impl, C: Codec> Delegation { } /// Retrive the `expiration` of a [`Delegation`] - pub fn expiration(&self) -> &Timestamp { - &self.payload.expiration + pub fn expiration(&self) -> Option<&Timestamp> { + self.payload.expiration.as_ref() } pub fn check_time(&self, now: SystemTime) -> Result<(), TimeBoundError> { diff --git a/src/delegation/agent.rs b/src/delegation/agent.rs index 38108824..c4395056 100644 --- a/src/delegation/agent.rs +++ b/src/delegation/agent.rs @@ -58,57 +58,47 @@ where pub fn delegate( &self, audience: DID, - subject: &DID, + subject: Option<&DID>, via: Option, command: String, new_policy: Vec, metadata: BTreeMap, - expiration: Timestamp, + expiration: Option, not_before: Option, now: SystemTime, varsig_header: V, - ) -> Result, DelegateError> { + ) -> Result, DelegateError> { let mut salt = self.did.clone().to_string().into_bytes(); let nonce = Nonce::generate_16(); - if *subject == self.did { - let payload: Payload = Payload { - issuer: self.did.clone(), - audience, - subject: Some(subject.clone()), - via, - command, - metadata, - nonce, - expiration: expiration.into(), - not_before: not_before.map(Into::into), - policy: new_policy, - }; - - return Ok(Delegation::try_sign(&self.signer, varsig_header, payload).expect("FIXME")); - } - - let proofs = &self - .store - .get_chain(&self.did, &subject, &command, vec![], now) - .map_err(DelegateError::StoreError)? - .ok_or(DelegateError::ProofsNotFound)?; - let to_delegate = proofs.first().1.payload(); - - let mut policy = to_delegate.policy.clone(); - policy.append(&mut new_policy.clone()); + let (subject, policy) = match subject { + Some(subject) if *subject == self.did => (Some(subject.clone()), new_policy), + None => (None, new_policy), + Some(subject) => { + let proofs = &self + .store + .get_chain(&self.did, &subject, &command, vec![], now) + .map_err(DelegateError::StoreError)? + .ok_or(DelegateError::ProofsNotFound)?; + let to_delegate = proofs.first().1.payload(); + + let mut policy = to_delegate.policy.clone(); + policy.extend(new_policy); + (Some(subject.clone()), policy) + } + }; let payload: Payload = Payload { issuer: self.did.clone(), audience, - subject: Some(subject.clone()), + subject, via, command, - policy, metadata, nonce, - expiration: expiration.into(), - not_before: not_before.map(Into::into), + expiration, + not_before, + policy, }; Ok(Delegation::try_sign(&self.signer, varsig_header, payload).expect("FIXME")) @@ -118,7 +108,7 @@ where &self, cid: Cid, // FIXME remove and generate from the capsule header? delegation: Delegation, - ) -> Result<(), ReceiveError> { + ) -> Result<(), ReceiveError> { if self.store.get(&cid).is_ok() { return Ok(()); } diff --git a/src/delegation/payload.rs b/src/delegation/payload.rs index 064fdca1..f65c18ae 100644 --- a/src/delegation/payload.rs +++ b/src/delegation/payload.rs @@ -77,7 +77,8 @@ pub struct Payload { /// given as a [Unix timestamp]. /// /// [Unix timestamp]: https://en.wikipedia.org/wiki/Unix_time - pub expiration: Timestamp, + #[builder(default)] + pub expiration: Option, /// An optional earliest wall-clock time that the UCAN is valid from, /// given as a [Unix timestamp]. @@ -91,8 +92,10 @@ impl Payload { pub fn check_time(&self, now: SystemTime) -> Result<(), TimeBoundError> { let ts_now = &Timestamp::postel(now); - if &self.expiration < ts_now { - return Err(TimeBoundError::Expired); + if let Some(ref exp) = self.expiration { + if exp < ts_now { + return Err(TimeBoundError::Expired); + } } if let Some(ref nbf) = self.not_before { @@ -190,8 +193,11 @@ where }, "exp" => match ipld { Ipld::Integer(i) => { - expiration = Some(Timestamp::try_from(i).map_err(ParseError::BadTimestamp)?) + expiration = Some(Some( + Timestamp::try_from(i).map_err(ParseError::BadTimestamp)?, + )) } + Ipld::Null => expiration = Some(None), bad => return Err(ParseError::WrongTypeForField("exp".to_string(), bad)), }, "nbf" => match ipld { @@ -308,7 +314,10 @@ impl From> for Named { Ipld::List(payload.policy.into_iter().map(|p| p.into()).collect()) }), ("nonce".to_string(), payload.nonce.into()), - ("exp".to_string(), payload.expiration.into()), + ( + "exp".to_string(), + payload.expiration.map_or(Ipld::Null, |e| e.into()), + ), ]); if let Some(subject) = payload.subject { @@ -348,7 +357,7 @@ where DID::arbitrary_with(did_args), String::arbitrary(), Nonce::arbitrary(), - Timestamp::arbitrary(), + Option::::arbitrary(), Option::::arbitrary(), prop::collection::btree_map(".*", ipld::Newtype::arbitrary(), 0..5).prop_map(|m| { m.into_iter() @@ -442,7 +451,7 @@ mod tests { prop_assert_eq!(cmd.unwrap(), &Ipld::String(payload.command.clone())); prop_assert_eq!(pol.unwrap(), &Ipld::List(payload.policy.clone().into_iter().map(|p| p.into()).collect())); prop_assert_eq!(nonce.unwrap(), &payload.nonce.into()); - prop_assert_eq!(exp.unwrap(), &payload.expiration.into()); + prop_assert_eq!(exp.unwrap(), &payload.expiration.map_or(Ipld::Null, |e| e.into())); // Optional Fields match (payload.subject, named.get("sub")) { diff --git a/src/delegation/store.rs b/src/delegation/store.rs index 680d5f99..22050f87 100644 --- a/src/delegation/store.rs +++ b/src/delegation/store.rs @@ -4,4 +4,4 @@ mod memory; mod traits; pub use memory::MemoryStore; -pub use traits::Store; +pub use traits::*; diff --git a/src/delegation/store/memory.rs b/src/delegation/store/memory.rs index 43260b00..46a2c5ff 100644 --- a/src/delegation/store/memory.rs +++ b/src/delegation/store/memory.rs @@ -144,12 +144,9 @@ where delegation::Payload: TryFrom>, Ipld: Encode, { - type DelegationStoreError = Infallible; + type Error = Infallible; - fn get( - &self, - cid: &Cid, - ) -> Result>>, Self::DelegationStoreError> { + fn get(&self, cid: &Cid) -> Result>>, Self::Error> { // cheap Arc clone Ok(self.lock().ucans.get(cid).cloned()) // FIXME @@ -159,7 +156,7 @@ where &self, cid: Cid, delegation: Delegation, - ) -> Result<(), Self::DelegationStoreError> { + ) -> Result<(), Self::Error> { let mut tx = self.lock(); tx.index @@ -174,7 +171,7 @@ where Ok(()) } - fn revoke(&self, cid: Cid) -> Result<(), Self::DelegationStoreError> { + fn revoke(&self, cid: Cid) -> Result<(), Self::Error> { self.lock().revocations.insert(cid); Ok(()) } @@ -186,8 +183,7 @@ where command: &str, policy: Vec, // FIXME now: SystemTime, - ) -> Result>)>>, Self::DelegationStoreError> - { + ) -> Result>)>>, Self::Error> { let blank_set = BTreeSet::new(); let blank_map = BTreeMap::new(); let tx = self.lock(); @@ -336,7 +332,6 @@ mod tests { .issuer(did.clone()) .audience(did.clone()) .command("/".into()) - .expiration(crate::time::Timestamp::five_years_from_now()) .build()?, )?; @@ -365,7 +360,6 @@ mod tests { .issuer(did.clone()) .audience(did.clone()) .command("/".into()) - .expiration(crate::time::Timestamp::five_years_from_now()) .build()?, )?; @@ -424,7 +418,6 @@ mod tests { .issuer(alice.clone()) .audience(bob.clone()) .command("/".into()) - .expiration(crate::time::Timestamp::five_years_from_now()) .build()?, )?; @@ -456,7 +449,6 @@ mod tests { .issuer(bob.clone()) .audience(carol.clone()) .command("/example".into()) - .expiration(crate::time::Timestamp::five_years_from_now()) .build()?, )?; @@ -470,7 +462,6 @@ mod tests { .issuer(alice.clone()) .audience(bob.clone()) .command("/".into()) - .expiration(crate::time::Timestamp::five_years_from_now()) .build()?, )?; @@ -484,7 +475,6 @@ mod tests { .issuer(alice.clone()) .audience(carol.clone()) .command("/test".into()) - .expiration(crate::time::Timestamp::five_years_from_now()) .build()?, )?; @@ -516,7 +506,6 @@ mod tests { .issuer(alice.clone()) .audience(bob.clone()) .command("/".into()) - .expiration(crate::time::Timestamp::five_years_from_now()) .build()?, )?; @@ -530,7 +519,6 @@ mod tests { .issuer(bob.clone()) .audience(carol.clone()) .command("/".into()) - .expiration(crate::time::Timestamp::five_years_from_now()) .build()?, )?; @@ -572,7 +560,6 @@ mod tests { .issuer(alice.clone()) .audience(bob.clone()) .command("/test".into()) - .expiration(crate::time::Timestamp::five_years_from_now()) .build()?, )?; @@ -586,7 +573,6 @@ mod tests { .issuer(bob.clone()) .audience(carol.clone()) .command("/test/me".into()) - .expiration(crate::time::Timestamp::five_years_from_now()) .build()?, )?; @@ -635,7 +621,6 @@ mod tests { .issuer(alice.clone()) .audience(bob.clone()) .command("/test".into()) - .expiration(crate::time::Timestamp::five_years_from_now()) .build()?, )?; @@ -649,7 +634,6 @@ mod tests { .issuer(carol.clone()) .audience(dan.clone()) .command("/test/me".into()) - .expiration(crate::time::Timestamp::five_years_from_now()) .build()?, )?; @@ -696,7 +680,6 @@ mod tests { .issuer(bob.clone()) .audience(carol.clone()) .command("/".into()) - .expiration(crate::time::Timestamp::five_years_from_now()) .build()?, )?; @@ -709,7 +692,6 @@ mod tests { .issuer(carol.clone()) .audience(dave.clone()) .command("/".into()) - .expiration(crate::time::Timestamp::five_years_from_now()) .build()?, // I don't love this is now failable )?; @@ -722,7 +704,6 @@ mod tests { .issuer(alice.clone()) .audience(bob.clone()) .command("/".into()) - .expiration(crate::time::Timestamp::five_years_from_now()) .build()?, )?; @@ -780,7 +761,6 @@ mod tests { .issuer(bob.clone()) .audience(carol.clone()) .command("/".into()) - .expiration(crate::time::Timestamp::five_years_from_now()) .build()?, )?; @@ -793,7 +773,6 @@ mod tests { .issuer(carol.clone()) .audience(dave.clone()) .command("/".into()) - .expiration(crate::time::Timestamp::five_years_from_now()) .build()?, // I don't love this is now failable )?; @@ -806,7 +785,6 @@ mod tests { .issuer(alice.clone()) .audience(bob.clone()) .command("/".into()) - .expiration(crate::time::Timestamp::five_years_from_now()) .build()?, )?; diff --git a/src/delegation/store/traits.rs b/src/delegation/store/traits.rs index f71cb5db..76486f40 100644 --- a/src/delegation/store/traits.rs +++ b/src/delegation/store/traits.rs @@ -20,31 +20,24 @@ where Payload: TryFrom>, Named: From>, { - type DelegationStoreError: Debug; + type Error: Debug; - fn get( - &self, - cid: &Cid, - ) -> Result>>, Self::DelegationStoreError>; + fn get(&self, cid: &Cid) -> Result>>, Self::Error>; fn insert( &self, delegation: Delegation, - ) -> Result<(), CannotCidOr> { + ) -> Result<(), DelegationInsertError> { self.insert_keyed(delegation.cid()?, delegation) - .map_err(CannotCidOr::StoreError) + .map_err(DelegationInsertError::StoreError) } - fn insert_keyed( - &self, - cid: Cid, - delegation: Delegation, - ) -> Result<(), Self::DelegationStoreError>; + fn insert_keyed(&self, cid: Cid, delegation: Delegation) -> Result<(), Self::Error>; // FIXME validate invocation // store invocation // just... move to invocation - fn revoke(&self, cid: Cid) -> Result<(), Self::DelegationStoreError>; + fn revoke(&self, cid: Cid) -> Result<(), Self::Error>; fn get_chain( &self, @@ -53,7 +46,7 @@ where command: &str, policy: Vec, now: SystemTime, - ) -> Result>)>>, Self::DelegationStoreError>; + ) -> Result>)>>, Self::Error>; fn get_chain_cids( &self, @@ -62,7 +55,7 @@ where command: &str, policy: Vec, now: SystemTime, - ) -> Result>, Self::DelegationStoreError> { + ) -> Result>, Self::Error> { self.get_chain(audience, subject, command, policy, now) .map(|chain| chain.map(|chain| chain.map(|(cid, _)| cid))) } @@ -74,7 +67,7 @@ where command: &str, policy: Vec, now: SystemTime, - ) -> Result { + ) -> Result { self.get_chain(audience, &issuer, command, policy, now) .map(|chain| chain.is_some()) } @@ -82,10 +75,10 @@ where fn get_many( &self, cids: &[Cid], - ) -> Result>>>, Self::DelegationStoreError> { + ) -> Result>>>, Self::Error> { cids.iter() .map(|cid| self.get(cid)) - .collect::>() + .collect::>() } } @@ -96,24 +89,17 @@ where Payload: TryFrom>, Named: From>, { - type DelegationStoreError = >::DelegationStoreError; + type Error = >::Error; - fn get( - &self, - cid: &Cid, - ) -> Result>>, Self::DelegationStoreError> { + fn get(&self, cid: &Cid) -> Result>>, Self::Error> { (**self).get(cid) } - fn insert_keyed( - &self, - cid: Cid, - delegation: Delegation, - ) -> Result<(), Self::DelegationStoreError> { + fn insert_keyed(&self, cid: Cid, delegation: Delegation) -> Result<(), Self::Error> { (**self).insert_keyed(cid, delegation) } - fn revoke(&self, cid: Cid) -> Result<(), Self::DelegationStoreError> { + fn revoke(&self, cid: Cid) -> Result<(), Self::Error> { (**self).revoke(cid) } @@ -124,14 +110,13 @@ where command: &str, policy: Vec, now: SystemTime, - ) -> Result>)>>, Self::DelegationStoreError> - { + ) -> Result>)>>, Self::Error> { (**self).get_chain(audience, subject, command, policy, now) } } #[derive(Debug, Error)] -pub enum CannotCidOr { +pub enum DelegationInsertError { #[error("Cannot make CID from delegation based on supplied Varsig")] CannotMakeCid(#[from] libipld_core::error::Error), diff --git a/src/invocation/agent.rs b/src/invocation/agent.rs index 1910a26f..00cf2431 100644 --- a/src/invocation/agent.rs +++ b/src/invocation/agent.rs @@ -97,7 +97,7 @@ where issued_at: Option, now: SystemTime, varsig_header: V, - ) -> Result, InvokeError> { + ) -> Result, InvokeError> { let proofs = if subject == self.did { vec![] } else { @@ -175,7 +175,7 @@ where pub fn receive( &self, invocation: Invocation, - ) -> Result>, ReceiveError> + ) -> Result>, ReceiveError> where arguments::Named: From, Payload: TryFrom>, @@ -188,7 +188,7 @@ where &self, invocation: Invocation, now: SystemTime, - ) -> Result>, ReceiveError> + ) -> Result>, ReceiveError> where arguments::Named: From, Payload: TryFrom>, @@ -219,7 +219,7 @@ where .ok_or(ReceiveError::DelegationNotFound(*cid))? .payload) }) - .collect::>>()?; + .collect::>>()?; let _ = &invocation .payload @@ -300,7 +300,7 @@ pub enum ReceiveError, V: varsig::Header< SigVerifyError(#[from] signature::ValidateError), #[error("invocation store error: {0}")] - InvocationStoreError(#[source] >::InvocationStoreError), + InvocationStoreError(#[source] >::Error), #[error("delegation store error: {0}")] DelegationStoreError(#[source] D), @@ -650,7 +650,6 @@ mod tests { .issuer(account.clone()) .audience(server.clone()) .command("/".into()) - .expiration(crate::time::Timestamp::five_years_from_now()) .build()?, )?; @@ -663,7 +662,6 @@ mod tests { .issuer(server.clone()) .audience(device.clone()) .command("/".into()) - .expiration(crate::time::Timestamp::five_years_from_now()) .build()?, // I don't love this is now failable )?; @@ -676,7 +674,6 @@ mod tests { .issuer(dnslink.clone()) .audience(account.clone()) .command("/".into()) - .expiration(crate::time::Timestamp::five_years_from_now()) .build()?, )?; diff --git a/src/invocation/payload.rs b/src/invocation/payload.rs index d1f8101d..5a2badb0 100644 --- a/src/invocation/payload.rs +++ b/src/invocation/payload.rs @@ -188,8 +188,10 @@ impl Payload { } } - if proof.expiration < now_ts { - return Err(ValidationError::Expired.into()); + if let Some(exp) = proof.expiration { + if exp < now_ts { + return Err(ValidationError::Expired.into()); + } } if let Some(nbf) = proof.not_before.clone() { diff --git a/src/invocation/store/memory.rs b/src/invocation/store/memory.rs index 083624df..06af37c6 100644 --- a/src/invocation/store/memory.rs +++ b/src/invocation/store/memory.rs @@ -49,20 +49,13 @@ impl, Enc: Codec> Default for MemoryStore, Enc: Codec> Store for MemoryStore { - type InvocationStoreError = Infallible; + type Error = Infallible; - fn get( - &self, - cid: Cid, - ) -> Result>>, Self::InvocationStoreError> { + fn get(&self, cid: Cid) -> Result>>, Self::Error> { Ok(self.lock().store.get(&cid).cloned()) } - fn put( - &self, - cid: Cid, - invocation: Invocation, - ) -> Result<(), Self::InvocationStoreError> { + fn put(&self, cid: Cid, invocation: Invocation) -> Result<(), Self::Error> { self.lock().store.insert(cid, Arc::new(invocation)); Ok(()) } diff --git a/src/invocation/store/traits.rs b/src/invocation/store/traits.rs index 7442d449..5340c925 100644 --- a/src/invocation/store/traits.rs +++ b/src/invocation/store/traits.rs @@ -3,20 +3,13 @@ use libipld_core::{cid::Cid, codec::Codec}; use std::{fmt::Debug, sync::Arc}; pub trait Store, C: Codec> { - type InvocationStoreError: Debug; + type Error: Debug; - fn get( - &self, - cid: Cid, - ) -> Result>>, Self::InvocationStoreError>; + fn get(&self, cid: Cid) -> Result>>, Self::Error>; - fn put( - &self, - cid: Cid, - invocation: Invocation, - ) -> Result<(), Self::InvocationStoreError>; + fn put(&self, cid: Cid, invocation: Invocation) -> Result<(), Self::Error>; - fn has(&self, cid: Cid) -> Result { + fn has(&self, cid: Cid) -> Result { Ok(self.get(cid).is_ok()) } } @@ -24,23 +17,13 @@ pub trait Store, C: Codec> { impl, T, DID: Did, V: varsig::Header, C: Codec> Store for &S { - type InvocationStoreError = >::InvocationStoreError; + type Error = >::Error; - fn get( - &self, - cid: Cid, - ) -> Result< - Option>>, - >::InvocationStoreError, - > { + fn get(&self, cid: Cid) -> Result>>, Self::Error> { (**self).get(cid) } - fn put( - &self, - cid: Cid, - invocation: Invocation, - ) -> Result<(), >::InvocationStoreError> { + fn put(&self, cid: Cid, invocation: Invocation) -> Result<(), Self::Error> { (**self).put(cid, invocation) } } From a8d1b1d5b8931e4049d84fbf5e49c03a3f54894f Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Fri, 29 Mar 2024 00:40:36 +0200 Subject: [PATCH 188/188] Cleanup unused imports & bump Nix Flake (#16) --- Cargo.toml | 2 +- flake.lock | 36 ++++++++++++------------ src/ability/crud/update.rs | 2 +- src/ability/msg.rs | 3 -- src/ability/ucan/assert.rs | 2 +- src/ability/ucan/revoke.rs | 1 - src/crypto/nonce.rs | 1 - src/delegation/agent.rs | 1 - src/delegation/payload.rs | 6 ++-- src/delegation/policy/predicate.rs | 2 +- src/delegation/policy/selector.rs | 11 +------- src/delegation/policy/selector/filter.rs | 6 ++-- src/delegation/policy/selector/select.rs | 3 +- src/delegation/store/memory.rs | 8 +++--- src/did/key/signer.rs | 8 ------ src/did/preset.rs | 2 +- src/did/traits.rs | 1 - src/invocation/agent.rs | 11 ++------ src/invocation/payload.rs | 31 +++++++++----------- src/invocation/promise/store/memory.rs | 1 - src/invocation/promise/store/traits.rs | 1 - src/ipld/cid.rs | 7 +++-- src/ipld/newtype.rs | 1 - src/ipld/number.rs | 2 +- src/ipld/promised.rs | 2 +- 25 files changed, 56 insertions(+), 95 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 648b98f6..cb455efc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,7 +8,7 @@ include = ["/src", "/examples", "/benches", "README.md", "LICENSE"] license = "Apache-2.0" readme = "README.md" edition = "2021" -rust-version = "1.75" +rust-version = "1.77" documentation = "https://docs.rs/ucan" repository = "https://github.com/ucan-wg/rs-ucan" authors = [ diff --git a/flake.lock b/flake.lock index a4fa5849..c13ba4a7 100644 --- a/flake.lock +++ b/flake.lock @@ -6,11 +6,11 @@ "nixpkgs": "nixpkgs" }, "locked": { - "lastModified": 1709701816, - "narHash": "sha256-Kwv17invnVzrNrm5fK3Bt6ISJqfXguCx6vc3JDOQtCE=", + "lastModified": 1709702368, + "narHash": "sha256-1YOPubkJ5M6HigdfN0gn0AZ3kx6MHboG9UbWpYpk3gM=", "owner": "expede", "repo": "nix-command-utils", - "rev": "12056907b5194b82060fd8bc6ea11c9fdffb5f25", + "rev": "8f7179876383495b1f98311e53ebb41649ca270a", "type": "github" }, "original": { @@ -25,11 +25,11 @@ "nixpkgs": "nixpkgs_2" }, "locked": { - "lastModified": 1708939976, - "narHash": "sha256-O5+nFozxz2Vubpdl1YZtPrilcIXPcRAjqNdNE8oCRoA=", + "lastModified": 1711099426, + "narHash": "sha256-HzpgM/wc3aqpnHJJ2oDqPBkNsqWbW0WfWUO8lKu8nGk=", "owner": "numtide", "repo": "devshell", - "rev": "5ddecd67edbd568ebe0a55905273e56cc82aabe3", + "rev": "2d45b54ca4a183f2fdcf4b19c895b64fbf620ee8", "type": "github" }, "original": { @@ -78,11 +78,11 @@ "systems": "systems_3" }, "locked": { - "lastModified": 1709126324, - "narHash": "sha256-q6EQdSeUZOG26WelxqkmR7kArjgWCdw5sfJVHPH/7j8=", + "lastModified": 1710146030, + "narHash": "sha256-SZ5L6eA7HJ/nmkzGG7/ISclqe6oZdOZTNoesiInkXPQ=", "owner": "numtide", "repo": "flake-utils", - "rev": "d465f4819400de7c8d874d50b982301f28a84605", + "rev": "b1d9ab70662946ef0850d488da1c9019f3a9752a", "type": "github" }, "original": { @@ -93,11 +93,11 @@ }, "nixos-unstable": { "locked": { - "lastModified": 1709558755, - "narHash": "sha256-hx4FIbk4X4ve1oiHLOj+VE6dzO4CtXBR5RSU6kaq34M=", + "lastModified": 1711616573, + "narHash": "sha256-FvZiEl6D4iLXqSQ3oGjQ/qehhPZ5E7iTHr/YA1Rw8kY=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "207107bbc7d6d19a8b2c36a088d3756d03490243", + "rev": "c99b66784962e8984444b4d9e72d00d3549afdb2", "type": "github" }, "original": { @@ -139,11 +139,11 @@ }, "nixpkgs_3": { "locked": { - "lastModified": 1709569716, - "narHash": "sha256-iOR44RU4jQ+YPGrn+uQeYAp7Xo7Z/+gT+wXJoGxxLTY=", + "lastModified": 1711460390, + "narHash": "sha256-akSgjDZL6pVHEfSE6sz1DNSXuYX6hq+P/1Z5IoYWs7E=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "617579a787259b9a6419492eaac670a5f7663917", + "rev": "44733514b72e732bd49f5511bd0203dea9b9a434", "type": "github" }, "original": { @@ -172,11 +172,11 @@ ] }, "locked": { - "lastModified": 1709691047, - "narHash": "sha256-2Vwx1FLufoMEcOS8KAwP8H83IP3Hw6ZPrIDHkSXrFCY=", + "lastModified": 1711592024, + "narHash": "sha256-oD4OJ3TRmVrbAuKZWxElRCyCagNCDuhfw2exBmNOy48=", "owner": "oxalica", "repo": "rust-overlay", - "rev": "d55139f3061cdf2c8f5f7bc8d49e884826e6a4ea", + "rev": "aa858717377db2ed8ffd2d44147d907baee656e5", "type": "github" }, "original": { diff --git a/src/ability/crud/update.rs b/src/ability/crud/update.rs index 935ab40f..b0f7a6bb 100644 --- a/src/ability/crud/update.rs +++ b/src/ability/crud/update.rs @@ -7,7 +7,7 @@ use crate::{ }; use libipld_core::ipld::Ipld; use serde::{Deserialize, Serialize}; -use std::{collections::BTreeMap, path::PathBuf}; +use std::path::PathBuf; use thiserror::Error; #[cfg_attr(doc, aquamarine::aquamarine)] diff --git a/src/ability/msg.rs b/src/ability/msg.rs index 9fd9230e..0e7e35d1 100644 --- a/src/ability/msg.rs +++ b/src/ability/msg.rs @@ -17,9 +17,6 @@ use receive::{PromisedReceive, Receive}; use send::{PromisedSend, Send}; use serde::{Deserialize, Serialize}; -#[cfg(feature = "test_utils")] -use proptest::prelude::*; - #[cfg(feature = "test_utils")] use proptest_derive::Arbitrary; diff --git a/src/ability/ucan/assert.rs b/src/ability/ucan/assert.rs index 62946c23..410d0148 100644 --- a/src/ability/ucan/assert.rs +++ b/src/ability/ucan/assert.rs @@ -1,6 +1,6 @@ use crate::ability::command::Command; use crate::task::Task; -use libipld_core::{cid::Cid, ipld::Ipld}; +use libipld_core::cid::Cid; // Things that you can assert include content and receipts diff --git a/src/ability/ucan/revoke.rs b/src/ability/ucan/revoke.rs index b7ee1313..37c4f051 100644 --- a/src/ability/ucan/revoke.rs +++ b/src/ability/ucan/revoke.rs @@ -9,7 +9,6 @@ use crate::{ }; use libipld_core::{cid::Cid, ipld::Ipld}; use serde::{Deserialize, Serialize}; -use std::collections::BTreeMap; use std::fmt::Debug; /// The fully resolved variant: ready to execute. diff --git a/src/crypto/nonce.rs b/src/crypto/nonce.rs index 5609ca82..ee1a3b53 100644 --- a/src/crypto/nonce.rs +++ b/src/crypto/nonce.rs @@ -31,7 +31,6 @@ impl PartialEq for Nonce { (Nonce::Custom(a), Nonce::Custom(b)) => a == b, (Nonce::Custom(a), Nonce::Nonce16(b)) => a.as_slice() == b, (Nonce::Nonce16(a), Nonce::Custom(b)) => a == b.as_slice(), - _ => false, } } } diff --git a/src/delegation/agent.rs b/src/delegation/agent.rs index c4395056..9b050878 100644 --- a/src/delegation/agent.rs +++ b/src/delegation/agent.rs @@ -68,7 +68,6 @@ where now: SystemTime, varsig_header: V, ) -> Result, DelegateError> { - let mut salt = self.did.clone().to_string().into_bytes(); let nonce = Nonce::generate_16(); let (subject, policy) = match subject { diff --git a/src/delegation/payload.rs b/src/delegation/payload.rs index f65c18ae..4446623b 100644 --- a/src/delegation/payload.rs +++ b/src/delegation/payload.rs @@ -3,15 +3,13 @@ use crate::ability::arguments::Named; use crate::time; use crate::{ capsule::Capsule, - crypto::{varsig, Nonce}, + crypto::Nonce, did::{Did, Verifiable}, time::{TimeBoundError, Timestamp}, }; use core::str::FromStr; use derive_builder::Builder; -use did_url::DID; -use libipld_core::{codec::Codec, error::SerdeError, ipld::Ipld, serde as ipld_serde}; -use serde::{Deserialize, Serialize}; +use libipld_core::ipld::Ipld; use std::{collections::BTreeMap, fmt::Debug}; use thiserror::Error; use web_time::SystemTime; diff --git a/src/delegation/policy/predicate.rs b/src/delegation/policy/predicate.rs index 30c9c0dd..18ee1b98 100644 --- a/src/delegation/policy/predicate.rs +++ b/src/delegation/policy/predicate.rs @@ -3,7 +3,7 @@ use super::selector::{Select, SelectorError}; use crate::ipld; use enum_as_inner::EnumAsInner; use libipld_core::ipld::Ipld; -use std::{fmt, str::FromStr}; +use std::str::FromStr; use thiserror::Error; #[cfg(feature = "test_utils")] diff --git a/src/delegation/policy/selector.rs b/src/delegation/policy/selector.rs index acba9a2c..f33cc7f3 100644 --- a/src/delegation/policy/selector.rs +++ b/src/delegation/policy/selector.rs @@ -9,16 +9,7 @@ pub use select::Select; pub use selectable::Selectable; use filter::Filter; -use nom::{ - self, - bytes::complete::tag, - character::complete::char, - combinator::map_res, - error::context, - multi::{many0, many1}, - sequence::preceded, - IResult, -}; +use nom::{self, character::complete::char, multi::many0, sequence::preceded}; use serde::{Deserialize, Deserializer, Serialize, Serializer}; use std::cmp::Ordering; use std::{fmt, str::FromStr}; diff --git a/src/delegation/policy/selector/filter.rs b/src/delegation/policy/selector/filter.rs index 96343a5a..a9d089d2 100644 --- a/src/delegation/policy/selector/filter.rs +++ b/src/delegation/policy/selector/filter.rs @@ -4,8 +4,8 @@ use nom::{ self, branch::alt, bytes::complete::tag, - character::complete::{alphanumeric1, anychar, char, digit1}, - combinator::{map_opt, map_res}, + character::complete::{alphanumeric1, char, digit1}, + combinator::map_res, error::context, multi::many1, sequence::{delimited, preceded, terminated}, @@ -33,7 +33,7 @@ impl Filter { (Filter::Values, Filter::Values) => true, (Filter::ArrayIndex(_a), Filter::Values) => true, (Filter::Field(_k), Filter::Values) => true, - (Filter::Try(a), Filter::Try(b)) => a.is_in(b), // FIXME Try is basically == null? + (Filter::Try(a), Filter::Try(b)) => a.is_in(b), _ => false, } } diff --git a/src/delegation/policy/selector/select.rs b/src/delegation/policy/selector/select.rs index 7506cc63..67f3eaa9 100644 --- a/src/delegation/policy/selector/select.rs +++ b/src/delegation/policy/selector/select.rs @@ -1,7 +1,6 @@ -use super::Selector; // FIXME cycle? +use super::Selector; use super::{error::SelectorErrorReason, filter::Filter, Selectable, SelectorError}; use libipld_core::ipld::Ipld; -use serde::{Deserialize, Serialize}; use std::cmp::Ordering; use std::fmt; use std::str::FromStr; diff --git a/src/delegation/store/memory.rs b/src/delegation/store/memory.rs index 46a2c5ff..bf1c4cab 100644 --- a/src/delegation/store/memory.rs +++ b/src/delegation/store/memory.rs @@ -10,8 +10,8 @@ use libipld_core::codec::Encode; use libipld_core::ipld::Ipld; use libipld_core::{cid::Cid, codec::Codec}; use nonempty::NonEmpty; -use std::borrow::Cow; use std::{ + borrow::Cow, collections::{BTreeMap, BTreeSet}, convert::Infallible, sync::{Arc, Mutex, MutexGuard}, @@ -160,7 +160,7 @@ where let mut tx = self.lock(); tx.index - .entry(delegation.subject().clone()) + .entry(delegation.subject().cloned()) .or_default() .entry(delegation.audience().clone()) .or_default() @@ -181,7 +181,7 @@ where aud: &DID, subject: &DID, command: &str, - policy: Vec, // FIXME + policy: Vec, now: SystemTime, ) -> Result>)>>, Self::Error> { let blank_set = BTreeSet::new(); @@ -251,7 +251,7 @@ where let issuer = delegation.issuer().clone(); // Hit a root delegation, AKA base case - if &Some(issuer.clone()) == delegation.subject() { + if Some(&issuer) == delegation.subject() { break 'outer; } diff --git a/src/did/key/signer.rs b/src/did/key/signer.rs index b7f95e40..7d69fa94 100644 --- a/src/did/key/signer.rs +++ b/src/did/key/signer.rs @@ -22,9 +22,6 @@ use crate::crypto::rs256; #[cfg(feature = "rs512")] use crate::crypto::rs512; -#[cfg(feature = "bls")] -use crate::crypto::bls12381; - /// Signer types that are verifiable by `did:key` [`Verifier`]s. #[derive(Clone, EnumAsInner)] pub enum Signer { @@ -63,11 +60,6 @@ pub enum Signer { /// `BLS 12-381` signer for the "min sig" variant. #[cfg(feature = "bls")] BlsMinSig(blst::min_sig::SecretKey), - // /// An unknown signer type. - // /// - // /// This is primarily for parsing, where reification is delayed - // /// until the DID method is known. - // FIXME rmeove Unknown(Vec), } impl signature::Signer for Signer { diff --git a/src/did/preset.rs b/src/did/preset.rs index a4616525..ff34c699 100644 --- a/src/did/preset.rs +++ b/src/did/preset.rs @@ -54,7 +54,7 @@ pub enum Signer { impl std::fmt::Debug for Signer { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { - Signer::Key(signer) => write!(f, "Signer::Key(HIDDEN)"), + Signer::Key(_signer) => write!(f, "Signer::Key(HIDDEN)"), } } } diff --git a/src/did/traits.rs b/src/did/traits.rs index 76e9220f..8e55df2d 100644 --- a/src/did/traits.rs +++ b/src/did/traits.rs @@ -1,4 +1,3 @@ -use did_url::DID; use std::fmt; use std::str::FromStr; diff --git a/src/invocation/agent.rs b/src/invocation/agent.rs index 00cf2431..ee040a21 100644 --- a/src/invocation/agent.rs +++ b/src/invocation/agent.rs @@ -4,20 +4,13 @@ use super::{ Invocation, }; use crate::{ - ability::{ - self, arguments, - arguments::Named, - command::ToCommand, - parse::{ParseAbility, ParseAbilityError}, - ucan::revoke::Revoke, - }, + ability::{self, arguments, arguments::Named, command::ToCommand, parse::ParseAbility}, crypto::{ signature::{self, Envelope}, varsig, Nonce, }, delegation, did::{self, Did}, - invocation::payload::PayloadBuilder, time::Timestamp, }; use enum_as_inner::EnumAsInner; @@ -26,7 +19,7 @@ use libipld_core::{ codec::{Codec, Encode}, ipld::Ipld, }; -use std::{collections::BTreeMap, fmt, marker::PhantomData}; +use std::{collections::BTreeMap, marker::PhantomData}; use thiserror::Error; use web_time::SystemTime; diff --git a/src/invocation/payload.rs b/src/invocation/payload.rs index 5a2badb0..4543046f 100644 --- a/src/invocation/payload.rs +++ b/src/invocation/payload.rs @@ -1,13 +1,10 @@ use super::promise::Resolvable; -use crate::ability::command::Command; use crate::ability::parse::ParseAbilityError; -use crate::delegation::policy::selector; -use crate::invocation::Named; use crate::time; use crate::{ - ability::{arguments, command::ToCommand, parse::ParseAbility}, + ability::{arguments::Named, command::ToCommand, parse::ParseAbility}, capsule::Capsule, - crypto::{varsig, Nonce}, + crypto::Nonce, delegation::{ self, policy::{selector::SelectorError, Predicate}, @@ -16,7 +13,7 @@ use crate::{ time::{Expired, Timestamp}, }; use derive_builder::Builder; -use libipld_core::{cid::Cid, codec::Codec, ipld::Ipld}; +use libipld_core::{cid::Cid, ipld::Ipld}; use serde::{ de::{self, MapAccess, Visitor}, ser::SerializeStruct, @@ -158,7 +155,7 @@ impl Payload { where A: ToCommand + Clone, DID: Clone, - arguments::Named: From, + Named: From, { let now_ts = Timestamp::postel(now); @@ -168,7 +165,7 @@ impl Payload { } } - let args: arguments::Named = self.ability.clone().into(); + let args: Named = self.ability.clone().into(); let mut cmd = self.ability.to_command(); if !cmd.ends_with('/') { @@ -274,17 +271,17 @@ impl Capsule for Payload { const TAG: &'static str = "ucan/i@1.0.0-rc.1"; } -impl From> for arguments::Named +impl From> for Named where - arguments::Named: From, + Named: From, { fn from(payload: Payload) -> Self { - let mut args = arguments::Named::from_iter([ + let mut args = Named::from_iter([ ("iss".into(), { payload.issuer.to_string().into() }), ("sub".into(), { payload.subject.to_string().into() }), ("cmd".into(), { payload.ability.to_command().into() }), ("args".into(), { - Ipld::Map(arguments::Named::::from(payload.ability).0) + Ipld::Map(Named::::from(payload.ability).0) }), ("prf".into(), { Ipld::List(payload.proofs.iter().map(Into::into).collect()) @@ -318,10 +315,10 @@ where impl From> for Ipld where - arguments::Named: From>, + Named: From>, { fn from(payload: Payload) -> Self { - arguments::Named::from(payload).into() + Named::from(payload).into() } } @@ -514,14 +511,14 @@ impl Verifiable for Payload { } } -impl TryFrom> for Payload +impl TryFrom> for Payload where ::ArgsErr: fmt::Debug, ::Err: fmt::Debug, { type Error = ParseError; - fn try_from(named: arguments::Named) -> Result { + fn try_from(named: Named) -> Result { let mut subject = None; let mut issuer = None; let mut audience = None; @@ -828,7 +825,7 @@ mod tests { } #[test_log::test] - fn test_non_payload(named in arguments::Named::::arbitrary()) { + fn test_non_payload(named in Named::::arbitrary()) { // Just ensuring that a negative test shows up let parsed = Payload::::try_from(named); prop_assert!(parsed.is_err()) diff --git a/src/invocation/promise/store/memory.rs b/src/invocation/promise/store/memory.rs index 6f587f7a..afc5653b 100644 --- a/src/invocation/promise/store/memory.rs +++ b/src/invocation/promise/store/memory.rs @@ -1,5 +1,4 @@ use super::Store; -use crate::{did::Did, invocation::promise::Resolvable}; use libipld_core::cid::Cid; use std::{ collections::{BTreeMap, BTreeSet}, diff --git a/src/invocation/promise/store/traits.rs b/src/invocation/promise/store/traits.rs index 0e894c84..35568ab3 100644 --- a/src/invocation/promise/store/traits.rs +++ b/src/invocation/promise/store/traits.rs @@ -1,4 +1,3 @@ -use crate::{did::Did, invocation::promise::Resolvable}; use libipld_core::cid::Cid; use std::collections::BTreeSet; diff --git a/src/ipld/cid.rs b/src/ipld/cid.rs index 5715b5c8..09d852a8 100644 --- a/src/ipld/cid.rs +++ b/src/ipld/cid.rs @@ -1,7 +1,7 @@ //! Utilities for [`Cid`]s use crate::ipld; -use libipld_core::{cid::Cid, ipld::Ipld, multihash::MultihashGeneric}; +use libipld_core::{cid::Cid, ipld::Ipld}; use serde::{Deserialize, Serialize}; use thiserror::Error; @@ -130,8 +130,9 @@ impl Arbitrary for Newtype { // Very much faking it any::<([u8; 32], SomeMultihash, SomeCodec)>() .prop_map(|(hash_bytes, hasher, codec)| { - let multihash = MultihashGeneric::wrap(hasher.0.into(), &hash_bytes.as_slice()) - .expect("Sha2_256 should always successfully encode a hash"); + let multihash = + multihash::MultihashGeneric::wrap(hasher.0.into(), &hash_bytes.as_slice()) + .expect("Sha2_256 should always successfully encode a hash"); let cid = Cid::new_v1(codec.0.into(), multihash); Newtype { cid } diff --git a/src/ipld/newtype.rs b/src/ipld/newtype.rs index 31451de1..7e898ed2 100644 --- a/src/ipld/newtype.rs +++ b/src/ipld/newtype.rs @@ -1,6 +1,5 @@ use libipld_core::ipld::Ipld; use serde::{Deserialize, Serialize}; -use std::fmt; use std::path::PathBuf; use thiserror::Error; diff --git a/src/ipld/number.rs b/src/ipld/number.rs index e7c3c611..25cbaae3 100644 --- a/src/ipld/number.rs +++ b/src/ipld/number.rs @@ -1,7 +1,7 @@ //! Helpers for working with [`Ipld`] numerics. use enum_as_inner::EnumAsInner; -use libipld_core::{error::SerdeError, ipld::Ipld, serde as ipld_serde}; +use libipld_core::ipld::Ipld; use serde_derive::{Deserialize, Serialize}; use thiserror::Error; diff --git a/src/ipld/promised.rs b/src/ipld/promised.rs index 0416fde2..5e336148 100644 --- a/src/ipld/promised.rs +++ b/src/ipld/promised.rs @@ -1,7 +1,7 @@ use crate::{ ability::arguments, invocation::promise::{self, Pending, PromiseErr, PromiseOk}, - ipld, url, + url, }; use enum_as_inner::EnumAsInner; use libipld_core::{cid::Cid, ipld::Ipld};

>::PromiseStoreError: fmt::Debug, ::Promised, DID, V, C>>::InvocationStoreError: fmt::Debug, diff --git a/src/invocation/store.rs b/src/invocation/store.rs index d9ef104d..e7e3ad50 100644 --- a/src/invocation/store.rs +++ b/src/invocation/store.rs @@ -5,7 +5,7 @@ use crate::{crypto::varsig, did::Did}; use libipld_core::{cid::Cid, codec::Codec}; use std::{collections::BTreeMap, convert::Infallible}; -pub trait Store, Enc: Codec + Into + TryFrom> { +pub trait Store, Enc: Codec + Into + TryFrom> { type InvocationStoreError; fn get( @@ -25,11 +25,11 @@ pub trait Store, Enc: Codec + Into + Tr } #[derive(Debug, Clone, PartialEq)] -pub struct MemoryStore, Enc: Codec + Into + TryFrom> { +pub struct MemoryStore, Enc: Codec + Into + TryFrom> { store: BTreeMap>, } -impl, Enc: Codec + Into + TryFrom> +impl, Enc: Codec + Into + TryFrom> Store for MemoryStore { type InvocationStoreError = Infallible; diff --git a/src/receipt.rs b/src/receipt.rs index 087bd013..1c1feb79 100644 --- a/src/receipt.rs +++ b/src/receipt.rs @@ -27,7 +27,7 @@ pub struct Receipt< T: Responds, DID: Did = did::preset::Verifier, V: varsig::Header = varsig::header::Preset, - C: Codec + Into + TryFrom = varsig::encoding::Preset, + C: Codec + Into + TryFrom = varsig::encoding::Preset, > { pub varsig_header: V, pub signature: DID::Signature, @@ -36,7 +36,7 @@ pub struct Receipt< _marker: std::marker::PhantomData, } -impl, C: Codec + TryFrom + Into> +impl, C: Codec + TryFrom + Into> did::Verifiable for Receipt { fn verifier(&self) -> &DID { @@ -48,7 +48,7 @@ impl< T: Responds + Clone, DID: Did + Clone, V: varsig::Header + Clone, - C: Codec + TryFrom + Into, + C: Codec + TryFrom + Into, > From> for Ipld where Payload: TryFrom, @@ -62,7 +62,7 @@ impl< T: Responds + Clone, DID: Did + Clone, V: varsig::Header + Clone, - C: Codec + TryFrom + Into, + C: Codec + TryFrom + Into, > Envelope for Receipt where Payload: TryFrom, @@ -106,7 +106,7 @@ impl< T: Responds + Clone, DID: Did + Clone, V: varsig::Header + Clone, - C: Codec + TryFrom + Into, + C: Codec + TryFrom + Into, > Serialize for Receipt where Payload: TryFrom, @@ -124,7 +124,7 @@ impl< T: Responds + Clone, DID: Did + Clone, V: varsig::Header + Clone, - C: Codec + TryFrom + Into, + C: Codec + TryFrom + Into, > Deserialize<'de> for Receipt where Payload: TryFrom, diff --git a/src/receipt/store/memory.rs b/src/receipt/store/memory.rs index 11e84731..6be098fa 100644 --- a/src/receipt/store/memory.rs +++ b/src/receipt/store/memory.rs @@ -14,14 +14,14 @@ pub struct MemoryStore< T: Responds, DID: Did, V: varsig::Header, - Enc: Codec + Into + TryFrom, + Enc: Codec + Into + TryFrom, > where T::Success: fmt::Debug + Clone + PartialEq, { store: BTreeMap>, } -impl, Enc: Codec + Into + TryFrom> +impl, Enc: Codec + Into + TryFrom> Store for MemoryStore where ::Success: TryFrom + Into + Clone + fmt::Debug + PartialEq, diff --git a/src/receipt/store/traits.rs b/src/receipt/store/traits.rs index 40a141d4..8d53a1f0 100644 --- a/src/receipt/store/traits.rs +++ b/src/receipt/store/traits.rs @@ -7,7 +7,7 @@ use crate::{ use libipld_core::{codec::Codec, ipld::Ipld}; /// A store for [`Receipt`]s indexed by their [`task::Id`]s. -pub trait Store, C: Codec + Into + TryFrom> { +pub trait Store, C: Codec + Into + TryFrom> { /// The error type representing all the ways a store operation can fail. type Error; From 842bf28e1642c01d0007ba6218e8eca9e2a6ed89 Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Thu, 7 Mar 2024 13:26:59 -0800 Subject: [PATCH 125/188] Builders --- Cargo.toml | 1 + src/delegation/payload.rs | 28 ++++++++-------------------- src/delegation/store/memory.rs | 8 ++++++-- src/invocation/payload.rs | 25 ++++++++++++++++++++++--- src/receipt.rs | 2 +- src/receipt/payload.rs | 8 +++++++- src/time/timestamp.rs | 5 +++++ 7 files changed, 50 insertions(+), 27 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 397845d4..3aa30508 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -48,6 +48,7 @@ ecdsa = { version = "0.16.8", features = ["alloc"], optional = true, default-fea ed25519-dalek = { version = "2.0.0", features = ["rand_core"], optional = true } # Code Convenience +derive_builder = "0.20" enum-as-inner = "0.6" getrandom = { version = "0.2", features = ["js", "rdrand"] } k256 = { version = "0.13.1", features = ["ecdsa"], optional = true, default-features = false } diff --git a/src/delegation/payload.rs b/src/delegation/payload.rs index 7a544e26..50d9107b 100644 --- a/src/delegation/payload.rs +++ b/src/delegation/payload.rs @@ -1,11 +1,12 @@ use super::policy::Predicate; use crate::{ capsule::Capsule, - crypto::Nonce, + crypto::{varsig, Nonce}, did::{Did, Verifiable}, time::{TimeBoundError, Timestamp}, }; -use libipld_core::{error::SerdeError, ipld::Ipld, serde as ipld_serde}; +use derive_builder::Builder; +use libipld_core::{codec::Codec, error::SerdeError, ipld::Ipld, serde as ipld_serde}; use serde::{Deserialize, Serialize}; use std::{collections::BTreeMap, fmt::Debug}; use web_time::SystemTime; @@ -20,7 +21,7 @@ use crate::ipld; /// /// This contains the semantic information about the delegation, including the /// issuer, subject, audience, the delegated ability, time bounds, and so on. -#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Builder)] pub struct Payload { /// The subject of the [`Delegation`]. /// @@ -49,15 +50,18 @@ pub struct Payload { pub command: String, /// Any [`Predicate`] policies that constrain the `args` on an [`Invocation`][crate::invocation::Invocation]. + #[builder(default)] pub policy: Vec, /// Extensible, free-form fields. + #[builder(default)] pub metadata: BTreeMap, /// A [cryptographic nonce] to ensure that the UCAN's [`Cid`] is unique. /// /// [cryptograpgic nonce]: https://en.wikipedia.org/wiki/Cryptographic_nonce /// [`Cid`]: libipld_core::cid::Cid ; + #[builder(default = "Nonce::generate_16(&mut vec![])")] pub nonce: Nonce, /// The latest wall-clock time that the UCAN is valid until, @@ -70,27 +74,11 @@ pub struct Payload { /// given as a [Unix timestamp]. /// /// [Unix timestamp]: https://en.wikipedia.org/wiki/Unix_time + #[builder(default)] pub not_before: Option, } impl Payload { - pub fn powerbox(issuer: DID, audience: DID, command: String, expiration: Timestamp) -> Self { - let mut seed = vec![]; - let nonce = Nonce::generate_12(seed.as_mut()); - - Payload { - issuer, - subject: None, - audience, - command, - policy: vec![], - metadata: BTreeMap::new(), - nonce, - expiration, - not_before: None, - } - } - pub fn check_time(&self, now: SystemTime) -> Result<(), TimeBoundError> { let ts_now = &Timestamp::postel(now); diff --git a/src/delegation/store/memory.rs b/src/delegation/store/memory.rs index b603fb5c..9d96a60d 100644 --- a/src/delegation/store/memory.rs +++ b/src/delegation/store/memory.rs @@ -2,7 +2,7 @@ use super::Store; use crate::{ crypto::varsig, delegation::{policy::Predicate, Delegation}, - did::Did, + did::{self, Did}, }; use libipld_core::{cid::Cid, codec::Codec}; use nonempty::NonEmpty; @@ -69,7 +69,11 @@ use web_time::SystemTime; /// linkStyle 1 stroke:orange; /// ``` #[derive(Debug, Clone, PartialEq)] -pub struct MemoryStore, C: Codec + TryFrom + Into> { +pub struct MemoryStore< + DID: Did + Ord, // = did::preset::Verifier, + V: varsig::Header, + C: Codec + TryFrom + Into, +> { ucans: BTreeMap>, index: BTreeMap, BTreeMap>>, revocations: BTreeSet, diff --git a/src/invocation/payload.rs b/src/invocation/payload.rs index f64a64c9..f93ed9af 100644 --- a/src/invocation/payload.rs +++ b/src/invocation/payload.rs @@ -2,7 +2,7 @@ use super::promise::Resolvable; use crate::{ ability::{arguments, command::ToCommand, parse::ParseAbility}, capsule::Capsule, - crypto::Nonce, + crypto::{varsig, Nonce}, delegation::{ self, policy::{selector::SelectorError, Predicate}, @@ -10,7 +10,8 @@ use crate::{ did::{Did, Verifiable}, time::{Expired, Timestamp}, }; -use libipld_core::{cid::Cid, ipld::Ipld}; +use derive_builder::Builder; +use libipld_core::{cid::Cid, codec::Codec, ipld::Ipld}; use serde::{ de::{self, MapAccess, Visitor}, ser::SerializeStruct, @@ -29,7 +30,7 @@ use crate::ipld; #[cfg(feature = "test_utils")] use crate::ipld::cid; -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Clone, PartialEq, Builder)] pub struct Payload { /// The subject of the [`Invocation`]. /// @@ -58,6 +59,7 @@ pub struct Payload { /// /// Note that if this is the same as the [`subject`], /// this field may be omitted. + #[builder(default)] pub audience: Option, /// The [Ability] being invoked. @@ -75,6 +77,7 @@ pub struct Payload { /// /// [`Invocation`]: super::Invocation /// [`Delegation`]: crate::delegation::Delegation + #[builder(default)] pub proofs: Vec, /// An optional [`Cid`] of the [`Receipt`] that requested this be invoked. @@ -82,14 +85,17 @@ pub struct Payload { /// This is helpful for provenance of calls. /// /// [`Receipt`]: crate::receipt::Receipt + #[builder(default)] pub cause: Option, /// Extensible, free-form fields. + #[builder(default)] pub metadata: BTreeMap, /// A [cryptographic nonce] to ensure that the UCAN's [`Cid`] is unique. /// /// [cryptographic nonce]: https://en.wikipedia.org/wiki/Cryptographic_nonce + #[builder(default = "Nonce::generate_16(&mut vec![])")] pub nonce: Nonce, /// An optional [Unix timestamp] (wall-clock time) at which this [`Invocation`] @@ -101,6 +107,7 @@ pub struct Payload { /// /// One way of thinking about this is as a `timeout`. It also guards against /// certain types of denial-of-service attacks. + #[builder(default = "Some(Timestamp::five_minutes_from_now())")] pub expiration: Option, } @@ -446,6 +453,18 @@ impl Verifiable for Payload { } } +// impl, DID: Did> TryFrom for Payload { +// type Error = (); +// +// fn try_from(ipld: Ipld) -> Result { +// if let Ipld::Map(btree) = ipld { +// let payload = btree.get(A::COMMAND).map_err(|_| ())?; +// } else { +// Err(()) +// } +// } +// } + /// A variant that accepts [`Promise`]s. /// /// [`Promise`]: crate::invocation::promise::Promise diff --git a/src/receipt.rs b/src/receipt.rs index 1c1feb79..c96a7cf9 100644 --- a/src/receipt.rs +++ b/src/receipt.rs @@ -10,7 +10,7 @@ mod responds; pub mod store; -pub use payload::Payload; +pub use payload::*; pub use responds::Responds; pub use store::Store; diff --git a/src/receipt/payload.rs b/src/receipt/payload.rs index ace1e4d5..4bc40a96 100644 --- a/src/receipt/payload.rs +++ b/src/receipt/payload.rs @@ -10,6 +10,7 @@ use crate::{ did::{Did, Verifiable}, time::Timestamp, }; +use derive_builder::Builder; use libipld_core::{cid::Cid, error::SerdeError, ipld::Ipld, serde as ipld_serde}; use serde::{ de::{self, MapAccess, Visitor}, @@ -36,7 +37,7 @@ impl Verifiable for Payload { /// The payload (non-signature) portion of a response from an [`Invocation`]. /// /// [`Invocation`]: crate::invocation::Invocation -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Clone, PartialEq, Builder)] pub struct Payload { /// The issuer of the [`Receipt`]. This [`Did`] *must* match the signature on /// the outer layer of [`Receipt`]. @@ -59,6 +60,7 @@ pub struct Payload { /// requested to be queued next. /// /// [`Invocation`]: crate::invocation::Invocation + #[builder(default)] pub next: Vec, /// An optional proof chain authorizing a different [`Did`] to @@ -66,21 +68,25 @@ pub struct Payload { /// [`Invocation`] that was run. /// /// [`Invocation`]: crate::invocation::Invocation + #[builder(default)] pub proofs: Vec, /// Extensible, free-form fields. + #[builder(default)] pub metadata: BTreeMap, /// A [cryptographic nonce] to ensure that the UCAN's [`Cid`] is unique. /// /// [cryptographic nonce]: https://en.wikipedia.org/wiki/Cryptographic_nonce /// [`Cid`]: libipld_core::cid::Cid + #[builder(default = "Nonce::generate_16(&mut vec![])")] pub nonce: Nonce, /// An optional [Unix timestamp] (wall-clock time) at which the /// receipt claims to have been issued at. /// /// [Unix timestamp]: https://en.wikipedia.org/wiki/Unix_time + #[builder(default)] pub issued_at: Option, } diff --git a/src/time/timestamp.rs b/src/time/timestamp.rs index 8b4cfea7..a378cf83 100644 --- a/src/time/timestamp.rs +++ b/src/time/timestamp.rs @@ -57,6 +57,11 @@ impl Timestamp { .expect("the current time to be somtime in the 3rd millenium CE") } + pub fn five_minutes_from_now() -> Timestamp { + Self::new(SystemTime::now() + Duration::from_secs(5 * 60)) + .expect("the current time to be somtime in the 3rd millenium CE") + } + pub fn five_years_from_now() -> Timestamp { Self::new(SystemTime::now() + Duration::from_secs(5 * 365 * 24 * 60 * 60)) .expect("the current time to be somtime in the 3rd millenium CE") From 2cb8ef87247a9f7138d70c4bcfd64553d8b7abc0 Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Thu, 7 Mar 2024 14:11:01 -0800 Subject: [PATCH 126/188] lol oops --- src/crypto/signature/envelope.rs | 2 +- src/invocation/payload.rs | 25 ++++++++++++++----------- 2 files changed, 15 insertions(+), 12 deletions(-) diff --git a/src/crypto/signature/envelope.rs b/src/crypto/signature/envelope.rs index 13d2b7a4..e8bff36a 100644 --- a/src/crypto/signature/envelope.rs +++ b/src/crypto/signature/envelope.rs @@ -100,7 +100,7 @@ pub trait Envelope: Sized { payload: Self::Payload, ) -> Result where - Ipld: Encode + From, + Ipld: Encode + From, // FIXME force it to be named args not IPLD { Self::try_sign_generic(signer, varsig_header, payload) } diff --git a/src/invocation/payload.rs b/src/invocation/payload.rs index f93ed9af..596462e1 100644 --- a/src/invocation/payload.rs +++ b/src/invocation/payload.rs @@ -453,17 +453,20 @@ impl Verifiable for Payload { } } -// impl, DID: Did> TryFrom for Payload { -// type Error = (); -// -// fn try_from(ipld: Ipld) -> Result { -// if let Ipld::Map(btree) = ipld { -// let payload = btree.get(A::COMMAND).map_err(|_| ())?; -// } else { -// Err(()) -// } -// } -// } +use crate::ability::command::Command; + +impl + Command, DID: Did> TryFrom for Payload { + type Error = (); // FIXME + + fn try_from(ipld: Ipld) -> Result { + if let Ipld::Map(btree) = ipld { + let payload_ipld = btree.get(A::COMMAND).ok_or(|_| ())?; + payload_ipld.clone().try_into().map_err(|_| ()) + } else { + Err(()) + } + } +} /// A variant that accepts [`Promise`]s. /// From 51c3b0276ad926528f10e7fb996fd4fb1f6cb4cc Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Thu, 7 Mar 2024 14:12:37 -0800 Subject: [PATCH 127/188] oops again! --- src/invocation/payload.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/invocation/payload.rs b/src/invocation/payload.rs index 596462e1..a558b00b 100644 --- a/src/invocation/payload.rs +++ b/src/invocation/payload.rs @@ -460,7 +460,7 @@ impl + Command, DID: Did> TryFrom for Payload { fn try_from(ipld: Ipld) -> Result { if let Ipld::Map(btree) = ipld { - let payload_ipld = btree.get(A::COMMAND).ok_or(|_| ())?; + let payload_ipld = btree.get(A::COMMAND).ok_or(())?; payload_ipld.clone().try_into().map_err(|_| ()) } else { Err(()) From ec1b2b6354b1b6549cb2631a1e9aac1ec8286d64 Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Thu, 7 Mar 2024 14:31:32 -0800 Subject: [PATCH 128/188] PartialOrd --- src/did/key/verifier.rs | 30 +++++++++++++++++++++++------- src/did/preset.rs | 2 +- 2 files changed, 24 insertions(+), 8 deletions(-) diff --git a/src/did/key/verifier.rs b/src/did/key/verifier.rs index 20b021eb..0a1be8d8 100644 --- a/src/did/key/verifier.rs +++ b/src/did/key/verifier.rs @@ -1,12 +1,12 @@ use super::Signature; +use blst::BLST_ERROR; +use did_url::DID; use enum_as_inner::EnumAsInner; use rsa::pkcs1::{DecodeRsaPublicKey, EncodeRsaPublicKey}; +use serde::{Deserialize, Serialize}; +use signature as sig; use std::{fmt::Display, str::FromStr}; -use serde::{Serialize, Deserialize}; use thiserror::Error; -use blst::BLST_ERROR; -use signature as sig; -use did_url::DID; #[cfg(feature = "test_utils")] use proptest::prelude::*; @@ -75,6 +75,18 @@ pub enum Verifier { BlsMinSig(blst::min_sig::PublicKey), } +impl PartialOrd for Verifier { + fn partial_cmp(&self, other: &Self) -> Option { + self.to_string().partial_cmp(&other.to_string()) + } +} + +impl Ord for Verifier { + fn cmp(&self, other: &Self) -> std::cmp::Ordering { + self.to_string().cmp(&other.to_string()) + } +} + impl signature::Verifier for Verifier { fn verify(&self, msg: &[u8], signature: &Signature) -> Result<(), signature::Error> { match (self, signature) { @@ -256,13 +268,17 @@ impl FromStr for Verifier { word => return Err(FromStrError::UnexpectedPrefix([word].into())), }, (word, _) => { - return Err(FromStrError::UnexpectedPrefix(word.iter().map(|u| u.clone().into()).collect())); + return Err(FromStrError::UnexpectedPrefix( + word.iter().map(|u| u.clone().into()).collect(), + )); } } - }, + } (s, _) => { - return Err(FromStrError::UnexpectedPrefix(s.to_string().chars().map(|u| u as usize).collect())); + return Err(FromStrError::UnexpectedPrefix( + s.to_string().chars().map(|u| u as usize).collect(), + )); } } } diff --git a/src/did/preset.rs b/src/did/preset.rs index aff0d261..cdc52528 100644 --- a/src/did/preset.rs +++ b/src/did/preset.rs @@ -6,7 +6,7 @@ use serde::{Deserialize, Serialize}; use std::{fmt::Display, str::FromStr}; /// The set of [`Did`] types that ship with this library ("presets"). -#[derive(Debug, Clone, EnumAsInner, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Debug, Clone, EnumAsInner, PartialEq, PartialOrd, Ord, Eq, Serialize, Deserialize)] #[serde(untagged)] pub enum Verifier { /// `did:key` DIDs. From 33a0ac3dcdf97d200143d457580ad8305eeba727 Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Thu, 7 Mar 2024 14:32:51 -0800 Subject: [PATCH 129/188] Defaults --- src/delegation/store/memory.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/delegation/store/memory.rs b/src/delegation/store/memory.rs index 9d96a60d..e54a8ee3 100644 --- a/src/delegation/store/memory.rs +++ b/src/delegation/store/memory.rs @@ -70,9 +70,9 @@ use web_time::SystemTime; /// ``` #[derive(Debug, Clone, PartialEq)] pub struct MemoryStore< - DID: Did + Ord, // = did::preset::Verifier, - V: varsig::Header, - C: Codec + TryFrom + Into, + DID: did::Did + Ord = did::preset::Verifier, + V: varsig::Header = varsig::header::Preset, + C: Codec + TryFrom + Into = varsig::encoding::Preset, > { ucans: BTreeMap>, index: BTreeMap, BTreeMap>>, From cfe55fde5fb59c82a540aa1ad00a84e6ca71ba90 Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Thu, 7 Mar 2024 14:43:38 -0800 Subject: [PATCH 130/188] this is such test code lol --- src/crypto/varsig/encoding/preset.rs | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/src/crypto/varsig/encoding/preset.rs b/src/crypto/varsig/encoding/preset.rs index a4189ba3..b12070d0 100644 --- a/src/crypto/varsig/encoding/preset.rs +++ b/src/crypto/varsig/encoding/preset.rs @@ -1,4 +1,6 @@ use libipld_core::codec::Codec; +use libipld_core::codec::Encode; +use libipld_core::ipld::Ipld; #[repr(u32)] #[derive(Clone, Copy, Debug, PartialEq)] @@ -11,6 +13,23 @@ pub enum Preset { Eip191 = 0xe191, } +impl Encode for Ipld { + fn encode( + &self, + c: Preset, + w: &mut W, + ) -> Result<(), libipld_core::error::Error> { + match c { + Preset::Identity => todo!(), + Preset::DagPb => todo!(), + Preset::DagCbor => self.encode(libipld_cbor::DagCborCodec, w), + Preset::DagJson => todo!(), + Preset::Jwt => todo!(), + Preset::Eip191 => todo!(), + } + } +} + impl TryFrom for Preset { type Error = libipld_core::error::UnsupportedCodec; From 8ecd176abdd90ba32aff859cce3615cdfc1a44e3 Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Thu, 7 Mar 2024 14:54:17 -0800 Subject: [PATCH 131/188] Give delegation an encode instance --- src/crypto/varsig/encoding/preset.rs | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/crypto/varsig/encoding/preset.rs b/src/crypto/varsig/encoding/preset.rs index b12070d0..e69a5a87 100644 --- a/src/crypto/varsig/encoding/preset.rs +++ b/src/crypto/varsig/encoding/preset.rs @@ -1,3 +1,5 @@ +use crate::crypto::signature::Envelope; +use crate::delegation::Delegation; use libipld_core::codec::Codec; use libipld_core::codec::Encode; use libipld_core::ipld::Ipld; @@ -30,6 +32,16 @@ impl Encode for Ipld { } } +impl Encode for Delegation { + fn encode( + &self, + c: Preset, + w: &mut W, + ) -> Result<(), libipld_core::error::Error> { + self.clone().to_ipld_envelope().encode(c, w) + } +} + impl TryFrom for Preset { type Error = libipld_core::error::UnsupportedCodec; From 326338cf818ff2d727323048fd0530b84b38d849 Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Thu, 7 Mar 2024 14:57:48 -0800 Subject: [PATCH 132/188] Helper functions on memstore --- src/delegation/store/memory.rs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/delegation/store/memory.rs b/src/delegation/store/memory.rs index e54a8ee3..d2eb1ce3 100644 --- a/src/delegation/store/memory.rs +++ b/src/delegation/store/memory.rs @@ -79,6 +79,16 @@ pub struct MemoryStore< revocations: BTreeSet, } +impl MemoryStore { + pub fn new() -> Self { + Self::default() + } + + fn is_empty(&self) -> bool { + self.ucans.is_empty() // FIXME acocunt for revocations? + } +} + impl, C: Codec + TryFrom + Into> Default for MemoryStore { From d5cfbac4deedf1b4101a85392cce5a07ec7f1384 Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Thu, 7 Mar 2024 14:58:20 -0800 Subject: [PATCH 133/188] make method public --- src/delegation/store/memory.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/delegation/store/memory.rs b/src/delegation/store/memory.rs index d2eb1ce3..7a3abb65 100644 --- a/src/delegation/store/memory.rs +++ b/src/delegation/store/memory.rs @@ -84,7 +84,7 @@ impl MemoryStore { Self::default() } - fn is_empty(&self) -> bool { + pub fn is_empty(&self) -> bool { self.ucans.is_empty() // FIXME acocunt for revocations? } } From 82130915623bb5ed448c9ef74465b477e58d2734 Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Thu, 7 Mar 2024 15:05:35 -0800 Subject: [PATCH 134/188] printable error --- src/delegation/store/memory.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/delegation/store/memory.rs b/src/delegation/store/memory.rs index 7a3abb65..9ef9b8f9 100644 --- a/src/delegation/store/memory.rs +++ b/src/delegation/store/memory.rs @@ -105,10 +105,10 @@ impl, C: Codec + TryFrom + Into> impl, Enc: Codec + TryFrom + Into> Store for MemoryStore { - type DelegationStoreError = (); // FIXME misisng + type DelegationStoreError = String; // FIXME misisng fn get(&self, cid: &Cid) -> Result<&Delegation, Self::DelegationStoreError> { - self.ucans.get(cid).ok_or(()) + self.ucans.get(cid).ok_or("nope".into()) } fn insert( From 6e746ad2be1314cd182f9cb8efc487346cd2be25 Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Thu, 7 Mar 2024 15:18:20 -0800 Subject: [PATCH 135/188] Hopefully this eliminates the stack overflow? --- src/crypto/signature/envelope.rs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/crypto/signature/envelope.rs b/src/crypto/signature/envelope.rs index e8bff36a..e7971871 100644 --- a/src/crypto/signature/envelope.rs +++ b/src/crypto/signature/envelope.rs @@ -134,9 +134,8 @@ pub trait Envelope: Sized { ipld.encode(*varsig::header::Header::codec(&varsig_header), &mut buffer) .map_err(SignError::PayloadEncodingError)?; - let signature = signer - .try_sign(&buffer) - .map_err(SignError::SignatureError)?; + let signature = + signature::Signer::try_sign(signer, &buffer).map_err(SignError::SignatureError)?; Ok(Self::construct(varsig_header, signature, payload)) } From 39939f468a77d675d53feb2d872374478a45733e Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Thu, 7 Mar 2024 15:20:21 -0800 Subject: [PATCH 136/188] remote debugging... gross --- src/crypto/signature/envelope.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/crypto/signature/envelope.rs b/src/crypto/signature/envelope.rs index e7971871..9d78eda1 100644 --- a/src/crypto/signature/envelope.rs +++ b/src/crypto/signature/envelope.rs @@ -134,8 +134,8 @@ pub trait Envelope: Sized { ipld.encode(*varsig::header::Header::codec(&varsig_header), &mut buffer) .map_err(SignError::PayloadEncodingError)?; - let signature = - signature::Signer::try_sign(signer, &buffer).map_err(SignError::SignatureError)?; + let signature = todo!(); + // signature::Signer::try_sign(signer, &buffer).map_err(SignError::SignatureError)?; Ok(Self::construct(varsig_header, signature, payload)) } From d0d9055bc4b4159538cd04811912348ee369e368 Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Thu, 7 Mar 2024 15:23:52 -0800 Subject: [PATCH 137/188] good ol' printline debugging --- src/crypto/signature/envelope.rs | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/crypto/signature/envelope.rs b/src/crypto/signature/envelope.rs index 9d78eda1..3eb20595 100644 --- a/src/crypto/signature/envelope.rs +++ b/src/crypto/signature/envelope.rs @@ -102,6 +102,7 @@ pub trait Envelope: Sized { where Ipld: Encode + From, // FIXME force it to be named args not IPLD { + dbg!("try_sign"); Self::try_sign_generic(signer, varsig_header, payload) } @@ -127,16 +128,21 @@ pub trait Envelope: Sized { where Ipld: Encode + From, { + dbg!("try_sign_generic"); let ipld: Ipld = BTreeMap::from_iter([(Self::Payload::TAG.into(), payload.clone().into())]).into(); + dbg!("buffer"); let mut buffer = vec![]; + dbg!("encode"); ipld.encode(*varsig::header::Header::codec(&varsig_header), &mut buffer) .map_err(SignError::PayloadEncodingError)?; - let signature = todo!(); - // signature::Signer::try_sign(signer, &buffer).map_err(SignError::SignatureError)?; + dbg!("sign"); + let signature = + signature::Signer::try_sign(signer, &buffer).map_err(SignError::SignatureError)?; + dbg!("construct"); Ok(Self::construct(varsig_header, signature, payload)) } From f50445dc77f8f5ab7defbdad759aee9d5147373e Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Thu, 7 Mar 2024 15:52:01 -0800 Subject: [PATCH 138/188] Serialize to IPLD --- src/crypto/signature/envelope.rs | 2 +- src/delegation/agent.rs | 2 +- src/delegation/payload.rs | 30 +++++++++++++- src/delegation/policy/predicate.rs | 53 ++++++++++++++++++++++++ src/delegation/policy/selector/select.rs | 13 ++++++ src/did/traits.rs | 4 +- src/ipld/collection.rs | 14 +++++++ 7 files changed, 113 insertions(+), 5 deletions(-) diff --git a/src/crypto/signature/envelope.rs b/src/crypto/signature/envelope.rs index 3eb20595..cb19990c 100644 --- a/src/crypto/signature/envelope.rs +++ b/src/crypto/signature/envelope.rs @@ -130,7 +130,7 @@ pub trait Envelope: Sized { { dbg!("try_sign_generic"); let ipld: Ipld = - BTreeMap::from_iter([(Self::Payload::TAG.into(), payload.clone().into())]).into(); + BTreeMap::from_iter([(Self::Payload::TAG.into(), Ipld::from(payload.clone()))]).into(); dbg!("buffer"); let mut buffer = vec![]; diff --git a/src/delegation/agent.rs b/src/delegation/agent.rs index abe27a34..139e8e41 100644 --- a/src/delegation/agent.rs +++ b/src/delegation/agent.rs @@ -36,7 +36,7 @@ pub struct Agent< impl< 'a, - DID: Did + ToString + Clone, + DID: Did + Clone, S: Store + Clone, V: varsig::Header + Clone, Enc: Codec + TryFrom + Into, diff --git a/src/delegation/payload.rs b/src/delegation/payload.rs index 50d9107b..f830ec23 100644 --- a/src/delegation/payload.rs +++ b/src/delegation/payload.rs @@ -114,9 +114,35 @@ impl Deserialize<'de>> TryFrom for Payload { } } -impl From> for Ipld { +impl From> for Ipld { fn from(payload: Payload) -> Self { - payload.into() + let mut btree: BTreeMap = BTreeMap::::from_iter([ + ("iss".to_string(), Ipld::from(payload.issuer.to_string())), + ("aud".to_string(), payload.audience.to_string().into()), + ("cmd".to_string(), payload.command.into()), + ( + "pol".to_string(), + Ipld::List(payload.policy.into_iter().map(|p| p.into()).collect()), + ), + ("nonce".to_string(), payload.nonce.into()), + ("exp".to_string(), payload.expiration.into()), + ]); + + if let Some(subject) = payload.subject { + btree.insert("sub".to_string(), Ipld::from(subject.to_string())); + } else { + btree.insert("sub".to_string(), Ipld::Null); + } + + if let Some(not_before) = payload.not_before { + btree.insert("nbf".to_string(), Ipld::from(not_before)); + } + + if !payload.metadata.is_empty() { + btree.insert("metadata".to_string(), Ipld::Map(payload.metadata)); + } + + Ipld::from(btree) } } diff --git a/src/delegation/policy/predicate.rs b/src/delegation/policy/predicate.rs index 6f9533e6..01617003 100644 --- a/src/delegation/policy/predicate.rs +++ b/src/delegation/policy/predicate.rs @@ -95,6 +95,59 @@ pub fn glob(input: &String, pattern: &String) -> bool { } } +impl From for Ipld { + fn from(p: Predicate) -> Self { + match p { + Predicate::True => Ipld::Bool(true), + Predicate::False => Ipld::Bool(false), + Predicate::Equal(lhs, rhs) => { + Ipld::List(vec![Ipld::String("==".to_string()), lhs.into(), rhs.into()]) + } + Predicate::GreaterThan(lhs, rhs) => { + Ipld::List(vec![Ipld::String(">".to_string()), lhs.into(), rhs.into()]) + } + Predicate::GreaterThanOrEqual(lhs, rhs) => { + Ipld::List(vec![Ipld::String(">=".to_string()), lhs.into(), rhs.into()]) + } + Predicate::LessThan(lhs, rhs) => { + Ipld::List(vec![Ipld::String("<".to_string()), lhs.into(), rhs.into()]) + } + Predicate::LessThanOrEqual(lhs, rhs) => { + Ipld::List(vec![Ipld::String("<=".to_string()), lhs.into(), rhs.into()]) + } + Predicate::Like(lhs, rhs) => Ipld::List(vec![ + Ipld::String("like".to_string()), + lhs.into(), + rhs.into(), + ]), + Predicate::Not(inner) => { + let unboxed = *inner; + Ipld::List(vec![Ipld::String("not".to_string()), unboxed.into()]) + } + Predicate::And(lhs, rhs) => Ipld::List(vec![ + Ipld::String("and".to_string()), + (*lhs).into(), + (*rhs).into(), + ]), + Predicate::Or(lhs, rhs) => Ipld::List(vec![ + Ipld::String("or".to_string()), + (*lhs).into(), + (*rhs).into(), + ]), + Predicate::Every(xs, p) => Ipld::List(vec![ + Ipld::String("every".to_string()), + xs.into(), + (*p).into(), + ]), + Predicate::Some(xs, p) => Ipld::List(vec![ + Ipld::String("some".to_string()), + xs.into(), + (*p).into(), + ]), + } + } +} + #[cfg(feature = "test_utils")] impl Arbitrary for Predicate { type Parameters = (); // FIXME? diff --git a/src/delegation/policy/selector/select.rs b/src/delegation/policy/selector/select.rs index e6064911..736bff0a 100644 --- a/src/delegation/policy/selector/select.rs +++ b/src/delegation/policy/selector/select.rs @@ -1,3 +1,4 @@ +use super::Selector; // FIXME cycle? use super::{error::SelectorErrorReason, filter::Filter, Selectable, SelectorError}; use libipld_core::ipld::Ipld; use serde::{Deserialize, Serialize}; @@ -11,6 +12,18 @@ pub enum Select { Pure(T), } +impl From> for Ipld +where + Ipld: From, +{ + fn from(s: Select) -> Self { + match s { + Select::Get(ops) => Selector(ops).to_string().into(), + Select::Pure(inner) => inner.into(), + } + } +} + impl Select { pub fn resolve(self, ctx: &Ipld) -> Result { match self { diff --git a/src/did/traits.rs b/src/did/traits.rs index 448186be..03ed7659 100644 --- a/src/did/traits.rs +++ b/src/did/traits.rs @@ -1,7 +1,9 @@ use did_url::DID; use std::fmt; -pub trait Did: PartialEq + TryFrom + Into + signature::Verifier { +pub trait Did: + PartialEq + ToString + TryFrom + Into + signature::Verifier +{ type Signature: signature::SignatureEncoding + PartialEq + fmt::Debug; type Signer: signature::Signer + fmt::Debug; } diff --git a/src/ipld/collection.rs b/src/ipld/collection.rs index 1478c805..fb7bc4b0 100644 --- a/src/ipld/collection.rs +++ b/src/ipld/collection.rs @@ -1,4 +1,5 @@ use crate::ipld; +use libipld_core::ipld::Ipld; use serde::{Deserialize, Serialize}; use std::collections::BTreeMap; @@ -11,6 +12,19 @@ pub enum Collection { Map(BTreeMap), } +impl From for Ipld { + fn from(collection: Collection) -> Self { + match collection { + Collection::Array(xs) => Ipld::List(xs.into_iter().map(Into::into).collect()), + Collection::Map(xs) => Ipld::Map( + xs.into_iter() + .map(|(k, v)| (k, v.into())) + .collect::>(), + ), + } + } +} + impl Collection { pub fn to_vec(self) -> Vec { match self { From 6a089868c3938cb50aa01fd8fda3b84488ffc12b Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Thu, 7 Mar 2024 15:54:49 -0800 Subject: [PATCH 139/188] Better defaults for inv payload builder --- src/invocation/payload.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/invocation/payload.rs b/src/invocation/payload.rs index a558b00b..90465671 100644 --- a/src/invocation/payload.rs +++ b/src/invocation/payload.rs @@ -100,6 +100,7 @@ pub struct Payload { /// An optional [Unix timestamp] (wall-clock time) at which this [`Invocation`] /// was created. + #[builder(default)] pub issued_at: Option, /// An optional [Unix timestamp] (wall-clock time) at which this [`Invocation`] From ef778149795d331d73d575ac41707f8061f01824 Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Thu, 7 Mar 2024 16:09:27 -0800 Subject: [PATCH 140/188] same for invocation --- src/invocation.rs | 13 +++++++++---- src/invocation/agent.rs | 13 +++++++++---- src/invocation/payload.rs | 11 +++++++---- 3 files changed, 25 insertions(+), 12 deletions(-) diff --git a/src/invocation.rs b/src/invocation.rs index 840b537a..09eaad20 100644 --- a/src/invocation.rs +++ b/src/invocation.rs @@ -21,6 +21,7 @@ pub mod store; pub use agent::Agent; pub use payload::*; +use crate::ability::command::ToCommand; use crate::{ crypto::{signature::Envelope, varsig}, did::{self, Did}, @@ -126,12 +127,13 @@ impl, C: Codec + TryFrom + Into> did } impl< - A: Clone, + A: Clone + ToCommand, DID: Did + Clone, V: varsig::Header + Clone, C: Codec + TryFrom + Into, > From> for Ipld where + Ipld: From, Payload: TryFrom, { fn from(invocation: Invocation) -> Self { @@ -140,12 +142,13 @@ where } impl< - A: Clone, + A: Clone + ToCommand, DID: Did + Clone, V: varsig::Header + Clone, C: Codec + TryFrom + Into, > Envelope for Invocation where + Ipld: From, Payload: TryFrom, { type DID = DID; @@ -184,12 +187,13 @@ where } impl< - A: Clone, + A: Clone + ToCommand, DID: Did + Clone, V: varsig::Header + Clone, C: Codec + TryFrom + Into, > Serialize for Invocation where + Ipld: From, Payload: TryFrom, { fn serialize(&self, serializer: S) -> Result @@ -202,12 +206,13 @@ where impl< 'de, - A: Clone, + A: Clone + ToCommand, DID: Did + Clone, V: varsig::Header + Clone, C: Codec + TryFrom + Into, > Deserialize<'de> for Invocation where + Ipld: From, Payload: TryFrom, as TryFrom>::Error: std::fmt::Display, { diff --git a/src/invocation/agent.rs b/src/invocation/agent.rs index 64a4e750..058f3d8e 100644 --- a/src/invocation/agent.rs +++ b/src/invocation/agent.rs @@ -4,6 +4,7 @@ use super::{ store::Store, Invocation, }; +use crate::ability::command::ToCommand; use crate::{ ability::{arguments, parse::ParseAbilityError, ucan::revoke::Revoke}, crypto::{ @@ -31,7 +32,7 @@ use web_time::SystemTime; #[derive(Debug)] pub struct Agent< 'a, - T: Resolvable, + T: Resolvable + ToCommand, DID: Did, S: Store, P: promise::Store, @@ -57,10 +58,10 @@ pub struct Agent< impl<'a, T, DID, S, P, D, V, C> Agent<'a, T, DID, S, P, D, V, C> where - T::Promised: Clone, - Ipld: Encode, + Ipld: Encode + From, delegation::Payload: Clone, - T: Resolvable + Clone, + T: Resolvable + ToCommand + Clone, + T::Promised: Clone + ToCommand, DID: Did + Clone, S: Store, P: promise::Store, @@ -104,6 +105,7 @@ where >, > where + Ipld: From, Payload: TryFrom, { let proofs = self @@ -151,6 +153,7 @@ where >, > where + Ipld: From, Payload: TryFrom, { let proofs = self @@ -185,6 +188,7 @@ where now: &SystemTime, ) -> Result>, ReceiveError> where + Ipld: From + From, Payload: TryFrom, arguments::Named: From, Invocation: Clone + Encode, @@ -249,6 +253,7 @@ where // FIXME return type ) -> Result, ()> where + Ipld: From, T: From, Payload: TryFrom, { diff --git a/src/invocation/payload.rs b/src/invocation/payload.rs index 90465671..2b42391f 100644 --- a/src/invocation/payload.rs +++ b/src/invocation/payload.rs @@ -236,8 +236,8 @@ impl Capsule for Payload { impl, DID: Did> From> for arguments::Named { fn from(payload: Payload) -> Self { let mut args = arguments::Named::from_iter([ - ("iss".into(), payload.issuer.into().to_string().into()), - ("sub".into(), payload.subject.into().to_string().into()), + ("iss".into(), payload.issuer.to_string().into()), + ("sub".into(), payload.subject.to_string().into()), ("cmd".into(), payload.ability.to_command().into()), ("args".into(), payload.ability.into()), ( @@ -474,9 +474,12 @@ impl + Command, DID: Did> TryFrom for Payload { /// [`Promise`]: crate::invocation::promise::Promise pub type Promised = Payload<::Promised, DID>; -impl From> for Ipld { +impl From> for Ipld +where + Ipld: From, +{ fn from(payload: Payload) -> Self { - payload.into() + arguments::Named::from(payload).into() } } From 2f3810898d287b0338303c3bf715d2042ecc4f6f Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Thu, 7 Mar 2024 16:19:47 -0800 Subject: [PATCH 141/188] Add some dbg --- src/delegation/store/memory.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/delegation/store/memory.rs b/src/delegation/store/memory.rs index 9ef9b8f9..6f6a3e5e 100644 --- a/src/delegation/store/memory.rs +++ b/src/delegation/store/memory.rs @@ -186,6 +186,8 @@ impl, Enc: Codec + TryFrom + } }); + dbg!(found.clone()); + if found.is_continue() { status = Status::NoPath; } From 6e9131c2492c4105c27749f4937f5973b075a55b Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Thu, 7 Mar 2024 22:36:17 -0800 Subject: [PATCH 142/188] debugging the store --- Cargo.toml | 3 + src/crypto/signature/envelope.rs | 6 - src/delegation/store/memory.rs | 341 +++++++++++++++++++++++++++---- 3 files changed, 299 insertions(+), 51 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 3aa30508..a1e3775a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -86,6 +86,9 @@ web-sys = { version = "0.3", features = ["Crypto", "CryptoKey", "CryptoKeyPair", [dev-dependencies] libipld = "0.16" +rand = "0.8" +testresult = "0.3" +test-log = "0.2" [target.'cfg(not(target_arch = "wasm32"))'.dev-dependencies] criterion = "0.4" diff --git a/src/crypto/signature/envelope.rs b/src/crypto/signature/envelope.rs index cb19990c..d080bfc0 100644 --- a/src/crypto/signature/envelope.rs +++ b/src/crypto/signature/envelope.rs @@ -102,7 +102,6 @@ pub trait Envelope: Sized { where Ipld: Encode + From, // FIXME force it to be named args not IPLD { - dbg!("try_sign"); Self::try_sign_generic(signer, varsig_header, payload) } @@ -128,21 +127,16 @@ pub trait Envelope: Sized { where Ipld: Encode + From, { - dbg!("try_sign_generic"); let ipld: Ipld = BTreeMap::from_iter([(Self::Payload::TAG.into(), Ipld::from(payload.clone()))]).into(); - dbg!("buffer"); let mut buffer = vec![]; - dbg!("encode"); ipld.encode(*varsig::header::Header::codec(&varsig_header), &mut buffer) .map_err(SignError::PayloadEncodingError)?; - dbg!("sign"); let signature = signature::Signer::try_sign(signer, &buffer).map_err(SignError::SignatureError)?; - dbg!("construct"); Ok(Self::construct(varsig_header, signature, payload)) } diff --git a/src/delegation/store/memory.rs b/src/delegation/store/memory.rs index 6f6a3e5e..f6801070 100644 --- a/src/delegation/store/memory.rs +++ b/src/delegation/store/memory.rs @@ -124,6 +124,22 @@ impl, Enc: Codec + TryFrom + .insert(cid); self.ucans.insert(cid.clone(), delegation); + + // dbg!(&self.ucans.keys().map(|k| k.to_string()).collect::>()); + // dbg!(&self + // .index + // .keys() + // .map(|k| k.clone().map(|y| y.to_string())) + // .collect::>()); + dbg!(self.ucans.len()); + dbg!(self.index.len()); + for (sub, inner) in self.index.clone() { + dbg!(sub.clone().map(|x| x.to_string())); + for (aud, cids) in inner { + dbg!(aud.to_string()); + dbg!(cids.len()); + } + } Ok(()) } @@ -139,65 +155,300 @@ impl, Enc: Codec + TryFrom + policy: Vec, // FIXME now: SystemTime, ) -> Result)>>, Self::DelegationStoreError> { - match self - .index - .get(subject) // FIXME probably need to rework this after last minbute chanegs - .and_then(|aud_map| aud_map.get(aud)) - { - None => Ok(None), - Some(delegation_subtree) => { - #[derive(PartialEq)] - enum Status { - Complete, - Looking, - NoPath, - } + dbg!("started get_chain"); + dbg!(subject.clone().map(|x| x.to_string()).clone()); + dbg!(aud.to_string().clone()); - let mut status = Status::Looking; - let mut target_aud = aud; - let mut chain = vec![]; + let blank_set = BTreeSet::new(); + let blank_map = BTreeMap::new(); - while status == Status::Looking { - let found = delegation_subtree.iter().try_for_each(|cid| { - if let Some(d) = self.ucans.get(cid) { - if self.revocations.contains(cid) { - return ControlFlow::Continue(()); - } + let all_powerlines = self.index.get(&None).unwrap_or(&blank_map); + let all_aud_for_subject = self.index.get(subject).unwrap_or(&blank_map); + let powerline_candidates = all_powerlines.get(aud).unwrap_or(&blank_set); + let sub_candidates = all_aud_for_subject.get(aud).unwrap_or(&blank_set); - if d.check_time(now).is_err() { - return ControlFlow::Continue(()); - } + let mut parent_candidate_stack = vec![]; + let mut hypothesis_chain = vec![]; - target_aud = &d.audience(); + dbg!(">>>>>>>>>>>>>>>."); + parent_candidate_stack.push(sub_candidates.iter().chain(powerline_candidates.iter())); - chain.push((*cid, d)); + let mut done = false; + while !done && !parent_candidate_stack.is_empty() { + dbg!("inner loop"); + + let mut next = None; + if let Some(parent_cid_candidates) = parent_candidate_stack.last_mut() { + dbg!("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"); + dbg!(parent_cid_candidates.clone().fold(0, |acc, _| acc + 1)); + + for cid in parent_cid_candidates { + dbg!("##########################"); + dbg!(hypothesis_chain.len()); + if self.revocations.contains(cid) { + dbg!("REVOKE"); + continue; + } + dbg!("@@@@@@@@@@@@@@@@@@@@@@@@2"); - if let Some(ref subject) = subject { - if d.issuer() == subject { - status = Status::Complete; - } - } else { - status = Status::Complete; - } + if let Some(delegation) = self.ucans.get(cid) { + if delegation.check_time(now).is_err() { + dbg!("TIME"); + continue; + } - ControlFlow::Break(()) - } else { - ControlFlow::Continue(()) + let issuer = delegation.issuer().clone(); + dbg!(issuer.to_string().clone()); + if &Some(issuer.clone()) == delegation.subject() + && (&subject == &delegation.subject() || subject == &None) + { + dbg!("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"); + hypothesis_chain.push((cid.clone(), delegation)); + done = true; + break; } - }); - dbg!(found.clone()); + let new_aud_candidates = + all_aud_for_subject.get(&issuer).unwrap_or(&blank_set); + let new_powerline_candidates = + all_powerlines.get(&issuer).unwrap_or(&blank_set); + + dbg!("%%%%%%%%%%%%%%%%%%%%%%%%"); + hypothesis_chain.push((cid.clone(), delegation)); - if found.is_continue() { - status = Status::NoPath; + if !new_aud_candidates.is_empty() && !new_powerline_candidates.is_empty() { + next = Some( + new_aud_candidates + .iter() + .chain(new_powerline_candidates.iter()), + ); + // parent_candidate_stack.push( + // new_aud_candidates + // .iter() + // .chain(new_powerline_candidates.iter()), + // ); + break; + } } } - match status { - Status::Complete => Ok(NonEmpty::from_vec(chain)), - _ => Ok(None), - } + // Didn't find a match, so drop this candidate + parent_candidate_stack.pop(); + hypothesis_chain.pop(); + } else { + dbg!("done"); + done = true; // FIXME pass up error? + } + + if let Some(n) = next { + parent_candidate_stack.push(n); } } + + dbg!(&hypothesis_chain + .iter() + .map(|(cid, _)| cid.to_string()) + .collect::>()); + + Ok(NonEmpty::from_vec(hypothesis_chain)) + } +} + +#[cfg(test)] +mod tests { + use crate::ability::command::Command; + use crate::crypto::signature::Envelope; + use crate::delegation::store::Store; + use libipld_core::ipld::Ipld; + use rand::thread_rng; + use testresult::TestResult; + + #[test_log::test] + fn test_powerbox_ucan_resource() -> TestResult { + let server_sk = ed25519_dalek::SigningKey::generate(&mut thread_rng()); + let server_signer = + crate::did::preset::Signer::Key(crate::did::key::Signer::EdDsa(server_sk.clone())); + + let server = crate::did::preset::Verifier::Key(crate::did::key::Verifier::EdDsa( + server_sk.verifying_key(), + )); + + let account_sk = ed25519_dalek::SigningKey::generate(&mut thread_rng()); + let account = crate::did::preset::Verifier::Key(crate::did::key::Verifier::EdDsa( + account_sk.verifying_key(), + )); + let account_signer = + crate::did::preset::Signer::Key(crate::did::key::Signer::EdDsa(account_sk)); + + let dnslink_sk = ed25519_dalek::SigningKey::generate(&mut thread_rng()); + let dnslink = crate::did::preset::Verifier::Key(crate::did::key::Verifier::EdDsa( + dnslink_sk.verifying_key(), + )); + let dnslink_signer = + crate::did::preset::Signer::Key(crate::did::key::Signer::EdDsa(dnslink_sk)); + + let device_sk = ed25519_dalek::SigningKey::generate(&mut thread_rng()); + let device = crate::did::preset::Verifier::Key(crate::did::key::Verifier::EdDsa( + device_sk.verifying_key(), + )); + let device_signer = + crate::did::preset::Signer::Key(crate::did::key::Signer::EdDsa(device_sk)); + + // FIXME perhaps add this back upstream as a named const + let varsig_header = crate::crypto::varsig::header::Preset::EdDsa( + crate::crypto::varsig::header::EdDsaHeader { + codec: crate::crypto::varsig::encoding::Preset::DagCbor, + }, + ); + + // 1. account -*-> server + // 2. server -a-> device + // 3. dnslink -d-> account + // 4. [dnslink -d-> account -*-> server -a-> device] + + // Both of these UCANs just create ephemeral DIDs & delegate all of those + // DID's rights to the server + // let (account_did, _account_ucan) = server_create_resource(&server_ed_did_key)?; + // let (dnslink_did, _dnslink_ucan) = server_create_resource(&server_ed_did_key)?; + + // 1. account -*-> server + let account_pbox = crate::Delegation::try_sign( + &account_signer, + varsig_header.clone(), + crate::delegation::PayloadBuilder::default() + .subject(None) + .issuer(account.clone()) + .audience(server.clone()) + .command("/".into()) + .expiration(crate::time::Timestamp::five_years_from_now()) + .build() + .expect("FIXME"), + ) + .expect("signature to work"); + + // 2. server -a-> device + let account_device_ucan = crate::Delegation::try_sign( + &server_signer, + varsig_header.clone(), // FIXME can also put this on a builder + crate::delegation::PayloadBuilder::default() + .subject(None) // FIXME needs a sibject when we figure out powerbox + .issuer(server.clone()) + .audience(device.clone()) + .command("/".into()) + .expiration(crate::time::Timestamp::five_years_from_now()) + .build() + .expect("FIXME"), // I don't love this is now failable + ) + .expect("signature to work"); + + // 3. dnslink -d-> account + let dnslink_ucan = crate::Delegation::try_sign( + &dnslink_signer, + varsig_header.clone(), + crate::delegation::PayloadBuilder::default() + .subject(Some(dnslink.clone())) + .issuer(dnslink.clone()) + .audience(account.clone()) + .command("/".into()) + .expiration(crate::time::Timestamp::five_years_from_now()) + .build() + .expect("FIXME"), + ) + .expect("signature to work"); + + #[derive(Debug, Clone, PartialEq)] + pub struct AccountInfo {} + + impl Command for AccountInfo { + const COMMAND: &'static str = "/account/info"; + } + + impl From for AccountInfo { + fn from(_: Ipld) -> Self { + AccountInfo {} + } + } + + impl From for Ipld { + fn from(_: AccountInfo) -> Self { + Ipld::Null + } + } + + // #[derive(Debug, Clone, PartialEq)] + // pub struct DnsLinkUpdate { + // pub cid: Cid, + // } + + // impl From for DnsLinkUpdate { + // fn from(_: Ipld) -> Self { + // todo!() + // } + // } + + // 4. [dnslink -d-> account -*-> server -a-> device] + let account_invocation = crate::Invocation::try_sign( + &device_signer, + varsig_header, + crate::invocation::PayloadBuilder::default() + .subject(account.clone()) + .issuer(device.clone()) + .audience(Some(server.clone())) + .ability(AccountInfo {}) + .proofs(vec![]) // FIXME + .build() + .expect("FIXME"), + ) + .expect("FIXME"); + + // FIXME reenable + // let dnslink_invocation = crate::Invocation::try_sign( + // &device, + // varsig_header, + // crate::invocation::PayloadBuilder::default() + // .subject(dnslink) + // .issuer(device) + // .audience(Some(server)) + // .ability(DnsLinkUpdate { cid: todo!() }) + // .build() + // .expect("FIXME"), + // ) + // .expect("FIXME"); + + use crate::crypto::varsig; + + let mut store: crate::delegation::store::MemoryStore< + crate::did::preset::Verifier, + varsig::header::Preset, + varsig::encoding::Preset, + > = Default::default(); + + let agent = crate::delegation::Agent::new(&server, &server_signer, &mut store); + + let _ = store.insert( + account_device_ucan.cid().expect("FIXME"), + account_device_ucan.clone(), + ); + + let _ = store.insert(account_pbox.cid().expect("FIXME"), account_pbox.clone()); + + let _ = store.insert(dnslink_ucan.cid().expect("FIXME"), dnslink_ucan.clone()); + + use std::time::SystemTime; + + dbg!(server.to_string().clone()); + dbg!(device.to_string().clone()); + dbg!(account.to_string().clone()); + // let chainer = store.get_chain(&device, &None, vec![], SystemTime::now()); + let chainer = store.get_chain(&device, &Some(dnslink), vec![], SystemTime::now()); + + // tracing::debug!(?account_pbox, "Capabilities"); + + // dbg!(store.clone()); + dbg!(chainer.clone()); + + assert!(chainer?.is_some()); + + Ok(()) } } From 5c3cea5508819f80c6000f02afa011f8e2241526 Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Fri, 8 Mar 2024 11:33:49 -0800 Subject: [PATCH 143/188] Dios mio memstore test works now --- src/crypto/signature/envelope.rs | 1 - src/delegation/store/memory.rs | 147 +++++++++++++++++-------------- 2 files changed, 83 insertions(+), 65 deletions(-) diff --git a/src/crypto/signature/envelope.rs b/src/crypto/signature/envelope.rs index d080bfc0..07b18300 100644 --- a/src/crypto/signature/envelope.rs +++ b/src/crypto/signature/envelope.rs @@ -174,7 +174,6 @@ pub trait Envelope: Sized { fn cid(&self) -> Result where - // Ipld: Encode + From, Self: Encode, { let codec = varsig::header::Header::codec(self.varsig_header()).clone(); diff --git a/src/delegation/store/memory.rs b/src/delegation/store/memory.rs index f6801070..1a390dab 100644 --- a/src/delegation/store/memory.rs +++ b/src/delegation/store/memory.rs @@ -1,4 +1,5 @@ use super::Store; +use crate::crypto::signature::Envelope; use crate::{ crypto::varsig, delegation::{policy::Predicate, Delegation}, @@ -101,9 +102,20 @@ impl, C: Codec + TryFrom + Into> } } +use crate::delegation; +use libipld_core::codec::Encode; +use libipld_core::ipld::Ipld; + // FIXME check that UCAN is valid -impl, Enc: Codec + TryFrom + Into> - Store for MemoryStore +impl< + DID: Did + Ord + Clone, + V: varsig::Header + Clone, + Enc: Codec + TryFrom + Into, + > Store for MemoryStore +where + Ipld: From>, + delegation::Payload: TryFrom, + Delegation: Encode, { type DelegationStoreError = String; // FIXME misisng @@ -116,6 +128,8 @@ impl, Enc: Codec + TryFrom + cid: Cid, delegation: Delegation, ) -> Result<(), Self::DelegationStoreError> { + dbg!("^^^^^^^^^^^^^^^^^^^^^ inert"); + dbg!(&cid.to_string()); self.index .entry(delegation.subject().clone()) .or_default() @@ -125,12 +139,6 @@ impl, Enc: Codec + TryFrom + self.ucans.insert(cid.clone(), delegation); - // dbg!(&self.ucans.keys().map(|k| k.to_string()).collect::>()); - // dbg!(&self - // .index - // .keys() - // .map(|k| k.clone().map(|y| y.to_string())) - // .collect::>()); dbg!(self.ucans.len()); dbg!(self.index.len()); for (sub, inner) in self.index.clone() { @@ -152,10 +160,11 @@ impl, Enc: Codec + TryFrom + &self, aud: &DID, subject: &Option, + // command: String, // FIXME policy: Vec, // FIXME now: SystemTime, ) -> Result)>>, Self::DelegationStoreError> { - dbg!("started get_chain"); + dbg!(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>> get chain"); dbg!(subject.clone().map(|x| x.to_string()).clone()); dbg!(aud.to_string().clone()); @@ -170,86 +179,102 @@ impl, Enc: Codec + TryFrom + let mut parent_candidate_stack = vec![]; let mut hypothesis_chain = vec![]; - dbg!(">>>>>>>>>>>>>>>."); parent_candidate_stack.push(sub_candidates.iter().chain(powerline_candidates.iter())); + let mut next = None; - let mut done = false; - while !done && !parent_candidate_stack.is_empty() { - dbg!("inner loop"); - - let mut next = None; + 'outer: loop { + dbg!("starting loop"); if let Some(parent_cid_candidates) = parent_candidate_stack.last_mut() { dbg!("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"); - dbg!(parent_cid_candidates.clone().fold(0, |acc, _| acc + 1)); - for cid in parent_cid_candidates { - dbg!("##########################"); - dbg!(hypothesis_chain.len()); + if parent_cid_candidates.clone().collect::>().is_empty() { + dbg!("EMPTY"); + parent_candidate_stack.pop(); + hypothesis_chain.pop(); + break 'outer; + } + + 'inner: for cid in parent_cid_candidates { + next = None; + + dbg!(cid.to_string()); if self.revocations.contains(cid) { - dbg!("REVOKE"); continue; } - dbg!("@@@@@@@@@@@@@@@@@@@@@@@@2"); if let Some(delegation) = self.ucans.get(cid) { + dbg!(delegation.cid().expect("FIXME").to_string()); if delegation.check_time(now).is_err() { - dbg!("TIME"); continue; } + hypothesis_chain.push((cid.clone(), delegation)); + dbg!(hypothesis_chain.len()); + + // if hypothesis_chain.last().map(|x| x.1.issuer()) + // == Some(delegation.issuer()) + // { + // break 'outer; + // } + let issuer = delegation.issuer().clone(); - dbg!(issuer.to_string().clone()); + + // Hit a root delegation, AKA base case if &Some(issuer.clone()) == delegation.subject() - && (&subject == &delegation.subject() || subject == &None) + // && (&subject == &delegation.subject() || subject == &None) { - dbg!("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"); - hypothesis_chain.push((cid.clone(), delegation)); - done = true; - break; + break 'outer; } + dbg!(issuer.to_string().clone()); let new_aud_candidates = all_aud_for_subject.get(&issuer).unwrap_or(&blank_set); + let new_powerline_candidates = all_powerlines.get(&issuer).unwrap_or(&blank_set); - dbg!("%%%%%%%%%%%%%%%%%%%%%%%%"); - hypothesis_chain.push((cid.clone(), delegation)); - - if !new_aud_candidates.is_empty() && !new_powerline_candidates.is_empty() { + // keep looking until exhausted? + // if subject == &None { + // dbg!("SUBJECT NONE"); + // next = Some( + // new_aud_candidates + // .iter() + // .chain(new_powerline_candidates.iter()), + // ); + // break 'inner; + // } + + if !new_aud_candidates.is_empty() || !new_powerline_candidates.is_empty() { + dbg!("MORE CANIDATES"); next = Some( new_aud_candidates .iter() .chain(new_powerline_candidates.iter()), ); - // parent_candidate_stack.push( - // new_aud_candidates - // .iter() - // .chain(new_powerline_candidates.iter()), - // ); - break; + break 'inner; + } else { + // break 'outer; } } } - // Didn't find a match, so drop this candidate + if let Some(ref n) = next { + dbg!("NNNNNNNNNNNNNNNNNNNNNNNNNNNNNNn"); + parent_candidate_stack.push(n.clone()); + } else { + dbg!("NO MORE CANDIDATES"); + // Didn't find a match, so drop this candidate + // parent_candidate_stack.pop(); + // hypothesis_chain.pop(); + break 'outer; + } + } else { + dbg!("ELSE"); parent_candidate_stack.pop(); hypothesis_chain.pop(); - } else { - dbg!("done"); - done = true; // FIXME pass up error? - } - - if let Some(n) = next { - parent_candidate_stack.push(n); } } - dbg!(&hypothesis_chain - .iter() - .map(|(cid, _)| cid.to_string()) - .collect::>()); - Ok(NonEmpty::from_vec(hypothesis_chain)) } } @@ -306,11 +331,6 @@ mod tests { // 3. dnslink -d-> account // 4. [dnslink -d-> account -*-> server -a-> device] - // Both of these UCANs just create ephemeral DIDs & delegate all of those - // DID's rights to the server - // let (account_did, _account_ucan) = server_create_resource(&server_ed_did_key)?; - // let (dnslink_did, _dnslink_ucan) = server_create_resource(&server_ed_did_key)?; - // 1. account -*-> server let account_pbox = crate::Delegation::try_sign( &account_signer, @@ -436,18 +456,17 @@ mod tests { use std::time::SystemTime; - dbg!(server.to_string().clone()); dbg!(device.to_string().clone()); + dbg!(server.to_string().clone()); dbg!(account.to_string().clone()); - // let chainer = store.get_chain(&device, &None, vec![], SystemTime::now()); - let chainer = store.get_chain(&device, &Some(dnslink), vec![], SystemTime::now()); - - // tracing::debug!(?account_pbox, "Capabilities"); + dbg!(dnslink.to_string().clone()); + let chain_for_powerline = store.get_chain(&device, &None, vec![], SystemTime::now()); + let chain_for_dnslink = store.get_chain(&device, &Some(dnslink), vec![], SystemTime::now()); - // dbg!(store.clone()); - dbg!(chainer.clone()); + let powerline_len = chain_for_powerline.expect("FIXME").unwrap().len(); + let dnslink_len = chain_for_dnslink.expect("FIXME").unwrap().len(); - assert!(chainer?.is_some()); + assert_eq!((powerline_len, dnslink_len), (3, 3)); // FIXME Ok(()) } From 9afd69d70300e2c14209c222def56dbe150a4c78 Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Sat, 9 Mar 2024 14:41:27 -0800 Subject: [PATCH 144/188] WIP --- src/delegation/agent.rs | 2 +- src/delegation/policy/predicate.rs | 255 +++++++++++++++++++++-- src/delegation/policy/selector.rs | 31 ++- src/delegation/policy/selector/filter.rs | 14 ++ src/delegation/policy/selector/select.rs | 55 ++++- src/delegation/store/memory.rs | 96 ++++----- src/delegation/store/traits.rs | 4 +- src/invocation/agent.rs | 15 +- src/ipld/number.rs | 8 +- 9 files changed, 397 insertions(+), 83 deletions(-) diff --git a/src/delegation/agent.rs b/src/delegation/agent.rs index 139e8e41..57e0c30e 100644 --- a/src/delegation/agent.rs +++ b/src/delegation/agent.rs @@ -93,7 +93,7 @@ where let to_delegate = &self .store - .get_chain(&self.did, &subject, vec![], now) + .get_chain(&self.did, &subject, "/".into(), vec![], now) .map_err(DelegateError::StoreError)? .ok_or(DelegateError::ProofsNotFound)? .first() diff --git a/src/delegation/policy/predicate.rs b/src/delegation/policy/predicate.rs index 01617003..352300da 100644 --- a/src/delegation/policy/predicate.rs +++ b/src/delegation/policy/predicate.rs @@ -1,7 +1,10 @@ +use super::selector::filter::Filter; use super::selector::{Select, SelectorError}; use crate::ipld; +use enum_as_inner::EnumAsInner; use libipld_core::ipld::Ipld; use serde::{Deserialize, Serialize}; +use std::cmp::Ordering; #[cfg(feature = "test_utils")] use proptest::prelude::*; @@ -16,15 +19,15 @@ pub enum Predicate { False, // Comparison - Equal(Select, Select), + Equal(Select, ipld::Newtype), - GreaterThan(Select, Select), - GreaterThanOrEqual(Select, Select), + GreaterThan(Select, ipld::Number), + GreaterThanOrEqual(Select, ipld::Number), - LessThan(Select, Select), - LessThanOrEqual(Select, Select), + LessThan(Select, ipld::Number), + LessThanOrEqual(Select, ipld::Number), - Like(Select, Select), + Like(Select, String), // Connectives Not(Box), @@ -36,17 +39,25 @@ pub enum Predicate { Some(Select, Box), // ∃x ∈ xs } +#[derive(Debug, Clone, PartialEq, EnumAsInner)] +pub enum Harmonization { + IncompatiblePredicate, // Failed check + IncomparablePath, // LHS is ok + LhsNarrowerOrEqual, // LHS succeeded + RhsNarrower, // Succeeded, but RHS is narrower +} + impl Predicate { pub fn run(self, data: &Ipld) -> Result { Ok(match self { Predicate::True => true, Predicate::False => false, - Predicate::Equal(lhs, rhs) => lhs.resolve(data)? == rhs.resolve(data)?, - Predicate::GreaterThan(lhs, rhs) => lhs.resolve(data)? > rhs.resolve(data)?, - Predicate::GreaterThanOrEqual(lhs, rhs) => lhs.resolve(data)? >= rhs.resolve(data)?, - Predicate::LessThan(lhs, rhs) => lhs.resolve(data)? < rhs.resolve(data)?, - Predicate::LessThanOrEqual(lhs, rhs) => lhs.resolve(data)? <= rhs.resolve(data)?, - Predicate::Like(lhs, rhs) => glob(&lhs.resolve(data)?, &rhs.resolve(data)?), + Predicate::Equal(lhs, rhs_data) => lhs.resolve(data)? == rhs_data, + Predicate::GreaterThan(lhs, rhs_data) => lhs.resolve(data)? > rhs_data, + Predicate::GreaterThanOrEqual(lhs, rhs_data) => lhs.resolve(data)? >= rhs_data, + Predicate::LessThan(lhs, rhs_data) => lhs.resolve(data)? < rhs_data, + Predicate::LessThanOrEqual(lhs, rhs_data) => lhs.resolve(data)? <= rhs_data, + Predicate::Like(lhs, rhs_data) => glob(&lhs.resolve(data)?, &rhs_data), Predicate::Not(inner) => !inner.run(data)?, Predicate::And(lhs, rhs) => lhs.run(data)? && rhs.run(data)?, Predicate::Or(lhs, rhs) => lhs.run(data)? || rhs.run(data)?, @@ -65,6 +76,226 @@ impl Predicate { } }) } + + // FIXME check paths are subsets, becase that changes some of these + pub fn harmonize( + &self, + other: &Self, + lhs_ctx: Vec, + rhs_ctx: Vec, + ) -> Harmonization { + match self { + Predicate::True => match other { + Predicate::True => Harmonization::LhsNarrowerOrEqual, + _ => todo!(), + }, + // FIXME this should generally fail always? But how does it compare? + Predicate::False => match other { + // FIXME correct? + Predicate::False => Harmonization::LhsNarrowerOrEqual, + _ => todo!(), + }, + Predicate::Equal(lhs_selector, lhs_ipld) => match other { + Predicate::Equal(rhs_selector, rhs_ipld) => { + // FIXME include ctx in path? + if lhs_selector.is_related(rhs_selector) { + if lhs_ipld == rhs_ipld { + Harmonization::LhsNarrowerOrEqual + } else { + Harmonization::IncompatiblePredicate + } + } else { + Harmonization::IncomparablePath + } + } + + /************ + * Numerics * + ************/ + Predicate::GreaterThan(rhs_selector, rhs_num) => { + if lhs_selector.is_related(rhs_selector) { + if let Ok(lhs_num) = ipld::Number::try_from(lhs_ipld.0.clone()) { + if lhs_num > *rhs_num { + Harmonization::LhsNarrowerOrEqual + } else { + Harmonization::IncompatiblePredicate + } + } else { + Harmonization::IncompatiblePredicate + } + } else { + Harmonization::IncomparablePath + } + } + Predicate::GreaterThanOrEqual(rhs_selector, rhs_num) => { + if lhs_selector.is_related(rhs_selector) { + if let Ok(lhs_num) = ipld::Number::try_from(lhs_ipld.0.clone()) { + if lhs_num >= *rhs_num { + Harmonization::LhsNarrowerOrEqual + } else { + Harmonization::IncompatiblePredicate + } + } else { + Harmonization::IncompatiblePredicate + } + } else { + Harmonization::IncomparablePath + } + } + Predicate::LessThan(rhs_selector, rhs_num) => { + if lhs_selector.is_related(rhs_selector) { + if let Ok(lhs_num) = ipld::Number::try_from(lhs_ipld.0.clone()) { + if lhs_num < *rhs_num { + Harmonization::LhsNarrowerOrEqual + } else { + Harmonization::IncompatiblePredicate + } + } else { + Harmonization::IncompatiblePredicate + } + } else { + Harmonization::IncomparablePath + } + } + Predicate::LessThanOrEqual(rhs_selector, rhs_num) => { + if lhs_selector.is_related(rhs_selector) { + if let Ok(lhs_num) = ipld::Number::try_from(lhs_ipld.0.clone()) { + if lhs_num <= *rhs_num { + Harmonization::LhsNarrowerOrEqual + } else { + Harmonization::IncompatiblePredicate + } + } else { + Harmonization::IncompatiblePredicate + } + } else { + Harmonization::IncomparablePath + } + } + /********** + * String * + **********/ + Predicate::Like(rhs_selector, rhs_str) => { + if lhs_selector.is_related(rhs_selector) { + if let Ok(lhs_str) = String::try_from(*lhs_ipld) { + if glob(&lhs_str, rhs_str) { + Harmonization::LhsNarrowerOrEqual + } else { + Harmonization::IncompatiblePredicate + } + } else { + Harmonization::IncompatiblePredicate + } + } else { + Harmonization::IncomparablePath + } + } + + /*************** + * Connectives * + ***************/ + Predicate::Not(rhs_inner) => { + let rhs_raw_pred: Predicate = **rhs_inner; + match self.harmonize(&rhs_raw_pred, lhs_ctx, rhs_ctx) { + Harmonization::LhsNarrowerOrEqual => Harmonization::RhsNarrower, + Harmonization::RhsNarrower => Harmonization::LhsNarrowerOrEqual, + Harmonization::IncomparablePath => Harmonization::IncomparablePath, + // FIXME double check + Harmonization::IncompatiblePredicate => Harmonization::LhsNarrowerOrEqual, + } + } + Predicate::And(and_left, and_right) => { + let rhs_raw_pred1: Predicate = **and_left; + let rhs_raw_pred2: Predicate = **and_right; + + match ( + self.harmonize(&rhs_raw_pred1, lhs_ctx.clone(), rhs_ctx.clone()), + self.harmonize(&rhs_raw_pred2, lhs_ctx, rhs_ctx), + ) { + (Harmonization::LhsNarrowerOrEqual, Harmonization::LhsNarrowerOrEqual) => { + Harmonization::LhsNarrowerOrEqual + } + (Harmonization::RhsNarrower, Harmonization::RhsNarrower) => { + Harmonization::RhsNarrower + } + (Harmonization::LhsNarrowerOrEqual, Harmonization::RhsNarrower) => { + Harmonization::IncompatiblePredicate + } + (Harmonization::RhsNarrower, Harmonization::LhsNarrowerOrEqual) => { + Harmonization::IncompatiblePredicate + } + (Harmonization::IncomparablePath, right) => right, + (left, Harmonization::IncomparablePath) => left, + (Harmonization::IncompatiblePredicate, _) => { + Harmonization::IncompatiblePredicate + } + (_, Harmonization::IncompatiblePredicate) => { + Harmonization::IncompatiblePredicate + } + } + } + Predicate::Or(or_left, or_right) => { + let rhs_raw_pred1: Predicate = *or_left.clone(); + let rhs_raw_pred2: Predicate = *or_right.clone(); + + match ( + self.harmonize(&rhs_raw_pred1, lhs_ctx.clone(), rhs_ctx.clone()), + self.harmonize(&rhs_raw_pred2, lhs_ctx, rhs_ctx), + ) { + (Harmonization::LhsNarrowerOrEqual, Harmonization::LhsNarrowerOrEqual) => { + Harmonization::LhsNarrowerOrEqual + } + (Harmonization::RhsNarrower, Harmonization::RhsNarrower) => { + Harmonization::RhsNarrower + } + (Harmonization::LhsNarrowerOrEqual, Harmonization::RhsNarrower) => { + Harmonization::LhsNarrowerOrEqual + } + (Harmonization::RhsNarrower, Harmonization::LhsNarrowerOrEqual) => { + Harmonization::LhsNarrowerOrEqual + } + (Harmonization::IncomparablePath, right) => right, + (left, Harmonization::IncomparablePath) => left, + (Harmonization::IncompatiblePredicate, _) => { + Harmonization::IncompatiblePredicate + } + (_, Harmonization::IncompatiblePredicate) => { + Harmonization::IncompatiblePredicate + } + } + } + /****************** + * Quantification * + ******************/ + Predicate::Every(rhs_selector, rhs_inner) => { + let rhs_raw_pred: Predicate = *rhs_inner.clone(); + todo!() + // match self.harmonize(&rhs_raw_pred, lhs_ctx, rhs_ctx) { + // Harmonization::LhsNarrowerOrEqual => Harmonization::LhsNarrowerOrEqual, + // Harmonization::RhsNarrower => Harmonization::RhsNarrower, + // Harmonization::IncomparablePath => Harmonization::IncomparablePath, + // Harmonization::IncompatiblePredicate => { + // Harmonization::IncompatiblePredicate + // } + // } + } + Predicate::Some(rhs_selector, rhs_inner) => { + let rhs_raw_pred: Predicate = *rhs_inner.clone(); + todo!() + // match self.harmonize(&rhs_raw_pred, lhs_ctx, rhs_ctx) { + // Harmonization::LhsNarrowerOrEqual => Harmonization::LhsNarrowerOrEqual, + // Harmonization::RhsNarrower => Harmonization::RhsNarrower, + // Harmonization::IncomparablePath => Harmonization::IncomparablePath, + // Harmonization::IncompatiblePredicate => { + // Harmonization::IncompatiblePredicate + // } + // } + } + _ => todo!(), // FIXME + }, + _ => todo!(), + } + } } pub fn glob(input: &String, pattern: &String) -> bool { diff --git a/src/delegation/policy/selector.rs b/src/delegation/policy/selector.rs index 4ee35a1b..965b89a1 100644 --- a/src/delegation/policy/selector.rs +++ b/src/delegation/policy/selector.rs @@ -21,12 +21,23 @@ use nom::{ IResult, }; use serde::{Deserialize, Deserializer, Serialize, Serializer}; +use std::cmp::Ordering; use std::{fmt, str::FromStr}; use thiserror::Error; -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Clone, PartialEq, Default)] pub struct Selector(pub Vec); +impl Selector { + pub fn new() -> Self { + Selector(vec![]) + } + + pub fn is_related(&self, other: &Selector) -> bool { + self.0.iter().zip(other.0.iter()).all(|(a, b)| a == b) + } +} + pub fn parse(input: &str) -> IResult<&str, Selector> { let without_this = many1(filter::parse); let with_this = preceded(char('.'), many0(filter::parse)); @@ -106,3 +117,21 @@ impl SelectorError { } } } + +impl PartialOrd for Selector { + fn partial_cmp(&self, other: &Self) -> Option { + if self == other { + return Some(Ordering::Equal); + } + + if self.0.starts_with(&other.0) { + return Some(Ordering::Greater); + } + + if other.0.starts_with(&self.0) { + return Some(Ordering::Less); + } + + None + } +} diff --git a/src/delegation/policy/selector/filter.rs b/src/delegation/policy/selector/filter.rs index b7273bdc..a1e054a3 100644 --- a/src/delegation/policy/selector/filter.rs +++ b/src/delegation/policy/selector/filter.rs @@ -25,6 +25,20 @@ pub enum Filter { Try(Box), // ? } +impl Filter { + pub fn is_in(&self, other: &Self) -> bool { + match (self, other) { + (Filter::ArrayIndex(a), Filter::ArrayIndex(b)) => a == b, + (Filter::Field(a), Filter::Field(b)) => a == b, + (Filter::Values, Filter::Values) => true, + (Filter::ArrayIndex(_a), Filter::Values) => true, + (Filter::Field(_k), Filter::Values) => true, + (Filter::Try(a), Filter::Try(b)) => a.is_in(b), // FIXME Try is basically == null? + _ => false, + } + } +} + impl fmt::Display for Filter { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { diff --git a/src/delegation/policy/selector/select.rs b/src/delegation/policy/selector/select.rs index 736bff0a..e9c32ae2 100644 --- a/src/delegation/policy/selector/select.rs +++ b/src/delegation/policy/selector/select.rs @@ -2,6 +2,7 @@ use super::Selector; // FIXME cycle? use super::{error::SelectorErrorReason, filter::Filter, Selectable, SelectorError}; use libipld_core::ipld::Ipld; use serde::{Deserialize, Serialize}; +use std::cmp::Ordering; #[cfg(feature = "test_utils")] use proptest::prelude::*; @@ -12,19 +13,21 @@ pub enum Select { Pure(T), } -impl From> for Ipld -where - Ipld: From, -{ - fn from(s: Select) -> Self { - match s { - Select::Get(ops) => Selector(ops).to_string().into(), - Select::Pure(inner) => inner.into(), +impl Select { + pub fn is_related(&self, other: &Select) -> bool + where + Ipld: From + From, + { + match (self, other) { + (Select::Pure(lhs_val), Select::Pure(rhs_val)) => { + Ipld::from(lhs_val.clone()) == Ipld::from(rhs_val.clone()) + } + (Select::Get(lhs_path), Select::Get(rhs_path)) => { + Selector(lhs_path.clone()).is_related(&Selector(rhs_path.clone())) + } + _ => false, } } -} - -impl Select { pub fn resolve(self, ctx: &Ipld) -> Result { match self { Select::Pure(inner) => Ok(inner), @@ -109,6 +112,36 @@ impl Select { } } +impl From> for Ipld +where + Ipld: From, +{ + fn from(s: Select) -> Self { + match s { + Select::Get(ops) => Selector(ops).to_string().into(), + Select::Pure(inner) => inner.into(), + } + } +} + +impl PartialOrd for Select { + fn partial_cmp(&self, other: &Self) -> Option { + match (self, other) { + (Select::Pure(inner), Select::Pure(other_inner)) => { + if inner == other_inner { + Some(Ordering::Equal) + } else { + None + } + } + (Select::Get(ops), Select::Get(other_ops)) => { + Selector(ops.clone()).partial_cmp(&Selector(other_ops.clone())) + } + _ => None, + } + } +} + #[cfg(feature = "test_utils")] impl Arbitrary for Select { type Parameters = T::Parameters; diff --git a/src/delegation/store/memory.rs b/src/delegation/store/memory.rs index 1a390dab..6d547e88 100644 --- a/src/delegation/store/memory.rs +++ b/src/delegation/store/memory.rs @@ -120,7 +120,7 @@ where type DelegationStoreError = String; // FIXME misisng fn get(&self, cid: &Cid) -> Result<&Delegation, Self::DelegationStoreError> { - self.ucans.get(cid).ok_or("nope".into()) + self.ucans.get(cid).ok_or("nope".into()) // FIXME } fn insert( @@ -128,7 +128,6 @@ where cid: Cid, delegation: Delegation, ) -> Result<(), Self::DelegationStoreError> { - dbg!("^^^^^^^^^^^^^^^^^^^^^ inert"); dbg!(&cid.to_string()); self.index .entry(delegation.subject().clone()) @@ -160,14 +159,10 @@ where &self, aud: &DID, subject: &Option, - // command: String, // FIXME + command: String, policy: Vec, // FIXME now: SystemTime, ) -> Result)>>, Self::DelegationStoreError> { - dbg!(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>> get chain"); - dbg!(subject.clone().map(|x| x.to_string()).clone()); - dbg!(aud.to_string().clone()); - let blank_set = BTreeSet::new(); let blank_map = BTreeMap::new(); @@ -179,97 +174,93 @@ where let mut parent_candidate_stack = vec![]; let mut hypothesis_chain = vec![]; + let corrected_target_command = if command.ends_with('/') { + command + } else { + format!("{}/", command) + }; + parent_candidate_stack.push(sub_candidates.iter().chain(powerline_candidates.iter())); let mut next = None; 'outer: loop { - dbg!("starting loop"); if let Some(parent_cid_candidates) = parent_candidate_stack.last_mut() { - dbg!("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"); - if parent_cid_candidates.clone().collect::>().is_empty() { - dbg!("EMPTY"); parent_candidate_stack.pop(); hypothesis_chain.pop(); break 'outer; } 'inner: for cid in parent_cid_candidates { - next = None; - - dbg!(cid.to_string()); if self.revocations.contains(cid) { continue; } if let Some(delegation) = self.ucans.get(cid) { - dbg!(delegation.cid().expect("FIXME").to_string()); if delegation.check_time(now).is_err() { continue; } - hypothesis_chain.push((cid.clone(), delegation)); - dbg!(hypothesis_chain.len()); + // FIXME extract + let corrected_delegation_command = + if delegation.payload.command.ends_with('/') { + delegation.payload.command.clone() + } else { + format!("{}/", delegation.payload.command) + }; + + if !corrected_delegation_command.starts_with(&corrected_target_command) { + continue; + } - // if hypothesis_chain.last().map(|x| x.1.issuer()) - // == Some(delegation.issuer()) - // { - // break 'outer; - // } + for target_pred in policy.iter() { + for delegate_pred in delegation.payload.policy.iter() { + let comparison = + target_pred.harmonize(delegate_pred, vec![], vec![]); + + if comparison.is_incompatible_predicate() + || comparison.is_rhs_narrower() + { + continue 'inner; + } + } + } + + hypothesis_chain.push((cid.clone(), delegation)); let issuer = delegation.issuer().clone(); // Hit a root delegation, AKA base case - if &Some(issuer.clone()) == delegation.subject() - // && (&subject == &delegation.subject() || subject == &None) - { + if &Some(issuer.clone()) == delegation.subject() { break 'outer; } - dbg!(issuer.to_string().clone()); let new_aud_candidates = all_aud_for_subject.get(&issuer).unwrap_or(&blank_set); let new_powerline_candidates = all_powerlines.get(&issuer).unwrap_or(&blank_set); - // keep looking until exhausted? - // if subject == &None { - // dbg!("SUBJECT NONE"); - // next = Some( - // new_aud_candidates - // .iter() - // .chain(new_powerline_candidates.iter()), - // ); - // break 'inner; - // } - if !new_aud_candidates.is_empty() || !new_powerline_candidates.is_empty() { - dbg!("MORE CANIDATES"); next = Some( new_aud_candidates .iter() .chain(new_powerline_candidates.iter()), ); + break 'inner; - } else { - // break 'outer; } } } if let Some(ref n) = next { - dbg!("NNNNNNNNNNNNNNNNNNNNNNNNNNNNNNn"); parent_candidate_stack.push(n.clone()); + next = None; } else { - dbg!("NO MORE CANDIDATES"); - // Didn't find a match, so drop this candidate - // parent_candidate_stack.pop(); - // hypothesis_chain.pop(); + // Didn't find a match break 'outer; } } else { - dbg!("ELSE"); parent_candidate_stack.pop(); hypothesis_chain.pop(); } @@ -460,8 +451,17 @@ mod tests { dbg!(server.to_string().clone()); dbg!(account.to_string().clone()); dbg!(dnslink.to_string().clone()); - let chain_for_powerline = store.get_chain(&device, &None, vec![], SystemTime::now()); - let chain_for_dnslink = store.get_chain(&device, &Some(dnslink), vec![], SystemTime::now()); + + let chain_for_powerline = + store.get_chain(&device, &None, "/".into(), vec![], SystemTime::now()); + + let chain_for_dnslink = store.get_chain( + &device, + &Some(dnslink), + "/".into(), + vec![], + SystemTime::now(), + ); let powerline_len = chain_for_powerline.expect("FIXME").unwrap().len(); let dnslink_len = chain_for_dnslink.expect("FIXME").unwrap().len(); diff --git a/src/delegation/store/traits.rs b/src/delegation/store/traits.rs index 58d08bbc..16fe3814 100644 --- a/src/delegation/store/traits.rs +++ b/src/delegation/store/traits.rs @@ -28,6 +28,7 @@ pub trait Store, Enc: Codec + TryFrom + In &self, audience: &DID, subject: &Option, + command: String, policy: Vec, now: SystemTime, ) -> Result)>>, Self::DelegationStoreError>; @@ -36,10 +37,11 @@ pub trait Store, Enc: Codec + TryFrom + In &self, issuer: DID, audience: &DID, + command: String, policy: Vec, now: SystemTime, ) -> Result { - self.get_chain(audience, &Some(issuer), policy, now) + self.get_chain(audience, &Some(issuer), command, policy, now) .map(|chain| chain.is_some()) } diff --git a/src/invocation/agent.rs b/src/invocation/agent.rs index 058f3d8e..5f5d927d 100644 --- a/src/invocation/agent.rs +++ b/src/invocation/agent.rs @@ -110,7 +110,7 @@ where { let proofs = self .delegation_store - .get_chain(self.did, &Some(subject.clone()), vec![], now) + .get_chain(self.did, &Some(subject.clone()), "/".into(), vec![], now) // FIXME .map_err(InvokeError::DelegationStoreError)? .map(|chain| chain.map(|(cid, _)| cid).into()) .unwrap_or(vec![]); @@ -158,7 +158,7 @@ where { let proofs = self .delegation_store - .get_chain(self.did, &Some(subject.clone()), vec![], now) + .get_chain(self.did, &Some(subject.clone()), "/".into(), vec![], now) .map_err(InvokeError::DelegationStoreError)? .map(|chain| chain.map(|(cid, _)| cid).into()) .unwrap_or(vec![]); @@ -261,11 +261,12 @@ where let proofs = if &subject == self.did { vec![] } else { - self.delegation_store - .get_chain(&subject, &Some(self.did.clone()), vec![], now.into()) - .map_err(|_| ())? - .map(|chain| chain.map(|(index_cid, _)| index_cid).into()) - .unwrap_or(vec![]) + todo!("update to latest trait interface"); // FIXME + // self.delegation_store + // .get_chain(&subject, &Some(self.did.clone()), vec![], now.into()) + // .map_err(|_| ())? + // .map(|chain| chain.map(|(index_cid, _)| index_cid).into()) + // .unwrap_or(vec![]) }; let payload = Payload { diff --git a/src/ipld/number.rs b/src/ipld/number.rs index 15e3bd0b..aced2a53 100644 --- a/src/ipld/number.rs +++ b/src/ipld/number.rs @@ -41,10 +41,14 @@ impl From for Ipld { } impl TryFrom for Number { - type Error = SerdeError; + type Error = (); // FIXME fn try_from(ipld: Ipld) -> Result { - ipld_serde::from_ipld(ipld) + match ipld { + Ipld::Integer(i) => Ok(Number::Integer(i)), + Ipld::Float(f) => Ok(Number::Float(f)), + _ => Err(()), + } } } From 30e687cf642749f788e996648cdfba48b0362b98 Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Sat, 9 Mar 2024 14:42:44 -0800 Subject: [PATCH 145/188] Remove bools from pred lang --- src/delegation/policy/predicate.rs | 26 ++++---------------------- 1 file changed, 4 insertions(+), 22 deletions(-) diff --git a/src/delegation/policy/predicate.rs b/src/delegation/policy/predicate.rs index 352300da..65d1db08 100644 --- a/src/delegation/policy/predicate.rs +++ b/src/delegation/policy/predicate.rs @@ -14,10 +14,6 @@ use proptest::prelude::*; // FIXME rename constraint or validation or expression or something? #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] pub enum Predicate { - // Booleans - True, - False, - // Comparison Equal(Select, ipld::Newtype), @@ -50,8 +46,6 @@ pub enum Harmonization { impl Predicate { pub fn run(self, data: &Ipld) -> Result { Ok(match self { - Predicate::True => true, - Predicate::False => false, Predicate::Equal(lhs, rhs_data) => lhs.resolve(data)? == rhs_data, Predicate::GreaterThan(lhs, rhs_data) => lhs.resolve(data)? > rhs_data, Predicate::GreaterThanOrEqual(lhs, rhs_data) => lhs.resolve(data)? >= rhs_data, @@ -85,16 +79,6 @@ impl Predicate { rhs_ctx: Vec, ) -> Harmonization { match self { - Predicate::True => match other { - Predicate::True => Harmonization::LhsNarrowerOrEqual, - _ => todo!(), - }, - // FIXME this should generally fail always? But how does it compare? - Predicate::False => match other { - // FIXME correct? - Predicate::False => Harmonization::LhsNarrowerOrEqual, - _ => todo!(), - }, Predicate::Equal(lhs_selector, lhs_ipld) => match other { Predicate::Equal(rhs_selector, rhs_ipld) => { // FIXME include ctx in path? @@ -177,7 +161,7 @@ impl Predicate { **********/ Predicate::Like(rhs_selector, rhs_str) => { if lhs_selector.is_related(rhs_selector) { - if let Ok(lhs_str) = String::try_from(*lhs_ipld) { + if let Ok(lhs_str) = String::try_from(lhs_ipld.clone()) { if glob(&lhs_str, rhs_str) { Harmonization::LhsNarrowerOrEqual } else { @@ -195,7 +179,7 @@ impl Predicate { * Connectives * ***************/ Predicate::Not(rhs_inner) => { - let rhs_raw_pred: Predicate = **rhs_inner; + let rhs_raw_pred: Predicate = *rhs_inner.clone(); match self.harmonize(&rhs_raw_pred, lhs_ctx, rhs_ctx) { Harmonization::LhsNarrowerOrEqual => Harmonization::RhsNarrower, Harmonization::RhsNarrower => Harmonization::LhsNarrowerOrEqual, @@ -205,8 +189,8 @@ impl Predicate { } } Predicate::And(and_left, and_right) => { - let rhs_raw_pred1: Predicate = **and_left; - let rhs_raw_pred2: Predicate = **and_right; + let rhs_raw_pred1: Predicate = *and_left.clone(); + let rhs_raw_pred2: Predicate = *and_right.clone(); match ( self.harmonize(&rhs_raw_pred1, lhs_ctx.clone(), rhs_ctx.clone()), @@ -329,8 +313,6 @@ pub fn glob(input: &String, pattern: &String) -> bool { impl From for Ipld { fn from(p: Predicate) -> Self { match p { - Predicate::True => Ipld::Bool(true), - Predicate::False => Ipld::Bool(false), Predicate::Equal(lhs, rhs) => { Ipld::List(vec![Ipld::String("==".to_string()), lhs.into(), rhs.into()]) } From 02d4c2f79a6beed1dc8969210e9fda1148de5aac Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Mon, 11 Mar 2024 20:48:18 -0700 Subject: [PATCH 146/188] Working on predicate unification / symbolic matching --- src/delegation/policy/predicate.rs | 684 ++++++++++++++++++++++------- src/delegation/store/memory.rs | 4 +- 2 files changed, 534 insertions(+), 154 deletions(-) diff --git a/src/delegation/policy/predicate.rs b/src/delegation/policy/predicate.rs index 65d1db08..c73dbbf6 100644 --- a/src/delegation/policy/predicate.rs +++ b/src/delegation/policy/predicate.rs @@ -3,6 +3,7 @@ use super::selector::{Select, SelectorError}; use crate::ipld; use enum_as_inner::EnumAsInner; use libipld_core::ipld::Ipld; +use multihash::Hasher; use serde::{Deserialize, Serialize}; use std::cmp::Ordering; @@ -37,10 +38,36 @@ pub enum Predicate { #[derive(Debug, Clone, PartialEq, EnumAsInner)] pub enum Harmonization { - IncompatiblePredicate, // Failed check - IncomparablePath, // LHS is ok - LhsNarrowerOrEqual, // LHS succeeded - RhsNarrower, // Succeeded, but RHS is narrower + Equal, // e.g. x > 10 vs x > 10 + Conflict, // e.g. x == 1 vs x == 2 + LhsWeaker, // e.g. x > 10 vs x > 100 (AKA compatible but rhs narrower than lhs) + LhsStronger, // e.g. x > 10 vs x > 1 (AKA compatible lhs narrower than rhs) + StrongerTogether, // e.g. x > 10 vs x < 100 (AKA both narrow each other) + IncomparablePath, // e.g. .foo and .bar +} + +impl Harmonization { + pub fn complement(self) -> Self { + match self { + Harmonization::Equal => Harmonization::Conflict, + Harmonization::Conflict => Harmonization::Equal, // FIXME Correct? + Harmonization::LhsWeaker => Harmonization::LhsStronger, + Harmonization::LhsStronger => Harmonization::LhsWeaker, + Harmonization::StrongerTogether => Harmonization::StrongerTogether, + Harmonization::IncomparablePath => Harmonization::IncomparablePath, + } + } + + pub fn flip(self) -> Self { + match self { + Harmonization::Equal => Harmonization::Equal, + Harmonization::Conflict => Harmonization::Conflict, + Harmonization::LhsWeaker => Harmonization::LhsStronger, + Harmonization::LhsStronger => Harmonization::LhsWeaker, + Harmonization::StrongerTogether => Harmonization::StrongerTogether, + Harmonization::IncomparablePath => Harmonization::IncomparablePath, + } + } } impl Predicate { @@ -78,205 +105,560 @@ impl Predicate { lhs_ctx: Vec, rhs_ctx: Vec, ) -> Harmonization { - match self { - Predicate::Equal(lhs_selector, lhs_ipld) => match other { - Predicate::Equal(rhs_selector, rhs_ipld) => { - // FIXME include ctx in path? - if lhs_selector.is_related(rhs_selector) { - if lhs_ipld == rhs_ipld { - Harmonization::LhsNarrowerOrEqual + match (self, other) { + ( + Predicate::Equal(lhs_selector, lhs_ipld), + Predicate::Equal(rhs_selector, rhs_ipld), + ) => { + // FIXME include ctx in path? + if lhs_selector.is_related(rhs_selector) { + if lhs_ipld == rhs_ipld { + Harmonization::Equal + } else { + Harmonization::Conflict + } + } else { + Harmonization::IncomparablePath + } + } + ( + Predicate::Equal(lhs_selector, lhs_ipld), + Predicate::GreaterThan(rhs_selector, rhs_num), + ) => { + // FIXME lhs + rhs selector must be exact + if lhs_selector.is_related(rhs_selector) { + if let Ok(lhs_num) = ipld::Number::try_from(lhs_ipld.0.clone()) { + if lhs_num > *rhs_num { + Harmonization::LhsStronger } else { - Harmonization::IncompatiblePredicate + Harmonization::Conflict } } else { - Harmonization::IncomparablePath + Harmonization::Conflict } + } else { + Harmonization::IncomparablePath } - - /************ - * Numerics * - ************/ - Predicate::GreaterThan(rhs_selector, rhs_num) => { - if lhs_selector.is_related(rhs_selector) { - if let Ok(lhs_num) = ipld::Number::try_from(lhs_ipld.0.clone()) { - if lhs_num > *rhs_num { - Harmonization::LhsNarrowerOrEqual - } else { - Harmonization::IncompatiblePredicate - } + } + ( + Predicate::Equal(lhs_selector, lhs_ipld), + Predicate::GreaterThanOrEqual(rhs_selector, rhs_num), + ) => { + if lhs_selector.is_related(rhs_selector) { + if let Ok(lhs_num) = ipld::Number::try_from(lhs_ipld.0.clone()) { + if lhs_num >= *rhs_num { + Harmonization::LhsStronger } else { - Harmonization::IncompatiblePredicate + Harmonization::Conflict } } else { - Harmonization::IncomparablePath + Harmonization::Conflict } + } else { + Harmonization::IncomparablePath } - Predicate::GreaterThanOrEqual(rhs_selector, rhs_num) => { - if lhs_selector.is_related(rhs_selector) { - if let Ok(lhs_num) = ipld::Number::try_from(lhs_ipld.0.clone()) { - if lhs_num >= *rhs_num { - Harmonization::LhsNarrowerOrEqual - } else { - Harmonization::IncompatiblePredicate - } + } + ( + Predicate::Equal(lhs_selector, lhs_ipld), + Predicate::LessThan(rhs_selector, rhs_num), + ) => { + if lhs_selector.is_related(rhs_selector) { + if let Ok(lhs_num) = ipld::Number::try_from(lhs_ipld.0.clone()) { + if lhs_num < *rhs_num { + Harmonization::LhsStronger } else { - Harmonization::IncompatiblePredicate + Harmonization::Conflict } } else { - Harmonization::IncomparablePath + Harmonization::Conflict } + } else { + Harmonization::IncomparablePath } - Predicate::LessThan(rhs_selector, rhs_num) => { - if lhs_selector.is_related(rhs_selector) { - if let Ok(lhs_num) = ipld::Number::try_from(lhs_ipld.0.clone()) { - if lhs_num < *rhs_num { - Harmonization::LhsNarrowerOrEqual - } else { - Harmonization::IncompatiblePredicate - } + } + ( + Predicate::Equal(lhs_selector, lhs_ipld), + Predicate::LessThanOrEqual(rhs_selector, rhs_num), + ) => { + if lhs_selector.is_related(rhs_selector) { + if let Ok(lhs_num) = ipld::Number::try_from(lhs_ipld.0.clone()) { + if lhs_num <= *rhs_num { + Harmonization::LhsStronger } else { - Harmonization::IncompatiblePredicate + Harmonization::Conflict } } else { - Harmonization::IncomparablePath + Harmonization::Conflict } + } else { + Harmonization::IncomparablePath } - Predicate::LessThanOrEqual(rhs_selector, rhs_num) => { - if lhs_selector.is_related(rhs_selector) { - if let Ok(lhs_num) = ipld::Number::try_from(lhs_ipld.0.clone()) { - if lhs_num <= *rhs_num { - Harmonization::LhsNarrowerOrEqual - } else { - Harmonization::IncompatiblePredicate - } + } + /*********** + * Strings * + ***********/ + (Predicate::Like(lhs_selector, lhs_str), Predicate::Like(rhs_selector, rhs_str)) => { + if lhs_selector.is_related(rhs_selector) { + if lhs_selector == rhs_selector { + if lhs_str == rhs_str { + Harmonization::Equal } else { - Harmonization::IncompatiblePredicate + // FIXME actually not accurate; need to walk both in case of inner patterns + match (glob(lhs_str, rhs_str), glob(rhs_str, lhs_str)) { + (true, true) => Harmonization::StrongerTogether, + _ => Harmonization::Conflict, + } } } else { - Harmonization::IncomparablePath + Harmonization::Conflict } + } else { + Harmonization::IncomparablePath } - /********** - * String * - **********/ - Predicate::Like(rhs_selector, rhs_str) => { - if lhs_selector.is_related(rhs_selector) { - if let Ok(lhs_str) = String::try_from(lhs_ipld.clone()) { - if glob(&lhs_str, rhs_str) { - Harmonization::LhsNarrowerOrEqual - } else { - Harmonization::IncompatiblePredicate - } + } + (Predicate::Equal(lhs_selector, lhs_ipld), Predicate::Like(rhs_selector, rhs_str)) => { + if lhs_selector.is_related(rhs_selector) { + if let Ipld::String(lhs_str) = &lhs_ipld.0 { + if glob(&lhs_str, rhs_str) { + // FIXME? + Harmonization::LhsStronger } else { - Harmonization::IncompatiblePredicate + Harmonization::Conflict } } else { - Harmonization::IncomparablePath + // NOTE Predicate::Like forces this to unify as a string, so anything else fails + // ...so this is not *not* a type checker + Harmonization::Conflict } + } else { + Harmonization::IncomparablePath } + } + (lhs @ Predicate::Like(_, _), rhs @ Predicate::Equal(_, _)) => { + rhs.harmonize(lhs, rhs_ctx, lhs_ctx).complement() + } - /*************** - * Connectives * - ***************/ - Predicate::Not(rhs_inner) => { - let rhs_raw_pred: Predicate = *rhs_inner.clone(); - match self.harmonize(&rhs_raw_pred, lhs_ctx, rhs_ctx) { - Harmonization::LhsNarrowerOrEqual => Harmonization::RhsNarrower, - Harmonization::RhsNarrower => Harmonization::LhsNarrowerOrEqual, - Harmonization::IncomparablePath => Harmonization::IncomparablePath, - // FIXME double check - Harmonization::IncompatiblePredicate => Harmonization::LhsNarrowerOrEqual, + /**************** + * Greater Than * + ***************/ + ( + Predicate::GreaterThan(lhs_selector, lhs_num), + Predicate::GreaterThan(rhs_selector, rhs_num), + ) => { + if lhs_selector.is_related(rhs_selector) { + if lhs_selector == rhs_selector { + if lhs_num == rhs_num { + Harmonization::Equal + } else if lhs_num > rhs_num { + Harmonization::LhsStronger + } else { + Harmonization::LhsWeaker + } + } else { + Harmonization::Conflict } + } else { + Harmonization::IncomparablePath } - Predicate::And(and_left, and_right) => { - let rhs_raw_pred1: Predicate = *and_left.clone(); - let rhs_raw_pred2: Predicate = *and_right.clone(); - - match ( - self.harmonize(&rhs_raw_pred1, lhs_ctx.clone(), rhs_ctx.clone()), - self.harmonize(&rhs_raw_pred2, lhs_ctx, rhs_ctx), - ) { - (Harmonization::LhsNarrowerOrEqual, Harmonization::LhsNarrowerOrEqual) => { - Harmonization::LhsNarrowerOrEqual + } + ( + Predicate::GreaterThan(lhs_selector, lhs_num), + Predicate::GreaterThanOrEqual(rhs_selector, rhs_num), + ) => { + if lhs_selector.is_related(rhs_selector) { + if lhs_selector == rhs_selector { + if lhs_num < rhs_num { + Harmonization::LhsWeaker + } else { + Harmonization::LhsStronger } - (Harmonization::RhsNarrower, Harmonization::RhsNarrower) => { - Harmonization::RhsNarrower + } else { + Harmonization::Conflict + } + } else { + Harmonization::IncomparablePath + } + } + ( + Predicate::GreaterThan(lhs_selector, lhs_num), + Predicate::LessThan(rhs_selector, rhs_num), + ) => { + if lhs_selector.is_related(rhs_selector) { + if lhs_selector == rhs_selector { + if lhs_num > rhs_num { + Harmonization::StrongerTogether + } else { + Harmonization::Conflict } - (Harmonization::LhsNarrowerOrEqual, Harmonization::RhsNarrower) => { - Harmonization::IncompatiblePredicate + } else { + Harmonization::Conflict + } + } else { + Harmonization::IncomparablePath + } + } + ( + Predicate::GreaterThan(lhs_selector, lhs_num), + Predicate::LessThanOrEqual(rhs_selector, rhs_num), + ) => { + if lhs_selector.is_related(rhs_selector) { + if lhs_selector == rhs_selector { + if lhs_num > rhs_num { + Harmonization::StrongerTogether + } else { + Harmonization::Conflict } - (Harmonization::RhsNarrower, Harmonization::LhsNarrowerOrEqual) => { - Harmonization::IncompatiblePredicate + } else { + Harmonization::Conflict + } + } else { + Harmonization::IncomparablePath + } + } + + /************************* + * Greater Than Or Equal * + *************************/ + ( + Predicate::GreaterThanOrEqual(lhs_selector, lhs_num), + Predicate::GreaterThanOrEqual(rhs_selector, rhs_num), + ) => { + if lhs_selector.is_related(rhs_selector) { + if lhs_selector == rhs_selector { + if lhs_num == rhs_num { + Harmonization::Equal + } else if lhs_num > rhs_num { + Harmonization::LhsStronger + } else { + Harmonization::LhsWeaker } - (Harmonization::IncomparablePath, right) => right, - (left, Harmonization::IncomparablePath) => left, - (Harmonization::IncompatiblePredicate, _) => { - Harmonization::IncompatiblePredicate + } else { + Harmonization::Conflict + } + } else { + Harmonization::IncomparablePath + } + } + ( + Predicate::GreaterThanOrEqual(lhs_selector, lhs_num), + Predicate::GreaterThan(rhs_selector, rhs_num), + ) => { + if lhs_selector.is_related(rhs_selector) { + if lhs_selector == rhs_selector { + if lhs_num < rhs_num { + Harmonization::LhsStronger + } else { + Harmonization::LhsWeaker } - (_, Harmonization::IncompatiblePredicate) => { - Harmonization::IncompatiblePredicate + } else { + Harmonization::Conflict + } + } else { + Harmonization::IncomparablePath + } + } + ( + Predicate::GreaterThanOrEqual(lhs_selector, lhs_num), + Predicate::LessThanOrEqual(rhs_selector, rhs_num), + ) => { + if lhs_selector.is_related(rhs_selector) { + if lhs_selector == rhs_selector { + if lhs_num <= rhs_num { + Harmonization::StrongerTogether + } else { + Harmonization::Conflict } + } else { + Harmonization::Conflict } + } else { + Harmonization::IncomparablePath } - Predicate::Or(or_left, or_right) => { - let rhs_raw_pred1: Predicate = *or_left.clone(); - let rhs_raw_pred2: Predicate = *or_right.clone(); + } + ( + Predicate::GreaterThanOrEqual(lhs_selector, lhs_num), + Predicate::LessThan(rhs_selector, rhs_num), + ) => { + if lhs_selector.is_related(rhs_selector) { + if lhs_selector == rhs_selector { + if lhs_num < rhs_num { + Harmonization::StrongerTogether + } else { + Harmonization::Conflict + } + } else { + Harmonization::Conflict + } + } else { + Harmonization::IncomparablePath + } + } - match ( - self.harmonize(&rhs_raw_pred1, lhs_ctx.clone(), rhs_ctx.clone()), - self.harmonize(&rhs_raw_pred2, lhs_ctx, rhs_ctx), - ) { - (Harmonization::LhsNarrowerOrEqual, Harmonization::LhsNarrowerOrEqual) => { - Harmonization::LhsNarrowerOrEqual + /********************** + * Less Than Or Equal * + **********************/ + ( + Predicate::LessThanOrEqual(lhs_selector, lhs_num), + Predicate::LessThanOrEqual(rhs_selector, rhs_num), + ) => { + if lhs_selector.is_related(rhs_selector) { + if lhs_selector == rhs_selector { + if lhs_num == rhs_num { + Harmonization::Equal + } else if lhs_num < rhs_num { + Harmonization::LhsStronger + } else { + Harmonization::LhsWeaker } - (Harmonization::RhsNarrower, Harmonization::RhsNarrower) => { - Harmonization::RhsNarrower + } else { + Harmonization::Conflict + } + } else { + Harmonization::IncomparablePath + } + } + ( + Predicate::LessThanOrEqual(lhs_selector, lhs_num), + Predicate::LessThan(rhs_selector, rhs_num), + ) => { + if lhs_selector.is_related(rhs_selector) { + if lhs_selector == rhs_selector { + if lhs_num == rhs_num { + Harmonization::LhsWeaker + } else if lhs_num < rhs_num { + Harmonization::LhsStronger + } else { + Harmonization::LhsWeaker } - (Harmonization::LhsNarrowerOrEqual, Harmonization::RhsNarrower) => { - Harmonization::LhsNarrowerOrEqual + } else { + Harmonization::Conflict + } + } else { + Harmonization::IncomparablePath + } + } + ( + Predicate::LessThanOrEqual(lhs_selector, lhs_num), + Predicate::GreaterThan(rhs_selector, rhs_num), + ) => { + if lhs_selector.is_related(rhs_selector) { + if lhs_selector == rhs_selector { + if lhs_num > rhs_num { + Harmonization::StrongerTogether + } else { + Harmonization::Conflict } - (Harmonization::RhsNarrower, Harmonization::LhsNarrowerOrEqual) => { - Harmonization::LhsNarrowerOrEqual + } else { + Harmonization::Conflict + } + } else { + Harmonization::IncomparablePath + } + } + ( + Predicate::LessThanOrEqual(lhs_selector, lhs_num), + Predicate::GreaterThanOrEqual(rhs_selector, rhs_num), + ) => { + if lhs_selector.is_related(rhs_selector) { + if lhs_selector == rhs_selector { + if lhs_num >= rhs_num { + Harmonization::StrongerTogether + } else { + Harmonization::Conflict + } + } else { + Harmonization::Conflict + } + } else { + Harmonization::IncomparablePath + } + } + + /************* + * Less Than * + *************/ + ( + Predicate::LessThan(lhs_selector, lhs_num), + Predicate::LessThan(rhs_selector, rhs_num), + ) => { + if lhs_selector.is_related(rhs_selector) { + if lhs_selector == rhs_selector { + if lhs_num == rhs_num { + Harmonization::Equal + } else if lhs_num < rhs_num { + Harmonization::LhsStronger + } else { + Harmonization::LhsWeaker + } + } else { + Harmonization::Conflict + } + } else { + Harmonization::IncomparablePath + } + } + ( + Predicate::LessThan(lhs_selector, lhs_num), + Predicate::LessThanOrEqual(rhs_selector, rhs_num), + ) => { + if lhs_selector.is_related(rhs_selector) { + if lhs_selector == rhs_selector { + if lhs_num == rhs_num { + Harmonization::LhsStronger + } else if lhs_num < rhs_num { + Harmonization::LhsStronger + } else { + Harmonization::LhsWeaker } - (Harmonization::IncomparablePath, right) => right, - (left, Harmonization::IncomparablePath) => left, - (Harmonization::IncompatiblePredicate, _) => { - Harmonization::IncompatiblePredicate + } else { + Harmonization::Conflict + } + } else { + Harmonization::IncomparablePath + } + } + ( + Predicate::LessThan(lhs_selector, lhs_num), + Predicate::GreaterThan(rhs_selector, rhs_num), + ) => { + if lhs_selector.is_related(rhs_selector) { + if lhs_selector == rhs_selector { + if lhs_num > rhs_num { + Harmonization::StrongerTogether + } else { + Harmonization::Conflict } - (_, Harmonization::IncompatiblePredicate) => { - Harmonization::IncompatiblePredicate + } else { + Harmonization::Conflict + } + } else { + Harmonization::IncomparablePath + } + } + ( + Predicate::LessThan(lhs_selector, lhs_num), + Predicate::GreaterThanOrEqual(rhs_selector, rhs_num), + ) => { + if lhs_selector.is_related(rhs_selector) { + if lhs_selector == rhs_selector { + if lhs_num > rhs_num { + Harmonization::StrongerTogether + } else { + Harmonization::Conflict } + } else { + Harmonization::Conflict } + } else { + Harmonization::IncomparablePath } - /****************** - * Quantification * - ******************/ - Predicate::Every(rhs_selector, rhs_inner) => { - let rhs_raw_pred: Predicate = *rhs_inner.clone(); - todo!() - // match self.harmonize(&rhs_raw_pred, lhs_ctx, rhs_ctx) { - // Harmonization::LhsNarrowerOrEqual => Harmonization::LhsNarrowerOrEqual, - // Harmonization::RhsNarrower => Harmonization::RhsNarrower, - // Harmonization::IncomparablePath => Harmonization::IncomparablePath, - // Harmonization::IncompatiblePredicate => { - // Harmonization::IncompatiblePredicate - // } - // } + } + + /*************** + * Connectives * + ***************/ + (_self, Predicate::Not(rhs_inner)) => { + self.harmonize(rhs_inner, lhs_ctx, rhs_ctx).complement() + } + (Predicate::Not(lhs_inner), rhs) => { + lhs_inner.harmonize(rhs, lhs_ctx, rhs_ctx).complement() + } + (_self, Predicate::And(and_left, and_right)) => { + let rhs_raw_pred1: Predicate = *and_left.clone(); + let rhs_raw_pred2: Predicate = *and_right.clone(); + + match ( + self.harmonize(&rhs_raw_pred1, lhs_ctx.clone(), rhs_ctx.clone()), + self.harmonize(&rhs_raw_pred2, lhs_ctx, rhs_ctx), + ) { + (Harmonization::Conflict, _) => Harmonization::Conflict, + (_, Harmonization::Conflict) => Harmonization::Conflict, + (Harmonization::IncomparablePath, right) => right, + (left, Harmonization::IncomparablePath) => left, + (Harmonization::Equal, rhs) => rhs, + (lhs, Harmonization::Equal) => lhs, + (Harmonization::LhsWeaker, Harmonization::LhsWeaker) => { + Harmonization::LhsWeaker + } + (Harmonization::LhsStronger, Harmonization::LhsStronger) => { + Harmonization::LhsStronger + } + (Harmonization::LhsStronger, Harmonization::LhsWeaker) => { + Harmonization::StrongerTogether + } + (Harmonization::LhsWeaker, Harmonization::LhsStronger) => { + Harmonization::StrongerTogether + } + (Harmonization::StrongerTogether, _) => Harmonization::StrongerTogether, + (_, Harmonization::StrongerTogether) => Harmonization::StrongerTogether, } - Predicate::Some(rhs_selector, rhs_inner) => { - let rhs_raw_pred: Predicate = *rhs_inner.clone(); - todo!() - // match self.harmonize(&rhs_raw_pred, lhs_ctx, rhs_ctx) { - // Harmonization::LhsNarrowerOrEqual => Harmonization::LhsNarrowerOrEqual, - // Harmonization::RhsNarrower => Harmonization::RhsNarrower, - // Harmonization::IncomparablePath => Harmonization::IncomparablePath, - // Harmonization::IncompatiblePredicate => { - // Harmonization::IncompatiblePredicate - // } - // } + } + (lhs @ Predicate::And(_, _), rhs) => lhs.harmonize(rhs, lhs_ctx, rhs_ctx).flip(), + (_self, Predicate::Or(or_left, or_right)) => { + let rhs_raw_pred1: Predicate = *or_left.clone(); + let rhs_raw_pred2: Predicate = *or_right.clone(); + + match ( + self.harmonize(&rhs_raw_pred1, lhs_ctx.clone(), rhs_ctx.clone()), + self.harmonize(&rhs_raw_pred2, lhs_ctx, rhs_ctx), + ) { + (Harmonization::Conflict, Harmonization::Conflict) => Harmonization::Conflict, + (lhs, Harmonization::Conflict) => lhs, + (Harmonization::Conflict, rhs) => rhs, + (Harmonization::IncomparablePath, right) => right, + (left, Harmonization::IncomparablePath) => left, + (Harmonization::Equal, rhs) => rhs, + (lhs, Harmonization::Equal) => lhs, + (Harmonization::LhsWeaker, Harmonization::LhsWeaker) => { + Harmonization::LhsWeaker + } + (Harmonization::LhsStronger, Harmonization::LhsStronger) => { + Harmonization::LhsStronger + } + (_, Harmonization::LhsWeaker) => Harmonization::LhsWeaker, + (Harmonization::LhsWeaker, _) => Harmonization::LhsWeaker, + (Harmonization::LhsStronger, Harmonization::StrongerTogether) => { + Harmonization::LhsStronger + } + (Harmonization::StrongerTogether, Harmonization::LhsStronger) => { + Harmonization::LhsStronger + } + (Harmonization::StrongerTogether, Harmonization::StrongerTogether) => { + Harmonization::StrongerTogether + } } - _ => todo!(), // FIXME - }, + } + (lhs @ Predicate::Or(_, _), rhs) => lhs.harmonize(rhs, lhs_ctx, rhs_ctx).flip(), + // /****************** + // * Quantification * + // ******************/ + // Predicate::Every(rhs_selector, rhs_inner) => { + // let rhs_raw_pred: Predicate = *rhs_inner.clone(); + // // TODO FIXME exact path + // todo!() + // // match self.harmonize(&rhs_raw_pred, lhs_ctx, rhs_ctx) { + // // Harmonization::LhsPassed => Harmonization::LhsPassed, + // // Harmonization::LhsWeaker => Harmonization::LhsWeaker, + // // Harmonization::IncomparablePath => Harmonization::IncomparablePath, + // // Harmonization::Conflict => { + // // Harmonization::Conflict + // // } + // // } + // } + // Predicate::Some(rhs_selector, rhs_inner) => { + // let rhs_raw_pred: Predicate = *rhs_inner.clone(); + // // TODO FIXME As long as the lhs path doens't terminate earlier, then pass + // todo!() + // // match self.harmonize(&rhs_raw_pred, lhs_ctx, rhs_ctx) { + // // Harmonization::LhsPassed => Harmonization::LhsPassed, + // // Harmonization::LhsWeaker => Harmonization::LhsWeaker, + // // Harmonization::IncomparablePath => Harmonization::IncomparablePath, + // // Harmonization::Conflict => { + // // Harmonization::Conflict + // // } + // // } + // } + // }, _ => todo!(), } } diff --git a/src/delegation/store/memory.rs b/src/delegation/store/memory.rs index 6d547e88..4b9373bb 100644 --- a/src/delegation/store/memory.rs +++ b/src/delegation/store/memory.rs @@ -218,9 +218,7 @@ where let comparison = target_pred.harmonize(delegate_pred, vec![], vec![]); - if comparison.is_incompatible_predicate() - || comparison.is_rhs_narrower() - { + if comparison.is_conflict() || comparison.is_lhs_weaker() { continue 'inner; } } From bfae42777bb904cb7c0d00e6765daa7119369e5f Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Tue, 12 Mar 2024 18:20:36 -0700 Subject: [PATCH 147/188] Add "via" functionality --- src/ability/arguments.rs | 23 ------------ src/delegation/agent.rs | 3 ++ src/delegation/payload.rs | 4 ++ src/did/traits.rs | 2 +- src/invocation/agent.rs | 2 +- src/invocation/payload.rs | 78 +++++++++++++++++++++++---------------- 6 files changed, 56 insertions(+), 56 deletions(-) diff --git a/src/ability/arguments.rs b/src/ability/arguments.rs index 7d504812..d53a5583 100644 --- a/src/ability/arguments.rs +++ b/src/ability/arguments.rs @@ -3,26 +3,3 @@ mod named; pub use named::*; - -use crate::ipld; -use libipld_core::ipld::Ipld; -use std::collections::BTreeMap; - -// FIXME just remove? -// FIXME move under invoc::promise? -// pub type Promised = Resolves>; -// -// impl Promised { -// pub fn try_resolve_option(self) -> Option> { -// match self.try_resolve() { -// Err(_) => None, -// Ok(named_promises) => named_promises -// .iter() -// .try_fold(BTreeMap::new(), |mut map, (k, v)| { -// map.insert(k.clone(), Ipld::try_from(v.clone()).ok()?); -// Some(map) -// }) -// .map(Named), -// } -// } -// } diff --git a/src/delegation/agent.rs b/src/delegation/agent.rs index 57e0c30e..c28cf6af 100644 --- a/src/delegation/agent.rs +++ b/src/delegation/agent.rs @@ -57,6 +57,7 @@ where &self, audience: DID, subject: Option, + via: Option, command: String, new_policy: Vec, metadata: BTreeMap, @@ -77,6 +78,7 @@ where issuer: self.did.clone(), audience, subject, + via, command, metadata, nonce, @@ -107,6 +109,7 @@ where issuer: self.did.clone(), audience, subject, + via, command, policy, metadata, diff --git a/src/delegation/payload.rs b/src/delegation/payload.rs index f830ec23..f74e4a94 100644 --- a/src/delegation/payload.rs +++ b/src/delegation/payload.rs @@ -46,6 +46,10 @@ pub struct Payload { /// The agent being delegated to. pub audience: DID, + /// A [`Did`] that must be in the delegation chain at invocation time. + #[builder(default)] + pub via: Option, + /// The command being delegated. pub command: String, diff --git a/src/did/traits.rs b/src/did/traits.rs index 03ed7659..53bebea9 100644 --- a/src/did/traits.rs +++ b/src/did/traits.rs @@ -2,7 +2,7 @@ use did_url::DID; use std::fmt; pub trait Did: - PartialEq + ToString + TryFrom + Into + signature::Verifier + PartialEq + ToString + TryFrom + Into + signature::Verifier + Ord { type Signature: signature::SignatureEncoding + PartialEq + fmt::Debug; type Signer: signature::Signer + fmt::Debug; diff --git a/src/invocation/agent.rs b/src/invocation/agent.rs index 5f5d927d..c380a39b 100644 --- a/src/invocation/agent.rs +++ b/src/invocation/agent.rs @@ -329,7 +329,7 @@ pub enum ReceiveError< DelegationStoreError(#[source] D), #[error("delegation validation error: {0}")] - ValidationError(#[source] ValidationError), + ValidationError(#[source] ValidationError), } #[derive(Debug, Error)] diff --git a/src/invocation/payload.rs b/src/invocation/payload.rs index 2b42391f..fa281e76 100644 --- a/src/invocation/payload.rs +++ b/src/invocation/payload.rs @@ -17,6 +17,7 @@ use serde::{ ser::SerializeStruct, Deserialize, Serialize, Serializer, }; +use std::collections::BTreeSet; use std::{collections::BTreeMap, fmt}; use thiserror::Error; use web_time::SystemTime; @@ -147,7 +148,7 @@ impl Payload { &self, proofs: Vec<&delegation::Payload>, now: &SystemTime, - ) -> Result<(), ValidationError> + ) -> Result<(), ValidationError> where A: ToCommand + Clone, DID: Clone, @@ -160,45 +161,57 @@ impl Payload { cmd.push('/'); } - proofs.into_iter().try_fold(&self.issuer, |iss, proof| { - if *iss != proof.audience { - return Err(ValidationError::InvalidSubject.into()); - } + let (_, vias) = proofs.into_iter().try_fold( + (&self.issuer, BTreeSet::new()), + |(iss, mut vias), proof| { + if *iss != proof.audience { + return Err(ValidationError::InvalidSubject.into()); + } - if let Some(proof_subject) = &proof.subject { - if self.subject != *proof_subject { - return Err(ValidationError::MisalignedIssAud.into()); + if let Some(proof_subject) = &proof.subject { + if self.subject != *proof_subject { + return Err(ValidationError::MisalignedIssAud.into()); + } } - } - if SystemTime::from(proof.expiration.clone()) > *now { - return Err(ValidationError::Expired.into()); - } + if SystemTime::from(proof.expiration.clone()) > *now { + return Err(ValidationError::Expired.into()); + } - if let Some(nbf) = proof.not_before.clone() { - if SystemTime::from(nbf) > *now { - return Err(ValidationError::NotYetValid.into()); + if let Some(nbf) = proof.not_before.clone() { + if SystemTime::from(nbf) > *now { + return Err(ValidationError::NotYetValid.into()); + } } - } - if !cmd.starts_with(&proof.command) { - return Err(ValidationError::CommandMismatch(proof.command.clone())); - } + vias.remove(&iss); + if let Some(via_did) = &proof.via { + vias.insert(via_did); + } + + if !cmd.starts_with(&proof.command) { + return Err(ValidationError::CommandMismatch(proof.command.clone())); + } - let ipld_args = Ipld::from(args.clone()); + let ipld_args = Ipld::from(args.clone()); - for predicate in proof.policy.iter() { - if !predicate - .clone() - .run(&ipld_args) - .map_err(ValidationError::SelectorError)? - { - return Err(ValidationError::FailedPolicy(predicate.clone())); + for predicate in proof.policy.iter() { + if !predicate + .clone() + .run(&ipld_args) + .map_err(ValidationError::SelectorError)? + { + return Err(ValidationError::FailedPolicy(predicate.clone())); + } } - } - Ok(&proof.issuer) - })?; + Ok((&proof.issuer, vias)) + }, + )?; + + if !vias.is_empty() { + todo!() + } Ok(()) } @@ -206,7 +219,7 @@ impl Payload { /// Delegation validation errors. #[derive(Debug, Clone, PartialEq, Error)] -pub enum ValidationError { +pub enum ValidationError { #[error("The subject of the delegation is invalid")] InvalidSubject, @@ -227,6 +240,9 @@ pub enum ValidationError { #[error(transparent)] SelectorError(#[from] SelectorError), + + #[error("via field constraint was unfulfilled: {0:?}")] + UnfulfilledViaConstraint(BTreeSet), } impl Capsule for Payload { From b65ec3e1a69b50d5865ab2223ac2bdab9621d953 Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Wed, 13 Mar 2024 09:40:15 -0700 Subject: [PATCH 148/188] Defaults --- src/invocation/agent.rs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/invocation/agent.rs b/src/invocation/agent.rs index c380a39b..4fad1b07 100644 --- a/src/invocation/agent.rs +++ b/src/invocation/agent.rs @@ -6,13 +6,13 @@ use super::{ }; use crate::ability::command::ToCommand; use crate::{ - ability::{arguments, parse::ParseAbilityError, ucan::revoke::Revoke}, + ability::{self, arguments, parse::ParseAbilityError, ucan::revoke::Revoke}, crypto::{ signature::{self, Envelope}, varsig, Nonce, }, delegation, - did::Did, + did::{self, Did}, invocation::promise, time::Timestamp, }; @@ -32,13 +32,13 @@ use web_time::SystemTime; #[derive(Debug)] pub struct Agent< 'a, - T: Resolvable + ToCommand, - DID: Did, S: Store, P: promise::Store, D: delegation::store::Store, - V: varsig::Header + Clone, - C: Codec + Into + TryFrom, + T: Resolvable + ToCommand = ability::preset::Preset, + DID: Did = did::preset::Verifier, + V: varsig::Header + Clone = varsig::header::Preset, + C: Codec + Into + TryFrom = varsig::encoding::Preset, > { /// The agent's [`DID`]. pub did: &'a DID, @@ -56,7 +56,7 @@ pub struct Agent< marker: PhantomData<(T, V, C)>, } -impl<'a, T, DID, S, P, D, V, C> Agent<'a, T, DID, S, P, D, V, C> +impl<'a, T, DID, S, P, D, V, C> Agent<'a, S, P, D, T, DID, V, C> where Ipld: Encode + From, delegation::Payload: Clone, From 61ed0cae918926d9ff0c28ce1eb6fd0a8d9944bc Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Wed, 13 Mar 2024 20:47:22 -0700 Subject: [PATCH 149/188] Much work, but clearer what's required --- Cargo.toml | 1 + src/ability/crud.rs | 11 ++ src/ability/crud/create.rs | 17 ++ src/ability/crud/destroy.rs | 13 ++ src/ability/crud/read.rs | 17 ++ src/ability/crud/update.rs | 14 +- src/ability/msg.rs | 9 + src/ability/msg/send.rs | 11 ++ src/ability/preset.rs | 11 ++ src/ability/ucan/revoke.rs | 7 + src/ability/wasm/run.rs | 10 ++ src/crypto/signature/envelope.rs | 42 +++-- src/delegation.rs | 10 +- src/delegation/agent.rs | 13 +- src/delegation/payload.rs | 105 ++++++++++-- src/delegation/policy/predicate.rs | 108 ++++++++++-- src/delegation/policy/selector.rs | 82 +++++++++ src/delegation/policy/selector/select.rs | 203 +++++++++++------------ src/delegation/store/memory.rs | 134 +++++++++++---- src/delegation/store/traits.rs | 9 +- src/did/traits.rs | 5 +- src/invocation.rs | 35 +++- src/invocation/agent.rs | 82 +++++++-- src/invocation/payload.rs | 138 ++++++++++++--- src/invocation/promise/store/memory.rs | 4 +- src/invocation/promise/store/traits.rs | 2 +- src/invocation/store.rs | 28 +++- src/receipt.rs | 15 +- src/receipt/payload.rs | 43 ++++- src/time/timestamp.rs | 24 +++ 30 files changed, 947 insertions(+), 256 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index a1e3775a..1415bdec 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -86,6 +86,7 @@ web-sys = { version = "0.3", features = ["Crypto", "CryptoKey", "CryptoKeyPair", [dev-dependencies] libipld = "0.16" +pretty_assertions = "1.4" rand = "0.8" testresult = "0.3" test-log = "0.2" diff --git a/src/ability/crud.rs b/src/ability/crud.rs index 0571b77f..e43a47a8 100644 --- a/src/ability/crud.rs +++ b/src/ability/crud.rs @@ -70,6 +70,17 @@ pub enum Crud { Destroy(Destroy), } +impl From for arguments::Named { + fn from(crud: Crud) -> Self { + match crud { + Crud::Create(create) => create.into(), + Crud::Read(read) => read.into(), + Crud::Update(update) => update.into(), + Crud::Destroy(destroy) => destroy.into(), + } + } +} + #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] pub enum PromisedCrud { Create(PromisedCreate), diff --git a/src/ability/crud/create.rs b/src/ability/crud/create.rs index 86df7070..69acac8c 100644 --- a/src/ability/crud/create.rs +++ b/src/ability/crud/create.rs @@ -7,6 +7,7 @@ use crate::{ }; use libipld_core::ipld::Ipld; use serde::{Deserialize, Serialize}; +use std::collections::BTreeMap; use std::path::PathBuf; use thiserror::Error; @@ -53,6 +54,22 @@ pub struct Create { pub args: Option>, } +impl From for Ipld { + fn from(create: Create) -> Self { + let mut map = BTreeMap::new(); + + if let Some(path) = create.path { + map.insert("path".to_string(), path.display().to_string().into()); + } + + if let Some(args) = create.args { + map.insert("args".to_string(), args.into()); + } + + Ipld::Map(map) + } +} + #[cfg_attr(doc, aquamarine::aquamarine)] /// An invoked `crud/create` ability (but possibly awaiting another /// [`Invocation`][crate::invocation::Invocation]). diff --git a/src/ability/crud/destroy.rs b/src/ability/crud/destroy.rs index 1f2516f5..3f52084a 100644 --- a/src/ability/crud/destroy.rs +++ b/src/ability/crud/destroy.rs @@ -7,6 +7,7 @@ use crate::{ }; use libipld_core::ipld::Ipld; use serde::{Deserialize, Serialize}; +use std::collections::BTreeMap; use std::path::PathBuf; use thiserror::Error; @@ -49,6 +50,18 @@ pub struct Destroy { pub path: Option, } +impl From for Ipld { + fn from(destroy: Destroy) -> Self { + let mut map = BTreeMap::new(); + + if let Some(path) = destroy.path { + map.insert("path".to_string(), path.display().to_string().into()); + } + + Ipld::Map(map) + } +} + const COMMAND: &'static str = "/crud/destroy"; impl Command for Destroy { diff --git a/src/ability/crud/read.rs b/src/ability/crud/read.rs index 1289a45a..873636e2 100644 --- a/src/ability/crud/read.rs +++ b/src/ability/crud/read.rs @@ -7,6 +7,7 @@ use crate::{ }; use libipld_core::{error::SerdeError, ipld::Ipld, serde as ipld_serde}; use serde::{Deserialize, Serialize}; +use std::collections::BTreeMap; use std::path::PathBuf; use thiserror::Error; @@ -49,6 +50,22 @@ pub struct Read { pub args: Option>, } +impl From for Ipld { + fn from(ready: Read) -> Self { + let mut map = BTreeMap::new(); + + if let Some(path) = ready.path { + map.insert("path".to_string(), Ipld::String(path.display().to_string())); + } + + if let Some(args) = ready.args { + map.insert("args".to_string(), args.into()); + } + + map.into() + } +} + #[cfg_attr(doc, aquamarine::aquamarine)] /// An invoked `crud/read` ability (but possibly awaiting another /// [`Invocation`][crate::invocation::Invocation]). diff --git a/src/ability/crud/update.rs b/src/ability/crud/update.rs index ebf0c281..935ab40f 100644 --- a/src/ability/crud/update.rs +++ b/src/ability/crud/update.rs @@ -189,9 +189,19 @@ impl TryFrom> for Update { } } -impl From for Ipld { +impl From for arguments::Named { fn from(create: Update) -> Self { - create.into() + let mut named = arguments::Named::::new(); + + if let Some(path) = create.path { + named.insert("path".to_string(), Ipld::String(path.display().to_string())); + } + + if let Some(args) = create.args { + named.insert("args".to_string(), args.into()); + } + + named } } diff --git a/src/ability/msg.rs b/src/ability/msg.rs index 2ad81030..f8682e02 100644 --- a/src/ability/msg.rs +++ b/src/ability/msg.rs @@ -27,6 +27,15 @@ pub enum Msg { Receive(Receive), } +impl From for arguments::Named { + fn from(msg: Msg) -> Self { + match msg { + Msg::Send(send) => send.into(), + Msg::Receive(receive) => receive.into(), + } + } +} + /// A promised version of the [`Msg`] ability. #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] pub enum PromisedMsg { diff --git a/src/ability/msg/send.rs b/src/ability/msg/send.rs index b37fe78c..c85dd4a7 100644 --- a/src/ability/msg/send.rs +++ b/src/ability/msg/send.rs @@ -7,6 +7,7 @@ use crate::{ }; use libipld_core::ipld::Ipld; use serde::{Deserialize, Serialize}; +use std::collections::BTreeMap; #[cfg_attr(doc, aquamarine::aquamarine)] /// The executable/dispatchable variant of the `msg/send` ability. @@ -51,6 +52,16 @@ pub struct Send { pub message: String, } +impl From for arguments::Named { + fn from(send: Send) -> Self { + arguments::Named::from_iter([ + ("to".to_string(), send.to.into()), + ("from".to_string(), send.from.into()), + ("message".to_string(), send.message.into()), + ]) + } +} + #[cfg_attr(doc, aquamarine::aquamarine)] /// The invoked variant of the `msg/send` ability /// diff --git a/src/ability/preset.rs b/src/ability/preset.rs index 34f74d90..44bd43cf 100644 --- a/src/ability/preset.rs +++ b/src/ability/preset.rs @@ -36,6 +36,17 @@ impl ToCommand for Preset { } } +impl From for arguments::Named { + fn from(preset: Preset) -> Self { + match preset { + Preset::Crud(crud) => crud.into(), + Preset::Msg(msg) => msg.into(), + Preset::Ucan(ucan) => ucan.into(), + Preset::Wasm(wasm) => wasm.into(), + } + } +} + #[derive(Debug, Clone, PartialEq)] //, Serialize, Deserialize)] pub enum PromisedPreset { Crud(PromisedCrud), diff --git a/src/ability/ucan/revoke.rs b/src/ability/ucan/revoke.rs index 4505fc77..b7ee1313 100644 --- a/src/ability/ucan/revoke.rs +++ b/src/ability/ucan/revoke.rs @@ -9,6 +9,7 @@ use crate::{ }; use libipld_core::{cid::Cid, ipld::Ipld}; use serde::{Deserialize, Serialize}; +use std::collections::BTreeMap; use std::fmt::Debug; /// The fully resolved variant: ready to execute. @@ -19,6 +20,12 @@ pub struct Revoke { // FIXME pub witness } +impl From for arguments::Named { + fn from(revoke: Revoke) -> Self { + arguments::Named::from_iter([("ucan".to_string(), Ipld::Link(revoke.ucan).into())]) + } +} + const COMMAND: &'static str = "/ucan/revoke"; impl Command for Revoke { diff --git a/src/ability/wasm/run.rs b/src/ability/wasm/run.rs index dcbe97de..77f95abc 100644 --- a/src/ability/wasm/run.rs +++ b/src/ability/wasm/run.rs @@ -33,6 +33,16 @@ pub struct Run { pub args: Vec, } +impl From for arguments::Named { + fn from(run: Run) -> Self { + arguments::Named::from_iter([ + ("mod".into(), Ipld::from(run.module)), + ("fun".into(), run.function.into()), + ("args".into(), run.args.into()), + ]) + } +} + impl TryFrom> for Run { type Error = (); diff --git a/src/crypto/signature/envelope.rs b/src/crypto/signature/envelope.rs index 07b18300..0cea6c02 100644 --- a/src/crypto/signature/envelope.rs +++ b/src/crypto/signature/envelope.rs @@ -1,3 +1,4 @@ +use crate::ability::arguments::Named; use crate::{capsule::Capsule, crypto::varsig, did::Did}; use libipld_core::{ cid::Cid, @@ -6,14 +7,14 @@ use libipld_core::{ ipld::Ipld, multihash::{Code, MultihashDigest}, }; +use signature::SignatureEncoding; use signature::Verifier; -use signature::{SignatureEncoding, Signer}; use std::collections::BTreeMap; use thiserror::Error; pub trait Envelope: Sized { type DID: Did; - type Payload: Clone + Capsule + TryFrom + Into; + type Payload: Clone + Capsule + TryFrom> + Into>; type VarsigHeader: varsig::Header + Clone; type Encoder: Codec + TryFrom + Into; @@ -29,9 +30,11 @@ pub trait Envelope: Sized { ) -> Self; fn to_ipld_envelope(&self) -> Ipld { + let inner_args: Named = self.payload().clone().into(); + let inner_ipld: Ipld = inner_args.into(); + let wrapped_payload: Ipld = - BTreeMap::from_iter([(Self::Payload::TAG.into(), self.payload().clone().into())]) - .into(); + BTreeMap::from_iter([(Self::Payload::TAG.into(), inner_ipld)]).into(); let header_bytes: Vec = (*self.varsig_header()).clone().into(); let header: Ipld = vec![header_bytes.into(), wrapped_payload].into(); @@ -42,15 +45,15 @@ pub trait Envelope: Sized { fn try_from_ipld_envelope( ipld: Ipld, - ) -> Result>::Error>> { + ) -> Result>>::Error>> { if let Ipld::List(list) = ipld { if let [Ipld::Bytes(sig), Ipld::List(inner)] = list.as_slice() { if let [Ipld::Bytes(varsig_header), Ipld::Map(btree)] = inner.as_slice() { - if let (1, Some(inner)) = ( + if let (1, Some(Ipld::Map(inner))) = ( btree.len(), btree.get(::TAG.into()), ) { - let payload = Self::Payload::try_from(inner.clone()) + let payload = Self::Payload::try_from(Named(inner.clone())) .map_err(FromIpldError::CannotParsePayload)?; let varsig_header = Self::VarsigHeader::try_from(varsig_header.as_slice()) @@ -100,7 +103,8 @@ pub trait Envelope: Sized { payload: Self::Payload, ) -> Result where - Ipld: Encode + From, // FIXME force it to be named args not IPLD + Ipld: Encode, + Named: From, { Self::try_sign_generic(signer, varsig_header, payload) } @@ -125,10 +129,14 @@ pub trait Envelope: Sized { payload: Self::Payload, ) -> Result where - Ipld: Encode + From, + Ipld: Encode, + Named: From, { - let ipld: Ipld = - BTreeMap::from_iter([(Self::Payload::TAG.into(), Ipld::from(payload.clone()))]).into(); + let ipld: Ipld = BTreeMap::from_iter([( + Self::Payload::TAG.into(), + Named::::from(payload.clone()).into(), + )]) + .into(); let mut buffer = vec![]; ipld.encode(*varsig::header::Header::codec(&varsig_header), &mut buffer) @@ -155,12 +163,16 @@ pub trait Envelope: Sized { /// FIXME fn validate_signature(&self) -> Result<(), ValidateError> where - Ipld: Encode + From, + Ipld: Encode, + Named: From, { let mut encoded = vec![]; - let ipld: Ipld = - BTreeMap::from_iter([(Self::Payload::TAG.into(), self.payload().clone().into())]) - .into(); + let ipld: Ipld = BTreeMap::from_iter([( + Self::Payload::TAG.to_string(), + Named::::from(self.payload().clone()).into(), + )]) + .into(); + ipld.encode( *varsig::header::Header::codec(self.varsig_header()), &mut encoded, diff --git a/src/delegation.rs b/src/delegation.rs index 67f873a0..e25aa5fe 100644 --- a/src/delegation.rs +++ b/src/delegation.rs @@ -21,6 +21,7 @@ mod payload; pub use agent::Agent; pub use payload::*; +use crate::ability::arguments::Named; use crate::{ capsule::Capsule, crypto::{signature::Envelope, varsig, Nonce}, @@ -126,7 +127,8 @@ impl, C: Codec + Into + TryFrom> Delega impl + Clone, C: Codec + TryFrom + Into> Envelope for Delegation where - Payload: TryFrom, + Payload: TryFrom>, + Named: From>, { type DID = DID; type Payload = Payload; @@ -166,7 +168,7 @@ where impl + Clone, C: Codec + TryFrom + Into> Serialize for Delegation where - Payload: TryFrom, + Payload: TryFrom>, { fn serialize(&self, serializer: S) -> Result where @@ -179,8 +181,8 @@ where impl<'de, DID: Did + Clone, V: varsig::Header + Clone, C: Codec + TryFrom + Into> Deserialize<'de> for Delegation where - Payload: TryFrom, - as TryFrom>::Error: std::fmt::Display, + Payload: TryFrom>, + as TryFrom>>::Error: std::fmt::Display, { fn deserialize(deserializer: D) -> Result where diff --git a/src/delegation/agent.rs b/src/delegation/agent.rs index c28cf6af..89f86a8b 100644 --- a/src/delegation/agent.rs +++ b/src/delegation/agent.rs @@ -1,4 +1,5 @@ use super::{payload::Payload, policy::Predicate, store::Store, Delegation}; +use crate::ability::arguments::Named; use crate::{ crypto::{signature::Envelope, varsig, Nonce}, did::Did, @@ -43,6 +44,8 @@ impl< > Agent<'a, DID, S, V, Enc> where Ipld: Encode, + Payload: TryFrom>, + Named: From>, { pub fn new(did: &'a DID, signer: &'a ::Signer, store: &'a mut S) -> Self { Self { @@ -65,10 +68,7 @@ where not_before: Option, now: SystemTime, varsig_header: V, - ) -> Result, DelegateError> - where - Payload: TryFrom, - { + ) -> Result, DelegateError> { let mut salt = self.did.clone().to_string().into_bytes(); let nonce = Nonce::generate_12(&mut salt); @@ -125,10 +125,7 @@ where &mut self, cid: Cid, // FIXME remove and generate from the capsule header? delegation: Delegation, - ) -> Result<(), ReceiveError> - where - Payload: TryFrom, - { + ) -> Result<(), ReceiveError> { if self.store.get(&cid).is_ok() { return Ok(()); } diff --git a/src/delegation/payload.rs b/src/delegation/payload.rs index f74e4a94..41d3048b 100644 --- a/src/delegation/payload.rs +++ b/src/delegation/payload.rs @@ -1,10 +1,12 @@ use super::policy::Predicate; +use crate::ability::arguments::Named; use crate::{ capsule::Capsule, crypto::{varsig, Nonce}, did::{Did, Verifiable}, time::{TimeBoundError, Timestamp}, }; +use core::str::FromStr; use derive_builder::Builder; use libipld_core::{codec::Codec, error::SerdeError, ipld::Ipld, serde as ipld_serde}; use serde::{Deserialize, Serialize}; @@ -21,7 +23,7 @@ use crate::ipld; /// /// This contains the semantic information about the delegation, including the /// issuer, subject, audience, the delegated ability, time bounds, and so on. -#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Builder)] +#[derive(Debug, Clone, PartialEq, Builder)] // FIXME Serialize, Deserialize, Builder)] pub struct Payload { /// The subject of the [`Delegation`]. /// @@ -110,17 +112,96 @@ impl Verifiable for Payload { } } -impl Deserialize<'de>> TryFrom for Payload { - type Error = SerdeError; +impl TryFrom> for Payload { + type Error = (); // FIXME - fn try_from(ipld: Ipld) -> Result { - ipld_serde::from_ipld(ipld) + fn try_from(args: Named) -> Result { + let mut subject = None; + let mut issuer = None; + let mut audience = None; + let mut via = None; + let mut command = None; + let mut policy = None; + let mut metadata = None; + let mut nonce = None; + let mut expiration = None; + let mut not_before = None; + + for (k, ipld) in args { + match k.as_str() { + "sub" => { + subject = Some( + match ipld { + Ipld::Null => None, + Ipld::String(s) => Some(DID::from_str(s.as_str()).map_err(|_| ())?), + _ => return Err(()), + } + .ok_or(())?, + ) + } + "iss" => match ipld { + Ipld::String(s) => issuer = Some(DID::from_str(s.as_str()).map_err(|_| ())?), + _ => return Err(()), + }, + "aud" => match ipld { + Ipld::String(s) => audience = Some(DID::from_str(s.as_str()).map_err(|_| ())?), + _ => return Err(()), + }, + "via" => match ipld { + Ipld::String(s) => via = Some(DID::from_str(s.as_str()).map_err(|_| ())?), + _ => return Err(()), + }, + "cmd" => match ipld { + Ipld::String(s) => command = Some(s), + _ => return Err(()), + }, + "pol" => match ipld { + Ipld::List(xs) => { + policy = xs + .iter() + .map(|x| Predicate::try_from(x.clone()).ok()) + .collect(); + } + _ => return Err(()), + }, + "metadata" => match ipld { + Ipld::Map(m) => metadata = Some(m), + _ => return Err(()), + }, + "nonce" => match ipld { + Ipld::Bytes(b) => nonce = Some(Nonce::from(b).into()), + _ => return Err(()), + }, + "exp" => match ipld { + Ipld::Integer(i) => expiration = Some(Timestamp::try_from(i).map_err(|_| ())?), + _ => return Err(()), + }, + "nbf" => match ipld { + Ipld::Integer(i) => not_before = Some(Timestamp::try_from(i).map_err(|_| ())?), + _ => return Err(()), + }, + _ => (), + } + } + + Ok(Payload { + subject, + issuer: issuer.ok_or(())?, + audience: audience.ok_or(())?, + via, + command: command.ok_or(())?, + policy: policy.ok_or(())?, + metadata: metadata.ok_or(())?, + nonce: nonce.ok_or(())?, + expiration: expiration.ok_or(())?, + not_before, + }) } } -impl From> for Ipld { +impl From> for Named { fn from(payload: Payload) -> Self { - let mut btree: BTreeMap = BTreeMap::::from_iter([ + let mut args = Named::::from_iter([ ("iss".to_string(), Ipld::from(payload.issuer.to_string())), ("aud".to_string(), payload.audience.to_string().into()), ("cmd".to_string(), payload.command.into()), @@ -133,20 +214,20 @@ impl From> for Ipld { ]); if let Some(subject) = payload.subject { - btree.insert("sub".to_string(), Ipld::from(subject.to_string())); + args.insert("sub".to_string(), Ipld::from(subject.to_string())); } else { - btree.insert("sub".to_string(), Ipld::Null); + args.insert("sub".to_string(), Ipld::Null); } if let Some(not_before) = payload.not_before { - btree.insert("nbf".to_string(), Ipld::from(not_before)); + args.insert("nbf".to_string(), Ipld::from(not_before)); } if !payload.metadata.is_empty() { - btree.insert("metadata".to_string(), Ipld::Map(payload.metadata)); + args.insert("meta".to_string(), Ipld::Map(payload.metadata)); } - Ipld::from(btree) + args } } diff --git a/src/delegation/policy/predicate.rs b/src/delegation/policy/predicate.rs index c73dbbf6..d222cc3e 100644 --- a/src/delegation/policy/predicate.rs +++ b/src/delegation/policy/predicate.rs @@ -1,11 +1,10 @@ use super::selector::filter::Filter; -use super::selector::{Select, SelectorError}; +use super::selector::{Select, Selector, SelectorError}; use crate::ipld; use enum_as_inner::EnumAsInner; use libipld_core::ipld::Ipld; -use multihash::Hasher; use serde::{Deserialize, Serialize}; -use std::cmp::Ordering; +use std::{fmt, str::FromStr}; #[cfg(feature = "test_utils")] use proptest::prelude::*; @@ -13,7 +12,7 @@ use proptest::prelude::*; // FIXME Normal form? // FIXME exract domain gen selectors first? // FIXME rename constraint or validation or expression or something? -#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +#[derive(Debug, Clone, PartialEq)] pub enum Predicate { // Comparison Equal(Select, ipld::Newtype), @@ -73,24 +72,24 @@ impl Harmonization { impl Predicate { pub fn run(self, data: &Ipld) -> Result { Ok(match self { - Predicate::Equal(lhs, rhs_data) => lhs.resolve(data)? == rhs_data, - Predicate::GreaterThan(lhs, rhs_data) => lhs.resolve(data)? > rhs_data, - Predicate::GreaterThanOrEqual(lhs, rhs_data) => lhs.resolve(data)? >= rhs_data, - Predicate::LessThan(lhs, rhs_data) => lhs.resolve(data)? < rhs_data, - Predicate::LessThanOrEqual(lhs, rhs_data) => lhs.resolve(data)? <= rhs_data, - Predicate::Like(lhs, rhs_data) => glob(&lhs.resolve(data)?, &rhs_data), + Predicate::Equal(lhs, rhs_data) => lhs.get(data)? == rhs_data, + Predicate::GreaterThan(lhs, rhs_data) => lhs.get(data)? > rhs_data, + Predicate::GreaterThanOrEqual(lhs, rhs_data) => lhs.get(data)? >= rhs_data, + Predicate::LessThan(lhs, rhs_data) => lhs.get(data)? < rhs_data, + Predicate::LessThanOrEqual(lhs, rhs_data) => lhs.get(data)? <= rhs_data, + Predicate::Like(lhs, rhs_data) => glob(&lhs.get(data)?, &rhs_data), Predicate::Not(inner) => !inner.run(data)?, Predicate::And(lhs, rhs) => lhs.run(data)? && rhs.run(data)?, Predicate::Or(lhs, rhs) => lhs.run(data)? || rhs.run(data)?, Predicate::Every(xs, p) => xs - .resolve(data)? + .get(data)? .to_vec() .iter() .try_fold(true, |acc, nt| Ok(acc && p.clone().run(&nt.0)?))?, Predicate::Some(xs, p) => { let pred = p.clone(); - xs.resolve(data)? + xs.get(data)? .to_vec() .iter() .try_fold(true, |acc, nt| Ok(acc || pred.clone().run(&nt.0)?))? @@ -692,6 +691,91 @@ pub fn glob(input: &String, pattern: &String) -> bool { } } +impl TryFrom for Predicate { + type Error = (); // FIXME + + fn try_from(ipld: Ipld) -> Result { + match ipld { + Ipld::List(v) => match v.as_slice() { + [Ipld::String(s), inner] if s == "not" => { + let inner = Box::new(Predicate::try_from(inner.clone())?); + Ok(Predicate::Not(inner)) + } + [Ipld::String(op_str), Ipld::String(sel_str), val] => match op_str.as_str() { + "==" => { + let sel = + Select::::from_str(sel_str.as_str()).map_err(|_| ())?; + + Ok(Predicate::Equal(sel, ipld::Newtype(val.clone()))) + } + ">" => { + let sel = + Select::::from_str(sel_str.as_str()).map_err(|_| ())?; + + let num = ipld::Number::try_from(val.clone())?; + Ok(Predicate::GreaterThan(sel, num)) + } + ">=" => { + let sel = + Select::::from_str(sel_str.as_str()).map_err(|_| ())?; + let num = ipld::Number::try_from(val.clone())?; + Ok(Predicate::GreaterThanOrEqual(sel, num)) + } + "<" => { + let sel = + Select::::from_str(sel_str.as_str()).map_err(|_| ())?; + let num = ipld::Number::try_from(val.clone())?; + Ok(Predicate::LessThan(sel, num)) + } + "<=" => { + let sel = + Select::::from_str(sel_str.as_str()).map_err(|_| ())?; + let num = ipld::Number::try_from(val.clone())?; + Ok(Predicate::LessThanOrEqual(sel, num)) + } + "like" => { + let sel = Select::::from_str(sel_str.as_str()).map_err(|_| ())?; + if let Ipld::String(s) = val { + Ok(Predicate::Like(sel, s.to_string())) + } else { + Err(()) + } + } + "every" => { + let sel = Select::::from_str(sel_str.as_str()) + .map_err(|_| ())?; + + let p = Box::new(Predicate::try_from(val.clone())?); + Ok(Predicate::Every(sel, p)) + } + "some" => { + let sel = Select::::from_str(sel_str.as_str()) + .map_err(|_| ())?; + let p = Box::new(Predicate::try_from(val.clone())?); + Ok(Predicate::Some(sel, p)) + } + _ => Err(()), + }, + [Ipld::String(op_str), lhs, rhs] => match op_str.as_str() { + "and" => { + let lhs = Box::new(Predicate::try_from(lhs.clone())?); + let rhs = Box::new(Predicate::try_from(rhs.clone())?); + Ok(Predicate::And(lhs, rhs)) + } + "or" => { + let lhs = Box::new(Predicate::try_from(lhs.clone())?); + let rhs = Box::new(Predicate::try_from(rhs.clone())?); + Ok(Predicate::Or(lhs, rhs)) + } + _ => Err(()), + }, + _ => Err(()), + }, + _ => Err(()), + } + } +} + impl From for Ipld { fn from(p: Predicate) -> Self { match p { diff --git a/src/delegation/policy/selector.rs b/src/delegation/policy/selector.rs index 965b89a1..e88cd17f 100644 --- a/src/delegation/policy/selector.rs +++ b/src/delegation/policy/selector.rs @@ -8,7 +8,9 @@ pub use error::{ParseError, SelectorErrorReason}; pub use select::Select; pub use selectable::Selectable; +use crate::ipld; use filter::Filter; +use libipld_core::ipld::Ipld; use nom::{ self, branch::alt, @@ -36,6 +38,86 @@ impl Selector { pub fn is_related(&self, other: &Selector) -> bool { self.0.iter().zip(other.0.iter()).all(|(a, b)| a == b) } + + // pub fn get(&self, ctx: &Ipld) -> Result { + // let ipld: Ipld = self + // .0 + // .iter() + // .try_fold((ctx.clone(), vec![]), |(ipld, mut seen_ops), op| { + // seen_ops.push(op); + // + // match op { + // Filter::Try(inner) => { + // let op: Filter = *inner.clone(); + // let ipld: Ipld = Select::Get::(vec![op]) + // .resolve(ctx) + // .unwrap_or(Ipld::Null); + // + // Ok((ipld, seen_ops)) + // } + // Filter::ArrayIndex(i) => { + // let result = { + // match ipld { + // Ipld::List(xs) => { + // if i.abs() as usize > xs.len() { + // return Err(SelectorError::from_refs( + // &seen_ops, + // SelectorErrorReason::IndexOutOfBounds, + // )); + // }; + // + // xs.get((xs.len() as i32 + *i) as usize) + // .ok_or(SelectorError::from_refs( + // &seen_ops, + // SelectorErrorReason::IndexOutOfBounds, + // )) + // .cloned() + // } + // // FIXME behaviour on maps? type error + // _ => Err(SelectorError::from_refs( + // &seen_ops, + // SelectorErrorReason::NotAList, + // )), + // } + // }; + // + // Ok((result?, seen_ops)) + // } + // Filter::Field(k) => { + // let result = match ipld { + // Ipld::Map(xs) => xs + // .get(k) + // .ok_or(SelectorError::from_refs( + // &seen_ops, + // SelectorErrorReason::KeyNotFound, + // )) + // .cloned(), + // _ => Err(SelectorError::from_refs( + // &seen_ops, + // SelectorErrorReason::NotAMap, + // )), + // }; + // + // Ok((result?, seen_ops)) + // } + // Filter::Values => { + // let result = match ipld { + // Ipld::List(xs) => Ok(Ipld::List(xs)), + // Ipld::Map(xs) => Ok(Ipld::List(xs.values().cloned().collect())), + // _ => Err(SelectorError::from_refs( + // &seen_ops, + // SelectorErrorReason::NotACollection, + // )), + // }; + // + // Ok((result?, seen_ops)) + // } + // } + // })? + // .0; + // + // Ok(ipld::Newtype(ipld)) + // } } pub fn parse(input: &str) -> IResult<&str, Selector> { diff --git a/src/delegation/policy/selector/select.rs b/src/delegation/policy/selector/select.rs index e9c32ae2..6d36c02b 100644 --- a/src/delegation/policy/selector/select.rs +++ b/src/delegation/policy/selector/select.rs @@ -3,112 +3,108 @@ use super::{error::SelectorErrorReason, filter::Filter, Selectable, SelectorErro use libipld_core::ipld::Ipld; use serde::{Deserialize, Serialize}; use std::cmp::Ordering; +use std::str::FromStr; #[cfg(feature = "test_utils")] use proptest::prelude::*; -#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] -pub enum Select { - Get(Vec), - Pure(T), +#[derive(Debug, Clone, PartialEq)] +pub struct Select { + filters: Vec, + _marker: std::marker::PhantomData, } impl Select { + pub fn new(filters: Vec) -> Self { + Self { + filters, + _marker: std::marker::PhantomData, + } + } + pub fn is_related(&self, other: &Select) -> bool where Ipld: From + From, { - match (self, other) { - (Select::Pure(lhs_val), Select::Pure(rhs_val)) => { - Ipld::from(lhs_val.clone()) == Ipld::from(rhs_val.clone()) - } - (Select::Get(lhs_path), Select::Get(rhs_path)) => { - Selector(lhs_path.clone()).is_related(&Selector(rhs_path.clone())) - } - _ => false, - } + Selector(self.filters.clone()).is_related(&Selector(other.filters.clone())) } - pub fn resolve(self, ctx: &Ipld) -> Result { - match self { - Select::Pure(inner) => Ok(inner), - Select::Get(ops) => { - ops.iter() - .try_fold((ctx.clone(), vec![]), |(ipld, mut seen_ops), op| { - seen_ops.push(op); - - match op { - Filter::Try(inner) => { - let op: Filter = *inner.clone(); - let ipld: Ipld = Select::Get::(vec![op]) - .resolve(ctx) - .unwrap_or(Ipld::Null); - - Ok((ipld, seen_ops)) - } - Filter::ArrayIndex(i) => { - let result = { - match ipld { - Ipld::List(xs) => { - if i.abs() as usize > xs.len() { - return Err(SelectorError::from_refs( - &seen_ops, - SelectorErrorReason::IndexOutOfBounds, - )); - }; - - xs.get((xs.len() as i32 + *i) as usize) - .ok_or(SelectorError::from_refs( - &seen_ops, - SelectorErrorReason::IndexOutOfBounds, - )) - .cloned() - } - // FIXME behaviour on maps? type error - _ => Err(SelectorError::from_refs( + + pub fn get(self, ctx: &Ipld) -> Result { + self.filters + .iter() + .try_fold((ctx.clone(), vec![]), |(ipld, mut seen_ops), op| { + seen_ops.push(op); + + match op { + Filter::Try(inner) => { + let op: Filter = *inner.clone(); + let ipld: Ipld = + Select::::new(vec![op]).get(ctx).unwrap_or(Ipld::Null); + + Ok((ipld, seen_ops)) + } + Filter::ArrayIndex(i) => { + let result = { + match ipld { + Ipld::List(xs) => { + if i.abs() as usize > xs.len() { + return Err(SelectorError::from_refs( &seen_ops, - SelectorErrorReason::NotAList, - )), - } - }; + SelectorErrorReason::IndexOutOfBounds, + )); + }; - Ok((result?, seen_ops)) - } - Filter::Field(k) => { - let result = match ipld { - Ipld::Map(xs) => xs - .get(k) + xs.get((xs.len() as i32 + *i) as usize) .ok_or(SelectorError::from_refs( &seen_ops, - SelectorErrorReason::KeyNotFound, + SelectorErrorReason::IndexOutOfBounds, )) - .cloned(), - _ => Err(SelectorError::from_refs( - &seen_ops, - SelectorErrorReason::NotAMap, - )), - }; - - Ok((result?, seen_ops)) + .cloned() + } + // FIXME behaviour on maps? type error + _ => Err(SelectorError::from_refs( + &seen_ops, + SelectorErrorReason::NotAList, + )), } - Filter::Values => { - let result = match ipld { - Ipld::List(xs) => Ok(Ipld::List(xs)), - Ipld::Map(xs) => Ok(Ipld::List(xs.values().cloned().collect())), - _ => Err(SelectorError::from_refs( - &seen_ops, - SelectorErrorReason::NotACollection, - )), - }; - - Ok((result?, seen_ops)) - } - } - }) - .and_then(|(ipld, ref path)| { - T::try_select(ipld).map_err(|e| SelectorError::from_refs(path, e)) - }) - } - } + }; + + Ok((result?, seen_ops)) + } + Filter::Field(k) => { + let result = match ipld { + Ipld::Map(xs) => xs + .get(k) + .ok_or(SelectorError::from_refs( + &seen_ops, + SelectorErrorReason::KeyNotFound, + )) + .cloned(), + _ => Err(SelectorError::from_refs( + &seen_ops, + SelectorErrorReason::NotAMap, + )), + }; + + Ok((result?, seen_ops)) + } + Filter::Values => { + let result = match ipld { + Ipld::List(xs) => Ok(Ipld::List(xs)), + Ipld::Map(xs) => Ok(Ipld::List(xs.values().cloned().collect())), + _ => Err(SelectorError::from_refs( + &seen_ops, + SelectorErrorReason::NotACollection, + )), + }; + + Ok((result?, seen_ops)) + } + } + }) + .and_then(|(ipld, ref path)| { + T::try_select(ipld).map_err(|e| SelectorError::from_refs(path, e)) + }) } } @@ -117,28 +113,25 @@ where Ipld: From, { fn from(s: Select) -> Self { - match s { - Select::Get(ops) => Selector(ops).to_string().into(), - Select::Pure(inner) => inner.into(), - } + Selector(s.filters).to_string().into() + } +} + +impl FromStr for Select { + type Err = (); + + fn from_str(s: &str) -> Result { + let selector = Selector::from_str(s).map_err(|_| ())?; + Ok(Select { + filters: selector.0, + _marker: std::marker::PhantomData, + }) } } impl PartialOrd for Select { fn partial_cmp(&self, other: &Self) -> Option { - match (self, other) { - (Select::Pure(inner), Select::Pure(other_inner)) => { - if inner == other_inner { - Some(Ordering::Equal) - } else { - None - } - } - (Select::Get(ops), Select::Get(other_ops)) => { - Selector(ops.clone()).partial_cmp(&Selector(other_ops.clone())) - } - _ => None, - } + Selector(self.filters.clone()).partial_cmp(&Selector(other.filters.clone())) } } diff --git a/src/delegation/store/memory.rs b/src/delegation/store/memory.rs index 4b9373bb..5b958d5f 100644 --- a/src/delegation/store/memory.rs +++ b/src/delegation/store/memory.rs @@ -1,5 +1,5 @@ use super::Store; -use crate::crypto::signature::Envelope; +use crate::ability::arguments::Named; use crate::{ crypto::varsig, delegation::{policy::Predicate, Delegation}, @@ -7,10 +7,7 @@ use crate::{ }; use libipld_core::{cid::Cid, codec::Codec}; use nonempty::NonEmpty; -use std::{ - collections::{BTreeMap, BTreeSet}, - ops::ControlFlow, -}; +use std::collections::{BTreeMap, BTreeSet}; use web_time::SystemTime; #[cfg_attr(doc, aquamarine::aquamarine)] @@ -113,8 +110,8 @@ impl< Enc: Codec + TryFrom + Into, > Store for MemoryStore where - Ipld: From>, - delegation::Payload: TryFrom, + Named: From>, + delegation::Payload: TryFrom>, Delegation: Encode, { type DelegationStoreError = String; // FIXME misisng @@ -270,6 +267,7 @@ where #[cfg(test)] mod tests { + use crate::ability::arguments::Named; use crate::ability::command::Command; use crate::crypto::signature::Envelope; use crate::delegation::store::Store; @@ -330,10 +328,8 @@ mod tests { .audience(server.clone()) .command("/".into()) .expiration(crate::time::Timestamp::five_years_from_now()) - .build() - .expect("FIXME"), - ) - .expect("signature to work"); + .build()?, + )?; // 2. server -a-> device let account_device_ucan = crate::Delegation::try_sign( @@ -345,10 +341,8 @@ mod tests { .audience(device.clone()) .command("/".into()) .expiration(crate::time::Timestamp::five_years_from_now()) - .build() - .expect("FIXME"), // I don't love this is now failable - ) - .expect("signature to work"); + .build()?, // I don't love this is now failable + )?; // 3. dnslink -d-> account let dnslink_ucan = crate::Delegation::try_sign( @@ -360,30 +354,64 @@ mod tests { .audience(account.clone()) .command("/".into()) .expiration(crate::time::Timestamp::five_years_from_now()) - .build() - .expect("FIXME"), - ) - .expect("signature to work"); + .build()?, + )?; #[derive(Debug, Clone, PartialEq)] - pub struct AccountInfo {} + pub struct AccountManage; - impl Command for AccountInfo { - const COMMAND: &'static str = "/account/info"; + use crate::invocation::promise::CantResolve; + use crate::invocation::promise::Resolvable; + use crate::ipld; + + impl From for Named { + fn from(_: AccountManage) -> Self { + Default::default() + } + } + + impl TryFrom> for AccountManage { + type Error = (); + + fn try_from(args: Named) -> Result { + if args == Default::default() { + Ok(AccountManage) + } else { + Err(()) + } + } } - impl From for AccountInfo { - fn from(_: Ipld) -> Self { - AccountInfo {} + impl From for Named { + fn from(_: AccountManage) -> Self { + Default::default() } } - impl From for Ipld { - fn from(_: AccountInfo) -> Self { - Ipld::Null + impl TryFrom> for AccountManage { + type Error = (); + + fn try_from(args: Named) -> Result { + if args == Default::default() { + Ok(AccountManage) + } else { + Err(()) + } } } + impl Resolvable for AccountManage { + type Promised = AccountManage; + + fn try_resolve(promised: Self::Promised) -> Result> { + Ok(promised) + } + } + + impl Command for AccountManage { + const COMMAND: &'static str = "/account/info"; + } + // #[derive(Debug, Clone, PartialEq)] // pub struct DnsLinkUpdate { // pub cid: Cid, @@ -398,17 +426,15 @@ mod tests { // 4. [dnslink -d-> account -*-> server -a-> device] let account_invocation = crate::Invocation::try_sign( &device_signer, - varsig_header, + varsig_header.clone(), crate::invocation::PayloadBuilder::default() .subject(account.clone()) .issuer(device.clone()) .audience(Some(server.clone())) - .ability(AccountInfo {}) + .ability(AccountManage) .proofs(vec![]) // FIXME - .build() - .expect("FIXME"), - ) - .expect("FIXME"); + .build()?, + )?; // FIXME reenable // let dnslink_invocation = crate::Invocation::try_sign( @@ -432,7 +458,7 @@ mod tests { varsig::encoding::Preset, > = Default::default(); - let agent = crate::delegation::Agent::new(&server, &server_signer, &mut store); + let del_agent = crate::delegation::Agent::new(&server, &server_signer, &mut store); let _ = store.insert( account_device_ucan.cid().expect("FIXME"), @@ -461,11 +487,49 @@ mod tests { SystemTime::now(), ); + use crate::invocation::Agent; + let powerline_len = chain_for_powerline.expect("FIXME").unwrap().len(); let dnslink_len = chain_for_dnslink.expect("FIXME").unwrap().len(); assert_eq!((powerline_len, dnslink_len), (3, 3)); // FIXME + let mut inv_store = crate::invocation::store::MemoryStore::default(); + let mut del_store = crate::delegation::store::MemoryStore::default(); + let mut prom_store = crate::invocation::promise::store::MemoryStore::default(); + + let mut agent: Agent< + '_, + crate::invocation::store::MemoryStore, + crate::delegation::store::MemoryStore, + crate::invocation::promise::store::MemoryStore, + AccountManage, + > = Agent::new( + &server, + &server_signer, + &mut inv_store, + &mut del_store, + &mut prom_store, + ); + + let observed = agent.receive(account_invocation, &SystemTime::now()); + assert!(observed.is_ok()); + + let not_account_invocation = crate::Invocation::try_sign( + &device_signer, + varsig_header, + crate::invocation::PayloadBuilder::default() + .subject(account.clone()) + .issuer(server.clone()) + .audience(Some(device.clone())) + .ability(AccountManage) + .proofs(vec![]) // FIXME + .build()?, + )?; + + let observed_other = agent.receive(not_account_invocation, &SystemTime::now()); + assert!(observed_other.is_err()); + Ok(()) } } diff --git a/src/delegation/store/traits.rs b/src/delegation/store/traits.rs index 16fe3814..c6e91443 100644 --- a/src/delegation/store/traits.rs +++ b/src/delegation/store/traits.rs @@ -8,7 +8,14 @@ use nonempty::NonEmpty; use std::fmt::Debug; use web_time::SystemTime; -pub trait Store, Enc: Codec + TryFrom + Into> { +pub trait Store< + + + + DID: Did = crate::did::preset::Verifier, + V: varsig::Header = varsig::header::Preset, + Enc: Codec + Into + TryFrom = varsig::encoding::Preset, + > { type DelegationStoreError: Debug; fn get(&self, cid: &Cid) -> Result<&Delegation, Self::DelegationStoreError>; diff --git a/src/did/traits.rs b/src/did/traits.rs index 53bebea9..76e9220f 100644 --- a/src/did/traits.rs +++ b/src/did/traits.rs @@ -1,9 +1,8 @@ use did_url::DID; use std::fmt; +use std::str::FromStr; -pub trait Did: - PartialEq + ToString + TryFrom + Into + signature::Verifier + Ord -{ +pub trait Did: PartialEq + ToString + FromStr + signature::Verifier + Ord { type Signature: signature::SignatureEncoding + PartialEq + fmt::Debug; type Signer: signature::Signer + fmt::Debug; } diff --git a/src/invocation.rs b/src/invocation.rs index 09eaad20..0d64902a 100644 --- a/src/invocation.rs +++ b/src/invocation.rs @@ -21,6 +21,7 @@ pub mod store; pub use agent::Agent; pub use payload::*; +use crate::ability::arguments::Named; use crate::ability::command::ToCommand; use crate::{ crypto::{signature::Envelope, varsig}, @@ -60,6 +61,22 @@ pub struct Invocation< _marker: std::marker::PhantomData, } +impl< + A: Clone + ToCommand, + DID: Clone + did::Did, + V: Clone + varsig::Header, + C: Codec + TryFrom + Into, + > Encode for Invocation +where + Ipld: Encode, + Named: From + From>, + Payload: TryFrom>, +{ + fn encode(&self, c: C, w: &mut W) -> Result<(), libipld_core::error::Error> { + self.to_ipld_envelope().encode(c, w) + } +} + impl, C: Codec + TryFrom + Into> Invocation where @@ -133,8 +150,8 @@ impl< C: Codec + TryFrom + Into, > From> for Ipld where - Ipld: From, - Payload: TryFrom, + Named: From, + Payload: TryFrom>, { fn from(invocation: Invocation) -> Self { invocation.to_ipld_envelope() @@ -148,8 +165,8 @@ impl< C: Codec + TryFrom + Into, > Envelope for Invocation where - Ipld: From, - Payload: TryFrom, + Named: From, + Payload: TryFrom>, { type DID = DID; type Payload = Payload; @@ -193,8 +210,8 @@ impl< C: Codec + TryFrom + Into, > Serialize for Invocation where - Ipld: From, - Payload: TryFrom, + Named: From, + Payload: TryFrom>, { fn serialize(&self, serializer: S) -> Result where @@ -212,9 +229,9 @@ impl< C: Codec + TryFrom + Into, > Deserialize<'de> for Invocation where - Ipld: From, - Payload: TryFrom, - as TryFrom>::Error: std::fmt::Display, + Named: From, + Payload: TryFrom>, + as TryFrom>>::Error: std::fmt::Display, { fn deserialize(deserializer: D) -> Result where diff --git a/src/invocation/agent.rs b/src/invocation/agent.rs index 4fad1b07..aefd5117 100644 --- a/src/invocation/agent.rs +++ b/src/invocation/agent.rs @@ -4,6 +4,7 @@ use super::{ store::Store, Invocation, }; +use crate::ability::arguments::Named; use crate::ability::command::ToCommand; use crate::{ ability::{self, arguments, parse::ParseAbilityError, ucan::revoke::Revoke}, @@ -33,8 +34,8 @@ use web_time::SystemTime; pub struct Agent< 'a, S: Store, - P: promise::Store, D: delegation::store::Store, + P: promise::Store, T: Resolvable + ToCommand = ability::preset::Preset, DID: Did = did::preset::Verifier, V: varsig::Header + Clone = varsig::header::Preset, @@ -56,16 +57,17 @@ pub struct Agent< marker: PhantomData<(T, V, C)>, } -impl<'a, T, DID, S, P, D, V, C> Agent<'a, S, P, D, T, DID, V, C> +impl<'a, T, DID, S, P, D, V, C> Agent<'a, S, D, P, T, DID, V, C> where - Ipld: Encode + From, + Ipld: Encode, + Named: From, delegation::Payload: Clone, T: Resolvable + ToCommand + Clone, T::Promised: Clone + ToCommand, DID: Did + Clone, S: Store, - P: promise::Store, D: delegation::store::Store, + P: promise::Store, V: varsig::Header + Clone, C: Codec + Into + TryFrom, { @@ -105,8 +107,8 @@ where >, > where - Ipld: From, - Payload: TryFrom, + Named: From, + Payload: TryFrom>, { let proofs = self .delegation_store @@ -153,8 +155,8 @@ where >, > where - Ipld: From, - Payload: TryFrom, + Named: From, + Payload: TryFrom>, { let proofs = self .delegation_store @@ -188,14 +190,14 @@ where now: &SystemTime, ) -> Result>, ReceiveError> where - Ipld: From + From, - Payload: TryFrom, - arguments::Named: From, + arguments::Named: From + From, + Payload: TryFrom>, Invocation: Clone + Encode, -

) -> Self { destroy.into() @@ -227,19 +219,6 @@ impl From for arguments::Named { } } -// impl From> for Promised { -// fn from(source: arguments::Named) -> Self { -// let path = source -// .get("path") -// .map(|ipld| ipld.clone().try_into().unwrap()); -// -// let args = source -// .get("args") -// .map(|ipld| ipld.clone().try_into().unwrap()); -// Promised { path, args } -// } -// } - impl From for Promised { fn from(r: Ready) -> Promised { Promised { @@ -269,3 +248,14 @@ impl Resolvable for Ready { Ok(Ready { path }) } } + +impl From for Ready { + fn from(p: Promised) -> Ready { + Ready { + path: p + .path + .map(|inner_path| inner_path.try_resolve().ok()) + .flatten(), + } + } +} diff --git a/src/ability/crud/read.rs b/src/ability/crud/read.rs index 7bd237a6..555d7cbc 100644 --- a/src/ability/crud/read.rs +++ b/src/ability/crud/read.rs @@ -133,12 +133,14 @@ impl Delegable for Ready { impl From for Builder { fn from(promised: Promised) -> Self { Builder { - path: promised.path.map(Into::into), - args: promised.args.map(Into::into), + path: promised.path.and_then(|p| p.try_resolve().ok()), + args: promised.args.and_then(|p| p.try_resolve_option()), // FIXME this needs to read better } } } +// FIXME resolves vs resolvable is confusing + impl, A: Into> From> for Ipld { fn from(read: Generic) -> Self { read.into() @@ -216,19 +218,23 @@ impl From for arguments::Named { ); } - if let Some(args_res) = promised.args { - named.insert( - "args".to_string(), - args_res - .map(|a| { - // FIXME extract - a.iter() - .map(|(k, v)| (k.to_string(), v.clone().serialize_as_ipld())) - .collect::>() - }) - .into(), - ); - } + // FIXME + // if let Some(args_res) = promised.args { + // let v = args_res.map(|a| { + // // FIXME extract + // a.iter().try_fold(BTreeMap::new(), |mut acc, (k, v)| { + // acc.insert(*k, (*v).try_into().ok()?); + // Some(acc) + // }) + // }); + + // // match v { + // // + // // } + // // named.insert( + // // "args".to_string(), + // // ); + // } named } diff --git a/src/ability/crud/update.rs b/src/ability/crud/update.rs index 64654b15..5ce08700 100644 --- a/src/ability/crud/update.rs +++ b/src/ability/crud/update.rs @@ -172,18 +172,6 @@ impl From for Ready { } } -impl From for Builder { - fn from(promised: Promised) -> Self { - Builder { - path: promised.path.and_then(Into::into), - args: promised.args.and_then(|res| match res.try_resolve() { - Ok(args) => Some(args.into()), - Err(unresolved) => None, - }), - } - } -} - impl From for Ipld { fn from(create: Ready) -> Self { create.into() @@ -202,12 +190,12 @@ impl TryFrom for Ready { Ok(Ready { path: map .get("path") - .map(|ipld| (ipld::Newtype(*ipld)).try_into().map_err(|_| ())) + .map(|ipld| (ipld::Newtype(ipld.clone())).try_into().map_err(|_| ())) .transpose()?, args: map .get("args") - .map(|ipld| (*ipld).try_into().map_err(|_| ())) + .map(|ipld| ipld.clone().try_into().map_err(|_| ())) .transpose()?, }) } else { @@ -277,12 +265,14 @@ impl From for arguments::Named { named.insert( "args".to_string(), args_res - .map(|a| { - // FIXME extract - a.iter() - .map(|(k, v)| (k.to_string(), v.clone().serialize_as_ipld())) - .collect::>() + .try_resolve() + .expect("FIXME") + .iter() + .try_fold(BTreeMap::new(), |mut map, (k, v)| { + map.insert(k.clone(), Ipld::try_from(v.clone()).ok()?); // FIXME double check + Some(map) }) + .expect("FIXME") .into(), ); } @@ -291,19 +281,6 @@ impl From for arguments::Named { } } -// impl From> for Promised { -// fn from(source: arguments::Named) -> Self { -// let path = source -// .get("path") -// .map(|ipld| ipld.clone().try_into().unwrap()); -// -// let args = source -// .get("args") -// .map(|ipld| ipld.clone().try_into().unwrap()); -// Promised { path, args } -// } -// } - impl From for Promised { fn from(r: Ready) -> Promised { Promised { @@ -352,3 +329,12 @@ impl Resolvable for Ready { Ok(Ready { path, args }) } } + +impl From for Builder { + fn from(promised: Promised) -> Self { + Builder { + path: promised.path.and_then(|p| p.try_resolve().ok()), + args: promised.args.and_then(|a| a.try_resolve_option()), + } + } +} diff --git a/src/ability/msg.rs b/src/ability/msg.rs index cc8d6c35..e0ff7556 100644 --- a/src/ability/msg.rs +++ b/src/ability/msg.rs @@ -83,6 +83,15 @@ impl Resolvable for Ready { } } +impl From for Builder { + fn from(promised: Promised) -> Self { + match promised { + Promised::Send(send) => Builder::Send(send.into()), + Promised::Receive(receive) => Builder::Receive(receive.into()), + } + } +} + impl CheckSame for Builder { type Error = (); // FIXME @@ -103,7 +112,6 @@ impl CheckParents for Builder { match (self, proof) { (Builder::Send(this), Any) => this.check_parent(&Any), (Builder::Receive(this), Any) => this.check_parent(&Any), - _ => Err(()), } } } diff --git a/src/ability/msg/receive.rs b/src/ability/msg/receive.rs index 3897a65f..05701fb2 100644 --- a/src/ability/msg/receive.rs +++ b/src/ability/msg/receive.rs @@ -46,7 +46,7 @@ pub struct Receive { pub from: Option, } -pub type Builder = Receive; +pub(super) type Builder = Receive; // FIXME needs promisory version @@ -58,13 +58,13 @@ impl Delegable for Receive { type Builder = Receive; } -// impl From for Builder { -// fn from(promised: Promised) -> Self { -// Builder { -// from: promised.from.map(Into::into), -// } -// } -// } +impl From for Builder { + fn from(promised: Promised) -> Self { + Builder { + from: promised.from.and_then(|x| x.try_resolve_option()), + } + } +} impl Checkable for Receive { type Hierarchy = Parentful; @@ -103,19 +103,21 @@ impl TryFrom for Receive { #[derive(Debug, Clone, PartialEq)] pub struct Promised { - pub from: promise::Resolves>, + pub from: Option>>, } impl From for arguments::Named { fn from(promised: Promised) -> Self { let mut args = arguments::Named::new(); - match promised.from { - promise::Resolves::Ok(from) => { - args.insert("from".into(), from.into()); - } - promise::Resolves::Err(from) => { - args.insert("from".into(), from.into()); + if let Some(from) = promised.from { + match from { + promise::Resolves::Ok(from) => { + args.insert("from".into(), from.into()); + } + promise::Resolves::Err(from) => { + args.insert("from".into(), from.into()); + } } } @@ -127,8 +129,16 @@ impl Resolvable for Receive { type Promised = Promised; fn try_resolve(p: Promised) -> Result { - promise::Resolves::try_resolve(p.from) - .map(|from| Receive { from }) - .map_err(|from| Promised { from }) + match &p.from { + None => Ok(Receive { from: None }), + Some(promise::Resolves::Ok(promiseOk)) => match promiseOk.clone().try_resolve() { + Ok(from) => Ok(Receive { from }), + Err(_from) => Err(Promised { from: p.from }), + }, + Some(promise::Resolves::Err(promiseErr)) => match promiseErr.clone().try_resolve() { + Ok(from) => Ok(Receive { from }), + Err(_from) => Err(Promised { from: p.from }), + }, + } } } diff --git a/src/ability/preset.rs b/src/ability/preset.rs index 6a523f1b..6bf60a75 100644 --- a/src/ability/preset.rs +++ b/src/ability/preset.rs @@ -152,3 +152,13 @@ impl Resolvable for Ready { } } } + +impl From for Builder { + fn from(promised: Promised) -> Self { + match promised { + Promised::Crud(promised) => Builder::Crud(promised.into()), + Promised::Msg(promised) => Builder::Msg(promised.into()), + Promised::Wasm(promised) => Builder::Wasm(promised.into()), + } + } +} diff --git a/src/ability/ucan/revoke.rs b/src/ability/ucan/revoke.rs index c2d28dc5..ac3aacb8 100644 --- a/src/ability/ucan/revoke.rs +++ b/src/ability/ucan/revoke.rs @@ -31,13 +31,13 @@ impl Delegable for Ready { type Builder = Builder; } -// impl From for Builder { -// fn from(promised: Promised) -> Self { -// Builder { -// ucan: promised.ucan.map(Into::into), -// } -// } -// } +impl From for Builder { + fn from(promised: Promised) -> Self { + Builder { + ucan: promised.ucan.try_resolve().ok(), + } + } +} impl Resolvable for Ready { type Promised = Promised; diff --git a/src/delegation/store.rs b/src/delegation/store.rs index af4ceb3f..91502f10 100644 --- a/src/delegation/store.rs +++ b/src/delegation/store.rs @@ -130,7 +130,7 @@ pub struct MemoryStore { impl Store for MemoryStore where - B::Hierarchy: Into>, + B::Hierarchy: Into> + Clone, { type Error = (); // FIXME misisng @@ -203,7 +203,7 @@ where } for condition in &conditions { - if !condition.validate(&d.payload.ability_builder.into()) { + if !condition.validate(&d.payload.ability_builder.clone().into()) { return ControlFlow::Continue(()); } } diff --git a/src/invocation.rs b/src/invocation.rs index 4d33d418..522acfc6 100644 --- a/src/invocation.rs +++ b/src/invocation.rs @@ -17,7 +17,7 @@ use crate::{ability, did, did::Did, signature}; /// For a version that can include [`Promise`][promise::Promise]s, /// wrap your `T` in [`invocation::Promised`](Promised) to get /// `Invocation>`. -pub type Invocation = signature::Envelope, DID>; +pub type Invocation = signature::Envelope, DID>; // FIXME rename pub type PromisedInvocation = Invocation; diff --git a/src/invocation/agent.rs b/src/invocation/agent.rs index a3ff1d88..6751bef5 100644 --- a/src/invocation/agent.rs +++ b/src/invocation/agent.rs @@ -31,7 +31,7 @@ pub struct Agent< > { pub did: &'a DID, - pub delegation_store: &'a D, + pub delegation_store: &'a mut D, pub invocation_store: &'a mut S, pub unresolved_promise_store: &'a mut P, pub resolved_promise_store: &'a mut P, @@ -49,13 +49,16 @@ impl< P: Store, D: delegation::store::Store, > Agent<'a, T, C, DID, S, P, D> +where + T::Promised: Clone, + // Payload<::Hierarchy, DID>: Clone, // FIXME + delegation::Payload<::Hierarchy, C, DID>: Clone, { pub fn new( did: &'a DID, signer: &'a ::Signer, invocation_store: &'a mut S, delegation_store: &'a mut D, - revocation_store: &'a mut D, unresolved_promise_store: &'a mut P, resolved_promise_store: &'a mut P, ) -> Self { @@ -63,7 +66,6 @@ impl< did, invocation_store, delegation_store, - revocation_store, unresolved_promise_store, resolved_promise_store, signer, @@ -85,7 +87,7 @@ impl< ) -> Result, ()> { let proofs = self .delegation_store - .get_chain(self.did, subject, &ability.into(), vec![], now) + .get_chain(self.did, subject, &ability.clone().into(), vec![], now) .map_err(|_| ())? .map(|chain| chain.map(|(cid, _)| cid).into()) .unwrap_or(vec![]); @@ -109,7 +111,7 @@ impl< } pub fn receive( - &self, + &mut self, promised: Invocation, now: &SystemTime, // FIXME return type @@ -120,7 +122,7 @@ impl< { // FIXME needs varsig header let mut buffer = vec![]; - Ipld::from(promised) + Ipld::from(promised.clone()) .encode(DagCborCodec, &mut buffer) .expect("FIXME not dag-cbor? DagCborCodec to encode any arbitrary `Ipld`"); @@ -143,12 +145,12 @@ impl< .verify( &encoded, &match promised.signature { - Signature::Solo(sig) => sig, + Signature::Solo(ref sig) => sig.clone(), }, ) .map_err(|_| ())?; - let resolved_ability: T = match Resolvable::try_resolve(promised.payload.ability) { + let resolved_ability: T = match Resolvable::try_resolve(promised.payload.ability.clone()) { Ok(resolved) => resolved, Err(_) => { // FIXME check if any of the unresolved promises are in the store @@ -167,16 +169,16 @@ impl< .get_many(&promised.payload.proofs) .map_err(|_| ())? .into_iter() - .map(|d| d.payload) + .map(|d| d.payload.clone()) .collect(); - let resolved_payload = promised.payload.map_ability(|_| resolved_ability); + let resolved_payload = promised.payload.clone().map_ability(|_| resolved_ability); - delegation::Payload::::from(resolved_payload) + delegation::Payload::::from(resolved_payload.clone()) .check(proof_payloads, now) .map_err(|_| ())?; - if promised.payload.audience != Some(*self.did) { + if promised.payload.audience != Some(self.did.clone()) { return Ok(Recipient::Other(resolved_payload)); } @@ -202,7 +204,7 @@ impl< .get_chain( subject, self.did, - &ability.into(), + &ability.clone().into(), vec![], &SystemTime::now(), ) diff --git a/src/invocation/payload.rs b/src/invocation/payload.rs index cf5de2b9..cdfade84 100644 --- a/src/invocation/payload.rs +++ b/src/invocation/payload.rs @@ -42,7 +42,7 @@ pub struct Payload { pub nonce: Nonce, pub not_before: Option, - pub expiration: Option, + pub expiration: Option, // FIXME this field may not make sense } // FIXME cleanup traits @@ -66,7 +66,7 @@ impl Payload { metadata: self.metadata, nonce: self.nonce, not_before: self.not_before, - expiration: self.expiration, + expiration: None, } } @@ -105,7 +105,7 @@ impl From> nonce: payload.nonce, not_before: payload.not_before, - expiration: payload.expiration, + expiration: SystemTime::now().into(), // FIXME } } } @@ -122,7 +122,6 @@ impl, DID: Did> From> for arguments::N Ipld::List(payload.proofs.iter().map(Into::into).collect()), ), ("nonce".into(), payload.nonce.into()), - ("exp".into(), payload.expiration.into()), ]); if let Some(audience) = payload.audience { @@ -133,6 +132,10 @@ impl, DID: Did> From> for arguments::N args.insert("nbf".into(), not_before.into()); } + if let Some(expiration) = payload.expiration { + args.insert("exp".into(), expiration.into()); + } + args } } @@ -285,8 +288,9 @@ struct InternalSerializer { #[serde(rename = "nbf", skip_serializing_if = "Option::is_none")] not_before: Option, - #[serde(rename = "exp")] - expiration: Timestamp, + + #[serde(rename = "exp", skip_serializing_if = "Option::is_none")] + expiration: Option, } impl From for Ipld { @@ -370,7 +374,7 @@ impl>, DID: Did> From nonce: payload.nonce, not_before: payload.not_before, - expiration: payload.expiration, + expiration: None, } } } @@ -378,9 +382,10 @@ impl>, DID: Did> From impl Encode for Payload where Ipld: Encode, + Payload: Clone, // FIXME { fn encode(&self, codec: C, writer: &mut W) -> Result<(), anyhow::Error> { - let ipld: Ipld = (*self).into(); + let ipld = Ipld::from(self.clone()); ipld.encode(codec, writer) } } diff --git a/src/invocation/promise/resolves.rs b/src/invocation/promise/resolves.rs index 216939aa..026308ee 100644 --- a/src/invocation/promise/resolves.rs +++ b/src/invocation/promise/resolves.rs @@ -24,6 +24,13 @@ impl Resolves> { }, } } + + pub fn try_resolve_option(self) -> Option { + match self { + Resolves::Ok(p_ok) => p_ok.try_resolve().ok()?, + Resolves::Err(p_err) => p_err.try_resolve().ok()?, + } + } } impl Resolves { diff --git a/src/invocation/store.rs b/src/invocation/store.rs index 9196f041..945aa5a5 100644 --- a/src/invocation/store.rs +++ b/src/invocation/store.rs @@ -52,7 +52,10 @@ pub trait PromiseIndex { invocation: Cid, ) -> Result<(), Self::PromiseIndexError>; - fn get_waiting(&self, waiting_on: Vec) -> Result, Self::PromiseIndexError>; + fn get_waiting( + &self, + waiting_on: &mut Vec, + ) -> Result, Self::PromiseIndexError>; } pub struct MemoryPromiseIndex { @@ -73,15 +76,18 @@ impl PromiseIndex for MemoryPromiseIndex { Ok(()) } - fn get_waiting(&self, waiting_on: Vec) -> Result, Self::PromiseIndexError> { + fn get_waiting( + &self, + waiting_on: &mut Vec, + ) -> Result, Self::PromiseIndexError> { Ok(match waiting_on.pop() { None => BTreeSet::new(), Some(first) => waiting_on .iter() .try_fold(BTreeSet::from_iter([first]), |mut acc, cid| { - let next = self.index.get(cid).ok_or(NotFound)?; + let next = self.index.get(cid).ok_or(())?; - let reduced = acc.intersection(*next.into()).collect(); + let reduced: BTreeSet = acc.intersection(&next).cloned().collect(); if reduced.is_empty() { return Err(()); } diff --git a/src/ipld/enriched.rs b/src/ipld/enriched.rs index c0752c25..71297e6c 100644 --- a/src/ipld/enriched.rs +++ b/src/ipld/enriched.rs @@ -42,6 +42,7 @@ pub struct PostOrderIpldIter<'a, T> { outbound: Vec>, } +// FIXME not sure if &'a worth it because nbow I'm cloning everywhere #[derive(Clone, Debug, PartialEq)] pub enum Item<'a, T> { Node(&'a Enriched), @@ -59,7 +60,7 @@ impl<'a, T> PostOrderIpldIter<'a, T> { } } -impl<'a, T> IntoIterator for &'a Enriched { +impl<'a, T: Clone> IntoIterator for &'a Enriched { type Item = Item<'a, T>; type IntoIter = PostOrderIpldIter<'a, T>; @@ -68,9 +69,9 @@ impl<'a, T> IntoIterator for &'a Enriched { } } -impl<'a, T: Clone> FromIterator> for &'a Enriched { +impl<'a, T: Clone> FromIterator> for Enriched { fn from_iter>>(it: I) -> Self { - &it.into_iter().fold(Enriched::Null, |acc, x| match x { + it.into_iter().fold(Enriched::Null, |acc, x| match x { Item::Node(Enriched::Null) => Enriched::Null, Item::Node(Enriched::Bool(b)) => Enriched::Bool(*b), Item::Node(Enriched::Integer(i)) => Enriched::Integer(*i), @@ -97,29 +98,29 @@ impl<'a, T: Clone> FromIterator> for &'a Enriched { } } -impl<'a, T> From<&'a Enriched> for PostOrderIpldIter<'a, T> { +impl<'a, T: Clone> From<&'a Enriched> for PostOrderIpldIter<'a, T> { fn from(enriched: &'a Enriched) -> Self { PostOrderIpldIter::new(enriched) } } -impl<'a, T> Iterator for PostOrderIpldIter<'a, T> { +impl<'a, T: Clone> Iterator for PostOrderIpldIter<'a, T> { type Item = Item<'a, T>; fn next(&mut self) -> Option { loop { match self.inbound.pop() { None => return self.outbound.pop(), - Some(map @ Item::Node(Enriched::Map(btree))) => { - self.outbound.push(map); + Some(ref map @ Item::Node(Enriched::Map(ref btree))) => { + self.outbound.push(map.clone()); for node in btree.values() { self.inbound.push(Item::Inner(node)); } } - Some(list @ Item::Node(Enriched::List(vector))) => { - self.outbound.push(list); + Some(ref list @ Item::Node(Enriched::List(ref vector))) => { + self.outbound.push(list.clone()); for node in vector { self.inbound.push(Item::Inner(node)); diff --git a/src/signature.rs b/src/signature.rs index 81a3b85e..cb8b0152 100644 --- a/src/signature.rs +++ b/src/signature.rs @@ -8,13 +8,11 @@ use libipld_core::{ ipld::Ipld, multihash::{Code, MultihashGeneric}, }; -use serde::{de::DeserializeOwned, Deserialize, Serialize}; -use signature::SignatureEncoding; use std::collections::BTreeMap; // FIXME #[cfg(feature = "dag-cbor")] use libipld_cbor::DagCborCodec; -use signature::Signer; +use signature::{SignatureEncoding, Signer}; pub trait Verifiable { fn verifier<'a>(&'a self) -> &'a DID; @@ -36,7 +34,7 @@ pub struct Envelope + Capsule, DID: Did> { pub payload: T, } -impl + Into, DID: Did> Envelope { +impl + Into + Clone, DID: Did> Envelope { pub fn try_sign(signer: &DID::Signer, payload: T) -> Result, ()> { Self::try_sign_generic::(signer, DagCborCodec, Code::Sha2_256, payload) } @@ -51,7 +49,7 @@ impl + Into, DID: Did> Envelope { where Ipld: Encode, { - let ipld: Ipld = BTreeMap::from_iter([(T::TAG.into(), payload.into())]).into(); + let ipld: Ipld = BTreeMap::from_iter([(T::TAG.into(), payload.clone().into())]).into(); let mut buffer = vec![]; ipld.encode(codec, &mut buffer) @@ -67,8 +65,8 @@ impl + Into, DID: Did> Envelope { pub fn validate_signature(&self) -> Result<(), ()> { // FIXME need varsig - let codec = todo!(); - let hasher = todo!(); + let codec = DagCborCodec; + let hasher = Code::Sha2_256; let mut buffer = vec![]; let ipld: Ipld = BTreeMap::from_iter([(T::TAG.into(), self.payload.clone().into())]).into(); @@ -82,7 +80,7 @@ impl + Into, DID: Did> Envelope { .expect("FIXME expect signing to work..."), ); - match self.signature { + match &self.signature { Signature::Solo(sig) => self .verifier() .verify(&cid.to_bytes(), &sig) @@ -119,23 +117,24 @@ pub enum Signature { impl + Capsule + Into, DID: Did> Encode for Envelope where Ipld: Encode, + Envelope: Clone, // FIXME? { fn encode(&self, codec: C, writer: &mut W) -> Result<(), anyhow::Error> { - Ipld::from(*self).encode(codec, writer) + Ipld::from((*self).clone()).encode(codec, writer) } } -// impl> From> for Ipld { -// fn from(signature: Signature) -> Self { -// match signature { -// Signature::Solo(sig) => sig.into(), -// // Signature::Batch { -// // signature, -// // merkle_proof, -// // } => Ipld::List(merkle_proof.into_iter().map(|p| p.into()).collect()), -// } -// } -// } +impl From> for Ipld { + fn from(signature: Signature) -> Self { + match signature { + Signature::Solo(sig) => sig.to_vec().into(), + // Signature::Batch { + // signature, + // merkle_proof, + // } => Ipld::List(merkle_proof.into_iter().map(|p| p.into()).collect()), + } + } +} impl + Capsule + Into, DID: Did> From> for Ipld { fn from(Envelope { signature, payload }: Envelope) -> Self { From 70ff160861c17d65db74ee98d02a346a4022bda4 Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Fri, 16 Feb 2024 16:59:56 -0800 Subject: [PATCH 082/188] Fix linter warnings, starting to remove commented out sections --- src/ability/arguments/named.rs | 4 +- src/ability/crud/any.rs | 2 - src/ability/crud/create.rs | 26 +- src/ability/crud/destroy.rs | 15 +- src/ability/crud/read.rs | 13 +- src/ability/crud/update.rs | 13 +- src/ability/dynamic.rs | 4 +- src/ability/msg.rs | 6 +- src/ability/msg/receive.rs | 15 +- src/ability/preset.rs | 13 +- src/agent.rs | 155 ---- src/builder.rs | 307 -------- src/delegation.rs | 2 +- src/delegation/agent.rs | 3 +- src/delegation/error.rs | 17 +- src/delegation/payload.rs | 10 +- src/delegation/store.rs | 2 - src/did.rs | 49 +- src/did_verifier.rs | 105 --- src/did_verifier/did_key.rs | 271 ------- src/error.rs | 49 -- src/invocation.rs | 5 +- src/invocation/agent.rs | 9 +- src/invocation/payload.rs | 3 +- src/invocation/promise.rs | 2 +- src/invocation/promise/any.rs | 4 +- src/invocation/store.rs | 10 +- src/ipld/enriched.rs | 1 - src/ipld/promised.rs | 10 +- src/lib.rs | 108 +-- src/plugins.rs | 251 ------- src/plugins/ucan.rs | 392 ---------- src/plugins/wnfs.rs | 389 ---------- src/proof/internal.rs | 3 +- src/proof/parentless.rs | 4 +- src/proof/prove.rs | 6 +- src/prove/traits/internal/checker.rs | 1 - src/reader.rs | 39 +- src/receipt.rs | 4 +- src/semantics/ability.rs | 54 -- src/semantics/caveat.rs | 125 --- src/semantics/mod.rs | 5 - src/semantics/resource.rs | 27 - src/signature.rs | 3 +- src/store.rs | 92 --- src/test_utils/mod.rs | 5 - src/test_utils/rvg.rs | 63 -- src/time.rs | 6 + src/ucan.rs | 1044 -------------------------- src/wasm.rs | 310 -------- 50 files changed, 127 insertions(+), 3929 deletions(-) delete mode 100644 src/agent.rs delete mode 100644 src/builder.rs delete mode 100644 src/did_verifier.rs delete mode 100644 src/did_verifier/did_key.rs delete mode 100644 src/error.rs delete mode 100644 src/plugins.rs delete mode 100644 src/plugins/ucan.rs delete mode 100644 src/plugins/wnfs.rs delete mode 100644 src/prove/traits/internal/checker.rs delete mode 100644 src/semantics/ability.rs delete mode 100644 src/semantics/caveat.rs delete mode 100644 src/semantics/mod.rs delete mode 100644 src/semantics/resource.rs delete mode 100644 src/store.rs delete mode 100644 src/test_utils/mod.rs delete mode 100644 src/test_utils/rvg.rs delete mode 100644 src/ucan.rs delete mode 100644 src/wasm.rs diff --git a/src/ability/arguments/named.rs b/src/ability/arguments/named.rs index 1cf45018..2e467503 100644 --- a/src/ability/arguments/named.rs +++ b/src/ability/arguments/named.rs @@ -1,5 +1,5 @@ -use crate::{invocation::promise, ipld}; -use libipld_core::{error::SerdeError, ipld::Ipld, serde as ipld_serde}; +use crate::ipld; +use libipld_core::ipld::Ipld; use serde::{Deserialize, Serialize}; use std::collections::BTreeMap; use thiserror::Error; diff --git a/src/ability/crud/any.rs b/src/ability/crud/any.rs index d28d867a..077d1d70 100644 --- a/src/ability/crud/any.rs +++ b/src/ability/crud/any.rs @@ -60,8 +60,6 @@ pub struct Any { pub path: Option, } -use crate::ability::command::ParseAbility; - impl Command for Any { const COMMAND: &'static str = "crud/*"; } diff --git a/src/ability/crud/create.rs b/src/ability/crud/create.rs index dff88b40..622cb06a 100644 --- a/src/ability/crud/create.rs +++ b/src/ability/crud/create.rs @@ -1,18 +1,15 @@ //! Create new resources. -use super::{error::ProofError, parents::MutableParents}; +use super::parents::MutableParents; use crate::{ ability::{arguments, command::Command}, delegation::Delegable, invocation::{promise, promise::Resolves, Resolvable}, ipld, - proof::{ - checkable::Checkable, error::OptionalFieldError, parentful::Parentful, - parents::CheckParents, same::CheckSame, util::check_optional, - }, + proof::{checkable::Checkable, parentful::Parentful, parents::CheckParents, same::CheckSame}, }; -use libipld_core::{error::SerdeError, ipld::Ipld, serde as ipld_serde}; -use serde::{de::DeserializeOwned, Deserialize, Serialize}; -use std::{collections::BTreeMap, path::PathBuf}; +use libipld_core::ipld::Ipld; +use serde::Serialize; +use std::path::PathBuf; // FIXME deserialize instance @@ -255,19 +252,6 @@ impl From for arguments::Named { } } -// impl From> for Promised { -// fn from(source: arguments::Named) -> Self { -// let path = source -// .get("path") -// .map(|ipld| ipld.clone().try_into().unwrap()); -// -// let args = source -// .get("args") -// .map(|ipld| ipld.clone().try_into().unwrap()); -// Promised { path, args } -// } -// } - impl From for Promised { fn from(r: Ready) -> Promised { Promised { diff --git a/src/ability/crud/destroy.rs b/src/ability/crud/destroy.rs index 68fd7b39..ec487632 100644 --- a/src/ability/crud/destroy.rs +++ b/src/ability/crud/destroy.rs @@ -1,18 +1,15 @@ //! Destroy a resource. -use super::{error::ProofError, parents::MutableParents}; +use super::parents::MutableParents; use crate::{ ability::{arguments, command::Command}, delegation::Delegable, - invocation::{promise, promise::Resolves, Resolvable}, + invocation::{promise, Resolvable}, ipld, - proof::{ - checkable::Checkable, error::OptionalFieldError, parentful::Parentful, - parents::CheckParents, same::CheckSame, util::check_optional, - }, + proof::{checkable::Checkable, parentful::Parentful, parents::CheckParents, same::CheckSame}, }; -use libipld_core::{error::SerdeError, ipld::Ipld, serde as ipld_serde}; -use serde::{de::DeserializeOwned, Deserialize, Serialize}; -use std::{collections::BTreeMap, path::PathBuf}; +use libipld_core::ipld::Ipld; +use serde::Serialize; +use std::path::PathBuf; // FIXME deserialize instance diff --git a/src/ability/crud/read.rs b/src/ability/crud/read.rs index 555d7cbc..6c2ce833 100644 --- a/src/ability/crud/read.rs +++ b/src/ability/crud/read.rs @@ -1,19 +1,16 @@ //! Read from a resource. -use super::{any as crud, error::ProofError, parents::MutableParents}; +use super::any as crud; use crate::{ ability::{arguments, command::Command}, delegation::Delegable, invocation::{promise, promise::Resolves, Resolvable}, ipld, - proof::{ - checkable::Checkable, error::OptionalFieldError, parentful::Parentful, - parents::CheckParents, same::CheckSame, util::check_optional, - }, + proof::{checkable::Checkable, parentful::Parentful, parents::CheckParents, same::CheckSame}, }; -use libipld_core::{error::SerdeError, ipld::Ipld, serde as ipld_serde}; -use serde::{de::DeserializeOwned, Deserialize, Serialize}; -use std::{collections::BTreeMap, path::PathBuf}; +use libipld_core::ipld::Ipld; +use serde::Serialize; +use std::path::PathBuf; // FIXME deserialize instance diff --git a/src/ability/crud/update.rs b/src/ability/crud/update.rs index 5ce08700..fdb81752 100644 --- a/src/ability/crud/update.rs +++ b/src/ability/crud/update.rs @@ -1,17 +1,14 @@ //! Update existing resources. -use super::{error::ProofError, parents::MutableParents}; +use super::parents::MutableParents; use crate::{ ability::{arguments, command::Command}, delegation::Delegable, invocation::{promise, promise::Resolves, Resolvable}, ipld, - proof::{ - checkable::Checkable, error::OptionalFieldError, parentful::Parentful, - parents::CheckParents, same::CheckSame, util::check_optional, - }, + proof::{checkable::Checkable, parentful::Parentful, parents::CheckParents, same::CheckSame}, }; -use libipld_core::{error::SerdeError, ipld::Ipld, serde as ipld_serde}; -use serde::{de::DeserializeOwned, Deserialize, Serialize}; +use libipld_core::ipld::Ipld; +use serde::Serialize; use std::{collections::BTreeMap, path::PathBuf}; // FIXME deserialize instance @@ -182,7 +179,7 @@ impl TryFrom for Ready { type Error = (); // FIXME fn try_from(ipld: Ipld) -> Result { - if let Ipld::Map(mut map) = ipld { + if let Ipld::Map(map) = ipld { if map.len() > 2 { return Err(()); // FIXME } diff --git a/src/ability/dynamic.rs b/src/ability/dynamic.rs index 64e7af1e..d28b4610 100644 --- a/src/ability/dynamic.rs +++ b/src/ability/dynamic.rs @@ -4,10 +4,10 @@ use super::{ arguments, command::{ParseAbility, ToCommand}, }; -use crate::{ipld, proof::same::CheckSame}; +use crate::proof::same::CheckSame; use libipld_core::{error::SerdeError, ipld::Ipld, serde as ipld_serde}; use serde::{Deserialize, Serialize}; -use std::{collections::BTreeMap, convert::Infallible, fmt::Debug}; +use std::{convert::Infallible, fmt::Debug}; #[cfg(target_arch = "wasm32")] use wasm_bindgen::prelude::*; diff --git a/src/ability/msg.rs b/src/ability/msg.rs index e0ff7556..01e1ee0a 100644 --- a/src/ability/msg.rs +++ b/src/ability/msg.rs @@ -108,10 +108,10 @@ impl CheckParents for Builder { type Parents = Any; type ParentError = (); - fn check_parent(&self, proof: &Self::Parents) -> Result<(), Self::ParentError> { + fn check_parent(&self, proof: &Any) -> Result<(), Self::ParentError> { match (self, proof) { - (Builder::Send(this), Any) => this.check_parent(&Any), - (Builder::Receive(this), Any) => this.check_parent(&Any), + (Builder::Send(this), any) => this.check_parent(&any), + (Builder::Receive(this), any) => this.check_parent(&any), } } } diff --git a/src/ability/msg/receive.rs b/src/ability/msg/receive.rs index 05701fb2..1c152c9f 100644 --- a/src/ability/msg/receive.rs +++ b/src/ability/msg/receive.rs @@ -82,8 +82,15 @@ impl CheckParents for Receive { type ParentError = ::Error; fn check_parent(&self, proof: &Self::Parents) -> Result<(), Self::ParentError> { - // self.from.check_same(&proof.from).map_err(|_| ()) - todo!() // FIXME + if let Some(from) = &self.from { + if let Some(proof_from) = &proof.from { + if from != &url::Newtype(proof_from.clone()) { + return Err(()); + } + } + } + + Ok(()) } } @@ -131,11 +138,11 @@ impl Resolvable for Receive { fn try_resolve(p: Promised) -> Result { match &p.from { None => Ok(Receive { from: None }), - Some(promise::Resolves::Ok(promiseOk)) => match promiseOk.clone().try_resolve() { + Some(promise::Resolves::Ok(promise_ok)) => match promise_ok.clone().try_resolve() { Ok(from) => Ok(Receive { from }), Err(_from) => Err(Promised { from: p.from }), }, - Some(promise::Resolves::Err(promiseErr)) => match promiseErr.clone().try_resolve() { + Some(promise::Resolves::Err(promise_err)) => match promise_err.clone().try_resolve() { Ok(from) => Ok(Receive { from }), Err(_from) => Err(Promised { from: p.from }), }, diff --git a/src/ability/preset.rs b/src/ability/preset.rs index 6bf60a75..48ebb30b 100644 --- a/src/ability/preset.rs +++ b/src/ability/preset.rs @@ -1,18 +1,11 @@ use super::{crud, msg, wasm}; use crate::{ - ability::{ - arguments, - command::{Command, ParseAbility}, - }, + ability::{arguments, command::ParseAbility}, delegation::Delegable, - invocation::{promise, Resolvable}, - proof::{ - checkable::Checkable, parentful::Parentful, parentless::NoParents, parents::CheckParents, - same::CheckSame, - }, + invocation::Resolvable, + proof::{checkable::Checkable, parentful::Parentful, parents::CheckParents, same::CheckSame}, }; use libipld_core::ipld::Ipld; -use serde::{Deserialize, Serialize}; #[derive(Debug, Clone, PartialEq)] //, Serialize, Deserialize)] pub enum Ready { diff --git a/src/agent.rs b/src/agent.rs deleted file mode 100644 index 7192f3bd..00000000 --- a/src/agent.rs +++ /dev/null @@ -1,155 +0,0 @@ -use crate::{ - delegation, - delegation::{condition::Condition, Delegable, Delegation}, - did::Did, - invocation, - nonce::Nonce, - proof::checkable::Checkable, - receipt, - receipt::Responds, - time::JsTime, -}; -use libipld_core::ipld::Ipld; -use nonempty::NonEmpty; -use std::{collections::BTreeMap, marker::PhantomData}; -use web_time::SystemTime; - -// FIXME move proofs to under delegation? - -#[derive(Debug, Clone, PartialEq)] -pub struct Agent< - T: Delegable + Responds, - C: Condition, - S: delegation::store::Store - + invocation::store::Store - + receipt::store::Store, -> { - pub did: Did, - // pub key: signature::Key, - pub store: S, - pub _phantom: PhantomData<(T, C)>, -} - -impl< - T: Delegable + Responds, - C: Condition + Clone, - S: delegation::store::Store - + invocation::store::Store - + receipt::store::Store, - > Agent -{ - fn new(did: Did, store: S) -> Self { - Self { - did, - store, - _phantom: PhantomData, - } - } - - pub fn delegate( - &self, - audience: Did, - subject: Did, - ability_builder: T::Builder, - new_conditions: Vec, - metadata: BTreeMap, - expiration: JsTime, - not_before: Option, - ) -> Result, ()> { - // FIXME check if possible in store first; - - let conditions = if subject == self.did { - new_conditions - } else { - let mut conds = self - .store - .get_chain(&self.did, &subject, &ability_builder, SystemTime::now()) - .map_err(|_| ())? // FIXME - .first() - .1 - .payload - .conditions; - - let mut new = new_conditions; - conds.append(&mut new); - conds - }; - - let mut salt = self.did.clone().to_string().into_bytes(); - - let payload = delegation::Payload { - issuer: self.did.clone(), - audience, - subject, - ability_builder, - conditions, - metadata, - nonce: Nonce::generate_12(&mut salt), - expiration: expiration.into(), - not_before: not_before.map(Into::into), - }; - - Ok(self.sign_delegation(payload)) - } - - pub fn sign_delegation( - &self, - payload: delegation::Payload, - ) -> delegation::Delegation { - // FIXME check if possible in store first; - let signature = todo!(); // self.key.sign(payload); - Delegation { payload, signature } - } - - pub fn receive_delegation() {} -} - -// impl Agent { -// } -// -// -// -// -// -// pub fn delegate(&self, payload: Payload) -> Delegation { -// let signature = self.key.sign(payload); -// signature::Envelope::new(payload, signature) -// } - -// pub fn invoke( -// &self, -// delegation: Delegation, -// proof_chain: Vec>, // FIXME T must also accept Self and * -// ) -> () -// where -// T::Parents: Delegable, -// { -// todo!() -// } - -// pub fn try_invoke(&self, ability: A) { -// todo!() -// } - -// pub fn revoke( -// &self, -// delegation: Delegation, -// ) -> () -// // where -// // T::Parents: Delegable, -// { -// todo!() -// } - -// pub fn receive_delegation( -// &self, -// delegation: Delegation, -// ) -> () { -// todo!() -// } - -// pub fn receive_invocation(&self, invocation: Invocation) -> () { -// todo!() -// } - -// pub fn check(&self, delegation: &Delegation) -> () // FIXME Includes cache diff --git a/src/builder.rs b/src/builder.rs deleted file mode 100644 index ef062361..00000000 --- a/src/builder.rs +++ /dev/null @@ -1,307 +0,0 @@ -//! A builder for creating UCANs - -use async_signature::AsyncSigner; -use cid::multihash; -use serde::{de::DeserializeOwned, Serialize}; -use signature::Signer; - -use crate::{ - capability::{Capabilities, Capability, CapabilityParser, DefaultCapabilityParser}, - crypto::{JWSSignature, SignerDid}, - error::Error, - time, - ucan::{Ucan, UcanHeader, UcanPayload, UCAN_VERSION}, - CidString, DefaultFact, -}; - -/// A builder for creating UCANs -#[derive(Debug, Clone)] -pub struct UcanBuilder { - version: Option, - audience: Option, - nonce: Option, - capabilities: Capabilities, - lifetime: Option, - expiration: Option, - not_before: Option, - facts: Option, - proofs: Option>, -} - -impl Default for UcanBuilder { - fn default() -> Self { - Self { - version: Default::default(), - audience: Default::default(), - nonce: Default::default(), - capabilities: Default::default(), - lifetime: Default::default(), - expiration: Default::default(), - not_before: Default::default(), - facts: Default::default(), - proofs: Default::default(), - } - } -} - -impl UcanBuilder -where - F: Clone + Serialize, - C: CapabilityParser, -{ - /// Set the UCAN version - pub fn version(mut self, version: &str) -> Self { - self.version = Some(version.to_string()); - self - } - - /// Set the audience of the UCAN - pub fn for_audience(mut self, audience: impl AsRef) -> Self { - self.audience = Some(audience.as_ref().to_string()); - self - } - - /// Set the nonce of the UCAN - pub fn with_nonce(mut self, nonce: impl AsRef) -> Self { - self.nonce = Some(nonce.as_ref().to_string()); - self - } - - /// Set the lifetime of the UCAN - pub fn with_lifetime(mut self, seconds: u64) -> Self { - self.lifetime = Some(seconds); - self - } - - /// Set the expiration of the UCAN - pub fn with_expiration(mut self, timestamp: u64) -> Self { - self.expiration = Some(timestamp); - self - } - - /// Set the not before of the UCAN - pub fn not_before(mut self, timestamp: u64) -> Self { - self.not_before = Some(timestamp); - self - } - - /// Set the fact of the UCAN - pub fn with_fact(mut self, fact: F) -> Self { - self.facts = Some(fact); - self - } - - /// Add a witness to the proofs of the UCAN - pub fn witnessed_by( - mut self, - authority: &Ucan, - hasher: Option, - ) -> Self - where - F2: Clone + DeserializeOwned, - C2: CapabilityParser, - { - match authority.to_cid(hasher) { - Ok(cid) => { - self.proofs - .get_or_insert(Default::default()) - .push(CidString(cid)); - } - Err(e) => panic!("Failed to add authority: {}", e), - } - - self - } - - /// Claim a capability for the UCAN - pub fn claiming_capability(mut self, capability: Capability) -> Self { - self.capabilities.push(capability); - self - } - - /// Claim multiple capabilities for the UCAN - pub fn claiming_capabilities(mut self, capabilities: &[Capability]) -> Self { - self.capabilities.extend_from_slice(capabilities); - self - } - - /// Sign the UCAN with the given signer - pub fn sign(self, signer: &S) -> Result, Error> - where - S: Signer + SignerDid, - K: JWSSignature, - { - let version = self.version.unwrap_or_else(|| UCAN_VERSION.to_string()); - - let issuer = signer.did().map_err(|e| Error::SigningError { - msg: format!("failed to construct DID, {}", e), - })?; - - let Some(audience) = self.audience else { - return Err(Error::SigningError { - msg: "an audience is required".to_string(), - }); - }; - - let header = jose_b64::serde::Json::new(UcanHeader { - alg: K::ALGORITHM.to_string(), - typ: "JWT".to_string(), - }) - .map_err(|e| Error::InternalUcanError { msg: e.to_string() })?; - - let expiration = match (self.expiration, self.lifetime) { - (None, None) => None, - (None, Some(lifetime)) => Some(time::now() + lifetime), - (Some(expiration), None) => Some(expiration), - (Some(_), Some(_)) => { - return Err(Error::SigningError { - msg: "only one of expiration or lifetime may be set".to_string(), - }) - } - }; - - let payload = jose_b64::serde::Json::new(UcanPayload { - ucv: version, - iss: issuer, - aud: audience, - exp: expiration, - nbf: self.not_before, - nnc: self.nonce, - cap: self.capabilities, - fct: self.facts, - prf: self.proofs, - }) - .map_err(|e| Error::InternalUcanError { msg: e.to_string() })?; - - let header_b64 = serde_json::to_value(&header) - .map_err(|e| Error::InternalUcanError { msg: e.to_string() })?; - - let payload_b64 = serde_json::to_value(&payload) - .map_err(|e| Error::InternalUcanError { msg: e.to_string() })?; - - let signed_data = format!( - "{}.{}", - header_b64.as_str().ok_or(Error::InternalUcanError { - msg: "Expected base64 encoding of header".to_string(), - })?, - payload_b64.as_str().ok_or(Error::InternalUcanError { - msg: "Expected base64 encoding of payload".to_string(), - })?, - ); - - let signature = signer.sign(signed_data.as_bytes()).to_vec().into(); - - Ok(Ucan:: { - header, - payload, - signature, - }) - } - - /// Sign the UCAN with the given async signer - pub async fn sign_async(self, signer: &S) -> Result, Error> - where - S: AsyncSigner + SignerDid, - K: JWSSignature + 'static, - { - let version = self.version.unwrap_or_else(|| UCAN_VERSION.to_string()); - - let issuer = signer.did().map_err(|e| Error::SigningError { - msg: format!("failed to construct DID, {}", e), - })?; - - let Some(audience) = self.audience else { - return Err(Error::SigningError { - msg: "an audience is required".to_string(), - }); - }; - - let header = jose_b64::serde::Json::new(UcanHeader { - alg: K::ALGORITHM.to_string(), - typ: "JWT".to_string(), - }) - .map_err(|e| Error::InternalUcanError { msg: e.to_string() })?; - - let expiration = match (self.expiration, self.lifetime) { - (None, None) => None, - (None, Some(lifetime)) => Some(time::now() + lifetime), - (Some(expiration), None) => Some(expiration), - (Some(_), Some(_)) => { - return Err(Error::SigningError { - msg: "only one of expiration or lifetime may be set".to_string(), - }) - } - }; - - let payload = jose_b64::serde::Json::new(UcanPayload { - ucv: version, - iss: issuer, - aud: audience, - exp: expiration, - nbf: self.not_before, - nnc: self.nonce, - cap: self.capabilities, - fct: self.facts, - prf: self.proofs, - }) - .map_err(|e| Error::InternalUcanError { msg: e.to_string() })?; - - let header_b64 = serde_json::to_value(&header) - .map_err(|e| Error::InternalUcanError { msg: e.to_string() })?; - - let payload_b64 = serde_json::to_value(&payload) - .map_err(|e| Error::InternalUcanError { msg: e.to_string() })?; - - let signed_data = format!( - "{}.{}", - header_b64.as_str().ok_or(Error::InternalUcanError { - msg: "Expected base64 encoding of header".to_string(), - })?, - payload_b64.as_str().ok_or(Error::InternalUcanError { - msg: "Expected base64 encoding of payload".to_string(), - })?, - ); - - let signature = signer - .sign_async(signed_data.as_bytes()) - .await - .map_err(|e| Error::SigningError { msg: e.to_string() })? - .to_vec() - .into(); - - Ok(Ucan:: { - header, - payload, - signature, - }) - } -} - -#[cfg(test)] -mod tests { - use signature::rand_core; - use std::str::FromStr; - - use crate::did_verifier::DidVerifierMap; - - use super::*; - - #[test] - fn test_round_trip_validate() -> Result<(), anyhow::Error> { - let did_verifier_map = DidVerifierMap::default(); - - let iss_key = ed25519_dalek::SigningKey::generate(&mut rand_core::OsRng); - let aud_key = ed25519_dalek::SigningKey::generate(&mut rand_core::OsRng); - - let ucan: Ucan = UcanBuilder::default() - .for_audience(aud_key.did()?) - .sign(&iss_key)?; - - let token = ucan.encode()?; - let decoded: Ucan = Ucan::from_str(&token)?; - - assert!(decoded.validate(0, &did_verifier_map).is_ok()); - - Ok(()) - } -} diff --git a/src/delegation.rs b/src/delegation.rs index f248ef26..eea99dd2 100644 --- a/src/delegation.rs +++ b/src/delegation.rs @@ -29,7 +29,7 @@ use web_time::SystemTime; /// /// # Examples /// FIXME -pub type Delegation = signature::Envelope, DID>; +pub type Delegation = signature::Envelope, DID>; pub type Preset = Delegation; diff --git a/src/delegation/agent.rs b/src/delegation/agent.rs index 87769369..23019ab0 100644 --- a/src/delegation/agent.rs +++ b/src/delegation/agent.rs @@ -5,6 +5,7 @@ use std::{collections::BTreeMap, marker::PhantomData}; use thiserror::Error; use web_time::SystemTime; +#[derive(Debug)] pub struct Agent<'a, B: Checkable, C: Condition, DID: Did, S: Store> { pub did: &'a DID, pub store: &'a mut S, @@ -21,7 +22,7 @@ impl< B: Checkable + Clone, C: Condition + Clone, DID: Did + ToString + Clone, - S: Store, + S: Store + Clone, > Agent<'a, B, C, DID, S> { pub fn new(did: &'a DID, signer: &'a ::Signer, store: &'a mut S) -> Self { diff --git a/src/delegation/error.rs b/src/delegation/error.rs index 1ff31a12..21bafe1e 100644 --- a/src/delegation/error.rs +++ b/src/delegation/error.rs @@ -1,3 +1,5 @@ +// FIXME rename this is not for the sign envelope +#[derive(Debug, Clone, PartialEq, Eq)] pub enum EnvelopeError { InvalidSubject, MisalignedIssAud, @@ -6,24 +8,13 @@ pub enum EnvelopeError { } // FIXME Error, etc +#[derive(Debug, Clone, PartialEq, Eq)] pub enum DelegationError { Envelope(EnvelopeError), FailedCondition, // FIXME add context? - SemanticError(Semantic), // // FIXME these are duplicated in Outcome - // // - // /// An error in the command chain. - // CommandEscelation, - - // /// An error in the argument chain. - // ArgumentEscelation(ArgErr), - - // /// An error in the proof chain. - // InvalidProofChain(ChainErr), - - // /// An error in the parents. - // InvalidParents(ParentErr), + SemanticError(Semantic), } impl From for DelegationError { diff --git a/src/delegation/payload.rs b/src/delegation/payload.rs index 5e83b655..0047767c 100644 --- a/src/delegation/payload.rs +++ b/src/delegation/payload.rs @@ -8,7 +8,6 @@ use crate::{ command::{Command, ParseAbility, ToCommand}, }, capsule::Capsule, - did, did::Did, nonce::Nonce, proof::{ @@ -365,15 +364,16 @@ impl From> for Ipld { } } -impl Payload { - pub fn check<'a>( - &'a self, +impl>, C: Condition, DID: Did + Clone> + Payload +{ + pub fn check( + &self, proofs: Vec>, now: &SystemTime, ) -> Result<(), DelegationError<::Error>> where T::Hierarchy: Clone + Into>, - T: Clone + Checkable + Prove + Into>, { let start: Acc = Acc { issuer: self.issuer.clone(), diff --git a/src/delegation/store.rs b/src/delegation/store.rs index 91502f10..184e2f96 100644 --- a/src/delegation/store.rs +++ b/src/delegation/store.rs @@ -8,8 +8,6 @@ use libipld_core::{cid::Cid, ipld::Ipld}; use nonempty::NonEmpty; use std::{ collections::{BTreeMap, BTreeSet}, - convert::Infallible, - fmt, ops::ControlFlow, }; use web_time::SystemTime; diff --git a/src/did.rs b/src/did.rs index 30784caf..493cc901 100644 --- a/src/did.rs +++ b/src/did.rs @@ -10,41 +10,42 @@ use serde::{Deserialize, Serialize}; use std::{fmt, string::ToString}; use thiserror::Error; -#[cfg(feature = "eddsa")] -use ed25519_dalek; - -#[cfg(feature = "es256")] -use p256; - -#[cfg(feature = "es256k")] -use k256; - -#[cfg(feature = "es384")] -use p384; - -#[cfg(feature = "es512")] -use crate::crypto::p521; - -#[cfg(feature = "rs256")] -use crate::crypto::rs256; - -#[cfg(feature = "rs512")] -use crate::crypto::rs512; - -#[cfg(feature = "bls")] -use blst; +// #[cfg(feature = "eddsa")] +// use ed25519_dalek; +// +// #[cfg(feature = "es256")] +// use p256; +// +// #[cfg(feature = "es256k")] +// use k256; +// +// #[cfg(feature = "es384")] +// use p384; +// +// #[cfg(feature = "es512")] +// use crate::crypto::p521; +// +// #[cfg(feature = "rs256")] +// use crate::crypto::rs256; +// +// #[cfg(feature = "rs512")] +// use crate::crypto::rs512; +// +// #[cfg(feature = "bls")] +// use blst; pub trait Did: PartialEq + TryFrom + Into + signature::Verifier { type Signature: signature::SignatureEncoding + PartialEq + fmt::Debug; - type Signer: signature::Signer; + type Signer: signature::Signer + fmt::Debug; } // impl Did for ed25519_dalek::VerifyingKey {} // //impl Did for key::Verifier {} // +#[derive(Debug, Clone, PartialEq, Eq)] pub enum Preset { Key(key::Verifier), // Dns(did_url::DID), diff --git a/src/did_verifier.rs b/src/did_verifier.rs deleted file mode 100644 index 14a2f47a..00000000 --- a/src/did_verifier.rs +++ /dev/null @@ -1,105 +0,0 @@ -// //! DID verifier methods - -use core::fmt; -use std::collections::HashMap; - -use crate::error::Error; - -#[cfg(feature = "did-key")] -pub mod did_key; - -/// A map from did method to verifier -#[derive(Debug)] -pub struct DidVerifierMap { - map: HashMap>, -} - -impl Default for DidVerifierMap { - fn default() -> Self { - #[allow(unused_mut)] - let mut did_verifier_map = Self { - map: HashMap::new(), - }; - - #[cfg(feature = "did-key")] - did_verifier_map.register(did_key::DidKeyVerifier::default()); - - did_verifier_map - } -} - -impl DidVerifierMap { - /// Register a verifier - pub fn register(&mut self, verifier: V) -> &mut Self - where - V: DidVerifier + 'static, - { - self.map - .insert(verifier.method().to_string(), Box::new(verifier)); - self - } - - /// Register a verifier that's already boxed - pub fn register_box(&mut self, verifier: Box) -> &mut Self { - self.map.insert(verifier.method().to_string(), verifier); - self - } - - /// Verify a signature using the registered verifier for the given method - pub fn verify( - &self, - method: &str, - identifier: &str, - payload: &[u8], - signature: &[u8], - ) -> Result<(), Error> { - self.map - .get(method) - .ok_or_else(|| Error::VerifyingError { - msg: format!("Unrecognized DID method, {}", method), - })? - .verify(identifier, payload, signature) - .map_err(|e| Error::VerifyingError { msg: e.to_string() }) - } -} - -impl FromIterator> for DidVerifierMap { - fn from_iter>>(iter: T) -> Self { - let mut map = Self::default(); - for verifier in iter { - map.register_box(verifier); - } - - map - } -} - -impl Extend> for DidVerifierMap { - fn extend>>(&mut self, iter: T) { - for verifier in iter { - self.register_box(verifier); - } - } -} - -/// A trait for implementing DID method verification -pub trait DidVerifier { - /// The DID method for this verifier - fn method(&self) -> &'static str; - - /// Verify a signature - fn verify( - &self, - identifier: &str, - payload: &[u8], - signature: &[u8], - ) -> Result<(), anyhow::Error>; -} - -impl fmt::Debug for dyn DidVerifier { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_struct("DidVerifier") - .field("method", &self.method()) - .finish() - } -} diff --git a/src/did_verifier/did_key.rs b/src/did_verifier/did_key.rs deleted file mode 100644 index 426df184..00000000 --- a/src/did_verifier/did_key.rs +++ /dev/null @@ -1,271 +0,0 @@ -//! did:key method verifier - -#[cfg(feature = "eddsa-verifier")] -use crate::crypto::eddsa::eddsa_verifier; -#[cfg(feature = "es256-verifier")] -use crate::crypto::es256::es256_verifier; -#[cfg(feature = "es256k-verifier")] -use crate::crypto::es256k::es256k_verifier; -#[cfg(feature = "es384-verifier")] -use crate::crypto::es384::es384_verifier; -#[cfg(feature = "ps256-verifier")] -use crate::crypto::ps256::ps256_verifier; -#[cfg(feature = "rs256-verifier")] -use crate::crypto::rs256::rs256_verifier; - -use core::fmt; -use std::{any::TypeId, collections::HashMap}; - -use anyhow::anyhow; -use multibase::Base; - -use super::DidVerifier; - -/// A closure for verifying a signature -pub type SignatureVerifier = dyn Fn(&[u8], &[u8], &[u8]) -> Result<(), anyhow::Error>; - -/// did:key method verifier -pub struct DidKeyVerifier { - /// map from type id of signature to verifier function - verifier_map: HashMap>, -} - -impl Default for DidKeyVerifier { - fn default() -> Self { - #[allow(unused_mut)] - let mut did_key_verifier = Self { - verifier_map: HashMap::new(), - }; - - #[cfg(feature = "eddsa-verifier")] - did_key_verifier.set::(eddsa_verifier); - - #[cfg(feature = "es256-verifier")] - did_key_verifier.set::(es256_verifier); - - #[cfg(feature = "es256k-verifier")] - did_key_verifier.set::(es256k_verifier); - - #[cfg(feature = "es384-verifier")] - did_key_verifier.set::(es384_verifier); - - #[cfg(feature = "ps256-verifier")] - did_key_verifier.set::(ps256_verifier); - - #[cfg(feature = "rs256-verifier")] - did_key_verifier.set::(rs256_verifier); - - did_key_verifier - } -} - -impl DidKeyVerifier { - /// set verifier function for type `T` - pub fn set(&mut self, f: F) -> &mut Self - where - T: 'static, - F: Fn(&[u8], &[u8], &[u8]) -> Result<(), anyhow::Error> + 'static, - { - self.verifier_map.insert(TypeId::of::(), Box::new(f)); - self - } - - /// check if verifier function for type `T` is set - pub fn has(&self) -> bool - where - T: 'static, - { - self.verifier_map.contains_key(&TypeId::of::()) - } -} - -impl DidVerifier for DidKeyVerifier { - fn method(&self) -> &'static str { - "key" - } - - fn verify( - &self, - identifier: &str, - payload: &[u8], - signature: &[u8], - ) -> Result<(), anyhow::Error> { - let (base, data) = multibase::decode(identifier).map_err(|e| anyhow!(e))?; - - let Base::Base58Btc = base else { - return Err(anyhow!("expected base58btc, got {:?}", base)); - }; - - let (multicodec, public_key) = - unsigned_varint::decode::u128(&data).map_err(|e| anyhow!(e))?; - - let multicodec_pub_key = MulticodecPubKey::try_from(multicodec)?; - - multicodec_pub_key.validate_pub_key_len(public_key)?; - - #[allow(unreachable_patterns)] - let verifier = match multicodec_pub_key { - #[cfg(feature = "es256k")] - MulticodecPubKey::Secp256k1Compressed => self - .verifier_map - .get(&TypeId::of::()), - #[cfg(feature = "eddsa")] - MulticodecPubKey::X25519 => return Err(anyhow!("x25519 not supported for signing")), - #[cfg(feature = "eddsa")] - MulticodecPubKey::Ed25519 => self.verifier_map.get(&TypeId::of::()), - #[cfg(feature = "es256")] - MulticodecPubKey::P256Compressed => self - .verifier_map - .get(&TypeId::of::()), - #[cfg(feature = "es384")] - MulticodecPubKey::P384Compressed => self - .verifier_map - .get(&TypeId::of::()), - #[cfg(feature = "es512")] - MulticodecPubKey::P521Compressed => self - .verifier_map - .get(&TypeId::of::>()), - #[cfg(feature = "rs256")] - MulticodecPubKey::RSAPKCS1 => self - .verifier_map - .get(&TypeId::of::()), - _ => Option::<&Box>::None, - } - .ok_or_else(|| anyhow!("no registered verifier for signature type"))?; - - verifier(public_key, payload, signature) - } -} - -impl fmt::Debug for DidKeyVerifier { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_struct("DidKeyVerifier").finish() - } -} - -/// Multicodec public key -#[derive(Debug)] -pub enum MulticodecPubKey { - /// secp256k1 compressed public key - #[cfg(feature = "es256k")] - Secp256k1Compressed, - /// x25519 public key - #[cfg(feature = "eddsa")] - X25519, - /// ed25519 public key - #[cfg(feature = "eddsa")] - Ed25519, - /// p256 compressed public key - #[cfg(feature = "es256")] - P256Compressed, - /// p384 compressed public key - #[cfg(feature = "es384")] - P384Compressed, - /// p521 compressed public key - #[cfg(feature = "es512")] - P521Compressed, - /// rsa pkcs1 public key - #[cfg(feature = "rs256")] - RSAPKCS1, -} - -impl MulticodecPubKey { - #[allow(unused_variables)] - fn validate_pub_key_len(&self, pub_key: &[u8]) -> Result<(), anyhow::Error> { - #[allow(unreachable_patterns)] - match self { - #[cfg(feature = "es256k")] - MulticodecPubKey::Secp256k1Compressed => { - if pub_key.len() != 33 { - return Err(anyhow!( - "expected 33 bytes for secp256k1 compressed public key, got {}", - pub_key.len() - )); - } - } - #[cfg(feature = "eddsa")] - MulticodecPubKey::X25519 => { - if pub_key.len() != 32 { - return Err(anyhow!( - "expected 32 bytes for x25519 public key, got {}", - pub_key.len() - )); - } - } - #[cfg(feature = "eddsa")] - MulticodecPubKey::Ed25519 => { - if pub_key.len() != 32 { - return Err(anyhow!( - "expected 32 bytes for ed25519 public key, got {}", - pub_key.len() - )); - } - } - #[cfg(feature = "es256")] - MulticodecPubKey::P256Compressed => { - if pub_key.len() != 33 { - return Err(anyhow!( - "expected 33 bytes for p256 compressed public key, got {}", - pub_key.len() - )); - } - } - #[cfg(feature = "es384")] - MulticodecPubKey::P384Compressed => { - if pub_key.len() != 49 { - return Err(anyhow!( - "expected 49 bytes for p384 compressed public key, got {}", - pub_key.len() - )); - } - } - #[cfg(feature = "es512")] - MulticodecPubKey::P521Compressed => { - if pub_key.len() > 67 { - return Err(anyhow!( - "expected <= 67 bytes for p521 compressed public key, got {}", - pub_key.len() - )); - } - } - #[cfg(feature = "rs256")] - MulticodecPubKey::RSAPKCS1 => match pub_key.len() { - 94 | 126 | 162 | 226 | 294 | 422 | 546 => {} - n => { - return Err(anyhow!( - "expected 94, 126, 162, 226, 294, 422, or 546 bytes for RSA PKCS1 public key, got {}", - n - )); - } - }, - _ => return Err(anyhow!("unsupported public key type")), - }; - - #[allow(unreachable_code)] - Ok(()) - } -} - -impl TryFrom for MulticodecPubKey { - type Error = anyhow::Error; - - fn try_from(value: u128) -> Result { - match value { - #[cfg(feature = "es256k")] - 0xe7 => Ok(MulticodecPubKey::Secp256k1Compressed), - #[cfg(feature = "eddsa")] - 0xec => Ok(MulticodecPubKey::X25519), - #[cfg(feature = "eddsa")] - 0xed => Ok(MulticodecPubKey::Ed25519), - #[cfg(feature = "es256")] - 0x1200 => Ok(MulticodecPubKey::P256Compressed), - #[cfg(feature = "es384")] - 0x1201 => Ok(MulticodecPubKey::P384Compressed), - #[cfg(feature = "es512")] - 0x1202 => Ok(MulticodecPubKey::P521Compressed), - #[cfg(feature = "rs256")] - 0x1205 => Ok(MulticodecPubKey::RSAPKCS1), - _ => Err(anyhow!("unsupported multicodec")), - } - } -} diff --git a/src/error.rs b/src/error.rs deleted file mode 100644 index a67fb364..00000000 --- a/src/error.rs +++ /dev/null @@ -1,49 +0,0 @@ -//! Error types for UCAN - -use thiserror::Error; - -/// Error types for UCAN -#[derive(Error, Debug)] -pub enum Error { - /// Parsing errors - #[error("An error occurred while parsing the token: {msg}")] - TokenParseError { - /// Error message - msg: String, - }, - /// Verification errors - #[error("An error occurred while verifying the token: {msg}")] - VerifyingError { - /// Error message - msg: String, - }, - /// Signing errors - #[error("An error occurred while signing the token: {msg}")] - SigningError { - /// Error message - msg: String, - }, - /// Plugin errors - #[error(transparent)] - PluginError(PluginError), - /// Internal errors - #[error("An unexpected error occurred in ucan: {msg}\n\nThis is a bug: please consider filing an issue at https://github.com/ucan-wg/ucan/issues")] - InternalUcanError { - /// Error message - msg: String, - }, -} - -/// Error types for plugins -#[derive(Error, Debug)] -#[error(transparent)] -pub struct PluginError { - #[from] - inner: anyhow::Error, -} - -impl From for Error { - fn from(inner: anyhow::Error) -> Self { - Self::PluginError(PluginError { inner }) - } -} diff --git a/src/invocation.rs b/src/invocation.rs index 522acfc6..3eff3a76 100644 --- a/src/invocation.rs +++ b/src/invocation.rs @@ -19,15 +19,12 @@ use crate::{ability, did, did::Did, signature}; /// `Invocation>`. pub type Invocation = signature::Envelope, DID>; -// FIXME rename -pub type PromisedInvocation = Invocation; - // FIXME use presnet ability, too pub type Preset = Invocation; pub type PresetPromised = Invocation; impl Invocation { - fn map_ability(self, f: impl FnOnce(T) -> T) -> Self { + pub fn map_ability(self, f: impl FnOnce(T) -> T) -> Self { let mut payload = self.payload; payload.ability = f(payload.ability); Self { diff --git a/src/invocation/agent.rs b/src/invocation/agent.rs index 6751bef5..0c96c26a 100644 --- a/src/invocation/agent.rs +++ b/src/invocation/agent.rs @@ -17,9 +17,9 @@ use libipld_core::{ multihash::{Code, MultihashGeneric}, }; use std::{collections::BTreeMap, marker::PhantomData}; -use thiserror::Error; use web_time::SystemTime; +#[derive(Debug)] pub struct Agent< 'a, T: Resolvable + Delegable, @@ -190,7 +190,7 @@ where subject: &DID, cause: Option, cid: Cid, - now: &JsTime, + now: JsTime, // FIXME return type ) -> Result, ()> where @@ -206,7 +206,7 @@ where self.did, &ability.clone().into(), vec![], - &SystemTime::now(), + &now.into(), ) .map_err(|_| ())? .map(|chain| chain.map(|(index_cid, _)| index_cid).into()) @@ -219,7 +219,7 @@ where audience: Some(self.did.clone()), ability, proofs, - cause: None, + cause, metadata: BTreeMap::new(), nonce: Nonce::generate_12(&mut vec![]), expiration: None, @@ -233,6 +233,7 @@ where } } +#[derive(Debug)] pub enum Recipient { You(T), Other(T), diff --git a/src/invocation/payload.rs b/src/invocation/payload.rs index cdfade84..6d89672b 100644 --- a/src/invocation/payload.rs +++ b/src/invocation/payload.rs @@ -11,11 +11,10 @@ use crate::{ }; use anyhow; use libipld_core::{ - cid::{Cid, CidGeneric}, + cid::Cid, codec::{Codec, Encode}, error::SerdeError, ipld::Ipld, - multihash::{Code, MultihashGeneric}, serde as ipld_serde, }; use serde::{Serialize, Serializer}; diff --git a/src/invocation/promise.rs b/src/invocation/promise.rs index d74bf61d..aaa9ad83 100644 --- a/src/invocation/promise.rs +++ b/src/invocation/promise.rs @@ -7,7 +7,7 @@ mod err; mod ok; mod resolves; -pub mod js; +// FIXME pub mod js; pub use any::PromiseAny; pub use err::PromiseErr; diff --git a/src/invocation/promise/any.rs b/src/invocation/promise/any.rs index 81e56c48..e37d1e87 100644 --- a/src/invocation/promise/any.rs +++ b/src/invocation/promise/any.rs @@ -185,7 +185,7 @@ impl TryFrom> for PromiseOk { fn try_from(p_any: PromiseAny) -> Result, Self::Error> { match p_any { PromiseAny::Fulfilled(val) => Ok(PromiseOk::Fulfilled(val)), - PromiseAny::Rejected(err) => Err(()), + PromiseAny::Rejected(_err) => Err(()), PromiseAny::Pending(cid) => Ok(PromiseOk::Pending(cid)), } } @@ -196,7 +196,7 @@ impl TryFrom> for PromiseErr { fn try_from(p_any: PromiseAny) -> Result, Self::Error> { match p_any { - PromiseAny::Fulfilled(val) => Err(()), + PromiseAny::Fulfilled(_val) => Err(()), PromiseAny::Rejected(err) => Ok(PromiseErr::Rejected(err)), PromiseAny::Pending(cid) => Ok(PromiseErr::Pending(cid)), } diff --git a/src/invocation/store.rs b/src/invocation/store.rs index 945aa5a5..e168836e 100644 --- a/src/invocation/store.rs +++ b/src/invocation/store.rs @@ -1,10 +1,7 @@ use super::Invocation; use crate::{did::Did, invocation::Resolvable}; -use libipld_core::{cid::Cid, link::Link}; -use std::{ - collections::{BTreeMap, BTreeSet}, - ops::ControlFlow, -}; +use libipld_core::cid::Cid; +use std::collections::{BTreeMap, BTreeSet}; use thiserror::Error; pub trait Store { @@ -58,6 +55,7 @@ pub trait PromiseIndex { ) -> Result, Self::PromiseIndexError>; } +#[derive(Debug, Clone, PartialEq)] pub struct MemoryPromiseIndex { pub index: BTreeMap>, } @@ -84,7 +82,7 @@ impl PromiseIndex for MemoryPromiseIndex { None => BTreeSet::new(), Some(first) => waiting_on .iter() - .try_fold(BTreeSet::from_iter([first]), |mut acc, cid| { + .try_fold(BTreeSet::from_iter([first]), |acc, cid| { let next = self.index.get(cid).ok_or(())?; let reduced: BTreeSet = acc.intersection(&next).cloned().collect(); diff --git a/src/ipld/enriched.rs b/src/ipld/enriched.rs index 71297e6c..3cb6616d 100644 --- a/src/ipld/enriched.rs +++ b/src/ipld/enriched.rs @@ -1,4 +1,3 @@ -use crate::invocation::Resolvable; use libipld_core::{cid::Cid, ipld::Ipld}; use serde::{Deserialize, Serialize}; use std::collections::BTreeMap; diff --git a/src/ipld/promised.rs b/src/ipld/promised.rs index 83ae2029..322c2db1 100644 --- a/src/ipld/promised.rs +++ b/src/ipld/promised.rs @@ -1,10 +1,6 @@ -use super::enriched::{Enriched, Item}; -use crate::ability::arguments; -use crate::invocation::{ - promise::{self, Promise, PromiseAny, PromiseErr, PromiseOk}, - Resolvable, // FIXME this shoudl be under promise -}; -use libipld_core::{error::SerdeError, ipld::Ipld, serde as ipld_serde}; +use super::enriched::Enriched; +use crate::invocation::promise::{Promise, PromiseAny, PromiseErr, PromiseOk}; +use libipld_core::ipld::Ipld; use serde::{Deserialize, Serialize}; /// A promise to recursively resolve to an [`Ipld`] value. diff --git a/src/lib.rs b/src/lib.rs index dd97bfe2..c9d44ab7 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,5 +1,13 @@ #![cfg_attr(docsrs, feature(doc_cfg))] -#![warn(missing_debug_implementations, missing_docs, rust_2018_idioms)] +#![warn( + missing_debug_implementations, + future_incompatible, + let_underscore, + // FIXME missing_docs, + rust_2021_compatibility, + nonstandard_style, +)] +// FIXME consider removing for Prove #![deny(unreachable_pub)] //! ucan @@ -7,68 +15,11 @@ #[cfg(target_arch = "wasm32")] extern crate alloc; -// use std::str::FromStr; -// -// use cid::{multihash, Cid}; -// use serde::{de, Deserialize, Deserializer, Serialize}; -// -// pub mod builder; -// pub mod capability; -// pub mod crypto; -// pub mod error; -// pub mod plugins; -// pub mod semantics; -// pub mod store; -// pub mod ucan; -// -// #[cfg(target_arch = "wasm32")] -// mod wasm; -// -// #[cfg(target_arch = "wasm32")] -// pub use wasm::*; -// -// #[doc(hidden)] -// #[cfg(not(target_arch = "wasm32"))] -// pub use linkme; -// -// /// The default multihash algorithm used for UCANs -// pub const DEFAULT_MULTIHASH: multihash::Code = multihash::Code::Sha2_256; -// -// /// A decentralized identifier. -// pub type Did = String; -// -// use std::fmt::Debug; - -// FIXME concrete abilitiy types in addition to promised version - -// impl DelegationPayload { -// fn check( -// &self, -// proof: &DelegationPayload + Capability, Cond>, -// now: SystemTime, -// ) -> Result<(), ()> { -// // FIXME heavily WIP -// // FIXME signature -// self.check_time(now).unwrap(); -// self.check_issuer(&proof.audience)?; // FIXME alignment -// self.check_subject(&proof.subject)?; -// self.check_conditions(&proof.conditions)?; -// -// proof.check_expiration(now)?; -// proof.check_not_before(now)?; -// -// self.check_ability(&proof.capability_builder)?; -// Ok(()) -// } -// } - pub mod ability; -pub mod crypto; -// pub mod agent; FIXME put back? pub mod capsule; +pub mod crypto; pub mod delegation; pub mod did; -// pub mod did_verifier; pub mod invocation; pub mod ipld; pub mod nonce; @@ -84,42 +35,3 @@ pub mod url; pub use delegation::Delegation; pub use invocation::Invocation; pub use receipt::Receipt; - -// FIXME consider a fact-system -// /// The empty fact -// #[derive(Debug, Clone, Default, Serialize, Deserialize)] -// pub struct EmptyFact {} -// -// /// The default fact -// pub type DefaultFact = EmptyFact; -// -// /// A newtype around Cid that (de)serializes as a string -// #[derive(Debug, Clone)] -// pub struct CidString(pub(crate) Cid); -// -// impl Serialize for CidString { -// fn serialize(&self, serializer: S) -> Result -// where -// S: serde::Serializer, -// { -// serializer.serialize_str(self.0.to_string().as_str()) -// } -// } -// -// impl<'de> Deserialize<'de> for CidString { -// fn deserialize(deserializer: D) -> Result -// where -// D: Deserializer<'de>, -// { -// let s = String::deserialize(deserializer)?; -// -// Cid::from_str(&s) -// .map(CidString) -// .map_err(|e| de::Error::custom(format!("invalid CID: {}", e))) -// } -// } -// -// /// Test utilities. -// #[cfg(any(test, feature = "test_utils"))] -// #[cfg_attr(docsrs, doc(cfg(feature = "test_utils")))] -// pub mod test_utils; diff --git a/src/plugins.rs b/src/plugins.rs deleted file mode 100644 index 6f19c827..00000000 --- a/src/plugins.rs +++ /dev/null @@ -1,251 +0,0 @@ -//! Plugins for definining custom semantics - -#[cfg(not(target_arch = "wasm32"))] -use linkme::distributed_slice; - -use core::fmt; -use downcast_rs::{impl_downcast, Downcast}; -use std::sync::RwLock; -use url::Url; - -use crate::{ - error::Error, - semantics::{ - ability::{Ability, TopAbility}, - caveat::{Caveat, EmptyCaveat}, - resource::Resource, - }, -}; - -pub mod ucan; -pub mod wnfs; - -#[cfg(not(target_arch = "wasm32"))] -#[distributed_slice] -#[doc(hidden)] -pub static STATIC_PLUGINS: [&dyn Plugin< - Resource = Box, - Ability = Box, - Caveat = Box, - Error = Error, ->] = [..]; - -type ErasedPlugin = dyn Plugin< - Resource = Box, - Ability = Box, - Caveat = Box, - Error = Error, ->; - -lazy_static::lazy_static! { - static ref RUNTIME_PLUGINS: RwLock> = RwLock::new(Vec::new()); -} - -/// A plugin for handling a specific scheme -pub trait Plugin: Send + Sync + Downcast + 'static { - /// The type of resource this plugin handles - type Resource; - - /// The type of ability this plugin handles - type Ability; - - /// The type of caveat this plugin handles - type Caveat; - - /// The type of error this plugin may return - type Error; - - /// The scheme this plugin handles - fn scheme(&self) -> &'static str; - - /// Handle a resource - fn try_handle_resource( - &self, - resource_uri: &Url, - ) -> Result, Self::Error>; - - /// Handle an ability - fn try_handle_ability( - &self, - resource: &Self::Resource, - ability: &str, - ) -> Result, Self::Error>; - - /// Handle a caveat - fn try_handle_caveat( - &self, - resource: &Self::Resource, - ability: &Self::Ability, - deserializer: &mut dyn erased_serde::Deserializer<'_>, - ) -> Result, Self::Error>; -} - -impl_downcast!(Plugin assoc Resource, Ability, Caveat, Error); - -/// A wrapped plugin that unifies plugin error handling, and handles common semantics, such -/// as top abilities. -pub struct WrappedPlugin -where - R: 'static, - A: 'static, - C: 'static, - E: 'static, -{ - #[doc(hidden)] - pub inner: &'static dyn Plugin, -} - -impl Plugin for WrappedPlugin -where - R: Resource, - A: Ability, - C: Caveat, - E: Into, -{ - type Resource = Box; - type Ability = Box; - type Caveat = Box; - - type Error = Error; - - fn scheme(&self) -> &'static str { - self.inner.scheme() - } - - fn try_handle_resource( - &self, - resource_uri: &Url, - ) -> Result, Self::Error> { - self.inner.try_handle_resource(resource_uri).map_or_else( - |e| Err(Error::PluginError(anyhow::anyhow!(e).into())), - |r| Ok(r.map(|r| Box::new(r) as Box)), - ) - } - - fn try_handle_ability( - &self, - resource: &Self::Resource, - ability: &str, - ) -> Result>, Self::Error> { - if ability == "*" { - return Ok(Some(Box::new(TopAbility))); - } - - let Some(resource) = resource.downcast_ref::() else { - return Ok(None); - }; - - self.inner - .try_handle_ability(resource, ability) - .map_or_else( - |e| Err(Error::PluginError(anyhow::anyhow!(e).into())), - |a| Ok(a.map(|a| Box::new(a) as Box)), - ) - } - - fn try_handle_caveat( - &self, - resource: &Self::Resource, - ability: &Self::Ability, - deserializer: &mut dyn erased_serde::Deserializer<'_>, - ) -> Result, Self::Error> { - let Some(resource) = resource.downcast_ref::() else { - return Ok(None); - }; - - if ability.is::() { - return Ok(Some(Box::new( - erased_serde::deserialize::(deserializer) - .map_err(|e| anyhow::anyhow!(e))?, - ))); - } - - let Some(ability) = ability.downcast_ref::() else { - return Ok(None); - }; - - self.inner - .try_handle_caveat(resource, ability, deserializer) - .map_or_else( - |e| Err(Error::PluginError(anyhow::anyhow!(e).into())), - |c| Ok(c.map(|c| Box::new(c) as Box)), - ) - } -} - -impl fmt::Debug for WrappedPlugin { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_struct("WrappedPlugin") - .field("scheme", &self.inner.scheme()) - .finish() - } -} - -/// Get an iterator over all plugins -pub fn plugins() -> impl Iterator< - Item = &'static dyn Plugin< - Resource = Box, - Ability = Box, - Caveat = Box, - Error = Error, - >, -> { - cfg_if::cfg_if! { - if #[cfg(target_arch = "wasm32")] { - RUNTIME_PLUGINS - .read() - .expect("plugin lock poisoned") - .clone() - .into_iter() - } else { - let static_plugins = STATIC_PLUGINS.iter().copied(); - let runtime_plugins = RUNTIME_PLUGINS - .read() - .expect("plugin lock poisoned") - .clone() - .into_iter(); - - static_plugins.chain(runtime_plugins) - } - } -} - -/// Register a plugin -pub fn register_plugin( - plugin: &'static dyn Plugin, -) where - R: Resource, - A: Ability, - C: Caveat, - E: Into, -{ - let erased = Box::new(WrappedPlugin { inner: plugin }); - let leaked = Box::leak::<'static>(erased); - - RUNTIME_PLUGINS - .write() - .expect("plugin lock poisoned") - .push(leaked); -} - -/// Register a plugin at compile time -#[cfg(not(target_arch = "wasm32"))] -#[macro_export] -macro_rules! register_plugin { - ($name:ident, $plugin:expr) => { - #[$crate::linkme::distributed_slice($crate::plugins::STATIC_PLUGINS)] - static $name: &'static dyn $crate::plugins::Plugin< - Resource = Box, - Ability = Box, - Caveat = Box, - Error = $crate::error::Error, - > = &$crate::plugins::WrappedPlugin { inner: $plugin }; - }; -} - -/// Register a plugin at compile time -#[cfg(target_arch = "wasm32")] -#[macro_export] -macro_rules! register_plugin { - ($name:ident, $plugin:expr) => {}; -} diff --git a/src/plugins/ucan.rs b/src/plugins/ucan.rs deleted file mode 100644 index 9190d590..00000000 --- a/src/plugins/ucan.rs +++ /dev/null @@ -1,392 +0,0 @@ -//! A plugin for handling the `ucan` scheme. - -use std::fmt::Display; - -use cid::Cid; -use url::Url; - -use crate::{ - semantics::{ability::Ability, caveat::EmptyCaveat}, - Did, -}; - -use super::{Plugin, Resource}; - -/// A plugin for handling the `ucan` scheme. -#[derive(Debug)] -pub struct UcanPlugin; - -crate::register_plugin!(UCAN, &UcanPlugin); - -impl Plugin for UcanPlugin { - type Resource = UcanResource; - type Ability = UcanAbilityDelegation; - type Caveat = EmptyCaveat; - - type Error = anyhow::Error; - - fn scheme(&self) -> &'static str { - "ucan" - } - - fn try_handle_resource( - &self, - resource_uri: &Url, - ) -> Result, Self::Error> { - // TODO: I'm not handling the OwnedBy or OwnedByWithScheme cases yet, - // because the spec probably needs to be modified to treat the DID as - // a literal, by wrapping it in square brackets, to avoid parsing issues - // from treating it as an authority with a port. - match resource_uri.path() { - "*" => Ok(Some(UcanResource::AllProvable)), - "./*" => Ok(Some(UcanResource::LocallyProvable)), - path => { - if let Ok(cid) = Cid::try_from(path) { - return Ok(Some(UcanResource::ByCid(cid))); - } - - Ok(None) - } - } - } - - fn try_handle_ability( - &self, - _resource: &Self::Resource, - ability: &str, - ) -> Result, Self::Error> { - match ability { - "ucan/*" => Ok(Some(UcanAbilityDelegation)), - _ => Ok(None), - } - } - - fn try_handle_caveat( - &self, - _resource: &Self::Resource, - _ability: &Self::Ability, - deserializer: &mut dyn erased_serde::Deserializer<'_>, - ) -> Result, Self::Error> { - erased_serde::deserialize(deserializer).map_err(|e| anyhow::anyhow!(e)) - } -} - -/// A resource for the `ucan` scheme. -#[derive(Debug, Clone, Eq, PartialEq)] -pub enum UcanResource { - /// ucan: - ByCid(Cid), - /// ucan:* - AllProvable, - /// ucan:./* - LocallyProvable, - /// ucan:///* - OwnedBy(Did), - /// ucan:/// - OwnedByWithScheme(Did, String), -} - -impl Display for UcanResource { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - let hier_part = match self { - UcanResource::ByCid(cid) => cid.to_string(), - UcanResource::AllProvable => "*".to_string(), - UcanResource::LocallyProvable => "./*".to_string(), - UcanResource::OwnedBy(did) => format!("//{}/*", did), - UcanResource::OwnedByWithScheme(did, scheme) => format!("//{}/{}", did, scheme), - }; - - f.write_fmt(format_args!("ucan:{}", hier_part)) - } -} - -impl Resource for UcanResource { - fn is_valid_attenuation(&self, other: &dyn Resource) -> bool { - if let Some(resource) = other.downcast_ref::() { - return self == resource; - }; - - false - } -} - -/// The UCAN delegation ability from the `ucan` scheme. -#[derive(Debug, Clone, Eq, PartialEq)] -pub struct UcanAbilityDelegation; - -impl Ability for UcanAbilityDelegation { - fn is_valid_attenuation(&self, other: &dyn Ability) -> bool { - if let Some(ability) = other.downcast_ref::() { - return self == ability; - }; - - false - } -} - -impl Display for UcanAbilityDelegation { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "ucan/*") - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_plugin_scheme() { - assert_eq!(UcanPlugin.scheme(), "ucan"); - } - - #[test] - fn test_plugin_try_handle_resource_by_cid() -> anyhow::Result<()> { - let resource = UcanPlugin.try_handle_resource(&Url::parse( - "ucan:bafybeigdyrzt5sfp7udm7hu76uh7y26nf3efuylqabf3oclgtqy55fbzdi", - )?)?; - - assert_eq!( - resource, - Some(UcanResource::ByCid(Cid::try_from( - "bafybeigdyrzt5sfp7udm7hu76uh7y26nf3efuylqabf3oclgtqy55fbzdi" - )?)) - ); - - Ok(()) - } - - #[test] - fn test_plugin_try_handle_resource_all_provable() -> anyhow::Result<()> { - let resource = UcanPlugin.try_handle_resource(&Url::parse("ucan:*")?)?; - - assert_eq!(resource, Some(UcanResource::AllProvable)); - - Ok(()) - } - - #[test] - fn test_plugin_try_handle_resource_locally_provable() -> anyhow::Result<()> { - let resource = UcanPlugin.try_handle_resource(&Url::parse("ucan:./*")?)?; - - assert_eq!(resource, Some(UcanResource::LocallyProvable)); - - Ok(()) - } - - #[test] - #[ignore = "Spec expects DID not to be URL encoded, but this results in invalid URLs"] - fn test_plugin_try_handle_resource_owned_by() -> anyhow::Result<()> { - let resource = UcanPlugin.try_handle_resource(&Url::parse( - "ucan://did:key:z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK/*", - )?)?; - - assert_eq!( - resource, - Some(UcanResource::OwnedBy( - "did:key:z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK".to_string() - )) - ); - - Ok(()) - } - - #[test] - #[ignore = "Spec expects DID not to be URL encoded, but this results in invalid URLs"] - fn test_plugin_try_handle_resource_owned_with_scheme() -> anyhow::Result<()> { - let resource = UcanPlugin.try_handle_resource(&Url::parse( - "ucan://did:key:z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK/wnfs", - )?)?; - - assert_eq!( - resource, - Some(UcanResource::OwnedByWithScheme( - "did:key:z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK".to_string(), - "wnfs".to_string() - )) - ); - - Ok(()) - } - - #[test] - fn test_plugin_try_handle_ability_delegation() -> anyhow::Result<()> { - let ability = UcanPlugin.try_handle_ability(&UcanResource::AllProvable, "ucan/*")?; - - assert_eq!(ability, Some(UcanAbilityDelegation)); - - Ok(()) - } - - #[test] - fn test_resource_by_cid_display() -> anyhow::Result<()> { - let resource = UcanResource::ByCid(Cid::try_from( - "bafybeigdyrzt5sfp7udm7hu76uh7y26nf3efuylqabf3oclgtqy55fbzdi", - )?); - - assert_eq!( - resource.to_string(), - "ucan:bafybeigdyrzt5sfp7udm7hu76uh7y26nf3efuylqabf3oclgtqy55fbzdi" - ); - - Ok(()) - } - - #[test] - fn test_resource_all_provable_display() { - let resource = UcanResource::AllProvable; - - assert_eq!(resource.to_string(), "ucan:*"); - } - - #[test] - fn test_resource_locally_provable_display() { - let resource = UcanResource::LocallyProvable; - - assert_eq!(resource.to_string(), "ucan:./*"); - } - - #[test] - fn test_resource_owned_by_display() { - let resource = UcanResource::OwnedBy( - "did:key:z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK".to_string(), - ); - - assert_eq!( - resource.to_string(), - "ucan://did:key:z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK/*" - ); - } - - #[test] - fn test_resource_owned_by_with_scheme_display() { - let resource = UcanResource::OwnedByWithScheme( - "did:key:z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK".to_string(), - "wnfs".to_string(), - ); - - assert_eq!( - resource.to_string(), - "ucan://did:key:z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK/wnfs" - ); - } - - #[test] - fn test_ability_delegation_display() { - let ability = UcanAbilityDelegation; - - assert_eq!(ability.to_string(), "ucan/*"); - } - - #[test] - fn test_resource_attenuation() -> anyhow::Result<()> { - let all_provable = UcanResource::AllProvable; - let locally_provable = UcanResource::LocallyProvable; - - let by_cid_1 = UcanResource::ByCid(Cid::try_from( - "bafybeigdyrzt5sfp7udm7hu76uh7y26nf3efuylqabf3oclgtqy55fbzdi", - )?); - - let by_cid_2 = UcanResource::ByCid(Cid::try_from( - "QmbWqxBEKC3P8tqsKc98xmWNzrzDtRLMiMPL8wBuTGsMnR", - )?); - - let owned_by_1 = UcanResource::OwnedBy( - "did:key:z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK".to_string(), - ); - - let owned_by_2 = UcanResource::OwnedBy("did:example:123456789abcdefghi".to_string()); - - let owned_by_with_scheme_1 = UcanResource::OwnedByWithScheme( - "did:key:z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK".to_string(), - "wnfs".to_string(), - ); - - let owned_by_with_scheme_2 = UcanResource::OwnedByWithScheme( - "did:key:z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK".to_string(), - "ucan".to_string(), - ); - - assert!(all_provable.is_valid_attenuation(&all_provable)); - assert!(!all_provable.is_valid_attenuation(&locally_provable)); - assert!(!all_provable.is_valid_attenuation(&by_cid_1)); - assert!(!all_provable.is_valid_attenuation(&by_cid_2)); - assert!(!all_provable.is_valid_attenuation(&owned_by_1)); - assert!(!all_provable.is_valid_attenuation(&owned_by_2)); - assert!(!all_provable.is_valid_attenuation(&owned_by_with_scheme_1)); - assert!(!all_provable.is_valid_attenuation(&owned_by_with_scheme_2)); - - assert!(!locally_provable.is_valid_attenuation(&all_provable)); - assert!(locally_provable.is_valid_attenuation(&locally_provable)); - assert!(!locally_provable.is_valid_attenuation(&by_cid_1)); - assert!(!locally_provable.is_valid_attenuation(&by_cid_2)); - assert!(!locally_provable.is_valid_attenuation(&owned_by_1)); - assert!(!locally_provable.is_valid_attenuation(&owned_by_2)); - assert!(!locally_provable.is_valid_attenuation(&owned_by_with_scheme_1)); - assert!(!locally_provable.is_valid_attenuation(&owned_by_with_scheme_2)); - - assert!(!by_cid_1.is_valid_attenuation(&all_provable)); - assert!(!by_cid_1.is_valid_attenuation(&locally_provable)); - assert!(by_cid_1.is_valid_attenuation(&by_cid_1)); - assert!(!by_cid_1.is_valid_attenuation(&by_cid_2)); - assert!(!by_cid_1.is_valid_attenuation(&owned_by_1)); - assert!(!by_cid_1.is_valid_attenuation(&owned_by_2)); - assert!(!by_cid_1.is_valid_attenuation(&owned_by_with_scheme_1)); - assert!(!by_cid_1.is_valid_attenuation(&owned_by_with_scheme_2)); - - assert!(!by_cid_2.is_valid_attenuation(&all_provable)); - assert!(!by_cid_2.is_valid_attenuation(&locally_provable)); - assert!(!by_cid_2.is_valid_attenuation(&by_cid_1)); - assert!(by_cid_2.is_valid_attenuation(&by_cid_2)); - assert!(!by_cid_2.is_valid_attenuation(&owned_by_1)); - assert!(!by_cid_2.is_valid_attenuation(&owned_by_2)); - assert!(!by_cid_2.is_valid_attenuation(&owned_by_with_scheme_1)); - assert!(!by_cid_2.is_valid_attenuation(&owned_by_with_scheme_2)); - - assert!(!owned_by_1.is_valid_attenuation(&all_provable)); - assert!(!owned_by_1.is_valid_attenuation(&locally_provable)); - assert!(!owned_by_1.is_valid_attenuation(&by_cid_1)); - assert!(!owned_by_1.is_valid_attenuation(&by_cid_2)); - assert!(owned_by_1.is_valid_attenuation(&owned_by_1)); - assert!(!owned_by_1.is_valid_attenuation(&owned_by_2)); - assert!(!owned_by_1.is_valid_attenuation(&owned_by_with_scheme_1)); - assert!(!owned_by_1.is_valid_attenuation(&owned_by_with_scheme_2)); - - assert!(!owned_by_2.is_valid_attenuation(&all_provable)); - assert!(!owned_by_2.is_valid_attenuation(&locally_provable)); - assert!(!owned_by_2.is_valid_attenuation(&by_cid_1)); - assert!(!owned_by_2.is_valid_attenuation(&by_cid_2)); - assert!(!owned_by_2.is_valid_attenuation(&owned_by_1)); - assert!(owned_by_2.is_valid_attenuation(&owned_by_2)); - assert!(!owned_by_2.is_valid_attenuation(&owned_by_with_scheme_1)); - assert!(!owned_by_2.is_valid_attenuation(&owned_by_with_scheme_2)); - - assert!(!owned_by_with_scheme_1.is_valid_attenuation(&all_provable)); - assert!(!owned_by_with_scheme_1.is_valid_attenuation(&locally_provable)); - assert!(!owned_by_with_scheme_1.is_valid_attenuation(&by_cid_1)); - assert!(!owned_by_with_scheme_1.is_valid_attenuation(&by_cid_2)); - assert!(!owned_by_with_scheme_1.is_valid_attenuation(&owned_by_1)); - assert!(!owned_by_with_scheme_1.is_valid_attenuation(&owned_by_2)); - assert!(owned_by_with_scheme_1.is_valid_attenuation(&owned_by_with_scheme_1)); - assert!(!owned_by_with_scheme_1.is_valid_attenuation(&owned_by_with_scheme_2)); - - assert!(!owned_by_with_scheme_2.is_valid_attenuation(&all_provable)); - assert!(!owned_by_with_scheme_2.is_valid_attenuation(&locally_provable)); - assert!(!owned_by_with_scheme_2.is_valid_attenuation(&by_cid_1)); - assert!(!owned_by_with_scheme_2.is_valid_attenuation(&by_cid_2)); - assert!(!owned_by_with_scheme_2.is_valid_attenuation(&owned_by_1)); - assert!(!owned_by_with_scheme_2.is_valid_attenuation(&owned_by_2)); - assert!(!owned_by_with_scheme_2.is_valid_attenuation(&owned_by_with_scheme_1)); - assert!(owned_by_with_scheme_2.is_valid_attenuation(&owned_by_with_scheme_2)); - - Ok(()) - } - - #[test] - fn test_ability_attenuation() -> anyhow::Result<()> { - let ability = UcanAbilityDelegation; - - assert!(ability.is_valid_attenuation(&ability)); - - Ok(()) - } -} diff --git a/src/plugins/wnfs.rs b/src/plugins/wnfs.rs deleted file mode 100644 index 66e3045b..00000000 --- a/src/plugins/wnfs.rs +++ /dev/null @@ -1,389 +0,0 @@ -//! A plugin for handling the `wnfs` scheme. - -use std::fmt::Display; - -use crate::semantics::{ability::Ability, caveat::EmptyCaveat, resource::Resource}; -use url::Url; - -use super::Plugin; - -/// A plugin for handling the `wnfs` scheme. -#[derive(Debug)] -pub struct WnfsPlugin; - -crate::register_plugin!(WNFS, &WnfsPlugin); - -impl Plugin for WnfsPlugin { - type Resource = WnfsResource; - type Ability = WnfsAbility; - type Caveat = EmptyCaveat; - - type Error = anyhow::Error; - - fn scheme(&self) -> &'static str { - "wnfs" - } - - fn try_handle_resource( - &self, - resource_uri: &Url, - ) -> Result, Self::Error> { - let Some(user) = resource_uri.host_str() else { - return Ok(None); - }; - - let Some(path_segments) = resource_uri.path_segments() else { - return Ok(None); - }; - - match path_segments.collect::>().as_slice() { - ["public", path @ ..] => Ok(Some(WnfsResource::PublicPath { - user: user.to_string(), - path: path.iter().map(|s| s.to_string()).collect(), - })), - ["private", ..] => todo!(), - _ => Ok(None), - } - } - - fn try_handle_ability( - &self, - _resource: &Self::Resource, - ability: &str, - ) -> Result, Self::Error> { - match ability { - "wnfs/create" => Ok(Some(WnfsAbility::Create)), - "wnfs/revise" => Ok(Some(WnfsAbility::Revise)), - "wnfs/soft_delete" => Ok(Some(WnfsAbility::SoftDelete)), - "wnfs/overwrite" => Ok(Some(WnfsAbility::Overwrite)), - "wnfs/super_user" => Ok(Some(WnfsAbility::SuperUser)), - _ => Ok(None), - } - } - - fn try_handle_caveat( - &self, - _resource: &Self::Resource, - _ability: &Self::Ability, - deserializer: &mut dyn erased_serde::Deserializer<'_>, - ) -> Result, Self::Error> { - erased_serde::deserialize(deserializer).map_err(|e| anyhow::anyhow!(e)) - } -} - -/// A resource for the `wnfs` scheme. -#[derive(Debug, Clone, Eq, PartialEq)] -pub enum WnfsResource { - /// wnfs:///public/ - PublicPath { - /// The user - user: String, - /// The path - path: Vec, - }, - /// wnfs:///private/ - PrivatePath { - /// The user - user: String, - }, // TODO -} - -impl Resource for WnfsResource { - fn is_valid_attenuation(&self, other: &dyn Resource) -> bool { - let Some(other) = other.downcast_ref::() else { - return false; - }; - - match self { - WnfsResource::PublicPath { user, path } => { - let WnfsResource::PublicPath { - user: other_user, - path: other_path, - } = other - else { - return false; - }; - - if user != other_user { - return false; - } - - path.strip_prefix(other_path.as_slice()).is_some() - } - WnfsResource::PrivatePath { .. } => todo!(), - } - } -} - -impl Display for WnfsResource { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - WnfsResource::PublicPath { user, path } => { - f.write_fmt(format_args!("wnfs://{}/public/{}", user, path.join("/"))) - } - - WnfsResource::PrivatePath { .. } => todo!(), - } - } -} - -/// An ability for the `wnfs` scheme. -#[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd)] -pub enum WnfsAbility { - /// wnfs/create - Create, - /// wnfs/revise - Revise, - /// wnfs/soft_delete - SoftDelete, - /// wnfs/overwrite - Overwrite, - /// wnfs/super_user - SuperUser, -} - -impl Ability for WnfsAbility { - fn is_valid_attenuation(&self, other: &dyn Ability) -> bool { - let Some(other) = other.downcast_ref::() else { - return false; - }; - - self <= other - } -} - -impl Display for WnfsAbility { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - WnfsAbility::Create => f.write_str("wnfs/create"), - WnfsAbility::Revise => f.write_str("wnfs/revise"), - WnfsAbility::SoftDelete => f.write_str("wnfs/soft_delete"), - WnfsAbility::Overwrite => f.write_str("wnfs/overwrite"), - WnfsAbility::SuperUser => f.write_str("wnfs/super_user"), - } - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_plugin_scheme() { - assert_eq!(WnfsPlugin.scheme(), "wnfs"); - } - - #[test] - fn test_plugin_try_handle_resource_public() -> anyhow::Result<()> { - let resource = - WnfsPlugin.try_handle_resource(&Url::parse("wnfs://user/public/path/to/file")?)?; - - assert_eq!( - resource, - Some(WnfsResource::PublicPath { - user: "user".to_string(), - path: vec!["path".to_string(), "to".to_string(), "file".to_string()], - }) - ); - - Ok(()) - } - - #[test] - fn test_plugin_try_handle_resource_invalid() -> anyhow::Result<()> { - let resource = - WnfsPlugin.try_handle_resource(&Url::parse("wnfs://user/invalid/path/to/file")?)?; - - assert_eq!(resource, None); - - Ok(()) - } - - #[test] - fn test_plugin_try_handle_ability_public() -> anyhow::Result<()> { - let resource = WnfsResource::PublicPath { - user: "user".to_string(), - path: vec!["path".to_string(), "to".to_string(), "file".to_string()], - }; - - let ability_create = WnfsPlugin.try_handle_ability(&resource, "wnfs/create")?; - let ability_revise = WnfsPlugin.try_handle_ability(&resource, "wnfs/revise")?; - let ability_soft_delete = WnfsPlugin.try_handle_ability(&resource, "wnfs/soft_delete")?; - let ability_overwrite = WnfsPlugin.try_handle_ability(&resource, "wnfs/overwrite")?; - let ability_super_user = WnfsPlugin.try_handle_ability(&resource, "wnfs/super_user")?; - let ability_invalid = WnfsPlugin.try_handle_ability(&resource, "wnfs/not-an-ability")?; - - assert_eq!(ability_create, Some(WnfsAbility::Create)); - assert_eq!(ability_revise, Some(WnfsAbility::Revise)); - assert_eq!(ability_soft_delete, Some(WnfsAbility::SoftDelete)); - assert_eq!(ability_overwrite, Some(WnfsAbility::Overwrite)); - assert_eq!(ability_super_user, Some(WnfsAbility::SuperUser)); - assert_eq!(ability_invalid, None); - - Ok(()) - } - - #[test] - fn test_resource_public_display() { - let resource = WnfsResource::PublicPath { - user: "user".to_string(), - path: vec!["foo".to_string(), "bar".to_string()], - }; - - assert_eq!(resource.to_string(), "wnfs://user/public/foo/bar"); - } - - #[test] - fn test_resource_public_attenuation_identity() { - let resource = WnfsResource::PublicPath { - user: "user".to_string(), - path: vec!["foo".to_string(), "bar".to_string()], - }; - - assert!(resource.is_valid_attenuation(&resource)); - } - - #[test] - fn test_resource_public_attenuation_child() { - let parent = WnfsResource::PublicPath { - user: "user".to_string(), - path: vec!["foo".to_string(), "bar".to_string()], - }; - - let child = WnfsResource::PublicPath { - user: "user".to_string(), - path: vec!["foo".to_string(), "bar".to_string(), "baz".to_string()], - }; - - assert!(child.is_valid_attenuation(&parent)); - } - - #[test] - fn test_resource_public_attenuation_descendent() { - let ancestor = WnfsResource::PublicPath { - user: "user".to_string(), - path: vec!["foo".to_string(), "bar".to_string()], - }; - - let descendent = WnfsResource::PublicPath { - user: "user".to_string(), - path: vec![ - "foo".to_string(), - "bar".to_string(), - "baz".to_string(), - "qux".to_string(), - ], - }; - - assert!(descendent.is_valid_attenuation(&ancestor)); - } - - #[test] - fn test_resource_public_attenuation_parent() { - let parent = WnfsResource::PublicPath { - user: "user".to_string(), - path: vec!["foo".to_string(), "bar".to_string()], - }; - - let child = WnfsResource::PublicPath { - user: "user".to_string(), - path: vec!["foo".to_string(), "bar".to_string(), "baz".to_string()], - }; - - assert!(!parent.is_valid_attenuation(&child)); - } - - #[test] - fn test_resource_public_attenuation_ancestor() { - let ancestor = WnfsResource::PublicPath { - user: "user".to_string(), - path: vec!["foo".to_string(), "bar".to_string()], - }; - - let descendent = WnfsResource::PublicPath { - user: "user".to_string(), - path: vec![ - "foo".to_string(), - "bar".to_string(), - "baz".to_string(), - "qux".to_string(), - ], - }; - - assert!(!ancestor.is_valid_attenuation(&descendent)); - } - - #[test] - fn test_resource_public_attenuation_sibling() { - let sibling_1 = WnfsResource::PublicPath { - user: "user".to_string(), - path: vec!["foo".to_string(), "bar".to_string()], - }; - - let sibling_2 = WnfsResource::PublicPath { - user: "user".to_string(), - path: vec!["foo".to_string(), "baz".to_string()], - }; - - assert!(!sibling_1.is_valid_attenuation(&sibling_2)); - assert!(!sibling_2.is_valid_attenuation(&sibling_1)); - } - - #[test] - fn test_resource_public_attenuation_distinct_users() { - let path_1 = WnfsResource::PublicPath { - user: "user1".to_string(), - path: vec!["foo".to_string(), "bar".to_string()], - }; - - let path_2 = WnfsResource::PublicPath { - user: "user2".to_string(), - path: vec!["foo".to_string(), "bar".to_string()], - }; - - assert!(!path_1.is_valid_attenuation(&path_2)); - assert!(!path_2.is_valid_attenuation(&path_1)); - } - - #[test] - fn test_ability_attenuation() { - assert!(WnfsAbility::Create.is_valid_attenuation(&WnfsAbility::Create)); - assert!(WnfsAbility::Create.is_valid_attenuation(&WnfsAbility::Revise)); - assert!(WnfsAbility::Create.is_valid_attenuation(&WnfsAbility::SoftDelete)); - assert!(WnfsAbility::Create.is_valid_attenuation(&WnfsAbility::Overwrite)); - assert!(WnfsAbility::Create.is_valid_attenuation(&WnfsAbility::SuperUser)); - - assert!(!WnfsAbility::Revise.is_valid_attenuation(&WnfsAbility::Create)); - assert!(WnfsAbility::Revise.is_valid_attenuation(&WnfsAbility::Revise)); - assert!(WnfsAbility::Revise.is_valid_attenuation(&WnfsAbility::SoftDelete)); - assert!(WnfsAbility::Revise.is_valid_attenuation(&WnfsAbility::Overwrite)); - assert!(WnfsAbility::Revise.is_valid_attenuation(&WnfsAbility::SuperUser)); - - assert!(!WnfsAbility::SoftDelete.is_valid_attenuation(&WnfsAbility::Create)); - assert!(!WnfsAbility::SoftDelete.is_valid_attenuation(&WnfsAbility::Revise)); - assert!(WnfsAbility::SoftDelete.is_valid_attenuation(&WnfsAbility::SoftDelete)); - assert!(WnfsAbility::SoftDelete.is_valid_attenuation(&WnfsAbility::Overwrite)); - assert!(WnfsAbility::SoftDelete.is_valid_attenuation(&WnfsAbility::SuperUser)); - - assert!(!WnfsAbility::Overwrite.is_valid_attenuation(&WnfsAbility::Create)); - assert!(!WnfsAbility::Overwrite.is_valid_attenuation(&WnfsAbility::Revise)); - assert!(!WnfsAbility::Overwrite.is_valid_attenuation(&WnfsAbility::SoftDelete)); - assert!(WnfsAbility::Overwrite.is_valid_attenuation(&WnfsAbility::Overwrite)); - assert!(WnfsAbility::Overwrite.is_valid_attenuation(&WnfsAbility::SuperUser)); - - assert!(!WnfsAbility::SuperUser.is_valid_attenuation(&WnfsAbility::Create)); - assert!(!WnfsAbility::SuperUser.is_valid_attenuation(&WnfsAbility::Revise)); - assert!(!WnfsAbility::SuperUser.is_valid_attenuation(&WnfsAbility::SoftDelete)); - assert!(!WnfsAbility::SuperUser.is_valid_attenuation(&WnfsAbility::Overwrite)); - assert!(WnfsAbility::Overwrite.is_valid_attenuation(&WnfsAbility::SuperUser)); - } - - #[test] - fn test_ability_display() { - assert_eq!(WnfsAbility::Create.to_string(), "wnfs/create"); - assert_eq!(WnfsAbility::Revise.to_string(), "wnfs/revise"); - assert_eq!(WnfsAbility::SoftDelete.to_string(), "wnfs/soft_delete"); - assert_eq!(WnfsAbility::Overwrite.to_string(), "wnfs/overwrite"); - assert_eq!(WnfsAbility::SuperUser.to_string(), "wnfs/super_user"); - } -} diff --git a/src/proof/internal.rs b/src/proof/internal.rs index 42df1327..06fd97fd 100644 --- a/src/proof/internal.rs +++ b/src/proof/internal.rs @@ -1,2 +1,3 @@ // NOTE: Must not get exported -pub(crate) trait Checker {} +// FIXME either mark downstream as ok to be provate, OOOOOR just leave this in an internal modukle +pub trait Checker {} diff --git a/src/proof/parentless.rs b/src/proof/parentless.rs index 1af372c4..645246db 100644 --- a/src/proof/parentless.rs +++ b/src/proof/parentless.rs @@ -6,8 +6,8 @@ use super::{ prove::{Prove, Success}, same::CheckSame, }; -use libipld_core::{error::SerdeError, ipld::Ipld, serde as ipld_serde}; -use serde::{de::DeserializeOwned, Deserialize, Serialize}; +use libipld_core::ipld::Ipld; +use serde::{Deserialize, Serialize}; /// The possible cases for an [ability][crate::ability]'s /// [Delegation][crate::delegation::Delegation] chain when diff --git a/src/proof/prove.rs b/src/proof/prove.rs index 9c2a517c..69e0feaf 100644 --- a/src/proof/prove.rs +++ b/src/proof/prove.rs @@ -2,14 +2,17 @@ use super::internal::Checker; +// FIXME move to internal? + /// An internal trait that checks based on the other traits for an ability type. -pub(crate) trait Prove: Checker { +pub trait Prove: Checker { type Error; // FIXME make the same as the trait name (prove) fn check(&self, proof: &Self) -> Result; } +#[derive(Debug, Clone, PartialEq)] pub enum Success { /// Success Proven, @@ -18,6 +21,7 @@ pub enum Success { ProvenByAny, } +#[derive(Debug, Clone, PartialEq)] pub enum Failure { /// An error in the command chain. CommandEscelation, diff --git a/src/prove/traits/internal/checker.rs b/src/prove/traits/internal/checker.rs deleted file mode 100644 index 8b137891..00000000 --- a/src/prove/traits/internal/checker.rs +++ /dev/null @@ -1 +0,0 @@ - diff --git a/src/reader.rs b/src/reader.rs index 599673a9..e5d69913 100644 --- a/src/reader.rs +++ b/src/reader.rs @@ -1,12 +1,8 @@ //! Configure & attach an ambient environment to a value. -use crate::{ - ability::{ - arguments, - command::{ParseAbility, ParseAbilityError, ToCommand}, - }, - delegation::Delegable, - invocation::Resolvable, +use crate::ability::{ + arguments, + command::{ParseAbility, ParseAbilityError, ToCommand}, }; use libipld_core::ipld::Ipld; use serde::{Deserialize, Serialize}; @@ -244,32 +240,3 @@ impl From>> for Reader { reader.map(|p| p.0) } } - -// use crate::proof::{checkable::Checkable, same::CheckSame}; -// -// impl Delegable for Reader -// where -// Reader: Checkable + CheckSame, -// { -// type Builder = Reader; -// } -// -// impl Resolvable for Reader -// where -// Reader: Into>, -// { -// type Promised = Reader; -// -// fn try_resolve(promised: Self::Promised) -> Result { -// match T::try_resolve(promised.val) { -// Ok(val) => Ok(Reader { -// env: promised.env, -// val, -// }), -// Err(val) => Err(Reader { -// env: promised.env, -// val, -// }), -// } -// } -// } diff --git a/src/receipt.rs b/src/receipt.rs index 05544091..445cdd9d 100644 --- a/src/receipt.rs +++ b/src/receipt.rs @@ -8,9 +8,9 @@ pub mod store; pub use payload::Payload; pub use responds::Responds; -use crate::{ability, did, did::Did, signature}; +use crate::{ability, did, signature}; /// The complete, signed receipt of an [`Invocation`][`crate::invocation::Invocation`]. -pub type Receipt = signature::Envelope, DID>; +pub type Receipt = signature::Envelope, DID>; pub type Preset = Receipt; diff --git a/src/semantics/ability.rs b/src/semantics/ability.rs deleted file mode 100644 index d4758496..00000000 --- a/src/semantics/ability.rs +++ /dev/null @@ -1,54 +0,0 @@ -//! UCAN Abilities - -use std::fmt::{self, Display}; - -use downcast_rs::{impl_downcast, Downcast}; -use dyn_clone::{clone_trait_object, DynClone}; - -use super::caveat::Caveat; - -/// An ability defined as part of a semantics -pub trait Ability: Send + Sync + Display + DynClone + Downcast + 'static { - /// Returns true if self is a valid attenuation of other - fn is_valid_attenuation(&self, other: &dyn Ability) -> bool; - - /// Returns true if caveat is a valid caveat for self - fn is_valid_caveat(&self, _caveat: &dyn Caveat) -> bool { - false - } -} - -clone_trait_object!(Ability); -impl_downcast!(Ability); - -impl Ability for Box { - fn is_valid_attenuation(&self, other: &dyn Ability) -> bool { - (**self).is_valid_attenuation(other) - } -} - -impl fmt::Debug for dyn Ability { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, r#"Ability("{}")"#, self) - } -} - -/// The top ability -#[derive(Debug, Clone, Eq, PartialEq)] -pub struct TopAbility; - -impl Ability for TopAbility { - fn is_valid_attenuation(&self, other: &dyn Ability) -> bool { - if let Some(ability) = other.downcast_ref::() { - return self == ability; - }; - - false - } -} - -impl Display for TopAbility { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "*") - } -} diff --git a/src/semantics/caveat.rs b/src/semantics/caveat.rs deleted file mode 100644 index 836d5023..00000000 --- a/src/semantics/caveat.rs +++ /dev/null @@ -1,125 +0,0 @@ -//! UCAN Caveats - -use std::fmt; - -use downcast_rs::{impl_downcast, Downcast}; -use dyn_clone::{clone_trait_object, DynClone}; -use erased_serde::serialize_trait_object; -use serde::{de::Visitor, ser::SerializeMap, Deserialize, Serialize}; - -/// A caveat defined as part of a semantics -pub trait Caveat: Send + Sync + DynClone + Downcast + erased_serde::Serialize + 'static { - /// Returns true if the caveat is valid - fn is_valid(&self) -> bool; - - /// Returns true if self is a valid attenuation of other - fn is_valid_attenuation(&self, other: &dyn Caveat) -> bool; -} - -clone_trait_object!(Caveat); -impl_downcast!(Caveat); -serialize_trait_object!(Caveat); - -impl fmt::Debug for dyn Caveat { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "Caveat({})", std::any::type_name::()) - } -} - -/// A caveat that is always valid -#[derive(Debug, Clone, Eq, PartialEq)] -pub struct EmptyCaveat; - -impl Caveat for EmptyCaveat { - fn is_valid(&self) -> bool { - true - } - - fn is_valid_attenuation(&self, other: &dyn Caveat) -> bool { - if let Some(resource) = other.downcast_ref::() { - return self == resource; - }; - - false - } -} - -impl Caveat for Box { - fn is_valid(&self) -> bool { - (**self).is_valid() - } - - fn is_valid_attenuation(&self, other: &dyn Caveat) -> bool { - (**self).is_valid_attenuation(other) - } -} - -impl<'de> Deserialize<'de> for EmptyCaveat { - fn deserialize(deserializer: D) -> Result - where - D: serde::Deserializer<'de>, - { - struct NoFieldsVisitor; - - impl<'de> Visitor<'de> for NoFieldsVisitor { - type Value = EmptyCaveat; - - fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { - formatter.write_str("an empty object") - } - - fn visit_map(self, mut map: A) -> Result - where - A: serde::de::MapAccess<'de>, - { - if let Some(field) = map.next_key()? { - return Err(serde::de::Error::unknown_field(field, &[])); - } - - Ok(EmptyCaveat) - } - } - - deserializer.deserialize_map(NoFieldsVisitor) - } -} - -impl Serialize for EmptyCaveat { - fn serialize(&self, serializer: S) -> Result - where - S: serde::Serializer, - { - serializer.serialize_map(Some(0))?.end() - } -} - -#[cfg(test)] -mod tests { - use serde_json::json; - - use super::*; - - #[test] - fn test_serialize_empty_caveat() { - let caveat = EmptyCaveat; - let serialized = serde_json::to_string(&caveat).unwrap(); - - assert_eq!(serialized, "{}"); - } - - #[test] - fn test_deserialize_empty_caveat() { - let deserialized: EmptyCaveat = serde_json::from_value(json!({})).unwrap(); - - assert_eq!(deserialized, EmptyCaveat); - } - - #[test] - fn test_deserialize_empty_caveat_unexpected_fields() { - let deserialized: Result = serde_json::from_value(json!({ - "foo": true - })); - - assert!(deserialized.is_err()); - } -} diff --git a/src/semantics/mod.rs b/src/semantics/mod.rs deleted file mode 100644 index 1d88f3b0..00000000 --- a/src/semantics/mod.rs +++ /dev/null @@ -1,5 +0,0 @@ -//! Semantics for UCAN schemes - -pub mod ability; -pub mod caveat; -pub mod resource; diff --git a/src/semantics/resource.rs b/src/semantics/resource.rs deleted file mode 100644 index a7f5360b..00000000 --- a/src/semantics/resource.rs +++ /dev/null @@ -1,27 +0,0 @@ -//! UCAN Resources - -use std::fmt::{self, Display}; - -use downcast_rs::{impl_downcast, Downcast}; -use dyn_clone::{clone_trait_object, DynClone}; - -/// A resource defined as part of a semantics -pub trait Resource: Send + Sync + Display + DynClone + Downcast + 'static { - /// Returns true if self is a valid attenuation of other - fn is_valid_attenuation(&self, other: &dyn Resource) -> bool; -} - -clone_trait_object!(Resource); -impl_downcast!(Resource); - -impl Resource for Box { - fn is_valid_attenuation(&self, other: &dyn Resource) -> bool { - (**self).is_valid_attenuation(other) - } -} - -impl fmt::Debug for dyn Resource { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, r#"Resource("{}")"#, self) - } -} diff --git a/src/signature.rs b/src/signature.rs index cb8b0152..2d4e9dac 100644 --- a/src/signature.rs +++ b/src/signature.rs @@ -36,13 +36,12 @@ pub struct Envelope + Capsule, DID: Did> { impl + Into + Clone, DID: Did> Envelope { pub fn try_sign(signer: &DID::Signer, payload: T) -> Result, ()> { - Self::try_sign_generic::(signer, DagCborCodec, Code::Sha2_256, payload) + Self::try_sign_generic::(signer, DagCborCodec, payload) } pub fn try_sign_generic>( signer: &DID::Signer, codec: C, - hasher: H, payload: T, ) -> Result, ()> // FIXME err = () diff --git a/src/store.rs b/src/store.rs deleted file mode 100644 index 84826f56..00000000 --- a/src/store.rs +++ /dev/null @@ -1,92 +0,0 @@ -//! A store for persisting UCAN tokens, to be referencable as proofs by other UCANs - -use std::{collections::HashMap, io::Cursor, marker::PhantomData}; - -use async_trait::async_trait; -use cid::{multihash, Cid}; -use libipld_core::{ - codec::{Codec, Decode, Encode}, - raw::RawCodec, -}; -use multihash::MultihashDigest; - -use crate::DEFAULT_MULTIHASH; - -/// A store for persisting UCAN tokens, to be referencable as proofs by other UCANs -pub trait Store -where - C: Codec, -{ - /// The error type for this store - type Error; - - /// Read a token from the store - fn read(&self, cid: Cid) -> Result, Self::Error> - where - T: Decode; - - /// Write a token to the store, using the specified hasher - fn write(&mut self, token: T, hasher: Option) -> Result - where - T: Encode; -} - -/// An async store for persisting UCAN tokens, to be referencable as proofs by other UCANs -// TODO: The send / sync bounds need to be conditional based on the target, to support wasm32 -#[async_trait] -pub trait AsyncStore: Send + Sync -where - C: Codec, -{ - /// The error type for this store - type Error; - - /// Read a token from the store - async fn read(&self, cid: Cid) -> Result, Self::Error> - where - T: Decode; - - /// Write a token to the store, using the specified hasher - async fn write( - &mut self, - token: T, - hasher: Option, - ) -> Result - where - T: Encode + Send; -} - -/// An in-memory store for development and testing -#[derive(Debug, Clone, Default)] -pub struct InMemoryStore { - store: HashMap>, - _phantom: PhantomData, -} - -impl Store for InMemoryStore { - type Error = anyhow::Error; - - fn read(&self, cid: Cid) -> Result, Self::Error> - where - T: Decode, - { - match self.store.get(cid) { - Some(block) => Ok(Some(T::decode(RawCodec, &mut Cursor::new(block))?)), - None => Ok(None), - } - } - - fn write(&mut self, token: T, hasher: Option) -> Result - where - T: Encode, - { - let hasher = hasher.unwrap_or(DEFAULT_MULTIHASH); - let block = RawCodec.encode(&token)?; - let digest = hasher.digest(&block); - let cid = Cid::new_v1(RawCodec.into(), digest); - - self.store.insert(cid, block); - - Ok(cid) - } -} diff --git a/src/test_utils/mod.rs b/src/test_utils/mod.rs deleted file mode 100644 index 4a30e2ad..00000000 --- a/src/test_utils/mod.rs +++ /dev/null @@ -1,5 +0,0 @@ -/// Random value generator for sampling data. -#[cfg(feature = "test_utils")] -mod rvg; -#[cfg(feature = "test_utils")] -pub use rvg::*; diff --git a/src/test_utils/rvg.rs b/src/test_utils/rvg.rs deleted file mode 100644 index 834c437e..00000000 --- a/src/test_utils/rvg.rs +++ /dev/null @@ -1,63 +0,0 @@ -use proptest::{ - collection::vec, - strategy::{Strategy, ValueTree}, - test_runner::{Config, TestRunner}, -}; - -/// A random value generator (RVG), which, given proptest strategies, will -/// generate random values based on those strategies. -#[derive(Debug, Default)] -pub struct Rvg { - runner: TestRunner, -} - -impl Rvg { - /// Creates a new RVG with the default random number generator. - pub fn new() -> Self { - Rvg { - runner: TestRunner::new(Config::default()), - } - } - - /// Creates a new RVG with a deterministic random number generator, - /// using the same seed across test runs. - pub fn deterministic() -> Self { - Rvg { - runner: TestRunner::deterministic(), - } - } - - /// Samples a value for the given strategy. - /// - /// # Example - /// - /// ``` - /// use ucan::test_utils::Rvg; - /// - /// let mut rvg = Rvg::new(); - /// let int = rvg.sample(&(0..100i32)); - /// ``` - pub fn sample(&mut self, strategy: &S) -> S::Value { - strategy - .new_tree(&mut self.runner) - .expect("No value can be generated") - .current() - } - - /// Samples a vec of some length with a value for the given strategy. - /// - /// # Example - /// - /// ``` - /// use ucan::test_utils::Rvg; - /// - /// let mut rvg = Rvg::new(); - /// let ints = rvg.sample_vec(&(0..100i32), 10); - /// ``` - pub fn sample_vec(&mut self, strategy: &S, len: usize) -> Vec { - vec(strategy, len..=len) - .new_tree(&mut self.runner) - .expect("No value can be generated") - .current() - } -} diff --git a/src/time.rs b/src/time.rs index b47a09d9..a0f7a355 100644 --- a/src/time.rs +++ b/src/time.rs @@ -118,6 +118,12 @@ pub struct JsTime { time: SystemTime, } +impl From for SystemTime { + fn from(js_time: JsTime) -> Self { + js_time.time + } +} + #[cfg(target_arch = "wasm32")] #[wasm_bindgen] impl JsTime { diff --git a/src/ucan.rs b/src/ucan.rs deleted file mode 100644 index c394710b..00000000 --- a/src/ucan.rs +++ /dev/null @@ -1,1044 +0,0 @@ -//! JWT embedding of a UCAN - -use std::{collections::vec_deque::VecDeque, str::FromStr}; - -use crate::{ - capability::{Capabilities, Capability, CapabilityParser, DefaultCapabilityParser}, - did_verifier::DidVerifierMap, - error::Error, - semantics::{ability::Ability, resource::Resource}, - store::Store, - CidString, DefaultFact, DEFAULT_MULTIHASH, -}; -use cid::{ - multihash::{self, MultihashDigest}, - Cid, -}; -use libipld_core::{ipld::Ipld, raw::RawCodec}; -use semver::Version; -use serde::{de::DeserializeOwned, Deserialize, Deserializer, Serialize}; -use tracing::{span, Level}; - -/// The current UCAN version -pub const UCAN_VERSION: &str = "0.10.0"; - -/// The UCAN header -#[derive(Clone, Debug, Serialize, Deserialize)] -pub struct UcanHeader { - pub(crate) alg: String, - pub(crate) typ: String, -} - -/// The UCAN payload -#[derive(Clone, Debug, Serialize, Deserialize)] -pub struct UcanPayload { - pub(crate) ucv: String, - pub(crate) iss: String, - pub(crate) aud: String, - #[serde(deserialize_with = "deserialize_required_nullable")] - pub(crate) exp: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub(crate) nbf: Option, - // TODO: nonce required in 1.0 - #[serde(skip_serializing_if = "Option::is_none")] - pub(crate) nnc: Option, - #[serde(bound = "C: CapabilityParser")] - pub(crate) cap: Capabilities, - #[serde(skip_serializing_if = "Option::is_none")] - pub(crate) fct: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub(crate) prf: Option>, -} - -/// A UCAN -#[derive(Clone, Debug)] -pub struct Ucan { - pub(crate) header: jose_b64::serde::Json, - pub(crate) payload: jose_b64::serde::Json>, - pub(crate) signature: jose_b64::serde::Bytes, -} - -impl Ucan -where - F: Clone + DeserializeOwned, - C: CapabilityParser, -{ - /// Validate the UCAN's signature and timestamps - pub fn validate(&self, at_time: u64, did_verifier_map: &DidVerifierMap) -> Result<(), Error> { - if self.typ() != "JWT" { - return Err(Error::VerifyingError { - msg: format!("expected header typ field to be 'JWT', got {}", self.typ()), - }); - } - - if Version::parse(self.version()).is_err() { - return Err(Error::VerifyingError { - msg: format!( - "expected header ucv field to be a semver, got {}", - self.version() - ), - }); - } - - if self.is_expired(at_time) { - return Err(Error::VerifyingError { - msg: "token is expired".to_string(), - }); - } - - if self.is_too_early(at_time) { - return Err(Error::VerifyingError { - msg: "current time is before token validity period begins".to_string(), - }); - } - - // TODO: parse and validate iss and aud DIDs during deserialization - self.payload - .aud - .strip_prefix("did:") - .and_then(|did| did.split_once(':')) - .ok_or(Error::VerifyingError { - msg: format!( - "expected did::, got {}", - self.payload.aud - ), - })?; - - let (method, identifier) = self - .payload - .iss - .strip_prefix("did:") - .and_then(|did| did.split_once(':')) - .ok_or(Error::VerifyingError { - msg: format!( - "expected did::, got {}", - self.payload.iss - ), - })?; - - let header = serde_json::to_value(&self.header) - .map_err(|e| Error::InternalUcanError { msg: e.to_string() })?; - - let payload = serde_json::to_value(&self.payload) - .map_err(|e| Error::InternalUcanError { msg: e.to_string() })?; - - let signed_data = format!( - "{}.{}", - header.as_str().ok_or(Error::InternalUcanError { - msg: "Expected base64 encoding of header".to_string(), - })?, - payload.as_str().ok_or(Error::InternalUcanError { - msg: "Expected base64 encoding of payload".to_string(), - })?, - ); - - did_verifier_map.verify(method, identifier, signed_data.as_bytes(), &self.signature) - } - - /// Returns true if the UCAN is authorized by the given issuer to - /// perform the ability against the resource - #[tracing::instrument(level = "trace", skip_all, fields(issuer = issuer.as_ref(), %resource, %ability, %at_time, self = %self.to_cid(None)?))] - pub fn capabilities_for( - &self, - issuer: impl AsRef, - resource: R, - ability: A, - at_time: u64, - did_verifier_map: &DidVerifierMap, - store: &S, - ) -> Result, Error> - where - R: Resource, - A: Ability, - S: Store, - { - let issuer = issuer.as_ref(); - - let mut capabilities = vec![]; - let mut proof_queue: VecDeque<(Ucan, Capability, Capability)> = VecDeque::default(); - - self.validate(at_time, did_verifier_map)?; - - for capability in self.capabilities() { - let span = span!(Level::TRACE, "capability", ?capability); - let _enter = span.enter(); - - let attenuated = Capability::clone_box(&resource, &ability, capability.caveat()); - - if !attenuated.is_subsumed_by(capability) { - tracing::trace!("skipping (not subsumed by)"); - - continue; - } - - if self.issuer() == issuer { - tracing::trace!("matched (by parenthood)"); - - capabilities.push(attenuated.clone()) - } - - proof_queue.push_back((self.clone(), capability.clone(), attenuated)); - - tracing::trace!("enqueued"); - } - - while let Some((ucan, attenuated_cap, leaf_cap)) = proof_queue.pop_front() { - let span = - span!(Level::TRACE, "ucan", ucan = %ucan.to_cid(None)?, ?attenuated_cap, ?leaf_cap); - - let _enter = span.enter(); - - for proof_cid in ucan.proofs().unwrap_or(vec![]) { - let span = span!(Level::TRACE, "proof", cid = %proof_cid); - let _enter = span.enter(); - - match store - .read::(proof_cid) - .map_err(|e| Error::InternalUcanError { - msg: format!( - "error while retrieving proof ({}) from store, {}", - proof_cid, e - ), - })? { - Some(Ipld::Bytes(bytes)) => { - let token = - String::from_utf8(bytes).map_err(|e| Error::InternalUcanError { - msg: format!( - "error converting token for proof ({}) into UTF-8 string, {}", - proof_cid, e - ), - })?; - - let proof_ucan = - Ucan::from_str(&token).map_err(|e| Error::InternalUcanError { - msg: format!( - "error decoding token for proof ({}) into UCAN, {}", - proof_cid, e - ), - })?; - - if !proof_ucan.lifetime_encompasses(&ucan) { - tracing::trace!("skipping (lifetime not encompassed)"); - - continue; - } - - if ucan.issuer() != proof_ucan.audience() { - tracing::trace!("skipping (issuer != audience)"); - - continue; - } - - if proof_ucan.validate(at_time, did_verifier_map).is_err() { - tracing::trace!("skipping (validation failed)"); - - continue; - } - - for capability in proof_ucan.capabilities() { - if !attenuated_cap.is_subsumed_by(capability) { - tracing::trace!("skipping (not subsumed by)"); - - continue; - } - - if proof_ucan.issuer() == issuer { - tracing::trace!("matched (by parenthood)"); - - capabilities.push(leaf_cap.clone()); - } - - proof_queue.push_back(( - proof_ucan.clone(), - capability.clone(), - leaf_cap.clone(), - )); - - tracing::trace!("enqueued"); - } - } - Some(ipld) => { - return Err(Error::InternalUcanError { - msg: format!( - "expected proof ({}) to map to bytes, got {:?}", - proof_cid, ipld - ), - }) - } - None => continue, - } - } - } - - Ok(capabilities) - } - - /// Encode the UCAN as a JWT token - pub fn encode(&self) -> Result { - let header = serde_json::to_value(&self.header) - .map_err(|e| Error::InternalUcanError { msg: e.to_string() })?; - - let payload = serde_json::to_value(&self.payload) - .map_err(|e| Error::InternalUcanError { msg: e.to_string() })?; - - let signature = serde_json::to_value(&self.signature) - .map_err(|e| Error::InternalUcanError { msg: e.to_string() })?; - - Ok(format!( - "{}.{}.{}", - header.as_str().ok_or(Error::InternalUcanError { - msg: "Expected base64 encoding of header".to_string(), - })?, - payload.as_str().ok_or(Error::InternalUcanError { - msg: "Expected base64 encoding of payload".to_string(), - })?, - signature.as_str().ok_or(Error::InternalUcanError { - msg: "Expected base64 encoding of signature".to_string(), - })? - )) - } - - /// Returns true if the UCAN has past its expiration date - pub fn is_expired(&self, at_time: u64) -> bool { - if let Some(exp) = self.payload.exp { - exp < at_time - } else { - false - } - } - - /// Returns the UCAN's signature - pub fn signature(&self) -> &jose_b64::serde::Bytes { - &self.signature - } - - /// Returns true if the not-before ("nbf") time is still in the future - pub fn is_too_early(&self, at_time: u64) -> bool { - match self.payload.nbf { - Some(nbf) => nbf > at_time, - None => false, - } - } - - /// Returns true if this UCAN's lifetime begins no later than the other - pub fn lifetime_begins_before(&self, other: &Ucan) -> bool - where - F2: DeserializeOwned, - C2: CapabilityParser, - { - match (self.payload.nbf, other.payload.nbf) { - (Some(nbf), Some(other_nbf)) => nbf <= other_nbf, - (Some(_), None) => false, - _ => true, - } - } - - /// Returns true if this UCAN expires no earlier than the other - pub fn lifetime_ends_after(&self, other: &Ucan) -> bool - where - F2: DeserializeOwned, - C2: CapabilityParser, - { - match (self.payload.exp, other.payload.exp) { - (Some(exp), Some(other_exp)) => exp >= other_exp, - (Some(_), None) => false, - (None, _) => true, - } - } - - /// Returns true if this UCAN's lifetime fully encompasses the other - pub fn lifetime_encompasses(&self, other: &Ucan) -> bool - where - F2: DeserializeOwned, - C2: CapabilityParser, - { - self.lifetime_begins_before(other) && self.lifetime_ends_after(other) - } - - /// Return the `typ` field of the UCAN header - pub fn typ(&self) -> &str { - &self.header.typ - } - - /// Return the `alg` field of the UCAN header - pub fn algorithm(&self) -> &str { - &self.header.alg - } - - /// Return the `iss` field of the UCAN payload - pub fn issuer(&self) -> &str { - &self.payload.iss - } - - /// Return the `aud` field of the UCAN payload - pub fn audience(&self) -> &str { - &self.payload.aud - } - - /// Return the `prf` field of the UCAN payload - pub fn proofs(&self) -> Option> { - self.payload - .prf - .as_ref() - .map(|f| f.iter().map(|c| &c.0).collect()) - } - - /// Return the `exp` field of the UCAN payload - pub fn expires_at(&self) -> Option { - self.payload.exp - } - - /// Return the `nbf` field of the UCAN payload - pub fn not_before(&self) -> Option { - self.payload.nbf - } - - /// Return the `nnc` field of the UCAN payload - pub fn nonce(&self) -> Option<&String> { - self.payload.nnc.as_ref() - } - - /// Return an iterator over the `cap` field of the UCAN payload - pub fn capabilities(&self) -> impl Iterator { - self.payload.cap.iter() - } - - /// Return the `fct` field of the UCAN payload - pub fn facts(&self) -> Option<&F> { - self.payload.fct.as_ref() - } - - /// Return the `ucv` field of the UCAN payload - pub fn version(&self) -> &str { - &self.payload.ucv - } - - /// Return the CID v1 of the UCAN encoded as a JWT token - pub fn to_cid(&self, hasher: Option) -> Result { - static RAW_CODEC: u64 = 0x55; - - let token = self.encode()?; - let digest = hasher.unwrap_or(DEFAULT_MULTIHASH).digest(token.as_bytes()); - let cid = Cid::new_v1(RAW_CODEC, digest); - - Ok(cid) - } -} - -impl<'a, F, C> TryFrom<&'a str> for Ucan -where - F: DeserializeOwned, - C: CapabilityParser, -{ - type Error = Error; - - fn try_from(ucan_token: &str) -> Result { - Ucan::::from_str(ucan_token) - } -} - -impl TryFrom for Ucan -where - F: DeserializeOwned, - C: CapabilityParser, -{ - type Error = Error; - - fn try_from(ucan_token: String) -> Result { - Ucan::from_str(ucan_token.as_str()) - } -} - -impl FromStr for Ucan -where - F: DeserializeOwned, - C: CapabilityParser, -{ - type Err = Error; - - fn from_str(ucan_token: &str) -> Result { - let &[header, payload, signature] = - ucan_token.splitn(3, '.').collect::>().as_slice() - else { - return Err(Error::TokenParseError { - msg: "malformed token, expected 3 parts separated by dots".to_string(), - }); - }; - - let header = - jose_b64::serde::Json::from_str(header).map_err(|_| Error::TokenParseError { - msg: "malformed header".to_string(), - })?; - - let payload = - jose_b64::serde::Json::from_str(payload).map_err(|_| Error::TokenParseError { - msg: "malformed payload".to_string(), - })?; - - let signature = - jose_b64::serde::Bytes::from_str(signature).map_err(|_| Error::TokenParseError { - msg: "malformed signature".to_string(), - })?; - - Ok(Ucan:: { - header, - payload, - signature, - }) - } -} - -impl<'de, F, C> Deserialize<'de> for Ucan -where - C: CapabilityParser, - F: DeserializeOwned, -{ - fn deserialize(deserializer: D) -> Result - where - D: Deserializer<'de>, - { - Ucan::::from_str(&String::deserialize(deserializer)?) - .map_err(|e| serde::de::Error::custom(e.to_string())) - } -} - -impl Serialize for Ucan -where - C: CapabilityParser, - F: Clone + DeserializeOwned, -{ - fn serialize(&self, serializer: S) -> Result - where - S: serde::Serializer, - { - self.encode() - .map_err(|e| serde::ser::Error::custom(e.to_string()))? - .serialize(serializer) - } -} - -fn deserialize_required_nullable<'de, T, D>(deserializer: D) -> Result -where - T: Deserialize<'de>, - D: Deserializer<'de>, -{ - Deserialize::deserialize(deserializer) - .map_err(|_| serde::de::Error::custom("required field is missing or has invalid type")) -} - -#[cfg(test)] -mod tests { - use signature::rand_core; - - use crate::{ - builder::UcanBuilder, - crypto::SignerDid, - did_verifier::DidVerifierMap, - plugins::wnfs::{WnfsAbility, WnfsResource}, - semantics::{ability::TopAbility, caveat::EmptyCaveat}, - store::InMemoryStore, - time, - }; - - use super::*; - - #[test] - fn test_capabilities_for_empty() -> Result<(), anyhow::Error> { - let store = InMemoryStore::::default(); - let did_verifier_map = DidVerifierMap::default(); - - let iss_key = ed25519_dalek::SigningKey::generate(&mut rand_core::OsRng); - let aud_key = ed25519_dalek::SigningKey::generate(&mut rand_core::OsRng); - - let ucan: Ucan = UcanBuilder::default() - .for_audience(aud_key.did()?) - .sign(&iss_key)?; - - let capabilities = ucan.capabilities_for( - iss_key.did()?, - WnfsResource::PublicPath { - user: "alice".to_string(), - path: vec!["photos".to_string()], - }, - WnfsAbility::Create, - 0, - &did_verifier_map, - &store, - )?; - - assert!(capabilities.is_empty()); - - Ok(()) - } - - #[test] - fn test_capabilities_for_root_capability_exact() -> Result<(), anyhow::Error> { - let store = InMemoryStore::::default(); - let did_verifier_map = DidVerifierMap::default(); - - let iss_key = ed25519_dalek::SigningKey::generate(&mut rand_core::OsRng); - let aud_key = ed25519_dalek::SigningKey::generate(&mut rand_core::OsRng); - - let ucan: Ucan = UcanBuilder::default() - .for_audience(aud_key.did()?) - .claiming_capability(Capability::new( - WnfsResource::PublicPath { - user: "alice".to_string(), - path: vec!["photos".to_string()], - }, - WnfsAbility::Create, - EmptyCaveat, - )) - .sign(&iss_key)?; - - let capabilities = ucan.capabilities_for( - iss_key.did()?, - WnfsResource::PublicPath { - user: "alice".to_string(), - path: vec!["photos".to_string()], - }, - WnfsAbility::Create, - 0, - &did_verifier_map, - &store, - )?; - - assert_eq!(capabilities.len(), 1); - - assert_eq!( - capabilities[0].resource().downcast_ref::(), - Some(&WnfsResource::PublicPath { - user: "alice".to_string(), - path: vec!["photos".to_string()], - }) - ); - - assert_eq!( - capabilities[0].ability().downcast_ref::(), - Some(&WnfsAbility::Create) - ); - - assert_eq!( - capabilities[0].caveat().downcast_ref::(), - Some(&EmptyCaveat) - ); - - Ok(()) - } - - #[test] - fn test_capabilities_for_root_capability_subsumed_by_semantics() -> Result<(), anyhow::Error> { - let store = InMemoryStore::::default(); - let did_verifier_map = DidVerifierMap::default(); - - let iss_key = ed25519_dalek::SigningKey::generate(&mut rand_core::OsRng); - let aud_key = ed25519_dalek::SigningKey::generate(&mut rand_core::OsRng); - - let ucan: Ucan = UcanBuilder::default() - .for_audience(aud_key.did()?) - .claiming_capability(Capability::new( - WnfsResource::PublicPath { - user: "alice".to_string(), - path: vec!["photos".to_string()], - }, - WnfsAbility::Overwrite, - EmptyCaveat, - )) - .sign(&iss_key)?; - - let capabilities = ucan.capabilities_for( - iss_key.did()?, - WnfsResource::PublicPath { - user: "alice".to_string(), - path: vec!["photos".to_string(), "vacation".to_string()], - }, - WnfsAbility::Create, - 0, - &did_verifier_map, - &store, - )?; - - assert_eq!(capabilities.len(), 1); - - assert_eq!( - capabilities[0].resource().downcast_ref::(), - Some(&WnfsResource::PublicPath { - user: "alice".to_string(), - path: vec!["photos".to_string(), "vacation".to_string()], - }) - ); - - assert_eq!( - capabilities[0].ability().downcast_ref::(), - Some(&WnfsAbility::Create) - ); - - assert_eq!( - capabilities[0].caveat().downcast_ref::(), - Some(&EmptyCaveat) - ); - - Ok(()) - } - - #[test] - fn test_capabilities_for_root_capability_subsumed_by_top() -> Result<(), anyhow::Error> { - let store = InMemoryStore::::default(); - let did_verifier_map = DidVerifierMap::default(); - - let iss_key = ed25519_dalek::SigningKey::generate(&mut rand_core::OsRng); - let aud_key = ed25519_dalek::SigningKey::generate(&mut rand_core::OsRng); - - let ucan: Ucan = UcanBuilder::default() - .for_audience(aud_key.did()?) - .claiming_capability(Capability::new( - WnfsResource::PublicPath { - user: "alice".to_string(), - path: vec!["photos".to_string()], - }, - TopAbility, - EmptyCaveat, - )) - .sign(&iss_key)?; - - let capabilities = ucan.capabilities_for( - iss_key.did()?, - WnfsResource::PublicPath { - user: "alice".to_string(), - path: vec!["photos".to_string(), "vacation".to_string()], - }, - WnfsAbility::Overwrite, - 0, - &did_verifier_map, - &store, - )?; - - assert_eq!(capabilities.len(), 1); - - assert_eq!( - capabilities[0].resource().downcast_ref::(), - Some(&WnfsResource::PublicPath { - user: "alice".to_string(), - path: vec!["photos".to_string(), "vacation".to_string()], - }) - ); - - assert_eq!( - capabilities[0].ability().downcast_ref::(), - Some(&WnfsAbility::Overwrite) - ); - - assert_eq!( - capabilities[0].caveat().downcast_ref::(), - Some(&EmptyCaveat) - ); - - Ok(()) - } - - #[test] - fn test_capabilities_for_invocation_no_lifetime() -> Result<(), anyhow::Error> { - let mut store = InMemoryStore::::default(); - let did_verifier_map = DidVerifierMap::default(); - - let iss_key = ed25519_dalek::SigningKey::generate(&mut rand_core::OsRng); - let aud_key = ed25519_dalek::SigningKey::generate(&mut rand_core::OsRng); - - let root_ucan: Ucan = UcanBuilder::default() - .for_audience(aud_key.did()?) - .claiming_capability(Capability::new( - WnfsResource::PublicPath { - user: "alice".to_string(), - path: vec!["photos".to_string()], - }, - TopAbility, - EmptyCaveat, - )) - .sign(&iss_key)?; - - store.write(Ipld::Bytes(root_ucan.encode()?.as_bytes().to_vec()), None)?; - - let invocation: Ucan = UcanBuilder::default() - .for_audience("did:web:fission.codes") - .claiming_capability(Capability::new( - WnfsResource::PublicPath { - user: "alice".to_string(), - path: vec!["photos".to_string()], - }, - WnfsAbility::Revise, - EmptyCaveat, - )) - .witnessed_by(&root_ucan, None) - .sign(&aud_key)?; - - let capabilities = invocation.capabilities_for( - iss_key.did()?, - WnfsResource::PublicPath { - user: "alice".to_string(), - path: vec!["photos".to_string()], - }, - WnfsAbility::Revise, - time::now(), - &did_verifier_map, - &store, - )?; - - assert_eq!(capabilities.len(), 1); - - assert_eq!( - capabilities[0].resource().downcast_ref::(), - Some(&WnfsResource::PublicPath { - user: "alice".to_string(), - path: vec!["photos".to_string()], - }) - ); - - assert_eq!( - capabilities[0].ability().downcast_ref::(), - Some(&WnfsAbility::Revise) - ); - - assert_eq!( - capabilities[0].caveat().downcast_ref::(), - Some(&EmptyCaveat) - ); - - Ok(()) - } - - #[test] - fn test_capabilities_for_invocation_lifetime_encompassed() -> Result<(), anyhow::Error> { - let mut store = InMemoryStore::::default(); - let did_verifier_map = DidVerifierMap::default(); - - let iss_key = ed25519_dalek::SigningKey::generate(&mut rand_core::OsRng); - let aud_key = ed25519_dalek::SigningKey::generate(&mut rand_core::OsRng); - - let root_ucan: Ucan = UcanBuilder::default() - .for_audience(aud_key.did()?) - .claiming_capability(Capability::new( - WnfsResource::PublicPath { - user: "alice".to_string(), - path: vec!["photos".to_string()], - }, - TopAbility, - EmptyCaveat, - )) - .with_lifetime(60) - .sign(&iss_key)?; - - store.write(Ipld::Bytes(root_ucan.encode()?.as_bytes().to_vec()), None)?; - - let invocation: Ucan = UcanBuilder::default() - .for_audience("did:web:fission.codes") - .claiming_capability(Capability::new( - WnfsResource::PublicPath { - user: "alice".to_string(), - path: vec!["photos".to_string()], - }, - WnfsAbility::Revise, - EmptyCaveat, - )) - .with_lifetime(30) - .witnessed_by(&root_ucan, None) - .sign(&aud_key)?; - - let capabilities = invocation.capabilities_for( - iss_key.did()?, - WnfsResource::PublicPath { - user: "alice".to_string(), - path: vec!["photos".to_string()], - }, - WnfsAbility::Revise, - time::now(), - &did_verifier_map, - &store, - )?; - - assert_eq!(capabilities.len(), 1); - - assert_eq!( - capabilities[0].resource().downcast_ref::(), - Some(&WnfsResource::PublicPath { - user: "alice".to_string(), - path: vec!["photos".to_string()], - }) - ); - - assert_eq!( - capabilities[0].ability().downcast_ref::(), - Some(&WnfsAbility::Revise) - ); - - assert_eq!( - capabilities[0].caveat().downcast_ref::(), - Some(&EmptyCaveat) - ); - - Ok(()) - } - - #[test] - fn test_capabilities_for_invocation_nbf_exposed() -> Result<(), anyhow::Error> { - let mut store = InMemoryStore::::default(); - let did_verifier_map = DidVerifierMap::default(); - - let iss_key = ed25519_dalek::SigningKey::generate(&mut rand_core::OsRng); - let aud_key = ed25519_dalek::SigningKey::generate(&mut rand_core::OsRng); - - let root_ucan: Ucan = UcanBuilder::default() - .for_audience(aud_key.did()?) - .claiming_capability(Capability::new( - WnfsResource::PublicPath { - user: "alice".to_string(), - path: vec!["photos".to_string()], - }, - TopAbility, - EmptyCaveat, - )) - .not_before(1) - .sign(&iss_key)?; - - store.write(Ipld::Bytes(root_ucan.encode()?.as_bytes().to_vec()), None)?; - - let invocation: Ucan = UcanBuilder::default() - .for_audience("did:web:fission.codes") - .claiming_capability(Capability::new( - WnfsResource::PublicPath { - user: "alice".to_string(), - path: vec!["photos".to_string()], - }, - WnfsAbility::Revise, - EmptyCaveat, - )) - .not_before(0) - .witnessed_by(&root_ucan, None) - .sign(&aud_key)?; - - let capabilities = invocation.capabilities_for( - iss_key.did()?, - WnfsResource::PublicPath { - user: "alice".to_string(), - path: vec!["photos".to_string()], - }, - WnfsAbility::Revise, - 0, - &did_verifier_map, - &store, - )?; - - assert_eq!(capabilities.len(), 0); - - Ok(()) - } - - #[test] - fn test_capabilities_for_invocation_exp_exposed() -> Result<(), anyhow::Error> { - let mut store = InMemoryStore::::default(); - let did_verifier_map = DidVerifierMap::default(); - - let iss_key = ed25519_dalek::SigningKey::generate(&mut rand_core::OsRng); - let aud_key = ed25519_dalek::SigningKey::generate(&mut rand_core::OsRng); - - let root_ucan: Ucan = UcanBuilder::default() - .for_audience(aud_key.did()?) - .claiming_capability(Capability::new( - WnfsResource::PublicPath { - user: "alice".to_string(), - path: vec!["photos".to_string()], - }, - TopAbility, - EmptyCaveat, - )) - .with_expiration(0) - .sign(&iss_key)?; - - store.write(Ipld::Bytes(root_ucan.encode()?.as_bytes().to_vec()), None)?; - - let invocation: Ucan = UcanBuilder::default() - .for_audience("did:web:fission.codes") - .claiming_capability(Capability::new( - WnfsResource::PublicPath { - user: "alice".to_string(), - path: vec!["photos".to_string()], - }, - WnfsAbility::Revise, - EmptyCaveat, - )) - .with_expiration(1) - .witnessed_by(&root_ucan, None) - .sign(&aud_key)?; - - let capabilities = invocation.capabilities_for( - iss_key.did()?, - WnfsResource::PublicPath { - user: "alice".to_string(), - path: vec!["photos".to_string()], - }, - WnfsAbility::Revise, - 0, - &did_verifier_map, - &store, - )?; - - assert_eq!(capabilities.len(), 0); - - Ok(()) - } - - #[test] - fn test_capabilities_for_invocation_lifetime_disjoint() -> Result<(), anyhow::Error> { - let mut store = InMemoryStore::::default(); - let did_verifier_map = DidVerifierMap::default(); - - let iss_key = ed25519_dalek::SigningKey::generate(&mut rand_core::OsRng); - let aud_key = ed25519_dalek::SigningKey::generate(&mut rand_core::OsRng); - - let root_ucan: Ucan = UcanBuilder::default() - .for_audience(aud_key.did()?) - .claiming_capability(Capability::new( - WnfsResource::PublicPath { - user: "alice".to_string(), - path: vec!["photos".to_string()], - }, - TopAbility, - EmptyCaveat, - )) - .not_before(0) - .with_expiration(1) - .sign(&iss_key)?; - - store.write(Ipld::Bytes(root_ucan.encode()?.as_bytes().to_vec()), None)?; - - let invocation: Ucan = UcanBuilder::default() - .for_audience("did:web:fission.codes") - .claiming_capability(Capability::new( - WnfsResource::PublicPath { - user: "alice".to_string(), - path: vec!["photos".to_string()], - }, - WnfsAbility::Revise, - EmptyCaveat, - )) - .not_before(2) - .with_expiration(3) - .witnessed_by(&root_ucan, None) - .sign(&aud_key)?; - - let capabilities = invocation.capabilities_for( - iss_key.did()?, - WnfsResource::PublicPath { - user: "alice".to_string(), - path: vec!["photos".to_string()], - }, - WnfsAbility::Revise, - 2, - &did_verifier_map, - &store, - )?; - - assert_eq!(capabilities.len(), 0); - - Ok(()) - } -} diff --git a/src/wasm.rs b/src/wasm.rs deleted file mode 100644 index 0575d3c1..00000000 --- a/src/wasm.rs +++ /dev/null @@ -1,310 +0,0 @@ -use anyhow::{anyhow, bail}; -use async_signature::AsyncSigner; -use async_trait::async_trait; -use js_sys::{Date, Error, Reflect, Uint8Array}; -use serde::{Deserialize, Serialize}; -use std::str::FromStr; -use wasm_bindgen::prelude::*; -use wasm_bindgen_futures::JsFuture; -use web_sys::{Crypto, CryptoKey, CryptoKeyPair, SubtleCrypto}; - -use crate::{builder::UcanBuilder, capability::DefaultCapabilityParser, crypto::SignerDid}; - -/// Convenience alias around `Result` -pub type JsResult = Result; - -/// A UCAN whose facts are a JSON value -#[wasm_bindgen] -#[derive(Clone, Debug, Serialize, Deserialize)] -pub struct Ucan { - ucan: crate::ucan::Ucan, -} - -struct JsSigner { - key_pair: CryptoKeyPair, - _marker: std::marker::PhantomData K>, -} - -impl JsSigner { - fn subtle_crypto() -> Result { - let global = js_sys::global(); - - match Reflect::get(&global, &JsValue::from_str("crypto")) { - Ok(value) => { - let crypto = value - .dyn_into::() - .map_err(|_| anyhow!("Failed to cast value to Crypto"))? - .subtle(); - - Ok(crypto) - } - Err(_) => bail!("Failed to get crypto from global object"), - } - } - - fn new(key_pair: CryptoKeyPair) -> Self { - Self { - key_pair, - _marker: std::marker::PhantomData, - } - } - - fn signing_key(&self) -> Result { - match Reflect::get(&self.key_pair, &JsValue::from_str("privateKey")) { - Ok(key) => key - .dyn_into::() - .map_err(|_| anyhow!("Failed to cast value to CryptoKey")), - Err(_) => bail!("Failed to get privateKey from CryptoKeyPair"), - } - } - - fn verifying_key(&self) -> Result { - match Reflect::get(&self.key_pair, &JsValue::from_str("publicKey")) { - Ok(key) => key - .dyn_into::() - .map_err(|_| anyhow!("Failed to cast value to CryptoKey")), - Err(_) => bail!("Failed to get publicKey from CryptoKeyPair"), - } - } -} - -impl SignerDid for JsSigner { - fn did(&self) -> Result { - Ok("test".to_string()) - } -} - -#[async_trait(?Send)] -impl AsyncSigner for JsSigner { - async fn sign_async( - &self, - msg: &[u8], - ) -> Result { - let subtle = Self::subtle_crypto().map_err(|e| async_signature::Error::from_source(e))?; - - // This can be done without copying using the unsafe `Uint8Array::view` method, - // but I've opted to stick to safe APIs for now, until we benchmark signing. - let data = Uint8Array::from(msg).buffer(); - - let key = self - .signing_key() - .map_err(|e| async_signature::Error::from_source(e))?; - - let promise = subtle - .sign_with_str_and_buffer_source("RSASSA-PKCS1-v1_5", &key, &data) - .map_err(|_| async_signature::Error::new())?; - - let result = JsFuture::from(promise) - .await - .map_err(|_| async_signature::Error::new())?; - - let signature = - rsa::pkcs1v15::Signature::try_from(Uint8Array::new(&result).to_vec().as_slice()) - .map_err(|_| async_signature::Error::new())?; - - Ok(signature) - } -} - -#[wasm_bindgen] -impl Ucan { - /// Returns a boolean indicating whether the given UCAN is expired at the given date - #[wasm_bindgen(js_name = "isExpired")] - pub fn is_expired(&self, at_time: &Date) -> bool { - let at_time = f64::floor(at_time.get_time() / 1000.) as u64; - - self.ucan.is_expired(at_time) - } - - /// Returns true if the UCAN is not yet valid at the given date - #[wasm_bindgen(js_name = "isTooEarly")] - pub fn is_too_early(&self, at_time: &Date) -> bool { - let at_time = f64::floor(at_time.get_time() / 1000.) as u64; - - self.ucan.is_too_early(at_time) - } - - /// Returns the UCAN's signature as a `Uint8Array` - #[wasm_bindgen(getter)] - pub fn signature(&self) -> Vec { - self.ucan.signature().to_vec() - } - - /// Returns the `typ` field of the UCAN's JWT header - #[wasm_bindgen(getter)] - pub fn typ(&self) -> String { - self.ucan.typ().to_string() - } - - /// Returns the `alg` field of the UCAN's JWT header - #[wasm_bindgen(getter)] - pub fn algorithm(&self) -> String { - self.ucan.algorithm().to_string() - } - - /// Returns the `iss` field of the UCAN's JWT payload - #[wasm_bindgen(getter)] - pub fn issuer(&self) -> String { - self.ucan.issuer().to_string() - } - - /// Returns the `aud` field of the UCAN's JWT payload - #[wasm_bindgen(getter)] - pub fn audience(&self) -> String { - self.ucan.audience().to_string() - } - - /// Returns the `exp` field of the UCAN's JWT payload - #[wasm_bindgen(getter, js_name = "expiresAt")] - pub fn expires_at(&self) -> Option { - self.ucan - .expires_at() - .map(|expires_at| Date::new(&JsValue::from_f64((expires_at as f64) * 1000.))) - } - - /// Returns the `nbf` field of the UCAN's JWT payload - #[wasm_bindgen(getter, js_name = "notBefore")] - pub fn not_before(&self) -> Option { - self.ucan - .not_before() - .map(|not_before| Date::new(&JsValue::from_f64((not_before as f64) * 1000.))) - } - - /// Returns the `nnc` field of the UCAN's JWT payload - #[wasm_bindgen(getter)] - pub fn nonce(&self) -> Option { - self.ucan.nonce().map(String::to_string) - } - - /// Returns the `fct` field of the UCAN's JWT payload - #[wasm_bindgen(getter)] - pub fn facts(&self) -> JsResult { - self.ucan - .facts() - .serialize(&serde_wasm_bindgen::Serializer::json_compatible()) - .map_err(|e| Error::new(&format!("Failed to serialize facts: {}", e))) - } - - /// Returns the `vsn` field of the UCAN's JWT payload - #[wasm_bindgen(getter)] - pub fn version(&self) -> String { - self.ucan.version().to_string() - } - - /// Returns the CID of the UCAN - #[wasm_bindgen] - pub fn cid(&self) -> JsResult { - match self.ucan.to_cid(None) { - Ok(cid) => Ok(cid.to_string()), - Err(e) => Err(Error::new(&format!("Failed to convert to CID: {}", e))), - } - } -} - -/// Decode a UCAN -#[wasm_bindgen] -pub async fn decode(token: String) -> JsResult { - let ucan = - crate::ucan::Ucan::from_str(&token).map_err(|e| Error::new(e.to_string().as_ref()))?; - - Ok(Ucan { ucan }) -} - -/// Options for building a UCAN -#[derive(Debug, Deserialize)] -pub struct BuildOptions { - /// The lifetime of the UCAN in seconds - #[serde(rename = "lifetimeInSeconds")] - pub lifetime_in_seconds: Option, - /// The expiration time of the UCAN in seconds since epoch - pub expiration: Option, - /// The time before which the UCAN is not valid in seconds since epoch - #[serde(rename = "notBefore")] - pub not_before: Option, - /// The facts included in the UCAN - pub facts: Option, - /// The proof CIDs referenced by the UCAN - pub proofs: Option>, - /// The nonce of the UCAN - pub nonce: Option, - // TODO: capabilities -} - -/// Build a UCAN -#[wasm_bindgen] -pub async fn build(issuer: CryptoKeyPair, audience: &str, options: JsValue) -> JsResult { - let options: BuildOptions = - serde_wasm_bindgen::from_value(options).map_err(|e| Error::new(e.to_string().as_ref()))?; - - let builder = - UcanBuilder::::default().for_audience(audience); - - let builder = match options.lifetime_in_seconds { - Some(lifetime_in_seconds) => builder.with_lifetime(lifetime_in_seconds), - None => builder, - }; - - let builder = match options.expiration { - Some(expiration) => builder.with_expiration(expiration), - None => builder, - }; - - let builder = match options.not_before { - Some(not_before) => builder.not_before(not_before), - None => builder, - }; - - let builder = match options.facts { - Some(facts) => builder.with_fact(facts), - None => builder, - }; - - let builder = match options.nonce { - Some(nonce) => builder.with_nonce(nonce), - None => builder, - }; - - // TODO: proofs (need store) - - let signer = JsSigner::::new(issuer); - - let ucan = builder - .sign_async(&signer) - .await - .map_err(|e| Error::new(e.to_string().as_ref()))?; - - Ok(Ucan { ucan }) -} - -/// Panic hook lets us get better error messages if our Rust code ever panics. -/// -/// For more details see -/// -#[wasm_bindgen(js_name = "setPanicHook")] -pub fn set_panic_hook() { - #[cfg(feature = "console_error_panic_hook")] - console_error_panic_hook::set_once(); -} - -#[wasm_bindgen] -extern "C" { - // For alerting - pub(crate) fn alert(s: &str); - // For logging in the console. - #[wasm_bindgen(js_namespace = console)] - pub fn log(s: &str); -} - -/// Return a representation of an object owned by JS. -#[macro_export] -macro_rules! value { - ($value:expr) => { - wasm_bindgen::JsValue::from($value) - }; -} - -/// Calls the wasm_bindgen console.log. -#[macro_export] -macro_rules! console_log { - ($($t:tt)*) => ($crate::log(&format_args!($($t)*).to_string())) -} From a48e35cc33be28035f6ad706830228ba32823e05 Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Fri, 16 Feb 2024 17:11:51 -0800 Subject: [PATCH 083/188] Attempting to remove some crates --- Cargo.toml | 7 +----- src/crypto.rs | 58 ++++++++++++++++++++++----------------------- src/crypto/eddsa.rs | 42 -------------------------------- src/crypto/es256.rs | 24 ------------------- src/url.rs | 21 +++++++++++++--- 5 files changed, 48 insertions(+), 104 deletions(-) delete mode 100644 src/crypto/eddsa.rs delete mode 100644 src/crypto/es256.rs diff --git a/Cargo.toml b/Cargo.toml index d653a035..35f36a86 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -30,16 +30,11 @@ path = "examples/counterparts.rs" [dependencies] anyhow = "1.0.75" aquamarine = { version = "0.5", optional = true } -async-signature = "0.4.0" +# FIXME actually use? async-signature = "0.4.0" async-trait = "0.1.73" base64 = "0.21" blst = { version = "0.3.11", optional = true, default-features = false } -cfg-if = "0.1" -# FIXME attempt to remove -cid = "0.11" did_url = "0.1" -downcast-rs = "1.2.0" -dyn-clone = "1.0.14" ecdsa = { version = "0.16.8", features = ["alloc"], optional = true, default-features = false } ed25519 = { version = "2.2.2", optional = true, default-features = false } ed25519-dalek = { version = "2.0.0", features = ["rand_core"], optional = true } diff --git a/src/crypto.rs b/src/crypto.rs index 18fecc8a..bc02b8bc 100644 --- a/src/crypto.rs +++ b/src/crypto.rs @@ -4,35 +4,35 @@ use signature::SignatureEncoding; pub mod domain_separator; -#[cfg(feature = "bls")] -pub mod bls12381; - -#[cfg(feature = "es512")] -pub mod p521; - -#[cfg(feature = "eddsa")] -pub mod eddsa; - -#[cfg(feature = "es256")] -pub mod es256; - -#[cfg(feature = "es256k")] -pub mod es256k; - -#[cfg(feature = "es384")] -pub mod es384; - -#[cfg(feature = "es512")] -pub mod es512; - -#[cfg(feature = "ps256")] -pub mod ps256; - -#[cfg(feature = "rs256")] -pub mod rs256; - -#[cfg(feature = "rs512")] -pub mod rs512; +// #[cfg(feature = "bls")] +// pub mod bls12381; +// +// #[cfg(feature = "es512")] +// pub mod p521; +// +// #[cfg(feature = "eddsa")] +// pub mod eddsa; +// +// #[cfg(feature = "es256")] +// pub mod es256; +// +// #[cfg(feature = "es256k")] +// pub mod es256k; +// +// #[cfg(feature = "es384")] +// pub mod es384; +// +// #[cfg(feature = "es512")] +// pub mod es512; +// +// #[cfg(feature = "ps256")] +// pub mod ps256; +// +// #[cfg(feature = "rs256")] +// pub mod rs256; +// +// #[cfg(feature = "rs512")] +// pub mod rs512; // FIXME switch to varsig /// A trait for mapping a SignatureEncoding to its algorithm name under JWS diff --git a/src/crypto/eddsa.rs b/src/crypto/eddsa.rs deleted file mode 100644 index 6bfc130f..00000000 --- a/src/crypto/eddsa.rs +++ /dev/null @@ -1,42 +0,0 @@ -//! EdDSA signature support - -#[cfg(feature = "eddsa-verifier")] -use anyhow::anyhow; -#[cfg(feature = "eddsa-verifier")] -use signature::Verifier; - -use multibase::Base; - -use super::{JWSSignature, SignerDid}; - -impl JWSSignature for ed25519::Signature { - const ALGORITHM: &'static str = "EdDSA"; -} - -impl SignerDid for ed25519_dalek::SigningKey { - fn did(&self) -> Result { - let mut buf = unsigned_varint::encode::u128_buffer(); - let multicodec = unsigned_varint::encode::u128(0xed, &mut buf); - - Ok(format!( - "did:key:{}", - multibase::encode( - Base::Base58Btc, - [multicodec, self.verifying_key().to_bytes().as_ref()].concat() - ) - )) - } -} - -/// A verifier for Ed25519 signatures using the `ed25519-dalek` crate -#[cfg(feature = "eddsa-verifier")] -pub fn eddsa_verifier(key: &[u8], payload: &[u8], signature: &[u8]) -> Result<(), anyhow::Error> { - let key = ed25519_dalek::VerifyingKey::try_from(key) - .map_err(|e| anyhow!("invalid Ed25519 key, {}", e))?; - - let signature = ed25519_dalek::Signature::try_from(signature) - .map_err(|e| anyhow!("invalid Ed25519 signature, {}", e))?; - - key.verify(payload, &signature) - .map_err(|e| anyhow!("signature mismatch, {}", e)) -} diff --git a/src/crypto/es256.rs b/src/crypto/es256.rs deleted file mode 100644 index 48f030ca..00000000 --- a/src/crypto/es256.rs +++ /dev/null @@ -1,24 +0,0 @@ -//! ES256 signature support - -#[cfg(feature = "es256-verifier")] -use anyhow::anyhow; -#[cfg(feature = "es256-verifier")] -use signature::Verifier; - -use super::JWSSignature; - -impl JWSSignature for p256::ecdsa::Signature { - const ALGORITHM: &'static str = "ES256"; -} - -/// A verifier for PS256 signatures -#[cfg(feature = "es256-verifier")] -pub fn es256_verifier(key: &[u8], payload: &[u8], signature: &[u8]) -> Result<(), anyhow::Error> { - let key = p256::ecdsa::VerifyingKey::try_from(key).map_err(|_| anyhow!("invalid P-256 key"))?; - - let signature = - p256::ecdsa::Signature::try_from(signature).map_err(|_| anyhow!("invalid P-256 key"))?; - - key.verify(payload, &signature) - .map_err(|e| anyhow!("signature mismatch, {}", e)) -} diff --git a/src/url.rs b/src/url.rs index b97d9b64..4e7a50a4 100644 --- a/src/url.rs +++ b/src/url.rs @@ -1,7 +1,8 @@ //! URL utilities. -use libipld_core::{error::SerdeError, ipld::Ipld, serde as ipld_serde}; +use libipld_core::ipld::Ipld; use serde::{Deserialize, Serialize}; +use thiserror::Error; use url::Url; /// A wrapper around [`Url`] that has additional trait implementations @@ -39,9 +40,23 @@ impl From for Ipld { } impl TryFrom for Newtype { - type Error = SerdeError; + type Error = FromIpldError; fn try_from(ipld: Ipld) -> Result { - ipld_serde::from_ipld(ipld) + match ipld { + Ipld::String(s) => Url::parse(&s) + .map(Newtype) + .map_err(FromIpldError::UrlParseError), + _ => Err(FromIpldError::NotAString), + } } } + +#[derive(Debug, Error)] +pub enum FromIpldError { + #[error("Not an IPLD string")] + NotAString, + + #[error(transparent)] + UrlParseError(#[from] url::ParseError), +} From bcf6a507ed1bd1a6480ca41f6e49bc9f679c91f5 Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Fri, 16 Feb 2024 17:44:06 -0800 Subject: [PATCH 084/188] Remove unused crates --- Cargo.toml | 113 +++++++++++++------------------------- src/crypto.rs | 94 +++++++++++++++---------------- src/crypto/eddsa.rs | 36 ++++++++++++ src/crypto/es256.rs | 24 ++++++++ src/crypto/es256k.rs | 34 ++++++------ src/crypto/es384.rs | 34 ++++++------ src/crypto/es512.rs | 10 ++-- src/crypto/ps256.rs | 50 ++++++++--------- src/invocation/agent.rs | 3 +- src/invocation/payload.rs | 21 +------ src/nonce.rs | 17 +++--- src/signature.rs | 20 +++---- 12 files changed, 231 insertions(+), 225 deletions(-) create mode 100644 src/crypto/eddsa.rs create mode 100644 src/crypto/es256.rs diff --git a/Cargo.toml b/Cargo.toml index 35f36a86..fa92eb96 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,8 +10,11 @@ readme = "README.md" edition = "2021" rust-version = "1.75" documentation = "https://docs.rs/ucan" -repository = "https://github.com/ucan-wg/ucan" -authors = ["Quinn Wilton ", "Brooklyn Zelenka ", + "Brooklyn Zelenka " +] [lib] crate-type = ["cdylib", "rlib"] @@ -19,7 +22,7 @@ path = "src/lib.rs" bench = false [[bench]] -name = "a_benchmark" # TODO rename +name = "a_benchmark" # FIXME rename harness = false required-features = ["test_utils"] @@ -28,48 +31,47 @@ name = "counterparts" path = "examples/counterparts.rs" [dependencies] -anyhow = "1.0.75" -aquamarine = { version = "0.5", optional = true } -# FIXME actually use? async-signature = "0.4.0" -async-trait = "0.1.73" -base64 = "0.21" +getrandom = { version = "0.2", features = ["js", "rdrand"] } +nonempty = { version = "0.9" } +proptest = { version = "1.1", optional = true } +regex = "1.10" +unsigned-varint = "0.7.2" + +# Crypto blst = { version = "0.3.11", optional = true, default-features = false } -did_url = "0.1" ecdsa = { version = "0.16.8", features = ["alloc"], optional = true, default-features = false } -ed25519 = { version = "2.2.2", optional = true, default-features = false } ed25519-dalek = { version = "2.0.0", features = ["rand_core"], optional = true } -enum-as-inner = "0.6" -erased-serde = "0.3.31" -getrandom = { version = "0.2", features = ["js", "rdrand"] } -jose-b64 = { version = "0.1.2", features = ["serde", "json"] } k256 = { version = "0.13.1", features = ["ecdsa"], optional = true, default-features = false } -lazy_static = "1.4.0" -libipld-core = { version = "0.16", features = ["serde-codec"] } -libipld-cbor = "0.16" -multibase = "0.9" -multihash = { version = "0.18", features = ["sha2"] } -nonempty = { version = "0.9" } p256 = { version = "0.13.2", features = ["alloc", "ecdsa"], optional = true, default-features = false } p384 = { version = "0.13.0", features = ["alloc", "ecdsa"], optional = true, default-features = false } p521 = { version = "0.13.0", features = ["alloc", "ecdsa", "getrandom"], optional = true, default-features = false } -proptest = { version = "1.1", optional = true } -regex = "1.10" rsa = { version = "0.9.6", features = ["sha2"], optional = true, default-features = false } -semver = "1.0.19" +signature = { version = "2.1.0", features = ["alloc"] } + +# Encoding +base64 = "0.21" serde = { version = "1.0.188", features = ["derive"] } serde_derive = "1.0" -serde_json = "1.0.107" -serde_with = {version = "3.5", features = ["alloc"] } -signature = { version = "2.1.0", features = ["alloc"] } -thiserror = "1.0" -tracing = "0.1.40" -unsigned-varint = "0.7.2" + +# Web Stack +did_url = "0.1" url = { version = "2.5", features = ["serde"] } web-time = "0.2.3" -[target.'cfg(not(target_arch = "wasm32"))'.dependencies] -# `linkme` relies on linker features that aren't available in wasm32 -linkme = "0.3.15" +# Interplanetary Stack +libipld-core = { version = "0.16", features = ["serde-codec"] } +libipld-cbor = "0.16" +multihash = { version = "0.18" } + +# Code Convenience +enum-as-inner = "0.6" +thiserror = "1.0" + +# Docs +aquamarine = { version = "0.5", optional = true } + +# FIXME actually use? async-signature = "0.4.0" +# FIXME see if possible to push aquamarkine into dev dependencies? # FIXME also have a wasi target [target.'cfg(target_arch = "wasm32")'.dependencies] @@ -78,11 +80,10 @@ js-sys = { version = "0.3" } serde-wasm-bindgen = "0.6" wasm-bindgen = "0.2" wasm-bindgen-derive = "0.2" -wasm-bindgen-futures = { version = "0.4" } +# FIXME? wasm-bindgen-futures = { version = "0.4" } web-sys = { version = "0.3", features = ["Crypto", "CryptoKey", "CryptoKeyPair", "SubtleCrypto"] } [dev-dependencies] -multihash = "0.18" libipld = "0.16" [target.'cfg(not(target_arch = "wasm32"))'.dev-dependencies] @@ -96,17 +97,6 @@ wasm-bindgen-test = "0.2" [features] default = [ - "did-key", - "eddsa-verifier", - "es256-verifier", - "es256k-verifier", - "es384-verifier", - "ps256-verifier", - "rs256-verifier", - "rs512-verifier", - "es512-verifier", - "bls-verifier", - # FIXME the below while debugging "es256", "es256k", "es384", @@ -116,10 +106,8 @@ default = [ "eddsa", "bls" ] - test_utils = ["proptest"] -did-key = [] # FIXME remove? -eddsa = ["dep:ed25519", "dep:ed25519-dalek"] +eddsa = ["dep:ed25519-dalek"] es256 = ["dep:p256"] es256k = ["dep:k256"] es384 = ["dep:p384"] @@ -128,40 +116,15 @@ ps256 = ["dep:rsa"] rs256 = ["dep:rsa"] rs512 = ["dep:rsa"] bls = ["dep:blst"] -# FIXME rename for varsig? -# FIXME actually remove these since they got ported to traits -eddsa-verifier = ["eddsa"] -es256-verifier = ["es256"] -es256k-verifier = ["es256k"] -es384-verifier = ["es384"] -es512-verifier = ["es512"] -ps256-verifier = ["ps256"] -rs256-verifier = ["rs256"] -rs512-verifier = ["rs512"] -bls-verifier = ["bls"] mermaid_docs = ["aquamarine"] [package.metadata.docs.rs] all-features = true +# # defines the configuration attribute `docsrs` rustdoc-args = ["--cfg", "docsrs"] cargo-args = ["--features='mermaid_docs'"] -# See https://doc.rust-lang.org/cargo/reference/profiles.html for more info. -# [profile.release] -# Do not perform backtrace for panic on release builds. -## panic = 'abort' -# Perform optimizations on all codegen units. -## codegen-units = 1 -# Tell `rustc` to optimize for small code size. -## opt-level = "s" # or 'z' to optimize "aggressively" for size -# Enable link time optimization. -## lto = true -# Amount of debug information. -# 0/false: no debug info at all; 1: line tables only; 2/true: full debug info -## debug = false -# Strip debug symbols -## strip = "symbols" - +# # Speedup build on macOS # See https://blog.rust-lang.org/2021/03/25/Rust-1.51.0.html#splitting-debug-information [profile.dev] diff --git a/src/crypto.rs b/src/crypto.rs index bc02b8bc..12df6c78 100644 --- a/src/crypto.rs +++ b/src/crypto.rs @@ -1,52 +1,52 @@ //! Cryptography utilities -use signature::SignatureEncoding; +// use signature::SignatureEncoding; pub mod domain_separator; -// #[cfg(feature = "bls")] -// pub mod bls12381; -// -// #[cfg(feature = "es512")] -// pub mod p521; -// -// #[cfg(feature = "eddsa")] -// pub mod eddsa; -// -// #[cfg(feature = "es256")] -// pub mod es256; -// -// #[cfg(feature = "es256k")] -// pub mod es256k; -// -// #[cfg(feature = "es384")] -// pub mod es384; -// -// #[cfg(feature = "es512")] -// pub mod es512; -// -// #[cfg(feature = "ps256")] -// pub mod ps256; -// -// #[cfg(feature = "rs256")] -// pub mod rs256; -// -// #[cfg(feature = "rs512")] -// pub mod rs512; - -// FIXME switch to varsig -/// A trait for mapping a SignatureEncoding to its algorithm name under JWS -pub trait JWSSignature: SignatureEncoding { - /// The algorithm name under JWS - // I'd originally referenced JWA types directly here, but supporting - // unspecified algorithms, like BLS, means leaving things more open-ended. - const ALGORITHM: &'static str; -} - -/// A trait for mapping a Signer to its DID. In most cases, this will -/// be a DID with method did-key, however other methods can be supported -/// by implementing this trait for a custom signer. -pub trait SignerDid { - /// The DID of the signer - fn did(&self) -> Result; -} +#[cfg(feature = "bls")] +pub mod bls12381; + +#[cfg(feature = "es512")] +pub mod p521; + +#[cfg(feature = "eddsa")] +pub mod eddsa; + +#[cfg(feature = "es256")] +pub mod es256; + +#[cfg(feature = "es256k")] +pub mod es256k; + +#[cfg(feature = "es384")] +pub mod es384; + +#[cfg(feature = "es512")] +pub mod es512; + +#[cfg(feature = "ps256")] +pub mod ps256; + +#[cfg(feature = "rs256")] +pub mod rs256; + +#[cfg(feature = "rs512")] +pub mod rs512; + +// // FIXME switch to varsig +// /// A trait for mapping a SignatureEncoding to its algorithm name under JWS +// pub trait JWSSignature: SignatureEncoding { +// /// The algorithm name under JWS +// // I'd originally referenced JWA types directly here, but supporting +// // unspecified algorithms, like BLS, means leaving things more open-ended. +// const ALGORITHM: &'static str; +// } + +// /// A trait for mapping a Signer to its DID. In most cases, this will +// /// be a DID with method did-key, however other methods can be supported +// /// by implementing this trait for a custom signer. +// pub trait SignerDid { +// /// The DID of the signer +// fn did(&self) -> Result; +// } diff --git a/src/crypto/eddsa.rs b/src/crypto/eddsa.rs new file mode 100644 index 00000000..0b8ef8c7 --- /dev/null +++ b/src/crypto/eddsa.rs @@ -0,0 +1,36 @@ +//! EdDSA signature support + +#[cfg(feature = "eddsa-verifier")] +use anyhow::anyhow; +#[cfg(feature = "eddsa-verifier")] +use signature::Verifier; + +// use multibase::Base; + +// impl SignerDid for ed25519_dalek::SigningKey { +// fn did(&self) -> Result { +// let mut buf = unsigned_varint::encode::u128_buffer(); +// let multicodec = unsigned_varint::encode::u128(0xed, &mut buf); +// +// Ok(format!( +// "did:key:{}", +// multibase::encode( +// Base::Base58Btc, +// [multicodec, self.verifying_key().to_bytes().as_ref()].concat() +// ) +// )) +// } +// } + +// /// A verifier for Ed25519 signatures using the `ed25519-dalek` crate +// #[cfg(feature = "eddsa-verifier")] +// pub fn eddsa_verifier(key: &[u8], payload: &[u8], signature: &[u8]) -> Result<(), anyhow::Error> { +// let key = ed25519_dalek::VerifyingKey::try_from(key) +// .map_err(|e| anyhow!("invalid Ed25519 key, {}", e))?; +// +// let signature = ed25519_dalek::Signature::try_from(signature) +// .map_err(|e| anyhow!("invalid Ed25519 signature, {}", e))?; +// +// key.verify(payload, &signature) +// .map_err(|e| anyhow!("signature mismatch, {}", e)) +// } diff --git a/src/crypto/es256.rs b/src/crypto/es256.rs new file mode 100644 index 00000000..390b3074 --- /dev/null +++ b/src/crypto/es256.rs @@ -0,0 +1,24 @@ +//! ES256 signature support + +#[cfg(feature = "es256-verifier")] +use anyhow::anyhow; +#[cfg(feature = "es256-verifier")] +use signature::Verifier; + +// use super::JWSSignature; +// +// impl JWSSignature for p256::ecdsa::Signature { +// const ALGORITHM: &'static str = "ES256"; +// } +// +// /// A verifier for PS256 signatures +// #[cfg(feature = "es256-verifier")] +// pub fn es256_verifier(key: &[u8], payload: &[u8], signature: &[u8]) -> Result<(), anyhow::Error> { +// let key = p256::ecdsa::VerifyingKey::try_from(key).map_err(|_| anyhow!("invalid P-256 key"))?; +// +// let signature = +// p256::ecdsa::Signature::try_from(signature).map_err(|_| anyhow!("invalid P-256 key"))?; +// +// key.verify(payload, &signature) +// .map_err(|e| anyhow!("signature mismatch, {}", e)) +// } diff --git a/src/crypto/es256k.rs b/src/crypto/es256k.rs index e521b50e..1000f553 100644 --- a/src/crypto/es256k.rs +++ b/src/crypto/es256k.rs @@ -5,21 +5,21 @@ use anyhow::anyhow; #[cfg(feature = "es256k-verifier")] use signature::Verifier; -use super::JWSSignature; +// use super::JWSSignature; +// +// impl JWSSignature for k256::ecdsa::Signature { +// const ALGORITHM: &'static str = "ES256K"; +// } -impl JWSSignature for k256::ecdsa::Signature { - const ALGORITHM: &'static str = "ES256K"; -} - -/// A verifier for ES256k signatures -#[cfg(feature = "es256k-verifier")] -pub fn es256k_verifier(key: &[u8], payload: &[u8], signature: &[u8]) -> Result<(), anyhow::Error> { - let key = - k256::ecdsa::VerifyingKey::try_from(key).map_err(|_| anyhow!("invalid secp256k1 key"))?; - - let signature = k256::ecdsa::Signature::try_from(signature) - .map_err(|_| anyhow!("invalid secp256k1 key"))?; - - key.verify(payload, &signature) - .map_err(|e| anyhow!("signature mismatch, {}", e)) -} +// A verifier for ES256k signatures +// #[cfg(feature = "es256k-verifier")] +// pub fn es256k_verifier(key: &[u8], payload: &[u8], signature: &[u8]) -> Result<(), anyhow::Error> { +// let key = +// k256::ecdsa::VerifyingKey::try_from(key).map_err(|_| anyhow!("invalid secp256k1 key"))?; +// +// let signature = k256::ecdsa::Signature::try_from(signature) +// .map_err(|_| anyhow!("invalid secp256k1 key"))?; +// +// key.verify(payload, &signature) +// .map_err(|e| anyhow!("signature mismatch, {}", e)) +// } diff --git a/src/crypto/es384.rs b/src/crypto/es384.rs index 8f8687dc..25709142 100644 --- a/src/crypto/es384.rs +++ b/src/crypto/es384.rs @@ -5,20 +5,20 @@ use anyhow::anyhow; #[cfg(feature = "es384-verifier")] use signature::Verifier; -use super::JWSSignature; - -impl JWSSignature for p384::ecdsa::Signature { - const ALGORITHM: &'static str = "ES384"; -} - -/// A verifier for ES384 signatures -#[cfg(feature = "es384-verifier")] -pub fn es384_verifier(key: &[u8], payload: &[u8], signature: &[u8]) -> Result<(), anyhow::Error> { - let key = p384::ecdsa::VerifyingKey::try_from(key).map_err(|_| anyhow!("invalid P-384 key"))?; - - let signature = - p384::ecdsa::Signature::try_from(signature).map_err(|_| anyhow!("invalid P-384 key"))?; - - key.verify(payload, &signature) - .map_err(|e| anyhow!("signature mismatch, {}", e)) -} +//use super::JWSSignature; +// +//impl JWSSignature for p384::ecdsa::Signature { +// const ALGORITHM: &'static str = "ES384"; +//} +// +///// A verifier for ES384 signatures +//#[cfg(feature = "es384-verifier")] +//pub fn es384_verifier(key: &[u8], payload: &[u8], signature: &[u8]) -> Result<(), anyhow::Error> { +// let key = p384::ecdsa::VerifyingKey::try_from(key).map_err(|_| anyhow!("invalid P-384 key"))?; +// +// let signature = +// p384::ecdsa::Signature::try_from(signature).map_err(|_| anyhow!("invalid P-384 key"))?; +// +// key.verify(payload, &signature) +// .map_err(|e| anyhow!("signature mismatch, {}", e)) +//} diff --git a/src/crypto/es512.rs b/src/crypto/es512.rs index f62653ca..0d4d4284 100644 --- a/src/crypto/es512.rs +++ b/src/crypto/es512.rs @@ -1,7 +1,7 @@ //! ES512 signature support -use super::JWSSignature; - -impl JWSSignature for ecdsa::Signature { - const ALGORITHM: &'static str = "ES512"; -} +// use super::JWSSignature; +// +// impl JWSSignature for ecdsa::Signature { +// const ALGORITHM: &'static str = "ES512"; +// } diff --git a/src/crypto/ps256.rs b/src/crypto/ps256.rs index 547548d7..9895df2c 100644 --- a/src/crypto/ps256.rs +++ b/src/crypto/ps256.rs @@ -1,27 +1,27 @@ //! PS256 signature support -#[cfg(feature = "ps256-verifier")] -use anyhow::anyhow; -#[cfg(feature = "ps256-verifier")] -use signature::Verifier; - -use super::JWSSignature; - -impl JWSSignature for rsa::pss::Signature { - const ALGORITHM: &'static str = "PS256"; -} - -/// A verifier for RS256 signatures -#[cfg(feature = "ps256-verifier")] -pub fn ps256_verifier(key: &[u8], payload: &[u8], signature: &[u8]) -> Result<(), anyhow::Error> { - let key = rsa::pkcs1::DecodeRsaPublicKey::from_pkcs1_der(key) - .map_err(|e| anyhow!("invalid PKCS#1 key, {}", e))?; - - let key = rsa::pss::VerifyingKey::::new(key); - - let signature = rsa::pss::Signature::try_from(signature) - .map_err(|e| anyhow!("invalid RSASSA-PKCS1-v1_5 signature, {}", e))?; - - key.verify(payload, &signature) - .map_err(|e| anyhow!("signature mismatch, {}", e)) -} +// #[cfg(feature = "ps256-verifier")] +// use anyhow::anyhow; +// #[cfg(feature = "ps256-verifier")] +// use signature::Verifier; +// +// use super::JWSSignature; +// +// impl JWSSignature for rsa::pss::Signature { +// const ALGORITHM: &'static str = "PS256"; +// } +// +// /// A verifier for RS256 signatures +// #[cfg(feature = "ps256-verifier")] +// pub fn ps256_verifier(key: &[u8], payload: &[u8], signature: &[u8]) -> Result<(), anyhow::Error> { +// let key = rsa::pkcs1::DecodeRsaPublicKey::from_pkcs1_der(key) +// .map_err(|e| anyhow!("invalid PKCS#1 key, {}", e))?; +// +// let key = rsa::pss::VerifyingKey::::new(key); +// +// let signature = rsa::pss::Signature::try_from(signature) +// .map_err(|e| anyhow!("invalid RSASSA-PKCS1-v1_5 signature, {}", e))?; +// +// key.verify(payload, &signature) +// .map_err(|e| anyhow!("signature mismatch, {}", e)) +// } diff --git a/src/invocation/agent.rs b/src/invocation/agent.rs index 0c96c26a..4a52204c 100644 --- a/src/invocation/agent.rs +++ b/src/invocation/agent.rs @@ -134,8 +134,7 @@ where ); let mut encoded = vec![]; - promised - .payload + Ipld::from(promised.payload.clone()) // FIXME use the varsig headre to get the codec .encode(DagCborCodec, &mut encoded) .expect("FIXME"); diff --git a/src/invocation/payload.rs b/src/invocation/payload.rs index 6d89672b..9fa5dcf4 100644 --- a/src/invocation/payload.rs +++ b/src/invocation/payload.rs @@ -9,14 +9,8 @@ use crate::{ signature::Verifiable, time::Timestamp, }; -use anyhow; -use libipld_core::{ - cid::Cid, - codec::{Codec, Encode}, - error::SerdeError, - ipld::Ipld, - serde as ipld_serde, -}; +// use anyhow; +use libipld_core::{cid::Cid, error::SerdeError, ipld::Ipld, serde as ipld_serde}; use serde::{Serialize, Serializer}; use std::{collections::BTreeMap, fmt::Debug}; use web_time::SystemTime; @@ -377,14 +371,3 @@ impl>, DID: Did> From } } } - -impl Encode for Payload -where - Ipld: Encode, - Payload: Clone, // FIXME -{ - fn encode(&self, codec: C, writer: &mut W) -> Result<(), anyhow::Error> { - let ipld = Ipld::from(self.clone()); - ipld.encode(codec, writer) - } -} diff --git a/src/nonce.rs b/src/nonce.rs index 1cee2fc3..a6a81a85 100644 --- a/src/nonce.rs +++ b/src/nonce.rs @@ -2,6 +2,7 @@ //! //! [Nonce]: https://en.wikipedia.org/wiki/Cryptographic_nonce +// FIXME use enum_as_inner more? use enum_as_inner::EnumAsInner; use getrandom::getrandom; use libipld_core::{ @@ -296,12 +297,12 @@ mod test { } // FIXME prop test with lots of inputs - #[test] - fn ser_de() { - let gen = Nonce::generate_16(&mut vec![]); - let ser = serde_json::to_string(&gen).unwrap(); - let de = serde_json::from_str(&ser).unwrap(); - - assert_eq!(gen, de); - } + // #[test] + // fn ser_de() { + // let gen = Nonce::generate_16(&mut vec![]); + // let ser = serde_json::to_string(&gen).unwrap(); + // let de = serde_json::from_str(&ser).unwrap(); + + // assert_eq!(gen, de); + // } } diff --git a/src/signature.rs b/src/signature.rs index 2d4e9dac..414a9075 100644 --- a/src/signature.rs +++ b/src/signature.rs @@ -1,7 +1,7 @@ //! Signatures and cryptographic envelopes. use crate::{capsule::Capsule, did::Did}; -use anyhow; +// use anyhow; use libipld_core::{ cid::{Cid, CidGeneric}, codec::{Codec, Encode}, @@ -113,15 +113,15 @@ pub enum Signature { // Right = 1, //} -impl + Capsule + Into, DID: Did> Encode for Envelope -where - Ipld: Encode, - Envelope: Clone, // FIXME? -{ - fn encode(&self, codec: C, writer: &mut W) -> Result<(), anyhow::Error> { - Ipld::from((*self).clone()).encode(codec, writer) - } -} +// impl + Capsule + Into, DID: Did> Encode for Envelope +// where +// Ipld: Encode, +// Envelope: Clone, // FIXME? +// { +// fn encode(&self, codec: C, writer: &mut W) -> Result<(), anyhow::Error> { +// Ipld::from((*self).clone()).encode(codec, writer) +// } +// } impl From> for Ipld { fn from(signature: Signature) -> Self { From a8ff1f3f0e7d91af938a82bcafd47ce4d0ac55fc Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Fri, 16 Feb 2024 21:32:13 -0800 Subject: [PATCH 085/188] Custom serilaizer --- src/ability/command.rs | 6 +- src/delegation/payload.rs | 19 +- src/invocation/payload.rs | 410 ++++++++++++++++++-------------------- 3 files changed, 204 insertions(+), 231 deletions(-) diff --git a/src/ability/command.rs b/src/ability/command.rs index 18458fc9..1ab0d979 100644 --- a/src/ability/command.rs +++ b/src/ability/command.rs @@ -57,14 +57,14 @@ pub trait Command { // FIXME definitely needs a better name pub trait ParseAbility: Sized { - type Error: fmt::Display; + type Error: fmt::Debug; // FIXME rename this trait to Ability? fn try_parse(cmd: &str, args: &arguments::Named) -> Result; } #[derive(Debug, Clone, Error)] -pub enum ParseAbilityError { +pub enum ParseAbilityError { #[error("Unknown command")] UnknownCommand, @@ -74,7 +74,7 @@ pub enum ParseAbilityError { impl> ParseAbility for T where - >::Error: fmt::Display, + >::Error: fmt::Debug, { type Error = ParseAbilityError<>::Error>; diff --git a/src/delegation/payload.rs b/src/delegation/payload.rs index 0047767c..b0024c14 100644 --- a/src/delegation/payload.rs +++ b/src/delegation/payload.rs @@ -168,14 +168,16 @@ where where S: Serializer, { - let mut state = serializer.serialize_struct("delegation::Payload", 9)?; + let count_nbf = if self.not_before.is_some() { 1 } else { 0 }; + + let mut state = serializer.serialize_struct("delegation::Payload", 8 + count_nbf)?; + state.serialize_field("iss", &self.issuer.clone().into().to_string())?; state.serialize_field("sub", &self.subject.clone().into().to_string())?; state.serialize_field("aud", &self.audience.clone().into().to_string())?; state.serialize_field("meta", &self.metadata)?; state.serialize_field("nonce", &self.nonce)?; state.serialize_field("exp", &self.expiration)?; - state.serialize_field("nbf", &self.not_before)?; state.serialize_field("cmd", &self.ability_builder.to_command())?; @@ -193,6 +195,10 @@ where .collect::>(), )?; + if let Some(nbf) = self.not_before { + state.serialize_field("nbf", &nbf)?; + } + state.end() } } @@ -218,7 +224,7 @@ impl< impl< 'de, - T: ParseAbility + ToCommand + Deserialize<'de>, + T: ParseAbility + Deserialize<'de>, C: Condition + Deserialize<'de>, DID: Did + Deserialize<'de>, > Visitor<'de> for DelegationPayloadVisitor @@ -229,10 +235,7 @@ impl< formatter.write_str("struct delegation::Payload") } - fn visit_map(self, mut map: M) -> Result - where - M: MapAccess<'de>, - { + fn visit_map>(self, mut map: M) -> Result { let mut issuer = None; let mut subject = None; let mut audience = None; @@ -318,7 +321,7 @@ impl< let ability_builder = ::try_parse(cmd.as_str(), &args).map_err(|e| { de::Error::custom(format!( - "Unable to parse ability field for {} because {}", + "Unable to parse ability field for {:?} because {:?}", cmd, e )) })?; diff --git a/src/invocation/payload.rs b/src/invocation/payload.rs index 9fa5dcf4..ed03a41d 100644 --- a/src/invocation/payload.rs +++ b/src/invocation/payload.rs @@ -1,17 +1,24 @@ use super::resolvable::Resolvable; use crate::{ - ability::{arguments, command::ToCommand}, + ability::{ + arguments, + command::{ParseAbility, ToCommand}, + }, capsule::Capsule, delegation::{self, condition::Condition, error::DelegationError, Delegable}, - did::{self, Did}, + did::Did, nonce::Nonce, proof::{checkable::Checkable, prove::Prove}, signature::Verifiable, time::Timestamp, }; // use anyhow; -use libipld_core::{cid::Cid, error::SerdeError, ipld::Ipld, serde as ipld_serde}; -use serde::{Serialize, Serializer}; +use libipld_core::{cid::Cid, ipld::Ipld}; +use serde::{ + de::{self, MapAccess, Visitor}, + ser::SerializeStruct, + Deserialize, Serialize, Serializer, +}; use std::{collections::BTreeMap, fmt::Debug}; use web_time::SystemTime; @@ -133,241 +140,204 @@ impl, DID: Did> From> for arguments::N } } -/// A variant that accepts [`Promise`]s. -/// -/// [`Promise`]: crate::invocation::promise::Promise -pub type Promised = Payload<::Promised, DID>; - -// impl Delegable for Payload { -// type Builder = Payload; -// } - -// use crate::proof::parentful::Parentful; -// -// impl Checkable for Payload -// where -// T::Builder: Checkable>, -// { -// type Hierarchy = (); -// } - -// impl TryFrom> for Payload { -// fn from(payload: Payload) -> Self { -// Payload { -// issuer: payload.issuer, -// subject: payload.subject, -// audience: payload.audience, -// -// ability: T::from(payload.ability), -// -// proofs: payload.proofs, -// cause: payload.cause, -// metadata: payload.metadata, -// nonce: payload.nonce, -// -// not_before: payload.not_before, -// expiration: payload.expiration, -// } -// } -// } - -// impl Resolvable for Payload -// where -// arguments::Named: From, -// Ipld: From, -// T::Promised: ToCommand, -// { -// type Promised = Promised; -// -// fn try_resolve(promised: Promised) -> Result { -// match ::try_resolve(promised.ability) { -// Ok(resolved_ability) => Ok(Payload { -// issuer: promised.issuer, -// subject: promised.subject, -// audience: promised.audience, -// -// ability: resolved_ability, -// -// proofs: promised.proofs, -// cause: promised.cause, -// metadata: promised.metadata, -// nonce: promised.nonce, -// -// not_before: promised.not_before, -// expiration: promised.expiration, -// }), -// Err(promised_ability) => Err(Payload { -// ability: promised_ability, -// ..promised -// }), -// } -// } -// } - -impl Serialize for Payload +impl Serialize for Payload where - Payload: Clone, - InternalSerializer: From>, + T: ToCommand + Into + Serialize, + DID: Did + Serialize, { fn serialize(&self, serializer: S) -> Result where S: Serializer, { - let s = InternalSerializer::from(self.clone()); - serde::Serialize::serialize(&s, serializer) + let mut field_count = 9; + if self.audience.is_some() { + field_count += 1 + }; + if self.not_before.is_some() { + field_count += 1 + }; + if self.expiration.is_some() { + field_count += 1 + }; + + let mut state = serializer.serialize_struct("invocation::Payload", field_count)?; + + state.serialize_field("iss", &self.issuer)?; + state.serialize_field("sub", &self.subject)?; + + state.serialize_field("cmd", &self.ability.to_command())?; + state.serialize_field("args", &self.ability)?; + + state.serialize_field("prf", &self.proofs)?; + state.serialize_field("nonce", &self.nonce)?; + state.serialize_field("cause", &self.cause)?; + state.serialize_field("meta", &self.metadata)?; + + if let Some(aud) = &self.audience { + state.serialize_field("aud", aud)?; + } + + if let Some(nbf) = &self.not_before { + state.serialize_field("nbf", nbf)?; + } + + if let Some(exp) = &self.expiration { + state.serialize_field("exp", &exp)?; + } + + state.end() } } -impl<'de, T, DID: Did> serde::Deserialize<'de> for Payload -where - Payload: TryFrom, - as TryFrom>::Error: Debug, +impl<'de, T: ParseAbility + Deserialize<'de>, DID: Did + Deserialize<'de>> Deserialize<'de> + for Payload { - fn deserialize(d: D) -> Result + fn deserialize(deserializer: D) -> Result, D::Error> where - D: serde::Deserializer<'de>, + D: de::Deserializer<'de>, { - match InternalSerializer::deserialize(d) { - Err(e) => Err(e), - Ok(s) => s - .try_into() - .map_err(|e| serde::de::Error::custom(format!("{:?}", e))), // FIXME better error + struct InvocationPayloadVisitor(std::marker::PhantomData<(T, DID)>); + + const FIELDS: &'static [&'static str] = &[ + "iss", "sub", "aud", "cmd", "args", "prf", "nonce", "cause", "meta", "nbf", "exp", + ]; + + impl<'de, T: ParseAbility + Deserialize<'de>, DID: Did + Deserialize<'de>> Visitor<'de> + for InvocationPayloadVisitor + { + type Value = Payload; + + fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { + formatter.write_str("struct invocation::Payload") + } + + fn visit_map>(self, mut map: M) -> Result { + let mut issuer = None; + let mut subject = None; + let mut audience = None; + let mut command = None; + let mut arguments = None; + let mut proofs = None; + let mut nonce = None; + let mut cause = None; + let mut metadata = None; + let mut not_before = None; + let mut expiration = None; + + while let Some(key) = map.next_key()? { + match key { + "iss" => { + if issuer.is_some() { + return Err(de::Error::duplicate_field("iss")); + } + issuer = Some(map.next_value()?); + } + "sub" => { + if subject.is_some() { + return Err(de::Error::duplicate_field("sub")); + } + subject = Some(map.next_value()?); + } + "aud" => { + if audience.is_some() { + return Err(de::Error::duplicate_field("aud")); + } + audience = map.next_value()?; + } + "cmd" => { + if command.is_some() { + return Err(de::Error::duplicate_field("cmd")); + } + command = Some(map.next_value()?); + } + "args" => { + if arguments.is_some() { + return Err(de::Error::duplicate_field("args")); + } + arguments = Some(map.next_value()?); + } + "prf" => { + if proofs.is_some() { + return Err(de::Error::duplicate_field("prf")); + } + proofs = Some(map.next_value()?); + } + "nonce" => { + if nonce.is_some() { + return Err(de::Error::duplicate_field("nonce")); + } + nonce = Some(map.next_value()?); + } + "cause" => { + if cause.is_some() { + return Err(de::Error::duplicate_field("cause")); + } + cause = map.next_value()?; + } + "meta" => { + if metadata.is_some() { + return Err(de::Error::duplicate_field("meta")); + } + metadata = Some(map.next_value()?); + } + "nbf" => { + if not_before.is_some() { + return Err(de::Error::duplicate_field("nbf")); + } + not_before = map.next_value()?; + } + "exp" => { + if expiration.is_some() { + return Err(de::Error::duplicate_field("exp")); + } + expiration = map.next_value()?; + } + other => { + return Err(de::Error::unknown_field(other, FIELDS)); + } + } + } + + let cmd: String = command.ok_or(de::Error::missing_field("cmd"))?; + let args = arguments.ok_or(de::Error::missing_field("args"))?; + + let ability = ::try_parse(cmd.as_str(), &args).map_err(|e| { + de::Error::custom(format!( + "Unable to parse ability field for {:?} becuase {:?}", + cmd, e + )) + })?; + + Ok(Payload { + issuer: issuer.ok_or(de::Error::missing_field("iss"))?, + subject: subject.ok_or(de::Error::missing_field("sub"))?, + proofs: proofs.ok_or(de::Error::missing_field("prf"))?, + metadata: metadata.ok_or(de::Error::missing_field("meta"))?, + nonce: nonce.ok_or(de::Error::missing_field("nonce"))?, + audience, + ability, + cause, + not_before, + expiration, + }) + } } - } -} -impl TryFrom for Payload -where - Payload: TryFrom, -{ - type Error = (); // FIXME - - fn try_from(ipld: Ipld) -> Result { - let s: InternalSerializer = ipld_serde::from_ipld(ipld).map_err(|_| ())?; - s.try_into().map_err(|_| ()) // FIXME + deserializer.deserialize_struct( + "invocation::Payload", + FIELDS, + InvocationPayloadVisitor(Default::default()), + ) } } +/// A variant that accepts [`Promise`]s. +/// +/// [`Promise`]: crate::invocation::promise::Promise +pub type Promised = Payload<::Promised, DID>; + impl From> for Ipld { fn from(payload: Payload) -> Self { payload.into() } } - -#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)] -#[serde(deny_unknown_fields)] -struct InternalSerializer { - #[serde(rename = "iss")] - issuer: did::Newtype, - #[serde(rename = "sub")] - subject: did::Newtype, - #[serde(rename = "aud", skip_serializing_if = "Option::is_none")] - audience: Option, - - #[serde(rename = "cmd")] - command: String, - #[serde(rename = "args")] - arguments: arguments::Named, - - #[serde(rename = "prf")] - proofs: Vec, - #[serde(rename = "nonce")] - nonce: Nonce, - - #[serde(rename = "cause")] - cause: Option, - #[serde(rename = "meta")] - metadata: BTreeMap, - - #[serde(rename = "nbf", skip_serializing_if = "Option::is_none")] - not_before: Option, - - #[serde(rename = "exp", skip_serializing_if = "Option::is_none")] - expiration: Option, -} - -impl From for Ipld { - fn from(serializer: InternalSerializer) -> Self { - serializer.into() - } -} - -impl TryFrom for InternalSerializer { - type Error = SerdeError; - - fn try_from(ipld: Ipld) -> Result { - ipld_serde::from_ipld(ipld) - } -} - -// FIXME -// impl From for Payload { -// fn from(s: InternalSerializer) -> Self { -// Payload { -// issuer: s.issuer, -// subject: s.subject, -// audience: s.audience, -// -// ability: dynamic::Dynamic { -// cmd: s.command, -// args: s.arguments.into(), -// }, -// -// proofs: s.proofs, -// cause: s.cause, -// metadata: s.metadata, -// -// nonce: s.nonce, -// -// not_before: s.not_before, -// expiration: s.expiration, -// } -// } -// } - -// FIXME -// impl From> for InternalSerializer { -// fn from(p: Payload) -> Self { -// InternalSerializer { -// issuer: p.issuer, -// subject: p.subject, -// audience: p.audience, -// -// command: p.ability.cmd, -// arguments: p.ability.args, -// -// proofs: p.proofs, -// cause: p.cause, -// metadata: p.metadata, -// -// nonce: p.nonce, -// -// not_before: p.not_before, -// expiration: p.expiration, -// } -// } -// } - -impl>, DID: Did> From> - for InternalSerializer -{ - fn from(payload: Payload) -> Self { - InternalSerializer { - issuer: payload.issuer.into(), - subject: payload.subject.into(), - audience: payload.audience.map(Into::into), - - command: payload.ability.to_command(), - arguments: payload.ability.into(), - - proofs: payload.proofs, - cause: payload.cause, - metadata: payload.metadata, - - nonce: payload.nonce, - - not_before: payload.not_before, - expiration: None, - } - } -} From eef78f3316a2ff48cd137296acd333f72172c8a3 Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Sat, 17 Feb 2024 00:08:17 -0800 Subject: [PATCH 086/188] Cleaning up naming, squashing TODOs, adding error types --- Cargo.toml | 12 +- src/ability.rs | 10 +- src/ability/:63 | 132 ------------------- src/crypto.rs | 21 +-- src/crypto/rs256.rs | 3 +- src/delegation/payload.rs | 3 +- src/did.rs | 32 +---- src/did/verifiable.rs | 5 + src/invocation/agent.rs | 6 +- src/invocation/payload.rs | 3 +- src/ipld.rs | 6 + src/ipld/enriched.rs | 19 ++- src/ipld/error.rs | 1 - src/ipld/newtype.rs | 6 +- src/ipld/promised.rs | 61 +-------- src/reader.rs | 247 ++---------------------------------- src/reader/builder.rs | 44 +++++++ src/reader/generic.rs | 176 +++++++++++++++++++++++++ src/reader/promised.rs | 41 ++++++ src/receipt.rs | 10 +- src/receipt/payload.rs | 14 +- src/receipt/store.rs | 53 +------- src/receipt/store/memory.rs | 33 +++++ src/receipt/store/traits.rs | 25 ++++ src/signature.rs | 159 +---------------------- src/signature/envelope.rs | 111 ++++++++++++++++ src/signature/witness.rs | 27 ++++ src/task.rs | 28 +--- src/task/id.rs | 27 ++++ src/time.rs | 229 ++------------------------------- src/time/error.rs | 24 ++++ src/time/js.rs | 109 ++++++++++++++++ src/time/timestamp.rs | 102 +++++++++++++++ src/url.rs | 3 + 34 files changed, 829 insertions(+), 953 deletions(-) delete mode 100644 src/ability/:63 create mode 100644 src/did/verifiable.rs delete mode 100644 src/ipld/error.rs create mode 100644 src/reader/builder.rs create mode 100644 src/reader/generic.rs create mode 100644 src/reader/promised.rs create mode 100644 src/receipt/store/memory.rs create mode 100644 src/receipt/store/traits.rs create mode 100644 src/signature/envelope.rs create mode 100644 src/signature/witness.rs create mode 100644 src/task/id.rs create mode 100644 src/time/error.rs create mode 100644 src/time/js.rs create mode 100644 src/time/timestamp.rs diff --git a/Cargo.toml b/Cargo.toml index fa92eb96..936493f5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -104,9 +104,13 @@ default = [ "rs256", "rs512", "eddsa", - "bls" + "bls", + + "ability-preset" ] + test_utils = ["proptest"] + eddsa = ["dep:ed25519-dalek"] es256 = ["dep:p256"] es256k = ["dep:k256"] @@ -116,6 +120,12 @@ ps256 = ["dep:rsa"] rs256 = ["dep:rsa"] rs512 = ["dep:rsa"] bls = ["dep:blst"] + +ability-preset = ["ability-crud", "ability-msg", "ability-wasm"] +ability-crud = [] +ability-msg = [] +ability-wasm = [] + mermaid_docs = ["aquamarine"] [package.metadata.docs.rs] diff --git a/src/ability.rs b/src/ability.rs index 63a05c60..2c3cebee 100644 --- a/src/ability.rs +++ b/src/ability.rs @@ -36,11 +36,19 @@ // FIXME feature flag each? // FIXME ability implementers guide (e.g. serde deny fields) // + +pub mod ucan; + +#[cfg(feature = "ability-crud")] pub mod crud; + +#[cfg(feature = "ability-msg")] pub mod msg; -pub mod ucan; + +#[cfg(feature = "ability-wasm")] pub mod wasm; +#[cfg(feature = "ability-preset")] pub mod preset; pub mod arguments; diff --git a/src/ability/:63 b/src/ability/:63 deleted file mode 100644 index b1803b0c..00000000 --- a/src/ability/:63 +++ /dev/null @@ -1,132 +0,0 @@ -use super::{msg, wasm}; -use crate::{ - ability::{ - arguments, - command::{Command, ParseAbility}, - }, - delegation::Delegable, - invocation::{promise, Resolvable}, - proof::{ - checkable::Checkable, parentful::Parentful, parentless::NoParents, parents::CheckParents, - same::CheckSame, - }, -}; -use libipld_core::ipld::Ipld; -use serde::{Deserialize, Serialize}; - -#[derive(Debug, Clone, PartialEq)] //, Serialize, Deserialize)] -pub enum Ready { - //Crud(), - Msg(msg::Ready), - Wasm(wasm::run::Ready), -} - -#[derive(Debug, Clone, PartialEq)] //, Serialize, Deserialize)] -pub enum Builder { - Msg(msg::Builder), - Wasm(wasm::run::Builder), -} - -pub enum Parents { - Msg(msg::Any), -} // NOTE WasmRun has no parents - -impl CheckSame for Parents { - type Error = (); // FIXME - - fn check_same(&self, proof: &Self) -> Result<(), Self::Error> { - match (self, proof) { - (Parents::Msg(self_), Parents::Msg(proof)) => self_.check_same(proof), - } - } -} - -impl ParseAbility for Parents { - type Error = String; // FIXME - - fn try_parse(cmd: &str, args: &arguments::Named) -> Result { - todo!() - // FIXME Ok(Self {}) - } -} - -impl Delegable for Ready { - type Builder = Builder; -} - -impl CheckParents for Builder { - type Parents = Parents; - type ParentError = (); // FIXME - - fn check_parent(&self, proof: &Parents) -> Result<(), Self::ParentError> { - match self { - Builder::Msg(builder) => builder.check_parent(proof), - Builder::Wasm(builder) => Ok(()), - } - } -} - -impl Checkable for Builder { - type Hierarchy = Parentful; -} - -impl From for Builder { - fn from(ready: Ready) -> Self { - match ready { - Ready::Msg(ready) => Builder::Msg(ready.into()), - Ready::Wasm(ready) => Builder::Wasm(ready.into()), - } - } -} - -impl TryFrom for Ready { - type Error = (); // FIXME - - fn try_from(builder: Builder) -> Result { - match builder { - Builder::Msg(builder) => builder.try_into().map(Ready::Msg), - Builder::Wasm(builder) => Ok(Ready::Wasm(builder.into())), - } - } -} - -#[derive(Debug, Clone, PartialEq)] //, Serialize, Deserialize)] -pub enum Promised { - Msg(msg::Promised), - Wasm(wasm::run::Promised), -} - -impl From for arguments::Named { - fn from(promised: Promised) -> Self { - match promised { - Promised::Msg(promised) => promised.into(), - Promised::Wasm(promised) => promised.into(), - } - } -} - -impl Resolvable for Ready { - type Promised = Promised; - - fn try_resolve(promised: Self::Promised) -> Result { - match promised { - Promised::Msg(promised) => Resolvable::try_resolve(promised) - .map(Ready::Msg) - .map_err(Promised::Msg), - Promised::Wasm(promised) => Resolvable::try_resolve(promised) - .map(Ready::Wasm) - .map_err(Promised::Wasm), - } - } -} - -impl CheckSame for Builder { - type Error = (); // FIXME - - fn check_same(&self, proof: &Self) -> Result<(), Self::Error> { - match (self, proof) { - (Builder::Wasm(builder), Builder::Wasm(proof)) => builder.check_same(proof), - _ => Err(()), - } - } -} diff --git a/src/crypto.rs b/src/crypto.rs index 12df6c78..5f961c47 100644 --- a/src/crypto.rs +++ b/src/crypto.rs @@ -1,6 +1,4 @@ -//! Cryptography utilities - -// use signature::SignatureEncoding; +//! Cryptographic signature utilities pub mod domain_separator; @@ -33,20 +31,3 @@ pub mod rs256; #[cfg(feature = "rs512")] pub mod rs512; - -// // FIXME switch to varsig -// /// A trait for mapping a SignatureEncoding to its algorithm name under JWS -// pub trait JWSSignature: SignatureEncoding { -// /// The algorithm name under JWS -// // I'd originally referenced JWA types directly here, but supporting -// // unspecified algorithms, like BLS, means leaving things more open-ended. -// const ALGORITHM: &'static str; -// } - -// /// A trait for mapping a Signer to its DID. In most cases, this will -// /// be a DID with method did-key, however other methods can be supported -// /// by implementing this trait for a custom signer. -// pub trait SignerDid { -// /// The DID of the signer -// fn did(&self) -> Result; -// } diff --git a/src/crypto/rs256.rs b/src/crypto/rs256.rs index 419093d2..f08729ee 100644 --- a/src/crypto/rs256.rs +++ b/src/crypto/rs256.rs @@ -8,8 +8,7 @@ pub struct VerifyingKey(pub rsa::pkcs1v15::VerifyingKey); impl PartialEq for VerifyingKey { fn eq(&self, other: &Self) -> bool { - // FIXME yikes that clone - rsa::RsaPublicKey::from(self.0.clone()) == rsa::RsaPublicKey::from(other.0.clone()) + self.0.as_ref() == other.0.as_ref() } } diff --git a/src/delegation/payload.rs b/src/delegation/payload.rs index b0024c14..0970bcb8 100644 --- a/src/delegation/payload.rs +++ b/src/delegation/payload.rs @@ -8,7 +8,7 @@ use crate::{ command::{Command, ParseAbility, ToCommand}, }, capsule::Capsule, - did::Did, + did::{Did, Verifiable}, nonce::Nonce, proof::{ checkable::Checkable, @@ -16,7 +16,6 @@ use crate::{ prove::{Prove, Success}, same::CheckSame, }, - signature::Verifiable, time::{TimeBoundError, Timestamp}, }; use libipld_core::{error::SerdeError, ipld::Ipld, serde as ipld_serde}; diff --git a/src/did.rs b/src/did.rs index 493cc901..7b2fe96d 100644 --- a/src/did.rs +++ b/src/did.rs @@ -2,38 +2,18 @@ //! //! [wiki]: https://en.wikipedia.org/wiki/Decentralized_identifier +mod verifiable; + pub mod key; +pub use verifiable::Verifiable; + use did_url::DID; use libipld_core::ipld::Ipld; use serde::{Deserialize, Serialize}; use std::{fmt, string::ToString}; use thiserror::Error; -// #[cfg(feature = "eddsa")] -// use ed25519_dalek; -// -// #[cfg(feature = "es256")] -// use p256; -// -// #[cfg(feature = "es256k")] -// use k256; -// -// #[cfg(feature = "es384")] -// use p384; -// -// #[cfg(feature = "es512")] -// use crate::crypto::p521; -// -// #[cfg(feature = "rs256")] -// use crate::crypto::rs256; -// -// #[cfg(feature = "rs512")] -// use crate::crypto::rs512; -// -// #[cfg(feature = "bls")] -// use blst; - pub trait Did: PartialEq + TryFrom + Into + signature::Verifier { @@ -41,10 +21,6 @@ pub trait Did: type Signer: signature::Signer + fmt::Debug; } -// impl Did for ed25519_dalek::VerifyingKey {} -// -//impl Did for key::Verifier {} -// #[derive(Debug, Clone, PartialEq, Eq)] pub enum Preset { Key(key::Verifier), diff --git a/src/did/verifiable.rs b/src/did/verifiable.rs new file mode 100644 index 00000000..26adb53c --- /dev/null +++ b/src/did/verifiable.rs @@ -0,0 +1,5 @@ +use super::Did; + +pub trait Verifiable { + fn verifier<'a>(&'a self) -> &'a DID; +} diff --git a/src/invocation/agent.rs b/src/invocation/agent.rs index 4a52204c..0cd992c1 100644 --- a/src/invocation/agent.rs +++ b/src/invocation/agent.rs @@ -3,10 +3,10 @@ use crate::{ ability::{arguments, ucan}, delegation, delegation::{condition::Condition, Delegable}, - did::Did, + did::{Did, Verifiable}, nonce::Nonce, proof::{checkable::Checkable, prove::Prove}, - signature::{Signature, Verifiable}, + signature::Witness, time::JsTime, }; use libipld_cbor::DagCborCodec; @@ -144,7 +144,7 @@ where .verify( &encoded, &match promised.signature { - Signature::Solo(ref sig) => sig.clone(), + Witness::Signature(ref sig) => sig.clone(), }, ) .map_err(|_| ())?; diff --git a/src/invocation/payload.rs b/src/invocation/payload.rs index ed03a41d..252ca24e 100644 --- a/src/invocation/payload.rs +++ b/src/invocation/payload.rs @@ -6,10 +6,9 @@ use crate::{ }, capsule::Capsule, delegation::{self, condition::Condition, error::DelegationError, Delegable}, - did::Did, + did::{Did, Verifiable}, nonce::Nonce, proof::{checkable::Checkable, prove::Prove}, - signature::Verifiable, time::Timestamp, }; // use anyhow; diff --git a/src/ipld.rs b/src/ipld.rs index d492e4da..131c47d8 100644 --- a/src/ipld.rs +++ b/src/ipld.rs @@ -1,4 +1,10 @@ //! Helpers for working with [`Ipld`][libipld_core::ipld::Ipld]. +//! +//! [`Ipld`] is a fully concrete data type, and only has a few trait implementations. +//! This module provides a few newtype wrappers that allow you to add trait implementations, +//! and generalized forms to embed non-IPLD into IPLD structure. +//! +//! [`Ipld`]: libipld_core::ipld::Ipld mod enriched; mod newtype; diff --git a/src/ipld/enriched.rs b/src/ipld/enriched.rs index 3cb6616d..1c8d8fb0 100644 --- a/src/ipld/enriched.rs +++ b/src/ipld/enriched.rs @@ -1,7 +1,16 @@ +//! A generalized version of [`Ipld`][libipld_core::ipld::Ipld] +//! that can contain non-IPLD leaves. + use libipld_core::{cid::Cid, ipld::Ipld}; use serde::{Deserialize, Serialize}; use std::collections::BTreeMap; +/// A generalized version of [`Ipld`][libipld_core::ipld::Ipld] +/// that can contain non-IPLD leaves. +/// +/// This is helpful especially when building (mutually) recursive +/// data strutcures that are reducable to [`Ipld`], such as +/// [`ipld::Promised`][crate::ipld::Promised]. #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub enum Enriched { /// Lifted [`Ipld::Null`] @@ -22,14 +31,14 @@ pub enum Enriched { /// Lifted [`Ipld::Bytes`] (byte array) Bytes(Vec), - /// [`Ipld::List`], but where the values are [`PromiseIpld`]. + /// Lifted [`Ipld::Link`] + Link(Cid), + + /// [`Ipld::List`], but where the values are the provided [`T`]. List(Vec), - /// [`Ipld::Map`], but where the values are [`PromiseIpld`]. + /// [`Ipld::Map`], but where the values are the provided [`T`]. Map(BTreeMap), - - /// Lifted [`Ipld::Link`] - Link(Cid), } /// A post-order [`Ipld`] iterator diff --git a/src/ipld/error.rs b/src/ipld/error.rs deleted file mode 100644 index 4752292a..00000000 --- a/src/ipld/error.rs +++ /dev/null @@ -1 +0,0 @@ -// pub enum diff --git a/src/ipld/newtype.rs b/src/ipld/newtype.rs index 25b0c5cc..72c3110c 100644 --- a/src/ipld/newtype.rs +++ b/src/ipld/newtype.rs @@ -9,7 +9,7 @@ use wasm_bindgen::prelude::*; use js_sys::{Array, Map, Object, Uint8Array}; // FIXME push into the submodules -/// A wrapper around [`Ipld`] that has additional trait implementations +/// A newtype wrapper around [`Ipld`] that has additional trait implementations. /// /// Usage is very simple: wrap a [`Newtype`] to gain access to additional traits and methods. /// @@ -79,7 +79,7 @@ impl Newtype { } } -// TODO testme +// FIXME testme #[cfg(target_arch = "wasm32")] impl From for JsValue { fn from(wrapped: Newtype) -> Self { @@ -115,7 +115,7 @@ impl From for JsValue { } } -// TODO testme +// FIXME testme #[cfg(target_arch = "wasm32")] impl TryFrom for Newtype { type Error = (); // FIXME diff --git a/src/ipld/promised.rs b/src/ipld/promised.rs index 322c2db1..fce3cdac 100644 --- a/src/ipld/promised.rs +++ b/src/ipld/promised.rs @@ -3,7 +3,9 @@ use crate::invocation::promise::{Promise, PromiseAny, PromiseErr, PromiseOk}; use libipld_core::ipld::Ipld; use serde::{Deserialize, Serialize}; -/// A promise to recursively resolve to an [`Ipld`] value. +/// A recursive data structure whose leaves may be [`Ipld`] or promises. +/// +/// [`Promised`] resolves to regular [`Ipld`]. #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(transparent)] pub struct Promised(pub Promise, Enriched>); @@ -32,16 +34,12 @@ impl From>> for Promised { } } -// IPLD - impl From for Promised { fn from(ipld: Ipld) -> Self { Promised(Promise::Ok(PromiseOk::Fulfilled(ipld.into()))) } } -// FIXME THIS is a great example of a try_resolve -// FIXME is this recursive? Will this blow the stack? impl TryFrom for Ipld { type Error = Promised; @@ -75,56 +73,3 @@ impl TryFrom for Ipld { } } } - -// FIXME surely the other version in this module can't be right if this also works? -// FIXME this is more iterative right? -// impl TryFrom for Ipld { -// // impl Resolvable for Ipld { -// type Error = Self; -// // type Promised = Promised; -// -// fn try_from(promised: Promised) -> Result { -// fn handle(enriched: super::Enriched) -> Result { -// enriched -// .into_iter() -// .try_fold(vec![], |mut acc, next| { -// match next { -// Item::Inner(promised) => { -// let inner: Ipld = Resolvable::try_resolve(*promised).map_err(|_| ())?; -// -// acc.push(inner); -// } -// Item::Node(node) => { -// let _ = Ipld::try_from(*node).map_err(|_| ())?; -// } -// } -// Ok(acc) -// }) -// .map(|vec| vec.first().expect("FIXME").clone()) -// } -// -// match promised.0 { -// Promise::Ok(promise_ok) => match promise_ok { -// PromiseOk::Fulfilled(enriched) => { -// handle(enriched).map_err(|_| PromiseOk::Fulfilled(enriched).into()) -// } -// PromiseOk::Pending(_) => Err(promised), -// }, -// Promise::Err(promise_err) => match promise_err { -// PromiseErr::Rejected(enriched) => { -// handle(enriched).map_err(|_| PromiseErr::Rejected(enriched).into()) -// } -// PromiseErr::Pending(_) => Err(promised), -// }, -// Promise::Any(promise_any) => match promise_any { -// PromiseAny::Fulfilled(enriched) => { -// handle(enriched).map_err(|_| PromiseAny::Fulfilled(enriched).into()) -// } -// PromiseAny::Rejected(enriched) => { -// handle(enriched).map_err(|_| PromiseAny::Rejected(enriched).into()) -// } -// PromiseAny::Pending(_) => Err(promised), -// }, -// } -// } -// } diff --git a/src/reader.rs b/src/reader.rs index e5d69913..a108640f 100644 --- a/src/reader.rs +++ b/src/reader.rs @@ -1,242 +1,11 @@ //! Configure & attach an ambient environment to a value. +//! +//! See the [`Reader`] struct for more information. -use crate::ability::{ - arguments, - command::{ParseAbility, ParseAbilityError, ToCommand}, -}; -use libipld_core::ipld::Ipld; -use serde::{Deserialize, Serialize}; +mod builder; +mod generic; +mod promised; -/// A struct that attaches an ambient environment to a value -/// -/// This is helpful for dependency injection and/or passing around values that -/// would otherwise need to be threaded through next to the value. -/// -/// This is loosely based on the [`Reader`][SO] from Haskell, but is not implemented -/// monadically. The fully "ambient" features of the `Reader` monad are not present here. -/// -/// # Examples -/// -/// ```rust -/// # use ucan::reader::Reader; -/// # use std::string::ToString; -/// # -/// struct Config { -/// name: String, -/// formatter: Box String>, -/// trimmer: Box String>, -/// } -/// -/// fn run(r: Reader) -> String { -/// let formatted = (r.env.formatter)(r.val.to_string()); -/// (r.env.trimmer)(formatted) -/// } -/// -/// let cfg1 = Config { -/// name: "cfg1".into(), -/// formatter: Box::new(|s| s.to_uppercase()), -/// trimmer: Box::new(|mut s| s.trim().into()) -/// }; -/// -/// let cfg2 = Config { -/// name: "cfg2".into(), -/// formatter: Box::new(|s| s.to_lowercase()), -/// trimmer: Box::new(|mut s| s.split_off(5).into()) -/// }; -/// -/// -/// let reader1 = Reader { -/// env: cfg1, -/// val: " value", -/// }; -/// -/// let reader2 = Reader { -/// env: cfg2, -/// val: " value", -/// }; -/// -/// assert_eq!(run(reader1), "VALUE"); -/// assert_eq!(run(reader2), "e"); -/// ``` -/// -/// [SO]: https://stackoverflow.com/questions/14178889/what-is-the-purpose-of-the-reader-monad -#[derive(Clone, PartialEq, Debug)] -pub struct Reader { - /// The environment (or configuration) being passed with the value - pub env: Env, - - /// The raw value - pub val: T, -} - -impl Reader { - /// Map a function over the `val` of the [`Reader`] - pub fn map(self, func: F) -> Reader - where - F: FnOnce(T) -> U, - { - Reader { - env: self.env, - val: func(self.val), - } - } - - /// Modify the `env` field of the [`Reader`] - pub fn map_env(self, func: F) -> Reader - where - F: FnOnce(Env) -> NewEnv, - { - Reader { - env: func(self.env), - val: self.val, - } - } - - /// Temporarily modify the environment - /// - /// # Examples - /// - /// ```rust - /// # use ucan::reader::Reader; - /// # use std::string::ToString; - /// # - /// # #[derive(Clone)] - /// struct Config<'a> { - /// name: String, - /// formatter: &'a dyn Fn(String) -> String, - /// trimmer: &'a dyn Fn(String) -> String, - /// } - /// - /// fn run(r: Reader) -> String { - /// let formatted = (r.env.formatter)(r.val.to_string()); - /// (r.env.trimmer)(formatted) - /// } - /// - /// let cfg = Config { - /// name: "cfg1".into(), - /// formatter: &|s| s.to_uppercase(), - /// trimmer: &|mut s| s.trim().into() - /// }; - /// - /// let my_reader = Reader { - /// env: cfg, - /// val: " value", - /// }; - /// - /// assert_eq!(run(my_reader.clone()), "VALUE"); - /// - /// // Modify the env locally - /// let observed = my_reader.clone().local(|mut env| { - /// // Modifying env - /// env.trimmer = &|mut s: String| s.split_off(5).into(); - /// env - /// }, |r| run(r)); // Running - /// assert_eq!(observed, "E"); - /// - /// // Back to normal (the above was in fact "local") - /// assert_eq!(run(my_reader.clone()), "VALUE"); - /// ``` - pub fn local(&self, modify_env: F, closure: G) -> U - where - T: Clone, - Env: Clone, - F: Fn(Env) -> Env, - G: Fn(Reader) -> U, - { - closure(Reader { - val: self.val.clone(), - env: modify_env(self.env.clone()), - }) - } -} - -impl>> From> for arguments::Named { - fn from(reader: Reader) -> Self { - reader.val.into() - } -} - -impl ToCommand for Reader { - fn to_command(&self) -> String { - self.env.to_command() - } -} - -impl ParseAbility for Reader { - type Error = ParseAbilityError<::Error>; - - fn try_parse(cmd: &str, args: &arguments::Named) -> Result { - Ok(Reader { - env: Default::default(), - val: T::try_parse(cmd, args).map_err(ParseAbilityError::InvalidArgs)?, - }) - } -} - -/// A helper newtype that marks a value as being a [`Delegable::Builder`]. -/// -/// The is often used as: -/// -/// ```rust -/// # use ucan::reader::{Reader, Builder}; -/// # type Env = (); -/// # let env = (); -/// let example: Reader> = Reader { -/// env: env, -/// val: Builder(42), -/// }; -/// ``` -#[derive(Clone, PartialEq, Debug, Serialize, Deserialize)] -pub struct Builder(pub T); - -impl>> From> for arguments::Named { - fn from(builder: Builder) -> Self { - builder.0.into() - } -} - -impl From> for Reader> { - fn from(reader: Reader) -> Self { - reader.map(Builder) - } -} - -/// A helper newtype that marks a value as being a [`Resolvable::Promised`]. -/// -/// The is often used as: -/// -/// ```rust -/// # use ucan::reader::{Reader, Promised}; -/// # type Env = (); -/// # let env = (); -/// let example: Reader> = Reader { -/// env: env, -/// val: Promised(42), -/// }; -/// ``` -#[derive(Clone, PartialEq, Debug, Serialize, Deserialize)] -pub struct Promised(pub T); - -impl>> From> for arguments::Named { - fn from(promised: Promised) -> Self { - promised.0.into() - } -} - -impl From>> for Reader { - fn from(reader: Reader>) -> Self { - reader.map(|b| b.0) - } -} - -impl From> for Reader> { - fn from(reader: Reader) -> Self { - reader.map(Promised) - } -} - -impl From>> for Reader { - fn from(reader: Reader>) -> Self { - reader.map(|p| p.0) - } -} +pub use builder::Builder; +pub use generic::Reader; +pub use promised::Promised; diff --git a/src/reader/builder.rs b/src/reader/builder.rs new file mode 100644 index 00000000..d7298de7 --- /dev/null +++ b/src/reader/builder.rs @@ -0,0 +1,44 @@ +use super::Reader; +use crate::{ability::arguments, delegation::Delegable, proof::checkable::Checkable}; +use serde::{Deserialize, Serialize}; + +/// A helper newtype that marks a value as being a [`Delegable::Builder`]. +/// +/// The is often used as: +/// +/// ```rust +/// # use ucan::reader::{Reader, Builder}; +/// # type Env = (); +/// # let env = (); +/// let example: Reader> = Reader { +/// env: env, +/// val: Builder(42), +/// }; +/// ``` +#[derive(Clone, PartialEq, Debug, Serialize, Deserialize)] +pub struct Builder(pub T); + +impl Delegable for Reader +where + Reader>: Checkable, +{ + type Builder = Reader>; +} + +impl>> From> for arguments::Named { + fn from(builder: Builder) -> Self { + builder.0.into() + } +} + +impl From> for Reader> { + fn from(reader: Reader) -> Self { + reader.map(Builder) + } +} + +impl From>> for Reader { + fn from(reader: Reader>) -> Self { + reader.map(|b| b.0) + } +} diff --git a/src/reader/generic.rs b/src/reader/generic.rs new file mode 100644 index 00000000..ae713d82 --- /dev/null +++ b/src/reader/generic.rs @@ -0,0 +1,176 @@ +use crate::ability::{ + arguments, + command::{ParseAbility, ParseAbilityError, ToCommand}, +}; +use libipld_core::ipld::Ipld; + +/// A struct that attaches an ambient environment to a value. +/// +/// This is a simple way to perform runtime [dependency injection][DI] in a way +/// that plumbs through traits. +/// +/// This is helpful for dependency injection and/or passing around values that +/// would otherwise need to be threaded through next to the value. +/// +/// This is loosely based on the [functional `Reader`][SO] type, +/// but is not implemented with forced purity. Many of the "ambient" features +/// and guarantees of the [functional `Reader`][SO] monad are not present here. +/// +/// # Examples +/// +/// ```rust +/// # use ucan::reader::Reader; +/// # use std::string::ToString; +/// # +/// struct Config { +/// name: String, +/// formatter: Box String>, +/// trimmer: Box String>, +/// } +/// +/// fn run(r: Reader) -> String { +/// let formatted = (r.env.formatter)(r.val.to_string()); +/// (r.env.trimmer)(formatted) +/// } +/// +/// let cfg1 = Config { +/// name: "cfg1".into(), +/// formatter: Box::new(|s| s.to_uppercase()), +/// trimmer: Box::new(|mut s| s.trim().into()) +/// }; +/// +/// let cfg2 = Config { +/// name: "cfg2".into(), +/// formatter: Box::new(|s| s.to_lowercase()), +/// trimmer: Box::new(|mut s| s.split_off(5).into()) +/// }; +/// +/// +/// let reader1 = Reader { +/// env: cfg1, +/// val: " value", +/// }; +/// +/// let reader2 = Reader { +/// env: cfg2, +/// val: " value", +/// }; +/// +/// assert_eq!(run(reader1), "VALUE"); +/// assert_eq!(run(reader2), "e"); +/// ``` +/// +/// [SO]: https://stackoverflow.com/questions/14178889/what-is-the-purpose-of-the-reader-monad +/// [DI]: https://en.wikipedia.org/wiki/Dependency_injection +#[derive(Clone, PartialEq, Debug)] +pub struct Reader { + /// The environment (or configuration) being passed with the value + pub env: Env, + + /// The raw value + pub val: T, +} + +impl Reader { + /// Map a function over the `val` of the [`Reader`] + pub fn map(self, func: F) -> Reader + where + F: FnOnce(T) -> U, + { + Reader { + env: self.env, + val: func(self.val), + } + } + + /// Modify the `env` field of the [`Reader`] + pub fn map_env(self, func: F) -> Reader + where + F: FnOnce(Env) -> NewEnv, + { + Reader { + env: func(self.env), + val: self.val, + } + } + + /// Temporarily modify the environment + /// + /// # Examples + /// + /// ```rust + /// # use ucan::reader::Reader; + /// # use std::string::ToString; + /// # + /// # #[derive(Clone)] + /// struct Config<'a> { + /// name: String, + /// formatter: &'a dyn Fn(String) -> String, + /// trimmer: &'a dyn Fn(String) -> String, + /// } + /// + /// fn run(r: Reader) -> String { + /// let formatted = (r.env.formatter)(r.val.to_string()); + /// (r.env.trimmer)(formatted) + /// } + /// + /// let cfg = Config { + /// name: "cfg1".into(), + /// formatter: &|s| s.to_uppercase(), + /// trimmer: &|mut s| s.trim().into() + /// }; + /// + /// let my_reader = Reader { + /// env: cfg, + /// val: " value", + /// }; + /// + /// assert_eq!(run(my_reader.clone()), "VALUE"); + /// + /// // Modify the env locally + /// let observed = my_reader.clone().local(|mut env| { + /// // Modifying env + /// env.trimmer = &|mut s: String| s.split_off(5).into(); + /// env + /// }, |r| run(r)); // Running + /// assert_eq!(observed, "E"); + /// + /// // Back to normal (the above was in fact "local") + /// assert_eq!(run(my_reader.clone()), "VALUE"); + /// ``` + pub fn local(&self, modify_env: F, closure: G) -> U + where + T: Clone, + Env: Clone, + F: Fn(Env) -> Env, + G: Fn(Reader) -> U, + { + closure(Reader { + val: self.val.clone(), + env: modify_env(self.env.clone()), + }) + } +} + +impl>> From> for arguments::Named { + fn from(reader: Reader) -> Self { + reader.val.into() + } +} + +impl ToCommand for Reader { + fn to_command(&self) -> String { + self.env.to_command() + } +} + +impl ParseAbility for Reader { + type Error = ParseAbilityError<::Error>; + + fn try_parse(cmd: &str, args: &arguments::Named) -> Result { + Ok(Reader { + env: Default::default(), + val: T::try_parse(cmd, args).map_err(ParseAbilityError::InvalidArgs)?, + }) + } +} diff --git a/src/reader/promised.rs b/src/reader/promised.rs new file mode 100644 index 00000000..bea7d705 --- /dev/null +++ b/src/reader/promised.rs @@ -0,0 +1,41 @@ +use super::Reader; +use crate::ability::{arguments, command::ToCommand}; +use serde::{Deserialize, Serialize}; + +/// A helper newtype that marks a value as being a [`Resolvable::Promised`][crate::invocation::Resolvable::Promised]. +/// +/// Despite this being the intention, due to constraits, the consuming type needs to +/// implement the [`Resolvable`][crate::invocation::Resolvable] trait. +/// For example, there is a `wasm_bindgen` implementation in this crate if +/// compiled for `wasm32`. +/// +/// The is often used as: +/// +/// ```rust +/// # use ucan::reader::{Reader, Promised}; +/// # type Env = (); +/// # let env = (); +/// let example: Reader> = Reader { +/// env: env, +/// val: Promised(42), +/// }; +/// ``` +#[derive(Clone, PartialEq, Debug, Serialize, Deserialize)] +pub struct Promised(pub T); + +impl>> From> for arguments::Named { + fn from(promised: Promised) -> Self { + promised.0.into() + } +} +impl From> for Reader> { + fn from(reader: Reader) -> Self { + reader.map(Promised) + } +} + +impl From>> for Reader { + fn from(reader: Reader>) -> Self { + reader.map(|p| p.0) + } +} diff --git a/src/receipt.rs b/src/receipt.rs index 445cdd9d..8d81e06f 100644 --- a/src/receipt.rs +++ b/src/receipt.rs @@ -1,4 +1,9 @@ -//! The (optional) response from an [`Invocation`][`crate::invocation::Invocation`]. +//! A [`Receipt`] is the (optional) response from an [`Invocation`][`crate::invocation::Invocation`]. +//! +//! - [`Receipt`]s are the result of an [`Invocation`][`crate::invocation::Invocation`]. +//! - [`Payload`] contains the pimary semantic information for a [`Receipt`]. +//! - [`Store`] is the storage interface for [`Receipt`]s. +//! - [`Responds`] associates the response success type to an [Ability][crate::ability]. mod payload; mod responds; @@ -7,10 +12,13 @@ pub mod store; pub use payload::Payload; pub use responds::Responds; +pub use store::Store; use crate::{ability, did, signature}; /// The complete, signed receipt of an [`Invocation`][`crate::invocation::Invocation`]. pub type Receipt = signature::Envelope, DID>; +/// An alias for the [`Receipt`] type with the library preset +/// [`Did`](crate::did)s and [Abilities](crate::ability). pub type Preset = Receipt; diff --git a/src/receipt/payload.rs b/src/receipt/payload.rs index b093f6c7..773b884f 100644 --- a/src/receipt/payload.rs +++ b/src/receipt/payload.rs @@ -4,7 +4,10 @@ use super::responds::Responds; use crate::{ - ability::arguments, capsule::Capsule, did::Did, nonce::Nonce, signature::Verifiable, + ability::arguments, + capsule::Capsule, + did::{Did, Verifiable}, + nonce::Nonce, time::Timestamp, }; use libipld_core::{cid::Cid, error::SerdeError, ipld::Ipld, serde as ipld_serde}; @@ -26,9 +29,7 @@ impl Verifiable for Payload { /// [`Invocation`]: crate::invocation::Invocation #[derive(Debug, Clone, PartialEq)] pub struct Payload { - /// The issuer of the [`Receipt`]. - /// - /// This [`Did`] *must* match the signature on + /// The issuer of the [`Receipt`]. This [`Did`] *must* match the signature on /// the outer layer of [`Receipt`]. /// /// [`Receipt`]: super::Receipt @@ -39,9 +40,8 @@ pub struct Payload { /// [`Invocation`]: crate::invocation::Invocation pub ran: Cid, - /// The output of the [`Invocation`]. - /// - /// This is always of the form `{"ok": ...}` or `{"err": ...}`. + /// The output of the [`Invocation`]. This is always of + /// the form `{"ok": ...}` or `{"err": ...}`. /// /// [`Invocation`]: crate::invocation::Invocation pub out: Result>, diff --git a/src/receipt/store.rs b/src/receipt/store.rs index d9a7b53c..ff1ecad8 100644 --- a/src/receipt/store.rs +++ b/src/receipt/store.rs @@ -1,50 +1,7 @@ -use super::{Receipt, Responds}; -use crate::{did::Did, task}; -use libipld_core::ipld::Ipld; -use std::{collections::BTreeMap, fmt}; -use thiserror::Error; +//! Store trait and MemoryStore implementation. -/// A store for [`Receipt`]s indexed by their [`task::Id`]s. -pub trait Store { - /// The error type representing all the ways a store operation can fail. - type Error; +mod memory; +mod traits; - /// Retrieve a [`Receipt`] by its [`task::Id`]. - fn get<'a>(&self, id: &task::Id) -> Result<&Receipt, Self::Error> - where - ::Success: TryFrom; - - /// Store a [`Receipt`] by its [`task::Id`]. - fn put(&mut self, id: task::Id, receipt: Receipt) -> Result<(), Self::Error> - where - ::Success: Into; -} - -#[derive(Debug, Clone, PartialEq)] -pub struct MemoryStore -where - T::Success: fmt::Debug + Clone + PartialEq, -{ - store: BTreeMap>, -} - -// FIXME extract -#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Error)] -#[error("Delegation not found")] -pub struct NotFound; - -impl Store for MemoryStore -where - ::Success: TryFrom + Into + Clone + fmt::Debug + PartialEq, -{ - type Error = NotFound; - - fn get(&self, id: &task::Id) -> Result<&Receipt, Self::Error> { - self.store.get(id).ok_or(NotFound) - } - - fn put(&mut self, id: task::Id, receipt: Receipt) -> Result<(), Self::Error> { - self.store.insert(id, receipt); - Ok(()) - } -} +pub use memory::MemoryStore; +pub use traits::Store; diff --git a/src/receipt/store/memory.rs b/src/receipt/store/memory.rs new file mode 100644 index 00000000..c3bd3ad4 --- /dev/null +++ b/src/receipt/store/memory.rs @@ -0,0 +1,33 @@ +use super::Store; +use crate::{ + did::Did, + receipt::{Receipt, Responds}, + task, +}; +use libipld_core::ipld::Ipld; +use std::{collections::BTreeMap, convert::Infallible, fmt}; + +/// An in-memory [`receipt::Store`][crate::receipt::Store]. +#[derive(Debug, Clone, PartialEq)] +pub struct MemoryStore +where + T::Success: fmt::Debug + Clone + PartialEq, +{ + store: BTreeMap>, +} + +impl Store for MemoryStore +where + ::Success: TryFrom + Into + Clone + fmt::Debug + PartialEq, +{ + type Error = Infallible; + + fn get(&self, id: &task::Id) -> Result>, Self::Error> { + Ok(self.store.get(id)) + } + + fn put(&mut self, id: task::Id, receipt: Receipt) -> Result<(), Self::Error> { + self.store.insert(id, receipt); + Ok(()) + } +} diff --git a/src/receipt/store/traits.rs b/src/receipt/store/traits.rs new file mode 100644 index 00000000..c2bed62b --- /dev/null +++ b/src/receipt/store/traits.rs @@ -0,0 +1,25 @@ +use crate::{ + did::Did, + receipt::{Receipt, Responds}, + task, +}; +use libipld_core::ipld::Ipld; + +/// A store for [`Receipt`]s indexed by their [`task::Id`]s. +pub trait Store { + /// The error type representing all the ways a store operation can fail. + type Error; + + /// Retrieve a [`Receipt`] by its [`task::Id`]. + /// + /// If the store itself did not experience an error, but the value + /// was not found, the result will be `Ok(None)`. + fn get<'a>(&self, id: &task::Id) -> Result>, Self::Error> + where + ::Success: TryFrom; + + /// Store a [`Receipt`] by its [`task::Id`]. + fn put(&mut self, id: task::Id, receipt: Receipt) -> Result<(), Self::Error> + where + ::Success: Into; +} diff --git a/src/signature.rs b/src/signature.rs index 414a9075..ca66a2a4 100644 --- a/src/signature.rs +++ b/src/signature.rs @@ -1,158 +1,7 @@ //! Signatures and cryptographic envelopes. -use crate::{capsule::Capsule, did::Did}; -// use anyhow; -use libipld_core::{ - cid::{Cid, CidGeneric}, - codec::{Codec, Encode}, - ipld::Ipld, - multihash::{Code, MultihashGeneric}, -}; -use std::collections::BTreeMap; +mod envelope; +mod witness; -// FIXME #[cfg(feature = "dag-cbor")] -use libipld_cbor::DagCborCodec; -use signature::{SignatureEncoding, Signer}; - -pub trait Verifiable { - fn verifier<'a>(&'a self) -> &'a DID; -} - -impl + Capsule, DID: Did> Verifiable for Envelope { - fn verifier(&self) -> &DID { - &self.payload.verifier() - } -} - -/// A container associating a `payload` with its signature over it. -#[derive(Debug, Clone, PartialEq)] // , Serialize, Deserialize)] -pub struct Envelope + Capsule, DID: Did> { - /// The signture of the `payload`. - pub signature: Signature, - - /// The payload that's being signed over. - pub payload: T, -} - -impl + Into + Clone, DID: Did> Envelope { - pub fn try_sign(signer: &DID::Signer, payload: T) -> Result, ()> { - Self::try_sign_generic::(signer, DagCborCodec, payload) - } - - pub fn try_sign_generic>( - signer: &DID::Signer, - codec: C, - payload: T, - ) -> Result, ()> - // FIXME err = () - where - Ipld: Encode, - { - let ipld: Ipld = BTreeMap::from_iter([(T::TAG.into(), payload.clone().into())]).into(); - - let mut buffer = vec![]; - ipld.encode(codec, &mut buffer) - .expect("FIXME not dag-cbor? DagCborCodec to encode any arbitrary `Ipld`"); - - let sig = signer.try_sign(&buffer).map_err(|_| ())?; - - Ok(Envelope { - signature: Signature::Solo(sig), - payload, - }) - } - - pub fn validate_signature(&self) -> Result<(), ()> { - // FIXME need varsig - let codec = DagCborCodec; - let hasher = Code::Sha2_256; - - let mut buffer = vec![]; - let ipld: Ipld = BTreeMap::from_iter([(T::TAG.into(), self.payload.clone().into())]).into(); - ipld.encode(codec, &mut buffer) - .expect("FIXME not dag-cbor? DagCborCodec to encode any arbitrary `Ipld`"); - - let cid: Cid = CidGeneric::new_v1( - codec.into(), - MultihashGeneric::wrap(hasher.into(), buffer.as_slice()) - .map_err(|_| ()) // FIXME - .expect("FIXME expect signing to work..."), - ); - - match &self.signature { - Signature::Solo(sig) => self - .verifier() - .verify(&cid.to_bytes(), &sig) - .map_err(|_| ()), - } - } -} - -// FIXME consider kicking Batch down the road for spec reasons? -#[derive(Debug, Clone, PartialEq)] // , Serialize, Deserialize)] - // #[serde(untagged)] -pub enum Signature { - Solo(S), - // Batch { - // signature: S, - // root: Vec, - // merkle_proof: Vec, - // }, -} - -//#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] -//pub enum MerkleStep { -// Node(Vec, Vec, Direction), -// Turn(Direction), -// End, -//} -// -//#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] -//pub enum Direction { -// Left = 0, -// Right = 1, -//} - -// impl + Capsule + Into, DID: Did> Encode for Envelope -// where -// Ipld: Encode, -// Envelope: Clone, // FIXME? -// { -// fn encode(&self, codec: C, writer: &mut W) -> Result<(), anyhow::Error> { -// Ipld::from((*self).clone()).encode(codec, writer) -// } -// } - -impl From> for Ipld { - fn from(signature: Signature) -> Self { - match signature { - Signature::Solo(sig) => sig.to_vec().into(), - // Signature::Batch { - // signature, - // merkle_proof, - // } => Ipld::List(merkle_proof.into_iter().map(|p| p.into()).collect()), - } - } -} - -impl + Capsule + Into, DID: Did> From> for Ipld { - fn from(Envelope { signature, payload }: Envelope) -> Self { - let ipld: Ipld = BTreeMap::from_iter([(T::TAG.into(), payload.into())]).into(); - - let codec = DagCborCodec; // FIXME get this from the payload - let hasher = Code::Sha2_256; // FIXME get this from the payload - - let mut buffer = vec![]; - ipld.encode(codec, &mut buffer) - .expect("FIXME not dag-cbor? DagCborCodec to encode any arbitrary `Ipld`"); - - let cid = CidGeneric::new_v1( - codec.into(), - MultihashGeneric::wrap(hasher.into(), buffer.as_slice()) - .map_err(|_| ()) // FIXME - .expect("FIXME expect signing to work..."), - ); - - BTreeMap::from_iter([("sig".into(), signature.into()), ("pld".into(), cid.into())]).into() - } -} +pub use envelope::*; +pub use witness::Witness; diff --git a/src/signature/envelope.rs b/src/signature/envelope.rs new file mode 100644 index 00000000..d371aa9c --- /dev/null +++ b/src/signature/envelope.rs @@ -0,0 +1,111 @@ +use super::Witness; +use crate::{ + capsule::Capsule, + did::{Did, Verifiable}, +}; +use libipld_core::{ + codec::{Codec, Encode}, + error::Result, + ipld::Ipld, + multihash::Code, +}; +use std::collections::BTreeMap; +use thiserror::Error; + +// FIXME #[cfg(feature = "dag-cbor")] +use libipld_cbor::DagCborCodec; +use signature::Signer; + +/// A container associating a `payload` with its signature over it. +#[derive(Debug, Clone, PartialEq)] // , Serialize, Deserialize)] +pub struct Envelope + Capsule, DID: Did> { + /// The signture of the `payload`. + pub signature: Witness, + + /// The payload that's being signed over. + pub payload: T, +} + +impl + Capsule, DID: Did> Verifiable for Envelope { + fn verifier(&self) -> &DID { + &self.payload.verifier() + } +} + +impl + Into + Clone, DID: Did> Envelope { + pub fn try_sign(signer: &DID::Signer, payload: T) -> Result, SignError> { + Self::try_sign_generic::(signer, DagCborCodec, payload) + } + + pub fn try_sign_generic>( + signer: &DID::Signer, + codec: C, + payload: T, + ) -> Result, SignError> + where + Ipld: Encode, + { + let ipld: Ipld = BTreeMap::from_iter([(T::TAG.into(), payload.clone().into())]).into(); + + let mut buffer = vec![]; + ipld.encode(codec, &mut buffer) + .map_err(SignError::PayloadEncodingError)?; + + let sig = signer + .try_sign(&buffer) + .map_err(SignError::SignatureError)?; + + Ok(Envelope { + signature: Witness::Signature(sig), + payload, + }) + } + + pub fn validate_signature(&self) -> Result<(), ValidateError> { + // FIXME need varsig + let codec = DagCborCodec; + + let mut encoded = vec![]; + let ipld: Ipld = BTreeMap::from_iter([(T::TAG.into(), self.payload.clone().into())]).into(); + ipld.encode(codec, &mut encoded) + .map_err(ValidateError::PayloadEncodingError)?; + + match &self.signature { + Witness::Signature(sig) => self + .verifier() + .verify(&encoded, &sig) + .map_err(ValidateError::VerifyError), + } + } +} + +impl + Capsule + Into, DID: Did> From> for Ipld { + fn from(Envelope { signature, payload }: Envelope) -> Self { + let ipld: Ipld = BTreeMap::from_iter([(T::TAG.into(), payload.into())]).into(); + BTreeMap::from_iter([("sig".into(), signature.into()), ("pld".into(), ipld)]).into() + } +} + +/// Errors that can occur when signing a [`siganture::Envelope`][Envelope]. +#[derive(Debug, Error)] +pub enum SignError { + /// Unable to encode the payload. + #[error("Unable to encode payload")] + PayloadEncodingError(#[from] libipld_core::error::Error), + + /// Error while signing. + #[error("Signature error: {0}")] + SignatureError(#[from] signature::Error), +} + +/// Errors that can occur when validating a [`signature::Envelope`][Envelope]. +#[derive(Debug, Error)] +pub enum ValidateError { + /// Unable to encode the payload. + #[error("Unable to encode payload")] + PayloadEncodingError(#[from] libipld_core::error::Error), + + /// Error while verifying the signature. + #[error("Signature verification failed: {0}")] + VerifyError(#[from] signature::Error), +} diff --git a/src/signature/witness.rs b/src/signature/witness.rs new file mode 100644 index 00000000..e5d87a76 --- /dev/null +++ b/src/signature/witness.rs @@ -0,0 +1,27 @@ +//! Signatures and related cryptographic witnesses. + +use libipld_core::ipld::Ipld; +use serde::{Deserialize, Serialize}; +use signature::SignatureEncoding; + +/// Asymmetric cryptographic witnesses. +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +#[serde(untagged)] +pub enum Witness { + /// A single cryptographic signature. + Signature(S), + // FIXME TODO + // Batch { + // signature: S, + // root: Vec, + // merkle_proof: Vec, + // }, +} + +impl From> for Ipld { + fn from(w: Witness) -> Self { + match w { + Witness::Signature(sig) => sig.to_vec().into(), + } + } +} diff --git a/src/task.rs b/src/task.rs index 60a0fbb2..50d51596 100644 --- a/src/task.rs +++ b/src/task.rs @@ -1,5 +1,9 @@ //! Task indices for [`Receipt`][crate::receipt::Receipt] reverse lookup. +mod id; + +pub use id::Id; + use crate::{ability::arguments, did, nonce::Nonce}; use libipld_cbor::DagCborCodec; use libipld_core::{ @@ -67,30 +71,6 @@ impl From for Cid { } } -/// The unique identifier for a [`Task`]. -#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] -#[serde(transparent)] -pub struct Id { - /// The CID of the [`Task`]. - /// - /// This acts as a unique identifier for the task. - pub cid: Cid, -} - -impl TryFrom for Id { - type Error = SerdeError; - - fn try_from(ipld: Ipld) -> Result { - ipld_serde::from_ipld(ipld) - } -} - -impl From for Ipld { - fn from(id: Id) -> Self { - id.cid.into() - } -} - impl From for Id { fn from(task: Task) -> Id { Id { diff --git a/src/task/id.rs b/src/task/id.rs new file mode 100644 index 00000000..6e1ec854 --- /dev/null +++ b/src/task/id.rs @@ -0,0 +1,27 @@ +use libipld_core::{cid::Cid, error::SerdeError, ipld::Ipld, serde as ipld_serde}; +use serde_derive::{Deserialize, Serialize}; +use std::fmt::Debug; + +/// The unique identifier for a [`Task`][super::Task]. +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] +#[serde(transparent)] +pub struct Id { + /// The CID of the [`Task`][super::Task]. + /// + /// This acts as a unique identifier for the task. + pub cid: Cid, +} + +impl TryFrom for Id { + type Error = SerdeError; + + fn try_from(ipld: Ipld) -> Result { + ipld_serde::from_ipld(ipld) + } +} + +impl From for Ipld { + fn from(id: Id) -> Self { + id.cid.into() + } +} diff --git a/src/time.rs b/src/time.rs index a0f7a355..fa3b622a 100644 --- a/src/time.rs +++ b/src/time.rs @@ -1,224 +1,11 @@ //! Time utilities. +//! +//! The [`Timestamp`] struct is the main type for representing time in a UCAN token. -use libipld_core::{error::SerdeError, ipld::Ipld, serde as ipld_serde}; -use serde::{Deserialize, Deserializer, Serialize, Serializer}; -use std::fmt; -use thiserror::Error; -use web_time::{Duration, SystemTime, UNIX_EPOCH}; +mod error; +mod js; +mod timestamp; -#[cfg(target_arch = "wasm32")] -use wasm_bindgen::prelude::*; - -/// Get the current time in seconds since [`UNIX_EPOCH`]. -pub fn now() -> u64 { - SystemTime::now() - .duration_since(SystemTime::UNIX_EPOCH) - .unwrap() - .as_secs() -} - -/// All timestamps that this library can handle. -/// -/// Strictly speaking, UCAN exclusively supports [`JsTime`] (for JavaScript interoperability). -#[derive(Debug, Copy, Clone, PartialEq)] -pub enum Timestamp { - /// An entry for [`JsTime`], which is compatible with JavaScript's 2⁵³ numeric range. - JsSafe(JsTime), - - /// Following [Postel's Law](https://en.wikipedia.org/wiki/Robustness_principle), - /// received timestamps may be parsed as regular [`SystemTime`]. - Postel(SystemTime), -} - -impl From for Timestamp { - fn from(js_time: JsTime) -> Self { - Timestamp::JsSafe(js_time) - } -} - -impl From for Timestamp { - fn from(sys_time: SystemTime) -> Self { - Timestamp::Postel(sys_time) - } -} - -impl From for Ipld { - fn from(timestamp: Timestamp) -> Self { - match timestamp { - Timestamp::JsSafe(js_time) => js_time.into(), - Timestamp::Postel(sys_time) => sys_time - .duration_since(UNIX_EPOCH) - .expect("FIXME") - .as_secs() - .into(), - } - } -} - -impl Serialize for Timestamp { - fn serialize(&self, serializer: S) -> Result - where - S: Serializer, - { - match self { - Timestamp::JsSafe(js_time) => js_time.serialize(serializer), - Timestamp::Postel(_sys_time) => todo!(), // FIXME See comment on deserilaizer sys_time.serialize(serializer), - } - } -} - -impl<'de> Deserialize<'de> for Timestamp { - fn deserialize(deserializer: D) -> Result - where - D: Deserializer<'de>, - { - if let Ok(secs) = u64::deserialize(deserializer) { - match UNIX_EPOCH.checked_add(Duration::new(secs, 0)) { - None => return Err(serde::de::Error::custom("time out of range for SystemTime")), - Some(sys_time) => match JsTime::new(sys_time) { - Ok(js_time) => Ok(Timestamp::JsSafe(js_time)), - Err(_) => Ok(Timestamp::Postel(sys_time)), - }, - } - } else { - Err(serde::de::Error::custom("not a Timestamp")) - } - } -} - -impl From for SystemTime { - fn from(timestamp: Timestamp) -> Self { - match timestamp { - Timestamp::JsSafe(js_time) => js_time.time, - Timestamp::Postel(sys_time) => sys_time, - } - } -} - -impl TryFrom for Timestamp { - type Error = SerdeError; - - fn try_from(ipld: Ipld) -> Result { - ipld_serde::from_ipld(ipld) - } -} - -/// A JavaScript-wrapper for [`Timestamp`]. -/// -/// Per the UCAN spec, timestamps MUST respect [IEEE-754] -/// (64-bit double precision = 53-bit truncated integer) for JavaScript interoperability. -/// -/// This range can represent millions of years into the future, -/// and is thus sufficient for "nearly" all auth use cases. -/// -/// [IEEE-754]: https://en.wikipedia.org/wiki/IEEE_754 -#[derive(Debug, Copy, Clone, PartialEq, Eq)] -#[cfg_attr(target_arch = "wasm32", wasm_bindgen)] -pub struct JsTime { - time: SystemTime, -} - -impl From for SystemTime { - fn from(js_time: JsTime) -> Self { - js_time.time - } -} - -#[cfg(target_arch = "wasm32")] -#[wasm_bindgen] -impl JsTime { - /// Lift a [`js_sys::Date`] into a Rust [`JsTime`] - pub fn from_date(date_time: js_sys::Date) -> Result { - let millis = date_time.get_time() as u64; - let secs: u64 = (millis / 1000) as u64; - let duration = Duration::new(secs, 0); // Just round off the nanos - JsTime::new(UNIX_EPOCH + duration).map_err(Into::into) - } - - /// Lower the [`JsTime`] to a [`js_sys::Date`] - pub fn to_date(&self) -> js_sys::Date { - js_sys::Date::new(&JsValue::from( - self.time - .duration_since(UNIX_EPOCH) - .expect("time should be in range since it's getting a JS Date") - .as_millis(), - )) - } -} - -impl JsTime { - /// Create a [`JsTime`] from a [`SystemTime`]. - /// - /// # Arguments - /// - /// * `time` — The time to convert - /// - /// # Errors - /// - /// * [`OutOfRangeError`] — If the time is more than 2⁵³ seconds since the Unix epoch - pub fn new(time: SystemTime) -> Result { - if time.duration_since(UNIX_EPOCH).expect("FIXME").as_secs() > 0x1FFFFFFFFFFFFF { - Err(OutOfRangeError { tried: time }) - } else { - Ok(JsTime { time }) - } - } -} - -impl From for Ipld { - fn from(js_time: JsTime) -> Self { - js_time - .time - .duration_since(UNIX_EPOCH) - .expect("FIXME") - .as_secs() - .into() - } -} - -impl Serialize for JsTime { - fn serialize(&self, serializer: S) -> Result - where - S: Serializer, - { - self.time - .duration_since(UNIX_EPOCH) - .expect("FIXME") - .as_secs() - .serialize(serializer) - } -} - -impl<'de> Deserialize<'de> for JsTime { - fn deserialize(deserializer: D) -> Result - where - D: Deserializer<'de>, - { - let seconds = u64::deserialize(deserializer)?; - JsTime::new(UNIX_EPOCH + Duration::from_secs(seconds)) - .map_err(|_| serde::de::Error::custom("time out of JsTime range")) - } -} - -/// An error expressing when a time is larger than 2⁵³ seconds past the Unix epoch -#[derive(Debug, Clone, PartialEq, Eq, Error)] -pub struct OutOfRangeError { - /// The [`SystemTime`] that is outside of the [`JsTime`] range (2⁵³). - pub tried: SystemTime, -} - -impl fmt::Display for OutOfRangeError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "time out of JsTime (2⁵³) range: {:?}", self.tried) - } -} - -// FIXME move to time.rs -#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Deserialize, Error)] -pub enum TimeBoundError { - #[error("The UCAN delegation has expired")] - Expired, - - #[error("The UCAN delegation is not yet valid")] - NotYetValid, -} +pub use error::{OutOfRangeError, TimeBoundError}; +pub use js::JsTime; +pub use timestamp::Timestamp; diff --git a/src/time/error.rs b/src/time/error.rs new file mode 100644 index 00000000..6b640b87 --- /dev/null +++ b/src/time/error.rs @@ -0,0 +1,24 @@ +//! Temporal errors. + +use thiserror::Error; +use web_time::SystemTime; + +/// An error expressing when a time is larger than 2⁵³ seconds past the Unix epoch +#[derive(Debug, Clone, PartialEq, Eq, Error)] +#[error("Time out of JsTime (2⁵³) range: {:?}", tried)] +pub struct OutOfRangeError { + /// The [`SystemTime`] that is outside of the [`JsTime`] range (2⁵³). + pub tried: SystemTime, +} + +/// An error expressing when a time is not within the bounds of a UCAN. +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Error)] +pub enum TimeBoundError { + /// The UCAN delegation has expired + #[error("Expired")] + Expired, + + /// Not yet valid + #[error("Not yet valid")] + NotYetValid, +} diff --git a/src/time/js.rs b/src/time/js.rs new file mode 100644 index 00000000..f1bb52ce --- /dev/null +++ b/src/time/js.rs @@ -0,0 +1,109 @@ +//! A JavaScript-wrapper for [`Timestamp`][crate::time::Timestamp]. + +use super::OutOfRangeError; +use libipld_core::ipld::Ipld; +use serde::{Deserialize, Deserializer, Serialize, Serializer}; +use web_time::{Duration, SystemTime, UNIX_EPOCH}; + +/// A JavaScript-wrapper for [`Timestamp`][super::Timestamp]. +/// +/// Per the UCAN spec, timestamps MUST respect [IEEE-754] +/// (64-bit double precision = 53-bit truncated integer) for JavaScript interoperability. +/// +/// This range can represent millions of years into the future, +/// and is thus sufficient for "nearly" all auth use cases. +/// +/// [IEEE-754]: https://en.wikipedia.org/wiki/IEEE_754 +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +#[cfg_attr(target_arch = "wasm32", wasm_bindgen)] +pub struct JsTime { + pub time: SystemTime, +} + +impl JsTime { + /// Get the current time in seconds since [`UNIX_EPOCH`] as a [`JsTime`]. + pub fn now() -> JsTime { + Self::new(SystemTime::now()) + .expect("the current time to be somtime in the 3rd millenium CE") + } + + /// Create a [`JsTime`] from a [`SystemTime`]. + /// + /// # Arguments + /// + /// * `time` — The time to convert + /// + /// # Errors + /// + /// * [`OutOfRangeError`] — If the time is more than 2⁵³ seconds since the Unix epoch + pub fn new(time: SystemTime) -> Result { + if time.duration_since(UNIX_EPOCH).expect("FIXME").as_secs() > 0x1FFFFFFFFFFFFF { + Err(OutOfRangeError { tried: time }) + } else { + Ok(JsTime { time }) + } + } +} + +#[cfg(target_arch = "wasm32")] +#[wasm_bindgen] +impl JsTime { + /// Lift a [`js_sys::Date`] into a Rust [`JsTime`] + pub fn from_date(date_time: js_sys::Date) -> Result { + let millis = date_time.get_time() as u64; + let secs: u64 = (millis / 1000) as u64; + let duration = Duration::new(secs, 0); // Just round off the nanos + JsTime::new(UNIX_EPOCH + duration).map_err(Into::into) + } + + /// Lower the [`JsTime`] to a [`js_sys::Date`] + pub fn to_date(&self) -> js_sys::Date { + js_sys::Date::new(&JsValue::from( + self.time + .duration_since(UNIX_EPOCH) + .expect("time should be in range since it's getting a JS Date") + .as_millis(), + )) + } +} + +impl From for SystemTime { + fn from(js_time: JsTime) -> Self { + js_time.time + } +} + +impl From for Ipld { + fn from(js_time: JsTime) -> Self { + js_time + .time + .duration_since(UNIX_EPOCH) + .expect("FIXME") + .as_secs() + .into() + } +} + +impl Serialize for JsTime { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + self.time + .duration_since(UNIX_EPOCH) + .expect("FIXME") + .as_secs() + .serialize(serializer) + } +} + +impl<'de> Deserialize<'de> for JsTime { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + let seconds = u64::deserialize(deserializer)?; + JsTime::new(UNIX_EPOCH + Duration::from_secs(seconds)) + .map_err(|_| serde::de::Error::custom("time out of JsTime range")) + } +} diff --git a/src/time/timestamp.rs b/src/time/timestamp.rs new file mode 100644 index 00000000..c94e2bc3 --- /dev/null +++ b/src/time/timestamp.rs @@ -0,0 +1,102 @@ +use super::JsTime; +use libipld_core::{error::SerdeError, ipld::Ipld, serde as ipld_serde}; +use serde::{Deserialize, Deserializer, Serialize, Serializer}; +use web_time::{Duration, SystemTime, UNIX_EPOCH}; + +/// All timestamps that this library can handle. +/// +/// Strictly speaking, UCAN exclusively supports [`JsTime`] (for JavaScript interoperability). +/// While this library only allows creation of [`JsTime`]s, it will parse the broader +/// [`SystemTime`] range to be liberal in what it accepts. Large numbers are only a problem in +/// langauges that lack 64-bit integers (like JavaScript). +#[derive(Debug, Copy, Clone, PartialEq)] +pub enum Timestamp { + /// An entry for [`JsTime`], which is compatible with JavaScript's 2⁵³ numeric range. + JsSafe(JsTime), + + /// Following [Postel's Law](https://en.wikipedia.org/wiki/Robustness_principle), + /// received timestamps may be parsed as regular [`SystemTime`]. + Postel(SystemTime), +} + +impl Timestamp { + /// Get the current time in seconds since [`UNIX_EPOCH`] as a [`Timestamp`]. + /// + /// This will always return the [`JsSafe`][Timestamp::JsSafe] variant. + pub fn now() -> Timestamp { + Timestamp::JsSafe(JsTime::now()) + } +} + +impl From for Timestamp { + fn from(js_time: JsTime) -> Self { + Timestamp::JsSafe(js_time) + } +} + +impl From for Timestamp { + fn from(sys_time: SystemTime) -> Self { + Timestamp::Postel(sys_time) + } +} + +impl From for Ipld { + fn from(timestamp: Timestamp) -> Self { + match timestamp { + Timestamp::JsSafe(js_time) => js_time.into(), + Timestamp::Postel(sys_time) => sys_time + .duration_since(UNIX_EPOCH) + .expect("FIXME") + .as_secs() + .into(), + } + } +} + +impl Serialize for Timestamp { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + match self { + Timestamp::JsSafe(js_time) => js_time.serialize(serializer), + Timestamp::Postel(sys_time) => sys_time.serialize(serializer), + } + } +} + +impl<'de> Deserialize<'de> for Timestamp { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + if let Ok(secs) = u64::deserialize(deserializer) { + match UNIX_EPOCH.checked_add(Duration::new(secs, 0)) { + None => return Err(serde::de::Error::custom("time out of range for SystemTime")), + Some(sys_time) => match JsTime::new(sys_time) { + Ok(js_time) => Ok(Timestamp::JsSafe(js_time)), + Err(_) => Ok(Timestamp::Postel(sys_time)), + }, + } + } else { + Err(serde::de::Error::custom("not a Timestamp")) + } + } +} + +impl From for SystemTime { + fn from(timestamp: Timestamp) -> Self { + match timestamp { + Timestamp::JsSafe(js_time) => js_time.time, + Timestamp::Postel(sys_time) => sys_time, + } + } +} + +impl TryFrom for Timestamp { + type Error = SerdeError; + + fn try_from(ipld: Ipld) -> Result { + ipld_serde::from_ipld(ipld) + } +} diff --git a/src/url.rs b/src/url.rs index 4e7a50a4..a55255ed 100644 --- a/src/url.rs +++ b/src/url.rs @@ -52,11 +52,14 @@ impl TryFrom for Newtype { } } +/// Possible errors when trying to convert from [`Ipld`]. #[derive(Debug, Error)] pub enum FromIpldError { + /// Not an IPLD string. #[error("Not an IPLD string")] NotAString, + /// Failed to parse the URL. #[error(transparent)] UrlParseError(#[from] url::ParseError), } From d1f05f37440a932f97371955adba7a2c77c72b2a Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Sat, 17 Feb 2024 10:21:21 -0800 Subject: [PATCH 087/188] Clean up did:key --- src/delegation.rs | 13 ++- src/delegation/agent.rs | 6 +- src/delegation/condition/max_number.rs | 2 +- src/delegation/condition/min_number.rs | 2 +- src/did.rs | 140 +--------------------- src/did/key.rs | 135 +--------------------- src/did/key/signature.rs | 66 +++++++++++ src/did/key/verifier.rs | 104 +++++++++++++++++ src/did/newtype.rs | 112 ++++++++++++++++++ src/did/preset.rs | 20 ++++ src/did/traits.rs | 13 +++ src/did/verifiable.rs | 5 - src/invocation.rs | 4 +- src/invocation/agent.rs | 12 +- src/invocation/payload.rs | 2 +- src/ipld.rs | 2 + src/ipld/enriched.rs | 117 ++++++++++--------- src/{ => ipld}/number.rs | 20 +++- src/lib.rs | 1 - src/receipt.rs | 2 +- src/signature/envelope.rs | 42 +++++++ src/signature/witness.rs | 3 +- src/time.rs | 2 - src/time/js.rs | 109 ----------------- src/time/timestamp.rs | 154 +++++++++++++++---------- 25 files changed, 568 insertions(+), 520 deletions(-) create mode 100644 src/did/key/signature.rs create mode 100644 src/did/key/verifier.rs create mode 100644 src/did/newtype.rs create mode 100644 src/did/preset.rs create mode 100644 src/did/traits.rs delete mode 100644 src/did/verifiable.rs rename src/{ => ipld}/number.rs (56%) delete mode 100644 src/time/js.rs diff --git a/src/delegation.rs b/src/delegation.rs index eea99dd2..b91ff05f 100644 --- a/src/delegation.rs +++ b/src/delegation.rs @@ -29,49 +29,60 @@ use web_time::SystemTime; /// /// # Examples /// FIXME +/// FIXME wrap in struct to make the docs & error messages better? pub type Delegation = signature::Envelope, DID>; -pub type Preset = Delegation; +pub type Preset = Delegation; // FIXME checkable -> provable? impl Delegation { + /// Retrive the `issuer` of a [`Delegation`] pub fn issuer(&self) -> &DID { &self.payload.issuer } + /// Retrive the `subject` of a [`Delegation`] pub fn subject(&self) -> &DID { &self.payload.subject } + /// Retrive the `audience` of a [`Delegation`] pub fn audience(&self) -> &DID { &self.payload.audience } + /// Retrive the `ability_builder` of a [`Delegation`] pub fn ability_builder(&self) -> &B { &self.payload.ability_builder } + /// Retrive the `condition` of a [`Delegation`] pub fn conditions(&self) -> &[C] { &self.payload.conditions } + /// Retrive the `metadata` of a [`Delegation`] pub fn metadata(&self) -> &BTreeMap { &self.payload.metadata } + /// Retrive the `nonce` of a [`Delegation`] pub fn nonce(&self) -> &Nonce { &self.payload.nonce } + /// Retrive the `not_before` of a [`Delegation`] pub fn not_before(&self) -> Option<&Timestamp> { self.payload.not_before.as_ref() } + /// Retrive the `expiration` of a [`Delegation`] pub fn expiration(&self) -> &Timestamp { &self.payload.expiration } + /// Retrive the `signature` of a [`Delegation`] pub fn check_time(&self, now: SystemTime) -> Result<(), TimeBoundError> { self.payload.check_time(now) } diff --git a/src/delegation/agent.rs b/src/delegation/agent.rs index 23019ab0..5df85df3 100644 --- a/src/delegation/agent.rs +++ b/src/delegation/agent.rs @@ -1,5 +1,5 @@ use super::{condition::Condition, payload::Payload, store::Store, Delegation}; -use crate::{did::Did, nonce::Nonce, proof::checkable::Checkable, time::JsTime}; +use crate::{did::Did, nonce::Nonce, proof::checkable::Checkable, time::Timestamp}; use libipld_core::{cid::Cid, ipld::Ipld}; use std::{collections::BTreeMap, marker::PhantomData}; use thiserror::Error; @@ -41,8 +41,8 @@ impl< ability_builder: B, new_conditions: Vec, metadata: BTreeMap, - expiration: JsTime, - not_before: Option, + expiration: Timestamp, + not_before: Option, now: &SystemTime, ) -> Result, DelegateError<>::Error>> { let mut salt = self.did.clone().to_string().into_bytes(); diff --git a/src/delegation/condition/max_number.rs b/src/delegation/condition/max_number.rs index 9e2609fd..41cc6eb0 100644 --- a/src/delegation/condition/max_number.rs +++ b/src/delegation/condition/max_number.rs @@ -1,6 +1,6 @@ //! A max number [`Condition`]. use super::traits::Condition; -use crate::{ability::arguments, number::Number}; +use crate::{ability::arguments, ipld::Number}; use libipld_core::{error::SerdeError, ipld::Ipld, serde as ipld_serde}; use serde_derive::{Deserialize, Serialize}; diff --git a/src/delegation/condition/min_number.rs b/src/delegation/condition/min_number.rs index f6e1e1d6..996c5f81 100644 --- a/src/delegation/condition/min_number.rs +++ b/src/delegation/condition/min_number.rs @@ -1,6 +1,6 @@ //! A min number [`Condition`]. use super::traits::Condition; -use crate::{ability::arguments, number::Number}; +use crate::{ability::arguments, ipld::Number}; use libipld_core::{error::SerdeError, ipld::Ipld, serde as ipld_serde}; use serde_derive::{Deserialize, Serialize}; diff --git a/src/did.rs b/src/did.rs index 7b2fe96d..65a00d29 100644 --- a/src/did.rs +++ b/src/did.rs @@ -2,141 +2,11 @@ //! //! [wiki]: https://en.wikipedia.org/wiki/Decentralized_identifier -mod verifiable; +mod newtype; +mod traits; pub mod key; +pub mod preset; -pub use verifiable::Verifiable; - -use did_url::DID; -use libipld_core::ipld::Ipld; -use serde::{Deserialize, Serialize}; -use std::{fmt, string::ToString}; -use thiserror::Error; - -pub trait Did: - PartialEq + TryFrom + Into + signature::Verifier -{ - type Signature: signature::SignatureEncoding + PartialEq + fmt::Debug; - type Signer: signature::Signer + fmt::Debug; -} - -#[derive(Debug, Clone, PartialEq, Eq)] -pub enum Preset { - Key(key::Verifier), - // Dns(did_url::DID), -} - -impl signature::Verifier for Preset { - fn verify(&self, message: &[u8], signature: &key::Signature) -> Result<(), signature::Error> { - match self { - Preset::Key(verifier) => verifier.verify(message, signature), - } - } -} - -#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] -/// A [Decentralized Identifier (DID)][wiki] -/// -/// This is a newtype wrapper around the [`DID`] type from the [`did_url`] crate. -/// -/// # Examples -/// -/// ```rust -/// # use ucan::did; -/// # -/// let did = did::Newtype::try_from("did:example:123".to_string()).unwrap(); -/// assert_eq!(did.0.method(), "example"); -/// ``` -/// -/// [wiki]: https://en.wikipedia.org/wiki/Decentralized_identifier -pub struct Newtype(pub DID); - -impl Serialize for Newtype { - fn serialize(&self, serializer: S) -> Result - where - S: serde::Serializer, - { - String::from(self.clone()).serialize(serializer) - } -} - -impl<'de> Deserialize<'de> for Newtype { - fn deserialize(deserializer: D) -> Result - where - D: serde::Deserializer<'de>, - { - let string = String::deserialize(deserializer)?; - Newtype::try_from(string).map_err(serde::de::Error::custom) - } -} - -impl From for String { - fn from(did: Newtype) -> Self { - did.0.to_string() - } -} - -impl TryFrom for Newtype { - type Error = >::Error; - - fn try_from(string: String) -> Result { - DID::parse(&string).map(Newtype) - } -} - -impl fmt::Display for Newtype { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{}", self.0.to_string()) - } -} - -impl From for Ipld { - fn from(did: Newtype) -> Self { - did.into() - } -} - -impl TryFrom for Newtype { - type Error = FromIpldError; - - fn try_from(ipld: Ipld) -> Result { - match ipld { - Ipld::String(string) => { - Newtype::try_from(string).map_err(FromIpldError::StructuralError) - } - other => Err(FromIpldError::NotAnIpldString(other)), - } - } -} - -/// Errors that can occur when converting to or from a [`Newtype`] -#[derive(Debug, Clone, PartialEq, Error)] -pub enum FromIpldError { - /// Strutural errors in the [`Newtype`] - #[error(transparent)] - StructuralError(#[from] did_url::Error), - - /// The [`Ipld`] was not a string - #[error("Not an IPLD String")] - NotAnIpldString(Ipld), -} - -impl Serialize for FromIpldError { - fn serialize(&self, serializer: S) -> Result - where - S: serde::Serializer, - { - self.to_string().serialize(serializer) - } -} - -impl<'de> Deserialize<'de> for FromIpldError { - fn deserialize(deserializer: D) -> Result - where - D: serde::Deserializer<'de>, - { - let ipld = Ipld::deserialize(deserializer)?; - Ok(FromIpldError::NotAnIpldString(ipld)) - } -} +pub use newtype::{FromIpldError, Newtype}; +pub use traits::{Did, Verifiable}; diff --git a/src/did/key.rs b/src/did/key.rs index 07e0f281..068c3fc3 100644 --- a/src/did/key.rs +++ b/src/did/key.rs @@ -1,132 +1,9 @@ -//! Support for the `did:key` scheme +//! Support for the [`did:key`](https://w3c-ccg.github.io/did-method-key/) DID method. -pub mod traits; - -use signature; - -#[cfg(feature = "eddsa")] -use ed25519_dalek; - -#[cfg(feature = "es256")] -use p256; - -#[cfg(feature = "es256k")] -use k256; - -#[cfg(feature = "es384")] -use p384; - -#[cfg(feature = "es512")] -use crate::crypto::p521; - -#[cfg(feature = "es512")] -use ::p521 as ext_p521; - -#[cfg(feature = "rs256")] -use crate::crypto::rs256; - -#[cfg(feature = "rs512")] -use crate::crypto::rs512; - -#[cfg(feature = "bls")] -use blst; - -#[cfg(feature = "bls")] -use crate::crypto::bls12381; - -#[derive(Debug, Clone, PartialEq, Eq)] -pub enum Verifier { - #[cfg(feature = "eddsa")] - Ed25519(ed25519_dalek::VerifyingKey), - - #[cfg(feature = "es256k")] - Sedcp256k1(k256::ecdsa::VerifyingKey), - - #[cfg(feature = "es256")] - P256(p256::ecdsa::VerifyingKey), +mod signature; +mod verifier; - #[cfg(feature = "es384")] - P384(p384::ecdsa::VerifyingKey), - - #[cfg(feature = "es512")] - P521(p521::VerifyingKey), - - #[cfg(feature = "rs256")] - Rs256(rs256::VerifyingKey), - - #[cfg(feature = "rs512")] - Rs512(rs512::VerifyingKey), - - #[cfg(feature = "bls")] - BlsMinPk(blst::min_pk::PublicKey), - - #[cfg(feature = "bls")] - BlsMinSig(blst::min_sig::PublicKey), -} - -#[derive(Debug, Clone, PartialEq, Eq)] -pub enum Signature { - #[cfg(feature = "eddsa")] - Ed25519(ed25519_dalek::Signature), - - #[cfg(feature = "es256k")] - Sedcp256k1(k256::ecdsa::Signature), - - #[cfg(feature = "es256")] - P256(p256::ecdsa::Signature), - - #[cfg(feature = "es384")] - P384(p384::ecdsa::Signature), - - #[cfg(feature = "es512")] - P521(ext_p521::ecdsa::Signature), - - #[cfg(feature = "rs256")] - Rs256(rs256::Signature), - - #[cfg(feature = "rs512")] - Rs512(rs512::Signature), - - #[cfg(feature = "bls")] - BlsMinPk(bls12381::min_pk::Signature), - - #[cfg(feature = "bls")] - BlsMinSig(bls12381::min_sig::Signature), -} +pub mod traits; -impl signature::Verifier for Verifier { - fn verify(&self, msg: &[u8], signature: &Signature) -> Result<(), signature::Error> { - match (self, signature) { - (Verifier::Ed25519(vk), Signature::Ed25519(sig)) => { - vk.verify(msg, sig).map_err(signature::Error::from_source) - } - (Verifier::Sedcp256k1(vk), Signature::Sedcp256k1(sig)) => { - vk.verify(msg, sig).map_err(signature::Error::from_source) - } - (Verifier::P256(vk), Signature::P256(sig)) => { - vk.verify(msg, sig).map_err(signature::Error::from_source) - } - (Verifier::P384(vk), Signature::P384(sig)) => { - vk.verify(msg, sig).map_err(signature::Error::from_source) - } - (Verifier::P521(vk), Signature::P521(sig)) => { - vk.verify(msg, sig).map_err(signature::Error::from_source) - } - (Verifier::Rs256(vk), Signature::Rs256(sig)) => { - vk.verify(msg, sig).map_err(signature::Error::from_source) - } - (Verifier::Rs512(vk), Signature::Rs512(sig)) => { - vk.verify(msg, sig).map_err(signature::Error::from_source) - } - (Verifier::BlsMinPk(vk), Signature::BlsMinPk(sig)) => { - vk.verify(msg, sig).map_err(signature::Error::from_source) - } - (Verifier::BlsMinSig(vk), Signature::BlsMinSig(sig)) => { - vk.verify(msg, sig).map_err(signature::Error::from_source) - } - (_, _) => Err(signature::Error::from_source( - "invalid signature type for verifier", - )), - } - } -} +pub use signature::Signature; +pub use verifier::Verifier; diff --git a/src/did/key/signature.rs b/src/did/key/signature.rs new file mode 100644 index 00000000..429aadd3 --- /dev/null +++ b/src/did/key/signature.rs @@ -0,0 +1,66 @@ +use enum_as_inner::EnumAsInner; +// FIXME use serde::{Deserialize, Serialize}; + +#[cfg(feature = "eddsa")] +use ed25519_dalek; + +#[cfg(feature = "es256")] +use p256; + +#[cfg(feature = "es256k")] +use k256; + +#[cfg(feature = "es384")] +use p384; + +#[cfg(feature = "es512")] +use ::p521 as ext_p521; + +#[cfg(feature = "rs256")] +use crate::crypto::rs256; + +#[cfg(feature = "rs512")] +use crate::crypto::rs512; + +#[cfg(feature = "bls")] +use crate::crypto::bls12381; + +/// Signature types that are verifiable by `did:key` [`Verifier`]s. +#[derive(Debug, Clone, PartialEq, Eq, EnumAsInner)] +pub enum Signature { + /// `EdDSA` signature. + #[cfg(feature = "eddsa")] + EdDSA(ed25519_dalek::Signature), + + /// `ES256K` (`secp256k1`) signature. + #[cfg(feature = "es256k")] + Es256k(k256::ecdsa::Signature), + + /// `P-256` signature. + #[cfg(feature = "es256")] + P256(p256::ecdsa::Signature), + + /// `P-384` signature. + #[cfg(feature = "es384")] + P384(p384::ecdsa::Signature), + + /// `P-521` signature. + #[cfg(feature = "es512")] + P521(ext_p521::ecdsa::Signature), + + /// `RS256` signature. + #[cfg(feature = "rs256")] + Rs256(rs256::Signature), + + /// `RS512` signature. + #[cfg(feature = "rs512")] + Rs512(rs512::Signature), + + /// `BLS 12-381` signature for the "min pub key" variant. + #[cfg(feature = "bls")] + BlsMinPk(bls12381::min_pk::Signature), + + /// `BLS 12-381` signature for the "min sig" variant. + #[cfg(feature = "bls")] + BlsMinSig(bls12381::min_sig::Signature), +} diff --git a/src/did/key/verifier.rs b/src/did/key/verifier.rs new file mode 100644 index 00000000..e26aa881 --- /dev/null +++ b/src/did/key/verifier.rs @@ -0,0 +1,104 @@ +use super::Signature; +use enum_as_inner::EnumAsInner; +// FIXME use serde::{Deserialize, Serialize}; + +#[cfg(feature = "eddsa")] +use ed25519_dalek; + +#[cfg(feature = "es256")] +use p256; + +#[cfg(feature = "es256k")] +use k256; + +#[cfg(feature = "es384")] +use p384; + +#[cfg(feature = "es512")] +use crate::crypto::p521; + +#[cfg(feature = "rs256")] +use crate::crypto::rs256; + +#[cfg(feature = "rs512")] +use crate::crypto::rs512; + +#[cfg(feature = "bls")] +use blst; + +/// Verifiers (public/verifying keys) for `did:key`. +#[derive(Debug, Clone, PartialEq, Eq, EnumAsInner)] +pub enum Verifier { + /// `EdDSA` verifying key. + #[cfg(feature = "eddsa")] + EdDSA(ed25519_dalek::VerifyingKey), + + /// `ES256K` (`secp256k1`) verifying key. + #[cfg(feature = "es256k")] + Es256k(k256::ecdsa::VerifyingKey), + + /// `P-256` verifying key. + #[cfg(feature = "es256")] + P256(p256::ecdsa::VerifyingKey), + + /// `P-384` verifying key. + #[cfg(feature = "es384")] + P384(p384::ecdsa::VerifyingKey), + + /// `P-521` verifying key. + #[cfg(feature = "es512")] + P521(p521::VerifyingKey), + + /// `RS256` verifying key. + #[cfg(feature = "rs256")] + Rs256(rs256::VerifyingKey), + + /// `RS512` verifying key. + #[cfg(feature = "rs512")] + Rs512(rs512::VerifyingKey), + + /// `BLS 12-381` verifying key for the "min pub key" variant. + #[cfg(feature = "bls")] + BlsMinPk(blst::min_pk::PublicKey), + + /// `BLS 12-381` verifying key for the "min sig" variant. + #[cfg(feature = "bls")] + BlsMinSig(blst::min_sig::PublicKey), +} + +impl signature::Verifier for Verifier { + fn verify(&self, msg: &[u8], signature: &Signature) -> Result<(), signature::Error> { + match (self, signature) { + (Verifier::EdDSA(vk), Signature::EdDSA(sig)) => { + vk.verify(msg, sig).map_err(signature::Error::from_source) + } + (Verifier::Es256k(vk), Signature::Es256k(sig)) => { + vk.verify(msg, sig).map_err(signature::Error::from_source) + } + (Verifier::P256(vk), Signature::P256(sig)) => { + vk.verify(msg, sig).map_err(signature::Error::from_source) + } + (Verifier::P384(vk), Signature::P384(sig)) => { + vk.verify(msg, sig).map_err(signature::Error::from_source) + } + (Verifier::P521(vk), Signature::P521(sig)) => { + vk.verify(msg, sig).map_err(signature::Error::from_source) + } + (Verifier::Rs256(vk), Signature::Rs256(sig)) => { + vk.verify(msg, sig).map_err(signature::Error::from_source) + } + (Verifier::Rs512(vk), Signature::Rs512(sig)) => { + vk.verify(msg, sig).map_err(signature::Error::from_source) + } + (Verifier::BlsMinPk(vk), Signature::BlsMinPk(sig)) => { + vk.verify(msg, sig).map_err(signature::Error::from_source) + } + (Verifier::BlsMinSig(vk), Signature::BlsMinSig(sig)) => { + vk.verify(msg, sig).map_err(signature::Error::from_source) + } + (_, _) => Err(signature::Error::from_source( + "invalid signature type for verifier", + )), + } + } +} diff --git a/src/did/newtype.rs b/src/did/newtype.rs new file mode 100644 index 00000000..2d29ea1b --- /dev/null +++ b/src/did/newtype.rs @@ -0,0 +1,112 @@ +use did_url::DID; +use enum_as_inner::EnumAsInner; +use libipld_core::ipld::Ipld; +use serde::{Deserialize, Serialize}; +use std::{fmt, string::ToString}; +use thiserror::Error; + +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] +/// A [Decentralized Identifier (DID)][wiki] +/// +/// This is a newtype wrapper around the [`DID`] type from the [`did_url`] crate. +/// +/// # Examples +/// +/// ```rust +/// # use ucan::did; +/// # +/// let did = did::Newtype::try_from("did:example:123".to_string()).unwrap(); +/// assert_eq!(did.0.method(), "example"); +/// ``` +/// +/// [wiki]: https://en.wikipedia.org/wiki/Decentralized_identifier +pub struct Newtype(pub DID); + +impl Serialize for Newtype { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + String::from(self.clone()).serialize(serializer) + } +} + +impl<'de> Deserialize<'de> for Newtype { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + let string = String::deserialize(deserializer)?; + Newtype::try_from(string).map_err(serde::de::Error::custom) + } +} + +impl From for String { + fn from(did: Newtype) -> Self { + did.0.to_string() + } +} + +impl TryFrom for Newtype { + type Error = >::Error; + + fn try_from(string: String) -> Result { + DID::parse(&string).map(Newtype) + } +} + +impl fmt::Display for Newtype { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", self.0.to_string()) + } +} + +impl From for Ipld { + fn from(did: Newtype) -> Self { + did.into() + } +} + +impl TryFrom for Newtype { + type Error = FromIpldError; + + fn try_from(ipld: Ipld) -> Result { + match ipld { + Ipld::String(string) => { + Newtype::try_from(string).map_err(FromIpldError::StructuralError) + } + other => Err(FromIpldError::NotAnIpldString(other)), + } + } +} + +/// Errors that can occur when converting to or from a [`Newtype`] +#[derive(Debug, Clone, EnumAsInner, PartialEq, Error)] +pub enum FromIpldError { + /// Strutural errors in the [`Newtype`] + #[error(transparent)] + StructuralError(#[from] did_url::Error), + + /// The [`Ipld`] was not a string + #[error("Not an IPLD String")] + NotAnIpldString(Ipld), +} + +impl Serialize for FromIpldError { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + self.to_string().serialize(serializer) + } +} + +impl<'de> Deserialize<'de> for FromIpldError { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + let ipld = Ipld::deserialize(deserializer)?; + Ok(FromIpldError::NotAnIpldString(ipld)) + } +} diff --git a/src/did/preset.rs b/src/did/preset.rs new file mode 100644 index 00000000..0808afd8 --- /dev/null +++ b/src/did/preset.rs @@ -0,0 +1,20 @@ +use super::key; +use enum_as_inner::EnumAsInner; + +/// The set of [`Did`] types that ship with this library ("presets"). +#[derive(Debug, Clone, EnumAsInner, PartialEq, Eq)] +pub enum Verifier { + /// `did:key` DIDs. + Key(key::Verifier), + // Dns(did_url::DID), +} + +// FIXME serialize with did:key etc + +impl signature::Verifier for Verifier { + fn verify(&self, message: &[u8], signature: &key::Signature) -> Result<(), signature::Error> { + match self { + Verifier::Key(verifier) => verifier.verify(message, signature), + } + } +} diff --git a/src/did/traits.rs b/src/did/traits.rs new file mode 100644 index 00000000..12931628 --- /dev/null +++ b/src/did/traits.rs @@ -0,0 +1,13 @@ +use super::Newtype; +use std::fmt; + +pub trait Did: + PartialEq + TryFrom + Into + signature::Verifier +{ + type Signature: signature::SignatureEncoding + PartialEq + fmt::Debug; + type Signer: signature::Signer + fmt::Debug; +} + +pub trait Verifiable { + fn verifier<'a>(&'a self) -> &'a DID; +} diff --git a/src/did/verifiable.rs b/src/did/verifiable.rs deleted file mode 100644 index 26adb53c..00000000 --- a/src/did/verifiable.rs +++ /dev/null @@ -1,5 +0,0 @@ -use super::Did; - -pub trait Verifiable { - fn verifier<'a>(&'a self) -> &'a DID; -} diff --git a/src/invocation.rs b/src/invocation.rs index 3eff3a76..c13d1933 100644 --- a/src/invocation.rs +++ b/src/invocation.rs @@ -20,8 +20,8 @@ use crate::{ability, did, did::Did, signature}; pub type Invocation = signature::Envelope, DID>; // FIXME use presnet ability, too -pub type Preset = Invocation; -pub type PresetPromised = Invocation; +pub type Preset = Invocation; +pub type PresetPromised = Invocation; impl Invocation { pub fn map_ability(self, f: impl FnOnce(T) -> T) -> Self { diff --git a/src/invocation/agent.rs b/src/invocation/agent.rs index 0cd992c1..1a161313 100644 --- a/src/invocation/agent.rs +++ b/src/invocation/agent.rs @@ -7,7 +7,7 @@ use crate::{ nonce::Nonce, proof::{checkable::Checkable, prove::Prove}, signature::Witness, - time::JsTime, + time::Timestamp, }; use libipld_cbor::DagCborCodec; use libipld_core::{ @@ -80,8 +80,8 @@ where ability: T::Promised, // FIXME give them an enum for promised or not probs doens't matter? metadata: BTreeMap, cause: Option, - expiration: Option, - not_before: Option, + expiration: Option, + not_before: Option, now: &SystemTime, // FIXME err type ) -> Result, ()> { @@ -103,8 +103,8 @@ where metadata, nonce: Nonce::generate_12(&mut seed), cause, - expiration: expiration.map(Into::into), - not_before: not_before.map(Into::into), + expiration, + not_before, }; Ok(Invocation::try_sign(self.signer, payload).map_err(|_| ())?) @@ -189,7 +189,7 @@ where subject: &DID, cause: Option, cid: Cid, - now: JsTime, + now: Timestamp, // FIXME return type ) -> Result, ()> where diff --git a/src/invocation/payload.rs b/src/invocation/payload.rs index 252ca24e..40d3fc49 100644 --- a/src/invocation/payload.rs +++ b/src/invocation/payload.rs @@ -104,7 +104,7 @@ impl From> nonce: payload.nonce, not_before: payload.not_before, - expiration: SystemTime::now().into(), // FIXME + expiration: Timestamp::postel(SystemTime::now()), // FIXME } } } diff --git a/src/ipld.rs b/src/ipld.rs index 131c47d8..95aeca60 100644 --- a/src/ipld.rs +++ b/src/ipld.rs @@ -8,10 +8,12 @@ mod enriched; mod newtype; +mod number; mod promised; pub mod cid; pub use enriched::Enriched; pub use newtype::Newtype; +pub use number::Number; pub use promised::Promised; diff --git a/src/ipld/enriched.rs b/src/ipld/enriched.rs index 1c8d8fb0..bea703fc 100644 --- a/src/ipld/enriched.rs +++ b/src/ipld/enriched.rs @@ -1,6 +1,7 @@ //! A generalized version of [`Ipld`][libipld_core::ipld::Ipld] //! that can contain non-IPLD leaves. +use enum_as_inner::EnumAsInner; use libipld_core::{cid::Cid, ipld::Ipld}; use serde::{Deserialize, Serialize}; use std::collections::BTreeMap; @@ -11,7 +12,7 @@ use std::collections::BTreeMap; /// This is helpful especially when building (mutually) recursive /// data strutcures that are reducable to [`Ipld`], such as /// [`ipld::Promised`][crate::ipld::Promised]. -#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, EnumAsInner, Serialize, Deserialize)] pub enum Enriched { /// Lifted [`Ipld::Null`] Null, @@ -41,33 +42,6 @@ pub enum Enriched { Map(BTreeMap), } -/// A post-order [`Ipld`] iterator -#[derive(Clone, Debug, Default, PartialEq)] -#[cfg_attr(feature = "serde-codec", derive(serde::Serialize))] -#[allow(clippy::module_name_repetitions)] -pub struct PostOrderIpldIter<'a, T> { - inbound: Vec>, - outbound: Vec>, -} - -// FIXME not sure if &'a worth it because nbow I'm cloning everywhere -#[derive(Clone, Debug, PartialEq)] -pub enum Item<'a, T> { - Node(&'a Enriched), - Inner(&'a T), -} - -impl<'a, T> PostOrderIpldIter<'a, T> { - /// Initialize a new [`PostOrderIpldIter`] - #[must_use] - pub fn new(enriched: &'a Enriched) -> Self { - PostOrderIpldIter { - inbound: vec![Item::Node(enriched)], - outbound: vec![], - } - } -} - impl<'a, T: Clone> IntoIterator for &'a Enriched { type Item = Item<'a, T>; type IntoIter = PostOrderIpldIter<'a, T>; @@ -111,35 +85,6 @@ impl<'a, T: Clone> From<&'a Enriched> for PostOrderIpldIter<'a, T> { PostOrderIpldIter::new(enriched) } } - -impl<'a, T: Clone> Iterator for PostOrderIpldIter<'a, T> { - type Item = Item<'a, T>; - - fn next(&mut self) -> Option { - loop { - match self.inbound.pop() { - None => return self.outbound.pop(), - Some(ref map @ Item::Node(Enriched::Map(ref btree))) => { - self.outbound.push(map.clone()); - - for node in btree.values() { - self.inbound.push(Item::Inner(node)); - } - } - - Some(ref list @ Item::Node(Enriched::List(ref vector))) => { - self.outbound.push(list.clone()); - - for node in vector { - self.inbound.push(Item::Inner(node)); - } - } - Some(node) => self.outbound.push(node), - } - } - } -} - impl> From for Enriched { fn from(ipld: Ipld) -> Self { match ipld { @@ -196,3 +141,61 @@ impl> TryFrom> for Ipld { } } } + +/*************************** +| POST ORDER IPLD ITERATOR | +***************************/ + +/// A post-order [`Ipld`] iterator +#[derive(Clone, Debug, Default, PartialEq)] +#[cfg_attr(feature = "serde-codec", derive(serde::Serialize))] +#[allow(clippy::module_name_repetitions)] +pub struct PostOrderIpldIter<'a, T> { + inbound: Vec>, + outbound: Vec>, +} + +#[derive(Clone, Debug, PartialEq)] +pub enum Item<'a, T> { + Node(&'a Enriched), + Inner(&'a T), +} + +impl<'a, T> PostOrderIpldIter<'a, T> { + /// Initialize a new [`PostOrderIpldIter`] + #[must_use] + pub fn new(enriched: &'a Enriched) -> Self { + PostOrderIpldIter { + inbound: vec![Item::Node(enriched)], + outbound: vec![], + } + } +} + +impl<'a, T: Clone> Iterator for PostOrderIpldIter<'a, T> { + type Item = Item<'a, T>; + + fn next(&mut self) -> Option { + loop { + match self.inbound.pop() { + None => return self.outbound.pop(), + Some(ref map @ Item::Node(Enriched::Map(ref btree))) => { + self.outbound.push(map.clone()); + + for node in btree.values() { + self.inbound.push(Item::Inner(node)); + } + } + + Some(ref list @ Item::Node(Enriched::List(ref vector))) => { + self.outbound.push(list.clone()); + + for node in vector { + self.inbound.push(Item::Inner(node)); + } + } + Some(node) => self.outbound.push(node), + } + } + } +} diff --git a/src/number.rs b/src/ipld/number.rs similarity index 56% rename from src/number.rs rename to src/ipld/number.rs index bfa72fa5..bfb5084c 100644 --- a/src/number.rs +++ b/src/ipld/number.rs @@ -1,10 +1,16 @@ //! Helpers for working with [`Ipld`] numerics. +use enum_as_inner::EnumAsInner; use libipld_core::{error::SerdeError, ipld::Ipld, serde as ipld_serde}; use serde_derive::{Deserialize, Serialize}; /// The union of [`Ipld`] numeric types -#[derive(Debug, Clone, PartialEq, PartialOrd, Serialize, Deserialize)] +/// +/// This is helpful when comparing different numeric types, such as +/// bounds checking in [`Condition`]s. +/// +/// [`Condition`]: crate::delegation::Condition +#[derive(Debug, Clone, PartialEq, PartialOrd, EnumAsInner, Serialize, Deserialize)] #[serde(untagged)] pub enum Number { /// Designate a floating point number @@ -27,3 +33,15 @@ impl TryFrom for Number { ipld_serde::from_ipld(ipld) } } + +impl From for Number { + fn from(i: i128) -> Number { + Number::Integer(i) + } +} + +impl From for Number { + fn from(f: f64) -> Number { + Number::Float(f) + } +} diff --git a/src/lib.rs b/src/lib.rs index c9d44ab7..45d1c929 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -23,7 +23,6 @@ pub mod did; pub mod invocation; pub mod ipld; pub mod nonce; -pub mod number; pub mod proof; pub mod reader; pub mod receipt; diff --git a/src/receipt.rs b/src/receipt.rs index 8d81e06f..cb1563ca 100644 --- a/src/receipt.rs +++ b/src/receipt.rs @@ -21,4 +21,4 @@ pub type Receipt = signature::Envelope, DID>; /// An alias for the [`Receipt`] type with the library preset /// [`Did`](crate::did)s and [Abilities](crate::ability). -pub type Preset = Receipt; +pub type Preset = Receipt; diff --git a/src/signature/envelope.rs b/src/signature/envelope.rs index d371aa9c..cd14f420 100644 --- a/src/signature/envelope.rs +++ b/src/signature/envelope.rs @@ -33,10 +33,39 @@ impl + Capsule, DID: Did> Verifiable for Envelope + Into + Clone, DID: Did> Envelope { + /// Attempt to sign some payload with a given signer. + /// + /// # Arguments + /// + /// * `signer` - The signer to use to sign the payload. + /// * `payload` - The payload to sign. + /// + /// # Errors + /// + /// * [`SignError`] - the payload can't be encoded or the signature fails. + /// + /// # Example + /// + /// FIXME pub fn try_sign(signer: &DID::Signer, payload: T) -> Result, SignError> { Self::try_sign_generic::(signer, DagCborCodec, payload) } + /// Attempt to sign some payload with a given signer and specific codec. + /// + /// # Arguments + /// + /// * `signer` - The signer to use to sign the payload. + /// * `codec` - The codec to use to encode the payload. + /// * `payload` - The payload to sign. + /// + /// # Errors + /// + /// * [`SignError`] - the payload can't be encoded or the signature fails. + /// + /// # Example + /// + /// FIXME pub fn try_sign_generic>( signer: &DID::Signer, codec: C, @@ -61,6 +90,19 @@ impl + Into + Clone, DID: Did> Envelope Result<(), ValidateError> { // FIXME need varsig let codec = DagCborCodec; diff --git a/src/signature/witness.rs b/src/signature/witness.rs index e5d87a76..705eae7b 100644 --- a/src/signature/witness.rs +++ b/src/signature/witness.rs @@ -1,11 +1,12 @@ //! Signatures and related cryptographic witnesses. +use enum_as_inner::EnumAsInner; use libipld_core::ipld::Ipld; use serde::{Deserialize, Serialize}; use signature::SignatureEncoding; /// Asymmetric cryptographic witnesses. -#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +#[derive(Debug, Clone, PartialEq, EnumAsInner, Serialize, Deserialize)] #[serde(untagged)] pub enum Witness { /// A single cryptographic signature. diff --git a/src/time.rs b/src/time.rs index fa3b622a..b4e167bf 100644 --- a/src/time.rs +++ b/src/time.rs @@ -3,9 +3,7 @@ //! The [`Timestamp`] struct is the main type for representing time in a UCAN token. mod error; -mod js; mod timestamp; pub use error::{OutOfRangeError, TimeBoundError}; -pub use js::JsTime; pub use timestamp::Timestamp; diff --git a/src/time/js.rs b/src/time/js.rs deleted file mode 100644 index f1bb52ce..00000000 --- a/src/time/js.rs +++ /dev/null @@ -1,109 +0,0 @@ -//! A JavaScript-wrapper for [`Timestamp`][crate::time::Timestamp]. - -use super::OutOfRangeError; -use libipld_core::ipld::Ipld; -use serde::{Deserialize, Deserializer, Serialize, Serializer}; -use web_time::{Duration, SystemTime, UNIX_EPOCH}; - -/// A JavaScript-wrapper for [`Timestamp`][super::Timestamp]. -/// -/// Per the UCAN spec, timestamps MUST respect [IEEE-754] -/// (64-bit double precision = 53-bit truncated integer) for JavaScript interoperability. -/// -/// This range can represent millions of years into the future, -/// and is thus sufficient for "nearly" all auth use cases. -/// -/// [IEEE-754]: https://en.wikipedia.org/wiki/IEEE_754 -#[derive(Debug, Copy, Clone, PartialEq, Eq)] -#[cfg_attr(target_arch = "wasm32", wasm_bindgen)] -pub struct JsTime { - pub time: SystemTime, -} - -impl JsTime { - /// Get the current time in seconds since [`UNIX_EPOCH`] as a [`JsTime`]. - pub fn now() -> JsTime { - Self::new(SystemTime::now()) - .expect("the current time to be somtime in the 3rd millenium CE") - } - - /// Create a [`JsTime`] from a [`SystemTime`]. - /// - /// # Arguments - /// - /// * `time` — The time to convert - /// - /// # Errors - /// - /// * [`OutOfRangeError`] — If the time is more than 2⁵³ seconds since the Unix epoch - pub fn new(time: SystemTime) -> Result { - if time.duration_since(UNIX_EPOCH).expect("FIXME").as_secs() > 0x1FFFFFFFFFFFFF { - Err(OutOfRangeError { tried: time }) - } else { - Ok(JsTime { time }) - } - } -} - -#[cfg(target_arch = "wasm32")] -#[wasm_bindgen] -impl JsTime { - /// Lift a [`js_sys::Date`] into a Rust [`JsTime`] - pub fn from_date(date_time: js_sys::Date) -> Result { - let millis = date_time.get_time() as u64; - let secs: u64 = (millis / 1000) as u64; - let duration = Duration::new(secs, 0); // Just round off the nanos - JsTime::new(UNIX_EPOCH + duration).map_err(Into::into) - } - - /// Lower the [`JsTime`] to a [`js_sys::Date`] - pub fn to_date(&self) -> js_sys::Date { - js_sys::Date::new(&JsValue::from( - self.time - .duration_since(UNIX_EPOCH) - .expect("time should be in range since it's getting a JS Date") - .as_millis(), - )) - } -} - -impl From for SystemTime { - fn from(js_time: JsTime) -> Self { - js_time.time - } -} - -impl From for Ipld { - fn from(js_time: JsTime) -> Self { - js_time - .time - .duration_since(UNIX_EPOCH) - .expect("FIXME") - .as_secs() - .into() - } -} - -impl Serialize for JsTime { - fn serialize(&self, serializer: S) -> Result - where - S: Serializer, - { - self.time - .duration_since(UNIX_EPOCH) - .expect("FIXME") - .as_secs() - .serialize(serializer) - } -} - -impl<'de> Deserialize<'de> for JsTime { - fn deserialize(deserializer: D) -> Result - where - D: Deserializer<'de>, - { - let seconds = u64::deserialize(deserializer)?; - JsTime::new(UNIX_EPOCH + Duration::from_secs(seconds)) - .map_err(|_| serde::de::Error::custom("time out of JsTime range")) - } -} diff --git a/src/time/timestamp.rs b/src/time/timestamp.rs index c94e2bc3..6e649e74 100644 --- a/src/time/timestamp.rs +++ b/src/time/timestamp.rs @@ -1,55 +1,110 @@ -use super::JsTime; -use libipld_core::{error::SerdeError, ipld::Ipld, serde as ipld_serde}; +//! A JavaScript-wrapper for [`Timestamp`][crate::time::Timestamp]. + +use super::OutOfRangeError; +use libipld_core::ipld::Ipld; use serde::{Deserialize, Deserializer, Serialize, Serializer}; use web_time::{Duration, SystemTime, UNIX_EPOCH}; -/// All timestamps that this library can handle. +/// A [`Timestamp`][super::Timestamp] with safe JavaScript interop. /// -/// Strictly speaking, UCAN exclusively supports [`JsTime`] (for JavaScript interoperability). -/// While this library only allows creation of [`JsTime`]s, it will parse the broader -/// [`SystemTime`] range to be liberal in what it accepts. Large numbers are only a problem in -/// langauges that lack 64-bit integers (like JavaScript). -#[derive(Debug, Copy, Clone, PartialEq)] -pub enum Timestamp { - /// An entry for [`JsTime`], which is compatible with JavaScript's 2⁵³ numeric range. - JsSafe(JsTime), - - /// Following [Postel's Law](https://en.wikipedia.org/wiki/Robustness_principle), - /// received timestamps may be parsed as regular [`SystemTime`]. - Postel(SystemTime), +/// Per the UCAN spec, timestamps MUST respect [IEEE-754] +/// (64-bit double precision = 53-bit truncated integer) for +/// JavaScript interoperability. +/// +/// This range can represent millions of years into the future, +/// and is thus sufficient for "nearly" all auth use cases. +/// +/// This type internally deserializes permissively from any [`SystemTime`], +/// but checks that any time created is in the 53-bit bound when created via +/// the public API. +/// +/// [IEEE-754]: https://en.wikipedia.org/wiki/IEEE_754 +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +#[cfg_attr(target_arch = "wasm32", wasm_bindgen)] +pub struct Timestamp { + time: SystemTime, } impl Timestamp { - /// Get the current time in seconds since [`UNIX_EPOCH`] as a [`Timestamp`]. + /// Create a [`Timestamp`] from a [`SystemTime`]. /// - /// This will always return the [`JsSafe`][Timestamp::JsSafe] variant. + /// # Arguments + /// + /// * `time` — The time to convert + /// + /// # Errors + /// + /// * [`OutOfRangeError`] — If the time is more than 2⁵³ seconds since the Unix epoch + pub fn new(time: SystemTime) -> Result { + if time.duration_since(UNIX_EPOCH).expect("FIXME").as_secs() > 0x1FFFFFFFFFFFFF { + Err(OutOfRangeError { tried: time }) + } else { + Ok(Timestamp { time }) + } + } + + /// Get the current time in seconds since [`UNIX_EPOCH`] as a [`Timestamp`]. pub fn now() -> Timestamp { - Timestamp::JsSafe(JsTime::now()) + Self::new(SystemTime::now()) + .expect("the current time to be somtime in the 3rd millenium CE") + } + + /// Convert a [`Timestamp`] to a [Unix timestamp]. + /// + /// [Unix timestamp]: https://en.wikipedia.org/wiki/Unix_time + pub fn to_unix(&self) -> u64 { + self.time + .duration_since(UNIX_EPOCH) + .expect("System time to be after the Unix epoch") + .as_secs() + } + + /// An intentionally permissive variant of `new` for + /// deseriazation. See the note on the struct. + pub(crate) fn postel(time: SystemTime) -> Self { + Timestamp { time } } } -impl From for Timestamp { - fn from(js_time: JsTime) -> Self { - Timestamp::JsSafe(js_time) +#[cfg(target_arch = "wasm32")] +#[wasm_bindgen] +impl Timestamp { + /// Lift a [`js_sys::Date`] into a Rust [`Timestamp`] + pub fn from_date(date_time: js_sys::Date) -> Result { + let millis = date_time.get_time() as u64; + let secs: u64 = (millis / 1000) as u64; + let duration = Duration::new(secs, 0); // Just round off the nanos + Timestamp::new(UNIX_EPOCH + duration).map_err(Into::into) + } + + /// Lower the [`Timestamp`] to a [`js_sys::Date`] + pub fn to_date(&self) -> js_sys::Date { + js_sys::Date::new(&JsValue::from( + self.time + .duration_since(UNIX_EPOCH) + .expect("time should be in range since it's getting a JS Date") + .as_millis(), + )) + } +} + +impl TryFrom for Timestamp { + type Error = OutOfRangeError; + + fn try_from(sys_time: SystemTime) -> Result { + Timestamp::new(sys_time) } } -impl From for Timestamp { - fn from(sys_time: SystemTime) -> Self { - Timestamp::Postel(sys_time) +impl From for SystemTime { + fn from(js_time: Timestamp) -> Self { + js_time.time } } impl From for Ipld { fn from(timestamp: Timestamp) -> Self { - match timestamp { - Timestamp::JsSafe(js_time) => js_time.into(), - Timestamp::Postel(sys_time) => sys_time - .duration_since(UNIX_EPOCH) - .expect("FIXME") - .as_secs() - .into(), - } + timestamp.to_unix().into() } } @@ -58,10 +113,7 @@ impl Serialize for Timestamp { where S: Serializer, { - match self { - Timestamp::JsSafe(js_time) => js_time.serialize(serializer), - Timestamp::Postel(sys_time) => sys_time.serialize(serializer), - } + self.to_unix().serialize(serializer) } } @@ -70,33 +122,7 @@ impl<'de> Deserialize<'de> for Timestamp { where D: Deserializer<'de>, { - if let Ok(secs) = u64::deserialize(deserializer) { - match UNIX_EPOCH.checked_add(Duration::new(secs, 0)) { - None => return Err(serde::de::Error::custom("time out of range for SystemTime")), - Some(sys_time) => match JsTime::new(sys_time) { - Ok(js_time) => Ok(Timestamp::JsSafe(js_time)), - Err(_) => Ok(Timestamp::Postel(sys_time)), - }, - } - } else { - Err(serde::de::Error::custom("not a Timestamp")) - } - } -} - -impl From for SystemTime { - fn from(timestamp: Timestamp) -> Self { - match timestamp { - Timestamp::JsSafe(js_time) => js_time.time, - Timestamp::Postel(sys_time) => sys_time, - } - } -} - -impl TryFrom for Timestamp { - type Error = SerdeError; - - fn try_from(ipld: Ipld) -> Result { - ipld_serde::from_ipld(ipld) + let seconds = u64::deserialize(deserializer)?; + Ok(Timestamp::postel(UNIX_EPOCH + Duration::from_secs(seconds))) } } From e9a77c24ae377fabe9234363ec1b65dc5bad1b8b Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Sat, 17 Feb 2024 11:03:20 -0800 Subject: [PATCH 088/188] Save before some newtyping --- src/capability.rs | 405 --------------------------------- src/crypto.rs | 22 +- src/crypto/bls12381/error.rs | 11 +- src/crypto/domain_separator.rs | 4 + src/crypto/eddsa.rs | 36 --- src/crypto/es256.rs | 24 -- src/crypto/es256k.rs | 25 -- src/crypto/es384.rs | 24 -- src/crypto/es512.rs | 38 +++- src/crypto/ps256.rs | 27 --- src/crypto/rs256.rs | 5 +- src/crypto/rs512.rs | 5 +- src/did/key/traits.rs | 4 +- src/did/key/verifier.rs | 4 +- 14 files changed, 61 insertions(+), 573 deletions(-) delete mode 100644 src/capability.rs delete mode 100644 src/crypto/eddsa.rs delete mode 100644 src/crypto/es256.rs delete mode 100644 src/crypto/es256k.rs delete mode 100644 src/crypto/es384.rs delete mode 100644 src/crypto/ps256.rs diff --git a/src/capability.rs b/src/capability.rs deleted file mode 100644 index de4377ad..00000000 --- a/src/capability.rs +++ /dev/null @@ -1,405 +0,0 @@ -//! Capabilities, and traits for deserializing them - -use std::collections::BTreeMap; - -use serde::{ - de::{DeserializeSeed, IgnoredAny, Visitor}, - Deserialize, Deserializer, Serialize, Serializer, -}; -use url::Url; - -use crate::semantics::{ - ability::{Ability, TopAbility}, - caveat::{Caveat, EmptyCaveat}, - resource::Resource, -}; - -/// The default capability handler, when deserializing a UCAN -pub type DefaultCapabilityParser = PluginCapability; - -/// A capability -#[derive(Debug, Clone)] -pub struct Capability { - /// The resource - resource: Box, - /// The ability - ability: Box, - /// The caveat - caveat: Box, -} - -impl Capability { - /// Creates a new capability - pub fn new(resource: R, ability: A, caveat: C) -> Self - where - R: Resource, - A: Ability, - C: Caveat, - { - Self { - resource: Box::new(resource), - ability: Box::new(ability), - caveat: Box::new(caveat), - } - } - - /// Creates a new capability by cloning the resource, ability, and caveat as trait objects - pub fn clone_box(resource: &dyn Resource, ability: &dyn Ability, caveat: &dyn Caveat) -> Self { - Self { - resource: dyn_clone::clone_box(resource), - ability: dyn_clone::clone_box(ability), - caveat: dyn_clone::clone_box(caveat), - } - } - - /// Returns the resource - pub fn resource(&self) -> &dyn Resource { - &*self.resource - } - - /// Returns the ability - pub fn ability(&self) -> &dyn Ability { - &*self.ability - } - - /// Returns the caveat - pub fn caveat(&self) -> &dyn Caveat { - &*self.caveat - } - - /// Returns true if self is subsumed by other - pub fn is_subsumed_by(&self, other: &Capability) -> bool { - if !self.resource.is_valid_attenuation(&*other.resource) { - return false; - } - - if !(other.ability.is::() || self.ability.is_valid_attenuation(&*other.ability)) - { - return false; - } - - other.caveat.is::() || self.caveat.is_valid_attenuation(&*other.caveat) - } -} - -/// A collection of capabilities -#[derive(Clone, Debug)] -pub struct Capabilities { - inner: Vec, - _marker: std::marker::PhantomData C>, -} - -impl Default for Capabilities { - fn default() -> Self { - Self { - inner: Default::default(), - _marker: Default::default(), - } - } -} - -impl Capabilities { - /// Creates a new collection of capabilities from a vector - pub fn new(inner: Vec) -> Self { - Self { - inner, - _marker: Default::default(), - } - } - - /// Pushes a capability to the collection - pub fn push(&mut self, capability: Capability) { - self.inner.push(capability); - } - - /// Extends the collection with the capabilities from a slice of capabilities - pub fn extend_from_slice(&mut self, capabilities: &[Capability]) { - self.inner.extend_from_slice(capabilities); - } - - /// Returns an iterator over the capabilities - pub fn iter(&self) -> impl Iterator { - self.inner.iter() - } -} - -/// Handles deserializing capabilities -pub trait CapabilityParser: Clone { - /// Tries to deserialize a capability from a resource_uri, ability, and a deserilizer for the caveat - fn try_handle( - resource_uri: &Url, - ability: &str, - caveat_deserializer: &mut dyn erased_serde::Deserializer<'_>, - ) -> Result, anyhow::Error> - where - Self: Sized; -} - -/// A capability handler that deserializes using the registered plugins -#[derive(Clone, Debug)] -pub struct PluginCapability {} - -impl CapabilityParser for PluginCapability { - fn try_handle( - resource_uri: &Url, - ability: &str, - caveat_deserializer: &mut dyn erased_serde::Deserializer<'_>, - ) -> Result, anyhow::Error> { - let resource_scheme = resource_uri.scheme(); - - for plugin in crate::plugins::plugins().filter(|p| p.scheme() == resource_scheme) { - let Some(resource) = plugin.try_handle_resource(resource_uri)? else { - continue; - }; - - let Some(ability) = plugin.try_handle_ability(&resource, ability)? else { - continue; - }; - - let Some(caveat) = plugin.try_handle_caveat(&resource, &ability, caveat_deserializer)? - else { - continue; - }; - - return Ok(Some(Capability { - resource, - ability, - caveat, - })); - } - - Ok(None) - } -} - -impl Serialize for Capabilities -where - Cap: CapabilityParser, -{ - fn serialize(&self, serializer: S) -> Result - where - S: Serializer, - { - let mut capabilities: BTreeMap>> = - Default::default(); - - for capability in self.iter() { - let resource_uri = capability.resource().to_string(); - let ability_key = capability.ability().to_string(); - let caveat = capability.caveat(); - - capabilities - .entry(resource_uri) - .or_default() - .entry(ability_key) - .or_default() - .push(caveat); - } - - capabilities.serialize(serializer) - } -} - -impl<'de, C> Deserialize<'de> for Capabilities -where - C: CapabilityParser, -{ - fn deserialize(deserializer: D) -> Result - where - D: Deserializer<'de>, - { - struct CapabilitiesVisitor { - _marker: std::marker::PhantomData C>, - } - - impl<'de, C> Visitor<'de> for CapabilitiesVisitor - where - C: CapabilityParser, - { - type Value = Vec; - - fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(formatter, "a map of capabilities") - } - - fn visit_map(self, mut map: A) -> Result - where - A: serde::de::MapAccess<'de>, - { - let mut capabilities = Vec::new(); - - while let Some(resource_key) = map.next_key::()? { - let resource_uri = - Url::parse(&resource_key).map_err(serde::de::Error::custom)?; - - map.next_value_seed(Abilities:: { - resource_uri, - capabilities: &mut capabilities, - _marker: Default::default(), - })?; - } - - Ok(capabilities) - } - } - - let caps = deserializer.deserialize_map(CapabilitiesVisitor:: { - _marker: Default::default(), - })?; - - Ok(Self::new(caps)) - } -} - -struct Abilities<'a, C> { - resource_uri: Url, - capabilities: &'a mut Vec, - _marker: std::marker::PhantomData C>, -} - -impl<'de, 'a, C> DeserializeSeed<'de> for Abilities<'a, C> -where - C: CapabilityParser, -{ - type Value = (); - - fn deserialize(self, deserializer: D) -> Result - where - D: Deserializer<'de>, - { - struct AbilitiesVisitor<'a, C> { - resource_uri: Url, - capabilities: &'a mut Vec, - _marker: std::marker::PhantomData C>, - } - - impl<'de, 'a, C> Visitor<'de> for AbilitiesVisitor<'a, C> - where - C: CapabilityParser, - { - type Value = (); - - fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(formatter, "a map of abilities for {}", self.resource_uri) - } - - fn visit_map(self, mut map: A) -> Result - where - A: serde::de::MapAccess<'de>, - { - while let Some(ability_key) = map.next_key::()? { - map.next_value_seed(Caveats:: { - resource_uri: self.resource_uri.clone(), - ability_key: ability_key.clone(), - capabilities: self.capabilities, - _marker: Default::default(), - })?; - } - - Ok(()) - } - } - - deserializer.deserialize_map(AbilitiesVisitor:: { - resource_uri: self.resource_uri, - capabilities: self.capabilities, - _marker: Default::default(), - }) - } -} - -struct Caveats<'a, C> { - resource_uri: Url, - ability_key: String, - capabilities: &'a mut Vec, - _marker: std::marker::PhantomData C>, -} - -impl<'de, 'a, C> DeserializeSeed<'de> for Caveats<'a, C> -where - C: CapabilityParser, -{ - type Value = (); - - fn deserialize(self, deserializer: D) -> Result - where - D: Deserializer<'de>, - { - struct CaveatsVisitor<'a, C> { - resource_uri: Url, - ability_key: String, - capabilities: &'a mut Vec, - _marker: std::marker::PhantomData C>, - } - - impl<'de, 'a, C> Visitor<'de> for CaveatsVisitor<'a, C> - where - C: CapabilityParser, - { - type Value = (); - - fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!( - formatter, - "a map of caveats for {} : {}", - self.resource_uri, self.ability_key - ) - } - - fn visit_seq(self, mut seq: A) -> Result - where - A: serde::de::SeqAccess<'de>, - { - while let Some(element) = seq.next_element_seed(CaveatSeed:: { - resource_uri: self.resource_uri.clone(), - ability_key: self.ability_key.clone(), - _marker: Default::default(), - })? { - if let Some(capability) = element { - self.capabilities.push(capability); - } - } - - Ok(()) - } - } - - deserializer.deserialize_seq(CaveatsVisitor:: { - resource_uri: self.resource_uri, - ability_key: self.ability_key, - capabilities: self.capabilities, - _marker: Default::default(), - }) - } -} - -struct CaveatSeed { - resource_uri: Url, - ability_key: String, - _marker: std::marker::PhantomData Cap>, -} - -impl<'de, Cap> DeserializeSeed<'de> for CaveatSeed -where - Cap: CapabilityParser, -{ - type Value = Option; - - fn deserialize(self, deserializer: D) -> Result - where - D: Deserializer<'de>, - { - let mut deserializer = >::erase(deserializer); - - let Some(capability) = - Cap::try_handle(&self.resource_uri, &self.ability_key, &mut deserializer) - .map_err(serde::de::Error::custom)? - else { - erased_serde::deserialize::(&mut deserializer) - .map_err(serde::de::Error::custom)?; - return Ok(None); - }; - - Ok(Some(capability)) - } -} diff --git a/src/crypto.rs b/src/crypto.rs index 5f961c47..086753be 100644 --- a/src/crypto.rs +++ b/src/crypto.rs @@ -1,31 +1,15 @@ //! Cryptographic signature utilities -pub mod domain_separator; +mod domain_separator; + +pub use domain_separator::DomainSeparator; #[cfg(feature = "bls")] pub mod bls12381; -#[cfg(feature = "es512")] -pub mod p521; - -#[cfg(feature = "eddsa")] -pub mod eddsa; - -#[cfg(feature = "es256")] -pub mod es256; - -#[cfg(feature = "es256k")] -pub mod es256k; - -#[cfg(feature = "es384")] -pub mod es384; - #[cfg(feature = "es512")] pub mod es512; -#[cfg(feature = "ps256")] -pub mod ps256; - #[cfg(feature = "rs256")] pub mod rs256; diff --git a/src/crypto/bls12381/error.rs b/src/crypto/bls12381/error.rs index 7af72c19..8475a875 100644 --- a/src/crypto/bls12381/error.rs +++ b/src/crypto/bls12381/error.rs @@ -1,26 +1,35 @@ use blst::BLST_ERROR; +use enum_as_inner::EnumAsInner; use thiserror::Error; -#[derive(Error, Debug, PartialEq, Eq, Clone, Copy)] +/// Errors that can occur during BLS verification. +#[derive(Debug, Copy, Clone, PartialEq, Eq, Error, EnumAsInner)] pub enum VerificationError { + /// Signature mismatch. #[error("signature mismatch")] VerifyMsgFail, + /// Bad encoding. #[error("bad encoding")] BadEncoding, + /// Point not on curve. #[error("point not on curve")] PointNotOnCurve, + /// Point not in group. #[error("bad point not in group")] PointNotInGroup, + /// Aggregate type mismatch. #[error("aggregate type mismatch")] AggrTypeMismatch, + /// Public key is infinity. #[error("public key is infinity")] PkIsInfinity, + /// Bad scalar. #[error("bad scalar")] BadScalar, } diff --git a/src/crypto/domain_separator.rs b/src/crypto/domain_separator.rs index 718aabc2..fbe325f3 100644 --- a/src/crypto/domain_separator.rs +++ b/src/crypto/domain_separator.rs @@ -1,3 +1,7 @@ +//! Domain separation utilities. + +/// Static domain separator for the DID method. pub trait DomainSeparator { + /// The domain separator bytes; const DST: &'static [u8]; } diff --git a/src/crypto/eddsa.rs b/src/crypto/eddsa.rs deleted file mode 100644 index 0b8ef8c7..00000000 --- a/src/crypto/eddsa.rs +++ /dev/null @@ -1,36 +0,0 @@ -//! EdDSA signature support - -#[cfg(feature = "eddsa-verifier")] -use anyhow::anyhow; -#[cfg(feature = "eddsa-verifier")] -use signature::Verifier; - -// use multibase::Base; - -// impl SignerDid for ed25519_dalek::SigningKey { -// fn did(&self) -> Result { -// let mut buf = unsigned_varint::encode::u128_buffer(); -// let multicodec = unsigned_varint::encode::u128(0xed, &mut buf); -// -// Ok(format!( -// "did:key:{}", -// multibase::encode( -// Base::Base58Btc, -// [multicodec, self.verifying_key().to_bytes().as_ref()].concat() -// ) -// )) -// } -// } - -// /// A verifier for Ed25519 signatures using the `ed25519-dalek` crate -// #[cfg(feature = "eddsa-verifier")] -// pub fn eddsa_verifier(key: &[u8], payload: &[u8], signature: &[u8]) -> Result<(), anyhow::Error> { -// let key = ed25519_dalek::VerifyingKey::try_from(key) -// .map_err(|e| anyhow!("invalid Ed25519 key, {}", e))?; -// -// let signature = ed25519_dalek::Signature::try_from(signature) -// .map_err(|e| anyhow!("invalid Ed25519 signature, {}", e))?; -// -// key.verify(payload, &signature) -// .map_err(|e| anyhow!("signature mismatch, {}", e)) -// } diff --git a/src/crypto/es256.rs b/src/crypto/es256.rs deleted file mode 100644 index 390b3074..00000000 --- a/src/crypto/es256.rs +++ /dev/null @@ -1,24 +0,0 @@ -//! ES256 signature support - -#[cfg(feature = "es256-verifier")] -use anyhow::anyhow; -#[cfg(feature = "es256-verifier")] -use signature::Verifier; - -// use super::JWSSignature; -// -// impl JWSSignature for p256::ecdsa::Signature { -// const ALGORITHM: &'static str = "ES256"; -// } -// -// /// A verifier for PS256 signatures -// #[cfg(feature = "es256-verifier")] -// pub fn es256_verifier(key: &[u8], payload: &[u8], signature: &[u8]) -> Result<(), anyhow::Error> { -// let key = p256::ecdsa::VerifyingKey::try_from(key).map_err(|_| anyhow!("invalid P-256 key"))?; -// -// let signature = -// p256::ecdsa::Signature::try_from(signature).map_err(|_| anyhow!("invalid P-256 key"))?; -// -// key.verify(payload, &signature) -// .map_err(|e| anyhow!("signature mismatch, {}", e)) -// } diff --git a/src/crypto/es256k.rs b/src/crypto/es256k.rs deleted file mode 100644 index 1000f553..00000000 --- a/src/crypto/es256k.rs +++ /dev/null @@ -1,25 +0,0 @@ -//! ES256K signature support - -#[cfg(feature = "es256k-verifier")] -use anyhow::anyhow; -#[cfg(feature = "es256k-verifier")] -use signature::Verifier; - -// use super::JWSSignature; -// -// impl JWSSignature for k256::ecdsa::Signature { -// const ALGORITHM: &'static str = "ES256K"; -// } - -// A verifier for ES256k signatures -// #[cfg(feature = "es256k-verifier")] -// pub fn es256k_verifier(key: &[u8], payload: &[u8], signature: &[u8]) -> Result<(), anyhow::Error> { -// let key = -// k256::ecdsa::VerifyingKey::try_from(key).map_err(|_| anyhow!("invalid secp256k1 key"))?; -// -// let signature = k256::ecdsa::Signature::try_from(signature) -// .map_err(|_| anyhow!("invalid secp256k1 key"))?; -// -// key.verify(payload, &signature) -// .map_err(|e| anyhow!("signature mismatch, {}", e)) -// } diff --git a/src/crypto/es384.rs b/src/crypto/es384.rs deleted file mode 100644 index 25709142..00000000 --- a/src/crypto/es384.rs +++ /dev/null @@ -1,24 +0,0 @@ -//! ES384 signature support - -#[cfg(feature = "es384-verifier")] -use anyhow::anyhow; -#[cfg(feature = "es384-verifier")] -use signature::Verifier; - -//use super::JWSSignature; -// -//impl JWSSignature for p384::ecdsa::Signature { -// const ALGORITHM: &'static str = "ES384"; -//} -// -///// A verifier for ES384 signatures -//#[cfg(feature = "es384-verifier")] -//pub fn es384_verifier(key: &[u8], payload: &[u8], signature: &[u8]) -> Result<(), anyhow::Error> { -// let key = p384::ecdsa::VerifyingKey::try_from(key).map_err(|_| anyhow!("invalid P-384 key"))?; -// -// let signature = -// p384::ecdsa::Signature::try_from(signature).map_err(|_| anyhow!("invalid P-384 key"))?; -// -// key.verify(payload, &signature) -// .map_err(|e| anyhow!("signature mismatch, {}", e)) -//} diff --git a/src/crypto/es512.rs b/src/crypto/es512.rs index 0d4d4284..18a31e25 100644 --- a/src/crypto/es512.rs +++ b/src/crypto/es512.rs @@ -1,7 +1,33 @@ -//! ES512 signature support +//! ES512 signature support (P-512) -// use super::JWSSignature; -// -// impl JWSSignature for ecdsa::Signature { -// const ALGORITHM: &'static str = "ES512"; -// } +use p521; +use signature::Verifier; +use std::fmt; + +/// The verifying/public key for ES512. +#[derive(Clone)] // FIXME , Serialize, Deserialize)] +pub struct VerifyingKey(pub p521::ecdsa::VerifyingKey); + +impl fmt::Debug for VerifyingKey { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_tuple("VerifyingKey").finish() + } +} + +impl PartialEq for VerifyingKey { + fn eq(&self, other: &Self) -> bool { + self.0.to_encoded_point(true) == other.0.to_encoded_point(true) + } +} + +impl Eq for VerifyingKey {} + +impl Verifier for VerifyingKey { + fn verify( + &self, + msg: &[u8], + signature: &p521::ecdsa::Signature, + ) -> Result<(), signature::Error> { + self.0.verify(msg, &signature) + } +} diff --git a/src/crypto/ps256.rs b/src/crypto/ps256.rs deleted file mode 100644 index 9895df2c..00000000 --- a/src/crypto/ps256.rs +++ /dev/null @@ -1,27 +0,0 @@ -//! PS256 signature support - -// #[cfg(feature = "ps256-verifier")] -// use anyhow::anyhow; -// #[cfg(feature = "ps256-verifier")] -// use signature::Verifier; -// -// use super::JWSSignature; -// -// impl JWSSignature for rsa::pss::Signature { -// const ALGORITHM: &'static str = "PS256"; -// } -// -// /// A verifier for RS256 signatures -// #[cfg(feature = "ps256-verifier")] -// pub fn ps256_verifier(key: &[u8], payload: &[u8], signature: &[u8]) -> Result<(), anyhow::Error> { -// let key = rsa::pkcs1::DecodeRsaPublicKey::from_pkcs1_der(key) -// .map_err(|e| anyhow!("invalid PKCS#1 key, {}", e))?; -// -// let key = rsa::pss::VerifyingKey::::new(key); -// -// let signature = rsa::pss::Signature::try_from(signature) -// .map_err(|e| anyhow!("invalid RSASSA-PKCS1-v1_5 signature, {}", e))?; -// -// key.verify(payload, &signature) -// .map_err(|e| anyhow!("signature mismatch, {}", e)) -// } diff --git a/src/crypto/rs256.rs b/src/crypto/rs256.rs index f08729ee..dae3a982 100644 --- a/src/crypto/rs256.rs +++ b/src/crypto/rs256.rs @@ -1,8 +1,9 @@ -//! RS256 signature support +//! RS256 signature support (2048-bit RSA PKCS #1 v1.5). use rsa; use signature::{SignatureEncoding, Signer, Verifier}; +/// The verifying/public key for RS256. #[derive(Debug, Clone)] // FIXME , Serialize, Deserialize)] pub struct VerifyingKey(pub rsa::pkcs1v15::VerifyingKey); @@ -20,6 +21,7 @@ impl Verifier for VerifyingKey { } } +/// The signing/secret key for RS256. #[derive(Debug, Clone)] // FIXME , Serialize, Deserialize)] pub struct SigningKey(pub rsa::pkcs1v15::SigningKey); @@ -29,6 +31,7 @@ impl Signer for SigningKey { } } +/// The signature for RS256. #[derive(Debug, Clone, PartialEq, Eq)] // FIXME , Serialize, Deserialize)] pub struct Signature(pub rsa::pkcs1v15::Signature); diff --git a/src/crypto/rs512.rs b/src/crypto/rs512.rs index d3af4635..32a739a7 100644 --- a/src/crypto/rs512.rs +++ b/src/crypto/rs512.rs @@ -1,8 +1,9 @@ -//! RS512 signature support +//! RS512 signature support (4096-bit RSA PKCS #1 v1.5). use rsa; use signature::{SignatureEncoding, Signer, Verifier}; +/// The verifying/public key for RS512. #[derive(Debug, Clone)] // FIXME , Serialize, Deserialize)] pub struct VerifyingKey(pub rsa::pkcs1v15::VerifyingKey); @@ -20,6 +21,7 @@ impl Verifier for VerifyingKey { } } +/// The signing/secret key for RS512. #[derive(Debug, Clone)] // FIXME , Serialize, Deserialize)] pub struct SigningKey(pub rsa::pkcs1v15::SigningKey); @@ -29,6 +31,7 @@ impl Signer for SigningKey { } } +/// The signature for RS512. #[derive(Debug, Clone, PartialEq, Eq)] // FIXME , Serialize, Deserialize)] pub struct Signature(pub rsa::pkcs1v15::Signature); diff --git a/src/did/key/traits.rs b/src/did/key/traits.rs index b4548e3e..59c3f304 100644 --- a/src/did/key/traits.rs +++ b/src/did/key/traits.rs @@ -1,4 +1,4 @@ -use crate::crypto::{bls12381, p521, rs256, rs512}; +use crate::crypto::{bls12381, es512, rs256, rs512}; use ::p521 as ext_p521; use ed25519_dalek; use k256; @@ -44,7 +44,7 @@ impl DidKey for p384::ecdsa::VerifyingKey { type Signature = p384::ecdsa::Signature; } -impl DidKey for p521::VerifyingKey { +impl DidKey for es512::VerifyingKey { const BASE58_PREFIX: &'static str = "2J9"; type Signer = ext_p521::ecdsa::SigningKey; diff --git a/src/did/key/verifier.rs b/src/did/key/verifier.rs index e26aa881..9e17cad8 100644 --- a/src/did/key/verifier.rs +++ b/src/did/key/verifier.rs @@ -15,7 +15,7 @@ use k256; use p384; #[cfg(feature = "es512")] -use crate::crypto::p521; +use crate::crypto::es512; #[cfg(feature = "rs256")] use crate::crypto::rs256; @@ -47,7 +47,7 @@ pub enum Verifier { /// `P-521` verifying key. #[cfg(feature = "es512")] - P521(p521::VerifyingKey), + P521(es512::VerifyingKey), /// `RS256` verifying key. #[cfg(feature = "rs256")] From 83f8e6d127b909d9ffd1da86299b9982f038ba5c Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Sat, 17 Feb 2024 12:27:44 -0800 Subject: [PATCH 089/188] Newtype invocation --- src/invocation.rs | 68 +++++++++++++++++++++++++++++++++++---- src/invocation/agent.rs | 18 +++++------ src/invocation/payload.rs | 58 +++++++++++++++++---------------- src/receipt.rs | 15 ++++++++- src/receipt/payload.rs | 16 ++++----- src/receipt/responds.rs | 3 +- 6 files changed, 125 insertions(+), 53 deletions(-) diff --git a/src/invocation.rs b/src/invocation.rs index c13d1933..326272c1 100644 --- a/src/invocation.rs +++ b/src/invocation.rs @@ -8,7 +8,8 @@ pub mod store; pub use payload::{Payload, Promised}; pub use resolvable::Resolvable; -use crate::{ability, did, did::Did, signature}; +use crate::{ability, did, did::Did, signature, time::Timestamp}; +use libipld_core::{cid::Cid, ipld::Ipld}; /// The complete, signed [`invocation::Payload`][Payload]. /// @@ -17,19 +18,74 @@ use crate::{ability, did, did::Did, signature}; /// For a version that can include [`Promise`][promise::Promise]s, /// wrap your `T` in [`invocation::Promised`](Promised) to get /// `Invocation>`. -pub type Invocation = signature::Envelope, DID>; +#[derive(Debug, Clone, PartialEq)] +pub struct Invocation(pub signature::Envelope, DID>); // FIXME use presnet ability, too pub type Preset = Invocation; pub type PresetPromised = Invocation; +impl Invocation { + pub fn payload(&self) -> &Payload { + &self.0.payload + } + + pub fn signature(&self) -> &signature::Witness { + &self.0.signature + } + + pub fn audience(&self) -> &Option { + &self.0.payload.audience + } + + pub fn issuer(&self) -> &DID { + &self.0.payload.issuer + } + + pub fn subject(&self) -> &DID { + &self.0.payload.subject + } + + pub fn ability(&self) -> &T { + &self.0.payload.ability + } + + pub fn proofs(&self) -> &Vec { + &self.0.payload.proofs + } + + pub fn issued_at(&self) -> &Option { + &self.0.payload.issued_at + } + + pub fn try_sign( + signer: &DID::Signer, + payload: Payload, + ) -> Result, signature::SignError> { + let envelope = signature::Envelope::try_sign(signer, payload)?; + Ok(Invocation(envelope)) + } +} + +impl did::Verifiable for Invocation { + fn verifier(&self) -> &DID { + &self.0.verifier() + } +} + impl Invocation { pub fn map_ability(self, f: impl FnOnce(T) -> T) -> Self { - let mut payload = self.payload; + let mut payload = self.0.payload; payload.ability = f(payload.ability); - Self { + Invocation(signature::Envelope { payload, - signature: self.signature, - } + signature: self.0.signature, + }) + } +} + +impl From> for Ipld { + fn from(invocation: Invocation) -> Self { + invocation.0.into() } } diff --git a/src/invocation/agent.rs b/src/invocation/agent.rs index 1a161313..48001f96 100644 --- a/src/invocation/agent.rs +++ b/src/invocation/agent.rs @@ -81,7 +81,7 @@ where metadata: BTreeMap, cause: Option, expiration: Option, - not_before: Option, + issued_at: Option, now: &SystemTime, // FIXME err type ) -> Result, ()> { @@ -104,7 +104,7 @@ where nonce: Nonce::generate_12(&mut seed), cause, expiration, - not_before, + issued_at, }; Ok(Invocation::try_sign(self.signer, payload).map_err(|_| ())?) @@ -134,7 +134,7 @@ where ); let mut encoded = vec![]; - Ipld::from(promised.payload.clone()) + Ipld::from(promised.payload().clone()) // FIXME use the varsig headre to get the codec .encode(DagCborCodec, &mut encoded) .expect("FIXME"); @@ -143,13 +143,13 @@ where .verifier() .verify( &encoded, - &match promised.signature { + &match promised.signature() { Witness::Signature(ref sig) => sig.clone(), }, ) .map_err(|_| ())?; - let resolved_ability: T = match Resolvable::try_resolve(promised.payload.ability.clone()) { + let resolved_ability: T = match Resolvable::try_resolve(promised.ability().clone()) { Ok(resolved) => resolved, Err(_) => { // FIXME check if any of the unresolved promises are in the store @@ -165,19 +165,19 @@ where let proof_payloads = self .delegation_store - .get_many(&promised.payload.proofs) + .get_many(&promised.proofs()) .map_err(|_| ())? .into_iter() .map(|d| d.payload.clone()) .collect(); - let resolved_payload = promised.payload.clone().map_ability(|_| resolved_ability); + let resolved_payload = promised.payload().clone().map_ability(|_| resolved_ability); delegation::Payload::::from(resolved_payload.clone()) .check(proof_payloads, now) .map_err(|_| ())?; - if promised.payload.audience != Some(self.did.clone()) { + if promised.audience() != &Some(self.did.clone()) { return Ok(Recipient::Other(resolved_payload)); } @@ -222,7 +222,7 @@ where metadata: BTreeMap::new(), nonce: Nonce::generate_12(&mut vec![]), expiration: None, - not_before: None, + issued_at: None, }; let invocation = Invocation::try_sign(self.signer, payload).map_err(|_| ())?; diff --git a/src/invocation/payload.rs b/src/invocation/payload.rs index 40d3fc49..f1aa0f8c 100644 --- a/src/invocation/payload.rs +++ b/src/invocation/payload.rs @@ -40,7 +40,7 @@ pub struct Payload { pub metadata: BTreeMap, pub nonce: Nonce, - pub not_before: Option, + pub issued_at: Option, pub expiration: Option, // FIXME this field may not make sense } @@ -64,8 +64,8 @@ impl Payload { cause: self.cause, metadata: self.metadata, nonce: self.nonce, - not_before: self.not_before, - expiration: None, + issued_at: self.issued_at, + expiration: self.expiration, } } @@ -91,20 +91,22 @@ impl Capsule for Payload { impl From> for delegation::Payload { - fn from(payload: Payload) -> Self { + fn from(inv_payload: Payload) -> Self { delegation::Payload { - issuer: payload.issuer.clone(), - subject: payload.subject.clone(), - audience: payload.audience.unwrap_or(payload.subject), + issuer: inv_payload.issuer.clone(), + subject: inv_payload.subject.clone(), + audience: inv_payload.audience.unwrap_or(inv_payload.subject), - ability_builder: T::Builder::from(payload.ability), + ability_builder: T::Builder::from(inv_payload.ability), conditions: vec![], - metadata: payload.metadata, - nonce: payload.nonce, + metadata: inv_payload.metadata, + nonce: inv_payload.nonce, - not_before: payload.not_before, - expiration: Timestamp::postel(SystemTime::now()), // FIXME + not_before: None, + expiration: inv_payload + .expiration + .unwrap_or(Timestamp::postel(SystemTime::now())), } } } @@ -123,16 +125,16 @@ impl, DID: Did> From> for arguments::N ("nonce".into(), payload.nonce.into()), ]); - if let Some(audience) = payload.audience { - args.insert("aud".into(), audience.into().to_string().into()); + if let Some(aud) = payload.audience { + args.insert("aud".into(), aud.into().to_string().into()); } - if let Some(not_before) = payload.not_before { - args.insert("nbf".into(), not_before.into()); + if let Some(iat) = payload.issued_at { + args.insert("iat".into(), iat.into()); } - if let Some(expiration) = payload.expiration { - args.insert("exp".into(), expiration.into()); + if let Some(exp) = payload.expiration { + args.insert("exp".into(), exp.into()); } args @@ -152,7 +154,7 @@ where if self.audience.is_some() { field_count += 1 }; - if self.not_before.is_some() { + if self.issued_at.is_some() { field_count += 1 }; if self.expiration.is_some() { @@ -176,8 +178,8 @@ where state.serialize_field("aud", aud)?; } - if let Some(nbf) = &self.not_before { - state.serialize_field("nbf", nbf)?; + if let Some(iat) = &self.issued_at { + state.serialize_field("iat", iat)?; } if let Some(exp) = &self.expiration { @@ -198,7 +200,7 @@ impl<'de, T: ParseAbility + Deserialize<'de>, DID: Did + Deserialize<'de>> Deser struct InvocationPayloadVisitor(std::marker::PhantomData<(T, DID)>); const FIELDS: &'static [&'static str] = &[ - "iss", "sub", "aud", "cmd", "args", "prf", "nonce", "cause", "meta", "nbf", "exp", + "iss", "sub", "aud", "cmd", "args", "prf", "nonce", "cause", "meta", "iat", "exp", ]; impl<'de, T: ParseAbility + Deserialize<'de>, DID: Did + Deserialize<'de>> Visitor<'de> @@ -220,7 +222,7 @@ impl<'de, T: ParseAbility + Deserialize<'de>, DID: Did + Deserialize<'de>> Deser let mut nonce = None; let mut cause = None; let mut metadata = None; - let mut not_before = None; + let mut issued_at = None; let mut expiration = None; while let Some(key) = map.next_key()? { @@ -279,11 +281,11 @@ impl<'de, T: ParseAbility + Deserialize<'de>, DID: Did + Deserialize<'de>> Deser } metadata = Some(map.next_value()?); } - "nbf" => { - if not_before.is_some() { - return Err(de::Error::duplicate_field("nbf")); + "issued_at" => { + if issued_at.is_some() { + return Err(de::Error::duplicate_field("iat")); } - not_before = map.next_value()?; + issued_at = map.next_value()?; } "exp" => { if expiration.is_some() { @@ -316,7 +318,7 @@ impl<'de, T: ParseAbility + Deserialize<'de>, DID: Did + Deserialize<'de>> Deser audience, ability, cause, - not_before, + issued_at, expiration, }) } diff --git a/src/receipt.rs b/src/receipt.rs index cb1563ca..8b1d3bf5 100644 --- a/src/receipt.rs +++ b/src/receipt.rs @@ -17,8 +17,21 @@ pub use store::Store; use crate::{ability, did, signature}; /// The complete, signed receipt of an [`Invocation`][`crate::invocation::Invocation`]. -pub type Receipt = signature::Envelope, DID>; +#[derive(Clone, Debug, PartialEq)] +pub struct Receipt(pub signature::Envelope, DID>); /// An alias for the [`Receipt`] type with the library preset /// [`Did`](crate::did)s and [Abilities](crate::ability). pub type Preset = Receipt; + +impl Receipt { + /// Returns the [`Payload`] of the [`Receipt`]. + pub fn payload(&self) -> &Payload { + &self.0.payload + } + + /// Returns the [`signature::Envelope`] of the [`Receipt`]. + pub fn signature(&self) -> &signature::Witness { + &self.0.signature + } +} diff --git a/src/receipt/payload.rs b/src/receipt/payload.rs index 773b884f..e8445318 100644 --- a/src/receipt/payload.rs +++ b/src/receipt/payload.rs @@ -50,7 +50,7 @@ pub struct Payload { /// requested to be queued next. /// /// [`Invocation`]: crate::invocation::Invocation - pub next: Vec, // FIXME rename here or in spec? + pub next: Vec, /// An optional proof chain authorizing a different [`Did`] to /// be the receipt `iss` than the audience (or subject) of the @@ -194,13 +194,13 @@ where } Ok(Payload { - issuer: issuer.ok_or_else(|| de::Error::missing_field("iss"))?, - ran: ran.ok_or_else(|| de::Error::missing_field("ran"))?, - out: out.ok_or_else(|| de::Error::missing_field("out"))?, - next: next.ok_or_else(|| de::Error::missing_field("next"))?, - proofs: proofs.ok_or_else(|| de::Error::missing_field("prf"))?, - metadata: metadata.ok_or_else(|| de::Error::missing_field("meta"))?, - nonce: nonce.ok_or_else(|| de::Error::missing_field("nonce"))?, + issuer: issuer.ok_or(de::Error::missing_field("iss"))?, + ran: ran.ok_or(de::Error::missing_field("ran"))?, + out: out.ok_or(de::Error::missing_field("out"))?, + next: next.ok_or(de::Error::missing_field("next"))?, + proofs: proofs.ok_or(de::Error::missing_field("prf"))?, + metadata: metadata.ok_or(de::Error::missing_field("meta"))?, + nonce: nonce.ok_or(de::Error::missing_field("nonce"))?, issued_at, }) } diff --git a/src/receipt/responds.rs b/src/receipt/responds.rs index 723d9e7d..6f7ca552 100644 --- a/src/receipt/responds.rs +++ b/src/receipt/responds.rs @@ -1,4 +1,5 @@ use crate::{nonce::Nonce, task, task::Task}; +use std::fmt; /// Describe the relationship between an ability and the [`Receipt`]s. /// @@ -8,7 +9,7 @@ use crate::{nonce::Nonce, task, task::Task}; /// [`Receipt`]: crate::receipt::Receipt pub trait Responds { /// The successful return type for running `Self`. - type Success; + type Success: Clone + fmt::Debug + PartialEq; /// Convert an Ability (`Self`) into a [`Task`]. /// From 233397ea57c40ffa8f9e62d40bbd857e3bd408ef Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Sat, 17 Feb 2024 13:02:41 -0800 Subject: [PATCH 090/188] Delegation newtype --- src/delegation.rs | 73 +++++++++++++++++++++++++++++---------- src/delegation/agent.rs | 4 +-- src/delegation/payload.rs | 10 +++--- src/delegation/store.rs | 30 ++++++++-------- src/invocation.rs | 12 ++++++- src/invocation/agent.rs | 6 ++-- src/invocation/payload.rs | 18 ++++++++-- src/signature/envelope.rs | 13 +++++-- src/time/timestamp.rs | 2 +- 9 files changed, 116 insertions(+), 52 deletions(-) diff --git a/src/delegation.rs b/src/delegation.rs index b91ff05f..9d71ce31 100644 --- a/src/delegation.rs +++ b/src/delegation.rs @@ -14,7 +14,7 @@ use crate::{ ability, did::{self, Did}, nonce::Nonce, - proof::{checkable::Checkable, parents::CheckParents, same::CheckSame}, + proof::{parents::CheckParents, same::CheckSame}, signature, time::{TimeBoundError, Timestamp}, }; @@ -30,69 +30,104 @@ use web_time::SystemTime; /// # Examples /// FIXME /// FIXME wrap in struct to make the docs & error messages better? -pub type Delegation = signature::Envelope, DID>; +#[derive(Clone, Debug, PartialEq)] +pub struct Delegation(pub signature::Envelope, DID>); pub type Preset = Delegation; // FIXME checkable -> provable? -impl Delegation { +impl Delegation { /// Retrive the `issuer` of a [`Delegation`] pub fn issuer(&self) -> &DID { - &self.payload.issuer + &self.0.payload.issuer } /// Retrive the `subject` of a [`Delegation`] pub fn subject(&self) -> &DID { - &self.payload.subject + &self.0.payload.subject } /// Retrive the `audience` of a [`Delegation`] pub fn audience(&self) -> &DID { - &self.payload.audience + &self.0.payload.audience } /// Retrive the `ability_builder` of a [`Delegation`] pub fn ability_builder(&self) -> &B { - &self.payload.ability_builder + &self.0.payload.ability_builder + } + + pub fn map_ability_builder(self, f: F) -> Delegation + where + F: FnOnce(B) -> T, + { + Delegation(signature::Envelope { + payload: self.0.payload.map_ability(f), + signature: self.0.signature, + }) } /// Retrive the `condition` of a [`Delegation`] pub fn conditions(&self) -> &[C] { - &self.payload.conditions + &self.0.payload.conditions } /// Retrive the `metadata` of a [`Delegation`] pub fn metadata(&self) -> &BTreeMap { - &self.payload.metadata + &self.0.payload.metadata } /// Retrive the `nonce` of a [`Delegation`] pub fn nonce(&self) -> &Nonce { - &self.payload.nonce + &self.0.payload.nonce } /// Retrive the `not_before` of a [`Delegation`] pub fn not_before(&self) -> Option<&Timestamp> { - self.payload.not_before.as_ref() + self.0.payload.not_before.as_ref() } /// Retrive the `expiration` of a [`Delegation`] pub fn expiration(&self) -> &Timestamp { - &self.payload.expiration + &self.0.payload.expiration } - /// Retrive the `signature` of a [`Delegation`] pub fn check_time(&self, now: SystemTime) -> Result<(), TimeBoundError> { - self.payload.check_time(now) + self.0.payload.check_time(now) + } + + pub fn payload(&self) -> &Payload { + &self.0.payload + } + + pub fn signature(&self) -> &signature::Witness { + &self.0.signature + } + + pub fn validate_signature(&self) -> Result<(), signature::ValidateError> + where + Payload: Clone, + { + self.0.validate_signature() + } + + pub fn try_sign( + signer: &DID::Signer, + payload: Payload, + ) -> Result + where + Payload: Clone, + { + signature::Envelope::try_sign(signer, payload).map(Delegation) } } -impl CheckSame for Delegation { - type Error = ::Error; +impl CheckSame for Delegation { + type Error = ::Error; - fn check_same(&self, proof: &Delegation) -> Result<(), Self::Error> { - self.payload.check_same(&proof.payload) + fn check_same(&self, proof: &Delegation) -> Result<(), Self::Error> { + self.0.payload.check_same(&proof.payload()) } } @@ -101,6 +136,6 @@ impl CheckParents for Delegation::ParentError; fn check_parent(&self, proof: &Self::Parents) -> Result<(), Self::ParentError> { - self.payload.check_parent(&proof.payload) + self.payload().check_parent(&proof.payload()) } } diff --git a/src/delegation/agent.rs b/src/delegation/agent.rs index 5df85df3..7db39707 100644 --- a/src/delegation/agent.rs +++ b/src/delegation/agent.rs @@ -43,7 +43,7 @@ impl< metadata: BTreeMap, expiration: Timestamp, not_before: Option, - now: &SystemTime, + now: SystemTime, ) -> Result, DelegateError<>::Error>> { let mut salt = self.did.clone().to_string().into_bytes(); let nonce = Nonce::generate_12(&mut salt); @@ -71,7 +71,7 @@ impl< .ok_or(DelegateError::ProofsNotFound)? .first() .1 - .payload; + .payload(); let mut conditions = to_delegate.conditions.clone(); conditions.append(&mut new_conditions.clone()); diff --git a/src/delegation/payload.rs b/src/delegation/payload.rs index 0970bcb8..9eafbe71 100644 --- a/src/delegation/payload.rs +++ b/src/delegation/payload.rs @@ -122,12 +122,14 @@ impl Payload { } pub fn check_time(&self, now: SystemTime) -> Result<(), TimeBoundError> { - if SystemTime::from(self.expiration.clone()) < now { + let ts_now = &Timestamp::postel(now); + + if &self.expiration < ts_now { return Err(TimeBoundError::Expired); } - if let Some(nbf) = self.not_before.clone() { - if SystemTime::from(nbf) > now { + if let Some(ref nbf) = self.not_before { + if nbf > ts_now { return Err(TimeBoundError::NotYetValid); } } @@ -371,7 +373,7 @@ impl>, C: Condition, DID: Did { pub fn check( &self, - proofs: Vec>, + proofs: Vec<&Payload>, now: &SystemTime, ) -> Result<(), DelegationError<::Error>> where diff --git a/src/delegation/store.rs b/src/delegation/store.rs index 184e2f96..233492e2 100644 --- a/src/delegation/store.rs +++ b/src/delegation/store.rs @@ -34,7 +34,7 @@ pub trait Store { subject: &DID, builder: &B, conditions: Vec, - now: &SystemTime, + now: SystemTime, ) -> Result)>>, Self::Error>; fn can_delegate( @@ -43,7 +43,7 @@ pub trait Store { audience: &DID, builder: &B, conditions: Vec, - now: &SystemTime, + now: SystemTime, ) -> Result { self.get_chain(audience, issuer, builder, conditions, now) .map(|chain| chain.is_some()) @@ -138,16 +138,14 @@ where fn insert(&mut self, cid: Cid, delegation: Delegation) -> Result<(), Self::Error> { self.index - .entry(delegation.payload.subject.clone()) + .entry(delegation.subject().clone()) .or_default() - .entry(delegation.payload.audience.clone()) + .entry(delegation.audience().clone()) .or_default() .insert(cid); - let hierarchy: Delegation = Delegation { - signature: delegation.signature, - payload: delegation.payload.map_ability(Into::into), - }; + let hierarchy: Delegation = + delegation.map_ability_builder(Into::into); self.ucans.insert(cid.clone(), hierarchy); Ok(()) @@ -164,7 +162,7 @@ where subject: &DID, builder: &B, conditions: Vec, - now: &SystemTime, + now: SystemTime, ) -> Result)>>, Self::Error> { match self.index.get(subject).and_then(|aud_map| aud_map.get(aud)) { None => Ok(None), @@ -188,30 +186,30 @@ where return ControlFlow::Continue(()); } - if d.payload.check_time(*now).is_err() { + if d.check_time(now).is_err() { return ControlFlow::Continue(()); } - target_aud = &d.payload.audience; + target_aud = &d.audience(); - if args.check(&d.payload.ability_builder).is_ok() { - args = &d.payload.ability_builder; + if args.check(&d.ability_builder()).is_ok() { + args = &d.ability_builder(); } else { return ControlFlow::Continue(()); } for condition in &conditions { - if !condition.validate(&d.payload.ability_builder.clone().into()) { + if !condition.validate(&d.ability_builder().clone().into()) { return ControlFlow::Continue(()); } } chain.push((*cid, d)); - if &d.payload.issuer == subject { + if d.issuer() == subject { status = Status::Complete; } else { - target_aud = &d.payload.issuer; + target_aud = &d.issuer(); } ControlFlow::Break(()) diff --git a/src/invocation.rs b/src/invocation.rs index 326272c1..509b6ae5 100644 --- a/src/invocation.rs +++ b/src/invocation.rs @@ -8,8 +8,14 @@ pub mod store; pub use payload::{Payload, Promised}; pub use resolvable::Resolvable; -use crate::{ability, did, did::Did, signature, time::Timestamp}; +use crate::{ + ability, did, + did::Did, + signature, + time::{TimeBoundError, Timestamp}, +}; use libipld_core::{cid::Cid, ipld::Ipld}; +use web_time::SystemTime; /// The complete, signed [`invocation::Payload`][Payload]. /// @@ -58,6 +64,10 @@ impl Invocation { &self.0.payload.issued_at } + pub fn check_time(&self, now: SystemTime) -> Result<(), TimeBoundError> { + self.0.payload.check_time(now) + } + pub fn try_sign( signer: &DID::Signer, payload: Payload, diff --git a/src/invocation/agent.rs b/src/invocation/agent.rs index 48001f96..2d19d317 100644 --- a/src/invocation/agent.rs +++ b/src/invocation/agent.rs @@ -82,7 +82,7 @@ where cause: Option, expiration: Option, issued_at: Option, - now: &SystemTime, + now: SystemTime, // FIXME err type ) -> Result, ()> { let proofs = self @@ -168,7 +168,7 @@ where .get_many(&promised.proofs()) .map_err(|_| ())? .into_iter() - .map(|d| d.payload.clone()) + .map(|d| d.payload()) .collect(); let resolved_payload = promised.payload().clone().map_ability(|_| resolved_ability); @@ -205,7 +205,7 @@ where self.did, &ability.clone().into(), vec![], - &now.into(), + now.into(), ) .map_err(|_| ())? .map(|chain| chain.map(|(index_cid, _)| index_cid).into()) diff --git a/src/invocation/payload.rs b/src/invocation/payload.rs index f1aa0f8c..6f00e12b 100644 --- a/src/invocation/payload.rs +++ b/src/invocation/payload.rs @@ -9,9 +9,8 @@ use crate::{ did::{Did, Verifiable}, nonce::Nonce, proof::{checkable::Checkable, prove::Prove}, - time::Timestamp, + time::{TimeBoundError, Timestamp}, }; -// use anyhow; use libipld_core::{cid::Cid, ipld::Ipld}; use serde::{ de::{self, MapAccess, Visitor}, @@ -69,9 +68,22 @@ impl Payload { } } + // FIXME err type + pub fn check_time(&self, now: SystemTime) -> Result<(), TimeBoundError> { + let ts_now = &Timestamp::postel(now); + + if let Some(ref exp) = self.expiration { + if exp < ts_now { + panic!("FIXME") + } + } + + Ok(()) + } + pub fn check( self, - proofs: Vec::Hierarchy, C, DID>>, + proofs: Vec<&delegation::Payload<::Hierarchy, C, DID>>, now: &SystemTime, ) -> Result<(), DelegationError<<::Hierarchy as Prove>::Error>> where diff --git a/src/signature/envelope.rs b/src/signature/envelope.rs index cd14f420..75d41ec5 100644 --- a/src/signature/envelope.rs +++ b/src/signature/envelope.rs @@ -32,7 +32,7 @@ impl + Capsule, DID: Did> Verifiable for Envelope + Into + Clone, DID: Did> Envelope { +impl + Into, DID: Did> Envelope { /// Attempt to sign some payload with a given signer. /// /// # Arguments @@ -47,7 +47,10 @@ impl + Into + Clone, DID: Did> Envelope Result, SignError> { + pub fn try_sign(signer: &DID::Signer, payload: T) -> Result, SignError> + where + T: Clone, + { Self::try_sign_generic::(signer, DagCborCodec, payload) } @@ -72,6 +75,7 @@ impl + Into + Clone, DID: Did> Envelope Result, SignError> where + T: Clone, Ipld: Encode, { let ipld: Ipld = BTreeMap::from_iter([(T::TAG.into(), payload.clone().into())]).into(); @@ -103,7 +107,10 @@ impl + Into + Clone, DID: Did> Envelope Result<(), ValidateError> { + pub fn validate_signature(&self) -> Result<(), ValidateError> + where + T: Clone, + { // FIXME need varsig let codec = DagCborCodec; diff --git a/src/time/timestamp.rs b/src/time/timestamp.rs index 6e649e74..2d608a98 100644 --- a/src/time/timestamp.rs +++ b/src/time/timestamp.rs @@ -19,7 +19,7 @@ use web_time::{Duration, SystemTime, UNIX_EPOCH}; /// the public API. /// /// [IEEE-754]: https://en.wikipedia.org/wiki/IEEE_754 -#[derive(Debug, Copy, Clone, PartialEq, Eq)] +#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)] #[cfg_attr(target_arch = "wasm32", wasm_bindgen)] pub struct Timestamp { time: SystemTime, From c2dcb1d303d95aec21e5bbfbb4086e60d11bd1f4 Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Sat, 17 Feb 2024 13:17:44 -0800 Subject: [PATCH 091/188] Better docs --- src/delegation/payload.rs | 6 ++--- src/invocation.rs | 51 +++++++++++++++++++++-------------- src/invocation/payload.rs | 57 ++++++++++++++++++++------------------- 3 files changed, 63 insertions(+), 51 deletions(-) diff --git a/src/delegation/payload.rs b/src/delegation/payload.rs index 9eafbe71..4dea88f2 100644 --- a/src/delegation/payload.rs +++ b/src/delegation/payload.rs @@ -368,15 +368,15 @@ impl From> for Ipld { } } -impl>, C: Condition, DID: Did + Clone> - Payload -{ +impl>, C: Condition, DID: Did> Payload { pub fn check( &self, proofs: Vec<&Payload>, now: &SystemTime, ) -> Result<(), DelegationError<::Error>> where + T: Clone, + DID: Clone, T::Hierarchy: Clone + Into>, { let start: Acc = Acc { diff --git a/src/invocation.rs b/src/invocation.rs index 509b6ae5..4465474a 100644 --- a/src/invocation.rs +++ b/src/invocation.rs @@ -22,17 +22,17 @@ use web_time::SystemTime; /// # Promises /// /// For a version that can include [`Promise`][promise::Promise]s, -/// wrap your `T` in [`invocation::Promised`](Promised) to get -/// `Invocation>`. +/// wrap your `A` in [`invocation::Promised`](Promised) to get +/// `Invocation>`. #[derive(Debug, Clone, PartialEq)] -pub struct Invocation(pub signature::Envelope, DID>); +pub struct Invocation(pub signature::Envelope, DID>); // FIXME use presnet ability, too pub type Preset = Invocation; pub type PresetPromised = Invocation; -impl Invocation { - pub fn payload(&self) -> &Payload { +impl Invocation { + pub fn payload(&self) -> &Payload { &self.0.payload } @@ -52,10 +52,20 @@ impl Invocation { &self.0.payload.subject } - pub fn ability(&self) -> &T { + pub fn ability(&self) -> &A { &self.0.payload.ability } + pub fn map_ability(self, f: F) -> Invocation + where + F: FnOnce(A) -> Z, + { + Invocation(signature::Envelope { + payload: self.0.payload.map_ability(f), + signature: self.0.signature, + }) + } + pub fn proofs(&self) -> &Vec { &self.0.payload.proofs } @@ -64,35 +74,36 @@ impl Invocation { &self.0.payload.issued_at } - pub fn check_time(&self, now: SystemTime) -> Result<(), TimeBoundError> { + pub fn expiration(&self) -> &Option { + &self.0.payload.expiration + } + + pub fn check_time(&self, now: SystemTime) -> Result<(), TimeBoundError> + where + A: Clone, + { self.0.payload.check_time(now) } pub fn try_sign( signer: &DID::Signer, - payload: Payload, - ) -> Result, signature::SignError> { + payload: Payload, + ) -> Result, signature::SignError> + where + Payload: Clone, + { let envelope = signature::Envelope::try_sign(signer, payload)?; Ok(Invocation(envelope)) } } -impl did::Verifiable for Invocation { +impl did::Verifiable for Invocation { fn verifier(&self) -> &DID { &self.0.verifier() } } -impl Invocation { - pub fn map_ability(self, f: impl FnOnce(T) -> T) -> Self { - let mut payload = self.0.payload; - payload.ability = f(payload.ability); - Invocation(signature::Envelope { - payload, - signature: self.0.signature, - }) - } -} +impl Invocation {} impl From> for Ipld { fn from(invocation: Invocation) -> Self { diff --git a/src/invocation/payload.rs b/src/invocation/payload.rs index 6f00e12b..5d2aba6c 100644 --- a/src/invocation/payload.rs +++ b/src/invocation/payload.rs @@ -27,12 +27,12 @@ impl Verifiable for Payload { } #[derive(Debug, Clone, PartialEq)] -pub struct Payload { +pub struct Payload { pub issuer: DID, pub subject: DID, pub audience: Option, - pub ability: T, + pub ability: A, pub proofs: Vec, pub cause: Option, @@ -49,10 +49,10 @@ pub struct Payload { // // This probably means putting the delegation T back to the upper level and bieng explicit about // the T::Builder in the type -impl Payload { - pub fn map_ability(self, f: F) -> Payload +impl Payload { + pub fn map_ability(self, f: F) -> Payload where - F: FnOnce(T) -> U, + F: FnOnce(A) -> Z, { Payload { issuer: self.issuer, @@ -83,33 +83,34 @@ impl Payload { pub fn check( self, - proofs: Vec<&delegation::Payload<::Hierarchy, C, DID>>, + proofs: Vec<&delegation::Payload<::Hierarchy, C, DID>>, now: &SystemTime, - ) -> Result<(), DelegationError<<::Hierarchy as Prove>::Error>> + ) -> Result<(), DelegationError<<::Hierarchy as Prove>::Error>> where - T: Delegable, - T::Builder: Clone + Checkable + Prove + Into>, - ::Hierarchy: Clone + Into>, + A: Delegable, + A::Builder: Clone + Into>, + ::Hierarchy: Clone + Into>, + DID: Clone, { - let builder_payload: delegation::Payload = self.into(); + let builder_payload: delegation::Payload = self.into(); builder_payload.check(proofs, now) } } -impl Capsule for Payload { +impl Capsule for Payload { const TAG: &'static str = "ucan/i/1.0.0-rc.1"; } -impl From> - for delegation::Payload +impl From> + for delegation::Payload { - fn from(inv_payload: Payload) -> Self { + fn from(inv_payload: Payload) -> Self { delegation::Payload { - issuer: inv_payload.issuer.clone(), + issuer: inv_payload.issuer, subject: inv_payload.subject.clone(), audience: inv_payload.audience.unwrap_or(inv_payload.subject), - ability_builder: T::Builder::from(inv_payload.ability), + ability_builder: A::Builder::from(inv_payload.ability), conditions: vec![], metadata: inv_payload.metadata, @@ -123,8 +124,8 @@ impl From> } } -impl, DID: Did> From> for arguments::Named { - fn from(payload: Payload) -> Self { +impl, DID: Did> From> for arguments::Named { + fn from(payload: Payload) -> Self { let mut args = arguments::Named::from_iter([ ("iss".into(), payload.issuer.into().to_string().into()), ("sub".into(), payload.subject.into().to_string().into()), @@ -153,9 +154,9 @@ impl, DID: Did> From> for arguments::N } } -impl Serialize for Payload +impl Serialize for Payload where - T: ToCommand + Into + Serialize, + A: ToCommand + Into + Serialize, DID: Did + Serialize, { fn serialize(&self, serializer: S) -> Result @@ -202,14 +203,14 @@ where } } -impl<'de, T: ParseAbility + Deserialize<'de>, DID: Did + Deserialize<'de>> Deserialize<'de> - for Payload +impl<'de, A: ParseAbility + Deserialize<'de>, DID: Did + Deserialize<'de>> Deserialize<'de> + for Payload { - fn deserialize(deserializer: D) -> Result, D::Error> + fn deserialize(deserializer: D) -> Result, D::Error> where D: de::Deserializer<'de>, { - struct InvocationPayloadVisitor(std::marker::PhantomData<(T, DID)>); + struct InvocationPayloadVisitor(std::marker::PhantomData<(A, DID)>); const FIELDS: &'static [&'static str] = &[ "iss", "sub", "aud", "cmd", "args", "prf", "nonce", "cause", "meta", "iat", "exp", @@ -347,10 +348,10 @@ impl<'de, T: ParseAbility + Deserialize<'de>, DID: Did + Deserialize<'de>> Deser /// A variant that accepts [`Promise`]s. /// /// [`Promise`]: crate::invocation::promise::Promise -pub type Promised = Payload<::Promised, DID>; +pub type Promised = Payload<::Promised, DID>; -impl From> for Ipld { - fn from(payload: Payload) -> Self { +impl From> for Ipld { + fn from(payload: Payload) -> Self { payload.into() } } From a358ffb9aa56a42cfa7f471b9e46b467c9eb81c5 Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Sat, 17 Feb 2024 16:32:46 -0800 Subject: [PATCH 092/188] Break up a few modules --- src/ability/crud.rs | 2 +- src/ability/crud/create.rs | 4 +- src/ability/crud/destroy.rs | 4 +- src/ability/crud/read.rs | 4 +- src/ability/crud/update.rs | 4 +- src/ability/msg.rs | 2 +- src/ability/msg/receive.rs | 4 +- src/ability/msg/send.rs | 4 +- src/ability/preset.rs | 2 +- src/ability/ucan/revoke.rs | 42 +++-- src/ability/wasm/run.rs | 4 +- src/delegation.rs | 19 +- src/delegation/agent.rs | 6 + src/delegation/condition.rs | 2 + src/delegation/delegable.rs | 10 +- src/delegation/error.rs | 24 --- src/delegation/payload.rs | 54 +++--- src/delegation/store.rs | 236 +------------------------ src/delegation/store/memory.rs | 185 +++++++++++++++++++ src/delegation/store/traits.rs | 57 ++++++ src/invocation.rs | 43 +++-- src/invocation/agent.rs | 11 +- src/invocation/payload.rs | 95 +++++++--- src/invocation/promise.rs | 6 +- src/invocation/promise/any.rs | 8 + src/invocation/promise/err.rs | 6 + src/invocation/promise/ok.rs | 6 + src/invocation/promise/resolvable.rs | 22 +++ src/invocation/promise/resolves.rs | 8 + src/invocation/promise/store.rs | 7 + src/invocation/promise/store/memory.rs | 46 +++++ src/invocation/promise/store/traits.rs | 14 ++ src/invocation/resolvable.rs | 19 -- src/invocation/store.rs | 78 +------- src/time.rs | 2 +- src/time/error.rs | 9 +- 36 files changed, 599 insertions(+), 450 deletions(-) delete mode 100644 src/delegation/error.rs create mode 100644 src/delegation/store/memory.rs create mode 100644 src/delegation/store/traits.rs create mode 100644 src/invocation/promise/resolvable.rs create mode 100644 src/invocation/promise/store.rs create mode 100644 src/invocation/promise/store/memory.rs create mode 100644 src/invocation/promise/store/traits.rs delete mode 100644 src/invocation/resolvable.rs diff --git a/src/ability/crud.rs b/src/ability/crud.rs index c080ae6d..98c8804e 100644 --- a/src/ability/crud.rs +++ b/src/ability/crud.rs @@ -54,7 +54,7 @@ pub use parents::*; use crate::{ ability::arguments, delegation::Delegable, - invocation::Resolvable, + invocation::promise::Resolvable, proof::{checkable::Checkable, parentful::Parentful, parents::CheckParents, same::CheckSame}, }; use libipld_core::ipld::Ipld; diff --git a/src/ability/crud/create.rs b/src/ability/crud/create.rs index 622cb06a..e62494ac 100644 --- a/src/ability/crud/create.rs +++ b/src/ability/crud/create.rs @@ -3,7 +3,7 @@ use super::parents::MutableParents; use crate::{ ability::{arguments, command::Command}, delegation::Delegable, - invocation::{promise, promise::Resolves, Resolvable}, + invocation::{promise, promise::Resolves}, ipld, proof::{checkable::Checkable, parentful::Parentful, parents::CheckParents, same::CheckSame}, }; @@ -276,7 +276,7 @@ impl From for Builder { } } -impl Resolvable for Ready { +impl promise::Resolvable for Ready { type Promised = Promised; fn try_resolve(p: Promised) -> Result { diff --git a/src/ability/crud/destroy.rs b/src/ability/crud/destroy.rs index ec487632..f47200e0 100644 --- a/src/ability/crud/destroy.rs +++ b/src/ability/crud/destroy.rs @@ -3,7 +3,7 @@ use super::parents::MutableParents; use crate::{ ability::{arguments, command::Command}, delegation::Delegable, - invocation::{promise, Resolvable}, + invocation::promise, ipld, proof::{checkable::Checkable, parentful::Parentful, parents::CheckParents, same::CheckSame}, }; @@ -226,7 +226,7 @@ impl From for Promised { } } -impl Resolvable for Ready { +impl promise::Resolvable for Ready { type Promised = Promised; fn try_resolve(p: Promised) -> Result { diff --git a/src/ability/crud/read.rs b/src/ability/crud/read.rs index 6c2ce833..99ee9022 100644 --- a/src/ability/crud/read.rs +++ b/src/ability/crud/read.rs @@ -4,7 +4,7 @@ use super::any as crud; use crate::{ ability::{arguments, command::Command}, delegation::Delegable, - invocation::{promise, promise::Resolves, Resolvable}, + invocation::{promise, promise::Resolves}, ipld, proof::{checkable::Checkable, parentful::Parentful, parents::CheckParents, same::CheckSame}, }; @@ -249,7 +249,7 @@ impl From for Promised { } } -impl Resolvable for Ready { +impl promise::Resolvable for Ready { type Promised = Promised; fn try_resolve(p: Promised) -> Result { diff --git a/src/ability/crud/update.rs b/src/ability/crud/update.rs index fdb81752..0dc4f992 100644 --- a/src/ability/crud/update.rs +++ b/src/ability/crud/update.rs @@ -3,7 +3,7 @@ use super::parents::MutableParents; use crate::{ ability::{arguments, command::Command}, delegation::Delegable, - invocation::{promise, promise::Resolves, Resolvable}, + invocation::{promise, promise::Resolves}, ipld, proof::{checkable::Checkable, parentful::Parentful, parents::CheckParents, same::CheckSame}, }; @@ -290,7 +290,7 @@ impl From for Promised { } } -impl Resolvable for Ready { +impl promise::Resolvable for Ready { type Promised = Promised; fn try_resolve(p: Promised) -> Result { diff --git a/src/ability/msg.rs b/src/ability/msg.rs index 01e1ee0a..7f7e33ab 100644 --- a/src/ability/msg.rs +++ b/src/ability/msg.rs @@ -11,7 +11,7 @@ pub use receive::Receive; use crate::{ ability::arguments, delegation::Delegable, - invocation::Resolvable, + invocation::promise::Resolvable, proof::{checkable::Checkable, parentful::Parentful, parents::CheckParents, same::CheckSame}, }; use libipld_core::ipld::Ipld; diff --git a/src/ability/msg/receive.rs b/src/ability/msg/receive.rs index 1c152c9f..36381e04 100644 --- a/src/ability/msg/receive.rs +++ b/src/ability/msg/receive.rs @@ -3,7 +3,7 @@ use crate::{ ability::{arguments, command::Command}, delegation::Delegable, - invocation::{promise, Resolvable}, + invocation::promise, proof::{checkable::Checkable, parentful::Parentful, parents::CheckParents, same::CheckSame}, url, }; @@ -132,7 +132,7 @@ impl From for arguments::Named { } } -impl Resolvable for Receive { +impl promise::Resolvable for Receive { type Promised = Promised; fn try_resolve(p: Promised) -> Result { diff --git a/src/ability/msg/send.rs b/src/ability/msg/send.rs index d5974af7..96d3299b 100644 --- a/src/ability/msg/send.rs +++ b/src/ability/msg/send.rs @@ -3,7 +3,7 @@ use crate::{ ability::{arguments, command::Command}, delegation::Delegable, - invocation::{promise, Resolvable}, + invocation::promise, proof::{checkable::Checkable, parentful::Parentful, parents::CheckParents, same::CheckSame}, url as url_newtype, }; @@ -127,7 +127,7 @@ impl Delegable for Ready { type Builder = Builder; } -impl Resolvable for Ready { +impl promise::Resolvable for Ready { type Promised = Promised; fn try_resolve(p: Promised) -> Result { diff --git a/src/ability/preset.rs b/src/ability/preset.rs index 48ebb30b..50822bb7 100644 --- a/src/ability/preset.rs +++ b/src/ability/preset.rs @@ -2,7 +2,7 @@ use super::{crud, msg, wasm}; use crate::{ ability::{arguments, command::ParseAbility}, delegation::Delegable, - invocation::Resolvable, + invocation::promise::Resolvable, proof::{checkable::Checkable, parentful::Parentful, parents::CheckParents, same::CheckSame}, }; use libipld_core::ipld::Ipld; diff --git a/src/ability/ucan/revoke.rs b/src/ability/ucan/revoke.rs index ac3aacb8..a0d61502 100644 --- a/src/ability/ucan/revoke.rs +++ b/src/ability/ucan/revoke.rs @@ -1,32 +1,28 @@ -//! UCAN [Revocations](https://github.com/ucan-wg/revocation) +//! This is an ability for revoking [`Delegation`][crate::delegation::Delegation]s by their [`Cid`]. +//! +//! For more, see the [UCAN Revocation spec](https://github.com/ucan-wg/revocation). use crate::{ ability::{arguments, command::Command}, delegation::Delegable, - invocation::{promise, Resolvable}, - proof::{parentless::NoParents, same::CheckSame}, + invocation::promise, + proof::{error::OptionalFieldError, parentless::NoParents, same::CheckSame}, }; use libipld_core::{cid::Cid, ipld::Ipld}; use serde::{Deserialize, Serialize}; use std::{collections::BTreeMap, fmt::Debug}; -/// An ability for revoking previously issued UCANs by [`Cid`] +/// The fully resolved variant: ready to execute. #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] -pub struct Generic { - // FIXME check spec +pub struct Ready { /// The UCAN to revoke - pub ucan: Arg, + pub ucan: Cid, } -impl Command for Generic { +impl Command for Ready { const COMMAND: &'static str = "ucan/revoke"; } -/// The fully resolved variant: ready to execute. -pub type Ready = Generic; - -impl NoParents for Builder {} - impl Delegable for Ready { type Builder = Builder; } @@ -39,7 +35,7 @@ impl From for Builder { } } -impl Resolvable for Ready { +impl promise::Resolvable for Ready { type Promised = Promised; fn try_resolve(promised: Self::Promised) -> Result { @@ -51,13 +47,18 @@ impl Resolvable for Ready { } /// A variant with some fields waiting to be set. -pub type Builder = Generic>; +#[derive(Debug, Default, Clone, PartialEq, Serialize, Deserialize)] +pub struct Builder { + pub ucan: Option, +} + +impl NoParents for Builder {} impl CheckSame for Builder { - type Error = (); // FIXME + type Error = OptionalFieldError; fn check_same(&self, proof: &Self) -> Result<(), Self::Error> { - self.ucan.check_same(&proof.ucan).map_err(|_| ()) + self.ucan.check_same(&proof.ucan) } } @@ -89,8 +90,11 @@ impl From for arguments::Named { } } -/// A variant where arguments may be [`Promise`]s. -pub type Promised = Generic>; +/// A variant where arguments may be [`Promise`][crate::invocation::promise]s. +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub struct Promised { + pub ucan: promise::Resolves, +} impl From for Promised { fn from(r: Ready) -> Promised { diff --git a/src/ability/wasm/run.rs b/src/ability/wasm/run.rs index 6ee7d6df..ecf4be6a 100644 --- a/src/ability/wasm/run.rs +++ b/src/ability/wasm/run.rs @@ -4,7 +4,7 @@ use super::module::Module; use crate::{ ability::{arguments, command::Command}, delegation::Delegable, - invocation::{promise, Resolvable}, + invocation::promise, proof::{parentless::NoParents, same::CheckSame}, }; use libipld_core::ipld::Ipld; @@ -35,7 +35,7 @@ impl Delegable for Ready { type Builder = Builder; } -impl Resolvable for Ready { +impl promise::Resolvable for Ready { type Promised = Promised; fn try_resolve(promised: Self::Promised) -> Result { diff --git a/src/delegation.rs b/src/delegation.rs index 9d71ce31..d0b951ad 100644 --- a/src/delegation.rs +++ b/src/delegation.rs @@ -1,5 +1,18 @@ +//! An [`Delegation`] is the way to grant someone else the use of [`Ability`][crate::ability]. +//! +//! ## Data +//! +//! - [`Delegation`] is the top-level, signed data struture. +//! - [`Payload`] is the fields unique to an invocation. +//! - [`Preset`] is an [`Delegation`] preloaded with this library's [preset abilities](crate::ability::preset::Ready). +//! - [`Condition`]s are syntactically-driven validation rules for [`Delegation`]s. +//! +//! ## Stateful Helpers +//! +//! - [`Agent`] is a high-level interface for sessions that will involve more than one invoctaion. +//! - [`store`] is an interface for caching [`Delegation`]s. + pub mod condition; -pub mod error; pub mod store; mod agent; @@ -8,7 +21,7 @@ mod payload; pub use agent::Agent; pub use delegable::Delegable; -pub use payload::Payload; +pub use payload::{Payload, ValidationError}; use crate::{ ability, @@ -29,10 +42,10 @@ use web_time::SystemTime; /// /// # Examples /// FIXME -/// FIXME wrap in struct to make the docs & error messages better? #[derive(Clone, Debug, PartialEq)] pub struct Delegation(pub signature::Envelope, DID>); +/// A variant of [`Delegation`] that has the abilties and DIDs from this library pre-filled. pub type Preset = Delegation; // FIXME checkable -> provable? diff --git a/src/delegation/agent.rs b/src/delegation/agent.rs index 7db39707..a9a152ce 100644 --- a/src/delegation/agent.rs +++ b/src/delegation/agent.rs @@ -5,9 +5,15 @@ use std::{collections::BTreeMap, marker::PhantomData}; use thiserror::Error; use web_time::SystemTime; +/// A stateful agent capable of delegatint to others, and being delegated to. +/// +/// This is helpful for sessions where more than one delegation will be made. #[derive(Debug)] pub struct Agent<'a, B: Checkable, C: Condition, DID: Did, S: Store> { + /// The [`Did`][Did] of the agent. pub did: &'a DID, + + /// The attached [`deleagtion::Store`][super::store::Store]. pub store: &'a mut S, signer: &'a ::Signer, diff --git a/src/delegation/condition.rs b/src/delegation/condition.rs index 79746ff1..f9594bbc 100644 --- a/src/delegation/condition.rs +++ b/src/delegation/condition.rs @@ -1,3 +1,5 @@ +//! Conditions for syntactic validation of abilities in [`Delegation`][super::Delegation]s. + mod contains_all; mod contains_any; mod contains_key; diff --git a/src/delegation/delegable.rs b/src/delegation/delegable.rs index 925aee08..19806dd5 100644 --- a/src/delegation/delegable.rs +++ b/src/delegation/delegable.rs @@ -1,7 +1,13 @@ use crate::proof::checkable::Checkable; +/// A trait for types that can be delegated. +/// +/// Since [`Delegation`]s may omit fields (until [`Invocation`]), +/// this trait helps associate the delegatable variant to the invocable one. +/// +/// [`Delegation`]: crate::delegation::Delegation +/// [`Invocation`]: crate::invocation::Invocation pub trait Delegable: Sized { - /// A delegation with some arguments filled - /// FIXME add more text + /// A delegation with some arguments filled. type Builder: TryInto + From + Checkable; } diff --git a/src/delegation/error.rs b/src/delegation/error.rs deleted file mode 100644 index 21bafe1e..00000000 --- a/src/delegation/error.rs +++ /dev/null @@ -1,24 +0,0 @@ -// FIXME rename this is not for the sign envelope -#[derive(Debug, Clone, PartialEq, Eq)] -pub enum EnvelopeError { - InvalidSubject, - MisalignedIssAud, - Expired, - NotYetValid, -} - -// FIXME Error, etc -#[derive(Debug, Clone, PartialEq, Eq)] -pub enum DelegationError { - Envelope(EnvelopeError), - - FailedCondition, // FIXME add context? - - SemanticError(Semantic), -} - -impl From for DelegationError { - fn from(err: EnvelopeError) -> Self { - DelegationError::Envelope(err) - } -} diff --git a/src/delegation/payload.rs b/src/delegation/payload.rs index 4dea88f2..4143da78 100644 --- a/src/delegation/payload.rs +++ b/src/delegation/payload.rs @@ -1,7 +1,4 @@ -use super::{ - condition::Condition, - error::{DelegationError, EnvelopeError}, -}; +use super::condition::Condition; use crate::{ ability::{ arguments, @@ -25,6 +22,7 @@ use serde::{ Deserialize, Serialize, Serializer, }; use std::{collections::BTreeMap, fmt, fmt::Debug}; +use thiserror::Error; use web_time::SystemTime; impl Verifiable for Payload { @@ -373,9 +371,10 @@ impl>, C: Condition, DID: Did> Payloa &self, proofs: Vec<&Payload>, now: &SystemTime, - ) -> Result<(), DelegationError<::Error>> + ) -> Result<(), ValidationError<::Error, C>> where T: Clone, + C: fmt::Debug + Clone, DID: Clone, T::Hierarchy: Clone + Into>, { @@ -426,25 +425,26 @@ impl Acc { proof: &Payload, args: &arguments::Named, now: &SystemTime, - ) -> Result::Error>> + ) -> Result::Error, C>> where + C: fmt::Debug + Clone, H: Prove + Clone + Into>, { if self.issuer != proof.audience { - return Err(EnvelopeError::InvalidSubject.into()); + return Err(ValidationError::InvalidSubject.into()); } if self.subject != proof.subject { - return Err(EnvelopeError::MisalignedIssAud.into()); + return Err(ValidationError::MisalignedIssAud.into()); } if SystemTime::from(proof.expiration.clone()) > *now { - return Err(EnvelopeError::Expired.into()); + return Err(ValidationError::Expired.into()); } if let Some(nbf) = proof.not_before.clone() { if SystemTime::from(nbf) > *now { - return Err(EnvelopeError::NotYetValid.into()); + return Err(ValidationError::NotYetValid.into()); } } @@ -456,22 +456,34 @@ impl Acc { // Plz let me know if I got this wrong. // —@expede if !c.validate(&args) || !c.validate(&self.hierarchy.clone().into()) { - return Err(DelegationError::FailedCondition); + return Err(ValidationError::FailedCondition(c.clone())); } } self.hierarchy .check(&proof.ability_builder.clone()) - .map_err(DelegationError::SemanticError) + .map_err(ValidationError::AbilityError) } } -// use crate::proof::{parentful::Parentful, parentless::Parentless}; -// -// impl>, C, DID: Did> Checkable for Payload { -// type Hierarchy = Parentless>; -// } -// -// impl>, C, DID: Did> Checkable for Payload { -// type Hierarchy = Parentful>; -// } +/// Delegation validation errors. +#[derive(Debug, Clone, PartialEq, Eq, Error)] +pub enum ValidationError { + #[error("The subject of the delegation is invalid")] + InvalidSubject, + + #[error("The issuer and audience of the delegation are misaligned")] + MisalignedIssAud, + + #[error("The delegation has expired")] + Expired, + + #[error("The delegation is not yet valid")] + NotYetValid, + + #[error("The delegation failed a condition: {0:?}")] + FailedCondition(C), // FIXME add context? + + #[error(transparent)] + AbilityError(AbilityError), +} diff --git a/src/delegation/store.rs b/src/delegation/store.rs index 233492e2..680d5f99 100644 --- a/src/delegation/store.rs +++ b/src/delegation/store.rs @@ -1,233 +1,7 @@ -use super::{condition::Condition, Delegation}; -use crate::{ - ability::arguments, - did::Did, - proof::{checkable::Checkable, prove::Prove}, -}; -use libipld_core::{cid::Cid, ipld::Ipld}; -use nonempty::NonEmpty; -use std::{ - collections::{BTreeMap, BTreeSet}, - ops::ControlFlow, -}; -use web_time::SystemTime; +//! Storage interface for [`Delegation`][super::Delegation]s. -// NOTE the T here is the builder... FIXME add one layer up and call T::Builder? May be confusing? -pub trait Store { - type Error; +mod memory; +mod traits; - fn get(&self, cid: &Cid) -> Result<&Delegation, Self::Error>; - - // FIXME add a variant that calculated the CID from the capsulre header? - // FIXME that means changing the name to insert_by_cid or similar - // FIXME rename put - fn insert(&mut self, cid: Cid, delegation: Delegation) -> Result<(), Self::Error>; - - // FIXME validate invocation - // sore invocation - // just... move to invocation - fn revoke(&mut self, cid: Cid) -> Result<(), Self::Error>; - - fn get_chain( - &self, - audience: &DID, - subject: &DID, - builder: &B, - conditions: Vec, - now: SystemTime, - ) -> Result)>>, Self::Error>; - - fn can_delegate( - &self, - issuer: &DID, - audience: &DID, - builder: &B, - conditions: Vec, - now: SystemTime, - ) -> Result { - self.get_chain(audience, issuer, builder, conditions, now) - .map(|chain| chain.is_some()) - } - - fn get_many( - &self, - cids: &[Cid], - ) -> Result>, Self::Error> { - cids.iter().try_fold(vec![], |mut acc, cid| { - let d: &Delegation = self.get(cid)?; - acc.push(d); - Ok(acc) - }) - } -} - -#[cfg_attr(doc, aquamarine::aquamarine)] -/// A simple in-memory store for delegations. -/// -/// The store is laid out as follows: -/// -/// `{Subject => {Audience => {Cid => Delegation}}}` -/// -/// ```mermaid -/// flowchart LR -/// subgraph Subjects -/// direction TB -/// -/// Akiko -/// Boris -/// Carol -/// -/// subgraph aud[Boris's Audiences] -/// direction TB -/// -/// Denzel -/// Erin -/// Frida -/// Georgia -/// Hugo -/// -/// subgraph cid[Frida's CIDs] -/// direction LR -/// -/// CID1 --> Delegation1 -/// CID2 --> Delegation2 -/// CID3 --> Delegation3 -/// end -/// end -/// end -/// -/// Akiko ~~~ Hugo -/// Carol ~~~ Hugo -/// Boris --> Frida --> CID2 -/// -/// Boris -.-> Denzel -/// Boris -.-> Erin -/// Boris -.-> Georgia -/// Boris -.-> Hugo -/// -/// Frida -.-> CID1 -/// Frida -.-> CID3 -/// -/// style Boris stroke:orange; -/// style Frida stroke:orange; -/// style CID2 stroke:orange; -/// style Delegation2 stroke:orange; -/// -/// linkStyle 5 stroke:orange; -/// linkStyle 6 stroke:orange; -/// linkStyle 1 stroke:orange; -/// ``` -#[derive(Debug, Clone, PartialEq)] -pub struct MemoryStore { - ucans: BTreeMap>, - index: BTreeMap>>, - revocations: BTreeSet, -} - -// FIXME check that UCAN is valid -impl Store - for MemoryStore -where - B::Hierarchy: Into> + Clone, -{ - type Error = (); // FIXME misisng - - fn get(&self, cid: &Cid) -> Result<&Delegation, Self::Error> { - self.ucans.get(cid).ok_or(()) - } - - fn insert(&mut self, cid: Cid, delegation: Delegation) -> Result<(), Self::Error> { - self.index - .entry(delegation.subject().clone()) - .or_default() - .entry(delegation.audience().clone()) - .or_default() - .insert(cid); - - let hierarchy: Delegation = - delegation.map_ability_builder(Into::into); - - self.ucans.insert(cid.clone(), hierarchy); - Ok(()) - } - - fn revoke(&mut self, cid: Cid) -> Result<(), Self::Error> { - self.revocations.insert(cid); - Ok(()) - } - - fn get_chain( - &self, - aud: &DID, - subject: &DID, - builder: &B, - conditions: Vec, - now: SystemTime, - ) -> Result)>>, Self::Error> { - match self.index.get(subject).and_then(|aud_map| aud_map.get(aud)) { - None => Ok(None), - Some(delegation_subtree) => { - #[derive(PartialEq)] - enum Status { - Complete, - Looking, - NoPath, - } - - let mut status = Status::Looking; - let mut target_aud = aud; - let mut args = &B::Hierarchy::from(builder.clone()); - let mut chain = vec![]; - - while status == Status::Looking { - let found = delegation_subtree.iter().try_for_each(|cid| { - if let Some(d) = self.ucans.get(cid) { - if self.revocations.contains(cid) { - return ControlFlow::Continue(()); - } - - if d.check_time(now).is_err() { - return ControlFlow::Continue(()); - } - - target_aud = &d.audience(); - - if args.check(&d.ability_builder()).is_ok() { - args = &d.ability_builder(); - } else { - return ControlFlow::Continue(()); - } - - for condition in &conditions { - if !condition.validate(&d.ability_builder().clone().into()) { - return ControlFlow::Continue(()); - } - } - - chain.push((*cid, d)); - - if d.issuer() == subject { - status = Status::Complete; - } else { - target_aud = &d.issuer(); - } - - ControlFlow::Break(()) - } else { - ControlFlow::Continue(()) - } - }); - - if found.is_continue() { - status = Status::NoPath; - } - } - - match status { - Status::Complete => Ok(NonEmpty::from_vec(chain)), - _ => Ok(None), - } - } - } - } -} +pub use memory::MemoryStore; +pub use traits::Store; diff --git a/src/delegation/store/memory.rs b/src/delegation/store/memory.rs new file mode 100644 index 00000000..27dd6e93 --- /dev/null +++ b/src/delegation/store/memory.rs @@ -0,0 +1,185 @@ +use super::Store; +use crate::{ + ability::arguments, + delegation::{condition::Condition, Delegation}, + did::Did, + proof::{checkable::Checkable, prove::Prove}, +}; +use libipld_core::{cid::Cid, ipld::Ipld}; +use nonempty::NonEmpty; +use std::{ + collections::{BTreeMap, BTreeSet}, + ops::ControlFlow, +}; +use web_time::SystemTime; + +#[cfg_attr(doc, aquamarine::aquamarine)] +/// A simple in-memory store for delegations. +/// +/// The store is laid out as follows: +/// +/// `{Subject => {Audience => {Cid => Delegation}}}` +/// +/// ```mermaid +/// flowchart LR +/// subgraph Subjects +/// direction TB +/// +/// Akiko +/// Boris +/// Carol +/// +/// subgraph aud[Boris's Audiences] +/// direction TB +/// +/// Denzel +/// Erin +/// Frida +/// Georgia +/// Hugo +/// +/// subgraph cid[Frida's CIDs] +/// direction LR +/// +/// CID1 --> Delegation1 +/// CID2 --> Delegation2 +/// CID3 --> Delegation3 +/// end +/// end +/// end +/// +/// Akiko ~~~ Hugo +/// Carol ~~~ Hugo +/// Boris --> Frida --> CID2 +/// +/// Boris -.-> Denzel +/// Boris -.-> Erin +/// Boris -.-> Georgia +/// Boris -.-> Hugo +/// +/// Frida -.-> CID1 +/// Frida -.-> CID3 +/// +/// style Boris stroke:orange; +/// style Frida stroke:orange; +/// style CID2 stroke:orange; +/// style Delegation2 stroke:orange; +/// +/// linkStyle 5 stroke:orange; +/// linkStyle 6 stroke:orange; +/// linkStyle 1 stroke:orange; +/// ``` +#[derive(Debug, Clone, PartialEq)] +pub struct MemoryStore { + ucans: BTreeMap>, + index: BTreeMap>>, + revocations: BTreeSet, +} + +// FIXME check that UCAN is valid +impl Store + for MemoryStore +where + B::Hierarchy: Into> + Clone, +{ + type Error = (); // FIXME misisng + + fn get(&self, cid: &Cid) -> Result<&Delegation, Self::Error> { + self.ucans.get(cid).ok_or(()) + } + + fn insert(&mut self, cid: Cid, delegation: Delegation) -> Result<(), Self::Error> { + self.index + .entry(delegation.subject().clone()) + .or_default() + .entry(delegation.audience().clone()) + .or_default() + .insert(cid); + + let hierarchy: Delegation = + delegation.map_ability_builder(Into::into); + + self.ucans.insert(cid.clone(), hierarchy); + Ok(()) + } + + fn revoke(&mut self, cid: Cid) -> Result<(), Self::Error> { + self.revocations.insert(cid); + Ok(()) + } + + fn get_chain( + &self, + aud: &DID, + subject: &DID, + builder: &B, + conditions: Vec, + now: SystemTime, + ) -> Result)>>, Self::Error> { + match self.index.get(subject).and_then(|aud_map| aud_map.get(aud)) { + None => Ok(None), + Some(delegation_subtree) => { + #[derive(PartialEq)] + enum Status { + Complete, + Looking, + NoPath, + } + + let mut status = Status::Looking; + let mut target_aud = aud; + let mut args = &B::Hierarchy::from(builder.clone()); + let mut chain = vec![]; + + while status == Status::Looking { + let found = delegation_subtree.iter().try_for_each(|cid| { + if let Some(d) = self.ucans.get(cid) { + if self.revocations.contains(cid) { + return ControlFlow::Continue(()); + } + + if d.check_time(now).is_err() { + return ControlFlow::Continue(()); + } + + target_aud = &d.audience(); + + if args.check(&d.ability_builder()).is_ok() { + args = &d.ability_builder(); + } else { + return ControlFlow::Continue(()); + } + + for condition in &conditions { + if !condition.validate(&d.ability_builder().clone().into()) { + return ControlFlow::Continue(()); + } + } + + chain.push((*cid, d)); + + if d.issuer() == subject { + status = Status::Complete; + } else { + target_aud = &d.issuer(); + } + + ControlFlow::Break(()) + } else { + ControlFlow::Continue(()) + } + }); + + if found.is_continue() { + status = Status::NoPath; + } + } + + match status { + Status::Complete => Ok(NonEmpty::from_vec(chain)), + _ => Ok(None), + } + } + } + } +} diff --git a/src/delegation/store/traits.rs b/src/delegation/store/traits.rs new file mode 100644 index 00000000..c1129344 --- /dev/null +++ b/src/delegation/store/traits.rs @@ -0,0 +1,57 @@ +use crate::{ + delegation::{condition::Condition, Delegation}, + did::Did, + proof::checkable::Checkable, +}; +use libipld_core::cid::Cid; +use nonempty::NonEmpty; +use web_time::SystemTime; + +// NOTE the T here is the builder... FIXME add one layer up and call T::Builder? May be confusing? +pub trait Store { + type Error; + + fn get(&self, cid: &Cid) -> Result<&Delegation, Self::Error>; + + // FIXME add a variant that calculated the CID from the capsulre header? + // FIXME that means changing the name to insert_by_cid or similar + // FIXME rename put + fn insert(&mut self, cid: Cid, delegation: Delegation) -> Result<(), Self::Error>; + + // FIXME validate invocation + // sore invocation + // just... move to invocation + fn revoke(&mut self, cid: Cid) -> Result<(), Self::Error>; + + fn get_chain( + &self, + audience: &DID, + subject: &DID, + builder: &B, + conditions: Vec, + now: SystemTime, + ) -> Result)>>, Self::Error>; + + fn can_delegate( + &self, + issuer: &DID, + audience: &DID, + builder: &B, + conditions: Vec, + now: SystemTime, + ) -> Result { + self.get_chain(audience, issuer, builder, conditions, now) + .map(|chain| chain.is_some()) + } + + fn get_many( + &self, + cids: &[Cid], + ) -> Result>, Self::Error> { + cids.iter().try_fold(vec![], |mut acc, cid| { + let d: &Delegation = self.get(cid)?; + acc.push(d); + Ok(acc) + }) + } +} diff --git a/src/invocation.rs b/src/invocation.rs index 4465474a..6aa7846b 100644 --- a/src/invocation.rs +++ b/src/invocation.rs @@ -1,37 +1,60 @@ +//! An [`Invocation`] is a request to use an [`Ability`][crate::ability]. +//! +//! ## Data +//! +//! - [`Invocation`] is the top-level, signed data struture. +//! - [`Payload`] is the fields unique to an invocation. +//! - [`Preset`] is an [`Invocation`] preloaded with this library's [preset abilities](crate::ability::preset::Ready). +//! - [`promise`]s are a mechanism to chain invocations together. +//! +//! ## Stateful Helpers +//! +//! - [`Agent`] is a high-level interface for sessions that will involve more than one invoctaion. +//! - [`store`] is an interface for caching [`Invocation`]s. + +mod agent; mod payload; -mod resolvable; -pub mod agent; pub mod promise; pub mod store; +pub use agent::Agent; pub use payload::{Payload, Promised}; -pub use resolvable::Resolvable; use crate::{ ability, did, did::Did, signature, - time::{TimeBoundError, Timestamp}, + time::{Expired, Timestamp}, }; use libipld_core::{cid::Cid, ipld::Ipld}; use web_time::SystemTime; /// The complete, signed [`invocation::Payload`][Payload]. /// -/// # Promises +/// Invocations are the actual "doing" in the UCAN lifecycle. +/// Unlike [`Delegation`][crate::Delegation]s, which live for some period of time and +/// can be used multiple times, [`Invocation`]s are unique and single-use. /// -/// For a version that can include [`Promise`][promise::Promise]s, -/// wrap your `A` in [`invocation::Promised`](Promised) to get -/// `Invocation>`. +/// # Expiration +/// +/// `Invocations` include an optional expiration field which behaves like a timeout: +/// "if this isn't run by a the expiration time, I'm going to assume that it didn't happen." +/// This is a best practice in message-passing distributed systems because the network is +/// [unreliable](https://en.wikipedia.org/wiki/Fallacies_of_distributed_computing). #[derive(Debug, Clone, PartialEq)] pub struct Invocation(pub signature::Envelope, DID>); -// FIXME use presnet ability, too +/// A variant of [`Invocation`] that has the abilties and DIDs from this library pre-filled. pub type Preset = Invocation; + pub type PresetPromised = Invocation; impl Invocation { + pub fn new(payload: Payload, signature: signature::Witness) -> Self { + Invocation(signature::Envelope { payload, signature }) + } + pub fn payload(&self) -> &Payload { &self.0.payload } @@ -78,7 +101,7 @@ impl Invocation { &self.0.payload.expiration } - pub fn check_time(&self, now: SystemTime) -> Result<(), TimeBoundError> + pub fn check_time(&self, now: SystemTime) -> Result<(), Expired> where A: Clone, { diff --git a/src/invocation/agent.rs b/src/invocation/agent.rs index 2d19d317..0250296f 100644 --- a/src/invocation/agent.rs +++ b/src/invocation/agent.rs @@ -1,4 +1,4 @@ -use super::{payload::Payload, store::Store, Invocation, Resolvable}; +use super::{payload::Payload, promise::Resolvable, store::Store, Invocation}; use crate::{ ability::{arguments, ucan}, delegation, @@ -16,7 +16,7 @@ use libipld_core::{ ipld::Ipld, multihash::{Code, MultihashGeneric}, }; -use std::{collections::BTreeMap, marker::PhantomData}; +use std::{collections::BTreeMap, fmt, marker::PhantomData}; use web_time::SystemTime; #[derive(Debug)] @@ -51,7 +51,6 @@ impl< > Agent<'a, T, C, DID, S, P, D> where T::Promised: Clone, - // Payload<::Hierarchy, DID>: Clone, // FIXME delegation::Payload<::Hierarchy, C, DID>: Clone, { pub fn new( @@ -117,6 +116,7 @@ where // FIXME return type ) -> Result>, ()> where + C: fmt::Debug + Clone, ::Hierarchy: Clone + Into>, T::Builder: Clone + Checkable + Prove + Into>, { @@ -237,8 +237,3 @@ pub enum Recipient { You(T), Other(T), } - -// impl Agent { -// FIXME err = () -// FIXME move to revocation agent wit own traits? -// } diff --git a/src/invocation/payload.rs b/src/invocation/payload.rs index 5d2aba6c..e2c1824f 100644 --- a/src/invocation/payload.rs +++ b/src/invocation/payload.rs @@ -1,15 +1,15 @@ -use super::resolvable::Resolvable; +use super::promise::Resolvable; use crate::{ ability::{ arguments, command::{ParseAbility, ToCommand}, }, capsule::Capsule, - delegation::{self, condition::Condition, error::DelegationError, Delegable}, + delegation::{self, condition::Condition, Delegable, ValidationError}, did::{Did, Verifiable}, nonce::Nonce, proof::{checkable::Checkable, prove::Prove}, - time::{TimeBoundError, Timestamp}, + time::{Expired, Timestamp}, }; use libipld_core::{cid::Cid, ipld::Ipld}; use serde::{ @@ -20,35 +20,81 @@ use serde::{ use std::{collections::BTreeMap, fmt::Debug}; use web_time::SystemTime; -impl Verifiable for Payload { - fn verifier(&self) -> &DID { - &self.issuer - } -} - #[derive(Debug, Clone, PartialEq)] pub struct Payload { - pub issuer: DID, + /// The subject of the [`Invocation`]. + /// + /// This is typically also the `audience`, hence the [`audence`] + /// field is optional. + /// + /// This role *must* have issued the earlier (root) + /// delegation in the chain. This makes the chains + /// self-certifying. + /// + /// The semantics of the delegation are established + /// by the subject. + /// + /// [`Invocation`]: super::Invocation pub subject: DID, + + /// The issuer of the [`Invocation`]. + /// + /// This [`Did`] *must* match the signature on + /// the outer layer of [`Invocation`]. + /// + /// [`Invocation`]: super::Invocation + pub issuer: DID, + + /// The agent being delegated to. + /// + /// Note that if this is the same as the [`subject`], + /// this field may be omitted. pub audience: Option, + /// The [Ability] being invoked. + /// + /// The specific shape and semantics of this ability + /// are established by the [`subject`] and the `A` type. + /// + /// [Ability]: crate::ability pub ability: A, + /// [`Cid`] links to the proofs that authorize this [`Invocation`]. + /// + /// These must be given in order starting from one where the [`issuer`] + /// of this invocation matches the [`audience`] of that [`Delegation`] proof. + /// + /// [`Invocation`]: super::Invocation + /// [`Delegation`]: crate::delegation::Delegation pub proofs: Vec, + + /// An optional [`Cid`] of the [`Receipt`] that requested this be invoked. + /// + /// This is helpful for provenance of calls. + /// + /// [`Receipt`]: crate::receipt::Receipt pub cause: Option, + + /// Extensible, free-form fields. pub metadata: BTreeMap, + + /// A [cryptographic nonce] to ensure that the UCAN's [`Cid`] is unique. + /// + /// [cryptographic nonce]: https://en.wikipedia.org/wiki/Cryptographic_nonce pub nonce: Nonce, + /// An optional [Unix timestamp] (wall-clock time) at which this [`Invocation`] + /// was created. pub issued_at: Option, - pub expiration: Option, // FIXME this field may not make sense + + /// An optional [Unix timestamp] (wall-clock time) at which this [`Invocation`] + /// should no longer be executed. + /// + /// One way of thinking about this is as a `timeout`. It also guards against + /// certain types of denial-of-service attacks. + pub expiration: Option, } -// FIXME cleanup traits -// one idea, because they keep comingup together: put hierarchy and builder on the same -// trair (as associated tyeps) to klet us skip the ::bulder::hierarchy indirection. -// -// This probably means putting the delegation T back to the upper level and bieng explicit about -// the T::Builder in the type impl Payload { pub fn map_ability(self, f: F) -> Payload where @@ -68,24 +114,23 @@ impl Payload { } } - // FIXME err type - pub fn check_time(&self, now: SystemTime) -> Result<(), TimeBoundError> { + pub fn check_time(&self, now: SystemTime) -> Result<(), Expired> { let ts_now = &Timestamp::postel(now); if let Some(ref exp) = self.expiration { if exp < ts_now { - panic!("FIXME") + return Err(Expired); } } Ok(()) } - pub fn check( + pub fn check( self, proofs: Vec<&delegation::Payload<::Hierarchy, C, DID>>, now: &SystemTime, - ) -> Result<(), DelegationError<<::Hierarchy as Prove>::Error>> + ) -> Result<(), ValidationError<<::Hierarchy as Prove>::Error, C>> where A: Delegable, A::Builder: Clone + Into>, @@ -345,6 +390,12 @@ impl<'de, A: ParseAbility + Deserialize<'de>, DID: Did + Deserialize<'de>> Deser } } +impl Verifiable for Payload { + fn verifier(&self) -> &DID { + &self.issuer + } +} + /// A variant that accepts [`Promise`]s. /// /// [`Promise`]: crate::invocation::promise::Promise diff --git a/src/invocation/promise.rs b/src/invocation/promise.rs index aaa9ad83..592aac94 100644 --- a/src/invocation/promise.rs +++ b/src/invocation/promise.rs @@ -1,18 +1,22 @@ -//! [UCAN Promise](https://github.com/ucan-wg/promise) +//! [UCAN Promise](https://github.com/ucan-wg/promise)s: selectors, wrappers, and traits. // FIXME put entire module behind feature flag mod any; mod err; mod ok; +mod resolvable; mod resolves; +pub mod store; // FIXME pub mod js; pub use any::PromiseAny; pub use err::PromiseErr; pub use ok::PromiseOk; +pub use resolvable::Resolvable; pub use resolves::Resolves; +pub use store::Store; use serde::{Deserialize, Serialize}; diff --git a/src/invocation/promise/any.rs b/src/invocation/promise/any.rs index e37d1e87..a8ecbd78 100644 --- a/src/invocation/promise/any.rs +++ b/src/invocation/promise/any.rs @@ -7,6 +7,14 @@ use serde::{ }; use std::fmt; +/// A promise that unwraps the same value from either the `{"ok": T}` or `{"err": T}` branches. +/// +/// Unlike [`Resolves`][super::Resolves]: +/// +/// 1. The branches may be of different types +/// 2. The underlying value is _left wrapped_ in `{"ok": T}` or `{"err": T}` capsules +/// +/// FIXME example #[derive(Debug, Clone, PartialEq, Eq, Serialize)] #[serde(untagged)] pub enum PromiseAny { diff --git a/src/invocation/promise/err.rs b/src/invocation/promise/err.rs index 623808ec..e30ce53e 100644 --- a/src/invocation/promise/err.rs +++ b/src/invocation/promise/err.rs @@ -3,6 +3,12 @@ use libipld_core::{cid::Cid, error::SerdeError, ipld::Ipld, serde as ipld_serde} use serde::{de::DeserializeOwned, Deserialize, Serialize}; use std::fmt::Debug; +/// A promise that only selects the `{"err": error}` branch of a result. +/// +/// On resolution, the value is unwrapped from the `{"err": error}`, +/// leaving just the `error` (much like [`Result::unwrap_err`]). +/// +/// FIXME exmaple #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] #[serde(untagged)] pub enum PromiseErr { diff --git a/src/invocation/promise/ok.rs b/src/invocation/promise/ok.rs index 2662344e..56f50e0a 100644 --- a/src/invocation/promise/ok.rs +++ b/src/invocation/promise/ok.rs @@ -3,6 +3,12 @@ use libipld_core::{cid::Cid, error::SerdeError, ipld::Ipld, serde as ipld_serde} use serde::{de::DeserializeOwned, Deserialize, Serialize}; use std::fmt::Debug; +/// A promise that only selects the `{"ok": value}` branch of a result. +/// +/// On resolution, the value is unwrapped from the `{"ok": value}`, +/// leaving just the `value` (much like [`Result::unwrap`]). +/// +/// FIXME exmaple #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] #[serde(untagged, deny_unknown_fields)] pub enum PromiseOk { diff --git a/src/invocation/promise/resolvable.rs b/src/invocation/promise/resolvable.rs new file mode 100644 index 00000000..d039585e --- /dev/null +++ b/src/invocation/promise/resolvable.rs @@ -0,0 +1,22 @@ +use crate::{ability::arguments, delegation::Delegable}; +use libipld_core::ipld::Ipld; + +// FIXME rename "Unresolved" +// FIXME better name + +/// A trait for [`Delegable`]s that can be deferred (by promises). +/// +/// FIXME exmaples +pub trait Resolvable: Delegable { + /// The promise type that resolves to `Self`. + /// + /// Note that this may be a more complex type than the promise selector + /// variants. One example is [letting any leaf][PromiseIpld] of an [`Ipld`] graph + /// be a promise. + /// + /// [PromiseIpld]: crate::ipld::Promised + type Promised: Into + Into>; + + /// Attempt to resolve the [`Self::Promised`]. + fn try_resolve(promised: Self::Promised) -> Result; +} diff --git a/src/invocation/promise/resolves.rs b/src/invocation/promise/resolves.rs index 026308ee..d998485e 100644 --- a/src/invocation/promise/resolves.rs +++ b/src/invocation/promise/resolves.rs @@ -3,6 +3,14 @@ use libipld_core::ipld::Ipld; use serde::{Deserialize, Serialize}; use std::fmt; +/// A promise that unwraps the same value from either the `{"ok": T}` or `{"err": T}` branches. +/// +/// Unlike [`PromiseAny`][super::PromiseAny]: +/// +/// 1. Both branches of this promise resolve to the same type +/// 2. The underlying value is unwrapped from the `{"ok": T}` or `{"err": T}` capsules +/// +/// FIXME example #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(untagged)] pub enum Resolves { diff --git a/src/invocation/promise/store.rs b/src/invocation/promise/store.rs new file mode 100644 index 00000000..2cd345c2 --- /dev/null +++ b/src/invocation/promise/store.rs @@ -0,0 +1,7 @@ +//! Storage of resolved and unresolved promises. + +mod memory; +mod traits; + +pub use memory::MemoryStore; +pub use traits::Store; diff --git a/src/invocation/promise/store/memory.rs b/src/invocation/promise/store/memory.rs new file mode 100644 index 00000000..ae960dfc --- /dev/null +++ b/src/invocation/promise/store/memory.rs @@ -0,0 +1,46 @@ +use super::Store; +use crate::{did::Did, invocation::promise::Resolvable}; +use libipld_core::cid::Cid; +use std::{ + collections::{BTreeMap, BTreeSet}, + convert::Infallible, +}; + +#[derive(Debug, Clone, PartialEq)] +pub struct MemoryStore { + pub index: BTreeMap>, +} + +impl Store for MemoryStore { + type PromiseIndexError = Infallible; + + fn put( + &mut self, + waiting_on: Vec, + invocation: Cid, + ) -> Result<(), Self::PromiseIndexError> { + self.index + .insert(invocation, BTreeSet::from_iter(waiting_on)); + + Ok(()) + } + + fn get(&self, waiting_on: &mut Vec) -> Result, Self::PromiseIndexError> { + Ok(match waiting_on.pop() { + None => BTreeSet::new(), + Some(first) => waiting_on + .iter() + .try_fold(BTreeSet::from_iter([first]), |acc, cid| { + let next = self.index.get(cid).ok_or(())?; + + let reduced: BTreeSet = acc.intersection(&next).cloned().collect(); + if reduced.is_empty() { + return Err(()); + } + + Ok(reduced) + }) + .unwrap_or_default(), + }) + } +} diff --git a/src/invocation/promise/store/traits.rs b/src/invocation/promise/store/traits.rs new file mode 100644 index 00000000..200ac656 --- /dev/null +++ b/src/invocation/promise/store/traits.rs @@ -0,0 +1,14 @@ +use crate::{did::Did, invocation::promise::Resolvable}; +use libipld_core::cid::Cid; +use std::collections::BTreeSet; + +pub trait Store { + type PromiseIndexError; + + // NOTE put_waiting + fn put(&mut self, waiting_on: Vec, invocation: Cid) + -> Result<(), Self::PromiseIndexError>; + + // NOTE get waiting + fn get(&self, waiting_on: &mut Vec) -> Result, Self::PromiseIndexError>; +} diff --git a/src/invocation/resolvable.rs b/src/invocation/resolvable.rs deleted file mode 100644 index 0f4d12dc..00000000 --- a/src/invocation/resolvable.rs +++ /dev/null @@ -1,19 +0,0 @@ -use crate::{ability::arguments, delegation::Delegable}; -use libipld_core::ipld::Ipld; - -pub trait Resolvable: Delegable { - // FIXME rename "Unresolved" - type Promised: Into + Into>; - - // FIXME indeed needed to get teh right err type - fn try_resolve(promised: Self::Promised) -> Result; - - // FIXME better name - // NOTE this takes anything taht doesn't resolve and returns None on those fields - // FIXME no, jsut use Into and NOTE THIS IN THE DOCS - // fn resolve_to_builder(&self) -> Self::Builder; -} - -// impl Delegable for Ipld { -// type Builder = Option; -// } diff --git a/src/invocation/store.rs b/src/invocation/store.rs index e168836e..a270db1d 100644 --- a/src/invocation/store.rs +++ b/src/invocation/store.rs @@ -1,13 +1,14 @@ +//! Storage for [`Invocation`]s. + use super::Invocation; -use crate::{did::Did, invocation::Resolvable}; +use crate::did::Did; use libipld_core::cid::Cid; -use std::collections::{BTreeMap, BTreeSet}; -use thiserror::Error; +use std::{collections::BTreeMap, convert::Infallible}; pub trait Store { type Error; - fn get(&self, cid: Cid) -> Result<&Invocation, Self::Error>; + fn get(&self, cid: Cid) -> Result>, Self::Error>; fn put(&mut self, cid: Cid, invocation: Invocation) -> Result<(), Self::Error>; @@ -21,15 +22,11 @@ pub struct MemoryStore { store: BTreeMap>, } -#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Error)] -#[error("Delegation not found")] -pub struct NotFound; - impl Store for MemoryStore { - type Error = NotFound; + type Error = Infallible; - fn get(&self, cid: Cid) -> Result<&Invocation, Self::Error> { - self.store.get(&cid).ok_or(NotFound) + fn get(&self, cid: Cid) -> Result>, Self::Error> { + Ok(self.store.get(&cid)) } fn put(&mut self, cid: Cid, invocation: Invocation) -> Result<(), Self::Error> { @@ -37,62 +34,3 @@ impl Store for MemoryStore { Ok(()) } } - -//////// - -pub trait PromiseIndex { - type PromiseIndexError; - - fn put_waiting( - &mut self, - waiting_on: Vec, - invocation: Cid, - ) -> Result<(), Self::PromiseIndexError>; - - fn get_waiting( - &self, - waiting_on: &mut Vec, - ) -> Result, Self::PromiseIndexError>; -} - -#[derive(Debug, Clone, PartialEq)] -pub struct MemoryPromiseIndex { - pub index: BTreeMap>, -} - -impl PromiseIndex for MemoryPromiseIndex { - type PromiseIndexError = NotFound; - - fn put_waiting( - &mut self, - waiting_on: Vec, - invocation: Cid, - ) -> Result<(), Self::PromiseIndexError> { - self.index - .insert(invocation, BTreeSet::from_iter(waiting_on)); - - Ok(()) - } - - fn get_waiting( - &self, - waiting_on: &mut Vec, - ) -> Result, Self::PromiseIndexError> { - Ok(match waiting_on.pop() { - None => BTreeSet::new(), - Some(first) => waiting_on - .iter() - .try_fold(BTreeSet::from_iter([first]), |acc, cid| { - let next = self.index.get(cid).ok_or(())?; - - let reduced: BTreeSet = acc.intersection(&next).cloned().collect(); - if reduced.is_empty() { - return Err(()); - } - - Ok(reduced) - }) - .unwrap_or_default(), - }) - } -} diff --git a/src/time.rs b/src/time.rs index b4e167bf..3c37cb1d 100644 --- a/src/time.rs +++ b/src/time.rs @@ -5,5 +5,5 @@ mod error; mod timestamp; -pub use error::{OutOfRangeError, TimeBoundError}; +pub use error::*; pub use timestamp::Timestamp; diff --git a/src/time/error.rs b/src/time/error.rs index 6b640b87..e9c3149e 100644 --- a/src/time/error.rs +++ b/src/time/error.rs @@ -14,11 +14,16 @@ pub struct OutOfRangeError { /// An error expressing when a time is not within the bounds of a UCAN. #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Error)] pub enum TimeBoundError { - /// The UCAN delegation has expired + /// The UCAN has expired. #[error("Expired")] Expired, - /// Not yet valid + /// The UCAN is not yet valid, but will be in the future. #[error("Not yet valid")] NotYetValid, } + +/// The UCAN has expired. +#[derive(Debug, Copy, Clone, PartialEq, Eq, Error)] +#[error("Expired")] +pub struct Expired; From 6b98b491aea7b8bed06ee7cd108a7d4ec9d9b2a3 Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Sat, 17 Feb 2024 16:42:24 -0800 Subject: [PATCH 093/188] Move signature under crypto --- src/crypto.rs | 4 ++++ src/{ => crypto}/nonce.rs | 5 ++--- src/{ => crypto}/signature.rs | 0 src/{ => crypto}/signature/envelope.rs | 0 src/{ => crypto}/signature/witness.rs | 0 src/delegation.rs | 3 +-- src/delegation/agent.rs | 2 +- src/delegation/payload.rs | 2 +- src/invocation.rs | 6 +++--- src/invocation/agent.rs | 3 +-- src/invocation/payload.rs | 2 +- src/lib.rs | 2 -- src/receipt.rs | 2 +- src/receipt/payload.rs | 2 +- src/receipt/responds.rs | 2 +- src/task.rs | 2 +- 16 files changed, 18 insertions(+), 19 deletions(-) rename src/{ => crypto}/nonce.rs (98%) rename src/{ => crypto}/signature.rs (100%) rename src/{ => crypto}/signature/envelope.rs (100%) rename src/{ => crypto}/signature/witness.rs (100%) diff --git a/src/crypto.rs b/src/crypto.rs index 086753be..cb236014 100644 --- a/src/crypto.rs +++ b/src/crypto.rs @@ -1,8 +1,12 @@ //! Cryptographic signature utilities mod domain_separator; +mod nonce; + +pub mod signature; pub use domain_separator::DomainSeparator; +pub use nonce::*; #[cfg(feature = "bls")] pub mod bls12381; diff --git a/src/nonce.rs b/src/crypto/nonce.rs similarity index 98% rename from src/nonce.rs rename to src/crypto/nonce.rs index a6a81a85..63ef9b08 100644 --- a/src/nonce.rs +++ b/src/crypto/nonce.rs @@ -70,7 +70,6 @@ impl From> for Nonce { } impl Nonce { - // NOTE salt = domain-separator /// Generate a 96-bit, 12-byte nonce. /// This is the minimum nonce size typically recommended. /// @@ -81,7 +80,7 @@ impl Nonce { /// # Example /// /// ```rust - /// # use ucan::nonce::Nonce; + /// # use ucan::crypto::Nonce; /// # use ucan::did::Did; /// # /// let mut salt = "did:example:123".as_bytes().to_vec(); @@ -118,7 +117,7 @@ impl Nonce { /// # Example /// /// ```rust - /// # use ucan::nonce::Nonce; + /// # use ucan::crypto::Nonce; /// # use ucan::did::Did; /// # /// let mut salt = "did:example:123".as_bytes().to_vec(); diff --git a/src/signature.rs b/src/crypto/signature.rs similarity index 100% rename from src/signature.rs rename to src/crypto/signature.rs diff --git a/src/signature/envelope.rs b/src/crypto/signature/envelope.rs similarity index 100% rename from src/signature/envelope.rs rename to src/crypto/signature/envelope.rs diff --git a/src/signature/witness.rs b/src/crypto/signature/witness.rs similarity index 100% rename from src/signature/witness.rs rename to src/crypto/signature/witness.rs diff --git a/src/delegation.rs b/src/delegation.rs index d0b951ad..690dbf49 100644 --- a/src/delegation.rs +++ b/src/delegation.rs @@ -25,10 +25,9 @@ pub use payload::{Payload, ValidationError}; use crate::{ ability, + crypto::{signature, Nonce}, did::{self, Did}, - nonce::Nonce, proof::{parents::CheckParents, same::CheckSame}, - signature, time::{TimeBoundError, Timestamp}, }; use condition::Condition; diff --git a/src/delegation/agent.rs b/src/delegation/agent.rs index a9a152ce..258cc1b5 100644 --- a/src/delegation/agent.rs +++ b/src/delegation/agent.rs @@ -1,5 +1,5 @@ use super::{condition::Condition, payload::Payload, store::Store, Delegation}; -use crate::{did::Did, nonce::Nonce, proof::checkable::Checkable, time::Timestamp}; +use crate::{crypto::Nonce, did::Did, proof::checkable::Checkable, time::Timestamp}; use libipld_core::{cid::Cid, ipld::Ipld}; use std::{collections::BTreeMap, marker::PhantomData}; use thiserror::Error; diff --git a/src/delegation/payload.rs b/src/delegation/payload.rs index 4143da78..3d9b6c70 100644 --- a/src/delegation/payload.rs +++ b/src/delegation/payload.rs @@ -5,8 +5,8 @@ use crate::{ command::{Command, ParseAbility, ToCommand}, }, capsule::Capsule, + crypto::Nonce, did::{Did, Verifiable}, - nonce::Nonce, proof::{ checkable::Checkable, parents::CheckParents, diff --git a/src/invocation.rs b/src/invocation.rs index 6aa7846b..846e2555 100644 --- a/src/invocation.rs +++ b/src/invocation.rs @@ -22,9 +22,9 @@ pub use agent::Agent; pub use payload::{Payload, Promised}; use crate::{ - ability, did, - did::Did, - signature, + ability, + crypto::signature, + did::{self, Did}, time::{Expired, Timestamp}, }; use libipld_core::{cid::Cid, ipld::Ipld}; diff --git a/src/invocation/agent.rs b/src/invocation/agent.rs index 0250296f..8b8aba07 100644 --- a/src/invocation/agent.rs +++ b/src/invocation/agent.rs @@ -1,12 +1,11 @@ use super::{payload::Payload, promise::Resolvable, store::Store, Invocation}; use crate::{ ability::{arguments, ucan}, + crypto::{signature::Witness, Nonce}, delegation, delegation::{condition::Condition, Delegable}, did::{Did, Verifiable}, - nonce::Nonce, proof::{checkable::Checkable, prove::Prove}, - signature::Witness, time::Timestamp, }; use libipld_cbor::DagCborCodec; diff --git a/src/invocation/payload.rs b/src/invocation/payload.rs index e2c1824f..aae8991a 100644 --- a/src/invocation/payload.rs +++ b/src/invocation/payload.rs @@ -5,9 +5,9 @@ use crate::{ command::{ParseAbility, ToCommand}, }, capsule::Capsule, + crypto::Nonce, delegation::{self, condition::Condition, Delegable, ValidationError}, did::{Did, Verifiable}, - nonce::Nonce, proof::{checkable::Checkable, prove::Prove}, time::{Expired, Timestamp}, }; diff --git a/src/lib.rs b/src/lib.rs index 45d1c929..719f2fdb 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -22,11 +22,9 @@ pub mod delegation; pub mod did; pub mod invocation; pub mod ipld; -pub mod nonce; pub mod proof; pub mod reader; pub mod receipt; -pub mod signature; pub mod task; pub mod time; pub mod url; diff --git a/src/receipt.rs b/src/receipt.rs index 8b1d3bf5..e7b70ebb 100644 --- a/src/receipt.rs +++ b/src/receipt.rs @@ -14,7 +14,7 @@ pub use payload::Payload; pub use responds::Responds; pub use store::Store; -use crate::{ability, did, signature}; +use crate::{ability, crypto::signature, did}; /// The complete, signed receipt of an [`Invocation`][`crate::invocation::Invocation`]. #[derive(Clone, Debug, PartialEq)] diff --git a/src/receipt/payload.rs b/src/receipt/payload.rs index e8445318..725e2b8d 100644 --- a/src/receipt/payload.rs +++ b/src/receipt/payload.rs @@ -6,8 +6,8 @@ use super::responds::Responds; use crate::{ ability::arguments, capsule::Capsule, + crypto::Nonce, did::{Did, Verifiable}, - nonce::Nonce, time::Timestamp, }; use libipld_core::{cid::Cid, error::SerdeError, ipld::Ipld, serde as ipld_serde}; diff --git a/src/receipt/responds.rs b/src/receipt/responds.rs index 6f7ca552..24db9f43 100644 --- a/src/receipt/responds.rs +++ b/src/receipt/responds.rs @@ -1,4 +1,4 @@ -use crate::{nonce::Nonce, task, task::Task}; +use crate::{crypto::Nonce, task, task::Task}; use std::fmt; /// Describe the relationship between an ability and the [`Receipt`]s. diff --git a/src/task.rs b/src/task.rs index 50d51596..e6b4f1d2 100644 --- a/src/task.rs +++ b/src/task.rs @@ -4,7 +4,7 @@ mod id; pub use id::Id; -use crate::{ability::arguments, did, nonce::Nonce}; +use crate::{ability::arguments, crypto::Nonce, did}; use libipld_cbor::DagCborCodec; use libipld_core::{ cid::{Cid, CidGeneric}, From c41e0b87a50fb9b0ad6c027327b7c609c6a928ec Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Sat, 17 Feb 2024 18:15:14 -0800 Subject: [PATCH 094/188] Porting IPLD proptest helpers from inlien-ipld --- Cargo.toml | 8 ++- src/ability/arguments/named.rs | 15 ++++++ src/crypto/nonce.rs | 19 +++++++ src/ipld/cid.rs | 30 ++++++++++- src/ipld/newtype.rs | 40 ++++++++++++++ src/lib.rs | 3 ++ src/task.rs | 29 +++++++++-- src/test_utils.rs | 95 ++++++++++++++++++++++++++++++++++ src/time/timestamp.rs | 13 +++++ src/url.rs | 20 ++++++- 10 files changed, 265 insertions(+), 7 deletions(-) create mode 100644 src/test_utils.rs diff --git a/Cargo.toml b/Cargo.toml index 936493f5..3848c016 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -59,6 +59,7 @@ url = { version = "2.5", features = ["serde"] } web-time = "0.2.3" # Interplanetary Stack +libipld = { version = "0.16", optional = true } libipld-core = { version = "0.16", features = ["serde-codec"] } libipld-cbor = "0.16" multihash = { version = "0.18" } @@ -106,10 +107,13 @@ default = [ "eddsa", "bls", - "ability-preset" + "ability-preset", + + # FIXME temp while developing + "test_utils", ] -test_utils = ["proptest"] +test_utils = ["dep:proptest", "dep:libipld"] eddsa = ["dep:ed25519-dalek"] es256 = ["dep:p256"] diff --git a/src/ability/arguments/named.rs b/src/ability/arguments/named.rs index 2e467503..7c726800 100644 --- a/src/ability/arguments/named.rs +++ b/src/ability/arguments/named.rs @@ -10,6 +10,9 @@ use wasm_bindgen::prelude::*; #[cfg(target_arch = "wasm32")] use js_sys::{Array, Map, Object, Reflect}; +#[cfg(feature = "test_utils")] +use proptest::prelude::*; + /// Named arguments /// /// Being such a common pattern, but with so few trait implementations, @@ -328,3 +331,15 @@ pub enum NamedError { #[error("arguments::Named field {0}: value doesn't match")] FieldValueMismatch(String), } + +#[cfg(feature = "test_utils")] +impl Arbitrary for Named { + type Parameters = T::Parameters; + type Strategy = BoxedStrategy; + + fn arbitrary_with(t_args: Self::Parameters) -> Self::Strategy { + prop::collection::btree_map(".*", T::arbitrary_with(t_args), 0..256) + .prop_map(Named) + .boxed() + } +} diff --git a/src/crypto/nonce.rs b/src/crypto/nonce.rs index 63ef9b08..fc83d952 100644 --- a/src/crypto/nonce.rs +++ b/src/crypto/nonce.rs @@ -16,6 +16,9 @@ use std::fmt; #[cfg(target_arch = "wasm32")] use wasm_bindgen::prelude::*; +#[cfg(feature = "test_utils")] +use proptest::prelude::*; + /// Known [`Nonce`] types #[derive(Clone, Debug, PartialEq, EnumAsInner, Serialize, Deserialize)] pub enum Nonce { @@ -194,6 +197,22 @@ impl TryFrom for Nonce { } } +#[cfg(feature = "test_utils")] +impl Arbitrary for Nonce { + type Parameters = (); + type Strategy = BoxedStrategy; + + fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy { + prop_oneof![ + any::<[u8; 12]>().prop_map(Nonce::Nonce12), + any::<[u8; 16]>().prop_map(Nonce::Nonce16), + any::>().prop_map(Nonce::Custom) + ] + .boxed() + } +} + +// FIXME move module? #[cfg(target_arch = "wasm32")] #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] #[wasm_bindgen] diff --git a/src/ipld/cid.rs b/src/ipld/cid.rs index a5cab51d..5715b5c8 100644 --- a/src/ipld/cid.rs +++ b/src/ipld/cid.rs @@ -1,7 +1,7 @@ //! Utilities for [`Cid`]s use crate::ipld; -use libipld_core::{cid::Cid, ipld::Ipld}; +use libipld_core::{cid::Cid, ipld::Ipld, multihash::MultihashGeneric}; use serde::{Deserialize, Serialize}; use thiserror::Error; @@ -11,6 +11,15 @@ use wasm_bindgen::prelude::*; #[cfg(target_arch = "wasm32")] use wasm_bindgen_derive::TryFromJsValue; +#[cfg(feature = "test_utils")] +use proptest::prelude::*; + +#[cfg(feature = "test_utils")] +use crate::test_utils::SomeCodec; + +#[cfg(feature = "test_utils")] +use crate::test_utils::SomeMultihash; + /// A newtype wrapper around a [`Cid`] /// /// This is largely to attach traits to [`Cid`]s, such as [`wasm_bindgen`] conversions. @@ -111,3 +120,22 @@ impl TryFrom<&Ipld> for Newtype { #[derive(Debug, PartialEq, Clone, Error, Serialize, Deserialize)] #[error("Not a CID: {0:?}")] pub struct NotACid(pub ipld::Newtype); + +#[cfg(feature = "test_utils")] +impl Arbitrary for Newtype { + type Parameters = (); + type Strategy = BoxedStrategy; + + fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy { + // Very much faking it + any::<([u8; 32], SomeMultihash, SomeCodec)>() + .prop_map(|(hash_bytes, hasher, codec)| { + let multihash = MultihashGeneric::wrap(hasher.0.into(), &hash_bytes.as_slice()) + .expect("Sha2_256 should always successfully encode a hash"); + + let cid = Cid::new_v1(codec.0.into(), multihash); + Newtype { cid } + }) + .boxed() + } +} diff --git a/src/ipld/newtype.rs b/src/ipld/newtype.rs index 72c3110c..fea821f4 100644 --- a/src/ipld/newtype.rs +++ b/src/ipld/newtype.rs @@ -8,6 +8,12 @@ use wasm_bindgen::prelude::*; #[cfg(target_arch = "wasm32")] use js_sys::{Array, Map, Object, Uint8Array}; +#[cfg(feature = "test_utils")] +use proptest::prelude::*; + +#[cfg(feature = "test_utils")] +use super::cid; + // FIXME push into the submodules /// A newtype wrapper around [`Ipld`] that has additional trait implementations. /// @@ -217,3 +223,37 @@ impl TryFrom for Newtype { Err(()) } } + +#[cfg(feature = "test_utils")] +impl Arbitrary for Newtype { + type Parameters = (); + type Strategy = BoxedStrategy; + + fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy { + let leaf = prop_oneof![ + Just(Ipld::Null), + any::().prop_map(Ipld::Bool), + any::>().prop_map(Ipld::Bytes), + any::().prop_map(move |i| { + Ipld::Integer((i % (2 ^ 53)).into()) // NOTE Because DAG-JSON + }), + any::().prop_map(Ipld::Float), + ".*".prop_map(Ipld::String), + any::().prop_map(|newtype_cid| { Ipld::Link(newtype_cid.cid) }) + ]; + + let coll = leaf.clone().prop_recursive(16, 1024, 128, |inner| { + prop_oneof![ + prop::collection::vec(inner.clone(), 0..128).prop_map(Ipld::List), + prop::collection::btree_map(".*", inner, 0..128).prop_map(Ipld::Map), + ] + }); + + prop_oneof![ + 1 => leaf, + 9 => coll + ] + .prop_map(Newtype) + .boxed() + } +} diff --git a/src/lib.rs b/src/lib.rs index 719f2fdb..bb7edaf4 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -29,6 +29,9 @@ pub mod task; pub mod time; pub mod url; +#[cfg(feature = "test_utils")] +pub mod test_utils; + pub use delegation::Delegation; pub use invocation::Invocation; pub use receipt::Receipt; diff --git a/src/task.rs b/src/task.rs index e6b4f1d2..3c31e711 100644 --- a/src/task.rs +++ b/src/task.rs @@ -11,13 +11,14 @@ use libipld_core::{ codec::Encode, error::SerdeError, ipld::Ipld, - multihash::MultihashGeneric, + multihash::{Code, MultihashGeneric}, serde as ipld_serde, }; use serde_derive::{Deserialize, Serialize}; use std::fmt::Debug; -const SHA2_256: u64 = 0x12; +#[cfg(feature = "test_utils")] +use proptest::prelude::*; /// The fields required to uniquely identify a [`Task`], potentially across multiple executors. /// @@ -65,7 +66,7 @@ impl From for Cid { CidGeneric::new_v1( DagCborCodec.into(), - MultihashGeneric::wrap(SHA2_256, buffer.as_slice()) + MultihashGeneric::wrap(Code::Sha2_256.into(), buffer.as_slice()) .expect("DagCborCodec + Sha2_256 should always successfully encode Ipld to a Cid"), ) } @@ -78,3 +79,25 @@ impl From for Id { } } } + +// #[cfg(feature = "test_utils")] +// impl Arbitrary for Task { +// type Parameters = (); +// type Strategy = BoxedStrategy; +// +// fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy { +// ( +// any::(), +// any::(), +// any::(), +// any::>(), +// ) +// .prop_map(|(sub, nonce, cmd, args)| Task { +// sub, +// nonce, +// cmd, +// args, +// }) +// .boxed() +// } +// } diff --git a/src/test_utils.rs b/src/test_utils.rs new file mode 100644 index 00000000..e3817b91 --- /dev/null +++ b/src/test_utils.rs @@ -0,0 +1,95 @@ +use libipld::{ + cid::multihash::{Code, MultihashDigest, MultihashGeneric}, + codec_impl::IpldCodec, +}; +use proptest::prelude::*; + +#[derive(Clone, Debug, PartialEq)] +pub struct SomeCodec(pub IpldCodec); + +impl Arbitrary for SomeCodec { + type Parameters = (); + type Strategy = BoxedStrategy; + + fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy { + prop_oneof![ + Just(IpldCodec::Raw), + Just(IpldCodec::DagCbor), + Just(IpldCodec::DagJson), + Just(IpldCodec::DagPb), + ] + .prop_map(SomeCodec) + .boxed() + } +} + +#[derive(Eq, Copy, Clone, Debug, PartialEq)] +pub struct SomeMultihash(pub Code); + +impl Default for SomeMultihash { + fn default() -> Self { + SomeMultihash(Code::Sha2_256) + } +} + +impl SomeMultihash { + pub fn new(multihash: Code) -> Self { + SomeMultihash(multihash) + } +} + +impl From for SomeMultihash { + fn from(multihash: Code) -> Self { + SomeMultihash(multihash) + } +} + +impl From for Code { + fn from(wrapper: SomeMultihash) -> Self { + wrapper.0 + } +} + +impl From for u64 { + fn from(wrapper: SomeMultihash) -> Self { + wrapper.0.into() + } +} + +impl TryFrom for SomeMultihash { + type Error = >::Error; + + fn try_from(code: u64) -> Result { + let inner = code.try_into()?; + Ok(SomeMultihash(inner)) + } +} + +impl MultihashDigest<64> for SomeMultihash { + fn digest(&self, input: &[u8]) -> MultihashGeneric<64> { + self.0.digest(input) + } + + fn wrap(&self, digest: &[u8]) -> Result, Self::Error> { + self.0.wrap(digest) + } +} + +impl Arbitrary for SomeMultihash { + type Parameters = (); + type Strategy = BoxedStrategy; + + fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy { + // Only the 256-bit variants for now + prop_oneof![ + Just(Code::Sha2_256), + Just(Code::Sha3_256), + Just(Code::Keccak256), + Just(Code::Blake2s256), + Just(Code::Blake2b256), + Just(Code::Blake3_256), + ] + .prop_map(SomeMultihash) + .boxed() + } +} diff --git a/src/time/timestamp.rs b/src/time/timestamp.rs index 2d608a98..d8659c29 100644 --- a/src/time/timestamp.rs +++ b/src/time/timestamp.rs @@ -5,6 +5,9 @@ use libipld_core::ipld::Ipld; use serde::{Deserialize, Deserializer, Serialize, Serializer}; use web_time::{Duration, SystemTime, UNIX_EPOCH}; +#[cfg(feature = "test_utils")] +use proptest::prelude::*; + /// A [`Timestamp`][super::Timestamp] with safe JavaScript interop. /// /// Per the UCAN spec, timestamps MUST respect [IEEE-754] @@ -126,3 +129,13 @@ impl<'de> Deserialize<'de> for Timestamp { Ok(Timestamp::postel(UNIX_EPOCH + Duration::from_secs(seconds))) } } + +#[cfg(feature = "test_utils")] +impl Arbitrary for Timestamp { + type Parameters = (); + type Strategy = BoxedStrategy; + + fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy { + any::().prop_map(Timestamp::postel).boxed() + } +} diff --git a/src/url.rs b/src/url.rs index a55255ed..22c6915b 100644 --- a/src/url.rs +++ b/src/url.rs @@ -5,7 +5,10 @@ use serde::{Deserialize, Serialize}; use thiserror::Error; use url::Url; -/// A wrapper around [`Url`] that has additional trait implementations +#[cfg(feature = "test_utils")] +use proptest::prelude::*; + +/// A wrapper around [`Url`] that has additional trait implementations. /// /// Usage is very simple: wrap a [`Newtype`] to gain access to additional traits and methods. /// @@ -63,3 +66,18 @@ pub enum FromIpldError { #[error(transparent)] UrlParseError(#[from] url::ParseError), } + +#[cfg(feature = "test_utils")] +impl Arbitrary for Newtype { + type Parameters = (); + type Strategy = BoxedStrategy; + + fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy { + let url_regex: &str = &"\\w+:(\\/?\\/?)[^\\s]+"; + url_regex + .prop_map(|s| { + Newtype(Url::parse(&s).expect("the regex generator to create valid URLs")) + }) + .boxed() + } +} From 58f530016167eae705095cee5aad3b84e5fcea28 Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Sat, 17 Feb 2024 23:46:07 -0800 Subject: [PATCH 095/188] More Arbitrary instances --- Cargo.toml | 5 +- src/ability/arguments/named.rs | 17 ++- src/did/key/verifier.rs | 194 ++++++++++++++++++++++++++++++++- src/did/newtype.rs | 44 ++++++++ src/did/traits.rs | 7 +- src/receipt/payload.rs | 7 +- src/task.rs | 42 +++---- src/task/id.rs | 20 ++++ 8 files changed, 301 insertions(+), 35 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 3848c016..54384153 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -44,12 +44,13 @@ ed25519-dalek = { version = "2.0.0", features = ["rand_core"], optional = true } k256 = { version = "0.13.1", features = ["ecdsa"], optional = true, default-features = false } p256 = { version = "0.13.2", features = ["alloc", "ecdsa"], optional = true, default-features = false } p384 = { version = "0.13.0", features = ["alloc", "ecdsa"], optional = true, default-features = false } -p521 = { version = "0.13.0", features = ["alloc", "ecdsa", "getrandom"], optional = true, default-features = false } -rsa = { version = "0.9.6", features = ["sha2"], optional = true, default-features = false } +p521 = { version = "0.13.3", features = ["alloc", "ecdsa", "getrandom"], optional = true, default-features = false } +rsa = { version = "0.9.6", features = ["sha2", "std"], optional = true, default-features = false } signature = { version = "2.1.0", features = ["alloc"] } # Encoding base64 = "0.21" +bs58 = "0.5" serde = { version = "1.0.188", features = ["derive"] } serde_derive = "1.0" diff --git a/src/ability/arguments/named.rs b/src/ability/arguments/named.rs index 7c726800..5ccf97c1 100644 --- a/src/ability/arguments/named.rs +++ b/src/ability/arguments/named.rs @@ -333,13 +333,20 @@ pub enum NamedError { } #[cfg(feature = "test_utils")] -impl Arbitrary for Named { - type Parameters = T::Parameters; +impl Arbitrary for Named { + type Parameters = (); type Strategy = BoxedStrategy; - fn arbitrary_with(t_args: Self::Parameters) -> Self::Strategy { - prop::collection::btree_map(".*", T::arbitrary_with(t_args), 0..256) - .prop_map(Named) + fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy { + prop::collection::btree_map(".*", ipld::Newtype::arbitrary(), 0..256) + .prop_map(|newtype_map| { + newtype_map + .into_iter() + .fold(Named::new(), |mut named, (k, v)| { + named.insert(k, v.0); + named + }) + }) .boxed() } } diff --git a/src/did/key/verifier.rs b/src/did/key/verifier.rs index 9e17cad8..2d5d3fcb 100644 --- a/src/did/key/verifier.rs +++ b/src/did/key/verifier.rs @@ -1,6 +1,10 @@ use super::Signature; use enum_as_inner::EnumAsInner; -// FIXME use serde::{Deserialize, Serialize}; +use rsa::pkcs1::{DecodeRsaPublicKey, EncodeRsaPublicKey}; +use std::{fmt::Display, str::FromStr}; + +#[cfg(feature = "test_utils")] +use proptest::prelude::*; #[cfg(feature = "eddsa")] use ed25519_dalek; @@ -102,3 +106,191 @@ impl signature::Verifier for Verifier { } } } + +impl Display for Verifier { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Verifier::EdDSA(ed25519_pk) => write!( + f, + "did:key:z6Mk{}", + bs58::encode(ed25519_pk.to_bytes()).into_string() + ), + Verifier::Es256k(secp256k1_pk) => write!( + f, + "did:key:zQ3s{}", + bs58::encode(secp256k1_pk.to_sec1_bytes()).into_string() + ), + Verifier::P256(p256_key) => { + write!( + f, + "did:key:zDn{}", + bs58::encode(p256_key.to_sec1_bytes()).into_string() + ) + } + Verifier::P384(p384_key) => write!( + f, + "did:key:z82{}", + bs58::encode(p384_key.to_sec1_bytes()).into_string() + ), + Verifier::P521(p521_key) => write!( + f, + "did:key:z2J9{}", + bs58::encode(p521_key.0.to_encoded_point(true).as_bytes()).into_string() + ), + Verifier::Rs256(rsa2048_key) => { + write!( + f, + "did:key:z4MX{}", + bs58::encode( + rsa2048_key + .0 + .to_pkcs1_der() + .expect("RSA key to encode") // FIXME? + .as_bytes() + ) + .into_string() + ) + } + Verifier::Rs512(rsa4096_key) => write!( + f, + "did:key:zgg{}", + bs58::encode( + rsa4096_key + .0 + .to_pkcs1_der() + .expect("RSA key to encode") // FIXME? + .as_bytes() + ) + .into_string() + ), + Verifier::BlsMinPk(bls_minpk_pk) => write!( + f, + "did:key:zUC7{}", + bs58::encode(bls_minpk_pk.serialize()).into_string() + ), + Verifier::BlsMinSig(bls_minsig_pk) => write!( + f, + "did:key:zUC7{}", + bs58::encode(bls_minsig_pk.serialize()).into_string() + ), + } + } +} + +impl FromStr for Verifier { + type Err = String; // FIXME + + // FIXME needs tests + fn from_str(s: &str) -> Result { + if s.len() < 32 { + // Smallest key size + return Err("invalid did:key".to_string()); + } + + if let ("did:key:z", more) = s.split_at(9) { + let bytes = more.as_bytes(); + match bytes.split_at(2) { + ([0xed, _], _) => { + let vk = ed25519_dalek::VerifyingKey::try_from(&bytes[1..33]) + .map_err(|e| e.to_string())?; + + return Ok(Verifier::EdDSA(vk)); + } + ([0xe7, _], _) => { + let vk = k256::ecdsa::VerifyingKey::from_sec1_bytes(&bytes[1..]) + .map_err(|e| e.to_string())?; + + return Ok(Verifier::Es256k(vk)); + } + ([0x12, 0x00], key_bytes) => { + let vk = p256::ecdsa::VerifyingKey::from_sec1_bytes(key_bytes) + .map_err(|e| e.to_string())?; + + return Ok(Verifier::P256(vk)); + } + ([0x12, 0x01], key_bytes) => { + let vk = p384::ecdsa::VerifyingKey::from_sec1_bytes(key_bytes) + .map_err(|e| e.to_string())?; + + return Ok(Verifier::P384(vk)); + } + ([0x12, 0x05], key_bytes) => match key_bytes.len() { + 2048 => { + let vk = rsa::pkcs1v15::VerifyingKey::from_pkcs1_der(key_bytes) + .map_err(|e| e.to_string())?; + + return Ok(Verifier::Rs256(rs256::VerifyingKey(vk))); + } + 4096 => { + let vk = rsa::pkcs1v15::VerifyingKey::from_pkcs1_der(key_bytes) + .map_err(|e| e.to_string())?; + + return Ok(Verifier::Rs512(rs512::VerifyingKey(vk))); + } + _ => todo!(), + }, + ([0xeb, 0x01], pk_bytes) => match pk_bytes.len() { + 48 => { + let pk = blst::min_pk::PublicKey::deserialize(pk_bytes) + .map_err(|_| "Failed BLS MinPk deserialization")?; + + return Ok(Verifier::BlsMinPk(pk)); + } + 96 => { + let pk = blst::min_sig::PublicKey::deserialize(pk_bytes) + .map_err(|_| "Failed BLS MinSig deserialization")?; + + return Ok(Verifier::BlsMinSig(pk)); + } + _ => return Err("invalid did:key".to_string()), + }, + _ => { + return Err("invalid did:key".to_string()); + } + } + } else { + return Err("invalid did:key".to_string()); + } + } +} + +#[cfg(feature = "test_utils")] +impl Arbitrary for Verifier { + type Parameters = (); + type Strategy = BoxedStrategy; + + fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy { + // NOTE these are just the test vectors from `did:key` v0.7 + prop_oneof![ + // did:key + Just("did:key:z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK"), + + // secp256k1 + Just("did:key:zQ3shokFTS3brHcDQrn82RUDfCZESWL1ZdCEJwekUDPQiYBme"), + Just("did:key:zQ3shtxV1FrJfhqE1dvxYRcCknWNjHc3c5X1y3ZSoPDi2aur2"), + Just("did:key:zQ3shZc2QzApp2oymGvQbzP8eKheVshBHbU4ZYjeXqwSKEn6N"), + + // BLS + Just("did:key:zUC7K4ndUaGZgV7Cp2yJy6JtMoUHY6u7tkcSYUvPrEidqBmLCTLmi6d5WvwnUqejscAkERJ3bfjEiSYtdPkRSE8kSa11hFBr4sTgnbZ95SJj19PN2jdvJjyzpSZgxkyyxNnBNnY"), + Just("did:key:zUC7KKoJk5ttwuuc8pmQDiUmtckEPTwcaFVZe4DSFV7fURuoRnD17D3xkBK3A9tZqdADkTTMKSwNkhjo9Hs6HfgNUXo48TNRaxU6XPLSPdRgMc15jCD5DfN34ixjoVemY62JxnW"), + + // P-256 + Just("did:key:zDnaerDaTF5BXEavCrfRZEk316dpbLsfPDZ3WJ5hRTPFU2169"), + Just("did:key:zDnaerx9CtbPJ1q36T5Ln5wYt3MQYeGRG5ehnPAmxcf5mDZpv"), + + // P-384 + Just("did:key:z82Lm1MpAkeJcix9K8TMiLd5NMAhnwkjjCBeWHXyu3U4oT2MVJJKXkcVBgjGhnLBn2Kaau9"), + Just("did:key:z82LkvCwHNreneWpsgPEbV3gu1C6NFJEBg4srfJ5gdxEsMGRJUz2sG9FE42shbn2xkZJh54"), + + // P-521 + Just("did:key:z2J9gaYxrKVpdoG9A4gRnmpnRCcxU6agDtFVVBVdn1JedouoZN7SzcyREXXzWgt3gGiwpoHq7K68X4m32D8HgzG8wv3sY5j7"), + Just("did:key:z2J9gcGdb2nEyMDmzQYv2QZQcM1vXktvy1Pw4MduSWxGabLZ9XESSWLQgbuPhwnXN7zP7HpTzWqrMTzaY5zWe6hpzJ2jnw4f"), + + // RSA-2048 + Just("did:key:z4MXj1wBzi9jUstyPMS4jQqB6KdJaiatPkAtVtGc6bQEQEEsKTic4G7Rou3iBf9vPmT5dbkm9qsZsuVNjq8HCuW1w24nhBFGkRE4cd2Uf2tfrB3N7h4mnyPp1BF3ZttHTYv3DLUPi1zMdkULiow3M1GfXkoC6DoxDUm1jmN6GBj22SjVsr6dxezRVQc7aj9TxE7JLbMH1wh5X3kA58H3DFW8rnYMakFGbca5CB2Jf6CnGQZmL7o5uJAdTwXfy2iiiyPxXEGerMhHwhjTA1mKYobyk2CpeEcmvynADfNZ5MBvcCS7m3XkFCMNUYBS9NQ3fze6vMSUPsNa6GVYmKx2x6JrdEjCk3qRMMmyjnjCMfR4pXbRMZa3i"), + + // RSA-4096 + Just("did:key:zgghBUVkqmWS8e1ioRVp2WN9Vw6x4NvnE9PGAyQsPqM3fnfPf8EdauiRVfBTcVDyzhqM5FFC7ekAvuV1cJHawtfgB9wDcru1hPDobk3hqyedijhgWmsYfJCmodkiiFnjNWATE7PvqTyoCjcmrc8yMRXmFPnoASyT5beUd4YZxTE9VfgmavcPy3BSouNmASMQ8xUXeiRwjb7xBaVTiDRjkmyPD7NYZdXuS93gFhyDFr5b3XLg7Rfj9nHEqtHDa7NmAX7iwDAbMUFEfiDEf9hrqZmpAYJracAjTTR8Cvn6mnDXMLwayNG8dcsXFodxok2qksYF4D8ffUxMRmyyQVQhhhmdSi4YaMPqTnC1J6HTG9Yfb98yGSVaWi4TApUhLXFow2ZvB6vqckCNhjCRL2R4MDUSk71qzxWHgezKyDeyThJgdxydrn1osqH94oSeA346eipkJvKqYREXBKwgB5VL6WF4qAK6sVZxJp2dQBfCPVZ4EbsBQaJXaVK7cNcWG8tZBFWZ79gG9Cu6C4u8yjBS8Ux6dCcJPUTLtixQu4z2n5dCsVSNdnP1EEs8ZerZo5pBgc68w4Yuf9KL3xVxPnAB1nRCBfs9cMU6oL1EdyHbqrTfnjE8HpY164akBqe92LFVsk8RusaGsVPrMekT8emTq5y8v8CabuZg5rDs3f9NPEtogjyx49wiub1FecM5B7QqEcZSYiKHgF4mfkteT2") + ].prop_map(|s: &str| Verifier::from_str(s).expect("did:key spec test vectors to work")).boxed() + } +} diff --git a/src/did/newtype.rs b/src/did/newtype.rs index 2d29ea1b..1057247b 100644 --- a/src/did/newtype.rs +++ b/src/did/newtype.rs @@ -5,6 +5,9 @@ use serde::{Deserialize, Serialize}; use std::{fmt, string::ToString}; use thiserror::Error; +#[cfg(feature = "test_utils")] +use proptest::prelude::*; + #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] /// A [Decentralized Identifier (DID)][wiki] /// @@ -110,3 +113,44 @@ impl<'de> Deserialize<'de> for FromIpldError { Ok(FromIpldError::NotAnIpldString(ipld)) } } + +#[cfg(feature = "test_utils")] +impl Arbitrary for Newtype { + type Parameters = (); + type Strategy = BoxedStrategy; + + fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy { + // NOTE these are just the test vectors from `did:key` v0.7 + prop_oneof![ + // did:key + Just("did:key:z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK"), + + // secp256k1 + Just("did:key:zQ3shokFTS3brHcDQrn82RUDfCZESWL1ZdCEJwekUDPQiYBme"), + Just("did:key:zQ3shtxV1FrJfhqE1dvxYRcCknWNjHc3c5X1y3ZSoPDi2aur2"), + Just("did:key:zQ3shZc2QzApp2oymGvQbzP8eKheVshBHbU4ZYjeXqwSKEn6N"), + + // BLS + Just("did:key:zUC7K4ndUaGZgV7Cp2yJy6JtMoUHY6u7tkcSYUvPrEidqBmLCTLmi6d5WvwnUqejscAkERJ3bfjEiSYtdPkRSE8kSa11hFBr4sTgnbZ95SJj19PN2jdvJjyzpSZgxkyyxNnBNnY"), + Just("did:key:zUC7KKoJk5ttwuuc8pmQDiUmtckEPTwcaFVZe4DSFV7fURuoRnD17D3xkBK3A9tZqdADkTTMKSwNkhjo9Hs6HfgNUXo48TNRaxU6XPLSPdRgMc15jCD5DfN34ixjoVemY62JxnW"), + + // P-256 + Just("did:key:zDnaerDaTF5BXEavCrfRZEk316dpbLsfPDZ3WJ5hRTPFU2169"), + Just("did:key:zDnaerx9CtbPJ1q36T5Ln5wYt3MQYeGRG5ehnPAmxcf5mDZpv"), + + // P-384 + Just("did:key:z82Lm1MpAkeJcix9K8TMiLd5NMAhnwkjjCBeWHXyu3U4oT2MVJJKXkcVBgjGhnLBn2Kaau9"), + Just("did:key:z82LkvCwHNreneWpsgPEbV3gu1C6NFJEBg4srfJ5gdxEsMGRJUz2sG9FE42shbn2xkZJh54"), + + // P-521 + Just("did:key:z2J9gaYxrKVpdoG9A4gRnmpnRCcxU6agDtFVVBVdn1JedouoZN7SzcyREXXzWgt3gGiwpoHq7K68X4m32D8HgzG8wv3sY5j7"), + Just("did:key:z2J9gcGdb2nEyMDmzQYv2QZQcM1vXktvy1Pw4MduSWxGabLZ9XESSWLQgbuPhwnXN7zP7HpTzWqrMTzaY5zWe6hpzJ2jnw4f"), + + // RSA-2048 + Just("did:key:z4MXj1wBzi9jUstyPMS4jQqB6KdJaiatPkAtVtGc6bQEQEEsKTic4G7Rou3iBf9vPmT5dbkm9qsZsuVNjq8HCuW1w24nhBFGkRE4cd2Uf2tfrB3N7h4mnyPp1BF3ZttHTYv3DLUPi1zMdkULiow3M1GfXkoC6DoxDUm1jmN6GBj22SjVsr6dxezRVQc7aj9TxE7JLbMH1wh5X3kA58H3DFW8rnYMakFGbca5CB2Jf6CnGQZmL7o5uJAdTwXfy2iiiyPxXEGerMhHwhjTA1mKYobyk2CpeEcmvynADfNZ5MBvcCS7m3XkFCMNUYBS9NQ3fze6vMSUPsNa6GVYmKx2x6JrdEjCk3qRMMmyjnjCMfR4pXbRMZa3i"), + + // RSA-4096 + Just("did:key:zgghBUVkqmWS8e1ioRVp2WN9Vw6x4NvnE9PGAyQsPqM3fnfPf8EdauiRVfBTcVDyzhqM5FFC7ekAvuV1cJHawtfgB9wDcru1hPDobk3hqyedijhgWmsYfJCmodkiiFnjNWATE7PvqTyoCjcmrc8yMRXmFPnoASyT5beUd4YZxTE9VfgmavcPy3BSouNmASMQ8xUXeiRwjb7xBaVTiDRjkmyPD7NYZdXuS93gFhyDFr5b3XLg7Rfj9nHEqtHDa7NmAX7iwDAbMUFEfiDEf9hrqZmpAYJracAjTTR8Cvn6mnDXMLwayNG8dcsXFodxok2qksYF4D8ffUxMRmyyQVQhhhmdSi4YaMPqTnC1J6HTG9Yfb98yGSVaWi4TApUhLXFow2ZvB6vqckCNhjCRL2R4MDUSk71qzxWHgezKyDeyThJgdxydrn1osqH94oSeA346eipkJvKqYREXBKwgB5VL6WF4qAK6sVZxJp2dQBfCPVZ4EbsBQaJXaVK7cNcWG8tZBFWZ79gG9Cu6C4u8yjBS8Ux6dCcJPUTLtixQu4z2n5dCsVSNdnP1EEs8ZerZo5pBgc68w4Yuf9KL3xVxPnAB1nRCBfs9cMU6oL1EdyHbqrTfnjE8HpY164akBqe92LFVsk8RusaGsVPrMekT8emTq5y8v8CabuZg5rDs3f9NPEtogjyx49wiub1FecM5B7QqEcZSYiKHgF4mfkteT2") + ].prop_map(|s: &str| Newtype(did_url::DID::parse(s).expect("did:key spec test vectors to work"))).boxed() + } +} diff --git a/src/did/traits.rs b/src/did/traits.rs index 12931628..bd903ad4 100644 --- a/src/did/traits.rs +++ b/src/did/traits.rs @@ -1,9 +1,8 @@ -use super::Newtype; +// use super::Newtype; +use did_url::DID; use std::fmt; -pub trait Did: - PartialEq + TryFrom + Into + signature::Verifier -{ +pub trait Did: PartialEq + TryFrom + Into + signature::Verifier { type Signature: signature::SignatureEncoding + PartialEq + fmt::Debug; type Signer: signature::Signer + fmt::Debug; } diff --git a/src/receipt/payload.rs b/src/receipt/payload.rs index 725e2b8d..142ce917 100644 --- a/src/receipt/payload.rs +++ b/src/receipt/payload.rs @@ -87,8 +87,11 @@ where where S: Serializer, { - let mut state = serializer.serialize_struct("receipt::Payload", 8)?; - state.serialize_field("iss", &self.issuer.clone().into())?; + let field_count = 7 + self.issued_at.is_some() as usize; + + let mut state = serializer.serialize_struct("receipt::Payload", field_count)?; + + state.serialize_field("iss", &self.issuer.clone().into().as_str())?; state.serialize_field("ran", &self.ran)?; state.serialize_field("out", &self.out)?; state.serialize_field("next", &self.next)?; diff --git a/src/task.rs b/src/task.rs index 3c31e711..032378e7 100644 --- a/src/task.rs +++ b/src/task.rs @@ -80,24 +80,24 @@ impl From for Id { } } -// #[cfg(feature = "test_utils")] -// impl Arbitrary for Task { -// type Parameters = (); -// type Strategy = BoxedStrategy; -// -// fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy { -// ( -// any::(), -// any::(), -// any::(), -// any::>(), -// ) -// .prop_map(|(sub, nonce, cmd, args)| Task { -// sub, -// nonce, -// cmd, -// args, -// }) -// .boxed() -// } -// } +#[cfg(feature = "test_utils")] +impl Arbitrary for Task { + type Parameters = (); + type Strategy = BoxedStrategy; + + fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy { + ( + any::(), + any::>(), + any::(), + any::>(), + ) + .prop_map(|(sub, nonce, cmd, args)| Task { + sub, + nonce, + cmd, + args, + }) + .boxed() + } +} diff --git a/src/task/id.rs b/src/task/id.rs index 6e1ec854..0414e722 100644 --- a/src/task/id.rs +++ b/src/task/id.rs @@ -2,6 +2,12 @@ use libipld_core::{cid::Cid, error::SerdeError, ipld::Ipld, serde as ipld_serde} use serde_derive::{Deserialize, Serialize}; use std::fmt::Debug; +#[cfg(feature = "test_utils")] +use proptest::prelude::*; + +#[cfg(feature = "test_utils")] +use crate::ipld::cid; + /// The unique identifier for a [`Task`][super::Task]. #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] #[serde(transparent)] @@ -25,3 +31,17 @@ impl From for Ipld { id.cid.into() } } + +#[cfg(feature = "test_utils")] +impl Arbitrary for Id { + type Parameters = (); + type Strategy = BoxedStrategy; + + fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy { + any::() + .prop_map(|cid_newtype| Id { + cid: cid_newtype.cid, + }) + .boxed() + } +} From bf3e2ff25fb3717df42694361b7e6ca85b057749 Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Sun, 18 Feb 2024 00:14:27 -0800 Subject: [PATCH 096/188] Add more arbitrary (receipt paylaod) --- src/delegation/payload.rs | 3 +-- src/invocation/payload.rs | 14 ++++------- src/receipt/payload.rs | 50 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 55 insertions(+), 12 deletions(-) diff --git a/src/delegation/payload.rs b/src/delegation/payload.rs index 3d9b6c70..7a7df4e7 100644 --- a/src/delegation/payload.rs +++ b/src/delegation/payload.rs @@ -167,8 +167,7 @@ where where S: Serializer, { - let count_nbf = if self.not_before.is_some() { 1 } else { 0 }; - + let count_nbf = self.not_before.is_some() as usize; let mut state = serializer.serialize_struct("delegation::Payload", 8 + count_nbf)?; state.serialize_field("iss", &self.issuer.clone().into().to_string())?; diff --git a/src/invocation/payload.rs b/src/invocation/payload.rs index aae8991a..7044e40d 100644 --- a/src/invocation/payload.rs +++ b/src/invocation/payload.rs @@ -208,16 +208,10 @@ where where S: Serializer, { - let mut field_count = 9; - if self.audience.is_some() { - field_count += 1 - }; - if self.issued_at.is_some() { - field_count += 1 - }; - if self.expiration.is_some() { - field_count += 1 - }; + let field_count = 9 + + self.audience.is_some() as usize + + self.issued_at.is_some() as usize + + self.expiration.is_some() as usize; let mut state = serializer.serialize_struct("invocation::Payload", field_count)?; diff --git a/src/receipt/payload.rs b/src/receipt/payload.rs index 142ce917..41ccea13 100644 --- a/src/receipt/payload.rs +++ b/src/receipt/payload.rs @@ -18,6 +18,15 @@ use serde::{ }; use std::{collections::BTreeMap, fmt, fmt::Debug}; +#[cfg(feature = "test_utils")] +use proptest::prelude::*; + +#[cfg(feature = "test_utils")] +use crate::ipld; + +#[cfg(feature = "test_utils")] +use crate::ipld::cid; + impl Verifiable for Payload { fn verifier(&self) -> &DID { &self.issuer @@ -233,3 +242,44 @@ where ipld_serde::from_ipld(ipld) } } + +#[cfg(feature = "test_utils")] +impl Arbitrary for Payload +where + T::Success: Arbitrary + 'static, +{ + type Parameters = (::Parameters, DID::Parameters); + type Strategy = BoxedStrategy; + + fn arbitrary_with((t_params, did_params): Self::Parameters) -> Self::Strategy { + ( + DID::arbitrary_with(did_params), + cid::Newtype::arbitrary(), + prop_oneof![ + T::Success::arbitrary_with(t_params).prop_map(Result::Ok), + arguments::Named::arbitrary().prop_map(Result::Err), + ], + prop::collection::vec(cid::Newtype::arbitrary(), 0..25), + prop::collection::vec(cid::Newtype::arbitrary(), 0..25), + prop::collection::hash_map(".*", ipld::Newtype::arbitrary(), 0..50), + Nonce::arbitrary(), + prop::option::of(Timestamp::arbitrary()), + ) + .prop_map( + |(issuer, ran, out, next, proofs, newtype_metadata, nonce, issued_at)| Payload { + issuer, + ran: ran.cid, + out, + next: next.into_iter().map(|nt| nt.cid).collect(), + proofs: proofs.into_iter().map(|nt| nt.cid).collect(), + metadata: newtype_metadata + .into_iter() + .map(|(k, v)| (k, v.0)) + .collect(), + nonce, + issued_at, + }, + ) + .boxed() + } +} From 501613200af3d9b39e9317719ef8438d11e90fd2 Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Sun, 18 Feb 2024 23:57:08 -0800 Subject: [PATCH 097/188] Varsig implementation --- src/ability/msg.rs | 2 +- src/crypto.rs | 1 + src/crypto/signature.rs | 2 - src/crypto/signature/envelope.rs | 108 +++++++++++++++-------- src/crypto/signature/witness.rs | 28 ------ src/crypto/varsig.rs | 4 + src/crypto/varsig/encoding.rs | 3 + src/crypto/varsig/encoding/preset.rs | 116 +++++++++++++++++++++++++ src/crypto/varsig/header.rs | 17 ++++ src/crypto/varsig/header/eddsa.rs | 43 +++++++++ src/crypto/varsig/header/es256.rs | 48 ++++++++++ src/crypto/varsig/header/es256k.rs | 48 ++++++++++ src/crypto/varsig/header/es512.rs | 48 ++++++++++ src/crypto/varsig/header/preset.rs | 76 ++++++++++++++++ src/crypto/varsig/header/rs256.rs | 49 +++++++++++ src/crypto/varsig/header/rs512.rs | 49 +++++++++++ src/crypto/varsig/header/traits.rs | 44 ++++++++++ src/delegation.rs | 73 ++++++++++++---- src/delegation/agent.rs | 44 +++++++--- src/delegation/payload.rs | 75 ++++++++++++++-- src/delegation/store/memory.rs | 44 +++++++--- src/delegation/store/traits.rs | 39 ++++++--- src/did/key/signature.rs | 53 ++++++++++- src/did/key/verifier.rs | 8 +- src/invocation.rs | 93 +++++++++++++------- src/invocation/agent.rs | 115 ++++++++++++++++-------- src/invocation/payload.rs | 66 ++++++++++++++ src/invocation/promise/any.rs | 25 ++++++ src/invocation/promise/err.rs | 21 +++++ src/invocation/promise/ok.rs | 21 +++++ src/invocation/promise/resolves.rs | 21 +++++ src/invocation/promise/store/memory.rs | 8 +- src/invocation/promise/store/traits.rs | 8 +- src/invocation/store.rs | 42 ++++++--- src/proof/parentful.rs | 4 + src/receipt.rs | 29 +++++-- src/receipt/payload.rs | 6 +- src/receipt/store/memory.rs | 20 +++-- src/receipt/store/traits.rs | 9 +- 39 files changed, 1279 insertions(+), 231 deletions(-) delete mode 100644 src/crypto/signature/witness.rs create mode 100644 src/crypto/varsig.rs create mode 100644 src/crypto/varsig/encoding.rs create mode 100644 src/crypto/varsig/encoding/preset.rs create mode 100644 src/crypto/varsig/header.rs create mode 100644 src/crypto/varsig/header/eddsa.rs create mode 100644 src/crypto/varsig/header/es256.rs create mode 100644 src/crypto/varsig/header/es256k.rs create mode 100644 src/crypto/varsig/header/es512.rs create mode 100644 src/crypto/varsig/header/preset.rs create mode 100644 src/crypto/varsig/header/rs256.rs create mode 100644 src/crypto/varsig/header/rs512.rs create mode 100644 src/crypto/varsig/header/traits.rs diff --git a/src/ability/msg.rs b/src/ability/msg.rs index 7f7e33ab..1f56e40b 100644 --- a/src/ability/msg.rs +++ b/src/ability/msg.rs @@ -32,7 +32,7 @@ pub enum Builder { #[derive(Debug, Clone, PartialEq)] pub enum Promised { Send(send::Promised), - Receive(receive::Promised), // FIXME + Receive(receive::Promised), } impl Delegable for Ready { diff --git a/src/crypto.rs b/src/crypto.rs index cb236014..4bc82072 100644 --- a/src/crypto.rs +++ b/src/crypto.rs @@ -4,6 +4,7 @@ mod domain_separator; mod nonce; pub mod signature; +pub mod varsig; pub use domain_separator::DomainSeparator; pub use nonce::*; diff --git a/src/crypto/signature.rs b/src/crypto/signature.rs index ca66a2a4..41e90739 100644 --- a/src/crypto/signature.rs +++ b/src/crypto/signature.rs @@ -1,7 +1,5 @@ //! Signatures and cryptographic envelopes. mod envelope; -mod witness; pub use envelope::*; -pub use witness::Witness; diff --git a/src/crypto/signature/envelope.rs b/src/crypto/signature/envelope.rs index 75d41ec5..2b1237f5 100644 --- a/src/crypto/signature/envelope.rs +++ b/src/crypto/signature/envelope.rs @@ -1,38 +1,65 @@ -use super::Witness; use crate::{ capsule::Capsule, + crypto::varsig, did::{Did, Verifiable}, }; use libipld_core::{ codec::{Codec, Encode}, error::Result, ipld::Ipld, - multihash::Code, }; +use signature::{SignatureEncoding, Signer}; use std::collections::BTreeMap; use thiserror::Error; -// FIXME #[cfg(feature = "dag-cbor")] -use libipld_cbor::DagCborCodec; -use signature::Signer; - /// A container associating a `payload` with its signature over it. #[derive(Debug, Clone, PartialEq)] // , Serialize, Deserialize)] -pub struct Envelope + Capsule, DID: Did> { +pub struct Envelope< + T: Verifiable + Capsule, + DID: Did, + V: varsig::Header, + Enc: Codec + TryFrom + Into, +> { + /// The [Varsig][crate::crypto::varsig] header. + pub varsig_header: V, + /// The signture of the `payload`. - pub signature: Witness, + pub signature: DID::Signature, /// The payload that's being signed over. pub payload: T, + + _phantom: std::marker::PhantomData, } -impl + Capsule, DID: Did> Verifiable for Envelope { +impl< + T: Verifiable + Capsule, + DID: Did, + V: varsig::Header, + Enc: Codec + TryFrom + Into, + > Verifiable for Envelope +{ fn verifier(&self) -> &DID { &self.payload.verifier() } } -impl + Into, DID: Did> Envelope { +impl< + T: Capsule + Verifiable + Into, + DID: Did, + V: varsig::Header, + Enc: Codec + TryFrom + Into, + > Envelope +{ + pub fn new(varsig_header: V, signature: DID::Signature, payload: T) -> Self { + Envelope { + varsig_header, + signature, + payload, + _phantom: std::marker::PhantomData, + } + } + /// Attempt to sign some payload with a given signer. /// /// # Arguments @@ -47,11 +74,16 @@ impl + Into, DID: Did> Envelope { /// # Example /// /// FIXME - pub fn try_sign(signer: &DID::Signer, payload: T) -> Result, SignError> + pub fn try_sign( + signer: &DID::Signer, + varsig_header: V, + payload: T, + ) -> Result, SignError> where T: Clone, + Ipld: Encode, { - Self::try_sign_generic::(signer, DagCborCodec, payload) + Self::try_sign_generic(signer, varsig_header, payload) } /// Attempt to sign some payload with a given signer and specific codec. @@ -69,28 +101,30 @@ impl + Into, DID: Did> Envelope { /// # Example /// /// FIXME - pub fn try_sign_generic>( + pub fn try_sign_generic( signer: &DID::Signer, - codec: C, + varsig_header: V, payload: T, - ) -> Result, SignError> + ) -> Result, SignError> where T: Clone, - Ipld: Encode, + Ipld: Encode, { let ipld: Ipld = BTreeMap::from_iter([(T::TAG.into(), payload.clone().into())]).into(); let mut buffer = vec![]; - ipld.encode(codec, &mut buffer) + ipld.encode(*varsig_header.codec(), &mut buffer) .map_err(SignError::PayloadEncodingError)?; - let sig = signer + let signature = signer .try_sign(&buffer) .map_err(SignError::SignatureError)?; Ok(Envelope { - signature: Witness::Signature(sig), + varsig_header, + signature, payload, + _phantom: std::marker::PhantomData, }) } @@ -107,31 +141,37 @@ impl + Into, DID: Did> Envelope { /// # Exmaples /// /// FIXME - pub fn validate_signature(&self) -> Result<(), ValidateError> + pub fn validate_signature(&self, varsig_header: &V) -> Result<(), ValidateError> where T: Clone, + Ipld: Encode, { - // FIXME need varsig - let codec = DagCborCodec; - let mut encoded = vec![]; let ipld: Ipld = BTreeMap::from_iter([(T::TAG.into(), self.payload.clone().into())]).into(); - ipld.encode(codec, &mut encoded) + ipld.encode(*varsig_header.codec(), &mut encoded) .map_err(ValidateError::PayloadEncodingError)?; - match &self.signature { - Witness::Signature(sig) => self - .verifier() - .verify(&encoded, &sig) - .map_err(ValidateError::VerifyError), - } + self.verifier() + .verify(&encoded, &self.signature) + .map_err(ValidateError::VerifyError) } } -impl + Capsule + Into, DID: Did> From> for Ipld { - fn from(Envelope { signature, payload }: Envelope) -> Self { - let ipld: Ipld = BTreeMap::from_iter([(T::TAG.into(), payload.into())]).into(); - BTreeMap::from_iter([("sig".into(), signature.into()), ("pld".into(), ipld)]).into() +impl< + T: Verifiable + Capsule + Into, + DID: Did, + V: varsig::Header, + Enc: Codec + Into + TryFrom, + > From> for Ipld +{ + fn from(envelope: Envelope) -> Self { + let ipld: Ipld = BTreeMap::from_iter([(T::TAG.into(), envelope.payload.into())]).into(); + let varsig_header: Ipld = Ipld::Bytes(envelope.varsig_header.into()); + + Ipld::Map(BTreeMap::from_iter([ + ("sig".into(), Ipld::Bytes(envelope.signature.to_vec())), + ("pld".into(), Ipld::List(vec![varsig_header, ipld])), + ])) } } diff --git a/src/crypto/signature/witness.rs b/src/crypto/signature/witness.rs deleted file mode 100644 index 705eae7b..00000000 --- a/src/crypto/signature/witness.rs +++ /dev/null @@ -1,28 +0,0 @@ -//! Signatures and related cryptographic witnesses. - -use enum_as_inner::EnumAsInner; -use libipld_core::ipld::Ipld; -use serde::{Deserialize, Serialize}; -use signature::SignatureEncoding; - -/// Asymmetric cryptographic witnesses. -#[derive(Debug, Clone, PartialEq, EnumAsInner, Serialize, Deserialize)] -#[serde(untagged)] -pub enum Witness { - /// A single cryptographic signature. - Signature(S), - // FIXME TODO - // Batch { - // signature: S, - // root: Vec, - // merkle_proof: Vec, - // }, -} - -impl From> for Ipld { - fn from(w: Witness) -> Self { - match w { - Witness::Signature(sig) => sig.to_vec().into(), - } - } -} diff --git a/src/crypto/varsig.rs b/src/crypto/varsig.rs new file mode 100644 index 00000000..9308f13e --- /dev/null +++ b/src/crypto/varsig.rs @@ -0,0 +1,4 @@ +pub mod encoding; +pub mod header; + +pub use header::Header; diff --git a/src/crypto/varsig/encoding.rs b/src/crypto/varsig/encoding.rs new file mode 100644 index 00000000..237cb0ef --- /dev/null +++ b/src/crypto/varsig/encoding.rs @@ -0,0 +1,3 @@ +mod preset; + +pub use preset::Preset; diff --git a/src/crypto/varsig/encoding/preset.rs b/src/crypto/varsig/encoding/preset.rs new file mode 100644 index 00000000..cf7cf8ea --- /dev/null +++ b/src/crypto/varsig/encoding/preset.rs @@ -0,0 +1,116 @@ +use libipld_core::codec::Codec; + +#[repr(u32)] +#[derive(Clone, Copy, Debug, PartialEq)] +pub enum Preset { + Identity = 0x5f, + DagPb = 0x70, + DagCbor = 0x71, + DagJson = 0x0129, + Jwt = 0x6a77, // FIXME break out Jwt & EIP-191? + Eip191 = 0xe191, +} + +impl TryFrom for Preset { + type Error = libipld_core::error::UnsupportedCodec; + + fn try_from(value: u64) -> Result { + match value { + 0x5f => Ok(Preset::Identity), + 0x70 => Ok(Preset::DagPb), + 0x71 => Ok(Preset::DagCbor), + 0x0129 => Ok(Preset::DagJson), + 0x6a77 => Ok(Preset::Jwt), + 0xe191 => Ok(Preset::Eip191), + // 0xe1 => Ok(Preset::MerkleBatchSig), + _ => Err(libipld_core::error::UnsupportedCodec(value)), + } + } +} + +impl From for u64 { + fn from(encoding: Preset) -> u64 { + encoding as u64 + } +} + +impl Codec for Preset {} + +// FIXME pub struct MerkleSig + +impl<'a> TryFrom<&'a [u8]> for Preset { + type Error = (); + + fn try_from(bytes: &'a [u8]) -> Result { + if let (encoding_info, &[]) = unsigned_varint::decode::u32(&bytes).map_err(|_| ())? { + return match encoding_info { + 0x5f => Ok(Preset::Identity), + 0x70 => Ok(Preset::DagPb), + 0x71 => Ok(Preset::DagCbor), + 0x0129 => Ok(Preset::DagJson), + 0x6a77 => Ok(Preset::Jwt), + 0xe191 => Ok(Preset::Eip191), + // 0xe1 => { + // let merkle_proof = Vec::new(); + // Ok(Preset::MerkleBatchSig(merkle_proof)) + // } + _ => Err(()), + }; + }; + + Err(()) + } +} + +impl AsRef<[u8]> for Preset { + fn as_ref(&self) -> &[u8] { + match self { + Preset::Identity => &[0x5f], + Preset::DagPb => &[0x70], + Preset::DagCbor => &[0x71], + Preset::DagJson => &[0x01, 0x29], + Preset::Jwt => &[0x6a, 0x77], + Preset::Eip191 => &[0xe1, 0x91], + // Preset::Eip191(inner) => { + // let mut buffer = vec![0xe191]; + // buffer.extend(inner.as_ref()); + // buffer.as_ref() + // } // Preset::MerkleBatchSig(merkle_proof) => { + // let mut buffer = vec![0xe1]; + // buffer.extend(merkle_proof.as_ref()); + // buffer.as_ref() + // } + } + } +} + +impl From for u32 { + fn from(encoding: Preset) -> u32 { + match encoding { + Preset::Identity => 0x5f, + Preset::DagPb => 0x70, + Preset::DagCbor => 0x71, + Preset::DagJson => 0x0129, + Preset::Jwt => 0x6a77, + Preset::Eip191 => 0xe191, + // Preset::MerkleBatchSig(_) => 0xe1, + } + } +} + +impl TryFrom for Preset { + type Error = libipld_core::error::UnsupportedCodec; + + fn try_from(value: u32) -> Result { + match value { + 0x5f => Ok(Preset::Identity), + 0x70 => Ok(Preset::DagPb), + 0x71 => Ok(Preset::DagCbor), + 0x0129 => Ok(Preset::DagJson), + 0x6a77 => Ok(Preset::Jwt), + 0xe191 => Ok(Preset::Eip191), + // 0xe1 => Ok(Preset::MerkleBatchSig), + _ => Err(libipld_core::error::UnsupportedCodec(value as u64)), + } + } +} diff --git a/src/crypto/varsig/header.rs b/src/crypto/varsig/header.rs new file mode 100644 index 00000000..17005ac6 --- /dev/null +++ b/src/crypto/varsig/header.rs @@ -0,0 +1,17 @@ +mod eddsa; +mod es256; +mod es256k; +mod es512; +mod preset; +mod rs256; +mod rs512; +mod traits; + +pub use eddsa::EdDsaHeader; +pub use es256::Es256Header; +pub use es256k::Es256kHeader; +pub use es512::Es512Header; +pub use preset::Preset; +pub use rs256::Rs256Header; +pub use rs512::Rs512Header; +pub use traits::Header; diff --git a/src/crypto/varsig/header/eddsa.rs b/src/crypto/varsig/header/eddsa.rs new file mode 100644 index 00000000..0ccffd0a --- /dev/null +++ b/src/crypto/varsig/header/eddsa.rs @@ -0,0 +1,43 @@ +use super::Header; +use libipld_core::codec::Codec; + +#[derive(Clone, Debug, PartialEq)] +pub struct EdDsaHeader { + pub codec: C, +} + +impl> TryFrom<&[u8]> for EdDsaHeader { + type Error = (); // FIXME + + fn try_from(bytes: &[u8]) -> Result { + if let Ok((0xed, inner)) = unsigned_varint::decode::u8(&bytes) { + if let Ok((codec_info, &[])) = unsigned_varint::decode::u32(&inner) { + let codec = C::try_from(codec_info).map_err(|_| ())?; + return Ok(EdDsaHeader { codec }); + } + } + + return Err(()); + } +} + +impl + Clone> From> for Vec { + fn from(ed: EdDsaHeader) -> Vec { + let mut tag_buf: [u8; 2] = Default::default(); + let tag: &[u8] = unsigned_varint::encode::u8(0xed, &mut tag_buf); + + let mut enc_buf: [u8; 5] = Default::default(); + let enc: &[u8] = unsigned_varint::encode::u32(ed.codec.into(), &mut enc_buf); + + [tag, enc].concat().into() + } +} + +impl + TryFrom> Header for EdDsaHeader { + type Signature = ed25519_dalek::Signature; + type Verifier = ed25519_dalek::VerifyingKey; + + fn codec(&self) -> &C { + &self.codec + } +} diff --git a/src/crypto/varsig/header/es256.rs b/src/crypto/varsig/header/es256.rs new file mode 100644 index 00000000..901d863d --- /dev/null +++ b/src/crypto/varsig/header/es256.rs @@ -0,0 +1,48 @@ +use super::Header; +use libipld_core::codec::Codec; + +#[derive(Clone, Debug, PartialEq)] +pub struct Es256Header { + pub codec: C, +} + +impl> TryFrom<&[u8]> for Es256Header { + type Error = (); // FIXME + + fn try_from(bytes: &[u8]) -> Result { + if let Ok((0x1200, inner)) = unsigned_varint::decode::u16(&bytes) { + if let Ok((0x12, more)) = unsigned_varint::decode::u8(&inner) { + if let Ok((codec_info, &[])) = unsigned_varint::decode::u32(&more) { + let codec = C::try_from(codec_info).map_err(|_| ())?; + return Ok(Es256Header { codec }); + } + } + } + + Err(()) + } +} + +impl> From> for Vec { + fn from(es: Es256Header) -> Vec { + let mut tag_buf: [u8; 3] = Default::default(); + let tag = unsigned_varint::encode::u16(0x1200, &mut tag_buf); + + let mut hash_buf: [u8; 2] = Default::default(); + let hash = unsigned_varint::encode::u8(0x12, &mut hash_buf); + + let mut enc_buf: [u8; 5] = Default::default(); + let enc = unsigned_varint::encode::u32(es.codec.into(), &mut enc_buf); + + [tag, hash, enc].concat().into() + } +} + +impl + TryFrom> Header for Es256Header { + type Signature = p256::ecdsa::Signature; + type Verifier = p256::ecdsa::VerifyingKey; + + fn codec(&self) -> &C { + &self.codec + } +} diff --git a/src/crypto/varsig/header/es256k.rs b/src/crypto/varsig/header/es256k.rs new file mode 100644 index 00000000..9b01c09f --- /dev/null +++ b/src/crypto/varsig/header/es256k.rs @@ -0,0 +1,48 @@ +use super::Header; +use libipld_core::codec::Codec; + +#[derive(Clone, Debug, PartialEq)] +pub struct Es256kHeader { + pub codec: C, +} + +impl> TryFrom<&[u8]> for Es256kHeader { + type Error = (); // FIXME + + fn try_from(bytes: &[u8]) -> Result { + if let Ok((0xe7, inner)) = unsigned_varint::decode::u8(&bytes) { + if let Ok((0x12, more)) = unsigned_varint::decode::u8(&inner).map_err(|_| ()) { + if let Ok((codec_info, &[])) = unsigned_varint::decode::u32(&more) { + let codec = C::try_from(codec_info).map_err(|_| ())?; + return Ok(Es256kHeader { codec }); + } + } + } + + Err(()) + } +} + +impl> From> for Vec { + fn from(es: Es256kHeader) -> Vec { + let mut tag_buf: [u8; 2] = Default::default(); + let tag = unsigned_varint::encode::u8(0xe7, &mut tag_buf); + + let mut hash_buf: [u8; 2] = Default::default(); + let hash = unsigned_varint::encode::u8(0x12, &mut hash_buf); + + let mut enc_buf: [u8; 5] = Default::default(); + let enc = unsigned_varint::encode::u32(es.codec.into(), &mut enc_buf); + + [tag, hash, enc].concat().into() + } +} + +impl + TryFrom> Header for Es256kHeader { + type Signature = k256::ecdsa::Signature; + type Verifier = k256::ecdsa::VerifyingKey; + + fn codec(&self) -> &C { + &self.codec + } +} diff --git a/src/crypto/varsig/header/es512.rs b/src/crypto/varsig/header/es512.rs new file mode 100644 index 00000000..00e4b1a1 --- /dev/null +++ b/src/crypto/varsig/header/es512.rs @@ -0,0 +1,48 @@ +use super::Header; +use libipld_core::codec::Codec; + +#[derive(Clone, Debug, PartialEq)] +pub struct Es512Header { + pub codec: C, +} + +impl> TryFrom<&[u8]> for Es512Header { + type Error = (); // FIXME + + fn try_from(bytes: &[u8]) -> Result { + if let Ok((0x1202, inner)) = unsigned_varint::decode::u16(&bytes) { + if let Ok((0x13, more)) = unsigned_varint::decode::u8(&inner) { + if let Ok((codec_info, &[])) = unsigned_varint::decode::u32(&more) { + let codec = C::try_from(codec_info).map_err(|_| ())?; + return Ok(Es512Header { codec }); + } + } + } + + Err(()) + } +} + +impl> From> for Vec { + fn from(es: Es512Header) -> Vec { + let mut tag_buf: [u8; 3] = Default::default(); + let tag = unsigned_varint::encode::u16(0x1202, &mut tag_buf); + + let mut hash_buf: [u8; 2] = Default::default(); + let hash = unsigned_varint::encode::u8(0x13, &mut hash_buf); + + let mut enc_buf: [u8; 5] = Default::default(); + let enc = unsigned_varint::encode::u32(es.codec.into(), &mut enc_buf); + + [tag, hash, enc].concat().into() + } +} + +impl + TryFrom> Header for Es512Header { + type Signature = p521::ecdsa::Signature; + type Verifier = p521::ecdsa::VerifyingKey; + + fn codec(&self) -> &C { + &self.codec + } +} diff --git a/src/crypto/varsig/header/preset.rs b/src/crypto/varsig/header/preset.rs new file mode 100644 index 00000000..4548682b --- /dev/null +++ b/src/crypto/varsig/header/preset.rs @@ -0,0 +1,76 @@ +use super::{eddsa, es256, es256k, es512, rs256, rs512, Header}; +use crate::{crypto::varsig::encoding, did::key}; + +#[derive(Clone, Debug, PartialEq)] +pub enum Preset { + EdDsa(eddsa::EdDsaHeader), + Es256(es256::Es256Header), + Es256k(es256k::Es256kHeader), + Es512(es512::Es512Header), + Rs256(rs256::Rs256Header), + Rs512(rs512::Rs512Header), + // FIXME BLS? + // FIXME Es384 +} + +impl From for Vec { + fn from(preset: Preset) -> Vec { + match preset { + Preset::EdDsa(ed) => ed.into(), + Preset::Rs256(rs256) => rs256.into(), + Preset::Rs512(rs512) => rs512.into(), + Preset::Es256(es256) => es256.into(), + Preset::Es256k(es256k) => es256k.into(), + Preset::Es512(es512) => es512.into(), + } + } +} + +impl<'a> TryFrom<&'a [u8]> for Preset { + type Error = (); + + fn try_from(bytes: &'a [u8]) -> Result { + if let Ok(ed) = eddsa::EdDsaHeader::try_from(bytes) { + return Ok(Preset::EdDsa(ed)); + } + + if let Ok(rs256) = rs256::Rs256Header::::try_from(bytes) { + return Ok(Preset::Rs256(rs256)); + } + + if let Ok(rs512) = rs512::Rs512Header::::try_from(bytes) { + return Ok(Preset::Rs512(rs512)); + } + + if let Ok(es256) = es256::Es256Header::::try_from(bytes) { + return Ok(Preset::Es256(es256)); + } + + if let Ok(es256k) = es256k::Es256kHeader::::try_from(bytes) { + return Ok(Preset::Es256k(es256k)); + } + + if let Ok(es512) = es512::Es512Header::::try_from(bytes) { + return Ok(Preset::Es512(es512)); + } + + Err(()) + } +} + +impl Header for Preset { + type Signature = key::Signature; + type Verifier = key::Verifier; + + fn codec(&self) -> &encoding::Preset { + match self { + Preset::EdDsa(ed) => ed.codec(), + Preset::Rs256(rs256) => rs256.codec(), + Preset::Rs512(rs512) => rs512.codec(), + Preset::Es256(es256) => es256.codec(), + Preset::Es256k(es256k) => es256k.codec(), + Preset::Es512(es512) => es512.codec(), + // BLS? + } + } +} diff --git a/src/crypto/varsig/header/rs256.rs b/src/crypto/varsig/header/rs256.rs new file mode 100644 index 00000000..92f03155 --- /dev/null +++ b/src/crypto/varsig/header/rs256.rs @@ -0,0 +1,49 @@ +use super::Header; +use crate::crypto::rs256::{Signature, VerifyingKey}; +use libipld_core::codec::Codec; + +#[derive(Clone, Debug, PartialEq)] +pub struct Rs256Header { + pub codec: C, +} + +impl> TryFrom<&[u8]> for Rs256Header { + type Error = (); // FIXME + + fn try_from(bytes: &[u8]) -> Result { + if let Ok((0x1205, inner)) = unsigned_varint::decode::u16(&bytes) { + if let Ok((0x12, more)) = unsigned_varint::decode::u8(&inner) { + if let Ok((codec_info, &[])) = unsigned_varint::decode::u32(&more) { + let codec = C::try_from(codec_info).map_err(|_| ())?; + return Ok(Rs256Header { codec }); + } + } + } + + Err(()) + } +} + +impl> From> for Vec { + fn from(rs: Rs256Header) -> Vec { + let mut tag_buf: [u8; 3] = Default::default(); + let tag = unsigned_varint::encode::u16(0x1205, &mut tag_buf); + + let mut hash_buf: [u8; 2] = Default::default(); + let hash = unsigned_varint::encode::u8(0x12, &mut hash_buf); + + let mut enc_buf: [u8; 5] = Default::default(); + let enc = unsigned_varint::encode::u32(rs.codec.into(), &mut enc_buf); + + [tag, hash, enc].concat().into() + } +} + +impl + TryFrom> Header for Rs256Header { + type Signature = Signature; + type Verifier = VerifyingKey; + + fn codec(&self) -> &C { + &self.codec + } +} diff --git a/src/crypto/varsig/header/rs512.rs b/src/crypto/varsig/header/rs512.rs new file mode 100644 index 00000000..d1cb098a --- /dev/null +++ b/src/crypto/varsig/header/rs512.rs @@ -0,0 +1,49 @@ +use super::Header; +use crate::crypto::rs512::{Signature, VerifyingKey}; +use libipld_core::codec::Codec; + +#[derive(Clone, Debug, PartialEq)] +pub struct Rs512Header { + pub codec: C, +} + +impl> TryFrom<&[u8]> for Rs512Header { + type Error = (); // FIXME + + fn try_from(bytes: &[u8]) -> Result { + if let Ok((0x1205, inner)) = unsigned_varint::decode::u16(&bytes) { + if let Ok((0x13, more)) = unsigned_varint::decode::u8(&inner) { + if let Ok((codec_info, &[])) = unsigned_varint::decode::u32(&more) { + let codec = C::try_from(codec_info).map_err(|_| ())?; + return Ok(Rs512Header { codec }); + } + } + } + + Err(()) + } +} + +impl> From> for Vec { + fn from(rs: Rs512Header) -> Vec { + let mut tag_buf: [u8; 3] = Default::default(); + let tag = unsigned_varint::encode::u16(0x1205, &mut tag_buf); + + let mut hash_buf: [u8; 2] = Default::default(); + let hash = unsigned_varint::encode::u8(0x13, &mut hash_buf); + + let mut enc_buf: [u8; 5] = Default::default(); + let enc = unsigned_varint::encode::u32(rs.codec.into(), &mut enc_buf); + + [tag, hash, enc].concat().into() + } +} + +impl + TryFrom> Header for Rs512Header { + type Signature = Signature; + type Verifier = VerifyingKey; + + fn codec(&self) -> &C { + &self.codec + } +} diff --git a/src/crypto/varsig/header/traits.rs b/src/crypto/varsig/header/traits.rs new file mode 100644 index 00000000..6a88f49d --- /dev/null +++ b/src/crypto/varsig/header/traits.rs @@ -0,0 +1,44 @@ +use libipld_core::codec::{Codec, Encode}; +use signature::Verifier; +use thiserror::Error; + +pub trait Header + Into>: + for<'a> TryFrom<&'a [u8]> + Into> +{ + type Signature: signature::SignatureEncoding; + type Verifier: signature::Verifier; + + fn codec(&self) -> &Enc; + + fn encode_payload, Buf: std::io::Write>( + &self, + payload: T, + buffer: &mut Buf, + ) -> Result<(), libipld_core::error::Error> { + payload.encode(Self::codec(self).clone(), buffer) + } + + fn try_verify<'a, T: Encode>( + &self, + verifier: &'a Self::Verifier, + signature: &'a Self::Signature, + payload: T, + ) -> Result<(), VerifyError> { + let mut buffer = vec![]; + self.encode_payload(payload, &mut buffer) + .map_err(VerifyError::CodecError)?; + + verifier + .verify(&buffer, signature) + .map_err(VerifyError::SignatureError) + } +} + +#[derive(Debug, Error)] +pub enum VerifyError { + #[error("Varsig codec error: {0}")] + CodecError(libipld_core::error::Error), + + #[error("varsig signature error: {0}")] + SignatureError(signature::Error), +} diff --git a/src/delegation.rs b/src/delegation.rs index 690dbf49..05c5033d 100644 --- a/src/delegation.rs +++ b/src/delegation.rs @@ -25,13 +25,16 @@ pub use payload::{Payload, ValidationError}; use crate::{ ability, - crypto::{signature, Nonce}, + crypto::{signature, varsig, Nonce}, did::{self, Did}, proof::{parents::CheckParents, same::CheckSame}, time::{TimeBoundError, Timestamp}, }; use condition::Condition; -use libipld_core::ipld::Ipld; +use libipld_core::{ + codec::{Codec, Encode}, + ipld::Ipld, +}; use std::collections::BTreeMap; use web_time::SystemTime; @@ -42,14 +45,28 @@ use web_time::SystemTime; /// # Examples /// FIXME #[derive(Clone, Debug, PartialEq)] -pub struct Delegation(pub signature::Envelope, DID>); +pub struct Delegation< + D, + C: Condition, + DID: Did, + V: varsig::Header, + Enc: Codec + TryFrom + Into, +>(pub signature::Envelope, DID, V, Enc>); /// A variant of [`Delegation`] that has the abilties and DIDs from this library pre-filled. -pub type Preset = Delegation; +pub type Preset = Delegation< + ability::preset::Builder, + condition::Preset, + did::preset::Verifier, + varsig::header::Preset, + varsig::encoding::Preset, +>; // FIXME checkable -> provable? -impl Delegation { +impl, Enc: Codec + Into + TryFrom> + Delegation +{ /// Retrive the `issuer` of a [`Delegation`] pub fn issuer(&self) -> &DID { &self.0.payload.issuer @@ -70,14 +87,15 @@ impl Delegation { &self.0.payload.ability_builder } - pub fn map_ability_builder(self, f: F) -> Delegation + pub fn map_ability_builder(self, f: F) -> Delegation where F: FnOnce(B) -> T, { - Delegation(signature::Envelope { - payload: self.0.payload.map_ability(f), - signature: self.0.signature, - }) + Delegation(signature::Envelope::new( + self.0.varsig_header, + self.0.signature, + self.0.payload.map_ability(f), + )) } /// Retrive the `condition` of a [`Delegation`] @@ -113,38 +131,59 @@ impl Delegation { &self.0.payload } - pub fn signature(&self) -> &signature::Witness { + pub fn varsig_header(&self) -> &V { + &self.0.varsig_header + } + + pub fn signature(&self) -> &DID::Signature { &self.0.signature } pub fn validate_signature(&self) -> Result<(), signature::ValidateError> where Payload: Clone, + Ipld: Encode, { - self.0.validate_signature() + self.0.validate_signature(self.varsig_header()) } pub fn try_sign( signer: &DID::Signer, + varsig_header: V, payload: Payload, ) -> Result where + Ipld: Encode, Payload: Clone, { - signature::Envelope::try_sign(signer, payload).map(Delegation) + signature::Envelope::try_sign(signer, varsig_header, payload).map(Delegation) } } -impl CheckSame for Delegation { +impl< + B: CheckSame, + C: Condition, + DID: Did, + V: varsig::Header, + Enc: Codec + TryFrom + Into, + > CheckSame for Delegation +{ type Error = ::Error; - fn check_same(&self, proof: &Delegation) -> Result<(), Self::Error> { + fn check_same(&self, proof: &Delegation) -> Result<(), Self::Error> { self.0.payload.check_same(&proof.payload()) } } -impl CheckParents for Delegation { - type Parents = Delegation; +impl< + T: CheckParents, + C: Condition, + DID: Did, + V: varsig::Header, + Enc: Codec + TryFrom + Into, + > CheckParents for Delegation +{ + type Parents = Delegation; type ParentError = ::ParentError; fn check_parent(&self, proof: &Self::Parents) -> Result<(), Self::ParentError> { diff --git a/src/delegation/agent.rs b/src/delegation/agent.rs index 258cc1b5..160bc1bb 100644 --- a/src/delegation/agent.rs +++ b/src/delegation/agent.rs @@ -1,6 +1,15 @@ use super::{condition::Condition, payload::Payload, store::Store, Delegation}; -use crate::{crypto::Nonce, did::Did, proof::checkable::Checkable, time::Timestamp}; -use libipld_core::{cid::Cid, ipld::Ipld}; +use crate::{ + crypto::{varsig, Nonce}, + did::Did, + proof::checkable::Checkable, + time::Timestamp, +}; +use libipld_core::{ + cid::Cid, + codec::{Codec, Encode}, + ipld::Ipld, +}; use std::{collections::BTreeMap, marker::PhantomData}; use thiserror::Error; use web_time::SystemTime; @@ -9,7 +18,15 @@ use web_time::SystemTime; /// /// This is helpful for sessions where more than one delegation will be made. #[derive(Debug)] -pub struct Agent<'a, B: Checkable, C: Condition, DID: Did, S: Store> { +pub struct Agent< + 'a, + B: Checkable, + C: Condition, + DID: Did, + S: Store, + V: varsig::Header, + Enc: Codec + TryFrom + Into, +> { /// The [`Did`][Did] of the agent. pub did: &'a DID, @@ -17,7 +34,7 @@ pub struct Agent<'a, B: Checkable, C: Condition, DID: Did, S: Store> pub store: &'a mut S, signer: &'a ::Signer, - _marker: PhantomData<(B, C)>, + _marker: PhantomData<(B, C, V, Enc)>, } // FIXME show example of multiple hierarchies of "all things accepted" @@ -28,8 +45,12 @@ impl< B: Checkable + Clone, C: Condition + Clone, DID: Did + ToString + Clone, - S: Store + Clone, - > Agent<'a, B, C, DID, S> + S: Store + Clone, + V: varsig::Header, + Enc: Codec + TryFrom + Into, + > Agent<'a, B, C, DID, S, V, Enc> +where + Ipld: Encode, { pub fn new(did: &'a DID, signer: &'a ::Signer, store: &'a mut S) -> Self { Self { @@ -50,7 +71,8 @@ impl< expiration: Timestamp, not_before: Option, now: SystemTime, - ) -> Result, DelegateError<>::Error>> { + varsig_header: V, + ) -> Result, DelegateError> { let mut salt = self.did.clone().to_string().into_bytes(); let nonce = Nonce::generate_12(&mut salt); @@ -67,7 +89,7 @@ impl< conditions: new_conditions, }; - return Ok(Delegation::try_sign(self.signer, payload).expect("FIXME")); + return Ok(Delegation::try_sign(self.signer, varsig_header, payload).expect("FIXME")); } let to_delegate = &self @@ -94,14 +116,14 @@ impl< not_before: not_before.map(Into::into), }; - Ok(Delegation::try_sign(self.signer, payload).expect("FIXME")) + Ok(Delegation::try_sign(self.signer, varsig_header, payload).expect("FIXME")) } pub fn receive( &mut self, cid: Cid, // FIXME remove and generate from the capsule header? - delegation: Delegation, - ) -> Result<(), ReceiveError<>::Error, DID>> { + delegation: Delegation, + ) -> Result<(), ReceiveError> { if self.store.get(&cid).is_ok() { return Ok(()); } diff --git a/src/delegation/payload.rs b/src/delegation/payload.rs index 7a7df4e7..3ab8d1f2 100644 --- a/src/delegation/payload.rs +++ b/src/delegation/payload.rs @@ -25,11 +25,11 @@ use std::{collections::BTreeMap, fmt, fmt::Debug}; use thiserror::Error; use web_time::SystemTime; -impl Verifiable for Payload { - fn verifier(&self) -> &DID { - &self.issuer - } -} +#[cfg(feature = "test_utils")] +use proptest::prelude::*; + +#[cfg(feature = "test_utils")] +use crate::ipld; /// The payload portion of a [`Delegation`][super::Delegation]. /// @@ -140,6 +140,12 @@ impl Capsule for Payload { const TAG: &'static str = "ucan/d/1.0.0-rc.1"; } +impl Verifiable for Payload { + fn verifier(&self) -> &DID { + &self.issuer + } +} + impl CheckSame for Payload { type Error = ::Error; @@ -481,8 +487,65 @@ pub enum ValidationError { NotYetValid, #[error("The delegation failed a condition: {0:?}")] - FailedCondition(C), // FIXME add context? + FailedCondition(C), #[error(transparent)] AbilityError(AbilityError), } + +#[cfg(feature = "test_utils")] +impl Arbitrary + for Payload +where + T::Strategy: 'static, + C::Strategy: 'static, + DID::Parameters: Clone, + C::Parameters: Clone, +{ + type Parameters = (T::Parameters, DID::Parameters, C::Parameters); + type Strategy = BoxedStrategy; + + fn arbitrary_with((t_args, did_args, c_args): Self::Parameters) -> Self::Strategy { + ( + T::arbitrary_with(t_args), + DID::arbitrary_with(did_args.clone()), + DID::arbitrary_with(did_args.clone()), + DID::arbitrary_with(did_args), + Nonce::arbitrary(), + Timestamp::arbitrary(), + Option::::arbitrary(), + prop::collection::btree_map(".*", ipld::Newtype::arbitrary(), 0..50).prop_map(|m| { + m.into_iter() + .map(|(k, v)| (k, v.0)) + .collect::>() + }), + prop::collection::vec(C::arbitrary_with(c_args), 0..10), + ) + .prop_map( + |( + ability_builder, + issuer, + audience, + subject, + nonce, + expiration, + not_before, + metadata, + conditions, + )| { + Payload { + issuer, + subject, + audience, + ability_builder, + conditions, + metadata, + nonce, + expiration, + not_before, + } + }, + ) + .boxed() + } +} diff --git a/src/delegation/store/memory.rs b/src/delegation/store/memory.rs index 27dd6e93..e87399af 100644 --- a/src/delegation/store/memory.rs +++ b/src/delegation/store/memory.rs @@ -1,11 +1,12 @@ use super::Store; use crate::{ ability::arguments, + crypto::varsig, delegation::{condition::Condition, Delegation}, did::Did, proof::{checkable::Checkable, prove::Prove}, }; -use libipld_core::{cid::Cid, ipld::Ipld}; +use libipld_core::{cid::Cid, codec::Codec, ipld::Ipld}; use nonempty::NonEmpty; use std::{ collections::{BTreeMap, BTreeSet}, @@ -70,25 +71,43 @@ use web_time::SystemTime; /// linkStyle 1 stroke:orange; /// ``` #[derive(Debug, Clone, PartialEq)] -pub struct MemoryStore { - ucans: BTreeMap>, +pub struct MemoryStore< + H, + C: Condition, + DID: Did + Ord, + V: varsig::Header, + Enc: Codec + TryFrom + Into, +> { + ucans: BTreeMap>, index: BTreeMap>>, revocations: BTreeSet, } // FIXME check that UCAN is valid -impl Store - for MemoryStore +impl< + B: Checkable + Clone, + C: Condition + PartialEq, + DID: Did + Ord + Clone, + V: varsig::Header, + Enc: Codec + TryFrom + Into, + > Store for MemoryStore where B::Hierarchy: Into> + Clone, { - type Error = (); // FIXME misisng + type DelegationStoreError = (); // FIXME misisng - fn get(&self, cid: &Cid) -> Result<&Delegation, Self::Error> { + fn get( + &self, + cid: &Cid, + ) -> Result<&Delegation, Self::DelegationStoreError> { self.ucans.get(cid).ok_or(()) } - fn insert(&mut self, cid: Cid, delegation: Delegation) -> Result<(), Self::Error> { + fn insert( + &mut self, + cid: Cid, + delegation: Delegation, + ) -> Result<(), Self::DelegationStoreError> { self.index .entry(delegation.subject().clone()) .or_default() @@ -96,14 +115,14 @@ where .or_default() .insert(cid); - let hierarchy: Delegation = + let hierarchy: Delegation = delegation.map_ability_builder(Into::into); self.ucans.insert(cid.clone(), hierarchy); Ok(()) } - fn revoke(&mut self, cid: Cid) -> Result<(), Self::Error> { + fn revoke(&mut self, cid: Cid) -> Result<(), Self::DelegationStoreError> { self.revocations.insert(cid); Ok(()) } @@ -115,7 +134,10 @@ where builder: &B, conditions: Vec, now: SystemTime, - ) -> Result)>>, Self::Error> { + ) -> Result< + Option)>>, + Self::DelegationStoreError, + > { match self.index.get(subject).and_then(|aud_map| aud_map.get(aud)) { None => Ok(None), Some(delegation_subtree) => { diff --git a/src/delegation/store/traits.rs b/src/delegation/store/traits.rs index c1129344..93c6d525 100644 --- a/src/delegation/store/traits.rs +++ b/src/delegation/store/traits.rs @@ -1,27 +1,43 @@ use crate::{ + crypto::varsig, delegation::{condition::Condition, Delegation}, did::Did, proof::checkable::Checkable, }; -use libipld_core::cid::Cid; +use libipld_core::{cid::Cid, codec::Codec}; use nonempty::NonEmpty; +use std::fmt::Debug; use web_time::SystemTime; // NOTE the T here is the builder... FIXME add one layer up and call T::Builder? May be confusing? -pub trait Store { - type Error; +pub trait Store< + B: Checkable, + C: Condition, + DID: Did, + V: varsig::Header, + Enc: Codec + TryFrom + Into, +> +{ + type DelegationStoreError: Debug; - fn get(&self, cid: &Cid) -> Result<&Delegation, Self::Error>; + fn get( + &self, + cid: &Cid, + ) -> Result<&Delegation, Self::DelegationStoreError>; // FIXME add a variant that calculated the CID from the capsulre header? // FIXME that means changing the name to insert_by_cid or similar // FIXME rename put - fn insert(&mut self, cid: Cid, delegation: Delegation) -> Result<(), Self::Error>; + fn insert( + &mut self, + cid: Cid, + delegation: Delegation, + ) -> Result<(), Self::DelegationStoreError>; // FIXME validate invocation // sore invocation // just... move to invocation - fn revoke(&mut self, cid: Cid) -> Result<(), Self::Error>; + fn revoke(&mut self, cid: Cid) -> Result<(), Self::DelegationStoreError>; fn get_chain( &self, @@ -30,7 +46,10 @@ pub trait Store { builder: &B, conditions: Vec, now: SystemTime, - ) -> Result)>>, Self::Error>; + ) -> Result< + Option)>>, + Self::DelegationStoreError, + >; fn can_delegate( &self, @@ -39,7 +58,7 @@ pub trait Store { builder: &B, conditions: Vec, now: SystemTime, - ) -> Result { + ) -> Result { self.get_chain(audience, issuer, builder, conditions, now) .map(|chain| chain.is_some()) } @@ -47,9 +66,9 @@ pub trait Store { fn get_many( &self, cids: &[Cid], - ) -> Result>, Self::Error> { + ) -> Result>, Self::DelegationStoreError> { cids.iter().try_fold(vec![], |mut acc, cid| { - let d: &Delegation = self.get(cid)?; + let d: &Delegation = self.get(cid)?; acc.push(d); Ok(acc) }) diff --git a/src/did/key/signature.rs b/src/did/key/signature.rs index 429aadd3..7cbc715e 100644 --- a/src/did/key/signature.rs +++ b/src/did/key/signature.rs @@ -30,7 +30,7 @@ use crate::crypto::bls12381; pub enum Signature { /// `EdDSA` signature. #[cfg(feature = "eddsa")] - EdDSA(ed25519_dalek::Signature), + EdDsa(ed25519_dalek::Signature), /// `ES256K` (`secp256k1`) signature. #[cfg(feature = "es256k")] @@ -63,4 +63,55 @@ pub enum Signature { /// `BLS 12-381` signature for the "min sig" variant. #[cfg(feature = "bls")] BlsMinSig(bls12381::min_sig::Signature), + + /// An unknown signature type. + /// + /// This is primarily for parsing, where reification is delayed + /// until the DID method is known. + Unknown(Vec), +} + +impl signature::SignatureEncoding for Signature { + type Repr = Vec; +} + +impl From for Vec { + fn from(sig: Signature) -> Vec { + match sig { + #[cfg(feature = "eddsa")] + Signature::EdDsa(sig) => sig.to_vec(), + + #[cfg(feature = "es256k")] + Signature::Es256k(sig) => sig.to_vec(), + + #[cfg(feature = "es256")] + Signature::P256(sig) => sig.to_vec(), + + #[cfg(feature = "es384")] + Signature::P384(sig) => sig.to_vec(), + + #[cfg(feature = "es512")] + Signature::P521(sig) => sig.to_vec(), + + #[cfg(feature = "rs256")] + Signature::Rs256(sig) => <[u8; 256]>::from(sig).into(), + + #[cfg(feature = "rs512")] + Signature::Rs512(sig) => <[u8; 512]>::from(sig).into(), + + #[cfg(feature = "bls")] + Signature::BlsMinPk(sig) => <[u8; 96]>::from(sig).into(), + + #[cfg(feature = "bls")] + Signature::BlsMinSig(sig) => <[u8; 48]>::from(sig).into(), + + Signature::Unknown(vec) => vec, + } + } +} + +impl From<&[u8]> for Signature { + fn from(arr: &[u8]) -> Signature { + Signature::Unknown(arr.to_vec()) + } } diff --git a/src/did/key/verifier.rs b/src/did/key/verifier.rs index 2d5d3fcb..743cff94 100644 --- a/src/did/key/verifier.rs +++ b/src/did/key/verifier.rs @@ -35,7 +35,7 @@ use blst; pub enum Verifier { /// `EdDSA` verifying key. #[cfg(feature = "eddsa")] - EdDSA(ed25519_dalek::VerifyingKey), + EdDsa(ed25519_dalek::VerifyingKey), /// `ES256K` (`secp256k1`) verifying key. #[cfg(feature = "es256k")] @@ -73,7 +73,7 @@ pub enum Verifier { impl signature::Verifier for Verifier { fn verify(&self, msg: &[u8], signature: &Signature) -> Result<(), signature::Error> { match (self, signature) { - (Verifier::EdDSA(vk), Signature::EdDSA(sig)) => { + (Verifier::EdDsa(vk), Signature::EdDsa(sig)) => { vk.verify(msg, sig).map_err(signature::Error::from_source) } (Verifier::Es256k(vk), Signature::Es256k(sig)) => { @@ -110,7 +110,7 @@ impl signature::Verifier for Verifier { impl Display for Verifier { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { - Verifier::EdDSA(ed25519_pk) => write!( + Verifier::EdDsa(ed25519_pk) => write!( f, "did:key:z6Mk{}", bs58::encode(ed25519_pk.to_bytes()).into_string() @@ -194,7 +194,7 @@ impl FromStr for Verifier { let vk = ed25519_dalek::VerifyingKey::try_from(&bytes[1..33]) .map_err(|e| e.to_string())?; - return Ok(Verifier::EdDSA(vk)); + return Ok(Verifier::EdDsa(vk)); } ([0xe7, _], _) => { let vk = k256::ecdsa::VerifyingKey::from_sec1_bytes(&bytes[1..]) diff --git a/src/invocation.rs b/src/invocation.rs index 846e2555..4b6a4c60 100644 --- a/src/invocation.rs +++ b/src/invocation.rs @@ -23,11 +23,15 @@ pub use payload::{Payload, Promised}; use crate::{ ability, - crypto::signature, + crypto::{signature, varsig}, did::{self, Did}, time::{Expired, Timestamp}, }; -use libipld_core::{cid::Cid, ipld::Ipld}; +use libipld_core::{ + cid::Cid, + codec::{Codec, Encode}, + ipld::Ipld, +}; use web_time::SystemTime; /// The complete, signed [`invocation::Payload`][Payload]. @@ -43,23 +47,46 @@ use web_time::SystemTime; /// This is a best practice in message-passing distributed systems because the network is /// [unreliable](https://en.wikipedia.org/wiki/Fallacies_of_distributed_computing). #[derive(Debug, Clone, PartialEq)] -pub struct Invocation(pub signature::Envelope, DID>); +pub struct Invocation< + A, + DID: did::Did, + V: varsig::Header, + Enc: Codec + TryFrom + Into, +>(pub signature::Envelope, DID, V, Enc>); /// A variant of [`Invocation`] that has the abilties and DIDs from this library pre-filled. -pub type Preset = Invocation; - -pub type PresetPromised = Invocation; - -impl Invocation { - pub fn new(payload: Payload, signature: signature::Witness) -> Self { - Invocation(signature::Envelope { payload, signature }) +pub type Preset = Invocation< + ability::preset::Ready, + did::preset::Verifier, + varsig::header::Preset, + varsig::encoding::Preset, +>; + +pub type PresetPromised = Invocation< + ability::preset::Promised, + did::preset::Verifier, + varsig::header::Preset, + varsig::encoding::Preset, +>; + +impl, Enc: Codec + TryFrom + Into> + Invocation +where + Ipld: Encode, +{ + pub fn new(payload: Payload, varsig_header: V, signature: DID::Signature) -> Self { + Invocation(signature::Envelope::new(varsig_header, signature, payload)) } pub fn payload(&self) -> &Payload { &self.0.payload } - pub fn signature(&self) -> &signature::Witness { + pub fn varsig_header(&self) -> &V { + &self.0.varsig_header + } + + pub fn signature(&self) -> &DID::Signature { &self.0.signature } @@ -79,57 +106,63 @@ impl Invocation { &self.0.payload.ability } - pub fn map_ability(self, f: F) -> Invocation + pub fn map_ability(self, f: F) -> Invocation where F: FnOnce(A) -> Z, { - Invocation(signature::Envelope { - payload: self.0.payload.map_ability(f), - signature: self.0.signature, - }) + Invocation(signature::Envelope::new( + self.0.varsig_header, + self.0.signature, + self.0.payload.map_ability(f), + )) } pub fn proofs(&self) -> &Vec { - &self.0.payload.proofs + &self.payload().proofs } pub fn issued_at(&self) -> &Option { - &self.0.payload.issued_at + &self.payload().issued_at } pub fn expiration(&self) -> &Option { - &self.0.payload.expiration + &self.payload().expiration } - pub fn check_time(&self, now: SystemTime) -> Result<(), Expired> - where - A: Clone, - { - self.0.payload.check_time(now) + pub fn check_time(&self, now: SystemTime) -> Result<(), Expired> { + self.payload().check_time(now) } pub fn try_sign( signer: &DID::Signer, + varsig_header: V, payload: Payload, - ) -> Result, signature::SignError> + ) -> Result, signature::SignError> where Payload: Clone, { - let envelope = signature::Envelope::try_sign(signer, payload)?; + let envelope = signature::Envelope::try_sign(signer, varsig_header, payload)?; Ok(Invocation(envelope)) } } -impl did::Verifiable for Invocation { +impl, Enc: Codec + TryFrom + Into> + did::Verifiable for Invocation +{ fn verifier(&self) -> &DID { &self.0.verifier() } } -impl Invocation {} +impl, Enc: Codec + TryFrom + Into> + Invocation +{ +} -impl From> for Ipld { - fn from(invocation: Invocation) -> Self { +impl, Enc: Codec + TryFrom + Into> + From> for Ipld +{ + fn from(invocation: Invocation) -> Self { invocation.0.into() } } diff --git a/src/invocation/agent.rs b/src/invocation/agent.rs index 8b8aba07..b7dd7a79 100644 --- a/src/invocation/agent.rs +++ b/src/invocation/agent.rs @@ -1,21 +1,23 @@ use super::{payload::Payload, promise::Resolvable, store::Store, Invocation}; use crate::{ ability::{arguments, ucan}, - crypto::{signature::Witness, Nonce}, + crypto::{varsig, Nonce}, delegation, delegation::{condition::Condition, Delegable}, did::{Did, Verifiable}, + invocation::promise, proof::{checkable::Checkable, prove::Prove}, time::Timestamp, }; use libipld_cbor::DagCborCodec; use libipld_core::{ cid::{Cid, CidGeneric}, - codec::Encode, + codec::{Codec, Encode}, ipld::Ipld, multihash::{Code, MultihashGeneric}, }; use std::{collections::BTreeMap, fmt, marker::PhantomData}; +use thiserror::Error; use web_time::SystemTime; #[derive(Debug)] @@ -24,9 +26,11 @@ pub struct Agent< T: Resolvable + Delegable, C: Condition, DID: Did, - S: Store, - P: Store, - D: delegation::store::Store, + S: Store, + P: promise::Store, + D: delegation::store::Store, + V: varsig::Header, + Enc: Codec + Into + TryFrom, > { pub did: &'a DID, @@ -36,7 +40,7 @@ pub struct Agent< pub resolved_promise_store: &'a mut P, signer: &'a ::Signer, - marker: PhantomData<(T, C)>, + marker: PhantomData<(T, C, V, Enc)>, } impl< @@ -44,12 +48,15 @@ impl< T: Resolvable + Delegable + Clone, C: Condition, DID: Did + Clone, - S: Store, - P: Store, - D: delegation::store::Store, - > Agent<'a, T, C, DID, S, P, D> + S: Store, + P: promise::Store, + D: delegation::store::Store, + V: varsig::Header, + Enc: Codec + Into + TryFrom, + > Agent<'a, T, C, DID, S, P, D, V, Enc> where T::Promised: Clone, + Ipld: Encode, delegation::Payload<::Hierarchy, C, DID>: Clone, { pub fn new( @@ -81,8 +88,9 @@ where expiration: Option, issued_at: Option, now: SystemTime, + varsig_header: V, // FIXME err type - ) -> Result, ()> { + ) -> Result, ()> { let proofs = self .delegation_store .get_chain(self.did, subject, &ability.clone().into(), vec![], now) @@ -105,57 +113,56 @@ where issued_at, }; - Ok(Invocation::try_sign(self.signer, payload).map_err(|_| ())?) + Ok(Invocation::try_sign(self.signer, varsig_header, payload).map_err(|_| ())?) } pub fn receive( &mut self, - promised: Invocation, + promised: Invocation, now: &SystemTime, - // FIXME return type - ) -> Result>, ()> + ) -> Result>, ReceiveError> where C: fmt::Debug + Clone, ::Hierarchy: Clone + Into>, T::Builder: Clone + Checkable + Prove + Into>, + Invocation: Clone, + <<::Builder as Checkable>::Hierarchy as Prove>::Error: fmt::Debug, +