diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3f76b061..c1e38c68 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -15,7 +15,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v6 - name: Install Rust ${{ matrix.rust }} uses: dtolnay/rust-toolchain@master with: @@ -39,7 +39,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v6 - name: Install Rust ${{ matrix.rust }} uses: dtolnay/rust-toolchain@stable - name: Build the workspace with the features @@ -57,7 +57,7 @@ jobs: runs-on: ${{ matrix.os }} steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v6 - name: Install Rust ${{ matrix.rust }} uses: dtolnay/rust-toolchain@master with: @@ -69,7 +69,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v6 - name: Install Rust uses: dtolnay/rust-toolchain@stable - name: Install doxygen 1.9.5 @@ -94,7 +94,7 @@ jobs: cp -a ffi/bindings/java/rodbus/target/apidocs ~/doc/java rm ffi/bindings/c/generated/logo.png ffi/bindings/c/generated/doxygen-awesome.css - name: Upload documentation - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v6 with: name: doc-api path: ~/doc @@ -111,7 +111,7 @@ jobs: test: false steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v6 - name: Install Rust uses: dtolnay/rust-toolchain@stable - name: Create the FFI modules dir @@ -127,7 +127,7 @@ jobs: Copy-Item -Path ./target/${{ matrix.target }}/release/rodbus_ffi.dll.lib -Destination ffi-modules/${{ matrix.target }} Copy-Item -Path ./target/${{ matrix.target }}/release/rodbus_ffi_java.dll -Destination ffi-modules/${{ matrix.target }} - name: Upload compiled FFI modules - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v6 with: name: ffi-modules-${{ matrix.target }} path: ffi-modules @@ -153,7 +153,7 @@ jobs: runs-on: ${{ matrix.runner }} steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v6 - name: Install Rust uses: dtolnay/rust-toolchain@stable - name: Create FFI modules dir @@ -167,7 +167,7 @@ jobs: cp ./target/release/librodbus_ffi.dylib ./ffi-modules/${{ matrix.target }} cp ./target/release/librodbus_ffi_java.dylib ./ffi-modules/${{ matrix.target }} - name: Upload compiled FFI modules - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v6 with: name: ffi-modules-${{ matrix.target }} path: ffi-modules @@ -205,7 +205,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v6 - name: Install Rust uses: dtolnay/rust-toolchain@stable - name: Install Rust Cross @@ -221,7 +221,7 @@ jobs: cp ./target/${{ matrix.cases.target }}/release/librodbus_ffi.so ./ffi-modules/${{ matrix.cases.target }} cp ./target/${{ matrix.cases.target }}/release/librodbus_ffi_java.so ./ffi-modules/${{ matrix.cases.target }} - name: Upload compiled FFI modules - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v6 with: name: ffi-modules-${{ matrix.cases.target }} path: ffi-modules @@ -229,7 +229,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v6 - name: Install dependencies working-directory: guide run: npm ci @@ -240,7 +240,7 @@ jobs: mkdir -p ~/doc/guide mv build/* ~/doc/guide - name: Upload guide - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v6 with: name: doc-guide path: ~/doc @@ -256,9 +256,9 @@ jobs: - name: Install custom allow-list tool run: cargo install --git https://github.com/stepfunc/bom-tools.git - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v6 - name: Download compiled FFI - uses: actions/download-artifact@v4 + uses: actions/download-artifact@v7 with: pattern: ffi-modules-* path: ffi-modules @@ -283,22 +283,22 @@ jobs: - name: Package Java bindings run: cargo run --bin rodbus-bindings -- --java --package ./ffi-modules --options ./packaging.json -f third-party-licenses-java.txt - name: Upload C/C++ bindings - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v6 with: name: c-bindings path: ffi/bindings/c/generated/* - name: Upload .NET bindings - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v6 with: name: dotnet-bindings path: ffi/bindings/dotnet/nupkg/* - name: Upload Java bindings - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v6 with: name: java-bindings-jar path: ffi/bindings/java/rodbus/target/*.jar - name: Upload Java pom.xml - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v6 with: name: java-bindings-pom path: ffi/bindings/java/rodbus/pom.xml @@ -318,12 +318,12 @@ jobs: if: startsWith(github.ref, 'refs/tags/') steps: - name: Download documentation artifacts - uses: actions/download-artifact@v4 + uses: actions/download-artifact@v7 with: path: artifacts pattern: doc-* - name: Checkout stepfunc/docs - uses: actions/checkout@v4 + uses: actions/checkout@v6 with: repository: stepfunc/docs ssh-key: ${{ secrets.SFIO_DOCS_SSH_KEY }} @@ -355,12 +355,12 @@ jobs: if: startsWith(github.ref, 'refs/tags/') steps: - name: Download Java artifacts - uses: actions/download-artifact@v4 + uses: actions/download-artifact@v7 with: path: artifacts pattern: java-bindings-* - name: Import PGP key - uses: crazy-max/ghaction-import-gpg@v3 + uses: crazy-max/ghaction-import-gpg@v7 with: gpg-private-key: ${{ secrets.SFIO_PGP_PRIVATE_KEY }} passphrase: ${{ secrets.SFIO_PGP_PRIVATE_KEY_PASSPHRASE }} @@ -444,7 +444,7 @@ jobs: if: startsWith(github.ref, 'refs/tags/') steps: - name: Download .NET artifacts - uses: actions/download-artifact@v4 + uses: actions/download-artifact@v7 with: name: dotnet-bindings path: artifacts/dotnet-bindings @@ -463,7 +463,7 @@ jobs: if: startsWith(github.ref, 'refs/tags/') steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v6 - name: Install Rust uses: dtolnay/rust-toolchain@stable - name: Publish to crates.io @@ -485,7 +485,7 @@ jobs: if: startsWith(github.ref, 'refs/tags/') steps: - name: Download all release artifacts - uses: actions/download-artifact@v4 + uses: actions/download-artifact@v7 with: path: artifacts - name: Package C Bindings @@ -494,7 +494,7 @@ jobs: cd artifacts/c-bindings zip -r ../../release/rodbus-${{github.ref_name}}.zip . - name: Create GitHub release - uses: softprops/action-gh-release@v1 + uses: softprops/action-gh-release@v2 with: draft: true files: | diff --git a/.github/workflows/security-audit.yml b/.github/workflows/security-audit.yml index b419d430..676de7af 100644 --- a/.github/workflows/security-audit.yml +++ b/.github/workflows/security-audit.yml @@ -19,7 +19,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v6 - name: Install cargo-binstall uses: cargo-bins/cargo-binstall@main diff --git a/Cargo.lock b/Cargo.lock index a0956430..1859c60e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,6 +1,6 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. -version = 3 +version = 4 [[package]] name = "addr2line" @@ -30,9 +30,9 @@ dependencies = [ [[package]] name = "aho-corasick" -version = "1.1.3" +version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" +checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" dependencies = [ "memchr", ] @@ -48,9 +48,9 @@ dependencies = [ [[package]] name = "anstream" -version = "0.6.21" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43d5b281e737544384e969a5ccad3f1cdd24b48086a0fc1b2a5262a26b8f4f4a" +checksum = "824a212faf96e9acacdbd09febd34438f8f711fb84e09a8916013cd7815ca28d" dependencies = [ "anstyle", "anstyle-parse", @@ -63,59 +63,37 @@ dependencies = [ [[package]] name = "anstyle" -version = "1.0.13" +version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5192cca8006f1fd4f7237516f40fa183bb07f8fbdfedaa0036de5ea9b0b45e78" +checksum = "940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000" [[package]] name = "anstyle-parse" -version = "0.2.7" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2" +checksum = "52ce7f38b242319f7cabaa6813055467063ecdc9d355bbb4ce0c68908cd8130e" dependencies = [ "utf8parse", ] [[package]] name = "anstyle-query" -version = "1.1.4" +version = "1.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e231f6134f61b71076a3eab506c379d4f36122f2af15a9ff04415ea4c3339e2" +checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" dependencies = [ - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] name = "anstyle-wincon" -version = "3.0.10" +version = "3.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e0633414522a32ffaac8ac6cc8f748e090c5717661fddeea04219e2344f5f2a" +checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" dependencies = [ "anstyle", "once_cell_polyfill", - "windows-sys 0.60.2", -] - -[[package]] -name = "async-stream" -version = "0.3.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b5a71a6f37880a80d1d7f19efd781e4b5de42c88f0722cc13bcb6cc2cfe8476" -dependencies = [ - "async-stream-impl", - "futures-core", - "pin-project-lite", -] - -[[package]] -name = "async-stream-impl" -version = "0.3.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7c24de15d275a1ecfd47a380fb4d5ec9bfe0933f309ed5e705b775596a3574d" -dependencies = [ - "proc-macro2", - "quote", - "syn", + "windows-sys 0.61.2", ] [[package]] @@ -126,9 +104,9 @@ checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" [[package]] name = "aws-lc-rs" -version = "1.14.1" +version = "1.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "879b6c89592deb404ba4dc0ae6b58ffd1795c78991cbb5b8bc441c48a070440d" +checksum = "94bffc006df10ac2a68c83692d734a465f8ee6c5b384d8545a636f81d858f4bf" dependencies = [ "aws-lc-sys", "zeroize", @@ -136,11 +114,10 @@ dependencies = [ [[package]] name = "aws-lc-sys" -version = "0.32.3" +version = "0.38.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "107a4e9d9cab9963e04e84bb8dee0e25f2a987f9a8bad5ed054abd439caa8f8c" +checksum = "4321e568ed89bb5a7d291a7f37997c2c0df89809d7b6d12062c81ddb54aa782e" dependencies = [ - "bindgen", "cc", "cmake", "dunce", @@ -170,29 +147,9 @@ checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" [[package]] name = "base64ct" -version = "1.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55248b47b0caf0546f7988906588779981c43bb1bc9d0c44087278f80cdb44ba" - -[[package]] -name = "bindgen" -version = "0.72.1" +version = "1.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "993776b509cfb49c750f11b8f07a46fa23e0a1386ffc01fb1e7d343efc387895" -dependencies = [ - "bitflags 2.9.4", - "cexpr", - "clang-sys", - "itertools", - "log", - "prettyplease", - "proc-macro2", - "quote", - "regex", - "rustc-hash", - "shlex", - "syn", -] +checksum = "2af50177e190e07a26ab74f8b1efbfe2ef87da2116221318cb1c2e82baf7de06" [[package]] name = "bitflags" @@ -202,9 +159,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.9.4" +version = "2.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2261d10cca569e4643e526d8dc2e62e433cc8aba21ab764233731f8d369bf394" +checksum = "843867be96c8daad0d758b57df9392b6d8d271134fce549de6ce169ff98a92af" [[package]] name = "block-buffer" @@ -226,9 +183,9 @@ dependencies = [ [[package]] name = "bumpalo" -version = "3.19.0" +version = "3.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43" +checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb" [[package]] name = "bytes" @@ -247,9 +204,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.2.41" +version = "1.2.57" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac9fe6cdbb24b6ade63616c0a0688e45bb56732262c158df3c0c4bea4ca47cb7" +checksum = "7a0dd1ca384932ff3641c8718a02769f1698e7563dc6974ffd03346116310423" dependencies = [ "find-msvc-tools", "jobserver", @@ -263,15 +220,6 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c" -[[package]] -name = "cexpr" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766" -dependencies = [ - "nom", -] - [[package]] name = "cfg-if" version = "1.0.4" @@ -286,9 +234,9 @@ checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" [[package]] name = "chrono" -version = "0.4.42" +version = "0.4.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "145052bdd345b87320e369255277e3fb5152762ad123a901ef5c262dd38fe8d2" +checksum = "c673075a2e0e5f4a1dde27ce9dee1ea4558c7ffe648f576438a20ca1d2acc4b0" dependencies = [ "iana-time-zone", "num-traits", @@ -305,22 +253,11 @@ dependencies = [ "inout", ] -[[package]] -name = "clang-sys" -version = "1.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b023947811758c97c59bf9d1c188fd619ad4718dcaa767947df1cadb14f39f4" -dependencies = [ - "glob", - "libc", - "libloading", -] - [[package]] name = "clap" -version = "4.5.49" +version = "4.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4512b90fa68d3a9932cea5184017c5d200f5921df706d45e853537dea51508f" +checksum = "b193af5b67834b676abd72466a96c1024e6a6ad978a1f484bd90b85c94041351" dependencies = [ "clap_builder", "clap_derive", @@ -328,9 +265,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.49" +version = "4.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0025e98baa12e766c67ba13ff4695a887a1eba19569aad00a472546795bd6730" +checksum = "714a53001bf66416adb0e2ef5ac857140e7dc3a0c48fb28b2f10762fc4b5069f" dependencies = [ "anstream", "anstyle", @@ -340,9 +277,9 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.5.49" +version = "4.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a0b5487afeab2deb2ff4e03a807ad1a03ac532ff5a2cee5d86884440c7f7671" +checksum = "1110bd8a634a1ab8cb04345d8d878267d57c3cf1b38d91b71af6686408bbca6a" dependencies = [ "heck", "proc-macro2", @@ -352,24 +289,24 @@ dependencies = [ [[package]] name = "clap_lex" -version = "0.7.6" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1d728cc89cf3aee9ff92b05e62b19ee65a02b5702cff7d5a377e32c6ae29d8d" +checksum = "c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9" [[package]] name = "cmake" -version = "0.1.54" +version = "0.1.57" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7caa3f9de89ddbe2c607f4101924c5abec803763ae9534e4f4d7d8f84aa81f0" +checksum = "75443c44cd6b379beb8c5b45d85d0773baf31cce901fe7bb252f4eff3008ef7d" dependencies = [ "cc", ] [[package]] name = "colorchoice" -version = "1.0.4" +version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75" +checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570" [[package]] name = "combine" @@ -414,9 +351,9 @@ dependencies = [ [[package]] name = "crc" -version = "3.3.0" +version = "3.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9710d3b3739c2e349eb44fe848ad0b7c8cb1e42bd87ee49371df2f7acaf3e675" +checksum = "5eb8a2a1cd12ab0d987a5d5e825195d372001a4094a0376319d5a0ad71c1ba0d" dependencies = [ "crc-catalog", ] @@ -429,9 +366,9 @@ checksum = "19d374276b40fb8bbdee95aef7c7fa6b5316ec764510eb64b8dd0e2ed0d7e7f5" [[package]] name = "crypto-common" -version = "0.1.6" +version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a" dependencies = [ "generic-array", "typenum", @@ -465,12 +402,6 @@ version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" -[[package]] -name = "either" -version = "1.15.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" - [[package]] name = "example-client" version = "1.5.0-RC1" @@ -508,9 +439,9 @@ dependencies = [ [[package]] name = "find-msvc-tools" -version = "0.1.4" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52051878f80a721bb68ebfbc930e07b65ba72f2da88968ea5c06fd6ca3d3a127" +checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" [[package]] name = "fs_extra" @@ -520,9 +451,9 @@ checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c" [[package]] name = "futures" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876" +checksum = "8b147ee9d1f6d097cef9ce628cd2ee62288d963e16fb287bd9286455b241382d" dependencies = [ "futures-channel", "futures-core", @@ -535,9 +466,9 @@ dependencies = [ [[package]] name = "futures-channel" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" +checksum = "07bbe89c50d7a535e539b8c17bc0b49bdb77747034daa8087407d655f3f7cc1d" dependencies = [ "futures-core", "futures-sink", @@ -545,15 +476,15 @@ dependencies = [ [[package]] name = "futures-core" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" +checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d" [[package]] name = "futures-executor" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f" +checksum = "baf29c38818342a3b26b5b923639e7b1f4a61fc5e76102d4b1981c6dc7a7579d" dependencies = [ "futures-core", "futures-task", @@ -562,15 +493,15 @@ dependencies = [ [[package]] name = "futures-io" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" +checksum = "cecba35d7ad927e23624b22ad55235f2239cfa44fd10428eecbeba6d6a717718" [[package]] name = "futures-macro" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" +checksum = "e835b70203e41293343137df5c0664546da5745f82ec9b84d40be8336958447b" dependencies = [ "proc-macro2", "quote", @@ -579,21 +510,21 @@ dependencies = [ [[package]] name = "futures-sink" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" +checksum = "c39754e157331b013978ec91992bde1ac089843443c49cbc7f46150b0fad0893" [[package]] name = "futures-task" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" +checksum = "037711b3d59c33004d3856fbdc83b99d4ff37a24768fa1be9ce3538a1cde4393" [[package]] name = "futures-util" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" +checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6" dependencies = [ "futures-channel", "futures-core", @@ -603,15 +534,14 @@ dependencies = [ "futures-task", "memchr", "pin-project-lite", - "pin-utils", "slab", ] [[package]] name = "generic-array" -version = "0.14.9" +version = "0.14.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4bb6743198531e02858aeaea5398fcc883e71851fcbcb5a2f773e2fb6cb1edf2" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" dependencies = [ "typenum", "version_check", @@ -619,9 +549,9 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.16" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" +checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" dependencies = [ "cfg-if", "libc", @@ -646,12 +576,6 @@ version = "0.32.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e629b9b98ef3dd8afe6ca2bd0f89306cec16d43d907889945bc5d6687f2f13c7" -[[package]] -name = "glob" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280" - [[package]] name = "heck" version = "0.5.0" @@ -675,9 +599,9 @@ dependencies = [ [[package]] name = "iana-time-zone" -version = "0.1.64" +version = "0.1.65" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33e57f83510bb73707521ebaffa789ec8caf86f9657cad665b092b581d40e9fb" +checksum = "e31bc9ad994ba00e440a8aa5c9ef0ec67d5cb5e5cb0cc7f8b744a35b389cc470" dependencies = [ "android_system_properties", "core-foundation-sys", @@ -727,24 +651,15 @@ dependencies = [ [[package]] name = "is_terminal_polyfill" -version = "1.70.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" - -[[package]] -name = "itertools" -version = "0.13.0" +version = "1.70.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" -dependencies = [ - "either", -] +checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" [[package]] name = "itoa" -version = "1.0.15" +version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" +checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2" [[package]] name = "jni" @@ -778,9 +693,9 @@ dependencies = [ [[package]] name = "js-sys" -version = "0.3.81" +version = "0.3.91" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec48937a97411dcb524a265206ccd4c90bb711fca92b2792c407f268825b9305" +checksum = "b49715b7073f385ba4bc528e5747d02e66cb39c6146efb66b781f131f0fb399c" dependencies = [ "once_cell", "wasm-bindgen", @@ -794,25 +709,15 @@ checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" [[package]] name = "libc" -version = "0.2.177" +version = "0.2.183" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2874a2af47a2325c2001a6e6fad9b16a53b802102b528163885171cf92b15976" - -[[package]] -name = "libloading" -version = "0.8.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7c4b02199fee7c5d21a5ae7d8cfa79a6ef5bb2fc834d6e9058e89c825efdc55" -dependencies = [ - "cfg-if", - "windows-link", -] +checksum = "b5b646652bf6661599e1da8901b3b9522896f01e736bad5f723fe7a3a27f899d" [[package]] name = "log" -version = "0.4.28" +version = "0.4.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34080505efa8e45a4b816c349525ebe327ceaa8559756f0356cba97ef3bf7432" +checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" [[package]] name = "mach2" @@ -825,15 +730,9 @@ dependencies = [ [[package]] name = "memchr" -version = "2.7.6" +version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" - -[[package]] -name = "minimal-lexical" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" +checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" [[package]] name = "miniz_oxide" @@ -846,9 +745,9 @@ dependencies = [ [[package]] name = "mio" -version = "1.1.0" +version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69d83b0086dc8ecf3ce9ae2874b2d1290252e2a30720bea58a5c6639b0092873" +checksum = "a69bcab0ad47271a0234d9422b131806bf3968021e5dc9328caf2d4cd58557fc" dependencies = [ "libc", "log", @@ -886,22 +785,12 @@ version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46" dependencies = [ - "bitflags 2.9.4", + "bitflags 2.11.0", "cfg-if", "cfg_aliases", "libc", ] -[[package]] -name = "nom" -version = "7.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" -dependencies = [ - "memchr", - "minimal-lexical", -] - [[package]] name = "nu-ansi-term" version = "0.50.3" @@ -941,15 +830,15 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.21.3" +version = "1.21.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" +checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" [[package]] name = "once_cell_polyfill" -version = "1.70.1" +version = "1.70.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4895175b425cb1f87721b59f0f286c2092bd4af812243672510e1ac53e2e0ad" +checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" [[package]] name = "oo-bindgen" @@ -967,7 +856,7 @@ dependencies = [ "semver", "serde", "serde_json", - "thiserror 2.0.17", + "thiserror 2.0.18", "tracing", ] @@ -1002,15 +891,9 @@ dependencies = [ [[package]] name = "pin-project-lite" -version = "0.2.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" - -[[package]] -name = "pin-utils" -version = "0.1.0" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" +checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" [[package]] name = "pkcs5" @@ -1045,30 +928,20 @@ version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e8d0eef3571242013a0d5dc84861c3ae4a652e56e12adf8bdc26ff5f8cb34c94" -[[package]] -name = "prettyplease" -version = "0.2.37" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" -dependencies = [ - "proc-macro2", - "syn", -] - [[package]] name = "proc-macro2" -version = "1.0.101" +version = "1.0.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89ae43fd86e4158d6db51ad8e2b80f313af9cc74f5c0e03ccb87de09998732de" +checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" dependencies = [ "unicode-ident", ] [[package]] name = "quote" -version = "1.0.41" +version = "1.0.45" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce25767e7b499d1b604768e7cde645d14cc8584231ea6b295e9c9eb22c02e1d1" +checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924" dependencies = [ "proc-macro2", ] @@ -1087,9 +960,9 @@ checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" [[package]] name = "regex" -version = "1.12.2" +version = "1.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "843bc0191f75f3e22651ae5f1e72939ab2f72a4bc30fa80a066bd66edefc24d4" +checksum = "e10754a14b9137dd7b1e3e5b0493cc9171fdd105e0ab477f51b72e7f3ac0e276" dependencies = [ "aho-corasick", "memchr", @@ -1099,9 +972,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.13" +version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5276caf25ac86c8d810222b3dbb938e512c55c6831a10f3e6ed1c93b84041f1c" +checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f" dependencies = [ "aho-corasick", "memchr", @@ -1110,9 +983,9 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.8.8" +version = "0.8.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58" +checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a" [[package]] name = "ring" @@ -1122,7 +995,7 @@ checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" dependencies = [ "cc", "cfg-if", - "getrandom 0.2.16", + "getrandom 0.2.17", "libc", "untrusted", "windows-sys 0.52.0", @@ -1161,7 +1034,7 @@ version = "1.5.0-RC1" dependencies = [ "clap", "rodbus", - "thiserror 2.0.17", + "thiserror 2.0.18", "tokio", "tracing", "tracing-subscriber", @@ -1206,21 +1079,15 @@ dependencies = [ [[package]] name = "rustc-demangle" -version = "0.1.26" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56f7d92ca342cea22a06f2121d944b4fd82af56988c270852495420f961d4ace" - -[[package]] -name = "rustc-hash" -version = "2.1.1" +version = "0.1.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" +checksum = "b50b8869d9fc858ce7266cce0194bd74df58b9d0e3f6df3a9fc8eb470d95c09d" [[package]] name = "rustls" -version = "0.23.33" +version = "0.23.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "751e04a496ca00bb97a5e043158d23d66b5aabf2e1d5aa2a0aaebb1aafe6f82c" +checksum = "758025cb5fccfd3bc2fd74708fd4682be41d99e5dff73c377c0646c6012c73a4" dependencies = [ "aws-lc-rs", "log", @@ -1234,18 +1101,18 @@ dependencies = [ [[package]] name = "rustls-pki-types" -version = "1.12.0" +version = "1.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "229a4a4c221013e7e1f1a043678c5cc39fe5171437c88fb47151a21e6f5b5c79" +checksum = "be040f8b0a225e40375822a563fa9524378b9d63112f53e19ffff34df5d33fdd" dependencies = [ "zeroize", ] [[package]] name = "rustls-webpki" -version = "0.103.7" +version = "0.103.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e10b3f4191e8a80e6b43eebabfac91e5dcecebb27a71f04e820c47ec41d314bf" +checksum = "d7df23109aa6c1567d1c575b9952556388da57401e4ace1d15f79eedad0d8f53" dependencies = [ "aws-lc-rs", "ring", @@ -1265,12 +1132,6 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3b4a7d26e4a603b3cedff6a968d356e445c10dbc3e4cf6bd414fd5428437e73" -[[package]] -name = "ryu" -version = "1.0.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" - [[package]] name = "salsa20" version = "0.10.2" @@ -1308,9 +1169,9 @@ dependencies = [ [[package]] name = "scursor" -version = "0.2.0" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49e9e097d5ccc66dd9a88d94c7ddad0833b13a35ed8f30d6ea28a8d5269cbd58" +checksum = "44cc835e9c5bfc9833d698556fc56bfeb8f6c672d96f1a02319b68c8e9be9d50" [[package]] name = "semver" @@ -1350,15 +1211,15 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.145" +version = "1.0.149" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "402a6f66d8c709116cf22f558eab210f5a50187f702eb4d7e5ef38d9a7f1c79c" +checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" dependencies = [ "itoa", "memchr", - "ryu", "serde", "serde_core", + "zmij", ] [[package]] @@ -1367,7 +1228,7 @@ version = "4.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2acaf3f973e8616d7ceac415f53fc60e190b2a686fbcf8d27d0256c741c5007b" dependencies = [ - "bitflags 2.9.4", + "bitflags 2.11.0", "cfg-if", "core-foundation", "core-foundation-sys", @@ -1454,9 +1315,9 @@ checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" [[package]] name = "slab" -version = "0.4.11" +version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a2ae44ef20feb57a68b23d846850f861394c2e02dc425a50098ae8c90267589" +checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" [[package]] name = "smallvec" @@ -1466,12 +1327,12 @@ checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" [[package]] name = "socket2" -version = "0.6.1" +version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17129e116933cf371d018bb80ae557e889637989d8638274fb25622827b03881" +checksum = "3a766e1110788c36f4fa1c2b71b387a7815aa65f88ce0229841826633d93723e" dependencies = [ "libc", - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] @@ -1498,9 +1359,9 @@ checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" [[package]] name = "syn" -version = "2.0.106" +version = "2.0.117" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ede7c438028d4436d71104916910f5bb611972c5cfd7f89b8300a8186e6fada6" +checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99" dependencies = [ "proc-macro2", "quote", @@ -1518,11 +1379,11 @@ dependencies = [ [[package]] name = "thiserror" -version = "2.0.17" +version = "2.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f63587ca0f12b72a0600bcba1d40081f830876000bb46dd2337a3051618f4fc8" +checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" dependencies = [ - "thiserror-impl 2.0.17", + "thiserror-impl 2.0.18", ] [[package]] @@ -1538,9 +1399,9 @@ dependencies = [ [[package]] name = "thiserror-impl" -version = "2.0.17" +version = "2.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ff15c8ecd7de3849db632e14d18d2571fa09dfc5ed93479bc4485c7a517c913" +checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" dependencies = [ "proc-macro2", "quote", @@ -1558,9 +1419,9 @@ dependencies = [ [[package]] name = "tokio" -version = "1.48.0" +version = "1.50.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff360e02eab121e0bc37a2d3b4d4dc622e6eda3a8e5253d5435ecf5bd4c68408" +checksum = "27ad5e34374e03cfffefc301becb44e9dc3c17584f414349ebe29ed26661822d" dependencies = [ "bytes", "libc", @@ -1573,9 +1434,9 @@ dependencies = [ [[package]] name = "tokio-macros" -version = "2.6.0" +version = "2.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af407857209536a95c8e56f8231ef2c2e2aff839b22e07a1ffcbc617e9db9fa5" +checksum = "5c55a2eff8b69ce66c84f85e1da1c233edc36ceb85a2058d11b0d6a3c7e7569c" dependencies = [ "proc-macro2", "quote", @@ -1608,9 +1469,9 @@ dependencies = [ [[package]] name = "tokio-stream" -version = "0.1.17" +version = "0.1.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eca58d7bba4a75707817a2c44174253f9236b2d5fbd055602e9d5c07c139a047" +checksum = "32da49809aab5c3bc678af03902d4ccddea2a87d028d86392a4b1560c6906c70" dependencies = [ "futures-core", "pin-project-lite", @@ -1619,12 +1480,10 @@ dependencies = [ [[package]] name = "tokio-test" -version = "0.4.4" +version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2468baabc3311435b55dd935f702f42cd1b8abb7e754fb7dfb16bd36aa88f9f7" +checksum = "3f6d24790a10a7af737693a3e8f1d03faef7e6ca0cc99aae5066f533766de545" dependencies = [ - "async-stream", - "bytes", "futures-core", "tokio", "tokio-stream", @@ -1632,9 +1491,9 @@ dependencies = [ [[package]] name = "tokio-util" -version = "0.7.16" +version = "0.7.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14307c986784f72ef81c89db7d9e28d6ac26d16213b109ea501696195e6e3ce5" +checksum = "9ae9cec805b01e8fc3fd2fe289f89149a9b66dd16786abd8b19cfa7b48cb0098" dependencies = [ "bytes", "futures-core", @@ -1645,9 +1504,9 @@ dependencies = [ [[package]] name = "tracing" -version = "0.1.41" +version = "0.1.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" +checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" dependencies = [ "pin-project-lite", "tracing-attributes", @@ -1656,9 +1515,9 @@ dependencies = [ [[package]] name = "tracing-attributes" -version = "0.1.30" +version = "0.1.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81383ab64e72a7a8b8e13130c49e3dab29def6d0c7d76a03087b3cf71c5c6903" +checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" dependencies = [ "proc-macro2", "quote", @@ -1667,9 +1526,9 @@ dependencies = [ [[package]] name = "tracing-core" -version = "0.1.34" +version = "0.1.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9d12581f227e93f094d3af2ae690a574abb8a2b9b7a96e7cfe9647b2b617678" +checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" dependencies = [ "once_cell", "valuable", @@ -1698,9 +1557,9 @@ dependencies = [ [[package]] name = "tracing-subscriber" -version = "0.3.20" +version = "0.3.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2054a14f5307d601f88daf0553e1cbf472acc4f2c51afab632431cdcd72124d5" +checksum = "cb7f578e5945fb242538965c2d0b04418d38ec25c79d160cd279bf0731c8d319" dependencies = [ "chrono", "nu-ansi-term", @@ -1722,18 +1581,18 @@ checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" [[package]] name = "unescaper" -version = "0.1.6" +version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c01d12e3a56a4432a8b436f293c25f4808bdf9e9f9f98f9260bba1f1bc5a1f26" +checksum = "4064ed685c487dbc25bd3f0e9548f2e34bab9d18cefc700f9ec2dba74ba1138e" dependencies = [ - "thiserror 2.0.17", + "thiserror 2.0.18", ] [[package]] name = "unicode-ident" -version = "1.0.19" +version = "1.0.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f63a545481291138910575129486daeaf8ac54aee4387fe7906919f7830c7d9d" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" [[package]] name = "untrusted" @@ -1777,18 +1636,18 @@ checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" [[package]] name = "wasip2" -version = "1.0.1+wasi-0.2.4" +version = "1.0.2+wasi-0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0562428422c63773dad2c345a1882263bbf4d65cf3f42e90921f787ef5ad58e7" +checksum = "9517f9239f02c069db75e65f174b3da828fe5f5b945c4dd26bd25d89c03ebcf5" dependencies = [ "wit-bindgen", ] [[package]] name = "wasm-bindgen" -version = "0.2.104" +version = "0.2.114" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1da10c01ae9f1ae40cbfac0bac3b1e724b320abfcf52229f80b547c0d250e2d" +checksum = "6532f9a5c1ece3798cb1c2cfdba640b9b3ba884f5db45973a6f442510a87d38e" dependencies = [ "cfg-if", "once_cell", @@ -1797,25 +1656,11 @@ dependencies = [ "wasm-bindgen-shared", ] -[[package]] -name = "wasm-bindgen-backend" -version = "0.2.104" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "671c9a5a66f49d8a47345ab942e2cb93c7d1d0339065d4f8139c486121b43b19" -dependencies = [ - "bumpalo", - "log", - "proc-macro2", - "quote", - "syn", - "wasm-bindgen-shared", -] - [[package]] name = "wasm-bindgen-macro" -version = "0.2.104" +version = "0.2.114" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ca60477e4c59f5f2986c50191cd972e3a50d8a95603bc9434501cf156a9a119" +checksum = "18a2d50fcf105fb33bb15f00e7a77b772945a2ee45dcf454961fd843e74c18e6" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -1823,22 +1668,22 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.104" +version = "0.2.114" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f07d2f20d4da7b26400c9f4a0511e6e0345b040694e8a75bd41d578fa4421d7" +checksum = "03ce4caeaac547cdf713d280eda22a730824dd11e6b8c3ca9e42247b25c631e3" dependencies = [ + "bumpalo", "proc-macro2", "quote", "syn", - "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.104" +version = "0.2.114" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bad67dc8b2a1a6e5448428adec4c3e84c43e561d8c9ee8a9e5aabeb193ec41d1" +checksum = "75a326b8c223ee17883a4251907455a2431acc2791c98c26279376490c378c16" dependencies = [ "unicode-ident", ] @@ -1939,16 +1784,7 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ - "windows-targets 0.52.6", -] - -[[package]] -name = "windows-sys" -version = "0.60.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" -dependencies = [ - "windows-targets 0.53.5", + "windows-targets", ] [[package]] @@ -1966,31 +1802,14 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" dependencies = [ - "windows_aarch64_gnullvm 0.52.6", - "windows_aarch64_msvc 0.52.6", - "windows_i686_gnu 0.52.6", - "windows_i686_gnullvm 0.52.6", - "windows_i686_msvc 0.52.6", - "windows_x86_64_gnu 0.52.6", - "windows_x86_64_gnullvm 0.52.6", - "windows_x86_64_msvc 0.52.6", -] - -[[package]] -name = "windows-targets" -version = "0.53.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3" -dependencies = [ - "windows-link", - "windows_aarch64_gnullvm 0.53.1", - "windows_aarch64_msvc 0.53.1", - "windows_i686_gnu 0.53.1", - "windows_i686_gnullvm 0.53.1", - "windows_i686_msvc 0.53.1", - "windows_x86_64_gnu 0.53.1", - "windows_x86_64_gnullvm 0.53.1", - "windows_x86_64_msvc 0.53.1", + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_gnullvm", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", ] [[package]] @@ -1999,104 +1818,62 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53" - [[package]] name = "windows_aarch64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" -[[package]] -name = "windows_aarch64_msvc" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006" - [[package]] name = "windows_i686_gnu" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" -[[package]] -name = "windows_i686_gnu" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3" - [[package]] name = "windows_i686_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" -[[package]] -name = "windows_i686_gnullvm" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c" - [[package]] name = "windows_i686_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" -[[package]] -name = "windows_i686_msvc" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2" - [[package]] name = "windows_x86_64_gnu" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" -[[package]] -name = "windows_x86_64_gnu" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499" - [[package]] name = "windows_x86_64_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1" - [[package]] name = "windows_x86_64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" -[[package]] -name = "windows_x86_64_msvc" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" - [[package]] name = "wit-bindgen" -version = "0.46.0" +version = "0.51.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59" +checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5" [[package]] name = "zeroize" version = "1.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0" + +[[package]] +name = "zmij" +version = "1.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa" diff --git a/Cargo.toml b/Cargo.toml index 58a69ebe..869672b9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -29,7 +29,7 @@ tracing-subscriber = "0.3" [workspace.package] authors = ["Step Function I/O LLC >"] -rust-version = "1.75" +rust-version = "1.94" edition = "2021" license-file = "LICENSE.txt" homepage = "https://stepfunc.io/products/libraries/modbus/" diff --git a/PERFORMANCE_ANALYSIS.md b/PERFORMANCE_ANALYSIS.md new file mode 100644 index 00000000..050817b7 --- /dev/null +++ b/PERFORMANCE_ANALYSIS.md @@ -0,0 +1,127 @@ +# TCP Client (Master) Performance Analysis + +Analysis of the TCP client hot path focusing on read/write operations from user code through serialization, TCP I/O, deserialization, and response delivery. + +## 1. Sequential Request Processing (Largest Architectural Win) + +**Location:** `rodbus/src/client/task.rs:257-299` (`execute_request`) + +The current design processes exactly **one request at a time**: serialize → write → wait for response → parse → next. The full network round-trip latency is paid per request. For a typical polling application reading 20 different address ranges, this means 20 serial round-trips. + +Modbus TCP's transaction ID exists specifically to support pipelining. The code already matches on `tx_id` in the response loop (line 285-291), so the protocol-level plumbing is partially there. + +**Possible approach:** Allow N requests in-flight simultaneously. The `ClientLoop` would maintain a small map of `TxId → Request`, send multiple requests before waiting for responses, and match responses back by transaction ID. This could improve throughput by up to Nx where N is the pipeline depth, since you amortize the RTT across N requests. + +**Risk:** Some Modbus servers may not handle pipelined requests well. This would need to be opt-in. + +**Estimated impact:** High — removes RTT-per-request bottleneck. + +## 2. Double Heap Allocation Per Request (Box + Oneshot) + +**Location:** `rodbus/src/client/channel.rs:106-117`, `rodbus/src/client/message.rs:258-306` + +Every `Channel::read_*` / `Channel::write_*` call creates: +1. A `tokio::sync::oneshot::channel()` (heap allocation) +2. A `Box` wrapping a closure that captures the oneshot sender (heap allocation) + +So **2 heap allocations per request** just for the response plumbing. The `Promise` type in `message.rs:258` boxes the callback unconditionally. + +**Fix:** Store a `PromiseInner` enum that can be either `Oneshot(oneshot::Sender>)` or `Boxed(Box>)`. The `Channel` API would use the `Oneshot` variant directly, avoiding the Box. The `CallbackSession` (FFI path) continues to use `Boxed`. This removes one allocation from the Rust API hot path. + +Similarly, `rodbus/src/client/requests/read_bits.rs:17-19` and `rodbus/src/client/requests/read_registers.rs:20-22` have their own `Promise` types that also box unconditionally. + +**Estimated impact:** Medium — removes 1 heap allocation per request. + +## 3. Vec Allocation on Every Read Response + +**Location:** `rodbus/src/client/requests/read_bits.rs:69`, `rodbus/src/client/requests/read_registers.rs:72` + +```rust +let _ = tx.send(x.map(|x| x.collect())); +``` + +Every read response allocates a new `Vec>` or `Vec>`. For polling applications that read the same register set thousands of times per second, this is a steady stream of allocations. + +**Fix options:** +- Provide an API that accepts a pre-allocated `&mut Vec>` which gets cleared and refilled. +- Provide an API returning an iterator over borrowed data (the `CallbackSession` already does this with `BitIterator`/`RegisterIterator` which are zero-copy, but the public `Channel` API forces the collect). + +**Estimated impact:** Medium — removes 1 heap allocation per read response. + +## 4. Tracing Span Per Transaction + +**Location:** `rodbus/src/client/task.rs:224-226` + +```rust +.instrument(tracing::info_span!("Transaction", tx_id = %tx_id)) +``` + +This creates a tracing span for **every** Modbus transaction. Even with no subscriber, the span creation and the `%tx_id` formatting (`Display::fmt` → `write!(f, "{:#04X}", self.value)`) have nonzero cost. At thousands of requests per second, this adds up. + +**Fix:** Guard the instrumentation behind the decode level check, or use `tracing::enabled!()` to skip span creation when no subscriber is active at the relevant level. + +**Estimated impact:** Low-Medium — saves formatting + span bookkeeping per request. + +## 5. Response Frame Copy + +**Location:** `rodbus/src/tcp/frame.rs:77-85` (`parse_body`) + +```rust +fn parse_body(header, adu_length, cursor) -> Result { + let mut frame = Frame::new(header); // zeroes 253 bytes + frame.set(cursor.read(adu_length)?); // copies payload into frame + Ok(frame) +} +``` + +Every response: +1. Zero-initializes a 253-byte `[u8; MAX_ADU_LENGTH]` array in `Frame::new()` +2. Copies the response payload into it + +This could be avoided by having the response parser work directly from the `ReadBuffer` data (borrowing rather than copying). The `Frame` struct exists to own the data across the parse boundary, but the data is consumed immediately after in `handle_response`. + +**Estimated impact:** Low — saves 253-byte zero-init + payload memcpy per response. + +## 6. ReadBuffer Shift Logic Bug (Correctness) + +**Location:** `rodbus/src/common/buffer.rs:94` + +```rust +if self.end == self.len() { +``` + +`self.len()` is `self.end - self.begin`. So this condition reduces to `self.begin == 0`, meaning the shift only triggers when data is already at the beginning (i.e., a no-op). The correct condition should be `self.end == self.buffer.len()` to detect when the write position has reached buffer capacity and consumed bytes at the front need to be shifted down. + +In practice this is benign for Modbus because the empty-check above resets indices between frames and responses typically fit in a single read. But it is a latent bug if a partial read leaves consumed bytes in the buffer. + +**Estimated impact:** N/A — correctness fix, not performance. + +## 7. ReadBuffer u16 Parsing + +**Location:** `rodbus/src/common/buffer.rs:68-72` + +```rust +pub(crate) fn read_u16_be(&mut self) -> Result { + let b1 = self.read_u8()? as u16; + let b2 = self.read_u8()? as u16; + Ok((b1 << 8) | b2) +} +``` + +Two separate bounds-checked reads instead of one. Could read 2 bytes at once and use `u16::from_be_bytes()`. Very minor but this is in the frame parsing hot path (called 3 times per MBAP header). + +**Estimated impact:** Negligible. + +## Summary + +| # | Optimization | Type | Est. Impact | +|---|---|---|---| +| 1 | Request pipelining | Architectural | **High** | +| 2 | Eliminate Box in Promise for Channel API | Allocation | **Medium** | +| 3 | Reusable Vec / zero-copy read API | Allocation | **Medium** | +| 4 | Conditional tracing span creation | CPU | **Low-Medium** | +| 5 | Zero-copy response parsing (avoid Frame copy) | Copy | **Low** | +| 6 | ReadBuffer shift condition fix | Correctness | N/A | +| 7 | Batch u16 reads in buffer | CPU | **Negligible** | + +The dominant bottleneck is #1 (sequential processing). For a typical Modbus polling application with many address ranges, pipelining would dwarf all other optimizations combined since the cost is dominated by network RTT, not CPU. diff --git a/allowed.json b/allowed.json index 2ea91300..3c8d7154 100644 --- a/allowed.json +++ b/allowed.json @@ -1919,6 +1919,21 @@ } } ] + }, + "zmij": { + "id": "zmij", + "source": "crates.io", + "licenses": [ + { + "MIT": { + "copyright": { + "Lines": [ + "Copyright (c) David Tolnay" + ] + } + } + } + ] } } } diff --git a/examples/perf/README.md b/examples/perf/README.md new file mode 100644 index 00000000..cfed88d1 --- /dev/null +++ b/examples/perf/README.md @@ -0,0 +1,50 @@ +# Performance Test + +Loopback performance test that spins up an in-process TCP server and client, then measures +request throughput over a configurable duration. + +## Usage + +```bash +cargo build --release -p example-perf +./target/release/example-perf [OPTIONS] +``` + +### Options + +| Flag | Description | Default | +|------|-------------|---------| +| `-s` | Number of parallel sessions | 1 | +| `-c` | Duration in seconds | 5 | +| `-l` | Enable tracing/logging | false | +| `-p` | TCP port | 40000 | + +## Profiling with Valgrind + +Valgrind provides deterministic, reproducible measurements that are independent of system load. + +### Instruction counts (callgrind) + +Measures total CPU instructions executed. Useful for detecting changes in serialization, +parsing, and other CPU-bound code paths. + +```bash +valgrind --tool=callgrind ./target/release/example-perf -s 1 -c 1 +``` + +Produces a `callgrind.out.` file. Use `callgrind_annotate` to inspect: + +```bash +callgrind_annotate callgrind.out. +``` + +### Allocation profiling (dhat) + +Counts heap allocations, bytes allocated, and allocation lifetimes per call site. +Useful for verifying that allocation-reduction optimizations have the expected effect. + +```bash +valgrind --tool=dhat --dhat-out-file=/tmp/dhat.out ./target/release/example-perf -s 1 -c 1 +``` + +To view results, open `/usr/libexec/valgrind/dh_view.html` in a browser and load the output file. diff --git a/examples/perf/src/main.rs b/examples/perf/src/main.rs index ffdb93a1..48825a6e 100644 --- a/examples/perf/src/main.rs +++ b/examples/perf/src/main.rs @@ -21,6 +21,25 @@ impl RequestHandler for Handler { } } +/// Listener that signals when the client reaches the Connected state +struct ConnectListener { + tx: tokio::sync::watch::Sender, +} + +impl Listener for ConnectListener { + fn update(&mut self, value: ClientState) -> MaybeAsync<()> { + let _ = self.tx.send(value); + MaybeAsync::ready(()) + } +} + +/// Wait for the client to reach the Connected state +async fn wait_for_connected(rx: &mut tokio::sync::watch::Receiver) { + while *rx.borrow_and_update() != ClientState::Connected { + rx.changed().await.unwrap(); + } +} + #[derive(Parser)] #[clap(author, version, about, long_about = None)] struct Cli { @@ -79,9 +98,11 @@ async fn main() -> Result<(), Box> { ) .await?; - // now spawn a bunch of clients + // now spawn a bunch of clients, waiting for each to connect let mut channels: Vec<(Channel, RequestParam)> = Vec::new(); for _ in 0..args.sessions { + let (tx, mut rx) = tokio::sync::watch::channel(ClientState::Disabled); + let listener = ConnectListener { tx }; let channel = spawn_tcp_client_task( addr.into(), 10, @@ -91,9 +112,10 @@ async fn main() -> Result<(), Box> { FrameDecodeLevel::Nothing, PhysDecodeLevel::Nothing, ), - None, + Some(Box::new(listener)), ); channel.enable().await.unwrap(); + wait_for_connected(&mut rx).await; let params = RequestParam::new(UnitId::new(1), Duration::from_secs(1)); channels.push((channel, params)); @@ -103,7 +125,7 @@ async fn main() -> Result<(), Box> { let start = std::time::Instant::now(); - // spawn tasks that make a query 1000 times + // spawn tasks that make requests for the specified duration for (mut channel, params) in channels { let handle: tokio::task::JoinHandle> = tokio::spawn(async move { diff --git a/rodbus/Cargo.toml b/rodbus/Cargo.toml index 165c4a7d..81752479 100644 --- a/rodbus/Cargo.toml +++ b/rodbus/Cargo.toml @@ -19,7 +19,7 @@ workspace = true [dependencies] crc = { version = "3", optional = true } -scursor = "0.2.0" +scursor = "0.5.0" tokio = { workspace = true, features = ["net", "sync", "io-util", "io-std", "time", "rt", "rt-multi-thread", "macros"] } tracing = { workspace = true } diff --git a/rodbus/src/client/message.rs b/rodbus/src/client/message.rs index c18abb7e..1fe86276 100644 --- a/rodbus/src/client/message.rs +++ b/rodbus/src/client/message.rs @@ -77,7 +77,8 @@ impl Request { // If we made it this far, then everything's alright // call the request-specific response handler - self.details.handle_response(cursor, decode) + self.details + .handle_response(cursor, expected_function, decode) } fn get_error_for( @@ -148,9 +149,9 @@ impl RequestDetails { fn handle_response( &mut self, cursor: ReadCursor, + function: FunctionCode, decode: AppDecodeLevel, ) -> Result<(), RequestError> { - let function = self.function(); match self { RequestDetails::ReadCoils(x) => x.handle_response(cursor, function, decode), RequestDetails::ReadDiscreteInputs(x) => x.handle_response(cursor, function, decode), @@ -255,11 +256,19 @@ pub(crate) trait Callback: impl Callback for F where F: FnOnce(Result) + Send + Sync + 'static {} +enum PromiseInner +where + T: Send + 'static, +{ + Oneshot(tokio::sync::oneshot::Sender>), + Boxed(Box>), +} + pub(crate) struct Promise where T: Send + 'static, { - callback: Option>>, + inner: Option>, } impl Promise @@ -271,14 +280,14 @@ where F: Callback, { Self { - callback: Some(Box::new(callback)), + inner: Some(PromiseInner::Boxed(Box::new(callback))), } } pub(crate) fn channel(tx: tokio::sync::oneshot::Sender>) -> Self { - Self::new(|x: Result| { - let _ = tx.send(x); - }) + Self { + inner: Some(PromiseInner::Oneshot(tx)), + } } pub(crate) fn failure(&mut self, err: RequestError) { @@ -290,8 +299,13 @@ where } fn complete(&mut self, result: Result) { - if let Some(callback) = self.callback.take() { - callback(result) + if let Some(inner) = self.inner.take() { + match inner { + PromiseInner::Oneshot(tx) => { + let _ = tx.send(result); + } + PromiseInner::Boxed(callback) => callback(result), + } } } } diff --git a/rodbus/src/client/requests/read_bits.rs b/rodbus/src/client/requests/read_bits.rs index 84b64575..433fbb60 100644 --- a/rodbus/src/client/requests/read_bits.rs +++ b/rodbus/src/client/requests/read_bits.rs @@ -14,8 +14,13 @@ pub(crate) trait BitsCallback: impl BitsCallback for T where T: FnOnce(Result) + Send + Sync + 'static {} +enum PromiseInner { + Oneshot(tokio::sync::oneshot::Sender>, RequestError>>), + Boxed(Box), +} + pub(crate) struct Promise { - callback: Option>, + inner: Option, } impl Drop for Promise { @@ -30,21 +35,35 @@ impl Promise { T: BitsCallback, { Self { - callback: Some(Box::new(callback)), + inner: Some(PromiseInner::Boxed(Box::new(callback))), } } - pub(crate) fn failure(&mut self, err: RequestError) { - self.complete(Err(err)) + fn oneshot(tx: tokio::sync::oneshot::Sender>, RequestError>>) -> Self { + Self { + inner: Some(PromiseInner::Oneshot(tx)), + } } - pub(crate) fn success(&mut self, iter: BitIterator) { - self.complete(Ok(iter)) + pub(crate) fn failure(&mut self, err: RequestError) { + if let Some(inner) = self.inner.take() { + match inner { + PromiseInner::Oneshot(tx) => { + let _ = tx.send(Err(err)); + } + PromiseInner::Boxed(callback) => callback(Err(err)), + } + } } - fn complete(&mut self, result: Result) { - if let Some(callback) = self.callback.take() { - callback(result) + pub(crate) fn success(&mut self, iter: BitIterator) { + if let Some(inner) = self.inner.take() { + match inner { + PromiseInner::Oneshot(tx) => { + let _ = tx.send(Ok(iter.collect())); + } + PromiseInner::Boxed(callback) => callback(Ok(iter)), + } } } } @@ -63,12 +82,7 @@ impl ReadBits { request: ReadBitsRange, tx: tokio::sync::oneshot::Sender>, RequestError>>, ) -> Self { - Self::new( - request, - Promise::new(|x: Result| { - let _ = tx.send(x.map(|x| x.collect())); - }), - ) + Self::new(request, Promise::oneshot(tx)) } pub(crate) fn serialize(&self, cursor: &mut WriteCursor) -> Result<(), RequestError> { diff --git a/rodbus/src/client/requests/read_registers.rs b/rodbus/src/client/requests/read_registers.rs index 8ba7e7ff..1c4f4d61 100644 --- a/rodbus/src/client/requests/read_registers.rs +++ b/rodbus/src/client/requests/read_registers.rs @@ -17,8 +17,13 @@ impl RegistersCallback for T where { } +enum PromiseInner { + Oneshot(tokio::sync::oneshot::Sender>, RequestError>>), + Boxed(Box), +} + pub(crate) struct Promise { - callback: Option>, + inner: Option, } impl Drop for Promise { @@ -33,21 +38,35 @@ impl Promise { T: RegistersCallback, { Self { - callback: Some(Box::new(callback)), + inner: Some(PromiseInner::Boxed(Box::new(callback))), } } - pub(crate) fn failure(&mut self, err: RequestError) { - self.complete(Err(err)) + fn oneshot(tx: tokio::sync::oneshot::Sender>, RequestError>>) -> Self { + Self { + inner: Some(PromiseInner::Oneshot(tx)), + } } - pub(crate) fn success(&mut self, iter: RegisterIterator) { - self.complete(Ok(iter)) + pub(crate) fn failure(&mut self, err: RequestError) { + if let Some(inner) = self.inner.take() { + match inner { + PromiseInner::Oneshot(tx) => { + let _ = tx.send(Err(err)); + } + PromiseInner::Boxed(callback) => callback(Err(err)), + } + } } - fn complete(&mut self, x: Result) { - if let Some(callback) = self.callback.take() { - callback(x) + pub(crate) fn success(&mut self, iter: RegisterIterator) { + if let Some(inner) = self.inner.take() { + match inner { + PromiseInner::Oneshot(tx) => { + let _ = tx.send(Ok(iter.collect_vec())); + } + PromiseInner::Boxed(callback) => callback(Ok(iter)), + } } } } @@ -66,12 +85,7 @@ impl ReadRegisters { request: ReadRegistersRange, tx: tokio::sync::oneshot::Sender>, RequestError>>, ) -> Self { - Self::new( - request, - Promise::new(|x: Result| { - let _ = tx.send(x.map(|x| x.collect())); - }), - ) + Self::new(request, Promise::oneshot(tx)) } pub(crate) fn serialize(&self, cursor: &mut WriteCursor) -> Result<(), RequestError> { diff --git a/rodbus/src/client/requests/write_multiple.rs b/rodbus/src/client/requests/write_multiple.rs index 8b9f4699..ea51376f 100644 --- a/rodbus/src/client/requests/write_multiple.rs +++ b/rodbus/src/client/requests/write_multiple.rs @@ -71,13 +71,14 @@ where } } - // implementing this allows collect to optimize the vector capacity fn size_hint(&self) -> (usize, Option) { let remaining = (self.range.count - self.pos) as usize; (remaining, Some(remaining)) } } +impl ExactSizeIterator for WriteMultipleIterator<'_, T> {} + pub(crate) struct MultipleWriteRequest where WriteMultiple: Serialize, diff --git a/rodbus/src/client/task.rs b/rodbus/src/client/task.rs index b3a00447..e0ea1fdb 100644 --- a/rodbus/src/client/task.rs +++ b/rodbus/src/client/task.rs @@ -220,10 +220,13 @@ impl ClientLoop { request: &mut Request, ) -> Result<(), SessionError> { let tx_id = self.tx_id.next(); - let result = self - .execute_request(io, request, tx_id) - .instrument(tracing::info_span!("Transaction", tx_id = %tx_id)) - .await; + let result = if self.decode != DecodeLevel::nothing() { + self.execute_request(io, request, tx_id) + .instrument(tracing::info_span!("Transaction", tx_id = %tx_id)) + .await + } else { + self.execute_request(io, request, tx_id).await + }; match result { Ok(()) => self.timeout_counter.reset(), diff --git a/rodbus/src/common/buffer.rs b/rodbus/src/common/buffer.rs index acdb6ed8..1f7e70a4 100644 --- a/rodbus/src/common/buffer.rs +++ b/rodbus/src/common/buffer.rs @@ -91,7 +91,7 @@ impl ReadBuffer { } // if we've reached capacity, but still need more data we have to shift - if self.end == self.len() { + if self.end == self.buffer.len() { let length = self.len(); self.buffer.copy_within(self.begin..self.end, 0); self.begin = 0; @@ -127,6 +127,49 @@ mod tests { ); } + #[test] + fn shifts_data_when_buffer_full_with_consumed_bytes() { + let mut buffer = ReadBuffer::new(); + let (io, mut io_handle) = sfio_tokio_mock_io::mock(); + let mut phys = PhysLayer::new_mock(io); + + let capacity = crate::common::frame::constants::MAX_FRAME_LENGTH; + + // fill the buffer to exactly capacity + { + let data = vec![0xAA; capacity]; + let mut task = task::spawn(async { + buffer + .read_some(&mut phys, PhysDecodeLevel::Nothing) + .await + .unwrap() + }); + io_handle.read(&data); + assert_ready_eq!(task.poll(), capacity); + } + + // consume some bytes from the front + assert_eq!(buffer.read(7).unwrap(), &[0xAA; 7]); + + // now buffer has begin=7, end=capacity, len=capacity-7 + // calling read_some should shift the data and read more + { + let mut task = task::spawn(async { + buffer + .read_some(&mut phys, PhysDecodeLevel::Nothing) + .await + .unwrap() + }); + io_handle.read(&[0xBB; 7]); + // this should NOT return UnexpectedEof — the shift should + // make room for the new data + assert_ready_eq!(task.poll(), 7); + } + + // verify the unconsumed original data is still intact + assert_eq!(buffer.read(1).unwrap(), &[0xAA]); + } + #[test] fn preserves_data_over_multiple_reads() { let mut buffer = ReadBuffer::new(); diff --git a/rodbus/src/common/serialize.rs b/rodbus/src/common/serialize.rs index 905cfe01..1586fbe6 100644 --- a/rodbus/src/common/serialize.rs +++ b/rodbus/src/common/serialize.rs @@ -16,7 +16,11 @@ use scursor::{ReadCursor, WriteCursor}; pub(crate) fn calc_bytes_for_bits(num_bits: usize) -> Result { let div_8 = num_bits / 8; - let count = if num_bits % 8 == 0 { div_8 } else { div_8 + 1 }; + let count = if num_bits.is_multiple_of(8) { + div_8 + } else { + div_8 + 1 + }; u8::try_from(count).map_err(|_| InternalError::BadByteCount(count)) } diff --git a/rodbus/src/tcp/frame.rs b/rodbus/src/tcp/frame.rs index 738574a6..b13fb3b3 100644 --- a/rodbus/src/tcp/frame.rs +++ b/rodbus/src/tcp/frame.rs @@ -89,32 +89,33 @@ impl MbapParser { cursor: &mut ReadBuffer, decode_level: FrameDecodeLevel, ) -> Result, RequestError> { - match self.state { - ParseState::Header(header, adu_length) => { - if cursor.len() < adu_length { - return Ok(None); + loop { + match self.state { + ParseState::Header(header, adu_length) => { + if cursor.len() < adu_length { + return Ok(None); + } + + let frame = Self::parse_body(&header, adu_length, cursor)?; + self.state = ParseState::Begin; + + if decode_level.enabled() { + tracing::info!( + "MBAP RX - {}", + MbapDisplay::new(decode_level, header, frame.payload()) + ); + } + + return Ok(Some(frame)); } + ParseState::Begin => { + if cursor.len() < constants::HEADER_LENGTH { + return Ok(None); + } - let frame = Self::parse_body(&header, adu_length, cursor)?; - self.state = ParseState::Begin; - - if decode_level.enabled() { - tracing::info!( - "MBAP RX - {}", - MbapDisplay::new(decode_level, header, frame.payload()) - ); + let (header, adu_len) = Self::parse_header(cursor)?; + self.state = ParseState::Header(header, adu_len); } - - Ok(Some(frame)) - } - ParseState::Begin => { - if cursor.len() < constants::HEADER_LENGTH { - return Ok(None); - } - - let (header, adu_len) = Self::parse_header(cursor)?; - self.state = ParseState::Header(header, adu_len); - self.parse(cursor, decode_level) } } } diff --git a/rodbus/src/types.rs b/rodbus/src/types.rs index 5961712d..b863abf5 100644 --- a/rodbus/src/types.rs +++ b/rodbus/src/types.rs @@ -149,6 +149,19 @@ impl<'a> RegisterIterator<'a> { pos: 0, }) } + + /// Collect into a Vec using `as_chunks` to avoid per-element bounds checking + pub(crate) fn collect_vec(self) -> Vec> { + let (chunks, _) = self.bytes.as_chunks::<2>(); + let mut result = Vec::with_capacity(chunks.len()); + for (i, &chunk) in chunks.iter().enumerate() { + result.push(Indexed::new( + self.range.start + (i as u16), + u16::from_be_bytes(chunk), + )); + } + result + } } impl<'a> RegisterIteratorDisplay<'a> { @@ -192,13 +205,14 @@ impl<'a> Iterator for BitIterator<'a> { } } - /// implementing this allows collect to optimize the vector capacity fn size_hint(&self) -> (usize, Option) { let remaining = (self.range.count - self.pos) as usize; (remaining, Some(remaining)) } } +impl ExactSizeIterator for BitIterator<'_> {} + impl<'a> Iterator for RegisterIterator<'a> { type Item = Indexed; @@ -219,13 +233,14 @@ impl<'a> Iterator for RegisterIterator<'a> { } } - // implementing this allows collect to optimize the vector capacity fn size_hint(&self) -> (usize, Option) { let remaining = (self.range.count - self.pos) as usize; (remaining, Some(remaining)) } } +impl ExactSizeIterator for RegisterIterator<'_> {} + impl From<(u16, T)> for Indexed where T: Copy,