From 6e9aae7075773c102b5b329cf41f95dc0695e787 Mon Sep 17 00:00:00 2001 From: Lut99 Date: Tue, 15 Oct 2024 14:05:25 +0200 Subject: [PATCH 01/82] Started major refactor into a library This branch will contain the work on the `policy-reasoner` to turn the repository into a library instead of a binary. This is done to remove the Brane-specific parts of it, and put that into the Brane repo. As such, end binaries will be in the codebase of the respective project; the exception is the existing binaries (kind of), which are now examples. So far, moved the most important parts. Some work still needs to be done to re-introduce the appropriate logging, and fixing the POSIX reasoner. The rest -i.e., Brane parts and the store-part- need to be moved to independent repositories. The same applies to the eFLINT base policy (or that has to live in Brane land, idk). --- Cargo.lock | 4376 +++-------------- Cargo.toml | 183 +- build.rs | 152 - examples/config/jwk_set_delib.json | 11 - examples/config/jwk_set_expert.json | 11 - examples/config/jwt_resolver.yaml | 1 - examples/eflint.rs | 203 + examples/eflint/defs.eflint | 242 - examples/eflint/example_workflow.eflint | 70 - examples/eflint/example_workflow_2.eflint | 248 - examples/eflint/higher_level.eflint | 4 - examples/eflint_json/interface.json | 523 -- examples/eflint_reasonerconn/eflint.rs | 22 - .../example-policy-content.json | 888 ---- .../eflint_reasonerconn/example-policy.json | 895 ---- .../eflint_reasonerconn/example-result.json | 1252 ----- .../eflint_reasonerconn/example-state.json | 36 - .../eflint_reasonerconn/example-workflow.json | 48 - examples/no_op.rs | 75 + examples/posix.rs | 18 + examples/requests/exec-task.json | 230 - examples/requests/workflow.json | 309 -- lib/audit-logger/Cargo.toml | 28 - lib/audit-logger/src/lib.rs | 336 -- lib/auth-resolver/Cargo.toml | 15 - lib/auth-resolver/src/lib.rs | 29 - lib/deliberation/Cargo.toml | 33 - lib/deliberation/src/lib.rs | 1 - lib/deliberation/src/spec.rs | 92 - lib/eflint-to-json/Cargo.toml | 23 +- lib/eflint-to-json/src/download.rs | 26 +- lib/eflint-to-json/src/lib.rs | 30 +- lib/eflint-to-json/src/log.rs | 71 + lib/loggers/file/Cargo.toml | 23 + lib/loggers/file/src/lib.rs | 20 + lib/loggers/file/src/logger.rs | 218 + lib/loggers/file/src/stmt.rs | 28 + lib/loggers/mock/Cargo.toml | 18 + lib/loggers/mock/src/lib.rs | 19 + lib/loggers/mock/src/logger.rs | 56 + lib/nested-cli-parser/Cargo.toml | 22 - lib/nested-cli-parser/src/lib.rs | 22 - lib/nested-cli-parser/src/map_parser.rs | 356 -- lib/nested-cli-parser/src/spec.rs | 102 - lib/policy/Cargo.toml | 27 - lib/policy/src/lib.rs | 120 - lib/reasonerconn/Cargo.toml | 40 - lib/reasonerconn/src/lib.rs | 127 - lib/reasoners/eflint-json/Cargo.toml | 24 + lib/reasoners/eflint-json/src/lib.rs | 22 + lib/reasoners/eflint-json/src/policy.rs | 13 + lib/reasoners/eflint-json/src/reasonerconn.rs | 260 + lib/reasoners/eflint-json/src/reasons.rs | 169 + lib/reasoners/eflint-json/src/spec.rs | 44 + lib/reasoners/no-op/Cargo.toml | 26 + lib/reasoners/no-op/src/lib.rs | 26 + lib/reasoners/no-op/src/reasonerconn.rs | 105 + lib/reasoners/posix/Cargo.toml | 155 + lib/reasoners/posix/src/lib.rs | 148 + lib/reasoners/posix/src/reasonerconn.rs | 453 ++ lib/reasoners/posix/src/workflow.rs | 112 + lib/resolvers/file/Cargo.toml | 22 + lib/resolvers/file/src/lib.rs | 19 + lib/resolvers/file/src/resolver.rs | 112 + lib/spec/Cargo.toml | 18 + lib/spec/src/auditlogger.rs | 108 + lib/spec/src/lib.rs | 23 + lib/spec/src/reasonerconn.rs | 73 + lib/spec/src/stateresolver.rs | 109 + lib/srv/Cargo.toml | 38 - lib/srv/src/deliberation.rs | 462 -- lib/srv/src/lib.rs | 187 - lib/srv/src/models.rs | 44 - lib/srv/src/policy.rs | 262 - lib/srv/src/problem.rs | 6 - lib/srv/src/reasoner_conn_ctx.rs | 57 - lib/state-resolver/Cargo.toml | 18 - lib/state-resolver/src/lib.rs | 62 - lib/workflow/Cargo.toml | 37 +- lib/workflow/examples/compile.rs | 337 -- lib/workflow/src/compile.rs | 493 -- lib/workflow/src/eflint.rs | 549 --- lib/workflow/src/lib.rs | 181 +- lib/workflow/src/optimize.rs | 378 +- lib/workflow/src/preprocess.rs | 1077 ---- lib/workflow/src/spec.rs | 203 - lib/workflow/src/tests.rs | 199 - lib/workflow/src/utils.rs | 86 - lib/workflow/src/visitor.rs | 519 ++ lib/workflow/src/visualize.rs | 84 +- migrations/.keep | 0 .../down.sql | 3 - .../2023-10-25-162029_create_policies/up.sql | 18 - .../down.sql | 6 - .../2024-01-03-120312_add_deactivation/up.sql | 6 - .../down.sql | 3 - .../up.sql | 3 - policy/eflint/interface/main.eflint | 36 - policy/eflint/interface/metadata.eflint | 36 - policy/eflint/interface/queries.eflint | 25 - policy/eflint/interface/state.eflint | 30 - policy/eflint/interface/state_assert.eflint | 24 - .../interface/tests/test_workflow_ext.eflint | 63 - .../eflint/interface/workflow_assert.eflint | 83 - policy/eflint/interface/workflow_base.eflint | 50 - policy/eflint/interface/workflow_ext.eflint | 64 - src/auth.rs | 147 - src/bin/eflint.rs | 125 - src/bin/implementation/eflint.rs | 588 --- src/bin/implementation/interface.rs | 42 - src/bin/implementation/mod.rs | 4 - src/bin/implementation/no_op.rs | 90 - src/bin/implementation/posix.rs | 535 -- src/bin/no_op.rs | 215 - src/bin/posix.rs | 130 - src/graphql/api_schema.json | 1241 ----- src/graphql/search_packages.graphql | 14 - src/jwt.rs | 107 - src/lib.rs | 43 +- src/logger.rs | 361 -- src/models.rs | 32 - src/schema.rs | 27 - src/sqlite.rs | 315 -- src/state.rs | 546 -- tools/checker-client/Cargo.toml | 42 - tools/checker-client/src/main.rs | 1187 ----- tools/key-manager/Cargo.toml | 24 - tools/key-manager/src/main.rs | 143 - tools/policy-builder/Cargo.toml | 23 - tools/policy-builder/src/main.rs | 127 - 130 files changed, 4755 insertions(+), 20802 deletions(-) delete mode 100644 build.rs delete mode 100644 examples/config/jwk_set_delib.json delete mode 100644 examples/config/jwk_set_expert.json delete mode 100644 examples/config/jwt_resolver.yaml create mode 100644 examples/eflint.rs delete mode 100644 examples/eflint/defs.eflint delete mode 100644 examples/eflint/example_workflow.eflint delete mode 100644 examples/eflint/example_workflow_2.eflint delete mode 100644 examples/eflint/higher_level.eflint delete mode 100644 examples/eflint_json/interface.json delete mode 100644 examples/eflint_reasonerconn/eflint.rs delete mode 100644 examples/eflint_reasonerconn/example-policy-content.json delete mode 100644 examples/eflint_reasonerconn/example-policy.json delete mode 100644 examples/eflint_reasonerconn/example-result.json delete mode 100644 examples/eflint_reasonerconn/example-state.json delete mode 100644 examples/eflint_reasonerconn/example-workflow.json create mode 100644 examples/no_op.rs create mode 100644 examples/posix.rs delete mode 100644 examples/requests/exec-task.json delete mode 100644 examples/requests/workflow.json delete mode 100644 lib/audit-logger/Cargo.toml delete mode 100644 lib/audit-logger/src/lib.rs delete mode 100644 lib/auth-resolver/Cargo.toml delete mode 100644 lib/auth-resolver/src/lib.rs delete mode 100644 lib/deliberation/Cargo.toml delete mode 100644 lib/deliberation/src/lib.rs delete mode 100644 lib/deliberation/src/spec.rs create mode 100644 lib/eflint-to-json/src/log.rs create mode 100644 lib/loggers/file/Cargo.toml create mode 100644 lib/loggers/file/src/lib.rs create mode 100644 lib/loggers/file/src/logger.rs create mode 100644 lib/loggers/file/src/stmt.rs create mode 100644 lib/loggers/mock/Cargo.toml create mode 100644 lib/loggers/mock/src/lib.rs create mode 100644 lib/loggers/mock/src/logger.rs delete mode 100644 lib/nested-cli-parser/Cargo.toml delete mode 100644 lib/nested-cli-parser/src/lib.rs delete mode 100644 lib/nested-cli-parser/src/map_parser.rs delete mode 100644 lib/nested-cli-parser/src/spec.rs delete mode 100644 lib/policy/Cargo.toml delete mode 100644 lib/policy/src/lib.rs delete mode 100644 lib/reasonerconn/Cargo.toml delete mode 100644 lib/reasonerconn/src/lib.rs create mode 100644 lib/reasoners/eflint-json/Cargo.toml create mode 100644 lib/reasoners/eflint-json/src/lib.rs create mode 100644 lib/reasoners/eflint-json/src/policy.rs create mode 100644 lib/reasoners/eflint-json/src/reasonerconn.rs create mode 100644 lib/reasoners/eflint-json/src/reasons.rs create mode 100644 lib/reasoners/eflint-json/src/spec.rs create mode 100644 lib/reasoners/no-op/Cargo.toml create mode 100644 lib/reasoners/no-op/src/lib.rs create mode 100644 lib/reasoners/no-op/src/reasonerconn.rs create mode 100644 lib/reasoners/posix/Cargo.toml create mode 100644 lib/reasoners/posix/src/lib.rs create mode 100644 lib/reasoners/posix/src/reasonerconn.rs create mode 100644 lib/reasoners/posix/src/workflow.rs create mode 100644 lib/resolvers/file/Cargo.toml create mode 100644 lib/resolvers/file/src/lib.rs create mode 100644 lib/resolvers/file/src/resolver.rs create mode 100644 lib/spec/Cargo.toml create mode 100644 lib/spec/src/auditlogger.rs create mode 100644 lib/spec/src/lib.rs create mode 100644 lib/spec/src/reasonerconn.rs create mode 100644 lib/spec/src/stateresolver.rs delete mode 100644 lib/srv/Cargo.toml delete mode 100644 lib/srv/src/deliberation.rs delete mode 100644 lib/srv/src/lib.rs delete mode 100644 lib/srv/src/models.rs delete mode 100644 lib/srv/src/policy.rs delete mode 100644 lib/srv/src/problem.rs delete mode 100644 lib/srv/src/reasoner_conn_ctx.rs delete mode 100644 lib/state-resolver/Cargo.toml delete mode 100644 lib/state-resolver/src/lib.rs delete mode 100644 lib/workflow/examples/compile.rs delete mode 100644 lib/workflow/src/compile.rs delete mode 100644 lib/workflow/src/eflint.rs delete mode 100644 lib/workflow/src/preprocess.rs delete mode 100644 lib/workflow/src/spec.rs delete mode 100644 lib/workflow/src/tests.rs delete mode 100644 lib/workflow/src/utils.rs create mode 100644 lib/workflow/src/visitor.rs delete mode 100644 migrations/.keep delete mode 100644 migrations/2023-10-25-162029_create_policies/down.sql delete mode 100644 migrations/2023-10-25-162029_create_policies/up.sql delete mode 100644 migrations/2024-01-03-120312_add_deactivation/down.sql delete mode 100644 migrations/2024-01-03-120312_add_deactivation/up.sql delete mode 100644 migrations/2024-01-03-150043_add_policy_base_def/down.sql delete mode 100644 migrations/2024-01-03-150043_add_policy_base_def/up.sql delete mode 100644 policy/eflint/interface/main.eflint delete mode 100644 policy/eflint/interface/metadata.eflint delete mode 100644 policy/eflint/interface/queries.eflint delete mode 100644 policy/eflint/interface/state.eflint delete mode 100644 policy/eflint/interface/state_assert.eflint delete mode 100644 policy/eflint/interface/tests/test_workflow_ext.eflint delete mode 100644 policy/eflint/interface/workflow_assert.eflint delete mode 100644 policy/eflint/interface/workflow_base.eflint delete mode 100644 policy/eflint/interface/workflow_ext.eflint delete mode 100644 src/auth.rs delete mode 100644 src/bin/eflint.rs delete mode 100644 src/bin/implementation/eflint.rs delete mode 100644 src/bin/implementation/interface.rs delete mode 100644 src/bin/implementation/mod.rs delete mode 100644 src/bin/implementation/no_op.rs delete mode 100644 src/bin/implementation/posix.rs delete mode 100644 src/bin/no_op.rs delete mode 100644 src/bin/posix.rs delete mode 100644 src/graphql/api_schema.json delete mode 100644 src/graphql/search_packages.graphql delete mode 100644 src/jwt.rs delete mode 100644 src/logger.rs delete mode 100644 src/models.rs delete mode 100644 src/schema.rs delete mode 100644 src/sqlite.rs delete mode 100644 src/state.rs delete mode 100644 tools/checker-client/Cargo.toml delete mode 100644 tools/checker-client/src/main.rs delete mode 100644 tools/key-manager/Cargo.toml delete mode 100644 tools/key-manager/src/main.rs delete mode 100644 tools/policy-builder/Cargo.toml delete mode 100644 tools/policy-builder/src/main.rs diff --git a/Cargo.lock b/Cargo.lock index e492bf1..b2dc282 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -92,2973 +92,1122 @@ dependencies = [ ] [[package]] -name = "anyhow" -version = "1.0.97" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dcfed56ad506cb2c684a14971b8861fdc3baaaae314b9e5f9bb532cbe3ba7a4f" - -[[package]] -name = "asn1-rs" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56624a96882bb8c26d61312ae18cb45868e5a9992ea73c58e45c3101e56a1e60" -dependencies = [ - "asn1-rs-derive", - "asn1-rs-impl", - "displaydoc", - "nom", - "num-traits", - "rusticata-macros", - "thiserror 2.0.12", - "time", -] - -[[package]] -name = "asn1-rs-derive" -version = "0.6.0" +name = "async-recursion" +version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3109e49b1e4909e9db6515a30c633684d68cdeaa252f215214cb4fa1a5bfee2c" +checksum = "3b43422f69d8ff38f95f1b2bb76517c91589a924d1559a0e935d7c8ce0274c11" dependencies = [ "proc-macro2", "quote", - "syn 2.0.100", - "synstructure", + "syn", ] [[package]] -name = "asn1-rs-impl" -version = "0.2.0" +name = "atomic-waker" +version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b18050c2cd6fe86c3a76584ef5e0baf286d038cda203eb6223df2cc413565f7" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.100", -] +checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" [[package]] -name = "async-compression" -version = "0.3.15" +name = "autocfg" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "942c7cd7ae39e91bde4820d74132e9862e62c2f386c3aa90ccf55949f5bad63a" -dependencies = [ - "flate2", - "futures-core", - "memchr", - "pin-project-lite", - "tokio", -] +checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" [[package]] -name = "async-compression" -version = "0.4.22" +name = "backtrace" +version = "0.3.74" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59a194f9d963d8099596278594b3107448656ba73831c9d8c783e613ce86da64" +checksum = "8d82cb332cdfaed17ae235a638438ac4d4839913cc2af585c3c6746e8f8bee1a" dependencies = [ - "flate2", - "futures-core", - "memchr", - "pin-project-lite", - "tokio", + "addr2line", + "cfg-if", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", + "windows-targets 0.52.6", ] [[package]] -name = "async-recursion" -version = "1.1.1" +name = "base64" +version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b43422f69d8ff38f95f1b2bb76517c91589a924d1559a0e935d7c8ce0274c11" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.100", -] +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" [[package]] -name = "async-stream" -version = "0.3.6" +name = "bitflags" +version = "2.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b5a71a6f37880a80d1d7f19efd781e4b5de42c88f0722cc13bcb6cc2cfe8476" -dependencies = [ - "async-stream-impl", - "futures-core", - "pin-project-lite", -] +checksum = "5c8214115b7bf84099f1309324e63141d4c5d7cc26862f97a0a857dbefe165bd" [[package]] -name = "async-stream-impl" -version = "0.3.6" +name = "block-buffer" +version = "0.10.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7c24de15d275a1ecfd47a380fb4d5ec9bfe0933f309ed5e705b775596a3574d" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.100", + "generic-array", ] [[package]] -name = "async-trait" -version = "0.1.88" +name = "bumpalo" +version = "3.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e539d3fca749fcee5236ab05e93a52867dd549cc157c8cb7f99595f3cedffdb5" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.100", -] +checksum = "1628fb46dfa0b37568d12e5edd512553eccf6a22a78e8bde00bb4aed84d5bdbf" [[package]] -name = "atomic-waker" -version = "1.1.2" +name = "bytes" +version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" - -[[package]] -name = "audit-logger" -version = "0.1.0" -dependencies = [ - "async-trait", - "auth-resolver 0.1.0", - "deliberation 0.1.0", - "enum-debug", - "hex", - "policy 0.1.0", - "serde", - "serde_json", - "state-resolver 0.1.0", - "warp", - "workflow 0.1.0", -] - -[[package]] -name = "audit-logger" -version = "0.1.0" -source = "git+https://github.com/braneframework/policy-reasoner#42261e9751c320914c89cf49641bf7477bd1c313" -dependencies = [ - "async-trait", - "auth-resolver 0.1.0 (git+https://github.com/braneframework/policy-reasoner)", - "deliberation 0.1.0 (git+https://github.com/braneframework/policy-reasoner)", - "enum-debug", - "hex", - "policy 0.1.0 (git+https://github.com/braneframework/policy-reasoner)", - "serde", - "serde_json", - "state-resolver 0.1.0 (git+https://github.com/braneframework/policy-reasoner)", - "warp", - "workflow 0.1.0 (git+https://github.com/braneframework/policy-reasoner)", -] - -[[package]] -name = "auth-resolver" -version = "0.1.0" -dependencies = [ - "async-trait", - "serde", - "warp", -] +checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" [[package]] -name = "auth-resolver" -version = "0.1.0" -source = "git+https://github.com/braneframework/policy-reasoner#42261e9751c320914c89cf49641bf7477bd1c313" +name = "cc" +version = "1.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fcb57c740ae1daf453ae85f16e37396f672b039e00d9d866e07ddb24e328e3a" dependencies = [ - "async-trait", - "serde", - "warp", + "shlex", ] [[package]] -name = "autocfg" -version = "1.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" - -[[package]] -name = "axum" -version = "0.6.20" +name = "cfg-if" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b829e4e32b91e643de6eafe82b1d90675f5874230191a4ffbc1b336dec4d6bf" -dependencies = [ - "async-trait", - "axum-core 0.3.4", - "bitflags 1.3.2", - "bytes", - "futures-util", - "http 0.2.12", - "http-body 0.4.6", - "hyper 0.14.32", - "itoa", - "matchit", - "memchr", - "mime", - "percent-encoding", - "pin-project-lite", - "rustversion", - "serde", - "sync_wrapper 0.1.2", - "tower 0.4.13", - "tower-layer", - "tower-service", -] +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] -name = "axum" -version = "0.7.9" +name = "chrono" +version = "0.4.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "edca88bc138befd0323b20752846e6587272d3b03b0343c8ea28a6f819e6e71f" +checksum = "1a7964611d71df112cb1730f2ee67324fcf4d0fc6606acbbe9bfe06df124637c" dependencies = [ - "async-trait", - "axum-core 0.4.5", - "bytes", - "futures-util", - "http 1.1.0", - "http-body 1.0.1", - "http-body-util", - "itoa", - "matchit", - "memchr", - "mime", - "percent-encoding", - "pin-project-lite", - "rustversion", - "serde", - "sync_wrapper 1.0.2", - "tower 0.5.2", - "tower-layer", - "tower-service", + "android-tzdata", + "iana-time-zone", + "js-sys", + "num-traits", + "wasm-bindgen", + "windows-link", ] [[package]] -name = "axum-core" -version = "0.3.4" +name = "clap" +version = "4.5.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "759fa577a247914fd3f7f76d62972792636412fbfd634cd452f6a385a74d2d2c" +checksum = "e958897981290da2a852763fe9cdb89cd36977a5d729023127095fa94d95e2ff" dependencies = [ - "async-trait", - "bytes", - "futures-util", - "http 0.2.12", - "http-body 0.4.6", - "mime", - "rustversion", - "tower-layer", - "tower-service", + "clap_builder", + "clap_derive", ] [[package]] -name = "axum-core" -version = "0.4.5" +name = "clap_builder" +version = "4.5.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09f2bd6146b97ae3359fa0cc6d6b376d9539582c7b4220f041a33ec24c226199" +checksum = "83b0f35019843db2160b5bb19ae09b4e6411ac33fc6a712003c33e03090e2489" dependencies = [ - "async-trait", - "bytes", - "futures-util", - "http 1.1.0", - "http-body 1.0.1", - "http-body-util", - "mime", - "pin-project-lite", - "rustversion", - "sync_wrapper 1.0.2", - "tower-layer", - "tower-service", + "anstream", + "anstyle", + "clap_lex", + "strsim", ] [[package]] -name = "backtrace" -version = "0.3.74" +name = "clap_derive" +version = "4.5.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d82cb332cdfaed17ae235a638438ac4d4839913cc2af585c3c6746e8f8bee1a" +checksum = "09176aae279615badda0765c0c0b3f6ed53f4709118af73cf4655d85d1530cd7" dependencies = [ - "addr2line", - "cfg-if", - "libc", - "miniz_oxide", - "object", - "rustc-demangle", - "windows-targets 0.52.6", + "heck", + "proc-macro2", + "quote", + "syn", ] [[package]] -name = "base16ct" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf" - -[[package]] -name = "base64" -version = "0.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" - -[[package]] -name = "base64" -version = "0.21.7" +name = "clap_lex" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" +checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6" [[package]] -name = "base64" -version = "0.22.1" +name = "colorchoice" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" +checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990" [[package]] -name = "base64ct" -version = "1.7.3" +name = "console" +version = "0.15.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89e25b6adfb930f02d1981565a6e5d9c547ac15a96606256d3b59040e5cd4ca3" +checksum = "054ccb5b10f9f2cbf51eb355ca1d05c2d279ce1804688d0db74b4733a5aeafd8" +dependencies = [ + "encode_unicode", + "libc", + "once_cell", + "unicode-width", + "windows-sys 0.59.0", +] [[package]] -name = "bitflags" -version = "1.3.2" +name = "core-foundation" +version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" +checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" +dependencies = [ + "core-foundation-sys", + "libc", +] [[package]] -name = "bitflags" -version = "2.9.0" +name = "core-foundation-sys" +version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c8214115b7bf84099f1309324e63141d4c5d7cc26862f97a0a857dbefe165bd" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" [[package]] -name = "block-buffer" -version = "0.10.4" +name = "cpufeatures" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" dependencies = [ - "generic-array", + "libc", ] [[package]] -name = "bollard" -version = "0.18.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97ccca1260af6a459d75994ad5acc1651bcabcbdbc41467cc9786519ab854c30" -dependencies = [ - "base64 0.22.1", - "bollard-stubs", - "bytes", - "futures-core", - "futures-util", - "hex", - "http 1.1.0", - "http-body-util", - "hyper 1.6.0", - "hyper-named-pipe", - "hyper-util", - "hyperlocal", - "log", - "pin-project-lite", - "serde", - "serde_derive", - "serde_json", - "serde_repr", - "serde_urlencoded", - "thiserror 2.0.12", - "tokio", - "tokio-util", - "tower-service", - "url", - "winapi", -] - -[[package]] -name = "bollard-stubs" -version = "1.47.1-rc.27.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f179cfbddb6e77a5472703d4b30436bff32929c0aa8a9008ecf23d1d3cdd0da" -dependencies = [ - "serde", - "serde_repr", - "serde_with", -] - -[[package]] -name = "brane-ast" -version = "3.0.0" -source = "git+https://github.com/braneframework/brane#9acf0eda6fd6c13efe0c198801d586e6478efffb" -dependencies = [ - "brane-dsl 3.0.0 (git+https://github.com/braneframework/brane)", - "brane-shr 3.0.0 (git+https://github.com/braneframework/brane)", - "console", - "enum-debug", - "lazy_static", - "log", - "num-traits", - "rand 0.9.0", - "serde", - "serde_json_any_key", - "specifications 3.0.0 (git+https://github.com/braneframework/brane)", - "strum 0.27.1", - "thiserror 2.0.12", - "uuid", -] - -[[package]] -name = "brane-ast" -version = "3.0.0" -source = "git+https://github.com/epi-project/brane#7524309001d9ff347c8063747f2535a474a73565" -dependencies = [ - "brane-dsl 3.0.0 (git+https://github.com/epi-project/brane)", - "brane-shr 3.0.0 (git+https://github.com/epi-project/brane)", - "console", - "enum-debug", - "lazy_static", - "log", - "num-traits", - "rand 0.8.5", - "serde", - "serde_json_any_key", - "specifications 3.0.0 (git+https://github.com/epi-project/brane)", - "strum 0.25.0", - "uuid", -] - -[[package]] -name = "brane-cfg" -version = "3.0.0" -source = "git+https://github.com/braneframework/brane#9acf0eda6fd6c13efe0c198801d586e6478efffb" -dependencies = [ - "async-trait", - "enum-debug", - "log", - "rustls 0.21.12", - "rustls-pemfile 1.0.4", - "serde", - "serde_yml 0.0.10", - "specifications 3.0.0 (git+https://github.com/braneframework/brane)", - "thiserror 2.0.12", - "tokio", - "x509-parser", -] - -[[package]] -name = "brane-ctl" -version = "3.0.0" -source = "git+https://github.com/braneframework/brane#9acf0eda6fd6c13efe0c198801d586e6478efffb" -dependencies = [ - "base64ct", - "bollard", - "brane-cfg", - "brane-shr 3.0.0 (git+https://github.com/braneframework/brane)", - "brane-tsk", - "clap", - "clap_complete", - "console", - "dialoguer", - "diesel", - "diesel_migrations", - "dirs", - "dotenvy", - "download", - "eflint-to-json 0.1.0 (git+https://github.com/braneframework/policy-reasoner)", - "enum-debug", - "error-trace", - "hex-literal", - "human-panic", - "humanlog", - "humantime", - "jsonwebtoken", - "lazy_static", - "log", - "names", - "policy 0.1.0 (git+https://github.com/braneframework/policy-reasoner)", - "rand 0.9.0", - "reqwest 0.12.15", - "serde", - "serde_json", - "serde_yml 0.0.10", - "shlex", - "specifications 3.0.0 (git+https://github.com/braneframework/brane)", - "srv 0.1.0 (git+https://github.com/braneframework/policy-reasoner)", - "tempfile", - "thiserror 2.0.12", - "tokio", -] - -[[package]] -name = "brane-dsl" -version = "3.0.0" -source = "git+https://github.com/braneframework/brane#9acf0eda6fd6c13efe0c198801d586e6478efffb" -dependencies = [ - "brane-shr 3.0.0 (git+https://github.com/braneframework/brane)", - "enum-debug", - "log", - "nom", - "nom_locate", - "rand 0.9.0", - "regex", - "serde", - "specifications 3.0.0 (git+https://github.com/braneframework/brane)", - "thiserror 2.0.12", -] - -[[package]] -name = "brane-dsl" -version = "3.0.0" -source = "git+https://github.com/epi-project/brane#7524309001d9ff347c8063747f2535a474a73565" -dependencies = [ - "brane-shr 3.0.0 (git+https://github.com/epi-project/brane)", - "bytes", - "enum-debug", - "log", - "nom", - "nom_locate", - "rand 0.8.5", - "regex", - "semver", - "serde", - "serde_json", - "specifications 3.0.0 (git+https://github.com/epi-project/brane)", - "thiserror 1.0.69", -] - -[[package]] -name = "brane-exe" -version = "3.0.0" -source = "git+https://github.com/braneframework/brane#9acf0eda6fd6c13efe0c198801d586e6478efffb" -dependencies = [ - "async-recursion", - "async-trait", - "base64 0.22.1", - "brane-ast 3.0.0 (git+https://github.com/braneframework/brane)", - "brane-shr 3.0.0 (git+https://github.com/braneframework/brane)", - "console", - "enum-debug", - "futures", - "lazy_static", - "log", - "num-traits", - "serde", - "serde_json", - "specifications 3.0.0 (git+https://github.com/braneframework/brane)", - "thiserror 2.0.12", - "tokio", -] - -[[package]] -name = "brane-exe" -version = "3.0.0" -source = "git+https://github.com/epi-project/brane#7524309001d9ff347c8063747f2535a474a73565" -dependencies = [ - "async-recursion", - "async-trait", - "base64 0.13.1", - "brane-ast 3.0.0 (git+https://github.com/epi-project/brane)", - "brane-shr 3.0.0 (git+https://github.com/epi-project/brane)", - "console", - "enum-debug", - "futures", - "lazy_static", - "log", - "num-traits", - "serde", - "serde_json", - "specifications 3.0.0 (git+https://github.com/epi-project/brane)", - "tokio", - "uuid", -] - -[[package]] -name = "brane-shr" -version = "3.0.0" -source = "git+https://github.com/braneframework/brane#9acf0eda6fd6c13efe0c198801d586e6478efffb" -dependencies = [ - "async-compression 0.4.22", - "console", - "dialoguer", - "enum-debug", - "fs2", - "hex", - "humanlog", - "indicatif", - "log", - "regex", - "reqwest 0.12.15", - "sha2", - "specifications 3.0.0 (git+https://github.com/braneframework/brane)", - "thiserror 2.0.12", - "tokio", - "tokio-stream", - "tokio-tar", - "url", -] - -[[package]] -name = "brane-shr" -version = "3.0.0" -source = "git+https://github.com/epi-project/brane#7524309001d9ff347c8063747f2535a474a73565" -dependencies = [ - "async-compression 0.3.15", - "console", - "dialoguer", - "enum-debug", - "fs2", - "futures-util", - "hex", - "humanlog", - "indicatif", - "log", - "num-traits", - "regex", - "reqwest 0.11.27", - "sha2", - "specifications 3.0.0 (git+https://github.com/epi-project/brane)", - "tokio", - "tokio-stream", - "tokio-tar", - "url", -] - -[[package]] -name = "brane-tsk" -version = "3.0.0" -source = "git+https://github.com/braneframework/brane#9acf0eda6fd6c13efe0c198801d586e6478efffb" -dependencies = [ - "async-trait", - "base64 0.22.1", - "base64ct", - "bollard", - "brane-ast 3.0.0 (git+https://github.com/braneframework/brane)", - "brane-cfg", - "brane-exe 3.0.0 (git+https://github.com/braneframework/brane)", - "brane-shr 3.0.0 (git+https://github.com/braneframework/brane)", - "chrono", - "console", - "dialoguer", - "enum-debug", - "futures-util", - "graphql_client", - "log", - "num-traits", - "parking_lot", - "rand 0.9.0", - "reqwest 0.12.15", - "serde", - "serde_json", - "serde_yml 0.0.10", - "sha2", - "specifications 3.0.0 (git+https://github.com/braneframework/brane)", - "thiserror 2.0.12", - "tokio", - "tokio-tar", - "tokio-util", - "tonic 0.12.3", - "uuid", -] - -[[package]] -name = "bumpalo" -version = "3.17.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1628fb46dfa0b37568d12e5edd512553eccf6a22a78e8bde00bb4aed84d5bdbf" - -[[package]] -name = "bytecount" -version = "0.6.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ce89b21cab1437276d2650d57e971f9d548a2d9037cc231abdc0562b97498ce" - -[[package]] -name = "byteorder" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" - -[[package]] -name = "bytes" -version = "1.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" - -[[package]] -name = "cc" -version = "1.2.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fcb57c740ae1daf453ae85f16e37396f672b039e00d9d866e07ddb24e328e3a" -dependencies = [ - "shlex", -] - -[[package]] -name = "cfg-if" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" - -[[package]] -name = "cfg_aliases" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" - -[[package]] -name = "checker-client" -version = "0.1.0" -dependencies = [ - "audit-logger 0.1.0", - "brane-ast 3.0.0 (git+https://github.com/braneframework/brane)", - "brane-shr 3.0.0 (git+https://github.com/braneframework/brane)", - "chrono", - "clap", - "console", - "deliberation 0.1.0", - "eflint-json", - "eflint-to-json 0.1.0", - "enum-debug", - "error-trace", - "hmac", - "humanlog", - "jwt", - "log", - "names", - "policy 0.1.0", - "rand 0.9.0", - "reqwest 0.12.15", - "serde_json", - "sha2", - "specifications 3.0.0 (git+https://github.com/braneframework/brane)", - "srv 0.1.0", -] - -[[package]] -name = "chrono" -version = "0.4.40" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a7964611d71df112cb1730f2ee67324fcf4d0fc6606acbbe9bfe06df124637c" -dependencies = [ - "android-tzdata", - "iana-time-zone", - "js-sys", - "num-traits", - "serde", - "wasm-bindgen", - "windows-link", -] - -[[package]] -name = "clap" -version = "4.5.34" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e958897981290da2a852763fe9cdb89cd36977a5d729023127095fa94d95e2ff" -dependencies = [ - "clap_builder", - "clap_derive", -] - -[[package]] -name = "clap_builder" -version = "4.5.34" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83b0f35019843db2160b5bb19ae09b4e6411ac33fc6a712003c33e03090e2489" -dependencies = [ - "anstream", - "anstyle", - "clap_lex", - "strsim", -] - -[[package]] -name = "clap_complete" -version = "4.5.47" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c06f5378ea264ad4f82bbc826628b5aad714a75abf6ece087e923010eb937fb6" -dependencies = [ - "clap", -] - -[[package]] -name = "clap_derive" -version = "4.5.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09176aae279615badda0765c0c0b3f6ed53f4709118af73cf4655d85d1530cd7" -dependencies = [ - "heck 0.5.0", - "proc-macro2", - "quote", - "syn 2.0.100", -] - -[[package]] -name = "clap_lex" -version = "0.7.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6" - -[[package]] -name = "colorchoice" -version = "1.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990" - -[[package]] -name = "combine" -version = "4.6.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd" -dependencies = [ - "bytes", - "memchr", -] - -[[package]] -name = "console" -version = "0.15.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "054ccb5b10f9f2cbf51eb355ca1d05c2d279ce1804688d0db74b4733a5aeafd8" -dependencies = [ - "encode_unicode", - "libc", - "once_cell", - "unicode-width", - "windows-sys 0.59.0", -] - -[[package]] -name = "const_format" -version = "0.2.34" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "126f97965c8ad46d6d9163268ff28432e8f6a1196a55578867832e3049df63dd" -dependencies = [ - "const_format_proc_macros", -] - -[[package]] -name = "const_format_proc_macros" -version = "0.2.34" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d57c2eccfb16dbac1f4e61e206105db5820c9d26c3c472bc17c774259ef7744" -dependencies = [ - "proc-macro2", - "quote", - "unicode-xid", -] - -[[package]] -name = "core-foundation" -version = "0.9.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" -dependencies = [ - "core-foundation-sys", - "libc", -] - -[[package]] -name = "core-foundation-sys" -version = "0.8.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" - -[[package]] -name = "cpufeatures" -version = "0.2.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" -dependencies = [ - "libc", -] - -[[package]] -name = "crc32fast" -version = "1.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3" -dependencies = [ - "cfg-if", -] - -[[package]] -name = "crypto-common" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" -dependencies = [ - "generic-array", - "typenum", -] - -[[package]] -name = "darling" -version = "0.20.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc7f46116c46ff9ab3eb1597a45688b6715c6e628b5c133e288e709a29bcb4ee" -dependencies = [ - "darling_core", - "darling_macro", -] - -[[package]] -name = "darling_core" -version = "0.20.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d00b9596d185e565c2207a0b01f8bd1a135483d02d9b7b0a54b11da8d53412e" -dependencies = [ - "fnv", - "ident_case", - "proc-macro2", - "quote", - "strsim", - "syn 2.0.100", -] - -[[package]] -name = "darling_macro" -version = "0.20.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead" -dependencies = [ - "darling_core", - "quote", - "syn 2.0.100", -] - -[[package]] -name = "data-encoding" -version = "2.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "575f75dfd25738df5b91b8e43e14d44bda14637a58fae779fd2b064f8bf3e010" - -[[package]] -name = "deliberation" -version = "0.1.0" -dependencies = [ - "brane-ast 3.0.0 (git+https://github.com/braneframework/brane)", - "brane-exe 3.0.0 (git+https://github.com/braneframework/brane)", - "clap", - "enum-debug", - "error-trace", - "humanlog", - "log", - "serde", - "serde_json", - "uuid", -] - -[[package]] -name = "deliberation" -version = "0.1.0" -source = "git+https://github.com/braneframework/policy-reasoner#42261e9751c320914c89cf49641bf7477bd1c313" -dependencies = [ - "brane-ast 3.0.0 (git+https://github.com/epi-project/brane)", - "brane-exe 3.0.0 (git+https://github.com/epi-project/brane)", - "enum-debug", - "log", - "serde", - "serde_json", - "uuid", -] - -[[package]] -name = "der-parser" -version = "10.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07da5016415d5a3c4dd39b11ed26f915f52fc4e0dc197d87908bc916e51bc1a6" -dependencies = [ - "asn1-rs", - "displaydoc", - "nom", - "num-bigint", - "num-traits", - "rusticata-macros", -] - -[[package]] -name = "deranged" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28cfac68e08048ae1883171632c2aef3ebc555621ae56fbccce1cbf22dd7f058" -dependencies = [ - "powerfmt", - "serde", -] - -[[package]] -name = "dialoguer" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "658bce805d770f407bc62102fca7c2c64ceef2fbcb2b8bd19d2765ce093980de" -dependencies = [ - "console", - "shell-words", - "tempfile", - "thiserror 1.0.69", - "zeroize", -] - -[[package]] -name = "diesel" -version = "2.2.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "470eb10efc8646313634c99bb1593f402a6434cbd86e266770c6e39219adb86a" -dependencies = [ - "chrono", - "diesel_derives", - "libsqlite3-sys", - "r2d2", - "time", -] - -[[package]] -name = "diesel_derives" -version = "2.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a93958254b70bea63b4187ff73d10180599d9d8d177071b7f91e6da4e0c0ad55" -dependencies = [ - "diesel_table_macro_syntax", - "dsl_auto_type", - "proc-macro2", - "quote", - "syn 2.0.100", -] - -[[package]] -name = "diesel_migrations" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a73ce704bad4231f001bff3314d91dce4aba0770cee8b233991859abc15c1f6" -dependencies = [ - "diesel", - "migrations_internals", - "migrations_macros", -] - -[[package]] -name = "diesel_table_macro_syntax" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "209c735641a413bc68c4923a9d6ad4bcb3ca306b794edaa7eb0b3228a99ffb25" -dependencies = [ - "syn 2.0.100", -] - -[[package]] -name = "digest" -version = "0.10.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" -dependencies = [ - "block-buffer", - "crypto-common", - "subtle", -] - -[[package]] -name = "dirs" -version = "6.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3e8aa94d75141228480295a7d0e7feb620b1a5ad9f12bc40be62411e38cce4e" -dependencies = [ - "dirs-sys", -] - -[[package]] -name = "dirs-sys" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e01a3366d27ee9890022452ee61b2b63a67e6f13f58900b651ff5665f0bb1fab" -dependencies = [ - "libc", - "option-ext", - "redox_users", - "windows-sys 0.59.0", -] - -[[package]] -name = "displaydoc" -version = "0.2.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.100", -] - -[[package]] -name = "dotenvy" -version = "0.15.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b" - -[[package]] -name = "download" -version = "0.1.0" -source = "git+https://github.com/Lut99/download-rs?tag=v0.1.0#ebba70884249fcfcb978eef74c57da16b593ac33" -dependencies = [ - "console", - "hex", - "indicatif", - "reqwest 0.11.27", - "sha2", - "url", -] - -[[package]] -name = "dsl_auto_type" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "139ae9aca7527f85f26dd76483eb38533fd84bd571065da1739656ef71c5ff5b" -dependencies = [ - "darling", - "either", - "heck 0.5.0", - "proc-macro2", - "quote", - "syn 2.0.100", -] - -[[package]] -name = "eflint-json" -version = "0.1.0" -source = "git+https://gitlab.com/eflint/json-spec-rs.git?branch=incorrect-is-invariant#a77ae8c5050fcbeb36340ec86f353099d6c51182" -dependencies = [ - "console", - "enum-debug", - "lazy_static", - "serde", -] - -[[package]] -name = "eflint-to-json" -version = "0.1.0" -dependencies = [ - "async-recursion", - "console", - "futures-util", - "hex", - "hex-literal", - "indicatif", - "log", - "reqwest 0.12.15", - "sha2", - "tokio", -] - -[[package]] -name = "eflint-to-json" -version = "0.1.0" -source = "git+https://github.com/braneframework/policy-reasoner#42261e9751c320914c89cf49641bf7477bd1c313" -dependencies = [ - "async-recursion", - "console", - "futures-util", - "hex", - "hex-literal", - "indicatif", - "log", - "reqwest 0.12.15", - "sha2", - "tokio", -] - -[[package]] -name = "either" -version = "1.15.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" - -[[package]] -name = "encode_unicode" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34aa73646ffb006b8f5147f3dc182bd4bcb190227ce861fc4a4844bf8e3cb2c0" - -[[package]] -name = "encoding_rs" -version = "0.8.35" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" -dependencies = [ - "cfg-if", -] - -[[package]] -name = "enum-debug" -version = "1.1.0" -source = "git+https://github.com/Lut99/enum-debug?tag=v1.1.0#f535466915f82861b0a2dc9d4e3a106f04d5ace7" -dependencies = [ - "enum-debug-derive", -] - -[[package]] -name = "enum-debug-derive" -version = "1.1.0" -source = "git+https://github.com/Lut99/enum-debug?tag=v1.1.0#f535466915f82861b0a2dc9d4e3a106f04d5ace7" -dependencies = [ - "quote", - "syn 2.0.100", -] - -[[package]] -name = "equivalent" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" - -[[package]] -name = "errno" -version = "0.3.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d" -dependencies = [ - "libc", - "windows-sys 0.59.0", -] - -[[package]] -name = "error-trace" -version = "3.0.0" -source = "git+https://github.com/Lut99/error-trace-rs?tag=v3.0.0#a026dc304b5bcc40bc68574f21459d2f1b6cad90" - -[[package]] -name = "fastrand" -version = "2.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" - -[[package]] -name = "filetime" -version = "0.2.25" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35c0522e981e68cbfa8c3f978441a5f34b30b96e146b33cd3359176b50fe8586" -dependencies = [ - "cfg-if", - "libc", - "libredox", - "windows-sys 0.59.0", -] - -[[package]] -name = "flate2" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11faaf5a5236997af9848be0bef4db95824b1d534ebc64d0f0c6cf3e67bd38dc" -dependencies = [ - "crc32fast", - "miniz_oxide", -] - -[[package]] -name = "fnv" -version = "1.0.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" - -[[package]] -name = "foreign-types" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" -dependencies = [ - "foreign-types-shared", -] - -[[package]] -name = "foreign-types-shared" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" - -[[package]] -name = "form_urlencoded" -version = "1.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" -dependencies = [ - "percent-encoding", -] - -[[package]] -name = "fs2" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9564fc758e15025b46aa6643b1b77d047d1a56a1aea6e01002ac0c7026876213" -dependencies = [ - "libc", - "winapi", -] - -[[package]] -name = "futures" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876" -dependencies = [ - "futures-channel", - "futures-core", - "futures-executor", - "futures-io", - "futures-sink", - "futures-task", - "futures-util", -] - -[[package]] -name = "futures-channel" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" -dependencies = [ - "futures-core", - "futures-sink", -] - -[[package]] -name = "futures-core" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" - -[[package]] -name = "futures-executor" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f" -dependencies = [ - "futures-core", - "futures-task", - "futures-util", -] - -[[package]] -name = "futures-io" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" - -[[package]] -name = "futures-macro" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.100", -] - -[[package]] -name = "futures-sink" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" - -[[package]] -name = "futures-task" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" - -[[package]] -name = "futures-util" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" -dependencies = [ - "futures-channel", - "futures-core", - "futures-io", - "futures-macro", - "futures-sink", - "futures-task", - "memchr", - "pin-project-lite", - "pin-utils", - "slab", -] - -[[package]] -name = "generic-array" -version = "0.14.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" -dependencies = [ - "typenum", - "version_check", -] - -[[package]] -name = "getrandom" -version = "0.2.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" -dependencies = [ - "cfg-if", - "js-sys", - "libc", - "wasi 0.11.0+wasi-snapshot-preview1", - "wasm-bindgen", -] - -[[package]] -name = "getrandom" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73fea8450eea4bac3940448fb7ae50d91f034f941199fcd9d909a5a07aa455f0" -dependencies = [ - "cfg-if", - "js-sys", - "libc", - "r-efi", - "wasi 0.14.2+wasi-0.2.4", - "wasm-bindgen", -] - -[[package]] -name = "gimli" -version = "0.31.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" - -[[package]] -name = "graphql-introspection-query" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f2a4732cf5140bd6c082434494f785a19cfb566ab07d1382c3671f5812fed6d" -dependencies = [ - "serde", -] - -[[package]] -name = "graphql-parser" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a818c0d883d7c0801df27be910917750932be279c7bc82dc541b8769425f409" -dependencies = [ - "combine", - "thiserror 1.0.69", -] - -[[package]] -name = "graphql_client" -version = "0.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a50cfdc7f34b7f01909d55c2dcb71d4c13cbcbb4a1605d6c8bd760d654c1144b" -dependencies = [ - "graphql_query_derive", - "serde", - "serde_json", -] - -[[package]] -name = "graphql_client_codegen" -version = "0.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e27ed0c2cf0c0cc52c6bcf3b45c907f433015e580879d14005386251842fb0a" -dependencies = [ - "graphql-introspection-query", - "graphql-parser", - "heck 0.4.1", - "lazy_static", - "proc-macro2", - "quote", - "serde", - "serde_json", - "syn 1.0.109", -] - -[[package]] -name = "graphql_query_derive" -version = "0.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83febfa838f898cfa73dfaa7a8eb69ff3409021ac06ee94cfb3d622f6eeb1a97" -dependencies = [ - "graphql_client_codegen", - "proc-macro2", - "syn 1.0.109", -] - -[[package]] -name = "h2" -version = "0.3.26" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81fe527a889e1532da5c525686d96d4c2e74cdd345badf8dfef9f6b39dd5f5e8" -dependencies = [ - "bytes", - "fnv", - "futures-core", - "futures-sink", - "futures-util", - "http 0.2.12", - "indexmap 2.8.0", - "slab", - "tokio", - "tokio-util", - "tracing", -] - -[[package]] -name = "h2" -version = "0.4.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5017294ff4bb30944501348f6f8e42e6ad28f42c8bbef7a74029aff064a4e3c2" -dependencies = [ - "atomic-waker", - "bytes", - "fnv", - "futures-core", - "futures-sink", - "http 1.1.0", - "indexmap 2.8.0", - "slab", - "tokio", - "tokio-util", - "tracing", -] - -[[package]] -name = "hashbrown" -version = "0.12.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" - -[[package]] -name = "hashbrown" -version = "0.15.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" - -[[package]] -name = "headers" -version = "0.3.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06683b93020a07e3dbcf5f8c0f6d40080d725bea7936fc01ad345c01b97dc270" -dependencies = [ - "base64 0.21.7", - "bytes", - "headers-core", - "http 0.2.12", - "httpdate", - "mime", - "sha1", -] - -[[package]] -name = "headers-core" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7f66481bfee273957b1f20485a4ff3362987f85b2c236580d81b4eb7a326429" -dependencies = [ - "http 0.2.12", -] - -[[package]] -name = "heck" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" - -[[package]] -name = "heck" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" - -[[package]] -name = "hex" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" - -[[package]] -name = "hex-literal" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6fe2267d4ed49bc07b63801559be28c718ea06c4738b7a03c94df7386d2cde46" - -[[package]] -name = "hmac" -version = "0.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" -dependencies = [ - "digest", -] - -[[package]] -name = "http" -version = "0.2.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "601cbb57e577e2f5ef5be8e7b83f0f63994f25aa94d673e54a92d5c516d101f1" -dependencies = [ - "bytes", - "fnv", - "itoa", -] - -[[package]] -name = "http" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21b9ddb458710bc376481b842f5da65cdf31522de232c1ca8146abce2a358258" -dependencies = [ - "bytes", - "fnv", - "itoa", -] - -[[package]] -name = "http-body" -version = "0.4.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2" -dependencies = [ - "bytes", - "http 0.2.12", - "pin-project-lite", -] - -[[package]] -name = "http-body" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" -dependencies = [ - "bytes", - "http 1.1.0", -] - -[[package]] -name = "http-body-util" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a" -dependencies = [ - "bytes", - "futures-core", - "http 1.1.0", - "http-body 1.0.1", - "pin-project-lite", -] - -[[package]] -name = "http-serde" -version = "1.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f560b665ad9f1572cfcaf034f7fb84338a7ce945216d64a90fd81f046a3caee" -dependencies = [ - "http 0.2.12", - "serde", -] - -[[package]] -name = "httparse" -version = "1.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" - -[[package]] -name = "httpdate" -version = "1.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" - -[[package]] -name = "human-panic" -version = "2.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "80b84a66a325082740043a6c28bbea400c129eac0d3a27673a1de971e44bf1f7" -dependencies = [ - "anstream", - "anstyle", - "backtrace", - "os_info", - "serde", - "serde_derive", - "toml", - "uuid", -] - -[[package]] -name = "humanlog" -version = "0.2.0" -source = "git+https://github.com/Lut99/humanlog-rs?tag=v0.2.0#0bceaaeb6426a6c990017c1d3c0aa7f51adbbfa1" -dependencies = [ - "chrono", - "console", - "log", - "parking_lot", -] - -[[package]] -name = "humantime" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b112acc8b3adf4b107a8ec20977da0273a8c386765a3ec0229bd500a1443f9f" - -[[package]] -name = "hyper" -version = "0.14.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41dfc780fdec9373c01bae43289ea34c972e40ee3c9f6b3c8801a35f35586ce7" -dependencies = [ - "bytes", - "futures-channel", - "futures-core", - "futures-util", - "h2 0.3.26", - "http 0.2.12", - "http-body 0.4.6", - "httparse", - "httpdate", - "itoa", - "pin-project-lite", - "socket2", - "tokio", - "tower-service", - "tracing", - "want", -] - -[[package]] -name = "hyper" -version = "1.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc2b571658e38e0c01b1fdca3bbbe93c00d3d71693ff2770043f8c29bc7d6f80" -dependencies = [ - "bytes", - "futures-channel", - "futures-util", - "h2 0.4.8", - "http 1.1.0", - "http-body 1.0.1", - "httparse", - "httpdate", - "itoa", - "pin-project-lite", - "smallvec", - "tokio", - "want", -] - -[[package]] -name = "hyper-named-pipe" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73b7d8abf35697b81a825e386fc151e0d503e8cb5fcb93cc8669c376dfd6f278" -dependencies = [ - "hex", - "hyper 1.6.0", - "hyper-util", - "pin-project-lite", - "tokio", - "tower-service", - "winapi", -] - -[[package]] -name = "hyper-rustls" -version = "0.27.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d191583f3da1305256f22463b9bb0471acad48a4e534a5218b9963e9c1f59b2" -dependencies = [ - "futures-util", - "http 1.1.0", - "hyper 1.6.0", - "hyper-util", - "rustls 0.23.25", - "rustls-pki-types", - "tokio", - "tokio-rustls", - "tower-service", -] - -[[package]] -name = "hyper-timeout" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbb958482e8c7be4bc3cf272a766a2b0bf1a6755e7a6ae777f017a31d11b13b1" -dependencies = [ - "hyper 0.14.32", - "pin-project-lite", - "tokio", - "tokio-io-timeout", -] - -[[package]] -name = "hyper-timeout" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b90d566bffbce6a75bd8b09a05aa8c2cb1fabb6cb348f8840c9e4c90a0d83b0" -dependencies = [ - "hyper 1.6.0", - "hyper-util", - "pin-project-lite", - "tokio", - "tower-service", -] - -[[package]] -name = "hyper-tls" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905" -dependencies = [ - "bytes", - "hyper 0.14.32", - "native-tls", - "tokio", - "tokio-native-tls", -] - -[[package]] -name = "hyper-tls" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0" -dependencies = [ - "bytes", - "http-body-util", - "hyper 1.6.0", - "hyper-util", - "native-tls", - "tokio", - "tokio-native-tls", - "tower-service", -] - -[[package]] -name = "hyper-util" -version = "0.1.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "497bbc33a26fdd4af9ed9c70d63f61cf56a938375fbb32df34db9b1cd6d643f2" -dependencies = [ - "bytes", - "futures-channel", - "futures-util", - "http 1.1.0", - "http-body 1.0.1", - "hyper 1.6.0", - "libc", - "pin-project-lite", - "socket2", - "tokio", - "tower-service", - "tracing", -] - -[[package]] -name = "hyperlocal" -version = "0.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "986c5ce3b994526b3cd75578e62554abd09f0899d6206de48b3e96ab34ccc8c7" -dependencies = [ - "hex", - "http-body-util", - "hyper 1.6.0", - "hyper-util", - "pin-project-lite", - "tokio", - "tower-service", -] - -[[package]] -name = "iana-time-zone" -version = "0.1.63" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0c919e5debc312ad217002b8048a17b7d83f80703865bbfcfebb0458b0b27d8" -dependencies = [ - "android_system_properties", - "core-foundation-sys", - "iana-time-zone-haiku", - "js-sys", - "log", - "wasm-bindgen", - "windows-core", -] - -[[package]] -name = "iana-time-zone-haiku" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" -dependencies = [ - "cc", -] - -[[package]] -name = "icu_collections" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db2fa452206ebee18c4b5c2274dbf1de17008e874b4dc4f0aea9d01ca79e4526" -dependencies = [ - "displaydoc", - "yoke", - "zerofrom", - "zerovec", -] - -[[package]] -name = "icu_locid" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13acbb8371917fc971be86fc8057c41a64b521c184808a698c02acc242dbf637" -dependencies = [ - "displaydoc", - "litemap", - "tinystr", - "writeable", - "zerovec", -] - -[[package]] -name = "icu_locid_transform" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01d11ac35de8e40fdeda00d9e1e9d92525f3f9d887cdd7aa81d727596788b54e" -dependencies = [ - "displaydoc", - "icu_locid", - "icu_locid_transform_data", - "icu_provider", - "tinystr", - "zerovec", -] - -[[package]] -name = "icu_locid_transform_data" -version = "1.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7515e6d781098bf9f7205ab3fc7e9709d34554ae0b21ddbcb5febfa4bc7df11d" - -[[package]] -name = "icu_normalizer" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19ce3e0da2ec68599d193c93d088142efd7f9c5d6fc9b803774855747dc6a84f" -dependencies = [ - "displaydoc", - "icu_collections", - "icu_normalizer_data", - "icu_properties", - "icu_provider", - "smallvec", - "utf16_iter", - "utf8_iter", - "write16", - "zerovec", -] - -[[package]] -name = "icu_normalizer_data" -version = "1.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c5e8338228bdc8ab83303f16b797e177953730f601a96c25d10cb3ab0daa0cb7" - -[[package]] -name = "icu_properties" -version = "1.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93d6020766cfc6302c15dbbc9c8778c37e62c14427cb7f6e601d849e092aeef5" -dependencies = [ - "displaydoc", - "icu_collections", - "icu_locid_transform", - "icu_properties_data", - "icu_provider", - "tinystr", - "zerovec", -] - -[[package]] -name = "icu_properties_data" -version = "1.5.1" +name = "crypto-common" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85fb8799753b75aee8d2a21d7c14d9f38921b54b3dbda10f5a3c7a7b82dba5e2" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "typenum", +] [[package]] -name = "icu_provider" -version = "1.5.0" +name = "digest" +version = "0.10.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ed421c8a8ef78d3e2dbc98a973be2f3770cb42b606e3ab18d6237c4dfde68d9" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ - "displaydoc", - "icu_locid", - "icu_provider_macros", - "stable_deref_trait", - "tinystr", - "writeable", - "yoke", - "zerofrom", - "zerovec", + "block-buffer", + "crypto-common", ] [[package]] -name = "icu_provider_macros" -version = "1.5.0" +name = "displaydoc" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6" +checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.100", + "syn", ] [[package]] -name = "ident_case" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" +name = "eflint-json" +version = "0.1.0" +source = "git+https://gitlab.com/eflint/json-spec-rs?branch=incorrect-is-invariant#a77ae8c5050fcbeb36340ec86f353099d6c51182" +dependencies = [ + "enum-debug", + "serde", +] [[package]] -name = "idna" -version = "1.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "686f825264d630750a544639377bae737628043f20d38bbc029e8f29ea968a7e" +name = "eflint-json-reasoner" +version = "0.2.0" dependencies = [ - "idna_adapter", - "smallvec", - "utf8_iter", + "eflint-json", + "error-trace 3.1.0", + "reqwest", + "serde_json", + "specifications", + "tracing", ] [[package]] -name = "idna_adapter" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "daca1df1c957320b2cf139ac61e7bd64fed304c5040df000a745aa1de3b4ef71" +name = "eflint-to-json" +version = "0.2.0" dependencies = [ - "icu_normalizer", - "icu_properties", + "async-recursion", + "console", + "futures-util", + "hex", + "hex-literal", + "indicatif", + "log", + "reqwest", + "sha2", + "tokio", + "tracing", ] [[package]] -name = "indexmap" -version = "1.9.3" +name = "encode_unicode" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" -dependencies = [ - "autocfg", - "hashbrown 0.12.3", - "serde", -] +checksum = "34aa73646ffb006b8f5147f3dc182bd4bcb190227ce861fc4a4844bf8e3cb2c0" [[package]] -name = "indexmap" -version = "2.8.0" +name = "encoding_rs" +version = "0.8.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3954d50fe15b02142bf25d3b8bdadb634ec3948f103d04ffe3031bc8fe9d7058" +checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" dependencies = [ - "equivalent", - "hashbrown 0.15.2", - "serde", + "cfg-if", ] [[package]] -name = "indicatif" -version = "0.17.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "183b3088984b400f4cfac3620d5e076c84da5364016b4f49473de574b2586235" +name = "enum-debug" +version = "1.1.0" +source = "git+https://github.com/Lut99/enum-debug?tag=v1.1.0#f535466915f82861b0a2dc9d4e3a106f04d5ace7" dependencies = [ - "console", - "number_prefix", - "portable-atomic", - "unicode-width", - "web-time", + "enum-debug-derive", ] [[package]] -name = "ipnet" -version = "2.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130" +name = "enum-debug-derive" +version = "1.1.0" +source = "git+https://github.com/Lut99/enum-debug?tag=v1.1.0#f535466915f82861b0a2dc9d4e3a106f04d5ace7" +dependencies = [ + "quote", + "syn", +] [[package]] -name = "is_terminal_polyfill" -version = "1.70.1" +name = "equivalent" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" [[package]] -name = "itertools" -version = "0.12.1" +name = "errno" +version = "0.3.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569" +checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d" dependencies = [ - "either", + "libc", + "windows-sys 0.59.0", ] [[package]] -name = "itertools" -version = "0.13.0" +name = "error-trace" +version = "3.0.0" +source = "git+https://github.com/Lut99/error-trace-rs?tag=v3.0.0#a026dc304b5bcc40bc68574f21459d2f1b6cad90" + +[[package]] +name = "error-trace" +version = "3.1.0" +source = "git+https://github.com/Lut99/error-trace-rs?tag=v3.1.0#c3046059215f2bf60175f0274ec91c241c6dd069" + +[[package]] +name = "fastrand" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" +checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" + +[[package]] +name = "file-logger" +version = "0.2.0" dependencies = [ - "either", + "chrono", + "enum-debug", + "serde", + "serde_json", + "specifications", + "tokio", + "tracing", ] [[package]] -name = "itertools" -version = "0.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285" +name = "file-resolver" +version = "0.2.0" dependencies = [ - "either", + "serde", + "serde_json", + "specifications", + "tokio", + "tracing", ] [[package]] -name = "itoa" -version = "1.0.15" +name = "fnv" +version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" [[package]] -name = "js-sys" -version = "0.3.77" +name = "foreign-types" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f" +checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" dependencies = [ - "once_cell", - "wasm-bindgen", + "foreign-types-shared", ] [[package]] -name = "jsonwebtoken" -version = "9.3.1" +name = "foreign-types-shared" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a87cc7a48537badeae96744432de36f4be2b4a34a05a5ef32e9dd8a1c169dde" -dependencies = [ - "base64 0.22.1", - "js-sys", - "pem", - "ring", - "serde", - "serde_json", - "simple_asn1", -] +checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" [[package]] -name = "jwt" -version = "0.16.0" +name = "form_urlencoded" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6204285f77fe7d9784db3fdc449ecce1a0114927a51d5a41c4c7a292011c015f" +checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" dependencies = [ - "base64 0.13.1", - "crypto-common", - "digest", - "hmac", - "serde", - "serde_json", - "sha2", + "percent-encoding", ] [[package]] -name = "key-manager" -version = "0.1.0" +name = "futures-channel" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" dependencies = [ - "brane-ctl", - "clap", - "error-trace", - "humanlog", - "humantime", - "jsonwebtoken", - "log", + "futures-core", + "futures-sink", ] [[package]] -name = "lazy_static" -version = "1.5.0" +name = "futures-core" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" +checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" [[package]] -name = "libc" -version = "0.2.171" +name = "futures-io" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c19937216e9d3aa9956d9bb8dfc0b0c8beb6058fc4f7a4dc4d850edf86a237d6" +checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" [[package]] -name = "libredox" -version = "0.1.3" +name = "futures-macro" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" +checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" dependencies = [ - "bitflags 2.9.0", - "libc", - "redox_syscall 0.5.10", + "proc-macro2", + "quote", + "syn", ] [[package]] -name = "libsqlite3-sys" -version = "0.31.0" +name = "futures-sink" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" + +[[package]] +name = "futures-task" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" + +[[package]] +name = "futures-util" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad8935b44e7c13394a179a438e0cebba0fe08fe01b54f152e29a93b5cf993fd4" +checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" dependencies = [ - "pkg-config", - "vcpkg", + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "pin-utils", + "slab", ] [[package]] -name = "libyml" -version = "0.0.3" +name = "generic-array" +version = "0.14.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e281a65eeba3d4503a2839252f86374528f9ceafe6fed97c1d3b52e1fb625c1" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] [[package]] -name = "libyml" -version = "0.0.4" +name = "getrandom" +version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64804cc6a5042d4f05379909ba25b503ec04e2c082151d62122d5dcaa274b961" +checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" +dependencies = [ + "cfg-if", + "libc", + "wasi 0.11.0+wasi-snapshot-preview1", +] [[package]] -name = "linux-raw-sys" -version = "0.9.3" +name = "getrandom" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe7db12097d22ec582439daf8618b8fdd1a7bef6270e9af3b1ebcd30893cf413" +checksum = "73fea8450eea4bac3940448fb7ae50d91f034f941199fcd9d909a5a07aa455f0" +dependencies = [ + "cfg-if", + "libc", + "r-efi", + "wasi 0.14.2+wasi-0.2.4", +] [[package]] -name = "litemap" -version = "0.7.5" +name = "gimli" +version = "0.31.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23fb14cb19457329c82206317a5663005a4d404783dc74f4252769b0d5f42856" +checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" [[package]] -name = "lock_api" -version = "0.4.12" +name = "h2" +version = "0.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" +checksum = "5017294ff4bb30944501348f6f8e42e6ad28f42c8bbef7a74029aff064a4e3c2" dependencies = [ - "autocfg", - "scopeguard", - "serde", + "atomic-waker", + "bytes", + "fnv", + "futures-core", + "futures-sink", + "http", + "indexmap", + "slab", + "tokio", + "tokio-util", + "tracing", ] [[package]] -name = "log" -version = "0.4.27" +name = "hashbrown" +version = "0.15.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" +checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" [[package]] -name = "matchit" -version = "0.7.3" +name = "heck" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e7465ac9959cc2b1404e8e2367b43684a6d13790fe23056cc8c6c5a6b7bcb94" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" [[package]] -name = "memchr" -version = "2.7.4" +name = "hex" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" [[package]] -name = "migrations_internals" -version = "2.2.0" +name = "hex-literal" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd01039851e82f8799046eabbb354056283fb265c8ec0996af940f4e85a380ff" -dependencies = [ - "serde", - "toml", -] +checksum = "6fe2267d4ed49bc07b63801559be28c718ea06c4738b7a03c94df7386d2cde46" [[package]] -name = "migrations_macros" -version = "2.2.0" +name = "http" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ffb161cc72176cb37aa47f1fc520d3ef02263d67d661f44f05d05a079e1237fd" +checksum = "21b9ddb458710bc376481b842f5da65cdf31522de232c1ca8146abce2a358258" dependencies = [ - "migrations_internals", - "proc-macro2", - "quote", + "bytes", + "fnv", + "itoa", ] [[package]] -name = "mime" -version = "0.3.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" - -[[package]] -name = "mime_guess" -version = "2.0.5" +name = "http-body" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7c44f8e672c00fe5308fa235f821cb4198414e1c77935c1ab6948d3fd78550e" +checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" dependencies = [ - "mime", - "unicase", + "bytes", + "http", ] [[package]] -name = "minimal-lexical" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" - -[[package]] -name = "miniz_oxide" -version = "0.8.5" +name = "http-body-util" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e3e04debbb59698c15bacbb6d93584a8c0ca9cc3213cb423d31f760d8843ce5" +checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a" dependencies = [ - "adler2", + "bytes", + "futures-core", + "http", + "http-body", + "pin-project-lite", ] [[package]] -name = "mio" -version = "1.0.3" +name = "httparse" +version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2886843bf800fba2e3377cff24abf6379b4c4d5c6681eaf9ea5b0d15090450bd" -dependencies = [ - "libc", - "wasi 0.11.0+wasi-snapshot-preview1", - "windows-sys 0.52.0", -] +checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" [[package]] -name = "multer" -version = "2.1.0" +name = "hyper" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01acbdc23469fd8fe07ab135923371d5f5a422fbf9c522158677c8eb15bc51c2" +checksum = "cc2b571658e38e0c01b1fdca3bbbe93c00d3d71693ff2770043f8c29bc7d6f80" dependencies = [ "bytes", - "encoding_rs", + "futures-channel", "futures-util", - "http 0.2.12", + "h2", + "http", + "http-body", "httparse", - "log", - "memchr", - "mime", - "spin", - "version_check", + "itoa", + "pin-project-lite", + "smallvec", + "tokio", + "want", ] [[package]] -name = "names" -version = "0.1.0" -source = "git+https://github.com/Lut99/names-rs?tag=v0.1.0#6fbb94733dfe526f7e68e374846e3802bac88327" +name = "hyper-rustls" +version = "0.27.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d191583f3da1305256f22463b9bb0471acad48a4e534a5218b9963e9c1f59b2" dependencies = [ - "rand 0.8.5", + "futures-util", + "http", + "hyper", + "hyper-util", + "rustls", + "rustls-pki-types", + "tokio", + "tokio-rustls", + "tower-service", ] [[package]] -name = "native-tls" -version = "0.2.14" +name = "hyper-tls" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87de3442987e9dbec73158d5c715e7ad9072fda936bb03d19d7fa10e00520f0e" +checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0" dependencies = [ - "libc", - "log", - "openssl", - "openssl-probe", - "openssl-sys", - "schannel", - "security-framework", - "security-framework-sys", - "tempfile", + "bytes", + "http-body-util", + "hyper", + "hyper-util", + "native-tls", + "tokio", + "tokio-native-tls", + "tower-service", ] [[package]] -name = "nested-cli-parser" -version = "0.1.0" +name = "hyper-util" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "497bbc33a26fdd4af9ed9c70d63f61cf56a938375fbb32df34db9b1cd6d643f2" dependencies = [ - "unicode-segmentation", + "bytes", + "futures-channel", + "futures-util", + "http", + "http-body", + "hyper", + "libc", + "pin-project-lite", + "socket2", + "tokio", + "tower-service", + "tracing", ] [[package]] -name = "nom" -version = "7.1.3" +name = "iana-time-zone" +version = "0.1.63" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +checksum = "b0c919e5debc312ad217002b8048a17b7d83f80703865bbfcfebb0458b0b27d8" dependencies = [ - "memchr", - "minimal-lexical", + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "log", + "wasm-bindgen", + "windows-core", ] [[package]] -name = "nom_locate" -version = "4.2.0" +name = "iana-time-zone-haiku" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e3c83c053b0713da60c5b8de47fe8e494fe3ece5267b2f23090a07a053ba8f3" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" dependencies = [ - "bytecount", - "memchr", - "nom", + "cc", ] [[package]] -name = "num-bigint" -version = "0.4.6" +name = "icu_collections" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" +checksum = "db2fa452206ebee18c4b5c2274dbf1de17008e874b4dc4f0aea9d01ca79e4526" dependencies = [ - "num-integer", - "num-traits", + "displaydoc", + "yoke", + "zerofrom", + "zerovec", ] [[package]] -name = "num-conv" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" - -[[package]] -name = "num-integer" -version = "0.1.46" +name = "icu_locid" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" +checksum = "13acbb8371917fc971be86fc8057c41a64b521c184808a698c02acc242dbf637" dependencies = [ - "num-traits", + "displaydoc", + "litemap", + "tinystr", + "writeable", + "zerovec", ] [[package]] -name = "num-traits" -version = "0.2.19" +name = "icu_locid_transform" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +checksum = "01d11ac35de8e40fdeda00d9e1e9d92525f3f9d887cdd7aa81d727596788b54e" dependencies = [ - "autocfg", + "displaydoc", + "icu_locid", + "icu_locid_transform_data", + "icu_provider", + "tinystr", + "zerovec", ] [[package]] -name = "number_prefix" -version = "0.4.0" +name = "icu_locid_transform_data" +version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "830b246a0e5f20af87141b25c173cd1b609bd7779a4617d6ec582abaf90870f3" +checksum = "7515e6d781098bf9f7205ab3fc7e9709d34554ae0b21ddbcb5febfa4bc7df11d" [[package]] -name = "object" -version = "0.36.7" +name = "icu_normalizer" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87" +checksum = "19ce3e0da2ec68599d193c93d088142efd7f9c5d6fc9b803774855747dc6a84f" dependencies = [ - "memchr", + "displaydoc", + "icu_collections", + "icu_normalizer_data", + "icu_properties", + "icu_provider", + "smallvec", + "utf16_iter", + "utf8_iter", + "write16", + "zerovec", ] [[package]] -name = "oid-registry" -version = "0.8.1" +name = "icu_normalizer_data" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c5e8338228bdc8ab83303f16b797e177953730f601a96c25d10cb3ab0daa0cb7" + +[[package]] +name = "icu_properties" +version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12f40cff3dde1b6087cc5d5f5d4d65712f34016a03ed60e9c08dcc392736b5b7" +checksum = "93d6020766cfc6302c15dbbc9c8778c37e62c14427cb7f6e601d849e092aeef5" dependencies = [ - "asn1-rs", + "displaydoc", + "icu_collections", + "icu_locid_transform", + "icu_properties_data", + "icu_provider", + "tinystr", + "zerovec", ] [[package]] -name = "once_cell" -version = "1.21.3" +name = "icu_properties_data" +version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" +checksum = "85fb8799753b75aee8d2a21d7c14d9f38921b54b3dbda10f5a3c7a7b82dba5e2" [[package]] -name = "openssl" -version = "0.10.71" +name = "icu_provider" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e14130c6a98cd258fdcb0fb6d744152343ff729cbfcb28c656a9d12b999fbcd" +checksum = "6ed421c8a8ef78d3e2dbc98a973be2f3770cb42b606e3ab18d6237c4dfde68d9" dependencies = [ - "bitflags 2.9.0", - "cfg-if", - "foreign-types", - "libc", - "once_cell", - "openssl-macros", - "openssl-sys", + "displaydoc", + "icu_locid", + "icu_provider_macros", + "stable_deref_trait", + "tinystr", + "writeable", + "yoke", + "zerofrom", + "zerovec", ] [[package]] -name = "openssl-macros" -version = "0.1.1" +name = "icu_provider_macros" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" +checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6" dependencies = [ "proc-macro2", "quote", - "syn 2.0.100", + "syn", ] [[package]] -name = "openssl-probe" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e" - -[[package]] -name = "openssl-sys" -version = "0.9.106" +name = "idna" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8bb61ea9811cc39e3c2069f40b8b8e2e70d8569b361f879786cc7ed48b777cdd" +checksum = "686f825264d630750a544639377bae737628043f20d38bbc029e8f29ea968a7e" dependencies = [ - "cc", - "libc", - "pkg-config", - "vcpkg", + "idna_adapter", + "smallvec", + "utf8_iter", ] [[package]] -name = "option-ext" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" - -[[package]] -name = "os_info" -version = "3.10.0" +name = "idna_adapter" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a604e53c24761286860eba4e2c8b23a0161526476b1de520139d69cdb85a6b5" +checksum = "daca1df1c957320b2cf139ac61e7bd64fed304c5040df000a745aa1de3b4ef71" dependencies = [ - "log", - "serde", - "windows-sys 0.52.0", + "icu_normalizer", + "icu_properties", ] [[package]] -name = "parking_lot" -version = "0.12.3" +name = "indexmap" +version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" +checksum = "3954d50fe15b02142bf25d3b8bdadb634ec3948f103d04ffe3031bc8fe9d7058" dependencies = [ - "lock_api", - "parking_lot_core", + "equivalent", + "hashbrown", ] [[package]] -name = "parking_lot_core" -version = "0.9.10" +name = "indicatif" +version = "0.17.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" +checksum = "183b3088984b400f4cfac3620d5e076c84da5364016b4f49473de574b2586235" dependencies = [ - "cfg-if", - "libc", - "redox_syscall 0.5.10", - "smallvec", - "windows-targets 0.52.6", + "console", + "number_prefix", + "portable-atomic", + "unicode-width", + "web-time", ] [[package]] -name = "pem" -version = "3.0.5" +name = "ipnet" +version = "2.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38af38e8470ac9dee3ce1bae1af9c1671fffc44ddfd8bd1d0a3445bf349a8ef3" -dependencies = [ - "base64 0.22.1", - "serde", -] +checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130" [[package]] -name = "percent-encoding" -version = "2.3.1" +name = "is_terminal_polyfill" +version = "1.70.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" +checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" [[package]] -name = "pin-project" -version = "1.1.10" +name = "itoa" +version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "677f1add503faace112b9f1373e43e9e054bfdd22ff1a63c1bc485eaec6a6a8a" -dependencies = [ - "pin-project-internal", -] +checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" [[package]] -name = "pin-project-internal" -version = "1.1.10" +name = "js-sys" +version = "0.3.77" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e918e4ff8c4549eb882f14b3a4bc8c8bc93de829416eacf579f1207a8fbf861" +checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f" dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.100", + "once_cell", + "wasm-bindgen", ] [[package]] -name = "pin-project-lite" -version = "0.2.16" +name = "lazy_static" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" [[package]] -name = "pin-utils" -version = "0.1.0" +name = "libc" +version = "0.2.171" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" +checksum = "c19937216e9d3aa9956d9bb8dfc0b0c8beb6058fc4f7a4dc4d850edf86a237d6" [[package]] -name = "pkg-config" -version = "0.3.32" +name = "linux-raw-sys" +version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" - -[[package]] -name = "policy" -version = "0.1.0" -dependencies = [ - "async-trait", - "chrono", - "clap", - "error-trace", - "humanlog", - "log", - "serde", - "serde_json", - "warp", -] +checksum = "fe7db12097d22ec582439daf8618b8fdd1a7bef6270e9af3b1ebcd30893cf413" [[package]] -name = "policy" -version = "0.1.0" -source = "git+https://github.com/braneframework/policy-reasoner#42261e9751c320914c89cf49641bf7477bd1c313" -dependencies = [ - "async-trait", - "chrono", - "log", - "serde", - "serde_json", - "warp", -] +name = "litemap" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23fb14cb19457329c82206317a5663005a4d404783dc74f4252769b0d5f42856" [[package]] -name = "policy-builder" -version = "0.1.0" -dependencies = [ - "clap", - "console", - "eflint-to-json 0.1.0", - "error-trace", - "humanlog", - "log", -] +name = "log" +version = "0.4.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" [[package]] -name = "policy-reasoner" +name = "matchers" version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558" dependencies = [ - "async-trait", - "audit-logger 0.1.0", - "auth-resolver 0.1.0", - "base16ct", - "base64ct", - "brane-cfg", - "brane-shr 3.0.0 (git+https://github.com/braneframework/brane)", - "chrono", - "clap", - "deliberation 0.1.0", - "diesel", - "diesel_migrations", - "dotenvy", - "eflint-json", - "eflint-to-json 0.1.0", - "enum-debug", - "error-trace", - "graphql_client", - "humanlog", - "itertools 0.14.0", - "jsonwebtoken", - "log", - "nested-cli-parser", - "policy 0.1.0", - "reasonerconn 0.1.0", - "reqwest 0.12.15", - "serde", - "serde_json", - "serde_yml 0.0.11", - "sha2", - "specifications 3.0.0 (git+https://github.com/braneframework/brane)", - "srv 0.1.0", - "state-resolver 0.1.0", - "thiserror 2.0.12", - "tokio", - "uuid", - "warp", - "workflow 0.1.0", + "regex-automata 0.1.10", ] [[package]] -name = "portable-atomic" -version = "1.11.0" +name = "memchr" +version = "2.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "350e9b48cbc6b0e028b0473b114454c6316e57336ee184ceab6e53f72c178b3e" +checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" [[package]] -name = "powerfmt" -version = "0.2.0" +name = "mime" +version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" [[package]] -name = "ppv-lite86" -version = "0.2.21" +name = "miniz_oxide" +version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" +checksum = "8e3e04debbb59698c15bacbb6d93584a8c0ca9cc3213cb423d31f760d8843ce5" dependencies = [ - "zerocopy", + "adler2", ] [[package]] -name = "problem_details" -version = "0.5.1" +name = "mio" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09c30ce00f18c85c0a9cfaecb51d7d2bce8ca36f2d7850b009b3ea166f2dca9d" +checksum = "2886843bf800fba2e3377cff24abf6379b4c4d5c6681eaf9ea5b0d15090450bd" dependencies = [ - "http 0.2.12", - "http-serde", - "serde", + "libc", + "wasi 0.11.0+wasi-snapshot-preview1", + "windows-sys 0.52.0", ] [[package]] -name = "proc-macro2" -version = "1.0.94" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a31971752e70b8b2686d7e46ec17fb38dad4051d94024c88df49b667caea9c84" +name = "mock-logger" +version = "0.2.0" dependencies = [ - "unicode-ident", + "specifications", + "tracing", ] [[package]] -name = "prost" -version = "0.12.6" +name = "native-tls" +version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "deb1435c188b76130da55f17a466d252ff7b1418b2ad3e037d127b94e3411f29" +checksum = "87de3442987e9dbec73158d5c715e7ad9072fda936bb03d19d7fa10e00520f0e" dependencies = [ - "bytes", - "prost-derive 0.12.6", + "libc", + "log", + "openssl", + "openssl-probe", + "openssl-sys", + "schannel", + "security-framework", + "security-framework-sys", + "tempfile", ] [[package]] -name = "prost" -version = "0.13.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2796faa41db3ec313a31f7624d9286acf277b52de526150b7e69f3debf891ee5" +name = "no-op-reasoner" +version = "0.2.0" dependencies = [ - "bytes", - "prost-derive 0.13.5", + "error-trace 3.1.0", + "specifications", + "tracing", ] [[package]] -name = "prost-derive" -version = "0.12.6" +name = "nu-ansi-term" +version = "0.46.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81bddcdb20abf9501610992b6759a4c888aef7d1a7247ef75e2404275ac24af1" +checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84" dependencies = [ - "anyhow", - "itertools 0.12.1", - "proc-macro2", - "quote", - "syn 2.0.100", + "overload", + "winapi", ] [[package]] -name = "prost-derive" -version = "0.13.5" +name = "num-traits" +version = "0.2.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a56d757972c98b346a9b766e3f02746cde6dd1cd1d1d563472929fdd74bec4d" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" dependencies = [ - "anyhow", - "itertools 0.13.0", - "proc-macro2", - "quote", - "syn 2.0.100", + "autocfg", ] [[package]] -name = "quinn" -version = "0.11.7" +name = "number_prefix" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3bd15a6f2967aef83887dcb9fec0014580467e33720d073560cf015a5683012" -dependencies = [ - "bytes", - "cfg_aliases", - "pin-project-lite", - "quinn-proto", - "quinn-udp", - "rustc-hash", - "rustls 0.23.25", - "socket2", - "thiserror 2.0.12", - "tokio", - "tracing", - "web-time", -] +checksum = "830b246a0e5f20af87141b25c173cd1b609bd7779a4617d6ec582abaf90870f3" [[package]] -name = "quinn-proto" -version = "0.11.10" +name = "object" +version = "0.36.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b820744eb4dc9b57a3398183639c511b5a26d2ed702cedd3febaa1393caa22cc" +checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87" dependencies = [ - "bytes", - "getrandom 0.3.2", - "rand 0.9.0", - "ring", - "rustc-hash", - "rustls 0.23.25", - "rustls-pki-types", - "slab", - "thiserror 2.0.12", - "tinyvec", - "tracing", - "web-time", + "memchr", ] [[package]] -name = "quinn-udp" -version = "0.5.11" +name = "once_cell" +version = "1.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" + +[[package]] +name = "openssl" +version = "0.10.71" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "541d0f57c6ec747a90738a52741d3221f7960e8ac2f0ff4b1a63680e033b4ab5" +checksum = "5e14130c6a98cd258fdcb0fb6d744152343ff729cbfcb28c656a9d12b999fbcd" dependencies = [ - "cfg_aliases", + "bitflags", + "cfg-if", + "foreign-types", "libc", "once_cell", - "socket2", - "tracing", - "windows-sys 0.59.0", + "openssl-macros", + "openssl-sys", ] [[package]] -name = "quote" -version = "1.0.40" +name = "openssl-macros" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" +checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", + "quote", + "syn", ] [[package]] -name = "r-efi" -version = "5.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74765f6d916ee2faa39bc8e68e4f3ed8949b48cccdac59983d287a7cb71ce9c5" - -[[package]] -name = "r2d2" -version = "0.8.10" +name = "openssl-probe" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51de85fb3fb6524929c8a2eb85e6b6d363de4e8c48f9e2c2eac4944abc181c93" -dependencies = [ - "log", - "parking_lot", - "scheduled-thread-pool", -] +checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e" [[package]] -name = "rand" -version = "0.8.5" +name = "openssl-sys" +version = "0.9.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +checksum = "8bb61ea9811cc39e3c2069f40b8b8e2e70d8569b361f879786cc7ed48b777cdd" dependencies = [ + "cc", "libc", - "rand_chacha 0.3.1", - "rand_core 0.6.4", + "pkg-config", + "vcpkg", ] [[package]] -name = "rand" -version = "0.9.0" +name = "overload" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3779b94aeb87e8bd4e834cee3650289ee9e0d5677f976ecdb6d219e5f4f6cd94" -dependencies = [ - "rand_chacha 0.9.0", - "rand_core 0.9.3", - "zerocopy", -] +checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" [[package]] -name = "rand_chacha" -version = "0.3.1" +name = "paste" +version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" -dependencies = [ - "ppv-lite86", - "rand_core 0.6.4", -] +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" [[package]] -name = "rand_chacha" -version = "0.9.0" +name = "percent-encoding" +version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" -dependencies = [ - "ppv-lite86", - "rand_core 0.9.3", -] +checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" [[package]] -name = "rand_core" -version = "0.6.4" +name = "pin-project-lite" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" -dependencies = [ - "getrandom 0.2.15", -] +checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" [[package]] -name = "rand_core" -version = "0.9.3" +name = "pin-utils" +version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" -dependencies = [ - "getrandom 0.3.2", -] +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" [[package]] -name = "reasonerconn" -version = "0.1.0" +name = "pkg-config" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" + +[[package]] +name = "policy-reasoner" +version = "1.0.0" dependencies = [ - "anyhow", - "async-trait", - "audit-logger 0.1.0", "clap", - "eflint-json", - "enum-debug", - "error-trace", - "humanlog", - "log", - "policy 0.1.0", - "serde", + "console", + "eflint-json-reasoner", + "eflint-to-json", + "error-trace 3.0.0", + "file-logger", + "file-resolver", + "mock-logger", + "no-op-reasoner", "serde_json", - "state-resolver 0.1.0", + "specifications", "tokio", - "transform", - "workflow 0.1.0", + "tracing", + "tracing-subscriber", + "workflow", ] [[package]] -name = "reasonerconn" -version = "0.1.0" -source = "git+https://github.com/braneframework/policy-reasoner#42261e9751c320914c89cf49641bf7477bd1c313" -dependencies = [ - "anyhow", - "async-trait", - "audit-logger 0.1.0 (git+https://github.com/braneframework/policy-reasoner)", - "eflint-json", - "enum-debug", - "log", - "policy 0.1.0 (git+https://github.com/braneframework/policy-reasoner)", - "serde", - "serde_json", - "state-resolver 0.1.0 (git+https://github.com/braneframework/policy-reasoner)", - "tokio", - "transform", - "workflow 0.1.0 (git+https://github.com/braneframework/policy-reasoner)", -] +name = "portable-atomic" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "350e9b48cbc6b0e028b0473b114454c6316e57336ee184ceab6e53f72c178b3e" [[package]] -name = "redox_syscall" -version = "0.3.5" +name = "proc-macro2" +version = "1.0.94" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29" +checksum = "a31971752e70b8b2686d7e46ec17fb38dad4051d94024c88df49b667caea9c84" dependencies = [ - "bitflags 1.3.2", + "unicode-ident", ] [[package]] -name = "redox_syscall" -version = "0.5.10" +name = "quote" +version = "1.0.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b8c0c260b63a8219631167be35e6a988e9554dbd323f8bd08439c8ed1302bd1" +checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" dependencies = [ - "bitflags 2.9.0", + "proc-macro2", ] [[package]] -name = "redox_users" -version = "0.5.0" +name = "r-efi" +version = "5.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd6f9d3d47bdd2ad6945c5015a226ec6155d0bcdfd8f7cd29f86b71f8de99d2b" -dependencies = [ - "getrandom 0.2.15", - "libredox", - "thiserror 2.0.12", -] +checksum = "74765f6d916ee2faa39bc8e68e4f3ed8949b48cccdac59983d287a7cb71ce9c5" [[package]] name = "regex" @@ -3068,8 +1217,17 @@ checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" dependencies = [ "aho-corasick", "memchr", - "regex-automata", - "regex-syntax", + "regex-automata 0.4.9", + "regex-syntax 0.8.5", +] + +[[package]] +name = "regex-automata" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" +dependencies = [ + "regex-syntax 0.6.29", ] [[package]] @@ -3080,57 +1238,20 @@ checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" dependencies = [ "aho-corasick", "memchr", - "regex-syntax", + "regex-syntax 0.8.5", ] [[package]] name = "regex-syntax" -version = "0.8.5" +version = "0.6.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" +checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" [[package]] -name = "reqwest" -version = "0.11.27" +name = "regex-syntax" +version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd67538700a17451e7cba03ac727fb961abb7607553461627b97de0b89cf4a62" -dependencies = [ - "base64 0.21.7", - "bytes", - "encoding_rs", - "futures-core", - "futures-util", - "h2 0.3.26", - "http 0.2.12", - "http-body 0.4.6", - "hyper 0.14.32", - "hyper-tls 0.5.0", - "ipnet", - "js-sys", - "log", - "mime", - "native-tls", - "once_cell", - "percent-encoding", - "pin-project-lite", - "rustls 0.21.12", - "rustls-pemfile 1.0.4", - "serde", - "serde_json", - "serde_urlencoded", - "sync_wrapper 0.1.2", - "system-configuration 0.5.1", - "tokio", - "tokio-native-tls", - "tokio-util", - "tower-service", - "url", - "wasm-bindgen", - "wasm-bindgen-futures", - "wasm-streams", - "web-sys", - "winreg", -] +checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" [[package]] name = "reqwest" @@ -3138,43 +1259,38 @@ version = "0.12.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d19c46a6fdd48bc4dab94b6103fccc55d34c67cc0ad04653aad4ea2a07cd7bbb" dependencies = [ - "base64 0.22.1", + "base64", "bytes", "encoding_rs", "futures-channel", "futures-core", "futures-util", - "h2 0.4.8", - "http 1.1.0", - "http-body 1.0.1", + "h2", + "http", + "http-body", "http-body-util", - "hyper 1.6.0", + "hyper", "hyper-rustls", - "hyper-tls 0.6.0", + "hyper-tls", "hyper-util", "ipnet", "js-sys", "log", "mime", - "mime_guess", "native-tls", "once_cell", "percent-encoding", "pin-project-lite", - "quinn", - "rustls 0.23.25", - "rustls-pemfile 2.2.0", - "rustls-pki-types", + "rustls-pemfile", "serde", "serde_json", "serde_urlencoded", - "sync_wrapper 1.0.2", - "system-configuration 0.6.1", + "sync_wrapper", + "system-configuration", "tokio", "tokio-native-tls", - "tokio-rustls", "tokio-util", - "tower 0.5.2", + "tower", "tower-service", "url", "wasm-bindgen", @@ -3204,46 +1320,19 @@ version = "0.1.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" -[[package]] -name = "rustc-hash" -version = "2.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" - -[[package]] -name = "rusticata-macros" -version = "4.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "faf0c4a6ece9950b9abdb62b1cfcf2a68b3b67a10ba445b3bb85be2a293d0632" -dependencies = [ - "nom", -] - [[package]] name = "rustix" version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e56a18552996ac8d29ecc3b190b4fdbb2d91ca4ec396de7bbffaf43f3d637e96" dependencies = [ - "bitflags 2.9.0", + "bitflags", "errno", "libc", "linux-raw-sys", "windows-sys 0.59.0", ] -[[package]] -name = "rustls" -version = "0.21.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f56a14d1f48b391359b22f731fd4bd7e43c97f3c50eee276f3aa09c94784d3e" -dependencies = [ - "log", - "ring", - "rustls-webpki 0.101.7", - "sct", -] - [[package]] name = "rustls" version = "0.23.25" @@ -3251,22 +1340,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "822ee9188ac4ec04a2f0531e55d035fb2de73f18b41a63c70c2712503b6fb13c" dependencies = [ "once_cell", - "ring", "rustls-pki-types", - "rustls-webpki 0.103.1", + "rustls-webpki", "subtle", "zeroize", ] -[[package]] -name = "rustls-pemfile" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c74cae0a4cf6ccbbf5f359f08efdf8ee7e1dc532573bf0db71968cb56b1448c" -dependencies = [ - "base64 0.21.7", -] - [[package]] name = "rustls-pemfile" version = "2.2.0" @@ -3281,268 +1360,104 @@ name = "rustls-pki-types" version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "917ce264624a4b4db1c364dcc35bfca9ded014d0a958cd47ad3e960e988ea51c" -dependencies = [ - "web-time", -] - -[[package]] -name = "rustls-webpki" -version = "0.101.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b6275d1ee7a1cd780b64aca7726599a1dbc893b1e64144529e55c3c2f745765" -dependencies = [ - "ring", - "untrusted", -] [[package]] name = "rustls-webpki" version = "0.103.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fef8b8769aaccf73098557a87cd1816b4f9c7c16811c9c77142aa695c16f2c03" -dependencies = [ - "ring", - "rustls-pki-types", - "untrusted", -] - -[[package]] -name = "rustversion" -version = "1.0.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eded382c5f5f786b989652c49544c4877d9f015cc22e145a5ea8ea66c2921cd2" - -[[package]] -name = "ryu" -version = "1.0.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" - -[[package]] -name = "schannel" -version = "0.1.27" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f29ebaa345f945cec9fbbc532eb307f0fdad8161f281b6369539c8d84876b3d" -dependencies = [ - "windows-sys 0.59.0", -] - -[[package]] -name = "scheduled-thread-pool" -version = "0.2.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3cbc66816425a074528352f5789333ecff06ca41b36b0b0efdfbb29edc391a19" -dependencies = [ - "parking_lot", -] - -[[package]] -name = "scoped-tls" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" - -[[package]] -name = "scopeguard" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" - -[[package]] -name = "sct" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da046153aa2352493d6cb7da4b6e5c0c057d8a1d0a9aa8560baffdd945acd414" -dependencies = [ - "ring", - "untrusted", -] - -[[package]] -name = "security-framework" -version = "2.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" -dependencies = [ - "bitflags 2.9.0", - "core-foundation", - "core-foundation-sys", - "libc", - "security-framework-sys", -] - -[[package]] -name = "security-framework-sys" -version = "2.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49db231d56a190491cb4aeda9527f1ad45345af50b0851622a7adb8c03b01c32" -dependencies = [ - "core-foundation-sys", - "libc", -] - -[[package]] -name = "semver" -version = "1.0.26" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56e6fa9c48d24d85fb3de5ad847117517440f6beceb7798af16b4a87d616b8d0" - -[[package]] -name = "serde" -version = "1.0.219" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" -dependencies = [ - "serde_derive", -] - -[[package]] -name = "serde_derive" -version = "1.0.219" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.100", -] - -[[package]] -name = "serde_json" -version = "1.0.140" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "20068b6e96dc6c9bd23e01df8827e6c7e1f2fddd43c21810382803c136b99373" -dependencies = [ - "itoa", - "memchr", - "ryu", - "serde", +checksum = "fef8b8769aaccf73098557a87cd1816b4f9c7c16811c9c77142aa695c16f2c03" +dependencies = [ + "ring", + "rustls-pki-types", + "untrusted", ] [[package]] -name = "serde_json_any_key" -version = "2.0.0" +name = "rustversion" +version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2c409ca1209f6c4741028b9e1e56d973c868ffaef25ffbaf2471e486c2a74b3" -dependencies = [ - "serde", - "serde_json", -] +checksum = "eded382c5f5f786b989652c49544c4877d9f015cc22e145a5ea8ea66c2921cd2" [[package]] -name = "serde_repr" -version = "0.1.20" +name = "ryu" +version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "175ee3e80ae9982737ca543e96133087cbd9a485eecc3bc4de9c1a37b47ea59c" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.100", -] +checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" [[package]] -name = "serde_spanned" -version = "0.6.8" +name = "schannel" +version = "0.1.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87607cb1398ed59d48732e575a4c28a7a8ebf2454b964fe3f224f2afc07909e1" +checksum = "1f29ebaa345f945cec9fbbc532eb307f0fdad8161f281b6369539c8d84876b3d" dependencies = [ - "serde", + "windows-sys 0.59.0", ] [[package]] -name = "serde_test" -version = "1.0.177" +name = "security-framework" +version = "2.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f901ee573cab6b3060453d2d5f0bae4e6d628c23c0a962ff9b5f1d7c8d4f1ed" +checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" dependencies = [ - "serde", + "bitflags", + "core-foundation", + "core-foundation-sys", + "libc", + "security-framework-sys", ] [[package]] -name = "serde_urlencoded" -version = "0.7.1" +name = "security-framework-sys" +version = "2.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" +checksum = "49db231d56a190491cb4aeda9527f1ad45345af50b0851622a7adb8c03b01c32" dependencies = [ - "form_urlencoded", - "itoa", - "ryu", - "serde", + "core-foundation-sys", + "libc", ] [[package]] -name = "serde_with" -version = "3.12.0" +name = "serde" +version = "1.0.219" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6b6f7f2fcb69f747921f79f3926bd1e203fce4fef62c268dd3abfb6d86029aa" +checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" dependencies = [ - "base64 0.22.1", - "chrono", - "hex", - "indexmap 1.9.3", - "indexmap 2.8.0", - "serde", "serde_derive", - "serde_json", - "serde_with_macros", - "time", ] [[package]] -name = "serde_with_macros" -version = "3.12.0" +name = "serde_derive" +version = "1.0.219" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d00caa5193a3c8362ac2b73be6b9e768aa5a4b2f721d8f4b339600c3cb51f8e" +checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" dependencies = [ - "darling", "proc-macro2", "quote", - "syn 2.0.100", + "syn", ] [[package]] -name = "serde_yml" -version = "0.0.10" +name = "serde_json" +version = "1.0.140" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78ce6afeda22f0b55dde2c34897bce76a629587348480384231205c14b59a01f" +checksum = "20068b6e96dc6c9bd23e01df8827e6c7e1f2fddd43c21810382803c136b99373" dependencies = [ - "indexmap 2.8.0", "itoa", - "libyml 0.0.3", - "log", "memchr", "ryu", "serde", - "serde_json", - "tempfile", ] [[package]] -name = "serde_yml" -version = "0.0.11" +name = "serde_urlencoded" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48e76bab63c3fd98d27c17f9cbce177f64a91f5e69ac04cafe04e1bb25d1dc3c" +checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" dependencies = [ - "indexmap 2.8.0", + "form_urlencoded", "itoa", - "libyml 0.0.4", - "log", - "memchr", "ryu", "serde", - "serde_json", - "tempfile", -] - -[[package]] -name = "sha1" -version = "0.10.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" -dependencies = [ - "cfg-if", - "cpufeatures", - "digest", ] [[package]] @@ -3557,10 +1472,13 @@ dependencies = [ ] [[package]] -name = "shell-words" -version = "1.1.0" +name = "sharded-slab" +version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24188a676b6ae68c3b2cb3a01be17fbf7240ce009799bb56d5b1409051e78fde" +checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" +dependencies = [ + "lazy_static", +] [[package]] name = "shlex" @@ -3577,18 +1495,6 @@ dependencies = [ "libc", ] -[[package]] -name = "simple_asn1" -version = "0.6.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "297f631f50729c8c99b84667867963997ec0b50f32b2a7dbcab828ef0541e8bb" -dependencies = [ - "num-bigint", - "num-traits", - "thiserror 2.0.12", - "time", -] - [[package]] name = "slab" version = "0.4.9" @@ -3616,121 +1522,10 @@ dependencies = [ [[package]] name = "specifications" -version = "3.0.0" -source = "git+https://github.com/braneframework/brane#9acf0eda6fd6c13efe0c198801d586e6478efffb" -dependencies = [ - "anyhow", - "async-trait", - "base64 0.22.1", - "base64ct", - "chrono", - "const_format", - "enum-debug", - "futures", - "jsonwebtoken", - "log", - "parking_lot", - "prost 0.13.5", - "reqwest 0.12.15", - "semver", - "serde", - "serde_json", - "serde_test", - "serde_with", - "serde_yml 0.0.10", - "strum 0.27.1", - "strum_macros 0.27.1", - "thiserror 2.0.12", - "tonic 0.12.3", - "uuid", -] - -[[package]] -name = "specifications" -version = "3.0.0" -source = "git+https://github.com/epi-project/brane#7524309001d9ff347c8063747f2535a474a73565" -dependencies = [ - "anyhow", - "async-trait", - "base64 0.21.7", - "base64ct", - "chrono", - "const_format", - "enum-debug", - "futures", - "jsonwebtoken", - "log", - "num-traits", - "parking_lot", - "prost 0.12.6", - "reqwest 0.11.27", - "semver", - "serde", - "serde_json", - "serde_test", - "serde_with", - "serde_yml 0.0.10", - "strum 0.24.1", - "strum_macros 0.24.3", - "tonic 0.11.0", - "uuid", -] - -[[package]] -name = "spin" -version = "0.9.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" - -[[package]] -name = "srv" -version = "0.1.0" -dependencies = [ - "audit-logger 0.1.0", - "auth-resolver 0.1.0", - "brane-ast 3.0.0 (git+https://github.com/braneframework/brane)", - "brane-exe 3.0.0 (git+https://github.com/braneframework/brane)", - "chrono", - "deliberation 0.1.0", - "error-trace", - "http 1.1.0", - "log", - "policy 0.1.0", - "problem_details", - "reasonerconn 0.1.0", - "serde", - "serde_json", - "state-resolver 0.1.0", - "tokio", - "uuid", - "warp", - "workflow 0.1.0", -] - -[[package]] -name = "srv" -version = "0.1.0" -source = "git+https://github.com/braneframework/policy-reasoner#42261e9751c320914c89cf49641bf7477bd1c313" +version = "0.2.0" dependencies = [ - "audit-logger 0.1.0 (git+https://github.com/braneframework/policy-reasoner)", - "auth-resolver 0.1.0 (git+https://github.com/braneframework/policy-reasoner)", - "brane-ast 3.0.0 (git+https://github.com/epi-project/brane)", - "brane-exe 3.0.0 (git+https://github.com/epi-project/brane)", - "chrono", - "deliberation 0.1.0 (git+https://github.com/braneframework/policy-reasoner)", - "error-trace", - "http 1.1.0", - "log", - "policy 0.1.0 (git+https://github.com/braneframework/policy-reasoner)", - "problem_details", - "reasonerconn 0.1.0 (git+https://github.com/braneframework/policy-reasoner)", + "paste", "serde", - "serde_json", - "state-resolver 0.1.0 (git+https://github.com/braneframework/policy-reasoner)", - "tokio", - "uuid", - "warp", - "workflow 0.1.0 (git+https://github.com/braneframework/policy-reasoner)", ] [[package]] @@ -3739,111 +1534,18 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" -[[package]] -name = "state-resolver" -version = "0.1.0" -dependencies = [ - "async-trait", - "serde", - "workflow 0.1.0", -] - -[[package]] -name = "state-resolver" -version = "0.1.0" -source = "git+https://github.com/braneframework/policy-reasoner#42261e9751c320914c89cf49641bf7477bd1c313" -dependencies = [ - "async-trait", - "serde", - "workflow 0.1.0 (git+https://github.com/braneframework/policy-reasoner)", -] - [[package]] name = "strsim" version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" -[[package]] -name = "strum" -version = "0.24.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "063e6045c0e62079840579a7e47a355ae92f60eb74daaf156fb1e84ba164e63f" - -[[package]] -name = "strum" -version = "0.25.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "290d54ea6f91c969195bdbcd7442c8c2a2ba87da8bf60a7ee86a235d4bc1e125" -dependencies = [ - "strum_macros 0.25.3", -] - -[[package]] -name = "strum" -version = "0.27.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f64def088c51c9510a8579e3c5d67c65349dcf755e5479ad3d010aa6454e2c32" -dependencies = [ - "strum_macros 0.27.1", -] - -[[package]] -name = "strum_macros" -version = "0.24.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e385be0d24f186b4ce2f9982191e7101bb737312ad61c1f2f984f34bcf85d59" -dependencies = [ - "heck 0.4.1", - "proc-macro2", - "quote", - "rustversion", - "syn 1.0.109", -] - -[[package]] -name = "strum_macros" -version = "0.25.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23dc1fa9ac9c169a78ba62f0b841814b7abae11bdd047b9c58f893439e309ea0" -dependencies = [ - "heck 0.4.1", - "proc-macro2", - "quote", - "rustversion", - "syn 2.0.100", -] - -[[package]] -name = "strum_macros" -version = "0.27.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c77a8c5abcaf0f9ce05d62342b7d298c346515365c36b673df4ebe3ced01fde8" -dependencies = [ - "heck 0.5.0", - "proc-macro2", - "quote", - "rustversion", - "syn 2.0.100", -] - [[package]] name = "subtle" version = "2.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" -[[package]] -name = "syn" -version = "1.0.109" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" -dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", -] - [[package]] name = "syn" version = "2.0.100" @@ -3855,12 +1557,6 @@ dependencies = [ "unicode-ident", ] -[[package]] -name = "sync_wrapper" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160" - [[package]] name = "sync_wrapper" version = "1.0.2" @@ -3878,18 +1574,7 @@ checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971" dependencies = [ "proc-macro2", "quote", - "syn 2.0.100", -] - -[[package]] -name = "system-configuration" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba3a3adc5c275d719af8cb4272ea1c4a6d668a777f37e115f6d11ddbc1c8e0e7" -dependencies = [ - "bitflags 1.3.2", - "core-foundation", - "system-configuration-sys 0.5.0", + "syn", ] [[package]] @@ -3898,19 +1583,9 @@ version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b" dependencies = [ - "bitflags 2.9.0", + "bitflags", "core-foundation", - "system-configuration-sys 0.6.0", -] - -[[package]] -name = "system-configuration-sys" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a75fb188eb626b924683e3b95e3a48e63551fcfb51949de2f06a9d91dbee93c9" -dependencies = [ - "core-foundation-sys", - "libc", + "system-configuration-sys", ] [[package]] @@ -3937,100 +1612,23 @@ dependencies = [ ] [[package]] -name = "thiserror" -version = "1.0.69" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" -dependencies = [ - "thiserror-impl 1.0.69", -] - -[[package]] -name = "thiserror" -version = "2.0.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708" -dependencies = [ - "thiserror-impl 2.0.12", -] - -[[package]] -name = "thiserror-impl" -version = "1.0.69" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.100", -] - -[[package]] -name = "thiserror-impl" -version = "2.0.12" +name = "thread_local" +version = "1.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d" +checksum = "f60246a4944f24f6e018aa17cdeffb7818b76356965d03b07d6a9886e8962185" dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.100", -] - -[[package]] -name = "time" -version = "0.3.41" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a7619e19bc266e0f9c5e6686659d394bc57973859340060a69221e57dbc0c40" -dependencies = [ - "deranged", - "itoa", - "num-conv", - "powerfmt", - "serde", - "time-core", - "time-macros", -] - -[[package]] -name = "time-core" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c9e9a38711f559d9e3ce1cdb06dd7c5b8ea546bc90052da6d06bb76da74bb07c" - -[[package]] -name = "time-macros" -version = "0.2.22" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3526739392ec93fd8b359c8e98514cb3e8e021beb4e5f597b00a0221f8ed8a49" -dependencies = [ - "num-conv", - "time-core", + "cfg-if", ] [[package]] name = "tinystr" version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9117f5d4db391c1cf6927e7bea3db74b9a1c1add8f7eda9ffd5364f40f57b82f" -dependencies = [ - "displaydoc", - "zerovec", -] - -[[package]] -name = "tinyvec" -version = "1.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09b3661f17e86524eccd4371ab0429194e0d7c008abb45f7a7495b1719463c71" -dependencies = [ - "tinyvec_macros", -] - -[[package]] -name = "tinyvec_macros" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" +checksum = "9117f5d4db391c1cf6927e7bea3db74b9a1c1add8f7eda9ffd5364f40f57b82f" +dependencies = [ + "displaydoc", + "zerovec", +] [[package]] name = "tokio" @@ -4042,7 +1640,6 @@ dependencies = [ "bytes", "libc", "mio", - "parking_lot", "pin-project-lite", "signal-hook-registry", "socket2", @@ -4050,16 +1647,6 @@ dependencies = [ "windows-sys 0.52.0", ] -[[package]] -name = "tokio-io-timeout" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30b74022ada614a1b4834de765f9bb43877f910cc8ce4be40e89042c9223a8bf" -dependencies = [ - "pin-project-lite", - "tokio", -] - [[package]] name = "tokio-macros" version = "2.5.0" @@ -4068,7 +1655,7 @@ checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.100", + "syn", ] [[package]] @@ -4087,46 +1674,8 @@ version = "0.26.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e727b36a1a0e8b74c376ac2211e40c2c8af09fb4013c60d910495810f008e9b" dependencies = [ - "rustls 0.23.25", - "tokio", -] - -[[package]] -name = "tokio-stream" -version = "0.1.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eca58d7bba4a75707817a2c44174253f9236b2d5fbd055602e9d5c07c139a047" -dependencies = [ - "futures-core", - "pin-project-lite", - "tokio", -] - -[[package]] -name = "tokio-tar" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d5714c010ca3e5c27114c1cdeb9d14641ace49874aa5626d7149e47aedace75" -dependencies = [ - "filetime", - "futures-core", - "libc", - "redox_syscall 0.3.5", - "tokio", - "tokio-stream", - "xattr", -] - -[[package]] -name = "tokio-tungstenite" -version = "0.21.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c83b561d025642014097b66e6c1bb422783339e0909e4429cde4749d1990bc38" -dependencies = [ - "futures-util", - "log", + "rustls", "tokio", - "tungstenite", ] [[package]] @@ -4142,117 +1691,6 @@ dependencies = [ "tokio", ] -[[package]] -name = "toml" -version = "0.8.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd87a5cdd6ffab733b2f74bc4fd7ee5fff6634124999ac278c35fc78c6120148" -dependencies = [ - "serde", - "serde_spanned", - "toml_datetime", - "toml_edit", -] - -[[package]] -name = "toml_datetime" -version = "0.6.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41" -dependencies = [ - "serde", -] - -[[package]] -name = "toml_edit" -version = "0.22.24" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17b4795ff5edd201c7cd6dca065ae59972ce77d1b80fa0a84d94950ece7d1474" -dependencies = [ - "indexmap 2.8.0", - "serde", - "serde_spanned", - "toml_datetime", - "winnow", -] - -[[package]] -name = "tonic" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76c4eb7a4e9ef9d4763600161f12f5070b92a578e1b634db88a6887844c91a13" -dependencies = [ - "async-stream", - "async-trait", - "axum 0.6.20", - "base64 0.21.7", - "bytes", - "h2 0.3.26", - "http 0.2.12", - "http-body 0.4.6", - "hyper 0.14.32", - "hyper-timeout 0.4.1", - "percent-encoding", - "pin-project", - "prost 0.12.6", - "tokio", - "tokio-stream", - "tower 0.4.13", - "tower-layer", - "tower-service", - "tracing", -] - -[[package]] -name = "tonic" -version = "0.12.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "877c5b330756d856ffcc4553ab34a5684481ade925ecc54bcd1bf02b1d0d4d52" -dependencies = [ - "async-stream", - "async-trait", - "axum 0.7.9", - "base64 0.22.1", - "bytes", - "h2 0.4.8", - "http 1.1.0", - "http-body 1.0.1", - "http-body-util", - "hyper 1.6.0", - "hyper-timeout 0.5.2", - "hyper-util", - "percent-encoding", - "pin-project", - "prost 0.13.5", - "socket2", - "tokio", - "tokio-stream", - "tower 0.4.13", - "tower-layer", - "tower-service", - "tracing", -] - -[[package]] -name = "tower" -version = "0.4.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8fa9be0de6cf49e536ce1851f987bd21a43b771b09473c3549a6c853db37c1c" -dependencies = [ - "futures-core", - "futures-util", - "indexmap 1.9.3", - "pin-project", - "pin-project-lite", - "rand 0.8.5", - "slab", - "tokio", - "tokio-util", - "tower-layer", - "tower-service", - "tracing", -] - [[package]] name = "tower" version = "0.5.2" @@ -4262,7 +1700,7 @@ dependencies = [ "futures-core", "futures-util", "pin-project-lite", - "sync_wrapper 1.0.2", + "sync_wrapper", "tokio", "tower-layer", "tower-service", @@ -4286,7 +1724,6 @@ version = "0.1.41" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" dependencies = [ - "log", "pin-project-lite", "tracing-attributes", "tracing-core", @@ -4300,7 +1737,7 @@ checksum = "395ae124c09f9e6918a2310af6038fba074bcf474ac352496d5910dd59a2226d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.100", + "syn", ] [[package]] @@ -4310,49 +1747,49 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e672c95779cf947c5311f83787af4fa8fffd12fb27e4993211a84bdfd9610f9c" dependencies = [ "once_cell", + "valuable", ] [[package]] -name = "transform" +name = "tracing-log" version = "0.2.0" -source = "git+https://github.com/Lut99/transform-rs?tag=v0.2.0#ea708962d9a770c1f89c3bce0b9309dfe48656a6" - -[[package]] -name = "try-lock" -version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" +checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" +dependencies = [ + "log", + "once_cell", + "tracing-core", +] [[package]] -name = "tungstenite" -version = "0.21.0" +name = "tracing-subscriber" +version = "0.3.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ef1a641ea34f399a848dea702823bbecfb4c486f911735368f1f137cb8257e1" +checksum = "e8189decb5ac0fa7bc8b96b7cb9b2701d60d48805aca84a238004d665fcc4008" dependencies = [ - "byteorder", - "bytes", - "data-encoding", - "http 1.1.0", - "httparse", - "log", - "rand 0.8.5", - "sha1", - "thiserror 1.0.69", - "url", - "utf-8", + "matchers", + "nu-ansi-term", + "once_cell", + "regex", + "sharded-slab", + "smallvec", + "thread_local", + "tracing", + "tracing-core", + "tracing-log", ] [[package]] -name = "typenum" -version = "1.18.0" +name = "try-lock" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f" +checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" [[package]] -name = "unicase" -version = "2.8.1" +name = "typenum" +version = "1.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75b844d17643ee918803943289730bec8aac480150456169e647ed0b576ba539" +checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f" [[package]] name = "unicode-ident" @@ -4360,24 +1797,12 @@ version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" -[[package]] -name = "unicode-segmentation" -version = "1.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" - [[package]] name = "unicode-width" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1fc81956842c57dac11422a97c3b8195a1ff727f06e85c84ed2e8aa277c9a0fd" -[[package]] -name = "unicode-xid" -version = "0.2.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" - [[package]] name = "untrusted" version = "0.9.0" @@ -4395,12 +1820,6 @@ dependencies = [ "percent-encoding", ] -[[package]] -name = "utf-8" -version = "0.7.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" - [[package]] name = "utf16_iter" version = "1.0.5" @@ -4420,15 +1839,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] -name = "uuid" -version = "1.16.0" +name = "valuable" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "458f7a779bf54acc9f347480ac654f68407d3aab21269a6e3c9f922acd9e2da9" -dependencies = [ - "getrandom 0.3.2", - "rand 0.9.0", - "serde", -] +checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" [[package]] name = "vcpkg" @@ -4451,35 +1865,6 @@ dependencies = [ "try-lock", ] -[[package]] -name = "warp" -version = "0.3.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4378d202ff965b011c64817db11d5829506d3404edeadb61f190d111da3f231c" -dependencies = [ - "bytes", - "futures-channel", - "futures-util", - "headers", - "http 0.2.12", - "hyper 0.14.32", - "log", - "mime", - "mime_guess", - "multer", - "percent-encoding", - "pin-project", - "scoped-tls", - "serde", - "serde_json", - "serde_urlencoded", - "tokio", - "tokio-tungstenite", - "tokio-util", - "tower-service", - "tracing", -] - [[package]] name = "wasi" version = "0.11.0+wasi-snapshot-preview1" @@ -4517,7 +1902,7 @@ dependencies = [ "log", "proc-macro2", "quote", - "syn 2.0.100", + "syn", "wasm-bindgen-shared", ] @@ -4552,7 +1937,7 @@ checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" dependencies = [ "proc-macro2", "quote", - "syn 2.0.100", + "syn", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -4642,7 +2027,7 @@ checksum = "a47fddd13af08290e67f4acabf4b459f647552718f683a7b415d290ac744a836" dependencies = [ "proc-macro2", "quote", - "syn 2.0.100", + "syn", ] [[package]] @@ -4653,7 +2038,7 @@ checksum = "bd9211b69f8dcdfa817bfd14bf1c97c9188afa36f4750130fcdf3f400eca9fa8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.100", + "syn", ] [[package]] @@ -4700,15 +2085,6 @@ dependencies = [ "windows-link", ] -[[package]] -name = "windows-sys" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" -dependencies = [ - "windows-targets 0.48.5", -] - [[package]] name = "windows-sys" version = "0.52.0" @@ -4727,21 +2103,6 @@ dependencies = [ "windows-targets 0.52.6", ] -[[package]] -name = "windows-targets" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" -dependencies = [ - "windows_aarch64_gnullvm 0.48.5", - "windows_aarch64_msvc 0.48.5", - "windows_i686_gnu 0.48.5", - "windows_i686_msvc 0.48.5", - "windows_x86_64_gnu 0.48.5", - "windows_x86_64_gnullvm 0.48.5", - "windows_x86_64_msvc 0.48.5", -] - [[package]] name = "windows-targets" version = "0.52.6" @@ -4774,12 +2135,6 @@ dependencies = [ "windows_x86_64_msvc 0.53.0", ] -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" - [[package]] name = "windows_aarch64_gnullvm" version = "0.52.6" @@ -4792,12 +2147,6 @@ version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "86b8d5f90ddd19cb4a147a5fa63ca848db3df085e25fee3cc10b39b6eebae764" -[[package]] -name = "windows_aarch64_msvc" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" - [[package]] name = "windows_aarch64_msvc" version = "0.52.6" @@ -4810,12 +2159,6 @@ version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c7651a1f62a11b8cbd5e0d42526e55f2c99886c77e007179efff86c2b137e66c" -[[package]] -name = "windows_i686_gnu" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" - [[package]] name = "windows_i686_gnu" version = "0.52.6" @@ -4840,12 +2183,6 @@ version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ce6ccbdedbf6d6354471319e781c0dfef054c81fbc7cf83f338a4296c0cae11" -[[package]] -name = "windows_i686_msvc" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" - [[package]] name = "windows_i686_msvc" version = "0.52.6" @@ -4858,12 +2195,6 @@ version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "581fee95406bb13382d2f65cd4a908ca7b1e4c2f1917f143ba16efe98a589b5d" -[[package]] -name = "windows_x86_64_gnu" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" - [[package]] name = "windows_x86_64_gnu" version = "0.52.6" @@ -4876,12 +2207,6 @@ version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2e55b5ac9ea33f2fc1716d1742db15574fd6fc8dadc51caab1c16a3d3b4190ba" -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" - [[package]] name = "windows_x86_64_gnullvm" version = "0.52.6" @@ -4894,12 +2219,6 @@ version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0a6e035dd0599267ce1ee132e51c27dd29437f63325753051e71dd9e42406c57" -[[package]] -name = "windows_x86_64_msvc" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" - [[package]] name = "windows_x86_64_msvc" version = "0.52.6" @@ -4912,71 +2231,21 @@ version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486" -[[package]] -name = "winnow" -version = "0.7.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e97b544156e9bebe1a0ffbc03484fc1ffe3100cbce3ffb17eac35f7cdd7ab36" -dependencies = [ - "memchr", -] - -[[package]] -name = "winreg" -version = "0.50.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "524e57b2c537c0f9b1e69f1965311ec12182b4122e45035b1508cd24d2adadb1" -dependencies = [ - "cfg-if", - "windows-sys 0.48.0", -] - [[package]] name = "wit-bindgen-rt" version = "0.39.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1" dependencies = [ - "bitflags 2.9.0", -] - -[[package]] -name = "workflow" -version = "0.1.0" -dependencies = [ - "brane-ast 3.0.0 (git+https://github.com/braneframework/brane)", - "brane-exe 3.0.0 (git+https://github.com/braneframework/brane)", - "brane-shr 3.0.0 (git+https://github.com/braneframework/brane)", - "clap", - "eflint-json", - "enum-debug", - "error-trace", - "humanlog", - "log", - "names", - "num-traits", - "rand 0.9.0", - "serde", - "serde_json", - "specifications 3.0.0 (git+https://github.com/braneframework/brane)", - "transform", + "bitflags", ] [[package]] name = "workflow" -version = "0.1.0" -source = "git+https://github.com/braneframework/policy-reasoner#42261e9751c320914c89cf49641bf7477bd1c313" +version = "0.2.0" dependencies = [ - "brane-ast 3.0.0 (git+https://github.com/epi-project/brane)", - "brane-exe 3.0.0 (git+https://github.com/epi-project/brane)", - "eflint-json", "enum-debug", - "log", - "num-traits", - "rand 0.8.5", "serde", - "specifications 3.0.0 (git+https://github.com/epi-project/brane)", - "transform", ] [[package]] @@ -4991,33 +2260,6 @@ version = "0.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51" -[[package]] -name = "x509-parser" -version = "0.17.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4569f339c0c402346d4a75a9e39cf8dad310e287eef1ff56d4c68e5067f53460" -dependencies = [ - "asn1-rs", - "data-encoding", - "der-parser", - "lazy_static", - "nom", - "oid-registry", - "rusticata-macros", - "thiserror 2.0.12", - "time", -] - -[[package]] -name = "xattr" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d65cbf2f12c15564212d48f4e3dfb87923d25d611f2aed18f4cb23f0413d89e" -dependencies = [ - "libc", - "rustix", -] - [[package]] name = "yoke" version = "0.7.5" @@ -5038,30 +2280,10 @@ checksum = "2380878cad4ac9aac1e2435f3eb4020e8374b5f13c296cb75b4620ff8e229154" dependencies = [ "proc-macro2", "quote", - "syn 2.0.100", + "syn", "synstructure", ] -[[package]] -name = "zerocopy" -version = "0.8.24" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2586fea28e186957ef732a5f8b3be2da217d65c5969d4b1e17f973ebbe876879" -dependencies = [ - "zerocopy-derive", -] - -[[package]] -name = "zerocopy-derive" -version = "0.8.24" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a996a8f63c5c4448cd959ac1bab0aaa3306ccfd060472f85943ee0750f0169be" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.100", -] - [[package]] name = "zerofrom" version = "0.1.6" @@ -5079,7 +2301,7 @@ checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" dependencies = [ "proc-macro2", "quote", - "syn 2.0.100", + "syn", "synstructure", ] @@ -5108,5 +2330,5 @@ checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6" dependencies = [ "proc-macro2", "quote", - "syn 2.0.100", + "syn", ] diff --git a/Cargo.toml b/Cargo.toml index 0271159..4824edc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,121 +1,98 @@ +[workspace] +resolver = "2" +members = [ + # Implementations + "lib/reasoners/eflint-json", + "lib/reasoners/no-op", + # "lib/reasoners/posix", + "lib/loggers/file", + "lib/loggers/mock", + "lib/resolvers/file", + + # Library + "lib/eflint-to-json", + + # Specifications + "lib/spec", + "lib/workflow" +] + +[workspace.package] +repository = "https://github.com/epi-project/policy-reasoner" +license = "Apache-2.0" + + [package] name = "policy-reasoner" -description = "Long-awaited implementation of the EPI Framework checkers, which reason about policies and contrain systems like [Brane](https://github.com/braneframework/brane)" -rust-version = "1.82" +version = "1.0.0" edition = "2021" -version.workspace = true +authors = ["Tim Müller", "Bas Kloosterman", "Daniel Voogsgerd"] repository.workspace = true -authors.workspace = true license.workspace = true +description = "A library for using several different reasoning backends to determine if a particular workflow is allowed by policy or not." -[workspace.package] -version = "0.1.0" -repository = "https://github.com/braneframework/policy-reasoner" -authors = ["Bas Kloosterman", "Tim Müller"] -license = "Apache-2.0" + +[[example]] +name = "eflint" +path = "./examples/eflint.rs" +required-features = ["eflint-json-reasoner", "eflint-to-json", "file-logger"] + +# [[example]] +# name = "posix" +# path = "./examples/posix.rs" +# required-features = ["posix-reasoner", "file-logger"] + +[[example]] +name = "no-op" +path = "./examples/no_op.rs" +required-features = ["no-op-reasoner", "file-logger"] [dependencies] -# Crates.io -async-trait = "0.1.67" -base64ct = { version = "1.6", features = ["std"] } -chrono = "0.4.35" -clap = { version = "4.5.6", features = ["derive", "env"] } -diesel = { version = "2.2.3", features = ["sqlite", "chrono", "r2d2"] } -dotenvy = "0.15.7" -itertools = "0.14.0" -jsonwebtoken = "9.2.0" -log = "0.4.22" -reqwest = { version = "0.12.0", features = ["json"] } -serde = { version = "1.0.204", features = ["derive"] } -serde_json = { version = "1.0.120", features = ["raw_value"] } -serde_yaml = { version = "0.0.11", package = "serde_yml" } -thiserror = "2.0.0" -tokio = { version = "1.38.0", features = ["full"] } -uuid = { version = "1.7.0", features = ["serde", "v4"], optional = true } -warp = "0.3" - -# Path -audit-logger = { path = "lib/audit-logger" } -auth-resolver = { path = "lib/auth-resolver" } -deliberation = { path = "./lib/deliberation" } -nested-cli-parser = { path = "lib/nested-cli-parser" } -policy = { path = "./lib/policy" } -reasonerconn = { path = "./lib/reasonerconn" } -srv = { path = "lib/srv" } -state-resolver = { path = "lib/state-resolver" } -workflow = { path = "./lib/workflow" } - -# Workspace dependencies -enum-debug.workspace = true -error-trace.workspace = true -humanlog.workspace = true - -# GitLab -eflint-json = { git = "https://gitlab.com/eflint/json-spec-rs.git", branch = "incorrect-is-invariant" } - -# Brane -brane-cfg = { git = "https://github.com/braneframework/brane", optional = true } -brane-shr = { git = "https://github.com/braneframework/brane" } -specifications = { git = "https://github.com/braneframework/brane" } - -# Weird -graphql_client = { version = "0.14.0", optional = true } - - -[build-dependencies] -base16ct = { version = "0.2", features = ["alloc"] } -diesel = { version = "2.2.3", default-features = false, features = ["sqlite"] } -diesel_migrations = "2.2.0" -sha2 = "0.10.6" - -eflint-to-json = { path = "./lib/eflint-to-json" } - -# Workspace dependencies -error-trace.workspace = true +eflint-json-reasoner = { path = "./lib/reasoners/eflint-json", optional = true } +no-op-reasoner = { path = "./lib/reasoners/no-op", optional = true } +# posix-reasoner = { path = "./lib/reasoners/posix", optional = true } +file-logger = { path = "./lib/loggers/file", optional = true } +mock-logger = { path = "./lib/loggers/mock", optional = true } +file-resolver = { path = "./lib/resolvers/file", optional = true } +eflint-to-json = { path = "./lib/eflint-to-json", features = ["async-tokio", "tracing"], optional = true } +spec = { path = "./lib/spec", package = "specifications" } +workflow = { path = "./lib/workflow", optional = true } + + +[dev-dependencies] +clap = { version = "4.5.20", features = ["derive"] } +console = "0.15.8" +error-trace = { git = "https://github.com/Lut99/error-trace-rs", tag = "v3.0.0" } +serde_json = "1.0.120" +tracing = "0.1.40" +tracing-subscriber = { version = "0.3.18", features = ["env-filter"] } +tokio = { version = "1.40.0", default-features = false, features = ["macros", "rt"] } + [features] -brane-api-resolver = ["dep:graphql_client", "dep:brane-cfg", "dep:uuid"] -leak-public-errors = [] +default = [] +all = ["reasoners", "loggers", "resolvers", "auxillary"] -[lints.clippy] -# I daren't listen to this, I'm pretty sure new Rust versions claim the opposite -redundant_static_lifetimes = "allow" +# reasoners = ["eflint-json-reasoner", "no-op-reasoner", "posix-reasoner"] +reasoners = ["eflint-json-reasoner", "no-op-reasoner"] +eflint-json-reasoner = ["dep:eflint-json-reasoner"] +no-op-reasoner = ["dep:no-op-reasoner"] +# posix-reasoner = ["dep:posix-reasoner"] +loggers = ["file-logger", "mock-logger"] +file-logger = ["dep:file-logger"] +mock-logger = ["dep:mock-logger"] + +resolvers = ["file-resolver"] +file-resolver = ["dep:file-resolver"] + +auxillary = ["eflint-to-json", "workflow"] +eflint-to-json = ["dep:eflint-to-json"] +workflow = ["dep:workflow"] -[workspace] -resolver = "2" -members = [ - "lib/auth-resolver", - "lib/deliberation", - "lib/eflint-to-json", - "lib/policy", - "lib/reasonerconn", - "lib/srv", - "lib/state-resolver", - "lib/workflow", - "lib/audit-logger", - "lib/nested-cli-parser", - - "tools/checker-client", - "tools/key-manager", - "tools/policy-builder", -] [workspace.dependencies] # The infamous lut99 crate set -enum-debug = { git = "https://github.com/Lut99/enum-debug", tag = "v1.1.0", features = [ - "derive", -] } -error-trace = { git = "https://github.com/Lut99/error-trace-rs", tag = "v3.0.0" } -humanlog = { git = "https://github.com/Lut99/humanlog-rs", tag = "v0.2.0" } -names = { git = "https://github.com/Lut99/names-rs", tag = "v0.1.0", default-features = false, features = [ - "rand", - "three-lowercase", - "three-usualcase", -] } -transform = { git = "https://github.com/Lut99/transform-rs", tag = "v0.2.0" } - -# Eflint -eflint-json = { git = "https://gitlab.com/eflint/json-spec-rs.git", branch = "incorrect-is-invariant" } +enum-debug = { git = "https://github.com/Lut99/enum-debug", tag = "v1.1.0", features = ["derive"] } diff --git a/build.rs b/build.rs deleted file mode 100644 index e9e9b3b..0000000 --- a/build.rs +++ /dev/null @@ -1,152 +0,0 @@ -// BUILD.rs -// by Lut99 -// -// Created: -// 13 Dec 2023, 11:45:11 -// Last edited: -// 12 Jun 2024, 17:49:49 -// Auto updated? -// Yes -// -// Description: -//! Build script for the main `policy-reasoner` executable. -//! -//! In particular, charged with compiling the eFLINT interface to eFLINT JSON before it can be included in the executable. -// - -use std::env::VarError; -use std::fs::File; -use std::io::Write; -use std::path::PathBuf; -use std::{env, fs}; - -use diesel::{Connection as _, SqliteConnection}; -use diesel_migrations::{FileBasedMigrations, MigrationHarness}; -use eflint_to_json::compile; -use error_trace::trace; -use sha2::{Digest as _, Sha256}; - -/***** HELPERS *****/ -/// Wraps around another Writer to always Write while updating a hash of whatever we write. -struct HashWriter(W, Sha256); -impl HashWriter { - /// Constructor for the HashWriter that initializes its digest. - /// - /// # Arguments - /// - `writer`: The [`Write`]r to wrap. - /// - /// # Returns - /// A new instance of a HashWriter. - #[inline] - fn new(writer: W) -> Self { Self(writer, Sha256::new()) } - - /// Finalizes the HashWriter and returns the digest. - /// - /// # Returns - /// The raw digest bytes encoded as Base64 (in constant time yay). - #[inline] - fn finalize(self) -> String { base16ct::lower::encode_string(&self.1.finalize()) } -} -impl Write for HashWriter { - #[inline] - fn write(&mut self, buf: &[u8]) -> std::io::Result { - // Update the hasher first before passing to the wrapper impl - self.1.update(buf); - self.0.write(buf) - } - - #[inline] - fn flush(&mut self) -> std::io::Result<()> { - // Flush only - self.0.flush() - } -} - -/***** TASKS *****/ -/// Compile & embed the eFLINT base definitions. -fn compile_eflint() { - // Read some environment variables - let src_dir: PathBuf = PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap()); - let eflint_to_json_exe: Option = match env::var("EFLINT_TO_JSON_PATH") { - Ok(path) => { - let path: PathBuf = path.into(); - if path.is_relative() { Some(src_dir.join(path)) } else { Some(path) } - }, - Err(VarError::NotPresent) => None, - Err(err) => panic!("{}", trace!(("Failed to get environment variable 'EFLINT_TO_JSON_PATH'"), err)), - }; - - // Mark the input files as source-dependent - let interface_dir: PathBuf = src_dir.join("policy").join("eflint").join("interface"); - println!("cargo:rerun-if-changed={}", interface_dir.display()); - println!("cargo:rerun-if-env-changed=EFLINT_TO_JSON_PATH"); - - // Compute the concrete input- and output paths - let main_path: PathBuf = interface_dir.join("main.eflint"); - let output_file: PathBuf = PathBuf::from(env::var("OUT_DIR").unwrap()).join("base-defs.eflint.json"); - println!("cargo:rustc-env=BASE_DEFS_EFLINT_JSON={}", output_file.display()); - - // Alright attempt to open the output file - let handle: File = match File::create(&output_file) { - Ok(handle) => handle, - Err(err) => panic!("{}", trace!(("Failed to create output file '{}'", output_file.display()), err)), - }; - let mut handle: HashWriter = HashWriter::new(handle); - - // Alright run the compiler, after which we reset the handle - if let Err(err) = compile(&main_path, &mut handle, eflint_to_json_exe.as_deref()) { - panic!("{}", trace!(("Failed to compile input file '{}'", main_path.display()), err)); - } - - // Also set the found hash - let hash: String = handle.finalize(); - println!("cargo:rustc-env=BASE_DEFS_EFLINT_JSON_HASH={hash}"); - - // Done -} - -/// Runs the Diesel migrations for the database. -fn build_database() { - // Setup the triggers for running this script - let src_dir: PathBuf = PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap()); - println!("cargo:rerun-if-changed={}", src_dir.join("migrations").display()); - - // See if the output file already exists - let data_dir: PathBuf = src_dir.join("data"); - let data_file: PathBuf = data_dir.join("policy.db"); - if !data_file.exists() { - // Touch the database file - if !data_dir.exists() { - if let Err(err) = fs::create_dir(&data_dir) { - panic!("{}", trace!(("Failed to create data directory '{}'", data_dir.display()), err)); - } - } - if let Err(err) = fs::File::create(&data_file) { - panic!("{}", trace!(("Failed to create policy database file '{}'", data_file.display()), err)); - } - - // Get the migrations defined - let migrations: FileBasedMigrations = match FileBasedMigrations::find_migrations_directory_in_path(&src_dir) { - Ok(migrations) => migrations, - Err(err) => panic!("{}", trace!(("Failed to find migration in source directory '{}'", src_dir.display()), err)), - }; - - // Apply them by connecting to the database - let mut conn: SqliteConnection = match SqliteConnection::establish(&data_file.display().to_string()) { - Ok(conn) => conn, - Err(err) => panic!("{}", trace!(("Failed to connect to database file '{}'", data_file.display()), err)), - }; - if let Err(err) = conn.run_pending_migrations(migrations) { - panic!("Failed to apply migrations to database '{}': {}", data_file.display(), err); - } - } -} - -/***** ENTRYPOINT *****/ -fn main() { - // 1. Compile the eFLINT base spec - compile_eflint(); - - // 2. Build the database - build_database(); -} diff --git a/examples/config/jwk_set_delib.json b/examples/config/jwk_set_delib.json deleted file mode 100644 index 71657a4..0000000 --- a/examples/config/jwk_set_delib.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "keys": [ - { - "use": "sig", - "alg": "HS256", - "kid": "deliberation", - "kty": "oct", - "k": "TtA7ZFN747MLjLiBDpHmA2266Mq67s26TIIExZJFn2E=" - } - ] -} \ No newline at end of file diff --git a/examples/config/jwk_set_expert.json b/examples/config/jwk_set_expert.json deleted file mode 100644 index 9a37983..0000000 --- a/examples/config/jwk_set_expert.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "keys": [ - { - "use": "sig", - "alg": "HS256", - "kid": "expert", - "kty": "oct", - "k": "abICAHBV51bjZeFcoml4sWxJcXwPvAme1yaPEBvTbBM=" - } - ] -} \ No newline at end of file diff --git a/examples/config/jwt_resolver.yaml b/examples/config/jwt_resolver.yaml deleted file mode 100644 index 0508594..0000000 --- a/examples/config/jwt_resolver.yaml +++ /dev/null @@ -1 +0,0 @@ -initiator_claim: username \ No newline at end of file diff --git a/examples/eflint.rs b/examples/eflint.rs new file mode 100644 index 0000000..c863944 --- /dev/null +++ b/examples/eflint.rs @@ -0,0 +1,203 @@ +// EFLINT.rs +// by Lut99 +// +// Created: +// 10 Oct 2024, 13:54:17 +// Last edited: +// 11 Oct 2024, 16:31:54 +// Auto updated? +// Yes +// +// Description: +//! Entrypoint to the example `eflint` policy reasoner. +// + +use std::fs::{self, File}; +use std::io::{self, Read as _}; +use std::path::PathBuf; + +use clap::Parser; +use console::style; +use eflint_json_reasoner::json::spec::RequestPhrases; +use eflint_json_reasoner::reasons::NoReason; +use error_trace::trace; +use policy_reasoner::loggers::file::FileLogger; +use policy_reasoner::reasoners::eflint_json::reasons::EFlintSilentReasonHandler; +use policy_reasoner::reasoners::eflint_json::{EFlintJsonReasonerConnector, State}; +use policy_reasoner::spec::auditlogger::SessionedAuditLogger; +use policy_reasoner::spec::reasonerconn::ReasonerConnector as _; +use spec::reasonerconn::ReasonerResponse; +use tracing::{Level, error, info}; + + +/***** ARGUMENTS *****/ +/// Defines the arguments for this binary. +#[derive(Parser)] +struct Arguments { + /// Whether to make `info!()` and `debug!()` visible. + #[clap(long, help = "If given, enables INFO- and DEBUG-level logging.")] + debug: bool, + /// Whether to make `trace!()` visible. + #[clap(long, help = "If given, enables TRACE-level logging. Implies '--debug'.")] + trace: bool, + + /// The file to use as input. + #[clap(name = "FILE", default_value = "-", help = "The eFLINT (JSON) file to read. Use '-' to read from stdin.")] + file: String, + /// Whether to read the input as DSL. + #[clap( + short, + long, + conflicts_with = "json", + help = "If given, assumes the input is standard eFLINT syntax. This is the default if no language flag is given. Mutually exclusive with \ + '--json'." + )] + dsl: bool, + /// Whether to read the input as JSON. + #[clap(short, long, conflicts_with = "dsl", help = "If given, assumes the input is eFLINT JSON syntax. Mutually exclusive with '--dsl'.")] + json: bool, + /// Which `eflint-to-json` to use. + #[clap( + short, + long, + help = "If '--json' is given, you can give this to use an existing 'eflint-to-json' binary instead of downloading one from the internet." + )] + eflint_path: Option, + + /// The address where the reasoner lives. + #[clap(short, long, default_value = "http://127.0.0.1:8080", help = "The address where the eFLINT reasoner lives.")] + address: String, +} + + + + + +/***** LIBRARY *****/ +#[tokio::main(flavor = "current_thread")] +async fn main() { + // Parse the arguments + let args = Arguments::parse(); + + // Setup the logger + tracing_subscriber::fmt() + .with_max_level(if args.trace { + Level::TRACE + } else if args.debug { + Level::DEBUG + } else { + Level::WARN + }) + .init(); + info!("{} - v{}", env!("CARGO_BIN_NAME"), env!("CARGO_PKG_VERSION")); + + // Create the logger + let logger: SessionedAuditLogger = + SessionedAuditLogger::new("test", FileLogger::new(format!("{} - v{}", env!("CARGO_BIN_NAME"), env!("CARGO_PKG_VERSION")), "./test.log")); + + // Decide which eflint to run + let dsl: bool = !args.json; + let policy: RequestPhrases = if dsl { + // First: resolve any stdin to a file + let file: PathBuf = if args.file == "-" { + let file: PathBuf = std::env::temp_dir().join(format!("{}-v{}-stdin", env!("CARGO_BIN_NAME"), env!("CARGO_PKG_VERSION"))); + let mut handle: File = match File::create(&file) { + Ok(handle) => handle, + Err(err) => { + error!("{}", trace!(("Failed to open temporary stdin file '{}'", file.display()), err)); + std::process::exit(1); + }, + }; + if let Err(err) = io::copy(&mut io::stdin(), &mut handle) { + error!("{}", trace!(("Failed to write stdin to temporary file '{}'", file.display()), err)); + std::process::exit(1); + } + file + } else { + PathBuf::from(&args.file) + }; + + // Compile first + let mut json: Vec = Vec::new(); + if let Err(err) = eflint_to_json::compile_async(&file, &mut json, args.eflint_path.as_ref().map(PathBuf::as_path)).await { + error!("{}", trace!(("Failed to compile input file '{}' to JSON", args.file), err)); + std::process::exit(1); + } + + // Now parse the file contents as a request and done + match serde_json::from_slice(&json) { + Ok(req) => req, + Err(err) => { + error!( + "{}", + trace!( + ( + "Failed to parse {} as an eFLINT JSON phrases request", + if args.file == "-" { "stdin".to_string() } else { format!("file {:?}", args.file) } + ), + err + ) + ); + std::process::exit(1); + }, + } + } else { + // Read the file + let raw: Vec = if args.file == "-" { + let mut raw: Vec = Vec::new(); + if let Err(err) = io::stdin().read_to_end(&mut raw) { + error!("{}", trace!(("Failed to read stdin"), err)); + std::process::exit(1); + } + raw + } else { + // Open the file + match fs::read(&args.file) { + Ok(raw) => raw, + Err(err) => { + error!("{}", trace!(("Failed to open & read file '{}'", args.file), err)); + std::process::exit(1); + }, + } + }; + + // Now parse the file contents as a request and done + match serde_json::from_slice(&raw) { + Ok(req) => req, + Err(err) => { + error!( + "{}", + trace!( + ( + "Failed to parse {} as an eFLINT JSON phrases request", + if args.file == "-" { "stdin".to_string() } else { format!("file {:?}", args.file) } + ), + err + ) + ); + std::process::exit(1); + }, + } + }; + + // Create the reasoner + let conn = EFlintJsonReasonerConnector::::new(&args.address, EFlintSilentReasonHandler); + let verdict: ReasonerResponse = match conn.consult(State { policy: policy.phrases, state: () }, (), &logger).await { + Ok(res) => res, + Err(err) => { + error!("{}", trace!(("Failed to send message to reasoner at {:?}", args.address), err)); + std::process::exit(1); + }, + }; + + // OK, report + match verdict { + ReasonerResponse::Success => println!("{} {}", style("Reasoner says:").bold(), style("OK").bold().green()), + ReasonerResponse::Violated(reasons) => { + println!("{} {}", style("Reasoner says:").bold(), style("VIOLATION").bold().red()); + println!("Reason:"); + println!("{reasons}"); + println!(); + }, + } +} diff --git a/examples/eflint/defs.eflint b/examples/eflint/defs.eflint deleted file mode 100644 index c7a93f1..0000000 --- a/examples/eflint/defs.eflint +++ /dev/null @@ -1,242 +0,0 @@ -// String-identified primitives -Fact workflow. -Fact task. -Fact dataset. -Fact domain. -Fact tag. -Fact signature. - -Fact user. -Fact domain Identified by user. - -// Relations (populate these) -Fact is-valid Identified by signature. -Fact metadata Identified by domain * tag * signature. - -Fact workflow-metadata Identified by workflow * metadata. -Fact task-metadata Identified by task * metadata. -Fact dataset-metadata Identified by dataset * metadata. -Fact domain-metadata Identified by domain * metadata. - -Fact task-in Identified by workflow * task. -Fact result Identified by workflow * dataset. -Fact result-recipient Identified by result * user. - -// Workflow can have only one result-recipient but multiple results (commits) -Fact recipient Identified by dataset * user - Holds when result(workflow, dataset) && result-recipient(result(workflow, dataset), user) - -// Missing derive -// 1. metadata, klopt metadata bij func ook met dataset (bijv diabetes onderzoek) ook task en workflow en user. Alle combi's -// 2. Waar komt func vandaan die op op mijn dataset - -// dataset is the actual code of the function not a dataset the function operates on -Fact function Identified by task * dataset. -Fact argument Identified by task * dataset. -Fact output Identified by task * dataset. -Fact task-at Identified by task * domain. - -// Where the input dataset resides -Fact data-at Identified by dataset * user. - -// Utilities (read only) -Fact input Identified by task * dataset - Holds when function(task, dataset), argument(task, dataset). - -// Input ends up as output -Fact input-to-output Identified by dataset1 * dataset2 - Holds when - (Exists task: task When (argument(task, dataset1) && output(task, dataset2) && dataset1 != dataset2)) - -Fact input-used-for-output Identified by dataset1 * dataset2 - Holds when - input-to-output(dataset1, dataset2) - || - ( - Exists dataset3: dataset3 When input-to-output(dataset1, dataset3) && input-used-for-output(dataset3,dataset2) - ). - -// TODO also include every dataset that is used as input for the produced result -Fact dataset-transferred-to Identified by dataset * user - Holds when ( - ( - Exists task, dataset2, user : task When ( - ( - input-used-for-output(dataset, dataset2) - && - (argument(task, dataset2) || output(task, dataset2)) - && - task-at(task, domain(user)) - ) - ) - ) - || - ( - Exists workflow: workflow When result(workflow, dataset) && result-recipient(result(workflow, dataset), user) - ) - || - ( - Exists dataset2, recipient: dataset2 When Holds(recipient) && ((recipient.user) == user) && ((recipient.dataset) == dataset2) && (input-used-for-output(dataset, dataset2)) - ) - ). - -// Fact dataset-transferred-to Identified by dataset * user -// Holds when ( -// Exists task, dataset2 : task When ( -// ( -// input-used-for-output(dataset, dataset2) -// && -// (argument(task, dataset2) || output(task, dataset2)) -// && -// task-at(task, domain(user)) -// ) -// || -// ( -// output(task, dataset) && task-at(task, domain(user)) -// ) -// ) || ( -// Exists workflow: workflow When result(workflow, dataset) && result-recipient(result(workflow, dataset), user) -// ) || ( -// Exists dataset2: dataset2 When (recipient.user) == user && (recipient.dataset) == dataset2 && input-used-for-output(dataset, dataset2) -// ) -// ). - -// Checker's policy reasoner connector interface -Fact task-to-execute Identified by task. -Fact dataset-to-transfer Identified by dataset * task. -Fact result-to-transfer Identified by workflow. -Fact workflow-to-exectue Identified by workflow. - -// Constraints / sanity checks - -// Exactly one function per task -Invariant one-function-per-task When - (Forall task: Count(Foreach dataset: dataset When Holds(function(task, dataset))) == 1). -// Duty one-function-per-task -// Holder task -// Claimant task // dummy -// Holds when task -// Violated when -// Count(Foreach dataset: dataset When Holds(function(task, dataset))) != 1. - -// // Zero or one output per task -Invariant zero-or-one-output-per-task When - (Forall task: Count(Foreach dataset: dataset When Holds(output(task, dataset))) < 2). -// Duty zero-or-one-output-per-task -// Holder task -// Claimant task // dummy -// Holds when task -// Violated when -// Count(Foreach dataset: dataset When Holds(output(task, dataset))) >= 2. - -// // // Zero or one recipient per workflow -Invariant zero-or-one-result-recipients-per-workflow When - (Forall workflow: (Count(Foreach dataset, user : True When result-recipient(result(workflow, dataset), user)) < 2)). - -// // // // Output dataset can't be used as a function's code -// Invariant output-dataset-cant-be-function-dataset When -// Not( -// Exists dataset, task: function(task, dataset) && output(task, dataset) -// ). - -+task-to-execute(X). - -// // // A requested to be exectued task should exists within the current workflow -Invariant requested-task-should-exist When - // (Forall task-to-execute: Holds(task-to-execute.task)). - (Forall task-to-execute: Holds(task-to-execute.task) When Holds(task-to-execute)). - -// // // Every task should be part of a workflow -Invariant task-should-be-part-of-workflow When (Forall task: (Exists workflow: task-in(workflow, task))). - - - -// // Fill knowledge base - -// // Users -// +user(Hospitala). -// +domain(user(Hospitala)). -// +user(Hospitalb). -// +domain(user(Hospitalb)). -// +user(Hospitalc). -// +domain(user(Hospitalc)). - -// +user(Scientista). - -// // Functions -// +dataset(Funca). - -// // Datasets (inputs) -// +dataset(Ina). -// +dataset(Inb). - -// // Model Workflow -// +workflow(Currentworkflow). - -// // task1 -// +task(X). -// +task-in(Currentworkflow, X). -// +function(X, Funca). -// // +function(X, Ina). -// +argument(X, Ina). -// +dataset(Outa). -// +output(X, Outa). -// +task-at(X, domain(Hospitala)). - -// // task2 -// +task(Y). -// +task-in(Currentworkflow, Y). -// +function(Y, Funca). -// +argument(Y, Outa). -// +argument(Y, Inb). -// +dataset(Outb). -// +output(Y, Outb). -// +task-at(Y, domain(Hospitalb)). - -// // task3 -// +task(Z). -// +task-in(Currentworkflow, Z). -// +function(Z, Funca). -// +argument(Z, Outb). -// +dataset(Outc). -// +output(Z, Outc). -// +task-at(Z, domain(Hospitalc)). - -// +result(Currentworkflow, Outc). -// +result(Currentworkflow, Outb). -// // +result-recipient(result(Currentworkflow, Outc), Scientista). -// +result-recipient(result(Currentworkflow, Outb), Scientista). - -// // // Action that is performed / checked by the checker - -// // // +task-to-execute(X). - -// // // Policy engineers policy code - -// // // Alleen taak draaien synth dataset - -// // // ?( -// // // Not( -// // // Exists task-to-execute: Holds( -// // // task-at(task-to-execute.task, domain(Hospitalb)) -// // // ) -// // // ) -// // // ). - -// // // Various Checks - -// // // Invariant foo When False. -// // // ?input-used-for-output(C,E). -// // // ?input-used-for-output(B,E). - - - -// // // ?--input-to-output. -// // // ?--input-used-for-output. -// // // ?--dataset-transferred-to. -// // // ?one-function-per-task. -// // // ?zero-or-one-output-per-task. -// // // ?output-dataset-cant-be-function-dataset. -// // // ?requested-task-should-exist. -// // // ?task-should-be-part-of-workflow. -// // // ?zero-or-one-result-recipients-per-workflow. diff --git a/examples/eflint/example_workflow.eflint b/examples/eflint/example_workflow.eflint deleted file mode 100644 index d93752f..0000000 --- a/examples/eflint/example_workflow.eflint +++ /dev/null @@ -1,70 +0,0 @@ -// String-identified primitives -Fact workflow. -Fact task. -Fact dataset. -Fact domain. -Fact tag. -Fact signature. - -// Relations (populate these) -Fact is-valid Identified by signature. -Fact metadata Identified by domain * tag * signature. - -Fact workflow-metadata Identified by workflow * metadata. -Fact task-metadata Identified by task * metadata. -Fact dataset-metadata Identified by dataset * metadata. -Fact domain-metadata Identified by domain * metadata. - -Fact task-in Identified by workflow * task. -Fact goal-of Identified by workflow * dataset. - -Fact function Identified by task * dataset. -Fact argument Identified by task * dataset. -Fact output Identified by task * dataset. -Fact at Identified by task * domain. - -// Utilities (read only) -Fact input Identified by task * dataset - Derived from function, argument. - -Fact allowed Identified by task. - -//////////////// EXAMPLE //////////// - -+task-in(Workflow1,Task1). -+function(Task1,F). -+function(Task1,G). -+argument(Task1,X). -+argument(Task1,Y). -+output(Task1,Y). - -?input(Task1, F). - -//////////////// BONUS STATEFUL STUFF ///////// - -Fact agent. - -Fact available Identified by dataset. -Act compute - Actor agent - Recipient task - Holds when (Forall input: - // when all inputs are available - available(input.dataset) - || input.task != task - ) - Creates (Foreach output: - available(output.dataset) - Where output.task == task - ). - - -//////////////// EXAMPLE //////////// - -+available(X). --available(X). -~available(X). -+available(F). -compute(Amy,Task1). -?available(Y). // succeeds - diff --git a/examples/eflint/example_workflow_2.eflint b/examples/eflint/example_workflow_2.eflint deleted file mode 100644 index 5237024..0000000 --- a/examples/eflint/example_workflow_2.eflint +++ /dev/null @@ -1,248 +0,0 @@ -// String-identified primitives -Fact workflow. -Fact task. -Fact dataset. -Fact domain. -Fact tag. -Fact signature. - -Fact user. -Fact domain Identified by user. - -Fact commit. -Fact commits Identified by commit * dataset * dataset. - -// Relations (populate these) -Fact is-valid Identified by signature. - -Placeholder owner For domain. -Placeholder assigner For domain. -Fact metadata Identified by owner * tag * assigner * signature. - -Fact workflow-metadata Identified by workflow * metadata. -Fact task-metadata Identified by task * metadata. -Fact dataset-metadata Identified by dataset * metadata. -Fact domain-metadata Identified by domain * metadata. - -Fact task-in Identified by workflow * task. -Fact result Identified by workflow * dataset. -Fact result-recipient Identified by result * user. - -// Workflow can have only one result-recipient but multiple results (commits) -Fact recipient Identified by dataset * user - Holds when result(workflow, dataset) && result-recipient(result(workflow, dataset), user) - -// Missing derive -// 1. metadata, klopt metadata bij func ook met dataset (bijv diabetes onderzoek) ook task en workflow en user. Alle combi's -// 2. Waar komt func vandaan die op op mijn dataset - -// dataset is the actual code of the function not a dataset the function operates on -Fact function Identified by task * string * dataset. -Fact argument Identified by task * dataset. -Fact output Identified by task * dataset. -Fact task-at Identified by task * domain. - -// Where the input dataset resides -Fact data-at Identified by dataset * user. - -// Utilities (read only) -Fact input Identified by task * dataset - Holds when function(task, dataset), argument(task, dataset). - -// Input ends up as output -Fact input-to-output Identified by dataset1 * dataset2 - Holds when - (Exists task: task When (argument(task, dataset1) && output(task, dataset2) && dataset1 != dataset2)) - -Fact input-used-for-output Identified by dataset1 * dataset2 - Holds when - input-to-output(dataset1, dataset2) - || - ( - Exists dataset3: dataset3 When input-to-output(dataset1, dataset3) && input-used-for-output(dataset3,dataset2) - ). - -// TODO also include every dataset that is used as input for the produced result -Fact dataset-transferred-to Identified by dataset * user - Holds when ( - ( - Exists task, dataset2, user : task When ( - ( - input-used-for-output(dataset, dataset2) - && - (argument(task, dataset2) || output(task, dataset2)) - && - task-at(task, domain(user)) - ) - ) - ) - || - ( - Exists workflow: workflow When result(workflow, dataset) && result-recipient(result(workflow, dataset), user) - ) - || - ( - Exists dataset2, recipient: dataset2 When Holds(recipient) && ((recipient.user) == user) && ((recipient.dataset) == dataset2) && (input-used-for-output(dataset, dataset2)) - ) - ). - -// Fact dataset-transferred-to Identified by dataset * user -// Holds when ( -// Exists task, dataset2 : task When ( -// ( -// input-used-for-output(dataset, dataset2) -// && -// (argument(task, dataset2) || output(task, dataset2)) -// && -// task-at(task, domain(user)) -// ) -// || -// ( -// output(task, dataset) && task-at(task, domain(user)) -// ) -// ) || ( -// Exists workflow: workflow When result(workflow, dataset) && result-recipient(result(workflow, dataset), user) -// ) || ( -// Exists dataset2: dataset2 When (recipient.user) == user && (recipient.dataset) == dataset2 && input-used-for-output(dataset, dataset2) -// ) -// ). - -// Checker's policy reasoner connector interface -Fact task-to-execute Identified by task. -Fact dataset-to-transfer Identified by dataset * task. -Fact result-to-transfer Identified by workflow. -Fact workflow-to-exectue Identified by workflow. - -// Constraints / sanity checks - -// Exactly one function per task -Invariant one-function-per-task When - (Forall task: Count(Foreach dataset: dataset When Holds(function(task, dataset))) == 1). -// Duty one-function-per-task -// Holder task -// Claimant task // dummy -// Holds when task -// Violated when -// Count(Foreach dataset: dataset When Holds(function(task, dataset))) != 1. - -// // Zero or one output per task -Invariant zero-or-one-output-per-task When - (Forall task: Count(Foreach dataset: dataset When Holds(output(task, dataset))) < 2). -// Duty zero-or-one-output-per-task -// Holder task -// Claimant task // dummy -// Holds when task -// Violated when -// Count(Foreach dataset: dataset When Holds(output(task, dataset))) >= 2. - -// // // Zero or one recipient per workflow -Invariant zero-or-one-result-recipients-per-workflow When - (Forall workflow: (Count(Foreach dataset, user : True When result-recipient(result(workflow, dataset), user)) < 2)). - -// // // // Output dataset can't be used as a function's code -// Invariant output-dataset-cant-be-function-dataset When -// Not( -// Exists dataset, task: function(task, dataset) && output(task, dataset) -// ). - -+task-to-execute(X). - -// // // A requested to be exectued task should exists within the current workflow -Invariant requested-task-should-exist When - // (Forall task-to-execute: Holds(task-to-execute.task)). - (Forall task-to-execute: Holds(task-to-execute.task) When Holds(task-to-execute)). - -// // // Every task should be part of a workflow -Invariant task-should-be-part-of-workflow When (Forall task: (Exists workflow: task-in(workflow, task))). - - - -// // Fill knowledge base - -// // Users -// +user(Hospitala). -// +domain(user(Hospitala)). -// +user(Hospitalb). -// +domain(user(Hospitalb)). -// +user(Hospitalc). -// +domain(user(Hospitalc)). - -// +user(Scientista). - -// // Functions -// +dataset(Funca). - -// // Datasets (inputs) -// +dataset(Ina). -// +dataset(Inb). - -// // Model Workflow -// +workflow(Currentworkflow). - -// // task1 -// +task(X). -// +task-in(Currentworkflow, X). -// +function(X, Funca). -// // +function(X, Ina). -// +argument(X, Ina). -// +dataset(Outa). -// +output(X, Outa). -// +task-at(X, domain(Hospitala)). - -// // task2 -// +task(Y). -// +task-in(Currentworkflow, Y). -// +function(Y, Funca). -// +argument(Y, Outa). -// +argument(Y, Inb). -// +dataset(Outb). -// +output(Y, Outb). -// +task-at(Y, domain(Hospitalb)). - -// // task3 -// +task(Z). -// +task-in(Currentworkflow, Z). -// +function(Z, Funca). -// +argument(Z, Outb). -// +dataset(Outc). -// +output(Z, Outc). -// +task-at(Z, domain(Hospitalc)). - -// +result(Currentworkflow, Outc). -// +result(Currentworkflow, Outb). -// // +result-recipient(result(Currentworkflow, Outc), Scientista). -// +result-recipient(result(Currentworkflow, Outb), Scientista). - -// // // Action that is performed / checked by the checker - -// // // +task-to-execute(X). - -// // // Policy engineers policy code - -// // // Alleen taak draaien synth dataset - -// // // ?( -// // // Not( -// // // Exists task-to-execute: Holds( -// // // task-at(task-to-execute.task, domain(Hospitalb)) -// // // ) -// // // ) -// // // ). - -// // // Various Checks - -// // // Invariant foo When False. -// // // ?input-used-for-output(C,E). -// // // ?input-used-for-output(B,E). - - - -// // // ?--input-to-output. -// // // ?--input-used-for-output. -// // // ?--dataset-transferred-to. -// // // ?one-function-per-task. -// // // ?zero-or-one-output-per-task. -// // // ?output-dataset-cant-be-function-dataset. -// // // ?requested-task-should-exist. -// // // ?task-should-be-part-of-workflow. -// // // ?zero-or-one-result-recipients-per-workflow. \ No newline at end of file diff --git a/examples/eflint/higher_level.eflint b/examples/eflint/higher_level.eflint deleted file mode 100644 index 8fad260..0000000 --- a/examples/eflint/higher_level.eflint +++ /dev/null @@ -1,4 +0,0 @@ -#require higher_level.eflint - - - diff --git a/examples/eflint_json/interface.json b/examples/eflint_json/interface.json deleted file mode 100644 index dd260b5..0000000 --- a/examples/eflint_json/interface.json +++ /dev/null @@ -1,523 +0,0 @@ -{ - "version": "0.1.0", - "kind": "phrases", - "phrases": [ - { - "kind": "afact", - "name": "user", - "type": "String" - }, - { - "kind": "cfact", - "name": "domain", - "identified-by": [ - "user" - ] - }, - { - "kind": "afact", - "name": "asset", - "type": "String" - }, - { - "kind": "cfact", - "name": "asset-access", - "identified-by": [ - "asset", - "user" - ] - }, - { - "kind": "cfact", - "name": "code", - "identified-by": [ - "asset" - ] - }, - { - "kind": "afact", - "name": "workflow", - "type": "String" - }, - { - "kind": "cfact", - "name": "workflow-result", - "identified-by": [ - "workflow", - "asset" - ] - }, - { - "kind": "cfact", - "name": "workflow-result-recipient", - "identified-by": [ - "workflow-result", - "user" - ] - }, - { - "kind": "cfact", - "name": "node", - "identified-by": [ - "workflow", - "string" - ] - }, - { - "kind": "cfact", - "name": "node-input", - "identified-by": [ - "node", - "asset" - ] - }, - { - "kind": "cfact", - "name": "node-input-ensured", - "identified-by": [ - "node-input" - ] - }, - { - "kind": "cfact", - "name": "node-output", - "identified-by": [ - "node", - "asset" - ] - }, - { - "kind": "cfact", - "name": "link", - "identified-by": [ - "node-output", - "node-input" - ] - }, - { - "kind": "cfact", - "name": "link-branch", - "identified-by": [ - "node-output", - "node-input" - ] - }, - { - "kind": "cfact", - "name": "task", - "identified-by": [ - "node" - ] - }, - { - "kind": "cfact", - "name": "task-code", - "identified-by": [ - "task", - "code" - ] - }, - { - "kind": "cfact", - "name": "task-at", - "identified-by": [ - "task", - "domain" - ] - }, - { - "kind": "cfact", - "name": "commit", - "identified-by": [ - "node" - ] - }, - { - "kind": "cfact", - "name": "signature", - "identified-by": [ - "user", - "string" - ] - }, - { - "kind": "cfact", - "name": "signature-valid", - "identified-by": [ - "signature" - ] - }, - { - "kind": "cfact", - "name": "tag", - "identified-by": [ - "user", - "string" - ] - }, - { - "kind": "cfact", - "name": "metadata", - "identified-by": [ - "tag", - "signature" - ] - }, - { - "kind": "cfact", - "name": "workflow-metadata", - "identified-by": [ - "workflow", - "metadata" - ] - }, - { - "kind": "cfact", - "name": "node-metadata", - "identified-by": [ - "node", - "metadata" - ] - }, - { - "kind": "cfact", - "name": "asset-metadata", - "identified-by": [ - "asset", - "metadata" - ] - }, - { - "kind": "cfact", - "name": "user-metadata", - "identified-by": [ - "user", - "metadata" - ] - }, - { - "kind": "predicate", - "is-invariant": true, - "name": "workflow-at-most-one-result-recipient", - "expression": { - "iterator": "FORALL", - "binds": [ - "workflow" - ], - "expression": { - "operator": "LTE", - "operands": [ - { - "operator": "COUNT", - "operands": [ - { - "iterator": "FOREACH", - "binds": [ - "asset", - "user" - ], - "expression": { - "operator": "WHEN", - "operands": [ - { - "identifier": "workflow-result-recipient", - "operands": [ - { - "identifier": "workflow-result", - "operands": [ - [ - "workflow" - ], - [ - "asset" - ] - ] - }, - [ - "user" - ] - ] - }, - { - "operator": "HOLDS", - "operands": [ - { - "identifier": "workflow-result-recipient", - "operands": [ - { - "identifier": "workflow-result", - "operands": [ - [ - "workflow" - ], - [ - "asset" - ] - ] - }, - [ - "user" - ] - ] - } - ] - } - ] - } - } - ] - }, - 1 - ] - } - } - }, - { - "kind": "predicate", - "is-invariant": true, - "name": "node-at-most-one-output", - "expression": { - "iterator": "FORALL", - "binds": [ - "node" - ], - "expression": { - "operator": "LTE", - "operands": [ - { - "operator": "COUNT", - "operands": [ - { - "iterator": "FOREACH", - "binds": [ - "asset" - ], - "expression": { - "operator": "WHEN", - "operands": [ - { - "identifier": "node-output", - "operands": [ - [ - "node" - ], - [ - "asset" - ] - ] - }, - { - "operator": "HOLDS", - "operands": [ - { - "identifier": "node-output", - "operands": [ - [ - "node" - ], - [ - "asset" - ] - ] - } - ] - } - ] - } - } - ] - }, - 1 - ] - } - } - }, - { - "kind": "predicate", - "is-invariant": true, - "name": "task-at-most-one-code", - "expression": { - "iterator": "FORALL", - "binds": [ - "task" - ], - "expression": { - "operator": "LTE", - "operands": [ - { - "operator": "COUNT", - "operands": [ - { - "iterator": "FOREACH", - "binds": [ - "code" - ], - "expression": { - "operator": "WHEN", - "operands": [ - { - "identifier": "task-code", - "operands": [ - [ - "task" - ], - [ - "code" - ] - ] - }, - { - "operator": "HOLDS", - "operands": [ - { - "identifier": "task-code", - "operands": [ - [ - "task" - ], - [ - "code" - ] - ] - } - ] - } - ] - } - } - ] - }, - 1 - ] - } - } - }, - { - "kind": "predicate", - "is-invariant": true, - "name": "task-exactly-one-at", - "expression": { - "iterator": "FORALL", - "binds": [ - "task" - ], - "expression": { - "operator": "EQ", - "operands": [ - { - "operator": "COUNT", - "operands": [ - { - "iterator": "FOREACH", - "binds": [ - "domain" - ], - "expression": { - "operator": "WHEN", - "operands": [ - { - "identifier": "task-at", - "operands": [ - [ - "task" - ], - [ - "domain" - ] - ] - }, - { - "operator": "HOLDS", - "operands": [ - { - "identifier": "task-at", - "operands": [ - [ - "task" - ], - [ - "domain" - ] - ] - } - ] - } - ] - } - } - ] - }, - 1 - ] - } - } - }, - { - "kind": "predicate", - "is-invariant": true, - "name": "commit-at-most-one-input-ensured", - "expression": { - "iterator": "FORALL", - "binds": [ - "commit" - ], - "expression": { - "operator": "LTE", - "operands": [ - { - "operator": "COUNT", - "operands": [ - { - "iterator": "FOREACH", - "binds": [ - "asset" - ], - "expression": { - "operator": "WHEN", - "operands": [ - { - "identifier": "node-input-ensure", - "operands": [ - { - "parameter": "node", - "operand": [ - "commit" - ] - }, - [ - "asset" - ] - ] - }, - { - "operator": "HOLDS", - "operands": [ - { - "identifier": "node-input-ensure", - "operands": [ - { - "parameter": "node", - "operand": [ - "commit" - ] - }, - [ - "asset" - ] - ] - } - ] - } - ] - } - } - ] - }, - 1 - ] - } - } - } - ], - "updates": true -} diff --git a/examples/eflint_reasonerconn/eflint.rs b/examples/eflint_reasonerconn/eflint.rs deleted file mode 100644 index 9a6ecb3..0000000 --- a/examples/eflint_reasonerconn/eflint.rs +++ /dev/null @@ -1,22 +0,0 @@ -use std::fs; - -use policy::Policy; -use reasonerconn::connector::ReasonerConnector; -use reasonerconn::eflint::EFlintReasonerConnector; -use state_resolver::State; -use workflow::spec::Workflow; - -#[tokio::main] -async fn main() { - let conn = EFlintReasonerConnector::new("http://localhost:8080".into()); - - let policy = fs::read_to_string("/examples/eflint_reasonerconn/example-policy.json").unwrap(); - let workflow = fs::read_to_string("/examples/eflint_reasonerconn/example-workflow.json").unwrap(); - let state = fs::read_to_string("/examples/eflint_reasonerconn/example-state.json").unwrap(); - - let workflow: Workflow = serde_json::from_str(&workflow).unwrap(); - let policy: Policy = serde_json::from_str(&policy).unwrap(); - let state: State = serde_json::from_str(&state).unwrap(); - - conn.execute_task(policy, state, workflow, "X".into()).await.unwrap(); -} diff --git a/examples/eflint_reasonerconn/example-policy-content.json b/examples/eflint_reasonerconn/example-policy-content.json deleted file mode 100644 index 38e1ac6..0000000 --- a/examples/eflint_reasonerconn/example-policy-content.json +++ /dev/null @@ -1,888 +0,0 @@ -[ - { - "reasoner": "eflint", - "reasoner_version": "0.1.0", - "content": [ - { - "kind": "afact", - "name": "workflow", - "type": "String" - }, - { - "kind": "afact", - "name": "task", - "type": "String" - }, - { - "kind": "afact", - "name": "dataset", - "type": "String" - }, - { - "kind": "afact", - "name": "domain", - "type": "String" - }, - { - "kind": "afact", - "name": "tag", - "type": "String" - }, - { - "kind": "afact", - "name": "signature", - "type": "String" - }, - { - "kind": "afact", - "name": "user", - "type": "String" - }, - { - "kind": "cfact", - "name": "domain", - "identified-by": [ - "user" - ] - }, - { - "kind": "afact", - "name": "commit", - "type": "String" - }, - { - "kind": "cfact", - "name": "commits", - "identified-by": [ - "commit", - "dataset", - "dataset" - ] - }, - { - "kind": "cfact", - "name": "is-valid", - "identified-by": [ - "signature" - ] - }, - { - "kind": "placeholder", - "name": [ - "owner" - ], - "for": "domain" - }, - { - "kind": "placeholder", - "name": [ - "assigner" - ], - "for": "domain" - }, - { - "kind": "cfact", - "name": "metadata", - "identified-by": [ - "owner", - "tag", - "assigner", - "signature" - ] - }, - { - "kind": "cfact", - "name": "workflow-metadata", - "identified-by": [ - "workflow", - "metadata" - ] - }, - { - "kind": "cfact", - "name": "task-metadata", - "identified-by": [ - "task", - "metadata" - ] - }, - { - "kind": "cfact", - "name": "dataset-metadata", - "identified-by": [ - "dataset", - "metadata" - ] - }, - { - "kind": "cfact", - "name": "domain-metadata", - "identified-by": [ - "domain", - "metadata" - ] - }, - { - "kind": "cfact", - "name": "task-in", - "identified-by": [ - "workflow", - "task" - ] - }, - { - "kind": "cfact", - "name": "result", - "identified-by": [ - "workflow", - "dataset" - ] - }, - { - "kind": "cfact", - "name": "result-recipient", - "identified-by": [ - "result", - "user" - ] - }, - { - "kind": "cfact", - "name": "recipient", - "identified-by": [ - "dataset", - "user" - ], - "holds-when": [ - { - "operator": "AND", - "operands": [ - { - "identifier": "result", - "operands": [ - [ - "workflow" - ], - [ - "dataset" - ] - ] - }, - { - "identifier": "result-recipient", - "operands": [ - { - "identifier": "result", - "operands": [ - [ - "workflow" - ], - [ - "dataset" - ] - ] - }, - [ - "user" - ] - ] - } - ] - } - ] - }, - { - "kind": "cfact", - "name": "function", - "identified-by": [ - "task", - "string", - "dataset" - ] - }, - { - "kind": "cfact", - "name": "argument", - "identified-by": [ - "task", - "dataset" - ] - }, - { - "kind": "cfact", - "name": "output", - "identified-by": [ - "task", - "dataset" - ] - }, - { - "kind": "cfact", - "name": "task-at", - "identified-by": [ - "task", - "domain" - ] - }, - { - "kind": "cfact", - "name": "data-at", - "identified-by": [ - "dataset", - "user" - ] - }, - { - "kind": "cfact", - "name": "input", - "identified-by": [ - "task", - "dataset" - ], - "holds-when": [ - { - "identifier": "function", - "operands": [ - [ - "task" - ], - [ - "dataset" - ] - ] - }, - { - "identifier": "argument", - "operands": [ - [ - "task" - ], - [ - "dataset" - ] - ] - } - ] - }, - { - "kind": "cfact", - "name": "input-to-output", - "identified-by": [ - "dataset1", - "dataset2" - ], - "holds-when": [ - { - "iterator": "EXISTS", - "binds": [ - "task" - ], - "expression": { - "operator": "WHEN", - "operands": [ - [ - "task" - ], - { - "operator": "AND", - "operands": [ - { - "identifier": "argument", - "operands": [ - [ - "task" - ], - [ - "dataset1" - ] - ] - }, - { - "operator": "AND", - "operands": [ - { - "identifier": "output", - "operands": [ - [ - "task" - ], - [ - "dataset2" - ] - ] - }, - { - "operator": "NEQ", - "operands": [ - [ - "dataset1" - ], - [ - "dataset2" - ] - ] - } - ] - } - ] - } - ] - } - } - ] - }, - { - "kind": "cfact", - "name": "input-used-for-output", - "identified-by": [ - "dataset1", - "dataset2" - ], - "holds-when": [ - { - "operator": "OR", - "operands": [ - { - "identifier": "input-to-output", - "operands": [ - [ - "dataset1" - ], - [ - "dataset2" - ] - ] - }, - { - "iterator": "EXISTS", - "binds": [ - "dataset3" - ], - "expression": { - "operator": "WHEN", - "operands": [ - [ - "dataset3" - ], - { - "operator": "AND", - "operands": [ - { - "identifier": "input-to-output", - "operands": [ - [ - "dataset1" - ], - [ - "dataset3" - ] - ] - }, - { - "identifier": "input-used-for-output", - "operands": [ - [ - "dataset3" - ], - [ - "dataset2" - ] - ] - } - ] - } - ] - } - } - ] - } - ] - }, - { - "kind": "cfact", - "name": "dataset-transferred-to", - "identified-by": [ - "dataset", - "user" - ], - "holds-when": [ - { - "operator": "OR", - "operands": [ - { - "iterator": "EXISTS", - "binds": [ - "task", - "dataset2", - "user" - ], - "expression": { - "operator": "WHEN", - "operands": [ - [ - "task" - ], - { - "operator": "AND", - "operands": [ - { - "identifier": "input-used-for-output", - "operands": [ - [ - "dataset" - ], - [ - "dataset2" - ] - ] - }, - { - "operator": "AND", - "operands": [ - { - "operator": "OR", - "operands": [ - { - "identifier": "argument", - "operands": [ - [ - "task" - ], - [ - "dataset2" - ] - ] - }, - { - "identifier": "output", - "operands": [ - [ - "task" - ], - [ - "dataset2" - ] - ] - } - ] - }, - { - "identifier": "task-at", - "operands": [ - [ - "task" - ], - { - "identifier": "domain", - "operands": [ - [ - "user" - ] - ] - } - ] - } - ] - } - ] - } - ] - } - }, - { - "operator": "OR", - "operands": [ - { - "iterator": "EXISTS", - "binds": [ - "workflow" - ], - "expression": { - "operator": "WHEN", - "operands": [ - [ - "workflow" - ], - { - "operator": "AND", - "operands": [ - { - "identifier": "result", - "operands": [ - [ - "workflow" - ], - [ - "dataset" - ] - ] - }, - { - "identifier": "result-recipient", - "operands": [ - { - "identifier": "result", - "operands": [ - [ - "workflow" - ], - [ - "dataset" - ] - ] - }, - [ - "user" - ] - ] - } - ] - } - ] - } - }, - { - "iterator": "EXISTS", - "binds": [ - "dataset2", - "recipient" - ], - "expression": { - "operator": "WHEN", - "operands": [ - [ - "dataset2" - ], - { - "operator": "AND", - "operands": [ - { - "operator": "HOLDS", - "operands": [ - [ - "recipient" - ] - ] - }, - { - "operator": "AND", - "operands": [ - { - "operator": "EQ", - "operands": [ - { - "parameter": "user", - "operand": [ - "recipient" - ] - }, - [ - "user" - ] - ] - }, - { - "operator": "AND", - "operands": [ - { - "operator": "EQ", - "operands": [ - { - "parameter": "dataset", - "operand": [ - "recipient" - ] - }, - [ - "dataset2" - ] - ] - }, - { - "identifier": "input-used-for-output", - "operands": [ - [ - "dataset" - ], - [ - "dataset2" - ] - ] - } - ] - } - ] - } - ] - } - ] - } - } - ] - } - ] - } - ] - }, - { - "kind": "cfact", - "name": "task-to-execute", - "identified-by": [ - "task" - ] - }, - { - "kind": "cfact", - "name": "dataset-to-transfer", - "identified-by": [ - "dataset", - "task" - ] - }, - { - "kind": "cfact", - "name": "result-to-transfer", - "identified-by": [ - "workflow" - ] - }, - { - "kind": "cfact", - "name": "workflow-to-exectue", - "identified-by": [ - "workflow" - ] - }, - { - "kind": "predicate", - "is-invariant": true, - "name": "one-function-per-task", - "expression": { - "iterator": "FORALL", - "binds": [ - "task" - ], - "expression": { - "operator": "EQ", - "operands": [ - { - "operator": "COUNT", - "operands": [ - { - "iterator": "FOREACH", - "binds": [ - "dataset" - ], - "expression": { - "operator": "WHEN", - "operands": [ - [ - "dataset" - ], - { - "operator": "HOLDS", - "operands": [ - { - "identifier": "function", - "operands": [ - [ - "task" - ], - [ - "dataset" - ] - ] - } - ] - } - ] - } - } - ] - }, - 1 - ] - } - } - }, - { - "kind": "predicate", - "is-invariant": true, - "name": "zero-or-one-output-per-task", - "expression": { - "iterator": "FORALL", - "binds": [ - "task" - ], - "expression": { - "operator": "LT", - "operands": [ - { - "operator": "COUNT", - "operands": [ - { - "iterator": "FOREACH", - "binds": [ - "dataset" - ], - "expression": { - "operator": "WHEN", - "operands": [ - [ - "dataset" - ], - { - "operator": "HOLDS", - "operands": [ - { - "identifier": "output", - "operands": [ - [ - "task" - ], - [ - "dataset" - ] - ] - } - ] - } - ] - } - } - ] - }, - 2 - ] - } - } - }, - { - "kind": "predicate", - "is-invariant": true, - "name": "zero-or-one-result-recipients-per-workflow", - "expression": { - "iterator": "FORALL", - "binds": [ - "workflow" - ], - "expression": { - "operator": "LT", - "operands": [ - { - "operator": "COUNT", - "operands": [ - { - "iterator": "FOREACH", - "binds": [ - "dataset", - "user" - ], - "expression": { - "operator": "WHEN", - "operands": [ - true, - { - "identifier": "result-recipient", - "operands": [ - { - "identifier": "result", - "operands": [ - [ - "workflow" - ], - [ - "dataset" - ] - ] - }, - [ - "user" - ] - ] - } - ] - } - } - ] - }, - 2 - ] - } - } - }, - { - "kind": "create", - "operand": { - "identifier": "task-to-execute", - "operands": [ - "X" - ] - } - }, - { - "kind": "predicate", - "is-invariant": true, - "name": "requested-task-should-exist", - "expression": { - "iterator": "FORALL", - "binds": [ - "task-to-execute" - ], - "expression": { - "operator": "WHEN", - "operands": [ - { - "operator": "HOLDS", - "operands": [ - { - "parameter": "task", - "operand": [ - "task-to-execute" - ] - } - ] - }, - { - "operator": "HOLDS", - "operands": [ - [ - "task-to-execute" - ] - ] - } - ] - } - } - }, - { - "kind": "predicate", - "is-invariant": true, - "name": "task-should-be-part-of-workflow", - "expression": { - "iterator": "FORALL", - "binds": [ - "task" - ], - "expression": { - "iterator": "EXISTS", - "binds": [ - "workflow" - ], - "expression": { - "identifier": "task-in", - "operands": [ - [ - "workflow" - ], - [ - "task" - ] - ] - } - } - } - } - ] - } -] diff --git a/examples/eflint_reasonerconn/example-policy.json b/examples/eflint_reasonerconn/example-policy.json deleted file mode 100644 index adb0211..0000000 --- a/examples/eflint_reasonerconn/example-policy.json +++ /dev/null @@ -1,895 +0,0 @@ -{ - "description": "Dit is een omschrijving", - "creator": "Bas Kloosterman", - "created_at": "2023-10-25T19:51:24.225137+02:00", - "version": 1, - "version_description": "Dit is een versie omschrijving", - "content": [ - { - "reasoner": "eflint", - "reasoner_version": "0.1.0", - "content": [ - { - "kind": "afact", - "name": "workflow", - "type": "String" - }, - { - "kind": "afact", - "name": "task", - "type": "String" - }, - { - "kind": "afact", - "name": "dataset", - "type": "String" - }, - { - "kind": "afact", - "name": "domain", - "type": "String" - }, - { - "kind": "afact", - "name": "tag", - "type": "String" - }, - { - "kind": "afact", - "name": "signature", - "type": "String" - }, - { - "kind": "afact", - "name": "user", - "type": "String" - }, - { - "kind": "cfact", - "name": "domain", - "identified-by": [ - "user" - ] - }, - { - "kind": "afact", - "name": "commit", - "type": "String" - }, - { - "kind": "cfact", - "name": "commits", - "identified-by": [ - "commit", - "dataset", - "dataset" - ] - }, - { - "kind": "cfact", - "name": "is-valid", - "identified-by": [ - "signature" - ] - }, - { - "kind": "placeholder", - "name": [ - "owner" - ], - "for": "domain" - }, - { - "kind": "placeholder", - "name": [ - "assigner" - ], - "for": "domain" - }, - { - "kind": "cfact", - "name": "metadata", - "identified-by": [ - "owner", - "tag", - "assigner", - "signature" - ] - }, - { - "kind": "cfact", - "name": "workflow-metadata", - "identified-by": [ - "workflow", - "metadata" - ] - }, - { - "kind": "cfact", - "name": "task-metadata", - "identified-by": [ - "task", - "metadata" - ] - }, - { - "kind": "cfact", - "name": "dataset-metadata", - "identified-by": [ - "dataset", - "metadata" - ] - }, - { - "kind": "cfact", - "name": "domain-metadata", - "identified-by": [ - "domain", - "metadata" - ] - }, - { - "kind": "cfact", - "name": "task-in", - "identified-by": [ - "workflow", - "task" - ] - }, - { - "kind": "cfact", - "name": "result", - "identified-by": [ - "workflow", - "dataset" - ] - }, - { - "kind": "cfact", - "name": "result-recipient", - "identified-by": [ - "result", - "user" - ] - }, - { - "kind": "cfact", - "name": "recipient", - "identified-by": [ - "dataset", - "user" - ], - "holds-when": [ - { - "operator": "AND", - "operands": [ - { - "identifier": "result", - "operands": [ - [ - "workflow" - ], - [ - "dataset" - ] - ] - }, - { - "identifier": "result-recipient", - "operands": [ - { - "identifier": "result", - "operands": [ - [ - "workflow" - ], - [ - "dataset" - ] - ] - }, - [ - "user" - ] - ] - } - ] - } - ] - }, - { - "kind": "cfact", - "name": "function", - "identified-by": [ - "task", - "string", - "dataset" - ] - }, - { - "kind": "cfact", - "name": "argument", - "identified-by": [ - "task", - "dataset" - ] - }, - { - "kind": "cfact", - "name": "output", - "identified-by": [ - "task", - "dataset" - ] - }, - { - "kind": "cfact", - "name": "task-at", - "identified-by": [ - "task", - "domain" - ] - }, - { - "kind": "cfact", - "name": "data-at", - "identified-by": [ - "dataset", - "user" - ] - }, - { - "kind": "cfact", - "name": "input", - "identified-by": [ - "task", - "dataset" - ], - "holds-when": [ - { - "identifier": "function", - "operands": [ - [ - "task" - ], - [ - "dataset" - ] - ] - }, - { - "identifier": "argument", - "operands": [ - [ - "task" - ], - [ - "dataset" - ] - ] - } - ] - }, - { - "kind": "cfact", - "name": "input-to-output", - "identified-by": [ - "dataset1", - "dataset2" - ], - "holds-when": [ - { - "iterator": "EXISTS", - "binds": [ - "task" - ], - "expression": { - "operator": "WHEN", - "operands": [ - [ - "task" - ], - { - "operator": "AND", - "operands": [ - { - "identifier": "argument", - "operands": [ - [ - "task" - ], - [ - "dataset1" - ] - ] - }, - { - "operator": "AND", - "operands": [ - { - "identifier": "output", - "operands": [ - [ - "task" - ], - [ - "dataset2" - ] - ] - }, - { - "operator": "NEQ", - "operands": [ - [ - "dataset1" - ], - [ - "dataset2" - ] - ] - } - ] - } - ] - } - ] - } - } - ] - }, - { - "kind": "cfact", - "name": "input-used-for-output", - "identified-by": [ - "dataset1", - "dataset2" - ], - "holds-when": [ - { - "operator": "OR", - "operands": [ - { - "identifier": "input-to-output", - "operands": [ - [ - "dataset1" - ], - [ - "dataset2" - ] - ] - }, - { - "iterator": "EXISTS", - "binds": [ - "dataset3" - ], - "expression": { - "operator": "WHEN", - "operands": [ - [ - "dataset3" - ], - { - "operator": "AND", - "operands": [ - { - "identifier": "input-to-output", - "operands": [ - [ - "dataset1" - ], - [ - "dataset3" - ] - ] - }, - { - "identifier": "input-used-for-output", - "operands": [ - [ - "dataset3" - ], - [ - "dataset2" - ] - ] - } - ] - } - ] - } - } - ] - } - ] - }, - { - "kind": "cfact", - "name": "dataset-transferred-to", - "identified-by": [ - "dataset", - "user" - ], - "holds-when": [ - { - "operator": "OR", - "operands": [ - { - "iterator": "EXISTS", - "binds": [ - "task", - "dataset2", - "user" - ], - "expression": { - "operator": "WHEN", - "operands": [ - [ - "task" - ], - { - "operator": "AND", - "operands": [ - { - "identifier": "input-used-for-output", - "operands": [ - [ - "dataset" - ], - [ - "dataset2" - ] - ] - }, - { - "operator": "AND", - "operands": [ - { - "operator": "OR", - "operands": [ - { - "identifier": "argument", - "operands": [ - [ - "task" - ], - [ - "dataset2" - ] - ] - }, - { - "identifier": "output", - "operands": [ - [ - "task" - ], - [ - "dataset2" - ] - ] - } - ] - }, - { - "identifier": "task-at", - "operands": [ - [ - "task" - ], - { - "identifier": "domain", - "operands": [ - [ - "user" - ] - ] - } - ] - } - ] - } - ] - } - ] - } - }, - { - "operator": "OR", - "operands": [ - { - "iterator": "EXISTS", - "binds": [ - "workflow" - ], - "expression": { - "operator": "WHEN", - "operands": [ - [ - "workflow" - ], - { - "operator": "AND", - "operands": [ - { - "identifier": "result", - "operands": [ - [ - "workflow" - ], - [ - "dataset" - ] - ] - }, - { - "identifier": "result-recipient", - "operands": [ - { - "identifier": "result", - "operands": [ - [ - "workflow" - ], - [ - "dataset" - ] - ] - }, - [ - "user" - ] - ] - } - ] - } - ] - } - }, - { - "iterator": "EXISTS", - "binds": [ - "dataset2", - "recipient" - ], - "expression": { - "operator": "WHEN", - "operands": [ - [ - "dataset2" - ], - { - "operator": "AND", - "operands": [ - { - "operator": "HOLDS", - "operands": [ - [ - "recipient" - ] - ] - }, - { - "operator": "AND", - "operands": [ - { - "operator": "EQ", - "operands": [ - { - "parameter": "user", - "operand": [ - "recipient" - ] - }, - [ - "user" - ] - ] - }, - { - "operator": "AND", - "operands": [ - { - "operator": "EQ", - "operands": [ - { - "parameter": "dataset", - "operand": [ - "recipient" - ] - }, - [ - "dataset2" - ] - ] - }, - { - "identifier": "input-used-for-output", - "operands": [ - [ - "dataset" - ], - [ - "dataset2" - ] - ] - } - ] - } - ] - } - ] - } - ] - } - } - ] - } - ] - } - ] - }, - { - "kind": "cfact", - "name": "task-to-execute", - "identified-by": [ - "task" - ] - }, - { - "kind": "cfact", - "name": "dataset-to-transfer", - "identified-by": [ - "dataset", - "task" - ] - }, - { - "kind": "cfact", - "name": "result-to-transfer", - "identified-by": [ - "workflow" - ] - }, - { - "kind": "cfact", - "name": "workflow-to-exectue", - "identified-by": [ - "workflow" - ] - }, - { - "kind": "predicate", - "is-invariant": true, - "name": "one-function-per-task", - "expression": { - "iterator": "FORALL", - "binds": [ - "task" - ], - "expression": { - "operator": "EQ", - "operands": [ - { - "operator": "COUNT", - "operands": [ - { - "iterator": "FOREACH", - "binds": [ - "dataset" - ], - "expression": { - "operator": "WHEN", - "operands": [ - [ - "dataset" - ], - { - "operator": "HOLDS", - "operands": [ - { - "identifier": "function", - "operands": [ - [ - "task" - ], - [ - "dataset" - ] - ] - } - ] - } - ] - } - } - ] - }, - 1 - ] - } - } - }, - { - "kind": "predicate", - "is-invariant": true, - "name": "zero-or-one-output-per-task", - "expression": { - "iterator": "FORALL", - "binds": [ - "task" - ], - "expression": { - "operator": "LT", - "operands": [ - { - "operator": "COUNT", - "operands": [ - { - "iterator": "FOREACH", - "binds": [ - "dataset" - ], - "expression": { - "operator": "WHEN", - "operands": [ - [ - "dataset" - ], - { - "operator": "HOLDS", - "operands": [ - { - "identifier": "output", - "operands": [ - [ - "task" - ], - [ - "dataset" - ] - ] - } - ] - } - ] - } - } - ] - }, - 2 - ] - } - } - }, - { - "kind": "predicate", - "is-invariant": true, - "name": "zero-or-one-result-recipients-per-workflow", - "expression": { - "iterator": "FORALL", - "binds": [ - "workflow" - ], - "expression": { - "operator": "LT", - "operands": [ - { - "operator": "COUNT", - "operands": [ - { - "iterator": "FOREACH", - "binds": [ - "dataset", - "user" - ], - "expression": { - "operator": "WHEN", - "operands": [ - true, - { - "identifier": "result-recipient", - "operands": [ - { - "identifier": "result", - "operands": [ - [ - "workflow" - ], - [ - "dataset" - ] - ] - }, - [ - "user" - ] - ] - } - ] - } - } - ] - }, - 2 - ] - } - } - }, - { - "kind": "create", - "operand": { - "identifier": "task-to-execute", - "operands": [ - "X" - ] - } - }, - { - "kind": "predicate", - "is-invariant": true, - "name": "requested-task-should-exist", - "expression": { - "iterator": "FORALL", - "binds": [ - "task-to-execute" - ], - "expression": { - "operator": "WHEN", - "operands": [ - { - "operator": "HOLDS", - "operands": [ - { - "parameter": "task", - "operand": [ - "task-to-execute" - ] - } - ] - }, - { - "operator": "HOLDS", - "operands": [ - [ - "task-to-execute" - ] - ] - } - ] - } - } - }, - { - "kind": "predicate", - "is-invariant": true, - "name": "task-should-be-part-of-workflow", - "expression": { - "iterator": "FORALL", - "binds": [ - "task" - ], - "expression": { - "iterator": "EXISTS", - "binds": [ - "workflow" - ], - "expression": { - "identifier": "task-in", - "operands": [ - [ - "workflow" - ], - [ - "task" - ] - ] - } - } - } - } - ] - } - ] -} \ No newline at end of file diff --git a/examples/eflint_reasonerconn/example-result.json b/examples/eflint_reasonerconn/example-result.json deleted file mode 100644 index 4f32d17..0000000 --- a/examples/eflint_reasonerconn/example-result.json +++ /dev/null @@ -1,1252 +0,0 @@ -{ - "success": true, - "results": [ - { - "success": true, - "changes": [ - { - "kind": "afact", - "name": "workflow", - "type": "String" - } - ], - "triggers": [], - "violated": false, - "violations": [] - }, - { - "success": true, - "changes": [ - { - "kind": "afact", - "name": "task", - "type": "String" - } - ], - "triggers": [], - "violated": false, - "violations": [] - }, - { - "success": true, - "changes": [ - { - "kind": "afact", - "name": "dataset", - "type": "String" - } - ], - "triggers": [], - "violated": false, - "violations": [] - }, - { - "success": true, - "changes": [ - { - "kind": "afact", - "name": "domain", - "type": "String" - } - ], - "triggers": [], - "violated": false, - "violations": [] - }, - { - "success": true, - "changes": [ - { - "kind": "afact", - "name": "tag", - "type": "String" - } - ], - "triggers": [], - "violated": false, - "violations": [] - }, - { - "success": true, - "changes": [ - { - "kind": "afact", - "name": "signature", - "type": "String" - } - ], - "triggers": [], - "violated": false, - "violations": [] - }, - { - "success": true, - "changes": [ - { - "kind": "afact", - "name": "user", - "type": "String" - } - ], - "triggers": [], - "violated": false, - "violations": [] - }, - { - "success": true, - "changes": [ - { - "kind": "cfact", - "name": "domain", - "identified-by": [ - "user" - ] - } - ], - "triggers": [], - "violated": false, - "violations": [] - }, - { - "success": true, - "changes": [ - { - "kind": "cfact", - "name": "is-valid", - "identified-by": [ - "signature" - ] - } - ], - "triggers": [], - "violated": false, - "violations": [] - }, - { - "success": true, - "changes": [ - { - "kind": "cfact", - "name": "metadata", - "identified-by": [ - "domain", - "tag", - "signature" - ] - } - ], - "triggers": [], - "violated": false, - "violations": [] - }, - { - "success": true, - "changes": [ - { - "kind": "cfact", - "name": "workflow-metadata", - "identified-by": [ - "workflow", - "metadata" - ] - } - ], - "triggers": [], - "violated": false, - "violations": [] - }, - { - "success": true, - "changes": [ - { - "kind": "cfact", - "name": "task-metadata", - "identified-by": [ - "task", - "metadata" - ] - } - ], - "triggers": [], - "violated": false, - "violations": [] - }, - { - "success": true, - "changes": [ - { - "kind": "cfact", - "name": "dataset-metadata", - "identified-by": [ - "dataset", - "metadata" - ] - } - ], - "triggers": [], - "violated": false, - "violations": [] - }, - { - "success": true, - "changes": [ - { - "kind": "cfact", - "name": "domain-metadata", - "identified-by": [ - "domain", - "metadata" - ] - } - ], - "triggers": [], - "violated": false, - "violations": [] - }, - { - "success": true, - "changes": [ - { - "kind": "cfact", - "name": "task-in", - "identified-by": [ - "workflow", - "task" - ] - } - ], - "triggers": [], - "violated": false, - "violations": [] - }, - { - "success": true, - "changes": [ - { - "kind": "cfact", - "name": "result", - "identified-by": [ - "workflow", - "dataset" - ] - } - ], - "triggers": [], - "violated": false, - "violations": [] - }, - { - "success": true, - "changes": [ - { - "kind": "cfact", - "name": "result-recipient", - "identified-by": [ - "result", - "user" - ] - } - ], - "triggers": [], - "violated": false, - "violations": [] - }, - { - "success": true, - "changes": [ - { - "kind": "cfact", - "name": "recipient", - "holds-when": [ - { - "operator": "AND", - "operands": [ - { - "identifier": "result", - "operands": [ - [ - "workflow" - ], - [ - "dataset" - ] - ] - }, - { - "identifier": "result-recipient", - "operands": [ - { - "identifier": "result", - "operands": [ - [ - "workflow" - ], - [ - "dataset" - ] - ] - }, - [ - "user" - ] - ] - } - ] - } - ], - "identified-by": [ - "dataset", - "user" - ] - } - ], - "triggers": [], - "violated": false, - "violations": [] - }, - { - "success": true, - "changes": [ - { - "kind": "cfact", - "name": "function", - "identified-by": [ - "task", - "dataset" - ] - } - ], - "triggers": [], - "violated": false, - "violations": [] - }, - { - "success": true, - "changes": [ - { - "kind": "cfact", - "name": "argument", - "identified-by": [ - "task", - "dataset" - ] - } - ], - "triggers": [], - "violated": false, - "violations": [] - }, - { - "success": true, - "changes": [ - { - "kind": "cfact", - "name": "output", - "identified-by": [ - "task", - "dataset" - ] - } - ], - "triggers": [], - "violated": false, - "violations": [] - }, - { - "success": true, - "changes": [ - { - "kind": "cfact", - "name": "task-at", - "identified-by": [ - "task", - "domain" - ] - } - ], - "triggers": [], - "violated": false, - "violations": [] - }, - { - "success": true, - "changes": [ - { - "kind": "cfact", - "name": "data-at", - "identified-by": [ - "dataset", - "user" - ] - } - ], - "triggers": [], - "violated": false, - "violations": [] - }, - { - "success": true, - "changes": [ - { - "kind": "cfact", - "name": "input", - "holds-when": [ - { - "identifier": "function", - "operands": [ - [ - "task" - ], - [ - "dataset" - ] - ] - }, - { - "identifier": "argument", - "operands": [ - [ - "task" - ], - [ - "dataset" - ] - ] - } - ], - "identified-by": [ - "task", - "dataset" - ] - } - ], - "triggers": [], - "violated": false, - "violations": [] - }, - { - "success": true, - "changes": [ - { - "kind": "cfact", - "name": "input-to-output", - "holds-when": [ - { - "operands": null, - "iterator": "EXISTS", - "binds": [ - "task" - ], - "expression": { - "operator": "WHEN", - "operands": [ - [ - "task" - ], - { - "operator": "AND", - "operands": [ - { - "identifier": "argument", - "operands": [ - [ - "task" - ], - [ - "dataset1" - ] - ] - }, - { - "operator": "AND", - "operands": [ - { - "identifier": "output", - "operands": [ - [ - "task" - ], - [ - "dataset2" - ] - ] - }, - { - "operator": "NEQ", - "operands": [ - [ - "dataset1" - ], - [ - "dataset2" - ] - ] - } - ] - } - ] - } - ] - } - } - ], - "identified-by": [ - "dataset1", - "dataset2" - ] - } - ], - "triggers": [], - "violated": false, - "violations": [] - }, - { - "success": true, - "changes": [ - { - "kind": "cfact", - "name": "input-used-for-output", - "holds-when": [ - { - "operator": "OR", - "operands": [ - { - "identifier": "input-to-output", - "operands": [ - [ - "dataset1" - ], - [ - "dataset2" - ] - ] - }, - { - "operands": null, - "iterator": "EXISTS", - "binds": [ - "dataset3" - ], - "expression": { - "operator": "WHEN", - "operands": [ - [ - "dataset3" - ], - { - "operator": "AND", - "operands": [ - { - "identifier": "input-to-output", - "operands": [ - [ - "dataset1" - ], - [ - "dataset3" - ] - ] - }, - { - "identifier": "input-used-for-output", - "operands": [ - [ - "dataset3" - ], - [ - "dataset2" - ] - ] - } - ] - } - ] - } - } - ] - } - ], - "identified-by": [ - "dataset1", - "dataset2" - ] - } - ], - "triggers": [], - "violated": false, - "violations": [] - }, - { - "success": true, - "changes": [ - { - "kind": "cfact", - "name": "dataset-transferred-to", - "holds-when": [ - { - "operator": "OR", - "operands": [ - { - "operands": null, - "iterator": "EXISTS", - "binds": [ - "task", - "dataset2", - "user" - ], - "expression": { - "operator": "WHEN", - "operands": [ - [ - "task" - ], - { - "operator": "AND", - "operands": [ - { - "identifier": "input-used-for-output", - "operands": [ - [ - "dataset" - ], - [ - "dataset2" - ] - ] - }, - { - "operator": "AND", - "operands": [ - { - "operator": "OR", - "operands": [ - { - "identifier": "argument", - "operands": [ - [ - "task" - ], - [ - "dataset2" - ] - ] - }, - { - "identifier": "output", - "operands": [ - [ - "task" - ], - [ - "dataset2" - ] - ] - } - ] - }, - { - "identifier": "task-at", - "operands": [ - [ - "task" - ], - { - "identifier": "domain", - "operands": [ - [ - "user" - ] - ] - } - ] - } - ] - } - ] - } - ] - } - }, - { - "operator": "OR", - "operands": [ - { - "operands": null, - "iterator": "EXISTS", - "binds": [ - "workflow" - ], - "expression": { - "operator": "WHEN", - "operands": [ - [ - "workflow" - ], - { - "operator": "AND", - "operands": [ - { - "identifier": "result", - "operands": [ - [ - "workflow" - ], - [ - "dataset" - ] - ] - }, - { - "identifier": "result-recipient", - "operands": [ - { - "identifier": "result", - "operands": [ - [ - "workflow" - ], - [ - "dataset" - ] - ] - }, - [ - "user" - ] - ] - } - ] - } - ] - } - }, - { - "operands": null, - "iterator": "EXISTS", - "binds": [ - "dataset2", - "recipient" - ], - "expression": { - "operator": "WHEN", - "operands": [ - [ - "dataset2" - ], - { - "operator": "AND", - "operands": [ - { - "operator": "HOLDS", - "operands": [ - [ - "recipient" - ] - ] - }, - { - "operator": "AND", - "operands": [ - { - "operator": "EQ", - "operands": [ - { - "operands": null, - "operand": [ - "recipient" - ], - "parameter": "user" - }, - [ - "user" - ] - ] - }, - { - "operator": "AND", - "operands": [ - { - "operator": "EQ", - "operands": [ - { - "operands": null, - "operand": [ - "recipient" - ], - "parameter": "dataset" - }, - [ - "dataset2" - ] - ] - }, - { - "identifier": "input-used-for-output", - "operands": [ - [ - "dataset" - ], - [ - "dataset2" - ] - ] - } - ] - } - ] - } - ] - } - ] - } - } - ] - } - ] - } - ], - "identified-by": [ - "dataset", - "user" - ] - } - ], - "triggers": [], - "violated": false, - "violations": [] - }, - { - "success": true, - "changes": [ - { - "kind": "cfact", - "name": "task-to-execute", - "identified-by": [ - "task" - ] - } - ], - "triggers": [], - "violated": false, - "violations": [] - }, - { - "success": true, - "changes": [ - { - "kind": "cfact", - "name": "dataset-to-transfer", - "identified-by": [ - "dataset", - "task" - ] - } - ], - "triggers": [], - "violated": false, - "violations": [] - }, - { - "success": true, - "changes": [ - { - "kind": "cfact", - "name": "result-to-transfer", - "identified-by": [ - "workflow" - ] - } - ], - "triggers": [], - "violated": false, - "violations": [] - }, - { - "success": true, - "changes": [ - { - "kind": "cfact", - "name": "workflow-to-exectue", - "identified-by": [ - "workflow" - ] - } - ], - "triggers": [], - "violated": false, - "violations": [] - }, - { - "success": true, - "changes": [ - { - "kind": "predicate", - "expression": { - "operands": null, - "iterator": "FORALL", - "binds": [ - "task" - ], - "expression": { - "operator": "EQ", - "operands": [ - { - "operator": "COUNT", - "operands": [ - { - "operands": null, - "iterator": "FOREACH", - "binds": [ - "dataset" - ], - "expression": { - "operator": "WHEN", - "operands": [ - [ - "dataset" - ], - { - "operator": "HOLDS", - "operands": [ - { - "identifier": "function", - "operands": [ - [ - "task" - ], - [ - "dataset" - ] - ] - } - ] - } - ] - } - } - ] - }, - 1 - ] - } - }, - "name": "one-function-per-task", - "is-invariant": true - } - ], - "triggers": [], - "violated": true, - "violations": [ - { - "kind": "invariant", - "identifier": "one-function-per-task", - "operands": [] - } - ] - }, - { - "success": true, - "changes": [ - { - "kind": "predicate", - "expression": { - "operands": null, - "iterator": "FORALL", - "binds": [ - "task" - ], - "expression": { - "operator": "LT", - "operands": [ - { - "operator": "COUNT", - "operands": [ - { - "operands": null, - "iterator": "FOREACH", - "binds": [ - "dataset" - ], - "expression": { - "operator": "WHEN", - "operands": [ - [ - "dataset" - ], - { - "operator": "HOLDS", - "operands": [ - { - "identifier": "output", - "operands": [ - [ - "task" - ], - [ - "dataset" - ] - ] - } - ] - } - ] - } - } - ] - }, - 2 - ] - } - }, - "name": "zero-or-one-output-per-task", - "is-invariant": true - }, - { - "kind": "create", - "operand": { - "identifier": "zero-or-one-output-per-task", - "operands": [] - } - } - ], - "triggers": [], - "violated": true, - "violations": [ - { - "kind": "invariant", - "identifier": "one-function-per-task", - "operands": [] - } - ] - }, - { - "success": true, - "changes": [ - { - "kind": "predicate", - "expression": { - "operands": null, - "iterator": "FORALL", - "binds": [ - "workflow" - ], - "expression": { - "operator": "LT", - "operands": [ - { - "operator": "COUNT", - "operands": [ - { - "operands": null, - "iterator": "FOREACH", - "binds": [ - "dataset", - "user" - ], - "expression": { - "operator": "WHEN", - "operands": [ - true, - { - "identifier": "result-recipient", - "operands": [ - { - "identifier": "result", - "operands": [ - [ - "workflow" - ], - [ - "dataset" - ] - ] - }, - [ - "user" - ] - ] - } - ] - } - } - ] - }, - 2 - ] - } - }, - "name": "zero-or-one-result-recipients-per-workflow", - "is-invariant": true - }, - { - "kind": "create", - "operand": { - "identifier": "zero-or-one-result-recipients-per-workflow", - "operands": [] - } - } - ], - "triggers": [], - "violated": true, - "violations": [ - { - "kind": "invariant", - "identifier": "one-function-per-task", - "operands": [] - } - ] - }, - { - "success": true, - "changes": [ - { - "kind": "predicate", - "expression": { - "operands": null, - "iterator": "FORALL", - "binds": [ - "task-to-execute" - ], - "expression": { - "operator": "WHEN", - "operands": [ - { - "operator": "HOLDS", - "operands": [ - { - "operands": null, - "operand": [ - "task-to-execute" - ], - "parameter": "task" - } - ] - }, - { - "operator": "HOLDS", - "operands": [ - [ - "task-to-execute" - ] - ] - } - ] - } - }, - "name": "requested-task-should-exist", - "is-invariant": true - }, - { - "kind": "create", - "operand": { - "identifier": "requested-task-should-exist", - "operands": [] - } - } - ], - "triggers": [], - "violated": true, - "violations": [ - { - "kind": "invariant", - "identifier": "one-function-per-task", - "operands": [] - } - ] - }, - { - "success": true, - "changes": [ - { - "kind": "predicate", - "expression": { - "operands": null, - "iterator": "FORALL", - "binds": [ - "task" - ], - "expression": { - "operands": null, - "iterator": "EXISTS", - "binds": [ - "workflow" - ], - "expression": { - "identifier": "task-in", - "operands": [ - [ - "workflow" - ], - [ - "task" - ] - ] - } - } - }, - "name": "task-should-be-part-of-workflow", - "is-invariant": true - } - ], - "triggers": [], - "violated": true, - "violations": [ - { - "kind": "invariant", - "identifier": "one-function-per-task", - "operands": [] - }, - { - "kind": "invariant", - "identifier": "task-should-be-part-of-workflow", - "operands": [] - } - ] - }, - { - "success": true, - "changes": [ - { - "kind": "obfuscate", - "operand": { - "identifier": "requested-task-should-exist", - "operands": [] - } - }, - { - "kind": "create", - "operand": { - "identifier": "task-to-execute", - "operands": [ - { - "identifier": "task", - "operands": [ - "X" - ] - } - ] - } - } - ], - "triggers": [], - "violated": true, - "violations": [ - { - "kind": "invariant", - "identifier": "task-should-be-part-of-workflow", - "operands": [] - }, - { - "kind": "invariant", - "identifier": "one-function-per-task", - "operands": [] - }, - { - "kind": "invariant", - "identifier": "requested-task-should-exist", - "operands": [] - } - ] - } - ] -} \ No newline at end of file diff --git a/examples/eflint_reasonerconn/example-state.json b/examples/eflint_reasonerconn/example-state.json deleted file mode 100644 index 7d38c09..0000000 --- a/examples/eflint_reasonerconn/example-state.json +++ /dev/null @@ -1,36 +0,0 @@ -{ - "users": [ - { - "name": "amy" - }, - { - "name": "surf" - } - ], - "locations": [ - { - "name": "st_antonius" - }, - { - "name": "umc_utrecht" - }, - { - "name": "surf" - } - ], - "datasets": [ - { - "name": "epi-1.0.0", - "from": null - }, - { - "name": "st_antonius_ect", - "from": null - }, - { - "name": "umc_utrecht_ect", - "from": null - } - ], - "functions": [] -} \ No newline at end of file diff --git a/examples/eflint_reasonerconn/example-workflow.json b/examples/eflint_reasonerconn/example-workflow.json deleted file mode 100644 index c9cafa4..0000000 --- a/examples/eflint_reasonerconn/example-workflow.json +++ /dev/null @@ -1,48 +0,0 @@ -{ - "id": "workflow-kQKDa34a", - "start": { - "Task": { - "id": "workflow-kQKDa34a-
:1-task", - "name": "copy_result", - "package": "copy_result", - "version": "1.1.0", - "input": [ - { - "name": "Test", - "from": null - } - ], - "output": { - "name": "result_copy_result_02da77", - "from": null - }, - "location": null, - "metadata": [], - "next": { - "Commit": { - "id": "workflow-kQKDa34a-
:3-commit", - "data_name": "result", - "input": [ - { - "name": "result_copy_result_02da77", - "from": null - } - ], - "next": { - "Stop": [ - { - "name": "result", - "from": null - } - ] - } - } - } - } - }, - "user": { - "name": "Danny Data Scientist" - }, - "metadata": [], - "signature": "its_signed_i_swear_mom" -} diff --git a/examples/no_op.rs b/examples/no_op.rs new file mode 100644 index 0000000..525ea0d --- /dev/null +++ b/examples/no_op.rs @@ -0,0 +1,75 @@ +// NO OP.rs +// by Lut99 +// +// Created: +// 10 Oct 2024, 16:17:21 +// Last edited: +// 11 Oct 2024, 16:32:08 +// Auto updated? +// Yes +// +// Description: +//! Showcases the reasoner with a super dummy backend reasoner that +//! always accepts anything. +// + +use clap::Parser; +use console::style; +use file_logger::FileLogger; +use policy_reasoner::reasoners::no_op::NoOpReasonerConnector; +use policy_reasoner::spec::ReasonerConnector as _; +use policy_reasoner::spec::auditlogger::SessionedAuditLogger; +use spec::reasonerconn::ReasonerResponse; +use tracing::{Level, info}; + + +/***** ARGUMENTS *****/ +/// The arguments for this binary. +#[derive(Parser)] +pub struct Arguments { + /// Whether to make `info!()` and `debug!()` visible. + #[clap(long, help = "If given, enables INFO- and DEBUG-level logging.")] + debug: bool, + /// Whether to make `trace!()` visible. + #[clap(long, help = "If given, enables TRACE-level logging. Implies '--debug'.")] + trace: bool, +} + + + + + +/***** ENTRYPOINT *****/ +#[tokio::main(flavor = "current_thread")] +async fn main() { + // Parse the arguments + let args = Arguments::parse(); + + // Setup the logger + tracing_subscriber::fmt() + .with_max_level(if args.trace { + Level::TRACE + } else if args.debug { + Level::DEBUG + } else { + Level::WARN + }) + .init(); + info!("{} - v{}", env!("CARGO_BIN_NAME"), env!("CARGO_PKG_VERSION")); + + // Create the logger + let logger: SessionedAuditLogger = + SessionedAuditLogger::new("test", FileLogger::new(format!("{} - v{}", env!("CARGO_BIN_NAME"), env!("CARGO_PKG_VERSION")), "./test.log")); + + // Run the reasoner + let conn: NoOpReasonerConnector<()> = NoOpReasonerConnector::new(); + let verdict: ReasonerResponse<()> = conn.consult((), (), &logger).await.unwrap(); + + // OK, report + match verdict { + ReasonerResponse::Success => println!("{} {}", style("Reasoner says:").bold(), style("OK").bold().green()), + ReasonerResponse::Violated(_) => { + println!("{} {}", style("Reasoner says:").bold(), style("VIOLATION").bold().red()); + }, + } +} diff --git a/examples/posix.rs b/examples/posix.rs new file mode 100644 index 0000000..db7966f --- /dev/null +++ b/examples/posix.rs @@ -0,0 +1,18 @@ +// UNIX.rs +// by Lut99 +// +// Created: +// 11 Oct 2024, 16:32:29 +// Last edited: +// 11 Oct 2024, 16:32:52 +// Auto updated? +// Yes +// +// Description: +//! Showcases the reasoner with a backend that overlays Unix file +//! persmissions. +// + + +/***** ENTRYPOINT *****/ +fn main() {} diff --git a/examples/requests/exec-task.json b/examples/requests/exec-task.json deleted file mode 100644 index 102caf2..0000000 --- a/examples/requests/exec-task.json +++ /dev/null @@ -1,230 +0,0 @@ -{ - "workflow": { - "table": { - "funcs": [ - { - "n": "print", - "a": [ - { - "kind": "str" - } - ], - "r": { - "kind": "void" - } - }, - { - "n": "println", - "a": [ - { - "kind": "str" - } - ], - "r": { - "kind": "void" - } - }, - { - "n": "len", - "a": [ - { - "kind": "arr", - "t": { - "kind": "any" - } - } - ], - "r": { - "kind": "int" - } - }, - { - "n": "commit_result", - "a": [ - { - "kind": "str" - }, - { - "kind": "res" - } - ], - "r": { - "kind": "data" - } - } - ], - "tasks": [ - { - "kind": "cmp", - "p": "copy_result", - "v": "1.1.0", - "d": { - "n": "copy_result", - "a": [ - { - "kind": "res" - } - ], - "r": { - "kind": "res" - } - }, - "a": [ - "result" - ], - "r": [] - } - ], - "classes": [ - { - "n": "Data", - "i": null, - "v": null, - "p": [ - { - "n": "name", - "t": { - "kind": "str" - } - } - ], - "m": [] - }, - { - "n": "IntermediateResult", - "i": null, - "v": null, - "p": [ - { - "n": "path", - "t": { - "kind": "str" - } - } - ], - "m": [] - } - ], - "vars": [ - { - "n": "data", - "t": { - "kind": "data" - } - }, - { - "n": "res", - "t": { - "kind": "res" - } - } - ], - "results": {} - }, - "graph": [ - { - "kind": "lin", - "i": [ - { - "kind": "vrd", - "d": 0 - }, - { - "kind": "str", - "v": "Test" - }, - { - "kind": "ins", - "d": 0 - }, - { - "kind": "vrs", - "d": 0 - }, - { - "kind": "vrd", - "d": 1 - }, - { - "kind": "vrg", - "d": 0 - }, - { - "kind": "cst", - "t": { - "kind": "res" - } - } - ], - "n": 1 - }, - { - "kind": "nod", - "t": 0, - "l": "all", - "s": "hospital_A", - "i": { - "{\"Data\":\"Test\"}": { - "kind": "available", - "h": { - "file": { - "path": "/some/path" - } - } - } - }, - "r": "result_copy_result_951985", - "n": 2 - }, - { - "kind": "lin", - "i": [ - { - "kind": "vrs", - "d": 1 - }, - { - "kind": "str", - "v": "result" - }, - { - "kind": "vrg", - "d": 1 - }, - { - "kind": "fnc", - "d": 3 - } - ], - "n": 3 - }, - { - "kind": "cll", - "i": [ - { - "IntermediateResult": "result_copy_result_951985" - } - ], - "r": [ - { - "Data": "result" - } - ], - "n": 4 - }, - { - "kind": "ret", - "r": [ - { - "Data": "result" - } - ] - } - ], - "funcs": {} - }, - "task_id": [ - 18446744073709551615, - 1 - ] -} \ No newline at end of file diff --git a/examples/requests/workflow.json b/examples/requests/workflow.json deleted file mode 100644 index 84656d4..0000000 --- a/examples/requests/workflow.json +++ /dev/null @@ -1,309 +0,0 @@ -{ - "table": { - "funcs": { - "d": [ - { - "n": "print", - "a": [ - { - "kind": "str" - } - ], - "r": { - "kind": "void" - }, - "t": { - "funcs": { - "d": [], - "o": 18446744073709551615 - }, - "tasks": { - "d": [], - "o": 18446744073709551615 - }, - "classes": { - "d": [], - "o": 18446744073709551615 - }, - "vars": { - "d": [], - "o": 18446744073709551615 - }, - "results": {} - } - }, - { - "n": "println", - "a": [ - { - "kind": "str" - } - ], - "r": { - "kind": "void" - }, - "t": { - "funcs": { - "d": [], - "o": 18446744073709551615 - }, - "tasks": { - "d": [], - "o": 18446744073709551615 - }, - "classes": { - "d": [], - "o": 18446744073709551615 - }, - "vars": { - "d": [], - "o": 18446744073709551615 - }, - "results": {} - } - }, - { - "n": "len", - "a": [ - { - "kind": "arr", - "t": { - "kind": "any" - } - } - ], - "r": { - "kind": "int" - }, - "t": { - "funcs": { - "d": [], - "o": 18446744073709551615 - }, - "tasks": { - "d": [], - "o": 18446744073709551615 - }, - "classes": { - "d": [], - "o": 18446744073709551615 - }, - "vars": { - "d": [], - "o": 18446744073709551615 - }, - "results": {} - } - }, - { - "n": "commit_result", - "a": [ - { - "kind": "str" - }, - { - "kind": "res" - } - ], - "r": { - "kind": "data" - }, - "t": { - "funcs": { - "d": [], - "o": 18446744073709551615 - }, - "tasks": { - "d": [], - "o": 18446744073709551615 - }, - "classes": { - "d": [], - "o": 18446744073709551615 - }, - "vars": { - "d": [], - "o": 18446744073709551615 - }, - "results": {} - } - } - ], - "o": 0 - }, - "tasks": { - "d": [ - { - "kind": "cmp", - "p": "copy_result", - "v": "1.1.0", - "d": { - "n": "copy_result", - "a": [ - { - "kind": "res" - } - ], - "r": { - "kind": "res" - }, - "t": { - "funcs": { - "d": [], - "o": 0 - }, - "tasks": { - "d": [], - "o": 0 - }, - "classes": { - "d": [], - "o": 0 - }, - "vars": { - "d": [], - "o": 0 - }, - "results": {} - } - }, - "a": [ - "result" - ], - "r": [] - } - ], - "o": 0 - }, - "classes": { - "d": [ - { - "n": "Data", - "i": null, - "v": null, - "p": [ - { - "n": "name", - "t": { - "kind": "str" - } - } - ], - "m": [] - }, - { - "n": "IntermediateResult", - "i": null, - "v": null, - "p": [ - { - "n": "path", - "t": { - "kind": "str" - } - } - ], - "m": [] - } - ], - "o": 0 - }, - "vars": { - "d": [ - { - "n": "data", - "t": { - "kind": "data" - } - }, - { - "n": "res", - "t": { - "kind": "res" - } - } - ], - "o": 0 - }, - "results": {} - }, - "graph": [ - { - "kind": "lin", - "i": [ - { - "kind": "vrd", - "d": 0 - }, - { - "kind": "str", - "v": "Test" - }, - { - "kind": "ins", - "d": 0 - }, - { - "kind": "vrs", - "d": 0 - }, - { - "kind": "vrd", - "d": 1 - }, - { - "kind": "vrg", - "d": 0 - }, - { - "kind": "cst", - "t": { - "kind": "res" - } - } - ], - "n": 1 - }, - { - "kind": "nod", - "t": 0, - "l": "all", - "s": null, - "i": { - "{\"Data\":\"Test\"}": null - }, - "r": "result_copy_result_d220be", - "n": 2 - }, - { - "kind": "lin", - "i": [ - { - "kind": "vrs", - "d": 1 - }, - { - "kind": "str", - "v": "result" - }, - { - "kind": "vrg", - "d": 1 - }, - { - "kind": "fnc", - "d": 3 - } - ], - "n": 3 - }, - { - "kind": "cll", - "n": 4 - }, - { - "kind": "ret" - } - ], - "funcs": {} -} diff --git a/lib/audit-logger/Cargo.toml b/lib/audit-logger/Cargo.toml deleted file mode 100644 index d5f798b..0000000 --- a/lib/audit-logger/Cargo.toml +++ /dev/null @@ -1,28 +0,0 @@ -[package] -name = "audit-logger" -rust-version = "1.81" -edition = "2021" -version.workspace = true -repository.workspace = true -authors.workspace = true -license.workspace = true - -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - -[dependencies] -# Crates.io -async-trait = "0.1.67" -hex = "0.4.3" -serde = "1.0.204" -serde_json = "1.0.120" -warp = "0.3" - -# Path -auth-resolver = { path = "../auth-resolver" } -deliberation = { path = "../deliberation" } -policy = { path = "../policy" } -state-resolver = { path = "../state-resolver" } -workflow = { path = "../workflow" } - -# Git -enum-debug.workspace = true diff --git a/lib/audit-logger/src/lib.rs b/lib/audit-logger/src/lib.rs deleted file mode 100644 index 3551493..0000000 --- a/lib/audit-logger/src/lib.rs +++ /dev/null @@ -1,336 +0,0 @@ -use std::borrow::Cow; -use std::collections::hash_map::DefaultHasher; -use std::fmt::Debug; -use std::hash::{Hash as _, Hasher as _}; - -use auth_resolver::AuthContext; -use deliberation::spec::Verdict; -use enum_debug::EnumDebug; -use policy::Policy; -use serde::{Deserialize, Serialize}; -use serde_json::Value; -use state_resolver::State; -use workflow::Workflow; - -#[derive(Debug)] -pub enum Error { - CouldNotDeliver(String), -} - -impl std::fmt::Display for Error { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - Self::CouldNotDeliver(msg) => { - write!(f, "Could not deliver: {}", msg) - }, - } - } -} - -impl std::error::Error for Error {} - -impl warp::reject::Reject for Error {} - -pub trait ConnectorContext { - fn r#type(&self) -> String; - fn version(&self) -> String; -} - -/// Defines an intermediary that allows us to conveniently log `ReasonerConnector`'s context. -pub trait ConnectorWithContext { - /// The type returned by [`Self::context()`]. - type Context: ConnectorContext + std::hash::Hash + Sync + Send + Serialize + Clone + core::fmt::Debug; - - /// Returns hash of connector's context. - /// - /// If anything about the connector changes that can have an effect on the evaluation of a policy - /// the returned hash must be different - fn hash() -> String { - let mut hasher = DefaultHasher::new(); - Self::context().hash(&mut hasher); - // digest() - let h = hasher.finish(); - hex::encode(h.to_be_bytes()) - } - /// Returns so-called "full context" about the reasoner connector that is relevant for the audit log. - /// - /// In particular, this should al least contain the type of the connector used and its version. - fn context() -> Self::Context; -} - -/// Collects everything we might want to log in an [`AuditLogger`]. -/// -/// TODO: Nicer to move this to the toplevel crate, `logger.rs`. -#[derive(Clone, Debug, Deserialize, EnumDebug, Serialize)] -#[serde(tag = "kind", rename_all = "SCREAMING-KEBAB-CASE")] -pub enum LogStatement<'a> { - /// A request that asks if a task may be executed has been received. - ExecuteTask { - reference: Cow<'a, str>, - auth: Cow<'a, AuthContext>, - policy: i64, - state: Cow<'a, State>, - workflow: Cow<'a, Workflow>, - task: Cow<'a, str>, - }, - /// A request that asks if an asset may be accessed has been received. - AssetAccess { - reference: Cow<'a, str>, - auth: Cow<'a, AuthContext>, - policy: i64, - state: Cow<'a, State>, - workflow: Cow<'a, Workflow>, - data: Cow<'a, str>, - #[serde(skip_serializing_if = "Option::is_none")] - task: Option>, - }, - /// A request that asks if a workflow is permitted has been received. - WorkflowValidate { - reference: Cow<'a, str>, - auth: Cow<'a, AuthContext>, - policy: i64, - state: Cow<'a, State>, - workflow: Cow<'a, Workflow>, - }, - - /// Logs the raw response of a reasoner. - ReasonerResponse { reference: Cow<'a, str>, response: Cow<'a, str> }, - /// Logs the official response of a reasoner. - ReasonerVerdict { reference: Cow<'a, str>, verdict: Cow<'a, Verdict> }, - - /// Logs the reasoner backend for during startup. - ReasonerContext { connector_context: Value, connector_context_hash: String }, - /// Logs the arrival of a new policy. - PolicyAdd { auth: Cow<'a, AuthContext>, connector_context_hash: String, policy: Cow<'a, Policy> }, - /// Logs the activation of an existing policy. - PolicyActivate { auth: Cow<'a, AuthContext>, policy: Cow<'a, Policy> }, - /// Logs the deactivation of the current active policy. - PolicyDeactivate { auth: Cow<'a, AuthContext> }, -} -impl<'a> LogStatement<'a> { - /// Constructor for a [`LogStatement::ExecuteTask`] that makes it a bit more convenient to initialize. - /// - /// # Arguments - /// - `reference`: The reference ID for this request. - /// - `auth`: The [`AuthContext`] that explains who performed the request. - /// - `policy`: The [`Policy`] used to evaluate the request. - /// - `state`: The [`State`] given to the policy for evaluation. - /// - `workflow`: The [`Workflow`] that is being evaluated. - /// - `task`: Which task in the `workflow` we're specifically considering. - /// - /// # Returns - /// A new [`LogStatement::ExecuteTask`] that is initialized with the given properties. - #[inline] - pub fn execute_task(reference: &'a str, auth: &'a AuthContext, policy: i64, state: &'a State, workflow: &'a Workflow, task: &'a str) -> Self { - Self::ExecuteTask { - reference: Cow::Borrowed(reference), - auth: Cow::Borrowed(auth), - policy, - state: Cow::Borrowed(state), - workflow: Cow::Borrowed(workflow), - task: Cow::Borrowed(task), - } - } - - /// Constructor for a [`LogStatement::AssetAccess`] that makes it a bit more convenient to initialize. - /// - /// # Arguments - /// - `reference`: The reference ID for this request. - /// - `auth`: The [`AuthContext`] that explains who performed the request. - /// - `policy`: The [`Policy`] used to evaluate the request. - /// - `state`: The [`State`] given to the policy for evaluation. - /// - `workflow`: The [`Workflow`] that is being evaluated. - /// - `task`: Which task in the `workflow` we're specifically considering. - /// - /// # Returns - /// A new [`LogStatement::AssetAccess`] that is initialized with the given properties. - #[inline] - pub fn asset_access( - reference: &'a str, - auth: &'a AuthContext, - policy: i64, - state: &'a State, - workflow: &'a Workflow, - data: &'a str, - task: &'a Option, - ) -> Self { - Self::AssetAccess { - reference: Cow::Borrowed(reference), - auth: Cow::Borrowed(auth), - policy, - state: Cow::Borrowed(state), - workflow: Cow::Borrowed(workflow), - data: Cow::Borrowed(data), - task: task.as_ref().map(|t| Cow::Borrowed(t.as_str())), - } - } - - /// Constructor for a [`LogStatement::WorkflowValidate`] that makes it a bit more convenient to initialize. - /// - /// # Arguments - /// - `reference`: The reference ID for this request. - /// - `auth`: The [`AuthContext`] that explains who performed the request. - /// - `policy`: The [`Policy`] used to evaluate the request. - /// - `state`: The [`State`] given to the policy for evaluation. - /// - `workflow`: The [`Workflow`] that is being evaluated. - /// - `task`: Which task in the `workflow` we're specifically considering. - /// - /// # Returns - /// A new [`LogStatement::WorkflowValidate`] that is initialized with the given properties. - #[inline] - pub fn workflow_validate(reference: &'a str, auth: &'a AuthContext, policy: i64, state: &'a State, workflow: &'a Workflow) -> Self { - Self::WorkflowValidate { - reference: Cow::Borrowed(reference), - auth: Cow::Borrowed(auth), - policy, - state: Cow::Borrowed(state), - workflow: Cow::Borrowed(workflow), - } - } - - /// Constructor for a [`LogStatement::ReasonerResponse`] that makes it a bit more convenient to initialize. - /// - /// # Arguments - /// - `reference`: The reference ID for this request. - /// - `response`: The raw response as returned by the reasoner. - /// - /// # Returns - /// A new [`LogStatement::ReasonerResponse`] that is initialized with the given properties. - #[inline] - pub fn reasoner_response(reference: &'a str, response: &'a str) -> Self { - Self::ReasonerResponse { reference: Cow::Borrowed(reference), response: Cow::Borrowed(response) } - } - - /// Constructor for a [`LogStatement::ReasonerVerdict`] that makes it a bit more convenient to initialize. - /// - /// # Arguments - /// - `reference`: The reference ID for this request. - /// - `verdict`: The verdict given by the reasoner. - /// - /// # Returns - /// A new [`LogStatement::ReasonerVerdict`] that is initialized with the given properties. - #[inline] - pub fn reasoner_verdict(reference: &'a str, verdict: &'a Verdict) -> Self { - Self::ReasonerVerdict { reference: Cow::Borrowed(reference), verdict: Cow::Borrowed(verdict) } - } - - /// Constructor for a [`LogStatement::ReasonerContext`] that makes it a bit more convenient to initialize. - /// - /// # Arguments - /// - `context`: The context that is used to give answers with this reasoner. - /// - /// # Returns - /// A new [`LogStatement::ReasonerContext`] that is initialized with the given properties. - #[inline] - pub fn reasoner_context() -> Self { - Self::ReasonerContext { - connector_context: serde_json::to_value(C::context()) - .unwrap_or_else(|err| panic!("Could not serialize context of {}: {}", std::any::type_name::(), err)), - connector_context_hash: C::hash(), - } - } - - /// Constructor for a [`LogStatement::PolicyAdd`] that makes it a bit more convenient to initialize. - /// - /// # Arguments - /// - `auth`: The [`AuthContext`] that explains who performed the request. - /// - `context`: The context that is used to give answers with this reasoner. - /// - `policy`: The [`Policy`] added to the checker in this request. - /// - /// # Returns - /// A new [`LogStatement::ReasonerContext`] that is initialized with the given properties. - #[inline] - pub fn policy_add(auth: &'a AuthContext, policy: &'a Policy) -> Self { - Self::PolicyAdd { auth: Cow::Borrowed(auth), connector_context_hash: C::hash(), policy: Cow::Borrowed(policy) } - } - - /// Constructor for a [`LogStatement::PolicyActivate`] that makes it a bit more convenient to initialize. - /// - /// # Arguments - /// - `auth`: The [`AuthContext`] that explains who performed the request. - /// - `policy`: The [`Policy`] that got activated in this request. - /// - /// # Returns - /// A new [`LogStatement::PolicyActivate`] that is initialized with the given properties. - #[inline] - pub fn policy_activate(auth: &'a AuthContext, policy: &'a Policy) -> Self { - Self::PolicyActivate { auth: Cow::Borrowed(auth), policy: Cow::Borrowed(policy) } - } - - /// Constructor for a [`LogStatement::PolicyDeactivate`] that makes it a bit more convenient to initialize. - /// - /// # Arguments - /// - `auth`: The [`AuthContext`] that explains who performed the request. - /// - /// # Returns - /// A new [`LogStatement::PolicyDeactivate`] that is initialized with the given properties. - #[inline] - pub fn policy_deactivate(auth: &'a AuthContext) -> Self { Self::PolicyDeactivate { auth: Cow::Borrowed(auth) } } -} - -#[async_trait::async_trait] -pub trait AuditLogger: ReasonerConnectorAuditLogger { - async fn log_exec_task_request( - &self, - reference: &str, - auth: &AuthContext, - policy: i64, - state: &State, - workflow: &Workflow, - task: &str, - ) -> Result<(), Error>; - - // OK so it _may_ have a few functions. But again, not worth it to make a struct IMO. - #[allow(clippy::too_many_arguments)] - async fn log_data_access_request( - &self, - reference: &str, - auth: &AuthContext, - policy: i64, - state: &State, - workflow: &Workflow, - data: &str, - task: &Option, - ) -> Result<(), Error>; - - async fn log_validate_workflow_request( - &self, - reference: &str, - auth: &AuthContext, - policy: i64, - state: &State, - workflow: &Workflow, - ) -> Result<(), Error>; - - async fn log_verdict(&self, reference: &str, verdict: &Verdict) -> Result<(), Error>; - - /// Dumps the full context of the reasoner on startup. - /// - /// Note that it's recommended to use `ReasonerConnector::FullContext` for this, to include the full base specification. - async fn log_reasoner_context(&self) -> Result<(), Error>; - /// Logs that a new policy has been added, including the full policy. - /// - /// Note that it's recommended to use `ReasonerConnector::Context` for this, as the full base spec as already been logged at startup. - async fn log_add_policy_request(&self, auth: &AuthContext, policy: &Policy) -> Result<(), Error>; - - async fn log_set_active_version_policy(&self, auth: &AuthContext, policy: &Policy) -> Result<(), Error>; - - async fn log_deactivate_policy(&self, auth: &AuthContext) -> Result<(), Error>; -} - -#[async_trait::async_trait] -pub trait ReasonerConnectorAuditLogger { - async fn log_reasoner_response(&self, reference: &str, response: &str) -> Result<(), Error>; -} - -pub struct SessionedConnectorAuditLogger { - pub reference: String, - logger: Logger, -} -impl SessionedConnectorAuditLogger { - pub fn new(reference: String, logger: Logger) -> Self { Self { reference, logger } } - - pub async fn log_reasoner_response(&self, response: &str) -> Result<(), Error> { - self.logger.log_reasoner_response(&self.reference, response).await - } -} diff --git a/lib/auth-resolver/Cargo.toml b/lib/auth-resolver/Cargo.toml deleted file mode 100644 index 548a2ae..0000000 --- a/lib/auth-resolver/Cargo.toml +++ /dev/null @@ -1,15 +0,0 @@ -[package] -name = "auth-resolver" -rust-version = "1.81" -edition = "2021" -version.workspace = true -repository.workspace = true -authors.workspace = true -license.workspace = true - -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - -[dependencies] -async-trait = "0.1.67" -serde = { version = "1.0.204", features = ["derive"] } -warp = "0.3" diff --git a/lib/auth-resolver/src/lib.rs b/lib/auth-resolver/src/lib.rs deleted file mode 100644 index f6c9bda..0000000 --- a/lib/auth-resolver/src/lib.rs +++ /dev/null @@ -1,29 +0,0 @@ -use serde::{Deserialize, Serialize}; - -#[derive(Debug, Clone, Deserialize, Serialize)] -pub struct AuthContext { - pub initiator: String, - pub system: String, -} - -#[derive(Debug)] -pub struct AuthResolverError { - err: String, -} - -impl AuthResolverError { - pub fn new(err: String) -> Self { Self { err } } -} - -impl std::fmt::Display for AuthResolverError { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "{}", self.err) } -} - -impl std::error::Error for AuthResolverError {} - -impl warp::reject::Reject for AuthResolverError {} - -#[async_trait::async_trait] -pub trait AuthResolver { - async fn authenticate(&self, headers: warp::http::HeaderMap) -> Result; -} diff --git a/lib/deliberation/Cargo.toml b/lib/deliberation/Cargo.toml deleted file mode 100644 index 99bf504..0000000 --- a/lib/deliberation/Cargo.toml +++ /dev/null @@ -1,33 +0,0 @@ -[package] -name = "deliberation" -rust-version = "1.81" -edition = "2021" -authors = ["Bas Kloosterman"] -version.workspace = true -repository.workspace = true -license.workspace = true - - -[dependencies] -# Crates.io -log = "0.4.22" -serde = { version="1.0.204", features=["derive"] } -serde_json = "1.0.120" -uuid = "1.7.0" - -# Workspace dependencies -enum-debug.workspace = true - -# Brane -brane-ast = { git = "https://github.com/braneframework/brane" } -brane-exe = { git = "https://github.com/braneframework/brane" } - - -[dev-dependencies] -# Crates.io -clap = { version = "4.5.6", features = ["derive"] } -log = "0.4.22" - -# Workspace dependencies -error-trace.workspace = true -humanlog.workspace = true diff --git a/lib/deliberation/src/lib.rs b/lib/deliberation/src/lib.rs deleted file mode 100644 index cc05df3..0000000 --- a/lib/deliberation/src/lib.rs +++ /dev/null @@ -1 +0,0 @@ -pub mod spec; diff --git a/lib/deliberation/src/spec.rs b/lib/deliberation/src/spec.rs deleted file mode 100644 index c448ffe..0000000 --- a/lib/deliberation/src/spec.rs +++ /dev/null @@ -1,92 +0,0 @@ -use brane_ast::Workflow; -use brane_exe::pc::ProgramCounter; -use serde::{Deserialize, Serialize}; - -/// ExecuteTaskRequest represents the question if it is allowed to execute a -/// certain task on this node -#[derive(Serialize, Deserialize)] -pub struct ExecuteTaskRequest { - /// Some identifier that allows the policy reasoner to assume a different context. - /// - /// Note that not any identifier is accepted. Which are depends on which plugins used. - pub use_case: String, - /// Workflow definition - pub workflow: Workflow, - /// The location of the task we're examining in the given `workflow`. - pub task_id: ProgramCounter, -} - -/// AccessDataRequest represents the question if a certain dataset -/// can be accessed -#[derive(Serialize, Deserialize)] -pub struct AccessDataRequest { - /// Some identifier that allows the policy reasoner to assume a different context. - /// - /// Note that not any identifier is accepted. Which are depends on which plugins used. - pub use_case: String, - /// Workflow definition - pub workflow: Workflow, - /// Identifier for the requested dataset - pub data_id: String, - /// The location of the task for which we transfer in the given `workflow`. If omitted, then this transfer should be interpreted as transferring the final result of the workflow. - pub task_id: Option, -} - -/// WorkflowValidationRequest represents the question -/// if a workflow as a whole is considered valid by the checker. -/// Used on the 'central' side to enforce 'central' policies -#[derive(Serialize, Deserialize)] -pub struct WorkflowValidationRequest { - /// Some identifier that allows the policy reasoner to assume a different context. - /// - /// Note that not any identifier is accepted. Which are depends on which plugins used. - pub use_case: String, - /// Workflow definition - pub workflow: Workflow, -} - -#[derive(Clone, Debug, Serialize, Deserialize)] -#[serde(tag = "verdict")] -pub enum Verdict { - // Checker says yes - #[serde(rename = "allow")] - Allow(DeliberationAllowResponse), - // Checker says no - #[serde(rename = "deny")] - Deny(DeliberationDenyResponse), -} - -// DeliberationResponse represents the shared part of the the deliberation repsonses -// (Allow, Deny) -#[derive(Clone, Debug, Serialize, Deserialize)] -pub struct DeliberationResponse { - pub verdict_reference: String, -} - -// DeliberationResponse represents the answer the checker came up with -#[derive(Clone, Debug, Serialize, Deserialize)] -pub struct DeliberationAllowResponse { - #[serde(flatten)] - pub shared: DeliberationResponse, - /// Signature by the checker - pub signature: String, -} - -// DeliberationResponse represents the answer the checker came up with -#[derive(Clone, Debug, Serialize, Deserialize)] -pub struct DeliberationDenyResponse { - #[serde(flatten)] - pub shared: DeliberationResponse, - /// A optional list that contains the reasons that the request is denied. - /// Only present if the request is denied and it only contains reasons - /// the checker wants to share. - pub reasons_for_denial: Option>, -} - -pub type TaskExecResponse = DeliberationResponse; -pub type DataAccessResponse = DeliberationResponse; -pub type WorkflowValidationResponse = DeliberationResponse; - -// POST /v1/deliberation/execute-task -// POST /v1/deliberation/access-data -// POST /v1/deliberation/execute-workflow diff --git a/lib/eflint-to-json/Cargo.toml b/lib/eflint-to-json/Cargo.toml index 12c7170..0f9f54e 100644 --- a/lib/eflint-to-json/Cargo.toml +++ b/lib/eflint-to-json/Cargo.toml @@ -3,20 +3,29 @@ name = "eflint-to-json" description = "Defines a high-level wrapper around Olaf's [`eflint-to-json`](https://github.com/Olaf-Erkemeij/eflint-server) executable that compiles eFLINT to eFLINT JSON Specification." rust-version = "1.81" edition = "2021" -version.workspace = true +version = "0.2.0" +authors = ["Tim Müller"] repository.workspace = true -authors.workspace = true license.workspace = true [dependencies] -async-recursion = "1.0" +async-recursion = { version = "1.0", optional = true } console = "0.15.5" -futures-util = "0.3.30" +futures-util = { version = "0.3.30", optional = true } hex = "0.4.3" hex-literal = "0.4" indicatif = "0.17" -log = "0.4.22" -reqwest = { version = "0.12.0", features = ["blocking", "stream"] } +log = { version = "0.4.22", optional = true } +reqwest = { version = "0.12.0", default-features = false, features = ["blocking", "stream"] } sha2 = "0.10.6" -tokio = { version = "1.38.0", default-features = false, features = ["fs", "process"]} +tokio = { version = "1.40.0", default-features = false, features = ["fs", "io-util", "process"], optional = true } +tracing = { version = "0.1.40", optional = true } + + +[features] +default = [] + +async-tokio = ["dep:async-recursion", "dep:futures-util", "dep:tokio"] +log = ["dep:log"] +tracing = ["dep:tracing"] diff --git a/lib/eflint-to-json/src/download.rs b/lib/eflint-to-json/src/download.rs index c5ad98a..e3f5bc3 100644 --- a/lib/eflint-to-json/src/download.rs +++ b/lib/eflint-to-json/src/download.rs @@ -4,7 +4,7 @@ // Created: // 29 Nov 2023, 15:11:58 // Last edited: -// 12 Jun 2024, 17:45:43 +// 11 Oct 2024, 16:08:06 // Auto updated? // Yes // @@ -19,14 +19,21 @@ use std::str::FromStr as _; use std::{error, fs}; use console::Style; +#[cfg(feature = "async-tokio")] use futures_util::StreamExt as _; use indicatif::{ProgressBar, ProgressStyle}; -use log::debug; -use reqwest::{Client, Request, Response, StatusCode, Url, blocking}; +#[cfg(feature = "async-tokio")] +use reqwest::{Client, Request, Response}; +use reqwest::{StatusCode, Url, blocking}; use sha2::{Digest as _, Sha256}; +#[cfg(feature = "async-tokio")] use tokio::fs as tfs; +#[cfg(feature = "async-tokio")] use tokio::io::AsyncWriteExt as _; +use crate::log::debug; + + /***** ERRORS *****/ /// Wraps the contents of an error body. #[derive(Debug)] @@ -36,7 +43,9 @@ impl Display for ResponseBodyError { } impl error::Error for ResponseBodyError {} -/// Defines errors occurring with [`download_file_async()`]. + + +/// Defines errors occurring with [`download_file()`]. #[derive(Debug)] pub enum Error { /// Failed to create a file. @@ -103,6 +112,10 @@ impl std::error::Error for Error { } } + + + + /***** AUXILLARY *****/ /// Defines things to do to assert a downloaded file is secure and what we expect. #[derive(Clone, Debug)] @@ -176,6 +189,10 @@ impl Display for DownloadSecurity<'_> { } } + + + + /***** LIBRARY *****/ /// Downloads some file from the interwebs to the given location. /// @@ -361,6 +378,7 @@ pub fn download_file(source: impl AsRef, target: impl AsRef, security /// /// # Errors /// This function may error if we failed to download the file or write it (which may happen if the parent directory of `local` does not exist, among other things). +#[cfg(feature = "async-tokio")] pub async fn download_file_async( source: impl AsRef, target: impl AsRef, diff --git a/lib/eflint-to-json/src/lib.rs b/lib/eflint-to-json/src/lib.rs index fe55551..6e1000b 100644 --- a/lib/eflint-to-json/src/lib.rs +++ b/lib/eflint-to-json/src/lib.rs @@ -4,7 +4,7 @@ // Created: // 30 Nov 2023, 10:38:50 // Last edited: -// 12 Jun 2024, 17:49:13 +// 11 Oct 2024, 16:10:25 // Auto updated? // Yes // @@ -16,6 +16,7 @@ // Declare modules pub mod download; +mod log; use std::borrow::Cow; use std::collections::HashSet; @@ -28,11 +29,17 @@ use std::process::{Child, ChildStdin, ChildStdout, Command, ExitStatus, Stdio}; use console::Style; use log::{debug, info}; +#[cfg(feature = "async-tokio")] use tokio::fs::{self as tfs, File as TFile}; +#[cfg(feature = "async-tokio")] use tokio::io::{AsyncBufReadExt as _, AsyncReadExt, AsyncWriteExt as _, BufReader as TBufReader}; +#[cfg(feature = "async-tokio")] use tokio::process::{Child as TChild, ChildStdin as TChildStdin, ChildStdout as TChildStdout, Command as TCommand}; -use crate::download::{DownloadSecurity, download_file, download_file_async}; +#[cfg(feature = "async-tokio")] +use crate::download::download_file_async; +use crate::download::{DownloadSecurity, download_file}; + /***** CONSTANTS *****/ /// Compiler download URL. @@ -40,6 +47,10 @@ const COMPILER_URL: &str = "https://github.com/Olaf-Erkemeij/eflint-server/raw/b /// Compiler download checksum. const COMPILER_CHECKSUM: [u8; 32] = hex_literal::hex!("4e4e59b158ca31e532ec0a22079951788696ffa5d020b36790b4461dbadec83d"); + + + + /***** ERRORS *****/ /// Defines a wrapper around multiple streams. #[derive(Debug)] @@ -84,6 +95,7 @@ impl ChildStream { /// /// # Returns /// A new ChildStream that either has the stream's contents, or some message saying the contents couldn't be retrieved. + #[cfg(feature = "async-tokio")] async fn new_async(what: &'static str, mut stream: impl AsyncReadExt + Unpin) -> Self { // Attempt to read it all let mut buf: String = String::new(); @@ -105,6 +117,8 @@ impl Display for ChildStream { } impl error::Error for ChildStream {} + + /// Defines toplevel errors. #[derive(Debug)] pub enum Error { @@ -186,6 +200,10 @@ impl error::Error for Error { } } + + + + /***** HELPER FUNCTIONS *****/ /// Analyses a potential `#input(...)` or `#require(...)` line from eFLINT. /// @@ -257,6 +275,7 @@ fn potentially_include(imported: &mut HashSet, path: &Path, line: &str) /// /// # Errors /// This function can error if we failed to open the included file. +#[cfg(feature = "async-tokio")] async fn potentially_include_async(imported: &mut HashSet, path: &Path, line: &str) -> Result>, Error> { // Strip whitespace let line: &str = line.trim(); @@ -356,6 +375,7 @@ fn load_input(imported: &mut HashSet, path: &Path, handle: BufReader, path: &Path, handle: TBufReader, child: &mut TChildStdin) -> Result<(), Error> { debug!("Importing file '{}'", path.display()); @@ -391,6 +411,10 @@ async fn load_input_async(imported: &mut HashSet, path: &Path, handle: Ok(()) } + + + + /***** LIBRARY *****/ /// Compiles a (tree of) `.eflint` files using Olaf's `eflint-to-json` compiler. /// @@ -524,6 +548,7 @@ pub fn compile(input_path: &Path, mut output: impl Write, compiler_path: Option< /// /// # Errors /// This function may error for a plethora of reasons. +#[cfg(feature = "async-tokio")] pub async fn compile_async(input_path: &Path, mut output: impl Write, compiler_path: Option<&Path>) -> Result<(), Error> { info!("Compiling input at '{}'", input_path.display()); @@ -553,6 +578,7 @@ pub async fn compile_async(input_path: &Path, mut output: impl Write, compiler_p use std::os::unix::fs::PermissionsExt as _; // ...and make it executable + debug!("Making compiler '{}' executable...", compiler_path.display()); let mut perms: Permissions = match tfs::metadata(&compiler_path).await { Ok(mdata) => mdata.permissions(), Err(err) => return Err(Error::FileMetadata { path: compiler_path, err }), diff --git a/lib/eflint-to-json/src/log.rs b/lib/eflint-to-json/src/log.rs new file mode 100644 index 0000000..0f4b9b6 --- /dev/null +++ b/lib/eflint-to-json/src/log.rs @@ -0,0 +1,71 @@ +// LOG.rs +// by Lut99 +// +// Created: +// 11 Oct 2024, 16:05:00 +// Last edited: +// 11 Oct 2024, 16:07:32 +// Auto updated? +// Yes +// +// Description: +//! Defines macros that log in various ways. +// + + +/***** LIBRARY *****/ +/// Depending on the backend, logs a DEBUG-level statement appropriately or not. +#[cfg(not(any(feature = "log", feature = "tracing")))] +macro_rules! debug { + ($($t:tt)*) => {}; +} +#[cfg(all(feature = "log", not(feature = "tracing")))] +macro_rules! debug { + ($($t:tt)*) => { + ::log::debug!($($t)*) + }; +} +#[cfg(all(not(feature = "log"), feature = "tracing"))] +macro_rules! debug { + ($($t:tt)*) => { + ::tracing::debug!($($t)*) + }; +} +#[cfg(all(feature = "log", feature = "tracing"))] +macro_rules! debug { + ($($t:tt)*) => { + { + ::log::debug!($($t)*); + ::tracing::debug!($($t)*); + } + }; +} +pub(crate) use debug; + +/// Depending on the backend, logs an INFO-level statement appropriately or not. +#[cfg(not(any(feature = "log", feature = "tracing")))] +macro_rules! info { + ($($t:tt)*) => {}; +} +#[cfg(all(feature = "log", not(feature = "tracing")))] +macro_rules! info { + ($($t:tt)*) => { + ::log::info!($($t)*) + }; +} +#[cfg(all(not(feature = "log"), feature = "tracing"))] +macro_rules! info { + ($($t:tt)*) => { + ::tracing::info!($($t)*) + }; +} +#[cfg(all(feature = "log", feature = "tracing"))] +macro_rules! info { + ($($t:tt)*) => { + { + ::log::info!($($t)*); + ::tracing::info!($($t)*); + } + }; +} +pub(crate) use info; diff --git a/lib/loggers/file/Cargo.toml b/lib/loggers/file/Cargo.toml new file mode 100644 index 0000000..064a90a --- /dev/null +++ b/lib/loggers/file/Cargo.toml @@ -0,0 +1,23 @@ +[package] +name = "file-logger" +description = "Implements an audit logger that writes everything to a file." +edition = "2021" +authors = [ "Bas Kloosterman", "Tim Müller" ] +version = "0.2.0" +repository.workspace = true +license.workspace = true + + +[dependencies] +chrono = "0.4.38" +enum-debug = { git = "https://github.com/Lut99/enum-debug", tag = "v1.1.0", features = ["derive"] } +serde = { version = "1.0.204", features = ["derive"] } +serde_json = "1.0.120" +tokio = { version = "1.40.0", default-features = false, features = ["fs", "io-util"] } +tracing = "0.1.40" + +spec = { path = "../../spec", package = "specifications" } + + +[features] +default = [] diff --git a/lib/loggers/file/src/lib.rs b/lib/loggers/file/src/lib.rs new file mode 100644 index 0000000..6d5adb9 --- /dev/null +++ b/lib/loggers/file/src/lib.rs @@ -0,0 +1,20 @@ +// LIB.rs +// by Lut99 +// +// Created: +// 10 Oct 2024, 14:11:09 +// Last edited: +// 10 Oct 2024, 14:24:54 +// Auto updated? +// Yes +// +// Description: +//! Implements an audit logger that writes everything to a file. +// + +// Declare modules +mod logger; +mod stmt; + +// Bring some of it into this namespace. +pub use logger::*; diff --git a/lib/loggers/file/src/logger.rs b/lib/loggers/file/src/logger.rs new file mode 100644 index 0000000..b396e48 --- /dev/null +++ b/lib/loggers/file/src/logger.rs @@ -0,0 +1,218 @@ +// LOGGER.rs +// by Lut99 +// +// Created: +// 10 Oct 2024, 14:16:24 +// Last edited: +// 10 Oct 2024, 14:40:18 +// Auto updated? +// Yes +// +// Description: +//! Defines the actual [`AuditLogger`] itself. +// + +use std::borrow::Cow; +use std::error; +use std::fmt::{Debug, Display, Formatter, Result as FResult}; +use std::future::Future; +use std::path::PathBuf; + +use enum_debug::EnumDebug as _; +use serde::Serialize; +use spec::auditlogger::AuditLogger; +use spec::reasonerconn::ReasonerResponse; +use tokio::fs::{File, OpenOptions}; +use tokio::io::AsyncWriteExt as _; +use tracing::debug; + +use crate::stmt::LogStatement; + + +/***** HELPER MACROS *****/ +/// Wraps a [`write!`]-macro to return its error as a [`FileLoggerError`]. +macro_rules! write_file { + ($path:expr, $handle:expr, $($t:tt)+) => { + // Psych we actually don't wrap that macro, since we're doing async ofc + async { + use tokio::io::AsyncWriteExt as _; + let contents: String = format!($($t)+); + $handle.write_all(contents.as_bytes()).await.map_err(|err| Error::FileWrite { path: ($path), err }) + } + }; +} + +/// Wraps a [`writeln!`]-macro to return its error as a [`FileLoggerError`]. +macro_rules! writeln_file { + ($path:expr, $handle:expr) => { + // Psych we actually don't wrap that macro, since we're doing async ofc + async { + use tokio::io::AsyncWriteExt as _; + $handle.write_all(b"\n").await.map_err(|err| Error::FileWrite { path: ($path), err }) + } + }; + ($path:expr, $handle:expr, $($t:tt)+) => { + // Psych we actually don't wrap that macro, since we're doing async ofc + async { + use tokio::io::AsyncWriteExt as _; + let mut contents: String = format!($($t)*); + contents.push('\n'); + $handle.write_all(contents.as_bytes()).await.map_err(|err| Error::FileWrite { path: ($path), err }) + } + }; +} + + + + + +/***** ERRORS *****/ +/// Defines the errors emitted by the [`FileLogger`]. +#[derive(Debug)] +pub enum Error { + /// Failed to create a new file. + FileCreate { path: PathBuf, err: std::io::Error }, + /// Failed to open an existing file. + FileOpen { path: PathBuf, err: std::io::Error }, + /// Failed to shutdown an open file. + FileShutdown { path: PathBuf, err: std::io::Error }, + /// Failed to write to a new file. + FileWrite { path: PathBuf, err: std::io::Error }, + /// Failed to serialize a logging statement. + LogStatementSerialize { kind: String, err: serde_json::Error }, +} +impl Display for Error { + #[inline] + fn fmt(&self, f: &mut Formatter<'_>) -> FResult { + use Error::*; + match self { + FileCreate { path, .. } => write!(f, "Failed to create a new file at {:?}", path.display()), + FileOpen { path, .. } => write!(f, "Failed to open existing file {:?}", path.display()), + FileShutdown { path, .. } => write!(f, "Failed to shutdown open file {:?}", path.display()), + FileWrite { path, .. } => write!(f, "Failed to write to flie {:?}", path.display()), + LogStatementSerialize { kind, .. } => write!(f, "Failed to serialize statement LogStatement::{kind}"), + } + } +} +impl error::Error for Error { + #[inline] + fn source(&self) -> Option<&(dyn error::Error + 'static)> { + use Error::*; + match self { + FileCreate { err, .. } => Some(err), + FileOpen { err, .. } => Some(err), + FileShutdown { err, .. } => Some(err), + FileWrite { err, .. } => Some(err), + LogStatementSerialize { err, .. } => Some(err), + } + } +} + + + + + +/***** LIBRARY *****/ +/// Implements an [`AuditLogger`] that writes everything to a local file. +#[derive(Clone, Debug)] +pub struct FileLogger { + /// The identifier of who/what is writing. + id: String, + /// The path we log to. + path: PathBuf, +} +impl FileLogger { + /// Constructor for the FileLogger that initializes it pointing to the given file. + /// + /// # Arguments + /// - `identifier`: Some identifier that represents who writes the log statement. E.g., `policy-reasoner v1.2.3`. + /// - `path`: The path to the file to log to. + /// + /// # Returns + /// A new instance of self, ready for action. + #[inline] + pub fn new(id: impl Into, path: impl Into) -> Self { Self { id: id.into(), path: path.into() } } + + /// Writes a log statement to the logging file. + /// + /// # Arguments + /// - `stmt`: The [`LogStatement`] that determines what we're gonna log. + /// + /// # Errors + /// This function errors if we failed to perform the logging completely (i.e., either write or flush). + async fn log(&self, stmt: LogStatement<'_, T>) -> Result<(), Error> { + // Step 1: Open the log file + let mut handle: File = if !self.path.exists() { + debug!("Creating new log file at '{}'...", self.path.display()); + match File::create(&self.path).await { + Ok(handle) => handle, + Err(err) => return Err(Error::FileCreate { path: self.path.clone(), err }), + } + } else { + debug!("Opening existing log file at '{}'...", self.path.display()); + match OpenOptions::new().write(true).append(true).open(&self.path).await { + Ok(handle) => handle, + Err(err) => return Err(Error::FileOpen { path: self.path.clone(), err }), + } + }; + + // // Navigate to the end of the file + // let end_pos: u64 = match handle.seek(SeekFrom::End(0)).await { + // Ok(pos) => pos, + // Err(err) => return Err(FileLoggerError::FileSeek { path: self.path.clone(), err }), + // }; + // debug!("End of file is after {end_pos} bytes"); + + // Write the message + debug!("Writing {}-statement to logfile...", stmt.variant()); + // Write who wrote it + write_file!(self.path.clone(), &mut handle, "[{}]", self.id).await?; + // Print the timestamp + write_file!(self.path.clone(), &mut handle, "[{}]", chrono::Local::now().format("%Y-%m-%d %H:%M:%S")).await?; + // Then write the logged message + match serde_json::to_string(&stmt) { + Ok(message) => writeln_file!(self.path.clone(), &mut handle, " {message}").await?, + Err(err) => return Err(Error::LogStatementSerialize { kind: format!("{stmt:?}"), err }), + } + + // Finally flush the file + debug!("Flushing log file..."); + if let Err(err) = handle.shutdown().await { + return Err(Error::FileShutdown { path: self.path.clone(), err }); + } + drop(handle); + + // Done, a smashing success + Ok(()) + } +} +impl AuditLogger for FileLogger { + type Error = Error; + + #[inline] + fn log_response<'a, R>( + &'a self, + reference: &'a str, + response: &'a ReasonerResponse, + raw: Option<&'a str>, + ) -> impl 'a + Future> + where + R: Display, + { + async move { + // Serialize the response first + let res: ReasonerResponse = match response { + ReasonerResponse::Success => ReasonerResponse::Success, + ReasonerResponse::Violated(r) => ReasonerResponse::Violated(r.to_string()), + }; + + // Log it + self.log(LogStatement::ReasonerResponse { + reference: Cow::Borrowed(reference), + response: Cow::Borrowed(&res), + raw: raw.map(Cow::Borrowed), + }) + .await + } + } +} diff --git a/lib/loggers/file/src/stmt.rs b/lib/loggers/file/src/stmt.rs new file mode 100644 index 0000000..1826089 --- /dev/null +++ b/lib/loggers/file/src/stmt.rs @@ -0,0 +1,28 @@ +// STMT.rs +// by Lut99 +// +// Created: +// 10 Oct 2024, 14:24:22 +// Last edited: +// 10 Oct 2024, 14:32:46 +// Auto updated? +// Yes +// +// Description: +//! Defines the internal representation of a log statement. +// + +use std::borrow::Cow; + +use enum_debug::EnumDebug; +use serde::{Deserialize, Serialize}; +use spec::reasonerconn::ReasonerResponse; + + +/***** LIBRARY *****/ +/// Defines the internal representation of a log statement. +#[derive(Clone, Debug, Deserialize, EnumDebug, Serialize)] +pub enum LogStatement<'a, T: Clone> { + /// Logging a reasoner response. + ReasonerResponse { reference: Cow<'a, str>, response: Cow<'a, ReasonerResponse>, raw: Option> }, +} diff --git a/lib/loggers/mock/Cargo.toml b/lib/loggers/mock/Cargo.toml new file mode 100644 index 0000000..8dd3863 --- /dev/null +++ b/lib/loggers/mock/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "mock-logger" +description = "Implements an audit logger that doesn't audit log anything but just logs a little using `tracing`." +edition = "2021" +authors = [ "Bas Kloosterman", "Tim Müller" ] +version = "0.2.0" +repository.workspace = true +license.workspace = true + + +[dependencies] +tracing = "0.1.40" + +spec = { path = "../../spec", package = "specifications" } + + +[features] +default = [] diff --git a/lib/loggers/mock/src/lib.rs b/lib/loggers/mock/src/lib.rs new file mode 100644 index 0000000..578852e --- /dev/null +++ b/lib/loggers/mock/src/lib.rs @@ -0,0 +1,19 @@ +// LIB.rs +// by Lut99 +// +// Created: +// 10 Oct 2024, 14:45:47 +// Last edited: +// 10 Oct 2024, 14:46:39 +// Auto updated? +// Yes +// +// Description: +//! Implements an audit logger that writes everything to a file. +// + +// Declare modules +mod logger; + +// Bring some of it into this namespace. +pub use logger::*; diff --git a/lib/loggers/mock/src/logger.rs b/lib/loggers/mock/src/logger.rs new file mode 100644 index 0000000..2acc46b --- /dev/null +++ b/lib/loggers/mock/src/logger.rs @@ -0,0 +1,56 @@ +// LOGGER.rs +// by Lut99 +// +// Created: +// 10 Oct 2024, 14:46:33 +// Last edited: +// 10 Oct 2024, 14:47:56 +// Auto updated? +// Yes +// +// Description: +//! Defines the actual [`AuditLogger`] itself. +// + +use std::convert::Infallible; +use std::fmt::Display; +use std::future::Future; + +use spec::auditlogger::AuditLogger; +use spec::reasonerconn::ReasonerResponse; + + +/***** LIBRARY *****/ +/// Implements an [`AuditLogger`] that doesn't log anything. +#[derive(Clone, Copy, Debug)] +pub struct MockLogger; +impl Default for MockLogger { + #[inline] + fn default() -> Self { Self::new() } +} +impl MockLogger { + /// Constructor for the MockLogger that initializes it. + /// # Returns + /// A new instance of self, ready for action. + #[inline] + pub const fn new() -> Self { Self } +} +impl AuditLogger for MockLogger { + type Error = Infallible; + + #[inline] + fn log_response<'a, R>( + &'a self, + _reference: &'a str, + _response: &'a ReasonerResponse, + _raw: Option<&'a str>, + ) -> impl 'a + Future> + where + R: Display, + { + async move { + println!("AUDIT LOG: log_response"); + Ok(()) + } + } +} diff --git a/lib/nested-cli-parser/Cargo.toml b/lib/nested-cli-parser/Cargo.toml deleted file mode 100644 index 28fe6d6..0000000 --- a/lib/nested-cli-parser/Cargo.toml +++ /dev/null @@ -1,22 +0,0 @@ -[package] -name = "nested-cli-parser" -rust-version = "1.78" -edition = "2021" -version.workspace = true -repository.workspace = true -authors.workspace = true -license.workspace = true - - -[dependencies] -unicode-segmentation = { version = "1.10", optional = true } - - -[features] -default = [ "map_parser" ] -map_parser = [ "dep:unicode-segmentation" ] - - -[lints.clippy] -# I truly don't know why they feel the range.contains() syntax is easier on the eyes -manual_range_contains = "allow" diff --git a/lib/nested-cli-parser/src/lib.rs b/lib/nested-cli-parser/src/lib.rs deleted file mode 100644 index 417aff9..0000000 --- a/lib/nested-cli-parser/src/lib.rs +++ /dev/null @@ -1,22 +0,0 @@ -// LIB.rs -// by Lut99 -// -// Created: -// 18 Jan 2024, 16:05:48 -// Last edited: -// 19 Jan 2024, 14:50:14 -// Auto updated? -// Yes -// -// Description: -//! Defines an interface (and some implementations) for defining CLI -//! parsing for plugins by processing nested commands. -// - -// Declare nested modules -#[cfg(feature = "map_parser")] -pub mod map_parser; -pub mod spec; - -// Bring some of it into the main namespace -pub use spec::*; diff --git a/lib/nested-cli-parser/src/map_parser.rs b/lib/nested-cli-parser/src/map_parser.rs deleted file mode 100644 index 9956265..0000000 --- a/lib/nested-cli-parser/src/map_parser.rs +++ /dev/null @@ -1,356 +0,0 @@ -// MAP PARSER.rs -// by Lut99 -// -// Created: -// 18 Jan 2024, 16:07:04 -// Last edited: -// 12 Jun 2024, 17:50:54 -// Auto updated? -// Yes -// -// Description: -//! Common implementation of a nested parser that simply parses a list -//! of key/value pairs. -// - -use std::collections::HashMap; -use std::error; -use std::fmt::{Display, Formatter, Result as FResult}; - -use unicode_segmentation::UnicodeSegmentation; - -use crate::NestedCliParser; - -/***** HELPER MACROS *****/ -/// Given a character, determines if it's valid for a key identifier. -macro_rules! is_valid_key_char { - ($c:ident) => {{ - if !$c.is_ascii() || $c.len() != 1 { - false - } else { - let c: char = $c.chars().next().unwrap(); - ((c >= '0' && c <= '9') || (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || c == '-' || c == '_') - } - }}; -} - -/***** ERRORS *****/ -/// Defines errors that may originate from the [`MapParser`]. -#[derive(Debug)] -pub enum Error { - /// An argument contained an unescaped `=` twice. - DuplicateEquals { prev_pos: usize, pos: usize }, - /// An equals sign was found without an actual value. - EmptyValue { pos: usize }, - /// A key contained an illegal character for a key. - IllegalKeyChar { pos: usize, c: String }, - /// A key was encountered that wasn't known. - UnknownKey { pos: usize, key: String }, - /// An escape character was found without a matching next character. - UnmatchedEscape { esc: usize, pos: usize }, - /// A quote was found without a terminating counterpart. - UnmatchedQuote { first: usize, pos: usize }, -} -impl Display for Error { - fn fmt(&self, f: &mut Formatter<'_>) -> FResult { - use Error::*; - match self { - DuplicateEquals { prev_pos, pos } => { - write!(f, "Encountered a second unescaped equals sign '=' at character {pos} (first equals sign was at {prev_pos})") - }, - EmptyValue { pos } => write!(f, "Expected value for key after equals sign '=' at character {pos}"), - IllegalKeyChar { pos, c } => write!(f, "Encountered illegal character for a key {c:?} at position {pos}"), - UnknownKey { pos, key } => write!(f, "Unknown option '{key}' at character {pos}"), - UnmatchedEscape { esc, pos } => write!(f, "Expected escaped character at position {pos} (to follow up escape character '/' at {esc})"), - UnmatchedQuote { first, pos } => { - write!(f, "Expected terminating quote '\"' at position {pos} (to close quote character '\"' at {first})") - }, - } - } -} -impl error::Error for Error {} - -/***** HELPER FUNCTIONS *****/ -/// Parses a single key/value argument. -/// -/// # Arguments -/// - `keys`: A list of known keys that can we accept. -/// - `arg`: The buffer containing the single argument. -/// - `arg_pos`: The position of the argument within the entire input. -/// -/// # Returns -/// A pair of the parsed key and an optional value if the user gave one. -/// -/// # Errors -/// This function errors if the input was not a valid key/value pair. -fn parse_arg(keys: &[(char, String, String)], arg: &str, arg_pos: usize) -> Result<(String, Option), Error> { - // Go through the buffer to find the equals character in a similar fashion - let mut key: Option = None; - let mut mode: ArgParseMode = ArgParseMode::Key; - let mut buf: String = String::new(); - for (pos, c) in arg.grapheme_indices(true) { - match &mode { - ArgParseMode::Key => match c { - // Equals is how we recognize we've seen the key - "=" => { - // The buffer is now only valid alphanumerical values - key = Some(buf); - buf = String::new(); - - // Move to the value - mode = ArgParseMode::Value(pos); - }, - - // Only accept key characters tho - c if is_valid_key_char!(c) => buf.push_str(c), - c => return Err(Error::IllegalKeyChar { pos: arg_pos + pos, c: c.into() }), - }, - ArgParseMode::Value(eq_pos) => match c { - // For values, we only focus on escapes - "\"" => mode = ArgParseMode::Quotes(pos, Box::new(mode)), - "\\" => mode = ArgParseMode::Escaped(pos, Box::new(mode)), - - // The rest is all valid - except equals - "=" => return Err(Error::DuplicateEquals { prev_pos: *eq_pos, pos: arg_pos + pos }), - c => buf.push_str(c), - }, - - ArgParseMode::Quotes(_, prev_mode) => match c { - // We end quote mode if we see a quote :) - "\"" => mode = (**prev_mode).clone(), - - // Escape on the escape character - "\\" => mode = ArgParseMode::Escaped(pos, Box::new(mode)), - - // The rest is still valid - c => buf.push_str(c), - }, - ArgParseMode::Escaped(_, prev_mode) => match c { - // A list of special whitespace characters - "n" => { - buf.push('\n'); - mode = (**prev_mode).clone(); - }, - "t" => { - buf.push('\t'); - mode = (**prev_mode).clone(); - }, - "0" => { - buf.push('\0'); - mode = (**prev_mode).clone(); - }, - - // The rest is passed literally (including quotes and escape characters itself) - c => { - buf.push_str(c); - mode = (**prev_mode).clone(); - }, - }, - } - } - - // Assert nothing is left unmatched - match mode { - ArgParseMode::Quotes(first_pos, _) => return Err(Error::UnmatchedQuote { first: arg_pos + first_pos, pos: arg_pos + buf.len() }), - ArgParseMode::Escaped(esc_pos, _) => return Err(Error::UnmatchedEscape { esc: arg_pos + esc_pos, pos: arg_pos + buf.len() }), - ArgParseMode::Key | ArgParseMode::Value(_) => {}, - } - - // Resolve the remaining buffer - let (key, value): (String, Option) = if key.is_none() { - (buf, None) - } else if !buf.is_empty() { - // I'll allow this warning here, because refactoring into `match` will ruin the nice three-case logic here IMO - #[allow(clippy::unnecessary_unwrap)] - (key.unwrap(), Some(buf)) - } else { - return Err(Error::EmptyValue { pos: arg_pos + buf.len() }); - }; - - // Assert the key checks out - match keys.iter().find(|(short, long, _)| { - let mut buf: [u8; 4] = [0; 4]; - short.encode_utf8(&mut buf) == &key || long == &key - }) { - Some((_, long, _)) => Ok((long.clone(), value)), - None => Err(Error::UnknownKey { pos: arg_pos, key }), - } -} - -/***** HELPERS *****/ -/// Defines possible modes of parsing the entire CLI string. -#[derive(Clone, Debug)] -enum ParseMode { - /// The start mode, where we assume a raw string unless we see quotes - Start, - /// The quotes mode, where we've seen an outer quote. - /// - /// The position of the first quote is stored. - Quotes(usize), - /// We've entered an escaped character. - /// - /// The position of the backslash (`\`) is stored, together with the previous state. - Escaped(usize, Box), -} - -/// Defines possible modes of parsing a single key/value pair. -#[derive(Clone, Debug)] -enum ArgParseMode { - /// The start mode, where we're parsing keys - Key, - /// The default mode for parsing values. - /// - /// The position of the separating equals is stored. - Value(usize), - /// The quotes mode, where we've seen an outer quote. - /// - /// The position of the first quote is stored, together with the previous state. - Quotes(usize, Box), - /// We've entered an escaped character. - /// - /// The position of the backslash (`\`) is stored, together with the previous state. - Escaped(usize, Box), -} - -/***** LIBRARY *****/ -/// Common implementation of a nested parser that simply parses a list of key/value pairs. -#[derive(Debug)] -pub struct MapParser { - /// The list of keys that are recognized by this parser. - pub keys: Vec<(char, String, String)>, -} -impl MapParser { - /// Constructor for the MapParser. - /// - /// # Arguments - /// - `keys`: A set of keys that are recognized by the parser. All others will eventually trigger [`UnknownKey`](Error::UnknownKey) errors down the line. - /// - /// # Returns - /// A new [`MapParser`] instance. - /// - /// # Panics - /// This function panics if any of the keys are not simple alphanumber strings (only underscores and dashes are allowed). - pub fn new, S3: Into>(keys: impl IntoIterator) -> Self { - // Build the set of keys - let iter = keys.into_iter(); - let (min, max): (usize, Option) = iter.size_hint(); - let mut keys: Vec<(char, String, String)> = Vec::with_capacity(if let Some(max) = max { max } else { min }); - for (short, long, desc) in iter { - let long: String = long.into(); - let desc: String = desc.into(); - - // Assert the short- and longname only exists of valid characters - let mut buf: [u8; 4] = [0; 4]; - let sshort: &str = short.encode_utf8(&mut buf); - if !is_valid_key_char!(sshort) { - panic!("Given shortname {short:?} is not valid (only alphanumeric characters, '-' and '_' are allowed)"); - } - for (i, c) in long.grapheme_indices(true) { - if !is_valid_key_char!(c) { - panic!( - "Given longname '{long}' has illegal character {c:?} at index {i} (only alphanumeric characters, '-' and '_' are allowed)" - ); - } - } - - // Add it if it passes - keys.push((short, long, desc)); - } - - // OK, build self - Self { keys } - } -} -impl NestedCliParser for MapParser { - type Args = HashMap>; - type ParseError = Error; - - fn help_fmt(&self, name: &str, short: char, long: &str, f: &mut Formatter<'_>) -> FResult { - writeln!(f, "{name} nested arguments")?; - writeln!(f, "Usage: -{short},--{long} \"[]\"")?; - writeln!(f)?; - writeln!(f, "Options:")?; - for (short, long, desc) in &self.keys { - writeln!(f, " {short}=,{long}=")?; - writeln!(f, " {desc}")?; - } - writeln!(f) - } - - fn parse(&self, args: &str) -> Result { - // Parse the arguments using a little state machine to be respectful to quotes - let mut parsed_args: HashMap> = HashMap::with_capacity(args.chars().filter(|c| *c == ',').count()); - let mut mode: ParseMode = ParseMode::Start; - let mut buf: String = String::new(); - for (pos, c) in args.grapheme_indices(true) { - match mode { - // Simply parse the contents until we discover a comma - ParseMode::Start => match c { - // Comma indicate the end of one arguments - "," => { - // `buf` now contains the entire argument, so parse it as such - let (key, value): (String, Option) = parse_arg(&self.keys, &buf, pos)?; - - // Alright, add the key/value pair! - parsed_args.insert(key, value); - }, - - // Mode changers - "\"" => mode = ParseMode::Quotes(pos), - "\\" => mode = ParseMode::Escaped(pos, Box::new(mode)), - - // Default; simply accept into the buffer - c => buf.push_str(c), - }, - - ParseMode::Quotes(_) => match c { - // We end quote mode if we see a quote :) - "\"" => mode = ParseMode::Start, - - // Escape on the escape character - "\\" => mode = ParseMode::Escaped(pos, Box::new(mode)), - - // The rest is still valid - c => buf.push_str(c), - }, - ParseMode::Escaped(_, prev_mode) => match c { - // A list of special whitespace characters - "n" => { - buf.push('\n'); - mode = *prev_mode; - }, - "t" => { - buf.push('\t'); - mode = *prev_mode; - }, - "0" => { - buf.push('\0'); - mode = *prev_mode; - }, - - // The rest is passed literally (including quotes and escape characters itself) - c => { - buf.push_str(c); - mode = *prev_mode; - }, - }, - } - } - - // Assert nothing is left unmatched - match mode { - ParseMode::Quotes(first_pos) => return Err(Error::UnmatchedQuote { first: first_pos, pos: args.len() }), - ParseMode::Escaped(esc_pos, _) => return Err(Error::UnmatchedEscape { esc: esc_pos, pos: args.len() }), - ParseMode::Start => {}, - } - - // Resolve the remaining buffer, if any - if !buf.is_empty() { - let (key, value): (String, Option) = parse_arg(&self.keys, &buf, args.len() - buf.len())?; - parsed_args.insert(key, value); - } - - // Done, return the parsed arguments! - Ok(parsed_args) - } -} diff --git a/lib/nested-cli-parser/src/spec.rs b/lib/nested-cli-parser/src/spec.rs deleted file mode 100644 index be9b3f2..0000000 --- a/lib/nested-cli-parser/src/spec.rs +++ /dev/null @@ -1,102 +0,0 @@ -// SPEC.rs -// by Lut99 -// -// Created: -// 18 Jan 2024, 16:06:11 -// Last edited: -// 07 Feb 2024, 18:02:27 -// Auto updated? -// Yes -// -// Description: -//! Defines the interface for the implementations provided in this -//! crate. -// - -use std::error::Error; -use std::fmt::{Display, Formatter, Result as FResult}; - -/***** FORMATTERS *****/ -/// Formats any given [`NestedCliParser`]. -pub struct NestedCliParserHelpFormatter<'n, 'l, P> { - /// A name for whatever we're parsing. - name: &'n str, - /// A shortname for the argument that contains the nested arguments we parse. - short: char, - /// A longname for the argument that contains the nested arguments we parse. - long: &'l str, - /// The parser in question. - parser: P, -} -impl Display for NestedCliParserHelpFormatter<'_, '_, P> { - #[inline] - fn fmt(&self, f: &mut Formatter<'_>) -> FResult { self.parser.help_fmt(self.name, self.short, self.long, f) } -} - -/***** LIBRARY *****/ -/// Defines that a particular type can parse nested CLI commands. -/// -/// The commands should be given as a string, which the parser then extracts as arguments. -pub trait NestedCliParser { - /// The associated state that is parsed from the nested argument list. - type Args; - /// Any error that is thrown when parsing. - type ParseError: Error; - - /// Formats the help string of the NestedCliParser. - /// - /// You typically don't call this method yourself, but instead use [`NestedCliParser::help()`] to call it for you through [`Display`]. - /// - /// # Arguments - /// - `name`: Some name of whatever we're parsing. - /// - `short`: A shortname for the argument that contains the nested arguments we parse. - /// - `long`: A longname for the argument that contains the nested arguments we parse. - /// - `f`: Some [`Formatter`] to which to write the help string. - /// - /// # Errors - /// This function errors if it failed to write to the given `f`ormatter. - fn help_fmt(&self, name: &str, short: char, long: &str, f: &mut Formatter<'_>) -> FResult; - /// Returns a formatter that can be used to [`Display`] the help string for this NestedCliParser. - /// - /// In contrast to [`NestedCliParser::help()`], this one consumes the parser to avoid the lifetime dependency. - /// - /// # Arguments - /// - `name`: Some name of whatever we're parsing. - /// - `short`: A shortname for the argument that contains the nested arguments we parse. - /// - `long`: A longname for the argument that contains the nested arguments we parse. - /// - /// # Returns - /// A [`NestedCliParserHelpFormatter`] that calls [`NestedCliParser::help_fmt()`] for the formatter to which it is applied. - #[inline] - fn into_help<'n, 'l>(self, name: &'n str, short: char, long: &'l str) -> NestedCliParserHelpFormatter<'n, 'l, Self> - where - Self: Sized, - { - NestedCliParserHelpFormatter { name, short, long, parser: self } - } - /// Returns a formatter that can be used to [`Display`] the help string for this NestedCliParser. - /// - /// # Arguments - /// - `name`: Some name of whatever we're parsing. - /// - `short`: A shortname for the argument that contains the nested arguments we parse. - /// - `long`: A longname for the argument that contains the nested arguments we parse. - /// - /// # Returns - /// A [`NestedCliParserHelpFormatter`] that calls [`NestedCliParser::help_fmt()`] for the formatter to which it is applied. - #[inline] - fn help<'s, 'n, 'l>(&'s self, name: &'n str, short: char, long: &'l str) -> NestedCliParserHelpFormatter<'n, 'l, &'s Self> { - NestedCliParserHelpFormatter { name, short, long, parser: self } - } - - /// Parses the given string as a set of nested CLI arguments. - /// - /// # Arguments - /// - `args`: The raw [`&str`] to parse. - /// - /// # Returns - /// A new instance of [`Self::Args`] that contains the information parsed from the raw `args`. - /// - /// # Errors - /// This function has free roam to error as it desires. Typically, however, this should be when the input is invalid for this parser. - fn parse(&self, args: &str) -> Result; -} diff --git a/lib/policy/Cargo.toml b/lib/policy/Cargo.toml deleted file mode 100644 index 37d33ff..0000000 --- a/lib/policy/Cargo.toml +++ /dev/null @@ -1,27 +0,0 @@ -[package] -name = "policy" -rust-version = "1.81" -edition = "2021" -authors = ["Bas Kloosterman"] -version.workspace = true -repository.workspace = true -license.workspace = true - - -[dependencies] -# Crates.io -async-trait = "0.1.67" -chrono = { version = "0.4.35", features=["serde"] } -log = "0.4.22" -serde = {version="1.0.204", features=["derive"]} -serde_json = {version = "1.0.120" , features = ["raw_value"]} -warp = "0.3" - -[dev-dependencies] -# Crates.io -clap = { version = "4.5.6", features = ["derive"] } -log = "0.4.22" - -# Workspace dependencies -error-trace.workspace = true -humanlog.workspace = true diff --git a/lib/policy/src/lib.rs b/lib/policy/src/lib.rs deleted file mode 100644 index 5a81b03..0000000 --- a/lib/policy/src/lib.rs +++ /dev/null @@ -1,120 +0,0 @@ -use core::fmt::Debug; -use std::fmt::Display; -use std::future::Future; - -use chrono::{DateTime, Local}; -use serde::{Deserialize, Serialize}; - -#[derive(Serialize, Deserialize, Debug, Clone)] -pub struct PolicyContent { - pub reasoner: String, - pub reasoner_version: String, - pub content: Box, -} - -#[derive(Serialize, Deserialize, Debug, Clone)] -pub struct PolicyVersion { - pub creator: Option, - pub created_at: DateTime, - pub version: Option, - pub version_description: String, - /// reasoner_connector_context contains the hash of the reasoner connector's base definitions - pub reasoner_connector_context: String, -} - -#[derive(Serialize, Deserialize, Debug, Clone)] -pub struct ActivePolicy { - pub version: String, -} - -#[derive(Serialize, Deserialize, Debug, Clone)] -pub struct Policy { - pub description: String, - #[serde(flatten)] - pub version: PolicyVersion, - pub content: Vec, -} - -#[derive(Debug)] -pub enum PolicyDataError { - NotFound, - GeneralError(String), -} - -impl Display for PolicyDataError { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - PolicyDataError::NotFound => write!(f, "PolicyData error: Item not found"), - PolicyDataError::GeneralError(err) => write!(f, "PolicyData general error: {}", err), - } - } -} - -impl std::error::Error for PolicyDataError {} - -impl warp::reject::Reject for PolicyDataError {} - -// impl std::error::Error for PolicyDataError { -// fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { -// match self { -// PolicyDataError::NotFound => Some(err), -// PolicyDataError::GeneralError() => todo!(), -// } -// } -// } - -// pub trait Transaction { -// pub fn cancel(self) -> Result<(), Error> { -// // Do the rollback -// //... - -// // Drop self _without_ calling its `drop()` -// std::mem::forget(self); -// } -// pub fn accept(self) -> Result { -// // Do the rollback -// //... - -// // Drop self _without_ calling its `drop()` -// std::mem::forget(self); -// } -// } -// impl Drop { -// fn drop(&mut self) { -// // Do commit -// panic!("should not be called implicit, user should execute cancel or accept") -// } -// } - -pub struct Context { - pub initiator: String, -} - -#[async_trait::async_trait] -pub trait PolicyDataAccess { - type Error; - #[must_use] - async fn add_version>>( - &self, - version: Policy, - context: Context, - transaction: impl 'static + Send + FnOnce(Policy) -> F, - ) -> Result; - async fn get_version(&self, version: i64) -> Result; - async fn get_most_recent(&self) -> Result; - async fn get_versions(&self) -> Result, PolicyDataError>; - async fn get_active(&self) -> Result; - #[must_use] - async fn set_active>>( - &self, - version: i64, - context: Context, - transaction: impl 'static + Send + FnOnce(Policy) -> F, - ) -> Result; - #[must_use] - async fn deactivate_policy>>( - &self, - context: Context, - transaction: impl 'static + Send + FnOnce() -> F, - ) -> Result<(), PolicyDataError>; -} diff --git a/lib/reasonerconn/Cargo.toml b/lib/reasonerconn/Cargo.toml deleted file mode 100644 index edadcf6..0000000 --- a/lib/reasonerconn/Cargo.toml +++ /dev/null @@ -1,40 +0,0 @@ -[package] -name = "reasonerconn" -description = "Defines the connector that translates for and communicates with a specific reasoner." -rust-version = "1.81" -edition = "2021" -authors = ["Bas Kloosterman"] -version.workspace = true -repository.workspace = true -license.workspace = true - - -[dependencies] -# Crates.io -anyhow = "1.0.66" -async-trait = "0.1.67" -log = "0.4.22" -serde = { version="1.0.204", features=["derive"] } -serde_json = { version = "1.0.120" , features = ["raw_value"] } -tokio = { version = "1.38.0", features = ["full"] } - -# Path -audit-logger = { path = "../audit-logger" } -policy = { path = "../policy" } -state-resolver = { path = "../state-resolver" } -workflow = { path = "../workflow", features = ["eflint"]} - -# Workspace dependencies -eflint-json.workspace = true -enum-debug.workspace = true -transform.workspace = true - - -[dev-dependencies] -# Crates.io -clap = { version = "4.5.6", features = ["derive"] } -log = "0.4.22" - -# Workspace dependencies -error-trace.workspace = true -humanlog.workspace = true diff --git a/lib/reasonerconn/src/lib.rs b/lib/reasonerconn/src/lib.rs deleted file mode 100644 index 3592b79..0000000 --- a/lib/reasonerconn/src/lib.rs +++ /dev/null @@ -1,127 +0,0 @@ -use std::fmt; - -use audit_logger::{ConnectorWithContext, ReasonerConnectorAuditLogger, SessionedConnectorAuditLogger}; -use policy::Policy; -use serde::{Deserialize, Serialize}; -use state_resolver::State; -use workflow::spec::Workflow; - -#[derive(Debug)] -pub struct ReasonerConnError { - err: String, -} - -impl fmt::Display for ReasonerConnError { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { self.err.fmt(f) } -} - -impl ReasonerConnError { - pub fn new>(t: T) -> Self { Self { err: t.into() } } - - pub fn from(t: T) -> Self { Self { err: format!("{}", t) } } -} - -impl std::error::Error for ReasonerConnError { - fn description(&self) -> &str { &self.err } -} - -#[derive(Serialize, Deserialize)] -pub struct ReasonerResponse { - pub success: bool, - pub errors: Vec, -} - -impl ReasonerResponse { - pub fn new(success: bool, errors: Vec) -> Self { ReasonerResponse { success, errors } } -} - -#[async_trait::async_trait] -pub trait ReasonerConnector: ConnectorWithContext { - async fn execute_task( - &self, - logger: SessionedConnectorAuditLogger, - policy: Policy, - state: State, - workflow: Workflow, - task: String, - ) -> Result; - async fn access_data_request( - &self, - logger: SessionedConnectorAuditLogger, - policy: Policy, - state: State, - workflow: Workflow, - data: String, - task: Option, - ) -> Result; - async fn workflow_validation_request( - &self, - logger: SessionedConnectorAuditLogger, - policy: Policy, - state: State, - workflow: Workflow, - ) -> Result; -} - -// #[async_trait::async_trait] -// pub trait LoggingReasonerConnector: ReasonerConnector + ReasonerConnectorAuditLogger { -// fn reference(&self) -> String; -// async fn log_raw_result(&self, raw_result: &str) -> Result<(), ReasonerConnError> { -// self.log_reasoner_response(&self.reference(), raw_result).await.map_err(|err| ReasonerConnError { err: "test".into() }) -// } -// fn new_session(&self, session_id: String) -> Self; -// } - -// pub struct DefaultLoggingReasonerConnector { -// session: Option, -// connector: Arc, -// logger: Arc, -// } - -// impl DefaultLoggingReasonerConnector { -// fn new(connector: Connector, logger: Logger) -> Self { Self { session: None, connector: Arc::new(connector), logger: Arc::new(logger) } } -// } - -// #[async_trait::async_trait] -// impl LoggingReasonerConnector -// for DefaultLoggingReasonerConnector -// { -// fn reference(&self) -> String { return self.session.clone().unwrap() } - -// fn new_session(&self, session_id: String) -> Self { -// Self { session: Some(session_id), connector: self.connector.clone(), logger: self.logger.clone() } -// } -// } - -// #[async_trait::async_trait] -// impl ReasonerConnectorAuditLogger -// for DefaultLoggingReasonerConnector -// { -// async fn log_reasoner_response(&self, reference: &str, response: &str) -> Result<(), AuditLoggerError> { -// self.logger.log_reasoner_response(reference, response).await -// } -// } - -// #[async_trait::async_trait] -// impl ReasonerConnector -// for DefaultLoggingReasonerConnector -// { -// async fn execute_task(&self, policy: Policy, state: State, workflow: Workflow, task: String) -> Result { -// self.connector.execute_task(policy, state, workflow, task).await -// } - -// async fn access_data_request( -// &self, -// policy: Policy, -// state: State, -// workflow: Workflow, -// data: String, -// task: Option, -// ) -> Result { -// self.connector.access_data_request(policy, state, workflow, data, task).await -// } - -// async fn workflow_validation_request(&self, policy: Policy, state: State, workflow: Workflow) -> Result { -// self.connector.workflow_validation_request(policy, state, workflow).await -// } -// } diff --git a/lib/reasoners/eflint-json/Cargo.toml b/lib/reasoners/eflint-json/Cargo.toml new file mode 100644 index 0000000..ea92ae0 --- /dev/null +++ b/lib/reasoners/eflint-json/Cargo.toml @@ -0,0 +1,24 @@ +[package] +name = "eflint-json-reasoner" +description = "Implements eFLINT as a backend reasoner for the policy reasoner." +edition = "2021" +authors = [ "Bas Kloosterman", "Tim Müller" ] +version = "0.2.0" +repository.workspace = true +license.workspace = true + + +[dependencies] +reqwest = { version = "0.12.0", features = ["json"] } +serde_json = "1.0.120" +tracing = "0.1.40" + +eflint-json = { git = "https://gitlab.com/eflint/json-spec-rs", branch = "incorrect-is-invariant" } +error-trace = { git = "https://github.com/Lut99/error-trace-rs", tag = "v3.1.0" } + +# workflow = { path = "../../../reasoner-workflow", package = "policy-reasoner-workflow" } +spec = { path = "../../spec", package = "specifications" } + + +[features] +default = [] diff --git a/lib/reasoners/eflint-json/src/lib.rs b/lib/reasoners/eflint-json/src/lib.rs new file mode 100644 index 0000000..df0437e --- /dev/null +++ b/lib/reasoners/eflint-json/src/lib.rs @@ -0,0 +1,22 @@ +// LIB.rs +// by Lut99 +// +// Created: +// 09 Oct 2024, 15:50:24 +// Last edited: +// 11 Oct 2024, 15:45:44 +// Auto updated? +// Yes +// +// Description: +//! Implements eFLINT as a backend reasoner for the policy reasoner. +// + +// Declare the modules +mod reasonerconn; +pub mod reasons; +pub mod spec; + +// Use some of that in the crate namespace +pub use eflint_json as json; +pub use reasonerconn::*; diff --git a/lib/reasoners/eflint-json/src/policy.rs b/lib/reasoners/eflint-json/src/policy.rs new file mode 100644 index 0000000..6c7defe --- /dev/null +++ b/lib/reasoners/eflint-json/src/policy.rs @@ -0,0 +1,13 @@ +// POLICY.rs +// by Lut99 +// +// Created: +// 09 Oct 2024, 15:59:29 +// Last edited: +// 09 Oct 2024, 15:59:36 +// Auto updated? +// Yes +// +// Description: +//! Defines how an eFLINT policy looks like. +// diff --git a/lib/reasoners/eflint-json/src/reasonerconn.rs b/lib/reasoners/eflint-json/src/reasonerconn.rs new file mode 100644 index 0000000..7a2bc75 --- /dev/null +++ b/lib/reasoners/eflint-json/src/reasonerconn.rs @@ -0,0 +1,260 @@ +// REASONERCONN.rs +// by Lut99 +// +// Created: +// 09 Oct 2024, 15:52:06 +// Last edited: +// 11 Oct 2024, 16:56:24 +// Auto updated? +// Yes +// +// Description: +//! Defines a [`ReasonerConnector`] for an eFLINT JSON reasoner. +// + +use std::collections::HashMap; +use std::error; +use std::fmt::{Display, Formatter, Result as FResult}; +use std::future::Future; +use std::marker::PhantomData; + +use eflint_json::spec::auxillary::Version; +use eflint_json::spec::{Phrase, PhraseResult, Request, RequestCommon, RequestPhrases, ResponsePhrases}; +use error_trace::{ErrorTrace as _, Trace}; +use spec::auditlogger::{AuditLogger, SessionedAuditLogger}; +use spec::reasonerconn::{ReasonerConnector, ReasonerResponse}; +use tracing::{Level, debug, span}; + +use crate::reasons::ReasonHandler; +use crate::spec::EFlintable; + + +/***** ERRORS *****/ +/// Defines the errors returned by the [`EFlintJsonReasonerConnectorector`]. +#[derive(Debug)] +pub enum Error { + /// Failed to log the reasoner's response to the given logger. + LogResponse { to: &'static str, err: Trace }, + /// Failed to receive a [`ResponsePhrases`] to the remote reasoner (as raw). + ReasonerResponse { addr: String, err: reqwest::Error }, + /// Failed to send a [`RequestPhrases`] to the remote reasoner. + ReasonerRequest { addr: String, err: reqwest::Error }, + /// Failed to extract the reasons for failure (i.e., violations) from a parsed [`ResponsePhrases`] object. + ResponseExtractReasons { addr: String, raw: String, err: R }, + /// The query returned in the response was of an illegal ending type. + ResponseIllegalQuery { addr: String, raw: String }, + /// Failed to parse the response of the reasoner as a valid [`ResponsePhrases`] object. + ResponseParse { addr: String, raw: String, err: serde_json::Error }, + /// Failed to serialize the state to eFLINT. + StateToEFlint { err: S }, + /// Failed ot serialize the question to eFLINT. + QuestionToEFlint { err: Q }, +} +impl Display for Error { + #[inline] + fn fmt(&self, f: &mut Formatter<'_>) -> FResult { + use Error::*; + match self { + LogResponse { to, .. } => write!(f, "Failed to log the reasoner's response to {to}"), + ReasonerResponse { addr, .. } => write!(f, "Failed to fetch reply from remote reasoner at {addr:?}"), + ReasonerRequest { addr, .. } => write!(f, "Failed to set PhrasesRequest to reasoner at {addr:?}"), + ResponseExtractReasons { addr, raw, .. } => write!( + f, + "Failed to extract reasons (i.e., violations) from the response of reasoner at {:?}\n\nParsed response:\n{}\n{}\n{}\n\n", + addr, + (0..80).map(|_| '-').collect::(), + raw, + (0..80).map(|_| '-').collect::() + ), + ResponseIllegalQuery { addr, raw } => write!( + f, + "Reasoner at {:?} returned result of instance query as last state change; this is unsupported!\n\nParsed response:\n{}\n{}\n{}\n\n", + addr, + (0..80).map(|_| '-').collect::(), + raw, + (0..80).map(|_| '-').collect::() + ), + ResponseParse { addr, raw, .. } => write!( + f, + "Failed to parse response from reasoner at {:?}\n\nRaw response:\n{}\n{}\n{}\n\n", + addr, + (0..80).map(|_| '-').collect::(), + raw, + (0..80).map(|_| '-').collect::() + ), + StateToEFlint { .. } => write!(f, "Failed to serialize given state to eFLINT"), + QuestionToEFlint { .. } => write!(f, "Failed to serialize given question to eFLINT"), + } + } +} +impl error::Error for Error { + #[inline] + fn source(&self) -> Option<&(dyn error::Error + 'static)> { + use Error::*; + match self { + LogResponse { err, .. } => Some(err), + ReasonerResponse { err, .. } => Some(err), + ReasonerRequest { err, .. } => Some(err), + ResponseExtractReasons { err, .. } => Some(err), + ResponseIllegalQuery { .. } => None, + ResponseParse { err, .. } => Some(err), + StateToEFlint { err } => Some(err), + QuestionToEFlint { err } => Some(err), + } + } +} + + + + + +/***** AUXILLARY *****/ +/// Defines the eFLINT reasoner state to submit to it. +#[derive(Clone, Debug)] +pub struct State { + /// The policy used. + pub policy: Vec, + /// The rest of the state that is appended to the end of the request. + pub state: S, +} + + + + + +/***** LIBRARY *****/ +/// Defines the interface to abackend eFLINT JSON reasoner. +#[derive(Clone, Debug)] +pub struct EFlintJsonReasonerConnector { + /// The address where we find the reasoner. + addr: String, + /// The reasoner handler that determines if and which reasons to give. + reason_handler: R, + + /// Dummy variable for remembering which state is being used. + _state: PhantomData, + /// Dummy variable for remembering which question is being used. + _question: PhantomData, +} +impl EFlintJsonReasonerConnector { + /// Constructor for the EFlintJsonReasonerConnector. + /// + /// # Arguments + /// - `addr`: The address of the remote reasoner that we will connect to. + /// - `handler`: The [`ReasonHandler`] that determines how errors from the reasoners are propagated to the user. + /// + /// # Returns + /// A new instance of Self, ready for reasoning. + #[inline] + pub fn new(addr: impl Into, handler: R) -> Self { + Self { addr: addr.into(), reason_handler: handler, _state: PhantomData, _question: PhantomData } + } +} +impl ReasonerConnector for EFlintJsonReasonerConnector +where + R: ReasonHandler, + R::Reason: Display, + R::Error: 'static, + S: EFlintable, + S::Error: 'static, + Q: EFlintable, + Q::Error: 'static, +{ + type Error = Error; + type Question = Q; + type Reason = R::Reason; + type State = State; + + fn consult( + &self, + state: Self::State, + question: Self::Question, + logger: &SessionedAuditLogger, + ) -> impl Future, Self::Error>> + where + L: AuditLogger, + { + async move { + // NOTE: Using `#[instrument]` adds some unnecessary trait bounds on `S` and such. + let _span = span!(Level::INFO, "EFlintJsonReasonerConnector::consult", reference = logger.reference()).entered(); + + // Build the full policy + debug!("Building full policy..."); + let mut phrases: Vec = state.policy; + phrases.extend(state.state.to_eflint().map_err(|err| Error::StateToEFlint { err })?); + phrases.extend(question.to_eflint().map_err(|err| Error::QuestionToEFlint { err })?); + debug!("Full request length: {} phrase(s)", phrases.len()); + + // Build the request + let request: Request = Request::Phrases(RequestPhrases { + common: RequestCommon { version: Version::v0_1_0(), extensions: HashMap::new() }, + phrases, + updates: true, + }); + debug!("Full request:\n\n{}\n\n", serde_json::to_string_pretty(&request).unwrap_or_else(|_| "".into())); + + // Send it on its way + debug!("Sending eFLINT phrases request to '{}'", self.addr); + let client = reqwest::Client::new(); + let res = client.post(&self.addr).json(&request).send().await.map_err(|err| Error::ReasonerRequest { addr: self.addr.clone(), err })?; + + debug!("Awaiting response..."); + let raw_body = res.text().await.map_err(|err| Error::ReasonerResponse { addr: self.addr.clone(), err })?; + + debug!("Parsing response..."); + // NOTE: No 'map_err' to avoid moving 'raw_body' out on the happy path + let response: ResponsePhrases = match serde_json::from_str(&raw_body) { + Ok(res) => res, + Err(err) => return Err(Error::ResponseParse { addr: self.addr.clone(), raw: raw_body, err }), + }; + + debug!("Analysing response..."); + // TODO proper handle invalid query and unexpected result + let verdict: ReasonerResponse = response + .results + .last() + .map(|r| match r { + PhraseResult::BooleanQuery(r) => { + if r.result { + Ok(ReasonerResponse::Success) + } else { + Ok(ReasonerResponse::Violated(self.reason_handler.extract_reasons(&response).map_err(|err| { + Error::ResponseExtractReasons { + addr: self.addr.clone(), + raw: serde_json::to_string_pretty(&response).unwrap_or_else(|_| "".into()), + err, + } + })?)) + } + }, + PhraseResult::InstanceQuery(_) => Err(Error::ResponseIllegalQuery { + addr: self.addr.clone(), + raw: serde_json::to_string_pretty(&response).unwrap_or_else(|_| "".into()), + }), + PhraseResult::StateChange(r) => { + if !r.violated { + Ok(ReasonerResponse::Success) + } else { + Ok(ReasonerResponse::Violated(self.reason_handler.extract_reasons(&response).map_err(|err| { + Error::ResponseExtractReasons { + addr: self.addr.clone(), + raw: serde_json::to_string_pretty(&response).unwrap_or_else(|_| "".into()), + err, + } + })?)) + } + }, + }) + .transpose()? + .unwrap_or(ReasonerResponse::Success); + + // OK, report and return + logger + .log_response(&verdict, Some(&raw_body)) + .await + .map_err(|err| Error::LogResponse { to: std::any::type_name::>(), err: err.freeze() })?; + debug!("Final reasoner verdict: {verdict:?}"); + Ok(verdict) + } + } +} diff --git a/lib/reasoners/eflint-json/src/reasons.rs b/lib/reasoners/eflint-json/src/reasons.rs new file mode 100644 index 0000000..5c11b79 --- /dev/null +++ b/lib/reasoners/eflint-json/src/reasons.rs @@ -0,0 +1,169 @@ +// REASONS.rs +// by Lut99 +// +// Created: +// 09 Oct 2024, 16:37:52 +// Last edited: +// 11 Oct 2024, 14:04:46 +// Auto updated? +// Yes +// +// Description: +//! Defines how the eFLINT reasoner deals with reasons for failure. +// + +use std::convert::Infallible; +use std::error::Error; +use std::fmt::{Debug, Display, Formatter, Result as FResult}; +use std::ops::{Deref, DerefMut}; + +use eflint_json::spec::ResponsePhrases; + + +/***** AUXILLARY *****/ +/// Represents that no reason is used. +#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] +pub struct NoReason; +impl Display for NoReason { + #[inline] + fn fmt(&self, f: &mut Formatter<'_>) -> FResult { write!(f, "") } +} + +/// Represents that multiple reasons can be given. +#[derive(Clone, Debug, Eq, Hash, PartialEq)] +pub struct ManyReason(Vec); +impl Default for ManyReason { + #[inline] + fn default() -> Self { Self::new() } +} +impl ManyReason { + /// Constructor for the ManyReason that initializes it as empty. + /// + /// # Returns + /// A new ManyReason that doesn't have any reasons embedded in it yet. + #[inline] + pub fn new() -> Self { Self(Vec::new()) } + + /// Constructor for the ManyReason that initializes it as empty but with space allocated for + /// a certain number of reasons. + /// + /// # Arguments + /// - `capacity`: The (minimum) number of reasons to allocate space for. + /// + /// # Returns + /// A new ManyReason that doesn't have any reasons embedded in it yet but space for at least `capacity` reasons. + #[inline] + pub fn with_capacity(capacity: usize) -> Self { Self(Vec::with_capacity(capacity)) } +} +impl Display for ManyReason { + #[inline] + fn fmt(&self, f: &mut Formatter<'_>) -> FResult { + for i in 0..self.0.len() { + if i > 0 && i < self.0.len() - 1 { + write!(f, ", ")?; + } else if i == self.0.len() { + write!(f, " and ")?; + } + write!(f, "{}", self.0[i])?; + } + Ok(()) + } +} +impl Deref for ManyReason { + type Target = Vec; + + #[inline] + fn deref(&self) -> &Self::Target { &self.0 } +} +impl DerefMut for ManyReason { + #[inline] + fn deref_mut(&mut self) -> &mut Self::Target { &mut self.0 } +} +impl FromIterator for ManyReason { + #[inline] + fn from_iter>(iter: T) -> Self { Self(iter.into_iter().collect()) } +} +impl> From for ManyReason { + #[inline] + fn from(value: I) -> Self { Self::from_iter(value) } +} + + + + + +/***** LIBRARY *****/ +/// Abstracts over different strategies for handling errors. +pub trait ReasonHandler { + /// The type of the reason(s) returned by this handler. + type Reason: Debug; + /// The type of error(s) returned by this handler. + type Error: Error; + + + /// Given an eFLINT response struct, return reasons for failure (i.e., violations). + /// + /// You can assume this function will only be called if the query was somehow unsuccessfull. + /// Doesn't mean there are violations necessarily; it may also be a failed boolean query, for + /// instance. + /// + /// # Arguments + /// - `response`: The [`ResponsePhrases`] returned by the reasoner. + /// + /// # Returns + /// A [`ReasonHandler::Reason`] that describes why the policy was not compliant. + /// + /// # Errors + /// This function may fail if the input `response` was in an invalid state. + fn extract_reasons(&self, response: &ResponsePhrases) -> Result; +} + + + +/// An eFLINT [`ReasonHandler`] that does not communicate failures. +#[derive(Clone, Debug)] +pub struct EFlintSilentReasonHandler; +impl ReasonHandler for EFlintSilentReasonHandler { + type Error = Infallible; + type Reason = NoReason; + + #[inline] + fn extract_reasons(&self, _response: &ResponsePhrases) -> Result { Ok(NoReason) } +} + +/// An eFLINT [`ReasonHandler`] that only communicates violations who's name starts with some prefix. +#[derive(Clone, Debug)] +pub struct EFlintPrefixedReasonHandler { + /// The prefix to use to filter violations. + pub prefix: String, +} +impl EFlintPrefixedReasonHandler { + /// Constructor for the EFlintPrefixedReasonHandler. + /// + /// # Arguments + /// - `prefix`: The prefix to use to filter violations. + /// + /// # Returns + /// A new instance of Self, ready for handling. + #[inline] + pub fn new(prefix: impl Into) -> Self { Self { prefix: prefix.into() } } +} +impl ReasonHandler for EFlintPrefixedReasonHandler { + type Error = Infallible; + type Reason = ManyReason; + + #[inline] + fn extract_reasons(&self, response: &ResponsePhrases) -> Result { + Ok(response + .results + .last() + .map(|r| match r { + eflint_json::spec::PhraseResult::StateChange(sc) => match &sc.violations { + Some(v) => v.iter().filter(|v| v.identifier.starts_with(&self.prefix)).map(|v| v.identifier.clone()).collect(), + None => ManyReason::new(), + }, + _ => ManyReason::new(), + }) + .unwrap_or_default()) + } +} diff --git a/lib/reasoners/eflint-json/src/spec.rs b/lib/reasoners/eflint-json/src/spec.rs new file mode 100644 index 0000000..2707be7 --- /dev/null +++ b/lib/reasoners/eflint-json/src/spec.rs @@ -0,0 +1,44 @@ +// SPEC.rs +// by Lut99 +// +// Created: +// 09 Oct 2024, 16:06:18 +// Last edited: +// 10 Oct 2024, 14:06:45 +// Auto updated? +// Yes +// +// Description: +//! Defines some general interface for this crate. +// + +use std::convert::Infallible; +use std::error::Error; + +use eflint_json::spec::Phrase; + + +/***** LIBRARY *****/ +/// Defines something that can be turned into eFLINT phrases. +pub trait EFlintable { + /// The error type returned when converting to eFLINT. + type Error: Error; + + + /// Converts this state to eFLINT phrases. + /// + /// # Returns + /// A list of [`Phrase`]s that represent the eFLINT to send to the reasoner. + /// + /// # Errors + /// This function can fail if `self` is not in a right state to be serialized to eFLINT. + fn to_eflint(&self) -> Result, Self::Error>; +} + +// Default impls +impl EFlintable for () { + type Error = Infallible; + + #[inline] + fn to_eflint(&self) -> Result, Self::Error> { Ok(Vec::new()) } +} diff --git a/lib/reasoners/no-op/Cargo.toml b/lib/reasoners/no-op/Cargo.toml new file mode 100644 index 0000000..53317ac --- /dev/null +++ b/lib/reasoners/no-op/Cargo.toml @@ -0,0 +1,26 @@ +[package] +name = "no-op-reasoner" +description = """ +A minimal policy reasoner implementation that can be used as a base for new policy reasoners. + +This no-operation reasoner is meant to be an example, and can be used as a base to build new reasoners on top of. +Furthermore it can be used for testing. The reasoner approves all workflow validation requests by default (it does +not perform any permission checks, and thus never rejects a request). +""" +edition = "2021" +authors = [ "Daniel Voogsgerd", "Tim Müller" ] +version = "0.2.0" +repository.workspace = true +license.workspace = true + + +[dependencies] +tracing = "0.1.40" + +error-trace = { git = "https://github.com/Lut99/error-trace-rs", tag = "v3.1.0" } + +spec = { path = "../../spec", package = "specifications" } + + +[features] +default = [] diff --git a/lib/reasoners/no-op/src/lib.rs b/lib/reasoners/no-op/src/lib.rs new file mode 100644 index 0000000..3ef725f --- /dev/null +++ b/lib/reasoners/no-op/src/lib.rs @@ -0,0 +1,26 @@ +// LIB.rs +// by Lut99 +// +// Created: +// 10 Oct 2024, 16:19:50 +// Last edited: +// 11 Oct 2024, 16:23:50 +// Auto updated? +// Yes +// +// Description: +//! A minimal policy reasoner implementation that can be used as a base +//! for new policy reasoners. +//! +//! This no-operation reasoner is meant to be an example, and can be used as +//! a base to build new reasoners on top of. Furthermore it can be used for +//! testing. The reasoner approves all workflow validation requests by +//! default (it does not perform any permission checks, and thus never +//! rejects a request). +// + +// Declare the modules +mod reasonerconn; + +// Bring it into this namespace +pub use reasonerconn::*; diff --git a/lib/reasoners/no-op/src/reasonerconn.rs b/lib/reasoners/no-op/src/reasonerconn.rs new file mode 100644 index 0000000..a885e23 --- /dev/null +++ b/lib/reasoners/no-op/src/reasonerconn.rs @@ -0,0 +1,105 @@ +// REASONERCONN.rs +// by Lut99 +// +// Created: +// 10 Oct 2024, 16:21:09 +// Last edited: +// 11 Oct 2024, 16:56:18 +// Auto updated? +// Yes +// +// Description: +//! +// + +use std::error; +use std::fmt::{Display, Formatter, Result as FResult}; +use std::future::Future; +use std::marker::PhantomData; + +use error_trace::{ErrorTrace as _, Trace}; +use spec::auditlogger::SessionedAuditLogger; +use spec::reasonerconn::ReasonerResponse; +use spec::{AuditLogger, ReasonerConnector}; +use tracing::{Level, debug, span}; + + +/***** ERRORS *****/ +#[derive(Debug)] +pub enum Error { + /// Failed to log the reasoner's response to the given logger. + LogResponse { to: &'static str, err: Trace }, +} +impl Display for Error { + #[inline] + fn fmt(&self, f: &mut Formatter<'_>) -> FResult { + use Error::*; + match self { + LogResponse { to, .. } => write!(f, "Failed to log the reasoner's response to {to}"), + } + } +} +impl error::Error for Error { + #[inline] + fn source(&self) -> Option<&(dyn error::Error + 'static)> { + use Error::*; + match self { + LogResponse { err, .. } => Some(err), + } + } +} + + + + + +/***** LIBRARY *****/ +/// The minimal no-operation reasoner connector, that approves all validation requests by default (it does not check any +/// policy/permissions). +#[derive(Clone, Copy, Debug)] +pub struct NoOpReasonerConnector { + /// The completely arbitrary question that can be asked. + _question: PhantomData, +} +impl Default for NoOpReasonerConnector { + #[inline] + fn default() -> Self { Self::new() } +} +impl NoOpReasonerConnector { + /// Constructor for the NoOpReasonerConnector. + /// + /// # Returns + /// A new connector, ready to allow anything in sight. + #[inline] + pub fn new() -> Self { Self { _question: PhantomData } } +} +impl ReasonerConnector for NoOpReasonerConnector { + type Error = Error; + type Question = Q; + type Reason = (); + type State = (); + + fn consult( + &self, + _state: Self::State, + _question: Self::Question, + logger: &SessionedAuditLogger, + ) -> impl Future, Self::Error>> + where + L: AuditLogger, + { + async move { + // NOTE: Using `#[instrument]` adds some unnecessary trait bounds on `S` and such. + let _span = span!(Level::INFO, "NoOpReasonerConnector::consult", reference = logger.reference()).entered(); + + // Log the reasoner has been called + debug!("NoOpReasonerConnector: request received"); + logger + .log_response::(&ReasonerResponse::Success, None) + .await + .map_err(|err| Error::LogResponse { to: std::any::type_name::>(), err: err.freeze() })?; + + Ok(ReasonerResponse::Success) + } + } +} diff --git a/lib/reasoners/posix/Cargo.toml b/lib/reasoners/posix/Cargo.toml new file mode 100644 index 0000000..0fb614f --- /dev/null +++ b/lib/reasoners/posix/Cargo.toml @@ -0,0 +1,155 @@ +[package] +name = "posix-reasoner" +description = """ +A policy reasoner implementation based on POSIX file permissions. + +This documentation is aimed at developers that want to maintain or extend the POSIX reasoner. High level +documentation for users of the POSIX reasoner can be found in the [Brane user +guide](https://wiki.enablingpersonalizedinterventions.nl/user-guide/). An explanation of the POSIX policy file can +also be found there. + +# Goal + +This policy reasoner is meant to be easy and widely applicable. The aim is to take few assumptions and require as +little configuration as possible, allowing this reasoner to function as an easy to deploy proof of concept. This +allows users of Brane to gather experience with the abstract concept of a policy reasoner, before one has to start +writing policies themselves. + +Additionally, it could function well as an initial reasoner as a user adopts Brane on their systems, since it could +use permissions already set on their current systems to infer which users have access to what data. + +# Design + +To describe the design of this policy reasoner, we will walk through both the setup and usage of the reasoner from a +high level point of view. + +First, it checks the `DATA_INDEX` environment variable or the .env file for the location of the data index. We +imagine this points towards a mounted distributed file system like NFS. Then, it scans the directories for data +index files. From these files a [DataIndex] is created which is passed on to the [PosixReasonerConnector]. + +Now that the [PosixReasonerConnector] is created, it can start to handle requests. There are three types of +requests: + +- [Execute task](fn@PosixReasonerConnector::execute_task) +- [Access data](fn@PosixReasonerConnector::access_data_request) +- [Workflow validation](fn@PosixReasonerConnector::workflow_validation_request) + +As of now, the assumption is taken that it does not matter for this reasoner which type of request comes in, as we +only look at the data usage in the [Workflow]. + +As one of these requests comes in, the provided [Workflow] is parsed using a [DatasetCollectorVisitor] (an +implementation of the new util trait [WorkflowVisitor]) and all data accesses in the workflow are gathered and +associated with an access type of either read, write, or execute (execute is currently unused as no usage was +found). + +From this point, we iterate over all the different datasets and associated requests/required permissions. For each +[Dataset] we look up the path in the [DataIndex]. Now that we have the path and the requested permissions, we can +check if the user in the mapping has access to this dataset. + +### Current permission model + +The current permission model is based on the POSIX file permissions. This means that we check if the user has the +required permissions on the file. This is done by checking the file permissions of the file itself, and checking if +the user is either the owner of the file, in the group of the file, or if the file is world readable. The uid and +the gids extracted from the policy are matched against the file's uid and gid. If the file is owned by the user, the +owner permissions are checked. If the file is owned by a group the user is in, the group permissions are checked. If +neither of these is true, the other permissions are checked. If the user has the required permissions, the request is +approved. If not, the request is denied : [satisfies_posix_permissions]. + + +# State of the implementation + +Right now, the POSIX policy reasoner works. One can submit workflow requests using a HTTP request, or one can use +the Policy Reasoner GUI to submit a workflow request to the policy reasoner. The policy reasoner will then evaluate +if all files are properly accessible and return a verdict. + +## Limitations + +We had to draw a line in the sand for the current implementation, after which we considered additional details or +features out of the scope for the current project. If more time would have been available, there would have been a +fair bit of additional features that we would have liked to have implemented. + +The main origin of this line is the lack of an actual implementation inside Brane. To fully understand the +implications of the POSIX file permissions scheme, we need to start using the current implementation in a staging +environment. We should investigate the effects of multiple sites with many datasets. However, right now it is not +possible (or so we have been told) to mount the network shares to the reasoner container. + +Right now, this implementation is limited by the fact that it will try to load a single data index from the +`DATA_INDEX` environment variable. However, once we start mounting multiple network shares, we imagine that it might +be nice to be able to load multiple data indices. + +Another limitation is that the current implementation is not fully POSIX compliant. We still need to figure out how +some of the POSIX permission behaviours map into this emulation. E.g., right now we only check the file permissions +on the file itself, we do not check the permissions on the directory. Since we are going to be working with network +shares (and possible hard/symlinks) this becomes non-trivial, a working implementation is needed to investigate what +behaviour is desired. This is compounded by the problem that not only the user needs to be able to access the data, +but also the policy reasoner needs to reach at least the directory in which the file resides in order for the +reasoner to be able to `stat(1)` the file. + +Right now, we are seemingly limited by the fact that the policy reasoner GUI does not send at which site a [Dataset] +is accessed, making it impossible to fully know which of the mappings in the policy to use. There is a location +field that could be used, but since it is always set to `None`, we opted to fall back to the assumed location as +hardcoded in the static: [`ASSUMED_LOCATION`] + +# Future work + +- Support file creation: This requires us to look at the permissions of the parent directory of the potential +dataset, but this might be tricky considering the fact that the policy reasoner also needs to be able to access the +directory in order to check the file permissions. + +- Support multiple user mappings per location: Right now there is support in the code for multiple locations with +each their own user mapping, as different sites will often comprise of different network shares. But as it is +unclear right now how the volumes will be mounted on the reasoner container, it is hard to tell how such an +implementation is best designed. + +- Right now, this reasoner is both a module and a binary, but it should probably just be a binary. At this moment +however, documenting binaries is tricky in rust. As soon as we find a better solution, this documentation should be +moved to the binary itself. This might be useful in general, but particularly it is important to document reference +implementations. + +- LDAP / Active Directory support: Currently the Brane user to uid / gid mapping is embedded in the policy that is +loaded at the moment the reasoner is started. The idea of mapping users to uid and gids is not unique though, these +mappings can be sythesized from all sorts of resources. The most straightforward variant would be the loading of a +`passwd(5)` file, but since we are aiming at distributed file systems this would probably be of limited use. In +situations where file systems like NFS are often used, the users are store in Active Directory and accessed using +LDAP. Writing such an adapter to function as the user map seems so be a valuable addition to this reasoner, as this +would complete the picture of current existing systems and would allow a user of the reasoner to attach their +already existing and managed file systems with the correct access control. This would significantly reduce the +required investment of introducing policies in new Brane users. + +- ACL support: Besides regular POSIX file permissions, many file systems also support [POSIX +ACL](https://web.archive.org/web/20240210045229/https://www.usenix.org/legacy/publications/library/proceedings/usenix03/tech/freenix03/full_papers/gruenbacher/gruenbacher_html/main.html). +This would be an obvious and very useful extension to this reasoner. Usage of these ACLs is by far less common that +the regular POSIX permission it attempts to extend, but usage is also far from uncommon and potenial users of Brane, +and more specifically the policy reasoner, could have used these ACLs on their existing file systems. + +- The last point of future work is not specific to the POSIX reasoner, but more to the policy reasoner repository. +There is a desperate need for more elaborate documentation. We attempted to create a nice start with this reasoner, +but it can be quite daunting to figure out how every part of this system works. The author made a good effort to +improve the documentation during the running of this project, and that helped a lot. That combined with the already +existing implementation of the eFlint reasoner made this project possible. We hope that the POSIX (and +[no_op](crate::no_op)) reasoner can help guide future contributors in either extension of the current reasoners or +the addition of new reasoner types. +""" +edition = "2021" +authors = [ "Daniel Voogsgerd", "Tim Müller" ] +version = "0.2.0" +repository.workspace = true +license.workspace = true + + +[dependencies] +itertools = "0.13.0" +serde = { version = "1.0.204", features = ["derive"] } +serde_json = "1.0.120" +thiserror = "1.0.61" +tracing = "0.1.40" + +error-trace = { git = "https://github.com/Lut99/error-trace-rs", tag = "v3.1.0" } + +spec = { path = "../../spec", package = "specifications" } +workflow = { path = "../../workflow" } + + +[features] +default = [] diff --git a/lib/reasoners/posix/src/lib.rs b/lib/reasoners/posix/src/lib.rs new file mode 100644 index 0000000..d759d8c --- /dev/null +++ b/lib/reasoners/posix/src/lib.rs @@ -0,0 +1,148 @@ +// LIB.rs +// by Lut99 +// +// Created: +// 11 Oct 2024, 16:35:23 +// Last edited: +// 11 Oct 2024, 16:54:39 +// Auto updated? +// Yes +// +// Description: +//! A policy reasoner implementation based on POSIX file permissions. +//! +//! This documentation is aimed at developers that want to maintain or extend the POSIX reasoner. High level +//! documentation for users of the POSIX reasoner can be found in the [Brane user +//! guide](https://wiki.enablingpersonalizedinterventions.nl/user-guide/). An explanation of the POSIX policy file can +//! also be found there. +//! +//! # Goal +//! +//! This policy reasoner is meant to be easy and widely applicable. The aim is to take few assumptions and require as +//! little configuration as possible, allowing this reasoner to function as an easy to deploy proof of concept. This +//! allows users of Brane to gather experience with the abstract concept of a policy reasoner, before one has to start +//! writing policies themselves. +//! +//! Additionally, it could function well as an initial reasoner as a user adopts Brane on their systems, since it could +//! use permissions already set on their current systems to infer which users have access to what data. +//! +//! # Design +//! +//! To describe the design of this policy reasoner, we will walk through both the setup and usage of the reasoner from a +//! high level point of view. +//! +//! First, it checks the `DATA_INDEX` environment variable or the .env file for the location of the data index. We +//! imagine this points towards a mounted distributed file system like NFS. Then, it scans the directories for data +//! index files. From these files a [DataIndex] is created which is passed on to the [PosixReasonerConnector]. +//! +//! Now that the [PosixReasonerConnector] is created, it can start to handle requests. There are three types of +//! requests: +//! +//! - [Execute task](fn@PosixReasonerConnector::execute_task) +//! - [Access data](fn@PosixReasonerConnector::access_data_request) +//! - [Workflow validation](fn@PosixReasonerConnector::workflow_validation_request) +//! +//! As of now, the assumption is taken that it does not matter for this reasoner which type of request comes in, as we +//! only look at the data usage in the [Workflow]. +//! +//! As one of these requests comes in, the provided [Workflow] is parsed using a [DatasetCollectorVisitor] (an +//! implementation of the new util trait [WorkflowVisitor]) and all data accesses in the workflow are gathered and +//! associated with an access type of either read, write, or execute (execute is currently unused as no usage was +//! found). +//! +//! From this point, we iterate over all the different datasets and associated requests/required permissions. For each +//! [Dataset] we look up the path in the [DataIndex]. Now that we have the path and the requested permissions, we can +//! check if the user in the mapping has access to this dataset. +//! +//! ### Current permission model +//! +//! The current permission model is based on the POSIX file permissions. This means that we check if the user has the +//! required permissions on the file. This is done by checking the file permissions of the file itself, and checking if +//! the user is either the owner of the file, in the group of the file, or if the file is world readable. The uid and +//! the gids extracted from the policy are matched against the file's uid and gid. If the file is owned by the user, the +//! owner permissions are checked. If the file is owned by a group the user is in, the group permissions are checked. If +//! neither of these is true, the other permissions are checked. If the user has the required permissions, the request is +//! approved. If not, the request is denied : [satisfies_posix_permissions]. +//! +//! +//! # State of the implementation +//! +//! Right now, the POSIX policy reasoner works. One can submit workflow requests using a HTTP request, or one can use +//! the Policy Reasoner GUI to submit a workflow request to the policy reasoner. The policy reasoner will then evaluate +//! if all files are properly accessible and return a verdict. +//! +//! ## Limitations +//! +//! We had to draw a line in the sand for the current implementation, after which we considered additional details or +//! features out of the scope for the current project. If more time would have been available, there would have been a +//! fair bit of additional features that we would have liked to have implemented. +//! +//! The main origin of this line is the lack of an actual implementation inside Brane. To fully understand the +//! implications of the POSIX file permissions scheme, we need to start using the current implementation in a staging +//! environment. We should investigate the effects of multiple sites with many datasets. However, right now it is not +//! possible (or so we have been told) to mount the network shares to the reasoner container. +//! +//! Right now, this implementation is limited by the fact that it will try to load a single data index from the +//! `DATA_INDEX` environment variable. However, once we start mounting multiple network shares, we imagine that it might +//! be nice to be able to load multiple data indices. +//! +//! Another limitation is that the current implementation is not fully POSIX compliant. We still need to figure out how +//! some of the POSIX permission behaviours map into this emulation. E.g., right now we only check the file permissions +//! on the file itself, we do not check the permissions on the directory. Since we are going to be working with network +//! shares (and possible hard/symlinks) this becomes non-trivial, a working implementation is needed to investigate what +//! behaviour is desired. This is compounded by the problem that not only the user needs to be able to access the data, +//! but also the policy reasoner needs to reach at least the directory in which the file resides in order for the +//! reasoner to be able to `stat(1)` the file. +//! +//! Right now, we are seemingly limited by the fact that the policy reasoner GUI does not send at which site a [Dataset] +//! is accessed, making it impossible to fully know which of the mappings in the policy to use. There is a location +//! field that could be used, but since it is always set to `None`, we opted to fall back to the assumed location as +//! hardcoded in the static: [`ASSUMED_LOCATION`] +//! +//! # Future work +//! +//! - Support file creation: This requires us to look at the permissions of the parent directory of the potential +//! dataset, but this might be tricky considering the fact that the policy reasoner also needs to be able to access the +//! directory in order to check the file permissions. +//! +//! - Support multiple user mappings per location: Right now there is support in the code for multiple locations with +//! each their own user mapping, as different sites will often comprise of different network shares. But as it is +//! unclear right now how the volumes will be mounted on the reasoner container, it is hard to tell how such an +//! implementation is best designed. +//! +//! - Right now, this reasoner is both a module and a binary, but it should probably just be a binary. At this moment +//! however, documenting binaries is tricky in rust. As soon as we find a better solution, this documentation should be +//! moved to the binary itself. This might be useful in general, but particularly it is important to document reference +//! implementations. +//! +//! - LDAP / Active Directory support: Currently the Brane user to uid / gid mapping is embedded in the policy that is +//! loaded at the moment the reasoner is started. The idea of mapping users to uid and gids is not unique though, these +//! mappings can be sythesized from all sorts of resources. The most straightforward variant would be the loading of a +//! `passwd(5)` file, but since we are aiming at distributed file systems this would probably be of limited use. In +//! situations where file systems like NFS are often used, the users are store in Active Directory and accessed using +//! LDAP. Writing such an adapter to function as the user map seems so be a valuable addition to this reasoner, as this +//! would complete the picture of current existing systems and would allow a user of the reasoner to attach their +//! already existing and managed file systems with the correct access control. This would significantly reduce the +//! required investment of introducing policies in new Brane users. +//! +//! - ACL support: Besides regular POSIX file permissions, many file systems also support [POSIX +//! ACL](https://web.archive.org/web/20240210045229/https://www.usenix.org/legacy/publications/library/proceedings/usenix03/tech/freenix03/full_papers/gruenbacher/gruenbacher_html/main.html). +//! This would be an obvious and very useful extension to this reasoner. Usage of these ACLs is by far less common that +//! the regular POSIX permission it attempts to extend, but usage is also far from uncommon and potenial users of Brane, +//! and more specifically the policy reasoner, could have used these ACLs on their existing file systems. +//! +//! - The last point of future work is not specific to the POSIX reasoner, but more to the policy reasoner repository. +//! There is a desperate need for more elaborate documentation. We attempted to create a nice start with this reasoner, +//! but it can be quite daunting to figure out how every part of this system works. The author made a good effort to +//! improve the documentation during the running of this project, and that helped a lot. That combined with the already +//! existing implementation of the eFlint reasoner made this project possible. We hope that the POSIX (and +//! [no_op](crate::no_op)) reasoner can help guide future contributors in either extension of the current reasoners or +//! the addition of new reasoner types. +// + +// Declare the modules +mod reasonerconn; +mod workflow; + +// Use some of it +pub use reasonerconn::*; diff --git a/lib/reasoners/posix/src/reasonerconn.rs b/lib/reasoners/posix/src/reasonerconn.rs new file mode 100644 index 0000000..7712860 --- /dev/null +++ b/lib/reasoners/posix/src/reasonerconn.rs @@ -0,0 +1,453 @@ +// REASONERCONN.rs +// by Lut99 +// +// Created: +// 11 Oct 2024, 16:54:51 +// Last edited: +// 14 Oct 2024, 11:58:58 +// Auto updated? +// Yes +// +// Description: +//! Implements the actual [`ReasonerConnector`]. +// + + +/***** LIBRARY *****/ +use std::collections::HashMap; +use std::convert::Infallible; +use std::future::Future; +use std::iter::repeat; +use std::ops::{BitAnd, BitAndAssign, BitOr, BitOrAssign}; +use std::os::unix::fs::{MetadataExt, PermissionsExt}; +use std::path::Path; + +use itertools::{Either, Itertools}; +use serde::Deserialize; +use spec::auditlogger::{AuditLogger, SessionedAuditLogger}; +use spec::reasonerconn::{ReasonerConnector, ReasonerResponse}; +use tracing::{info, span, Level}; +use workflow::Workflow; + +use crate::workflow::WorkflowDatasets; + + +/***** TYPES *****/ +/// E.g., `st_antonius_etc`. +type LocationIdentifier = String; +/// The global username as defined in [`Workflow.user`]. E.g., `test`. +type GlobalUsername = String; + + + + + +/***** ERRORS *****/ +/// Represents an error that occurred during the validation of a policy. These errors contain more information about the +/// problems that occurred during validation. +#[derive(thiserror::Error, Debug)] +enum PolicyError { + #[error("Missing location: {0}")] + MissingLocation(String), + #[error("Missing user: {0} for location: {1}")] + MissingUser(String, String), +} + +/// Represents a validation error that occurred during the validation of a workflow. These errors contain more +/// information about the problems that occurred during validation. +#[derive(thiserror::Error, Debug)] +enum ValidationError { + #[error("Policy Error: {0}")] + PolicyError(PolicyError), + #[error("Unknown dataset: {0}")] + UnknownDataset(String), +} + + + + + +/***** HELPER FUNCTIONS *****/ +/// Check if all the data accesses performed in the `workflow` are done on behalf of users that have the required +/// permissions. If not all permissions are met, then [`ValidationError`]s are returned. These errors contain more +/// information about the problems that occurred during validation. +fn validate_dataset_permissions(workflow: &Workflow, policy: &PosixPolicy) -> Result> { + let _span = span!(Level::INFO, "PosixReasonerConnector::validate_dataset_permissions", workflow = workflow.id).entered(); + + // The datasets used in the workflow. E.g., `st_antonius_ect`. + let datasets: WorkflowDatasets = WorkflowDatasets::from(workflow); + + // Loop to find the permissions on the disk + let (forbidden, errors): (Vec<_>, Vec<_>) = std::iter::empty() + .chain(datasets.read_sets.iter().zip(repeat(PosixFilePermission::Read.to_set()))) + .chain(datasets.write_sets.iter().zip(repeat(PosixFilePermission::Write.to_set()))) + .chain(datasets.execute_sets.iter().zip(repeat(PosixFilePermission::Read | PosixFilePermission::Execute))) + .flat_map(|((location, dataset), permission)| { + let Some(dataset) = data_index.get(&dataset.name) else { + return Either::Left(std::iter::once(Err(ValidationError::UnknownDataset(dataset.name.clone())))); + }; + Either::Right(dataset.access.values().map(move |kind| match kind { + specifications::data::AccessKind::File { path } => { + info!("Contents of the DataInfo object:\n{:#?}", dataset); + let local_identity = policy.get_local_identity(location, &workflow.user.name).map_err(ValidationError::PolicyError)?; + let result = satisfies_posix_permissions(path, local_identity, &permission); + Ok((dataset.name.clone(), path, result)) + }, + })) + }) + // This is where we are going to focus on the problems that occurred in the validation + // These can be separated into groups: Errors (e.g. Non-existing users / files), and + // validation failures. + .filter(|res| match res { + // Filter out what was okay in either sense. + Ok((_, _, true)) => false, + _ => true, + }) + .partition_map(|elem| match elem { + Ok((dataset_identifier, _, _)) => Either::Left(dataset_identifier), + Err(x) => Either::Right(x), + }); + + if !errors.is_empty() { + Err(errors) + } else if forbidden.is_empty() { + return Ok(ValidationOutput::Ok); + } else { + return Ok(ValidationOutput::Fail(forbidden)); + } +} + +/// Verifies whether the passed [`PosixLocalIdentity`] has all of the requested permissions (e.g., `Read` and `Write`) +/// on a particular file (defined by the `path`). The identity's user id and group ids are checked against the file +/// owner's user id and group id respectively. Additionally, the `Others` class permissions are also checked. +fn satisfies_posix_permissions(path: impl AsRef, local_identity: &PosixLocalIdentity, requested_permissions: &[PosixFilePermission]) -> bool { + let metadata = std::fs::metadata(&path).expect("Could not get file metadata"); + + let mode_bits = metadata.permissions().mode(); + let file_owner_uid = metadata.uid(); + let file_owner_gid = metadata.gid(); + + if file_owner_uid == local_identity.uid { + let mask = PosixFileClass::Owner.get_mode_bitmask(requested_permissions); + if mode_bits & mask == mask { + return true; + } + } + + if local_identity.gids.contains(&file_owner_gid) { + let mask = PosixFileClass::Group.get_mode_bitmask(requested_permissions); + if mode_bits & mask == mask { + return true; + } + } + + let mask = PosixFileClass::Others.get_mode_bitmask(requested_permissions); + mode_bits & mask == mask +} + + + + + +/***** HELPERS *****/ +enum ValidationOutput { + Ok, + // Below we might want to encapsulate the Dataset itself. + /// The string here represents a `Dataset.name`. + Fail(Vec), +} + +/// Represents a POSIX file class, also known as a scope. See: +/// . +#[derive(Copy, Clone, Deserialize)] +enum PosixFileClass { + Owner, + Group, + Others, +} +impl PosixFileClass { + /// Given a list of [`PosixFilePermission`]s will return an octal mode bitmask for this [`PosixFileClass`]. + /// + /// This bitmask represents what mode bits should be set on a file such that this class (e.g., `Owner`) satisfies + /// the permissions (e.g, `Read`, `Write`). In this case it would be `0o400` (Read for Owner) and `0o200` (Write for + /// Owner), which sums to the returned `0o600` (Read and Write for Owner). + fn get_mode_bitmask(&self, required_permissions: &[PosixFilePermission]) -> u32 { + let alignment_multiplier = match self { + PosixFileClass::Owner => 0o100, + PosixFileClass::Group => 0o10, + PosixFileClass::Others => 0o1, + }; + required_permissions.iter().fold(0, |acc, f| acc | (alignment_multiplier * f.to_mode_bit())) + } +} + +/// Part of the [`PosixPolicy`]. Represents a location (e.g., `st_antonius_etc`) and contains the global workflow +/// username to local identity mappings for this location. +#[derive(Deserialize, Debug)] +pub struct PosixPolicyLocation { + user_map: HashMap, +} + +/// The local identity defines a user id and a list of group ids. The local identity is used on the machine on which a +/// dataset resides to check the local file permissions. For more about this permissions check see +/// [`validate_dataset_permissions`]. +/// +/// This identity is defined in the POSIX policy file. Global usernames in the POSIX policy map to these local +/// identities. +/// +/// Example, given the POSIX policy file below, then for the `st_antonius_ect` location, the `test` global username maps +/// to a local identity that contains the uid and gids. +/// ``` yaml +/// # file: posix-policy.yml +/// content: +/// st_antonius_ect: +/// user_map: +/// test: +/// uid: 1000 +/// gids: +/// - 1001 +/// - 1002 +/// - 1003 +/// ``` +#[derive(Deserialize, Debug)] +struct PosixLocalIdentity { + /// The user identifier of a Linux user. + uid: u32, + /// A list of Linux group identifiers. + gids: Vec, +} + +/// Represents a set of file permissions. +#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] +struct PosixFilePermissions(u8); +impl PosixFilePermissions { + /// Returns whether the read bit is set. + #[inline] + const fn read(&self) -> bool { (self.0 & (PosixFilePermission::Read.to_mode_bit() as u8)) != 0 } + + /// Returns whether the write bit is set. + #[inline] + const fn write(&self) -> bool { (self.0 & (PosixFilePermission::Write.to_mode_bit() as u8)) != 0 } + + /// Returns whether the execute bit is set. + #[inline] + const fn exec(&self) -> bool { (self.0 & (PosixFilePermission::Execute.to_mode_bit() as u8)) != 0 } + + /// Returns the raw bit pattern for this permission set. + #[inline] + const fn as_u8(&self) -> u8 { self.0 } +} +impl BitAnd for PosixFilePermissions { + type Output = Self; + + #[inline] + fn bitand(self, rhs: PosixFilePermission) -> Self::Output { Self(self.0 & (rhs.to_mode_bit() as u8)) } +} +impl BitAndAssign for PosixFilePermissions { + #[inline] + fn bitand_assign(&mut self, rhs: PosixFilePermission) { self.0 &= rhs.to_mode_bit() as u8; } +} +impl BitOr for PosixFilePermissions { + type Output = Self; + + #[inline] + fn bitor(self, rhs: PosixFilePermission) -> Self::Output { Self(self.0 | (rhs.to_mode_bit() as u8)) } +} +impl BitOrAssign for PosixFilePermissions { + #[inline] + fn bitor_assign(&mut self, rhs: PosixFilePermission) { self.0 |= rhs.to_mode_bit() as u8; } +} +impl BitAnd for PosixFilePermissions { + type Output = Self; + + #[inline] + fn bitand(self, rhs: Self) -> Self::Output { Self(self.0 & rhs.0) } +} +impl BitAndAssign for PosixFilePermissions { + #[inline] + fn bitand_assign(&mut self, rhs: Self) { self.0 &= rhs.0; } +} +impl BitOr for PosixFilePermissions { + type Output = Self; + + #[inline] + fn bitor(self, rhs: Self) -> Self::Output { Self(self.0 | rhs.0) } +} +impl BitOrAssign for PosixFilePermissions { + #[inline] + fn bitor_assign(&mut self, rhs: Self) { self.0 |= rhs.0; } +} +impl From for PosixFilePermissions { + #[inline] + fn from(value: PosixFilePermission) -> Self { Self(value.to_mode_bit() as u8) } +} +impl From for u8 { + #[inline] + fn from(value: PosixFilePermissions) -> Self { value.as_u8() } +} +impl From<&PosixFilePermissions> for u8 { + #[inline] + fn from(value: &PosixFilePermissions) -> Self { value.as_u8() } +} +impl From<&mut PosixFilePermissions> for u8 { + #[inline] + fn from(value: &mut PosixFilePermissions) -> Self { value.as_u8() } +} + +/// Represents a POSIX file permission. See: . +#[derive(Debug, Copy, Clone)] +enum PosixFilePermission { + Read, + Write, + Execute, +} +impl PosixFilePermission { + /// Returns this permission's mode bit. + /// - `Read` → `4` + /// - `Write` → `2` + /// - `Execute` → `1`. + /// + /// For more about POSIX permission bits see: + /// . + /// + /// Also see the related [`UserType::get_mode_bitmask`]. + const fn to_mode_bit(self) -> u32 { + match self { + PosixFilePermission::Read => 4, + PosixFilePermission::Write => 2, + PosixFilePermission::Execute => 1, + } + } + + /// Returns the permission as a set of permissions. + /// + /// # Returns + /// A [`PosixFilePermissions`] representing the ste. + #[inline] + fn to_set(&self) -> PosixFilePermissions { PosixFilePermissions(self.to_mode_bit() as u8) } +} +impl BitAnd for PosixFilePermission { + type Output = PosixFilePermissions; + + #[inline] + fn bitand(self, rhs: PosixFilePermission) -> Self::Output { PosixFilePermissions((self.to_mode_bit() & rhs.to_mode_bit()) as u8) } +} +impl BitOr for PosixFilePermission { + type Output = PosixFilePermissions; + + #[inline] + fn bitor(self, rhs: PosixFilePermission) -> Self::Output { PosixFilePermissions((self.to_mode_bit() | rhs.to_mode_bit()) as u8) } +} +impl BitAnd for PosixFilePermission { + type Output = PosixFilePermissions; + + #[inline] + fn bitand(self, rhs: PosixFilePermissions) -> Self::Output { PosixFilePermissions((self.to_mode_bit() as u8) & rhs.0) } +} +impl BitOr for PosixFilePermission { + type Output = PosixFilePermissions; + + #[inline] + fn bitor(self, rhs: PosixFilePermissions) -> Self::Output { PosixFilePermissions((self.to_mode_bit() as u8) | rhs.0) } +} + + + + + +/***** LIBRARY *****/ +/// The overarching input to the POSIX reasoner. +#[derive(Debug)] +pub struct State { + /// The policy to give. + pub policy: PosixPolicy, + /// The workflow considered. + pub workflow: Workflow, +} + +/// The overarching POSIX policy. Check out the module documentation for an overview. +#[derive(Deserialize, Debug)] +pub struct PosixPolicy { + datasets: HashMap, +} +impl PosixPolicy { + /// Given a location (e.g., `st_antonius_ect`) and the workflow user's name (e.g., `test`), returns the + /// [`PosixLocalIdentity`] for that user. + /// + /// The returned identity is used for file permission checks. For more about this permissions check see + /// [`validate_dataset_permissions`]. + fn get_local_identity(&self, location: &str, workflow_user: &str) -> Result<&PosixLocalIdentity, PolicyError> { + self.datasets + .get(location) + .ok_or_else(|| PolicyError::MissingLocation(location.to_owned()))? + .user_map + .get(workflow_user) + .ok_or_else(|| PolicyError::MissingUser(workflow_user.to_owned(), location.to_owned())) + } +} + + + +/// The POSIX reasoner connector. This connector is used to validate workflows based on POSIX file permissions. +pub struct PosixReasonerConnector; +impl PosixReasonerConnector { + #[inline] + pub const fn new() -> Self { PosixReasonerConnector } +} +impl ReasonerConnector for PosixReasonerConnector { + type Error = Infallible; + type Question = (); + type Reason = Vec; + type State = State; + + fn consult( + &self, + state: Self::State, + _question: Self::Question, + logger: &SessionedAuditLogger, + ) -> impl Future, Self::Error>> + where + L: AuditLogger, + { + async move { + let _span = span!(Level::INFO, "ReasonerConnector::consult", reference = logger.reference()).entered(); + + match validate_dataset_permissions(&state.workflow, &state.policy) { + Ok(ValidationOutput::Ok) => Ok(ReasonerResponse::Success), + Ok(ValidationOutput::Fail(datasets)) => Ok(ReasonerResponse::Violated( + datasets.into_iter().map(|dataset| format!("We do not have sufficient permissions for dataset: {dataset}")).collect(), + )), + Err(errors) => Ok(ReasonerResponse::Violated(errors.into_iter().map(|error| error.to_string()).collect())), + } + } + } +} + +// /// The context of the POSIX reasoner connector. This context is used to identify the reasoner connector. +// /// See [`ConnectorContext`] and [`ConnectorWithContext`]. +// #[derive(Debug, Clone, serde::Serialize)] +// pub struct PosixReasonerConnectorContext { +// #[serde(rename = "type")] +// pub t: String, +// pub version: String, +// } + +// impl std::hash::Hash for PosixReasonerConnectorContext { +// fn hash(&self, state: &mut H) { +// self.t.hash(state); +// self.version.hash(state); +// } +// } + +// impl ConnectorContext for PosixReasonerConnectorContext { +// fn r#type(&self) -> String { self.t.clone() } + +// fn version(&self) -> String { self.version.clone() } +// } + +// impl ConnectorWithContext for PosixReasonerConnector { +// type Context = PosixReasonerConnectorContext; + +// #[inline] +// fn context() -> Self::Context { PosixReasonerConnectorContext { t: "posix".into(), version: "0.1.0".into() } } +// } diff --git a/lib/reasoners/posix/src/workflow.rs b/lib/reasoners/posix/src/workflow.rs new file mode 100644 index 0000000..775b962 --- /dev/null +++ b/lib/reasoners/posix/src/workflow.rs @@ -0,0 +1,112 @@ +// WORKFLOW.rs +// by Lut99 +// +// Created: +// 11 Oct 2024, 16:54:04 +// Last edited: +// 14 Oct 2024, 11:58:50 +// Auto updated? +// Yes +// +// Description: +//! Deals with analysing the reasoner's "generic" [`Workflow`] AST. +// + +use std::convert::Infallible; +use std::sync::LazyLock; + +use tracing::{debug, span, Level}; +use workflow::visitor::Visitor; +use workflow::{Dataset, ElemCall, Entity, Workflow}; + + +/***** CONSTANTS *****/ +/// Special location that indicates to the policy that a task was unplanned. +pub static UNSPECIFIED_LOCATION: LazyLock = LazyLock::new(|| Entity { id: "".into() }); + + + + + +/***** VISITORS *****/ +/// Visits a [`Workflow`] in order to find all datasets used. +struct DatasetCollector<'w> { + read_sets: Vec<(&'w Entity, &'w Dataset)>, + write_sets: Vec<(&'w Entity, &'w Dataset)>, + execute_sets: Vec<(&'w Entity, &'w Dataset)>, +} +impl<'w> Default for DatasetCollector<'w> { + #[inline] + fn default() -> Self { Self { read_sets: Default::default(), write_sets: Default::default(), execute_sets: Default::default() } } +} +impl<'w> Visitor<'w> for DatasetCollector<'w> { + type Error = Infallible; + + // fn visit_task(&mut self, task: &workflow::ElemTask) { + // // FIXME: Location is not currently sent as part of the workflow validation request, + // // this makes this not really possible to do now. To ensure the code is working + // // however, we will for the mean time assume the location + + // let location = task.location.clone().unwrap_or_else(|| String::from(ASSUMED_LOCATION)); + // if let Some(output) = &task.output { + // self.read_sets.push((location.clone(), output.clone())); + // } + // } + + // fn visit_commit(&mut self, commit: &workflow::ElemCommit) { + // let location = commit.location.clone().unwrap_or_else(|| String::from(ASSUMED_LOCATION)); + // self.read_sets.extend(repeat(location.clone()).zip(commit.input.iter().cloned())); + + // // TODO: Maybe create a dedicated enum type for this e.g. NewDataset for datasets that will be + // // created, might fail if one already exists. + // let location = commit.location.clone().unwrap_or_else(|| String::from(ASSUMED_LOCATION)); + // self.write_sets.push((location.clone(), Dataset { id: commit.data_name.clone() })); + // } + + // // TODO: We do not really have a location for this one right now, we should figure out how to + // // interpret this + // fn visit_stop(&mut self, stop_sets: &HashSet) { + // let location = String::from(ASSUMED_LOCATION); + // self.write_sets.extend(repeat(location).zip(stop_sets.iter().cloned())); + // } + + fn visit_call(&mut self, elem: &'w ElemCall) -> Result<(), Self::Error> { + // We take a more simplified view on dataset reading/writing. + + // We consider a task's inputs as READING. Any task's outputs are WRITING. + // Commit collapses to this behaviour. Similarly, any special "identity" task that implies + // the read at the end of a workflow also follows this pattern. + // Another different from before: we rename the unknown location to "" to + // be able to write policies explicitly for this use-case. + + let location: &'w Entity = elem.at.as_ref().unwrap_or_else(|| LazyLock::force(&UNSPECIFIED_LOCATION)); + self.read_sets.extend(elem.input.iter().map(|d| (location, d))); + self.write_sets.extend(elem.output.iter().map(|d| (location, d))); + Ok(()) + } +} + + + + + +/***** LIBRARY *****/ +/// The datasets accessed and/or modified in a workflow. These are grouped by file permission type. For creating this +/// struct see: [`find_datasets_in_workflow`]. +pub struct WorkflowDatasets<'w> { + pub read_sets: Vec<(&'w Entity, &'w Dataset)>, + pub write_sets: Vec<(&'w Entity, &'w Dataset)>, + pub execute_sets: Vec<(&'w Entity, &'w Dataset)>, +} +impl<'w> From<&'w Workflow> for WorkflowDatasets<'w> { + #[inline] + fn from(value: &'w Workflow) -> Self { + let _span = span!(Level::INFO, "PosixReasonerConnector::find_datasets_in_workflow", workflow = value.id).entered(); + debug!("Walking the workflow in order to find datasets. Starting with {:?}", &value.start); + + let mut visitor = DatasetCollector::default(); + value.visit(&mut visitor); + + WorkflowDatasets { read_sets: visitor.read_sets, write_sets: visitor.write_sets, execute_sets: visitor.execute_sets } + } +} diff --git a/lib/resolvers/file/Cargo.toml b/lib/resolvers/file/Cargo.toml new file mode 100644 index 0000000..c76c1b7 --- /dev/null +++ b/lib/resolvers/file/Cargo.toml @@ -0,0 +1,22 @@ +[package] +name = "file-resolver" +description = "Resolves state from a file by parsing it as `serde` JSON." +edition = "2021" +authors = [ "Bas Kloosterman", "Tim Müller" ] +version = "0.2.0" +repository.workspace = true +license.workspace = true + + +[dependencies] +# enum-debug = { git = "https://github.com/Lut99/enum-debug", tag = "v1.1.0", features = ["derive"] } +serde = { version = "1.0.204", features = ["derive"] } +serde_json = "1.0.120" +tokio = { version = "1.40.0", default-features = false, features = ["fs"] } +tracing = "0.1.40" + +spec = { path = "../../spec", package = "specifications" } + + +[features] +default = [] diff --git a/lib/resolvers/file/src/lib.rs b/lib/resolvers/file/src/lib.rs new file mode 100644 index 0000000..05059c0 --- /dev/null +++ b/lib/resolvers/file/src/lib.rs @@ -0,0 +1,19 @@ +// LIB.rs +// by Lut99 +// +// Created: +// 10 Oct 2024, 15:54:54 +// Last edited: +// 10 Oct 2024, 15:57:17 +// Auto updated? +// Yes +// +// Description: +//! Resolves state from a file by parsing it as `serde` JSON. +// + +// Declare modules +mod resolver; + +// Bring it into this namespace +pub use resolver::*; diff --git a/lib/resolvers/file/src/resolver.rs b/lib/resolvers/file/src/resolver.rs new file mode 100644 index 0000000..69d7d33 --- /dev/null +++ b/lib/resolvers/file/src/resolver.rs @@ -0,0 +1,112 @@ +// RESOLVER.rs +// by Lut99 +// +// Created: +// 10 Oct 2024, 15:55:23 +// Last edited: +// 10 Oct 2024, 16:10:46 +// Auto updated? +// Yes +// +// Description: +//! Defines the actual [`StateResolver`]. +// + +use std::error; +use std::fmt::{Display, Formatter, Result as FResult}; +use std::future::Future; +use std::marker::PhantomData; +use std::path::PathBuf; + +use serde::Deserialize; +use spec::AuditLogger; +use spec::auditlogger::SessionedAuditLogger; +use spec::stateresolver::StateResolver; +use tokio::fs; +use tracing::{Level, debug, span}; + + +/***** ERRORS *****/ +/// Defines the errors that are occurring in the [`FileResolver`]. +#[derive(Debug)] +pub enum Error { + /// Failed to deserialize the target file's contents. + FileDeserialize { to: &'static str, path: PathBuf, err: serde_json::Error }, + /// Failed to read the target file. + FileRead { path: PathBuf, err: std::io::Error }, +} +impl Display for Error { + #[inline] + fn fmt(&self, f: &mut Formatter<'_>) -> FResult { + use Error::*; + match self { + FileDeserialize { to, path, .. } => write!(f, "Failed to deserialize contents of file {:?} as {}", path.display(), to), + FileRead { path, .. } => write!(f, "Failed to read file {:?}", path.display()), + } + } +} +impl error::Error for Error { + #[inline] + fn source(&self) -> Option<&(dyn error::Error + 'static)> { + use Error::*; + match self { + FileDeserialize { err, .. } => Some(err), + FileRead { err, .. } => Some(err), + } + } +} + + + + + +/***** LIBRARY *****/ +/// Defines a [`StateResolver`] that resolves a [`serde`]-[`Deserialize`]able state from an +/// arbitrary file. +#[derive(Clone, Debug)] +pub struct FileResolver { + /// The file to resolve from. + path: PathBuf, + /// Remembers what we're resolving to. + _resolved: PhantomData, +} +impl FileResolver { + /// Constructor for the FileResolver. + /// + /// # Arguments + /// - `path`: The path to the file that we're resolving from. + /// + /// # Returns + /// A new FileResolver ready for resolution. + #[inline] + pub fn new(path: impl Into) -> Self { Self { path: path.into(), _resolved: PhantomData } } +} +impl Deserialize<'de>> StateResolver for FileResolver { + type Error = Error; + type Resolved = R; + type State = (); + + fn resolve(&self, _state: Self::State, logger: &SessionedAuditLogger) -> impl Future> + where + L: AuditLogger, + { + async move { + // NOTE: Using `#[instrument]` adds some unnecessary trait bounds on `S` and such. + let _span = span!(Level::INFO, "FileResolver::resolve", reference = logger.reference()).entered(); + + // Read the file in one go// Read the file in one go + debug!("Opening input file '{}'...", self.path.display()); + let state: String = match fs::read_to_string(&self.path).await { + Ok(state) => state, + Err(err) => return Err(Error::FileRead { path: self.path.clone(), err }), + }; + + // Parse it as JSON + debug!("Parsing input file '{}'...", self.path.display()); + match serde_json::from_str(&state) { + Ok(state) => Ok(state), + Err(err) => Err(Error::FileDeserialize { to: std::any::type_name::(), path: self.path.clone(), err }), + } + } + } +} diff --git a/lib/spec/Cargo.toml b/lib/spec/Cargo.toml new file mode 100644 index 0000000..ff52b98 --- /dev/null +++ b/lib/spec/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "specifications" +description = "Defines various interfaces between various parts of the reasoner." +edition = "2021" +authors = [ "Bas Kloosterman", "Tim Müller" ] +version = "0.2.0" +repository.workspace = true +license.workspace = true + + +[dependencies] +# enum-debug = { git = "https://github.com/Lut99/enum-debug", tag = "v1.1.0", features = ["derive"] } +paste = "1.0.15" +serde = { version = "1.0.204", features = ["derive"] } + + +[features] +default = [] diff --git a/lib/spec/src/auditlogger.rs b/lib/spec/src/auditlogger.rs new file mode 100644 index 0000000..2d9b7de --- /dev/null +++ b/lib/spec/src/auditlogger.rs @@ -0,0 +1,108 @@ +// AUDITLOGGER.rs +// by Lut99 +// +// Created: +// 09 Oct 2024, 13:38:41 +// Last edited: +// 10 Oct 2024, 14:39:33 +// Auto updated? +// Yes +// +// Description: +//! Defines the interface between the reasoner and some component +//! creating audit trails. +// + +use std::error::Error; +use std::fmt::Display; +use std::future::Future; + +use crate::reasonerconn::ReasonerResponse; + + +/***** AUXILLARY *****/ +/// Defines a wrapper around any [`AuditLogger`] that extends it with some kind of [`tracing`]-like +/// session information. +#[derive(Clone, Debug)] +pub struct SessionedAuditLogger { + /// The reference used to recognize the logs based on user input. + reference: String, + /// The nested logger + logger: L, +} +impl SessionedAuditLogger { + /// Constructor for the SessionedAuditLogger. + /// + /// # Arguments + /// - `reference`: The reference used to recognize the logs based on user input. + /// - `logger`: The nested logger. + /// + /// # Returns + /// A new instance of Self, ready for logging. + #[inline] + pub fn new(reference: impl Into, logger: L) -> Self { Self { reference: reference.into(), logger } } + + /// Provides read-only access to the internal reference. + #[inline] + pub fn reference(&self) -> &str { &self.reference } +} +impl SessionedAuditLogger { + /// Alias for [`AuditLogger::log_response()`] but using the internal response instead of the + /// given one. + /// + /// # Arguments + /// - `response`: The [`ReasonerResponse`] that we're logging. + /// - `raw`: The raw response produced by the reasoner, if applicable. + pub fn log_response<'a, R>( + &'a self, + response: &'a ReasonerResponse, + raw: Option<&'a str>, + ) -> impl 'a + Future::Error>> + where + R: Display, + { + L::log_response(&self.logger, &self.reference, response, raw) + } +} +impl AuditLogger for SessionedAuditLogger { + type Error = L::Error; + + fn log_response<'a, R>( + &'a self, + reference: &'a str, + response: &'a ReasonerResponse, + raw: Option<&'a str>, + ) -> impl 'a + Future> + where + R: Display, + { + L::log_response(&self.logger, reference, response, raw) + } +} + + + + + +/***** LIBRARY *****/ +/// Defines a generic interface to write to an audit trail. +pub trait AuditLogger { + /// Defines the errors returned by this logger. + type Error: Error; + + + /// Log the response of a reasoner. + /// + /// # Arguments + /// - `reference`: Some reference that links the response to a particular query. + /// - `response`: The [`ReasonerResponse`] that we're logging. + /// - `raw`: The raw response produced by the reasoner, if applicable. + fn log_response<'a, R>( + &'a self, + reference: &'a str, + response: &'a ReasonerResponse, + raw: Option<&'a str>, + ) -> impl 'a + Future> + where + R: Display; +} diff --git a/lib/spec/src/lib.rs b/lib/spec/src/lib.rs new file mode 100644 index 0000000..9a2dc19 --- /dev/null +++ b/lib/spec/src/lib.rs @@ -0,0 +1,23 @@ +// LIB.rs +// by Lut99 +// +// Created: +// 09 Oct 2024, 13:37:15 +// Last edited: +// 10 Oct 2024, 15:01:20 +// Auto updated? +// Yes +// +// Description: +//! Defines various interfaces between various parts of the reasoner. +// + +// Declare the modules +pub mod auditlogger; +pub mod reasonerconn; +pub mod stateresolver; + +// Bring some of it into the namespace. +pub use auditlogger::AuditLogger; +pub use reasonerconn::ReasonerConnector; +pub use stateresolver::StateResolver; diff --git a/lib/spec/src/reasonerconn.rs b/lib/spec/src/reasonerconn.rs new file mode 100644 index 0000000..9361277 --- /dev/null +++ b/lib/spec/src/reasonerconn.rs @@ -0,0 +1,73 @@ +// REASONERCONN.rs +// by Lut99 +// +// Created: +// 09 Oct 2024, 13:35:41 +// Last edited: +// 10 Oct 2024, 16:07:17 +// Auto updated? +// Yes +// +// Description: +//! Defines the interface to the backend reasoner. +// + +use std::error::Error; +use std::future::Future; + +use serde::{Deserialize, Serialize}; + +use crate::auditlogger::{AuditLogger, SessionedAuditLogger}; + + +/***** AUXILLARY *****/ +/// Defines the result of a reasoner. +/// +/// # Generics +/// - `R`: A type that describes the reason(s) for the query being violating. +#[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)] +pub enum ReasonerResponse { + /// The state is compliant to the policy w.r.t. the question. + Success, + /// The state is _not_ compliant to the policy w.r.t. the question. + Violated(R), +} + + + + + +/***** LIBRARY *****/ +/// Defines the interface with the backend reasoner. +pub trait ReasonerConnector { + /// The type of state that this reasoner accepts. + type State; + /// The type of question that this reasoner accepts. + type Question; + /// Any reason(s) that are given by the reasoner that explain why something is violating. + type Reason; + /// The error returned by the reasoner. + type Error: Error; + + + /// Sends a policy to the backend reasoner. + /// + /// # Arguments + /// - `state`: The [`ReasonerConnector::State`] that describes the state to check in the reasoner. + /// - `question`: The [`ReasonerConnector::Question`] that selects exactly what kind of compliance is being checked. + /// - `logger`: A [`SessionedAuditLogger`] wrapping some [`AuditLogger`] that is used to write to the audit trail as the question's being asked. + /// + /// # Returns + /// A [`ReasonerResponse`] that describes the answer to the `question` of compliance of the `state`. + /// + /// # Errors + /// This function may error if the reasoner was unreachable or did not respond (correctly). + fn consult( + &self, + state: Self::State, + question: Self::Question, + logger: &SessionedAuditLogger, + ) -> impl Future, Self::Error>> + where + L: AuditLogger; +} diff --git a/lib/spec/src/stateresolver.rs b/lib/spec/src/stateresolver.rs new file mode 100644 index 0000000..e47bfbe --- /dev/null +++ b/lib/spec/src/stateresolver.rs @@ -0,0 +1,109 @@ +// STATERESOLVER.rs +// by Lut99 +// +// Created: +// 10 Oct 2024, 14:57:24 +// Last edited: +// 10 Oct 2024, 16:07:13 +// Auto updated? +// Yes +// +// Description: +//! Provides an interface to state resolvers. +// + +use std::error::Error; +use std::future::Future; + +use paste::paste; + +use crate::auditlogger::{AuditLogger, SessionedAuditLogger}; + + +/***** HELPER MACRO *****/ +/// Implements the [`StateResolver`] for tuples of the given sizes. +macro_rules! tuple_impls { + // Public interface + ($fi:tt $(, $i:tt)*) => { + // First, let's reverse the tuple + tuple_impls!(pair ($fi, $fi): $($i),* :); + }; + + // Pair up the items + (pair ($fi:tt, $pi:tt): : $(($pri:tt, $ri:tt)),*) => { + tuple_impls!(reverse ($fi): $(($pri, $ri)),*:); + }; + (pair ($fi:tt, $pi:tt): $i1:tt $(, $i:tt)*: $(($pri:tt, $ri:tt)),*) => { + tuple_impls!(pair ($fi, $i1): $($i),*: $(($pri, $ri),)* ($pi, $i1)); + }; + + // Reverse the items + (reverse ($fi:tt): : $(($pri:tt, $ri:tt)),*) => {}; + (reverse ($fi:tt): ($pi1:tt, $i1:tt) $(, ($pi:tt, $i:tt))* : $(($pri:tt, $ri:tt)),*) => { + tuple_impl!($fi, $i1, $(($pri, $ri),)* ($pi1, $i1)); + tuple_impls!(reverse ($fi): $(($pi, $i)),* : $(($pri, $ri),)* ($pi1, $i1)); + }; +} + +/// Implements the [`StateResolver`] for a tuple of a specific length. +macro_rules! tuple_impl { + ($fi:tt, $li:tt, $(($pi:tt, $i:tt)),*) => { + paste! { + impl] $(, [])*> StateResolver for ([] $(, [])*) + where + E: std::error::Error, + []: StateResolver, + $([]: StateResolver]::Resolved, Error = E>,)* + { + type State = []::State; + type Resolved = []::Resolved; + type Error = E; + + fn resolve(&self, state: Self::State, logger: &SessionedAuditLogger) -> impl Future> + where + L: AuditLogger, + { + async move { + let resolved: []::Resolved = self.$fi.resolve(state, logger).await?; + $(let resolved: []::Resolved = self.$i.resolve(resolved, logger).await?;)* + Ok(resolved) + } + } + } + } + }; +} + + + + + +/***** LIBRARY *****/ +/// Defines an interface that sits before a reasoner to collect its state. +pub trait StateResolver { + /// Defines the state that is being resolved. + type State; + /// Defines the state after resolution; may be the same. + type Resolved; + /// Defines the errors occurring during resolution. + type Error: Error; + + + /// Resolves the given state. + /// + /// # Arguments + /// - `state`: The [`StateResolver::State`] that we already have and want to (further) resolve. + /// - `logger`: A [`SessionedAuditLogger`] wrapping some [`AuditLogger`] that is used to write to the audit trail as the question's being asked. + /// + /// # Returns + /// A [`StateResolver::Resolved`] that represents the (more) resolved version of `state`. + /// + /// # Errors + /// This function may error if it failed to do its resolution. + fn resolve(&self, state: Self::State, logger: &SessionedAuditLogger) -> impl Future> + where + L: AuditLogger; +} + +// Default impls +tuple_impls!(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15); diff --git a/lib/srv/Cargo.toml b/lib/srv/Cargo.toml deleted file mode 100644 index 10d79d6..0000000 --- a/lib/srv/Cargo.toml +++ /dev/null @@ -1,38 +0,0 @@ -[package] -name = "srv" -rust-version = "1.81" -edition = "2021" -version.workspace = true -repository.workspace = true -authors.workspace = true -license.workspace = true - -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - -[dependencies] -# Crates.io -chrono = "0.4.35" -http = "1.1.0" -log = "0.4.22" -problem_details = "0.5.1" -serde = { version="1.0.204", features=["derive"] } -serde_json = {version = "1.0.120" , features = ["raw_value"] } -tokio = { version = "1.38.0", features = ["full"] } -uuid = { version="1.7.0", features = ["v4"] } -warp = "0.3" - -# Path -audit-logger = { path = "../audit-logger" } -auth-resolver = { path = "../auth-resolver" } -deliberation = { path = "../deliberation" } -policy = { path = "../policy" } -reasonerconn = {path = "../reasonerconn"} -state-resolver = { path = "../state-resolver" } -workflow = { path = "../workflow" } - -# Workspace dependencies -error-trace.workspace = true - -# Brane -brane-ast = { git = "https://github.com/braneframework/brane" } -brane-exe = { git = "https://github.com/braneframework/brane" } diff --git a/lib/srv/src/deliberation.rs b/lib/srv/src/deliberation.rs deleted file mode 100644 index f9f4e19..0000000 --- a/lib/srv/src/deliberation.rs +++ /dev/null @@ -1,462 +0,0 @@ -// DELIBERATION.rs -// by Lut99 -// -// Created: -// 09 Jan 2024, 13:45:18 -// Last edited: -// 12 Jun 2024, 17:51:56 -// Auto updated? -// Yes -// -// Description: -//! Implements the deliberation side of the [`Srv`]. -// - -use std::error::Error; -use std::fmt::{Debug, Display, Formatter, Result as FResult}; -use std::sync::Arc; - -use audit_logger::{AuditLogger, SessionedConnectorAuditLogger}; -use auth_resolver::{AuthContext, AuthResolver}; -use brane_ast::SymTable; -use deliberation::spec::{ - AccessDataRequest, DataAccessResponse, DeliberationAllowResponse, DeliberationDenyResponse, DeliberationResponse, ExecuteTaskRequest, - TaskExecResponse, Verdict, WorkflowValidationRequest, WorkflowValidationResponse, -}; -use error_trace::ErrorTrace as _; -use log::{debug, error, info}; -use policy::{Policy, PolicyDataAccess, PolicyDataError}; -use reasonerconn::ReasonerConnector; -use serde::Serialize; -use state_resolver::StateResolver; -use warp::Filter; -use warp::hyper::StatusCode; -use warp::reject::{Reject, Rejection}; -use warp::reply::{Json, WithStatus}; -use workflow::Workflow; - -use crate::Srv; - -/***** HELPER FUNCTIONS *****/ -/// Retrieves the currently active policy, or immediately denies the request if there is no such policy. -/// -/// # Arguments -/// - `logger`: A [`SessionedConnectorAuditLogger`] on which to log the verdict if we deny because no active policy was found. -/// - `reference`: The UUID that the policy expert can use to recognize that this verdict belongs to a particular request, if any. -/// - `policystore`: The story with [`PolicyDataAccess`] from which we'll try to retrieve the active policy. -/// -/// # Errors -/// This function may error (= reject the request) if no active policy was found or there was another error trying to retrieve it. -async fn get_active_policy( - logger: &L, - reference: &str, - policystore: &P, -) -> Result>, Rejection> { - // Attempt to get the policy first - match policystore.get_active().await { - Ok(policy) => Ok(Ok(policy)), - Err(PolicyDataError::NotFound) => { - debug!("Denying incoming request by default (no active policy found)"); - - // Create the verdict - let verdict = Verdict::Deny(DeliberationDenyResponse { - shared: DeliberationResponse { verdict_reference: reference.into() }, - reasons_for_denial: None, - }); - - // Log it: first, the "actual response" with the reason and then the verdict returned to the user - logger.log_reasoner_response(reference, "").await.map_err(|err| { - debug!("Could not log \"reasoner response\" to audit log : {:?} | request id: {}", err, reference); - warp::reject::custom(err) - })?; - logger.log_verdict(reference, &verdict).await.map_err(|err| { - debug!("Could not log verdict to audit log : {:?} | request id: {}", err, reference); - warp::reject::custom(err) - })?; - - // Then send it to the user as promised - Ok(Err(warp::reply::with_status(warp::reply::json(&verdict), StatusCode::OK))) - }, - Err(PolicyDataError::GeneralError(err)) => { - error!("Failed to get currently active policy: {err}"); - Err(warp::reject::custom(RejectableString(err))) - }, - } -} - -/***** HELPERS *****/ -/// Defines a wrapper around a [`String`] to make it [`Reject`]able. -struct RejectableString(String); -impl Debug for RejectableString { - #[inline] - fn fmt(&self, f: &mut Formatter<'_>) -> FResult { if f.alternate() { write!(f, "{:#?}", self.0) } else { write!(f, "{:?}", self.0) } } -} -impl Reject for RejectableString {} - -/// Defines a wrapper around an [`Error`] that also makes it [`Reject`]. -#[derive(Debug)] -struct RejectableError(E); -impl Display for RejectableError { - #[inline] - fn fmt(&self, f: &mut Formatter<'_>) -> FResult { write!(f, "{}", self.0) } -} -impl Error for RejectableError { - #[inline] - fn source(&self) -> Option<&(dyn Error + 'static)> { self.0.source() } -} -impl Reject for RejectableError {} - -/***** IMPLEMENTATION *****/ -impl Srv -where - L: 'static + AuditLogger + Send + Sync + Clone, - C: 'static + ReasonerConnector + Send + Sync, - P: 'static + PolicyDataAccess + Send + Sync, - S: 'static + StateResolver + Send + Sync, - PA: 'static + AuthResolver + Send + Sync, - DA: 'static + AuthResolver + Send + Sync, - C::Context: Send + Sync + Debug + Serialize, -{ - // POST /v1/deliberation/execute-task - async fn handle_execute_task_request( - auth_ctx: AuthContext, - this: Arc, - body: ExecuteTaskRequest, - ) -> Result, warp::reject::Rejection> { - info!("Handling exec-task request"); - let ExecuteTaskRequest { use_case, workflow, task_id } = body; - let verdict_reference: String = uuid::Uuid::new_v4().into(); - - // First, resolve the task ID in the workflow to the ProgramCounter ID needed for `task_id` below (and before we pass it by ownership to be converted) - debug!("Compiling WIR workflow to Checker Workflow..."); - let task_pc: String = task_id.resolved(&workflow.table).to_string(); - - // Read the body's workflow as a Checker Workflow - let workflow: Workflow = match Workflow::try_from(workflow) { - Ok(workflow) => workflow, - Err(err) => { - return Ok(warp::reply::with_status(warp::reply::json(&err.to_string()), warp::hyper::StatusCode::BAD_REQUEST)); - }, - }; - // Get the task ID based on the request's target ID - let task_id = format!("{}-{}-task", workflow.id, task_pc); - debug!("Considering task '{}' in workflow '{}'", task_id, workflow.id); - - debug!("Retrieving state..."); - let state = match this.stateresolver.get_state(use_case).await { - Ok(state) => state, - Err(err) => { - error!("Could not retrieve state: {err} | request id: {verdict_reference}"); - return Err(warp::reject::custom(RejectableError(err))); - }, - }; - debug!( - "Got state with {} datasets, {} functions, {} locations and {} users", - state.datasets.len(), - state.functions.len(), - state.locations.len(), - state.users.len() - ); - - let verdict_reference: String = uuid::Uuid::new_v4().into(); - debug!("Generated verdict_reference: {}", verdict_reference); - - debug!("Retrieving active policy..."); - let policy: Policy = match get_active_policy(&this.logger, &verdict_reference, &this.policystore).await? { - Ok(policy) => policy, - Err(err) => return Ok(err), - }; - // let policy = this.policystore.get_active().await.unwrap(); - debug!("Got policy with {} bodies", policy.content.len()); - - this.logger - .log_exec_task_request(&verdict_reference, &auth_ctx, policy.version.version.unwrap(), &state, &workflow, &task_id) - .await - .map_err(|err| { - debug!("Could not log exec task request to audit log : {:?} | request id: {}", err, verdict_reference); - warp::reject::custom(err) - })?; - - debug!("Consulting reasoner connector..."); - - match this - .reasonerconn - .execute_task(SessionedConnectorAuditLogger::new(verdict_reference.clone(), this.logger.clone()), policy, state, workflow, task_id) - .await - { - Ok(v) => { - let resp: Verdict = if !v.success { - Verdict::Deny(DeliberationDenyResponse { - shared: TaskExecResponse { verdict_reference: verdict_reference.clone() }, - reasons_for_denial: Some(v.errors), - }) - } else { - Verdict::Allow(DeliberationAllowResponse { - shared: TaskExecResponse { verdict_reference: verdict_reference.clone() }, - // TODO implement signature - signature: "signature".into(), - }) - }; - - this.logger.log_verdict(&verdict_reference, &resp).await.map_err(|err| { - debug!("Could not log execute task verdict to audit log : {:?} | request id: {}", err, verdict_reference); - warp::reject::custom(err) - })?; - - Ok(warp::reply::with_status(warp::reply::json(&resp), warp::hyper::StatusCode::OK)) - }, - Err(err) => Ok(warp::reply::with_status(warp::reply::json(&format!("{}", err)), warp::hyper::StatusCode::OK)), - } - } - - // POST /v1/deliberation/access-data - async fn handle_access_data_request( - auth_ctx: AuthContext, - this: Arc, - body: AccessDataRequest, - ) -> Result, warp::reject::Rejection> { - info!("Handling access-data request"); - let AccessDataRequest { use_case, workflow, data_id, task_id } = body; - - let verdict_reference: String = uuid::Uuid::new_v4().into(); - - debug!("Compiling WIR workflow to Checker Workflow..."); - - // Read the body's workflow as a Checker Workflow - // NOTE: We need the deep clone of the table here to ensure that the `Arc` in the WIR is not duplicated. Nice design, Tim! - let table: SymTable = (*workflow.table).clone(); - let workflow: Workflow = match Workflow::try_from(workflow) { - Ok(workflow) => workflow, - Err(err) => { - return Ok(warp::reply::with_status(warp::reply::json(&err.trace().to_string()), warp::hyper::StatusCode::BAD_REQUEST)); - }, - }; - - debug!("Retrieving state..."); - let state = match this.stateresolver.get_state(use_case).await { - Ok(state) => state, - Err(err) => { - error!("Could not retrieve state: {err} | request id: {verdict_reference}"); - return Err(warp::reject::custom(RejectableError(err))); - }, - }; - debug!( - "Got state with {} datasets, {} functions, {} locations and {} users", - state.datasets.len(), - state.functions.len(), - state.locations.len(), - state.users.len() - ); - - debug!("Retrieving active policy..."); - let policy = match get_active_policy(&this.logger, &verdict_reference, &this.policystore).await? { - Ok(policy) => policy, - Err(err) => return Ok(err), - }; - debug!("Got policy with {} bodies", policy.content.len()); - - let task_id: Option = match task_id { - Some(task_id) => { - // First, resolve the task ID in the workflow to the ProgramCounter ID needed for `task_id` below (and before we pass it by ownership to be converted) - let task_pc: String = task_id.resolved(&table).to_string(); - - // Get the task ID based on the request's target ID - let task_id = format!("{}-{}-task", workflow.id, task_pc); - debug!("Considering task '{}' in workflow '{}'", task_id, workflow.id); - Some(task_id) - }, - None => None, - }; - - debug!("Retrieving active policy..."); - let policy = match this.policystore.get_active().await { - Ok(p) => p, - Err(_) => { - let resp = Verdict::Deny(DeliberationDenyResponse { - shared: DataAccessResponse { verdict_reference: verdict_reference.clone() }, - reasons_for_denial: vec![].into(), - }); - - this.logger.log_data_access_request(&verdict_reference, &auth_ctx, -1, &state, &workflow, &data_id, &task_id).await.map_err( - |err| { - debug!("Could not log data access request to audit log : {:?} | request id: {}", err, verdict_reference); - warp::reject::custom(err) - }, - )?; - - this.logger.log_verdict(&verdict_reference, &resp).await.map_err(|err| { - debug!("Could not log data access verdict to audit log : {:?} | request id: {}", err, verdict_reference); - warp::reject::custom(err) - })?; - - return Ok(warp::reply::with_status(warp::reply::json(&resp), warp::hyper::StatusCode::OK)); - }, - }; - debug!("Got policy with {} bodies", policy.content.len()); - - this.logger - .log_data_access_request(&verdict_reference, &auth_ctx, policy.version.version.unwrap(), &state, &workflow, &data_id, &task_id) - .await - .map_err(|err| { - debug!("Could not log data access request to audit log : {:?} | request id: {}", err, verdict_reference); - warp::reject::custom(err) - })?; - - debug!("Consulting reasoner connector..."); - - match this - .reasonerconn - .access_data_request( - SessionedConnectorAuditLogger::new(verdict_reference.clone(), this.logger.clone()), - policy, - state, - workflow, - data_id, - task_id, - ) - .await - { - Ok(v) => { - let resp: Verdict = if !v.success { - Verdict::Deny(DeliberationDenyResponse { - shared: DataAccessResponse { verdict_reference: verdict_reference.clone() }, - reasons_for_denial: Some(v.errors), - }) - } else { - Verdict::Allow(DeliberationAllowResponse { - shared: DataAccessResponse { verdict_reference: verdict_reference.clone() }, - // TODO implement signature - signature: "signature".into(), - }) - }; - - this.logger.log_verdict(&verdict_reference, &resp).await.map_err(|err| { - debug!("Could not log data access verdict to audit log : {:?} | request id: {}", err, verdict_reference); - warp::reject::custom(err) - })?; - - Ok(warp::reply::with_status(warp::reply::json(&resp), warp::hyper::StatusCode::OK)) - }, - Err(err) => Ok(warp::reply::with_status(warp::reply::json(&format!("{}", err)), warp::hyper::StatusCode::OK)), - } - } - - // POST /v1/deliberation/validate-workflow - async fn handle_validate_workflow_request( - auth_ctx: AuthContext, - this: Arc, - body: WorkflowValidationRequest, - ) -> Result, warp::reject::Rejection> { - info!("Handling validate request"); - let WorkflowValidationRequest { use_case, workflow } = body; - - let verdict_reference: String = uuid::Uuid::new_v4().into(); - - debug!("Compiling WIR workflow to Checker Workflow..."); - // Read the body's workflow as a Checker Workflow - let workflow: Workflow = match Workflow::try_from(workflow) { - Ok(workflow) => workflow, - Err(err) => { - return Ok(warp::reply::with_status(warp::reply::json(&err.to_string()), warp::hyper::StatusCode::BAD_REQUEST)); - }, - }; - - debug!("Retrieving state..."); - let state = match this.stateresolver.get_state(use_case).await { - Ok(state) => state, - Err(err) => { - error!("Could not retrieve state: {err} | request id: {verdict_reference}"); - return Err(warp::reject::custom(RejectableError(err))); - }, - }; - debug!( - "Got state with {} datasets, {} functions, {} locations and {} users", - state.datasets.len(), - state.functions.len(), - state.locations.len(), - state.users.len() - ); - - let verdict_reference: String = uuid::Uuid::new_v4().into(); - debug!("Generated verdict_reference: {}", verdict_reference); - - debug!("Retrieving active policy..."); - let policy = match get_active_policy(&this.logger, &verdict_reference, &this.policystore).await? { - Ok(policy) => policy, - Err(err) => return Ok(err), - }; - debug!("Got policy with {} bodies", policy.content.len()); - - this.logger.log_validate_workflow_request(&verdict_reference, &auth_ctx, policy.version.version.unwrap(), &state, &workflow).await.map_err( - |err| { - debug!("Could not log validate workflow request to audit log : {:?} | request id: {}", err, verdict_reference); - warp::reject::custom(err) - }, - )?; - - debug!("Consulting reasoner connector..."); - - match this - .reasonerconn - .workflow_validation_request(SessionedConnectorAuditLogger::new(verdict_reference.clone(), this.logger.clone()), policy, state, workflow) - .await - { - Ok(v) => { - let resp: Verdict = if !v.success { - Verdict::Deny(DeliberationDenyResponse { - shared: WorkflowValidationResponse { verdict_reference: verdict_reference.clone() }, - reasons_for_denial: Some(v.errors), - }) - } else { - Verdict::Allow(DeliberationAllowResponse { - shared: WorkflowValidationResponse { verdict_reference: verdict_reference.clone() }, - // TODO implement signature - signature: "signature".into(), - }) - }; - - this.logger.log_verdict(&verdict_reference, &resp).await.map_err(|err| { - debug!("Could not log workflow validation verdict to audit log : {:?} | request id: {}", err, verdict_reference); - warp::reject::custom(err) - })?; - - Ok(warp::reply::with_status(warp::reply::json(&resp), warp::hyper::StatusCode::OK)) - }, - Err(err) => Ok(warp::reply::with_status(warp::reply::json(&format!("{}", err)), warp::hyper::StatusCode::OK)), - } - } - - pub fn deliberation_handlers(this: Arc) -> impl Filter + Clone { - let exec_task = warp::post() - .and(warp::path!("execute-task")) - .and(Self::with_deliberation_api_auth(this.clone())) - .and(Self::with_self(this.clone())) - .and(warp::body::json()) - .and_then(Self::handle_execute_task_request); - - let access_data = warp::post() - .and(warp::path!("access-data")) - .and(Self::with_deliberation_api_auth(this.clone())) - .and(Self::with_self(this.clone())) - .and(warp::body::json()) - .and_then(Self::handle_access_data_request); - - let execute_workflow = warp::post() - .and(warp::path!("execute-workflow")) - .and(Self::with_deliberation_api_auth(this.clone())) - .and(Self::with_self(this.clone())) - .and(warp::body::json()) - .and_then(Self::handle_validate_workflow_request); - - warp::path("v1").and(warp::path("deliberation")).and(exec_task.or(access_data).or(execute_workflow)) - } - - pub fn with_deliberation_api_auth(this: Arc) -> impl Filter + Clone { - Self::with_self(this.clone()).and(warp::header::headers_cloned()).and_then(|this: Arc, headers| async move { - match this.dauthresolver.authenticate(headers).await { - Ok(v) => Ok(v), - Err(err) => Err(warp::reject::custom(err)), - } - }) - } -} diff --git a/lib/srv/src/lib.rs b/lib/srv/src/lib.rs deleted file mode 100644 index 7d8e870..0000000 --- a/lib/srv/src/lib.rs +++ /dev/null @@ -1,187 +0,0 @@ -use std::convert::Infallible; -use std::fmt::Debug; -use std::net::SocketAddr; -use std::sync::Arc; -use std::time::Duration; - -use ::policy::{Context, PolicyDataAccess, PolicyDataError}; -use audit_logger::AuditLogger; -use auth_resolver::{AuthContext, AuthResolver}; -use error_trace::trace; -use log::{debug, error, info, warn}; -use reasonerconn::ReasonerConnector; -use serde::{Deserialize, Serialize}; -use state_resolver::StateResolver; -use tokio::signal::unix::{Signal, SignalKind, signal}; -use warp::Filter; -use warp::reject::Rejection; -use warp::reply::Reply; - -use crate::problem::Problem; - -pub mod deliberation; -pub mod models; -pub mod policy; -pub mod problem; -pub mod reasoner_conn_ctx; - -/// Function that returns a future that only returns if either SIGTERM or SIGINT has been sent to this process. -/// -/// This is used to gracefully shut down the warp server, which takes an async function and will run until it returns. This mostly improves Docker-compatability, as it responds to `docker stop` and all that. -/// -/// Only works on Unix. -async fn graceful_signal() { - // Register a SIGTERM handler to be Docker-friendly - let term_handler: Option = match signal(SignalKind::terminate()) { - Ok(handler) => Some(handler), - Err(err) => { - error!("{}", trace!(("Failed to register SIGTERM signal handler"), err)); - warn!("Service will NOT shutdown gracefully on SIGTERM"); - None - }, - }; - - // Also register a SIGINT handler to be manual-friendly - let int_handler: Option = match signal(SignalKind::interrupt()) { - Ok(handler) => Some(handler), - Err(err) => { - error!("{}", trace!(("Failed to register SIGINT signal handler"), err)); - warn!("Service will NOT shutdown gracefully on SIGINT"); - None - }, - }; - - // Wait until we receive such a signal after which we terminate the server - match (term_handler, int_handler) { - (Some(mut term), Some(mut int)) => tokio::select! { - _ = term.recv() => { - info!("Received SIGTERM, shutting down gracefully..."); - }, - - _ = int.recv() => { - info!("Received SIGINT, shutting down gracefully..."); - }, - }, - - (Some(mut term), None) => { - term.recv().await; - info!("Received SIGTERM, shutting down gracefully..."); - }, - (None, Some(mut int)) => { - int.recv().await; - info!("Received SIGINT, shutting down gracefully..."); - }, - - // Just wait forever to not stop the warp server - (None, None) => loop { - tokio::time::sleep(Duration::from_secs(24 * 3600)).await; - }, - } -} - -pub struct Srv { - addr: SocketAddr, - logger: L, - reasonerconn: C, - policystore: P, - stateresolver: S, - pauthresolver: PA, - dauthresolver: DA, -} - -#[derive(Serialize, Deserialize)] -struct PingResponse { - success: bool, - ping: String, -} - -impl Srv -where - L: 'static + AuditLogger + Send + Sync + Clone, - C: 'static + ReasonerConnector + Send + Sync, - P: 'static + PolicyDataAccess + Send + Sync, - S: 'static + StateResolver + Send + Sync, - PA: 'static + AuthResolver + Send + Sync, - DA: 'static + AuthResolver + Send + Sync, - C::Context: Send + Sync + Debug + Serialize, -{ - pub fn new( - addr: impl Into, - logger: L, - reasonerconn: C, - policystore: P, - stateresolver: S, - pauthresolver: PA, - dauthresolver: DA, - ) -> Self { - Srv { addr: addr.into(), logger, reasonerconn, policystore, stateresolver, pauthresolver, dauthresolver } - } - - fn with_self(this: Arc) -> impl Filter,), Error = Infallible> + Clone { warp::any().map(move || this.clone()) } - - pub async fn run(self) { - let addr: SocketAddr = self.addr; - let this_arc: Arc = Arc::new(self); - - let ping = warp::get().and(warp::path("ping")).map(|| warp::reply::json(&PingResponse { success: true, ping: String::from("pong") })); - let policy_api = Self::policy_handlers(this_arc.clone()); - let reasoner_conn_api = Self::reasoner_connector_handlers(this_arc.clone()); - let deliberation_api = Self::deliberation_handlers(this_arc.clone()); - - let index = warp::any().and(deliberation_api.or(policy_api).or(reasoner_conn_api).or(ping)).recover(|err: Rejection| async move { - debug!("err: {:?}", err); - let res: Result, Rejection> = if let Some(auth_resolver::AuthResolverError { .. }) = err.find() { - Ok(Box::new(warp::reply::with_status(warp::reply::reply(), warp::http::StatusCode::UNAUTHORIZED))) - } else if let Some(audit_logger::Error::CouldNotDeliver { .. }) = err.find() { - Ok(Box::new(warp::reply::with_status(warp::reply::reply(), warp::http::StatusCode::INTERNAL_SERVER_ERROR))) - } else if let Some(problem) = err.find::() { - Ok(Box::new(warp::reply::with_status(warp::reply::json(&problem.0), problem.0.status.unwrap()))) - } else { - debug!("Got err: {:?}", err); - Err(err) - }; - res - }); - - // Log reasoner connector context - let ctx_hash = C::hash(); - match this_arc.clone().logger.log_reasoner_context::().await { - Ok(_) => {}, - Err(err) => panic!("Failed to log reasoner context on startup {:?}", err), - } - - // Disable active policy if base definitions changed - if let Ok(v) = this_arc.policystore.get_active().await { - let t = this_arc.clone(); - if v.version.reasoner_connector_context != ctx_hash { - let ap = this_arc.policystore.get_active().await.unwrap(); - let result = t - .policystore - .deactivate_policy(Context { initiator: "system".into() }, || async move { - this_arc - .logger - .log_deactivate_policy(&AuthContext { initiator: "system".into(), system: "self".into() }) - .await - .map_err(|err| PolicyDataError::GeneralError(err.to_string())) - }) - .await; - - match result { - Ok(_) => {}, - Err(err) => { - panic!("Could not deactivate policy because of changed base definition: {:?}", err); - }, - } - - debug!( - "Deactivated policy because of changed base definition; hash changed from '{}' to '{}'", - ap.version.reasoner_connector_context, ctx_hash - ) - } - } - - let (addr, srv) = warp::serve(index).bind_with_graceful_shutdown(addr, graceful_signal()); - info!("Now serving at {addr}; ready for requests"); - srv.await; - } -} diff --git a/lib/srv/src/models.rs b/lib/srv/src/models.rs deleted file mode 100644 index 2cbef02..0000000 --- a/lib/srv/src/models.rs +++ /dev/null @@ -1,44 +0,0 @@ -use policy::{Policy, PolicyContent, PolicyVersion}; -use serde::{Deserialize, Serialize}; - -#[derive(Deserialize, Serialize)] -pub struct SetVersionPostModel { - pub version: i64, -} - -#[derive(Deserialize, Serialize)] -pub struct PolicyContentPostModel { - pub reasoner: String, - pub reasoner_version: String, - pub content: Box, -} - -#[derive(Deserialize, Serialize)] -pub struct AddPolicyPostModel { - pub description: Option, - pub version_description: String, - pub content: Vec, -} - -impl AddPolicyPostModel { - pub fn to_domain(&self) -> Policy { - Policy { - description: match self.description.clone() { - Some(d) => d, - None => "".into(), - }, - version: PolicyVersion { - creator: None, - created_at: chrono::Local::now(), - version: None, - version_description: self.version_description.clone(), - reasoner_connector_context: "".into(), - }, - content: self - .content - .iter() - .map(|c| PolicyContent { reasoner: c.reasoner.clone(), reasoner_version: c.reasoner_version.clone(), content: c.content.clone() }) - .collect(), - } - } -} diff --git a/lib/srv/src/policy.rs b/lib/srv/src/policy.rs deleted file mode 100644 index 988d5c4..0000000 --- a/lib/srv/src/policy.rs +++ /dev/null @@ -1,262 +0,0 @@ -use std::fmt::Debug; -use std::sync::Arc; - -use audit_logger::AuditLogger; -use auth_resolver::{AuthContext, AuthResolver}; -use policy::{Context, PolicyDataAccess, PolicyDataError}; -use problem_details::ProblemDetails; -use reasonerconn::ReasonerConnector; -use serde::Serialize; -use state_resolver::StateResolver; -use warp::Filter; - -use crate::problem::Problem; -use crate::{Srv, models}; - -impl Srv -where - L: 'static + AuditLogger + Send + Sync + Clone, - C: 'static + ReasonerConnector + Send + Sync, - P: 'static + PolicyDataAccess + Send + Sync, - S: 'static + StateResolver + Send + Sync, - PA: 'static + AuthResolver + Send + Sync, - DA: 'static + AuthResolver + Send + Sync, - C::Context: Send + Sync + Debug + Serialize, -{ - // GET specific version - // GET /v1/policies/:version - // out: - // - 200 Policy - // - 404 - - async fn handle_get_policy_version(_auth_ctx: AuthContext, version: i64, this: Arc) -> Result { - match this.policystore.get_version(version).await { - Ok(v) => Ok(warp::reply::json(&v)), - Err(err) => match err { - PolicyDataError::NotFound => { - let p = ProblemDetails::new().with_status(warp::http::StatusCode::NOT_FOUND); - Err(warp::reject::custom(Problem(p))) - }, - PolicyDataError::GeneralError(msg) => { - let p = ProblemDetails::new().with_status(warp::http::StatusCode::BAD_REQUEST).with_detail(msg); - Err(warp::reject::custom(Problem(p))) - }, - }, - } - } - - // List policy's versions - // GET /v1/policies - // out: - // - 200 Vec - - async fn handle_get_all_policies(_auth_ctx: AuthContext, this: Arc) -> Result { - match this.policystore.get_versions().await { - Ok(v) => Ok(warp::reply::json(&v)), - Err(err) => match err { - PolicyDataError::NotFound => { - let p = ProblemDetails::new().with_status(warp::http::StatusCode::NOT_FOUND); - Err(warp::reject::custom(Problem(p))) - }, - PolicyDataError::GeneralError(msg) => { - let p = ProblemDetails::new().with_status(warp::http::StatusCode::BAD_REQUEST).with_detail(msg); - Err(warp::reject::custom(Problem(p))) - }, - }, - } - } - - // Create new version of policy - // POST /v1/policies - // in: Policy - // out: - // - 201 Policy. version in body - // - 400 problem+json - - async fn handle_add_policy( - auth_ctx: AuthContext, - this: Arc, - body: models::AddPolicyPostModel, - ) -> Result { - let t: Arc = this.clone(); - let mut model = body.to_domain(); - model.version.reasoner_connector_context = C::hash(); - match this - .policystore - .add_version(model, Context { initiator: auth_ctx.initiator.clone() }, |policy| async move { - t.logger.log_add_policy_request::(&auth_ctx, &policy).await.map_err(|err| match err { - audit_logger::Error::CouldNotDeliver(err) => PolicyDataError::GeneralError(err), - }) - }) - .await - { - Ok(policy) => Ok(warp::reply::json(&policy)), - Err(err) => match err { - PolicyDataError::NotFound => { - let p = ProblemDetails::new().with_status(warp::http::StatusCode::NOT_FOUND); - Err(warp::reject::custom(Problem(p))) - }, - PolicyDataError::GeneralError(msg) => { - let p = ProblemDetails::new().with_status(warp::http::StatusCode::BAD_REQUEST).with_detail(msg); - Err(warp::reject::custom(Problem(p))) - }, - }, - } - } - - // Show active policy - // GET /v1/policies/active - // out: 200 {version: string} - - async fn handle_get_active_policy(_auth_ctx: AuthContext, this: Arc) -> Result { - match this.policystore.get_active().await { - Ok(v) => Ok(warp::reply::json(&v)), - Err(err) => match err { - PolicyDataError::NotFound => { - let p = ProblemDetails::new().with_status(warp::http::StatusCode::NOT_FOUND).with_detail("No version currently active"); - Err(warp::reject::custom(Problem(p))) - }, - PolicyDataError::GeneralError(msg) => { - let p = ProblemDetails::new().with_status(warp::http::StatusCode::BAD_REQUEST).with_detail(msg); - Err(warp::reject::custom(Problem(p))) - }, - }, - } - } - - // Set active policy - // PUT /v1/policies/active - // in: {version: string} - // out: - // 200 {version: string} - // 400 problem+json - - async fn handle_set_active_policy( - auth_ctx: AuthContext, - this: Arc, - body: models::SetVersionPostModel, - ) -> Result { - // Reject activation of policy with invalid base defs - let conn_hash = C::hash(); - if let Ok(policy) = this.policystore.get_version(body.version).await { - if policy.version.reasoner_connector_context != conn_hash { - let p = ProblemDetails::new().with_status(warp::http::StatusCode::BAD_REQUEST).with_detail(format!( - "Cannot activate policy which has a different base policy than current the reasoners connector's base. Policy base defs hash is \ - '{}' and connector's base defs hash is '{}'", - policy.version.reasoner_connector_context, conn_hash - )); - return Err(warp::reject::custom(Problem(p))); - } - } - - let t = this.clone(); - match this - .policystore - .set_active(body.version, Context { initiator: auth_ctx.initiator.clone() }, |policy| async move { - t.logger.log_set_active_version_policy(&auth_ctx, &policy).await.map_err(|err| match err { - audit_logger::Error::CouldNotDeliver(err) => PolicyDataError::GeneralError(err), - }) - }) - .await - { - Ok(policy) => Ok(warp::reply::json(&policy)), - Err(err) => match err { - PolicyDataError::NotFound => { - let p = ProblemDetails::new() - .with_status(warp::http::StatusCode::BAD_REQUEST) - .with_detail(format!("Invalid version: {}", body.version)); - Err(warp::reject::custom(Problem(p))) - }, - PolicyDataError::GeneralError(msg) => { - let p = ProblemDetails::new().with_status(warp::http::StatusCode::BAD_REQUEST).with_detail(msg); - Err(warp::reject::custom(Problem(p))) - }, - }, - } - } - - // Set active policy - // DELETE /v1/policies/active - // out: - // 200 - // 400 problem+json - - async fn handle_deactivate_policy(auth_ctx: AuthContext, this: Arc) -> Result { - let t = this.clone(); - match this - .policystore - .deactivate_policy(Context { initiator: auth_ctx.initiator.clone() }, || async move { - t.logger.log_deactivate_policy(&auth_ctx).await.map_err(|err| match err { - audit_logger::Error::CouldNotDeliver(err) => PolicyDataError::GeneralError(err), - }) - }) - .await - { - Ok(policy) => Ok(warp::reply::json(&policy)), - Err(err) => match err { - PolicyDataError::NotFound => { - let p = ProblemDetails::new().with_status(warp::http::StatusCode::BAD_REQUEST).with_detail("No active version to deactivate"); - Err(warp::reject::custom(Problem(p))) - }, - PolicyDataError::GeneralError(msg) => { - let p = ProblemDetails::new().with_status(warp::http::StatusCode::BAD_REQUEST).with_detail(msg); - Err(warp::reject::custom(Problem(p))) - }, - }, - } - } - - pub fn policy_handlers(this: Arc) -> impl Filter + Clone { - let add_version = warp::post() - .and(warp::path::end()) - .and(Self::with_policy_api_auth(this.clone())) - .and(Self::with_self(this.clone())) - .and(warp::body::json()) - .and_then(Self::handle_add_policy); - - let get_version = warp::get() - .and(Self::with_policy_api_auth(this.clone())) - .and(warp::path!(i64)) - .and(Self::with_self(this.clone())) - .and_then(Self::handle_get_policy_version); - - let get_all = warp::get() - .and(warp::path::end()) - .and(Self::with_policy_api_auth(this.clone())) - .and(Self::with_self(this.clone())) - .and_then(Self::handle_get_all_policies); - - let get_active = warp::get() - .and(warp::path!("active")) - .and(Self::with_policy_api_auth(this.clone())) - .and(Self::with_self(this.clone())) - .and_then(Self::handle_get_active_policy); - - let set_active = warp::put() - .and(warp::path!("active")) - .and(Self::with_policy_api_auth(this.clone())) - .and(Self::with_self(this.clone())) - .and(warp::body::json()) - .and_then(Self::handle_set_active_policy); - - let deactivate = warp::delete() - .and(warp::path!("active")) - .and(Self::with_policy_api_auth(this.clone())) - .and(Self::with_self(this.clone())) - .and_then(Self::handle_deactivate_policy); - - warp::path("v1") - .and(warp::path("management")) - .and(warp::path("policies")) - .and(get_version.or(get_all).or(get_active).or(set_active).or(add_version).or(deactivate)) - } - - fn with_policy_api_auth(this: Arc) -> impl Filter + Clone { - Self::with_self(this.clone()).and(warp::header::headers_cloned()).and_then(|this: Arc, headers| async move { - match this.pauthresolver.authenticate(headers).await { - Ok(v) => Ok(v), - Err(err) => Err(warp::reject::custom(err)), - } - }) - } -} diff --git a/lib/srv/src/problem.rs b/lib/srv/src/problem.rs deleted file mode 100644 index 57743a4..0000000 --- a/lib/srv/src/problem.rs +++ /dev/null @@ -1,6 +0,0 @@ -use problem_details::ProblemDetails; - -#[derive(Debug)] -pub struct Problem(pub ProblemDetails); - -impl warp::reject::Reject for Problem {} diff --git a/lib/srv/src/reasoner_conn_ctx.rs b/lib/srv/src/reasoner_conn_ctx.rs deleted file mode 100644 index d38d183..0000000 --- a/lib/srv/src/reasoner_conn_ctx.rs +++ /dev/null @@ -1,57 +0,0 @@ -use std::fmt::Debug; -use std::sync::Arc; - -use audit_logger::AuditLogger; -use auth_resolver::{AuthContext, AuthResolver}; -use policy::PolicyDataAccess; -use reasonerconn::ReasonerConnector; -use serde::Serialize; -use state_resolver::StateResolver; -use warp::Filter; - -use crate::Srv; - -#[derive(Serialize)] -struct ConnectorContextViewModel { - context: T, - hash: String, -} - -impl Srv -where - L: 'static + AuditLogger + Send + Sync + Clone, - C: 'static + ReasonerConnector + Send + Sync, - P: 'static + PolicyDataAccess + Send + Sync, - S: 'static + StateResolver + Send + Sync, - PA: 'static + AuthResolver + Send + Sync, - DA: 'static + AuthResolver + Send + Sync, - C::Context: Send + Sync + Debug + Serialize, -{ - // Get reasoner connector context - // GET /v1/reasoner-connector-context - // out: - // 200 - - async fn handle_reasoner_conn_ctx(_: AuthContext, _this: Arc) -> Result { - Ok(warp::reply::json(&ConnectorContextViewModel { context: Box::new(C::context()), hash: C::hash() })) - } - - pub fn reasoner_connector_handlers(this: Arc) -> impl Filter + Clone { - let get_context = warp::get() - .and(warp::path!("management" / "reasoner-connector-context")) - .and(Self::with_reasoner_connector_api_auth(this.clone())) - .and(Self::with_self(this.clone())) - .and_then(Self::handle_reasoner_conn_ctx); - - warp::path("v1").and(get_context) - } - - fn with_reasoner_connector_api_auth(this: Arc) -> impl Filter + Clone { - Self::with_self(this.clone()).and(warp::header::headers_cloned()).and_then(|this: Arc, headers| async move { - match this.pauthresolver.authenticate(headers).await { - Ok(v) => Ok(v), - Err(err) => Err(warp::reject::custom(err)), - } - }) - } -} diff --git a/lib/state-resolver/Cargo.toml b/lib/state-resolver/Cargo.toml deleted file mode 100644 index 5000d60..0000000 --- a/lib/state-resolver/Cargo.toml +++ /dev/null @@ -1,18 +0,0 @@ -[package] -name = "state-resolver" -rust-version = "1.81" -edition = "2021" -version.workspace = true -repository.workspace = true -authors.workspace = true -license.workspace = true - -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - -[dependencies] -# Crates.io -async-trait = "0.1.67" -serde = { version="1.0.204", features=["derive"] } - -# Path -workflow = {path = "../workflow"} diff --git a/lib/state-resolver/src/lib.rs b/lib/state-resolver/src/lib.rs deleted file mode 100644 index 37f690d..0000000 --- a/lib/state-resolver/src/lib.rs +++ /dev/null @@ -1,62 +0,0 @@ -use std::error::Error; - -use serde::{Deserialize, Serialize}; -use workflow::spec::{Dataset, User}; - -/***** ERRORS *****/ -/// Defines some errors being constructable in the type used in the [`StateResolver`]. -pub trait StateResolverError { - /// Checks if this error was generated because the `use_case` identifier supplied to [`StateResolver::get_state()`] was not recognized. - /// - /// # Returns - /// The given use_case identifier as a [`String`], or [`None`] if this error does not represent this case. - fn try_as_unknown_use_case(&self) -> Option<&String>; -} - -/// We implement it for `std::convert::Infallible` to allow implementations to not care about errors. -impl StateResolverError for std::convert::Infallible { - #[inline] - fn try_as_unknown_use_case(&self) -> Option<&String> { - // It will never error, so it can never be an unknown case - None - } -} - -/***** AUXILLARY *****/ -/// The state that captures runtime context, returned by a [`StateResolver`] dynamically. -/// -/// This defines everything a policy gets to know about the state of the system at the time a policy is being checked. -#[derive(Clone, Debug, Serialize, Deserialize)] -pub struct State { - // Only scientists for now - pub users: Vec, - pub locations: Vec, - pub datasets: Vec, - pub functions: Vec, - // TODO: Somehow add events / audit trail - // TODO: Somehow add duties or duty policies, maybe encode in Dataset? -} - -/***** LIBRARY *****/ -/// Defines how a state resolver looks like in general. -#[async_trait::async_trait] -pub trait StateResolver { - /// The error type emitted by this trait's functions. - /// - /// Note that the error is supposed to implement [`StateResolverError`] to communicate standard errors. - type Error: 'static + Send + StateResolverError + Sync + Error; - - /// Retrieves the current reasoner state necessary for resolving policies. - /// - /// Note that this state is agnostic to the specific reasoner connector used (and therefore policy language). - /// - /// # Arguments - /// - `use_case`: Some identifier that allows the state resolver to assume a different state depending on the use-case used. - /// - /// # Returns - /// A new [`State`] struct that encodes the current state. - /// - /// # Errors - /// This function may error whenever it likes. However, it's recommended to trigger the errors specified in the [`StateResolverError`] trait if applicable. - async fn get_state(&self, use_case: String) -> Result; -} diff --git a/lib/workflow/Cargo.toml b/lib/workflow/Cargo.toml index 061fcc7..3727802 100644 --- a/lib/workflow/Cargo.toml +++ b/lib/workflow/Cargo.toml @@ -4,44 +4,19 @@ description = "Defines the workflow representation used internally by the checke rust-version = "1.81" edition = "2021" authors = [ "Tim Müller" ] -version.workspace = true +version = "0.2.0" repository.workspace = true license.workspace = true [dependencies] -# Crates.io -log = "0.4.22" -num-traits = "0.2.18" -rand = "0.9.0" -serde = { version = "1.0.204", features = ["derive"] } +serde = { version = "1.0.204", features = ["derive"], optional = true } -# Workspace dependencies -eflint-json = { workspace = true, optional = true } enum-debug.workspace = true -transform.workspace = true - -# Brane -brane-ast = { git = "https://github.com/braneframework/brane" } -brane-exe = { git = "https://github.com/braneframework/brane" } -specifications = { git = "https://github.com/braneframework/brane" } - - -[dev-dependencies] -# Crates -log = "0.4.22" -serde_json = "1.0.120" -clap = { version = "4.5", features = ["derive"] } - -# Workspace dependencies -eflint-json = { workspace = true, features = ["display_eflint"] } -error-trace.workspace = true -humanlog.workspace = true -names.workspace = true - -# Brane -brane-shr = { git = "https://github.com/braneframework/brane" } [features] -"eflint" = [ "dep:eflint-json" ] +default = ["visualize"] + +serde = ["dep:serde"] +visualize = [] diff --git a/lib/workflow/examples/compile.rs b/lib/workflow/examples/compile.rs deleted file mode 100644 index 2929543..0000000 --- a/lib/workflow/examples/compile.rs +++ /dev/null @@ -1,337 +0,0 @@ -// COMPILE.rs -// by Lut99 -// -// Created: -// 31 Oct 2023, 14:20:54 -// Last edited: -// 29 Jan 2024, 15:57:57 -// Auto updated? -// Yes -// -// Description: -//! Implements a wrapper that converts the -//! [WIR](brane_ast::ast::Workflow) into -//! [checker workflows](workflow::workflow::spec::Workflow). -// - -use std::collections::HashMap; -use std::fmt::{Display, Formatter, Result as FResult}; -use std::path::PathBuf; -use std::str::FromStr; -use std::sync::Arc; -use std::{error, fs}; - -use brane_ast::{CompileResult, ParserOptions, ast, compile_program}; -use brane_shr::utilities::{create_data_index_from, create_package_index_from}; -use clap::Parser; -use enum_debug::EnumDebug; -use error_trace::ErrorTrace as _; -use humanlog::{DebugMode, HumanLogger}; -use log::{Level, debug, error, info}; -use specifications::data::DataIndex; -use specifications::package::PackageIndex; -use workflow::spec::Workflow; - -/***** ERRORS *****/ -/// Defines what may go wrong when parsing [`InputLanguage`]s from strings. -#[derive(Debug)] -enum InputLanguageParseError { - /// The given string identifier was unknown. - Unknown { raw: String }, -} -impl Display for InputLanguageParseError { - fn fmt(&self, f: &mut Formatter<'_>) -> FResult { - use InputLanguageParseError::*; - match self { - Unknown { raw } => write!(f, "Unknown input language '{raw}' (see `--help` for more information)"), - } - } -} -impl error::Error for InputLanguageParseError {} - -/// Defines what may go wrong when parsing [`OutputLanguage`]s from strings. -#[derive(Debug)] -enum OutputLanguageParseError { - /// The given string identifier was unknown. - Unknown { raw: String }, -} -impl Display for OutputLanguageParseError { - fn fmt(&self, f: &mut Formatter<'_>) -> FResult { - use OutputLanguageParseError::*; - match self { - Unknown { raw } => write!(f, "Unknown output language '{raw}' (see `--help` for more information)"), - } - } -} -impl error::Error for OutputLanguageParseError {} - -/// Defines binary-level origin errors. -#[derive(Debug)] -enum Error { - /// Failed to read an input file. - InputRead { path: PathBuf, err: std::io::Error }, - /// Failed to deserialize the given file as a JSON WIR. - InputDeserialize { path: PathBuf, err: serde_json::Error }, - /// Failed to compile the WIR into a Workflow - InputCompile { path: PathBuf, err: workflow::compile::Error }, -} -impl Display for Error { - fn fmt(&self, f: &mut Formatter<'_>) -> FResult { - use Error::*; - match self { - InputRead { path, .. } => write!(f, "Failed to read input file '{}'", path.display()), - InputDeserialize { path, .. } => write!(f, "Failed to deserialize input file '{}' as valid WIR JSON", path.display()), - InputCompile { path, .. } => write!(f, "Failed to compile input file '{}' as a checker workflow", path.display()), - } - } -} -impl error::Error for Error { - fn source(&self) -> Option<&(dyn error::Error + 'static)> { - use Error::*; - match self { - InputRead { err, .. } => Some(err), - InputDeserialize { err, .. } => Some(err), - InputCompile { err, .. } => Some(err), - } - } -} - -/***** ARGUMENTS *****/ -/// Defines the possible input languages. -#[derive(Clone, Copy, Debug, EnumDebug, Eq, Hash, PartialEq)] -enum InputLanguage { - /// BraneScript, which we turn into WIR before compiling. - BraneScript, - /// The WIR directory, - WorkflowIntermediateRepresentation, -} -impl FromStr for InputLanguage { - type Err = InputLanguageParseError; - - #[inline] - fn from_str(s: &str) -> Result { - match s { - "bs" | "bscript" | "branescript" => Ok(Self::BraneScript), - "wir" => Ok(Self::WorkflowIntermediateRepresentation), - raw => Err(InputLanguageParseError::Unknown { raw: raw.into() }), - } - } -} - -/// Defines the possible output languages. -#[derive(Clone, Copy, Debug, EnumDebug, Eq, Hash, PartialEq)] -enum OutputLanguage { - /// The workflow visualisation - Workflow, - /// JSON serialization - Json, - /// eFLINT JSON phrases - #[cfg(feature = "eflint")] - EFlintJson, - /// eFLINT phrases - #[cfg(feature = "eflint")] - EFlint, -} -impl FromStr for OutputLanguage { - type Err = OutputLanguageParseError; - - #[inline] - fn from_str(s: &str) -> Result { - match s { - "wf" | "workflow" => Ok(Self::Workflow), - "json" => Ok(Self::Json), - #[cfg(feature = "eflint")] - "eflint-json" => Ok(Self::EFlintJson), - #[cfg(feature = "eflint")] - "eflint" => Ok(Self::EFlint), - raw => Err(OutputLanguageParseError::Unknown { raw: raw.into() }), - } - } -} - -/// Defines the toplevel arguments of the binary. -#[derive(Debug, Parser)] -struct Arguments { - /// The path to the file to compile. - #[clap(name = "INPUTS", help = "The input file(s) to compile. See `--language` to switch between what kind.")] - inputs: Vec, - - /// Enables debug. - #[clap(long, global = true, help = "If given, enables INFO- and DEBUG-level log statements.")] - debug: bool, - /// Enables trace. - #[clap(long, global = true, help = "If given, enables TRACE-level log statements. Implies '--debug'.")] - trace: bool, - /// The language of the input. - #[clap( - short, - long, - default_value = "bs", - help = "The language of the input file(s). Options are: `bs`, `bscript` or `branescript` for BraneScript; and `wir` for the already \ - compiled WIR directly." - )] - input: InputLanguage, - /// Language to write to - #[clap( - short, - long, - default_value = "workflow", - help = "The language of the output. Options are: `wf` or `workflow` for the workflow visualisation; `json` for JSON; `eflint-json` for \ - eFLINT JSON; or 'eflint' for eFLINT phrases. Note that the latter two are only available when compiled with the `eflint`-feature." - )] - output: OutputLanguage, - /// Whether to plan inputs. - #[clap(long, help = "If given, plans tasks and input locations on the 'localhost' location.")] - plan: bool, - /// Whether to skip optimisation or not. - #[clap(long, alias = "no-optimize", global = true, help = "If given, does not optimise the workflow before printing.")] - no_optimise: bool, - /// The location where to read packages from. - #[clap( - short, - long, - default_value = "./tests/packages", - help = "The location where we're reading packages from to compile the test files. Ignored if input language is not BraneScript." - )] - packages_path: PathBuf, - /// The location where to read datasets from. - #[clap( - short, - long, - default_value = "./tests/data", - help = "The location where we're reading data from to compile the test files. Ignored if input language is not BraneScript." - )] - data_path: PathBuf, - /// The user to add at the bottom of the workflow. - #[clap(short, long, help = "If given, determines the name to add to the compiled workflow. Chooses a random one if omitted.")] - user: Option, -} - -/***** ENTRYPOINT *****/ -fn main() { - // Parse the arguments - let args: Arguments = Arguments::parse(); - - // Initialize the logger - if let Err(err) = HumanLogger::terminal(DebugMode::from_flags(args.trace, args.debug)).init() { - eprintln!("WARNING: Failed to setup logger: {err} (no logging for this session)"); - } - info!("{} ({}) - v{}", env!("CARGO_BIN_NAME"), env!("CARGO_PKG_NAME"), env!("CARGO_PKG_VERSION")); - - // Go thru the inputs - for input in args.inputs { - // Get the input file - debug!("Reading file '{}' as {}...", input.display(), args.input.variant()); - let raw: String = match fs::read_to_string(&input) { - Ok(raw) => raw, - Err(err) => { - error!("{}", Error::InputRead { path: input, err }.trace()); - std::process::exit(1); - }, - }; - - // If it's a BraneScript file, compile to WIR first - let wir: ast::Workflow = if args.input == InputLanguage::BraneScript { - debug!("Input BraneScript:\n\n{raw}\n"); - - // Get the package and data index - debug!("Reading package index from '{}'...", args.packages_path.display()); - let pindex: PackageIndex = create_package_index_from(&args.packages_path); - debug!("Reading data index from '{}'...", args.data_path.display()); - let dindex: DataIndex = create_data_index_from(&args.data_path); - - // Compile to WIR first - debug!("Compiling input {} to {}...", InputLanguage::BraneScript.variant(), InputLanguage::WorkflowIntermediateRepresentation.variant()); - match compile_program(raw.as_bytes(), &pindex, &dindex, &ParserOptions::bscript()) { - CompileResult::Workflow(wir, warns) => { - for warn in warns { - warn.prettyprint(input.display().to_string(), &raw); - } - wir - }, - CompileResult::Unresolved(_, _) => { - unreachable!(); - }, - CompileResult::Program(_, _) => { - unreachable!(); - }, - - CompileResult::Eof(eof) => { - eof.prettyprint(input.display().to_string(), raw); - error!("Compilation failed (see output above)"); - std::process::exit(1); - }, - CompileResult::Err(errs) => { - for err in errs { - err.prettyprint(input.display().to_string(), &raw); - } - error!("Compilation failed (see output above)"); - std::process::exit(1); - }, - } - } else { - // Simply deserialize the JSON - debug!("Input JSON:\n\n{raw}\n"); - match serde_json::from_str(&raw) { - Ok(wir) => wir, - Err(err) => { - error!("{}", Error::InputDeserialize { path: input, err }.trace()); - std::process::exit(1); - }, - } - }; - - // Plan using the dummy planner of Brane - let mut wir: ast::Workflow = if args.plan { brane_exe::dummy::DummyPlanner::plan(&mut HashMap::new(), wir) } else { wir }; - // Also assign a dummy user if not given - wir.user = Arc::new(Some(args.user.clone().unwrap_or_else(|| names::three::usualcase::rand().into()))); - - // If debug, write the representation - if log::max_level() >= Level::Debug { - let mut debug_wir: Vec = Vec::new(); - brane_ast::traversals::print::ast::do_traversal(&wir, &mut debug_wir).unwrap(); - debug!("Input WIR:\n\n{}\n", String::from_utf8_lossy(&debug_wir)); - } - - // Now compile to our own representation! - debug!("Compiling {} to CheckerWorkflow...", InputLanguage::WorkflowIntermediateRepresentation.variant()); - let mut wf: Workflow = match wir.try_into() { - Ok(wf) => wf, - Err(err) => { - error!("{}", Error::InputCompile { path: input, err }.trace()); - std::process::exit(1); - }, - }; - - // Optimise if not told not to - if !args.no_optimise { - wf.optimize(); - } - - // That's it, print it - match args.output { - OutputLanguage::Workflow => println!("{}", wf.visualize()), - OutputLanguage::Json => match serde_json::to_string_pretty(&wf) { - Ok(wf) => println!("{wf}"), - Err(err) => { - error!("{}", err.trace()); - std::process::exit(1); - }, - }, - #[cfg(feature = "eflint")] - OutputLanguage::EFlintJson => match serde_json::to_string_pretty(&wf.to_eflint()) { - Ok(phrases) => println!("{phrases}"), - Err(err) => { - error!("{}", err.trace()); - std::process::exit(1); - }, - }, - #[cfg(feature = "eflint")] - OutputLanguage::EFlint => { - for phrase in wf.to_eflint() { - print!("{:#}", ::display_syntax(&phrase)); - } - }, - } - } -} diff --git a/lib/workflow/src/compile.rs b/lib/workflow/src/compile.rs deleted file mode 100644 index c0afebf..0000000 --- a/lib/workflow/src/compile.rs +++ /dev/null @@ -1,493 +0,0 @@ -// COMPILE.rs -// by Lut99 -// -// Created: -// 27 Oct 2023, 17:39:59 -// Last edited: -// 12 Jun 2024, 17:38:33 -// Auto updated? -// Yes -// -// Description: -//! Defines conversion functions between the -//! [Checker Workflow](Workflow) and the [WIR](ast::Workflow). -// - -use std::collections::{HashMap, HashSet}; -use std::convert::TryFrom; -use std::error; -use std::fmt::{Display, Formatter, Result as FResult}; -use std::panic::catch_unwind; - -use brane_ast::spec::BuiltinFunctions; -use brane_ast::{MergeStrategy, ast}; -use brane_exe::pc::{ProgramCounter, ResolvedProgramCounter}; -use enum_debug::EnumDebug as _; -use log::{Level, debug, trace}; -use specifications::data::{AvailabilityKind, DataName, PreprocessKind}; - -use super::preprocess; -use super::spec::{Dataset, Elem, ElemBranch, ElemCommit, ElemLoop, ElemParallel, ElemTask, User, Workflow}; -use crate::{Metadata, utils}; - -/***** ERRORS *****/ -/// Defines errors that may occur when compiling an [`ast::Workflow`] to a [`Workflow`]. -#[derive(Debug)] -pub enum Error { - /// No user was given in the input workflow. - MissingUser, - /// Failed to preprocess the given workflow. - Preprocess { err: super::preprocess::Error }, - /// Function ID was out-of-bounds. - PcOutOfBounds { pc: ResolvedProgramCounter, max: usize }, - /// A parallel edge was found who's `merge` was not found. - ParallelMergeOutOfBounds { pc: ResolvedProgramCounter, merge: ResolvedProgramCounter }, - /// A parallel edge was found who's `merge` is not an [`ast::Edge::Join`]. - ParallelWithNonJoin { pc: ResolvedProgramCounter, merge: ResolvedProgramCounter, got: String }, - /// Found a join that wasn't paired with a parallel edge. - StrayJoin { pc: ResolvedProgramCounter }, - /// A call was performed to a non-builtin - IllegalCall { pc: ResolvedProgramCounter, name: String }, - /// A `commit_result()` was found that returns more than 1 result. - CommitTooMuchOutput { pc: ResolvedProgramCounter, got: usize }, - /// A `commit_result()` was found without output. - CommitNoOutput { pc: ResolvedProgramCounter }, - /// A `commit_result()` was found that outputs a result instead of a dataset. - CommitReturnsResult { pc: ResolvedProgramCounter }, -} -impl Display for Error { - fn fmt(&self, f: &mut Formatter<'_>) -> FResult { - use Error::*; - match self { - MissingUser => write!(f, "User not specified in given workflow"), - Preprocess { .. } => write!(f, "Failed to preprocess input WIR workflow"), - PcOutOfBounds { pc, max } => write!( - f, - "Program counter {} is out-of-bounds (function {} has {} edges)", - pc, - if let Some(func_name) = pc.func_name() { func_name.clone() } else { pc.func_id().to_string() }, - max - ), - ParallelMergeOutOfBounds { pc, merge } => { - write!(f, "Parallel edge at {pc}'s merge pointer {merge} is out-of-bounds") - }, - ParallelWithNonJoin { pc, merge, got } => { - write!(f, "Parallel edge at {pc}'s merge edge (at {merge}) was not an Edge::Join, but an Edge::{got}") - }, - StrayJoin { pc } => write!(f, "Found Join-edge without preceding Parallel-edge at {pc}"), - IllegalCall { pc, name } => { - write!(f, "Encountered illegal call to function '{name}' at {pc} (calls to non-task, non-builtin functions are not supported)") - }, - CommitTooMuchOutput { pc, got } => { - write!(f, "Call to `commit_result()` as {pc} returns more than 1 outputs (got {got})") - }, - CommitNoOutput { pc } => write!(f, "Call to `commit_result()` at {pc} does not return a dataset"), - CommitReturnsResult { pc } => { - write!(f, "Call to `commit_result()` at {pc} returns an IntermediateResult instead of a Data") - }, - } - } -} -impl error::Error for Error { - fn source(&self) -> Option<&(dyn error::Error + 'static)> { - use Error::*; - match self { - MissingUser => None, - Preprocess { err, .. } => Some(err), - PcOutOfBounds { .. } - | ParallelMergeOutOfBounds { .. } - | ParallelWithNonJoin { .. } - | StrayJoin { .. } - | IllegalCall { .. } - | CommitTooMuchOutput { .. } - | CommitNoOutput { .. } - | CommitReturnsResult { .. } => None, - } - } -} - -/***** HELPER FUNCTIONS *****/ -/// Analyses the given [`WIR`](ast::Workflow) graph to find the Last Known Locations (LKLs) of the datasets and results mentioned. -/// -/// # Arguments -/// - `lkls`: The map of datasets/results to Last Known Locations to populate. Maps from edge index to map of data names to possible locations it's at. -/// - `wir`: The entire workflow graph. -/// - `pc`: The [`ProgramCounter`] pointing to the current edge we're analysing. -/// - `breakpoint`: Some possible edge that, if encounters, halts the analysis and returns immediately. -fn analyse_data_lkls(lkls: &mut HashMap>, wir: &ast::Workflow, pc: ProgramCounter, breakpoint: Option) { - // Stop if we hit the breakpoint - if let Some(breakpoint) = breakpoint { - if pc == breakpoint { - return; - } - } - - // Get the edge we're talking about - let edge: &ast::Edge = match utils::get_edge(wir, pc) { - Some(edge) => edge, - None => return, - }; - - // Match the edge - trace!("Analysing data LKLs in {:?}", edge.variant()); - match edge { - ast::Edge::Linear { instrs: _, next } => { - // Note: we don't analyse data reference instantiations since it contains jack shit about the dataset referenced :/ - // Continue with the next graph - analyse_data_lkls(lkls, wir, pc.jump(*next), breakpoint) - }, - - ast::Edge::Node { task: _, locs: _, at, input, result, metadata: _, next } => { - // Mark the locations we're getting the results from - for (i, access) in input { - match access { - Some(AvailabilityKind::Available { .. }) => { - // It's available at the location of the node - if let Some(at) = at { - *lkls.entry(i.clone()).or_default() = HashSet::from([at.clone()]); - } - }, - Some(AvailabilityKind::Unavailable { how: PreprocessKind::TransferRegistryTar { location, dataname: _ } }) => { - // It's available at the planned location - *lkls.entry(i.clone()).or_default() = HashSet::from([location.clone()]); - }, - None => continue, - } - } - - // Mark where the output is, if any - if let (Some(result), Some(at)) = (result, at) { - *lkls.entry(DataName::IntermediateResult(result.clone())).or_default() = HashSet::from([at.clone()]); - } - - // Continue the analysis - analyse_data_lkls(lkls, wir, pc.jump(*next), breakpoint) - }, - - ast::Edge::Stop {} => (), - - ast::Edge::Branch { true_next, false_next, merge } => { - // Do the branches first... - analyse_data_lkls(lkls, wir, pc.jump(*true_next), merge.map(|m| pc.jump(m))); - if let Some(false_next) = false_next { - analyse_data_lkls(lkls, wir, pc.jump(*false_next), merge.map(|m| pc.jump(m))); - } - - // ...before we continue with the rest - if let Some(merge) = merge { - analyse_data_lkls(lkls, wir, pc.jump(*merge), breakpoint) - } - }, - - ast::Edge::Parallel { branches, merge } => { - // Do all the branches - for branch in branches { - analyse_data_lkls(lkls, wir, pc.jump(*branch), Some(pc.jump(*merge))); - } - - // Run the merge onwards - analyse_data_lkls(lkls, wir, pc.jump(*merge), breakpoint) - }, - - ast::Edge::Join { merge: _, next } => analyse_data_lkls(lkls, wir, pc.jump(*next), breakpoint), - - ast::Edge::Loop { cond, body, next } => { - // Build the body first - analyse_data_lkls(lkls, wir, pc.jump(*body), Some(pc.jump(*cond))); - // The condition - analyse_data_lkls(lkls, wir, pc.jump(*cond), Some(pc.jump(*body - 1))); - // And the next - if let Some(next) = next { - analyse_data_lkls(lkls, wir, pc.jump(*next), breakpoint); - } - }, - - ast::Edge::Call { input: _, result: _, next } => { - // Even for commits, we can't really do anything here (that's the whole point of this analysis, actually, to be able to), and as such continue - analyse_data_lkls(lkls, wir, pc.jump(*next), breakpoint) - }, - - ast::Edge::Return { result } => { - for res in result { - // Assume the end location - lkls.entry(res.clone()).or_default().insert("Danny Data Scientist".into()); - } - }, - } -} - -/// Reconstructs the workflow graph to [`Elem`]s instead of [`ast::Edge`]s. -/// -/// # Arguments -/// - `wir`: The [`ast::Workflow`] to analyse. -/// - `wf_id`: The identifier of the workflow we're compiling in. -/// - `calls`: The map of Call program-counter-indices to function IDs called. -/// - `lkls`: The map of program counter/dataset pairs that map to the locations where we last saw them. Mutable to update it as we make decisions for commits. -/// - `pc`: The program-counter-index of the edge to analyse. These are pairs of `(function, edge_idx)`, where main is referred to by [`usize::MAX`](usize). -/// - `plug`: The element to write when we reached the (implicit) end of a branch. -/// - `breakpoint`: An optional program-counter-index that, if given, will not analyse that edge onwards (excluding it too). -/// -/// # Returns -/// An [`Elem`] representing the given branch of the workflow. -/// -/// # Errors -/// This function errors if a definition in the Workflow was unknown. -fn reconstruct_graph( - wir: &ast::Workflow, - wf_id: &str, - calls: &HashMap, - lkls: &mut HashMap>, - pc: ProgramCounter, - plug: Elem, - breakpoint: Option, -) -> Result { - // Stop if we hit the breakpoint - if let Some(breakpoint) = breakpoint { - if pc == breakpoint { - return Ok(plug); - } - } - - // Get the edge we're talking about - let edge: &ast::Edge = match utils::get_edge(wir, pc) { - Some(edge) => edge, - None => return Ok(plug), - }; - - // Match the edge - trace!("Compiling {:?}", edge.variant()); - match edge { - ast::Edge::Linear { next, .. } => { - // Simply skip to the next, as linear connectors are no longer interesting - reconstruct_graph(wir, wf_id, calls, lkls, pc.jump(*next), plug, breakpoint) - }, - - ast::Edge::Node { task, locs: _, at, input, result, metadata, next } => { - // Resolve the task definition - let def: &ast::ComputeTaskDef = match catch_unwind(|| wir.table.task(*task)) { - Ok(def) => { - if let ast::TaskDef::Compute(c) = def { - c - } else { - unimplemented!(); - } - }, - Err(_) => panic!("Encountered unknown task '{task}' after preprocessing"), - }; - - // Return the elem - Ok(Elem::Task(ElemTask { - id: format!("{}-{}-task", wf_id, pc.resolved(&wir.table)), - name: def.function.name.clone(), - package: def.package.clone(), - version: def.version, - input: input - .iter() - .map(|(name, avail)| Dataset { - name: name.name().into(), - from: avail.as_ref().and_then(|avail| match avail { - AvailabilityKind::Available { how: _ } => None, - AvailabilityKind::Unavailable { how: PreprocessKind::TransferRegistryTar { location, dataname: _ } } => { - Some(location.clone()) - }, - }), - }) - .collect(), - output: result.as_ref().map(|name| Dataset { name: name.clone(), from: None }), - location: at.clone(), - metadata: metadata - .iter() - .map(|md| Metadata { owner: md.owner.clone(), tag: md.tag.clone(), signature: md.signature.clone() }) - .collect(), - next: Box::new(reconstruct_graph(wir, wf_id, calls, lkls, pc.jump(*next), plug, breakpoint)?), - })) - }, - - ast::Edge::Stop {} => Ok(Elem::Stop(HashSet::new())), - - ast::Edge::Branch { true_next, false_next, merge } => { - // Construct the branches first - let mut branches: Vec = - vec![reconstruct_graph(wir, wf_id, calls, lkls, pc.jump(*true_next), Elem::Next, merge.map(|merge| pc.jump(merge)))?]; - if let Some(false_next) = false_next { - branches.push(reconstruct_graph(wir, wf_id, calls, lkls, pc.jump(*false_next), Elem::Next, merge.map(|merge| pc.jump(merge)))?) - } - - // Build the next, if there is any - let next: Elem = merge - .map(|merge| reconstruct_graph(wir, wf_id, calls, lkls, pc.jump(merge), plug, breakpoint)) - .transpose()? - .unwrap_or(Elem::Stop(HashSet::new())); - - // Build the elem using those branches and next - Ok(Elem::Branch(ElemBranch { branches, next: Box::new(next) })) - }, - - ast::Edge::Parallel { branches, merge } => { - // Construct the branches first - let mut elem_branches: Vec = Vec::with_capacity(branches.len()); - for branch in branches { - elem_branches.push(reconstruct_graph(wir, wf_id, calls, lkls, pc.jump(*branch), Elem::Next, Some(pc.jump(*merge)))?); - } - - // Let us checkout that the merge point is a join - let merge_edge: &ast::Edge = match utils::get_edge(wir, pc.jump(*merge)) { - Some(edge) => edge, - None => return Err(Error::ParallelMergeOutOfBounds { pc: pc.resolved(&wir.table), merge: pc.jump(*merge).resolved(&wir.table) }), - }; - let (strategy, next): (MergeStrategy, usize) = if let ast::Edge::Join { merge, next } = merge_edge { - (*merge, *next) - } else { - return Err(Error::ParallelWithNonJoin { - pc: pc.resolved(&wir.table), - merge: pc.jump(*merge).resolved(&wir.table), - got: merge_edge.variant().to_string(), - }); - }; - - // Build the post-join point onwards - let next: Elem = reconstruct_graph(wir, wf_id, calls, lkls, pc.jump(next), plug, breakpoint)?; - - // We have enough to build ourselves - Ok(Elem::Parallel(ElemParallel { branches: elem_branches, merge: strategy, next: Box::new(next) })) - }, - - ast::Edge::Join { .. } => Err(Error::StrayJoin { pc: pc.resolved(&wir.table) }), - - ast::Edge::Loop { cond, body, next } => { - // Build the body first - let body_elems: Elem = reconstruct_graph(wir, wf_id, calls, lkls, pc.jump(*body), Elem::Next, Some(pc.jump(*cond)))?; - - // Build the condition, with immediately following the body for any open ends that we find - let cond: Elem = reconstruct_graph(wir, wf_id, calls, lkls, pc.jump(*cond), body_elems, Some(pc.jump(*body - 1)))?; - - // Build the next - let next: Elem = next - .map(|next| reconstruct_graph(wir, wf_id, calls, lkls, pc.jump(next), plug, breakpoint)) - .transpose()? - .unwrap_or(Elem::Stop(HashSet::new())); - - // We have enough to build self - Ok(Elem::Loop(ElemLoop { body: Box::new(cond), next: Box::new(next) })) - }, - - ast::Edge::Call { input, result, next } => { - // Attempt to get the call ID & matching definition - let func_def: &ast::FunctionDef = match calls.get(&pc) { - Some(id) => match wir.table.funcs.get(*id) { - Some(def) => def, - None => panic!("Encountered unknown function '{id}' after preprocessing"), - }, - None => panic!("Encountered unresolved call after preprocessing"), - }; - - // Only allow calls to builtins - if func_def.name == BuiltinFunctions::CommitResult.name() { - // Deduce the commit's location (or rather, the output location) based on the inputs - let mut locs: HashSet = HashSet::with_capacity(input.len()); - let mut new_input: Vec = Vec::with_capacity(input.len()); - for i in input { - // See if it has any known locations - let location: Option = lkls.get(i).and_then(|locs| locs.iter().next().cloned()); - - // Add it to the list of possible input locations - if let Some(location) = &location { - locs.insert(location.clone()); - } - - // Then create a new Dataset with that - new_input.push(Dataset { name: i.name().into(), from: location }); - } - - // Attempt to fetch the name of the dataset - if result.len() > 1 { - return Err(Error::CommitTooMuchOutput { pc: pc.resolved(&wir.table), got: result.len() }); - } - let data_name: String = if let Some(name) = result.iter().next() { - if let DataName::Data(name) = name { - name.clone() - } else { - return Err(Error::CommitReturnsResult { pc: pc.resolved(&wir.table) }); - } - } else { - return Err(Error::CommitNoOutput { pc: pc.resolved(&wir.table) }); - }; - - // Construct next first - let next: Elem = reconstruct_graph(wir, wf_id, calls, lkls, pc.jump(*next), plug, breakpoint)?; - - // Then we wrap the rest in a commit - Ok(Elem::Commit(ElemCommit { - id: format!("{}-{}-commit", wf_id, pc.resolved(&wir.table)), - data_name, - location: locs.into_iter().next(), - input: new_input, - next: Box::new(next), - })) - } else if func_def.name == BuiltinFunctions::Print.name() - || func_def.name == BuiltinFunctions::PrintLn.name() - || func_def.name == BuiltinFunctions::Len.name() - { - // Using them is OK, we just ignore them for the improved workflow - reconstruct_graph(wir, wf_id, calls, lkls, pc.jump(*next), plug, breakpoint) - } else { - Err(Error::IllegalCall { pc: pc.resolved(&wir.table), name: func_def.name.clone() }) - } - }, - - ast::Edge::Return { result } => Ok(Elem::Stop(result.iter().map(|data| Dataset { name: data.name().into(), from: None }).collect())), - } -} - -/***** LIBRARY *****/ -impl TryFrom for Workflow { - type Error = Error; - - #[inline] - fn try_from(value: ast::Workflow) -> Result { - let mut buf: Vec = Vec::new(); - brane_ast::traversals::print::ast::do_traversal(&value, &mut buf).unwrap(); - debug!("Compiling workflow:\n\n{}\n", String::from_utf8(buf).unwrap()); - - // First first; check if there is a user, lol - let user: String = if let Some(user) = (*value.user).clone() { - user - } else { - return Err(Error::MissingUser); - }; - - // First, analyse the calls in the workflow as much as possible (and simplify) - let wf_id: String = value.id.clone(); - let (wir, calls): (ast::Workflow, HashMap) = match preprocess::simplify(value) { - Ok(res) => res, - Err(err) => return Err(Error::Preprocess { err }), - }; - if log::max_level() >= Level::Debug { - // Write the processed graph - let mut buf: Vec = vec![]; - brane_ast::traversals::print::ast::do_traversal(&wir, &mut buf).unwrap(); - debug!("Preprocessed workflow:\n\n{}\n", String::from_utf8_lossy(&buf)); - } - - // Collect the map of data to Last Known Locations (LKL). - let mut lkls: HashMap> = HashMap::new(); - analyse_data_lkls(&mut lkls, &wir, ProgramCounter::start(), None); - - // Alright now attempt to re-build the graph in the new style - let graph: Elem = reconstruct_graph(&wir, &wf_id, &calls, &mut lkls, ProgramCounter::start(), Elem::Stop(HashSet::new()), None)?; - - // Build a new Workflow with that! - Ok(Self { - id: wf_id, - start: graph, - - user: User { name: user }, - metadata: wir - .metadata - .iter() - .map(|md| Metadata { owner: md.owner.clone(), tag: md.tag.clone(), signature: md.signature.clone() }) - .collect(), - signature: "its_signed_i_swear_mom".into(), - }) - } -} diff --git a/lib/workflow/src/eflint.rs b/lib/workflow/src/eflint.rs deleted file mode 100644 index abcc1dc..0000000 --- a/lib/workflow/src/eflint.rs +++ /dev/null @@ -1,549 +0,0 @@ -// EFLINT.rs -// by Lut99 -// -// Created: -// 08 Nov 2023, 14:44:31 -// Last edited: -// 12 Jun 2024, 17:39:40 -// Auto updated? -// Yes -// -// Description: -//! Defines a compiler of the Checker Workflow to the eFLINT JSON -//! Specification. -// - -use std::collections::{HashMap, HashSet}; - -use eflint_json::spec::{ConstructorInput, Expression, ExpressionConstructorApp, ExpressionPrimitive, Phrase, PhraseCreate}; -use enum_debug::EnumDebug as _; -use log::{trace, warn}; -use rand::Rng as _; -use rand::distr::Alphanumeric; - -use crate::spec::{Dataset, Elem, ElemBranch, ElemCommit, ElemLoop, ElemParallel, ElemTask, Metadata, User, Workflow}; - -/***** HELPER MACROS *****/ -/// Shorthand for creating an eFLINT JSON Specification true postulation. -macro_rules! create { - ($inst:expr) => { - Phrase::Create(PhraseCreate { operand: $inst }) - }; -} - -/// Shorthand for creating an eFLINT JSON Specification constructor application. -macro_rules! constr_app { - ($id:expr $(, $args:expr)* $(,)?) => { - Expression::ConstructorApp(ExpressionConstructorApp { - identifier: ($id).into(), - operands: ConstructorInput::ArraySyntax(vec![ $($args),* ]), - }) - }; -} - -/// Shorthand for creating an eFLINT JSON Specification string literal. -macro_rules! str_lit { - ($val:expr) => { - Expression::Primitive(ExpressionPrimitive::String(($val).into())) - }; -} - -/***** HELPER FUNCTIONS *****/ -/// Simple traversal that names all [`ElemLoop`]s. -/// -/// # Arguments -/// - `elem`: The graph [`Elem`]ent to analyse. -/// - `wf_id`: The identifier of the workflow to use for new loop IDs. -/// - `loops`: A map of pointers to their IDs. -fn name_loops(mut elem: &Elem, wf_id: &str, loops: &mut HashMap<*const ElemLoop, String>) { - // Note we're doing a combination of actual recursion and looping, to minimize stack usage - loop { - match elem { - Elem::Task(ElemTask { id: _, name: _, package: _, version: _, input: _, output: _, location: _, metadata: _, next }) => elem = next, - Elem::Commit(ElemCommit { id: _, data_name: _, location: _, input: _, next }) => elem = next, - - Elem::Branch(ElemBranch { branches, next }) => { - for branch in branches { - name_loops(branch, wf_id, loops); - } - elem = next; - }, - Elem::Parallel(ElemParallel { merge: _, branches, next }) => { - for branch in branches { - name_loops(branch, wf_id, loops); - } - elem = next; - }, - Elem::Loop(l) => { - let ElemLoop { body, next } = l; - - // Generate a name for this loop - loops.insert( - l as *const ElemLoop, - format!("{wf_id}-{}-loop", rand::rng().sample_iter(Alphanumeric).take(4).map(char::from).collect::()), - ); - - // Continue - name_loops(body, wf_id, loops); - elem = next; - }, - - Elem::Stop(_) => return, - Elem::Next => return, - } - } -} - -/// Analyses the given loop's body branch of the graph to find various details. -/// -/// # Arguments -/// - `elem`: The graph [`Elem`]ent to analyse. -/// - `loop_names`: A map of [`ElemLoop`]s to names we computed beforehand. -/// - `first`: The first node(s) (node, commit or loop) in the subgraph. -/// - `last`: The last node(s) (node, commit or loop) in the subgraph. -/// -/// If no nodes are within this body, [`None`] is returned instead. -fn analyse_loop_body( - mut elem: &Elem, - loop_names: &HashMap<*const ElemLoop, String>, - first: &mut Vec<(String, HashSet)>, - last: &mut HashSet, -) { - // Note we're doing a combination of actual recursion and looping, to minimize stack usage - loop { - match elem { - Elem::Task(ElemTask { id, name: _, package: _, version: _, input, output, location: _, metadata: _, next }) => { - // Add it if it's the first one we encounter - if first.is_empty() { - *first = vec![(id.clone(), input.iter().cloned().collect())]; - } - // Always add as the last one - *last = output.iter().cloned().collect(); - - // Continue with iteration - elem = next; - }, - Elem::Commit(ElemCommit { id, data_name, location, input, next }) => { - // Add it if it's the first one we encounter - if first.is_empty() { - *first = vec![(id.clone(), input.iter().cloned().collect())]; - } - // Always add as the last one - *last = HashSet::from([Dataset { name: data_name.clone(), from: location.clone() }]); - - // Continue with iteration - elem = next; - }, - - Elem::Branch(ElemBranch { branches, next }) | Elem::Parallel(ElemParallel { merge: _, branches, next }) => { - // Aggregate the inputs & outputs of the branches - let mut branch_firsts: Vec<(String, HashSet)> = Vec::new(); - let mut branch_lasts: HashSet = HashSet::new(); - for branch in branches { - let mut branch_first: Vec<(String, HashSet)> = Vec::new(); - let mut branch_last: HashSet = HashSet::new(); - analyse_loop_body(branch, loop_names, &mut branch_first, &mut branch_last); - branch_firsts.extend(branch_first); - branch_lasts.extend(branch_last); - } - - // Add them to this branch' result - if first.is_empty() { - *first = branch_firsts; - } - *last = branch_lasts; - - // Continue with iteration - elem = next; - }, - Elem::Loop(l) => { - let ElemLoop { body, next } = l; - - // We recurse to find the inputs- and outputs - let mut body_first: Vec<(String, HashSet)> = vec![]; - let mut body_last: HashSet = HashSet::new(); - analyse_loop_body(body, loop_names, &mut body_first, &mut body_last); - - // Propagate these - if first.is_empty() { - // Get the loop's name - let id: &String = - loop_names.get(&(l as *const ElemLoop)).unwrap_or_else(|| panic!("Encountered loop without name after loop naming")); - - // Set this loop as the first node, combining all the input dataset from the children - *first = vec![(id.clone(), body_first.into_iter().flat_map(|(_, data)| data).collect::>())] - } - *last = body_last; - - // Continue with iteration - elem = next; - }, - - Elem::Stop(_) => return, - Elem::Next => return, - } - } -} - -/// Compiles a given piece of metadata. -/// -/// # Arguments -/// - `metadata`: The [`Metadata`] to compile. -/// - `phrases`: The buffer to compile to. -fn compile_metadata(metadata: &Metadata, phrases: &mut Vec) { - // First, we push the tag - // ```eflint - // +tag(user(#metadata.owner), #metadata.tag). - // ``` - let tag: Expression = constr_app!("tag", constr_app!("user", str_lit!(metadata.owner.clone())), str_lit!(metadata.tag.clone())); - phrases.push(create!(tag.clone())); - - // Push the signature - let signature: Expression = if let Some((assigner, signature)) = &metadata.signature { - // ```eflint - // +signature(user(#assigner), #signature). - // ``` - constr_app!("signature", constr_app!("user", str_lit!(assigner.clone())), str_lit!(signature.clone())) - } else { - // Push an empty signature, to be sure that the one is in serialized metadata is still findable - // ```eflint - // +signature(user(""), ""). - // ``` - constr_app!("signature", constr_app!("user", str_lit!("")), str_lit!("")) - }; - phrases.push(create!(signature.clone())); - - // Then push the metadata as a whole - phrases.push(create!(constr_app!("metadata", tag, signature))); -} - -/// Compiles the given [`Elem`] onwards to a series of eFLINT [`Phrase`]s. -/// -/// # Arguments -/// - `elem`: The current [`Elem`] we're compiling. -/// - `wf_id`: The identifier/name of the workflow we're working with. -/// - `wf_user`: The identifier/name of the user who will see the workflow result. -/// - `loop_names`: A map of [`ElemLoop`]s to names we computed beforehand. -/// - `phrases`: The list of eFLINT [`Phrase`]s we're compiling to. -fn compile_eflint(mut elem: &Elem, wf_id: &str, wf_user: &User, loop_names: &HashMap<*const ElemLoop, String>, phrases: &mut Vec) { - // Note we're doing a combination of actual recursion and looping, to minimize stack usage - loop { - trace!("Compiling {:?} to eFLINT", elem.variant()); - match elem { - Elem::Task(ElemTask { id, name, package, version, input, output, location, metadata, next }) => { - // Define a new task call and make it part of the workflow - // ```eflint - // +node(workflow(#wf_id), #id). - // +task(node(workflow(#wf_id), #id)). - // ``` - let node: Expression = constr_app!("node", constr_app!("workflow", str_lit!(wf_id)), str_lit!(id.clone())); - phrases.push(create!(node.clone())); - phrases.push(create!(constr_app!("task", node.clone()))); - - // Link the code input - // ```eflint - // +node-input(#node, asset("#package-#version")). - // +function(node-input(#node, asset("#package-#version")), #name). - // ``` - let code_input: Expression = - constr_app!("node-input", node.clone(), constr_app!("asset", str_lit!(format!("{}-{}", package, version)))); - phrases.push(create!(code_input.clone())); - phrases.push(create!(constr_app!("function", code_input.clone(), str_lit!(name.clone())))); - - // Add its inputs - for i in input { - // Link this input to the task - // ```eflint - // +node-input(#node, asset(#i.name)). - // ``` - let node_input: Expression = constr_app!("node-input", node.clone(), constr_app!("asset", str_lit!(i.name.clone()))); - phrases.push(create!(node_input.clone())); - - // Add where this dataset lives if we know that - if let Some(from) = &i.from { - // It's planned to be transferred from this location - // ```eflint - // +node-input-from(#node-input, domain(user(#from))). - // ``` - phrases.push(create!(constr_app!( - "node-input-from", - node_input, - constr_app!("domain", constr_app!("user", str_lit!(from.clone()))) - ))); - } else if let Some(at) = location { - // It's present on the task's location - // ```eflint - // +node-input-from(#node-input, domain(user(#at))). - // ``` - phrases.push(create!(constr_app!( - "node-input-from", - node_input, - constr_app!("domain", constr_app!("user", str_lit!(at.clone()))) - ))); - } else { - warn!("Encountered input dataset '{}' without transfer source in task '{}' as part of workflow '{}'", i.name, id, wf_id); - } - } - // Add the output, if any - if let Some(o) = &output { - // ```eflint - // +node-output(#node, asset(#o.name)). - // ``` - phrases.push(create!(constr_app!("node-output", node.clone(), constr_app!("asset", str_lit!(o.name.clone()))))); - } - // Add the location of the task execution - if let Some(at) = location { - // ```eflint - // +node-at(#node, domain(user(#at))). - // ``` - phrases.push(create!(constr_app!("node-at", node.clone(), constr_app!("domain", constr_app!("user", str_lit!(at.clone())))))); - } else { - warn!("Encountered unplanned task '{id}' part of workflow '{wf_id}'"); - } - - // Finally, add any task metadata - for m in metadata { - // Write the metadata's children - compile_metadata(m, phrases); - - // Resolve the metadata's signature - let (assigner, signature): (&str, &str) = - m.signature.as_ref().map(|(assigner, signature)| (assigner.as_str(), signature.as_str())).unwrap_or(("", "")); - - // Write the phrase - // ```eflint - // +node-metadata(#node, metadata(tag(user(#m.owner), #m.tag), signature(user(#m.assigner), #m.signature)))). - // ``` - phrases.push(create!(constr_app!( - "node-metadata", - node.clone(), - constr_app!( - "metadata", - constr_app!("tag", constr_app!("user", str_lit!(m.owner.clone())), str_lit!(m.tag.clone())), - constr_app!("signature", constr_app!("user", str_lit!(assigner)), str_lit!(signature)), - ) - ))); - } - - // OK, move to the next - elem = next; - }, - Elem::Commit(ElemCommit { id, data_name, location, input, next }) => { - // Add the commit task - // ```eflint - // +node(workflow(#wf_id), #id). - // +commit(node(workflow(#wf_id), #id)). - // ``` - let node: Expression = constr_app!("node", constr_app!("workflow", str_lit!(wf_id)), str_lit!(id.clone())); - phrases.push(create!(node.clone())); - phrases.push(create!(constr_app!("commit", node.clone()))); - - // Add the commits it (possibly!) does - for i in input { - // ```eflint - // +node-input(#node, asset(#i.name)). - // ``` - let node_input: Expression = constr_app!("node-input", node.clone(), constr_app!("asset", str_lit!(i.name.clone()))); - phrases.push(create!(node_input.clone())); - - // Add where this dataset lives if we know that - if let Some(from) = &i.from { - // It's planned to be transferred from this location - // ```eflint - // +node-input-from(#node-input, domain(user(#from))). - // ``` - phrases.push(create!(constr_app!( - "node-input-from", - node_input, - constr_app!("domain", constr_app!("user", str_lit!(from.clone()))) - ))); - } else { - warn!("Encountered input dataset '{}' without transfer source in commit '{}' as part of workflow '{}'", i.name, id, wf_id); - } - } - // Add the output of the node - // ```eflint - // +node-output(#node, asset(#data_name)). - // +workflow-result(workflow(#wf_id), asset(#data_name)). - // ``` - phrases.push(create!(constr_app!("node-output", node.clone(), constr_app!("asset", str_lit!(data_name.clone()))))); - phrases.push(create!(constr_app!( - "workflow-result", - constr_app!("workflow", str_lit!(wf_id)), - constr_app!("asset", str_lit!(data_name.clone())) - ))); - - // Add the location of this commit - if let Some(location) = location { - // ```eflint - // +node-at(#node, domain(user(#at))). - // ``` - phrases.push(create!(constr_app!("node-at", node, constr_app!("domain", constr_app!("user", str_lit!(location.clone())))))); - } - - // Continue with the next - elem = next; - }, - - Elem::Branch(ElemBranch { branches, next }) => { - // Do the branches in sequence - for branch in branches { - compile_eflint(branch, wf_id, wf_user, loop_names, phrases); - } - // Continue with the next one - elem = next; - }, - Elem::Parallel(ElemParallel { branches, merge: _, next }) => { - // Do the branches in sequence - for branch in branches { - compile_eflint(branch, wf_id, wf_user, loop_names, phrases); - } - // Continue with the next one - elem = next; - }, - Elem::Loop(ElemLoop { body, next }) => { - // Serialize the body phrases first - compile_eflint(body, wf_id, wf_user, loop_names, phrases); - - // Serialize the node - // ```eflint - // +node(workflow(#wf_id), #id). - // +commit(node(workflow(#wf_id), #id)). - // ``` - let id: String = format!("{}-{}-loop", wf_id, rand::rng().sample_iter(Alphanumeric).take(4).map(char::from).collect::()); - let node: Expression = constr_app!("node", constr_app!("workflow", str_lit!(wf_id)), str_lit!(id.clone())); - phrases.push(create!(node.clone())); - phrases.push(create!(constr_app!("loop", node.clone()))); - - // Collect the inputs & outputs of the body - let mut first: Vec<(String, HashSet)> = Vec::new(); - let mut last: HashSet = HashSet::new(); - analyse_loop_body(body, loop_names, &mut first, &mut last); - - // Post-process the input into a list of body nodes and a list of data input - let (bodies, inputs): (Vec, Vec>) = first.into_iter().unzip(); - let inputs: HashSet = inputs.into_iter().flatten().collect(); - - // Add the loop inputs - for input in inputs { - // ```eflint - // +node-input(#node, asset(#i.name)). - // ``` - let node_input: Expression = constr_app!("node-input", node.clone(), constr_app!("asset", str_lit!(input.name.clone()))); - phrases.push(create!(node_input.clone())); - - // Add where this dataset lives if we know that - if let Some(from) = &input.from { - // It's planned to be transferred from this location - // ```eflint - // +node-input-from(#node-input, domain(user(#from))). - // ``` - phrases.push(create!(constr_app!( - "node-input-from", - node_input, - constr_app!("domain", constr_app!("user", str_lit!(from.clone()))) - ))); - } else { - warn!( - "Encountered input dataset '{}' without transfer source in commit '{}' as part of workflow '{}'", - input.name, id, wf_id - ); - } - } - // Add the loop outputs - for output in last { - // ```eflint - // +node-output(#node, asset(#output.name)). - // ``` - phrases.push(create!(constr_app!("node-output", node.clone(), constr_app!("asset", str_lit!(output.name.clone()))))); - } - // Add the loop's bodies - for body in bodies { - // ```eflint - // +loop-body(loop(#node), node(workflow(#wf_id), #body)). - // ``` - phrases.push(create!(constr_app!( - "loop-body", - constr_app!("loop", node.clone()), - constr_app!("node", constr_app!("workflow", str_lit!(wf_id)), str_lit!(body)) - ))); - } - - // Done, continue with the next one - elem = next; - }, - - Elem::Next => return, - Elem::Stop(results) => { - // Mark the results as results of the workflow - for r in results { - // ```eflint - // +workflow-result-recipient(workflow-result(workflow(#wf_id), asset(#r.name)), user(#wf_user.name)). - // ``` - phrases.push(create!(constr_app!( - "workflow-result-recipient", - constr_app!("workflow-result", constr_app!("workflow", str_lit!(wf_id)), constr_app!("asset", str_lit!(r.name.clone()))), - constr_app!("user", str_lit!(wf_user.name.clone())), - ))); - } - - // Done - return; - }, - } - } -} - -/***** LIBRARY *****/ -impl Workflow { - /// Compiles the Workflow to a series of eFLINT phrases. - /// - /// Note that this only creates references to datasets, functions and users; any definition of them needs to be added separately. - /// - /// # Returns - /// A series of eFLINT statements that represent this Workflow. - pub fn to_eflint(&self) -> Vec { - let mut phrases: Vec = vec![]; - - // First, we shall name all loops - let mut loop_names: HashMap<*const ElemLoop, String> = HashMap::new(); - name_loops(&self.start, &self.id, &mut loop_names); - - // Kick off the first phrase(s) by adding the notion of the workflow as a whole - // ```eflint - // +workflow(#self.id). - // ``` - let workflow: Expression = constr_app!("workflow", str_lit!(self.id.clone())); - phrases.push(create!(workflow.clone())); - - // Add workflow metadata - for m in &self.metadata { - // Write the metadata's children - compile_metadata(m, &mut phrases); - - // Resolve the metadata's signature - let (assigner, signature): (&str, &str) = - m.signature.as_ref().map(|(assigner, signature)| (assigner.as_str(), signature.as_str())).unwrap_or(("", "")); - - // Write the phrase - // ```eflint - // +workflow-metadata(#workflow, metadata(tag(user(#m.owner), #m.tag), signature(user(#m.assigner), #m.signature)))). - // ``` - phrases.push(create!(constr_app!( - "workflow-metadata", - workflow.clone(), - constr_app!( - "metadata", - constr_app!("tag", constr_app!("user", str_lit!(m.owner.clone())), str_lit!(m.tag.clone())), - constr_app!("signature", constr_app!("user", str_lit!(assigner)), str_lit!(signature)), - ) - ))); - } - - // Compile the 'flow to a list of phrases - compile_eflint(&self.start, &self.id, &self.user, &loop_names, &mut phrases); - - // Done! - phrases - } -} diff --git a/lib/workflow/src/lib.rs b/lib/workflow/src/lib.rs index 1df5395..8d36609 100644 --- a/lib/workflow/src/lib.rs +++ b/lib/workflow/src/lib.rs @@ -2,9 +2,9 @@ // by Lut99 // // Created: -// 27 Oct 2023, 15:54:17 +// 08 Oct 2024, 16:16:26 // Last edited: -// 16 Nov 2023, 17:24:56 +// 08 Oct 2024, 19:19:27 // Auto updated? // Yes // @@ -12,17 +12,170 @@ //! Defines the workflow representation used internally by the checker. // -// Declare the subsubmodules -pub mod compile; -#[cfg(feature = "eflint")] -pub mod eflint; -pub mod optimize; -pub mod preprocess; -pub mod spec; -#[cfg(test)] -pub mod tests; -pub mod utils; +// Declare modules +mod optimize; +pub mod visitor; +#[cfg(feature = "visualize")] pub mod visualize; -// Bring some of it into the main namespace -pub use spec::*; +// Imports +use enum_debug::EnumDebug; +#[cfg(feature = "serde")] +use serde::{Deserialize, Serialize}; + + +/***** AUXILLARY DATA *****/ +/// Defines a representation of a dataset. +#[derive(Clone, Debug)] +#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))] +pub struct Dataset { + /// Some identifier of the dataset. + pub id: String, +} + +/// Represents a user/site that can compute, store data, do neither or do both. +#[derive(Clone, Debug)] +#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))] +pub struct Entity { + /// Some identifier of this domain. + pub id: String, +} + +/// Represents a "tag" and everything we need to know. +#[derive(Clone, Debug)] +#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))] +pub struct Metadata { + /// The arbitrary data embedded as metadata. + pub tag: String, + /// The signature verifying this metadata. If present, it's given as a pair of the person signing it and their signature. + pub signature: Option<(Entity, String)>, +} + + + + + +/***** LIBRARY *****/ +/// Defines the workflow's toplevel view. +#[derive(Clone, Debug)] +#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))] +pub struct Workflow { + /// The identifier of this workflow as a whole. + pub id: String, + /// Defines the first node in the workflow. + pub start: Elem, + + /// The user instigating this workflow (and thus getting the result, if any). + pub user: Entity, + /// The metadata associated with this workflow as a whole. + pub metadata: Vec, + /// The signature verifying this workflow. If present, it's given as a pair of the person signing it and their signature. + pub signature: Option<(Entity, String)>, +} + +/// Defines an element in the graph. This is either a _Node_, which defines a task execution, or an _Edge_, which defines how next tasks may be reached. +#[derive(Clone, Debug, EnumDebug)] +#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))] +pub enum Elem { + // Nodes + /// Defines a task that is executed, accessing and potentially producing data. + Call(ElemCall), + + // Edges + /// Defines an edge that connects to multiple next graph-branches of which only _one_ must be taken. Note that, because we don't include dynamic control flow information, we don't know _which_ will be taken. + Branch(ElemBranch), + /// Defines an edge that connects to multiple next graph-branches of which _all_ must be taken _concurrently_. + Parallel(ElemParallel), + /// Defines an edge that repeats a particular branch an unknown amount of times. + Loop(ElemLoop), + + // Terminators + /// Defines that the next element to execute is given by the parent `next`-field. + /// + /// This occurs at the end of a [`Elem::Branch`]'s branch, for example. + Next, + /// Defines that no more execution takes place. + Stop, +} +impl From for Elem { + #[inline] + fn from(value: ElemCall) -> Self { Self::Call(value) } +} +impl From for Elem { + #[inline] + fn from(value: ElemBranch) -> Self { Self::Branch(value) } +} +impl From for Elem { + #[inline] + fn from(value: ElemParallel) -> Self { Self::Parallel(value) } +} +impl From for Elem { + #[inline] + fn from(value: ElemLoop) -> Self { Self::Loop(value) } +} + + + +/// Defines a task node in the graph consisting of [`Elem`]s, which defines data access. +/// +/// Yeah so basically represents a task execution, with all checker-relevant information. +#[derive(Clone, Debug)] +#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))] +pub struct ElemCall { + /// Some identifier for this call specifically. + pub id: String, + /// Some identifier for the task that is executed in this call. + pub task: String, + /// Any input datasets used by the task. + /// + /// Note that this denotes a set of **possible** input sets. Zero or more of these may actually be used at runtime. + pub input: Vec, + /// If there are outputs produced by this task, this names it. + pub output: Vec, + + /// The location where the task is planned to be executed, if any. + pub at: Option, + + /// The list of metadata belonging to this task. Note: may need to be populated by the checker! + pub metadata: Vec, + /// The next graph element that this task connects to. + pub next: Box, +} + + + +/// Defines a branching connection between graph [`Elem`]ents. +/// +/// Or rather, defines a linear connection between two nodes, with a set of branches in between them. +#[derive(Clone, Debug)] +#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))] +pub struct ElemBranch { + /// The branches of which one _must_ be taken, but we don't know which one. + pub branches: Vec, + /// The next graph element after all the branches. + pub next: Box, +} + +/// Defines a parallel connection between graph [`Elem`]ents. +/// +/// Is like a [branch](ElemBranch), except that _all_ branches are taken _concurrently_ instead of only one. +#[derive(Clone, Debug)] +#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))] +pub struct ElemParallel { + /// The branches, _all_ of which but be taken _concurrently_. + pub branches: Vec, + /// The next graph element after all the branches. + pub next: Box, +} + +/// Defines a looping connection between graph [`Elem`]ents. +/// +/// Simply defines a branch that is taken repeatedly. Any condition that was there is embedded in the branching part, since that's how the branch is dynamically taken and we can't know how often any of them is taken anyway. +#[derive(Clone, Debug)] +#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))] +pub struct ElemLoop { + /// The body (and embedded condition) of the loop. + pub body: Box, + /// The next graph element that this parallel edge connects to. + pub next: Box, +} diff --git a/lib/workflow/src/optimize.rs b/lib/workflow/src/optimize.rs index d870d09..1434544 100644 --- a/lib/workflow/src/optimize.rs +++ b/lib/workflow/src/optimize.rs @@ -2,143 +2,283 @@ // by Lut99 // // Created: -// 31 Oct 2023, 15:44:51 +// 08 Oct 2024, 17:34:14 // Last edited: -// 02 Nov 2023, 14:43:47 +// 05 May 2025, 12:01:56 // Auto updated? // Yes // // Description: -//! Optimizes a [`Workflow`] by aggregating elements that we can -//! aggregate. +//! Optimizes a [`Workflow`] in various ways. // -use std::collections::HashSet; - -use log::debug; -use transform::Transform as _; - -use super::spec::{Elem, ElemBranch, Workflow}; - -/***** HELPER FUNCTIONS *****/ -/// Attempts to optimize the given branch of [`Elem`]s. -/// -/// # Arguments -/// - `elem`: The [`Elem`] to optimize. -/// -/// # Returns -/// Whether or not an optimization occurred. This can be used to saturate them while possible. -fn optimize_elem(elem: Elem) -> (bool, Elem) { - // Match on the element - match elem { - Elem::Task(task) => (false, Elem::Task(task)), - - Elem::Branch(mut branch) => { - // Recurse into all branches with transpose to be able to remove or merge them - let mut changed: bool = false; - branch.branches = branch - .branches - .drain(..) - .transform(|mut b| { - // Optimize the branch first - let (b_changed, new_b) = optimize_elem(b); - changed |= b_changed; - b = new_b; - - // Now see if we can do any branch-specific optimizations - match b { - // If the only thing in the branch is a next, then don't consider it anymore - Elem::Next => { - debug!("Applied optimization: empty branch pruning"); - changed = true; - vec![] - }, - - // If the branch only exists of branches, then we can collapse them into ourselves - Elem::Branch(nested_branch) => { - // Assert it only exists of branches - let mut next_branch: &ElemBranch = &nested_branch; - let mut only_branches: bool = true; - loop { - match &*next_branch.next { - Elem::Branch(nb) => { - next_branch = nb; - }, - Elem::Next => { - break; - }, - _ => { - only_branches = false; - break; - }, - } - } - - // If so, iterate again, aggregating all branches - if only_branches { - let mut branches: Vec = nested_branch.branches; - let mut next_branch: Box = nested_branch.next; - while let Elem::Branch(nb) = *next_branch { - branches.extend(nb.branches); - next_branch = nb.next; - } - debug!("Applied optimization: branch aggregation"); - changed = true; - branches - } else { - vec![Elem::Branch(nested_branch)] - } - }, - - // Nothing to do for the rest - b => vec![b], - } - }) - .collect(); - - // If the branches are empty, then replace the next with the branch - if branch.branches.is_empty() { - let (_, next) = optimize_elem(*branch.next); - (true, next) +use std::convert::Infallible; + +use crate::visitor::VisitorOwned; +use crate::{Elem, ElemBranch, Workflow}; + + +/***** TESTS *****/ +#[cfg(test)] +mod tests { + use super::*; + use crate::{ElemCall, Entity}; + + + /// Generates a workflow with minimal info + #[inline] + fn gen_wf(id: impl Into, start: impl Into) -> Workflow { + Workflow { id: id.into(), start: start.into(), user: Entity { id: "amy".into() }, metadata: vec![], signature: None } + } + + /// Generates a branch. + #[inline] + fn gen_branch(branches: impl IntoIterator, next: Elem) -> Elem { + Elem::Branch(ElemBranch { branches: branches.into_iter().collect(), next: Box::new(next) }) + } + + /// Generates a call to a specific package, nothing else. + #[inline] + fn gen_void_call(id: impl Into, task: impl Into, next: Elem) -> Elem { + Elem::Call(ElemCall { id: id.into(), task: task.into(), input: vec![], output: vec![], at: None, metadata: vec![], next: Box::new(next) }) + } + + + /// Checks if two workflows have the same structure. + #[inline] + fn compares(left: &Workflow, right: &Workflow) -> bool { + fn cmp_edge(left: &Elem, right: &Elem) -> bool { + match (left, right) { + (Elem::Call(l), Elem::Call(r)) => l.task == r.task && cmp_edge(&l.next, &r.next), + + (Elem::Branch(l), Elem::Branch(r)) => { + l.branches.len() == r.branches.len() + && l.branches.iter().zip(r.branches.iter()).all(|(l, r)| cmp_edge(l, r)) + && cmp_edge(&l.next, &r.next) + }, + (Elem::Parallel(l), Elem::Parallel(r)) => { + l.branches.len() == r.branches.len() + && l.branches.iter().zip(r.branches.iter()).all(|(l, r)| cmp_edge(l, r)) + && cmp_edge(&l.next, &r.next) + }, + (Elem::Loop(l), Elem::Loop(r)) => cmp_edge(&l.body, &r.body) && cmp_edge(&l.next, &r.next), + + (Elem::Next, Elem::Next) => true, + (Elem::Stop, Elem::Stop) => true, + + _ => false, + } + } + + cmp_edge(&left.start, &right.start) + } + + + + /// Tests whether branches are flattened. + #[test] + fn test_branch_flattener() { + // Case 1 + let mut pred: Workflow = gen_wf( + "Prediction", + gen_branch( + [ + gen_branch([gen_void_call("foo", "Foo", Elem::Next)], Elem::Next), + gen_branch([gen_void_call("bar", "Bar", Elem::Next), gen_void_call("baz", "Baz", Elem::Next)], Elem::Next), + ], + Elem::Stop, + ), + ); + pred.optimize(); + assert!(compares( + &pred, + &gen_wf( + "Truth", + gen_branch( + [gen_void_call("foo", "Foo", Elem::Next), gen_void_call("bar", "Bar", Elem::Next), gen_void_call("baz", "Baz", Elem::Next)], + Elem::Stop + ) + ) + )); + } + + /// Tests whether dead branches are pruned. + #[test] + fn test_dead_branch_pruner() { + // Case 1 + let mut pred: Workflow = gen_wf("Prediction", gen_branch([gen_void_call("foo", "Foo", Elem::Next), Elem::Next], Elem::Stop)); + pred.optimize(); + assert!(compares(&pred, &gen_wf("Truth", gen_branch([gen_void_call("foo", "Foo", Elem::Next),], Elem::Stop)))); + } + + /// Tests whether empty branches are removed. + #[test] + fn test_empty_branch_remover() { + // Case 1 + let mut pred: Workflow = gen_wf("Prediction", gen_branch([], Elem::Stop)); + pred.optimize(); + assert!(compares(&pred, &gen_wf("Truth", Elem::Stop))); + } +} + + + + + +/***** HELPERS *****/ +// Interface +/// Defines what an optimizer looks like in the abstract. +trait Optimizer: VisitorOwned { + /// Applies the optimizer to the given workflow. + /// + /// # Returns + /// Whether anything has been optimized. + fn optimize(wf: &mut Workflow) -> bool + where + Self: Default + VisitorOwned, + { + let mut opt: Self = Self::default(); + wf.visit_owned(&mut opt).unwrap(); + opt.has_optimized() + } + + /// Returns whether this optimizer has done anything. + fn has_optimized(&self) -> bool; +} + + + +// Branches +/// Optimizes the workflow graph by collapsing a branch that only has branches as elements. +struct BranchFlattener { + /// Keeps track of whether this optimizer has done anything. + /// + /// Used to saturate the process. + optimized: bool, +} +impl Default for BranchFlattener { + #[inline] + fn default() -> Self { Self { optimized: false } } +} +impl Optimizer for BranchFlattener { + #[inline] + fn has_optimized(&self) -> bool { self.optimized } +} +impl VisitorOwned for BranchFlattener { + type Error = Infallible; + + fn visit_branch(&mut self, mut elem: ElemBranch) -> Result { + // Investigate whether all nested branches are, themselves, branches that terminate where + // this branch terminates. + for b in &elem.branches { + // First, verify it's a branch + if let Elem::Branch(b) = b { + // Then verify nothing follows it + if !matches!(*b.next, Elem::Next) { + return Ok(Elem::Branch(elem)); + } } else { - // Now optimize the res of the program as normal - let (next_changed, next) = optimize_elem(*branch.next); - branch.next = Box::new(next); - (changed | next_changed, Elem::Branch(branch)) + return Ok(Elem::Branch(elem)); } - }, - Elem::Parallel(parallel) => (false, Elem::Parallel(parallel)), - Elem::Loop(l) => (false, Elem::Loop(l)), - Elem::Commit(commit) => (false, Elem::Commit(commit)), + } + + // OK, now we collapse all branches into this one + elem.branches = elem.branches.drain(..).flat_map(|b| if let Elem::Branch(b) = b { b.branches.into_iter() } else { unreachable!() }).collect(); + self.optimized = true; + + // Now continue by calling the newly set branches + for b in &mut elem.branches { + self.visit_mut(b)?; + } + self.visit_mut(&mut elem.next)?; - Elem::Next => (false, Elem::Next), - Elem::Stop(returns) => (false, Elem::Stop(returns)), + // OK + Ok(Elem::Branch(elem)) } } -/***** LIBRARY *****/ -impl Workflow { - /// Optimizes the workflow graph by pruning elements which do task-independent things (like branching without tasks) and aggregates aggregatable edges. - pub fn optimize(&mut self) { - let Self { start, .. } = self; +/// Optimizes the workflow graph by pruning empty branches. +struct DeadBranchPruner { + /// Keeps track of whether this optimizer has done anything. + /// + /// Used to saturate the process. + optimized: bool, +} +impl Default for DeadBranchPruner { + #[inline] + fn default() -> Self { Self { optimized: false } } +} +impl Optimizer for DeadBranchPruner { + #[inline] + fn has_optimized(&self) -> bool { self.optimized } +} +impl VisitorOwned for DeadBranchPruner { + type Error = Infallible; + + fn visit_branch(&mut self, mut elem: ElemBranch) -> Result { + // Investigate which branches to keep (we discard any that are immediately terminating) + let old_len: usize = elem.branches.len(); + elem.branches = elem.branches.drain(..).filter(|e| !matches!(e, Elem::Next)).collect(); + self.optimized |= elem.branches.len() != old_len; - // Get the start out of self - let mut graph: Elem = Elem::Stop(HashSet::new()); - std::mem::swap(&mut graph, start); + // Now continue by calling all the remaining nested branches and then the next node + for b in &mut elem.branches { + self.visit_mut(b)?; + } + self.visit_mut(&mut elem.next)?; - // Decide which functions can be discarded - /* TODO */ + // OK + Ok(Elem::Branch(elem)) + } +} - // Analyze the individual edges first - loop { - let (changed, new_graph) = optimize_elem(graph); - graph = new_graph; - if !changed { - break; +/// Optimizes the workflow graph by pruning empty [`Elem::Branch`]es. +struct EmptyBranchRemover { + /// Keeps track of whether this optimizer has done anything. + /// + /// Used to saturate the process. + optimized: bool, +} +impl Default for EmptyBranchRemover { + #[inline] + fn default() -> Self { Self { optimized: false } } +} +impl Optimizer for EmptyBranchRemover { + #[inline] + fn has_optimized(&self) -> bool { self.optimized } +} +impl VisitorOwned for EmptyBranchRemover { + type Error = Infallible; + + fn visit_branch(&mut self, mut elem: ElemBranch) -> Result { + // If there are no more branches here, then replace with next + if elem.branches.is_empty() { + self.optimized = true; + self.visit_mut(&mut elem.next)?; + Ok(*elem.next) + } else { + for b in &mut elem.branches { + self.visit_mut(b)?; } + self.visit_mut(&mut elem.next)?; + Ok(Elem::Branch(elem)) } + } +} - // Restore the graph - std::mem::swap(start, &mut graph); + + + + +/***** LIBRARY *****/ +impl Workflow { + /// Optimizes the workflow graph by pruning elements which do task-independent things (like branching without tasks) and aggregates aggregatable edges. + pub fn optimize(&mut self) { + // Optimize while anything's being optimized + let mut saturated: bool = false; + while !saturated { + // Apply the optimizations + saturated = !(BranchFlattener::optimize(self) | DeadBranchPruner::optimize(self) | EmptyBranchRemover::optimize(self)); + } } } diff --git a/lib/workflow/src/preprocess.rs b/lib/workflow/src/preprocess.rs deleted file mode 100644 index 608c4d3..0000000 --- a/lib/workflow/src/preprocess.rs +++ /dev/null @@ -1,1077 +0,0 @@ -// PREPROCESS.rs -// by Lut99 -// -// Created: -// 02 Nov 2023, 14:52:26 -// Last edited: -// 12 Jun 2024, 17:41:53 -// Auto updated? -// Yes -// -// Description: -//! Defines a preprocessing step on a [WIR](Workflow) that simplifies it -//! to increase the support of the simpler checker workflow. -// - -use std::collections::{HashMap, HashSet}; -use std::error; -use std::fmt::{Display, Formatter, Result as FResult}; -use std::panic::catch_unwind; -use std::sync::Arc; - -use brane_ast::MergeStrategy; -use brane_ast::ast::{Edge, EdgeInstr, FunctionDef, SymTable, TaskDef, Workflow}; -use brane_ast::func_id::FunctionId; -use brane_ast::spec::BuiltinFunctions; -use brane_exe::pc::{ProgramCounter, ResolvedProgramCounter}; -use enum_debug::EnumDebug as _; -use log::{debug, trace}; - -use super::utils; - -/***** TESTS *****/ -#[cfg(test)] -mod tests { - use std::ffi::OsStr; - use std::path::PathBuf; - - use brane_ast::traversals::print::ast; - use brane_ast::{CompileResult, ParserOptions, compile_program}; - use brane_shr::utilities::{create_data_index_from, create_package_index_from, test_on_dsl_files_in}; - use humanlog::{DebugMode, HumanLogger}; - use specifications::data::DataIndex; - use specifications::package::PackageIndex; - - use super::*; - - /// Runs checks to verify the workflow inlining analysis - #[test] - fn test_checker_workflow_inline_analysis() { - // Setup logger if told - if std::env::var("TEST_LOGGER").map(|value| value == "1" || value == "true").unwrap_or(false) { - if let Err(err) = HumanLogger::terminal(DebugMode::Full).init() { - eprintln!("WARNING: Failed to setup test logger: {err} (no logging for this session)"); - } - } - - // Defines a few test files with expected inlinable functions - let tests: [(&str, &str, HashMap>>); 5] = [ - ("case1", r#"println("Hello, world!");"#, HashMap::from([(1, None)])), - ( - "case2", - r#"func hello_world() { return "Hello, world!"; } println(hello_world());"#, - HashMap::from([(1, None), (4, Some(HashSet::new()))]), - ), - ( - "case3", - r#"func foo() { return "Foo"; } func foobar() { return foo() + "Bar"; } println(foobar());"#, - HashMap::from([(1, None), (4, Some(HashSet::new())), (5, Some(HashSet::from([4])))]), - ), - ("case4", r#"import hello_world; println(hello_world());"#, HashMap::from([(1, None)])), - ( - "case5", - r#"func hello_world(n) { if (n <= 0) { return "Hello, world!"; } else { return "Hello, " + hello_world(n - 1) + "\n"; } } println(hello_world(3));"#, - HashMap::from([(1, None), (4, None)]), - ), - ]; - - // Load example package- and data indices - let tests_path: PathBuf = PathBuf::from(super::super::tests::TESTS_DIR); - let pindex: PackageIndex = create_package_index_from(tests_path.join("packages")); - let dindex: DataIndex = create_data_index_from(tests_path.join("data")); - - // Test them each - for (id, test, gold) in tests.into_iter() { - // Compile to BraneScript (we'll assume this works) - let wir: Workflow = match compile_program(test.as_bytes(), &pindex, &dindex, &ParserOptions::bscript()) { - CompileResult::Workflow(wir, _) => wir, - CompileResult::Err(errs) => { - for err in errs { - err.prettyprint(format!("<{id}>"), test); - } - panic!("Failed to compile BraneScript (see error above)"); - }, - CompileResult::Eof(err) => { - err.prettyprint(format!("<{id}>"), test); - panic!("Failed to compile BraneScript (see error above)"); - }, - - _ => { - unreachable!(); - }, - }; - // Emit the compiled workflow - println!("{}", (0..80).map(|_| '-').collect::()); - println!("Test '{id}'"); - println!(); - ast::do_traversal(&wir, std::io::stdout()).unwrap(); - println!(); - - // Analyse function calls (we'll assume this works too) - let calls: HashMap = resolve_calls(&wir, &wir.table, &mut vec![], ProgramCounter::start(), None, None).unwrap().0; - println!( - "Resolved functions calls: {:?}", - calls.iter().map(|(pc, func_id)| (format!("{}", pc.resolved(&wir.table)), *func_id)).collect::>() - ); - - // Analyse the inlinable funcs - let mut pred: HashMap>> = HashMap::with_capacity(calls.len()); - find_inlinable_funcs(&wir, &calls, &mut vec![], ProgramCounter::start(), None, &mut pred); - println!("Inlinable functions: {pred:?}"); - println!(); - - // Neat, done, assert it was right - assert_eq!(pred, gold); - } - } - - /// Runs the workflow inlining on the test files only - #[test] - fn test_checker_workflow_simplify() { - let tests_path: PathBuf = PathBuf::from(super::super::tests::TESTS_DIR); - - // Setup logger if told - if std::env::var("TEST_LOGGER").map(|value| value == "1" || value == "true").unwrap_or(false) { - if let Err(err) = HumanLogger::terminal(DebugMode::Full).init() { - eprintln!("WARNING: Failed to setup test logger: {err} (no logging for this session)"); - } - } - // Scope the function - let test_file: Option = std::env::var("TEST_FILE").ok(); - - // Run the compiler for every applicable DSL file - test_on_dsl_files_in("BraneScript", &tests_path, |path: PathBuf, code: String| { - // Skip if not the file we're looking for - if let Some(test_file) = &test_file { - if path.file_name().is_none() || path.file_name().unwrap().to_string_lossy() != test_file.as_str() { - return; - } - } - - // Start by the name to always know which file this is - println!("{}", (0..80).map(|_| '-').collect::()); - println!("File '{}' gave us:", path.display()); - - // Skip some files, sadly - if let Some(name) = path.file_name() { - if name == OsStr::new("class.bs") { - println!("Skipping test, since instance calling is not supported in checker workflows..."); - println!("{}\n\n", (0..80).map(|_| '-').collect::()); - return; - } - } - - // Load the package index - let pindex: PackageIndex = create_package_index_from(tests_path.join("packages")); - let dindex: DataIndex = create_data_index_from(tests_path.join("data")); - - // Compile the raw source to WIR - let wir: Workflow = match compile_program(code.as_bytes(), &pindex, &dindex, &ParserOptions::bscript()) { - CompileResult::Workflow(wir, warns) => { - // Print warnings if any - for w in warns { - w.prettyprint(path.to_string_lossy(), &code); - } - wir - }, - CompileResult::Eof(err) => { - // Print the error - err.prettyprint(path.to_string_lossy(), &code); - panic!("Failed to compile to WIR (see output above)"); - }, - CompileResult::Err(errs) => { - // Print the errors - for e in errs { - e.prettyprint(path.to_string_lossy(), &code); - } - panic!("Failed to compile to WIR (see output above)"); - }, - - _ => { - unreachable!(); - }, - }; - - // Alright preprocess it - let wir: Workflow = match simplify(wir) { - Ok((wir, _)) => wir, - Err(err) => { - panic!("Failed to preprocess WIR: {err}"); - }, - }; - - // Now print the file for prettyness - ast::do_traversal(&wir, std::io::stdout()).unwrap(); - println!("{}\n\n", (0..80).map(|_| '-').collect::()); - }); - } -} - -/***** ERRORS *****/ -/// Defines errors that may occur when preprocessing a [`Workflow`]. -#[derive(Debug)] -pub enum Error { - /// Unknown task given. - UnknownTask { id: usize }, - /// Unknown function given. - UnknownFunc { id: FunctionId }, - /// A [`Call`](brane_ast::ast::Edge::Call)-edge was encountered while we didn't know of a function ID on the stack. - CallingWithoutId { pc: ResolvedProgramCounter }, -} -impl Display for Error { - fn fmt(&self, f: &mut Formatter<'_>) -> FResult { - use Error::*; - match self { - UnknownTask { id } => write!(f, "Encountered unknown task ID {id} in Node"), - UnknownFunc { id } => write!(f, "Encountered unknown function ID {id} in Call"), - CallingWithoutId { pc } => write!(f, "Attempted to call function at {pc} without statically known task ID on the stack"), - } - } -} -impl error::Error for Error {} - -/***** ANALYSIS FUNCTIONS *****/ -/// Checks whether the given stream of instructions would end with a function ID on top of the stack. -/// -/// # Arguments -/// - `instrs`: The list of instructions to analyse. -/// - `idx`: The index of the particular instruction (i.e., the previous one) to examine. When calling this functio non-recursively, use the **last** instruction. -/// -/// # Returns -/// A double [`Option`] detailling what's possible: -/// - [`Some(Some(...))`] means that there was a function ID on top. -/// - [`Some(None)`] means that we _know_ there is _no_ function ID on top. -/// - [`None`] means that nothing was pushed, i.e., whatever was on top is still on top. -fn pushes_func_id(instrs: &[EdgeInstr], idx: usize) -> Option> { - // Pop the next instruction - let instr: &EdgeInstr = if idx < instrs.len() { - &instrs[idx] - } else { - // If we reached the last instruction, then we know no value was pushed :celebrate: - return None; - }; - - // Examine what it does - // NOTE: The BraneScript compiler only supports function calls over identifiers and projections. So we can ignore gnarly array stuff etc! - // NOTE: Actually... we know violently little statically of class calls in general, because they are fully pushed to dynamic land. We _could_ learn it by tracking - // a variable's contents over multiple edges, but that fucks; let's give up and only support direct calls for now. - match instr { - // What we're looking for! - EdgeInstr::Function { def } => Some(Some(*def)), - - // Things instructions only pop, potentially (accidentally) removing our function - // Jep just tell the thign we don't know, we don't need it for direct function calls - EdgeInstr::Pop {} | EdgeInstr::PopMarker {} | EdgeInstr::DynamicPop {} | EdgeInstr::VarSet { .. } => Some(None), - - // Alright some weird local branching; fuck it, also give up because we don't know which of the branches will do it - EdgeInstr::Branch { .. } | EdgeInstr::BranchNot { .. } => Some(None), - - // These instructions never pop- or push anything - EdgeInstr::VarDec { .. } | EdgeInstr::VarUndec { .. } => Some(None), - - // These instructions push invalid things _for sure_ - EdgeInstr::Cast { .. } - | EdgeInstr::Not {} - | EdgeInstr::Neg {} - | EdgeInstr::And {} - | EdgeInstr::Or {} - | EdgeInstr::Add {} - | EdgeInstr::Sub {} - | EdgeInstr::Mul {} - | EdgeInstr::Div {} - | EdgeInstr::Mod {} - | EdgeInstr::Eq {} - | EdgeInstr::Ne {} - | EdgeInstr::Lt {} - | EdgeInstr::Le {} - | EdgeInstr::Gt {} - | EdgeInstr::Ge {} - | EdgeInstr::Array { .. } - | EdgeInstr::ArrayIndex { .. } - | EdgeInstr::Instance { .. } - | EdgeInstr::Proj { .. } - | EdgeInstr::VarGet { .. } - | EdgeInstr::Boolean { .. } - | EdgeInstr::Integer { .. } - | EdgeInstr::Real { .. } - | EdgeInstr::String { .. } => Some(None), - } -} - -/// Analyses the edges in an [`Workflow`] to resolve function calls to the ID of the functions they call. -/// -/// # Arguments -/// - `wir`: The [`Workflow`] to analyse. -/// - `table`: A running [`SymTable`] that determines the current types in scope. -/// - `trace`: A stack of call pointers that keeps track of the trace of function calls. Allows us to avoid recursion. -/// - `stack_id`: The function ID currently known to be on the stack. Is [`None`] if we don't know this. -/// - `pc`: The program-counter-index of the edge to analyse. These are pairs of `(function, edge_idx)`, where main is referred to by [`usize::MAX`](usize). -/// - `breakpoint`: An optional program-counter-index that, if given, will not analyse that edge onwards (excluding it too). -/// -/// # Returns -/// A tuple with a [`HashMap`] that maps call indices (as program-counter-indices) to function IDs and an optional top call ID currently on the stack. -/// -/// Note that, if a call ID occurs in the map but has [`None`] as function ID, it means it does not map to a body (e.g., a builtin). -/// -/// # Errors -/// This function may error if we failed to statically discover the function IDs. -fn resolve_calls( - wir: &Workflow, - table: &SymTable, - trace: &mut Vec, - pc: ProgramCounter, - stack_id: Option, - breakpoint: Option, -) -> Result<(HashMap, Option), Error> { - // Quit if we're at the breakpoint - if let Some(breakpoint) = breakpoint { - if pc == breakpoint { - return Ok((HashMap::new(), None)); - } - } - - // Get the edge in the workflow - let edge: &Edge = match utils::get_edge(wir, pc) { - Some(edge) => edge, - None => return Ok((HashMap::new(), None)), - }; - - // Match to recursively process it - trace!("Attempting to resolve calls in {} ({:?})", pc.resolved(table), edge.variant()); - match edge { - Edge::Node { task, next, .. } => { - // Attempt to discover the return type of the Node. - let def: &TaskDef = match table.tasks.get(*task) { - Some(def) => def, - None => return Err(Error::UnknownTask { id: *task }), - }; - - // Alright, recurse with the next instruction - resolve_calls(wir, table, trace, pc.jump(*next), if def.func().ret.is_void() { stack_id } else { None }, breakpoint) - }, - - Edge::Linear { instrs, next } => { - // Analyse the instructions to find out if we can deduce a new `stack_id` - let stack_id: Option = if !instrs.is_empty() { pushes_func_id(instrs, instrs.len() - 1).unwrap_or(stack_id) } else { stack_id }; - - // Analyse the next one - resolve_calls(wir, table, trace, pc.jump(*next), stack_id, breakpoint) - }, - - Edge::Stop {} => Ok((HashMap::new(), None)), - - Edge::Branch { true_next, false_next, merge } => { - // First, analyse the branches - let (mut calls, mut stack_id): (HashMap<_, _>, Option) = - resolve_calls(wir, table, trace, pc.jump(*true_next), stack_id, merge.map(|merge| pc.jump(merge)))?; - if let Some(false_next) = false_next { - let (false_calls, false_stack) = resolve_calls(wir, table, trace, pc.jump(*false_next), stack_id, merge.map(|merge| pc.jump(merge)))?; - calls.extend(false_calls); - if stack_id != false_stack { - stack_id = None; - } - } - - // Analyse the remaining part next - if let Some(merge) = merge { - let (merge_calls, merge_stack) = resolve_calls(wir, table, trace, pc.jump(*merge), stack_id, breakpoint)?; - calls.extend(merge_calls); - stack_id = merge_stack; - } - - // Alright, return the found results - Ok((calls, stack_id)) - }, - - Edge::Parallel { branches, merge } => { - // Simply analyse all branches first. No need to worry about their return values and such, since that's not until the `Join`. - let mut calls: HashMap<_, _> = HashMap::new(); - for branch in branches { - calls.extend(resolve_calls(wir, table, trace, pc.jump(*branch), stack_id, breakpoint)?.0); - } - - // OK, then analyse the rest assuming the stack is unchanged (we can do that because the parallel's branches get clones) - let (new_calls, stack_id): (HashMap<_, _>, Option) = resolve_calls(wir, table, trace, pc.jump(*merge), stack_id, breakpoint)?; - calls.extend(new_calls); - Ok((calls, stack_id)) - }, - - Edge::Join { merge, next } => { - // Simply do the next, only _not_ resetting the stack ID if no value is returned. - resolve_calls(wir, table, trace, pc.jump(*next), if *merge == MergeStrategy::None { stack_id } else { None }, breakpoint) - }, - - Edge::Loop { cond, body, next } => { - // Traverse the three individually, using the stack ID of the codebody that precedes it - let (mut calls, mut cond_id): (HashMap<_, _>, Option) = - resolve_calls(wir, table, trace, pc.jump(*cond), stack_id, Some(pc.jump(*body - 1)))?; - let (body_calls, _): (HashMap<_, _>, Option) = resolve_calls(wir, table, trace, pc.jump(*body), cond_id, Some(pc.jump(*cond)))?; - calls.extend(body_calls); - if let Some(next) = next { - let (next_calls, next_id): (HashMap<_, _>, Option) = resolve_calls(wir, table, trace, pc.jump(*next), cond_id, breakpoint)?; - calls.extend(next_calls); - cond_id = next_id; - } - - // Done! - Ok((calls, cond_id)) - }, - - Edge::Call { input: _, result: _, next } => { - // Alright time to jump functions based on the current top-of-the-stack - let stack_id: usize = match stack_id { - Some(id) => id, - None => { - return Err(Error::CallingWithoutId { pc: pc.resolved(table) }); - }, - }; - - // We can early quit upon recursion - if trace.contains(&pc) { - let mut calls: HashMap = HashMap::from([(pc, stack_id)]); - let (next_calls, next_id): (HashMap<_, _>, Option) = resolve_calls(wir, table, trace, pc.jump(*next), None, breakpoint)?; - calls.extend(next_calls); - return Ok((calls, next_id)); - } - - // Add the mapping to the table - let mut calls: HashMap = HashMap::from([(pc, stack_id)]); - - // Resolve the call of the function (builtins simply return nothing, so are implicitly handled) - trace.push(pc); - let (call_calls, call_id): (HashMap<_, _>, Option) = resolve_calls(wir, table, trace, ProgramCounter::call(stack_id), None, None)?; - trace.pop(); - calls.extend(call_calls); - - // Then continue with the next one - let (next_calls, next_id): (HashMap<_, _>, Option) = resolve_calls(wir, table, trace, pc.jump(*next), call_id, breakpoint)?; - calls.extend(next_calls); - Ok((calls, next_id)) - }, - - Edge::Return { result: _ } => { - // If we're in the main function, this acts as an [`Elem::Stop`] with value - if pc.is_main() { - return Ok((HashMap::new(), None)); - } - - // To see whether we pass a function ID, consult the function definition - let def: &FunctionDef = match catch_unwind(|| table.func(pc.func_id)) { - Ok(def) => def, - Err(_) => return Err(Error::UnknownFunc { id: pc.func_id }), - }; - - // Only return the current one if the function returns void - if def.ret.is_void() { Ok((HashMap::new(), stack_id)) } else { Ok((HashMap::new(), None)) } - }, - } -} - -/// Attempts to find all non-recursive functions in the given WIR. -/// -/// The only moment when we don't consider a function inlinable is if the function call is: -/// - Recursive -/// - A builtin -/// - Undecidable -/// -/// # Arguments -/// - `wir`: The input [WIR](Workflow) to analyse. -/// - `calls`: The map of call indices to which function is actually called. -/// - `trace`: A trace of function IDs that we've "called". -/// - `pc`: Points to the current [`Edge`] to analyse. -/// - `breakpoint`: If given, then analysis should stop when this PC is hit. -/// - `inlinable`: The result we're recursively building. This set simply collects all function IDs and maps them to inlinable or not. If they are, then their ID is mapped to a list of functions on which the call depends (or else [`None`]). -/// -/// # Returns -/// A list of all function calls found (that are inlinable). This builds a dependency tree of which calls the given depends on. -fn find_inlinable_funcs( - wir: &Workflow, - calls: &HashMap, - trace: &mut Vec, - pc: ProgramCounter, - breakpoint: Option, - inlinable: &mut HashMap>>, -) -> HashSet { - // Stop on the breakpoint - if let Some(breakpoint) = breakpoint { - if pc == breakpoint { - return HashSet::new(); - } - } - // Attempt to get the edge - let edge: &Edge = match utils::get_edge(wir, pc) { - Some(edge) => edge, - None => return HashSet::new(), - }; - - // Match on its kind - trace!("Finding inlinable functions in {} ({:?})", pc.resolved(&wir.table), edge.variant()); - match edge { - Edge::Node { next, .. } | Edge::Linear { next, .. } => { - // Doesn't call any functions, so just proceed with the next one - find_inlinable_funcs(wir, calls, trace, pc.jump(*next), breakpoint, inlinable) - }, - - Edge::Stop {} => HashSet::new(), - - Edge::Branch { true_next, false_next, merge } => { - // Analyse the left branch... - let mut dependencies: HashSet = - find_inlinable_funcs(wir, calls, trace, pc.jump(*true_next), merge.map(|merge| pc.jump(merge)), inlinable); - // ...the right branch... - if let Some(false_next) = false_next { - dependencies.extend(find_inlinable_funcs(wir, calls, trace, pc.jump(*false_next), merge.map(|merge| pc.jump(merge)), inlinable)); - } - // ...and the merge! - if let Some(merge) = merge { - dependencies.extend(find_inlinable_funcs(wir, calls, trace, pc.jump(*merge), breakpoint, inlinable)); - } - dependencies - }, - - Edge::Parallel { branches, merge } => { - // Collect all the branches - let mut dependencies: HashSet = HashSet::new(); - for branch in branches { - dependencies.extend(find_inlinable_funcs(wir, calls, trace, pc.jump(*branch), Some(pc.jump(*merge)), inlinable)); - } - - // Run merge and done is Cees - dependencies.extend(find_inlinable_funcs(wir, calls, trace, pc.jump(*merge), breakpoint, inlinable)); - dependencies - }, - - Edge::Join { next, .. } => find_inlinable_funcs(wir, calls, trace, pc.jump(*next), breakpoint, inlinable), - - Edge::Loop { cond, body, next } => { - // Traverse the condition... - let mut dependencies: HashSet = find_inlinable_funcs(wir, calls, trace, pc.jump(*cond), Some(pc.jump(*body - 1)), inlinable); - // ...the body... - dependencies.extend(find_inlinable_funcs(wir, calls, trace, pc.jump(*body), Some(pc.jump(*cond)), inlinable)); - // ...and finally, the next step, if any - if let Some(next) = next { - dependencies.extend(find_inlinable_funcs(wir, calls, trace, pc.jump(*next), breakpoint, inlinable)); - } - dependencies - }, - - Edge::Call { next, .. } => { - // OK, the exciting point! - - // Resolve the function ID we're calling - let func_id: usize = match calls.get(&pc) { - Some(id) => *id, - None => { - panic!("Encountered unresolved call after running call analysis"); - }, - }; - let def: &FunctionDef = match wir.table.funcs.get(func_id) { - Some(def) => def, - None => panic!("Failed to get definition of function {func_id} after call analysis"), - }; - - // Analyse next, since all codepaths do this always - let mut dependencies: HashSet = find_inlinable_funcs(wir, calls, trace, pc.jump(*next), None, inlinable); - dependencies.insert(func_id); - - // Functions are not inlinable if builtins; if so, return - if BuiltinFunctions::is_builtin(&def.name) { - trace!("Function {} ('{}') is not inlinable because it is a builtin", func_id, def.name); - inlinable.insert(func_id, None); - return dependencies; - } - - // Examine if this call would introduce a recursive problem - if trace.contains(&func_id) { - // It's been in our callstack before - that means recursion! - // Change our minds about its inlinability - trace!("Function {} ('{}') is not inlinable because it is recursive", func_id, def.name); - inlinable.insert(func_id, None); - return dependencies; - } - if inlinable.contains_key(&func_id) { - // We've already seen this one! However, _don't_ change our mind about its inlinability because it means a repeated function call - // NOTE: No need to go into the call body, as we've done this the first time we saw it - trace!("Function {} ('{}') is skipped because we have seen it before", func_id, def.name); - return dependencies; - } - trace!("Function {} ('{}') is assumed as inlinable until we see it recursive", func_id, def.name); - - // For now assume that the function exist with no deps; we inject these later - inlinable.insert(func_id, Some(HashSet::new())); - - // If we get this far, recurse into the body - trace.push(func_id); - let func_deps: HashSet = find_inlinable_funcs(wir, calls, trace, ProgramCounter::call(func_id), None, inlinable); - trace.pop(); - - // Now we can inject the entries - if let Some(deps) = inlinable.get_mut(&func_id).unwrap() { - deps.extend(func_deps); - } - - // Return the dependencies in _this_ body. - dependencies - }, - - Edge::Return { result: _ } => HashSet::new(), - } -} - -/// Orders a given map of inlinable functions such that, when ordered inline, every function will have its calls inlined if possible. -/// -/// More specifically, the order makes sure that functions on which other functions depend (i.e., they make calls to it) are inlined first so that they can be inlined properly in the functions calling them. -/// -/// # Arguments -/// - `ordered`: The vector of ordered function IDs that is being populated. The inline order is left-to-right (i.e., the leftmost function should never have a dependency, the second-to-left can only depend on the leftmost, etc). -/// - `inlinable`: The map of inlinable functions to their dependencies. -fn order_inlinable<'i>(ordered: &mut Vec, inlinable: &HashMap>>, mut next: impl Iterator) { - // Get a function to inline - let func_id: usize = match next.next() { - Some(id) => *id, - None => return, - }; - let deps: &HashSet = match inlinable.get(&func_id).unwrap() { - Some(deps) => deps, - None => { - // No need to inline this one, so just continue - trace!("order_inlinable(): Not considering function {func_id} because it is not inlinable (deps is None)"); - order_inlinable(ordered, inlinable, next); - return; - }, - }; - - // Examine the dependencies - if deps.is_empty() { - // Base-case; add to the list first before any other - trace!("order_inlinable(): Function {func_id} is inlinable but has no dependencies"); - ordered.push(func_id); - order_inlinable(ordered, inlinable, next); - trace!("order_inlinable(): New result: {ordered:?}"); - } else { - // Recursive case: add all the dependencies first - trace!("order_inlinable(): Function {func_id} is inlinable and has dependencies"); - order_inlinable(ordered, inlinable, deps.iter()); - ordered.push(func_id); - trace!("order_inlinable(): New result: {ordered:?}"); - order_inlinable(ordered, inlinable, next); - } -} - -/// Given a vector, removes all duplicates from it. -/// -/// Retains the **first** occurrences. -/// -/// # Arguments -/// - `data`: The vector to deduplicate. -fn keep_unique_first(data: &mut Vec) { - // A buffer of seen elements - let mut seen: HashSet = HashSet::new(); - data.retain(|elem| { - if seen.contains(elem) { - false - } else { - seen.insert(*elem); - true - } - }); -} - -/// Traverses the given function body and replaces all [`Edge::Return`] with an [`Edge::Linear`] pointing to the given edge index. -/// -/// Also bumps definition pointers with the given values. This is necessary because we need to pull function scopes one layer up. -/// -/// # Arguments -/// - `edges`: The edges to traverse. -/// - `calls`: The map of program counters to calls that we update with any nested call's' new position. -/// - `func_id`: The ID of this function. -/// - `start_idx`: The index to add all next indices. -/// - `ret_idx`: The index to point the returning linears to. -/// - `pc`: Points to the current [`Edge`] to replace potentially. -/// - `breakpoint`: If given, then analysis should stop when this PC is hit. -fn prep_func_body( - edges: &mut [Edge], - calls: &mut HashMap, - func_id: usize, - start_idx: usize, - ret_idx: usize, - pc: usize, - breakpoint: Option, -) { - // Stop on the breakpoint - if let Some(breakpoint) = breakpoint { - if pc == breakpoint { - return; - } - } - // Attempt to get the edge - let edge: &mut Edge = match edges.get_mut(pc) { - Some(edge) => edge, - None => return, - }; - - // Match on its kind - match edge { - Edge::Node { next, .. } | Edge::Linear { next, .. } => { - // Update the nexts - let old_next: usize = *next; - *next += start_idx; - - // Continue traversing - prep_func_body(edges, calls, func_id, start_idx, ret_idx, old_next, breakpoint); - }, - - Edge::Stop {} => (), - - Edge::Branch { true_next, false_next, merge } => { - let (old_true_next, old_false_next, old_merge): (usize, Option, Option) = (*true_next, *false_next, *merge); - - // Update the nexts - *true_next += start_idx; - if let Some(false_next) = false_next { - *false_next += start_idx; - } - if let Some(merge) = merge { - *merge += start_idx; - } - - // Analyse the left branch... - prep_func_body(edges, calls, func_id, start_idx, ret_idx, old_true_next, old_merge); - // ...the right branch... - if let Some(old_false_next) = old_false_next { - prep_func_body(edges, calls, func_id, start_idx, ret_idx, old_false_next, old_merge); - } - // ...and the merge! - if let Some(old_merge) = old_merge { - prep_func_body(edges, calls, func_id, start_idx, ret_idx, old_merge, breakpoint); - } - }, - - Edge::Parallel { branches, merge } => { - let (old_branches, old_merge): (Vec, usize) = (branches.clone(), *merge); - - // Update the nexts - for branch in branches { - *branch += start_idx; - } - *merge += start_idx; - - // Collect all the branches - for old_branch in old_branches { - prep_func_body(edges, calls, func_id, start_idx, ret_idx, old_branch, Some(old_merge)); - } - - // Run merge and done is Cees - prep_func_body(edges, calls, func_id, start_idx, ret_idx, old_merge, breakpoint); - }, - - Edge::Join { next, .. } => { - let old_next: usize = *next; - *next += start_idx; - prep_func_body(edges, calls, func_id, start_idx, ret_idx, old_next, breakpoint); - }, - - Edge::Loop { cond, body: lbody, next } => { - let (old_cond, old_lbody, old_next): (usize, usize, Option) = (*cond, *lbody, *next); - - // Update the nexts - *cond += start_idx; - *lbody += start_idx; - if let Some(next) = next { - *next += start_idx; - } - - // Traverse the condition... - prep_func_body(edges, calls, func_id, start_idx, ret_idx, old_cond, Some(old_lbody - 1)); - // ...the body... - prep_func_body(edges, calls, func_id, start_idx, ret_idx, old_lbody, Some(old_cond)); - // ...and finally, the next step, if any - if let Some(old_next) = old_next { - prep_func_body(edges, calls, func_id, start_idx, ret_idx, old_next, breakpoint); - } - }, - - Edge::Call { next, .. } => { - let old_next: usize = *next; - - // Update the next - *next += start_idx; - - // Update the call list with this dude's new position - calls.insert( - ProgramCounter::new(FunctionId::Main, start_idx + pc), - *calls.get(&ProgramCounter::new(func_id, pc)).unwrap_or_else(|| panic!("Encountered unresolved call after call ID analysis")), - ); - - // Prepare the remainder - prep_func_body(edges, calls, func_id, start_idx, ret_idx, old_next, breakpoint); - }, - - Edge::Return { result: _ } => { - // Yank it - trace!("Yanking return edge at '{pc}' with a linear edge to '{ret_idx}'"); - *edge = Edge::Linear { instrs: vec![], next: ret_idx }; - }, - } -} - -/// Inlines the given set of functions in the given WIR function body. -/// -/// Note that this is a rather confusing operation space-wise. To prevent program counter pointers from becoming invalid, we simply replace the call with an empty [`Edge::Linear`] that connects to the body appended at the end of the stream. Then, the body connects back to the call's old `next`. -/// -/// # Arguments -/// - `body`: A [WIR](Workflow) function body to inline functions _in_. -/// - `calls`: The map of call indices to which function is actually called. -/// - `funcs`: A map of call IDs to function bodies ready to be substituted in the `body`. -/// - `inlinable`: A collection of functions that determines if functions are inlinable. If the set of `deps` is [`Some`], it's inlinable; else it's not. -/// - `func_id`: The ID of the function we're inlining. -/// - `pc`: Points to the current [`Edge`] to analyse. -/// - `breakpoint`: If given, then analysis should stop when this PC is hit. -// It's a compiler function, too many arguments are kinda its thing :P No it's not worth it to come up with structs for this. -#[allow(clippy::too_many_arguments)] -fn inline_funcs_in_body( - body: &mut Vec, - calls: &mut HashMap, - funcs: &HashMap>, - inlinable: &HashMap>>, - func_id: FunctionId, - pc: usize, - breakpoint: Option, -) { - // Stop on the breakpoint - if let Some(breakpoint) = breakpoint { - if pc == breakpoint { - return; - } - } - // Attempt to get the edge - let body_len: usize = body.len(); - let edge: &mut Edge = match body.get_mut(pc) { - Some(edge) => edge, - None => return, - }; - - // Match on its kind - match edge { - Edge::Node { next, .. } | Edge::Linear { next, .. } => { - let next: usize = *next; - inline_funcs_in_body(body, calls, funcs, inlinable, func_id, next, breakpoint) - }, - - Edge::Stop {} => (), - - Edge::Branch { true_next, false_next, merge } => { - let (true_next, false_next, merge): (usize, Option, Option) = (*true_next, *false_next, *merge); - - // Analyse the left branch... - inline_funcs_in_body(body, calls, funcs, inlinable, func_id, true_next, merge); - // ...the right branch... - if let Some(false_next) = false_next { - inline_funcs_in_body(body, calls, funcs, inlinable, func_id, false_next, merge) - } - // ...and the merge! - if let Some(merge) = merge { - inline_funcs_in_body(body, calls, funcs, inlinable, func_id, merge, breakpoint) - } - }, - - Edge::Parallel { branches, merge } => { - let (branches, merge): (Vec, usize) = (branches.clone(), *merge); - - // Collect all the branches - for branch in branches { - inline_funcs_in_body(body, calls, funcs, inlinable, func_id, branch, Some(merge)); - } - - // Run merge and done is Cees - inline_funcs_in_body(body, calls, funcs, inlinable, func_id, merge, breakpoint); - }, - - Edge::Join { next, .. } => { - let next: usize = *next; - inline_funcs_in_body(body, calls, funcs, inlinable, func_id, next, breakpoint) - }, - - Edge::Loop { cond, body: lbody, next } => { - let (cond, lbody, next): (usize, usize, Option) = (*cond, *lbody, *next); - - // Traverse the condition... - inline_funcs_in_body(body, calls, funcs, inlinable, func_id, cond, Some(lbody - 1)); - // ...the body... - inline_funcs_in_body(body, calls, funcs, inlinable, func_id, lbody, Some(cond)); - // ...and finally, the next step, if any - if let Some(next) = next { - inline_funcs_in_body(body, calls, funcs, inlinable, func_id, next, breakpoint); - } - }, - - Edge::Call { next, .. } => { - let next: usize = *next; - - // Resolve the function ID we're calling - let call_id: usize = match calls.get(&ProgramCounter::new(func_id, pc)) { - Some(id) => *id, - None => { - panic!("Encountered unresolved call after running inline analysis"); - }, - }; - - // Assert this is an inlinable function (and not external) - if inlinable.get(&call_id).map(|deps| deps.is_none()).unwrap_or(true) { - // Simply skip after doing the next - trace!("Not inlining function call to function {call_id} at {pc}"); - inline_funcs_in_body(body, calls, funcs, inlinable, func_id, next, breakpoint); - return; - } - trace!("Inlining function call to function {call_id} at {pc}"); - - // Otherwise, yank the call with a linear that refers to the inlined body instead (we'll put it after all the other edges to avoid them moving) - // Note: we insert a pop to consume the function reference pushed on the stack to execute the call - *edge = Edge::Linear { instrs: vec![EdgeInstr::Pop {}], next: body_len }; - - // Prepare the call body by replacing returns with normal links and by bumping all definitions - let mut call_body: Vec = funcs - .get(&call_id) - .unwrap_or_else(|| { - panic!("Encountered function ID '{call_id}' without function body after inline analysis (might be an uninlined dependency)") - }) - .clone(); - prep_func_body(&mut call_body, calls, call_id, body_len, next, 0, None); - - // Append it to the main body and the inlining is complete - body.extend(call_body); - - // End with the next edges - inline_funcs_in_body(body, calls, funcs, inlinable, func_id, next, breakpoint); - }, - - Edge::Return { result: _ } => (), - } -} - -/***** SIMPLIFICATION FUNCTIONS *****/ -/// Attempts to inline functions in the WIR as much as possible. -/// -/// The only moment when we don't is if the function call is: -/// - Recursive -/// - A builtin -/// - Undecidable -/// -/// # Arguments -/// - `wir`: The input [WIR](Workflow) to simply. -/// - `calls`: The map of call indices to which function is actually called. -/// -/// # Returns -/// The same `wir` as given, but then optimized. -/// -/// # Errors -/// This function may error if the input workflow is incoherent. -pub fn inline_functions(mut wir: Workflow, calls: &mut HashMap) -> Workflow { - // Analyse which functions in the WIR are non-recursive - let mut inlinable: HashMap>> = HashMap::with_capacity(calls.len()); - find_inlinable_funcs(&wir, calls, &mut vec![], ProgramCounter::start(), None, &mut inlinable); - debug!( - "Inlinable functions: {}", - inlinable - .iter() - .filter_map(|(id, deps)| if let Some(deps) = deps { - Some(format!( - "'{}' (depends on {})", - wir.table.funcs.get(*id).map(|def| def.name.as_str()).unwrap_or("???"), - deps.iter() - .map(|id| format!("'{}'", wir.table.funcs.get(*id).map(|def| def.name.as_str()).unwrap_or("???"))) - .collect::>() - .join(", "), - )) - } else { - None - }) - .collect::>() - .join(", ") - ); - - // Order them so that we satisfy function dependencies - let mut inline_order: Vec = Vec::with_capacity(inlinable.len()); - order_inlinable(&mut inline_order, &inlinable, inlinable.keys()); - keep_unique_first(&mut inline_order); - debug!( - "Inline order: {}", - inline_order - .iter() - .map(|id| format!("'{}'", wir.table.funcs.get(*id).map(|def| def.name.as_str()).unwrap_or("???"),)) - .collect::>() - .join(", ") - ); - - { - // Tear open the Workflow to satisfy the borrow checker - let Workflow { id: _, graph: wir_graph, metadata: _, funcs: wir_funcs, table: wir_table, user: _ } = &mut wir; - - // Extract the graph behind the Arc - let mut graph: Arc> = Arc::new(vec![]); - std::mem::swap(&mut graph, wir_graph); - let mut graph: Vec = Arc::into_inner(graph).unwrap(); - // Extract the functions behind the Arc - let mut funcs: Arc>> = Arc::new(HashMap::new()); - std::mem::swap(&mut funcs, wir_funcs); - let mut funcs: HashMap> = Arc::into_inner(funcs).unwrap(); - // Extract the WIR table - let mut table: Arc = Arc::new(SymTable::new()); - std::mem::swap(&mut table, wir_table); - let table: SymTable = Arc::into_inner(table).unwrap(); - - // Inline non-main function bodies first - let mut new_funcs: HashMap> = HashMap::new(); - for id in inline_order { - // Acquire the body - let mut new_body: Vec = funcs.get(&id).unwrap().clone(); - - // Inline the functions in this body - debug!("Inlining functions in function {id}"); - inline_funcs_in_body(&mut new_body, calls, &new_funcs, &inlinable, FunctionId::Func(id), 0, None); - new_funcs.insert(id, new_body); - } - funcs = new_funcs; - - // Now inline the main with all function bodies inlined correctly - debug!("Inlining functions in main"); - inline_funcs_in_body(&mut graph, calls, &funcs, &inlinable, FunctionId::Main, 0, None); - - // Write the functions and graphs back - let mut table: Arc = Arc::new(table); - std::mem::swap(wir_table, &mut table); - let mut funcs: Arc>> = Arc::new(funcs); - std::mem::swap(wir_funcs, &mut funcs); - let mut graph: Arc> = Arc::new(graph); - std::mem::swap(wir_graph, &mut graph); - } - - // OK, we did all we could - wir -} - -/***** LIBRARY *****/ -/// Simplifies the given WIR-workflow as much as possible to increase the compatability with checker workflows. -/// -/// Most importantly, it: -/// - Attempts to inline functions as long as they're non-recursive (since functions are not supported) -/// -/// # Arguments -/// - `wir`: The input [WIR](Workflow) to simply. -/// -/// # Returns -/// A tuple of the same `wir` as given, but then optimized, and a mapping of (remaining) [`Edge::Call`]s to whatever function they actually map. -/// -/// # Errors -/// This function may error if the input workflow is incoherent. -pub fn simplify(mut wir: Workflow) -> Result<(Workflow, HashMap), Error> { - // Analyse call dependencies first - let (mut calls, _): (HashMap, _) = resolve_calls(&wir, &wir.table, &mut vec![], ProgramCounter::start(), None, None)?; - debug!("Resolved calls as: {:?}", calls.iter().map(|(pc, id)| (format!("{}", pc.resolved(&wir.table)), *id)).collect::>()); - - // Simplify functions as much as possible - wir = inline_functions(wir, &mut calls); - - // Done! - Ok((wir, calls)) -} diff --git a/lib/workflow/src/spec.rs b/lib/workflow/src/spec.rs deleted file mode 100644 index 714c1e9..0000000 --- a/lib/workflow/src/spec.rs +++ /dev/null @@ -1,203 +0,0 @@ -// SPEC.rs -// by Lut99 -// -// Created: -// 27 Oct 2023, 15:56:55 -// Last edited: -// 13 Dec 2023, 08:37:20 -// Auto updated? -// Yes -// -// Description: -//! Defines the checker workflow itself. -// - -use std::collections::HashSet; -use std::hash::Hash; - -use brane_ast::MergeStrategy; -use brane_ast::locations::Location; -use enum_debug::EnumDebug; -use serde::{Deserialize, Serialize}; -use specifications::version::Version; - -/***** AUXILLARY DATA *****/ -/// Defines how a user looks like. -#[derive(Clone, Debug, Deserialize, Serialize)] -pub struct User { - /// The name of the user. - pub name: String, -} - -/// Defines a representation of a dataset. -#[derive(Clone, Debug, Deserialize, Serialize)] -pub struct Dataset { - /// The name of the dataset. - pub name: String, - /// The place that we get it from. No transfer is necessary if this is the place of task execution. - pub from: Option, -} -impl Dataset { - /// Constructor for the Dataset that only takes information originating from the workflow. - /// - /// # Arguments - /// - `name`: The name of the dataset. - /// - `from`: The location where to pull the dataset from, or [`None`] if no transfer is planned (i.e., it lives on the same domain as the task using it as input). - /// - /// # Returns - /// A new instance of self with the given `name` and `from`, and all other properties initialized to some default value. - #[inline] - pub fn new(name: impl Into, from: impl Into>) -> Self { Self { name: name.into(), from: from.into() } } -} -impl Eq for Dataset {} -impl PartialEq for Dataset { - #[inline] - fn eq(&self, other: &Self) -> bool { self.name == other.name } -} -impl Hash for Dataset { - #[inline] - fn hash(&self, state: &mut H) { self.name.hash(state) } -} - -/// Represents a "tag" and everything we need to know. -#[derive(Clone, Debug, Deserialize, Serialize)] -pub struct Metadata { - /// The "namespace" where the tag may be found. Represents the "owner", or the "definer" of the tag. - pub owner: String, - /// The tag itself. - pub tag: String, - /// The signature verifying this metadata. If present, it's given as a pair of the person signing it and their signature. - pub signature: Option<(String, String)>, -} - -/***** LIBRARY *****/ -/// Defines the workflow's toplevel view. -#[derive(Clone, Debug, Deserialize, Serialize)] -pub struct Workflow { - /// The identifier of this workflow as a whole. - pub id: String, - /// Defines the first node in the workflow. - pub start: Elem, - - /// The user instigating this workflow (and getting the result, if any). - pub user: User, - /// The metadata associated with this workflow as a whole. - pub metadata: Vec, - /// The signature verifying this workflow. - pub signature: String, -} - -/// Defines an element in the graph. This is either a _Node_, which defines a task execution, or an _Edge_, which defines how next tasks may be reached. -#[derive(Clone, Debug, Deserialize, EnumDebug, Serialize)] -pub enum Elem { - // Nodes - /// Defines a task that is executed, accessing and potentially producing data. - Task(ElemTask), - /// Defines the commiting of a result into a dataset, which will linger beyong the workflow with a specific name. - Commit(ElemCommit), - - // Edges - /// Defines an edge that connects to multiple next graph-branches of which only _one_ must be taken. Note that, because we don't include dynamic control flow information, we don't know _which_ will be taken. - Branch(ElemBranch), - /// Defines an edge that connects to multiple next graph-branches of which _all_ must be taken _concurrently_. - Parallel(ElemParallel), - /// Defines an edge that repeats a particular branch an unknown amount of times. - Loop(ElemLoop), - - // Terminators - /// Defines that the next element to execute is given by the parent `next`-field. - Next, - /// Defines that no more execution takes place. - /// - /// The option indicates if any data is carried to the remaining code. - Stop(HashSet), -} - -/// Defines a task node in the graph consisting of [`Elem`]s, which defines data access. -/// -/// Yeah so basically represents a task execution, with all checker-relevant information. -#[derive(Clone, Debug, Deserialize, Serialize)] -pub struct ElemTask { - /// Some identifier for this call specifically. - pub id: String, - - /// The name of the task to execute - pub name: String, - /// The name of the package in which to find the task. - pub package: String, - /// The version number of the package in which to find the task. - pub version: Version, - - /// Any input datasets used by the task. - /// - /// Note that this denotes a set of **possible** input sets. One or more of these may actually be used at runtime. - pub input: Vec, - /// If there is an output dataset produced by this task, this names it. - pub output: Option, - - /// The location where the task is planned to be executed, if any. - pub location: Option, - /// The list of metadata belonging to this task. Note: may need to be populated by the checker! - pub metadata: Vec, - - /// The next graph element that this task connects to. - pub next: Box, -} - -/// Defines a commit node in the graph consisting of [`Elem`]s, which defines data promotion. -/// -/// Checkers can assume that anything produced by a function will be deleted after the workflow stops (or at least, domains **should** do so) _unless_ committed. -#[derive(Clone, Debug, Deserialize, Serialize)] -pub struct ElemCommit { - /// Some identifier for this call specifically. - pub id: String, - - /// The name after committing. - pub data_name: String, - /// The location where the commit is planned to be "executed", if any. - /// - /// Note that this location is a little bit weird in the context of a commit, as it's just an adminstrative procedure. It can thus be interpreted purely as: "the location where the new output will be advertised". - pub location: Option, - /// Any input datasets used by the task. - /// - /// Note that this denotes a set of **possible** input sets. One or more of these may actually be used at runtime. - pub input: Vec, - - /// The next graph element that this task connects to. - pub next: Box, -} - -/// Defines a branching connection between graph [`Elem`]ents. -/// -/// Or rather, defines a linear connection between two nodes, with a set of branches in between them. -#[derive(Clone, Debug, Deserialize, Serialize)] -pub struct ElemBranch { - /// The branches of which one _must_ be taken, but we don't know which one. - pub branches: Vec, - /// The next graph element that this branching edge connects to. - pub next: Box, -} - -/// Defines a parallel connection between graph [`Elem`]ents. -/// -/// Is like a [branch](ElemBranch), except that _all_ branches are taken _concurrently_ instead of only one. -#[derive(Clone, Debug, Deserialize, Serialize)] -pub struct ElemParallel { - /// The branches, _all_ of which but be taken _concurrently_. - pub branches: Vec, - /// The method of joining the branches. - pub merge: MergeStrategy, - /// The next graph element that this parallel edge connects to. - pub next: Box, -} - -/// Defines a looping connection between graph [`Elem`]ents. -/// -/// Simply defines a branch that is taken repeatedly. Any condition that was there is embedded in the branching part, since that's how the branch is dynamically taken and we can't know how often any of them is taken anyway. -#[derive(Clone, Debug, Deserialize, Serialize)] -pub struct ElemLoop { - /// The body (and embedded condition) of the loop. - pub body: Box, - /// The next graph element that this parallel edge connects to. - pub next: Box, -} diff --git a/lib/workflow/src/tests.rs b/lib/workflow/src/tests.rs deleted file mode 100644 index dae0fd6..0000000 --- a/lib/workflow/src/tests.rs +++ /dev/null @@ -1,199 +0,0 @@ -// TESTS.rs -// by Lut99 -// -// Created: -// 31 Oct 2023, 15:27:38 -// Last edited: -// 16 Jan 2024, 15:44:03 -// Auto updated? -// Yes -// -// Description: -//! Implements tests for the [`Workflow`](super::spec::Workflow) (or -//! rather, its compiler(s)). -// - -use std::ffi::OsStr; -use std::path::PathBuf; -use std::sync::Arc; - -use brane_ast::{CompileResult, ParserOptions, ast, compile_program}; -use brane_shr::utilities::{create_data_index_from, create_package_index_from, test_on_dsl_files_in}; -use log::{Level, debug}; -use specifications::data::DataIndex; -use specifications::package::PackageIndex; - -use super::spec::Workflow; - -/***** CONSTANTS *****/ -/// Defines the location of the tests -pub(crate) const TESTS_DIR: &str = "../../tests"; - -/***** HELPER FUNCTIONS *****/ -/// Injects some (random) data in a workflow to simulate required information from the Brane runtime. -/// -/// Specifically, injects: -/// - The end user of the workflow. -/// -/// # Arguments -/// - `wir`: A (mutable reference to a) BraneScript [`Workflow`](ast::Workflow). -fn prepare_workflow(wir: &mut ast::Workflow) { - // Inject the user with a random name - wir.user = Arc::new(Some(names::three::rand().into())); -} - -/***** LIBRARY *****/ -/// Run all the BraneScript tests -#[test] -fn test_checker_workflow_unoptimized() { - let tests_path: PathBuf = PathBuf::from(TESTS_DIR); - - // Run the compiler for every applicable DSL file - test_on_dsl_files_in("BraneScript", &tests_path, |path: PathBuf, code: String| { - // Start by the name to always know which file this is - println!("{}", (0..80).map(|_| '-').collect::()); - println!("File '{}' gave us:", path.display()); - - // Skip some files, sadly - if let Some(name) = path.file_name() { - if name == OsStr::new("class.bs") { - println!("Skipping test, since instance calling is not supported in checker workflows..."); - println!("{}\n\n", (0..80).map(|_| '-').collect::()); - return; - } - } - - // Load the package index - let pindex: PackageIndex = create_package_index_from(tests_path.join("packages")); - let dindex: DataIndex = create_data_index_from(tests_path.join("data")); - - // Compile the raw source to WIR - let mut wir: ast::Workflow = match compile_program(code.as_bytes(), &pindex, &dindex, &ParserOptions::bscript()) { - CompileResult::Workflow(wir, warns) => { - // Print warnings if any - for w in warns { - w.prettyprint(path.to_string_lossy(), &code); - } - wir - }, - CompileResult::Eof(err) => { - // Print the error - err.prettyprint(path.to_string_lossy(), &code); - panic!("Failed to compile to WIR (see output above)"); - }, - CompileResult::Err(errs) => { - // Print the errors - for e in errs { - e.prettyprint(path.to_string_lossy(), &code); - } - panic!("Failed to compile to WIR (see output above)"); - }, - - _ => { - unreachable!(); - }, - }; - - // Insert some additional content - prepare_workflow(&mut wir); - - // Print the WIR in debug mode - if log::max_level() >= Level::Debug { - // Write the processed graph - let mut buf: Vec = vec![]; - brane_ast::traversals::print::ast::do_traversal(&wir, &mut buf).unwrap(); - debug!("Compiled workflow:\n\n{}\n", String::from_utf8_lossy(&buf)); - } - - // Next, compile to the checker's workflow - let wf: Workflow = match wir.try_into() { - Ok(wf) => wf, - Err(err) => { - panic!("Failed to compile WIR to CheckerWorkflow: {err}"); - }, - }; - - // Now print the file for prettyness - println!("{}", wf.visualize()); - println!("{}\n\n", (0..80).map(|_| '-').collect::()); - }); -} - -/// Run all the BraneScript tests _with_ optimization -#[test] -fn test_checker_workflow_optimized() { - let tests_path: PathBuf = PathBuf::from(TESTS_DIR); - - // Run the compiler for every applicable DSL file - test_on_dsl_files_in("BraneScript", &tests_path, |path: PathBuf, code: String| { - // Start by the name to always know which file this is - println!("{}", (0..80).map(|_| '-').collect::()); - println!("(Optimized) File '{}' gave us:", path.display()); - - // Skip some files, sadly - if let Some(name) = path.file_name() { - if name == OsStr::new("class.bs") { - println!("Skipping test, since instance calling is not supported in checker workflows..."); - println!("{}\n\n", (0..80).map(|_| '-').collect::()); - return; - } - } - - // Load the package index - let pindex: PackageIndex = create_package_index_from(tests_path.join("packages")); - let dindex: DataIndex = create_data_index_from(tests_path.join("data")); - - // Compile the raw source to WIR - let mut wir: ast::Workflow = match compile_program(code.as_bytes(), &pindex, &dindex, &ParserOptions::bscript()) { - CompileResult::Workflow(wir, warns) => { - // Print warnings if any - for w in warns { - w.prettyprint(path.to_string_lossy(), &code); - } - wir - }, - CompileResult::Eof(err) => { - // Print the error - err.prettyprint(path.to_string_lossy(), &code); - panic!("Failed to compile to WIR (see output above)"); - }, - CompileResult::Err(errs) => { - // Print the errors - for e in errs { - e.prettyprint(path.to_string_lossy(), &code); - } - panic!("Failed to compile to WIR (see output above)"); - }, - - _ => { - unreachable!(); - }, - }; - - // Insert some additional content - prepare_workflow(&mut wir); - - // Print the WIR in debug mode - if log::max_level() >= Level::Debug { - // Write the processed graph - let mut buf: Vec = vec![]; - brane_ast::traversals::print::ast::do_traversal(&wir, &mut buf).unwrap(); - debug!("Compiled workflow:\n\n{}\n", String::from_utf8_lossy(&buf)); - } - - // Next, compile to the checker's workflow - let mut wf: Workflow = match wir.try_into() { - Ok(wf) => wf, - Err(err) => { - panic!("Failed to compile WIR to CheckerWorkflow: {err}"); - }, - }; - - // Slide in that optimization - wf.optimize(); - - // Now print the file for prettyness - println!("{}", wf.visualize()); - println!("{}\n\n", (0..80).map(|_| '-').collect::()); - }); -} diff --git a/lib/workflow/src/utils.rs b/lib/workflow/src/utils.rs deleted file mode 100644 index a3942b2..0000000 --- a/lib/workflow/src/utils.rs +++ /dev/null @@ -1,86 +0,0 @@ -// UTILS.rs -// by Lut99 -// -// Created: -// 02 Nov 2023, 15:11:34 -// Last edited: -// 12 Jun 2024, 17:42:17 -// Auto updated? -// Yes -// -// Description: -//! Defines some utilities used in multiple places in this crate. -// - -use std::collections::HashSet; - -use brane_ast::ast; -use brane_exe::pc::ProgramCounter; - -use crate::{Dataset, Elem, ElemBranch, ElemCommit, ElemLoop, ElemParallel, ElemTask}; - -/***** LIBRARY FUNCTIONS *****/ -/// Gets a workflow edge from a PC. -/// -/// # Arguments -/// - `wir`: The [`ast::Workflow`] to get the edge from. -/// - `pc`: The program counter that points to the edge (hopefully). -/// -/// # Returns -/// The edge the `pc` pointed to, or [`None`] if it was out-of-bounds. -#[inline] -pub fn get_edge(wir: &ast::Workflow, pc: ProgramCounter) -> Option<&ast::Edge> { - if pc.func_id.is_main() { wir.graph.get(pc.edge_idx) } else { wir.funcs.get(&pc.func_id.id()).and_then(|edges| edges.get(pc.edge_idx)) } -} - -/// A definition of a visitor for Workflow graphs -pub trait WorkflowVisitor { - fn visit_task(&mut self, _task: &ElemTask) {} - fn visit_commit(&mut self, _commit: &ElemCommit) {} - fn visit_branch(&mut self, _branch: &ElemBranch) {} - fn visit_parallel(&mut self, _parallel: &ElemParallel) {} - fn visit_loop(&mut self, _loop: &ElemLoop) {} - fn visit_next(&mut self) {} - fn visit_stop(&mut self, _stop: &HashSet) {} -} - -/// A walker that visits all [`Elem`]s in preorder -pub fn walk_workflow_preorder(elem: &Elem, visitor: &mut impl WorkflowVisitor) { - match elem { - Elem::Task(task) => { - visitor.visit_task(task); - walk_workflow_preorder(&task.next, visitor); - }, - Elem::Commit(commit) => { - visitor.visit_commit(commit); - walk_workflow_preorder(&commit.next, visitor); - }, - Elem::Branch(branch) => { - visitor.visit_branch(branch); - for elem in &branch.branches { - walk_workflow_preorder(elem, visitor); - } - - walk_workflow_preorder(&branch.next, visitor); - }, - Elem::Parallel(parallel) => { - visitor.visit_parallel(parallel); - for elem in ¶llel.branches { - walk_workflow_preorder(elem, visitor); - } - - walk_workflow_preorder(¶llel.next, visitor); - }, - Elem::Loop(r#loop) => { - visitor.visit_loop(r#loop); - walk_workflow_preorder(&r#loop.body, visitor); - walk_workflow_preorder(&r#loop.next, visitor); - }, - Elem::Next => { - visitor.visit_next(); - }, - Elem::Stop(stop) => { - visitor.visit_stop(stop); - }, - } -} diff --git a/lib/workflow/src/visitor.rs b/lib/workflow/src/visitor.rs new file mode 100644 index 0000000..0ec21ff --- /dev/null +++ b/lib/workflow/src/visitor.rs @@ -0,0 +1,519 @@ +// VISITOR.rs +// by Lut99 +// +// Created: +// 08 Oct 2024, 17:35:03 +// Last edited: +// 14 Oct 2024, 11:55:45 +// Auto updated? +// Yes +// +// Description: +//! Implements a visitor for [`Workflow`] elements. +// + +use std::error::Error; + +use crate::{Elem, ElemBranch, ElemCall, ElemLoop, ElemParallel, Workflow}; + + +/***** LIBRARY *****/ +/// Visits [`Elem`]s by reference. +pub trait Visitor<'w> { + /// The error returned by this Visitor. + /// + /// Tip: use [`Infallible`](std::convert::Infallible) if you don't have any. + type Error: Error; + + + /// Visits an [`Elem`]. + /// + /// The default implementation determines what node it deals with, and then calls the + /// appropriate `Visitor::visit_X()`-function. It's usually not necessary to override this + /// method. + /// + /// # Arguments + /// - `elem`: The visited [`Elem`]. + /// + /// # Errors + /// If this visitor fails, then the whole visiting processes is terminated. + fn visit(&mut self, elem: &'w Elem) -> Result<(), Self::Error> { + match elem { + Elem::Call(c) => self.visit_call(c), + + Elem::Branch(b) => self.visit_branch(b), + Elem::Parallel(p) => self.visit_parallel(p), + Elem::Loop(l) => self.visit_loop(l), + + Elem::Next => self.visit_next(), + Elem::Stop => self.visit_stop(), + } + } + + + /// Visits an [`Elem::Call`]. + /// + /// The default implementation doesn't do anything meaningful besides visiting the next node. + /// + /// # Arguments + /// - `elem`: The visited [`ElemCall`]. + /// + /// # Errors + /// If this visitor fails, then the whole visiting processes is terminated. + #[inline] + fn visit_call(&mut self, elem: &'w ElemCall) -> Result<(), Self::Error> { self.visit(&elem.next) } + + /// Visits an [`Elem::Branch`]. + /// + /// The default implementation doesn't do anything meaningful besides visiting the branches, + /// and then the next node. + /// + /// # Arguments + /// - `elem`: The visited [`ElemBranch`]. + /// + /// # Errors + /// If this visitor fails, then the whole visiting processes is terminated. + #[inline] + fn visit_branch(&mut self, elem: &'w ElemBranch) -> Result<(), Self::Error> { + for b in &elem.branches { + self.visit(b)?; + } + self.visit(&elem.next) + } + + /// Visits an [`Elem::Parallel`]. + /// + /// The default implementation doesn't do anything meaningful besides visiting the branches, + /// and then the next node. + /// + /// # Arguments + /// - `elem`: The visited [`ElemParallel`]. + /// + /// # Errors + /// If this visitor fails, then the whole visiting processes is terminated. + #[inline] + fn visit_parallel(&mut self, elem: &'w ElemParallel) -> Result<(), Self::Error> { + for b in &elem.branches { + self.visit(b)?; + } + self.visit(&elem.next) + } + + /// Visits an [`Elem::Loop`]. + /// + /// The default implementation doesn't do anything meaningful besides visiting the body, and + /// then the next node. + /// + /// # Arguments + /// - `elem`: The visited [`ElemLoop`]. + /// + /// # Errors + /// If this visitor fails, then the whole visiting processes is terminated. + #[inline] + fn visit_loop(&mut self, elem: &'w ElemLoop) -> Result<(), Self::Error> { + self.visit(&elem.body)?; + self.visit(&elem.next) + } + + + /// Visits an [`Elem::Next`]. + /// + /// The default implementation doesn't do anything meaningful. + /// + /// # Errors + /// If this visitor fails, then the whole visiting processes is terminated. + #[inline] + fn visit_next(&mut self) -> Result<(), Self::Error> { Ok(()) } + + /// Visits an [`Elem::Stop`]. + /// + /// The default implementation doesn't do anything meaningful. + /// + /// # Errors + /// If this visitor fails, then the whole visiting processes is terminated. + #[inline] + fn visit_stop(&mut self) -> Result<(), Self::Error> { Ok(()) } +} +impl<'a, 'w, T: Visitor<'w>> Visitor<'w> for &'a mut T { + type Error = T::Error; + + #[inline] + fn visit(&mut self, elem: &'w Elem) -> Result<(), Self::Error> { T::visit(self, elem) } + + #[inline] + fn visit_call(&mut self, elem: &'w ElemCall) -> Result<(), Self::Error> { T::visit_call(self, elem) } + + #[inline] + fn visit_branch(&mut self, elem: &'w ElemBranch) -> Result<(), Self::Error> { T::visit_branch(self, elem) } + + #[inline] + fn visit_parallel(&mut self, elem: &'w ElemParallel) -> Result<(), Self::Error> { T::visit_parallel(self, elem) } + + #[inline] + fn visit_loop(&mut self, elem: &'w ElemLoop) -> Result<(), Self::Error> { T::visit_loop(self, elem) } + + #[inline] + fn visit_next(&mut self) -> Result<(), Self::Error> { T::visit_next(self) } + + #[inline] + fn visit_stop(&mut self) -> Result<(), Self::Error> { T::visit_stop(self) } +} + + + +/// Visits [`Elem`]s by mutable reference. +pub trait VisitorMut<'w> { + /// The error returned by this VisitorMut. + /// + /// Tip: use [`Infallible`](std::convert::Infallible) if you don't have any. + type Error: Error; + + + /// Visits an [`Elem`]. + /// + /// The default implementation determines what node it deals with, and then calls the + /// appropriate `Visitor::visit_X()`-function. It's usually not necessary to override this + /// method. + /// + /// # Arguments + /// - `elem`: The visited [`Elem`]. + /// + /// # Errors + /// If this visitor fails, then the whole visiting processes is terminated. + fn visit(&mut self, elem: &'w mut Elem) -> Result<(), Self::Error> { + match elem { + Elem::Call(c) => self.visit_call(c), + + Elem::Branch(b) => self.visit_branch(b), + Elem::Parallel(p) => self.visit_parallel(p), + Elem::Loop(l) => self.visit_loop(l), + + Elem::Next => self.visit_next(), + Elem::Stop => self.visit_stop(), + } + } + + + /// Visits an [`Elem::Call`]. + /// + /// The default implementation doesn't do anything meaningful besides visiting the next node. + /// + /// # Arguments + /// - `elem`: The visited [`ElemCall`]. + /// + /// # Errors + /// If this visitor fails, then the whole visiting processes is terminated. + #[inline] + fn visit_call(&mut self, elem: &'w mut ElemCall) -> Result<(), Self::Error> { self.visit(&mut elem.next) } + + /// Visits an [`Elem::Branch`]. + /// + /// The default implementation doesn't do anything meaningful besides visiting the branches, + /// and then the next node. + /// + /// # Arguments + /// - `elem`: The visited [`ElemBranch`]. + /// + /// # Errors + /// If this visitor fails, then the whole visiting processes is terminated. + #[inline] + fn visit_branch(&mut self, elem: &'w mut ElemBranch) -> Result<(), Self::Error> { + for b in &mut elem.branches { + self.visit(b)?; + } + self.visit(&mut elem.next) + } + + /// Visits an [`Elem::Parallel`]. + /// + /// The default implementation doesn't do anything meaningful besides visiting the branches, + /// and then the next node. + /// + /// # Arguments + /// - `elem`: The visited [`ElemParallel`]. + /// + /// # Errors + /// If this visitor fails, then the whole visiting processes is terminated. + #[inline] + fn visit_parallel(&mut self, elem: &'w mut ElemParallel) -> Result<(), Self::Error> { + for b in &mut elem.branches { + self.visit(b)?; + } + self.visit(&mut elem.next) + } + + /// Visits an [`Elem::Loop`]. + /// + /// The default implementation doesn't do anything meaningful besides visiting the body, and + /// then the next node. + /// + /// # Arguments + /// - `elem`: The visited [`ElemLoop`]. + /// + /// # Errors + /// If this visitor fails, then the whole visiting processes is terminated. + #[inline] + fn visit_loop(&mut self, elem: &'w mut ElemLoop) -> Result<(), Self::Error> { + self.visit(&mut elem.body)?; + self.visit(&mut elem.next) + } + + + /// Visits an [`Elem::Next`]. + /// + /// The default implementation doesn't do anything meaningful. + /// + /// # Errors + /// If this visitor fails, then the whole visiting processes is terminated. + #[inline] + fn visit_next(&mut self) -> Result<(), Self::Error> { Ok(()) } + + /// Visits an [`Elem::Stop`]. + /// + /// The default implementation doesn't do anything meaningful. + /// + /// # Errors + /// If this visitor fails, then the whole visiting processes is terminated. + #[inline] + fn visit_stop(&mut self) -> Result<(), Self::Error> { Ok(()) } +} +impl<'a, 'w, T: VisitorMut<'w>> VisitorMut<'w> for &'a mut T { + type Error = T::Error; + + #[inline] + fn visit(&mut self, elem: &'w mut Elem) -> Result<(), Self::Error> { T::visit(self, elem) } + + #[inline] + fn visit_call(&mut self, elem: &'w mut ElemCall) -> Result<(), Self::Error> { T::visit_call(self, elem) } + + #[inline] + fn visit_branch(&mut self, elem: &'w mut ElemBranch) -> Result<(), Self::Error> { T::visit_branch(self, elem) } + + #[inline] + fn visit_parallel(&mut self, elem: &'w mut ElemParallel) -> Result<(), Self::Error> { T::visit_parallel(self, elem) } + + #[inline] + fn visit_loop(&mut self, elem: &'w mut ElemLoop) -> Result<(), Self::Error> { T::visit_loop(self, elem) } + + #[inline] + fn visit_next(&mut self) -> Result<(), Self::Error> { T::visit_next(self) } + + #[inline] + fn visit_stop(&mut self) -> Result<(), Self::Error> { T::visit_stop(self) } +} + + + +/// Visits [`Elem`]s by ownership. +pub trait VisitorOwned { + /// The error returned by this VisitorMut. + /// + /// Tip: use [`Infallible`](std::convert::Infallible) if you don't have any. + type Error: Error; + + + /// Visits an [`Elem`]. + /// + /// The default implementation determines what node it deals with, and then calls the + /// appropriate `Visitor::visit_X()`-function. It's usually not necessary to override this + /// method. + /// + /// # Arguments + /// - `elem`: The visited [`Elem`]. + /// + /// # Returns + /// The element returned by the appropriate visit. + /// + /// # Errors + /// If this visitor fails, then the whole visiting processes is terminated. + fn visit(&mut self, elem: Elem) -> Result { + match elem { + Elem::Call(c) => self.visit_call(c), + + Elem::Branch(b) => self.visit_branch(b), + Elem::Parallel(p) => self.visit_parallel(p), + Elem::Loop(l) => self.visit_loop(l), + + Elem::Next => self.visit_next(), + Elem::Stop => self.visit_stop(), + } + } + + /// Visits an [`Elem`] that is only accessible through a mutable reference. + /// + /// This wraps [`VisitorOwned::visit()`] and deals with the mutability. + /// + /// # Arguments + /// - `elem`: The visited [`Elem`]. Note that it's automatically updated if the visit lead that + /// way. + /// + /// # Errors + /// If this visitor fails, then the whole visiting processes is terminated. + fn visit_mut(&mut self, elem: &mut Elem) -> Result<(), Self::Error> { + let mut temp: Elem = Elem::Stop; + std::mem::swap(&mut temp, elem); + *elem = self.visit(temp)?; + Ok(()) + } + + + /// Visits an [`Elem::Call`]. + /// + /// The default implementation doesn't do anything meaningful besides visiting the next node. + /// + /// # Arguments + /// - `elem`: The visited [`ElemCall`]. + /// + /// # Returns + /// The given `elem` (i.e., nothing is replaced). + /// + /// # Errors + /// If this visitor fails, then the whole visiting processes is terminated. + #[inline] + fn visit_call(&mut self, mut elem: ElemCall) -> Result { + self.visit_mut(&mut elem.next)?; + Ok(Elem::Call(elem)) + } + + /// Visits an [`Elem::Branch`]. + /// + /// The default implementation doesn't do anything meaningful besides visiting the branches, + /// and then the next node. + /// + /// # Arguments + /// - `elem`: The visited [`ElemBranch`]. + /// + /// # Returns + /// The given `elem` (i.e., nothing is replaced). + /// + /// # Errors + /// If this visitor fails, then the whole visiting processes is terminated. + #[inline] + fn visit_branch(&mut self, mut elem: ElemBranch) -> Result { + for b in &mut elem.branches { + self.visit_mut(b)?; + } + self.visit_mut(&mut elem.next)?; + Ok(Elem::Branch(elem)) + } + + /// Visits an [`Elem::Parallel`]. + /// + /// The default implementation doesn't do anything meaningful besides visiting the branches, + /// and then the next node. + /// + /// # Arguments + /// - `elem`: The visited [`ElemParallel`]. + /// + /// # Returns + /// The given `elem` (i.e., nothing is replaced). + /// + /// # Errors + /// If this visitor fails, then the whole visiting processes is terminated. + #[inline] + fn visit_parallel(&mut self, mut elem: ElemParallel) -> Result { + for b in &mut elem.branches { + self.visit_mut(b)?; + } + self.visit_mut(&mut elem.next)?; + Ok(Elem::Parallel(elem)) + } + + /// Visits an [`Elem::Loop`]. + /// + /// The default implementation doesn't do anything meaningful besides visiting the body, and + /// then the next node. + /// + /// # Arguments + /// - `elem`: The visited [`ElemLoop`]. + /// + /// # Returns + /// The given `elem` (i.e., nothing is replaced). + /// + /// # Errors + /// If this visitor fails, then the whole visiting processes is terminated. + #[inline] + fn visit_loop(&mut self, mut elem: ElemLoop) -> Result { + self.visit_mut(&mut elem.body)?; + self.visit_mut(&mut elem.next)?; + Ok(Elem::Loop(elem)) + } + + + /// Visits an [`Elem::Next`]. + /// + /// The default implementation doesn't do anything meaningful. + /// + /// # Returns + /// An [`Elem::Next`] (i.e., nothing is replaced). + /// + /// # Errors + /// If this visitor fails, then the whole visiting processes is terminated. + #[inline] + fn visit_next(&mut self) -> Result { Ok(Elem::Next) } + + /// Visits an [`Elem::Stop`]. + /// + /// The default implementation doesn't do anything meaningful. + /// + /// # Returns + /// An [`Elem::Stop`] (i.e., nothing is replaced). + /// + /// # Errors + /// If this visitor fails, then the whole visiting processes is terminated. + #[inline] + fn visit_stop(&mut self) -> Result { Ok(Elem::Stop) } +} +impl<'a, T: VisitorOwned> VisitorOwned for &'a mut T { + type Error = T::Error; + + #[inline] + fn visit(&mut self, elem: Elem) -> Result { T::visit(self, elem) } + + #[inline] + fn visit_call(&mut self, elem: ElemCall) -> Result { T::visit_call(self, elem) } + + #[inline] + fn visit_branch(&mut self, elem: ElemBranch) -> Result { T::visit_branch(self, elem) } + + #[inline] + fn visit_parallel(&mut self, elem: ElemParallel) -> Result { T::visit_parallel(self, elem) } + + #[inline] + fn visit_loop(&mut self, elem: ElemLoop) -> Result { T::visit_loop(self, elem) } + + #[inline] + fn visit_next(&mut self) -> Result { T::visit_next(self) } + + #[inline] + fn visit_stop(&mut self) -> Result { T::visit_stop(self) } +} + + + + + +/***** IMPLEMENTATIONS *****/ +// Visitor +impl Workflow { + /// Visits all [`Elem`]ents in this graph and calls the appropriate functions for them. + /// + /// # Arguments + /// - `visitor`: The [`Visitor`] used to visit all elements. + #[inline] + pub fn visit<'w, V: Visitor<'w>>(&'w self, mut visitor: V) -> Result<(), V::Error> { visitor.visit(&self.start) } + + /// Visits all [`Elem`]ents in this graph mutably and calls the appropriate functions for them. + /// + /// # Arguments + /// - `visitor`: The [`Visitor`] used to visit all elements. + #[inline] + pub fn visit_mut<'w, V: VisitorMut<'w>>(&'w mut self, mut visitor: V) -> Result<(), V::Error> { visitor.visit(&mut self.start) } + + /// Visits all [`Elem`]ents in this graph by ownership and calls the appropriate functions for them. + /// + /// # Arguments + /// - `visitor`: The [`Visitor`] used to visit all elements. + #[inline] + pub fn visit_owned(&mut self, mut visitor: V) -> Result<(), V::Error> { visitor.visit_mut(&mut self.start) } +} diff --git a/lib/workflow/src/visualize.rs b/lib/workflow/src/visualize.rs index 516ef9d..4327006 100644 --- a/lib/workflow/src/visualize.rs +++ b/lib/workflow/src/visualize.rs @@ -4,7 +4,7 @@ // Created: // 31 Oct 2023, 14:30:00 // Last edited: -// 13 Dec 2023, 08:44:28 +// 08 Oct 2024, 18:15:02 // Auto updated? // Yes // @@ -15,7 +15,8 @@ use std::fmt::{Display, Formatter, Result as FResult}; -use super::spec::{Elem, ElemBranch, ElemCommit, ElemLoop, ElemParallel, ElemTask, Workflow}; +use super::{Elem, ElemBranch, ElemCall, ElemLoop, ElemParallel, Workflow}; + /***** HELPER MACROS *****/ /// Prints a given iterator somewhat nicely to a string. @@ -37,6 +38,10 @@ macro_rules! write_iter { }}; } + + + + /***** HELPER FUNCTIONS *****/ /// Writes an [`Elem`] to the given formatter. /// @@ -51,33 +56,24 @@ macro_rules! write_iter { fn print_elem(f: &mut Formatter, elem: &Elem, prefix: &dyn Display) -> FResult { // Print the element match elem { - Elem::Task(ElemTask { id, name, package, version, input, output, location, metadata, next }) => { + Elem::Call(ElemCall { id, task, input, output, at, metadata, next }) => { writeln!(f, "{prefix}task")?; - writeln!(f, "{prefix} - id : {id}")?; + writeln!(f, "{prefix} - id : {id:?}")?; + writeln!(f, "{prefix} - task : {task:?}")?; + writeln!(f, "{prefix} - input : {}", write_iter!(input.iter().map(|data| format!("'{}'", data.id)), " or "))?; + writeln!(f, "{prefix} - output : {}", write_iter!(output.iter().map(|data| format!("'{}'", data.id)), " or "))?; writeln!(f, "{prefix}")?; - writeln!(f, "{prefix} - name : {name}")?; - writeln!(f, "{prefix} - package : {package}")?; - writeln!(f, "{prefix} - version : {version}")?; + writeln!(f, "{prefix} - at : {}", if let Some(at) = &at { at.id.as_str() } else { "" })?; writeln!(f, "{prefix}")?; - writeln!(f, "{prefix} - input : {}", write_iter!(input.iter().map(|data| format!("'{}'", data.name)), " or "))?; writeln!( f, - "{} - output : {}", - prefix, - if let Some(output) = &output { format!("'{}'", output.name.as_str()) } else { "".into() } - )?; - writeln!(f, "{prefix}")?; - writeln!(f, "{prefix} - location : {}", if let Some(location) = &location { location.as_str() } else { "" })?; - writeln!( - f, - "{} - metadata : {}", + "{} - metadata: {}", prefix, write_iter!( metadata.iter().map(|metadata| format!( - "#{}.{}{}", - metadata.owner, + "{:?}{}", metadata.tag, - if let Some((assigner, signature)) = &metadata.signature { format!("#{assigner}:{signature}") } else { String::new() } + if let Some((assigner, signature)) = &metadata.signature { format!("{}:{}", assigner.id, signature) } else { String::new() } )), ", " ) @@ -100,9 +96,8 @@ fn print_elem(f: &mut Formatter, elem: &Elem, prefix: &dyn Display) -> FResult { // Do next print_elem(f, next, prefix) }, - Elem::Parallel(ElemParallel { branches, merge, next }) => { + Elem::Parallel(ElemParallel { branches, next }) => { writeln!(f, "{prefix}parallel")?; - writeln!(f, "{prefix} - merge strategy : {merge:?}")?; // Write the branches for (i, branch) in branches.iter().enumerate() { @@ -123,40 +118,20 @@ fn print_elem(f: &mut Formatter, elem: &Elem, prefix: &dyn Display) -> FResult { // Do next print_elem(f, next, prefix) }, - Elem::Commit(ElemCommit { id, data_name, location, input, next }) => { - writeln!(f, "{prefix}commit <{} as '{}'>", write_iter!(input.iter().map(|data| format!("'{}'", data.name)), " or "), data_name)?; - writeln!(f, "{prefix} - id : {id}")?; - for i in input { - if let Some(from) = &i.from { - writeln!(f, "{prefix} - from : '{}' <- '{}'", i.name, from)?; - } - } - if let Some(location) = location { - writeln!(f, "{prefix} - to : {location}")?; - } - - // Do next - print_elem(f, next, prefix) - }, Elem::Next => { writeln!(f, "{}next", prefix) }, - Elem::Stop(returns) => { - writeln!( - f, - "{}stop{}", - prefix, - if !returns.is_empty() { - format!(" ", write_iter!(returns.iter().map(|data| format!("'{}'", data.name)), " or ")) - } else { - String::new() - } - ) + Elem::Stop => { + writeln!(f, "{}stop", prefix) }, } } + + + + /***** HELPERS *****/ /// Writes two display things successively. struct Pair(D1, D2); @@ -176,6 +151,10 @@ impl Display for Indent { } } + + + + /***** FORMATTERS *****/ /// Capable of printing the [`Workflow`] to some writer. #[derive(Debug)] @@ -190,16 +169,17 @@ impl Display for WorkflowFormatter<'_> { // Print global metadata if !self.wf.metadata.is_empty() { + writeln!(f, "{} - id : {:?}", Indent(4), self.wf.id)?; + writeln!(f, "{} - user : {:?}", Indent(4), self.wf.user.id)?; writeln!( f, - "{}{}", + "{} - metadata: {:?}", Indent(4), write_iter!( self.wf.metadata.iter().map(|metadata| format!( - "#{}.{}{}", - metadata.owner, + "{:?}{}", metadata.tag, - if let Some((assigner, signature)) = &metadata.signature { format!("#{assigner}:{signature}") } else { String::new() } + if let Some((assigner, signature)) = &metadata.signature { format!("{}:{}", assigner.id, signature) } else { String::new() } )), ", " ) diff --git a/migrations/.keep b/migrations/.keep deleted file mode 100644 index e69de29..0000000 diff --git a/migrations/2023-10-25-162029_create_policies/down.sql b/migrations/2023-10-25-162029_create_policies/down.sql deleted file mode 100644 index 52583a1..0000000 --- a/migrations/2023-10-25-162029_create_policies/down.sql +++ /dev/null @@ -1,3 +0,0 @@ --- This file should undo anything in `up.sql` -DROP TABLE policies; -DROP TABLE active_version; \ No newline at end of file diff --git a/migrations/2023-10-25-162029_create_policies/up.sql b/migrations/2023-10-25-162029_create_policies/up.sql deleted file mode 100644 index 51529ef..0000000 --- a/migrations/2023-10-25-162029_create_policies/up.sql +++ /dev/null @@ -1,18 +0,0 @@ --- Your SQL goes here - CREATE TABLE policies ( - version bigint PRIMARY KEY NOT NULL, - description Text NOT NULL, - version_description Text NOT NULL, - creator Text NOT NULL, - created_at BigInt NOT NULL, - content Text NOT NULL - ); - --- Your SQL goes here - CREATE TABLE active_version ( - version bigint NOT NULL, - activated_on DATETIME NOT NULL, - activated_by TEXT NOT NULL, - FOREIGN KEY(version) REFERENCES policies(version) - PRIMARY KEY (version, activated_on) - ); \ No newline at end of file diff --git a/migrations/2024-01-03-120312_add_deactivation/down.sql b/migrations/2024-01-03-120312_add_deactivation/down.sql deleted file mode 100644 index 62d3141..0000000 --- a/migrations/2024-01-03-120312_add_deactivation/down.sql +++ /dev/null @@ -1,6 +0,0 @@ --- This file should undo anything in `up.sql` -ALTER TABLE active_version - DROP COLUMN deactivated_on DATETIME NULL; - -ALTER TABLE active_version - DROP COLUMN deactivated_by TEXT NULL; \ No newline at end of file diff --git a/migrations/2024-01-03-120312_add_deactivation/up.sql b/migrations/2024-01-03-120312_add_deactivation/up.sql deleted file mode 100644 index 586107b..0000000 --- a/migrations/2024-01-03-120312_add_deactivation/up.sql +++ /dev/null @@ -1,6 +0,0 @@ --- Your SQL goes here -ALTER TABLE active_version - ADD deactivated_on DATETIME NULL; - -ALTER TABLE active_version - ADD deactivated_by TEXT NULL; diff --git a/migrations/2024-01-03-150043_add_policy_base_def/down.sql b/migrations/2024-01-03-150043_add_policy_base_def/down.sql deleted file mode 100644 index deba5cb..0000000 --- a/migrations/2024-01-03-150043_add_policy_base_def/down.sql +++ /dev/null @@ -1,3 +0,0 @@ --- This file should undo anything in `up.sql` -ALTER TABLE policies - DROP COLUMN reasoner_connector_context; \ No newline at end of file diff --git a/migrations/2024-01-03-150043_add_policy_base_def/up.sql b/migrations/2024-01-03-150043_add_policy_base_def/up.sql deleted file mode 100644 index eed66bc..0000000 --- a/migrations/2024-01-03-150043_add_policy_base_def/up.sql +++ /dev/null @@ -1,3 +0,0 @@ --- Your SQL goes here -ALTER TABLE policies - ADD reasoner_connector_context TEXT NOT NULL; diff --git a/policy/eflint/interface/main.eflint b/policy/eflint/interface/main.eflint deleted file mode 100644 index b1eb13e..0000000 --- a/policy/eflint/interface/main.eflint +++ /dev/null @@ -1,36 +0,0 @@ -// MAIN.eflint -// by Lut99 -// -// Created: -// 30 Nov 2023, 11:13:01 -// Last edited: -// 19 Dec 2023, 18:35:40 -// Auto updated? -// Yes -// -// Description: -// "Entrypoint" file that collects all eFLINT policy interface into one file. -// - - -///// DEFINITIONS ///// -// Add in Brane definitions (which is basically the world) -#require "state.eflint". - -// Add in the workflow definitions -#require "workflow_base.eflint". - -// Add in the metadata -#require "metadata.eflint". - -// Add in the queries -#require "queries.eflint" - - - -///// ASSERTIONS ///// -// Add state assertions -// #require "state_assert.eflint". - -// Add workflow assertions -// #require "workflow_assert.eflint". diff --git a/policy/eflint/interface/metadata.eflint b/policy/eflint/interface/metadata.eflint deleted file mode 100644 index 2c58159..0000000 --- a/policy/eflint/interface/metadata.eflint +++ /dev/null @@ -1,36 +0,0 @@ -// METADATA.eflint -// by Lut99 -// -// Created: -// 30 Nov 2023, 11:23:48 -// Last edited: -// 30 Nov 2023, 11:48:36 -// Auto updated? -// Yes -// -// Description: -// Defines metadata that can be assigned to most parts of the policy. -// - -#require "state.eflint". -#require "workflow_base.eflint". - - -// Defines a cryptographic signature that proves a user saw something. Given as a pair of the person signing it and the physical signature. -Fact signature Identified by user * string. -// Predicate over `signature`s which denotes which of them have been validated to be correct by the checker. -Fact signature-valid Identified by signature. - -// An arbitrary piece of information related to various pieces of information to provide them with metadata. Given as a pair of the person defining/owning it and the information itself. -Fact tag Identified by user * string. - -// Relates a `tag` to a `signature`. -Fact metadata Identified by tag * signature. -// States that a tag has been assigned to a particular workflow by the person providing the signature. -Fact workflow-metadata Identified by workflow * metadata. -// States that a tag has been assigned to a particular node in a workflow by the person providing the signature. -Fact node-metadata Identified by node * metadata. -// States that a tag has been assigned to a particular asset by the person providing the signature. -Fact asset-metadata Identified by asset * metadata. -// States that a tag has been assigned to a particular user by the person providing the signature. -Fact user-metadata Identified by user * metadata. diff --git a/policy/eflint/interface/queries.eflint b/policy/eflint/interface/queries.eflint deleted file mode 100644 index 5e5cecb..0000000 --- a/policy/eflint/interface/queries.eflint +++ /dev/null @@ -1,25 +0,0 @@ -// QUERIES.eflint -// by Lut99 -// -// Created: -// 07 Dec 2023, 16:51:58 -// Last edited: -// 07 Dec 2023, 17:00:50 -// Auto updated? -// Yes -// -// Description: -// Defines the queries that can be asked by the checker to the policy. -// - -#require "workflow_base.eflint" - - -// Asks if a workflow as a whole is OK. -Fact workflow-to-execute Identified by workflow. -// Asks if a particular task is authorised by a checker in the context of a workflow. -Fact task-to-execute Identified by task. -// Asks if a particular input to a node can be transferred to the domain executing that node. -Fact dataset-to-transfer Identified by node-input. -// Asks if the result of a workflow may be transferred to the receiving party. -Fact result-to-transfer Identified by workflow-result-recipient. diff --git a/policy/eflint/interface/state.eflint b/policy/eflint/interface/state.eflint deleted file mode 100644 index 39638fd..0000000 --- a/policy/eflint/interface/state.eflint +++ /dev/null @@ -1,30 +0,0 @@ -// STATE.eflint -// by Lut99 -// -// Created: -// 30 Nov 2023, 11:15:54 -// Last edited: -// 06 Dec 2023, 15:17:25 -// Auto updated? -// Yes -// -// Description: -// Defines Facts that we use to denote the instance/reasoner's state. Can -// be thought of as a Brane "core ontology". -// - - -// Defines users that exist in the system (by ID). -Fact user. -// Defines a user that may be considered for planning (i.e., they can act as a worker). -Fact domain Identified by user. - -// Defines an asset to the system. -Fact asset. -// States that a particular user has access to a particular asset. -// -// Observations: -// - If the `user` happens to be a `domain`, then the user may use their access to perform tasks on the data within Brane. -Fact asset-access Identified by asset * user. -// Defines assets that can be used in function-position in tasks (see `workflow_base.eflint`). -Fact code Identified by asset. diff --git a/policy/eflint/interface/state_assert.eflint b/policy/eflint/interface/state_assert.eflint deleted file mode 100644 index c1cbcae..0000000 --- a/policy/eflint/interface/state_assert.eflint +++ /dev/null @@ -1,24 +0,0 @@ -// STATE ASSERT.eflint -// by Lut99 -// -// Created: -// 06 Dec 2023, 11:59:57 -// Last edited: -// 06 Dec 2023, 15:17:27 -// Auto updated? -// Yes -// -// Description: -// Defines assertions that enforce particular assumptions on state -// predicates. -// - -#require "state.eflint". - - -// Assert that users exist when mentioned somewhere. -Invariant user-exists When - (Forall domain : domain.user) && (Forall asset-access : asset-access.user). -// Assert that assets exist when mentioned somewhere. -Invariant asset-exists When - (Forall asset-access : asset-access.asset) && (Forall code : code.asset). diff --git a/policy/eflint/interface/tests/test_workflow_ext.eflint b/policy/eflint/interface/tests/test_workflow_ext.eflint deleted file mode 100644 index b18eb24..0000000 --- a/policy/eflint/interface/tests/test_workflow_ext.eflint +++ /dev/null @@ -1,63 +0,0 @@ -// TEST WORKFLOW EXT.eflint -// by Lut99 -// -// Created: -// 30 Nov 2023, 14:20:44 -// Last edited: -// 06 Dec 2023, 15:36:32 -// Auto updated? -// Yes -// -// Description: -// File to test if the other files work. -// - -#require "../workflow_ext.eflint". - - -///// TESTS ///// -+asset(A). -+asset(B). -+asset(C). -+asset(D). - -+asset(F). -+code(asset(F)). -+asset(G). -+code(asset(G)). - -+user(Amy). -+domain(user(Amy)). -+user(Bob). -+domain(user(Bob)). -+user(Cho). -+domain(user(Cho)). - -+workflow(W). - -+node(workflow(W), X). -+task(node(workflow(W), X)). -+node-input(node(workflow(W), X), asset(F)). -+node-input-from(node-input(node(workflow(W), X), asset(F)), domain(user(Central))). -+function(node-input(node(workflow(W), X), asset(F)), "f"). -+node-input(node(workflow(W), X), asset(A)). -+node-input-from(node-input(node(workflow(W), X), asset(A)), domain(user(Amy))). -+node-output(node(workflow(W), X), asset(B)). -+node-at(node(workflow(W), X), domain(user(Amy))). - -+node(workflow(W), Y). -+task(node(workflow(W), Y)). -+node-input(node(workflow(W), Y), asset(G)). -+node-input-from(node-input(node(workflow(W), Y), asset(G)), domain(user(Central))). -+function(node-input(node(workflow(W), Y), asset(G)), "g"). -+node-input(node(workflow(W), Y), asset(B)). -+node-input-from(node-input(node(workflow(W), Y), asset(B)), domain(user(Amy))). -+node-output(node(workflow(W), Y), asset(C)). -+node-at(node(workflow(W), Y), domain(user(Bob))). - -+node(workflow(W), Z). -+commit(node(workflow(W), Z)). -+node-input(node(workflow(W), Z), asset(C)). -+node-input-from(node-input(node(workflow(W), Z), asset(C)), domain(user(Bob))). -+node-output(node(workflow(W), Z), asset(D)). -+node-at(node(workflow(W), Z), domain(user(Cho))). diff --git a/policy/eflint/interface/workflow_assert.eflint b/policy/eflint/interface/workflow_assert.eflint deleted file mode 100644 index 017c2f7..0000000 --- a/policy/eflint/interface/workflow_assert.eflint +++ /dev/null @@ -1,83 +0,0 @@ -// WORKFLOW ASSERT.eflint -// by Lut99 -// -// Created: -// 30 Nov 2023, 11:55:00 -// Last edited: -// 13 Dec 2023, 16:11:25 -// Auto updated? -// Yes -// -// Description: -// Defines assertions that enforce particular assumptions on workflow -// predicates. -// - -#require "workflow_base.eflint". - - -// Asserts that users exist when mentioned somewhere. -Invariant user-exists-workflow When - (Forall workflow-result-recipient : workflow-result-recipient.user). -// Asserts that domains exist when mentioned somewhere. -Invariant domain-exists-workflow When - (Forall task-at : task-at.domain). -// Asserts that assets exist when mentioned somewhere. -Invariant asset-exists-workflow When - (Forall workflow-result : workflow-result.asset) && - (Forall node-input : node-input.asset) && (Forall node-output : node-output.asset). -// Asserts that codes exist when mentioned somewhere. -Invariant code-exists-workflow When - (Forall task-code : task-code.code). - -// Asserts that workflows exist when mentioned somewhere. -Invariant workflow-exists When - (Forall workflow-result : workflow-result.workflow) && (Forall node : node.workflow). -// Asserts that workflow-results exist when mentioned somewhere. -Invariant workflow-result-exists When - (Forall workflow-result-recipient : workflow-result-recipient.workflow-result). -// Asserts that nodes exist when mentioned somewhere. -Invariant node-exists When - (Forall node-input : node-input.node) && (Forall node-output : node-output.node) && - (Forall node-at : node-at.node) && - (Forall task : task.node) && (Forall commit : commit.node) && (Forall loop : loop.node) && (Forall loop-body : loop-body.node). -// Asserts that loops exist when mentioned somewhere. -Invariant loop-exists When - (Forall loop-body : loop-body.loop). -// // Asserts that node-depends-ons exist when mentioned somewhere -// Invariant node-depends-on-exists When -// (Forall node-depends-on-ensured : node-depends-on-ensured.node-depends-on). -// // Asserts that tasks exist when mentioned somewhere -// Invariant task-exists When -// (Forall task-code : task-code.task). - -// Asserts that every workflow has at most 1 result that is being received by someone. -Invariant workflow-at-most-one-result-recipient When - (Forall workflow : Count(Foreach asset, user : workflow-result-recipient(workflow-result(workflow, asset), user) When workflow-result-recipient(workflow-result(workflow, asset), user)) <= 1). - -// Asserts that every input has exactly one source location. -Invariant node-input-exactly-one-from When - (Forall node-input : Count(Foreach domain : node-input-from(node-input, domain) When node-input-from(node-input, domain)) == 1). -// Asserts that no node produces its own output. -Invariant node-not-recursive-input-output When - (Forall node : Not(Exists asset : (node-input(node, asset) && node-output(node, asset)))). -// Asserts that every node has at most 1 output. -Invariant node-at-most-one-output When - (Forall node : Count(Foreach asset : node-output(node, asset) When node-output(node, asset)) <= 1). -// Asserts that every node has exactly 1 assigned domain. -Invariant node-exactly-one-at When - (Forall node : Count(Foreach domain : node-at(node, domain) When node-at(node, domain)) == 1). - -// Asserts that every task has exactly 1 function. -Invariant task-exactly-one-function When - (Forall task : Count(Foreach function : function When (((function.node-input).node) == (task.node))) <= 1). -// Asserts that every function is only linking code. -Invariant function-over-code When - (Forall function : code((function.node-input).asset)). - -// Asserts every loop has exactly one body -Invariant loop-exactly-one-body When - (Forall loop : Count(Foreach node : loop-body(loop, node) When loop-body(loop, node)) == 1). -// Asserts that the input of a loop matches the first node in the loop's body -Invariant loop-input-equals-body-input When - (Forall loop-body : (Forall asset : (node-input((loop-body.loop).node, asset) && node-input(loop-body.node, asset)) When (node-input((loop-body.loop).node, asset) || node-input(loop-body.node, asset)))). diff --git a/policy/eflint/interface/workflow_base.eflint b/policy/eflint/interface/workflow_base.eflint deleted file mode 100644 index 5272ad6..0000000 --- a/policy/eflint/interface/workflow_base.eflint +++ /dev/null @@ -1,50 +0,0 @@ -// WORKFLOW BASE.eflint -// by Lut99 -// -// Created: -// 30 Nov 2023, 11:14:15 -// Last edited: -// 07 Dec 2023, 13:32:48 -// Auto updated? -// Yes -// -// Description: -// Defines the `Fact`s that we use to express a Checker Workflow in. -// - -#require "state.eflint". - - -///// DEFINITIONS ///// -// Declares a particular workflow. -Fact workflow. -// Defines a dataset that is the product of this workflow. -Fact workflow-result Identified by workflow * asset. -// Names a dataset that is received by the submitter of the workflow. -Fact workflow-result-recipient Identified by workflow-result * user. - -// Declares a node in a workflow, which is either an executable `task` or the publication of a dataset (`commit`). Given as a pair of a workflow and the node ID, as it models a call to something and that's always in the context of a particular workflow. -Fact node Identified by workflow * string. -// Relates _data_ as _possible_ input to a node. Whether this data is actually given as input depends on dynamic control flow. See `node-input-ensured` to find out which inputs are given in \*all\* control flow paths. -Fact node-input Identified by node * asset. -// Relates an input to a domain where it will be downloaded from. Can be at most 1. -Fact node-input-from Identified by node-input * domain. -// Relates asset as output to a node. Can be at most 1. -Fact node-output Identified by node * asset. -// Defines where a particular node is executed. Can be at most 1. -Fact node-at Identified by node * domain. - -// Declares a particular call of a function. -Fact task Identified by node. -// Determines that a particular input asset acts as the code for that asset. Given as the input and the name of the function to call in that input. -Fact function Identified by node-input * string. - -// Declares the publication of a particular dataset. -Fact commit Identified by node. - -// Declares a node that is a stand-in for a subgraph that is repeatedly executed. -// -// The input of the loop acts as the input of the `loop-body`'s first node, and the output corresponds to the output of the `loop-body`'s last node(s). -Fact loop Identified by node. -// Links a `loop` node to the body that is repeatedly executed. -Fact loop-body Identified by loop * node. diff --git a/policy/eflint/interface/workflow_ext.eflint b/policy/eflint/interface/workflow_ext.eflint deleted file mode 100644 index 4a21b73..0000000 --- a/policy/eflint/interface/workflow_ext.eflint +++ /dev/null @@ -1,64 +0,0 @@ -// WORKFLOW EXT.eflint -// by Lut99 -// -// Created: -// 30 Nov 2023, 11:33:50 -// Last edited: -// 13 Dec 2023, 16:13:19 -// Auto updated? -// Yes -// -// Description: -// Defines Facts that can be derived from every workflow. Not part of the -// standard interface, but more like standard library. -// - -#require "workflow_base.eflint". - - -// Automatically derives that a node exists if a task, commit or loop exists. -Extend Fact node - Derived from (Foreach task : task.node) - Derived from (Foreach commit : commit.node) - Derived from (Foreach loop : loop.node). - -// Unifies inputs and outputs of a node. -Fact node-asset Identified by node * asset - Holds when (node-input(node, asset) || node-output(node, asset)). -// Relates _code_ as what is being executed in a task. Can be at most 1. -Fact task-code Identified by task * code - Derived from (Foreach node-input : task-code(task(node-input.node), code(node-input.asset)) When (task(node-input.node) && code(node-input.asset))). - -// Compute the assets on all domains that compute stuff -Extend Fact asset-access - Derived from (Foreach node-input-from : asset-access((node-input-from.node-input).asset, (node-input-from.domain).user)) - Derived from (Foreach node, asset, domain : asset-access(asset, domain.user) When ((node-asset(node, asset) || (task(node) && task-code(task(node), code(asset)))) && node-at(node, domain))). - -// Indicates that the input of the first node \*might\* depend on the output of the second. Whether this is actually the case depends on dynamic control flow. -Fact node-depends-on Identified by node1 * node2 - Conditioned by ((node1.workflow) == (node2.workflow)) - Holds when node1 == node2 - Holds when (Exists asset : node-input(node1, asset) && node-output(node2, asset)) - Holds when (Exists node3 : node-depends-on(node1, node3) && node-depends-on(node3, node2)). -// Derives a concrete relation of all the assets upon which a node \*might\* depend. -Fact node-depends-on-asset Identified by node * asset - Derived from (Foreach node-depends-on, asset : - node-depends-on-asset(node-depends-on.node1, asset) - When node-input(node-depends-on.node2, asset) - ). -// Derives a concrete relation of all the domains upon which a node \*might\* depend (i.e., where it's executed and gets all its input from). -Fact node-depends-on-domain Identified by node * domain - Derived from (Foreach node-at : node-depends-on-domain(node-at.node, node-at.domain)) - Derived from (Foreach node-input-from : node-depends-on-domain((node-input-from.node-input).node, node-input-from.domain)). - -// Derives all nodes that are dependencies of particular assets -Fact asset-dependency-of-node Identified by asset * node - Derived from (Foreach node-depends-on-asset : asset-dependency-of-node(node-depends-on-asset.asset, node-depends-on-asset.node)). -// Derives all the domains to which an asset downstreams. -Fact asset-downstream-domain Identified by asset * domain - // An asset touches the domains from which it is downloaded - Derived from (Foreach node-input-from : asset-downstream-domain((node-input-from.node-input).asset, node-input-from.domain)) - // An asset touches the domain of a task it's input or output of - Derived from (Foreach node-asset, domain : asset-downstream-domain(node-asset.asset, domain) When node-at(node-asset.node, domain)) - // An asset touches the domains of all nodes that depend on it (since they need to see (a result of) it somehow) - Derived from (Foreach asset-dependency-of-node, domain : asset-downstream-domain(asset-dependency-of-node.asset, domain) When node-at(asset-dependency-of-node.node, domain)). diff --git a/src/auth.rs b/src/auth.rs deleted file mode 100644 index f7fbbbd..0000000 --- a/src/auth.rs +++ /dev/null @@ -1,147 +0,0 @@ -use std::collections::HashMap; -use std::fs; - -use auth_resolver::{AuthContext, AuthResolver, AuthResolverError}; -use base64ct::Encoding as _; -use jsonwebtoken::jwk::{AlgorithmParameters, Jwk, JwkSet}; -use jsonwebtoken::{DecodingKey, Header, Validation}; -use log::{debug, info}; -use serde::Deserialize; -use warp::http::{HeaderMap, HeaderValue}; - -#[async_trait::async_trait] -pub trait KeyResolver { - async fn resolve_key(&self, header: &Header) -> Result; -} - -pub struct KidResolver { - jwk_store: JwkSet, -} - -impl KidResolver { - pub fn new(key_set_loc: &str) -> Result { - let r = fs::read_to_string(key_set_loc) - .map_err(|err| AuthResolverError::new(format!("Could not load jwk set from location: {}; {}", key_set_loc, err)))?; - let keyfile: JwkSet = serde_json::from_str(&r).map_err(|err| AuthResolverError::new(format!("Could not load parse jwk set: {}", err)))?; - - Ok(Self { jwk_store: keyfile }) - } -} - -#[async_trait::async_trait] -impl KeyResolver for KidResolver { - async fn resolve_key(&self, header: &Header) -> Result { - let kid = header.kid.as_ref().ok_or_else(|| AuthResolverError::new("No kid present in header".into()))?; - - // Get the key - let key: &Jwk = match self.jwk_store.find(kid) { - Some(key) => key, - None => return Err(AuthResolverError::new(format!("Could not find key for kid: {}", kid))), - }; - // match self.jwk_store.find(&kid) { - // Some(key) => DecodingKey::from_jwk(key) - // .map_err(|err| AuthResolverError::new(format!("Could not transform jwk ({}) into DecodingKey: {}", kid, err))), - // None => Err(AuthResolverError::new(format!("Could not find key for kid: {}", kid))), - // } - - // Extract the secret from it - let secret: Vec = if let AlgorithmParameters::OctetKey(oct) = &key.algorithm { - match base64ct::Base64Url::decode_vec(&oct.value) { - Ok(val) => val, - Err(err) => return Err(AuthResolverError::new(format!("Could not decode secret key as URL-safe base64: {err}"))), - } - } else { - return Err(AuthResolverError::new("Unsupported key type".into())); - }; - - // Now return that as decoding key - Ok(DecodingKey::from_secret(&secret)) - } -} - -pub struct JwtResolver { - config: JwtConfig, - key_resolver: KR, -} - -#[derive(Deserialize)] -pub struct JwtConfig { - initiator_claim: String, -} - -impl JwtResolver -where - KR: KeyResolver + Sync, -{ - #[inline] - pub fn new(config: JwtConfig, key_resolver: KR) -> Result> { Ok(JwtResolver { config, key_resolver }) } - - pub fn extract_jwt(&self, auth_header: Option<&HeaderValue>) -> Result { - let header_val: &str = match auth_header { - Some(v) => match v.to_str() { - Ok(v) => v, - Err(_) => return Err(AuthResolverError::new("Invalid authorization header".into())), - }, - None => { - return Err(AuthResolverError::new("Authorization header not present".into())); - }, - }; - - let parts = header_val.splitn(2, ' ').collect::>(); - - if parts[0] != "Bearer" { - return Err(AuthResolverError::new("Invalid authorization header".into())); - } - - Ok(parts[1].into()) - } -} - -#[async_trait::async_trait] -impl AuthResolver for JwtResolver -where - KR: KeyResolver + Sync + Send, -{ - async fn authenticate(&self, headers: HeaderMap) -> Result { - info!("Handling JWT authentication for incoming request"); - - let raw_jwt = self.extract_jwt(headers.get("Authorization"))?; - debug!("Received JWT: '{raw_jwt}'"); - - let header = jsonwebtoken::decode_header(&raw_jwt).map_err(|err| AuthResolverError::new(format!("Could not parse header: {}", err)))?; - debug!("JWT header: '{header:?}'"); - - debug!("Resolving key in keystore..."); - let decoding_key = self.key_resolver.resolve_key(&header).await?; - let validation = Validation::new(header.alg); - debug!("Validating JWT with {:?}...", header.alg); - let result = jsonwebtoken::decode::>(&raw_jwt, &decoding_key, &validation) - .map_err(|err| AuthResolverError::new(format!("Could not validate jwt: {}", err)))?; - debug!("Validating OK"); - - match result.claims.get(&self.config.initiator_claim) { - Some(initiator) => match initiator { - serde_json::Value::Number(v) => Ok(AuthContext { initiator: v.to_string(), system: "TODO implement!".into() }), - serde_json::Value::String(v) => Ok(AuthContext { initiator: v.clone(), system: "TODO implement!".into() }), - _ => Err(AuthResolverError::new(format!( - "Invalid type for initiator claim (only string or number allowed): {}", - self.config.initiator_claim - ))), - }, - None => Err(AuthResolverError::new(format!("Missing initiator claim: {}", self.config.initiator_claim))), - } - } -} - -pub struct MockAuthResolver { - ctx: AuthContext, -} - -impl MockAuthResolver { - pub fn new(initiator: String, system: String) -> Self { Self { ctx: AuthContext { initiator, system } } } -} - -#[async_trait::async_trait] -impl AuthResolver for MockAuthResolver { - async fn authenticate(&self, _: HeaderMap) -> Result { Ok(self.ctx.clone()) } -} diff --git a/src/bin/eflint.rs b/src/bin/eflint.rs deleted file mode 100644 index e4d4e2c..0000000 --- a/src/bin/eflint.rs +++ /dev/null @@ -1,125 +0,0 @@ -// MAIN.rs -// by Lut99 -// -// Created: -// 09 Jan 2024, 13:32:03 -// Last edited: -// 07 Feb 2024, 18:08:43 -// Auto updated? -// Yes -// -// Description: -//! Entrypoint to the main `policy-reasoner` binary. -// - -pub mod implementation; - -use std::env; -use std::fs::File; - -use clap::Parser; -use error_trace::ErrorTrace as _; -use humanlog::{DebugMode, HumanLogger}; -#[cfg(not(feature = "leak-public-errors"))] -use implementation::eflint::EFlintLeakNoErrors; -#[cfg(feature = "leak-public-errors")] -use implementation::eflint::EFlintLeakPrefixErrors; -use implementation::eflint::EFlintReasonerConnector; -use implementation::interface::Arguments; -use log::{error, info}; -use policy_reasoner::auth::{JwtConfig, JwtResolver, KidResolver}; -use policy_reasoner::logger::FileLogger; -use policy_reasoner::sqlite::SqlitePolicyDataStore; -use srv::Srv; - -/***** HELPER FUNCTIONS *****/ -fn get_pauth_resolver() -> JwtResolver { - let kid_resolver = KidResolver::new("./examples/config/jwk_set_expert.json").unwrap(); - let r = File::open("./examples/config/jwt_resolver.yaml").unwrap(); - let jwt_cfg: JwtConfig = serde_yaml::from_reader(r).unwrap(); - JwtResolver::new(jwt_cfg, kid_resolver).unwrap() -} -fn get_dauth_resolver() -> JwtResolver { - let kid_resolver = KidResolver::new("./examples/config/jwk_set_delib.json").unwrap(); - let r = File::open("./examples/config/jwt_resolver.yaml").unwrap(); - let jwt_cfg: JwtConfig = serde_yaml::from_reader(r).unwrap(); - JwtResolver::new(jwt_cfg, kid_resolver).unwrap() -} - -/***** PLUGINS *****/ -/// The plugin used to do the audit logging. -type AuditLogPlugin = FileLogger; - -/// The plugin used to do authentication for the policy expert API. -type PolicyAuthResolverPlugin = JwtResolver; -/// The plugin used to do authentication for the deliberation API. -type DeliberationAuthResolverPlugin = JwtResolver; - -/// The plugin used to interact with the policy store. -type PolicyStorePlugin = SqlitePolicyDataStore; - -/// The plugin used to interact with the backend reasoner. -#[cfg(feature = "leak-public-errors")] -type ReasonerConnectorPlugin = EFlintReasonerConnector; -#[cfg(not(feature = "leak-public-errors"))] -type ReasonerConnectorPlugin = EFlintReasonerConnector; - -/// The plugin used to resolve policy input state. -#[cfg(feature = "brane-api-resolver")] -type StateResolverPlugin = policy_reasoner::state::BraneApiResolver; -#[cfg(not(feature = "brane-api-resolver"))] -type StateResolverPlugin = policy_reasoner::state::FileStateResolver; - -/***** ENTRYPOINT *****/ -#[tokio::main] -async fn main() { - // Parse arguments - let args = Arguments::parse(); - - // Setup a logger - if let Err(err) = HumanLogger::terminal(if args.trace { DebugMode::Full } else { DebugMode::Debug }).init() { - eprintln!("WARNING: Failed to setup logger: {err} (no logging for this session)"); - } - info!("{} - v{}", env!("CARGO_PKG_NAME"), env!("CARGO_PKG_VERSION")); - - // Handle help - let mut exit: bool = false; - if args.help_reasoner_connector { - println!("{}", ReasonerConnectorPlugin::help('r', "reasoner-connector")); - exit = true; - } - if args.help_state_resolver { - println!("{}", StateResolverPlugin::help('s', "state-resolver")); - exit = true; - } - if exit { - std::process::exit(0); - } - - // Initialize the plugins - let log_identifier = format!("{binary} v{version}", binary = env!("CARGO_BIN_NAME"), version = env!("CARGO_PKG_VERSION")); - let logger: AuditLogPlugin = FileLogger::new(log_identifier, "./audit-log.log"); - let pauthresolver: PolicyAuthResolverPlugin = get_pauth_resolver(); - let dauthresolver: DeliberationAuthResolverPlugin = get_dauth_resolver(); - let pstore: PolicyStorePlugin = SqlitePolicyDataStore::new("./data/policy.db"); - let rconn: ReasonerConnectorPlugin = match ReasonerConnectorPlugin::new(args.reasoner_connector.unwrap_or_else(String::new)) { - Ok(rconn) => rconn, - Err(err) => { - error!("{}", err.trace()); - std::process::exit(1); - }, - }; - - let sresolve: StateResolverPlugin = match StateResolverPlugin::new(args.state_resolver.unwrap_or_else(String::new)) { - Ok(sresolve) => sresolve, - Err(err) => { - error!("{}", err.trace()); - std::process::exit(1); - }, - }; - - // Run them! - let server = Srv::new(args.address, logger, rconn, pstore, sresolve, pauthresolver, dauthresolver); - - server.run().await; -} diff --git a/src/bin/implementation/eflint.rs b/src/bin/implementation/eflint.rs deleted file mode 100644 index 802c2f9..0000000 --- a/src/bin/implementation/eflint.rs +++ /dev/null @@ -1,588 +0,0 @@ -use std::collections::HashMap; -use std::error; -use std::fmt::{Display, Formatter, Result as FResult}; - -use audit_logger::{ConnectorContext, ConnectorWithContext, ReasonerConnectorAuditLogger, SessionedConnectorAuditLogger}; -use eflint_json::spec::auxillary::Version; -use eflint_json::spec::{ - ConstructorInput, Expression, ExpressionConstructorApp, ExpressionPrimitive, Phrase, PhraseCreate, PhraseResult, Request, RequestCommon, - RequestPhrases, -}; -use log::{debug, error, info}; -use nested_cli_parser::map_parser::MapParser; -use nested_cli_parser::{NestedCliParser as _, NestedCliParserHelpFormatter}; -use policy::{Policy, PolicyContent}; -use reasonerconn::{ReasonerConnError, ReasonerConnector, ReasonerResponse}; -use state_resolver::State; -use workflow::spec::Workflow; - -/***** HELPER MACROS *****/ -/// Shortcut for creating an eFLINT JSON Specification [`Phrase::Create`]. -/// -/// # Arguments -/// - `inst`: A single eFLINT [`Expression`] that is an instance expression determining what to create; i.e., `foo(Amy, Bob)` in `+foo(Amy, Bob).`. -/// -/// # Returns -/// A new [`Phrase::Create`] (or rather, the Rust code to create it). -macro_rules! create { - ($inst:expr) => { - Phrase::Create(PhraseCreate { operand: $inst }) - }; -} - -/// Shortcut for creating an eFLINT JSON Specification [`Expression::ConstructorApp`]. -/// -/// # Arguments -/// - _array syntax_ -/// - `id`: The (string) identifier of the relation to construct; i.e., `foo` in `foo(Amy, Bob)`. -/// - `args...`: Zero or more addinitional [`Expression`]s that make up the arguments of the constructor application; i.e., `Amy` or `Bob` in `foo(Amy, Bob)`. -/// -/// # Returns -/// A new [`Expression::ConstructorApp`] (or rather, the Rust code to create it). -macro_rules! constr_app { - ($id:expr $(, $args:expr)* $(,)?) => { - Expression::ConstructorApp(ExpressionConstructorApp { - identifier: ($id).into(), - operands: ConstructorInput::ArraySyntax(vec![ $($args),* ]), - }) - }; -} - -/// Shortcut for creating an eFLINT JSON Specification [`Expression::Primitive(ExpressionPrimitive::String)`]. -/// -/// # Arguments -/// - `val`: The string value to put in the string primitive. Note that this is automatically `into()`d; so passing a `&str` will work, for example. -/// -/// # Returns -/// A new [`Expression::Primitive(ExpressionPrimitive::String)`] (or rather, the Rust code to create it). -macro_rules! str_lit { - ($val:expr) => { - Expression::Primitive(ExpressionPrimitive::String(($val).into())) - }; -} - -/***** CONSTANTS *****/ -/// The identifier used for this connector backend. -pub const EFLINT_JSON_ID: &'static str = "eflint-json"; - -// Externalized "constants" -/// The entire base specification, already serialized as eFLINT JSON. See `build.rs` to find how the `BASE_DEFS_EFLINT_JSON` environment variable is populated. -const JSON_BASE_SPEC: &'static str = include_str!(env!("BASE_DEFS_EFLINT_JSON")); -/// A hash of the entire base specification, precomputed by `build.rs`. -const JSON_BASE_SPEC_HASH: &'static str = env!("BASE_DEFS_EFLINT_JSON_HASH"); - -/***** ERRORS *****/ -/// Main error that originates from the [`EFlintReasonerConnector`]. -#[derive(Debug)] -pub enum Error { - /// Failed to parse the CLI arguments to the eFLINT reasoner connector. - CliArgumentsParse { raw: String, err: nested_cli_parser::map_parser::Error }, - /// Failed to construct the nested ErrorHandler plugin. - ErrorHandler { name: &'static str, err: E }, -} -impl Display for Error { - fn fmt(&self, f: &mut Formatter<'_>) -> FResult { - use Error::*; - match self { - CliArgumentsParse { raw, .. } => write!(f, "Failed to parse '{raw}' as CLI argument string for an EFlintReasonerConnector"), - ErrorHandler { name, .. } => write!(f, "Failed to initialize error handler plugin '{name}'"), - } - } -} -impl error::Error for Error { - fn source(&self) -> Option<&(dyn error::Error + 'static)> { - use Error::*; - match self { - CliArgumentsParse { err, .. } => Some(err), - ErrorHandler { err, .. } => Some(err), - } - } -} - -/// Error that originates from the [`EFlintLeakPrefixErrors`]. -#[derive(Debug)] -pub enum EFlintLeakPrefixErrorsError { - /// Failed to parse the CLI arguments to the EFlintLeakPrefixErrors. - CliArgumentsParse { raw: String, err: nested_cli_parser::map_parser::Error }, -} -impl Display for EFlintLeakPrefixErrorsError { - fn fmt(&self, f: &mut Formatter<'_>) -> FResult { - use EFlintLeakPrefixErrorsError::*; - match self { - CliArgumentsParse { raw, .. } => write!(f, "Failed to parse '{raw}' as CLI argument string for an EFlintLeakPrefixErrors"), - } - } -} -impl error::Error for EFlintLeakPrefixErrorsError { - fn source(&self) -> Option<&(dyn error::Error + 'static)> { - use EFlintLeakPrefixErrorsError::*; - match self { - CliArgumentsParse { err, .. } => Some(err), - } - } -} - -/***** ERROR HANDLERS *****/ -pub trait EFlintErrorHandler { - type Error: error::Error; - - fn new(cli_args: &HashMap>) -> Result - where - Self: Sized; - - #[inline] - fn extract_errors(&self, _: Option<&PhraseResult>) -> Vec { vec![] } - - #[inline] - fn nested_args() -> Vec<(char, &'static str, &'static str)> { vec![] } -} - -pub struct EFlintLeakNoErrors; -impl EFlintErrorHandler for EFlintLeakNoErrors { - type Error = std::convert::Infallible; - - #[inline] - fn new(_cli_args: &HashMap>) -> Result - where - Self: Sized, - { - // Doesn't need to parse anything! - Ok(Self) - } -} - -/// EFlintLeakPrefixErrors is an e-flint error handler -/// that returns errors if the violation identifier start with a certain -/// prefix. Which prefix is matched against can be configured. -pub struct EFlintLeakPrefixErrors { - prefix: String, -} -impl EFlintErrorHandler for EFlintLeakPrefixErrors { - type Error = EFlintLeakPrefixErrorsError; - - fn new(args: &HashMap>) -> Result - where - Self: Sized, - { - debug!("Parsing nested arguments for EFlintLeakPrefixErrors"); - let prefix: String = match args.get("prefix") { - Some(Some(path)) => path.into(), - _ => "pub-".into(), - }; - - // Done - Ok(Self { prefix }) - } - - fn extract_errors(&self, result: Option<&PhraseResult>) -> Vec { - result - .map(|r| match r { - eflint_json::spec::PhraseResult::StateChange(sc) => match &sc.violations { - Some(v) => v.iter().filter(|v| v.identifier.starts_with(&self.prefix)).map(|v| v.identifier.clone()).collect(), - None => vec![], - }, - _ => vec![], - }) - .unwrap_or_default() - } - - #[inline] - fn nested_args() -> Vec<(char, &'static str, &'static str)> { - vec![('p', "prefix", "Any eFLINT facts that have this prefix will be shared with clients. Default: 'pub-'")] - } -} - -/***** LIBRARY *****/ -pub struct EFlintReasonerConnector { - pub addr: String, - err_handler: T, - base_defs: Vec, -} - -impl EFlintReasonerConnector { - pub fn new(cli_args: String) -> Result> { - info!("Creating new EFlintReasonerConnector with {} plugin", std::any::type_name::()); - - debug!("Parsing nested arguments for EFlintReasonerConnector<{}>", std::any::type_name::()); - let parser = MapParser::new(Self::cli_args()); - let args: HashMap> = match parser.parse(&cli_args) { - Ok(args) => args, - Err(err) => return Err(Error::CliArgumentsParse { raw: cli_args, err }), - }; - - // See what to do with it - let addr: String = match args.get("reasoner-address") { - Some(Some(path)) => path.into(), - _ => "http://localhost:8080".into(), - }; - let err_handler: T = match T::new(&args) { - Ok(handler) => handler, - Err(err) => return Err(Error::ErrorHandler { name: std::any::type_name::(), err }), - }; - - debug!("Creating new EFlintReasonerConnector to '{addr}'"); - let base_defs: RequestPhrases = serde_json::from_str(JSON_BASE_SPEC).unwrap(); - Ok(EFlintReasonerConnector { addr, base_defs: base_defs.phrases, err_handler }) - } - - /// Returns the arguments necessary to build the parser for the EFlintReasonerConnector. - /// - /// # Returns - /// A vector of arguments appropriate to use to build a [`MapParser`]. - #[inline] - fn cli_args() -> Vec<(char, &'static str, &'static str)> { - let mut args: Vec<(char, &'static str, &'static str)> = vec![( - 'r', - "reasoner-address", - "The address (as `://:`) of the actual reasoner to connect with. Default: 'http://localhost:8080'", - )]; - args.extend(T::nested_args()); - args - } - - /// Returns a formatter that can be printed to understand the arguments to this connector. - /// - /// # Arguments - /// - `short`: A shortname for the argument that contains the nested arguments we parse. - /// - `long`: A longname for the argument that contains the nested arguments we parse. - /// - /// # Returns - /// A [`NestedCliParserHelpFormatter`] that implements [`Display`]. - // Don't agree with clippy about the unnecessary lifetimes here. Removing them needs me to write a `'_`, implying its disconnected from `long`. - #[allow(clippy::needless_lifetimes)] - pub fn help<'l>(short: char, long: &'l str) -> NestedCliParserHelpFormatter<'static, 'l, MapParser> { - MapParser::new(Self::cli_args()).into_help("EFlintReasonerConnector plugin", short, long) - } - - fn conv_state_to_eflint(&self, state: State) -> Vec { - debug!( - "Serializing state of {} datasets, {} functions, {} locations and {} users to eFLINT phrases", - state.datasets.len(), - state.functions.len(), - state.locations.len(), - state.users.len() - ); - let mut result: Vec = Vec::::new(); - - for user in state.users.iter() { - // ```eflint - // +user(#user.name). - // ``` - let user_constr: Expression = constr_app!("user", str_lit!(user.name.clone())); - result.push(create!(user_constr.clone())); - } - let user_len: usize = result.len(); - debug!("Generated {} user phrases", user_len); - - for location in state.locations.iter() { - // ```eflint - // +user(#location.name). - // +domain(user(#location.name)) - // ``` - let user_constr: Expression = constr_app!("user", str_lit!(location.name.clone())); - result.push(create!(user_constr.clone())); - result.push(create!(constr_app!("domain", user_constr))); - - // add metadata - } - let location_len: usize = result.len(); - debug!("Generated {} location phrases", location_len - user_len); - - for dataset in state.datasets.iter() { - // ```eflint - // +asset(#data.name). - // ``` - result.push(create!(constr_app!("asset", str_lit!(dataset.name.clone())))); - } - let dataset_len: usize = result.len(); - debug!("Generated {} dataset phrases", dataset_len - location_len); - - for function in state.functions.iter() { - // ```eflint - // +asset(#function.name). - // +code(asset(#function.name)). - // ``` - let asset_constr: Expression = constr_app!("asset", str_lit!(function.name.clone())); - result.push(create!(asset_constr.clone())); - result.push(create!(constr_app!("code", asset_constr))); - } - let function_len: usize = result.len(); - debug!("Generated {} function phrases", function_len - dataset_len); - - result - } - - fn extract_eflint_policy(&self, policy: &Policy) -> Vec { - info!("Extracting eFLINT policy..."); - let eflint_content: Vec<&PolicyContent> = policy.content.iter().filter(|x| x.reasoner == EFLINT_JSON_ID).collect(); - let eflint_content = eflint_content.first().unwrap(); - debug!("Deserializing input to eFLINT JSON..."); - let content: &str = eflint_content.content.get(); - let result: RequestPhrases = match serde_json::from_str(content) { - Ok(result) => match result { - Request::Phrases(phrases) => phrases, - Request::Handshake(_) | Request::Inspect(_) | Request::Ping(_) => panic!("Cannot accept non-Phrases Request input from request"), - }, - Err(err) => panic!( - "Input is not valid eFLINT JSON: {err}\n\nInput:\n{}\n{}\n{}\n", - (0..80).map(|_| '-').collect::(), - content, - (0..80).map(|_| '-').collect::() - ), - }; - result.phrases - } - - fn conv_workflow(&self, workflow: Workflow) -> Vec { - info!("Compiling Checker Workflow to eFLINT phrases..."); - workflow.to_eflint() - } - - fn extract_eflint_version(&self, policy: &Policy) -> Result { - info!("Retrieving eFLINT reasoner version from policy..."); - let eflint_content: Vec<&PolicyContent> = policy.content.iter().filter(|x| x.reasoner == EFLINT_JSON_ID).collect(); - let eflint_content = eflint_content.first().unwrap(); - let parts: Vec<&str> = eflint_content.reasoner_version.split('.').collect(); - - if parts.len() != 3 { - return Err(format!("Invalid version format, should be 'maj.min.patch', got '{}'", eflint_content.reasoner_version)); - } - - let maj = parts[0].parse::().map_err(|_| format!("Invalid major version part, could not parse {} into u32", parts[0]))?; - let min = parts[1].parse::().map_err(|_| format!("Invalid minor version part, could not parse {} into u32", parts[1]))?; - let patch = parts[2].parse::().map_err(|_| format!("Invalid patch version part, could not parse {} into u32", parts[2]))?; - - Ok(Version(maj, min, patch)) - } - - fn build_phrases(&self, policy: &Policy, state: State, workflow: Workflow, question: Phrase) -> Vec { - let mut phrases = Vec::::new(); - - // Build request - // 1. Base Facts - debug!("Loading interface ({} phrase(s))", self.base_defs.len()); - phrases.extend(self.base_defs.clone()); - - // 2. Fill knowledgebase from state - let state_phrases: Vec = self.conv_state_to_eflint(state); - debug!("Loading state ({} phrase(s))", state_phrases.len()); - phrases.extend(state_phrases); - - // 3. Add request - debug!("Loading question (1 phrase(s))"); - phrases.push(question); - - // 4. Add workflow - let workflow_phrases: Vec = self.conv_workflow(workflow); - debug!("Loading workflow ({} phrase(s))", workflow_phrases.len()); - phrases.extend(workflow_phrases); - - // 5. Add Policy - let policy_phrases: Vec = self.extract_eflint_policy(policy); - debug!("Loading policy ({} phrase(s))", policy_phrases.len()); - phrases.extend(policy_phrases); - - phrases - } - - async fn process_phrases( - &self, - logger: SessionedConnectorAuditLogger, - policy: &Policy, - phrases: Vec, - ) -> Result { - let version = self.extract_eflint_version(policy).map_err(ReasonerConnError::new)?; - debug!("Full request length: {} phrase(s)", phrases.len()); - let request = Request::Phrases(RequestPhrases { common: RequestCommon { version, extensions: HashMap::new() }, phrases, updates: true }); - debug!("Full request:\n\n{}\n\n", serde_json::to_string_pretty(&request).unwrap_or_else(|_| "".into())); - - // Make request - debug!("Sending eFLINT exec-task request to '{}'", self.addr); - let client = reqwest::Client::new(); - let res = client.post(&self.addr).json(&request).send().await.map_err(|err| ReasonerConnError::new(err.to_string()))?; - - debug!("Awaiting response..."); - let raw_body = res.text().await.map_err(|err| ReasonerConnError::new(err.to_string()))?; - - debug!("Log raw response..."); - - logger.log_reasoner_response(&raw_body).await.map_err(|err| { - debug!("Error trying to log{:?}", err); - ReasonerConnError::new(err.to_string()) - })?; - - debug!("Parsing response..."); - let response = serde_json::from_str::(&raw_body).map_err(|err| { - error!( - "{}\n\nRaw response:\n{}\n{}\n{}\n", - err, - (0..80).map(|_| '-').collect::(), - raw_body, - (0..80).map(|_| '-').collect::() - ); - ReasonerConnError::new(err.to_string()) - })?; - - debug!("Analysing response..."); - let errors: Vec = self.err_handler.extract_errors(response.results.last()); - - // TODO proper handle invalid query and unexpected result - let success: Result = response - .results - .last() - .map(|r| match r { - eflint_json::spec::PhraseResult::BooleanQuery(r) => Ok(r.result), - eflint_json::spec::PhraseResult::InstanceQuery(_) => Err("Invalid query".into()), - eflint_json::spec::PhraseResult::StateChange(r) => Ok(!r.violated), - }) - .unwrap_or_else(|| Err("Unexpected result".into())); - - match success { - Ok(success) => { - debug!( - "Response judged as: {} ({} && {})", - if success && response.common.success { "success" } else { "violated" }, - success, - response.common.success - ); - Ok(ReasonerResponse::new(success && response.common.success, errors)) - }, - // TODO better error handling - Err(err) => Err(ReasonerConnError::new(err)), - } - } -} - -#[derive(Debug, Clone, serde::Serialize)] -pub struct EFlintReasonerConnectorContext { - #[serde(rename = "type")] - pub t: String, - pub version: String, - pub base_defs: String, - pub base_defs_hash: String, -} - -impl std::hash::Hash for EFlintReasonerConnectorContext { - fn hash(&self, state: &mut H) { - self.t.hash(state); - self.version.hash(state); - self.base_defs_hash.hash(state); - } -} - -impl ConnectorContext for EFlintReasonerConnectorContext { - fn r#type(&self) -> String { self.t.clone() } - - fn version(&self) -> String { self.version.clone() } -} - -impl ConnectorWithContext for EFlintReasonerConnector { - type Context = EFlintReasonerConnectorContext; - - #[inline] - fn context() -> Self::Context { - EFlintReasonerConnectorContext { - t: "eflint-json".into(), - // NOTE: Must stay at 0.1.0, since else Olaf's reasoner will complain it's the wrong version lol - // TODO: Decouple reasoner version from the version on the wire (at least for now) - version: "0.1.0".into(), - base_defs: JSON_BASE_SPEC.into(), - base_defs_hash: JSON_BASE_SPEC_HASH.into(), - } - } -} -#[async_trait::async_trait] -impl ReasonerConnector - for EFlintReasonerConnector -{ - async fn execute_task( - &self, - logger: SessionedConnectorAuditLogger, - policy: Policy, - state: State, - workflow: Workflow, - task: String, - ) -> Result { - info!("Considering task '{}' in workflow '{}' for execution", task, workflow.id); - - // Add the question for this task - // ```eflint - // +task-to-execute(task(node(workflow(#workflow.id), #task))). - // ``` - let question: Phrase = create!(constr_app!( - "task-to-execute", - constr_app!("task", constr_app!("node", constr_app!("workflow", str_lit!(workflow.id.clone())), str_lit!(task))) - )); - - // Build & submit the phrases with the given policy, state, workflow _and_ question - let phrases = self.build_phrases(&policy, state, workflow, question); - self.process_phrases(logger, &policy, phrases).await - } - - async fn access_data_request( - &self, - logger: SessionedConnectorAuditLogger, - policy: Policy, - state: State, - workflow: Workflow, - data: String, - task: Option, - ) -> Result { - // Determine if we're asking for a node-to-node data transfer (there's a task as context) or a node-to-user (there's no task). - let question: Phrase = match task { - Some(task_id) => { - info!("Considering data access '{}' for task '{}' in workflow '{}'", data, task_id, workflow.id); - - // ```eflint - // +dataset-to-transfer(node-input(node(workflow(#workflow.id), #task), asset(#data))). - // ``` - create!(constr_app!( - "dataset-to-transfer", - constr_app!( - "node-input", - constr_app!("node", constr_app!("workflow", str_lit!(workflow.id.clone())), str_lit!(task_id)), - constr_app!("asset", str_lit!(data)), - ) - )) - }, - None => { - info!("Considering data access '{}' for result of workflow '{}'", data, workflow.id); - - // ```eflint - // +result-to-transfer(workflow-result-recipient(workflow-result(workflow(#workflow.id), asset(#data)), user(#workflow.user))). - // ``` - create!(constr_app!( - "result-to-transfer", - constr_app!( - "workflow-result-recipient", - constr_app!("workflow-result", constr_app!("workflow", str_lit!(workflow.id.clone())), constr_app!("asset", str_lit!(data))), - constr_app!("user", str_lit!(workflow.user.name.clone())) - ) - )) - }, - }; - - let phrases = self.build_phrases(&policy, state, workflow, question); - self.process_phrases(logger, &policy, phrases).await - } - - async fn workflow_validation_request( - &self, - logger: SessionedConnectorAuditLogger, - policy: Policy, - state: State, - workflow: Workflow, - ) -> Result { - info!("Considering workflow '{}'", workflow.id); - - // Add the question for this task - // ```eflint - // +workflow-to-execute(workflow(#workflow.id)). - // ``` - let question = create!(constr_app!("workflow-to-execute", constr_app!("workflow", str_lit!(workflow.id.clone())))); - - // Build & submit the phrases with the given policy, state, workflow _and_ question - let phrases = self.build_phrases(&policy, state, workflow, question); - self.process_phrases(logger, &policy, phrases).await - } -} diff --git a/src/bin/implementation/interface.rs b/src/bin/implementation/interface.rs deleted file mode 100644 index 00a988f..0000000 --- a/src/bin/implementation/interface.rs +++ /dev/null @@ -1,42 +0,0 @@ -use std::net::SocketAddr; - -use clap::Parser; - -/***** ARGUMENTS *****/ -/// Defines the arguments for the `policy-reasoner` server. -#[derive(Debug, Parser)] -pub struct Arguments { - /// Whether to enable full debugging - #[clap(long, global = true, help = "If given, enables more verbose debugging.")] - pub trace: bool, - - /// The address on which to bind ourselves. - #[clap(short, long, env, default_value = "127.0.0.1:3030", help = "The address on which to bind the server.")] - pub address: SocketAddr, - - /// Shows the help menu for the state resolver. - #[clap(long, help = "If given, shows the possible arguments to pass to the state resolver plugin in '--state-resolver'.")] - pub help_state_resolver: bool, - - /// Arguments specific to the state resolver. - #[clap( - short, - long, - env, - help = "Arguments to pass to the current state resolver plugin. To find which are possible, see '--help-state-resolver'." - )] - pub state_resolver: Option, - - /// Shows the help menu for the reasoner connector. - #[clap(long, help = "If given, shows the possible arguments to pass to the reasoner connector plugin in '--reasoner-connector'.")] - pub help_reasoner_connector: bool, - - /// Arguments specific to the state resolver. - #[clap( - short, - long, - env, - help = "Arguments to pass to the current reasoner connector plugin. To find which are possible, see '--help-reasoner-connector'." - )] - pub reasoner_connector: Option, -} diff --git a/src/bin/implementation/mod.rs b/src/bin/implementation/mod.rs deleted file mode 100644 index e7bc293..0000000 --- a/src/bin/implementation/mod.rs +++ /dev/null @@ -1,4 +0,0 @@ -pub mod eflint; -pub mod interface; -pub mod no_op; -pub mod posix; diff --git a/src/bin/implementation/no_op.rs b/src/bin/implementation/no_op.rs deleted file mode 100644 index 9d8eaa0..0000000 --- a/src/bin/implementation/no_op.rs +++ /dev/null @@ -1,90 +0,0 @@ -//! A minimal policy reasoner implementation that can be used as a base for new policy reasoners. -//! -//! This no-operation reasoner is meant to be an example, and can be used as a base to build new reasoners on top of. -//! Furthermore it can be used for testing. The reasoner approves all workflow validation requests by default (it does -//! not perform any permission checks, and thus never rejects a request). -use audit_logger::{ConnectorContext, ConnectorWithContext, ReasonerConnectorAuditLogger, SessionedConnectorAuditLogger}; -use log::debug; -use policy::Policy; -use reasonerconn::{ReasonerConnError, ReasonerConnector, ReasonerResponse}; -use state_resolver::State; -use workflow::spec::Workflow; - -/// The minimal no-operation reasoner connector, that approves all validation requests by default (it does not check any -/// policy/permissions). -#[derive(Default)] -pub struct NoOpReasonerConnector; - -impl NoOpReasonerConnector { - pub fn new() -> Self { Default::default() } -} - -/***** LIBRARY *****/ -#[async_trait::async_trait] -impl ReasonerConnector for NoOpReasonerConnector { - async fn execute_task( - &self, - _logger: SessionedConnectorAuditLogger, - _policy: Policy, - _state: State, - _workflow: Workflow, - _task: String, - ) -> Result { - debug!("NoOpReasonerConnector: Execute task request received"); - return Ok(ReasonerResponse::new(true, vec![])); - } - - async fn access_data_request( - &self, - _logger: SessionedConnectorAuditLogger, - _policy: Policy, - _state: State, - _workflow: Workflow, - _data: String, - _task: Option, - ) -> Result { - debug!("NoOpReasonerConnector: Access data request received"); - return Ok(ReasonerResponse::new(true, vec![])); - } - - async fn workflow_validation_request( - &self, - _logger: SessionedConnectorAuditLogger, - _policy: Policy, - _state: State, - workflow: Workflow, - ) -> Result { - debug!("NoOpReasonerConnector: Workflow validation request received"); - println!("Workflow: {:#?}", workflow); - return Ok(ReasonerResponse::new(true, vec![])); - } -} - -/// The context for the no-operation reasoner connector. -/// See the [`ConnectorContext`] and [`ConnectorWithContext`] traits for more information. -#[derive(Debug, Clone, serde::Serialize)] -pub struct NoOpReasonerConnectorContext { - #[serde(rename = "type")] - pub t: String, - pub version: String, -} - -impl std::hash::Hash for NoOpReasonerConnectorContext { - fn hash(&self, state: &mut H) { - self.t.hash(state); - self.version.hash(state); - } -} - -impl ConnectorContext for NoOpReasonerConnectorContext { - fn r#type(&self) -> String { self.t.clone() } - - fn version(&self) -> String { self.version.clone() } -} - -impl ConnectorWithContext for NoOpReasonerConnector { - type Context = NoOpReasonerConnectorContext; - - #[inline] - fn context() -> Self::Context { NoOpReasonerConnectorContext { t: "noop".into(), version: "0.1.0".into() } } -} diff --git a/src/bin/implementation/posix.rs b/src/bin/implementation/posix.rs deleted file mode 100644 index 3f2f757..0000000 --- a/src/bin/implementation/posix.rs +++ /dev/null @@ -1,535 +0,0 @@ -//! A policy reasoner implementation based on POSIX file permissions. -//! -//! This documentation is aimed at developers that want to maintain or extend the POSIX reasoner. High level -//! documentation for users of the POSIX reasoner can be found in the [Brane user -//! guide](https://wiki.enablingpersonalizedinterventions.nl/user-guide/). An explanation of the POSIX policy file can -//! also be found there. -//! -//! # Goal -//! -//! This policy reasoner is meant to be easy and widely applicable. The aim is to take few assumptions and require as -//! little configuration as possible, allowing this reasoner to function as an easy to deploy proof of concept. This -//! allows users of Brane to gather experience with the abstract concept of a policy reasoner, before one has to start -//! writing policies themselves. -//! -//! Additionally, it could function well as an initial reasoner as a user adopts Brane on their systems, since it could -//! use permissions already set on their current systems to infer which users have access to what data. -//! -//! # Design -//! -//! To describe the design of this policy reasoner, we will walk through both the setup and usage of the reasoner from a -//! high level point of view. -//! -//! First, it checks the `DATA_INDEX` environment variable or the .env file for the location of the data index. We -//! imagine this points towards a mounted distributed file system like NFS. Then, it scans the directories for data -//! index files. From these files a [DataIndex] is created which is passed on to the [PosixReasonerConnector]. -//! -//! Now that the [PosixReasonerConnector] is created, it can start to handle requests. There are three types of -//! requests: -//! -//! - [Execute task](fn@PosixReasonerConnector::execute_task) -//! - [Access data](fn@PosixReasonerConnector::access_data_request) -//! - [Workflow validation](fn@PosixReasonerConnector::workflow_validation_request) -//! -//! As of now, the assumption is taken that it does not matter for this reasoner which type of request comes in, as we -//! only look at the data usage in the [Workflow]. -//! -//! As one of these requests comes in, the provided [Workflow] is parsed using a [DatasetCollectorVisitor] (an -//! implementation of the new util trait [WorkflowVisitor]) and all data accesses in the workflow are gathered and -//! associated with an access type of either read, write, or execute (execute is currently unused as no usage was -//! found). -//! -//! From this point, we iterate over all the different datasets and associated requests/required permissions. For each -//! [Dataset] we look up the path in the [DataIndex]. Now that we have the path and the requested permissions, we can -//! check if the user in the mapping has access to this dataset. -//! -//! ### Current permission model -//! -//! The current permission model is based on the POSIX file permissions. This means that we check if the user has the -//! required permissions on the file. This is done by checking the file permissions of the file itself, and checking if -//! the user is either the owner of the file, in the group of the file, or if the file is world readable. The uid and -//! the gids extracted from the policy are matched against the file's uid and gid. If the file is owned by the user, the -//! owner permissions are checked. If the file is owned by a group the user is in, the group permissions are checked. If -//! neither of these is true, the other permissions are checked. If the user has the required permissions, the request is -//! approved. If not, the request is denied : [satisfies_posix_permissions]. -//! -//! -//! # State of the implementation -//! -//! Right now, the POSIX policy reasoner works. One can submit workflow requests using a HTTP request, or one can use -//! the Policy Reasoner GUI to submit a workflow request to the policy reasoner. The policy reasoner will then evaluate -//! if all files are properly accessible and return a verdict. -//! -//! ## Limitations -//! -//! We had to draw a line in the sand for the current implementation, after which we considered additional details or -//! features out of the scope for the current project. If more time would have been available, there would have been a -//! fair bit of additional features that we would have liked to have implemented. -//! -//! The main origin of this line is the lack of an actual implementation inside Brane. To fully understand the -//! implications of the POSIX file permissions scheme, we need to start using the current implementation in a staging -//! environment. We should investigate the effects of multiple sites with many datasets. However, right now it is not -//! possible (or so we have been told) to mount the network shares to the reasoner container. -//! -//! Right now, this implementation is limited by the fact that it will try to load a single data index from the -//! `DATA_INDEX` environment variable. However, once we start mounting multiple network shares, we imagine that it might -//! be nice to be able to load multiple data indices. -//! -//! Another limitation is that the current implementation is not fully POSIX compliant. We still need to figure out how -//! some of the POSIX permission behaviours map into this emulation. E.g., right now we only check the file permissions -//! on the file itself, we do not check the permissions on the directory. Since we are going to be working with network -//! shares (and possible hard/symlinks) this becomes non-trivial, a working implementation is needed to investigate what -//! behaviour is desired. This is compounded by the problem that not only the user needs to be able to access the data, -//! but also the policy reasoner needs to reach at least the directory in which the file resides in order for the -//! reasoner to be able to `stat(1)` the file. -//! -//! Right now, we are seemingly limited by the fact that the policy reasoner GUI does not send at which site a [Dataset] -//! is accessed, making it impossible to fully know which of the mappings in the policy to use. There is a location -//! field that could be used, but since it is always set to `None`, we opted to fall back to the assumed location as -//! hardcoded in the static: [`ASSUMED_LOCATION`] -//! -//! # Future work -//! -//! - Support file creation: This requires us to look at the permissions of the parent directory of the potential -//! dataset, but this might be tricky considering the fact that the policy reasoner also needs to be able to access the -//! directory in order to check the file permissions. -//! -//! - Support multiple user mappings per location: Right now there is support in the code for multiple locations with -//! each their own user mapping, as different sites will often comprise of different network shares. But as it is -//! unclear right now how the volumes will be mounted on the reasoner container, it is hard to tell how such an -//! implementation is best designed. -//! -//! - Right now, this reasoner is both a module and a binary, but it should probably just be a binary. At this moment -//! however, documenting binaries is tricky in rust. As soon as we find a better solution, this documentation should be -//! moved to the binary itself. This might be useful in general, but particularly it is important to document reference -//! implementations. -//! -//! - LDAP / Active Directory support: Currently the Brane user to uid / gid mapping is embedded in the policy that is -//! loaded at the moment the reasoner is started. The idea of mapping users to uid and gids is not unique though, these -//! mappings can be sythesized from all sorts of resources. The most straightforward variant would be the loading of a -//! `passwd(5)` file, but since we are aiming at distributed file systems this would probably be of limited use. In -//! situations where file systems like NFS are often used, the users are store in Active Directory and accessed using -//! LDAP. Writing such an adapter to function as the user map seems so be a valuable addition to this reasoner, as this -//! would complete the picture of current existing systems and would allow a user of the reasoner to attach their -//! already existing and managed file systems with the correct access control. This would significantly reduce the -//! required investment of introducing policies in new Brane users. -//! -//! - ACL support: Besides regular POSIX file permissions, many file systems also support [POSIX -//! ACL](https://web.archive.org/web/20240210045229/https://www.usenix.org/legacy/publications/library/proceedings/usenix03/tech/freenix03/full_papers/gruenbacher/gruenbacher_html/main.html). -//! This would be an obvious and very useful extension to this reasoner. Usage of these ACLs is by far less common that -//! the regular POSIX permission it attempts to extend, but usage is also far from uncommon and potenial users of Brane, -//! and more specifically the policy reasoner, could have used these ACLs on their existing file systems. -//! -//! - The last point of future work is not specific to the POSIX reasoner, but more to the policy reasoner repository. -//! There is a desperate need for more elaborate documentation. We attempted to create a nice start with this reasoner, -//! but it can be quite daunting to figure out how every part of this system works. The author made a good effort to -//! improve the documentation during the running of this project, and that helped a lot. That combined with the already -//! existing implementation of the eFlint reasoner made this project possible. We hope that the POSIX (and -//! no_op reasoner can help guide future contributors in either extension of the current reasoners or -//! the addition of new reasoner types. - -use std::collections::{HashMap, HashSet}; -use std::iter::repeat; -use std::os::unix::fs::{MetadataExt, PermissionsExt}; -use std::path::Path; - -use audit_logger::{ConnectorContext, ConnectorWithContext, ReasonerConnectorAuditLogger, SessionedConnectorAuditLogger}; -use itertools::{Either, Itertools}; -use log::{debug, error, info}; -use policy::{Policy, PolicyContent}; -use reasonerconn::{ReasonerConnError, ReasonerConnector, ReasonerResponse}; -use serde::Deserialize; -use specifications::data::{DataIndex, Location}; -use state_resolver::State; -use workflow::Dataset; -use workflow::spec::Workflow; -use workflow::utils::{WorkflowVisitor, walk_workflow_preorder}; - -/// This location is an assumption right now, and is needed as long as the location is not passed to the workflow -/// validator. -static ASSUMED_LOCATION: &str = "surf"; - -/***** LIBRARY *****/ -/// E.g., `st_antonius_etc`. -type LocationIdentifier = String; -/// The global username as defined in [`Workflow.user`]. E.g., `test`. -type GlobalUsername = String; - -/// The overarching POSIX policy. Check out the module documentation for an overview. -#[derive(Deserialize, Debug)] -pub struct PosixPolicy { - datasets: HashMap, -} - -impl PosixPolicy { - /// Extracts and parses a [`PosixPolicy`] from a generic [`Policy`] object. Expects the policy to be specified and - /// expects it to adhere to the [`PosixPolicy`] YAML structure. See [`PosixPolicy`]. - fn from_policy(policy: Policy) -> Self { - let policy_content: PolicyContent = policy.content.first().expect("Failed to parse PolicyContent").clone(); - let content_str = policy_content.content.get().trim(); - PosixPolicy { datasets: serde_json::from_str(content_str).expect("Failed to parse PosixPolicy") } - } - - /// Given a location (e.g., `st_antonius_ect`) and the workflow user's name (e.g., `test`), returns the - /// [`PosixLocalIdentity`] for that user. - /// - /// The returned identity is used for file permission checks. For more about this permissions check see - /// [`validate_dataset_permissions`]. - fn get_local_identity(&self, location: &str, workflow_user: &str) -> Result<&PosixLocalIdentity, PolicyError> { - self.datasets - .get(location) - .ok_or_else(|| PolicyError::MissingLocation(location.to_owned()))? - .user_map - .get(workflow_user) - .ok_or_else(|| PolicyError::MissingUser(workflow_user.to_owned(), location.to_owned())) - } -} - -/// Represents an error that occurred during the validation of a policy. These errors contain more information about the -/// problems that occurred during validation. -#[derive(thiserror::Error, Debug)] -enum PolicyError { - #[error("Missing location: {0}")] - MissingLocation(String), - #[error("Missing user: {0} for location: {1}")] - MissingUser(String, String), -} - -/// Part of the [`PosixPolicy`]. Represents a location (e.g., `st_antonius_etc`) and contains the global workflow -/// username to local identity mappings for this location. -#[derive(Deserialize, Debug)] -pub struct PosixPolicyLocation { - user_map: HashMap, -} - -/// The local identity defines a user id and a list of group ids. The local identity is used on the machine on which a -/// dataset resides to check the local file permissions. For more about this permissions check see -/// [`validate_dataset_permissions`]. -/// -/// This identity is defined in the POSIX policy file. Global usernames in the POSIX policy map to these local -/// identities. -/// -/// Example, given the POSIX policy file below, then for the `st_antonius_ect` location, the `test` global username maps -/// to a local identity that contains the uid and gids. -/// ``` yaml -/// # file: posix-policy.yml -/// content: -/// st_antonius_ect: -/// user_map: -/// test: -/// uid: 1000 -/// gids: -/// - 1001 -/// - 1002 -/// - 1003 -/// ``` -#[derive(Deserialize, Debug)] -struct PosixLocalIdentity { - /// The user identifier of a Linux user. - uid: u32, - /// A list of Linux group identifiers. - gids: Vec, -} - -/// Represents a POSIX file permission. See: . -#[derive(Debug, Copy, Clone)] -enum PosixFilePermission { - Read, - Write, - Execute, -} - -impl PosixFilePermission { - /// Returns this permission's mode bit. - /// - `Read` → `4` - /// - `Write` → `2` - /// - `Execute` → `1`. - /// - /// For more about POSIX permission bits see: - /// . - /// - /// Also see the related [`PosixFileClass::get_mode_bitmask`]. - fn to_mode_bit(self) -> u32 { - match self { - PosixFilePermission::Read => 4, - PosixFilePermission::Write => 2, - PosixFilePermission::Execute => 1, - } - } -} - -/// Represents a POSIX file class, also known as a scope. See: -/// . -#[derive(Copy, Clone, Deserialize)] -enum PosixFileClass { - Owner, - Group, - Others, -} - -impl PosixFileClass { - /// Given a list of [`PosixFilePermission`]s will return an octal mode bitmask for this [`PosixFileClass`]. - /// - /// This bitmask represents what mode bits should be set on a file such that this class (e.g., `Owner`) satisfies - /// the permissions (e.g, `Read`, `Write`). In this case it would be `0o400` (Read for Owner) and `0o200` (Write for - /// Owner), which sums to the returned `0o600` (Read and Write for Owner). - fn get_mode_bitmask(&self, required_permissions: &[PosixFilePermission]) -> u32 { - let alignment_multiplier = match self { - PosixFileClass::Owner => 0o100, - PosixFileClass::Group => 0o10, - PosixFileClass::Others => 0o1, - }; - required_permissions.iter().fold(0, |acc, f| acc | (alignment_multiplier * f.to_mode_bit())) - } -} - -/// Verifies whether the passed [`PosixLocalIdentity`] has all of the requested permissions (e.g., `Read` and `Write`) -/// on a particular file (defined by the `path`). The identity's user id and group ids are checked against the file -/// owner's user id and group id respectively. Additionally, the `Others` class permissions are also checked. -fn satisfies_posix_permissions(path: impl AsRef, local_identity: &PosixLocalIdentity, requested_permissions: &[PosixFilePermission]) -> bool { - let metadata = std::fs::metadata(&path).expect("Could not get file metadata"); - - let mode_bits = metadata.permissions().mode(); - let file_owner_uid = metadata.uid(); - let file_owner_gid = metadata.gid(); - - if file_owner_uid == local_identity.uid { - let mask = PosixFileClass::Owner.get_mode_bitmask(requested_permissions); - if mode_bits & mask == mask { - return true; - } - } - - if local_identity.gids.contains(&file_owner_gid) { - let mask = PosixFileClass::Group.get_mode_bitmask(requested_permissions); - if mode_bits & mask == mask { - return true; - } - } - - let mask = PosixFileClass::Others.get_mode_bitmask(requested_permissions); - mode_bits & mask == mask -} - -enum ValidationOutput { - Ok, - // Below we might want to encapsulate the Dataset itself. - /// The string here represents a `Dataset.name`. - Fail(Vec), -} - -/// Represents a validation error that occurred during the validation of a workflow. These errors contain more -/// information about the problems that occurred during validation. -#[derive(thiserror::Error, Debug)] -enum ValidationError { - #[error("Policy Error: {0}")] - PolicyError(PolicyError), - #[error("Unknown dataset: {0}")] - UnknownDataset(String), -} - -/// Check if all the data accesses performed in the `workflow` are done on behalf of users that have the required -/// permissions. If not all permissions are met, then [`ValidationError`]s are returned. These errors contain more -/// information about the problems that occurred during validation. -fn validate_dataset_permissions(workflow: &Workflow, data_index: &DataIndex, policy: &PosixPolicy) -> Result> { - // The datasets used in the workflow. E.g., `st_antonius_ect`. - let datasets = find_datasets_in_workflow(workflow); - - let (forbidden, errors): (Vec<_>, Vec<_>) = std::iter::empty() - .chain(datasets.read_sets.iter().zip(repeat(vec![PosixFilePermission::Read]))) - .chain(datasets.write_sets.iter().zip(repeat(vec![PosixFilePermission::Write]))) - .chain(datasets.execute_sets.iter().zip(repeat(vec![PosixFilePermission::Read, PosixFilePermission::Execute]))) - .flat_map(|((location, dataset), permission)| { - let Some(dataset) = data_index.get(&dataset.name) else { - return Either::Left(std::iter::once(Err(ValidationError::UnknownDataset(dataset.name.clone())))); - }; - Either::Right(dataset.access.values().map(move |kind| match kind { - specifications::data::AccessKind::File { path } => { - info!("Contents of the DataInfo object:\n{:#?}", dataset); - let local_identity = policy.get_local_identity(location, &workflow.user.name).map_err(ValidationError::PolicyError)?; - let result = satisfies_posix_permissions(path, local_identity, &permission); - Ok((dataset.name.clone(), path, result)) - }, - })) - }) - // This is where we are going to focus on the problems that occurred in the validation - // These can be separated into groups: Errors (e.g. Non-existing users / files), and - // validation failures. - .filter(|res| match res { - // Filter out what was okay in either sense. - Ok((_, _, true)) => false, - _ => true, - }) - .partition_map(|elem| match elem { - Ok((dataset_identifier, _, _)) => Either::Left(dataset_identifier), - Err(x) => Either::Right(x), - }); - - if !errors.is_empty() { - Err(errors) - } else if forbidden.is_empty() { - return Ok(ValidationOutput::Ok); - } else { - return Ok(ValidationOutput::Fail(forbidden)); - } -} - -/// The POSIX reasoner connector. This connector is used to validate workflows based on POSIX file permissions. -pub struct PosixReasonerConnector { - data_index: DataIndex, -} - -impl PosixReasonerConnector { - pub fn new(data_index: DataIndex) -> Self { - info!("Creating new PosixReasonerConnector with {} plugin", std::any::type_name::()); - debug!("Parsing nested arguments for PosixReasonerConnector<{}>", std::any::type_name::()); - - PosixReasonerConnector { data_index } - } -} - -/***** LIBRARY *****/ -#[async_trait::async_trait] -impl ReasonerConnector for PosixReasonerConnector { - async fn execute_task( - &self, - _logger: SessionedConnectorAuditLogger, - policy: Policy, - _state: State, - workflow: Workflow, - _task: String, - ) -> Result { - let posix_policy = PosixPolicy::from_policy(policy); - match validate_dataset_permissions(&workflow, &self.data_index, &posix_policy) { - Ok(ValidationOutput::Ok) => Ok(ReasonerResponse::new(true, vec![])), - Ok(ValidationOutput::Fail(datasets)) => Ok(ReasonerResponse::new( - false, - datasets.into_iter().map(|dataset| format!("We do not have sufficient permissions for dataset: {dataset}")).collect(), - )), - Err(errors) => Ok(ReasonerResponse::new(false, errors.into_iter().map(|error| error.to_string()).collect())), - } - } - - async fn access_data_request( - &self, - _logger: SessionedConnectorAuditLogger, - policy: Policy, - _state: State, - workflow: Workflow, - _data: String, - _task: Option, - ) -> Result { - let posix_policy = PosixPolicy::from_policy(policy); - match validate_dataset_permissions(&workflow, &self.data_index, &posix_policy) { - Ok(ValidationOutput::Ok) => Ok(ReasonerResponse::new(true, vec![])), - Ok(ValidationOutput::Fail(datasets)) => Ok(ReasonerResponse::new( - false, - datasets.into_iter().map(|dataset| format!("We do not have sufficient permissions for dataset: {dataset}")).collect(), - )), - Err(errors) => Ok(ReasonerResponse::new(false, errors.into_iter().map(|error| error.to_string()).collect())), - } - } - - async fn workflow_validation_request( - &self, - _logger: SessionedConnectorAuditLogger, - policy: Policy, - _state: State, - workflow: Workflow, - ) -> Result { - let posix_policy = PosixPolicy::from_policy(policy); - match validate_dataset_permissions(&workflow, &self.data_index, &posix_policy) { - Ok(ValidationOutput::Ok) => Ok(ReasonerResponse::new(true, vec![])), - Ok(ValidationOutput::Fail(datasets)) => Ok(ReasonerResponse::new( - false, - datasets.into_iter().map(|dataset| format!("We do not have sufficient permissions for dataset: {dataset}")).collect(), - )), - Err(errors) => Ok(ReasonerResponse::new(false, errors.into_iter().map(|error| error.to_string()).collect())), - } - } -} - -/// The context of the POSIX reasoner connector. This context is used to identify the reasoner connector. -/// See [`ConnectorContext`] and [`ConnectorWithContext`]. -#[derive(Debug, Clone, serde::Serialize)] -pub struct PosixReasonerConnectorContext { - #[serde(rename = "type")] - pub t: String, - pub version: String, -} - -impl std::hash::Hash for PosixReasonerConnectorContext { - fn hash(&self, state: &mut H) { - self.t.hash(state); - self.version.hash(state); - } -} - -impl ConnectorContext for PosixReasonerConnectorContext { - fn r#type(&self) -> String { self.t.clone() } - - fn version(&self) -> String { self.version.clone() } -} - -impl ConnectorWithContext for PosixReasonerConnector { - type Context = PosixReasonerConnectorContext; - - #[inline] - fn context() -> Self::Context { PosixReasonerConnectorContext { t: "posix".into(), version: "0.1.0".into() } } -} - -/// The datasets accessed and/or modified in a workflow. These are grouped by file permission type. For creating this -/// struct see: [`find_datasets_in_workflow`]. -struct WorkflowDatasets { - read_sets: Vec<(Location, Dataset)>, - write_sets: Vec<(Location, Dataset)>, - execute_sets: Vec<(Location, Dataset)>, -} - -fn find_datasets_in_workflow(workflow: &Workflow) -> WorkflowDatasets { - debug!("Walking the workflow in order to find datasets. Starting with {:?}", &workflow.start); - let mut visitor = - DatasetCollectorVisitor { read_sets: Default::default(), write_sets: Default::default(), execute_sets: Default::default() }; - - walk_workflow_preorder(&workflow.start, &mut visitor); - - WorkflowDatasets { read_sets: visitor.read_sets, write_sets: visitor.write_sets, execute_sets: visitor.execute_sets } -} - -/// Implements a visitor that traverses a [`Workflow`] and collect the datasets that are accessed and/or modified in -/// the workflow. See: [`WorkflowDatasets`] and [`WorkflowVisitor`]. -struct DatasetCollectorVisitor { - pub read_sets: Vec<(Location, Dataset)>, - pub write_sets: Vec<(Location, Dataset)>, - pub execute_sets: Vec<(Location, Dataset)>, -} - -impl WorkflowVisitor for DatasetCollectorVisitor { - fn visit_task(&mut self, task: &workflow::ElemTask) { - // FIXME: Location is not currently sent as part of the workflow validation request, - // this makes this not really possible to do now. To ensure the code is working - // however, we will for the mean time assume the location - - let location = task.location.clone().unwrap_or_else(|| String::from(ASSUMED_LOCATION)); - if let Some(output) = &task.output { - self.read_sets.push((location.clone(), output.clone())); - } - } - - fn visit_commit(&mut self, commit: &workflow::ElemCommit) { - let location = commit.location.clone().unwrap_or_else(|| String::from(ASSUMED_LOCATION)); - self.read_sets.extend(repeat(location.clone()).zip(commit.input.iter().cloned())); - - // TODO: Maybe create a dedicated enum type for this e.g. NewDataset for datasets that will be - // created, might fail if one already exists. - let location = commit.location.clone().unwrap_or_else(|| String::from(ASSUMED_LOCATION)); - self.write_sets.push((location.clone(), Dataset { name: commit.data_name.clone(), from: None })); - } - - // TODO: We do not really have a location for this one right now, we should figure out how to - // interpret this - fn visit_stop(&mut self, stop_sets: &HashSet) { - let location = String::from(ASSUMED_LOCATION); - self.write_sets.extend(repeat(location).zip(stop_sets.iter().cloned())); - } -} diff --git a/src/bin/no_op.rs b/src/bin/no_op.rs deleted file mode 100644 index 03272ed..0000000 --- a/src/bin/no_op.rs +++ /dev/null @@ -1,215 +0,0 @@ -//! No-op policy reasoner -//! This reasoner does a little as possible and functions as base for other implementations of the -//! policy reasoner. -use std::env; -use std::fs::File; -use std::future::Future; - -pub mod implementation; - -use async_trait::async_trait; -use clap::Parser; -use humanlog::{DebugMode, HumanLogger}; -use implementation::interface::Arguments; -use implementation::no_op::NoOpReasonerConnector; -use log::info; -use policy::{Context, Policy, PolicyDataAccess, PolicyDataError, PolicyVersion}; -use policy_reasoner::auth::{JwtConfig, JwtResolver, KidResolver}; -use policy_reasoner::logger::FileLogger; -use reasonerconn::ReasonerConnector; -use srv::Srv; -use state_resolver::{State, StateResolver}; - -/***** HELPER FUNCTIONS *****/ -fn get_pauth_resolver() -> policy_reasoner::auth::JwtResolver { - let kid_resolver = KidResolver::new("./examples/config/jwk_set_expert.json").unwrap(); - let r = File::open("./examples/config/jwt_resolver.yaml").unwrap(); - let jwt_cfg: JwtConfig = serde_yaml::from_reader(r).unwrap(); - JwtResolver::new(jwt_cfg, kid_resolver).unwrap() -} -fn get_dauth_resolver() -> policy_reasoner::auth::JwtResolver { - let kid_resolver = KidResolver::new("./examples/config/jwk_set_delib.json").unwrap(); - let r = File::open("./examples/config/jwt_resolver.yaml").unwrap(); - let jwt_cfg: JwtConfig = serde_yaml::from_reader(r).unwrap(); - JwtResolver::new(jwt_cfg, kid_resolver).unwrap() -} - -/***** PLUGINS *****/ -/// The plugin used to do the audit logging. -type AuditLogPlugin = FileLogger; - -/// The plugin used to do authentication for the policy expert API. -type PolicyAuthResolverPlugin = JwtResolver; -/// The plugin used to do authentication for the deliberation API. -type DeliberationAuthResolverPlugin = JwtResolver; - -/// The plugin used to interact with the policy store. -type PolicyStorePlugin = DummyPolicyStore; - -/// The plugin used to resolve policy input state. -struct DummyStateResolver; - -#[async_trait] -impl StateResolver for DummyStateResolver { - type Error = std::convert::Infallible; - - async fn get_state(&self, _use_case: String) -> Result { - Ok(State { users: Default::default(), locations: Default::default(), datasets: Default::default(), functions: Default::default() }) - } -} - -type StateResolverPlugin = DummyStateResolver; - -struct DummyPolicyStore; - -#[async_trait] -impl PolicyDataAccess for DummyPolicyStore { - type Error = std::convert::Infallible; - - async fn add_version>>( - &self, - _version: Policy, - _context: Context, - _transaction: impl 'static + Send + FnOnce(Policy) -> F, - ) -> Result { - #[allow(unreachable_code)] - Ok(Policy { - description: String::from("This is a dummy policy"), - version: policy::PolicyVersion { - creator: None, - created_at: chrono::DateTime::from_timestamp_nanos(0).into(), - version: Some(1), - version_description: String::from("This is a dummy version of a dummy policy"), - // TODO: Compute hash by hand - reasoner_connector_context: String::from("No context exists"), - }, - content: Vec::new(), - }) - } - - async fn get_version(&self, _version: i64) -> Result { - #[allow(unreachable_code)] - Ok(Policy { - description: String::from("This is a dummy policy"), - version: policy::PolicyVersion { - creator: None, - created_at: chrono::DateTime::from_timestamp_nanos(0).into(), - version: Some(1), - version_description: String::from("This is a dummy version of a dummy policy"), - // TODO: Compute hash by hand - reasoner_connector_context: String::from("No context exists"), - }, - content: Vec::new(), - }) - } - - async fn get_most_recent(&self) -> Result { - #[allow(unreachable_code)] - Ok(Policy { - description: String::from("This is a dummy policy"), - version: policy::PolicyVersion { - creator: None::, - created_at: chrono::DateTime::from_timestamp_nanos(0).into(), - version: Some(1), - version_description: String::from("This is a dummy version of a dummy policy"), - // TODO: Compute hash by hand - reasoner_connector_context: String::from("No context exists"), - }, - content: Vec::new(), - }) - } - - async fn get_versions(&self) -> Result, PolicyDataError> { - #[allow(unreachable_code)] - Ok(vec![PolicyVersion { - creator: None, - created_at: chrono::DateTime::from_timestamp_nanos(0).into(), - version: Some(1), - version_description: String::from("This is a dummy version of a dummy policy"), - // TODO: Compute hash by hand - reasoner_connector_context: String::from("No context exists"), - }]) - } - - async fn get_active(&self) -> Result { - #[allow(unreachable_code)] - Ok(Policy { - description: String::from("This is a dummy policy"), - version: policy::PolicyVersion { - creator: None, - created_at: chrono::DateTime::from_timestamp_nanos(0).into(), - version: Some(1), - version_description: String::from("This is a dummy version of a dummy policy"), - // TODO: Compute hash by hand - reasoner_connector_context: String::from("No context exists"), - }, - content: Vec::new(), - }) - } - - async fn set_active>>( - &self, - _version: i64, - _context: Context, - _transaction: impl 'static + Send + FnOnce(Policy) -> F, - ) -> Result { - #[allow(unreachable_code)] - Ok(Policy { - description: String::from("This is a dummy policy"), - version: policy::PolicyVersion { - creator: None, - created_at: chrono::DateTime::from_timestamp_nanos(0).into(), - version: Some(1), - version_description: String::from("This is a dummy version of a dummy policy"), - // TODO: Compute hash by hand - reasoner_connector_context: String::from("No context exists"), - }, - content: Vec::new(), - }) - } - - async fn deactivate_policy>>( - &self, - _context: Context, - _transaction: impl 'static + Send + FnOnce() -> F, - ) -> Result<(), PolicyDataError> { - // Nothing to do - Ok(()) - } -} - -/***** ENTRYPOINT *****/ -#[tokio::main] -async fn main() { - let args: Arguments = Arguments::parse(); - - let rconn = NoOpReasonerConnector::new(); - - // Setup a logger - if let Err(err) = HumanLogger::terminal(if args.trace { DebugMode::Full } else { DebugMode::Debug }).init() { - eprintln!("WARNING: Failed to setup logger: {err} (no logging for this session)"); - } - - info!("{} - v{}", env!("CARGO_PKG_NAME"), env!("CARGO_PKG_VERSION")); - - run_app(args, rconn).await; -} - -async fn run_app(args: Arguments, rconn: R) -where - R: ReasonerConnector + Send + Sync + 'static, -{ - // Initialize the plugins - let log_identifier = format!("{binary} v{version}", binary = env!("CARGO_BIN_NAME"), version = env!("CARGO_PKG_VERSION")); - let logger: AuditLogPlugin = FileLogger::new(log_identifier, "./audit-log.log"); - let pauthresolver: PolicyAuthResolverPlugin = get_pauth_resolver(); - let dauthresolver: DeliberationAuthResolverPlugin = get_dauth_resolver(); - let pstore: PolicyStorePlugin = DummyPolicyStore {}; - - let sresolve: StateResolverPlugin = DummyStateResolver {}; - - // Run them! - let server = Srv::new(args.address, logger, rconn, pstore, sresolve, pauthresolver, dauthresolver); - - server.run().await; -} diff --git a/src/bin/posix.rs b/src/bin/posix.rs deleted file mode 100644 index dbf280a..0000000 --- a/src/bin/posix.rs +++ /dev/null @@ -1,130 +0,0 @@ -// MAIN.rs -// by Lut99 -// -// Created: -// 09 Jan 2024, 13:32:03 -// Last edited: -// 07 Feb 2024, 18:08:43 -// Auto updated? -// Yes -// -// Description: -//! Entrypoint to the main `policy-reasoner` binary. -// - -pub mod implementation; - -use std::env; -use std::fs::File; - -use clap::Parser; -use error_trace::ErrorTrace as _; -use humanlog::{DebugMode, HumanLogger}; -use implementation::interface::Arguments; -use implementation::posix; -use log::{error, info}; -use policy_reasoner::auth::{JwtConfig, JwtResolver, KidResolver}; -use policy_reasoner::logger::FileLogger; -use policy_reasoner::sqlite::SqlitePolicyDataStore; -use policy_reasoner::state; -use reasonerconn::ReasonerConnector; -use srv::Srv; - -/***** HELPER FUNCTIONS *****/ -fn get_pauth_resolver() -> policy_reasoner::auth::JwtResolver { - let kid_resolver = KidResolver::new("./examples/config/jwk_set_expert.json").unwrap(); - let r = File::open("./examples/config/jwt_resolver.yaml").unwrap(); - let jwt_cfg: JwtConfig = serde_yaml::from_reader(r).unwrap(); - JwtResolver::new(jwt_cfg, kid_resolver).unwrap() -} -fn get_dauth_resolver() -> policy_reasoner::auth::JwtResolver { - let kid_resolver = KidResolver::new("./examples/config/jwk_set_delib.json").unwrap(); - let r = File::open("./examples/config/jwt_resolver.yaml").unwrap(); - let jwt_cfg: JwtConfig = serde_yaml::from_reader(r).unwrap(); - JwtResolver::new(jwt_cfg, kid_resolver).unwrap() -} - -/***** PLUGINS *****/ -/// The plugin used to do the audit logging. -type AuditLogPlugin = FileLogger; - -/// The plugin used to do authentication for the policy expert API. -type PolicyAuthResolverPlugin = JwtResolver; -/// The plugin used to do authentication for the deliberation API. -type DeliberationAuthResolverPlugin = JwtResolver; - -/// The plugin used to interact with the policy store. -type PolicyStorePlugin = SqlitePolicyDataStore; - -// TODO: Might need to support cfg. -type PosixReasonerConnectorPlugin = posix::PosixReasonerConnector; - -/// The plugin used to resolve policy input state. -#[cfg(feature = "brane-api-resolver")] -type StateResolverPlugin = crate::state::BraneApiResolver; -#[cfg(not(feature = "brane-api-resolver"))] -type StateResolverPlugin = state::FileStateResolver; - -/***** ENTRYPOINT *****/ -#[tokio::main] -async fn main() { - if dotenvy::dotenv().is_err() { - eprintln!("Could not load or find .env file. Assuming all necessary environment variables are set"); - } - - // Parse arguments - let args: Arguments = Arguments::parse(); - - let data_index = brane_shr::utilities::create_data_index_from( - std::env::var("DATA_INDEX").expect("Data index should either be provided by environment variable (DATA_INDEX) or in the .env file."), - ); - let rconn = PosixReasonerConnectorPlugin::new(data_index); - - // Setup a logger - if let Err(err) = HumanLogger::terminal(if args.trace { DebugMode::Full } else { DebugMode::Debug }).init() { - eprintln!("WARNING: Failed to setup logger: {err} (no logging for this session)"); - } - info!("{} - v{}", env!("CARGO_PKG_NAME"), env!("CARGO_PKG_VERSION")); - - // Handle help - // TODO: This should be refactored a bit, as we are creating multiple reasoners now, we probably want to use dynamic dispatch - let mut exit: bool = false; - // if args.help_reasoner_connector { - // println!("{}", rconn::help('r', "reasoner-connector")); - // exit = true; - // } - if args.help_state_resolver { - println!("{}", StateResolverPlugin::help('s', "state-resolver")); - exit = true; - } - if exit { - std::process::exit(0); - } - - run_app(args, rconn).await; // TODO: Add cfg support -} - -async fn run_app(args: Arguments, rconn: R) -where - R: ReasonerConnector + Send + Sync + 'static, -{ - // Initialize the plugins - let log_identifier = format!("{binary} v{version}", binary = env!("CARGO_BIN_NAME"), version = env!("CARGO_PKG_VERSION")); - let logger: AuditLogPlugin = FileLogger::new(log_identifier, "./audit-log.log"); - let pauthresolver: PolicyAuthResolverPlugin = get_pauth_resolver(); - let dauthresolver: DeliberationAuthResolverPlugin = get_dauth_resolver(); - let pstore: PolicyStorePlugin = SqlitePolicyDataStore::new("./data/policy.db"); - - let sresolve: StateResolverPlugin = match StateResolverPlugin::new(args.state_resolver.unwrap_or_default()) { - Ok(sresolve) => sresolve, - Err(err) => { - error!("{}", err.trace()); - std::process::exit(1); - }, - }; - - // Run them! - let server = Srv::new(args.address, logger, rconn, pstore, sresolve, pauthresolver, dauthresolver); - - server.run().await; -} diff --git a/src/graphql/api_schema.json b/src/graphql/api_schema.json deleted file mode 100644 index 5d69ecc..0000000 --- a/src/graphql/api_schema.json +++ /dev/null @@ -1,1241 +0,0 @@ -{ - "data": { - "__schema": { - "directives": [ - { - "args": [ - { - "defaultValue": null, - "description": null, - "name": "if", - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { - "kind": "SCALAR", - "name": "Boolean", - "ofType": null - } - } - } - ], - "description": null, - "locations": [ - "FIELD", - "FRAGMENT_SPREAD", - "INLINE_FRAGMENT" - ], - "name": "include" - }, - { - "args": [ - { - "defaultValue": null, - "description": null, - "name": "if", - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { - "kind": "SCALAR", - "name": "Boolean", - "ofType": null - } - } - } - ], - "description": null, - "locations": [ - "FIELD", - "FRAGMENT_SPREAD", - "INLINE_FRAGMENT" - ], - "name": "skip" - } - ], - "mutationType": { - "name": "Mutations" - }, - "queryType": { - "name": "Query" - }, - "subscriptionType": null, - "types": [ - { - "description": null, - "enumValues": null, - "fields": null, - "inputFields": null, - "interfaces": null, - "kind": "SCALAR", - "name": "Boolean", - "possibleTypes": null - }, - { - "description": null, - "enumValues": null, - "fields": [ - { - "args": [], - "deprecationReason": null, - "description": null, - "isDeprecated": false, - "name": "name", - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { - "kind": "SCALAR", - "name": "String", - "ofType": null - } - } - }, - { - "args": [], - "deprecationReason": null, - "description": null, - "isDeprecated": false, - "name": "description", - "type": { - "kind": "SCALAR", - "name": "String", - "ofType": null - } - }, - { - "args": [], - "deprecationReason": null, - "description": null, - "isDeprecated": false, - "name": "type", - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { - "kind": "OBJECT", - "name": "__Type", - "ofType": null - } - } - }, - { - "args": [], - "deprecationReason": null, - "description": null, - "isDeprecated": false, - "name": "defaultValue", - "type": { - "kind": "SCALAR", - "name": "String", - "ofType": null - } - } - ], - "inputFields": null, - "interfaces": [], - "kind": "OBJECT", - "name": "__InputValue", - "possibleTypes": null - }, - { - "description": null, - "enumValues": null, - "fields": null, - "inputFields": null, - "interfaces": null, - "kind": "SCALAR", - "name": "String", - "possibleTypes": null - }, - { - "description": null, - "enumValues": null, - "fields": [ - { - "args": [], - "deprecationReason": null, - "description": null, - "isDeprecated": false, - "name": "name", - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { - "kind": "SCALAR", - "name": "String", - "ofType": null - } - } - }, - { - "args": [], - "deprecationReason": null, - "description": null, - "isDeprecated": false, - "name": "description", - "type": { - "kind": "SCALAR", - "name": "String", - "ofType": null - } - }, - { - "args": [], - "deprecationReason": null, - "description": null, - "isDeprecated": false, - "name": "args", - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { - "kind": "LIST", - "name": null, - "ofType": { - "kind": "NON_NULL", - "name": null, - "ofType": { - "kind": "OBJECT", - "name": "__InputValue", - "ofType": null - } - } - } - } - }, - { - "args": [], - "deprecationReason": null, - "description": null, - "isDeprecated": false, - "name": "type", - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { - "kind": "OBJECT", - "name": "__Type", - "ofType": null - } - } - }, - { - "args": [], - "deprecationReason": null, - "description": null, - "isDeprecated": false, - "name": "isDeprecated", - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { - "kind": "SCALAR", - "name": "Boolean", - "ofType": null - } - } - }, - { - "args": [], - "deprecationReason": null, - "description": null, - "isDeprecated": false, - "name": "deprecationReason", - "type": { - "kind": "SCALAR", - "name": "String", - "ofType": null - } - } - ], - "inputFields": null, - "interfaces": [], - "kind": "OBJECT", - "name": "__Field", - "possibleTypes": null - }, - { - "description": "GraphQL type kind\n\nThe GraphQL specification defines a number of type kinds - the meta type of a type.", - "enumValues": [ - { - "deprecationReason": null, - "description": "## Scalar types\n\nScalar types appear as the leaf nodes of GraphQL queries. Strings, numbers, and booleans are the built in types, and while it's possible to define your own, it's relatively uncommon.", - "isDeprecated": false, - "name": "SCALAR" - }, - { - "deprecationReason": null, - "description": "## Object types\n\nThe most common type to be implemented by users. Objects have fields and can implement interfaces.", - "isDeprecated": false, - "name": "OBJECT" - }, - { - "deprecationReason": null, - "description": "## Interface types\n\nInterface types are used to represent overlapping fields between multiple types, and can be queried for their concrete type.", - "isDeprecated": false, - "name": "INTERFACE" - }, - { - "deprecationReason": null, - "description": "## Union types\n\nUnions are similar to interfaces but can not contain any fields on their own.", - "isDeprecated": false, - "name": "UNION" - }, - { - "deprecationReason": null, - "description": "## Enum types\n\nLike scalars, enum types appear as the leaf nodes of GraphQL queries.", - "isDeprecated": false, - "name": "ENUM" - }, - { - "deprecationReason": null, - "description": "## Input objects\n\nRepresents complex values provided in queries _into_ the system.", - "isDeprecated": false, - "name": "INPUT_OBJECT" - }, - { - "deprecationReason": null, - "description": "## List types\n\nRepresent lists of other types. This library provides implementations for vectors and slices, but other Rust types can be extended to serve as GraphQL lists.", - "isDeprecated": false, - "name": "LIST" - }, - { - "deprecationReason": null, - "description": "## Non-null types\n\nIn GraphQL, nullable types are the default. By putting a `!` after a type, it becomes non-nullable.", - "isDeprecated": false, - "name": "NON_NULL" - } - ], - "fields": null, - "inputFields": null, - "interfaces": null, - "kind": "ENUM", - "name": "__TypeKind", - "possibleTypes": null - }, - { - "description": "DateTime", - "enumValues": null, - "fields": null, - "inputFields": null, - "interfaces": null, - "kind": "SCALAR", - "name": "DateTimeUtc", - "possibleTypes": null - }, - { - "description": null, - "enumValues": null, - "fields": [ - { - "args": [], - "deprecationReason": null, - "description": null, - "isDeprecated": false, - "name": "created", - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { - "kind": "SCALAR", - "name": "DateTimeUtc", - "ofType": null - } - } - }, - { - "args": [], - "deprecationReason": null, - "description": null, - "isDeprecated": false, - "name": "description", - "type": { - "kind": "SCALAR", - "name": "String", - "ofType": null - } - }, - { - "args": [], - "deprecationReason": null, - "description": null, - "isDeprecated": false, - "name": "detached", - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { - "kind": "SCALAR", - "name": "Boolean", - "ofType": null - } - } - }, - { - "args": [], - "deprecationReason": null, - "description": null, - "isDeprecated": false, - "name": "digest", - "type": { - "kind": "SCALAR", - "name": "String", - "ofType": null - } - }, - { - "args": [], - "deprecationReason": null, - "description": null, - "isDeprecated": false, - "name": "owners", - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { - "kind": "LIST", - "name": null, - "ofType": { - "kind": "NON_NULL", - "name": null, - "ofType": { - "kind": "SCALAR", - "name": "String", - "ofType": null - } - } - } - } - }, - { - "args": [], - "deprecationReason": null, - "description": null, - "isDeprecated": false, - "name": "id", - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { - "kind": "SCALAR", - "name": "Uuid", - "ofType": null - } - } - }, - { - "args": [], - "deprecationReason": null, - "description": null, - "isDeprecated": false, - "name": "kind", - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { - "kind": "SCALAR", - "name": "String", - "ofType": null - } - } - }, - { - "args": [], - "deprecationReason": null, - "description": null, - "isDeprecated": false, - "name": "name", - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { - "kind": "SCALAR", - "name": "String", - "ofType": null - } - } - }, - { - "args": [], - "deprecationReason": null, - "description": null, - "isDeprecated": false, - "name": "version", - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { - "kind": "SCALAR", - "name": "String", - "ofType": null - } - } - }, - { - "args": [], - "deprecationReason": null, - "description": null, - "isDeprecated": false, - "name": "functionsAsJson", - "type": { - "kind": "SCALAR", - "name": "String", - "ofType": null - } - }, - { - "args": [], - "deprecationReason": null, - "description": null, - "isDeprecated": false, - "name": "typesAsJson", - "type": { - "kind": "SCALAR", - "name": "String", - "ofType": null - } - } - ], - "inputFields": null, - "interfaces": [], - "kind": "OBJECT", - "name": "Package", - "possibleTypes": null - }, - { - "description": null, - "enumValues": null, - "fields": [ - { - "args": [], - "deprecationReason": null, - "description": null, - "isDeprecated": false, - "name": "name", - "type": { - "kind": "SCALAR", - "name": "String", - "ofType": null - } - }, - { - "args": [], - "deprecationReason": null, - "description": null, - "isDeprecated": false, - "name": "description", - "type": { - "kind": "SCALAR", - "name": "String", - "ofType": null - } - }, - { - "args": [], - "deprecationReason": null, - "description": null, - "isDeprecated": false, - "name": "kind", - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { - "kind": "ENUM", - "name": "__TypeKind", - "ofType": null - } - } - }, - { - "args": [ - { - "defaultValue": "false", - "description": null, - "name": "includeDeprecated", - "type": { - "kind": "SCALAR", - "name": "Boolean", - "ofType": null - } - } - ], - "deprecationReason": null, - "description": null, - "isDeprecated": false, - "name": "fields", - "type": { - "kind": "LIST", - "name": null, - "ofType": { - "kind": "NON_NULL", - "name": null, - "ofType": { - "kind": "OBJECT", - "name": "__Field", - "ofType": null - } - } - } - }, - { - "args": [], - "deprecationReason": null, - "description": null, - "isDeprecated": false, - "name": "ofType", - "type": { - "kind": "OBJECT", - "name": "__Type", - "ofType": null - } - }, - { - "args": [], - "deprecationReason": null, - "description": null, - "isDeprecated": false, - "name": "inputFields", - "type": { - "kind": "LIST", - "name": null, - "ofType": { - "kind": "NON_NULL", - "name": null, - "ofType": { - "kind": "OBJECT", - "name": "__InputValue", - "ofType": null - } - } - } - }, - { - "args": [], - "deprecationReason": null, - "description": null, - "isDeprecated": false, - "name": "interfaces", - "type": { - "kind": "LIST", - "name": null, - "ofType": { - "kind": "NON_NULL", - "name": null, - "ofType": { - "kind": "OBJECT", - "name": "__Type", - "ofType": null - } - } - } - }, - { - "args": [], - "deprecationReason": null, - "description": null, - "isDeprecated": false, - "name": "possibleTypes", - "type": { - "kind": "LIST", - "name": null, - "ofType": { - "kind": "NON_NULL", - "name": null, - "ofType": { - "kind": "OBJECT", - "name": "__Type", - "ofType": null - } - } - } - }, - { - "args": [ - { - "defaultValue": "false", - "description": null, - "name": "includeDeprecated", - "type": { - "kind": "SCALAR", - "name": "Boolean", - "ofType": null - } - } - ], - "deprecationReason": null, - "description": null, - "isDeprecated": false, - "name": "enumValues", - "type": { - "kind": "LIST", - "name": null, - "ofType": { - "kind": "NON_NULL", - "name": null, - "ofType": { - "kind": "OBJECT", - "name": "__EnumValue", - "ofType": null - } - } - } - } - ], - "inputFields": null, - "interfaces": [], - "kind": "OBJECT", - "name": "__Type", - "possibleTypes": null - }, - { - "description": null, - "enumValues": null, - "fields": [ - { - "args": [], - "deprecationReason": null, - "description": null, - "isDeprecated": false, - "name": "types", - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { - "kind": "LIST", - "name": null, - "ofType": { - "kind": "NON_NULL", - "name": null, - "ofType": { - "kind": "OBJECT", - "name": "__Type", - "ofType": null - } - } - } - } - }, - { - "args": [], - "deprecationReason": null, - "description": null, - "isDeprecated": false, - "name": "queryType", - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { - "kind": "OBJECT", - "name": "__Type", - "ofType": null - } - } - }, - { - "args": [], - "deprecationReason": null, - "description": null, - "isDeprecated": false, - "name": "mutationType", - "type": { - "kind": "OBJECT", - "name": "__Type", - "ofType": null - } - }, - { - "args": [], - "deprecationReason": null, - "description": null, - "isDeprecated": false, - "name": "subscriptionType", - "type": { - "kind": "OBJECT", - "name": "__Type", - "ofType": null - } - }, - { - "args": [], - "deprecationReason": null, - "description": null, - "isDeprecated": false, - "name": "directives", - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { - "kind": "LIST", - "name": null, - "ofType": { - "kind": "NON_NULL", - "name": null, - "ofType": { - "kind": "OBJECT", - "name": "__Directive", - "ofType": null - } - } - } - } - } - ], - "inputFields": null, - "interfaces": [], - "kind": "OBJECT", - "name": "__Schema", - "possibleTypes": null - }, - { - "description": null, - "enumValues": null, - "fields": [ - { - "args": [ - { - "defaultValue": null, - "description": null, - "name": "username", - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { - "kind": "SCALAR", - "name": "String", - "ofType": null - } - } - }, - { - "defaultValue": null, - "description": null, - "name": "password", - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { - "kind": "SCALAR", - "name": "String", - "ofType": null - } - } - } - ], - "deprecationReason": null, - "description": "\n\n", - "isDeprecated": false, - "name": "login", - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { - "kind": "SCALAR", - "name": "String", - "ofType": null - } - } - }, - { - "args": [ - { - "defaultValue": null, - "description": null, - "name": "name", - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { - "kind": "SCALAR", - "name": "String", - "ofType": null - } - } - }, - { - "defaultValue": null, - "description": null, - "name": "version", - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { - "kind": "SCALAR", - "name": "String", - "ofType": null - } - } - } - ], - "deprecationReason": null, - "description": "\n\n", - "isDeprecated": false, - "name": "unpublishPackage", - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { - "kind": "SCALAR", - "name": "String", - "ofType": null - } - } - } - ], - "inputFields": null, - "interfaces": [], - "kind": "OBJECT", - "name": "Mutations", - "possibleTypes": null - }, - { - "description": null, - "enumValues": null, - "fields": [ - { - "args": [], - "deprecationReason": null, - "description": "\n\n", - "isDeprecated": false, - "name": "apiVersion", - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { - "kind": "SCALAR", - "name": "String", - "ofType": null - } - } - }, - { - "args": [ - { - "defaultValue": null, - "description": null, - "name": "name", - "type": { - "kind": "SCALAR", - "name": "String", - "ofType": null - } - }, - { - "defaultValue": null, - "description": null, - "name": "version", - "type": { - "kind": "SCALAR", - "name": "String", - "ofType": null - } - }, - { - "defaultValue": null, - "description": null, - "name": "term", - "type": { - "kind": "SCALAR", - "name": "String", - "ofType": null - } - } - ], - "deprecationReason": null, - "description": "\n\n", - "isDeprecated": false, - "name": "packages", - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { - "kind": "LIST", - "name": null, - "ofType": { - "kind": "NON_NULL", - "name": null, - "ofType": { - "kind": "OBJECT", - "name": "Package", - "ofType": null - } - } - } - } - } - ], - "inputFields": null, - "interfaces": [], - "kind": "OBJECT", - "name": "Query", - "possibleTypes": null - }, - { - "description": null, - "enumValues": null, - "fields": [ - { - "args": [], - "deprecationReason": null, - "description": null, - "isDeprecated": false, - "name": "name", - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { - "kind": "SCALAR", - "name": "String", - "ofType": null - } - } - }, - { - "args": [], - "deprecationReason": null, - "description": null, - "isDeprecated": false, - "name": "description", - "type": { - "kind": "SCALAR", - "name": "String", - "ofType": null - } - }, - { - "args": [], - "deprecationReason": null, - "description": null, - "isDeprecated": false, - "name": "isDeprecated", - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { - "kind": "SCALAR", - "name": "Boolean", - "ofType": null - } - } - }, - { - "args": [], - "deprecationReason": null, - "description": null, - "isDeprecated": false, - "name": "deprecationReason", - "type": { - "kind": "SCALAR", - "name": "String", - "ofType": null - } - } - ], - "inputFields": null, - "interfaces": [], - "kind": "OBJECT", - "name": "__EnumValue", - "possibleTypes": null - }, - { - "description": "Uuid", - "enumValues": null, - "fields": null, - "inputFields": null, - "interfaces": null, - "kind": "SCALAR", - "name": "Uuid", - "possibleTypes": null - }, - { - "description": null, - "enumValues": [ - { - "deprecationReason": null, - "description": null, - "isDeprecated": false, - "name": "QUERY" - }, - { - "deprecationReason": null, - "description": null, - "isDeprecated": false, - "name": "MUTATION" - }, - { - "deprecationReason": null, - "description": null, - "isDeprecated": false, - "name": "SUBSCRIPTION" - }, - { - "deprecationReason": null, - "description": null, - "isDeprecated": false, - "name": "FIELD" - }, - { - "deprecationReason": null, - "description": null, - "isDeprecated": false, - "name": "FRAGMENT_DEFINITION" - }, - { - "deprecationReason": null, - "description": null, - "isDeprecated": false, - "name": "FRAGMENT_SPREAD" - }, - { - "deprecationReason": null, - "description": null, - "isDeprecated": false, - "name": "INLINE_FRAGMENT" - } - ], - "fields": null, - "inputFields": null, - "interfaces": null, - "kind": "ENUM", - "name": "__DirectiveLocation", - "possibleTypes": null - }, - { - "description": null, - "enumValues": null, - "fields": [ - { - "args": [], - "deprecationReason": null, - "description": null, - "isDeprecated": false, - "name": "name", - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { - "kind": "SCALAR", - "name": "String", - "ofType": null - } - } - }, - { - "args": [], - "deprecationReason": null, - "description": null, - "isDeprecated": false, - "name": "description", - "type": { - "kind": "SCALAR", - "name": "String", - "ofType": null - } - }, - { - "args": [], - "deprecationReason": null, - "description": null, - "isDeprecated": false, - "name": "locations", - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { - "kind": "LIST", - "name": null, - "ofType": { - "kind": "NON_NULL", - "name": null, - "ofType": { - "kind": "ENUM", - "name": "__DirectiveLocation", - "ofType": null - } - } - } - } - }, - { - "args": [], - "deprecationReason": null, - "description": null, - "isDeprecated": false, - "name": "args", - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { - "kind": "LIST", - "name": null, - "ofType": { - "kind": "NON_NULL", - "name": null, - "ofType": { - "kind": "OBJECT", - "name": "__InputValue", - "ofType": null - } - } - } - } - }, - { - "args": [], - "deprecationReason": "Use the locations array instead", - "description": null, - "isDeprecated": true, - "name": "onOperation", - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { - "kind": "SCALAR", - "name": "Boolean", - "ofType": null - } - } - }, - { - "args": [], - "deprecationReason": "Use the locations array instead", - "description": null, - "isDeprecated": true, - "name": "onFragment", - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { - "kind": "SCALAR", - "name": "Boolean", - "ofType": null - } - } - }, - { - "args": [], - "deprecationReason": "Use the locations array instead", - "description": null, - "isDeprecated": true, - "name": "onField", - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { - "kind": "SCALAR", - "name": "Boolean", - "ofType": null - } - } - } - ], - "inputFields": null, - "interfaces": [], - "kind": "OBJECT", - "name": "__Directive", - "possibleTypes": null - } - ] - } - } -} \ No newline at end of file diff --git a/src/graphql/search_packages.graphql b/src/graphql/search_packages.graphql deleted file mode 100644 index e29f416..0000000 --- a/src/graphql/search_packages.graphql +++ /dev/null @@ -1,14 +0,0 @@ -query SearchPackages($term: String) { - packages(term: $term) { - created, - description, - detached, - functionsAsJson, - id, - kind, - name, - owners, - typesAsJson, - version - } -} \ No newline at end of file diff --git a/src/jwt.rs b/src/jwt.rs deleted file mode 100644 index bbc1e70..0000000 --- a/src/jwt.rs +++ /dev/null @@ -1,107 +0,0 @@ -use std::collections::HashMap; -use std::fs; -use std::fs::File; - -use auth_resolver::{AuthContext, AuthResolver, AuthResolverError}; -use jsonwebtoken::jwk::JwkSet; -use jsonwebtoken::{DecodingKey, Header, Validation}; -use serde::Deserialize; -use warp::http::{HeaderMap, HeaderValue}; - -#[async_trait::async_trait] -pub trait KeyResolver { - async fn resolve_key(&self, header: &Header) -> Result; -} - -pub struct KidResolver { - jwk_store: JwkSet, -} - -impl KidResolver { - pub fn new(key_set_loc: &str) -> Result { - let r = fs::read_to_string(key_set_loc) - .map_err(|err| AuthResolverError::new(format!("Could not load jwk set from location: {}; {}", key_set_loc, err)))?; - let keyfile: JwkSet = serde_json::from_str(&r).map_err(|err| AuthResolverError::new(format!("Could not load parse jwk set: {}", err)))?; - - Ok(Self { jwk_store: keyfile }) - } -} - -#[async_trait::async_trait] -impl KeyResolver for KidResolver { - async fn resolve_key(&self, header: &Header) -> Result { - let kid = header.kid.as_ref().ok_or_else(|| AuthResolverError::new("No kid present in header".into()))?; - - match self.jwk_store.find(&kid) { - Some(key) => DecodingKey::from_jwk(key) - .map_err(|err| AuthResolverError::new(format!("Could not transform jwk ({}) into DecodingKey: {}", kid, err))), - None => Err(AuthResolverError::new(format!("Could not find key for kid: {}", kid))), - } - } -} - -pub struct JwtResolver { - config: JwtConfig, - key_resolver: KR, -} - -#[derive(Deserialize)] -pub struct JwtConfig { - initiator_claim: String, -} - -impl JwtResolver -where - KR: KeyResolver + Sync, -{ - pub fn new(config: JwtConfig, key_resolver: KR) -> Result> { return Ok(JwtResolver { config, key_resolver }); } - - pub fn extract_jwt(&self, auth_header: Option<&HeaderValue>) -> Result { - let header_val: &str = match auth_header { - Some(v) => match v.to_str() { - Ok(v) => v, - Err(_) => return Err(AuthResolverError::new("Invalid authorization header".into())), - }, - None => { - return Err(AuthResolverError::new("Authorization header not present".into())); - }, - }; - - let parts = header_val.splitn(2, " ").collect::>(); - - if parts[0] != "Bearer" { - return Err(AuthResolverError::new("Invalid authorization header".into())); - } - - Ok(parts[1].into()) - } -} - -#[async_trait::async_trait] -impl AuthResolver for JwtResolver -where - KR: KeyResolver + Sync + Send, -{ - async fn authenticate(&self, headers: HeaderMap) -> Result { - let raw_jwt = self.extract_jwt(headers.get("Authorization"))?; - - let header = jsonwebtoken::decode_header(&raw_jwt).map_err(|err| AuthResolverError::new(format!("Could not parse header: {}", err)))?; - - let decoding_key = self.key_resolver.resolve_key(&header).await?; - let validation = Validation::new(header.alg); - let result = jsonwebtoken::decode::>(&raw_jwt, &decoding_key, &validation) - .map_err(|err| AuthResolverError::new(format!("Could not validate jwt: {}", err)))?; - - match result.claims.get(&self.config.initiator_claim) { - Some(initiator) => match initiator { - serde_json::Value::Number(v) => Ok(AuthContext { initiator: v.to_string(), system: "TODO implement!".into() }), - serde_json::Value::String(v) => Ok(AuthContext { initiator: v.clone(), system: "TODO implement!".into() }), - _ => Err(AuthResolverError::new(format!( - "Invalid type for initiator claim (only string or number allowed): {}", - self.config.initiator_claim - ))), - }, - None => Err(AuthResolverError::new(format!("Missing initiator claim: {}", self.config.initiator_claim))), - } - } -} diff --git a/src/lib.rs b/src/lib.rs index 909fc11..94249e6 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,6 +1,37 @@ -pub mod auth; -pub mod logger; -pub mod models; -pub mod schema; -pub mod sqlite; -pub mod state; +// LIB.rs +// by Lut99 +// +// Created: +// 08 Oct 2024, 16:13:30 +// Last edited: +// 11 Oct 2024, 16:23:03 +// Auto updated? +// Yes +// +// Description: +//! A library for using several different reasoning backends to +//! determine if a particular workflow is allowed by policy or not. +// + +/// Contains the backend reasoners. +pub mod reasoners { + #[cfg(feature = "eflint-json-reasoner")] + pub use eflint_json_reasoner as eflint_json; + #[cfg(feature = "no-op-reasoner")] + pub use no_op_reasoner as no_op; +} +/// Contains the backend loggers. +pub mod loggers { + #[cfg(feature = "file-logger")] + pub use file_logger as file; + #[cfg(feature = "mock-logger")] + pub use mock_logger as mock; +} +/// Contains any state resolvers. +pub mod resolvers { + #[cfg(feature = "file-resolver")] + pub use file_resolver as file; +} +#[cfg(feature = "eflint-to-json")] +pub use eflint_to_json; +pub use spec; diff --git a/src/logger.rs b/src/logger.rs deleted file mode 100644 index 963071f..0000000 --- a/src/logger.rs +++ /dev/null @@ -1,361 +0,0 @@ -use std::error::Error; -use std::fmt::{Debug, Display, Formatter, Result as FResult}; -use std::path::PathBuf; - -use audit_logger::{AuditLogger, ConnectorWithContext, Error as AuditLoggerError, LogStatement, ReasonerConnectorAuditLogger}; -use auth_resolver::AuthContext; -use deliberation::spec::Verdict; -use enum_debug::EnumDebug; -use error_trace::ErrorTrace as _; -use log::debug; -use policy::Policy; -use state_resolver::State; -use tokio::fs::{File, OpenOptions}; -use tokio::io::AsyncWriteExt; -use workflow::Workflow; - -/***** HELPER MACROS *****/ -/// Wraps a [`write!`]-macro to return its error as a [`FileLoggerError`]. -macro_rules! write_file { - ($path:expr, $handle:expr, $($t:tt)+) => { - // Psych we actually don't wrap that macro, since we're doing async ofc - async { - use tokio::io::AsyncWriteExt as _; - let contents: String = format!($($t)+); - $handle.write_all(contents.as_bytes()).await.map_err(|err| FileLoggerError::FileWrite { path: ($path), err }) - } - }; -} - -/// Wraps a [`writeln!`]-macro to return its error as a [`FileLoggerError`]. -macro_rules! writeln_file { - ($path:expr, $handle:expr) => { - // Psych we actually don't wrap that macro, since we're doing async ofc - async { - use tokio::io::AsyncWriteExt as _; - $handle.write_all(b"\n").await.map_err(|err| FileLoggerError::FileWrite { path: ($path), err }) - } - }; - ($path:expr, $handle:expr, $($t:tt)+) => { - // Psych we actually don't wrap that macro, since we're doing async ofc - async { - use tokio::io::AsyncWriteExt as _; - let mut contents: String = format!($($t)*); - contents.push('\n'); - $handle.write_all(contents.as_bytes()).await.map_err(|err| FileLoggerError::FileWrite { path: ($path), err }) - } - }; -} - -/***** ERRORS *****/ -/// Defines errors originating from the [`FileLogger`]. -#[derive(Debug)] -pub enum FileLoggerError { - /// Failed to create a new logfile. - FileCreate { path: PathBuf, err: std::io::Error }, - /// Failed to open an existing logfile. - FileOpen { path: PathBuf, err: std::io::Error }, - /// Failed to seek in the logfile. - FileSeek { path: PathBuf, err: std::io::Error }, - /// Failed to flush the given logfile. - FileShutdown { path: PathBuf, err: std::io::Error }, - /// Failed to write to the logfile. - FileWrite { path: PathBuf, err: std::io::Error }, - /// Failed to serialize a statement. - StatementSerialize { kind: String, err: serde_json::Error }, -} -impl Display for FileLoggerError { - fn fmt(&self, f: &mut Formatter<'_>) -> FResult { - use FileLoggerError::*; - match self { - FileCreate { path, .. } => write!(f, "Failed to create new log file '{}'", path.display()), - FileOpen { path, .. } => write!(f, "Failed to open existing log file '{}'", path.display()), - FileSeek { path, .. } => write!(f, "Failed to seek in log file '{}'", path.display()), - FileShutdown { path, .. } => write!(f, "Failed to flush log file '{}'", path.display()), - FileWrite { path, .. } => write!(f, "Failed to write to log file '{}'", path.display()), - StatementSerialize { kind, .. } => write!(f, "Failed to serialize {kind}"), - } - } -} -impl Error for FileLoggerError { - fn source(&self) -> Option<&(dyn Error + 'static)> { - use FileLoggerError::*; - match self { - FileCreate { err, .. } => Some(err), - FileOpen { err, .. } => Some(err), - FileSeek { err, .. } => Some(err), - FileShutdown { err, .. } => Some(err), - FileWrite { err, .. } => Some(err), - StatementSerialize { err, .. } => Some(err), - } - } -} - -/***** LIBRARY *****/ -/// A mock version of the logger that simply ignores all logged statements. -/// -/// Just here for testing purposes. -pub struct MockLogger {} -impl Default for MockLogger { - #[inline] - fn default() -> Self { Self::new() } -} -impl MockLogger { - #[inline] - pub fn new() -> Self { MockLogger {} } -} -impl Clone for MockLogger { - fn clone(&self) -> Self { Self {} } -} -#[async_trait::async_trait] -impl AuditLogger for MockLogger { - async fn log_exec_task_request( - &self, - _reference: &str, - _auth: &AuthContext, - _policy: i64, - _state: &State, - _workflow: &Workflow, - _task: &str, - ) -> Result<(), AuditLoggerError> { - println!("AUDIT LOG: log_exec_task_request"); - Ok(()) - } - - async fn log_data_access_request( - &self, - _reference: &str, - _auth: &AuthContext, - _policy: i64, - _state: &State, - _workflow: &Workflow, - _data: &str, - _task: &Option, - ) -> Result<(), AuditLoggerError> { - println!("AUDIT LOG: log_data_access_request"); - Ok(()) - } - - async fn log_validate_workflow_request( - &self, - _reference: &str, - _auth: &AuthContext, - _policy: i64, - _state: &State, - _workflow: &Workflow, - ) -> Result<(), AuditLoggerError> { - println!("AUDIT LOG: log_validate_workflow_request"); - Ok(()) - } - - async fn log_verdict(&self, _reference: &str, _verdict: &Verdict) -> Result<(), AuditLoggerError> { - println!("AUDIT LOG: log_verdict"); - Ok(()) - } - - async fn log_add_policy_request(&self, _auth: &AuthContext, _policy: &Policy) -> Result<(), AuditLoggerError> { - println!("AUDIT LOG: log_add_policy_request"); - Ok(()) - } - - async fn log_set_active_version_policy(&self, _auth: &AuthContext, _policy: &Policy) -> Result<(), AuditLoggerError> { - println!("AUDIT LOG: log_set_active_version_policy"); - Ok(()) - } - - async fn log_deactivate_policy(&self, _auth: &AuthContext) -> Result<(), AuditLoggerError> { - println!("AUDIT LOG: log_deactivate_policy"); - Ok(()) - } - - async fn log_reasoner_context(&self) -> Result<(), AuditLoggerError> { - println!("AUDIT LOG: log_reasoner_context"); - Ok(()) - } -} - -#[async_trait::async_trait] -impl ReasonerConnectorAuditLogger for MockLogger { - async fn log_reasoner_response(&self, _reference: &str, _response: &str) -> Result<(), AuditLoggerError> { - println!("AUDIT LOG: log_reasoner_response"); - Ok(()) - } -} - -/// A more serious version of a logger that logs to a file. -/// -/// Note that this logger is not exactly the perfect audit log, as it does nothing w.r.t. ensuring that the file is the same as last time or signing changes or w/e. -#[derive(Clone)] -pub struct FileLogger { - /// The identifier of source of the logger. E.g. "policy-reasoner v1.2.3". - /// This value will be printed before all log entries - identifier: String, - - /// The path of the file to log to. - path: PathBuf, -} -impl FileLogger { - /// Constructor for the FileLogger that initializes it pointing to the given file. - /// - /// # Arguments - /// - `path`: The path to the file to log to. - /// - /// # Returns - /// A new instance of self, ready for action. - #[inline] - pub fn new(identifier: String, path: impl Into) -> Self { Self { identifier, path: path.into() } } - - /// Writes a log statement to the logging file. - /// - /// # Arguments - /// - `stmt`: The [`LogStatement`] that determines what we're gonna log. - /// - /// # Errors - /// This function errors if we failed to perform the logging completely (i.e., either write or flush). - pub async fn log(&self, stmt: LogStatement<'_>) -> Result<(), FileLoggerError> { - // Step 1: Open the log file - let mut handle: File = if !self.path.exists() { - debug!("Creating new log file at '{}'...", self.path.display()); - match File::create(&self.path).await { - Ok(handle) => handle, - Err(err) => return Err(FileLoggerError::FileCreate { path: self.path.clone(), err }), - } - } else { - debug!("Opening existing log file at '{}'...", self.path.display()); - match OpenOptions::new().write(true).append(true).open(&self.path).await { - Ok(handle) => handle, - Err(err) => return Err(FileLoggerError::FileOpen { path: self.path.clone(), err }), - } - }; - - // // Navigate to the end of the file - // let end_pos: u64 = match handle.seek(SeekFrom::End(0)).await { - // Ok(pos) => pos, - // Err(err) => return Err(FileLoggerError::FileSeek { path: self.path.clone(), err }), - // }; - // debug!("End of file is after {end_pos} bytes"); - - // Write the message - debug!("Writing {}-statement to logfile...", stmt.variant()); - // Write who wrote it - write_file!(self.path.clone(), &mut handle, "[{}]", self.identifier).await?; - // Print the timestamp - write_file!(self.path.clone(), &mut handle, "[{}]", chrono::Local::now().format("%Y-%m-%d %H:%M:%S")).await?; - // Then write the logged message - match serde_json::to_string(&stmt) { - Ok(message) => writeln_file!(self.path.clone(), &mut handle, " {message}").await?, - Err(err) => return Err(FileLoggerError::StatementSerialize { kind: format!("{:?}", stmt.variant()), err }), - } - - // Finally flush the file - debug!("Flushing log file..."); - if let Err(err) = handle.shutdown().await { - return Err(FileLoggerError::FileShutdown { path: self.path.clone(), err }); - } - drop(handle); - - // Done, a smashing success - Ok(()) - } -} -#[async_trait::async_trait] -impl AuditLogger for FileLogger { - async fn log_exec_task_request( - &self, - reference: &str, - auth: &AuthContext, - policy: i64, - state: &State, - workflow: &Workflow, - task: &str, - ) -> Result<(), AuditLoggerError> { - debug!("Handling request to log execute_task request"); - - // Construct the full message that we want to log, then log it (simple as that) - let stmt: LogStatement = LogStatement::execute_task(reference, auth, policy, state, workflow, task); - self.log(stmt).await.map_err(|err| AuditLoggerError::CouldNotDeliver(format!("{}", err.trace()))) - } - - async fn log_data_access_request( - &self, - reference: &str, - auth: &AuthContext, - policy: i64, - state: &State, - workflow: &Workflow, - data: &str, - task: &Option, - ) -> Result<(), AuditLoggerError> { - debug!("Handling request to log data_access request"); - - // Construct the full message that we want to log, then log it (simple as that) - let stmt = LogStatement::asset_access(reference, auth, policy, state, workflow, data, task); - self.log(stmt).await.map_err(|err| AuditLoggerError::CouldNotDeliver(format!("{}", err.trace()))) - } - - async fn log_validate_workflow_request( - &self, - reference: &str, - auth: &AuthContext, - policy: i64, - state: &State, - workflow: &Workflow, - ) -> Result<(), AuditLoggerError> { - debug!("Handling request to log workflow_validate request"); - - // Construct the full message that we want to log, then log it (simple as that) - let stmt = LogStatement::workflow_validate(reference, auth, policy, state, workflow); - self.log(stmt).await.map_err(|err| AuditLoggerError::CouldNotDeliver(format!("{}", err.trace()))) - } - - async fn log_verdict(&self, reference: &str, verdict: &Verdict) -> Result<(), AuditLoggerError> { - debug!("Handling request to log reasoner verdict"); - - // Construct the full message that we want to log, then log it (simple as that) - let stmt = LogStatement::reasoner_verdict(reference, verdict); - self.log(stmt).await.map_err(|err| AuditLoggerError::CouldNotDeliver(format!("{}", err.trace()))) - } - - async fn log_reasoner_context(&self) -> Result<(), AuditLoggerError> { - debug!("Handling request to log reasoner connector context"); - - // Construct the full message that we want to log, then log it (simple as that) - let stmt: LogStatement = LogStatement::reasoner_context::(); - self.log(stmt).await.map_err(|err| AuditLoggerError::CouldNotDeliver(format!("{}", err.trace()))) - } - - async fn log_add_policy_request(&self, auth: &AuthContext, policy: &Policy) -> Result<(), AuditLoggerError> { - debug!("Handling request to log policy add"); - - // Construct the full message that we want to log, then log it (simple as that) - let stmt: LogStatement = LogStatement::policy_add::(auth, policy); - self.log(stmt).await.map_err(|err| AuditLoggerError::CouldNotDeliver(format!("{}", err.trace()))) - } - - async fn log_set_active_version_policy(&self, auth: &AuthContext, policy: &Policy) -> Result<(), AuditLoggerError> { - debug!("Handling request to log policy activate"); - - // Construct the full message that we want to log, then log it (simple as that) - let stmt = LogStatement::policy_activate(auth, policy); - self.log(stmt).await.map_err(|err| AuditLoggerError::CouldNotDeliver(format!("{}", err.trace()))) - } - - async fn log_deactivate_policy(&self, auth: &AuthContext) -> Result<(), AuditLoggerError> { - debug!("Handling request to log policy deactivation"); - - // Construct the full message that we want to log, then log it (simple as that) - let stmt = LogStatement::policy_deactivate(auth); - self.log(stmt).await.map_err(|err| AuditLoggerError::CouldNotDeliver(format!("{}", err.trace()))) - } -} - -#[async_trait::async_trait] -impl ReasonerConnectorAuditLogger for FileLogger { - async fn log_reasoner_response(&self, reference: &str, response: &str) -> Result<(), AuditLoggerError> { - debug!("Handling request to log reasoner response"); - - // Construct the full message that we want to log, then log it (simple as that) - let stmt = LogStatement::reasoner_response(reference, response); - self.log(stmt).await.map_err(|err| AuditLoggerError::CouldNotDeliver(format!("{}", err.trace()))) - } -} diff --git a/src/models.rs b/src/models.rs deleted file mode 100644 index cc6c4e4..0000000 --- a/src/models.rs +++ /dev/null @@ -1,32 +0,0 @@ -use chrono::{NaiveDateTime, Utc}; -use diesel::prelude::*; - -use crate::schema::{active_version, policies}; - -#[derive(Queryable, Insertable, Selectable)] -#[diesel(table_name = policies)] -pub struct SqlitePolicy { - pub description: String, - pub version: i64, - pub version_description: String, - pub creator: String, - pub created_at: i64, - pub content: String, - pub reasoner_connector_context: String, -} - -#[derive(Queryable, Insertable, Selectable)] -#[diesel(table_name = active_version)] -pub struct SqliteActiveVersion { - pub version: i64, - pub activated_on: NaiveDateTime, - pub activated_by: String, - pub deactivated_on: Option, - pub deactivated_by: Option, -} - -impl SqliteActiveVersion { - pub fn new(version: i64, activated_by: String) -> Self { - Self { version, activated_by, activated_on: Utc::now().naive_local(), deactivated_by: None, deactivated_on: None } - } -} diff --git a/src/schema.rs b/src/schema.rs deleted file mode 100644 index df54a31..0000000 --- a/src/schema.rs +++ /dev/null @@ -1,27 +0,0 @@ -// @generated automatically by Diesel CLI. - -diesel::table! { - active_version (version, activated_on) { - version -> BigInt, - activated_on -> Timestamp, - activated_by -> Text, - deactivated_on -> Nullable, - deactivated_by -> Nullable, - } -} - -diesel::table! { - policies (version) { - version -> BigInt, - description -> Text, - version_description -> Text, - creator -> Text, - created_at -> BigInt, - content -> Text, - reasoner_connector_context -> Text, - } -} - -diesel::joinable!(active_version -> policies (version)); - -diesel::allow_tables_to_appear_in_same_query!(active_version, policies,); diff --git a/src/sqlite.rs b/src/sqlite.rs deleted file mode 100644 index ea0c383..0000000 --- a/src/sqlite.rs +++ /dev/null @@ -1,315 +0,0 @@ -use std::future::Future; - -use ::policy::{Context, Policy, PolicyContent, PolicyDataAccess, PolicyDataError, PolicyVersion}; -use chrono::{DateTime, Utc}; -use diesel::r2d2::{ConnectionManager, Pool}; -use diesel::result::Error; -use diesel::sqlite::SqliteConnection; -use diesel::{ExpressionMethods, QueryDsl, RunQueryDsl, SelectableHelper}; -use tokio::runtime::Handle; - -use crate::models::{SqliteActiveVersion, SqlitePolicy}; -pub struct SqlitePolicyDataStore { - pool: Pool>, -} - -struct SqlitePolicyDataStoreError { - msg: String, -} - -impl From for SqlitePolicyDataStoreError { - fn from(value: PolicyDataError) -> Self { - match value { - PolicyDataError::NotFound => SqlitePolicyDataStoreError { msg: "Not Found".into() }, - PolicyDataError::GeneralError(msg) => SqlitePolicyDataStoreError { msg }, - } - } -} - -impl From for SqlitePolicyDataStoreError { - fn from(value: diesel::result::Error) -> Self { Self { msg: value.to_string() } } -} - -impl From for PolicyDataError { - fn from(value: SqlitePolicyDataStoreError) -> Self { PolicyDataError::GeneralError(value.msg) } -} - -impl SqlitePolicyDataStore { - pub fn new(database_url: &str) -> Self { - let manager = ConnectionManager::::new(database_url); - // Refer to the `r2d2` documentation for more methods to use - // when building a connection pool - let pool = Pool::builder().test_on_check_out(true).build(manager).expect("Could not build connection pool"); - Self { pool } - } - - async fn _get_active(&self) -> Result { - use crate::schema::active_version::dsl::active_version; - let mut conn = self.pool.get().unwrap(); - let av: SqliteActiveVersion = match active_version - .limit(1) - .order_by(crate::schema::active_version::dsl::activated_on.desc()) - .select(SqliteActiveVersion::as_select()) - .load(&mut conn) - { - Ok(mut r) => { - if r.len() != 1 { - return Err(PolicyDataError::NotFound); - } - - r.remove(0) - }, - Err(err) => return Err(PolicyDataError::GeneralError(err.to_string())), - }; - - if av.deactivated_on.is_some() { - return Err(PolicyDataError::NotFound); - } - - Ok(av.version) - } -} - -#[async_trait::async_trait] -impl PolicyDataAccess for SqlitePolicyDataStore { - type Error = String; - - async fn get_most_recent(&self) -> Result { - use crate::schema::policies::dsl::policies; - - let mut conn = self.pool.get().unwrap(); - match policies.limit(1).order_by(crate::schema::policies::dsl::created_at.desc()).select(SqlitePolicy::as_select()).load(&mut conn) { - Ok(mut r) => { - if r.len() != 1 { - return Err(PolicyDataError::NotFound); - } - let item = r.remove(0); - let content = serde_json::from_str::>(item.content.as_str()).expect("error"); - let created_at = DateTime::from_timestamp_micros(item.created_at).unwrap(); - let policy = Policy { - description: item.description, - version: PolicyVersion { - creator: Some(item.creator), - created_at: created_at.into(), - version: Some(item.version), - version_description: item.version_description, - reasoner_connector_context: item.reasoner_connector_context, - }, - content, - }; - return Ok(policy); - }, - Err(err) => Err(match err { - Error::NotFound => PolicyDataError::NotFound, - _ => PolicyDataError::GeneralError(err.to_string()), - }), - } - } - - async fn add_version>>( - &self, - mut version: Policy, - context: Context, - transaction: impl 'static + Send + FnOnce(Policy) -> F, - ) -> Result { - use crate::schema::policies::dsl::policies; - let mut conn = self.pool.get().unwrap(); - - // get last version - let v: Result, Error> = policies::select(policies, crate::schema::policies::dsl::version) - .order_by(crate::schema::policies::dsl::created_at.desc()) - .limit(1) - .load(&mut conn); - - let mut latest_version = 0; - match v { - Ok(versions) => { - if versions.len() == 1 { - latest_version = versions[0]; - } - }, - Err(_) => todo!(), - } - - // up to next version - let next_version = latest_version + 1; - let str_content = serde_json::to_string(&version.content).unwrap(); - - let model = SqlitePolicy { - description: version.description.clone(), - version: next_version, - version_description: version.version.version_description.clone(), - creator: context.initiator, - created_at: version.version.created_at.timestamp_micros(), - content: str_content, - reasoner_connector_context: version.version.reasoner_connector_context.clone(), - }; - - let rt_handle: Handle = Handle::current(); - match tokio::task::spawn_blocking(move || { - conn.exclusive_transaction(|conn| -> Result { - let policy = match diesel::insert_into(policies).values(&model).execute(conn) { - Ok(_) => { - version.version.version = Some(next_version); - version - }, - Err(err) => return Err(SqlitePolicyDataStoreError { msg: err.to_string() }), - }; - - rt_handle.block_on(transaction(policy.clone())).map_err(SqlitePolicyDataStoreError::from)?; - - Ok(policy) - }) - }) - .await - { - Ok(res) => res, - Err(err) => panic!("Failed to complete task in transaction: {:?}", err), - } - .map_err(|err: SqlitePolicyDataStoreError| err.into()) - } - - async fn get_version(&self, version: i64) -> Result { - use crate::schema::policies::dsl::policies; - let mut conn = self.pool.get().unwrap(); - - match policies - .limit(1) - .filter(crate::schema::policies::dsl::version.eq(version)) - .order_by(crate::schema::policies::dsl::created_at.desc()) - .select(SqlitePolicy::as_select()) - .load::(&mut conn) - { - Ok(mut r) => { - if r.len() != 1 { - return Err(PolicyDataError::NotFound); - } - - let item: SqlitePolicy = r.remove(0); - let content = serde_json::from_str::>(item.content.as_str()).expect("error"); - let created_at = DateTime::from_timestamp_micros(item.created_at).unwrap(); - let policy = Policy { - description: item.description, - version: PolicyVersion { - creator: Some(item.creator), - created_at: created_at.into(), - version: Some(item.version), - version_description: item.version_description, - reasoner_connector_context: item.reasoner_connector_context, - }, - content, - }; - - return Ok(policy); - }, - Err(err) => Err(match err { - Error::NotFound => PolicyDataError::NotFound, - _ => PolicyDataError::GeneralError(err.to_string()), - }), - } - } - - async fn get_versions(&self) -> Result, PolicyDataError> { - use crate::schema::policies::dsl::{created_at, creator, policies, reasoner_connector_context, version, version_description}; - let mut conn = self.pool.get().unwrap(); - - match policies - .order_by(crate::schema::policies::dsl::created_at.desc()) - .select((version, version_description, creator, created_at, reasoner_connector_context)) - .load::<(i64, String, String, i64, String)>(&mut conn) - { - Ok(r) => { - let items: Vec = r - .into_iter() - .map(|x| PolicyVersion { - version: Some(x.0), - version_description: x.1, - creator: Some(x.2), - created_at: DateTime::from_timestamp_micros(x.3).unwrap().into(), - reasoner_connector_context: x.4, - }) - .collect(); - - return Ok(items); - }, - Err(err) => Err(match err { - Error::NotFound => PolicyDataError::NotFound, - _ => PolicyDataError::GeneralError(err.to_string()), - }), - } - } - - async fn get_active(&self) -> Result { - let av = self._get_active().await?; - - self.get_version(av).await - } - - async fn set_active>>( - &self, - version: i64, - context: Context, - transaction: impl 'static + Send + FnOnce(Policy) -> F, - ) -> Result { - use crate::schema::active_version::dsl::active_version; - let mut conn = self.pool.get().unwrap(); - - let policy = self.get_version(version).await?; - - let av = self._get_active().await; - - if av.is_ok_and(|v| v == version) { - return Err(PolicyDataError::GeneralError(format!("Version already active: {}", version))); - } - - let model = SqliteActiveVersion::new(version, context.initiator); - - let rt_handle: Handle = Handle::current(); - match tokio::task::spawn_blocking(move || { - conn.exclusive_transaction(|conn| { - diesel::insert_into(active_version).values(&model).execute(conn)?; - - rt_handle.block_on(transaction(policy.clone())).map_err(SqlitePolicyDataStoreError::from)?; - - Ok(policy) - }) - }) - .await - { - Ok(res) => res, - Err(err) => panic!("Failed to complete task in transaction: {:?}", err), - } - .map_err(|err: SqlitePolicyDataStoreError| err.into()) - } - - async fn deactivate_policy>>( - &self, - context: Context, - transaction: impl 'static + Send + FnOnce() -> F, - ) -> Result<(), PolicyDataError> { - use crate::schema::active_version::dsl::{active_version, deactivated_by, deactivated_on, version}; - let mut conn = self.pool.get().unwrap(); - - let av = self._get_active().await?; - - let rt_handle: Handle = Handle::current(); - match tokio::task::spawn_blocking(move || { - conn.exclusive_transaction(|conn| { - diesel::update(active_version) - .filter(version.eq(av)) - .set((deactivated_on.eq(Utc::now().naive_local()), deactivated_by.eq(context.initiator))) - .execute(conn)?; - - rt_handle.block_on(transaction()).map_err(SqlitePolicyDataStoreError::from)?; - - Ok(()) - }) - }) - .await - { - Ok(res) => res, - Err(err) => panic!("Failed to complete task in transaction: {:?}", err), - } - .map_err(|err: SqlitePolicyDataStoreError| err.into()) - } -} diff --git a/src/state.rs b/src/state.rs deleted file mode 100644 index 9ec38ab..0000000 --- a/src/state.rs +++ /dev/null @@ -1,546 +0,0 @@ -// STATE.rs -// by Lut99 -// -// Created: -// 09 Jan 2024, 13:14:34 -// Last edited: -// 12 Jun 2024, 18:00:40 -// Auto updated? -// Yes -// -// Description: -//! Implements resolvers for the policy state, e.g., which datasets -//! there are, which domains, etc. -// - -use std::collections::HashMap; -use std::error::Error; -use std::fmt::{Display, Formatter, Result as FResult}; -use std::fs; -use std::path::PathBuf; - -#[cfg(feature = "brane-api-resolver")] -use ::{ - brane_cfg::info::Info, - brane_cfg::node::{NodeConfig, NodeSpecificConfig, WorkerUsecase}, - chrono::{DateTime, Utc}, - enum_debug::EnumDebug as _, - graphql_client::GraphQLQuery, - log::{info, warn}, - reqwest::{Client, Request, Response, StatusCode}, - specifications::address::Address, - specifications::data::DataInfo, - state_resolver::StateResolverError, - std::fs::File, - uuid::Uuid, - workflow::{Dataset, User}, -}; -use async_trait::async_trait; -use log::debug; -use nested_cli_parser::map_parser::MapParser; -use nested_cli_parser::{NestedCliParser, NestedCliParserHelpFormatter}; -use state_resolver::{State, StateResolver}; - -/***** CONSTANTS *****/ -/// The list of recognized keys for the arguments of the [`FileStateResolver`]. -pub const FILE_STATE_RESOLVER_KEYS: [&'static str; 2] = ["f", "file"]; - -/// The list of recognized keys for the arguments of the [`BraneApiResolver`]. -#[cfg(feature = "brane-api-resolver")] -pub const BRANE_API_STATE_RESOLVER_KEYS: [&'static str; 2] = ["u", "use-case-file"]; - -/***** TYPE ALIASES *****/ -/// Type alias for [`DateTime`] required by the GraphQL client. -#[cfg(feature = "brane-api-resolver")] -pub type DateTimeUtc = DateTime; - -/***** ERRORS *****/ -/// Defines errors occurring in the [`FileStateResolver`]. -#[derive(Debug)] -pub enum FileStateResolverError { - /// Failed to parse the nested CLI arguments. - CliArgumentsParse { raw: String, err: nested_cli_parser::map_parser::Error }, - /// Given the flag for the use case argument twice. - CliDuplicatePath, - /// The user did not tell us the path to the use case file. - CliMissingPath, - /// Failed to read a file. - FileRead { path: PathBuf, err: std::io::Error }, - /// Failed to deserialize a file into JSON. - FileDeserialize { path: PathBuf, err: serde_json::Error }, -} -impl Display for FileStateResolverError { - fn fmt(&self, f: &mut Formatter<'_>) -> FResult { - use FileStateResolverError::*; - match self { - CliArgumentsParse { raw, .. } => write!(f, "Failed to parse '{raw}' as CLI argument string for a FileStateResolver"), - CliDuplicatePath => write!(f, "Duplicate specification of file path (both 'p=...' and 'path=...' given)"), - CliMissingPath => { - write!(f, "File path not specified (give it as either '--state-resolver \"p=...\"' or '--state-resolver \"path=...\"')") - }, - FileRead { path, .. } => write!(f, "Failed to read file '{}'", path.display()), - FileDeserialize { path, .. } => write!(f, "Failed to deserialize file '{}' as JSON", path.display()), - } - } -} -impl Error for FileStateResolverError { - fn source(&self) -> Option<&(dyn Error + 'static)> { - use FileStateResolverError::*; - match self { - CliArgumentsParse { err, .. } => Some(err), - CliDuplicatePath => None, - CliMissingPath => None, - FileRead { err, .. } => Some(err), - FileDeserialize { err, .. } => Some(err), - } - } -} - -/// Defines a wrapper around a list of [`graphql_client::Error`]s. -#[cfg(feature = "brane-api-resolver")] -#[derive(Debug)] -pub struct GraphQlErrors(Vec); -#[cfg(feature = "brane-api-resolver")] -impl Display for GraphQlErrors { - fn fmt(&self, f: &mut Formatter<'_>) -> FResult { - writeln!(f, "The following errors were returned by the GraphQL server:")?; - if !self.0.is_empty() { - for err in &self.0 { - writeln!(f, " - {err}")?; - } - writeln!(f) - } else { - writeln!(f, "\n") - } - } -} -#[cfg(feature = "brane-api-resolver")] -impl Error for GraphQlErrors {} - -/// Defines errors occurring in the [`BraneApiResolver`]. -#[cfg(feature = "brane-api-resolver")] -#[derive(Debug)] -pub enum BraneApiResolverError { - /// Failed to parse the nested CLI arguments. - CliArgumentsParse { raw: String, err: nested_cli_parser::map_parser::Error }, - /// Given the flag for the use case argument twice. - CliDuplicateNodeFilePath, - /// The user did not tell us the path to the use case file. - CliMissingNodeFilePath, - /// Failed to open the use case file given. - NodeFileOpen { path: PathBuf, err: std::io::Error }, - /// Failed to read & parse the use case file given. - NodeFileRead { path: PathBuf, err: brane_cfg::info::YamlError }, - /// The given node file was not of the correct type. - NodeFileIncorrectKind { path: PathBuf, got: String, expected: String }, - - /// A GraphQL request failed. - GraphQl { from: String, errs: Option }, - /// Failed to build a request to a particular address. - RequestBuild { kind: &'static str, to: String, err: reqwest::Error }, - /// Failed to send a request to a particular address. - RequestSend { kind: &'static str, to: String, err: reqwest::Error }, - /// Failed to download the body of a response. - ResponseBody { from: String, err: reqwest::Error }, - /// Failed to parse the body of a response. - ResponseBodyParse { from: String, raw: String, err: serde_json::Error }, - /// The response was not a 200 OK - ResponseFailed { from: String, code: StatusCode, response: Option }, - /// The given use-case identifier was not known to us. - UnknownUseCase { raw: String }, -} -#[cfg(feature = "brane-api-resolver")] -impl Display for BraneApiResolverError { - fn fmt(&self, f: &mut Formatter<'_>) -> FResult { - use BraneApiResolverError::*; - match self { - CliArgumentsParse { raw, .. } => write!(f, "Failed to parse '{raw}' as CLI argument string for a BraneApiResolver"), - CliDuplicateNodeFilePath => write!(f, "Duplicate specification of node file path (both 'n=...' and 'node-file-path=...' given)"), - CliMissingNodeFilePath => write!( - f, - "Node file path not specified (give it as either '--state-resolver \"n=...\"' or '--state-resolver \"node-file-path=...\"')" - ), - NodeFileOpen { path, .. } => write!(f, "Failed to open node file '{}'", path.display()), - NodeFileRead { path, .. } => write!(f, "Failed to read & parse node file '{}' as YAML", path.display()), - NodeFileIncorrectKind { path, got, expected } => { - write!(f, "Given node file '{}' was for a {}, but it should be for a {}", path.display(), got, expected) - }, - - GraphQl { from, .. } => write!(f, "Received GraphQL errors from '{from}'"), - RequestBuild { kind, to, .. } => write!(f, "Failed to build {kind}-request to '{to}'"), - RequestSend { kind, to, .. } => write!(f, "Failed to send {kind}-request to '{to}'"), - ResponseBody { from, .. } => write!(f, "Failed to download response body from '{from}'"), - ResponseBodyParse { from, raw, .. } => write!( - f, - "Failed to parse response body from '{}' as JSON\n\nRaw response:\n{}\n{}\n{}\n", - from, - (0..80).map(|_| '-').collect::(), - raw, - (0..80).map(|_| '-').collect::() - ), - ResponseFailed { from, code, response } => write!( - f, - "Registry at {} returned {} ({}){}", - from, - code.as_u16(), - code.canonical_reason().unwrap_or("???"), - if let Some(response) = response { - format!( - "\n\nResponse:\n{}\n{}\n{}\n", - (0..80).map(|_| '-').collect::(), - response, - (0..80).map(|_| '-').collect::() - ) - } else { - String::new() - } - ), - UnknownUseCase { raw } => write!(f, "Unknown use-case identifier '{raw}'"), - } - } -} -#[cfg(feature = "brane-api-resolver")] -impl Error for BraneApiResolverError { - fn source(&self) -> Option<&(dyn Error + 'static)> { - use BraneApiResolverError::*; - match self { - CliArgumentsParse { err, .. } => Some(err), - CliDuplicateNodeFilePath => None, - CliMissingNodeFilePath => None, - NodeFileOpen { err, .. } => Some(err), - NodeFileRead { err, .. } => Some(err), - NodeFileIncorrectKind { .. } => None, - - GraphQl { errs, .. } => errs.as_ref().map(|errs| { - // Bit ugly, but needed as cast from `&GraphQlErrors` to `&dyn Error` - let errs: &dyn Error = errs; - errs - }), - RequestBuild { err, .. } => Some(err), - RequestSend { err, .. } => Some(err), - ResponseBody { err, .. } => Some(err), - ResponseBodyParse { err, .. } => Some(err), - ResponseFailed { .. } => None, - UnknownUseCase { .. } => None, - } - } -} -#[cfg(feature = "brane-api-resolver")] -impl StateResolverError for BraneApiResolverError { - #[inline] - fn try_as_unknown_use_case(&self) -> Option<&String> { if let Self::UnknownUseCase { raw } = self { Some(raw) } else { None } } -} - -/***** LIBRARY *****/ -/// Defines a resolver that resolves from a static file. -#[derive(Debug)] -pub struct FileStateResolver { - /// The state read from the file. - state: State, -} - -impl FileStateResolver { - /// Constructor for the FileStateResolver. - /// - /// # Arguments - /// - `cli_args`: A raw string with CLI arguments given to us by the `policy-reasoner` executable. - /// - /// # Returns - /// A new FileStateResolver instance. - /// - /// # Errors - /// This function may error if it failed to read the given file. - #[inline] - pub fn new(cli_args: String) -> Result { - // Parse the arguments using the [`MapParser`]. - debug!("Parsing nested arguments for FileStateResolver"); - let parser = MapParser::new(Self::cli_args()); - let args: HashMap> = match parser.parse(&cli_args) { - Ok(args) => args, - Err(err) => return Err(FileStateResolverError::CliArgumentsParse { raw: cli_args, err }), - }; - - // See what to do with it - let path: PathBuf = match args.get("path") { - Some(Some(path)) => path.into(), - _ => concat!(env!("CARGO_MANIFEST_DIR"), "/examples/eflint_reasonerconn/example-state.json").into(), - }; - - // Read the file in one go - debug!("Opening input file '{}'...", path.display()); - let state: String = match fs::read_to_string(&path) { - Ok(state) => state, - Err(err) => return Err(FileStateResolverError::FileRead { path, err }), - }; - - // Parse it as JSON - debug!("Parsing input file '{}'...", path.display()); - let state: State = match serde_json::from_str(&state) { - Ok(state) => state, - Err(err) => return Err(FileStateResolverError::FileDeserialize { path, err }), - }; - - // Build ourselves with it - Ok(Self { state }) - } - - /// Returns the arguments necessary to build the parser for the FileStateResolver. - /// - /// # Returns - /// A vector of arguments appropriate to use to build a [`MapParser`]. - #[inline] - fn cli_args() -> [(char, &'static str, &'static str); 1] { - [( - 'p', - "path", - concat!( - "The path to the file that we read the state from. Default: '", - env!("CARGO_MANIFEST_DIR"), - "/examples/eflint_reasonerconn/example-state.json'" - ), - )] - } - - /// Returns a formatter that can be printed to understand the arguments to this resolver. - /// - /// # Arguments - /// - `short`: A shortname for the argument that contains the nested arguments we parse. - /// - `long`: A longname for the argument that contains the nested arguments we parse. - /// - /// # Returns - /// A [`NestedCliParserHelpFormatter`] that implements [`Display`]. - // Don't agree with clippy again about the elided lifetimes. Not clearer! - #[allow(clippy::needless_lifetimes)] - pub fn help<'l>(short: char, long: &'l str) -> NestedCliParserHelpFormatter<'static, 'l, MapParser> { - MapParser::new(Self::cli_args()).into_help("FileStateResolver plugin", short, long) - } -} - -#[async_trait] -impl StateResolver for FileStateResolver { - type Error = std::convert::Infallible; - - async fn get_state(&self, _use_case: String) -> Result { - // Simply return a clone of the internal one - Ok(self.state.clone()) - } -} - -/// Defines a resolver that resolves state using Brane's API service. -#[cfg(feature = "brane-api-resolver")] -#[derive(Debug)] -pub struct BraneApiResolver { - /// A map from use case identifiers to where we can find the relevant Brane API registry. - use_cases: HashMap, -} - -#[cfg(feature = "brane-api-resolver")] -impl BraneApiResolver { - /// Constructor for the BraneApiResolver. - /// - /// # Arguments - /// - `cli_args`: A raw string with CLI arguments given to us by the `policy-reasoner` executable. - /// - /// # Returns - /// A new BraneApiResolver instance. - /// - /// # Errors - /// This function errors if `cli_args` was not parsed successfully. - pub fn new(cli_args: String) -> Result { - // Parse the arguments using the [`MapParser`]. - debug!("Parsing nested arguments for BraneApiResolver"); - let parser = MapParser::new(Self::cli_args()); - let args: HashMap> = match parser.parse(&cli_args) { - Ok(args) => args, - Err(err) => return Err(BraneApiResolverError::CliArgumentsParse { raw: cli_args, err }), - }; - - // See what to do with it - let use_cases: HashMap = match args.get("node-file-path") { - Some(Some(path)) => { - // Attempt to open the file - debug!("Opening node file '{path}'..."); - let handle: File = match File::open(path) { - Ok(handle) => handle, - Err(err) => return Err(BraneApiResolverError::NodeFileOpen { path: path.into(), err }), - }; - - // Attempt to parse the file - debug!("Parsing node file '{path}'..."); - match NodeConfig::from_reader(handle) { - Ok(use_cases) => match use_cases.node { - NodeSpecificConfig::Worker(worker) => worker.usecases, - node => { - return Err(BraneApiResolverError::NodeFileIncorrectKind { - path: path.into(), - got: node.variant().to_string(), - expected: "Worker".into(), - }); - }, - }, - Err(err) => return Err(BraneApiResolverError::NodeFileRead { path: path.into(), err }), - } - }, - _ => return Err(BraneApiResolverError::CliMissingNodeFilePath), - }; - - // Done, store the list of use cases! - Ok(Self { use_cases }) - } - - /// Returns the arguments necessary to build the parser for the BraneApiResolver. - /// - /// # Returns - /// A vector of arguments appropriate to use to build a [`MapParser`]. - #[inline] - fn cli_args() -> [(char, &'static str, &'static str); 1] { - [('n', "node-file-path", "The path to the `node.yml` file that maps use-case identifiers to registry addresses for us.")] - } - - /// Returns a formatter that can be printed to understand the arguments to this resolver. - /// - /// # Arguments - /// - `short`: A shortname for the argument that contains the nested arguments we parse. - /// - `long`: A longname for the argument that contains the nested arguments we parse. - /// - /// # Returns - /// A [`NestedCliParserHelpFormatter`] that implements [`Display`]. - pub fn help<'l>(short: char, long: &'l str) -> NestedCliParserHelpFormatter<'static, 'l, MapParser> { - MapParser::new(Self::cli_args()).into_help("BraneApiResolver plugin", short, long) - } -} - -#[cfg(feature = "brane-api-resolver")] -#[async_trait] -impl StateResolver for BraneApiResolver { - type Error = BraneApiResolverError; - - async fn get_state(&self, use_case: String) -> Result { - info!("Resolving state using `brane-api` for use-case '{use_case}'"); - - // Attempt to find a registry to call - let address: &Address = match self.use_cases.get(&use_case) { - Some(address) => &address.api, - None => return Err(BraneApiResolverError::UnknownUseCase { raw: use_case }), - }; - debug!("Use case '{use_case}' is known, requesting state from registry at '{address}'"); - - // Do the users call first - let users: Vec = { - warn!("Cannot request list of users from 'brane-api', as it does not have this information; assuming empty list"); - vec![] - }; - debug!("Retrieved {} users", users.len()); - - // Next, retrieve domains - let locations: Vec = { - let url: String = format!("{address}/infra/registries"); - debug!("Retrieving list of domains from '{url}'..."); - - // Build the request - let client = Client::new(); - let req: Request = match client.get(&url).build() { - Ok(req) => req, - Err(err) => return Err(BraneApiResolverError::RequestBuild { kind: "GET", to: url, err }), - }; - let res: Response = match client.execute(req).await { - Ok(res) => res, - Err(err) => return Err(BraneApiResolverError::RequestSend { kind: "GET", to: url, err }), - }; - if !res.status().is_success() { - return Err(BraneApiResolverError::ResponseFailed { from: url, code: res.status(), response: res.text().await.ok() }); - } - - // Attempt to parse the result as a map of domains - let body: String = match res.text().await { - Ok(body) => body, - Err(err) => return Err(BraneApiResolverError::ResponseBody { from: url, err }), - }; - let registries: HashMap = match serde_json::from_str(&body) { - Ok(regs) => regs, - Err(err) => return Err(BraneApiResolverError::ResponseBodyParse { from: url, raw: body, err }), - }; - - // The keys of the map are our domains - registries.into_keys().map(|name| User { name }).collect() - }; - debug!("Retrieved {} locations", locations.len()); - - // Then we retrieve the list of available datasets - let datasets: Vec = { - let url: String = format!("{address}/data/info"); - debug!("Retrieving list of datasets from '{url}'..."); - - // Build the request - let client = Client::new(); - let req: Request = match client.get(&url).build() { - Ok(req) => req, - Err(err) => return Err(BraneApiResolverError::RequestBuild { kind: "GET", to: url, err }), - }; - let res: Response = match client.execute(req).await { - Ok(res) => res, - Err(err) => return Err(BraneApiResolverError::RequestSend { kind: "GET", to: url, err }), - }; - if !res.status().is_success() { - return Err(BraneApiResolverError::ResponseFailed { from: url, code: res.status(), response: res.text().await.ok() }); - } - - // Attempt to parse the result as a list of [`DataInfo`]s - let body: String = match res.text().await { - Ok(body) => body, - Err(err) => return Err(BraneApiResolverError::ResponseBody { from: url, err }), - }; - let datasets: HashMap = match serde_json::from_str(&body) { - Ok(regs) => regs, - Err(err) => return Err(BraneApiResolverError::ResponseBodyParse { from: url, raw: body, err }), - }; - - // We build our own objects from this - datasets.into_keys().map(|name| Dataset { name, from: None }).collect() - }; - debug!("Retrieved {} datasets", datasets.len()); - - // Finally, retrieve the list of containers - let functions: Vec = { - // Build the GraphQL file - #[derive(GraphQLQuery)] - #[graphql(schema_path = "src/graphql/api_schema.json", query_path = "src/graphql/search_packages.graphql", response_derives = "Debug")] - pub struct SearchPackages; - - let url: String = format!("{address}/graphql"); - debug!("Retrieving list of datasets from '{url}'..."); - - // Prepare GraphQL query. - let variables = search_packages::Variables { term: None }; - let graphql_query = SearchPackages::build_query(variables); - - // Send a request - let client = Client::new(); - let graphql_response = match client.post(&url).json(&graphql_query).send().await { - Ok(res) => res, - Err(err) => return Err(BraneApiResolverError::RequestSend { kind: "POST", to: url, err }), - }; - let graphql_response: String = match graphql_response.text().await { - Ok(res) => res, - Err(err) => return Err(BraneApiResolverError::ResponseBody { from: url, err }), - }; - let graphql_response: graphql_client::Response = match serde_json::from_str(&graphql_response) { - Ok(res) => res, - Err(err) => return Err(BraneApiResolverError::ResponseBodyParse { from: url, raw: graphql_response, err }), - }; - - // See if any data was returned - if let Some(data) = graphql_response.data { - data.packages.into_iter().map(|package| Dataset { name: package.name, from: Some("".into()) }).collect() - } else { - return Err(BraneApiResolverError::GraphQl { from: url, errs: graphql_response.errors.map(|errs| GraphQlErrors(errs)) }); - } - }; - debug!("Retrieved {} functions", functions.len()); - - // Done, return it as one set - let state = State { users, locations, datasets, functions }; - debug!("Complete state retrieved from '{address}': {state:#?}"); - Ok(state) - } -} diff --git a/tools/checker-client/Cargo.toml b/tools/checker-client/Cargo.toml deleted file mode 100644 index fa70b1a..0000000 --- a/tools/checker-client/Cargo.toml +++ /dev/null @@ -1,42 +0,0 @@ -[package] -name = "checker-client" -description = "A tool to make requests to the checker conveniently, for demo/testing purposes." -rust-version = "1.81" -edition = "2021" -version.workspace = true -repository.workspace = true -authors.workspace = true -license.workspace = true - - -[dependencies] -# Crates.io -chrono = "0.4.35" -clap = { version = "4.5.6", features = ["derive"] } -console = "0.15.5" -hmac = "0.12" -jwt = "0.16" -log = "0.4.22" -rand = "0.9.0" -reqwest = { version = "0.12.0", features = ["blocking"] } -serde_json = { version = "1.0.120", features = ["raw_value"] } -sha2 = "0.10.6" - -# Path -audit-logger = { path = "../../lib/audit-logger" } -deliberation = { path = "../../lib/deliberation" } -eflint-to-json = { path = "../../lib/eflint-to-json" } -policy = { path = "../../lib/policy" } -srv = { path = "../../lib/srv" } - -# Workspace dependencies -eflint-json = { workspace = true, features = ["display_eflint"] } -enum-debug.workspace = true -error-trace.workspace = true -humanlog.workspace = true -names.workspace = true - -# Brane -brane-ast = { git = "https://github.com/braneframework/brane" } -brane-shr = { git = "https://github.com/braneframework/brane" } -specifications = { git = "https://github.com/braneframework/brane" } diff --git a/tools/checker-client/src/main.rs b/tools/checker-client/src/main.rs deleted file mode 100644 index 4d7fd26..0000000 --- a/tools/checker-client/src/main.rs +++ /dev/null @@ -1,1187 +0,0 @@ -// MAIN.rs -// by Lut99 -// -// Created: -// 15 Dec 2023, 15:08:35 -// Last edited: -// 07 Feb 2024, 11:56:13 -// Auto updated? -// Yes -// -// Description: -//! Entrypoint to the `checker-client` binary. -// - -use std::borrow::Cow; -use std::collections::{BTreeMap, HashMap}; -use std::env; -use std::error::Error; -use std::fmt::{Display, Formatter, Result as FResult}; -use std::fs::{self, File}; -use std::io::{BufRead as _, BufReader}; -use std::path::{Path, PathBuf}; -use std::str::FromStr; -use std::sync::Arc; -use std::time::{self, Duration, SystemTime}; - -use audit_logger::LogStatement; -use brane_ast::ast::Edge; -use brane_ast::locations::Locations; -use brane_ast::{CompileResult, ParserOptions, Workflow}; -use chrono::DateTime; -use clap::{Parser, Subcommand}; -use console::style; -use deliberation::spec::{Verdict, WorkflowValidationRequest}; -use eflint_json::DisplayEFlint; -use eflint_to_json::compile; -use enum_debug::EnumDebug; -use error_trace::{ErrorTrace as _, trace}; -use hmac::{Hmac, Mac as _}; -use humanlog::{DebugMode, HumanLogger}; -use jwt::SignWithKey as _; -use log::{LevelFilter, debug, error, info, trace as trace_log, warn}; -use policy::Policy; -use rand::Rng as _; -use rand::distr::Alphanumeric; -use reqwest::blocking::{Client, Request, Response}; -use reqwest::{Method, StatusCode}; -use serde_json::value::RawValue; -use sha2::Sha256; -use specifications::data::DataIndex; -use specifications::package::PackageIndex; -use srv::models::{AddPolicyPostModel, PolicyContentPostModel, SetVersionPostModel}; - -/***** CONSTANTS *****/ -/// The key to use to create JWTs (for testing purposes only). -const JWT_KEY: &[u8] = b"wL5hkXZpM929BXRCMgVt1GNdM3cSDovRZsU_mPaOPrNJ8x9TvOv9yb3Ps5GkIqdfCyXWM9HEzh0zNDvc_pA_BqAlLiCtlrSajDtCza42HQgWkE71ocWFB5yMkeVcDWaBwUcDm_lPiy-BdfGjmpdox8H7-mOQoieEMNt8hXQR5E7rA3PC9Ih8lma0pFtkRkuCDYyLmBH7geajvkTE77pB5YVUQ57Qm4uijpBus8083tN2UP-oCqBmpAfZ0BtyGY3oFlRk3sf_HwhSz2gFalYUuK8379hY4BOzuM80pIL18VHVzFgOwRI48RBCk21M5aoFiLMc5Gp9VTKKd9VxQNgExA"; - -/// The checker path to the policy API's policy list request path. -const POLICY_ADD_POLICY_PATH: (Method, &str) = (Method::POST, "v1/management/policies"); -/// The checker path to the policy API's set-active-policy request path. -const POLICY_SET_ACTIVE_POLICY_PATH: (Method, &str) = (Method::PUT, "v1/management/policies/active"); -/// The checker path to the policy API's get-active-policy request path. -const POLICY_GET_ACTIVE_POLICY_PATH: (Method, &str) = (Method::GET, "v1/management/policies/active"); -/// The checker path to the deliberation API's workflow check request path. -const DELIB_WORKFLOW_VALIDATION_PATH: (Method, &str) = (Method::POST, "v1/deliberation/execute-workflow"); - -/***** ERRORS *****/ -/// Defines errors that originate from parsing [`PolicyLanguage`]s. -#[derive(Debug)] -enum PolicyLanguageParseError { - /// It's an unknown language. - Unknown { raw: String }, -} -impl Display for PolicyLanguageParseError { - fn fmt(&self, f: &mut Formatter<'_>) -> FResult { - use PolicyLanguageParseError::*; - match self { - Unknown { raw } => write!(f, "Failed to parse '{raw}' as a policy language (expected 'eflint', 'eflint_json' or 'eflint-json'"), - } - } -} -impl Error for PolicyLanguageParseError {} - -/// Defines errors that originate from parsing [`PolicyLanguage`]s. -#[derive(Debug)] -enum WorkflowLanguageParseError { - /// It's an unknown language. - Unknown { raw: String }, -} -impl Display for WorkflowLanguageParseError { - fn fmt(&self, f: &mut Formatter<'_>) -> FResult { - use WorkflowLanguageParseError::*; - match self { - Unknown { raw } => { - write!(f, "Failed to parse '{raw}' as a workflow language (expected 'bs', 'bscript', 'branescript', 'wir' or 'checker'") - }, - } - } -} -impl Error for WorkflowLanguageParseError {} - -/// Defines errors that originate from creating JSON Web Tokens. -#[derive(Debug)] -enum JwtError { - /// Failed to create/sign a token - Create { err: jwt::Error }, -} -impl Display for JwtError { - fn fmt(&self, f: &mut Formatter<'_>) -> FResult { - use JwtError::*; - match self { - Create { .. } => write!(f, "Failed to create new JWT"), - } - } -} -impl Error for JwtError { - fn source(&self) -> Option<&(dyn Error + 'static)> { - use JwtError::*; - match self { - Create { err } => Some(err), - } - } -} - -/***** HELPERS *****/ -/// Defines accepted policy input languages. -#[derive(Clone, Copy, Debug, EnumDebug, Eq, Hash, PartialEq)] -enum PolicyLanguage { - /// It's normal eFLINT syntax. - EFlint, - /// It's eFLINT JSON syntax. - EFlintJson, -} -impl FromStr for PolicyLanguage { - type Err = PolicyLanguageParseError; - - fn from_str(s: &str) -> Result { - match s { - "eflint" => Ok(Self::EFlint), - "eflint_json" | "eflint-json" => Ok(Self::EFlintJson), - raw => Err(PolicyLanguageParseError::Unknown { raw: raw.into() }), - } - } -} - -/// Defines accepted workflow input languages. -#[derive(Clone, Copy, Debug, EnumDebug, Eq, Hash, PartialEq)] -enum WorkflowLanguage { - /// It's BraneScript. - BraneScript, - /// It's the WIR. - Wir, -} -impl FromStr for WorkflowLanguage { - type Err = WorkflowLanguageParseError; - - fn from_str(s: &str) -> Result { - match s { - "bs" | "bscript" | "branescript" => Ok(Self::BraneScript), - "wir" => Ok(Self::Wir), - raw => Err(WorkflowLanguageParseError::Unknown { raw: raw.into() }), - } - } -} - -/***** ARGUMENTS *****/ -/// Defines the arguments of the `checker-client` binary. -#[derive(Debug, Parser)] -struct Arguments { - /// Whether to enable debug logs - #[clap(long, global = true, help = "If given, enabled additional log statements (DEBUG, INFO) and adds more information per statement.")] - debug: bool, - /// Whether to enable debug + trace logs - #[clap( - long, - global = true, - help = "If given, enabled additional log statements (TRACE, DEBUG, INFO) and adds maximum information per statement (implies '--debug')." - )] - trace: bool, - - /// The address of the checker to connect to. - #[clap(short, long, global = true, default_value = "localhost", help = "The address of the checker we're connecting to.")] - address: String, - /// The port of the checker to connect to. - #[clap(short, long, global = true, default_value = "3030", help = "The port of the checker we're connecting to.")] - port: u16, - /// The name of the person submitting policies. - #[clap(short, long, global = true, help = "The name under which to submit policies. Chooses a random name if omitted.")] - name: Option, - /// A JWT that authenticates the user. - #[clap(short, long, global = true, help = "A JWT that is used to authenticate with the checker. Ignores '--name' if given.")] - jwt: Option, - - /// The toplevel subcommand that decides what to do - #[clap(subcommand)] - subcommand: Subcommands, -} - -/// Defines the toplevel subcommands for the `checker-client` binary. -#[derive(Debug, Subcommand)] -enum Subcommands { - /// Policy-related stuff - #[clap(name = "policy", about = "Groups commands relating to policy management.")] - Policy(PolicyArguments), - /// Deliberation-related stuff - #[clap(name = "check", about = "Groups commands relating to deliberating the checker.")] - Check(CheckArguments), - /// Audit log-related stuff - #[clap(name = "log", about = "Groups commands for better understanding audit logs.")] - Log(LogArguments), -} - -/// Defines arguments for the `checker-client policy` subcommand. -#[derive(Debug, Parser)] -struct PolicyArguments { - /// Subcommand further - #[clap(subcommand)] - action: PolicySubcommands, -} - -/// Defines nested subcommands for the `checker-client policy` subcommand. -#[derive(Debug, Subcommand)] -enum PolicySubcommands { - /// Pushes a new policy to the checker. - #[clap(name = "push", about = "Pushes a new policy to the checker.")] - Push(PolicyPushArguments), - /// Returns the currently active policy as active in the checker. - #[clap(name = "get", about = "Retrieves the currently active policy in the checker.")] - Get(PolicyGetArguments), - /// Sets a policy as active in the checker. - #[clap(name = "set", about = "Makes a policy with the given version ID active in the checker.")] - Set(PolicySetArguments), -} - -/// Defines arguments for the `checker-client policy push` subcommand. -#[derive(Debug, Parser)] -struct PolicyPushArguments { - /// The path to the policy file to push. - #[clap(name = "POLICY", help = "The path of the policy file to push.")] - path: PathBuf, - - /// Determines the input language of the policy file. - #[clap( - short, - long, - default_value = "eflint", - help = "The language of the input file. Can be 'eflint' for eFLINT; or 'eflint_json' or 'eflint-json' for eFLINT JSON." - )] - language: PolicyLanguage, - /// Whether we're using an external `eflint-to-json` executable or not. - #[clap(short, long, help = "If given, does not download the Linux x86-64 'eflint-to-json' executable but instead uses the provided one.")] - eflint_to_json_path: Option, -} - -/// Defines arguments for the `checker-client policy get` subcommand. -#[derive(Debug, Parser)] -struct PolicyGetArguments { - /// If given, attempts to parse the returned set of policy as eFLINT JSON and shows it as such. - #[clap(short, long, help = "If given, attempts to parse the returned set of policy as eFLINT JSON and shows it as such.")] - eflint: bool, -} - -/// Defines arguments for the `checker-client policy set` subcommand. -#[derive(Debug, Parser)] -struct PolicySetArguments { - /// The ID of the policy to set. - #[clap(name = "VERSION", help = "The ID of the policy to set.")] - version: i64, -} - -/// Defines arguments for the `checker-client check` subcommand. -#[derive(Debug, Parser)] -struct CheckArguments { - /// Subcommand further - #[clap(subcommand)] - action: CheckSubcommands, - - /// A use-case to perform the command under. - #[clap(short, long, default_value = "default", global = true, help = "Determines the use-case as which to report to the checker.")] - use_case: String, - /// A user to designate as receiver of results. - #[clap(short, long, global = true, help = "Determines who will be reported as receiving the final result of the submitted workflow.")] - result_owner: Option, -} - -/// Defines nested subcommands for the `checker-client check` subcommand. -#[derive(Debug, Subcommand)] -enum CheckSubcommands { - /// Sends a workflow to the checker for validation. - #[clap(name = "workflow", alias = "wf", about = "Asks the checker to validate an entire workflow.")] - Workflow(CheckWorkflowArguments), -} - -/// Defines arguments for the `checker-client check workflow` subcommand. -#[derive(Debug, Parser)] -struct CheckWorkflowArguments { - /// The path to the workflow file to check. - #[clap(name = "WORKFLOW", help = "The path of the workflow file to check.")] - path: PathBuf, - - /// Determines the input language of the policy file. - #[clap( - short, - long, - default_value = "branescript", - help = "The language of the input file. Can be 'bs', 'bscript' or 'branescript' for BraneScript; or 'wir' for the Brane WIR." - )] - language: WorkflowLanguage, - /// Determines the package index location. - #[clap(short='P', long, default_value = concat!(env!("CARGO_MANIFEST_DIR"), "/../../tests/packages"), help = "The location where the package index is read from. Note that this is read in test mode (i.e., `brane`'s default package index does not work)")] - packages: PathBuf, - /// Determines the data index location. - #[clap(short='D', long, default_value = concat!(env!("CARGO_MANIFEST_DIR"), "/../../tests/data"), help = "The location where the data index is read from. Note that this is read in test mode (i.e., `brane`'s default data index does not work)")] - data: PathBuf, -} - -/// Defines arguments for the `checker-client log` subcommand. -#[derive(Debug, Parser)] -struct LogArguments { - /// The audit log used - #[clap(short, long, global = true, default_value = "./audit-log.log", help = "The path to the audit log to read.")] - log: PathBuf, - - /// Subcommand further - #[clap(subcommand)] - action: LogSubcommands, -} - -/// Defines nested subcommands for the `checker-client log` subcommand. -#[derive(Debug, Subcommand)] -enum LogSubcommands { - /// Attempts to find the reasons why a policy was denied - #[clap(name = "reason", about = "Reads the audit log to find reasons why the request with given reference ID is denied.")] - Reason(LogReasonArguments), -} - -/// Defines the arguments for the `checker-client log reason` subcommand. -#[derive(Debug, Parser)] -struct LogReasonArguments { - /// The reference ID to search for. - #[clap(name = "REFERENCE_ID", help = "The reference ID provided by the checker to find why the request failed.")] - reference_id: String, -} - -/***** HELPER FUNCTIONS *****/ -/// Given a potentially given JWT, uses it or generates a new one. -/// -/// # Arguments -/// - `name`: The name to embed in the JWT if we're generating one. -/// - `jwt`: The JWT given by the user, or [`None`] if they didn't. -/// -/// # Returns -/// A new, already serialized (and encoded!) JSON web token. -fn resolve_jwt(name: impl Into, jwt: Option) -> Result { - match jwt { - Some(jwt) => { - debug!("Using given JWT '{jwt}'"); - Ok(jwt) - }, - None => { - // Create a key from the internal one - let key: Hmac = match Hmac::new_from_slice(JWT_KEY) { - Ok(key) => key, - Err(err) => { - error!("{}", trace!(("Failed to create HMAC key from private key"), err)); - std::process::exit(1); - }, - }; - - // Generate the claims - let mut claims: BTreeMap<&str, String> = BTreeMap::new(); - claims.insert("sub", "1234567890".into()); - claims.insert("username", name.into()); - claims.insert("iat", SystemTime::now().duration_since(time::UNIX_EPOCH).unwrap().as_secs().to_string()); - claims - .insert("exp", (SystemTime::now() + Duration::from_secs(24 * 3600)).duration_since(time::UNIX_EPOCH).unwrap().as_secs().to_string()); - - // Create a JWT with it - match claims.sign_with_key(&key) { - Ok(jwt) => { - debug!("Using generated JWT '{jwt}'"); - Ok(jwt) - }, - Err(err) => Err(JwtError::Create { err }), - } - }, - } -} - -/// "Trivially" plans a workflow. -/// -/// Means: will plan if possible and properly scoped by user, or else give up. -/// -/// # Arguments -/// - `edges`: The list of edges to plan. -/// - `pc`: The current program counter that points to the edge we're currently planning. -/// - `breakpoint`: If [`Some`], then this points to an edge we should stop and return at. -fn plan_wir(edges: &mut [Edge], pc: (usize, usize), breakpoint: Option<(usize, usize)>) { - // Break at the breakpoint - if let Some(breakpoint) = breakpoint { - if pc == breakpoint { - return; - } - } - - // Get the edge - let edge: &mut Edge = match edges.get_mut(pc.1) { - Some(edge) => edge, - None => return, - }; - - // Match on it - use Edge::*; - #[allow(clippy::needless_return)] - match edge { - Node { task: _, locs, at, input: _, result: _, metadata: _, next } => { - let next: usize = *next; - - // If there is a single location possible, log it - if let Locations::Restricted(list) = locs { - if list.len() == 1 { - *at = Some(list.first().cloned().unwrap()); - } else { - warn!("Cannot plan edge ({},{}) because it does not have exactly one possible location (instead: {:?})", pc.0, pc.1, list); - } - } else { - warn!("Cannot plan edge ({},{}) because its possible locations are not restricted", pc.0, pc.1); - } - - // Continue - plan_wir(edges, (pc.0, next), breakpoint) - }, - Linear { instrs: _, next } => { - let next: usize = *next; - plan_wir(edges, (pc.0, next), breakpoint); - }, - Stop {} => return, - - Branch { true_next, false_next, merge } => { - let (true_next, false_next, merge): (usize, Option, Option) = (*true_next, *false_next, *merge); - - plan_wir(edges, (pc.0, true_next), merge.map(|m| (pc.0, m))); - if let Some(false_next) = false_next { - plan_wir(edges, (pc.0, false_next), merge.map(|m| (pc.0, m))); - } - if let Some(merge) = merge { - plan_wir(edges, (pc.0, merge), breakpoint); - } - }, - Parallel { branches, merge } => { - let (branches, merge): (Vec, usize) = (branches.clone(), *merge); - - for branch in branches { - plan_wir(edges, (pc.0, branch), Some((pc.0, merge))); - } - plan_wir(edges, (pc.0, merge), breakpoint); - }, - Join { merge: _, next } => { - let next: usize = *next; - plan_wir(edges, (pc.0, next), breakpoint); - }, - Loop { cond, body, next } => { - let (cond, body, next): (usize, usize, Option) = (*cond, *body, *next); - - plan_wir(edges, (pc.0, cond), Some((pc.0, body - 1))); - plan_wir(edges, (pc.0, body), Some((pc.0, cond))); - if let Some(next) = next { - plan_wir(edges, (pc.0, next), breakpoint); - } - }, - - Call { input: _, result: _, next } => { - let next: usize = *next; - plan_wir(edges, (pc.0, next), breakpoint); - }, - Return { result: _ } => return, - }; -} - -/// Analyses a line to see if it's the start of a logging line. -/// -/// Specifically, checks if it starts with `[policy-reasoner ][] `. -/// -/// # Arguments -/// - `line`: The line to check. -/// -/// # Returns -/// The position from where the real line begins, or else [`None`] if this wasn't the start of a log line. -fn line_is_log_line(line: &str) -> Option { - let line: &str = line.trim(); - - // Find twice the `]` - let brack_pos: usize = match line.find(']') { - Some(pos) => pos, - None => { - trace_log!("Line '{line}' is not a log line because it does not have a ']'"); - return None; - }, - }; - let first: &str = &line[..brack_pos]; - let rem: &str = &line[brack_pos + 1..]; - let brack_pos2: usize = match rem.find(']') { - Some(pos) => pos, - None => { - trace_log!("Line '{line}' is not a log line because it does not have a second ']'"); - return None; - }, - }; - let second: &str = &rem[..brack_pos2]; - let rem: &str = &rem[brack_pos2 + 1..]; - - /* FIRST PART */ - // Assert it begins with '[policy-reasoner v' - if first.len() < 18 || &first[..18] != "[policy-reasoner v" { - trace_log!("Line '{line}' is not a log line because the first part ('{first}') does not begin with '[policy-reasoner v'"); - return None; - } - - /* SECOND PART */ - // Assert it begins with '[' - if !matches!(second.chars().next(), Some('[')) { - trace_log!("Line '{line}' is not a log line because the second part ('{second}') does not begin with '['"); - return None; - } - let second: &str = &second[1..]; - - // Attempt to parse the middle part as a datetime - if let Err(err) = DateTime::parse_from_str(&format!("{second}.000 +0000"), "%Y-%m-%d %H:%M:%S%.3f %z") { - trace_log!("Line '{line}' is not a log line because the second part ('{second}.000 %z') does not parse as a datetime: {err}"); - return None; - } - - /* REMAINDER */ - // Now all that remains is to check for the final space - rem.chars().next().filter(|c| *c == ' ').map(|_| brack_pos + 1 + brack_pos2 + 2) -} - -/***** ENTRYPOINT *****/ -fn main() { - // Parse the args - let args = Arguments::parse(); - - // Setup the logger - if let Err(err) = HumanLogger::terminal(DebugMode::from_flags(args.trace, args.debug)).init() { - eprintln!("WARNING: Failed to setup logger: {err} (logging disabled for this session)"); - } - info!("{} v{}", env!("CARGO_BIN_NAME"), env!("CARGO_PKG_VERSION")); - - // Resolve the name - let name: Cow = match args.name { - Some(name) => Cow::Owned(name), - None => Cow::Borrowed(names::three::usualcase::rand()), - }; - debug!("Working as '{name}'"); - - // Match on the given subcommand - match args.subcommand { - Subcommands::Policy(policy) => match policy.action { - PolicySubcommands::Push(push) => { - info!("Handling `policy push` subcommand"); - - // Resolve the JWT - let jwt: String = match resolve_jwt(name, args.jwt) { - Ok(jwt) => jwt, - Err(err) => { - error!("{}", err.trace()); - std::process::exit(1); - }, - }; - - // Match on the input language - let json_path: Cow = match push.language { - PolicyLanguage::EFlint => { - let json_path: PathBuf = env::temp_dir() - .join(format!("policy-{}.json", rand::rng().sample_iter(Alphanumeric).take(8).map(char::from).collect::())); - debug!("Compiling input file '{}' to eFLINT JSON file '{}'...", push.path.display(), json_path.display()); - - // Open the output file - debug!("Creating output file '{}'...", json_path.display()); - let handle: File = match File::create(&json_path) { - Ok(handle) => handle, - Err(err) => { - error!("{}", trace!(("Failed to create output file '{}'", json_path.display()), err)); - std::process::exit(1); - }, - }; - - // Run the compiler - debug!("Running eflint-to-json compiler on '{}'...", push.path.display()); - if let Err(err) = compile(&push.path, handle, push.eflint_to_json_path.as_deref()) { - error!("{}", trace!(("Failed to compile input file '{}'", push.path.display()), err)); - std::process::exit(1); - }; - Cow::Owned(json_path) - }, - PolicyLanguage::EFlintJson => Cow::Borrowed(&push.path), - }; - - // Open that file to send it - debug!("Opening policy file '{}'...", json_path.display()); - let body: Vec = { - // // Open the file - // let handle: File = match File::open(&json_path) { - // Ok(handle) => handle, - // Err(err) => { - // error!("{}", Error::PolicyOpen { path: push.path, err }.trace()); - // std::process::exit(1); - // }, - // }; - - // // Read its metadata - // let metadata: Metadata = match handle.metadata() { - // Ok(metadata) => metadata, - // Err(err) => { - // error!("{}", Error::PolicyMetadata { path: push.path, err }.trace()); - // std::process::exit(1); - // }, - // }; - - // // Put it in a request body - // (Body::new(handle), metadata.len()) - - // First, read the file in its entirety - let policy: String = match fs::read_to_string(&json_path) { - Ok(policy) => policy, - Err(err) => { - error!("{}", trace!(("Failed to read eFLINT JSON file '{}'", json_path.display()), err)); - std::process::exit(1); - }, - }; - // Deserialize it to a raw JSON value - let policy: Box = match serde_json::from_str(&policy) { - Ok(policy) => policy, - Err(err) => { - error!("{}", trace!(("Failed to parse eFLINT JSON file as JSON '{}'", json_path.display()), err)); - std::process::exit(1); - }, - }; - - // Wrap it in the request - let request: AddPolicyPostModel = AddPolicyPostModel { - description: None, - version_description: "A test version of policy uploaded using the checker-client tool".into(), - content: vec![PolicyContentPostModel { reasoner: "eflint".into(), reasoner_version: "0.1.0".into(), content: policy }], - }; - // Re-serialize - match serde_json::to_string(&request) { - Ok(req) => req.into_bytes(), - Err(err) => { - error!("{}", trace!(("Failed to serialize checker add-policy request to JSON"), err)); - std::process::exit(1); - }, - } - }; - - // Build a request to the checker - let addr: String = format!("http://{}:{}/{}", args.address, args.port, POLICY_ADD_POLICY_PATH.1); - debug!("Building request to checker '{addr}'..."); - let client: Client = Client::new(); - let req: Request = match client - .request(POLICY_ADD_POLICY_PATH.0, &addr) - .header(reqwest::header::AUTHORIZATION, format!("Bearer {jwt}")) - .header(reqwest::header::CONTENT_LENGTH, body.len()) - .body(body) - .build() - { - Ok(req) => req, - Err(err) => { - error!("{}", trace!(("Failed to build request to '{}:{}'", args.address, args.port), err)); - std::process::exit(1); - }, - }; - - // Send it - debug!("Sending request to checker '{addr}'..."); - let res: Response = match client.execute(req) { - Ok(res) => res, - Err(err) => { - error!("{}", trace!(("Failed to execute request to '{}:{}'", args.address, args.port), err)); - std::process::exit(1); - }, - }; - let status: StatusCode = res.status(); - if !status.is_success() { - error!( - "Request to '{}' failed with {} ({}){}", - addr, - status.as_u16(), - status.canonical_reason().unwrap_or("???"), - if let Ok(err) = res.text() { - format!( - "\n\nResponse:\n{}\n{}\n{}\n", - (0..80).map(|_| '-').collect::(), - err, - (0..80).map(|_| '-').collect::() - ) - } else { - String::new() - } - ); - std::process::exit(1); - } - - // Show the response to the user - println!("{}", style("Checker replied with:").bold()); - println!("{}", res.text().unwrap_or("".into())); - println!(); - }, - - PolicySubcommands::Get(get) => { - info!("Handling `policy get` subcommand"); - - // Resolve the JWT - let jwt: String = match resolve_jwt(name, args.jwt) { - Ok(jwt) => jwt, - Err(err) => { - error!("{}", err.trace()); - std::process::exit(1); - }, - }; - - // Build a request to the checker - let addr: String = format!("http://{}:{}/{}", args.address, args.port, POLICY_GET_ACTIVE_POLICY_PATH.1); - debug!("Building request to checker '{addr}'..."); - let client: Client = Client::new(); - let req: Request = match client - .request(POLICY_GET_ACTIVE_POLICY_PATH.0, &addr) - .header(reqwest::header::AUTHORIZATION, format!("Bearer {jwt}")) - .build() - { - Ok(req) => req, - Err(err) => { - error!("{}", trace!(("Failed to build request to '{}:{}'", args.address, args.port), err)); - std::process::exit(1); - }, - }; - - // Send it - debug!("Sending request to checker '{addr}'..."); - let res: Response = match client.execute(req) { - Ok(res) => res, - Err(err) => { - error!("{}", trace!(("Failed to execute request to '{}:{}'", args.address, args.port), err)); - std::process::exit(1); - }, - }; - let status: StatusCode = res.status(); - if !status.is_success() { - error!( - "Request to '{}' failed with {} ({}){}", - addr, - status.as_u16(), - status.canonical_reason().unwrap_or("???"), - if let Ok(err) = res.text() { - format!( - "\n\nResponse:\n{}\n{}\n{}\n", - (0..80).map(|_| '-').collect::(), - err, - (0..80).map(|_| '-').collect::() - ) - } else { - String::new() - } - ); - std::process::exit(1); - } - - // EITHER: Show the raw response or the parsed one - let text: Result = res.text(); - if get.eflint { - // Parse the incoming request - debug!("Parsing checker response..."); - let policy: Policy = match text { - Ok(response) => match serde_json::from_str(&response) { - Ok(policy) => policy, - Err(err) => { - error!( - "Failed to parse response text as Policy: {}\n\nResponse:\n{}\n{}\n{}\n", - err, - (0..80).map(|_| '-').collect::(), - response, - (0..80).map(|_| '-').collect::() - ); - std::process::exit(1); - }, - }, - Err(err) => { - error!("{}", trace!(("Failed to get response"), err)); - std::process::exit(1); - }, - }; - - // Next, parse the policies - for (i, policy) in policy.content.into_iter().enumerate() { - // Attempt to parse the embedded eFLINT - debug!("Deserializing policy {i}..."); - let policy: eflint_json::spec::Request = match serde_json::from_str(policy.content.get()) { - Ok(policy) => policy, - Err(err) => { - error!("{}", trace!(("Failed to parse policy {i} in request as valid eFLINT JSON"), err)); - std::process::exit(1); - }, - }; - - // Show it to the user - println!("{}", style(format!("Active checker policy {i}")).bold()); - println!("{:#}", policy.display_syntax()); - println!(); - } - } else { - println!("{}", style("Checker replied with:").bold()); - println!("{}", text.unwrap_or("".into())); - println!(); - } - }, - - PolicySubcommands::Set(set) => { - info!("Handling `policy set` subcommand"); - - // Resolve the JWT - let jwt: String = match resolve_jwt(name, args.jwt) { - Ok(jwt) => jwt, - Err(err) => { - error!("{}", err.trace()); - std::process::exit(1); - }, - }; - - // Create the request body to send - debug!("Generating policy request..."); - let body: SetVersionPostModel = SetVersionPostModel { version: set.version }; - let body: Vec = match serde_json::to_string(&body) { - Ok(body) => body.into_bytes(), - Err(err) => { - error!("{}", trace!(("Failed to serialize checker set-policy request to JSON"), err)); - std::process::exit(1); - }, - }; - - // Build a request to the checker - let addr: String = format!("http://{}:{}/{}", args.address, args.port, POLICY_SET_ACTIVE_POLICY_PATH.1); - debug!("Building request to checker '{addr}'..."); - let client: Client = Client::new(); - let req: Request = match client - .request(POLICY_SET_ACTIVE_POLICY_PATH.0, &addr) - .header(reqwest::header::AUTHORIZATION, format!("Bearer {jwt}")) - .header(reqwest::header::CONTENT_LENGTH, body.len()) - .body(body) - .build() - { - Ok(req) => req, - Err(err) => { - error!("{}", trace!(("Failed to build request to '{}:{}'", args.address, args.port), err)); - std::process::exit(1); - }, - }; - - // Send it - debug!("Sending request to checker '{addr}'..."); - let res: Response = match client.execute(req) { - Ok(res) => res, - Err(err) => { - error!("{}", trace!(("Failed to execute request to '{}:{}'", args.address, args.port), err)); - std::process::exit(1); - }, - }; - let status: StatusCode = res.status(); - if !status.is_success() { - error!( - "Request to '{}' failed with {} ({}){}", - addr, - status.as_u16(), - status.canonical_reason().unwrap_or("???"), - if let Ok(err) = res.text() { - format!( - "\n\nResponse:\n{}\n{}\n{}\n", - (0..80).map(|_| '-').collect::(), - err, - (0..80).map(|_| '-').collect::() - ) - } else { - String::new() - } - ); - std::process::exit(1); - } - - // Show the response to the user - println!("{}", style("Checker replied with:").bold()); - println!("{}", res.text().unwrap_or("".into())); - println!(); - }, - }, - - Subcommands::Check(check) => match check.action { - CheckSubcommands::Workflow(wf) => { - info!("Handling `check workflow` subcommand"); - - // Resolve the JWT - let jwt: String = match resolve_jwt(name, args.jwt) { - Ok(jwt) => jwt, - Err(err) => { - error!("{}", err.trace()); - std::process::exit(1); - }, - }; - - // Read the data index (we'll need it for planning) - let dindex: DataIndex = brane_shr::utilities::create_data_index_from(&wf.data); - - // Match on the input language - let mut wir: Workflow = match wf.language { - WorkflowLanguage::BraneScript => { - debug!("Compiling input file '{}' to a Brane WIR...", wf.path.display()); - - // Open the input file - debug!("Reading input file '{}' as BraneScript", wf.path.display()); - let input: String = match fs::read_to_string(&wf.path) { - Ok(input) => input, - Err(err) => { - error!("{}", trace!(("Failed to read input file '{}'", wf.path.display()), err)); - std::process::exit(1); - }, - }; - - // Open the package index, too - let pindex: PackageIndex = brane_shr::utilities::create_package_index_from(&wf.packages); - - // Run the compiler - debug!("Running eflint-to-json compiler on '{}'...", wf.path.display()); - match brane_ast::compile_program(input.as_bytes(), &pindex, &dindex, &ParserOptions::bscript()) { - CompileResult::Workflow(workflow, warns) => { - // Print warnings (if any) - let spath: String = wf.path.display().to_string(); - for warn in warns { - warn.prettyprint(&spath, &input) - } - workflow - }, - CompileResult::Err(errs) => { - // Print the errors that occurred - let spath: String = wf.path.display().to_string(); - for err in errs { - err.prettyprint(&spath, &input); - } - error!("Failed to compile input file '{spath}' (see output above)"); - std::process::exit(1); - }, - CompileResult::Eof(err) => { - // Print the errors that occurred - let spath: String = wf.path.display().to_string(); - err.prettyprint(&spath, input); - error!("Failed to compile input file '{spath}' (see output above)"); - std::process::exit(1); - }, - - CompileResult::Program(_, _) | CompileResult::Unresolved(_, _) => unreachable!(), - } - }, - WorkflowLanguage::Wir => { - // Open the input file - debug!("Reading input file '{}' as Brane WIR...", wf.path.display()); - let input: String = match fs::read_to_string(&wf.path) { - Ok(input) => input, - Err(err) => { - error!("{}", trace!(("Failed to read input file '{}'", wf.path.display()), err)); - std::process::exit(1); - }, - }; - - // Deserialize - match serde_json::from_str(&input) { - Ok(wir) => wir, - Err(err) => { - error!("{}", trace!(("Failed to parse input file '{}' as Brane WIR", wf.path.display()), err)); - std::process::exit(1); - }, - } - }, - }; - - // Trivially plan the workflow - { - // Plan the main workflow - let mut graph: Arc> = Arc::new(vec![]); - std::mem::swap(&mut graph, &mut wir.graph); - let mut graph: Vec = Arc::into_inner(graph).unwrap(); - plan_wir(&mut graph, (usize::MAX, 0), None); - let mut graph: Arc> = Arc::new(graph); - std::mem::swap(&mut wir.graph, &mut graph); - - // Plan the functions in the workflow - let mut funcs: Arc>> = Arc::new(HashMap::new()); - std::mem::swap(&mut funcs, &mut wir.funcs); - let mut funcs: HashMap> = Arc::into_inner(funcs).unwrap(); - for edges in funcs.values_mut() { - plan_wir(edges, (usize::MAX, 0), None); - } - let mut funcs: Arc>> = Arc::new(funcs); - std::mem::swap(&mut wir.funcs, &mut funcs); - } - if log::max_level() >= LevelFilter::Debug { - let mut buf: Vec = Vec::new(); - brane_ast::traversals::print::ast::do_traversal(&wir, &mut buf).unwrap(); - debug!("Workflow after planning:\n\n{}\n", String::from_utf8_lossy(&buf)); - } - - // Also add a user - wir.user = Arc::new(Some(check.result_owner.unwrap_or_else(|| names::three::usualcase::rand().into()))); - - // Now put the workflow in a request and serialize it - let body: Vec = match serde_json::to_string(&WorkflowValidationRequest { use_case: check.use_case, workflow: wir }) { - Ok(body) => body.into_bytes(), - Err(err) => { - error!("{}", trace!(("Failed to serialize given Brane WIR in a WorkflowValidationRequest to JSON"), err)); - std::process::exit(1); - }, - }; - - // Build a request to the checker - let addr: String = format!("http://{}:{}/{}", args.address, args.port, DELIB_WORKFLOW_VALIDATION_PATH.1); - debug!("Building request to checker '{addr}'..."); - let client: Client = Client::new(); - let req: Request = match client - .request(DELIB_WORKFLOW_VALIDATION_PATH.0, &addr) - .header(reqwest::header::AUTHORIZATION, format!("Bearer {jwt}")) - .header(reqwest::header::CONTENT_LENGTH, body.len()) - .body(body) - .build() - { - Ok(req) => req, - Err(err) => { - error!("{}", trace!(("Failed to build request to '{}:{}'", args.address, args.port), err)); - std::process::exit(1); - }, - }; - - // Send it - debug!("Sending request to checker '{addr}'..."); - let res: Response = match client.execute(req) { - Ok(res) => res, - Err(err) => { - error!("{}", trace!(("Failed to execute request to '{}:{}'", args.address, args.port), err)); - std::process::exit(1); - }, - }; - let status: StatusCode = res.status(); - if !status.is_success() { - error!( - "Request to '{}' failed with {} ({}){}", - addr, - status.as_u16(), - status.canonical_reason().unwrap_or("???"), - if let Ok(err) = res.text() { - format!( - "\n\nResponse:\n{}\n{}\n{}\n", - (0..80).map(|_| '-').collect::(), - err, - (0..80).map(|_| '-').collect::() - ) - } else { - String::new() - } - ); - std::process::exit(1); - } - - // Show the response to the user - println!("{}", style("Checker replied with:").bold()); - println!("{}", res.text().unwrap_or("".into())); - println!(); - }, - }, - - Subcommands::Log(log) => { - // Open the log file - debug!("Opening log file '{}'...", log.log.display()); - let handle: BufReader = match File::open(&log.log) { - Ok(handle) => BufReader::new(handle), - Err(err) => { - error!("{}", trace!(("Failed to open log file '{}'", log.log.display()), err)); - std::process::exit(1); - }, - }; - - // Separate the log into statements - debug!("Finding log statements..."); - let mut buf: String = String::new(); - let mut statements: Vec = Vec::new(); - for line in handle.lines() { - // Unwrap the line - let line: String = match line { - Ok(line) => line, - Err(err) => { - error!("{}", trace!(("Failed to read line from '{}'", log.log.display()), err)); - std::process::exit(1); - }, - }; - - // See if the line begins with what we want - if let Some(start_pos) = line_is_log_line(&line) { - // Flush the buffer if we have any to flush - if !buf.is_empty() { - // Attempt to parse the non-intro part as a LogStatement - match serde_json::from_str(&buf) { - Ok(stmt) => { - statements.push(stmt); - }, - Err(err) => { - error!( - "Failed to parse audit log line(s) as a log statement: {}\n\nLine(s):\n{}\n{}\n{}\n", - err, - (0..80).map(|_| '-').collect::(), - buf, - (0..80).map(|_| '-').collect::() - ); - std::process::exit(1); - }, - }; - // Clean the buffer to continue - buf.clear(); - } - - // Add the new line to the buffer - buf.push_str(&line[start_pos..]); - } else { - // Add to the buffer - buf.push('\n'); - buf.push_str(&line); - } - } - - // Parse the remainder of the buffer, too - if !buf.is_empty() { - // Attempt to parse the non-intro part as a LogStatement - match serde_json::from_str(&buf) { - Ok(stmt) => { - statements.push(stmt); - }, - Err(err) => { - error!( - "Failed to parse audit log line(s) as a log statement: {}\n\nLine(s):\n{}\n{}\n{}\n", - err, - (0..80).map(|_| '-').collect::(), - buf, - (0..80).map(|_| '-').collect::() - ); - std::process::exit(1); - }, - }; - } - - // Now continue with the subcommand to parse the statements - match log.action { - LogSubcommands::Reason(reason) => { - info!("Handling `log reason` subcommand"); - - // Search statements for reasoner outputs - let mut found: bool = false; - for stmt in statements { - if let LogStatement::ReasonerVerdict { reference, verdict } = stmt { - if reason.reference_id != reference { - continue; - } - - // Show the verdict - let verdict: &Verdict = verdict.as_ref(); - println!( - "Request '{}' was {}", - style(reference).bold(), - if let Verdict::Allow(_) = verdict { style("AUTHORIZED").bold().green() } else { style("DENIED").bold().red() } - ); - - // Mark as found - found = true; - } - } - - // Show special case if not found - if !found { - println!("Request '{}' was {} in the audit log", style(&reason.reference_id).bold(), style("not found").bold().yellow()); - } - }, - } - }, - } -} diff --git a/tools/key-manager/Cargo.toml b/tools/key-manager/Cargo.toml deleted file mode 100644 index 402c6a4..0000000 --- a/tools/key-manager/Cargo.toml +++ /dev/null @@ -1,24 +0,0 @@ -[package] -name = "key-manager" -description = "A tool that copies `branectl`s behaviour in making it easy to generates key/tokens." -rust-version = "1.82" -edition = "2021" -version.workspace = true -repository.workspace = true -authors.workspace = true -license.workspace = true - - -[dependencies] -# Crates.io -clap = { version = "4.5.6", features = ["derive"] } -humantime = "2.1" -jsonwebtoken = "9.2.0" -log = "0.4.22" - -# Brane -brane-ctl = { git = "https://github.com/braneframework/brane" } - -# Workspace dependencies -error-trace.workspace = true -humanlog.workspace = true diff --git a/tools/key-manager/src/main.rs b/tools/key-manager/src/main.rs deleted file mode 100644 index 2244887..0000000 --- a/tools/key-manager/src/main.rs +++ /dev/null @@ -1,143 +0,0 @@ -// MAIN.rs -// by Lut99 -// -// Created: -// 12 Mar 2024, 14:13:29 -// Last edited: -// 12 Mar 2024, 14:39:11 -// Auto updated? -// Yes -// -// Description: -//! Entrypoint for the `key-manager` binary. -// - -use std::path::PathBuf; - -use clap::{Parser, Subcommand}; -use error_trace::ErrorTrace as _; -use humanlog::{DebugMode, HumanLogger}; -use humantime::Duration; -use jsonwebtoken::jwk::KeyAlgorithm; -use log::{error, info}; - -/***** ARGUMENTS *****/ -/// The toplevel arguments! -#[derive(Debug, Parser)] -struct Arguments { - /// If given, enables additional INFO- and DEBUG-level statements. - #[clap(long, global = true, help = "If given, enables additional INFO- and DEBUG-level statements.")] - debug: bool, - /// If given, enables additional TRACE-level statements (implies '--debug'). - #[clap(long, global = true, help = "If given, enables additional TRACE-level statements (implies '--debug')")] - trace: bool, - - /// The toplevel subcommand to execute. - #[clap(subcommand)] - action: Subcommands, -} - -/// The toplevel subcommands. -#[derive(Debug, Subcommand)] -enum Subcommands { - #[clap(name = "generate", about = "Generates keys/tokens for the policy reasoner.")] - Generate(GenerateArguments), -} - -/// Generate-level arguments. -#[derive(Debug, Parser)] -struct GenerateArguments { - /// Defines the furtherly nested subcommand - #[clap(subcommand)] - action: GenerateSubcommands, -} - -/// Generate-level subcommands -#[derive(Debug, Subcommand)] -enum GenerateSubcommands { - #[clap(name = "key", alias = "jwk", about = "Generates a private key (as a JWK) for the policy reasoner.")] - Key(GenerateKeyArguments), - #[clap(name = "token", alias = "jwt", about = "Generates a token (as a JWT) for the policy reasoner using an existing key (JWK).")] - Token(GenerateTokenArguments), -} - -/// Defines the arguments for the `generate key`-subcommand. -#[derive(Debug, Parser)] -struct GenerateKeyArguments { - /// The path to export the key to. - #[clap(name = "OUTPUT_PATH", help = "The path of the file to write the new JWK to.")] - output: PathBuf, - - /// If given, fixes missing directories before attempting to create the output file. - #[clap(short, long, help = "If given, fixes missing directories before attempting to create the output file.")] - fix_dirs: bool, - /// The identifier of the key set, if useful. - #[clap(short, long, default_value = "foo", help = "The identifier of this key in the key set. In case this matters.")] - id: String, - /// The algorithm for which this key is suitable. - #[clap(short, long, default_value = "HS256", help = "The algorithm for which this key will be suitable. Determines the length of the key etc.")] - alg: KeyAlgorithm, -} - -/// Defines the arguments for the `generate token`-subcommand. -#[derive(Debug, Parser)] -struct GenerateTokenArguments { - /// The path to export the key to. - #[clap(name = "OUTPUT_PATH", help = "The path of the file to write the new JWK to.")] - output: PathBuf, - /// The name of the user that will be using the JWT. - #[clap(name = "USER", help = "The name of the user that will be using the JWT.")] - user: String, - /// The name of the system that is used to access the reasoner through, in case this matters. Omit to default to the same value as `USER`. - #[clap( - name = "SYSTEM", - help = "The name of the system that is used to access the reasoner through, in case this matters. Omit to default to the same value as \ - `USER`." - )] - system: Option, - /// The duration for which this token is valid. You can use postfixes (e.g., `d` for days, `y` for years, etc) - #[clap( - name = "DURATION", - default_value = "31d", - help = "The name of the The duration for which this token is valid. You can use postfixes (e.g., `d` for days, `y` for years, etc)." - )] - duration: Duration, - - /// If given, fixes missing directories before attempting to create the output file. - #[clap(short, long, help = "If given, fixes missing directories before attempting to create the output file.")] - fix_dirs: bool, - /// The path to the key to use. - #[clap(short, long, help = "The path to the private key (JWK) to use.")] - key: PathBuf, -} - -/***** ENTRYPOINT *****/ -fn main() { - // Parse arguments - let args = Arguments::parse(); - - // Initialize the logger - if let Err(err) = HumanLogger::terminal(DebugMode::from_flags(args.trace, args.debug)).init() { - eprintln!("WARNING: Failed to setup logger: {err} (no logging for this session)"); - } - info!("{} - v{}", env!("CARGO_BIN_NAME"), env!("CARGO_PKG_VERSION")); - - // Match on the subcommand - match args.action { - Subcommands::Generate(generate) => match generate.action { - GenerateSubcommands::Key(GenerateKeyArguments { output, fix_dirs, id, alg }) => { - if let Err(err) = brane_ctl::generate::policy_secret(fix_dirs, output, id, alg) { - error!("{}", err.trace()); - std::process::exit(1); - } - }, - GenerateSubcommands::Token(GenerateTokenArguments { output, user, system, duration, fix_dirs, key }) => { - let system: String = system.unwrap_or_else(|| user.clone()); - if let Err(err) = brane_ctl::generate::policy_token(fix_dirs, output, key, user, system, *duration) { - error!("{}", err.trace()); - std::process::exit(1); - } - }, - }, - } -} diff --git a/tools/policy-builder/Cargo.toml b/tools/policy-builder/Cargo.toml deleted file mode 100644 index 8daa7dc..0000000 --- a/tools/policy-builder/Cargo.toml +++ /dev/null @@ -1,23 +0,0 @@ -[package] -name = "policy-builder" -description = "Tool for taking a collection of eFLINT files and compiling them to a single eFLINT JSON Spec." -rust-version = "1.81" -edition = "2021" -authors = [ "Tim Müller" ] -version.workspace = true -repository.workspace = true -license.workspace = true - - -[dependencies] -# Crates.io -clap = { version = "4.5.6", features = ["derive"] } -console = "0.15.5" -log = "0.4.22" - -# Workspace dependencies -error-trace.workspace = true -humanlog.workspace = true - -# Path -eflint-to-json = { path = "../../lib/eflint-to-json" } diff --git a/tools/policy-builder/src/main.rs b/tools/policy-builder/src/main.rs deleted file mode 100644 index 06159d1..0000000 --- a/tools/policy-builder/src/main.rs +++ /dev/null @@ -1,127 +0,0 @@ -// MAIN.rs -// by Lut99 -// -// Created: -// 29 Nov 2023, 15:11:08 -// Last edited: -// 13 Dec 2023, 15:32:42 -// Auto updated? -// Yes -// -// Description: -//! Entrypoint for the `policy-builder` tool. -// - -use std::borrow::Cow; -use std::error; -use std::fmt::{Display, Formatter, Result as FResult}; -use std::fs::File; -use std::io::Write; -use std::path::PathBuf; - -use clap::Parser; -use console::Style; -use eflint_to_json::compile; -use error_trace::ErrorTrace as _; -use humanlog::{DebugMode, HumanLogger}; -use log::{debug, error, info}; - -/***** ERRORS *****/ -/// Defines errors originating in the binary itself. -#[derive(Debug)] -enum Error { - /// Failed to create the output file. - FileCreate { path: PathBuf, err: std::io::Error }, -} -impl Display for Error { - fn fmt(&self, f: &mut Formatter<'_>) -> FResult { - use Error::*; - match self { - FileCreate { path, .. } => write!(f, "Failed to create output file '{}'", path.display()), - } - } -} -impl error::Error for Error { - fn source(&self) -> Option<&(dyn error::Error + 'static)> { - use Error::*; - match self { - FileCreate { err, .. } => Some(err), - } - } -} - -/***** ARGUMENTS *****/ -/// The arguments for the tool. -#[derive(Debug, Parser)] -struct Arguments { - /// Whether to do INFO- and DEBUG-level statements. - #[clap(long, global = true, help = "If given, enables INFO- and DEBUG-level log statements.")] - debug: bool, - /// Whether to do TRACE-level statements. - #[clap(long, global = true, help = "If given, enables TRACE-level log statements. Implies '--debug'.")] - trace: bool, - - /// The eFLINT file to compile. - #[clap(name = "PATH", help = "Path pointing to the file to compile.")] - path: PathBuf, - /// The file to compile to. - #[clap( - short, - long, - help = "If given, writes the result to a file at the given location instead of stdout. Use '-' to explicitly redirect to stdout." - )] - output: Option, - - /// Overrides downloading to default location. - #[clap( - short, - long, - help = "If given, uses an existing 'eflint-to-json' executable. Otherwise, attempts to download from GitHub and writes to \ - `/tmp/eflint-to-json'." - )] - compiler: Option, -} - -/***** ENTRYPOINT *****/ -fn main() { - // Parse the arguments - let args: Arguments = Arguments::parse(); - - // Setup the logger - if let Err(err) = HumanLogger::terminal(DebugMode::from_flags(args.trace, args.debug)).init() { - eprintln!("WARNING: Failed to setup logger: {err} (no logging for this session)"); - } - info!("{} - v{}", env!("CARGO_PKG_NAME"), env!("CARGO_PKG_VERSION")); - - // Resolve the input file - debug!("Resolving output file..."); - let (output, output_dsc): (Box, Cow) = if let Some(output) = args.output { - if output != "-" { - let output_path: PathBuf = output.into(); - match File::create(&output_path) { - Ok(handle) => (Box::new(handle), Cow::Owned(output_path.to_string_lossy().into())), - Err(err) => { - error!("{}", Error::FileCreate { path: output_path, err }.trace()); - std::process::exit(1); - }, - } - } else { - (Box::new(std::io::stdout()), "".into()) - } - } else { - (Box::new(std::io::stdout()), "".into()) - }; - - // Run the thing, then - if let Err(err) = compile(&args.path, output, args.compiler.as_deref()) { - error!("{}", err.trace()); - std::process::exit(1); - } - - // Done - println!( - "Successfully compiled {} to {}", - Style::new().bold().green().apply_to(args.path.display()), - Style::new().bold().green().apply_to(output_dsc), - ); -} From 2e46d822cbefe705c79a1604b1733867ce78df47 Mon Sep 17 00:00:00 2001 From: Lut99 Date: Tue, 15 Oct 2024 17:43:27 +0200 Subject: [PATCH 02/82] Posix reasoner is now working Settled on having the paths come into a config instead of the ol' Brane way. Next up: extending logging, re-introducing contexts. --- Cargo.lock | 35 +++ Cargo.toml | 22 +- examples/posix.rs | 188 ++++++++++++- lib/reasoners/posix/Cargo.toml | 3 +- lib/reasoners/posix/src/config.rs | 77 ++++++ lib/reasoners/posix/src/lib.rs | 3 +- lib/reasoners/posix/src/reasonerconn.rs | 354 ++++++------------------ lib/reasoners/posix/src/workflow.rs | 28 +- lib/workflow/src/lib.rs | 3 +- run-posix-example.sh | 26 ++ src/lib.rs | 6 +- 11 files changed, 461 insertions(+), 284 deletions(-) create mode 100644 lib/reasoners/posix/src/config.rs create mode 100755 run-posix-example.sh diff --git a/Cargo.lock b/Cargo.lock index b2dc282..aa408c4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1171,6 +1171,7 @@ dependencies = [ "file-resolver", "mock-logger", "no-op-reasoner", + "posix-reasoner", "serde_json", "specifications", "tokio", @@ -1185,6 +1186,20 @@ version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "350e9b48cbc6b0e028b0473b114454c6316e57336ee184ceab6e53f72c178b3e" +[[package]] +name = "posix-reasoner" +version = "0.2.0" +dependencies = [ + "error-trace 3.1.0", + "serde", + "serde_json", + "specifications", + "thiserror", + "tokio", + "tracing", + "workflow", +] + [[package]] name = "proc-macro2" version = "1.0.94" @@ -1611,6 +1626,26 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "thiserror" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "thread_local" version = "1.1.9" diff --git a/Cargo.toml b/Cargo.toml index 4824edc..940473b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,7 +4,7 @@ members = [ # Implementations "lib/reasoners/eflint-json", "lib/reasoners/no-op", - # "lib/reasoners/posix", + "lib/reasoners/posix", "lib/loggers/file", "lib/loggers/mock", "lib/resolvers/file", @@ -37,10 +37,10 @@ name = "eflint" path = "./examples/eflint.rs" required-features = ["eflint-json-reasoner", "eflint-to-json", "file-logger"] -# [[example]] -# name = "posix" -# path = "./examples/posix.rs" -# required-features = ["posix-reasoner", "file-logger"] +[[example]] +name = "posix" +path = "./examples/posix.rs" +required-features = ["posix-reasoner", "file-logger", "serde", "workflow"] [[example]] name = "no-op" @@ -51,7 +51,7 @@ required-features = ["no-op-reasoner", "file-logger"] [dependencies] eflint-json-reasoner = { path = "./lib/reasoners/eflint-json", optional = true } no-op-reasoner = { path = "./lib/reasoners/no-op", optional = true } -# posix-reasoner = { path = "./lib/reasoners/posix", optional = true } +posix-reasoner = { path = "./lib/reasoners/posix", optional = true } file-logger = { path = "./lib/loggers/file", optional = true } mock-logger = { path = "./lib/loggers/mock", optional = true } file-resolver = { path = "./lib/resolvers/file", optional = true } @@ -67,7 +67,7 @@ error-trace = { git = "https://github.com/Lut99/error-trace-rs", tag = "v3.0.0" serde_json = "1.0.120" tracing = "0.1.40" tracing-subscriber = { version = "0.3.18", features = ["env-filter"] } -tokio = { version = "1.40.0", default-features = false, features = ["macros", "rt"] } +tokio = { version = "1.40.0", default-features = false, features = ["fs", "io-std", "macros", "rt"] } [features] @@ -75,11 +75,11 @@ default = [] all = ["reasoners", "loggers", "resolvers", "auxillary"] -# reasoners = ["eflint-json-reasoner", "no-op-reasoner", "posix-reasoner"] -reasoners = ["eflint-json-reasoner", "no-op-reasoner"] +reasoners = ["eflint-json-reasoner", "no-op-reasoner", "posix-reasoner"] +# reasoners = ["eflint-json-reasoner", "no-op-reasoner"] eflint-json-reasoner = ["dep:eflint-json-reasoner"] no-op-reasoner = ["dep:no-op-reasoner"] -# posix-reasoner = ["dep:posix-reasoner"] +posix-reasoner = ["dep:posix-reasoner"] loggers = ["file-logger", "mock-logger"] file-logger = ["dep:file-logger"] @@ -92,6 +92,8 @@ auxillary = ["eflint-to-json", "workflow"] eflint-to-json = ["dep:eflint-to-json"] workflow = ["dep:workflow"] +serde = ["workflow/serde"] + [workspace.dependencies] # The infamous lut99 crate set diff --git a/examples/posix.rs b/examples/posix.rs index db7966f..8329646 100644 --- a/examples/posix.rs +++ b/examples/posix.rs @@ -4,7 +4,7 @@ // Created: // 11 Oct 2024, 16:32:29 // Last edited: -// 11 Oct 2024, 16:32:52 +// 15 Oct 2024, 17:05:12 // Auto updated? // Yes // @@ -13,6 +13,190 @@ //! persmissions. // +use std::path::PathBuf; + +use clap::Parser; +use console::style; +use error_trace::trace; +use file_logger::FileLogger; +use policy_reasoner::reasoners::posix::{PosixReasonerConnector, State}; +use policy_reasoner::spec::ReasonerConnector as _; +use policy_reasoner::spec::auditlogger::SessionedAuditLogger; +use policy_reasoner::workflow::Workflow; +use posix_reasoner::config::Config; +use spec::reasonerconn::ReasonerResponse; +use tokio::fs; +use tokio::io::{self, AsyncReadExt as _}; +use tracing::{Level, debug, error, info}; + + +/***** HELPER FUNCTIONS *****/ +/// Reads a [`Workflow`] from either stdin or disk. +/// +/// # Arguments +/// - `input`: Either '-' to read from stdin, or a path of the file to read from otherwise. +/// +/// # Returns +/// A parsed [`Workflow`] file. +/// +/// # Errors +/// This function errors if it failed to read stdin OR the file, or parse it as a valid Workflow. +/// +/// Note that errorring is done by calling [`std::process::exit()`]. +async fn load_workflow(input: String) -> Workflow { + let workflow: String = if input == "-" { + let mut raw: Vec = Vec::new(); + if let Err(err) = io::stdin().read_buf(&mut raw).await { + error!("{}", trace!(("Failed to read from stdin"), err)); + std::process::exit(1); + } + match String::from_utf8(raw) { + Ok(raw) => raw, + Err(err) => { + error!("{}", trace!(("Stdin is not valid UTF-8"), err)); + std::process::exit(1); + }, + } + } else { + match fs::read_to_string(&input).await { + Ok(raw) => raw, + Err(err) => { + error!("{}", trace!(("Failed to read the workflow file {input:?}"), err)); + std::process::exit(1); + }, + } + }; + match serde_json::from_str(&workflow) { + Ok(config) => config, + Err(err) => { + error!("{}", trace!(("{} is not a valid workflow", if input == "-" { "Stdin".to_string() } else { format!("File {input:?}") }), err)); + std::process::exit(1); + }, + } +} + +/// Reads a [`Config`] from disk. +/// +/// # Arguments +/// - `path`: The path to the config file to load. +/// +/// # Returns +/// A parsed [`Config`] file. +/// +/// # Errors +/// This function errors if it failed to read the file, or it did not contain a valid config. +/// +/// Note that errorring is done by calling [`std::process::exit()`]. +async fn load_config(path: PathBuf) -> Config { + // Load the file and parse it + let config: String = match fs::read_to_string(&path).await { + Ok(raw) => raw, + Err(err) => { + error!("{}", trace!(("Failed to read the config file {:?}", path.display()), err)); + std::process::exit(1); + }, + }; + let mut config: Config = match serde_json::from_str(&config) { + Ok(config) => config, + Err(err) => { + error!("{}", trace!(("File {:?} is not a valid config file", path.display()), err)); + std::process::exit(1); + }, + }; + + // Resolve relative files to relative to the binary, for consistency of calling the example + let prefix: PathBuf = match std::env::current_exe() { + Ok(path) => { + if let Some(parent) = path.parent() { + parent.into() + } else { + path + } + }, + Err(err) => { + error!("{}", trace!(("Failed to obtain the current executable's path"), err)); + std::process::exit(1); + }, + }; + for path in config.data.values_mut().map(|data| &mut data.path) { + if path.is_relative() { + *path = prefix.join(&*path); + } + } + debug!("Config after resolving relative paths: {config:?}"); + + // Done + config +} + + + + + +/***** ARGUMENTS *****/ +/// The arguments for this binary. +#[derive(Parser)] +pub struct Arguments { + /// Whether to make `info!()` and `debug!()` visible. + #[clap(long, help = "If given, enables INFO- and DEBUG-level logging.")] + debug: bool, + /// Whether to make `trace!()` visible. + #[clap(long, help = "If given, enables TRACE-level logging. Implies '--debug'.")] + trace: bool, + + /// The file containing the workflow to check. + #[clap(name = "WORKFLOW", default_value = "-", help = "The JSON workflow to evaluate. Use '-' to read from stdin.")] + workflow: String, + /// The file containing the config for the reasoner. + #[clap(short, long, help = "The JSON configuration file to read that configures the policy.")] + config: PathBuf, +} + + + + /***** ENTRYPOINT *****/ -fn main() {} +#[tokio::main(flavor = "current_thread")] +async fn main() { + // Parse the arguments + let args = Arguments::parse(); + + // Setup the logger + tracing_subscriber::fmt() + .with_max_level(if args.trace { + Level::TRACE + } else if args.debug { + Level::DEBUG + } else { + Level::WARN + }) + .init(); + info!("{} - v{}", env!("CARGO_BIN_NAME"), env!("CARGO_PKG_VERSION")); + + // Read the workflow & config + let workflow: Workflow = load_workflow(args.workflow).await; + let config: Config = load_config(args.config).await; + + // Create the logger + let logger: SessionedAuditLogger = + SessionedAuditLogger::new("test", FileLogger::new(format!("{} - v{}", env!("CARGO_BIN_NAME"), env!("CARGO_PKG_VERSION")), "./test.log")); + + // Run the reasoner + let conn: PosixReasonerConnector = PosixReasonerConnector::new(); + let verdict: ReasonerResponse<()> = match conn.consult(State { workflow, config }, (), &logger).await { + Ok(res) => res, + Err(err) => { + error!("{}", trace!(("Failed to consult the POSIX reasoner"), err)); + std::process::exit(1); + }, + }; + + // OK, report + match verdict { + ReasonerResponse::Success => println!("{} {}", style("Reasoner says:").bold(), style("OK").bold().green()), + ReasonerResponse::Violated(_) => { + println!("{} {}", style("Reasoner says:").bold(), style("VIOLATION").bold().red()); + }, + } +} diff --git a/lib/reasoners/posix/Cargo.toml b/lib/reasoners/posix/Cargo.toml index 0fb614f..b1f1fc2 100644 --- a/lib/reasoners/posix/Cargo.toml +++ b/lib/reasoners/posix/Cargo.toml @@ -139,10 +139,11 @@ license.workspace = true [dependencies] -itertools = "0.13.0" +# itertools = "0.13.0" serde = { version = "1.0.204", features = ["derive"] } serde_json = "1.0.120" thiserror = "1.0.61" +tokio = { version = "1.40.0", default-features = false, features = ["fs", "io-util"] } tracing = "0.1.40" error-trace = { git = "https://github.com/Lut99/error-trace-rs", tag = "v3.1.0" } diff --git a/lib/reasoners/posix/src/config.rs b/lib/reasoners/posix/src/config.rs new file mode 100644 index 0000000..7a31d89 --- /dev/null +++ b/lib/reasoners/posix/src/config.rs @@ -0,0 +1,77 @@ +// CONFIG.rs +// by Lut99 +// +// Created: +// 15 Oct 2024, 14:17:44 +// Last edited: +// 15 Oct 2024, 14:37:11 +// Auto updated? +// Yes +// +// Description: +//! Defines the outwards-facing config file that sets the +//! `posix`-reasoner up. +// + +use std::collections::HashMap; +use std::path::PathBuf; + +use serde::{Deserialize, Serialize}; + + +/***** LIBRARY *****/ +/// Defines the config for the POSIX-reasoner. +/// +/// Specifically, this: +/// - Maps datasets to where to find them; and +/// - Maps which users are allowed to read from/write to it. +#[derive(Clone, Debug, Deserialize, Serialize)] +pub struct Config { + /// Defines the location ID of this location. + pub id: String, + /// Defines a map from datasets to where to find them on the disk (that one Harry Potter movie?) + #[serde(default = "HashMap::new", skip_serializing_if = "HashMap::is_empty")] + pub data: HashMap, +} + + + +/// Part of the [`Config`]. Represents a location (e.g., `st_antonius_etc`) and contains the global workflow +/// username to local identity mappings for this location. +#[derive(Clone, Debug, Deserialize, Serialize)] +pub struct DataPolicy { + /// The location where we find this dataset on disk. + pub path: PathBuf, + #[serde(default = "HashMap::new", skip_serializing_if = "HashMap::is_empty")] + pub user_map: HashMap, +} + +/// The local identity defines a user id and a list of group ids. The local identity is used on the machine on which a +/// dataset resides to check the local file permissions. For more about this permissions check see +/// [`validate_dataset_permissions`]. +/// +/// This identity is defined in the POSIX policy file. Global usernames in the POSIX policy map to these local +/// identities. +/// +/// Example, given the POSIX policy file below, then for the `st_antonius_ect` location, the `test` global username maps +/// to a local identity that contains the uid and gids. +/// ``` yaml +/// # file: posix-policy.yml +/// content: +/// st_antonius_ect: +/// user_map: +/// test: +/// uid: 1000 +/// gids: +/// - 1001 +/// - 1002 +/// - 1003 +/// ``` +#[derive(Clone, Debug, Deserialize, Serialize)] +pub struct PosixLocalIdentity { + /// The user identifier of a Linux user. + pub uid: u32, + /// A list of Linux group identifiers. + #[serde(default = "Vec::new", skip_serializing_if = "Vec::is_empty")] + pub gids: Vec, +} diff --git a/lib/reasoners/posix/src/lib.rs b/lib/reasoners/posix/src/lib.rs index d759d8c..15c43db 100644 --- a/lib/reasoners/posix/src/lib.rs +++ b/lib/reasoners/posix/src/lib.rs @@ -4,7 +4,7 @@ // Created: // 11 Oct 2024, 16:35:23 // Last edited: -// 11 Oct 2024, 16:54:39 +// 15 Oct 2024, 14:18:42 // Auto updated? // Yes // @@ -141,6 +141,7 @@ // // Declare the modules +pub mod config; mod reasonerconn; mod workflow; diff --git a/lib/reasoners/posix/src/reasonerconn.rs b/lib/reasoners/posix/src/reasonerconn.rs index 7712860..b0b5c2d 100644 --- a/lib/reasoners/posix/src/reasonerconn.rs +++ b/lib/reasoners/posix/src/reasonerconn.rs @@ -4,7 +4,7 @@ // Created: // 11 Oct 2024, 16:54:51 // Last edited: -// 14 Oct 2024, 11:58:58 +// 15 Oct 2024, 17:20:44 // Auto updated? // Yes // @@ -14,53 +14,38 @@ /***** LIBRARY *****/ -use std::collections::HashMap; -use std::convert::Infallible; use std::future::Future; use std::iter::repeat; -use std::ops::{BitAnd, BitAndAssign, BitOr, BitOrAssign}; -use std::os::unix::fs::{MetadataExt, PermissionsExt}; -use std::path::Path; +use std::ops::BitOr; +use std::os::unix::fs::{MetadataExt as _, PermissionsExt as _}; +use std::path::{Path, PathBuf}; -use itertools::{Either, Itertools}; use serde::Deserialize; use spec::auditlogger::{AuditLogger, SessionedAuditLogger}; use spec::reasonerconn::{ReasonerConnector, ReasonerResponse}; -use tracing::{info, span, Level}; +use thiserror::Error; +use tokio::fs; +use tracing::{Level, debug, info, span}; use workflow::Workflow; +use crate::config::{Config, DataPolicy, PosixLocalIdentity}; use crate::workflow::WorkflowDatasets; -/***** TYPES *****/ -/// E.g., `st_antonius_etc`. -type LocationIdentifier = String; -/// The global username as defined in [`Workflow.user`]. E.g., `test`. -type GlobalUsername = String; - - - - - /***** ERRORS *****/ -/// Represents an error that occurred during the validation of a policy. These errors contain more information about the -/// problems that occurred during validation. -#[derive(thiserror::Error, Debug)] -enum PolicyError { - #[error("Missing location: {0}")] - MissingLocation(String), - #[error("Missing user: {0} for location: {1}")] - MissingUser(String, String), -} - -/// Represents a validation error that occurred during the validation of a workflow. These errors contain more -/// information about the problems that occurred during validation. -#[derive(thiserror::Error, Debug)] -enum ValidationError { - #[error("Policy Error: {0}")] - PolicyError(PolicyError), - #[error("Unknown dataset: {0}")] - UnknownDataset(String), +/// Represents an error that occurs during validation of the policy. +#[derive(Debug, Error)] +pub enum Error { + /// Failed to retrieve a file's metadata. + #[error("Failed to get file {:?} metadata", path.display())] + FileMetadata { + path: PathBuf, + #[source] + err: std::io::Error, + }, + /// The dataset was unknown to us. + #[error("Unknown dataset {data:?}")] + UnknownDataset { data: String }, } @@ -68,81 +53,52 @@ enum ValidationError { /***** HELPER FUNCTIONS *****/ -/// Check if all the data accesses performed in the `workflow` are done on behalf of users that have the required -/// permissions. If not all permissions are met, then [`ValidationError`]s are returned. These errors contain more -/// information about the problems that occurred during validation. -fn validate_dataset_permissions(workflow: &Workflow, policy: &PosixPolicy) -> Result> { - let _span = span!(Level::INFO, "PosixReasonerConnector::validate_dataset_permissions", workflow = workflow.id).entered(); - - // The datasets used in the workflow. E.g., `st_antonius_ect`. - let datasets: WorkflowDatasets = WorkflowDatasets::from(workflow); - - // Loop to find the permissions on the disk - let (forbidden, errors): (Vec<_>, Vec<_>) = std::iter::empty() - .chain(datasets.read_sets.iter().zip(repeat(PosixFilePermission::Read.to_set()))) - .chain(datasets.write_sets.iter().zip(repeat(PosixFilePermission::Write.to_set()))) - .chain(datasets.execute_sets.iter().zip(repeat(PosixFilePermission::Read | PosixFilePermission::Execute))) - .flat_map(|((location, dataset), permission)| { - let Some(dataset) = data_index.get(&dataset.name) else { - return Either::Left(std::iter::once(Err(ValidationError::UnknownDataset(dataset.name.clone())))); - }; - Either::Right(dataset.access.values().map(move |kind| match kind { - specifications::data::AccessKind::File { path } => { - info!("Contents of the DataInfo object:\n{:#?}", dataset); - let local_identity = policy.get_local_identity(location, &workflow.user.name).map_err(ValidationError::PolicyError)?; - let result = satisfies_posix_permissions(path, local_identity, &permission); - Ok((dataset.name.clone(), path, result)) - }, - })) - }) - // This is where we are going to focus on the problems that occurred in the validation - // These can be separated into groups: Errors (e.g. Non-existing users / files), and - // validation failures. - .filter(|res| match res { - // Filter out what was okay in either sense. - Ok((_, _, true)) => false, - _ => true, - }) - .partition_map(|elem| match elem { - Ok((dataset_identifier, _, _)) => Either::Left(dataset_identifier), - Err(x) => Either::Right(x), - }); - - if !errors.is_empty() { - Err(errors) - } else if forbidden.is_empty() { - return Ok(ValidationOutput::Ok); - } else { - return Ok(ValidationOutput::Fail(forbidden)); - } -} - /// Verifies whether the passed [`PosixLocalIdentity`] has all of the requested permissions (e.g., `Read` and `Write`) /// on a particular file (defined by the `path`). The identity's user id and group ids are checked against the file /// owner's user id and group id respectively. Additionally, the `Others` class permissions are also checked. -fn satisfies_posix_permissions(path: impl AsRef, local_identity: &PosixLocalIdentity, requested_permissions: &[PosixFilePermission]) -> bool { - let metadata = std::fs::metadata(&path).expect("Could not get file metadata"); +async fn satisfies_posix_permissions( + path: impl AsRef, + local_identity: Option<&PosixLocalIdentity>, + requested_permissions: PosixFilePermissions, +) -> Result { + #[inline] + const fn is_user_owner(owner_id: u32, local_identity: Option<&PosixLocalIdentity>) -> bool { + if let Some(id) = local_identity { owner_id == id.uid } else { false } + } + #[inline] + fn is_group_owner(group_id: u32, local_identity: Option<&PosixLocalIdentity>) -> bool { + if let Some(id) = local_identity { id.gids.contains(&group_id) } else { false } + } + + let path: &Path = path.as_ref(); + let metadata = fs::metadata(path).await.map_err(|err| Error::FileMetadata { path: path.into(), err })?; + // First, get the appropriate UIDs from the file let mode_bits = metadata.permissions().mode(); let file_owner_uid = metadata.uid(); let file_owner_gid = metadata.gid(); + debug!("Checking if user {local_identity:?} is owner of file with UID={file_owner_uid},GID={file_owner_gid}"); + + // Then decide which permissions to base ourselves on + let mask: u32 = if is_user_owner(file_owner_uid, local_identity) { + // If the user owns the file, then we exclusively assume user permissions + let mask: u32 = PosixFileClass::Owner.get_mode_bitmask(requested_permissions); + debug!("Using OWNER permissions ({mask:o})"); + mask + } else if is_group_owner(file_owner_gid, local_identity) { + // If the user has a group owning the file, then we exclusively assume group permissions + let mask: u32 = PosixFileClass::Group.get_mode_bitmask(requested_permissions); + debug!("Using GROUP permissions ({mask:o})"); + mask + } else { + // In any other scenario (including user unknown), we assume other permissions + let mask: u32 = PosixFileClass::Others.get_mode_bitmask(requested_permissions); + debug!("Using OTHER permissions ({mask:o})"); + mask + }; - if file_owner_uid == local_identity.uid { - let mask = PosixFileClass::Owner.get_mode_bitmask(requested_permissions); - if mode_bits & mask == mask { - return true; - } - } - - if local_identity.gids.contains(&file_owner_gid) { - let mask = PosixFileClass::Group.get_mode_bitmask(requested_permissions); - if mode_bits & mask == mask { - return true; - } - } - - let mask = PosixFileClass::Others.get_mode_bitmask(requested_permissions); - mode_bits & mask == mask + // Finally, check if the permissions align + Ok(mode_bits & mask == mask) } @@ -150,13 +106,6 @@ fn satisfies_posix_permissions(path: impl AsRef, local_identity: &PosixLoc /***** HELPERS *****/ -enum ValidationOutput { - Ok, - // Below we might want to encapsulate the Dataset itself. - /// The string here represents a `Dataset.name`. - Fail(Vec), -} - /// Represents a POSIX file class, also known as a scope. See: /// . #[derive(Copy, Clone, Deserialize)] @@ -171,128 +120,24 @@ impl PosixFileClass { /// This bitmask represents what mode bits should be set on a file such that this class (e.g., `Owner`) satisfies /// the permissions (e.g, `Read`, `Write`). In this case it would be `0o400` (Read for Owner) and `0o200` (Write for /// Owner), which sums to the returned `0o600` (Read and Write for Owner). - fn get_mode_bitmask(&self, required_permissions: &[PosixFilePermission]) -> u32 { + fn get_mode_bitmask(&self, required_permissions: PosixFilePermissions) -> u32 { let alignment_multiplier = match self { PosixFileClass::Owner => 0o100, PosixFileClass::Group => 0o10, PosixFileClass::Others => 0o1, }; - required_permissions.iter().fold(0, |acc, f| acc | (alignment_multiplier * f.to_mode_bit())) + alignment_multiplier * (required_permissions.as_u8() as u32) } } -/// Part of the [`PosixPolicy`]. Represents a location (e.g., `st_antonius_etc`) and contains the global workflow -/// username to local identity mappings for this location. -#[derive(Deserialize, Debug)] -pub struct PosixPolicyLocation { - user_map: HashMap, -} - -/// The local identity defines a user id and a list of group ids. The local identity is used on the machine on which a -/// dataset resides to check the local file permissions. For more about this permissions check see -/// [`validate_dataset_permissions`]. -/// -/// This identity is defined in the POSIX policy file. Global usernames in the POSIX policy map to these local -/// identities. -/// -/// Example, given the POSIX policy file below, then for the `st_antonius_ect` location, the `test` global username maps -/// to a local identity that contains the uid and gids. -/// ``` yaml -/// # file: posix-policy.yml -/// content: -/// st_antonius_ect: -/// user_map: -/// test: -/// uid: 1000 -/// gids: -/// - 1001 -/// - 1002 -/// - 1003 -/// ``` -#[derive(Deserialize, Debug)] -struct PosixLocalIdentity { - /// The user identifier of a Linux user. - uid: u32, - /// A list of Linux group identifiers. - gids: Vec, -} - /// Represents a set of file permissions. #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] struct PosixFilePermissions(u8); impl PosixFilePermissions { - /// Returns whether the read bit is set. - #[inline] - const fn read(&self) -> bool { (self.0 & (PosixFilePermission::Read.to_mode_bit() as u8)) != 0 } - - /// Returns whether the write bit is set. - #[inline] - const fn write(&self) -> bool { (self.0 & (PosixFilePermission::Write.to_mode_bit() as u8)) != 0 } - - /// Returns whether the execute bit is set. - #[inline] - const fn exec(&self) -> bool { (self.0 & (PosixFilePermission::Execute.to_mode_bit() as u8)) != 0 } - /// Returns the raw bit pattern for this permission set. #[inline] const fn as_u8(&self) -> u8 { self.0 } } -impl BitAnd for PosixFilePermissions { - type Output = Self; - - #[inline] - fn bitand(self, rhs: PosixFilePermission) -> Self::Output { Self(self.0 & (rhs.to_mode_bit() as u8)) } -} -impl BitAndAssign for PosixFilePermissions { - #[inline] - fn bitand_assign(&mut self, rhs: PosixFilePermission) { self.0 &= rhs.to_mode_bit() as u8; } -} -impl BitOr for PosixFilePermissions { - type Output = Self; - - #[inline] - fn bitor(self, rhs: PosixFilePermission) -> Self::Output { Self(self.0 | (rhs.to_mode_bit() as u8)) } -} -impl BitOrAssign for PosixFilePermissions { - #[inline] - fn bitor_assign(&mut self, rhs: PosixFilePermission) { self.0 |= rhs.to_mode_bit() as u8; } -} -impl BitAnd for PosixFilePermissions { - type Output = Self; - - #[inline] - fn bitand(self, rhs: Self) -> Self::Output { Self(self.0 & rhs.0) } -} -impl BitAndAssign for PosixFilePermissions { - #[inline] - fn bitand_assign(&mut self, rhs: Self) { self.0 &= rhs.0; } -} -impl BitOr for PosixFilePermissions { - type Output = Self; - - #[inline] - fn bitor(self, rhs: Self) -> Self::Output { Self(self.0 | rhs.0) } -} -impl BitOrAssign for PosixFilePermissions { - #[inline] - fn bitor_assign(&mut self, rhs: Self) { self.0 |= rhs.0; } -} -impl From for PosixFilePermissions { - #[inline] - fn from(value: PosixFilePermission) -> Self { Self(value.to_mode_bit() as u8) } -} -impl From for u8 { - #[inline] - fn from(value: PosixFilePermissions) -> Self { value.as_u8() } -} -impl From<&PosixFilePermissions> for u8 { - #[inline] - fn from(value: &PosixFilePermissions) -> Self { value.as_u8() } -} -impl From<&mut PosixFilePermissions> for u8 { - #[inline] - fn from(value: &mut PosixFilePermissions) -> Self { value.as_u8() } -} /// Represents a POSIX file permission. See: . #[derive(Debug, Copy, Clone)] @@ -326,30 +171,12 @@ impl PosixFilePermission { #[inline] fn to_set(&self) -> PosixFilePermissions { PosixFilePermissions(self.to_mode_bit() as u8) } } -impl BitAnd for PosixFilePermission { - type Output = PosixFilePermissions; - - #[inline] - fn bitand(self, rhs: PosixFilePermission) -> Self::Output { PosixFilePermissions((self.to_mode_bit() & rhs.to_mode_bit()) as u8) } -} impl BitOr for PosixFilePermission { type Output = PosixFilePermissions; #[inline] fn bitor(self, rhs: PosixFilePermission) -> Self::Output { PosixFilePermissions((self.to_mode_bit() | rhs.to_mode_bit()) as u8) } } -impl BitAnd for PosixFilePermission { - type Output = PosixFilePermissions; - - #[inline] - fn bitand(self, rhs: PosixFilePermissions) -> Self::Output { PosixFilePermissions((self.to_mode_bit() as u8) & rhs.0) } -} -impl BitOr for PosixFilePermission { - type Output = PosixFilePermissions; - - #[inline] - fn bitor(self, rhs: PosixFilePermissions) -> Self::Output { PosixFilePermissions((self.to_mode_bit() as u8) | rhs.0) } -} @@ -360,32 +187,11 @@ impl BitOr for PosixFilePermission { #[derive(Debug)] pub struct State { /// The policy to give. - pub policy: PosixPolicy, + pub config: Config, /// The workflow considered. pub workflow: Workflow, } -/// The overarching POSIX policy. Check out the module documentation for an overview. -#[derive(Deserialize, Debug)] -pub struct PosixPolicy { - datasets: HashMap, -} -impl PosixPolicy { - /// Given a location (e.g., `st_antonius_ect`) and the workflow user's name (e.g., `test`), returns the - /// [`PosixLocalIdentity`] for that user. - /// - /// The returned identity is used for file permission checks. For more about this permissions check see - /// [`validate_dataset_permissions`]. - fn get_local_identity(&self, location: &str, workflow_user: &str) -> Result<&PosixLocalIdentity, PolicyError> { - self.datasets - .get(location) - .ok_or_else(|| PolicyError::MissingLocation(location.to_owned()))? - .user_map - .get(workflow_user) - .ok_or_else(|| PolicyError::MissingUser(workflow_user.to_owned(), location.to_owned())) - } -} - /// The POSIX reasoner connector. This connector is used to validate workflows based on POSIX file permissions. @@ -395,11 +201,12 @@ impl PosixReasonerConnector { pub const fn new() -> Self { PosixReasonerConnector } } impl ReasonerConnector for PosixReasonerConnector { - type Error = Infallible; + type Error = Error; type Question = (); - type Reason = Vec; + type Reason = (); type State = State; + #[inline] fn consult( &self, state: Self::State, @@ -412,13 +219,32 @@ impl ReasonerConnector for PosixReasonerConnector { async move { let _span = span!(Level::INFO, "ReasonerConnector::consult", reference = logger.reference()).entered(); - match validate_dataset_permissions(&state.workflow, &state.policy) { - Ok(ValidationOutput::Ok) => Ok(ReasonerResponse::Success), - Ok(ValidationOutput::Fail(datasets)) => Ok(ReasonerResponse::Violated( - datasets.into_iter().map(|dataset| format!("We do not have sufficient permissions for dataset: {dataset}")).collect(), - )), - Err(errors) => Ok(ReasonerResponse::Violated(errors.into_iter().map(|error| error.to_string()).collect())), + // The datasets used in the workflow. E.g., `st_antonius_ect`. + let datasets: WorkflowDatasets = WorkflowDatasets::from(&state.workflow); + debug!("Found datasets in workflow {:?}: {:#?}", state.workflow.id, datasets); + + // Loop to find the permissions on the disk + for ((location, dataset), permission) in std::iter::empty() + .chain(datasets.read_sets.iter().zip(repeat(PosixFilePermission::Read.to_set()))) + .chain(datasets.write_sets.iter().zip(repeat(PosixFilePermission::Write.to_set()))) + .chain(datasets.execute_sets.iter().zip(repeat(PosixFilePermission::Read | PosixFilePermission::Execute))) + { + info!("Testing dataset {:?} for permission to {:?} for user {:?}", dataset.id, permission, location); + + // Find the location of the dataset in the list + let policy: &DataPolicy = match state.config.data.get(&dataset.id) { + Some(data) => &data, + None => return Err(Error::UnknownDataset { data: dataset.id.clone() }), + }; + + // Now check the policy! + if !satisfies_posix_permissions(&policy.path, policy.user_map.get(&location.id), permission).await? { + return Ok(ReasonerResponse::Violated(())); + } } + + // If none of them failed prematurely, then we're done + Ok(ReasonerResponse::Success) } } } diff --git a/lib/reasoners/posix/src/workflow.rs b/lib/reasoners/posix/src/workflow.rs index 775b962..1088206 100644 --- a/lib/reasoners/posix/src/workflow.rs +++ b/lib/reasoners/posix/src/workflow.rs @@ -4,7 +4,7 @@ // Created: // 11 Oct 2024, 16:54:04 // Last edited: -// 14 Oct 2024, 11:58:50 +// 15 Oct 2024, 17:42:33 // Auto updated? // Yes // @@ -15,7 +15,7 @@ use std::convert::Infallible; use std::sync::LazyLock; -use tracing::{debug, span, Level}; +use tracing::{Level, debug, span}; use workflow::visitor::Visitor; use workflow::{Dataset, ElemCall, Entity, Workflow}; @@ -28,6 +28,20 @@ pub static UNSPECIFIED_LOCATION: LazyLock = LazyLock::new(|| Entity { id +/***** HELPERS *****/ +/// Special trait that will throw a compile error if [`DatasetCollector`] does not have an +/// [`Infallible`] error type. +/// +/// This is important because of the unsafe block in [`WorkflowDatasets::from()`]. +trait _InfallibleAssertion<'w>: Visitor<'w, Error = Infallible> {} +// Note: this cannot be implemented anymore if the error is not Infallible +// If that ever occurs, re-consider the `unwrap_unchecked()` in the from below +impl<'w> _InfallibleAssertion<'w> for DatasetCollector<'w> {} + + + + + /***** VISITORS *****/ /// Visits a [`Workflow`] in order to find all datasets used. struct DatasetCollector<'w> { @@ -82,7 +96,9 @@ impl<'w> Visitor<'w> for DatasetCollector<'w> { let location: &'w Entity = elem.at.as_ref().unwrap_or_else(|| LazyLock::force(&UNSPECIFIED_LOCATION)); self.read_sets.extend(elem.input.iter().map(|d| (location, d))); self.write_sets.extend(elem.output.iter().map(|d| (location, d))); - Ok(()) + + // Also visit the next one before returning, lol + self.visit(&elem.next) } } @@ -93,6 +109,7 @@ impl<'w> Visitor<'w> for DatasetCollector<'w> { /***** LIBRARY *****/ /// The datasets accessed and/or modified in a workflow. These are grouped by file permission type. For creating this /// struct see: [`find_datasets_in_workflow`]. +#[derive(Clone, Debug)] pub struct WorkflowDatasets<'w> { pub read_sets: Vec<(&'w Entity, &'w Dataset)>, pub write_sets: Vec<(&'w Entity, &'w Dataset)>, @@ -105,7 +122,10 @@ impl<'w> From<&'w Workflow> for WorkflowDatasets<'w> { debug!("Walking the workflow in order to find datasets. Starting with {:?}", &value.start); let mut visitor = DatasetCollector::default(); - value.visit(&mut visitor); + // SAFETY: We can do this because `DatasetCollector` cannot physically error (its error is `Infallible`) + unsafe { + value.visit(&mut visitor).unwrap_unchecked(); + } WorkflowDatasets { read_sets: visitor.read_sets, write_sets: visitor.write_sets, execute_sets: visitor.execute_sets } } diff --git a/lib/workflow/src/lib.rs b/lib/workflow/src/lib.rs index 8d36609..6780842 100644 --- a/lib/workflow/src/lib.rs +++ b/lib/workflow/src/lib.rs @@ -4,7 +4,7 @@ // Created: // 08 Oct 2024, 16:16:26 // Last edited: -// 08 Oct 2024, 19:19:27 +// 15 Oct 2024, 16:40:22 // Auto updated? // Yes // @@ -76,6 +76,7 @@ pub struct Workflow { /// Defines an element in the graph. This is either a _Node_, which defines a task execution, or an _Edge_, which defines how next tasks may be reached. #[derive(Clone, Debug, EnumDebug)] #[cfg_attr(feature = "serde", derive(Deserialize, Serialize))] +#[cfg_attr(feature = "serde", serde(tag = "kind", rename_all = "snake_case"))] pub enum Elem { // Nodes /// Defines a task that is executed, accessing and potentially producing data. diff --git a/run-posix-example.sh b/run-posix-example.sh new file mode 100755 index 0000000..2bac853 --- /dev/null +++ b/run-posix-example.sh @@ -0,0 +1,26 @@ +#!/bin/bash +# RUN POSIX EXAMPLE.sh +# by Tim Müller +# +# Script for running the POSIX example. +# +# This will set the user permissions for the Jedis file correctly. +# + + +# Execute from the script's folder +cd "$(dirname "$0")" + +# Read CLI +debug="" +for arg in $@; do + if [[ "$arg" == "--debug" ]]; then + debug="--debug" + fi +done + +# Set the permissions +chmod 640 ./tests/posix/files/jedis.csv || exit "$?" + +# Run the script +cargo run --example posix --features file-logger,posix-reasoner,serde,workflow -- "$debug" ./tests/workflow/jedis.json --config ./tests/posix/jedis.json || exit "$?" diff --git a/src/lib.rs b/src/lib.rs index 94249e6..7a9c0ab 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -4,7 +4,7 @@ // Created: // 08 Oct 2024, 16:13:30 // Last edited: -// 11 Oct 2024, 16:23:03 +// 15 Oct 2024, 17:00:42 // Auto updated? // Yes // @@ -19,6 +19,8 @@ pub mod reasoners { pub use eflint_json_reasoner as eflint_json; #[cfg(feature = "no-op-reasoner")] pub use no_op_reasoner as no_op; + #[cfg(feature = "posix-reasoner")] + pub use posix_reasoner as posix; } /// Contains the backend loggers. pub mod loggers { @@ -35,3 +37,5 @@ pub mod resolvers { #[cfg(feature = "eflint-to-json")] pub use eflint_to_json; pub use spec; +#[cfg(feature = "workflow")] +pub use workflow; From d2a810f3053a32b083dd6a4331fa2d6b550b690e Mon Sep 17 00:00:00 2001 From: Lut99 Date: Tue, 15 Oct 2024 17:43:46 +0200 Subject: [PATCH 03/82] Removed (for now) unrelated tests Also added a test example for the POSIX reasoner --- tests/branescript/attributes.bs | 57 - tests/branescript/call.bs | 11 - tests/branescript/call_chain.bs | 15 - tests/branescript/copy_result.bs | 5 - tests/branescript/dan-not.bs | 22 - tests/branescript/dan.bs | 22 - tests/branescript/data_complex.bs | 59 - tests/branescript/epi.bs | 15 - tests/branescript/epi_one.bs | 14 - tests/branescript/epi_parallel.bs | 18 - tests/branescript/function.bs | 18 - tests/branescript/hello_world.bs | 4 - tests/branescript/if_complex.bs | 36 - tests/branescript/scopes.bs | 19 - tests/branescript/task_branch.bs | 14 - tests/branescript/task_chain.bs | 16 - tests/branescript/task_loop.bs | 14 - tests/branescript/task_parallel.bs | 19 - tests/branescript/with-tags.bs | 27 - tests/branescript/without-tags.bs | 25 - tests/data/st_antonius_ect/test.txt | 1 - tests/data/st_antonius_ect/test.yml | 13 - tests/data/test/test.csv | 5 - tests/data/test/test.yml | 14 - tests/data/umc_utrecht_ect/test.txt | 1 - tests/data/umc_utrecht_ect/test.yml | 14 - tests/deliberation/execute-workflow.json | 142 --- tests/management/add-tautology.json | 20 - tests/packages/copy_result/container.yml | 38 - tests/packages/copy_result/copy.sh | 31 - tests/packages/data_init/container.yml | 43 - tests/packages/data_init/entrypoint.sh | 39 - tests/packages/data_init/zeroes.py | 91 -- tests/packages/epi/container.yml | 53 - tests/packages/epi/epi.sh | 58 - tests/packages/hello-world/container.yml | 38 - tests/packages/hello-world/hello_world.sh | 19 - tests/packages/test_data.yml | 40 - tests/posix/files/jedis.csv | 5 + tests/posix/jedis.json | 18 + tests/wir/attributes.json | 562 --------- tests/wir/call.json | 204 ---- tests/wir/call_chain.json | 280 ----- tests/wir/copy_result.json | 221 ---- tests/wir/dan-not.json | 334 ------ tests/wir/dan.json | 334 ------ tests/wir/data_complex.json | 1274 --------------------- tests/wir/epi.json | 316 ----- tests/wir/epi_one.json | 275 ----- tests/wir/epi_parallel.json | 366 ------ tests/wir/function.json | 377 ------ tests/wir/hello_world.json | 139 --- tests/wir/if_complex.json | 703 ------------ tests/wir/scopes.json | 219 ---- tests/wir/task_branch.json | 402 ------- tests/wir/task_chain.json | 331 ------ tests/wir/task_loop.json | 381 ------ tests/wir/task_parallel.json | 448 -------- tests/wir/with-tags.json | 340 ------ tests/wir/without-tags.json | 334 ------ tests/workflow/jedis.json | 26 + 61 files changed, 49 insertions(+), 8930 deletions(-) delete mode 100644 tests/branescript/attributes.bs delete mode 100644 tests/branescript/call.bs delete mode 100644 tests/branescript/call_chain.bs delete mode 100644 tests/branescript/copy_result.bs delete mode 100644 tests/branescript/dan-not.bs delete mode 100644 tests/branescript/dan.bs delete mode 100644 tests/branescript/data_complex.bs delete mode 100644 tests/branescript/epi.bs delete mode 100644 tests/branescript/epi_one.bs delete mode 100644 tests/branescript/epi_parallel.bs delete mode 100644 tests/branescript/function.bs delete mode 100644 tests/branescript/hello_world.bs delete mode 100644 tests/branescript/if_complex.bs delete mode 100644 tests/branescript/scopes.bs delete mode 100644 tests/branescript/task_branch.bs delete mode 100644 tests/branescript/task_chain.bs delete mode 100644 tests/branescript/task_loop.bs delete mode 100644 tests/branescript/task_parallel.bs delete mode 100644 tests/branescript/with-tags.bs delete mode 100644 tests/branescript/without-tags.bs delete mode 100644 tests/data/st_antonius_ect/test.txt delete mode 100644 tests/data/st_antonius_ect/test.yml delete mode 100644 tests/data/test/test.csv delete mode 100644 tests/data/test/test.yml delete mode 100644 tests/data/umc_utrecht_ect/test.txt delete mode 100644 tests/data/umc_utrecht_ect/test.yml delete mode 100644 tests/deliberation/execute-workflow.json delete mode 100644 tests/management/add-tautology.json delete mode 100644 tests/packages/copy_result/container.yml delete mode 100644 tests/packages/copy_result/copy.sh delete mode 100644 tests/packages/data_init/container.yml delete mode 100644 tests/packages/data_init/entrypoint.sh delete mode 100644 tests/packages/data_init/zeroes.py delete mode 100644 tests/packages/epi/container.yml delete mode 100644 tests/packages/epi/epi.sh delete mode 100644 tests/packages/hello-world/container.yml delete mode 100644 tests/packages/hello-world/hello_world.sh delete mode 100644 tests/packages/test_data.yml create mode 100644 tests/posix/files/jedis.csv create mode 100644 tests/posix/jedis.json delete mode 100644 tests/wir/attributes.json delete mode 100644 tests/wir/call.json delete mode 100644 tests/wir/call_chain.json delete mode 100644 tests/wir/copy_result.json delete mode 100644 tests/wir/dan-not.json delete mode 100644 tests/wir/dan.json delete mode 100644 tests/wir/data_complex.json delete mode 100644 tests/wir/epi.json delete mode 100644 tests/wir/epi_one.json delete mode 100644 tests/wir/epi_parallel.json delete mode 100644 tests/wir/function.json delete mode 100644 tests/wir/hello_world.json delete mode 100644 tests/wir/if_complex.json delete mode 100644 tests/wir/scopes.json delete mode 100644 tests/wir/task_branch.json delete mode 100644 tests/wir/task_chain.json delete mode 100644 tests/wir/task_loop.json delete mode 100644 tests/wir/task_parallel.json delete mode 100644 tests/wir/with-tags.json delete mode 100644 tests/wir/without-tags.json create mode 100644 tests/workflow/jedis.json diff --git a/tests/branescript/attributes.bs b/tests/branescript/attributes.bs deleted file mode 100644 index 4b02b58..0000000 --- a/tests/branescript/attributes.bs +++ /dev/null @@ -1,57 +0,0 @@ -// File that showcases some attribute usage - -import hello_world; // hello_world() - -// Attributes can be used to provide additional context to the compiler -// Typically, one is attached to a statement by prefixing it; for example, this tags this call to `hello_world()` with `amy.foo` -#[tag("amy.foo")] -println(hello_world()); - -// One can also attach an attribute to an entire scope, applying it to all statements in it; -// This... -{ - #[tag("bob.bar")] - println(hello_world()); - #[tag("bob.bar")] - println(hello_world()); -} -// ...is equivalent to... -#[tag("bob.bar")] -{ - println(hello_world()); - println(hello_world()); -} - -// One can also use the `#![...]` syntax to attach an attribute to a whole scope from _within_ that scope -// This equals the previous example: -{ - #![tag("bob.bar")] - println(hello_world()); - println(hello_world()); -} - -// We can use this latter fact to annotate an entire workflow... -#![wf_tag("cho.baz")] -// ...or all statements in the entire workflow (note this also applies to previous statements!) -#![tag("dan.qux")] -println(hello_world()); -println(hello_world()); - - - -// Finally, there are some subteties to if-, for-, while- and parallel-statements -// When applied to the whole statement, it applies to *all* its blocks and *all* its expressions; i.e., in -#[tag("eve.quux")] -if (hello_world() == "Hello, world!") { - println(hello_world()); -} else { - println(hello_world()); -} -// all `hello_world()`-calls are executed with a `qaz`-tag. However, in this case; -if (hello_world() == "Hello, world!") { - #![tag("eve.quux")] - println(hello_world()); -} else { - println(hello_world()); -} -// only the call in the true-branch is. diff --git a/tests/branescript/call.bs b/tests/branescript/call.bs deleted file mode 100644 index 1af3a0d..0000000 --- a/tests/branescript/call.bs +++ /dev/null @@ -1,11 +0,0 @@ -// Test function calls - -println("Hello there!"); -println("General \" Kenobi,\n\tyou are a bold one!"); - -func test() { - println("General Kenobi!!!"); -} - -test(); -test(); diff --git a/tests/branescript/call_chain.bs b/tests/branescript/call_chain.bs deleted file mode 100644 index 05fe256..0000000 --- a/tests/branescript/call_chain.bs +++ /dev/null @@ -1,15 +0,0 @@ - -func foo() { - return "Foo"; -} -func bar() { - return foo() + ",Bar"; -} -func baz() { - return bar() + ",Baz"; -} -func quz() { - return baz() + ",Quz"; -} - -println(quz()); diff --git a/tests/branescript/copy_result.bs b/tests/branescript/copy_result.bs deleted file mode 100644 index cef68ca..0000000 --- a/tests/branescript/copy_result.bs +++ /dev/null @@ -1,5 +0,0 @@ -import copy_result; - -let data := new Data { name := "Test" }; -let res := copy_result(data); -return commit_result("result", res); diff --git a/tests/branescript/dan-not.bs b/tests/branescript/dan-not.bs deleted file mode 100644 index 4bd0b31..0000000 --- a/tests/branescript/dan-not.bs +++ /dev/null @@ -1,22 +0,0 @@ -//// DAN.bs -//// by Tim Müller -//// -//// Example workflow that should be allowed by the checker when the `examples/eflint/no-dan.eflint` policy is used. -//// - -import epi; // local_compute(), aggregate() - - -// We do a local compute on two domains first -let local_results := parallel [all] [{ - return local_compute(new Data { name := "st_antonius_ect" }); -}, { - return local_compute(new Data { name := "umc_utrecht_ect" }); -}]; - -// Then we aggregate on a third party (not Dan, that's the important bit!) -#[on("surf")] -let central := aggregate(local_results[0], local_results[1]); - -// Return it to the user! -return central; diff --git a/tests/branescript/dan.bs b/tests/branescript/dan.bs deleted file mode 100644 index 6acb8a6..0000000 --- a/tests/branescript/dan.bs +++ /dev/null @@ -1,22 +0,0 @@ -//// DAN.bs -//// by Tim Müller -//// -//// Example workflow that should be refused by the checker when the `examples/eflint/no-dan.eflint` policy is used. -//// - -import epi; // local_compute(), aggregate() - - -// We do a local compute on two domains first -let local_results := parallel [all] [{ - return local_compute(new Data { name := "st_antonius_ect" }); -}, { - return local_compute(new Data { name := "umc_utrecht_ect" }); -}]; - -// Then we aggregate on a third party (our dearest Dan) -#[on("Dan")] -let central := aggregate(local_results[0], local_results[1]); - -// Return it to the user! -return central; diff --git a/tests/branescript/data_complex.bs b/tests/branescript/data_complex.bs deleted file mode 100644 index b229ae7..0000000 --- a/tests/branescript/data_complex.bs +++ /dev/null @@ -1,59 +0,0 @@ -// Data deduction, harder -import copy_result; -import data_test; - - - -// Run something straightforward -let data0 := new Data { name := "Test" }; -let res1 := run_script(data0); -let gres1 := aggregate(res1, res1); -// Checkpoint commit -let data1 := commit_result("test_data_1", gres1); - - - -// Run something branching -let res2 := null; -if (42 == 32) { - res2 := run_script(data1); -} else { - res2 := run_script(data1); -} -let gres2 := aggregate(res2, res2); -// Checkpoint commit -let data2 := commit_result("test_data_2", gres2); - - - -// Run some analysis again repeatedly but without feedback -let gres3 := null; -for (let i := 0; i < 10; i := i + 1) { - let res3 := run_script(data2); - gres3 := aggregate(res3, res3); -} -// Checkpoint commit -let data3 := commit_result("test_data_3", gres3); - - - -// Run some analysis again repeatedly but with feedback -let gres4 := copy_result(data3); -for (let i := 0; i < 10; i := i + 1) { - let res4 := run_script(gres4); - gres4 := aggregate(res4, res4); -} -// Checkpoint commit -let data4 := commit_result("test_data_4", gres4); - - - -// Merge all sorts of shit together -let data12 := aggregate(data1, data2); -let data34 := aggregate(data3, data4); -let data1234 := aggregate(data12, data34); - - - -// Final commit -return commit_result("test_data_1234", data1234); diff --git a/tests/branescript/epi.bs b/tests/branescript/epi.bs deleted file mode 100644 index f5c6a52..0000000 --- a/tests/branescript/epi.bs +++ /dev/null @@ -1,15 +0,0 @@ -// Simple file that implements Rosanne's use-case for the EPI project. -// by Tim Müller - -import epi; - -// Do local computation on both the St. Antonius and UMC Utrecht -let res_sta := local_compute(new Data{ name := "st_antonius_ect" }); -let res_umc := local_compute(new Data{ name := "umc_utrecht_ect" }); - -// Do the aggregation step -#[on("surf")] -{ - let res := aggregate(res_sta, res_umc); - return commit_result("surf_res", res); -} diff --git a/tests/branescript/epi_one.bs b/tests/branescript/epi_one.bs deleted file mode 100644 index e57d8f0..0000000 --- a/tests/branescript/epi_one.bs +++ /dev/null @@ -1,14 +0,0 @@ -// Simple file that implements Rosanne's use-case for the EPI project. -// by Tim Müller - -import epi; - -// Do local computation on both the UMC Utrecht only -let res_umc := local_compute(new Data{ name := "umc_utrecht_ect" }); - -// Do the aggregation step with that same dataset twice lol -#[on("surf")] -{ - let res := aggregate(res_umc, res_umc); - return commit_result("surf_res", res); -} diff --git a/tests/branescript/epi_parallel.bs b/tests/branescript/epi_parallel.bs deleted file mode 100644 index e8f9083..0000000 --- a/tests/branescript/epi_parallel.bs +++ /dev/null @@ -1,18 +0,0 @@ -// Simple file that implements Rosanne's use-case for the EPI project. -// by Tim Müller - -import epi; - -// Do local computation on both the St. Antonius and UMC Utrecht -let res := parallel [all] [{ - return local_compute(new Data{ name := "st_antonius_ect" }); -}, { - return local_compute(new Data{ name := "umc_utrecht_ect" }); -}]; - -// Do the aggregation step -#[on("surf")] -{ - let gres := aggregate(res[0], res[1]); - return commit_result("surf_res", gres); -} diff --git a/tests/branescript/function.bs b/tests/branescript/function.bs deleted file mode 100644 index 3936f9b..0000000 --- a/tests/branescript/function.bs +++ /dev/null @@ -1,18 +0,0 @@ -// Test some function definitions -func hello_there() { - println("Hello there!"); -} - -func say(text) { - println(text); -} - -func add(lhs, rhs) { - let result := lhs + rhs; - return result; -} - - -hello_there(); -say("General Kenobi!"); -println(add(21, 21)); diff --git a/tests/branescript/hello_world.bs b/tests/branescript/hello_world.bs deleted file mode 100644 index 272c661..0000000 --- a/tests/branescript/hello_world.bs +++ /dev/null @@ -1,4 +0,0 @@ -// Test scripts that runs the hello_world package. -import hello_world; - -println(hello_world()); diff --git a/tests/branescript/if_complex.bs b/tests/branescript/if_complex.bs deleted file mode 100644 index 7dbce74..0000000 --- a/tests/branescript/if_complex.bs +++ /dev/null @@ -1,36 +0,0 @@ -// Let's nest a lot -let value := 42; -if (value > 0) { - if (value > 42) { - if (value > 84) { - println("Value is more than 84!"); - } else { - if (value < 84) { - println("Value is less than 84!"); - } else { - println("Value is 84!"); - } - } - } else { - if (value < 42) { - println("Value is less than 42!"); - } else { - println("Value is 42!"); - } - } -} else { - if (value < 0) { - println("Value is less than 0!"); - if (value > -42) { - println("Value is more than -42!"); - } else { - if (value < -42) { - println("Value is less than -42!"); - } else { - println("Value is -42!"); - } - } - } else { - println("Value is 0!"); - } -} diff --git a/tests/branescript/scopes.bs b/tests/branescript/scopes.bs deleted file mode 100644 index 229145b..0000000 --- a/tests/branescript/scopes.bs +++ /dev/null @@ -1,19 +0,0 @@ -// Note: uncommenting te second works in full-workflow mode, but fails in REPL-mode. :( - -{ - func test(text) { - print("Test123"); - print(text); - } -} -// { -// func test(text) { -// print("Test321"); -// print(text); -// } -// } - -let i := 1; -let j := i; - -// This should error when uncommented // let k := k; // And, fun-fact, these three comments should be allowed while at it >:( // Failed at doing that ;( diff --git a/tests/branescript/task_branch.bs b/tests/branescript/task_branch.bs deleted file mode 100644 index a5dadb5..0000000 --- a/tests/branescript/task_branch.bs +++ /dev/null @@ -1,14 +0,0 @@ -import copy_result; // copy_result() -import data_init; // zeroes() - -// Initialize some thing -let start := zeroes(16, "vector"); - -// Conditionally use it -let test := 42; -if (test == 42) { - let end := copy_result(start); -} else { - let intermediate := copy_result(start); - let end := copy_result(intermediate); -} diff --git a/tests/branescript/task_chain.bs b/tests/branescript/task_chain.bs deleted file mode 100644 index 63a2d84..0000000 --- a/tests/branescript/task_chain.bs +++ /dev/null @@ -1,16 +0,0 @@ -import hello_world; - -func once() { - return hello_world(); -} -func twice() { - return once() + once(); -} -func fours() { - return twice() + twice(); -} -func eights() { - return fours() + fours(); -} - -println(eights()); diff --git a/tests/branescript/task_loop.bs b/tests/branescript/task_loop.bs deleted file mode 100644 index af87824..0000000 --- a/tests/branescript/task_loop.bs +++ /dev/null @@ -1,14 +0,0 @@ -import copy_result; // copy_result() -import data_init; // zeroes() - -// Initialize some thing -let start := zeroes(16, "vector"); - -// Do it multiple times -let accumulate := start; -for (let i := 0; i < 16; i := i + 1) { - accumulate := copy_result(accumulate); -} - -// Commit the result -commit_result("final_result", accumulate); diff --git a/tests/branescript/task_parallel.bs b/tests/branescript/task_parallel.bs deleted file mode 100644 index cfd2370..0000000 --- a/tests/branescript/task_parallel.bs +++ /dev/null @@ -1,19 +0,0 @@ -import copy_result; // copy_result() -import data_init; // zeroes() - -// Initialize some thing -let start := zeroes(16, "vector"); - -// Use it in parallel -let intermediate := parallel [all] [{ - return copy_result(start); -}, { - return copy_result(start); -}]; - -// Use the result -let end1 := copy_result(intermediate[0]); -let end2 := copy_result(intermediate[1]); -commit_result("final_result1", end1); -commit_result("final_result2", end2); - diff --git a/tests/branescript/with-tags.bs b/tests/branescript/with-tags.bs deleted file mode 100644 index 6e5de0f..0000000 --- a/tests/branescript/with-tags.bs +++ /dev/null @@ -1,27 +0,0 @@ -/// WITH TAGS.bs -/// by Tim Müller -/// -/// Simple policy file that uses tags to satisfy the `only-synthetic.eflint` policy. -/// -/// Modelled on the EPI ECT use-case. -/// - -import epi; // local_compute(), aggregate() - - -// We do a local compute on two domains first -let local_results := parallel [all] [{ - #[tag("amy.Epi")] - return local_compute(new Data { name := "st_antonius_ect" }); -}, { - #[tag("amy.Epi")] - return local_compute(new Data { name := "umc_utrecht_ect" }); -}]; - -// Then we aggregate on a third party (our dearest Dan) -#[on("surf")] -#[tag("amy.Epi")] -let central := aggregate(local_results[0], local_results[1]); - -// Return it to the user! -return central; diff --git a/tests/branescript/without-tags.bs b/tests/branescript/without-tags.bs deleted file mode 100644 index 809a8d6..0000000 --- a/tests/branescript/without-tags.bs +++ /dev/null @@ -1,25 +0,0 @@ -/// WITHOUT TAGS.bs -/// by Tim Müller -/// -/// Simple policy file that does _not_ use tags to satisfy the `only-synthetic.eflint` policy -/// (and therefore should trigger a 'deny'). -/// -/// Modelled on the EPI ECT use-case. -/// - -import epi; // local_compute(), aggregate() - - -// We do a local compute on two domains first -let local_results := parallel [all] [{ - return local_compute(new Data { name := "st_antonius_ect" }); -}, { - return local_compute(new Data { name := "umc_utrecht_ect" }); -}]; - -// Then we aggregate on a third party (our dearest Dan) -#[on("surf")] -let central := aggregate(local_results[0], local_results[1]); - -// Return it to the user! -return central; diff --git a/tests/data/st_antonius_ect/test.txt b/tests/data/st_antonius_ect/test.txt deleted file mode 100644 index d606037..0000000 --- a/tests/data/st_antonius_ect/test.txt +++ /dev/null @@ -1 +0,0 @@ -test2 \ No newline at end of file diff --git a/tests/data/st_antonius_ect/test.yml b/tests/data/st_antonius_ect/test.yml deleted file mode 100644 index 7c0d3c4..0000000 --- a/tests/data/st_antonius_ect/test.yml +++ /dev/null @@ -1,13 +0,0 @@ -# TEST.yml -# by Tim Müller -# -# An example data index file that describes a single dataset. -# - -# This is the identifier of the dataset. It must be unique across all domains. -name: st_antonius_ect -owners: null -description: A test dataset for testing the EPI use-case in Brane. -created: 1970-01-01T00:00:00Z -access: !file - path: ./test.txt diff --git a/tests/data/test/test.csv b/tests/data/test/test.csv deleted file mode 100644 index 1334c71..0000000 --- a/tests/data/test/test.csv +++ /dev/null @@ -1,5 +0,0 @@ -name,is_master,lightsaber_colour -Obi-Wan Kenobi,true,blue -Mace Windu,true,purple -Master Yoda,true,green -Anakin Skywalker,false,blue diff --git a/tests/data/test/test.yml b/tests/data/test/test.yml deleted file mode 100644 index e800436..0000000 --- a/tests/data/test/test.yml +++ /dev/null @@ -1,14 +0,0 @@ -# TEST.yml -# by Tim Müller -# -# An example data index file that describes a single dataset. -# - - -# This is the identifier of the dataset. It must be unique across all domains. -name: Test -owners: null -description: A test dataset for testing the VM. -created: 1970-01-01T00:00:00Z -access: !file - path: ./test.csv diff --git a/tests/data/umc_utrecht_ect/test.txt b/tests/data/umc_utrecht_ect/test.txt deleted file mode 100644 index f079749..0000000 --- a/tests/data/umc_utrecht_ect/test.txt +++ /dev/null @@ -1 +0,0 @@ -test1 \ No newline at end of file diff --git a/tests/data/umc_utrecht_ect/test.yml b/tests/data/umc_utrecht_ect/test.yml deleted file mode 100644 index ed10ef8..0000000 --- a/tests/data/umc_utrecht_ect/test.yml +++ /dev/null @@ -1,14 +0,0 @@ -# TEST.yml -# by Tim Müller -# -# An example data index file that describes a single dataset. -# - - -# This is the identifier of the dataset. It must be unique across all domains. -name: umc_utrecht_ect -owners: null -description: A test dataset for testing the EPI use-case in Brane. -created: 1970-01-01T00:00:00Z -access: !file - path: ./test.txt diff --git a/tests/deliberation/execute-workflow.json b/tests/deliberation/execute-workflow.json deleted file mode 100644 index de59973..0000000 --- a/tests/deliberation/execute-workflow.json +++ /dev/null @@ -1,142 +0,0 @@ -{ - "use_case": "example", - "workflow": { - "id": "workflow-lMgy9F3B", - "table": { - "funcs": [ - { - "n": "print", - "a": [ - { - "kind": "str" - } - ], - "r": { - "kind": "void" - } - }, - { - "n": "println", - "a": [ - { - "kind": "str" - } - ], - "r": { - "kind": "void" - } - }, - { - "n": "len", - "a": [ - { - "kind": "arr", - "t": { - "kind": "any" - } - } - ], - "r": { - "kind": "int" - } - }, - { - "n": "commit_result", - "a": [ - { - "kind": "str" - }, - { - "kind": "res" - } - ], - "r": { - "kind": "data" - } - } - ], - "tasks": [ - { - "kind": "cmp", - "p": "hello_world", - "v": "1.0.0", - "d": { - "n": "hello_world", - "a": [], - "r": { - "kind": "str" - } - }, - "a": [], - "r": [] - } - ], - "classes": [ - { - "n": "Data", - "i": null, - "v": null, - "p": [ - { - "n": "name", - "t": { - "kind": "str" - } - } - ], - "m": [] - }, - { - "n": "IntermediateResult", - "i": null, - "v": null, - "p": [ - { - "n": "path", - "t": { - "kind": "str" - } - } - ], - "m": [] - } - ], - "vars": [], - "results": {} - }, - "metadata": [], - "user": "amy", - "graph": [ - { - "kind": "nod", - "t": 0, - "l": "all", - "s": null, - "i": {}, - "r": null, - "m": [], - "n": 1 - }, - { - "kind": "lin", - "i": [ - { - "kind": "fnc", - "d": 1 - } - ], - "n": 2 - }, - { - "kind": "cll", - "i": [], - "r": [], - "n": 3 - }, - { - "kind": "stp" - } - ], - "funcs": {} - } -} \ No newline at end of file diff --git a/tests/management/add-tautology.json b/tests/management/add-tautology.json deleted file mode 100644 index 07abbde..0000000 --- a/tests/management/add-tautology.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "description": "Policy that is used for testing.", - "version_description": "An example policy that allows EVERYTHING", - "content": [{ - "reasoner": "eflint-json", - "reasoner_version": "0.1.0", - "content": { - "version": "0.1.0", - "kind": "phrases", - "phrases": [ - { - "kind": "predicate", - "is_invariant": true, - "name": "tautology", - "expression": true - } - ] - } - }] -} \ No newline at end of file diff --git a/tests/packages/copy_result/container.yml b/tests/packages/copy_result/container.yml deleted file mode 100644 index 8c2f42e..0000000 --- a/tests/packages/copy_result/container.yml +++ /dev/null @@ -1,38 +0,0 @@ -# CONTAINER.yml -# by Tim Müller -# -# This file implements the `copy_result` package (see the README.md). -# - -# Define the file metadata -name: copy_result -version: 1.1.0 -kind: ecu - -# Provides a brief description -description: Patchwork package to decouple an input intermediate result from an output result. See https://github.com/braneframework/brane-std. - -# Define the files part of this package -files: -- copy.sh - -# Defines the file to call whenever a function is called -entrypoint: - kind: task - exec: copy.sh - -# Define the dependencies -dependencies: -- jq - -# Define the actions -actions: - # The `func cat(result)` function - copy_result: - command: - input: - - name: result - type: IntermediateResult - output: - - name: output - type: IntermediateResult diff --git a/tests/packages/copy_result/copy.sh b/tests/packages/copy_result/copy.sh deleted file mode 100644 index f6be210..0000000 --- a/tests/packages/copy_result/copy.sh +++ /dev/null @@ -1,31 +0,0 @@ -#!/bin/bash -# COPY.sh -# by Lut99 -# -# Created: -# 02 Jan 2023, 16:39:22 -# Last edited: -# 02 Nov 2023, 11:27:07 -# Auto updated? -# Yes -# -# Description: -# Defines the package code for the `copy_result` package. -# -# Its usage is simple. Simply provide the input in the `RESULT` environment -# variable, and the script will copy its contents (or the file itself, if it -# isn't a directory) to `/result`. -# -# See `README.md` for more information. -# - -# Get the input arguments by removing their quotes (since they are strings) -source=$(echo "${RESULT:q}" | jq -r '.') - -# Switch on whether the input is a file or directory -if [[ -d "$source" ]]; then - cp -rf "$source"/* "/result/" -else - # Copy the source directly to a 'contents' file - cp -rf "$source" "/result/contents" -fi \ No newline at end of file diff --git a/tests/packages/data_init/container.yml b/tests/packages/data_init/container.yml deleted file mode 100644 index ac98351..0000000 --- a/tests/packages/data_init/container.yml +++ /dev/null @@ -1,43 +0,0 @@ -# CONTAINER.yml -# by Tim Müller -# -# This file implements the `data_init` package (see the README.md). -# - -# Define the file metadata -name: data_init -version: 1.0.0 -kind: ecu - -# Provides a brief description -description: Provides various functions that generate IntermediateResults. See https://github.com/braneframework/brane-std. - -# Define the files part of this package -files: -- entrypoint.sh -- zeroes.py - -# Defines the file to call whenever a function is called -entrypoint: - kind: task - exec: entrypoint.sh - -# Define the dependencies -dependencies: -- python3 - -# Define the actions -actions: - # The `func zeroes(number)` function - zeroes: - command: - args: - - zeroes - input: - - name: number - type: int - - name: kind - type: string - output: - - name: output - type: IntermediateResult diff --git a/tests/packages/data_init/entrypoint.sh b/tests/packages/data_init/entrypoint.sh deleted file mode 100644 index 65dc44a..0000000 --- a/tests/packages/data_init/entrypoint.sh +++ /dev/null @@ -1,39 +0,0 @@ -#!/bin/bash -# ENTRYPOINT.sh -# by Lut99 -# -# Created: -# 16 Jan 2023, 13:13:39 -# Last edited: -# 07 Dec 2023, 11:35:46 -# Auto updated? -# Yes -# -# Description: -# Simple script that handles parsing the input and call the correct Python -# file. -# - - -# Read the command -if [[ "$#" -ne 1 ]]; then - 2>&1 echo "Usage: $0 " - exit 1 -fi -func="$1" - -# Read the kind -kind=$(echo "$KIND" | python3 -c "import json, sys; print(json.load(sys.stdin))") - -# Match on the function -if [[ "$func" == "zeroes" ]]; then - # Read the number from the environment variables - num=$NUMBER - - # Call the python script - python3 ./zeroes.py "$num" "$kind" - -else - 2>&1 echo "Unknown function '$func'" - exit 1 -fi diff --git a/tests/packages/data_init/zeroes.py b/tests/packages/data_init/zeroes.py deleted file mode 100644 index 422cfcc..0000000 --- a/tests/packages/data_init/zeroes.py +++ /dev/null @@ -1,91 +0,0 @@ -#!/usr/bin/env python3 -# ZEROES.py -# by Lut99 -# -# Created: -# 16 Jan 2023, 13:16:26 -# Last edited: -# 07 Dec 2023, 11:36:03 -# Auto updated? -# Yes -# -# Description: -# Implements a generator for an intermediate result of only zeroes (ASCII -# `0`). -# - -import argparse -import sys - - -##### GENERATION FUNCTIONS ##### -def generate_vector(n: int) -> int: - """ - Generates a zeroes file with the vector layout. - - Specifically, generates a file /result/data with `n` zeroes, delimited - by spaces. Every zero is simply the ASCII character for `0`. - - # Arguments - - `n`: The number of ASCII `0` to generate. - - # Returns - The return code of the operation. `0` means success. - """ - - # Attempt to open the output file - try: - with open("/result/data", "w") as h: - # Write the number of zeroes - for i in range(n): - if i > 0: h.write(" ") - h.write("0") - - except IOError as e: - print(f"Failed to write to output file '/result/data': {e}", file=sys.stderr) - return e.errno - - # Done - return 0 - - - - - -##### ENTRYPOINT ##### -def main(n: int, kind: str) -> int: - """ - Entrypoint to the script. - - # Arguments - - `n`: The number of zeroes to generate. - - `kind`: The kind of dataset to generate. - - # Returns - The exit code for the script. `0` means success. - """ - - # Match on the kind - if kind == "vector": - return generate_vector(n) - - # Should never happen - raise RuntimeError(f"main() saw a non-allowed kind '{kind}' (should have been taken care of by the argument parser)") - - - -# The actual entrypoint -if __name__ == "__main__": - # Parse the arguments - parser = argparse.ArgumentParser() - parser.add_argument("NUMBER", type=int, help="The number of zeroes to generate in the file.") - parser.add_argument("KIND", choices=["vector"], help="The kind of dataset to generate.") - args = parser.parse_args() - - # Verify numbers is in-of-bounds - if args.NUMBER < 0: - print(f"NUMBER has to be a non-negative integer, not {args.NUMBER}", file=sys.stderr) - exit(1) - - # Call main - exit(main(args.NUMBER, args.KIND)) diff --git a/tests/packages/epi/container.yml b/tests/packages/epi/container.yml deleted file mode 100644 index 566ecb8..0000000 --- a/tests/packages/epi/container.yml +++ /dev/null @@ -1,53 +0,0 @@ -# CONTAINER.yml -# by Tim Müller -# -# This file defines the packages necessary for the EPI project. The results themselves are quite dummy. -# - - -# Define the file metadata -name: epi -version: 1.0.0 -kind: ecu - -# Dependencies to install in the container (as Ubuntu packages) -dependencies: -- python3 - -# Specify the files that are part of the package. All entries will be resolved to relative to the container.yml file (by default) -files: -- epi.sh - -# Define the entrypoint: i.e., which file to call when the package function(s) are run -entrypoint: - kind: task - exec: epi.sh - -# Define the functions in this package -actions: - 'local_compute': - command: - args: - - local_compute - # It takes the local dataset - input: - - type: Data - name: input - # It outputs the local result - output: - - type: IntermediateResult - name: output - 'aggregate': - command: - args: - - aggregate - # It takes two datasets - input: - - type: IntermediateResult - name: lhs - - type: IntermediateResult - name: rhs - # It outputs the global result - output: - - type: IntermediateResult - name: output diff --git a/tests/packages/epi/epi.sh b/tests/packages/epi/epi.sh deleted file mode 100644 index fc2e7a9..0000000 --- a/tests/packages/epi/epi.sh +++ /dev/null @@ -1,58 +0,0 @@ -#!/bin/bash - -# Implements the simplest script ever with two functions; it simply checks if the dataset exists and then outputs '0'. - -# Read the function -if [[ "$#" -ne 1 ]]; then - echo "Usage: $0 " - echo "" - echo "Possible functions are:" - echo " - local_compute" - echo " - aggregate" - exit 1 -else - func="$1" -fi - - - -# Match the function -if [[ "$func" == "local_compute" ]]; then - # Read the input dataset from the env - input=$(echo "$INPUT" | python3 -c "import json, sys; print(json.load(sys.stdin))") - if [[ ! -f "$input" ]]; then - echo "Input dataset '$input' does not exist (or is not a file)" - exit 1 - fi - - # Write '0' to the output thing - echo "0" >> "/result/output_local.txt" - - # No need to return; Brane will know what's up - -elif [[ "$func" == "aggregate" ]]; then - # Read the input datasets from the env - lhs=$(echo "$LHS" | python3 -c "import json, sys; print(json.load(sys.stdin))")/output_local.txt - rhs=$(echo "$RHS" | python3 -c "import json, sys; print(json.load(sys.stdin))")/output_local.txt - if [[ ! -f "$lhs" ]]; then - echo "Input dataset '$lhs' does not exist (or is not a file)" - exit 1 - fi - if [[ ! -f "$rhs" ]]; then - echo "Input dataset '$rhs' does not exist (or is not a file)" - exit 1 - fi - - # Aggregate all input things - echo "$(cat "$lhs")$(cat "$rhs")" >> "/result/output.txt" - - # No need to return; Brane will know what's up - -else - echo "Unknown function '$func'" - echo "" - echo "Possible functions are:" - echo " - local_compute" - echo " - aggregate" - exit 1 -fi diff --git a/tests/packages/hello-world/container.yml b/tests/packages/hello-world/container.yml deleted file mode 100644 index b66b61e..0000000 --- a/tests/packages/hello-world/container.yml +++ /dev/null @@ -1,38 +0,0 @@ -# CONTAINER.yml -# by Tim Müller -# -# This file implements the first tutorial in the Brane: The User Guide book. -# Check: https://wiki.enablingpersonalizedinterventions.nl/user-guide/software- -# engineers/hello-world.html -# -# Specifically, it defines a simple package that always returns 'Hello, world!'. -# - - -# Define the file metadata -# Note the 'kind', which defines that it is an Executable Code Unit (i.e., runs arbitrary code) -name: hello_world -version: 1.0.0 -kind: ecu - -# Specify the files that are part of the package. All entries will be resolved to relative to the container.yml file (by default) -files: -- hello_world.sh - -# Define the entrypoint: i.e., which file to call when the package function(s) are run -entrypoint: - kind: task - exec: hello_world.sh - -# Define the functions in this package -actions: - # We only have one: the 'hello_world()' function - 'hello_world': - # No special arguments needed to pass to the script - command: - # No input needed - input: - # We define the output: a string string, which will be read from the return YAML under the 'output' key. - output: - - type: string - name: output diff --git a/tests/packages/hello-world/hello_world.sh b/tests/packages/hello-world/hello_world.sh deleted file mode 100644 index 0e65be1..0000000 --- a/tests/packages/hello-world/hello_world.sh +++ /dev/null @@ -1,19 +0,0 @@ -#!/bin/bash - -# HELLO WORLD.sh -# by Lut99 -# -# Created: -# 21 Feb 2022, 13:46:57 -# Last edited: -# 12 May 2022, 13:09:03 -# Auto updated? -# Yes -# -# Description: -# Simple script that returns 'Hello, world!' in such a way that the Brane -# framework can extract it as a string. -# - -# Simply echo the hello world with the specified format -echo 'output: Hello, world!' diff --git a/tests/packages/test_data.yml b/tests/packages/test_data.yml deleted file mode 100644 index be8f3c6..0000000 --- a/tests/packages/test_data.yml +++ /dev/null @@ -1,40 +0,0 @@ -# Defines a package that is used to test the data deduction - -# Define the file metadata -name: data_test -version: 1.0.0 -kind: ecu - -# Defines the file to call whenever a function is called -entrypoint: - kind: task - exec: echo - -# Define the dependencies (as Ubuntu packages) -dependencies: -- fortune - -# Define the actions -actions: - run_script: - command: - args: - - "result: \"/home/test/result.csv\"" - input: - - name: dataset - type: IntermediateResult - output: - - name: result - type: IntermediateResult - aggregate: - command: - args: - - "global: \"/home/test/global.csv\"" - input: - - name: local1 - type: IntermediateResult - - name: local2 - type: IntermediateResult - output: - - name: global - type: IntermediateResult diff --git a/tests/posix/files/jedis.csv b/tests/posix/files/jedis.csv new file mode 100644 index 0000000..a7e5655 --- /dev/null +++ b/tests/posix/files/jedis.csv @@ -0,0 +1,5 @@ +name,is_master,lightsaber_color +Anakin Skywalker,0,blue +Obi-Wan Kenobi,1,green +Master Yoda,1,green +Mace Windu,1,purple diff --git a/tests/posix/jedis.json b/tests/posix/jedis.json new file mode 100644 index 0000000..789feba --- /dev/null +++ b/tests/posix/jedis.json @@ -0,0 +1,18 @@ +{ + "id": "coruscant", + "data": { + "jedis": { + "path": "../../../tests/posix/files/jedis.csv", + "user_map": { + "tatooine": { + "uid": 1000, + "gids": [1000] + }, + "moraband": { + "uid": 1001, + "gids": [1001] + } + } + } + } +} diff --git a/tests/wir/attributes.json b/tests/wir/attributes.json deleted file mode 100644 index 49546bf..0000000 --- a/tests/wir/attributes.json +++ /dev/null @@ -1,562 +0,0 @@ -{ - "id": "workflow-TZxEEFXq", - "table": { - "funcs": [ - { - "n": "print", - "a": [ - { - "kind": "str" - } - ], - "r": { - "kind": "void" - } - }, - { - "n": "println", - "a": [ - { - "kind": "str" - } - ], - "r": { - "kind": "void" - } - }, - { - "n": "len", - "a": [ - { - "kind": "arr", - "t": { - "kind": "any" - } - } - ], - "r": { - "kind": "int" - } - }, - { - "n": "commit_result", - "a": [ - { - "kind": "str" - }, - { - "kind": "res" - } - ], - "r": { - "kind": "data" - } - } - ], - "tasks": [ - { - "kind": "cmp", - "p": "hello_world", - "v": "1.0.0", - "d": { - "n": "hello_world", - "a": [], - "r": { - "kind": "str" - } - }, - "a": [], - "r": [] - } - ], - "classes": [ - { - "n": "Data", - "i": null, - "v": null, - "p": [ - { - "n": "name", - "t": { - "kind": "str" - } - } - ], - "m": [] - }, - { - "n": "IntermediateResult", - "i": null, - "v": null, - "p": [ - { - "n": "path", - "t": { - "kind": "str" - } - } - ], - "m": [] - } - ], - "vars": [], - "results": {} - }, - "metadata": [ - "cho.baz" - ], - "user": "amy", - "graph": [ - { - "kind": "nod", - "t": 0, - "l": "all", - "s": null, - "i": {}, - "r": null, - "m": [ - "amy.foo", - "dan.qux" - ], - "n": 1 - }, - { - "kind": "lin", - "i": [ - { - "kind": "fnc", - "d": 1 - } - ], - "n": 2 - }, - { - "kind": "cll", - "i": [], - "r": [], - "n": 3 - }, - { - "kind": "nod", - "t": 0, - "l": "all", - "s": null, - "i": {}, - "r": null, - "m": [ - "dan.qux", - "bob.bar" - ], - "n": 4 - }, - { - "kind": "lin", - "i": [ - { - "kind": "fnc", - "d": 1 - } - ], - "n": 5 - }, - { - "kind": "cll", - "i": [], - "r": [], - "n": 6 - }, - { - "kind": "nod", - "t": 0, - "l": "all", - "s": null, - "i": {}, - "r": null, - "m": [ - "bob.bar", - "dan.qux" - ], - "n": 7 - }, - { - "kind": "lin", - "i": [ - { - "kind": "fnc", - "d": 1 - } - ], - "n": 8 - }, - { - "kind": "cll", - "i": [], - "r": [], - "n": 9 - }, - { - "kind": "nod", - "t": 0, - "l": "all", - "s": null, - "i": {}, - "r": null, - "m": [ - "bob.bar", - "dan.qux" - ], - "n": 10 - }, - { - "kind": "lin", - "i": [ - { - "kind": "fnc", - "d": 1 - } - ], - "n": 11 - }, - { - "kind": "cll", - "i": [], - "r": [], - "n": 12 - }, - { - "kind": "nod", - "t": 0, - "l": "all", - "s": null, - "i": {}, - "r": null, - "m": [ - "bob.bar", - "dan.qux" - ], - "n": 13 - }, - { - "kind": "lin", - "i": [ - { - "kind": "fnc", - "d": 1 - } - ], - "n": 14 - }, - { - "kind": "cll", - "i": [], - "r": [], - "n": 15 - }, - { - "kind": "nod", - "t": 0, - "l": "all", - "s": null, - "i": {}, - "r": null, - "m": [ - "dan.qux", - "bob.bar" - ], - "n": 16 - }, - { - "kind": "lin", - "i": [ - { - "kind": "fnc", - "d": 1 - } - ], - "n": 17 - }, - { - "kind": "cll", - "i": [], - "r": [], - "n": 18 - }, - { - "kind": "nod", - "t": 0, - "l": "all", - "s": null, - "i": {}, - "r": null, - "m": [ - "dan.qux", - "bob.bar" - ], - "n": 19 - }, - { - "kind": "lin", - "i": [ - { - "kind": "fnc", - "d": 1 - } - ], - "n": 20 - }, - { - "kind": "cll", - "i": [], - "r": [], - "n": 21 - }, - { - "kind": "nod", - "t": 0, - "l": "all", - "s": null, - "i": {}, - "r": null, - "m": [ - "dan.qux" - ], - "n": 22 - }, - { - "kind": "lin", - "i": [ - { - "kind": "fnc", - "d": 1 - } - ], - "n": 23 - }, - { - "kind": "cll", - "i": [], - "r": [], - "n": 24 - }, - { - "kind": "nod", - "t": 0, - "l": "all", - "s": null, - "i": {}, - "r": null, - "m": [ - "dan.qux" - ], - "n": 25 - }, - { - "kind": "lin", - "i": [ - { - "kind": "fnc", - "d": 1 - } - ], - "n": 26 - }, - { - "kind": "cll", - "i": [], - "r": [], - "n": 27 - }, - { - "kind": "nod", - "t": 0, - "l": "all", - "s": null, - "i": {}, - "r": null, - "m": [ - "eve.quux", - "dan.qux" - ], - "n": 28 - }, - { - "kind": "lin", - "i": [ - { - "kind": "str", - "v": "Hello, world!" - }, - { - "kind": "eq" - } - ], - "n": 29 - }, - { - "kind": "brc", - "t": 30, - "f": 33, - "m": 36 - }, - { - "kind": "nod", - "t": 0, - "l": "all", - "s": null, - "i": {}, - "r": null, - "m": [ - "eve.quux", - "dan.qux" - ], - "n": 31 - }, - { - "kind": "lin", - "i": [ - { - "kind": "fnc", - "d": 1 - } - ], - "n": 32 - }, - { - "kind": "cll", - "i": [], - "r": [], - "n": 36 - }, - { - "kind": "nod", - "t": 0, - "l": "all", - "s": null, - "i": {}, - "r": null, - "m": [ - "eve.quux", - "dan.qux" - ], - "n": 34 - }, - { - "kind": "lin", - "i": [ - { - "kind": "fnc", - "d": 1 - } - ], - "n": 35 - }, - { - "kind": "cll", - "i": [], - "r": [], - "n": 36 - }, - { - "kind": "lin", - "i": [], - "n": 37 - }, - { - "kind": "nod", - "t": 0, - "l": "all", - "s": null, - "i": {}, - "r": null, - "m": [ - "dan.qux" - ], - "n": 38 - }, - { - "kind": "lin", - "i": [ - { - "kind": "str", - "v": "Hello, world!" - }, - { - "kind": "eq" - } - ], - "n": 39 - }, - { - "kind": "brc", - "t": 40, - "f": 43, - "m": 46 - }, - { - "kind": "nod", - "t": 0, - "l": "all", - "s": null, - "i": {}, - "r": null, - "m": [ - "dan.qux", - "eve.quux" - ], - "n": 41 - }, - { - "kind": "lin", - "i": [ - { - "kind": "fnc", - "d": 1 - } - ], - "n": 42 - }, - { - "kind": "cll", - "i": [], - "r": [], - "n": 46 - }, - { - "kind": "nod", - "t": 0, - "l": "all", - "s": null, - "i": {}, - "r": null, - "m": [ - "dan.qux" - ], - "n": 44 - }, - { - "kind": "lin", - "i": [ - { - "kind": "fnc", - "d": 1 - } - ], - "n": 45 - }, - { - "kind": "cll", - "i": [], - "r": [], - "n": 46 - }, - { - "kind": "lin", - "i": [], - "n": 47 - }, - { - "kind": "stp" - } - ], - "funcs": {} -} diff --git a/tests/wir/call.json b/tests/wir/call.json deleted file mode 100644 index b919dc5..0000000 --- a/tests/wir/call.json +++ /dev/null @@ -1,204 +0,0 @@ -{ - "id": "workflow-2kssWnvE", - "table": { - "funcs": [ - { - "n": "print", - "a": [ - { - "kind": "str" - } - ], - "r": { - "kind": "void" - } - }, - { - "n": "println", - "a": [ - { - "kind": "str" - } - ], - "r": { - "kind": "void" - } - }, - { - "n": "len", - "a": [ - { - "kind": "arr", - "t": { - "kind": "any" - } - } - ], - "r": { - "kind": "int" - } - }, - { - "n": "commit_result", - "a": [ - { - "kind": "str" - }, - { - "kind": "res" - } - ], - "r": { - "kind": "data" - } - }, - { - "n": "test", - "a": [], - "r": { - "kind": "void" - } - } - ], - "tasks": [], - "classes": [ - { - "n": "Data", - "i": null, - "v": null, - "p": [ - { - "n": "name", - "t": { - "kind": "str" - } - } - ], - "m": [] - }, - { - "n": "IntermediateResult", - "i": null, - "v": null, - "p": [ - { - "n": "path", - "t": { - "kind": "str" - } - } - ], - "m": [] - } - ], - "vars": [], - "results": {} - }, - "metadata": [], - "user": "amy", - "graph": [ - { - "kind": "lin", - "i": [ - { - "kind": "str", - "v": "Hello there!" - }, - { - "kind": "fnc", - "d": 1 - } - ], - "n": 1 - }, - { - "kind": "cll", - "i": [], - "r": [], - "n": 2 - }, - { - "kind": "lin", - "i": [ - { - "kind": "str", - "v": "General \" Kenobi,\n\tyou are a bold one!" - }, - { - "kind": "fnc", - "d": 1 - } - ], - "n": 3 - }, - { - "kind": "cll", - "i": [], - "r": [], - "n": 4 - }, - { - "kind": "lin", - "i": [ - { - "kind": "fnc", - "d": 4 - } - ], - "n": 5 - }, - { - "kind": "cll", - "i": [], - "r": [], - "n": 6 - }, - { - "kind": "lin", - "i": [ - { - "kind": "fnc", - "d": 4 - } - ], - "n": 7 - }, - { - "kind": "cll", - "i": [], - "r": [], - "n": 8 - }, - { - "kind": "stp" - } - ], - "funcs": { - "4": [ - { - "kind": "lin", - "i": [ - { - "kind": "str", - "v": "General Kenobi!!!" - }, - { - "kind": "fnc", - "d": 1 - } - ], - "n": 1 - }, - { - "kind": "cll", - "i": [], - "r": [], - "n": 2 - }, - { - "kind": "ret", - "r": [] - } - ] - } -} diff --git a/tests/wir/call_chain.json b/tests/wir/call_chain.json deleted file mode 100644 index ece46f9..0000000 --- a/tests/wir/call_chain.json +++ /dev/null @@ -1,280 +0,0 @@ -{ - "id": "workflow-kMRvxo5K", - "table": { - "funcs": [ - { - "n": "print", - "a": [ - { - "kind": "str" - } - ], - "r": { - "kind": "void" - } - }, - { - "n": "println", - "a": [ - { - "kind": "str" - } - ], - "r": { - "kind": "void" - } - }, - { - "n": "len", - "a": [ - { - "kind": "arr", - "t": { - "kind": "any" - } - } - ], - "r": { - "kind": "int" - } - }, - { - "n": "commit_result", - "a": [ - { - "kind": "str" - }, - { - "kind": "res" - } - ], - "r": { - "kind": "data" - } - }, - { - "n": "foo", - "a": [], - "r": { - "kind": "str" - } - }, - { - "n": "bar", - "a": [], - "r": { - "kind": "str" - } - }, - { - "n": "baz", - "a": [], - "r": { - "kind": "str" - } - }, - { - "n": "quz", - "a": [], - "r": { - "kind": "str" - } - } - ], - "tasks": [], - "classes": [ - { - "n": "Data", - "i": null, - "v": null, - "p": [ - { - "n": "name", - "t": { - "kind": "str" - } - } - ], - "m": [] - }, - { - "n": "IntermediateResult", - "i": null, - "v": null, - "p": [ - { - "n": "path", - "t": { - "kind": "str" - } - } - ], - "m": [] - } - ], - "vars": [], - "results": {} - }, - "metadata": [], - "user": "amy", - "graph": [ - { - "kind": "lin", - "i": [ - { - "kind": "fnc", - "d": 7 - } - ], - "n": 1 - }, - { - "kind": "cll", - "i": [], - "r": [], - "n": 2 - }, - { - "kind": "lin", - "i": [ - { - "kind": "fnc", - "d": 1 - } - ], - "n": 3 - }, - { - "kind": "cll", - "i": [], - "r": [], - "n": 4 - }, - { - "kind": "stp" - } - ], - "funcs": { - "7": [ - { - "kind": "lin", - "i": [ - { - "kind": "fnc", - "d": 6 - } - ], - "n": 1 - }, - { - "kind": "cll", - "i": [], - "r": [], - "n": 2 - }, - { - "kind": "lin", - "i": [ - { - "kind": "str", - "v": ",Quz" - }, - { - "kind": "add" - } - ], - "n": 3 - }, - { - "kind": "ret", - "r": [] - } - ], - "6": [ - { - "kind": "lin", - "i": [ - { - "kind": "fnc", - "d": 5 - } - ], - "n": 1 - }, - { - "kind": "cll", - "i": [], - "r": [], - "n": 2 - }, - { - "kind": "lin", - "i": [ - { - "kind": "str", - "v": ",Baz" - }, - { - "kind": "add" - } - ], - "n": 3 - }, - { - "kind": "ret", - "r": [] - } - ], - "5": [ - { - "kind": "lin", - "i": [ - { - "kind": "fnc", - "d": 4 - } - ], - "n": 1 - }, - { - "kind": "cll", - "i": [], - "r": [], - "n": 2 - }, - { - "kind": "lin", - "i": [ - { - "kind": "str", - "v": ",Bar" - }, - { - "kind": "add" - } - ], - "n": 3 - }, - { - "kind": "ret", - "r": [] - } - ], - "4": [ - { - "kind": "lin", - "i": [ - { - "kind": "str", - "v": "Foo" - } - ], - "n": 1 - }, - { - "kind": "ret", - "r": [] - } - ] - } -} diff --git a/tests/wir/copy_result.json b/tests/wir/copy_result.json deleted file mode 100644 index f2535b1..0000000 --- a/tests/wir/copy_result.json +++ /dev/null @@ -1,221 +0,0 @@ -{ - "id": "workflow-h8i9HRCI", - "table": { - "funcs": [ - { - "n": "print", - "a": [ - { - "kind": "str" - } - ], - "r": { - "kind": "void" - } - }, - { - "n": "println", - "a": [ - { - "kind": "str" - } - ], - "r": { - "kind": "void" - } - }, - { - "n": "len", - "a": [ - { - "kind": "arr", - "t": { - "kind": "any" - } - } - ], - "r": { - "kind": "int" - } - }, - { - "n": "commit_result", - "a": [ - { - "kind": "str" - }, - { - "kind": "res" - } - ], - "r": { - "kind": "data" - } - } - ], - "tasks": [ - { - "kind": "cmp", - "p": "copy_result", - "v": "1.1.0", - "d": { - "n": "copy_result", - "a": [ - { - "kind": "res" - } - ], - "r": { - "kind": "res" - } - }, - "a": [ - "result" - ], - "r": [] - } - ], - "classes": [ - { - "n": "Data", - "i": null, - "v": null, - "p": [ - { - "n": "name", - "t": { - "kind": "str" - } - } - ], - "m": [] - }, - { - "n": "IntermediateResult", - "i": null, - "v": null, - "p": [ - { - "n": "path", - "t": { - "kind": "str" - } - } - ], - "m": [] - } - ], - "vars": [ - { - "n": "data", - "t": { - "kind": "data" - } - }, - { - "n": "res", - "t": { - "kind": "res" - } - } - ], - "results": {} - }, - "metadata": [], - "user": "amy", - "graph": [ - { - "kind": "lin", - "i": [ - { - "kind": "vrd", - "d": 0 - }, - { - "kind": "str", - "v": "Test" - }, - { - "kind": "ins", - "d": 0 - }, - { - "kind": "vrs", - "d": 0 - }, - { - "kind": "vrd", - "d": 1 - }, - { - "kind": "vrg", - "d": 0 - }, - { - "kind": "cst", - "t": { - "kind": "res" - } - } - ], - "n": 1 - }, - { - "kind": "nod", - "t": 0, - "l": "all", - "s": null, - "i": { - "{\"Data\":\"Test\"}": null - }, - "r": "result_copy_result_7bb66c", - "m": [], - "n": 2 - }, - { - "kind": "lin", - "i": [ - { - "kind": "vrs", - "d": 1 - }, - { - "kind": "str", - "v": "result" - }, - { - "kind": "vrg", - "d": 1 - }, - { - "kind": "fnc", - "d": 3 - } - ], - "n": 3 - }, - { - "kind": "cll", - "i": [ - { - "IntermediateResult": "result_copy_result_7bb66c" - } - ], - "r": [ - { - "Data": "result" - } - ], - "n": 4 - }, - { - "kind": "ret", - "r": [ - { - "Data": "result" - } - ] - } - ], - "funcs": {} -} diff --git a/tests/wir/dan-not.json b/tests/wir/dan-not.json deleted file mode 100644 index 63a5e0e..0000000 --- a/tests/wir/dan-not.json +++ /dev/null @@ -1,334 +0,0 @@ -{ - "id": "workflow-nSApHcQj", - "table": { - "funcs": [ - { - "n": "print", - "a": [ - { - "kind": "str" - } - ], - "r": { - "kind": "void" - } - }, - { - "n": "println", - "a": [ - { - "kind": "str" - } - ], - "r": { - "kind": "void" - } - }, - { - "n": "len", - "a": [ - { - "kind": "arr", - "t": { - "kind": "any" - } - } - ], - "r": { - "kind": "int" - } - }, - { - "n": "commit_result", - "a": [ - { - "kind": "str" - }, - { - "kind": "res" - } - ], - "r": { - "kind": "data" - } - } - ], - "tasks": [ - { - "kind": "cmp", - "p": "epi", - "v": "1.0.0", - "d": { - "n": "aggregate", - "a": [ - { - "kind": "res" - }, - { - "kind": "res" - } - ], - "r": { - "kind": "res" - } - }, - "a": [ - "lhs", - "rhs" - ], - "r": [] - }, - { - "kind": "cmp", - "p": "epi", - "v": "1.0.0", - "d": { - "n": "local_compute", - "a": [ - { - "kind": "data" - } - ], - "r": { - "kind": "res" - } - }, - "a": [ - "input" - ], - "r": [] - } - ], - "classes": [ - { - "n": "Data", - "i": null, - "v": null, - "p": [ - { - "n": "name", - "t": { - "kind": "str" - } - } - ], - "m": [] - }, - { - "n": "IntermediateResult", - "i": null, - "v": null, - "p": [ - { - "n": "path", - "t": { - "kind": "str" - } - } - ], - "m": [] - } - ], - "vars": [ - { - "n": "local_results", - "t": { - "kind": "arr", - "t": { - "kind": "res" - } - } - }, - { - "n": "central", - "t": { - "kind": "res" - } - } - ], - "results": {} - }, - "metadata": [], - "user": "amy", - "graph": [ - { - "kind": "par", - "b": [ - 1, - 5 - ], - "m": 9 - }, - { - "kind": "lin", - "i": [ - { - "kind": "str", - "v": "st_antonius_ect" - } - ], - "n": 2 - }, - { - "kind": "lin", - "i": [ - { - "kind": "ins", - "d": 0 - } - ], - "n": 3 - }, - { - "kind": "nod", - "t": 1, - "l": "all", - "s": null, - "i": { - "{\"Data\":\"st_antonius_ect\"}": null - }, - "r": "result_local_compute_d9b844", - "m": [], - "n": 4 - }, - { - "kind": "ret", - "r": [ - { - "IntermediateResult": "result_local_compute_d9b844" - } - ] - }, - { - "kind": "lin", - "i": [ - { - "kind": "str", - "v": "umc_utrecht_ect" - } - ], - "n": 6 - }, - { - "kind": "lin", - "i": [ - { - "kind": "ins", - "d": 0 - } - ], - "n": 7 - }, - { - "kind": "nod", - "t": 1, - "l": "all", - "s": null, - "i": { - "{\"Data\":\"umc_utrecht_ect\"}": null - }, - "r": "result_local_compute_b6560e", - "m": [], - "n": 8 - }, - { - "kind": "ret", - "r": [ - { - "IntermediateResult": "result_local_compute_b6560e" - } - ] - }, - { - "kind": "join", - "m": "All", - "n": 10 - }, - { - "kind": "lin", - "i": [ - { - "kind": "vrd", - "d": 0 - }, - { - "kind": "vrs", - "d": 0 - }, - { - "kind": "vrd", - "d": 1 - }, - { - "kind": "vrg", - "d": 0 - }, - { - "kind": "int", - "v": 0 - }, - { - "kind": "arx", - "t": { - "kind": "res" - } - }, - { - "kind": "vrg", - "d": 0 - }, - { - "kind": "int", - "v": 1 - }, - { - "kind": "arx", - "t": { - "kind": "res" - } - } - ], - "n": 11 - }, - { - "kind": "nod", - "t": 0, - "l": { - "restricted": [ - "surf" - ] - }, - "s": null, - "i": { - "{\"IntermediateResult\":\"result_local_compute_d9b844\"}": null, - "{\"IntermediateResult\":\"result_local_compute_b6560e\"}": null - }, - "r": "result_aggregate_6395a1", - "m": [], - "n": 12 - }, - { - "kind": "lin", - "i": [ - { - "kind": "vrs", - "d": 1 - }, - { - "kind": "vrg", - "d": 1 - } - ], - "n": 13 - }, - { - "kind": "ret", - "r": [ - { - "IntermediateResult": "result_aggregate_6395a1" - } - ] - } - ], - "funcs": {} -} diff --git a/tests/wir/dan.json b/tests/wir/dan.json deleted file mode 100644 index 0862ff8..0000000 --- a/tests/wir/dan.json +++ /dev/null @@ -1,334 +0,0 @@ -{ - "id": "workflow-PxYnxgGx", - "table": { - "funcs": [ - { - "n": "print", - "a": [ - { - "kind": "str" - } - ], - "r": { - "kind": "void" - } - }, - { - "n": "println", - "a": [ - { - "kind": "str" - } - ], - "r": { - "kind": "void" - } - }, - { - "n": "len", - "a": [ - { - "kind": "arr", - "t": { - "kind": "any" - } - } - ], - "r": { - "kind": "int" - } - }, - { - "n": "commit_result", - "a": [ - { - "kind": "str" - }, - { - "kind": "res" - } - ], - "r": { - "kind": "data" - } - } - ], - "tasks": [ - { - "kind": "cmp", - "p": "epi", - "v": "1.0.0", - "d": { - "n": "aggregate", - "a": [ - { - "kind": "res" - }, - { - "kind": "res" - } - ], - "r": { - "kind": "res" - } - }, - "a": [ - "lhs", - "rhs" - ], - "r": [] - }, - { - "kind": "cmp", - "p": "epi", - "v": "1.0.0", - "d": { - "n": "local_compute", - "a": [ - { - "kind": "data" - } - ], - "r": { - "kind": "res" - } - }, - "a": [ - "input" - ], - "r": [] - } - ], - "classes": [ - { - "n": "Data", - "i": null, - "v": null, - "p": [ - { - "n": "name", - "t": { - "kind": "str" - } - } - ], - "m": [] - }, - { - "n": "IntermediateResult", - "i": null, - "v": null, - "p": [ - { - "n": "path", - "t": { - "kind": "str" - } - } - ], - "m": [] - } - ], - "vars": [ - { - "n": "local_results", - "t": { - "kind": "arr", - "t": { - "kind": "res" - } - } - }, - { - "n": "central", - "t": { - "kind": "res" - } - } - ], - "results": {} - }, - "metadata": [], - "user": "amy", - "graph": [ - { - "kind": "par", - "b": [ - 1, - 5 - ], - "m": 9 - }, - { - "kind": "lin", - "i": [ - { - "kind": "str", - "v": "st_antonius_ect" - } - ], - "n": 2 - }, - { - "kind": "lin", - "i": [ - { - "kind": "ins", - "d": 0 - } - ], - "n": 3 - }, - { - "kind": "nod", - "t": 1, - "l": "all", - "s": null, - "i": { - "{\"Data\":\"st_antonius_ect\"}": null - }, - "r": "result_local_compute_fae675", - "m": [], - "n": 4 - }, - { - "kind": "ret", - "r": [ - { - "IntermediateResult": "result_local_compute_fae675" - } - ] - }, - { - "kind": "lin", - "i": [ - { - "kind": "str", - "v": "umc_utrecht_ect" - } - ], - "n": 6 - }, - { - "kind": "lin", - "i": [ - { - "kind": "ins", - "d": 0 - } - ], - "n": 7 - }, - { - "kind": "nod", - "t": 1, - "l": "all", - "s": null, - "i": { - "{\"Data\":\"umc_utrecht_ect\"}": null - }, - "r": "result_local_compute_562feb", - "m": [], - "n": 8 - }, - { - "kind": "ret", - "r": [ - { - "IntermediateResult": "result_local_compute_562feb" - } - ] - }, - { - "kind": "join", - "m": "All", - "n": 10 - }, - { - "kind": "lin", - "i": [ - { - "kind": "vrd", - "d": 0 - }, - { - "kind": "vrs", - "d": 0 - }, - { - "kind": "vrd", - "d": 1 - }, - { - "kind": "vrg", - "d": 0 - }, - { - "kind": "int", - "v": 0 - }, - { - "kind": "arx", - "t": { - "kind": "res" - } - }, - { - "kind": "vrg", - "d": 0 - }, - { - "kind": "int", - "v": 1 - }, - { - "kind": "arx", - "t": { - "kind": "res" - } - } - ], - "n": 11 - }, - { - "kind": "nod", - "t": 0, - "l": { - "restricted": [ - "Dan" - ] - }, - "s": null, - "i": { - "{\"IntermediateResult\":\"result_local_compute_562feb\"}": null, - "{\"IntermediateResult\":\"result_local_compute_fae675\"}": null - }, - "r": "result_aggregate_03e2b6", - "m": [], - "n": 12 - }, - { - "kind": "lin", - "i": [ - { - "kind": "vrs", - "d": 1 - }, - { - "kind": "vrg", - "d": 1 - } - ], - "n": 13 - }, - { - "kind": "ret", - "r": [ - { - "IntermediateResult": "result_aggregate_03e2b6" - } - ] - } - ], - "funcs": {} -} diff --git a/tests/wir/data_complex.json b/tests/wir/data_complex.json deleted file mode 100644 index 2caceea..0000000 --- a/tests/wir/data_complex.json +++ /dev/null @@ -1,1274 +0,0 @@ -{ - "id": "workflow-9G2CExpQ", - "table": { - "funcs": [ - { - "n": "print", - "a": [ - { - "kind": "str" - } - ], - "r": { - "kind": "void" - } - }, - { - "n": "println", - "a": [ - { - "kind": "str" - } - ], - "r": { - "kind": "void" - } - }, - { - "n": "len", - "a": [ - { - "kind": "arr", - "t": { - "kind": "any" - } - } - ], - "r": { - "kind": "int" - } - }, - { - "n": "commit_result", - "a": [ - { - "kind": "str" - }, - { - "kind": "res" - } - ], - "r": { - "kind": "data" - } - } - ], - "tasks": [ - { - "kind": "cmp", - "p": "copy_result", - "v": "1.1.0", - "d": { - "n": "copy_result", - "a": [ - { - "kind": "res" - } - ], - "r": { - "kind": "res" - } - }, - "a": [ - "result" - ], - "r": [] - }, - { - "kind": "cmp", - "p": "data_test", - "v": "1.0.0", - "d": { - "n": "aggregate", - "a": [ - { - "kind": "res" - }, - { - "kind": "res" - } - ], - "r": { - "kind": "res" - } - }, - "a": [ - "local1", - "local2" - ], - "r": [] - }, - { - "kind": "cmp", - "p": "data_test", - "v": "1.0.0", - "d": { - "n": "run_script", - "a": [ - { - "kind": "res" - } - ], - "r": { - "kind": "res" - } - }, - "a": [ - "dataset" - ], - "r": [] - } - ], - "classes": [ - { - "n": "Data", - "i": null, - "v": null, - "p": [ - { - "n": "name", - "t": { - "kind": "str" - } - } - ], - "m": [] - }, - { - "n": "IntermediateResult", - "i": null, - "v": null, - "p": [ - { - "n": "path", - "t": { - "kind": "str" - } - } - ], - "m": [] - } - ], - "vars": [ - { - "n": "data0", - "t": { - "kind": "data" - } - }, - { - "n": "res1", - "t": { - "kind": "res" - } - }, - { - "n": "gres1", - "t": { - "kind": "res" - } - }, - { - "n": "data1", - "t": { - "kind": "data" - } - }, - { - "n": "res2", - "t": { - "kind": "res" - } - }, - { - "n": "gres2", - "t": { - "kind": "res" - } - }, - { - "n": "data2", - "t": { - "kind": "data" - } - }, - { - "n": "gres3", - "t": { - "kind": "res" - } - }, - { - "n": "i", - "t": { - "kind": "int" - } - }, - { - "n": "res3", - "t": { - "kind": "res" - } - }, - { - "n": "data3", - "t": { - "kind": "data" - } - }, - { - "n": "gres4", - "t": { - "kind": "res" - } - }, - { - "n": "i", - "t": { - "kind": "int" - } - }, - { - "n": "res4", - "t": { - "kind": "res" - } - }, - { - "n": "data4", - "t": { - "kind": "data" - } - }, - { - "n": "data12", - "t": { - "kind": "res" - } - }, - { - "n": "data34", - "t": { - "kind": "res" - } - }, - { - "n": "data1234", - "t": { - "kind": "res" - } - } - ], - "results": {} - }, - "metadata": [], - "user": "amy", - "graph": [ - { - "kind": "lin", - "i": [ - { - "kind": "vrd", - "d": 0 - }, - { - "kind": "str", - "v": "Test" - }, - { - "kind": "ins", - "d": 0 - }, - { - "kind": "vrs", - "d": 0 - }, - { - "kind": "vrd", - "d": 1 - }, - { - "kind": "vrg", - "d": 0 - }, - { - "kind": "cst", - "t": { - "kind": "res" - } - } - ], - "n": 1 - }, - { - "kind": "nod", - "t": 2, - "l": "all", - "s": null, - "i": { - "{\"Data\":\"Test\"}": null - }, - "r": "result_run_script_4a2193", - "m": [], - "n": 2 - }, - { - "kind": "lin", - "i": [ - { - "kind": "vrs", - "d": 1 - }, - { - "kind": "vrd", - "d": 2 - }, - { - "kind": "vrg", - "d": 1 - }, - { - "kind": "vrg", - "d": 1 - } - ], - "n": 3 - }, - { - "kind": "nod", - "t": 1, - "l": "all", - "s": null, - "i": { - "{\"IntermediateResult\":\"result_run_script_4a2193\"}": null - }, - "r": "result_aggregate_e04261", - "m": [], - "n": 4 - }, - { - "kind": "lin", - "i": [ - { - "kind": "vrs", - "d": 2 - }, - { - "kind": "vrd", - "d": 3 - }, - { - "kind": "str", - "v": "test_data_1" - }, - { - "kind": "vrg", - "d": 2 - }, - { - "kind": "fnc", - "d": 3 - } - ], - "n": 5 - }, - { - "kind": "cll", - "i": [ - { - "IntermediateResult": "result_aggregate_e04261" - } - ], - "r": [ - { - "Data": "test_data_1" - } - ], - "n": 6 - }, - { - "kind": "lin", - "i": [ - { - "kind": "vrs", - "d": 3 - }, - { - "kind": "vrd", - "d": 4 - }, - { - "kind": "int", - "v": 42 - }, - { - "kind": "int", - "v": 32 - }, - { - "kind": "eq" - } - ], - "n": 7 - }, - { - "kind": "brc", - "t": 8, - "f": 12, - "m": 16 - }, - { - "kind": "lin", - "i": [ - { - "kind": "vrg", - "d": 3 - } - ], - "n": 9 - }, - { - "kind": "lin", - "i": [ - { - "kind": "cst", - "t": { - "kind": "res" - } - } - ], - "n": 10 - }, - { - "kind": "nod", - "t": 2, - "l": "all", - "s": null, - "i": { - "{\"Data\":\"test_data_1\"}": null - }, - "r": "result_run_script_30b562", - "m": [], - "n": 11 - }, - { - "kind": "lin", - "i": [ - { - "kind": "vrs", - "d": 4 - } - ], - "n": 16 - }, - { - "kind": "lin", - "i": [ - { - "kind": "vrg", - "d": 3 - } - ], - "n": 13 - }, - { - "kind": "lin", - "i": [ - { - "kind": "cst", - "t": { - "kind": "res" - } - } - ], - "n": 14 - }, - { - "kind": "nod", - "t": 2, - "l": "all", - "s": null, - "i": { - "{\"Data\":\"test_data_1\"}": null - }, - "r": "result_run_script_1336f7", - "m": [], - "n": 15 - }, - { - "kind": "lin", - "i": [ - { - "kind": "vrs", - "d": 4 - } - ], - "n": 16 - }, - { - "kind": "lin", - "i": [ - { - "kind": "vrd", - "d": 5 - }, - { - "kind": "vrg", - "d": 4 - }, - { - "kind": "vrg", - "d": 4 - } - ], - "n": 17 - }, - { - "kind": "nod", - "t": 1, - "l": "all", - "s": null, - "i": { - "{\"IntermediateResult\":\"result_run_script_30b562\"}": null, - "{\"IntermediateResult\":\"result_run_script_1336f7\"}": null - }, - "r": "result_aggregate_2bf8c7", - "m": [], - "n": 18 - }, - { - "kind": "lin", - "i": [ - { - "kind": "vrs", - "d": 5 - }, - { - "kind": "vrd", - "d": 6 - }, - { - "kind": "str", - "v": "test_data_2" - }, - { - "kind": "vrg", - "d": 5 - }, - { - "kind": "fnc", - "d": 3 - } - ], - "n": 19 - }, - { - "kind": "cll", - "i": [ - { - "IntermediateResult": "result_aggregate_2bf8c7" - } - ], - "r": [ - { - "Data": "test_data_2" - } - ], - "n": 20 - }, - { - "kind": "lin", - "i": [ - { - "kind": "vrs", - "d": 6 - }, - { - "kind": "vrd", - "d": 7 - }, - { - "kind": "vrd", - "d": 8 - }, - { - "kind": "int", - "v": 0 - }, - { - "kind": "vrs", - "d": 8 - } - ], - "n": 21 - }, - { - "kind": "loop", - "c": 22, - "b": 26, - "n": 40 - }, - { - "kind": "lin", - "i": [ - { - "kind": "vrg", - "d": 8 - } - ], - "n": 23 - }, - { - "kind": "lin", - "i": [ - { - "kind": "int", - "v": 10 - } - ], - "n": 24 - }, - { - "kind": "lin", - "i": [ - { - "kind": "lt" - } - ], - "n": 25 - }, - { - "kind": "brc", - "t": 26, - "f": 40, - "m": 40 - }, - { - "kind": "lin", - "i": [ - { - "kind": "vrd", - "d": 9 - } - ], - "n": 27 - }, - { - "kind": "lin", - "i": [ - { - "kind": "vrg", - "d": 6 - } - ], - "n": 28 - }, - { - "kind": "lin", - "i": [ - { - "kind": "cst", - "t": { - "kind": "res" - } - } - ], - "n": 29 - }, - { - "kind": "nod", - "t": 2, - "l": "all", - "s": null, - "i": { - "{\"Data\":\"test_data_2\"}": null - }, - "r": "result_run_script_447871", - "m": [], - "n": 30 - }, - { - "kind": "lin", - "i": [ - { - "kind": "vrs", - "d": 9 - } - ], - "n": 31 - }, - { - "kind": "lin", - "i": [ - { - "kind": "vrg", - "d": 9 - } - ], - "n": 32 - }, - { - "kind": "lin", - "i": [ - { - "kind": "vrg", - "d": 9 - } - ], - "n": 33 - }, - { - "kind": "nod", - "t": 1, - "l": "all", - "s": null, - "i": { - "{\"IntermediateResult\":\"result_run_script_447871\"}": null - }, - "r": "result_aggregate_7a73e2", - "m": [], - "n": 34 - }, - { - "kind": "lin", - "i": [ - { - "kind": "vrs", - "d": 7 - } - ], - "n": 35 - }, - { - "kind": "lin", - "i": [ - { - "kind": "vrg", - "d": 8 - } - ], - "n": 36 - }, - { - "kind": "lin", - "i": [ - { - "kind": "int", - "v": 1 - } - ], - "n": 37 - }, - { - "kind": "lin", - "i": [ - { - "kind": "add" - } - ], - "n": 38 - }, - { - "kind": "lin", - "i": [ - { - "kind": "vrs", - "d": 8 - } - ], - "n": 39 - }, - { - "kind": "lin", - "i": [ - { - "kind": "vru", - "d": 9 - } - ], - "n": 22 - }, - { - "kind": "lin", - "i": [ - { - "kind": "vrd", - "d": 10 - }, - { - "kind": "str", - "v": "test_data_3" - }, - { - "kind": "vrg", - "d": 7 - }, - { - "kind": "fnc", - "d": 3 - } - ], - "n": 41 - }, - { - "kind": "cll", - "i": [ - { - "IntermediateResult": "result_aggregate_7a73e2" - } - ], - "r": [ - { - "Data": "test_data_3" - } - ], - "n": 42 - }, - { - "kind": "lin", - "i": [ - { - "kind": "vrs", - "d": 10 - }, - { - "kind": "vrd", - "d": 11 - }, - { - "kind": "vrg", - "d": 10 - }, - { - "kind": "cst", - "t": { - "kind": "res" - } - } - ], - "n": 43 - }, - { - "kind": "nod", - "t": 0, - "l": "all", - "s": null, - "i": { - "{\"Data\":\"test_data_3\"}": null - }, - "r": "result_copy_result_757daf", - "m": [], - "n": 44 - }, - { - "kind": "lin", - "i": [ - { - "kind": "vrs", - "d": 11 - }, - { - "kind": "vrd", - "d": 12 - }, - { - "kind": "int", - "v": 0 - }, - { - "kind": "vrs", - "d": 12 - } - ], - "n": 45 - }, - { - "kind": "loop", - "c": 46, - "b": 50, - "n": 63 - }, - { - "kind": "lin", - "i": [ - { - "kind": "vrg", - "d": 12 - } - ], - "n": 47 - }, - { - "kind": "lin", - "i": [ - { - "kind": "int", - "v": 10 - } - ], - "n": 48 - }, - { - "kind": "lin", - "i": [ - { - "kind": "lt" - } - ], - "n": 49 - }, - { - "kind": "brc", - "t": 50, - "f": 63, - "m": 63 - }, - { - "kind": "lin", - "i": [ - { - "kind": "vrd", - "d": 13 - } - ], - "n": 51 - }, - { - "kind": "lin", - "i": [ - { - "kind": "vrg", - "d": 11 - } - ], - "n": 52 - }, - { - "kind": "nod", - "t": 2, - "l": "all", - "s": null, - "i": { - "{\"IntermediateResult\":\"result_copy_result_757daf\"}": null, - "{\"IntermediateResult\":\"result_aggregate_7cbaf0\"}": null - }, - "r": "result_run_script_8e4f1b", - "m": [], - "n": 53 - }, - { - "kind": "lin", - "i": [ - { - "kind": "vrs", - "d": 13 - } - ], - "n": 54 - }, - { - "kind": "lin", - "i": [ - { - "kind": "vrg", - "d": 13 - } - ], - "n": 55 - }, - { - "kind": "lin", - "i": [ - { - "kind": "vrg", - "d": 13 - } - ], - "n": 56 - }, - { - "kind": "nod", - "t": 1, - "l": "all", - "s": null, - "i": { - "{\"IntermediateResult\":\"result_run_script_8e4f1b\"}": null - }, - "r": "result_aggregate_7cbaf0", - "m": [], - "n": 57 - }, - { - "kind": "lin", - "i": [ - { - "kind": "vrs", - "d": 11 - } - ], - "n": 58 - }, - { - "kind": "lin", - "i": [ - { - "kind": "vrg", - "d": 12 - } - ], - "n": 59 - }, - { - "kind": "lin", - "i": [ - { - "kind": "int", - "v": 1 - } - ], - "n": 60 - }, - { - "kind": "lin", - "i": [ - { - "kind": "add" - } - ], - "n": 61 - }, - { - "kind": "lin", - "i": [ - { - "kind": "vrs", - "d": 12 - } - ], - "n": 62 - }, - { - "kind": "lin", - "i": [ - { - "kind": "vru", - "d": 13 - } - ], - "n": 46 - }, - { - "kind": "lin", - "i": [ - { - "kind": "vrd", - "d": 14 - }, - { - "kind": "str", - "v": "test_data_4" - }, - { - "kind": "vrg", - "d": 11 - }, - { - "kind": "fnc", - "d": 3 - } - ], - "n": 64 - }, - { - "kind": "cll", - "i": [ - { - "IntermediateResult": "result_copy_result_757daf" - }, - { - "IntermediateResult": "result_aggregate_7cbaf0" - } - ], - "r": [ - { - "Data": "test_data_4" - } - ], - "n": 65 - }, - { - "kind": "lin", - "i": [ - { - "kind": "vrs", - "d": 14 - }, - { - "kind": "vrd", - "d": 15 - }, - { - "kind": "vrg", - "d": 3 - }, - { - "kind": "cst", - "t": { - "kind": "res" - } - }, - { - "kind": "vrg", - "d": 6 - }, - { - "kind": "cst", - "t": { - "kind": "res" - } - } - ], - "n": 66 - }, - { - "kind": "nod", - "t": 1, - "l": "all", - "s": null, - "i": { - "{\"Data\":\"test_data_1\"}": null, - "{\"Data\":\"test_data_2\"}": null - }, - "r": "result_aggregate_82a128", - "m": [], - "n": 67 - }, - { - "kind": "lin", - "i": [ - { - "kind": "vrs", - "d": 15 - }, - { - "kind": "vrd", - "d": 16 - }, - { - "kind": "vrg", - "d": 10 - }, - { - "kind": "cst", - "t": { - "kind": "res" - } - }, - { - "kind": "vrg", - "d": 14 - }, - { - "kind": "cst", - "t": { - "kind": "res" - } - } - ], - "n": 68 - }, - { - "kind": "nod", - "t": 1, - "l": "all", - "s": null, - "i": { - "{\"Data\":\"test_data_4\"}": null, - "{\"Data\":\"test_data_3\"}": null - }, - "r": "result_aggregate_711ebc", - "m": [], - "n": 69 - }, - { - "kind": "lin", - "i": [ - { - "kind": "vrs", - "d": 16 - }, - { - "kind": "vrd", - "d": 17 - }, - { - "kind": "vrg", - "d": 15 - }, - { - "kind": "vrg", - "d": 16 - } - ], - "n": 70 - }, - { - "kind": "nod", - "t": 1, - "l": "all", - "s": null, - "i": { - "{\"IntermediateResult\":\"result_aggregate_82a128\"}": null, - "{\"IntermediateResult\":\"result_aggregate_711ebc\"}": null - }, - "r": "result_aggregate_b4ba7c", - "m": [], - "n": 71 - }, - { - "kind": "lin", - "i": [ - { - "kind": "vrs", - "d": 17 - }, - { - "kind": "str", - "v": "test_data_1234" - }, - { - "kind": "vrg", - "d": 17 - }, - { - "kind": "fnc", - "d": 3 - } - ], - "n": 72 - }, - { - "kind": "cll", - "i": [ - { - "IntermediateResult": "result_aggregate_b4ba7c" - } - ], - "r": [ - { - "Data": "test_data_1234" - } - ], - "n": 73 - }, - { - "kind": "ret", - "r": [ - { - "Data": "test_data_1234" - } - ] - } - ], - "funcs": {} -} diff --git a/tests/wir/epi.json b/tests/wir/epi.json deleted file mode 100644 index 053db75..0000000 --- a/tests/wir/epi.json +++ /dev/null @@ -1,316 +0,0 @@ -{ - "id": "workflow-NptX8BVF", - "table": { - "funcs": [ - { - "n": "print", - "a": [ - { - "kind": "str" - } - ], - "r": { - "kind": "void" - } - }, - { - "n": "println", - "a": [ - { - "kind": "str" - } - ], - "r": { - "kind": "void" - } - }, - { - "n": "len", - "a": [ - { - "kind": "arr", - "t": { - "kind": "any" - } - } - ], - "r": { - "kind": "int" - } - }, - { - "n": "commit_result", - "a": [ - { - "kind": "str" - }, - { - "kind": "res" - } - ], - "r": { - "kind": "data" - } - } - ], - "tasks": [ - { - "kind": "cmp", - "p": "epi", - "v": "1.0.0", - "d": { - "n": "aggregate", - "a": [ - { - "kind": "res" - }, - { - "kind": "res" - } - ], - "r": { - "kind": "res" - } - }, - "a": [ - "lhs", - "rhs" - ], - "r": [] - }, - { - "kind": "cmp", - "p": "epi", - "v": "1.0.0", - "d": { - "n": "local_compute", - "a": [ - { - "kind": "data" - } - ], - "r": { - "kind": "res" - } - }, - "a": [ - "input" - ], - "r": [] - } - ], - "classes": [ - { - "n": "Data", - "i": null, - "v": null, - "p": [ - { - "n": "name", - "t": { - "kind": "str" - } - } - ], - "m": [] - }, - { - "n": "IntermediateResult", - "i": null, - "v": null, - "p": [ - { - "n": "path", - "t": { - "kind": "str" - } - } - ], - "m": [] - } - ], - "vars": [ - { - "n": "res_sta", - "t": { - "kind": "res" - } - }, - { - "n": "res_umc", - "t": { - "kind": "res" - } - }, - { - "n": "res", - "t": { - "kind": "res" - } - } - ], - "results": {} - }, - "metadata": [], - "user": "amy", - "graph": [ - { - "kind": "lin", - "i": [ - { - "kind": "vrd", - "d": 0 - }, - { - "kind": "str", - "v": "st_antonius_ect" - }, - { - "kind": "ins", - "d": 0 - } - ], - "n": 1 - }, - { - "kind": "nod", - "t": 1, - "l": "all", - "s": null, - "i": { - "{\"Data\":\"st_antonius_ect\"}": null - }, - "r": "result_local_compute_d354bf", - "m": [], - "n": 2 - }, - { - "kind": "lin", - "i": [ - { - "kind": "vrs", - "d": 0 - }, - { - "kind": "vrd", - "d": 1 - }, - { - "kind": "str", - "v": "umc_utrecht_ect" - }, - { - "kind": "ins", - "d": 0 - } - ], - "n": 3 - }, - { - "kind": "nod", - "t": 1, - "l": "all", - "s": null, - "i": { - "{\"Data\":\"umc_utrecht_ect\"}": null - }, - "r": "result_local_compute_ab5819", - "m": [], - "n": 4 - }, - { - "kind": "lin", - "i": [ - { - "kind": "vrs", - "d": 1 - }, - { - "kind": "vrd", - "d": 2 - }, - { - "kind": "vrg", - "d": 0 - }, - { - "kind": "vrg", - "d": 1 - } - ], - "n": 5 - }, - { - "kind": "nod", - "t": 0, - "l": { - "restricted": [ - "surf" - ] - }, - "s": null, - "i": { - "{\"IntermediateResult\":\"result_local_compute_d354bf\"}": null, - "{\"IntermediateResult\":\"result_local_compute_ab5819\"}": null - }, - "r": "result_aggregate_d121bf", - "m": [], - "n": 6 - }, - { - "kind": "lin", - "i": [ - { - "kind": "vrs", - "d": 2 - }, - { - "kind": "str", - "v": "surf_res" - }, - { - "kind": "vrg", - "d": 2 - }, - { - "kind": "fnc", - "d": 3 - } - ], - "n": 7 - }, - { - "kind": "cll", - "i": [ - { - "IntermediateResult": "result_aggregate_d121bf" - } - ], - "r": [ - { - "Data": "surf_res" - } - ], - "n": 8 - }, - { - "kind": "lin", - "i": [ - { - "kind": "vru", - "d": 2 - } - ], - "n": 9 - }, - { - "kind": "ret", - "r": [ - { - "Data": "surf_res" - } - ] - } - ], - "funcs": {} -} diff --git a/tests/wir/epi_one.json b/tests/wir/epi_one.json deleted file mode 100644 index e6f7446..0000000 --- a/tests/wir/epi_one.json +++ /dev/null @@ -1,275 +0,0 @@ -{ - "id": "workflow-XnUaDZbg", - "table": { - "funcs": [ - { - "n": "print", - "a": [ - { - "kind": "str" - } - ], - "r": { - "kind": "void" - } - }, - { - "n": "println", - "a": [ - { - "kind": "str" - } - ], - "r": { - "kind": "void" - } - }, - { - "n": "len", - "a": [ - { - "kind": "arr", - "t": { - "kind": "any" - } - } - ], - "r": { - "kind": "int" - } - }, - { - "n": "commit_result", - "a": [ - { - "kind": "str" - }, - { - "kind": "res" - } - ], - "r": { - "kind": "data" - } - } - ], - "tasks": [ - { - "kind": "cmp", - "p": "epi", - "v": "1.0.0", - "d": { - "n": "local_compute", - "a": [ - { - "kind": "data" - } - ], - "r": { - "kind": "res" - } - }, - "a": [ - "input" - ], - "r": [] - }, - { - "kind": "cmp", - "p": "epi", - "v": "1.0.0", - "d": { - "n": "aggregate", - "a": [ - { - "kind": "res" - }, - { - "kind": "res" - } - ], - "r": { - "kind": "res" - } - }, - "a": [ - "lhs", - "rhs" - ], - "r": [] - } - ], - "classes": [ - { - "n": "Data", - "i": null, - "v": null, - "p": [ - { - "n": "name", - "t": { - "kind": "str" - } - } - ], - "m": [] - }, - { - "n": "IntermediateResult", - "i": null, - "v": null, - "p": [ - { - "n": "path", - "t": { - "kind": "str" - } - } - ], - "m": [] - } - ], - "vars": [ - { - "n": "res_umc", - "t": { - "kind": "res" - } - }, - { - "n": "res", - "t": { - "kind": "res" - } - } - ], - "results": {} - }, - "metadata": [], - "user": "amy", - "graph": [ - { - "kind": "lin", - "i": [ - { - "kind": "vrd", - "d": 0 - }, - { - "kind": "str", - "v": "umc_utrecht_ect" - }, - { - "kind": "ins", - "d": 0 - } - ], - "n": 1 - }, - { - "kind": "nod", - "t": 0, - "l": "all", - "s": null, - "i": { - "{\"Data\":\"umc_utrecht_ect\"}": null - }, - "r": "result_local_compute_07ae78", - "m": [], - "n": 2 - }, - { - "kind": "lin", - "i": [ - { - "kind": "vrs", - "d": 0 - }, - { - "kind": "vrd", - "d": 1 - }, - { - "kind": "vrg", - "d": 0 - }, - { - "kind": "vrg", - "d": 0 - } - ], - "n": 3 - }, - { - "kind": "nod", - "t": 1, - "l": { - "restricted": [ - "surf" - ] - }, - "s": null, - "i": { - "{\"IntermediateResult\":\"result_local_compute_07ae78\"}": null - }, - "r": "result_aggregate_231806", - "m": [], - "n": 4 - }, - { - "kind": "lin", - "i": [ - { - "kind": "vrs", - "d": 1 - }, - { - "kind": "str", - "v": "surf_res" - }, - { - "kind": "vrg", - "d": 1 - }, - { - "kind": "fnc", - "d": 3 - } - ], - "n": 5 - }, - { - "kind": "cll", - "i": [ - { - "IntermediateResult": "result_aggregate_231806" - } - ], - "r": [ - { - "Data": "surf_res" - } - ], - "n": 6 - }, - { - "kind": "lin", - "i": [ - { - "kind": "vru", - "d": 1 - } - ], - "n": 7 - }, - { - "kind": "ret", - "r": [ - { - "Data": "surf_res" - } - ] - } - ], - "funcs": {} -} diff --git a/tests/wir/epi_parallel.json b/tests/wir/epi_parallel.json deleted file mode 100644 index ad601e3..0000000 --- a/tests/wir/epi_parallel.json +++ /dev/null @@ -1,366 +0,0 @@ -{ - "id": "workflow-lmO8yfzt", - "table": { - "funcs": [ - { - "n": "print", - "a": [ - { - "kind": "str" - } - ], - "r": { - "kind": "void" - } - }, - { - "n": "println", - "a": [ - { - "kind": "str" - } - ], - "r": { - "kind": "void" - } - }, - { - "n": "len", - "a": [ - { - "kind": "arr", - "t": { - "kind": "any" - } - } - ], - "r": { - "kind": "int" - } - }, - { - "n": "commit_result", - "a": [ - { - "kind": "str" - }, - { - "kind": "res" - } - ], - "r": { - "kind": "data" - } - } - ], - "tasks": [ - { - "kind": "cmp", - "p": "epi", - "v": "1.0.0", - "d": { - "n": "aggregate", - "a": [ - { - "kind": "res" - }, - { - "kind": "res" - } - ], - "r": { - "kind": "res" - } - }, - "a": [ - "lhs", - "rhs" - ], - "r": [] - }, - { - "kind": "cmp", - "p": "epi", - "v": "1.0.0", - "d": { - "n": "local_compute", - "a": [ - { - "kind": "data" - } - ], - "r": { - "kind": "res" - } - }, - "a": [ - "input" - ], - "r": [] - } - ], - "classes": [ - { - "n": "Data", - "i": null, - "v": null, - "p": [ - { - "n": "name", - "t": { - "kind": "str" - } - } - ], - "m": [] - }, - { - "n": "IntermediateResult", - "i": null, - "v": null, - "p": [ - { - "n": "path", - "t": { - "kind": "str" - } - } - ], - "m": [] - } - ], - "vars": [ - { - "n": "res", - "t": { - "kind": "arr", - "t": { - "kind": "res" - } - } - }, - { - "n": "gres", - "t": { - "kind": "res" - } - } - ], - "results": {} - }, - "metadata": [], - "user": "amy", - "graph": [ - { - "kind": "par", - "b": [ - 1, - 5 - ], - "m": 9 - }, - { - "kind": "lin", - "i": [ - { - "kind": "str", - "v": "st_antonius_ect" - } - ], - "n": 2 - }, - { - "kind": "lin", - "i": [ - { - "kind": "ins", - "d": 0 - } - ], - "n": 3 - }, - { - "kind": "nod", - "t": 1, - "l": "all", - "s": null, - "i": { - "{\"Data\":\"st_antonius_ect\"}": null - }, - "r": "result_local_compute_32ab8e", - "m": [], - "n": 4 - }, - { - "kind": "ret", - "r": [ - { - "IntermediateResult": "result_local_compute_32ab8e" - } - ] - }, - { - "kind": "lin", - "i": [ - { - "kind": "str", - "v": "umc_utrecht_ect" - } - ], - "n": 6 - }, - { - "kind": "lin", - "i": [ - { - "kind": "ins", - "d": 0 - } - ], - "n": 7 - }, - { - "kind": "nod", - "t": 1, - "l": "all", - "s": null, - "i": { - "{\"Data\":\"umc_utrecht_ect\"}": null - }, - "r": "result_local_compute_55608d", - "m": [], - "n": 8 - }, - { - "kind": "ret", - "r": [ - { - "IntermediateResult": "result_local_compute_55608d" - } - ] - }, - { - "kind": "join", - "m": "All", - "n": 10 - }, - { - "kind": "lin", - "i": [ - { - "kind": "vrd", - "d": 0 - }, - { - "kind": "vrs", - "d": 0 - }, - { - "kind": "vrd", - "d": 1 - }, - { - "kind": "vrg", - "d": 0 - }, - { - "kind": "int", - "v": 0 - }, - { - "kind": "arx", - "t": { - "kind": "res" - } - }, - { - "kind": "vrg", - "d": 0 - }, - { - "kind": "int", - "v": 1 - }, - { - "kind": "arx", - "t": { - "kind": "res" - } - } - ], - "n": 11 - }, - { - "kind": "nod", - "t": 0, - "l": { - "restricted": [ - "surf" - ] - }, - "s": null, - "i": { - "{\"IntermediateResult\":\"result_local_compute_55608d\"}": null, - "{\"IntermediateResult\":\"result_local_compute_32ab8e\"}": null - }, - "r": "result_aggregate_cb3875", - "m": [], - "n": 12 - }, - { - "kind": "lin", - "i": [ - { - "kind": "vrs", - "d": 1 - }, - { - "kind": "str", - "v": "surf_res" - }, - { - "kind": "vrg", - "d": 1 - }, - { - "kind": "fnc", - "d": 3 - } - ], - "n": 13 - }, - { - "kind": "cll", - "i": [ - { - "IntermediateResult": "result_aggregate_cb3875" - } - ], - "r": [ - { - "Data": "surf_res" - } - ], - "n": 14 - }, - { - "kind": "lin", - "i": [ - { - "kind": "vru", - "d": 1 - } - ], - "n": 15 - }, - { - "kind": "ret", - "r": [ - { - "Data": "surf_res" - } - ] - } - ], - "funcs": {} -} diff --git a/tests/wir/function.json b/tests/wir/function.json deleted file mode 100644 index 07e984d..0000000 --- a/tests/wir/function.json +++ /dev/null @@ -1,377 +0,0 @@ -{ - "id": "workflow-sjRdpNio", - "table": { - "funcs": [ - { - "n": "print", - "a": [ - { - "kind": "str" - } - ], - "r": { - "kind": "void" - } - }, - { - "n": "println", - "a": [ - { - "kind": "str" - } - ], - "r": { - "kind": "void" - } - }, - { - "n": "len", - "a": [ - { - "kind": "arr", - "t": { - "kind": "any" - } - } - ], - "r": { - "kind": "int" - } - }, - { - "n": "commit_result", - "a": [ - { - "kind": "str" - }, - { - "kind": "res" - } - ], - "r": { - "kind": "data" - } - }, - { - "n": "hello_there", - "a": [], - "r": { - "kind": "void" - } - }, - { - "n": "say", - "a": [ - { - "kind": "any" - } - ], - "r": { - "kind": "void" - } - }, - { - "n": "add", - "a": [ - { - "kind": "any" - }, - { - "kind": "any" - } - ], - "r": { - "kind": "any" - } - } - ], - "tasks": [], - "classes": [ - { - "n": "Data", - "i": null, - "v": null, - "p": [ - { - "n": "name", - "t": { - "kind": "str" - } - } - ], - "m": [] - }, - { - "n": "IntermediateResult", - "i": null, - "v": null, - "p": [ - { - "n": "path", - "t": { - "kind": "str" - } - } - ], - "m": [] - } - ], - "vars": [ - { - "n": "text", - "t": { - "kind": "any" - } - }, - { - "n": "lhs", - "t": { - "kind": "any" - } - }, - { - "n": "rhs", - "t": { - "kind": "any" - } - }, - { - "n": "result", - "t": { - "kind": "any" - } - } - ], - "results": {} - }, - "metadata": [], - "user": "amy", - "graph": [ - { - "kind": "lin", - "i": [ - { - "kind": "fnc", - "d": 4 - } - ], - "n": 1 - }, - { - "kind": "cll", - "i": [], - "r": [], - "n": 2 - }, - { - "kind": "lin", - "i": [ - { - "kind": "str", - "v": "General Kenobi!" - }, - { - "kind": "cst", - "t": { - "kind": "any" - } - }, - { - "kind": "fnc", - "d": 5 - } - ], - "n": 3 - }, - { - "kind": "cll", - "i": [], - "r": [], - "n": 4 - }, - { - "kind": "lin", - "i": [ - { - "kind": "int", - "v": 21 - }, - { - "kind": "cst", - "t": { - "kind": "any" - } - }, - { - "kind": "int", - "v": 21 - }, - { - "kind": "cst", - "t": { - "kind": "any" - } - }, - { - "kind": "fnc", - "d": 6 - } - ], - "n": 5 - }, - { - "kind": "cll", - "i": [], - "r": [], - "n": 6 - }, - { - "kind": "lin", - "i": [ - { - "kind": "cst", - "t": { - "kind": "str" - } - }, - { - "kind": "fnc", - "d": 1 - } - ], - "n": 7 - }, - { - "kind": "cll", - "i": [], - "r": [], - "n": 8 - }, - { - "kind": "stp" - } - ], - "funcs": { - "4": [ - { - "kind": "lin", - "i": [ - { - "kind": "str", - "v": "Hello there!" - }, - { - "kind": "fnc", - "d": 1 - } - ], - "n": 1 - }, - { - "kind": "cll", - "i": [], - "r": [], - "n": 2 - }, - { - "kind": "ret", - "r": [] - } - ], - "5": [ - { - "kind": "lin", - "i": [ - { - "kind": "vrd", - "d": 0 - }, - { - "kind": "vrs", - "d": 0 - }, - { - "kind": "vrg", - "d": 0 - }, - { - "kind": "cst", - "t": { - "kind": "str" - } - }, - { - "kind": "fnc", - "d": 1 - } - ], - "n": 1 - }, - { - "kind": "cll", - "i": [], - "r": [], - "n": 2 - }, - { - "kind": "ret", - "r": [] - } - ], - "6": [ - { - "kind": "lin", - "i": [ - { - "kind": "vrd", - "d": 2 - }, - { - "kind": "vrs", - "d": 2 - }, - { - "kind": "vrd", - "d": 1 - }, - { - "kind": "vrs", - "d": 1 - }, - { - "kind": "vrd", - "d": 3 - }, - { - "kind": "vrg", - "d": 1 - }, - { - "kind": "vrg", - "d": 2 - }, - { - "kind": "add" - }, - { - "kind": "vrs", - "d": 3 - }, - { - "kind": "vrg", - "d": 3 - }, - { - "kind": "vru", - "d": 3 - } - ], - "n": 1 - }, - { - "kind": "ret", - "r": [] - } - ] - } -} diff --git a/tests/wir/hello_world.json b/tests/wir/hello_world.json deleted file mode 100644 index ceaaf89..0000000 --- a/tests/wir/hello_world.json +++ /dev/null @@ -1,139 +0,0 @@ -{ - "id": "workflow-lMgy9F3B", - "table": { - "funcs": [ - { - "n": "print", - "a": [ - { - "kind": "str" - } - ], - "r": { - "kind": "void" - } - }, - { - "n": "println", - "a": [ - { - "kind": "str" - } - ], - "r": { - "kind": "void" - } - }, - { - "n": "len", - "a": [ - { - "kind": "arr", - "t": { - "kind": "any" - } - } - ], - "r": { - "kind": "int" - } - }, - { - "n": "commit_result", - "a": [ - { - "kind": "str" - }, - { - "kind": "res" - } - ], - "r": { - "kind": "data" - } - } - ], - "tasks": [ - { - "kind": "cmp", - "p": "hello_world", - "v": "1.0.0", - "d": { - "n": "hello_world", - "a": [], - "r": { - "kind": "str" - } - }, - "a": [], - "r": [] - } - ], - "classes": [ - { - "n": "Data", - "i": null, - "v": null, - "p": [ - { - "n": "name", - "t": { - "kind": "str" - } - } - ], - "m": [] - }, - { - "n": "IntermediateResult", - "i": null, - "v": null, - "p": [ - { - "n": "path", - "t": { - "kind": "str" - } - } - ], - "m": [] - } - ], - "vars": [], - "results": {} - }, - "metadata": [], - "user": "amy", - "graph": [ - { - "kind": "nod", - "t": 0, - "l": "all", - "s": null, - "i": {}, - "r": null, - "m": [], - "n": 1 - }, - { - "kind": "lin", - "i": [ - { - "kind": "fnc", - "d": 1 - } - ], - "n": 2 - }, - { - "kind": "cll", - "i": [], - "r": [], - "n": 3 - }, - { - "kind": "stp" - } - ], - "funcs": {} -} diff --git a/tests/wir/if_complex.json b/tests/wir/if_complex.json deleted file mode 100644 index 5cefe76..0000000 --- a/tests/wir/if_complex.json +++ /dev/null @@ -1,703 +0,0 @@ -{ - "id": "workflow-eMOlvwjw", - "table": { - "funcs": [ - { - "n": "print", - "a": [ - { - "kind": "str" - } - ], - "r": { - "kind": "void" - } - }, - { - "n": "println", - "a": [ - { - "kind": "str" - } - ], - "r": { - "kind": "void" - } - }, - { - "n": "len", - "a": [ - { - "kind": "arr", - "t": { - "kind": "any" - } - } - ], - "r": { - "kind": "int" - } - }, - { - "n": "commit_result", - "a": [ - { - "kind": "str" - }, - { - "kind": "res" - } - ], - "r": { - "kind": "data" - } - } - ], - "tasks": [], - "classes": [ - { - "n": "Data", - "i": null, - "v": null, - "p": [ - { - "n": "name", - "t": { - "kind": "str" - } - } - ], - "m": [] - }, - { - "n": "IntermediateResult", - "i": null, - "v": null, - "p": [ - { - "n": "path", - "t": { - "kind": "str" - } - } - ], - "m": [] - } - ], - "vars": [ - { - "n": "value", - "t": { - "kind": "int" - } - } - ], - "results": {} - }, - "metadata": [], - "user": "amy", - "graph": [ - { - "kind": "lin", - "i": [ - { - "kind": "vrd", - "d": 0 - }, - { - "kind": "int", - "v": 42 - }, - { - "kind": "vrs", - "d": 0 - }, - { - "kind": "vrg", - "d": 0 - }, - { - "kind": "int", - "v": 0 - }, - { - "kind": "gt" - } - ], - "n": 1 - }, - { - "kind": "brc", - "t": 2, - "f": 37, - "m": 69 - }, - { - "kind": "lin", - "i": [ - { - "kind": "vrg", - "d": 0 - } - ], - "n": 3 - }, - { - "kind": "lin", - "i": [ - { - "kind": "int", - "v": 42 - } - ], - "n": 4 - }, - { - "kind": "lin", - "i": [ - { - "kind": "gt" - } - ], - "n": 5 - }, - { - "kind": "brc", - "t": 6, - "f": 25, - "m": 36 - }, - { - "kind": "lin", - "i": [ - { - "kind": "vrg", - "d": 0 - } - ], - "n": 7 - }, - { - "kind": "lin", - "i": [ - { - "kind": "int", - "v": 84 - } - ], - "n": 8 - }, - { - "kind": "lin", - "i": [ - { - "kind": "gt" - } - ], - "n": 9 - }, - { - "kind": "brc", - "t": 10, - "f": 13, - "m": 24 - }, - { - "kind": "lin", - "i": [ - { - "kind": "str", - "v": "Value is more than 84!" - } - ], - "n": 11 - }, - { - "kind": "lin", - "i": [ - { - "kind": "fnc", - "d": 1 - } - ], - "n": 12 - }, - { - "kind": "cll", - "i": [], - "r": [], - "n": 24 - }, - { - "kind": "lin", - "i": [ - { - "kind": "vrg", - "d": 0 - } - ], - "n": 14 - }, - { - "kind": "lin", - "i": [ - { - "kind": "int", - "v": 84 - } - ], - "n": 15 - }, - { - "kind": "lin", - "i": [ - { - "kind": "lt" - } - ], - "n": 16 - }, - { - "kind": "brc", - "t": 17, - "f": 20, - "m": 23 - }, - { - "kind": "lin", - "i": [ - { - "kind": "str", - "v": "Value is less than 84!" - } - ], - "n": 18 - }, - { - "kind": "lin", - "i": [ - { - "kind": "fnc", - "d": 1 - } - ], - "n": 19 - }, - { - "kind": "cll", - "i": [], - "r": [], - "n": 23 - }, - { - "kind": "lin", - "i": [ - { - "kind": "str", - "v": "Value is 84!" - } - ], - "n": 21 - }, - { - "kind": "lin", - "i": [ - { - "kind": "fnc", - "d": 1 - } - ], - "n": 22 - }, - { - "kind": "cll", - "i": [], - "r": [], - "n": 23 - }, - { - "kind": "lin", - "i": [], - "n": 24 - }, - { - "kind": "lin", - "i": [], - "n": 36 - }, - { - "kind": "lin", - "i": [ - { - "kind": "vrg", - "d": 0 - } - ], - "n": 26 - }, - { - "kind": "lin", - "i": [ - { - "kind": "int", - "v": 42 - } - ], - "n": 27 - }, - { - "kind": "lin", - "i": [ - { - "kind": "lt" - } - ], - "n": 28 - }, - { - "kind": "brc", - "t": 29, - "f": 32, - "m": 35 - }, - { - "kind": "lin", - "i": [ - { - "kind": "str", - "v": "Value is less than 42!" - } - ], - "n": 30 - }, - { - "kind": "lin", - "i": [ - { - "kind": "fnc", - "d": 1 - } - ], - "n": 31 - }, - { - "kind": "cll", - "i": [], - "r": [], - "n": 35 - }, - { - "kind": "lin", - "i": [ - { - "kind": "str", - "v": "Value is 42!" - } - ], - "n": 33 - }, - { - "kind": "lin", - "i": [ - { - "kind": "fnc", - "d": 1 - } - ], - "n": 34 - }, - { - "kind": "cll", - "i": [], - "r": [], - "n": 35 - }, - { - "kind": "lin", - "i": [], - "n": 36 - }, - { - "kind": "lin", - "i": [], - "n": 69 - }, - { - "kind": "lin", - "i": [ - { - "kind": "vrg", - "d": 0 - } - ], - "n": 38 - }, - { - "kind": "lin", - "i": [ - { - "kind": "int", - "v": 0 - } - ], - "n": 39 - }, - { - "kind": "lin", - "i": [ - { - "kind": "lt" - } - ], - "n": 40 - }, - { - "kind": "brc", - "t": 41, - "f": 65, - "m": 68 - }, - { - "kind": "lin", - "i": [ - { - "kind": "str", - "v": "Value is less than 0!" - } - ], - "n": 42 - }, - { - "kind": "lin", - "i": [ - { - "kind": "fnc", - "d": 1 - } - ], - "n": 43 - }, - { - "kind": "cll", - "i": [], - "r": [], - "n": 44 - }, - { - "kind": "lin", - "i": [ - { - "kind": "vrg", - "d": 0 - } - ], - "n": 45 - }, - { - "kind": "lin", - "i": [ - { - "kind": "int", - "v": 42 - } - ], - "n": 46 - }, - { - "kind": "lin", - "i": [ - { - "kind": "neg" - } - ], - "n": 47 - }, - { - "kind": "lin", - "i": [ - { - "kind": "gt" - } - ], - "n": 48 - }, - { - "kind": "brc", - "t": 49, - "f": 52, - "m": 64 - }, - { - "kind": "lin", - "i": [ - { - "kind": "str", - "v": "Value is more than -42!" - } - ], - "n": 50 - }, - { - "kind": "lin", - "i": [ - { - "kind": "fnc", - "d": 1 - } - ], - "n": 51 - }, - { - "kind": "cll", - "i": [], - "r": [], - "n": 64 - }, - { - "kind": "lin", - "i": [ - { - "kind": "vrg", - "d": 0 - } - ], - "n": 53 - }, - { - "kind": "lin", - "i": [ - { - "kind": "int", - "v": 42 - } - ], - "n": 54 - }, - { - "kind": "lin", - "i": [ - { - "kind": "neg" - } - ], - "n": 55 - }, - { - "kind": "lin", - "i": [ - { - "kind": "lt" - } - ], - "n": 56 - }, - { - "kind": "brc", - "t": 57, - "f": 60, - "m": 63 - }, - { - "kind": "lin", - "i": [ - { - "kind": "str", - "v": "Value is less than -42!" - } - ], - "n": 58 - }, - { - "kind": "lin", - "i": [ - { - "kind": "fnc", - "d": 1 - } - ], - "n": 59 - }, - { - "kind": "cll", - "i": [], - "r": [], - "n": 63 - }, - { - "kind": "lin", - "i": [ - { - "kind": "str", - "v": "Value is -42!" - } - ], - "n": 61 - }, - { - "kind": "lin", - "i": [ - { - "kind": "fnc", - "d": 1 - } - ], - "n": 62 - }, - { - "kind": "cll", - "i": [], - "r": [], - "n": 63 - }, - { - "kind": "lin", - "i": [], - "n": 64 - }, - { - "kind": "lin", - "i": [], - "n": 68 - }, - { - "kind": "lin", - "i": [ - { - "kind": "str", - "v": "Value is 0!" - } - ], - "n": 66 - }, - { - "kind": "lin", - "i": [ - { - "kind": "fnc", - "d": 1 - } - ], - "n": 67 - }, - { - "kind": "cll", - "i": [], - "r": [], - "n": 68 - }, - { - "kind": "lin", - "i": [], - "n": 69 - }, - { - "kind": "lin", - "i": [], - "n": 70 - }, - { - "kind": "stp" - } - ], - "funcs": {} -} diff --git a/tests/wir/scopes.json b/tests/wir/scopes.json deleted file mode 100644 index 177c3fb..0000000 --- a/tests/wir/scopes.json +++ /dev/null @@ -1,219 +0,0 @@ -{ - "id": "workflow-KKjq6qR0", - "table": { - "funcs": [ - { - "n": "print", - "a": [ - { - "kind": "str" - } - ], - "r": { - "kind": "void" - } - }, - { - "n": "println", - "a": [ - { - "kind": "str" - } - ], - "r": { - "kind": "void" - } - }, - { - "n": "len", - "a": [ - { - "kind": "arr", - "t": { - "kind": "any" - } - } - ], - "r": { - "kind": "int" - } - }, - { - "n": "commit_result", - "a": [ - { - "kind": "str" - }, - { - "kind": "res" - } - ], - "r": { - "kind": "data" - } - }, - { - "n": "test", - "a": [ - { - "kind": "any" - } - ], - "r": { - "kind": "void" - } - } - ], - "tasks": [], - "classes": [ - { - "n": "Data", - "i": null, - "v": null, - "p": [ - { - "n": "name", - "t": { - "kind": "str" - } - } - ], - "m": [] - }, - { - "n": "IntermediateResult", - "i": null, - "v": null, - "p": [ - { - "n": "path", - "t": { - "kind": "str" - } - } - ], - "m": [] - } - ], - "vars": [ - { - "n": "text", - "t": { - "kind": "any" - } - }, - { - "n": "i", - "t": { - "kind": "int" - } - }, - { - "n": "j", - "t": { - "kind": "int" - } - } - ], - "results": {} - }, - "metadata": [], - "user": "amy", - "graph": [ - { - "kind": "lin", - "i": [ - { - "kind": "vrd", - "d": 1 - }, - { - "kind": "int", - "v": 1 - }, - { - "kind": "vrs", - "d": 1 - }, - { - "kind": "vrd", - "d": 2 - }, - { - "kind": "vrg", - "d": 1 - }, - { - "kind": "vrs", - "d": 2 - } - ], - "n": 1 - }, - { - "kind": "stp" - } - ], - "funcs": { - "4": [ - { - "kind": "lin", - "i": [ - { - "kind": "vrd", - "d": 0 - }, - { - "kind": "vrs", - "d": 0 - }, - { - "kind": "str", - "v": "Test123" - }, - { - "kind": "fnc", - "d": 0 - } - ], - "n": 1 - }, - { - "kind": "cll", - "i": [], - "r": [], - "n": 2 - }, - { - "kind": "lin", - "i": [ - { - "kind": "vrg", - "d": 0 - }, - { - "kind": "cst", - "t": { - "kind": "str" - } - }, - { - "kind": "fnc", - "d": 0 - } - ], - "n": 3 - }, - { - "kind": "cll", - "i": [], - "r": [], - "n": 4 - }, - { - "kind": "ret", - "r": [] - } - ] - } -} diff --git a/tests/wir/task_branch.json b/tests/wir/task_branch.json deleted file mode 100644 index a61b02d..0000000 --- a/tests/wir/task_branch.json +++ /dev/null @@ -1,402 +0,0 @@ -{ - "id": "workflow-t4KDFAWg", - "table": { - "funcs": [ - { - "n": "print", - "a": [ - { - "kind": "str" - } - ], - "r": { - "kind": "void" - } - }, - { - "n": "println", - "a": [ - { - "kind": "str" - } - ], - "r": { - "kind": "void" - } - }, - { - "n": "len", - "a": [ - { - "kind": "arr", - "t": { - "kind": "any" - } - } - ], - "r": { - "kind": "int" - } - }, - { - "n": "commit_result", - "a": [ - { - "kind": "str" - }, - { - "kind": "res" - } - ], - "r": { - "kind": "data" - } - } - ], - "tasks": [ - { - "kind": "cmp", - "p": "copy_result", - "v": "1.1.0", - "d": { - "n": "copy_result", - "a": [ - { - "kind": "res" - } - ], - "r": { - "kind": "res" - } - }, - "a": [ - "result" - ], - "r": [] - }, - { - "kind": "cmp", - "p": "data_init", - "v": "1.0.0", - "d": { - "n": "zeroes", - "a": [ - { - "kind": "int" - }, - { - "kind": "str" - } - ], - "r": { - "kind": "res" - } - }, - "a": [ - "number", - "kind" - ], - "r": [] - } - ], - "classes": [ - { - "n": "Data", - "i": null, - "v": null, - "p": [ - { - "n": "name", - "t": { - "kind": "str" - } - } - ], - "m": [] - }, - { - "n": "IntermediateResult", - "i": null, - "v": null, - "p": [ - { - "n": "path", - "t": { - "kind": "str" - } - } - ], - "m": [] - } - ], - "vars": [ - { - "n": "start", - "t": { - "kind": "res" - } - }, - { - "n": "test", - "t": { - "kind": "int" - } - }, - { - "n": "end", - "t": { - "kind": "res" - } - }, - { - "n": "intermediate", - "t": { - "kind": "res" - } - }, - { - "n": "end", - "t": { - "kind": "res" - } - } - ], - "results": {} - }, - "metadata": [], - "user": "amy", - "graph": [ - { - "kind": "lin", - "i": [ - { - "kind": "vrd", - "d": 0 - }, - { - "kind": "int", - "v": 16 - }, - { - "kind": "str", - "v": "vector" - } - ], - "n": 1 - }, - { - "kind": "nod", - "t": 1, - "l": "all", - "s": null, - "i": {}, - "r": "result_zeroes_cbbbea", - "m": [], - "n": 2 - }, - { - "kind": "lin", - "i": [ - { - "kind": "vrs", - "d": 0 - }, - { - "kind": "vrd", - "d": 1 - }, - { - "kind": "int", - "v": 42 - }, - { - "kind": "vrs", - "d": 1 - }, - { - "kind": "vrg", - "d": 1 - }, - { - "kind": "int", - "v": 42 - }, - { - "kind": "eq" - } - ], - "n": 3 - }, - { - "kind": "brc", - "t": 4, - "f": 9, - "m": 19 - }, - { - "kind": "lin", - "i": [ - { - "kind": "vrd", - "d": 2 - } - ], - "n": 5 - }, - { - "kind": "lin", - "i": [ - { - "kind": "vrg", - "d": 0 - } - ], - "n": 6 - }, - { - "kind": "nod", - "t": 0, - "l": "all", - "s": null, - "i": { - "{\"IntermediateResult\":\"result_zeroes_cbbbea\"}": null - }, - "r": "result_copy_result_59f249", - "m": [], - "n": 7 - }, - { - "kind": "lin", - "i": [ - { - "kind": "vrs", - "d": 2 - } - ], - "n": 8 - }, - { - "kind": "lin", - "i": [ - { - "kind": "vru", - "d": 2 - } - ], - "n": 19 - }, - { - "kind": "lin", - "i": [ - { - "kind": "vrd", - "d": 3 - } - ], - "n": 10 - }, - { - "kind": "lin", - "i": [ - { - "kind": "vrg", - "d": 0 - } - ], - "n": 11 - }, - { - "kind": "nod", - "t": 0, - "l": "all", - "s": null, - "i": { - "{\"IntermediateResult\":\"result_zeroes_cbbbea\"}": null - }, - "r": "result_copy_result_150954", - "m": [], - "n": 12 - }, - { - "kind": "lin", - "i": [ - { - "kind": "vrs", - "d": 3 - } - ], - "n": 13 - }, - { - "kind": "lin", - "i": [ - { - "kind": "vrd", - "d": 4 - } - ], - "n": 14 - }, - { - "kind": "lin", - "i": [ - { - "kind": "vrg", - "d": 3 - } - ], - "n": 15 - }, - { - "kind": "nod", - "t": 0, - "l": "all", - "s": null, - "i": { - "{\"IntermediateResult\":\"result_copy_result_150954\"}": null - }, - "r": "result_copy_result_b9ea44", - "m": [], - "n": 16 - }, - { - "kind": "lin", - "i": [ - { - "kind": "vrs", - "d": 4 - } - ], - "n": 17 - }, - { - "kind": "lin", - "i": [ - { - "kind": "vru", - "d": 3 - } - ], - "n": 18 - }, - { - "kind": "lin", - "i": [ - { - "kind": "vru", - "d": 4 - } - ], - "n": 19 - }, - { - "kind": "lin", - "i": [], - "n": 20 - }, - { - "kind": "stp" - } - ], - "funcs": {} -} diff --git a/tests/wir/task_chain.json b/tests/wir/task_chain.json deleted file mode 100644 index 705d7bb..0000000 --- a/tests/wir/task_chain.json +++ /dev/null @@ -1,331 +0,0 @@ -{ - "id": "workflow-nXiO5DZR", - "table": { - "funcs": [ - { - "n": "print", - "a": [ - { - "kind": "str" - } - ], - "r": { - "kind": "void" - } - }, - { - "n": "println", - "a": [ - { - "kind": "str" - } - ], - "r": { - "kind": "void" - } - }, - { - "n": "len", - "a": [ - { - "kind": "arr", - "t": { - "kind": "any" - } - } - ], - "r": { - "kind": "int" - } - }, - { - "n": "commit_result", - "a": [ - { - "kind": "str" - }, - { - "kind": "res" - } - ], - "r": { - "kind": "data" - } - }, - { - "n": "once", - "a": [], - "r": { - "kind": "str" - } - }, - { - "n": "twice", - "a": [], - "r": { - "kind": "str" - } - }, - { - "n": "fours", - "a": [], - "r": { - "kind": "str" - } - }, - { - "n": "eights", - "a": [], - "r": { - "kind": "str" - } - } - ], - "tasks": [ - { - "kind": "cmp", - "p": "hello_world", - "v": "1.0.0", - "d": { - "n": "hello_world", - "a": [], - "r": { - "kind": "str" - } - }, - "a": [], - "r": [] - } - ], - "classes": [ - { - "n": "Data", - "i": null, - "v": null, - "p": [ - { - "n": "name", - "t": { - "kind": "str" - } - } - ], - "m": [] - }, - { - "n": "IntermediateResult", - "i": null, - "v": null, - "p": [ - { - "n": "path", - "t": { - "kind": "str" - } - } - ], - "m": [] - } - ], - "vars": [], - "results": {} - }, - "metadata": [], - "user": "amy", - "graph": [ - { - "kind": "lin", - "i": [ - { - "kind": "fnc", - "d": 7 - } - ], - "n": 1 - }, - { - "kind": "cll", - "i": [], - "r": [], - "n": 2 - }, - { - "kind": "lin", - "i": [ - { - "kind": "fnc", - "d": 1 - } - ], - "n": 3 - }, - { - "kind": "cll", - "i": [], - "r": [], - "n": 4 - }, - { - "kind": "stp" - } - ], - "funcs": { - "4": [ - { - "kind": "nod", - "t": 0, - "l": "all", - "s": null, - "i": {}, - "r": null, - "m": [], - "n": 1 - }, - { - "kind": "ret", - "r": [] - } - ], - "6": [ - { - "kind": "lin", - "i": [ - { - "kind": "fnc", - "d": 5 - } - ], - "n": 1 - }, - { - "kind": "cll", - "i": [], - "r": [], - "n": 2 - }, - { - "kind": "lin", - "i": [ - { - "kind": "fnc", - "d": 5 - } - ], - "n": 3 - }, - { - "kind": "cll", - "i": [], - "r": [], - "n": 4 - }, - { - "kind": "lin", - "i": [ - { - "kind": "add" - } - ], - "n": 5 - }, - { - "kind": "ret", - "r": [] - } - ], - "7": [ - { - "kind": "lin", - "i": [ - { - "kind": "fnc", - "d": 6 - } - ], - "n": 1 - }, - { - "kind": "cll", - "i": [], - "r": [], - "n": 2 - }, - { - "kind": "lin", - "i": [ - { - "kind": "fnc", - "d": 6 - } - ], - "n": 3 - }, - { - "kind": "cll", - "i": [], - "r": [], - "n": 4 - }, - { - "kind": "lin", - "i": [ - { - "kind": "add" - } - ], - "n": 5 - }, - { - "kind": "ret", - "r": [] - } - ], - "5": [ - { - "kind": "lin", - "i": [ - { - "kind": "fnc", - "d": 4 - } - ], - "n": 1 - }, - { - "kind": "cll", - "i": [], - "r": [], - "n": 2 - }, - { - "kind": "lin", - "i": [ - { - "kind": "fnc", - "d": 4 - } - ], - "n": 3 - }, - { - "kind": "cll", - "i": [], - "r": [], - "n": 4 - }, - { - "kind": "lin", - "i": [ - { - "kind": "add" - } - ], - "n": 5 - }, - { - "kind": "ret", - "r": [] - } - ] - } -} diff --git a/tests/wir/task_loop.json b/tests/wir/task_loop.json deleted file mode 100644 index a4a7cd4..0000000 --- a/tests/wir/task_loop.json +++ /dev/null @@ -1,381 +0,0 @@ -{ - "id": "workflow-U09uHxym", - "table": { - "funcs": [ - { - "n": "print", - "a": [ - { - "kind": "str" - } - ], - "r": { - "kind": "void" - } - }, - { - "n": "println", - "a": [ - { - "kind": "str" - } - ], - "r": { - "kind": "void" - } - }, - { - "n": "len", - "a": [ - { - "kind": "arr", - "t": { - "kind": "any" - } - } - ], - "r": { - "kind": "int" - } - }, - { - "n": "commit_result", - "a": [ - { - "kind": "str" - }, - { - "kind": "res" - } - ], - "r": { - "kind": "data" - } - } - ], - "tasks": [ - { - "kind": "cmp", - "p": "copy_result", - "v": "1.1.0", - "d": { - "n": "copy_result", - "a": [ - { - "kind": "res" - } - ], - "r": { - "kind": "res" - } - }, - "a": [ - "result" - ], - "r": [] - }, - { - "kind": "cmp", - "p": "data_init", - "v": "1.0.0", - "d": { - "n": "zeroes", - "a": [ - { - "kind": "int" - }, - { - "kind": "str" - } - ], - "r": { - "kind": "res" - } - }, - "a": [ - "number", - "kind" - ], - "r": [] - } - ], - "classes": [ - { - "n": "Data", - "i": null, - "v": null, - "p": [ - { - "n": "name", - "t": { - "kind": "str" - } - } - ], - "m": [] - }, - { - "n": "IntermediateResult", - "i": null, - "v": null, - "p": [ - { - "n": "path", - "t": { - "kind": "str" - } - } - ], - "m": [] - } - ], - "vars": [ - { - "n": "start", - "t": { - "kind": "res" - } - }, - { - "n": "accumulate", - "t": { - "kind": "res" - } - }, - { - "n": "i", - "t": { - "kind": "int" - } - } - ], - "results": {} - }, - "metadata": [], - "user": "amy", - "graph": [ - { - "kind": "lin", - "i": [ - { - "kind": "vrd", - "d": 0 - }, - { - "kind": "int", - "v": 16 - }, - { - "kind": "str", - "v": "vector" - } - ], - "n": 1 - }, - { - "kind": "nod", - "t": 1, - "l": "all", - "s": null, - "i": {}, - "r": "result_zeroes_31e8ba", - "m": [], - "n": 2 - }, - { - "kind": "lin", - "i": [ - { - "kind": "vrs", - "d": 0 - }, - { - "kind": "vrd", - "d": 1 - }, - { - "kind": "vrg", - "d": 0 - }, - { - "kind": "vrs", - "d": 1 - }, - { - "kind": "vrd", - "d": 2 - }, - { - "kind": "int", - "v": 0 - }, - { - "kind": "vrs", - "d": 2 - } - ], - "n": 3 - }, - { - "kind": "loop", - "c": 4, - "b": 8, - "n": 15 - }, - { - "kind": "lin", - "i": [ - { - "kind": "vrg", - "d": 2 - } - ], - "n": 5 - }, - { - "kind": "lin", - "i": [ - { - "kind": "int", - "v": 16 - } - ], - "n": 6 - }, - { - "kind": "lin", - "i": [ - { - "kind": "lt" - } - ], - "n": 7 - }, - { - "kind": "brc", - "t": 8, - "f": 15, - "m": 15 - }, - { - "kind": "lin", - "i": [ - { - "kind": "vrg", - "d": 1 - } - ], - "n": 9 - }, - { - "kind": "nod", - "t": 0, - "l": "all", - "s": null, - "i": { - "{\"IntermediateResult\":\"result_zeroes_31e8ba\"}": null, - "{\"IntermediateResult\":\"result_copy_result_ab31d7\"}": null - }, - "r": "result_copy_result_ab31d7", - "m": [], - "n": 10 - }, - { - "kind": "lin", - "i": [ - { - "kind": "vrs", - "d": 1 - } - ], - "n": 11 - }, - { - "kind": "lin", - "i": [ - { - "kind": "vrg", - "d": 2 - } - ], - "n": 12 - }, - { - "kind": "lin", - "i": [ - { - "kind": "int", - "v": 1 - } - ], - "n": 13 - }, - { - "kind": "lin", - "i": [ - { - "kind": "add" - } - ], - "n": 14 - }, - { - "kind": "lin", - "i": [ - { - "kind": "vrs", - "d": 2 - } - ], - "n": 4 - }, - { - "kind": "lin", - "i": [ - { - "kind": "str", - "v": "final_result" - }, - { - "kind": "vrg", - "d": 1 - }, - { - "kind": "fnc", - "d": 3 - } - ], - "n": 16 - }, - { - "kind": "cll", - "i": [ - { - "IntermediateResult": "result_zeroes_31e8ba" - }, - { - "IntermediateResult": "result_copy_result_ab31d7" - } - ], - "r": [ - { - "Data": "final_result" - } - ], - "n": 17 - }, - { - "kind": "lin", - "i": [ - { - "kind": "pop" - } - ], - "n": 18 - }, - { - "kind": "stp" - } - ], - "funcs": {} -} diff --git a/tests/wir/task_parallel.json b/tests/wir/task_parallel.json deleted file mode 100644 index 7b1c7fa..0000000 --- a/tests/wir/task_parallel.json +++ /dev/null @@ -1,448 +0,0 @@ -{ - "id": "workflow-kIbzuWVB", - "table": { - "funcs": [ - { - "n": "print", - "a": [ - { - "kind": "str" - } - ], - "r": { - "kind": "void" - } - }, - { - "n": "println", - "a": [ - { - "kind": "str" - } - ], - "r": { - "kind": "void" - } - }, - { - "n": "len", - "a": [ - { - "kind": "arr", - "t": { - "kind": "any" - } - } - ], - "r": { - "kind": "int" - } - }, - { - "n": "commit_result", - "a": [ - { - "kind": "str" - }, - { - "kind": "res" - } - ], - "r": { - "kind": "data" - } - } - ], - "tasks": [ - { - "kind": "cmp", - "p": "copy_result", - "v": "1.1.0", - "d": { - "n": "copy_result", - "a": [ - { - "kind": "res" - } - ], - "r": { - "kind": "res" - } - }, - "a": [ - "result" - ], - "r": [] - }, - { - "kind": "cmp", - "p": "data_init", - "v": "1.0.0", - "d": { - "n": "zeroes", - "a": [ - { - "kind": "int" - }, - { - "kind": "str" - } - ], - "r": { - "kind": "res" - } - }, - "a": [ - "number", - "kind" - ], - "r": [] - } - ], - "classes": [ - { - "n": "Data", - "i": null, - "v": null, - "p": [ - { - "n": "name", - "t": { - "kind": "str" - } - } - ], - "m": [] - }, - { - "n": "IntermediateResult", - "i": null, - "v": null, - "p": [ - { - "n": "path", - "t": { - "kind": "str" - } - } - ], - "m": [] - } - ], - "vars": [ - { - "n": "start", - "t": { - "kind": "res" - } - }, - { - "n": "intermediate", - "t": { - "kind": "arr", - "t": { - "kind": "res" - } - } - }, - { - "n": "end1", - "t": { - "kind": "res" - } - }, - { - "n": "end2", - "t": { - "kind": "res" - } - } - ], - "results": {} - }, - "metadata": [], - "user": "amy", - "graph": [ - { - "kind": "lin", - "i": [ - { - "kind": "vrd", - "d": 0 - }, - { - "kind": "int", - "v": 16 - }, - { - "kind": "str", - "v": "vector" - } - ], - "n": 1 - }, - { - "kind": "nod", - "t": 1, - "l": "all", - "s": null, - "i": {}, - "r": "result_zeroes_f438cb", - "m": [], - "n": 2 - }, - { - "kind": "lin", - "i": [ - { - "kind": "vrs", - "d": 0 - } - ], - "n": 3 - }, - { - "kind": "par", - "b": [ - 4, - 7 - ], - "m": 10 - }, - { - "kind": "lin", - "i": [ - { - "kind": "vrg", - "d": 0 - } - ], - "n": 5 - }, - { - "kind": "nod", - "t": 0, - "l": "all", - "s": null, - "i": { - "{\"IntermediateResult\":\"result_zeroes_f438cb\"}": null - }, - "r": "result_copy_result_66d269", - "m": [], - "n": 6 - }, - { - "kind": "ret", - "r": [ - { - "IntermediateResult": "result_copy_result_66d269" - } - ] - }, - { - "kind": "lin", - "i": [ - { - "kind": "vrg", - "d": 0 - } - ], - "n": 8 - }, - { - "kind": "nod", - "t": 0, - "l": "all", - "s": null, - "i": { - "{\"IntermediateResult\":\"result_zeroes_f438cb\"}": null - }, - "r": "result_copy_result_40c0a2", - "m": [], - "n": 9 - }, - { - "kind": "ret", - "r": [ - { - "IntermediateResult": "result_copy_result_40c0a2" - } - ] - }, - { - "kind": "join", - "m": "All", - "n": 11 - }, - { - "kind": "lin", - "i": [ - { - "kind": "vrd", - "d": 1 - }, - { - "kind": "vrs", - "d": 1 - }, - { - "kind": "vrd", - "d": 2 - }, - { - "kind": "vrg", - "d": 1 - }, - { - "kind": "int", - "v": 0 - }, - { - "kind": "arx", - "t": { - "kind": "res" - } - } - ], - "n": 12 - }, - { - "kind": "nod", - "t": 0, - "l": "all", - "s": null, - "i": { - "{\"IntermediateResult\":\"result_copy_result_66d269\"}": null, - "{\"IntermediateResult\":\"result_copy_result_40c0a2\"}": null - }, - "r": "result_copy_result_70f797", - "m": [], - "n": 13 - }, - { - "kind": "lin", - "i": [ - { - "kind": "vrs", - "d": 2 - }, - { - "kind": "vrd", - "d": 3 - }, - { - "kind": "vrg", - "d": 1 - }, - { - "kind": "int", - "v": 1 - }, - { - "kind": "arx", - "t": { - "kind": "res" - } - } - ], - "n": 14 - }, - { - "kind": "nod", - "t": 0, - "l": "all", - "s": null, - "i": { - "{\"IntermediateResult\":\"result_copy_result_40c0a2\"}": null, - "{\"IntermediateResult\":\"result_copy_result_66d269\"}": null - }, - "r": "result_copy_result_2068fd", - "m": [], - "n": 15 - }, - { - "kind": "lin", - "i": [ - { - "kind": "vrs", - "d": 3 - }, - { - "kind": "str", - "v": "final_result1" - }, - { - "kind": "vrg", - "d": 2 - }, - { - "kind": "fnc", - "d": 3 - } - ], - "n": 16 - }, - { - "kind": "cll", - "i": [ - { - "IntermediateResult": "result_copy_result_70f797" - } - ], - "r": [ - { - "Data": "final_result1" - } - ], - "n": 17 - }, - { - "kind": "lin", - "i": [ - { - "kind": "pop" - }, - { - "kind": "str", - "v": "final_result2" - }, - { - "kind": "vrg", - "d": 3 - }, - { - "kind": "fnc", - "d": 3 - } - ], - "n": 18 - }, - { - "kind": "cll", - "i": [ - { - "IntermediateResult": "result_copy_result_2068fd" - } - ], - "r": [ - { - "Data": "final_result2" - } - ], - "n": 19 - }, - { - "kind": "lin", - "i": [ - { - "kind": "pop" - } - ], - "n": 20 - }, - { - "kind": "stp" - } - ], - "funcs": {} -} diff --git a/tests/wir/with-tags.json b/tests/wir/with-tags.json deleted file mode 100644 index f87f096..0000000 --- a/tests/wir/with-tags.json +++ /dev/null @@ -1,340 +0,0 @@ -{ - "id": "workflow-AAuG5vMa", - "table": { - "funcs": [ - { - "n": "print", - "a": [ - { - "kind": "str" - } - ], - "r": { - "kind": "void" - } - }, - { - "n": "println", - "a": [ - { - "kind": "str" - } - ], - "r": { - "kind": "void" - } - }, - { - "n": "len", - "a": [ - { - "kind": "arr", - "t": { - "kind": "any" - } - } - ], - "r": { - "kind": "int" - } - }, - { - "n": "commit_result", - "a": [ - { - "kind": "str" - }, - { - "kind": "res" - } - ], - "r": { - "kind": "data" - } - } - ], - "tasks": [ - { - "kind": "cmp", - "p": "epi", - "v": "1.0.0", - "d": { - "n": "aggregate", - "a": [ - { - "kind": "res" - }, - { - "kind": "res" - } - ], - "r": { - "kind": "res" - } - }, - "a": [ - "lhs", - "rhs" - ], - "r": [] - }, - { - "kind": "cmp", - "p": "epi", - "v": "1.0.0", - "d": { - "n": "local_compute", - "a": [ - { - "kind": "data" - } - ], - "r": { - "kind": "res" - } - }, - "a": [ - "input" - ], - "r": [] - } - ], - "classes": [ - { - "n": "Data", - "i": null, - "v": null, - "p": [ - { - "n": "name", - "t": { - "kind": "str" - } - } - ], - "m": [] - }, - { - "n": "IntermediateResult", - "i": null, - "v": null, - "p": [ - { - "n": "path", - "t": { - "kind": "str" - } - } - ], - "m": [] - } - ], - "vars": [ - { - "n": "local_results", - "t": { - "kind": "arr", - "t": { - "kind": "res" - } - } - }, - { - "n": "central", - "t": { - "kind": "res" - } - } - ], - "results": {} - }, - "metadata": [], - "user": "amy", - "graph": [ - { - "kind": "par", - "b": [ - 1, - 5 - ], - "m": 9 - }, - { - "kind": "lin", - "i": [ - { - "kind": "str", - "v": "st_antonius_ect" - } - ], - "n": 2 - }, - { - "kind": "lin", - "i": [ - { - "kind": "ins", - "d": 0 - } - ], - "n": 3 - }, - { - "kind": "nod", - "t": 1, - "l": "all", - "s": null, - "i": { - "{\"Data\":\"st_antonius_ect\"}": null - }, - "r": "result_local_compute_3f6623", - "m": [ - "amy.Epi" - ], - "n": 4 - }, - { - "kind": "ret", - "r": [ - { - "IntermediateResult": "result_local_compute_3f6623" - } - ] - }, - { - "kind": "lin", - "i": [ - { - "kind": "str", - "v": "umc_utrecht_ect" - } - ], - "n": 6 - }, - { - "kind": "lin", - "i": [ - { - "kind": "ins", - "d": 0 - } - ], - "n": 7 - }, - { - "kind": "nod", - "t": 1, - "l": "all", - "s": null, - "i": { - "{\"Data\":\"umc_utrecht_ect\"}": null - }, - "r": "result_local_compute_6c35b5", - "m": [ - "amy.Epi" - ], - "n": 8 - }, - { - "kind": "ret", - "r": [ - { - "IntermediateResult": "result_local_compute_6c35b5" - } - ] - }, - { - "kind": "join", - "m": "All", - "n": 10 - }, - { - "kind": "lin", - "i": [ - { - "kind": "vrd", - "d": 0 - }, - { - "kind": "vrs", - "d": 0 - }, - { - "kind": "vrd", - "d": 1 - }, - { - "kind": "vrg", - "d": 0 - }, - { - "kind": "int", - "v": 0 - }, - { - "kind": "arx", - "t": { - "kind": "res" - } - }, - { - "kind": "vrg", - "d": 0 - }, - { - "kind": "int", - "v": 1 - }, - { - "kind": "arx", - "t": { - "kind": "res" - } - } - ], - "n": 11 - }, - { - "kind": "nod", - "t": 0, - "l": { - "restricted": [ - "surf" - ] - }, - "s": null, - "i": { - "{\"IntermediateResult\":\"result_local_compute_6c35b5\"}": null, - "{\"IntermediateResult\":\"result_local_compute_3f6623\"}": null - }, - "r": "result_aggregate_7ef86b", - "m": [ - "amy.Epi" - ], - "n": 12 - }, - { - "kind": "lin", - "i": [ - { - "kind": "vrs", - "d": 1 - }, - { - "kind": "vrg", - "d": 1 - } - ], - "n": 13 - }, - { - "kind": "ret", - "r": [ - { - "IntermediateResult": "result_aggregate_7ef86b" - } - ] - } - ], - "funcs": {} -} diff --git a/tests/wir/without-tags.json b/tests/wir/without-tags.json deleted file mode 100644 index 1f740e0..0000000 --- a/tests/wir/without-tags.json +++ /dev/null @@ -1,334 +0,0 @@ -{ - "id": "workflow-XkGHhzbp", - "table": { - "funcs": [ - { - "n": "print", - "a": [ - { - "kind": "str" - } - ], - "r": { - "kind": "void" - } - }, - { - "n": "println", - "a": [ - { - "kind": "str" - } - ], - "r": { - "kind": "void" - } - }, - { - "n": "len", - "a": [ - { - "kind": "arr", - "t": { - "kind": "any" - } - } - ], - "r": { - "kind": "int" - } - }, - { - "n": "commit_result", - "a": [ - { - "kind": "str" - }, - { - "kind": "res" - } - ], - "r": { - "kind": "data" - } - } - ], - "tasks": [ - { - "kind": "cmp", - "p": "epi", - "v": "1.0.0", - "d": { - "n": "local_compute", - "a": [ - { - "kind": "data" - } - ], - "r": { - "kind": "res" - } - }, - "a": [ - "input" - ], - "r": [] - }, - { - "kind": "cmp", - "p": "epi", - "v": "1.0.0", - "d": { - "n": "aggregate", - "a": [ - { - "kind": "res" - }, - { - "kind": "res" - } - ], - "r": { - "kind": "res" - } - }, - "a": [ - "lhs", - "rhs" - ], - "r": [] - } - ], - "classes": [ - { - "n": "Data", - "i": null, - "v": null, - "p": [ - { - "n": "name", - "t": { - "kind": "str" - } - } - ], - "m": [] - }, - { - "n": "IntermediateResult", - "i": null, - "v": null, - "p": [ - { - "n": "path", - "t": { - "kind": "str" - } - } - ], - "m": [] - } - ], - "vars": [ - { - "n": "local_results", - "t": { - "kind": "arr", - "t": { - "kind": "res" - } - } - }, - { - "n": "central", - "t": { - "kind": "res" - } - } - ], - "results": {} - }, - "metadata": [], - "user": "amy", - "graph": [ - { - "kind": "par", - "b": [ - 1, - 5 - ], - "m": 9 - }, - { - "kind": "lin", - "i": [ - { - "kind": "str", - "v": "st_antonius_ect" - } - ], - "n": 2 - }, - { - "kind": "lin", - "i": [ - { - "kind": "ins", - "d": 0 - } - ], - "n": 3 - }, - { - "kind": "nod", - "t": 0, - "l": "all", - "s": null, - "i": { - "{\"Data\":\"st_antonius_ect\"}": null - }, - "r": "result_local_compute_41436e", - "m": [], - "n": 4 - }, - { - "kind": "ret", - "r": [ - { - "IntermediateResult": "result_local_compute_41436e" - } - ] - }, - { - "kind": "lin", - "i": [ - { - "kind": "str", - "v": "umc_utrecht_ect" - } - ], - "n": 6 - }, - { - "kind": "lin", - "i": [ - { - "kind": "ins", - "d": 0 - } - ], - "n": 7 - }, - { - "kind": "nod", - "t": 0, - "l": "all", - "s": null, - "i": { - "{\"Data\":\"umc_utrecht_ect\"}": null - }, - "r": "result_local_compute_8cd36c", - "m": [], - "n": 8 - }, - { - "kind": "ret", - "r": [ - { - "IntermediateResult": "result_local_compute_8cd36c" - } - ] - }, - { - "kind": "join", - "m": "All", - "n": 10 - }, - { - "kind": "lin", - "i": [ - { - "kind": "vrd", - "d": 0 - }, - { - "kind": "vrs", - "d": 0 - }, - { - "kind": "vrd", - "d": 1 - }, - { - "kind": "vrg", - "d": 0 - }, - { - "kind": "int", - "v": 0 - }, - { - "kind": "arx", - "t": { - "kind": "res" - } - }, - { - "kind": "vrg", - "d": 0 - }, - { - "kind": "int", - "v": 1 - }, - { - "kind": "arx", - "t": { - "kind": "res" - } - } - ], - "n": 11 - }, - { - "kind": "nod", - "t": 1, - "l": { - "restricted": [ - "surf" - ] - }, - "s": null, - "i": { - "{\"IntermediateResult\":\"result_local_compute_41436e\"}": null, - "{\"IntermediateResult\":\"result_local_compute_8cd36c\"}": null - }, - "r": "result_aggregate_3cc4ca", - "m": [], - "n": 12 - }, - { - "kind": "lin", - "i": [ - { - "kind": "vrs", - "d": 1 - }, - { - "kind": "vrg", - "d": 1 - } - ], - "n": 13 - }, - { - "kind": "ret", - "r": [ - { - "IntermediateResult": "result_aggregate_3cc4ca" - } - ] - } - ], - "funcs": {} -} diff --git a/tests/workflow/jedis.json b/tests/workflow/jedis.json new file mode 100644 index 0000000..ab32666 --- /dev/null +++ b/tests/workflow/jedis.json @@ -0,0 +1,26 @@ +{ + "id": "test-jedis", + "start": { + "kind": "call", + "id": "call-1", + "task": "color-obi-wan-lightsaber", + "input": [{ "id": "jedis" }], + "output": [{ "id": "jedis" }], + "at": { "id": "tatooine" }, + "metadata": [], + "next": { + "kind": "call", + "id": "call-2", + "task": "promote-anakin-to-master", + "input": [{ "id": "jedis" }], + "output": [{ "id": "jedis" }], + "at": { "id": "moraband" }, + "metadata": [], + "next": { "kind": "stop" } + } + }, + + "user": { "id": "Darth Sidious" }, + "metadata": [], + "signature": null +} \ No newline at end of file From 0a665ac1bf39ba4c51e9536b7920af6951fdfc36 Mon Sep 17 00:00:00 2001 From: Lut99 Date: Thu, 17 Oct 2024 13:04:32 +0200 Subject: [PATCH 04/82] Added context logging again --- Cargo.lock | 21 ++- Cargo.toml | 12 +- examples/{eflint.rs => eflint_json.rs} | 26 ++- examples/no_op.rs | 19 +- examples/posix.rs | 15 +- lib/loggers/file/src/logger.rs | 43 ++++- lib/loggers/file/src/stmt.rs | 4 +- lib/loggers/{mock => no-op}/Cargo.toml | 3 +- lib/loggers/{mock => no-op}/src/lib.rs | 0 lib/loggers/{mock => no-op}/src/logger.rs | 16 +- lib/reasoners/eflint-json/Cargo.toml | 2 + lib/reasoners/eflint-json/src/reasonerconn.rs | 166 +++++++++++------- lib/reasoners/eflint-json/src/reasons.rs | 78 +------- lib/reasoners/no-op/src/reasonerconn.rs | 4 +- lib/reasoners/posix/src/reasonerconn.rs | 48 ++++- lib/spec/src/auditlogger.rs | 28 ++- lib/spec/src/context.rs | 49 ++++++ lib/spec/src/lib.rs | 4 +- lib/spec/src/reasonerconn.rs | 4 +- lib/spec/src/reasons.rs | 85 +++++++++ src/lib.rs | 6 +- 21 files changed, 433 insertions(+), 200 deletions(-) rename examples/{eflint.rs => eflint_json.rs} (91%) rename lib/loggers/{mock => no-op}/Cargo.toml (90%) rename lib/loggers/{mock => no-op}/src/lib.rs (100%) rename lib/loggers/{mock => no-op}/src/logger.rs (77%) create mode 100644 lib/spec/src/context.rs create mode 100644 lib/spec/src/reasons.rs diff --git a/Cargo.lock b/Cargo.lock index aa408c4..810d5f4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -322,8 +322,10 @@ dependencies = [ "eflint-json", "error-trace 3.1.0", "reqwest", + "serde", "serde_json", "specifications", + "thiserror", "tracing", ] @@ -1004,14 +1006,6 @@ dependencies = [ "windows-sys 0.52.0", ] -[[package]] -name = "mock-logger" -version = "0.2.0" -dependencies = [ - "specifications", - "tracing", -] - [[package]] name = "native-tls" version = "0.2.14" @@ -1029,6 +1023,15 @@ dependencies = [ "tempfile", ] +[[package]] +name = "no-op-logger" +version = "0.2.0" +dependencies = [ + "serde", + "specifications", + "tracing", +] + [[package]] name = "no-op-reasoner" version = "0.2.0" @@ -1169,7 +1172,7 @@ dependencies = [ "error-trace 3.0.0", "file-logger", "file-resolver", - "mock-logger", + "no-op-logger", "no-op-reasoner", "posix-reasoner", "serde_json", diff --git a/Cargo.toml b/Cargo.toml index 940473b..a53c939 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,7 +6,7 @@ members = [ "lib/reasoners/no-op", "lib/reasoners/posix", "lib/loggers/file", - "lib/loggers/mock", + "lib/loggers/no-op", "lib/resolvers/file", # Library @@ -33,8 +33,8 @@ description = "A library for using several different reasoning backends to deter [[example]] -name = "eflint" -path = "./examples/eflint.rs" +name = "eflint-json" +path = "./examples/eflint_json.rs" required-features = ["eflint-json-reasoner", "eflint-to-json", "file-logger"] [[example]] @@ -53,7 +53,7 @@ eflint-json-reasoner = { path = "./lib/reasoners/eflint-json", optional = true } no-op-reasoner = { path = "./lib/reasoners/no-op", optional = true } posix-reasoner = { path = "./lib/reasoners/posix", optional = true } file-logger = { path = "./lib/loggers/file", optional = true } -mock-logger = { path = "./lib/loggers/mock", optional = true } +no-op-logger = { path = "./lib/loggers/no-op", optional = true } file-resolver = { path = "./lib/resolvers/file", optional = true } eflint-to-json = { path = "./lib/eflint-to-json", features = ["async-tokio", "tracing"], optional = true } spec = { path = "./lib/spec", package = "specifications" } @@ -81,9 +81,9 @@ eflint-json-reasoner = ["dep:eflint-json-reasoner"] no-op-reasoner = ["dep:no-op-reasoner"] posix-reasoner = ["dep:posix-reasoner"] -loggers = ["file-logger", "mock-logger"] +loggers = ["file-logger", "no-op-logger"] file-logger = ["dep:file-logger"] -mock-logger = ["dep:mock-logger"] +no-op-logger = ["dep:no-op-logger"] resolvers = ["file-resolver"] file-resolver = ["dep:file-resolver"] diff --git a/examples/eflint.rs b/examples/eflint_json.rs similarity index 91% rename from examples/eflint.rs rename to examples/eflint_json.rs index c863944..8d57619 100644 --- a/examples/eflint.rs +++ b/examples/eflint_json.rs @@ -1,10 +1,10 @@ -// EFLINT.rs +// EFLINT JSON.rs // by Lut99 // // Created: // 10 Oct 2024, 13:54:17 // Last edited: -// 11 Oct 2024, 16:31:54 +// 17 Oct 2024, 12:07:02 // Auto updated? // Yes // @@ -18,14 +18,14 @@ use std::path::PathBuf; use clap::Parser; use console::style; -use eflint_json_reasoner::json::spec::RequestPhrases; -use eflint_json_reasoner::reasons::NoReason; use error_trace::trace; use policy_reasoner::loggers::file::FileLogger; +use policy_reasoner::reasoners::eflint_json::json::spec::RequestPhrases; use policy_reasoner::reasoners::eflint_json::reasons::EFlintSilentReasonHandler; use policy_reasoner::reasoners::eflint_json::{EFlintJsonReasonerConnector, State}; use policy_reasoner::spec::auditlogger::SessionedAuditLogger; use policy_reasoner::spec::reasonerconn::ReasonerConnector as _; +use policy_reasoner::spec::reasons::NoReason; use spec::reasonerconn::ReasonerResponse; use tracing::{Level, error, info}; @@ -92,7 +92,7 @@ async fn main() { info!("{} - v{}", env!("CARGO_BIN_NAME"), env!("CARGO_PKG_VERSION")); // Create the logger - let logger: SessionedAuditLogger = + let mut logger: SessionedAuditLogger = SessionedAuditLogger::new("test", FileLogger::new(format!("{} - v{}", env!("CARGO_BIN_NAME"), env!("CARGO_PKG_VERSION")), "./test.log")); // Decide which eflint to run @@ -181,8 +181,20 @@ async fn main() { }; // Create the reasoner - let conn = EFlintJsonReasonerConnector::::new(&args.address, EFlintSilentReasonHandler); - let verdict: ReasonerResponse = match conn.consult(State { policy: policy.phrases, state: () }, (), &logger).await { + let conn = match EFlintJsonReasonerConnector::::new_async( + &args.address, + EFlintSilentReasonHandler, + &mut logger, + ) + .await + { + Ok(conn) => conn, + Err(err) => { + error!("{}", trace!(("Failed to create eFLINT reasoner"), err)); + std::process::exit(1); + }, + }; + let verdict: ReasonerResponse = match conn.consult(State { policy: policy.phrases, state: () }, (), &mut logger).await { Ok(res) => res, Err(err) => { error!("{}", trace!(("Failed to send message to reasoner at {:?}", args.address), err)); diff --git a/examples/no_op.rs b/examples/no_op.rs index 525ea0d..6d3584b 100644 --- a/examples/no_op.rs +++ b/examples/no_op.rs @@ -4,7 +4,7 @@ // Created: // 10 Oct 2024, 16:17:21 // Last edited: -// 11 Oct 2024, 16:32:08 +// 17 Oct 2024, 12:05:45 // Auto updated? // Yes // @@ -15,12 +15,13 @@ use clap::Parser; use console::style; -use file_logger::FileLogger; +use error_trace::trace; +use policy_reasoner::loggers::file::FileLogger; use policy_reasoner::reasoners::no_op::NoOpReasonerConnector; -use policy_reasoner::spec::ReasonerConnector as _; use policy_reasoner::spec::auditlogger::SessionedAuditLogger; -use spec::reasonerconn::ReasonerResponse; -use tracing::{Level, info}; +use policy_reasoner::spec::reasonerconn::ReasonerResponse; +use policy_reasoner::spec::{AuditLogger, ReasonerConnector as _}; +use tracing::{Level, error, info}; /***** ARGUMENTS *****/ @@ -58,12 +59,16 @@ async fn main() { info!("{} - v{}", env!("CARGO_BIN_NAME"), env!("CARGO_PKG_VERSION")); // Create the logger - let logger: SessionedAuditLogger = + let mut logger: SessionedAuditLogger = SessionedAuditLogger::new("test", FileLogger::new(format!("{} - v{}", env!("CARGO_BIN_NAME"), env!("CARGO_PKG_VERSION")), "./test.log")); + if let Err(err) = logger.log_context("no-op").await { + error!("{}", trace!(("Failed to log no-op reasoner context"), err)); + std::process::exit(1); + } // Run the reasoner let conn: NoOpReasonerConnector<()> = NoOpReasonerConnector::new(); - let verdict: ReasonerResponse<()> = conn.consult((), (), &logger).await.unwrap(); + let verdict: ReasonerResponse<()> = conn.consult((), (), &mut logger).await.unwrap(); // OK, report match verdict { diff --git a/examples/posix.rs b/examples/posix.rs index 8329646..4086d39 100644 --- a/examples/posix.rs +++ b/examples/posix.rs @@ -4,7 +4,7 @@ // Created: // 11 Oct 2024, 16:32:29 // Last edited: -// 15 Oct 2024, 17:05:12 +// 17 Oct 2024, 12:00:53 // Auto updated? // Yes // @@ -25,6 +25,7 @@ use policy_reasoner::spec::auditlogger::SessionedAuditLogger; use policy_reasoner::workflow::Workflow; use posix_reasoner::config::Config; use spec::reasonerconn::ReasonerResponse; +use spec::reasons::NoReason; use tokio::fs; use tokio::io::{self, AsyncReadExt as _}; use tracing::{Level, debug, error, info}; @@ -179,12 +180,18 @@ async fn main() { let config: Config = load_config(args.config).await; // Create the logger - let logger: SessionedAuditLogger = + let mut logger: SessionedAuditLogger = SessionedAuditLogger::new("test", FileLogger::new(format!("{} - v{}", env!("CARGO_BIN_NAME"), env!("CARGO_PKG_VERSION")), "./test.log")); // Run the reasoner - let conn: PosixReasonerConnector = PosixReasonerConnector::new(); - let verdict: ReasonerResponse<()> = match conn.consult(State { workflow, config }, (), &logger).await { + let conn: PosixReasonerConnector = match PosixReasonerConnector::new_async(&mut logger).await { + Ok(conn) => conn, + Err(err) => { + error!("{}", trace!(("Failed to create the POSIX reasoner"), err)); + std::process::exit(1); + }, + }; + let verdict: ReasonerResponse = match conn.consult(State { workflow, config }, (), &mut logger).await { Ok(res) => res, Err(err) => { error!("{}", trace!(("Failed to consult the POSIX reasoner"), err)); diff --git a/lib/loggers/file/src/logger.rs b/lib/loggers/file/src/logger.rs index b396e48..6564cc0 100644 --- a/lib/loggers/file/src/logger.rs +++ b/lib/loggers/file/src/logger.rs @@ -4,7 +4,7 @@ // Created: // 10 Oct 2024, 14:16:24 // Last edited: -// 10 Oct 2024, 14:40:18 +// 17 Oct 2024, 11:26:13 // Auto updated? // Yes // @@ -21,6 +21,7 @@ use std::path::PathBuf; use enum_debug::EnumDebug as _; use serde::Serialize; use spec::auditlogger::AuditLogger; +use spec::context::Context; use spec::reasonerconn::ReasonerResponse; use tokio::fs::{File, OpenOptions}; use tokio::io::AsyncWriteExt as _; @@ -117,9 +118,12 @@ impl error::Error for Error { #[derive(Clone, Debug)] pub struct FileLogger { /// The identifier of who/what is writing. - id: String, + id: String, /// The path we log to. path: PathBuf, + /// Whether the user has already printed the context or not. + #[cfg(debug_assertions)] + logged_context: bool, } impl FileLogger { /// Constructor for the FileLogger that initializes it pointing to the given file. @@ -131,7 +135,14 @@ impl FileLogger { /// # Returns /// A new instance of self, ready for action. #[inline] - pub fn new(id: impl Into, path: impl Into) -> Self { Self { id: id.into(), path: path.into() } } + pub fn new(id: impl Into, path: impl Into) -> Self { + Self { + id: id.into(), + path: path.into(), + #[cfg(debug_assertions)] + logged_context: false, + } + } /// Writes a log statement to the logging file. /// @@ -189,9 +200,28 @@ impl FileLogger { impl AuditLogger for FileLogger { type Error = Error; + #[inline] + fn log_context<'a, C>(&'a mut self, context: &'a C) -> impl 'a + Future> + where + C: ?Sized + Context, + { + async move { + // Serialize the context first + let ctx: String = match serde_json::to_string_pretty(context) { + Ok(ctx) => ctx, + Err(err) => return Err(Error::LogStatementSerialize { kind: "LogStatement::Context".into(), err }), + }; + + // Log it + self.log::<()>(LogStatement::Context { context: Cow::Owned(ctx) }).await?; + self.logged_context = true; + Ok(()) + } + } + #[inline] fn log_response<'a, R>( - &'a self, + &'a mut self, reference: &'a str, response: &'a ReasonerResponse, raw: Option<&'a str>, @@ -200,6 +230,11 @@ impl AuditLogger for FileLogger { R: Display, { async move { + #[cfg(debug_assertions)] + if !self.logged_context { + tracing::warn!("Logging reasoner response without having logged the reasoner context; please call FileLogger::log_context() first."); + } + // Serialize the response first let res: ReasonerResponse = match response { ReasonerResponse::Success => ReasonerResponse::Success, diff --git a/lib/loggers/file/src/stmt.rs b/lib/loggers/file/src/stmt.rs index 1826089..07779b9 100644 --- a/lib/loggers/file/src/stmt.rs +++ b/lib/loggers/file/src/stmt.rs @@ -4,7 +4,7 @@ // Created: // 10 Oct 2024, 14:24:22 // Last edited: -// 10 Oct 2024, 14:32:46 +// 17 Oct 2024, 09:49:04 // Auto updated? // Yes // @@ -23,6 +23,8 @@ use spec::reasonerconn::ReasonerResponse; /// Defines the internal representation of a log statement. #[derive(Clone, Debug, Deserialize, EnumDebug, Serialize)] pub enum LogStatement<'a, T: Clone> { + /// Logging a reasoner context. + Context { context: Cow<'a, str> }, /// Logging a reasoner response. ReasonerResponse { reference: Cow<'a, str>, response: Cow<'a, ReasonerResponse>, raw: Option> }, } diff --git a/lib/loggers/mock/Cargo.toml b/lib/loggers/no-op/Cargo.toml similarity index 90% rename from lib/loggers/mock/Cargo.toml rename to lib/loggers/no-op/Cargo.toml index 8dd3863..92f1d1c 100644 --- a/lib/loggers/mock/Cargo.toml +++ b/lib/loggers/no-op/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "mock-logger" +name = "no-op-logger" description = "Implements an audit logger that doesn't audit log anything but just logs a little using `tracing`." edition = "2021" authors = [ "Bas Kloosterman", "Tim Müller" ] @@ -9,6 +9,7 @@ license.workspace = true [dependencies] +serde = "1.0.204" tracing = "0.1.40" spec = { path = "../../spec", package = "specifications" } diff --git a/lib/loggers/mock/src/lib.rs b/lib/loggers/no-op/src/lib.rs similarity index 100% rename from lib/loggers/mock/src/lib.rs rename to lib/loggers/no-op/src/lib.rs diff --git a/lib/loggers/mock/src/logger.rs b/lib/loggers/no-op/src/logger.rs similarity index 77% rename from lib/loggers/mock/src/logger.rs rename to lib/loggers/no-op/src/logger.rs index 2acc46b..1d745f7 100644 --- a/lib/loggers/mock/src/logger.rs +++ b/lib/loggers/no-op/src/logger.rs @@ -4,7 +4,7 @@ // Created: // 10 Oct 2024, 14:46:33 // Last edited: -// 10 Oct 2024, 14:47:56 +// 17 Oct 2024, 11:23:08 // Auto updated? // Yes // @@ -17,6 +17,7 @@ use std::fmt::Display; use std::future::Future; use spec::auditlogger::AuditLogger; +use spec::context::Context; use spec::reasonerconn::ReasonerResponse; @@ -38,9 +39,20 @@ impl MockLogger { impl AuditLogger for MockLogger { type Error = Infallible; + #[inline] + fn log_context<'a, C>(&'a mut self, _context: &'a C) -> impl 'a + Future> + where + C: ?Sized + Context, + { + async move { + println!("AUDIT LOG: log_context"); + Ok(()) + } + } + #[inline] fn log_response<'a, R>( - &'a self, + &'a mut self, _reference: &'a str, _response: &'a ReasonerResponse, _raw: Option<&'a str>, diff --git a/lib/reasoners/eflint-json/Cargo.toml b/lib/reasoners/eflint-json/Cargo.toml index ea92ae0..5e2a3bf 100644 --- a/lib/reasoners/eflint-json/Cargo.toml +++ b/lib/reasoners/eflint-json/Cargo.toml @@ -10,7 +10,9 @@ license.workspace = true [dependencies] reqwest = { version = "0.12.0", features = ["json"] } +serde = { version = "1.0.204", features = ["derive"] } serde_json = "1.0.120" +thiserror = "1.0.61" tracing = "0.1.40" eflint-json = { git = "https://gitlab.com/eflint/json-spec-rs", branch = "incorrect-is-invariant" } diff --git a/lib/reasoners/eflint-json/src/reasonerconn.rs b/lib/reasoners/eflint-json/src/reasonerconn.rs index 7a2bc75..e01f114 100644 --- a/lib/reasoners/eflint-json/src/reasonerconn.rs +++ b/lib/reasoners/eflint-json/src/reasonerconn.rs @@ -4,7 +4,7 @@ // Created: // 09 Oct 2024, 15:52:06 // Last edited: -// 11 Oct 2024, 16:56:24 +// 17 Oct 2024, 12:12:54 // Auto updated? // Yes // @@ -13,16 +13,17 @@ // use std::collections::HashMap; -use std::error; -use std::fmt::{Display, Formatter, Result as FResult}; +use std::fmt::Display; use std::future::Future; use std::marker::PhantomData; use eflint_json::spec::auxillary::Version; use eflint_json::spec::{Phrase, PhraseResult, Request, RequestCommon, RequestPhrases, ResponsePhrases}; use error_trace::{ErrorTrace as _, Trace}; +use serde::{Deserialize, Serialize}; use spec::auditlogger::{AuditLogger, SessionedAuditLogger}; use spec::reasonerconn::{ReasonerConnector, ReasonerResponse}; +use thiserror::Error; use tracing::{Level, debug, span}; use crate::reasons::ReasonHandler; @@ -31,77 +32,79 @@ use crate::spec::EFlintable; /***** ERRORS *****/ /// Defines the errors returned by the [`EFlintJsonReasonerConnectorector`]. -#[derive(Debug)] +#[derive(Debug, Error)] pub enum Error { + /// Failed to log the context of the reasoner. + #[error("Failed to log the reasoner's context to {to}")] + LogContext { + to: &'static str, + #[source] + err: Trace, + }, /// Failed to log the reasoner's response to the given logger. - LogResponse { to: &'static str, err: Trace }, + #[error("Failed to log the reasoner's response to {to}")] + LogResponse { + to: &'static str, + #[source] + err: Trace, + }, /// Failed to receive a [`ResponsePhrases`] to the remote reasoner (as raw). - ReasonerResponse { addr: String, err: reqwest::Error }, + #[error("Failed to fetch reply from remote reasoner at {addr:?}")] + ReasonerResponse { + addr: String, + #[source] + err: reqwest::Error, + }, /// Failed to send a [`RequestPhrases`] to the remote reasoner. - ReasonerRequest { addr: String, err: reqwest::Error }, + #[error("Failed to set PhrasesRequest to reasoner at {addr:?}")] + ReasonerRequest { + addr: String, + #[source] + err: reqwest::Error, + }, /// Failed to extract the reasons for failure (i.e., violations) from a parsed [`ResponsePhrases`] object. - ResponseExtractReasons { addr: String, raw: String, err: R }, + #[error("Failed to extract reasons (i.e., violations) from the response of reasoner at {:?}\n\nParsed response:\n{}\n{}\n{}\n\n", + addr, + (0..80).map(|_| '-').collect::(), + raw, + (0..80).map(|_| '-').collect::())] + ResponseExtractReasons { + addr: String, + raw: String, + #[source] + err: R, + }, /// The query returned in the response was of an illegal ending type. - ResponseIllegalQuery { addr: String, raw: String }, - /// Failed to parse the response of the reasoner as a valid [`ResponsePhrases`] object. - ResponseParse { addr: String, raw: String, err: serde_json::Error }, - /// Failed to serialize the state to eFLINT. - StateToEFlint { err: S }, - /// Failed ot serialize the question to eFLINT. - QuestionToEFlint { err: Q }, -} -impl Display for Error { - #[inline] - fn fmt(&self, f: &mut Formatter<'_>) -> FResult { - use Error::*; - match self { - LogResponse { to, .. } => write!(f, "Failed to log the reasoner's response to {to}"), - ReasonerResponse { addr, .. } => write!(f, "Failed to fetch reply from remote reasoner at {addr:?}"), - ReasonerRequest { addr, .. } => write!(f, "Failed to set PhrasesRequest to reasoner at {addr:?}"), - ResponseExtractReasons { addr, raw, .. } => write!( - f, - "Failed to extract reasons (i.e., violations) from the response of reasoner at {:?}\n\nParsed response:\n{}\n{}\n{}\n\n", + #[error("Reasoner at {:?} returned result of instance query as last state change; this is unsupported!\n\nParsed response:\n{}\n{}\n{}\n\n", addr, (0..80).map(|_| '-').collect::(), raw, - (0..80).map(|_| '-').collect::() - ), - ResponseIllegalQuery { addr, raw } => write!( - f, - "Reasoner at {:?} returned result of instance query as last state change; this is unsupported!\n\nParsed response:\n{}\n{}\n{}\n\n", - addr, - (0..80).map(|_| '-').collect::(), - raw, - (0..80).map(|_| '-').collect::() - ), - ResponseParse { addr, raw, .. } => write!( - f, - "Failed to parse response from reasoner at {:?}\n\nRaw response:\n{}\n{}\n{}\n\n", + (0..80).map(|_| '-').collect::())] + ResponseIllegalQuery { addr: String, raw: String }, + /// Failed to parse the response of the reasoner as a valid [`ResponsePhrases`] object. + #[error("Failed to parse response from reasoner at {:?}\n\nRaw response:\n{}\n{}\n{}\n\n", addr, (0..80).map(|_| '-').collect::(), raw, - (0..80).map(|_| '-').collect::() - ), - StateToEFlint { .. } => write!(f, "Failed to serialize given state to eFLINT"), - QuestionToEFlint { .. } => write!(f, "Failed to serialize given question to eFLINT"), - } - } -} -impl error::Error for Error { - #[inline] - fn source(&self) -> Option<&(dyn error::Error + 'static)> { - use Error::*; - match self { - LogResponse { err, .. } => Some(err), - ReasonerResponse { err, .. } => Some(err), - ReasonerRequest { err, .. } => Some(err), - ResponseExtractReasons { err, .. } => Some(err), - ResponseIllegalQuery { .. } => None, - ResponseParse { err, .. } => Some(err), - StateToEFlint { err } => Some(err), - QuestionToEFlint { err } => Some(err), - } - } + (0..80).map(|_| '-').collect::())] + ResponseParse { + addr: String, + raw: String, + #[source] + err: serde_json::Error, + }, + /// Failed to serialize the state to eFLINT. + #[error("Failed to serialize given state to eFLINT")] + StateToEFlint { + #[source] + err: S, + }, + /// Failed ot serialize the question to eFLINT. + #[error("Failed to serialize given question to eFLINT")] + QuestionToEFlint { + #[source] + err: Q, + }, } @@ -109,6 +112,17 @@ impl &str { "eflint-json" } +} + /// Defines the eFLINT reasoner state to submit to it. #[derive(Clone, Debug)] pub struct State { @@ -139,15 +153,37 @@ pub struct EFlintJsonReasonerConnector { impl EFlintJsonReasonerConnector { /// Constructor for the EFlintJsonReasonerConnector. /// + /// This constructor logs asynchronously. + /// /// # Arguments /// - `addr`: The address of the remote reasoner that we will connect to. /// - `handler`: The [`ReasonHandler`] that determines how errors from the reasoners are propagated to the user. + /// - `logger`: A logger to write this reasoner's context to. /// /// # Returns /// A new instance of Self, ready for reasoning. + /// + /// # Errors + /// This function may error if it failed to log to the given `logger`. #[inline] - pub fn new(addr: impl Into, handler: R) -> Self { - Self { addr: addr.into(), reason_handler: handler, _state: PhantomData, _question: PhantomData } + pub fn new_async<'l, L: AuditLogger>( + addr: impl 'l + Into, + handler: R, + logger: &'l mut L, + ) -> impl 'l + Future>> + where + R: 'l + ReasonHandler, + R::Reason: Display, + R::Error: 'static, + S: EFlintable, + S::Error: 'static, + Q: EFlintable, + Q::Error: 'static, + { + async move { + logger.log_context("").await.map_err(|err| Error::LogContext { to: std::any::type_name::(), err: err.freeze() })?; + Ok(Self { addr: addr.into(), reason_handler: handler, _state: PhantomData, _question: PhantomData }) + } } } impl ReasonerConnector for EFlintJsonReasonerConnector @@ -169,7 +205,7 @@ where &self, state: Self::State, question: Self::Question, - logger: &SessionedAuditLogger, + logger: &mut SessionedAuditLogger, ) -> impl Future, Self::Error>> where L: AuditLogger, diff --git a/lib/reasoners/eflint-json/src/reasons.rs b/lib/reasoners/eflint-json/src/reasons.rs index 5c11b79..7c6d17e 100644 --- a/lib/reasoners/eflint-json/src/reasons.rs +++ b/lib/reasoners/eflint-json/src/reasons.rs @@ -4,7 +4,7 @@ // Created: // 09 Oct 2024, 16:37:52 // Last edited: -// 11 Oct 2024, 14:04:46 +// 17 Oct 2024, 09:55:31 // Auto updated? // Yes // @@ -14,82 +14,10 @@ use std::convert::Infallible; use std::error::Error; -use std::fmt::{Debug, Display, Formatter, Result as FResult}; -use std::ops::{Deref, DerefMut}; +use std::fmt::Debug; use eflint_json::spec::ResponsePhrases; - - -/***** AUXILLARY *****/ -/// Represents that no reason is used. -#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] -pub struct NoReason; -impl Display for NoReason { - #[inline] - fn fmt(&self, f: &mut Formatter<'_>) -> FResult { write!(f, "") } -} - -/// Represents that multiple reasons can be given. -#[derive(Clone, Debug, Eq, Hash, PartialEq)] -pub struct ManyReason(Vec); -impl Default for ManyReason { - #[inline] - fn default() -> Self { Self::new() } -} -impl ManyReason { - /// Constructor for the ManyReason that initializes it as empty. - /// - /// # Returns - /// A new ManyReason that doesn't have any reasons embedded in it yet. - #[inline] - pub fn new() -> Self { Self(Vec::new()) } - - /// Constructor for the ManyReason that initializes it as empty but with space allocated for - /// a certain number of reasons. - /// - /// # Arguments - /// - `capacity`: The (minimum) number of reasons to allocate space for. - /// - /// # Returns - /// A new ManyReason that doesn't have any reasons embedded in it yet but space for at least `capacity` reasons. - #[inline] - pub fn with_capacity(capacity: usize) -> Self { Self(Vec::with_capacity(capacity)) } -} -impl Display for ManyReason { - #[inline] - fn fmt(&self, f: &mut Formatter<'_>) -> FResult { - for i in 0..self.0.len() { - if i > 0 && i < self.0.len() - 1 { - write!(f, ", ")?; - } else if i == self.0.len() { - write!(f, " and ")?; - } - write!(f, "{}", self.0[i])?; - } - Ok(()) - } -} -impl Deref for ManyReason { - type Target = Vec; - - #[inline] - fn deref(&self) -> &Self::Target { &self.0 } -} -impl DerefMut for ManyReason { - #[inline] - fn deref_mut(&mut self) -> &mut Self::Target { &mut self.0 } -} -impl FromIterator for ManyReason { - #[inline] - fn from_iter>(iter: T) -> Self { Self(iter.into_iter().collect()) } -} -impl> From for ManyReason { - #[inline] - fn from(value: I) -> Self { Self::from_iter(value) } -} - - - +use spec::reasons::{ManyReason, NoReason}; /***** LIBRARY *****/ diff --git a/lib/reasoners/no-op/src/reasonerconn.rs b/lib/reasoners/no-op/src/reasonerconn.rs index a885e23..91d49c3 100644 --- a/lib/reasoners/no-op/src/reasonerconn.rs +++ b/lib/reasoners/no-op/src/reasonerconn.rs @@ -4,7 +4,7 @@ // Created: // 10 Oct 2024, 16:21:09 // Last edited: -// 11 Oct 2024, 16:56:18 +// 17 Oct 2024, 09:50:23 // Auto updated? // Yes // @@ -83,7 +83,7 @@ impl ReasonerConnector for NoOpReasonerConnector { &self, _state: Self::State, _question: Self::Question, - logger: &SessionedAuditLogger, + logger: &mut SessionedAuditLogger, ) -> impl Future, Self::Error>> where L: AuditLogger, diff --git a/lib/reasoners/posix/src/reasonerconn.rs b/lib/reasoners/posix/src/reasonerconn.rs index b0b5c2d..3be85cb 100644 --- a/lib/reasoners/posix/src/reasonerconn.rs +++ b/lib/reasoners/posix/src/reasonerconn.rs @@ -4,7 +4,7 @@ // Created: // 11 Oct 2024, 16:54:51 // Last edited: -// 15 Oct 2024, 17:20:44 +// 17 Oct 2024, 12:10:18 // Auto updated? // Yes // @@ -20,9 +20,11 @@ use std::ops::BitOr; use std::os::unix::fs::{MetadataExt as _, PermissionsExt as _}; use std::path::{Path, PathBuf}; +use error_trace::{ErrorTrace as _, Trace}; use serde::Deserialize; use spec::auditlogger::{AuditLogger, SessionedAuditLogger}; use spec::reasonerconn::{ReasonerConnector, ReasonerResponse}; +use spec::reasons::NoReason; use thiserror::Error; use tokio::fs; use tracing::{Level, debug, info, span}; @@ -43,6 +45,20 @@ pub enum Error { #[source] err: std::io::Error, }, + /// Failed to log the context of the reasoner. + #[error("Failed to log the reasoner's context to {to}")] + LogContext { + to: &'static str, + #[source] + err: Trace, + }, + /// Failed to log the reasoner's response to the given logger. + #[error("Failed to log the reasoner's response to {to}")] + LogResponse { + to: &'static str, + #[source] + err: Trace, + }, /// The dataset was unknown to us. #[error("Unknown dataset {data:?}")] UnknownDataset { data: String }, @@ -197,13 +213,27 @@ pub struct State { /// The POSIX reasoner connector. This connector is used to validate workflows based on POSIX file permissions. pub struct PosixReasonerConnector; impl PosixReasonerConnector { + /// Constructor for the PosixReasonerConnector. + /// + /// This constructor logs asynchronously. + /// + /// # Arguments + /// - `logger`: A logger to write this reasoner's context to. + /// + /// # Errors + /// This function may error if it failed to log to the given `logger`. #[inline] - pub const fn new() -> Self { PosixReasonerConnector } + pub fn new_async<'l, L: AuditLogger>(logger: &'l mut L) -> impl 'l + Future> { + async move { + logger.log_context("posix").await.map_err(|err| Error::LogContext { to: std::any::type_name::(), err: err.freeze() })?; + Ok(Self) + } + } } impl ReasonerConnector for PosixReasonerConnector { type Error = Error; type Question = (); - type Reason = (); + type Reason = NoReason; type State = State; #[inline] @@ -211,7 +241,7 @@ impl ReasonerConnector for PosixReasonerConnector { &self, state: Self::State, _question: Self::Question, - logger: &SessionedAuditLogger, + logger: &mut SessionedAuditLogger, ) -> impl Future, Self::Error>> where L: AuditLogger, @@ -239,11 +269,19 @@ impl ReasonerConnector for PosixReasonerConnector { // Now check the policy! if !satisfies_posix_permissions(&policy.path, policy.user_map.get(&location.id), permission).await? { - return Ok(ReasonerResponse::Violated(())); + logger + .log_response(&ReasonerResponse::Violated(NoReason), Some("false")) + .await + .map_err(|err| Error::LogResponse { to: std::any::type_name::>(), err: err.freeze() })?; + return Ok(ReasonerResponse::Violated(NoReason)); } } // If none of them failed prematurely, then we're done + logger + .log_response(&ReasonerResponse::::Success, Some("true")) + .await + .map_err(|err| Error::LogResponse { to: std::any::type_name::>(), err: err.freeze() })?; Ok(ReasonerResponse::Success) } } diff --git a/lib/spec/src/auditlogger.rs b/lib/spec/src/auditlogger.rs index 2d9b7de..1467502 100644 --- a/lib/spec/src/auditlogger.rs +++ b/lib/spec/src/auditlogger.rs @@ -4,7 +4,7 @@ // Created: // 09 Oct 2024, 13:38:41 // Last edited: -// 10 Oct 2024, 14:39:33 +// 17 Oct 2024, 11:22:53 // Auto updated? // Yes // @@ -17,6 +17,7 @@ use std::error::Error; use std::fmt::Display; use std::future::Future; +use crate::context::Context; use crate::reasonerconn::ReasonerResponse; @@ -54,21 +55,28 @@ impl SessionedAuditLogger { /// - `response`: The [`ReasonerResponse`] that we're logging. /// - `raw`: The raw response produced by the reasoner, if applicable. pub fn log_response<'a, R>( - &'a self, + &'a mut self, response: &'a ReasonerResponse, raw: Option<&'a str>, ) -> impl 'a + Future::Error>> where R: Display, { - L::log_response(&self.logger, &self.reference, response, raw) + L::log_response(&mut self.logger, &self.reference, response, raw) } } impl AuditLogger for SessionedAuditLogger { type Error = L::Error; + fn log_context<'a, C>(&'a mut self, context: &'a C) -> impl 'a + Future> + where + C: ?Sized + Context, + { + L::log_context(&mut self.logger, context) + } + fn log_response<'a, R>( - &'a self, + &'a mut self, reference: &'a str, response: &'a ReasonerResponse, raw: Option<&'a str>, @@ -76,7 +84,7 @@ impl AuditLogger for SessionedAuditLogger { where R: Display, { - L::log_response(&self.logger, reference, response, raw) + L::log_response(&mut self.logger, reference, response, raw) } } @@ -91,6 +99,14 @@ pub trait AuditLogger { type Error: Error; + /// Logs the context of a reasoner at startup. + /// + /// # Arguments + /// - `context`: Something [`Serialize`]able that we want to write at startup. + fn log_context<'a, C>(&'a mut self, context: &'a C) -> impl 'a + Future> + where + C: ?Sized + Context; + /// Log the response of a reasoner. /// /// # Arguments @@ -98,7 +114,7 @@ pub trait AuditLogger { /// - `response`: The [`ReasonerResponse`] that we're logging. /// - `raw`: The raw response produced by the reasoner, if applicable. fn log_response<'a, R>( - &'a self, + &'a mut self, reference: &'a str, response: &'a ReasonerResponse, raw: Option<&'a str>, diff --git a/lib/spec/src/context.rs b/lib/spec/src/context.rs new file mode 100644 index 0000000..e5bee6f --- /dev/null +++ b/lib/spec/src/context.rs @@ -0,0 +1,49 @@ +// CONTEXT.rs +// by Lut99 +// +// Created: +// 17 Oct 2024, 11:17:05 +// Last edited: +// 17 Oct 2024, 11:20:02 +// Auto updated? +// Yes +// +// Description: +//! Defines how the context of a reasoner looks like. +// + +use std::borrow::Cow; + +pub use serde::Serialize; + + +/***** LIBRARY *****/ +/// Defines the context that is logged and deterministically determines reasoner behaviour. +/// +/// In other words, same context == same result given a state. +pub trait Context: Serialize { + /// Returns the (unique!) identifier of this reasoner. + fn kind(&self) -> &str; +} + +// Default impls for strings +impl Context for str { + #[inline] + fn kind(&self) -> &str { self } +} +impl<'a> Context for &'a str { + #[inline] + fn kind(&self) -> &str { self } +} +impl<'a> Context for &'a mut str { + #[inline] + fn kind(&self) -> &str { self } +} +impl<'a> Context for Cow<'a, str> { + #[inline] + fn kind(&self) -> &str { self } +} +impl Context for String { + #[inline] + fn kind(&self) -> &str { self.as_str() } +} diff --git a/lib/spec/src/lib.rs b/lib/spec/src/lib.rs index 9a2dc19..bc469fe 100644 --- a/lib/spec/src/lib.rs +++ b/lib/spec/src/lib.rs @@ -4,7 +4,7 @@ // Created: // 09 Oct 2024, 13:37:15 // Last edited: -// 10 Oct 2024, 15:01:20 +// 17 Oct 2024, 11:16:55 // Auto updated? // Yes // @@ -14,7 +14,9 @@ // Declare the modules pub mod auditlogger; +pub mod context; pub mod reasonerconn; +pub mod reasons; pub mod stateresolver; // Bring some of it into the namespace. diff --git a/lib/spec/src/reasonerconn.rs b/lib/spec/src/reasonerconn.rs index 9361277..23ab1ba 100644 --- a/lib/spec/src/reasonerconn.rs +++ b/lib/spec/src/reasonerconn.rs @@ -4,7 +4,7 @@ // Created: // 09 Oct 2024, 13:35:41 // Last edited: -// 10 Oct 2024, 16:07:17 +// 17 Oct 2024, 09:50:17 // Auto updated? // Yes // @@ -66,7 +66,7 @@ pub trait ReasonerConnector { &self, state: Self::State, question: Self::Question, - logger: &SessionedAuditLogger, + logger: &mut SessionedAuditLogger, ) -> impl Future, Self::Error>> where L: AuditLogger; diff --git a/lib/spec/src/reasons.rs b/lib/spec/src/reasons.rs new file mode 100644 index 0000000..bf8ae92 --- /dev/null +++ b/lib/spec/src/reasons.rs @@ -0,0 +1,85 @@ +// REASONS.rs +// by Lut99 +// +// Created: +// 17 Oct 2024, 09:53:49 +// Last edited: +// 17 Oct 2024, 09:54:09 +// Auto updated? +// Yes +// +// Description: +//! Defines reasons for reasoner connectors calling a policy violated. +// + +use std::fmt::{Display, Formatter, Result as FResult}; +use std::ops::{Deref, DerefMut}; + + +/***** LIBRARY ****/ +/// Represents that no reason is used. +#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] +pub struct NoReason; +impl Display for NoReason { + #[inline] + fn fmt(&self, f: &mut Formatter<'_>) -> FResult { write!(f, "") } +} + +/// Represents that multiple reasons can be given. +#[derive(Clone, Debug, Eq, Hash, PartialEq)] +pub struct ManyReason(Vec); +impl Default for ManyReason { + #[inline] + fn default() -> Self { Self::new() } +} +impl ManyReason { + /// Constructor for the ManyReason that initializes it as empty. + /// + /// # Returns + /// A new ManyReason that doesn't have any reasons embedded in it yet. + #[inline] + pub fn new() -> Self { Self(Vec::new()) } + + /// Constructor for the ManyReason that initializes it as empty but with space allocated for + /// a certain number of reasons. + /// + /// # Arguments + /// - `capacity`: The (minimum) number of reasons to allocate space for. + /// + /// # Returns + /// A new ManyReason that doesn't have any reasons embedded in it yet but space for at least `capacity` reasons. + #[inline] + pub fn with_capacity(capacity: usize) -> Self { Self(Vec::with_capacity(capacity)) } +} +impl Display for ManyReason { + #[inline] + fn fmt(&self, f: &mut Formatter<'_>) -> FResult { + for i in 0..self.0.len() { + if i > 0 && i < self.0.len() - 1 { + write!(f, ", ")?; + } else if i == self.0.len() { + write!(f, " and ")?; + } + write!(f, "{}", self.0[i])?; + } + Ok(()) + } +} +impl Deref for ManyReason { + type Target = Vec; + + #[inline] + fn deref(&self) -> &Self::Target { &self.0 } +} +impl DerefMut for ManyReason { + #[inline] + fn deref_mut(&mut self) -> &mut Self::Target { &mut self.0 } +} +impl FromIterator for ManyReason { + #[inline] + fn from_iter>(iter: T) -> Self { Self(iter.into_iter().collect()) } +} +impl> From for ManyReason { + #[inline] + fn from(value: I) -> Self { Self::from_iter(value) } +} diff --git a/src/lib.rs b/src/lib.rs index 7a9c0ab..332cfb2 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -4,7 +4,7 @@ // Created: // 08 Oct 2024, 16:13:30 // Last edited: -// 15 Oct 2024, 17:00:42 +// 17 Oct 2024, 12:02:50 // Auto updated? // Yes // @@ -26,8 +26,8 @@ pub mod reasoners { pub mod loggers { #[cfg(feature = "file-logger")] pub use file_logger as file; - #[cfg(feature = "mock-logger")] - pub use mock_logger as mock; + #[cfg(feature = "no-op-logger")] + pub use no_op_logger as no_op; } /// Contains any state resolvers. pub mod resolvers { From 24ec852b702097c042b926805cb708e22f883479 Mon Sep 17 00:00:00 2001 From: Lut99 Date: Thu, 17 Oct 2024 14:29:32 +0200 Subject: [PATCH 05/82] Added logging of the reasoner input --- Cargo.lock | 2 + lib/loggers/file/src/logger.rs | 50 ++++++++++++---- lib/loggers/file/src/stmt.rs | 12 ++-- lib/loggers/no-op/src/logger.rs | 20 ++++++- lib/reasoners/eflint-json/src/reasonerconn.rs | 27 +++++++-- lib/reasoners/no-op/Cargo.toml | 2 + lib/reasoners/no-op/src/reasonerconn.rs | 57 ++++++++++--------- lib/reasoners/posix/Cargo.toml | 2 +- lib/reasoners/posix/src/reasonerconn.rs | 19 ++++++- lib/spec/src/auditlogger.rs | 36 +++++++++++- 10 files changed, 171 insertions(+), 56 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 810d5f4..2bfd813 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1037,7 +1037,9 @@ name = "no-op-reasoner" version = "0.2.0" dependencies = [ "error-trace 3.1.0", + "serde", "specifications", + "thiserror", "tracing", ] diff --git a/lib/loggers/file/src/logger.rs b/lib/loggers/file/src/logger.rs index 6564cc0..6241dbf 100644 --- a/lib/loggers/file/src/logger.rs +++ b/lib/loggers/file/src/logger.rs @@ -4,7 +4,7 @@ // Created: // 10 Oct 2024, 14:16:24 // Last edited: -// 17 Oct 2024, 11:26:13 +// 17 Oct 2024, 13:17:01 // Auto updated? // Yes // @@ -20,6 +20,7 @@ use std::path::PathBuf; use enum_debug::EnumDebug as _; use serde::Serialize; +use serde_json::Value; use spec::auditlogger::AuditLogger; use spec::context::Context; use spec::reasonerconn::ReasonerResponse; @@ -151,7 +152,7 @@ impl FileLogger { /// /// # Errors /// This function errors if we failed to perform the logging completely (i.e., either write or flush). - async fn log(&self, stmt: LogStatement<'_, T>) -> Result<(), Error> { + async fn log(&self, stmt: LogStatement<'_>) -> Result<(), Error> { // Step 1: Open the log file let mut handle: File = if !self.path.exists() { debug!("Creating new log file at '{}'...", self.path.display()); @@ -207,13 +208,13 @@ impl AuditLogger for FileLogger { { async move { // Serialize the context first - let ctx: String = match serde_json::to_string_pretty(context) { + let context: Value = match serde_json::to_value(context) { Ok(ctx) => ctx, Err(err) => return Err(Error::LogStatementSerialize { kind: "LogStatement::Context".into(), err }), }; // Log it - self.log::<()>(LogStatement::Context { context: Cow::Owned(ctx) }).await?; + self.log(LogStatement::Context { context }).await?; self.logged_context = true; Ok(()) } @@ -236,18 +237,43 @@ impl AuditLogger for FileLogger { } // Serialize the response first - let res: ReasonerResponse = match response { + let response: Value = match serde_json::to_value(&match response { ReasonerResponse::Success => ReasonerResponse::Success, - ReasonerResponse::Violated(r) => ReasonerResponse::Violated(r.to_string()), + ReasonerResponse::Violated(reasons) => ReasonerResponse::Violated(reasons.to_string()), + }) { + Ok(res) => res, + Err(err) => return Err(Error::LogStatementSerialize { kind: "LogStatement::ReasonerResponse".into(), err }), }; // Log it - self.log(LogStatement::ReasonerResponse { - reference: Cow::Borrowed(reference), - response: Cow::Borrowed(&res), - raw: raw.map(Cow::Borrowed), - }) - .await + self.log(LogStatement::ReasonerResponse { reference: Cow::Borrowed(reference), response, raw: raw.map(Cow::Borrowed) }).await + } + } + + #[inline] + fn log_question<'a, S, Q>(&'a mut self, reference: &'a str, state: &'a S, question: &'a Q) -> impl 'a + Future> + where + S: Serialize, + Q: Serialize, + { + async move { + #[cfg(debug_assertions)] + if !self.logged_context { + tracing::warn!("Logging reasoner response without having logged the reasoner context; please call FileLogger::log_context() first."); + } + + // Serialize the state & question first + let state: Value = match serde_json::to_value(state) { + Ok(res) => res, + Err(err) => return Err(Error::LogStatementSerialize { kind: "LogStatement::ReasonerConsult".into(), err }), + }; + let question: Value = match serde_json::to_value(question) { + Ok(res) => res, + Err(err) => return Err(Error::LogStatementSerialize { kind: "LogStatement::ReasonerConsult".into(), err }), + }; + + // Log it + self.log(LogStatement::ReasonerConsult { reference: Cow::Borrowed(reference), state, question }).await } } } diff --git a/lib/loggers/file/src/stmt.rs b/lib/loggers/file/src/stmt.rs index 07779b9..b7b0106 100644 --- a/lib/loggers/file/src/stmt.rs +++ b/lib/loggers/file/src/stmt.rs @@ -4,7 +4,7 @@ // Created: // 10 Oct 2024, 14:24:22 // Last edited: -// 17 Oct 2024, 09:49:04 +// 17 Oct 2024, 13:15:51 // Auto updated? // Yes // @@ -16,15 +16,17 @@ use std::borrow::Cow; use enum_debug::EnumDebug; use serde::{Deserialize, Serialize}; -use spec::reasonerconn::ReasonerResponse; +use serde_json::Value; /***** LIBRARY *****/ /// Defines the internal representation of a log statement. #[derive(Clone, Debug, Deserialize, EnumDebug, Serialize)] -pub enum LogStatement<'a, T: Clone> { +pub enum LogStatement<'a> { /// Logging a reasoner context. - Context { context: Cow<'a, str> }, + Context { context: Value }, + /// Logging a question to a reasoner. + ReasonerConsult { reference: Cow<'a, str>, state: Value, question: Value }, /// Logging a reasoner response. - ReasonerResponse { reference: Cow<'a, str>, response: Cow<'a, ReasonerResponse>, raw: Option> }, + ReasonerResponse { reference: Cow<'a, str>, response: Value, raw: Option> }, } diff --git a/lib/loggers/no-op/src/logger.rs b/lib/loggers/no-op/src/logger.rs index 1d745f7..a579c34 100644 --- a/lib/loggers/no-op/src/logger.rs +++ b/lib/loggers/no-op/src/logger.rs @@ -4,7 +4,7 @@ // Created: // 10 Oct 2024, 14:46:33 // Last edited: -// 17 Oct 2024, 11:23:08 +// 17 Oct 2024, 13:14:34 // Auto updated? // Yes // @@ -16,6 +16,7 @@ use std::convert::Infallible; use std::fmt::Display; use std::future::Future; +use serde::Serialize; use spec::auditlogger::AuditLogger; use spec::context::Context; use spec::reasonerconn::ReasonerResponse; @@ -65,4 +66,21 @@ impl AuditLogger for MockLogger { Ok(()) } } + + #[inline] + fn log_question<'a, S, Q>( + &'a mut self, + _reference: &'a str, + _state: &'a S, + _question: &'a Q, + ) -> impl 'a + Future> + where + S: Serialize, + Q: Serialize, + { + async move { + println!("AUDIT LOG: log_question"); + Ok(()) + } + } } diff --git a/lib/reasoners/eflint-json/src/reasonerconn.rs b/lib/reasoners/eflint-json/src/reasonerconn.rs index e01f114..211b1c4 100644 --- a/lib/reasoners/eflint-json/src/reasonerconn.rs +++ b/lib/reasoners/eflint-json/src/reasonerconn.rs @@ -4,7 +4,7 @@ // Created: // 09 Oct 2024, 15:52:06 // Last edited: -// 17 Oct 2024, 12:12:54 +// 17 Oct 2024, 13:59:28 // Auto updated? // Yes // @@ -48,6 +48,13 @@ pub enum Error { #[source] err: Trace, }, + /// Failed to log the question to the given logger. + #[error("Failed to log the question to {to}")] + LogQuestion { + to: &'static str, + #[source] + err: Trace, + }, /// Failed to receive a [`ResponsePhrases`] to the remote reasoner (as raw). #[error("Failed to fetch reply from remote reasoner at {addr:?}")] ReasonerResponse { @@ -124,7 +131,7 @@ impl spec::context::Context for Context { } /// Defines the eFLINT reasoner state to submit to it. -#[derive(Clone, Debug)] +#[derive(Clone, Debug, Deserialize, Serialize)] pub struct State { /// The policy used. pub policy: Vec, @@ -181,8 +188,12 @@ impl EFlintJsonReasonerConnector { Q::Error: 'static, { async move { - logger.log_context("").await.map_err(|err| Error::LogContext { to: std::any::type_name::(), err: err.freeze() })?; - Ok(Self { addr: addr.into(), reason_handler: handler, _state: PhantomData, _question: PhantomData }) + let addr: String = addr.into(); + logger + .log_context(&Context { addr: addr.clone() }) + .await + .map_err(|err| Error::LogContext { to: std::any::type_name::(), err: err.freeze() })?; + Ok(Self { addr, reason_handler: handler, _state: PhantomData, _question: PhantomData }) } } } @@ -191,9 +202,9 @@ where R: ReasonHandler, R::Reason: Display, R::Error: 'static, - S: EFlintable, + S: EFlintable + Serialize, S::Error: 'static, - Q: EFlintable, + Q: EFlintable + Serialize, Q::Error: 'static, { type Error = Error; @@ -213,6 +224,10 @@ where async move { // NOTE: Using `#[instrument]` adds some unnecessary trait bounds on `S` and such. let _span = span!(Level::INFO, "EFlintJsonReasonerConnector::consult", reference = logger.reference()).entered(); + logger + .log_question(&state, &question) + .await + .map_err(|err| Error::LogQuestion { to: std::any::type_name::>(), err: err.freeze() })?; // Build the full policy debug!("Building full policy..."); diff --git a/lib/reasoners/no-op/Cargo.toml b/lib/reasoners/no-op/Cargo.toml index 53317ac..a1c083c 100644 --- a/lib/reasoners/no-op/Cargo.toml +++ b/lib/reasoners/no-op/Cargo.toml @@ -15,6 +15,8 @@ license.workspace = true [dependencies] +serde = "1.0.204" +thiserror = "1.0.61" tracing = "0.1.40" error-trace = { git = "https://github.com/Lut99/error-trace-rs", tag = "v3.1.0" } diff --git a/lib/reasoners/no-op/src/reasonerconn.rs b/lib/reasoners/no-op/src/reasonerconn.rs index 91d49c3..922aa6c 100644 --- a/lib/reasoners/no-op/src/reasonerconn.rs +++ b/lib/reasoners/no-op/src/reasonerconn.rs @@ -4,7 +4,7 @@ // Created: // 10 Oct 2024, 16:21:09 // Last edited: -// 17 Oct 2024, 09:50:23 +// 17 Oct 2024, 13:56:10 // Auto updated? // Yes // @@ -12,41 +12,35 @@ //! // -use std::error; -use std::fmt::{Display, Formatter, Result as FResult}; use std::future::Future; use std::marker::PhantomData; use error_trace::{ErrorTrace as _, Trace}; +use serde::Serialize; use spec::auditlogger::SessionedAuditLogger; use spec::reasonerconn::ReasonerResponse; use spec::{AuditLogger, ReasonerConnector}; +use thiserror::Error; use tracing::{Level, debug, span}; /***** ERRORS *****/ -#[derive(Debug)] +#[derive(Debug, Error)] pub enum Error { /// Failed to log the reasoner's response to the given logger. - LogResponse { to: &'static str, err: Trace }, -} -impl Display for Error { - #[inline] - fn fmt(&self, f: &mut Formatter<'_>) -> FResult { - use Error::*; - match self { - LogResponse { to, .. } => write!(f, "Failed to log the reasoner's response to {to}"), - } - } -} -impl error::Error for Error { - #[inline] - fn source(&self) -> Option<&(dyn error::Error + 'static)> { - use Error::*; - match self { - LogResponse { err, .. } => Some(err), - } - } + #[error("Failed to log the reasoner's response to {to}")] + LogResponse { + to: &'static str, + #[source] + err: Trace, + }, + /// Failed to log the question to the given logger. + #[error("Failed to log the question to {to}")] + LogQuestion { + to: &'static str, + #[source] + err: Trace, + }, } @@ -73,7 +67,10 @@ impl NoOpReasonerConnector { #[inline] pub fn new() -> Self { Self { _question: PhantomData } } } -impl ReasonerConnector for NoOpReasonerConnector { +impl ReasonerConnector for NoOpReasonerConnector +where + Q: Serialize, +{ type Error = Error; type Question = Q; type Reason = (); @@ -81,8 +78,8 @@ impl ReasonerConnector for NoOpReasonerConnector { fn consult( &self, - _state: Self::State, - _question: Self::Question, + state: Self::State, + question: Self::Question, logger: &mut SessionedAuditLogger, ) -> impl Future, Self::Error>> where @@ -91,9 +88,15 @@ impl ReasonerConnector for NoOpReasonerConnector { async move { // NOTE: Using `#[instrument]` adds some unnecessary trait bounds on `S` and such. let _span = span!(Level::INFO, "NoOpReasonerConnector::consult", reference = logger.reference()).entered(); + debug!("NoOpReasonerConnector: request received"); + + // Log that the question has been asked + logger + .log_question(&state, &question) + .await + .map_err(|err| Error::LogQuestion { to: std::any::type_name::>(), err: err.freeze() })?; // Log the reasoner has been called - debug!("NoOpReasonerConnector: request received"); logger .log_response::(&ReasonerResponse::Success, None) .await diff --git a/lib/reasoners/posix/Cargo.toml b/lib/reasoners/posix/Cargo.toml index b1f1fc2..0e7c3d7 100644 --- a/lib/reasoners/posix/Cargo.toml +++ b/lib/reasoners/posix/Cargo.toml @@ -149,7 +149,7 @@ tracing = "0.1.40" error-trace = { git = "https://github.com/Lut99/error-trace-rs", tag = "v3.1.0" } spec = { path = "../../spec", package = "specifications" } -workflow = { path = "../../workflow" } +workflow = { path = "../../workflow", features = ["serde"]} [features] diff --git a/lib/reasoners/posix/src/reasonerconn.rs b/lib/reasoners/posix/src/reasonerconn.rs index 3be85cb..d8c74d5 100644 --- a/lib/reasoners/posix/src/reasonerconn.rs +++ b/lib/reasoners/posix/src/reasonerconn.rs @@ -4,7 +4,7 @@ // Created: // 11 Oct 2024, 16:54:51 // Last edited: -// 17 Oct 2024, 12:10:18 +// 17 Oct 2024, 13:57:53 // Auto updated? // Yes // @@ -21,7 +21,7 @@ use std::os::unix::fs::{MetadataExt as _, PermissionsExt as _}; use std::path::{Path, PathBuf}; use error_trace::{ErrorTrace as _, Trace}; -use serde::Deserialize; +use serde::{Deserialize, Serialize}; use spec::auditlogger::{AuditLogger, SessionedAuditLogger}; use spec::reasonerconn::{ReasonerConnector, ReasonerResponse}; use spec::reasons::NoReason; @@ -59,6 +59,13 @@ pub enum Error { #[source] err: Trace, }, + /// Failed to log the question to the given logger. + #[error("Failed to log the question to {to}")] + LogQuestion { + to: &'static str, + #[source] + err: Trace, + }, /// The dataset was unknown to us. #[error("Unknown dataset {data:?}")] UnknownDataset { data: String }, @@ -200,7 +207,7 @@ impl BitOr for PosixFilePermission { /***** LIBRARY *****/ /// The overarching input to the POSIX reasoner. -#[derive(Debug)] +#[derive(Clone, Debug, Deserialize, Serialize)] pub struct State { /// The policy to give. pub config: Config, @@ -249,6 +256,12 @@ impl ReasonerConnector for PosixReasonerConnector { async move { let _span = span!(Level::INFO, "ReasonerConnector::consult", reference = logger.reference()).entered(); + // Log the input + logger + .log_question(&state, &()) + .await + .map_err(|err| Error::LogQuestion { to: std::any::type_name::>(), err: err.freeze() })?; + // The datasets used in the workflow. E.g., `st_antonius_ect`. let datasets: WorkflowDatasets = WorkflowDatasets::from(&state.workflow); debug!("Found datasets in workflow {:?}: {:#?}", state.workflow.id, datasets); diff --git a/lib/spec/src/auditlogger.rs b/lib/spec/src/auditlogger.rs index 1467502..ff02139 100644 --- a/lib/spec/src/auditlogger.rs +++ b/lib/spec/src/auditlogger.rs @@ -4,7 +4,7 @@ // Created: // 09 Oct 2024, 13:38:41 // Last edited: -// 17 Oct 2024, 11:22:53 +// 17 Oct 2024, 13:14:18 // Auto updated? // Yes // @@ -17,6 +17,8 @@ use std::error::Error; use std::fmt::Display; use std::future::Future; +use serde::Serialize; + use crate::context::Context; use crate::reasonerconn::ReasonerResponse; @@ -64,6 +66,19 @@ impl SessionedAuditLogger { { L::log_response(&mut self.logger, &self.reference, response, raw) } + + /// Logs that the reasoner is being asked a question. + /// + /// # Arguments + /// - `state`: Some serializable state given as input to the reasoner. + /// - `question`: Some serializable question that we're asking. + pub fn log_question<'a, S, Q>(&'a mut self, state: &'a S, question: &'a Q) -> impl 'a + Future::Error>> + where + S: Serialize, + Q: Serialize, + { + L::log_question(&mut self.logger, &self.reference, state, question) + } } impl AuditLogger for SessionedAuditLogger { type Error = L::Error; @@ -86,6 +101,14 @@ impl AuditLogger for SessionedAuditLogger { { L::log_response(&mut self.logger, reference, response, raw) } + + fn log_question<'a, S, Q>(&'a mut self, reference: &'a str, state: &'a S, question: &'a Q) -> impl 'a + Future> + where + S: Serialize, + Q: Serialize, + { + L::log_question(&mut self.logger, reference, state, question) + } } @@ -121,4 +144,15 @@ pub trait AuditLogger { ) -> impl 'a + Future> where R: Display; + + /// Logs that the reasoner is being asked a question. + /// + /// # Arguments + /// - `reference`: Some reference that links the response to a particular answer. + /// - `state`: Some serializable state given as input to the reasoner. + /// - `question`: Some serializable question that we're asking. + fn log_question<'a, S, Q>(&'a mut self, reference: &'a str, state: &'a S, question: &'a Q) -> impl 'a + Future> + where + S: Serialize, + Q: Serialize; } From f142d1cca65cdb71372854437333a099153c7228 Mon Sep 17 00:00:00 2001 From: Lut99 Date: Fri, 18 Oct 2024 11:27:39 +0200 Subject: [PATCH 06/82] `Dataset`s in `Workflow`s now carry `from`-information again --- lib/reasoners/posix/src/reasonerconn.rs | 4 +-- lib/reasoners/posix/src/workflow.rs | 38 ++++++++++++++++--------- lib/workflow/src/lib.rs | 6 ++-- 3 files changed, 31 insertions(+), 17 deletions(-) diff --git a/lib/reasoners/posix/src/reasonerconn.rs b/lib/reasoners/posix/src/reasonerconn.rs index d8c74d5..d09c1d5 100644 --- a/lib/reasoners/posix/src/reasonerconn.rs +++ b/lib/reasoners/posix/src/reasonerconn.rs @@ -4,7 +4,7 @@ // Created: // 11 Oct 2024, 16:54:51 // Last edited: -// 17 Oct 2024, 13:57:53 +// 18 Oct 2024, 11:27:13 // Auto updated? // Yes // @@ -263,7 +263,7 @@ impl ReasonerConnector for PosixReasonerConnector { .map_err(|err| Error::LogQuestion { to: std::any::type_name::>(), err: err.freeze() })?; // The datasets used in the workflow. E.g., `st_antonius_ect`. - let datasets: WorkflowDatasets = WorkflowDatasets::from(&state.workflow); + let datasets: WorkflowDatasets = WorkflowDatasets::new(&state.config.id, &state.workflow); debug!("Found datasets in workflow {:?}: {:#?}", state.workflow.id, datasets); // Loop to find the permissions on the disk diff --git a/lib/reasoners/posix/src/workflow.rs b/lib/reasoners/posix/src/workflow.rs index 1088206..2f36210 100644 --- a/lib/reasoners/posix/src/workflow.rs +++ b/lib/reasoners/posix/src/workflow.rs @@ -4,7 +4,7 @@ // Created: // 11 Oct 2024, 16:54:04 // Last edited: -// 15 Oct 2024, 17:42:33 +// 18 Oct 2024, 11:27:06 // Auto updated? // Yes // @@ -45,13 +45,14 @@ impl<'w> _InfallibleAssertion<'w> for DatasetCollector<'w> {} /***** VISITORS *****/ /// Visits a [`Workflow`] in order to find all datasets used. struct DatasetCollector<'w> { - read_sets: Vec<(&'w Entity, &'w Dataset)>, - write_sets: Vec<(&'w Entity, &'w Dataset)>, + here: &'w str, + read_sets: Vec<(&'w Entity, &'w Dataset)>, + write_sets: Vec<(&'w Entity, &'w Dataset)>, execute_sets: Vec<(&'w Entity, &'w Dataset)>, } -impl<'w> Default for DatasetCollector<'w> { +impl<'w> DatasetCollector<'w> { #[inline] - fn default() -> Self { Self { read_sets: Default::default(), write_sets: Default::default(), execute_sets: Default::default() } } + fn new(here: &'w str) -> Self { Self { here, read_sets: Default::default(), write_sets: Default::default(), execute_sets: Default::default() } } } impl<'w> Visitor<'w> for DatasetCollector<'w> { type Error = Infallible; @@ -94,8 +95,19 @@ impl<'w> Visitor<'w> for DatasetCollector<'w> { // be able to write policies explicitly for this use-case. let location: &'w Entity = elem.at.as_ref().unwrap_or_else(|| LazyLock::force(&UNSPECIFIED_LOCATION)); - self.read_sets.extend(elem.input.iter().map(|d| (location, d))); - self.write_sets.extend(elem.output.iter().map(|d| (location, d))); + self.read_sets.extend( + elem.input + .iter() + .filter(|data| { + // We only consider input datasets that are at out location + data.from.as_ref().is_some_and(|ent| ent.id == self.here) + }) + .map(|d| (location, d)), + ); + // We only consider written sets that are created at our location + if location.id == self.here { + self.write_sets.extend(elem.output.iter().map(|d| (location, d))); + } // Also visit the next one before returning, lol self.visit(&elem.next) @@ -115,16 +127,16 @@ pub struct WorkflowDatasets<'w> { pub write_sets: Vec<(&'w Entity, &'w Dataset)>, pub execute_sets: Vec<(&'w Entity, &'w Dataset)>, } -impl<'w> From<&'w Workflow> for WorkflowDatasets<'w> { +impl<'w> WorkflowDatasets<'w> { #[inline] - fn from(value: &'w Workflow) -> Self { - let _span = span!(Level::INFO, "PosixReasonerConnector::find_datasets_in_workflow", workflow = value.id).entered(); - debug!("Walking the workflow in order to find datasets. Starting with {:?}", &value.start); + pub fn new(here: &'w str, wf: &'w Workflow) -> Self { + let _span = span!(Level::INFO, "PosixReasonerConnector::find_datasets_in_workflow", workflow = wf.id).entered(); + debug!("Walking the workflow in order to find datasets. Starting with {:?}", &wf.start); - let mut visitor = DatasetCollector::default(); + let mut visitor = DatasetCollector::new(here); // SAFETY: We can do this because `DatasetCollector` cannot physically error (its error is `Infallible`) unsafe { - value.visit(&mut visitor).unwrap_unchecked(); + wf.visit(&mut visitor).unwrap_unchecked(); } WorkflowDatasets { read_sets: visitor.read_sets, write_sets: visitor.write_sets, execute_sets: visitor.execute_sets } diff --git a/lib/workflow/src/lib.rs b/lib/workflow/src/lib.rs index 6780842..5747fc0 100644 --- a/lib/workflow/src/lib.rs +++ b/lib/workflow/src/lib.rs @@ -4,7 +4,7 @@ // Created: // 08 Oct 2024, 16:16:26 // Last edited: -// 15 Oct 2024, 16:40:22 +// 18 Oct 2024, 11:20:46 // Auto updated? // Yes // @@ -30,7 +30,9 @@ use serde::{Deserialize, Serialize}; #[cfg_attr(feature = "serde", derive(Deserialize, Serialize))] pub struct Dataset { /// Some identifier of the dataset. - pub id: String, + pub id: String, + /// If planned, the location where the dataset is transferred from. + pub from: Option, } /// Represents a user/site that can compute, store data, do neither or do both. From a583ca9323debd99ff432a83ea04cf6aad7bf517 Mon Sep 17 00:00:00 2001 From: Lut99 Date: Sat, 19 Oct 2024 10:50:41 +0200 Subject: [PATCH 07/82] Added `Eq`, `Hash` and `PartialEq` for `Dataset` --- lib/workflow/src/lib.rs | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/lib/workflow/src/lib.rs b/lib/workflow/src/lib.rs index 5747fc0..cc3613a 100644 --- a/lib/workflow/src/lib.rs +++ b/lib/workflow/src/lib.rs @@ -4,7 +4,7 @@ // Created: // 08 Oct 2024, 16:16:26 // Last edited: -// 18 Oct 2024, 11:20:46 +// 19 Oct 2024, 10:50:29 // Auto updated? // Yes // @@ -18,6 +18,8 @@ pub mod visitor; #[cfg(feature = "visualize")] pub mod visualize; +use std::hash::{Hash, Hasher}; + // Imports use enum_debug::EnumDebug; #[cfg(feature = "serde")] @@ -26,6 +28,10 @@ use serde::{Deserialize, Serialize}; /***** AUXILLARY DATA *****/ /// Defines a representation of a dataset. +/// +/// Note that its uniqueness (i.e., what is considered in [`Hash`] and [`PartialEq`]) is purely a +/// function of the dataset's id; i.e., two datasets with the same name but different sources are +/// considered the same. #[derive(Clone, Debug)] #[cfg_attr(feature = "serde", derive(Deserialize, Serialize))] pub struct Dataset { @@ -34,6 +40,18 @@ pub struct Dataset { /// If planned, the location where the dataset is transferred from. pub from: Option, } +impl Eq for Dataset {} +impl Hash for Dataset { + #[inline] + fn hash(&self, state: &mut H) { self.id.hash(state) } +} +impl PartialEq for Dataset { + #[inline] + fn eq(&self, other: &Self) -> bool { self.id == other.id } + + #[inline] + fn ne(&self, other: &Self) -> bool { self.id != other.id } +} /// Represents a user/site that can compute, store data, do neither or do both. #[derive(Clone, Debug)] From e54c27824ea9a2b8392d196ac1b44cadeaad6b60 Mon Sep 17 00:00:00 2001 From: Lut99 Date: Mon, 21 Oct 2024 13:33:23 +0200 Subject: [PATCH 08/82] Workflows now accept not having an end user --- lib/workflow/src/lib.rs | 4 ++-- lib/workflow/src/optimize.rs | 4 ++-- lib/workflow/src/visualize.rs | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/lib/workflow/src/lib.rs b/lib/workflow/src/lib.rs index cc3613a..a6324f6 100644 --- a/lib/workflow/src/lib.rs +++ b/lib/workflow/src/lib.rs @@ -4,7 +4,7 @@ // Created: // 08 Oct 2024, 16:16:26 // Last edited: -// 19 Oct 2024, 10:50:29 +// 21 Oct 2024, 13:32:08 // Auto updated? // Yes // @@ -86,7 +86,7 @@ pub struct Workflow { pub start: Elem, /// The user instigating this workflow (and thus getting the result, if any). - pub user: Entity, + pub user: Option, /// The metadata associated with this workflow as a whole. pub metadata: Vec, /// The signature verifying this workflow. If present, it's given as a pair of the person signing it and their signature. diff --git a/lib/workflow/src/optimize.rs b/lib/workflow/src/optimize.rs index 1434544..f870ca8 100644 --- a/lib/workflow/src/optimize.rs +++ b/lib/workflow/src/optimize.rs @@ -4,7 +4,7 @@ // Created: // 08 Oct 2024, 17:34:14 // Last edited: -// 05 May 2025, 12:01:56 +// 05 May 2025, 12:04:21 // Auto updated? // Yes // @@ -28,7 +28,7 @@ mod tests { /// Generates a workflow with minimal info #[inline] fn gen_wf(id: impl Into, start: impl Into) -> Workflow { - Workflow { id: id.into(), start: start.into(), user: Entity { id: "amy".into() }, metadata: vec![], signature: None } + Workflow { id: id.into(), start: start.into(), user: Some(Entity { id: "amy".into() }), metadata: vec![], signature: None } } /// Generates a branch. diff --git a/lib/workflow/src/visualize.rs b/lib/workflow/src/visualize.rs index 4327006..1c1b19d 100644 --- a/lib/workflow/src/visualize.rs +++ b/lib/workflow/src/visualize.rs @@ -4,7 +4,7 @@ // Created: // 31 Oct 2023, 14:30:00 // Last edited: -// 08 Oct 2024, 18:15:02 +// 21 Oct 2024, 13:33:05 // Auto updated? // Yes // @@ -170,7 +170,7 @@ impl Display for WorkflowFormatter<'_> { // Print global metadata if !self.wf.metadata.is_empty() { writeln!(f, "{} - id : {:?}", Indent(4), self.wf.id)?; - writeln!(f, "{} - user : {:?}", Indent(4), self.wf.user.id)?; + writeln!(f, "{} - user : {:?}", Indent(4), self.wf.user.as_ref().map(|ent| &ent.id))?; writeln!( f, "{} - metadata: {:?}", From 01275f5c7fc3e349df8a7fee19e52920f682bb3f Mon Sep 17 00:00:00 2001 From: Lut99 Date: Tue, 22 Oct 2024 10:36:13 +0200 Subject: [PATCH 09/82] Implemented `Eq`, `Hash` and `PartialEq` for `Entity` --- lib/workflow/src/lib.rs | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/lib/workflow/src/lib.rs b/lib/workflow/src/lib.rs index a6324f6..96ce0c0 100644 --- a/lib/workflow/src/lib.rs +++ b/lib/workflow/src/lib.rs @@ -4,7 +4,7 @@ // Created: // 08 Oct 2024, 16:16:26 // Last edited: -// 21 Oct 2024, 13:32:08 +// 22 Oct 2024, 10:35:57 // Auto updated? // Yes // @@ -54,12 +54,27 @@ impl PartialEq for Dataset { } /// Represents a user/site that can compute, store data, do neither or do both. +/// +/// Note that its uniqueness (i.e., what is considered in [`Hash`] and [`PartialEq`]) is purely a +/// function of the entity's id. #[derive(Clone, Debug)] #[cfg_attr(feature = "serde", derive(Deserialize, Serialize))] pub struct Entity { /// Some identifier of this domain. pub id: String, } +impl Eq for Entity {} +impl Hash for Entity { + #[inline] + fn hash(&self, state: &mut H) { self.id.hash(state) } +} +impl PartialEq for Entity { + #[inline] + fn eq(&self, other: &Self) -> bool { self.id == other.id } + + #[inline] + fn ne(&self, other: &Self) -> bool { self.id != other.id } +} /// Represents a "tag" and everything we need to know. #[derive(Clone, Debug)] From ef03ed8520a0bc7337ef2cca13f8a904ff67c9e1 Mon Sep 17 00:00:00 2001 From: Lut99 Date: Tue, 22 Oct 2024 10:59:58 +0200 Subject: [PATCH 10/82] `Visitor` and `VisitorMut` can now optimize recursion for linear connections This to avoid Brane WIR-like problems with long, linear workflows that cause the stack to overflow. --- lib/reasoners/posix/src/workflow.rs | 8 +- lib/workflow/src/visitor.rs | 247 +++++++++++++++++++++++----- 2 files changed, 208 insertions(+), 47 deletions(-) diff --git a/lib/reasoners/posix/src/workflow.rs b/lib/reasoners/posix/src/workflow.rs index 2f36210..d96172d 100644 --- a/lib/reasoners/posix/src/workflow.rs +++ b/lib/reasoners/posix/src/workflow.rs @@ -4,7 +4,7 @@ // Created: // 11 Oct 2024, 16:54:04 // Last edited: -// 18 Oct 2024, 11:27:06 +// 22 Oct 2024, 10:54:08 // Auto updated? // Yes // @@ -17,7 +17,7 @@ use std::sync::LazyLock; use tracing::{Level, debug, span}; use workflow::visitor::Visitor; -use workflow::{Dataset, ElemCall, Entity, Workflow}; +use workflow::{Dataset, Elem, ElemCall, Entity, Workflow}; /***** CONSTANTS *****/ @@ -85,7 +85,7 @@ impl<'w> Visitor<'w> for DatasetCollector<'w> { // self.write_sets.extend(repeat(location).zip(stop_sets.iter().cloned())); // } - fn visit_call(&mut self, elem: &'w ElemCall) -> Result<(), Self::Error> { + fn visit_call(&mut self, elem: &'w ElemCall) -> Result, Self::Error> { // We take a more simplified view on dataset reading/writing. // We consider a task's inputs as READING. Any task's outputs are WRITING. @@ -110,7 +110,7 @@ impl<'w> Visitor<'w> for DatasetCollector<'w> { } // Also visit the next one before returning, lol - self.visit(&elem.next) + Ok(Some(&elem.next)) } } diff --git a/lib/workflow/src/visitor.rs b/lib/workflow/src/visitor.rs index 0ec21ff..d6cb418 100644 --- a/lib/workflow/src/visitor.rs +++ b/lib/workflow/src/visitor.rs @@ -4,7 +4,7 @@ // Created: // 08 Oct 2024, 17:35:03 // Last edited: -// 14 Oct 2024, 11:55:45 +// 22 Oct 2024, 10:59:10 // Auto updated? // Yes // @@ -37,16 +37,43 @@ pub trait Visitor<'w> { /// /// # Errors /// If this visitor fails, then the whole visiting processes is terminated. - fn visit(&mut self, elem: &'w Elem) -> Result<(), Self::Error> { - match elem { - Elem::Call(c) => self.visit_call(c), - - Elem::Branch(b) => self.visit_branch(b), - Elem::Parallel(p) => self.visit_parallel(p), - Elem::Loop(l) => self.visit_loop(l), - - Elem::Next => self.visit_next(), - Elem::Stop => self.visit_stop(), + fn visit(&mut self, mut elem: &'w Elem) -> Result<(), Self::Error> { + loop { + match elem { + Elem::Call(c) => match self.visit_call(c) { + Ok(Some(next)) => { + elem = next; + }, + Ok(None) => return Ok(()), + Err(err) => return Err(err), + }, + + Elem::Branch(b) => match self.visit_branch(b) { + Ok(Some(next)) => { + elem = next; + }, + Ok(None) => return Ok(()), + Err(err) => return Err(err), + }, + Elem::Parallel(p) => match self.visit_parallel(p) { + Ok(Some(next)) => { + elem = next; + }, + Ok(None) => return Ok(()), + Err(err) => return Err(err), + }, + Elem::Loop(l) => match self.visit_loop(l) { + Ok(Some(next)) => { + elem = next; + }, + Ok(None) => return Ok(()), + Err(err) => return Err(err), + }, + + // Note: these can never return a next + Elem::Next => return self.visit_next(), + Elem::Stop => return self.visit_stop(), + } } } @@ -55,30 +82,54 @@ pub trait Visitor<'w> { /// /// The default implementation doesn't do anything meaningful besides visiting the next node. /// + /// Usually, you don't call this function directly. Call [`Visitor::visit()`] instead to + /// traverse a node. If you do call it manually, take care to process the + /// [returned value](#returns) correctly. + /// /// # Arguments /// - `elem`: The visited [`ElemCall`]. /// + /// # Returns + /// This function can return the reference to a new [`Elem`] to traverse to. + /// + /// Implementations can do so when there is a "next" element to traverse that happens after all + /// visiting for this element is done. If so, then returning it instead of calling + /// [`Visitor::visit()`] manually saves stack space because it is traversed by iteration + /// instead of recursion. + /// /// # Errors /// If this visitor fails, then the whole visiting processes is terminated. #[inline] - fn visit_call(&mut self, elem: &'w ElemCall) -> Result<(), Self::Error> { self.visit(&elem.next) } + fn visit_call(&mut self, elem: &'w ElemCall) -> Result, Self::Error> { Ok(Some(&elem.next)) } /// Visits an [`Elem::Branch`]. /// /// The default implementation doesn't do anything meaningful besides visiting the branches, /// and then the next node. /// + /// Usually, you don't call this function directly. Call [`Visitor::visit()`] instead to + /// traverse a node. If you do call it manually, take care to process the + /// [returned value](#returns) correctly. + /// /// # Arguments /// - `elem`: The visited [`ElemBranch`]. /// + /// # Returns + /// This function can return the reference to a new [`Elem`] to traverse to. + /// + /// Implementations can do so when there is a "next" element to traverse that happens after all + /// visiting for this element is done. If so, then returning it instead of calling + /// [`Visitor::visit()`] manually saves stack space because it is traversed by iteration + /// instead of recursion. + /// /// # Errors /// If this visitor fails, then the whole visiting processes is terminated. #[inline] - fn visit_branch(&mut self, elem: &'w ElemBranch) -> Result<(), Self::Error> { + fn visit_branch(&mut self, elem: &'w ElemBranch) -> Result, Self::Error> { for b in &elem.branches { self.visit(b)?; } - self.visit(&elem.next) + Ok(Some(&elem.next)) } /// Visits an [`Elem::Parallel`]. @@ -86,17 +137,29 @@ pub trait Visitor<'w> { /// The default implementation doesn't do anything meaningful besides visiting the branches, /// and then the next node. /// + /// Usually, you don't call this function directly. Call [`Visitor::visit()`] instead to + /// traverse a node. If you do call it manually, take care to process the + /// [returned value](#returns) correctly. + /// /// # Arguments /// - `elem`: The visited [`ElemParallel`]. /// + /// # Returns + /// This function can return the reference to a new [`Elem`] to traverse to. + /// + /// Implementations can do so when there is a "next" element to traverse that happens after all + /// visiting for this element is done. If so, then returning it instead of calling + /// [`Visitor::visit()`] manually saves stack space because it is traversed by iteration + /// instead of recursion. + /// /// # Errors /// If this visitor fails, then the whole visiting processes is terminated. #[inline] - fn visit_parallel(&mut self, elem: &'w ElemParallel) -> Result<(), Self::Error> { + fn visit_parallel(&mut self, elem: &'w ElemParallel) -> Result, Self::Error> { for b in &elem.branches { self.visit(b)?; } - self.visit(&elem.next) + Ok(Some(&elem.next)) } /// Visits an [`Elem::Loop`]. @@ -110,9 +173,9 @@ pub trait Visitor<'w> { /// # Errors /// If this visitor fails, then the whole visiting processes is terminated. #[inline] - fn visit_loop(&mut self, elem: &'w ElemLoop) -> Result<(), Self::Error> { + fn visit_loop(&mut self, elem: &'w ElemLoop) -> Result, Self::Error> { self.visit(&elem.body)?; - self.visit(&elem.next) + Ok(Some(&elem.next)) } @@ -141,16 +204,16 @@ impl<'a, 'w, T: Visitor<'w>> Visitor<'w> for &'a mut T { fn visit(&mut self, elem: &'w Elem) -> Result<(), Self::Error> { T::visit(self, elem) } #[inline] - fn visit_call(&mut self, elem: &'w ElemCall) -> Result<(), Self::Error> { T::visit_call(self, elem) } + fn visit_call(&mut self, elem: &'w ElemCall) -> Result, Self::Error> { T::visit_call(self, elem) } #[inline] - fn visit_branch(&mut self, elem: &'w ElemBranch) -> Result<(), Self::Error> { T::visit_branch(self, elem) } + fn visit_branch(&mut self, elem: &'w ElemBranch) -> Result, Self::Error> { T::visit_branch(self, elem) } #[inline] - fn visit_parallel(&mut self, elem: &'w ElemParallel) -> Result<(), Self::Error> { T::visit_parallel(self, elem) } + fn visit_parallel(&mut self, elem: &'w ElemParallel) -> Result, Self::Error> { T::visit_parallel(self, elem) } #[inline] - fn visit_loop(&mut self, elem: &'w ElemLoop) -> Result<(), Self::Error> { T::visit_loop(self, elem) } + fn visit_loop(&mut self, elem: &'w ElemLoop) -> Result, Self::Error> { T::visit_loop(self, elem) } #[inline] fn visit_next(&mut self) -> Result<(), Self::Error> { T::visit_next(self) } @@ -180,16 +243,42 @@ pub trait VisitorMut<'w> { /// /// # Errors /// If this visitor fails, then the whole visiting processes is terminated. - fn visit(&mut self, elem: &'w mut Elem) -> Result<(), Self::Error> { - match elem { - Elem::Call(c) => self.visit_call(c), - - Elem::Branch(b) => self.visit_branch(b), - Elem::Parallel(p) => self.visit_parallel(p), - Elem::Loop(l) => self.visit_loop(l), - - Elem::Next => self.visit_next(), - Elem::Stop => self.visit_stop(), + fn visit(&mut self, mut elem: &'w mut Elem) -> Result<(), Self::Error> { + loop { + match elem { + Elem::Call(c) => match self.visit_call(c) { + Ok(Some(next)) => { + elem = next; + }, + Ok(None) => return Ok(()), + Err(err) => return Err(err), + }, + + Elem::Branch(b) => match self.visit_branch(b) { + Ok(Some(next)) => { + elem = next; + }, + Ok(None) => return Ok(()), + Err(err) => return Err(err), + }, + Elem::Parallel(p) => match self.visit_parallel(p) { + Ok(Some(next)) => { + elem = next; + }, + Ok(None) => return Ok(()), + Err(err) => return Err(err), + }, + Elem::Loop(l) => match self.visit_loop(l) { + Ok(Some(next)) => { + elem = next; + }, + Ok(None) => return Ok(()), + Err(err) => return Err(err), + }, + + Elem::Next => return self.visit_next(), + Elem::Stop => return self.visit_stop(), + } } } @@ -198,30 +287,54 @@ pub trait VisitorMut<'w> { /// /// The default implementation doesn't do anything meaningful besides visiting the next node. /// + /// Usually, you don't call this function directly. Call [`VisitorMut::visit()`] instead to + /// traverse a node. If you do call it manually, take care to process the + /// [returned value](#returns) correctly. + /// /// # Arguments /// - `elem`: The visited [`ElemCall`]. /// + /// # Returns + /// This function can return the reference to a new [`Elem`] to traverse to. + /// + /// Implementations can do so when there is a "next" element to traverse that happens after all + /// visiting for this element is done. If so, then returning it instead of calling + /// [`VisitorMut::visit()`] manually saves stack space because it is traversed by iteration + /// instead of recursion. + /// /// # Errors /// If this visitor fails, then the whole visiting processes is terminated. #[inline] - fn visit_call(&mut self, elem: &'w mut ElemCall) -> Result<(), Self::Error> { self.visit(&mut elem.next) } + fn visit_call(&mut self, elem: &'w mut ElemCall) -> Result, Self::Error> { Ok(Some(&mut elem.next)) } /// Visits an [`Elem::Branch`]. /// /// The default implementation doesn't do anything meaningful besides visiting the branches, /// and then the next node. /// + /// Usually, you don't call this function directly. Call [`VisitorMut::visit()`] instead to + /// traverse a node. If you do call it manually, take care to process the + /// [returned value](#returns) correctly. + /// /// # Arguments /// - `elem`: The visited [`ElemBranch`]. /// + /// # Returns + /// This function can return the reference to a new [`Elem`] to traverse to. + /// + /// Implementations can do so when there is a "next" element to traverse that happens after all + /// visiting for this element is done. If so, then returning it instead of calling + /// [`VisitorMut::visit()`] manually saves stack space because it is traversed by iteration + /// instead of recursion. + /// /// # Errors /// If this visitor fails, then the whole visiting processes is terminated. #[inline] - fn visit_branch(&mut self, elem: &'w mut ElemBranch) -> Result<(), Self::Error> { + fn visit_branch(&mut self, elem: &'w mut ElemBranch) -> Result, Self::Error> { for b in &mut elem.branches { self.visit(b)?; } - self.visit(&mut elem.next) + Ok(Some(&mut elem.next)) } /// Visits an [`Elem::Parallel`]. @@ -229,17 +342,29 @@ pub trait VisitorMut<'w> { /// The default implementation doesn't do anything meaningful besides visiting the branches, /// and then the next node. /// + /// Usually, you don't call this function directly. Call [`VisitorMut::visit()`] instead to + /// traverse a node. If you do call it manually, take care to process the + /// [returned value](#returns) correctly. + /// /// # Arguments /// - `elem`: The visited [`ElemParallel`]. /// + /// # Returns + /// This function can return the reference to a new [`Elem`] to traverse to. + /// + /// Implementations can do so when there is a "next" element to traverse that happens after all + /// visiting for this element is done. If so, then returning it instead of calling + /// [`VisitorMut::visit()`] manually saves stack space because it is traversed by iteration + /// instead of recursion. + /// /// # Errors /// If this visitor fails, then the whole visiting processes is terminated. #[inline] - fn visit_parallel(&mut self, elem: &'w mut ElemParallel) -> Result<(), Self::Error> { + fn visit_parallel(&mut self, elem: &'w mut ElemParallel) -> Result, Self::Error> { for b in &mut elem.branches { self.visit(b)?; } - self.visit(&mut elem.next) + Ok(Some(&mut elem.next)) } /// Visits an [`Elem::Loop`]. @@ -247,15 +372,27 @@ pub trait VisitorMut<'w> { /// The default implementation doesn't do anything meaningful besides visiting the body, and /// then the next node. /// + /// Usually, you don't call this function directly. Call [`VisitorMut::visit()`] instead to + /// traverse a node. If you do call it manually, take care to process the + /// [returned value](#returns) correctly. + /// /// # Arguments /// - `elem`: The visited [`ElemLoop`]. /// + /// # Returns + /// This function can return the reference to a new [`Elem`] to traverse to. + /// + /// Implementations can do so when there is a "next" element to traverse that happens after all + /// visiting for this element is done. If so, then returning it instead of calling + /// [`VisitorMut::visit()`] manually saves stack space because it is traversed by iteration + /// instead of recursion. + /// /// # Errors /// If this visitor fails, then the whole visiting processes is terminated. #[inline] - fn visit_loop(&mut self, elem: &'w mut ElemLoop) -> Result<(), Self::Error> { + fn visit_loop(&mut self, elem: &'w mut ElemLoop) -> Result, Self::Error> { self.visit(&mut elem.body)?; - self.visit(&mut elem.next) + Ok(Some(&mut elem.next)) } @@ -284,16 +421,16 @@ impl<'a, 'w, T: VisitorMut<'w>> VisitorMut<'w> for &'a mut T { fn visit(&mut self, elem: &'w mut Elem) -> Result<(), Self::Error> { T::visit(self, elem) } #[inline] - fn visit_call(&mut self, elem: &'w mut ElemCall) -> Result<(), Self::Error> { T::visit_call(self, elem) } + fn visit_call(&mut self, elem: &'w mut ElemCall) -> Result, Self::Error> { T::visit_call(self, elem) } #[inline] - fn visit_branch(&mut self, elem: &'w mut ElemBranch) -> Result<(), Self::Error> { T::visit_branch(self, elem) } + fn visit_branch(&mut self, elem: &'w mut ElemBranch) -> Result, Self::Error> { T::visit_branch(self, elem) } #[inline] - fn visit_parallel(&mut self, elem: &'w mut ElemParallel) -> Result<(), Self::Error> { T::visit_parallel(self, elem) } + fn visit_parallel(&mut self, elem: &'w mut ElemParallel) -> Result, Self::Error> { T::visit_parallel(self, elem) } #[inline] - fn visit_loop(&mut self, elem: &'w mut ElemLoop) -> Result<(), Self::Error> { T::visit_loop(self, elem) } + fn visit_loop(&mut self, elem: &'w mut ElemLoop) -> Result, Self::Error> { T::visit_loop(self, elem) } #[inline] fn visit_next(&mut self) -> Result<(), Self::Error> { T::visit_next(self) } @@ -361,6 +498,10 @@ pub trait VisitorOwned { /// /// The default implementation doesn't do anything meaningful besides visiting the next node. /// + /// Usually, you don't call this function directly. Call [`VisitorOwned::visit()`] or + /// [`VisitorOwned::visit_mut()`] instead to traverse a node. If you do call it manually, take + /// care to process the [returned value](#returns) correctly. + /// /// # Arguments /// - `elem`: The visited [`ElemCall`]. /// @@ -380,6 +521,10 @@ pub trait VisitorOwned { /// The default implementation doesn't do anything meaningful besides visiting the branches, /// and then the next node. /// + /// Usually, you don't call this function directly. Call [`VisitorOwned::visit()`] or + /// [`VisitorOwned::visit_mut()`] instead to traverse a node. If you do call it manually, take + /// care to process the [returned value](#returns) correctly. + /// /// # Arguments /// - `elem`: The visited [`ElemBranch`]. /// @@ -402,6 +547,10 @@ pub trait VisitorOwned { /// The default implementation doesn't do anything meaningful besides visiting the branches, /// and then the next node. /// + /// Usually, you don't call this function directly. Call [`VisitorOwned::visit()`] or + /// [`VisitorOwned::visit_mut()`] instead to traverse a node. If you do call it manually, take + /// care to process the [returned value](#returns) correctly. + /// /// # Arguments /// - `elem`: The visited [`ElemParallel`]. /// @@ -424,6 +573,10 @@ pub trait VisitorOwned { /// The default implementation doesn't do anything meaningful besides visiting the body, and /// then the next node. /// + /// Usually, you don't call this function directly. Call [`VisitorOwned::visit()`] or + /// [`VisitorOwned::visit_mut()`] instead to traverse a node. If you do call it manually, take + /// care to process the [returned value](#returns) correctly. + /// /// # Arguments /// - `elem`: The visited [`ElemLoop`]. /// @@ -444,6 +597,10 @@ pub trait VisitorOwned { /// /// The default implementation doesn't do anything meaningful. /// + /// Usually, you don't call this function directly. Call [`VisitorOwned::visit()`] or + /// [`VisitorOwned::visit_mut()`] instead to traverse a node. If you do call it manually, take + /// care to process the [returned value](#returns) correctly. + /// /// # Returns /// An [`Elem::Next`] (i.e., nothing is replaced). /// @@ -456,6 +613,10 @@ pub trait VisitorOwned { /// /// The default implementation doesn't do anything meaningful. /// + /// Usually, you don't call this function directly. Call [`VisitorOwned::visit()`] or + /// [`VisitorOwned::visit_mut()`] instead to traverse a node. If you do call it manually, take + /// care to process the [returned value](#returns) correctly. + /// /// # Returns /// An [`Elem::Stop`] (i.e., nothing is replaced). /// From 8d07921dd6cce94e0989054f3e8de40607b4a796 Mon Sep 17 00:00:00 2001 From: Lut99 Date: Mon, 4 Nov 2024 16:36:13 +0100 Subject: [PATCH 11/82] `AuditLogger` commutes over mutable references --- lib/spec/src/auditlogger.rs | 37 ++++++++++++++++++++++++++++++++++++- 1 file changed, 36 insertions(+), 1 deletion(-) diff --git a/lib/spec/src/auditlogger.rs b/lib/spec/src/auditlogger.rs index ff02139..fc297c8 100644 --- a/lib/spec/src/auditlogger.rs +++ b/lib/spec/src/auditlogger.rs @@ -4,7 +4,7 @@ // Created: // 09 Oct 2024, 13:38:41 // Last edited: -// 17 Oct 2024, 13:14:18 +// 04 Nov 2024, 16:35:48 // Auto updated? // Yes // @@ -156,3 +156,38 @@ pub trait AuditLogger { S: Serialize, Q: Serialize; } + +// Standard impls +impl<'a, T: AuditLogger> AuditLogger for &'a mut T { + type Error = T::Error; + + #[inline] + fn log_context<'s, C>(&'s mut self, context: &'s C) -> impl 's + Future> + where + C: ?Sized + Context, + { + ::log_context(self, context) + } + + #[inline] + fn log_response<'s, R>( + &'s mut self, + reference: &'s str, + response: &'s ReasonerResponse, + raw: Option<&'s str>, + ) -> impl 's + Future> + where + R: Display, + { + ::log_response(self, reference, response, raw) + } + + #[inline] + fn log_question<'s, S, Q>(&'s mut self, reference: &'s str, state: &'s S, question: &'s Q) -> impl 's + Future> + where + S: Serialize, + Q: Serialize, + { + ::log_question(self, reference, state, question) + } +} From d309b722cdf05f02395aab578500365d7832254b Mon Sep 17 00:00:00 2001 From: Lut99 Date: Tue, 5 Nov 2024 10:44:47 +0100 Subject: [PATCH 12/82] `AuditLogger` no longer requires mutable access to `self` This because we decided to use the logger from different threads. --- lib/loggers/file/src/logger.rs | 19 ++--- lib/loggers/no-op/src/logger.rs | 13 ++-- lib/reasoners/eflint-json/src/reasonerconn.rs | 4 +- lib/reasoners/no-op/src/reasonerconn.rs | 4 +- lib/reasoners/posix/src/reasonerconn.rs | 4 +- lib/spec/src/auditlogger.rs | 69 ++++++++++++++----- lib/spec/src/reasonerconn.rs | 4 +- 7 files changed, 74 insertions(+), 43 deletions(-) diff --git a/lib/loggers/file/src/logger.rs b/lib/loggers/file/src/logger.rs index 6241dbf..7896ece 100644 --- a/lib/loggers/file/src/logger.rs +++ b/lib/loggers/file/src/logger.rs @@ -4,7 +4,7 @@ // Created: // 10 Oct 2024, 14:16:24 // Last edited: -// 17 Oct 2024, 13:17:01 +// 05 Nov 2024, 10:43:25 // Auto updated? // Yes // @@ -124,7 +124,7 @@ pub struct FileLogger { path: PathBuf, /// Whether the user has already printed the context or not. #[cfg(debug_assertions)] - logged_context: bool, + logged_context: std::sync::Arc, } impl FileLogger { /// Constructor for the FileLogger that initializes it pointing to the given file. @@ -141,7 +141,7 @@ impl FileLogger { id: id.into(), path: path.into(), #[cfg(debug_assertions)] - logged_context: false, + logged_context: std::sync::Arc::new(std::sync::atomic::AtomicBool::new(false)), } } @@ -202,7 +202,7 @@ impl AuditLogger for FileLogger { type Error = Error; #[inline] - fn log_context<'a, C>(&'a mut self, context: &'a C) -> impl 'a + Future> + fn log_context<'a, C>(&'a self, context: &'a C) -> impl 'a + Future> where C: ?Sized + Context, { @@ -215,14 +215,15 @@ impl AuditLogger for FileLogger { // Log it self.log(LogStatement::Context { context }).await?; - self.logged_context = true; + #[cfg(debug_assertions)] + self.logged_context.store(true, std::sync::atomic::Ordering::Relaxed); Ok(()) } } #[inline] fn log_response<'a, R>( - &'a mut self, + &'a self, reference: &'a str, response: &'a ReasonerResponse, raw: Option<&'a str>, @@ -232,7 +233,7 @@ impl AuditLogger for FileLogger { { async move { #[cfg(debug_assertions)] - if !self.logged_context { + if !self.logged_context.load(std::sync::atomic::Ordering::Relaxed) { tracing::warn!("Logging reasoner response without having logged the reasoner context; please call FileLogger::log_context() first."); } @@ -251,14 +252,14 @@ impl AuditLogger for FileLogger { } #[inline] - fn log_question<'a, S, Q>(&'a mut self, reference: &'a str, state: &'a S, question: &'a Q) -> impl 'a + Future> + fn log_question<'a, S, Q>(&'a self, reference: &'a str, state: &'a S, question: &'a Q) -> impl 'a + Future> where S: Serialize, Q: Serialize, { async move { #[cfg(debug_assertions)] - if !self.logged_context { + if !self.logged_context.load(std::sync::atomic::Ordering::Relaxed) { tracing::warn!("Logging reasoner response without having logged the reasoner context; please call FileLogger::log_context() first."); } diff --git a/lib/loggers/no-op/src/logger.rs b/lib/loggers/no-op/src/logger.rs index a579c34..5627526 100644 --- a/lib/loggers/no-op/src/logger.rs +++ b/lib/loggers/no-op/src/logger.rs @@ -4,7 +4,7 @@ // Created: // 10 Oct 2024, 14:46:33 // Last edited: -// 17 Oct 2024, 13:14:34 +// 05 Nov 2024, 10:23:27 // Auto updated? // Yes // @@ -41,7 +41,7 @@ impl AuditLogger for MockLogger { type Error = Infallible; #[inline] - fn log_context<'a, C>(&'a mut self, _context: &'a C) -> impl 'a + Future> + fn log_context<'a, C>(&'a self, _context: &'a C) -> impl 'a + Future> where C: ?Sized + Context, { @@ -53,7 +53,7 @@ impl AuditLogger for MockLogger { #[inline] fn log_response<'a, R>( - &'a mut self, + &'a self, _reference: &'a str, _response: &'a ReasonerResponse, _raw: Option<&'a str>, @@ -68,12 +68,7 @@ impl AuditLogger for MockLogger { } #[inline] - fn log_question<'a, S, Q>( - &'a mut self, - _reference: &'a str, - _state: &'a S, - _question: &'a Q, - ) -> impl 'a + Future> + fn log_question<'a, S, Q>(&'a self, _reference: &'a str, _state: &'a S, _question: &'a Q) -> impl 'a + Future> where S: Serialize, Q: Serialize, diff --git a/lib/reasoners/eflint-json/src/reasonerconn.rs b/lib/reasoners/eflint-json/src/reasonerconn.rs index 211b1c4..22f5346 100644 --- a/lib/reasoners/eflint-json/src/reasonerconn.rs +++ b/lib/reasoners/eflint-json/src/reasonerconn.rs @@ -4,7 +4,7 @@ // Created: // 09 Oct 2024, 15:52:06 // Last edited: -// 17 Oct 2024, 13:59:28 +// 05 Nov 2024, 10:44:14 // Auto updated? // Yes // @@ -216,7 +216,7 @@ where &self, state: Self::State, question: Self::Question, - logger: &mut SessionedAuditLogger, + logger: &SessionedAuditLogger, ) -> impl Future, Self::Error>> where L: AuditLogger, diff --git a/lib/reasoners/no-op/src/reasonerconn.rs b/lib/reasoners/no-op/src/reasonerconn.rs index 922aa6c..2cb42a0 100644 --- a/lib/reasoners/no-op/src/reasonerconn.rs +++ b/lib/reasoners/no-op/src/reasonerconn.rs @@ -4,7 +4,7 @@ // Created: // 10 Oct 2024, 16:21:09 // Last edited: -// 17 Oct 2024, 13:56:10 +// 05 Nov 2024, 10:43:39 // Auto updated? // Yes // @@ -80,7 +80,7 @@ where &self, state: Self::State, question: Self::Question, - logger: &mut SessionedAuditLogger, + logger: &SessionedAuditLogger, ) -> impl Future, Self::Error>> where L: AuditLogger, diff --git a/lib/reasoners/posix/src/reasonerconn.rs b/lib/reasoners/posix/src/reasonerconn.rs index d09c1d5..3db6387 100644 --- a/lib/reasoners/posix/src/reasonerconn.rs +++ b/lib/reasoners/posix/src/reasonerconn.rs @@ -4,7 +4,7 @@ // Created: // 11 Oct 2024, 16:54:51 // Last edited: -// 18 Oct 2024, 11:27:13 +// 05 Nov 2024, 10:44:07 // Auto updated? // Yes // @@ -248,7 +248,7 @@ impl ReasonerConnector for PosixReasonerConnector { &self, state: Self::State, _question: Self::Question, - logger: &mut SessionedAuditLogger, + logger: &SessionedAuditLogger, ) -> impl Future, Self::Error>> where L: AuditLogger, diff --git a/lib/spec/src/auditlogger.rs b/lib/spec/src/auditlogger.rs index fc297c8..554fa4b 100644 --- a/lib/spec/src/auditlogger.rs +++ b/lib/spec/src/auditlogger.rs @@ -4,7 +4,7 @@ // Created: // 09 Oct 2024, 13:38:41 // Last edited: -// 04 Nov 2024, 16:35:48 +// 05 Nov 2024, 10:23:16 // Auto updated? // Yes // @@ -57,14 +57,14 @@ impl SessionedAuditLogger { /// - `response`: The [`ReasonerResponse`] that we're logging. /// - `raw`: The raw response produced by the reasoner, if applicable. pub fn log_response<'a, R>( - &'a mut self, + &'a self, response: &'a ReasonerResponse, raw: Option<&'a str>, ) -> impl 'a + Future::Error>> where R: Display, { - L::log_response(&mut self.logger, &self.reference, response, raw) + L::log_response(&self.logger, &self.reference, response, raw) } /// Logs that the reasoner is being asked a question. @@ -72,26 +72,26 @@ impl SessionedAuditLogger { /// # Arguments /// - `state`: Some serializable state given as input to the reasoner. /// - `question`: Some serializable question that we're asking. - pub fn log_question<'a, S, Q>(&'a mut self, state: &'a S, question: &'a Q) -> impl 'a + Future::Error>> + pub fn log_question<'a, S, Q>(&'a self, state: &'a S, question: &'a Q) -> impl 'a + Future::Error>> where S: Serialize, Q: Serialize, { - L::log_question(&mut self.logger, &self.reference, state, question) + L::log_question(&self.logger, &self.reference, state, question) } } impl AuditLogger for SessionedAuditLogger { type Error = L::Error; - fn log_context<'a, C>(&'a mut self, context: &'a C) -> impl 'a + Future> + fn log_context<'a, C>(&'a self, context: &'a C) -> impl 'a + Future> where C: ?Sized + Context, { - L::log_context(&mut self.logger, context) + L::log_context(&self.logger, context) } fn log_response<'a, R>( - &'a mut self, + &'a self, reference: &'a str, response: &'a ReasonerResponse, raw: Option<&'a str>, @@ -99,15 +99,15 @@ impl AuditLogger for SessionedAuditLogger { where R: Display, { - L::log_response(&mut self.logger, reference, response, raw) + L::log_response(&self.logger, reference, response, raw) } - fn log_question<'a, S, Q>(&'a mut self, reference: &'a str, state: &'a S, question: &'a Q) -> impl 'a + Future> + fn log_question<'a, S, Q>(&'a self, reference: &'a str, state: &'a S, question: &'a Q) -> impl 'a + Future> where S: Serialize, Q: Serialize, { - L::log_question(&mut self.logger, reference, state, question) + L::log_question(&self.logger, reference, state, question) } } @@ -117,6 +117,8 @@ impl AuditLogger for SessionedAuditLogger { /***** LIBRARY *****/ /// Defines a generic interface to write to an audit trail. +/// +/// Note that this logger may be used across threads. As such, any mutability must be inferior. pub trait AuditLogger { /// Defines the errors returned by this logger. type Error: Error; @@ -126,7 +128,7 @@ pub trait AuditLogger { /// /// # Arguments /// - `context`: Something [`Serialize`]able that we want to write at startup. - fn log_context<'a, C>(&'a mut self, context: &'a C) -> impl 'a + Future> + fn log_context<'a, C>(&'a self, context: &'a C) -> impl 'a + Future> where C: ?Sized + Context; @@ -137,7 +139,7 @@ pub trait AuditLogger { /// - `response`: The [`ReasonerResponse`] that we're logging. /// - `raw`: The raw response produced by the reasoner, if applicable. fn log_response<'a, R>( - &'a mut self, + &'a self, reference: &'a str, response: &'a ReasonerResponse, raw: Option<&'a str>, @@ -151,18 +153,51 @@ pub trait AuditLogger { /// - `reference`: Some reference that links the response to a particular answer. /// - `state`: Some serializable state given as input to the reasoner. /// - `question`: Some serializable question that we're asking. - fn log_question<'a, S, Q>(&'a mut self, reference: &'a str, state: &'a S, question: &'a Q) -> impl 'a + Future> + fn log_question<'a, S, Q>(&'a self, reference: &'a str, state: &'a S, question: &'a Q) -> impl 'a + Future> where S: Serialize, Q: Serialize; } // Standard impls +impl<'a, T: AuditLogger> AuditLogger for &'a T { + type Error = T::Error; + + #[inline] + fn log_context<'s, C>(&'s self, context: &'s C) -> impl 's + Future> + where + C: ?Sized + Context, + { + ::log_context(self, context) + } + + #[inline] + fn log_response<'s, R>( + &'s self, + reference: &'s str, + response: &'s ReasonerResponse, + raw: Option<&'s str>, + ) -> impl 's + Future> + where + R: Display, + { + ::log_response(self, reference, response, raw) + } + + #[inline] + fn log_question<'s, S, Q>(&'s self, reference: &'s str, state: &'s S, question: &'s Q) -> impl 's + Future> + where + S: Serialize, + Q: Serialize, + { + ::log_question(self, reference, state, question) + } +} impl<'a, T: AuditLogger> AuditLogger for &'a mut T { type Error = T::Error; #[inline] - fn log_context<'s, C>(&'s mut self, context: &'s C) -> impl 's + Future> + fn log_context<'s, C>(&'s self, context: &'s C) -> impl 's + Future> where C: ?Sized + Context, { @@ -171,7 +206,7 @@ impl<'a, T: AuditLogger> AuditLogger for &'a mut T { #[inline] fn log_response<'s, R>( - &'s mut self, + &'s self, reference: &'s str, response: &'s ReasonerResponse, raw: Option<&'s str>, @@ -183,7 +218,7 @@ impl<'a, T: AuditLogger> AuditLogger for &'a mut T { } #[inline] - fn log_question<'s, S, Q>(&'s mut self, reference: &'s str, state: &'s S, question: &'s Q) -> impl 's + Future> + fn log_question<'s, S, Q>(&'s self, reference: &'s str, state: &'s S, question: &'s Q) -> impl 's + Future> where S: Serialize, Q: Serialize, diff --git a/lib/spec/src/reasonerconn.rs b/lib/spec/src/reasonerconn.rs index 23ab1ba..3969715 100644 --- a/lib/spec/src/reasonerconn.rs +++ b/lib/spec/src/reasonerconn.rs @@ -4,7 +4,7 @@ // Created: // 09 Oct 2024, 13:35:41 // Last edited: -// 17 Oct 2024, 09:50:17 +// 05 Nov 2024, 10:43:44 // Auto updated? // Yes // @@ -66,7 +66,7 @@ pub trait ReasonerConnector { &self, state: Self::State, question: Self::Question, - logger: &mut SessionedAuditLogger, + logger: &SessionedAuditLogger, ) -> impl Future, Self::Error>> where L: AuditLogger; From 6a63077afd764ea6da3ba723a7685f1b25876d76 Mon Sep 17 00:00:00 2001 From: Lut99 Date: Tue, 5 Nov 2024 11:02:24 +0100 Subject: [PATCH 13/82] Added appropriate async bounds to `StateResolver::resolve()` --- lib/resolvers/file/src/resolver.rs | 21 ++++++++++++++------- lib/spec/src/stateresolver.rs | 22 +++++++++++++++------- 2 files changed, 29 insertions(+), 14 deletions(-) diff --git a/lib/resolvers/file/src/resolver.rs b/lib/resolvers/file/src/resolver.rs index 69d7d33..f972162 100644 --- a/lib/resolvers/file/src/resolver.rs +++ b/lib/resolvers/file/src/resolver.rs @@ -4,7 +4,7 @@ // Created: // 10 Oct 2024, 15:55:23 // Last edited: -// 10 Oct 2024, 16:10:46 +// 05 Nov 2024, 11:02:05 // Auto updated? // Yes // @@ -81,28 +81,35 @@ impl FileResolver { #[inline] pub fn new(path: impl Into) -> Self { Self { path: path.into(), _resolved: PhantomData } } } -impl Deserialize<'de>> StateResolver for FileResolver { +impl Deserialize<'de>> StateResolver for FileResolver { type Error = Error; type Resolved = R; type State = (); - fn resolve(&self, _state: Self::State, logger: &SessionedAuditLogger) -> impl Future> + fn resolve<'a, L>( + &'a self, + _state: Self::State, + logger: &'a SessionedAuditLogger, + ) -> impl 'a + Send + Future> where - L: AuditLogger, + L: Sync + AuditLogger, { async move { // NOTE: Using `#[instrument]` adds some unnecessary trait bounds on `S` and such. - let _span = span!(Level::INFO, "FileResolver::resolve", reference = logger.reference()).entered(); + // NOTE: Using `entered()` carries the scope across await points, which isn't correct. + // As we know `fs::read_to_string()` and `serde_json::from_str()` won't call + // tracing themselves, we only use the guard on the debugs themselves. + let span = span!(Level::INFO, "FileResolver::resolve", reference = logger.reference()); // Read the file in one go// Read the file in one go - debug!("Opening input file '{}'...", self.path.display()); + span.in_scope(|| debug!("Opening input file '{}'...", self.path.display())); let state: String = match fs::read_to_string(&self.path).await { Ok(state) => state, Err(err) => return Err(Error::FileRead { path: self.path.clone(), err }), }; // Parse it as JSON - debug!("Parsing input file '{}'...", self.path.display()); + span.in_scope(|| debug!("Parsing input file '{}'...", self.path.display())); match serde_json::from_str(&state) { Ok(state) => Ok(state), Err(err) => Err(Error::FileDeserialize { to: std::any::type_name::(), path: self.path.clone(), err }), diff --git a/lib/spec/src/stateresolver.rs b/lib/spec/src/stateresolver.rs index e47bfbe..b481b79 100644 --- a/lib/spec/src/stateresolver.rs +++ b/lib/spec/src/stateresolver.rs @@ -4,7 +4,7 @@ // Created: // 10 Oct 2024, 14:57:24 // Last edited: -// 10 Oct 2024, 16:07:13 +// 05 Nov 2024, 10:55:01 // Auto updated? // Yes // @@ -52,16 +52,20 @@ macro_rules! tuple_impl { impl] $(, [])*> StateResolver for ([] $(, [])*) where E: std::error::Error, - []: StateResolver, - $([]: StateResolver]::Resolved, Error = E>,)* + []: Sync + StateResolver, + []::State: Send, + $( + []: Sync + StateResolver]::Resolved, Error = E>, + []::State: Send, + )* { type State = []::State; type Resolved = []::Resolved; type Error = E; - fn resolve(&self, state: Self::State, logger: &SessionedAuditLogger) -> impl Future> + fn resolve<'a, L>(&'a self, state: Self::State, logger: &'a SessionedAuditLogger) -> impl 'a + Send + Future> where - L: AuditLogger, + L: Sync + AuditLogger, { async move { let resolved: []::Resolved = self.$fi.resolve(state, logger).await?; @@ -100,9 +104,13 @@ pub trait StateResolver { /// /// # Errors /// This function may error if it failed to do its resolution. - fn resolve(&self, state: Self::State, logger: &SessionedAuditLogger) -> impl Future> + fn resolve<'a, L>( + &'a self, + state: Self::State, + logger: &'a SessionedAuditLogger, + ) -> impl 'a + Send + Future> where - L: AuditLogger; + L: Sync + AuditLogger; } // Default impls From 80447a08c3fe0e4ed26b1231b9a3241ea34c4b1c Mon Sep 17 00:00:00 2001 From: Lut99 Date: Tue, 5 Nov 2024 11:17:42 +0100 Subject: [PATCH 14/82] Added proper async bounds to `ReasonerConnector` and `AuditLogger`. --- lib/loggers/file/src/logger.rs | 21 +++-- lib/reasoners/eflint-json/src/reasonerconn.rs | 25 +++-- lib/reasoners/no-op/src/reasonerconn.rs | 19 ++-- lib/reasoners/posix/src/reasonerconn.rs | 17 ++-- lib/resolvers/file/src/resolver.rs | 15 +-- lib/spec/src/auditlogger.rs | 92 ++++++++++++------- lib/spec/src/reasonerconn.rs | 12 +-- 7 files changed, 111 insertions(+), 90 deletions(-) diff --git a/lib/loggers/file/src/logger.rs b/lib/loggers/file/src/logger.rs index 7896ece..70db778 100644 --- a/lib/loggers/file/src/logger.rs +++ b/lib/loggers/file/src/logger.rs @@ -4,7 +4,7 @@ // Created: // 10 Oct 2024, 14:16:24 // Last edited: -// 05 Nov 2024, 10:43:25 +// 05 Nov 2024, 11:13:00 // Auto updated? // Yes // @@ -202,9 +202,9 @@ impl AuditLogger for FileLogger { type Error = Error; #[inline] - fn log_context<'a, C>(&'a self, context: &'a C) -> impl 'a + Future> + fn log_context<'a, C>(&'a self, context: &'a C) -> impl 'a + Send + Future> where - C: ?Sized + Context, + C: ?Sized + Sync + Context, { async move { // Serialize the context first @@ -227,9 +227,9 @@ impl AuditLogger for FileLogger { reference: &'a str, response: &'a ReasonerResponse, raw: Option<&'a str>, - ) -> impl 'a + Future> + ) -> impl 'a + Send + Future> where - R: Display, + R: Sync + Display, { async move { #[cfg(debug_assertions)] @@ -252,10 +252,15 @@ impl AuditLogger for FileLogger { } #[inline] - fn log_question<'a, S, Q>(&'a self, reference: &'a str, state: &'a S, question: &'a Q) -> impl 'a + Future> + fn log_question<'a, S, Q>( + &'a self, + reference: &'a str, + state: &'a S, + question: &'a Q, + ) -> impl 'a + Send + Future> where - S: Serialize, - Q: Serialize, + S: Sync + Serialize, + Q: Sync + Serialize, { async move { #[cfg(debug_assertions)] diff --git a/lib/reasoners/eflint-json/src/reasonerconn.rs b/lib/reasoners/eflint-json/src/reasonerconn.rs index 22f5346..a9d528b 100644 --- a/lib/reasoners/eflint-json/src/reasonerconn.rs +++ b/lib/reasoners/eflint-json/src/reasonerconn.rs @@ -4,7 +4,7 @@ // Created: // 09 Oct 2024, 15:52:06 // Last edited: -// 05 Nov 2024, 10:44:14 +// 05 Nov 2024, 11:17:24 // Auto updated? // Yes // @@ -24,7 +24,7 @@ use serde::{Deserialize, Serialize}; use spec::auditlogger::{AuditLogger, SessionedAuditLogger}; use spec::reasonerconn::{ReasonerConnector, ReasonerResponse}; use thiserror::Error; -use tracing::{Level, debug, span}; +use tracing::{Instrument as _, Level, debug, span}; use crate::reasons::ReasonHandler; use crate::spec::EFlintable; @@ -199,12 +199,12 @@ impl EFlintJsonReasonerConnector { } impl ReasonerConnector for EFlintJsonReasonerConnector where - R: ReasonHandler, - R::Reason: Display, + R: Sync + ReasonHandler, + R::Reason: Send + Sync + Display, R::Error: 'static, - S: EFlintable + Serialize, + S: Send + Sync + EFlintable + Serialize, S::Error: 'static, - Q: EFlintable + Serialize, + Q: Send + Sync + EFlintable + Serialize, Q::Error: 'static, { type Error = Error; @@ -212,18 +212,16 @@ where type Reason = R::Reason; type State = State; - fn consult( - &self, + fn consult<'a, L>( + &'a self, state: Self::State, question: Self::Question, - logger: &SessionedAuditLogger, - ) -> impl Future, Self::Error>> + logger: &'a SessionedAuditLogger, + ) -> impl 'a + Send + Future, Self::Error>> where - L: AuditLogger, + L: Sync + AuditLogger, { async move { - // NOTE: Using `#[instrument]` adds some unnecessary trait bounds on `S` and such. - let _span = span!(Level::INFO, "EFlintJsonReasonerConnector::consult", reference = logger.reference()).entered(); logger .log_question(&state, &question) .await @@ -307,5 +305,6 @@ where debug!("Final reasoner verdict: {verdict:?}"); Ok(verdict) } + .instrument(span!(Level::INFO, "EFlintJsonReasonerConnector::consult", reference = logger.reference())) } } diff --git a/lib/reasoners/no-op/src/reasonerconn.rs b/lib/reasoners/no-op/src/reasonerconn.rs index 2cb42a0..8ad28e6 100644 --- a/lib/reasoners/no-op/src/reasonerconn.rs +++ b/lib/reasoners/no-op/src/reasonerconn.rs @@ -4,7 +4,7 @@ // Created: // 10 Oct 2024, 16:21:09 // Last edited: -// 05 Nov 2024, 10:43:39 +// 05 Nov 2024, 11:14:47 // Auto updated? // Yes // @@ -21,7 +21,7 @@ use spec::auditlogger::SessionedAuditLogger; use spec::reasonerconn::ReasonerResponse; use spec::{AuditLogger, ReasonerConnector}; use thiserror::Error; -use tracing::{Level, debug, span}; +use tracing::{Instrument as _, Level, debug, span}; /***** ERRORS *****/ @@ -69,25 +69,23 @@ impl NoOpReasonerConnector { } impl ReasonerConnector for NoOpReasonerConnector where - Q: Serialize, + Q: Send + Sync + Serialize, { type Error = Error; type Question = Q; type Reason = (); type State = (); - fn consult( - &self, + fn consult<'a, L>( + &'a self, state: Self::State, question: Self::Question, - logger: &SessionedAuditLogger, - ) -> impl Future, Self::Error>> + logger: &'a SessionedAuditLogger, + ) -> impl 'a + Send + Future, Self::Error>> where - L: AuditLogger, + L: Sync + AuditLogger, { async move { - // NOTE: Using `#[instrument]` adds some unnecessary trait bounds on `S` and such. - let _span = span!(Level::INFO, "NoOpReasonerConnector::consult", reference = logger.reference()).entered(); debug!("NoOpReasonerConnector: request received"); // Log that the question has been asked @@ -104,5 +102,6 @@ where Ok(ReasonerResponse::Success) } + .instrument(span!(Level::INFO, "NoOpReasonerConnector::consult", reference = logger.reference())) } } diff --git a/lib/reasoners/posix/src/reasonerconn.rs b/lib/reasoners/posix/src/reasonerconn.rs index 3db6387..b3fa998 100644 --- a/lib/reasoners/posix/src/reasonerconn.rs +++ b/lib/reasoners/posix/src/reasonerconn.rs @@ -4,7 +4,7 @@ // Created: // 11 Oct 2024, 16:54:51 // Last edited: -// 05 Nov 2024, 10:44:07 +// 05 Nov 2024, 11:15:59 // Auto updated? // Yes // @@ -27,7 +27,7 @@ use spec::reasonerconn::{ReasonerConnector, ReasonerResponse}; use spec::reasons::NoReason; use thiserror::Error; use tokio::fs; -use tracing::{Level, debug, info, span}; +use tracing::{Instrument as _, Level, debug, info, span}; use workflow::Workflow; use crate::config::{Config, DataPolicy, PosixLocalIdentity}; @@ -244,18 +244,16 @@ impl ReasonerConnector for PosixReasonerConnector { type State = State; #[inline] - fn consult( - &self, + fn consult<'a, L>( + &'a self, state: Self::State, _question: Self::Question, - logger: &SessionedAuditLogger, - ) -> impl Future, Self::Error>> + logger: &'a SessionedAuditLogger, + ) -> impl 'a + Send + Future, Self::Error>> where - L: AuditLogger, + L: Sync + AuditLogger, { async move { - let _span = span!(Level::INFO, "ReasonerConnector::consult", reference = logger.reference()).entered(); - // Log the input logger .log_question(&state, &()) @@ -297,6 +295,7 @@ impl ReasonerConnector for PosixReasonerConnector { .map_err(|err| Error::LogResponse { to: std::any::type_name::>(), err: err.freeze() })?; Ok(ReasonerResponse::Success) } + .instrument(span!(Level::INFO, "ReasonerConnector::consult", reference = logger.reference())) } } diff --git a/lib/resolvers/file/src/resolver.rs b/lib/resolvers/file/src/resolver.rs index f972162..1a0e6e8 100644 --- a/lib/resolvers/file/src/resolver.rs +++ b/lib/resolvers/file/src/resolver.rs @@ -4,7 +4,7 @@ // Created: // 10 Oct 2024, 15:55:23 // Last edited: -// 05 Nov 2024, 11:02:05 +// 05 Nov 2024, 11:15:26 // Auto updated? // Yes // @@ -23,7 +23,7 @@ use spec::AuditLogger; use spec::auditlogger::SessionedAuditLogger; use spec::stateresolver::StateResolver; use tokio::fs; -use tracing::{Level, debug, span}; +use tracing::{Instrument as _, Level, debug, span}; /***** ERRORS *****/ @@ -95,25 +95,20 @@ impl Deserialize<'de>> StateResolver for FileResolver { L: Sync + AuditLogger, { async move { - // NOTE: Using `#[instrument]` adds some unnecessary trait bounds on `S` and such. - // NOTE: Using `entered()` carries the scope across await points, which isn't correct. - // As we know `fs::read_to_string()` and `serde_json::from_str()` won't call - // tracing themselves, we only use the guard on the debugs themselves. - let span = span!(Level::INFO, "FileResolver::resolve", reference = logger.reference()); - // Read the file in one go// Read the file in one go - span.in_scope(|| debug!("Opening input file '{}'...", self.path.display())); + debug!("Opening input file '{}'...", self.path.display()); let state: String = match fs::read_to_string(&self.path).await { Ok(state) => state, Err(err) => return Err(Error::FileRead { path: self.path.clone(), err }), }; // Parse it as JSON - span.in_scope(|| debug!("Parsing input file '{}'...", self.path.display())); + debug!("Parsing input file '{}'...", self.path.display()); match serde_json::from_str(&state) { Ok(state) => Ok(state), Err(err) => Err(Error::FileDeserialize { to: std::any::type_name::(), path: self.path.clone(), err }), } } + .instrument(span!(Level::INFO, "FileResolver::resolve", reference = logger.reference())) } } diff --git a/lib/spec/src/auditlogger.rs b/lib/spec/src/auditlogger.rs index 554fa4b..96bc3f6 100644 --- a/lib/spec/src/auditlogger.rs +++ b/lib/spec/src/auditlogger.rs @@ -4,7 +4,7 @@ // Created: // 09 Oct 2024, 13:38:41 // Last edited: -// 05 Nov 2024, 10:23:16 +// 05 Nov 2024, 11:13:21 // Auto updated? // Yes // @@ -60,9 +60,9 @@ impl SessionedAuditLogger { &'a self, response: &'a ReasonerResponse, raw: Option<&'a str>, - ) -> impl 'a + Future::Error>> + ) -> impl 'a + Send + Future::Error>> where - R: Display, + R: Sync + Display, { L::log_response(&self.logger, &self.reference, response, raw) } @@ -72,10 +72,14 @@ impl SessionedAuditLogger { /// # Arguments /// - `state`: Some serializable state given as input to the reasoner. /// - `question`: Some serializable question that we're asking. - pub fn log_question<'a, S, Q>(&'a self, state: &'a S, question: &'a Q) -> impl 'a + Future::Error>> + pub fn log_question<'a, S, Q>( + &'a self, + state: &'a S, + question: &'a Q, + ) -> impl 'a + Send + Future::Error>> where - S: Serialize, - Q: Serialize, + S: Sync + Serialize, + Q: Sync + Serialize, { L::log_question(&self.logger, &self.reference, state, question) } @@ -83,9 +87,9 @@ impl SessionedAuditLogger { impl AuditLogger for SessionedAuditLogger { type Error = L::Error; - fn log_context<'a, C>(&'a self, context: &'a C) -> impl 'a + Future> + fn log_context<'a, C>(&'a self, context: &'a C) -> impl 'a + Send + Future> where - C: ?Sized + Context, + C: ?Sized + Sync + Context, { L::log_context(&self.logger, context) } @@ -95,17 +99,22 @@ impl AuditLogger for SessionedAuditLogger { reference: &'a str, response: &'a ReasonerResponse, raw: Option<&'a str>, - ) -> impl 'a + Future> + ) -> impl 'a + Send + Future> where - R: Display, + R: Sync + Display, { L::log_response(&self.logger, reference, response, raw) } - fn log_question<'a, S, Q>(&'a self, reference: &'a str, state: &'a S, question: &'a Q) -> impl 'a + Future> + fn log_question<'a, S, Q>( + &'a self, + reference: &'a str, + state: &'a S, + question: &'a Q, + ) -> impl 'a + Send + Future> where - S: Serialize, - Q: Serialize, + S: Sync + Serialize, + Q: Sync + Serialize, { L::log_question(&self.logger, reference, state, question) } @@ -128,9 +137,9 @@ pub trait AuditLogger { /// /// # Arguments /// - `context`: Something [`Serialize`]able that we want to write at startup. - fn log_context<'a, C>(&'a self, context: &'a C) -> impl 'a + Future> + fn log_context<'a, C>(&'a self, context: &'a C) -> impl 'a + Send + Future> where - C: ?Sized + Context; + C: ?Sized + Sync + Context; /// Log the response of a reasoner. /// @@ -143,9 +152,9 @@ pub trait AuditLogger { reference: &'a str, response: &'a ReasonerResponse, raw: Option<&'a str>, - ) -> impl 'a + Future> + ) -> impl 'a + Send + Future> where - R: Display; + R: Sync + Display; /// Logs that the reasoner is being asked a question. /// @@ -153,10 +162,15 @@ pub trait AuditLogger { /// - `reference`: Some reference that links the response to a particular answer. /// - `state`: Some serializable state given as input to the reasoner. /// - `question`: Some serializable question that we're asking. - fn log_question<'a, S, Q>(&'a self, reference: &'a str, state: &'a S, question: &'a Q) -> impl 'a + Future> + fn log_question<'a, S, Q>( + &'a self, + reference: &'a str, + state: &'a S, + question: &'a Q, + ) -> impl 'a + Send + Future> where - S: Serialize, - Q: Serialize; + S: Sync + Serialize, + Q: Sync + Serialize; } // Standard impls @@ -164,9 +178,9 @@ impl<'a, T: AuditLogger> AuditLogger for &'a T { type Error = T::Error; #[inline] - fn log_context<'s, C>(&'s self, context: &'s C) -> impl 's + Future> + fn log_context<'s, C>(&'s self, context: &'s C) -> impl 's + Send + Future> where - C: ?Sized + Context, + C: ?Sized + Sync + Context, { ::log_context(self, context) } @@ -177,18 +191,23 @@ impl<'a, T: AuditLogger> AuditLogger for &'a T { reference: &'s str, response: &'s ReasonerResponse, raw: Option<&'s str>, - ) -> impl 's + Future> + ) -> impl 's + Send + Future> where - R: Display, + R: Sync + Display, { ::log_response(self, reference, response, raw) } #[inline] - fn log_question<'s, S, Q>(&'s self, reference: &'s str, state: &'s S, question: &'s Q) -> impl 's + Future> + fn log_question<'s, S, Q>( + &'s self, + reference: &'s str, + state: &'s S, + question: &'s Q, + ) -> impl 's + Send + Future> where - S: Serialize, - Q: Serialize, + S: Sync + Serialize, + Q: Sync + Serialize, { ::log_question(self, reference, state, question) } @@ -197,9 +216,9 @@ impl<'a, T: AuditLogger> AuditLogger for &'a mut T { type Error = T::Error; #[inline] - fn log_context<'s, C>(&'s self, context: &'s C) -> impl 's + Future> + fn log_context<'s, C>(&'s self, context: &'s C) -> impl 's + Send + Future> where - C: ?Sized + Context, + C: ?Sized + Sync + Context, { ::log_context(self, context) } @@ -210,18 +229,23 @@ impl<'a, T: AuditLogger> AuditLogger for &'a mut T { reference: &'s str, response: &'s ReasonerResponse, raw: Option<&'s str>, - ) -> impl 's + Future> + ) -> impl 's + Send + Future> where - R: Display, + R: Sync + Display, { ::log_response(self, reference, response, raw) } #[inline] - fn log_question<'s, S, Q>(&'s self, reference: &'s str, state: &'s S, question: &'s Q) -> impl 's + Future> + fn log_question<'s, S, Q>( + &'s self, + reference: &'s str, + state: &'s S, + question: &'s Q, + ) -> impl 's + Send + Future> where - S: Serialize, - Q: Serialize, + S: Sync + Serialize, + Q: Sync + Serialize, { ::log_question(self, reference, state, question) } diff --git a/lib/spec/src/reasonerconn.rs b/lib/spec/src/reasonerconn.rs index 3969715..732280d 100644 --- a/lib/spec/src/reasonerconn.rs +++ b/lib/spec/src/reasonerconn.rs @@ -4,7 +4,7 @@ // Created: // 09 Oct 2024, 13:35:41 // Last edited: -// 05 Nov 2024, 10:43:44 +// 05 Nov 2024, 11:06:39 // Auto updated? // Yes // @@ -62,12 +62,12 @@ pub trait ReasonerConnector { /// /// # Errors /// This function may error if the reasoner was unreachable or did not respond (correctly). - fn consult( - &self, + fn consult<'a, L>( + &'a self, state: Self::State, question: Self::Question, - logger: &SessionedAuditLogger, - ) -> impl Future, Self::Error>> + logger: &'a SessionedAuditLogger, + ) -> impl 'a + Send + Future, Self::Error>> where - L: AuditLogger; + L: Sync + AuditLogger; } From 7ce7bff25abf0120a629bc2eb1cc9ae7360fbc77 Mon Sep 17 00:00:00 2001 From: Lut99 Date: Wed, 6 Nov 2024 14:25:31 +0100 Subject: [PATCH 15/82] Removed the mutability requirement on `logger` for the EFlintJsonReasonerConnector --- lib/reasoners/eflint-json/src/reasonerconn.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/reasoners/eflint-json/src/reasonerconn.rs b/lib/reasoners/eflint-json/src/reasonerconn.rs index a9d528b..a1afebb 100644 --- a/lib/reasoners/eflint-json/src/reasonerconn.rs +++ b/lib/reasoners/eflint-json/src/reasonerconn.rs @@ -4,7 +4,7 @@ // Created: // 09 Oct 2024, 15:52:06 // Last edited: -// 05 Nov 2024, 11:17:24 +// 06 Nov 2024, 14:25:12 // Auto updated? // Yes // @@ -176,7 +176,7 @@ impl EFlintJsonReasonerConnector { pub fn new_async<'l, L: AuditLogger>( addr: impl 'l + Into, handler: R, - logger: &'l mut L, + logger: &'l L, ) -> impl 'l + Future>> where R: 'l + ReasonHandler, From d6fa392272d1837a00073521c5170d8c48005e8b Mon Sep 17 00:00:00 2001 From: Lut99 Date: Wed, 6 Nov 2024 14:39:06 +0100 Subject: [PATCH 16/82] Added some additional impls for `EFlintable`. --- lib/reasoners/eflint-json/src/spec.rs | 90 +++++++++++++++++++++++++-- 1 file changed, 86 insertions(+), 4 deletions(-) diff --git a/lib/reasoners/eflint-json/src/spec.rs b/lib/reasoners/eflint-json/src/spec.rs index 2707be7..05b88a6 100644 --- a/lib/reasoners/eflint-json/src/spec.rs +++ b/lib/reasoners/eflint-json/src/spec.rs @@ -4,7 +4,7 @@ // Created: // 09 Oct 2024, 16:06:18 // Last edited: -// 10 Oct 2024, 14:06:45 +// 06 Nov 2024, 14:38:50 // Auto updated? // Yes // @@ -12,17 +12,36 @@ //! Defines some general interface for this crate. // +use std::borrow::Cow; use std::convert::Infallible; -use std::error::Error; +use std::error; use eflint_json::spec::Phrase; +use thiserror::Error; + + +/***** ERRORS *****/ +/// Special error that occurs when something cannot be serialized as eFLINT in a container of +/// those things. +#[derive(Debug, Error)] +#[error("Failed to serialize element {i} to eFLINT")] +pub struct Error { + /// The index of the failed element. + i: usize, + /// The nested error. + #[source] + err: E, +} + + + /***** LIBRARY *****/ /// Defines something that can be turned into eFLINT phrases. pub trait EFlintable { /// The error type returned when converting to eFLINT. - type Error: Error; + type Error: error::Error; /// Converts this state to eFLINT phrases. @@ -35,10 +54,73 @@ pub trait EFlintable { fn to_eflint(&self) -> Result, Self::Error>; } -// Default impls +// Practical impls impl EFlintable for () { type Error = Infallible; #[inline] fn to_eflint(&self) -> Result, Self::Error> { Ok(Vec::new()) } } + +// eFLINT impls +impl EFlintable for Phrase { + type Error = Infallible; + + #[inline] + fn to_eflint(&self) -> Result, Self::Error> { Ok(vec![self.clone()]) } +} + +// Pointer impls +impl<'a, T: EFlintable> EFlintable for &'a T { + type Error = T::Error; + + #[inline] + fn to_eflint(&self) -> Result, Self::Error> { ::to_eflint(self) } +} +impl<'a, T: EFlintable> EFlintable for &'a mut T { + type Error = T::Error; + + #[inline] + fn to_eflint(&self) -> Result, Self::Error> { ::to_eflint(self) } +} +impl<'a, T: Clone + EFlintable> EFlintable for Cow<'a, T> { + type Error = T::Error; + + #[inline] + fn to_eflint(&self) -> Result, Self::Error> { ::to_eflint(self) } +} + +// Container impls +impl EFlintable for [T] +where + T::Error: 'static, +{ + type Error = Error; + + #[inline] + fn to_eflint(&self) -> Result, Self::Error> { + let mut res: Vec = Vec::with_capacity(self.len()); + for (i, e) in self.iter().enumerate() { + res.extend(e.to_eflint().map_err(|err| Error { i, err })?); + } + Ok(res) + } +} +impl EFlintable for [T; LEN] +where + T::Error: 'static, +{ + type Error = Error; + + #[inline] + fn to_eflint(&self) -> Result, Self::Error> { <[T] as EFlintable>::to_eflint(self) } +} +impl EFlintable for Vec +where + T::Error: 'static, +{ + type Error = Error; + + #[inline] + fn to_eflint(&self) -> Result, Self::Error> { <[T] as EFlintable>::to_eflint(self) } +} From 3b9993f503b22286ce31a4d32e977a83a36ec188 Mon Sep 17 00:00:00 2001 From: Lut99 Date: Wed, 6 Nov 2024 14:42:01 +0100 Subject: [PATCH 17/82] Relaxed pointer-like constraints on `EFlintable` impls --- lib/reasoners/eflint-json/src/spec.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/reasoners/eflint-json/src/spec.rs b/lib/reasoners/eflint-json/src/spec.rs index 05b88a6..64c9796 100644 --- a/lib/reasoners/eflint-json/src/spec.rs +++ b/lib/reasoners/eflint-json/src/spec.rs @@ -4,7 +4,7 @@ // Created: // 09 Oct 2024, 16:06:18 // Last edited: -// 06 Nov 2024, 14:38:50 +// 06 Nov 2024, 14:41:36 // Auto updated? // Yes // @@ -71,19 +71,19 @@ impl EFlintable for Phrase { } // Pointer impls -impl<'a, T: EFlintable> EFlintable for &'a T { +impl<'a, T: ?Sized + EFlintable> EFlintable for &'a T { type Error = T::Error; #[inline] fn to_eflint(&self) -> Result, Self::Error> { ::to_eflint(self) } } -impl<'a, T: EFlintable> EFlintable for &'a mut T { +impl<'a, T: ?Sized + EFlintable> EFlintable for &'a mut T { type Error = T::Error; #[inline] fn to_eflint(&self) -> Result, Self::Error> { ::to_eflint(self) } } -impl<'a, T: Clone + EFlintable> EFlintable for Cow<'a, T> { +impl<'a, T: ?Sized + EFlintable + ToOwned> EFlintable for Cow<'a, T> { type Error = T::Error; #[inline] From 9d0ed5000826adc0737553450689908b52b77f12 Mon Sep 17 00:00:00 2001 From: Lut99 Date: Wed, 6 Nov 2024 15:00:03 +0100 Subject: [PATCH 18/82] Removed `State` from the EFlintJsonReasoner --- examples/eflint_json.rs | 9 +++++---- lib/reasoners/eflint-json/src/reasonerconn.rs | 17 ++++------------- 2 files changed, 9 insertions(+), 17 deletions(-) diff --git a/examples/eflint_json.rs b/examples/eflint_json.rs index 8d57619..9186aca 100644 --- a/examples/eflint_json.rs +++ b/examples/eflint_json.rs @@ -4,7 +4,7 @@ // Created: // 10 Oct 2024, 13:54:17 // Last edited: -// 17 Oct 2024, 12:07:02 +// 06 Nov 2024, 14:59:22 // Auto updated? // Yes // @@ -18,11 +18,12 @@ use std::path::PathBuf; use clap::Parser; use console::style; +use eflint_json_reasoner::json::spec::Phrase; use error_trace::trace; use policy_reasoner::loggers::file::FileLogger; +use policy_reasoner::reasoners::eflint_json::EFlintJsonReasonerConnector; use policy_reasoner::reasoners::eflint_json::json::spec::RequestPhrases; use policy_reasoner::reasoners::eflint_json::reasons::EFlintSilentReasonHandler; -use policy_reasoner::reasoners::eflint_json::{EFlintJsonReasonerConnector, State}; use policy_reasoner::spec::auditlogger::SessionedAuditLogger; use policy_reasoner::spec::reasonerconn::ReasonerConnector as _; use policy_reasoner::spec::reasons::NoReason; @@ -181,7 +182,7 @@ async fn main() { }; // Create the reasoner - let conn = match EFlintJsonReasonerConnector::::new_async( + let conn = match EFlintJsonReasonerConnector::, ()>::new_async( &args.address, EFlintSilentReasonHandler, &mut logger, @@ -194,7 +195,7 @@ async fn main() { std::process::exit(1); }, }; - let verdict: ReasonerResponse = match conn.consult(State { policy: policy.phrases, state: () }, (), &mut logger).await { + let verdict: ReasonerResponse = match conn.consult(policy.phrases, (), &mut logger).await { Ok(res) => res, Err(err) => { error!("{}", trace!(("Failed to send message to reasoner at {:?}", args.address), err)); diff --git a/lib/reasoners/eflint-json/src/reasonerconn.rs b/lib/reasoners/eflint-json/src/reasonerconn.rs index a1afebb..8ddf425 100644 --- a/lib/reasoners/eflint-json/src/reasonerconn.rs +++ b/lib/reasoners/eflint-json/src/reasonerconn.rs @@ -4,7 +4,7 @@ // Created: // 09 Oct 2024, 15:52:06 // Last edited: -// 06 Nov 2024, 14:25:12 +// 06 Nov 2024, 14:59:35 // Auto updated? // Yes // @@ -130,15 +130,6 @@ impl spec::context::Context for Context { fn kind(&self) -> &str { "eflint-json" } } -/// Defines the eFLINT reasoner state to submit to it. -#[derive(Clone, Debug, Deserialize, Serialize)] -pub struct State { - /// The policy used. - pub policy: Vec, - /// The rest of the state that is appended to the end of the request. - pub state: S, -} - @@ -210,7 +201,7 @@ where type Error = Error; type Question = Q; type Reason = R::Reason; - type State = State; + type State = S; fn consult<'a, L>( &'a self, @@ -229,8 +220,8 @@ where // Build the full policy debug!("Building full policy..."); - let mut phrases: Vec = state.policy; - phrases.extend(state.state.to_eflint().map_err(|err| Error::StateToEFlint { err })?); + let mut phrases: Vec = Vec::new(); + phrases.extend(state.to_eflint().map_err(|err| Error::StateToEFlint { err })?); phrases.extend(question.to_eflint().map_err(|err| Error::QuestionToEFlint { err })?); debug!("Full request length: {} phrase(s)", phrases.len()); From ffeec7e5feca5de03c087ea5e4916ff19eb31e4e Mon Sep 17 00:00:00 2001 From: Lut99 Date: Wed, 6 Nov 2024 15:04:05 +0100 Subject: [PATCH 19/82] Added `serde` impls for `NoReason` and `ManyReason` --- lib/reasoners/eflint-json/src/reasons.rs | 2 +- lib/reasoners/eflint-json/src/spec.rs | 2 +- lib/spec/src/reasons.rs | 8 +++++--- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/lib/reasoners/eflint-json/src/reasons.rs b/lib/reasoners/eflint-json/src/reasons.rs index 7c6d17e..d2692d1 100644 --- a/lib/reasoners/eflint-json/src/reasons.rs +++ b/lib/reasoners/eflint-json/src/reasons.rs @@ -4,7 +4,7 @@ // Created: // 09 Oct 2024, 16:37:52 // Last edited: -// 17 Oct 2024, 09:55:31 +// 06 Nov 2024, 15:03:18 // Auto updated? // Yes // diff --git a/lib/reasoners/eflint-json/src/spec.rs b/lib/reasoners/eflint-json/src/spec.rs index 64c9796..62775d9 100644 --- a/lib/reasoners/eflint-json/src/spec.rs +++ b/lib/reasoners/eflint-json/src/spec.rs @@ -4,7 +4,7 @@ // Created: // 09 Oct 2024, 16:06:18 // Last edited: -// 06 Nov 2024, 14:41:36 +// 06 Nov 2024, 15:03:26 // Auto updated? // Yes // diff --git a/lib/spec/src/reasons.rs b/lib/spec/src/reasons.rs index bf8ae92..985dd65 100644 --- a/lib/spec/src/reasons.rs +++ b/lib/spec/src/reasons.rs @@ -4,7 +4,7 @@ // Created: // 17 Oct 2024, 09:53:49 // Last edited: -// 17 Oct 2024, 09:54:09 +// 06 Nov 2024, 15:03:48 // Auto updated? // Yes // @@ -15,10 +15,12 @@ use std::fmt::{Display, Formatter, Result as FResult}; use std::ops::{Deref, DerefMut}; +use serde::{Deserialize, Serialize}; + /***** LIBRARY ****/ /// Represents that no reason is used. -#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] +#[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)] pub struct NoReason; impl Display for NoReason { #[inline] @@ -26,7 +28,7 @@ impl Display for NoReason { } /// Represents that multiple reasons can be given. -#[derive(Clone, Debug, Eq, Hash, PartialEq)] +#[derive(Clone, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)] pub struct ManyReason(Vec); impl Default for ManyReason { #[inline] From 256c0ecd80f5a9e86dd272aaccad62883f66fe60 Mon Sep 17 00:00:00 2001 From: Lut99 Date: Thu, 14 Nov 2024 15:25:42 +0100 Subject: [PATCH 20/82] Added `ManyReason::into_iter()` --- lib/spec/src/reasons.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/lib/spec/src/reasons.rs b/lib/spec/src/reasons.rs index 985dd65..917feea 100644 --- a/lib/spec/src/reasons.rs +++ b/lib/spec/src/reasons.rs @@ -4,7 +4,7 @@ // Created: // 17 Oct 2024, 09:53:49 // Last edited: -// 06 Nov 2024, 15:03:48 +// 14 Nov 2024, 15:25:23 // Auto updated? // Yes // @@ -52,6 +52,10 @@ impl ManyReason { /// A new ManyReason that doesn't have any reasons embedded in it yet but space for at least `capacity` reasons. #[inline] pub fn with_capacity(capacity: usize) -> Self { Self(Vec::with_capacity(capacity)) } + + /// Consumes the set and returns and iterator-by-ownership. + #[inline] + pub fn into_iter(self) -> std::vec::IntoIter { self.0.into_iter() } } impl Display for ManyReason { #[inline] From d47ff14fd18db1c487c703936c2985b127cdc1b0 Mon Sep 17 00:00:00 2001 From: Lut99 Date: Mon, 2 Dec 2024 14:35:02 +0100 Subject: [PATCH 21/82] Added the `ReasonerContext` --- examples/no_op.rs | 16 ++-- lib/loggers/file/src/logger.rs | 7 +- lib/loggers/no-op/src/logger.rs | 7 +- lib/reasoners/eflint-json/src/reasonerconn.rs | 69 +++++++++++++++--- lib/reasoners/no-op/src/reasonerconn.rs | 71 +++++++++++++++--- lib/reasoners/posix/src/reasonerconn.rs | 73 ++++++++++--------- lib/spec/src/auditlogger.rs | 13 ++-- lib/spec/src/context.rs | 49 ------------- lib/spec/src/lib.rs | 3 +- lib/spec/src/reasonerconn.rs | 45 +++++++++++- 10 files changed, 227 insertions(+), 126 deletions(-) delete mode 100644 lib/spec/src/context.rs diff --git a/examples/no_op.rs b/examples/no_op.rs index 6d3584b..23ad317 100644 --- a/examples/no_op.rs +++ b/examples/no_op.rs @@ -4,7 +4,7 @@ // Created: // 10 Oct 2024, 16:17:21 // Last edited: -// 17 Oct 2024, 12:05:45 +// 02 Dec 2024, 14:34:47 // Auto updated? // Yes // @@ -18,9 +18,9 @@ use console::style; use error_trace::trace; use policy_reasoner::loggers::file::FileLogger; use policy_reasoner::reasoners::no_op::NoOpReasonerConnector; +use policy_reasoner::spec::ReasonerConnector as _; use policy_reasoner::spec::auditlogger::SessionedAuditLogger; use policy_reasoner::spec::reasonerconn::ReasonerResponse; -use policy_reasoner::spec::{AuditLogger, ReasonerConnector as _}; use tracing::{Level, error, info}; @@ -61,13 +61,15 @@ async fn main() { // Create the logger let mut logger: SessionedAuditLogger = SessionedAuditLogger::new("test", FileLogger::new(format!("{} - v{}", env!("CARGO_BIN_NAME"), env!("CARGO_PKG_VERSION")), "./test.log")); - if let Err(err) = logger.log_context("no-op").await { - error!("{}", trace!(("Failed to log no-op reasoner context"), err)); - std::process::exit(1); - } // Run the reasoner - let conn: NoOpReasonerConnector<()> = NoOpReasonerConnector::new(); + let conn: NoOpReasonerConnector<()> = match NoOpReasonerConnector::new_async(&mut logger).await { + Ok(conn) => conn, + Err(err) => { + error!("{}", trace!(("Failed to setup no-op reasoner"), err)); + std::process::exit(1); + }, + }; let verdict: ReasonerResponse<()> = conn.consult((), (), &mut logger).await.unwrap(); // OK, report diff --git a/lib/loggers/file/src/logger.rs b/lib/loggers/file/src/logger.rs index 70db778..391b122 100644 --- a/lib/loggers/file/src/logger.rs +++ b/lib/loggers/file/src/logger.rs @@ -4,7 +4,7 @@ // Created: // 10 Oct 2024, 14:16:24 // Last edited: -// 05 Nov 2024, 11:13:00 +// 02 Dec 2024, 14:21:52 // Auto updated? // Yes // @@ -22,8 +22,7 @@ use enum_debug::EnumDebug as _; use serde::Serialize; use serde_json::Value; use spec::auditlogger::AuditLogger; -use spec::context::Context; -use spec::reasonerconn::ReasonerResponse; +use spec::reasonerconn::{ReasonerContext, ReasonerResponse}; use tokio::fs::{File, OpenOptions}; use tokio::io::AsyncWriteExt as _; use tracing::debug; @@ -204,7 +203,7 @@ impl AuditLogger for FileLogger { #[inline] fn log_context<'a, C>(&'a self, context: &'a C) -> impl 'a + Send + Future> where - C: ?Sized + Sync + Context, + C: ?Sized + Sync + ReasonerContext, { async move { // Serialize the context first diff --git a/lib/loggers/no-op/src/logger.rs b/lib/loggers/no-op/src/logger.rs index 5627526..fe8bc2f 100644 --- a/lib/loggers/no-op/src/logger.rs +++ b/lib/loggers/no-op/src/logger.rs @@ -4,7 +4,7 @@ // Created: // 10 Oct 2024, 14:46:33 // Last edited: -// 05 Nov 2024, 10:23:27 +// 02 Dec 2024, 14:21:38 // Auto updated? // Yes // @@ -18,8 +18,7 @@ use std::future::Future; use serde::Serialize; use spec::auditlogger::AuditLogger; -use spec::context::Context; -use spec::reasonerconn::ReasonerResponse; +use spec::reasonerconn::{ReasonerContext, ReasonerResponse}; /***** LIBRARY *****/ @@ -43,7 +42,7 @@ impl AuditLogger for MockLogger { #[inline] fn log_context<'a, C>(&'a self, _context: &'a C) -> impl 'a + Future> where - C: ?Sized + Context, + C: ?Sized + ReasonerContext, { async move { println!("AUDIT LOG: log_context"); diff --git a/lib/reasoners/eflint-json/src/reasonerconn.rs b/lib/reasoners/eflint-json/src/reasonerconn.rs index 8ddf425..942e0f2 100644 --- a/lib/reasoners/eflint-json/src/reasonerconn.rs +++ b/lib/reasoners/eflint-json/src/reasonerconn.rs @@ -4,7 +4,7 @@ // Created: // 09 Oct 2024, 15:52:06 // Last edited: -// 06 Nov 2024, 14:59:35 +// 02 Dec 2024, 14:32:35 // Auto updated? // Yes // @@ -12,6 +12,7 @@ //! Defines a [`ReasonerConnector`] for an eFLINT JSON reasoner. // +use std::borrow::Cow; use std::collections::HashMap; use std::fmt::Display; use std::future::Future; @@ -22,7 +23,7 @@ use eflint_json::spec::{Phrase, PhraseResult, Request, RequestCommon, RequestPhr use error_trace::{ErrorTrace as _, Trace}; use serde::{Deserialize, Serialize}; use spec::auditlogger::{AuditLogger, SessionedAuditLogger}; -use spec::reasonerconn::{ReasonerConnector, ReasonerResponse}; +use spec::reasonerconn::{ReasonerConnector, ReasonerContext, ReasonerResponse}; use thiserror::Error; use tracing::{Instrument as _, Level, debug, span}; @@ -120,14 +121,61 @@ pub enum Error { /***** AUXILLARY *****/ /// Defines the context for the eFLINT reasoner. -#[derive(Clone, Debug, Deserialize, Serialize)] -pub struct Context { - /// The address of the context. - addr: String, +#[derive(Clone, Copy, Debug, Deserialize, Serialize)] +pub struct EFlintJsonReasonerContext { + /// The version of the reasoner. + pub version: &'static str, + /// The language identifier of the reasoner. + pub language: &'static str, + /// The language version identifier of the reasoner. + pub language_version: &'static str, +} +impl Default for EFlintJsonReasonerContext { + #[inline] + fn default() -> Self { Self { version: env!("CARGO_PKG_VERSION"), language: "eflint-json", language_version: "0.1.0-srv" } } +} +impl ReasonerContext for EFlintJsonReasonerContext { + #[inline] + fn version(&self) -> Cow { Cow::Borrowed(self.version) } + + #[inline] + fn language(&self) -> Cow { Cow::Borrowed(self.language) } + + #[inline] + fn language_version(&self) -> Cow { Cow::Borrowed(self.language_version) } +} + +/// Defines a slightly more elaborate context for the eFLINT reasoner that's relevant for private +/// use, but not for public. +/// +/// Mostly exists for logging to the audit log. +#[derive(Clone, Copy, Debug, Serialize)] +pub struct EFlintJsonReasonerContextFull<'a> { + /// The normal context + pub context: EFlintJsonReasonerContext, + /// The address of the reasoner we're connecting to. + pub addr: &'a str, } -impl spec::context::Context for Context { +impl<'a> EFlintJsonReasonerContextFull<'a> { + /// Constructor for the EFlintJsonReasonerContextFull. + /// + /// # Arguments + /// - `addr`: The address of the physical reasoner we connect to. + /// + /// # Returns + /// A new EFlintJsonReasonerContextFull that can be logged. #[inline] - fn kind(&self) -> &str { "eflint-json" } + pub fn new(addr: &'a str) -> Self { Self { context: EFlintJsonReasonerContext::default(), addr } } +} +impl<'a> ReasonerContext for EFlintJsonReasonerContextFull<'a> { + #[inline] + fn version(&self) -> Cow { Cow::Borrowed(self.context.version) } + + #[inline] + fn language(&self) -> Cow { Cow::Borrowed(self.context.language) } + + #[inline] + fn language_version(&self) -> Cow { Cow::Borrowed(self.context.language_version) } } @@ -181,7 +229,7 @@ impl EFlintJsonReasonerConnector { async move { let addr: String = addr.into(); logger - .log_context(&Context { addr: addr.clone() }) + .log_context(&EFlintJsonReasonerContextFull::new(&addr)) .await .map_err(|err| Error::LogContext { to: std::any::type_name::(), err: err.freeze() })?; Ok(Self { addr, reason_handler: handler, _state: PhantomData, _question: PhantomData }) @@ -198,11 +246,14 @@ where Q: Send + Sync + EFlintable + Serialize, Q::Error: 'static, { + type Context = EFlintJsonReasonerContext; type Error = Error; type Question = Q; type Reason = R::Reason; type State = S; + fn context(&self) -> Self::Context { EFlintJsonReasonerContext::default() } + fn consult<'a, L>( &'a self, state: Self::State, diff --git a/lib/reasoners/no-op/src/reasonerconn.rs b/lib/reasoners/no-op/src/reasonerconn.rs index 8ad28e6..7b2aa97 100644 --- a/lib/reasoners/no-op/src/reasonerconn.rs +++ b/lib/reasoners/no-op/src/reasonerconn.rs @@ -4,7 +4,7 @@ // Created: // 10 Oct 2024, 16:21:09 // Last edited: -// 05 Nov 2024, 11:14:47 +// 02 Dec 2024, 14:33:59 // Auto updated? // Yes // @@ -12,13 +12,14 @@ //! // +use std::borrow::Cow; use std::future::Future; use std::marker::PhantomData; use error_trace::{ErrorTrace as _, Trace}; -use serde::Serialize; +use serde::{Deserialize, Serialize}; use spec::auditlogger::SessionedAuditLogger; -use spec::reasonerconn::ReasonerResponse; +use spec::reasonerconn::{ReasonerContext, ReasonerResponse}; use spec::{AuditLogger, ReasonerConnector}; use thiserror::Error; use tracing::{Instrument as _, Level, debug, span}; @@ -27,6 +28,13 @@ use tracing::{Instrument as _, Level, debug, span}; /***** ERRORS *****/ #[derive(Debug, Error)] pub enum Error { + /// Failed to log the reasoner's context to the given logger. + #[error("Failed to log the reasoner's context to {to}")] + LogContext { + to: &'static str, + #[source] + err: Trace, + }, /// Failed to log the reasoner's response to the given logger. #[error("Failed to log the reasoner's response to {to}")] LogResponse { @@ -47,6 +55,36 @@ pub enum Error { +/***** AUXILLARY *****/ +/// The [`ReasonerContext`] returned by the [`NoOpReasonerConnector`]. +#[derive(Clone, Copy, Debug, Deserialize, Serialize)] +pub struct NoOpReasonerContext { + /// The version of this reasoner. + pub version: &'static str, + /// The language identifier of this reasoner. + pub language: &'static str, + /// The language's version identifier of this reasoner. + pub language_version: &'static str, +} +impl Default for NoOpReasonerContext { + #[inline] + fn default() -> Self { Self { version: env!("CARGO_PKG_VERSION"), language: "no-op", language_version: "v1" } } +} +impl ReasonerContext for NoOpReasonerContext { + #[inline] + fn version(&self) -> Cow { Cow::Borrowed(self.version) } + + #[inline] + fn language(&self) -> Cow { Cow::Borrowed(self.language) } + + #[inline] + fn language_version(&self) -> Cow { Cow::Borrowed(self.language_version) } +} + + + + + /***** LIBRARY *****/ /// The minimal no-operation reasoner connector, that approves all validation requests by default (it does not check any /// policy/permissions). @@ -55,27 +93,40 @@ pub struct NoOpReasonerConnector { /// The completely arbitrary question that can be asked. _question: PhantomData, } -impl Default for NoOpReasonerConnector { - #[inline] - fn default() -> Self { Self::new() } -} impl NoOpReasonerConnector { /// Constructor for the NoOpReasonerConnector. /// - /// # Returns - /// A new connector, ready to allow anything in sight. + /// This constructor logs asynchronously. + /// + /// # Arguments + /// - `logger`: A logger to write this reasoner's context to. + /// + /// # Errors + /// This function may error if it failed to log to the given `logger`. #[inline] - pub fn new() -> Self { Self { _question: PhantomData } } + pub fn new_async<'l, L: AuditLogger>(logger: &'l mut L) -> impl 'l + Future> { + async move { + logger + .log_context(&NoOpReasonerContext::default()) + .await + .map_err(|err| Error::LogContext { to: std::any::type_name::(), err: err.freeze() })?; + Ok(Self { _question: PhantomData }) + } + } } impl ReasonerConnector for NoOpReasonerConnector where Q: Send + Sync + Serialize, { + type Context = NoOpReasonerContext; type Error = Error; type Question = Q; type Reason = (); type State = (); + #[inline] + fn context(&self) -> Self::Context { NoOpReasonerContext::default() } + fn consult<'a, L>( &'a self, state: Self::State, diff --git a/lib/reasoners/posix/src/reasonerconn.rs b/lib/reasoners/posix/src/reasonerconn.rs index b3fa998..c230d19 100644 --- a/lib/reasoners/posix/src/reasonerconn.rs +++ b/lib/reasoners/posix/src/reasonerconn.rs @@ -4,7 +4,7 @@ // Created: // 11 Oct 2024, 16:54:51 // Last edited: -// 05 Nov 2024, 11:15:59 +// 02 Dec 2024, 14:28:32 // Auto updated? // Yes // @@ -13,6 +13,7 @@ // +use std::borrow::Cow; /***** LIBRARY *****/ use std::future::Future; use std::iter::repeat; @@ -23,7 +24,7 @@ use std::path::{Path, PathBuf}; use error_trace::{ErrorTrace as _, Trace}; use serde::{Deserialize, Serialize}; use spec::auditlogger::{AuditLogger, SessionedAuditLogger}; -use spec::reasonerconn::{ReasonerConnector, ReasonerResponse}; +use spec::reasonerconn::{ReasonerConnector, ReasonerContext, ReasonerResponse}; use spec::reasons::NoReason; use thiserror::Error; use tokio::fs; @@ -205,7 +206,32 @@ impl BitOr for PosixFilePermission { -/***** LIBRARY *****/ +/***** AUXILLARY *****/ +/// The [`ReasonerContext`] returned by the [`PosixReasonerConnector`]. +#[derive(Clone, Copy, Debug, Deserialize, Serialize)] +pub struct PosixReasonerContext { + /// The version of this reasoner. + pub version: &'static str, + /// The language identifier of this reasoner. + pub language: &'static str, + /// The language's version identifier of this reasoner. + pub language_version: &'static str, +} +impl Default for PosixReasonerContext { + #[inline] + fn default() -> Self { Self { version: env!("CARGO_PKG_VERSION"), language: "posix", language_version: "0.2.0" } } +} +impl ReasonerContext for PosixReasonerContext { + #[inline] + fn version(&self) -> Cow { Cow::Borrowed(self.version) } + + #[inline] + fn language(&self) -> Cow { Cow::Borrowed(self.language) } + + #[inline] + fn language_version(&self) -> Cow { Cow::Borrowed(self.language_version) } +} + /// The overarching input to the POSIX reasoner. #[derive(Clone, Debug, Deserialize, Serialize)] pub struct State { @@ -217,6 +243,9 @@ pub struct State { + + +/***** LIBRARY *****/ /// The POSIX reasoner connector. This connector is used to validate workflows based on POSIX file permissions. pub struct PosixReasonerConnector; impl PosixReasonerConnector { @@ -232,17 +261,24 @@ impl PosixReasonerConnector { #[inline] pub fn new_async<'l, L: AuditLogger>(logger: &'l mut L) -> impl 'l + Future> { async move { - logger.log_context("posix").await.map_err(|err| Error::LogContext { to: std::any::type_name::(), err: err.freeze() })?; + logger + .log_context(&PosixReasonerContext::default()) + .await + .map_err(|err| Error::LogContext { to: std::any::type_name::(), err: err.freeze() })?; Ok(Self) } } } impl ReasonerConnector for PosixReasonerConnector { + type Context = PosixReasonerContext; type Error = Error; type Question = (); type Reason = NoReason; type State = State; + #[inline] + fn context(&self) -> Self::Context { PosixReasonerContext::default() } + #[inline] fn consult<'a, L>( &'a self, @@ -298,32 +334,3 @@ impl ReasonerConnector for PosixReasonerConnector { .instrument(span!(Level::INFO, "ReasonerConnector::consult", reference = logger.reference())) } } - -// /// The context of the POSIX reasoner connector. This context is used to identify the reasoner connector. -// /// See [`ConnectorContext`] and [`ConnectorWithContext`]. -// #[derive(Debug, Clone, serde::Serialize)] -// pub struct PosixReasonerConnectorContext { -// #[serde(rename = "type")] -// pub t: String, -// pub version: String, -// } - -// impl std::hash::Hash for PosixReasonerConnectorContext { -// fn hash(&self, state: &mut H) { -// self.t.hash(state); -// self.version.hash(state); -// } -// } - -// impl ConnectorContext for PosixReasonerConnectorContext { -// fn r#type(&self) -> String { self.t.clone() } - -// fn version(&self) -> String { self.version.clone() } -// } - -// impl ConnectorWithContext for PosixReasonerConnector { -// type Context = PosixReasonerConnectorContext; - -// #[inline] -// fn context() -> Self::Context { PosixReasonerConnectorContext { t: "posix".into(), version: "0.1.0".into() } } -// } diff --git a/lib/spec/src/auditlogger.rs b/lib/spec/src/auditlogger.rs index 96bc3f6..ec669c1 100644 --- a/lib/spec/src/auditlogger.rs +++ b/lib/spec/src/auditlogger.rs @@ -4,7 +4,7 @@ // Created: // 09 Oct 2024, 13:38:41 // Last edited: -// 05 Nov 2024, 11:13:21 +// 02 Dec 2024, 14:21:01 // Auto updated? // Yes // @@ -19,8 +19,7 @@ use std::future::Future; use serde::Serialize; -use crate::context::Context; -use crate::reasonerconn::ReasonerResponse; +use crate::reasonerconn::{ReasonerContext, ReasonerResponse}; /***** AUXILLARY *****/ @@ -89,7 +88,7 @@ impl AuditLogger for SessionedAuditLogger { fn log_context<'a, C>(&'a self, context: &'a C) -> impl 'a + Send + Future> where - C: ?Sized + Sync + Context, + C: ?Sized + Sync + ReasonerContext, { L::log_context(&self.logger, context) } @@ -139,7 +138,7 @@ pub trait AuditLogger { /// - `context`: Something [`Serialize`]able that we want to write at startup. fn log_context<'a, C>(&'a self, context: &'a C) -> impl 'a + Send + Future> where - C: ?Sized + Sync + Context; + C: ?Sized + Sync + ReasonerContext; /// Log the response of a reasoner. /// @@ -180,7 +179,7 @@ impl<'a, T: AuditLogger> AuditLogger for &'a T { #[inline] fn log_context<'s, C>(&'s self, context: &'s C) -> impl 's + Send + Future> where - C: ?Sized + Sync + Context, + C: ?Sized + Sync + ReasonerContext, { ::log_context(self, context) } @@ -218,7 +217,7 @@ impl<'a, T: AuditLogger> AuditLogger for &'a mut T { #[inline] fn log_context<'s, C>(&'s self, context: &'s C) -> impl 's + Send + Future> where - C: ?Sized + Sync + Context, + C: ?Sized + Sync + ReasonerContext, { ::log_context(self, context) } diff --git a/lib/spec/src/context.rs b/lib/spec/src/context.rs deleted file mode 100644 index e5bee6f..0000000 --- a/lib/spec/src/context.rs +++ /dev/null @@ -1,49 +0,0 @@ -// CONTEXT.rs -// by Lut99 -// -// Created: -// 17 Oct 2024, 11:17:05 -// Last edited: -// 17 Oct 2024, 11:20:02 -// Auto updated? -// Yes -// -// Description: -//! Defines how the context of a reasoner looks like. -// - -use std::borrow::Cow; - -pub use serde::Serialize; - - -/***** LIBRARY *****/ -/// Defines the context that is logged and deterministically determines reasoner behaviour. -/// -/// In other words, same context == same result given a state. -pub trait Context: Serialize { - /// Returns the (unique!) identifier of this reasoner. - fn kind(&self) -> &str; -} - -// Default impls for strings -impl Context for str { - #[inline] - fn kind(&self) -> &str { self } -} -impl<'a> Context for &'a str { - #[inline] - fn kind(&self) -> &str { self } -} -impl<'a> Context for &'a mut str { - #[inline] - fn kind(&self) -> &str { self } -} -impl<'a> Context for Cow<'a, str> { - #[inline] - fn kind(&self) -> &str { self } -} -impl Context for String { - #[inline] - fn kind(&self) -> &str { self.as_str() } -} diff --git a/lib/spec/src/lib.rs b/lib/spec/src/lib.rs index bc469fe..54a9570 100644 --- a/lib/spec/src/lib.rs +++ b/lib/spec/src/lib.rs @@ -4,7 +4,7 @@ // Created: // 09 Oct 2024, 13:37:15 // Last edited: -// 17 Oct 2024, 11:16:55 +// 02 Dec 2024, 14:20:39 // Auto updated? // Yes // @@ -14,7 +14,6 @@ // Declare the modules pub mod auditlogger; -pub mod context; pub mod reasonerconn; pub mod reasons; pub mod stateresolver; diff --git a/lib/spec/src/reasonerconn.rs b/lib/spec/src/reasonerconn.rs index 732280d..87d0e2f 100644 --- a/lib/spec/src/reasonerconn.rs +++ b/lib/spec/src/reasonerconn.rs @@ -4,7 +4,7 @@ // Created: // 09 Oct 2024, 13:35:41 // Last edited: -// 05 Nov 2024, 11:06:39 +// 02 Dec 2024, 14:21:21 // Auto updated? // Yes // @@ -12,6 +12,7 @@ //! Defines the interface to the backend reasoner. // +use std::borrow::Cow; use std::error::Error; use std::future::Future; @@ -21,6 +22,36 @@ use crate::auditlogger::{AuditLogger, SessionedAuditLogger}; /***** AUXILLARY *****/ +/// Defines the general information contained within a [`ReasonerConnector::Context`]. +pub trait ReasonerContext: Serialize { + /// Returns some identifier for the specific reasoner version. + /// + /// This is useful, because users may (want) to depend on specific non-language, but + /// yes-reasoner, features. + /// + /// # Returns + /// A string identifier denoting this reasoner's version. + fn version(&self) -> Cow; + + /// Returns some identifier of the language that's being used as backend. + /// + /// # Returns + /// A string identifier that tells users the language used. + fn language(&self) -> Cow; + + /// Returns some identifier of the specific language version being used as backend. + /// + /// This would usually be a semantic version number. However, it may also denote specific + /// dialects or additional extensions. + /// + /// # Returns + /// A string identifier that tells users which version of the backend + /// [language](ReasonerContext::language()) is being used. + fn language_version(&self) -> Cow; +} + + + /// Defines the result of a reasoner. /// /// # Generics @@ -40,6 +71,8 @@ pub enum ReasonerResponse { /***** LIBRARY *****/ /// Defines the interface with the backend reasoner. pub trait ReasonerConnector { + /// Some context returned that describes this reasoner for policy writers. + type Context: ReasonerContext; /// The type of state that this reasoner accepts. type State; /// The type of question that this reasoner accepts. @@ -50,6 +83,16 @@ pub trait ReasonerConnector { type Error: Error; + /// Retrieves some context of the connector that is relevant for people writing policy. + /// + /// Ideally, the context allows policy reasoners to uniquely identify everything they need to + /// know to write policy in the appropriate language, version and interface required by the + /// reasoner. + /// + /// # Returns + /// A [`Context`](ReasonerConnector::Context) that describes this context. + fn context(&self) -> Self::Context; + /// Sends a policy to the backend reasoner. /// /// # Arguments From 1817e4ca8539ac96c0ed6e0d2861317ba1bd71c9 Mon Sep 17 00:00:00 2001 From: Lut99 Date: Mon, 2 Dec 2024 15:50:02 +0100 Subject: [PATCH 22/82] Contexts are now actually deserializable --- lib/reasoners/eflint-json/src/reasonerconn.rs | 26 +++++++++---------- lib/reasoners/no-op/src/reasonerconn.rs | 18 ++++++------- lib/reasoners/posix/src/reasonerconn.rs | 18 ++++++------- 3 files changed, 31 insertions(+), 31 deletions(-) diff --git a/lib/reasoners/eflint-json/src/reasonerconn.rs b/lib/reasoners/eflint-json/src/reasonerconn.rs index 942e0f2..33cbad3 100644 --- a/lib/reasoners/eflint-json/src/reasonerconn.rs +++ b/lib/reasoners/eflint-json/src/reasonerconn.rs @@ -4,7 +4,7 @@ // Created: // 09 Oct 2024, 15:52:06 // Last edited: -// 02 Dec 2024, 14:32:35 +// 02 Dec 2024, 15:49:51 // Auto updated? // Yes // @@ -121,35 +121,35 @@ pub enum Error { /***** AUXILLARY *****/ /// Defines the context for the eFLINT reasoner. -#[derive(Clone, Copy, Debug, Deserialize, Serialize)] +#[derive(Clone, Debug, Deserialize, Serialize)] pub struct EFlintJsonReasonerContext { /// The version of the reasoner. - pub version: &'static str, + pub version: String, /// The language identifier of the reasoner. - pub language: &'static str, + pub language: String, /// The language version identifier of the reasoner. - pub language_version: &'static str, + pub language_version: String, } impl Default for EFlintJsonReasonerContext { #[inline] - fn default() -> Self { Self { version: env!("CARGO_PKG_VERSION"), language: "eflint-json", language_version: "0.1.0-srv" } } + fn default() -> Self { Self { version: env!("CARGO_PKG_VERSION").into(), language: "eflint-json".into(), language_version: "0.1.0-srv".into() } } } impl ReasonerContext for EFlintJsonReasonerContext { #[inline] - fn version(&self) -> Cow { Cow::Borrowed(self.version) } + fn version(&self) -> Cow { Cow::Borrowed(&self.version) } #[inline] - fn language(&self) -> Cow { Cow::Borrowed(self.language) } + fn language(&self) -> Cow { Cow::Borrowed(&self.language) } #[inline] - fn language_version(&self) -> Cow { Cow::Borrowed(self.language_version) } + fn language_version(&self) -> Cow { Cow::Borrowed(&self.language_version) } } /// Defines a slightly more elaborate context for the eFLINT reasoner that's relevant for private /// use, but not for public. /// /// Mostly exists for logging to the audit log. -#[derive(Clone, Copy, Debug, Serialize)] +#[derive(Clone, Debug, Serialize)] pub struct EFlintJsonReasonerContextFull<'a> { /// The normal context pub context: EFlintJsonReasonerContext, @@ -169,13 +169,13 @@ impl<'a> EFlintJsonReasonerContextFull<'a> { } impl<'a> ReasonerContext for EFlintJsonReasonerContextFull<'a> { #[inline] - fn version(&self) -> Cow { Cow::Borrowed(self.context.version) } + fn version(&self) -> Cow { Cow::Borrowed(&self.context.version) } #[inline] - fn language(&self) -> Cow { Cow::Borrowed(self.context.language) } + fn language(&self) -> Cow { Cow::Borrowed(&self.context.language) } #[inline] - fn language_version(&self) -> Cow { Cow::Borrowed(self.context.language_version) } + fn language_version(&self) -> Cow { Cow::Borrowed(&self.context.language_version) } } diff --git a/lib/reasoners/no-op/src/reasonerconn.rs b/lib/reasoners/no-op/src/reasonerconn.rs index 7b2aa97..5e7048a 100644 --- a/lib/reasoners/no-op/src/reasonerconn.rs +++ b/lib/reasoners/no-op/src/reasonerconn.rs @@ -4,7 +4,7 @@ // Created: // 10 Oct 2024, 16:21:09 // Last edited: -// 02 Dec 2024, 14:33:59 +// 02 Dec 2024, 15:49:08 // Auto updated? // Yes // @@ -57,28 +57,28 @@ pub enum Error { /***** AUXILLARY *****/ /// The [`ReasonerContext`] returned by the [`NoOpReasonerConnector`]. -#[derive(Clone, Copy, Debug, Deserialize, Serialize)] +#[derive(Clone, Debug, Deserialize, Serialize)] pub struct NoOpReasonerContext { /// The version of this reasoner. - pub version: &'static str, + pub version: String, /// The language identifier of this reasoner. - pub language: &'static str, + pub language: String, /// The language's version identifier of this reasoner. - pub language_version: &'static str, + pub language_version: String, } impl Default for NoOpReasonerContext { #[inline] - fn default() -> Self { Self { version: env!("CARGO_PKG_VERSION"), language: "no-op", language_version: "v1" } } + fn default() -> Self { Self { version: env!("CARGO_PKG_VERSION").into(), language: "no-op".into(), language_version: "v1".into() } } } impl ReasonerContext for NoOpReasonerContext { #[inline] - fn version(&self) -> Cow { Cow::Borrowed(self.version) } + fn version(&self) -> Cow { Cow::Borrowed(&self.version) } #[inline] - fn language(&self) -> Cow { Cow::Borrowed(self.language) } + fn language(&self) -> Cow { Cow::Borrowed(&self.language) } #[inline] - fn language_version(&self) -> Cow { Cow::Borrowed(self.language_version) } + fn language_version(&self) -> Cow { Cow::Borrowed(&self.language_version) } } diff --git a/lib/reasoners/posix/src/reasonerconn.rs b/lib/reasoners/posix/src/reasonerconn.rs index c230d19..7eda717 100644 --- a/lib/reasoners/posix/src/reasonerconn.rs +++ b/lib/reasoners/posix/src/reasonerconn.rs @@ -4,7 +4,7 @@ // Created: // 11 Oct 2024, 16:54:51 // Last edited: -// 02 Dec 2024, 14:28:32 +// 02 Dec 2024, 15:48:38 // Auto updated? // Yes // @@ -208,28 +208,28 @@ impl BitOr for PosixFilePermission { /***** AUXILLARY *****/ /// The [`ReasonerContext`] returned by the [`PosixReasonerConnector`]. -#[derive(Clone, Copy, Debug, Deserialize, Serialize)] +#[derive(Clone, Debug, Deserialize, Serialize)] pub struct PosixReasonerContext { /// The version of this reasoner. - pub version: &'static str, + pub version: String, /// The language identifier of this reasoner. - pub language: &'static str, + pub language: String, /// The language's version identifier of this reasoner. - pub language_version: &'static str, + pub language_version: String, } impl Default for PosixReasonerContext { #[inline] - fn default() -> Self { Self { version: env!("CARGO_PKG_VERSION"), language: "posix", language_version: "0.2.0" } } + fn default() -> Self { Self { version: env!("CARGO_PKG_VERSION").into(), language: "posix".into(), language_version: "0.2.0".into() } } } impl ReasonerContext for PosixReasonerContext { #[inline] - fn version(&self) -> Cow { Cow::Borrowed(self.version) } + fn version(&self) -> Cow { Cow::Borrowed(&self.version) } #[inline] - fn language(&self) -> Cow { Cow::Borrowed(self.language) } + fn language(&self) -> Cow { Cow::Borrowed(&self.language) } #[inline] - fn language_version(&self) -> Cow { Cow::Borrowed(self.language_version) } + fn language_version(&self) -> Cow { Cow::Borrowed(&self.language_version) } } /// The overarching input to the POSIX reasoner. From 9452ebc7fbfa1807465f509049e8783042b309fa Mon Sep 17 00:00:00 2001 From: Lut99 Date: Thu, 17 Apr 2025 00:07:36 +0200 Subject: [PATCH 23/82] (WIP) Started work on the Haskell interpreter It's late, I should have rebased first, I know. Let's call that a WIP too, eh? --- Cargo.lock | 57 +++- Cargo.toml | 18 +- lib/eflint-spec/Cargo.toml | 26 ++ lib/eflint-spec/src/lib.rs | 14 + lib/reasoners/eflint-haskell/Cargo.toml | 26 ++ lib/reasoners/eflint-haskell/src/lib.rs | 18 ++ .../eflint-haskell/src/reasonerconn.rs | 263 ++++++++++++++++++ .../eflint-haskell/src/reasonertrace.rs | 14 + lib/reasoners/eflint-haskell/src/spec.rs | 112 ++++++++ 9 files changed, 531 insertions(+), 17 deletions(-) create mode 100644 lib/eflint-spec/Cargo.toml create mode 100644 lib/eflint-spec/src/lib.rs create mode 100644 lib/reasoners/eflint-haskell/Cargo.toml create mode 100644 lib/reasoners/eflint-haskell/src/lib.rs create mode 100644 lib/reasoners/eflint-haskell/src/reasonerconn.rs create mode 100644 lib/reasoners/eflint-haskell/src/reasonertrace.rs create mode 100644 lib/reasoners/eflint-haskell/src/spec.rs diff --git a/Cargo.lock b/Cargo.lock index 2bfd813..4516924 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -193,9 +193,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.34" +version = "4.5.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e958897981290da2a852763fe9cdb89cd36977a5d729023127095fa94d95e2ff" +checksum = "40b6887a1d8685cebccf115538db5c0efe625ccac9696ad45c409d96566e910f" dependencies = [ "clap_builder", "clap_derive", @@ -203,9 +203,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.34" +version = "4.5.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83b0f35019843db2160b5bb19ae09b4e6411ac33fc6a712003c33e03090e2489" +checksum = "e0c66c08ce9f0c698cbce5c0279d0bb6ac936d8674174fe48f736533b964f59e" dependencies = [ "anstream", "anstyle", @@ -215,9 +215,9 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.5.32" +version = "4.5.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09176aae279615badda0765c0c0b3f6ed53f4709118af73cf4655d85d1530cd7" +checksum = "d2c7947ae4cc3d851207c1adb5b5e260ff0cca11446b1d6d1423788e442257ce" dependencies = [ "heck", "proc-macro2", @@ -306,6 +306,18 @@ dependencies = [ "syn", ] +[[package]] +name = "eflint-haskell-reasoner" +version = "0.1.0" +dependencies = [ + "error-trace 3.1.0", + "serde", + "specifications", + "thiserror 2.0.12", + "tokio", + "tracing", +] + [[package]] name = "eflint-json" version = "0.1.0" @@ -325,10 +337,14 @@ dependencies = [ "serde", "serde_json", "specifications", - "thiserror", + "thiserror 1.0.69", "tracing", ] +[[package]] +name = "eflint-spec" +version = "0.1.0" + [[package]] name = "eflint-to-json" version = "0.2.0" @@ -1039,7 +1055,7 @@ dependencies = [ "error-trace 3.1.0", "serde", "specifications", - "thiserror", + "thiserror 1.0.69", "tracing", ] @@ -1169,6 +1185,7 @@ version = "1.0.0" dependencies = [ "clap", "console", + "eflint-haskell-reasoner", "eflint-json-reasoner", "eflint-to-json", "error-trace 3.0.0", @@ -1199,7 +1216,7 @@ dependencies = [ "serde", "serde_json", "specifications", - "thiserror", + "thiserror 1.0.69", "tokio", "tracing", "workflow", @@ -1637,7 +1654,16 @@ version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" dependencies = [ - "thiserror-impl", + "thiserror-impl 1.0.69", +] + +[[package]] +name = "thiserror" +version = "2.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708" +dependencies = [ + "thiserror-impl 2.0.12", ] [[package]] @@ -1651,6 +1677,17 @@ dependencies = [ "syn", ] +[[package]] +name = "thiserror-impl" +version = "2.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "thread_local" version = "1.1.9" diff --git a/Cargo.toml b/Cargo.toml index a53c939..3f72f0d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,6 +2,7 @@ resolver = "2" members = [ # Implementations + "lib/reasoners/eflint-haskell", "lib/reasoners/eflint-json", "lib/reasoners/no-op", "lib/reasoners/posix", @@ -13,6 +14,7 @@ members = [ "lib/eflint-to-json", # Specifications + "lib/eflint-spec", "lib/spec", "lib/workflow" ] @@ -49,6 +51,7 @@ required-features = ["no-op-reasoner", "file-logger"] [dependencies] +eflint-haskell-reasoner = { path = "./lib/reasoners/eflint-haskell", optional = true } eflint-json-reasoner = { path = "./lib/reasoners/eflint-json", optional = true } no-op-reasoner = { path = "./lib/reasoners/no-op", optional = true } posix-reasoner = { path = "./lib/reasoners/posix", optional = true } @@ -61,13 +64,13 @@ workflow = { path = "./lib/workflow", optional = true } [dev-dependencies] -clap = { version = "4.5.20", features = ["derive"] } -console = "0.15.8" +clap = { version = "4.5.36", features = ["derive"] } +console = "0.15.11" error-trace = { git = "https://github.com/Lut99/error-trace-rs", tag = "v3.0.0" } -serde_json = "1.0.120" -tracing = "0.1.40" -tracing-subscriber = { version = "0.3.18", features = ["env-filter"] } -tokio = { version = "1.40.0", default-features = false, features = ["fs", "io-std", "macros", "rt"] } +serde_json = "1.0.140" +tracing = "0.1.41" +tracing-subscriber = { version = "0.3.19", features = ["env-filter"] } +tokio = { version = "1.44.2", default-features = false, features = ["fs", "io-std", "macros", "rt"] } [features] @@ -75,8 +78,9 @@ default = [] all = ["reasoners", "loggers", "resolvers", "auxillary"] -reasoners = ["eflint-json-reasoner", "no-op-reasoner", "posix-reasoner"] +reasoners = ["eflint-haskell-reasoner", "eflint-json-reasoner", "no-op-reasoner", "posix-reasoner"] # reasoners = ["eflint-json-reasoner", "no-op-reasoner"] +eflint-haskell-reasoner = ["dep:eflint-haskell-reasoner"] eflint-json-reasoner = ["dep:eflint-json-reasoner"] no-op-reasoner = ["dep:no-op-reasoner"] posix-reasoner = ["dep:posix-reasoner"] diff --git a/lib/eflint-spec/Cargo.toml b/lib/eflint-spec/Cargo.toml new file mode 100644 index 0000000..a7fefcf --- /dev/null +++ b/lib/eflint-spec/Cargo.toml @@ -0,0 +1,26 @@ +[package] +name = "eflint-spec" +description = "Implements some common eFLINT definitions used across various eFLINT reasoners." +edition = "2021" +authors = [ "Tim Müller" ] +version = "0.1.0" +repository.workspace = true +license.workspace = true + + +[dependencies] +# reqwest = { version = "0.12.0", features = ["json"] } +# serde = { version = "1.0.204", features = ["derive"] } +# serde_json = "1.0.120" +# thiserror = "1.0.61" +# tracing = "0.1.40" + +# eflint-json = { git = "https://gitlab.com/eflint/json-spec-rs", branch = "incorrect-is-invariant" } +# error-trace = { git = "https://github.com/Lut99/error-trace-rs", tag = "v3.1.0" } + +# # workflow = { path = "../../../reasoner-workflow", package = "policy-reasoner-workflow" } +# spec = { path = "../../spec", package = "specifications" } + + +[features] +default = [] diff --git a/lib/eflint-spec/src/lib.rs b/lib/eflint-spec/src/lib.rs new file mode 100644 index 0000000..cb361fb --- /dev/null +++ b/lib/eflint-spec/src/lib.rs @@ -0,0 +1,14 @@ +// LIB.rs +// by Lut99 +// +// Created: +// 16 Apr 2025, 23:16:06 +// Last edited: +// 16 Apr 2025, 23:19:01 +// Auto updated? +// Yes +// +// Description: +//! Implements some common eFLINT definitions used across various eFLINT +//! reasoners. +// diff --git a/lib/reasoners/eflint-haskell/Cargo.toml b/lib/reasoners/eflint-haskell/Cargo.toml new file mode 100644 index 0000000..9e98d81 --- /dev/null +++ b/lib/reasoners/eflint-haskell/Cargo.toml @@ -0,0 +1,26 @@ +[package] +name = "eflint-haskell-reasoner" +description = "Wraps the infamous Haskell interpreter to supply an eFLINT DSL backend." +edition = "2021" +authors = [ "Tim Müller" ] +version = "0.1.0" +repository.workspace = true +license.workspace = true + + +[dependencies] +# reqwest = { version = "0.12.0", features = ["json"] } +serde = { version = "1.0.204", features = ["derive"] } +# serde_json = "1.0.120" +thiserror = "2.0.12" +tokio = { version = "1.44.2", default-features = false, features = [] } +tracing = "0.1.40" + +# eflint-json = { git = "https://gitlab.com/eflint/json-spec-rs", branch = "incorrect-is-invariant" } +error-trace = { git = "https://github.com/Lut99/error-trace-rs", tag = "v3.1.0" } + +spec = { path = "../../spec", package = "specifications" } + + +[features] +default = [] diff --git a/lib/reasoners/eflint-haskell/src/lib.rs b/lib/reasoners/eflint-haskell/src/lib.rs new file mode 100644 index 0000000..8856bbc --- /dev/null +++ b/lib/reasoners/eflint-haskell/src/lib.rs @@ -0,0 +1,18 @@ +// LIB.rs +// by Lut99 +// +// Created: +// 16 Apr 2025, 23:09:00 +// Last edited: +// 16 Apr 2025, 23:43:01 +// Auto updated? +// Yes +// +// Description: +//! Wraps the infamous Haskell interpreter to supply an eFLINT DSL +//! backend. +// + +// Define the submodules +pub mod reasonerconn; +pub mod spec; diff --git a/lib/reasoners/eflint-haskell/src/reasonerconn.rs b/lib/reasoners/eflint-haskell/src/reasonerconn.rs new file mode 100644 index 0000000..b3109c6 --- /dev/null +++ b/lib/reasoners/eflint-haskell/src/reasonerconn.rs @@ -0,0 +1,263 @@ +// REASONERCONN.rs +// by Lut99 +// +// Created: +// 16 Apr 2025, 23:09:26 +// Last edited: +// 17 Apr 2025, 00:04:49 +// Auto updated? +// Yes +// +// Description: +//! Defines the actual [`ReasonerConnector`] that wraps the [Haskell +//! implementation](https://gitlab.com/eflint/haskell-implementation). +// + +use std::borrow::Cow; +use std::future::Future; +use std::marker::PhantomData; +use std::process::{ExitStatus, Stdio}; + +use error_trace::{ErrorTrace as _, Trace}; +use serde::{Deserialize, Serialize}; +use spec::auditlogger::SessionedAuditLogger; +use spec::reasonerconn::{ReasonerContext, ReasonerResponse}; +use spec::{AuditLogger, ReasonerConnector}; +use thiserror::Error; +use tokio::io::AsyncWriteExt; +use tokio::process::Command; +use tracing::debug; + +use crate::spec::{EFlintable, EFlintableExt as _}; + + +/***** ERRORS *****/ +/// Defines errors originating from the [`EFlintHaskellReasonerConnector`]. +#[derive(Debug, Error)] +pub enum Error { + /// Failed to log the context of the reasoner. + #[error("Failed to log the reasoner's context to {to}")] + LogContext { + to: &'static str, + #[source] + err: Trace, + }, + /// Failed to log the question to the given logger. + #[error("Failed to log the question to {to}")] + LogQuestion { + to: &'static str, + #[source] + err: Trace, + }, + + #[error("Empty REPL-command given")] + EmptyReplCommand, + + #[error("Failed to spawn command {cmd:?}")] + CommandSpawn { + cmd: Command, + #[source] + err: std::io::Error, + }, + #[error("Failed to write to subprocess stdin")] + CommandStdinWrite { + #[source] + err: std::io::Error, + }, + #[error("Failed to wait for command {cmd:?} to complete")] + CommandJoin { + cmd: Command, + #[source] + err: std::io::Error, + }, + #[error("Command {:?} failed with exit code {}\n\nstdout:\n{}\n{}\n{}\n\nstderr:\n{}\n{}\n{}\n", cmd, status.code().unwrap_or(-1), "-".repeat(80), stdout, "-".repeat(80), "-".repeat(80), stderr, "-".repeat(80))] + CommandFailure { cmd: Command, status: ExitStatus, stdout: String, stderr: String }, +} + + + + + +/***** AUXILLARY *****/ +/// Defines the public reasoner context for this reasoner. +/// +/// This does not include private details; [`EFlintHaskellReasonerContextFull`] contains everything +/// needed for the reasoner to run. +#[derive(Clone, Debug, Deserialize, Serialize)] +pub struct EFlintHaskellReasonerContext { + // Note: we keep these fields here to serialize/deserialize properly. + /// The version number of the reasoner itself. + pub version: String, + /// The language identifier of this reasoner. + pub language: String, + /// The version identifier of the language targeted by this reasoner. + pub language_version: String, +} +impl Default for EFlintHaskellReasonerContext { + #[inline] + fn default() -> Self { + Self { + version: env!("CARGO_PKG_VERSION").into(), + language: "eflint".into(), + // NOTE: This really needn't be accurate, but it's kind of hard to check the eFLINT version from the Haskell version atm. + language_version: "4.0.0.1".into(), + } + } +} +impl ReasonerContext for EFlintHaskellReasonerContext { + #[inline] + fn version(&self) -> Cow { Cow::Borrowed(&self.version) } + + #[inline] + fn language(&self) -> Cow { Cow::Borrowed(&self.language) } + + #[inline] + fn language_version(&self) -> Cow { Cow::Borrowed(&self.language_version) } +} + +/// Defines the full reasoner context for this reasoner. +/// +/// This includes private details. +#[derive(Clone, Debug, Deserialize, Serialize)] +pub struct EFlintHaskellReasonerContextFull { + /// The public part + pub public: EFlintHaskellReasonerContext, + + // The private part + /// A command to call the eFLINT reasoner. + pub cmd: (String, Vec), +} +impl ReasonerContext for EFlintHaskellReasonerContextFull { + #[inline] + fn version(&self) -> Cow { self.public.version() } + + #[inline] + fn language(&self) -> Cow { self.public.language() } + + #[inline] + fn language_version(&self) -> Cow { self.public.language_version() } +} + + + + + +/***** LIBRARY *****/ +/// Defines a connector to make the eFLINT Haskell implementation `policy-reasoner`-complaint. +#[derive(Clone, Debug)] +pub struct EFlintHaskellReasonerConnector { + /// The context for interpreting. + context: EFlintHaskellReasonerContextFull, + /// A handler for determining what kind of reasons to give back to the user. + handler: R, + + /// For us to remember the state we're configured for. + _state: PhantomData, + /// For us to remember the questions we're configured for. + _question: PhantomData, +} +impl EFlintHaskellReasonerConnector { + /// Constructor for the EFlintHaskellReasonerConnector. + /// + /// # Arguments + /// - `cmd`: Some command that is used to call the eFLINT reasoner. + /// - `handler`: Some [`ReasonHandler`] that can be used to determine what information to return to the user upon failure. + /// - `logger`: An [`AuditLogger`] for logging the reasoning context with. + /// + /// # Returns + /// A new EFlintHaskellReasonerConnector ready to reason. + /// + /// # Errors + /// This function can error if it failed to log the initial context to the given `logger`. + pub async fn new_async<'l, L: AuditLogger>(cmd: impl IntoIterator, handler: R, logger: &'l L) -> Result { + // Get the command and split it in a program and arguments + let mut cmd: Vec = cmd.into_iter().collect(); + let exec: Option = cmd.pop(); + let cmd: (String, Vec) = (exec.ok_or(Error::EmptyReplCommand)?, cmd); + + // Build the context & log it + let context: EFlintHaskellReasonerContextFull = EFlintHaskellReasonerContextFull { public: EFlintHaskellReasonerContext::default(), cmd }; + logger.log_context(&context).await.map_err(|err| Error::LogContext { to: std::any::type_name::(), err: err.freeze() })?; + + // OK, return ourselves + Ok(Self { context, handler, _state: PhantomData, _question: PhantomData }) + } +} +impl ReasonerConnector for EFlintHaskellReasonerConnector +where + R: Sync, + S: Send + Sync + EFlintable + Serialize, + Q: Send + Sync + EFlintable + Serialize, +{ + type Context = EFlintHaskellReasonerContext; + type Error = Error; + type Question = Q; + type Reason = R; + type State = S; + + #[inline] + fn context(&self) -> Self::Context { self.context.public.clone() } + + #[inline] + fn consult<'a, L>( + &'a self, + state: Self::State, + question: Self::Question, + logger: &'a SessionedAuditLogger, + ) -> impl 'a + Send + Future, Self::Error>> + where + L: Sync + AuditLogger, + { + async move { + logger + .log_question(&state, &question) + .await + .map_err(|err| Error::LogQuestion { to: std::any::type_name::>(), err: err.freeze() })?; + + // Prepare the full file to send + let spec: String = format!("{}{}", state.eflint(), question.eflint()); + debug!("Full spec to submit to reasoner:{}\n{}\n{}\n", "-".repeat(80), spec, "-".repeat(80)); + + // Prepare the command to execute + let mut cmd = Command::new(&self.context.cmd.0); + cmd.args(&self.context.cmd.1); + cmd.stdin(Stdio::piped()); + cmd.stdout(Stdio::piped()); + cmd.stderr(Stdio::piped()); + + // Attempt to execute it, sending the full spec on the input + // NOTE: Using match to avoid moving `cmd` a closure and having to clone it (which it can't) + debug!("Calling reasoner with {cmd:?}..."); + let mut handle = match cmd.spawn() { + Ok(handle) => handle, + Err(err) => return Err(Error::CommandSpawn { cmd, err }), + }; + handle + .stdin + .as_mut() + .unwrap_or_else(|| panic!("No stdin on subprocess even though it's piped!")) + .write_all(spec.as_bytes()) + .await + .map_err(|err| Error::CommandStdinWrite { err })?; + debug!("Inputs submitted, waiting for reasoner to complete..."); + let output = match handle.wait_with_output().await { + Ok(handle) => handle, + Err(err) => return Err(Error::CommandJoin { cmd, err }), + }; + if !output.status.success() { + return Err(Error::CommandFailure { + cmd, + status: output.status, + stdout: String::from_utf8_lossy(&output.stdout).into(), + stderr: String::from_utf8_lossy(&output.stderr).into(), + }); + } + + // Attempt to parse the output + let output: Cow = String::from_utf8_lossy(&output.stdout); + debug!("Reasoner output:\n{}\n{}\n{}\n", "-".repeat(80), output, "-".repeat(80)); + + todo!() + } + } +} diff --git a/lib/reasoners/eflint-haskell/src/reasonertrace.rs b/lib/reasoners/eflint-haskell/src/reasonertrace.rs new file mode 100644 index 0000000..d326771 --- /dev/null +++ b/lib/reasoners/eflint-haskell/src/reasonertrace.rs @@ -0,0 +1,14 @@ +// REASONERTRACE.rs +// by Lut99 +// +// Created: +// 17 Apr 2025, 00:06:39 +// Last edited: +// 17 Apr 2025, 00:06:40 +// Auto updated? +// Yes +// +// Description: +//! Implements a little IR for parsed traces produced by the eFLINT +//! reasoner. +// diff --git a/lib/reasoners/eflint-haskell/src/spec.rs b/lib/reasoners/eflint-haskell/src/spec.rs new file mode 100644 index 0000000..ffe5d0b --- /dev/null +++ b/lib/reasoners/eflint-haskell/src/spec.rs @@ -0,0 +1,112 @@ +// SPEC.rs +// by Lut99 +// +// Created: +// 16 Apr 2025, 23:43:13 +// Last edited: +// 16 Apr 2025, 23:51:25 +// Auto updated? +// Yes +// +// Description: +//! Defines this crate's version of some interfaces necessary to make it +//! work. +// + +use std::borrow::Cow; +use std::fmt::{Display, Formatter, Result as FResult}; + + +/***** FORMATTERS *****/ +/// Formatter wrapping an [`EFlintable`] such that it implements [`Display`]. +#[derive(Clone, Copy)] +pub struct EFlintableFormatter<'o, E: ?Sized> { + /// Some object implementing [`EFlintable`]. + obj: &'o E, +} +impl<'o, E: ?Sized + EFlintable> Display for EFlintableFormatter<'o, E> { + #[inline] + fn fmt(&self, f: &mut Formatter<'_>) -> FResult { self.obj.eflint_fmt(f) } +} + + + + + +/***** LIBRARY *****/ +/// A less-nice version of `eflint-json`'s `EFlintable`-trait. +/// +/// It is less nice because we do not depend on some IR other than good ol' strings. +pub trait EFlintable { + /// Writes an eFLINT (string) representation of this type to the given formatter. + /// + /// # Arguments + /// - `f`: Some [`Formatter`] to write to. + /// + /// # Errors + /// This function should error if it failed to write to the given formatter. + fn eflint_fmt(&self, f: &mut Formatter<'_>) -> FResult; +} + +// Practical impls +impl EFlintable for () { + #[inline] + fn eflint_fmt(&self, _f: &mut Formatter<'_>) -> FResult { Ok(()) } +} + +// Pointer impls +impl<'a, T: ?Sized + EFlintable> EFlintable for &'a T { + #[inline] + fn eflint_fmt(&self, f: &mut Formatter<'_>) -> FResult { ::eflint_fmt(self, f) } +} +impl<'a, T: ?Sized + EFlintable> EFlintable for &'a mut T { + #[inline] + fn eflint_fmt(&self, f: &mut Formatter<'_>) -> FResult { ::eflint_fmt(self, f) } +} +impl<'a, T: ?Sized + Clone + EFlintable> EFlintable for Cow<'a, T> { + #[inline] + fn eflint_fmt(&self, f: &mut Formatter<'_>) -> FResult { ::eflint_fmt(self, f) } +} + +// Container impls +impl EFlintable for [T] { + #[inline] + fn eflint_fmt(&self, f: &mut Formatter<'_>) -> FResult { + for elem in self { + ::eflint_fmt(elem, f)?; + } + Ok(()) + } +} +impl EFlintable for [T; LEN] { + #[inline] + fn eflint_fmt(&self, f: &mut Formatter<'_>) -> FResult { + for elem in self { + ::eflint_fmt(elem, f)?; + } + Ok(()) + } +} +impl EFlintable for Vec { + #[inline] + fn eflint_fmt(&self, f: &mut Formatter<'_>) -> FResult { + for elem in self { + ::eflint_fmt(elem, f)?; + } + Ok(()) + } +} + + + +/// Extension upon an [`EFlintable`] to make it optionally nicer to work with. +pub trait EFlintableExt: EFlintable { + /// Returns some formatter that implements [`Display`]. + /// + /// # Returns + /// An [`EFlintableFormatter`] that will use [`EFlintable::eflint_fmt()`] in order to write + /// serialized eFLINT to some source. + #[inline] + fn eflint(&self) -> EFlintableFormatter { EFlintableFormatter { obj: self } } +} +impl EFlintableExt for T {} From 3f51990c91fd40d564453a0482d0b2dfaeebaa10 Mon Sep 17 00:00:00 2001 From: Lut99 Date: Fri, 25 Apr 2025 10:16:17 +0200 Subject: [PATCH 24/82] Added parsers for eFLINT traces Still WIP, but the ones we have now seem to work. Will have to add tests for postulation and instance, to be complete. --- lib/reasoners/eflint-haskell/Cargo.toml | 2 +- lib/reasoners/eflint-haskell/src/lib.rs | 3 +- .../eflint-haskell/src/reasonertrace.rs | 14 - lib/reasoners/eflint-haskell/src/trace.rs | 466 ++++++++++++++++++ 4 files changed, 469 insertions(+), 16 deletions(-) delete mode 100644 lib/reasoners/eflint-haskell/src/reasonertrace.rs create mode 100644 lib/reasoners/eflint-haskell/src/trace.rs diff --git a/lib/reasoners/eflint-haskell/Cargo.toml b/lib/reasoners/eflint-haskell/Cargo.toml index 9e98d81..c209a5e 100644 --- a/lib/reasoners/eflint-haskell/Cargo.toml +++ b/lib/reasoners/eflint-haskell/Cargo.toml @@ -13,7 +13,7 @@ license.workspace = true serde = { version = "1.0.204", features = ["derive"] } # serde_json = "1.0.120" thiserror = "2.0.12" -tokio = { version = "1.44.2", default-features = false, features = [] } +tokio = { version = "1.44.2", default-features = false, features = ["io-util", "process"] } tracing = "0.1.40" # eflint-json = { git = "https://gitlab.com/eflint/json-spec-rs", branch = "incorrect-is-invariant" } diff --git a/lib/reasoners/eflint-haskell/src/lib.rs b/lib/reasoners/eflint-haskell/src/lib.rs index 8856bbc..fb7cce9 100644 --- a/lib/reasoners/eflint-haskell/src/lib.rs +++ b/lib/reasoners/eflint-haskell/src/lib.rs @@ -4,7 +4,7 @@ // Created: // 16 Apr 2025, 23:09:00 // Last edited: -// 16 Apr 2025, 23:43:01 +// 25 Apr 2025, 09:08:47 // Auto updated? // Yes // @@ -16,3 +16,4 @@ // Define the submodules pub mod reasonerconn; pub mod spec; +pub mod trace; diff --git a/lib/reasoners/eflint-haskell/src/reasonertrace.rs b/lib/reasoners/eflint-haskell/src/reasonertrace.rs deleted file mode 100644 index d326771..0000000 --- a/lib/reasoners/eflint-haskell/src/reasonertrace.rs +++ /dev/null @@ -1,14 +0,0 @@ -// REASONERTRACE.rs -// by Lut99 -// -// Created: -// 17 Apr 2025, 00:06:39 -// Last edited: -// 17 Apr 2025, 00:06:40 -// Auto updated? -// Yes -// -// Description: -//! Implements a little IR for parsed traces produced by the eFLINT -//! reasoner. -// diff --git a/lib/reasoners/eflint-haskell/src/trace.rs b/lib/reasoners/eflint-haskell/src/trace.rs new file mode 100644 index 0000000..1ff1344 --- /dev/null +++ b/lib/reasoners/eflint-haskell/src/trace.rs @@ -0,0 +1,466 @@ +// TRACE.rs +// by Lut99 +// +// Created: +// 17 Apr 2025, 00:06:39 +// Last edited: +// 25 Apr 2025, 10:15:44 +// Auto updated? +// Yes +// +// Description: +//! Implements a little IR for parsed traces produced by the eFLINT +//! reasoner. +// + +use std::convert::Infallible; +use std::error; +use std::fmt::{Display, Formatter, Result as FResult}; + +use thiserror::Error; + + +/***** ERRORS *****/ +/// Defines fatal parsing errors for parsing traces [from strings](FromStrHead::from_str_head()). +#[derive(Debug, Error, Eq, PartialEq)] +pub enum Error { + #[error("Expected a comma at {s:?}")] + ExpectedComma { s: String }, + #[error("Out-of-range integer at {s:?}")] + OutOfRangeInt { s: String }, + #[error("Failed to parse instance following postulation op {op} at {s:?}")] + PostulationOpWithoutInstance { op: PostulationOp, s: String }, + #[error("Expected closing parenthesis at {s:?}")] + UnterminatedParen { s: String }, + #[error("Unterminated string at {s:?}")] + UnterminatedString { s: String }, +} + + + + + +/***** INTERFACES *****/ +/// Generalizes parsing for all of the trace nodes. +pub trait FromStrHead { + /// The type of error emitted when parsing fails. + type Error: error::Error; + + /// Parses an instance of this type from the head of the given string. + /// + /// Any unparsed string is returned. + /// + /// # Arguments + /// - `s`: The input string to parse from. + /// + /// # Returns + /// A tuple of the remaining string, if any, and the parsed `self`. + /// + /// If this input was not recognized, then [`None`] is returned instead. + /// + /// # Errors + /// This function errors if the head of the input was recognized as an instance but invalid. + fn from_str_head(s: &str) -> Result, Self::Error> + where + Self: Sized; +} + + + + + +/***** LIBRARY *****/ +/// Defines a postulation delta. +/// +/// This means that a new fact has been created, terminated or obfuscated. +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct Postulation { + /// The operation applied. + pub op: PostulationOp, + /// The instance that was postulated. + pub inst: Instance, +} +impl FromStrHead for Postulation { + type Error = Error; + + #[inline] + fn from_str_head(s: &str) -> Result, Self::Error> { + // Parse the postulation op first + let (rem, op): (&str, PostulationOp) = match PostulationOp::from_str_head(s).unwrap() { + Some(res) => res, + None => return Ok(None), + }; + + // Then any whitespace + let rem = rem.trim_start(); + + // Finally the instance + match Instance::from_str_head(rem)? { + Some((rem, inst)) => Ok(Some((rem, Self { op, inst }))), + None => Err(Error::PostulationOpWithoutInstance { op, s: s.into() }), + } + } +} + +/// Defines the possible kinds of postulation. +#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] +pub enum PostulationOp { + /// Transitioning a fact to be true (`+`). + Create, + /// Transitioning a fact to be false (`-`). + Terminate, + /// Transitioning to undo a postulation (`~`). + Obfuscate, +} +impl Display for PostulationOp { + #[inline] + fn fmt(&self, f: &mut Formatter<'_>) -> FResult { + match self { + Self::Create => write!(f, "+"), + Self::Terminate => write!(f, "-"), + Self::Obfuscate => write!(f, "~"), + } + } +} +impl FromStrHead for PostulationOp { + type Error = Infallible; + + #[inline] + fn from_str_head(s: &str) -> Result, Self::Error> { + match s.chars().next() { + Some('+') => Ok(Some((&s[1..], Self::Create))), + Some('-') => Ok(Some((&s[1..], Self::Terminate))), + Some('~') => Ok(Some((&s[1..], Self::Obfuscate))), + _ => Ok(None), + } + } +} + + + +/// Defines an instance. +#[derive(Clone, Debug, Eq, PartialEq)] +pub enum Instance { + /// A naked string literal. + StringLit(StringLit), + /// A naked int literal. + IntLit(IntLit), + /// A composite type. + Composite(Composite), +} +impl FromStrHead for Instance { + type Error = Error; + + #[inline] + fn from_str_head(s: &str) -> Result, Self::Error> { + if let Some((rem, lit)) = StringLit::from_str_head(s)? { + return Ok(Some((rem, Instance::StringLit(lit)))); + } + if let Some((rem, lit)) = IntLit::from_str_head(s)? { + return Ok(Some((rem, Instance::IntLit(lit)))); + } + if let Some((rem, comp)) = Composite::from_str_head(s)? { + return Ok(Some((rem, Instance::Composite(comp)))); + } + Ok(None) + } +} + +/// Defines a string literal. +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct StringLit(pub String); +impl FromStrHead for StringLit { + type Error = Error; + + #[inline] + fn from_str_head(s: &str) -> Result, Self::Error> { + // Feel like I'm always writing these. Here we go. + + // Match the opening quote + let mut chars = s.char_indices(); + if !matches!(chars.next(), Some((0, '"'))) { + return Ok(None); + } + + // Parse the remainder in a lil' state machine + enum State { + Body, + Escaped, + } + let mut state = State::Body; + let mut value: String = String::new(); + for (i, c) in chars { + match state { + State::Body if c == '"' => return Ok(Some((&s[i + 1..], StringLit(value)))), + State::Body if c == '\\' => state = State::Escaped, + State::Body => value.push(c), + + State::Escaped if c == 'n' => { + value.push('\n'); + state = State::Body; + }, + State::Escaped if c == 't' => { + value.push('\t'); + state = State::Body; + }, + State::Escaped if c == 'r' => { + value.push('\r'); + state = State::Body; + }, + State::Escaped => { + value.push(c); + state = State::Body; + }, + } + } + + // We ran out of input + Err(Error::UnterminatedString { s: s.into() }) + } +} + +/// Defines an integer literal. +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub struct IntLit(pub i64); +impl FromStrHead for IntLit { + type Error = Error; + + #[inline] + fn from_str_head(s: &str) -> Result, Self::Error> { + // Parse negatives + let mut chars = s.char_indices(); + let mut modifier: i64 = 1; + let mut next = chars.next(); + while let Some((_, '-')) = next { + modifier *= -1; + next = chars.next(); + } + + // Parse the integer body + let mut value: i64 = 0; + let mut seen_one: bool = false; + for (i, c) in next.into_iter().chain(chars) { + if c >= '0' && c <= '9' { + // Multiply the existing value by 10 to "move them left" + if modifier > 0 && value > i64::MAX / 10 || modifier < 0 && value < i64::MIN / 10 { + return Err(Error::OutOfRangeInt { s: s.into() }); + } + value *= 10; + + // Add the digit + let digit: i64 = modifier * ((c as i64) - ('0' as i64)); + if modifier > 0 && value > i64::MAX - digit || modifier < 0 && value < i64::MIN - digit { + return Err(Error::OutOfRangeInt { s: s.into() }); + } + value += digit; + + // Remember we've seen this one + seen_one = true; + } else if seen_one { + return Ok(Some((&s[i..], IntLit(value)))); + } else { + return Ok(None); + } + } + + // If we got here, then there were 0 digits + if seen_one { + return Ok(Some((&s[s.len()..], IntLit(value)))); + } else { + return Ok(None); + } + } +} + +/// Defines a composite type. +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct Composite { + /// The name of the type. + pub name: String, + /// The arguments of the type. + pub args: Vec, +} +impl FromStrHead for Composite { + type Error = Error; + + #[inline] + fn from_str_head(s: &str) -> Result, Self::Error> { + // Parse an identifier type first + let (rem, name): (&str, String) = match TypeName::from_str_head(s).unwrap() { + Some((rem, TypeName(name))) => (rem, name), + None => return Ok(None), + }; + + // Parse whitespace, then an opening parenthesis + let rem = rem.trim_start(); + if !matches!(rem.chars().next(), Some('(')) { + return Ok(None); + } + let mut rem = rem[1..].trim_start(); + + // Parse instances delimited by commas + let mut args: Vec = Vec::new(); + while let Some((newrem, inst)) = Instance::from_str_head(rem)? { + // Accept the instance + args.push(inst); + rem = newrem; + + // Parse a comma + rem = rem.trim_start(); + let next = rem.chars().next(); + if matches!(next, Some(')')) { + return Ok(Some((&rem[1..], Self { name, args }))); + } else if !matches!(next, Some(',')) { + return Err(Error::ExpectedComma { s: newrem.into() }); + } + rem = rem[1..].trim_start(); + } + + // If we got here, not enough time to find closing parenthesis + if matches!(rem.chars().next(), Some(')')) { + return Ok(Some((&rem[1..], Self { name, args }))); + } else { + Err(Error::UnterminatedParen { s: rem.into() }) + } + } +} + + + +/// Parses an eFLINT type name. +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct TypeName(pub String); +impl FromStrHead for TypeName { + type Error = Infallible; + + #[inline] + fn from_str_head(s: &str) -> Result, Self::Error> { + enum Kind { + Plain, + Brackets, + Chevrons, + Uninit, + } + + let mut kind = Kind::Uninit; + let mut depth: usize = 1; + for (i, c) in s.char_indices() { + if i == 0 { + if (c >= 'a' && c <= 'z') || c == '_' || c == '-' { + kind = Kind::Plain; + } else if c == '[' { + kind = Kind::Brackets; + } else if c == '<' { + kind = Kind::Chevrons; + } else { + return Ok(None); + } + } else if matches!(kind, Kind::Plain) { + // Stop when we find a non-valid character + if (c < 'a' || c > 'z') && (c < 'A' || c > 'Z') && c != '-' && c != '_' { + return Ok(Some((&s[i..], Self(s[..i].into())))); + } + } else if matches!(kind, Kind::Brackets) { + // Stop when we find a closing bracket at depth 1 + if c == ']' && depth == 1 { + return Ok(Some((&s[i + 1..], Self(s[..i + 1].into())))); + } else if c == ']' { + depth -= 1; + } else if c == '[' { + depth += 1; + } + } else if matches!(kind, Kind::Chevrons) { + // Stop when we find a closing chevron at depth 1 + if c == '>' && depth == 1 { + return Ok(Some((&s[i + 1..], Self(s[..i + 1].into())))); + } else if c == '>' { + depth -= 1; + } else if c == '<' { + depth += 1; + } + } else { + unreachable!(); + } + } + + // No input + Ok(None) + } +} + + + + + +/***** TESTS *****/ +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_parse_string_lit() { + assert_eq!(StringLit::from_str_head("\"Hello, world!\""), Ok(Some(("", StringLit("Hello, world!".into()))))); + assert_eq!(StringLit::from_str_head("\"Hello, world!\\n\""), Ok(Some(("", StringLit("Hello, world!\n".into()))))); + assert_eq!(StringLit::from_str_head("\"Hello, world!\" skibidi"), Ok(Some((" skibidi", StringLit("Hello, world!".into()))))); + assert_eq!(StringLit::from_str_head("\"Hello, world!"), Err(Error::UnterminatedString { s: "\"Hello, world!".into() })); + assert_eq!(StringLit::from_str_head("Hello, world!\""), Ok(None)); + } + + #[test] + fn test_parse_int_lit() { + assert_eq!(IntLit::from_str_head("42"), Ok(Some(("", IntLit(42))))); + assert_eq!(IntLit::from_str_head("-42"), Ok(Some(("", IntLit(-42))))); + assert_eq!(IntLit::from_str_head("--42"), Ok(Some(("", IntLit(42))))); + assert_eq!(IntLit::from_str_head("---42"), Ok(Some(("", IntLit(-42))))); + assert_eq!(IntLit::from_str_head("42 skibidi"), Ok(Some((" skibidi", IntLit(42))))); + assert_eq!(IntLit::from_str_head("9223372036854775807"), Ok(Some(("", IntLit(9223372036854775807))))); + assert_eq!(IntLit::from_str_head("-9223372036854775808"), Ok(Some(("", IntLit(-9223372036854775808))))); + assert_eq!(IntLit::from_str_head("9223372036854775808"), Err(Error::OutOfRangeInt { s: "9223372036854775808".into() })); + assert_eq!(IntLit::from_str_head("-9223372036854775809"), Err(Error::OutOfRangeInt { s: "-9223372036854775809".into() })); + assert_eq!(IntLit::from_str_head("98216387163871623817623817632"), Err(Error::OutOfRangeInt { s: "98216387163871623817623817632".into() })); + assert_eq!(IntLit::from_str_head("-98216387163871623817623817632"), Err(Error::OutOfRangeInt { s: "-98216387163871623817623817632".into() })); + assert_eq!(IntLit::from_str_head("---a"), Ok(None)); + assert_eq!(IntLit::from_str_head("\"Hello, world!\""), Ok(None)); + } + + #[test] + fn test_parse_composite() { + assert_eq!(Composite::from_str_head("foo()"), Ok(Some(("", Composite { name: "foo".into(), args: vec![] })))); + assert_eq!( + Composite::from_str_head("foo(\"bar\")"), + Ok(Some(("", Composite { name: "foo".into(), args: vec![Instance::StringLit(StringLit("bar".into()))] }))) + ); + assert_eq!( + Composite::from_str_head("foo(5532)"), + Ok(Some(("", Composite { name: "foo".into(), args: vec![Instance::IntLit(IntLit(5532))] }))) + ); + assert_eq!( + Composite::from_str_head("foo(-333)"), + Ok(Some(("", Composite { name: "foo".into(), args: vec![Instance::IntLit(IntLit(-333))] }))) + ); + assert_eq!( + Composite::from_str_head("foo(bar())"), + Ok(Some(("", Composite { name: "foo".into(), args: vec![Instance::Composite(Composite { name: "bar".into(), args: vec![] })] }))) + ); + assert_eq!( + Composite::from_str_head("foo(bar(\"baz\", quz()), 42, -88, \"howdy\", qux(\"say whhaaaa\"))"), + Ok(Some(("", Composite { + name: "foo".into(), + args: vec![ + Instance::Composite(Composite { + name: "bar".into(), + args: vec![Instance::StringLit(StringLit("baz".into())), Instance::Composite(Composite { name: "quz".into(), args: vec![] })], + }), + Instance::IntLit(IntLit(42)), + Instance::IntLit(IntLit(-88)), + Instance::StringLit(StringLit("howdy".into())), + Instance::Composite(Composite { name: "qux".into(), args: vec![Instance::StringLit(StringLit("say whhaaaa".into()))] }) + ], + }))) + ); + assert_eq!(Composite::from_str_head("foo"), Ok(None)); + assert_eq!(Composite::from_str_head("foo("), Err(Error::UnterminatedParen { s: "".into() })); + assert_eq!(Composite::from_str_head("foo(quz"), Err(Error::UnterminatedParen { s: "quz".into() })); + assert_eq!(Composite::from_str_head("foo(quz() bar())"), Err(Error::ExpectedComma { s: " bar())".into() })); + } +} From 87d2f82dcf13114fe2c5b1dd69bcc60b46e0257e Mon Sep 17 00:00:00 2001 From: Lut99 Date: Fri, 25 Apr 2025 15:45:40 +0200 Subject: [PATCH 25/82] Now parsing deltas, queries and triggers --- lib/reasoners/eflint-haskell/src/trace.rs | 318 +++++++++++++++++++++- 1 file changed, 317 insertions(+), 1 deletion(-) diff --git a/lib/reasoners/eflint-haskell/src/trace.rs b/lib/reasoners/eflint-haskell/src/trace.rs index 1ff1344..aea8239 100644 --- a/lib/reasoners/eflint-haskell/src/trace.rs +++ b/lib/reasoners/eflint-haskell/src/trace.rs @@ -4,7 +4,7 @@ // Created: // 17 Apr 2025, 00:06:39 // Last edited: -// 25 Apr 2025, 10:15:44 +// 25 Apr 2025, 15:45:13 // Auto updated? // Yes // @@ -16,20 +16,40 @@ use std::convert::Infallible; use std::error; use std::fmt::{Display, Formatter, Result as FResult}; +use std::str::FromStr; use thiserror::Error; +/***** CONSTANTS *****/ +const EXEC_TRANS: &'static str = "executed transition:"; +const TRANS_ENABLED: &'static str = "(ENABLED)"; +const TRANS_DISABLED: &'static str = "(DISABLED)"; +const QUERY_SUCCESS: &'static str = "query successful"; +const QUERY_FAILED: &'static str = "query failed"; + + + + + /***** ERRORS *****/ /// Defines fatal parsing errors for parsing traces [from strings](FromStrHead::from_str_head()). #[derive(Debug, Error, Eq, PartialEq)] pub enum Error { #[error("Expected a comma at {s:?}")] ExpectedComma { s: String }, + #[error("Expected \"`\" to follow \"|\" while parsing transition trees at {s:?}")] + ExpectedHookAfterBar { s: String }, + #[error("Expected \"-\" to follow \"`\" while parsing transition trees at {s:?}")] + ExpectedPipeAfterHook { s: String }, + #[error("Expected instance after magic 'executed transition' keyword at {s:?}")] + MissingInstanceAfterExecuted { s: String }, #[error("Out-of-range integer at {s:?}")] OutOfRangeInt { s: String }, #[error("Failed to parse instance following postulation op {op} at {s:?}")] PostulationOpWithoutInstance { op: PostulationOp, s: String }, + #[error("Unparsable input at {s:?}")] + UnparsableInput { s: String }, #[error("Expected closing parenthesis at {s:?}")] UnterminatedParen { s: String }, #[error("Unterminated string at {s:?}")] @@ -70,6 +90,95 @@ pub trait FromStrHead { /***** LIBRARY *****/ +/// Defines a trace as a whole. +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct Trace { + /// The deltas emitted by eFLINT. + pub deltas: Vec, +} +impl FromStr for Trace { + type Err = Error; + + #[inline] + fn from_str(s: &str) -> Result { + // Ensure all of the input is consumed this time + // SAFETY: Note that `Self::from_str_head()` actually never yields `None` + let (rem, this): (&str, Self) = Self::from_str_head(s)?.unwrap(); + if rem.trim().is_empty() { Ok(this) } else { Err(Error::UnparsableInput { s: rem.into() }) } + } +} +impl FromStrHead for Trace { + type Error = Error; + + #[inline] + fn from_str_head(s: &str) -> Result, Self::Error> { + let mut deltas: Vec = Vec::new(); + let mut rem = s.trim_start(); + while let Some((newrem, newdeltas)) = Vec::::from_str_head(rem)? { + deltas.extend(newdeltas); + rem = newrem.trim_start(); + } + Ok(Some((rem, Self { deltas }))) + } +} + + + +/// Defines a delta, which is like the toplevel instance of the trace. +#[derive(Clone, Debug, Eq, PartialEq)] +pub enum Delta { + /// It's a postulation - i.e., a database update. + Postulation(Postulation), + /// It's a query - i.e., the answer to a question. + /// + /// Note we wouldn't know the question itself, actually. + Query(Query), + /// It's a trigger - i.e., a transition. + Trigger(Trigger), +} +impl FromStrHead for Vec { + type Error = Error; + + #[inline] + fn from_str_head(s: &str) -> Result, Self::Error> { + if let Some((rem, pos)) = Postulation::from_str_head(s)? { + return Ok(Some((rem, vec![Delta::Postulation(pos)]))); + } + if let Some((rem, quer)) = Query::from_str_head(s).unwrap() { + return Ok(Some((rem, vec![Delta::Query(quer)]))); + } + if let Some((rem, trigs)) = Vec::::from_str_head(s)? { + return Ok(Some((rem, trigs.into_iter().map(Delta::Trigger).collect()))); + } + Ok(None) + } +} + +/// Defines the answer to a query. +/// +/// Note this is just the answer. The rest we wouldn't know. +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub enum Query { + /// The answer is yes + Succes, + /// The answer is no + Fail, +} +impl FromStrHead for Query { + type Error = Infallible; + + #[inline] + fn from_str_head(s: &str) -> Result, Self::Error> { + if s.starts_with(QUERY_SUCCESS) { + return Ok(Some((&s[QUERY_SUCCESS.len()..], Self::Succes))); + } + if s.starts_with(QUERY_FAILED) { + return Ok(Some((&s[QUERY_FAILED.len()..], Self::Fail))); + } + Ok(None) + } +} + /// Defines a postulation delta. /// /// This means that a new fact has been created, terminated or obfuscated. @@ -136,6 +245,83 @@ impl FromStrHead for PostulationOp { } } +/// Defines a triggered instance. +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct Trigger { + /// The triggered instance. + pub inst: Instance, + /// Whether it was enabled or not. + /// + /// Not given for events. + pub enabled: Option, +} +impl FromStrHead for Vec { + type Error = Error; + + #[inline] + fn from_str_head(s: &str) -> Result, Self::Error> { + // Parse the magic prefix first + if !s.starts_with(EXEC_TRANS) { + return Ok(None); + } + let rem = s[EXEC_TRANS.len()..].trim_start(); + + // Now parse the triggered instance + let (rem, inst): (&str, Instance) = match Instance::from_str_head(rem)? { + Some(res) => res, + None => return Err(Error::MissingInstanceAfterExecuted { s: rem.into() }), + }; + + // Parse the optional 'ENABLED|DISABLED' bizz + let rem = rem.trim_start(); + let (mut rem, enabled): (&str, Option) = if rem.starts_with(TRANS_ENABLED) { + (rem[TRANS_ENABLED.len()..].trim_start(), Some(true)) + } else if rem.starts_with(TRANS_DISABLED) { + (rem[TRANS_DISABLED.len()..].trim_start(), Some(false)) + } else { + (rem, None) + }; + + // Now we will parse an optional tree of triggered instances, if `Syncs with` is used. + let mut result: Vec = vec![Trigger { inst, enabled }]; + while rem.starts_with('|') { + // Pop the rest of the tree symbol + let newrem = rem[1..].trim_start(); + if !newrem.starts_with('`') { + return Err(Error::ExpectedHookAfterBar { s: newrem.into() }); + } + let newrem = newrem[1..].trim_start(); + if !newrem.starts_with('-') { + return Err(Error::ExpectedPipeAfterHook { s: newrem.into() }); + } + let newrem = newrem[1..].trim_start(); + + // Now parse a new instance / enabled pair + let (newrem, inst): (&str, Instance) = match Instance::from_str_head(newrem)? { + Some(res) => res, + None => return Err(Error::MissingInstanceAfterExecuted { s: newrem.into() }), + }; + + // Parse the optional 'ENABLED|DISABLED' bizz + let newrem = newrem.trim_start(); + let (newrem, enabled): (&str, Option) = if newrem.starts_with(TRANS_ENABLED) { + (newrem[TRANS_ENABLED.len()..].trim_start(), Some(true)) + } else if newrem.starts_with(TRANS_DISABLED) { + (newrem[TRANS_DISABLED.len()..].trim_start(), Some(false)) + } else { + (newrem, None) + }; + + // Try again + result.push(Trigger { inst, enabled }); + rem = newrem; + } + + // Done! + Ok(Some((rem, result))) + } +} + /// Defines an instance. @@ -397,6 +583,136 @@ impl FromStrHead for TypeName { mod tests { use super::*; + #[test] + fn test_parse_query() { + assert_eq!(Query::from_str_head("query successful"), Ok(Some(("", Query::Succes)))); + assert_eq!(Query::from_str_head("query failed"), Ok(Some(("", Query::Fail)))); + assert_eq!(Query::from_str_head("query successfulAND MORE"), Ok(Some(("AND MORE", Query::Succes)))); + assert_eq!(Query::from_str_head("query successfu"), Ok(None)); + assert_eq!(Query::from_str_head("aquery failed"), Ok(None)); + assert_eq!(Query::from_str_head(""), Ok(None)); + } + + #[test] + fn test_parse_postulation() { + assert_eq!(Postulation::from_str_head("+42"), Ok(Some(("", Postulation { op: PostulationOp::Create, inst: Instance::IntLit(IntLit(42)) })))); + assert_eq!( + Postulation::from_str_head("+-42"), + Ok(Some(("", Postulation { op: PostulationOp::Create, inst: Instance::IntLit(IntLit(-42)) }))) + ); + assert_eq!( + Postulation::from_str_head("---42"), + Ok(Some(("", Postulation { op: PostulationOp::Terminate, inst: Instance::IntLit(IntLit(42)) }))) + ); + assert_eq!( + Postulation::from_str_head("+foo(\"Amy\")"), + Ok(Some(("", Postulation { + op: PostulationOp::Create, + inst: Instance::Composite(Composite { name: "foo".into(), args: vec![Instance::StringLit(StringLit("Amy".into()))] }), + }))) + ); + assert_eq!( + Postulation::from_str_head("-bar()"), + Ok(Some(("", Postulation { op: PostulationOp::Terminate, inst: Instance::Composite(Composite { name: "bar".into(), args: vec![] }) }))) + ); + assert_eq!( + Postulation::from_str_head("~baz(foo(\"Bob\"), 42)"), + Ok(Some(("", Postulation { + op: PostulationOp::Obfuscate, + inst: Instance::Composite(Composite { + name: "baz".into(), + args: vec![ + Instance::Composite(Composite { name: "foo".into(), args: vec![Instance::StringLit(StringLit("Bob".into()))] }), + Instance::IntLit(IntLit(42)) + ], + }), + }))) + ); + assert_eq!(Postulation::from_str_head("foo(\"Amy\")"), Ok(None)); + assert_eq!(Postulation::from_str_head(""), Ok(None)); + } + + #[test] + fn test_parse_postulation_op() { + assert_eq!(PostulationOp::from_str_head("+"), Ok(Some(("", PostulationOp::Create)))); + assert_eq!(PostulationOp::from_str_head("-"), Ok(Some(("", PostulationOp::Terminate)))); + assert_eq!(PostulationOp::from_str_head("~"), Ok(Some(("", PostulationOp::Obfuscate)))); + assert_eq!(PostulationOp::from_str_head("+a"), Ok(Some(("a", PostulationOp::Create)))); + assert_eq!(PostulationOp::from_str_head("a"), Ok(None)); + assert_eq!(PostulationOp::from_str_head(""), Ok(None)); + } + + #[test] + fn test_parse_trigger() { + assert_eq!( + Vec::::from_str_head("executed transition:\ngo(string(\"y\"))"), + Ok(Some(("", vec![Trigger { + inst: Instance::Composite(Composite { + name: "go".into(), + args: vec![Instance::Composite(Composite { name: "string".into(), args: vec![Instance::StringLit(StringLit("y".into()))] })], + }), + enabled: None, + }]))) + ); + assert_eq!( + Vec::::from_str_head("executed transition: \ngo(string(\"y\")) (ENABLED)"), + Ok(Some(("", vec![Trigger { + inst: Instance::Composite(Composite { + name: "go".into(), + args: vec![Instance::Composite(Composite { name: "string".into(), args: vec![Instance::StringLit(StringLit("y".into()))] })], + }), + enabled: Some(true), + }]))) + ); + assert_eq!( + Vec::::from_str_head("executed transition: go(string(\"y\")) (DISABLED)"), + Ok(Some(("", vec![Trigger { + inst: Instance::Composite(Composite { + name: "go".into(), + args: vec![Instance::Composite(Composite { name: "string".into(), args: vec![Instance::StringLit(StringLit("y".into()))] })], + }), + enabled: Some(false), + }]))) + ); + assert_eq!( + Vec::::from_str_head( + r#"executed transition: + go(string("y")) (DISABLED) + | + `- go(string("x")) (DISABLED) + "# + ), + Ok(Some(("", vec![ + Trigger { + inst: Instance::Composite(Composite { + name: "go".into(), + args: vec![Instance::Composite(Composite { name: "string".into(), args: vec![Instance::StringLit(StringLit("y".into()))] })], + }), + enabled: Some(false), + }, + Trigger { + inst: Instance::Composite(Composite { + name: "go".into(), + args: vec![Instance::Composite(Composite { name: "string".into(), args: vec![Instance::StringLit(StringLit("x".into()))] })], + }), + enabled: Some(false), + } + ]))) + ); + } + + + + #[test] + fn test_parse_instance() { + assert_eq!(Instance::from_str_head("\"Hello, world!\""), Ok(Some(("", Instance::StringLit(StringLit("Hello, world!".into())))))); + assert_eq!(Instance::from_str_head("\"Hello, world!\\n\""), Ok(Some(("", Instance::StringLit(StringLit("Hello, world!\n".into())))))); + assert_eq!(Instance::from_str_head("42"), Ok(Some(("", Instance::IntLit(IntLit(42)))))); + assert_eq!(Instance::from_str_head("-42"), Ok(Some(("", Instance::IntLit(IntLit(-42)))))); + assert_eq!(Instance::from_str_head("foo()"), Ok(Some(("", Instance::Composite(Composite { name: "foo".into(), args: vec![] }))))); + assert_eq!(Instance::from_str_head("+42"), Ok(None)); + } + #[test] fn test_parse_string_lit() { assert_eq!(StringLit::from_str_head("\"Hello, world!\""), Ok(Some(("", StringLit("Hello, world!".into()))))); From 27102538a5e13ed38b9ca726bd5a00478ee706c5 Mon Sep 17 00:00:00 2001 From: Lut99 Date: Fri, 25 Apr 2025 16:27:07 +0200 Subject: [PATCH 26/82] Now parsing violations, newtypes --- lib/reasoners/eflint-haskell/src/trace.rs | 265 +++++++++++++++++++++- 1 file changed, 261 insertions(+), 4 deletions(-) diff --git a/lib/reasoners/eflint-haskell/src/trace.rs b/lib/reasoners/eflint-haskell/src/trace.rs index aea8239..c96cb77 100644 --- a/lib/reasoners/eflint-haskell/src/trace.rs +++ b/lib/reasoners/eflint-haskell/src/trace.rs @@ -4,7 +4,7 @@ // Created: // 17 Apr 2025, 00:06:39 // Last edited: -// 25 Apr 2025, 15:45:13 +// 25 Apr 2025, 16:26:47 // Auto updated? // Yes // @@ -22,11 +22,16 @@ use thiserror::Error; /***** CONSTANTS *****/ +const DISABLED_ACTION: &'static str = "disabled action:"; const EXEC_TRANS: &'static str = "executed transition:"; +const NEW_TYPE: &'static str = "New type"; const TRANS_ENABLED: &'static str = "(ENABLED)"; const TRANS_DISABLED: &'static str = "(DISABLED)"; const QUERY_SUCCESS: &'static str = "query successful"; const QUERY_FAILED: &'static str = "query failed"; +const VIOLATIONS: &'static str = "violations:"; +const VIOLATED_DUTY: &'static str = "violated duty!:"; +const VIOLATED_INVARIANT: &'static str = "violated invariant!:"; @@ -40,8 +45,16 @@ pub enum Error { ExpectedComma { s: String }, #[error("Expected \"`\" to follow \"|\" while parsing transition trees at {s:?}")] ExpectedHookAfterBar { s: String }, + #[error("Expected instance to follow \"disabled action\" at {s:?}")] + ExpectedInstanceAfterDisabledAction { s: String }, + #[error("Expected instance to follow \"violated duty!\" at {s:?}")] + ExpectedInstanceAfterViolatedDuty { s: String }, #[error("Expected \"-\" to follow \"`\" while parsing transition trees at {s:?}")] ExpectedPipeAfterHook { s: String }, + #[error("Expected type name to follow \"New type\" at {s:?}")] + ExpectedTypeNameAfterNewType { s: String }, + #[error("Expected type name to follow \"violated invariant!\" at {s:?}")] + ExpectedTypeNameAfterViolatedInvariant { s: String }, #[error("Expected instance after magic 'executed transition' keyword at {s:?}")] MissingInstanceAfterExecuted { s: String }, #[error("Out-of-range integer at {s:?}")] @@ -50,6 +63,8 @@ pub enum Error { PostulationOpWithoutInstance { op: PostulationOp, s: String }, #[error("Unparsable input at {s:?}")] UnparsableInput { s: String }, + #[error("Expected closing delimiter {delim:?} for opening delimiter starting at {s:?}")] + UnterminatedDelim { delim: char, s: String }, #[error("Expected closing parenthesis at {s:?}")] UnterminatedParen { s: String }, #[error("Unterminated string at {s:?}")] @@ -127,6 +142,8 @@ impl FromStrHead for Trace { /// Defines a delta, which is like the toplevel instance of the trace. #[derive(Clone, Debug, Eq, PartialEq)] pub enum Delta { + /// A type definition. + NewType(NewType), /// It's a postulation - i.e., a database update. Postulation(Postulation), /// It's a query - i.e., the answer to a question. @@ -141,6 +158,9 @@ impl FromStrHead for Vec { #[inline] fn from_str_head(s: &str) -> Result, Self::Error> { + if let Some((rem, nty)) = NewType::from_str_head(s)? { + return Ok(Some((rem, vec![Delta::NewType(nty)]))); + } if let Some((rem, pos)) = Postulation::from_str_head(s)? { return Ok(Some((rem, vec![Delta::Postulation(pos)]))); } @@ -154,6 +174,31 @@ impl FromStrHead for Vec { } } +/// Defines a type definition. +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct NewType { + /// The name of the newly defined type. + pub name: String, +} +impl FromStrHead for NewType { + type Error = Error; + + #[inline] + fn from_str_head(s: &str) -> Result, Self::Error> { + // Parse the magic first + if !s.starts_with(NEW_TYPE) { + return Ok(None); + } + let rem = s[NEW_TYPE.len()..].trim_start(); + + // Then parse the type name + match TypeName::from_str_head(rem)? { + Some((rem, TypeName(name))) => Ok(Some((rem, Self { name }))), + None => Err(Error::ExpectedTypeNameAfterNewType { s: rem.into() }), + } + } +} + /// Defines the answer to a query. /// /// Note this is just the answer. The rest we wouldn't know. @@ -322,6 +367,121 @@ impl FromStrHead for Vec { } } +/// Defines any violation. +#[derive(Clone, Debug, Eq, PartialEq)] +pub enum Violation { + /// An action has been violated. + Act(ActViolation), + /// A duty has been violated. + Duty(DutyViolation), + /// An invariant has been violated. + Invariant(InvariantViolation), +} +impl FromStrHead for Vec { + type Error = Error; + + #[inline] + fn from_str_head(s: &str) -> Result, Self::Error> { + // Parse the initial magic + if !s.starts_with(VIOLATIONS) { + return Ok(None); + } + let mut rem = s[VIOLATIONS.len()..].trim_start(); + + // Now keep popping violations + let mut res: Vec = Vec::new(); + loop { + if let Some((newrem, act)) = ActViolation::from_str_head(rem)? { + res.push(Violation::Act(act)); + rem = newrem.trim_start(); + } else if let Some((newrem, duty)) = DutyViolation::from_str_head(rem)? { + res.push(Violation::Duty(duty)); + rem = newrem.trim_start(); + } else if let Some((newrem, inv)) = InvariantViolation::from_str_head(rem)? { + res.push(Violation::Invariant(inv)); + rem = newrem.trim_start(); + } else { + return Ok(Some((rem, res))); + } + } + } +} + +/// Defines the violation of an act. +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct ActViolation { + /// The violated instance. + pub inst: Instance, +} +impl FromStrHead for ActViolation { + type Error = Error; + + #[inline] + fn from_str_head(s: &str) -> Result, Self::Error> { + // Parse the prompt first + if !s.starts_with(DISABLED_ACTION) { + return Ok(None); + } + let rem = s[DISABLED_ACTION.len()..].trim_start(); + + // Then parse the instance that was violated + match Instance::from_str_head(rem)? { + Some((rem, inst)) => Ok(Some((rem, ActViolation { inst }))), + None => Err(Error::ExpectedInstanceAfterDisabledAction { s: rem.into() }), + } + } +} + +/// Defines the violation of a duty. +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct DutyViolation { + /// The violated instance. + pub inst: Instance, +} +impl FromStrHead for DutyViolation { + type Error = Error; + + #[inline] + fn from_str_head(s: &str) -> Result, Self::Error> { + // Parse the prompt first + if !s.starts_with(VIOLATED_DUTY) { + return Ok(None); + } + let rem = s[VIOLATED_DUTY.len()..].trim_start(); + + // Then parse the instance that was violated + match Instance::from_str_head(rem)? { + Some((rem, inst)) => Ok(Some((rem, DutyViolation { inst }))), + None => Err(Error::ExpectedInstanceAfterViolatedDuty { s: rem.into() }), + } + } +} + +/// Defines the violation of an invariant. +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct InvariantViolation { + /// The violated invariant. + pub name: String, +} +impl FromStrHead for InvariantViolation { + type Error = Error; + + #[inline] + fn from_str_head(s: &str) -> Result, Self::Error> { + // Parse the prompt first + if !s.starts_with(VIOLATED_INVARIANT) { + return Ok(None); + } + let rem = s[VIOLATED_INVARIANT.len()..].trim_start(); + + // Then parse the invariant that was violated + match TypeName::from_str_head(rem)? { + Some((rem, TypeName(name))) => Ok(Some((rem, InvariantViolation { name }))), + None => Err(Error::ExpectedTypeNameAfterViolatedInvariant { s: rem.into() }), + } + } +} + /// Defines an instance. @@ -472,7 +632,7 @@ impl FromStrHead for Composite { #[inline] fn from_str_head(s: &str) -> Result, Self::Error> { // Parse an identifier type first - let (rem, name): (&str, String) = match TypeName::from_str_head(s).unwrap() { + let (rem, name): (&str, String) = match TypeName::from_str_head(s)? { Some((rem, TypeName(name))) => (rem, name), None => return Ok(None), }; @@ -517,7 +677,7 @@ impl FromStrHead for Composite { #[derive(Clone, Debug, Eq, PartialEq)] pub struct TypeName(pub String); impl FromStrHead for TypeName { - type Error = Infallible; + type Error = Error; #[inline] fn from_str_head(s: &str) -> Result, Self::Error> { @@ -570,7 +730,13 @@ impl FromStrHead for TypeName { } // No input - Ok(None) + if matches!(kind, Kind::Plain) { + Ok(Some((&s[s.len()..], Self(s.into())))) + } else if matches!(kind, Kind::Uninit) { + Ok(None) + } else { + Err(Error::UnterminatedDelim { delim: (if matches!(kind, Kind::Brackets) { ']' } else { '>' }), s: s.into() }) + } } } @@ -583,6 +749,16 @@ impl FromStrHead for TypeName { mod tests { use super::*; + #[test] + fn test_parse_newtype() { + assert_eq!(NewType::from_str_head("New type foo"), Ok(Some(("", NewType { name: "foo".into() })))); + assert_eq!(NewType::from_str_head("New type a"), Ok(Some(("", NewType { name: "a".into() })))); + assert_eq!(NewType::from_str_head("New type a a"), Ok(Some((" a", NewType { name: "a".into() })))); + assert_eq!(NewType::from_str_head("New type"), Err(Error::ExpectedTypeNameAfterNewType { s: "".into() })); + assert_eq!(NewType::from_str_head("ew type foo"), Ok(None)); + assert_eq!(NewType::from_str_head(""), Ok(None)); + } + #[test] fn test_parse_query() { assert_eq!(Query::from_str_head("query successful"), Ok(Some(("", Query::Succes)))); @@ -701,6 +877,60 @@ mod tests { ); } + #[test] + fn test_parse_violation() { + assert_eq!( + Vec::::from_str_head("violations:disabled action:foo()"), + Ok(Some(("", vec![Violation::Act(ActViolation { inst: Instance::Composite(Composite { name: "foo".into(), args: vec![] }) })]))) + ); + assert_eq!( + Vec::::from_str_head("violations: disabled action:\n\n\nfoo()violated duty!:bar()"), + Ok(Some(("", vec![ + Violation::Act(ActViolation { inst: Instance::Composite(Composite { name: "foo".into(), args: vec![] }) }), + Violation::Duty(DutyViolation { inst: Instance::Composite(Composite { name: "bar".into(), args: vec![] }) }) + ]))) + ); + assert_eq!( + Vec::::from_str_head("violations:disabled action:foo()violated duty!:bar()violated invariant!:baz"), + Ok(Some(("", vec![ + Violation::Act(ActViolation { inst: Instance::Composite(Composite { name: "foo".into(), args: vec![] }) }), + Violation::Duty(DutyViolation { inst: Instance::Composite(Composite { name: "bar".into(), args: vec![] }) }), + Violation::Invariant(InvariantViolation { name: "baz".into() }) + ]))) + ); + } + + #[test] + fn test_parse_act_violation() { + assert_eq!( + ActViolation::from_str_head("disabled action: foo()"), + Ok(Some(("", ActViolation { inst: Instance::Composite(Composite { name: "foo".into(), args: vec![] }) }))) + ); + assert_eq!(ActViolation::from_str_head("disabled action: foo"), Err(Error::ExpectedInstanceAfterDisabledAction { s: "foo".into() })); + assert_eq!(ActViolation::from_str_head("disabled actio: foo()"), Ok(None)); + } + + #[test] + fn test_parse_duty_violation() { + assert_eq!( + DutyViolation::from_str_head("violated duty!: foo()"), + Ok(Some(("", DutyViolation { inst: Instance::Composite(Composite { name: "foo".into(), args: vec![] }) }))) + ); + assert_eq!(DutyViolation::from_str_head("violated duty!: foo"), Err(Error::ExpectedInstanceAfterViolatedDuty { s: "foo".into() })); + assert_eq!(DutyViolation::from_str_head("violated duty! foo()"), Ok(None)); + } + + #[test] + fn test_parse_invariant_violation() { + assert_eq!(InvariantViolation::from_str_head("violated invariant!: foo"), Ok(Some(("", InvariantViolation { name: "foo".into() })))); + assert_eq!(InvariantViolation::from_str_head("violated invariant!: foo()"), Ok(Some(("()", InvariantViolation { name: "foo".into() })))); + assert_eq!( + InvariantViolation::from_str_head("violated invariant!: AMY"), + Err(Error::ExpectedTypeNameAfterViolatedInvariant { s: "AMY".into() }) + ); + assert_eq!(InvariantViolation::from_str_head("violated invariant! foo"), Ok(None)); + } + #[test] @@ -779,4 +1009,31 @@ mod tests { assert_eq!(Composite::from_str_head("foo(quz"), Err(Error::UnterminatedParen { s: "quz".into() })); assert_eq!(Composite::from_str_head("foo(quz() bar())"), Err(Error::ExpectedComma { s: " bar())".into() })); } + + + + #[test] + fn test_parse_type_name() { + assert_eq!(TypeName::from_str_head("foo"), Ok(Some(("", TypeName("foo".into()))))); + assert_eq!(TypeName::from_str_head("a"), Ok(Some(("", TypeName("a".into()))))); + assert_eq!(TypeName::from_str_head("camelCase"), Ok(Some(("", TypeName("camelCase".into()))))); + assert_eq!(TypeName::from_str_head("kebab-case"), Ok(Some(("", TypeName("kebab-case".into()))))); + assert_eq!(TypeName::from_str_head("snake_case"), Ok(Some(("", TypeName("snake_case".into()))))); + assert_eq!(TypeName::from_str_head("mixCase-es_es"), Ok(Some(("", TypeName("mixCase-es_es".into()))))); + assert_eq!( + TypeName::from_str_head("[everything goes in <> square BRACKAETS]"), + Ok(Some(("", TypeName("[everything goes in <> square BRACKAETS]".into())))) + ); + assert_eq!( + TypeName::from_str_head(""), + Ok(Some(("", TypeName("".into())))) + ); + assert_eq!(TypeName::from_str_head("[[nested brackets]]"), Ok(Some(("", TypeName("[[nested brackets]]".into()))))); + assert_eq!(TypeName::from_str_head("<>"), Ok(Some(("", TypeName("<>".into()))))); + assert_eq!(TypeName::from_str_head("Foo"), Ok(None)); + assert_eq!(TypeName::from_str_head("[unterminated"), Err(Error::UnterminatedDelim { delim: ']', s: "[unterminated".into() })); + assert_eq!(TypeName::from_str_head("[[unterminated]"), Err(Error::UnterminatedDelim { delim: ']', s: "[[unterminated]".into() })); + assert_eq!(TypeName::from_str_head("', s: ""), Err(Error::UnterminatedDelim { delim: '>', s: "<".into() })); + } } From e504dcbf96f9d6236e1af441960b85023019147d Mon Sep 17 00:00:00 2001 From: Lut99 Date: Fri, 25 Apr 2025 17:27:36 +0200 Subject: [PATCH 27/82] In theory, finished new backend I'm quite confident the trace parser works (well enough). However, the main reasoner component is yet untested. Since I don't want to add a test that installs Haskell and such, I'll move this to `brane-chk` so that I can do it in a container. --- lib/reasoners/eflint-haskell/src/lib.rs | 3 +- .../eflint-haskell/src/reasonerconn.rs | 51 +- lib/reasoners/eflint-haskell/src/reasons.rs | 145 + lib/reasoners/eflint-haskell/src/trace.rs | 171 +- lib/reasoners/eflint-haskell/tests/traces.rs | 41 + .../tests/traces/01_deriv_holds.same0 | 2 + .../tests/traces/02_create_holds.same0 | 2 + .../tests/traces/03_derive_chain.same0 | 4 + .../tests/traces/04_suppress_derive.same0 | 1 + .../tests/traces/05_suppress_create.same0 | 2 + .../tests/traces/06_trigger.same0 | 3 + .../tests/traces/07_create_terminate.same0 | 4 + .../tests/traces/08_create_seq.same0 | 4 + .../traces/09_create_terminate_seq.same0 | 7 + .../tests/traces/10_create_obfuscate.same0 | 4 + .../tests/traces/11_complex_facts.same0 | 12 + .../tests/traces/12_trig_act.same0 | 10 + .../eflint-haskell/tests/traces/13_sync.same0 | 17 + .../tests/traces/14_deriv_chain.same0 | 6 + .../tests/traces/14_deriv_chain.same1 | 8 + .../tests/traces/14_deriv_chain.same2 | 11 + .../tests/traces/15_sync_chain.same0 | 16 + .../tests/traces/15_sync_chain.same1 | 42 + .../tests/traces/16_act_viol.same0 | 17 + .../tests/traces/17_not_enabled_act.same0 | 17 + .../eflint-haskell/tests/traces/18_duty.same0 | 8 + .../eflint-haskell/tests/traces/19_naf.same0 | 11 + .../eflint-haskell/tests/traces/20_agg.same0 | 9 + .../eflint-haskell/tests/traces/21_aggs.same0 | 23 + .../tests/traces/22_forall_exists.same0 | 16 + .../tests/traces/23_duty_derived.same0 | 7 + .../tests/traces/23_duty_derived.same1 | 9 + .../tests/traces/23_duty_derived.same2 | 7 + .../tests/traces/access_union.same1 | 86 + .../tests/traces/access_union.same2 | 85 + .../tests/traces/bench_between.perf0 | 1 + .../tests/traces/bench_between.perf1 | 6 + .../tests/traces/bench_between.perf2 | 10 + .../tests/traces/bench_between.perf3 | 18 + .../tests/traces/bench_between.perf4 | 34 + .../tests/traces/bench_between.perf5 | 66 + .../tests/traces/bench_between.perf6 | 130 + .../tests/traces/bench_between.perf7 | 258 ++ .../tests/traces/bench_derive.perf1 | 6 + .../tests/traces/bench_derive.perf2 | 10 + .../tests/traces/bench_derive.perf3 | 18 + .../tests/traces/bench_derive.perf4 | 34 + .../tests/traces/bench_derive.perf5 | 66 + .../tests/traces/bench_derive.perf6 | 130 + .../tests/traces/bench_derive.perf7 | 258 ++ .../tests/traces/bench_derive_loop.perf0 | 2 + .../tests/traces/bench_derive_loop.perf1 | 14 + .../tests/traces/bench_triple.perf0 | 2 + .../tests/traces/bench_triple.perf1 | 8 + .../tests/traces/bench_triple.perf2 | 20 + .../tests/traces/bench_triple.perf3 | 46 + .../tests/traces/bench_triple.perf4 | 92 + .../tests/traces/bench_triple.perf5 | 164 + .../tests/traces/bench_triple.perf6 | 1124 +++++ .../tests/traces/bench_triple.perf7 | 3634 +++++++++++++++++ .../tests/traces/diff_1_naf.diff0 | 4 + .../tests/traces/diff_2_no_stable.diff0 | 2 + .../tests/traces/diff_3_2_stable.diff0 | 2 + .../tests/traces/diff_4_exists_forall.diff0 | 14 + .../tests/traces/diff_clingo_no_model.diff0 | 16 + .../eflint-haskell/tests/traces/moving.same0 | 31 + .../tests/traces/running-example.same0 | 13 + .../tests/traces/syncs-with.same0 | 9 + 68 files changed, 7081 insertions(+), 22 deletions(-) create mode 100644 lib/reasoners/eflint-haskell/src/reasons.rs create mode 100644 lib/reasoners/eflint-haskell/tests/traces.rs create mode 100644 lib/reasoners/eflint-haskell/tests/traces/01_deriv_holds.same0 create mode 100644 lib/reasoners/eflint-haskell/tests/traces/02_create_holds.same0 create mode 100644 lib/reasoners/eflint-haskell/tests/traces/03_derive_chain.same0 create mode 100644 lib/reasoners/eflint-haskell/tests/traces/04_suppress_derive.same0 create mode 100644 lib/reasoners/eflint-haskell/tests/traces/05_suppress_create.same0 create mode 100644 lib/reasoners/eflint-haskell/tests/traces/06_trigger.same0 create mode 100644 lib/reasoners/eflint-haskell/tests/traces/07_create_terminate.same0 create mode 100644 lib/reasoners/eflint-haskell/tests/traces/08_create_seq.same0 create mode 100644 lib/reasoners/eflint-haskell/tests/traces/09_create_terminate_seq.same0 create mode 100644 lib/reasoners/eflint-haskell/tests/traces/10_create_obfuscate.same0 create mode 100644 lib/reasoners/eflint-haskell/tests/traces/11_complex_facts.same0 create mode 100644 lib/reasoners/eflint-haskell/tests/traces/12_trig_act.same0 create mode 100644 lib/reasoners/eflint-haskell/tests/traces/13_sync.same0 create mode 100644 lib/reasoners/eflint-haskell/tests/traces/14_deriv_chain.same0 create mode 100644 lib/reasoners/eflint-haskell/tests/traces/14_deriv_chain.same1 create mode 100644 lib/reasoners/eflint-haskell/tests/traces/14_deriv_chain.same2 create mode 100644 lib/reasoners/eflint-haskell/tests/traces/15_sync_chain.same0 create mode 100644 lib/reasoners/eflint-haskell/tests/traces/15_sync_chain.same1 create mode 100644 lib/reasoners/eflint-haskell/tests/traces/16_act_viol.same0 create mode 100644 lib/reasoners/eflint-haskell/tests/traces/17_not_enabled_act.same0 create mode 100644 lib/reasoners/eflint-haskell/tests/traces/18_duty.same0 create mode 100644 lib/reasoners/eflint-haskell/tests/traces/19_naf.same0 create mode 100644 lib/reasoners/eflint-haskell/tests/traces/20_agg.same0 create mode 100644 lib/reasoners/eflint-haskell/tests/traces/21_aggs.same0 create mode 100644 lib/reasoners/eflint-haskell/tests/traces/22_forall_exists.same0 create mode 100644 lib/reasoners/eflint-haskell/tests/traces/23_duty_derived.same0 create mode 100644 lib/reasoners/eflint-haskell/tests/traces/23_duty_derived.same1 create mode 100644 lib/reasoners/eflint-haskell/tests/traces/23_duty_derived.same2 create mode 100644 lib/reasoners/eflint-haskell/tests/traces/access_union.same1 create mode 100644 lib/reasoners/eflint-haskell/tests/traces/access_union.same2 create mode 100644 lib/reasoners/eflint-haskell/tests/traces/bench_between.perf0 create mode 100644 lib/reasoners/eflint-haskell/tests/traces/bench_between.perf1 create mode 100644 lib/reasoners/eflint-haskell/tests/traces/bench_between.perf2 create mode 100644 lib/reasoners/eflint-haskell/tests/traces/bench_between.perf3 create mode 100644 lib/reasoners/eflint-haskell/tests/traces/bench_between.perf4 create mode 100644 lib/reasoners/eflint-haskell/tests/traces/bench_between.perf5 create mode 100644 lib/reasoners/eflint-haskell/tests/traces/bench_between.perf6 create mode 100644 lib/reasoners/eflint-haskell/tests/traces/bench_between.perf7 create mode 100644 lib/reasoners/eflint-haskell/tests/traces/bench_derive.perf1 create mode 100644 lib/reasoners/eflint-haskell/tests/traces/bench_derive.perf2 create mode 100644 lib/reasoners/eflint-haskell/tests/traces/bench_derive.perf3 create mode 100644 lib/reasoners/eflint-haskell/tests/traces/bench_derive.perf4 create mode 100644 lib/reasoners/eflint-haskell/tests/traces/bench_derive.perf5 create mode 100644 lib/reasoners/eflint-haskell/tests/traces/bench_derive.perf6 create mode 100644 lib/reasoners/eflint-haskell/tests/traces/bench_derive.perf7 create mode 100644 lib/reasoners/eflint-haskell/tests/traces/bench_derive_loop.perf0 create mode 100644 lib/reasoners/eflint-haskell/tests/traces/bench_derive_loop.perf1 create mode 100644 lib/reasoners/eflint-haskell/tests/traces/bench_triple.perf0 create mode 100644 lib/reasoners/eflint-haskell/tests/traces/bench_triple.perf1 create mode 100644 lib/reasoners/eflint-haskell/tests/traces/bench_triple.perf2 create mode 100644 lib/reasoners/eflint-haskell/tests/traces/bench_triple.perf3 create mode 100644 lib/reasoners/eflint-haskell/tests/traces/bench_triple.perf4 create mode 100644 lib/reasoners/eflint-haskell/tests/traces/bench_triple.perf5 create mode 100644 lib/reasoners/eflint-haskell/tests/traces/bench_triple.perf6 create mode 100644 lib/reasoners/eflint-haskell/tests/traces/bench_triple.perf7 create mode 100644 lib/reasoners/eflint-haskell/tests/traces/diff_1_naf.diff0 create mode 100644 lib/reasoners/eflint-haskell/tests/traces/diff_2_no_stable.diff0 create mode 100644 lib/reasoners/eflint-haskell/tests/traces/diff_3_2_stable.diff0 create mode 100644 lib/reasoners/eflint-haskell/tests/traces/diff_4_exists_forall.diff0 create mode 100644 lib/reasoners/eflint-haskell/tests/traces/diff_clingo_no_model.diff0 create mode 100644 lib/reasoners/eflint-haskell/tests/traces/moving.same0 create mode 100644 lib/reasoners/eflint-haskell/tests/traces/running-example.same0 create mode 100644 lib/reasoners/eflint-haskell/tests/traces/syncs-with.same0 diff --git a/lib/reasoners/eflint-haskell/src/lib.rs b/lib/reasoners/eflint-haskell/src/lib.rs index fb7cce9..1e94c93 100644 --- a/lib/reasoners/eflint-haskell/src/lib.rs +++ b/lib/reasoners/eflint-haskell/src/lib.rs @@ -4,7 +4,7 @@ // Created: // 16 Apr 2025, 23:09:00 // Last edited: -// 25 Apr 2025, 09:08:47 +// 25 Apr 2025, 16:37:24 // Auto updated? // Yes // @@ -15,5 +15,6 @@ // Define the submodules pub mod reasonerconn; +pub mod reasons; pub mod spec; pub mod trace; diff --git a/lib/reasoners/eflint-haskell/src/reasonerconn.rs b/lib/reasoners/eflint-haskell/src/reasonerconn.rs index b3109c6..d946a84 100644 --- a/lib/reasoners/eflint-haskell/src/reasonerconn.rs +++ b/lib/reasoners/eflint-haskell/src/reasonerconn.rs @@ -4,7 +4,7 @@ // Created: // 16 Apr 2025, 23:09:26 // Last edited: -// 17 Apr 2025, 00:04:49 +// 25 Apr 2025, 16:40:44 // Auto updated? // Yes // @@ -17,8 +17,9 @@ use std::borrow::Cow; use std::future::Future; use std::marker::PhantomData; use std::process::{ExitStatus, Stdio}; +use std::str::FromStr as _; -use error_trace::{ErrorTrace as _, Trace}; +use error_trace::ErrorTrace as _; use serde::{Deserialize, Serialize}; use spec::auditlogger::SessionedAuditLogger; use spec::reasonerconn::{ReasonerContext, ReasonerResponse}; @@ -26,9 +27,11 @@ use spec::{AuditLogger, ReasonerConnector}; use thiserror::Error; use tokio::io::AsyncWriteExt; use tokio::process::Command; -use tracing::debug; +use tracing::{debug, warn}; +use crate::reasons::{Problem, ReasonHandler}; use crate::spec::{EFlintable, EFlintableExt as _}; +use crate::trace::{Delta, Trace}; /***** ERRORS *****/ @@ -40,14 +43,14 @@ pub enum Error { LogContext { to: &'static str, #[source] - err: Trace, + err: error_trace::Trace, }, /// Failed to log the question to the given logger. #[error("Failed to log the question to {to}")] LogQuestion { to: &'static str, #[source] - err: Trace, + err: error_trace::Trace, }, #[error("Empty REPL-command given")] @@ -72,6 +75,12 @@ pub enum Error { }, #[error("Command {:?} failed with exit code {}\n\nstdout:\n{}\n{}\n{}\n\nstderr:\n{}\n{}\n{}\n", cmd, status.code().unwrap_or(-1), "-".repeat(80), stdout, "-".repeat(80), "-".repeat(80), stderr, "-".repeat(80))] CommandFailure { cmd: Command, status: ExitStatus, stdout: String, stderr: String }, + #[error("Failed to parse reasoner output\n\nstdout:\n{}\n{}\n{}\n", "-".repeat(80), output, "-".repeat(80))] + IllegalReasonerResponse { + output: String, + #[source] + err: crate::trace::Error, + }, } @@ -185,14 +194,14 @@ impl EFlintHaskellReasonerConnector { } impl ReasonerConnector for EFlintHaskellReasonerConnector where - R: Sync, + R: Sync + ReasonHandler, S: Send + Sync + EFlintable + Serialize, Q: Send + Sync + EFlintable + Serialize, { type Context = EFlintHaskellReasonerContext; type Error = Error; type Question = Q; - type Reason = R; + type Reason = R::Reason; type State = S; #[inline] @@ -256,8 +265,32 @@ where // Attempt to parse the output let output: Cow = String::from_utf8_lossy(&output.stdout); debug!("Reasoner output:\n{}\n{}\n{}\n", "-".repeat(80), output, "-".repeat(80)); - - todo!() + let trace: Trace = match Trace::from_str(output.as_ref()) { + Ok(trace) => trace, + Err(err) => return Err(Error::IllegalReasonerResponse { output: output.into(), err }), + }; + debug!("Reasoner trace:\n{}\n{}\n{}\n", "-".repeat(80), trace, "-".repeat(80)); + + // Analyze the output to find violations + // The rule is: + // 1. Check the last delta + // a. If it's a query, then it must succeed; or + // b. If it's not a query, it must not be a violation. + // 2. If there is no last delta, then we default to **success**. + Ok(trace + .deltas + .into_iter() + .last() + .map(|delta| match delta { + Delta::Query(query) if query.is_succes() => ReasonerResponse::Success, + Delta::Query(_) => ReasonerResponse::Violated(self.handler.handle(Problem::QueryFailed)), + Delta::Violation(viol) => ReasonerResponse::Violated(self.handler.handle(Problem::Violation(viol))), + delta => { + warn!("Got non-query, non-violation delta as last delta ({delta:?}); assuming OK"); + ReasonerResponse::Success + }, + }) + .unwrap_or(ReasonerResponse::Success)) } } } diff --git a/lib/reasoners/eflint-haskell/src/reasons.rs b/lib/reasoners/eflint-haskell/src/reasons.rs new file mode 100644 index 0000000..0076564 --- /dev/null +++ b/lib/reasoners/eflint-haskell/src/reasons.rs @@ -0,0 +1,145 @@ +// REASONS.rs +// by Lut99 +// +// Created: +// 25 Apr 2025, 16:36:41 +// Last edited: +// 25 Apr 2025, 16:55:09 +// Auto updated? +// Yes +// +// Description: +//! Defines reason handlers for the haskell interpreter. +// + +use std::fmt::{Display, Formatter, Result as FResult}; + +use crate::trace::Violation; + + +/***** AUXILLARY *****/ +/// Defines an empty reason. +#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] +pub struct NoReason; +impl Display for NoReason { + #[inline] + fn fmt(&self, f: &mut Formatter<'_>) -> FResult { write!(f, "") } +} + +/// Defines an optional reason. +#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] +pub struct OptReason(pub Option); +impl Display for OptReason { + #[inline] + fn fmt(&self, f: &mut Formatter<'_>) -> FResult { + match &self.0 { + Some(r) => r.fmt(f), + None => write!(f, ""), + } + } +} + + + +/// Defines either a [`Query`] or a [`Violation`]. +#[derive(Clone, Debug, Eq, PartialEq)] +pub enum Problem { + QueryFailed, + Violation(Violation), +} +impl Display for Problem { + #[inline] + fn fmt(&self, f: &mut Formatter<'_>) -> FResult { + match self { + Self::QueryFailed => write!(f, "Query failed"), + Self::Violation(v) => v.fmt(f), + } + } +} + + + + + +/***** INTERFACES *****/ +/// Converts a failed query/violation into a Reason. +pub trait ReasonHandler { + /// The reasons returned by this handler. + type Reason: Display; + + /// Maps a query/violation to a reason. + /// + /// # Arguments + /// - `problem`: A [`Problem::QueryFailed`] or a [`Problem::Violation`] that describes the + /// reason. + /// + /// # Returns + /// A [`Self::Reason`](ReasonHandler::Reason) that represents the outputted reason. + fn handle(&self, problem: Problem) -> Self::Reason; +} + + + + + +/***** LIBRARY *****/ +/// Reason handler that doesn't report anything. +#[derive(Debug)] +pub struct SilentHandler; +impl ReasonHandler for SilentHandler { + type Reason = NoReason; + + #[inline] + fn handle(&self, _problem: Problem) -> Self::Reason { NoReason } +} + + + +/// Reason handler reports only violations with a specific prefix. +#[derive(Debug)] +pub struct PrefixedHandler<'s> { + prefix: &'s str, +} +impl<'s> ReasonHandler for PrefixedHandler<'s> { + type Reason = OptReason; + + #[inline] + fn handle(&self, problem: Problem) -> Self::Reason { + match problem { + Problem::QueryFailed => OptReason(None), + Problem::Violation(Violation::Act(a)) => { + if a.inst.name.starts_with(self.prefix) { + OptReason(Some(Violation::Act(a))) + } else { + OptReason(None) + } + }, + Problem::Violation(Violation::Duty(d)) => { + if d.inst.name.starts_with(self.prefix) { + OptReason(Some(Violation::Duty(d))) + } else { + OptReason(None) + } + }, + Problem::Violation(Violation::Invariant(i)) => { + if i.name.starts_with(self.prefix) { + OptReason(Some(Violation::Invariant(i))) + } else { + OptReason(None) + } + }, + } + } +} + + + +/// Reason handler reports everything. +#[derive(Debug)] +pub struct VerboseHandler; +impl ReasonHandler for VerboseHandler { + type Reason = Problem; + + #[inline] + fn handle(&self, problem: Problem) -> Self::Reason { problem } +} diff --git a/lib/reasoners/eflint-haskell/src/trace.rs b/lib/reasoners/eflint-haskell/src/trace.rs index c96cb77..0c051e6 100644 --- a/lib/reasoners/eflint-haskell/src/trace.rs +++ b/lib/reasoners/eflint-haskell/src/trace.rs @@ -4,7 +4,7 @@ // Created: // 17 Apr 2025, 00:06:39 // Last edited: -// 25 Apr 2025, 16:26:47 +// 25 Apr 2025, 17:26:06 // Auto updated? // Yes // @@ -24,6 +24,7 @@ use thiserror::Error; /***** CONSTANTS *****/ const DISABLED_ACTION: &'static str = "disabled action:"; const EXEC_TRANS: &'static str = "executed transition:"; +const NEW_INVARIANT: &'static str = "New invariant"; const NEW_TYPE: &'static str = "New type"; const TRANS_ENABLED: &'static str = "(ENABLED)"; const TRANS_DISABLED: &'static str = "(DISABLED)"; @@ -51,6 +52,8 @@ pub enum Error { ExpectedInstanceAfterViolatedDuty { s: String }, #[error("Expected \"-\" to follow \"`\" while parsing transition trees at {s:?}")] ExpectedPipeAfterHook { s: String }, + #[error("Expected type name to follow \"New invariant\" at {s:?}")] + ExpectedTypeNameAfterNewInvariant { s: String }, #[error("Expected type name to follow \"New type\" at {s:?}")] ExpectedTypeNameAfterNewType { s: String }, #[error("Expected type name to follow \"violated invariant!\" at {s:?}")] @@ -111,6 +114,16 @@ pub struct Trace { /// The deltas emitted by eFLINT. pub deltas: Vec, } +impl Display for Trace { + #[inline] + fn fmt(&self, f: &mut Formatter) -> FResult { + for delta in &self.deltas { + delta.fmt(f)?; + writeln!(f)?; + } + Ok(()) + } +} impl FromStr for Trace { type Err = Error; @@ -142,6 +155,8 @@ impl FromStrHead for Trace { /// Defines a delta, which is like the toplevel instance of the trace. #[derive(Clone, Debug, Eq, PartialEq)] pub enum Delta { + /// A type is marked as an invariant. + NewInvariant(NewInvariant), /// A type definition. NewType(NewType), /// It's a postulation - i.e., a database update. @@ -152,12 +167,30 @@ pub enum Delta { Query(Query), /// It's a trigger - i.e., a transition. Trigger(Trigger), + /// It's a violation - i.e., an illegal state. + Violation(Violation), +} +impl Display for Delta { + #[inline] + fn fmt(&self, f: &mut Formatter<'_>) -> FResult { + match self { + Self::NewInvariant(ni) => ni.fmt(f), + Self::NewType(nt) => nt.fmt(f), + Self::Postulation(p) => p.fmt(f), + Self::Query(q) => q.fmt(f), + Self::Trigger(t) => t.fmt(f), + Self::Violation(v) => v.fmt(f), + } + } } impl FromStrHead for Vec { type Error = Error; #[inline] fn from_str_head(s: &str) -> Result, Self::Error> { + if let Some((rem, nin)) = NewInvariant::from_str_head(s)? { + return Ok(Some((rem, vec![Delta::NewInvariant(nin)]))); + } if let Some((rem, nty)) = NewType::from_str_head(s)? { return Ok(Some((rem, vec![Delta::NewType(nty)]))); } @@ -170,16 +203,52 @@ impl FromStrHead for Vec { if let Some((rem, trigs)) = Vec::::from_str_head(s)? { return Ok(Some((rem, trigs.into_iter().map(Delta::Trigger).collect()))); } + if let Some((rem, viols)) = Vec::::from_str_head(s)? { + return Ok(Some((rem, viols.into_iter().map(Delta::Violation).collect()))); + } Ok(None) } } +/// Defines an invariant definition. +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct NewInvariant { + /// The name of the newly defined invariant. + pub name: String, +} +impl Display for NewInvariant { + #[inline] + fn fmt(&self, f: &mut Formatter<'_>) -> FResult { write!(f, "Marked \"{}\" as invariant", self.name) } +} +impl FromStrHead for NewInvariant { + type Error = Error; + + #[inline] + fn from_str_head(s: &str) -> Result, Self::Error> { + // Parse the magic first + if !s.starts_with(NEW_INVARIANT) { + return Ok(None); + } + let rem = s[NEW_INVARIANT.len()..].trim_start(); + + // Then parse the type name + match TypeName::from_str_head(rem)? { + Some((rem, TypeName(name))) => Ok(Some((rem, Self { name }))), + None => Err(Error::ExpectedTypeNameAfterNewInvariant { s: rem.into() }), + } + } +} + /// Defines a type definition. #[derive(Clone, Debug, Eq, PartialEq)] pub struct NewType { /// The name of the newly defined type. pub name: String, } +impl Display for NewType { + #[inline] + fn fmt(&self, f: &mut Formatter<'_>) -> FResult { write!(f, "New type \"{}\"", self.name) } +} impl FromStrHead for NewType { type Error = Error; @@ -209,6 +278,18 @@ pub enum Query { /// The answer is no Fail, } +impl Query { + /// Returns true if this query was a success. + /// + /// # Returns + /// True if this is a [`Query::Succes`], or false otherwise. + #[inline] + pub const fn is_succes(&self) -> bool { matches!(self, Self::Succes) } +} +impl Display for Query { + #[inline] + fn fmt(&self, f: &mut Formatter<'_>) -> FResult { if self.is_succes() { write!(f, "Query succes") } else { write!(f, "Query failed") } } +} impl FromStrHead for Query { type Error = Infallible; @@ -234,6 +315,16 @@ pub struct Postulation { /// The instance that was postulated. pub inst: Instance, } +impl Display for Postulation { + #[inline] + fn fmt(&self, f: &mut Formatter<'_>) -> FResult { + match self.op { + PostulationOp::Create => write!(f, "Created {}", self.inst), + PostulationOp::Terminate => write!(f, "Terminated {}", self.inst), + PostulationOp::Obfuscate => write!(f, "Obfuscated {}", self.inst), + } + } +} impl FromStrHead for Postulation { type Error = Error; @@ -300,6 +391,16 @@ pub struct Trigger { /// Not given for events. pub enabled: Option, } +impl Display for Trigger { + #[inline] + fn fmt(&self, f: &mut Formatter<'_>) -> FResult { + write!(f, "Triggered {}{}", self.inst, match self.enabled { + Some(true) => " (ENABLED)", + Some(false) => " (DISABLED)", + None => "", + }) + } +} impl FromStrHead for Vec { type Error = Error; @@ -377,6 +478,16 @@ pub enum Violation { /// An invariant has been violated. Invariant(InvariantViolation), } +impl Display for Violation { + #[inline] + fn fmt(&self, f: &mut Formatter<'_>) -> FResult { + match self { + Self::Act(a) => a.fmt(f), + Self::Duty(d) => d.fmt(f), + Self::Invariant(i) => i.fmt(f), + } + } +} impl FromStrHead for Vec { type Error = Error; @@ -411,7 +522,11 @@ impl FromStrHead for Vec { #[derive(Clone, Debug, Eq, PartialEq)] pub struct ActViolation { /// The violated instance. - pub inst: Instance, + pub inst: Composite, +} +impl Display for ActViolation { + #[inline] + fn fmt(&self, f: &mut Formatter<'_>) -> FResult { write!(f, "Violated action {}", self.inst) } } impl FromStrHead for ActViolation { type Error = Error; @@ -425,7 +540,7 @@ impl FromStrHead for ActViolation { let rem = s[DISABLED_ACTION.len()..].trim_start(); // Then parse the instance that was violated - match Instance::from_str_head(rem)? { + match Composite::from_str_head(rem)? { Some((rem, inst)) => Ok(Some((rem, ActViolation { inst }))), None => Err(Error::ExpectedInstanceAfterDisabledAction { s: rem.into() }), } @@ -436,7 +551,11 @@ impl FromStrHead for ActViolation { #[derive(Clone, Debug, Eq, PartialEq)] pub struct DutyViolation { /// The violated instance. - pub inst: Instance, + pub inst: Composite, +} +impl Display for DutyViolation { + #[inline] + fn fmt(&self, f: &mut Formatter<'_>) -> FResult { write!(f, "Violated duty {}", self.inst) } } impl FromStrHead for DutyViolation { type Error = Error; @@ -450,7 +569,7 @@ impl FromStrHead for DutyViolation { let rem = s[VIOLATED_DUTY.len()..].trim_start(); // Then parse the instance that was violated - match Instance::from_str_head(rem)? { + match Composite::from_str_head(rem)? { Some((rem, inst)) => Ok(Some((rem, DutyViolation { inst }))), None => Err(Error::ExpectedInstanceAfterViolatedDuty { s: rem.into() }), } @@ -463,6 +582,10 @@ pub struct InvariantViolation { /// The violated invariant. pub name: String, } +impl Display for InvariantViolation { + #[inline] + fn fmt(&self, f: &mut Formatter<'_>) -> FResult { write!(f, "Violated invariant {}", self.name) } +} impl FromStrHead for InvariantViolation { type Error = Error; @@ -494,6 +617,16 @@ pub enum Instance { /// A composite type. Composite(Composite), } +impl Display for Instance { + #[inline] + fn fmt(&self, f: &mut Formatter<'_>) -> FResult { + match self { + Self::StringLit(sl) => sl.fmt(f), + Self::IntLit(il) => il.fmt(f), + Self::Composite(c) => c.fmt(f), + } + } +} impl FromStrHead for Instance { type Error = Error; @@ -515,6 +648,10 @@ impl FromStrHead for Instance { /// Defines a string literal. #[derive(Clone, Debug, Eq, PartialEq)] pub struct StringLit(pub String); +impl Display for StringLit { + #[inline] + fn fmt(&self, f: &mut Formatter<'_>) -> FResult { write!(f, "{:?}", self.0) } +} impl FromStrHead for StringLit { type Error = Error; @@ -568,6 +705,10 @@ impl FromStrHead for StringLit { /// Defines an integer literal. #[derive(Clone, Copy, Debug, Eq, PartialEq)] pub struct IntLit(pub i64); +impl Display for IntLit { + #[inline] + fn fmt(&self, f: &mut Formatter<'_>) -> FResult { write!(f, "{}", self.0) } +} impl FromStrHead for IntLit { type Error = Error; @@ -626,6 +767,12 @@ pub struct Composite { /// The arguments of the type. pub args: Vec, } +impl Display for Composite { + #[inline] + fn fmt(&self, f: &mut Formatter<'_>) -> FResult { + write!(f, "{}({})", self.name, self.args.iter().map(Instance::to_string).collect::>().join(", ")) + } +} impl FromStrHead for Composite { type Error = Error; @@ -881,20 +1028,20 @@ mod tests { fn test_parse_violation() { assert_eq!( Vec::::from_str_head("violations:disabled action:foo()"), - Ok(Some(("", vec![Violation::Act(ActViolation { inst: Instance::Composite(Composite { name: "foo".into(), args: vec![] }) })]))) + Ok(Some(("", vec![Violation::Act(ActViolation { inst: Composite { name: "foo".into(), args: vec![] } })]))) ); assert_eq!( Vec::::from_str_head("violations: disabled action:\n\n\nfoo()violated duty!:bar()"), Ok(Some(("", vec![ - Violation::Act(ActViolation { inst: Instance::Composite(Composite { name: "foo".into(), args: vec![] }) }), - Violation::Duty(DutyViolation { inst: Instance::Composite(Composite { name: "bar".into(), args: vec![] }) }) + Violation::Act(ActViolation { inst: Composite { name: "foo".into(), args: vec![] } }), + Violation::Duty(DutyViolation { inst: Composite { name: "bar".into(), args: vec![] } }) ]))) ); assert_eq!( Vec::::from_str_head("violations:disabled action:foo()violated duty!:bar()violated invariant!:baz"), Ok(Some(("", vec![ - Violation::Act(ActViolation { inst: Instance::Composite(Composite { name: "foo".into(), args: vec![] }) }), - Violation::Duty(DutyViolation { inst: Instance::Composite(Composite { name: "bar".into(), args: vec![] }) }), + Violation::Act(ActViolation { inst: Composite { name: "foo".into(), args: vec![] } }), + Violation::Duty(DutyViolation { inst: Composite { name: "bar".into(), args: vec![] } }), Violation::Invariant(InvariantViolation { name: "baz".into() }) ]))) ); @@ -904,7 +1051,7 @@ mod tests { fn test_parse_act_violation() { assert_eq!( ActViolation::from_str_head("disabled action: foo()"), - Ok(Some(("", ActViolation { inst: Instance::Composite(Composite { name: "foo".into(), args: vec![] }) }))) + Ok(Some(("", ActViolation { inst: Composite { name: "foo".into(), args: vec![] } }))) ); assert_eq!(ActViolation::from_str_head("disabled action: foo"), Err(Error::ExpectedInstanceAfterDisabledAction { s: "foo".into() })); assert_eq!(ActViolation::from_str_head("disabled actio: foo()"), Ok(None)); @@ -914,7 +1061,7 @@ mod tests { fn test_parse_duty_violation() { assert_eq!( DutyViolation::from_str_head("violated duty!: foo()"), - Ok(Some(("", DutyViolation { inst: Instance::Composite(Composite { name: "foo".into(), args: vec![] }) }))) + Ok(Some(("", DutyViolation { inst: Composite { name: "foo".into(), args: vec![] } }))) ); assert_eq!(DutyViolation::from_str_head("violated duty!: foo"), Err(Error::ExpectedInstanceAfterViolatedDuty { s: "foo".into() })); assert_eq!(DutyViolation::from_str_head("violated duty! foo()"), Ok(None)); diff --git a/lib/reasoners/eflint-haskell/tests/traces.rs b/lib/reasoners/eflint-haskell/tests/traces.rs new file mode 100644 index 0000000..fad16a3 --- /dev/null +++ b/lib/reasoners/eflint-haskell/tests/traces.rs @@ -0,0 +1,41 @@ +// TRACES.rs +// by Lut99 +// +// Created: +// 25 Apr 2025, 17:18:10 +// Last edited: +// 25 Apr 2025, 17:23:15 +// Auto updated? +// Yes +// +// Description: +//! Test for running the full trace parser on various files. +// + +use std::fs::{self, ReadDir}; +use std::path::PathBuf; +use std::str::FromStr as _; + +use eflint_haskell_reasoner::trace::Trace; + + +/***** Tests *****/ +#[test] +fn test_all_trace_files() { + // Visit all traces + let traces_path: PathBuf = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("tests").join("traces"); + let entries: ReadDir = + fs::read_dir(&traces_path).unwrap_or_else(|err| panic!("Failed to read traces directory {:?}: {err}", traces_path.display())); + for (i, entry) in entries.enumerate() { + let entry = entry.unwrap_or_else(|err| panic!("Failed to read entry {i} in traces directory {:?}: {err}", traces_path.display())); + + // Load the file + let trace: String = + fs::read_to_string(entry.path()).unwrap_or_else(|err| panic!("Failed to read trace file {:?}: {err}", entry.path().display())); + + // Attempt to parse it + if let Err(err) = Trace::from_str(&trace) { + panic!("Failed to parse trace of trace file {:?}: {err}\n\n{}\n{}\n{}\n", entry.path().display(), "-".repeat(80), trace, "-".repeat(80)); + } + } +} diff --git a/lib/reasoners/eflint-haskell/tests/traces/01_deriv_holds.same0 b/lib/reasoners/eflint-haskell/tests/traces/01_deriv_holds.same0 new file mode 100644 index 0000000..fb0af7f --- /dev/null +++ b/lib/reasoners/eflint-haskell/tests/traces/01_deriv_holds.same0 @@ -0,0 +1,2 @@ +New type x ++x("hi") diff --git a/lib/reasoners/eflint-haskell/tests/traces/02_create_holds.same0 b/lib/reasoners/eflint-haskell/tests/traces/02_create_holds.same0 new file mode 100644 index 0000000..fb0af7f --- /dev/null +++ b/lib/reasoners/eflint-haskell/tests/traces/02_create_holds.same0 @@ -0,0 +1,2 @@ +New type x ++x("hi") diff --git a/lib/reasoners/eflint-haskell/tests/traces/03_derive_chain.same0 b/lib/reasoners/eflint-haskell/tests/traces/03_derive_chain.same0 new file mode 100644 index 0000000..ba45549 --- /dev/null +++ b/lib/reasoners/eflint-haskell/tests/traces/03_derive_chain.same0 @@ -0,0 +1,4 @@ +New type x +New type y ++x("hi") ++y("hi") diff --git a/lib/reasoners/eflint-haskell/tests/traces/04_suppress_derive.same0 b/lib/reasoners/eflint-haskell/tests/traces/04_suppress_derive.same0 new file mode 100644 index 0000000..76beb21 --- /dev/null +++ b/lib/reasoners/eflint-haskell/tests/traces/04_suppress_derive.same0 @@ -0,0 +1 @@ +New type x diff --git a/lib/reasoners/eflint-haskell/tests/traces/05_suppress_create.same0 b/lib/reasoners/eflint-haskell/tests/traces/05_suppress_create.same0 new file mode 100644 index 0000000..fb0af7f --- /dev/null +++ b/lib/reasoners/eflint-haskell/tests/traces/05_suppress_create.same0 @@ -0,0 +1,2 @@ +New type x ++x("hi") diff --git a/lib/reasoners/eflint-haskell/tests/traces/06_trigger.same0 b/lib/reasoners/eflint-haskell/tests/traces/06_trigger.same0 new file mode 100644 index 0000000..9a2999d --- /dev/null +++ b/lib/reasoners/eflint-haskell/tests/traces/06_trigger.same0 @@ -0,0 +1,3 @@ +New type x +executed transition: +x() diff --git a/lib/reasoners/eflint-haskell/tests/traces/07_create_terminate.same0 b/lib/reasoners/eflint-haskell/tests/traces/07_create_terminate.same0 new file mode 100644 index 0000000..d35a980 --- /dev/null +++ b/lib/reasoners/eflint-haskell/tests/traces/07_create_terminate.same0 @@ -0,0 +1,4 @@ +New type x +executed transition: +x(string("hi")) ++string("hi") diff --git a/lib/reasoners/eflint-haskell/tests/traces/08_create_seq.same0 b/lib/reasoners/eflint-haskell/tests/traces/08_create_seq.same0 new file mode 100644 index 0000000..261032e --- /dev/null +++ b/lib/reasoners/eflint-haskell/tests/traces/08_create_seq.same0 @@ -0,0 +1,4 @@ +New type x ++x("a") ++x("b") ++x("c") diff --git a/lib/reasoners/eflint-haskell/tests/traces/09_create_terminate_seq.same0 b/lib/reasoners/eflint-haskell/tests/traces/09_create_terminate_seq.same0 new file mode 100644 index 0000000..4aff419 --- /dev/null +++ b/lib/reasoners/eflint-haskell/tests/traces/09_create_terminate_seq.same0 @@ -0,0 +1,7 @@ +New type x ++x("a") ++x("b") +-x("c") +-x("b") +-x("a") ++x("b") diff --git a/lib/reasoners/eflint-haskell/tests/traces/10_create_obfuscate.same0 b/lib/reasoners/eflint-haskell/tests/traces/10_create_obfuscate.same0 new file mode 100644 index 0000000..116f20e --- /dev/null +++ b/lib/reasoners/eflint-haskell/tests/traces/10_create_obfuscate.same0 @@ -0,0 +1,4 @@ +New type x +executed transition: +x(string("wah")) ++string("wah") diff --git a/lib/reasoners/eflint-haskell/tests/traces/11_complex_facts.same0 b/lib/reasoners/eflint-haskell/tests/traces/11_complex_facts.same0 new file mode 100644 index 0000000..32b0433 --- /dev/null +++ b/lib/reasoners/eflint-haskell/tests/traces/11_complex_facts.same0 @@ -0,0 +1,12 @@ +New type x +New type y +New type z ++x("hey") ++x("hi") ++x("ho") ++x("woo") ++y(x("hey"),x("ho")) ++z(x("hi"),y(x("hey"),x("ho")),y(x("ho"),x("woo"))) ++y(x("ho"),x("woo")) ++x("wah") ++y(x("wah"),x("ho")) diff --git a/lib/reasoners/eflint-haskell/tests/traces/12_trig_act.same0 b/lib/reasoners/eflint-haskell/tests/traces/12_trig_act.same0 new file mode 100644 index 0000000..5f29cb6 --- /dev/null +++ b/lib/reasoners/eflint-haskell/tests/traces/12_trig_act.same0 @@ -0,0 +1,10 @@ +New type go +New type person +executed transition: +go(person("Amy")) (DISABLED) +violations: + disabled action: go(person("Amy")) +executed transition: +go(person("Bob")) (DISABLED) +violations: + disabled action: go(person("Bob")) diff --git a/lib/reasoners/eflint-haskell/tests/traces/13_sync.same0 b/lib/reasoners/eflint-haskell/tests/traces/13_sync.same0 new file mode 100644 index 0000000..0d0ac7d --- /dev/null +++ b/lib/reasoners/eflint-haskell/tests/traces/13_sync.same0 @@ -0,0 +1,17 @@ +New type go +executed transition: +go(string("x")) (DISABLED) +violations: + disabled action: go(string("x")) +executed transition: +go(string("y")) (DISABLED) +| +`- go(string("x")) (DISABLED) +violations: + disabled action: go(string("y")) +executed transition: +go(string("z")) (DISABLED) +| +`- go(string("x")) (DISABLED) +violations: + disabled action: go(string("z")) diff --git a/lib/reasoners/eflint-haskell/tests/traces/14_deriv_chain.same0 b/lib/reasoners/eflint-haskell/tests/traces/14_deriv_chain.same0 new file mode 100644 index 0000000..678acaf --- /dev/null +++ b/lib/reasoners/eflint-haskell/tests/traces/14_deriv_chain.same0 @@ -0,0 +1,6 @@ +New type x ++x(int(0)) ++x(int(1)) ++x(int(2)) ++x(int(3)) ++x(int(4)) diff --git a/lib/reasoners/eflint-haskell/tests/traces/14_deriv_chain.same1 b/lib/reasoners/eflint-haskell/tests/traces/14_deriv_chain.same1 new file mode 100644 index 0000000..7669248 --- /dev/null +++ b/lib/reasoners/eflint-haskell/tests/traces/14_deriv_chain.same1 @@ -0,0 +1,8 @@ +New type x ++x(int(0)) ++x(int(1)) ++x(int(2)) ++x(int(3)) ++x(int(4)) ++x(int(5)) ++x(int(6)) diff --git a/lib/reasoners/eflint-haskell/tests/traces/14_deriv_chain.same2 b/lib/reasoners/eflint-haskell/tests/traces/14_deriv_chain.same2 new file mode 100644 index 0000000..c11593f --- /dev/null +++ b/lib/reasoners/eflint-haskell/tests/traces/14_deriv_chain.same2 @@ -0,0 +1,11 @@ +New type x ++x(int(0)) ++x(int(1)) ++x(int(2)) ++x(int(3)) ++x(int(4)) +~x(int(3)) +~x(int(4)) +~x(int(0)) +~x(int(1)) +~x(int(2)) diff --git a/lib/reasoners/eflint-haskell/tests/traces/15_sync_chain.same0 b/lib/reasoners/eflint-haskell/tests/traces/15_sync_chain.same0 new file mode 100644 index 0000000..8cf9d57 --- /dev/null +++ b/lib/reasoners/eflint-haskell/tests/traces/15_sync_chain.same0 @@ -0,0 +1,16 @@ +New type x +executed transition: +x(int(4)) +| +`- x(int(3)) + | + `- x(int(2)) + | + `- x(int(1)) + | + `- x(int(0)) ++int(0) ++int(1) ++int(2) ++int(3) ++int(4) diff --git a/lib/reasoners/eflint-haskell/tests/traces/15_sync_chain.same1 b/lib/reasoners/eflint-haskell/tests/traces/15_sync_chain.same1 new file mode 100644 index 0000000..532d058 --- /dev/null +++ b/lib/reasoners/eflint-haskell/tests/traces/15_sync_chain.same1 @@ -0,0 +1,42 @@ +New type x +executed transition: +x(int(7)) +| +`- x(int(6)) + | + `- x(int(5)) + | + `- x(int(4)) + | + `- x(int(3)) + | + `- x(int(2)) + | + `- x(int(1)) + | + `- x(int(0)) ++int(0) ++int(1) ++int(2) ++int(3) ++int(4) ++int(5) ++int(6) ++int(7) +executed transition: +x(int(3)) +| +`- x(int(2)) + | + `- x(int(1)) + | + `- x(int(0)) +-x(int(2)) +executed transition: +x(int(3)) +| +`- x(int(2)) + | + `- x(int(1)) + | + `- x(int(0)) diff --git a/lib/reasoners/eflint-haskell/tests/traces/16_act_viol.same0 b/lib/reasoners/eflint-haskell/tests/traces/16_act_viol.same0 new file mode 100644 index 0000000..f6a4f10 --- /dev/null +++ b/lib/reasoners/eflint-haskell/tests/traces/16_act_viol.same0 @@ -0,0 +1,17 @@ +New type x +executed transition: +x(int(0)) (DISABLED) +violations: + disabled action: x(int(0)) ++x(int(0)) +executed transition: +x(int(0)) (ENABLED) ++x(int(2)) +executed transition: +x(int(2)) (ENABLED) +-x(int(2)) +executed transition: +x(int(2)) (DISABLED) +violations: + disabled action: x(int(2)) ++x(int(2)) diff --git a/lib/reasoners/eflint-haskell/tests/traces/17_not_enabled_act.same0 b/lib/reasoners/eflint-haskell/tests/traces/17_not_enabled_act.same0 new file mode 100644 index 0000000..566ecf3 --- /dev/null +++ b/lib/reasoners/eflint-haskell/tests/traces/17_not_enabled_act.same0 @@ -0,0 +1,17 @@ +New type x +executed transition: +x(int(3)) (DISABLED) +violations: + disabled action: x(int(3)) ++x(int(3)) +executed transition: +x(int(3)) (ENABLED) +executed transition: +x(int(8)) (DISABLED) +violations: + disabled action: x(int(8)) ++x(int(8)) +executed transition: +x(int(8)) (DISABLED) +violations: + disabled action: x(int(8)) diff --git a/lib/reasoners/eflint-haskell/tests/traces/18_duty.same0 b/lib/reasoners/eflint-haskell/tests/traces/18_duty.same0 new file mode 100644 index 0000000..25fc632 --- /dev/null +++ b/lib/reasoners/eflint-haskell/tests/traces/18_duty.same0 @@ -0,0 +1,8 @@ +New type bad +New type duty ++duty(int(2),string("x")) ++duty(int(2),string("y")) ++bad(duty(int(3),string("x"))) +violations: + violated duty!: duty(int(3),string("x")) ++duty(int(3),string("x")) diff --git a/lib/reasoners/eflint-haskell/tests/traces/19_naf.same0 b/lib/reasoners/eflint-haskell/tests/traces/19_naf.same0 new file mode 100644 index 0000000..93c76a6 --- /dev/null +++ b/lib/reasoners/eflint-haskell/tests/traces/19_naf.same0 @@ -0,0 +1,11 @@ +New type x ++x(0) +~x(0) ++x(1) +~x(1) ++x(0) +~x(0) ++x(1) +~x(1) ++x(0) +-x(0) diff --git a/lib/reasoners/eflint-haskell/tests/traces/20_agg.same0 b/lib/reasoners/eflint-haskell/tests/traces/20_agg.same0 new file mode 100644 index 0000000..ba9fe1a --- /dev/null +++ b/lib/reasoners/eflint-haskell/tests/traces/20_agg.same0 @@ -0,0 +1,9 @@ +New type x +New type count ++count(0) +~count(0) ++count(1) ++x(1) +~count(1) ++count(2) ++x(2) diff --git a/lib/reasoners/eflint-haskell/tests/traces/21_aggs.same0 b/lib/reasoners/eflint-haskell/tests/traces/21_aggs.same0 new file mode 100644 index 0000000..bfb1783 --- /dev/null +++ b/lib/reasoners/eflint-haskell/tests/traces/21_aggs.same0 @@ -0,0 +1,23 @@ +New type countx +New type sumx +New type x ++countx(0) ++sumx(0) +~countx(0) +~sumx(0) ++countx(1) ++sumx(1) ++x(1) +~countx(1) +~sumx(1) ++countx(2) ++x(3) ++sumx(4) +~countx(2) ++x(0) ++countx(3) +~countx(3) +~x(3) +~sumx(4) ++sumx(1) ++countx(2) diff --git a/lib/reasoners/eflint-haskell/tests/traces/22_forall_exists.same0 b/lib/reasoners/eflint-haskell/tests/traces/22_forall_exists.same0 new file mode 100644 index 0000000..9bc1ff3 --- /dev/null +++ b/lib/reasoners/eflint-haskell/tests/traces/22_forall_exists.same0 @@ -0,0 +1,16 @@ +New type no_dubs +New type some +New type x +New type zeta ++no_dubs("") ++zeta("") +~zeta("") ++some("") ++x(2) +~no_dubs("") ++x(3) +~x(2) ++no_dubs("") +~some("") +~x(3) ++zeta("") diff --git a/lib/reasoners/eflint-haskell/tests/traces/23_duty_derived.same0 b/lib/reasoners/eflint-haskell/tests/traces/23_duty_derived.same0 new file mode 100644 index 0000000..5c1e9d5 --- /dev/null +++ b/lib/reasoners/eflint-haskell/tests/traces/23_duty_derived.same0 @@ -0,0 +1,7 @@ +violations: + violated duty!: d(string("foo"),string("foo")) +New type d ++string("foo") ++d(string("foo"),string("foo")) +violations: + violated duty!: d(string("foo"),string("foo")) diff --git a/lib/reasoners/eflint-haskell/tests/traces/23_duty_derived.same1 b/lib/reasoners/eflint-haskell/tests/traces/23_duty_derived.same1 new file mode 100644 index 0000000..1b04a32 --- /dev/null +++ b/lib/reasoners/eflint-haskell/tests/traces/23_duty_derived.same1 @@ -0,0 +1,9 @@ +violations: + violated duty!: d(string("foo"),string("foo")) +New type d ++string("foo") ++d(string("foo"),string("foo")) +violations: + violated duty!: d(string("foo"),string("bar")) + violated duty!: d(string("foo"),string("foo")) ++d(string("foo"),string("bar")) diff --git a/lib/reasoners/eflint-haskell/tests/traces/23_duty_derived.same2 b/lib/reasoners/eflint-haskell/tests/traces/23_duty_derived.same2 new file mode 100644 index 0000000..5c1e9d5 --- /dev/null +++ b/lib/reasoners/eflint-haskell/tests/traces/23_duty_derived.same2 @@ -0,0 +1,7 @@ +violations: + violated duty!: d(string("foo"),string("foo")) +New type d ++string("foo") ++d(string("foo"),string("foo")) +violations: + violated duty!: d(string("foo"),string("foo")) diff --git a/lib/reasoners/eflint-haskell/tests/traces/access_union.same1 b/lib/reasoners/eflint-haskell/tests/traces/access_union.same1 new file mode 100644 index 0000000..36a2520 --- /dev/null +++ b/lib/reasoners/eflint-haskell/tests/traces/access_union.same1 @@ -0,0 +1,86 @@ +New type accurate-for-purpose +New type controller +New type data +New type processes +New type processor +New type purpose +New type subject +New type subject-of +New type collect-personal-data +New type consent +New type give-consent +New type access +New type actual_dataset +New type affiliated-with +New type code +New type coded +New type coordinator +New type country +New type country_of +New type dataset +New type dcog +New type dipg +New type donor +New type ecommittee +New type eligible-access +New type findings +New type member +New type person +New type project +New type proposal +New type researcher +New type thirdparty ++dcog("DCOG") ++dipg("DIPG") ++ecommittee("EC") +New type approve-project +New type approved +New type duty-to-select-data +New type duty-to-send-data +New type duty-to-use-data-for-project +New type letter-of-approval-sent +New type letter-of-approval-signed +New type make-data-available +New type propose-project +New type select-data +New type selected +New type send-data +New type send-letter-of-approval +New type sign-letter-of-approval ++processor("DCOG") ++purpose("DIPGResearch") +New type asset +New type read +New type write +New type owner-of ++controller("HospitalA") ++member("HospitalA") ++processor("HospitalA") ++researcher("HospitalA") ++actor("John") ++affiliated-with(person("John"),member("HospitalA")) ++donor("Alice") ++subject("Alice") ++give-consent(subject("Alice"),controller("HospitalA"),purpose("DIPGResearch")) +executed transition: +give-consent(subject("Alice"),controller("HospitalA"),purpose("DIPGResearch")) (ENABLED) +~give-consent(subject("Alice"),controller("HospitalA"),purpose("DIPGResearch")) ++consent(subject("Alice"),controller("HospitalA"),purpose("DIPGResearch")) ++donor("Bob") ++subject("Bob") ++give-consent(subject("Bob"),controller("HospitalA"),purpose("DIPGResearch")) +executed transition: +give-consent(subject("Bob"),controller("HospitalA"),purpose("DIPGResearch")) (ENABLED) +~give-consent(subject("Bob"),controller("HospitalA"),purpose("DIPGResearch")) ++consent(subject("Bob"),controller("HospitalA"),purpose("DIPGResearch")) ++asset("X1") ++data("X1") ++dataset("X1") ++accurate-for-purpose(data("X1"),purpose("DIPGResearch")) ++make-data-available(member("HospitalA"),dcog("DCOG"),dataset("X1")) ++access(actor("John"),asset("X1")) ++write(actor("John"),asset("X1")) ++coded(actual_dataset("X1")) ++subject-of(subject("Alice"),data("X1")) ++subject-of(subject("Bob"),data("X1")) +query successful diff --git a/lib/reasoners/eflint-haskell/tests/traces/access_union.same2 b/lib/reasoners/eflint-haskell/tests/traces/access_union.same2 new file mode 100644 index 0000000..9733994 --- /dev/null +++ b/lib/reasoners/eflint-haskell/tests/traces/access_union.same2 @@ -0,0 +1,85 @@ +New type accurate-for-purpose +New type controller +New type data +New type processes +New type processor +New type purpose +New type subject +New type subject-of +New type collect-personal-data +New type consent +New type give-consent +New type access +New type actual_dataset +New type affiliated-with +New type code +New type coded +New type coordinator +New type country +New type country_of +New type dataset +New type dcog +New type dipg +New type donor +New type ecommittee +New type eligible-access +New type findings +New type member +New type person +New type project +New type proposal +New type researcher +New type thirdparty ++dcog("DCOG") ++dipg("DIPG") ++ecommittee("EC") +New type approve-project +New type approved +New type duty-to-select-data +New type duty-to-send-data +New type duty-to-use-data-for-project +New type letter-of-approval-sent +New type letter-of-approval-signed +New type make-data-available +New type propose-project +New type select-data +New type selected +New type send-data +New type send-letter-of-approval +New type sign-letter-of-approval ++processor("DCOG") ++purpose("DIPGResearch") +New type asset +New type read +New type write +New type owner-of ++controller("HospitalB") ++member("HospitalB") ++processor("HospitalB") ++researcher("HospitalB") ++actor("Eve") ++affiliated-with(person("Eve"),member("HospitalB")) ++project("P1") ++purpose("P1") ++propose-project(researcher("HospitalB"),ecommittee("EC"),project("P1")) +executed transition: +propose-project(researcher("HospitalB"),ecommittee("EC"),project("P1")) (ENABLED) ++approve-project(ecommittee("EC"),member("HospitalB"),project("P1")) ++proposal(member("HospitalB"),purpose("P1"),project("P1")) +executed transition: +approve-project(ecommittee("EC"),member("HospitalB"),project("P1")) (ENABLED) ++send-letter-of-approval(dipg("DIPG"),member("HospitalB"),project("P1")) ++approved(project("P1"),member("HospitalB")) +executed transition: +send-letter-of-approval(dipg("DIPG"),member("HospitalB"),project("P1")) (ENABLED) ++sign-letter-of-approval(researcher("HospitalB"),ecommittee("EC"),project("P1")) ++letter-of-approval-sent(project("P1"),member("HospitalB")) +executed transition: +sign-letter-of-approval(researcher("HospitalB"),ecommittee("EC"),project("P1")) (ENABLED) ++duty-to-select-data(ecommittee("EC"),member("HospitalB"),project("P1")) ++duty-to-send-data(ecommittee("EC"),member("HospitalB"),project("P1")) ++letter-of-approval-signed(project("P1"),member("HospitalB")) ++asset("X1") ++data("X1") ++dataset("X1") +query failed diff --git a/lib/reasoners/eflint-haskell/tests/traces/bench_between.perf0 b/lib/reasoners/eflint-haskell/tests/traces/bench_between.perf0 new file mode 100644 index 0000000..76beb21 --- /dev/null +++ b/lib/reasoners/eflint-haskell/tests/traces/bench_between.perf0 @@ -0,0 +1 @@ +New type x diff --git a/lib/reasoners/eflint-haskell/tests/traces/bench_between.perf1 b/lib/reasoners/eflint-haskell/tests/traces/bench_between.perf1 new file mode 100644 index 0000000..578d029 --- /dev/null +++ b/lib/reasoners/eflint-haskell/tests/traces/bench_between.perf1 @@ -0,0 +1,6 @@ +New type x ++x(0) ++x(1) ++x(2) ++x(3) ++x(4) diff --git a/lib/reasoners/eflint-haskell/tests/traces/bench_between.perf2 b/lib/reasoners/eflint-haskell/tests/traces/bench_between.perf2 new file mode 100644 index 0000000..b8e09a5 --- /dev/null +++ b/lib/reasoners/eflint-haskell/tests/traces/bench_between.perf2 @@ -0,0 +1,10 @@ +New type x ++x(0) ++x(1) ++x(2) ++x(3) ++x(4) ++x(5) ++x(6) ++x(7) ++x(8) diff --git a/lib/reasoners/eflint-haskell/tests/traces/bench_between.perf3 b/lib/reasoners/eflint-haskell/tests/traces/bench_between.perf3 new file mode 100644 index 0000000..cd43d4e --- /dev/null +++ b/lib/reasoners/eflint-haskell/tests/traces/bench_between.perf3 @@ -0,0 +1,18 @@ +New type x ++x(0) ++x(1) ++x(2) ++x(3) ++x(4) ++x(5) ++x(6) ++x(7) ++x(8) ++x(9) ++x(10) ++x(11) ++x(12) ++x(13) ++x(14) ++x(15) ++x(16) diff --git a/lib/reasoners/eflint-haskell/tests/traces/bench_between.perf4 b/lib/reasoners/eflint-haskell/tests/traces/bench_between.perf4 new file mode 100644 index 0000000..647afce --- /dev/null +++ b/lib/reasoners/eflint-haskell/tests/traces/bench_between.perf4 @@ -0,0 +1,34 @@ +New type x ++x(0) ++x(1) ++x(2) ++x(3) ++x(4) ++x(5) ++x(6) ++x(7) ++x(8) ++x(9) ++x(10) ++x(11) ++x(12) ++x(13) ++x(14) ++x(15) ++x(16) ++x(17) ++x(18) ++x(19) ++x(20) ++x(21) ++x(22) ++x(23) ++x(24) ++x(25) ++x(26) ++x(27) ++x(28) ++x(29) ++x(30) ++x(31) ++x(32) diff --git a/lib/reasoners/eflint-haskell/tests/traces/bench_between.perf5 b/lib/reasoners/eflint-haskell/tests/traces/bench_between.perf5 new file mode 100644 index 0000000..5b23705 --- /dev/null +++ b/lib/reasoners/eflint-haskell/tests/traces/bench_between.perf5 @@ -0,0 +1,66 @@ +New type x ++x(0) ++x(1) ++x(2) ++x(3) ++x(4) ++x(5) ++x(6) ++x(7) ++x(8) ++x(9) ++x(10) ++x(11) ++x(12) ++x(13) ++x(14) ++x(15) ++x(16) ++x(17) ++x(18) ++x(19) ++x(20) ++x(21) ++x(22) ++x(23) ++x(24) ++x(25) ++x(26) ++x(27) ++x(28) ++x(29) ++x(30) ++x(31) ++x(32) ++x(33) ++x(34) ++x(35) ++x(36) ++x(37) ++x(38) ++x(39) ++x(40) ++x(41) ++x(42) ++x(43) ++x(44) ++x(45) ++x(46) ++x(47) ++x(48) ++x(49) ++x(50) ++x(51) ++x(52) ++x(53) ++x(54) ++x(55) ++x(56) ++x(57) ++x(58) ++x(59) ++x(60) ++x(61) ++x(62) ++x(63) ++x(64) diff --git a/lib/reasoners/eflint-haskell/tests/traces/bench_between.perf6 b/lib/reasoners/eflint-haskell/tests/traces/bench_between.perf6 new file mode 100644 index 0000000..33a8b8e --- /dev/null +++ b/lib/reasoners/eflint-haskell/tests/traces/bench_between.perf6 @@ -0,0 +1,130 @@ +New type x ++x(0) ++x(1) ++x(2) ++x(3) ++x(4) ++x(5) ++x(6) ++x(7) ++x(8) ++x(9) ++x(10) ++x(11) ++x(12) ++x(13) ++x(14) ++x(15) ++x(16) ++x(17) ++x(18) ++x(19) ++x(20) ++x(21) ++x(22) ++x(23) ++x(24) ++x(25) ++x(26) ++x(27) ++x(28) ++x(29) ++x(30) ++x(31) ++x(32) ++x(33) ++x(34) ++x(35) ++x(36) ++x(37) ++x(38) ++x(39) ++x(40) ++x(41) ++x(42) ++x(43) ++x(44) ++x(45) ++x(46) ++x(47) ++x(48) ++x(49) ++x(50) ++x(51) ++x(52) ++x(53) ++x(54) ++x(55) ++x(56) ++x(57) ++x(58) ++x(59) ++x(60) ++x(61) ++x(62) ++x(63) ++x(64) ++x(65) ++x(66) ++x(67) ++x(68) ++x(69) ++x(70) ++x(71) ++x(72) ++x(73) ++x(74) ++x(75) ++x(76) ++x(77) ++x(78) ++x(79) ++x(80) ++x(81) ++x(82) ++x(83) ++x(84) ++x(85) ++x(86) ++x(87) ++x(88) ++x(89) ++x(90) ++x(91) ++x(92) ++x(93) ++x(94) ++x(95) ++x(96) ++x(97) ++x(98) ++x(99) ++x(100) ++x(101) ++x(102) ++x(103) ++x(104) ++x(105) ++x(106) ++x(107) ++x(108) ++x(109) ++x(110) ++x(111) ++x(112) ++x(113) ++x(114) ++x(115) ++x(116) ++x(117) ++x(118) ++x(119) ++x(120) ++x(121) ++x(122) ++x(123) ++x(124) ++x(125) ++x(126) ++x(127) ++x(128) diff --git a/lib/reasoners/eflint-haskell/tests/traces/bench_between.perf7 b/lib/reasoners/eflint-haskell/tests/traces/bench_between.perf7 new file mode 100644 index 0000000..7908944 --- /dev/null +++ b/lib/reasoners/eflint-haskell/tests/traces/bench_between.perf7 @@ -0,0 +1,258 @@ +New type x ++x(0) ++x(1) ++x(2) ++x(3) ++x(4) ++x(5) ++x(6) ++x(7) ++x(8) ++x(9) ++x(10) ++x(11) ++x(12) ++x(13) ++x(14) ++x(15) ++x(16) ++x(17) ++x(18) ++x(19) ++x(20) ++x(21) ++x(22) ++x(23) ++x(24) ++x(25) ++x(26) ++x(27) ++x(28) ++x(29) ++x(30) ++x(31) ++x(32) ++x(33) ++x(34) ++x(35) ++x(36) ++x(37) ++x(38) ++x(39) ++x(40) ++x(41) ++x(42) ++x(43) ++x(44) ++x(45) ++x(46) ++x(47) ++x(48) ++x(49) ++x(50) ++x(51) ++x(52) ++x(53) ++x(54) ++x(55) ++x(56) ++x(57) ++x(58) ++x(59) ++x(60) ++x(61) ++x(62) ++x(63) ++x(64) ++x(65) ++x(66) ++x(67) ++x(68) ++x(69) ++x(70) ++x(71) ++x(72) ++x(73) ++x(74) ++x(75) ++x(76) ++x(77) ++x(78) ++x(79) ++x(80) ++x(81) ++x(82) ++x(83) ++x(84) ++x(85) ++x(86) ++x(87) ++x(88) ++x(89) ++x(90) ++x(91) ++x(92) ++x(93) ++x(94) ++x(95) ++x(96) ++x(97) ++x(98) ++x(99) ++x(100) ++x(101) ++x(102) ++x(103) ++x(104) ++x(105) ++x(106) ++x(107) ++x(108) ++x(109) ++x(110) ++x(111) ++x(112) ++x(113) ++x(114) ++x(115) ++x(116) ++x(117) ++x(118) ++x(119) ++x(120) ++x(121) ++x(122) ++x(123) ++x(124) ++x(125) ++x(126) ++x(127) ++x(128) ++x(129) ++x(130) ++x(131) ++x(132) ++x(133) ++x(134) ++x(135) ++x(136) ++x(137) ++x(138) ++x(139) ++x(140) ++x(141) ++x(142) ++x(143) ++x(144) ++x(145) ++x(146) ++x(147) ++x(148) ++x(149) ++x(150) ++x(151) ++x(152) ++x(153) ++x(154) ++x(155) ++x(156) ++x(157) ++x(158) ++x(159) ++x(160) ++x(161) ++x(162) ++x(163) ++x(164) ++x(165) ++x(166) ++x(167) ++x(168) ++x(169) ++x(170) ++x(171) ++x(172) ++x(173) ++x(174) ++x(175) ++x(176) ++x(177) ++x(178) ++x(179) ++x(180) ++x(181) ++x(182) ++x(183) ++x(184) ++x(185) ++x(186) ++x(187) ++x(188) ++x(189) ++x(190) ++x(191) ++x(192) ++x(193) ++x(194) ++x(195) ++x(196) ++x(197) ++x(198) ++x(199) ++x(200) ++x(201) ++x(202) ++x(203) ++x(204) ++x(205) ++x(206) ++x(207) ++x(208) ++x(209) ++x(210) ++x(211) ++x(212) ++x(213) ++x(214) ++x(215) ++x(216) ++x(217) ++x(218) ++x(219) ++x(220) ++x(221) ++x(222) ++x(223) ++x(224) ++x(225) ++x(226) ++x(227) ++x(228) ++x(229) ++x(230) ++x(231) ++x(232) ++x(233) ++x(234) ++x(235) ++x(236) ++x(237) ++x(238) ++x(239) ++x(240) ++x(241) ++x(242) ++x(243) ++x(244) ++x(245) ++x(246) ++x(247) ++x(248) ++x(249) ++x(250) ++x(251) ++x(252) ++x(253) ++x(254) ++x(255) ++x(256) diff --git a/lib/reasoners/eflint-haskell/tests/traces/bench_derive.perf1 b/lib/reasoners/eflint-haskell/tests/traces/bench_derive.perf1 new file mode 100644 index 0000000..678acaf --- /dev/null +++ b/lib/reasoners/eflint-haskell/tests/traces/bench_derive.perf1 @@ -0,0 +1,6 @@ +New type x ++x(int(0)) ++x(int(1)) ++x(int(2)) ++x(int(3)) ++x(int(4)) diff --git a/lib/reasoners/eflint-haskell/tests/traces/bench_derive.perf2 b/lib/reasoners/eflint-haskell/tests/traces/bench_derive.perf2 new file mode 100644 index 0000000..e80f596 --- /dev/null +++ b/lib/reasoners/eflint-haskell/tests/traces/bench_derive.perf2 @@ -0,0 +1,10 @@ +New type x ++x(int(0)) ++x(int(1)) ++x(int(2)) ++x(int(3)) ++x(int(4)) ++x(int(5)) ++x(int(6)) ++x(int(7)) ++x(int(8)) diff --git a/lib/reasoners/eflint-haskell/tests/traces/bench_derive.perf3 b/lib/reasoners/eflint-haskell/tests/traces/bench_derive.perf3 new file mode 100644 index 0000000..3983b2f --- /dev/null +++ b/lib/reasoners/eflint-haskell/tests/traces/bench_derive.perf3 @@ -0,0 +1,18 @@ +New type x ++x(int(0)) ++x(int(1)) ++x(int(2)) ++x(int(3)) ++x(int(4)) ++x(int(5)) ++x(int(6)) ++x(int(7)) ++x(int(8)) ++x(int(9)) ++x(int(10)) ++x(int(11)) ++x(int(12)) ++x(int(13)) ++x(int(14)) ++x(int(15)) ++x(int(16)) diff --git a/lib/reasoners/eflint-haskell/tests/traces/bench_derive.perf4 b/lib/reasoners/eflint-haskell/tests/traces/bench_derive.perf4 new file mode 100644 index 0000000..6132265 --- /dev/null +++ b/lib/reasoners/eflint-haskell/tests/traces/bench_derive.perf4 @@ -0,0 +1,34 @@ +New type x ++x(int(0)) ++x(int(1)) ++x(int(2)) ++x(int(3)) ++x(int(4)) ++x(int(5)) ++x(int(6)) ++x(int(7)) ++x(int(8)) ++x(int(9)) ++x(int(10)) ++x(int(11)) ++x(int(12)) ++x(int(13)) ++x(int(14)) ++x(int(15)) ++x(int(16)) ++x(int(17)) ++x(int(18)) ++x(int(19)) ++x(int(20)) ++x(int(21)) ++x(int(22)) ++x(int(23)) ++x(int(24)) ++x(int(25)) ++x(int(26)) ++x(int(27)) ++x(int(28)) ++x(int(29)) ++x(int(30)) ++x(int(31)) ++x(int(32)) diff --git a/lib/reasoners/eflint-haskell/tests/traces/bench_derive.perf5 b/lib/reasoners/eflint-haskell/tests/traces/bench_derive.perf5 new file mode 100644 index 0000000..5a328fb --- /dev/null +++ b/lib/reasoners/eflint-haskell/tests/traces/bench_derive.perf5 @@ -0,0 +1,66 @@ +New type x ++x(int(0)) ++x(int(1)) ++x(int(2)) ++x(int(3)) ++x(int(4)) ++x(int(5)) ++x(int(6)) ++x(int(7)) ++x(int(8)) ++x(int(9)) ++x(int(10)) ++x(int(11)) ++x(int(12)) ++x(int(13)) ++x(int(14)) ++x(int(15)) ++x(int(16)) ++x(int(17)) ++x(int(18)) ++x(int(19)) ++x(int(20)) ++x(int(21)) ++x(int(22)) ++x(int(23)) ++x(int(24)) ++x(int(25)) ++x(int(26)) ++x(int(27)) ++x(int(28)) ++x(int(29)) ++x(int(30)) ++x(int(31)) ++x(int(32)) ++x(int(33)) ++x(int(34)) ++x(int(35)) ++x(int(36)) ++x(int(37)) ++x(int(38)) ++x(int(39)) ++x(int(40)) ++x(int(41)) ++x(int(42)) ++x(int(43)) ++x(int(44)) ++x(int(45)) ++x(int(46)) ++x(int(47)) ++x(int(48)) ++x(int(49)) ++x(int(50)) ++x(int(51)) ++x(int(52)) ++x(int(53)) ++x(int(54)) ++x(int(55)) ++x(int(56)) ++x(int(57)) ++x(int(58)) ++x(int(59)) ++x(int(60)) ++x(int(61)) ++x(int(62)) ++x(int(63)) ++x(int(64)) diff --git a/lib/reasoners/eflint-haskell/tests/traces/bench_derive.perf6 b/lib/reasoners/eflint-haskell/tests/traces/bench_derive.perf6 new file mode 100644 index 0000000..c7cb5bb --- /dev/null +++ b/lib/reasoners/eflint-haskell/tests/traces/bench_derive.perf6 @@ -0,0 +1,130 @@ +New type x ++x(int(0)) ++x(int(1)) ++x(int(2)) ++x(int(3)) ++x(int(4)) ++x(int(5)) ++x(int(6)) ++x(int(7)) ++x(int(8)) ++x(int(9)) ++x(int(10)) ++x(int(11)) ++x(int(12)) ++x(int(13)) ++x(int(14)) ++x(int(15)) ++x(int(16)) ++x(int(17)) ++x(int(18)) ++x(int(19)) ++x(int(20)) ++x(int(21)) ++x(int(22)) ++x(int(23)) ++x(int(24)) ++x(int(25)) ++x(int(26)) ++x(int(27)) ++x(int(28)) ++x(int(29)) ++x(int(30)) ++x(int(31)) ++x(int(32)) ++x(int(33)) ++x(int(34)) ++x(int(35)) ++x(int(36)) ++x(int(37)) ++x(int(38)) ++x(int(39)) ++x(int(40)) ++x(int(41)) ++x(int(42)) ++x(int(43)) ++x(int(44)) ++x(int(45)) ++x(int(46)) ++x(int(47)) ++x(int(48)) ++x(int(49)) ++x(int(50)) ++x(int(51)) ++x(int(52)) ++x(int(53)) ++x(int(54)) ++x(int(55)) ++x(int(56)) ++x(int(57)) ++x(int(58)) ++x(int(59)) ++x(int(60)) ++x(int(61)) ++x(int(62)) ++x(int(63)) ++x(int(64)) ++x(int(65)) ++x(int(66)) ++x(int(67)) ++x(int(68)) ++x(int(69)) ++x(int(70)) ++x(int(71)) ++x(int(72)) ++x(int(73)) ++x(int(74)) ++x(int(75)) ++x(int(76)) ++x(int(77)) ++x(int(78)) ++x(int(79)) ++x(int(80)) ++x(int(81)) ++x(int(82)) ++x(int(83)) ++x(int(84)) ++x(int(85)) ++x(int(86)) ++x(int(87)) ++x(int(88)) ++x(int(89)) ++x(int(90)) ++x(int(91)) ++x(int(92)) ++x(int(93)) ++x(int(94)) ++x(int(95)) ++x(int(96)) ++x(int(97)) ++x(int(98)) ++x(int(99)) ++x(int(100)) ++x(int(101)) ++x(int(102)) ++x(int(103)) ++x(int(104)) ++x(int(105)) ++x(int(106)) ++x(int(107)) ++x(int(108)) ++x(int(109)) ++x(int(110)) ++x(int(111)) ++x(int(112)) ++x(int(113)) ++x(int(114)) ++x(int(115)) ++x(int(116)) ++x(int(117)) ++x(int(118)) ++x(int(119)) ++x(int(120)) ++x(int(121)) ++x(int(122)) ++x(int(123)) ++x(int(124)) ++x(int(125)) ++x(int(126)) ++x(int(127)) ++x(int(128)) diff --git a/lib/reasoners/eflint-haskell/tests/traces/bench_derive.perf7 b/lib/reasoners/eflint-haskell/tests/traces/bench_derive.perf7 new file mode 100644 index 0000000..f9a012f --- /dev/null +++ b/lib/reasoners/eflint-haskell/tests/traces/bench_derive.perf7 @@ -0,0 +1,258 @@ +New type x ++x(int(0)) ++x(int(1)) ++x(int(2)) ++x(int(3)) ++x(int(4)) ++x(int(5)) ++x(int(6)) ++x(int(7)) ++x(int(8)) ++x(int(9)) ++x(int(10)) ++x(int(11)) ++x(int(12)) ++x(int(13)) ++x(int(14)) ++x(int(15)) ++x(int(16)) ++x(int(17)) ++x(int(18)) ++x(int(19)) ++x(int(20)) ++x(int(21)) ++x(int(22)) ++x(int(23)) ++x(int(24)) ++x(int(25)) ++x(int(26)) ++x(int(27)) ++x(int(28)) ++x(int(29)) ++x(int(30)) ++x(int(31)) ++x(int(32)) ++x(int(33)) ++x(int(34)) ++x(int(35)) ++x(int(36)) ++x(int(37)) ++x(int(38)) ++x(int(39)) ++x(int(40)) ++x(int(41)) ++x(int(42)) ++x(int(43)) ++x(int(44)) ++x(int(45)) ++x(int(46)) ++x(int(47)) ++x(int(48)) ++x(int(49)) ++x(int(50)) ++x(int(51)) ++x(int(52)) ++x(int(53)) ++x(int(54)) ++x(int(55)) ++x(int(56)) ++x(int(57)) ++x(int(58)) ++x(int(59)) ++x(int(60)) ++x(int(61)) ++x(int(62)) ++x(int(63)) ++x(int(64)) ++x(int(65)) ++x(int(66)) ++x(int(67)) ++x(int(68)) ++x(int(69)) ++x(int(70)) ++x(int(71)) ++x(int(72)) ++x(int(73)) ++x(int(74)) ++x(int(75)) ++x(int(76)) ++x(int(77)) ++x(int(78)) ++x(int(79)) ++x(int(80)) ++x(int(81)) ++x(int(82)) ++x(int(83)) ++x(int(84)) ++x(int(85)) ++x(int(86)) ++x(int(87)) ++x(int(88)) ++x(int(89)) ++x(int(90)) ++x(int(91)) ++x(int(92)) ++x(int(93)) ++x(int(94)) ++x(int(95)) ++x(int(96)) ++x(int(97)) ++x(int(98)) ++x(int(99)) ++x(int(100)) ++x(int(101)) ++x(int(102)) ++x(int(103)) ++x(int(104)) ++x(int(105)) ++x(int(106)) ++x(int(107)) ++x(int(108)) ++x(int(109)) ++x(int(110)) ++x(int(111)) ++x(int(112)) ++x(int(113)) ++x(int(114)) ++x(int(115)) ++x(int(116)) ++x(int(117)) ++x(int(118)) ++x(int(119)) ++x(int(120)) ++x(int(121)) ++x(int(122)) ++x(int(123)) ++x(int(124)) ++x(int(125)) ++x(int(126)) ++x(int(127)) ++x(int(128)) ++x(int(129)) ++x(int(130)) ++x(int(131)) ++x(int(132)) ++x(int(133)) ++x(int(134)) ++x(int(135)) ++x(int(136)) ++x(int(137)) ++x(int(138)) ++x(int(139)) ++x(int(140)) ++x(int(141)) ++x(int(142)) ++x(int(143)) ++x(int(144)) ++x(int(145)) ++x(int(146)) ++x(int(147)) ++x(int(148)) ++x(int(149)) ++x(int(150)) ++x(int(151)) ++x(int(152)) ++x(int(153)) ++x(int(154)) ++x(int(155)) ++x(int(156)) ++x(int(157)) ++x(int(158)) ++x(int(159)) ++x(int(160)) ++x(int(161)) ++x(int(162)) ++x(int(163)) ++x(int(164)) ++x(int(165)) ++x(int(166)) ++x(int(167)) ++x(int(168)) ++x(int(169)) ++x(int(170)) ++x(int(171)) ++x(int(172)) ++x(int(173)) ++x(int(174)) ++x(int(175)) ++x(int(176)) ++x(int(177)) ++x(int(178)) ++x(int(179)) ++x(int(180)) ++x(int(181)) ++x(int(182)) ++x(int(183)) ++x(int(184)) ++x(int(185)) ++x(int(186)) ++x(int(187)) ++x(int(188)) ++x(int(189)) ++x(int(190)) ++x(int(191)) ++x(int(192)) ++x(int(193)) ++x(int(194)) ++x(int(195)) ++x(int(196)) ++x(int(197)) ++x(int(198)) ++x(int(199)) ++x(int(200)) ++x(int(201)) ++x(int(202)) ++x(int(203)) ++x(int(204)) ++x(int(205)) ++x(int(206)) ++x(int(207)) ++x(int(208)) ++x(int(209)) ++x(int(210)) ++x(int(211)) ++x(int(212)) ++x(int(213)) ++x(int(214)) ++x(int(215)) ++x(int(216)) ++x(int(217)) ++x(int(218)) ++x(int(219)) ++x(int(220)) ++x(int(221)) ++x(int(222)) ++x(int(223)) ++x(int(224)) ++x(int(225)) ++x(int(226)) ++x(int(227)) ++x(int(228)) ++x(int(229)) ++x(int(230)) ++x(int(231)) ++x(int(232)) ++x(int(233)) ++x(int(234)) ++x(int(235)) ++x(int(236)) ++x(int(237)) ++x(int(238)) ++x(int(239)) ++x(int(240)) ++x(int(241)) ++x(int(242)) ++x(int(243)) ++x(int(244)) ++x(int(245)) ++x(int(246)) ++x(int(247)) ++x(int(248)) ++x(int(249)) ++x(int(250)) ++x(int(251)) ++x(int(252)) ++x(int(253)) ++x(int(254)) ++x(int(255)) ++x(int(256)) diff --git a/lib/reasoners/eflint-haskell/tests/traces/bench_derive_loop.perf0 b/lib/reasoners/eflint-haskell/tests/traces/bench_derive_loop.perf0 new file mode 100644 index 0000000..6648430 --- /dev/null +++ b/lib/reasoners/eflint-haskell/tests/traces/bench_derive_loop.perf0 @@ -0,0 +1,2 @@ +New type x +New type y diff --git a/lib/reasoners/eflint-haskell/tests/traces/bench_derive_loop.perf1 b/lib/reasoners/eflint-haskell/tests/traces/bench_derive_loop.perf1 new file mode 100644 index 0000000..59be160 --- /dev/null +++ b/lib/reasoners/eflint-haskell/tests/traces/bench_derive_loop.perf1 @@ -0,0 +1,14 @@ +New type x +New type y ++x(0) ++x(1) ++y(x(0),x(1),x(0)) ++y(x(0),x(1),x(1)) ++x(2) ++y(x(0),x(1),x(2)) ++y(x(0),x(2),x(0)) ++y(x(0),x(2),x(1)) ++y(x(0),x(2),x(2)) ++y(x(1),x(2),x(0)) ++y(x(1),x(2),x(1)) ++y(x(1),x(2),x(2)) diff --git a/lib/reasoners/eflint-haskell/tests/traces/bench_triple.perf0 b/lib/reasoners/eflint-haskell/tests/traces/bench_triple.perf0 new file mode 100644 index 0000000..6648430 --- /dev/null +++ b/lib/reasoners/eflint-haskell/tests/traces/bench_triple.perf0 @@ -0,0 +1,2 @@ +New type x +New type y diff --git a/lib/reasoners/eflint-haskell/tests/traces/bench_triple.perf1 b/lib/reasoners/eflint-haskell/tests/traces/bench_triple.perf1 new file mode 100644 index 0000000..f25b72a --- /dev/null +++ b/lib/reasoners/eflint-haskell/tests/traces/bench_triple.perf1 @@ -0,0 +1,8 @@ +New type x +New type y ++x(0) ++x(1) ++y(x(0),x(0),x(0)) ++y(x(0),x(0),x(1)) ++y(x(1),x(0),x(1)) ++y(x(1),x(1),x(1)) diff --git a/lib/reasoners/eflint-haskell/tests/traces/bench_triple.perf2 b/lib/reasoners/eflint-haskell/tests/traces/bench_triple.perf2 new file mode 100644 index 0000000..54f5d91 --- /dev/null +++ b/lib/reasoners/eflint-haskell/tests/traces/bench_triple.perf2 @@ -0,0 +1,20 @@ +New type x +New type y ++x(0) ++x(1) ++x(2) ++y(x(0),x(0),x(0)) ++y(x(0),x(0),x(1)) ++y(x(0),x(0),x(2)) ++y(x(0),x(1),x(0)) ++y(x(0),x(1),x(2)) ++y(x(1),x(0),x(1)) ++y(x(1),x(0),x(2)) ++y(x(1),x(1),x(0)) ++y(x(1),x(1),x(1)) ++y(x(1),x(1),x(2)) ++y(x(2),x(0),x(1)) ++y(x(2),x(0),x(2)) ++y(x(2),x(1),x(0)) ++y(x(2),x(1),x(2)) ++y(x(2),x(2),x(2)) diff --git a/lib/reasoners/eflint-haskell/tests/traces/bench_triple.perf3 b/lib/reasoners/eflint-haskell/tests/traces/bench_triple.perf3 new file mode 100644 index 0000000..391de1e --- /dev/null +++ b/lib/reasoners/eflint-haskell/tests/traces/bench_triple.perf3 @@ -0,0 +1,46 @@ +New type x +New type y ++x(0) ++x(1) ++x(2) ++x(3) ++y(x(0),x(0),x(0)) ++y(x(0),x(0),x(1)) ++y(x(0),x(0),x(2)) ++y(x(0),x(0),x(3)) ++y(x(0),x(1),x(0)) ++y(x(0),x(1),x(2)) ++y(x(0),x(1),x(3)) ++y(x(0),x(2),x(0)) ++y(x(0),x(2),x(1)) ++y(x(0),x(2),x(3)) ++y(x(1),x(0),x(1)) ++y(x(1),x(0),x(2)) ++y(x(1),x(0),x(3)) ++y(x(1),x(1),x(0)) ++y(x(1),x(1),x(1)) ++y(x(1),x(1),x(2)) ++y(x(1),x(1),x(3)) ++y(x(1),x(2),x(0)) ++y(x(1),x(2),x(1)) ++y(x(1),x(2),x(3)) ++y(x(2),x(0),x(1)) ++y(x(2),x(0),x(2)) ++y(x(2),x(0),x(3)) ++y(x(2),x(1),x(0)) ++y(x(2),x(1),x(2)) ++y(x(2),x(1),x(3)) ++y(x(2),x(2),x(0)) ++y(x(2),x(2),x(1)) ++y(x(2),x(2),x(2)) ++y(x(2),x(2),x(3)) ++y(x(3),x(0),x(1)) ++y(x(3),x(0),x(2)) ++y(x(3),x(0),x(3)) ++y(x(3),x(1),x(0)) ++y(x(3),x(1),x(2)) ++y(x(3),x(1),x(3)) ++y(x(3),x(2),x(0)) ++y(x(3),x(2),x(1)) ++y(x(3),x(2),x(3)) ++y(x(3),x(3),x(3)) diff --git a/lib/reasoners/eflint-haskell/tests/traces/bench_triple.perf4 b/lib/reasoners/eflint-haskell/tests/traces/bench_triple.perf4 new file mode 100644 index 0000000..8d436ad --- /dev/null +++ b/lib/reasoners/eflint-haskell/tests/traces/bench_triple.perf4 @@ -0,0 +1,92 @@ +New type x +New type y ++x(0) ++x(1) ++x(2) ++x(3) ++x(4) ++y(x(0),x(0),x(0)) ++y(x(0),x(0),x(1)) ++y(x(0),x(0),x(2)) ++y(x(0),x(0),x(3)) ++y(x(0),x(0),x(4)) ++y(x(0),x(1),x(0)) ++y(x(0),x(1),x(2)) ++y(x(0),x(1),x(3)) ++y(x(0),x(1),x(4)) ++y(x(0),x(2),x(0)) ++y(x(0),x(2),x(1)) ++y(x(0),x(2),x(3)) ++y(x(0),x(2),x(4)) ++y(x(0),x(3),x(0)) ++y(x(0),x(3),x(1)) ++y(x(0),x(3),x(2)) ++y(x(0),x(3),x(4)) ++y(x(1),x(0),x(1)) ++y(x(1),x(0),x(2)) ++y(x(1),x(0),x(3)) ++y(x(1),x(0),x(4)) ++y(x(1),x(1),x(0)) ++y(x(1),x(1),x(1)) ++y(x(1),x(1),x(2)) ++y(x(1),x(1),x(3)) ++y(x(1),x(1),x(4)) ++y(x(1),x(2),x(0)) ++y(x(1),x(2),x(1)) ++y(x(1),x(2),x(3)) ++y(x(1),x(2),x(4)) ++y(x(1),x(3),x(0)) ++y(x(1),x(3),x(1)) ++y(x(1),x(3),x(2)) ++y(x(1),x(3),x(4)) ++y(x(2),x(0),x(1)) ++y(x(2),x(0),x(2)) ++y(x(2),x(0),x(3)) ++y(x(2),x(0),x(4)) ++y(x(2),x(1),x(0)) ++y(x(2),x(1),x(2)) ++y(x(2),x(1),x(3)) ++y(x(2),x(1),x(4)) ++y(x(2),x(2),x(0)) ++y(x(2),x(2),x(1)) ++y(x(2),x(2),x(2)) ++y(x(2),x(2),x(3)) ++y(x(2),x(2),x(4)) ++y(x(2),x(3),x(0)) ++y(x(2),x(3),x(1)) ++y(x(2),x(3),x(2)) ++y(x(2),x(3),x(4)) ++y(x(3),x(0),x(1)) ++y(x(3),x(0),x(2)) ++y(x(3),x(0),x(3)) ++y(x(3),x(0),x(4)) ++y(x(3),x(1),x(0)) ++y(x(3),x(1),x(2)) ++y(x(3),x(1),x(3)) ++y(x(3),x(1),x(4)) ++y(x(3),x(2),x(0)) ++y(x(3),x(2),x(1)) ++y(x(3),x(2),x(3)) ++y(x(3),x(2),x(4)) ++y(x(3),x(3),x(0)) ++y(x(3),x(3),x(1)) ++y(x(3),x(3),x(2)) ++y(x(3),x(3),x(3)) ++y(x(3),x(3),x(4)) ++y(x(4),x(0),x(1)) ++y(x(4),x(0),x(2)) ++y(x(4),x(0),x(3)) ++y(x(4),x(0),x(4)) ++y(x(4),x(1),x(0)) ++y(x(4),x(1),x(2)) ++y(x(4),x(1),x(3)) ++y(x(4),x(1),x(4)) ++y(x(4),x(2),x(0)) ++y(x(4),x(2),x(1)) ++y(x(4),x(2),x(3)) ++y(x(4),x(2),x(4)) ++y(x(4),x(3),x(0)) ++y(x(4),x(3),x(1)) ++y(x(4),x(3),x(2)) ++y(x(4),x(3),x(4)) ++y(x(4),x(4),x(4)) diff --git a/lib/reasoners/eflint-haskell/tests/traces/bench_triple.perf5 b/lib/reasoners/eflint-haskell/tests/traces/bench_triple.perf5 new file mode 100644 index 0000000..f373a7e --- /dev/null +++ b/lib/reasoners/eflint-haskell/tests/traces/bench_triple.perf5 @@ -0,0 +1,164 @@ +New type x +New type y ++x(0) ++x(1) ++x(2) ++x(3) ++x(4) ++x(5) ++y(x(0),x(0),x(0)) ++y(x(0),x(0),x(1)) ++y(x(0),x(0),x(2)) ++y(x(0),x(0),x(3)) ++y(x(0),x(0),x(4)) ++y(x(0),x(0),x(5)) ++y(x(0),x(1),x(0)) ++y(x(0),x(1),x(2)) ++y(x(0),x(1),x(3)) ++y(x(0),x(1),x(4)) ++y(x(0),x(1),x(5)) ++y(x(0),x(2),x(0)) ++y(x(0),x(2),x(1)) ++y(x(0),x(2),x(3)) ++y(x(0),x(2),x(4)) ++y(x(0),x(2),x(5)) ++y(x(0),x(3),x(0)) ++y(x(0),x(3),x(1)) ++y(x(0),x(3),x(2)) ++y(x(0),x(3),x(4)) ++y(x(0),x(3),x(5)) ++y(x(0),x(4),x(0)) ++y(x(0),x(4),x(1)) ++y(x(0),x(4),x(2)) ++y(x(0),x(4),x(3)) ++y(x(0),x(4),x(5)) ++y(x(1),x(0),x(1)) ++y(x(1),x(0),x(2)) ++y(x(1),x(0),x(3)) ++y(x(1),x(0),x(4)) ++y(x(1),x(0),x(5)) ++y(x(1),x(1),x(0)) ++y(x(1),x(1),x(1)) ++y(x(1),x(1),x(2)) ++y(x(1),x(1),x(3)) ++y(x(1),x(1),x(4)) ++y(x(1),x(1),x(5)) ++y(x(1),x(2),x(0)) ++y(x(1),x(2),x(1)) ++y(x(1),x(2),x(3)) ++y(x(1),x(2),x(4)) ++y(x(1),x(2),x(5)) ++y(x(1),x(3),x(0)) ++y(x(1),x(3),x(1)) ++y(x(1),x(3),x(2)) ++y(x(1),x(3),x(4)) ++y(x(1),x(3),x(5)) ++y(x(1),x(4),x(0)) ++y(x(1),x(4),x(1)) ++y(x(1),x(4),x(2)) ++y(x(1),x(4),x(3)) ++y(x(1),x(4),x(5)) ++y(x(2),x(0),x(1)) ++y(x(2),x(0),x(2)) ++y(x(2),x(0),x(3)) ++y(x(2),x(0),x(4)) ++y(x(2),x(0),x(5)) ++y(x(2),x(1),x(0)) ++y(x(2),x(1),x(2)) ++y(x(2),x(1),x(3)) ++y(x(2),x(1),x(4)) ++y(x(2),x(1),x(5)) ++y(x(2),x(2),x(0)) ++y(x(2),x(2),x(1)) ++y(x(2),x(2),x(2)) ++y(x(2),x(2),x(3)) ++y(x(2),x(2),x(4)) ++y(x(2),x(2),x(5)) ++y(x(2),x(3),x(0)) ++y(x(2),x(3),x(1)) ++y(x(2),x(3),x(2)) ++y(x(2),x(3),x(4)) ++y(x(2),x(3),x(5)) ++y(x(2),x(4),x(0)) ++y(x(2),x(4),x(1)) ++y(x(2),x(4),x(2)) ++y(x(2),x(4),x(3)) ++y(x(2),x(4),x(5)) ++y(x(3),x(0),x(1)) ++y(x(3),x(0),x(2)) ++y(x(3),x(0),x(3)) ++y(x(3),x(0),x(4)) ++y(x(3),x(0),x(5)) ++y(x(3),x(1),x(0)) ++y(x(3),x(1),x(2)) ++y(x(3),x(1),x(3)) ++y(x(3),x(1),x(4)) ++y(x(3),x(1),x(5)) ++y(x(3),x(2),x(0)) ++y(x(3),x(2),x(1)) ++y(x(3),x(2),x(3)) ++y(x(3),x(2),x(4)) ++y(x(3),x(2),x(5)) ++y(x(3),x(3),x(0)) ++y(x(3),x(3),x(1)) ++y(x(3),x(3),x(2)) ++y(x(3),x(3),x(3)) ++y(x(3),x(3),x(4)) ++y(x(3),x(3),x(5)) ++y(x(3),x(4),x(0)) ++y(x(3),x(4),x(1)) ++y(x(3),x(4),x(2)) ++y(x(3),x(4),x(3)) ++y(x(3),x(4),x(5)) ++y(x(4),x(0),x(1)) ++y(x(4),x(0),x(2)) ++y(x(4),x(0),x(3)) ++y(x(4),x(0),x(4)) ++y(x(4),x(0),x(5)) ++y(x(4),x(1),x(0)) ++y(x(4),x(1),x(2)) ++y(x(4),x(1),x(3)) ++y(x(4),x(1),x(4)) ++y(x(4),x(1),x(5)) ++y(x(4),x(2),x(0)) ++y(x(4),x(2),x(1)) ++y(x(4),x(2),x(3)) ++y(x(4),x(2),x(4)) ++y(x(4),x(2),x(5)) ++y(x(4),x(3),x(0)) ++y(x(4),x(3),x(1)) ++y(x(4),x(3),x(2)) ++y(x(4),x(3),x(4)) ++y(x(4),x(3),x(5)) ++y(x(4),x(4),x(0)) ++y(x(4),x(4),x(1)) ++y(x(4),x(4),x(2)) ++y(x(4),x(4),x(3)) ++y(x(4),x(4),x(4)) ++y(x(4),x(4),x(5)) ++y(x(5),x(0),x(1)) ++y(x(5),x(0),x(2)) ++y(x(5),x(0),x(3)) ++y(x(5),x(0),x(4)) ++y(x(5),x(0),x(5)) ++y(x(5),x(1),x(0)) ++y(x(5),x(1),x(2)) ++y(x(5),x(1),x(3)) ++y(x(5),x(1),x(4)) ++y(x(5),x(1),x(5)) ++y(x(5),x(2),x(0)) ++y(x(5),x(2),x(1)) ++y(x(5),x(2),x(3)) ++y(x(5),x(2),x(4)) ++y(x(5),x(2),x(5)) ++y(x(5),x(3),x(0)) ++y(x(5),x(3),x(1)) ++y(x(5),x(3),x(2)) ++y(x(5),x(3),x(4)) ++y(x(5),x(3),x(5)) ++y(x(5),x(4),x(0)) ++y(x(5),x(4),x(1)) ++y(x(5),x(4),x(2)) ++y(x(5),x(4),x(3)) ++y(x(5),x(4),x(5)) ++y(x(5),x(5),x(5)) diff --git a/lib/reasoners/eflint-haskell/tests/traces/bench_triple.perf6 b/lib/reasoners/eflint-haskell/tests/traces/bench_triple.perf6 new file mode 100644 index 0000000..6c6e413 --- /dev/null +++ b/lib/reasoners/eflint-haskell/tests/traces/bench_triple.perf6 @@ -0,0 +1,1124 @@ +New type x +New type y ++x(0) ++x(1) ++x(2) ++x(3) ++x(4) ++x(5) ++x(6) ++x(7) ++x(8) ++x(9) ++x(10) ++y(x(0),x(0),x(0)) ++y(x(0),x(0),x(1)) ++y(x(0),x(0),x(2)) ++y(x(0),x(0),x(3)) ++y(x(0),x(0),x(4)) ++y(x(0),x(0),x(5)) ++y(x(0),x(0),x(6)) ++y(x(0),x(0),x(7)) ++y(x(0),x(0),x(8)) ++y(x(0),x(0),x(9)) ++y(x(0),x(0),x(10)) ++y(x(0),x(1),x(0)) ++y(x(0),x(1),x(2)) ++y(x(0),x(1),x(3)) ++y(x(0),x(1),x(4)) ++y(x(0),x(1),x(5)) ++y(x(0),x(1),x(6)) ++y(x(0),x(1),x(7)) ++y(x(0),x(1),x(8)) ++y(x(0),x(1),x(9)) ++y(x(0),x(1),x(10)) ++y(x(0),x(2),x(0)) ++y(x(0),x(2),x(1)) ++y(x(0),x(2),x(3)) ++y(x(0),x(2),x(4)) ++y(x(0),x(2),x(5)) ++y(x(0),x(2),x(6)) ++y(x(0),x(2),x(7)) ++y(x(0),x(2),x(8)) ++y(x(0),x(2),x(9)) ++y(x(0),x(2),x(10)) ++y(x(0),x(3),x(0)) ++y(x(0),x(3),x(1)) ++y(x(0),x(3),x(2)) ++y(x(0),x(3),x(4)) ++y(x(0),x(3),x(5)) ++y(x(0),x(3),x(6)) ++y(x(0),x(3),x(7)) ++y(x(0),x(3),x(8)) ++y(x(0),x(3),x(9)) ++y(x(0),x(3),x(10)) ++y(x(0),x(4),x(0)) ++y(x(0),x(4),x(1)) ++y(x(0),x(4),x(2)) ++y(x(0),x(4),x(3)) ++y(x(0),x(4),x(5)) ++y(x(0),x(4),x(6)) ++y(x(0),x(4),x(7)) ++y(x(0),x(4),x(8)) ++y(x(0),x(4),x(9)) ++y(x(0),x(4),x(10)) ++y(x(0),x(5),x(0)) ++y(x(0),x(5),x(1)) ++y(x(0),x(5),x(2)) ++y(x(0),x(5),x(3)) ++y(x(0),x(5),x(4)) ++y(x(0),x(5),x(6)) ++y(x(0),x(5),x(7)) ++y(x(0),x(5),x(8)) ++y(x(0),x(5),x(9)) ++y(x(0),x(5),x(10)) ++y(x(0),x(6),x(0)) ++y(x(0),x(6),x(1)) ++y(x(0),x(6),x(2)) ++y(x(0),x(6),x(3)) ++y(x(0),x(6),x(4)) ++y(x(0),x(6),x(5)) ++y(x(0),x(6),x(7)) ++y(x(0),x(6),x(8)) ++y(x(0),x(6),x(9)) ++y(x(0),x(6),x(10)) ++y(x(0),x(7),x(0)) ++y(x(0),x(7),x(1)) ++y(x(0),x(7),x(2)) ++y(x(0),x(7),x(3)) ++y(x(0),x(7),x(4)) ++y(x(0),x(7),x(5)) ++y(x(0),x(7),x(6)) ++y(x(0),x(7),x(8)) ++y(x(0),x(7),x(9)) ++y(x(0),x(7),x(10)) ++y(x(0),x(8),x(0)) ++y(x(0),x(8),x(1)) ++y(x(0),x(8),x(2)) ++y(x(0),x(8),x(3)) ++y(x(0),x(8),x(4)) ++y(x(0),x(8),x(5)) ++y(x(0),x(8),x(6)) ++y(x(0),x(8),x(7)) ++y(x(0),x(8),x(9)) ++y(x(0),x(8),x(10)) ++y(x(0),x(9),x(0)) ++y(x(0),x(9),x(1)) ++y(x(0),x(9),x(2)) ++y(x(0),x(9),x(3)) ++y(x(0),x(9),x(4)) ++y(x(0),x(9),x(5)) ++y(x(0),x(9),x(6)) ++y(x(0),x(9),x(7)) ++y(x(0),x(9),x(8)) ++y(x(0),x(9),x(10)) ++y(x(1),x(0),x(1)) ++y(x(1),x(0),x(2)) ++y(x(1),x(0),x(3)) ++y(x(1),x(0),x(4)) ++y(x(1),x(0),x(5)) ++y(x(1),x(0),x(6)) ++y(x(1),x(0),x(7)) ++y(x(1),x(0),x(8)) ++y(x(1),x(0),x(9)) ++y(x(1),x(0),x(10)) ++y(x(1),x(1),x(0)) ++y(x(1),x(1),x(1)) ++y(x(1),x(1),x(2)) ++y(x(1),x(1),x(3)) ++y(x(1),x(1),x(4)) ++y(x(1),x(1),x(5)) ++y(x(1),x(1),x(6)) ++y(x(1),x(1),x(7)) ++y(x(1),x(1),x(8)) ++y(x(1),x(1),x(9)) ++y(x(1),x(1),x(10)) ++y(x(1),x(2),x(0)) ++y(x(1),x(2),x(1)) ++y(x(1),x(2),x(3)) ++y(x(1),x(2),x(4)) ++y(x(1),x(2),x(5)) ++y(x(1),x(2),x(6)) ++y(x(1),x(2),x(7)) ++y(x(1),x(2),x(8)) ++y(x(1),x(2),x(9)) ++y(x(1),x(2),x(10)) ++y(x(1),x(3),x(0)) ++y(x(1),x(3),x(1)) ++y(x(1),x(3),x(2)) ++y(x(1),x(3),x(4)) ++y(x(1),x(3),x(5)) ++y(x(1),x(3),x(6)) ++y(x(1),x(3),x(7)) ++y(x(1),x(3),x(8)) ++y(x(1),x(3),x(9)) ++y(x(1),x(3),x(10)) ++y(x(1),x(4),x(0)) ++y(x(1),x(4),x(1)) ++y(x(1),x(4),x(2)) ++y(x(1),x(4),x(3)) ++y(x(1),x(4),x(5)) ++y(x(1),x(4),x(6)) ++y(x(1),x(4),x(7)) ++y(x(1),x(4),x(8)) ++y(x(1),x(4),x(9)) ++y(x(1),x(4),x(10)) ++y(x(1),x(5),x(0)) ++y(x(1),x(5),x(1)) ++y(x(1),x(5),x(2)) ++y(x(1),x(5),x(3)) ++y(x(1),x(5),x(4)) ++y(x(1),x(5),x(6)) ++y(x(1),x(5),x(7)) ++y(x(1),x(5),x(8)) ++y(x(1),x(5),x(9)) ++y(x(1),x(5),x(10)) ++y(x(1),x(6),x(0)) ++y(x(1),x(6),x(1)) ++y(x(1),x(6),x(2)) ++y(x(1),x(6),x(3)) ++y(x(1),x(6),x(4)) ++y(x(1),x(6),x(5)) ++y(x(1),x(6),x(7)) ++y(x(1),x(6),x(8)) ++y(x(1),x(6),x(9)) ++y(x(1),x(6),x(10)) ++y(x(1),x(7),x(0)) ++y(x(1),x(7),x(1)) ++y(x(1),x(7),x(2)) ++y(x(1),x(7),x(3)) ++y(x(1),x(7),x(4)) ++y(x(1),x(7),x(5)) ++y(x(1),x(7),x(6)) ++y(x(1),x(7),x(8)) ++y(x(1),x(7),x(9)) ++y(x(1),x(7),x(10)) ++y(x(1),x(8),x(0)) ++y(x(1),x(8),x(1)) ++y(x(1),x(8),x(2)) ++y(x(1),x(8),x(3)) ++y(x(1),x(8),x(4)) ++y(x(1),x(8),x(5)) ++y(x(1),x(8),x(6)) ++y(x(1),x(8),x(7)) ++y(x(1),x(8),x(9)) ++y(x(1),x(8),x(10)) ++y(x(1),x(9),x(0)) ++y(x(1),x(9),x(1)) ++y(x(1),x(9),x(2)) ++y(x(1),x(9),x(3)) ++y(x(1),x(9),x(4)) ++y(x(1),x(9),x(5)) ++y(x(1),x(9),x(6)) ++y(x(1),x(9),x(7)) ++y(x(1),x(9),x(8)) ++y(x(1),x(9),x(10)) ++y(x(2),x(0),x(1)) ++y(x(2),x(0),x(2)) ++y(x(2),x(0),x(3)) ++y(x(2),x(0),x(4)) ++y(x(2),x(0),x(5)) ++y(x(2),x(0),x(6)) ++y(x(2),x(0),x(7)) ++y(x(2),x(0),x(8)) ++y(x(2),x(0),x(9)) ++y(x(2),x(0),x(10)) ++y(x(2),x(1),x(0)) ++y(x(2),x(1),x(2)) ++y(x(2),x(1),x(3)) ++y(x(2),x(1),x(4)) ++y(x(2),x(1),x(5)) ++y(x(2),x(1),x(6)) ++y(x(2),x(1),x(7)) ++y(x(2),x(1),x(8)) ++y(x(2),x(1),x(9)) ++y(x(2),x(1),x(10)) ++y(x(2),x(2),x(0)) ++y(x(2),x(2),x(1)) ++y(x(2),x(2),x(2)) ++y(x(2),x(2),x(3)) ++y(x(2),x(2),x(4)) ++y(x(2),x(2),x(5)) ++y(x(2),x(2),x(6)) ++y(x(2),x(2),x(7)) ++y(x(2),x(2),x(8)) ++y(x(2),x(2),x(9)) ++y(x(2),x(2),x(10)) ++y(x(2),x(3),x(0)) ++y(x(2),x(3),x(1)) ++y(x(2),x(3),x(2)) ++y(x(2),x(3),x(4)) ++y(x(2),x(3),x(5)) ++y(x(2),x(3),x(6)) ++y(x(2),x(3),x(7)) ++y(x(2),x(3),x(8)) ++y(x(2),x(3),x(9)) ++y(x(2),x(3),x(10)) ++y(x(2),x(4),x(0)) ++y(x(2),x(4),x(1)) ++y(x(2),x(4),x(2)) ++y(x(2),x(4),x(3)) ++y(x(2),x(4),x(5)) ++y(x(2),x(4),x(6)) ++y(x(2),x(4),x(7)) ++y(x(2),x(4),x(8)) ++y(x(2),x(4),x(9)) ++y(x(2),x(4),x(10)) ++y(x(2),x(5),x(0)) ++y(x(2),x(5),x(1)) ++y(x(2),x(5),x(2)) ++y(x(2),x(5),x(3)) ++y(x(2),x(5),x(4)) ++y(x(2),x(5),x(6)) ++y(x(2),x(5),x(7)) ++y(x(2),x(5),x(8)) ++y(x(2),x(5),x(9)) ++y(x(2),x(5),x(10)) ++y(x(2),x(6),x(0)) ++y(x(2),x(6),x(1)) ++y(x(2),x(6),x(2)) ++y(x(2),x(6),x(3)) ++y(x(2),x(6),x(4)) ++y(x(2),x(6),x(5)) ++y(x(2),x(6),x(7)) ++y(x(2),x(6),x(8)) ++y(x(2),x(6),x(9)) ++y(x(2),x(6),x(10)) ++y(x(2),x(7),x(0)) ++y(x(2),x(7),x(1)) ++y(x(2),x(7),x(2)) ++y(x(2),x(7),x(3)) ++y(x(2),x(7),x(4)) ++y(x(2),x(7),x(5)) ++y(x(2),x(7),x(6)) ++y(x(2),x(7),x(8)) ++y(x(2),x(7),x(9)) ++y(x(2),x(7),x(10)) ++y(x(2),x(8),x(0)) ++y(x(2),x(8),x(1)) ++y(x(2),x(8),x(2)) ++y(x(2),x(8),x(3)) ++y(x(2),x(8),x(4)) ++y(x(2),x(8),x(5)) ++y(x(2),x(8),x(6)) ++y(x(2),x(8),x(7)) ++y(x(2),x(8),x(9)) ++y(x(2),x(8),x(10)) ++y(x(2),x(9),x(0)) ++y(x(2),x(9),x(1)) ++y(x(2),x(9),x(2)) ++y(x(2),x(9),x(3)) ++y(x(2),x(9),x(4)) ++y(x(2),x(9),x(5)) ++y(x(2),x(9),x(6)) ++y(x(2),x(9),x(7)) ++y(x(2),x(9),x(8)) ++y(x(2),x(9),x(10)) ++y(x(3),x(0),x(1)) ++y(x(3),x(0),x(2)) ++y(x(3),x(0),x(3)) ++y(x(3),x(0),x(4)) ++y(x(3),x(0),x(5)) ++y(x(3),x(0),x(6)) ++y(x(3),x(0),x(7)) ++y(x(3),x(0),x(8)) ++y(x(3),x(0),x(9)) ++y(x(3),x(0),x(10)) ++y(x(3),x(1),x(0)) ++y(x(3),x(1),x(2)) ++y(x(3),x(1),x(3)) ++y(x(3),x(1),x(4)) ++y(x(3),x(1),x(5)) ++y(x(3),x(1),x(6)) ++y(x(3),x(1),x(7)) ++y(x(3),x(1),x(8)) ++y(x(3),x(1),x(9)) ++y(x(3),x(1),x(10)) ++y(x(3),x(2),x(0)) ++y(x(3),x(2),x(1)) ++y(x(3),x(2),x(3)) ++y(x(3),x(2),x(4)) ++y(x(3),x(2),x(5)) ++y(x(3),x(2),x(6)) ++y(x(3),x(2),x(7)) ++y(x(3),x(2),x(8)) ++y(x(3),x(2),x(9)) ++y(x(3),x(2),x(10)) ++y(x(3),x(3),x(0)) ++y(x(3),x(3),x(1)) ++y(x(3),x(3),x(2)) ++y(x(3),x(3),x(3)) ++y(x(3),x(3),x(4)) ++y(x(3),x(3),x(5)) ++y(x(3),x(3),x(6)) ++y(x(3),x(3),x(7)) ++y(x(3),x(3),x(8)) ++y(x(3),x(3),x(9)) ++y(x(3),x(3),x(10)) ++y(x(3),x(4),x(0)) ++y(x(3),x(4),x(1)) ++y(x(3),x(4),x(2)) ++y(x(3),x(4),x(3)) ++y(x(3),x(4),x(5)) ++y(x(3),x(4),x(6)) ++y(x(3),x(4),x(7)) ++y(x(3),x(4),x(8)) ++y(x(3),x(4),x(9)) ++y(x(3),x(4),x(10)) ++y(x(3),x(5),x(0)) ++y(x(3),x(5),x(1)) ++y(x(3),x(5),x(2)) ++y(x(3),x(5),x(3)) ++y(x(3),x(5),x(4)) ++y(x(3),x(5),x(6)) ++y(x(3),x(5),x(7)) ++y(x(3),x(5),x(8)) ++y(x(3),x(5),x(9)) ++y(x(3),x(5),x(10)) ++y(x(3),x(6),x(0)) ++y(x(3),x(6),x(1)) ++y(x(3),x(6),x(2)) ++y(x(3),x(6),x(3)) ++y(x(3),x(6),x(4)) ++y(x(3),x(6),x(5)) ++y(x(3),x(6),x(7)) ++y(x(3),x(6),x(8)) ++y(x(3),x(6),x(9)) ++y(x(3),x(6),x(10)) ++y(x(3),x(7),x(0)) ++y(x(3),x(7),x(1)) ++y(x(3),x(7),x(2)) ++y(x(3),x(7),x(3)) ++y(x(3),x(7),x(4)) ++y(x(3),x(7),x(5)) ++y(x(3),x(7),x(6)) ++y(x(3),x(7),x(8)) ++y(x(3),x(7),x(9)) ++y(x(3),x(7),x(10)) ++y(x(3),x(8),x(0)) ++y(x(3),x(8),x(1)) ++y(x(3),x(8),x(2)) ++y(x(3),x(8),x(3)) ++y(x(3),x(8),x(4)) ++y(x(3),x(8),x(5)) ++y(x(3),x(8),x(6)) ++y(x(3),x(8),x(7)) ++y(x(3),x(8),x(9)) ++y(x(3),x(8),x(10)) ++y(x(3),x(9),x(0)) ++y(x(3),x(9),x(1)) ++y(x(3),x(9),x(2)) ++y(x(3),x(9),x(3)) ++y(x(3),x(9),x(4)) ++y(x(3),x(9),x(5)) ++y(x(3),x(9),x(6)) ++y(x(3),x(9),x(7)) ++y(x(3),x(9),x(8)) ++y(x(3),x(9),x(10)) ++y(x(4),x(0),x(1)) ++y(x(4),x(0),x(2)) ++y(x(4),x(0),x(3)) ++y(x(4),x(0),x(4)) ++y(x(4),x(0),x(5)) ++y(x(4),x(0),x(6)) ++y(x(4),x(0),x(7)) ++y(x(4),x(0),x(8)) ++y(x(4),x(0),x(9)) ++y(x(4),x(0),x(10)) ++y(x(4),x(1),x(0)) ++y(x(4),x(1),x(2)) ++y(x(4),x(1),x(3)) ++y(x(4),x(1),x(4)) ++y(x(4),x(1),x(5)) ++y(x(4),x(1),x(6)) ++y(x(4),x(1),x(7)) ++y(x(4),x(1),x(8)) ++y(x(4),x(1),x(9)) ++y(x(4),x(1),x(10)) ++y(x(4),x(2),x(0)) ++y(x(4),x(2),x(1)) ++y(x(4),x(2),x(3)) ++y(x(4),x(2),x(4)) ++y(x(4),x(2),x(5)) ++y(x(4),x(2),x(6)) ++y(x(4),x(2),x(7)) ++y(x(4),x(2),x(8)) ++y(x(4),x(2),x(9)) ++y(x(4),x(2),x(10)) ++y(x(4),x(3),x(0)) ++y(x(4),x(3),x(1)) ++y(x(4),x(3),x(2)) ++y(x(4),x(3),x(4)) ++y(x(4),x(3),x(5)) ++y(x(4),x(3),x(6)) ++y(x(4),x(3),x(7)) ++y(x(4),x(3),x(8)) ++y(x(4),x(3),x(9)) ++y(x(4),x(3),x(10)) ++y(x(4),x(4),x(0)) ++y(x(4),x(4),x(1)) ++y(x(4),x(4),x(2)) ++y(x(4),x(4),x(3)) ++y(x(4),x(4),x(4)) ++y(x(4),x(4),x(5)) ++y(x(4),x(4),x(6)) ++y(x(4),x(4),x(7)) ++y(x(4),x(4),x(8)) ++y(x(4),x(4),x(9)) ++y(x(4),x(4),x(10)) ++y(x(4),x(5),x(0)) ++y(x(4),x(5),x(1)) ++y(x(4),x(5),x(2)) ++y(x(4),x(5),x(3)) ++y(x(4),x(5),x(4)) ++y(x(4),x(5),x(6)) ++y(x(4),x(5),x(7)) ++y(x(4),x(5),x(8)) ++y(x(4),x(5),x(9)) ++y(x(4),x(5),x(10)) ++y(x(4),x(6),x(0)) ++y(x(4),x(6),x(1)) ++y(x(4),x(6),x(2)) ++y(x(4),x(6),x(3)) ++y(x(4),x(6),x(4)) ++y(x(4),x(6),x(5)) ++y(x(4),x(6),x(7)) ++y(x(4),x(6),x(8)) ++y(x(4),x(6),x(9)) ++y(x(4),x(6),x(10)) ++y(x(4),x(7),x(0)) ++y(x(4),x(7),x(1)) ++y(x(4),x(7),x(2)) ++y(x(4),x(7),x(3)) ++y(x(4),x(7),x(4)) ++y(x(4),x(7),x(5)) ++y(x(4),x(7),x(6)) ++y(x(4),x(7),x(8)) ++y(x(4),x(7),x(9)) ++y(x(4),x(7),x(10)) ++y(x(4),x(8),x(0)) ++y(x(4),x(8),x(1)) ++y(x(4),x(8),x(2)) ++y(x(4),x(8),x(3)) ++y(x(4),x(8),x(4)) ++y(x(4),x(8),x(5)) ++y(x(4),x(8),x(6)) ++y(x(4),x(8),x(7)) ++y(x(4),x(8),x(9)) ++y(x(4),x(8),x(10)) ++y(x(4),x(9),x(0)) ++y(x(4),x(9),x(1)) ++y(x(4),x(9),x(2)) ++y(x(4),x(9),x(3)) ++y(x(4),x(9),x(4)) ++y(x(4),x(9),x(5)) ++y(x(4),x(9),x(6)) ++y(x(4),x(9),x(7)) ++y(x(4),x(9),x(8)) ++y(x(4),x(9),x(10)) ++y(x(5),x(0),x(1)) ++y(x(5),x(0),x(2)) ++y(x(5),x(0),x(3)) ++y(x(5),x(0),x(4)) ++y(x(5),x(0),x(5)) ++y(x(5),x(0),x(6)) ++y(x(5),x(0),x(7)) ++y(x(5),x(0),x(8)) ++y(x(5),x(0),x(9)) ++y(x(5),x(0),x(10)) ++y(x(5),x(1),x(0)) ++y(x(5),x(1),x(2)) ++y(x(5),x(1),x(3)) ++y(x(5),x(1),x(4)) ++y(x(5),x(1),x(5)) ++y(x(5),x(1),x(6)) ++y(x(5),x(1),x(7)) ++y(x(5),x(1),x(8)) ++y(x(5),x(1),x(9)) ++y(x(5),x(1),x(10)) ++y(x(5),x(2),x(0)) ++y(x(5),x(2),x(1)) ++y(x(5),x(2),x(3)) ++y(x(5),x(2),x(4)) ++y(x(5),x(2),x(5)) ++y(x(5),x(2),x(6)) ++y(x(5),x(2),x(7)) ++y(x(5),x(2),x(8)) ++y(x(5),x(2),x(9)) ++y(x(5),x(2),x(10)) ++y(x(5),x(3),x(0)) ++y(x(5),x(3),x(1)) ++y(x(5),x(3),x(2)) ++y(x(5),x(3),x(4)) ++y(x(5),x(3),x(5)) ++y(x(5),x(3),x(6)) ++y(x(5),x(3),x(7)) ++y(x(5),x(3),x(8)) ++y(x(5),x(3),x(9)) ++y(x(5),x(3),x(10)) ++y(x(5),x(4),x(0)) ++y(x(5),x(4),x(1)) ++y(x(5),x(4),x(2)) ++y(x(5),x(4),x(3)) ++y(x(5),x(4),x(5)) ++y(x(5),x(4),x(6)) ++y(x(5),x(4),x(7)) ++y(x(5),x(4),x(8)) ++y(x(5),x(4),x(9)) ++y(x(5),x(4),x(10)) ++y(x(5),x(5),x(0)) ++y(x(5),x(5),x(1)) ++y(x(5),x(5),x(2)) ++y(x(5),x(5),x(3)) ++y(x(5),x(5),x(4)) ++y(x(5),x(5),x(5)) ++y(x(5),x(5),x(6)) ++y(x(5),x(5),x(7)) ++y(x(5),x(5),x(8)) ++y(x(5),x(5),x(9)) ++y(x(5),x(5),x(10)) ++y(x(5),x(6),x(0)) ++y(x(5),x(6),x(1)) ++y(x(5),x(6),x(2)) ++y(x(5),x(6),x(3)) ++y(x(5),x(6),x(4)) ++y(x(5),x(6),x(5)) ++y(x(5),x(6),x(7)) ++y(x(5),x(6),x(8)) ++y(x(5),x(6),x(9)) ++y(x(5),x(6),x(10)) ++y(x(5),x(7),x(0)) ++y(x(5),x(7),x(1)) ++y(x(5),x(7),x(2)) ++y(x(5),x(7),x(3)) ++y(x(5),x(7),x(4)) ++y(x(5),x(7),x(5)) ++y(x(5),x(7),x(6)) ++y(x(5),x(7),x(8)) ++y(x(5),x(7),x(9)) ++y(x(5),x(7),x(10)) ++y(x(5),x(8),x(0)) ++y(x(5),x(8),x(1)) ++y(x(5),x(8),x(2)) ++y(x(5),x(8),x(3)) ++y(x(5),x(8),x(4)) ++y(x(5),x(8),x(5)) ++y(x(5),x(8),x(6)) ++y(x(5),x(8),x(7)) ++y(x(5),x(8),x(9)) ++y(x(5),x(8),x(10)) ++y(x(5),x(9),x(0)) ++y(x(5),x(9),x(1)) ++y(x(5),x(9),x(2)) ++y(x(5),x(9),x(3)) ++y(x(5),x(9),x(4)) ++y(x(5),x(9),x(5)) ++y(x(5),x(9),x(6)) ++y(x(5),x(9),x(7)) ++y(x(5),x(9),x(8)) ++y(x(5),x(9),x(10)) ++y(x(6),x(0),x(1)) ++y(x(6),x(0),x(2)) ++y(x(6),x(0),x(3)) ++y(x(6),x(0),x(4)) ++y(x(6),x(0),x(5)) ++y(x(6),x(0),x(6)) ++y(x(6),x(0),x(7)) ++y(x(6),x(0),x(8)) ++y(x(6),x(0),x(9)) ++y(x(6),x(0),x(10)) ++y(x(6),x(1),x(0)) ++y(x(6),x(1),x(2)) ++y(x(6),x(1),x(3)) ++y(x(6),x(1),x(4)) ++y(x(6),x(1),x(5)) ++y(x(6),x(1),x(6)) ++y(x(6),x(1),x(7)) ++y(x(6),x(1),x(8)) ++y(x(6),x(1),x(9)) ++y(x(6),x(1),x(10)) ++y(x(6),x(2),x(0)) ++y(x(6),x(2),x(1)) ++y(x(6),x(2),x(3)) ++y(x(6),x(2),x(4)) ++y(x(6),x(2),x(5)) ++y(x(6),x(2),x(6)) ++y(x(6),x(2),x(7)) ++y(x(6),x(2),x(8)) ++y(x(6),x(2),x(9)) ++y(x(6),x(2),x(10)) ++y(x(6),x(3),x(0)) ++y(x(6),x(3),x(1)) ++y(x(6),x(3),x(2)) ++y(x(6),x(3),x(4)) ++y(x(6),x(3),x(5)) ++y(x(6),x(3),x(6)) ++y(x(6),x(3),x(7)) ++y(x(6),x(3),x(8)) ++y(x(6),x(3),x(9)) ++y(x(6),x(3),x(10)) ++y(x(6),x(4),x(0)) ++y(x(6),x(4),x(1)) ++y(x(6),x(4),x(2)) ++y(x(6),x(4),x(3)) ++y(x(6),x(4),x(5)) ++y(x(6),x(4),x(6)) ++y(x(6),x(4),x(7)) ++y(x(6),x(4),x(8)) ++y(x(6),x(4),x(9)) ++y(x(6),x(4),x(10)) ++y(x(6),x(5),x(0)) ++y(x(6),x(5),x(1)) ++y(x(6),x(5),x(2)) ++y(x(6),x(5),x(3)) ++y(x(6),x(5),x(4)) ++y(x(6),x(5),x(6)) ++y(x(6),x(5),x(7)) ++y(x(6),x(5),x(8)) ++y(x(6),x(5),x(9)) ++y(x(6),x(5),x(10)) ++y(x(6),x(6),x(0)) ++y(x(6),x(6),x(1)) ++y(x(6),x(6),x(2)) ++y(x(6),x(6),x(3)) ++y(x(6),x(6),x(4)) ++y(x(6),x(6),x(5)) ++y(x(6),x(6),x(6)) ++y(x(6),x(6),x(7)) ++y(x(6),x(6),x(8)) ++y(x(6),x(6),x(9)) ++y(x(6),x(6),x(10)) ++y(x(6),x(7),x(0)) ++y(x(6),x(7),x(1)) ++y(x(6),x(7),x(2)) ++y(x(6),x(7),x(3)) ++y(x(6),x(7),x(4)) ++y(x(6),x(7),x(5)) ++y(x(6),x(7),x(6)) ++y(x(6),x(7),x(8)) ++y(x(6),x(7),x(9)) ++y(x(6),x(7),x(10)) ++y(x(6),x(8),x(0)) ++y(x(6),x(8),x(1)) ++y(x(6),x(8),x(2)) ++y(x(6),x(8),x(3)) ++y(x(6),x(8),x(4)) ++y(x(6),x(8),x(5)) ++y(x(6),x(8),x(6)) ++y(x(6),x(8),x(7)) ++y(x(6),x(8),x(9)) ++y(x(6),x(8),x(10)) ++y(x(6),x(9),x(0)) ++y(x(6),x(9),x(1)) ++y(x(6),x(9),x(2)) ++y(x(6),x(9),x(3)) ++y(x(6),x(9),x(4)) ++y(x(6),x(9),x(5)) ++y(x(6),x(9),x(6)) ++y(x(6),x(9),x(7)) ++y(x(6),x(9),x(8)) ++y(x(6),x(9),x(10)) ++y(x(7),x(0),x(1)) ++y(x(7),x(0),x(2)) ++y(x(7),x(0),x(3)) ++y(x(7),x(0),x(4)) ++y(x(7),x(0),x(5)) ++y(x(7),x(0),x(6)) ++y(x(7),x(0),x(7)) ++y(x(7),x(0),x(8)) ++y(x(7),x(0),x(9)) ++y(x(7),x(0),x(10)) ++y(x(7),x(1),x(0)) ++y(x(7),x(1),x(2)) ++y(x(7),x(1),x(3)) ++y(x(7),x(1),x(4)) ++y(x(7),x(1),x(5)) ++y(x(7),x(1),x(6)) ++y(x(7),x(1),x(7)) ++y(x(7),x(1),x(8)) ++y(x(7),x(1),x(9)) ++y(x(7),x(1),x(10)) ++y(x(7),x(2),x(0)) ++y(x(7),x(2),x(1)) ++y(x(7),x(2),x(3)) ++y(x(7),x(2),x(4)) ++y(x(7),x(2),x(5)) ++y(x(7),x(2),x(6)) ++y(x(7),x(2),x(7)) ++y(x(7),x(2),x(8)) ++y(x(7),x(2),x(9)) ++y(x(7),x(2),x(10)) ++y(x(7),x(3),x(0)) ++y(x(7),x(3),x(1)) ++y(x(7),x(3),x(2)) ++y(x(7),x(3),x(4)) ++y(x(7),x(3),x(5)) ++y(x(7),x(3),x(6)) ++y(x(7),x(3),x(7)) ++y(x(7),x(3),x(8)) ++y(x(7),x(3),x(9)) ++y(x(7),x(3),x(10)) ++y(x(7),x(4),x(0)) ++y(x(7),x(4),x(1)) ++y(x(7),x(4),x(2)) ++y(x(7),x(4),x(3)) ++y(x(7),x(4),x(5)) ++y(x(7),x(4),x(6)) ++y(x(7),x(4),x(7)) ++y(x(7),x(4),x(8)) ++y(x(7),x(4),x(9)) ++y(x(7),x(4),x(10)) ++y(x(7),x(5),x(0)) ++y(x(7),x(5),x(1)) ++y(x(7),x(5),x(2)) ++y(x(7),x(5),x(3)) ++y(x(7),x(5),x(4)) ++y(x(7),x(5),x(6)) ++y(x(7),x(5),x(7)) ++y(x(7),x(5),x(8)) ++y(x(7),x(5),x(9)) ++y(x(7),x(5),x(10)) ++y(x(7),x(6),x(0)) ++y(x(7),x(6),x(1)) ++y(x(7),x(6),x(2)) ++y(x(7),x(6),x(3)) ++y(x(7),x(6),x(4)) ++y(x(7),x(6),x(5)) ++y(x(7),x(6),x(7)) ++y(x(7),x(6),x(8)) ++y(x(7),x(6),x(9)) ++y(x(7),x(6),x(10)) ++y(x(7),x(7),x(0)) ++y(x(7),x(7),x(1)) ++y(x(7),x(7),x(2)) ++y(x(7),x(7),x(3)) ++y(x(7),x(7),x(4)) ++y(x(7),x(7),x(5)) ++y(x(7),x(7),x(6)) ++y(x(7),x(7),x(7)) ++y(x(7),x(7),x(8)) ++y(x(7),x(7),x(9)) ++y(x(7),x(7),x(10)) ++y(x(7),x(8),x(0)) ++y(x(7),x(8),x(1)) ++y(x(7),x(8),x(2)) ++y(x(7),x(8),x(3)) ++y(x(7),x(8),x(4)) ++y(x(7),x(8),x(5)) ++y(x(7),x(8),x(6)) ++y(x(7),x(8),x(7)) ++y(x(7),x(8),x(9)) ++y(x(7),x(8),x(10)) ++y(x(7),x(9),x(0)) ++y(x(7),x(9),x(1)) ++y(x(7),x(9),x(2)) ++y(x(7),x(9),x(3)) ++y(x(7),x(9),x(4)) ++y(x(7),x(9),x(5)) ++y(x(7),x(9),x(6)) ++y(x(7),x(9),x(7)) ++y(x(7),x(9),x(8)) ++y(x(7),x(9),x(10)) ++y(x(8),x(0),x(1)) ++y(x(8),x(0),x(2)) ++y(x(8),x(0),x(3)) ++y(x(8),x(0),x(4)) ++y(x(8),x(0),x(5)) ++y(x(8),x(0),x(6)) ++y(x(8),x(0),x(7)) ++y(x(8),x(0),x(8)) ++y(x(8),x(0),x(9)) ++y(x(8),x(0),x(10)) ++y(x(8),x(1),x(0)) ++y(x(8),x(1),x(2)) ++y(x(8),x(1),x(3)) ++y(x(8),x(1),x(4)) ++y(x(8),x(1),x(5)) ++y(x(8),x(1),x(6)) ++y(x(8),x(1),x(7)) ++y(x(8),x(1),x(8)) ++y(x(8),x(1),x(9)) ++y(x(8),x(1),x(10)) ++y(x(8),x(2),x(0)) ++y(x(8),x(2),x(1)) ++y(x(8),x(2),x(3)) ++y(x(8),x(2),x(4)) ++y(x(8),x(2),x(5)) ++y(x(8),x(2),x(6)) ++y(x(8),x(2),x(7)) ++y(x(8),x(2),x(8)) ++y(x(8),x(2),x(9)) ++y(x(8),x(2),x(10)) ++y(x(8),x(3),x(0)) ++y(x(8),x(3),x(1)) ++y(x(8),x(3),x(2)) ++y(x(8),x(3),x(4)) ++y(x(8),x(3),x(5)) ++y(x(8),x(3),x(6)) ++y(x(8),x(3),x(7)) ++y(x(8),x(3),x(8)) ++y(x(8),x(3),x(9)) ++y(x(8),x(3),x(10)) ++y(x(8),x(4),x(0)) ++y(x(8),x(4),x(1)) ++y(x(8),x(4),x(2)) ++y(x(8),x(4),x(3)) ++y(x(8),x(4),x(5)) ++y(x(8),x(4),x(6)) ++y(x(8),x(4),x(7)) ++y(x(8),x(4),x(8)) ++y(x(8),x(4),x(9)) ++y(x(8),x(4),x(10)) ++y(x(8),x(5),x(0)) ++y(x(8),x(5),x(1)) ++y(x(8),x(5),x(2)) ++y(x(8),x(5),x(3)) ++y(x(8),x(5),x(4)) ++y(x(8),x(5),x(6)) ++y(x(8),x(5),x(7)) ++y(x(8),x(5),x(8)) ++y(x(8),x(5),x(9)) ++y(x(8),x(5),x(10)) ++y(x(8),x(6),x(0)) ++y(x(8),x(6),x(1)) ++y(x(8),x(6),x(2)) ++y(x(8),x(6),x(3)) ++y(x(8),x(6),x(4)) ++y(x(8),x(6),x(5)) ++y(x(8),x(6),x(7)) ++y(x(8),x(6),x(8)) ++y(x(8),x(6),x(9)) ++y(x(8),x(6),x(10)) ++y(x(8),x(7),x(0)) ++y(x(8),x(7),x(1)) ++y(x(8),x(7),x(2)) ++y(x(8),x(7),x(3)) ++y(x(8),x(7),x(4)) ++y(x(8),x(7),x(5)) ++y(x(8),x(7),x(6)) ++y(x(8),x(7),x(8)) ++y(x(8),x(7),x(9)) ++y(x(8),x(7),x(10)) ++y(x(8),x(8),x(0)) ++y(x(8),x(8),x(1)) ++y(x(8),x(8),x(2)) ++y(x(8),x(8),x(3)) ++y(x(8),x(8),x(4)) ++y(x(8),x(8),x(5)) ++y(x(8),x(8),x(6)) ++y(x(8),x(8),x(7)) ++y(x(8),x(8),x(8)) ++y(x(8),x(8),x(9)) ++y(x(8),x(8),x(10)) ++y(x(8),x(9),x(0)) ++y(x(8),x(9),x(1)) ++y(x(8),x(9),x(2)) ++y(x(8),x(9),x(3)) ++y(x(8),x(9),x(4)) ++y(x(8),x(9),x(5)) ++y(x(8),x(9),x(6)) ++y(x(8),x(9),x(7)) ++y(x(8),x(9),x(8)) ++y(x(8),x(9),x(10)) ++y(x(9),x(0),x(1)) ++y(x(9),x(0),x(2)) ++y(x(9),x(0),x(3)) ++y(x(9),x(0),x(4)) ++y(x(9),x(0),x(5)) ++y(x(9),x(0),x(6)) ++y(x(9),x(0),x(7)) ++y(x(9),x(0),x(8)) ++y(x(9),x(0),x(9)) ++y(x(9),x(0),x(10)) ++y(x(9),x(1),x(0)) ++y(x(9),x(1),x(2)) ++y(x(9),x(1),x(3)) ++y(x(9),x(1),x(4)) ++y(x(9),x(1),x(5)) ++y(x(9),x(1),x(6)) ++y(x(9),x(1),x(7)) ++y(x(9),x(1),x(8)) ++y(x(9),x(1),x(9)) ++y(x(9),x(1),x(10)) ++y(x(9),x(2),x(0)) ++y(x(9),x(2),x(1)) ++y(x(9),x(2),x(3)) ++y(x(9),x(2),x(4)) ++y(x(9),x(2),x(5)) ++y(x(9),x(2),x(6)) ++y(x(9),x(2),x(7)) ++y(x(9),x(2),x(8)) ++y(x(9),x(2),x(9)) ++y(x(9),x(2),x(10)) ++y(x(9),x(3),x(0)) ++y(x(9),x(3),x(1)) ++y(x(9),x(3),x(2)) ++y(x(9),x(3),x(4)) ++y(x(9),x(3),x(5)) ++y(x(9),x(3),x(6)) ++y(x(9),x(3),x(7)) ++y(x(9),x(3),x(8)) ++y(x(9),x(3),x(9)) ++y(x(9),x(3),x(10)) ++y(x(9),x(4),x(0)) ++y(x(9),x(4),x(1)) ++y(x(9),x(4),x(2)) ++y(x(9),x(4),x(3)) ++y(x(9),x(4),x(5)) ++y(x(9),x(4),x(6)) ++y(x(9),x(4),x(7)) ++y(x(9),x(4),x(8)) ++y(x(9),x(4),x(9)) ++y(x(9),x(4),x(10)) ++y(x(9),x(5),x(0)) ++y(x(9),x(5),x(1)) ++y(x(9),x(5),x(2)) ++y(x(9),x(5),x(3)) ++y(x(9),x(5),x(4)) ++y(x(9),x(5),x(6)) ++y(x(9),x(5),x(7)) ++y(x(9),x(5),x(8)) ++y(x(9),x(5),x(9)) ++y(x(9),x(5),x(10)) ++y(x(9),x(6),x(0)) ++y(x(9),x(6),x(1)) ++y(x(9),x(6),x(2)) ++y(x(9),x(6),x(3)) ++y(x(9),x(6),x(4)) ++y(x(9),x(6),x(5)) ++y(x(9),x(6),x(7)) ++y(x(9),x(6),x(8)) ++y(x(9),x(6),x(9)) ++y(x(9),x(6),x(10)) ++y(x(9),x(7),x(0)) ++y(x(9),x(7),x(1)) ++y(x(9),x(7),x(2)) ++y(x(9),x(7),x(3)) ++y(x(9),x(7),x(4)) ++y(x(9),x(7),x(5)) ++y(x(9),x(7),x(6)) ++y(x(9),x(7),x(8)) ++y(x(9),x(7),x(9)) ++y(x(9),x(7),x(10)) ++y(x(9),x(8),x(0)) ++y(x(9),x(8),x(1)) ++y(x(9),x(8),x(2)) ++y(x(9),x(8),x(3)) ++y(x(9),x(8),x(4)) ++y(x(9),x(8),x(5)) ++y(x(9),x(8),x(6)) ++y(x(9),x(8),x(7)) ++y(x(9),x(8),x(9)) ++y(x(9),x(8),x(10)) ++y(x(9),x(9),x(0)) ++y(x(9),x(9),x(1)) ++y(x(9),x(9),x(2)) ++y(x(9),x(9),x(3)) ++y(x(9),x(9),x(4)) ++y(x(9),x(9),x(5)) ++y(x(9),x(9),x(6)) ++y(x(9),x(9),x(7)) ++y(x(9),x(9),x(8)) ++y(x(9),x(9),x(9)) ++y(x(9),x(9),x(10)) ++y(x(10),x(0),x(1)) ++y(x(10),x(0),x(2)) ++y(x(10),x(0),x(3)) ++y(x(10),x(0),x(4)) ++y(x(10),x(0),x(5)) ++y(x(10),x(0),x(6)) ++y(x(10),x(0),x(7)) ++y(x(10),x(0),x(8)) ++y(x(10),x(0),x(9)) ++y(x(10),x(0),x(10)) ++y(x(10),x(1),x(0)) ++y(x(10),x(1),x(2)) ++y(x(10),x(1),x(3)) ++y(x(10),x(1),x(4)) ++y(x(10),x(1),x(5)) ++y(x(10),x(1),x(6)) ++y(x(10),x(1),x(7)) ++y(x(10),x(1),x(8)) ++y(x(10),x(1),x(9)) ++y(x(10),x(1),x(10)) ++y(x(10),x(2),x(0)) ++y(x(10),x(2),x(1)) ++y(x(10),x(2),x(3)) ++y(x(10),x(2),x(4)) ++y(x(10),x(2),x(5)) ++y(x(10),x(2),x(6)) ++y(x(10),x(2),x(7)) ++y(x(10),x(2),x(8)) ++y(x(10),x(2),x(9)) ++y(x(10),x(2),x(10)) ++y(x(10),x(3),x(0)) ++y(x(10),x(3),x(1)) ++y(x(10),x(3),x(2)) ++y(x(10),x(3),x(4)) ++y(x(10),x(3),x(5)) ++y(x(10),x(3),x(6)) ++y(x(10),x(3),x(7)) ++y(x(10),x(3),x(8)) ++y(x(10),x(3),x(9)) ++y(x(10),x(3),x(10)) ++y(x(10),x(4),x(0)) ++y(x(10),x(4),x(1)) ++y(x(10),x(4),x(2)) ++y(x(10),x(4),x(3)) ++y(x(10),x(4),x(5)) ++y(x(10),x(4),x(6)) ++y(x(10),x(4),x(7)) ++y(x(10),x(4),x(8)) ++y(x(10),x(4),x(9)) ++y(x(10),x(4),x(10)) ++y(x(10),x(5),x(0)) ++y(x(10),x(5),x(1)) ++y(x(10),x(5),x(2)) ++y(x(10),x(5),x(3)) ++y(x(10),x(5),x(4)) ++y(x(10),x(5),x(6)) ++y(x(10),x(5),x(7)) ++y(x(10),x(5),x(8)) ++y(x(10),x(5),x(9)) ++y(x(10),x(5),x(10)) ++y(x(10),x(6),x(0)) ++y(x(10),x(6),x(1)) ++y(x(10),x(6),x(2)) ++y(x(10),x(6),x(3)) ++y(x(10),x(6),x(4)) ++y(x(10),x(6),x(5)) ++y(x(10),x(6),x(7)) ++y(x(10),x(6),x(8)) ++y(x(10),x(6),x(9)) ++y(x(10),x(6),x(10)) ++y(x(10),x(7),x(0)) ++y(x(10),x(7),x(1)) ++y(x(10),x(7),x(2)) ++y(x(10),x(7),x(3)) ++y(x(10),x(7),x(4)) ++y(x(10),x(7),x(5)) ++y(x(10),x(7),x(6)) ++y(x(10),x(7),x(8)) ++y(x(10),x(7),x(9)) ++y(x(10),x(7),x(10)) ++y(x(10),x(8),x(0)) ++y(x(10),x(8),x(1)) ++y(x(10),x(8),x(2)) ++y(x(10),x(8),x(3)) ++y(x(10),x(8),x(4)) ++y(x(10),x(8),x(5)) ++y(x(10),x(8),x(6)) ++y(x(10),x(8),x(7)) ++y(x(10),x(8),x(9)) ++y(x(10),x(8),x(10)) ++y(x(10),x(9),x(0)) ++y(x(10),x(9),x(1)) ++y(x(10),x(9),x(2)) ++y(x(10),x(9),x(3)) ++y(x(10),x(9),x(4)) ++y(x(10),x(9),x(5)) ++y(x(10),x(9),x(6)) ++y(x(10),x(9),x(7)) ++y(x(10),x(9),x(8)) ++y(x(10),x(9),x(10)) ++y(x(10),x(10),x(10)) diff --git a/lib/reasoners/eflint-haskell/tests/traces/bench_triple.perf7 b/lib/reasoners/eflint-haskell/tests/traces/bench_triple.perf7 new file mode 100644 index 0000000..eac0d7a --- /dev/null +++ b/lib/reasoners/eflint-haskell/tests/traces/bench_triple.perf7 @@ -0,0 +1,3634 @@ +New type x +New type y ++x(0) ++x(1) ++x(2) ++x(3) ++x(4) ++x(5) ++x(6) ++x(7) ++x(8) ++x(9) ++x(10) ++x(11) ++x(12) ++x(13) ++x(14) ++x(15) ++y(x(0),x(0),x(0)) ++y(x(0),x(0),x(1)) ++y(x(0),x(0),x(2)) ++y(x(0),x(0),x(3)) ++y(x(0),x(0),x(4)) ++y(x(0),x(0),x(5)) ++y(x(0),x(0),x(6)) ++y(x(0),x(0),x(7)) ++y(x(0),x(0),x(8)) ++y(x(0),x(0),x(9)) ++y(x(0),x(0),x(10)) ++y(x(0),x(0),x(11)) ++y(x(0),x(0),x(12)) ++y(x(0),x(0),x(13)) ++y(x(0),x(0),x(14)) ++y(x(0),x(0),x(15)) ++y(x(0),x(1),x(0)) ++y(x(0),x(1),x(2)) ++y(x(0),x(1),x(3)) ++y(x(0),x(1),x(4)) ++y(x(0),x(1),x(5)) ++y(x(0),x(1),x(6)) ++y(x(0),x(1),x(7)) ++y(x(0),x(1),x(8)) ++y(x(0),x(1),x(9)) ++y(x(0),x(1),x(10)) ++y(x(0),x(1),x(11)) ++y(x(0),x(1),x(12)) ++y(x(0),x(1),x(13)) ++y(x(0),x(1),x(14)) ++y(x(0),x(1),x(15)) ++y(x(0),x(2),x(0)) ++y(x(0),x(2),x(1)) ++y(x(0),x(2),x(3)) ++y(x(0),x(2),x(4)) ++y(x(0),x(2),x(5)) ++y(x(0),x(2),x(6)) ++y(x(0),x(2),x(7)) ++y(x(0),x(2),x(8)) ++y(x(0),x(2),x(9)) ++y(x(0),x(2),x(10)) ++y(x(0),x(2),x(11)) ++y(x(0),x(2),x(12)) ++y(x(0),x(2),x(13)) ++y(x(0),x(2),x(14)) ++y(x(0),x(2),x(15)) ++y(x(0),x(3),x(0)) ++y(x(0),x(3),x(1)) ++y(x(0),x(3),x(2)) ++y(x(0),x(3),x(4)) ++y(x(0),x(3),x(5)) ++y(x(0),x(3),x(6)) ++y(x(0),x(3),x(7)) ++y(x(0),x(3),x(8)) ++y(x(0),x(3),x(9)) ++y(x(0),x(3),x(10)) ++y(x(0),x(3),x(11)) ++y(x(0),x(3),x(12)) ++y(x(0),x(3),x(13)) ++y(x(0),x(3),x(14)) ++y(x(0),x(3),x(15)) ++y(x(0),x(4),x(0)) ++y(x(0),x(4),x(1)) ++y(x(0),x(4),x(2)) ++y(x(0),x(4),x(3)) ++y(x(0),x(4),x(5)) ++y(x(0),x(4),x(6)) ++y(x(0),x(4),x(7)) ++y(x(0),x(4),x(8)) ++y(x(0),x(4),x(9)) ++y(x(0),x(4),x(10)) ++y(x(0),x(4),x(11)) ++y(x(0),x(4),x(12)) ++y(x(0),x(4),x(13)) ++y(x(0),x(4),x(14)) ++y(x(0),x(4),x(15)) ++y(x(0),x(5),x(0)) ++y(x(0),x(5),x(1)) ++y(x(0),x(5),x(2)) ++y(x(0),x(5),x(3)) ++y(x(0),x(5),x(4)) ++y(x(0),x(5),x(6)) ++y(x(0),x(5),x(7)) ++y(x(0),x(5),x(8)) ++y(x(0),x(5),x(9)) ++y(x(0),x(5),x(10)) ++y(x(0),x(5),x(11)) ++y(x(0),x(5),x(12)) ++y(x(0),x(5),x(13)) ++y(x(0),x(5),x(14)) ++y(x(0),x(5),x(15)) ++y(x(0),x(6),x(0)) ++y(x(0),x(6),x(1)) ++y(x(0),x(6),x(2)) ++y(x(0),x(6),x(3)) ++y(x(0),x(6),x(4)) ++y(x(0),x(6),x(5)) ++y(x(0),x(6),x(7)) ++y(x(0),x(6),x(8)) ++y(x(0),x(6),x(9)) ++y(x(0),x(6),x(10)) ++y(x(0),x(6),x(11)) ++y(x(0),x(6),x(12)) ++y(x(0),x(6),x(13)) ++y(x(0),x(6),x(14)) ++y(x(0),x(6),x(15)) ++y(x(0),x(7),x(0)) ++y(x(0),x(7),x(1)) ++y(x(0),x(7),x(2)) ++y(x(0),x(7),x(3)) ++y(x(0),x(7),x(4)) ++y(x(0),x(7),x(5)) ++y(x(0),x(7),x(6)) ++y(x(0),x(7),x(8)) ++y(x(0),x(7),x(9)) ++y(x(0),x(7),x(10)) ++y(x(0),x(7),x(11)) ++y(x(0),x(7),x(12)) ++y(x(0),x(7),x(13)) ++y(x(0),x(7),x(14)) ++y(x(0),x(7),x(15)) ++y(x(0),x(8),x(0)) ++y(x(0),x(8),x(1)) ++y(x(0),x(8),x(2)) ++y(x(0),x(8),x(3)) ++y(x(0),x(8),x(4)) ++y(x(0),x(8),x(5)) ++y(x(0),x(8),x(6)) ++y(x(0),x(8),x(7)) ++y(x(0),x(8),x(9)) ++y(x(0),x(8),x(10)) ++y(x(0),x(8),x(11)) ++y(x(0),x(8),x(12)) ++y(x(0),x(8),x(13)) ++y(x(0),x(8),x(14)) ++y(x(0),x(8),x(15)) ++y(x(0),x(9),x(0)) ++y(x(0),x(9),x(1)) ++y(x(0),x(9),x(2)) ++y(x(0),x(9),x(3)) ++y(x(0),x(9),x(4)) ++y(x(0),x(9),x(5)) ++y(x(0),x(9),x(6)) ++y(x(0),x(9),x(7)) ++y(x(0),x(9),x(8)) ++y(x(0),x(9),x(10)) ++y(x(0),x(9),x(11)) ++y(x(0),x(9),x(12)) ++y(x(0),x(9),x(13)) ++y(x(0),x(9),x(14)) ++y(x(0),x(9),x(15)) ++y(x(0),x(10),x(0)) ++y(x(0),x(10),x(1)) ++y(x(0),x(10),x(2)) ++y(x(0),x(10),x(3)) ++y(x(0),x(10),x(4)) ++y(x(0),x(10),x(5)) ++y(x(0),x(10),x(6)) ++y(x(0),x(10),x(7)) ++y(x(0),x(10),x(8)) ++y(x(0),x(10),x(9)) ++y(x(0),x(10),x(11)) ++y(x(0),x(10),x(12)) ++y(x(0),x(10),x(13)) ++y(x(0),x(10),x(14)) ++y(x(0),x(10),x(15)) ++y(x(0),x(11),x(0)) ++y(x(0),x(11),x(1)) ++y(x(0),x(11),x(2)) ++y(x(0),x(11),x(3)) ++y(x(0),x(11),x(4)) ++y(x(0),x(11),x(5)) ++y(x(0),x(11),x(6)) ++y(x(0),x(11),x(7)) ++y(x(0),x(11),x(8)) ++y(x(0),x(11),x(9)) ++y(x(0),x(11),x(10)) ++y(x(0),x(11),x(12)) ++y(x(0),x(11),x(13)) ++y(x(0),x(11),x(14)) ++y(x(0),x(11),x(15)) ++y(x(0),x(12),x(0)) ++y(x(0),x(12),x(1)) ++y(x(0),x(12),x(2)) ++y(x(0),x(12),x(3)) ++y(x(0),x(12),x(4)) ++y(x(0),x(12),x(5)) ++y(x(0),x(12),x(6)) ++y(x(0),x(12),x(7)) ++y(x(0),x(12),x(8)) ++y(x(0),x(12),x(9)) ++y(x(0),x(12),x(10)) ++y(x(0),x(12),x(11)) ++y(x(0),x(12),x(13)) ++y(x(0),x(12),x(14)) ++y(x(0),x(12),x(15)) ++y(x(0),x(13),x(0)) ++y(x(0),x(13),x(1)) ++y(x(0),x(13),x(2)) ++y(x(0),x(13),x(3)) ++y(x(0),x(13),x(4)) ++y(x(0),x(13),x(5)) ++y(x(0),x(13),x(6)) ++y(x(0),x(13),x(7)) ++y(x(0),x(13),x(8)) ++y(x(0),x(13),x(9)) ++y(x(0),x(13),x(10)) ++y(x(0),x(13),x(11)) ++y(x(0),x(13),x(12)) ++y(x(0),x(13),x(14)) ++y(x(0),x(13),x(15)) ++y(x(0),x(14),x(0)) ++y(x(0),x(14),x(1)) ++y(x(0),x(14),x(2)) ++y(x(0),x(14),x(3)) ++y(x(0),x(14),x(4)) ++y(x(0),x(14),x(5)) ++y(x(0),x(14),x(6)) ++y(x(0),x(14),x(7)) ++y(x(0),x(14),x(8)) ++y(x(0),x(14),x(9)) ++y(x(0),x(14),x(10)) ++y(x(0),x(14),x(11)) ++y(x(0),x(14),x(12)) ++y(x(0),x(14),x(13)) ++y(x(0),x(14),x(15)) ++y(x(1),x(0),x(1)) ++y(x(1),x(0),x(2)) ++y(x(1),x(0),x(3)) ++y(x(1),x(0),x(4)) ++y(x(1),x(0),x(5)) ++y(x(1),x(0),x(6)) ++y(x(1),x(0),x(7)) ++y(x(1),x(0),x(8)) ++y(x(1),x(0),x(9)) ++y(x(1),x(0),x(10)) ++y(x(1),x(0),x(11)) ++y(x(1),x(0),x(12)) ++y(x(1),x(0),x(13)) ++y(x(1),x(0),x(14)) ++y(x(1),x(0),x(15)) ++y(x(1),x(1),x(0)) ++y(x(1),x(1),x(1)) ++y(x(1),x(1),x(2)) ++y(x(1),x(1),x(3)) ++y(x(1),x(1),x(4)) ++y(x(1),x(1),x(5)) ++y(x(1),x(1),x(6)) ++y(x(1),x(1),x(7)) ++y(x(1),x(1),x(8)) ++y(x(1),x(1),x(9)) ++y(x(1),x(1),x(10)) ++y(x(1),x(1),x(11)) ++y(x(1),x(1),x(12)) ++y(x(1),x(1),x(13)) ++y(x(1),x(1),x(14)) ++y(x(1),x(1),x(15)) ++y(x(1),x(2),x(0)) ++y(x(1),x(2),x(1)) ++y(x(1),x(2),x(3)) ++y(x(1),x(2),x(4)) ++y(x(1),x(2),x(5)) ++y(x(1),x(2),x(6)) ++y(x(1),x(2),x(7)) ++y(x(1),x(2),x(8)) ++y(x(1),x(2),x(9)) ++y(x(1),x(2),x(10)) ++y(x(1),x(2),x(11)) ++y(x(1),x(2),x(12)) ++y(x(1),x(2),x(13)) ++y(x(1),x(2),x(14)) ++y(x(1),x(2),x(15)) ++y(x(1),x(3),x(0)) ++y(x(1),x(3),x(1)) ++y(x(1),x(3),x(2)) ++y(x(1),x(3),x(4)) ++y(x(1),x(3),x(5)) ++y(x(1),x(3),x(6)) ++y(x(1),x(3),x(7)) ++y(x(1),x(3),x(8)) ++y(x(1),x(3),x(9)) ++y(x(1),x(3),x(10)) ++y(x(1),x(3),x(11)) ++y(x(1),x(3),x(12)) ++y(x(1),x(3),x(13)) ++y(x(1),x(3),x(14)) ++y(x(1),x(3),x(15)) ++y(x(1),x(4),x(0)) ++y(x(1),x(4),x(1)) ++y(x(1),x(4),x(2)) ++y(x(1),x(4),x(3)) ++y(x(1),x(4),x(5)) ++y(x(1),x(4),x(6)) ++y(x(1),x(4),x(7)) ++y(x(1),x(4),x(8)) ++y(x(1),x(4),x(9)) ++y(x(1),x(4),x(10)) ++y(x(1),x(4),x(11)) ++y(x(1),x(4),x(12)) ++y(x(1),x(4),x(13)) ++y(x(1),x(4),x(14)) ++y(x(1),x(4),x(15)) ++y(x(1),x(5),x(0)) ++y(x(1),x(5),x(1)) ++y(x(1),x(5),x(2)) ++y(x(1),x(5),x(3)) ++y(x(1),x(5),x(4)) ++y(x(1),x(5),x(6)) ++y(x(1),x(5),x(7)) ++y(x(1),x(5),x(8)) ++y(x(1),x(5),x(9)) ++y(x(1),x(5),x(10)) ++y(x(1),x(5),x(11)) ++y(x(1),x(5),x(12)) ++y(x(1),x(5),x(13)) ++y(x(1),x(5),x(14)) ++y(x(1),x(5),x(15)) ++y(x(1),x(6),x(0)) ++y(x(1),x(6),x(1)) ++y(x(1),x(6),x(2)) ++y(x(1),x(6),x(3)) ++y(x(1),x(6),x(4)) ++y(x(1),x(6),x(5)) ++y(x(1),x(6),x(7)) ++y(x(1),x(6),x(8)) ++y(x(1),x(6),x(9)) ++y(x(1),x(6),x(10)) ++y(x(1),x(6),x(11)) ++y(x(1),x(6),x(12)) ++y(x(1),x(6),x(13)) ++y(x(1),x(6),x(14)) ++y(x(1),x(6),x(15)) ++y(x(1),x(7),x(0)) ++y(x(1),x(7),x(1)) ++y(x(1),x(7),x(2)) ++y(x(1),x(7),x(3)) ++y(x(1),x(7),x(4)) ++y(x(1),x(7),x(5)) ++y(x(1),x(7),x(6)) ++y(x(1),x(7),x(8)) ++y(x(1),x(7),x(9)) ++y(x(1),x(7),x(10)) ++y(x(1),x(7),x(11)) ++y(x(1),x(7),x(12)) ++y(x(1),x(7),x(13)) ++y(x(1),x(7),x(14)) ++y(x(1),x(7),x(15)) ++y(x(1),x(8),x(0)) ++y(x(1),x(8),x(1)) ++y(x(1),x(8),x(2)) ++y(x(1),x(8),x(3)) ++y(x(1),x(8),x(4)) ++y(x(1),x(8),x(5)) ++y(x(1),x(8),x(6)) ++y(x(1),x(8),x(7)) ++y(x(1),x(8),x(9)) ++y(x(1),x(8),x(10)) ++y(x(1),x(8),x(11)) ++y(x(1),x(8),x(12)) ++y(x(1),x(8),x(13)) ++y(x(1),x(8),x(14)) ++y(x(1),x(8),x(15)) ++y(x(1),x(9),x(0)) ++y(x(1),x(9),x(1)) ++y(x(1),x(9),x(2)) ++y(x(1),x(9),x(3)) ++y(x(1),x(9),x(4)) ++y(x(1),x(9),x(5)) ++y(x(1),x(9),x(6)) ++y(x(1),x(9),x(7)) ++y(x(1),x(9),x(8)) ++y(x(1),x(9),x(10)) ++y(x(1),x(9),x(11)) ++y(x(1),x(9),x(12)) ++y(x(1),x(9),x(13)) ++y(x(1),x(9),x(14)) ++y(x(1),x(9),x(15)) ++y(x(1),x(10),x(0)) ++y(x(1),x(10),x(1)) ++y(x(1),x(10),x(2)) ++y(x(1),x(10),x(3)) ++y(x(1),x(10),x(4)) ++y(x(1),x(10),x(5)) ++y(x(1),x(10),x(6)) ++y(x(1),x(10),x(7)) ++y(x(1),x(10),x(8)) ++y(x(1),x(10),x(9)) ++y(x(1),x(10),x(11)) ++y(x(1),x(10),x(12)) ++y(x(1),x(10),x(13)) ++y(x(1),x(10),x(14)) ++y(x(1),x(10),x(15)) ++y(x(1),x(11),x(0)) ++y(x(1),x(11),x(1)) ++y(x(1),x(11),x(2)) ++y(x(1),x(11),x(3)) ++y(x(1),x(11),x(4)) ++y(x(1),x(11),x(5)) ++y(x(1),x(11),x(6)) ++y(x(1),x(11),x(7)) ++y(x(1),x(11),x(8)) ++y(x(1),x(11),x(9)) ++y(x(1),x(11),x(10)) ++y(x(1),x(11),x(12)) ++y(x(1),x(11),x(13)) ++y(x(1),x(11),x(14)) ++y(x(1),x(11),x(15)) ++y(x(1),x(12),x(0)) ++y(x(1),x(12),x(1)) ++y(x(1),x(12),x(2)) ++y(x(1),x(12),x(3)) ++y(x(1),x(12),x(4)) ++y(x(1),x(12),x(5)) ++y(x(1),x(12),x(6)) ++y(x(1),x(12),x(7)) ++y(x(1),x(12),x(8)) ++y(x(1),x(12),x(9)) ++y(x(1),x(12),x(10)) ++y(x(1),x(12),x(11)) ++y(x(1),x(12),x(13)) ++y(x(1),x(12),x(14)) ++y(x(1),x(12),x(15)) ++y(x(1),x(13),x(0)) ++y(x(1),x(13),x(1)) ++y(x(1),x(13),x(2)) ++y(x(1),x(13),x(3)) ++y(x(1),x(13),x(4)) ++y(x(1),x(13),x(5)) ++y(x(1),x(13),x(6)) ++y(x(1),x(13),x(7)) ++y(x(1),x(13),x(8)) ++y(x(1),x(13),x(9)) ++y(x(1),x(13),x(10)) ++y(x(1),x(13),x(11)) ++y(x(1),x(13),x(12)) ++y(x(1),x(13),x(14)) ++y(x(1),x(13),x(15)) ++y(x(1),x(14),x(0)) ++y(x(1),x(14),x(1)) ++y(x(1),x(14),x(2)) ++y(x(1),x(14),x(3)) ++y(x(1),x(14),x(4)) ++y(x(1),x(14),x(5)) ++y(x(1),x(14),x(6)) ++y(x(1),x(14),x(7)) ++y(x(1),x(14),x(8)) ++y(x(1),x(14),x(9)) ++y(x(1),x(14),x(10)) ++y(x(1),x(14),x(11)) ++y(x(1),x(14),x(12)) ++y(x(1),x(14),x(13)) ++y(x(1),x(14),x(15)) ++y(x(2),x(0),x(1)) ++y(x(2),x(0),x(2)) ++y(x(2),x(0),x(3)) ++y(x(2),x(0),x(4)) ++y(x(2),x(0),x(5)) ++y(x(2),x(0),x(6)) ++y(x(2),x(0),x(7)) ++y(x(2),x(0),x(8)) ++y(x(2),x(0),x(9)) ++y(x(2),x(0),x(10)) ++y(x(2),x(0),x(11)) ++y(x(2),x(0),x(12)) ++y(x(2),x(0),x(13)) ++y(x(2),x(0),x(14)) ++y(x(2),x(0),x(15)) ++y(x(2),x(1),x(0)) ++y(x(2),x(1),x(2)) ++y(x(2),x(1),x(3)) ++y(x(2),x(1),x(4)) ++y(x(2),x(1),x(5)) ++y(x(2),x(1),x(6)) ++y(x(2),x(1),x(7)) ++y(x(2),x(1),x(8)) ++y(x(2),x(1),x(9)) ++y(x(2),x(1),x(10)) ++y(x(2),x(1),x(11)) ++y(x(2),x(1),x(12)) ++y(x(2),x(1),x(13)) ++y(x(2),x(1),x(14)) ++y(x(2),x(1),x(15)) ++y(x(2),x(2),x(0)) ++y(x(2),x(2),x(1)) ++y(x(2),x(2),x(2)) ++y(x(2),x(2),x(3)) ++y(x(2),x(2),x(4)) ++y(x(2),x(2),x(5)) ++y(x(2),x(2),x(6)) ++y(x(2),x(2),x(7)) ++y(x(2),x(2),x(8)) ++y(x(2),x(2),x(9)) ++y(x(2),x(2),x(10)) ++y(x(2),x(2),x(11)) ++y(x(2),x(2),x(12)) ++y(x(2),x(2),x(13)) ++y(x(2),x(2),x(14)) ++y(x(2),x(2),x(15)) ++y(x(2),x(3),x(0)) ++y(x(2),x(3),x(1)) ++y(x(2),x(3),x(2)) ++y(x(2),x(3),x(4)) ++y(x(2),x(3),x(5)) ++y(x(2),x(3),x(6)) ++y(x(2),x(3),x(7)) ++y(x(2),x(3),x(8)) ++y(x(2),x(3),x(9)) ++y(x(2),x(3),x(10)) ++y(x(2),x(3),x(11)) ++y(x(2),x(3),x(12)) ++y(x(2),x(3),x(13)) ++y(x(2),x(3),x(14)) ++y(x(2),x(3),x(15)) ++y(x(2),x(4),x(0)) ++y(x(2),x(4),x(1)) ++y(x(2),x(4),x(2)) ++y(x(2),x(4),x(3)) ++y(x(2),x(4),x(5)) ++y(x(2),x(4),x(6)) ++y(x(2),x(4),x(7)) ++y(x(2),x(4),x(8)) ++y(x(2),x(4),x(9)) ++y(x(2),x(4),x(10)) ++y(x(2),x(4),x(11)) ++y(x(2),x(4),x(12)) ++y(x(2),x(4),x(13)) ++y(x(2),x(4),x(14)) ++y(x(2),x(4),x(15)) ++y(x(2),x(5),x(0)) ++y(x(2),x(5),x(1)) ++y(x(2),x(5),x(2)) ++y(x(2),x(5),x(3)) ++y(x(2),x(5),x(4)) ++y(x(2),x(5),x(6)) ++y(x(2),x(5),x(7)) ++y(x(2),x(5),x(8)) ++y(x(2),x(5),x(9)) ++y(x(2),x(5),x(10)) ++y(x(2),x(5),x(11)) ++y(x(2),x(5),x(12)) ++y(x(2),x(5),x(13)) ++y(x(2),x(5),x(14)) ++y(x(2),x(5),x(15)) ++y(x(2),x(6),x(0)) ++y(x(2),x(6),x(1)) ++y(x(2),x(6),x(2)) ++y(x(2),x(6),x(3)) ++y(x(2),x(6),x(4)) ++y(x(2),x(6),x(5)) ++y(x(2),x(6),x(7)) ++y(x(2),x(6),x(8)) ++y(x(2),x(6),x(9)) ++y(x(2),x(6),x(10)) ++y(x(2),x(6),x(11)) ++y(x(2),x(6),x(12)) ++y(x(2),x(6),x(13)) ++y(x(2),x(6),x(14)) ++y(x(2),x(6),x(15)) ++y(x(2),x(7),x(0)) ++y(x(2),x(7),x(1)) ++y(x(2),x(7),x(2)) ++y(x(2),x(7),x(3)) ++y(x(2),x(7),x(4)) ++y(x(2),x(7),x(5)) ++y(x(2),x(7),x(6)) ++y(x(2),x(7),x(8)) ++y(x(2),x(7),x(9)) ++y(x(2),x(7),x(10)) ++y(x(2),x(7),x(11)) ++y(x(2),x(7),x(12)) ++y(x(2),x(7),x(13)) ++y(x(2),x(7),x(14)) ++y(x(2),x(7),x(15)) ++y(x(2),x(8),x(0)) ++y(x(2),x(8),x(1)) ++y(x(2),x(8),x(2)) ++y(x(2),x(8),x(3)) ++y(x(2),x(8),x(4)) ++y(x(2),x(8),x(5)) ++y(x(2),x(8),x(6)) ++y(x(2),x(8),x(7)) ++y(x(2),x(8),x(9)) ++y(x(2),x(8),x(10)) ++y(x(2),x(8),x(11)) ++y(x(2),x(8),x(12)) ++y(x(2),x(8),x(13)) ++y(x(2),x(8),x(14)) ++y(x(2),x(8),x(15)) ++y(x(2),x(9),x(0)) ++y(x(2),x(9),x(1)) ++y(x(2),x(9),x(2)) ++y(x(2),x(9),x(3)) ++y(x(2),x(9),x(4)) ++y(x(2),x(9),x(5)) ++y(x(2),x(9),x(6)) ++y(x(2),x(9),x(7)) ++y(x(2),x(9),x(8)) ++y(x(2),x(9),x(10)) ++y(x(2),x(9),x(11)) ++y(x(2),x(9),x(12)) ++y(x(2),x(9),x(13)) ++y(x(2),x(9),x(14)) ++y(x(2),x(9),x(15)) ++y(x(2),x(10),x(0)) ++y(x(2),x(10),x(1)) ++y(x(2),x(10),x(2)) ++y(x(2),x(10),x(3)) ++y(x(2),x(10),x(4)) ++y(x(2),x(10),x(5)) ++y(x(2),x(10),x(6)) ++y(x(2),x(10),x(7)) ++y(x(2),x(10),x(8)) ++y(x(2),x(10),x(9)) ++y(x(2),x(10),x(11)) ++y(x(2),x(10),x(12)) ++y(x(2),x(10),x(13)) ++y(x(2),x(10),x(14)) ++y(x(2),x(10),x(15)) ++y(x(2),x(11),x(0)) ++y(x(2),x(11),x(1)) ++y(x(2),x(11),x(2)) ++y(x(2),x(11),x(3)) ++y(x(2),x(11),x(4)) ++y(x(2),x(11),x(5)) ++y(x(2),x(11),x(6)) ++y(x(2),x(11),x(7)) ++y(x(2),x(11),x(8)) ++y(x(2),x(11),x(9)) ++y(x(2),x(11),x(10)) ++y(x(2),x(11),x(12)) ++y(x(2),x(11),x(13)) ++y(x(2),x(11),x(14)) ++y(x(2),x(11),x(15)) ++y(x(2),x(12),x(0)) ++y(x(2),x(12),x(1)) ++y(x(2),x(12),x(2)) ++y(x(2),x(12),x(3)) ++y(x(2),x(12),x(4)) ++y(x(2),x(12),x(5)) ++y(x(2),x(12),x(6)) ++y(x(2),x(12),x(7)) ++y(x(2),x(12),x(8)) ++y(x(2),x(12),x(9)) ++y(x(2),x(12),x(10)) ++y(x(2),x(12),x(11)) ++y(x(2),x(12),x(13)) ++y(x(2),x(12),x(14)) ++y(x(2),x(12),x(15)) ++y(x(2),x(13),x(0)) ++y(x(2),x(13),x(1)) ++y(x(2),x(13),x(2)) ++y(x(2),x(13),x(3)) ++y(x(2),x(13),x(4)) ++y(x(2),x(13),x(5)) ++y(x(2),x(13),x(6)) ++y(x(2),x(13),x(7)) ++y(x(2),x(13),x(8)) ++y(x(2),x(13),x(9)) ++y(x(2),x(13),x(10)) ++y(x(2),x(13),x(11)) ++y(x(2),x(13),x(12)) ++y(x(2),x(13),x(14)) ++y(x(2),x(13),x(15)) ++y(x(2),x(14),x(0)) ++y(x(2),x(14),x(1)) ++y(x(2),x(14),x(2)) ++y(x(2),x(14),x(3)) ++y(x(2),x(14),x(4)) ++y(x(2),x(14),x(5)) ++y(x(2),x(14),x(6)) ++y(x(2),x(14),x(7)) ++y(x(2),x(14),x(8)) ++y(x(2),x(14),x(9)) ++y(x(2),x(14),x(10)) ++y(x(2),x(14),x(11)) ++y(x(2),x(14),x(12)) ++y(x(2),x(14),x(13)) ++y(x(2),x(14),x(15)) ++y(x(3),x(0),x(1)) ++y(x(3),x(0),x(2)) ++y(x(3),x(0),x(3)) ++y(x(3),x(0),x(4)) ++y(x(3),x(0),x(5)) ++y(x(3),x(0),x(6)) ++y(x(3),x(0),x(7)) ++y(x(3),x(0),x(8)) ++y(x(3),x(0),x(9)) ++y(x(3),x(0),x(10)) ++y(x(3),x(0),x(11)) ++y(x(3),x(0),x(12)) ++y(x(3),x(0),x(13)) ++y(x(3),x(0),x(14)) ++y(x(3),x(0),x(15)) ++y(x(3),x(1),x(0)) ++y(x(3),x(1),x(2)) ++y(x(3),x(1),x(3)) ++y(x(3),x(1),x(4)) ++y(x(3),x(1),x(5)) ++y(x(3),x(1),x(6)) ++y(x(3),x(1),x(7)) ++y(x(3),x(1),x(8)) ++y(x(3),x(1),x(9)) ++y(x(3),x(1),x(10)) ++y(x(3),x(1),x(11)) ++y(x(3),x(1),x(12)) ++y(x(3),x(1),x(13)) ++y(x(3),x(1),x(14)) ++y(x(3),x(1),x(15)) ++y(x(3),x(2),x(0)) ++y(x(3),x(2),x(1)) ++y(x(3),x(2),x(3)) ++y(x(3),x(2),x(4)) ++y(x(3),x(2),x(5)) ++y(x(3),x(2),x(6)) ++y(x(3),x(2),x(7)) ++y(x(3),x(2),x(8)) ++y(x(3),x(2),x(9)) ++y(x(3),x(2),x(10)) ++y(x(3),x(2),x(11)) ++y(x(3),x(2),x(12)) ++y(x(3),x(2),x(13)) ++y(x(3),x(2),x(14)) ++y(x(3),x(2),x(15)) ++y(x(3),x(3),x(0)) ++y(x(3),x(3),x(1)) ++y(x(3),x(3),x(2)) ++y(x(3),x(3),x(3)) ++y(x(3),x(3),x(4)) ++y(x(3),x(3),x(5)) ++y(x(3),x(3),x(6)) ++y(x(3),x(3),x(7)) ++y(x(3),x(3),x(8)) ++y(x(3),x(3),x(9)) ++y(x(3),x(3),x(10)) ++y(x(3),x(3),x(11)) ++y(x(3),x(3),x(12)) ++y(x(3),x(3),x(13)) ++y(x(3),x(3),x(14)) ++y(x(3),x(3),x(15)) ++y(x(3),x(4),x(0)) ++y(x(3),x(4),x(1)) ++y(x(3),x(4),x(2)) ++y(x(3),x(4),x(3)) ++y(x(3),x(4),x(5)) ++y(x(3),x(4),x(6)) ++y(x(3),x(4),x(7)) ++y(x(3),x(4),x(8)) ++y(x(3),x(4),x(9)) ++y(x(3),x(4),x(10)) ++y(x(3),x(4),x(11)) ++y(x(3),x(4),x(12)) ++y(x(3),x(4),x(13)) ++y(x(3),x(4),x(14)) ++y(x(3),x(4),x(15)) ++y(x(3),x(5),x(0)) ++y(x(3),x(5),x(1)) ++y(x(3),x(5),x(2)) ++y(x(3),x(5),x(3)) ++y(x(3),x(5),x(4)) ++y(x(3),x(5),x(6)) ++y(x(3),x(5),x(7)) ++y(x(3),x(5),x(8)) ++y(x(3),x(5),x(9)) ++y(x(3),x(5),x(10)) ++y(x(3),x(5),x(11)) ++y(x(3),x(5),x(12)) ++y(x(3),x(5),x(13)) ++y(x(3),x(5),x(14)) ++y(x(3),x(5),x(15)) ++y(x(3),x(6),x(0)) ++y(x(3),x(6),x(1)) ++y(x(3),x(6),x(2)) ++y(x(3),x(6),x(3)) ++y(x(3),x(6),x(4)) ++y(x(3),x(6),x(5)) ++y(x(3),x(6),x(7)) ++y(x(3),x(6),x(8)) ++y(x(3),x(6),x(9)) ++y(x(3),x(6),x(10)) ++y(x(3),x(6),x(11)) ++y(x(3),x(6),x(12)) ++y(x(3),x(6),x(13)) ++y(x(3),x(6),x(14)) ++y(x(3),x(6),x(15)) ++y(x(3),x(7),x(0)) ++y(x(3),x(7),x(1)) ++y(x(3),x(7),x(2)) ++y(x(3),x(7),x(3)) ++y(x(3),x(7),x(4)) ++y(x(3),x(7),x(5)) ++y(x(3),x(7),x(6)) ++y(x(3),x(7),x(8)) ++y(x(3),x(7),x(9)) ++y(x(3),x(7),x(10)) ++y(x(3),x(7),x(11)) ++y(x(3),x(7),x(12)) ++y(x(3),x(7),x(13)) ++y(x(3),x(7),x(14)) ++y(x(3),x(7),x(15)) ++y(x(3),x(8),x(0)) ++y(x(3),x(8),x(1)) ++y(x(3),x(8),x(2)) ++y(x(3),x(8),x(3)) ++y(x(3),x(8),x(4)) ++y(x(3),x(8),x(5)) ++y(x(3),x(8),x(6)) ++y(x(3),x(8),x(7)) ++y(x(3),x(8),x(9)) ++y(x(3),x(8),x(10)) ++y(x(3),x(8),x(11)) ++y(x(3),x(8),x(12)) ++y(x(3),x(8),x(13)) ++y(x(3),x(8),x(14)) ++y(x(3),x(8),x(15)) ++y(x(3),x(9),x(0)) ++y(x(3),x(9),x(1)) ++y(x(3),x(9),x(2)) ++y(x(3),x(9),x(3)) ++y(x(3),x(9),x(4)) ++y(x(3),x(9),x(5)) ++y(x(3),x(9),x(6)) ++y(x(3),x(9),x(7)) ++y(x(3),x(9),x(8)) ++y(x(3),x(9),x(10)) ++y(x(3),x(9),x(11)) ++y(x(3),x(9),x(12)) ++y(x(3),x(9),x(13)) ++y(x(3),x(9),x(14)) ++y(x(3),x(9),x(15)) ++y(x(3),x(10),x(0)) ++y(x(3),x(10),x(1)) ++y(x(3),x(10),x(2)) ++y(x(3),x(10),x(3)) ++y(x(3),x(10),x(4)) ++y(x(3),x(10),x(5)) ++y(x(3),x(10),x(6)) ++y(x(3),x(10),x(7)) ++y(x(3),x(10),x(8)) ++y(x(3),x(10),x(9)) ++y(x(3),x(10),x(11)) ++y(x(3),x(10),x(12)) ++y(x(3),x(10),x(13)) ++y(x(3),x(10),x(14)) ++y(x(3),x(10),x(15)) ++y(x(3),x(11),x(0)) ++y(x(3),x(11),x(1)) ++y(x(3),x(11),x(2)) ++y(x(3),x(11),x(3)) ++y(x(3),x(11),x(4)) ++y(x(3),x(11),x(5)) ++y(x(3),x(11),x(6)) ++y(x(3),x(11),x(7)) ++y(x(3),x(11),x(8)) ++y(x(3),x(11),x(9)) ++y(x(3),x(11),x(10)) ++y(x(3),x(11),x(12)) ++y(x(3),x(11),x(13)) ++y(x(3),x(11),x(14)) ++y(x(3),x(11),x(15)) ++y(x(3),x(12),x(0)) ++y(x(3),x(12),x(1)) ++y(x(3),x(12),x(2)) ++y(x(3),x(12),x(3)) ++y(x(3),x(12),x(4)) ++y(x(3),x(12),x(5)) ++y(x(3),x(12),x(6)) ++y(x(3),x(12),x(7)) ++y(x(3),x(12),x(8)) ++y(x(3),x(12),x(9)) ++y(x(3),x(12),x(10)) ++y(x(3),x(12),x(11)) ++y(x(3),x(12),x(13)) ++y(x(3),x(12),x(14)) ++y(x(3),x(12),x(15)) ++y(x(3),x(13),x(0)) ++y(x(3),x(13),x(1)) ++y(x(3),x(13),x(2)) ++y(x(3),x(13),x(3)) ++y(x(3),x(13),x(4)) ++y(x(3),x(13),x(5)) ++y(x(3),x(13),x(6)) ++y(x(3),x(13),x(7)) ++y(x(3),x(13),x(8)) ++y(x(3),x(13),x(9)) ++y(x(3),x(13),x(10)) ++y(x(3),x(13),x(11)) ++y(x(3),x(13),x(12)) ++y(x(3),x(13),x(14)) ++y(x(3),x(13),x(15)) ++y(x(3),x(14),x(0)) ++y(x(3),x(14),x(1)) ++y(x(3),x(14),x(2)) ++y(x(3),x(14),x(3)) ++y(x(3),x(14),x(4)) ++y(x(3),x(14),x(5)) ++y(x(3),x(14),x(6)) ++y(x(3),x(14),x(7)) ++y(x(3),x(14),x(8)) ++y(x(3),x(14),x(9)) ++y(x(3),x(14),x(10)) ++y(x(3),x(14),x(11)) ++y(x(3),x(14),x(12)) ++y(x(3),x(14),x(13)) ++y(x(3),x(14),x(15)) ++y(x(4),x(0),x(1)) ++y(x(4),x(0),x(2)) ++y(x(4),x(0),x(3)) ++y(x(4),x(0),x(4)) ++y(x(4),x(0),x(5)) ++y(x(4),x(0),x(6)) ++y(x(4),x(0),x(7)) ++y(x(4),x(0),x(8)) ++y(x(4),x(0),x(9)) ++y(x(4),x(0),x(10)) ++y(x(4),x(0),x(11)) ++y(x(4),x(0),x(12)) ++y(x(4),x(0),x(13)) ++y(x(4),x(0),x(14)) ++y(x(4),x(0),x(15)) ++y(x(4),x(1),x(0)) ++y(x(4),x(1),x(2)) ++y(x(4),x(1),x(3)) ++y(x(4),x(1),x(4)) ++y(x(4),x(1),x(5)) ++y(x(4),x(1),x(6)) ++y(x(4),x(1),x(7)) ++y(x(4),x(1),x(8)) ++y(x(4),x(1),x(9)) ++y(x(4),x(1),x(10)) ++y(x(4),x(1),x(11)) ++y(x(4),x(1),x(12)) ++y(x(4),x(1),x(13)) ++y(x(4),x(1),x(14)) ++y(x(4),x(1),x(15)) ++y(x(4),x(2),x(0)) ++y(x(4),x(2),x(1)) ++y(x(4),x(2),x(3)) ++y(x(4),x(2),x(4)) ++y(x(4),x(2),x(5)) ++y(x(4),x(2),x(6)) ++y(x(4),x(2),x(7)) ++y(x(4),x(2),x(8)) ++y(x(4),x(2),x(9)) ++y(x(4),x(2),x(10)) ++y(x(4),x(2),x(11)) ++y(x(4),x(2),x(12)) ++y(x(4),x(2),x(13)) ++y(x(4),x(2),x(14)) ++y(x(4),x(2),x(15)) ++y(x(4),x(3),x(0)) ++y(x(4),x(3),x(1)) ++y(x(4),x(3),x(2)) ++y(x(4),x(3),x(4)) ++y(x(4),x(3),x(5)) ++y(x(4),x(3),x(6)) ++y(x(4),x(3),x(7)) ++y(x(4),x(3),x(8)) ++y(x(4),x(3),x(9)) ++y(x(4),x(3),x(10)) ++y(x(4),x(3),x(11)) ++y(x(4),x(3),x(12)) ++y(x(4),x(3),x(13)) ++y(x(4),x(3),x(14)) ++y(x(4),x(3),x(15)) ++y(x(4),x(4),x(0)) ++y(x(4),x(4),x(1)) ++y(x(4),x(4),x(2)) ++y(x(4),x(4),x(3)) ++y(x(4),x(4),x(4)) ++y(x(4),x(4),x(5)) ++y(x(4),x(4),x(6)) ++y(x(4),x(4),x(7)) ++y(x(4),x(4),x(8)) ++y(x(4),x(4),x(9)) ++y(x(4),x(4),x(10)) ++y(x(4),x(4),x(11)) ++y(x(4),x(4),x(12)) ++y(x(4),x(4),x(13)) ++y(x(4),x(4),x(14)) ++y(x(4),x(4),x(15)) ++y(x(4),x(5),x(0)) ++y(x(4),x(5),x(1)) ++y(x(4),x(5),x(2)) ++y(x(4),x(5),x(3)) ++y(x(4),x(5),x(4)) ++y(x(4),x(5),x(6)) ++y(x(4),x(5),x(7)) ++y(x(4),x(5),x(8)) ++y(x(4),x(5),x(9)) ++y(x(4),x(5),x(10)) ++y(x(4),x(5),x(11)) ++y(x(4),x(5),x(12)) ++y(x(4),x(5),x(13)) ++y(x(4),x(5),x(14)) ++y(x(4),x(5),x(15)) ++y(x(4),x(6),x(0)) ++y(x(4),x(6),x(1)) ++y(x(4),x(6),x(2)) ++y(x(4),x(6),x(3)) ++y(x(4),x(6),x(4)) ++y(x(4),x(6),x(5)) ++y(x(4),x(6),x(7)) ++y(x(4),x(6),x(8)) ++y(x(4),x(6),x(9)) ++y(x(4),x(6),x(10)) ++y(x(4),x(6),x(11)) ++y(x(4),x(6),x(12)) ++y(x(4),x(6),x(13)) ++y(x(4),x(6),x(14)) ++y(x(4),x(6),x(15)) ++y(x(4),x(7),x(0)) ++y(x(4),x(7),x(1)) ++y(x(4),x(7),x(2)) ++y(x(4),x(7),x(3)) ++y(x(4),x(7),x(4)) ++y(x(4),x(7),x(5)) ++y(x(4),x(7),x(6)) ++y(x(4),x(7),x(8)) ++y(x(4),x(7),x(9)) ++y(x(4),x(7),x(10)) ++y(x(4),x(7),x(11)) ++y(x(4),x(7),x(12)) ++y(x(4),x(7),x(13)) ++y(x(4),x(7),x(14)) ++y(x(4),x(7),x(15)) ++y(x(4),x(8),x(0)) ++y(x(4),x(8),x(1)) ++y(x(4),x(8),x(2)) ++y(x(4),x(8),x(3)) ++y(x(4),x(8),x(4)) ++y(x(4),x(8),x(5)) ++y(x(4),x(8),x(6)) ++y(x(4),x(8),x(7)) ++y(x(4),x(8),x(9)) ++y(x(4),x(8),x(10)) ++y(x(4),x(8),x(11)) ++y(x(4),x(8),x(12)) ++y(x(4),x(8),x(13)) ++y(x(4),x(8),x(14)) ++y(x(4),x(8),x(15)) ++y(x(4),x(9),x(0)) ++y(x(4),x(9),x(1)) ++y(x(4),x(9),x(2)) ++y(x(4),x(9),x(3)) ++y(x(4),x(9),x(4)) ++y(x(4),x(9),x(5)) ++y(x(4),x(9),x(6)) ++y(x(4),x(9),x(7)) ++y(x(4),x(9),x(8)) ++y(x(4),x(9),x(10)) ++y(x(4),x(9),x(11)) ++y(x(4),x(9),x(12)) ++y(x(4),x(9),x(13)) ++y(x(4),x(9),x(14)) ++y(x(4),x(9),x(15)) ++y(x(4),x(10),x(0)) ++y(x(4),x(10),x(1)) ++y(x(4),x(10),x(2)) ++y(x(4),x(10),x(3)) ++y(x(4),x(10),x(4)) ++y(x(4),x(10),x(5)) ++y(x(4),x(10),x(6)) ++y(x(4),x(10),x(7)) ++y(x(4),x(10),x(8)) ++y(x(4),x(10),x(9)) ++y(x(4),x(10),x(11)) ++y(x(4),x(10),x(12)) ++y(x(4),x(10),x(13)) ++y(x(4),x(10),x(14)) ++y(x(4),x(10),x(15)) ++y(x(4),x(11),x(0)) ++y(x(4),x(11),x(1)) ++y(x(4),x(11),x(2)) ++y(x(4),x(11),x(3)) ++y(x(4),x(11),x(4)) ++y(x(4),x(11),x(5)) ++y(x(4),x(11),x(6)) ++y(x(4),x(11),x(7)) ++y(x(4),x(11),x(8)) ++y(x(4),x(11),x(9)) ++y(x(4),x(11),x(10)) ++y(x(4),x(11),x(12)) ++y(x(4),x(11),x(13)) ++y(x(4),x(11),x(14)) ++y(x(4),x(11),x(15)) ++y(x(4),x(12),x(0)) ++y(x(4),x(12),x(1)) ++y(x(4),x(12),x(2)) ++y(x(4),x(12),x(3)) ++y(x(4),x(12),x(4)) ++y(x(4),x(12),x(5)) ++y(x(4),x(12),x(6)) ++y(x(4),x(12),x(7)) ++y(x(4),x(12),x(8)) ++y(x(4),x(12),x(9)) ++y(x(4),x(12),x(10)) ++y(x(4),x(12),x(11)) ++y(x(4),x(12),x(13)) ++y(x(4),x(12),x(14)) ++y(x(4),x(12),x(15)) ++y(x(4),x(13),x(0)) ++y(x(4),x(13),x(1)) ++y(x(4),x(13),x(2)) ++y(x(4),x(13),x(3)) ++y(x(4),x(13),x(4)) ++y(x(4),x(13),x(5)) ++y(x(4),x(13),x(6)) ++y(x(4),x(13),x(7)) ++y(x(4),x(13),x(8)) ++y(x(4),x(13),x(9)) ++y(x(4),x(13),x(10)) ++y(x(4),x(13),x(11)) ++y(x(4),x(13),x(12)) ++y(x(4),x(13),x(14)) ++y(x(4),x(13),x(15)) ++y(x(4),x(14),x(0)) ++y(x(4),x(14),x(1)) ++y(x(4),x(14),x(2)) ++y(x(4),x(14),x(3)) ++y(x(4),x(14),x(4)) ++y(x(4),x(14),x(5)) ++y(x(4),x(14),x(6)) ++y(x(4),x(14),x(7)) ++y(x(4),x(14),x(8)) ++y(x(4),x(14),x(9)) ++y(x(4),x(14),x(10)) ++y(x(4),x(14),x(11)) ++y(x(4),x(14),x(12)) ++y(x(4),x(14),x(13)) ++y(x(4),x(14),x(15)) ++y(x(5),x(0),x(1)) ++y(x(5),x(0),x(2)) ++y(x(5),x(0),x(3)) ++y(x(5),x(0),x(4)) ++y(x(5),x(0),x(5)) ++y(x(5),x(0),x(6)) ++y(x(5),x(0),x(7)) ++y(x(5),x(0),x(8)) ++y(x(5),x(0),x(9)) ++y(x(5),x(0),x(10)) ++y(x(5),x(0),x(11)) ++y(x(5),x(0),x(12)) ++y(x(5),x(0),x(13)) ++y(x(5),x(0),x(14)) ++y(x(5),x(0),x(15)) ++y(x(5),x(1),x(0)) ++y(x(5),x(1),x(2)) ++y(x(5),x(1),x(3)) ++y(x(5),x(1),x(4)) ++y(x(5),x(1),x(5)) ++y(x(5),x(1),x(6)) ++y(x(5),x(1),x(7)) ++y(x(5),x(1),x(8)) ++y(x(5),x(1),x(9)) ++y(x(5),x(1),x(10)) ++y(x(5),x(1),x(11)) ++y(x(5),x(1),x(12)) ++y(x(5),x(1),x(13)) ++y(x(5),x(1),x(14)) ++y(x(5),x(1),x(15)) ++y(x(5),x(2),x(0)) ++y(x(5),x(2),x(1)) ++y(x(5),x(2),x(3)) ++y(x(5),x(2),x(4)) ++y(x(5),x(2),x(5)) ++y(x(5),x(2),x(6)) ++y(x(5),x(2),x(7)) ++y(x(5),x(2),x(8)) ++y(x(5),x(2),x(9)) ++y(x(5),x(2),x(10)) ++y(x(5),x(2),x(11)) ++y(x(5),x(2),x(12)) ++y(x(5),x(2),x(13)) ++y(x(5),x(2),x(14)) ++y(x(5),x(2),x(15)) ++y(x(5),x(3),x(0)) ++y(x(5),x(3),x(1)) ++y(x(5),x(3),x(2)) ++y(x(5),x(3),x(4)) ++y(x(5),x(3),x(5)) ++y(x(5),x(3),x(6)) ++y(x(5),x(3),x(7)) ++y(x(5),x(3),x(8)) ++y(x(5),x(3),x(9)) ++y(x(5),x(3),x(10)) ++y(x(5),x(3),x(11)) ++y(x(5),x(3),x(12)) ++y(x(5),x(3),x(13)) ++y(x(5),x(3),x(14)) ++y(x(5),x(3),x(15)) ++y(x(5),x(4),x(0)) ++y(x(5),x(4),x(1)) ++y(x(5),x(4),x(2)) ++y(x(5),x(4),x(3)) ++y(x(5),x(4),x(5)) ++y(x(5),x(4),x(6)) ++y(x(5),x(4),x(7)) ++y(x(5),x(4),x(8)) ++y(x(5),x(4),x(9)) ++y(x(5),x(4),x(10)) ++y(x(5),x(4),x(11)) ++y(x(5),x(4),x(12)) ++y(x(5),x(4),x(13)) ++y(x(5),x(4),x(14)) ++y(x(5),x(4),x(15)) ++y(x(5),x(5),x(0)) ++y(x(5),x(5),x(1)) ++y(x(5),x(5),x(2)) ++y(x(5),x(5),x(3)) ++y(x(5),x(5),x(4)) ++y(x(5),x(5),x(5)) ++y(x(5),x(5),x(6)) ++y(x(5),x(5),x(7)) ++y(x(5),x(5),x(8)) ++y(x(5),x(5),x(9)) ++y(x(5),x(5),x(10)) ++y(x(5),x(5),x(11)) ++y(x(5),x(5),x(12)) ++y(x(5),x(5),x(13)) ++y(x(5),x(5),x(14)) ++y(x(5),x(5),x(15)) ++y(x(5),x(6),x(0)) ++y(x(5),x(6),x(1)) ++y(x(5),x(6),x(2)) ++y(x(5),x(6),x(3)) ++y(x(5),x(6),x(4)) ++y(x(5),x(6),x(5)) ++y(x(5),x(6),x(7)) ++y(x(5),x(6),x(8)) ++y(x(5),x(6),x(9)) ++y(x(5),x(6),x(10)) ++y(x(5),x(6),x(11)) ++y(x(5),x(6),x(12)) ++y(x(5),x(6),x(13)) ++y(x(5),x(6),x(14)) ++y(x(5),x(6),x(15)) ++y(x(5),x(7),x(0)) ++y(x(5),x(7),x(1)) ++y(x(5),x(7),x(2)) ++y(x(5),x(7),x(3)) ++y(x(5),x(7),x(4)) ++y(x(5),x(7),x(5)) ++y(x(5),x(7),x(6)) ++y(x(5),x(7),x(8)) ++y(x(5),x(7),x(9)) ++y(x(5),x(7),x(10)) ++y(x(5),x(7),x(11)) ++y(x(5),x(7),x(12)) ++y(x(5),x(7),x(13)) ++y(x(5),x(7),x(14)) ++y(x(5),x(7),x(15)) ++y(x(5),x(8),x(0)) ++y(x(5),x(8),x(1)) ++y(x(5),x(8),x(2)) ++y(x(5),x(8),x(3)) ++y(x(5),x(8),x(4)) ++y(x(5),x(8),x(5)) ++y(x(5),x(8),x(6)) ++y(x(5),x(8),x(7)) ++y(x(5),x(8),x(9)) ++y(x(5),x(8),x(10)) ++y(x(5),x(8),x(11)) ++y(x(5),x(8),x(12)) ++y(x(5),x(8),x(13)) ++y(x(5),x(8),x(14)) ++y(x(5),x(8),x(15)) ++y(x(5),x(9),x(0)) ++y(x(5),x(9),x(1)) ++y(x(5),x(9),x(2)) ++y(x(5),x(9),x(3)) ++y(x(5),x(9),x(4)) ++y(x(5),x(9),x(5)) ++y(x(5),x(9),x(6)) ++y(x(5),x(9),x(7)) ++y(x(5),x(9),x(8)) ++y(x(5),x(9),x(10)) ++y(x(5),x(9),x(11)) ++y(x(5),x(9),x(12)) ++y(x(5),x(9),x(13)) ++y(x(5),x(9),x(14)) ++y(x(5),x(9),x(15)) ++y(x(5),x(10),x(0)) ++y(x(5),x(10),x(1)) ++y(x(5),x(10),x(2)) ++y(x(5),x(10),x(3)) ++y(x(5),x(10),x(4)) ++y(x(5),x(10),x(5)) ++y(x(5),x(10),x(6)) ++y(x(5),x(10),x(7)) ++y(x(5),x(10),x(8)) ++y(x(5),x(10),x(9)) ++y(x(5),x(10),x(11)) ++y(x(5),x(10),x(12)) ++y(x(5),x(10),x(13)) ++y(x(5),x(10),x(14)) ++y(x(5),x(10),x(15)) ++y(x(5),x(11),x(0)) ++y(x(5),x(11),x(1)) ++y(x(5),x(11),x(2)) ++y(x(5),x(11),x(3)) ++y(x(5),x(11),x(4)) ++y(x(5),x(11),x(5)) ++y(x(5),x(11),x(6)) ++y(x(5),x(11),x(7)) ++y(x(5),x(11),x(8)) ++y(x(5),x(11),x(9)) ++y(x(5),x(11),x(10)) ++y(x(5),x(11),x(12)) ++y(x(5),x(11),x(13)) ++y(x(5),x(11),x(14)) ++y(x(5),x(11),x(15)) ++y(x(5),x(12),x(0)) ++y(x(5),x(12),x(1)) ++y(x(5),x(12),x(2)) ++y(x(5),x(12),x(3)) ++y(x(5),x(12),x(4)) ++y(x(5),x(12),x(5)) ++y(x(5),x(12),x(6)) ++y(x(5),x(12),x(7)) ++y(x(5),x(12),x(8)) ++y(x(5),x(12),x(9)) ++y(x(5),x(12),x(10)) ++y(x(5),x(12),x(11)) ++y(x(5),x(12),x(13)) ++y(x(5),x(12),x(14)) ++y(x(5),x(12),x(15)) ++y(x(5),x(13),x(0)) ++y(x(5),x(13),x(1)) ++y(x(5),x(13),x(2)) ++y(x(5),x(13),x(3)) ++y(x(5),x(13),x(4)) ++y(x(5),x(13),x(5)) ++y(x(5),x(13),x(6)) ++y(x(5),x(13),x(7)) ++y(x(5),x(13),x(8)) ++y(x(5),x(13),x(9)) ++y(x(5),x(13),x(10)) ++y(x(5),x(13),x(11)) ++y(x(5),x(13),x(12)) ++y(x(5),x(13),x(14)) ++y(x(5),x(13),x(15)) ++y(x(5),x(14),x(0)) ++y(x(5),x(14),x(1)) ++y(x(5),x(14),x(2)) ++y(x(5),x(14),x(3)) ++y(x(5),x(14),x(4)) ++y(x(5),x(14),x(5)) ++y(x(5),x(14),x(6)) ++y(x(5),x(14),x(7)) ++y(x(5),x(14),x(8)) ++y(x(5),x(14),x(9)) ++y(x(5),x(14),x(10)) ++y(x(5),x(14),x(11)) ++y(x(5),x(14),x(12)) ++y(x(5),x(14),x(13)) ++y(x(5),x(14),x(15)) ++y(x(6),x(0),x(1)) ++y(x(6),x(0),x(2)) ++y(x(6),x(0),x(3)) ++y(x(6),x(0),x(4)) ++y(x(6),x(0),x(5)) ++y(x(6),x(0),x(6)) ++y(x(6),x(0),x(7)) ++y(x(6),x(0),x(8)) ++y(x(6),x(0),x(9)) ++y(x(6),x(0),x(10)) ++y(x(6),x(0),x(11)) ++y(x(6),x(0),x(12)) ++y(x(6),x(0),x(13)) ++y(x(6),x(0),x(14)) ++y(x(6),x(0),x(15)) ++y(x(6),x(1),x(0)) ++y(x(6),x(1),x(2)) ++y(x(6),x(1),x(3)) ++y(x(6),x(1),x(4)) ++y(x(6),x(1),x(5)) ++y(x(6),x(1),x(6)) ++y(x(6),x(1),x(7)) ++y(x(6),x(1),x(8)) ++y(x(6),x(1),x(9)) ++y(x(6),x(1),x(10)) ++y(x(6),x(1),x(11)) ++y(x(6),x(1),x(12)) ++y(x(6),x(1),x(13)) ++y(x(6),x(1),x(14)) ++y(x(6),x(1),x(15)) ++y(x(6),x(2),x(0)) ++y(x(6),x(2),x(1)) ++y(x(6),x(2),x(3)) ++y(x(6),x(2),x(4)) ++y(x(6),x(2),x(5)) ++y(x(6),x(2),x(6)) ++y(x(6),x(2),x(7)) ++y(x(6),x(2),x(8)) ++y(x(6),x(2),x(9)) ++y(x(6),x(2),x(10)) ++y(x(6),x(2),x(11)) ++y(x(6),x(2),x(12)) ++y(x(6),x(2),x(13)) ++y(x(6),x(2),x(14)) ++y(x(6),x(2),x(15)) ++y(x(6),x(3),x(0)) ++y(x(6),x(3),x(1)) ++y(x(6),x(3),x(2)) ++y(x(6),x(3),x(4)) ++y(x(6),x(3),x(5)) ++y(x(6),x(3),x(6)) ++y(x(6),x(3),x(7)) ++y(x(6),x(3),x(8)) ++y(x(6),x(3),x(9)) ++y(x(6),x(3),x(10)) ++y(x(6),x(3),x(11)) ++y(x(6),x(3),x(12)) ++y(x(6),x(3),x(13)) ++y(x(6),x(3),x(14)) ++y(x(6),x(3),x(15)) ++y(x(6),x(4),x(0)) ++y(x(6),x(4),x(1)) ++y(x(6),x(4),x(2)) ++y(x(6),x(4),x(3)) ++y(x(6),x(4),x(5)) ++y(x(6),x(4),x(6)) ++y(x(6),x(4),x(7)) ++y(x(6),x(4),x(8)) ++y(x(6),x(4),x(9)) ++y(x(6),x(4),x(10)) ++y(x(6),x(4),x(11)) ++y(x(6),x(4),x(12)) ++y(x(6),x(4),x(13)) ++y(x(6),x(4),x(14)) ++y(x(6),x(4),x(15)) ++y(x(6),x(5),x(0)) ++y(x(6),x(5),x(1)) ++y(x(6),x(5),x(2)) ++y(x(6),x(5),x(3)) ++y(x(6),x(5),x(4)) ++y(x(6),x(5),x(6)) ++y(x(6),x(5),x(7)) ++y(x(6),x(5),x(8)) ++y(x(6),x(5),x(9)) ++y(x(6),x(5),x(10)) ++y(x(6),x(5),x(11)) ++y(x(6),x(5),x(12)) ++y(x(6),x(5),x(13)) ++y(x(6),x(5),x(14)) ++y(x(6),x(5),x(15)) ++y(x(6),x(6),x(0)) ++y(x(6),x(6),x(1)) ++y(x(6),x(6),x(2)) ++y(x(6),x(6),x(3)) ++y(x(6),x(6),x(4)) ++y(x(6),x(6),x(5)) ++y(x(6),x(6),x(6)) ++y(x(6),x(6),x(7)) ++y(x(6),x(6),x(8)) ++y(x(6),x(6),x(9)) ++y(x(6),x(6),x(10)) ++y(x(6),x(6),x(11)) ++y(x(6),x(6),x(12)) ++y(x(6),x(6),x(13)) ++y(x(6),x(6),x(14)) ++y(x(6),x(6),x(15)) ++y(x(6),x(7),x(0)) ++y(x(6),x(7),x(1)) ++y(x(6),x(7),x(2)) ++y(x(6),x(7),x(3)) ++y(x(6),x(7),x(4)) ++y(x(6),x(7),x(5)) ++y(x(6),x(7),x(6)) ++y(x(6),x(7),x(8)) ++y(x(6),x(7),x(9)) ++y(x(6),x(7),x(10)) ++y(x(6),x(7),x(11)) ++y(x(6),x(7),x(12)) ++y(x(6),x(7),x(13)) ++y(x(6),x(7),x(14)) ++y(x(6),x(7),x(15)) ++y(x(6),x(8),x(0)) ++y(x(6),x(8),x(1)) ++y(x(6),x(8),x(2)) ++y(x(6),x(8),x(3)) ++y(x(6),x(8),x(4)) ++y(x(6),x(8),x(5)) ++y(x(6),x(8),x(6)) ++y(x(6),x(8),x(7)) ++y(x(6),x(8),x(9)) ++y(x(6),x(8),x(10)) ++y(x(6),x(8),x(11)) ++y(x(6),x(8),x(12)) ++y(x(6),x(8),x(13)) ++y(x(6),x(8),x(14)) ++y(x(6),x(8),x(15)) ++y(x(6),x(9),x(0)) ++y(x(6),x(9),x(1)) ++y(x(6),x(9),x(2)) ++y(x(6),x(9),x(3)) ++y(x(6),x(9),x(4)) ++y(x(6),x(9),x(5)) ++y(x(6),x(9),x(6)) ++y(x(6),x(9),x(7)) ++y(x(6),x(9),x(8)) ++y(x(6),x(9),x(10)) ++y(x(6),x(9),x(11)) ++y(x(6),x(9),x(12)) ++y(x(6),x(9),x(13)) ++y(x(6),x(9),x(14)) ++y(x(6),x(9),x(15)) ++y(x(6),x(10),x(0)) ++y(x(6),x(10),x(1)) ++y(x(6),x(10),x(2)) ++y(x(6),x(10),x(3)) ++y(x(6),x(10),x(4)) ++y(x(6),x(10),x(5)) ++y(x(6),x(10),x(6)) ++y(x(6),x(10),x(7)) ++y(x(6),x(10),x(8)) ++y(x(6),x(10),x(9)) ++y(x(6),x(10),x(11)) ++y(x(6),x(10),x(12)) ++y(x(6),x(10),x(13)) ++y(x(6),x(10),x(14)) ++y(x(6),x(10),x(15)) ++y(x(6),x(11),x(0)) ++y(x(6),x(11),x(1)) ++y(x(6),x(11),x(2)) ++y(x(6),x(11),x(3)) ++y(x(6),x(11),x(4)) ++y(x(6),x(11),x(5)) ++y(x(6),x(11),x(6)) ++y(x(6),x(11),x(7)) ++y(x(6),x(11),x(8)) ++y(x(6),x(11),x(9)) ++y(x(6),x(11),x(10)) ++y(x(6),x(11),x(12)) ++y(x(6),x(11),x(13)) ++y(x(6),x(11),x(14)) ++y(x(6),x(11),x(15)) ++y(x(6),x(12),x(0)) ++y(x(6),x(12),x(1)) ++y(x(6),x(12),x(2)) ++y(x(6),x(12),x(3)) ++y(x(6),x(12),x(4)) ++y(x(6),x(12),x(5)) ++y(x(6),x(12),x(6)) ++y(x(6),x(12),x(7)) ++y(x(6),x(12),x(8)) ++y(x(6),x(12),x(9)) ++y(x(6),x(12),x(10)) ++y(x(6),x(12),x(11)) ++y(x(6),x(12),x(13)) ++y(x(6),x(12),x(14)) ++y(x(6),x(12),x(15)) ++y(x(6),x(13),x(0)) ++y(x(6),x(13),x(1)) ++y(x(6),x(13),x(2)) ++y(x(6),x(13),x(3)) ++y(x(6),x(13),x(4)) ++y(x(6),x(13),x(5)) ++y(x(6),x(13),x(6)) ++y(x(6),x(13),x(7)) ++y(x(6),x(13),x(8)) ++y(x(6),x(13),x(9)) ++y(x(6),x(13),x(10)) ++y(x(6),x(13),x(11)) ++y(x(6),x(13),x(12)) ++y(x(6),x(13),x(14)) ++y(x(6),x(13),x(15)) ++y(x(6),x(14),x(0)) ++y(x(6),x(14),x(1)) ++y(x(6),x(14),x(2)) ++y(x(6),x(14),x(3)) ++y(x(6),x(14),x(4)) ++y(x(6),x(14),x(5)) ++y(x(6),x(14),x(6)) ++y(x(6),x(14),x(7)) ++y(x(6),x(14),x(8)) ++y(x(6),x(14),x(9)) ++y(x(6),x(14),x(10)) ++y(x(6),x(14),x(11)) ++y(x(6),x(14),x(12)) ++y(x(6),x(14),x(13)) ++y(x(6),x(14),x(15)) ++y(x(7),x(0),x(1)) ++y(x(7),x(0),x(2)) ++y(x(7),x(0),x(3)) ++y(x(7),x(0),x(4)) ++y(x(7),x(0),x(5)) ++y(x(7),x(0),x(6)) ++y(x(7),x(0),x(7)) ++y(x(7),x(0),x(8)) ++y(x(7),x(0),x(9)) ++y(x(7),x(0),x(10)) ++y(x(7),x(0),x(11)) ++y(x(7),x(0),x(12)) ++y(x(7),x(0),x(13)) ++y(x(7),x(0),x(14)) ++y(x(7),x(0),x(15)) ++y(x(7),x(1),x(0)) ++y(x(7),x(1),x(2)) ++y(x(7),x(1),x(3)) ++y(x(7),x(1),x(4)) ++y(x(7),x(1),x(5)) ++y(x(7),x(1),x(6)) ++y(x(7),x(1),x(7)) ++y(x(7),x(1),x(8)) ++y(x(7),x(1),x(9)) ++y(x(7),x(1),x(10)) ++y(x(7),x(1),x(11)) ++y(x(7),x(1),x(12)) ++y(x(7),x(1),x(13)) ++y(x(7),x(1),x(14)) ++y(x(7),x(1),x(15)) ++y(x(7),x(2),x(0)) ++y(x(7),x(2),x(1)) ++y(x(7),x(2),x(3)) ++y(x(7),x(2),x(4)) ++y(x(7),x(2),x(5)) ++y(x(7),x(2),x(6)) ++y(x(7),x(2),x(7)) ++y(x(7),x(2),x(8)) ++y(x(7),x(2),x(9)) ++y(x(7),x(2),x(10)) ++y(x(7),x(2),x(11)) ++y(x(7),x(2),x(12)) ++y(x(7),x(2),x(13)) ++y(x(7),x(2),x(14)) ++y(x(7),x(2),x(15)) ++y(x(7),x(3),x(0)) ++y(x(7),x(3),x(1)) ++y(x(7),x(3),x(2)) ++y(x(7),x(3),x(4)) ++y(x(7),x(3),x(5)) ++y(x(7),x(3),x(6)) ++y(x(7),x(3),x(7)) ++y(x(7),x(3),x(8)) ++y(x(7),x(3),x(9)) ++y(x(7),x(3),x(10)) ++y(x(7),x(3),x(11)) ++y(x(7),x(3),x(12)) ++y(x(7),x(3),x(13)) ++y(x(7),x(3),x(14)) ++y(x(7),x(3),x(15)) ++y(x(7),x(4),x(0)) ++y(x(7),x(4),x(1)) ++y(x(7),x(4),x(2)) ++y(x(7),x(4),x(3)) ++y(x(7),x(4),x(5)) ++y(x(7),x(4),x(6)) ++y(x(7),x(4),x(7)) ++y(x(7),x(4),x(8)) ++y(x(7),x(4),x(9)) ++y(x(7),x(4),x(10)) ++y(x(7),x(4),x(11)) ++y(x(7),x(4),x(12)) ++y(x(7),x(4),x(13)) ++y(x(7),x(4),x(14)) ++y(x(7),x(4),x(15)) ++y(x(7),x(5),x(0)) ++y(x(7),x(5),x(1)) ++y(x(7),x(5),x(2)) ++y(x(7),x(5),x(3)) ++y(x(7),x(5),x(4)) ++y(x(7),x(5),x(6)) ++y(x(7),x(5),x(7)) ++y(x(7),x(5),x(8)) ++y(x(7),x(5),x(9)) ++y(x(7),x(5),x(10)) ++y(x(7),x(5),x(11)) ++y(x(7),x(5),x(12)) ++y(x(7),x(5),x(13)) ++y(x(7),x(5),x(14)) ++y(x(7),x(5),x(15)) ++y(x(7),x(6),x(0)) ++y(x(7),x(6),x(1)) ++y(x(7),x(6),x(2)) ++y(x(7),x(6),x(3)) ++y(x(7),x(6),x(4)) ++y(x(7),x(6),x(5)) ++y(x(7),x(6),x(7)) ++y(x(7),x(6),x(8)) ++y(x(7),x(6),x(9)) ++y(x(7),x(6),x(10)) ++y(x(7),x(6),x(11)) ++y(x(7),x(6),x(12)) ++y(x(7),x(6),x(13)) ++y(x(7),x(6),x(14)) ++y(x(7),x(6),x(15)) ++y(x(7),x(7),x(0)) ++y(x(7),x(7),x(1)) ++y(x(7),x(7),x(2)) ++y(x(7),x(7),x(3)) ++y(x(7),x(7),x(4)) ++y(x(7),x(7),x(5)) ++y(x(7),x(7),x(6)) ++y(x(7),x(7),x(7)) ++y(x(7),x(7),x(8)) ++y(x(7),x(7),x(9)) ++y(x(7),x(7),x(10)) ++y(x(7),x(7),x(11)) ++y(x(7),x(7),x(12)) ++y(x(7),x(7),x(13)) ++y(x(7),x(7),x(14)) ++y(x(7),x(7),x(15)) ++y(x(7),x(8),x(0)) ++y(x(7),x(8),x(1)) ++y(x(7),x(8),x(2)) ++y(x(7),x(8),x(3)) ++y(x(7),x(8),x(4)) ++y(x(7),x(8),x(5)) ++y(x(7),x(8),x(6)) ++y(x(7),x(8),x(7)) ++y(x(7),x(8),x(9)) ++y(x(7),x(8),x(10)) ++y(x(7),x(8),x(11)) ++y(x(7),x(8),x(12)) ++y(x(7),x(8),x(13)) ++y(x(7),x(8),x(14)) ++y(x(7),x(8),x(15)) ++y(x(7),x(9),x(0)) ++y(x(7),x(9),x(1)) ++y(x(7),x(9),x(2)) ++y(x(7),x(9),x(3)) ++y(x(7),x(9),x(4)) ++y(x(7),x(9),x(5)) ++y(x(7),x(9),x(6)) ++y(x(7),x(9),x(7)) ++y(x(7),x(9),x(8)) ++y(x(7),x(9),x(10)) ++y(x(7),x(9),x(11)) ++y(x(7),x(9),x(12)) ++y(x(7),x(9),x(13)) ++y(x(7),x(9),x(14)) ++y(x(7),x(9),x(15)) ++y(x(7),x(10),x(0)) ++y(x(7),x(10),x(1)) ++y(x(7),x(10),x(2)) ++y(x(7),x(10),x(3)) ++y(x(7),x(10),x(4)) ++y(x(7),x(10),x(5)) ++y(x(7),x(10),x(6)) ++y(x(7),x(10),x(7)) ++y(x(7),x(10),x(8)) ++y(x(7),x(10),x(9)) ++y(x(7),x(10),x(11)) ++y(x(7),x(10),x(12)) ++y(x(7),x(10),x(13)) ++y(x(7),x(10),x(14)) ++y(x(7),x(10),x(15)) ++y(x(7),x(11),x(0)) ++y(x(7),x(11),x(1)) ++y(x(7),x(11),x(2)) ++y(x(7),x(11),x(3)) ++y(x(7),x(11),x(4)) ++y(x(7),x(11),x(5)) ++y(x(7),x(11),x(6)) ++y(x(7),x(11),x(7)) ++y(x(7),x(11),x(8)) ++y(x(7),x(11),x(9)) ++y(x(7),x(11),x(10)) ++y(x(7),x(11),x(12)) ++y(x(7),x(11),x(13)) ++y(x(7),x(11),x(14)) ++y(x(7),x(11),x(15)) ++y(x(7),x(12),x(0)) ++y(x(7),x(12),x(1)) ++y(x(7),x(12),x(2)) ++y(x(7),x(12),x(3)) ++y(x(7),x(12),x(4)) ++y(x(7),x(12),x(5)) ++y(x(7),x(12),x(6)) ++y(x(7),x(12),x(7)) ++y(x(7),x(12),x(8)) ++y(x(7),x(12),x(9)) ++y(x(7),x(12),x(10)) ++y(x(7),x(12),x(11)) ++y(x(7),x(12),x(13)) ++y(x(7),x(12),x(14)) ++y(x(7),x(12),x(15)) ++y(x(7),x(13),x(0)) ++y(x(7),x(13),x(1)) ++y(x(7),x(13),x(2)) ++y(x(7),x(13),x(3)) ++y(x(7),x(13),x(4)) ++y(x(7),x(13),x(5)) ++y(x(7),x(13),x(6)) ++y(x(7),x(13),x(7)) ++y(x(7),x(13),x(8)) ++y(x(7),x(13),x(9)) ++y(x(7),x(13),x(10)) ++y(x(7),x(13),x(11)) ++y(x(7),x(13),x(12)) ++y(x(7),x(13),x(14)) ++y(x(7),x(13),x(15)) ++y(x(7),x(14),x(0)) ++y(x(7),x(14),x(1)) ++y(x(7),x(14),x(2)) ++y(x(7),x(14),x(3)) ++y(x(7),x(14),x(4)) ++y(x(7),x(14),x(5)) ++y(x(7),x(14),x(6)) ++y(x(7),x(14),x(7)) ++y(x(7),x(14),x(8)) ++y(x(7),x(14),x(9)) ++y(x(7),x(14),x(10)) ++y(x(7),x(14),x(11)) ++y(x(7),x(14),x(12)) ++y(x(7),x(14),x(13)) ++y(x(7),x(14),x(15)) ++y(x(8),x(0),x(1)) ++y(x(8),x(0),x(2)) ++y(x(8),x(0),x(3)) ++y(x(8),x(0),x(4)) ++y(x(8),x(0),x(5)) ++y(x(8),x(0),x(6)) ++y(x(8),x(0),x(7)) ++y(x(8),x(0),x(8)) ++y(x(8),x(0),x(9)) ++y(x(8),x(0),x(10)) ++y(x(8),x(0),x(11)) ++y(x(8),x(0),x(12)) ++y(x(8),x(0),x(13)) ++y(x(8),x(0),x(14)) ++y(x(8),x(0),x(15)) ++y(x(8),x(1),x(0)) ++y(x(8),x(1),x(2)) ++y(x(8),x(1),x(3)) ++y(x(8),x(1),x(4)) ++y(x(8),x(1),x(5)) ++y(x(8),x(1),x(6)) ++y(x(8),x(1),x(7)) ++y(x(8),x(1),x(8)) ++y(x(8),x(1),x(9)) ++y(x(8),x(1),x(10)) ++y(x(8),x(1),x(11)) ++y(x(8),x(1),x(12)) ++y(x(8),x(1),x(13)) ++y(x(8),x(1),x(14)) ++y(x(8),x(1),x(15)) ++y(x(8),x(2),x(0)) ++y(x(8),x(2),x(1)) ++y(x(8),x(2),x(3)) ++y(x(8),x(2),x(4)) ++y(x(8),x(2),x(5)) ++y(x(8),x(2),x(6)) ++y(x(8),x(2),x(7)) ++y(x(8),x(2),x(8)) ++y(x(8),x(2),x(9)) ++y(x(8),x(2),x(10)) ++y(x(8),x(2),x(11)) ++y(x(8),x(2),x(12)) ++y(x(8),x(2),x(13)) ++y(x(8),x(2),x(14)) ++y(x(8),x(2),x(15)) ++y(x(8),x(3),x(0)) ++y(x(8),x(3),x(1)) ++y(x(8),x(3),x(2)) ++y(x(8),x(3),x(4)) ++y(x(8),x(3),x(5)) ++y(x(8),x(3),x(6)) ++y(x(8),x(3),x(7)) ++y(x(8),x(3),x(8)) ++y(x(8),x(3),x(9)) ++y(x(8),x(3),x(10)) ++y(x(8),x(3),x(11)) ++y(x(8),x(3),x(12)) ++y(x(8),x(3),x(13)) ++y(x(8),x(3),x(14)) ++y(x(8),x(3),x(15)) ++y(x(8),x(4),x(0)) ++y(x(8),x(4),x(1)) ++y(x(8),x(4),x(2)) ++y(x(8),x(4),x(3)) ++y(x(8),x(4),x(5)) ++y(x(8),x(4),x(6)) ++y(x(8),x(4),x(7)) ++y(x(8),x(4),x(8)) ++y(x(8),x(4),x(9)) ++y(x(8),x(4),x(10)) ++y(x(8),x(4),x(11)) ++y(x(8),x(4),x(12)) ++y(x(8),x(4),x(13)) ++y(x(8),x(4),x(14)) ++y(x(8),x(4),x(15)) ++y(x(8),x(5),x(0)) ++y(x(8),x(5),x(1)) ++y(x(8),x(5),x(2)) ++y(x(8),x(5),x(3)) ++y(x(8),x(5),x(4)) ++y(x(8),x(5),x(6)) ++y(x(8),x(5),x(7)) ++y(x(8),x(5),x(8)) ++y(x(8),x(5),x(9)) ++y(x(8),x(5),x(10)) ++y(x(8),x(5),x(11)) ++y(x(8),x(5),x(12)) ++y(x(8),x(5),x(13)) ++y(x(8),x(5),x(14)) ++y(x(8),x(5),x(15)) ++y(x(8),x(6),x(0)) ++y(x(8),x(6),x(1)) ++y(x(8),x(6),x(2)) ++y(x(8),x(6),x(3)) ++y(x(8),x(6),x(4)) ++y(x(8),x(6),x(5)) ++y(x(8),x(6),x(7)) ++y(x(8),x(6),x(8)) ++y(x(8),x(6),x(9)) ++y(x(8),x(6),x(10)) ++y(x(8),x(6),x(11)) ++y(x(8),x(6),x(12)) ++y(x(8),x(6),x(13)) ++y(x(8),x(6),x(14)) ++y(x(8),x(6),x(15)) ++y(x(8),x(7),x(0)) ++y(x(8),x(7),x(1)) ++y(x(8),x(7),x(2)) ++y(x(8),x(7),x(3)) ++y(x(8),x(7),x(4)) ++y(x(8),x(7),x(5)) ++y(x(8),x(7),x(6)) ++y(x(8),x(7),x(8)) ++y(x(8),x(7),x(9)) ++y(x(8),x(7),x(10)) ++y(x(8),x(7),x(11)) ++y(x(8),x(7),x(12)) ++y(x(8),x(7),x(13)) ++y(x(8),x(7),x(14)) ++y(x(8),x(7),x(15)) ++y(x(8),x(8),x(0)) ++y(x(8),x(8),x(1)) ++y(x(8),x(8),x(2)) ++y(x(8),x(8),x(3)) ++y(x(8),x(8),x(4)) ++y(x(8),x(8),x(5)) ++y(x(8),x(8),x(6)) ++y(x(8),x(8),x(7)) ++y(x(8),x(8),x(8)) ++y(x(8),x(8),x(9)) ++y(x(8),x(8),x(10)) ++y(x(8),x(8),x(11)) ++y(x(8),x(8),x(12)) ++y(x(8),x(8),x(13)) ++y(x(8),x(8),x(14)) ++y(x(8),x(8),x(15)) ++y(x(8),x(9),x(0)) ++y(x(8),x(9),x(1)) ++y(x(8),x(9),x(2)) ++y(x(8),x(9),x(3)) ++y(x(8),x(9),x(4)) ++y(x(8),x(9),x(5)) ++y(x(8),x(9),x(6)) ++y(x(8),x(9),x(7)) ++y(x(8),x(9),x(8)) ++y(x(8),x(9),x(10)) ++y(x(8),x(9),x(11)) ++y(x(8),x(9),x(12)) ++y(x(8),x(9),x(13)) ++y(x(8),x(9),x(14)) ++y(x(8),x(9),x(15)) ++y(x(8),x(10),x(0)) ++y(x(8),x(10),x(1)) ++y(x(8),x(10),x(2)) ++y(x(8),x(10),x(3)) ++y(x(8),x(10),x(4)) ++y(x(8),x(10),x(5)) ++y(x(8),x(10),x(6)) ++y(x(8),x(10),x(7)) ++y(x(8),x(10),x(8)) ++y(x(8),x(10),x(9)) ++y(x(8),x(10),x(11)) ++y(x(8),x(10),x(12)) ++y(x(8),x(10),x(13)) ++y(x(8),x(10),x(14)) ++y(x(8),x(10),x(15)) ++y(x(8),x(11),x(0)) ++y(x(8),x(11),x(1)) ++y(x(8),x(11),x(2)) ++y(x(8),x(11),x(3)) ++y(x(8),x(11),x(4)) ++y(x(8),x(11),x(5)) ++y(x(8),x(11),x(6)) ++y(x(8),x(11),x(7)) ++y(x(8),x(11),x(8)) ++y(x(8),x(11),x(9)) ++y(x(8),x(11),x(10)) ++y(x(8),x(11),x(12)) ++y(x(8),x(11),x(13)) ++y(x(8),x(11),x(14)) ++y(x(8),x(11),x(15)) ++y(x(8),x(12),x(0)) ++y(x(8),x(12),x(1)) ++y(x(8),x(12),x(2)) ++y(x(8),x(12),x(3)) ++y(x(8),x(12),x(4)) ++y(x(8),x(12),x(5)) ++y(x(8),x(12),x(6)) ++y(x(8),x(12),x(7)) ++y(x(8),x(12),x(8)) ++y(x(8),x(12),x(9)) ++y(x(8),x(12),x(10)) ++y(x(8),x(12),x(11)) ++y(x(8),x(12),x(13)) ++y(x(8),x(12),x(14)) ++y(x(8),x(12),x(15)) ++y(x(8),x(13),x(0)) ++y(x(8),x(13),x(1)) ++y(x(8),x(13),x(2)) ++y(x(8),x(13),x(3)) ++y(x(8),x(13),x(4)) ++y(x(8),x(13),x(5)) ++y(x(8),x(13),x(6)) ++y(x(8),x(13),x(7)) ++y(x(8),x(13),x(8)) ++y(x(8),x(13),x(9)) ++y(x(8),x(13),x(10)) ++y(x(8),x(13),x(11)) ++y(x(8),x(13),x(12)) ++y(x(8),x(13),x(14)) ++y(x(8),x(13),x(15)) ++y(x(8),x(14),x(0)) ++y(x(8),x(14),x(1)) ++y(x(8),x(14),x(2)) ++y(x(8),x(14),x(3)) ++y(x(8),x(14),x(4)) ++y(x(8),x(14),x(5)) ++y(x(8),x(14),x(6)) ++y(x(8),x(14),x(7)) ++y(x(8),x(14),x(8)) ++y(x(8),x(14),x(9)) ++y(x(8),x(14),x(10)) ++y(x(8),x(14),x(11)) ++y(x(8),x(14),x(12)) ++y(x(8),x(14),x(13)) ++y(x(8),x(14),x(15)) ++y(x(9),x(0),x(1)) ++y(x(9),x(0),x(2)) ++y(x(9),x(0),x(3)) ++y(x(9),x(0),x(4)) ++y(x(9),x(0),x(5)) ++y(x(9),x(0),x(6)) ++y(x(9),x(0),x(7)) ++y(x(9),x(0),x(8)) ++y(x(9),x(0),x(9)) ++y(x(9),x(0),x(10)) ++y(x(9),x(0),x(11)) ++y(x(9),x(0),x(12)) ++y(x(9),x(0),x(13)) ++y(x(9),x(0),x(14)) ++y(x(9),x(0),x(15)) ++y(x(9),x(1),x(0)) ++y(x(9),x(1),x(2)) ++y(x(9),x(1),x(3)) ++y(x(9),x(1),x(4)) ++y(x(9),x(1),x(5)) ++y(x(9),x(1),x(6)) ++y(x(9),x(1),x(7)) ++y(x(9),x(1),x(8)) ++y(x(9),x(1),x(9)) ++y(x(9),x(1),x(10)) ++y(x(9),x(1),x(11)) ++y(x(9),x(1),x(12)) ++y(x(9),x(1),x(13)) ++y(x(9),x(1),x(14)) ++y(x(9),x(1),x(15)) ++y(x(9),x(2),x(0)) ++y(x(9),x(2),x(1)) ++y(x(9),x(2),x(3)) ++y(x(9),x(2),x(4)) ++y(x(9),x(2),x(5)) ++y(x(9),x(2),x(6)) ++y(x(9),x(2),x(7)) ++y(x(9),x(2),x(8)) ++y(x(9),x(2),x(9)) ++y(x(9),x(2),x(10)) ++y(x(9),x(2),x(11)) ++y(x(9),x(2),x(12)) ++y(x(9),x(2),x(13)) ++y(x(9),x(2),x(14)) ++y(x(9),x(2),x(15)) ++y(x(9),x(3),x(0)) ++y(x(9),x(3),x(1)) ++y(x(9),x(3),x(2)) ++y(x(9),x(3),x(4)) ++y(x(9),x(3),x(5)) ++y(x(9),x(3),x(6)) ++y(x(9),x(3),x(7)) ++y(x(9),x(3),x(8)) ++y(x(9),x(3),x(9)) ++y(x(9),x(3),x(10)) ++y(x(9),x(3),x(11)) ++y(x(9),x(3),x(12)) ++y(x(9),x(3),x(13)) ++y(x(9),x(3),x(14)) ++y(x(9),x(3),x(15)) ++y(x(9),x(4),x(0)) ++y(x(9),x(4),x(1)) ++y(x(9),x(4),x(2)) ++y(x(9),x(4),x(3)) ++y(x(9),x(4),x(5)) ++y(x(9),x(4),x(6)) ++y(x(9),x(4),x(7)) ++y(x(9),x(4),x(8)) ++y(x(9),x(4),x(9)) ++y(x(9),x(4),x(10)) ++y(x(9),x(4),x(11)) ++y(x(9),x(4),x(12)) ++y(x(9),x(4),x(13)) ++y(x(9),x(4),x(14)) ++y(x(9),x(4),x(15)) ++y(x(9),x(5),x(0)) ++y(x(9),x(5),x(1)) ++y(x(9),x(5),x(2)) ++y(x(9),x(5),x(3)) ++y(x(9),x(5),x(4)) ++y(x(9),x(5),x(6)) ++y(x(9),x(5),x(7)) ++y(x(9),x(5),x(8)) ++y(x(9),x(5),x(9)) ++y(x(9),x(5),x(10)) ++y(x(9),x(5),x(11)) ++y(x(9),x(5),x(12)) ++y(x(9),x(5),x(13)) ++y(x(9),x(5),x(14)) ++y(x(9),x(5),x(15)) ++y(x(9),x(6),x(0)) ++y(x(9),x(6),x(1)) ++y(x(9),x(6),x(2)) ++y(x(9),x(6),x(3)) ++y(x(9),x(6),x(4)) ++y(x(9),x(6),x(5)) ++y(x(9),x(6),x(7)) ++y(x(9),x(6),x(8)) ++y(x(9),x(6),x(9)) ++y(x(9),x(6),x(10)) ++y(x(9),x(6),x(11)) ++y(x(9),x(6),x(12)) ++y(x(9),x(6),x(13)) ++y(x(9),x(6),x(14)) ++y(x(9),x(6),x(15)) ++y(x(9),x(7),x(0)) ++y(x(9),x(7),x(1)) ++y(x(9),x(7),x(2)) ++y(x(9),x(7),x(3)) ++y(x(9),x(7),x(4)) ++y(x(9),x(7),x(5)) ++y(x(9),x(7),x(6)) ++y(x(9),x(7),x(8)) ++y(x(9),x(7),x(9)) ++y(x(9),x(7),x(10)) ++y(x(9),x(7),x(11)) ++y(x(9),x(7),x(12)) ++y(x(9),x(7),x(13)) ++y(x(9),x(7),x(14)) ++y(x(9),x(7),x(15)) ++y(x(9),x(8),x(0)) ++y(x(9),x(8),x(1)) ++y(x(9),x(8),x(2)) ++y(x(9),x(8),x(3)) ++y(x(9),x(8),x(4)) ++y(x(9),x(8),x(5)) ++y(x(9),x(8),x(6)) ++y(x(9),x(8),x(7)) ++y(x(9),x(8),x(9)) ++y(x(9),x(8),x(10)) ++y(x(9),x(8),x(11)) ++y(x(9),x(8),x(12)) ++y(x(9),x(8),x(13)) ++y(x(9),x(8),x(14)) ++y(x(9),x(8),x(15)) ++y(x(9),x(9),x(0)) ++y(x(9),x(9),x(1)) ++y(x(9),x(9),x(2)) ++y(x(9),x(9),x(3)) ++y(x(9),x(9),x(4)) ++y(x(9),x(9),x(5)) ++y(x(9),x(9),x(6)) ++y(x(9),x(9),x(7)) ++y(x(9),x(9),x(8)) ++y(x(9),x(9),x(9)) ++y(x(9),x(9),x(10)) ++y(x(9),x(9),x(11)) ++y(x(9),x(9),x(12)) ++y(x(9),x(9),x(13)) ++y(x(9),x(9),x(14)) ++y(x(9),x(9),x(15)) ++y(x(9),x(10),x(0)) ++y(x(9),x(10),x(1)) ++y(x(9),x(10),x(2)) ++y(x(9),x(10),x(3)) ++y(x(9),x(10),x(4)) ++y(x(9),x(10),x(5)) ++y(x(9),x(10),x(6)) ++y(x(9),x(10),x(7)) ++y(x(9),x(10),x(8)) ++y(x(9),x(10),x(9)) ++y(x(9),x(10),x(11)) ++y(x(9),x(10),x(12)) ++y(x(9),x(10),x(13)) ++y(x(9),x(10),x(14)) ++y(x(9),x(10),x(15)) ++y(x(9),x(11),x(0)) ++y(x(9),x(11),x(1)) ++y(x(9),x(11),x(2)) ++y(x(9),x(11),x(3)) ++y(x(9),x(11),x(4)) ++y(x(9),x(11),x(5)) ++y(x(9),x(11),x(6)) ++y(x(9),x(11),x(7)) ++y(x(9),x(11),x(8)) ++y(x(9),x(11),x(9)) ++y(x(9),x(11),x(10)) ++y(x(9),x(11),x(12)) ++y(x(9),x(11),x(13)) ++y(x(9),x(11),x(14)) ++y(x(9),x(11),x(15)) ++y(x(9),x(12),x(0)) ++y(x(9),x(12),x(1)) ++y(x(9),x(12),x(2)) ++y(x(9),x(12),x(3)) ++y(x(9),x(12),x(4)) ++y(x(9),x(12),x(5)) ++y(x(9),x(12),x(6)) ++y(x(9),x(12),x(7)) ++y(x(9),x(12),x(8)) ++y(x(9),x(12),x(9)) ++y(x(9),x(12),x(10)) ++y(x(9),x(12),x(11)) ++y(x(9),x(12),x(13)) ++y(x(9),x(12),x(14)) ++y(x(9),x(12),x(15)) ++y(x(9),x(13),x(0)) ++y(x(9),x(13),x(1)) ++y(x(9),x(13),x(2)) ++y(x(9),x(13),x(3)) ++y(x(9),x(13),x(4)) ++y(x(9),x(13),x(5)) ++y(x(9),x(13),x(6)) ++y(x(9),x(13),x(7)) ++y(x(9),x(13),x(8)) ++y(x(9),x(13),x(9)) ++y(x(9),x(13),x(10)) ++y(x(9),x(13),x(11)) ++y(x(9),x(13),x(12)) ++y(x(9),x(13),x(14)) ++y(x(9),x(13),x(15)) ++y(x(9),x(14),x(0)) ++y(x(9),x(14),x(1)) ++y(x(9),x(14),x(2)) ++y(x(9),x(14),x(3)) ++y(x(9),x(14),x(4)) ++y(x(9),x(14),x(5)) ++y(x(9),x(14),x(6)) ++y(x(9),x(14),x(7)) ++y(x(9),x(14),x(8)) ++y(x(9),x(14),x(9)) ++y(x(9),x(14),x(10)) ++y(x(9),x(14),x(11)) ++y(x(9),x(14),x(12)) ++y(x(9),x(14),x(13)) ++y(x(9),x(14),x(15)) ++y(x(10),x(0),x(1)) ++y(x(10),x(0),x(2)) ++y(x(10),x(0),x(3)) ++y(x(10),x(0),x(4)) ++y(x(10),x(0),x(5)) ++y(x(10),x(0),x(6)) ++y(x(10),x(0),x(7)) ++y(x(10),x(0),x(8)) ++y(x(10),x(0),x(9)) ++y(x(10),x(0),x(10)) ++y(x(10),x(0),x(11)) ++y(x(10),x(0),x(12)) ++y(x(10),x(0),x(13)) ++y(x(10),x(0),x(14)) ++y(x(10),x(0),x(15)) ++y(x(10),x(1),x(0)) ++y(x(10),x(1),x(2)) ++y(x(10),x(1),x(3)) ++y(x(10),x(1),x(4)) ++y(x(10),x(1),x(5)) ++y(x(10),x(1),x(6)) ++y(x(10),x(1),x(7)) ++y(x(10),x(1),x(8)) ++y(x(10),x(1),x(9)) ++y(x(10),x(1),x(10)) ++y(x(10),x(1),x(11)) ++y(x(10),x(1),x(12)) ++y(x(10),x(1),x(13)) ++y(x(10),x(1),x(14)) ++y(x(10),x(1),x(15)) ++y(x(10),x(2),x(0)) ++y(x(10),x(2),x(1)) ++y(x(10),x(2),x(3)) ++y(x(10),x(2),x(4)) ++y(x(10),x(2),x(5)) ++y(x(10),x(2),x(6)) ++y(x(10),x(2),x(7)) ++y(x(10),x(2),x(8)) ++y(x(10),x(2),x(9)) ++y(x(10),x(2),x(10)) ++y(x(10),x(2),x(11)) ++y(x(10),x(2),x(12)) ++y(x(10),x(2),x(13)) ++y(x(10),x(2),x(14)) ++y(x(10),x(2),x(15)) ++y(x(10),x(3),x(0)) ++y(x(10),x(3),x(1)) ++y(x(10),x(3),x(2)) ++y(x(10),x(3),x(4)) ++y(x(10),x(3),x(5)) ++y(x(10),x(3),x(6)) ++y(x(10),x(3),x(7)) ++y(x(10),x(3),x(8)) ++y(x(10),x(3),x(9)) ++y(x(10),x(3),x(10)) ++y(x(10),x(3),x(11)) ++y(x(10),x(3),x(12)) ++y(x(10),x(3),x(13)) ++y(x(10),x(3),x(14)) ++y(x(10),x(3),x(15)) ++y(x(10),x(4),x(0)) ++y(x(10),x(4),x(1)) ++y(x(10),x(4),x(2)) ++y(x(10),x(4),x(3)) ++y(x(10),x(4),x(5)) ++y(x(10),x(4),x(6)) ++y(x(10),x(4),x(7)) ++y(x(10),x(4),x(8)) ++y(x(10),x(4),x(9)) ++y(x(10),x(4),x(10)) ++y(x(10),x(4),x(11)) ++y(x(10),x(4),x(12)) ++y(x(10),x(4),x(13)) ++y(x(10),x(4),x(14)) ++y(x(10),x(4),x(15)) ++y(x(10),x(5),x(0)) ++y(x(10),x(5),x(1)) ++y(x(10),x(5),x(2)) ++y(x(10),x(5),x(3)) ++y(x(10),x(5),x(4)) ++y(x(10),x(5),x(6)) ++y(x(10),x(5),x(7)) ++y(x(10),x(5),x(8)) ++y(x(10),x(5),x(9)) ++y(x(10),x(5),x(10)) ++y(x(10),x(5),x(11)) ++y(x(10),x(5),x(12)) ++y(x(10),x(5),x(13)) ++y(x(10),x(5),x(14)) ++y(x(10),x(5),x(15)) ++y(x(10),x(6),x(0)) ++y(x(10),x(6),x(1)) ++y(x(10),x(6),x(2)) ++y(x(10),x(6),x(3)) ++y(x(10),x(6),x(4)) ++y(x(10),x(6),x(5)) ++y(x(10),x(6),x(7)) ++y(x(10),x(6),x(8)) ++y(x(10),x(6),x(9)) ++y(x(10),x(6),x(10)) ++y(x(10),x(6),x(11)) ++y(x(10),x(6),x(12)) ++y(x(10),x(6),x(13)) ++y(x(10),x(6),x(14)) ++y(x(10),x(6),x(15)) ++y(x(10),x(7),x(0)) ++y(x(10),x(7),x(1)) ++y(x(10),x(7),x(2)) ++y(x(10),x(7),x(3)) ++y(x(10),x(7),x(4)) ++y(x(10),x(7),x(5)) ++y(x(10),x(7),x(6)) ++y(x(10),x(7),x(8)) ++y(x(10),x(7),x(9)) ++y(x(10),x(7),x(10)) ++y(x(10),x(7),x(11)) ++y(x(10),x(7),x(12)) ++y(x(10),x(7),x(13)) ++y(x(10),x(7),x(14)) ++y(x(10),x(7),x(15)) ++y(x(10),x(8),x(0)) ++y(x(10),x(8),x(1)) ++y(x(10),x(8),x(2)) ++y(x(10),x(8),x(3)) ++y(x(10),x(8),x(4)) ++y(x(10),x(8),x(5)) ++y(x(10),x(8),x(6)) ++y(x(10),x(8),x(7)) ++y(x(10),x(8),x(9)) ++y(x(10),x(8),x(10)) ++y(x(10),x(8),x(11)) ++y(x(10),x(8),x(12)) ++y(x(10),x(8),x(13)) ++y(x(10),x(8),x(14)) ++y(x(10),x(8),x(15)) ++y(x(10),x(9),x(0)) ++y(x(10),x(9),x(1)) ++y(x(10),x(9),x(2)) ++y(x(10),x(9),x(3)) ++y(x(10),x(9),x(4)) ++y(x(10),x(9),x(5)) ++y(x(10),x(9),x(6)) ++y(x(10),x(9),x(7)) ++y(x(10),x(9),x(8)) ++y(x(10),x(9),x(10)) ++y(x(10),x(9),x(11)) ++y(x(10),x(9),x(12)) ++y(x(10),x(9),x(13)) ++y(x(10),x(9),x(14)) ++y(x(10),x(9),x(15)) ++y(x(10),x(10),x(0)) ++y(x(10),x(10),x(1)) ++y(x(10),x(10),x(2)) ++y(x(10),x(10),x(3)) ++y(x(10),x(10),x(4)) ++y(x(10),x(10),x(5)) ++y(x(10),x(10),x(6)) ++y(x(10),x(10),x(7)) ++y(x(10),x(10),x(8)) ++y(x(10),x(10),x(9)) ++y(x(10),x(10),x(10)) ++y(x(10),x(10),x(11)) ++y(x(10),x(10),x(12)) ++y(x(10),x(10),x(13)) ++y(x(10),x(10),x(14)) ++y(x(10),x(10),x(15)) ++y(x(10),x(11),x(0)) ++y(x(10),x(11),x(1)) ++y(x(10),x(11),x(2)) ++y(x(10),x(11),x(3)) ++y(x(10),x(11),x(4)) ++y(x(10),x(11),x(5)) ++y(x(10),x(11),x(6)) ++y(x(10),x(11),x(7)) ++y(x(10),x(11),x(8)) ++y(x(10),x(11),x(9)) ++y(x(10),x(11),x(10)) ++y(x(10),x(11),x(12)) ++y(x(10),x(11),x(13)) ++y(x(10),x(11),x(14)) ++y(x(10),x(11),x(15)) ++y(x(10),x(12),x(0)) ++y(x(10),x(12),x(1)) ++y(x(10),x(12),x(2)) ++y(x(10),x(12),x(3)) ++y(x(10),x(12),x(4)) ++y(x(10),x(12),x(5)) ++y(x(10),x(12),x(6)) ++y(x(10),x(12),x(7)) ++y(x(10),x(12),x(8)) ++y(x(10),x(12),x(9)) ++y(x(10),x(12),x(10)) ++y(x(10),x(12),x(11)) ++y(x(10),x(12),x(13)) ++y(x(10),x(12),x(14)) ++y(x(10),x(12),x(15)) ++y(x(10),x(13),x(0)) ++y(x(10),x(13),x(1)) ++y(x(10),x(13),x(2)) ++y(x(10),x(13),x(3)) ++y(x(10),x(13),x(4)) ++y(x(10),x(13),x(5)) ++y(x(10),x(13),x(6)) ++y(x(10),x(13),x(7)) ++y(x(10),x(13),x(8)) ++y(x(10),x(13),x(9)) ++y(x(10),x(13),x(10)) ++y(x(10),x(13),x(11)) ++y(x(10),x(13),x(12)) ++y(x(10),x(13),x(14)) ++y(x(10),x(13),x(15)) ++y(x(10),x(14),x(0)) ++y(x(10),x(14),x(1)) ++y(x(10),x(14),x(2)) ++y(x(10),x(14),x(3)) ++y(x(10),x(14),x(4)) ++y(x(10),x(14),x(5)) ++y(x(10),x(14),x(6)) ++y(x(10),x(14),x(7)) ++y(x(10),x(14),x(8)) ++y(x(10),x(14),x(9)) ++y(x(10),x(14),x(10)) ++y(x(10),x(14),x(11)) ++y(x(10),x(14),x(12)) ++y(x(10),x(14),x(13)) ++y(x(10),x(14),x(15)) ++y(x(11),x(0),x(1)) ++y(x(11),x(0),x(2)) ++y(x(11),x(0),x(3)) ++y(x(11),x(0),x(4)) ++y(x(11),x(0),x(5)) ++y(x(11),x(0),x(6)) ++y(x(11),x(0),x(7)) ++y(x(11),x(0),x(8)) ++y(x(11),x(0),x(9)) ++y(x(11),x(0),x(10)) ++y(x(11),x(0),x(11)) ++y(x(11),x(0),x(12)) ++y(x(11),x(0),x(13)) ++y(x(11),x(0),x(14)) ++y(x(11),x(0),x(15)) ++y(x(11),x(1),x(0)) ++y(x(11),x(1),x(2)) ++y(x(11),x(1),x(3)) ++y(x(11),x(1),x(4)) ++y(x(11),x(1),x(5)) ++y(x(11),x(1),x(6)) ++y(x(11),x(1),x(7)) ++y(x(11),x(1),x(8)) ++y(x(11),x(1),x(9)) ++y(x(11),x(1),x(10)) ++y(x(11),x(1),x(11)) ++y(x(11),x(1),x(12)) ++y(x(11),x(1),x(13)) ++y(x(11),x(1),x(14)) ++y(x(11),x(1),x(15)) ++y(x(11),x(2),x(0)) ++y(x(11),x(2),x(1)) ++y(x(11),x(2),x(3)) ++y(x(11),x(2),x(4)) ++y(x(11),x(2),x(5)) ++y(x(11),x(2),x(6)) ++y(x(11),x(2),x(7)) ++y(x(11),x(2),x(8)) ++y(x(11),x(2),x(9)) ++y(x(11),x(2),x(10)) ++y(x(11),x(2),x(11)) ++y(x(11),x(2),x(12)) ++y(x(11),x(2),x(13)) ++y(x(11),x(2),x(14)) ++y(x(11),x(2),x(15)) ++y(x(11),x(3),x(0)) ++y(x(11),x(3),x(1)) ++y(x(11),x(3),x(2)) ++y(x(11),x(3),x(4)) ++y(x(11),x(3),x(5)) ++y(x(11),x(3),x(6)) ++y(x(11),x(3),x(7)) ++y(x(11),x(3),x(8)) ++y(x(11),x(3),x(9)) ++y(x(11),x(3),x(10)) ++y(x(11),x(3),x(11)) ++y(x(11),x(3),x(12)) ++y(x(11),x(3),x(13)) ++y(x(11),x(3),x(14)) ++y(x(11),x(3),x(15)) ++y(x(11),x(4),x(0)) ++y(x(11),x(4),x(1)) ++y(x(11),x(4),x(2)) ++y(x(11),x(4),x(3)) ++y(x(11),x(4),x(5)) ++y(x(11),x(4),x(6)) ++y(x(11),x(4),x(7)) ++y(x(11),x(4),x(8)) ++y(x(11),x(4),x(9)) ++y(x(11),x(4),x(10)) ++y(x(11),x(4),x(11)) ++y(x(11),x(4),x(12)) ++y(x(11),x(4),x(13)) ++y(x(11),x(4),x(14)) ++y(x(11),x(4),x(15)) ++y(x(11),x(5),x(0)) ++y(x(11),x(5),x(1)) ++y(x(11),x(5),x(2)) ++y(x(11),x(5),x(3)) ++y(x(11),x(5),x(4)) ++y(x(11),x(5),x(6)) ++y(x(11),x(5),x(7)) ++y(x(11),x(5),x(8)) ++y(x(11),x(5),x(9)) ++y(x(11),x(5),x(10)) ++y(x(11),x(5),x(11)) ++y(x(11),x(5),x(12)) ++y(x(11),x(5),x(13)) ++y(x(11),x(5),x(14)) ++y(x(11),x(5),x(15)) ++y(x(11),x(6),x(0)) ++y(x(11),x(6),x(1)) ++y(x(11),x(6),x(2)) ++y(x(11),x(6),x(3)) ++y(x(11),x(6),x(4)) ++y(x(11),x(6),x(5)) ++y(x(11),x(6),x(7)) ++y(x(11),x(6),x(8)) ++y(x(11),x(6),x(9)) ++y(x(11),x(6),x(10)) ++y(x(11),x(6),x(11)) ++y(x(11),x(6),x(12)) ++y(x(11),x(6),x(13)) ++y(x(11),x(6),x(14)) ++y(x(11),x(6),x(15)) ++y(x(11),x(7),x(0)) ++y(x(11),x(7),x(1)) ++y(x(11),x(7),x(2)) ++y(x(11),x(7),x(3)) ++y(x(11),x(7),x(4)) ++y(x(11),x(7),x(5)) ++y(x(11),x(7),x(6)) ++y(x(11),x(7),x(8)) ++y(x(11),x(7),x(9)) ++y(x(11),x(7),x(10)) ++y(x(11),x(7),x(11)) ++y(x(11),x(7),x(12)) ++y(x(11),x(7),x(13)) ++y(x(11),x(7),x(14)) ++y(x(11),x(7),x(15)) ++y(x(11),x(8),x(0)) ++y(x(11),x(8),x(1)) ++y(x(11),x(8),x(2)) ++y(x(11),x(8),x(3)) ++y(x(11),x(8),x(4)) ++y(x(11),x(8),x(5)) ++y(x(11),x(8),x(6)) ++y(x(11),x(8),x(7)) ++y(x(11),x(8),x(9)) ++y(x(11),x(8),x(10)) ++y(x(11),x(8),x(11)) ++y(x(11),x(8),x(12)) ++y(x(11),x(8),x(13)) ++y(x(11),x(8),x(14)) ++y(x(11),x(8),x(15)) ++y(x(11),x(9),x(0)) ++y(x(11),x(9),x(1)) ++y(x(11),x(9),x(2)) ++y(x(11),x(9),x(3)) ++y(x(11),x(9),x(4)) ++y(x(11),x(9),x(5)) ++y(x(11),x(9),x(6)) ++y(x(11),x(9),x(7)) ++y(x(11),x(9),x(8)) ++y(x(11),x(9),x(10)) ++y(x(11),x(9),x(11)) ++y(x(11),x(9),x(12)) ++y(x(11),x(9),x(13)) ++y(x(11),x(9),x(14)) ++y(x(11),x(9),x(15)) ++y(x(11),x(10),x(0)) ++y(x(11),x(10),x(1)) ++y(x(11),x(10),x(2)) ++y(x(11),x(10),x(3)) ++y(x(11),x(10),x(4)) ++y(x(11),x(10),x(5)) ++y(x(11),x(10),x(6)) ++y(x(11),x(10),x(7)) ++y(x(11),x(10),x(8)) ++y(x(11),x(10),x(9)) ++y(x(11),x(10),x(11)) ++y(x(11),x(10),x(12)) ++y(x(11),x(10),x(13)) ++y(x(11),x(10),x(14)) ++y(x(11),x(10),x(15)) ++y(x(11),x(11),x(0)) ++y(x(11),x(11),x(1)) ++y(x(11),x(11),x(2)) ++y(x(11),x(11),x(3)) ++y(x(11),x(11),x(4)) ++y(x(11),x(11),x(5)) ++y(x(11),x(11),x(6)) ++y(x(11),x(11),x(7)) ++y(x(11),x(11),x(8)) ++y(x(11),x(11),x(9)) ++y(x(11),x(11),x(10)) ++y(x(11),x(11),x(11)) ++y(x(11),x(11),x(12)) ++y(x(11),x(11),x(13)) ++y(x(11),x(11),x(14)) ++y(x(11),x(11),x(15)) ++y(x(11),x(12),x(0)) ++y(x(11),x(12),x(1)) ++y(x(11),x(12),x(2)) ++y(x(11),x(12),x(3)) ++y(x(11),x(12),x(4)) ++y(x(11),x(12),x(5)) ++y(x(11),x(12),x(6)) ++y(x(11),x(12),x(7)) ++y(x(11),x(12),x(8)) ++y(x(11),x(12),x(9)) ++y(x(11),x(12),x(10)) ++y(x(11),x(12),x(11)) ++y(x(11),x(12),x(13)) ++y(x(11),x(12),x(14)) ++y(x(11),x(12),x(15)) ++y(x(11),x(13),x(0)) ++y(x(11),x(13),x(1)) ++y(x(11),x(13),x(2)) ++y(x(11),x(13),x(3)) ++y(x(11),x(13),x(4)) ++y(x(11),x(13),x(5)) ++y(x(11),x(13),x(6)) ++y(x(11),x(13),x(7)) ++y(x(11),x(13),x(8)) ++y(x(11),x(13),x(9)) ++y(x(11),x(13),x(10)) ++y(x(11),x(13),x(11)) ++y(x(11),x(13),x(12)) ++y(x(11),x(13),x(14)) ++y(x(11),x(13),x(15)) ++y(x(11),x(14),x(0)) ++y(x(11),x(14),x(1)) ++y(x(11),x(14),x(2)) ++y(x(11),x(14),x(3)) ++y(x(11),x(14),x(4)) ++y(x(11),x(14),x(5)) ++y(x(11),x(14),x(6)) ++y(x(11),x(14),x(7)) ++y(x(11),x(14),x(8)) ++y(x(11),x(14),x(9)) ++y(x(11),x(14),x(10)) ++y(x(11),x(14),x(11)) ++y(x(11),x(14),x(12)) ++y(x(11),x(14),x(13)) ++y(x(11),x(14),x(15)) ++y(x(12),x(0),x(1)) ++y(x(12),x(0),x(2)) ++y(x(12),x(0),x(3)) ++y(x(12),x(0),x(4)) ++y(x(12),x(0),x(5)) ++y(x(12),x(0),x(6)) ++y(x(12),x(0),x(7)) ++y(x(12),x(0),x(8)) ++y(x(12),x(0),x(9)) ++y(x(12),x(0),x(10)) ++y(x(12),x(0),x(11)) ++y(x(12),x(0),x(12)) ++y(x(12),x(0),x(13)) ++y(x(12),x(0),x(14)) ++y(x(12),x(0),x(15)) ++y(x(12),x(1),x(0)) ++y(x(12),x(1),x(2)) ++y(x(12),x(1),x(3)) ++y(x(12),x(1),x(4)) ++y(x(12),x(1),x(5)) ++y(x(12),x(1),x(6)) ++y(x(12),x(1),x(7)) ++y(x(12),x(1),x(8)) ++y(x(12),x(1),x(9)) ++y(x(12),x(1),x(10)) ++y(x(12),x(1),x(11)) ++y(x(12),x(1),x(12)) ++y(x(12),x(1),x(13)) ++y(x(12),x(1),x(14)) ++y(x(12),x(1),x(15)) ++y(x(12),x(2),x(0)) ++y(x(12),x(2),x(1)) ++y(x(12),x(2),x(3)) ++y(x(12),x(2),x(4)) ++y(x(12),x(2),x(5)) ++y(x(12),x(2),x(6)) ++y(x(12),x(2),x(7)) ++y(x(12),x(2),x(8)) ++y(x(12),x(2),x(9)) ++y(x(12),x(2),x(10)) ++y(x(12),x(2),x(11)) ++y(x(12),x(2),x(12)) ++y(x(12),x(2),x(13)) ++y(x(12),x(2),x(14)) ++y(x(12),x(2),x(15)) ++y(x(12),x(3),x(0)) ++y(x(12),x(3),x(1)) ++y(x(12),x(3),x(2)) ++y(x(12),x(3),x(4)) ++y(x(12),x(3),x(5)) ++y(x(12),x(3),x(6)) ++y(x(12),x(3),x(7)) ++y(x(12),x(3),x(8)) ++y(x(12),x(3),x(9)) ++y(x(12),x(3),x(10)) ++y(x(12),x(3),x(11)) ++y(x(12),x(3),x(12)) ++y(x(12),x(3),x(13)) ++y(x(12),x(3),x(14)) ++y(x(12),x(3),x(15)) ++y(x(12),x(4),x(0)) ++y(x(12),x(4),x(1)) ++y(x(12),x(4),x(2)) ++y(x(12),x(4),x(3)) ++y(x(12),x(4),x(5)) ++y(x(12),x(4),x(6)) ++y(x(12),x(4),x(7)) ++y(x(12),x(4),x(8)) ++y(x(12),x(4),x(9)) ++y(x(12),x(4),x(10)) ++y(x(12),x(4),x(11)) ++y(x(12),x(4),x(12)) ++y(x(12),x(4),x(13)) ++y(x(12),x(4),x(14)) ++y(x(12),x(4),x(15)) ++y(x(12),x(5),x(0)) ++y(x(12),x(5),x(1)) ++y(x(12),x(5),x(2)) ++y(x(12),x(5),x(3)) ++y(x(12),x(5),x(4)) ++y(x(12),x(5),x(6)) ++y(x(12),x(5),x(7)) ++y(x(12),x(5),x(8)) ++y(x(12),x(5),x(9)) ++y(x(12),x(5),x(10)) ++y(x(12),x(5),x(11)) ++y(x(12),x(5),x(12)) ++y(x(12),x(5),x(13)) ++y(x(12),x(5),x(14)) ++y(x(12),x(5),x(15)) ++y(x(12),x(6),x(0)) ++y(x(12),x(6),x(1)) ++y(x(12),x(6),x(2)) ++y(x(12),x(6),x(3)) ++y(x(12),x(6),x(4)) ++y(x(12),x(6),x(5)) ++y(x(12),x(6),x(7)) ++y(x(12),x(6),x(8)) ++y(x(12),x(6),x(9)) ++y(x(12),x(6),x(10)) ++y(x(12),x(6),x(11)) ++y(x(12),x(6),x(12)) ++y(x(12),x(6),x(13)) ++y(x(12),x(6),x(14)) ++y(x(12),x(6),x(15)) ++y(x(12),x(7),x(0)) ++y(x(12),x(7),x(1)) ++y(x(12),x(7),x(2)) ++y(x(12),x(7),x(3)) ++y(x(12),x(7),x(4)) ++y(x(12),x(7),x(5)) ++y(x(12),x(7),x(6)) ++y(x(12),x(7),x(8)) ++y(x(12),x(7),x(9)) ++y(x(12),x(7),x(10)) ++y(x(12),x(7),x(11)) ++y(x(12),x(7),x(12)) ++y(x(12),x(7),x(13)) ++y(x(12),x(7),x(14)) ++y(x(12),x(7),x(15)) ++y(x(12),x(8),x(0)) ++y(x(12),x(8),x(1)) ++y(x(12),x(8),x(2)) ++y(x(12),x(8),x(3)) ++y(x(12),x(8),x(4)) ++y(x(12),x(8),x(5)) ++y(x(12),x(8),x(6)) ++y(x(12),x(8),x(7)) ++y(x(12),x(8),x(9)) ++y(x(12),x(8),x(10)) ++y(x(12),x(8),x(11)) ++y(x(12),x(8),x(12)) ++y(x(12),x(8),x(13)) ++y(x(12),x(8),x(14)) ++y(x(12),x(8),x(15)) ++y(x(12),x(9),x(0)) ++y(x(12),x(9),x(1)) ++y(x(12),x(9),x(2)) ++y(x(12),x(9),x(3)) ++y(x(12),x(9),x(4)) ++y(x(12),x(9),x(5)) ++y(x(12),x(9),x(6)) ++y(x(12),x(9),x(7)) ++y(x(12),x(9),x(8)) ++y(x(12),x(9),x(10)) ++y(x(12),x(9),x(11)) ++y(x(12),x(9),x(12)) ++y(x(12),x(9),x(13)) ++y(x(12),x(9),x(14)) ++y(x(12),x(9),x(15)) ++y(x(12),x(10),x(0)) ++y(x(12),x(10),x(1)) ++y(x(12),x(10),x(2)) ++y(x(12),x(10),x(3)) ++y(x(12),x(10),x(4)) ++y(x(12),x(10),x(5)) ++y(x(12),x(10),x(6)) ++y(x(12),x(10),x(7)) ++y(x(12),x(10),x(8)) ++y(x(12),x(10),x(9)) ++y(x(12),x(10),x(11)) ++y(x(12),x(10),x(12)) ++y(x(12),x(10),x(13)) ++y(x(12),x(10),x(14)) ++y(x(12),x(10),x(15)) ++y(x(12),x(11),x(0)) ++y(x(12),x(11),x(1)) ++y(x(12),x(11),x(2)) ++y(x(12),x(11),x(3)) ++y(x(12),x(11),x(4)) ++y(x(12),x(11),x(5)) ++y(x(12),x(11),x(6)) ++y(x(12),x(11),x(7)) ++y(x(12),x(11),x(8)) ++y(x(12),x(11),x(9)) ++y(x(12),x(11),x(10)) ++y(x(12),x(11),x(12)) ++y(x(12),x(11),x(13)) ++y(x(12),x(11),x(14)) ++y(x(12),x(11),x(15)) ++y(x(12),x(12),x(0)) ++y(x(12),x(12),x(1)) ++y(x(12),x(12),x(2)) ++y(x(12),x(12),x(3)) ++y(x(12),x(12),x(4)) ++y(x(12),x(12),x(5)) ++y(x(12),x(12),x(6)) ++y(x(12),x(12),x(7)) ++y(x(12),x(12),x(8)) ++y(x(12),x(12),x(9)) ++y(x(12),x(12),x(10)) ++y(x(12),x(12),x(11)) ++y(x(12),x(12),x(12)) ++y(x(12),x(12),x(13)) ++y(x(12),x(12),x(14)) ++y(x(12),x(12),x(15)) ++y(x(12),x(13),x(0)) ++y(x(12),x(13),x(1)) ++y(x(12),x(13),x(2)) ++y(x(12),x(13),x(3)) ++y(x(12),x(13),x(4)) ++y(x(12),x(13),x(5)) ++y(x(12),x(13),x(6)) ++y(x(12),x(13),x(7)) ++y(x(12),x(13),x(8)) ++y(x(12),x(13),x(9)) ++y(x(12),x(13),x(10)) ++y(x(12),x(13),x(11)) ++y(x(12),x(13),x(12)) ++y(x(12),x(13),x(14)) ++y(x(12),x(13),x(15)) ++y(x(12),x(14),x(0)) ++y(x(12),x(14),x(1)) ++y(x(12),x(14),x(2)) ++y(x(12),x(14),x(3)) ++y(x(12),x(14),x(4)) ++y(x(12),x(14),x(5)) ++y(x(12),x(14),x(6)) ++y(x(12),x(14),x(7)) ++y(x(12),x(14),x(8)) ++y(x(12),x(14),x(9)) ++y(x(12),x(14),x(10)) ++y(x(12),x(14),x(11)) ++y(x(12),x(14),x(12)) ++y(x(12),x(14),x(13)) ++y(x(12),x(14),x(15)) ++y(x(13),x(0),x(1)) ++y(x(13),x(0),x(2)) ++y(x(13),x(0),x(3)) ++y(x(13),x(0),x(4)) ++y(x(13),x(0),x(5)) ++y(x(13),x(0),x(6)) ++y(x(13),x(0),x(7)) ++y(x(13),x(0),x(8)) ++y(x(13),x(0),x(9)) ++y(x(13),x(0),x(10)) ++y(x(13),x(0),x(11)) ++y(x(13),x(0),x(12)) ++y(x(13),x(0),x(13)) ++y(x(13),x(0),x(14)) ++y(x(13),x(0),x(15)) ++y(x(13),x(1),x(0)) ++y(x(13),x(1),x(2)) ++y(x(13),x(1),x(3)) ++y(x(13),x(1),x(4)) ++y(x(13),x(1),x(5)) ++y(x(13),x(1),x(6)) ++y(x(13),x(1),x(7)) ++y(x(13),x(1),x(8)) ++y(x(13),x(1),x(9)) ++y(x(13),x(1),x(10)) ++y(x(13),x(1),x(11)) ++y(x(13),x(1),x(12)) ++y(x(13),x(1),x(13)) ++y(x(13),x(1),x(14)) ++y(x(13),x(1),x(15)) ++y(x(13),x(2),x(0)) ++y(x(13),x(2),x(1)) ++y(x(13),x(2),x(3)) ++y(x(13),x(2),x(4)) ++y(x(13),x(2),x(5)) ++y(x(13),x(2),x(6)) ++y(x(13),x(2),x(7)) ++y(x(13),x(2),x(8)) ++y(x(13),x(2),x(9)) ++y(x(13),x(2),x(10)) ++y(x(13),x(2),x(11)) ++y(x(13),x(2),x(12)) ++y(x(13),x(2),x(13)) ++y(x(13),x(2),x(14)) ++y(x(13),x(2),x(15)) ++y(x(13),x(3),x(0)) ++y(x(13),x(3),x(1)) ++y(x(13),x(3),x(2)) ++y(x(13),x(3),x(4)) ++y(x(13),x(3),x(5)) ++y(x(13),x(3),x(6)) ++y(x(13),x(3),x(7)) ++y(x(13),x(3),x(8)) ++y(x(13),x(3),x(9)) ++y(x(13),x(3),x(10)) ++y(x(13),x(3),x(11)) ++y(x(13),x(3),x(12)) ++y(x(13),x(3),x(13)) ++y(x(13),x(3),x(14)) ++y(x(13),x(3),x(15)) ++y(x(13),x(4),x(0)) ++y(x(13),x(4),x(1)) ++y(x(13),x(4),x(2)) ++y(x(13),x(4),x(3)) ++y(x(13),x(4),x(5)) ++y(x(13),x(4),x(6)) ++y(x(13),x(4),x(7)) ++y(x(13),x(4),x(8)) ++y(x(13),x(4),x(9)) ++y(x(13),x(4),x(10)) ++y(x(13),x(4),x(11)) ++y(x(13),x(4),x(12)) ++y(x(13),x(4),x(13)) ++y(x(13),x(4),x(14)) ++y(x(13),x(4),x(15)) ++y(x(13),x(5),x(0)) ++y(x(13),x(5),x(1)) ++y(x(13),x(5),x(2)) ++y(x(13),x(5),x(3)) ++y(x(13),x(5),x(4)) ++y(x(13),x(5),x(6)) ++y(x(13),x(5),x(7)) ++y(x(13),x(5),x(8)) ++y(x(13),x(5),x(9)) ++y(x(13),x(5),x(10)) ++y(x(13),x(5),x(11)) ++y(x(13),x(5),x(12)) ++y(x(13),x(5),x(13)) ++y(x(13),x(5),x(14)) ++y(x(13),x(5),x(15)) ++y(x(13),x(6),x(0)) ++y(x(13),x(6),x(1)) ++y(x(13),x(6),x(2)) ++y(x(13),x(6),x(3)) ++y(x(13),x(6),x(4)) ++y(x(13),x(6),x(5)) ++y(x(13),x(6),x(7)) ++y(x(13),x(6),x(8)) ++y(x(13),x(6),x(9)) ++y(x(13),x(6),x(10)) ++y(x(13),x(6),x(11)) ++y(x(13),x(6),x(12)) ++y(x(13),x(6),x(13)) ++y(x(13),x(6),x(14)) ++y(x(13),x(6),x(15)) ++y(x(13),x(7),x(0)) ++y(x(13),x(7),x(1)) ++y(x(13),x(7),x(2)) ++y(x(13),x(7),x(3)) ++y(x(13),x(7),x(4)) ++y(x(13),x(7),x(5)) ++y(x(13),x(7),x(6)) ++y(x(13),x(7),x(8)) ++y(x(13),x(7),x(9)) ++y(x(13),x(7),x(10)) ++y(x(13),x(7),x(11)) ++y(x(13),x(7),x(12)) ++y(x(13),x(7),x(13)) ++y(x(13),x(7),x(14)) ++y(x(13),x(7),x(15)) ++y(x(13),x(8),x(0)) ++y(x(13),x(8),x(1)) ++y(x(13),x(8),x(2)) ++y(x(13),x(8),x(3)) ++y(x(13),x(8),x(4)) ++y(x(13),x(8),x(5)) ++y(x(13),x(8),x(6)) ++y(x(13),x(8),x(7)) ++y(x(13),x(8),x(9)) ++y(x(13),x(8),x(10)) ++y(x(13),x(8),x(11)) ++y(x(13),x(8),x(12)) ++y(x(13),x(8),x(13)) ++y(x(13),x(8),x(14)) ++y(x(13),x(8),x(15)) ++y(x(13),x(9),x(0)) ++y(x(13),x(9),x(1)) ++y(x(13),x(9),x(2)) ++y(x(13),x(9),x(3)) ++y(x(13),x(9),x(4)) ++y(x(13),x(9),x(5)) ++y(x(13),x(9),x(6)) ++y(x(13),x(9),x(7)) ++y(x(13),x(9),x(8)) ++y(x(13),x(9),x(10)) ++y(x(13),x(9),x(11)) ++y(x(13),x(9),x(12)) ++y(x(13),x(9),x(13)) ++y(x(13),x(9),x(14)) ++y(x(13),x(9),x(15)) ++y(x(13),x(10),x(0)) ++y(x(13),x(10),x(1)) ++y(x(13),x(10),x(2)) ++y(x(13),x(10),x(3)) ++y(x(13),x(10),x(4)) ++y(x(13),x(10),x(5)) ++y(x(13),x(10),x(6)) ++y(x(13),x(10),x(7)) ++y(x(13),x(10),x(8)) ++y(x(13),x(10),x(9)) ++y(x(13),x(10),x(11)) ++y(x(13),x(10),x(12)) ++y(x(13),x(10),x(13)) ++y(x(13),x(10),x(14)) ++y(x(13),x(10),x(15)) ++y(x(13),x(11),x(0)) ++y(x(13),x(11),x(1)) ++y(x(13),x(11),x(2)) ++y(x(13),x(11),x(3)) ++y(x(13),x(11),x(4)) ++y(x(13),x(11),x(5)) ++y(x(13),x(11),x(6)) ++y(x(13),x(11),x(7)) ++y(x(13),x(11),x(8)) ++y(x(13),x(11),x(9)) ++y(x(13),x(11),x(10)) ++y(x(13),x(11),x(12)) ++y(x(13),x(11),x(13)) ++y(x(13),x(11),x(14)) ++y(x(13),x(11),x(15)) ++y(x(13),x(12),x(0)) ++y(x(13),x(12),x(1)) ++y(x(13),x(12),x(2)) ++y(x(13),x(12),x(3)) ++y(x(13),x(12),x(4)) ++y(x(13),x(12),x(5)) ++y(x(13),x(12),x(6)) ++y(x(13),x(12),x(7)) ++y(x(13),x(12),x(8)) ++y(x(13),x(12),x(9)) ++y(x(13),x(12),x(10)) ++y(x(13),x(12),x(11)) ++y(x(13),x(12),x(13)) ++y(x(13),x(12),x(14)) ++y(x(13),x(12),x(15)) ++y(x(13),x(13),x(0)) ++y(x(13),x(13),x(1)) ++y(x(13),x(13),x(2)) ++y(x(13),x(13),x(3)) ++y(x(13),x(13),x(4)) ++y(x(13),x(13),x(5)) ++y(x(13),x(13),x(6)) ++y(x(13),x(13),x(7)) ++y(x(13),x(13),x(8)) ++y(x(13),x(13),x(9)) ++y(x(13),x(13),x(10)) ++y(x(13),x(13),x(11)) ++y(x(13),x(13),x(12)) ++y(x(13),x(13),x(13)) ++y(x(13),x(13),x(14)) ++y(x(13),x(13),x(15)) ++y(x(13),x(14),x(0)) ++y(x(13),x(14),x(1)) ++y(x(13),x(14),x(2)) ++y(x(13),x(14),x(3)) ++y(x(13),x(14),x(4)) ++y(x(13),x(14),x(5)) ++y(x(13),x(14),x(6)) ++y(x(13),x(14),x(7)) ++y(x(13),x(14),x(8)) ++y(x(13),x(14),x(9)) ++y(x(13),x(14),x(10)) ++y(x(13),x(14),x(11)) ++y(x(13),x(14),x(12)) ++y(x(13),x(14),x(13)) ++y(x(13),x(14),x(15)) ++y(x(14),x(0),x(1)) ++y(x(14),x(0),x(2)) ++y(x(14),x(0),x(3)) ++y(x(14),x(0),x(4)) ++y(x(14),x(0),x(5)) ++y(x(14),x(0),x(6)) ++y(x(14),x(0),x(7)) ++y(x(14),x(0),x(8)) ++y(x(14),x(0),x(9)) ++y(x(14),x(0),x(10)) ++y(x(14),x(0),x(11)) ++y(x(14),x(0),x(12)) ++y(x(14),x(0),x(13)) ++y(x(14),x(0),x(14)) ++y(x(14),x(0),x(15)) ++y(x(14),x(1),x(0)) ++y(x(14),x(1),x(2)) ++y(x(14),x(1),x(3)) ++y(x(14),x(1),x(4)) ++y(x(14),x(1),x(5)) ++y(x(14),x(1),x(6)) ++y(x(14),x(1),x(7)) ++y(x(14),x(1),x(8)) ++y(x(14),x(1),x(9)) ++y(x(14),x(1),x(10)) ++y(x(14),x(1),x(11)) ++y(x(14),x(1),x(12)) ++y(x(14),x(1),x(13)) ++y(x(14),x(1),x(14)) ++y(x(14),x(1),x(15)) ++y(x(14),x(2),x(0)) ++y(x(14),x(2),x(1)) ++y(x(14),x(2),x(3)) ++y(x(14),x(2),x(4)) ++y(x(14),x(2),x(5)) ++y(x(14),x(2),x(6)) ++y(x(14),x(2),x(7)) ++y(x(14),x(2),x(8)) ++y(x(14),x(2),x(9)) ++y(x(14),x(2),x(10)) ++y(x(14),x(2),x(11)) ++y(x(14),x(2),x(12)) ++y(x(14),x(2),x(13)) ++y(x(14),x(2),x(14)) ++y(x(14),x(2),x(15)) ++y(x(14),x(3),x(0)) ++y(x(14),x(3),x(1)) ++y(x(14),x(3),x(2)) ++y(x(14),x(3),x(4)) ++y(x(14),x(3),x(5)) ++y(x(14),x(3),x(6)) ++y(x(14),x(3),x(7)) ++y(x(14),x(3),x(8)) ++y(x(14),x(3),x(9)) ++y(x(14),x(3),x(10)) ++y(x(14),x(3),x(11)) ++y(x(14),x(3),x(12)) ++y(x(14),x(3),x(13)) ++y(x(14),x(3),x(14)) ++y(x(14),x(3),x(15)) ++y(x(14),x(4),x(0)) ++y(x(14),x(4),x(1)) ++y(x(14),x(4),x(2)) ++y(x(14),x(4),x(3)) ++y(x(14),x(4),x(5)) ++y(x(14),x(4),x(6)) ++y(x(14),x(4),x(7)) ++y(x(14),x(4),x(8)) ++y(x(14),x(4),x(9)) ++y(x(14),x(4),x(10)) ++y(x(14),x(4),x(11)) ++y(x(14),x(4),x(12)) ++y(x(14),x(4),x(13)) ++y(x(14),x(4),x(14)) ++y(x(14),x(4),x(15)) ++y(x(14),x(5),x(0)) ++y(x(14),x(5),x(1)) ++y(x(14),x(5),x(2)) ++y(x(14),x(5),x(3)) ++y(x(14),x(5),x(4)) ++y(x(14),x(5),x(6)) ++y(x(14),x(5),x(7)) ++y(x(14),x(5),x(8)) ++y(x(14),x(5),x(9)) ++y(x(14),x(5),x(10)) ++y(x(14),x(5),x(11)) ++y(x(14),x(5),x(12)) ++y(x(14),x(5),x(13)) ++y(x(14),x(5),x(14)) ++y(x(14),x(5),x(15)) ++y(x(14),x(6),x(0)) ++y(x(14),x(6),x(1)) ++y(x(14),x(6),x(2)) ++y(x(14),x(6),x(3)) ++y(x(14),x(6),x(4)) ++y(x(14),x(6),x(5)) ++y(x(14),x(6),x(7)) ++y(x(14),x(6),x(8)) ++y(x(14),x(6),x(9)) ++y(x(14),x(6),x(10)) ++y(x(14),x(6),x(11)) ++y(x(14),x(6),x(12)) ++y(x(14),x(6),x(13)) ++y(x(14),x(6),x(14)) ++y(x(14),x(6),x(15)) ++y(x(14),x(7),x(0)) ++y(x(14),x(7),x(1)) ++y(x(14),x(7),x(2)) ++y(x(14),x(7),x(3)) ++y(x(14),x(7),x(4)) ++y(x(14),x(7),x(5)) ++y(x(14),x(7),x(6)) ++y(x(14),x(7),x(8)) ++y(x(14),x(7),x(9)) ++y(x(14),x(7),x(10)) ++y(x(14),x(7),x(11)) ++y(x(14),x(7),x(12)) ++y(x(14),x(7),x(13)) ++y(x(14),x(7),x(14)) ++y(x(14),x(7),x(15)) ++y(x(14),x(8),x(0)) ++y(x(14),x(8),x(1)) ++y(x(14),x(8),x(2)) ++y(x(14),x(8),x(3)) ++y(x(14),x(8),x(4)) ++y(x(14),x(8),x(5)) ++y(x(14),x(8),x(6)) ++y(x(14),x(8),x(7)) ++y(x(14),x(8),x(9)) ++y(x(14),x(8),x(10)) ++y(x(14),x(8),x(11)) ++y(x(14),x(8),x(12)) ++y(x(14),x(8),x(13)) ++y(x(14),x(8),x(14)) ++y(x(14),x(8),x(15)) ++y(x(14),x(9),x(0)) ++y(x(14),x(9),x(1)) ++y(x(14),x(9),x(2)) ++y(x(14),x(9),x(3)) ++y(x(14),x(9),x(4)) ++y(x(14),x(9),x(5)) ++y(x(14),x(9),x(6)) ++y(x(14),x(9),x(7)) ++y(x(14),x(9),x(8)) ++y(x(14),x(9),x(10)) ++y(x(14),x(9),x(11)) ++y(x(14),x(9),x(12)) ++y(x(14),x(9),x(13)) ++y(x(14),x(9),x(14)) ++y(x(14),x(9),x(15)) ++y(x(14),x(10),x(0)) ++y(x(14),x(10),x(1)) ++y(x(14),x(10),x(2)) ++y(x(14),x(10),x(3)) ++y(x(14),x(10),x(4)) ++y(x(14),x(10),x(5)) ++y(x(14),x(10),x(6)) ++y(x(14),x(10),x(7)) ++y(x(14),x(10),x(8)) ++y(x(14),x(10),x(9)) ++y(x(14),x(10),x(11)) ++y(x(14),x(10),x(12)) ++y(x(14),x(10),x(13)) ++y(x(14),x(10),x(14)) ++y(x(14),x(10),x(15)) ++y(x(14),x(11),x(0)) ++y(x(14),x(11),x(1)) ++y(x(14),x(11),x(2)) ++y(x(14),x(11),x(3)) ++y(x(14),x(11),x(4)) ++y(x(14),x(11),x(5)) ++y(x(14),x(11),x(6)) ++y(x(14),x(11),x(7)) ++y(x(14),x(11),x(8)) ++y(x(14),x(11),x(9)) ++y(x(14),x(11),x(10)) ++y(x(14),x(11),x(12)) ++y(x(14),x(11),x(13)) ++y(x(14),x(11),x(14)) ++y(x(14),x(11),x(15)) ++y(x(14),x(12),x(0)) ++y(x(14),x(12),x(1)) ++y(x(14),x(12),x(2)) ++y(x(14),x(12),x(3)) ++y(x(14),x(12),x(4)) ++y(x(14),x(12),x(5)) ++y(x(14),x(12),x(6)) ++y(x(14),x(12),x(7)) ++y(x(14),x(12),x(8)) ++y(x(14),x(12),x(9)) ++y(x(14),x(12),x(10)) ++y(x(14),x(12),x(11)) ++y(x(14),x(12),x(13)) ++y(x(14),x(12),x(14)) ++y(x(14),x(12),x(15)) ++y(x(14),x(13),x(0)) ++y(x(14),x(13),x(1)) ++y(x(14),x(13),x(2)) ++y(x(14),x(13),x(3)) ++y(x(14),x(13),x(4)) ++y(x(14),x(13),x(5)) ++y(x(14),x(13),x(6)) ++y(x(14),x(13),x(7)) ++y(x(14),x(13),x(8)) ++y(x(14),x(13),x(9)) ++y(x(14),x(13),x(10)) ++y(x(14),x(13),x(11)) ++y(x(14),x(13),x(12)) ++y(x(14),x(13),x(14)) ++y(x(14),x(13),x(15)) ++y(x(14),x(14),x(0)) ++y(x(14),x(14),x(1)) ++y(x(14),x(14),x(2)) ++y(x(14),x(14),x(3)) ++y(x(14),x(14),x(4)) ++y(x(14),x(14),x(5)) ++y(x(14),x(14),x(6)) ++y(x(14),x(14),x(7)) ++y(x(14),x(14),x(8)) ++y(x(14),x(14),x(9)) ++y(x(14),x(14),x(10)) ++y(x(14),x(14),x(11)) ++y(x(14),x(14),x(12)) ++y(x(14),x(14),x(13)) ++y(x(14),x(14),x(14)) ++y(x(14),x(14),x(15)) ++y(x(15),x(0),x(1)) ++y(x(15),x(0),x(2)) ++y(x(15),x(0),x(3)) ++y(x(15),x(0),x(4)) ++y(x(15),x(0),x(5)) ++y(x(15),x(0),x(6)) ++y(x(15),x(0),x(7)) ++y(x(15),x(0),x(8)) ++y(x(15),x(0),x(9)) ++y(x(15),x(0),x(10)) ++y(x(15),x(0),x(11)) ++y(x(15),x(0),x(12)) ++y(x(15),x(0),x(13)) ++y(x(15),x(0),x(14)) ++y(x(15),x(0),x(15)) ++y(x(15),x(1),x(0)) ++y(x(15),x(1),x(2)) ++y(x(15),x(1),x(3)) ++y(x(15),x(1),x(4)) ++y(x(15),x(1),x(5)) ++y(x(15),x(1),x(6)) ++y(x(15),x(1),x(7)) ++y(x(15),x(1),x(8)) ++y(x(15),x(1),x(9)) ++y(x(15),x(1),x(10)) ++y(x(15),x(1),x(11)) ++y(x(15),x(1),x(12)) ++y(x(15),x(1),x(13)) ++y(x(15),x(1),x(14)) ++y(x(15),x(1),x(15)) ++y(x(15),x(2),x(0)) ++y(x(15),x(2),x(1)) ++y(x(15),x(2),x(3)) ++y(x(15),x(2),x(4)) ++y(x(15),x(2),x(5)) ++y(x(15),x(2),x(6)) ++y(x(15),x(2),x(7)) ++y(x(15),x(2),x(8)) ++y(x(15),x(2),x(9)) ++y(x(15),x(2),x(10)) ++y(x(15),x(2),x(11)) ++y(x(15),x(2),x(12)) ++y(x(15),x(2),x(13)) ++y(x(15),x(2),x(14)) ++y(x(15),x(2),x(15)) ++y(x(15),x(3),x(0)) ++y(x(15),x(3),x(1)) ++y(x(15),x(3),x(2)) ++y(x(15),x(3),x(4)) ++y(x(15),x(3),x(5)) ++y(x(15),x(3),x(6)) ++y(x(15),x(3),x(7)) ++y(x(15),x(3),x(8)) ++y(x(15),x(3),x(9)) ++y(x(15),x(3),x(10)) ++y(x(15),x(3),x(11)) ++y(x(15),x(3),x(12)) ++y(x(15),x(3),x(13)) ++y(x(15),x(3),x(14)) ++y(x(15),x(3),x(15)) ++y(x(15),x(4),x(0)) ++y(x(15),x(4),x(1)) ++y(x(15),x(4),x(2)) ++y(x(15),x(4),x(3)) ++y(x(15),x(4),x(5)) ++y(x(15),x(4),x(6)) ++y(x(15),x(4),x(7)) ++y(x(15),x(4),x(8)) ++y(x(15),x(4),x(9)) ++y(x(15),x(4),x(10)) ++y(x(15),x(4),x(11)) ++y(x(15),x(4),x(12)) ++y(x(15),x(4),x(13)) ++y(x(15),x(4),x(14)) ++y(x(15),x(4),x(15)) ++y(x(15),x(5),x(0)) ++y(x(15),x(5),x(1)) ++y(x(15),x(5),x(2)) ++y(x(15),x(5),x(3)) ++y(x(15),x(5),x(4)) ++y(x(15),x(5),x(6)) ++y(x(15),x(5),x(7)) ++y(x(15),x(5),x(8)) ++y(x(15),x(5),x(9)) ++y(x(15),x(5),x(10)) ++y(x(15),x(5),x(11)) ++y(x(15),x(5),x(12)) ++y(x(15),x(5),x(13)) ++y(x(15),x(5),x(14)) ++y(x(15),x(5),x(15)) ++y(x(15),x(6),x(0)) ++y(x(15),x(6),x(1)) ++y(x(15),x(6),x(2)) ++y(x(15),x(6),x(3)) ++y(x(15),x(6),x(4)) ++y(x(15),x(6),x(5)) ++y(x(15),x(6),x(7)) ++y(x(15),x(6),x(8)) ++y(x(15),x(6),x(9)) ++y(x(15),x(6),x(10)) ++y(x(15),x(6),x(11)) ++y(x(15),x(6),x(12)) ++y(x(15),x(6),x(13)) ++y(x(15),x(6),x(14)) ++y(x(15),x(6),x(15)) ++y(x(15),x(7),x(0)) ++y(x(15),x(7),x(1)) ++y(x(15),x(7),x(2)) ++y(x(15),x(7),x(3)) ++y(x(15),x(7),x(4)) ++y(x(15),x(7),x(5)) ++y(x(15),x(7),x(6)) ++y(x(15),x(7),x(8)) ++y(x(15),x(7),x(9)) ++y(x(15),x(7),x(10)) ++y(x(15),x(7),x(11)) ++y(x(15),x(7),x(12)) ++y(x(15),x(7),x(13)) ++y(x(15),x(7),x(14)) ++y(x(15),x(7),x(15)) ++y(x(15),x(8),x(0)) ++y(x(15),x(8),x(1)) ++y(x(15),x(8),x(2)) ++y(x(15),x(8),x(3)) ++y(x(15),x(8),x(4)) ++y(x(15),x(8),x(5)) ++y(x(15),x(8),x(6)) ++y(x(15),x(8),x(7)) ++y(x(15),x(8),x(9)) ++y(x(15),x(8),x(10)) ++y(x(15),x(8),x(11)) ++y(x(15),x(8),x(12)) ++y(x(15),x(8),x(13)) ++y(x(15),x(8),x(14)) ++y(x(15),x(8),x(15)) ++y(x(15),x(9),x(0)) ++y(x(15),x(9),x(1)) ++y(x(15),x(9),x(2)) ++y(x(15),x(9),x(3)) ++y(x(15),x(9),x(4)) ++y(x(15),x(9),x(5)) ++y(x(15),x(9),x(6)) ++y(x(15),x(9),x(7)) ++y(x(15),x(9),x(8)) ++y(x(15),x(9),x(10)) ++y(x(15),x(9),x(11)) ++y(x(15),x(9),x(12)) ++y(x(15),x(9),x(13)) ++y(x(15),x(9),x(14)) ++y(x(15),x(9),x(15)) ++y(x(15),x(10),x(0)) ++y(x(15),x(10),x(1)) ++y(x(15),x(10),x(2)) ++y(x(15),x(10),x(3)) ++y(x(15),x(10),x(4)) ++y(x(15),x(10),x(5)) ++y(x(15),x(10),x(6)) ++y(x(15),x(10),x(7)) ++y(x(15),x(10),x(8)) ++y(x(15),x(10),x(9)) ++y(x(15),x(10),x(11)) ++y(x(15),x(10),x(12)) ++y(x(15),x(10),x(13)) ++y(x(15),x(10),x(14)) ++y(x(15),x(10),x(15)) ++y(x(15),x(11),x(0)) ++y(x(15),x(11),x(1)) ++y(x(15),x(11),x(2)) ++y(x(15),x(11),x(3)) ++y(x(15),x(11),x(4)) ++y(x(15),x(11),x(5)) ++y(x(15),x(11),x(6)) ++y(x(15),x(11),x(7)) ++y(x(15),x(11),x(8)) ++y(x(15),x(11),x(9)) ++y(x(15),x(11),x(10)) ++y(x(15),x(11),x(12)) ++y(x(15),x(11),x(13)) ++y(x(15),x(11),x(14)) ++y(x(15),x(11),x(15)) ++y(x(15),x(12),x(0)) ++y(x(15),x(12),x(1)) ++y(x(15),x(12),x(2)) ++y(x(15),x(12),x(3)) ++y(x(15),x(12),x(4)) ++y(x(15),x(12),x(5)) ++y(x(15),x(12),x(6)) ++y(x(15),x(12),x(7)) ++y(x(15),x(12),x(8)) ++y(x(15),x(12),x(9)) ++y(x(15),x(12),x(10)) ++y(x(15),x(12),x(11)) ++y(x(15),x(12),x(13)) ++y(x(15),x(12),x(14)) ++y(x(15),x(12),x(15)) ++y(x(15),x(13),x(0)) ++y(x(15),x(13),x(1)) ++y(x(15),x(13),x(2)) ++y(x(15),x(13),x(3)) ++y(x(15),x(13),x(4)) ++y(x(15),x(13),x(5)) ++y(x(15),x(13),x(6)) ++y(x(15),x(13),x(7)) ++y(x(15),x(13),x(8)) ++y(x(15),x(13),x(9)) ++y(x(15),x(13),x(10)) ++y(x(15),x(13),x(11)) ++y(x(15),x(13),x(12)) ++y(x(15),x(13),x(14)) ++y(x(15),x(13),x(15)) ++y(x(15),x(14),x(0)) ++y(x(15),x(14),x(1)) ++y(x(15),x(14),x(2)) ++y(x(15),x(14),x(3)) ++y(x(15),x(14),x(4)) ++y(x(15),x(14),x(5)) ++y(x(15),x(14),x(6)) ++y(x(15),x(14),x(7)) ++y(x(15),x(14),x(8)) ++y(x(15),x(14),x(9)) ++y(x(15),x(14),x(10)) ++y(x(15),x(14),x(11)) ++y(x(15),x(14),x(12)) ++y(x(15),x(14),x(13)) ++y(x(15),x(14),x(15)) ++y(x(15),x(15),x(15)) diff --git a/lib/reasoners/eflint-haskell/tests/traces/diff_1_naf.diff0 b/lib/reasoners/eflint-haskell/tests/traces/diff_1_naf.diff0 new file mode 100644 index 0000000..f1377bc --- /dev/null +++ b/lib/reasoners/eflint-haskell/tests/traces/diff_1_naf.diff0 @@ -0,0 +1,4 @@ +New type x +New type y ++x("") ++y("") diff --git a/lib/reasoners/eflint-haskell/tests/traces/diff_2_no_stable.diff0 b/lib/reasoners/eflint-haskell/tests/traces/diff_2_no_stable.diff0 new file mode 100644 index 0000000..1aca53c --- /dev/null +++ b/lib/reasoners/eflint-haskell/tests/traces/diff_2_no_stable.diff0 @@ -0,0 +1,2 @@ +New type p ++p("") diff --git a/lib/reasoners/eflint-haskell/tests/traces/diff_3_2_stable.diff0 b/lib/reasoners/eflint-haskell/tests/traces/diff_3_2_stable.diff0 new file mode 100644 index 0000000..7ff91e9 --- /dev/null +++ b/lib/reasoners/eflint-haskell/tests/traces/diff_3_2_stable.diff0 @@ -0,0 +1,2 @@ +New type f ++f(1) diff --git a/lib/reasoners/eflint-haskell/tests/traces/diff_4_exists_forall.diff0 b/lib/reasoners/eflint-haskell/tests/traces/diff_4_exists_forall.diff0 new file mode 100644 index 0000000..ca857f3 --- /dev/null +++ b/lib/reasoners/eflint-haskell/tests/traces/diff_4_exists_forall.diff0 @@ -0,0 +1,14 @@ +New type meta +New type no_dubs +New type some +New type x ++meta("") ++no_dubs("") ++some("") ++x(2) +~no_dubs("") ++x(3) +~x(2) ++no_dubs("") +~some("") +~x(3) diff --git a/lib/reasoners/eflint-haskell/tests/traces/diff_clingo_no_model.diff0 b/lib/reasoners/eflint-haskell/tests/traces/diff_clingo_no_model.diff0 new file mode 100644 index 0000000..61246a2 --- /dev/null +++ b/lib/reasoners/eflint-haskell/tests/traces/diff_clingo_no_model.diff0 @@ -0,0 +1,16 @@ +New type x +New type y ++x(0) ++y(x(0),x(0),x(0),x(0)) ++x(1) ++y(x(0),x(0),x(0),x(1)) ++y(x(0),x(0),x(1),x(0)) ++y(x(0),x(0),x(1),x(1)) ++y(x(0),x(1),x(0),x(1)) ++y(x(0),x(1),x(1),x(0)) ++y(x(1),x(0),x(0),x(1)) ++y(x(1),x(0),x(1),x(0)) ++y(x(1),x(1),x(0),x(0)) ++y(x(1),x(1),x(0),x(1)) ++y(x(1),x(1),x(1),x(0)) ++y(x(1),x(1),x(1),x(1)) diff --git a/lib/reasoners/eflint-haskell/tests/traces/moving.same0 b/lib/reasoners/eflint-haskell/tests/traces/moving.same0 new file mode 100644 index 0000000..2238d8b --- /dev/null +++ b/lib/reasoners/eflint-haskell/tests/traces/moving.same0 @@ -0,0 +1,31 @@ +New type at +New type move +New type person +New type place ++person(string("Dan")) ++person(string("Amy")) ++person(string("Bob")) ++place(string("UvA")) ++place(string("VU")) ++at(person(string("Amy")),place(string("UvA"))) ++move(person(string("Amy")),place(string("UvA")),place(string("UvA"))) ++move(person(string("Amy")),place(string("UvA")),place(string("VU"))) ++at(person(string("Bob")),place(string("VU"))) ++move(person(string("Bob")),place(string("VU")),place(string("UvA"))) ++move(person(string("Bob")),place(string("VU")),place(string("VU"))) +executed transition: +move(person(string("Amy")),place(string("UvA")),place(string("VU"))) (ENABLED) +~move(person(string("Amy")),place(string("UvA")),place(string("UvA"))) +~move(person(string("Amy")),place(string("UvA")),place(string("VU"))) +-at(person(string("Amy")),place(string("UvA"))) ++at(person(string("Amy")),place(string("VU"))) ++move(person(string("Amy")),place(string("VU")),place(string("UvA"))) ++move(person(string("Amy")),place(string("VU")),place(string("VU"))) +executed transition: +move(person(string("Bob")),place(string("VU")),place(string("UvA"))) (ENABLED) +~move(person(string("Bob")),place(string("VU")),place(string("UvA"))) +~move(person(string("Bob")),place(string("VU")),place(string("VU"))) +-at(person(string("Bob")),place(string("VU"))) ++at(person(string("Bob")),place(string("UvA"))) ++move(person(string("Bob")),place(string("UvA")),place(string("UvA"))) ++move(person(string("Bob")),place(string("UvA")),place(string("VU"))) diff --git a/lib/reasoners/eflint-haskell/tests/traces/running-example.same0 b/lib/reasoners/eflint-haskell/tests/traces/running-example.same0 new file mode 100644 index 0000000..8fa25c7 --- /dev/null +++ b/lib/reasoners/eflint-haskell/tests/traces/running-example.same0 @@ -0,0 +1,13 @@ +New type access +New type controls +New type dataset +New type elapsed +New type instant +New type must_notify +New type notify +New type user ++user("Amy") ++instant(9) ++dataset("X-Rays") ++controls(user("Amy"),dataset("X-Rays")) ++access(user("Amy"),dataset("X-Rays"),instant(9)) diff --git a/lib/reasoners/eflint-haskell/tests/traces/syncs-with.same0 b/lib/reasoners/eflint-haskell/tests/traces/syncs-with.same0 new file mode 100644 index 0000000..d92dcff --- /dev/null +++ b/lib/reasoners/eflint-haskell/tests/traces/syncs-with.same0 @@ -0,0 +1,9 @@ +New type bar +New type foo +executed transition: +foo(string("")) (DISABLED) +violations: + disabled action: foo(string("")) ++string("Foo") ++bar(string("Foo")) ++foo(string("Foo")) From 687f47c3906de49fb33e7df44718e4eb7efeda3c Mon Sep 17 00:00:00 2001 From: Lut99 Date: Tue, 29 Apr 2025 15:15:55 +0200 Subject: [PATCH 28/82] Added haskell reasoner to toplevel module alias --- src/lib.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index 332cfb2..bb15507 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -4,7 +4,7 @@ // Created: // 08 Oct 2024, 16:13:30 // Last edited: -// 17 Oct 2024, 12:02:50 +// 29 Apr 2025, 15:15:34 // Auto updated? // Yes // @@ -15,6 +15,8 @@ /// Contains the backend reasoners. pub mod reasoners { + #[cfg(feature = "eflint-haskell-reasoner")] + pub use eflint_haskell_reasoner as eflint_haskell; #[cfg(feature = "eflint-json-reasoner")] pub use eflint_json_reasoner as eflint_json; #[cfg(feature = "no-op-reasoner")] From b9e93d601dab201669eac5b1a15110b4fffb0bcd Mon Sep 17 00:00:00 2001 From: Lut99 Date: Tue, 29 Apr 2025 15:38:18 +0200 Subject: [PATCH 29/82] Added some covenience imports --- lib/reasoners/eflint-haskell/src/lib.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/lib/reasoners/eflint-haskell/src/lib.rs b/lib/reasoners/eflint-haskell/src/lib.rs index 1e94c93..ece480e 100644 --- a/lib/reasoners/eflint-haskell/src/lib.rs +++ b/lib/reasoners/eflint-haskell/src/lib.rs @@ -4,7 +4,7 @@ // Created: // 16 Apr 2025, 23:09:00 // Last edited: -// 25 Apr 2025, 16:37:24 +// 29 Apr 2025, 15:38:06 // Auto updated? // Yes // @@ -18,3 +18,6 @@ pub mod reasonerconn; pub mod reasons; pub mod spec; pub mod trace; + +// Use some of that +pub use reasonerconn::*; From c896d264b7c2b546523a77e8488f94213fd4afc8 Mon Sep 17 00:00:00 2001 From: Lut99 Date: Tue, 29 Apr 2025 15:43:01 +0200 Subject: [PATCH 30/82] Implemented `Clone` on handlers --- lib/reasoners/eflint-haskell/src/reasons.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/reasoners/eflint-haskell/src/reasons.rs b/lib/reasoners/eflint-haskell/src/reasons.rs index 0076564..7aae06e 100644 --- a/lib/reasoners/eflint-haskell/src/reasons.rs +++ b/lib/reasoners/eflint-haskell/src/reasons.rs @@ -4,7 +4,7 @@ // Created: // 25 Apr 2025, 16:36:41 // Last edited: -// 25 Apr 2025, 16:55:09 +// 29 Apr 2025, 15:42:50 // Auto updated? // Yes // @@ -84,7 +84,7 @@ pub trait ReasonHandler { /***** LIBRARY *****/ /// Reason handler that doesn't report anything. -#[derive(Debug)] +#[derive(Clone, Debug)] pub struct SilentHandler; impl ReasonHandler for SilentHandler { type Reason = NoReason; @@ -96,7 +96,7 @@ impl ReasonHandler for SilentHandler { /// Reason handler reports only violations with a specific prefix. -#[derive(Debug)] +#[derive(Clone, Debug)] pub struct PrefixedHandler<'s> { prefix: &'s str, } @@ -135,7 +135,7 @@ impl<'s> ReasonHandler for PrefixedHandler<'s> { /// Reason handler reports everything. -#[derive(Debug)] +#[derive(Clone, Debug)] pub struct VerboseHandler; impl ReasonHandler for VerboseHandler { type Reason = Problem; From 4caf27089ed0f2fa433b944510d04ca5cc57a179 Mon Sep 17 00:00:00 2001 From: Lut99 Date: Tue, 29 Apr 2025 15:48:25 +0200 Subject: [PATCH 31/82] Added `EFlintable` impl for `String` --- lib/reasoners/eflint-haskell/src/spec.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/lib/reasoners/eflint-haskell/src/spec.rs b/lib/reasoners/eflint-haskell/src/spec.rs index ffe5d0b..1257a4e 100644 --- a/lib/reasoners/eflint-haskell/src/spec.rs +++ b/lib/reasoners/eflint-haskell/src/spec.rs @@ -4,7 +4,7 @@ // Created: // 16 Apr 2025, 23:43:13 // Last edited: -// 16 Apr 2025, 23:51:25 +// 29 Apr 2025, 15:48:13 // Auto updated? // Yes // @@ -53,6 +53,10 @@ impl EFlintable for () { #[inline] fn eflint_fmt(&self, _f: &mut Formatter<'_>) -> FResult { Ok(()) } } +impl EFlintable for String { + #[inline] + fn eflint_fmt(&self, f: &mut Formatter<'_>) -> FResult { self.fmt(f) } +} // Pointer impls impl<'a, T: ?Sized + EFlintable> EFlintable for &'a T { From 3848fa310b4006bac898841b244fde87d96146b3 Mon Sep 17 00:00:00 2001 From: Lut99 Date: Tue, 29 Apr 2025 23:43:42 +0200 Subject: [PATCH 32/82] Made it possible to construct `PrefixedHandler` --- lib/reasoners/eflint-haskell/src/reasons.rs | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/lib/reasoners/eflint-haskell/src/reasons.rs b/lib/reasoners/eflint-haskell/src/reasons.rs index 7aae06e..13a5c53 100644 --- a/lib/reasoners/eflint-haskell/src/reasons.rs +++ b/lib/reasoners/eflint-haskell/src/reasons.rs @@ -4,7 +4,7 @@ // Created: // 25 Apr 2025, 16:36:41 // Last edited: -// 29 Apr 2025, 15:42:50 +// 29 Apr 2025, 23:43:33 // Auto updated? // Yes // @@ -98,7 +98,19 @@ impl ReasonHandler for SilentHandler { /// Reason handler reports only violations with a specific prefix. #[derive(Clone, Debug)] pub struct PrefixedHandler<'s> { - prefix: &'s str, + pub prefix: &'s str, +} +impl<'s> PrefixedHandler<'s> { + /// Constructor for the PrefixedHandler. + /// + /// # Arguments + /// - `prefix`: The prefix to match violations on. + /// + /// # Returns + /// A new PrefixedHandler that will only pass violations to the user that violate something + /// starting with the given prefix. + #[inline] + pub const fn new(prefix: &'s str) -> Self { Self { prefix } } } impl<'s> ReasonHandler for PrefixedHandler<'s> { type Reason = OptReason; From cfbd38d1cdbc61c471480104aac12f653baa3a85 Mon Sep 17 00:00:00 2001 From: Lut99 Date: Thu, 1 May 2025 10:18:52 +0200 Subject: [PATCH 33/82] Added various `Deserialize` and `Serialize` impls --- lib/reasoners/eflint-haskell/src/reasons.rs | 10 +++--- lib/reasoners/eflint-haskell/src/trace.rs | 37 +++++++++++---------- 2 files changed, 25 insertions(+), 22 deletions(-) diff --git a/lib/reasoners/eflint-haskell/src/reasons.rs b/lib/reasoners/eflint-haskell/src/reasons.rs index 13a5c53..e7cd9c5 100644 --- a/lib/reasoners/eflint-haskell/src/reasons.rs +++ b/lib/reasoners/eflint-haskell/src/reasons.rs @@ -4,7 +4,7 @@ // Created: // 25 Apr 2025, 16:36:41 // Last edited: -// 29 Apr 2025, 23:43:33 +// 01 May 2025, 10:17:09 // Auto updated? // Yes // @@ -14,12 +14,14 @@ use std::fmt::{Display, Formatter, Result as FResult}; +use serde::{Deserialize, Serialize}; + use crate::trace::Violation; /***** AUXILLARY *****/ /// Defines an empty reason. -#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] +#[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)] pub struct NoReason; impl Display for NoReason { #[inline] @@ -27,7 +29,7 @@ impl Display for NoReason { } /// Defines an optional reason. -#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] +#[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)] pub struct OptReason(pub Option); impl Display for OptReason { #[inline] @@ -42,7 +44,7 @@ impl Display for OptReason { /// Defines either a [`Query`] or a [`Violation`]. -#[derive(Clone, Debug, Eq, PartialEq)] +#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)] pub enum Problem { QueryFailed, Violation(Violation), diff --git a/lib/reasoners/eflint-haskell/src/trace.rs b/lib/reasoners/eflint-haskell/src/trace.rs index 0c051e6..a05b4a2 100644 --- a/lib/reasoners/eflint-haskell/src/trace.rs +++ b/lib/reasoners/eflint-haskell/src/trace.rs @@ -4,7 +4,7 @@ // Created: // 17 Apr 2025, 00:06:39 // Last edited: -// 25 Apr 2025, 17:26:06 +// 01 May 2025, 10:18:38 // Auto updated? // Yes // @@ -18,6 +18,7 @@ use std::error; use std::fmt::{Display, Formatter, Result as FResult}; use std::str::FromStr; +use serde::{Deserialize, Serialize}; use thiserror::Error; @@ -109,7 +110,7 @@ pub trait FromStrHead { /***** LIBRARY *****/ /// Defines a trace as a whole. -#[derive(Clone, Debug, Eq, PartialEq)] +#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)] pub struct Trace { /// The deltas emitted by eFLINT. pub deltas: Vec, @@ -153,7 +154,7 @@ impl FromStrHead for Trace { /// Defines a delta, which is like the toplevel instance of the trace. -#[derive(Clone, Debug, Eq, PartialEq)] +#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)] pub enum Delta { /// A type is marked as an invariant. NewInvariant(NewInvariant), @@ -211,7 +212,7 @@ impl FromStrHead for Vec { } /// Defines an invariant definition. -#[derive(Clone, Debug, Eq, PartialEq)] +#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)] pub struct NewInvariant { /// The name of the newly defined invariant. pub name: String, @@ -240,7 +241,7 @@ impl FromStrHead for NewInvariant { } /// Defines a type definition. -#[derive(Clone, Debug, Eq, PartialEq)] +#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)] pub struct NewType { /// The name of the newly defined type. pub name: String, @@ -271,7 +272,7 @@ impl FromStrHead for NewType { /// Defines the answer to a query. /// /// Note this is just the answer. The rest we wouldn't know. -#[derive(Clone, Copy, Debug, Eq, PartialEq)] +#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)] pub enum Query { /// The answer is yes Succes, @@ -308,7 +309,7 @@ impl FromStrHead for Query { /// Defines a postulation delta. /// /// This means that a new fact has been created, terminated or obfuscated. -#[derive(Clone, Debug, Eq, PartialEq)] +#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)] pub struct Postulation { /// The operation applied. pub op: PostulationOp, @@ -348,7 +349,7 @@ impl FromStrHead for Postulation { } /// Defines the possible kinds of postulation. -#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] +#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)] pub enum PostulationOp { /// Transitioning a fact to be true (`+`). Create, @@ -382,7 +383,7 @@ impl FromStrHead for PostulationOp { } /// Defines a triggered instance. -#[derive(Clone, Debug, Eq, PartialEq)] +#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)] pub struct Trigger { /// The triggered instance. pub inst: Instance, @@ -469,7 +470,7 @@ impl FromStrHead for Vec { } /// Defines any violation. -#[derive(Clone, Debug, Eq, PartialEq)] +#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)] pub enum Violation { /// An action has been violated. Act(ActViolation), @@ -519,7 +520,7 @@ impl FromStrHead for Vec { } /// Defines the violation of an act. -#[derive(Clone, Debug, Eq, PartialEq)] +#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)] pub struct ActViolation { /// The violated instance. pub inst: Composite, @@ -548,7 +549,7 @@ impl FromStrHead for ActViolation { } /// Defines the violation of a duty. -#[derive(Clone, Debug, Eq, PartialEq)] +#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)] pub struct DutyViolation { /// The violated instance. pub inst: Composite, @@ -577,7 +578,7 @@ impl FromStrHead for DutyViolation { } /// Defines the violation of an invariant. -#[derive(Clone, Debug, Eq, PartialEq)] +#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)] pub struct InvariantViolation { /// The violated invariant. pub name: String, @@ -608,7 +609,7 @@ impl FromStrHead for InvariantViolation { /// Defines an instance. -#[derive(Clone, Debug, Eq, PartialEq)] +#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)] pub enum Instance { /// A naked string literal. StringLit(StringLit), @@ -646,7 +647,7 @@ impl FromStrHead for Instance { } /// Defines a string literal. -#[derive(Clone, Debug, Eq, PartialEq)] +#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)] pub struct StringLit(pub String); impl Display for StringLit { #[inline] @@ -703,7 +704,7 @@ impl FromStrHead for StringLit { } /// Defines an integer literal. -#[derive(Clone, Copy, Debug, Eq, PartialEq)] +#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)] pub struct IntLit(pub i64); impl Display for IntLit { #[inline] @@ -760,7 +761,7 @@ impl FromStrHead for IntLit { } /// Defines a composite type. -#[derive(Clone, Debug, Eq, PartialEq)] +#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)] pub struct Composite { /// The name of the type. pub name: String, @@ -821,7 +822,7 @@ impl FromStrHead for Composite { /// Parses an eFLINT type name. -#[derive(Clone, Debug, Eq, PartialEq)] +#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)] pub struct TypeName(pub String); impl FromStrHead for TypeName { type Error = Error; From 057cce70d5fe3fe46f69699ec0f9feeabd4c449b Mon Sep 17 00:00:00 2001 From: Lut99 Date: Thu, 1 May 2025 10:21:40 +0200 Subject: [PATCH 34/82] `PrefixedHandler` now stores a `Cow` instead of a reference --- lib/reasoners/eflint-haskell/src/reasons.rs | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/lib/reasoners/eflint-haskell/src/reasons.rs b/lib/reasoners/eflint-haskell/src/reasons.rs index e7cd9c5..eadd977 100644 --- a/lib/reasoners/eflint-haskell/src/reasons.rs +++ b/lib/reasoners/eflint-haskell/src/reasons.rs @@ -4,7 +4,7 @@ // Created: // 25 Apr 2025, 16:36:41 // Last edited: -// 01 May 2025, 10:17:09 +// 01 May 2025, 10:21:21 // Auto updated? // Yes // @@ -12,6 +12,7 @@ //! Defines reason handlers for the haskell interpreter. // +use std::borrow::Cow; use std::fmt::{Display, Formatter, Result as FResult}; use serde::{Deserialize, Serialize}; @@ -100,7 +101,7 @@ impl ReasonHandler for SilentHandler { /// Reason handler reports only violations with a specific prefix. #[derive(Clone, Debug)] pub struct PrefixedHandler<'s> { - pub prefix: &'s str, + pub prefix: Cow<'s, str>, } impl<'s> PrefixedHandler<'s> { /// Constructor for the PrefixedHandler. @@ -112,7 +113,7 @@ impl<'s> PrefixedHandler<'s> { /// A new PrefixedHandler that will only pass violations to the user that violate something /// starting with the given prefix. #[inline] - pub const fn new(prefix: &'s str) -> Self { Self { prefix } } + pub fn new(prefix: impl Into>) -> Self { Self { prefix: prefix.into() } } } impl<'s> ReasonHandler for PrefixedHandler<'s> { type Reason = OptReason; @@ -122,21 +123,21 @@ impl<'s> ReasonHandler for PrefixedHandler<'s> { match problem { Problem::QueryFailed => OptReason(None), Problem::Violation(Violation::Act(a)) => { - if a.inst.name.starts_with(self.prefix) { + if a.inst.name.starts_with(self.prefix.as_ref()) { OptReason(Some(Violation::Act(a))) } else { OptReason(None) } }, Problem::Violation(Violation::Duty(d)) => { - if d.inst.name.starts_with(self.prefix) { + if d.inst.name.starts_with(self.prefix.as_ref()) { OptReason(Some(Violation::Duty(d))) } else { OptReason(None) } }, Problem::Violation(Violation::Invariant(i)) => { - if i.name.starts_with(self.prefix) { + if i.name.starts_with(self.prefix.as_ref()) { OptReason(Some(Violation::Invariant(i))) } else { OptReason(None) From ac5993771dffd653331af169f827207c444e13fb Mon Sep 17 00:00:00 2001 From: Lut99 Date: Thu, 1 May 2025 15:25:36 +0200 Subject: [PATCH 35/82] Added notion of a base policy to this layer of the reasoner, as we're including it as command-line argument instead of through stdin (which is WAY faster) --- Cargo.lock | 8 + lib/reasoners/eflint-haskell/Cargo.toml | 2 + lib/reasoners/eflint-haskell/src/hash.rs | 271 ++++++++++++++++++ lib/reasoners/eflint-haskell/src/lib.rs | 3 +- .../eflint-haskell/src/reasonerconn.rs | 31 +- 5 files changed, 311 insertions(+), 4 deletions(-) create mode 100644 lib/reasoners/eflint-haskell/src/hash.rs diff --git a/Cargo.lock b/Cargo.lock index 4516924..4ff650a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -129,6 +129,12 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "base16ct" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf" + [[package]] name = "base64" version = "0.22.1" @@ -310,8 +316,10 @@ dependencies = [ name = "eflint-haskell-reasoner" version = "0.1.0" dependencies = [ + "base16ct", "error-trace 3.1.0", "serde", + "sha2", "specifications", "thiserror 2.0.12", "tokio", diff --git a/lib/reasoners/eflint-haskell/Cargo.toml b/lib/reasoners/eflint-haskell/Cargo.toml index c209a5e..04deb5e 100644 --- a/lib/reasoners/eflint-haskell/Cargo.toml +++ b/lib/reasoners/eflint-haskell/Cargo.toml @@ -9,9 +9,11 @@ license.workspace = true [dependencies] +base16ct = { version = "0.2.0", features = ["std"] } # reqwest = { version = "0.12.0", features = ["json"] } serde = { version = "1.0.204", features = ["derive"] } # serde_json = "1.0.120" +sha2 = "0.10.6" thiserror = "2.0.12" tokio = { version = "1.44.2", default-features = false, features = ["io-util", "process"] } tracing = "0.1.40" diff --git a/lib/reasoners/eflint-haskell/src/hash.rs b/lib/reasoners/eflint-haskell/src/hash.rs new file mode 100644 index 0000000..21a39f1 --- /dev/null +++ b/lib/reasoners/eflint-haskell/src/hash.rs @@ -0,0 +1,271 @@ +// HASH.rs +// by Lut99 +// +// Created: +// 01 May 2025, 14:33:06 +// Last edited: +// 01 May 2025, 15:24:08 +// Auto updated? +// Yes +// +// Description: +//! Provides functions to compute hashes of eFLINT specs. +// + +use std::borrow::Cow; +use std::collections::HashSet; +use std::fmt::{Display, Formatter, Result as FResult}; +use std::io::Write; +use std::path::{Path, PathBuf}; + +use sha2::{Digest as _, Sha256}; +use thiserror::Error; +use tokio::fs::File; +use tokio::io::AsyncReadExt as _; +use tracing::{Level, debug, span}; + + +/***** ERRORS *****/ +/// Formats a list but like, prettily. +struct PrettyPathListFormatter<'l>(&'l [PathBuf]); +impl<'l> Display for PrettyPathListFormatter<'l> { + #[inline] + fn fmt(&self, f: &mut Formatter<'_>) -> FResult { + if self.0.is_empty() { + return write!(f, ""); + } + for (i, path) in self.0.into_iter().enumerate() { + if i > 0 && i < self.0.len() - 1 { + write!(f, ", ")?; + } else if i > 0 { + write!(f, " or ")?; + } + write!(f, "{:?}", path.display())?; + } + Ok(()) + } +} + +/// Errors emitted by [`compute_policy_hash()`]. +#[derive(Debug, Error)] +pub enum Error { + #[error("Failed to open file {:?}", path.display())] + FileOpen { path: PathBuf, source: std::io::Error }, + #[error("Failed to read from file {:?}", path.display())] + FileRead { path: PathBuf, source: std::io::Error }, + #[error("Failed to find dependency {path:?} in {}", PrettyPathListFormatter(include_dirs))] + ImportNotFound { path: String, include_dirs: Vec }, +} + + + + + +/***** HELPERS *****/ +/// Represents which of the two import types we've seen. +enum Import { + Include, + Require, +} +impl Import { + /// Returns the keyword we're parsing. + #[inline] + pub const fn keyword(&self) -> &'static [u8] { + match self { + Self::Include => b"#include", + Self::Require => b"#require", + } + } +} + +/// Defines the parsing states. +enum State { + /// Waiting for the pound `#` symbol. + Pound, + /// Waiting to see if the pound symbol is followed by `i` or `r`. + IncludeOrRequire, + /// Parsing the full `#include`- or `#require`-keyword. The number indicates the index up to which point we've already parsed. + Import(Import, usize), + /// We're parsing the keyword. Now parse the start of the path string. + PathStart(Import), + /// We're now actively parsing the path. + Path(Import, Vec), + /// We've parsed the path. Just the dot to go now. + Dot(Import, Vec), +} + + + + + +/***** IMPLEMENTATION *****/ +/// Does the heavy-lifting of [`compute_policy_hash()`]. +async fn compute_policy_hash_of( + mut handle: File, + path: &Path, + base_path: &Path, + include_dirs: &[&Path], + included: &mut HashSet, + res: &mut Sha256, +) -> Result<(), Error> { + debug!("Computing policy hash of {:?}", path.display()); + + // Go through the file chunk-by-chunk + let mut state = State::Pound; + let mut buf: [u8; 2048] = [0; 2048]; + loop { + // Read a chunk + let buf_len: usize = handle.read(&mut buf).await.map_err(|source| Error::FileRead { path: path.into(), source })?; + if buf_len == 0 { + return Ok(()); + } + + // Evaluate it + let mut i: usize = 0; + while i < buf_len { + let b: u8 = buf[i]; + match state { + State::Pound if b == b'#' => { + i += 1; + state = State::IncludeOrRequire; + }, + State::Pound => { + res.write_all(&[b]).unwrap(); + i += 1; + }, + + State::IncludeOrRequire if b == b'i' => { + i += 1; + state = State::Import(Import::Include, 2); + }, + State::IncludeOrRequire if b == b'r' => { + i += 1; + state = State::Import(Import::Require, 2); + }, + State::IncludeOrRequire => { + // Write the `#` we memorized, then try this byte again + res.write_all(&[b'#']).unwrap(); + state = State::Pound; + }, + + // Either we've 1) completed, 2) found an correct char or 3) found an incorrect char. + State::Import(imp, j) if j >= imp.keyword().len() => { + // Don't increment, this byte may be the start already + state = State::PathStart(imp); + }, + State::Import(imp, j) if j < imp.keyword().len() && b == imp.keyword()[j] => { + i += 1; + state = State::Import(Import::Include, j + 1); + }, + State::Import(imp, j) => { + // Write that which we've parsed, then try this byte as if fresh + res.write_all(&imp.keyword()[..j]).unwrap(); + state = State::Pound; + }, + + State::PathStart(imp) if b == b'"' => { + i += 1; + state = State::Path(imp, Vec::with_capacity(32)); + }, + State::PathStart(imp) if (b as char).is_whitespace() => { + // These we skip idly. We don't even write the hash, as it's idle space AND we don't want to accidentally mix up the order if the next byte reveals we were wrong. + i += 1; + state = State::PathStart(imp); + }, + State::PathStart(imp) => { + // Not it after all + res.write_all(imp.keyword()).unwrap(); + state = State::Pound; + }, + + // Note: we don't escape these strings + State::Path(imp, mut path) if b != b'"' => { + path.push(b); + i += 1; + state = State::Path(imp, path); + }, + State::Path(imp, path) => { + // Just wait for the dot! + i += 1; + state = State::Dot(imp, path); + }, + + State::Dot(imp, path) if b == b'.' => { + // We've successfully parsed a chunk! Let's get the path as a path + let path: Cow = String::from_utf8_lossy(&path); + let path: &Path = >::as_ref(path.as_ref()); + + // Include the file if it's an include OR we've never included it before. + if matches!(imp, Import::Include) || !included.contains(path) { + Box::pin(compute_policy_hash_of( + File::open(path).await.map_err(|source| Error::FileOpen { path: path.into(), source })?, + path, + base_path, + include_dirs, + included, + res, + )) + .await?; + } + included.insert(path.into()); + + // Now continue as before with the next byte + i += 1; + state = State::Pound; + }, + State::Dot(imp, path) if (b as char).is_whitespace() => { + // Idly skip + i += 1; + state = State::Dot(imp, path); + }, + State::Dot(imp, path) => { + // Not it AFTER ALL + res.write_all(imp.keyword()).unwrap(); + res.write_all(&[b'"']).unwrap(); + res.write_all(&path).unwrap(); + res.write_all(&[b'"']).unwrap(); + state = State::Pound; + }, + } + } + } +} + + + + + +/***** LIBRARY *****/ +/// Recursively computes the hash of the given eFLINT file. +/// +/// This is non-trivial as any imports will have to be chased. +/// +/// # Arguments +/// - `path`: The path to the eFLINT file to hash (which, in turn, specifies the dependencies). +/// - `include_dirs`: Any additional include directories to use for the search. By default, the +/// current working directory, the directory of the given file and the directory of the currently +/// recursed file are included. +/// +/// # Returns +/// The hash of the policy, as a 256-bit byte array. +/// +/// # Errors +/// This function may error if we failed to open the given `path` as a file, or failed to find any +/// of the (recursive) dependencies. +pub async fn compute_policy_hash(path: impl AsRef, include_dirs: &[&Path]) -> Result<[u8; 32], Error> { + let path: &Path = path.as_ref(); + let _span = span!(Level::DEBUG, "compute_policy_hash", file = path.display().to_string()); + + // We use the excellent Write impl of `Sha256` + let mut hasher = Sha256::new(); + compute_policy_hash_of( + File::open(path).await.map_err(|source| Error::FileOpen { path: path.into(), source })?, + path, + path, + &include_dirs, + &mut HashSet::new(), + &mut hasher, + ) + .await?; + Ok(hasher.finalize().into()) +} diff --git a/lib/reasoners/eflint-haskell/src/lib.rs b/lib/reasoners/eflint-haskell/src/lib.rs index ece480e..ce815c5 100644 --- a/lib/reasoners/eflint-haskell/src/lib.rs +++ b/lib/reasoners/eflint-haskell/src/lib.rs @@ -4,7 +4,7 @@ // Created: // 16 Apr 2025, 23:09:00 // Last edited: -// 29 Apr 2025, 15:38:06 +// 01 May 2025, 14:33:35 // Auto updated? // Yes // @@ -14,6 +14,7 @@ // // Define the submodules +pub mod hash; pub mod reasonerconn; pub mod reasons; pub mod spec; diff --git a/lib/reasoners/eflint-haskell/src/reasonerconn.rs b/lib/reasoners/eflint-haskell/src/reasonerconn.rs index d946a84..1b8c56a 100644 --- a/lib/reasoners/eflint-haskell/src/reasonerconn.rs +++ b/lib/reasoners/eflint-haskell/src/reasonerconn.rs @@ -4,7 +4,7 @@ // Created: // 16 Apr 2025, 23:09:26 // Last edited: -// 25 Apr 2025, 16:40:44 +// 01 May 2025, 15:24:48 // Auto updated? // Yes // @@ -16,6 +16,7 @@ use std::borrow::Cow; use std::future::Future; use std::marker::PhantomData; +use std::path::PathBuf; use std::process::{ExitStatus, Stdio}; use std::str::FromStr as _; @@ -29,6 +30,7 @@ use tokio::io::AsyncWriteExt; use tokio::process::Command; use tracing::{debug, warn}; +use crate::hash::compute_policy_hash; use crate::reasons::{Problem, ReasonHandler}; use crate::spec::{EFlintable, EFlintableExt as _}; use crate::trace::{Delta, Trace}; @@ -52,6 +54,9 @@ pub enum Error { #[source] err: error_trace::Trace, }, + /// Failed to hash the input policy. + #[error("Failed to hash the input policy {:?}", path.display())] + PolicyHash { path: PathBuf, source: crate::hash::Error }, #[error("Empty REPL-command given")] EmptyReplCommand, @@ -135,6 +140,10 @@ pub struct EFlintHaskellReasonerContextFull { // The private part /// A command to call the eFLINT reasoner. pub cmd: (String, Vec), + /// The base policy to provide to the eFLINT reasoner. + pub base_policy: PathBuf, + /// A hash of the base policy calculated at construction time. + pub base_policy_hash: [u8; 32], } impl ReasonerContext for EFlintHaskellReasonerContextFull { #[inline] @@ -170,6 +179,9 @@ impl EFlintHaskellReasonerConnector { /// /// # Arguments /// - `cmd`: Some command that is used to call the eFLINT reasoner. + /// - `base_policy_path`: A path to an eFLINT file containing the base policy to load. We load + /// this as a file instead of a string since that is MUCH more efficient than feeding large + /// files to eFLINT by pipe. /// - `handler`: Some [`ReasonHandler`] that can be used to determine what information to return to the user upon failure. /// - `logger`: An [`AuditLogger`] for logging the reasoning context with. /// @@ -178,14 +190,26 @@ impl EFlintHaskellReasonerConnector { /// /// # Errors /// This function can error if it failed to log the initial context to the given `logger`. - pub async fn new_async<'l, L: AuditLogger>(cmd: impl IntoIterator, handler: R, logger: &'l L) -> Result { + pub async fn new_async<'l, L: AuditLogger>( + cmd: impl IntoIterator, + base_policy_path: impl Into, + handler: R, + logger: &'l L, + ) -> Result { + let base_policy: PathBuf = base_policy_path.into(); + // Get the command and split it in a program and arguments let mut cmd: Vec = cmd.into_iter().collect(); let exec: Option = cmd.pop(); let cmd: (String, Vec) = (exec.ok_or(Error::EmptyReplCommand)?, cmd); + // Compute the hash of the input policy + let base_policy_hash: [u8; 32] = + compute_policy_hash(&base_policy, &[]).await.map_err(|source| Error::PolicyHash { path: base_policy.clone(), source })?; + // Build the context & log it - let context: EFlintHaskellReasonerContextFull = EFlintHaskellReasonerContextFull { public: EFlintHaskellReasonerContext::default(), cmd }; + let context: EFlintHaskellReasonerContextFull = + EFlintHaskellReasonerContextFull { public: EFlintHaskellReasonerContext::default(), cmd, base_policy, base_policy_hash }; logger.log_context(&context).await.map_err(|err| Error::LogContext { to: std::any::type_name::(), err: err.freeze() })?; // OK, return ourselves @@ -230,6 +254,7 @@ where // Prepare the command to execute let mut cmd = Command::new(&self.context.cmd.0); cmd.args(&self.context.cmd.1); + cmd.arg(&self.context.base_policy); cmd.stdin(Stdio::piped()); cmd.stdout(Stdio::piped()); cmd.stderr(Stdio::piped()); From 467469beb883ff858ac24a354bcf19db9d5dcdf4 Mon Sep 17 00:00:00 2001 From: Lut99 Date: Thu, 1 May 2025 15:56:14 +0200 Subject: [PATCH 36/82] Added functions to get full context from reasoner connector. Also refactored `hash.rs` a bit in order to first find all deps, then hash the files (to be more accurate & allow this to be done externally). --- lib/reasoners/eflint-haskell/src/hash.rs | 207 ++++++++++++------ .../eflint-haskell/src/reasonerconn.rs | 28 ++- 2 files changed, 164 insertions(+), 71 deletions(-) diff --git a/lib/reasoners/eflint-haskell/src/hash.rs b/lib/reasoners/eflint-haskell/src/hash.rs index 21a39f1..7e65698 100644 --- a/lib/reasoners/eflint-haskell/src/hash.rs +++ b/lib/reasoners/eflint-haskell/src/hash.rs @@ -4,7 +4,7 @@ // Created: // 01 May 2025, 14:33:06 // Last edited: -// 01 May 2025, 15:24:08 +// 01 May 2025, 15:54:30 // Auto updated? // Yes // @@ -53,8 +53,10 @@ pub enum Error { FileOpen { path: PathBuf, source: std::io::Error }, #[error("Failed to read from file {:?}", path.display())] FileRead { path: PathBuf, source: std::io::Error }, - #[error("Failed to find dependency {path:?} in {}", PrettyPathListFormatter(include_dirs))] - ImportNotFound { path: String, include_dirs: Vec }, + #[error("Failed to get current working directory")] + GetCwd { source: std::io::Error }, + #[error("Failed to find dependency {path:?} as {}", PrettyPathListFormatter(imppaths))] + ImportNotFound { path: PathBuf, imppaths: Vec }, } @@ -86,12 +88,12 @@ enum State { IncludeOrRequire, /// Parsing the full `#include`- or `#require`-keyword. The number indicates the index up to which point we've already parsed. Import(Import, usize), - /// We're parsing the keyword. Now parse the start of the path string. - PathStart(Import), + /// We've parsed the keyword. Now parse the start of the path string. + PathStart, /// We're now actively parsing the path. - Path(Import, Vec), + Path(Vec), /// We've parsed the path. Just the dot to go now. - Dot(Import, Vec), + Dot(Vec), } @@ -100,15 +102,8 @@ enum State { /***** IMPLEMENTATION *****/ /// Does the heavy-lifting of [`compute_policy_hash()`]. -async fn compute_policy_hash_of( - mut handle: File, - path: &Path, - base_path: &Path, - include_dirs: &[&Path], - included: &mut HashSet, - res: &mut Sha256, -) -> Result<(), Error> { - debug!("Computing policy hash of {:?}", path.display()); +async fn find_deps_of(mut handle: File, path: &Path, base_path: &Path, include_dirs: &[&Path], files: &mut HashSet) -> Result<(), Error> { + debug!("Searching for dependencies of eFLINT file {:?}", path.display()); // Go through the file chunk-by-chunk let mut state = State::Pound; @@ -130,7 +125,6 @@ async fn compute_policy_hash_of( state = State::IncludeOrRequire; }, State::Pound => { - res.write_all(&[b]).unwrap(); i += 1; }, @@ -143,87 +137,114 @@ async fn compute_policy_hash_of( state = State::Import(Import::Require, 2); }, State::IncludeOrRequire => { - // Write the `#` we memorized, then try this byte again - res.write_all(&[b'#']).unwrap(); + // Try this byte again state = State::Pound; }, // Either we've 1) completed, 2) found an correct char or 3) found an incorrect char. State::Import(imp, j) if j >= imp.keyword().len() => { // Don't increment, this byte may be the start already - state = State::PathStart(imp); + state = State::PathStart; }, State::Import(imp, j) if j < imp.keyword().len() && b == imp.keyword()[j] => { i += 1; - state = State::Import(Import::Include, j + 1); + state = State::Import(imp, j + 1); }, - State::Import(imp, j) => { - // Write that which we've parsed, then try this byte as if fresh - res.write_all(&imp.keyword()[..j]).unwrap(); + State::Import(_, _) => { + // Try this byte as if it's afresh state = State::Pound; }, - State::PathStart(imp) if b == b'"' => { + State::PathStart if b == b'"' => { i += 1; - state = State::Path(imp, Vec::with_capacity(32)); + state = State::Path(Vec::with_capacity(32)); }, - State::PathStart(imp) if (b as char).is_whitespace() => { + State::PathStart if (b as char).is_whitespace() => { // These we skip idly. We don't even write the hash, as it's idle space AND we don't want to accidentally mix up the order if the next byte reveals we were wrong. i += 1; - state = State::PathStart(imp); + state = State::PathStart; }, - State::PathStart(imp) => { + State::PathStart => { // Not it after all - res.write_all(imp.keyword()).unwrap(); state = State::Pound; }, // Note: we don't escape these strings - State::Path(imp, mut path) if b != b'"' => { - path.push(b); + State::Path(mut imppath) if b != b'"' => { + imppath.push(b); i += 1; - state = State::Path(imp, path); + state = State::Path(imppath); }, - State::Path(imp, path) => { + State::Path(imppath) => { // Just wait for the dot! i += 1; - state = State::Dot(imp, path); + state = State::Dot(imppath); }, - State::Dot(imp, path) if b == b'.' => { + State::Dot(imppath) if b == b'.' => { // We've successfully parsed a chunk! Let's get the path as a path - let path: Cow = String::from_utf8_lossy(&path); - let path: &Path = >::as_ref(path.as_ref()); - - // Include the file if it's an include OR we've never included it before. - if matches!(imp, Import::Include) || !included.contains(path) { - Box::pin(compute_policy_hash_of( - File::open(path).await.map_err(|source| Error::FileOpen { path: path.into(), source })?, - path, - base_path, - include_dirs, - included, - res, - )) - .await?; + let imppath: Cow = String::from_utf8_lossy(&imppath); + let imppath: &Path = >::as_ref(imppath.as_ref()); + + // Attempt to resolve the path if it's relative + let imppaths: Vec> = if imppath.is_relative() { + let mut imppaths = Vec::with_capacity(3 + include_dirs.len()); + imppaths.push(Cow::Owned(std::env::current_dir().map_err(|source| Error::GetCwd { source })?)); + if let Some(parent) = base_path.parent() { + imppaths.push(Cow::Owned(parent.join(imppath))); + } + if let Some(parent) = path.parent() { + imppaths.push(Cow::Owned(parent.join(imppath))); + } + for dir in include_dirs { + imppaths.push(Cow::Owned(dir.join(imppath))); + } + imppaths + } else { + vec![Cow::Borrowed(imppath)] + }; + + // Recurse & then update the dependency list + let mut found: bool = false; + for imppath in &imppaths { + // Ensure it exists + if !imppath.exists() { + continue; + } + found = true; + + // If we haven't added it yet, add it + if !files.contains(path) { + Box::pin(find_deps_of( + File::open(path).await.map_err(|source| Error::FileOpen { path: path.into(), source })?, + path, + base_path, + include_dirs, + files, + )) + .await?; + } + files.insert(path.into()); + break; + } + if !found { + return Err(Error::ImportNotFound { + path: imppath.into(), + imppaths: imppaths.into_iter().map(Cow::into_owned).collect(), + }); } - included.insert(path.into()); // Now continue as before with the next byte i += 1; state = State::Pound; }, - State::Dot(imp, path) if (b as char).is_whitespace() => { + State::Dot(imppath) if (b as char).is_whitespace() => { // Idly skip i += 1; - state = State::Dot(imp, path); + state = State::Dot(imppath); }, - State::Dot(imp, path) => { + State::Dot(_) => { // Not it AFTER ALL - res.write_all(imp.keyword()).unwrap(); - res.write_all(&[b'"']).unwrap(); - res.write_all(&path).unwrap(); - res.write_all(&[b'"']).unwrap(); state = State::Pound; }, } @@ -236,6 +257,38 @@ async fn compute_policy_hash_of( /***** LIBRARY *****/ +/// Finds all dependencies of the given eFLINT file. +/// +/// This will read the file, search for `#include`- and `#require`-directives, and then recursively +/// search those files until one set of files is returned. +/// +/// # Arguments +/// - `path`: The path to the eFLINT file to hash (which, in turn, specifies the dependencies). +/// - `include_dirs`: Any additional include directories to use for the search. By default, the +/// current working directory, the directory of the given file and the directory of the currently +/// recursed file are included. +/// +/// # Returns +/// A [`HashSet`] of [`PathBuf`]s encoding the found files. This includes the given `path`. +/// +/// # Errors +/// This function may error if we failed to open the given `path` as a file, or failed to find any +/// of the (recursive) dependencies. +pub async fn find_deps(path: impl AsRef, include_dirs: &[&Path]) -> Result, Error> { + let path: &Path = path.as_ref(); + let _span = span!(Level::DEBUG, "find_deps", file = path.display().to_string()); + + // Delegate to the recursive function + let mut res: HashSet = HashSet::with_capacity(16); + find_deps_of(File::open(path).await.map_err(|source| Error::FileOpen { path: path.into(), source })?, path, path, &include_dirs, &mut res) + .await?; + + // Done + Ok(res) +} + + + /// Recursively computes the hash of the given eFLINT file. /// /// This is non-trivial as any imports will have to be chased. @@ -253,19 +306,33 @@ async fn compute_policy_hash_of( /// This function may error if we failed to open the given `path` as a file, or failed to find any /// of the (recursive) dependencies. pub async fn compute_policy_hash(path: impl AsRef, include_dirs: &[&Path]) -> Result<[u8; 32], Error> { - let path: &Path = path.as_ref(); - let _span = span!(Level::DEBUG, "compute_policy_hash", file = path.display().to_string()); + // Find the set of all files first + let files: HashSet = find_deps(path, include_dirs).await?; + + // Order them + let mut files: Vec = files.into_iter().collect(); + files.sort(); - // We use the excellent Write impl of `Sha256` + // Now hash all of the files let mut hasher = Sha256::new(); - compute_policy_hash_of( - File::open(path).await.map_err(|source| Error::FileOpen { path: path.into(), source })?, - path, - path, - &include_dirs, - &mut HashSet::new(), - &mut hasher, - ) - .await?; + for file in files { + // Open the file + debug!("Hashing eFLINT file {:?}", file.display()); + let mut handle = File::open(&file).await.map_err(|source| Error::FileOpen { path: file.clone(), source })?; + let mut buf: [u8; 16384] = [0; 16384]; + loop { + // Read a chunk + let buf_len: usize = handle.read(&mut buf).await.map_err(|source| Error::FileRead { path: file.clone(), source })?; + if buf_len == 0 { + break; + } + + // Write it to the hasher + // SAFETY: It's a hasher, what's it gonna do, crash on me? + hasher.write_all(&buf[..buf_len]).unwrap(); + } + } + + // Done Ok(hasher.finalize().into()) } diff --git a/lib/reasoners/eflint-haskell/src/reasonerconn.rs b/lib/reasoners/eflint-haskell/src/reasonerconn.rs index 1b8c56a..7de60a6 100644 --- a/lib/reasoners/eflint-haskell/src/reasonerconn.rs +++ b/lib/reasoners/eflint-haskell/src/reasonerconn.rs @@ -4,7 +4,7 @@ // Created: // 16 Apr 2025, 23:09:26 // Last edited: -// 01 May 2025, 15:24:48 +// 01 May 2025, 15:55:27 // Auto updated? // Yes // @@ -215,6 +215,32 @@ impl EFlintHaskellReasonerConnector { // OK, return ourselves Ok(Self { context, handler, _state: PhantomData, _question: PhantomData }) } + + /// Returns the command used to call the `eflint-repl` binary. + /// + /// # Returns + /// A pair of the executable and a list of arguments that represents the command. + #[inline] + pub const fn cmd(&self) -> &(String, Vec) { &self.context.cmd } + + /// Returns the path of the base policy provided to every reasoner call. + /// + /// Note that the given file may depend on other eFLINT files. If you want to find all files, + /// then call [`find_deps()`](crate::hash::find_deps()) on the resulting file. + /// + /// # Returns + /// A [`Path`] representing this file. + #[inline] + pub const fn base_policy(&self) -> &PathBuf { &self.context.base_policy } + + /// Returns the hash of the base policy provided to every reasoner call. + /// + /// # Returns + /// A binary string of 32 bytes (256-bit) that represents a secure SHA256 hash. + /// + /// Note: hashing the file and any file it depends on manually + #[inline] + pub const fn base_policy_hash(&self) -> &[u8; 32] { &self.context.base_policy_hash } } impl ReasonerConnector for EFlintHaskellReasonerConnector where From 6cf13ca86407faabeea7404c36f6ee30c2756ad0 Mon Sep 17 00:00:00 2001 From: Lut99 Date: Thu, 1 May 2025 15:59:00 +0200 Subject: [PATCH 37/82] Removed obsolete `base16ct` dependency --- Cargo.lock | 7 ------- lib/reasoners/eflint-haskell/Cargo.toml | 1 - 2 files changed, 8 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 4ff650a..9d5cf75 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -129,12 +129,6 @@ dependencies = [ "windows-targets 0.52.6", ] -[[package]] -name = "base16ct" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf" - [[package]] name = "base64" version = "0.22.1" @@ -316,7 +310,6 @@ dependencies = [ name = "eflint-haskell-reasoner" version = "0.1.0" dependencies = [ - "base16ct", "error-trace 3.1.0", "serde", "sha2", diff --git a/lib/reasoners/eflint-haskell/Cargo.toml b/lib/reasoners/eflint-haskell/Cargo.toml index 04deb5e..cc135c6 100644 --- a/lib/reasoners/eflint-haskell/Cargo.toml +++ b/lib/reasoners/eflint-haskell/Cargo.toml @@ -9,7 +9,6 @@ license.workspace = true [dependencies] -base16ct = { version = "0.2.0", features = ["std"] } # reqwest = { version = "0.12.0", features = ["json"] } serde = { version = "1.0.204", features = ["derive"] } # serde_json = "1.0.120" From ab3e57854df3c332caeda1d57e727565bff719cc Mon Sep 17 00:00:00 2001 From: Lut99 Date: Thu, 1 May 2025 16:06:30 +0200 Subject: [PATCH 38/82] Moved `base_policy_hash` to public context --- .../eflint-haskell/src/reasonerconn.rs | 39 +++++++------------ 1 file changed, 14 insertions(+), 25 deletions(-) diff --git a/lib/reasoners/eflint-haskell/src/reasonerconn.rs b/lib/reasoners/eflint-haskell/src/reasonerconn.rs index 7de60a6..be1f2a4 100644 --- a/lib/reasoners/eflint-haskell/src/reasonerconn.rs +++ b/lib/reasoners/eflint-haskell/src/reasonerconn.rs @@ -4,7 +4,7 @@ // Created: // 16 Apr 2025, 23:09:26 // Last edited: -// 01 May 2025, 15:55:27 +// 01 May 2025, 16:06:12 // Auto updated? // Yes // @@ -106,17 +106,8 @@ pub struct EFlintHaskellReasonerContext { pub language: String, /// The version identifier of the language targeted by this reasoner. pub language_version: String, -} -impl Default for EFlintHaskellReasonerContext { - #[inline] - fn default() -> Self { - Self { - version: env!("CARGO_PKG_VERSION").into(), - language: "eflint".into(), - // NOTE: This really needn't be accurate, but it's kind of hard to check the eFLINT version from the Haskell version atm. - language_version: "4.0.0.1".into(), - } - } + /// A hash of the base policy calculated at construction time. + pub base_policy_hash: [u8; 32], } impl ReasonerContext for EFlintHaskellReasonerContext { #[inline] @@ -142,8 +133,6 @@ pub struct EFlintHaskellReasonerContextFull { pub cmd: (String, Vec), /// The base policy to provide to the eFLINT reasoner. pub base_policy: PathBuf, - /// A hash of the base policy calculated at construction time. - pub base_policy_hash: [u8; 32], } impl ReasonerContext for EFlintHaskellReasonerContextFull { #[inline] @@ -208,8 +197,17 @@ impl EFlintHaskellReasonerConnector { compute_policy_hash(&base_policy, &[]).await.map_err(|source| Error::PolicyHash { path: base_policy.clone(), source })?; // Build the context & log it - let context: EFlintHaskellReasonerContextFull = - EFlintHaskellReasonerContextFull { public: EFlintHaskellReasonerContext::default(), cmd, base_policy, base_policy_hash }; + let context: EFlintHaskellReasonerContextFull = EFlintHaskellReasonerContextFull { + public: EFlintHaskellReasonerContext { + version: env!("CARGO_PKG_VERSION").into(), + language: "eflint".into(), + // NOTE: This really needn't be accurate, but it's kind of hard to check the eFLINT version from the Haskell version atm. + language_version: "4.0.0.1".into(), + base_policy_hash, + }, + cmd, + base_policy, + }; logger.log_context(&context).await.map_err(|err| Error::LogContext { to: std::any::type_name::(), err: err.freeze() })?; // OK, return ourselves @@ -232,15 +230,6 @@ impl EFlintHaskellReasonerConnector { /// A [`Path`] representing this file. #[inline] pub const fn base_policy(&self) -> &PathBuf { &self.context.base_policy } - - /// Returns the hash of the base policy provided to every reasoner call. - /// - /// # Returns - /// A binary string of 32 bytes (256-bit) that represents a secure SHA256 hash. - /// - /// Note: hashing the file and any file it depends on manually - #[inline] - pub const fn base_policy_hash(&self) -> &[u8; 32] { &self.context.base_policy_hash } } impl ReasonerConnector for EFlintHaskellReasonerConnector where From 8d850fcfc36a949147a50e2d56dc9b79dab99f7d Mon Sep 17 00:00:00 2001 From: Lut99 Date: Thu, 1 May 2025 16:30:54 +0200 Subject: [PATCH 39/82] Fixed bug where the wrong path is being recursed --- lib/reasoners/eflint-haskell/src/hash.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/reasoners/eflint-haskell/src/hash.rs b/lib/reasoners/eflint-haskell/src/hash.rs index 7e65698..01957f3 100644 --- a/lib/reasoners/eflint-haskell/src/hash.rs +++ b/lib/reasoners/eflint-haskell/src/hash.rs @@ -4,7 +4,7 @@ // Created: // 01 May 2025, 14:33:06 // Last edited: -// 01 May 2025, 15:54:30 +// 01 May 2025, 16:30:43 // Auto updated? // Yes // @@ -214,17 +214,17 @@ async fn find_deps_of(mut handle: File, path: &Path, base_path: &Path, include_d found = true; // If we haven't added it yet, add it - if !files.contains(path) { + if !files.contains(imppath.as_ref()) { + files.insert(imppath.to_owned().into_owned()); Box::pin(find_deps_of( - File::open(path).await.map_err(|source| Error::FileOpen { path: path.into(), source })?, - path, + File::open(imppath).await.map_err(|source| Error::FileOpen { path: imppath.to_owned().into_owned(), source })?, + imppath.as_ref(), base_path, include_dirs, files, )) .await?; } - files.insert(path.into()); break; } if !found { From 3b3febe2a4aab88df851e04f9a750e0c22ab7524 Mon Sep 17 00:00:00 2001 From: Lut99 Date: Thu, 1 May 2025 16:33:43 +0200 Subject: [PATCH 40/82] Fixed current directory not being followed by the actual path --- lib/reasoners/eflint-haskell/src/hash.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/reasoners/eflint-haskell/src/hash.rs b/lib/reasoners/eflint-haskell/src/hash.rs index 01957f3..6c74afb 100644 --- a/lib/reasoners/eflint-haskell/src/hash.rs +++ b/lib/reasoners/eflint-haskell/src/hash.rs @@ -4,7 +4,7 @@ // Created: // 01 May 2025, 14:33:06 // Last edited: -// 01 May 2025, 16:30:43 +// 01 May 2025, 16:33:30 // Auto updated? // Yes // @@ -189,7 +189,7 @@ async fn find_deps_of(mut handle: File, path: &Path, base_path: &Path, include_d // Attempt to resolve the path if it's relative let imppaths: Vec> = if imppath.is_relative() { let mut imppaths = Vec::with_capacity(3 + include_dirs.len()); - imppaths.push(Cow::Owned(std::env::current_dir().map_err(|source| Error::GetCwd { source })?)); + imppaths.push(Cow::Owned(std::env::current_dir().map_err(|source| Error::GetCwd { source })?.join(imppath))); if let Some(parent) = base_path.parent() { imppaths.push(Cow::Owned(parent.join(imppath))); } From 52a65ea3ca46b75179cf1ad9879f663490368e09 Mon Sep 17 00:00:00 2001 From: Lut99 Date: Thu, 1 May 2025 16:43:34 +0200 Subject: [PATCH 41/82] Fixed the connector not filtering prompts from event stream --- .../eflint-haskell/src/reasonerconn.rs | 50 +++++++++++++++++-- 1 file changed, 45 insertions(+), 5 deletions(-) diff --git a/lib/reasoners/eflint-haskell/src/reasonerconn.rs b/lib/reasoners/eflint-haskell/src/reasonerconn.rs index be1f2a4..cd1ca87 100644 --- a/lib/reasoners/eflint-haskell/src/reasonerconn.rs +++ b/lib/reasoners/eflint-haskell/src/reasonerconn.rs @@ -4,7 +4,7 @@ // Created: // 16 Apr 2025, 23:09:26 // Last edited: -// 01 May 2025, 16:06:12 +// 01 May 2025, 16:43:10 // Auto updated? // Yes // @@ -302,12 +302,52 @@ where }); } - // Attempt to parse the output + // Stript the prompts from the eFLINT output let output: Cow = String::from_utf8_lossy(&output.stdout); - debug!("Reasoner output:\n{}\n{}\n{}\n", "-".repeat(80), output, "-".repeat(80)); - let trace: Trace = match Trace::from_str(output.as_ref()) { + let mut clean_output: String = String::with_capacity(output.len()); + let mut buf: String = String::new(); + let mut state: usize = 0; + for c in output.chars() { + // Loop exists to be able to examine some chars again + loop { + match state { + // Finding pounds + 0 if c == '#' => { + buf.push('#'); + state = 1; + break; + }, + 0 => { + clean_output.push(c); + break; + }, + + // Parsing numbers & whitespace + 1 if c.is_ascii_digit() || c.is_whitespace() => { + buf.push(c); + break; + }, + 1 if c == '>' => { + state = 0; + break; + }, + 1 => { + clean_output.push_str(&buf); + buf.clear(); + state = 0; + // Don't break, re-try this character + }, + + _ => unreachable!(), + } + } + } + + // Attempt to parse the output + debug!("Reasoner output:\n{}\n{}\n{}\n", "-".repeat(80), clean_output, "-".repeat(80)); + let trace: Trace = match Trace::from_str(clean_output.as_ref()) { Ok(trace) => trace, - Err(err) => return Err(Error::IllegalReasonerResponse { output: output.into(), err }), + Err(err) => return Err(Error::IllegalReasonerResponse { output: clean_output, err }), }; debug!("Reasoner trace:\n{}\n{}\n{}\n", "-".repeat(80), trace, "-".repeat(80)); From 5f3f318d14d872b19553059fea85296339b6a708 Mon Sep 17 00:00:00 2001 From: Lut99 Date: Thu, 1 May 2025 16:48:33 +0200 Subject: [PATCH 42/82] Now debugging final reasoner verdict --- lib/reasoners/eflint-haskell/src/reasonerconn.rs | 8 +++++--- lib/reasoners/eflint-haskell/src/reasons.rs | 2 +- lib/spec/src/reasonerconn.rs | 16 +++++++++++++++- 3 files changed, 21 insertions(+), 5 deletions(-) diff --git a/lib/reasoners/eflint-haskell/src/reasonerconn.rs b/lib/reasoners/eflint-haskell/src/reasonerconn.rs index cd1ca87..5784c79 100644 --- a/lib/reasoners/eflint-haskell/src/reasonerconn.rs +++ b/lib/reasoners/eflint-haskell/src/reasonerconn.rs @@ -4,7 +4,7 @@ // Created: // 16 Apr 2025, 23:09:26 // Last edited: -// 01 May 2025, 16:43:10 +// 01 May 2025, 16:46:12 // Auto updated? // Yes // @@ -357,7 +357,7 @@ where // a. If it's a query, then it must succeed; or // b. If it's not a query, it must not be a violation. // 2. If there is no last delta, then we default to **success**. - Ok(trace + let res: ReasonerResponse<_> = trace .deltas .into_iter() .last() @@ -370,7 +370,9 @@ where ReasonerResponse::Success }, }) - .unwrap_or(ReasonerResponse::Success)) + .unwrap_or(ReasonerResponse::Success); + debug!("Reasoner verdict: {res}"); + Ok(res) } } } diff --git a/lib/reasoners/eflint-haskell/src/reasons.rs b/lib/reasoners/eflint-haskell/src/reasons.rs index eadd977..3f10d45 100644 --- a/lib/reasoners/eflint-haskell/src/reasons.rs +++ b/lib/reasoners/eflint-haskell/src/reasons.rs @@ -4,7 +4,7 @@ // Created: // 25 Apr 2025, 16:36:41 // Last edited: -// 01 May 2025, 10:21:21 +// 01 May 2025, 16:46:45 // Auto updated? // Yes // diff --git a/lib/spec/src/reasonerconn.rs b/lib/spec/src/reasonerconn.rs index 87d0e2f..824d25c 100644 --- a/lib/spec/src/reasonerconn.rs +++ b/lib/spec/src/reasonerconn.rs @@ -4,7 +4,7 @@ // Created: // 09 Oct 2024, 13:35:41 // Last edited: -// 02 Dec 2024, 14:21:21 +// 01 May 2025, 16:48:24 // Auto updated? // Yes // @@ -14,6 +14,7 @@ use std::borrow::Cow; use std::error::Error; +use std::fmt::{Display, Formatter, Result as FResult}; use std::future::Future; use serde::{Deserialize, Serialize}; @@ -63,6 +64,19 @@ pub enum ReasonerResponse { /// The state is _not_ compliant to the policy w.r.t. the question. Violated(R), } +impl Display for ReasonerResponse { + #[inline] + fn fmt(&self, f: &mut Formatter<'_>) -> FResult { + match self { + Self::Success => write!(f, "SUCCESS"), + Self::Violated(r) => { + write!(f, "VIOLATION(")?; + r.fmt(f)?; + write!(f, ")") + }, + } + } +} From 67d91e27436aa753dbf90efa0947b04193a94100 Mon Sep 17 00:00:00 2001 From: Lut99 Date: Thu, 1 May 2025 16:59:12 +0200 Subject: [PATCH 43/82] Now returning spec-compliant reasons again --- .../eflint-haskell/src/reasonerconn.rs | 15 +++- lib/reasoners/eflint-haskell/src/reasons.rs | 86 +++++++------------ 2 files changed, 43 insertions(+), 58 deletions(-) diff --git a/lib/reasoners/eflint-haskell/src/reasonerconn.rs b/lib/reasoners/eflint-haskell/src/reasonerconn.rs index 5784c79..c4a06cb 100644 --- a/lib/reasoners/eflint-haskell/src/reasonerconn.rs +++ b/lib/reasoners/eflint-haskell/src/reasonerconn.rs @@ -4,7 +4,7 @@ // Created: // 16 Apr 2025, 23:09:26 // Last edited: -// 01 May 2025, 16:46:12 +// 01 May 2025, 16:59:00 // Auto updated? // Yes // @@ -357,14 +357,23 @@ where // a. If it's a query, then it must succeed; or // b. If it's not a query, it must not be a violation. // 2. If there is no last delta, then we default to **success**. + let problems: Vec = trace + .deltas + .iter() + .filter_map(|delta| match delta { + Delta::Query(query) if query.is_succes() => Some(Problem::QueryFailed), + Delta::Violation(viol) => Some(Problem::Violation(viol.clone())), + _ => None, + }) + .collect(); let res: ReasonerResponse<_> = trace .deltas .into_iter() .last() .map(|delta| match delta { Delta::Query(query) if query.is_succes() => ReasonerResponse::Success, - Delta::Query(_) => ReasonerResponse::Violated(self.handler.handle(Problem::QueryFailed)), - Delta::Violation(viol) => ReasonerResponse::Violated(self.handler.handle(Problem::Violation(viol))), + Delta::Query(_) => ReasonerResponse::Violated(self.handler.handle(problems)), + Delta::Violation(_) => ReasonerResponse::Violated(self.handler.handle(problems)), delta => { warn!("Got non-query, non-violation delta as last delta ({delta:?}); assuming OK"); ReasonerResponse::Success diff --git a/lib/reasoners/eflint-haskell/src/reasons.rs b/lib/reasoners/eflint-haskell/src/reasons.rs index 3f10d45..8dc0548 100644 --- a/lib/reasoners/eflint-haskell/src/reasons.rs +++ b/lib/reasoners/eflint-haskell/src/reasons.rs @@ -4,7 +4,7 @@ // Created: // 25 Apr 2025, 16:36:41 // Last edited: -// 01 May 2025, 16:46:45 +// 01 May 2025, 16:56:22 // Auto updated? // Yes // @@ -16,34 +16,12 @@ use std::borrow::Cow; use std::fmt::{Display, Formatter, Result as FResult}; use serde::{Deserialize, Serialize}; +use spec::reasons::{ManyReason, NoReason}; use crate::trace::Violation; /***** AUXILLARY *****/ -/// Defines an empty reason. -#[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)] -pub struct NoReason; -impl Display for NoReason { - #[inline] - fn fmt(&self, f: &mut Formatter<'_>) -> FResult { write!(f, "") } -} - -/// Defines an optional reason. -#[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)] -pub struct OptReason(pub Option); -impl Display for OptReason { - #[inline] - fn fmt(&self, f: &mut Formatter<'_>) -> FResult { - match &self.0 { - Some(r) => r.fmt(f), - None => write!(f, ""), - } - } -} - - - /// Defines either a [`Query`] or a [`Violation`]. #[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)] pub enum Problem { @@ -73,12 +51,12 @@ pub trait ReasonHandler { /// Maps a query/violation to a reason. /// /// # Arguments - /// - `problem`: A [`Problem::QueryFailed`] or a [`Problem::Violation`] that describes the - /// reason. + /// - `problems`: A sequence of [`Problem::QueryFailed`]s and/or [`Problem::Violation`]s that + /// describes the reasons. /// /// # Returns /// A [`Self::Reason`](ReasonHandler::Reason) that represents the outputted reason. - fn handle(&self, problem: Problem) -> Self::Reason; + fn handle(&self, problems: impl IntoIterator) -> Self::Reason; } @@ -93,7 +71,7 @@ impl ReasonHandler for SilentHandler { type Reason = NoReason; #[inline] - fn handle(&self, _problem: Problem) -> Self::Reason { NoReason } + fn handle(&self, _problems: impl IntoIterator) -> Self::Reason { NoReason } } @@ -116,34 +94,32 @@ impl<'s> PrefixedHandler<'s> { pub fn new(prefix: impl Into>) -> Self { Self { prefix: prefix.into() } } } impl<'s> ReasonHandler for PrefixedHandler<'s> { - type Reason = OptReason; + type Reason = ManyReason; #[inline] - fn handle(&self, problem: Problem) -> Self::Reason { - match problem { - Problem::QueryFailed => OptReason(None), - Problem::Violation(Violation::Act(a)) => { - if a.inst.name.starts_with(self.prefix.as_ref()) { - OptReason(Some(Violation::Act(a))) - } else { - OptReason(None) - } - }, - Problem::Violation(Violation::Duty(d)) => { - if d.inst.name.starts_with(self.prefix.as_ref()) { - OptReason(Some(Violation::Duty(d))) - } else { - OptReason(None) - } - }, - Problem::Violation(Violation::Invariant(i)) => { - if i.name.starts_with(self.prefix.as_ref()) { - OptReason(Some(Violation::Invariant(i))) - } else { - OptReason(None) - } - }, + fn handle(&self, problems: impl IntoIterator) -> Self::Reason { + let mut reason = ManyReason::new(); + for problem in problems { + match problem { + Problem::QueryFailed => continue, + Problem::Violation(Violation::Act(a)) => { + if a.inst.name.starts_with(self.prefix.as_ref()) { + reason.push(Violation::Act(a).to_string()); + } + }, + Problem::Violation(Violation::Duty(d)) => { + if d.inst.name.starts_with(self.prefix.as_ref()) { + reason.push(Violation::Duty(d).to_string()); + } + }, + Problem::Violation(Violation::Invariant(i)) => { + if i.name.starts_with(self.prefix.as_ref()) { + reason.push(Violation::Invariant(i).to_string()); + } + }, + } } + reason } } @@ -153,8 +129,8 @@ impl<'s> ReasonHandler for PrefixedHandler<'s> { #[derive(Clone, Debug)] pub struct VerboseHandler; impl ReasonHandler for VerboseHandler { - type Reason = Problem; + type Reason = ManyReason; #[inline] - fn handle(&self, problem: Problem) -> Self::Reason { problem } + fn handle(&self, problems: impl IntoIterator) -> Self::Reason { ManyReason::from(problems.into_iter().map(|p| p.to_string())) } } From 8fa98aa24b58cb377d866ed855e4462fdda6bdef Mon Sep 17 00:00:00 2001 From: Lut99 Date: Tue, 6 May 2025 10:58:21 +0200 Subject: [PATCH 44/82] Re-moved git dependencies to workspace root Also removed obsolete comments --- Cargo.lock | 21 +++++++------------ Cargo.toml | 7 ++++++- examples/eflint_json.rs | 2 +- lib/eflint-spec/Cargo.toml | 11 ---------- lib/loggers/file/Cargo.toml | 5 +++-- lib/reasoners/eflint-haskell/Cargo.toml | 5 +---- lib/reasoners/eflint-json/Cargo.toml | 5 ++--- lib/reasoners/eflint-json/src/reasonerconn.rs | 2 +- lib/reasoners/no-op/Cargo.toml | 2 +- lib/reasoners/posix/Cargo.toml | 3 +-- lib/resolvers/file/Cargo.toml | 1 - lib/spec/Cargo.toml | 1 - 12 files changed, 24 insertions(+), 41 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 9d5cf75..4392d0f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -179,9 +179,9 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "chrono" -version = "0.4.40" +version = "0.4.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a7964611d71df112cb1730f2ee67324fcf4d0fc6606acbbe9bfe06df124637c" +checksum = "c469d952047f47f91b68d1cba3f10d63c11d73e4636f24f08daf0278abf01c4d" dependencies = [ "android-tzdata", "iana-time-zone", @@ -310,7 +310,7 @@ dependencies = [ name = "eflint-haskell-reasoner" version = "0.1.0" dependencies = [ - "error-trace 3.1.0", + "error-trace", "serde", "sha2", "specifications", @@ -322,7 +322,7 @@ dependencies = [ [[package]] name = "eflint-json" version = "0.1.0" -source = "git+https://gitlab.com/eflint/json-spec-rs?branch=incorrect-is-invariant#a77ae8c5050fcbeb36340ec86f353099d6c51182" +source = "git+https://gitlab.com/eflint/json-spec-rs.git?branch=incorrect-is-invariant#a77ae8c5050fcbeb36340ec86f353099d6c51182" dependencies = [ "enum-debug", "serde", @@ -333,7 +333,7 @@ name = "eflint-json-reasoner" version = "0.2.0" dependencies = [ "eflint-json", - "error-trace 3.1.0", + "error-trace", "reqwest", "serde", "serde_json", @@ -411,11 +411,6 @@ dependencies = [ "windows-sys 0.59.0", ] -[[package]] -name = "error-trace" -version = "3.0.0" -source = "git+https://github.com/Lut99/error-trace-rs?tag=v3.0.0#a026dc304b5bcc40bc68574f21459d2f1b6cad90" - [[package]] name = "error-trace" version = "3.1.0" @@ -1053,7 +1048,7 @@ dependencies = [ name = "no-op-reasoner" version = "0.2.0" dependencies = [ - "error-trace 3.1.0", + "error-trace", "serde", "specifications", "thiserror 1.0.69", @@ -1189,7 +1184,7 @@ dependencies = [ "eflint-haskell-reasoner", "eflint-json-reasoner", "eflint-to-json", - "error-trace 3.0.0", + "error-trace", "file-logger", "file-resolver", "no-op-logger", @@ -1213,7 +1208,7 @@ checksum = "350e9b48cbc6b0e028b0473b114454c6316e57336ee184ceab6e53f72c178b3e" name = "posix-reasoner" version = "0.2.0" dependencies = [ - "error-trace 3.1.0", + "error-trace", "serde", "serde_json", "specifications", diff --git a/Cargo.toml b/Cargo.toml index 3f72f0d..c99d0f5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -66,7 +66,7 @@ workflow = { path = "./lib/workflow", optional = true } [dev-dependencies] clap = { version = "4.5.36", features = ["derive"] } console = "0.15.11" -error-trace = { git = "https://github.com/Lut99/error-trace-rs", tag = "v3.0.0" } +error-trace.workspace = true serde_json = "1.0.140" tracing = "0.1.41" tracing-subscriber = { version = "0.3.19", features = ["env-filter"] } @@ -102,3 +102,8 @@ serde = ["workflow/serde"] [workspace.dependencies] # The infamous lut99 crate set enum-debug = { git = "https://github.com/Lut99/enum-debug", tag = "v1.1.0", features = ["derive"] } +error-trace = { git = "https://github.com/Lut99/error-trace-rs", tag = "v3.1.0" } + +# Eflint +# NOTE: For now, we must skip the redirect. See +eflint-json = { git = "https://gitlab.com/eflint/json-spec-rs.git", branch = "incorrect-is-invariant" } diff --git a/examples/eflint_json.rs b/examples/eflint_json.rs index 9186aca..5cdc263 100644 --- a/examples/eflint_json.rs +++ b/examples/eflint_json.rs @@ -4,7 +4,7 @@ // Created: // 10 Oct 2024, 13:54:17 // Last edited: -// 06 Nov 2024, 14:59:22 +// 06 May 2025, 10:57:22 // Auto updated? // Yes // diff --git a/lib/eflint-spec/Cargo.toml b/lib/eflint-spec/Cargo.toml index a7fefcf..734f654 100644 --- a/lib/eflint-spec/Cargo.toml +++ b/lib/eflint-spec/Cargo.toml @@ -9,17 +9,6 @@ license.workspace = true [dependencies] -# reqwest = { version = "0.12.0", features = ["json"] } -# serde = { version = "1.0.204", features = ["derive"] } -# serde_json = "1.0.120" -# thiserror = "1.0.61" -# tracing = "0.1.40" - -# eflint-json = { git = "https://gitlab.com/eflint/json-spec-rs", branch = "incorrect-is-invariant" } -# error-trace = { git = "https://github.com/Lut99/error-trace-rs", tag = "v3.1.0" } - -# # workflow = { path = "../../../reasoner-workflow", package = "policy-reasoner-workflow" } -# spec = { path = "../../spec", package = "specifications" } [features] diff --git a/lib/loggers/file/Cargo.toml b/lib/loggers/file/Cargo.toml index 064a90a..a27145e 100644 --- a/lib/loggers/file/Cargo.toml +++ b/lib/loggers/file/Cargo.toml @@ -9,13 +9,14 @@ license.workspace = true [dependencies] -chrono = "0.4.38" -enum-debug = { git = "https://github.com/Lut99/enum-debug", tag = "v1.1.0", features = ["derive"] } +chrono = "0.4.41" serde = { version = "1.0.204", features = ["derive"] } serde_json = "1.0.120" tokio = { version = "1.40.0", default-features = false, features = ["fs", "io-util"] } tracing = "0.1.40" +enum-debug.workspace = true + spec = { path = "../../spec", package = "specifications" } diff --git a/lib/reasoners/eflint-haskell/Cargo.toml b/lib/reasoners/eflint-haskell/Cargo.toml index cc135c6..3205f81 100644 --- a/lib/reasoners/eflint-haskell/Cargo.toml +++ b/lib/reasoners/eflint-haskell/Cargo.toml @@ -9,16 +9,13 @@ license.workspace = true [dependencies] -# reqwest = { version = "0.12.0", features = ["json"] } serde = { version = "1.0.204", features = ["derive"] } -# serde_json = "1.0.120" sha2 = "0.10.6" thiserror = "2.0.12" tokio = { version = "1.44.2", default-features = false, features = ["io-util", "process"] } tracing = "0.1.40" -# eflint-json = { git = "https://gitlab.com/eflint/json-spec-rs", branch = "incorrect-is-invariant" } -error-trace = { git = "https://github.com/Lut99/error-trace-rs", tag = "v3.1.0" } +error-trace.workspace = true spec = { path = "../../spec", package = "specifications" } diff --git a/lib/reasoners/eflint-json/Cargo.toml b/lib/reasoners/eflint-json/Cargo.toml index 5e2a3bf..b30ebc9 100644 --- a/lib/reasoners/eflint-json/Cargo.toml +++ b/lib/reasoners/eflint-json/Cargo.toml @@ -15,10 +15,9 @@ serde_json = "1.0.120" thiserror = "1.0.61" tracing = "0.1.40" -eflint-json = { git = "https://gitlab.com/eflint/json-spec-rs", branch = "incorrect-is-invariant" } -error-trace = { git = "https://github.com/Lut99/error-trace-rs", tag = "v3.1.0" } +eflint-json.workspace = true +error-trace.workspace = true -# workflow = { path = "../../../reasoner-workflow", package = "policy-reasoner-workflow" } spec = { path = "../../spec", package = "specifications" } diff --git a/lib/reasoners/eflint-json/src/reasonerconn.rs b/lib/reasoners/eflint-json/src/reasonerconn.rs index 33cbad3..60165f9 100644 --- a/lib/reasoners/eflint-json/src/reasonerconn.rs +++ b/lib/reasoners/eflint-json/src/reasonerconn.rs @@ -4,7 +4,7 @@ // Created: // 09 Oct 2024, 15:52:06 // Last edited: -// 02 Dec 2024, 15:49:51 +// 06 May 2025, 10:56:58 // Auto updated? // Yes // diff --git a/lib/reasoners/no-op/Cargo.toml b/lib/reasoners/no-op/Cargo.toml index a1c083c..2d4b8fe 100644 --- a/lib/reasoners/no-op/Cargo.toml +++ b/lib/reasoners/no-op/Cargo.toml @@ -19,7 +19,7 @@ serde = "1.0.204" thiserror = "1.0.61" tracing = "0.1.40" -error-trace = { git = "https://github.com/Lut99/error-trace-rs", tag = "v3.1.0" } +error-trace.workspace = true spec = { path = "../../spec", package = "specifications" } diff --git a/lib/reasoners/posix/Cargo.toml b/lib/reasoners/posix/Cargo.toml index 0e7c3d7..22b86a1 100644 --- a/lib/reasoners/posix/Cargo.toml +++ b/lib/reasoners/posix/Cargo.toml @@ -139,14 +139,13 @@ license.workspace = true [dependencies] -# itertools = "0.13.0" serde = { version = "1.0.204", features = ["derive"] } serde_json = "1.0.120" thiserror = "1.0.61" tokio = { version = "1.40.0", default-features = false, features = ["fs", "io-util"] } tracing = "0.1.40" -error-trace = { git = "https://github.com/Lut99/error-trace-rs", tag = "v3.1.0" } +error-trace.workspace = true spec = { path = "../../spec", package = "specifications" } workflow = { path = "../../workflow", features = ["serde"]} diff --git a/lib/resolvers/file/Cargo.toml b/lib/resolvers/file/Cargo.toml index c76c1b7..22053e7 100644 --- a/lib/resolvers/file/Cargo.toml +++ b/lib/resolvers/file/Cargo.toml @@ -9,7 +9,6 @@ license.workspace = true [dependencies] -# enum-debug = { git = "https://github.com/Lut99/enum-debug", tag = "v1.1.0", features = ["derive"] } serde = { version = "1.0.204", features = ["derive"] } serde_json = "1.0.120" tokio = { version = "1.40.0", default-features = false, features = ["fs"] } diff --git a/lib/spec/Cargo.toml b/lib/spec/Cargo.toml index ff52b98..a3c2023 100644 --- a/lib/spec/Cargo.toml +++ b/lib/spec/Cargo.toml @@ -9,7 +9,6 @@ license.workspace = true [dependencies] -# enum-debug = { git = "https://github.com/Lut99/enum-debug", tag = "v1.1.0", features = ["derive"] } paste = "1.0.15" serde = { version = "1.0.204", features = ["derive"] } From 1992ff0592eb65b2ebb5990b788057c2e9693071 Mon Sep 17 00:00:00 2001 From: Lut99 Date: Tue, 6 May 2025 11:07:14 +0200 Subject: [PATCH 45/82] Re-added MSRV Crazy, I know, but everything seems to be 1.75.0 now! --- Cargo.toml | 1 + lib/eflint-spec/Cargo.toml | 1 + lib/eflint-to-json/Cargo.toml | 2 +- lib/loggers/file/Cargo.toml | 1 + lib/loggers/no-op/Cargo.toml | 1 + lib/reasoners/eflint-haskell/Cargo.toml | 1 + lib/reasoners/eflint-json/Cargo.toml | 1 + lib/reasoners/no-op/Cargo.toml | 1 + lib/reasoners/posix/Cargo.toml | 1 + lib/resolvers/file/Cargo.toml | 1 + lib/spec/Cargo.toml | 1 + lib/workflow/Cargo.toml | 2 +- 12 files changed, 12 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index c99d0f5..301fde0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -28,6 +28,7 @@ license = "Apache-2.0" name = "policy-reasoner" version = "1.0.0" edition = "2021" +rust-version = "1.75.0" authors = ["Tim Müller", "Bas Kloosterman", "Daniel Voogsgerd"] repository.workspace = true license.workspace = true diff --git a/lib/eflint-spec/Cargo.toml b/lib/eflint-spec/Cargo.toml index 734f654..eb54efd 100644 --- a/lib/eflint-spec/Cargo.toml +++ b/lib/eflint-spec/Cargo.toml @@ -2,6 +2,7 @@ name = "eflint-spec" description = "Implements some common eFLINT definitions used across various eFLINT reasoners." edition = "2021" +rust-version = "1.75.0" authors = [ "Tim Müller" ] version = "0.1.0" repository.workspace = true diff --git a/lib/eflint-to-json/Cargo.toml b/lib/eflint-to-json/Cargo.toml index 0f9f54e..8a98dcf 100644 --- a/lib/eflint-to-json/Cargo.toml +++ b/lib/eflint-to-json/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "eflint-to-json" description = "Defines a high-level wrapper around Olaf's [`eflint-to-json`](https://github.com/Olaf-Erkemeij/eflint-server) executable that compiles eFLINT to eFLINT JSON Specification." -rust-version = "1.81" +rust-version = "1.75.0" edition = "2021" version = "0.2.0" authors = ["Tim Müller"] diff --git a/lib/loggers/file/Cargo.toml b/lib/loggers/file/Cargo.toml index a27145e..4af00e1 100644 --- a/lib/loggers/file/Cargo.toml +++ b/lib/loggers/file/Cargo.toml @@ -2,6 +2,7 @@ name = "file-logger" description = "Implements an audit logger that writes everything to a file." edition = "2021" +rust-version = "1.75.0" authors = [ "Bas Kloosterman", "Tim Müller" ] version = "0.2.0" repository.workspace = true diff --git a/lib/loggers/no-op/Cargo.toml b/lib/loggers/no-op/Cargo.toml index 92f1d1c..09352e4 100644 --- a/lib/loggers/no-op/Cargo.toml +++ b/lib/loggers/no-op/Cargo.toml @@ -2,6 +2,7 @@ name = "no-op-logger" description = "Implements an audit logger that doesn't audit log anything but just logs a little using `tracing`." edition = "2021" +rust-version = "1.75.0" authors = [ "Bas Kloosterman", "Tim Müller" ] version = "0.2.0" repository.workspace = true diff --git a/lib/reasoners/eflint-haskell/Cargo.toml b/lib/reasoners/eflint-haskell/Cargo.toml index 3205f81..69d5ea5 100644 --- a/lib/reasoners/eflint-haskell/Cargo.toml +++ b/lib/reasoners/eflint-haskell/Cargo.toml @@ -2,6 +2,7 @@ name = "eflint-haskell-reasoner" description = "Wraps the infamous Haskell interpreter to supply an eFLINT DSL backend." edition = "2021" +rust-version = "1.75.0" authors = [ "Tim Müller" ] version = "0.1.0" repository.workspace = true diff --git a/lib/reasoners/eflint-json/Cargo.toml b/lib/reasoners/eflint-json/Cargo.toml index b30ebc9..012776a 100644 --- a/lib/reasoners/eflint-json/Cargo.toml +++ b/lib/reasoners/eflint-json/Cargo.toml @@ -2,6 +2,7 @@ name = "eflint-json-reasoner" description = "Implements eFLINT as a backend reasoner for the policy reasoner." edition = "2021" +rust-version = "1.75.0" authors = [ "Bas Kloosterman", "Tim Müller" ] version = "0.2.0" repository.workspace = true diff --git a/lib/reasoners/no-op/Cargo.toml b/lib/reasoners/no-op/Cargo.toml index 2d4b8fe..b597d36 100644 --- a/lib/reasoners/no-op/Cargo.toml +++ b/lib/reasoners/no-op/Cargo.toml @@ -8,6 +8,7 @@ Furthermore it can be used for testing. The reasoner approves all workflow valid not perform any permission checks, and thus never rejects a request). """ edition = "2021" +rust-version = "1.75.0" authors = [ "Daniel Voogsgerd", "Tim Müller" ] version = "0.2.0" repository.workspace = true diff --git a/lib/reasoners/posix/Cargo.toml b/lib/reasoners/posix/Cargo.toml index 22b86a1..2fda921 100644 --- a/lib/reasoners/posix/Cargo.toml +++ b/lib/reasoners/posix/Cargo.toml @@ -132,6 +132,7 @@ existing implementation of the eFlint reasoner made this project possible. We ho the addition of new reasoner types. """ edition = "2021" +rust-version = "1.75.0" authors = [ "Daniel Voogsgerd", "Tim Müller" ] version = "0.2.0" repository.workspace = true diff --git a/lib/resolvers/file/Cargo.toml b/lib/resolvers/file/Cargo.toml index 22053e7..f0c4929 100644 --- a/lib/resolvers/file/Cargo.toml +++ b/lib/resolvers/file/Cargo.toml @@ -2,6 +2,7 @@ name = "file-resolver" description = "Resolves state from a file by parsing it as `serde` JSON." edition = "2021" +rust-version = "1.75.0" authors = [ "Bas Kloosterman", "Tim Müller" ] version = "0.2.0" repository.workspace = true diff --git a/lib/spec/Cargo.toml b/lib/spec/Cargo.toml index a3c2023..761802e 100644 --- a/lib/spec/Cargo.toml +++ b/lib/spec/Cargo.toml @@ -2,6 +2,7 @@ name = "specifications" description = "Defines various interfaces between various parts of the reasoner." edition = "2021" +rust-version = "1.75.0" authors = [ "Bas Kloosterman", "Tim Müller" ] version = "0.2.0" repository.workspace = true diff --git a/lib/workflow/Cargo.toml b/lib/workflow/Cargo.toml index 3727802..82c047f 100644 --- a/lib/workflow/Cargo.toml +++ b/lib/workflow/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "workflow" description = "Defines the workflow representation used internally by the checker." -rust-version = "1.81" +rust-version = "1.75.0" edition = "2021" authors = [ "Tim Müller" ] version = "0.2.0" From 94924462d74a4a34a7913cd8285cb5f8ba0eb653 Mon Sep 17 00:00:00 2001 From: Lut99 Date: Tue, 6 May 2025 11:17:41 +0200 Subject: [PATCH 46/82] Added `eflint-haskell` example --- Cargo.lock | 1 + Cargo.toml | 6 ++ examples/eflint_haskell.rs | 129 +++++++++++++++++++++++++++++++++++++ 3 files changed, 136 insertions(+) create mode 100644 examples/eflint_haskell.rs diff --git a/Cargo.lock b/Cargo.lock index 4392d0f..20fc773 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1191,6 +1191,7 @@ dependencies = [ "no-op-reasoner", "posix-reasoner", "serde_json", + "shlex", "specifications", "tokio", "tracing", diff --git a/Cargo.toml b/Cargo.toml index 301fde0..5752600 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -35,6 +35,11 @@ license.workspace = true description = "A library for using several different reasoning backends to determine if a particular workflow is allowed by policy or not." +[[example]] +name = "eflint-haskell" +path = "./examples/eflint_haskell.rs" +required-features = ["eflint-haskell-reasoner", "file-logger"] + [[example]] name = "eflint-json" path = "./examples/eflint_json.rs" @@ -69,6 +74,7 @@ clap = { version = "4.5.36", features = ["derive"] } console = "0.15.11" error-trace.workspace = true serde_json = "1.0.140" +shlex = "1.3.0" tracing = "0.1.41" tracing-subscriber = { version = "0.3.19", features = ["env-filter"] } tokio = { version = "1.44.2", default-features = false, features = ["fs", "io-std", "macros", "rt"] } diff --git a/examples/eflint_haskell.rs b/examples/eflint_haskell.rs new file mode 100644 index 0000000..c5494eb --- /dev/null +++ b/examples/eflint_haskell.rs @@ -0,0 +1,129 @@ +// EFLINT HASKELL.rs +// by Lut99 +// +// Created: +// 06 May 2025, 11:09:11 +// Last edited: +// 06 May 2025, 11:17:02 +// Auto updated? +// Yes +// +// Description: +//! Showcases using the `eflint-haskell-reasoner` backend of the policy +//! reasoner. +// + +use std::path::PathBuf; + +use clap::Parser; +use console::style; +use error_trace::trace; +use policy_reasoner::loggers::file::FileLogger; +use policy_reasoner::reasoners::eflint_haskell::EFlintHaskellReasonerConnector; +use policy_reasoner::reasoners::eflint_haskell::reasons::SilentHandler; +use policy_reasoner::spec::auditlogger::SessionedAuditLogger; +use policy_reasoner::spec::reasonerconn::ReasonerConnector as _; +use policy_reasoner::spec::reasons::NoReason; +use spec::reasonerconn::ReasonerResponse; +use tracing::{Level, error, info}; + + +/***** ARGUMENTS *****/ +/// Defines the arguments for this binary. +#[derive(Parser)] +struct Arguments { + /// Whether to make `info!()` and `debug!()` visible. + #[clap(long, help = "If given, enables INFO- and DEBUG-level logging.")] + debug: bool, + /// Whether to make `trace!()` visible. + #[clap(long, help = "If given, enables TRACE-level logging. Implies '--debug'.")] + trace: bool, + + /// The file to use as input. + #[clap(name = "FILE", default_value = "-", help = "The eFLINT (JSON) file to read. Use '-' to read from stdin.")] + file: String, + + /// Which `eflint-repl` to use. + #[clap(short, long, default_value = "eflint-repl", help = "The command used to launch the `eflint-repl` binary.")] + eflint_cmd: String, +} + + + + + +/***** LIBRARY *****/ +#[tokio::main(flavor = "current_thread")] +async fn main() { + // Parse the arguments + let args = Arguments::parse(); + + // Setup the logger + tracing_subscriber::fmt() + .with_max_level(if args.trace { + Level::TRACE + } else if args.debug { + Level::DEBUG + } else { + Level::WARN + }) + .init(); + info!("{} - v{}", env!("CARGO_BIN_NAME"), env!("CARGO_PKG_VERSION")); + + // Create the logger + let mut logger: SessionedAuditLogger = + SessionedAuditLogger::new("test", FileLogger::new(format!("{} - v{}", env!("CARGO_BIN_NAME"), env!("CARGO_PKG_VERSION")), "./test.log")); + + // Ensure there is a file to input + let policy: PathBuf = if args.file == "-" { + let file: PathBuf = std::env::temp_dir().join(format!("{}-v{}-stdin.eflint", env!("CARGO_BIN_NAME"), env!("CARGO_PKG_VERSION"))); + let mut handle: tokio::fs::File = match tokio::fs::File::create(&file).await { + Ok(handle) => handle, + Err(err) => { + error!("{}", trace!(("Failed to open temporary stdin file '{}'", file.display()), err)); + std::process::exit(1); + }, + }; + if let Err(err) = tokio::io::copy(&mut tokio::io::stdin(), &mut handle).await { + error!("{}", trace!(("Failed to write stdin to temporary file '{}'", file.display()), err)); + std::process::exit(1); + } + file + } else { + PathBuf::from(args.file) + }; + + // Create the reasoner + let conn = match EFlintHaskellReasonerConnector::::new_async( + shlex::split(&args.eflint_cmd).into_iter().flatten(), + &policy, + SilentHandler, + &mut logger, + ) + .await + { + Ok(conn) => conn, + Err(err) => { + error!("{}", trace!(("Failed to create eFLINT reasoner"), err)); + std::process::exit(1); + }, + }; + let verdict: ReasonerResponse = match conn.consult("".into(), (), &mut logger).await { + Ok(res) => res, + Err(err) => { + error!("{}", trace!(("Failed to send message to reasoner {:?}", args.eflint_cmd), err)); + std::process::exit(1); + }, + }; + + // OK, report + match verdict { + ReasonerResponse::Success => println!("{} {}", style("Reasoner says:").bold(), style("OK").bold().green()), + ReasonerResponse::Violated(reasons) => { + println!("{} {}", style("Reasoner says:").bold(), style("VIOLATION").bold().red()); + println!("Reason:"); + println!("{reasons}"); + println!(); + }, + } +} From 74fbfb3001f866a5a32fe9a545ff6fbe18b1db18 Mon Sep 17 00:00:00 2001 From: Lut99 Date: Tue, 6 May 2025 11:22:17 +0200 Subject: [PATCH 47/82] Fixed broken doc links --- lib/reasoners/eflint-haskell/src/reasonerconn.rs | 4 ++-- lib/reasoners/eflint-haskell/src/reasons.rs | 4 ++-- lib/reasoners/eflint-json/src/reasonerconn.rs | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/lib/reasoners/eflint-haskell/src/reasonerconn.rs b/lib/reasoners/eflint-haskell/src/reasonerconn.rs index c4a06cb..001f7b9 100644 --- a/lib/reasoners/eflint-haskell/src/reasonerconn.rs +++ b/lib/reasoners/eflint-haskell/src/reasonerconn.rs @@ -4,7 +4,7 @@ // Created: // 16 Apr 2025, 23:09:26 // Last edited: -// 01 May 2025, 16:59:00 +// 06 May 2025, 11:20:38 // Auto updated? // Yes // @@ -227,7 +227,7 @@ impl EFlintHaskellReasonerConnector { /// then call [`find_deps()`](crate::hash::find_deps()) on the resulting file. /// /// # Returns - /// A [`Path`] representing this file. + /// A [`PathBuf`] representing this file. #[inline] pub const fn base_policy(&self) -> &PathBuf { &self.context.base_policy } } diff --git a/lib/reasoners/eflint-haskell/src/reasons.rs b/lib/reasoners/eflint-haskell/src/reasons.rs index 8dc0548..8000e68 100644 --- a/lib/reasoners/eflint-haskell/src/reasons.rs +++ b/lib/reasoners/eflint-haskell/src/reasons.rs @@ -4,7 +4,7 @@ // Created: // 25 Apr 2025, 16:36:41 // Last edited: -// 01 May 2025, 16:56:22 +// 06 May 2025, 11:21:19 // Auto updated? // Yes // @@ -22,7 +22,7 @@ use crate::trace::Violation; /***** AUXILLARY *****/ -/// Defines either a [`Query`] or a [`Violation`]. +/// Defines either a failed [`Query`](crate::trace::Query) or a [`Violation`]. #[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)] pub enum Problem { QueryFailed, diff --git a/lib/reasoners/eflint-json/src/reasonerconn.rs b/lib/reasoners/eflint-json/src/reasonerconn.rs index 60165f9..f0ea4a0 100644 --- a/lib/reasoners/eflint-json/src/reasonerconn.rs +++ b/lib/reasoners/eflint-json/src/reasonerconn.rs @@ -4,7 +4,7 @@ // Created: // 09 Oct 2024, 15:52:06 // Last edited: -// 06 May 2025, 10:56:58 +// 06 May 2025, 11:21:41 // Auto updated? // Yes // @@ -32,7 +32,7 @@ use crate::spec::EFlintable; /***** ERRORS *****/ -/// Defines the errors returned by the [`EFlintJsonReasonerConnectorector`]. +/// Defines the errors returned by the [`EFlintJsonReasonerConnector`]. #[derive(Debug, Error)] pub enum Error { /// Failed to log the context of the reasoner. From fd09eaa6ec005030c89dc429dabda777390396eb Mon Sep 17 00:00:00 2001 From: Lut99 Date: Tue, 6 May 2025 11:27:54 +0200 Subject: [PATCH 48/82] Fixed `cargo check --no-default-features` failing --- lib/reasoners/eflint-haskell/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/reasoners/eflint-haskell/Cargo.toml b/lib/reasoners/eflint-haskell/Cargo.toml index 69d5ea5..85f5083 100644 --- a/lib/reasoners/eflint-haskell/Cargo.toml +++ b/lib/reasoners/eflint-haskell/Cargo.toml @@ -13,7 +13,7 @@ license.workspace = true serde = { version = "1.0.204", features = ["derive"] } sha2 = "0.10.6" thiserror = "2.0.12" -tokio = { version = "1.44.2", default-features = false, features = ["io-util", "process"] } +tokio = { version = "1.44.2", default-features = false, features = ["fs", "io-util", "process"] } tracing = "0.1.40" error-trace.workspace = true From f108e7fb465e9110356a463bd15fa7efb65c27dc Mon Sep 17 00:00:00 2001 From: Lut99 Date: Tue, 6 May 2025 11:44:03 +0200 Subject: [PATCH 49/82] Now actually fixed broken doc links Turns out warnings are also important. Who knew. --- lib/reasoners/no-op/src/reasonerconn.rs | 4 +-- lib/reasoners/posix/src/config.rs | 5 ++-- lib/reasoners/posix/src/lib.rs | 34 +++++++++++-------------- lib/spec/src/auditlogger.rs | 6 ++--- 4 files changed, 22 insertions(+), 27 deletions(-) diff --git a/lib/reasoners/no-op/src/reasonerconn.rs b/lib/reasoners/no-op/src/reasonerconn.rs index 5e7048a..778dafa 100644 --- a/lib/reasoners/no-op/src/reasonerconn.rs +++ b/lib/reasoners/no-op/src/reasonerconn.rs @@ -4,12 +4,12 @@ // Created: // 10 Oct 2024, 16:21:09 // Last edited: -// 02 Dec 2024, 15:49:08 +// 06 May 2025, 11:31:04 // Auto updated? // Yes // // Description: -//! +//! Implements a reasoner connector that doesn't do ANYTHING (lazy thing). // use std::borrow::Cow; diff --git a/lib/reasoners/posix/src/config.rs b/lib/reasoners/posix/src/config.rs index 7a31d89..7a71477 100644 --- a/lib/reasoners/posix/src/config.rs +++ b/lib/reasoners/posix/src/config.rs @@ -4,7 +4,7 @@ // Created: // 15 Oct 2024, 14:17:44 // Last edited: -// 15 Oct 2024, 14:37:11 +// 06 May 2025, 11:42:26 // Auto updated? // Yes // @@ -47,8 +47,7 @@ pub struct DataPolicy { } /// The local identity defines a user id and a list of group ids. The local identity is used on the machine on which a -/// dataset resides to check the local file permissions. For more about this permissions check see -/// [`validate_dataset_permissions`]. +/// dataset resides to check the local file permissions. /// /// This identity is defined in the POSIX policy file. Global usernames in the POSIX policy map to these local /// identities. diff --git a/lib/reasoners/posix/src/lib.rs b/lib/reasoners/posix/src/lib.rs index 15c43db..ab763f0 100644 --- a/lib/reasoners/posix/src/lib.rs +++ b/lib/reasoners/posix/src/lib.rs @@ -4,7 +4,7 @@ // Created: // 11 Oct 2024, 16:35:23 // Last edited: -// 15 Oct 2024, 14:18:42 +// 06 May 2025, 11:43:36 // Auto updated? // Yes // @@ -33,26 +33,22 @@ //! //! First, it checks the `DATA_INDEX` environment variable or the .env file for the location of the data index. We //! imagine this points towards a mounted distributed file system like NFS. Then, it scans the directories for data -//! index files. From these files a [DataIndex] is created which is passed on to the [PosixReasonerConnector]. +//! index files. From these files a Brane `DataIndex` is created which is passed on to the [PosixReasonerConnector]. //! -//! Now that the [PosixReasonerConnector] is created, it can start to handle requests. There are three types of -//! requests: -//! -//! - [Execute task](fn@PosixReasonerConnector::execute_task) -//! - [Access data](fn@PosixReasonerConnector::access_data_request) -//! - [Workflow validation](fn@PosixReasonerConnector::workflow_validation_request) +//! Now that the [PosixReasonerConnector] is created, it can start to handle requests. These are all handled by +//! [`PosixReasonerConnector::consult()`](spec::reasonerconn::ReasonerConnector::consult()). //! //! As of now, the assumption is taken that it does not matter for this reasoner which type of request comes in, as we -//! only look at the data usage in the [Workflow]. +//! only look at the data usage in the [Workflow](::workflow::Workflow). //! -//! As one of these requests comes in, the provided [Workflow] is parsed using a [DatasetCollectorVisitor] (an -//! implementation of the new util trait [WorkflowVisitor]) and all data accesses in the workflow are gathered and -//! associated with an access type of either read, write, or execute (execute is currently unused as no usage was -//! found). +//! As one of these requests comes in, the provided [Workflow](::workflow::Workflow) is parsed using a +//! `DatasetCollector` (an implementation of the new util trait [Visitor](::workflow::visitor::Visitor)) +//! and all data accesses in the workflow are gathered and associated with an access type of either read, write, or execute +//! (execute is currently unused as no usage was found). //! //! From this point, we iterate over all the different datasets and associated requests/required permissions. For each -//! [Dataset] we look up the path in the [DataIndex]. Now that we have the path and the requested permissions, we can -//! check if the user in the mapping has access to this dataset. +//! [Dataset](::workflow::Dataset) we look up the path in the `DataIndex`. Now that we have the path and the requested +//! permissions, we can check if the user in the mapping has access to this dataset. //! //! ### Current permission model //! @@ -62,7 +58,7 @@ //! the gids extracted from the policy are matched against the file's uid and gid. If the file is owned by the user, the //! owner permissions are checked. If the file is owned by a group the user is in, the group permissions are checked. If //! neither of these is true, the other permissions are checked. If the user has the required permissions, the request is -//! approved. If not, the request is denied : [satisfies_posix_permissions]. +//! approved. If not, the request is denied : `satisfies_posix_permissions()`. //! //! //! # State of the implementation @@ -94,10 +90,10 @@ //! but also the policy reasoner needs to reach at least the directory in which the file resides in order for the //! reasoner to be able to `stat(1)` the file. //! -//! Right now, we are seemingly limited by the fact that the policy reasoner GUI does not send at which site a [Dataset] +//! Right now, we are seemingly limited by the fact that the policy reasoner GUI does not send at which site a [Dataset](::workflow::Dataset) //! is accessed, making it impossible to fully know which of the mappings in the policy to use. There is a location //! field that could be used, but since it is always set to `None`, we opted to fall back to the assumed location as -//! hardcoded in the static: [`ASSUMED_LOCATION`] +//! hardcoded in the static: `ASSUMED_LOCATION`. //! //! # Future work //! @@ -136,7 +132,7 @@ //! but it can be quite daunting to figure out how every part of this system works. The author made a good effort to //! improve the documentation during the running of this project, and that helped a lot. That combined with the already //! existing implementation of the eFlint reasoner made this project possible. We hope that the POSIX (and -//! [no_op](crate::no_op)) reasoner can help guide future contributors in either extension of the current reasoners or +//! `no_op` reasoner can help guide future contributors in either extension of the current reasoners or //! the addition of new reasoner types. // diff --git a/lib/spec/src/auditlogger.rs b/lib/spec/src/auditlogger.rs index ec669c1..14d32c8 100644 --- a/lib/spec/src/auditlogger.rs +++ b/lib/spec/src/auditlogger.rs @@ -4,7 +4,7 @@ // Created: // 09 Oct 2024, 13:38:41 // Last edited: -// 02 Dec 2024, 14:21:01 +// 06 May 2025, 11:32:28 // Auto updated? // Yes // @@ -23,8 +23,8 @@ use crate::reasonerconn::{ReasonerContext, ReasonerResponse}; /***** AUXILLARY *****/ -/// Defines a wrapper around any [`AuditLogger`] that extends it with some kind of [`tracing`]-like -/// session information. +/// Defines a wrapper around any [`AuditLogger`] that extends it with some kind of +/// [`tracing`](https://github.com/tokio-rs/tracing)-like session information. #[derive(Clone, Debug)] pub struct SessionedAuditLogger { /// The reference used to recognize the logs based on user input. From e8412828aa469b4803b99528972afd9890177352 Mon Sep 17 00:00:00 2001 From: Lut99 Date: Tue, 6 May 2025 12:47:38 +0200 Subject: [PATCH 50/82] Added `IntoIterator` impls to `ManyReason` Note that, due to the infernal default `impl From for T`, this requires us to remove the `from()` impl --- lib/reasoners/eflint-haskell/src/reasons.rs | 6 ++-- lib/spec/src/reasons.rs | 31 +++++++++++++++------ 2 files changed, 26 insertions(+), 11 deletions(-) diff --git a/lib/reasoners/eflint-haskell/src/reasons.rs b/lib/reasoners/eflint-haskell/src/reasons.rs index 8000e68..3754759 100644 --- a/lib/reasoners/eflint-haskell/src/reasons.rs +++ b/lib/reasoners/eflint-haskell/src/reasons.rs @@ -4,7 +4,7 @@ // Created: // 25 Apr 2025, 16:36:41 // Last edited: -// 06 May 2025, 11:21:19 +// 06 May 2025, 12:44:08 // Auto updated? // Yes // @@ -132,5 +132,7 @@ impl ReasonHandler for VerboseHandler { type Reason = ManyReason; #[inline] - fn handle(&self, problems: impl IntoIterator) -> Self::Reason { ManyReason::from(problems.into_iter().map(|p| p.to_string())) } + fn handle(&self, problems: impl IntoIterator) -> Self::Reason { + ManyReason::from_iter(problems.into_iter().map(|p| p.to_string())) + } } diff --git a/lib/spec/src/reasons.rs b/lib/spec/src/reasons.rs index 917feea..07acd17 100644 --- a/lib/spec/src/reasons.rs +++ b/lib/spec/src/reasons.rs @@ -4,7 +4,7 @@ // Created: // 17 Oct 2024, 09:53:49 // Last edited: -// 14 Nov 2024, 15:25:23 +// 06 May 2025, 12:45:11 // Auto updated? // Yes // @@ -52,10 +52,6 @@ impl ManyReason { /// A new ManyReason that doesn't have any reasons embedded in it yet but space for at least `capacity` reasons. #[inline] pub fn with_capacity(capacity: usize) -> Self { Self(Vec::with_capacity(capacity)) } - - /// Consumes the set and returns and iterator-by-ownership. - #[inline] - pub fn into_iter(self) -> std::vec::IntoIter { self.0.into_iter() } } impl Display for ManyReason { #[inline] @@ -81,11 +77,28 @@ impl DerefMut for ManyReason { #[inline] fn deref_mut(&mut self) -> &mut Self::Target { &mut self.0 } } -impl FromIterator for ManyReason { +impl<'a, R> IntoIterator for &'a ManyReason { + type IntoIter = std::slice::Iter<'a, R>; + type Item = &'a R; + #[inline] - fn from_iter>(iter: T) -> Self { Self(iter.into_iter().collect()) } + fn into_iter(self) -> Self::IntoIter { self.0.iter() } } -impl> From for ManyReason { +impl<'a, R> IntoIterator for &'a mut ManyReason { + type IntoIter = std::slice::IterMut<'a, R>; + type Item = &'a mut R; + #[inline] - fn from(value: I) -> Self { Self::from_iter(value) } + fn into_iter(self) -> Self::IntoIter { self.0.iter_mut() } +} +impl IntoIterator for ManyReason { + type IntoIter = std::vec::IntoIter; + type Item = R; + + #[inline] + fn into_iter(self) -> Self::IntoIter { self.0.into_iter() } +} +impl FromIterator for ManyReason { + #[inline] + fn from_iter>(iter: T) -> Self { Self(iter.into_iter().collect()) } } From 1ade6c75459fb62d2bb0bd8805b558881f418bd4 Mon Sep 17 00:00:00 2001 From: Lut99 Date: Tue, 6 May 2025 12:55:41 +0200 Subject: [PATCH 51/82] Clippy fixes --- lib/loggers/no-op/src/logger.rs | 30 +++------ lib/reasoners/eflint-haskell/src/hash.rs | 13 ++-- .../eflint-haskell/src/reasonerconn.rs | 8 +-- lib/reasoners/eflint-haskell/src/reasons.rs | 2 +- lib/reasoners/eflint-haskell/src/spec.rs | 10 +-- lib/reasoners/eflint-haskell/src/trace.rs | 62 +++++++++---------- lib/reasoners/eflint-json/src/reasonerconn.rs | 20 +++--- lib/reasoners/eflint-json/src/spec.rs | 6 +- lib/reasoners/no-op/src/reasonerconn.rs | 16 +++-- lib/reasoners/posix/Cargo.toml | 2 +- lib/reasoners/posix/src/lib.rs | 54 ++++++++-------- lib/reasoners/posix/src/reasonerconn.rs | 20 +++--- lib/spec/src/auditlogger.rs | 4 +- lib/workflow/src/lib.rs | 8 +-- lib/workflow/src/visitor.rs | 6 +- 15 files changed, 116 insertions(+), 145 deletions(-) diff --git a/lib/loggers/no-op/src/logger.rs b/lib/loggers/no-op/src/logger.rs index fe8bc2f..9d0e793 100644 --- a/lib/loggers/no-op/src/logger.rs +++ b/lib/loggers/no-op/src/logger.rs @@ -14,7 +14,6 @@ use std::convert::Infallible; use std::fmt::Display; -use std::future::Future; use serde::Serialize; use spec::auditlogger::AuditLogger; @@ -40,41 +39,30 @@ impl AuditLogger for MockLogger { type Error = Infallible; #[inline] - fn log_context<'a, C>(&'a self, _context: &'a C) -> impl 'a + Future> + async fn log_context<'a, C>(&'a self, _context: &'a C) -> Result<(), Self::Error> where C: ?Sized + ReasonerContext, { - async move { - println!("AUDIT LOG: log_context"); - Ok(()) - } + println!("AUDIT LOG: log_context"); + Ok(()) } #[inline] - fn log_response<'a, R>( - &'a self, - _reference: &'a str, - _response: &'a ReasonerResponse, - _raw: Option<&'a str>, - ) -> impl 'a + Future> + async fn log_response<'a, R>(&'a self, _reference: &'a str, _response: &'a ReasonerResponse, _raw: Option<&'a str>) -> Result<(), Self::Error> where R: Display, { - async move { - println!("AUDIT LOG: log_response"); - Ok(()) - } + println!("AUDIT LOG: log_response"); + Ok(()) } #[inline] - fn log_question<'a, S, Q>(&'a self, _reference: &'a str, _state: &'a S, _question: &'a Q) -> impl 'a + Future> + async fn log_question<'a, S, Q>(&'a self, _reference: &'a str, _state: &'a S, _question: &'a Q) -> Result<(), Self::Error> where S: Serialize, Q: Serialize, { - async move { - println!("AUDIT LOG: log_question"); - Ok(()) - } + println!("AUDIT LOG: log_question"); + Ok(()) } } diff --git a/lib/reasoners/eflint-haskell/src/hash.rs b/lib/reasoners/eflint-haskell/src/hash.rs index 6c74afb..4182a75 100644 --- a/lib/reasoners/eflint-haskell/src/hash.rs +++ b/lib/reasoners/eflint-haskell/src/hash.rs @@ -4,7 +4,7 @@ // Created: // 01 May 2025, 14:33:06 // Last edited: -// 01 May 2025, 16:33:30 +// 06 May 2025, 12:53:18 // Auto updated? // Yes // @@ -28,13 +28,13 @@ use tracing::{Level, debug, span}; /***** ERRORS *****/ /// Formats a list but like, prettily. struct PrettyPathListFormatter<'l>(&'l [PathBuf]); -impl<'l> Display for PrettyPathListFormatter<'l> { +impl Display for PrettyPathListFormatter<'_> { #[inline] fn fmt(&self, f: &mut Formatter<'_>) -> FResult { if self.0.is_empty() { return write!(f, ""); } - for (i, path) in self.0.into_iter().enumerate() { + for (i, path) in self.0.iter().enumerate() { if i > 0 && i < self.0.len() - 1 { write!(f, ", ")?; } else if i > 0 { @@ -215,9 +215,9 @@ async fn find_deps_of(mut handle: File, path: &Path, base_path: &Path, include_d // If we haven't added it yet, add it if !files.contains(imppath.as_ref()) { - files.insert(imppath.to_owned().into_owned()); + files.insert(imppath.clone().into_owned()); Box::pin(find_deps_of( - File::open(imppath).await.map_err(|source| Error::FileOpen { path: imppath.to_owned().into_owned(), source })?, + File::open(imppath).await.map_err(|source| Error::FileOpen { path: imppath.clone().into_owned(), source })?, imppath.as_ref(), base_path, include_dirs, @@ -280,8 +280,7 @@ pub async fn find_deps(path: impl AsRef, include_dirs: &[&Path]) -> Result // Delegate to the recursive function let mut res: HashSet = HashSet::with_capacity(16); - find_deps_of(File::open(path).await.map_err(|source| Error::FileOpen { path: path.into(), source })?, path, path, &include_dirs, &mut res) - .await?; + find_deps_of(File::open(path).await.map_err(|source| Error::FileOpen { path: path.into(), source })?, path, path, include_dirs, &mut res).await?; // Done Ok(res) diff --git a/lib/reasoners/eflint-haskell/src/reasonerconn.rs b/lib/reasoners/eflint-haskell/src/reasonerconn.rs index 001f7b9..892b9c4 100644 --- a/lib/reasoners/eflint-haskell/src/reasonerconn.rs +++ b/lib/reasoners/eflint-haskell/src/reasonerconn.rs @@ -4,7 +4,7 @@ // Created: // 16 Apr 2025, 23:09:26 // Last edited: -// 06 May 2025, 11:20:38 +// 06 May 2025, 12:53:34 // Auto updated? // Yes // @@ -179,11 +179,11 @@ impl EFlintHaskellReasonerConnector { /// /// # Errors /// This function can error if it failed to log the initial context to the given `logger`. - pub async fn new_async<'l, L: AuditLogger>( + pub async fn new_async( cmd: impl IntoIterator, base_policy_path: impl Into, handler: R, - logger: &'l L, + logger: &L, ) -> Result { let base_policy: PathBuf = base_policy_path.into(); @@ -369,7 +369,7 @@ where let res: ReasonerResponse<_> = trace .deltas .into_iter() - .last() + .next_back() .map(|delta| match delta { Delta::Query(query) if query.is_succes() => ReasonerResponse::Success, Delta::Query(_) => ReasonerResponse::Violated(self.handler.handle(problems)), diff --git a/lib/reasoners/eflint-haskell/src/reasons.rs b/lib/reasoners/eflint-haskell/src/reasons.rs index 3754759..d23f25c 100644 --- a/lib/reasoners/eflint-haskell/src/reasons.rs +++ b/lib/reasoners/eflint-haskell/src/reasons.rs @@ -93,7 +93,7 @@ impl<'s> PrefixedHandler<'s> { #[inline] pub fn new(prefix: impl Into>) -> Self { Self { prefix: prefix.into() } } } -impl<'s> ReasonHandler for PrefixedHandler<'s> { +impl ReasonHandler for PrefixedHandler<'_> { type Reason = ManyReason; #[inline] diff --git a/lib/reasoners/eflint-haskell/src/spec.rs b/lib/reasoners/eflint-haskell/src/spec.rs index 1257a4e..3955c22 100644 --- a/lib/reasoners/eflint-haskell/src/spec.rs +++ b/lib/reasoners/eflint-haskell/src/spec.rs @@ -4,7 +4,7 @@ // Created: // 16 Apr 2025, 23:43:13 // Last edited: -// 29 Apr 2025, 15:48:13 +// 06 May 2025, 12:53:46 // Auto updated? // Yes // @@ -24,7 +24,7 @@ pub struct EFlintableFormatter<'o, E: ?Sized> { /// Some object implementing [`EFlintable`]. obj: &'o E, } -impl<'o, E: ?Sized + EFlintable> Display for EFlintableFormatter<'o, E> { +impl Display for EFlintableFormatter<'_, E> { #[inline] fn fmt(&self, f: &mut Formatter<'_>) -> FResult { self.obj.eflint_fmt(f) } } @@ -59,15 +59,15 @@ impl EFlintable for String { } // Pointer impls -impl<'a, T: ?Sized + EFlintable> EFlintable for &'a T { +impl EFlintable for &T { #[inline] fn eflint_fmt(&self, f: &mut Formatter<'_>) -> FResult { ::eflint_fmt(self, f) } } -impl<'a, T: ?Sized + EFlintable> EFlintable for &'a mut T { +impl EFlintable for &mut T { #[inline] fn eflint_fmt(&self, f: &mut Formatter<'_>) -> FResult { ::eflint_fmt(self, f) } } -impl<'a, T: ?Sized + Clone + EFlintable> EFlintable for Cow<'a, T> { +impl EFlintable for Cow<'_, T> { #[inline] fn eflint_fmt(&self, f: &mut Formatter<'_>) -> FResult { ::eflint_fmt(self, f) } } diff --git a/lib/reasoners/eflint-haskell/src/trace.rs b/lib/reasoners/eflint-haskell/src/trace.rs index a05b4a2..41cf632 100644 --- a/lib/reasoners/eflint-haskell/src/trace.rs +++ b/lib/reasoners/eflint-haskell/src/trace.rs @@ -4,7 +4,7 @@ // Created: // 17 Apr 2025, 00:06:39 // Last edited: -// 01 May 2025, 10:18:38 +// 06 May 2025, 12:55:32 // Auto updated? // Yes // @@ -23,17 +23,17 @@ use thiserror::Error; /***** CONSTANTS *****/ -const DISABLED_ACTION: &'static str = "disabled action:"; -const EXEC_TRANS: &'static str = "executed transition:"; -const NEW_INVARIANT: &'static str = "New invariant"; -const NEW_TYPE: &'static str = "New type"; -const TRANS_ENABLED: &'static str = "(ENABLED)"; -const TRANS_DISABLED: &'static str = "(DISABLED)"; -const QUERY_SUCCESS: &'static str = "query successful"; -const QUERY_FAILED: &'static str = "query failed"; -const VIOLATIONS: &'static str = "violations:"; -const VIOLATED_DUTY: &'static str = "violated duty!:"; -const VIOLATED_INVARIANT: &'static str = "violated invariant!:"; +const DISABLED_ACTION: &str = "disabled action:"; +const EXEC_TRANS: &str = "executed transition:"; +const NEW_INVARIANT: &str = "New invariant"; +const NEW_TYPE: &str = "New type"; +const TRANS_ENABLED: &str = "(ENABLED)"; +const TRANS_DISABLED: &str = "(DISABLED)"; +const QUERY_SUCCESS: &str = "query successful"; +const QUERY_FAILED: &str = "query failed"; +const VIOLATIONS: &str = "violations:"; +const VIOLATED_DUTY: &str = "violated duty!:"; +const VIOLATED_INVARIANT: &str = "violated invariant!:"; @@ -296,11 +296,11 @@ impl FromStrHead for Query { #[inline] fn from_str_head(s: &str) -> Result, Self::Error> { - if s.starts_with(QUERY_SUCCESS) { - return Ok(Some((&s[QUERY_SUCCESS.len()..], Self::Succes))); + if let Some(rem) = s.strip_prefix(QUERY_SUCCESS) { + return Ok(Some((rem, Self::Succes))); } - if s.starts_with(QUERY_FAILED) { - return Ok(Some((&s[QUERY_FAILED.len()..], Self::Fail))); + if let Some(rem) = s.strip_prefix(QUERY_FAILED) { + return Ok(Some((rem, Self::Fail))); } Ok(None) } @@ -421,10 +421,10 @@ impl FromStrHead for Vec { // Parse the optional 'ENABLED|DISABLED' bizz let rem = rem.trim_start(); - let (mut rem, enabled): (&str, Option) = if rem.starts_with(TRANS_ENABLED) { - (rem[TRANS_ENABLED.len()..].trim_start(), Some(true)) - } else if rem.starts_with(TRANS_DISABLED) { - (rem[TRANS_DISABLED.len()..].trim_start(), Some(false)) + let (mut rem, enabled): (&str, Option) = if let Some(rem) = rem.strip_prefix(TRANS_ENABLED) { + (rem.trim_start(), Some(true)) + } else if let Some(rem) = rem.strip_prefix(TRANS_DISABLED) { + (rem.trim_start(), Some(false)) } else { (rem, None) }; @@ -451,10 +451,10 @@ impl FromStrHead for Vec { // Parse the optional 'ENABLED|DISABLED' bizz let newrem = newrem.trim_start(); - let (newrem, enabled): (&str, Option) = if newrem.starts_with(TRANS_ENABLED) { - (newrem[TRANS_ENABLED.len()..].trim_start(), Some(true)) - } else if newrem.starts_with(TRANS_DISABLED) { - (newrem[TRANS_DISABLED.len()..].trim_start(), Some(false)) + let (newrem, enabled): (&str, Option) = if let Some(rem) = newrem.strip_prefix(TRANS_ENABLED) { + (rem.trim_start(), Some(true)) + } else if let Some(rem) = newrem.strip_prefix(TRANS_DISABLED) { + (rem.trim_start(), Some(false)) } else { (newrem, None) }; @@ -728,7 +728,7 @@ impl FromStrHead for IntLit { let mut value: i64 = 0; let mut seen_one: bool = false; for (i, c) in next.into_iter().chain(chars) { - if c >= '0' && c <= '9' { + if c.is_ascii_digit() { // Multiply the existing value by 10 to "move them left" if modifier > 0 && value > i64::MAX / 10 || modifier < 0 && value < i64::MIN / 10 { return Err(Error::OutOfRangeInt { s: s.into() }); @@ -752,11 +752,7 @@ impl FromStrHead for IntLit { } // If we got here, then there were 0 digits - if seen_one { - return Ok(Some((&s[s.len()..], IntLit(value)))); - } else { - return Ok(None); - } + if seen_one { Ok(Some((&s[s.len()..], IntLit(value)))) } else { Ok(None) } } } @@ -812,7 +808,7 @@ impl FromStrHead for Composite { // If we got here, not enough time to find closing parenthesis if matches!(rem.chars().next(), Some(')')) { - return Ok(Some((&rem[1..], Self { name, args }))); + Ok(Some((&rem[1..], Self { name, args }))) } else { Err(Error::UnterminatedParen { s: rem.into() }) } @@ -840,7 +836,7 @@ impl FromStrHead for TypeName { let mut depth: usize = 1; for (i, c) in s.char_indices() { if i == 0 { - if (c >= 'a' && c <= 'z') || c == '_' || c == '-' { + if c.is_ascii_lowercase() || c == '_' || c == '-' { kind = Kind::Plain; } else if c == '[' { kind = Kind::Brackets; @@ -851,7 +847,7 @@ impl FromStrHead for TypeName { } } else if matches!(kind, Kind::Plain) { // Stop when we find a non-valid character - if (c < 'a' || c > 'z') && (c < 'A' || c > 'Z') && c != '-' && c != '_' { + if !c.is_ascii_lowercase() && !c.is_ascii_uppercase() && c != '-' && c != '_' { return Ok(Some((&s[i..], Self(s[..i].into())))); } } else if matches!(kind, Kind::Brackets) { diff --git a/lib/reasoners/eflint-json/src/reasonerconn.rs b/lib/reasoners/eflint-json/src/reasonerconn.rs index f0ea4a0..0a410a0 100644 --- a/lib/reasoners/eflint-json/src/reasonerconn.rs +++ b/lib/reasoners/eflint-json/src/reasonerconn.rs @@ -167,7 +167,7 @@ impl<'a> EFlintJsonReasonerContextFull<'a> { #[inline] pub fn new(addr: &'a str) -> Self { Self { context: EFlintJsonReasonerContext::default(), addr } } } -impl<'a> ReasonerContext for EFlintJsonReasonerContextFull<'a> { +impl ReasonerContext for EFlintJsonReasonerContextFull<'_> { #[inline] fn version(&self) -> Cow { Cow::Borrowed(&self.context.version) } @@ -212,11 +212,11 @@ impl EFlintJsonReasonerConnector { /// # Errors /// This function may error if it failed to log to the given `logger`. #[inline] - pub fn new_async<'l, L: AuditLogger>( + pub async fn new_async<'l, L: AuditLogger>( addr: impl 'l + Into, handler: R, logger: &'l L, - ) -> impl 'l + Future>> + ) -> Result> where R: 'l + ReasonHandler, R::Reason: Display, @@ -226,14 +226,12 @@ impl EFlintJsonReasonerConnector { Q: EFlintable, Q::Error: 'static, { - async move { - let addr: String = addr.into(); - logger - .log_context(&EFlintJsonReasonerContextFull::new(&addr)) - .await - .map_err(|err| Error::LogContext { to: std::any::type_name::(), err: err.freeze() })?; - Ok(Self { addr, reason_handler: handler, _state: PhantomData, _question: PhantomData }) - } + let addr: String = addr.into(); + logger + .log_context(&EFlintJsonReasonerContextFull::new(&addr)) + .await + .map_err(|err| Error::LogContext { to: std::any::type_name::(), err: err.freeze() })?; + Ok(Self { addr, reason_handler: handler, _state: PhantomData, _question: PhantomData }) } } impl ReasonerConnector for EFlintJsonReasonerConnector diff --git a/lib/reasoners/eflint-json/src/spec.rs b/lib/reasoners/eflint-json/src/spec.rs index 62775d9..ac7a153 100644 --- a/lib/reasoners/eflint-json/src/spec.rs +++ b/lib/reasoners/eflint-json/src/spec.rs @@ -71,19 +71,19 @@ impl EFlintable for Phrase { } // Pointer impls -impl<'a, T: ?Sized + EFlintable> EFlintable for &'a T { +impl EFlintable for &T { type Error = T::Error; #[inline] fn to_eflint(&self) -> Result, Self::Error> { ::to_eflint(self) } } -impl<'a, T: ?Sized + EFlintable> EFlintable for &'a mut T { +impl EFlintable for &mut T { type Error = T::Error; #[inline] fn to_eflint(&self) -> Result, Self::Error> { ::to_eflint(self) } } -impl<'a, T: ?Sized + EFlintable + ToOwned> EFlintable for Cow<'a, T> { +impl EFlintable for Cow<'_, T> { type Error = T::Error; #[inline] diff --git a/lib/reasoners/no-op/src/reasonerconn.rs b/lib/reasoners/no-op/src/reasonerconn.rs index 778dafa..56aeb12 100644 --- a/lib/reasoners/no-op/src/reasonerconn.rs +++ b/lib/reasoners/no-op/src/reasonerconn.rs @@ -4,7 +4,7 @@ // Created: // 10 Oct 2024, 16:21:09 // Last edited: -// 06 May 2025, 11:31:04 +// 06 May 2025, 12:51:00 // Auto updated? // Yes // @@ -104,14 +104,12 @@ impl NoOpReasonerConnector { /// # Errors /// This function may error if it failed to log to the given `logger`. #[inline] - pub fn new_async<'l, L: AuditLogger>(logger: &'l mut L) -> impl 'l + Future> { - async move { - logger - .log_context(&NoOpReasonerContext::default()) - .await - .map_err(|err| Error::LogContext { to: std::any::type_name::(), err: err.freeze() })?; - Ok(Self { _question: PhantomData }) - } + pub async fn new_async(logger: &mut L) -> Result { + logger + .log_context(&NoOpReasonerContext::default()) + .await + .map_err(|err| Error::LogContext { to: std::any::type_name::(), err: err.freeze() })?; + Ok(Self { _question: PhantomData }) } } impl ReasonerConnector for NoOpReasonerConnector diff --git a/lib/reasoners/posix/Cargo.toml b/lib/reasoners/posix/Cargo.toml index 2fda921..8bf4b5e 100644 --- a/lib/reasoners/posix/Cargo.toml +++ b/lib/reasoners/posix/Cargo.toml @@ -132,7 +132,7 @@ existing implementation of the eFlint reasoner made this project possible. We ho the addition of new reasoner types. """ edition = "2021" -rust-version = "1.75.0" +rust-version = "1.80.0" authors = [ "Daniel Voogsgerd", "Tim Müller" ] version = "0.2.0" repository.workspace = true diff --git a/lib/reasoners/posix/src/lib.rs b/lib/reasoners/posix/src/lib.rs index ab763f0..9e575f3 100644 --- a/lib/reasoners/posix/src/lib.rs +++ b/lib/reasoners/posix/src/lib.rs @@ -4,7 +4,7 @@ // Created: // 11 Oct 2024, 16:35:23 // Last edited: -// 06 May 2025, 11:43:36 +// 06 May 2025, 12:50:19 // Auto updated? // Yes // @@ -98,42 +98,42 @@ //! # Future work //! //! - Support file creation: This requires us to look at the permissions of the parent directory of the potential -//! dataset, but this might be tricky considering the fact that the policy reasoner also needs to be able to access the -//! directory in order to check the file permissions. +//! dataset, but this might be tricky considering the fact that the policy reasoner also needs to be able to access the +//! directory in order to check the file permissions. //! //! - Support multiple user mappings per location: Right now there is support in the code for multiple locations with -//! each their own user mapping, as different sites will often comprise of different network shares. But as it is -//! unclear right now how the volumes will be mounted on the reasoner container, it is hard to tell how such an -//! implementation is best designed. +//! each their own user mapping, as different sites will often comprise of different network shares. But as it is +//! unclear right now how the volumes will be mounted on the reasoner container, it is hard to tell how such an +//! implementation is best designed. //! //! - Right now, this reasoner is both a module and a binary, but it should probably just be a binary. At this moment -//! however, documenting binaries is tricky in rust. As soon as we find a better solution, this documentation should be -//! moved to the binary itself. This might be useful in general, but particularly it is important to document reference -//! implementations. +//! however, documenting binaries is tricky in rust. As soon as we find a better solution, this documentation should be +//! moved to the binary itself. This might be useful in general, but particularly it is important to document reference +//! implementations. //! //! - LDAP / Active Directory support: Currently the Brane user to uid / gid mapping is embedded in the policy that is -//! loaded at the moment the reasoner is started. The idea of mapping users to uid and gids is not unique though, these -//! mappings can be sythesized from all sorts of resources. The most straightforward variant would be the loading of a -//! `passwd(5)` file, but since we are aiming at distributed file systems this would probably be of limited use. In -//! situations where file systems like NFS are often used, the users are store in Active Directory and accessed using -//! LDAP. Writing such an adapter to function as the user map seems so be a valuable addition to this reasoner, as this -//! would complete the picture of current existing systems and would allow a user of the reasoner to attach their -//! already existing and managed file systems with the correct access control. This would significantly reduce the -//! required investment of introducing policies in new Brane users. +//! loaded at the moment the reasoner is started. The idea of mapping users to uid and gids is not unique though, these +//! mappings can be sythesized from all sorts of resources. The most straightforward variant would be the loading of a +//! `passwd(5)` file, but since we are aiming at distributed file systems this would probably be of limited use. In +//! situations where file systems like NFS are often used, the users are store in Active Directory and accessed using +//! LDAP. Writing such an adapter to function as the user map seems so be a valuable addition to this reasoner, as this +//! would complete the picture of current existing systems and would allow a user of the reasoner to attach their +//! already existing and managed file systems with the correct access control. This would significantly reduce the +//! required investment of introducing policies in new Brane users. //! //! - ACL support: Besides regular POSIX file permissions, many file systems also support [POSIX -//! ACL](https://web.archive.org/web/20240210045229/https://www.usenix.org/legacy/publications/library/proceedings/usenix03/tech/freenix03/full_papers/gruenbacher/gruenbacher_html/main.html). -//! This would be an obvious and very useful extension to this reasoner. Usage of these ACLs is by far less common that -//! the regular POSIX permission it attempts to extend, but usage is also far from uncommon and potenial users of Brane, -//! and more specifically the policy reasoner, could have used these ACLs on their existing file systems. +//! ACL](https://web.archive.org/web/20240210045229/https://www.usenix.org/legacy/publications/library/proceedings/usenix03/tech/freenix03/full_papers/gruenbacher/gruenbacher_html/main.html). +//! This would be an obvious and very useful extension to this reasoner. Usage of these ACLs is by far less common that +//! the regular POSIX permission it attempts to extend, but usage is also far from uncommon and potenial users of Brane, +//! and more specifically the policy reasoner, could have used these ACLs on their existing file systems. //! //! - The last point of future work is not specific to the POSIX reasoner, but more to the policy reasoner repository. -//! There is a desperate need for more elaborate documentation. We attempted to create a nice start with this reasoner, -//! but it can be quite daunting to figure out how every part of this system works. The author made a good effort to -//! improve the documentation during the running of this project, and that helped a lot. That combined with the already -//! existing implementation of the eFlint reasoner made this project possible. We hope that the POSIX (and -//! `no_op` reasoner can help guide future contributors in either extension of the current reasoners or -//! the addition of new reasoner types. +//! There is a desperate need for more elaborate documentation. We attempted to create a nice start with this reasoner, +//! but it can be quite daunting to figure out how every part of this system works. The author made a good effort to +//! improve the documentation during the running of this project, and that helped a lot. That combined with the already +//! existing implementation of the eFlint reasoner made this project possible. We hope that the POSIX (and +//! `no_op` reasoner can help guide future contributors in either extension of the current reasoners or +//! the addition of new reasoner types. // // Declare the modules diff --git a/lib/reasoners/posix/src/reasonerconn.rs b/lib/reasoners/posix/src/reasonerconn.rs index 7eda717..81d9d18 100644 --- a/lib/reasoners/posix/src/reasonerconn.rs +++ b/lib/reasoners/posix/src/reasonerconn.rs @@ -4,7 +4,7 @@ // Created: // 11 Oct 2024, 16:54:51 // Last edited: -// 02 Dec 2024, 15:48:38 +// 06 May 2025, 12:52:13 // Auto updated? // Yes // @@ -193,7 +193,7 @@ impl PosixFilePermission { /// # Returns /// A [`PosixFilePermissions`] representing the ste. #[inline] - fn to_set(&self) -> PosixFilePermissions { PosixFilePermissions(self.to_mode_bit() as u8) } + fn to_set(self) -> PosixFilePermissions { PosixFilePermissions(self.to_mode_bit() as u8) } } impl BitOr for PosixFilePermission { type Output = PosixFilePermissions; @@ -259,14 +259,12 @@ impl PosixReasonerConnector { /// # Errors /// This function may error if it failed to log to the given `logger`. #[inline] - pub fn new_async<'l, L: AuditLogger>(logger: &'l mut L) -> impl 'l + Future> { - async move { - logger - .log_context(&PosixReasonerContext::default()) - .await - .map_err(|err| Error::LogContext { to: std::any::type_name::(), err: err.freeze() })?; - Ok(Self) - } + pub async fn new_async(logger: &mut L) -> Result { + logger + .log_context(&PosixReasonerContext::default()) + .await + .map_err(|err| Error::LogContext { to: std::any::type_name::(), err: err.freeze() })?; + Ok(Self) } } impl ReasonerConnector for PosixReasonerConnector { @@ -310,7 +308,7 @@ impl ReasonerConnector for PosixReasonerConnector { // Find the location of the dataset in the list let policy: &DataPolicy = match state.config.data.get(&dataset.id) { - Some(data) => &data, + Some(data) => data, None => return Err(Error::UnknownDataset { data: dataset.id.clone() }), }; diff --git a/lib/spec/src/auditlogger.rs b/lib/spec/src/auditlogger.rs index 14d32c8..e7a1fe0 100644 --- a/lib/spec/src/auditlogger.rs +++ b/lib/spec/src/auditlogger.rs @@ -173,7 +173,7 @@ pub trait AuditLogger { } // Standard impls -impl<'a, T: AuditLogger> AuditLogger for &'a T { +impl AuditLogger for &T { type Error = T::Error; #[inline] @@ -211,7 +211,7 @@ impl<'a, T: AuditLogger> AuditLogger for &'a T { ::log_question(self, reference, state, question) } } -impl<'a, T: AuditLogger> AuditLogger for &'a mut T { +impl AuditLogger for &mut T { type Error = T::Error; #[inline] diff --git a/lib/workflow/src/lib.rs b/lib/workflow/src/lib.rs index 96ce0c0..1ea81ad 100644 --- a/lib/workflow/src/lib.rs +++ b/lib/workflow/src/lib.rs @@ -4,7 +4,7 @@ // Created: // 08 Oct 2024, 16:16:26 // Last edited: -// 22 Oct 2024, 10:35:57 +// 06 May 2025, 12:48:36 // Auto updated? // Yes // @@ -48,9 +48,6 @@ impl Hash for Dataset { impl PartialEq for Dataset { #[inline] fn eq(&self, other: &Self) -> bool { self.id == other.id } - - #[inline] - fn ne(&self, other: &Self) -> bool { self.id != other.id } } /// Represents a user/site that can compute, store data, do neither or do both. @@ -71,9 +68,6 @@ impl Hash for Entity { impl PartialEq for Entity { #[inline] fn eq(&self, other: &Self) -> bool { self.id == other.id } - - #[inline] - fn ne(&self, other: &Self) -> bool { self.id != other.id } } /// Represents a "tag" and everything we need to know. diff --git a/lib/workflow/src/visitor.rs b/lib/workflow/src/visitor.rs index d6cb418..b3dbaba 100644 --- a/lib/workflow/src/visitor.rs +++ b/lib/workflow/src/visitor.rs @@ -197,7 +197,7 @@ pub trait Visitor<'w> { #[inline] fn visit_stop(&mut self) -> Result<(), Self::Error> { Ok(()) } } -impl<'a, 'w, T: Visitor<'w>> Visitor<'w> for &'a mut T { +impl<'w, T: Visitor<'w>> Visitor<'w> for &mut T { type Error = T::Error; #[inline] @@ -414,7 +414,7 @@ pub trait VisitorMut<'w> { #[inline] fn visit_stop(&mut self) -> Result<(), Self::Error> { Ok(()) } } -impl<'a, 'w, T: VisitorMut<'w>> VisitorMut<'w> for &'a mut T { +impl<'w, T: VisitorMut<'w>> VisitorMut<'w> for &mut T { type Error = T::Error; #[inline] @@ -625,7 +625,7 @@ pub trait VisitorOwned { #[inline] fn visit_stop(&mut self) -> Result { Ok(Elem::Stop) } } -impl<'a, T: VisitorOwned> VisitorOwned for &'a mut T { +impl VisitorOwned for &mut T { type Error = T::Error; #[inline] From b34e81aaa3664b6a7d1a7292fdb82b7aba82c84c Mon Sep 17 00:00:00 2001 From: Lut99 Date: Tue, 6 May 2025 13:30:09 +0200 Subject: [PATCH 52/82] Updated tokio, actually got proper MSRV this time Also removed obsolete skeleton of a crate. --- Cargo.lock | 4 ---- Cargo.toml | 1 - lib/eflint-spec/Cargo.toml | 16 ---------------- lib/eflint-spec/src/lib.rs | 14 -------------- lib/eflint-to-json/Cargo.toml | 4 ++-- lib/loggers/file/Cargo.toml | 2 +- lib/reasoners/eflint-haskell/Cargo.toml | 2 +- lib/reasoners/eflint-json/Cargo.toml | 2 +- lib/reasoners/posix/Cargo.toml | 4 ++-- lib/resolvers/file/Cargo.toml | 2 +- 10 files changed, 8 insertions(+), 43 deletions(-) delete mode 100644 lib/eflint-spec/Cargo.toml delete mode 100644 lib/eflint-spec/src/lib.rs diff --git a/Cargo.lock b/Cargo.lock index 20fc773..22da984 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -342,10 +342,6 @@ dependencies = [ "tracing", ] -[[package]] -name = "eflint-spec" -version = "0.1.0" - [[package]] name = "eflint-to-json" version = "0.2.0" diff --git a/Cargo.toml b/Cargo.toml index 5752600..5f4863a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,7 +14,6 @@ members = [ "lib/eflint-to-json", # Specifications - "lib/eflint-spec", "lib/spec", "lib/workflow" ] diff --git a/lib/eflint-spec/Cargo.toml b/lib/eflint-spec/Cargo.toml deleted file mode 100644 index eb54efd..0000000 --- a/lib/eflint-spec/Cargo.toml +++ /dev/null @@ -1,16 +0,0 @@ -[package] -name = "eflint-spec" -description = "Implements some common eFLINT definitions used across various eFLINT reasoners." -edition = "2021" -rust-version = "1.75.0" -authors = [ "Tim Müller" ] -version = "0.1.0" -repository.workspace = true -license.workspace = true - - -[dependencies] - - -[features] -default = [] diff --git a/lib/eflint-spec/src/lib.rs b/lib/eflint-spec/src/lib.rs deleted file mode 100644 index cb361fb..0000000 --- a/lib/eflint-spec/src/lib.rs +++ /dev/null @@ -1,14 +0,0 @@ -// LIB.rs -// by Lut99 -// -// Created: -// 16 Apr 2025, 23:16:06 -// Last edited: -// 16 Apr 2025, 23:19:01 -// Auto updated? -// Yes -// -// Description: -//! Implements some common eFLINT definitions used across various eFLINT -//! reasoners. -// diff --git a/lib/eflint-to-json/Cargo.toml b/lib/eflint-to-json/Cargo.toml index 8a98dcf..7f68738 100644 --- a/lib/eflint-to-json/Cargo.toml +++ b/lib/eflint-to-json/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "eflint-to-json" description = "Defines a high-level wrapper around Olaf's [`eflint-to-json`](https://github.com/Olaf-Erkemeij/eflint-server) executable that compiles eFLINT to eFLINT JSON Specification." -rust-version = "1.75.0" +rust-version = "1.81.0" edition = "2021" version = "0.2.0" authors = ["Tim Müller"] @@ -19,7 +19,7 @@ indicatif = "0.17" log = { version = "0.4.22", optional = true } reqwest = { version = "0.12.0", default-features = false, features = ["blocking", "stream"] } sha2 = "0.10.6" -tokio = { version = "1.40.0", default-features = false, features = ["fs", "io-util", "process"], optional = true } +tokio = { version = "1.44.2", default-features = false, features = ["fs", "io-util", "process"], optional = true } tracing = { version = "0.1.40", optional = true } diff --git a/lib/loggers/file/Cargo.toml b/lib/loggers/file/Cargo.toml index 4af00e1..87179ea 100644 --- a/lib/loggers/file/Cargo.toml +++ b/lib/loggers/file/Cargo.toml @@ -13,7 +13,7 @@ license.workspace = true chrono = "0.4.41" serde = { version = "1.0.204", features = ["derive"] } serde_json = "1.0.120" -tokio = { version = "1.40.0", default-features = false, features = ["fs", "io-util"] } +tokio = { version = "1.44.2", default-features = false, features = ["fs", "io-util"] } tracing = "0.1.40" enum-debug.workspace = true diff --git a/lib/reasoners/eflint-haskell/Cargo.toml b/lib/reasoners/eflint-haskell/Cargo.toml index 85f5083..5e40116 100644 --- a/lib/reasoners/eflint-haskell/Cargo.toml +++ b/lib/reasoners/eflint-haskell/Cargo.toml @@ -2,7 +2,7 @@ name = "eflint-haskell-reasoner" description = "Wraps the infamous Haskell interpreter to supply an eFLINT DSL backend." edition = "2021" -rust-version = "1.75.0" +rust-version = "1.77.2" authors = [ "Tim Müller" ] version = "0.1.0" repository.workspace = true diff --git a/lib/reasoners/eflint-json/Cargo.toml b/lib/reasoners/eflint-json/Cargo.toml index 012776a..346c54c 100644 --- a/lib/reasoners/eflint-json/Cargo.toml +++ b/lib/reasoners/eflint-json/Cargo.toml @@ -2,7 +2,7 @@ name = "eflint-json-reasoner" description = "Implements eFLINT as a backend reasoner for the policy reasoner." edition = "2021" -rust-version = "1.75.0" +rust-version = "1.81.0" authors = [ "Bas Kloosterman", "Tim Müller" ] version = "0.2.0" repository.workspace = true diff --git a/lib/reasoners/posix/Cargo.toml b/lib/reasoners/posix/Cargo.toml index 8bf4b5e..544a0d1 100644 --- a/lib/reasoners/posix/Cargo.toml +++ b/lib/reasoners/posix/Cargo.toml @@ -132,7 +132,7 @@ existing implementation of the eFlint reasoner made this project possible. We ho the addition of new reasoner types. """ edition = "2021" -rust-version = "1.80.0" +rust-version = "1.80.1" authors = [ "Daniel Voogsgerd", "Tim Müller" ] version = "0.2.0" repository.workspace = true @@ -143,7 +143,7 @@ license.workspace = true serde = { version = "1.0.204", features = ["derive"] } serde_json = "1.0.120" thiserror = "1.0.61" -tokio = { version = "1.40.0", default-features = false, features = ["fs", "io-util"] } +tokio = { version = "1.44.2", default-features = false, features = ["fs", "io-util"] } tracing = "0.1.40" error-trace.workspace = true diff --git a/lib/resolvers/file/Cargo.toml b/lib/resolvers/file/Cargo.toml index f0c4929..f3d1a11 100644 --- a/lib/resolvers/file/Cargo.toml +++ b/lib/resolvers/file/Cargo.toml @@ -12,7 +12,7 @@ license.workspace = true [dependencies] serde = { version = "1.0.204", features = ["derive"] } serde_json = "1.0.120" -tokio = { version = "1.40.0", default-features = false, features = ["fs"] } +tokio = { version = "1.44.2", default-features = false, features = ["fs"] } tracing = "0.1.40" spec = { path = "../../spec", package = "specifications" } From cb7ee4b83502c17cba5b3b35beff54575663655f Mon Sep 17 00:00:00 2001 From: Lut99 Date: Tue, 6 May 2025 13:31:41 +0200 Subject: [PATCH 53/82] Final(?) clippy fixes --- lib/workflow/src/visualize.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/workflow/src/visualize.rs b/lib/workflow/src/visualize.rs index 1c1b19d..8e56bed 100644 --- a/lib/workflow/src/visualize.rs +++ b/lib/workflow/src/visualize.rs @@ -120,10 +120,10 @@ fn print_elem(f: &mut Formatter, elem: &Elem, prefix: &dyn Display) -> FResult { }, Elem::Next => { - writeln!(f, "{}next", prefix) + writeln!(f, "{prefix}next") }, Elem::Stop => { - writeln!(f, "{}stop", prefix) + writeln!(f, "{prefix}stop") }, } } From 95ba28d983ecf36163fa60cd5e88b3123da2deb6 Mon Sep 17 00:00:00 2001 From: Lut99 Date: Tue, 6 May 2025 14:12:05 +0200 Subject: [PATCH 54/82] Fixed repo to `BraneFramework` :) --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 5f4863a..9669af9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,7 +19,7 @@ members = [ ] [workspace.package] -repository = "https://github.com/epi-project/policy-reasoner" +repository = "https://github.com/BraneFramework/policy-reasoner" license = "Apache-2.0" From 20aed1f2950efc847f8917a6be8ac1f6aee77b5c Mon Sep 17 00:00:00 2001 From: Daniel Voogsgerd Date: Tue, 27 May 2025 18:27:44 +0200 Subject: [PATCH 55/82] chore(deps): Set thiserror version to 2.0.0 --- Cargo.lock | 38 ++++++------------------- lib/reasoners/eflint-haskell/Cargo.toml | 2 +- lib/reasoners/eflint-json/Cargo.toml | 2 +- lib/reasoners/no-op/Cargo.toml | 2 +- lib/reasoners/posix/Cargo.toml | 2 +- 5 files changed, 13 insertions(+), 33 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 22da984..aa08dff 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -314,7 +314,7 @@ dependencies = [ "serde", "sha2", "specifications", - "thiserror 2.0.12", + "thiserror", "tokio", "tracing", ] @@ -338,7 +338,7 @@ dependencies = [ "serde", "serde_json", "specifications", - "thiserror 1.0.69", + "thiserror", "tracing", ] @@ -1047,7 +1047,7 @@ dependencies = [ "error-trace", "serde", "specifications", - "thiserror 1.0.69", + "thiserror", "tracing", ] @@ -1209,7 +1209,7 @@ dependencies = [ "serde", "serde_json", "specifications", - "thiserror 1.0.69", + "thiserror", "tokio", "tracing", "workflow", @@ -1643,38 +1643,18 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.69" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" -dependencies = [ - "thiserror-impl 1.0.69", -] - -[[package]] -name = "thiserror" -version = "2.0.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708" -dependencies = [ - "thiserror-impl 2.0.12", -] - -[[package]] -name = "thiserror-impl" -version = "1.0.69" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" +checksum = "15291287e9bff1bc6f9ff3409ed9af665bec7a5fc8ac079ea96be07bca0e2668" dependencies = [ - "proc-macro2", - "quote", - "syn", + "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "2.0.12" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d" +checksum = "22efd00f33f93fa62848a7cab956c3d38c8d43095efda1decfc2b3a5dc0b8972" dependencies = [ "proc-macro2", "quote", diff --git a/lib/reasoners/eflint-haskell/Cargo.toml b/lib/reasoners/eflint-haskell/Cargo.toml index 5e40116..6136928 100644 --- a/lib/reasoners/eflint-haskell/Cargo.toml +++ b/lib/reasoners/eflint-haskell/Cargo.toml @@ -12,7 +12,7 @@ license.workspace = true [dependencies] serde = { version = "1.0.204", features = ["derive"] } sha2 = "0.10.6" -thiserror = "2.0.12" +thiserror = "2.0.0" tokio = { version = "1.44.2", default-features = false, features = ["fs", "io-util", "process"] } tracing = "0.1.40" diff --git a/lib/reasoners/eflint-json/Cargo.toml b/lib/reasoners/eflint-json/Cargo.toml index 346c54c..7715504 100644 --- a/lib/reasoners/eflint-json/Cargo.toml +++ b/lib/reasoners/eflint-json/Cargo.toml @@ -13,7 +13,7 @@ license.workspace = true reqwest = { version = "0.12.0", features = ["json"] } serde = { version = "1.0.204", features = ["derive"] } serde_json = "1.0.120" -thiserror = "1.0.61" +thiserror = "2.0.0" tracing = "0.1.40" eflint-json.workspace = true diff --git a/lib/reasoners/no-op/Cargo.toml b/lib/reasoners/no-op/Cargo.toml index b597d36..0daf97f 100644 --- a/lib/reasoners/no-op/Cargo.toml +++ b/lib/reasoners/no-op/Cargo.toml @@ -17,7 +17,7 @@ license.workspace = true [dependencies] serde = "1.0.204" -thiserror = "1.0.61" +thiserror = "2.0.0" tracing = "0.1.40" error-trace.workspace = true diff --git a/lib/reasoners/posix/Cargo.toml b/lib/reasoners/posix/Cargo.toml index 544a0d1..454b944 100644 --- a/lib/reasoners/posix/Cargo.toml +++ b/lib/reasoners/posix/Cargo.toml @@ -142,7 +142,7 @@ license.workspace = true [dependencies] serde = { version = "1.0.204", features = ["derive"] } serde_json = "1.0.120" -thiserror = "1.0.61" +thiserror = "2.0.0" tokio = { version = "1.44.2", default-features = false, features = ["fs", "io-util"] } tracing = "0.1.40" From 374b5f25922a8b1fc8372d627bcd1c8c9105344e Mon Sep 17 00:00:00 2001 From: Daniel Voogsgerd Date: Tue, 27 May 2025 18:29:35 +0200 Subject: [PATCH 56/82] chore(deps): Downgrade paste minimal version to 1.0.0 --- lib/spec/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/spec/Cargo.toml b/lib/spec/Cargo.toml index 761802e..5ca0979 100644 --- a/lib/spec/Cargo.toml +++ b/lib/spec/Cargo.toml @@ -10,7 +10,7 @@ license.workspace = true [dependencies] -paste = "1.0.15" +paste = "1.0.0" serde = { version = "1.0.204", features = ["derive"] } From 76af8d2c94b371232c99a763e38404a2d500e0ad Mon Sep 17 00:00:00 2001 From: Daniel Voogsgerd Date: Tue, 27 May 2025 18:30:38 +0200 Subject: [PATCH 57/82] chore(deps): Downgrade chrono minimal version to 0.4.35 --- Cargo.lock | 6 +++--- lib/loggers/file/Cargo.toml | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index aa08dff..f57aee9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -179,16 +179,16 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "chrono" -version = "0.4.41" +version = "0.4.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c469d952047f47f91b68d1cba3f10d63c11d73e4636f24f08daf0278abf01c4d" +checksum = "8eaf5903dcbc0a39312feb77df2ff4c76387d591b9fc7b04a238dcf8bb62639a" dependencies = [ "android-tzdata", "iana-time-zone", "js-sys", "num-traits", "wasm-bindgen", - "windows-link", + "windows-targets 0.52.6", ] [[package]] diff --git a/lib/loggers/file/Cargo.toml b/lib/loggers/file/Cargo.toml index 87179ea..c2c203e 100644 --- a/lib/loggers/file/Cargo.toml +++ b/lib/loggers/file/Cargo.toml @@ -10,7 +10,7 @@ license.workspace = true [dependencies] -chrono = "0.4.41" +chrono = "0.4.35" serde = { version = "1.0.204", features = ["derive"] } serde_json = "1.0.120" tokio = { version = "1.44.2", default-features = false, features = ["fs", "io-util"] } From 2ac9cf176a099a21ef00f3b6ccad01ab8c02ff48 Mon Sep 17 00:00:00 2001 From: Daniel Voogsgerd Date: Wed, 28 May 2025 21:03:19 +0200 Subject: [PATCH 58/82] chore(deps): Lower minimal versions This is probably not the lowest we can go, but it is already a lot lower and fairly compatible with other minimal versions in the Brane framework --- Cargo.toml | 12 ++++++------ lib/eflint-to-json/Cargo.toml | 15 +++++++-------- lib/loggers/file/Cargo.toml | 8 ++++---- lib/loggers/no-op/Cargo.toml | 4 ++-- lib/reasoners/eflint-haskell/Cargo.toml | 6 +++--- lib/reasoners/eflint-json/Cargo.toml | 6 +++--- lib/reasoners/no-op/Cargo.toml | 4 ++-- lib/reasoners/posix/Cargo.toml | 6 +++--- lib/resolvers/file/Cargo.toml | 6 +++--- lib/spec/Cargo.toml | 2 +- lib/workflow/Cargo.toml | 2 +- 11 files changed, 35 insertions(+), 36 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 9669af9..48faae6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -69,13 +69,13 @@ workflow = { path = "./lib/workflow", optional = true } [dev-dependencies] -clap = { version = "4.5.36", features = ["derive"] } -console = "0.15.11" +clap = { version = "4.0.0", features = ["derive"] } +console = "0.15.0" error-trace.workspace = true -serde_json = "1.0.140" -shlex = "1.3.0" -tracing = "0.1.41" -tracing-subscriber = { version = "0.3.19", features = ["env-filter"] } +serde_json = "1.0.29" +shlex = "1.0.0" +tracing = "0.1.37" +tracing-subscriber = { version = "0.3.0", features = ["env-filter"] } tokio = { version = "1.44.2", default-features = false, features = ["fs", "io-std", "macros", "rt"] } diff --git a/lib/eflint-to-json/Cargo.toml b/lib/eflint-to-json/Cargo.toml index 7f68738..62c956c 100644 --- a/lib/eflint-to-json/Cargo.toml +++ b/lib/eflint-to-json/Cargo.toml @@ -10,18 +10,17 @@ license.workspace = true [dependencies] -async-recursion = { version = "1.0", optional = true } -console = "0.15.5" +async-recursion = { version = "1.0.0", optional = true } +console = "0.15.0" futures-util = { version = "0.3.30", optional = true } -hex = "0.4.3" -hex-literal = "0.4" +hex = "0.4.0" +hex-literal = "0.4.0" indicatif = "0.17" -log = { version = "0.4.22", optional = true } +log = { version = "0.4.6", optional = true } reqwest = { version = "0.12.0", default-features = false, features = ["blocking", "stream"] } -sha2 = "0.10.6" +sha2 = "0.10.1" tokio = { version = "1.44.2", default-features = false, features = ["fs", "io-util", "process"], optional = true } -tracing = { version = "0.1.40", optional = true } - +tracing = { version = "0.1.37", optional = true } [features] default = [] diff --git a/lib/loggers/file/Cargo.toml b/lib/loggers/file/Cargo.toml index c2c203e..c60128f 100644 --- a/lib/loggers/file/Cargo.toml +++ b/lib/loggers/file/Cargo.toml @@ -10,11 +10,11 @@ license.workspace = true [dependencies] -chrono = "0.4.35" -serde = { version = "1.0.204", features = ["derive"] } -serde_json = "1.0.120" +chrono = "0.4.30" +serde = { version = "1.0.184", features = ["derive"] } +serde_json = "1.0.29" tokio = { version = "1.44.2", default-features = false, features = ["fs", "io-util"] } -tracing = "0.1.40" +tracing = "0.1.37" enum-debug.workspace = true diff --git a/lib/loggers/no-op/Cargo.toml b/lib/loggers/no-op/Cargo.toml index 09352e4..fc30934 100644 --- a/lib/loggers/no-op/Cargo.toml +++ b/lib/loggers/no-op/Cargo.toml @@ -10,8 +10,8 @@ license.workspace = true [dependencies] -serde = "1.0.204" -tracing = "0.1.40" +serde = "1.0.184" +tracing = "0.1.37" spec = { path = "../../spec", package = "specifications" } diff --git a/lib/reasoners/eflint-haskell/Cargo.toml b/lib/reasoners/eflint-haskell/Cargo.toml index 6136928..aa0dd6b 100644 --- a/lib/reasoners/eflint-haskell/Cargo.toml +++ b/lib/reasoners/eflint-haskell/Cargo.toml @@ -10,11 +10,11 @@ license.workspace = true [dependencies] -serde = { version = "1.0.204", features = ["derive"] } -sha2 = "0.10.6" +serde = { version = "1.0.184", features = ["derive"] } +sha2 = "0.10.1" thiserror = "2.0.0" tokio = { version = "1.44.2", default-features = false, features = ["fs", "io-util", "process"] } -tracing = "0.1.40" +tracing = "0.1.37" error-trace.workspace = true diff --git a/lib/reasoners/eflint-json/Cargo.toml b/lib/reasoners/eflint-json/Cargo.toml index 7715504..e02665e 100644 --- a/lib/reasoners/eflint-json/Cargo.toml +++ b/lib/reasoners/eflint-json/Cargo.toml @@ -11,10 +11,10 @@ license.workspace = true [dependencies] reqwest = { version = "0.12.0", features = ["json"] } -serde = { version = "1.0.204", features = ["derive"] } -serde_json = "1.0.120" +serde = { version = "1.0.184", features = ["derive"] } +serde_json = "1.0.29" thiserror = "2.0.0" -tracing = "0.1.40" +tracing = "0.1.37" eflint-json.workspace = true error-trace.workspace = true diff --git a/lib/reasoners/no-op/Cargo.toml b/lib/reasoners/no-op/Cargo.toml index 0daf97f..6c1646a 100644 --- a/lib/reasoners/no-op/Cargo.toml +++ b/lib/reasoners/no-op/Cargo.toml @@ -16,9 +16,9 @@ license.workspace = true [dependencies] -serde = "1.0.204" +serde = "1.0.184" thiserror = "2.0.0" -tracing = "0.1.40" +tracing = "0.1.37" error-trace.workspace = true diff --git a/lib/reasoners/posix/Cargo.toml b/lib/reasoners/posix/Cargo.toml index 454b944..87c1551 100644 --- a/lib/reasoners/posix/Cargo.toml +++ b/lib/reasoners/posix/Cargo.toml @@ -140,11 +140,11 @@ license.workspace = true [dependencies] -serde = { version = "1.0.204", features = ["derive"] } -serde_json = "1.0.120" +serde = { version = "1.0.184", features = ["derive"] } +serde_json = "1.0.29" thiserror = "2.0.0" tokio = { version = "1.44.2", default-features = false, features = ["fs", "io-util"] } -tracing = "0.1.40" +tracing = "0.1.37" error-trace.workspace = true diff --git a/lib/resolvers/file/Cargo.toml b/lib/resolvers/file/Cargo.toml index f3d1a11..9ae08c2 100644 --- a/lib/resolvers/file/Cargo.toml +++ b/lib/resolvers/file/Cargo.toml @@ -10,10 +10,10 @@ license.workspace = true [dependencies] -serde = { version = "1.0.204", features = ["derive"] } -serde_json = "1.0.120" +serde = { version = "1.0.184", features = ["derive"] } +serde_json = "1.0.29" tokio = { version = "1.44.2", default-features = false, features = ["fs"] } -tracing = "0.1.40" +tracing = "0.1.37" spec = { path = "../../spec", package = "specifications" } diff --git a/lib/spec/Cargo.toml b/lib/spec/Cargo.toml index 5ca0979..8fedc71 100644 --- a/lib/spec/Cargo.toml +++ b/lib/spec/Cargo.toml @@ -11,7 +11,7 @@ license.workspace = true [dependencies] paste = "1.0.0" -serde = { version = "1.0.204", features = ["derive"] } +serde = { version = "1.0.184", features = ["derive"] } [features] diff --git a/lib/workflow/Cargo.toml b/lib/workflow/Cargo.toml index 82c047f..b0d352a 100644 --- a/lib/workflow/Cargo.toml +++ b/lib/workflow/Cargo.toml @@ -10,7 +10,7 @@ license.workspace = true [dependencies] -serde = { version = "1.0.204", features = ["derive"], optional = true } +serde = { version = "1.0.184", features = ["derive"], optional = true } enum-debug.workspace = true From a02d83656d904ec4cee3562fb35578cddfb0a413 Mon Sep 17 00:00:00 2001 From: Daniel Voogsgerd Date: Sat, 7 Jun 2025 09:50:53 +0200 Subject: [PATCH 59/82] chore(deps): Use error trace from crates.io --- Cargo.lock | 5 +++-- Cargo.toml | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index f57aee9..aa8d2fb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -409,8 +409,9 @@ dependencies = [ [[package]] name = "error-trace" -version = "3.1.0" -source = "git+https://github.com/Lut99/error-trace-rs?tag=v3.1.0#c3046059215f2bf60175f0274ec91c241c6dd069" +version = "3.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f003273bc107da5ef350a569bef0171f58da33da8aad8793bb1d79d81a5f10f2" [[package]] name = "fastrand" diff --git a/Cargo.toml b/Cargo.toml index 48faae6..63d6fd9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -108,7 +108,7 @@ serde = ["workflow/serde"] [workspace.dependencies] # The infamous lut99 crate set enum-debug = { git = "https://github.com/Lut99/enum-debug", tag = "v1.1.0", features = ["derive"] } -error-trace = { git = "https://github.com/Lut99/error-trace-rs", tag = "v3.1.0" } +error-trace = "3.1.0" # Eflint # NOTE: For now, we must skip the redirect. See From b3749a3b4accb43ef17cd0ade4bdebbbeb102b8e Mon Sep 17 00:00:00 2001 From: Lut99 Date: Fri, 20 Jun 2025 12:20:47 +0200 Subject: [PATCH 60/82] Updated README.md & ARCHITECTURE.md to reflect the refactor. Also added some OG POSIX reasoner authors to the `Cargo.toml` file. --- ARCHITECTURE.md | 71 +++------- README.md | 234 ++++++++++++--------------------- lib/reasoners/posix/Cargo.toml | 14 +- 3 files changed, 115 insertions(+), 204 deletions(-) diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md index 8f5336f..47a07f3 100644 --- a/ARCHITECTURE.md +++ b/ARCHITECTURE.md @@ -1,53 +1,24 @@ Policy reasoner architecture ============================ -The policy reasoner repository consists of two main parts. A library and a set of -implementations. - -### Library - -The entry point for the library is `src/lib.rs` as per usual. -In these files no particular implementation of a policy reasoner is included, only -the mechanisms on which the concept of a policy reasoner can be implemented. - -### Interface - -The interface for the different reasoners must be the same. Its command line -arguments are defined in `src/bin/implementation/interface.rs`. It could be that a new -reasoner requires more arguments. You can implement another arguments struct, however -make sure the arguments defined in the common interface are supported as other -components of infrastructure may depend on it. - -### Implementations - -As of now there are three different implementations of a policy reasoners in -this repository. The entrypoint for a reasoner can be found in `src/bin/{...}.rs`. -The subsequent libraries that support the implementation, can be placed in the -`implementation` module in `src/bin/implementation/{...}`. - -#### No-Op reasoner - -The most minimal reasoner is called the no_op reasoner, it will always returns the -same verdict. This is not a good policy reasoner, but it can be a minimal -example for futher implementations of new policy reasoners. - -#### Posix reasoner - -There exists a reasoner that uses posix file permissions for access control. As -of now there exists a yaml mapping between file system uids and users with -regards to the policy reasoner, but the idea is to inject multiple ways of -mapping these users to file system uid / gids by means of LDAP/AD for example. -Right now there is no support for linux ACL (facl), but this could be added in -the future. - -#### eFLINT reasoner - -Lastly, there is a reasoner that implements the eFLINT language as a method of -expressing the control policies. This language can be very expressive with -regards to possible policies, but can also be more complicated to set up. - -#### Extra reasonsers - -We are looking for new and different ways of expressing policies. If you have an -idea on an implementation for a policy reasoner, feel free to open an issue and -we can discuss how to go about implementing it. +This project is organised as a collection of crates that implement various aspects of various instantiations of a `policy-reasoner`. They are separate crates instead of feature-gated modules in order to tame the wildy varying dependencies a little. + +The crates doing the actual implementations can be found in the [`lib/`](lib/)-folder. Then, the [`src/`](src/)-folder provides a collection library that dynamically includes the crates based on features. Finally, [`examples/`](examples/) contains various example instantiations of the reasoner for different backends. They aren't (yet) exhaustive in terms of _all_ possible instantiations, but they do serve as an introduction to how to use and install each backend. + +## Libraries +The following libraries are provided by the `policy-reasoner` workspace, grouped by function: +- [_reasoners_](lib/reasoners/) implement the `ReasonerConnector`-trait, which abstracts over specific backends. + - [`eflint-haskell`](lib/reasoners/eflint-haskell) contributes a wrapper around the [eFLINT Haskell implementation](https://gitlab.com/eflint/haskell-implementation). It accepts eFLINT's DSL. + - [`eflint-json`](lib/reasoners/eflint-json) contributes a wrapper around Olaf's [eFLINT GO-implementation](https://github.com/Olaf-Erkemeij/eflint-server/) (or, more precisely, [our fork](https://github.com/BraneFramework/eflint-server-go)). It accepts [eFLINT's JSON specification](https://gitlab.com/eflint/json-specification). + - [`no-op`](lib/reasoners/no-op) contributes a dummy reasoner that blindly accepts anything. It is mostly used for debugging purposes. + - [`posix`](lib/reasoners/posix) contributes a wrapper around Unix's filesystem to use its permissions for deciding dataset access. It is based on the work done by [Daniel Voogsgerd, ](). +- [_resolvers_](lib/resolvers/) implement the `StateResolver`-trait, which is responsible for providing current information about the runtime system to a static policy. + - [`file`](lib/resolvers/file) contributes a resolver that reads the current system state from a file. This one is mostly for debugging, though; usually, systems implement their own resolvers to discover the current state. +- [_loggers_](lib/loggers/) implement the `AuditLogger`-trait, which is triggered with auditable information of specific events in the reasoner and can store it in implementation-specific ways. + - [`file`](lib/loggers/file) contributes an implementation that writes the events to a file. + - [`no-op`](lib/loggers/no-op) contributes a dummy implementation that doesn't write any audit event. This is used for debugging, or in scenarios where no audit trail is desired. + +Then there are also a few miscellaneous, auxillary libraries: +- [`eflint-to-json`](lib/eflint-to-json) contributes a wrapper around Olaf's [eFLINT to eFLINT JSON compiler](https://github.com/Olaf-Erkemeij/eflint-server/) (or more precisely, [our fork](https://github.com/BraneFramework/eflint-server-go)). This allows frontends to also use the eFLINT DSL with the [`eflint-json`](lib/reasoners/eflint-json) backend. +- [`spec`](lib/spec) contributes the core traits of the `policy-reasoner` mentioned above. +- [`workflow`](lib/workflow) contributes a specification of an abstract workflow that can be used to inform the policy to the task at hand. diff --git a/README.md b/README.md index 836398a..0a6d6ba 100644 --- a/README.md +++ b/README.md @@ -10,187 +10,119 @@ Implements the famous policy reasoner, known as `checker` in Brane terminology. This project is complemented by the [Policy Reasoner GUI](https://github.com/braneframework/policy-reasoner-gui). +## Project structure +The project structure is described in [`ARCHITECTURE.md`](ARCHITECTURE.md). + + ## Installation -This section describes how to install the policy reasoner in isolation. It is automatically built as the `brane-chk` service in [Brane](https://github.com/braneframework/brane). +This section describes how to prepare your system to run the policy reasoner in isolation. If you want to use it in the context of Brane, note that it installs it by itself as part of the [`brane-chk`](https://github.com/BraneFramework/brane/tree/main/brane-chk)-service. + +The various backends of the reasoner are executable as standalone binaries in the `examples/`-directory. They simply take a policy as input, run it, and show you the verdict that would be reported to services like Brane. +### eFLINT Haskell backend +The eFLINT Haskell backend relies on your system to have an `eflint-repl`-binary available to call to evaluate policy. -### Compiling `eflint-server` (and `eflint-json`) -By default, this repository uses a connector that runs the eFLINT JSON Specification. That means that in addition to the policy reasoner itself, the _actual_ eFLINT reasoner also has to be built. +As such, install the Haskell implementation of eFLINT first. See the [README.md](https://gitlab.com/eflint/haskell-implementation) at the repository for up-to-date instructions. If you don't or cannot install Haskell to compile the binary, you can also use the Docker image; then, when running the example, be sure to specify a different command for running the `eflint-repl` binary (see the `--eflint-cmd`-option). -This project relies on the [eFLINT GO server](https://github.com/Olaf-Erkemeij/eflint-server) (or rather, the fork [here](https://github.com/epi-project/eflint-server-go)). +### eFLINT JSON backend +The eFLINT JSON backend relies on you running the `eflint-server`-binary from the -repository. The latest release will have precompiled binaries for Linux x86-64 and Arm 64-bit; or, alternatively, you can compile the binary yourself by running the steps below. -To install, clone the repository and use the [Go compiler](https://go.dev/doc/install) to build it: -```bash -git clone https://github.com/epi-project/eflint-server-go && cd eflint-server-go +First, install the [Go compiler](https://go.dev/doc/install). Then, run the following commands to clone the appropriate repository and compile the server binary: +```sh +git clone https://github.com/BraneFramework/eflint-server-go && cd eflint-server-go cd cmd/eflint-server go build . ``` -This will produce `cmd/eflint-server/eflint-server`, which is the binary that must be run later. - -If you are on Linux x86/64, you can also simply download the precompiled binary for the `eflint-server` [here](https://github.com/epi-project/eflint-server-go/raw/main/eflint-server). - +This will produce `cmd/eflint-server/eflint-server`, which is the binary that you should execute in the background of the policy reasoner binary. See how to do that below. -### Setting up the database -The database setup is done automatically by `build.rs` if it isn't found already. +### no-op backend +The no-op backend does not require additional system dependencies. Simply compile it and you're ready to go. -If you somehow have to setup a manual database, you should first install the `diesel-cli` that can apply the migrations though the command-line: -```bash -cargo install diesel_cli --no-default-features --feature sqlite -``` - -Next, run the following commands to create the `data/policy.db`-file: -```bash -mkdir data && touch data/policy.db -diesel migration run --database-url data/policy.db -``` +### POSIX backend +The POSIX backend does not require additional system dependencies. Simply compile it and you're ready to go. ## Running -Running is quite straightforwardly done using Cargo's `run`-subcommand. However, there are some details, so read below: +This section describes how to run the example binaries in the [`examples/`](examples/)-folder in this project. -### Generating keys & tokens -The policy reasoner features two endpoints, the _deliberation endpoint_, and the _policy expert endpoint_ or _management endpoint_ (see [below](#usage)). Both of these are protected by the [JSON Web Key Sets](https://auth0.com/docs/secure/tokens/json-web-tokens/json-web-key-sets) [`examples/config/jwk_set_delib.json`](examples/config/jwk_set_delib.json) and [`examples/config/jwk_set_expert.json`](examples/config/jwk_set_expert.json), respectively. - -It is recommended to change the default provided keys, as these are for testing purposes only. You can do so using the `key-manager` tool: -```rust -cargo run --package key-manager -- generate jwk ./examples/config/jwk_set_delib.json -cargo run --package key-manager -- generate jwk ./examples/config/jwk_set_expert.json -``` +We will once again enumerate the steps for every backend binary. -Then, you can generate tokens that can be used to login using: -```bash -cargo run --package key-manager -- generate jwt -k ./examples/config/jwk_set_delib.json ./jwt_delib.json -cargo run --package key-manager -- generate jwt -k ./examples/config/jwk_set_expert.json ./jwt_expert.json +### eFLINT Haskell backend +To run the eFLINT Haskell backend, run: +```sh +cargo run --example eflint-haskell --features eflint-haskell-reasoner,file-logger -- ``` -where `` should be the name of the user using the JWT, `` the name of the system they are accessing through (if it matters; otherwise, just use the same value as ``) and `` is the time the JWT is valid for. For example: -```bash -cargo run --package key-manager -- generate jwt -k ./examples/config/jwk_set_delib.json ./jwt_delib.json amy site-a-worker 31d -cargo run --package key-manager -- generate jwt -k ./examples/config/jwk_set_expert.json ./jwt_expert.json amy site-a-worker 31d +Optionally, if your downloaded `eflint-repl` binary is executable by another command than simply that, give it with the `--eflint-cmd`-option. For example, when running the Docker version: +```sh +cargo run --example eflint-haskell --features eflint-haskell-reasoner,file-logger -- --eflint-cmd 'docker run -it --rm eflint repl' ``` +The reasoner will evaluate the policy file specified, and analyse the trace of results produced by the binary to give a verdict. It will be shown to you on the terminal. -### Backend reasoner -If you're running a backend reasoner like eFLINT's, run that first. Simply run: -```bash -./cmd/eflint-server/eflint-server +### eFLINT JSON backend +To run the eFLINT JSON backend, first run the `eflint-server` binary in a separate terminal window: +```sh +./eflint-server ``` -from the root of the closed `eflint-server-go`-repository (see [above](#compiling-eflint-server-and-eflint-json)). Note that you have to leave this process running for as long as the policy reasoner itself runs. - - -### The Policy Reasoner -To run the `policy-reasoner`, use the `cargo run`-command to build and execute it automatically. - -Note that doing so will: -- Automatically compile the eFLINT DSL interface to eFLINT JSON; and -- Generate the `data/policy.db` file if it doesn't already exist. - -If the default settings work for you, run: -```bash -cargo run --release -``` -to launch the reasoner. - -By default, the former downloads the precpompiled Linux x86/64 binary from the [fork](https://github.com/epi-project/eflint-server-go). If you're in another system, compile the `eflint-to-json` binary yourself (same procedure as for the `eflint-server`, but replace the latter term with the former in the process) and then give it as an environment variable: -```bash -EFLINT_TO_JSON_PATH="/cmd/eflint-to-json/eflint-to-json" cargo run --release +It will then report to you the address where the server may be reached. Note it down, and then run the example in another terminal window: +```sh +cargo run --example eflint-json --features eflint-json-reasoner,eflint-to-json,file-logger -- --address
``` +where `
` is the address you copied from the `eflint-server` output. +The eFLINT file can either be given in eFLINT DSL syntax, or the JSON syntax. You can use the `--dsl` and `--json` flags to tell the binary which of the two you are giving. Omitting it will default to `--dsl`. -## Usage -> The [Policy Reasoner GUI](https://github.com/braneframework/policy-reasoner-gui) provides an alternative interface to the Policy Reasoner. You can consult that repository for more information on using it. +If you are specifying the DSL syntax, the binary will attempt to automatically download the `eflint-to-json` binary for your system from the internet. It it fails, or you have another reason for preferring otherwise, use the `--eflint-path`-option to refer to an existing binary instead. -The Policy Reasoner implements two endpoints: -- The _deliberation endpoint_ is used by systems using the reasoners to consult policy; and -- The _policy expert endpoint_ or _management endpoint_ is used by policy experts to manage policy. +The reasoner will evaluate the policy file specified, and analyse the trace of results produced by the binary to give a verdict. It will be shown to you on the terminal. -Both require different a JWT to access, which you can generate by following the [section above](#generating-keys--tokens). These should be given as [Bearer-tokens](https://swagger.io/docs/specification/authentication/bearer-authentication/) in the Authorization header of every request, e.g., -```http -Authorization: Bearer +### no-op backend +The no-op backend is the simplest to use, as it does not do anything. Simply hit: +```sh +cargo run --example no-op --features no-op-reasoner,file-logger ``` - -With the keys set, you can access the following endpoints: -- Deliberation API - - `POST v1/deliberation/execute-workflow`: Ask if the reasoner would be OK with participating in the given workflow. - - As a body, a JSON object should be given with: - - `use_case`: A string that defines the use-case for which this request is done for. Currently only relevant when using the `BraneApiStateResolver` to choose which central registry to ask for state. - - `workflow`: A nested JSON Object that represents Brane's [WIR](https://wiki.enablingpersonalizedinterventions.nl/specification/spec/wir/introduction.html) (i.e., the input workflow). - - The response is a JSON object with: - - `verdict`: The verdict of the checker, which is either a JSON string `"allow"` or a JSON string `"deny"`. - - `verdict_reference`: A JSON String with a UUID that can be traced back in the logs to explain the verdict. - - `signature`: A JSON string that carries the checker's signature (unimplemented, currently dummy implementation). - - `POST v1/deliberation/execute-task`: Ask if the reasoner would be OK with executing a particular task in the given workflow. - - As a body, a JSON object should be given with: - - `use_case`: A string that defines the use-case for which this request is done for. Currently only relevant when using the `BraneApiStateResolver` to choose which central registry to ask for state. - - `workflow`: A nested JSON Object that represents Brane's [WIR](https://wiki.enablingpersonalizedinterventions.nl/specification/spec/wir/introduction.html) (i.e., the input workflow). - - `task_id`: The identifier of the task that is asked about. Given as an array of two elements, with either `
` or the function ID of a specific function as first element, and the edge index within that function as second element (see the [WIR](https://wiki.enablingpersonalizedinterventions.nl/specification/spec/wir/introduction.html) for more information). - - The response is a JSON object with: - - `verdict`: The verdict of the checker, which is either a JSON string `"allow"` or a JSON string `"deny"`. - - `verdict_reference`: A JSON String with a UUID that can be traced back in the logs to explain the verdict. - - `signature`: A JSON string that carries the checker's signature (unimplemented, currently dummy implementation). - - `POST v1/deliberation/access-data`: Ask if the reasoner would be OK with transferring a particular dataset to be used as input to the given task in the given workflow OR as result of the given workflow. - - As a body, a JSON object should be given with: - - `use_case`: A string that defines the use-case for which this request is done for. Currently only relevant when using the `BraneApiStateResolver` to choose which central registry to ask for state. - - `workflow`: A nested JSON Object that represents Brane's [WIR](https://wiki.enablingpersonalizedinterventions.nl/specification/spec/wir/introduction.html) (i.e., the input workflow). - - `task_id`: An _optional_ identifier of the task that is asked about. Given as an array of two elements, with either `
` or the function ID of a specific function as first element, and the edge index within that function as second element (see the [WIR](https://wiki.enablingpersonalizedinterventions.nl/specification/spec/wir/introduction.html) for more information). - If this identifier is omitted, it means that instead this workflow returns a result to the user submitting it and we're asking if that transfer would be OK. - - `data_id`: The ID of the dataset/intermediate result that we're asking about. - - The response is a JSON object with: - - `verdict`: The verdict of the checker, which is either a JSON string `"allow"` or a JSON string `"deny"`. - - `verdict_reference`: A JSON String with a UUID that can be traced back in the logs to explain the verdict. - - `signature`: A JSON string that carries the checker's signature (unimplemented, currently dummy implementation). -- Management API - - `GET v1/management/policies`: Retrieve the list of all policy versions on the reasoner. - - No body is required for this request. - - The call returns a JSON Array of policy versions stored on the reasoner, each of which is a JSON Object with: - - `creator`: An _optional_ JSON String with the name of the user that submitted the workflow. - - `created_at`: The time the policy was uploaded. - - `version`: The ID of this version, as a formatted time string. - - `version_description`: The description for this specific version. - - `reasoner_connector_context`: The hash of the context for which this policy is valid. - - `POST v1/management/policies`: Push a new policy version to the reasoner. - - The body of this request should be a JSON Object with: - - `description`: An _optional_ JSON String that provides a generic description for policy in this reasoner. You can expect this one to be duplicate across versions. - - `version_description`: A JSON String that provides a short description or commit message for this version of the policy. - - `content`: A JSON Array with nested JSON Objects with: - - `reasoner`: The string identifier that determines the reasoning backend for which this policy is meant (for the eFLINT backend, this is `eflint-json`). - - `reasoner_version`: A JSON String that denotes the version of the backend reasoner for which this policy is meant (for the eFLINT backend, this is `0.1.0`). - - `content`: The content of the policy. This is arbitrary other JSON, and will be passed as-is to the backend connector that translates it to the reasoner implemented. - - The request returns a JSON Object with the same fields to confirm the policy has been uploaded. - - `GET v1/management/policies/:id`: Retrieve the contents of a particular policy version with identifier `:id`. - - No body is required for this request. - - A JSON Object is returned that contains the requested policy. The fields are indentical as returned by `POST v1/management/policies`. - - `GET v1/management/policies/active`: Get the ID of the currently active policy. - - No body is required for this request. - - A JSON Object is returned that contains the requested policy. The fields are indentical as returned by `POST v1/management/policies`. - - `PUT v1/management/policies/active`: Update the currently active policy. - - The body of this request should be a JSON Object with: - - `version`: A JSON integer that is the ID of the policy to set active. - - A JSON Object is returned that contains the policy to which the reasoner has switched. The fields are indentical as returned by `POST v1/management/policies`. - - `DELETE v1/management/policies/active`: De-active the currently active policy, reverting to "deny all" policy. - - No body is required for this request. - - No result is returned by this request. - -For example, using [curl](https://curl.se/): -```bash -# Run this first to set the keys! -export JWT_DELIB="$(cat ./jwt_delib.json)" -export JWT_EXPERT="$(cat ./jwt_expert.json)" +and let it do the rest. It will tell you the verdict of the reasoner at the end of the file (which is also OK). + +### POSIX backend +The POSIX backend does not accept a single policy file, but instead a workflow that represents the scenario and a config that represents the policy. + +The workflow file should be given as a JSON serialization of the `Workflow`-struct provided by the [`workflow`](lib/workflow/)-crate. The best documentation is the Rust struct itself; or, alternatively, refer to examples in the [`tests/workflow`](tests/workflow/)-folder. + +The config file is also given as JSON, but has a simpler syntax. For example (using illegal comments to show you what's what): +```json +{ + // The location identifier of the worker for which this policy is written. + "id": "amy", + // Maps datasets to policy information + "data": { + // The workflow identifier of the target dataset + "some_dataset": { + // The path where it resides. This may actually be some other file + // than the dataset itself if you want to separate the permissions. + "path": "/home/pi/some_dataset", + // Defines how _workflow users_ map to _POSIX users_ + "user_map": { + "bob": { + // This is the user ID of the user to which `bob` maps + "uid": 1000, + // This is a list of group IDs which the user is + // additionally a member of. + "gids": [1000, 1001, 1002] + } + } + } + + // More data mappings ... + } +} ``` -```bash -# Check a workflow! -curl -X POST -H "Authorization: Bearer $JWT_DELIB" -H "Content-Type: application/json" -d "@tests/deliberation/execute-workflow.json" localhost:3030/v1/deliberation/execute-workflow +Once you're created a workflow and a config file, you can run the reasoner on them with: +```sh +cargo run --example posix --features posix-reasoner,file-logger,serde,workflow -- --workflow --config ``` -```bash -# Push a new policy! -curl -X POST -H "Authorization: Bearer $JWT_EXPERT" -H "Content-Type: application/json" -d "@tests/management/add-tautology.json" localhost:3030/v1/management/policies -``` - -```bash -# Make a policy active! -curl -X PUT -H "Authorization: Bearer $JWT_EXPERT" -H "Content-Type: application/json" -d '{ "version": 1 }' localhost:3030/v1/management/policies/active -``` +If you omit the `--workflow` option, the binary will attempt to read it from stdin instead. ## Contribution diff --git a/lib/reasoners/posix/Cargo.toml b/lib/reasoners/posix/Cargo.toml index 87c1551..ccb75ef 100644 --- a/lib/reasoners/posix/Cargo.toml +++ b/lib/reasoners/posix/Cargo.toml @@ -133,7 +133,12 @@ the addition of new reasoner types. """ edition = "2021" rust-version = "1.80.1" -authors = [ "Daniel Voogsgerd", "Tim Müller" ] +authors = [ + "Daniel Voogsgerd", + "Haraldur Davíðsson", + "Simon Noorts", + "Tim Müller", +] version = "0.2.0" repository.workspace = true license.workspace = true @@ -143,13 +148,16 @@ license.workspace = true serde = { version = "1.0.184", features = ["derive"] } serde_json = "1.0.29" thiserror = "2.0.0" -tokio = { version = "1.44.2", default-features = false, features = ["fs", "io-util"] } +tokio = { version = "1.44.2", default-features = false, features = [ + "fs", + "io-util", +] } tracing = "0.1.37" error-trace.workspace = true spec = { path = "../../spec", package = "specifications" } -workflow = { path = "../../workflow", features = ["serde"]} +workflow = { path = "../../workflow", features = ["serde"] } [features] From dbacd00a8dd88fb3f015ff2d93595d2a7cd00971 Mon Sep 17 00:00:00 2001 From: Daniel Voogsgerd Date: Thu, 12 Jun 2025 11:55:44 +0200 Subject: [PATCH 61/82] refactor: Fix async functions These functions were made async by hand, but can just use the async keyword --- lib/loggers/file/src/logger.rs | 101 +++---- .../eflint-haskell/src/reasonerconn.rs | 249 +++++++++--------- lib/reasoners/eflint-json/src/reasonerconn.rs | 161 ++++++----- lib/reasoners/no-op/src/reasonerconn.rs | 41 ++- lib/reasoners/posix/src/reasonerconn.rs | 86 +++--- lib/resolvers/file/src/resolver.rs | 35 +-- lib/spec/src/stateresolver.rs | 16 +- 7 files changed, 323 insertions(+), 366 deletions(-) diff --git a/lib/loggers/file/src/logger.rs b/lib/loggers/file/src/logger.rs index 391b122..3eebf50 100644 --- a/lib/loggers/file/src/logger.rs +++ b/lib/loggers/file/src/logger.rs @@ -15,7 +15,6 @@ use std::borrow::Cow; use std::error; use std::fmt::{Debug, Display, Formatter, Result as FResult}; -use std::future::Future; use std::path::PathBuf; use enum_debug::EnumDebug as _; @@ -201,84 +200,68 @@ impl AuditLogger for FileLogger { type Error = Error; #[inline] - fn log_context<'a, C>(&'a self, context: &'a C) -> impl 'a + Send + Future> + async fn log_context<'a, C>(&'a self, context: &'a C) -> Result<(), Self::Error> where C: ?Sized + Sync + ReasonerContext, { - async move { - // Serialize the context first - let context: Value = match serde_json::to_value(context) { - Ok(ctx) => ctx, - Err(err) => return Err(Error::LogStatementSerialize { kind: "LogStatement::Context".into(), err }), - }; + // Serialize the context first + let context: Value = match serde_json::to_value(context) { + Ok(ctx) => ctx, + Err(err) => return Err(Error::LogStatementSerialize { kind: "LogStatement::Context".into(), err }), + }; - // Log it - self.log(LogStatement::Context { context }).await?; - #[cfg(debug_assertions)] - self.logged_context.store(true, std::sync::atomic::Ordering::Relaxed); - Ok(()) - } + // Log it + self.log(LogStatement::Context { context }).await?; + #[cfg(debug_assertions)] + self.logged_context.store(true, std::sync::atomic::Ordering::Relaxed); + Ok(()) } #[inline] - fn log_response<'a, R>( - &'a self, - reference: &'a str, - response: &'a ReasonerResponse, - raw: Option<&'a str>, - ) -> impl 'a + Send + Future> + async fn log_response<'a, R>(&'a self, reference: &'a str, response: &'a ReasonerResponse, raw: Option<&'a str>) -> Result<(), Self::Error> where R: Sync + Display, { - async move { - #[cfg(debug_assertions)] - if !self.logged_context.load(std::sync::atomic::Ordering::Relaxed) { - tracing::warn!("Logging reasoner response without having logged the reasoner context; please call FileLogger::log_context() first."); - } + #[cfg(debug_assertions)] + if !self.logged_context.load(std::sync::atomic::Ordering::Relaxed) { + tracing::warn!("Logging reasoner response without having logged the reasoner context; please call FileLogger::log_context() first."); + } - // Serialize the response first - let response: Value = match serde_json::to_value(&match response { - ReasonerResponse::Success => ReasonerResponse::Success, - ReasonerResponse::Violated(reasons) => ReasonerResponse::Violated(reasons.to_string()), - }) { - Ok(res) => res, - Err(err) => return Err(Error::LogStatementSerialize { kind: "LogStatement::ReasonerResponse".into(), err }), - }; + // Serialize the response first + let response: Value = match serde_json::to_value(&match response { + ReasonerResponse::Success => ReasonerResponse::Success, + ReasonerResponse::Violated(reasons) => ReasonerResponse::Violated(reasons.to_string()), + }) { + Ok(res) => res, + Err(err) => return Err(Error::LogStatementSerialize { kind: "LogStatement::ReasonerResponse".into(), err }), + }; - // Log it - self.log(LogStatement::ReasonerResponse { reference: Cow::Borrowed(reference), response, raw: raw.map(Cow::Borrowed) }).await - } + // Log it + self.log(LogStatement::ReasonerResponse { reference: Cow::Borrowed(reference), response, raw: raw.map(Cow::Borrowed) }).await } #[inline] - fn log_question<'a, S, Q>( - &'a self, - reference: &'a str, - state: &'a S, - question: &'a Q, - ) -> impl 'a + Send + Future> + async fn log_question<'a, S, Q>(&'a self, reference: &'a str, state: &'a S, question: &'a Q) -> Result<(), Self::Error> where S: Sync + Serialize, Q: Sync + Serialize, { - async move { - #[cfg(debug_assertions)] - if !self.logged_context.load(std::sync::atomic::Ordering::Relaxed) { - tracing::warn!("Logging reasoner response without having logged the reasoner context; please call FileLogger::log_context() first."); - } + #[cfg(debug_assertions)] + if !self.logged_context.load(std::sync::atomic::Ordering::Relaxed) { + tracing::warn!("Logging reasoner response without having logged the reasoner context; please call FileLogger::log_context() first."); + } - // Serialize the state & question first - let state: Value = match serde_json::to_value(state) { - Ok(res) => res, - Err(err) => return Err(Error::LogStatementSerialize { kind: "LogStatement::ReasonerConsult".into(), err }), - }; - let question: Value = match serde_json::to_value(question) { - Ok(res) => res, - Err(err) => return Err(Error::LogStatementSerialize { kind: "LogStatement::ReasonerConsult".into(), err }), - }; + // Serialize the state & question first + let state: Value = match serde_json::to_value(state) { + Ok(res) => res, + Err(err) => return Err(Error::LogStatementSerialize { kind: "LogStatement::ReasonerConsult".into(), err }), + }; + let question: Value = match serde_json::to_value(question) { + Ok(res) => res, + Err(err) => return Err(Error::LogStatementSerialize { kind: "LogStatement::ReasonerConsult".into(), err }), + }; - // Log it - self.log(LogStatement::ReasonerConsult { reference: Cow::Borrowed(reference), state, question }).await - } + // Log it + self.log(LogStatement::ReasonerConsult { reference: Cow::Borrowed(reference), state, question }).await } } diff --git a/lib/reasoners/eflint-haskell/src/reasonerconn.rs b/lib/reasoners/eflint-haskell/src/reasonerconn.rs index 892b9c4..ddb2ebd 100644 --- a/lib/reasoners/eflint-haskell/src/reasonerconn.rs +++ b/lib/reasoners/eflint-haskell/src/reasonerconn.rs @@ -14,7 +14,6 @@ // use std::borrow::Cow; -use std::future::Future; use std::marker::PhantomData; use std::path::PathBuf; use std::process::{ExitStatus, Stdio}; @@ -247,141 +246,139 @@ where fn context(&self) -> Self::Context { self.context.public.clone() } #[inline] - fn consult<'a, L>( + async fn consult<'a, L>( &'a self, state: Self::State, question: Self::Question, logger: &'a SessionedAuditLogger, - ) -> impl 'a + Send + Future, Self::Error>> + ) -> Result, Self::Error> where L: Sync + AuditLogger, { - async move { - logger - .log_question(&state, &question) - .await - .map_err(|err| Error::LogQuestion { to: std::any::type_name::>(), err: err.freeze() })?; - - // Prepare the full file to send - let spec: String = format!("{}{}", state.eflint(), question.eflint()); - debug!("Full spec to submit to reasoner:{}\n{}\n{}\n", "-".repeat(80), spec, "-".repeat(80)); - - // Prepare the command to execute - let mut cmd = Command::new(&self.context.cmd.0); - cmd.args(&self.context.cmd.1); - cmd.arg(&self.context.base_policy); - cmd.stdin(Stdio::piped()); - cmd.stdout(Stdio::piped()); - cmd.stderr(Stdio::piped()); - - // Attempt to execute it, sending the full spec on the input - // NOTE: Using match to avoid moving `cmd` a closure and having to clone it (which it can't) - debug!("Calling reasoner with {cmd:?}..."); - let mut handle = match cmd.spawn() { - Ok(handle) => handle, - Err(err) => return Err(Error::CommandSpawn { cmd, err }), - }; - handle - .stdin - .as_mut() - .unwrap_or_else(|| panic!("No stdin on subprocess even though it's piped!")) - .write_all(spec.as_bytes()) - .await - .map_err(|err| Error::CommandStdinWrite { err })?; - debug!("Inputs submitted, waiting for reasoner to complete..."); - let output = match handle.wait_with_output().await { - Ok(handle) => handle, - Err(err) => return Err(Error::CommandJoin { cmd, err }), - }; - if !output.status.success() { - return Err(Error::CommandFailure { - cmd, - status: output.status, - stdout: String::from_utf8_lossy(&output.stdout).into(), - stderr: String::from_utf8_lossy(&output.stderr).into(), - }); - } + logger + .log_question(&state, &question) + .await + .map_err(|err| Error::LogQuestion { to: std::any::type_name::>(), err: err.freeze() })?; + + // Prepare the full file to send + let spec: String = format!("{}{}", state.eflint(), question.eflint()); + debug!("Full spec to submit to reasoner:{}\n{}\n{}\n", "-".repeat(80), spec, "-".repeat(80)); + + // Prepare the command to execute + let mut cmd = Command::new(&self.context.cmd.0); + cmd.args(&self.context.cmd.1); + cmd.arg(&self.context.base_policy); + cmd.stdin(Stdio::piped()); + cmd.stdout(Stdio::piped()); + cmd.stderr(Stdio::piped()); + + // Attempt to execute it, sending the full spec on the input + // NOTE: Using match to avoid moving `cmd` a closure and having to clone it (which it can't) + debug!("Calling reasoner with {cmd:?}..."); + let mut handle = match cmd.spawn() { + Ok(handle) => handle, + Err(err) => return Err(Error::CommandSpawn { cmd, err }), + }; + handle + .stdin + .as_mut() + .unwrap_or_else(|| panic!("No stdin on subprocess even though it's piped!")) + .write_all(spec.as_bytes()) + .await + .map_err(|err| Error::CommandStdinWrite { err })?; + debug!("Inputs submitted, waiting for reasoner to complete..."); + let output = match handle.wait_with_output().await { + Ok(handle) => handle, + Err(err) => return Err(Error::CommandJoin { cmd, err }), + }; + if !output.status.success() { + return Err(Error::CommandFailure { + cmd, + status: output.status, + stdout: String::from_utf8_lossy(&output.stdout).into(), + stderr: String::from_utf8_lossy(&output.stderr).into(), + }); + } - // Stript the prompts from the eFLINT output - let output: Cow = String::from_utf8_lossy(&output.stdout); - let mut clean_output: String = String::with_capacity(output.len()); - let mut buf: String = String::new(); - let mut state: usize = 0; - for c in output.chars() { - // Loop exists to be able to examine some chars again - loop { - match state { - // Finding pounds - 0 if c == '#' => { - buf.push('#'); - state = 1; - break; - }, - 0 => { - clean_output.push(c); - break; - }, - - // Parsing numbers & whitespace - 1 if c.is_ascii_digit() || c.is_whitespace() => { - buf.push(c); - break; - }, - 1 if c == '>' => { - state = 0; - break; - }, - 1 => { - clean_output.push_str(&buf); - buf.clear(); - state = 0; - // Don't break, re-try this character - }, - - _ => unreachable!(), - } - } - } + // Stript the prompts from the eFLINT output + let output: Cow = String::from_utf8_lossy(&output.stdout); + let mut clean_output: String = String::with_capacity(output.len()); + let mut buf: String = String::new(); + let mut state: usize = 0; + for c in output.chars() { + // Loop exists to be able to examine some chars again + loop { + match state { + // Finding pounds + 0 if c == '#' => { + buf.push('#'); + state = 1; + break; + }, + 0 => { + clean_output.push(c); + break; + }, - // Attempt to parse the output - debug!("Reasoner output:\n{}\n{}\n{}\n", "-".repeat(80), clean_output, "-".repeat(80)); - let trace: Trace = match Trace::from_str(clean_output.as_ref()) { - Ok(trace) => trace, - Err(err) => return Err(Error::IllegalReasonerResponse { output: clean_output, err }), - }; - debug!("Reasoner trace:\n{}\n{}\n{}\n", "-".repeat(80), trace, "-".repeat(80)); - - // Analyze the output to find violations - // The rule is: - // 1. Check the last delta - // a. If it's a query, then it must succeed; or - // b. If it's not a query, it must not be a violation. - // 2. If there is no last delta, then we default to **success**. - let problems: Vec = trace - .deltas - .iter() - .filter_map(|delta| match delta { - Delta::Query(query) if query.is_succes() => Some(Problem::QueryFailed), - Delta::Violation(viol) => Some(Problem::Violation(viol.clone())), - _ => None, - }) - .collect(); - let res: ReasonerResponse<_> = trace - .deltas - .into_iter() - .next_back() - .map(|delta| match delta { - Delta::Query(query) if query.is_succes() => ReasonerResponse::Success, - Delta::Query(_) => ReasonerResponse::Violated(self.handler.handle(problems)), - Delta::Violation(_) => ReasonerResponse::Violated(self.handler.handle(problems)), - delta => { - warn!("Got non-query, non-violation delta as last delta ({delta:?}); assuming OK"); - ReasonerResponse::Success + // Parsing numbers & whitespace + 1 if c.is_ascii_digit() || c.is_whitespace() => { + buf.push(c); + break; }, - }) - .unwrap_or(ReasonerResponse::Success); - debug!("Reasoner verdict: {res}"); - Ok(res) + 1 if c == '>' => { + state = 0; + break; + }, + 1 => { + clean_output.push_str(&buf); + buf.clear(); + state = 0; + // Don't break, re-try this character + }, + + _ => unreachable!(), + } + } } + + // Attempt to parse the output + debug!("Reasoner output:\n{}\n{}\n{}\n", "-".repeat(80), clean_output, "-".repeat(80)); + let trace: Trace = match Trace::from_str(clean_output.as_ref()) { + Ok(trace) => trace, + Err(err) => return Err(Error::IllegalReasonerResponse { output: clean_output, err }), + }; + debug!("Reasoner trace:\n{}\n{}\n{}\n", "-".repeat(80), trace, "-".repeat(80)); + + // Analyze the output to find violations + // The rule is: + // 1. Check the last delta + // a. If it's a query, then it must succeed; or + // b. If it's not a query, it must not be a violation. + // 2. If there is no last delta, then we default to **success**. + let problems: Vec = trace + .deltas + .iter() + .filter_map(|delta| match delta { + Delta::Query(query) if query.is_succes() => Some(Problem::QueryFailed), + Delta::Violation(viol) => Some(Problem::Violation(viol.clone())), + _ => None, + }) + .collect(); + let res: ReasonerResponse<_> = trace + .deltas + .into_iter() + .next_back() + .map(|delta| match delta { + Delta::Query(query) if query.is_succes() => ReasonerResponse::Success, + Delta::Query(_) => ReasonerResponse::Violated(self.handler.handle(problems)), + Delta::Violation(_) => ReasonerResponse::Violated(self.handler.handle(problems)), + delta => { + warn!("Got non-query, non-violation delta as last delta ({delta:?}); assuming OK"); + ReasonerResponse::Success + }, + }) + .unwrap_or(ReasonerResponse::Success); + debug!("Reasoner verdict: {res}"); + Ok(res) } } diff --git a/lib/reasoners/eflint-json/src/reasonerconn.rs b/lib/reasoners/eflint-json/src/reasonerconn.rs index 0a410a0..df4dc04 100644 --- a/lib/reasoners/eflint-json/src/reasonerconn.rs +++ b/lib/reasoners/eflint-json/src/reasonerconn.rs @@ -15,7 +15,6 @@ use std::borrow::Cow; use std::collections::HashMap; use std::fmt::Display; -use std::future::Future; use std::marker::PhantomData; use eflint_json::spec::auxillary::Version; @@ -25,7 +24,7 @@ use serde::{Deserialize, Serialize}; use spec::auditlogger::{AuditLogger, SessionedAuditLogger}; use spec::reasonerconn::{ReasonerConnector, ReasonerContext, ReasonerResponse}; use thiserror::Error; -use tracing::{Instrument as _, Level, debug, span}; +use tracing::{debug, instrument}; use crate::reasons::ReasonHandler; use crate::spec::EFlintable; @@ -252,99 +251,97 @@ where fn context(&self) -> Self::Context { EFlintJsonReasonerContext::default() } - fn consult<'a, L>( + #[instrument(name = "EFlintJsonReasonerConnector::consult", skip_all, fields(reference = logger.reference()))] + async fn consult<'a, L>( &'a self, state: Self::State, question: Self::Question, logger: &'a SessionedAuditLogger, - ) -> impl 'a + Send + Future, Self::Error>> + ) -> Result, Self::Error> where L: Sync + AuditLogger, { - async move { - logger - .log_question(&state, &question) - .await - .map_err(|err| Error::LogQuestion { to: std::any::type_name::>(), err: err.freeze() })?; + logger + .log_question(&state, &question) + .await + .map_err(|err| Error::LogQuestion { to: std::any::type_name::>(), err: err.freeze() })?; - // Build the full policy - debug!("Building full policy..."); - let mut phrases: Vec = Vec::new(); - phrases.extend(state.to_eflint().map_err(|err| Error::StateToEFlint { err })?); - phrases.extend(question.to_eflint().map_err(|err| Error::QuestionToEFlint { err })?); - debug!("Full request length: {} phrase(s)", phrases.len()); + // Build the full policy + debug!("Building full policy..."); + let mut phrases: Vec = Vec::new(); + phrases.extend(state.to_eflint().map_err(|err| Error::StateToEFlint { err })?); + phrases.extend(question.to_eflint().map_err(|err| Error::QuestionToEFlint { err })?); + debug!("Full request length: {} phrase(s)", phrases.len()); - // Build the request - let request: Request = Request::Phrases(RequestPhrases { - common: RequestCommon { version: Version::v0_1_0(), extensions: HashMap::new() }, - phrases, - updates: true, - }); - debug!("Full request:\n\n{}\n\n", serde_json::to_string_pretty(&request).unwrap_or_else(|_| "".into())); + // Build the request + let request: Request = Request::Phrases(RequestPhrases { + common: RequestCommon { version: Version::v0_1_0(), extensions: HashMap::new() }, + phrases, + updates: true, + }); + debug!("Full request:\n\n{}\n\n", serde_json::to_string_pretty(&request).unwrap_or_else(|_| "".into())); - // Send it on its way - debug!("Sending eFLINT phrases request to '{}'", self.addr); - let client = reqwest::Client::new(); - let res = client.post(&self.addr).json(&request).send().await.map_err(|err| Error::ReasonerRequest { addr: self.addr.clone(), err })?; + // Send it on its way + debug!("Sending eFLINT phrases request to '{}'", self.addr); + let client = reqwest::Client::new(); + let res = client.post(&self.addr).json(&request).send().await.map_err(|err| Error::ReasonerRequest { addr: self.addr.clone(), err })?; - debug!("Awaiting response..."); - let raw_body = res.text().await.map_err(|err| Error::ReasonerResponse { addr: self.addr.clone(), err })?; + debug!("Awaiting response..."); + let raw_body = res.text().await.map_err(|err| Error::ReasonerResponse { addr: self.addr.clone(), err })?; - debug!("Parsing response..."); - // NOTE: No 'map_err' to avoid moving 'raw_body' out on the happy path - let response: ResponsePhrases = match serde_json::from_str(&raw_body) { - Ok(res) => res, - Err(err) => return Err(Error::ResponseParse { addr: self.addr.clone(), raw: raw_body, err }), - }; + debug!("Parsing response..."); + // NOTE: No 'map_err' to avoid moving 'raw_body' out on the happy path + let response: ResponsePhrases = match serde_json::from_str(&raw_body) { + Ok(res) => res, + Err(err) => return Err(Error::ResponseParse { addr: self.addr.clone(), raw: raw_body, err }), + }; - debug!("Analysing response..."); - // TODO proper handle invalid query and unexpected result - let verdict: ReasonerResponse = response - .results - .last() - .map(|r| match r { - PhraseResult::BooleanQuery(r) => { - if r.result { - Ok(ReasonerResponse::Success) - } else { - Ok(ReasonerResponse::Violated(self.reason_handler.extract_reasons(&response).map_err(|err| { - Error::ResponseExtractReasons { - addr: self.addr.clone(), - raw: serde_json::to_string_pretty(&response).unwrap_or_else(|_| "".into()), - err, - } - })?)) - } - }, - PhraseResult::InstanceQuery(_) => Err(Error::ResponseIllegalQuery { - addr: self.addr.clone(), - raw: serde_json::to_string_pretty(&response).unwrap_or_else(|_| "".into()), - }), - PhraseResult::StateChange(r) => { - if !r.violated { - Ok(ReasonerResponse::Success) - } else { - Ok(ReasonerResponse::Violated(self.reason_handler.extract_reasons(&response).map_err(|err| { - Error::ResponseExtractReasons { - addr: self.addr.clone(), - raw: serde_json::to_string_pretty(&response).unwrap_or_else(|_| "".into()), - err, - } - })?)) - } - }, - }) - .transpose()? - .unwrap_or(ReasonerResponse::Success); + debug!("Analysing response..."); + // TODO proper handle invalid query and unexpected result + let verdict: ReasonerResponse = response + .results + .last() + .map(|r| match r { + PhraseResult::BooleanQuery(r) => { + if r.result { + Ok(ReasonerResponse::Success) + } else { + Ok(ReasonerResponse::Violated(self.reason_handler.extract_reasons(&response).map_err(|err| { + Error::ResponseExtractReasons { + addr: self.addr.clone(), + raw: serde_json::to_string_pretty(&response).unwrap_or_else(|_| "".into()), + err, + } + })?)) + } + }, + PhraseResult::InstanceQuery(_) => Err(Error::ResponseIllegalQuery { + addr: self.addr.clone(), + raw: serde_json::to_string_pretty(&response).unwrap_or_else(|_| "".into()), + }), + PhraseResult::StateChange(r) => { + if !r.violated { + Ok(ReasonerResponse::Success) + } else { + Ok(ReasonerResponse::Violated(self.reason_handler.extract_reasons(&response).map_err(|err| { + Error::ResponseExtractReasons { + addr: self.addr.clone(), + raw: serde_json::to_string_pretty(&response).unwrap_or_else(|_| "".into()), + err, + } + })?)) + } + }, + }) + .transpose()? + .unwrap_or(ReasonerResponse::Success); - // OK, report and return - logger - .log_response(&verdict, Some(&raw_body)) - .await - .map_err(|err| Error::LogResponse { to: std::any::type_name::>(), err: err.freeze() })?; - debug!("Final reasoner verdict: {verdict:?}"); - Ok(verdict) - } - .instrument(span!(Level::INFO, "EFlintJsonReasonerConnector::consult", reference = logger.reference())) + // OK, report and return + logger + .log_response(&verdict, Some(&raw_body)) + .await + .map_err(|err| Error::LogResponse { to: std::any::type_name::>(), err: err.freeze() })?; + debug!("Final reasoner verdict: {verdict:?}"); + Ok(verdict) } } diff --git a/lib/reasoners/no-op/src/reasonerconn.rs b/lib/reasoners/no-op/src/reasonerconn.rs index 56aeb12..03b3d0a 100644 --- a/lib/reasoners/no-op/src/reasonerconn.rs +++ b/lib/reasoners/no-op/src/reasonerconn.rs @@ -13,7 +13,6 @@ // use std::borrow::Cow; -use std::future::Future; use std::marker::PhantomData; use error_trace::{ErrorTrace as _, Trace}; @@ -22,7 +21,7 @@ use spec::auditlogger::SessionedAuditLogger; use spec::reasonerconn::{ReasonerContext, ReasonerResponse}; use spec::{AuditLogger, ReasonerConnector}; use thiserror::Error; -use tracing::{Instrument as _, Level, debug, span}; +use tracing::{debug, instrument}; /***** ERRORS *****/ @@ -125,32 +124,30 @@ where #[inline] fn context(&self) -> Self::Context { NoOpReasonerContext::default() } - fn consult<'a, L>( + #[instrument(name = "NoOpReasonerContext", skip_all, fields(reference=logger.reference()))] + async fn consult<'a, L>( &'a self, state: Self::State, question: Self::Question, logger: &'a SessionedAuditLogger, - ) -> impl 'a + Send + Future, Self::Error>> + ) -> Result, Self::Error> where L: Sync + AuditLogger, { - async move { - debug!("NoOpReasonerConnector: request received"); - - // Log that the question has been asked - logger - .log_question(&state, &question) - .await - .map_err(|err| Error::LogQuestion { to: std::any::type_name::>(), err: err.freeze() })?; - - // Log the reasoner has been called - logger - .log_response::(&ReasonerResponse::Success, None) - .await - .map_err(|err| Error::LogResponse { to: std::any::type_name::>(), err: err.freeze() })?; - - Ok(ReasonerResponse::Success) - } - .instrument(span!(Level::INFO, "NoOpReasonerConnector::consult", reference = logger.reference())) + debug!("NoOpReasonerConnector: request received"); + + // Log that the question has been asked + logger + .log_question(&state, &question) + .await + .map_err(|err| Error::LogQuestion { to: std::any::type_name::>(), err: err.freeze() })?; + + // Log the reasoner has been called + logger + .log_response::(&ReasonerResponse::Success, None) + .await + .map_err(|err| Error::LogResponse { to: std::any::type_name::>(), err: err.freeze() })?; + + Ok(ReasonerResponse::Success) } } diff --git a/lib/reasoners/posix/src/reasonerconn.rs b/lib/reasoners/posix/src/reasonerconn.rs index 81d9d18..ebc1868 100644 --- a/lib/reasoners/posix/src/reasonerconn.rs +++ b/lib/reasoners/posix/src/reasonerconn.rs @@ -15,7 +15,6 @@ use std::borrow::Cow; /***** LIBRARY *****/ -use std::future::Future; use std::iter::repeat; use std::ops::BitOr; use std::os::unix::fs::{MetadataExt as _, PermissionsExt as _}; @@ -28,7 +27,7 @@ use spec::reasonerconn::{ReasonerConnector, ReasonerContext, ReasonerResponse}; use spec::reasons::NoReason; use thiserror::Error; use tokio::fs; -use tracing::{Instrument as _, Level, debug, info, span}; +use tracing::{debug, info}; use workflow::Workflow; use crate::config::{Config, DataPolicy, PosixLocalIdentity}; @@ -278,57 +277,54 @@ impl ReasonerConnector for PosixReasonerConnector { fn context(&self) -> Self::Context { PosixReasonerContext::default() } #[inline] - fn consult<'a, L>( + async fn consult<'a, L>( &'a self, state: Self::State, _question: Self::Question, logger: &'a SessionedAuditLogger, - ) -> impl 'a + Send + Future, Self::Error>> + ) -> Result, Self::Error> where L: Sync + AuditLogger, { - async move { - // Log the input - logger - .log_question(&state, &()) - .await - .map_err(|err| Error::LogQuestion { to: std::any::type_name::>(), err: err.freeze() })?; - - // The datasets used in the workflow. E.g., `st_antonius_ect`. - let datasets: WorkflowDatasets = WorkflowDatasets::new(&state.config.id, &state.workflow); - debug!("Found datasets in workflow {:?}: {:#?}", state.workflow.id, datasets); - - // Loop to find the permissions on the disk - for ((location, dataset), permission) in std::iter::empty() - .chain(datasets.read_sets.iter().zip(repeat(PosixFilePermission::Read.to_set()))) - .chain(datasets.write_sets.iter().zip(repeat(PosixFilePermission::Write.to_set()))) - .chain(datasets.execute_sets.iter().zip(repeat(PosixFilePermission::Read | PosixFilePermission::Execute))) - { - info!("Testing dataset {:?} for permission to {:?} for user {:?}", dataset.id, permission, location); - - // Find the location of the dataset in the list - let policy: &DataPolicy = match state.config.data.get(&dataset.id) { - Some(data) => data, - None => return Err(Error::UnknownDataset { data: dataset.id.clone() }), - }; - - // Now check the policy! - if !satisfies_posix_permissions(&policy.path, policy.user_map.get(&location.id), permission).await? { - logger - .log_response(&ReasonerResponse::Violated(NoReason), Some("false")) - .await - .map_err(|err| Error::LogResponse { to: std::any::type_name::>(), err: err.freeze() })?; - return Ok(ReasonerResponse::Violated(NoReason)); - } + // Log the input + logger + .log_question(&state, &()) + .await + .map_err(|err| Error::LogQuestion { to: std::any::type_name::>(), err: err.freeze() })?; + + // The datasets used in the workflow. E.g., `st_antonius_ect`. + let datasets: WorkflowDatasets = WorkflowDatasets::new(&state.config.id, &state.workflow); + debug!("Found datasets in workflow {:?}: {:#?}", state.workflow.id, datasets); + + // Loop to find the permissions on the disk + for ((location, dataset), permission) in std::iter::empty() + .chain(datasets.read_sets.iter().zip(repeat(PosixFilePermission::Read.to_set()))) + .chain(datasets.write_sets.iter().zip(repeat(PosixFilePermission::Write.to_set()))) + .chain(datasets.execute_sets.iter().zip(repeat(PosixFilePermission::Read | PosixFilePermission::Execute))) + { + info!("Testing dataset {:?} for permission to {:?} for user {:?}", dataset.id, permission, location); + + // Find the location of the dataset in the list + let policy: &DataPolicy = match state.config.data.get(&dataset.id) { + Some(data) => data, + None => return Err(Error::UnknownDataset { data: dataset.id.clone() }), + }; + + // Now check the policy! + if !satisfies_posix_permissions(&policy.path, policy.user_map.get(&location.id), permission).await? { + logger + .log_response(&ReasonerResponse::Violated(NoReason), Some("false")) + .await + .map_err(|err| Error::LogResponse { to: std::any::type_name::>(), err: err.freeze() })?; + return Ok(ReasonerResponse::Violated(NoReason)); } - - // If none of them failed prematurely, then we're done - logger - .log_response(&ReasonerResponse::::Success, Some("true")) - .await - .map_err(|err| Error::LogResponse { to: std::any::type_name::>(), err: err.freeze() })?; - Ok(ReasonerResponse::Success) } - .instrument(span!(Level::INFO, "ReasonerConnector::consult", reference = logger.reference())) + + // If none of them failed prematurely, then we're done + logger + .log_response(&ReasonerResponse::::Success, Some("true")) + .await + .map_err(|err| Error::LogResponse { to: std::any::type_name::>(), err: err.freeze() })?; + Ok(ReasonerResponse::Success) } } diff --git a/lib/resolvers/file/src/resolver.rs b/lib/resolvers/file/src/resolver.rs index 1a0e6e8..ac322ec 100644 --- a/lib/resolvers/file/src/resolver.rs +++ b/lib/resolvers/file/src/resolver.rs @@ -14,7 +14,6 @@ use std::error; use std::fmt::{Display, Formatter, Result as FResult}; -use std::future::Future; use std::marker::PhantomData; use std::path::PathBuf; @@ -23,7 +22,7 @@ use spec::AuditLogger; use spec::auditlogger::SessionedAuditLogger; use spec::stateresolver::StateResolver; use tokio::fs; -use tracing::{Instrument as _, Level, debug, span}; +use tracing::{debug, instrument}; /***** ERRORS *****/ @@ -86,29 +85,23 @@ impl Deserialize<'de>> StateResolver for FileResolver { type Resolved = R; type State = (); - fn resolve<'a, L>( - &'a self, - _state: Self::State, - logger: &'a SessionedAuditLogger, - ) -> impl 'a + Send + Future> + #[instrument(name = "FileResolver::resolve", skip_all, fields(reference=logger.reference()))] + async fn resolve<'a, L>(&'a self, _state: Self::State, logger: &'a SessionedAuditLogger) -> Result where L: Sync + AuditLogger, { - async move { - // Read the file in one go// Read the file in one go - debug!("Opening input file '{}'...", self.path.display()); - let state: String = match fs::read_to_string(&self.path).await { - Ok(state) => state, - Err(err) => return Err(Error::FileRead { path: self.path.clone(), err }), - }; + // Read the file in one go// Read the file in one go + debug!("Opening input file '{}'...", self.path.display()); + let state: String = match fs::read_to_string(&self.path).await { + Ok(state) => state, + Err(err) => return Err(Error::FileRead { path: self.path.clone(), err }), + }; - // Parse it as JSON - debug!("Parsing input file '{}'...", self.path.display()); - match serde_json::from_str(&state) { - Ok(state) => Ok(state), - Err(err) => Err(Error::FileDeserialize { to: std::any::type_name::(), path: self.path.clone(), err }), - } + // Parse it as JSON + debug!("Parsing input file '{}'...", self.path.display()); + match serde_json::from_str(&state) { + Ok(state) => Ok(state), + Err(err) => Err(Error::FileDeserialize { to: std::any::type_name::(), path: self.path.clone(), err }), } - .instrument(span!(Level::INFO, "FileResolver::resolve", reference = logger.reference())) } } diff --git a/lib/spec/src/stateresolver.rs b/lib/spec/src/stateresolver.rs index b481b79..1d78fee 100644 --- a/lib/spec/src/stateresolver.rs +++ b/lib/spec/src/stateresolver.rs @@ -63,15 +63,13 @@ macro_rules! tuple_impl { type Resolved = []::Resolved; type Error = E; - fn resolve<'a, L>(&'a self, state: Self::State, logger: &'a SessionedAuditLogger) -> impl 'a + Send + Future> + async fn resolve<'a, L>(&'a self, state: Self::State, logger: &'a SessionedAuditLogger) -> Result where L: Sync + AuditLogger, { - async move { - let resolved: []::Resolved = self.$fi.resolve(state, logger).await?; - $(let resolved: []::Resolved = self.$i.resolve(resolved, logger).await?;)* - Ok(resolved) - } + let resolved: []::Resolved = self.$fi.resolve(state, logger).await?; + $(let resolved: []::Resolved = self.$i.resolve(resolved, logger).await?;)* + Ok(resolved) } } } @@ -104,11 +102,7 @@ pub trait StateResolver { /// /// # Errors /// This function may error if it failed to do its resolution. - fn resolve<'a, L>( - &'a self, - state: Self::State, - logger: &'a SessionedAuditLogger, - ) -> impl 'a + Send + Future> + fn resolve<'a, L>(&'a self, state: Self::State, logger: &'a SessionedAuditLogger) -> impl Future> where L: Sync + AuditLogger; } From 4ef5884b9c9b6d8a718c850f982a8b5f6b548a02 Mon Sep 17 00:00:00 2001 From: Daniel Voogsgerd Date: Sun, 15 Jun 2025 16:26:32 +0200 Subject: [PATCH 62/82] refactor: Fix clippy errors --- examples/eflint_haskell.rs | 6 +++--- examples/eflint_json.rs | 29 +++++++++++++---------------- examples/no_op.rs | 2 +- examples/posix.rs | 2 +- 4 files changed, 18 insertions(+), 21 deletions(-) diff --git a/examples/eflint_haskell.rs b/examples/eflint_haskell.rs index c5494eb..e2e32f5 100644 --- a/examples/eflint_haskell.rs +++ b/examples/eflint_haskell.rs @@ -71,7 +71,7 @@ async fn main() { info!("{} - v{}", env!("CARGO_BIN_NAME"), env!("CARGO_PKG_VERSION")); // Create the logger - let mut logger: SessionedAuditLogger = + let logger: SessionedAuditLogger = SessionedAuditLogger::new("test", FileLogger::new(format!("{} - v{}", env!("CARGO_BIN_NAME"), env!("CARGO_PKG_VERSION")), "./test.log")); // Ensure there is a file to input @@ -98,7 +98,7 @@ async fn main() { shlex::split(&args.eflint_cmd).into_iter().flatten(), &policy, SilentHandler, - &mut logger, + &logger, ) .await { @@ -108,7 +108,7 @@ async fn main() { std::process::exit(1); }, }; - let verdict: ReasonerResponse = match conn.consult("".into(), (), &mut logger).await { + let verdict: ReasonerResponse = match conn.consult("".into(), (), &logger).await { Ok(res) => res, Err(err) => { error!("{}", trace!(("Failed to send message to reasoner {:?}", args.eflint_cmd), err)); diff --git a/examples/eflint_json.rs b/examples/eflint_json.rs index 5cdc263..15d6495 100644 --- a/examples/eflint_json.rs +++ b/examples/eflint_json.rs @@ -93,7 +93,7 @@ async fn main() { info!("{} - v{}", env!("CARGO_BIN_NAME"), env!("CARGO_PKG_VERSION")); // Create the logger - let mut logger: SessionedAuditLogger = + let logger: SessionedAuditLogger = SessionedAuditLogger::new("test", FileLogger::new(format!("{} - v{}", env!("CARGO_BIN_NAME"), env!("CARGO_PKG_VERSION")), "./test.log")); // Decide which eflint to run @@ -120,7 +120,7 @@ async fn main() { // Compile first let mut json: Vec = Vec::new(); - if let Err(err) = eflint_to_json::compile_async(&file, &mut json, args.eflint_path.as_ref().map(PathBuf::as_path)).await { + if let Err(err) = eflint_to_json::compile_async(&file, &mut json, args.eflint_path.as_deref()).await { error!("{}", trace!(("Failed to compile input file '{}' to JSON", args.file), err)); std::process::exit(1); } @@ -182,20 +182,17 @@ async fn main() { }; // Create the reasoner - let conn = match EFlintJsonReasonerConnector::, ()>::new_async( - &args.address, - EFlintSilentReasonHandler, - &mut logger, - ) - .await - { - Ok(conn) => conn, - Err(err) => { - error!("{}", trace!(("Failed to create eFLINT reasoner"), err)); - std::process::exit(1); - }, - }; - let verdict: ReasonerResponse = match conn.consult(policy.phrases, (), &mut logger).await { + let conn = + match EFlintJsonReasonerConnector::, ()>::new_async(&args.address, EFlintSilentReasonHandler, &logger) + .await + { + Ok(conn) => conn, + Err(err) => { + error!("{}", trace!(("Failed to create eFLINT reasoner"), err)); + std::process::exit(1); + }, + }; + let verdict: ReasonerResponse = match conn.consult(policy.phrases, (), &logger).await { Ok(res) => res, Err(err) => { error!("{}", trace!(("Failed to send message to reasoner at {:?}", args.address), err)); diff --git a/examples/no_op.rs b/examples/no_op.rs index 23ad317..ca37ff1 100644 --- a/examples/no_op.rs +++ b/examples/no_op.rs @@ -70,7 +70,7 @@ async fn main() { std::process::exit(1); }, }; - let verdict: ReasonerResponse<()> = conn.consult((), (), &mut logger).await.unwrap(); + let verdict: ReasonerResponse<()> = conn.consult((), (), &logger).await.unwrap(); // OK, report match verdict { diff --git a/examples/posix.rs b/examples/posix.rs index 4086d39..7f0a4cf 100644 --- a/examples/posix.rs +++ b/examples/posix.rs @@ -191,7 +191,7 @@ async fn main() { std::process::exit(1); }, }; - let verdict: ReasonerResponse = match conn.consult(State { workflow, config }, (), &mut logger).await { + let verdict: ReasonerResponse = match conn.consult(State { workflow, config }, (), &logger).await { Ok(res) => res, Err(err) => { error!("{}", trace!(("Failed to consult the POSIX reasoner"), err)); From 4aa012db2da61eb8b56db5f78f5757ec19a9bb96 Mon Sep 17 00:00:00 2001 From: Daniel Voogsgerd Date: Thu, 12 Jun 2025 11:56:41 +0200 Subject: [PATCH 63/82] refactor: Make the code more idiomatic --- lib/eflint-to-json/src/lib.rs | 8 ++++---- lib/spec/src/reasonerconn.rs | 4 +--- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/lib/eflint-to-json/src/lib.rs b/lib/eflint-to-json/src/lib.rs index 6e1000b..ad51ff0 100644 --- a/lib/eflint-to-json/src/lib.rs +++ b/lib/eflint-to-json/src/lib.rs @@ -222,7 +222,7 @@ fn potentially_include(imported: &mut HashSet, path: &Path, line: &str) let line: &str = line.trim(); // Check it's a line - if line.len() < 8 || (&line[..8] != "#include" && &line[..8] != "#require") || line.chars().last().map(|c| c != '.').unwrap_or(true) { + if !line.starts_with("#include") && !line.starts_with("#require") || line.chars().last().map(|c| c != '.').unwrap_or(true) { return Ok(None); } @@ -248,7 +248,7 @@ fn potentially_include(imported: &mut HashSet, path: &Path, line: &str) }; // Check if we've seen this before if it's require - if &line[..8] == "#require" && imported.contains(&incl_path) { + if line.starts_with("#require") && imported.contains(&incl_path) { return Ok(Some(None)); } imported.insert(incl_path.clone()); @@ -281,7 +281,7 @@ async fn potentially_include_async(imported: &mut HashSet, path: &Path, let line: &str = line.trim(); // Check it's a line - if line.len() < 8 || (&line[..8] != "#include" && &line[..8] != "#require") || line.chars().last().map(|c| c != '.').unwrap_or(true) { + if !line.starts_with("#include") && !line.starts_with("#require") || line.chars().last().map(|c| c != '.').unwrap_or(true) { return Ok(None); } @@ -307,7 +307,7 @@ async fn potentially_include_async(imported: &mut HashSet, path: &Path, }; // Check if we've seen this before if it's require - if &line[..8] == "#require" && imported.contains(&incl_path) { + if line.starts_with("#require") && imported.contains(&incl_path) { return Ok(Some(None)); } imported.insert(incl_path.clone()); diff --git a/lib/spec/src/reasonerconn.rs b/lib/spec/src/reasonerconn.rs index 824d25c..4ecee49 100644 --- a/lib/spec/src/reasonerconn.rs +++ b/lib/spec/src/reasonerconn.rs @@ -70,9 +70,7 @@ impl Display for ReasonerResponse { match self { Self::Success => write!(f, "SUCCESS"), Self::Violated(r) => { - write!(f, "VIOLATION(")?; - r.fmt(f)?; - write!(f, ")") + write!(f, "VIOLATION({r})") }, } } From b9c82f2873f527ea257c9a012201ab66187d7e2d Mon Sep 17 00:00:00 2001 From: Daniel Voogsgerd Date: Sat, 14 Jun 2025 14:43:31 +0200 Subject: [PATCH 64/82] chore: Remove commented code --- lib/eflint-to-json/src/download.rs | 28 ---------------------------- 1 file changed, 28 deletions(-) diff --git a/lib/eflint-to-json/src/download.rs b/lib/eflint-to-json/src/download.rs index e3f5bc3..58706c4 100644 --- a/lib/eflint-to-json/src/download.rs +++ b/lib/eflint-to-json/src/download.rs @@ -227,20 +227,6 @@ pub fn download_file(source: impl AsRef, target: impl AsRef, security // Open the target file for writing let mut handle: fs::File = match fs::File::create(target) { - // Ok(handle) => { - // // Prepare the permissions to set by reading the file's metadata - // let mut permissions: Permissions = match handle.metadata() { - // Ok(metadata) => metadata.permissions(), - // Err(err) => { return Err(Error::FileMetadataError{ what: "temporary binary", path: local.into(), err }); }, - // }; - // permissions.set_mode(permissions.mode() | 0o100); - - // // Set them - // if let Err(err) = handle.set_permissions(permissions) { return Err(Error::FilePermissionsError{ what: "temporary binary", path: local.into(), err }); } - - // // Return the handle - // handle - // }, Ok(handle) => handle, Err(err) => { return Err(Error::FileCreate { path: target.into(), err }); @@ -402,20 +388,6 @@ pub async fn download_file_async( // Open the target file for writing let mut handle: tfs::File = match tfs::File::create(target).await { - // Ok(handle) => { - // // Prepare the permissions to set by reading the file's metadata - // let mut permissions: Permissions = match handle.metadata() { - // Ok(metadata) => metadata.permissions(), - // Err(err) => { return Err(Error::FileMetadataError{ what: "temporary binary", path: local.into(), err }); }, - // }; - // permissions.set_mode(permissions.mode() | 0o100); - - // // Set them - // if let Err(err) = handle.set_permissions(permissions) { return Err(Error::FilePermissionsError{ what: "temporary binary", path: local.into(), err }); } - - // // Return the handle - // handle - // }, Ok(handle) => handle, Err(err) => { return Err(Error::FileCreate { path: target.into(), err }); From a3b77b3aaf355ca70752a990224a7a1388c0b399 Mon Sep 17 00:00:00 2001 From: Daniel Voogsgerd Date: Thu, 12 Jun 2025 12:23:12 +0200 Subject: [PATCH 65/82] refactor: Clean up error code --- Cargo.lock | 3 + lib/eflint-to-json/Cargo.toml | 1 + lib/eflint-to-json/src/download.rs | 215 +++++---------- lib/eflint-to-json/src/lib.rs | 258 ++++++------------ lib/loggers/file/Cargo.toml | 1 + lib/loggers/file/src/logger.rs | 104 +++---- .../eflint-haskell/src/reasonerconn.rs | 55 ++-- lib/reasoners/eflint-json/src/reasonerconn.rs | 110 +++----- lib/reasoners/eflint-json/src/spec.rs | 7 +- lib/reasoners/no-op/src/reasonerconn.rs | 24 +- lib/reasoners/posix/src/reasonerconn.rs | 34 +-- lib/resolvers/file/Cargo.toml | 1 + lib/resolvers/file/src/resolver.rs | 46 +--- 13 files changed, 274 insertions(+), 585 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index aa8d2fb..8877450 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -355,6 +355,7 @@ dependencies = [ "log", "reqwest", "sha2", + "thiserror", "tokio", "tracing", ] @@ -428,6 +429,7 @@ dependencies = [ "serde", "serde_json", "specifications", + "thiserror", "tokio", "tracing", ] @@ -439,6 +441,7 @@ dependencies = [ "serde", "serde_json", "specifications", + "thiserror", "tokio", "tracing", ] diff --git a/lib/eflint-to-json/Cargo.toml b/lib/eflint-to-json/Cargo.toml index 62c956c..192602f 100644 --- a/lib/eflint-to-json/Cargo.toml +++ b/lib/eflint-to-json/Cargo.toml @@ -19,6 +19,7 @@ indicatif = "0.17" log = { version = "0.4.6", optional = true } reqwest = { version = "0.12.0", default-features = false, features = ["blocking", "stream"] } sha2 = "0.10.1" +thiserror = "2.0.0" tokio = { version = "1.44.2", default-features = false, features = ["fs", "io-util", "process"], optional = true } tracing = { version = "0.1.37", optional = true } diff --git a/lib/eflint-to-json/src/download.rs b/lib/eflint-to-json/src/download.rs index 58706c4..be5d567 100644 --- a/lib/eflint-to-json/src/download.rs +++ b/lib/eflint-to-json/src/download.rs @@ -13,10 +13,10 @@ // use std::fmt::{Display, Formatter, Result as FResult}; +use std::fs; use std::io::Write as _; use std::path::{Path, PathBuf}; use std::str::FromStr as _; -use std::{error, fs}; use console::Style; #[cfg(feature = "async-tokio")] @@ -36,80 +36,41 @@ use crate::log::debug; /***** ERRORS *****/ /// Wraps the contents of an error body. -#[derive(Debug)] +#[derive(Debug, thiserror::Error)] +#[error("{0}")] pub struct ResponseBodyError(pub String); -impl Display for ResponseBodyError { - fn fmt(&self, f: &mut Formatter<'_>) -> FResult { write!(f, "{}", self.0) } -} -impl error::Error for ResponseBodyError {} /// Defines errors occurring with [`download_file()`]. -#[derive(Debug)] +#[derive(Debug, thiserror::Error)] pub enum Error { /// Failed to create a file. - FileCreate { path: PathBuf, err: std::io::Error }, + #[error("Failed to create output file '{}'", path.display())] + FileCreate { path: PathBuf, source: std::io::Error }, /// Failed to write to the output file. - FileWrite { path: PathBuf, err: std::io::Error }, + #[error("Failed to write to output file '{}'", path.display())] + FileWrite { path: PathBuf, source: std::io::Error }, /// The checksum of a file was not what we expected. + #[error("Checksum of downloaded file '{}' is incorrect: expected '{got}', got '{expected}'", path.display())] FileChecksum { path: PathBuf, got: String, expected: String }, /// Directory not found. + #[error("Directory '{}' not found", path.display())] DirNotFound { path: PathBuf }, /// The given address did not have HTTPS enabled. + #[error("Security policy requires HTTPS is enabled, but '{address}' does not enable it (or we cannot parse the URL)")] NotHttps { address: String }, /// Failed to send a request to the given address. - Request { address: String, err: reqwest::Error }, + #[error("Failed to send GET-request to '{address}'")] + Request { address: String, source: reqwest::Error }, /// The given server responded with a non-2xx status code. - RequestFailure { address: String, code: StatusCode, err: Option }, + #[error("GET-request to '{address}' failed with status code {} ({})", code.as_u16(), code.canonical_reason().unwrap_or("???"))] + RequestFailure { address: String, code: StatusCode, source: Option }, /// Failed to download the full file stream. - Download { address: String, err: reqwest::Error }, -} -impl Display for Error { - fn fmt(&self, f: &mut Formatter<'_>) -> FResult { - use Error::*; - match self { - FileCreate { path, .. } => write!(f, "Failed to create output file '{}'", path.display()), - FileWrite { path, .. } => write!(f, "Failed to write to output file '{}'", path.display()), - FileChecksum { path, got, expected } => { - write!(f, "Checksum of downloaded file '{}' is incorrect: expected '{}', got '{}'", path.display(), got, expected) - }, - - DirNotFound { path } => write!(f, "Directory '{}' not found", path.display()), - - NotHttps { address } => { - write!(f, "Security policy requires HTTPS is enabled, but '{address}' does not enable it (or we cannot parse the URL)") - }, - Request { address, .. } => write!(f, "Failed to send GET-request to '{address}'"), - RequestFailure { address, code, .. } => { - write!(f, "GET-request to '{}' failed with status code {} ({})", address, code.as_u16(), code.canonical_reason().unwrap_or("???")) - }, - Download { address, .. } => write!(f, "Failed to download file '{address}'"), - } - } -} -impl std::error::Error for Error { - fn source(&self) -> Option<&(dyn error::Error + 'static)> { - use Error::*; - match self { - FileCreate { err, .. } => Some(err), - FileWrite { err, .. } => Some(err), - FileChecksum { .. } => None, - - DirNotFound { .. } => None, - - NotHttps { .. } => None, - Request { err, .. } => Some(err), - RequestFailure { err, .. } => err.as_ref().map(|err| { - // Use a little bit of indirection to coerce into the trait object - let err: &dyn error::Error = err; - err - }), - Download { err, .. } => Some(err), - } - } + #[error("Failed to download file '{address}'")] + Download { address: String, source: reqwest::Error }, } @@ -209,12 +170,17 @@ impl Display for DownloadSecurity<'_> { /// /// # Errors /// This function may error if we failed to download the file or write it (which may happen if the parent directory of `local` does not exist, among other things). -pub fn download_file(source: impl AsRef, target: impl AsRef, security: DownloadSecurity<'_>, verbose: Option