From 0e850f9609c14f48e71b1621447546632a9e3f56 Mon Sep 17 00:00:00 2001 From: Peter LeVasseur Date: Thu, 11 Jan 2024 16:59:09 -0500 Subject: [PATCH] Initial implementation of up-streamer-rust --- .github/workflows/build.yaml | 109 + .gitignore | 17 + CONTRIBUTING.md | 35 + Cargo.lock | 2235 +++++++++++++++++ Cargo.toml | 60 + LICENSE.txt | 201 ++ NOTICE.md | 28 + README.md | 97 +- .../integration-test-utils/Cargo.toml | 26 + .../src/integration_test_listeners.rs | 75 + .../src/integration_test_messages.rs | 114 + .../src/integration_test_utils.rs | 335 +++ .../src/integration_test_uuris.rs | 53 + .../integration-test-utils/src/lib.rs | 36 + .../src/up_client_foo.rs | 413 +++ tools/coverage.sh | 21 + tools/fmt_clippy_doc.sh | 5 + tools/generate_test_coverage_report.sh | 17 + up-streamer/Cargo.toml | 39 + up-streamer/src/lib.rs | 23 + up-streamer/src/route.rs | 110 + up-streamer/src/ustreamer.rs | 986 ++++++++ .../tests/single_local_single_remote.rs | 234 ++ ...ingle_local_two_remote_add_remove_rules.rs | 445 ++++ ..._authorities_different_remote_transport.rs | 357 +++ ...emote_authorities_same_remote_transport.rs | 357 +++ 26 files changed, 6426 insertions(+), 2 deletions(-) create mode 100644 .github/workflows/build.yaml create mode 100644 .gitignore create mode 100644 CONTRIBUTING.md create mode 100644 Cargo.lock create mode 100644 Cargo.toml create mode 100644 LICENSE.txt create mode 100644 NOTICE.md create mode 100644 example-utils/integration-test-utils/Cargo.toml create mode 100644 example-utils/integration-test-utils/src/integration_test_listeners.rs create mode 100644 example-utils/integration-test-utils/src/integration_test_messages.rs create mode 100644 example-utils/integration-test-utils/src/integration_test_utils.rs create mode 100644 example-utils/integration-test-utils/src/integration_test_uuris.rs create mode 100644 example-utils/integration-test-utils/src/lib.rs create mode 100644 example-utils/integration-test-utils/src/up_client_foo.rs create mode 100755 tools/coverage.sh create mode 100755 tools/fmt_clippy_doc.sh create mode 100755 tools/generate_test_coverage_report.sh create mode 100644 up-streamer/Cargo.toml create mode 100644 up-streamer/src/lib.rs create mode 100644 up-streamer/src/route.rs create mode 100644 up-streamer/src/ustreamer.rs create mode 100644 up-streamer/tests/single_local_single_remote.rs create mode 100644 up-streamer/tests/single_local_two_remote_add_remove_rules.rs create mode 100644 up-streamer/tests/single_local_two_remote_authorities_different_remote_transport.rs create mode 100644 up-streamer/tests/single_local_two_remote_authorities_same_remote_transport.rs diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml new file mode 100644 index 00000000..978daff1 --- /dev/null +++ b/.github/workflows/build.yaml @@ -0,0 +1,109 @@ +# ******************************************************************************** +# Copyright (c) 2024 Contributors to the Eclipse Foundation +# +# See the NOTICE file(s) distributed with this work for additional +# information regarding copyright ownership. +# +# This program and the accompanying materials are made available under the +# terms of the Apache License Version 2.0 which is available at +# https://www.apache.org/licenses/LICENSE-2.0 +# +# SPDX-License-Identifier: Apache-2.0 +# *******************************************************************************/ + +name: Build checks and tests + +on: + push: + branches: [ main, feature/initial-pluggable-ustreamer_utransport-thread-safe ] + pull_request: + paths: + - "up-streamer/src/**" + - "Cargo.*" + workflow_call: + workflow_dispatch: + +concurrency: + group: ${{ github.ref }}-${{ github.workflow }} + cancel-in-progress: true + +jobs: + lint: + name: Lint + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + - name: cargo fmt + working-directory: ${{github.workspace}} + run: cargo fmt -- --check + - name: cargo clippy + working-directory: ${{github.workspace}} + run: cargo clippy --all-targets -- -W warnings -D warnings + + test: + name: Test + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@stable + - name: Install dependencies + run: | + cargo install cargo-all-features + - name: Show toolchain information + working-directory: ${{github.workspace}} + run: | + rustup toolchain list + cargo --version + - name: cargo-check all possible feature combinations + run: | + cargo check-all-features + - name: cargo-test all possible feature combinations + run: | + cargo test-all-features + + coverage: + name: Collect coverage info + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@stable + - name: Install dependencies + run: | + cargo install cargo-tarpaulin + - name: Show toolchain information + working-directory: ${{github.workspace}} + run: | + rustup toolchain list + cargo --version + - name: Run tests to collect code coverage information + run: | + # enable nightly features so that we can also include Doctests + RUSTC_BOOTSTRAP=1 cargo tarpaulin -o xml -o lcov -o html --doc --tests --all-features + - name: Upload coverage report (xml) + uses: actions/upload-artifact@v4 + with: + name: Test Coverage Results (xml) + path: cobertura.xml + - name: Upload coverage report (lcov) + uses: actions/upload-artifact@v4 + with: + name: Test Coverage Results (lcov) + path: lcov.info + - name: Upload coverage report (html) + uses: actions/upload-artifact@v4 + with: + name: Test Coverage Results (html) + path: tarpaulin-report.html + + build-docs: + name: Build documentation + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + - name: Create Documentation + working-directory: ${{github.workspace}} + run: RUSTDOCFLAGS=-Dwarnings cargo doc -p up-streamer --no-deps \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..e661d24a --- /dev/null +++ b/.gitignore @@ -0,0 +1,17 @@ +# Generated by Cargo +# will have compiled files and executables +debug/ +target/ + +# These are backup files generated by rustfmt +**/*.rs.bk + +# MSVC Windows builds of rustc generate these, which store debugging information +*.pdb + +# cargo-tarpaulin +cobertura.xml +lcov.info +tarpaulin-report.html + +.idea/ \ No newline at end of file diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 00000000..8ff84251 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,35 @@ +# Contributing to Eclipse uProtocol + +Thanks for your interest in this project. Contributions are welcome! + +Please refer to the main [CONTRIBUTING](https://github.com/eclipse-uprotocol/.github/blob/main/CONTRIBUTING.adoc) resource for Eclipse uProtocol to get most of the general overview. + +The following is from [`up-rust`](https://github.com/eclipse-uprotocol/up-rust) project and is specific to Rust development for uProtocol. + +## Setting up a development environment + +You can use any development environment you like to contribute to `up-streamer-rust`. However, it is mandatory to use the Rust linter ('[clippy]()') for any pull requests you do. +To set up VSCode to run clippy per default every time you save your code, have a look here: [How to use Clippy in VS Code with rust-analyzer?](https://users.rust-lang.org/t/how-to-use-clippy-in-vs-code-with-rust-analyzer/41881) + +Similarly, the project requests that markdown is formatted and linted properly - to help with this, it is recommended to use [markdown linters](https://marketplace.visualstudio.com/items?itemName=DavidAnson.vscode-markdownlint). + +During development, before submitting a PR, you can use `./tools/fmt_clippy_doc.sh` to run these checks on the workspace. + +There also exists a helper script in ./tools to generate test results and test code coverage reports. These reports are placed in the `./target/tarpaulin` directory. If you use VSCode with the [Coverage Gutters](https://marketplace.visualstudio.com/items?itemName=ryanluker.vscode-coverage-gutters) extension, you can enable display of code coverage information with these settings: + +``` json + "coverage-gutters.coverageBaseDir": "**", + "coverage-gutters.coverageFileNames": [ + "target/tarpaulin/lcov.info", + ], +``` + +## DevContainer + +All of these prerequisites are made available as a VSCode devcontainer, configured at the usual place (`.devcontainer`). + +## Contact + +Contact the project developers via the project's "dev" list. + + diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 00000000..3dc2bfc1 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,2235 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "addr2line" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a30b2e23b9e17a9f90641c7ab1549cd9b44f296d3ccbf309d2863cfe398a0cb" +dependencies = [ + "gimli", +] + +[[package]] +name = "adler" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" + +[[package]] +name = "aho-corasick" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" +dependencies = [ + "memchr", +] + +[[package]] +name = "android-tzdata" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" + +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + +[[package]] +name = "anyhow" +version = "1.0.81" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0952808a6c2afd1aa8947271f3a60f1a6763c7b912d210184c5149b5cf147247" + +[[package]] +name = "async-attributes" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3203e79f4dd9bdda415ed03cf14dae5a2bf775c683a00f94e9cd1faf0f596e5" +dependencies = [ + "quote", + "syn 1.0.109", +] + +[[package]] +name = "async-broadcast" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "258b52a1aa741b9f09783b2d86cf0aeeb617bbf847f6933340a39644227acbdb" +dependencies = [ + "event-listener 5.2.0", + "event-listener-strategy 0.5.0", + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "async-channel" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81953c529336010edd6d8e358f886d9581267795c61b19475b71314bffa46d35" +dependencies = [ + "concurrent-queue", + "event-listener 2.5.3", + "futures-core", +] + +[[package]] +name = "async-channel" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f28243a43d821d11341ab73c80bed182dc015c514b951616cf79bd4af39af0c3" +dependencies = [ + "concurrent-queue", + "event-listener 5.2.0", + "event-listener-strategy 0.5.0", + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "async-executor" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17ae5ebefcc48e7452b4987947920dac9450be1110cadf34d1b8c116bdbaf97c" +dependencies = [ + "async-lock 3.3.0", + "async-task", + "concurrent-queue", + "fastrand 2.0.1", + "futures-lite 2.3.0", + "slab", +] + +[[package]] +name = "async-global-executor" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05b1b633a2115cd122d73b955eadd9916c18c8f510ec9cd1686404c60ad1c29c" +dependencies = [ + "async-channel 2.2.0", + "async-executor", + "async-io 2.3.2", + "async-lock 3.3.0", + "blocking", + "futures-lite 2.3.0", + "once_cell", +] + +[[package]] +name = "async-io" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fc5b45d93ef0529756f812ca52e44c221b35341892d3dcc34132ac02f3dd2af" +dependencies = [ + "async-lock 2.8.0", + "autocfg", + "cfg-if", + "concurrent-queue", + "futures-lite 1.13.0", + "log", + "parking", + "polling 2.8.0", + "rustix 0.37.27", + "slab", + "socket2 0.4.10", + "waker-fn", +] + +[[package]] +name = "async-io" +version = "2.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dcccb0f599cfa2f8ace422d3555572f47424da5648a4382a9dd0310ff8210884" +dependencies = [ + "async-lock 3.3.0", + "cfg-if", + "concurrent-queue", + "futures-io", + "futures-lite 2.3.0", + "parking", + "polling 3.5.0", + "rustix 0.38.32", + "slab", + "tracing", + "windows-sys 0.52.0", +] + +[[package]] +name = "async-lock" +version = "2.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "287272293e9d8c41773cec55e365490fe034813a2f172f502d6ddcf75b2f582b" +dependencies = [ + "event-listener 2.5.3", +] + +[[package]] +name = "async-lock" +version = "3.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d034b430882f8381900d3fe6f0aaa3ad94f2cb4ac519b429692a1bc2dda4ae7b" +dependencies = [ + "event-listener 4.0.3", + "event-listener-strategy 0.4.0", + "pin-project-lite", +] + +[[package]] +name = "async-process" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea6438ba0a08d81529c69b36700fa2f95837bfe3e776ab39cde9c14d9149da88" +dependencies = [ + "async-io 1.13.0", + "async-lock 2.8.0", + "async-signal", + "blocking", + "cfg-if", + "event-listener 3.1.0", + "futures-lite 1.13.0", + "rustix 0.38.32", + "windows-sys 0.48.0", +] + +[[package]] +name = "async-signal" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e47d90f65a225c4527103a8d747001fc56e375203592b25ad103e1ca13124c5" +dependencies = [ + "async-io 2.3.2", + "async-lock 2.8.0", + "atomic-waker", + "cfg-if", + "futures-core", + "futures-io", + "rustix 0.38.32", + "signal-hook-registry", + "slab", + "windows-sys 0.48.0", +] + +[[package]] +name = "async-std" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62565bb4402e926b29953c785397c6dc0391b7b446e45008b0049eb43cec6f5d" +dependencies = [ + "async-attributes", + "async-channel 1.9.0", + "async-global-executor", + "async-io 1.13.0", + "async-lock 2.8.0", + "async-process", + "crossbeam-utils", + "futures-channel", + "futures-core", + "futures-io", + "futures-lite 1.13.0", + "gloo-timers", + "kv-log-macro", + "log", + "memchr", + "once_cell", + "pin-project-lite", + "pin-utils", + "slab", + "wasm-bindgen-futures", +] + +[[package]] +name = "async-task" +version = "4.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fbb36e985947064623dbd357f727af08ffd077f93d696782f3c56365fa2e2799" + +[[package]] +name = "async-trait" +version = "0.1.78" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "461abc97219de0eaaf81fe3ef974a540158f3d079c2ab200f891f1a2ef201e85" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.53", +] + +[[package]] +name = "atomic-waker" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" + +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + +[[package]] +name = "backtrace" +version = "0.3.71" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26b05800d2e817c8b3b4b54abd461726265fa9789ae34330622f2db9ee696f9d" +dependencies = [ + "addr2line", + "cc", + "cfg-if", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", +] + +[[package]] +name = "base64" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9475866fec1451be56a3c2400fd081ff546538961565ccb5b7142cbd22bc7a51" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bitflags" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf4b9d6a944f767f8e5e0db018570623c85f3d925ac718db4e06d0187adb21c1" + +[[package]] +name = "blocking" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a37913e8dc4ddcc604f0c6d3bf2887c995153af3611de9e23c352b44c1b9118" +dependencies = [ + "async-channel 2.2.0", + "async-lock 3.3.0", + "async-task", + "fastrand 2.0.1", + "futures-io", + "futures-lite 2.3.0", + "piper", + "tracing", +] + +[[package]] +name = "bumpalo" +version = "3.15.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ff69b9dd49fd426c69a0db9fc04dd934cdb6645ff000864d98f7e2af8830eaa" + +[[package]] +name = "bytes" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2bd12c1caf447e69cd4528f47f94d203fd2582878ecb9e9465484c4148a8223" + +[[package]] +name = "cc" +version = "1.0.90" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8cd6604a82acf3039f1144f54b8eb34e91ffba622051189e71b781822d5ee1f5" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "chrono" +version = "0.4.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8eaf5903dcbc0a39312feb77df2ff4c76387d591b9fc7b04a238dcf8bb62639a" +dependencies = [ + "android-tzdata", + "iana-time-zone", + "js-sys", + "num-traits", + "wasm-bindgen", + "windows-targets 0.52.4", +] + +[[package]] +name = "concurrent-queue" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d16048cd947b08fa32c24458a22f5dc5e835264f689f4f5653210c69fd107363" +dependencies = [ + "crossbeam-utils", +] + +[[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.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f" + +[[package]] +name = "crossbeam-utils" +version = "0.8.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "248e3bacc7dc6baa3b21e405ee045c3047101a49145e7e9eca583ab4c2ca5345" + +[[package]] +name = "either" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11157ac094ffbdde99aa67b23417ebdd801842852b500e395a45a9c0aac03e4a" + +[[package]] +name = "encoding_rs" +version = "0.8.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7268b386296a025e474d5140678f75d6de9493ae55a5d709eeb9dd08149945e1" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "env_logger" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cd405aab171cb85d6735e5c8d9db038c17d3ca007a4d2c25f337935c3d90580" +dependencies = [ + "humantime", + "is-terminal", + "log", + "regex", + "termcolor", +] + +[[package]] +name = "equivalent" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" + +[[package]] +name = "errno" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a258e46cdc063eb8519c00b9fc845fc47bcfca4130e2f08e88665ceda8474245" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] + +[[package]] +name = "event-listener" +version = "2.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" + +[[package]] +name = "event-listener" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d93877bcde0eb80ca09131a08d23f0a5c18a620b01db137dba666d18cd9b30c2" +dependencies = [ + "concurrent-queue", + "parking", + "pin-project-lite", +] + +[[package]] +name = "event-listener" +version = "4.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67b215c49b2b248c855fb73579eb1f4f26c38ffdc12973e20e07b91d78d5646e" +dependencies = [ + "concurrent-queue", + "parking", + "pin-project-lite", +] + +[[package]] +name = "event-listener" +version = "5.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b5fb89194fa3cad959b833185b3063ba881dbfc7030680b314250779fb4cc91" +dependencies = [ + "concurrent-queue", + "parking", + "pin-project-lite", +] + +[[package]] +name = "event-listener-strategy" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "958e4d70b6d5e81971bebec42271ec641e7ff4e170a6fa605f2b8a8b65cb97d3" +dependencies = [ + "event-listener 4.0.3", + "pin-project-lite", +] + +[[package]] +name = "event-listener-strategy" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "feedafcaa9b749175d5ac357452a9d41ea2911da598fde46ce1fe02c37751291" +dependencies = [ + "event-listener 5.2.0", + "pin-project-lite", +] + +[[package]] +name = "fastrand" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e51093e27b0797c359783294ca4f0a911c270184cb10f85783b118614a1501be" +dependencies = [ + "instant", +] + +[[package]] +name = "fastrand" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25cbce373ec4653f1a01a31e8a5e5ec0c622dc27ff9c4e6606eefef5cbbed4a5" + +[[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 = "futures" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "645c6916888f6cb6350d2550b80fb63e734897a8498abe35cfb732b6487804b0" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eac8f7d7865dcb88bd4373ab671c8cf4508703796caa2b1985a9ca867b3fcb78" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d" + +[[package]] +name = "futures-executor" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a576fc72ae164fca6b9db127eaa9a9dda0d61316034f33a0a0d4eda41f02b01d" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-io" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a44623e20b9681a318efdd71c299b6b222ed6f231972bfe2f224ebad6311f0c1" + +[[package]] +name = "futures-lite" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49a9d51ce47660b1e808d3c990b4709f2f415d928835a17dfd16991515c46bce" +dependencies = [ + "fastrand 1.9.0", + "futures-core", + "futures-io", + "memchr", + "parking", + "pin-project-lite", + "waker-fn", +] + +[[package]] +name = "futures-lite" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52527eb5074e35e9339c6b4e8d12600c7128b68fb25dcb9fa9dec18f7c25f3a5" +dependencies = [ + "fastrand 2.0.1", + "futures-core", + "futures-io", + "parking", + "pin-project-lite", +] + +[[package]] +name = "futures-macro" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.53", +] + +[[package]] +name = "futures-sink" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fb8e00e87438d937621c1c6269e53f536c14d3fbd6a042bb24879e57d474fb5" + +[[package]] +name = "futures-task" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004" + +[[package]] +name = "futures-util" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "pin-utils", + "slab", +] + +[[package]] +name = "getrandom" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "190092ea657667030ac6a35e305e62fc4dd69fd98ac98631e5d3a2b1575a12b5" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "gimli" +version = "0.28.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253" + +[[package]] +name = "gloo-timers" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b995a66bb87bebce9a0f4a95aed01daca4872c050bfcb21653361c03bc35e5c" +dependencies = [ + "futures-channel", + "futures-core", + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "h2" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "816ec7294445779408f36fe57bc5b7fc1cf59664059096c65f905c1c61f58069" +dependencies = [ + "bytes", + "fnv", + "futures-core", + "futures-sink", + "futures-util", + "http", + "indexmap 2.2.6", + "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.14.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604" + +[[package]] +name = "hermit-abi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" + +[[package]] +name = "home" +version = "0.5.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3d1354bf6b7235cb4a0576c2619fd4ed18183f689b12b006a0ee7329eeff9a5" +dependencies = [ + "windows-sys 0.52.0", +] + +[[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 = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1cac85db508abc24a2e48553ba12a996e87244a0395ce011e62b37158745d643" +dependencies = [ + "bytes", + "http", +] + +[[package]] +name = "http-body-util" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0475f8b2ac86659c21b64320d5d653f9efe42acd2a4e560073ec61a155a34f1d" +dependencies = [ + "bytes", + "futures-core", + "http", + "http-body", + "pin-project-lite", +] + +[[package]] +name = "httparse" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904" + +[[package]] +name = "humantime" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" + +[[package]] +name = "hyper" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "186548d73ac615b32a73aafe38fb4f56c0d340e110e5a200bcadbaf2e199263a" +dependencies = [ + "bytes", + "futures-channel", + "futures-util", + "h2", + "http", + "http-body", + "httparse", + "itoa", + "pin-project-lite", + "smallvec", + "tokio", + "want", +] + +[[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", + "hyper-util", + "native-tls", + "tokio", + "tokio-native-tls", + "tower-service", +] + +[[package]] +name = "hyper-util" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca38ef113da30126bbff9cd1705f9273e15d45498615d138b0c20279ac7a76aa" +dependencies = [ + "bytes", + "futures-channel", + "futures-util", + "http", + "http-body", + "hyper", + "pin-project-lite", + "socket2 0.5.6", + "tokio", + "tower", + "tower-service", + "tracing", +] + +[[package]] +name = "iana-time-zone" +version = "0.1.60" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7ffbb5a1b541ea2561f8c41c087286cc091e21e556a4f09a8f6cbf17b69b141" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "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 = "idna" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6" +dependencies = [ + "unicode-bidi", + "unicode-normalization", +] + +[[package]] +name = "indexmap" +version = "1.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" +dependencies = [ + "autocfg", + "hashbrown 0.12.3", +] + +[[package]] +name = "indexmap" +version = "2.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "168fb715dda47215e360912c096649d23d58bf392ac62f73919e831745e40f26" +dependencies = [ + "equivalent", + "hashbrown 0.14.3", +] + +[[package]] +name = "instant" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "integration-test-utils" +version = "0.1.5-dev" +dependencies = [ + "async-broadcast", + "async-std", + "async-trait", + "env_logger", + "futures", + "log", + "prost", + "rand", + "serde_json", + "up-rust", + "up-streamer", + "uuid", +] + +[[package]] +name = "io-lifetimes" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eae7b9aee968036d54dce06cebaefd919e4472e753296daccd6d344e3e2df0c2" +dependencies = [ + "hermit-abi", + "libc", + "windows-sys 0.48.0", +] + +[[package]] +name = "ipnet" +version = "2.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f518f335dce6725a761382244631d86cf0ccb2863413590b31338feb467f9c3" + +[[package]] +name = "is-terminal" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f23ff5ef2b80d608d61efee834934d862cd92461afc0560dedf493e4c033738b" +dependencies = [ + "hermit-abi", + "libc", + "windows-sys 0.52.0", +] + +[[package]] +name = "itertools" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1c173a5686ce8bfa551b3563d0c2170bf24ca44da99c7ca4bfdab5418c3fe57" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "1.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c" + +[[package]] +name = "js-sys" +version = "0.3.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29c15563dc2726973df627357ce0c9ddddbea194836909d655df6a75d2cf296d" +dependencies = [ + "wasm-bindgen", +] + +[[package]] +name = "kv-log-macro" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0de8b303297635ad57c9f5059fd9cee7a47f8e8daa09df0fcd07dd39fb22977f" +dependencies = [ + "log", +] + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + +[[package]] +name = "libc" +version = "0.2.153" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd" + +[[package]] +name = "linux-raw-sys" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef53942eb7bf7ff43a617b3e2c1c4a5ecf5944a7c1bc12d7ee39bbb15e5c1519" + +[[package]] +name = "linux-raw-sys" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01cda141df6706de531b6c46c3a33ecca755538219bd484262fa09410c13539c" + +[[package]] +name = "log" +version = "0.4.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c" +dependencies = [ + "value-bag", +] + +[[package]] +name = "mediatype" +version = "0.19.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8878cd8d1b3c8c8ae4b2ba0a36652b7cf192f618a599a7fbdfa25cffd4ea72dd" + +[[package]] +name = "memchr" +version = "2.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149" + +[[package]] +name = "mime" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" + +[[package]] +name = "miniz_oxide" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d811f3e15f28568be3407c8e7fdb6514c1cda3cb30683f15b6a1a1dc4ea14a7" +dependencies = [ + "adler", +] + +[[package]] +name = "mio" +version = "0.8.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c" +dependencies = [ + "libc", + "wasi", + "windows-sys 0.48.0", +] + +[[package]] +name = "native-tls" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07226173c32f2926027b63cce4bcd8076c3552846cbe7925f3aaffeac0a3b92e" +dependencies = [ + "lazy_static", + "libc", + "log", + "openssl", + "openssl-probe", + "openssl-sys", + "schannel", + "security-framework", + "security-framework-sys", + "tempfile", +] + +[[package]] +name = "num-traits" +version = "0.2.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da0df0e5185db44f69b44f26786fe401b6c293d1907744beaa7fa62b2e5a517a" +dependencies = [ + "autocfg", +] + +[[package]] +name = "num_cpus" +version = "1.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" +dependencies = [ + "hermit-abi", + "libc", +] + +[[package]] +name = "object" +version = "0.32.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6a622008b6e321afc04970976f62ee297fdbaa6f95318ca343e3eebb9648441" +dependencies = [ + "memchr", +] + +[[package]] +name = "once_cell" +version = "1.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" + +[[package]] +name = "openssl" +version = "0.10.64" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95a0481286a310808298130d22dd1fef0fa571e05a8f44ec801801e84b216b1f" +dependencies = [ + "bitflags 2.5.0", + "cfg-if", + "foreign-types", + "libc", + "once_cell", + "openssl-macros", + "openssl-sys", +] + +[[package]] +name = "openssl-macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.53", +] + +[[package]] +name = "openssl-probe" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" + +[[package]] +name = "openssl-sys" +version = "0.9.102" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c597637d56fbc83893a35eb0dd04b2b8e7a50c91e64e9493e398b5df4fb45fa2" +dependencies = [ + "cc", + "libc", + "pkg-config", + "vcpkg", +] + +[[package]] +name = "parking" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb813b8af86854136c6922af0598d719255ecb2179515e6e7730d468f05c9cae" + +[[package]] +name = "percent-encoding" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" + +[[package]] +name = "pin-project" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6bf43b791c5b9e34c3d182969b4abb522f9343702850a2e57f460d00d09b4b3" +dependencies = [ + "pin-project-internal", +] + +[[package]] +name = "pin-project-internal" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f38a4412a78282e09a2cf38d195ea5420d15ba0602cb375210efbc877243965" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.53", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "piper" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "668d31b1c4eba19242f2088b2bf3316b82ca31082a8335764db4e083db7485d4" +dependencies = [ + "atomic-waker", + "fastrand 2.0.1", + "futures-io", +] + +[[package]] +name = "pkg-config" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec" + +[[package]] +name = "polling" +version = "2.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b2d323e8ca7996b3e23126511a523f7e62924d93ecd5ae73b333815b0eb3dce" +dependencies = [ + "autocfg", + "bitflags 1.3.2", + "cfg-if", + "concurrent-queue", + "libc", + "log", + "pin-project-lite", + "windows-sys 0.48.0", +] + +[[package]] +name = "polling" +version = "3.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24f040dee2588b4963afb4e420540439d126f73fdacf4a9c486a96d840bac3c9" +dependencies = [ + "cfg-if", + "concurrent-queue", + "pin-project-lite", + "rustix 0.38.32", + "tracing", + "windows-sys 0.52.0", +] + +[[package]] +name = "ppv-lite86" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" + +[[package]] +name = "proc-macro2" +version = "1.0.79" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e835ff2298f5721608eb1a980ecaee1aef2c132bf95ecc026a11b7bf3c01c02e" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "prost" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "146c289cda302b98a28d40c8b3b90498d6e526dd24ac2ecea73e4e491685b94a" +dependencies = [ + "bytes", + "prost-derive", +] + +[[package]] +name = "prost-derive" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "efb6c9a1dd1def8e2124d17e83a20af56f1570d6c2d2bd9e266ccb768df3840e" +dependencies = [ + "anyhow", + "itertools", + "proc-macro2", + "quote", + "syn 2.0.53", +] + +[[package]] +name = "protobuf" +version = "3.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "58678a64de2fced2bdec6bca052a6716a0efe692d6e3f53d1bda6a1def64cfc0" +dependencies = [ + "once_cell", + "protobuf-support", + "thiserror", +] + +[[package]] +name = "protobuf-codegen" +version = "3.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32777b0b3f6538d9d2e012b3fad85c7e4b9244b5958d04a6415f4333782b7a77" +dependencies = [ + "anyhow", + "once_cell", + "protobuf", + "protobuf-parse", + "regex", + "tempfile", + "thiserror", +] + +[[package]] +name = "protobuf-parse" +version = "3.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96cb37955261126624a25b5e6bda40ae34cf3989d52a783087ca6091b29b5642" +dependencies = [ + "anyhow", + "indexmap 1.9.3", + "log", + "protobuf", + "protobuf-support", + "tempfile", + "thiserror", + "which", +] + +[[package]] +name = "protobuf-support" +version = "3.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1ed294a835b0f30810e13616b1cd34943c6d1e84a8f3b0dcfe466d256c3e7e7" +dependencies = [ + "thiserror", +] + +[[package]] +name = "protoc-bin-vendored" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "005ca8623e5633e298ad1f917d8be0a44bcf406bf3cde3b80e63003e49a3f27d" +dependencies = [ + "protoc-bin-vendored-linux-aarch_64", + "protoc-bin-vendored-linux-ppcle_64", + "protoc-bin-vendored-linux-x86_32", + "protoc-bin-vendored-linux-x86_64", + "protoc-bin-vendored-macos-x86_64", + "protoc-bin-vendored-win32", +] + +[[package]] +name = "protoc-bin-vendored-linux-aarch_64" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fb9fc9cce84c8694b6ea01cc6296617b288b703719b725b8c9c65f7c5874435" + +[[package]] +name = "protoc-bin-vendored-linux-ppcle_64" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02d2a07dcf7173a04d49974930ccbfb7fd4d74df30ecfc8762cf2f895a094516" + +[[package]] +name = "protoc-bin-vendored-linux-x86_32" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d54fef0b04fcacba64d1d80eed74a20356d96847da8497a59b0a0a436c9165b0" + +[[package]] +name = "protoc-bin-vendored-linux-x86_64" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8782f2ce7d43a9a5c74ea4936f001e9e8442205c244f7a3d4286bd4c37bc924" + +[[package]] +name = "protoc-bin-vendored-macos-x86_64" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5de656c7ee83f08e0ae5b81792ccfdc1d04e7876b1d9a38e6876a9e09e02537" + +[[package]] +name = "protoc-bin-vendored-win32" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9653c3ed92974e34c5a6e0a510864dab979760481714c172e0a34e437cb98804" + +[[package]] +name = "quote" +version = "1.0.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom", +] + +[[package]] +name = "regex" +version = "1.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b62dbe01f0b06f9d8dc7d49e05a0785f153b00b2c227856282f671e0318c9b15" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86b83b8b9847f9bf95ef68afb0b8e6cdb80f498442f5179a29fad448fcc1eaea" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" + +[[package]] +name = "reqwest" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e6cc1e89e689536eb5aeede61520e874df5a4707df811cd5da4aa5fbb2aae19" +dependencies = [ + "base64", + "bytes", + "encoding_rs", + "futures-channel", + "futures-core", + "futures-util", + "h2", + "http", + "http-body", + "http-body-util", + "hyper", + "hyper-tls", + "hyper-util", + "ipnet", + "js-sys", + "log", + "mime", + "native-tls", + "once_cell", + "percent-encoding", + "pin-project-lite", + "rustls-pemfile", + "serde", + "serde_json", + "serde_urlencoded", + "sync_wrapper", + "system-configuration", + "tokio", + "tokio-native-tls", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "winreg", +] + +[[package]] +name = "rustc-demangle" +version = "0.1.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" + +[[package]] +name = "rustix" +version = "0.37.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fea8ca367a3a01fe35e6943c400addf443c0f57670e6ec51196f71a4b8762dd2" +dependencies = [ + "bitflags 1.3.2", + "errno", + "io-lifetimes", + "libc", + "linux-raw-sys 0.3.8", + "windows-sys 0.48.0", +] + +[[package]] +name = "rustix" +version = "0.38.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65e04861e65f21776e67888bfbea442b3642beaa0138fdb1dd7a84a52dffdb89" +dependencies = [ + "bitflags 2.5.0", + "errno", + "libc", + "linux-raw-sys 0.4.13", + "windows-sys 0.52.0", +] + +[[package]] +name = "rustls-pemfile" +version = "2.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29993a25686778eb88d4189742cd713c9bce943bc54251a33509dc63cbacf73d" +dependencies = [ + "base64", + "rustls-pki-types", +] + +[[package]] +name = "rustls-pki-types" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ecd36cc4259e3e4514335c4a138c6b43171a8d61d8f5c9348f9fc7529416f247" + +[[package]] +name = "ryu" +version = "1.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e86697c916019a8588c99b5fac3cead74ec0b4b819707a682fd4d23fa0ce1ba1" + +[[package]] +name = "schannel" +version = "0.1.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fbc91545643bcf3a0bbb6569265615222618bdf33ce4ffbbd13c4bbd4c093534" +dependencies = [ + "windows-sys 0.52.0", +] + +[[package]] +name = "security-framework" +version = "2.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "770452e37cad93e0a50d5abc3990d2bc351c36d0328f86cefec2f2fb206eaef6" +dependencies = [ + "bitflags 1.3.2", + "core-foundation", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41f3cc463c0ef97e11c3461a9d3787412d30e8e7eb907c79180c4a57bf7c04ef" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "serde" +version = "1.0.197" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fb1c873e1b9b056a4dc4c0c198b24c3ffa059243875552b2bd0933b1aee4ce2" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.197" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7eb0b34b42edc17f6b7cac84a52a1c5f0e1bb2227e997ca9011ea3dd34e8610b" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.53", +] + +[[package]] +name = "serde_json" +version = "1.0.114" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c5f09b1bd632ef549eaa9f60a1f8de742bdbc698e6cee2095fc84dde5f549ae0" +dependencies = [ + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "serde_urlencoded" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" +dependencies = [ + "form_urlencoded", + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "signal-hook-registry" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8229b473baa5980ac72ef434c4415e70c4b5e71b423043adb4ba059f89c99a1" +dependencies = [ + "libc", +] + +[[package]] +name = "slab" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" +dependencies = [ + "autocfg", +] + +[[package]] +name = "smallvec" +version = "1.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" + +[[package]] +name = "socket2" +version = "0.4.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f7916fc008ca5542385b89a3d3ce689953c143e9304a9bf8beec1de48994c0d" +dependencies = [ + "libc", + "winapi", +] + +[[package]] +name = "socket2" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05ffd9c0a93b7543e062e759284fcf5f5e3b098501104bfbdde4d404db792871" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] + +[[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.53" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7383cd0e49fff4b6b90ca5670bfd3e9d6a733b3f90c686605aa7eec8c4996032" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "sync_wrapper" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160" + +[[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", +] + +[[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", +] + +[[package]] +name = "tempfile" +version = "3.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85b77fafb263dd9d05cbeac119526425676db3784113aa9295c88498cbf8bff1" +dependencies = [ + "cfg-if", + "fastrand 2.0.1", + "rustix 0.38.32", + "windows-sys 0.52.0", +] + +[[package]] +name = "termcolor" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "thiserror" +version = "1.0.58" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03468839009160513471e86a034bb2c5c0e4baae3b43f79ffc55c4a5427b3297" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.58" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c61f3ba182994efc43764a46c018c347bc492c79f024e705f46567b418f6d4f7" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.53", +] + +[[package]] +name = "tinyvec" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + +[[package]] +name = "tokio" +version = "1.37.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1adbebffeca75fcfd058afa480fb6c0b81e165a0323f9c9d39c9697e37c46787" +dependencies = [ + "backtrace", + "bytes", + "libc", + "mio", + "num_cpus", + "pin-project-lite", + "socket2 0.5.6", + "windows-sys 0.48.0", +] + +[[package]] +name = "tokio-native-tls" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" +dependencies = [ + "native-tls", + "tokio", +] + +[[package]] +name = "tokio-util" +version = "0.7.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5419f34732d9eb6ee4c3578b7989078579b7f039cbbb9ca2c4da015749371e15" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "pin-project-lite", + "tokio", + "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", + "pin-project", + "pin-project-lite", + "tokio", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "tower-layer" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c20c8dbed6283a09604c3e69b4b7eeb54e298b8a600d4d5ecb5ad39de609f1d0" + +[[package]] +name = "tower-service" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52" + +[[package]] +name = "tracing" +version = "0.1.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" +dependencies = [ + "log", + "pin-project-lite", + "tracing-core", +] + +[[package]] +name = "tracing-core" +version = "0.1.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" +dependencies = [ + "once_cell", +] + +[[package]] +name = "try-lock" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" + +[[package]] +name = "unicode-bidi" +version = "0.3.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08f95100a766bf4f8f28f90d77e0a5461bbdb219042e7679bebe79004fed8d75" + +[[package]] +name = "unicode-ident" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" + +[[package]] +name = "unicode-normalization" +version = "0.1.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a56d1686db2308d901306f92a263857ef59ea39678a5458e7cb17f01415101f5" +dependencies = [ + "tinyvec", +] + +[[package]] +name = "up-rust" +version = "0.1.5" +source = "git+https://github.com/eclipse-uprotocol/up-rust?rev=c705ac97602ad6917a93d23651e8a504ec7bb718#c705ac97602ad6917a93d23651e8a504ec7bb718" +dependencies = [ + "async-trait", + "bytes", + "chrono", + "mediatype", + "once_cell", + "protobuf", + "protobuf-codegen", + "protoc-bin-vendored", + "rand", + "regex", + "reqwest", + "url", + "uuid", +] + +[[package]] +name = "up-streamer" +version = "0.1.5-dev" +dependencies = [ + "async-broadcast", + "async-std", + "async-trait", + "chrono", + "env_logger", + "futures", + "integration-test-utils", + "log", + "prost", + "serde_json", + "up-rust", + "uuid", +] + +[[package]] +name = "url" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "31e6302e3bb753d46e83516cae55ae196fc0c309407cf11ab35cc51a4c2a4633" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", +] + +[[package]] +name = "uuid" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a183cf7feeba97b4dd1c0d46788634f6221d87fa961b305bed08c851829efcc0" + +[[package]] +name = "value-bag" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74797339c3b98616c009c7c3eb53a0ce41e85c8ec66bd3db96ed132d20cfdee8" + +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + +[[package]] +name = "waker-fn" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3c4517f54858c779bbcbf228f4fca63d121bf85fbecb2dc578cdf4a39395690" + +[[package]] +name = "want" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" +dependencies = [ + "try-lock", +] + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "wasm-bindgen" +version = "0.2.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4be2531df63900aeb2bca0daaaddec08491ee64ceecbee5076636a3b026795a8" +dependencies = [ + "cfg-if", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "614d787b966d3989fa7bb98a654e369c762374fd3213d212cfc0251257e747da" +dependencies = [ + "bumpalo", + "log", + "once_cell", + "proc-macro2", + "quote", + "syn 2.0.53", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.42" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76bc14366121efc8dbb487ab05bcc9d346b3b5ec0eaa76e46594cabbe51762c0" +dependencies = [ + "cfg-if", + "js-sys", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1f8823de937b71b9460c0c34e25f3da88250760bec0ebac694b49997550d726" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.53", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af190c94f2773fdb3729c55b007a722abb5384da03bc0986df4c289bf5567e96" + +[[package]] +name = "web-sys" +version = "0.3.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77afa9a11836342370f4817622a2f0f418b134426d91a82dfb48f532d2ec13ef" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "which" +version = "4.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87ba24419a2078cd2b0f2ede2691b6c66d8e47836da3b6db8265ebad47afbfc7" +dependencies = [ + "either", + "home", + "once_cell", + "rustix 0.38.32", +] + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-util" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f29e6f9198ba0d26b4c9f07dbe6f9ed633e1f3d5b8b414090084349e46a52596" +dependencies = [ + "winapi", +] + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows-core" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" +dependencies = [ + "windows-targets 0.52.4", +] + +[[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" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets 0.52.4", +] + +[[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.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7dd37b7e5ab9018759f893a1952c9420d060016fc19a472b4bb20d1bdd694d1b" +dependencies = [ + "windows_aarch64_gnullvm 0.52.4", + "windows_aarch64_msvc 0.52.4", + "windows_i686_gnu 0.52.4", + "windows_i686_msvc 0.52.4", + "windows_x86_64_gnu 0.52.4", + "windows_x86_64_gnullvm 0.52.4", + "windows_x86_64_msvc 0.52.4", +] + +[[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.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bcf46cf4c365c6f2d1cc93ce535f2c8b244591df96ceee75d8e83deb70a9cac9" + +[[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.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da9f259dd3bcf6990b55bffd094c4f7235817ba4ceebde8e6d11cd0c5633b675" + +[[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.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b474d8268f99e0995f25b9f095bc7434632601028cf86590aea5c8a5cb7801d3" + +[[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.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1515e9a29e5bed743cb4415a9ecf5dfca648ce85ee42e15873c3cd8610ff8e02" + +[[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.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5eee091590e89cc02ad514ffe3ead9eb6b660aedca2183455434b93546371a03" + +[[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.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77ca79f2451b49fa9e2af39f0747fe999fcda4f5e241b2898624dca97a1f2177" + +[[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.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32b752e52a2da0ddfbdbcc6fceadfeede4c939ed16d13e648833a61dfb611ed8" + +[[package]] +name = "winreg" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a277a57398d4bfa075df44f501a17cfdf8542d224f0d36095a2adc7aee4ef0a5" +dependencies = [ + "cfg-if", + "windows-sys 0.48.0", +] diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 00000000..9c6d5184 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,60 @@ +# Copyright (c) 2024 Contributors to the Eclipse Foundation +# +# See the NOTICE file(s) distributed with this work for additional +# information regarding copyright ownership. +# +# This program and the accompanying materials are made available under the +# terms of the Apache License Version 2.0 which is available at +# https://www.apache.org/licenses/LICENSE-2.0 +# +# SPDX-License-Identifier: Apache-2.0 + +[workspace] +resolver = "2" +members = [ + "up-streamer", +] + +[workspace.package] +rust-version = "1.66.1" +version = "0.1.5-dev" # uProtocol version +repository = "https://github.com/eclipse-uprotocol/up-streamer-rust" +homepage = "https://github.com/eclipse-uprotocol" +authors = [ + "Pete LeVasseur ", +] +edition = "2021" +keywords = ["uProtocol", "SDV", "routing", "streamer"] +license = "Apache-2.0" + +[workspace.dependencies] +async-std = { version = "1.12.0", features = ["unstable", "attributes"] } # unstable feature needed for task::spawn_local() +async-trait = { version = "0.1" } +env_logger = { version = "0.10.1" } +futures = { version = "0.3.30" } +log = { version = "0.4.20" } +prost = { version = "0.12" } +prost-types = { version = "0.12" } +serde_json = { version = "1.0.111" } +uuid = { version = "1.7.0" } +up-rust = { git = "https://github.com/eclipse-uprotocol/up-rust", rev = "c705ac97602ad6917a93d23651e8a504ec7bb718" } + + +[profile.dev] +debug = true +opt-level = 0 + +[profile.fast] +inherits = "release" +opt-level = 3 +debug = true +debug-assertions = true +overflow-checks = true +lto = false + +[profile.release] +debug = false # If you want debug symbol in release mode, set the env variable: RUSTFLAGS=-g +lto = "fat" +codegen-units = 1 +opt-level = 3 +panic = "abort" diff --git a/LICENSE.txt b/LICENSE.txt new file mode 100644 index 00000000..261eeb9e --- /dev/null +++ b/LICENSE.txt @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/NOTICE.md b/NOTICE.md new file mode 100644 index 00000000..53f5f730 --- /dev/null +++ b/NOTICE.md @@ -0,0 +1,28 @@ +# Notices for Eclipse uProtocol + +This content is produced and maintained by the Eclipse uProtocol project. + +Project home: + +## Trademarks + +Eclipse uProtocol is trademark of the Eclipse Foundation. Eclipse, and the Eclipse Logo are registered trademarks of the Eclipse Foundation. + +## Copyright + +All content is the property of the respective authors or their employers. For more information regarding authorship of content, please consult the listed source code repository logs. + +## Declared Project Licenses + +This program and the accompanying materials are made available under the terms of the or the Apache License, Version 2.0 which is available at . + +SPDX-License-Identifier: Apache-2.0 + +## Third-party Content + +The following are libraries that uProtocol project uses: + + +## NOTE + +Please refer to Cargo.toml for more information of library dependencies diff --git a/README.md b/README.md index 3e9e7fb5..22875939 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,95 @@ -# uprotocol-platform-linux-zenoh -Linux uPlatform that is based off the Zenoh uTransport +# up-streamer-rust + +Generic, pluggable uStreamer that should be usable in most places we need +to bridge from one transport to another. + +## Overview + +Implementation of the uProtocol's uStreamer specification in Rust. + +### Visual Breakdown + +```mermaid +sequenceDiagram + participant main thread + participant TransportForwarder - Foo + participant TransportForwarder - Bar + participant UPClientFoo owned thread / task + participant UPClientBar owned thread / task + + main thread->>main thread: let utransport_foo: Arc>> = Arc::new(Mutex::new(Box::new(UPClientFoo::new()))) + main thread->>main thread: let local_authority = ... + main thread->>main thread: let local_route = Route::new(local_authority.clone(), utransport_foo.clone()) + + main thread->>main thread: let utransport_bar: Arc>> = Arc::new(Mutex::new(Box::new(UPClientBar::new()))) + main thread->>main thread: let remote_authority = ... + main thread->>main thread: let remote_route = Route::new(remote_authority.clone(), utransport_bar.clone()) + + main thread->>main thread: let ustreamer = UStreamer::new() + + main thread->>main thread: ustreamer.add_forwarding_rule(local_route, remote_route) + main thread->>TransportForwarder - Foo: launch TransportForwarder - Foo + activate TransportForwarder - Foo + main thread->>UPClientFoo owned thread / task: (within ustreamer.add_forwarding_rule())
local_route.transport.lock().await.register_listener
(uauthority_to_uuri(remote_route.authority), forwarding_listener).await + activate UPClientFoo owned thread / task + + main thread->>main thread: ustreamer.add_forwarding_rule(remote_route, local_route) + main thread->>TransportForwarder - Bar: launch TransportForwarder - Bar + activate TransportForwarder - Bar + main thread->>UPClientBar owned thread / task: (within ustreamer.add_forwarding_rule())
remote_route.transport.lock().await.register_listener
(uauthority_to_uuri(local_route.authority), forwarding_listener).await + activate UPClientBar owned thread / task + + loop Park the main thread, let background tasks run until closing UStreamer app + + par UPClientFoo thread / task calls ForwardingListener.on_receive() + + UPClientFoo owned thread / task->>UPClientFoo owned thread / task: forwarding_listener.on_receive(UMessage) + UPClientFoo owned thread / task-->>TransportForwarder - Foo: Send UMesssage over channel + TransportForwarder - Foo->>TransportForwarder - Foo: out_transport.send(UMessage)
(out_transport => utransport_bar in this case) + + end + + par UPClientBar thread / task calls ForwardingListener.on_receive() + + UPClientBar owned thread / task->>UPClientBar owned thread / task: forwarding_listener.on_receive(UMessage) + UPClientBar owned thread / task-->>TransportForwarder - Bar: Send UMesssage over channel + TransportForwarder - Bar->>TransportForwarder - Bar: out_transport.send(UMessage)
(out_transport => utransport_foo in this case) + + end + + deactivate UPClientFoo owned thread / task + deactivate UPClientBar owned thread / task + deactivate TransportForwarder - Foo + deactivate TransportForwarder - Bar + + end +``` + +### Generating cargo docs locally + +Documentation can be generated locally with: +```bash +cargo doc --package up-streamer --open +``` +which will open your browser to view the docs. + +## Getting Started + +### Working with the library + +`up-streamer-rust` is generic and pluggable and can serve your needs so long as +1. Each transport you want to bridge over has a `up-client-foo-rust` library + and UPClientFoo struct which has `impl`ed `UTransport` +2. `UTransportBuilder` has been `impl`ed on a struct so that a + `Box` can be safely created by the `UTransportRouter` + in the proper thread's context + +### Usage + +After following along with the [cargo docs](#generating-cargo-docs-locally) generated to add all your forwarding routes, you'll then need to keep the instantiated `UStreamer`, `UTransportRouter`, and `UTransportRouterHandle` around and then pause the main thread, so it will not exit, while the routing happens in the background threads spun up. + +## Implementation Status + +- [x] Routing of Request, Response, and Notification Messages +- [ ] Routing of Publish messages (requires further development of uSubscription interface) +- [x] Mechanism to retrieve messages received on and sent over transports \ No newline at end of file diff --git a/example-utils/integration-test-utils/Cargo.toml b/example-utils/integration-test-utils/Cargo.toml new file mode 100644 index 00000000..d4210bab --- /dev/null +++ b/example-utils/integration-test-utils/Cargo.toml @@ -0,0 +1,26 @@ +[package] +name = "integration-test-utils" +rust-version.workspace = true +version.workspace = true +repository.workspace = true +homepage.workspace = true +authors.workspace = true +edition.workspace = true +keywords.workspace = true +license.workspace = true + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +async-broadcast = { version = "0.7.0" } +async-std = { workspace = true, features = ["unstable"] } +async-trait = { workspace = true } +env_logger = { workspace = true } +futures = { workspace = true } +log = { workspace = true } +prost = { workspace = true } +uuid = { workspace = true } +serde_json = { workspace = true } +up-rust = { workspace = true } +up-streamer = { path = "../../up-streamer" } +rand = "0.8.5" diff --git a/example-utils/integration-test-utils/src/integration_test_listeners.rs b/example-utils/integration-test-utils/src/integration_test_listeners.rs new file mode 100644 index 00000000..7ce248e3 --- /dev/null +++ b/example-utils/integration-test-utils/src/integration_test_listeners.rs @@ -0,0 +1,75 @@ +use async_std::sync::Mutex; +use async_trait::async_trait; +use log::debug; +use std::sync::Arc; +use up_rust::{UListener, UMessage, UStatus}; + +#[derive(Clone)] +pub struct LocalClientListener { + message_store: Arc>>, +} + +impl LocalClientListener { + pub fn new() -> Self { + Self { + message_store: Arc::new(Mutex::new(Vec::with_capacity(10000))), + } + } + + pub fn retrieve_message_store(&self) -> Arc>> { + self.message_store.clone() + } +} + +#[async_trait] +impl UListener for LocalClientListener { + async fn on_receive(&self, msg: UMessage) { + self.message_store.lock().await.push(msg.clone()); + debug!("within local_client_listener! msg: {:?}", msg); + } + + async fn on_error(&self, err: UStatus) { + debug!("within local_client_listener! err: {:?}", err); + } +} + +impl Default for LocalClientListener { + fn default() -> Self { + Self::new() + } +} + +#[derive(Clone)] +pub struct RemoteClientListener { + message_store: Arc>>, +} + +impl RemoteClientListener { + pub fn new() -> Self { + Self { + message_store: Arc::new(Mutex::new(Vec::with_capacity(10000))), + } + } + + pub fn retrieve_message_store(&self) -> Arc>> { + self.message_store.clone() + } +} + +#[async_trait] +impl UListener for RemoteClientListener { + async fn on_receive(&self, msg: UMessage) { + self.message_store.lock().await.push(msg.clone()); + debug!("within remote_client_listener! msg: {:?}", msg); + } + + async fn on_error(&self, err: UStatus) { + debug!("within remote_client_listener! err: {:?}", err); + } +} + +impl Default for RemoteClientListener { + fn default() -> Self { + Self::new() + } +} diff --git a/example-utils/integration-test-utils/src/integration_test_messages.rs b/example-utils/integration-test-utils/src/integration_test_messages.rs new file mode 100644 index 00000000..b952207d --- /dev/null +++ b/example-utils/integration-test-utils/src/integration_test_messages.rs @@ -0,0 +1,114 @@ +use crate::local_client_uuri; +use up_rust::UMessageType::{ + UMESSAGE_TYPE_NOTIFICATION, UMESSAGE_TYPE_PUBLISH, UMESSAGE_TYPE_REQUEST, + UMESSAGE_TYPE_RESPONSE, +}; +use up_rust::{UAttributes, UMessage, UUri}; + +pub fn publish_from_local_client_for_remote_client(local_id: u32) -> UMessage { + UMessage { + attributes: Some(UAttributes { + source: Some(local_client_uuri(local_id)).into(), + type_: UMESSAGE_TYPE_PUBLISH.into(), + ..Default::default() + }) + .into(), + ..Default::default() + } +} + +pub fn notification_from_local_client_for_remote_client( + local_id: u32, + remote_uuri: UUri, +) -> UMessage { + UMessage { + attributes: Some(UAttributes { + source: Some(local_client_uuri(local_id)).into(), + sink: Some(remote_uuri).into(), + type_: UMESSAGE_TYPE_NOTIFICATION.into(), + ..Default::default() + }) + .into(), + ..Default::default() + } +} + +pub fn request_from_local_client_for_remote_client(local_id: u32, remote_uuri: UUri) -> UMessage { + UMessage { + attributes: Some(UAttributes { + source: Some(local_client_uuri(local_id)).into(), + sink: Some(remote_uuri).into(), + type_: UMESSAGE_TYPE_REQUEST.into(), + ..Default::default() + }) + .into(), + ..Default::default() + } +} + +pub fn response_from_local_client_for_remote_client(local_id: u32, remote_uuri: UUri) -> UMessage { + UMessage { + attributes: Some(UAttributes { + source: Some(local_client_uuri(local_id)).into(), + sink: Some(remote_uuri).into(), + type_: UMESSAGE_TYPE_RESPONSE.into(), + ..Default::default() + }) + .into(), + ..Default::default() + } +} + +pub fn publish_from_remote_client_for_local_client(remote_uuri: UUri) -> UMessage { + UMessage { + attributes: Some(UAttributes { + source: Some(remote_uuri).into(), + type_: UMESSAGE_TYPE_PUBLISH.into(), + ..Default::default() + }) + .into(), + ..Default::default() + } +} + +pub fn notification_from_remote_client_for_local_client( + remote_uuri: UUri, + local_id: u32, +) -> UMessage { + UMessage { + attributes: Some(UAttributes { + source: Some(remote_uuri).into(), + sink: Some(local_client_uuri(local_id)).into(), + type_: UMESSAGE_TYPE_NOTIFICATION.into(), + ..Default::default() + }) + .into(), + ..Default::default() + } +} + +pub fn request_from_remote_client_for_local_client(remote_uuri: UUri, local_id: u32) -> UMessage { + UMessage { + attributes: Some(UAttributes { + source: Some(remote_uuri).into(), + sink: Some(local_client_uuri(local_id)).into(), + type_: UMESSAGE_TYPE_REQUEST.into(), + ..Default::default() + }) + .into(), + ..Default::default() + } +} + +pub fn response_from_remote_client_for_local_client(remote_uuri: UUri, local_id: u32) -> UMessage { + UMessage { + attributes: Some(UAttributes { + source: Some(remote_uuri).into(), + sink: Some(local_client_uuri(local_id)).into(), + type_: UMESSAGE_TYPE_RESPONSE.into(), + ..Default::default() + }) + .into(), + ..Default::default() + } +} diff --git a/example-utils/integration-test-utils/src/integration_test_utils.rs b/example-utils/integration-test-utils/src/integration_test_utils.rs new file mode 100644 index 00000000..88a82ff1 --- /dev/null +++ b/example-utils/integration-test-utils/src/integration_test_utils.rs @@ -0,0 +1,335 @@ +use crate::UPClientFoo; +use async_broadcast::{Receiver, Sender}; +use async_std::sync::{Condvar, Mutex}; +use async_std::task; +use log::{debug, error}; +use rand::random; +use std::collections::HashMap; +use std::sync::atomic::{AtomicU64, Ordering}; +use std::sync::Arc; +use std::thread::JoinHandle; +use std::time::{Duration, Instant}; +use up_rust::{UListener, UMessage, UStatus, UTransport, UUIDBuilder, UUri}; + +const CLEAR_RAND_B: u64 = 0x6000_0000_0000_0000; + +pub type Signal = Arc<(Mutex, Condvar)>; + +pub type ActiveConnections = Vec; +#[derive(PartialEq)] +pub enum ClientCommand { + NoOp, + Stop, + DisconnectedFromStreamer(ActiveConnections), + ConnectedToStreamer(ActiveConnections), +} + +pub async fn check_send_receive_message_discrepancy( + number_of_sent_messages: u64, + number_of_messages_received: u64, + percentage_slack: f64, +) { + assert!(number_of_sent_messages > 0); + assert!(number_of_messages_received > 0); + + let slack_in_message_count = number_of_sent_messages as f64 * percentage_slack; + println!("slack_in_message_count: {slack_in_message_count}"); + println!("number_of_sent_messages: {number_of_sent_messages} number_of_messages_received: {number_of_messages_received}"); + if f64::abs(number_of_sent_messages as f64 - number_of_messages_received as f64) + > slack_in_message_count + { + panic!("The discrepancy between number_of_sent_messages and number_of_message_received \ + is higher than allowable slack: number_of_sent_messages: {number_of_sent_messages}, \ + number_of_messages_received: {number_of_messages_received}, slack_in_message_count: \ + {slack_in_message_count}"); + } +} + +pub async fn check_messages_in_order(messages: Arc>>) { + let messages = messages.lock().await; + if messages.is_empty() { + return; + } + + // Step 1: Group messages by id.lsb + let mut grouped_messages: HashMap> = HashMap::new(); + for msg in messages.iter() { + let lsb = msg.attributes.as_ref().unwrap().id.lsb; + grouped_messages.entry(lsb).or_default().push(msg); + } + + // Step 2: Check each group for strict increasing order of id.msb + for (lsb, group) in grouped_messages { + debug!("lsb: {lsb}"); + if let Some(first_msg) = group.first() { + let mut prev_msb = first_msg.attributes.as_ref().unwrap().id.msb; + for msg in group.iter().skip(1) { + let curr_msb = msg.attributes.as_ref().unwrap().id.msb; + debug!("prev_msb: {prev_msb}, curr_msb: {curr_msb}"); + if curr_msb <= prev_msb { + panic!("!! -- Message ordering issue for lsb: {} -- !!", lsb); + } + prev_msb = curr_msb; + } + } + } +} + +#[inline(always)] +fn override_lsb_rand_b(lsb: u64, new_rand_b: u64) -> u64 { + lsb & CLEAR_RAND_B | new_rand_b +} + +pub async fn wait_for_pause(signal: Signal) { + let (lock, cvar) = &*signal; + let mut has_paused = lock.lock().await; + while !*has_paused { + debug!("inside wait_for_pause entered while loop"); + // Wait until the client signals it has paused + has_paused = cvar.wait(has_paused).await; + debug!("received has_paused notification"); + } + debug!("exiting wait_for_pause"); +} + +pub async fn signal_to_pause(signal: Signal) { + let (lock, cvar) = &*signal; + let mut should_pause = lock.lock().await; + *should_pause = true; // Indicate the client should pause + cvar.notify_all(); // Wake up the client so it can check the condition and continue +} + +pub async fn signal_to_resume(signal: Signal) { + let (lock, cvar) = &*signal; + let mut should_pause = lock.lock().await; + *should_pause = false; // Indicate the client should no longer pause + cvar.notify_all(); // Wake up the client so it can check the condition and continue +} + +pub async fn reset_pause(signal: Signal) { + { + let (lock, _cvar) = &*signal; + let mut has_paused = lock.lock().await; + *has_paused = false; + } +} + +#[allow(clippy::too_many_arguments)] +pub async fn run_client( + name: String, + my_client_uuri: UUri, + _other_client_uuri: UUri, + listener: Arc, + tx: Sender>, + rx: Receiver>, + _publish_msgs: Vec, + mut notification_msgs: Vec, + mut request_msgs: Vec, + mut response_msgs: Vec, + send: bool, + pause_execution: Signal, + execution_paused: Signal, + client_command: Arc>, + number_of_sends: Arc, + sent_message_vec_capacity: usize, +) -> JoinHandle> { + std::thread::spawn(move || { + task::block_on(async move { + let client = UPClientFoo::new(&name, rx, tx).await; + + let client_rand_b = random::() >> 2; + + let mut sent_messages = Vec::with_capacity(sent_message_vec_capacity); + + let register_res = client + .register_listener(my_client_uuri.clone(), listener) + .await; + let Ok(_registration_string) = register_res else { + panic!("Unable to register!"); + }; + + let mut active_connection_listing = Vec::new(); + + // let register_res = client + // .register_listener(other_client_uuri.clone(), listener) + // .await; + // let Ok(_registration_string) = register_res else { + // panic!("Unable to register!"); + // }; + + let start = Instant::now(); + + loop { + debug!("top of loop"); + + { + // allows us to pause execution upon command and then signal back when we've done so + let (lock, cvar) = &*pause_execution; + let mut should_pause = lock.lock().await; + while *should_pause { + task::sleep(Duration::from_millis(100)).await; + + let command = client_command.lock().await; + if *command == ClientCommand::Stop { + let times: u64 = client.times_received.load(Ordering::SeqCst); + println!("{name} had rx of: {times}"); + task::sleep(Duration::from_millis(1000)).await; + return sent_messages; + } else { + match &*command { + ClientCommand::NoOp => {} + ClientCommand::ConnectedToStreamer(active_connections) => { + debug!("{} commmand: ConnectedToStreamer", &name); + active_connection_listing = active_connections.clone(); + debug!( + "{} set connected_to_streamer to: {:?}", + &name, active_connection_listing + ); + } + ClientCommand::DisconnectedFromStreamer(active_connections) => { + debug!("{} commmand: DisconnectedFromStreamer", &name); + active_connection_listing = active_connections.clone(); + debug!( + "{} set connected_to_streamer to: {:?}", + &name, active_connection_listing + ); + } + _ => { + error!( + "{} ClientCommand::Stop should have been handled earlier", + &name + ) + } + } + { + let (lock_exec_pause, cvar_exec_pause) = &*execution_paused; + let mut has_paused = lock_exec_pause.lock().await; + *has_paused = true; + debug!("{} has_paused set to true", &name); + cvar_exec_pause.notify_one(); + debug!("{} cvar_exec_pause.notify_one()", &name); + } + debug!("{} Loop paused. Waiting...", &name); + should_pause = cvar.wait(should_pause).await; + debug!("{} Got signal to pause", &name); + } + } + } + + let current = Instant::now(); + let ellapsed = current - start; + + debug!("ellapsed: {}", ellapsed.as_millis()); + + debug!("-----------------------------------------------------------------------"); + + if !send { + continue; + } + + // TODO: Doesn't work currently + // Requires use of uSubscription + // let send_res = client.send(publish_msg.clone()).await; + // if send_res.is_err() { + // panic!("Unable to send from client: {}", &name); + // } + + for (index, notification_msg) in &mut notification_msgs.iter_mut().enumerate() { + if let Some(attributes) = notification_msg.attributes.as_mut() { + let new_id = UUIDBuilder::build(); + attributes.id.0 = Some(Box::new(new_id)); + let uuid = attributes.id.as_mut().unwrap(); + let lsb = &mut uuid.lsb; + *lsb = override_lsb_rand_b(*lsb, client_rand_b); + } + + debug!( + "prior to sending from client {}, the request message: {:?}", + &name, ¬ification_msg + ); + + let send_res = client.send(notification_msg.clone()).await; + if send_res.is_err() { + error!("Unable to send from client: {}", &name); + } else if !active_connection_listing.is_empty() + && active_connection_listing[index] + { + sent_messages.push(notification_msg.clone()); + number_of_sends.fetch_add(1, Ordering::SeqCst); + debug!( + "{} after Notification send, we have sent: {}", + &name, + number_of_sends.load(Ordering::SeqCst) + ); + } + } + + for (index, request_msg) in &mut request_msgs.iter_mut().enumerate() { + if let Some(attributes) = request_msg.attributes.as_mut() { + let new_id = UUIDBuilder::build(); + attributes.id.0 = Some(Box::new(new_id)); + let uuid = attributes.id.as_mut().unwrap(); + let lsb = &mut uuid.lsb; + *lsb = override_lsb_rand_b(*lsb, client_rand_b); + } + + debug!( + "prior to sending from client {}, the request message: {:?}", + &name, &request_msg + ); + + sent_messages.push(request_msg.clone()); + + let send_res = client.send(request_msg.clone()).await; + if send_res.is_err() { + error!("Unable to send from client: {}", &name); + } else if !active_connection_listing.is_empty() + && active_connection_listing[index] + { + sent_messages.push(request_msg.clone()); + number_of_sends.fetch_add(1, Ordering::SeqCst); + debug!( + "{} after Request send, we have sent: {}", + &name, + number_of_sends.load(Ordering::SeqCst) + ); + } + } + + for (index, response_msg) in &mut response_msgs.iter_mut().enumerate() { + if let Some(attributes) = response_msg.attributes.as_mut() { + let new_id = UUIDBuilder::build(); + attributes.id.0 = Some(Box::new(new_id)); + let uuid = attributes.id.as_mut().unwrap(); + let lsb = &mut uuid.lsb; + *lsb = override_lsb_rand_b(*lsb, client_rand_b); + } + + debug!( + "prior to sending from client {}, the response message: {:?}", + &name, &response_msg + ); + + sent_messages.push(response_msg.clone()); + + let send_res = client.send(response_msg.clone()).await; + if send_res.is_err() { + error!("Unable to send from client: {}", &name); + } else if !active_connection_listing.is_empty() + && active_connection_listing[index] + { + sent_messages.push(response_msg.clone()); + number_of_sends.fetch_add(1, Ordering::SeqCst); + debug!( + "{} after Response send, we have sent: {}", + &name, + number_of_sends.load(Ordering::SeqCst) + ); + } + } + + task::sleep(Duration::from_millis(1)).await; + } + }) + }) +} diff --git a/example-utils/integration-test-utils/src/integration_test_uuris.rs b/example-utils/integration-test-utils/src/integration_test_uuris.rs new file mode 100644 index 00000000..b7689908 --- /dev/null +++ b/example-utils/integration-test-utils/src/integration_test_uuris.rs @@ -0,0 +1,53 @@ +use up_rust::{Number, UAuthority, UEntity, UUri}; + +pub fn local_authority() -> UAuthority { + UAuthority { + name: Some("local_authority".to_string()), + number: Number::Ip(vec![192, 168, 1, 100]).into(), + ..Default::default() + } +} + +pub fn remote_authority_a() -> UAuthority { + UAuthority { + name: Some("remote_authority_a".to_string()), + number: Number::Ip(vec![192, 168, 1, 200]).into(), + ..Default::default() + } +} + +pub fn remote_authority_b() -> UAuthority { + UAuthority { + name: Some("remote_authority_b".to_string()), + number: Number::Ip(vec![192, 168, 1, 201]).into(), + ..Default::default() + } +} + +pub fn local_client_uuri(id: u32) -> UUri { + UUri { + authority: Some(local_authority()).into(), + entity: Some(UEntity { + name: format!("local_entity_{id}").to_string(), + id: Some(id), + version_major: Some(1), + ..Default::default() + }) + .into(), + ..Default::default() + } +} + +pub fn remote_client_uuri(authority: UAuthority, id: u32) -> UUri { + UUri { + authority: Some(authority).into(), + entity: Some(UEntity { + name: format!("remote_entity_{id}").to_string(), + id: Some(id), + version_major: Some(1), + ..Default::default() + }) + .into(), + ..Default::default() + } +} diff --git a/example-utils/integration-test-utils/src/lib.rs b/example-utils/integration-test-utils/src/lib.rs new file mode 100644 index 00000000..0a48d688 --- /dev/null +++ b/example-utils/integration-test-utils/src/lib.rs @@ -0,0 +1,36 @@ +/******************************************************************************** + * Copyright (c) 2024 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ + +mod up_client_foo; +pub use up_client_foo::UPClientFoo; +mod integration_test_utils; + +pub use integration_test_utils::{ + check_messages_in_order, check_send_receive_message_discrepancy, reset_pause, run_client, + signal_to_pause, signal_to_resume, wait_for_pause, ClientCommand, Signal, +}; +mod integration_test_listeners; +pub use integration_test_listeners::{LocalClientListener, RemoteClientListener}; +mod integration_test_uuris; + +pub use integration_test_uuris::{ + local_authority, local_client_uuri, remote_authority_a, remote_authority_b, remote_client_uuri, +}; +mod integration_test_messages; +pub use integration_test_messages::{ + notification_from_local_client_for_remote_client, + notification_from_remote_client_for_local_client, publish_from_local_client_for_remote_client, + publish_from_remote_client_for_local_client, request_from_local_client_for_remote_client, + request_from_remote_client_for_local_client, response_from_local_client_for_remote_client, + response_from_remote_client_for_local_client, +}; diff --git a/example-utils/integration-test-utils/src/up_client_foo.rs b/example-utils/integration-test-utils/src/up_client_foo.rs new file mode 100644 index 00000000..95f350e7 --- /dev/null +++ b/example-utils/integration-test-utils/src/up_client_foo.rs @@ -0,0 +1,413 @@ +/******************************************************************************** + * Copyright (c) 2024 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ + +use async_broadcast::{Receiver, Sender}; +use async_std::sync::Mutex; +use async_std::task; +use async_trait::async_trait; +use log::{debug, error}; +use std::collections::{HashMap, HashSet}; +use std::sync::atomic::{AtomicU64, Ordering}; +use std::sync::Arc; +use up_rust::{ + ComparableListener, UAuthority, UCode, UListener, UMessage, UMessageType, UStatus, UTransport, + UUri, +}; + +pub struct UPClientFoo { + #[allow(dead_code)] + name: Arc, + #[allow(dead_code)] + protocol_receiver: Receiver>, + protocol_sender: Sender>, + listeners: Arc>>>, + authority_listeners: Arc>>>, + pub times_received: Arc, +} + +impl UPClientFoo { + pub async fn new( + name: &str, + protocol_receiver: Receiver>, + protocol_sender: Sender>, + ) -> Self { + let name = Arc::new(name.to_string()); + let listeners: Arc>>> = + Arc::new(Mutex::new(HashMap::new())); + let authority_listeners: Arc>>> = + Arc::new(Mutex::new(HashMap::new())); + + let name_clone = name.clone(); + let authority_listeners_clone = authority_listeners.clone(); + let listeners_clone = listeners.clone(); + let protocol_receiver_clone = protocol_receiver.clone(); + + let times_received = Arc::new(AtomicU64::new(0)); + let times_received_task = times_received.clone(); + task::spawn(async move { + let name_clone = name_clone.clone(); + let mut protocol_receiver_clone = protocol_receiver_clone.clone(); + let listeners_clone = listeners_clone.clone(); + + while let Ok(received) = protocol_receiver_clone.recv().await { + match &received { + Ok(msg) => { + let UMessage { attributes, .. } = &msg; + let Some(attr) = attributes.as_ref() else { + debug!("{}: No UAttributes!", &name_clone); + continue; + }; + + match attr.type_.enum_value() { + Ok(enum_value) => match enum_value { + UMessageType::UMESSAGE_TYPE_UNSPECIFIED => { + debug!("{}: Type unspecified! Fail!", &name_clone); + } + UMessageType::UMESSAGE_TYPE_NOTIFICATION => { + let sink_uuri = attr.sink.as_ref(); + debug!("{}: Request sink uuri: {sink_uuri:?}", &name_clone); + match sink_uuri { + None => { + debug!("{}: No sink uuri!", &name_clone); + } + Some(topic) => { + let authority_listeners = + authority_listeners_clone.lock().await; + if let Some(authority) = topic.authority.as_ref() { + debug!( + "{}: Notification: authority: {authority:?}", + &name_clone + ); + + let authority_listeners = + authority_listeners.get(authority); + + if let Some(authority_listeners) = + authority_listeners + { + debug!( + "{}: Notification: authority listeners found: {authority:?}", + &name_clone + ); + + for (authority_listener_num, al) in + authority_listeners.iter().enumerate() + { + debug!("{}: Notification: Authority listener num: {}", &name_clone, authority_listener_num); + al.on_receive(msg.clone()).await; + } + } else { + debug!( + "{}: Notification: authority no listeners: {authority:?}", + &name_clone + ); + } + } + + let listeners = listeners_clone.lock().await; + let topic_listeners = listeners.get(topic); + + if let Some(topic_listeners) = topic_listeners { + debug!( + "{}: Notification: topic: {topic:?} -- listeners found", + &name_clone + ); + times_received_task.fetch_add(1, Ordering::SeqCst); + for tl in topic_listeners.iter() { + tl.on_receive(msg.clone()).await; + } + } else { + debug!( + "{}: Notification: topic: {topic:?} -- listeners not found", + &name_clone + ); + } + } + } + } + UMessageType::UMESSAGE_TYPE_PUBLISH => { + unimplemented!("Still need to handle Publish messages"); + } + UMessageType::UMESSAGE_TYPE_REQUEST => { + let sink_uuri = attr.sink.as_ref(); + debug!("{}: Request sink uuri: {sink_uuri:?}", &name_clone); + match sink_uuri { + None => { + debug!("{}: No sink uuri!", &name_clone); + } + Some(topic) => { + let authority_listeners = + authority_listeners_clone.lock().await; + if let Some(authority) = topic.authority.as_ref() { + debug!( + "{}: Request: authority: {authority:?}", + &name_clone + ); + + let authority_listeners = + authority_listeners.get(authority); + + if let Some(authority_listeners) = + authority_listeners + { + debug!( + "{}: Request: authority listeners found: {authority:?}", + &name_clone + ); + for (authority_listener_num, al) in + authority_listeners.iter().enumerate() + { + debug!("{}: Request: Authority listener num: {}", &name_clone, authority_listener_num); + al.on_receive(msg.clone()).await; + } + } else { + debug!( + "{}: Request: authority no listeners: {authority:?}", + &name_clone + ); + } + } + + let listeners = listeners_clone.lock().await; + let topic_listeners = listeners.get(topic); + + if let Some(topic_listeners) = topic_listeners { + debug!( + "{}: Request: topic: {topic:?} -- listeners found", + &name_clone + ); + times_received_task.fetch_add(1, Ordering::SeqCst); + for tl in topic_listeners.iter() { + tl.on_receive(msg.clone()).await; + } + } else { + debug!( + "{}: Request: topic: {topic:?} -- listeners not found", + &name_clone + ); + } + } + } + } + UMessageType::UMESSAGE_TYPE_RESPONSE => { + let sink_uuri = attr.sink.as_ref(); + debug!("{}: Response sink uuri: {sink_uuri:?}", &name_clone); + match sink_uuri { + None => { + debug!("{}: No sink uuri!", &name_clone); + } + Some(topic) => { + let authority_listeners = + authority_listeners_clone.lock().await; + if let Some(authority) = topic.authority.as_ref() { + debug!( + "{}: Response: authority: {authority:?}", + &name_clone + ); + + let authority_listeners = + authority_listeners.get(authority); + + if let Some(authority_listeners) = + authority_listeners + { + debug!( + "{}: Response: authority listeners found: {authority:?}", + &name_clone + ); + for (authority_listener_num, al) in + authority_listeners.iter().enumerate() + { + debug!("{}: Response: Authority listener num: {}", &name_clone, authority_listener_num); + al.on_receive(msg.clone()).await; + } + } else { + debug!( + "{}: Response: authority no listeners: {authority:?}", + &name_clone + ); + } + } + + let listeners = listeners_clone.lock().await; + let topic_listeners = listeners.get(topic); + + if let Some(topic_listeners) = topic_listeners { + debug!( + "{}: Response: topic: {topic:?} -- listeners found", + &name_clone + ); + times_received_task.fetch_add(1, Ordering::SeqCst); + for tl in topic_listeners.iter() { + tl.on_receive(msg.clone()).await; + } + } else { + debug!( + "{}: Response: topic: {topic:?} -- listeners not found", + &name_clone + ); + } + } + } + } + }, + Err(_) => { + debug!("No matching type or an error occurred!"); + } + } + } + Err(status) => { + debug!("Got an error! err: {status:?}"); + } + } + } + }); + + Self { + name, + protocol_sender, + protocol_receiver, + listeners, + authority_listeners, + times_received, + } + } +} + +#[async_trait] +impl UTransport for UPClientFoo { + async fn send(&self, message: UMessage) -> Result<(), UStatus> { + debug!("sending: {message:?}"); + match self.protocol_sender.broadcast(Ok(message)).await { + Ok(_) => Ok(()), + Err(_) => Err(UStatus::fail_with_code( + UCode::INTERNAL, + "Unable to send over Foo protocol", + )), + } + } + + async fn receive(&self, _topic: UUri) -> Result { + unimplemented!() + } + + async fn register_listener( + &self, + topic: UUri, + listener: Arc, + ) -> Result<(), UStatus> { + debug!("{}: registering listener for: {topic:?}", &self.name); + + return if topic.resource.is_none() && topic.entity.is_none() { + debug!("{}: registering authority listener", &self.name); + + let mut authority_listeners = self.authority_listeners.lock().await; + let Some(authority) = topic.authority.as_ref() else { + return Err(UStatus::fail_with_code( + UCode::INVALID_ARGUMENT, + "No authority provided!", + )); + }; + let authority_listeners = authority_listeners.entry(authority.clone()).or_default(); + let comparable_listener = ComparableListener::new(listener); + let inserted = authority_listeners.insert(comparable_listener); + + match inserted { + true => Ok(()), + false => Err(UStatus::fail_with_code( + UCode::ALREADY_EXISTS, + "UUri and listener already registered!", + )), + } + } else { + debug!("{}: registering regular listener", &self.name); + + let mut listeners = self.listeners.lock().await; + let topic_listeners = listeners.entry(topic).or_default(); + let comparable_listener = ComparableListener::new(listener); + let inserted = topic_listeners.insert(comparable_listener); + + match inserted { + true => Ok(()), + false => Err(UStatus::fail_with_code( + UCode::ALREADY_EXISTS, + "UUri and listener already registered!", + )), + } + }; + } + + async fn unregister_listener( + &self, + topic: UUri, + listener: Arc, + ) -> Result<(), UStatus> { + debug!("{} unregistering listener for topic: {topic:?}", &self.name); + + return if topic.resource.is_none() && topic.entity.is_none() { + debug!("{}: unregistering authority listener", &self.name); + + let mut authority_listeners = self.authority_listeners.lock().await; + + let Some(authority) = topic.authority.as_ref() else { + let err = UStatus::fail_with_code( + UCode::INVALID_ARGUMENT, + format!("Missing authority portion of topic: {topic:?}"), + ); + error!("{} {err:?}", &self.name); + return Err(err); + }; + + let Some(authority_listeners) = authority_listeners.get_mut(authority) else { + let err = UStatus::fail_with_code( + UCode::NOT_FOUND, + format!("No authority listeners for topic: {topic:?}"), + ); + error!("{} {err:?}", &self.name); + return Err(err); + }; + + let comparable_listener = ComparableListener::new(listener); + let removed = authority_listeners.remove(&comparable_listener); + match removed { + true => Ok(()), + false => { + let err = UStatus::fail_with_code( + UCode::NOT_FOUND, + format!("Unable to find authority listener for topic: {topic:?}"), + ); + error!("{} {err:?}", &self.name); + Err(err) + } + } + } else { + let mut listeners = self.listeners.lock().await; + let Some(topic_listeners) = listeners.get_mut(&topic) else { + return Err(UStatus::fail_with_code( + UCode::NOT_FOUND, + "No listeners registered for topic!", + )); + }; + let comparable_listener = ComparableListener::new(listener); + let removed = topic_listeners.remove(&comparable_listener); + + match removed { + false => Err(UStatus::fail_with_code( + UCode::NOT_FOUND, + "No listeners registered for topic! topic: {topic:?}", + )), + true => Ok(()), + } + }; + } +} diff --git a/tools/coverage.sh b/tools/coverage.sh new file mode 100755 index 00000000..7b522ab6 --- /dev/null +++ b/tools/coverage.sh @@ -0,0 +1,21 @@ +#!/bin/bash +# This runs cargo test and creates test coverage data, as well as a test result report, currently this requires the 'nightly' rust toolchain. +# Run this in the project root, and cargo2junit and grcov binaries (do `cargo install cargo2junit grcov`) +# Result files will be placed in ./reports + +PROJECT_NAME_UNDERSCORE="uprotocol_sdk" +RUSTFLAGS="--cfg uuid_unstable -Zprofile -Ccodegen-units=1 -Copt-level=0 -Clink-dead-code -Coverflow-checks=off -Zpanic_abort_tests -Cpanic=abort" +RUSTDOCFLAGS="-Cpanic=abort" +TEMP=`mktemp --directory` + + +mkdir -p $TEMP/reports + +cargo test $CARGO_OPTIONS -- -Z unstable-options --format json | cargo2junit > $TEMP/results.xml +zip -0 $TEMP/ccov.zip `find . \( -name "$PROJECT_NAME_UNDERSCORE*.gc*" \) -print` +grcov $TEMP/ccov.zip -s . -t lcov --llvm --ignore-not-existing --ignore "/*" --ignore "tests/*" --ignore "target/*" -o $TEMP/lcov.info +genhtml $TEMP/lcov.info --output-directory $TEMP/out + +rm $TEMP/ccov.zip +mv -r $TEMP/* ./reports +rm -fr $TEMP \ No newline at end of file diff --git a/tools/fmt_clippy_doc.sh b/tools/fmt_clippy_doc.sh new file mode 100755 index 00000000..536a01da --- /dev/null +++ b/tools/fmt_clippy_doc.sh @@ -0,0 +1,5 @@ +#!/bin/sh + +cargo fmt -- --check +cargo clippy --all-targets -- -W warnings -D warnings +cargo doc -p up-streamer --no-deps diff --git a/tools/generate_test_coverage_report.sh b/tools/generate_test_coverage_report.sh new file mode 100755 index 00000000..1a54e482 --- /dev/null +++ b/tools/generate_test_coverage_report.sh @@ -0,0 +1,17 @@ +#!/bin/bash + +################################################################################ +# Copyright (c) 2024 Contributors to the Eclipse Foundation +# +# See the NOTICE file(s) distributed with this work for additional +# information regarding copyright ownership. +# +# This program and the accompanying materials are made available under the +# terms of the Apache License Version 2.0 which is available at +# https://www.apache.org/licenses/LICENSE-2.0 +# +# SPDX-License-Identifier: Apache-2.0 +################################################################################ + +# we want both html and lcov output formats +cargo tarpaulin -o lcov -o html --output-dir target/tarpaulin diff --git a/up-streamer/Cargo.toml b/up-streamer/Cargo.toml new file mode 100644 index 00000000..924c7f74 --- /dev/null +++ b/up-streamer/Cargo.toml @@ -0,0 +1,39 @@ +# Copyright (c) 2024 Contributors to the Eclipse Foundation +# +# See the NOTICE file(s) distributed with this work for additional +# information regarding copyright ownership. +# +# This program and the accompanying materials are made available under the +# terms of the Apache License Version 2.0 which is available at +# https://www.apache.org/licenses/LICENSE-2.0 +# +# SPDX-License-Identifier: Apache-2.0 + +[package] +name = "up-streamer" +rust-version.workspace = true +version.workspace = true +repository.workspace = true +homepage.workspace = true +authors.workspace = true +edition.workspace = true +keywords.workspace = true +license.workspace = true + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +async-std = { workspace = true, features = ["unstable"] } +async-trait = { workspace = true } +env_logger = { workspace = true } +futures = { workspace = true } +log = { workspace = true } +prost = { workspace = true } +uuid = { workspace = true } +serde_json = { workspace = true } +up-rust = { workspace = true } + +[dev-dependencies] +async-broadcast = { version = "0.7.0" } +chrono = { version = "0.4.31", features = [] } +integration-test-utils = { path = "../example-utils/integration-test-utils" } diff --git a/up-streamer/src/lib.rs b/up-streamer/src/lib.rs new file mode 100644 index 00000000..27e0d0b9 --- /dev/null +++ b/up-streamer/src/lib.rs @@ -0,0 +1,23 @@ +/******************************************************************************** + * Copyright (c) 2024 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ + +//! # up-streamer +//! +//! `up-streamer` implements the `UStreamer` spec to allow bridging between different +//! transports. + +mod route; +pub use route::Route; + +mod ustreamer; +pub use ustreamer::UStreamer; diff --git a/up-streamer/src/route.rs b/up-streamer/src/route.rs new file mode 100644 index 00000000..4ef61f40 --- /dev/null +++ b/up-streamer/src/route.rs @@ -0,0 +1,110 @@ +/******************************************************************************** + * Copyright (c) 2024 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ + +use async_std::sync::{Arc, Mutex}; +use log::*; +use up_rust::{UAuthority, UTransport}; + +const ROUTE_TAG: &str = "Route:"; +const ROUTEFN_NEW_TAG: &str = "new():"; + +/// +/// [`Route`] is defined as a combination of [`UAuthority`][up_rust::UAuthority] and +/// [`Arc>>`][up_rust::UTransport] as routes are at the [`UAuthority`][up_rust::UAuthority] level. +/// +/// # Examples +/// +/// ``` +/// use std::sync::Arc; +/// use async_std::sync::Mutex; +/// use up_rust::{Number, UAuthority, UTransport}; +/// use up_streamer::Route; +/// +/// # pub mod up_client_foo { +/// # use std::sync::Arc; +/// # use up_rust::{UMessage, UTransport, UStatus, UUIDBuilder, UUri, UListener}; +/// # use async_trait::async_trait; +/// # pub struct UPClientFoo; +/// # +/// # #[async_trait] +/// # impl UTransport for UPClientFoo { +/// # async fn send(&self, _message: UMessage) -> Result<(), UStatus> { +/// # todo!() +/// # } +/// # +/// # async fn receive(&self, _topic: UUri) -> Result { +/// # todo!() +/// # } +/// # +/// # async fn register_listener( +/// # &self, +/// # topic: UUri, +/// # _listener: Arc, +/// # ) -> Result<(), UStatus> { +/// # println!("UPClientFoo: registering topic: {:?}", topic); +/// # Ok(()) +/// # } +/// # +/// # async fn unregister_listener(&self, topic: UUri, _listener: Arc) -> Result<(), UStatus> { +/// # println!( +/// # "UPClientFoo: unregistering topic: {topic:?}" +/// # ); +/// # Ok(()) +/// # } +/// # } +/// # +/// # impl UPClientFoo { +/// # pub fn new() -> Self { +/// # Self {} +/// # } +/// # } +/// # } +/// +/// let local_transport: Arc>> = Arc::new(Mutex::new(Box::new(up_client_foo::UPClientFoo::new()))); +/// +/// let authority_foo = UAuthority { +/// name: Some("foo_name".to_string()).into(), +/// number: Some(Number::Ip(vec![192, 168, 1, 100])), +/// ..Default::default() +/// }; +/// +/// let local_route = Route::new("local_route", authority_foo, local_transport); +/// ``` +#[derive(Clone)] +pub struct Route { + pub(crate) name: String, + pub(crate) authority: UAuthority, + pub(crate) transport: Arc>>, +} + +impl Route { + pub fn new( + name: &str, + authority: UAuthority, + transport: Arc>>, + ) -> Self { + // Try to initiate logging. + // Required in case of dynamic lib, otherwise no logs. + // But cannot be done twice in case of static link. + let _ = env_logger::try_init(); + debug!( + "{}:{} Creating Route from: ({:?})", + &ROUTE_TAG, &ROUTEFN_NEW_TAG, &authority, + ); + Self { + name: name.to_string(), + authority, + transport, + } + } +} diff --git a/up-streamer/src/ustreamer.rs b/up-streamer/src/ustreamer.rs new file mode 100644 index 00000000..214ae4c6 --- /dev/null +++ b/up-streamer/src/ustreamer.rs @@ -0,0 +1,986 @@ +/******************************************************************************** + * Copyright (c) 2024 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ + +use crate::route::Route; +use async_std::channel::{Receiver, Sender}; +use async_std::sync::{Arc, Mutex}; +use async_std::{channel, task}; +use async_trait::async_trait; +use log::*; +use std::collections::HashMap; +use std::hash::{Hash, Hasher}; +use std::ops::Deref; +use up_rust::{UAuthority, UCode, UListener, UMessage, UStatus, UTransport, UUIDBuilder, UUri}; + +const USTREAMER_TAG: &str = "UStreamer:"; +const USTREAMER_FN_NEW_TAG: &str = "new():"; +const USTREAMER_FN_ADD_FORWARDING_RULE_TAG: &str = "add_forwarding_rule():"; +const USTREAMER_FN_DELETE_FORWARDING_RULE_TAG: &str = "delete_forwarding_rule():"; + +// the 'gatekeeper' which will prevent us from erroneously being able to add duplicate +// forwarding rules or delete those rules which don't exist +type ForwardingRules = Arc< + Mutex< + HashMap< + ( + UAuthority, + UAuthority, + ComparableTransport, + ComparableTransport, + ), + Arc, + >, + >, +>; +// for keeping track of how many users there are of the TransportForwarder so that we can remove +// from the TransportForwarders later when no longer needed +type TransportForwarderCount = Arc>>; +// we only need one TransportForwarder per out `UTransport`, so we keep track of that one here +// and the Sender necessary to hand off to the listener for the in `UTransport` +type TransportForwarders = + Arc>)>>>; +// we need to keep track of in `UTransport` and out `UAuthority` since in the case of the same +// in `UTransport` and out `UAuthority` we should not register more than once +type InTransportOutAuthorities = Arc>>; + +/// A [`UStreamer`] is used to coordinate the addition and deletion of forwarding rules between +/// [`Route`][crate::Route]s +/// +/// Essentially, it's a means of setting up rules so that messages from one transport (e.g. Zenoh) +/// are bridged onto another transport (e.g. SOME/IP). +/// +/// # Examples +/// +/// ## Typical usage +/// ``` +/// use std::sync::Arc; +/// use async_std::sync::Mutex; +/// use up_rust::{Number, UAuthority, UListener, UTransport}; +/// use up_streamer::{Route, UStreamer}; +/// # pub mod up_client_foo { +/// # use std::sync::Arc; +/// use async_trait::async_trait; +/// # use up_rust::{UListener, UMessage, UStatus, UUIDBuilder, UUri}; +/// # use up_rust::UTransport; +/// # +/// # pub struct UPClientFoo; +/// # +/// # #[async_trait] +/// # impl UTransport for UPClientFoo { +/// # async fn send(&self, _message: UMessage) -> Result<(), UStatus> { +/// # todo!() +/// # } +/// # +/// # async fn receive(&self, _topic: UUri) -> Result { +/// # todo!() +/// # } +/// # +/// # async fn register_listener( +/// # &self, +/// # topic: UUri, +/// # _listener: Arc, +/// # ) -> Result<(), UStatus> { +/// # println!("UPClientFoo: registering topic: {:?}", topic); +/// # Ok(()) +/// # } +/// # +/// # async fn unregister_listener(&self, topic: UUri, _listener: Arc) -> Result<(), UStatus> { +/// # println!( +/// # "UPClientFoo: unregistering topic: {topic:?}" +/// # ); +/// # Ok(()) +/// # } +/// # } +/// # +/// # impl UPClientFoo { +/// # pub fn new() -> Self { +/// # Self {} +/// # } +/// # } +/// # } +/// # +/// # pub mod up_client_bar { +/// # use std::sync::Arc; +/// # use async_trait::async_trait; +/// # use up_rust::{UListener, UMessage, UStatus, UTransport, UUIDBuilder, UUri}; +/// # pub struct UPClientBar; +/// # +/// # #[async_trait] +/// # impl UTransport for UPClientBar { +/// # async fn send(&self, _message: UMessage) -> Result<(), UStatus> { +/// # todo!() +/// # } +/// # +/// # async fn receive(&self, _topic: UUri) -> Result { +/// # todo!() +/// # } +/// # +/// # async fn register_listener( +/// # &self, +/// # topic: UUri, +/// # _listener: Arc, +/// # ) -> Result<(), UStatus> { +/// # println!("UPClientBar: registering topic: {:?}", topic); +/// # Ok(()) +/// # } +/// # +/// # async fn unregister_listener(&self, topic: UUri, _listener: Arc) -> Result<(), UStatus> { +/// # println!( +/// # "UPClientFoo: unregistering topic: {topic:?}" +/// # ); +/// # Ok(()) +/// # } +/// # } +/// # +/// # impl UPClientBar { +/// # pub fn new() -> Self { +/// # Self {} +/// # } +/// # } +/// # } +/// # +/// # async fn async_main() { +/// +/// // Local transport +/// let local_transport: Arc>> = Arc::new(Mutex::new(Box::new(up_client_foo::UPClientFoo::new()))); +/// +/// // Remote transport router +/// let remote_transport: Arc>> = Arc::new(Mutex::new(Box::new(up_client_bar::UPClientBar::new()))); +/// +/// // Local route +/// let local_authority = UAuthority { +/// name: Some("local".to_string()), +/// number: Some(Number::Ip(vec![192, 168, 1, 100])), +/// ..Default::default() +/// }; +/// let local_route = Route::new("local_route", local_authority, local_transport.clone()); +/// +/// // A remote route +/// let remote_authority = UAuthority { +/// name: Some("remote".to_string()), +/// number: Some(Number::Ip(vec![192, 168, 1, 200])), +/// ..Default::default() +/// }; +/// let remote_route = Route::new("remote_route", remote_authority, remote_transport.clone()); +/// +/// let streamer = UStreamer::new("hoge", 100); +/// +/// // Add forwarding rules to route local<->remote +/// assert_eq!( +/// streamer +/// .add_forwarding_rule(local_route.clone(), remote_route.clone()) +/// .await, +/// Ok(()) +/// ); +/// assert_eq!( +/// streamer +/// .add_forwarding_rule(remote_route.clone(), local_route.clone()) +/// .await, +/// Ok(()) +/// ); +/// +/// // Add forwarding rules to route local<->local, should report an error +/// assert!(streamer +/// .add_forwarding_rule(local_route.clone(), local_route.clone()) +/// .await +/// .is_err()); +/// +/// // Rule already exists so it should report an error +/// assert!(streamer +/// .add_forwarding_rule(local_route.clone(), remote_route.clone()) +/// .await +/// .is_err()); +/// +/// // Try and remove an invalid rule +/// assert!(streamer +/// .delete_forwarding_rule(remote_route.clone(), remote_route.clone()) +/// .await +/// .is_err()); +/// +/// // remove valid routing rules +/// assert_eq!( +/// streamer +/// .delete_forwarding_rule(local_route.clone(), remote_route.clone()) +/// .await, +/// Ok(()) +/// ); +/// assert_eq!( +/// streamer +/// .delete_forwarding_rule(remote_route.clone(), local_route.clone()) +/// .await, +/// Ok(()) +/// ); +/// +/// // Try and remove a rule that doesn't exist, should report an error +/// assert!(streamer +/// .delete_forwarding_rule(local_route.clone(), remote_route.clone()) +/// .await +/// .is_err()); +/// # } +/// ``` +#[derive(Clone)] +pub struct UStreamer { + #[allow(dead_code)] + name: String, + registered_forwarding_rules: ForwardingRules, + transport_forwarders: TransportForwarders, + transport_forwarder_count: TransportForwarderCount, + in_transport_out_authorities: InTransportOutAuthorities, + message_queue_size: usize, +} + +impl UStreamer { + /// Creates a new UStreamer which can be used to add forwarding rules. + /// + /// # Parameters + /// + /// * name - Used to uniquely identify this UStreamer in logs + /// * message_queue_size - Determines size of channel used to communicate between `ForwardingListener` + /// and the worker tasks for each currently routed `UTransport` + pub fn new(name: &str, message_queue_size: usize) -> Self { + let name = format!("{USTREAMER_TAG}:{name}:"); + // Try to initiate logging. + // Required in case of dynamic lib, otherwise no logs. + // But cannot be done twice in case of static link. + let _ = env_logger::try_init(); + debug!( + "{}:{}:{} UStreamer created", + &name, USTREAMER_TAG, USTREAMER_FN_NEW_TAG + ); + + Self { + name: name.to_string(), + registered_forwarding_rules: Arc::new(Mutex::new(HashMap::new())), + transport_forwarders: Arc::new(Mutex::new(HashMap::new())), + transport_forwarder_count: Arc::new(Mutex::new(HashMap::new())), + in_transport_out_authorities: Arc::new(Mutex::new(HashMap::new())), + message_queue_size, + } + } + + fn uauthority_to_uuri(authority: UAuthority) -> UUri { + UUri { + authority: Some(authority).into(), + ..Default::default() + } + } + + #[inline(always)] + fn forwarding_id(r#in: &Route, out: &Route) -> String { + format!( + "[in.name: {}, in.authority: {:?} ; out.name: {}, out.authority: {:?}]", + r#in.name, r#in.authority, out.name, out.authority + ) + } + + #[inline(always)] + fn fail_due_to_same_authority(&self, r#in: &Route, out: &Route) -> Result<(), UStatus> { + let err = Err(UStatus::fail_with_code( + UCode::INVALID_ARGUMENT, + format!( + "{} are the same. Unable to delete.", + Self::forwarding_id(r#in, out) + ), + )); + error!( + "{}:{}:{} Deleting forwarding rule failed: {:?}", + self.name, USTREAMER_TAG, USTREAMER_FN_ADD_FORWARDING_RULE_TAG, err + ); + err + } + + /// Adds a forwarding rule to the [`UStreamer`] based on an in [`Route`][crate::Route] and an + /// out [`Route`][crate::Route] + /// + /// Works for any [`UMessage`][up_rust::UMessage] type which has a destination / sink contained + /// in its attributes, i.e. + /// * [`UMessageType::UMESSAGE_TYPE_NOTIFICATION`][up_rust::UMessageType::UMESSAGE_TYPE_NOTIFICATION] + /// * [`UMessageType::UMESSAGE_TYPE_REQUEST`][up_rust::UMessageType::UMESSAGE_TYPE_REQUEST] + /// * [`UMessageType::UMESSAGE_TYPE_RESPONSE`][up_rust::UMessageType::UMESSAGE_TYPE_RESPONSE] + /// + /// # Parameters + /// + /// * `in` - [`Route`][crate::Route] we will bridge _from_ + /// * `out` - [`Route`][crate::Route] we will bridge _onto_ + /// + /// # Errors + /// + /// If unable to add this forwarding rule, we return a [`UStatus`][up_rust::UStatus] noting + /// the error. + /// + /// Typical errors include + /// * already have this forwarding rule registered + /// * attempting to forward onto the same [`Route`][crate::Route] + pub async fn add_forwarding_rule(&self, r#in: Route, out: Route) -> Result<(), UStatus> { + debug!( + "{}:{}:{} Adding forwarding rule for {}", + self.name, + USTREAMER_TAG, + USTREAMER_FN_ADD_FORWARDING_RULE_TAG, + Self::forwarding_id(&r#in, &out) + ); + + if r#in.authority == out.authority { + return self.fail_due_to_same_authority(&r#in, &out); + } + + let in_comparable_transport = ComparableTransport::new(r#in.transport.clone()); + let out_comparable_transport = ComparableTransport::new(out.transport.clone()); + + let sender = { + let mut transport_forwarders = self.transport_forwarders.lock().await; + + let (tx, rx) = channel::bounded(self.message_queue_size); + let (_, sender) = transport_forwarders + .entry(out_comparable_transport.clone()) + .or_insert((TransportForwarder::new(out.transport.clone(), rx).await, tx)); + sender.clone() + }; + let forwarding_listener: Arc = Arc::new( + ForwardingListener::new(&Self::forwarding_id(&r#in, &out), sender.clone()).await, + ); + + let insertion_result = { + let mut registered_forwarding_rules = self.registered_forwarding_rules.lock().await; + registered_forwarding_rules.insert( + ( + r#in.authority.clone(), + out.authority.clone(), + in_comparable_transport.clone(), + out_comparable_transport.clone(), + ), + forwarding_listener.clone(), + ) + }; + if let Some(_exists) = insertion_result { + let err = Err(UStatus::fail_with_code( + UCode::ALREADY_EXISTS, + format!( + "{} are already routed. Unable to add same rule twice.", + Self::forwarding_id(&r#in, &out) + ), + )); + error!( + "{}:{}:{} Adding forwarding rule failed: {:?}", + self.name, USTREAMER_TAG, USTREAMER_FN_ADD_FORWARDING_RULE_TAG, err + ); + err + } else { + // we keep track of how many entries are using this TransportForwarder so that we can + // remove it later + { + let mut transport_forwarder_count = self.transport_forwarder_count.lock().await; + let count = transport_forwarder_count + .entry(in_comparable_transport.clone()) + .or_insert(0); + *count += 1; + } + + let mut in_transport_out_authorities = self.in_transport_out_authorities.lock().await; + if let Some(count) = in_transport_out_authorities + .get_mut(&(in_comparable_transport.clone(), out.authority.clone())) + { + debug!( + "{}:{}:{} This pair of in_transport and out_authority already registered, avoid duplication of messages.", + self.name, USTREAMER_TAG, USTREAMER_FN_ADD_FORWARDING_RULE_TAG + ); + *count += 1; + Ok(()) + } else { + debug!( + "{}:{}:{} This pair of in_transport and out_authority not registered yet, proceeding to register on in_transport.", + self.name, USTREAMER_TAG, USTREAMER_FN_ADD_FORWARDING_RULE_TAG + ); + let registration_result = r#in + .transport + .lock() + .await + .register_listener( + Self::uauthority_to_uuri(out.authority.clone()), + forwarding_listener, + ) + .await; + if let Err(err) = registration_result.as_ref() { + error!( + "{}:{}:{} Adding forwarding rule failed: {:?}", + self.name, USTREAMER_TAG, USTREAMER_FN_ADD_FORWARDING_RULE_TAG, err + ); + } else { + in_transport_out_authorities + .insert((in_comparable_transport.clone(), out.authority), 1); + debug!( + "{}:{}:{} Adding forwarding rule succeeded", + self.name, USTREAMER_TAG, USTREAMER_FN_ADD_FORWARDING_RULE_TAG + ); + } + registration_result + } + } + } + + /// Deletes a forwarding rule from the [`UStreamer`] based on an in [`Route`][crate::Route] and an + /// out [`Route`][crate::Route] + /// + /// Works for any [`UMessage`][up_rust::UMessage] type which has a destination / sink contained + /// in its attributes, i.e. + /// * [`UMessageType::UMESSAGE_TYPE_NOTIFICATION`][up_rust::UMessageType::UMESSAGE_TYPE_NOTIFICATION] + /// * [`UMessageType::UMESSAGE_TYPE_REQUEST`][up_rust::UMessageType::UMESSAGE_TYPE_REQUEST] + /// * [`UMessageType::UMESSAGE_TYPE_RESPONSE`][up_rust::UMessageType::UMESSAGE_TYPE_RESPONSE] + /// + /// # Parameters + /// + /// * `in` - [`Route`][crate::Route] we will bridge _from_ + /// * `out` - [`Route`][crate::Route] we will bridge _onto_ + /// + /// # Errors + /// + /// If unable to delete this forwarding rule, we return a [`UStatus`][up_rust::UStatus] noting + /// the error. + /// + /// Typical errors include + /// * No such route has been added + /// * attempting to delete a forwarding rule where we would forward onto the same [`Route`][crate::Route] + pub async fn delete_forwarding_rule(&self, r#in: Route, out: Route) -> Result<(), UStatus> { + debug!( + "{}:{}:{} Deleting forwarding rule for {}", + self.name, + USTREAMER_TAG, + USTREAMER_FN_DELETE_FORWARDING_RULE_TAG, + Self::forwarding_id(&r#in, &out) + ); + + if r#in.authority == out.authority { + return self.fail_due_to_same_authority(&r#in, &out); + } + + let in_comparable_transport = ComparableTransport::new(r#in.transport.clone()); + let out_comparable_transport = ComparableTransport::new(out.transport.clone()); + + let remove_res = { + let mut registered_forwarding_rules = self.registered_forwarding_rules.lock().await; + registered_forwarding_rules.remove(&( + r#in.authority.clone(), + out.authority.clone(), + in_comparable_transport.clone(), + out_comparable_transport.clone(), + )) + }; + if let Some(exists) = remove_res { + // check if all users of this TransportForwarder have been unregistered and if so + // remove it + { + let count = { + let mut transport_forwarder_count = self.transport_forwarder_count.lock().await; + let count = transport_forwarder_count + .entry(in_comparable_transport.clone()) + .or_insert(0); + *count -= 1; + *count + }; + if count == 0 { + let mut transport_forwarders = self.transport_forwarders.lock().await; + + if let Some(_exists) = + transport_forwarders.remove(&out_comparable_transport.clone()) + { + debug!( + "{}:{}:{} Removing TransportForwarder succeeded", + self.name, USTREAMER_TAG, USTREAMER_FN_DELETE_FORWARDING_RULE_TAG, + ); + } else { + let err = UStatus::fail_with_code( + UCode::NOT_FOUND, + "TrnasportForwarder not found", + ); + error!( + "{}:{}:{} Removing TransportForwarder failed. {:?}", + self.name, USTREAMER_TAG, USTREAMER_FN_DELETE_FORWARDING_RULE_TAG, err, + ); + return Err(err); + } + } + } + + let mut in_transport_out_authorities = self.in_transport_out_authorities.lock().await; + if let Some(count) = in_transport_out_authorities + .get_mut(&(in_comparable_transport.clone(), out.authority.clone())) + { + *count -= 1; + if *count == 0 { + let unregister_res = r#in + .transport + .lock() + .await + .unregister_listener(Self::uauthority_to_uuri(out.authority), exists) + .await; + + if let Err(err) = unregister_res.as_ref() { + error!( + "{}:{}:{} Deleting forwarding rule failed: {:?}", + self.name, USTREAMER_TAG, USTREAMER_FN_DELETE_FORWARDING_RULE_TAG, err + ); + } else { + debug!( + "{}:{}:{} Deleting forwarding rule succeeded", + self.name, USTREAMER_TAG, USTREAMER_FN_DELETE_FORWARDING_RULE_TAG + ); + } + unregister_res + } else { + let no_in_transport_out_authority_found = UStatus::fail_with_code( + UCode::NOT_FOUND, + "No in_transport and out_authority found corresponding", + ); + error!( + "{}:{}:{} Deleting forwarding rule failed: {:?}", + self.name, + USTREAMER_TAG, + USTREAMER_FN_DELETE_FORWARDING_RULE_TAG, + &no_in_transport_out_authority_found + ); + Err(no_in_transport_out_authority_found) + } + } else { + Ok(()) + } + } else { + let err = Err(UStatus::fail_with_code( + UCode::NOT_FOUND, + format!( + "{} is not routed. No rule to delete.", + Self::forwarding_id(&r#in, &out) + ), + )); + error!( + "{}:{}:{} Deleting forwarding rule failed: {:?}", + self.name, USTREAMER_TAG, USTREAMER_FN_DELETE_FORWARDING_RULE_TAG, err + ); + err + } + } +} + +#[derive(Clone)] +pub(crate) struct ComparableTransport { + transport: Arc>>, +} + +impl ComparableTransport { + pub fn new(transport: Arc>>) -> Self { + Self { transport } + } +} + +impl Hash for ComparableTransport { + fn hash(&self, state: &mut H) { + Arc::as_ptr(&self.transport).hash(state); + } +} + +impl PartialEq for ComparableTransport { + fn eq(&self, other: &Self) -> bool { + Arc::ptr_eq(&self.transport, &other.transport) + } +} + +impl Eq for ComparableTransport {} + +const TRANSPORT_FORWARDER_TAG: &str = "TransportForwarder:"; +const TRANSPORT_FORWARDER_FN_MESSAGE_FORWARDING_LOOP_TAG: &str = "message_forwarding_loop():"; +pub(crate) struct TransportForwarder {} + +impl TransportForwarder { + async fn new( + out_transport: Arc>>, + message_receiver: Receiver>, + ) -> Self { + let out_transport_clone = out_transport.clone(); + let message_receiver_clone = message_receiver.clone(); + task::spawn(Self::message_forwarding_loop( + UUIDBuilder::build().to_hyphenated_string(), + out_transport_clone, + message_receiver_clone, + )); + + Self {} + } + + async fn message_forwarding_loop( + id: String, + out_transport: Arc>>, + message_receiver: Receiver>, + ) { + while let Ok(msg) = message_receiver.recv().await { + debug!( + "{}:{}:{} Attempting send of message: {:?}", + id, + TRANSPORT_FORWARDER_TAG, + TRANSPORT_FORWARDER_FN_MESSAGE_FORWARDING_LOOP_TAG, + msg + ); + + let out_transport = out_transport.lock().await; + let send_res = out_transport.send(msg.deref().clone()).await; + if let Err(err) = send_res { + error!( + "{}:{}:{} Sending on out_transport failed: {:?}", + id, + TRANSPORT_FORWARDER_TAG, + TRANSPORT_FORWARDER_FN_MESSAGE_FORWARDING_LOOP_TAG, + err + ); + } else { + debug!( + "{}:{}:{} Sending on out_transport succeeded", + id, TRANSPORT_FORWARDER_TAG, TRANSPORT_FORWARDER_FN_MESSAGE_FORWARDING_LOOP_TAG + ); + } + } + } +} + +const FORWARDING_LISTENER_TAG: &str = "ForwardingListener:"; +const FORWARDING_LISTENER_FN_ON_RECEIVE_TAG: &str = "on_receive():"; +const FORWARDING_LISTENER_FN_ON_ERROR_TAG: &str = "on_error():"; + +#[derive(Clone)] +pub(crate) struct ForwardingListener { + forwarding_id: String, + sender: Sender>, +} + +impl ForwardingListener { + pub(crate) async fn new(forwarding_id: &str, sender: Sender>) -> Self { + Self { + forwarding_id: forwarding_id.to_string(), + sender, + } + } +} + +#[async_trait] +impl UListener for ForwardingListener { + async fn on_receive(&self, msg: UMessage) { + debug!( + "{}:{}:{} Received message: {:?}", + self.forwarding_id, + FORWARDING_LISTENER_TAG, + FORWARDING_LISTENER_FN_ON_RECEIVE_TAG, + &msg + ); + if let Err(e) = self.sender.send(Arc::new(msg)).await { + error!( + "{}:{}:{} Unable to send message to worker pool: {e:?}", + self.forwarding_id, FORWARDING_LISTENER_TAG, FORWARDING_LISTENER_FN_ON_RECEIVE_TAG, + ); + } + } + + async fn on_error(&self, err: UStatus) { + error!( + "{}:{}:{} Received error instead of message from UTransport, with error: {err:?}", + self.forwarding_id, FORWARDING_LISTENER_TAG, FORWARDING_LISTENER_FN_ON_ERROR_TAG + ); + } +} + +#[cfg(test)] +mod tests { + use crate::{Route, UStreamer}; + use async_std::sync::Mutex; + use async_trait::async_trait; + use std::sync::Arc; + use up_rust::{Number, UAuthority, UListener, UMessage, UStatus, UTransport, UUri}; + + pub struct UPClientFoo; + + #[async_trait] + impl UTransport for UPClientFoo { + async fn send(&self, _message: UMessage) -> Result<(), UStatus> { + todo!() + } + + async fn receive(&self, _topic: UUri) -> Result { + todo!() + } + + async fn register_listener( + &self, + topic: UUri, + _listener: Arc, + ) -> Result<(), UStatus> { + println!("UPClientFoo: registering topic: {:?}", topic); + Ok(()) + } + + async fn unregister_listener( + &self, + topic: UUri, + _listener: Arc, + ) -> Result<(), UStatus> { + println!("UPClientFoo: unregistering topic: {topic:?}"); + Ok(()) + } + } + + pub struct UPClientBar; + + #[async_trait] + impl UTransport for UPClientBar { + async fn send(&self, _message: UMessage) -> Result<(), UStatus> { + todo!() + } + + async fn receive(&self, _topic: UUri) -> Result { + todo!() + } + + async fn register_listener( + &self, + topic: UUri, + _listener: Arc, + ) -> Result<(), UStatus> { + println!("UPClientBar: registering topic: {:?}", topic); + Ok(()) + } + + async fn unregister_listener( + &self, + topic: UUri, + _listener: Arc, + ) -> Result<(), UStatus> { + println!("UPClientBar: unregistering topic: {topic:?}"); + Ok(()) + } + } + + #[async_std::test] + async fn test_simple_with_a_single_input_and_output_route() { + // Local route + let local_authority = UAuthority { + name: Some("local".to_string()), + number: Some(Number::Ip(vec![192, 168, 1, 100])), + ..Default::default() + }; + let local_transport: Arc>> = + Arc::new(Mutex::new(Box::new(UPClientFoo))); + let local_route = Route::new( + "local_route", + local_authority.clone(), + local_transport.clone(), + ); + + // A remote route + let remote_authority = UAuthority { + name: Some("remote".to_string()), + number: Some(Number::Ip(vec![192, 168, 1, 200])), + ..Default::default() + }; + let remote_transport: Arc>> = + Arc::new(Mutex::new(Box::new(UPClientBar))); + let remote_route = Route::new( + "remote_route", + remote_authority.clone(), + remote_transport.clone(), + ); + + let ustreamer = UStreamer::new("foo_bar_streamer", 100); + // Add forwarding rules to route local<->remote + assert!(ustreamer + .add_forwarding_rule(local_route.clone(), remote_route.clone()) + .await + .is_ok()); + assert!(ustreamer + .add_forwarding_rule(remote_route.clone(), local_route.clone()) + .await + .is_ok()); + + // Add forwarding rules to route local<->local, should report an error + assert!(ustreamer + .add_forwarding_rule(local_route.clone(), local_route.clone()) + .await + .is_err()); + + // Rule already exists so it should report an error + assert!(ustreamer + .add_forwarding_rule(local_route.clone(), remote_route.clone()) + .await + .is_err()); + + // Add forwarding rules to route remote<->remote, should report an error + assert!(ustreamer + .add_forwarding_rule(remote_route.clone(), remote_route.clone()) + .await + .is_err()); + + // remove valid routing rules + assert!(ustreamer + .delete_forwarding_rule(local_route.clone(), remote_route.clone()) + .await + .is_ok()); + assert!(ustreamer + .delete_forwarding_rule(remote_route.clone(), local_route.clone()) + .await + .is_ok()); + + // Try and remove a rule that doesn't exist, should report an error + assert!(ustreamer + .delete_forwarding_rule(local_route.clone(), remote_route.clone()) + .await + .is_err()); + } + + #[async_std::test] + async fn test_advanced_where_there_is_a_local_route_and_two_remote_routes() { + // Local route + let local_authority = UAuthority { + name: Some("local".to_string()), + number: Some(Number::Ip(vec![192, 168, 1, 100])), + ..Default::default() + }; + let local_transport: Arc>> = + Arc::new(Mutex::new(Box::new(UPClientFoo))); + let local_route = Route::new( + "local_route", + local_authority.clone(), + local_transport.clone(), + ); + + // Remote route - A + let remote_authority_a = UAuthority { + name: Some("remote_a".to_string()), + number: Some(Number::Ip(vec![192, 168, 1, 200])), + ..Default::default() + }; + let remote_transport_a: Arc>> = + Arc::new(Mutex::new(Box::new(UPClientBar))); + let remote_route_a = Route::new( + "remote_route_a", + remote_authority_a.clone(), + remote_transport_a.clone(), + ); + + // Remote route - B + let remote_authority_b = UAuthority { + name: Some("remote_b".to_string()), + number: Some(Number::Ip(vec![192, 168, 1, 201])), + ..Default::default() + }; + let remote_transport_b: Arc>> = + Arc::new(Mutex::new(Box::new(UPClientBar))); + let remote_route_b = Route::new( + "remote_route_b", + remote_authority_b.clone(), + remote_transport_b.clone(), + ); + + let ustreamer = UStreamer::new("foo_bar_streamer", 100); + + // Add forwarding rules to route local<->remote_a + assert!(ustreamer + .add_forwarding_rule(local_route.clone(), remote_route_a.clone()) + .await + .is_ok()); + assert!(ustreamer + .add_forwarding_rule(remote_route_a.clone(), local_route.clone()) + .await + .is_ok()); + + // Add forwarding rules to route local<->remote_b + assert!(ustreamer + .add_forwarding_rule(local_route.clone(), remote_route_b.clone()) + .await + .is_ok()); + assert!(ustreamer + .add_forwarding_rule(remote_route_b.clone(), local_route.clone()) + .await + .is_ok()); + + // Add forwarding rules to route remote_a<->remote_b + assert!(ustreamer + .add_forwarding_rule(remote_route_a.clone(), remote_route_b.clone()) + .await + .is_ok()); + assert!(ustreamer + .add_forwarding_rule(remote_route_b.clone(), remote_route_a.clone()) + .await + .is_ok()); + } + + #[async_std::test] + async fn test_advanced_where_there_is_a_local_route_and_two_remote_routes_but_the_remote_routes_have_the_same_instance_of_utransport( + ) { + // Local route + let local_authority = UAuthority { + name: Some("local".to_string()), + number: Some(Number::Ip(vec![192, 168, 1, 100])), + ..Default::default() + }; + let local_transport: Arc>> = + Arc::new(Mutex::new(Box::new(UPClientFoo))); + let local_route = Route::new( + "local_route", + local_authority.clone(), + local_transport.clone(), + ); + + let remote_transport: Arc>> = + Arc::new(Mutex::new(Box::new(UPClientBar))); + + // Remote route - A + let remote_authority_a = UAuthority { + name: Some("remote_a".to_string()), + number: Some(Number::Ip(vec![192, 168, 1, 200])), + ..Default::default() + }; + let remote_route_a = Route::new( + "remote_route_a", + remote_authority_a.clone(), + remote_transport.clone(), + ); + + // Remote route - B + let remote_authority_b = UAuthority { + name: Some("remote_b".to_string()), + number: Some(Number::Ip(vec![192, 168, 1, 201])), + ..Default::default() + }; + let remote_route_b = Route::new( + "remote_route_b", + remote_authority_b.clone(), + remote_transport.clone(), + ); + + let ustreamer = UStreamer::new("foo_bar_streamer", 100); + + // Add forwarding rules to route local<->remote_a + assert!(ustreamer + .add_forwarding_rule(local_route.clone(), remote_route_a.clone()) + .await + .is_ok()); + assert!(ustreamer + .add_forwarding_rule(remote_route_a.clone(), local_route.clone()) + .await + .is_ok()); + + // Add forwarding rules to route local<->remote_b + assert!(ustreamer + .add_forwarding_rule(local_route.clone(), remote_route_b.clone()) + .await + .is_ok()); + assert!(ustreamer + .add_forwarding_rule(remote_route_b.clone(), local_route.clone()) + .await + .is_ok()); + } +} diff --git a/up-streamer/tests/single_local_single_remote.rs b/up-streamer/tests/single_local_single_remote.rs new file mode 100644 index 00000000..b245f980 --- /dev/null +++ b/up-streamer/tests/single_local_single_remote.rs @@ -0,0 +1,234 @@ +/******************************************************************************** + * Copyright (c) 2024 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ + +use async_broadcast::broadcast; +use async_std::sync::{Condvar, Mutex}; +use async_std::task; +use futures::future::join; +use integration_test_utils::{ + check_messages_in_order, check_send_receive_message_discrepancy, local_authority, + local_client_uuri, notification_from_local_client_for_remote_client, + notification_from_remote_client_for_local_client, publish_from_local_client_for_remote_client, + publish_from_remote_client_for_local_client, remote_authority_a, remote_client_uuri, + request_from_local_client_for_remote_client, request_from_remote_client_for_local_client, + reset_pause, response_from_local_client_for_remote_client, + response_from_remote_client_for_local_client, run_client, signal_to_pause, signal_to_resume, + wait_for_pause, ClientCommand, LocalClientListener, RemoteClientListener, UPClientFoo, +}; +use log::debug; +use std::sync::atomic::{AtomicU64, Ordering}; +use std::sync::Arc; +use std::time::Duration; +use up_rust::{UListener, UTransport}; +use up_streamer::{Route, UStreamer}; + +const DURATION_TO_RUN_CLIENTS: u128 = 1_000; +const SENT_MESSAGE_VEC_CAPACITY: usize = 10_000; + +#[async_std::test] +async fn single_local_single_remote() { + // using async_broadcast to simulate communication protocol + let (tx_1, rx_1) = broadcast(10000); + let (tx_2, rx_2) = broadcast(10000); + + let utransport_foo: Arc>> = Arc::new(Mutex::new(Box::new( + UPClientFoo::new("upclient_foo", rx_1.clone(), tx_1.clone()).await, + ))); + let utransport_bar: Arc>> = Arc::new(Mutex::new(Box::new( + UPClientFoo::new("upclient_bar", rx_2.clone(), tx_2.clone()).await, + ))); + + // setting up streamer to bridge between "foo" and "bar" + let ustreamer = UStreamer::new("foo_bar_streamer", 3000); + + // setting up routes between authorities and protocols + let local_route = Route::new("local_route", local_authority(), utransport_foo.clone()); + let remote_route = Route::new("remote_route", remote_authority_a(), utransport_bar.clone()); + + // adding local to remote routing + let add_forwarding_rule_res = ustreamer + .add_forwarding_rule(local_route.clone(), remote_route.clone()) + .await; + assert!(add_forwarding_rule_res.is_ok()); + + // adding remote to local routing + let add_forwarding_rule_res = ustreamer + .add_forwarding_rule(remote_route.clone(), local_route.clone()) + .await; + assert!(add_forwarding_rule_res.is_ok()); + + let local_client_listener = Arc::new(LocalClientListener::new()); + let remote_client_listener = Arc::new(RemoteClientListener::new()); + + let local_client_listener_trait_obj: Arc = local_client_listener.clone(); + let remote_client_listener_trait_obj: Arc = remote_client_listener.clone(); + + let all_signal_should_pause = Arc::new((Mutex::new(true), Condvar::new())); + let local_signal_has_paused = Arc::new((Mutex::new(false), Condvar::new())); + let remote_signal_has_paused = Arc::new((Mutex::new(false), Condvar::new())); + let local_command = Arc::new(Mutex::new(ClientCommand::ConnectedToStreamer(vec![true]))); + let remote_command = Arc::new(Mutex::new(ClientCommand::ConnectedToStreamer(vec![true]))); + + let local_sends = Arc::new(AtomicU64::new(0)); + let remote_sends = Arc::new(AtomicU64::new(0)); + + // kicking off a "local_foo_client" and "remote_bar_client" in order to keep exercising + // the streamer periodically + let local_handle = run_client( + "local_foo_client".to_string(), + local_client_uuri(10), + remote_client_uuri(remote_authority_a(), 20), + local_client_listener_trait_obj, + tx_1.clone(), + rx_1.clone(), + vec![publish_from_local_client_for_remote_client(10)], + vec![notification_from_local_client_for_remote_client( + 10, + remote_client_uuri(remote_authority_a(), 200), + )], + vec![request_from_local_client_for_remote_client( + 10, + remote_client_uuri(remote_authority_a(), 200), + )], + vec![response_from_local_client_for_remote_client( + 10, + remote_client_uuri(remote_authority_a(), 200), + )], + true, + all_signal_should_pause.clone(), + local_signal_has_paused.clone(), + local_command.clone(), + local_sends.clone(), + SENT_MESSAGE_VEC_CAPACITY, + ) + .await; + let remote_handle = run_client( + "remote_bar_client".to_string(), + remote_client_uuri(remote_authority_a(), 200), + local_client_uuri(100), + remote_client_listener_trait_obj, + tx_2.clone(), + rx_2.clone(), + vec![publish_from_remote_client_for_local_client( + remote_client_uuri(remote_authority_a(), 20), + )], + vec![notification_from_remote_client_for_local_client( + remote_client_uuri(remote_authority_a(), 20), + 10, + )], + vec![request_from_remote_client_for_local_client( + remote_client_uuri(remote_authority_a(), 20), + 10, + )], + vec![response_from_remote_client_for_local_client( + remote_client_uuri(remote_authority_a(), 20), + 10, + )], + true, + all_signal_should_pause.clone(), + remote_signal_has_paused.clone(), + remote_command.clone(), + remote_sends.clone(), + SENT_MESSAGE_VEC_CAPACITY, + ) + .await; + + debug!("waiting for clients start"); + + let local_paused = wait_for_pause(local_signal_has_paused.clone()); + let remote_paused = wait_for_pause(remote_signal_has_paused.clone()); + debug!("called wait_for_pause"); + + join(local_paused, remote_paused).await; + debug!("passed join on local_paused and remote_paused"); + + reset_pause(local_signal_has_paused.clone()).await; + debug!("after local has paused set to false"); + reset_pause(remote_signal_has_paused.clone()).await; + debug!("after remote has paused set to false"); + + // Now signal both clients to resume + signal_to_resume(all_signal_should_pause.clone()).await; + + debug!("after signal_to_resume"); + + task::sleep(Duration::from_millis(DURATION_TO_RUN_CLIENTS as u64)).await; + + debug!("past wait on clients to run, now tell them to stop"); + { + let mut local_command = local_command.lock().await; + *local_command = ClientCommand::Stop; + } + { + let mut remote_command = remote_command.lock().await; + *remote_command = ClientCommand::Stop; + } + debug!("setting up commands for Stop"); + signal_to_pause(all_signal_should_pause).await; + debug!("signaled for clients to pause and read command"); + + let local_client_sent_messages = local_handle.join().expect("Unable to join on handle_1"); + let remote_client_sent_messages = remote_handle.join().expect("Unable to join on handle_2"); + + let local_client_sent_messages_num = local_client_sent_messages.len(); + let remote_client_sent_messages_num = remote_client_sent_messages.len(); + + println!("local_client_sent_messages_num: {local_client_sent_messages_num}"); + println!("remote_client_sent_messages_num: {remote_client_sent_messages_num}"); + + let number_of_messages_sent = local_client_sent_messages_num + remote_client_sent_messages_num; + + println!( + "total messages sent via reviewing messages: {}", + number_of_messages_sent + ); + + let number_of_sent_messages = + local_sends.load(Ordering::SeqCst) + remote_sends.load(Ordering::SeqCst); + println!( + "total messages sent via reviewing atomic counts: {}", + number_of_sent_messages + ); + + let local_client_listener_msg_rx_num = local_client_listener + .retrieve_message_store() + .lock() + .await + .len(); + let remote_client_listener_msg_rx_num = remote_client_listener + .retrieve_message_store() + .lock() + .await + .len(); + + println!("local_client_listener_msg_rx_num: {local_client_listener_msg_rx_num}"); + println!("remote_client_listener_msg_rx_num: {remote_client_listener_msg_rx_num}"); + + let number_of_received_messages = + local_client_listener_msg_rx_num + remote_client_listener_msg_rx_num; + + println!("total messages received by clients: {number_of_received_messages}"); + + let percentage_slack = 0.000; + check_send_receive_message_discrepancy( + number_of_sent_messages, + number_of_received_messages as u64, + percentage_slack, + ) + .await; + + check_messages_in_order(local_client_listener.retrieve_message_store()).await; + check_messages_in_order(remote_client_listener.retrieve_message_store()).await; + + debug!("All clients finished."); +} diff --git a/up-streamer/tests/single_local_two_remote_add_remove_rules.rs b/up-streamer/tests/single_local_two_remote_add_remove_rules.rs new file mode 100644 index 00000000..c21abba3 --- /dev/null +++ b/up-streamer/tests/single_local_two_remote_add_remove_rules.rs @@ -0,0 +1,445 @@ +/******************************************************************************** + * Copyright (c) 2024 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ + +use async_broadcast::broadcast; +use async_std::sync::{Condvar, Mutex}; +use async_std::task; +use futures::future::join; +use integration_test_utils::{ + check_messages_in_order, check_send_receive_message_discrepancy, local_authority, + local_client_uuri, notification_from_local_client_for_remote_client, + notification_from_remote_client_for_local_client, publish_from_local_client_for_remote_client, + publish_from_remote_client_for_local_client, remote_authority_a, remote_authority_b, + remote_client_uuri, request_from_local_client_for_remote_client, + request_from_remote_client_for_local_client, reset_pause, + response_from_local_client_for_remote_client, response_from_remote_client_for_local_client, + run_client, signal_to_pause, signal_to_resume, wait_for_pause, ClientCommand, + LocalClientListener, RemoteClientListener, UPClientFoo, +}; +use log::debug; +use std::sync::atomic::{AtomicU64, Ordering}; +use std::sync::Arc; +use std::time::Duration; +use up_rust::{UListener, UTransport}; +use up_streamer::{Route, UStreamer}; + +const DURATION_TO_RUN_CLIENTS: u128 = 3; +const SENT_MESSAGE_VEC_CAPACITY: usize = 10_000; + +#[async_std::test] +async fn single_local_two_remote_add_remove_rules() { + // using async_broadcast to simulate communication protocol + let (tx_1, rx_1) = broadcast(20000); + let (tx_2, rx_2) = broadcast(20000); + let (tx_3, rx_3) = broadcast(20000); + + let utransport_foo: Arc>> = Arc::new(Mutex::new(Box::new( + UPClientFoo::new("upclient_foo", rx_1.clone(), tx_1.clone()).await, + ))); + let utransport_bar_1: Arc>> = Arc::new(Mutex::new(Box::new( + UPClientFoo::new("upclient_bar_1", rx_2.clone(), tx_2.clone()).await, + ))); + let utransport_bar_2: Arc>> = Arc::new(Mutex::new(Box::new( + UPClientFoo::new("upclient_bar_2", rx_3.clone(), tx_3.clone()).await, + ))); + + // setting up streamer to bridge between "foo" and "bar" + let ustreamer = UStreamer::new("foo_bar_streamer", 3000); + + // setting up routes between authorities and protocols + let local_route = Route::new("local_route", local_authority(), utransport_foo.clone()); + let remote_route_a = Route::new( + "remote_route_a", + remote_authority_a(), + utransport_bar_1.clone(), + ); + let remote_route_b = Route::new( + "remote_route_b", + remote_authority_b(), + utransport_bar_2.clone(), + ); + + // adding local to remote_a routing + let add_forwarding_rule_res = ustreamer + .add_forwarding_rule(local_route.clone(), remote_route_a.clone()) + .await; + assert!(add_forwarding_rule_res.is_ok()); + + // adding remote_a to local routing + let add_forwarding_rule_res = ustreamer + .add_forwarding_rule(remote_route_a.clone(), local_route.clone()) + .await; + assert!(add_forwarding_rule_res.is_ok()); + + let local_client_listener = Arc::new(LocalClientListener::new()); + let remote_a_client_listener = Arc::new(RemoteClientListener::new()); + let remote_b_client_listener = Arc::new(RemoteClientListener::new()); + + let local_client_listener_trait_obj: Arc = local_client_listener.clone(); + let remote_a_client_listener_trait_obj: Arc = remote_a_client_listener.clone(); + let remote_b_client_listener_trait_obj: Arc = remote_b_client_listener.clone(); + + let all_signal_should_pause = Arc::new((Mutex::new(true), Condvar::new())); + let local_signal_has_paused = Arc::new((Mutex::new(false), Condvar::new())); + let remote_a_signal_has_paused = Arc::new((Mutex::new(false), Condvar::new())); + let remote_b_signal_has_paused = Arc::new((Mutex::new(false), Condvar::new())); + let local_command = Arc::new(Mutex::new(ClientCommand::ConnectedToStreamer(vec![ + true, false, + ]))); + let remote_a_command = Arc::new(Mutex::new(ClientCommand::ConnectedToStreamer(vec![true]))); + + let local_sends = Arc::new(AtomicU64::new(0)); + let remote_a_sends = Arc::new(AtomicU64::new(0)); + let remote_b_sends = Arc::new(AtomicU64::new(0)); + + // kicking off a "local_foo_client" and "remote_bar_client" in order to keep exercising + // the streamer periodically + let local_handle = run_client( + "local_foo_client".to_string(), + local_client_uuri(10), + remote_client_uuri(remote_authority_a(), 200), + local_client_listener_trait_obj, + tx_1.clone(), + rx_1.clone(), + vec![publish_from_local_client_for_remote_client(10)], + vec![ + notification_from_local_client_for_remote_client( + 10, + remote_client_uuri(remote_authority_a(), 200), + ), + notification_from_local_client_for_remote_client( + 10, + remote_client_uuri(remote_authority_b(), 200), + ), + ], + vec![ + request_from_local_client_for_remote_client( + 10, + remote_client_uuri(remote_authority_a(), 200), + ), + request_from_local_client_for_remote_client( + 10, + remote_client_uuri(remote_authority_b(), 200), + ), + ], + vec![ + response_from_local_client_for_remote_client( + 10, + remote_client_uuri(remote_authority_a(), 200), + ), + response_from_local_client_for_remote_client( + 10, + remote_client_uuri(remote_authority_b(), 200), + ), + ], + true, + all_signal_should_pause.clone(), + local_signal_has_paused.clone(), + local_command.clone(), + local_sends.clone(), + SENT_MESSAGE_VEC_CAPACITY, + ) + .await; + let remote_a_handle = run_client( + "remote_a_bar_client".to_string(), + remote_client_uuri(remote_authority_a(), 200), + local_client_uuri(10), + remote_a_client_listener_trait_obj, + tx_2.clone(), + rx_2.clone(), + vec![publish_from_remote_client_for_local_client( + remote_client_uuri(remote_authority_a(), 200), + )], + vec![notification_from_remote_client_for_local_client( + remote_client_uuri(remote_authority_a(), 200), + 10, + )], + vec![request_from_remote_client_for_local_client( + remote_client_uuri(remote_authority_a(), 200), + 10, + )], + vec![response_from_remote_client_for_local_client( + remote_client_uuri(remote_authority_a(), 200), + 10, + )], + true, + all_signal_should_pause.clone(), + remote_a_signal_has_paused.clone(), + remote_a_command.clone(), + remote_a_sends.clone(), + SENT_MESSAGE_VEC_CAPACITY, + ) + .await; + + debug!("waiting for clients start"); + + let local_paused = wait_for_pause(local_signal_has_paused.clone()); + let remote_a_paused = wait_for_pause(remote_a_signal_has_paused.clone()); + debug!("called wait_for_pause"); + + join(local_paused, remote_a_paused).await; + debug!("passed join on local_paused and remote_paused"); + + reset_pause(local_signal_has_paused.clone()).await; + debug!("after local has paused set to false"); + reset_pause(remote_a_signal_has_paused.clone()).await; + debug!("after remote_a has paused set to false"); + + // Now signal clients to resume + signal_to_resume(all_signal_should_pause.clone()).await; + + debug!("signalled to resume"); + + task::sleep(Duration::from_millis(DURATION_TO_RUN_CLIENTS as u64)).await; + + { + let mut local_command = local_command.lock().await; + *local_command = ClientCommand::ConnectedToStreamer(vec![true, true]); + } + { + let mut remote_a_command = remote_a_command.lock().await; + *remote_a_command = ClientCommand::NoOp; + } + + debug!("set NoOp command just to have local and remote_a pause"); + + signal_to_pause(all_signal_should_pause.clone()).await; + + debug!("signalled to pause after NoOp"); + + let local_paused = wait_for_pause(local_signal_has_paused.clone()); + let remote_a_paused = wait_for_pause(remote_a_signal_has_paused.clone()); + join(local_paused, remote_a_paused).await; + + debug!("finished waiting after signalling NoOp"); + + let remote_b_command = Arc::new(Mutex::new(ClientCommand::ConnectedToStreamer(vec![true]))); + + let remote_b_handle = run_client( + "remote_b_bar_client".to_string(), + remote_client_uuri(remote_authority_b(), 200), + local_client_uuri(10), + remote_b_client_listener_trait_obj, + tx_3.clone(), + rx_3.clone(), + vec![publish_from_remote_client_for_local_client( + remote_client_uuri(remote_authority_b(), 200), + )], + vec![notification_from_remote_client_for_local_client( + remote_client_uuri(remote_authority_b(), 200), + 10, + )], + vec![request_from_remote_client_for_local_client( + remote_client_uuri(remote_authority_b(), 200), + 10, + )], + vec![response_from_remote_client_for_local_client( + remote_client_uuri(remote_authority_b(), 200), + 10, + )], + true, + all_signal_should_pause.clone(), + remote_b_signal_has_paused.clone(), + remote_b_command.clone(), + remote_b_sends.clone(), + SENT_MESSAGE_VEC_CAPACITY, + ) + .await; + + debug!("ran run_client for remote_b"); + + // adding local to remote_b routing + let add_forwarding_rule_res = ustreamer + .add_forwarding_rule(local_route.clone(), remote_route_b.clone()) + .await; + assert!(add_forwarding_rule_res.is_ok()); + + // adding remote_b to local routing + let add_forwarding_rule_res = ustreamer + .add_forwarding_rule(remote_route_b.clone(), local_route.clone()) + .await; + assert!(add_forwarding_rule_res.is_ok()); + + debug!("added forwarding rules for remote_b <-> local"); + + wait_for_pause(remote_b_signal_has_paused.clone()).await; + + debug!("remote_b signalled it paused"); + + signal_to_resume(all_signal_should_pause.clone()).await; + + debug!("signalled local, remote_a, remote_b to resume"); + + task::sleep(Duration::from_millis(DURATION_TO_RUN_CLIENTS as u64)).await; + + debug!("after running local, remote_a, remote_b"); + + { + let mut local_command = local_command.lock().await; + *local_command = ClientCommand::ConnectedToStreamer(vec![false, true]); + } + { + let mut remote_a_command = remote_a_command.lock().await; + *remote_a_command = ClientCommand::Stop; + } + { + let mut remote_b_command = remote_b_command.lock().await; + *remote_b_command = ClientCommand::NoOp; + } + + debug!( + "set remote_a to Stop, local to disconnect remote_a for counting sends, remote_b is NoOp" + ); + + signal_to_pause(all_signal_should_pause.clone()).await; + + debug!("signalled all to pause to recognize commands"); + + let remote_a_client_sent_messages = remote_a_handle + .join() + .expect("Unable to join on remote_a_handle"); + + debug!("joined on closing of remote_a"); + + let local_paused = wait_for_pause(local_signal_has_paused.clone()); + let remote_b_paused = wait_for_pause(remote_b_signal_has_paused.clone()); + debug!("called wait_for_pause for local and remote_b"); + + join(local_paused, remote_b_paused).await; + debug!("joined on local_paused and remote_b_paused"); + + // deleting local to remote_a routing + let delete_forwarding_rule_res = ustreamer + .delete_forwarding_rule(local_route.clone(), remote_route_a.clone()) + .await; + assert!(delete_forwarding_rule_res.is_ok()); + + // deleting remote_a to local routing + let delete_forwarding_rule_res = ustreamer + .delete_forwarding_rule(remote_route_a.clone(), local_route.clone()) + .await; + assert!(delete_forwarding_rule_res.is_ok()); + + debug!("deleting forwarding rules local <-> remote_a"); + + signal_to_resume(all_signal_should_pause.clone()).await; + + debug!("signalled all to resume: local & remote_b"); + + task::sleep(Duration::from_millis(DURATION_TO_RUN_CLIENTS as u64)).await; + + { + let mut local_command = local_command.lock().await; + *local_command = ClientCommand::Stop; + } + { + let mut remote_b_command = remote_b_command.lock().await; + *remote_b_command = ClientCommand::Stop; + } + + debug!("after loading Stop command for local, remote_a, remote_b"); + + signal_to_pause(all_signal_should_pause.clone()).await; + + debug!("signalled local, remote_b to pause and gave them Stop command"); + + let local_client_sent_messages = local_handle.join().expect("Unable to join on local_handle"); + let remote_b_client_sent_messages = remote_b_handle + .join() + .expect("Unable to join on remote_b_handle"); + + let local_client_sent_messages_num = local_client_sent_messages.len(); + let remote_a_client_sent_messages_num = remote_a_client_sent_messages.len(); + let remote_b_client_sent_messages_num = remote_b_client_sent_messages.len(); + + println!("local_client_sent_messages_num: {local_client_sent_messages_num}"); + println!("remote_a_client_sent_messages_num: {remote_a_client_sent_messages_num}"); + println!("remote_b_client_sent_messages_num: {remote_b_client_sent_messages_num}"); + + let number_of_messages_received_client = local_client_sent_messages_num + + remote_a_client_sent_messages_num + + remote_b_client_sent_messages_num; + + println!( + "total messages received at client: {}", + number_of_messages_received_client + ); + + let local_client_listener_message_count = local_client_listener + .retrieve_message_store() + .lock() + .await + .len(); + let remote_a_client_listener_message_count = remote_a_client_listener + .retrieve_message_store() + .lock() + .await + .len(); + let remote_b_client_listener_message_count = remote_b_client_listener + .retrieve_message_store() + .lock() + .await + .len(); + + println!( + "local_client_listener_message_count: {}", + local_client_listener_message_count + ); + println!( + "remote_a_client_listener_message_count: {}", + remote_a_client_listener_message_count + ); + println!( + "remote_b_client_listener_message_count: {}", + remote_b_client_listener_message_count + ); + + let number_of_messages_received_listeners = local_client_listener_message_count + + remote_a_client_listener_message_count + + remote_b_client_listener_message_count; + + println!( + "number_of_messages_received_listeners: {}", + number_of_messages_received_listeners + ); + + let local_send_num = local_sends.load(Ordering::SeqCst); + let remote_a_send_num = remote_a_sends.load(Ordering::SeqCst); + let remote_b_send_num = remote_b_sends.load(Ordering::SeqCst); + + println!("local_send_num: {}", local_send_num); + println!("remote_a_send_num: {}", remote_a_send_num); + println!("remote_b_send_num: {}", remote_b_send_num); + + let number_of_sent_messages = local_sends.load(Ordering::SeqCst) + + remote_a_sends.load(Ordering::SeqCst) + + remote_b_sends.load(Ordering::SeqCst); + println!("total messages sent: {}", number_of_sent_messages); + + let percentage_slack = 0.000; + check_send_receive_message_discrepancy( + number_of_sent_messages, + number_of_messages_received_listeners as u64, + percentage_slack, + ) + .await; + + println!("check local message ordering:"); + check_messages_in_order(local_client_listener.retrieve_message_store()).await; + println!("check remote_a message ordering:"); + check_messages_in_order(remote_a_client_listener.retrieve_message_store()).await; + println!("check remote_b message ordering:"); + check_messages_in_order(remote_b_client_listener.retrieve_message_store()).await; + + debug!("All clients finished."); +} diff --git a/up-streamer/tests/single_local_two_remote_authorities_different_remote_transport.rs b/up-streamer/tests/single_local_two_remote_authorities_different_remote_transport.rs new file mode 100644 index 00000000..2e78ede3 --- /dev/null +++ b/up-streamer/tests/single_local_two_remote_authorities_different_remote_transport.rs @@ -0,0 +1,357 @@ +/******************************************************************************** + * Copyright (c) 2024 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ + +use async_broadcast::broadcast; +use async_std::sync::{Condvar, Mutex}; +use async_std::task; +use futures::future::join; +use integration_test_utils::{ + check_messages_in_order, check_send_receive_message_discrepancy, local_authority, + local_client_uuri, notification_from_local_client_for_remote_client, + notification_from_remote_client_for_local_client, publish_from_local_client_for_remote_client, + publish_from_remote_client_for_local_client, remote_authority_a, remote_authority_b, + remote_client_uuri, request_from_local_client_for_remote_client, + request_from_remote_client_for_local_client, reset_pause, + response_from_local_client_for_remote_client, response_from_remote_client_for_local_client, + run_client, signal_to_pause, signal_to_resume, wait_for_pause, ClientCommand, + LocalClientListener, RemoteClientListener, UPClientFoo, +}; +use log::debug; +use std::sync::atomic::{AtomicU64, Ordering}; +use std::sync::Arc; +use std::time::Duration; +use up_rust::{UListener, UTransport}; +use up_streamer::{Route, UStreamer}; + +const DURATION_TO_RUN_CLIENTS: u128 = 1_000; +const SENT_MESSAGE_VEC_CAPACITY: usize = 10_000; + +#[async_std::test] +async fn single_local_two_remote_authorities_different_remote_transport() { + // using async_broadcast to simulate communication protocol + let (tx_1, rx_1) = broadcast(20000); + let (tx_2, rx_2) = broadcast(20000); + let (tx_3, rx_3) = broadcast(20000); + + let utransport_foo: Arc>> = Arc::new(Mutex::new(Box::new( + UPClientFoo::new("upclient_foo", rx_1.clone(), tx_1.clone()).await, + ))); + let utransport_bar_1: Arc>> = Arc::new(Mutex::new(Box::new( + UPClientFoo::new("upclient_bar_1", rx_2.clone(), tx_2.clone()).await, + ))); + let utransport_bar_2: Arc>> = Arc::new(Mutex::new(Box::new( + UPClientFoo::new("upclient_bar_2", rx_3.clone(), tx_3.clone()).await, + ))); + + // setting up streamer to bridge between "foo" and "bar" + let ustreamer = UStreamer::new("foo_bar_streamer", 3000); + + // setting up routes between authorities and protocols + let local_route = Route::new("local_route", local_authority(), utransport_foo.clone()); + let remote_route_a = Route::new( + "remote_route_a", + remote_authority_a(), + utransport_bar_1.clone(), + ); + let remote_route_b = Route::new( + "remote_route_b", + remote_authority_b(), + utransport_bar_2.clone(), + ); + + // adding local to remote_a routing + let add_forwarding_rule_res = ustreamer + .add_forwarding_rule(local_route.clone(), remote_route_a.clone()) + .await; + assert!(add_forwarding_rule_res.is_ok()); + + // adding remote_a to local routing + let add_forwarding_rule_res = ustreamer + .add_forwarding_rule(remote_route_a.clone(), local_route.clone()) + .await; + assert!(add_forwarding_rule_res.is_ok()); + + // adding local to remote_b routing + let add_forwarding_rule_res = ustreamer + .add_forwarding_rule(local_route.clone(), remote_route_b.clone()) + .await; + assert!(add_forwarding_rule_res.is_ok()); + + // adding remote_b to local routing + let add_forwarding_rule_res = ustreamer + .add_forwarding_rule(remote_route_b.clone(), local_route.clone()) + .await; + assert!(add_forwarding_rule_res.is_ok()); + + let local_client_listener = Arc::new(LocalClientListener::new()); + let remote_a_client_listener = Arc::new(RemoteClientListener::new()); + let remote_b_client_listener = Arc::new(RemoteClientListener::new()); + + let local_client_listener_trait_obj: Arc = local_client_listener.clone(); + let remote_a_client_listener_trait_obj: Arc = remote_a_client_listener.clone(); + let remote_b_client_listener_trait_obj: Arc = remote_b_client_listener.clone(); + + let all_signal_should_pause = Arc::new((Mutex::new(true), Condvar::new())); + let local_signal_has_paused = Arc::new((Mutex::new(false), Condvar::new())); + let remote_a_signal_has_paused = Arc::new((Mutex::new(false), Condvar::new())); + let remote_b_signal_has_paused = Arc::new((Mutex::new(false), Condvar::new())); + let local_command = Arc::new(Mutex::new(ClientCommand::ConnectedToStreamer(vec![ + true, true, + ]))); + let remote_a_command = Arc::new(Mutex::new(ClientCommand::ConnectedToStreamer(vec![true]))); + let remote_b_command = Arc::new(Mutex::new(ClientCommand::ConnectedToStreamer(vec![true]))); + + let local_sends = Arc::new(AtomicU64::new(0)); + let remote_a_sends = Arc::new(AtomicU64::new(0)); + let remote_b_sends = Arc::new(AtomicU64::new(0)); + + // kicking off a "local_foo_client" and "remote_bar_client" in order to keep exercising + // the streamer periodically + let local_handle = run_client( + "local_foo_client".to_string(), + local_client_uuri(10), + remote_client_uuri(remote_authority_a(), 200), + local_client_listener_trait_obj, + tx_1.clone(), + rx_1.clone(), + vec![publish_from_local_client_for_remote_client(10)], + vec![ + notification_from_local_client_for_remote_client( + 10, + remote_client_uuri(remote_authority_a(), 200), + ), + notification_from_local_client_for_remote_client( + 10, + remote_client_uuri(remote_authority_b(), 200), + ), + ], + vec![ + request_from_local_client_for_remote_client( + 10, + remote_client_uuri(remote_authority_a(), 200), + ), + request_from_local_client_for_remote_client( + 10, + remote_client_uuri(remote_authority_b(), 200), + ), + ], + vec![ + response_from_local_client_for_remote_client( + 10, + remote_client_uuri(remote_authority_a(), 200), + ), + response_from_local_client_for_remote_client( + 10, + remote_client_uuri(remote_authority_b(), 200), + ), + ], + true, + all_signal_should_pause.clone(), + local_signal_has_paused.clone(), + local_command.clone(), + local_sends.clone(), + SENT_MESSAGE_VEC_CAPACITY, + ) + .await; + let remote_a_handle = run_client( + "remote_a_bar_client".to_string(), + remote_client_uuri(remote_authority_a(), 200), + local_client_uuri(10), + remote_a_client_listener_trait_obj, + tx_2.clone(), + rx_2.clone(), + vec![publish_from_remote_client_for_local_client( + remote_client_uuri(remote_authority_a(), 200), + )], + vec![notification_from_remote_client_for_local_client( + remote_client_uuri(remote_authority_a(), 200), + 10, + )], + vec![request_from_remote_client_for_local_client( + remote_client_uuri(remote_authority_a(), 200), + 10, + )], + vec![response_from_remote_client_for_local_client( + remote_client_uuri(remote_authority_a(), 200), + 10, + )], + true, + all_signal_should_pause.clone(), + remote_a_signal_has_paused.clone(), + remote_a_command.clone(), + remote_a_sends.clone(), + SENT_MESSAGE_VEC_CAPACITY, + ) + .await; + let remote_b_handle = run_client( + "remote_b_bar_client".to_string(), + remote_client_uuri(remote_authority_b(), 200), + local_client_uuri(10), + remote_b_client_listener_trait_obj, + tx_3.clone(), + rx_3.clone(), + vec![publish_from_remote_client_for_local_client( + remote_client_uuri(remote_authority_b(), 200), + )], + vec![notification_from_remote_client_for_local_client( + remote_client_uuri(remote_authority_b(), 200), + 10, + )], + vec![request_from_remote_client_for_local_client( + remote_client_uuri(remote_authority_b(), 200), + 10, + )], + vec![response_from_remote_client_for_local_client( + remote_client_uuri(remote_authority_b(), 200), + 10, + )], + true, + all_signal_should_pause.clone(), + remote_b_signal_has_paused.clone(), + remote_b_command.clone(), + remote_b_sends.clone(), + SENT_MESSAGE_VEC_CAPACITY, + ) + .await; + + debug!("waiting for clients start"); + + let local_paused = wait_for_pause(local_signal_has_paused.clone()); + let remote_a_paused = wait_for_pause(remote_a_signal_has_paused.clone()); + let remote_b_paused = wait_for_pause(remote_b_signal_has_paused.clone()); + debug!("called wait_for_pause"); + + join(join(local_paused, remote_a_paused), remote_b_paused).await; + debug!("passed join on local_paused and remote_paused"); + + reset_pause(local_signal_has_paused.clone()).await; + debug!("after local has paused set to false"); + reset_pause(remote_a_signal_has_paused.clone()).await; + debug!("after remote_a has paused set to false"); + reset_pause(remote_b_signal_has_paused.clone()).await; + debug!("after remote_b has paused set to false"); + + // Now signal both clients to resume + signal_to_resume(all_signal_should_pause.clone()).await; + + task::sleep(Duration::from_millis(DURATION_TO_RUN_CLIENTS as u64)).await; + + { + let mut local_command = local_command.lock().await; + *local_command = ClientCommand::Stop; + } + { + let mut remote_a_command = remote_a_command.lock().await; + *remote_a_command = ClientCommand::Stop; + } + { + let mut remote_b_command = remote_b_command.lock().await; + *remote_b_command = ClientCommand::Stop; + } + signal_to_pause(all_signal_should_pause).await; + + debug!("after signal_to_resume"); + + let local_client_sent_messages = local_handle.join().expect("Unable to join on local_handle"); + let remote_a_client_sent_messages = remote_a_handle + .join() + .expect("Unable to join on remote_a_handle"); + let remote_b_client_sent_messages = remote_b_handle + .join() + .expect("Unable to join on remote_b_handle"); + + let local_client_sent_messages_num = local_client_sent_messages.len(); + let remote_a_client_sent_messages_num = remote_a_client_sent_messages.len(); + let remote_b_client_sent_messages_num = remote_b_client_sent_messages.len(); + + println!("local_client_sent_messages_num: {local_client_sent_messages_num}"); + println!("remote_a_client_sent_messages_num: {remote_a_client_sent_messages_num}"); + println!("remote_b_client_sent_messages_num: {remote_b_client_sent_messages_num}"); + + let number_of_messages_received_client = local_client_sent_messages_num + + remote_a_client_sent_messages_num + + remote_b_client_sent_messages_num; + + println!( + "total messages received at client: {}", + number_of_messages_received_client + ); + + let local_client_listener_message_count = local_client_listener + .retrieve_message_store() + .lock() + .await + .len(); + let remote_a_client_listener_message_count = remote_a_client_listener + .retrieve_message_store() + .lock() + .await + .len(); + let remote_b_client_listener_message_count = remote_b_client_listener + .retrieve_message_store() + .lock() + .await + .len(); + + println!( + "local_client_listener_message_count: {}", + local_client_listener_message_count + ); + println!( + "remote_a_client_listener_message_count: {}", + remote_a_client_listener_message_count + ); + println!( + "remote_b_client_listener_message_count: {}", + remote_b_client_listener_message_count + ); + + let number_of_messages_received_listeners = local_client_listener_message_count + + remote_a_client_listener_message_count + + remote_b_client_listener_message_count; + + println!( + "number_of_messages_received_listeners: {}", + number_of_messages_received_listeners + ); + + let local_send_num = local_sends.load(Ordering::SeqCst); + let remote_a_send_num = remote_a_sends.load(Ordering::SeqCst); + let remote_b_send_num = remote_b_sends.load(Ordering::SeqCst); + + println!("local_send_num: {}", local_send_num); + println!("remote_a_send_num: {}", remote_a_send_num); + println!("remote_b_send_num: {}", remote_b_send_num); + + let number_of_sent_messages = local_sends.load(Ordering::SeqCst) + + remote_a_sends.load(Ordering::SeqCst) + + remote_b_sends.load(Ordering::SeqCst); + println!("total messages sent: {}", number_of_sent_messages); + + let percentage_slack = 0.000; + check_send_receive_message_discrepancy( + number_of_sent_messages, + number_of_messages_received_listeners as u64, + percentage_slack, + ) + .await; + + println!("check local message ordering:"); + check_messages_in_order(local_client_listener.retrieve_message_store()).await; + println!("check remote_a message ordering:"); + check_messages_in_order(remote_a_client_listener.retrieve_message_store()).await; + println!("check remote_b message ordering:"); + check_messages_in_order(remote_b_client_listener.retrieve_message_store()).await; + + debug!("All clients finished."); +} diff --git a/up-streamer/tests/single_local_two_remote_authorities_same_remote_transport.rs b/up-streamer/tests/single_local_two_remote_authorities_same_remote_transport.rs new file mode 100644 index 00000000..e88da68f --- /dev/null +++ b/up-streamer/tests/single_local_two_remote_authorities_same_remote_transport.rs @@ -0,0 +1,357 @@ +/******************************************************************************** + * Copyright (c) 2024 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ + +use async_broadcast::broadcast; +use async_std::sync::{Condvar, Mutex}; +use async_std::task; +use futures::future::join; +use integration_test_utils::{ + check_messages_in_order, check_send_receive_message_discrepancy, local_authority, + local_client_uuri, notification_from_local_client_for_remote_client, + notification_from_remote_client_for_local_client, publish_from_local_client_for_remote_client, + publish_from_remote_client_for_local_client, remote_authority_a, remote_authority_b, + remote_client_uuri, request_from_local_client_for_remote_client, + request_from_remote_client_for_local_client, reset_pause, + response_from_local_client_for_remote_client, response_from_remote_client_for_local_client, + run_client, signal_to_pause, signal_to_resume, wait_for_pause, ClientCommand, + LocalClientListener, RemoteClientListener, UPClientFoo, +}; +use log::debug; +use std::sync::atomic::{AtomicU64, Ordering}; +use std::sync::Arc; +use std::time::Duration; +use up_rust::{UListener, UTransport}; +use up_streamer::{Route, UStreamer}; + +const DURATION_TO_RUN_CLIENTS: u128 = 1000; +const SENT_MESSAGE_VEC_CAPACITY: usize = 10_000; + +#[async_std::test] +async fn single_local_two_remote_authorities_same_remote_transport() { + // using async_broadcast to simulate communication protocol + let (tx_1, rx_1) = broadcast(20000); + let (tx_2, rx_2) = broadcast(20000); + + let utransport_foo: Arc>> = Arc::new(Mutex::new(Box::new( + UPClientFoo::new("upclient_foo", rx_1.clone(), tx_1.clone()).await, + ))); + let utransport_bar: Arc>> = Arc::new(Mutex::new(Box::new( + UPClientFoo::new("upclient_bar", rx_2.clone(), tx_2.clone()).await, + ))); + + // setting up streamer to bridge between "foo" and "bar" + let ustreamer = UStreamer::new("foo_bar_streamer", 3000); + + // setting up routes between authorities and protocols + let local_route = Route::new("local_route", local_authority(), utransport_foo.clone()); + let remote_route_a = Route::new( + "remote_route_a", + remote_authority_a(), + utransport_bar.clone(), + ); + let remote_route_b = Route::new( + "remote_route_b", + remote_authority_b(), + utransport_bar.clone(), + ); + + // adding local to remote_a routing + let add_forwarding_rule_res = ustreamer + .add_forwarding_rule(local_route.clone(), remote_route_a.clone()) + .await; + assert!(add_forwarding_rule_res.is_ok()); + + // adding remote_a to local routing + let add_forwarding_rule_res = ustreamer + .add_forwarding_rule(remote_route_a.clone(), local_route.clone()) + .await; + assert!(add_forwarding_rule_res.is_ok()); + + // adding local to remote_b routing + let add_forwarding_rule_res = ustreamer + .add_forwarding_rule(local_route.clone(), remote_route_b.clone()) + .await; + assert!(add_forwarding_rule_res.is_ok()); + + // adding remote_b to local routing + let add_forwarding_rule_res = ustreamer + .add_forwarding_rule(remote_route_b.clone(), local_route.clone()) + .await; + assert!(add_forwarding_rule_res.is_ok()); + + let local_client_listener = Arc::new(LocalClientListener::new()); + let remote_a_client_listener = Arc::new(RemoteClientListener::new()); + let remote_b_client_listener = Arc::new(RemoteClientListener::new()); + + let local_client_listener_trait_obj: Arc = local_client_listener.clone(); + let remote_a_client_listener_trait_obj: Arc = remote_a_client_listener.clone(); + let remote_b_client_listener_trait_obj: Arc = remote_b_client_listener.clone(); + + let all_signal_should_pause = Arc::new((Mutex::new(true), Condvar::new())); + let local_signal_has_paused = Arc::new((Mutex::new(false), Condvar::new())); + let remote_a_signal_has_paused = Arc::new((Mutex::new(false), Condvar::new())); + let remote_b_signal_has_paused = Arc::new((Mutex::new(false), Condvar::new())); + let local_command = Arc::new(Mutex::new(ClientCommand::ConnectedToStreamer(vec![ + true, true, + ]))); + let remote_a_command = Arc::new(Mutex::new(ClientCommand::ConnectedToStreamer(vec![true]))); + let remote_b_command = Arc::new(Mutex::new(ClientCommand::ConnectedToStreamer(vec![ + true, true, + ]))); + + let local_sends = Arc::new(AtomicU64::new(0)); + let remote_a_sends = Arc::new(AtomicU64::new(0)); + let remote_b_sends = Arc::new(AtomicU64::new(0)); + + // kicking off a "local_foo_client" and "remote_bar_client" in order to keep exercising + // the streamer periodically + let local_handle = run_client( + "local_foo_client".to_string(), + local_client_uuri(10), + remote_client_uuri(remote_authority_a(), 200), + local_client_listener_trait_obj, + tx_1.clone(), + rx_1.clone(), + vec![publish_from_local_client_for_remote_client(10)], + vec![ + notification_from_local_client_for_remote_client( + 10, + remote_client_uuri(remote_authority_a(), 200), + ), + notification_from_local_client_for_remote_client( + 10, + remote_client_uuri(remote_authority_b(), 200), + ), + ], + vec![ + request_from_local_client_for_remote_client( + 10, + remote_client_uuri(remote_authority_a(), 200), + ), + request_from_local_client_for_remote_client( + 10, + remote_client_uuri(remote_authority_b(), 200), + ), + ], + vec![ + response_from_local_client_for_remote_client( + 10, + remote_client_uuri(remote_authority_a(), 200), + ), + response_from_local_client_for_remote_client( + 10, + remote_client_uuri(remote_authority_b(), 200), + ), + ], + true, + all_signal_should_pause.clone(), + local_signal_has_paused.clone(), + local_command.clone(), + local_sends.clone(), + SENT_MESSAGE_VEC_CAPACITY, + ) + .await; + let remote_a_handle = run_client( + "remote_a_bar_client".to_string(), + remote_client_uuri(remote_authority_a(), 200), + local_client_uuri(10), + remote_a_client_listener_trait_obj, + tx_2.clone(), + rx_2.clone(), + vec![publish_from_remote_client_for_local_client( + remote_client_uuri(remote_authority_a(), 200), + )], + vec![notification_from_remote_client_for_local_client( + remote_client_uuri(remote_authority_a(), 200), + 10, + )], + vec![request_from_remote_client_for_local_client( + remote_client_uuri(remote_authority_a(), 200), + 10, + )], + vec![response_from_remote_client_for_local_client( + remote_client_uuri(remote_authority_a(), 200), + 10, + )], + true, + all_signal_should_pause.clone(), + remote_a_signal_has_paused.clone(), + remote_a_command.clone(), + remote_a_sends.clone(), + SENT_MESSAGE_VEC_CAPACITY, + ) + .await; + let remote_b_handle = run_client( + "remote_b_bar_client".to_string(), + remote_client_uuri(remote_authority_b(), 200), + local_client_uuri(10), + remote_b_client_listener_trait_obj, + tx_2.clone(), + rx_2.clone(), + vec![publish_from_remote_client_for_local_client( + remote_client_uuri(remote_authority_b(), 200), + )], + vec![notification_from_remote_client_for_local_client( + remote_client_uuri(remote_authority_b(), 200), + 10, + )], + vec![request_from_remote_client_for_local_client( + remote_client_uuri(remote_authority_b(), 200), + 10, + )], + vec![response_from_remote_client_for_local_client( + remote_client_uuri(remote_authority_b(), 200), + 10, + )], + true, + all_signal_should_pause.clone(), + remote_b_signal_has_paused.clone(), + remote_b_command.clone(), + remote_b_sends.clone(), + SENT_MESSAGE_VEC_CAPACITY, + ) + .await; + + debug!("waiting for let clients start"); + + debug!("waiting for clients start"); + + let local_paused = wait_for_pause(local_signal_has_paused.clone()); + let remote_a_paused = wait_for_pause(remote_a_signal_has_paused.clone()); + let remote_b_paused = wait_for_pause(remote_b_signal_has_paused.clone()); + debug!("called wait_for_pause"); + + join(join(local_paused, remote_a_paused), remote_b_paused).await; + debug!("passed join on local_paused and remote_paused"); + + reset_pause(local_signal_has_paused.clone()).await; + debug!("after local has paused set to false"); + reset_pause(remote_a_signal_has_paused.clone()).await; + debug!("after remote_a has paused set to false"); + reset_pause(remote_b_signal_has_paused.clone()).await; + debug!("after remote_b has paused set to false"); + + // Now signal both clients to resume + signal_to_resume(all_signal_should_pause.clone()).await; + + task::sleep(Duration::from_millis(DURATION_TO_RUN_CLIENTS as u64)).await; + + { + let mut local_command = local_command.lock().await; + *local_command = ClientCommand::Stop; + } + { + let mut remote_a_command = remote_a_command.lock().await; + *remote_a_command = ClientCommand::Stop; + } + { + let mut remote_b_command = remote_b_command.lock().await; + *remote_b_command = ClientCommand::Stop; + } + signal_to_pause(all_signal_should_pause).await; + + debug!("after signal_to_resume"); + + let local_client_sent_messages = local_handle.join().expect("Unable to join on local_handle"); + let remote_a_client_sent_messages = remote_a_handle + .join() + .expect("Unable to join on remote_a_handle"); + let remote_b_client_sent_messages = remote_b_handle + .join() + .expect("Unable to join on remote_b_handle"); + + let local_client_sent_messages_num = local_client_sent_messages.len(); + let remote_a_client_sent_messages_num = remote_a_client_sent_messages.len(); + let remote_b_client_sent_messages_num = remote_b_client_sent_messages.len(); + + println!("local_client_sent_messages_num: {local_client_sent_messages_num}"); + println!("remote_a_client_sent_messages_num: {remote_a_client_sent_messages_num}"); + println!("remote_b_client_sent_messages_num: {remote_b_client_sent_messages_num}"); + + let number_of_messages_received_client = local_client_sent_messages_num + + remote_a_client_sent_messages_num + + remote_b_client_sent_messages_num; + + println!( + "total messages received at client: {}", + number_of_messages_received_client + ); + + let local_client_listener_message_count = local_client_listener + .retrieve_message_store() + .lock() + .await + .len(); + let remote_a_client_listener_message_count = remote_a_client_listener + .retrieve_message_store() + .lock() + .await + .len(); + let remote_b_client_listener_message_count = remote_b_client_listener + .retrieve_message_store() + .lock() + .await + .len(); + + println!( + "local_client_listener_message_count: {}", + local_client_listener_message_count + ); + println!( + "remote_a_client_listener_message_count: {}", + remote_a_client_listener_message_count + ); + println!( + "remote_b_client_listener_message_count: {}", + remote_b_client_listener_message_count + ); + + let number_of_messages_received_listeners = local_client_listener_message_count + + remote_a_client_listener_message_count + + remote_b_client_listener_message_count; + + println!( + "number_of_messages_received_listeners: {}", + number_of_messages_received_listeners + ); + + let local_send_num = local_sends.load(Ordering::SeqCst); + let remote_a_send_num = remote_a_sends.load(Ordering::SeqCst); + let remote_b_send_num = remote_b_sends.load(Ordering::SeqCst); + + println!("local_send_num: {}", local_send_num); + println!("remote_a_send_num: {}", remote_a_send_num); + println!("remote_b_send_num: {}", remote_b_send_num); + + let number_of_sent_messages = local_sends.load(Ordering::SeqCst) + + remote_a_sends.load(Ordering::SeqCst) + + remote_b_sends.load(Ordering::SeqCst); + println!("total messages sent: {}", number_of_sent_messages); + + let percentage_slack = 0.000; + check_send_receive_message_discrepancy( + number_of_sent_messages, + number_of_messages_received_listeners as u64, + percentage_slack, + ) + .await; + + println!("check local message ordering:"); + check_messages_in_order(local_client_listener.retrieve_message_store()).await; + println!("check remote_a message ordering:"); + check_messages_in_order(remote_a_client_listener.retrieve_message_store()).await; + println!("check remote_b message ordering:"); + check_messages_in_order(remote_b_client_listener.retrieve_message_store()).await; + + debug!("All clients finished."); +}