diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 39e3832bf..7e92b2677 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -22,44 +22,48 @@ jobs: os: [ubuntu-latest, macos-latest] steps: - - uses: actions/checkout@v4 + - name: Check out zenoh-kotlin + uses: actions/checkout@v4 + with: + path: zenoh-kotlin + + - name: Check out zenoh-flat-jni + uses: actions/checkout@v4 + with: + repository: eclipse-zenoh/zenoh-flat-jni + # No ref: track that repository's default branch. A pinned commit has + # to be hand-edited for every upstream fix, and this is the one hop + # in the chain no bot covers - zenoh-flat-jni's own Cargo.lock is + # kept aligned with zenoh for it, so following its main keeps the + # whole chain automatic. zenoh-flat is not checked out at all: it is + # resolved from git by that lockfile. + path: zenoh-flat-jni - uses: actions/setup-java@v4 with: distribution: temurin java-version: 11 + # zenoh-flat-jni pins its toolchain in rust-toolchain.toml. Install it + # from that directory, so a missing toolchain fails here rather than + # inside the Gradle build - and so this repo never has to name a version + # its dependency is free to change. - name: Install Rust toolchain - run: | - rustup show - rustup component add rustfmt clippy - - - name: Cargo Format - working-directory: zenoh-jni - run: cargo fmt --all --check - - - name: Clippy Check without Cargo.lock - working-directory: zenoh-jni - run: | - rm Cargo.lock - cargo clippy --all-targets --all-features -- -D warnings - git restore Cargo.lock - - - name: Check for feature leaks - working-directory: zenoh-jni - run: cargo test --no-default-features - - - name: Build Zenoh-JNI - working-directory: zenoh-jni - run: cargo build + working-directory: zenoh-flat-jni + run: rustup show - name: Setup Gradle uses: gradle/actions/setup-gradle@v4 - with: - gradle-version: 8.12.1 - name: Gradle Test - run: gradle jvmTest --info + working-directory: zenoh-kotlin + # CI builds against the sibling checkout above, so it opts into the + # composite build. A release does not: it resolves zenoh-flat-jni from + # Maven Central like any other consumer. + # + # No cargo step precedes this: the composite build's test task depends + # on zenoh-flat-jni's own native build, so Gradle drives cargo. + run: ./gradlew jvmTest --info -PuseLocalFlatJni=true markdown_lint: runs-on: ubuntu-latest @@ -84,25 +88,13 @@ jobs: - name: Check whether all jobs pass run: echo '${{ toJson(needs) }}' | jq -e 'all(.result == "success")' - # Publish snapshot packages. These jobs will only be triggered when the CI is executed on main. - publish_jvm_snapshot_package: - name: Publish JVM snapshot package - if: contains(fromJSON('["refs/heads/main"]'), github.ref) - needs: ci - uses: ./.github/workflows/publish-jvm.yml - permissions: - contents: read - packages: write - with: - snapshot: true - branch: ${{ github.ref_name }} - secrets: inherit - - publish_android_snapshot_package: - name: Publish Android snapshot package + # Publish snapshot packages. Both coordinates come from one Gradle + # invocation — see .github/workflows/publish.yml. + publish_snapshot_package: + name: Publish snapshot package if: contains(fromJSON('["refs/heads/main"]'), github.ref) needs: ci - uses: ./.github/workflows/publish-android.yml + uses: ./.github/workflows/publish.yml permissions: contents: read packages: write diff --git a/.github/workflows/publish-dokka.yml b/.github/workflows/publish-dokka.yml index f9b3e57da..3729cdd52 100644 --- a/.github/workflows/publish-dokka.yml +++ b/.github/workflows/publish-dokka.yml @@ -27,14 +27,9 @@ jobs: - name: Setup Gradle uses: gradle/actions/setup-gradle@v4 - with: - gradle-version: 8.12.1 - - name: Gradle Wrapper - run: | - gradle wrapper - name: Build doc - run: gradle dokkaGenerate + run: ./gradlew dokkaGenerate - name: Deploy doc if: ${{ inputs.live-run || false }} diff --git a/.github/workflows/publish-jvm.yml b/.github/workflows/publish-jvm.yml deleted file mode 100644 index b77ab8421..000000000 --- a/.github/workflows/publish-jvm.yml +++ /dev/null @@ -1,202 +0,0 @@ -name: Publish (JVM) - -on: - workflow_call: - inputs: - snapshot: - required: true - type: boolean - description: "If the publication is for a snapshot version." - default: false - branch: - description: Target branch - type: string - required: false - maven_publish: - type: boolean - required: false - default: true - -env: - CARGO_TERM_COLOR: always - JNI_LIB_PATHS: jni-libs # Edit on the inner build.gradle.kts file as well. - -jobs: - builds: - name: Build for ${{ matrix.job.target }} on ${{ matrix.job.os }} - if: ${{ !(github.event.inputs.build == 'false') }} - runs-on: ${{ matrix.job.os }} - strategy: - fail-fast: false - matrix: - job: - # In order to load any added target at runtime, editing the Zenoh class under jvmMain is required. - - { - target: x86_64-unknown-linux-gnu, - arch: amd64, - os: ubuntu-latest, - build-cmd: "cargo", - } - - { - target: aarch64-unknown-linux-gnu, - arch: arm64, - os: ubuntu-latest, - build-cmd: "cross", - } - - { - target: x86_64-apple-darwin, - arch: darwin, - os: macos-latest, - build-cmd: "cargo", - } - - { - target: aarch64-apple-darwin, - arch: darwin, - os: macos-latest, - build-cmd: "cargo", - } - - { - target: x86_64-pc-windows-msvc, - arch: win64, - os: windows-latest, - build-cmd: "cargo", - } - - { - target: aarch64-pc-windows-msvc, - arch: arm64, - os: windows-latest, - build-cmd: "cargo", - } - steps: - - name: Checkout source code - uses: actions/checkout@v4 - with: - ref: ${{ inputs.branch }} - - - name: Install prerequisites - shell: bash - run: | - case ${{ matrix.job.target }} in - *-linux-gnu*) cargo +stable install cargo-deb --locked ;; - esac - - case ${{ matrix.job.target }} in - aarch64-unknown-linux-gnu) - sudo apt-get -y update - sudo apt-get -y install gcc-aarch64-linux-gnu - ;; - esac - - cargo +stable install cross --locked - - - name: Install Rust toolchain - run: | - rustup show - rustup target add ${{ matrix.job.target }} - - - name: Build - run: ${{ matrix.job.build-cmd }} build --release --bins --lib --features=${{ github.event.inputs.features}} --target=${{ matrix.job.target }} --manifest-path zenoh-jni/Cargo.toml - - - name: Packaging - id: package - shell: bash - run: | - TARGET=${{ matrix.job.target }} - MAIN_PKG_NAME="${GITHUB_WORKSPACE}/${TARGET}.zip" - - case ${TARGET} in - *linux*) - cd "zenoh-jni/target/${TARGET}/release/" - echo "Packaging ${MAIN_PKG_NAME}:" - zip ${MAIN_PKG_NAME} libzenoh_jni.so - cd - - echo "MAIN_PKG_NAME=${MAIN_PKG_NAME}" >> $GITHUB_OUTPUT - ;; - *apple*) - cd "zenoh-jni/target/${TARGET}/release/" - echo "Packaging ${MAIN_PKG_NAME}:" - zip ${MAIN_PKG_NAME} libzenoh_jni.dylib - cd - - echo "MAIN_PKG_NAME=${MAIN_PKG_NAME}" >> $GITHUB_OUTPUT - ;; - *windows*) - cd "zenoh-jni/target/${TARGET}/release/" - echo "Packaging ${MAIN_PKG_NAME}:" - 7z -y a "${MAIN_PKG_NAME}" zenoh_jni.dll - cd - - echo "MAIN_PKG_NAME=${MAIN_PKG_NAME}" >> $GITHUB_OUTPUT - ;; - esac - - - name: "Upload packages" - uses: actions/upload-artifact@v4 - with: - name: ${{ matrix.job.target }} - path: | - ${{ steps.package.outputs.MAIN_PKG_NAME }} - - publish_jvm_package: - name: Publish JVM package - needs: builds - permissions: - contents: read - packages: write - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@v4 - with: - ref: ${{ inputs.branch }} - - - name: Create resources destination - run: mkdir ${{env.JNI_LIB_PATHS}} - - - name: Download result of previous builds - uses: actions/download-artifact@v4 - with: - path: ${{env.JNI_LIB_PATHS}} - - - uses: actions/setup-java@v4 - with: - distribution: temurin - java-version: 11 - - - name: Setup Gradle - uses: gradle/actions/setup-gradle@v4 - with: - gradle-version: 8.12.1 - - - name: Gradle Wrapper - run: | - gradle wrapper - - - name: Set pub mode env var - # Note: This step is intended to allow publishing snapshot packages. - # It allows to optionally append the property -PSNAPSHOT to the gradle - # publication task on the next step, resulting in the package version - # following the convention '-SNAPSHOT'. - run: | - if [[ "${{ inputs.snapshot }}" == "true" ]]; then - echo "PUB_MODE=-PSNAPSHOT" >> $GITHUB_ENV - else - echo "RELEASE=closeAndReleaseSonatypeStagingRepository" >> $GITHUB_ENV - fi - - - if: ${{ inputs.maven_publish == true }} - name: Gradle Publish JVM Package to Maven Central repository - run: | - ./gradlew publishJvmPublicationToSonatypeRepository ${{ env.RELEASE }} --info -PremotePublication=true ${{ env.PUB_MODE }} - env: - CENTRAL_SONATYPE_TOKEN_USERNAME: ${{ secrets.CENTRAL_SONATYPE_TOKEN_USERNAME}} - CENTRAL_SONATYPE_TOKEN_PASSWORD: ${{ secrets.CENTRAL_SONATYPE_TOKEN_PASSWORD }} - ORG_GPG_KEY_ID: ${{ secrets.ORG_GPG_KEY_ID }} - ORG_GPG_SUBKEY_ID: ${{ secrets.ORG_GPG_SUBKEY_ID }} - ORG_GPG_PRIVATE_KEY: ${{ secrets.ORG_GPG_PRIVATE_KEY }} - ORG_GPG_PASSPHRASE: ${{ secrets.ORG_GPG_PASSPHRASE }} - - - name: "Upload gradle problems report" - if: always() - uses: actions/upload-artifact@v4 - with: - name: problem-reports-${{ github.job }}.zip - path: ${{ github.workspace }}/build/reports/problems/ diff --git a/.github/workflows/publish-android.yml b/.github/workflows/publish.yml similarity index 60% rename from .github/workflows/publish-android.yml rename to .github/workflows/publish.yml index f3317b251..746ca26bf 100644 --- a/.github/workflows/publish-android.yml +++ b/.github/workflows/publish.yml @@ -1,4 +1,10 @@ -name: Publish (Android) +name: Publish + +# Both publications come from a *single* Gradle invocation, so they share one +# staging repository and one close/release: either both coordinates become public +# or neither does. That is only possible because zenoh-flat-jni is a Kotlin +# Multiplatform library — one dependency declaration resolves per target, so an +# Android-enabled build produces both publications correctly. on: workflow_call: @@ -17,12 +23,13 @@ on: required: false default: true -env: - CARGO_TERM_COLOR: always - +# The native libraries are not built here: they ship inside the +# org.eclipse.zenoh:zenoh-flat-jni artifact this SDK depends on, already +# cross-compiled and verified by that repository's release. This workflow only +# compiles Kotlin and publishes. jobs: - publish_android_package: - name: Publish Android package + publish_package: + name: Publish to Maven Central runs-on: ubuntu-latest permissions: contents: read @@ -37,33 +44,14 @@ jobs: distribution: temurin java-version: 11 - - uses: nttld/setup-ndk@v1 - id: setup-ndk - with: - ndk-version: r26 - add-to-path: false - link-to-sdk: true - - - name: Install Rust toolchain - run: | - rustup show - rustup component add rustfmt clippy - - - name: Setup Rust toolchains - run: | - rustup target add armv7-linux-androideabi - rustup target add i686-linux-android - rustup target add aarch64-linux-android - rustup target add x86_64-linux-android - - name: Setup Gradle uses: gradle/actions/setup-gradle@v4 - with: - gradle-version: 8.12.1 - - name: Gradle Wrapper - run: | - gradle wrapper + # Assembles both artifacts and generates their POMs without uploading, so + # a `maven_publish: false` rehearsal actually proves something. Only the + # remote upload below is gated. + - name: Assemble and verify the publications + run: ./gradlew publishJvmPublicationToMavenLocal publishAndroidReleasePublicationToMavenLocal --info -Pandroid=true - name: Set pub mode env var # Note: This step is intended to allow publishing snapshot packages. @@ -78,9 +66,11 @@ jobs: fi - if: ${{ inputs.maven_publish == true }} - name: Gradle Publish Android Package to Maven Central repository + name: Gradle Publish to Maven Central repository run: | - ./gradlew publishAndroidReleasePublicationToSonatypeRepository ${{ env.RELEASE }} --info -PremotePublication=true -Pandroid=true ${{ env.PUB_MODE }} + ./gradlew publishJvmPublicationToSonatypeRepository \ + publishAndroidReleasePublicationToSonatypeRepository \ + ${{ env.RELEASE }} --info -PremotePublication=true -Pandroid=true ${{ env.PUB_MODE }} env: CENTRAL_SONATYPE_TOKEN_USERNAME: ${{ secrets.CENTRAL_SONATYPE_TOKEN_USERNAME }} CENTRAL_SONATYPE_TOKEN_PASSWORD: ${{ secrets.CENTRAL_SONATYPE_TOKEN_PASSWORD }} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index e874c7785..44970225f 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -26,9 +26,9 @@ on: type: string description: Release number required: false - zenoh-version: + zenoh-flat-jni-version: type: string - description: Release number of Zenoh + description: The zenoh-flat-jni release to build against (must already be on Maven Central for a live run) required: false branch: type: string @@ -67,29 +67,14 @@ jobs: env: LIVE_RUN: ${{ inputs.live-run || false }} VERSION: ${{ steps.create-release-branch.outputs.version }} - BUMP_DEPS_VERSION: ${{ inputs.zenoh-version }} - BUMP_DEPS_PATTERN: ${{ inputs.zenoh-version && 'zenoh.*' || '' }} - BUMP_DEPS_BRANCH: ${{ inputs.zenoh-version && format('release/{0}', inputs.zenoh-version) || '' }} + FLAT_JNI_VERSION: ${{ inputs.zenoh-flat-jni-version }} GIT_USER_NAME: eclipse-zenoh-bot GIT_USER_EMAIL: eclipse-zenoh-bot@users.noreply.github.com - publish-jvm: - name: Publish JVM package + publish: + name: Publish to Maven Central needs: tag - uses: ./.github/workflows/publish-jvm.yml - with: - snapshot: ${{ !(inputs.live-run || false) }} - branch: ${{ needs.tag.outputs.branch }} - maven_publish: ${{ !contains(inputs.maven_publish, 'false') }} - permissions: - contents: read - packages: write - secrets: inherit - - publish-android: - name: Publish Android package - needs: tag - uses: ./.github/workflows/publish-android.yml + uses: ./.github/workflows/publish.yml with: snapshot: ${{ !(inputs.live-run || false) }} branch: ${{ needs.tag.outputs.branch }} @@ -101,20 +86,12 @@ jobs: publish-dokka: name: Publish documentation - needs: [tag, publish-android, publish-jvm] + needs: [tag, publish] uses: ./.github/workflows/publish-dokka.yml with: live-run: ${{ inputs.live-run || false }} branch: ${{ needs.tag.outputs.branch }} - - publish-github: - needs: [tag, publish-android, publish-jvm] - runs-on: macos-latest - steps: - - uses: eclipse-zenoh/ci/publish-crates-github@main - with: - repo: ${{ github.repository }} - live-run: ${{ inputs.live-run || false }} - version: ${{ needs.tag.outputs.version }} - branch: ${{ needs.tag.outputs.branch }} - github-token: ${{ secrets.BOT_TOKEN_WORKFLOW }} + # peaceiris/actions-gh-pages pushes the generated site to the gh-pages + # branch, which the default read-only token cannot do. + permissions: + contents: write diff --git a/.gitignore b/.gitignore index ae24d8d45..262440965 100644 --- a/.gitignore +++ b/.gitignore @@ -20,8 +20,5 @@ .gradle build/ examples/build -gradle/ -gradlew -gradlew.bat local.properties zenoh-kotlin/build diff --git a/PUBLISHING.md b/PUBLISHING.md new file mode 100644 index 000000000..09345c227 --- /dev/null +++ b/PUBLISHING.md @@ -0,0 +1,275 @@ +# Publishing zenoh-kotlin + +This document describes how `zenoh-kotlin` is built, verified, and published to +Maven Central, and how to rehearse a release without publishing one. + +It describes the pipeline as it exists in this repository. Where something is +not yet implemented or not yet exercised, it is listed under +[Known gaps](#known-gaps) rather than described as if it worked. + +If you just need to run a release, go to [Running a release](#running-a-release). +For the JVM publishing concepts — coordinates, staging, signing — see +[zenoh-flat-jni's PUBLISHING.md](https://github.com/eclipse-zenoh/zenoh-flat-jni/blob/main/PUBLISHING.md#background-if-you-do-not-work-in-the-jvm-ecosystem), +which covers them once for the whole stack. + +## Contents + +- [What this repository publishes](#what-this-repository-publishes) +- [Relationship to zenoh-flat-jni](#relationship-to-zenoh-flat-jni) +- [Running a release](#running-a-release) + - [Before the first run](#before-the-first-run) + - [Rehearsal (dry run)](#rehearsal-dry-run) + - [The real release](#the-real-release) + - [After a release](#after-a-release) +- [Rehearsing before zenoh-flat-jni is released](#rehearsing-before-zenoh-flat-jni-is-released) +- [How the pipeline works](#how-the-pipeline-works) +- [Local development](#local-development) +- [Required secrets](#required-secrets) +- [Known gaps](#known-gaps) +- [Release checklist](#release-checklist) + +## What this repository publishes + +```text +org.eclipse.zenoh:zenoh-kotlin: the JVM artifact +org.eclipse.zenoh:zenoh-kotlin-android: the Android artifact +``` + +Both are **pure JVM/Kotlin**. This repository contains no Rust and builds no +native libraries: they arrive inside the zenoh-flat-jni artifacts, already +cross-compiled and verified by that repository's own release, and a consumer of +`zenoh-kotlin` gets them transitively. + +`zenoh-flat-jni` is itself a Kotlin Multiplatform library, so this SDK declares +**one** dependency on its root coordinate and Gradle resolves the variant +matching each target: + +| zenoh-kotlin artifact | resolves | which carries | +| --- | --- | --- | +| `zenoh-kotlin` | `zenoh-flat-jni-jvm` | six desktop targets | +| `zenoh-kotlin-android` | `zenoh-flat-jni-android` | four Android ABIs, as `jni//` | + +Nothing selects between them by hand, so a publication cannot name the wrong one. +And because one Gradle invocation produces both publications correctly, they are +uploaded into a single staging repository and released together. + +That automatic resolution applies to the *dependency*, not to this SDK itself. +zenoh-kotlin keeps the two plain coordinates above: the release publishes the +`jvm` and `androidRelease` publications only, never the Kotlin Multiplatform +root module, so there is no module metadata for a consumer to resolve against +and an Android consumer must name `zenoh-kotlin-android` explicitly. That is +deliberate — it keeps `org.eclipse.zenoh:zenoh-kotlin` meaning the JVM artifact, +as it always has. It is also why the publish workflow names its two publication +tasks instead of using `publishAllPublicationsTo…`: the unpublished root +publication carries the same `zenoh-kotlin` artifactId as the JVM one, and +publishing both would upload two different things to one coordinate. + +That is the whole reason the publishing here is simple — there is no build +matrix, no cross-compilation, and no native artifact to inspect. + +## Relationship to zenoh-flat-jni + +```text +zenoh-flat-jni: released to Maven Central + │ + └── zenoh-kotlin: built against it, then released +``` + +The order is not a convention, it is a constraint. `zenoh-kotlin` cannot be +released until the `zenoh-flat-jni` version it depends on is really on Maven +Central, because: + +- a release must not depend on a snapshot — consumers do not configure the + snapshot repository, and snapshots mutate and are eventually removed; +- Maven Central releases are immutable, so a `zenoh-kotlin` release pointing at + a binding version that never materializes cannot be repaired, only superseded. + +`ci/scripts/bump-and-tag.bash` enforces the first half: it refuses to run a +**live** release against a `-SNAPSHOT` binding version. A rehearsal is allowed +to, which is what makes the transition workable — see +[Rehearsing before zenoh-flat-jni is released](#rehearsing-before-zenoh-flat-jni-is-released). + +## Running a release + +Run the **Release** workflow from the Actions tab, from `main` or a release +branch. The workflow creates the release branch, bumps the version, tags it, +builds and publishes. + +### Before the first run + +- **Secrets are already in place.** `CENTRAL_SONATYPE_TOKEN_*` and `ORG_GPG_*` + are organization-level secrets on `eclipse-zenoh`, inherited automatically. +- **The zenoh-flat-jni version must already be on Maven Central** for a live + run. Check before starting: + + ```bash + curl -sfI https://repo1.maven.org/maven2/org/eclipse/zenoh/zenoh-flat-jni//zenoh-flat-jni-.pom + ``` + +### Rehearsal (dry run) + +| Field | Value | +| --- | --- | +| `live-run` | **unchecked** | +| `version` | a fresh provisional number, not one already used | +| `zenoh-flat-jni-version` | leave empty for a rehearsal, or a released version | +| `maven_publish` | checked — or uncheck for the very first run | + +`live-run` and `maven_publish` behave exactly as in zenoh-flat-jni: unchecking +`live-run` publishes `-SNAPSHOT` to the **mutable** snapshot repository +and never runs `closeAndReleaseSonatypeStagingRepository`, while `maven_publish` +decides whether any upload happens at all. **A rehearsal with `maven_publish` +checked performs a real, signed upload** — into the snapshot repository — and is +the only configuration that exercises the credentials. + +`bump-and-tag.bash` tags whatever version it is handed, rehearsals included, so +never give a rehearsal the number you intend to release. + +### The real release + +| Field | Value | +| --- | --- | +| `live-run` | **checked** | +| `version` | the release number | +| `zenoh-flat-jni-version` | the zenoh-flat-jni release to build against — **must already be on Central** | +| `maven_publish` | checked | + +Supplying `zenoh-flat-jni-version` rewrites `zenohFlatJniVersion` in +`gradle.properties` and commits it, so the published POM records exactly which +binding release the SDK was built against. + +### After a release + +Confirm the coordinates resolve, then verify the dependency is right — the POM +must reference a real `zenoh-flat-jni` release: + +```bash +curl -s https://repo1.maven.org/maven2/org/eclipse/zenoh/zenoh-kotlin//zenoh-kotlin-.pom \ + | grep -A2 zenoh-flat-jni +``` + +## Rehearsing before zenoh-flat-jni is released + +This is the common case during the transition, and it works — only the *live* +release is blocked. + +| Rehearsal | resolves zenoh-flat-jni from | proves | +| --- | --- | --- | +| local build and tests | a sibling checkout, via `-PuseLocalFlatJni=true` | the code compiles and the tests pass | +| CI, `maven_publish` unchecked | the snapshot repository | the artifacts assemble | +| CI, snapshot publication | `zenoh-flat-jni:-SNAPSHOT` | signing, credentials, a real upload | +| live release | `zenoh-flat-jni:` on Central | **blocked until that exists** | + +A snapshot may depend on a snapshot, because nothing published is permanent. So +the answer is to consume the snapshot that zenoh-flat-jni's *own* rehearsal +published — a rehearsal there with `maven_publish` enabled uploads +`zenoh-flat-jni:-SNAPSHOT` to the Central snapshot repository. + +Nothing needs editing. Name the version on the command line: + +```bash +./gradlew build -PzenohFlatJniVersion=1.9.0-rc4-SNAPSHOT +``` + +or pass the same value as the `zenoh-flat-jni-version` input to a rehearsal of +the release workflow. + +The Central snapshot repository is declared **conditionally** in +`build.gradle.kts`, and this is the part worth understanding: + +```kotlin +if (zenohFlatJniVersion.endsWith("-SNAPSHOT")) { + maven { + url = uri("https://central.sonatype.com/repository/maven-snapshots/") + content { includeGroup("org.eclipse.zenoh") } + } +} +``` + +`includeGroup`, not `includeModule`: the dependency is declared on the root +coordinate, but what Gradle actually downloads is `zenoh-flat-jni-jvm` or +`zenoh-flat-jni-android`. Naming a single module would hide those from the +snapshot repository and the build would fail to resolve. + +It enters the resolution path only when a snapshot version was explicitly asked +for, and even then serves only that one group. A release version never ends in +`-SNAPSHOT`, so a release build cannot resolve a mutable artifact — not by +policy, but because the repository that serves them is not on the path. + +## How the pipeline works + +`release.yml` runs three jobs: + +1. **`tag`** — `eclipse-zenoh/ci/create-release-branch` cuts the release branch, + then `ci/scripts/bump-and-tag.bash` writes `version.txt`, optionally rewrites + `zenohFlatJniVersion` in `gradle.properties`, commits and tags. It refuses a + `-SNAPSHOT` binding version on a live run. +2. **`publish`** — compiles Kotlin and publishes both artifacts in one Gradle + invocation, so they share one staging repository and are released together. + No native toolchain is installed and no Rust is built; both would be + pointless here. +3. **`publish-dokka`** — regenerates the API documentation and, on a live run + only, deploys it to the `gh-pages` site README.md links to. The javadoc JAR + attached to the Maven publications does not serve that site; this job does. + +Publishing goes through `io.github.gradle-nexus.publish-plugin` to the Central +Portal, signed with the organization GPG key, exactly as in zenoh-flat-jni. + +## Local development + +The default build resolves `zenoh-flat-jni` from Maven Central like any consumer. +To work against a local checkout of it: + +```bash +./gradlew build -PuseLocalFlatJni=true +``` + +That substitutes `../zenoh-flat-jni` through a Gradle composite build, so changes +there are picked up without publishing anything. Set it in your personal +`gradle.properties` if you want it always on. + +**It must never be enabled for a release.** With it on, the published artifact +would be built against whatever happens to be on the builder's disk rather than +the resolved dependency, and the POM would still claim the released version. It +is off by default and CI passes it explicitly only where it checks the sibling +out. + +## Required secrets + +| Secret | Use | +| --- | --- | +| `CENTRAL_SONATYPE_TOKEN_USERNAME` / `_PASSWORD` | Central Portal user token | +| `ORG_GPG_KEY_ID` / `_SUBKEY_ID` / `_PRIVATE_KEY` / `_PASSPHRASE` | signing | +| `BOT_TOKEN_WORKFLOW` | release branch and tag push | + +All are organization-level on `eclipse-zenoh`; nothing is configured per +repository. + +## Known gaps + +- **The rewritten release path has never run.** The workflows were repaired for + a repository that no longer contains Rust; no rehearsal has yet exercised + them. +- **No consumer test.** Unlike zenoh-flat-jni, nothing resolves the published + `zenoh-kotlin` artifact from a repository and runs it before release. The + tests here run against the build's own output. +- **No GitHub release is created.** Unlike zenoh-java, `release.yml` has no + `publish-github` job, so a release produces Maven artifacts, a tag and updated + documentation, but no GitHub release entry. This predates the flat-jni + transition and is left as it was. +- **The Android artifact has no runtime test.** Only the JVM tests run + (`jvmTest`); the Android variant is assembled and published unexercised. +- **`zenoh-flat-jni` itself has not been released**, so the ordering constraint + above has never been satisfied for a real release. + +## Release checklist + +- [ ] The `zenoh-flat-jni` version to build against is on Maven Central. +- [ ] `version.txt` and the intended tag agree. +- [ ] A rehearsal completed under a fresh version, with publication enabled at + least once so signing and credentials were exercised. +- [ ] `useLocalFlatJni` is off — the release resolves from Central. +- [ ] The published POM references a released `zenoh-flat-jni`, not a snapshot. +- [ ] For an Android release: the Android POM references + `zenoh-flat-jni-android`, not the desktop coordinate. +- [ ] The released coordinates resolve from Maven Central. diff --git a/README.md b/README.md index 6b5ee063f..757e49ad6 100644 --- a/README.md +++ b/README.md @@ -21,7 +21,7 @@ Check the website [zenoh.io](http://zenoh.io) and the [roadmap](https://github.c This repository provides a Kotlin binding based on the main [Zenoh implementation written in Rust](https://github.com/eclipse-zenoh/zenoh). -The code relies on the Zenoh JNI native library, which written in Rust and communicates with the Kotlin layer via the Java Native Interface (JNI). +The code relies on a native library written in Rust, communicating with the Kotlin layer through the Java Native Interface (JNI). That library is not built in this repository: it is generated and published separately as [zenoh-flat-jni](https://github.com/eclipse-zenoh/zenoh-flat-jni) and consumed here as an ordinary Maven dependency. ## Zenoh Documentation @@ -96,7 +96,7 @@ dependencyResolutionManagement { After that add to the dependencies in the app's `build.gradle.kts`: ```kotlin -implementation("org.eclipse.zenoh:zenoh-kotlin-jvm:1.1.1") +implementation("org.eclipse.zenoh:zenoh-kotlin:1.1.1") ``` ### Platforms @@ -118,7 +118,6 @@ For the moment, the library targets the following platforms: Basically: -- Rust ([Installation guide](https://doc.rust-lang.org/cargo/getting-started/installation.html)) - Kotlin ([Installation guide](https://kotlinlang.org/docs/getting-started.html#backend)) - Gradle ([Installation guide](https://gradle.org/install/)) @@ -126,6 +125,16 @@ and in case of targetting Android you'll also need: - Android SDK ([Installation guide](https://developer.android.com/about/versions/11/setup-sdk)) +> **Note:** zenoh-kotlin builds no native code, so no Rust toolchain and no NDK +> are needed. The generated JNI bindings and the native libraries come from +> [zenoh-flat-jni](https://github.com/eclipse-zenoh/zenoh-flat-jni), resolved as +> `org.eclipse.zenoh:zenoh-flat-jni` — a Kotlin Multiplatform library, so the JVM +> or Android variant is selected automatically. To build against a sibling +> `../zenoh-flat-jni` checkout instead, pass `-PuseLocalFlatJni=true`; that path +> does build the native library from source and so needs a Rust toolchain (see +> [rustup.rs](https://rustup.rs)). See [PUBLISHING.md](PUBLISHING.md) for how +> releases work. + ## JVM JVM To publish a library for a JVM project into Maven local, run @@ -134,13 +143,9 @@ To publish a library for a JVM project into Maven local, run gradle publishJvmPublicationToMavenLocal ``` -This will first, trigger the compilation of Zenoh-JNI, and second publish the library into maven local, containing the native library -as a resource that will be loaded during runtime. - -:warning: The native library will be compiled against the default rustup target on your machine, so although it may work fine -for you on your desktop, the generated publication may not be working on another computer with a different operating system and/or a different cpu architecture. +This publishes the zenoh-kotlin library to Maven local. The published artifact declares a dependency on `zenoh-flat-jni`, which provides the generated JNI bindings and the native binaries, and is released separately from the [zenoh-flat-jni](https://github.com/eclipse-zenoh/zenoh-flat-jni) repository. -Once we have published the package, we should be able to find it under `~/.m2/repository/org/eclipse/zenoh/zenoh-kotlin-jvm/1.1.1`. +Once we have published the package, we should be able to find it under `~/.m2/repository/org/eclipse/zenoh/zenoh-kotlin/1.1.1`. Finally, in the `build.gradle.kts` file of the project where you intend to use this library, add mavenLocal to the list of repositories and add zenoh-kotlin as a dependency: @@ -151,46 +156,19 @@ repositories { } dependencies { - implementation("org.eclipse.zenoh:zenoh-kotlin-jvm:1.1.1") + implementation("org.eclipse.zenoh:zenoh-kotlin:1.1.1") } ``` ## Android Android -In order to use these bindings in a native Android project, what we will do is to build them as an Android NDK Library, -publishing it into Maven local for us to be able to easily import it in our project. - -It is required to have the [NDK (native development kit)](https://developer.android.com/ndk) installed, since we are going to compile Zenoh JNI for multiple -android native targets. The currently used NDK version is **26.0.10792818**. -It can be set up by using Android Studio (go to `Preferences > Languages & Frameworks > Android SDK > SDK Tools`, tick `Show Package Details` and pick the right NDK version), -or alternatively it can be found [here](https://developer.android.com/ndk/downloads). - -The native platforms we are going to target are the following ones: - -- x86 -- x86_64 -- arm -- arm64 - -Therefore, if they are not yet already added to the Rust toolchain, run: - -```bash -rustup target add armv7-linux-androideabi; \ -rustup target add i686-linux-android; \ -rustup target add aarch64-linux-android; \ -rustup target add x86_64-linux-android -``` - -to install them. - -So, in order to publish the library onto Maven Local, run: +In order to use these bindings in a native Android project, publish them into Maven local: ```bash gradle -Pandroid=true publishAndroidReleasePublicationToMavenLocal ``` -This will first trigger the compilation of the Zenoh-JNI for the previously mentioned targets, and secondly will -publish the library, containing the native binaries. +This publishes the zenoh-kotlin-android artifact to Maven local. It declares a dependency on `zenoh-flat-jni`, whose Android variant carries the prebuilt native libraries for all four ABIs, released separately from the [zenoh-flat-jni](https://github.com/eclipse-zenoh/zenoh-flat-jni) repository — no Rust toolchain and no NDK cross-compilation is required here. You should now be able to see the package under `~/.m2/repository/org/eclipse/zenoh/zenoh-kotlin-android/1.1.1`. @@ -226,13 +204,22 @@ gradle dokkaGenerate ## Running the tests -To run the tests, run: - ```bash gradle jvmTest ``` -This will compile the native library on debug mode (if not already available) and run the tests afterward against the JVM target. +By default this resolves `zenoh-flat-jni` from Maven Central, so no Rust +toolchain is involved. To run the tests against a sibling `../zenoh-flat-jni` +checkout instead — which is what CI does, and what you want when changing both +repositories together — add `-PuseLocalFlatJni=true`: + +```bash +gradle jvmTest -PuseLocalFlatJni=true +``` + +That substitutes the artifact through a Gradle composite build and does compile +the native library from source, so it requires a Rust toolchain (see +[rustup.rs](https://rustup.rs)). ## Logging @@ -293,6 +280,6 @@ Then after that, add the dependency as usual: ```kotlin dependencies { - implementation("org.eclipse.zenoh:zenoh-kotlin-jvm:") + implementation("org.eclipse.zenoh:zenoh-kotlin:") } ``` diff --git a/build.gradle.kts b/build.gradle.kts index 6ea68a7dc..a52d2ad73 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -18,7 +18,6 @@ buildscript { } dependencies { classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:1.9.0") - classpath("org.mozilla.rust-android-gradle:plugin:0.9.6") classpath("com.android.tools.build:gradle:7.4.2") classpath("com.gradleup.shadow:shadow-gradle-plugin:9.0.0-beta6") } @@ -28,7 +27,6 @@ plugins { id("com.android.library") version "7.4.2" apply false id("org.jetbrains.kotlin.android") version "1.9.10" apply false id("org.jetbrains.kotlin.multiplatform") version "1.9.0" apply false - id("org.mozilla.rust-android-gradle.rust-android") version "0.9.6" apply false id("org.jetbrains.dokka") version "2.0.0" apply false id("com.adarshr.test-logger") version "3.2.0" apply false kotlin("plugin.serialization") version "1.9.0" apply false @@ -56,9 +54,27 @@ nexusPublishing { } } +val zenohFlatJniVersion: String by project + subprojects { repositories { google() mavenCentral() + + // Only reachable when a snapshot of the binding was explicitly asked + // for, which is how this SDK is rehearsed before zenoh-flat-jni has a + // real release. A release version never ends in -SNAPSHOT, so a release + // build cannot resolve a mutable artifact. + // + // includeGroup, not includeModule: the dependency names the root + // coordinate, but what Gradle downloads is zenoh-flat-jni-jvm or + // zenoh-flat-jni-android. Filtering to one module would hide those. + if (zenohFlatJniVersion.endsWith("-SNAPSHOT")) { + maven { + name = "centralSnapshots" + url = uri("https://central.sonatype.com/repository/maven-snapshots/") + content { includeGroup("org.eclipse.zenoh") } + } + } } } diff --git a/ci/scripts/bump-and-tag.bash b/ci/scripts/bump-and-tag.bash index 5b5f7ee4c..70cad84f6 100644 --- a/ci/scripts/bump-and-tag.bash +++ b/ci/scripts/bump-and-tag.bash @@ -5,58 +5,49 @@ set -xeo pipefail readonly live_run=${LIVE_RUN:-false} # Release number readonly version=${VERSION:?input VERSION is required} -# Dependencies' pattern -readonly bump_deps_pattern=${BUMP_DEPS_PATTERN:-''} -# Dependencies' version -readonly bump_deps_version=${BUMP_DEPS_VERSION:-''} -# Dependencies' git branch -readonly bump_deps_branch=${BUMP_DEPS_BRANCH:-''} # Git actor name readonly git_user_name=${GIT_USER_NAME:?input GIT_USER_NAME is required} # Git actor email readonly git_user_email=${GIT_USER_EMAIL:?input GIT_USER_EMAIL is required} - -cargo +stable install toml-cli - -# NOTE(fuzzypixelz): toml-cli doesn't yet support in-place modification -# See: https://github.com/gnprice/toml-cli?tab=readme-ov-file#writing-ish-toml-set -function toml_set_in_place() { - local tmp=$(mktemp) - toml set "$1" "$2" "$3" > "$tmp" - mv "$tmp" "$1" -} +# The zenoh-flat-jni release to build against, if it is moving with this release +readonly flat_jni_version=${FLAT_JNI_VERSION:-''} export GIT_AUTHOR_NAME=$git_user_name export GIT_AUTHOR_EMAIL=$git_user_email export GIT_COMMITTER_NAME=$git_user_name export GIT_COMMITTER_EMAIL=$git_user_email -# Bump Gradle project version +# Bump Gradle project version. There is no Cargo manifest here any more: the +# native libraries live inside the zenoh-flat-jni artifact this SDK depends on. printf '%s' "$version" > version.txt -# Propagate version change to zenoh-jni -toml_set_in_place zenoh-jni/Cargo.toml "package.version" "$version" - -git commit version.txt zenoh-jni/Cargo.toml -m "chore: Bump version to \`$version\`" - -# Select all package dependencies that match $bump_deps_pattern and bump them to $bump_deps_version -if [[ "$bump_deps_pattern" != '' ]]; then - deps=$(toml get zenoh-jni/Cargo.toml dependencies | jq -r "keys[] | select(test(\"$bump_deps_pattern\"))") - for dep in $deps; do - if [[ -n $bump_deps_version ]]; then - toml_set_in_place zenoh-jni/Cargo.toml "dependencies.$dep.version" "$bump_deps_version" - fi - - if [[ -n $bump_deps_branch ]]; then - toml_set_in_place zenoh-jni/Cargo.toml "dependencies.$dep.branch" "$bump_deps_branch" - fi - done - # Update lockfile - cargo check --manifest-path zenoh-jni/Cargo.toml - if [[ -n $bump_deps_version || -n $bump_deps_branch ]]; then - git commit zenoh-jni/Cargo.toml zenoh-jni/Cargo.lock -m "chore: Bump \`$bump_deps_pattern\` dependencies to \`$bump_deps_version\`" +git commit version.txt -m "chore: Bump version to \`$version\`" + +# Point at the zenoh-flat-jni release this SDK is built against. +if [[ -n "$flat_jni_version" ]]; then + # A *release* may not depend on a snapshot: consumers do not configure the + # snapshot repository, and snapshots mutate and expire. A rehearsal may — that + # is how the SDK is exercised before the binding is released at all. + case "$flat_jni_version" in + *-SNAPSHOT) + if [[ "$live_run" == "true" ]]; then + echo "error: refusing to release against a snapshot dependency ($flat_jni_version)" >&2 + exit 1 + fi + echo "note: rehearsing against snapshot $flat_jni_version" + ;; + esac + + sed -i.bak -E "s|^zenohFlatJniVersion=.*|zenohFlatJniVersion=$flat_jni_version|" gradle.properties + rm -f gradle.properties.bak + + # Only commit when it actually moved: `git commit` on an unchanged file exits + # non-zero, which under `set -e` would abort the release before tagging. + if git diff --quiet gradle.properties; then + echo "note: already building against zenoh-flat-jni $flat_jni_version" else - echo "warn: no changes have been made to any dependencies matching $bump_deps_pattern" + git diff gradle.properties + git commit gradle.properties -m "chore: Build against zenoh-flat-jni \`$flat_jni_version\`" fi fi diff --git a/examples/build.gradle.kts b/examples/build.gradle.kts index 4013e0d24..a890df2fb 100644 --- a/examples/build.gradle.kts +++ b/examples/build.gradle.kts @@ -80,19 +80,9 @@ tasks { examples.forEach { example -> register(example, JavaExec::class) { - dependsOn("CompileZenohJNI") description = "Run the $example example" mainClass.set("io.zenoh.${example}Kt") classpath(sourceSets["main"].runtimeClasspath) - val zenohPaths = "../zenoh-jni/target/release" - val defaultJvmArgs = arrayListOf("-Djava.library.path=$zenohPaths") - jvmArgs(defaultJvmArgs) } } } - -tasks.register("CompileZenohJNI") { - project.exec { - commandLine("cargo", "build", "--release", "--manifest-path", "../zenoh-jni/Cargo.toml") - } -} diff --git a/examples/src/main/kotlin/io.zenoh/ZQueryable.kt b/examples/src/main/kotlin/io.zenoh/ZQueryable.kt index a4c8b946b..a1b398d1e 100644 --- a/examples/src/main/kotlin/io.zenoh/ZQueryable.kt +++ b/examples/src/main/kotlin/io.zenoh/ZQueryable.kt @@ -23,6 +23,7 @@ import io.zenoh.keyexpr.intoKeyExpr import io.zenoh.query.Query import kotlinx.coroutines.channels.Channel import kotlinx.coroutines.runBlocking +import io.zenoh.time.Timestamp import org.apache.commons.net.ntp.TimeStamp import java.util.concurrent.CountDownLatch @@ -47,6 +48,9 @@ class ZQueryable(private val emptyArgs: Boolean) : CliktCommand( } private fun runChannelExample(session: Session, keyExpr: KeyExpr) { + // A timestamp is the pair (instant, id of the clock that produced + // it), so a reply stamps with THIS session's id. + val zid = session.info().zid().getOrThrow() println("Declaring Queryable on $key...") val queryable = session.declareQueryable(keyExpr, Channel()).getOrThrow() runBlocking { @@ -56,7 +60,7 @@ class ZQueryable(private val emptyArgs: Boolean) : CliktCommand( query.reply( keyExpr, payload = ZBytes.from(value), - timestamp = TimeStamp.getCurrentTime() + timestamp = Timestamp.ofNtp64(TimeStamp.getCurrentTime().ntpValue(), zid) ).onFailure { println(">> [Queryable ] Error sending reply: $it") } } } @@ -64,6 +68,9 @@ class ZQueryable(private val emptyArgs: Boolean) : CliktCommand( } private fun runCallbackExample(session: Session, keyExpr: KeyExpr) { + // A timestamp is the pair (instant, id of the clock that produced + // it), so a reply stamps with THIS session's id. + val zid = session.info().zid().getOrThrow() println("Declaring Queryable on $key...") val queryable = session.declareQueryable(keyExpr, callback = { query -> val valueInfo = query.payload?.let { value -> " with value '$value'" } ?: "" @@ -71,7 +78,7 @@ class ZQueryable(private val emptyArgs: Boolean) : CliktCommand( query.reply( keyExpr, payload = ZBytes.from(value), - timestamp = TimeStamp.getCurrentTime() + timestamp = Timestamp.ofNtp64(TimeStamp.getCurrentTime().ntpValue(), zid) ).onFailure { println(">> [Queryable ] Error sending reply: $it") } }).getOrThrow() @@ -82,6 +89,9 @@ class ZQueryable(private val emptyArgs: Boolean) : CliktCommand( } private fun runHandlerExample(session: Session, keyExpr: KeyExpr) { + // A timestamp is the pair (instant, id of the clock that produced + // it), so a reply stamps with THIS session's id. + val zid = session.info().zid().getOrThrow() // Create your own handler implementation class ExampleHandler : Handler { @@ -91,7 +101,7 @@ class ZQueryable(private val emptyArgs: Boolean) : CliktCommand( t.reply( keyExpr, payload = ZBytes.from(value), - timestamp = TimeStamp.getCurrentTime() + timestamp = Timestamp.ofNtp64(TimeStamp.getCurrentTime().ntpValue(), zid) ).onFailure { println(">> [Queryable ] Error sending reply: $it") } } diff --git a/gradle.properties b/gradle.properties index 5c311496a..3d1a48dfa 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,2 +1,11 @@ org.jetbrains.dokka.experimental.gradle.pluginMode=V2Enabled org.jetbrains.dokka.experimental.gradle.pluginMode.noWarn=true + +# The zenoh-flat-jni release this SDK is built against. Rewritten by +# ci/scripts/bump-and-tag.bash at release time; override for a rehearsal with +# -PzenohFlatJniVersion=1.9.0-rc3-SNAPSHOT. +zenohFlatJniVersion=1.9.0 + +# Substitute a sibling ../zenoh-flat-jni checkout instead of resolving the +# artifact. Never enable this for a release. +useLocalFlatJni=false diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 000000000..a4b76b953 Binary files /dev/null and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 000000000..63532c5ec --- /dev/null +++ b/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,8 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-8.12.1-bin.zip +distributionSha256Sum=8d97a97984f6cbd2b85fe4c60a743440a347544bf18818048e611f5288d46c94 +networkTimeout=10000 +validateDistributionUrl=true +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew new file mode 100755 index 000000000..057afac53 --- /dev/null +++ b/gradlew @@ -0,0 +1,251 @@ +#!/bin/sh + +# +# Copyright © 2015-2021 the original authors. +# +# 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 +# +# https://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. +# +# SPDX-License-Identifier: Apache-2.0 +# + +############################################################################## +# +# Gradle start up script for POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh Gradle +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# +############################################################################## + +# Attempt to set APP_HOME + +# Resolve links: $0 may be a link +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac +done + +# This is normally unused +# shellcheck disable=SC2034 +APP_BASE_NAME=${0##*/} +# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) +APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD=maximum + +warn () { + echo "$*" +} >&2 + +die () { + echo + echo "$*" + echo + exit 1 +} >&2 + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD=$JAVA_HOME/jre/sh/java + else + JAVACMD=$JAVA_HOME/bin/java + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD=java + if ! command -v java >/dev/null 2>&1 + then + die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +fi + +# Increase the maximum file descriptors if we can. +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac +fi + +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. + +# For Cygwin or MSYS, switch paths to Windows format before running java +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + + # Now convert the arguments - kludge to limit ourselves to /bin/sh + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) + fi + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg + done +fi + + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='-Dfile.encoding=UTF-8 "-Xmx64m" "-Xms64m"' + +# Collect all arguments for the java command: +# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# and any embedded shellness will be escaped. +# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be +# treated as '${Hostname}' itself on the command line. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + org.gradle.wrapper.GradleWrapperMain \ + "$@" + +# Stop when "xargs" is not available. +if ! command -v xargs >/dev/null 2>&1 +then + die "xargs is not available" +fi + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' + +exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat new file mode 100644 index 000000000..640d68685 --- /dev/null +++ b/gradlew.bat @@ -0,0 +1,94 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem +@rem SPDX-License-Identifier: Apache-2.0 +@rem + +@if "%DEBUG%"=="" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%"=="" set DIRNAME=. +@rem This is normally unused +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS=-Dfile.encoding=UTF-8 "-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if %ERRORLEVEL% equ 0 goto execute + +echo. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* + +:end +@rem End local scope for the variables with windows NT shell +if %ERRORLEVEL% equ 0 goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +set EXIT_CODE=%ERRORLEVEL% +if %EXIT_CODE% equ 0 set EXIT_CODE=1 +if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% +exit /b %EXIT_CODE% + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/rust-toolchain.toml b/rust-toolchain.toml deleted file mode 100644 index 724360432..000000000 --- a/rust-toolchain.toml +++ /dev/null @@ -1,2 +0,0 @@ -[toolchain] -channel = "1.97.1" diff --git a/settings.gradle.kts b/settings.gradle.kts index 6126e8ce5..1eca211c8 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -23,7 +23,17 @@ rootProject.name = "zenoh-kotlin" include(":zenoh-kotlin") include(":examples") -include(":zenoh-jni") + +// zenoh-flat-jni (https://github.com/eclipse-zenoh/zenoh-flat-jni) is consumed as +// an ordinary Maven artifact: org.eclipse.zenoh:zenoh-flat-jni:$zenohFlatJniVersion. +// +// For coordinated local development, `-PuseLocalFlatJni=true` substitutes a +// sibling checkout through a composite build. It is off by default and must stay +// off for a release: with it on, the published artifact would be built against +// whatever is on that developer's disk rather than the resolved dependency. +if (providers.gradleProperty("useLocalFlatJni").orNull?.toBoolean() == true) { + includeBuild("../zenoh-flat-jni") +} plugins { id("org.gradle.toolchains.foojay-resolver-convention") version("0.4.0") diff --git a/zenoh-jni/Cargo.lock b/zenoh-jni/Cargo.lock deleted file mode 100644 index 4f503f1f8..000000000 --- a/zenoh-jni/Cargo.lock +++ /dev/null @@ -1,4269 +0,0 @@ -# This file is automatically @generated by Cargo. -# It is not intended for manual editing. -version = 3 - -[[package]] -name = "adler2" -version = "2.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" - -[[package]] -name = "aes" -version = "0.8.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0" -dependencies = [ - "cfg-if", - "cipher", - "cpufeatures 0.2.17", -] - -[[package]] -name = "ahash" -version = "0.8.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75" -dependencies = [ - "cfg-if", - "getrandom 0.3.4", - "once_cell", - "version_check", - "zerocopy", -] - -[[package]] -name = "aho-corasick" -version = "1.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c982642fa9e8606056828ee9a8505737230110bb1099153c79efe865c59d12ba" -dependencies = [ - "memchr", -] - -[[package]] -name = "allocator-api2" -version = "0.2.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" - -[[package]] -name = "android-logd-logger" -version = "0.4.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0483169d5fac0887f85c2fa8fecfe08669791712d8260de1a6ec30630a62932f" -dependencies = [ - "bytes", - "env_logger", - "lazy_static", - "libc", - "log", - "parking_lot", - "redox_syscall 0.4.1", - "thiserror 1.0.69", - "time", - "winapi", -] - -[[package]] -name = "android_system_properties" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae221649c9976a6f6c56ae1facf410f3ddb33cc661c4b7b61020a912d4237fbc" -dependencies = [ - "libc", -] - -[[package]] -name = "anyhow" -version = "1.0.104" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "330a5ed07fa54e4702c9d6c4174f74427fc0ef6e214bbd677ae50a5099946470" - -[[package]] -name = "arc-swap" -version = "1.9.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c049c0be4daef0b145cb3555416b3b8ef5b7888a38aea1a3a155801fe7b0810b" -dependencies = [ - "rustversion", -] - -[[package]] -name = "array-init" -version = "2.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d62b7694a562cdf5a74227903507c56ab2cc8bdd1f781ed5cb4cf9c9f810bfc" - -[[package]] -name = "asn1-rs" -version = "0.7.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7f43a50ac4fdca5df8e885c21b835997f0a1cdee65494a6847694a98652d9d8" -dependencies = [ - "asn1-rs-derive", - "asn1-rs-impl", - "displaydoc", - "nom", - "num-traits", - "rusticata-macros", - "thiserror 2.0.20", - "time", -] - -[[package]] -name = "asn1-rs-derive" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3109e49b1e4909e9db6515a30c633684d68cdeaa252f215214cb4fa1a5bfee2c" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.119", - "synstructure", -] - -[[package]] -name = "asn1-rs-impl" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b18050c2cd6fe86c3a76584ef5e0baf286d038cda203eb6223df2cc413565f7" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.119", -] - -[[package]] -name = "async-std" -version = "1.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62565bb4402e926b29953c785397c6dc0391b7b446e45008b0049eb43cec6f5d" - -[[package]] -name = "async-trait" -version = "0.1.92" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82f6aeea286b8eb4dd3431a1be1b59d290ace00f5bfd8e2a159bc2a05e2c1667" -dependencies = [ - "proc-macro2", - "quote", - "syn 3.0.3", -] - -[[package]] -name = "atty" -version = "0.2.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" -dependencies = [ - "hermit-abi 0.1.19", - "libc", - "winapi", -] - -[[package]] -name = "autocfg" -version = "1.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53" - -[[package]] -name = "base64" -version = "0.22.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" - -[[package]] -name = "base64ct" -version = "1.8.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2af50177e190e07a26ab74f8b1efbfe2ef87da2116221318cb1c2e82baf7de06" - -[[package]] -name = "bincode" -version = "1.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad" -dependencies = [ - "serde", -] - -[[package]] -name = "bit-vec" -version = "0.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b71798fca2c1fe1086445a7258a4bc81e6e49dcd24c8d0dd9a1e57395b603f51" -dependencies = [ - "serde", -] - -[[package]] -name = "bitflags" -version = "1.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" - -[[package]] -name = "bitflags" -version = "2.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b588b76d00fde79687d7646a9b5bdf3cc0f655e0bbd080335a95d7e96f3587da" -dependencies = [ - "serde_core", -] - -[[package]] -name = "block-buffer" -version = "0.10.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" -dependencies = [ - "generic-array", -] - -[[package]] -name = "bs58" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf88ba1141d185c399bee5288d850d63b8369520c1eafc32a0430b5b6c287bf4" -dependencies = [ - "tinyvec", -] - -[[package]] -name = "bumpalo" -version = "3.20.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649" - -[[package]] -name = "byteorder" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" - -[[package]] -name = "bytes" -version = "1.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc652a48c352aef3ea3aed32080501cf3ef6ed5da78602a020c991775b0aff04" - -[[package]] -name = "cc" -version = "1.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d262e149917187838d5b42777c8253bcb64500067342904e7d429499a6f277e" -dependencies = [ - "find-msvc-tools", - "shlex", -] - -[[package]] -name = "cesu8" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c" - -[[package]] -name = "cfg-if" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" - -[[package]] -name = "cfg_aliases" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f079e83a288787bcd14a6aea84cee5c87a67c5a3e660c30f557a3d24761b3527" - -[[package]] -name = "chacha20" -version = "0.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d524456ba66e72eb8b115ff89e01e497f8e6d11d78b70b1aa13c0fbd97540a81" -dependencies = [ - "cfg-if", - "cpufeatures 0.3.0", - "rand_core 0.10.1", -] - -[[package]] -name = "chrono" -version = "0.4.45" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1aa79e62e7697b8e29b513a68abacf485adcd1fe8284a4316c5ae868e6633327" -dependencies = [ - "iana-time-zone", - "num-traits", - "serde", - "windows-link", -] - -[[package]] -name = "cipher" -version = "0.4.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" -dependencies = [ - "crypto-common", - "inout", -] - -[[package]] -name = "clap" -version = "3.2.25" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ea181bf566f71cb9a5d17a59e1871af638180a18fb0035c92ae62b705207123" -dependencies = [ - "atty", - "bitflags 1.3.2", - "clap_lex", - "indexmap 1.9.3", - "strsim 0.10.0", - "termcolor", - "textwrap", -] - -[[package]] -name = "clap_lex" -version = "0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2850f2f5a82cbf437dd5af4d49848fbdfc27c157c3d010345776f952765261c5" -dependencies = [ - "os_str_bytes", -] - -[[package]] -name = "combine" -version = "4.6.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd" -dependencies = [ - "bytes", - "memchr", -] - -[[package]] -name = "const-oid" -version = "0.9.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" - -[[package]] -name = "const_format" -version = "0.2.36" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4481a617ad9a412be3b97c5d403fef8ed023103368908b9c50af598ff467cc1e" -dependencies = [ - "const_format_proc_macros", - "konst", -] - -[[package]] -name = "const_format_proc_macros" -version = "0.2.34" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d57c2eccfb16dbac1f4e61e206105db5820c9d26c3c472bc17c774259ef7744" -dependencies = [ - "proc-macro2", - "quote", - "unicode-xid", -] - -[[package]] -name = "core-foundation" -version = "0.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2a6cd9ae233e7f62ba4e9353e81a88df7fc8a5987b8d445b4d90c879bd156f6" -dependencies = [ - "core-foundation-sys", - "libc", -] - -[[package]] -name = "core-foundation-sys" -version = "0.8.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" - -[[package]] -name = "cpufeatures" -version = "0.2.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" -dependencies = [ - "libc", -] - -[[package]] -name = "cpufeatures" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b2a41393f66f16b0823bb79094d54ac5fbd34ab292ddafb9a0456ac9f87d201" -dependencies = [ - "libc", -] - -[[package]] -name = "crc32fast" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511" -dependencies = [ - "cfg-if", -] - -[[package]] -name = "crossbeam" -version = "0.8.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1137cd7e7fc0fb5d3c5a8678be38ec56e819125d8d7907411fe24ccb943faca8" -dependencies = [ - "crossbeam-channel", - "crossbeam-deque", - "crossbeam-epoch", - "crossbeam-queue", - "crossbeam-utils", -] - -[[package]] -name = "crossbeam-channel" -version = "0.5.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d85363c37faeca707aef026efa9f3b34d077bce547e48f770770625c6013679e" -dependencies = [ - "crossbeam-utils", -] - -[[package]] -name = "crossbeam-deque" -version = "0.8.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5181e0de7b61eb03a81e347d6dd8797bae9da5146707b51077e2d71a54ec0ceb" -dependencies = [ - "crossbeam-epoch", - "crossbeam-utils", -] - -[[package]] -name = "crossbeam-epoch" -version = "0.9.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d6914041f254d6e9176c01941b21115dcfb7089e55135a35411081bd106ef3f" -dependencies = [ - "crossbeam-utils", -] - -[[package]] -name = "crossbeam-queue" -version = "0.3.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "803d13fb3b09d88be9f4dbc29062c66b19bf7170867ceb746d2a8689bf6c7a26" -dependencies = [ - "crossbeam-utils", -] - -[[package]] -name = "crossbeam-utils" -version = "0.8.22" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61803da095bee82a81bb1a452ecc25d3b2f1416d1897eb86430c6159ef717c17" - -[[package]] -name = "crypto-common" -version = "0.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a" -dependencies = [ - "generic-array", - "typenum", -] - -[[package]] -name = "darling" -version = "0.23.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25ae13da2f202d56bd7f91c25fba009e7717a1e4a1cc98a76d844b65ae912e9d" -dependencies = [ - "darling_core", - "darling_macro", -] - -[[package]] -name = "darling_core" -version = "0.23.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9865a50f7c335f53564bb694ef660825eb8610e0a53d3e11bf1b0d3df31e03b0" -dependencies = [ - "ident_case", - "proc-macro2", - "quote", - "strsim 0.11.1", - "syn 2.0.119", -] - -[[package]] -name = "darling_macro" -version = "0.23.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac3984ec7bd6cfa798e62b4a642426a5be0e68f9401cfc2a01e3fa9ea2fcdb8d" -dependencies = [ - "darling_core", - "quote", - "syn 2.0.119", -] - -[[package]] -name = "data-encoding" -version = "2.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4583a4551df46e2792f82ceeac45e850d2e2d5debba0b91f102385cda5b11f06" - -[[package]] -name = "der" -version = "0.7.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7c1832837b905bbfb5101e07cc24c8deddf52f93225eee6ead5f4d63d53ddcb" -dependencies = [ - "const-oid", - "pem-rfc7468", - "zeroize", -] - -[[package]] -name = "der-parser" -version = "10.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07da5016415d5a3c4dd39b11ed26f915f52fc4e0dc197d87908bc916e51bc1a6" -dependencies = [ - "asn1-rs", - "displaydoc", - "nom", - "num-bigint", - "num-traits", - "rusticata-macros", -] - -[[package]] -name = "deranged" -version = "0.5.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c" -dependencies = [ - "serde_core", -] - -[[package]] -name = "digest" -version = "0.10.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" -dependencies = [ - "block-buffer", - "const-oid", - "crypto-common", - "subtle", -] - -[[package]] -name = "dirs" -version = "6.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3e8aa94d75141228480295a7d0e7feb620b1a5ad9f12bc40be62411e38cce4e" -dependencies = [ - "dirs-sys", -] - -[[package]] -name = "dirs-sys" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e01a3366d27ee9890022452ee61b2b63a67e6f13f58900b651ff5665f0bb1fab" -dependencies = [ - "libc", - "option-ext", - "redox_users", - "windows-sys 0.61.2", -] - -[[package]] -name = "displaydoc" -version = "0.2.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6232dd377dcc64799954cbd3a9bb882e9cdc1308ccd87b1c098f1fb2eaf82a8" -dependencies = [ - "proc-macro2", - "quote", - "syn 3.0.3", -] - -[[package]] -name = "dyn-clone" -version = "1.0.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555" - -[[package]] -name = "either" -version = "1.17.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e5e8f6c15a24b9a3ee5efec809ccd006d3b30e8b3bb63c39af737c7f87daa1d" - -[[package]] -name = "env_logger" -version = "0.10.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4cd405aab171cb85d6735e5c8d9db038c17d3ca007a4d2c25f337935c3d90580" -dependencies = [ - "log", - "regex", -] - -[[package]] -name = "equivalent" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" - -[[package]] -name = "event-listener" -version = "5.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a23add41df1562121a9393cb065eab5146a1242410f23a644851e90cfd669d2" -dependencies = [ - "parking", - "pin-project-lite", -] - -[[package]] -name = "fastbloom" -version = "0.17.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef975e30683b2d965054bb0a836f8973857c4ebf6acf274fe46617cd285060d8" -dependencies = [ - "foldhash 0.2.0", - "libm", - "portable-atomic", - "siphasher", -] - -[[package]] -name = "fastrand" -version = "2.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da7c62ceae207dd37ea5b845da6a0696c799f85e97da1ab5b7910be3c1c80223" - -[[package]] -name = "find-msvc-tools" -version = "0.1.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26b73573e6edcd2af0cdf47bd6cb58f0b3839491263c314eaad1ccf24430e1de" - -[[package]] -name = "fixedbitset" -version = "0.5.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d674e81391d1e1ab681a28d99df07927c6d4aa5b027d7da16ba32d1d21ecd99" - -[[package]] -name = "flate2" -version = "1.1.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "843fba2746e448b37e26a819579957415c8cef339bf08564fe8b7ddbd959573c" -dependencies = [ - "crc32fast", - "miniz_oxide", -] - -[[package]] -name = "flume" -version = "0.10.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1657b4441c3403d9f7b3409e47575237dac27b1b5726df654a6ecbf92f0f7577" -dependencies = [ - "futures-core", - "futures-sink", - "nanorand", - "pin-project", - "spin 0.9.9", -] - -[[package]] -name = "flume" -version = "0.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da0e4dd2a88388a1f4ccc7c9ce104604dab68d9f408dc34cd45823d5a9069095" -dependencies = [ - "futures-core", - "futures-sink", - "nanorand", - "spin 0.9.9", -] - -[[package]] -name = "foldhash" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" - -[[package]] -name = "foldhash" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb" - -[[package]] -name = "form_urlencoded" -version = "1.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf" -dependencies = [ - "percent-encoding", -] - -[[package]] -name = "futures" -version = "0.3.33" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a88cf1f829d945f548cf8fec32c61b1f202b6d93b45848602fc02af4b12ad218" -dependencies = [ - "futures-channel", - "futures-core", - "futures-executor", - "futures-io", - "futures-sink", - "futures-task", - "futures-util", -] - -[[package]] -name = "futures-channel" -version = "0.3.33" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "262590f4fe6afeb0bc83be1daa64e52657fe185690a958af7f3ad0e92085c5ae" -dependencies = [ - "futures-core", - "futures-sink", -] - -[[package]] -name = "futures-core" -version = "0.3.33" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2cd50c473c80f6d7c3670a752354b8e569b1a7cbfdc0419ec88e5edad85e0dc7" - -[[package]] -name = "futures-executor" -version = "0.3.33" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6754879cc9f2c66f88c6e5c35344bb0bdb0708b0352b1201815667c7eabc7458" -dependencies = [ - "futures-core", - "futures-task", - "futures-util", -] - -[[package]] -name = "futures-io" -version = "0.3.33" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4577ecaa3c4f96589d473f679a71b596316f6641bc350038b962a5daf0085d7a" - -[[package]] -name = "futures-macro" -version = "0.3.33" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d6d3cde68c518367be28956066ddfef33813991b77a55005a69dae04bf3b10b" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.119", -] - -[[package]] -name = "futures-sink" -version = "0.3.33" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e34418ac499d6305c2fb5ad0ed2f6ac998c5f8ca209b4510f7f94242c647e307" - -[[package]] -name = "futures-task" -version = "0.3.33" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b231ed28831efb4a61a08580c4bc233ec56bc009f4cd8f52da2c3cb97df0c109" - -[[package]] -name = "futures-util" -version = "0.3.33" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a77a90a256fce34da66415271e30f94ee91c57b04b8a2c042d9cf3220179deaa" -dependencies = [ - "futures-channel", - "futures-core", - "futures-io", - "futures-macro", - "futures-sink", - "futures-task", - "memchr", - "pin-project-lite", - "slab", -] - -[[package]] -name = "generic-array" -version = "0.14.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" -dependencies = [ - "typenum", - "version_check", -] - -[[package]] -name = "getrandom" -version = "0.2.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" -dependencies = [ - "cfg-if", - "js-sys", - "libc", - "wasi", - "wasm-bindgen", -] - -[[package]] -name = "getrandom" -version = "0.3.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" -dependencies = [ - "cfg-if", - "libc", - "r-efi 5.3.0", - "wasip2", -] - -[[package]] -name = "getrandom" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "300e883d756b2e4ec94e02791f39b04b522276138852cfc41d9fb7e904106099" -dependencies = [ - "cfg-if", - "js-sys", - "libc", - "r-efi 6.0.0", - "rand_core 0.10.1", - "wasm-bindgen", -] - -[[package]] -name = "git-version" -version = "0.3.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ad568aa3db0fcbc81f2f116137f263d7304f512a1209b35b85150d3ef88ad19" -dependencies = [ - "git-version-macro", -] - -[[package]] -name = "git-version-macro" -version = "0.3.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53010ccb100b96a67bc32c0175f0ed1426b31b655d562898e57325f81c023ac0" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.119", -] - -[[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.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" -dependencies = [ - "ahash", - "allocator-api2", -] - -[[package]] -name = "hashbrown" -version = "0.15.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" -dependencies = [ - "foldhash 0.1.5", -] - -[[package]] -name = "hashbrown" -version = "0.16.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" -dependencies = [ - "allocator-api2", - "equivalent", - "foldhash 0.2.0", -] - -[[package]] -name = "hashbrown" -version = "0.17.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" - -[[package]] -name = "hermit-abi" -version = "0.1.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" -dependencies = [ - "libc", -] - -[[package]] -name = "hermit-abi" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c" - -[[package]] -name = "hex" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" - -[[package]] -name = "hmac" -version = "0.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" -dependencies = [ - "digest", -] - -[[package]] -name = "home" -version = "0.5.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc627f471c528ff0c4a49e1d5e60450c8f6461dd6d10ba9dcd3a61d3dff7728d" -dependencies = [ - "windows-sys 0.61.2", -] - -[[package]] -name = "http" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "918d3568bebf352712bc2ef3d46a8bcf1a75b373be6539de198e9105cbbf9ce0" -dependencies = [ - "bytes", - "itoa", -] - -[[package]] -name = "httparse" -version = "1.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" - -[[package]] -name = "humantime" -version = "2.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "15cdd26707701c53297e2fa6afb323d55fbc1d0810c3aec078ae3ef0424c3c15" - -[[package]] -name = "iana-time-zone" -version = "0.1.65" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e31bc9ad994ba00e440a8aa5c9ef0ec67d5cb5e5cb0cc7f8b744a35b389cc470" -dependencies = [ - "android_system_properties", - "core-foundation-sys", - "iana-time-zone-haiku", - "js-sys", - "log", - "wasm-bindgen", - "windows-core", -] - -[[package]] -name = "iana-time-zone-haiku" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" -dependencies = [ - "cc", -] - -[[package]] -name = "icu_collections" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2984d1cd16c883d7935b9e07e44071dca8d917fd52ecc02c04d5fa0b5a3f191c" -dependencies = [ - "displaydoc", - "potential_utf", - "utf8_iter", - "yoke", - "zerofrom", - "zerovec", -] - -[[package]] -name = "icu_locale_core" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92219b62b3e2b4d88ac5119f8904c10f8f61bf7e95b640d25ba3075e6cac2c29" -dependencies = [ - "displaydoc", - "litemap", - "tinystr", - "writeable", - "zerovec", -] - -[[package]] -name = "icu_normalizer" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c56e5ee99d6e3d33bd91c5d85458b6005a22140021cc324cea84dd0e72cff3b4" -dependencies = [ - "icu_collections", - "icu_normalizer_data", - "icu_properties", - "icu_provider", - "smallvec", - "zerovec", -] - -[[package]] -name = "icu_normalizer_data" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da3be0ae77ea334f4da67c12f149704f19f81d1adf7c51cf482943e84a2bad38" - -[[package]] -name = "icu_properties" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bee3b67d0ea5c2cca5003417989af8996f8604e34fb9ddf96208a033901e70de" -dependencies = [ - "icu_collections", - "icu_locale_core", - "icu_properties_data", - "icu_provider", - "zerotrie", - "zerovec", -] - -[[package]] -name = "icu_properties_data" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e2bbb201e0c04f7b4b3e14382af113e17ba4f63e2c9d2ee626b720cbce54a14" - -[[package]] -name = "icu_provider" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "139c4cf31c8b5f33d7e199446eff9c1e02decfc2f0eec2c8d71f65befa45b421" -dependencies = [ - "displaydoc", - "icu_locale_core", - "writeable", - "yoke", - "zerofrom", - "zerotrie", - "zerovec", -] - -[[package]] -name = "ident_case" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" - -[[package]] -name = "idna" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de" -dependencies = [ - "idna_adapter", - "smallvec", - "utf8_iter", -] - -[[package]] -name = "idna_adapter" -version = "1.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb68373c0d6620ef8105e855e7745e18b0d00d3bdb07fb532e434244cdb9a714" -dependencies = [ - "icu_normalizer", - "icu_properties", -] - -[[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", - "serde", -] - -[[package]] -name = "indexmap" -version = "2.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9" -dependencies = [ - "equivalent", - "hashbrown 0.17.1", - "serde", - "serde_core", -] - -[[package]] -name = "inout" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "879f10e63c20629ecabbb64a8010319738c66a5cd0c29b02d63d272b03751d01" -dependencies = [ - "generic-array", -] - -[[package]] -name = "ipnetwork" -version = "0.20.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf466541e9d546596ee94f9f69590f89473455f88372423e0008fc1a7daf100e" -dependencies = [ - "serde", -] - -[[package]] -name = "itertools" -version = "0.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285" -dependencies = [ - "either", -] - -[[package]] -name = "itoa" -version = "1.0.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" - -[[package]] -name = "jni" -version = "0.21.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a87aa2bb7d2af34197c04845522473242e1aa17c12f4935d5856491a7fb8c97" -dependencies = [ - "cesu8", - "cfg-if", - "combine", - "jni-sys 0.3.1", - "log", - "thiserror 1.0.69", - "walkdir", - "windows-sys 0.45.0", -] - -[[package]] -name = "jni" -version = "0.22.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5efd9a482cf3a427f00d6b35f14332adc7902ce91efb778580e180ff90fa3498" -dependencies = [ - "cfg-if", - "combine", - "jni-macros", - "jni-sys 0.4.1", - "log", - "simd_cesu8", - "thiserror 2.0.20", - "walkdir", - "windows-link", -] - -[[package]] -name = "jni-macros" -version = "0.22.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a00109accc170f0bdb141fed3e393c565b6f5e072365c3bd58f5b062591560a3" -dependencies = [ - "proc-macro2", - "quote", - "rustc_version", - "simd_cesu8", - "syn 2.0.119", -] - -[[package]] -name = "jni-sys" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41a652e1f9b6e0275df1f15b32661cf0d4b78d4d87ddec5e0c3c20f097433258" -dependencies = [ - "jni-sys 0.4.1", -] - -[[package]] -name = "jni-sys" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6377a88cb3910bee9b0fa88d4f42e1d2da8e79915598f65fb0c7ee14c878af2" -dependencies = [ - "jni-sys-macros", -] - -[[package]] -name = "jni-sys-macros" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38c0b942f458fe50cdac086d2f946512305e5631e720728f2a61aabcd47a6264" -dependencies = [ - "quote", - "syn 2.0.119", -] - -[[package]] -name = "js-sys" -version = "0.3.104" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e0c1080212aad755ea003d18543e8768dd432c48819efd73a7bf1e39b7a5a3a" -dependencies = [ - "cfg-if", - "futures-util", - "wasm-bindgen", -] - -[[package]] -name = "json5" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96b0db21af676c1ce64250b5f40f3ce2cf27e4e47cb91ed91eb6fe9350b430c1" -dependencies = [ - "pest", - "pest_derive", - "serde", -] - -[[package]] -name = "keccak" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb26cec98cce3a3d96cbb7bced3c4b16e3d13f27ec56dbd62cbc8f39cfb9d653" -dependencies = [ - "cpufeatures 0.2.17", -] - -[[package]] -name = "keyed-set" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89d255a6b6ecd77bb93ce91de984d7039bff7503f500eb4851a1269732f22baf" -dependencies = [ - "hashbrown 0.14.5", -] - -[[package]] -name = "konst" -version = "0.2.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "128133ed7824fcd73d6e7b17957c5eb7bacb885649bd8c69708b2331a10bcefb" -dependencies = [ - "konst_macro_rules", -] - -[[package]] -name = "konst_macro_rules" -version = "0.2.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4933f3f57a8e9d9da04db23fb153356ecaf00cbd14aee46279c33dc80925c37" - -[[package]] -name = "lazy_static" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" -dependencies = [ - "spin 0.9.9", -] - -[[package]] -name = "leb128" -version = "0.2.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c83bff1d572d6b9aeef67ddfc8448e4a3737909cb28e81f97c791b9018703e52" - -[[package]] -name = "libc" -version = "0.2.189" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3eaf3ede3fee6db1a4c2ee091bf8a8b4dccdc6d17f656fb07896ee72867612f2" - -[[package]] -name = "libloading" -version = "0.8.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7c4b02199fee7c5d21a5ae7d8cfa79a6ef5bb2fc834d6e9058e89c825efdc55" -dependencies = [ - "cfg-if", - "windows-link", -] - -[[package]] -name = "libm" -version = "0.2.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981" - -[[package]] -name = "libredox" -version = "0.1.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2026a5056764a10b2bf5d56488cba40da507f5493a6a429340e2004d9ed085fa" -dependencies = [ - "libc", -] - -[[package]] -name = "litemap" -version = "0.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92daf443525c4cce67b150400bc2316076100ce0b3686209eb8cf3c31612e6f0" - -[[package]] -name = "lock_api" -version = "0.4.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" -dependencies = [ - "scopeguard", -] - -[[package]] -name = "log" -version = "0.4.33" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ceec5bc11778974d1bcb055b18002eba7f4b3518b6a0081b3af5f21666da9ad" - -[[package]] -name = "lru" -version = "0.16.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f66e8d5d03f609abc3a39e6f08e4164ebf1447a732906d39eb9b99b7919ef39" -dependencies = [ - "hashbrown 0.16.1", -] - -[[package]] -name = "lru-slab" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154" - -[[package]] -name = "lz4_flex" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b8c72594ac26bfd34f2d99dfced2edfaddfe8a476e3ff2ca0eb293d925c4f83" -dependencies = [ - "twox-hash", -] - -[[package]] -name = "matchers" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d1525a2a28c7f4fa0fc98bb91ae755d1e2d1505079e05539e35bc876b5d65ae9" -dependencies = [ - "regex-automata", -] - -[[package]] -name = "memchr" -version = "2.8.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98" - -[[package]] -name = "minimal-lexical" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" - -[[package]] -name = "miniz_oxide" -version = "0.8.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" -dependencies = [ - "adler2", - "simd-adler32", -] - -[[package]] -name = "mio" -version = "1.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30d65c71f1ce40ab09135ce117d742b9f8a19ff91a41a8b57ed50bc2de59c427" -dependencies = [ - "libc", - "wasi", - "windows-sys 0.61.2", -] - -[[package]] -name = "nanorand" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a51313c5820b0b02bd422f4b44776fbf47961755c74ce64afc73bfad10226c3" -dependencies = [ - "getrandom 0.2.17", -] - -[[package]] -name = "nix" -version = "0.29.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46" -dependencies = [ - "bitflags 2.13.1", - "cfg-if", - "cfg_aliases", - "libc", -] - -[[package]] -name = "no-std-net" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43794a0ace135be66a25d3ae77d41b91615fb68ae937f904090203e81f755b65" - -[[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 = "nonempty-collections" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e216d0e8cf9d54fa66e5780f6e1d5dc96d1c1b3c25aeba3b6758548bcbbd8b9d" -dependencies = [ - "serde", -] - -[[package]] -name = "nu-ansi-term" -version = "0.50.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5" -dependencies = [ - "windows-sys 0.61.2", -] - -[[package]] -name = "num-bigint" -version = "0.4.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c89e69e7e0f03bea5ef08013795c25018e101932225a656383bd384495ecc367" -dependencies = [ - "num-integer", - "num-traits", -] - -[[package]] -name = "num-bigint-dig" -version = "0.8.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e661dda6640fad38e827a6d4a310ff4763082116fe217f279885c97f511bb0b7" -dependencies = [ - "lazy_static", - "libm", - "num-integer", - "num-iter", - "num-traits", - "rand 0.8.7", - "smallvec", - "zeroize", -] - -[[package]] -name = "num-conv" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "521739c6d2bac4aa25192232afe6841231376b2b26d4d9fae5ecf8ca5772e441" - -[[package]] -name = "num-integer" -version = "0.1.46" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" -dependencies = [ - "num-traits", -] - -[[package]] -name = "num-iter" -version = "0.1.46" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c92800bd69a1eac91786bcfe9da64a897eb72911b8dc3095decbd07429e8048b" -dependencies = [ - "num-integer", - "num-traits", -] - -[[package]] -name = "num-traits" -version = "0.2.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" -dependencies = [ - "autocfg", - "libm", -] - -[[package]] -name = "num_cpus" -version = "1.17.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91df4bbde75afed763b708b7eee1e8e7651e02d97f6d5dd763e89367e957b23b" -dependencies = [ - "hermit-abi 0.5.2", - "libc", -] - -[[package]] -name = "oid-registry" -version = "0.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12f40cff3dde1b6087cc5d5f5d4d65712f34016a03ed60e9c08dcc392736b5b7" -dependencies = [ - "asn1-rs", -] - -[[package]] -name = "once_cell" -version = "1.21.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" - -[[package]] -name = "openssl-probe" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe" - -[[package]] -name = "option-ext" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" - -[[package]] -name = "os_str_bytes" -version = "6.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2355d85b9a3786f481747ced0e0ff2ba35213a1f9bd406ed906554d7af805a1" - -[[package]] -name = "parking" -version = "2.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba" - -[[package]] -name = "parking_lot" -version = "0.12.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" -dependencies = [ - "lock_api", - "parking_lot_core", -] - -[[package]] -name = "parking_lot_core" -version = "0.9.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" -dependencies = [ - "cfg-if", - "libc", - "redox_syscall 0.5.18", - "smallvec", - "windows-link", -] - -[[package]] -name = "paste" -version = "1.0.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" - -[[package]] -name = "pem" -version = "3.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d30c53c26bc5b31a98cd02d20f25a7c8567146caf63ed593a9d87b2775291be" -dependencies = [ - "base64", - "serde_core", -] - -[[package]] -name = "pem-rfc7468" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88b39c9bfcfc231068454382784bb460aae594343fb030d46e9f50a645418412" -dependencies = [ - "base64ct", -] - -[[package]] -name = "percent-encoding" -version = "2.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" - -[[package]] -name = "pest" -version = "2.8.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7df728be843c7070fab6ab7c328c4e9e9d78e23bf749c0669c86ee7ebfa050a2" -dependencies = [ - "memchr", - "ucd-trie", -] - -[[package]] -name = "pest_derive" -version = "2.8.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e2dd6fc3b26b3462ee188aac870f5a41d398f1cd5e2408d16531bd71c9591fd" -dependencies = [ - "pest", - "pest_generator", -] - -[[package]] -name = "pest_generator" -version = "2.8.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a7a9205cfb6f596a9e8b689c0a15f9ceb7a1aafae7aaf788150ac65b29975b6" -dependencies = [ - "pest", - "pest_meta", - "proc-macro2", - "quote", - "syn 2.0.119", -] - -[[package]] -name = "pest_meta" -version = "2.8.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85abd351c0de1e8384fc791a0737111a350394937e92b956b743dac12429f57c" -dependencies = [ - "pest", -] - -[[package]] -name = "petgraph" -version = "0.8.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8701b58ea97060d5e5b155d383a69952a60943f0e6dfe30b04c287beb0b27455" -dependencies = [ - "fixedbitset", - "hashbrown 0.15.5", - "indexmap 2.14.0", - "serde", -] - -[[package]] -name = "phf" -version = "0.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1562dc717473dbaa4c1f85a36410e03c047b2e7df7f45ee938fbef64ae7fadf" -dependencies = [ - "phf_macros", - "phf_shared", - "serde", -] - -[[package]] -name = "phf_generator" -version = "0.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "135ace3a761e564ec88c03a77317a7c6b80bb7f7135ef2544dbe054243b89737" -dependencies = [ - "fastrand", - "phf_shared", -] - -[[package]] -name = "phf_macros" -version = "0.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "812f032b54b1e759ccd5f8b6677695d5268c588701effba24601f6932f8269ef" -dependencies = [ - "phf_generator", - "phf_shared", - "proc-macro2", - "quote", - "syn 2.0.119", -] - -[[package]] -name = "phf_shared" -version = "0.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e57fef6bc5981e38c2ce2d63bfa546861309f875b8a75f092d1d54ae2d64f266" -dependencies = [ - "siphasher", -] - -[[package]] -name = "pin-project" -version = "1.1.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2466b2336ed02bcdca6b294417127b90ec92038d1d5c4fbeac971a922e0e0924" -dependencies = [ - "pin-project-internal", -] - -[[package]] -name = "pin-project-internal" -version = "1.1.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c96395f0a926bc13b1c17622aaddda1ecb55d49c8f1bf9777e4d877800a43f8b" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.119", -] - -[[package]] -name = "pin-project-lite" -version = "0.2.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" - -[[package]] -name = "pkcs1" -version = "0.7.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8ffb9f10fa047879315e6625af03c164b16962a5368d724ed16323b68ace47f" -dependencies = [ - "der", - "pkcs8", - "spki", -] - -[[package]] -name = "pkcs8" -version = "0.10.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" -dependencies = [ - "der", - "spki", -] - -[[package]] -name = "pnet_base" -version = "0.35.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ffc190d4067df16af3aba49b3b74c469e611cad6314676eaf1157f31aa0fb2f7" -dependencies = [ - "no-std-net", -] - -[[package]] -name = "pnet_datalink" -version = "0.35.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e79e70ec0be163102a332e1d2d5586d362ad76b01cec86f830241f2b6452a7b7" -dependencies = [ - "ipnetwork", - "libc", - "pnet_base", - "pnet_sys", - "winapi", -] - -[[package]] -name = "pnet_sys" -version = "0.35.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d4643d3d4db6b08741050c2f3afa9a892c4244c085a72fcda93c9c2c9a00f4b" -dependencies = [ - "libc", - "winapi", -] - -[[package]] -name = "portable-atomic" -version = "1.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d20d5497ef88037a52ff98267d066e7f11fcc5e99bbfbd58a42336193aacec3" - -[[package]] -name = "potential_utf" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0103b1cef7ec0cf76490e969665504990193874ea05c85ff9bab8b911d0a0564" -dependencies = [ - "zerovec", -] - -[[package]] -name = "powerfmt" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" - -[[package]] -name = "ppv-lite86" -version = "0.2.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" -dependencies = [ - "zerocopy", -] - -[[package]] -name = "proc-macro-crate" -version = "3.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e67ba7e9b2b56446f1d419b1d807906278ffa1a658a8a5d8a39dcb1f5a78614f" -dependencies = [ - "toml_edit", -] - -[[package]] -name = "proc-macro2" -version = "1.0.107" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "985e7ec9bb745e6ce6535b544d84d6cd6f7ad8bd711c398938ae983b91a766d9" -dependencies = [ - "unicode-ident", -] - -[[package]] -name = "quinn" -version = "0.11.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c1a41e437b6bbd489372cd4971de128e85c855f56c57f283d20ff016cf7c0a8" -dependencies = [ - "bytes", - "cfg_aliases", - "pin-project-lite", - "quinn-proto", - "quinn-udp", - "rustc-hash", - "rustls", - "socket2 0.6.5", - "thiserror 2.0.20", - "tokio", - "tracing", - "web-time", -] - -[[package]] -name = "quinn-proto" -version = "0.11.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f4bfc015262b9df63c8845072ce59068853ff5872180c2ce2f13038b970e560" -dependencies = [ - "bytes", - "fastbloom", - "getrandom 0.4.3", - "lru-slab", - "rand 0.10.2", - "rand_pcg", - "ring", - "rustc-hash", - "rustls", - "rustls-pki-types", - "rustls-platform-verifier", - "slab", - "thiserror 2.0.20", - "tinyvec", - "tracing", - "web-time", -] - -[[package]] -name = "quinn-udp" -version = "0.5.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35a133f956daabe89a61a685c2649f13d82d5aa4bd5d12d1277e1072a21c0694" -dependencies = [ - "cfg_aliases", - "libc", - "once_cell", - "socket2 0.6.5", - "tracing", - "windows-sys 0.61.2", -] - -[[package]] -name = "quote" -version = "1.0.47" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fbf4db142a473a8d80c26bbf18454ed458bf8d26c8219c331daecfdbd079001" -dependencies = [ - "proc-macro2", -] - -[[package]] -name = "r-efi" -version = "5.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" - -[[package]] -name = "r-efi" -version = "6.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" - -[[package]] -name = "rand" -version = "0.8.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22f6172bdec972074665ed81ed53b71da00bfc44b65a753cfde883ec4c702a1a" -dependencies = [ - "libc", - "rand_chacha", - "rand_core 0.6.4", -] - -[[package]] -name = "rand" -version = "0.10.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7f5fa3a058cd35567ef9bfa5e75732bee0f9e4c55fa90477bef2dfcdbc4be80" -dependencies = [ - "chacha20", - "getrandom 0.4.3", - "rand_core 0.10.1", -] - -[[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 0.6.4", -] - -[[package]] -name = "rand_core" -version = "0.6.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" -dependencies = [ - "getrandom 0.2.17", -] - -[[package]] -name = "rand_core" -version = "0.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "63b8176103e19a2643978565ca18b50549f6101881c443590420e4dc998a3c69" - -[[package]] -name = "rand_pcg" -version = "0.10.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "caa0f4137e1c0a72f4c651489402276c8e8e1cf081f3b0ba156d2cbeef09e86a" -dependencies = [ - "rand_core 0.10.1", -] - -[[package]] -name = "rcgen" -version = "0.14.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57f6d249aad744e274e682777a50283a225a32705394ee6d5fcc01efa25e4055" -dependencies = [ - "pem", - "ring", - "rustls-pki-types", - "time", - "x509-parser", - "yasna", -] - -[[package]] -name = "redox_syscall" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa" -dependencies = [ - "bitflags 1.3.2", -] - -[[package]] -name = "redox_syscall" -version = "0.5.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" -dependencies = [ - "bitflags 2.13.1", -] - -[[package]] -name = "redox_users" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4e608c6638b9c18977b00b475ac1f28d14e84b27d8d42f70e0bf1e3dec127ac" -dependencies = [ - "getrandom 0.2.17", - "libredox", - "thiserror 2.0.20", -] - -[[package]] -name = "ref-cast" -version = "1.0.26" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "216e8f773d7923bcba9ceb86a86c93cabb3903a11872fc3f138c49630e50b96d" -dependencies = [ - "ref-cast-impl", -] - -[[package]] -name = "ref-cast-impl" -version = "1.0.26" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c9283685feec7d69af75fb0e858d5e7378f33fe4fc699383b2916ab9273e03c" -dependencies = [ - "proc-macro2", - "quote", - "syn 3.0.3", -] - -[[package]] -name = "regex" -version = "1.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f020237b6c8eed93db2e2cb53c00c60a8e1bc73da7d073199a1180401450218d" -dependencies = [ - "aho-corasick", - "memchr", - "regex-automata", - "regex-syntax", -] - -[[package]] -name = "regex-automata" -version = "0.4.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad8553b9b26413251cbf30e620595c7a41b3887f03da04579c0e6b0d6a06b4b2" -dependencies = [ - "aho-corasick", - "memchr", - "regex-syntax", -] - -[[package]] -name = "regex-syntax" -version = "0.8.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6f6ff9a378485b298a5286656da665ba74413d36db0979633275d2e708145d4" - -[[package]] -name = "ring" -version = "0.17.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" -dependencies = [ - "cc", - "cfg-if", - "getrandom 0.2.17", - "libc", - "untrusted", - "windows-sys 0.52.0", -] - -[[package]] -name = "ringbuffer-spsc" -version = "0.1.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d3e7aa0a681b232e7cd7f856a53b10603df88ca74b79a8d8088845185492e35" -dependencies = [ - "array-init", - "crossbeam", -] - -[[package]] -name = "ron" -version = "0.12.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81116b9531d61eabc41aeb228e4b6b2435bcca3233b98cf3b3077d4e6e9debb3" -dependencies = [ - "bitflags 2.13.1", - "once_cell", - "serde", - "serde_derive", - "typeid", - "unicode-ident", -] - -[[package]] -name = "rsa" -version = "0.9.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8573f03f5883dcaebdfcf4725caa1ecb9c15b2ef50c43a07b816e06799bb12d" -dependencies = [ - "const-oid", - "digest", - "num-bigint-dig", - "num-integer", - "num-traits", - "pkcs1", - "pkcs8", - "rand_core 0.6.4", - "signature", - "spki", - "subtle", - "zeroize", -] - -[[package]] -name = "rustc-hash" -version = "2.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b1e7f9a428571be2dc5bc0505c13fb6bf936822b894ec87abf8a08a4e51742d" - -[[package]] -name = "rustc_version" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" -dependencies = [ - "semver", -] - -[[package]] -name = "rusticata-macros" -version = "4.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "faf0c4a6ece9950b9abdb62b1cfcf2a68b3b67a10ba445b3bb85be2a293d0632" -dependencies = [ - "nom", -] - -[[package]] -name = "rustls" -version = "0.23.43" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0283386ce02abc0151e1761d08802dfe86c173b0b494af5cbc086574e453da06" -dependencies = [ - "log", - "once_cell", - "ring", - "rustls-pki-types", - "rustls-webpki", - "subtle", - "zeroize", -] - -[[package]] -name = "rustls-native-certs" -version = "0.8.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dab5152771c58876a2146916e53e35057e1a4dfa2b9df0f0305b07f611fdea4d" -dependencies = [ - "openssl-probe", - "rustls-pki-types", - "schannel", - "security-framework", -] - -[[package]] -name = "rustls-pemfile" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dce314e5fee3f39953d46bb63bb8a46d40c2f8fb7cc5a3b6cab2bde9721d6e50" -dependencies = [ - "rustls-pki-types", -] - -[[package]] -name = "rustls-pki-types" -version = "1.15.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f4925028c7eb5d1fcdaf196971378ed9d2c1c4efc7dc5d011256f76c99c0a96" -dependencies = [ - "web-time", - "zeroize", -] - -[[package]] -name = "rustls-platform-verifier" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26d1e2536ce4f35f4846aa13bff16bd0ff40157cdb14cc056c7b14ba41233ba0" -dependencies = [ - "core-foundation", - "core-foundation-sys", - "jni 0.22.4", - "log", - "once_cell", - "rustls", - "rustls-native-certs", - "rustls-platform-verifier-android", - "rustls-webpki", - "security-framework", - "security-framework-sys", - "webpki-root-certs", - "windows-sys 0.61.2", -] - -[[package]] -name = "rustls-platform-verifier-android" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f87165f0995f63a9fbeea62b64d10b4d9d8e78ec6d7d51fb2125fda7bb36788f" - -[[package]] -name = "rustls-webpki" -version = "0.103.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61c429a8649f110dddef65e2a5ad240f747e85f7758a6bccc7e5777bd33f756e" -dependencies = [ - "ring", - "rustls-pki-types", - "untrusted", -] - -[[package]] -name = "rustversion" -version = "1.0.23" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf54715a573b99ac80df0bc206da022bcd442c974952c7b9720069370852e21f" - -[[package]] -name = "ryu" -version = "1.0.23" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f" - -[[package]] -name = "same-file" -version = "1.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" -dependencies = [ - "winapi-util", -] - -[[package]] -name = "schannel" -version = "0.1.29" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91c1b7e4904c873ef0710c1f407dde2e6287de2bebc1bbbf7d430bb7cbffd939" -dependencies = [ - "windows-sys 0.61.2", -] - -[[package]] -name = "schemars" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4cd191f9397d57d581cddd31014772520aa448f65ef991055d7f61582c65165f" -dependencies = [ - "dyn-clone", - "ref-cast", - "serde", - "serde_json", -] - -[[package]] -name = "schemars" -version = "1.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "687274d293b6cdc6e73e0fee520bf2049650090d7164f87672d212a3c530cf4a" -dependencies = [ - "dyn-clone", - "either", - "ref-cast", - "schemars_derive", - "serde", - "serde_json", -] - -[[package]] -name = "schemars_derive" -version = "1.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d98c67716b46af2f0b8cf752abc930f6f9aecfbf671ecfb531db8a31dbe4e2ba" -dependencies = [ - "proc-macro2", - "quote", - "serde_derive_internals", - "syn 3.0.3", -] - -[[package]] -name = "scopeguard" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" - -[[package]] -name = "secrecy" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9bd1c54ea06cfd2f6b63219704de0b9b4f72dcc2b8fdef820be6cd799780e91e" -dependencies = [ - "serde", - "zeroize", -] - -[[package]] -name = "security-framework" -version = "3.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7f4bc775c73d9a02cde8bf7b2ec4c9d12743edf609006c7facc23998404cd1d" -dependencies = [ - "bitflags 2.13.1", - "core-foundation", - "core-foundation-sys", - "libc", - "security-framework-sys", -] - -[[package]] -name = "security-framework-sys" -version = "2.17.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ce2691df843ecc5d231c0b14ece2acc3efb62c0a398c7e1d875f3983ce020e3" -dependencies = [ - "core-foundation-sys", - "libc", -] - -[[package]] -name = "semver" -version = "1.0.28" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd" - -[[package]] -name = "serde" -version = "1.0.229" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4148590afebada386688f18773da617792bf2ef03ffc1e4cbd2b1d45b023e0ba" -dependencies = [ - "serde_core", - "serde_derive", -] - -[[package]] -name = "serde_core" -version = "1.0.229" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67dca2c9c51e58a4791a4b1ed58308b39c64224d349a935ab5039aa360942a48" -dependencies = [ - "serde_derive", -] - -[[package]] -name = "serde_derive" -version = "1.0.229" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348" -dependencies = [ - "proc-macro2", - "quote", - "syn 3.0.3", -] - -[[package]] -name = "serde_derive_internals" -version = "0.30.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f852137cce035d6a4df67ccce505ff6b3e9fd3a10e3e52b24dc71e650bb1a9bd" -dependencies = [ - "proc-macro2", - "quote", - "syn 3.0.3", -] - -[[package]] -name = "serde_json" -version = "1.0.151" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c841b55ecdae098c80dcae9cf767f6f8a0c2cdb3416bbef72181df4d0fe73f14" -dependencies = [ - "itoa", - "memchr", - "serde", - "serde_core", - "zmij", -] - -[[package]] -name = "serde_spanned" -version = "1.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6662b5879511e06e8999a8a235d848113e942c9124f211511b16466ee2995f26" -dependencies = [ - "serde_core", -] - -[[package]] -name = "serde_with" -version = "3.21.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76a5c54c7310e7b8b9577c286d7e399ddd876c3e12b3ed917a8aabc4b96e9e8c" -dependencies = [ - "base64", - "bs58", - "chrono", - "hex", - "indexmap 1.9.3", - "indexmap 2.14.0", - "schemars 0.9.0", - "schemars 1.2.2", - "serde_core", - "serde_json", - "serde_with_macros", - "time", -] - -[[package]] -name = "serde_with_macros" -version = "3.21.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "84d57bc0c8b9a17920c178daa6bb924850d54a9c97ab45194bb8c17ad66bb660" -dependencies = [ - "darling", - "proc-macro2", - "quote", - "syn 2.0.119", -] - -[[package]] -name = "serde_yaml" -version = "0.9.34+deprecated" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a8b1a1a2ebf674015cc02edccce75287f1a0130d394307b36743c2f5d504b47" -dependencies = [ - "indexmap 2.14.0", - "itoa", - "ryu", - "serde", - "unsafe-libyaml", -] - -[[package]] -name = "sha1" -version = "0.10.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a978451301f4db1d02937a4ab3ccce137717b81826e79b7d49ffe3244a13c3b8" -dependencies = [ - "cfg-if", - "cpufeatures 0.2.17", - "digest", -] - -[[package]] -name = "sha2-const-stable" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f179d4e11094a893b82fff208f74d448a7512f99f5a0acbd5c679b705f83ed9" - -[[package]] -name = "sha3" -version = "0.10.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77fd7028345d415a4034cf8777cd4f8ab1851274233b45f84e3d955502d93874" -dependencies = [ - "digest", - "keccak", -] - -[[package]] -name = "sharded-slab" -version = "0.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" -dependencies = [ - "lazy_static", -] - -[[package]] -name = "shellexpand" -version = "3.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32824fab5e16e6c4d86dc1ba84489390419a39f97699852b66480bb87d297ed8" -dependencies = [ - "dirs", -] - -[[package]] -name = "shlex" -version = "2.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba" - -[[package]] -name = "signature" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" -dependencies = [ - "digest", - "rand_core 0.6.4", -] - -[[package]] -name = "simd-adler32" -version = "0.3.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a219298ac11a56ea9a6d2120044824d6f01aeb034955e7af7bc16858527deea" - -[[package]] -name = "simd_cesu8" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11031e251abf8611c80f460e19dbdeb54a66db918e49c65a7065b46ac7aec520" -dependencies = [ - "rustc_version", - "simdutf8", -] - -[[package]] -name = "simdutf8" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e" - -[[package]] -name = "siphasher" -version = "1.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ee5873ec9cce0195efcb7a4e9507a04cd49aec9c83d0389df45b1ef7ba2e649" - -[[package]] -name = "slab" -version = "0.4.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" - -[[package]] -name = "smallvec" -version = "1.15.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ed6a63f02c8539c91a8685a86f4099661ba3da017932f6ebbea6de3f0fa7c90" - -[[package]] -name = "socket2" -version = "0.5.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e22376abed350d73dd1cd119b57ffccad95b4e585a7cda43e286245ce23c0678" -dependencies = [ - "libc", - "windows-sys 0.52.0", -] - -[[package]] -name = "socket2" -version = "0.6.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3d1e2c7f27f8d4cb10542a02c49005dbd6e93095799d6f3be745fae9f8fedd4" -dependencies = [ - "libc", - "windows-sys 0.61.2", -] - -[[package]] -name = "spin" -version = "0.9.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3763264f6b73151db08c50ff20d7d8a0b8796e021cdea7ceedad07b80155fa0e" -dependencies = [ - "lock_api", -] - -[[package]] -name = "spin" -version = "0.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "023a211cb3138dbc438680b32560ad89f699977624c9f8dbb95a47d5b4c07dd3" - -[[package]] -name = "spki" -version = "0.7.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" -dependencies = [ - "base64ct", - "der", -] - -[[package]] -name = "stabby" -version = "72.1.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d53d2428934c46277fafd2d41e39357595aa1e47954c75db2b14ed90632f3cc" -dependencies = [ - "rustversion", - "stabby-abi", -] - -[[package]] -name = "stabby-abi" -version = "72.1.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f375eae680bb54203ee5e47d4cd2ae7b79c0a79ed90919279f38f500ad53f190" -dependencies = [ - "rustc_version", - "rustversion", - "sha2-const-stable", - "stabby-macros", -] - -[[package]] -name = "stabby-macros" -version = "72.1.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea664671a576c5f7e32fee291ac123d82af5e92b0689beb3555347c00c76eef1" -dependencies = [ - "proc-macro-crate", - "proc-macro2", - "quote", - "syn 2.0.119", -] - -[[package]] -name = "stable_deref_trait" -version = "1.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" - -[[package]] -name = "static_assertions" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" - -[[package]] -name = "strsim" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" - -[[package]] -name = "strsim" -version = "0.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" - -[[package]] -name = "subtle" -version = "2.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" - -[[package]] -name = "syn" -version = "2.0.119" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "872831b642d1a07999a962a351ed35b955ea2cfc8f3862091e2a240a84f17297" -dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", -] - -[[package]] -name = "syn" -version = "3.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53e9bae58849f64dfa4f5d5ae372c8341f7305f82a3868709269343628b659a3" -dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", -] - -[[package]] -name = "synstructure" -version = "0.13.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.119", -] - -[[package]] -name = "termcolor" -version = "1.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755" -dependencies = [ - "winapi-util", -] - -[[package]] -name = "textwrap" -version = "0.16.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c13547615a44dc9c452a8a534638acdf07120d4b6847c8178705da06306a3057" - -[[package]] -name = "thiserror" -version = "1.0.69" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" -dependencies = [ - "thiserror-impl 1.0.69", -] - -[[package]] -name = "thiserror" -version = "2.0.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec86235f5fcc2a73650310756d2ac5b138a5780bbbdfae3eeccec992c435ba4f" -dependencies = [ - "thiserror-impl 2.0.20", -] - -[[package]] -name = "thiserror-impl" -version = "1.0.69" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.119", -] - -[[package]] -name = "thiserror-impl" -version = "2.0.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc04cd3e1236dd4a98afca4569f2deb3f120e5422a4023be2cb683f8486292af" -dependencies = [ - "proc-macro2", - "quote", - "syn 3.0.3", -] - -[[package]] -name = "thread_local" -version = "1.1.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ad99c4c6d32803332c548b1af0540b357b3f5fc0be8f6c6bfe8b2e6ae784070" -dependencies = [ - "cfg-if", -] - -[[package]] -name = "time" -version = "0.3.55" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cdb87b95ec50ddfa440816d227a17b2ccbdda963a316a727fda0fc4334f7d134" -dependencies = [ - "deranged", - "num-conv", - "powerfmt", - "serde_core", - "time-core", - "time-macros", -] - -[[package]] -name = "time-core" -version = "0.1.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e1c906769ad99c88eaa54e728060edef082f8e358ff32030cb7c7d315e81109" - -[[package]] -name = "time-macros" -version = "0.2.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e689342a48d2ea927c87ea50cabf8594854bf940e9310208848d680d668ed85" -dependencies = [ - "num-conv", - "time-core", -] - -[[package]] -name = "tinystr" -version = "0.8.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8323304221c2a851516f22236c5722a72eaa19749016521d6dff0824447d96d" -dependencies = [ - "displaydoc", - "zerovec", -] - -[[package]] -name = "tinyvec" -version = "1.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb4ebadaa0af04fab11ae01eb5f9fdb5f9c5b875506e210e71c07873528baa7f" -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 = "tls-listener" -version = "0.11.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1461056cc1ef47003f7ee16e4cef3741068d4c7f6b627bfce49b7c00c120a530" -dependencies = [ - "futures-util", - "pin-project-lite", - "thiserror 2.0.20", - "tokio", - "tokio-rustls", -] - -[[package]] -name = "token-cell" -version = "2.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb48920ae769b58126c8c93269805011c793201f95fde28b479b81a9a531bbde" -dependencies = [ - "paste", - "portable-atomic", - "rustversion", -] - -[[package]] -name = "tokio" -version = "1.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "202caea871b69668250d242070849eb495be178ed697a3e98aebce5bc81a0bed" -dependencies = [ - "bytes", - "libc", - "mio", - "pin-project-lite", - "socket2 0.6.5", - "tokio-macros", - "windows-sys 0.61.2", -] - -[[package]] -name = "tokio-macros" -version = "2.7.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78773a2a397f451582ce068015985c33193cf6dea8b74d2a639fe457b2f07b0e" -dependencies = [ - "proc-macro2", - "quote", - "syn 3.0.3", -] - -[[package]] -name = "tokio-rustls" -version = "0.26.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1729aa945f29d91ba541258c8df89027d5792d85a8841fb65e8bf0f4ede4ef61" -dependencies = [ - "rustls", - "tokio", -] - -[[package]] -name = "tokio-tungstenite" -version = "0.24.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "edc5f74e248dc973e0dbb7b74c7e0d6fcc301c694ff50049504004ef4d0cdcd9" -dependencies = [ - "futures-util", - "log", - "tokio", - "tungstenite", -] - -[[package]] -name = "tokio-util" -version = "0.7.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "494815d09bf52b5548659851081238f0ca39ff638363907596da739561c62c52" -dependencies = [ - "bytes", - "futures-core", - "futures-sink", - "futures-util", - "pin-project-lite", - "tokio", -] - -[[package]] -name = "toml" -version = "0.9.12+spec-1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf92845e79fc2e2def6a5d828f0801e29a2f8acc037becc5ab08595c7d5e9863" -dependencies = [ - "indexmap 2.14.0", - "serde_core", - "serde_spanned", - "toml_datetime 0.7.5+spec-1.1.0", - "toml_parser", - "toml_writer", - "winnow 0.7.15", -] - -[[package]] -name = "toml_datetime" -version = "0.7.5+spec-1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92e1cfed4a3038bc5a127e35a2d360f145e1f4b971b551a2ba5fd7aedf7e1347" -dependencies = [ - "serde_core", -] - -[[package]] -name = "toml_datetime" -version = "1.1.1+spec-1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3165f65f62e28e0115a00b2ebdd37eb6f3b641855f9d636d3cd4103767159ad7" -dependencies = [ - "serde_core", -] - -[[package]] -name = "toml_edit" -version = "0.25.13+spec-1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6975367e4d2ef766d86af01ffad14b622fecc8d4357a998fbc4deb6e9bacaf9b" -dependencies = [ - "indexmap 2.14.0", - "toml_datetime 1.1.1+spec-1.1.0", - "toml_parser", - "winnow 1.0.4", -] - -[[package]] -name = "toml_parser" -version = "1.1.3+spec-1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d38ac1cf9b95face32296c0a3ede1fdc270627c9d9c02a7274dd6d960dc4d56" -dependencies = [ - "winnow 1.0.4", -] - -[[package]] -name = "toml_writer" -version = "1.1.2+spec-1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d56353a2a665ad0f41a421187180aab746c8c325620617ad883a99a1cbe66d2" - -[[package]] -name = "tracing" -version = "0.1.44" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" -dependencies = [ - "log", - "pin-project-lite", - "tracing-attributes", - "tracing-core", -] - -[[package]] -name = "tracing-attributes" -version = "0.1.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.119", -] - -[[package]] -name = "tracing-core" -version = "0.1.36" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" -dependencies = [ - "once_cell", - "valuable", -] - -[[package]] -name = "tracing-log" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" -dependencies = [ - "log", - "once_cell", - "tracing-core", -] - -[[package]] -name = "tracing-serde" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "704b1aeb7be0d0a84fc9828cae51dab5970fee5088f83d1dd7ee6f6246fc6ff1" -dependencies = [ - "serde", - "tracing-core", -] - -[[package]] -name = "tracing-subscriber" -version = "0.3.23" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb7f578e5945fb242538965c2d0b04418d38ec25c79d160cd279bf0731c8d319" -dependencies = [ - "matchers", - "nu-ansi-term", - "once_cell", - "regex-automata", - "serde", - "serde_json", - "sharded-slab", - "smallvec", - "thread_local", - "tracing", - "tracing-core", - "tracing-log", - "tracing-serde", -] - -[[package]] -name = "tungstenite" -version = "0.24.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18e5b8366ee7a95b16d32197d0b2604b43a0be89dc5fac9f8e96ccafbaedda8a" -dependencies = [ - "byteorder", - "bytes", - "data-encoding", - "http", - "httparse", - "log", - "rand 0.8.7", - "sha1", - "thiserror 1.0.69", - "utf-8", -] - -[[package]] -name = "twox-hash" -version = "1.6.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97fee6b57c6a41524a810daee9286c02d7752c4253064d0b05472833a438f675" -dependencies = [ - "cfg-if", - "static_assertions", -] - -[[package]] -name = "typeid" -version = "1.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc7d623258602320d5c55d1bc22793b57daff0ec7efc270ea7d55ce1d5f5471c" - -[[package]] -name = "typenum" -version = "1.20.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6f5e870be6c3b371b77fe0ee0bafb859fa4964b4404c27de1d380043c4dda20" - -[[package]] -name = "ucd-trie" -version = "0.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2896d95c02a80c6d6a5d6e953d479f5ddf2dfdb6a244441010e373ac0fb88971" - -[[package]] -name = "uhlc" -version = "0.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b62a645e3e4e6c85b7abe49b086aa3204119431f42b6123b0070419fb6e9d24e" -dependencies = [ - "humantime", - "lazy_static", - "log", - "rand 0.8.7", - "serde", - "spin 0.10.1", -] - -[[package]] -name = "unicode-ident" -version = "1.0.24" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" - -[[package]] -name = "unicode-xid" -version = "0.2.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" - -[[package]] -name = "unsafe-libyaml" -version = "0.2.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "673aac59facbab8a9007c7f6108d11f63b603f7cabff99fabf650fea5c32b861" - -[[package]] -name = "untrusted" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" - -[[package]] -name = "unzip-n" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b5bb2756c16fb66f80cfbf5fb0e0c09a7001e739f453c9ec241b9c8b1556fda" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.119", -] - -[[package]] -name = "url" -version = "2.5.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff67a8a4397373c3ef660812acab3268222035010ab8680ec4215f38ba3d0eed" -dependencies = [ - "form_urlencoded", - "idna", - "percent-encoding", - "serde", -] - -[[package]] -name = "utf-8" -version = "0.7.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" - -[[package]] -name = "utf8_iter" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" - -[[package]] -name = "uuid" -version = "1.24.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf3923a6f5c4c6382e0b653c4117f48d631ea17f38ed86e2a828e6f7412f5239" -dependencies = [ - "getrandom 0.4.3", - "js-sys", - "wasm-bindgen", -] - -[[package]] -name = "validated_struct" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "869a93e8a7286e339e1128630051d82babbcd75d585975af07b9f3327220e60e" -dependencies = [ - "json5", - "serde", - "serde_json", - "validated_struct_macros", -] - -[[package]] -name = "validated_struct_macros" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c44ce98e7227a04eeb4cf9c784109a5c9710e54849ceb4f09f8597247897f1e" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.119", - "unzip-n", -] - -[[package]] -name = "valuable" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" - -[[package]] -name = "vec_map" -version = "0.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191" - -[[package]] -name = "version_check" -version = "0.9.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" - -[[package]] -name = "walkdir" -version = "2.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" -dependencies = [ - "same-file", - "winapi-util", -] - -[[package]] -name = "wasi" -version = "0.11.1+wasi-snapshot-preview1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" - -[[package]] -name = "wasip2" -version = "1.0.4+wasi-0.2.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b67efb37e106e55ce722a510d6b5f9c17f083e5fc79afc2badeb12cc313d9487" -dependencies = [ - "wit-bindgen", -] - -[[package]] -name = "wasm-bindgen" -version = "0.2.127" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b70935747edd64d89de3efa29d73789b806c15798f8e7dca4d8ac356b50ce70" -dependencies = [ - "cfg-if", - "once_cell", - "rustversion", - "wasm-bindgen-macro", - "wasm-bindgen-shared", -] - -[[package]] -name = "wasm-bindgen-macro" -version = "0.2.127" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77775f8f3f7217702089053b94958f8f54061a3f663417df76e19cbdcca29bc1" -dependencies = [ - "quote", - "wasm-bindgen-macro-support", -] - -[[package]] -name = "wasm-bindgen-macro-support" -version = "0.2.127" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e11d33f857dc2fb11b8bc75aee111aa9cbeb12cd9f25efd3d4c2a3dd4e235284" -dependencies = [ - "bumpalo", - "proc-macro2", - "quote", - "syn 2.0.119", - "wasm-bindgen-shared", -] - -[[package]] -name = "wasm-bindgen-shared" -version = "0.2.127" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ef64dbcc55df09c7e5a46182d181c2cfa3e925f3da937ea764728b4bbb9dcbf" -dependencies = [ - "unicode-ident", -] - -[[package]] -name = "web-time" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" -dependencies = [ - "js-sys", - "wasm-bindgen", -] - -[[package]] -name = "webpki-root-certs" -version = "1.0.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b96554aa2acc8ccdb7e1c9a58a7a68dd5d13bccc69cd124cb09406db612a1c9b" -dependencies = [ - "rustls-pki-types", -] - -[[package]] -name = "webpki-roots" -version = "1.0.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7dcd9d09a39985f5344844e66b0c530a33843579125f23e21e9f0f220850f22a" -dependencies = [ - "rustls-pki-types", -] - -[[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.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" -dependencies = [ - "windows-sys 0.61.2", -] - -[[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.62.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb" -dependencies = [ - "windows-implement", - "windows-interface", - "windows-link", - "windows-result", - "windows-strings", -] - -[[package]] -name = "windows-implement" -version = "0.60.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.119", -] - -[[package]] -name = "windows-interface" -version = "0.59.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.119", -] - -[[package]] -name = "windows-link" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" - -[[package]] -name = "windows-result" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" -dependencies = [ - "windows-link", -] - -[[package]] -name = "windows-strings" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" -dependencies = [ - "windows-link", -] - -[[package]] -name = "windows-sys" -version = "0.45.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" -dependencies = [ - "windows-targets 0.42.2", -] - -[[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.6", -] - -[[package]] -name = "windows-sys" -version = "0.61.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" -dependencies = [ - "windows-link", -] - -[[package]] -name = "windows-targets" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" -dependencies = [ - "windows_aarch64_gnullvm 0.42.2", - "windows_aarch64_msvc 0.42.2", - "windows_i686_gnu 0.42.2", - "windows_i686_msvc 0.42.2", - "windows_x86_64_gnu 0.42.2", - "windows_x86_64_gnullvm 0.42.2", - "windows_x86_64_msvc 0.42.2", -] - -[[package]] -name = "windows-targets" -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", - "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_aarch64_gnullvm" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" - -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" - -[[package]] -name = "windows_i686_gnu" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" - -[[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_gnullvm" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" - -[[package]] -name = "windows_i686_msvc" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" - -[[package]] -name = "windows_i686_msvc" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" - -[[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_gnullvm" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" - -[[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_msvc" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" - -[[package]] -name = "windows_x86_64_msvc" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" - -[[package]] -name = "winnow" -version = "0.7.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df79d97927682d2fd8adb29682d1140b343be4ac0f08fd68b7765d9c059d3945" - -[[package]] -name = "winnow" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23b97319f7b8343df12cc98938e5c3eb436064524c8d2b4e30a1d3a36eecdf81" -dependencies = [ - "memchr", -] - -[[package]] -name = "wit-bindgen" -version = "0.57.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e" - -[[package]] -name = "writeable" -version = "0.6.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ffae5123b2d3fc086436f8834ae3ab053a283cfac8fe0a0b8eaae044768a4c4" - -[[package]] -name = "x509-parser" -version = "0.18.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d43b0f71ce057da06bc0851b23ee24f3f86190b07203dd8f567d0b706a185202" -dependencies = [ - "asn1-rs", - "data-encoding", - "der-parser", - "lazy_static", - "nom", - "oid-registry", - "ring", - "rusticata-macros", - "thiserror 2.0.20", - "time", -] - -[[package]] -name = "yasna" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5f6765e852b9b4dc8e2a76843e4d64d1cea8e79bcde0b6901aea8e7c7f08282" -dependencies = [ - "bit-vec", - "time", -] - -[[package]] -name = "yoke" -version = "0.8.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "709fe23a0424b6a435d82152b1bd3fdfb0833487d5fa90d05d42762a9891fef5" -dependencies = [ - "stable_deref_trait", - "yoke-derive", - "zerofrom", -] - -[[package]] -name = "yoke-derive" -version = "0.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de844c262c8848816172cef550288e7dc6c7b7814b4ee56b3e1553f275f1858e" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.119", - "synstructure", -] - -[[package]] -name = "zenoh" -version = "1.9.0" -source = "git+https://github.com/eclipse-zenoh/zenoh.git?branch=main#773126fd4e1fe55270e3c1b29d16fecc2884b34e" -dependencies = [ - "ahash", - "arc-swap", - "async-trait", - "bytes", - "const_format", - "flate2", - "flume 0.11.1", - "futures", - "git-version", - "itertools", - "json5", - "lazy_static", - "nonempty-collections", - "once_cell", - "petgraph", - "phf", - "rand 0.8.7", - "rustc_version", - "serde", - "serde_json", - "socket2 0.5.10", - "tokio", - "tokio-util", - "tracing", - "uhlc", - "vec_map", - "zenoh-buffers", - "zenoh-codec", - "zenoh-collections", - "zenoh-config", - "zenoh-core", - "zenoh-keyexpr", - "zenoh-link", - "zenoh-link-commons", - "zenoh-macros", - "zenoh-plugin-trait", - "zenoh-protocol", - "zenoh-result", - "zenoh-runtime", - "zenoh-sync", - "zenoh-task", - "zenoh-transport", - "zenoh-util", -] - -[[package]] -name = "zenoh-buffers" -version = "1.9.0" -source = "git+https://github.com/eclipse-zenoh/zenoh.git?branch=main#773126fd4e1fe55270e3c1b29d16fecc2884b34e" -dependencies = [ - "zenoh-collections", -] - -[[package]] -name = "zenoh-codec" -version = "1.9.0" -source = "git+https://github.com/eclipse-zenoh/zenoh.git?branch=main#773126fd4e1fe55270e3c1b29d16fecc2884b34e" -dependencies = [ - "tracing", - "uhlc", - "zenoh-buffers", - "zenoh-protocol", -] - -[[package]] -name = "zenoh-collections" -version = "1.9.0" -source = "git+https://github.com/eclipse-zenoh/zenoh.git?branch=main#773126fd4e1fe55270e3c1b29d16fecc2884b34e" -dependencies = [ - "ahash", -] - -[[package]] -name = "zenoh-config" -version = "1.9.0" -source = "git+https://github.com/eclipse-zenoh/zenoh.git?branch=main#773126fd4e1fe55270e3c1b29d16fecc2884b34e" -dependencies = [ - "json5", - "nonempty-collections", - "num_cpus", - "secrecy", - "serde", - "serde_json", - "serde_with", - "serde_yaml", - "toml", - "tracing", - "uhlc", - "validated_struct", - "zenoh-core", - "zenoh-keyexpr", - "zenoh-macros", - "zenoh-protocol", - "zenoh-result", - "zenoh-util", -] - -[[package]] -name = "zenoh-core" -version = "1.9.0" -source = "git+https://github.com/eclipse-zenoh/zenoh.git?branch=main#773126fd4e1fe55270e3c1b29d16fecc2884b34e" -dependencies = [ - "lazy_static", - "tokio", - "zenoh-result", - "zenoh-runtime", -] - -[[package]] -name = "zenoh-crypto" -version = "1.9.0" -source = "git+https://github.com/eclipse-zenoh/zenoh.git?branch=main#773126fd4e1fe55270e3c1b29d16fecc2884b34e" -dependencies = [ - "aes", - "hmac", - "rand 0.8.7", - "rand_chacha", - "sha3", - "zenoh-result", -] - -[[package]] -name = "zenoh-ext" -version = "1.9.0" -source = "git+https://github.com/eclipse-zenoh/zenoh.git?branch=main#773126fd4e1fe55270e3c1b29d16fecc2884b34e" -dependencies = [ - "bincode", - "flume 0.11.1", - "futures", - "leb128", - "lru", - "serde", - "tokio", - "tokio-util", - "tracing", - "uhlc", - "zenoh", - "zenoh-macros", - "zenoh-util", -] - -[[package]] -name = "zenoh-keyexpr" -version = "1.9.0" -source = "git+https://github.com/eclipse-zenoh/zenoh.git?branch=main#773126fd4e1fe55270e3c1b29d16fecc2884b34e" -dependencies = [ - "getrandom 0.2.17", - "hashbrown 0.16.1", - "keyed-set", - "rand 0.8.7", - "schemars 1.2.2", - "serde", - "token-cell", - "zenoh-result", -] - -[[package]] -name = "zenoh-link" -version = "1.9.0" -source = "git+https://github.com/eclipse-zenoh/zenoh.git?branch=main#773126fd4e1fe55270e3c1b29d16fecc2884b34e" -dependencies = [ - "zenoh-config", - "zenoh-link-commons", - "zenoh-link-quic", - "zenoh-link-quic_datagram", - "zenoh-link-tcp", - "zenoh-link-tls", - "zenoh-link-udp", - "zenoh-link-unixsock_stream", - "zenoh-link-ws", - "zenoh-protocol", - "zenoh-result", -] - -[[package]] -name = "zenoh-link-commons" -version = "1.9.0" -source = "git+https://github.com/eclipse-zenoh/zenoh.git?branch=main#773126fd4e1fe55270e3c1b29d16fecc2884b34e" -dependencies = [ - "async-trait", - "base64", - "bytes", - "flume 0.11.1", - "futures", - "quinn", - "quinn-proto", - "rcgen", - "rustls", - "rustls-pemfile", - "rustls-pki-types", - "rustls-webpki", - "secrecy", - "serde", - "socket2 0.5.10", - "time", - "tokio", - "tokio-util", - "tracing", - "webpki-roots", - "x509-parser", - "zenoh-buffers", - "zenoh-codec", - "zenoh-config", - "zenoh-core", - "zenoh-protocol", - "zenoh-result", - "zenoh-runtime", - "zenoh-util", -] - -[[package]] -name = "zenoh-link-quic" -version = "1.9.0" -source = "git+https://github.com/eclipse-zenoh/zenoh.git?branch=main#773126fd4e1fe55270e3c1b29d16fecc2884b34e" -dependencies = [ - "async-trait", - "rustls-webpki", - "time", - "tracing", - "zenoh-core", - "zenoh-link-commons", - "zenoh-link-quic_datagram", - "zenoh-protocol", - "zenoh-result", -] - -[[package]] -name = "zenoh-link-quic_datagram" -version = "1.9.0" -source = "git+https://github.com/eclipse-zenoh/zenoh.git?branch=main#773126fd4e1fe55270e3c1b29d16fecc2884b34e" -dependencies = [ - "async-trait", - "rustls-webpki", - "time", - "tokio-util", - "tracing", - "zenoh-core", - "zenoh-link-commons", - "zenoh-protocol", - "zenoh-result", -] - -[[package]] -name = "zenoh-link-tcp" -version = "1.9.0" -source = "git+https://github.com/eclipse-zenoh/zenoh.git?branch=main#773126fd4e1fe55270e3c1b29d16fecc2884b34e" -dependencies = [ - "async-trait", - "socket2 0.5.10", - "tokio", - "tokio-util", - "tracing", - "zenoh-config", - "zenoh-core", - "zenoh-link-commons", - "zenoh-protocol", - "zenoh-result", -] - -[[package]] -name = "zenoh-link-tls" -version = "1.9.0" -source = "git+https://github.com/eclipse-zenoh/zenoh.git?branch=main#773126fd4e1fe55270e3c1b29d16fecc2884b34e" -dependencies = [ - "async-trait", - "base64", - "rustls", - "rustls-pemfile", - "rustls-pki-types", - "rustls-webpki", - "secrecy", - "socket2 0.5.10", - "time", - "tls-listener", - "tokio", - "tokio-rustls", - "tokio-util", - "tracing", - "webpki-roots", - "x509-parser", - "zenoh-config", - "zenoh-core", - "zenoh-link-commons", - "zenoh-protocol", - "zenoh-result", - "zenoh-runtime", -] - -[[package]] -name = "zenoh-link-udp" -version = "1.9.0" -source = "git+https://github.com/eclipse-zenoh/zenoh.git?branch=main#773126fd4e1fe55270e3c1b29d16fecc2884b34e" -dependencies = [ - "async-trait", - "libc", - "socket2 0.5.10", - "tokio", - "tokio-util", - "tracing", - "windows-sys 0.61.2", - "zenoh-buffers", - "zenoh-core", - "zenoh-link-commons", - "zenoh-link-quic_datagram", - "zenoh-protocol", - "zenoh-result", - "zenoh-sync", - "zenoh-util", -] - -[[package]] -name = "zenoh-link-unixsock_stream" -version = "1.9.0" -source = "git+https://github.com/eclipse-zenoh/zenoh.git?branch=main#773126fd4e1fe55270e3c1b29d16fecc2884b34e" -dependencies = [ - "async-trait", - "nix", - "tokio", - "tokio-util", - "tracing", - "uuid", - "zenoh-core", - "zenoh-link-commons", - "zenoh-protocol", - "zenoh-result", - "zenoh-runtime", -] - -[[package]] -name = "zenoh-link-ws" -version = "1.9.0" -source = "git+https://github.com/eclipse-zenoh/zenoh.git?branch=main#773126fd4e1fe55270e3c1b29d16fecc2884b34e" -dependencies = [ - "async-trait", - "futures-util", - "tokio", - "tokio-tungstenite", - "tokio-util", - "tracing", - "url", - "zenoh-core", - "zenoh-link-commons", - "zenoh-protocol", - "zenoh-result", - "zenoh-runtime", - "zenoh-util", -] - -[[package]] -name = "zenoh-macros" -version = "1.9.0" -source = "git+https://github.com/eclipse-zenoh/zenoh.git?branch=main#773126fd4e1fe55270e3c1b29d16fecc2884b34e" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.119", - "zenoh-keyexpr", -] - -[[package]] -name = "zenoh-plugin-trait" -version = "1.9.0" -source = "git+https://github.com/eclipse-zenoh/zenoh.git?branch=main#773126fd4e1fe55270e3c1b29d16fecc2884b34e" -dependencies = [ - "git-version", - "libloading", - "serde", - "stabby", - "tracing", - "zenoh-config", - "zenoh-keyexpr", - "zenoh-macros", - "zenoh-result", - "zenoh-util", -] - -[[package]] -name = "zenoh-protocol" -version = "1.9.0" -source = "git+https://github.com/eclipse-zenoh/zenoh.git?branch=main#773126fd4e1fe55270e3c1b29d16fecc2884b34e" -dependencies = [ - "const_format", - "rand 0.8.7", - "serde", - "uhlc", - "zenoh-buffers", - "zenoh-keyexpr", - "zenoh-macros", - "zenoh-result", -] - -[[package]] -name = "zenoh-result" -version = "1.9.0" -source = "git+https://github.com/eclipse-zenoh/zenoh.git?branch=main#773126fd4e1fe55270e3c1b29d16fecc2884b34e" -dependencies = [ - "anyhow", -] - -[[package]] -name = "zenoh-runtime" -version = "1.9.0" -source = "git+https://github.com/eclipse-zenoh/zenoh.git?branch=main#773126fd4e1fe55270e3c1b29d16fecc2884b34e" -dependencies = [ - "lazy_static", - "ron", - "serde", - "tokio", - "tracing", - "zenoh-macros", - "zenoh-result", -] - -[[package]] -name = "zenoh-sync" -version = "1.9.0" -source = "git+https://github.com/eclipse-zenoh/zenoh.git?branch=main#773126fd4e1fe55270e3c1b29d16fecc2884b34e" -dependencies = [ - "arc-swap", - "event-listener", - "futures", - "tokio", - "zenoh-buffers", - "zenoh-collections", - "zenoh-core", -] - -[[package]] -name = "zenoh-task" -version = "1.9.0" -source = "git+https://github.com/eclipse-zenoh/zenoh.git?branch=main#773126fd4e1fe55270e3c1b29d16fecc2884b34e" -dependencies = [ - "futures", - "tokio", - "tokio-util", - "tracing", - "zenoh-core", - "zenoh-runtime", -] - -[[package]] -name = "zenoh-transport" -version = "1.9.0" -source = "git+https://github.com/eclipse-zenoh/zenoh.git?branch=main#773126fd4e1fe55270e3c1b29d16fecc2884b34e" -dependencies = [ - "async-trait", - "crossbeam-utils", - "flume 0.11.1", - "futures", - "lazy_static", - "lz4_flex", - "rand 0.8.7", - "ringbuffer-spsc", - "rsa", - "serde", - "sha3", - "tokio", - "tokio-util", - "tracing", - "zenoh-buffers", - "zenoh-codec", - "zenoh-config", - "zenoh-core", - "zenoh-crypto", - "zenoh-link", - "zenoh-link-commons", - "zenoh-protocol", - "zenoh-result", - "zenoh-runtime", - "zenoh-sync", - "zenoh-task", - "zenoh-util", -] - -[[package]] -name = "zenoh-util" -version = "1.9.0" -source = "git+https://github.com/eclipse-zenoh/zenoh.git?branch=main#773126fd4e1fe55270e3c1b29d16fecc2884b34e" -dependencies = [ - "async-trait", - "const_format", - "flume 0.11.1", - "home", - "humantime", - "lazy_static", - "libc", - "libloading", - "pnet_datalink", - "schemars 1.2.2", - "serde", - "serde_json", - "shellexpand", - "tokio", - "tracing", - "tracing-subscriber", - "winapi", - "zenoh-core", - "zenoh-result", -] - -[[package]] -name = "zenoh_jni" -version = "1.9.0" -dependencies = [ - "android-logd-logger", - "async-std", - "clap", - "flume 0.10.14", - "jni 0.21.1", - "json5", - "rustc_version", - "serde_yaml", - "tracing", - "uhlc", - "zenoh", - "zenoh-ext", -] - -[[package]] -name = "zerocopy" -version = "0.8.56" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "556764e583adb45a9f8d413c2a147fa7e8d821e48e12b14fd560b607998b75eb" -dependencies = [ - "zerocopy-derive", -] - -[[package]] -name = "zerocopy-derive" -version = "0.8.56" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2ab42fc20575779bd240faa45f94a74256f755c0fa9e89f0ede20d91d0cdfc1" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.119", -] - -[[package]] -name = "zerofrom" -version = "0.1.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ec05a11813ea801ff6d75110ad09cd0824ddba17dfe17128ea0d5f68e6c5272" -dependencies = [ - "zerofrom-derive", -] - -[[package]] -name = "zerofrom-derive" -version = "0.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11532158c46691caf0f2593ea8358fed6bbf68a0315e80aae9bd41fbade684a1" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.119", - "synstructure", -] - -[[package]] -name = "zeroize" -version = "1.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e13c156562582aa81c60cb29407084cdb54c4164760106ab78e6c5b0858cf64e" - -[[package]] -name = "zerotrie" -version = "0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f9152d31db0792fa83f70fb2f83148effb5c1f5b8c7686c3459e361d9bc20bf" -dependencies = [ - "displaydoc", - "yoke", - "zerofrom", -] - -[[package]] -name = "zerovec" -version = "0.11.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90f911cbc359ab6af17377d242225f4d75119aec87ea711a880987b18cd7b239" -dependencies = [ - "yoke", - "zerofrom", - "zerovec-derive", -] - -[[package]] -name = "zerovec-derive" -version = "0.11.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "625dc425cab0dca6dc3c3319506e6593dcb08a9f387ea3b284dbd52a92c40555" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.119", -] - -[[package]] -name = "zmij" -version = "1.0.23" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29666d0abbfad1e3dc4dcf6144730dd3a3ab225bbbdac83319345b1b44ccfc1b" diff --git a/zenoh-jni/Cargo.toml b/zenoh-jni/Cargo.toml deleted file mode 100644 index 9e0b70ddd..000000000 --- a/zenoh-jni/Cargo.toml +++ /dev/null @@ -1,55 +0,0 @@ -# -# Copyright (c) 2023 ZettaScale Technology -# -# This program and the accompanying materials are made available under the -# terms of the Eclipse Public License 2.0 which is available at -# http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 -# which is available at https://www.apache.org/licenses/LICENSE-2.0. -# -# SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 -# -# Contributors: -# ZettaScale Zenoh Team, -# - -[package] -rust-version = "1.75.0" -version = "1.9.0" -repository = "https://github.com/eclipse-zenoh/zenoh" -homepage = "http://zenoh.io" -edition = "2021" -license = "EPL-2.0 OR Apache-2.0" -categories = ["network-programming"] -description = "Zenoh: Zero Overhead Pub/sub, Store/Query and Compute." -name = "zenoh_jni" - -[features] -default = ["zenoh/default", "zenoh-ext"] - -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html -[dependencies] -android-logd-logger = "0.4.0" -async-std = { version = "=1.12.0", default-features = false } -clap = "3.2.23" -jni = "0.21.1" -flume = "0.10.14" -uhlc = "0.8.0" -json5 = "0.4.1" -serde_yaml = "0.9.19" -zenoh = { version = "1.9.0", git = "https://github.com/eclipse-zenoh/zenoh.git", branch = "main", features = ["unstable", "internal"], default-features = false } -zenoh-ext = { version = "1.9.0", git = "https://github.com/eclipse-zenoh/zenoh.git", branch = "main", features = ["unstable", "internal"], default-features = false, optional = true } -tracing = { version = "0.1" , features = ["log"] } - -[lib] -name = "zenoh_jni" -crate-type = ["staticlib", "dylib"] - -[build-dependencies] -rustc_version = "0.4.0" - -[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/zenoh-jni/src/config.rs b/zenoh-jni/src/config.rs deleted file mode 100644 index 0ada13407..000000000 --- a/zenoh-jni/src/config.rs +++ /dev/null @@ -1,185 +0,0 @@ -// -// Copyright (c) 2023 ZettaScale Technology -// -// This program and the accompanying materials are made available under the -// terms of the Eclipse Public License 2.0 which is available at -// http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 -// which is available at https://www.apache.org/licenses/LICENSE-2.0. -// -// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 -// -// Contributors: -// ZettaScale Zenoh Team, -// - -use std::{ptr::null, sync::Arc}; - -use jni::{ - objects::{JClass, JString}, - sys::jstring, - JNIEnv, -}; -use zenoh::Config; - -use crate::{errors::ZResult, zerror}; -use crate::{throw_exception, utils::decode_string}; - -/// Loads the default configuration, returning a raw pointer to it. -/// -/// The pointer to the config is expected to be freed later on upon the destruction of the -/// Kotlin Config instance. -/// -#[no_mangle] -#[allow(non_snake_case)] -pub extern "C" fn Java_io_zenoh_jni_JNIConfig_00024Companion_loadDefaultConfigViaJNI( - _env: JNIEnv, - _class: JClass, -) -> *const Config { - let config = Config::default(); - Arc::into_raw(Arc::new(config)) -} - -/// Loads the config from a file, returning a pointer to the loaded config in case of success. -/// In case of failure, an exception is thrown via JNI. -/// -/// The pointer to the config is expected to be freed later on upon the destruction of the -/// Kotlin Config instance. -/// -#[no_mangle] -#[allow(non_snake_case)] -pub extern "C" fn Java_io_zenoh_jni_JNIConfig_00024Companion_loadConfigFileViaJNI( - mut env: JNIEnv, - _class: JClass, - config_path: JString, -) -> *const Config { - || -> ZResult<*const Config> { - let config_file_path = decode_string(&mut env, &config_path)?; - let config = Config::from_file(config_file_path).map_err(|err| zerror!(err))?; - Ok(Arc::into_raw(Arc::new(config))) - }() - .unwrap_or_else(|err| { - throw_exception!(env, err); - null() - }) -} - -/// Loads the config from a json/json5 formatted string, returning a pointer to the loaded config -/// in case of success. In case of failure, an exception is thrown via JNI. -/// -/// The pointer to the config is expected to be freed later on upon the destruction of the -/// Kotlin Config instance. -/// -#[no_mangle] -#[allow(non_snake_case)] -pub extern "C" fn Java_io_zenoh_jni_JNIConfig_00024Companion_loadJsonConfigViaJNI( - mut env: JNIEnv, - _class: JClass, - json_config: JString, -) -> *const Config { - || -> ZResult<*const Config> { - let json_config = decode_string(&mut env, &json_config)?; - let mut deserializer = - json5::Deserializer::from_str(&json_config).map_err(|err| zerror!(err))?; - let config = Config::from_deserializer(&mut deserializer).map_err(|err| match err { - Ok(c) => zerror!("Invalid configuration: {}", c), - Err(e) => zerror!("JSON error: {}", e), - })?; - Ok(Arc::into_raw(Arc::new(config))) - }() - .unwrap_or_else(|err| { - throw_exception!(env, err); - null() - }) -} - -/// Loads the config from a yaml-formatted string, returning a pointer to the loaded config -/// in case of success. In case of failure, an exception is thrown via JNI. -/// -/// The pointer to the config is expected to be freed later on upon the destruction of the -/// Kotlin Config instance. -/// -#[no_mangle] -#[allow(non_snake_case)] -pub extern "C" fn Java_io_zenoh_jni_JNIConfig_00024Companion_loadYamlConfigViaJNI( - mut env: JNIEnv, - _class: JClass, - yaml_config: JString, -) -> *const Config { - || -> ZResult<*const Config> { - let yaml_config = decode_string(&mut env, &yaml_config)?; - let deserializer = serde_yaml::Deserializer::from_str(&yaml_config); - let config = Config::from_deserializer(deserializer).map_err(|err| match err { - Ok(c) => zerror!("Invalid configuration: {}", c), - Err(e) => zerror!("YAML error: {}", e), - })?; - Ok(Arc::into_raw(Arc::new(config))) - }() - .unwrap_or_else(|err| { - throw_exception!(env, err); - null() - }) -} - -/// Returns the json value associated to the provided [key]. May throw an exception in case of failure, which must be handled -/// on the kotlin layer. -#[no_mangle] -#[allow(non_snake_case)] -pub unsafe extern "C" fn Java_io_zenoh_jni_JNIConfig_00024Companion_getJsonViaJNI( - mut env: JNIEnv, - _class: JClass, - cfg_ptr: *const Config, - key: JString, -) -> jstring { - let arc_cfg: Arc = Arc::from_raw(cfg_ptr); - let result = || -> ZResult { - let key = decode_string(&mut env, &key)?; - let json = arc_cfg.get_json(&key).map_err(|err| zerror!(err))?; - let java_json = env.new_string(json).map_err(|err| zerror!(err))?; - Ok(java_json.as_raw()) - }() - .unwrap_or_else(|err| { - throw_exception!(env, err); - JString::default().as_raw() - }); - std::mem::forget(arc_cfg); - result -} - -/// Inserts a json5 value associated to the provided [key]. May throw an exception in case of failure, which must be handled -/// on the kotlin layer. -#[no_mangle] -#[allow(non_snake_case)] -pub unsafe extern "C" fn Java_io_zenoh_jni_JNIConfig_00024Companion_insertJson5ViaJNI( - mut env: JNIEnv, - _class: JClass, - cfg_ptr: *const Config, - key: JString, - value: JString, -) { - || -> ZResult<()> { - let key = decode_string(&mut env, &key)?; - let value = decode_string(&mut env, &value)?; - let mut config = core::ptr::read(cfg_ptr); - let insert_result = config - .insert_json5(&key, &value) - .map_err(|err| zerror!(err)); - core::ptr::write(cfg_ptr as *mut _, config); - insert_result - }() - .unwrap_or_else(|err| { - throw_exception!(env, err); - }) -} - -/// Frees the pointer to the config. The pointer should be valid and should have been obtained through -/// one of the preceding `load` functions. This function should be called upon destruction of the kotlin -/// Config instance. -#[no_mangle] -#[allow(non_snake_case)] -pub(crate) unsafe extern "C" fn Java_io_zenoh_jni_JNIConfig_00024Companion_freePtrViaJNI( - _env: JNIEnv, - _: JClass, - config_ptr: *const Config, -) { - Arc::from_raw(config_ptr); -} diff --git a/zenoh-jni/src/errors.rs b/zenoh-jni/src/errors.rs deleted file mode 100644 index 23687d4cc..000000000 --- a/zenoh-jni/src/errors.rs +++ /dev/null @@ -1,59 +0,0 @@ -// -// Copyright (c) 2023 ZettaScale Technology -// -// This program and the accompanying materials are made available under the -// terms of the Eclipse Public License 2.0 which is available at -// http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 -// which is available at https://www.apache.org/licenses/LICENSE-2.0. -// -// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 -// -// Contributors: -// ZettaScale Zenoh Team, -// - -use std::fmt; - -use jni::JNIEnv; - -#[macro_export] -macro_rules! throw_exception { - ($env:expr, $err:expr) => {{ - let _ = $err.throw_on_jvm(&mut $env).map_err(|err| { - tracing::error!("Unable to throw exception: {}", err); - }); - }}; -} - -#[macro_export] -macro_rules! zerror { - ($arg:expr) => { - $crate::errors::ZError($arg.to_string()) - }; - ($fmt:expr, $($arg:tt)*) => { - $crate::errors::ZError(format!($fmt, $($arg)*)) - }; -} - -pub(crate) type ZResult = core::result::Result; - -#[derive(Debug)] -pub(crate) struct ZError(pub String); - -impl fmt::Display for ZError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{}", self.0) - } -} - -impl ZError { - const KOTLIN_EXCEPTION_NAME: &'static str = "io/zenoh/exceptions/ZError"; - - pub fn throw_on_jvm(&self, env: &mut JNIEnv) -> ZResult<()> { - let exception_class = env - .find_class(Self::KOTLIN_EXCEPTION_NAME) - .map_err(|err| zerror!("Failed to retrieve exception class: {}", err))?; - env.throw_new(exception_class, self.to_string()) - .map_err(|err| zerror!("Failed to throw exception: {}", err)) - } -} diff --git a/zenoh-jni/src/ext/advanced_publisher.rs b/zenoh-jni/src/ext/advanced_publisher.rs deleted file mode 100644 index d0a51e366..000000000 --- a/zenoh-jni/src/ext/advanced_publisher.rs +++ /dev/null @@ -1,339 +0,0 @@ -// -// Copyright (c) 2025 ZettaScale Technology -// -// This program and the accompanying materials are made available under the -// terms of the Eclipse Public License 2.0 which is available at -// http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 -// which is available at https://www.apache.org/licenses/LICENSE-2.0. -// -// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 -// -// Contributors: -// ZettaScale Zenoh Team, -// - -use std::sync::Arc; - -use jni::objects::JValue; -use jni::{ - objects::{JByteArray, JClass, JString}, - sys::jint, - JNIEnv, -}; -use zenoh::handlers::{Callback, DefaultHandler}; -use zenoh::Wait; -use zenoh_ext::AdvancedPublisher; - -use crate::owned_object::OwnedObject; -use crate::utils::{get_callback_global_ref, get_java_vm, load_on_close}; - -use crate::throw_exception; -use crate::{ - errors::ZResult, - utils::{decode_byte_array, decode_encoding}, - zerror, -}; -use jni::sys::jboolean; -use std::ptr::null; - -use jni::objects::JObject; -use zenoh::matching::{MatchingListener, MatchingListenerBuilder, MatchingStatus}; - -trait SetJniMatchingStatusCallback { - type WithCallback; - - unsafe fn set_jni_matching_status_callback( - self, - env: &mut JNIEnv, - callback: JObject, - on_close: JObject, - ) -> ZResult; -} - -impl<'a> SetJniMatchingStatusCallback for MatchingListenerBuilder<'a, DefaultHandler> { - type WithCallback = MatchingListenerBuilder<'a, Callback>; - - unsafe fn set_jni_matching_status_callback( - self, - env: &mut JNIEnv, - callback: JObject, - on_close: JObject, - ) -> ZResult { - let java_vm = Arc::new(get_java_vm(env)?); - let callback_global_ref = get_callback_global_ref(env, callback)?; - let on_close_global_ref = get_callback_global_ref(env, on_close)?; - let on_close = load_on_close(&java_vm, on_close_global_ref); - - let builder = self.callback(move |matching_status| { - on_close.noop(); // Moves `on_close` inside the closure so it gets destroyed with the closure - let _ = || -> ZResult<()> { - let mut env = java_vm.attach_current_thread_as_daemon().map_err(|err| { - zerror!("Unable to attach thread for matching listener: {}", err) - })?; - - env.call_method( - &callback_global_ref, - "run", - "(Z)V", - &[JValue::from(matching_status.matching())], - ) - .map_err(|err| zerror!(err))?; - Ok(()) - }() - .map_err(|err| tracing::error!("On matching listener callback error: {err}")); - }); - Ok(builder) - } -} - -/// Declare a MatchingListener for [AdvancedPublisher] via JNI. -/// -/// Parameters: -/// - `env`: The JNI environment. -/// - `_class`: The JNI class. -/// - `advanced_publisher_ptr`: The raw pointer to an [AdvancedPublisher]. -/// - `callback`: The callback function as an instance of the `JNIMatchingListenerCallback` interface in Java/Kotlin. -/// - `on_close`: A Java/Kotlin `JNIOnCloseCallback` function interface to be called upon undeclaring the [MatchingListener]. -/// -/// Returns: -/// - A raw pointer to the declared [MatchingListener]. In case of failure, an exception is thrown and null is returned. -/// -/// Safety: -/// - The function is marked as unsafe due to raw pointer manipulation and JNI interaction. -/// - It assumes that the provided [AdvancedPublisher] pointer is valid and has not been modified or freed. -/// - The [AdvancedPublisher] pointer remains valid and the ownership of the [AdvancedPublisher] is not transferred, -/// allowing safe usage of the [AdvancedPublisher] after this function call. -/// - The callback function passed as `callback` must be a valid instance of the `JNIMatchingListenerCallback` interface -/// in Java/Kotlin, matching the specified signature. -/// - The function may throw a JNI exception in case of failure, which should be handled by the caller. -/// -#[cfg(feature = "zenoh-ext")] -#[no_mangle] -#[allow(non_snake_case)] -pub unsafe extern "C" fn Java_io_zenoh_jni_JNIAdvancedPublisher_declareMatchingListenerViaJNI( - mut env: JNIEnv, - _class: JClass, - advanced_publisher_ptr: *const AdvancedPublisher, - - callback: JObject, - on_close: JObject, -) -> *const MatchingListener<()> { - let advanced_publisher = OwnedObject::from_raw(advanced_publisher_ptr); - - || -> ZResult<*const MatchingListener<()>> { - tracing::debug!( - "Declaring matching listener on '{}'...", - advanced_publisher.key_expr() - ); - - let matching_listener = advanced_publisher - .matching_listener() - .set_jni_matching_status_callback(&mut env, callback, on_close)? - .wait() - .map_err(|err| zerror!("Unable to declare matching listener: {}", err))?; - - tracing::debug!( - "Matching listener declared on '{}'...", - advanced_publisher.key_expr() - ); - Ok(Arc::into_raw(Arc::new(matching_listener))) - }() - .unwrap_or_else(|err| { - throw_exception!(env, err); - null() - }) -} - -/// Declare a background matching listener for [AdvancedPublisher] via JNI. -/// Register the listener callback to be run in background until the [AdvancedPublisher] is undeclared. -/// -/// Parameters: -/// - `env`: The JNI environment. -/// - `_class`: The JNI class. -/// - `advanced_publisher_ptr`: The raw pointer to an [AdvancedPublisher]. -/// - `callback`: The callback function as an instance of the `JNIMatchingListenerCallback` interface in Java/Kotlin. -/// - `on_close`: A Java/Kotlin `JNIOnCloseCallback` function interface to be called upon undeclaring the [AdvancedPublisher]. -/// -/// Safety: -/// - The function is marked as unsafe due to raw pointer manipulation and JNI interaction. -/// - It assumes that the provided [AdvancedPublisher] pointer is valid and has not been modified or freed. -/// - The [AdvancedPublisher] pointer remains valid and the ownership of the [AdvancedPublisher] is not transferred, -/// allowing safe usage of the [AdvancedPublisher] after this function call. -/// - The callback function passed as `callback` must be a valid instance of the `JNIMatchingListenerCallback` interface -/// in Java/Kotlin, matching the specified signature. -/// - The function may throw a JNI exception in case of failure, which should be handled by the caller. -/// -#[cfg(feature = "zenoh-ext")] -#[no_mangle] -#[allow(non_snake_case)] -pub unsafe extern "C" fn Java_io_zenoh_jni_JNIAdvancedPublisher_declareBackgroundMatchingListenerViaJNI( - mut env: JNIEnv, - _class: JClass, - advanced_publisher_ptr: *const AdvancedPublisher, - - callback: JObject, - on_close: JObject, -) { - let advanced_publisher = OwnedObject::from_raw(advanced_publisher_ptr); - - || -> ZResult<()> { - tracing::debug!( - "Declaring background matching listener on '{}'...", - advanced_publisher.key_expr() - ); - - advanced_publisher - .matching_listener() - .set_jni_matching_status_callback(&mut env, callback, on_close)? - .background() - .wait() - .map_err(|err| zerror!("Unable to declare background matching listener: {}", err))?; - - tracing::debug!( - "Background matching listener declared on '{}'...", - advanced_publisher.key_expr() - ); - Ok(()) - }() - .unwrap_or_else(|err| { - throw_exception!(env, err); - }); -} - -/// Return the matching status of the [AdvancedPublisher]. -/// -/// Parameters: -/// - `env`: The JNI environment. -/// - `_class`: The JNI class. -/// - `advanced_publisher_ptr`: The raw pointer to an [AdvancedPublisher]. -/// -/// Returns: -/// - will return true if there exist Subscribers matching the Publisher's key expression and false otherwise. -/// -/// Safety: -/// - The function is marked as unsafe due to raw pointer manipulation and JNI interaction. -/// - It assumes that the provided [AdvancedPublisher] pointer is valid and has not been modified or freed. -/// - The [AdvancedPublisher] pointer remains valid and the ownership of the [AdvancedPublisher] is not transferred, -/// allowing safe usage of the [AdvancedPublisher] after this function call. -/// - The function may throw a JNI exception in case of failure, which should be handled by the caller. -/// -#[cfg(feature = "zenoh-ext")] -#[no_mangle] -#[allow(non_snake_case)] -pub unsafe extern "C" fn Java_io_zenoh_jni_JNIAdvancedPublisher_getMatchingStatusViaJNI( - mut env: JNIEnv, - _class: JClass, - advanced_publisher_ptr: *const AdvancedPublisher, -) -> jboolean { - use crate::errors::ZError; - - let advanced_publisher = OwnedObject::from_raw(advanced_publisher_ptr); - advanced_publisher - .matching_status() - .wait() - .map(|val| val.matching() as jboolean) - .map_err(|e| zerror!(e.to_string())) - .unwrap_or_else(|err: ZError| { - throw_exception!(env, err); - false as jboolean - }) -} - -/// Performs a PUT operation on an [AdvancedPublisher] via JNI. -/// -/// # Parameters -/// - `env`: The JNI environment pointer. -/// - `_class`: The Java class reference (unused). -/// - `payload`: The byte array to be published. -/// - `encoding_id`: The encoding ID of the payload. -/// - `encoding_schema`: Nullable encoding schema string of the payload. -/// - `attachment`: Nullble byte array for the attachment. -/// - `publisher_ptr`: The raw pointer to the [AdvancedPublisher]. -/// -/// # Safety -/// - This function is marked as unsafe due to raw pointer manipulation and JNI interaction. -/// - Assumes that the provided [AdvancedPublisher] pointer is valid and has not been modified or freed. -/// - The [AdvancedPublisher] pointer remains valid after this function call. -/// - May throw an exception in case of failure, which must be handled by the caller. -/// -#[no_mangle] -#[allow(non_snake_case)] -pub unsafe extern "C" fn Java_io_zenoh_jni_JNIAdvancedPublisher_putViaJNI( - mut env: JNIEnv, - _class: JClass, - payload: JByteArray, - encoding_id: jint, - encoding_schema: /*nullable*/ JString, - attachment: /*nullable*/ JByteArray, - publisher_ptr: *const AdvancedPublisher<'static>, -) { - let publisher = OwnedObject::from_raw(publisher_ptr); - let _ = || -> ZResult<()> { - let payload = decode_byte_array(&env, payload)?; - let mut publication = publisher.put(payload); - let encoding = decode_encoding(&mut env, encoding_id, &encoding_schema)?; - publication = publication.encoding(encoding); - if !attachment.is_null() { - let attachment = decode_byte_array(&env, attachment)?; - publication = publication.attachment::>(attachment) - }; - publication.wait().map_err(|err| zerror!(err)) - }() - .map_err(|err| throw_exception!(env, err)); -} - -/// Performs a DELETE operation on an [AdvancedPublisher] via JNI. -/// -/// # Parameters -/// - `env`: The JNI environment pointer. -/// - `_class`: The Java class reference (unused). -/// - `attachment`: Nullble byte array for the attachment. -/// - `publisher_ptr`: The raw pointer to the [AdvancedPublisher]. -/// -/// # Safety -/// - This function is marked as unsafe due to raw pointer manipulation and JNI interaction. -/// - Assumes that the provided [AdvancedPublisher] pointer is valid and has not been modified or freed. -/// - The [AdvancedPublisher] pointer remains valid after this function call. -/// - May throw an exception in case of failure, which must be handled by the caller. -/// -#[no_mangle] -#[allow(non_snake_case)] -pub unsafe extern "C" fn Java_io_zenoh_jni_JNIAdvancedPublisher_deleteViaJNI( - mut env: JNIEnv, - _class: JClass, - attachment: /*nullable*/ JByteArray, - publisher_ptr: *const AdvancedPublisher<'static>, -) { - let publisher = OwnedObject::from_raw(publisher_ptr); - let _ = || -> ZResult<()> { - let mut delete = publisher.delete(); - if !attachment.is_null() { - let attachment = decode_byte_array(&env, attachment)?; - delete = delete.attachment::>(attachment) - }; - delete.wait().map_err(|err| zerror!(err)) - }() - .map_err(|err| throw_exception!(env, err)); -} - -/// Frees the [AdvancedPublisher]. -/// -/// # Parameters: -/// - `_env`: The JNI environment. -/// - `_class`: The JNI class. -/// - `publisher_ptr`: The raw pointer to the [AdvancedPublisher]. -/// -/// # Safety: -/// - The function is marked as unsafe due to raw pointer manipulation. -/// - It assumes that the provided [AdvancedPublisher] pointer is valid and has not been modified or freed. -/// - After calling this function, the [AdvancedPublisher] pointer becomes invalid and should not be used anymore. -/// -#[no_mangle] -#[allow(non_snake_case)] -pub(crate) unsafe extern "C" fn Java_io_zenoh_jni_JNIAdvancedPublisher_freePtrViaJNI( - _env: JNIEnv, - _: JClass, - publisher_ptr: *const AdvancedPublisher, -) { - Arc::from_raw(publisher_ptr); -} diff --git a/zenoh-jni/src/ext/advanced_subscriber.rs b/zenoh-jni/src/ext/advanced_subscriber.rs deleted file mode 100644 index 532c84e88..000000000 --- a/zenoh-jni/src/ext/advanced_subscriber.rs +++ /dev/null @@ -1,359 +0,0 @@ -// -// Copyright (c) 2025 ZettaScale Technology -// -// This program and the accompanying materials are made available under the -// terms of the Eclipse Public License 2.0 which is available at -// http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 -// which is available at https://www.apache.org/licenses/LICENSE-2.0. -// -// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 -// -// Contributors: -// ZettaScale Zenoh Team, -// - -use std::sync::Arc; - -use jni::sys::jboolean; -use jni::{objects::JClass, JNIEnv}; -use zenoh::handlers::{Callback, DefaultHandler}; -use zenoh::pubsub::Subscriber; -use zenoh_ext::SampleMissListener; -use zenoh_ext::{AdvancedSubscriber, Miss, SampleMissListenerBuilder}; - -use crate::sample_callback::SetJniSampleCallback; -use jni::objects::JObject; - -use crate::errors::ZResult; -use jni::objects::JValue; -use zenoh::Wait; - -use crate::owned_object::OwnedObject; - -use crate::utils::{get_callback_global_ref, get_java_vm, load_on_close}; -use crate::zerror; -use std::ptr::null; - -use crate::throw_exception; - -trait SetJniSampleMissListenerCallback { - type WithCallback; - - unsafe fn set_jni_sample_miss_callback( - self, - env: &mut JNIEnv, - callback: JObject, - on_close: JObject, - ) -> ZResult; -} - -impl<'a> SetJniSampleMissListenerCallback for SampleMissListenerBuilder<'a, DefaultHandler> { - type WithCallback = SampleMissListenerBuilder<'a, Callback>; - - unsafe fn set_jni_sample_miss_callback( - self, - env: &mut JNIEnv, - callback: JObject, - on_close: JObject, - ) -> ZResult { - let java_vm = Arc::new(get_java_vm(env)?); - let callback_global_ref = get_callback_global_ref(env, callback)?; - let on_close_global_ref = get_callback_global_ref(env, on_close)?; - let on_close = load_on_close(&java_vm, on_close_global_ref); - - let builder = self.callback(move |miss| { - on_close.noop(); // Moves `on_close` inside the closure so it gets destroyed with the closure - let _ = || -> ZResult<()> { - let mut env = java_vm.attach_current_thread_as_daemon().map_err(|err| { - zerror!("Unable to attach thread for sample miss listener: {}", err) - })?; - - let (zid_lower, zid_upper, eid) = { - let id = miss.source(); - - let zid = id.zid().to_le_bytes(); - let zid_lower = i64::from_le_bytes(zid[0..8].try_into().unwrap()); - let zid_upper = i64::from_le_bytes(zid[8..16].try_into().unwrap()); - - (zid_lower, zid_upper, id.eid()) - }; - let missed_count = miss.nb(); - - env.call_method( - &callback_global_ref, - "run", - "(JJJJ)V", - &[ - JValue::from(zid_lower), - JValue::from(zid_upper), - JValue::from(eid as i64), - JValue::from(missed_count as i64), - ], - ) - .map_err(|err| zerror!(err))?; - Ok(()) - }() - .map_err(|err| tracing::error!("On sample miss listener callback error: {err}")); - }); - Ok(builder) - } -} - -/// Declares a subscriber to detect matching publishers for an [AdvancedSubscriber] via JNI. -/// -/// Parameters: -/// - `env`: The JNI environment. -/// - `_class`: The JNI class. -/// - `advanced_subscriber_ptr`: The raw pointer to the [AdvancedSubscriber]. -/// - `callback`: The callback function as an instance of the `JNISubscriberCallback` interface in Java/Kotlin. -/// - `on_close`: A Java/Kotlin `JNIOnCloseCallback` function interface to be called upon closing the subscriber. -/// -/// Returns: -/// - A raw pointer to the declared [Subscriber]. In case of failure, an exception is thrown and null is returned. -/// -/// Safety: -/// - The function is marked as unsafe due to raw pointer manipulation and JNI interaction. -/// - It assumes that the provided [AdvancedSubscriber] pointer is valid and has not been modified or freed. -/// - The [AdvancedSubscriber] pointer remains valid and the ownership of the [AdvancedSubscriber] is not transferred, -/// allowing safe usage of the [AdvancedSubscriber] after this function call. -/// - The callback function passed as `callback` must be a valid instance of the `JNISubscriberCallback` interface -/// in Java/Kotlin, matching the specified signature. -/// - The function may throw a JNI exception in case of failure, which should be handled by the caller. -/// -#[cfg(feature = "zenoh-ext")] -#[no_mangle] -#[allow(non_snake_case)] -pub unsafe extern "C" fn Java_io_zenoh_jni_JNIAdvancedSubscriber_declareDetectPublishersSubscriberViaJNI( - mut env: JNIEnv, - _class: JClass, - advanced_subscriber_ptr: *const AdvancedSubscriber<()>, - history: jboolean, - callback: JObject, - on_close: JObject, -) -> *const Subscriber<()> { - let advanced_subscriber = OwnedObject::from_raw(advanced_subscriber_ptr); - - || -> ZResult<*const Subscriber<()>> { - tracing::debug!( - "Declaring detect publishers subscriber on '{}'...", - advanced_subscriber.key_expr() - ); - - let detect_publishers_subscriber = advanced_subscriber - .detect_publishers() - .history(history != 0) - .set_jni_sample_callback(&mut env, callback, on_close)? - .wait() - .map_err(|err| zerror!("Unable to declare detect publishers subscriber: {}", err))?; - - tracing::debug!( - "Detect publishers subscriber declared on '{}'...", - advanced_subscriber.key_expr() - ); - Ok(Arc::into_raw(Arc::new(detect_publishers_subscriber))) - }() - .unwrap_or_else(|err| { - throw_exception!(env, err); - null() - }) -} - -/// Declares a background subscriber to detect matching publishers for an [AdvancedSubscriber] via JNI. -/// -/// Parameters: -/// - `env`: The JNI environment. -/// - `_class`: The JNI class. -/// - `advanced_subscriber_ptr`: The raw pointer to the [AdvancedSubscriber]. -/// - `callback`: The callback function as an instance of the `JNISubscriberCallback` interface in Java/Kotlin. -/// - `on_close`: A Java/Kotlin `JNIOnCloseCallback` function interface to be called upon closing the subscriber. -/// -/// Safety: -/// - The function is marked as unsafe due to raw pointer manipulation and JNI interaction. -/// - It assumes that the provided [AdvancedSubscriber] pointer is valid and has not been modified or freed. -/// - The [AdvancedSubscriber] pointer remains valid and the ownership of the [AdvancedSubscriber] is not transferred, -/// allowing safe usage of the [AdvancedSubscriber] after this function call. -/// - The callback function passed as `callback` must be a valid instance of the `JNISubscriberCallback` interface -/// in Java/Kotlin, matching the specified signature. -/// - The function may throw a JNI exception in case of failure, which should be handled by the caller. -/// -#[cfg(feature = "zenoh-ext")] -#[no_mangle] -#[allow(non_snake_case)] -pub unsafe extern "C" fn Java_io_zenoh_jni_JNIAdvancedSubscriber_declareBackgroundDetectPublishersSubscriberViaJNI( - mut env: JNIEnv, - _class: JClass, - advanced_subscriber_ptr: *const AdvancedSubscriber<()>, - history: jboolean, - callback: JObject, - on_close: JObject, -) { - let advanced_subscriber = OwnedObject::from_raw(advanced_subscriber_ptr); - - || -> ZResult<()> { - tracing::debug!( - "Declaring background detect publishers subscriber on '{}'...", - advanced_subscriber.key_expr() - ); - - advanced_subscriber - .detect_publishers() - .history(history != 0) - .set_jni_sample_callback(&mut env, callback, on_close)? - .background() - .wait() - .map_err(|err| { - zerror!( - "Unable to declare background detect publishers subscriber: {}", - err - ) - })?; - - tracing::debug!( - "Background detect publishers subscriber declared on '{}'...", - advanced_subscriber.key_expr() - ); - Ok(()) - }() - .unwrap_or_else(|err| { - throw_exception!(env, err); - }); -} - -/// Declares a [SampleMissListener] to detect missed samples for an [AdvancedSubscriber] via JNI. -/// -/// Parameters: -/// - `env`: The JNI environment. -/// - `_class`: The JNI class. -/// - `advanced_subscriber_ptr`: The raw pointer to the [AdvancedSubscriber]. -/// - `callback`: The callback function as an instance of the `JNISampleMissListenerCallback` interface in Java/Kotlin. -/// - `on_close`: A Java/Kotlin `JNIOnCloseCallback` function interface to be called upon closing the subscriber. -/// -/// Returns: -/// - A raw pointer to the declared [SampleMissListener]. In case of failure, an exception is thrown and null is returned. -/// -/// Safety: -/// - The function is marked as unsafe due to raw pointer manipulation and JNI interaction. -/// - It assumes that the provided [AdvancedSubscriber] pointer is valid and has not been modified or freed. -/// - The [AdvancedSubscriber] pointer remains valid and the ownership of the [AdvancedSubscriber] is not transferred, -/// allowing safe usage of the [AdvancedSubscriber] after this function call. -/// - The callback function passed as `callback` must be a valid instance of the `JNISampleMissListenerCallback` interface -/// in Java/Kotlin, matching the specified signature. -/// - The function may throw a JNI exception in case of failure, which should be handled by the caller. -/// -#[cfg(feature = "zenoh-ext")] -#[no_mangle] -#[allow(non_snake_case)] -pub unsafe extern "C" fn Java_io_zenoh_jni_JNIAdvancedSubscriber_declareSampleMissListenerViaJNI( - mut env: JNIEnv, - _class: JClass, - advanced_subscriber_ptr: *const AdvancedSubscriber<()>, - - callback: JObject, - on_close: JObject, -) -> *const SampleMissListener<()> { - let advanced_subscriber = OwnedObject::from_raw(advanced_subscriber_ptr); - - || -> ZResult<*const SampleMissListener<()>> { - tracing::debug!( - "Declaring sample miss listener on '{}'...", - advanced_subscriber.key_expr() - ); - - let result = advanced_subscriber - .sample_miss_listener() - .set_jni_sample_miss_callback(&mut env, callback, on_close)? - .wait(); - - let sample_miss_listener = - result.map_err(|err| zerror!("Unable to declare sample miss listener: {}", err))?; - - tracing::debug!( - "Matching listener declared on '{}'...", - advanced_subscriber.key_expr() - ); - Ok(Arc::into_raw(Arc::new(sample_miss_listener))) - }() - .unwrap_or_else(|err| { - throw_exception!(env, err); - null() - }) -} - -/// Declare a background sample miss listener for [AdvancedSubscriber] via JNI. -/// Register the listener callback to be run in background until the [AdvancedSubscriber] is undeclared. -/// -/// Parameters: -/// - `env`: The JNI environment. -/// - `_class`: The JNI class. -/// - `advanced_subscriber_ptr`: The raw pointer to an [AdvancedSubscriber]. -/// - `callback`: The callback function as an instance of the `JNISampleMissListenerCallback` interface in Java/Kotlin. -/// - `on_close`: A Java/Kotlin `JNIOnCloseCallback` function interface to be called upon undeclaring the [AdvancedSubscriber]. -/// -/// Safety: -/// - The function is marked as unsafe due to raw pointer manipulation and JNI interaction. -/// - It assumes that the provided [AdvancedSubscriber] pointer is valid and has not been modified or freed. -/// - The [AdvancedSubscriber] pointer remains valid and the ownership of the [AdvancedSubscriber] is not transferred, -/// allowing safe usage of the [AdvancedSubscriber] after this function call. -/// - The callback function passed as `callback` must be a valid instance of the `JNISampleMissListenerCallback` interface -/// in Java/Kotlin, matching the specified signature. -/// - The function may throw a JNI exception in case of failure, which should be handled by the caller. -/// -#[cfg(feature = "zenoh-ext")] -#[no_mangle] -#[allow(non_snake_case)] -pub unsafe extern "C" fn Java_io_zenoh_jni_JNIAdvancedSubscriber_declareBackgroundSampleMissListenerViaJNI( - mut env: JNIEnv, - _class: JClass, - advanced_subscriber_ptr: *const AdvancedSubscriber<()>, - - callback: JObject, - on_close: JObject, -) { - let advanced_subscriber = OwnedObject::from_raw(advanced_subscriber_ptr); - - || -> ZResult<()> { - tracing::debug!( - "Declaring background sample miss listener on '{}'...", - advanced_subscriber.key_expr() - ); - - advanced_subscriber - .sample_miss_listener() - .set_jni_sample_miss_callback(&mut env, callback, on_close)? - .background() - .wait() - .map_err(|err| zerror!("Unable to declare background sample miss listener: {}", err))?; - - tracing::debug!( - "Background sample miss listener declared on '{}'...", - advanced_subscriber.key_expr() - ); - Ok(()) - }() - .unwrap_or_else(|err| { - throw_exception!(env, err); - }) -} - -/// Frees the [AdvancedSubscriber]. -/// -/// # Parameters: -/// - `_env`: The JNI environment. -/// - `_class`: The JNI class. -/// - `subscriber_ptr`: The raw pointer to the [AdvancedSubscriber]. -/// -/// # Safety: -/// - The function is marked as unsafe due to raw pointer manipulation. -/// - It assumes that the provided [AdvancedSubscriber] pointer is valid and has not been modified or freed. -/// - The function takes ownership of the raw pointer and releases the associated memory. -/// - After calling this function, the [AdvancedSubscriber] pointer becomes invalid and should not be used anymore. -/// -#[no_mangle] -#[allow(non_snake_case)] -pub(crate) unsafe extern "C" fn Java_io_zenoh_jni_JNIAdvancedSubscriber_freePtrViaJNI( - _env: JNIEnv, - _: JClass, - subscriber_ptr: *const AdvancedSubscriber<()>, -) { - Arc::from_raw(subscriber_ptr); -} diff --git a/zenoh-jni/src/ext/matching_listener.rs b/zenoh-jni/src/ext/matching_listener.rs deleted file mode 100644 index fd8856e17..000000000 --- a/zenoh-jni/src/ext/matching_listener.rs +++ /dev/null @@ -1,41 +0,0 @@ -// -// Copyright (c) 2025 ZettaScale Technology -// -// This program and the accompanying materials are made available under the -// terms of the Eclipse Public License 2.0 which is available at -// http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 -// which is available at https://www.apache.org/licenses/LICENSE-2.0. -// -// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 -// -// Contributors: -// ZettaScale Zenoh Team, -// - -use std::sync::Arc; - -use jni::{objects::JClass, JNIEnv}; -use zenoh::matching::MatchingListener; - -/// Frees the [MatchingListener]. -/// -/// # Parameters: -/// - `_env`: The JNI environment. -/// - `_class`: The JNI class. -/// - `matching_listener_ptr`: The raw pointer to the [MatchingListener]. -/// -/// # Safety: -/// - The function is marked as unsafe due to raw pointer manipulation. -/// - It assumes that the provided [MatchingListener] pointer is valid and has not been modified or freed. -/// - The function takes ownership of the raw pointer and releases the associated memory. -/// - After calling this function, the [MatchingListener] pointer becomes invalid and should not be used anymore. -/// -#[no_mangle] -#[allow(non_snake_case)] -pub(crate) unsafe extern "C" fn Java_io_zenoh_jni_JNIMatchingListener_freePtrViaJNI( - _env: JNIEnv, - _: JClass, - matching_listener_ptr: *const MatchingListener<()>, -) { - Arc::from_raw(matching_listener_ptr); -} diff --git a/zenoh-jni/src/ext/mod.rs b/zenoh-jni/src/ext/mod.rs deleted file mode 100644 index 193fd21ce..000000000 --- a/zenoh-jni/src/ext/mod.rs +++ /dev/null @@ -1,18 +0,0 @@ -// -// Copyright (c) 2025 ZettaScale Technology -// -// This program and the accompanying materials are made available under the -// terms of the Eclipse Public License 2.0 which is available at -// http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 -// which is available at https://www.apache.org/licenses/LICENSE-2.0. -// -// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 -// -// Contributors: -// ZettaScale Zenoh Team, -// - -mod advanced_publisher; -mod advanced_subscriber; -mod matching_listener; -mod sample_miss_listener; diff --git a/zenoh-jni/src/ext/sample_miss_listener.rs b/zenoh-jni/src/ext/sample_miss_listener.rs deleted file mode 100644 index ede727b64..000000000 --- a/zenoh-jni/src/ext/sample_miss_listener.rs +++ /dev/null @@ -1,41 +0,0 @@ -// -// Copyright (c) 2025 ZettaScale Technology -// -// This program and the accompanying materials are made available under the -// terms of the Eclipse Public License 2.0 which is available at -// http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 -// which is available at https://www.apache.org/licenses/LICENSE-2.0. -// -// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 -// -// Contributors: -// ZettaScale Zenoh Team, -// - -use std::sync::Arc; - -use jni::{objects::JClass, JNIEnv}; -use zenoh_ext::SampleMissListener; - -/// Frees the [SampleMissListener]. -/// -/// # Parameters: -/// - `_env`: The JNI environment. -/// - `_class`: The JNI class. -/// - `sample_miss_listener_ptr`: The raw pointer to the [SampleMissListener]. -/// -/// # Safety: -/// - The function is marked as unsafe due to raw pointer manipulation. -/// - It assumes that the provided [SampleMissListener] pointer is valid and has not been modified or freed. -/// - The function takes ownership of the raw pointer and releases the associated memory. -/// - After calling this function, the [SampleMissListener] pointer becomes invalid and should not be used anymore. -/// -#[no_mangle] -#[allow(non_snake_case)] -pub(crate) unsafe extern "C" fn Java_io_zenoh_jni_JNISampleMissListener_freePtrViaJNI( - _env: JNIEnv, - _: JClass, - sample_miss_listener_ptr: *const SampleMissListener<()>, -) { - Arc::from_raw(sample_miss_listener_ptr); -} diff --git a/zenoh-jni/src/key_expr.rs b/zenoh-jni/src/key_expr.rs deleted file mode 100644 index 11d39d478..000000000 --- a/zenoh-jni/src/key_expr.rs +++ /dev/null @@ -1,333 +0,0 @@ -// -// Copyright (c) 2023 ZettaScale Technology -// -// This program and the accompanying materials are made available under the -// terms of the Eclipse Public License 2.0 which is available at -// http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 -// which is available at https://www.apache.org/licenses/LICENSE-2.0. -// -// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 -// -// Contributors: -// ZettaScale Zenoh Team, -// - -use std::ops::Deref; -use std::sync::Arc; - -use jni::objects::JClass; -use jni::sys::{jboolean, jint, jstring}; -use jni::{objects::JString, JNIEnv}; -use zenoh::key_expr::KeyExpr; - -use crate::errors::ZResult; -use crate::utils::decode_string; -use crate::{throw_exception, zerror}; - -/// Validates the provided `key_expr` to be a valid key expression, returning it back -/// in case of success or throwing an exception in case of failure. -/// -/// # Parameters: -/// `env`: The JNI environment. -/// `_class`: the Java class (unused). -/// `key_expr`: Java string representation of the intended key expression. -/// -#[no_mangle] -#[allow(non_snake_case)] -pub extern "C" fn Java_io_zenoh_jni_JNIKeyExpr_00024Companion_tryFromViaJNI( - mut env: JNIEnv, - _class: JClass, - key_expr: JString, -) -> jstring { - validate_key_expr(&mut env, &key_expr) - .map(|_| **key_expr) - .unwrap_or_else(|err| { - throw_exception!(env, err); - JString::default().as_raw() - }) -} - -/// Returns a java string representation of the autocanonized version of the provided `key_expr`. -/// In case of failure and exception will be thrown. -/// -/// # Parameters: -/// `env`: The JNI environment. -/// `_class`: the Java class (unused). -/// `key_expr`: Java string representation of the intended key expression. -/// -#[no_mangle] -#[allow(non_snake_case)] -pub extern "C" fn Java_io_zenoh_jni_JNIKeyExpr_00024Companion_autocanonizeViaJNI( - mut env: JNIEnv, - _class: JClass, - key_expr: JString, -) -> jstring { - autocanonize_key_expr(&mut env, &key_expr) - .and_then(|key_expr| { - env.new_string(key_expr.to_string()) - .map(|kexp| kexp.as_raw()) - .map_err(|err| zerror!(err)) - }) - .unwrap_or_else(|err| { - throw_exception!(env, err); - JString::default().as_raw() - }) -} - -/// Returns true in case key_expr_1 intersects key_expr_2. -/// -/// # Params: -/// - `key_expr_ptr_1`: Pointer to the key expression 1, differs from null only if it's a declared key expr. -/// - `key_expr_str_1`: String representation of the key expression 1. -/// - `key_expr_ptr_2`: Pointer to the key expression 2, differs from null only if it's a declared key expr. -/// - `key_expr_str_2`: String representation of the key expression 2. -/// -/// # Safety -/// - This function is marked as unsafe due to raw pointer manipulation, which happens only when providing -/// key expressions that were declared from a session (in that case the key expression has a pointer associated). -/// -/// In that case, this function assumes the pointers are valid pointers to key expressions and those pointers -/// remain valid after the call to this function. -/// -#[no_mangle] -#[allow(non_snake_case)] -pub unsafe extern "C" fn Java_io_zenoh_jni_JNIKeyExpr_00024Companion_intersectsViaJNI( - mut env: JNIEnv, - _: JClass, - key_expr_ptr_1: /*nullable*/ *const KeyExpr<'static>, - key_expr_str_1: JString, - key_expr_ptr_2: /*nullable*/ *const KeyExpr<'static>, - key_expr_str_2: JString, -) -> jboolean { - || -> ZResult { - let key_expr_1 = process_kotlin_key_expr(&mut env, &key_expr_str_1, key_expr_ptr_1)?; - let key_expr_2 = process_kotlin_key_expr(&mut env, &key_expr_str_2, key_expr_ptr_2)?; - Ok(key_expr_1.intersects(&key_expr_2) as jboolean) - }() - .unwrap_or_else(|err| { - throw_exception!(env, err); - false as jboolean - }) -} - -/// Returns true in case key_expr_1 includes key_expr_2. -/// -/// # Params: -/// - `key_expr_ptr_1`: Pointer to the key expression 1, differs from null only if it's a declared key expr. -/// - `key_expr_str_1`: String representation of the key expression 1. -/// - `key_expr_ptr_2`: Pointer to the key expression 2, differs from null only if it's a declared key expr. -/// - `key_expr_str_2`: String representation of the key expression 2. -/// -/// # Safety -/// - This function is marked as unsafe due to raw pointer manipulation, which happens only when providing -/// key expressions that were declared from a session (in that case the key expression has a pointer associated). -/// -/// In that case, this function assumes the pointers are valid pointers to key expressions and those pointers -/// remain valid after the call to this function. -/// -#[no_mangle] -#[allow(non_snake_case)] -pub unsafe extern "C" fn Java_io_zenoh_jni_JNIKeyExpr_00024Companion_includesViaJNI( - mut env: JNIEnv, - _: JClass, - key_expr_ptr_1: /*nullable*/ *const KeyExpr<'static>, - key_expr_str_1: JString, - key_expr_ptr_2: /*nullable*/ *const KeyExpr<'static>, - key_expr_str_2: JString, -) -> jboolean { - || -> ZResult { - let key_expr_1 = process_kotlin_key_expr(&mut env, &key_expr_str_1, key_expr_ptr_1)?; - let key_expr_2 = process_kotlin_key_expr(&mut env, &key_expr_str_2, key_expr_ptr_2)?; - Ok(key_expr_1.includes(&key_expr_2) as jboolean) - }() - .unwrap_or_else(|err| { - throw_exception!(env, err); - false as jboolean - }) -} - -/// Returns the integer representation of the intersection level of the key expression 1 and key expression 2, -/// from the perspective of key expression 1. -/// -/// # Params: -/// - `key_expr_ptr_1`: Pointer to the key expression 1, differs from null only if it's a declared key expr. -/// - `key_expr_str_1`: String representation of the key expression 1. -/// - `key_expr_ptr_2`: Pointer to the key expression 2, differs from null only if it's a declared key expr. -/// - `key_expr_str_2`: String representation of the key expression 2. -/// -/// # Safety -/// - This function is marked as unsafe due to raw pointer manipulation, which happens only when providing -/// key expressions that were declared from a session (in that case the key expression has a pointer associated). -/// -/// In that case, this function assumes the pointers are valid pointers to key expressions and those pointers -/// remain valid after the call to this function. -/// -#[no_mangle] -#[allow(non_snake_case)] -pub unsafe extern "C" fn Java_io_zenoh_jni_JNIKeyExpr_00024Companion_relationToViaJNI( - mut env: JNIEnv, - _: JClass, - key_expr_ptr_1: /*nullable*/ *const KeyExpr<'static>, - key_expr_str_1: JString, - key_expr_ptr_2: /*nullable*/ *const KeyExpr<'static>, - key_expr_str_2: JString, -) -> jint { - || -> ZResult { - let key_expr_1 = process_kotlin_key_expr(&mut env, &key_expr_str_1, key_expr_ptr_1)?; - let key_expr_2 = process_kotlin_key_expr(&mut env, &key_expr_str_2, key_expr_ptr_2)?; - Ok(key_expr_1.relation_to(&key_expr_2) as jint) - }() - .unwrap_or_else(|err| { - throw_exception!(env, err); - -1 as jint - }) -} - -/// Joins key expression 1 with key expression 2, where key_expr_2 is a string. Returns the string representation -/// of the result, or throws an exception in case of failure. -/// -/// # Params: -/// - `key_expr_ptr_1`: Pointer to the key expression 1, differs from null only if it's a declared key expr. -/// - `key_expr_ptr_1`: String representation of the key expression 1. -/// - `key_expr_2`: String representation of the key expression 2. -/// -/// # Safety -/// - This function is marked as unsafe due to raw pointer manipulation, which happens only when providing -/// key expressions that were declared from a session (in that case the key expression has a pointer associated). -/// -/// In that case, this function assumes the pointers are valid pointers to key expressions and those pointers -/// remain valid after the call to this function. -/// -#[no_mangle] -#[allow(non_snake_case)] -pub unsafe extern "C" fn Java_io_zenoh_jni_JNIKeyExpr_00024Companion_joinViaJNI( - mut env: JNIEnv, - _class: JClass, - key_expr_ptr_1: /*nullable*/ *const KeyExpr<'static>, - key_expr_str_1: JString, - key_expr_2: JString, -) -> jstring { - || -> ZResult { - let key_expr_1 = process_kotlin_key_expr(&mut env, &key_expr_str_1, key_expr_ptr_1)?; - let key_expr_2_str = decode_string(&mut env, &key_expr_2)?; - let result = key_expr_1 - .join(key_expr_2_str.as_str()) - .map_err(|err| zerror!(err))?; - env.new_string(result.to_string()) - .map(|kexp| kexp.as_raw()) - .map_err(|err| zerror!(err)) - }() - .unwrap_or_else(|err| { - throw_exception!(env, err); - JString::default().as_raw() - }) -} - -/// Concats key_expr_1 with key_expr_2, where key_expr_2 is a string. Returns the string representation -/// of the result, or throws an exception in case of failure. -/// -/// # Params: -/// - `key_expr_ptr_1`: Pointer to the key expression 1, differs from null only if it's a declared key expr. -/// - `key_expr_ptr_1`: String representation of the key expression 1. -/// - `key_expr_2`: String representation of the key expression 2. -/// -/// # Safety -/// - This function is marked as unsafe due to raw pointer manipulation, which happens only when providing -/// key expressions that were declared from a session (in that case the key expression has a pointer associated). -/// -/// In that case, this function assumes the pointers are valid pointers to key expressions and those pointers -/// remain valid after the call to this function. -/// -#[no_mangle] -#[allow(non_snake_case)] -pub unsafe extern "C" fn Java_io_zenoh_jni_JNIKeyExpr_00024Companion_concatViaJNI( - mut env: JNIEnv, - _class: JClass, - key_expr_ptr_1: /*nullable*/ *const KeyExpr<'static>, - key_expr_str_1: JString, - key_expr_2: JString, -) -> jstring { - || -> ZResult { - let key_expr_1 = process_kotlin_key_expr(&mut env, &key_expr_str_1, key_expr_ptr_1)?; - let key_expr_2_str = decode_string(&mut env, &key_expr_2)?; - let result = key_expr_1 - .concat(key_expr_2_str.as_str()) - .map_err(|err| zerror!(err))?; - env.new_string(result.to_string()) - .map(|kexp| kexp.as_raw()) - .map_err(|err| zerror!(err)) - }() - .unwrap_or_else(|err| { - throw_exception!(env, err); - JString::default().as_raw() - }) -} - -/// Frees a declared key expression. -/// -/// # Parameters -/// - `_env`: Unused. The JNI environment. -/// - `_class`: Unused. The java class from which the function was called. -/// - `key_expr_ptr`: the pointer to the key expression. -/// -/// # Safety -/// - This function assumes the provided pointer is valid and points to a native key expression. -/// - The memory associated to the pointer is freed after returning from this call, turning the -/// pointer invalid after that. -/// -#[no_mangle] -#[allow(non_snake_case)] -pub unsafe extern "C" fn Java_io_zenoh_jni_JNIKeyExpr_freePtrViaJNI( - _env: JNIEnv, - _: JClass, - key_expr_ptr: *const KeyExpr<'static>, -) { - Arc::from_raw(key_expr_ptr); -} - -fn validate_key_expr(env: &mut JNIEnv, key_expr: &JString) -> ZResult> { - let key_expr_str = decode_string(env, key_expr) - .map_err(|err| zerror!("Unable to get key expression string value: '{}'.", err))?; - - KeyExpr::try_from(key_expr_str) - .map_err(|err| zerror!("Unable to create key expression: '{}'.", err)) -} - -fn autocanonize_key_expr(env: &mut JNIEnv, key_expr: &JString) -> ZResult> { - decode_string(env, key_expr) - .map_err(|err| zerror!("Unable to get key expression string value: '{}'.", err)) - .and_then(|key_expr_str| { - KeyExpr::autocanonize(key_expr_str) - .map_err(|err| zerror!("Unable to create key expression: '{}'", err)) - }) -} - -/// Processes a kotlin key expression. -/// -/// Receives the Java/Kotlin string representation of the key expression as well as a pointer. -/// The pointer is only valid in cases where the key expression is associated to a native pointer -/// (when the key expression was declared from a session). -/// If the pointer is valid, the key expression returned is the key expression the pointer pointed to. -/// Otherwise, a key expression created from the string representation of the key expression is returned. -/// -/// # Safety: -/// -/// The key_expr_str argument provided should already have been validated upon creation of the -/// KeyExpr instance on Kotlin. -/// -pub(crate) unsafe fn process_kotlin_key_expr( - env: &mut JNIEnv, - key_expr_str: &JString, - key_expr_ptr: *const KeyExpr<'static>, -) -> ZResult> { - if key_expr_ptr.is_null() { - let key_expr = decode_string(env, key_expr_str) - .map_err(|err| zerror!("Unable to get key expression string value: '{}'.", err))?; - Ok(KeyExpr::from_string_unchecked(key_expr)) - } else { - let key_expr = Arc::from_raw(key_expr_ptr); - let key_expr_clone = key_expr.deref().clone(); - std::mem::forget(key_expr); - Ok(key_expr_clone) - } -} diff --git a/zenoh-jni/src/lib.rs b/zenoh-jni/src/lib.rs deleted file mode 100644 index 93765edb8..000000000 --- a/zenoh-jni/src/lib.rs +++ /dev/null @@ -1,41 +0,0 @@ -// -// Copyright (c) 2023 ZettaScale Technology -// -// This program and the accompanying materials are made available under the -// terms of the Eclipse Public License 2.0 which is available at -// http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 -// which is available at https://www.apache.org/licenses/LICENSE-2.0. -// -// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 -// -// Contributors: -// ZettaScale Zenoh Team, -// - -mod config; -mod errors; -#[cfg(feature = "zenoh-ext")] -mod ext; -mod key_expr; -mod liveliness; -mod logger; -pub(crate) mod owned_object; -mod publisher; -mod querier; -mod query; -mod queryable; -pub(crate) mod sample_callback; -mod scouting; -mod session; -mod subscriber; -mod utils; -#[cfg(feature = "zenoh-ext")] -mod zbytes; -mod zenoh_id; - -// Test should be runned with `cargo test --no-default-features` -#[test] -#[cfg(not(feature = "default"))] -fn test_no_default_features() { - assert_eq!(zenoh::FEATURES, concat!(" zenoh/unstable")); -} diff --git a/zenoh-jni/src/liveliness.rs b/zenoh-jni/src/liveliness.rs deleted file mode 100644 index d822eb948..000000000 --- a/zenoh-jni/src/liveliness.rs +++ /dev/null @@ -1,174 +0,0 @@ -// -// Copyright (c) 2023 ZettaScale Technology -// -// This program and the accompanying materials are made available under the -// terms of the Eclipse Public License 2.0 which is available at -// http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 -// which is available at https://www.apache.org/licenses/LICENSE-2.0. -// -// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 -// -// Contributors: -// ZettaScale Zenoh Team, -// - -use std::{ptr::null, sync::Arc, time::Duration}; - -use jni::{ - objects::{JClass, JObject, JString}, - sys::{jboolean, jlong}, - JNIEnv, -}; - -use zenoh::{ - internal::runtime::ZRuntime, key_expr::KeyExpr, liveliness::LivelinessToken, - pubsub::Subscriber, Session, Wait, -}; - -use crate::{ - errors::ZResult, - key_expr::process_kotlin_key_expr, - owned_object::OwnedObject, - sample_callback::SetJniSampleCallback, - session::{on_reply_error, on_reply_success}, - throw_exception, - utils::{get_callback_global_ref, get_java_vm, load_on_close}, - zerror, -}; - -#[no_mangle] -#[allow(non_snake_case)] -pub extern "C" fn Java_io_zenoh_jni_JNILiveliness_getViaJNI( - mut env: JNIEnv, - _class: JClass, - session_ptr: *const Session, - key_expr_ptr: /*nullable*/ *const KeyExpr<'static>, - key_expr_str: JString, - callback: JObject, - timeout_ms: jlong, - on_close: JObject, -) { - let session = unsafe { OwnedObject::from_raw(session_ptr) }; - let _ = || -> ZResult<()> { - let key_expr = unsafe { process_kotlin_key_expr(&mut env, &key_expr_str, key_expr_ptr) }?; - let java_vm = Arc::new(get_java_vm(&mut env)?); - let callback_global_ref = get_callback_global_ref(&mut env, callback)?; - let on_close_global_ref = get_callback_global_ref(&mut env, on_close)?; - let on_close = load_on_close(&java_vm, on_close_global_ref); - let timeout = Duration::from_millis(timeout_ms as u64); - let replies = session - .liveliness() - .get(key_expr.to_owned()) - .timeout(timeout) - .wait() - .map_err(|err| zerror!(err))?; - - ZRuntime::Application.spawn(async move { - on_close.noop(); // Does nothing, but moves `on_close` inside the closure so it gets destroyed with the closure - while let Ok(reply) = replies.recv_async().await { - || -> ZResult<()> { - tracing::debug!("Receiving liveliness reply through JNI: {:?}", reply); - let mut env = java_vm.attach_current_thread_as_daemon().map_err(|err| { - zerror!( - "Unable to attach thread for GET liveliness query callback: {}.", - err - ) - })?; - match reply.result() { - Ok(sample) => on_reply_success( - &mut env, - reply.replier_id(), - sample, - &callback_global_ref, - ), - Err(error) => on_reply_error( - &mut env, - reply.replier_id(), - error, - &callback_global_ref, - ), - } - }() - .unwrap_or_else(|err| tracing::error!("Error on get liveliness callback: {err}.")); - } - }); - Ok(()) - }() - .map_err(|err| { - throw_exception!(env, err); - }); -} - -#[no_mangle] -#[allow(non_snake_case)] -pub extern "C" fn Java_io_zenoh_jni_JNILiveliness_declareTokenViaJNI( - mut env: JNIEnv, - _class: JClass, - session_ptr: *const Session, - key_expr_ptr: /*nullable*/ *const KeyExpr<'static>, - key_expr_str: JString, -) -> *const LivelinessToken { - let session = unsafe { OwnedObject::from_raw(session_ptr) }; - || -> ZResult<*const LivelinessToken> { - let key_expr = unsafe { process_kotlin_key_expr(&mut env, &key_expr_str, key_expr_ptr) }?; - tracing::trace!("Declaring liveliness token on '{key_expr}'."); - let token = session - .liveliness() - .declare_token(key_expr) - .wait() - .map_err(|err| zerror!(err))?; - Ok(Arc::into_raw(Arc::new(token))) - }() - .unwrap_or_else(|err| { - throw_exception!(env, err); - null() - }) -} - -#[no_mangle] -#[allow(non_snake_case)] -pub extern "C" fn Java_io_zenoh_jni_JNILivelinessToken_00024Companion_undeclareViaJNI( - _env: JNIEnv, - _: JClass, - token_ptr: *const LivelinessToken, -) { - unsafe { Arc::from_raw(token_ptr) }; -} - -#[no_mangle] -#[allow(non_snake_case)] -pub extern "C" fn Java_io_zenoh_jni_JNILiveliness_declareSubscriberViaJNI( - mut env: JNIEnv, - _class: JClass, - session_ptr: *const Session, - key_expr_ptr: /*nullable*/ *const KeyExpr<'static>, - key_expr_str: JString, - callback: JObject, - history: jboolean, - on_close: JObject, -) -> *const Subscriber<()> { - let session = unsafe { OwnedObject::from_raw(session_ptr) }; - || -> ZResult<*const Subscriber<()>> { - let key_expr = unsafe { process_kotlin_key_expr(&mut env, &key_expr_str, key_expr_ptr)? }; - tracing::debug!("Declaring liveliness subscriber on '{}'...", key_expr); - - let result = unsafe { - session - .liveliness() - .declare_subscriber(key_expr.to_owned()) - .history(history != 0) - .set_jni_sample_callback(&mut env, callback, on_close)? - .wait() - }; - - let subscriber = - result.map_err(|err| zerror!("Unable to declare liveliness subscriber: {}", err))?; - - tracing::debug!("Liveliness subscriber declared on '{}'.", key_expr); - Ok(Arc::into_raw(Arc::new(subscriber))) - }() - .unwrap_or_else(|err| { - throw_exception!(env, err); - null() - }) -} diff --git a/zenoh-jni/src/logger.rs b/zenoh-jni/src/logger.rs deleted file mode 100644 index 12047a3e6..000000000 --- a/zenoh-jni/src/logger.rs +++ /dev/null @@ -1,79 +0,0 @@ -// -// Copyright (c) 2023 ZettaScale Technology -// -// This program and the accompanying materials are made available under the -// terms of the Eclipse Public License 2.0 which is available at -// http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 -// which is available at https://www.apache.org/licenses/LICENSE-2.0. -// -// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 -// -// Contributors: -// ZettaScale Zenoh Team, -// - -use jni::{ - objects::{JClass, JString}, - JNIEnv, -}; - -use crate::{errors::ZResult, throw_exception, zerror}; - -/// Redirects the Rust logs either to logcat for Android systems or to the standard output (for non-Android systems). -/// -/// This function is meant to be called from Java/Kotlin code through JNI. It takes a `filter` -/// indicating the desired log level. -/// If the logger was already initialized in a previous call, then it does nothing. -/// -/// See https://docs.rs/env_logger/latest/env_logger/index.html for accepted filter format. -/// -/// # Parameters: -/// - `env`: The JNI environment. -/// - `_class`: The JNI class. -/// - `filter`: The logs filter. -/// -/// # Errors: -/// - If there is an error parsing the log level string, a `JNIException` is thrown on the JVM. -/// -#[no_mangle] -#[allow(non_snake_case)] -pub extern "C" fn Java_io_zenoh_Logger_00024Companion_startLogsViaJNI( - mut env: JNIEnv, - _class: JClass, - filter: JString, -) { - || -> ZResult<()> { - let log_level = parse_filter(&mut env, filter)?; - #[cfg(target_os = "android")] - { - android_logd_logger::builder() - .parse_filters(log_level.as_str()) - .tag_target_strip() - .prepend_module(true) - .pstore(false) - .try_init() - .ok(); - } - - #[cfg(not(target_os = "android"))] - { - android_logd_logger::builder() - .parse_filters(log_level.as_str()) - .tag_target_strip() - .prepend_module(true) - .try_init() - .ok(); - } - - Ok(()) - }() - .unwrap_or_else(|err| throw_exception!(env, err)) -} - -fn parse_filter(env: &mut JNIEnv, log_level: JString) -> ZResult { - let log_level = env.get_string(&log_level).map_err(|err| zerror!(err))?; - log_level - .to_str() - .map(|level| Ok(level.to_string())) - .map_err(|err| zerror!(err))? -} diff --git a/zenoh-jni/src/owned_object.rs b/zenoh-jni/src/owned_object.rs deleted file mode 100644 index 25f1c5215..000000000 --- a/zenoh-jni/src/owned_object.rs +++ /dev/null @@ -1,46 +0,0 @@ -// -// Copyright (c) 2023 ZettaScale Technology -// -// This program and the accompanying materials are made available under the -// terms of the Eclipse Public License 2.0 which is available at -// http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 -// which is available at https://www.apache.org/licenses/LICENSE-2.0. -// -// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 -// -// Contributors: -// ZettaScale Zenoh Team, -// - -use std::{ops::Deref, sync::Arc}; - -/// Safe accessor to refocounted ([Arc]) owned objects. -/// Helps to avoid early drop by offloading [std::mem::forget] from user -pub(crate) struct OwnedObject { - inner: Option>, -} - -impl Deref for OwnedObject { - type Target = T; - - fn deref(&self) -> &Self::Target { - // SAFETY: inner is always initialized - unsafe { self.inner.as_ref().unwrap_unchecked() } - } -} - -impl Drop for OwnedObject { - fn drop(&mut self) { - // SAFETY: inner is always initialized - let inner = unsafe { self.inner.take().unwrap_unchecked() }; - std::mem::forget(inner); - } -} - -impl OwnedObject { - pub(crate) unsafe fn from_raw(ptr: *const T) -> Self { - Self { - inner: Some(Arc::from_raw(ptr)), - } - } -} diff --git a/zenoh-jni/src/publisher.rs b/zenoh-jni/src/publisher.rs deleted file mode 100644 index ead60c3f6..000000000 --- a/zenoh-jni/src/publisher.rs +++ /dev/null @@ -1,130 +0,0 @@ -// -// Copyright (c) 2023 ZettaScale Technology -// -// This program and the accompanying materials are made available under the -// terms of the Eclipse Public License 2.0 which is available at -// http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 -// which is available at https://www.apache.org/licenses/LICENSE-2.0. -// -// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 -// -// Contributors: -// ZettaScale Zenoh Team, -// - -use std::sync::Arc; - -use jni::{ - objects::{JByteArray, JClass, JString}, - sys::jint, - JNIEnv, -}; -use zenoh::{pubsub::Publisher, Wait}; - -use crate::throw_exception; -use crate::{ - errors::ZResult, - utils::{decode_byte_array, decode_encoding}, - zerror, -}; - -/// Performs a PUT operation on a Zenoh publisher via JNI. -/// -/// # Parameters -/// - `env`: The JNI environment pointer. -/// - `_class`: The Java class reference (unused). -/// - `payload`: The byte array to be published. -/// - `encoding_id`: The encoding ID of the payload. -/// - `encoding_schema`: Nullable encoding schema string of the payload. -/// - `attachment`: Nullble byte array for the attachment. -/// - `publisher_ptr`: The raw pointer to the Zenoh publisher ([Publisher]). -/// -/// # Safety -/// - This function is marked as unsafe due to raw pointer manipulation and JNI interaction. -/// - Assumes that the provided publisher pointer is valid and has not been modified or freed. -/// - The publisher pointer remains valid after this function call. -/// - May throw an exception in case of failure, which must be handled by the caller. -/// -#[no_mangle] -#[allow(non_snake_case)] -pub unsafe extern "C" fn Java_io_zenoh_jni_JNIPublisher_putViaJNI( - mut env: JNIEnv, - _class: JClass, - payload: JByteArray, - encoding_id: jint, - encoding_schema: /*nullable*/ JString, - attachment: /*nullable*/ JByteArray, - publisher_ptr: *const Publisher<'static>, -) { - let publisher = Arc::from_raw(publisher_ptr); - let _ = || -> ZResult<()> { - let payload = decode_byte_array(&env, payload)?; - let mut publication = publisher.put(payload); - let encoding = decode_encoding(&mut env, encoding_id, &encoding_schema)?; - publication = publication.encoding(encoding); - if !attachment.is_null() { - let attachment = decode_byte_array(&env, attachment)?; - publication = publication.attachment::>(attachment) - }; - publication.wait().map_err(|err| zerror!(err)) - }() - .map_err(|err| throw_exception!(env, err)); - std::mem::forget(publisher); -} - -/// Performs a DELETE operation on a Zenoh publisher via JNI. -/// -/// # Parameters -/// - `env`: The JNI environment pointer. -/// - `_class`: The Java class reference (unused). -/// - `attachment`: Nullble byte array for the attachment. -/// - `publisher_ptr`: The raw pointer to the Zenoh publisher ([Publisher]). -/// -/// # Safety -/// - This function is marked as unsafe due to raw pointer manipulation and JNI interaction. -/// - Assumes that the provided publisher pointer is valid and has not been modified or freed. -/// - The publisher pointer remains valid after this function call. -/// - May throw an exception in case of failure, which must be handled by the caller. -/// -#[no_mangle] -#[allow(non_snake_case)] -pub unsafe extern "C" fn Java_io_zenoh_jni_JNIPublisher_deleteViaJNI( - mut env: JNIEnv, - _class: JClass, - attachment: /*nullable*/ JByteArray, - publisher_ptr: *const Publisher<'static>, -) { - let publisher = Arc::from_raw(publisher_ptr); - let _ = || -> ZResult<()> { - let mut delete = publisher.delete(); - if !attachment.is_null() { - let attachment = decode_byte_array(&env, attachment)?; - delete = delete.attachment::>(attachment) - }; - delete.wait().map_err(|err| zerror!(err)) - }() - .map_err(|err| throw_exception!(env, err)); - std::mem::forget(publisher) -} - -/// Frees the publisher. -/// -/// # Parameters: -/// - `_env`: The JNI environment. -/// - `_class`: The JNI class. -/// - `publisher_ptr`: The raw pointer to the Zenoh publisher ([Publisher]). -/// -/// # Safety: -/// - The function is marked as unsafe due to raw pointer manipulation. -/// - It assumes that the provided publisher pointer is valid and has not been modified or freed. -/// - After calling this function, the publisher pointer becomes invalid and should not be used anymore. -/// -#[no_mangle] -#[allow(non_snake_case)] -pub(crate) unsafe extern "C" fn Java_io_zenoh_jni_JNIPublisher_freePtrViaJNI( - _env: JNIEnv, - _: JClass, - publisher_ptr: *const Publisher, -) { - Arc::from_raw(publisher_ptr); -} diff --git a/zenoh-jni/src/querier.rs b/zenoh-jni/src/querier.rs deleted file mode 100644 index 8c2394983..000000000 --- a/zenoh-jni/src/querier.rs +++ /dev/null @@ -1,137 +0,0 @@ -// -// Copyright (c) 2023 ZettaScale Technology -// -// This program and the accompanying materials are made available under the -// terms of the Eclipse Public License 2.0 which is available at -// http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 -// which is available at https://www.apache.org/licenses/LICENSE-2.0. -// -// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 -// -// Contributors: -// ZettaScale Zenoh Team, -// - -use std::sync::Arc; - -use jni::{ - objects::{JByteArray, JClass, JObject, JString}, - sys::jint, - JNIEnv, -}; -use zenoh::{key_expr::KeyExpr, query::Querier, Wait}; - -use crate::{ - errors::ZResult, - key_expr::process_kotlin_key_expr, - session::{on_reply_error, on_reply_success}, - throw_exception, - utils::{ - decode_byte_array, decode_encoding, decode_string, get_callback_global_ref, get_java_vm, - load_on_close, - }, - zerror, -}; - -/// Perform a Zenoh GET through a querier. -/// -/// This function is meant to be called from Java/Kotlin code through JNI. -/// -/// Parameters: -/// - `env`: The JNI environment. -/// - `_class`: The JNI class. -/// - `querier_ptr`: The raw pointer to the querier. -/// - `key_expr_ptr`: A raw pointer to the [KeyExpr] provided to the kotlin querier. May be null in case of using an -/// undeclared key expression. -/// - `key_expr_str`: String representation of the key expression used during the querier declaration. -/// It won't be considered in case a key_expr_ptr to a declared key expression is provided. -/// - `selector_params`: Optional selector parameters for the query. -/// - `callback`: Reference to the Kotlin callback to be run upon receiving a reply. -/// - `on_close`: Reference to a kotlin callback to be run upon finishing the get operation, mostly used for closing a provided channel. -/// - `attachment`: Optional attachment. -/// - `payload`: Optional payload for the query. -/// - `encoding_id`: Encoding id of the payload provided. -/// - `encoding_schema`: Encoding schema of the payload provided. -/// -#[no_mangle] -#[allow(non_snake_case)] -pub unsafe extern "C" fn Java_io_zenoh_jni_JNIQuerier_getViaJNI( - mut env: JNIEnv, - _class: JClass, - querier_ptr: *const Querier, - key_expr_ptr: /*nullable*/ *const KeyExpr<'static>, - key_expr_str: JString, - selector_params: /*nullable*/ JString, - callback: JObject, - on_close: JObject, - attachment: /*nullable*/ JByteArray, - payload: /*nullable*/ JByteArray, - encoding_id: jint, - encoding_schema: /*nullable*/ JString, -) { - let querier = Arc::from_raw(querier_ptr); - let _ = || -> ZResult<()> { - let key_expr = process_kotlin_key_expr(&mut env, &key_expr_str, key_expr_ptr)?; - let java_vm = Arc::new(get_java_vm(&mut env)?); - let callback_global_ref = get_callback_global_ref(&mut env, callback)?; - let on_close_global_ref = get_callback_global_ref(&mut env, on_close)?; - let on_close = load_on_close(&java_vm, on_close_global_ref); - let mut get_builder = querier.get().callback(move |reply| { - || -> ZResult<()> { - on_close.noop(); // Does nothing, but moves `on_close` inside the closure so it gets destroyed with the closure - tracing::debug!("Receiving reply through JNI: {:?}", reply); - let mut env = java_vm.attach_current_thread_as_daemon().map_err(|err| { - zerror!("Unable to attach thread for GET query callback: {}.", err) - })?; - - match reply.result() { - Ok(sample) => { - on_reply_success(&mut env, reply.replier_id(), sample, &callback_global_ref) - } - Err(error) => { - on_reply_error(&mut env, reply.replier_id(), error, &callback_global_ref) - } - } - }() - .unwrap_or_else(|err| tracing::error!("Error on get callback: {err}")); - }); - - if !selector_params.is_null() { - let params = decode_string(&mut env, &selector_params)?; - get_builder = get_builder.parameters(params) - }; - - if !payload.is_null() { - let encoding = decode_encoding(&mut env, encoding_id, &encoding_schema)?; - get_builder = get_builder.encoding(encoding); - get_builder = get_builder.payload(decode_byte_array(&env, payload)?); - } - - if !attachment.is_null() { - let attachment = decode_byte_array(&env, attachment)?; - get_builder = get_builder.attachment::>(attachment); - } - - get_builder - .wait() - .map(|_| tracing::trace!("Performing get on '{key_expr}'.",)) - .map_err(|err| zerror!(err)) - }() - .map_err(|err| throw_exception!(env, err)); - std::mem::forget(querier); -} - -/// -/// Frees the pointer of the querier. -/// -/// After a call to this function, no further jni operations should be performed using the querier associated to the raw pointer provided. -/// -#[no_mangle] -#[allow(non_snake_case)] -pub(crate) unsafe extern "C" fn Java_io_zenoh_jni_JNIQuerier_freePtrViaJNI( - _env: JNIEnv, - _: JClass, - querier_ptr: *const Querier<'static>, -) { - Arc::from_raw(querier_ptr); -} diff --git a/zenoh-jni/src/query.rs b/zenoh-jni/src/query.rs deleted file mode 100644 index 95affb7e7..000000000 --- a/zenoh-jni/src/query.rs +++ /dev/null @@ -1,206 +0,0 @@ -// -// Copyright (c) 2023 ZettaScale Technology -// -// This program and the accompanying materials are made available under the -// terms of the Eclipse Public License 2.0 which is available at -// http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 -// which is available at https://www.apache.org/licenses/LICENSE-2.0. -// -// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 -// -// Contributors: -// ZettaScale Zenoh Team, -// - -use std::sync::Arc; - -use crate::utils::{decode_byte_array, decode_encoding}; -use crate::zerror; -use crate::{errors::ZResult, key_expr::process_kotlin_key_expr, throw_exception}; -use jni::{ - objects::{JByteArray, JClass, JString}, - sys::{jboolean, jint, jlong}, - JNIEnv, -}; -use uhlc::ID; -use zenoh::{ - key_expr::KeyExpr, - query::Query, - time::{Timestamp, NTP64}, - Wait, -}; - -/// Replies with `success` to a Zenoh [Query] via JNI, freeing the query in the process. -/// -/// # Parameters: -/// - `env`: The JNI environment. -/// - `_class`: The JNI class. -/// - `query_ptr`: The raw pointer to the Zenoh query. -/// - `key_expr_ptr`: Nullable key expression pointer associated with the query result. This parameter -/// is meant to be used with declared key expressions, which have a pointer associated to them. -/// In case of it being null, then the `key_expr_string` will be used to perform the reply. -/// - `key_expr_str`: The string representation of the key expression associated with the query result. -/// - `payload`: The payload for the reply. -/// - `encoding_id`: The encoding id of the payload. -/// - `encoding_schema`: Nullable encoding schema. -/// - `timestamp_enabled`: A boolean indicating whether the timestamp is enabled. -/// - `timestamp_ntp_64`: The NTP64 timestamp value. -/// - `attachment`: Nullable user attachment encoded as a byte array. -/// - `qos_express`: Whether the reply should be sent with the express flag. -/// -/// # Safety: -/// - This function is marked as unsafe due to raw pointer manipulation and JNI interaction. -/// - It assumes that the provided raw pointer to the Zenoh query is valid and has not been modified or freed. -/// - The query pointer is freed after calling this function (queries shouldn't be replied more than once), -/// therefore the query isn't valid anymore after that. -/// - May throw a JNI exception in case of failure, which should be handled by the caller. -/// -#[no_mangle] -#[allow(non_snake_case)] -pub(crate) unsafe extern "C" fn Java_io_zenoh_jni_JNIQuery_replySuccessViaJNI( - mut env: JNIEnv, - _class: JClass, - query_ptr: *const Query, - key_expr_ptr: /*nullable*/ *const KeyExpr<'static>, - key_expr_str: JString, - payload: JByteArray, - encoding_id: jint, - encoding_schema: /*nullable*/ JString, - timestamp_enabled: jboolean, - timestamp_ntp_64: jlong, - attachment: /*nullable*/ JByteArray, - qos_express: jboolean, -) { - let _ = || -> ZResult<()> { - let query = Arc::from_raw(query_ptr); - let key_expr = process_kotlin_key_expr(&mut env, &key_expr_str, key_expr_ptr)?; - let payload = decode_byte_array(&env, payload)?; - let mut reply_builder = query.reply(key_expr, payload); - let encoding = decode_encoding(&mut env, encoding_id, &encoding_schema)?; - reply_builder = reply_builder.encoding(encoding); - if timestamp_enabled != 0 { - let ts = Timestamp::new(NTP64(timestamp_ntp_64 as u64), ID::rand()); - reply_builder = reply_builder.timestamp(ts) - } - if !attachment.is_null() { - reply_builder = reply_builder.attachment(decode_byte_array(&env, attachment)?); - } - reply_builder = reply_builder.express(qos_express != 0); - reply_builder.wait().map_err(|err| zerror!(err)) - }() - .map_err(|err| throw_exception!(env, err)); -} - -/// Replies with `error` to a Zenoh [Query] via JNI, freeing the query in the process. -/// -/// # Parameters: -/// - `env`: The JNI environment. -/// - `_class`: The JNI class. -/// - `query_ptr`: The raw pointer to the Zenoh query. -/// - `payload`: The payload for the reply. -/// - `encoding_id`: The encoding id of the payload. -/// - `encoding_schema`: Nullable encoding schema. -/// -/// # Safety: -/// - This function is marked as unsafe due to raw pointer manipulation and JNI interaction. -/// - It assumes that the provided raw pointer to the Zenoh query is valid and has not been modified or freed. -/// - May throw a JNI exception in case of failure, which should be handled by the caller. -/// - The query pointer is freed after calling this function (queries shouldn't be replied more than once), -/// therefore the query isn't valid anymore after that. -/// -#[no_mangle] -#[allow(non_snake_case)] -pub(crate) unsafe extern "C" fn Java_io_zenoh_jni_JNIQuery_replyErrorViaJNI( - mut env: JNIEnv, - _class: JClass, - query_ptr: *const Query, - payload: JByteArray, - encoding_id: jint, - encoding_schema: /*nullable*/ JString, -) { - let _ = || -> ZResult<()> { - let query = Arc::from_raw(query_ptr); - let encoding = decode_encoding(&mut env, encoding_id, &encoding_schema)?; - query - .reply_err(decode_byte_array(&env, payload)?) - .encoding(encoding) - .wait() - .map_err(|err| zerror!(err)) - }() - .map_err(|err| throw_exception!(env, err)); -} - -/// Replies with `delete` to a Zenoh [Query] via JNI, freeing the query in the process. -/// -/// # Parameters: -/// - `env`: The JNI environment. -/// - `_class`: The JNI class. -/// - `query_ptr`: The raw pointer to the Zenoh query. -/// - `key_expr_ptr`: Nullable key expression pointer associated with the query result. This parameter -/// is meant to be used with declared key expressions, which have a pointer associated to them. -/// In case of it being null, then the `key_expr_string` will be used to perform the reply. -/// - `key_expr_str`: The string representation of the key expression associated with the query result. -/// - `timestamp_enabled`: A boolean indicating whether the timestamp is enabled. -/// - `timestamp_ntp_64`: The NTP64 timestamp value. -/// - `attachment`: Nullable user attachment encoded as a byte array. -/// - `qos_express`: Whether the reply should be sent with the express flag. -/// -/// # Safety: -/// - This function is marked as unsafe due to raw pointer manipulation and JNI interaction. -/// - It assumes that the provided raw pointer to the Zenoh query is valid and has not been modified or freed. -/// - May throw a JNI exception in case of failure, which should be handled by the caller. -/// - The query pointer is freed after calling this function (queries shouldn't be replied more than once), -/// therefore the query isn't valid anymore after that. -/// -#[no_mangle] -#[allow(non_snake_case)] -pub(crate) unsafe extern "C" fn Java_io_zenoh_jni_JNIQuery_replyDeleteViaJNI( - mut env: JNIEnv, - _class: JClass, - query_ptr: *const Query, - key_expr_ptr: /*nullable*/ *const KeyExpr<'static>, - key_expr_str: JString, - timestamp_enabled: jboolean, - timestamp_ntp_64: jlong, - attachment: /*nullable*/ JByteArray, - qos_express: jboolean, -) { - let _ = || -> ZResult<()> { - let query = Arc::from_raw(query_ptr); - let key_expr = process_kotlin_key_expr(&mut env, &key_expr_str, key_expr_ptr)?; - let mut reply_builder = query.reply_del(key_expr); - if timestamp_enabled != 0 { - let ts = Timestamp::new(NTP64(timestamp_ntp_64 as u64), ID::rand()); - reply_builder = reply_builder.timestamp(ts) - } - if !attachment.is_null() { - reply_builder = reply_builder.attachment(decode_byte_array(&env, attachment)?); - } - reply_builder = reply_builder.express(qos_express != 0); - reply_builder.wait().map_err(|err| zerror!(err)) - }() - .map_err(|err| throw_exception!(env, err)); -} - -/// Frees the Query via JNI. -/// -/// Parameters: -/// - `_env`: The JNI environment. -/// - `_class`: The JNI class. -/// - `ptr`: The raw pointer to the Zenoh query ([Query]). -/// -/// Safety: -/// - The function is marked as unsafe due to raw pointer manipulation. -/// - It assumes that the provided query pointer is valid and has not been modified or freed. -/// - The function takes ownership of the raw pointer and releases the associated memory. -/// - After calling this function, the query pointer becomes invalid and should not be used anymore. -/// -#[no_mangle] -#[allow(non_snake_case)] -pub(crate) unsafe extern "C" fn Java_io_zenoh_jni_JNIQuery_freePtrViaJNI( - _env: JNIEnv, - _: JClass, - query_ptr: *const Query, -) { - Arc::from_raw(query_ptr); -} diff --git a/zenoh-jni/src/queryable.rs b/zenoh-jni/src/queryable.rs deleted file mode 100644 index 5d2ddb1d2..000000000 --- a/zenoh-jni/src/queryable.rs +++ /dev/null @@ -1,41 +0,0 @@ -// -// Copyright (c) 2023 ZettaScale Technology -// -// This program and the accompanying materials are made available under the -// terms of the Eclipse Public License 2.0 which is available at -// http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 -// which is available at https://www.apache.org/licenses/LICENSE-2.0. -// -// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 -// -// Contributors: -// ZettaScale Zenoh Team, -// - -use std::sync::Arc; - -use jni::{objects::JClass, JNIEnv}; -use zenoh::query::Queryable; - -/// Frees the [Queryable]. -/// -/// # Parameters: -/// - `_env`: The JNI environment. -/// - `_class`: The JNI class. -/// - `queryable_ptr`: The raw pointer to the Zenoh queryable ([Queryable]). -/// -/// # Safety: -/// - The function is marked as unsafe due to raw pointer manipulation. -/// - It assumes that the provided queryable pointer is valid and has not been modified or freed. -/// - The function takes ownership of the raw pointer and releases the associated memory. -/// - After calling this function, the queryable pointer becomes invalid and should not be used anymore. -/// -#[no_mangle] -#[allow(non_snake_case)] -pub(crate) unsafe extern "C" fn Java_io_zenoh_jni_JNIQueryable_freePtrViaJNI( - _env: JNIEnv, - _: JClass, - queryable_ptr: *const Queryable<()>, -) { - Arc::from_raw(queryable_ptr); -} diff --git a/zenoh-jni/src/sample_callback.rs b/zenoh-jni/src/sample_callback.rs deleted file mode 100644 index 091814905..000000000 --- a/zenoh-jni/src/sample_callback.rs +++ /dev/null @@ -1,138 +0,0 @@ -// -// Copyright (c) 2023 ZettaScale Technology -// -// This program and the accompanying materials are made available under the -// terms of the Eclipse Public License 2.0 which is available at -// http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 -// which is available at https://www.apache.org/licenses/LICENSE-2.0. -// -// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 -// -// Contributors: -// ZettaScale Zenoh Team, -// - -use std::sync::Arc; - -use jni::{ - objects::{JByteArray, JObject, JString, JValue}, - sys::jint, - JNIEnv, -}; -use zenoh::{ - handlers::{Callback, DefaultHandler}, - liveliness::LivelinessSubscriberBuilder, - pubsub::SubscriberBuilder, - sample::Sample, -}; - -use crate::{errors::ZResult, utils::*, zerror}; - -pub(crate) trait SetJniSampleCallback: Sized + HasSampleCallbackSetter { - unsafe fn set_jni_sample_callback( - self, - env: &mut JNIEnv, - callback: JObject, - on_close: JObject, - ) -> ZResult { - let java_vm = Arc::new(get_java_vm(env)?); - let callback_global_ref = get_callback_global_ref(env, callback)?; - let on_close_global_ref = get_callback_global_ref(env, on_close)?; - let on_close = load_on_close(&java_vm, on_close_global_ref); - - let builder = self.set_callback(move |sample: Sample| { - on_close.noop(); // Moves `on_close` inside the closure so it gets destroyed with the closure - let _ = || -> ZResult<()> { - let mut env = java_vm.attach_current_thread_as_daemon().map_err(|err| { - zerror!("Unable to attach thread for sample callback: {}", err) - })?; - let byte_array = bytes_to_java_array(&env, sample.payload()) - .map(|array| env.auto_local(array))?; - - let encoding_id: jint = sample.encoding().id() as jint; - let encoding_schema = match sample.encoding().schema() { - Some(schema) => slice_to_java_string(&env, schema)?, - None => JString::default(), - }; - let kind = sample.kind() as jint; - let (timestamp, is_valid) = sample - .timestamp() - .map(|timestamp| (timestamp.get_time().as_u64(), true)) - .unwrap_or((0, false)); - - let attachment_bytes = sample - .attachment() - .map_or_else( - || Ok(JByteArray::default()), - |attachment| bytes_to_java_array(&env, attachment), - ) - .map(|array| env.auto_local(array)) - .map_err(|err| zerror!("Error processing attachment: {}", err))?; - - let key_expr_str = env.auto_local( - env.new_string(sample.key_expr().to_string()) - .map_err(|err| zerror!("Error processing sample key expr: {}", err))?, - ); - - let express = sample.express(); - let priority = sample.priority() as jint; - let cc = sample.congestion_control() as jint; - - env.call_method( - &callback_global_ref, - "run", - "(Ljava/lang/String;[BILjava/lang/String;IJZ[BZII)V", - &[ - JValue::from(&key_expr_str), - JValue::from(&byte_array), - JValue::from(encoding_id), - JValue::from(&encoding_schema), - JValue::from(kind), - JValue::from(timestamp as i64), - JValue::from(is_valid), - JValue::from(&attachment_bytes), - JValue::from(express), - JValue::from(priority), - JValue::from(cc), - ], - ) - .map_err(|err| zerror!(err))?; - Ok(()) - }() - .map_err(|err| tracing::error!("On sample callback error: {err}")); - }); - Ok(builder) - } -} - -impl SetJniSampleCallback for T {} - -pub(crate) trait HasSampleCallbackSetter { - type BuilderWithCallback; - - fn set_callback(self, callback: F) -> Self::BuilderWithCallback - where - F: Fn(Sample) + Send + Sync + 'static; -} - -impl<'a, 'b> HasSampleCallbackSetter for SubscriberBuilder<'a, 'b, DefaultHandler> { - type BuilderWithCallback = SubscriberBuilder<'a, 'b, Callback>; - - fn set_callback(self, callback: F) -> Self::BuilderWithCallback - where - F: Fn(Sample) + Send + Sync + 'static, - { - self.callback(callback) - } -} - -impl<'a, 'b> HasSampleCallbackSetter for LivelinessSubscriberBuilder<'a, 'b, DefaultHandler> { - type BuilderWithCallback = LivelinessSubscriberBuilder<'a, 'b, Callback>; - - fn set_callback(self, callback: F) -> Self::BuilderWithCallback - where - F: Fn(Sample) + Send + Sync + 'static, - { - self.callback(callback) - } -} diff --git a/zenoh-jni/src/scouting.rs b/zenoh-jni/src/scouting.rs deleted file mode 100644 index b0a665c10..000000000 --- a/zenoh-jni/src/scouting.rs +++ /dev/null @@ -1,113 +0,0 @@ -// -// Copyright (c) 2023 ZettaScale Technology -// -// This program and the accompanying materials are made available under the -// terms of the Eclipse Public License 2.0 which is available at -// http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 -// which is available at https://www.apache.org/licenses/LICENSE-2.0. -// -// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 -// -// Contributors: -// ZettaScale Zenoh Team, -// - -use std::{ptr::null, sync::Arc}; - -use jni::{ - objects::{GlobalRef, JClass, JList, JObject, JValue}, - sys::jint, - JNIEnv, -}; -use zenoh::{config::WhatAmIMatcher, Wait}; -use zenoh::{scouting::Scout, Config}; - -use crate::utils::{get_callback_global_ref, get_java_vm, load_on_close}; -use crate::{errors::ZResult, throw_exception, zerror}; - -/// Start a scout. -/// -/// # Params -/// - `whatAmI`: Ordinal value of the WhatAmI enum. -/// - `callback`: Callback to be executed whenever a hello message is received. -/// - `config_ptr`: Optional config pointer. -/// -/// Returns a pointer to the scout, which must be freed afterwards. -/// If starting the scout fails, an exception is thrown on the JVM, and a null pointer is returned. -/// -#[no_mangle] -#[allow(non_snake_case)] -pub unsafe extern "C" fn Java_io_zenoh_jni_JNIScout_00024Companion_scoutViaJNI( - mut env: JNIEnv, - _class: JClass, - whatAmI: jint, - callback: JObject, - on_close: JObject, - config_ptr: /*nullable=*/ *const Config, -) -> *const Scout<()> { - || -> ZResult<*const Scout<()>> { - let callback_global_ref = get_callback_global_ref(&mut env, callback)?; - let java_vm = Arc::new(get_java_vm(&mut env)?); - let on_close_global_ref: GlobalRef = get_callback_global_ref(&mut env, on_close)?; - let on_close = load_on_close(&java_vm, on_close_global_ref); - let whatAmIMatcher: WhatAmIMatcher = (whatAmI as u8).try_into().unwrap(); // The validity of the operation is guaranteed on the kotlin layer. - let config = if config_ptr.is_null() { - Config::default() - } else { - let arc_cfg = Arc::from_raw(config_ptr); - let config_clone = arc_cfg.as_ref().clone(); - std::mem::forget(arc_cfg); - config_clone - }; - zenoh::scout(whatAmIMatcher, config) - .callback(move |hello| { - on_close.noop(); // Moves `on_close` inside the closure so it gets destroyed with the closure - tracing::debug!("Received hello: {hello}"); - let _ = || -> jni::errors::Result<()> { - let mut env = java_vm.attach_current_thread_as_daemon()?; - let whatami = hello.whatami() as jint; - let zenoh_id = env - .byte_array_from_slice(&hello.zid().to_le_bytes()) - .map(|it| env.auto_local(it))?; - let locators = env - .new_object("java/util/ArrayList", "()V", &[]) - .map(|it| env.auto_local(it))?; - let jlist = JList::from_env(&mut env, &locators)?; - for value in hello.locators() { - let locator = env.new_string(value.as_str())?; - jlist.add(&mut env, &locator)?; - } - env.call_method( - &callback_global_ref, - "run", - "(I[BLjava/util/List;)V", - &[ - JValue::from(whatami), - JValue::from(&zenoh_id), - JValue::from(&locators), - ], - )?; - Ok(()) - }() - .map_err(|err| tracing::error!("Error while scouting: ${err}")); - }) - .wait() - .map(|scout| Arc::into_raw(Arc::new(scout))) - .map_err(|err| zerror!(err)) - }() - .unwrap_or_else(|err| { - throw_exception!(env, err); - null() - }) -} - -/// Frees the scout. -#[no_mangle] -#[allow(non_snake_case)] -pub(crate) unsafe extern "C" fn Java_io_zenoh_jni_JNIScout_00024Companion_freePtrViaJNI( - _env: JNIEnv, - _: JClass, - scout_ptr: *const Scout<()>, -) { - Arc::from_raw(scout_ptr); -} diff --git a/zenoh-jni/src/session.rs b/zenoh-jni/src/session.rs deleted file mode 100644 index 4c9702f97..000000000 --- a/zenoh-jni/src/session.rs +++ /dev/null @@ -1,1447 +0,0 @@ -// -// Copyright (c) 2023 ZettaScale Technology -// -// This program and the accompanying materials are made available under the -// terms of the Eclipse Public License 2.0 which is available at -// http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 -// which is available at https://www.apache.org/licenses/LICENSE-2.0. -// -// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 -// -// Contributors: -// ZettaScale Zenoh Team, -// - -use std::{mem, ops::Deref, ptr::null, sync::Arc, time::Duration}; - -use jni::{ - objects::{GlobalRef, JByteArray, JClass, JList, JObject, JString, JValue}, - sys::{jboolean, jbyteArray, jint, jlong, jobject}, - JNIEnv, -}; -use zenoh::{ - config::Config, - handlers::Callback, - key_expr::KeyExpr, - pubsub::{Publisher, PublisherBuilder, Subscriber, SubscriberBuilder}, - query::{Querier, Query, Queryable, ReplyError, Selector}, - sample::Sample, - session::{EntityGlobalId, Session, ZenohId}, - Wait, -}; - -use crate::owned_object::OwnedObject; -#[cfg(feature = "zenoh-ext")] -use jni::sys::jdouble; -#[cfg(feature = "zenoh-ext")] -use zenoh_ext::{ - AdvancedPublisher, AdvancedPublisherBuilderExt, AdvancedSubscriber, - AdvancedSubscriberBuilderExt, CacheConfig, HistoryConfig, MissDetectionConfig, RecoveryConfig, - RepliesConfig, -}; - -use crate::{ - errors::ZResult, key_expr::process_kotlin_key_expr, sample_callback::SetJniSampleCallback, - throw_exception, utils::*, zerror, -}; - -/// Open a Zenoh session via JNI. -/// -/// It returns an [Arc] raw pointer to the Zenoh Session, which should be stored as a private read-only attribute -/// of the session object in the Java/Kotlin code. Subsequent calls to other session functions will require -/// this raw pointer to retrieve the [Session] using `Arc::from_raw`. -/// -/// If opening the session fails, an exception is thrown on the JVM, and a null pointer is returned. -/// -/// # Parameters: -/// - `env`: The JNI environment. -/// - `_class`: The JNI class (parameter required by the JNI interface but unused). -/// - `config_path`: Nullable path to the Zenoh config file. If null, the default configuration will be loaded. -/// -#[no_mangle] -#[allow(non_snake_case)] -pub unsafe extern "C" fn Java_io_zenoh_jni_JNISession_openSessionViaJNI( - mut env: JNIEnv, - _class: JClass, - config_ptr: *const Config, -) -> *const Session { - let session = open_session(config_ptr); - match session { - Ok(session) => Arc::into_raw(Arc::new(session)), - Err(err) => { - tracing::error!("Unable to open session: {}", err); - throw_exception!(env, zerror!(err)); - null() - } - } -} - -/// Open a Zenoh session with the configuration pointed out by `config_path`. -/// -/// If the config path provided is null then the default configuration is loaded. -/// -unsafe fn open_session(config_ptr: *const Config) -> ZResult { - let config = Arc::from_raw(config_ptr); - let result = zenoh::open(config.as_ref().clone()) - .wait() - .map_err(|err| zerror!(err)); - mem::forget(config); - result -} - -/// Open a Zenoh session with a JSON configuration. -/// -/// It returns an [Arc] raw pointer to the Zenoh Session, which should be stored as a private read-only attribute -/// of the session object in the Java/Kotlin code. Subsequent calls to other session functions will require -/// this raw pointer to retrieve the [Session] using `Arc::from_raw`. -/// -/// If opening the session fails, an exception is thrown on the JVM, and a null pointer is returned. -/// -/// # Parameters: -/// - `env`: The JNI environment. -/// - `_class`: The JNI class (parameter required by the JNI interface but unused). -/// - `json_config`: Configuration as a JSON string. -/// -#[no_mangle] -#[allow(non_snake_case)] -pub extern "C" fn Java_io_zenoh_jni_JNISession_openSessionWithJsonConfigViaJNI( - mut env: JNIEnv, - _class: JClass, - json_config: JString, -) -> *const Session { - let session = open_session_with_json_config(&mut env, json_config); - match session { - Ok(session) => Arc::into_raw(Arc::new(session)), - Err(err) => { - tracing::error!("Unable to open session: {}", err); - throw_exception!(env, zerror!(err)); - null() - } - } -} - -/// Open a Zenoh session with the provided json configuration. -/// -fn open_session_with_json_config(env: &mut JNIEnv, json_config: JString) -> ZResult { - let json_config = decode_string(env, &json_config)?; - let mut deserializer = - json5::Deserializer::from_str(&json_config).map_err(|err| zerror!(err))?; - let config = Config::from_deserializer(&mut deserializer).map_err(|err| match err { - Ok(c) => zerror!("Invalid configuration: {}", c), - Err(e) => zerror!("JSON error: {}", e), - })?; - zenoh::open(config).wait().map_err(|err| zerror!(err)) -} - -/// Open a Zenoh session with a YAML configuration. -/// -/// It returns an [Arc] raw pointer to the Zenoh Session, which should be stored as a private read-only attribute -/// of the session object in the Java/Kotlin code. Subsequent calls to other session functions will require -/// this raw pointer to retrieve the [Session] using `Arc::from_raw`. -/// -/// If opening the session fails, an exception is thrown on the JVM, and a null pointer is returned. -/// -/// # Parameters: -/// - `env`: The JNI environment. -/// - `_class`: The JNI class (parameter required by the JNI interface but unused). -/// - `yaml_config`: Configuration as a YAML string. -/// -#[no_mangle] -#[allow(non_snake_case)] -pub extern "C" fn Java_io_zenoh_jni_JNISession_openSessionWithYamlConfigViaJNI( - mut env: JNIEnv, - _class: JClass, - yaml_config: JString, -) -> *const Session { - let session = open_session_with_yaml_config(&mut env, yaml_config); - match session { - Ok(session) => Arc::into_raw(Arc::new(session)), - Err(err) => { - tracing::error!("Unable to open session: {}", err); - throw_exception!(env, zerror!(err)); - null() - } - } -} - -/// Open a Zenoh session with the provided yaml configuration. -/// -fn open_session_with_yaml_config(env: &mut JNIEnv, yaml_config: JString) -> ZResult { - let yaml_config = decode_string(env, &yaml_config)?; - let deserializer = serde_yaml::Deserializer::from_str(&yaml_config); - let config = Config::from_deserializer(deserializer).map_err(|err| match err { - Ok(c) => zerror!("Invalid configuration: {}", c), - Err(e) => zerror!("YAML error: {}", e), - })?; - zenoh::open(config).wait().map_err(|err| zerror!(err)) -} - -/// Closes a Zenoh session via JNI. -/// -/// # Parameters: -/// - `env`: The JNI environment. -/// - `_class`: The JNI class. -/// - `session_ptr`: The raw pointer to the Zenoh session. -/// -/// # Safety: -/// - The function is marked as unsafe due to raw pointer manipulation and JNI interaction. -/// - It assumes that the provided session pointer is valid and has not been modified or freed. -/// - The function may throw a JNI exception in case of failure, which should be handled by the caller. -/// - After the session is closed, the provided pointer is no more valid. -/// -#[no_mangle] -#[allow(non_snake_case, unused)] -pub unsafe extern "C" fn Java_io_zenoh_jni_JNISession_closeSessionViaJNI( - mut env: JNIEnv, - _class: JClass, - session_ptr: *const Session, -) { - Arc::from_raw(session_ptr); -} - -/// Declare an advanced Zenoh subscriber via JNI. -/// -/// Parameters: -/// - `env`: The JNI environment. -/// - `_class`: The JNI class. -/// - `key_expr_ptr`: The key expression pointer for the subscriber. May be null in case of using an -/// undeclared key expression. -/// - `key_expr_str`: String representation of the key expression to be used to declare the subscriber. -/// It won't be considered in case a key_expr_ptr to a declared key expression is provided. -/// - `history_detect_late_publishers` : Enable detection of late joiner publishers and query for their historical data. -/// Late joiner detection can only be achieved for [`AdvancedPublisher`] that enable publisher detection. -/// History can only be retransmitted by [`AdvancedPublisher`] that enable cache. -/// - `history_max_samples` : Specify how many samples to query for each resource. 0 means no limit. -/// - `history_max_age_seconds` : Specify the maximum age of samples to query. <= 0.0 means no limit. -/// - `recovery_config_enabled` : Enable missed samples recovery. -/// - recovery_config_is_heartbeat: If true, use heartbeat mode and subscribe to heartbeats of [`AdvancedPublisher`], -/// if false - enable periodic queries for not yet received Samples. -/// - `recovery_query_period_ms` : Specify period for Periodic queries mode. -/// - `subscriber_detection` : Allow this subscriber to be detected through liveliness. -/// - `session_ptr`: The raw pointer to the Zenoh session. -/// - `callback`: The callback function as an instance of the `JNISubscriberCallback` interface in Java/Kotlin. -/// - `on_close`: A Java/Kotlin `JNIOnCloseCallback` function interface to be called upon closing the subscriber. -/// -/// Returns: -/// - A raw pointer to the declared [AdvancedSubscriber]. In case of failure, an exception is thrown and null is returned. -/// -/// Safety: -/// - The function is marked as unsafe due to raw pointer manipulation and JNI interaction. -/// - It assumes that the provided [Session] pointer is valid and has not been modified or freed. -/// - The [Session] pointer remains valid and the ownership of the [Session] is not transferred, -/// allowing safe usage of the [Session] after this function call. -/// - The callback function passed as `callback` must be a valid instance of the `JNISubscriberCallback` interface -/// in Java/Kotlin, matching the specified signature. -/// - The function may throw a JNI exception in case of failure, which should be handled by the caller. -/// -#[cfg(feature = "zenoh-ext")] -#[no_mangle] -#[allow(non_snake_case)] -pub unsafe extern "C" fn Java_io_zenoh_jni_JNISession_declareAdvancedSubscriberViaJNI( - mut env: JNIEnv, - _class: JClass, - key_expr_ptr: /*nullable*/ *const KeyExpr<'static>, - key_expr_str: JString, - session_ptr: *const Session, - // HistoryConfig - history_config_enabled: jboolean, - history_detect_late_publishers: jboolean, - history_max_samples: jlong, - history_max_age_seconds: jdouble, - // RecoveryConfig - recovery_config_enabled: jboolean, - recovery_config_is_heartbeat: jboolean, - recovery_query_period_ms: jlong, - - subscriber_detection: jboolean, - - callback: JObject, - on_close: JObject, -) -> *const AdvancedSubscriber<()> { - let session = Arc::from_raw(session_ptr); - let subscriber_ptr = || -> ZResult<*const AdvancedSubscriber<()>> { - let mut builder = prepare_subscriber_builder( - &mut env, - key_expr_ptr, - &key_expr_str, - &session, - callback, - on_close, - "advanced subscriber", - )? - .advanced(); - - if history_config_enabled != 0 { - let mut history = match history_detect_late_publishers != 0 { - true => HistoryConfig::default().detect_late_publishers(), - false => HistoryConfig::default(), - }; - - if history_max_samples > 0 { - history = history.max_samples( - history_max_samples - .try_into() - .map_err(|e: std::num::TryFromIntError| zerror!(e.to_string()))?, - ); - } - - if history_max_age_seconds > 0.0 { - history = history.max_age(history_max_age_seconds); - } - - builder = builder.history(history); - } - - if recovery_config_enabled != 0 { - let recovery = if recovery_config_is_heartbeat != 0 { - RecoveryConfig::default().heartbeat() - } else { - let dur = Duration::from_millis( - recovery_query_period_ms - .try_into() - .map_err(|e: std::num::TryFromIntError| zerror!(e.to_string()))?, - ); - RecoveryConfig::default().periodic_queries(dur) - }; - builder = builder.recovery(recovery); - } - - if subscriber_detection != 0 { - builder = builder.subscriber_detection(); - } - - builder - .wait() - .map(|s| Arc::into_raw(Arc::new(s))) - .map_err(|err| zerror!("Unable to declare advanced subscriber: {}", err)) - }() - .unwrap_or_else(|err| { - throw_exception!(env, err); - null() - }); - std::mem::forget(session); - subscriber_ptr -} - -/// Declare an advanced Zenoh publisher via JNI. -/// -/// # Parameters: -/// - `env`: The JNI environment. -/// - `_class`: The JNI class. -/// - `key_expr_ptr`: Raw pointer to the [KeyExpr] to be used for the publisher, may be null. -/// - `key_expr_str`: String representation of the [KeyExpr] to be used for the publisher. -/// It is only considered when the key_expr_ptr parameter is null, meaning the function is -/// receiving a key expression that was not declared. -/// - `session_ptr`: Raw pointer to the Zenoh [Session] to be used for the publisher. -/// - `congestion_control`: The [zenoh::publisher::CongestionControl] configuration as an ordinal. -/// - `priority`: The [zenoh::core::Priority] configuration as an ordinal. -/// - `is_express`: The express config of the publisher (see [zenoh::prelude::QoSBuilderTrait]). -/// - `reliability`: The reliability value as an ordinal. -/// -/// - `cache_max_samples` : If > 0 - Attach a cache to this [`AdvancedPublisher`] and specify how many samples to keep for each resource. -/// - `cache_replies_priority` : The [zenoh::core::Priority] configuration as an ordinal used for cache replies. -/// - `cache_replies_congestion_control` : The [zenoh::publisher::CongestionControl] configuration as an ordinal used for cache replies. -/// - `cache_replies_is_express: jboolean` : The express config of the publisher (see [zenoh::prelude::QoSBuilderTrait]) used for cache replies. -/// -/// - `sample_miss_detection_enabled` : Enables sample miss detection functionality: allow matching -/// [`AdvancedSubscriber`] to detect lost samples and optionally ask for retransimission. Retransmission can only be achieved if cache is enabled. -/// - `sample_miss_detection_is_not_heartbeat` : Use sample miss detection without heartbeat -/// - `sample_miss_detection_heartbeat_ms` : Specify heartbeat period for heartbeat mode. -/// - `sample_miss_detection_heartbeat_is_sporadic` : determine if heartbeat argument is treated as sporadic -/// -/// # Returns: -/// - A raw pointer to the declared [AdvancedPublisher] or null in case of failure. -/// -/// # Safety: -/// - The function is marked as unsafe due to raw pointer manipulation and JNI interaction. -/// - It assumes that the provided [Session] pointer is valid and has not been modified or freed. -/// - The ownership of the [Session] is not transferred, and the [Session] pointer remains valid -/// after this function call so it is safe to use it after this call. -/// - The function may throw an exception in case of failure, which should be handled by the caller. -/// -#[cfg(feature = "zenoh-ext")] -#[no_mangle] -#[allow(non_snake_case)] -pub unsafe extern "C" fn Java_io_zenoh_jni_JNISession_declareAdvancedPublisherViaJNI( - mut env: JNIEnv, - _class: JClass, - key_expr_ptr: /*nullable*/ *const KeyExpr<'static>, - key_expr_str: JString, - session_ptr: *const Session, - congestion_control: jint, - priority: jint, - is_express: jboolean, - reliability: jint, - // CacheConfig - cache_enabled: jboolean, - cache_max_samples: jlong, - cache_replies_priority: jint, - cache_replies_congestion_control: jint, - cache_replies_is_express: jboolean, - // MissDetectionConfig - sample_miss_detection_enabled: jboolean, - sample_miss_detection_enable_heartbeat: jboolean, - sample_miss_detection_heartbeat_ms: jlong, - sample_miss_detection_heartbeat_is_sporadic: jboolean, - - publisher_detection: jboolean, -) -> *const AdvancedPublisher<'static> { - let session = OwnedObject::from_raw(session_ptr); - let publisher_ptr = || -> ZResult<*const AdvancedPublisher<'static>> { - let mut builder = prepare_publisher_builder( - &mut env, - key_expr_ptr, - &key_expr_str, - &session, - congestion_control, - priority, - is_express, - reliability, - )? - .advanced(); - - // fill CacheConfig - if cache_enabled != 0 { - let cache_congestion_control = - decode_congestion_control(cache_replies_congestion_control)?; - - let cache_priority = decode_priority(cache_replies_priority)?; - - let replies_config = RepliesConfig::default() - .priority(cache_priority) - .congestion_control(cache_congestion_control) - .express(cache_replies_is_express != 0); - - let cache_config = CacheConfig::default() - .max_samples( - cache_max_samples - .try_into() - .map_err(|e: std::num::TryFromIntError| zerror!(e.to_string()))?, - ) - .replies_config(replies_config); - - builder = builder.cache(cache_config); - } - - // fill MissDetectionConfig - if sample_miss_detection_enabled != 0 { - let miss_detection_config = { - let mut result = MissDetectionConfig::default(); - if sample_miss_detection_enable_heartbeat != 0 { - let duration = Duration::from_millis( - sample_miss_detection_heartbeat_ms - .try_into() - .map_err(|e: std::num::TryFromIntError| zerror!(e.to_string()))?, - ); - - result = match sample_miss_detection_heartbeat_is_sporadic != 0 { - true => result.sporadic_heartbeat(duration), - false => result.heartbeat(duration), - }; - } - result - }; - builder = builder.sample_miss_detection(miss_detection_config); - } - - if publisher_detection != 0 { - builder = builder.publisher_detection(); - } - - let result = builder.wait(); - match result { - Ok(publisher) => Ok(Arc::into_raw(Arc::new(publisher))), - Err(err) => Err(zerror!(err)), - } - }() - .unwrap_or_else(|err| { - throw_exception!(env, err); - null() - }); - publisher_ptr -} - -/// Declare a Zenoh publisher via JNI. -/// -/// # Parameters: -/// - `env`: The JNI environment. -/// - `_class`: The JNI class. -/// - `key_expr_ptr`: Raw pointer to the [KeyExpr] to be used for the publisher, may be null. -/// - `key_expr_str`: String representation of the [KeyExpr] to be used for the publisher. -/// It is only considered when the key_expr_ptr parameter is null, meaning the function is -/// receiving a key expression that was not declared. -/// - `session_ptr`: Raw pointer to the Zenoh [Session] to be used for the publisher. -/// - `congestion_control`: The [zenoh::publisher::CongestionControl] configuration as an ordinal. -/// - `priority`: The [zenoh::core::Priority] configuration as an ordinal. -/// - `is_express`: The express config of the publisher (see [zenoh::prelude::QoSBuilderTrait]). -/// - `reliability`: The reliability value as an ordinal. -/// -/// # Returns: -/// - A raw pointer to the declared Zenoh publisher or null in case of failure. -/// -/// # Safety: -/// - The function is marked as unsafe due to raw pointer manipulation and JNI interaction. -/// - It assumes that the provided session pointer is valid and has not been modified or freed. -/// - The ownership of the session is not transferred, and the session pointer remains valid -/// after this function call so it is safe to use it after this call. -/// - The function may throw an exception in case of failure, which should be handled by the caller. -/// -#[no_mangle] -#[allow(non_snake_case)] -pub unsafe extern "C" fn Java_io_zenoh_jni_JNISession_declarePublisherViaJNI( - mut env: JNIEnv, - _class: JClass, - key_expr_ptr: /*nullable*/ *const KeyExpr<'static>, - key_expr_str: JString, - session_ptr: *const Session, - congestion_control: jint, - priority: jint, - is_express: jboolean, - reliability: jint, -) -> *const Publisher<'static> { - let session = Arc::from_raw(session_ptr); - let publisher_ptr = || -> ZResult<*const Publisher<'static>> { - prepare_publisher_builder( - &mut env, - key_expr_ptr, - &key_expr_str, - &session, - congestion_control, - priority, - is_express, - reliability, - )? - .wait() - .map(|publisher| Arc::into_raw(Arc::new(publisher))) - .map_err(|e| zerror!(e)) - }() - .unwrap_or_else(|err| { - throw_exception!(env, err); - null() - }); - std::mem::forget(session); - publisher_ptr -} - -#[allow(clippy::too_many_arguments)] -unsafe fn prepare_publisher_builder<'a, 'b>( - env: &mut JNIEnv, - key_expr_ptr: /*nullable*/ *const KeyExpr<'static>, - key_expr_str: &JString, - session: &'a Session, - congestion_control: jint, - priority: jint, - is_express: jboolean, - reliability: jint, -) -> ZResult> { - let key_expr = process_kotlin_key_expr(env, key_expr_str, key_expr_ptr)?; - let congestion_control = decode_congestion_control(congestion_control)?; - let priority = decode_priority(priority)?; - let reliability = decode_reliability(reliability)?; - let builder = session - .declare_publisher(key_expr) - .congestion_control(congestion_control) - .priority(priority) - .express(is_express != 0) - .reliability(reliability); - Ok(builder) -} - -/// Performs a `put` operation in the Zenoh session via JNI. -/// -/// Parameters: -/// - `env`: The JNI environment. -/// - `_class`: The JNI class. -/// - `key_expr_ptr`: Raw pointer to the [KeyExpr] to be used for the operation, may be null. -/// - `key_expr_str`: String representation of the [KeyExpr] to be used for the operation. -/// It is only considered when the key_expr_ptr parameter is null, meaning the function is -/// receiving a key expression that was not declared. -/// - `session_ptr`: Raw pointer to the [Session] to be used for the operation. -/// - `payload`: The payload to send through the network. -/// - `encoding_id`: The encoding id of the payload. -/// - `encoding_schema`: Optional encoding schema, may be null. -/// - `congestion_control`: The [CongestionControl] mechanism specified. -/// - `priority`: The [Priority] mechanism specified. -/// - `is_express`: The express flag. -/// - `attachment`: Optional attachment encoded into a byte array. May be null. -/// - `reliability`: The reliability value as an ordinal. -/// -/// Safety: -/// - The function is marked as unsafe due to raw pointer manipulation and JNI interaction. -/// - It assumes that the provided session pointer is valid and has not been modified or freed. -/// - The session pointer remains valid and the ownership of the session is not transferred, -/// allowing safe usage of the session after this function call. -/// - The function may throw an exception in case of failure, which should be handled by the Java/Kotlin caller. -/// -#[no_mangle] -#[allow(non_snake_case)] -pub unsafe extern "C" fn Java_io_zenoh_jni_JNISession_putViaJNI( - mut env: JNIEnv, - _class: JClass, - key_expr_ptr: /*nullable*/ *const KeyExpr<'static>, - key_expr_str: JString, - session_ptr: *const Session, - payload: JByteArray, - encoding_id: jint, - encoding_schema: JString, - congestion_control: jint, - priority: jint, - is_express: jboolean, - attachment: JByteArray, - reliability: jint, -) { - let session = Arc::from_raw(session_ptr); - let _ = || -> ZResult<()> { - let key_expr = process_kotlin_key_expr(&mut env, &key_expr_str, key_expr_ptr)?; - let payload = decode_byte_array(&env, payload)?; - let encoding = decode_encoding(&mut env, encoding_id, &encoding_schema)?; - let congestion_control = decode_congestion_control(congestion_control)?; - let priority = decode_priority(priority)?; - let reliability = decode_reliability(reliability)?; - - let mut put_builder = session - .put(&key_expr, payload) - .congestion_control(congestion_control) - .encoding(encoding) - .express(is_express != 0) - .priority(priority) - .reliability(reliability); - - if !attachment.is_null() { - let attachment = decode_byte_array(&env, attachment)?; - put_builder = put_builder.attachment(attachment) - } - - put_builder - .wait() - .map(|_| tracing::trace!("Put on '{key_expr}'")) - .map_err(|err| zerror!(err)) - }() - .map_err(|err| throw_exception!(env, err)); - std::mem::forget(session); -} - -/// Performs a `delete` operation in the Zenoh session via JNI. -/// -/// Parameters: -/// - `env`: The JNI environment. -/// - `_class`: The JNI class. -/// - `key_expr_ptr`: Raw pointer to the [KeyExpr] to be used for the operation, may be null. -/// - `key_expr_str`: String representation of the [KeyExpr] to be used for the operation. -/// It is only considered when the key_expr_ptr parameter is null, meaning the function is -/// receiving a key expression that was not declared. -/// - `session_ptr`: Raw pointer to the [Session] to be used for the operation. -/// - `congestion_control`: The [CongestionControl] mechanism specified. -/// - `priority`: The [Priority] mechanism specified. -/// - `is_express`: The express flag. -/// - `attachment`: Optional attachment encoded into a byte array. May be null. -/// - `reliability`: The reliability value as an ordinal. -/// -/// Safety: -/// - The function is marked as unsafe due to raw pointer manipulation and JNI interaction. -/// - It assumes that the provided session pointer is valid and has not been modified or freed. -/// - The session pointer remains valid and the ownership of the session is not transferred, -/// allowing safe usage of the session after this function call. -/// - The function may throw a JNI exception or a Session exception in case of failure, which -/// should be handled by the Java/Kotlin caller. -/// -#[no_mangle] -#[allow(non_snake_case)] -pub unsafe extern "C" fn Java_io_zenoh_jni_JNISession_deleteViaJNI( - mut env: JNIEnv, - _class: JClass, - key_expr_ptr: /*nullable*/ *const KeyExpr<'static>, - key_expr_str: JString, - session_ptr: *const Session, - congestion_control: jint, - priority: jint, - is_express: jboolean, - attachment: JByteArray, - reliability: jint, -) { - let session = Arc::from_raw(session_ptr); - let _ = || -> ZResult<()> { - let key_expr = process_kotlin_key_expr(&mut env, &key_expr_str, key_expr_ptr)?; - let congestion_control = decode_congestion_control(congestion_control)?; - let priority = decode_priority(priority)?; - let reliability = decode_reliability(reliability)?; - - let mut delete_builder = session - .delete(&key_expr) - .congestion_control(congestion_control) - .express(is_express != 0) - .priority(priority) - .reliability(reliability); - - if !attachment.is_null() { - let attachment = decode_byte_array(&env, attachment)?; - delete_builder = delete_builder.attachment(attachment) - } - - delete_builder - .wait() - .map(|_| tracing::trace!("Delete on '{key_expr}'")) - .map_err(|err| zerror!(err)) - }() - .map_err(|err| throw_exception!(env, err)); - std::mem::forget(session); -} - -/// Declare a Zenoh subscriber via JNI. -/// -/// Parameters: -/// - `env`: The JNI environment. -/// - `_class`: The JNI class. -/// - `key_expr_ptr`: The key expression pointer for the subscriber. May be null in case of using an -/// undeclared key expression. -/// - `key_expr_str`: String representation of the key expression to be used to declare the subscriber. -/// It won't be considered in case a key_expr_ptr to a declared key expression is provided. -/// - `session_ptr`: The raw pointer to the Zenoh session. -/// - `callback`: The callback function as an instance of the `JNISubscriberCallback` interface in Java/Kotlin. -/// - `on_close`: A Java/Kotlin `JNIOnCloseCallback` function interface to be called upon closing the subscriber. -/// -/// Returns: -/// - A raw pointer to the declared Zenoh subscriber. In case of failure, an exception is thrown and null is returned. -/// -/// Safety: -/// - The function is marked as unsafe due to raw pointer manipulation and JNI interaction. -/// - It assumes that the provided session pointer is valid and has not been modified or freed. -/// - The session pointer remains valid and the ownership of the session is not transferred, -/// allowing safe usage of the session after this function call. -/// - The callback function passed as `callback` must be a valid instance of the `JNISubscriberCallback` interface -/// in Java/Kotlin, matching the specified signature. -/// - The function may throw a JNI exception in case of failure, which should be handled by the caller. -/// -#[no_mangle] -#[allow(non_snake_case)] -pub unsafe extern "C" fn Java_io_zenoh_jni_JNISession_declareSubscriberViaJNI( - mut env: JNIEnv, - _class: JClass, - key_expr_ptr: /*nullable*/ *const KeyExpr<'static>, - key_expr_str: JString, - session_ptr: *const Session, - callback: JObject, - on_close: JObject, -) -> *const Subscriber<()> { - let session = Arc::from_raw(session_ptr); - let subscriber_ptr = || -> ZResult<*const Subscriber<()>> { - prepare_subscriber_builder( - &mut env, - key_expr_ptr, - &key_expr_str, - &session, - callback, - on_close, - "subscriber", - )? - .wait() - .map(|s| Arc::into_raw(Arc::new(s))) - .map_err(|err| zerror!("Unable to declare subscriber: {}", err)) - }() - .unwrap_or_else(|err| { - throw_exception!(env, err); - null() - }); - std::mem::forget(session); - subscriber_ptr -} - -unsafe fn prepare_subscriber_builder<'a, 'b>( - env: &mut JNIEnv, - key_expr_ptr: /*nullable*/ *const KeyExpr<'static>, - key_expr_str: &JString, - session: &'a Session, - callback: JObject, - on_close: JObject, - entity_name: &str, -) -> ZResult>> { - let key_expr = process_kotlin_key_expr(env, key_expr_str, key_expr_ptr)?; - tracing::debug!("Declaring {entity_name} on '{}'...", key_expr); - - let builder = session - .declare_subscriber(key_expr.to_owned()) - .set_jni_sample_callback(env, callback, on_close)?; - - tracing::debug!("{entity_name} declared on '{}'.", key_expr); - Ok(builder) -} - -/// Declare a Zenoh querier via JNI. -/// -/// This function is meant to be called from Java/Kotlin code through JNI. -/// -/// Parameters: -/// - `env`: The JNI environment. -/// - `_class`: The JNI class. -/// - `key_expr_ptr`: A raw pointer to the [KeyExpr] to be used for the querier. May be null in case of using an -/// undeclared key expression. -/// - `key_expr_str`: String representation of the key expression to be used to declare the querier. -/// It won't be considered in case a key_expr_ptr to a declared key expression is provided. -/// - `target`: The ordinal value of the query target enum value. -/// - `consolidation`: The ordinal value of the consolidation enum value. -/// - `congestion_control`: The ordinal value of the congestion control enum value. -/// - `priority`: The ordinal value of the priority enum value. -/// - `is_express`: The boolean express value of the QoS provided. -/// - `timeout_ms`: The timeout in milliseconds. -#[no_mangle] -#[allow(non_snake_case)] -pub unsafe extern "C" fn Java_io_zenoh_jni_JNISession_declareQuerierViaJNI( - mut env: JNIEnv, - _class: JClass, - key_expr_ptr: /*nullable*/ *const KeyExpr<'static>, - key_expr_str: JString, - session_ptr: *const Session, - target: jint, - consolidation: jint, - congestion_control: jint, - priority: jint, - is_express: jboolean, - timeout_ms: jlong, - accept_replies: jint, -) -> *const Querier<'static> { - let session = Arc::from_raw(session_ptr); - || -> ZResult<*const Querier<'static>> { - let key_expr = process_kotlin_key_expr(&mut env, &key_expr_str, key_expr_ptr)?; - let query_target = decode_query_target(target)?; - let consolidation = decode_consolidation(consolidation)?; - let congestion_control = decode_congestion_control(congestion_control)?; - let timeout = Duration::from_millis(timeout_ms as u64); - let priority = decode_priority(priority)?; - let reply_key_expr = decode_reply_key_expr(accept_replies)?; - tracing::debug!("Declaring querier on '{}'...", key_expr); - - let querier = session - .declare_querier(key_expr.to_owned()) - .congestion_control(congestion_control) - .consolidation(consolidation) - .express(is_express != 0) - .target(query_target) - .priority(priority) - .timeout(timeout) - .accept_replies(reply_key_expr) - .wait() - .map_err(|err| zerror!(err))?; - - tracing::debug!("Querier declared on '{}'.", key_expr); - std::mem::forget(session); - Ok(Arc::into_raw(Arc::new(querier))) - }() - .unwrap_or_else(|err| { - throw_exception!(env, err); - null() - }) -} - -/// Declare a Zenoh queryable via JNI. -/// -/// This function is meant to be called from Java/Kotlin code through JNI. -/// -/// Parameters: -/// - `env`: The JNI environment. -/// - `_class`: The JNI class. -/// - `key_expr_ptr`: A raw pointer to the [KeyExpr] to be used for the queryable. May be null in case of using an -/// undeclared key expression. -/// - `key_expr_str`: String representation of the key expression to be used to declare the queryable. -/// It won't be considered in case a key_expr_ptr to a declared key expression is provided. -/// - `session_ptr`: A raw pointer to the Zenoh [Session] to be used to declare the queryable. -/// - `callback`: The callback function as an instance of the `JNIQueryableCallback` interface in Java/Kotlin. -/// - `on_close`: A Java/Kotlin `JNIOnCloseCallback` function interface to be called upon closing the queryable. -/// - `complete`: The completeness of the queryable. -/// -/// Returns: -/// - A raw pointer to the declared Zenoh queryable. In case of failure, an exception is thrown and null is returned. -/// -/// Safety: -/// - The function is marked as unsafe due to raw pointer manipulation and JNI interaction. -/// - It assumes that the provided session pointer is valid and has not been modified or freed. -/// - The session pointer remains valid and the ownership of the session is not transferred, -/// allowing safe usage of the session after this function call. -/// - The callback function passed as `callback` must be a valid instance of the `JNIQueryableCallback` interface -/// in Java/Kotlin, matching the specified signature. -/// - The function may throw a JNI exception in case of failure, which should be handled by the caller. -/// -#[no_mangle] -#[allow(non_snake_case)] -pub unsafe extern "C" fn Java_io_zenoh_jni_JNISession_declareQueryableViaJNI( - mut env: JNIEnv, - _class: JClass, - key_expr_ptr: /*nullable*/ *const KeyExpr<'static>, - key_expr_str: JString, - session_ptr: *const Session, - callback: JObject, - on_close: JObject, - complete: jboolean, -) -> *const Queryable<()> { - let session = Arc::from_raw(session_ptr); - let query_ptr = || -> ZResult<*const Queryable<()>> { - let java_vm = Arc::new(get_java_vm(&mut env)?); - let callback_global_ref = get_callback_global_ref(&mut env, callback)?; - let on_close_global_ref = get_callback_global_ref(&mut env, on_close)?; - let key_expr = process_kotlin_key_expr(&mut env, &key_expr_str, key_expr_ptr)?; - let complete = complete != 0; - let on_close = load_on_close(&java_vm, on_close_global_ref); - tracing::debug!("Declaring queryable through JNI on {}", key_expr); - let builder = session - .declare_queryable(key_expr) - .callback(move |query: Query| { - on_close.noop(); // Does nothing, but moves `on_close` inside the closure so it gets destroyed with the closure - let env = match java_vm.attach_current_thread_as_daemon() { - Ok(env) => env, - Err(err) => { - tracing::error!("Unable to attach thread for queryable callback: {}", err); - return; - } - }; - - tracing::debug!("Receiving query through JNI: {}", query.to_string()); - match on_query(env, query, &callback_global_ref) { - Ok(_) => tracing::debug!("Queryable callback called successfully."), - Err(err) => tracing::error!("Error calling queryable callback: {}", err), - } - }) - .complete(complete); - - let queryable = builder - .wait() - .map_err(|err| zerror!("Error declaring queryable: {}", err))?; - Ok(Arc::into_raw(Arc::new(queryable))) - }() - .unwrap_or_else(|err| { - throw_exception!(env, err); - null() - }); - std::mem::forget(session); - query_ptr -} - -fn on_query(mut env: JNIEnv, query: Query, callback_global_ref: &GlobalRef) -> ZResult<()> { - let selector_params_jstr = env - .new_string(query.parameters().to_string()) - .map(|value| env.auto_local(value)) - .map_err(|err| { - zerror!( - "Could not create a JString through JNI for the Query key expression. {}", - err - ) - })?; - - let (payload, encoding_id, encoding_schema) = if let Some(payload) = query.payload() { - let encoding = query.encoding().unwrap(); //If there is payload, there is encoding. - let encoding_id = encoding.id() as jint; - let encoding_schema = encoding - .schema() - .map_or_else( - || Ok(JString::default()), - |schema| slice_to_java_string(&env, schema), - ) - .map(|value| env.auto_local(value))?; - let byte_array = bytes_to_java_array(&env, payload).map(|value| env.auto_local(value))?; - (byte_array, encoding_id, encoding_schema) - } else { - ( - env.auto_local(JByteArray::default()), - 0, - env.auto_local(JString::default()), - ) - }; - - let attachment_bytes = query - .attachment() - .map_or_else( - || Ok(JByteArray::default()), - |attachment| bytes_to_java_array(&env, attachment), - ) - .map(|value| env.auto_local(value)) - .map_err(|err| zerror!("Error processing attachment of reply: {}.", err))?; - - let key_expr_str = env - .new_string(query.key_expr().to_string()) - .map(|key_expr| env.auto_local(key_expr)) - .map_err(|err| { - zerror!( - "Could not create a JString through JNI for the Query key expression: {}.", - err - ) - })?; - - let query_ptr = Arc::into_raw(Arc::new(query)); - - let result = env - .call_method( - callback_global_ref, - "run", - "(Ljava/lang/String;Ljava/lang/String;[BILjava/lang/String;[BJ)V", - &[ - JValue::from(&key_expr_str), - JValue::from(&selector_params_jstr), - JValue::from(&payload), - JValue::from(encoding_id), - JValue::from(&encoding_schema), - JValue::from(&attachment_bytes), - JValue::from(query_ptr as jlong), - ], - ) - .map(|_| ()) - .map_err(|err| { - // The callback could not be invoked, therefore the created kotlin query object won't be - // used. Since `query_ptr` as well as `key_expr_ptr` was created within this function - // and remains unaltered, it is safe to reclaim ownership of the memory by converting - // the raw pointers back into an `Arc` and freeing the memory. - unsafe { - Arc::from_raw(query_ptr); - }; - _ = env.exception_describe(); - zerror!(err) - }); - result -} - -/// Declare a [KeyExpr] through a [Session] via JNI. -/// -/// # Parameters: -/// - `env`: The JNI environment. -/// - `_class`: The JNI class. -/// - `session_ptr`: A raw pointer to the Zenoh [Session] from which to declare the key expression. -/// - `key_expr_str`: A Java String with the intended key expression. -/// -/// # Returns: -/// - A raw pointer to the declared key expression. In case of failure, an exception is thrown and null is returned. -/// -/// # Safety: -/// - The function is marked as unsafe due to raw pointer manipulation and JNI interaction. -/// - It assumes that the provided session pointer is valid and has not been modified or freed. -/// - The session pointer remains valid and the ownership of the session is not transferred, -/// allowing safe usage of the session after this function call. -/// - The function may throw an exception in case of failure, which should be handled by the caller. -/// -#[no_mangle] -#[allow(non_snake_case)] -pub unsafe extern "C" fn Java_io_zenoh_jni_JNISession_declareKeyExprViaJNI( - mut env: JNIEnv, - _class: JClass, - session_ptr: *const Session, - key_expr_str: JString, -) -> *const KeyExpr<'static> { - let session: Arc = Arc::from_raw(session_ptr); - let key_expr_ptr = || -> ZResult<*const KeyExpr<'static>> { - let key_expr_str = decode_string(&mut env, &key_expr_str)?; - let key_expr = session - .declare_keyexpr(key_expr_str.to_owned()) - .wait() - .map_err(|err| { - zerror!( - "Unable to declare key expression '{}': {}", - key_expr_str, - err - ) - })?; - Ok(Arc::into_raw(Arc::new(key_expr))) - }() - .unwrap_or_else(|err| { - throw_exception!(env, err); - null() - }); - mem::forget(session); - key_expr_ptr -} - -/// Undeclare a [KeyExpr] through a [Session] via JNI. -/// -/// The key expression must have been previously declared on the specified session, otherwise an -/// exception is thrown. -/// -/// This functions frees the key expression pointer provided. -/// -/// # Parameters: -/// - `env`: The JNI environment. -/// - `_class`: The JNI class. -/// - `session_ptr`: A raw pointer to the Zenoh [Session] from which to undeclare the key expression. -/// - `key_expr_ptr`: A raw pointer to the [KeyExpr] to undeclare. -/// -/// # Safety: -/// - The function is marked as unsafe due to raw pointer manipulation and JNI interaction. -/// - It assumes that the provided session and keyexpr pointers are valid and have not been modified or freed. -/// - The session pointer remains valid after this function call. -/// - The key expression pointer is voided after this function call. -/// - The function may throw an exception in case of failure, which should be handled by the caller. -/// -#[no_mangle] -#[allow(non_snake_case)] -pub unsafe extern "C" fn Java_io_zenoh_jni_JNISession_undeclareKeyExprViaJNI( - mut env: JNIEnv, - _class: JClass, - session_ptr: *const Session, - key_expr_ptr: *const KeyExpr<'static>, -) { - let session = Arc::from_raw(session_ptr); - let key_expr = Arc::from_raw(key_expr_ptr); - let key_expr_clone = key_expr.deref().clone(); - match session.undeclare(key_expr_clone).wait() { - Ok(_) => {} - Err(err) => { - throw_exception!( - env, - zerror!("Unable to declare key expression '{}': {}", key_expr, err) - ); - } - } - std::mem::forget(session); - // `key_expr` is intentionally left to be freed by Rust -} - -/// Performs a `get` operation in the Zenoh session via JNI with Value. -/// -/// # Parameters: -/// - `env`: The JNI environment. -/// - `_class`: The JNI class. -/// - `key_expr_ptr`: Raw pointer to a declared [KeyExpr] to be used for the query. May be null in case -/// of using a non declared key expression, in which case the `key_expr_str` parameter will be used instead. -/// - `key_expr_str`: String representation of the key expression to be used to declare the query. It is not -/// considered if a `key_expr_ptr` is provided. -/// - `selector_params`: Optional parameters of the selector. -/// - `session_ptr`: A raw pointer to the Zenoh [Session]. -/// - `callback`: A Java/Kotlin callback to be called upon receiving a reply. -/// - `on_close`: A Java/Kotlin `JNIOnCloseCallback` function interface to be called when no more replies will be received. -/// - `timeout_ms`: The timeout in milliseconds. -/// - `target`: The query target as the ordinal of the enum. -/// - `consolidation`: The consolidation mode as the ordinal of the enum. -/// - `attachment`: An optional attachment encoded into a byte array. -/// - `payload`: Optional payload for the query. -/// - `encoding_id`: The encoding of the payload. -/// - `encoding_schema`: The encoding schema of the payload, may be null. -/// - `congestion_control`: The ordinal value of the congestion control enum value. -/// - `priority`: The ordinal value of the priority enum value. -/// - `is_express`: The boolean express value of the QoS provided. -/// -/// Safety: -/// - The function is marked as unsafe due to raw pointer manipulation and JNI interaction. -/// - It assumes that the provided session pointer is valid and has not been modified or freed. -/// - The session pointer remains valid and the ownership of the session is not transferred, -/// allowing safe usage of the session after this function call. -/// - The function may throw a JNI exception in case of failure, which should be handled by the caller. -/// -/// Throws: -/// - An exception in case of failure handling the query. -/// -#[no_mangle] -#[allow(non_snake_case)] -pub unsafe extern "C" fn Java_io_zenoh_jni_JNISession_getViaJNI( - mut env: JNIEnv, - _class: JClass, - key_expr_ptr: /*nullable*/ *const KeyExpr<'static>, - key_expr_str: JString, - selector_params: /*nullable*/ JString, - session_ptr: *const Session, - callback: JObject, - on_close: JObject, - timeout_ms: jlong, - target: jint, - consolidation: jint, - attachment: /*nullable*/ JByteArray, - payload: /*nullable*/ JByteArray, - encoding_id: jint, - encoding_schema: /*nullable*/ JString, - congestion_control: jint, - priority: jint, - is_express: jboolean, - accept_replies: jint, -) { - let session = Arc::from_raw(session_ptr); - let _ = || -> ZResult<()> { - let key_expr = process_kotlin_key_expr(&mut env, &key_expr_str, key_expr_ptr)?; - let java_vm = Arc::new(get_java_vm(&mut env)?); - let callback_global_ref = get_callback_global_ref(&mut env, callback)?; - let on_close_global_ref = get_callback_global_ref(&mut env, on_close)?; - let query_target = decode_query_target(target)?; - let consolidation = decode_consolidation(consolidation)?; - let timeout = Duration::from_millis(timeout_ms as u64); - let congestion_control = decode_congestion_control(congestion_control)?; - let priority = decode_priority(priority)?; - let reply_key_expr = decode_reply_key_expr(accept_replies)?; - let on_close = load_on_close(&java_vm, on_close_global_ref); - let selector_params = if selector_params.is_null() { - String::new() - } else { - decode_string(&mut env, &selector_params)? - }; - let selector = Selector::owned(&key_expr, selector_params); - let mut get_builder = session - .get(selector) - .congestion_control(congestion_control) - .priority(priority) - .express(is_express != 0) - .callback(move |reply| { - || -> ZResult<()> { - on_close.noop(); // Does nothing, but moves `on_close` inside the closure so it gets destroyed with the closure - tracing::debug!("Receiving reply through JNI: {:?}", reply); - let mut env = java_vm.attach_current_thread_as_daemon().map_err(|err| { - zerror!("Unable to attach thread for GET query callback: {}.", err) - })?; - - match reply.result() { - Ok(sample) => on_reply_success( - &mut env, - reply.replier_id(), - sample, - &callback_global_ref, - ), - Err(error) => on_reply_error( - &mut env, - reply.replier_id(), - error, - &callback_global_ref, - ), - } - }() - .unwrap_or_else(|err| tracing::error!("Error on get callback: {err}")); - }) - .target(query_target) - .timeout(timeout) - .consolidation(consolidation) - .accept_replies(reply_key_expr); - - if !payload.is_null() { - let encoding = decode_encoding(&mut env, encoding_id, &encoding_schema)?; - get_builder = get_builder.encoding(encoding); - get_builder = get_builder.payload(decode_byte_array(&env, payload)?); - } - - if !attachment.is_null() { - let attachment = decode_byte_array(&env, attachment)?; - get_builder = get_builder.attachment::>(attachment); - } - - get_builder - .wait() - .map(|_| tracing::trace!("Performing get on '{key_expr}'.",)) - .map_err(|err| zerror!(err)) - }() - .map_err(|err| throw_exception!(env, err)); - std::mem::forget(session); -} - -pub(crate) fn on_reply_success( - env: &mut JNIEnv, - replier_id: Option, - sample: &Sample, - callback_global_ref: &GlobalRef, -) -> ZResult<()> { - let zenoh_id = replier_id - .map_or_else( - || Ok(JByteArray::default()), - |replier_id| { - env.byte_array_from_slice(&replier_id.zid().to_le_bytes()) - .map_err(|err| zerror!(err)) - }, - ) - .map(|value| env.auto_local(value))?; - let eid = replier_id.map_or_else(|| 0, |replier_id| replier_id.eid() as jint); - - let byte_array = - bytes_to_java_array(env, sample.payload()).map(|value| env.auto_local(value))?; - let encoding: jint = sample.encoding().id() as jint; - let encoding_schema = sample - .encoding() - .schema() - .map_or_else( - || Ok(JString::default()), - |schema| slice_to_java_string(env, schema), - ) - .map(|value| env.auto_local(value))?; - let kind = sample.kind() as jint; - - let (timestamp, is_valid) = sample - .timestamp() - .map(|timestamp| (timestamp.get_time().as_u64(), true)) - .unwrap_or((0, false)); - - let attachment_bytes = sample - .attachment() - .map_or_else( - || Ok(JByteArray::default()), - |attachment| bytes_to_java_array(env, attachment), - ) - .map(|value| env.auto_local(value)) - .map_err(|err| zerror!("Error processing attachment of reply: {}.", err))?; - - let key_expr_str = env - .new_string(sample.key_expr().to_string()) - .map(|value| env.auto_local(value)) - .map_err(|err| { - zerror!( - "Could not create a JString through JNI for the Sample key expression. {}", - err - ) - })?; - - let express = sample.express(); - let priority = sample.priority() as jint; - let cc = sample.congestion_control() as jint; - - let result = match env.call_method( - callback_global_ref, - "run", - "([BIZLjava/lang/String;[BILjava/lang/String;IJZ[BZII)V", - &[ - JValue::from(&zenoh_id), - JValue::from(eid), - JValue::from(true), - JValue::from(&key_expr_str), - JValue::from(&byte_array), - JValue::from(encoding), - JValue::from(&encoding_schema), - JValue::from(kind), - JValue::from(timestamp as i64), - JValue::from(is_valid), - JValue::from(&attachment_bytes), - JValue::from(express), - JValue::from(priority), - JValue::from(cc), - ], - ) { - Ok(_) => Ok(()), - Err(err) => { - _ = env.exception_describe(); - Err(zerror!("On GET callback error: {}", err)) - } - }; - result -} - -pub(crate) fn on_reply_error( - env: &mut JNIEnv, - replier_id: Option, - reply_error: &ReplyError, - callback_global_ref: &GlobalRef, -) -> ZResult<()> { - let zenoh_id = replier_id - .map_or_else( - || Ok(JByteArray::default()), - |replier_id| { - env.byte_array_from_slice(&replier_id.zid().to_le_bytes()) - .map_err(|err| zerror!(err)) - }, - ) - .map(|value| env.auto_local(value))?; - let eid = replier_id.map_or_else(|| 0, |replier_id| replier_id.eid() as jint); - - let payload = - bytes_to_java_array(env, reply_error.payload()).map(|value| env.auto_local(value))?; - let encoding_id: jint = reply_error.encoding().id() as jint; - let encoding_schema = reply_error - .encoding() - .schema() - .map_or_else( - || Ok(JString::default()), - |schema| slice_to_java_string(env, schema), - ) - .map(|value| env.auto_local(value))?; - let result = match env.call_method( - callback_global_ref, - "run", - "([BIZLjava/lang/String;[BILjava/lang/String;IJZ[BZII)V", - &[ - JValue::from(&zenoh_id), - JValue::from(eid), - JValue::from(false), - JValue::from(&JString::default()), - JValue::from(&payload), - JValue::from(encoding_id), - JValue::from(&encoding_schema), - // The remaining parameters aren't used in case of replying error, so we set them to default. - JValue::from(0 as jint), - JValue::from(0_i64), - JValue::from(false), - JValue::from(&JByteArray::default()), - JValue::from(false), - JValue::from(0 as jint), - JValue::from(0 as jint), - ], - ) { - Ok(_) => Ok(()), - Err(err) => { - _ = env.exception_describe(); - Err(zerror!("On GET callback error: {}", err)) - } - }; - result -} - -/// Returns a list of zenoh ids as byte arrays corresponding to the peers connected to the session provided. -/// -#[no_mangle] -#[allow(non_snake_case)] -pub unsafe extern "C" fn Java_io_zenoh_jni_JNISession_getPeersZidViaJNI( - mut env: JNIEnv, - _class: JClass, - session_ptr: *const Session, -) -> jobject { - let session = Arc::from_raw(session_ptr); - let ids = { - let peers_zid = session.info().peers_zid().wait(); - let ids = peers_zid.collect::>(); - ids_to_java_list(&mut env, ids).map_err(|err| zerror!(err)) - } - .unwrap_or_else(|err| { - throw_exception!(env, err); - JObject::default().as_raw() - }); - std::mem::forget(session); - ids -} - -/// Returns a list of zenoh ids as byte arrays corresponding to the routers connected to the session provided. -/// -#[no_mangle] -#[allow(non_snake_case)] -pub unsafe extern "C" fn Java_io_zenoh_jni_JNISession_getRoutersZidViaJNI( - mut env: JNIEnv, - _class: JClass, - session_ptr: *const Session, -) -> jobject { - let session = Arc::from_raw(session_ptr); - let ids = { - let peers_zid = session.info().routers_zid().wait(); - let ids = peers_zid.collect::>(); - ids_to_java_list(&mut env, ids).map_err(|err| zerror!(err)) - } - .unwrap_or_else(|err| { - throw_exception!(env, err); - JObject::default().as_raw() - }); - std::mem::forget(session); - ids -} - -/// Returns the Zenoh ID as a byte array of the session. -#[no_mangle] -#[allow(non_snake_case)] -pub unsafe extern "C" fn Java_io_zenoh_jni_JNISession_getZidViaJNI( - mut env: JNIEnv, - _class: JClass, - session_ptr: *const Session, -) -> jbyteArray { - let session = Arc::from_raw(session_ptr); - let ids = { - let zid = session.info().zid().wait(); - env.byte_array_from_slice(&zid.to_le_bytes()) - .map(|x| x.as_raw()) - .map_err(|err| zerror!(err)) - } - .unwrap_or_else(|err| { - throw_exception!(env, err); - JByteArray::default().as_raw() - }); - std::mem::forget(session); - ids -} - -fn ids_to_java_list(env: &mut JNIEnv, ids: Vec) -> jni::errors::Result { - let array_list = env.new_object("java/util/ArrayList", "()V", &[])?; - let jlist = JList::from_env(env, &array_list)?; - for id in ids { - let value = &mut env.byte_array_from_slice(&id.to_le_bytes())?; - jlist.add(env, value)?; - } - Ok(array_list.as_raw()) -} diff --git a/zenoh-jni/src/subscriber.rs b/zenoh-jni/src/subscriber.rs deleted file mode 100644 index 3462ec7e0..000000000 --- a/zenoh-jni/src/subscriber.rs +++ /dev/null @@ -1,41 +0,0 @@ -// -// Copyright (c) 2023 ZettaScale Technology -// -// This program and the accompanying materials are made available under the -// terms of the Eclipse Public License 2.0 which is available at -// http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 -// which is available at https://www.apache.org/licenses/LICENSE-2.0. -// -// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 -// -// Contributors: -// ZettaScale Zenoh Team, -// - -use std::sync::Arc; - -use jni::{objects::JClass, JNIEnv}; -use zenoh::pubsub::Subscriber; - -/// Frees the [Subscriber]. -/// -/// # Parameters: -/// - `_env`: The JNI environment. -/// - `_class`: The JNI class. -/// - `subscriber_ptr`: The raw pointer to the Zenoh subscriber ([Subscriber]). -/// -/// # Safety: -/// - The function is marked as unsafe due to raw pointer manipulation. -/// - It assumes that the provided subscriber pointer is valid and has not been modified or freed. -/// - The function takes ownership of the raw pointer and releases the associated memory. -/// - After calling this function, the subscriber pointer becomes invalid and should not be used anymore. -/// -#[no_mangle] -#[allow(non_snake_case)] -pub(crate) unsafe extern "C" fn Java_io_zenoh_jni_JNISubscriber_freePtrViaJNI( - _env: JNIEnv, - _: JClass, - subscriber_ptr: *const Subscriber<()>, -) { - Arc::from_raw(subscriber_ptr); -} diff --git a/zenoh-jni/src/utils.rs b/zenoh-jni/src/utils.rs deleted file mode 100644 index e60add131..000000000 --- a/zenoh-jni/src/utils.rs +++ /dev/null @@ -1,189 +0,0 @@ -// -// Copyright (c) 2023 ZettaScale Technology -// -// This program and the accompanying materials are made available under the -// terms of the Eclipse Public License 2.0 which is available at -// http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 -// which is available at https://www.apache.org/licenses/LICENSE-2.0. -// -// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 -// -// Contributors: -// ZettaScale Zenoh Team, -// - -use std::sync::Arc; - -use crate::{errors::ZResult, throw_exception, zerror}; -use jni::{ - objects::{JByteArray, JObject, JString}, - sys::jint, - JNIEnv, JavaVM, -}; -use zenoh::{ - bytes::{Encoding, ZBytes}, - internal::buffers::ZSlice, - qos::{CongestionControl, Priority, Reliability}, - query::{ConsolidationMode, QueryTarget, ReplyKeyExpr}, -}; - -/// Converts a JString into a rust String. -pub(crate) fn decode_string(env: &mut JNIEnv, string: &JString) -> ZResult { - let binding = env - .get_string(string) - .map_err(|err| zerror!("Error while retrieving JString: {}", err))?; - let value = binding - .to_str() - .map_err(|err| zerror!("Error decoding JString: {}", err))?; - Ok(value.to_string()) -} - -pub(crate) fn decode_encoding( - env: &mut JNIEnv, - encoding: jint, - schema: &JString, -) -> ZResult { - let schema: Option = if schema.is_null() { - None - } else { - Some(decode_string(env, schema)?.into_bytes().into()) - }; - let encoding_id = - u16::try_from(encoding).map_err(|err| zerror!("Failed to decode encoding: {}", err))?; - Ok(Encoding::new(encoding_id, schema)) -} - -pub(crate) fn get_java_vm(env: &mut JNIEnv) -> ZResult { - env.get_java_vm() - .map_err(|err| zerror!("Unable to retrieve JVM reference: {}", err)) -} - -pub(crate) fn get_callback_global_ref( - env: &mut JNIEnv, - callback: JObject, -) -> crate::errors::ZResult { - env.new_global_ref(callback) - .map_err(|err| zerror!("Unable to get reference to the provided callback: {}", err)) -} - -/// Helper function to convert a JByteArray into a Vec. -pub(crate) fn decode_byte_array(env: &JNIEnv<'_>, payload: JByteArray) -> ZResult> { - let payload_len = env - .get_array_length(&payload) - .map(|length| length as usize) - .map_err(|err| zerror!(err))?; - let mut buff = vec![0; payload_len]; - env.get_byte_array_region(payload, 0, &mut buff[..]) - .map_err(|err| zerror!(err))?; - let buff: Vec = unsafe { std::mem::transmute::, Vec>(buff) }; - Ok(buff) -} - -pub(crate) fn decode_priority(priority: jint) -> ZResult { - Priority::try_from(priority as u8).map_err(|err| zerror!("Error retrieving priority: {}.", err)) -} - -pub(crate) fn decode_congestion_control(congestion_control: jint) -> ZResult { - match congestion_control { - 1 => Ok(CongestionControl::Block), - 0 => Ok(CongestionControl::Drop), - value => Err(zerror!("Unknown congestion control '{}'.", value)), - } -} - -pub(crate) fn decode_query_target(target: jint) -> ZResult { - match target { - 0 => Ok(QueryTarget::BestMatching), - 1 => Ok(QueryTarget::All), - 2 => Ok(QueryTarget::AllComplete), - value => Err(zerror!("Unable to decode QueryTarget '{}'.", value)), - } -} - -pub(crate) fn decode_reply_key_expr(reply_key_expr: jint) -> ZResult { - match reply_key_expr { - 0 => Ok(ReplyKeyExpr::Any), - 1 => Ok(ReplyKeyExpr::MatchingQuery), - value => Err(zerror!("Unable to decode ReplyKeyExpr '{}'.", value)), - } -} - -pub(crate) fn decode_consolidation(consolidation: jint) -> ZResult { - match consolidation { - 0 => Ok(ConsolidationMode::Auto), - 1 => Ok(ConsolidationMode::None), - 2 => Ok(ConsolidationMode::Monotonic), - 3 => Ok(ConsolidationMode::Latest), - value => Err(zerror!("Unable to decode consolidation '{}'", value)), - } -} - -pub(crate) fn decode_reliability(reliability: jint) -> ZResult { - match reliability { - 0 => Ok(Reliability::BestEffort), - 1 => Ok(Reliability::Reliable), - value => Err(zerror!("Unable to decode reliability '{}'", value)), - } -} - -pub(crate) fn bytes_to_java_array<'a>(env: &JNIEnv<'a>, slice: &ZBytes) -> ZResult> { - env.byte_array_from_slice(&slice.to_bytes()) - .map_err(|err| zerror!(err)) -} - -pub(crate) fn slice_to_java_string<'a>(env: &JNIEnv<'a>, slice: &ZSlice) -> ZResult> { - env.new_string( - String::from_utf8(slice.to_vec()) - .map_err(|err| zerror!("Unable to decode string: {}", err))?, - ) - .map_err(|err| zerror!(err)) -} - -/// A type that calls a function when dropped -pub(crate) struct CallOnDrop(core::mem::MaybeUninit); -impl CallOnDrop { - /// Constructs a value that calls `f` when dropped. - pub fn new(f: F) -> Self { - Self(core::mem::MaybeUninit::new(f)) - } - /// Does nothing, but tricks closures into moving the value inside, - /// so that the closure's destructor will call `drop(self)`. - pub fn noop(&self) {} -} -impl Drop for CallOnDrop { - fn drop(&mut self) { - // Take ownership of the closure that is always initialized, - // since the only constructor uses `MaybeUninit::new` - let f = unsafe { self.0.assume_init_read() }; - // Call the now owned function - f(); - } -} - -pub(crate) fn load_on_close( - java_vm: &Arc, - on_close_global_ref: jni::objects::GlobalRef, -) -> CallOnDrop { - CallOnDrop::new({ - let java_vm = java_vm.clone(); - move || { - let mut env = match java_vm.attach_current_thread_as_daemon() { - Ok(env) => env, - Err(err) => { - tracing::error!("Unable to attach thread for 'onClose' callback: {}", err); - return; - } - }; - match env.call_method(on_close_global_ref, "run", "()V", &[]) { - Ok(_) => (), - Err(err) => { - _ = env.exception_describe(); - throw_exception!( - env, - zerror!("Error while running 'onClose' callback: {}", err) - ); - } - } - } - }) -} diff --git a/zenoh-jni/src/zbytes.rs b/zenoh-jni/src/zbytes.rs deleted file mode 100644 index 0a1b04439..000000000 --- a/zenoh-jni/src/zbytes.rs +++ /dev/null @@ -1,571 +0,0 @@ -// -// Copyright (c) 2023 ZettaScale Technology -// -// This program and the accompanying materials are made available under the -// terms of the Eclipse Public License 2.0 which is available at -// http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 -// which is available at https://www.apache.org/licenses/LICENSE-2.0. -// -// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 -// -// Contributors: -// ZettaScale Zenoh Team, -// - -use jni::{ - objects::{AutoLocal, JByteArray, JClass, JList, JMap, JObject, JString, JValue}, - sys::jobject, - JNIEnv, -}; -use zenoh::bytes::ZBytes; -use zenoh_ext::{VarInt, ZDeserializeError, ZDeserializer, ZSerializer}; - -use crate::{ - errors::ZResult, - throw_exception, - utils::{bytes_to_java_array, decode_byte_array}, - zerror, -}; - -enum KotlinType { - Boolean, - String, - ByteArray, - Byte, - Short, - Int, - Long, - Float, - Double, - UByte, - UShort, - UInt, - ULong, - List(Box), - Map(Box, Box), - Pair(Box, Box), - Triple(Box, Box, Box), -} - -fn decode_ktype(env: &mut JNIEnv, ktype: JObject) -> ZResult { - let classifier_obj = env - .call_method( - &ktype, - "getClassifier", - "()Lkotlin/reflect/KClassifier;", - &[], - ) - .map_err(|err| zerror!(err))? - .l() - .map_err(|err| zerror!(err))?; - let classifier_obj = AutoLocal::new(classifier_obj, env); - - let kclass_class = env - .find_class("kotlin/reflect/KClass") - .map_err(|err| zerror!(err))?; - let is_kclass = env - .is_instance_of(&classifier_obj, kclass_class) - .map_err(|err| zerror!(err))?; - if is_kclass { - let qualified_name_jobject = env - .call_method( - &classifier_obj, - "getQualifiedName", - "()Ljava/lang/String;", - &[], - ) - .map_err(|err| zerror!(err))? - .l() - .map_err(|err| zerror!(err))?; - - let qualified_name: String = env - .get_string(&JString::from(qualified_name_jobject)) - .map_err(|err| zerror!(err))? - .into(); - - match qualified_name.as_str() { - "kotlin.Boolean" => Ok(KotlinType::Boolean), - "kotlin.String" => Ok(KotlinType::String), - "kotlin.ByteArray" => Ok(KotlinType::ByteArray), - "kotlin.Byte" => Ok(KotlinType::Byte), - "kotlin.Short" => Ok(KotlinType::Short), - "kotlin.Int" => Ok(KotlinType::Int), - "kotlin.Long" => Ok(KotlinType::Long), - "kotlin.Float" => Ok(KotlinType::Float), - "kotlin.Double" => Ok(KotlinType::Double), - "kotlin.UByte" => Ok(KotlinType::UByte), - "kotlin.UShort" => Ok(KotlinType::UShort), - "kotlin.UInt" => Ok(KotlinType::UInt), - "kotlin.ULong" => Ok(KotlinType::ULong), - "kotlin.collections.List" => Ok(KotlinType::List(Box::new(decode_ktype_arg( - env, &ktype, 0, - )?))), - "kotlin.collections.Map" => Ok(KotlinType::Map( - Box::new(decode_ktype_arg(env, &ktype, 0)?), - Box::new(decode_ktype_arg(env, &ktype, 1)?), - )), - "kotlin.Pair" => Ok(KotlinType::Pair( - Box::new(decode_ktype_arg(env, &ktype, 0)?), - Box::new(decode_ktype_arg(env, &ktype, 1)?), - )), - "kotlin.Triple" => Ok(KotlinType::Triple( - Box::new(decode_ktype_arg(env, &ktype, 0)?), - Box::new(decode_ktype_arg(env, &ktype, 1)?), - Box::new(decode_ktype_arg(env, &ktype, 2)?), - )), - _ => Err(zerror!("Unsupported type: {}", qualified_name)), - } - } else { - Err(zerror!("Classifier is not a KClass")) - } -} - -fn decode_ktype_arg(env: &mut JNIEnv, ktype: &JObject, idx: i32) -> ZResult { - let arguments = env - .call_method(ktype, "getArguments", "()Ljava/util/List;", &[]) - .map_err(|err| zerror!(err))? - .l() - .map_err(|err| zerror!(err))?; - let arg = env - .call_method( - &arguments, - "get", - "(I)Ljava/lang/Object;", - &[JValue::Int(idx)], - ) - .map_err(|err| zerror!(err))? - .l() - .map_err(|err| zerror!(err))?; - let ktype = env - .call_method(arg, "getType", "()Lkotlin/reflect/KType;", &[]) - .map_err(|err| zerror!(err))? - .l() - .map_err(|err| zerror!(err))?; - decode_ktype(env, ktype) -} - -#[no_mangle] -#[allow(non_snake_case)] -pub extern "C" fn Java_io_zenoh_jni_JNIZBytes_serializeViaJNI( - mut env: JNIEnv, - _class: JClass, - any: JObject, - ktype: JObject, -) -> jobject { - || -> ZResult { - let mut serializer = ZSerializer::new(); - let ktype = decode_ktype(&mut env, ktype)?; - serialize(&mut env, &mut serializer, any, &ktype)?; - let zbytes = serializer.finish(); - - let byte_array = bytes_to_java_array(&env, &zbytes).map_err(|err| zerror!(err))?; - let zbytes_obj = env - .new_object( - "io/zenoh/bytes/ZBytes", - "([B)V", - &[JValue::Object(&JObject::from(byte_array))], - ) - .map_err(|err| zerror!(err))?; - - Ok(zbytes_obj.as_raw()) - }() - .unwrap_or_else(|err| { - throw_exception!(env, err); - JObject::default().as_raw() - }) -} - -fn serialize( - env: &mut JNIEnv, - serializer: &mut ZSerializer, - any: JObject, - ktype: &KotlinType, -) -> ZResult<()> { - match ktype { - KotlinType::Byte => { - let byte_value = env - .call_method(any, "byteValue", "()B", &[]) - .map_err(|err| zerror!(err))? - .b() - .map_err(|err| zerror!(err))?; - serializer.serialize(byte_value); - } - KotlinType::Short => { - let short_value = env - .call_method(any, "shortValue", "()S", &[]) - .map_err(|err| zerror!(err))? - .s() - .map_err(|err| zerror!(err))?; - serializer.serialize(short_value); - } - KotlinType::Int => { - let int_value = env - .call_method(any, "intValue", "()I", &[]) - .map_err(|err| zerror!(err))? - .i() - .map_err(|err| zerror!(err))?; - serializer.serialize(int_value); - } - KotlinType::Long => { - let long_value = env - .call_method(any, "longValue", "()J", &[]) - .map_err(|err| zerror!(err))? - .j() - .map_err(|err| zerror!(err))?; - serializer.serialize(long_value); - } - KotlinType::Float => { - let float_value = env - .call_method(any, "floatValue", "()F", &[]) - .map_err(|err| zerror!(err))? - .f() - .map_err(|err| zerror!(err))?; - serializer.serialize(float_value); - } - KotlinType::Double => { - let double_value = env - .call_method(any, "doubleValue", "()D", &[]) - .map_err(|err| zerror!(err))? - .d() - .map_err(|err| zerror!(err))?; - serializer.serialize(double_value); - } - KotlinType::Boolean => { - let boolean_value = env - .call_method(any, "booleanValue", "()Z", &[]) - .map_err(|err| zerror!(err))? - .z() - .map_err(|err| zerror!(err))?; - serializer.serialize(boolean_value); - } - KotlinType::String => { - let jstring = JString::from(any); - let string_value: String = env.get_string(&jstring).map_err(|err| zerror!(err))?.into(); - serializer.serialize(string_value); - } - KotlinType::ByteArray => { - let jbyte_array = JByteArray::from(any); - let bytes = decode_byte_array(env, jbyte_array).map_err(|err| zerror!(err))?; - serializer.serialize(bytes); - } - KotlinType::UByte => { - let byte = env - .get_field(any, "data", "B") - .map_err(|err| zerror!(err))? - .b() - .map_err(|err| zerror!(err))?; - serializer.serialize(byte as u8); - } - KotlinType::UShort => { - let short = env - .get_field(any, "data", "S") - .map_err(|err| zerror!(err))? - .s() - .map_err(|err| zerror!(err))?; - serializer.serialize(short as u16); - } - KotlinType::UInt => { - let int = env - .get_field(any, "data", "I") - .map_err(|err| zerror!(err))? - .i() - .map_err(|err| zerror!(err))?; - serializer.serialize(int as u32); - } - KotlinType::ULong => { - let long = env - .get_field(any, "data", "J") - .map_err(|err| zerror!(err))? - .j() - .map_err(|err| zerror!(err))?; - serializer.serialize(long as u64); - } - KotlinType::List(kotlin_type) => { - let jlist: JList<'_, '_, '_> = - JList::from_env(env, &any).map_err(|err| zerror!(err))?; - let mut iterator = jlist.iter(env).map_err(|err| zerror!(err))?; - let list_size = jlist.size(env).unwrap(); - serializer.serialize(zenoh_ext::VarInt(list_size as usize)); - while let Some(value) = iterator.next(env).map_err(|err| zerror!(err))? { - serialize(env, serializer, value, kotlin_type)?; - } - } - KotlinType::Map(key_type, value_type) => { - let jmap = JMap::from_env(env, &any).map_err(|err| zerror!(err))?; - - let map_size = env - .call_method(&jmap, "size", "()I", &[]) - .map_err(|err| zerror!(err))? - .i() - .map_err(|err| zerror!(err))?; - - serializer.serialize(zenoh_ext::VarInt(map_size as usize)); - - let mut iterator = jmap.iter(env).map_err(|err| zerror!(err))?; - while let Some((key, value)) = iterator.next(env).map_err(|err| zerror!(err))? { - serialize(env, serializer, key, key_type)?; - serialize(env, serializer, value, value_type)?; - } - } - KotlinType::Pair(first_type, second_type) => { - let first = env - .call_method(&any, "getFirst", "()Ljava/lang/Object;", &[]) - .map_err(|err| zerror!(err))? - .l() - .map_err(|err| zerror!(err))?; - let second = env - .call_method(&any, "getSecond", "()Ljava/lang/Object;", &[]) - .map_err(|err| zerror!(err))? - .l() - .map_err(|err| zerror!(err))?; - serialize(env, serializer, first, first_type)?; - serialize(env, serializer, second, second_type)?; - } - KotlinType::Triple(first_type, second_type, third_type) => { - let first = env - .call_method(&any, "getFirst", "()Ljava/lang/Object;", &[]) - .map_err(|err| zerror!(err))? - .l() - .map_err(|err| zerror!(err))?; - let second = env - .call_method(&any, "getSecond", "()Ljava/lang/Object;", &[]) - .map_err(|err| zerror!(err))? - .l() - .map_err(|err| zerror!(err))?; - let third = env - .call_method(&any, "getThird", "()Ljava/lang/Object;", &[]) - .map_err(|err| zerror!(err))? - .l() - .map_err(|err| zerror!(err))?; - serialize(env, serializer, first, first_type)?; - serialize(env, serializer, second, second_type)?; - serialize(env, serializer, third, third_type)?; - } - } - Ok(()) -} - -#[no_mangle] -#[allow(non_snake_case)] -pub extern "C" fn Java_io_zenoh_jni_JNIZBytes_deserializeViaJNI( - mut env: JNIEnv, - _class: JClass, - zbytes: JObject, - ktype: JObject, -) -> jobject { - || -> ZResult { - let payload = env - .get_field(zbytes, "bytes", "[B") - .map_err(|err| zerror!(err))?; - let decoded_bytes: Vec = - decode_byte_array(&env, JByteArray::from(payload.l().unwrap()))?; - let zbytes = ZBytes::from(decoded_bytes); - let mut deserializer = ZDeserializer::new(&zbytes); - let ktype = decode_ktype(&mut env, ktype)?; - let obj = deserialize(&mut env, &mut deserializer, &ktype)?; - if !deserializer.done() { - return Err(zerror!(ZDeserializeError)); - } - Ok(obj) - }() - .unwrap_or_else(|err| { - throw_exception!(env, err); - JObject::default().as_raw() - }) -} - -fn deserialize( - env: &mut JNIEnv, - deserializer: &mut ZDeserializer, - ktype: &KotlinType, -) -> ZResult { - match ktype { - KotlinType::Byte => { - let byte = deserializer - .deserialize::() - .map_err(|err| zerror!(err))?; - let byte_obj = env - .new_object("java/lang/Byte", "(B)V", &[JValue::Byte(byte)]) - .map_err(|err| zerror!(err))?; - Ok(byte_obj.as_raw()) - } - KotlinType::Short => { - let short = deserializer - .deserialize::() - .map_err(|err| zerror!(err))?; - let short_obj = env - .new_object("java/lang/Short", "(S)V", &[JValue::Short(short)]) - .map_err(|err| zerror!(err))?; - Ok(short_obj.as_raw()) - } - KotlinType::Int => { - let integer = deserializer - .deserialize::() - .map_err(|err| zerror!(err))?; - let integer_obj = env - .new_object("java/lang/Integer", "(I)V", &[JValue::Int(integer)]) - .map_err(|err| zerror!(err))?; - Ok(integer_obj.as_raw()) - } - KotlinType::Long => { - let long = deserializer - .deserialize::() - .map_err(|err| zerror!(err))?; - let long_obj = env - .new_object("java/lang/Long", "(J)V", &[JValue::Long(long)]) - .map_err(|err| zerror!(err))?; - Ok(long_obj.as_raw()) - } - KotlinType::Float => { - let float = deserializer - .deserialize::() - .map_err(|err| zerror!(err))?; - let float_obj = env - .new_object("java/lang/Float", "(F)V", &[JValue::Float(float)]) - .map_err(|err| zerror!(err))?; - Ok(float_obj.as_raw()) - } - KotlinType::Double => { - let double = deserializer - .deserialize::() - .map_err(|err| zerror!(err))?; - let double_obj = env - .new_object("java/lang/Double", "(D)V", &[JValue::Double(double)]) - .map_err(|err| zerror!(err))?; - Ok(double_obj.as_raw()) - } - KotlinType::Boolean => { - let boolean_value = deserializer - .deserialize::() - .map_err(|err| zerror!(err))?; - let jboolean = if boolean_value { 1u8 } else { 0u8 }; - let boolean_obj = env - .new_object("java/lang/Boolean", "(Z)V", &[JValue::Bool(jboolean)]) - .map_err(|err| zerror!(err))?; - Ok(boolean_obj.as_raw()) - } - KotlinType::String => { - let deserialized_string = deserializer - .deserialize::() - .map_err(|err| zerror!(err))?; - let jstring = env - .new_string(&deserialized_string) - .map_err(|err| zerror!(err))?; - Ok(jstring.into_raw()) - } - KotlinType::ByteArray => { - let deserialized_bytes = deserializer - .deserialize::>() - .map_err(|err| zerror!(err))?; - let jbytes = env - .byte_array_from_slice(deserialized_bytes.as_slice()) - .map_err(|err| zerror!(err))?; - Ok(jbytes.into_raw()) - } - KotlinType::UByte => { - let ubyte = deserializer - .deserialize::() - .map_err(|err| zerror!(err))?; - let ubyte_obj = env - .new_object("kotlin/UByte", "(B)V", &[JValue::Byte(ubyte as i8)]) - .map_err(|err| zerror!(err))?; - Ok(ubyte_obj.as_raw()) - } - KotlinType::UShort => { - let ushort = deserializer - .deserialize::() - .map_err(|err| zerror!(err))?; - let ushort_obj = env - .new_object("kotlin/UShort", "(S)V", &[JValue::Short(ushort as i16)]) - .map_err(|err| zerror!(err))?; - Ok(ushort_obj.as_raw()) - } - KotlinType::UInt => { - let uint = deserializer - .deserialize::() - .map_err(|err| zerror!(err))?; - let uint_obj = env - .new_object("kotlin/UInt", "(I)V", &[JValue::Int(uint as i32)]) - .map_err(|err| zerror!(err))?; - Ok(uint_obj.as_raw()) - } - KotlinType::ULong => { - let ulong = deserializer - .deserialize::() - .map_err(|err| zerror!(err))?; - let ulong_obj = env - .new_object("kotlin/ULong", "(J)V", &[JValue::Long(ulong as i64)]) - .map_err(|err| zerror!(err))?; - Ok(ulong_obj.as_raw()) - } - KotlinType::List(kotlin_type) => { - let list_size = deserializer - .deserialize::>() - .map_err(|err| zerror!(err))? - .0; - let array_list = env - .new_object("java/util/ArrayList", "()V", &[]) - .map_err(|err| zerror!(err))?; - let jlist = JList::from_env(env, &array_list).map_err(|err| zerror!(err))?; - - for _ in 0..list_size { - let item = deserialize(env, deserializer, kotlin_type)?; - let item_obj = unsafe { JObject::from_raw(item) }; - jlist.add(env, &item_obj).map_err(|err| zerror!(err))?; - } - Ok(array_list.as_raw()) - } - KotlinType::Map(key_type, value_type) => { - let map_size = deserializer - .deserialize::>() - .map_err(|err| zerror!(err))? - .0; - let map = env - .new_object("java/util/HashMap", "()V", &[]) - .map_err(|err| zerror!(err))?; - let jmap = JMap::from_env(env, &map).map_err(|err| zerror!(err))?; - - for _ in 0..map_size { - let key = deserialize(env, deserializer, key_type)?; - let key_obj = unsafe { JObject::from_raw(key) }; - let value = deserialize(env, deserializer, value_type)?; - let value_obj = unsafe { JObject::from_raw(value) }; - jmap.put(env, &key_obj, &value_obj) - .map_err(|err| zerror!(err))?; - } - Ok(map.as_raw()) - } - KotlinType::Pair(first_type, second_type) => { - let first = deserialize(env, deserializer, first_type)?; - let second = deserialize(env, deserializer, second_type)?; - let pair = env - .new_object( - "kotlin/Pair", - "(Ljava/lang/Object;Ljava/lang/Object;)V", - &[ - JValue::Object(&unsafe { JObject::from_raw(first) }), - JValue::Object(&unsafe { JObject::from_raw(second) }), - ], - ) - .map_err(|err| zerror!(err))?; - Ok(pair.as_raw()) - } - KotlinType::Triple(first_type, second_type, third_type) => { - let first = deserialize(env, deserializer, first_type)?; - let second = deserialize(env, deserializer, second_type)?; - let third = deserialize(env, deserializer, third_type)?; - let triple = env - .new_object( - "kotlin/Triple", - "(Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;)V", - &[ - JValue::Object(&unsafe { JObject::from_raw(first) }), - JValue::Object(&unsafe { JObject::from_raw(second) }), - JValue::Object(&unsafe { JObject::from_raw(third) }), - ], - ) - .map_err(|err| zerror!(err))?; - Ok(triple.as_raw()) - } - } -} diff --git a/zenoh-jni/src/zenoh_id.rs b/zenoh-jni/src/zenoh_id.rs deleted file mode 100644 index 6647f86f0..000000000 --- a/zenoh-jni/src/zenoh_id.rs +++ /dev/null @@ -1,42 +0,0 @@ -// -// Copyright (c) 2023 ZettaScale Technology -// -// This program and the accompanying materials are made available under the -// terms of the Eclipse Public License 2.0 which is available at -// http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 -// which is available at https://www.apache.org/licenses/LICENSE-2.0. -// -// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 -// -// Contributors: -// ZettaScale Zenoh Team, -// - -use crate::{errors::ZResult, throw_exception, utils::decode_byte_array, zerror}; -use jni::{ - objects::{JByteArray, JClass, JString}, - sys::jstring, - JNIEnv, -}; -use zenoh::session::ZenohId; - -/// Returns the string representation of a ZenohID. -#[no_mangle] -#[allow(non_snake_case)] -pub extern "C" fn Java_io_zenoh_jni_JNIZenohId_toStringViaJNI( - mut env: JNIEnv, - _class: JClass, - zenoh_id: JByteArray, -) -> jstring { - || -> ZResult { - let bytes = decode_byte_array(&env, zenoh_id)?; - let zenohid = ZenohId::try_from(bytes.as_slice()).map_err(|err| zerror!(err))?; - env.new_string(zenohid.to_string()) - .map_err(|err| zerror!(err)) - }() - .unwrap_or_else(|err| { - throw_exception!(env, err); - JString::default() - }) - .as_raw() -} diff --git a/zenoh-kotlin/build.gradle.kts b/zenoh-kotlin/build.gradle.kts index f17c3e737..3c725e7fc 100644 --- a/zenoh-kotlin/build.gradle.kts +++ b/zenoh-kotlin/build.gradle.kts @@ -12,8 +12,6 @@ // ZettaScale Zenoh Team, // -import com.nishtahir.CargoExtension - plugins { kotlin("multiplatform") kotlin("plugin.serialization") @@ -24,19 +22,16 @@ plugins { } val androidEnabled = project.findProperty("android")?.toString()?.toBoolean() == true -val release = project.findProperty("release")?.toString()?.toBoolean() == true + +// The zenoh-flat-jni release this SDK is built against — see gradle.properties. +val zenohFlatJniVersion: String by project // If the publication is meant to be done on a remote repository (Maven central). // Modifying this property will affect the release workflows! val isRemotePublication = project.findProperty("remotePublication")?.toString()?.toBoolean() == true -var buildMode = if (release) BuildMode.RELEASE else BuildMode.DEBUG - if (androidEnabled) { apply(plugin = "com.android.library") - apply(plugin = "org.mozilla.rust-android-gradle.rust-android") - - configureCargo() configureAndroid() } @@ -57,39 +52,44 @@ kotlin { sourceSets { val commonMain by getting { dependencies { - implementation("commons-net:commons-net:3.9.0") implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.3") implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.6.0") - implementation("org.jetbrains.kotlin:kotlin-reflect") + // Zenoh Flat JNI. One coordinate: it is a Kotlin Multiplatform + // library, so Gradle resolves the JVM or Android variant from + // its module metadata and the matching native libraries come + // with it. Nothing here selects a platform by hand. + implementation("org.eclipse.zenoh:zenoh-flat-jni:$zenohFlatJniVersion") } } val commonTest by getting { dependencies { implementation(kotlin("test")) + // Only the tests need an NTP64 clock now: the SDK's Timestamp + // carries the raw bits and an originating-node id, so it no + // longer depends on commons-net itself. + implementation("commons-net:commons-net:3.9.0") implementation("org.junit.jupiter:junit-jupiter-api:5.10.0") runtimeOnly("org.junit.jupiter:junit-jupiter-engine:5.10.0") implementation("org.junit.jupiter:junit-jupiter-params:5.10.0") } } + // jvmAndAndroidMain is an intermediate source set between commonMain and both jvmMain/androidMain. + // It holds the reflection-based serde entry points (zSerialize/zDeserialize, + // KTypeSerde), which need `typeOf()` and KClass.qualifiedName. Both are + // served by the JVM stdlib's kotlin.jvm.internal.Reflection — no kotlin-reflect + // artifact — but only on JVM and Android (ART). + val jvmAndAndroidMain by creating { dependsOn(commonMain) } + val jvmMain by getting { + dependsOn(jvmAndAndroidMain) + } if (androidEnabled) { + val androidMain by getting { dependsOn(jvmAndAndroidMain) } val androidUnitTest by getting { dependencies { implementation(kotlin("test-junit")) } } } - val jvmMain by getting { - if (isRemotePublication) { - // The line below is intended to load the native libraries that are crosscompiled on GitHub actions when publishing a JVM package. - resources.srcDir("../jni-libs").include("*/**") - } else { - resources.srcDir("../zenoh-jni/target/$buildMode").include(arrayListOf("*.dylib", "*.so", "*.dll")) - } - } - - val jvmTest by getting { - resources.srcDir("../zenoh-jni/target/$buildMode").include(arrayListOf("*.dylib", "*.so", "*.dll")) - } } val javadocJar by tasks.registering(Jar::class) { @@ -164,65 +164,6 @@ tasks.withType().configureEach { dependsOn(tasks.withType()) } -tasks.withType { - doFirst { - // The line below is added for the Android Unit tests which are equivalent to the JVM tests. - // For them to work we need to specify the path to the native library as a system property and not as a jvmArg. - systemProperty("java.library.path", "../zenoh-jni/target/$buildMode") - } -} - -tasks.whenObjectAdded { - if ((this.name == "mergeDebugJniLibFolders" || this.name == "mergeReleaseJniLibFolders")) { - this.dependsOn("cargoBuild") - } -} - -tasks.named("compileKotlinJvm") { - dependsOn("buildZenohJni") -} - -tasks.register("buildZenohJni") { - doLast { - if (!isRemotePublication) { - // This is intended for local publications. For publications done through GitHub workflows, - // the zenoh-jni build is achieved and loaded differently from the CI - buildZenohJNI(buildMode) - } - } -} - -fun buildZenohJNI(mode: BuildMode = BuildMode.DEBUG) { - val cargoCommand = mutableListOf("cargo", "build") - - if (mode == BuildMode.RELEASE) { - cargoCommand.add("--release") - } - - val result = project.exec { - commandLine(*(cargoCommand.toTypedArray()), "--manifest-path", "../zenoh-jni/Cargo.toml") - } - - if (result.exitValue != 0) { - throw GradleException("Failed to build Zenoh-JNI.") - } - - Thread.sleep(1000) -} - -enum class BuildMode { - DEBUG { - override fun toString(): String { - return "debug" - } - }, - RELEASE { - override fun toString(): String { - return "release" - } - } -} - fun Project.configureAndroid() { extensions.configure("android") { namespace = "io.zenoh" @@ -260,20 +201,3 @@ fun Project.configureAndroid() { } } } - -fun Project.configureCargo() { - extensions.configure("cargo") { - pythonCommand = "python3" - module = "../zenoh-jni" - libname = "zenoh-jni" - targetIncludes = arrayOf("libzenoh_jni.so") - targetDirectory = "../zenoh-jni/target/" - profile = "release" - targets = arrayListOf( - "arm", - "arm64", - "x86", - "x86_64", - ) - } -} diff --git a/zenoh-kotlin/src/androidMain/kotlin/io.zenoh/Zenoh.kt b/zenoh-kotlin/src/androidMain/kotlin/io.zenoh/Zenoh.kt deleted file mode 100644 index 74d42e513..000000000 --- a/zenoh-kotlin/src/androidMain/kotlin/io.zenoh/Zenoh.kt +++ /dev/null @@ -1,27 +0,0 @@ -// -// Copyright (c) 2023 ZettaScale Technology -// -// This program and the accompanying materials are made available under the -// terms of the Eclipse Public License 2.0 which is available at -// http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 -// which is available at https://www.apache.org/licenses/LICENSE-2.0. -// -// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 -// -// Contributors: -// ZettaScale Zenoh Team, -// - -package io.zenoh - -/** - * Static singleton class to load the Zenoh native library once and only once, as well as the logger in function of the - * log level configuration. - */ -internal actual object ZenohLoad { - private const val ZENOH_LIB_NAME = "zenoh_jni" - - init { - System.loadLibrary(ZENOH_LIB_NAME) - } -} diff --git a/zenoh-kotlin/src/commonMain/kotlin/io/zenoh/Config.kt b/zenoh-kotlin/src/commonMain/kotlin/io/zenoh/Config.kt index a6f69f44a..4cc97e966 100644 --- a/zenoh-kotlin/src/commonMain/kotlin/io/zenoh/Config.kt +++ b/zenoh-kotlin/src/commonMain/kotlin/io/zenoh/Config.kt @@ -14,7 +14,11 @@ package io.zenoh -import io.zenoh.jni.JNIConfig +import io.zenoh.exceptions.ZError +import io.zenoh.exceptions.throwZError0 +import io.zenoh.exceptions.zCall +import io.zenoh.exceptions.zCallUnit +import io.zenoh.jni.config.Config as JniConfig import java.io.File import java.nio.file.Path import kotlinx.serialization.json.JsonElement @@ -32,6 +36,10 @@ import kotlinx.serialization.json.JsonElement * * A default configuration can be loaded using [Config.default]. * + * The native configuration a Config wraps is cloned by [Zenoh.open] (so the + * Config stays reusable) and borrowed by [Zenoh.scout]; a Config that is + * never used is released by the garbage-collection backstop. + * * ## Examples: * * ### Loading default config: @@ -122,7 +130,7 @@ import kotlinx.serialization.json.JsonElement * Visit the [default configuration](https://github.com/eclipse-zenoh/zenoh/blob/main/DEFAULT_CONFIG.json5) for more * information on the Zenoh config parameters. */ -class Config internal constructor(internal val jniConfig: JNIConfig) { +class Config internal constructor(internal val jniConfig: JniConfig) { companion object { @@ -132,7 +140,7 @@ class Config internal constructor(internal val jniConfig: JNIConfig) { * Returns the default config. */ fun default(): Config { - return JNIConfig.loadDefaultConfig() + return Config(JniConfig.newDefault(throwZError0)) } /** @@ -142,9 +150,7 @@ class Config internal constructor(internal val jniConfig: JNIConfig) { * Note the format is determined after the file extension. * @return A result with the [Config]. */ - fun fromFile(file: File): Result { - return JNIConfig.loadConfigFile(file) - } + fun fromFile(file: File): Result = fromFile(file.toPath()) /** * Loads the configuration from the [Path] specified. @@ -153,9 +159,10 @@ class Config internal constructor(internal val jniConfig: JNIConfig) { * Note the format is determined after the file extension. * @return A result with the [Config]. */ - fun fromFile(path: Path): Result { - return JNIConfig.loadConfigFile(path) - } + fun fromFile(path: Path): Result = + zCall({ JniConfig(0L) }) { onBindingError, onError -> + JniConfig.newFromFile(path.toString(), onBindingError, onError) + }.map { Config(it) } /** * Loads the configuration from json-formatted string. @@ -189,9 +196,13 @@ class Config internal constructor(internal val jniConfig: JNIConfig) { * @param config Json formatted config. * @return A result with the [Config]. */ - fun fromJson(config: String): Result { - return JNIConfig.loadJsonConfig(config) - } + // Parsed by the JSON5 reader: base zenoh's `Config` has no `from_json`, + // and JSON is a subset of JSON5, so every input accepted here before is + // still accepted and parses to the same config. + fun fromJson(config: String): Result = + zCall({ JniConfig(0L) }) { onBindingError, onError -> + JniConfig.newFromJson5(config, onBindingError, onError) + }.map { Config(it) } /** * Loads the configuration from json5-formatted string. @@ -225,9 +236,10 @@ class Config internal constructor(internal val jniConfig: JNIConfig) { * @param config Json5 formatted config * @return A result with the [Config]. */ - fun fromJson5(config: String): Result { - return JNIConfig.loadJson5Config(config) - } + fun fromJson5(config: String): Result = + zCall({ JniConfig(0L) }) { onBindingError, onError -> + JniConfig.newFromJson5(config, onBindingError, onError) + }.map { Config(it) } /** * Loads the configuration from yaml-formatted string. @@ -257,31 +269,28 @@ class Config internal constructor(internal val jniConfig: JNIConfig) { * @param config Yaml formatted config * @return A result with the [Config]. */ - fun fromYaml(config: String): Result { - return JNIConfig.loadYamlConfig(config) - } + fun fromYaml(config: String): Result = + zCall({ JniConfig(0L) }) { onBindingError, onError -> + JniConfig.newFromYaml(config, onBindingError, onError) + }.map { Config(it) } /** * Loads the configuration from the [jsonElement] specified. * * @param jsonElement The zenoh config as a [JsonElement]. */ - fun fromJsonElement(jsonElement: JsonElement): Result { - return JNIConfig.loadJsonConfig(jsonElement.toString()) - } + fun fromJsonElement(jsonElement: JsonElement): Result = + fromJson(jsonElement.toString()) /** * Loads the configuration from the env variable [CONFIG_ENV]. * * @return A result with the config. */ - fun fromEnv(): Result = runCatching { + fun fromEnv(): Result { val envValue = System.getenv(CONFIG_ENV) - if (envValue != null) { - return fromFile(File(envValue)) - } else { - throw Exception("Couldn't load env variable: $CONFIG_ENV.") - } + ?: return Result.failure(ZError("Couldn't load env variable: $CONFIG_ENV.")) + return fromFile(File(envValue)) } } @@ -289,7 +298,7 @@ class Config internal constructor(internal val jniConfig: JNIConfig) { * Returns the json value associated to the [key]. */ fun getJson(key: String): Result { - return jniConfig.getJson(key) + return zCall({ "" }) { onBindingError, onError -> jniConfig.getJson(key, onBindingError, onError) } } /** @@ -313,10 +322,6 @@ class Config internal constructor(internal val jniConfig: JNIConfig) { * @return A result with the status of the operation. */ fun insertJson5(key: String, value: String): Result { - return jniConfig.insertJson5(key, value) - } - - protected fun finalize() { - jniConfig.close() + return zCallUnit { onBindingError, onError -> jniConfig.insertJson5(key, value, onBindingError, onError) } } } diff --git a/zenoh-kotlin/src/commonMain/kotlin/io/zenoh/FlatCallbacks.kt b/zenoh-kotlin/src/commonMain/kotlin/io/zenoh/FlatCallbacks.kt new file mode 100644 index 000000000..99c35829e --- /dev/null +++ b/zenoh-kotlin/src/commonMain/kotlin/io/zenoh/FlatCallbacks.kt @@ -0,0 +1,117 @@ +// +// Copyright (c) 2026 ZettaScale Technology +// +// This program and the accompanying materials are made available under the +// terms of the Eclipse Public License 2.0 which is available at +// http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 +// which is available at https://www.apache.org/licenses/LICENSE-2.0. +// +// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 +// +// Contributors: +// ZettaScale Zenoh Team, +// + +package io.zenoh + +import io.zenoh.bytes.Encoding +import io.zenoh.bytes.ZBytes +import io.zenoh.config.EntityGlobalId +import io.zenoh.config.WhatAmI +import io.zenoh.config.ZenohId +import io.zenoh.pubsub.Miss +import io.zenoh.query.Query +import io.zenoh.query.Reply +import io.zenoh.query.ReplyError +import io.zenoh.sample.Sample +import io.zenoh.scouting.Hello + +/** + * Adapters from the generated JNI callback lambdas to a plain + * `(SdkType) -> Unit`. A callback argument whose type has a canonical output + * is decomposed natively — a `Sample`, `Query`, `Hello` or `Reply` arrives + * as its leaves in ONE JNI crossing (no per-field accessor calls) and the + * SDK object graph is built from them via [Sample.fromParts] / + * [Query.fromParts] / the [Hello] constructor. A `Query` additionally delivers + * its owned handle as the final leaf so the [Query] can reply. A `Reply` is a + * sum type decomposed as a product: both arms' leaves are always in the + * signature and the not-taken arm's are null — `isOk` discriminates. + */ + +internal fun sampleCallbackOf( + f: (Sample) -> Unit +): io.zenoh.jni.sample.SampleCallback = + io.zenoh.jni.sample.SampleCallback { keStr, payloadH, encId, encSchema, kindInt, timestamp, express, prioInt, ccInt, attachH, reliabilityInt, sourceInfo -> + f(Sample.fromParts(keStr, payloadH, encId, encSchema, kindInt, timestamp, express, prioInt, ccInt, attachH, reliabilityInt, sourceInfo)) + } + +internal fun sampleMissCallbackOf( + f: (Miss) -> Unit +): io.zenoh.jni.pubsub.MissCallback = + io.zenoh.jni.pubsub.MissCallback { miss -> + f(Miss(EntityGlobalId(ZenohId(miss.source.zid.bytes), miss.source.eid.toUInt()), miss.nb)) + } + +internal fun queryCallbackOf( + f: (Query) -> Unit +): io.zenoh.jni.query.QueryCallback = + io.zenoh.jni.query.QueryCallback { keStr, parameters, payloadH, encId, encSchema, attachH, acceptsReplies, zq -> + // The decomposed leaves — including the key-expr string and + // the owned `zq` query handle — are folded into the SDK [Query]. Unlike + // the decomposed read-only types (Sample/Hello), the query OWNS `zq` and + // is NOT closed here: it may be retained beyond this callback (e.g. put + // on a channel by a queue handler) and replied to later. The native query + // is dropped when it is replied to (see [Query.reply]) or when [Query] is + // closed — that drop is what finalizes the querier's get. + val query = try { + Query.fromParts(keStr, parameters, payloadH, encId, encSchema, attachH, acceptsReplies, zq) + } catch (t: Throwable) { + // Defense in depth: the leaves carry remote (attacker-controlled) + // data, and fromParts must be total on it — but if decomposition + // ever throws, free the owned native leaves (payload/attachment + // have no GC backstop) and finalize the query so the remote get + // completes, instead of leaking them. The rethrown exception is + // swallowed by the native callback bridge, by design. + payloadH?.close() + attachH?.close() + zq.close() + throw t + } + f(query) + } + +internal fun replyCallbackOf( + f: (Reply) -> Unit +): io.zenoh.jni.query.ReplyCallback = + io.zenoh.jni.query.ReplyCallback { replier, isOk, keStr, payloadH, encId, encSchema, kindInt, timestamp, express, prioInt, ccInt, attachH, reliabilityInt, sourceInfo, errPayloadH, errEncId, errEncSchema -> + // The replier's id arrives as one whole `EntityGlobalId` value rather + // than as a (zid, eid) pair, so its optionality is the value's own. + val replierId = replier?.let { EntityGlobalId(ZenohId(it.zid.bytes), it.eid.toUInt()) } + f( + if (isOk) { + Reply( + replierId, + Result.success( + Sample.fromParts(keStr!!, payloadH!!, encId!!, encSchema, kindInt!!, timestamp, express!!, prioInt!!, ccInt!!, attachH, reliabilityInt!!, sourceInfo) + ) + ) + } else { + Reply( + replierId, + Result.failure( + ReplyError( + ZBytes.fromHandle(errPayloadH!!), + errEncId?.let { Encoding(it, schema = errEncSchema?.toString(Charsets.UTF_8)) } ?: Encoding.default() + ) + ) + ) + } + ) + } + +internal fun helloCallbackOf( + f: (Hello) -> Unit +): io.zenoh.jni.scouting.HelloCallback = + io.zenoh.jni.scouting.HelloCallback { whatamiInt, zid, locators -> + f(Hello(WhatAmI.fromInt(whatamiInt), ZenohId(zid.bytes), locators)) + } diff --git a/zenoh-kotlin/src/commonMain/kotlin/io/zenoh/FlatEnums.kt b/zenoh-kotlin/src/commonMain/kotlin/io/zenoh/FlatEnums.kt new file mode 100644 index 000000000..7deaac4be --- /dev/null +++ b/zenoh-kotlin/src/commonMain/kotlin/io/zenoh/FlatEnums.kt @@ -0,0 +1,46 @@ +// +// Copyright (c) 2026 ZettaScale Technology +// +// This program and the accompanying materials are made available under the +// terms of the Eclipse Public License 2.0 which is available at +// http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 +// which is available at https://www.apache.org/licenses/LICENSE-2.0. +// +// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 +// +// Contributors: +// ZettaScale Zenoh Team, +// + +package io.zenoh + +import io.zenoh.qos.CongestionControl +import io.zenoh.qos.Priority +import io.zenoh.qos.Reliability +import io.zenoh.query.ConsolidationMode +import io.zenoh.query.QueryTarget +import io.zenoh.query.ReplyKeyExpr + +/** + * Adapters from the SDK enums to the typed enums of the generated flat + * bindings. The SDK enums carry the flat wire value in their `value` field, + * so the mapping is a table lookup — no JNI crossing. + */ + +internal val CongestionControl.jni: io.zenoh.jni.qos.CongestionControl + get() = io.zenoh.jni.qos.CongestionControl.fromInt(value) + +internal val Priority.jni: io.zenoh.jni.qos.Priority + get() = io.zenoh.jni.qos.Priority.fromInt(value) + +internal val Reliability.jni: io.zenoh.jni.qos.Reliability + get() = io.zenoh.jni.qos.Reliability.fromInt(value) + +internal val QueryTarget.jni: io.zenoh.jni.query.QueryTarget + get() = io.zenoh.jni.query.QueryTarget.fromInt(value) + +internal val ConsolidationMode.jni: io.zenoh.jni.query.ConsolidationMode + get() = io.zenoh.jni.query.ConsolidationMode.fromInt(value) + +internal val ReplyKeyExpr.jni: io.zenoh.jni.query.ReplyKeyExpr + get() = io.zenoh.jni.query.ReplyKeyExpr.fromInt(value) diff --git a/zenoh-kotlin/src/commonMain/kotlin/io/zenoh/Logger.kt b/zenoh-kotlin/src/commonMain/kotlin/io/zenoh/Logger.kt index 540e004cd..4c413175b 100644 --- a/zenoh-kotlin/src/commonMain/kotlin/io/zenoh/Logger.kt +++ b/zenoh-kotlin/src/commonMain/kotlin/io/zenoh/Logger.kt @@ -14,7 +14,8 @@ package io.zenoh -import io.zenoh.exceptions.ZError +import io.zenoh.exceptions.zCallUnit0 +import io.zenoh.jni.logger.initAndroidLogs /** Logger class to redirect the Rust logs from Zenoh to the kotlin environment. */ internal class Logger { @@ -23,17 +24,13 @@ internal class Logger { internal const val LOG_ENV: String = "RUST_LOG" - fun start(filter: String) = runCatching { - startLogsViaJNI(filter) - } - /** * Redirects the rust logs either to logcat for Android systems or to the standard output (for non-android * systems). * * See https://docs.rs/env_logger/latest/env_logger/index.html for accepted filter format. */ - @Throws(ZError::class) - private external fun startLogsViaJNI(filter: String) + fun start(filter: String): Result = + zCallUnit0 { initAndroidLogs(filter, it) } } } diff --git a/zenoh-kotlin/src/commonMain/kotlin/io/zenoh/Session.kt b/zenoh-kotlin/src/commonMain/kotlin/io/zenoh/Session.kt index c3ad5ccc9..07d847776 100644 --- a/zenoh-kotlin/src/commonMain/kotlin/io/zenoh/Session.kt +++ b/zenoh-kotlin/src/commonMain/kotlin/io/zenoh/Session.kt @@ -16,20 +16,48 @@ package io.zenoh import io.zenoh.annotations.Unstable import io.zenoh.exceptions.ZError +import io.zenoh.exceptions.zCall +import io.zenoh.exceptions.zCall0 +import io.zenoh.exceptions.zCallUnit import io.zenoh.handlers.Callback import io.zenoh.handlers.ChannelHandler import io.zenoh.handlers.Handler -import io.zenoh.jni.JNISession +import io.zenoh.jni.config.Config as JniConfig +import io.zenoh.jni.config.ZenohId as JniZenohId +import io.zenoh.jni.keyexpr.KeyExpr as JniKeyExpr +import io.zenoh.jni.pubsub.AdvancedPublisher as JniAdvancedPublisher +import io.zenoh.jni.pubsub.AdvancedSubscriber as JniAdvancedSubscriber +import io.zenoh.jni.pubsub.CacheConfig as JniCacheConfig +import io.zenoh.jni.pubsub.HistoryConfig as JniHistoryConfig +import io.zenoh.jni.pubsub.MissDetectionConfig as JniMissDetectionConfig +import io.zenoh.jni.pubsub.Publisher as JniPublisher +import io.zenoh.jni.pubsub.RecoveryConfig as JniRecoveryConfig +import io.zenoh.jni.query.Selector as JniSelector +import io.zenoh.jni.pubsub.RecoveryMode as JniRecoveryMode +import io.zenoh.jni.pubsub.RepliesConfig as JniRepliesConfig +import io.zenoh.jni.pubsub.Subscriber as JniSubscriber +import io.zenoh.jni.query.Querier as JniQuerier +import io.zenoh.jni.query.Queryable as JniQueryable +import io.zenoh.jni.session.Session as JniSession import io.zenoh.keyexpr.KeyExpr +import io.zenoh.keyexpr.jniHandle +import io.zenoh.keyexpr.jniSel +import io.zenoh.keyexpr.jniStr import io.zenoh.bytes.Encoding +import io.zenoh.bytes.jniHandle +import io.zenoh.bytes.jniId +import io.zenoh.bytes.jniSchema +import io.zenoh.bytes.jniSel import io.zenoh.qos.QoS import io.zenoh.bytes.IntoZBytes import io.zenoh.bytes.ZBytes import io.zenoh.config.ZenohId import io.zenoh.ext.CacheConfig +import io.zenoh.ext.HeartbeatMode import io.zenoh.ext.HistoryConfig import io.zenoh.ext.MissDetectionConfig import io.zenoh.ext.RecoveryConfig +import io.zenoh.ext.RecoveryMode import io.zenoh.liveliness.Liveliness import io.zenoh.pubsub.AdvancedPublisher import io.zenoh.pubsub.AdvancedSubscriber @@ -39,9 +67,9 @@ import io.zenoh.pubsub.Put import io.zenoh.query.* import io.zenoh.query.Query import io.zenoh.query.Queryable -import io.zenoh.sample.Sample import io.zenoh.query.Selector import io.zenoh.qos.Reliability +import io.zenoh.sample.Sample import io.zenoh.session.SessionDeclaration import io.zenoh.session.SessionInfo import io.zenoh.pubsub.Subscriber @@ -67,7 +95,7 @@ import java.time.Duration */ class Session private constructor(private val config: Config) : AutoCloseable { - internal var jniSession: JNISession? = null + internal var jniSession: JniSession? = null // AdvancedSubscribers, Subscribers and Queryables that keep running despite losing references to them. private var strongDeclarations = mutableListOf() @@ -200,8 +228,9 @@ class Session private constructor(private val config: Config) : AutoCloseable { sampleMissDetection: MissDetectionConfig? = null, publisherDetection: Boolean = false ): Result { - return resolveAdvancedPublisher(keyExpr, qos, encoding, reliability, - cacheConfig, sampleMissDetection, publisherDetection) + return resolveAdvancedPublisher( + keyExpr, qos, encoding, reliability, cacheConfig, sampleMissDetection, publisherDetection + ) } /** @@ -272,8 +301,9 @@ class Session private constructor(private val config: Config) : AutoCloseable { val resolvedOnClose = fun() { onClose?.invoke() } - return resolveAdvancedSubscriber(keyExpr, historyConfig, recoveryConfig, subscriberDetection, - callback, resolvedOnClose, Unit) + return resolveAdvancedSubscriber( + keyExpr, historyConfig, recoveryConfig, subscriberDetection, callback, resolvedOnClose, Unit + ) } /** @@ -370,8 +400,9 @@ class Session private constructor(private val config: Config) : AutoCloseable { onClose?.invoke() } val callback = Callback { t: Sample -> handler.handle(t) } - return resolveAdvancedSubscriber(keyExpr, historyConfig, recoveryConfig, subscriberDetection, - callback, resolvedOnClose, handler.receiver()) + return resolveAdvancedSubscriber( + keyExpr, historyConfig, recoveryConfig, subscriberDetection, callback, resolvedOnClose, handler.receiver() + ) } /** @@ -458,8 +489,9 @@ class Session private constructor(private val config: Config) : AutoCloseable { onClose?.invoke() } val callback = Callback { t: Sample -> channelHandler.handle(t) } - return resolveAdvancedSubscriber(keyExpr, historyConfig, recoveryConfig, subscriberDetection, - callback, resolvedOnClose, channelHandler.receiver()) + return resolveAdvancedSubscriber( + keyExpr, historyConfig, recoveryConfig, subscriberDetection, callback, resolvedOnClose, channelHandler.receiver() + ) } /** @@ -653,9 +685,12 @@ class Session private constructor(private val config: Config) : AutoCloseable { * @return A result with the declared key expression. */ fun declareKeyExpr(keyExpr: String): Result { - return jniSession?.run { - declareKeyExpr(keyExpr).onSuccess { strongDeclarations.add(it) } - } ?: Result.failure(sessionClosedException) + val session = jniSession ?: return Result.failure(sessionClosedException) + return zCall({ JniKeyExpr(0L) }) { onBindingError, onError -> + session.declareKeyexpr(keyExpr, onBindingError, onError) + } + .map { KeyExpr(keyExpr, it) } + .onSuccess { strongDeclarations.add(it) } } /** @@ -668,9 +703,20 @@ class Session private constructor(private val config: Config) : AutoCloseable { * @return A result with the status of the undeclare operation. */ fun undeclare(keyExpr: KeyExpr): Result { - return jniSession?.run { - undeclareKeyExpr(keyExpr) - } ?: Result.failure(sessionClosedException) + val session = jniSession ?: return Result.failure(sessionClosedException) + val handle = keyExpr.jniKeyExpr + ?: return Result.failure(ZError("Key expression is not declared through a session.")) + return zCallUnit { onBindingError, onError -> session.undeclareKeyexpr(handle, onBindingError, onError) } + .also { + // The generated wrapper consumes the handle even when the + // native undeclare fails (the Rust side takes it by value); + // on a pre-call guard failure it instead stays live. Either + // way the declared handle must never be selected again: close + // it (a no-op if consumed) and detach it, degrading the + // KeyExpr to its string form. + handle.close() + keyExpr.jniKeyExpr = null + } } /** @@ -1075,51 +1121,108 @@ class Session private constructor(private val config: Config) : AutoCloseable { encoding: Encoding, reliability: Reliability ): Result { - return jniSession?.run { - declarePublisher(keyExpr, qos, encoding, reliability).onSuccess { weakDeclarations.add(WeakReference(it)) } - } ?: Result.failure(sessionClosedException) + val session = jniSession ?: return Result.failure(sessionClosedException) + return zCall({ JniPublisher(0L) }) { onBindingError, onError -> + session.declarePublisher( + keyExpr.jniSel, keyExpr.jniStr, keyExpr.cloneHandle(), + encoding.jniSel, encoding.jniId, encoding.jniSchema, encoding.jniHandle, + qos.congestionControl.jni, qos.priority.jni, qos.express, reliability.jni, + onBindingError, onError + ) + }.map { Publisher(keyExpr, qos, encoding, it) } + .onSuccess { weakDeclarations.add(WeakReference(it)) } } - @Unstable private fun resolveAdvancedPublisher( keyExpr: KeyExpr, qos: QoS, encoding: Encoding, reliability: Reliability, - cacheConfig: CacheConfig? = null, - sampleMissDetection: MissDetectionConfig? = null, - publisherDetection: Boolean = false + cacheConfig: CacheConfig?, + sampleMissDetection: MissDetectionConfig?, + publisherDetection: Boolean ): Result { - return jniSession?.run { - declareAdvancedPublisher(keyExpr, qos, encoding, reliability, - cacheConfig, sampleMissDetection, publisherDetection).onSuccess { weakDeclarations.add(WeakReference(it)) } - } ?: Result.failure(sessionClosedException) + val session = jniSession ?: return Result.failure(sessionClosedException) + // Lower the pure-Kotlin config holders to the generated data-class holders. + val missDetection = sampleMissDetection?.let { + when (val heartbeat = it.heartbeat) { + is HeartbeatMode.PeriodicHeartbeat -> JniMissDetectionConfig(heartbeat.milliseconds.toULong(), false) + is HeartbeatMode.SporadicHeartbeat -> JniMissDetectionConfig(heartbeat.milliseconds.toULong(), true) + null -> JniMissDetectionConfig(null, false) + } + } + val cache = cacheConfig?.let { + JniCacheConfig( + it.maxSamples.toULong(), + JniRepliesConfig(it.repliesQoS.priority.jni, it.repliesQoS.congestionControl.jni, it.repliesQoS.express) + ) + } + return zCall({ JniAdvancedPublisher(0L) }) { onBindingError, onError -> + session.declareAdvancedPublisher( + keyExpr.jniSel, keyExpr.jniStr, keyExpr.cloneHandle(), + encoding.jniSel, encoding.jniId, encoding.jniSchema, encoding.jniHandle, + qos.congestionControl.jni, qos.priority.jni, qos.express, reliability.jni, + missDetection, publisherDetection, cache, + onBindingError, onError + ) + }.map { AdvancedPublisher(keyExpr, qos, encoding, it) } + .onSuccess { weakDeclarations.add(WeakReference(it)) } } - private fun resolveSubscriber( + private fun resolveAdvancedSubscriber( keyExpr: KeyExpr, + historyConfig: HistoryConfig?, + recoveryConfig: RecoveryConfig?, + subscriberDetection: Boolean, callback: Callback, onClose: () -> Unit, - receiver: R - ): Result> { - return jniSession?.run { - declareSubscriber(keyExpr, callback, onClose, receiver).onSuccess { strongDeclarations.add(it) } - } ?: Result.failure(sessionClosedException) + receiver: R, + ): Result> { + val session = jniSession ?: return Result.failure(sessionClosedException) + // Lower the pure-Kotlin config holders to the generated data-class holders. + val history = historyConfig?.let { + JniHistoryConfig(it.detectLatePublishers, it.maxSamples?.toULong(), it.maxAgeSeconds) + } + // The recovery mode is a data-carrying enum now, so the choice and its + // payload are one value rather than a (period, flag) pair in which + // only one of the two was ever meaningful. `retentionPeriod` is a + // separate field with no counterpart on the SDK config, so it stays + // absent, as it effectively was before. + val recovery = recoveryConfig?.let { + val mode = when (val m = it.mode) { + is RecoveryMode.PeriodicQuery -> JniRecoveryMode.PeriodicQueries(m.milliseconds.toULong()) + RecoveryMode.Heartbeat -> JniRecoveryMode.Heartbeat + } + JniRecoveryConfig(mode, null) + } + return zCall({ JniAdvancedSubscriber(0L) }) { onBindingError, onError -> + session.declareAdvancedSubscriber( + keyExpr.jniSel, keyExpr.jniStr, keyExpr.cloneHandle(), + sampleCallbackOf { callback.run(it) }, + { onClose() }, + history, recovery, null, subscriberDetection, + onBindingError, onError + ) + }.map { AdvancedSubscriber(keyExpr, receiver, it) } + .onSuccess { strongDeclarations.add(it) } } - @Unstable - private fun resolveAdvancedSubscriber( + private fun resolveSubscriber( keyExpr: KeyExpr, - history: HistoryConfig?, - recovery: RecoveryConfig?, - subscriberDetection: Boolean, callback: Callback, onClose: () -> Unit, receiver: R - ): Result> { - return jniSession?.run { - declareAdvancedSubscriber(keyExpr, history, recovery, subscriberDetection, callback, onClose, receiver).onSuccess { strongDeclarations.add(it) } - } ?: Result.failure(sessionClosedException) + ): Result> { + val session = jniSession ?: return Result.failure(sessionClosedException) + return zCall({ JniSubscriber(0L) }) { onBindingError, onError -> + session.declareSubscriber( + keyExpr.jniSel, keyExpr.jniStr, keyExpr.cloneHandle(), + sampleCallbackOf { callback.run(it) }, + { onClose() }, + onBindingError, onError + ) + }.map { Subscriber(keyExpr, receiver, it) } + .onSuccess { strongDeclarations.add(it) } } private fun resolveQueryable( @@ -1129,9 +1232,17 @@ class Session private constructor(private val config: Config) : AutoCloseable { receiver: R, complete: Boolean ): Result> { - return jniSession?.run { - declareQueryable(keyExpr, callback, onClose, receiver, complete).onSuccess { strongDeclarations.add(it) } - } ?: Result.failure(sessionClosedException) + val session = jniSession ?: return Result.failure(sessionClosedException) + return zCall({ JniQueryable(0L) }) { onBindingError, onError -> + session.declareQueryable( + keyExpr.jniSel, keyExpr.jniStr, keyExpr.cloneHandle(), + complete, + queryCallbackOf { callback.run(it) }, + { onClose() }, + onBindingError, onError + ) + }.map { Queryable(keyExpr, receiver, it) } + .onSuccess { strongDeclarations.add(it) } } private fun resolveQuerier( @@ -1142,9 +1253,17 @@ class Session private constructor(private val config: Config) : AutoCloseable { timeout: Duration, acceptReplies: ReplyKeyExpr ): Result { - return jniSession?.run { - declareQuerier(keyExpr, target, consolidation, qos, timeout, acceptReplies).onSuccess { weakDeclarations.add(WeakReference(it)) } - } ?: Result.failure(sessionClosedException) + val session = jniSession ?: return Result.failure(sessionClosedException) + return zCall({ JniQuerier(0L) }) { onBindingError, onError -> + session.declareQuerier( + keyExpr.jniSel, keyExpr.jniStr, keyExpr.cloneHandle(), + target.jni, consolidation.jni, + qos.congestionControl.jni, qos.priority.jni, qos.express, + timeout.toMillis(), acceptReplies.jni, + onBindingError, onError + ) + }.map { Querier(keyExpr, qos, it) } + .onSuccess { weakDeclarations.add(WeakReference(it)) } } private fun resolveGet( @@ -1161,49 +1280,83 @@ class Session private constructor(private val config: Config) : AutoCloseable { qos: QoS, acceptReplies: ReplyKeyExpr ): Result { - return jniSession?.run { - performGet( - selector, - callback, - onClose, - receiver, - timeout, - target, - consolidation, - payload, - encoding, - attachment, - qos, - acceptReplies + val session = jniSession ?: return Result.failure(sessionClosedException) + return zCallUnit { onBindingError, onError -> + session.get( + // `session_get` takes the whole selector by value: the key + // expression goes in as an owned handle, and the parameters + // ride along inside it rather than as a separate argument. + JniSelector(selector.keyExpr.intoJniHandle(), selector.parameters?.toString() ?: ""), + timeout.toMillis(), + target.jni, consolidation.jni, acceptReplies.jni, + qos.congestionControl.jni, qos.priority.jni, qos.express, + payload?.into()?.bytes, + encoding.jniSel, encoding.jniId, encoding.jniSchema, encoding.jniHandle, + attachment?.into()?.bytes, + replyCallbackOf { callback.run(it) }, + { onClose() }, + onBindingError, onError ) - } ?: Result.failure(sessionClosedException) + }.map { receiver } } - private fun resolvePut(keyExpr: KeyExpr, put: Put): Result = runCatching { - jniSession?.run { performPut(keyExpr, put) } + private fun resolvePut(keyExpr: KeyExpr, put: Put): Result { + val session = jniSession ?: return Result.failure(sessionClosedException) + return zCallUnit { onBindingError, onError -> + session.put( + keyExpr.jniSel, keyExpr.jniStr, keyExpr.jniHandle, + put.payload.bytes, + put.encoding.jniSel, put.encoding.jniId, put.encoding.jniSchema, put.encoding.jniHandle, + put.qos.congestionControl.jni, put.qos.priority.jni, put.qos.express, + put.attachment?.bytes, + put.reliability.jni, + onBindingError, onError + ) + } } - private fun resolveDelete(keyExpr: KeyExpr, delete: Delete): Result = runCatching { - jniSession?.run { performDelete(keyExpr, delete) } + private fun resolveDelete(keyExpr: KeyExpr, delete: Delete): Result { + val session = jniSession ?: return Result.failure(sessionClosedException) + return zCallUnit { onBindingError, onError -> + session.delete( + keyExpr.jniSel, keyExpr.jniStr, keyExpr.jniHandle, + delete.qos.congestionControl.jni, delete.qos.priority.jni, delete.qos.express, + delete.attachment?.bytes, + delete.reliability.jni, + onBindingError, onError + ) + } } internal fun zid(): Result { - return jniSession?.zid() ?: Result.failure(sessionClosedException) + val session = jniSession ?: return Result.failure(sessionClosedException) + return zCall0({ JniZenohId(ByteArray(0)) }) { session.getZid(it) } + .map { ZenohId(it.bytes) } } internal fun getPeersId(): Result> { - return jniSession?.peersZid() ?: Result.failure(sessionClosedException) + val session = jniSession ?: return Result.failure(sessionClosedException) + return zCall0({ emptyList() }) { session.getPeersZid(it) } + .map { ids -> ids.map { ZenohId(it.bytes) } } } internal fun getRoutersId(): Result> { - return jniSession?.routersZid() ?: Result.failure(sessionClosedException) + val session = jniSession ?: return Result.failure(sessionClosedException) + return zCall0({ emptyList() }) { session.getRoutersZid(it) } + .map { ids -> ids.map { ZenohId(it.bytes) } } } /** Launches the session through the jni session, returning the [Session] on success. */ - private fun launch(): Result = runCatching { - jniSession = JNISession() - return jniSession!!.open(config) - .map { this@Session } - .onFailure { jniSession = null } + private fun launch(): Result { + // `open` consumes its config; clone so the user's [Config] stays reusable. + val cloned = zCall0({ JniConfig(0L) }) { config.jniConfig.newClone(it) } + .getOrElse { return Result.failure(it) } + return zCall({ JniSession(0L) }) { onBindingError, onError -> + JniSession.open(cloned, onBindingError, onError) + } + .map { + jniSession = it + this@Session + } } } diff --git a/zenoh-kotlin/src/commonMain/kotlin/io/zenoh/Zenoh.kt b/zenoh-kotlin/src/commonMain/kotlin/io/zenoh/Zenoh.kt index 12049903b..2d1deae1b 100644 --- a/zenoh-kotlin/src/commonMain/kotlin/io/zenoh/Zenoh.kt +++ b/zenoh-kotlin/src/commonMain/kotlin/io/zenoh/Zenoh.kt @@ -15,14 +15,15 @@ package io.zenoh import io.zenoh.Logger.Companion.LOG_ENV +import io.zenoh.config.WhatAmI +import io.zenoh.config.WhatAmI.* +import io.zenoh.exceptions.zCall import io.zenoh.handlers.Callback import io.zenoh.handlers.ChannelHandler import io.zenoh.handlers.Handler -import io.zenoh.jni.JNIScout +import io.zenoh.jni.scouting.Scout as JniScout import io.zenoh.scouting.Hello import io.zenoh.scouting.Scout -import io.zenoh.config.WhatAmI -import io.zenoh.config.WhatAmI.* import kotlinx.coroutines.channels.Channel object Zenoh { @@ -52,10 +53,7 @@ object Zenoh { callback: Callback, whatAmI: Set = setOf(Peer, Router), config: Config? = null - ): Result> { - ZenohLoad - return JNIScout.scout(whatAmI = whatAmI, callback = callback, receiver = Unit, onClose = {}, config = config) - } + ): Result> = performScout(callback, {}, Unit, whatAmI, config) /** * Scout for routers and/or peers. @@ -72,16 +70,8 @@ object Zenoh { handler: Handler, whatAmI: Set = setOf(Peer, Router), config: Config? = null - ): Result> { - ZenohLoad - return JNIScout.scout( - whatAmI = whatAmI, - callback = { hello -> handler.handle(hello) }, - receiver = handler.receiver(), - onClose = handler::onClose, - config = config - ) - } + ): Result> = + performScout(handler::handle, handler::onClose, handler.receiver(), whatAmI, config) /** * Scout for routers and/or peers. @@ -99,15 +89,30 @@ object Zenoh { whatAmI: Set = setOf(Peer, Router), config: Config? = null ): Result>> { - ZenohLoad val handler = ChannelHandler(channel) - return JNIScout.scout( - whatAmI = whatAmI, - callback = { hello -> handler.handle(hello) }, - receiver = handler.receiver(), - onClose = handler::onClose, - config = config - ) + return performScout(handler::handle, handler::onClose, handler.receiver(), whatAmI, config) + } + + private fun performScout( + callback: Callback, + onClose: () -> Unit, + receiver: R, + whatAmI: Set, + config: Config? + ): Result> { + return zCall({ JniScout(0L) }) { onBindingError, onError -> + // Argument preparation stays inside the captured block: an empty + // [whatAmI] makes `reduce` throw, which must surface as + // Result.failure (the pre-flat API contract). + val binaryWhatAmI = whatAmI.map { it.value }.reduce { acc, it -> acc or it } + io.zenoh.jni.scouting.scout( + binaryWhatAmI, + config?.jniConfig, + helloCallbackOf { callback.run(it) }, + { onClose() }, + onBindingError, onError + ) + }.map { Scout(receiver, it) } } /** @@ -122,7 +127,6 @@ object Zenoh { fun tryInitLogFromEnv() { val logEnv = System.getenv(LOG_ENV) if (logEnv != null) { - ZenohLoad Logger.start(logEnv) } } @@ -137,15 +141,8 @@ object Zenoh { * @param fallbackFilter: The fallback filter if the `RUST_LOG` environment variable is not set. * @see Logger */ - fun initLogFromEnvOr(fallbackFilter: String): Result = runCatching { - ZenohLoad + fun initLogFromEnvOr(fallbackFilter: String): Result { val logLevelProp = System.getenv(LOG_ENV) - logLevelProp?.let { Logger.start(it) } ?: Logger.start(fallbackFilter) + return Logger.start(logLevelProp ?: fallbackFilter) } } - -/** - * Static singleton class to load the Zenoh native library once and only once, as well as the logger in function of the - * log level configuration. - */ -internal expect object ZenohLoad diff --git a/zenoh-kotlin/src/commonMain/kotlin/io/zenoh/bytes/Encoding.kt b/zenoh-kotlin/src/commonMain/kotlin/io/zenoh/bytes/Encoding.kt index e9c149842..e397b64f2 100644 --- a/zenoh-kotlin/src/commonMain/kotlin/io/zenoh/bytes/Encoding.kt +++ b/zenoh-kotlin/src/commonMain/kotlin/io/zenoh/bytes/Encoding.kt @@ -445,3 +445,25 @@ class Encoding private constructor( return id.hashCode() } } + +// The four slots of the generated encoding selector block (`encodingSel, +// encoding00, encoding01, encoding1`), computed from one optional [Encoding] +// in a single expression per slot so every send call site stays one flat call +// with zero extra JNI crossings. zenoh-kotlin's [Encoding] is a pure JVM +// value, so the handle arm (1) is never taken: absent -> -1, present -> (id, +// schema) built Rust-side inside the same call (arm 0). + +internal val Encoding?.jniSel: Int + get() = if (this == null) -1 else 0 + +internal val Encoding?.jniId: Int? + get() = this?.id + +// A schema is raw bytes on the wire; zenoh transmits it verbatim and does not +// require it to be UTF-8. This SDK's schema is a String, so the transcode +// happens here, at the one point where the value crosses. +internal val Encoding?.jniSchema: ByteArray? + get() = this?.schema?.toByteArray(Charsets.UTF_8) + +internal val Encoding?.jniHandle: io.zenoh.jni.bytes.Encoding? + get() = null diff --git a/zenoh-kotlin/src/commonMain/kotlin/io/zenoh/bytes/ZBytes.kt b/zenoh-kotlin/src/commonMain/kotlin/io/zenoh/bytes/ZBytes.kt index 32a933cc5..9494a77e9 100644 --- a/zenoh-kotlin/src/commonMain/kotlin/io/zenoh/bytes/ZBytes.kt +++ b/zenoh-kotlin/src/commonMain/kotlin/io/zenoh/bytes/ZBytes.kt @@ -14,6 +14,9 @@ package io.zenoh.bytes +import io.zenoh.exceptions.throwZError0 +import io.zenoh.jni.bytes.ZBytes as JniZBytes + /** * ZBytes contains the serialized bytes of user data. * @@ -29,9 +32,54 @@ package io.zenoh.bytes * encouraged to use any data format of their choice like JSON, protobuf, * flatbuffers, etc. * + * # Native memory lifecycle + * + * A ZBytes *created* from user data ([from]) is a plain value — it never + * holds native memory. A *received* ZBytes (a sample's payload or + * attachment, a query payload, a reply) wraps a native buffer that is + * freed automatically on the first [bytes] access (the bytes are copied + * out lazily, once). A received ZBytes whose content is **never read** + * keeps its native buffer allocated: unlike every other handle-owning + * class in this SDK, ZBytes is deliberately NOT covered by the + * garbage-collection backstop — payloads are the per-message hot path, + * and registering a GC cleaner per message measured −23% throughput at + * small payload sizes. In callback-based subscribers/queryables, access + * (or discard) payloads and attachments you care about; unread ones on + * dropped samples are the one place native memory can be retained. + * * See also: [ZBytes examples](https://github.com/eclipse-zenoh/zenoh-kotlin/blob/main/examples/src/main/kotlin/io.zenoh/ZBytes.kt) */ -class ZBytes internal constructor(internal val bytes: ByteArray) : IntoZBytes { +class ZBytes private constructor( + initialBytes: ByteArray?, + private var handle: JniZBytes?, +) : IntoZBytes { + + // Volatile: [bytes] publishes this from inside the lock and reads it from + // outside. A received ZBytes routinely crosses threads (a sample piped + // through a Channel), and without the volatile write a reader could see + // the array reference before the copied contents. + @Volatile + private var eager: ByteArray? = initialBytes + + internal constructor(bytes: ByteArray) : this(bytes, null) + + /** + * The payload bytes. A handle-backed (received) ZBytes materializes them + * LAZILY on first access — one borrow-copy out of the native buffer — + * then closes the native handle (forward-extraction rule: the handle is + * delivered eagerly, the heavy bytes on demand). + */ + internal val bytes: ByteArray + get() = eager ?: synchronized(this) { + eager ?: run { + val h = handle!! + val b = h.toBytes(throwZError0) + eager = b + handle = null + h.close() + b + } + } companion object { @@ -44,8 +92,20 @@ class ZBytes internal constructor(internal val bytes: ByteArray) : IntoZBytes { * Creates a [ZBytes] instance from a [ByteArray]. */ fun from(bytes: ByteArray) = ZBytes(bytes) + + /** Wrap a received owned handle; bytes are read lazily (see [bytes]). */ + internal fun fromHandle(handle: JniZBytes): ZBytes = + ZBytes(null, handle) } + /** + * Builds a fresh native `ZBytes` handle from these bytes. The raw + * payload/attachment parameters take it **by value** (Rust frees it), so + * the caller does not close it. + */ + internal fun toZZBytes(): JniZBytes = + JniZBytes.newFromVec(bytes, throwZError0) + /** Returns the internal byte representation of the [ZBytes]. */ fun toBytes(): ByteArray = bytes diff --git a/zenoh-kotlin/src/commonMain/kotlin/io/zenoh/config/ZenohId.kt b/zenoh-kotlin/src/commonMain/kotlin/io/zenoh/config/ZenohId.kt index d98485148..854db052b 100644 --- a/zenoh-kotlin/src/commonMain/kotlin/io/zenoh/config/ZenohId.kt +++ b/zenoh-kotlin/src/commonMain/kotlin/io/zenoh/config/ZenohId.kt @@ -14,16 +14,20 @@ package io.zenoh.config -import io.zenoh.jni.JNIZenohId +import io.zenoh.jni.config.ZenohId as JniZenohId +import io.zenoh.jni.config.zidString /** * The global unique id of a Zenoh peer. */ data class ZenohId internal constructor(internal val bytes: ByteArray) { - override fun toString(): String { - return JNIZenohId.toStringViaJNI(bytes) - } + /** + * The standard string form, computed in pure Kotlin by the shared + * bindings tier ([zidString]); correspondence with the native formatter + * is verified upstream (`ZenohIdCorrespondenceTest` in zenoh-java). + */ + override fun toString(): String = JniZenohId(bytes).zidString() override fun equals(other: Any?): Boolean { if (this === other) return true diff --git a/zenoh-kotlin/src/commonMain/kotlin/io/zenoh/exceptions/ResultHandlers.kt b/zenoh-kotlin/src/commonMain/kotlin/io/zenoh/exceptions/ResultHandlers.kt new file mode 100644 index 000000000..44c351ec9 --- /dev/null +++ b/zenoh-kotlin/src/commonMain/kotlin/io/zenoh/exceptions/ResultHandlers.kt @@ -0,0 +1,131 @@ +// +// Copyright (c) 2026 ZettaScale Technology +// +// This program and the accompanying materials are made available under the +// terms of the Eclipse Public License 2.0 which is available at +// http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 +// which is available at https://www.apache.org/licenses/LICENSE-2.0. +// +// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 +// +// Contributors: +// ZettaScale Zenoh Team, +// + +package io.zenoh.exceptions + +import io.zenoh.jni.ErrorHandler +import io.zenoh.jni.JniErrorHandler + +/** + * Helpers converting the generated flat-jni error-callback protocol into + * [Result]. + * + * Two distinct failure channels are folded into one [Result]: + * + * 1. **Native errors** — a generated wrapper never throws from native code; + * on failure it *returns* the value produced by an error handler (a + * generated typed `fun interface`). A fallible wrapper has TWO handler + * channels (prebindgen #45): `onBindingError` (a [JniErrorHandler], any + * binding-layer failure — UTF-8 decode, closed handle, …) and `onError` + * (the typed domain [ErrorHandler], the decomposed zenoh error message). + * The [zCall]/[zCallUnit] helpers supply BOTH — each records the error + * into one local and, where the wrapper's return type demands a value, + * produces a throw-away *sentinel* (a born-closed handle such as + * `Session(0L)`, an empty list, …). No exception is ever in flight for a + * native error. Binding-only-fallible wrappers ([zCall0]/[zCallUnit0]) take + * just the single [JniErrorHandler]. + * 2. **JVM-side exceptions** — argument preparation (collection ops, …), + * user-supplied conversions (`IntoZBytes.into()`), and class + * initialization (native-library loading) can throw before or around the + * native call. The block runs inside `runCatching` so these surface as + * [Result.failure], preserving the public `Result` contract of the + * pre-flat API. The `runCatching` never observes a native error — channel + * 1 reports those without throwing. + * + * For the few non-[Result] contexts (where the public API today already lets + * an unchecked [ZError] propagate), [throwZError]/[throwZError0] throw from + * the handler instead — documented safe, as the handler runs after the native + * call has returned. + */ + +/** Fallible flat call returning `Unit` — no sentinel needed. The wrapper takes + * both channels (`onBindingError`, `onError`); each records into [err]. */ +internal inline fun zCallUnit( + crossinline block: (JniErrorHandler, ErrorHandler) -> Unit +): Result { + var err: ZError? = null + val outcome = runCatching { + block( + JniErrorHandler { je -> err = ZError(je ?: "native binding error") }, + ErrorHandler { message -> err = ZError(message) }, + ) + } + err?.let { return Result.failure(it) } + return outcome +} + +/** Binding-only-fallible flat call returning `Unit`. */ +internal inline fun zCallUnit0(crossinline block: (JniErrorHandler) -> Unit): Result { + var err: ZError? = null + val outcome = runCatching { + block(JniErrorHandler { je -> err = ZError(je ?: "native binding error") }) + } + err?.let { return Result.failure(it) } + return outcome +} + +/** + * Fallible flat call returning `T`; [sentinel] runs only on the error path to + * satisfy the wrapper's return type (its value is discarded). The wrapper takes + * both channels (`onBindingError`, `onError`); each records into [err] and + * produces the sentinel. + */ +internal inline fun zCall( + crossinline sentinel: () -> T, + crossinline block: (JniErrorHandler, ErrorHandler) -> T +): Result { + var err: ZError? = null + val outcome = runCatching { + block( + JniErrorHandler { je -> + err = ZError(je ?: "native binding error") + sentinel() + }, + ErrorHandler { message -> + err = ZError(message) + sentinel() + }, + ) + } + err?.let { return Result.failure(it) } + return outcome +} + +/** Binding-only-fallible flat call returning `T`; [sentinel] as in [zCall]. */ +internal inline fun zCall0( + crossinline sentinel: () -> T, + crossinline block: (JniErrorHandler) -> T +): Result { + var err: ZError? = null + val outcome = runCatching { + block( + JniErrorHandler { je -> + err = ZError(je ?: "native binding error") + sentinel() + } + ) + } + err?.let { return Result.failure(it) } + return outcome +} + +/** Domain handler for non-[Result] contexts: throws [ZError] (safe — runs after + * the native call has returned). Binds `out R` to [Nothing] so one instance + * satisfies every wrapper; paired with [throwZError0] as the `onBindingError`. */ +internal val throwZError: ErrorHandler = + ErrorHandler { message -> throw ZError(message) } + +/** [throwZError] twin for binding-only-fallible wrappers. */ +internal val throwZError0: JniErrorHandler = + JniErrorHandler { je -> throw ZError(je ?: "native binding error") } diff --git a/zenoh-kotlin/src/commonMain/kotlin/io/zenoh/exceptions/ZError.kt b/zenoh-kotlin/src/commonMain/kotlin/io/zenoh/exceptions/ZError.kt index c63a19ce0..13ba0fbc5 100644 --- a/zenoh-kotlin/src/commonMain/kotlin/io/zenoh/exceptions/ZError.kt +++ b/zenoh-kotlin/src/commonMain/kotlin/io/zenoh/exceptions/ZError.kt @@ -17,4 +17,4 @@ package io.zenoh.exceptions /** * A Zenoh Error. */ -class ZError(override val message: String? = null): Exception() +class ZError(override val message: String? = null) : Exception() diff --git a/zenoh-kotlin/src/commonMain/kotlin/io/zenoh/handlers/SampleMissCallback.kt b/zenoh-kotlin/src/commonMain/kotlin/io/zenoh/handlers/SampleMissCallback.kt index 4bf2aff61..eb56fe9f6 100644 --- a/zenoh-kotlin/src/commonMain/kotlin/io/zenoh/handlers/SampleMissCallback.kt +++ b/zenoh-kotlin/src/commonMain/kotlin/io/zenoh/handlers/SampleMissCallback.kt @@ -15,7 +15,7 @@ package io.zenoh.handlers import io.zenoh.annotations.Unstable -import io.zenoh.pubsub.SampleMiss +import io.zenoh.pubsub.Miss /** * Runnable sample miss callback. @@ -26,6 +26,6 @@ import io.zenoh.pubsub.SampleMiss fun interface SampleMissCallback { /** Callback to be run. */ - fun run(miss: SampleMiss) + fun run(miss: Miss) } diff --git a/zenoh-kotlin/src/commonMain/kotlin/io/zenoh/handlers/SampleMissChannelHandler.kt b/zenoh-kotlin/src/commonMain/kotlin/io/zenoh/handlers/SampleMissChannelHandler.kt index 70d81f6e4..58fdbbcc4 100644 --- a/zenoh-kotlin/src/commonMain/kotlin/io/zenoh/handlers/SampleMissChannelHandler.kt +++ b/zenoh-kotlin/src/commonMain/kotlin/io/zenoh/handlers/SampleMissChannelHandler.kt @@ -15,12 +15,12 @@ package io.zenoh.handlers import io.zenoh.annotations.Unstable -import io.zenoh.pubsub.SampleMiss +import io.zenoh.pubsub.Miss import kotlinx.coroutines.channels.* import kotlinx.coroutines.runBlocking /** - * Channel handler for [SampleMiss] events. + * Channel handler for [Miss] events. * * Implementation of a [Handler] with a [Channel] receiver. * @@ -28,21 +28,21 @@ import kotlinx.coroutines.runBlocking * @constructor Create empty Channel handler */ @Unstable -internal class SampleMissChannelHandler(private val channel: Channel) : SampleMissHandler> { +internal class SampleMissChannelHandler(private val channel: Channel) : SampleMissHandler> { /** * Handle the received sample miss event. * * @param miss sample miss event. */ - override fun handle(miss: SampleMiss) { + override fun handle(miss: Miss) { runBlocking { channel.send(miss) } } /** * Return the receiver of the handler. */ - override fun receiver(): Channel { + override fun receiver(): Channel { return channel } diff --git a/zenoh-kotlin/src/commonMain/kotlin/io/zenoh/handlers/SampleMissHandler.kt b/zenoh-kotlin/src/commonMain/kotlin/io/zenoh/handlers/SampleMissHandler.kt index 7752de24f..a39573a7c 100644 --- a/zenoh-kotlin/src/commonMain/kotlin/io/zenoh/handlers/SampleMissHandler.kt +++ b/zenoh-kotlin/src/commonMain/kotlin/io/zenoh/handlers/SampleMissHandler.kt @@ -15,22 +15,22 @@ package io.zenoh.handlers import io.zenoh.annotations.Unstable -import io.zenoh.pubsub.SampleMiss +import io.zenoh.pubsub.Miss /** - * Handler interface for classes implementing behavior to handle the incoming [SampleMiss] events. + * Handler interface for classes implementing behavior to handle the incoming [Miss] events. * * **Example**: * ```kotlin - * class SampleMissQueueHandler : SampleMissHandler> { + * class SampleMissQueueHandler : SampleMissHandler> { * private val queue: ArrayDeque = ArrayDeque() * - * override fun handle(miss: SampleMiss) { + * override fun handle(miss: Miss) { * println("Received $miss, enqueuing...") * queue.add(miss) * } * - * override fun receiver(): ArrayDeque { + * override fun receiver(): ArrayDeque { * return queue * } * @@ -47,11 +47,11 @@ import io.zenoh.pubsub.SampleMiss interface SampleMissHandler { /** - * Handle the received [SampleMiss] event. + * Handle the received [Miss] event. * - * @param miss A [SampleMiss] event. + * @param miss A [Miss] event. */ - fun handle(miss: SampleMiss) + fun handle(miss: Miss) /** * Return the receiver of the handler. diff --git a/zenoh-kotlin/src/commonMain/kotlin/io/zenoh/jni/JNIAdvancedPublisher.kt b/zenoh-kotlin/src/commonMain/kotlin/io/zenoh/jni/JNIAdvancedPublisher.kt deleted file mode 100644 index b629f6d07..000000000 --- a/zenoh-kotlin/src/commonMain/kotlin/io/zenoh/jni/JNIAdvancedPublisher.kt +++ /dev/null @@ -1,124 +0,0 @@ -// -// Copyright (c) 2025 ZettaScale Technology -// -// This program and the accompanying materials are made available under the -// terms of the Eclipse Public License 2.0 which is available at -// http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 -// which is available at https://www.apache.org/licenses/LICENSE-2.0. -// -// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 -// -// Contributors: -// ZettaScale Zenoh Team, -// - -package io.zenoh.jni - -import io.zenoh.exceptions.ZError -import io.zenoh.bytes.Encoding -import io.zenoh.bytes.IntoZBytes -import io.zenoh.handlers.MatchingCallback -import io.zenoh.jni.callbacks.JNIMatchingListenerCallback -import io.zenoh.jni.callbacks.JNIOnCloseCallback -import io.zenoh.pubsub.AdvancedPublisher -import io.zenoh.pubsub.MatchingListener - -/** - * Adapter class to handle the interactions with Zenoh through JNI for an [AdvancedPublisher]. - * - * @property ptr: raw pointer to the underlying native Publisher. - */ -internal class JNIAdvancedPublisher(private val ptr: Long) { - - fun declareMatchingListener( - callback: MatchingCallback, onClose: () -> Unit - ): Result = runCatching { - val matchingListenerCallback = - JNIMatchingListenerCallback { matching: Boolean -> - callback.run(matching) - } - val matchingListenerRawPtr = declareMatchingListenerViaJNI( - ptr, matchingListenerCallback, onClose - ) - MatchingListener(JNIMatchingListener(matchingListenerRawPtr)) - } - - fun declareBackgroundMatchingListener( - callback: MatchingCallback, onClose: () -> Unit - ): Result = runCatching { - val matchingListenerCallback = - JNIMatchingListenerCallback { matching: Boolean -> - callback.run(matching) - } - declareBackgroundMatchingListenerViaJNI( - ptr, matchingListenerCallback, onClose - ) - } - - /** - * Return the matching status of the publisher. - * - * Will return true if there exist Subscribers matching the Publisher's key expression and false otherwise. - */ - fun getMatchingStatus(): Result = runCatching { - getMatchingStatusViaJNI(ptr) - } - - /** - * Put operation. - * - * @param payload Payload of the put. - * @param encoding Encoding of the payload. - * @param attachment Optional attachment. - */ - fun put(payload: IntoZBytes, encoding: Encoding?, attachment: IntoZBytes?): Result = runCatching { - val resolvedEncoding = encoding ?: Encoding.default() - putViaJNI(payload.into().bytes, resolvedEncoding.id, resolvedEncoding.schema, attachment?.into()?.bytes, ptr) - } - - /** - * Delete operation. - * - * @param attachment Optional attachment. - */ - fun delete(attachment: IntoZBytes?): Result = runCatching { - deleteViaJNI(attachment?.into()?.bytes, ptr) - } - - /** - * Close and free the underlying publisher pointer. - * - * Further operations with this publisher should not be performed anymore. - */ - fun close() { - freePtrViaJNI(ptr) - } - - @Throws(ZError::class) - private external fun declareMatchingListenerViaJNI( - ptr: Long, - callback: JNIMatchingListenerCallback, - onClose: JNIOnCloseCallback, - ): Long - - @Throws(ZError::class) - private external fun declareBackgroundMatchingListenerViaJNI( - ptr: Long, - callback: JNIMatchingListenerCallback, - onClose: JNIOnCloseCallback, - ) - - @Throws(ZError::class) - private external fun getMatchingStatusViaJNI(ptr: Long): Boolean - - @Throws(ZError::class) - private external fun putViaJNI( - valuePayload: ByteArray, encodingId: Int, encodingSchema: String?, attachment: ByteArray?, ptr: Long - ) - - @Throws(ZError::class) - private external fun deleteViaJNI(attachment: ByteArray?, ptr: Long) - - private external fun freePtrViaJNI(ptr: Long) - -} diff --git a/zenoh-kotlin/src/commonMain/kotlin/io/zenoh/jni/JNIAdvancedSubscriber.kt b/zenoh-kotlin/src/commonMain/kotlin/io/zenoh/jni/JNIAdvancedSubscriber.kt deleted file mode 100644 index 99a396e5f..000000000 --- a/zenoh-kotlin/src/commonMain/kotlin/io/zenoh/jni/JNIAdvancedSubscriber.kt +++ /dev/null @@ -1,168 +0,0 @@ -// -// Copyright (c) 2025 ZettaScale Technology -// -// This program and the accompanying materials are made available under the -// terms of the Eclipse Public License 2.0 which is available at -// http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 -// which is available at https://www.apache.org/licenses/LICENSE-2.0. -// -// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 -// -// Contributors: -// ZettaScale Zenoh Team, -// - -package io.zenoh.jni - -import io.zenoh.bytes.Encoding -import io.zenoh.bytes.into -import io.zenoh.exceptions.ZError -import io.zenoh.handlers.Callback -import io.zenoh.handlers.MatchingCallback -import io.zenoh.handlers.SampleMissCallback -import io.zenoh.jni.callbacks.JNIMatchingListenerCallback -import io.zenoh.jni.callbacks.JNIOnCloseCallback -import io.zenoh.jni.callbacks.JNISampleMissListenerCallback -import io.zenoh.jni.callbacks.JNISubscriberCallback -import io.zenoh.keyexpr.KeyExpr -import io.zenoh.pubsub.AdvancedSubscriber -import io.zenoh.pubsub.MatchingListener -import io.zenoh.pubsub.SampleMiss -import io.zenoh.pubsub.SampleMissListener -import io.zenoh.pubsub.Subscriber -import io.zenoh.qos.CongestionControl -import io.zenoh.qos.Priority -import io.zenoh.qos.QoS -import io.zenoh.sample.Sample -import io.zenoh.sample.SampleKind -import org.apache.commons.net.ntp.TimeStamp - -/** - * Adapter class to handle the interactions with Zenoh through JNI for an [AdvancedSubscriber] - * - * @property ptr: raw pointer to the underlying native Subscriber. - */ -internal class JNIAdvancedSubscriber(private val ptr: Long) { - - fun declareDetectPublishersSubscriber( - keyExpr: KeyExpr, - history: Boolean, - callback: Callback, - onClose: () -> Unit, receiver: R - ): Result> = runCatching { - val subCallback = - JNISubscriberCallback { keyExpr, payload, encodingId, encodingSchema, kind, timestampNTP64, timestampIsValid, attachmentBytes, express: Boolean, priority: Int, congestionControl: Int -> - val timestamp = if (timestampIsValid) TimeStamp(timestampNTP64) else null - val sample = Sample( - KeyExpr(keyExpr, null), - payload.into(), - Encoding(encodingId, schema = encodingSchema), - SampleKind.fromInt(kind), - timestamp, - QoS(CongestionControl.fromInt(congestionControl), Priority.fromInt(priority), express), - attachmentBytes?.into() - ) - callback.run(sample) - } - val subscriberRawPtr = declareDetectPublishersSubscriberViaJNI( - ptr, history, subCallback, onClose - ) - Subscriber(keyExpr, receiver, JNISubscriber(subscriberRawPtr)) - } - - fun declareBackgroundDetectPublishersSubscriber( - keyExpr: KeyExpr, - history: Boolean, - callback: Callback, - onClose: () -> Unit - ): Result = runCatching { - val subCallback = - JNISubscriberCallback { keyExpr, payload, encodingId, encodingSchema, kind, timestampNTP64, timestampIsValid, attachmentBytes, express: Boolean, priority: Int, congestionControl: Int -> - val timestamp = if (timestampIsValid) TimeStamp(timestampNTP64) else null - val sample = Sample( - KeyExpr(keyExpr, null), - payload.into(), - Encoding(encodingId, schema = encodingSchema), - SampleKind.fromInt(kind), - timestamp, - QoS(CongestionControl.fromInt(congestionControl), Priority.fromInt(priority), express), - attachmentBytes?.into() - ) - callback.run(sample) - } - declareBackgroundDetectPublishersSubscriberViaJNI( - ptr, history, subCallback, onClose - ) - } - - fun declareSampleMissListener( - callback: SampleMissCallback, onClose: () -> Unit - ): Result = runCatching { - val sampleMissListenerCallback = - JNISampleMissListenerCallback { zidLower: Long, - zidUpper: Long, - eid: Long, - missedCount: Long -> - val miss = SampleMiss(zidLower, zidUpper, eid, missedCount) - callback.run(miss) - } - val sampleMissListenerRawPtr = declareSampleMissListenerViaJNI( - ptr, sampleMissListenerCallback, onClose - ) - SampleMissListener(JNISampleMissListener(sampleMissListenerRawPtr)) - } - - fun declareBackgroundSampleMissListener( - callback: SampleMissCallback, onClose: () -> Unit - ): Result = runCatching { - val sampleMissListenerCallback = - JNISampleMissListenerCallback { zidLower: Long, - zidUpper: Long, - eid: Long, - missedCount: Long -> - val miss = SampleMiss(zidLower, zidUpper, eid, missedCount) - callback.run(miss) - } - declareBackgroundSampleMissListenerViaJNI( - ptr, sampleMissListenerCallback, onClose - ) - } - - fun close() { - freePtrViaJNI(ptr) - } - - @Throws(ZError::class) - private external fun declareBackgroundDetectPublishersSubscriberViaJNI( - advancedSubscriberPtr: Long, - history: Boolean, - callback: JNISubscriberCallback, - onClose: JNIOnCloseCallback, - ) - - @Throws(ZError::class) - private external fun declareDetectPublishersSubscriberViaJNI( - advancedSubscriberPtr: Long, - history: Boolean, - callback: JNISubscriberCallback, - onClose: JNIOnCloseCallback, - ): Long - - @Throws(ZError::class) - private external fun declareBackgroundSampleMissListenerViaJNI( - ptr: Long, - callback: JNISampleMissListenerCallback, - onClose: JNIOnCloseCallback, - ) - - @Throws(ZError::class) - private external fun declareSampleMissListenerViaJNI( - ptr: Long, - callback: JNISampleMissListenerCallback, - onClose: JNIOnCloseCallback, - ): Long - - /** Frees the underlying native Subscriber. */ - private external fun freePtrViaJNI(ptr: Long) - -} diff --git a/zenoh-kotlin/src/commonMain/kotlin/io/zenoh/jni/JNIConfig.kt b/zenoh-kotlin/src/commonMain/kotlin/io/zenoh/jni/JNIConfig.kt deleted file mode 100644 index 4c7c0344f..000000000 --- a/zenoh-kotlin/src/commonMain/kotlin/io/zenoh/jni/JNIConfig.kt +++ /dev/null @@ -1,94 +0,0 @@ -// -// Copyright (c) 2023 ZettaScale Technology -// -// This program and the accompanying materials are made available under the -// terms of the Eclipse Public License 2.0 which is available at -// http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 -// which is available at https://www.apache.org/licenses/LICENSE-2.0. -// -// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 -// -// Contributors: -// ZettaScale Zenoh Team, -// - -package io.zenoh.jni - -import io.zenoh.Config -import io.zenoh.ZenohLoad -import io.zenoh.exceptions.ZError -import java.io.File -import java.nio.file.Path - -internal class JNIConfig(internal val ptr: Long) { - - companion object { - - init { - ZenohLoad - } - - fun loadDefaultConfig(): Config { - val cfgPtr = loadDefaultConfigViaJNI() - return Config(JNIConfig(cfgPtr)) - } - - fun loadConfigFile(path: Path): Result = runCatching { - val cfgPtr = loadConfigFileViaJNI(path.toString()) - Config(JNIConfig(cfgPtr)) - } - - fun loadConfigFile(file: File): Result = loadConfigFile(file.toPath()) - - fun loadJsonConfig(rawConfig: String): Result = runCatching { - val cfgPtr = loadJsonConfigViaJNI(rawConfig) - Config(JNIConfig(cfgPtr)) - } - - fun loadJson5Config(rawConfig: String): Result = runCatching { - val cfgPtr = loadJsonConfigViaJNI(rawConfig) - Config(JNIConfig(cfgPtr)) - } - - fun loadYamlConfig(rawConfig: String): Result = runCatching { - val cfgPtr = loadYamlConfigViaJNI(rawConfig) - Config(JNIConfig(cfgPtr)) - } - - @Throws(ZError::class) - private external fun loadDefaultConfigViaJNI(): Long - - @Throws(ZError::class) - private external fun loadConfigFileViaJNI(path: String): Long - - @Throws(ZError::class) - private external fun loadJsonConfigViaJNI(rawConfig: String): Long - - @Throws(ZError::class) - private external fun loadYamlConfigViaJNI(rawConfig: String): Long - - @Throws(ZError::class) - private external fun getIdViaJNI(ptr: Long): ByteArray - - @Throws(ZError::class) - private external fun insertJson5ViaJNI(ptr: Long, key: String, value: String): Long - - /** Frees the underlying native config. */ - private external fun freePtrViaJNI(ptr: Long) - - @Throws(ZError::class) - private external fun getJsonViaJNI(ptr: Long, key: String): String - } - - fun close() { - freePtrViaJNI(ptr) - } - - fun getJson(key: String): Result = runCatching { - getJsonViaJNI(ptr, key) - } - - fun insertJson5(key: String, value: String): Result = runCatching { - insertJson5ViaJNI(this.ptr, key, value) - } -} diff --git a/zenoh-kotlin/src/commonMain/kotlin/io/zenoh/jni/JNIKeyExpr.kt b/zenoh-kotlin/src/commonMain/kotlin/io/zenoh/jni/JNIKeyExpr.kt deleted file mode 100644 index 7022e5210..000000000 --- a/zenoh-kotlin/src/commonMain/kotlin/io/zenoh/jni/JNIKeyExpr.kt +++ /dev/null @@ -1,97 +0,0 @@ -// -// Copyright (c) 2023 ZettaScale Technology -// -// This program and the accompanying materials are made available under the -// terms of the Eclipse Public License 2.0 which is available at -// http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 -// which is available at https://www.apache.org/licenses/LICENSE-2.0. -// -// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 -// -// Contributors: -// ZettaScale Zenoh Team, -// - -package io.zenoh.jni - -import io.zenoh.ZenohLoad -import io.zenoh.exceptions.ZError -import io.zenoh.keyexpr.KeyExpr -import io.zenoh.keyexpr.SetIntersectionLevel - -internal class JNIKeyExpr(internal val ptr: Long) { - - companion object { - init { - ZenohLoad - } - - fun tryFrom(keyExpr: String): Result = runCatching { - KeyExpr(tryFromViaJNI(keyExpr)) - } - - fun autocanonize(keyExpr: String): Result = runCatching { - KeyExpr(autocanonizeViaJNI(keyExpr)) - } - - fun intersects(keyExprA: KeyExpr, keyExprB: KeyExpr): Boolean = intersectsViaJNI( - keyExprA.jniKeyExpr?.ptr ?: 0, - keyExprA.keyExpr, - keyExprB.jniKeyExpr?.ptr ?: 0, - keyExprB.keyExpr - ) - - fun includes(keyExprA: KeyExpr, keyExprB: KeyExpr): Boolean = includesViaJNI( - keyExprA.jniKeyExpr?.ptr ?: 0, - keyExprA.keyExpr, - keyExprB.jniKeyExpr?.ptr ?: 0, - keyExprB.keyExpr - ) - - fun relationTo(keyExpr: KeyExpr, other: KeyExpr): SetIntersectionLevel { - val intersection = relationToViaJNI( - keyExpr.jniKeyExpr?.ptr ?: 0, - keyExpr.keyExpr, - other.jniKeyExpr?.ptr ?: 0, - other.keyExpr - ) - return SetIntersectionLevel.fromInt(intersection) - } - - fun joinViaJNI(keyExpr: KeyExpr, other: String): Result = runCatching { - KeyExpr(joinViaJNI(keyExpr.jniKeyExpr?.ptr ?: 0, keyExpr.keyExpr, other)) - } - - fun concatViaJNI(keyExpr: KeyExpr, other: String): Result = runCatching { - KeyExpr(concatViaJNI(keyExpr.jniKeyExpr?.ptr ?: 0, keyExpr.keyExpr, other)) - } - - @Throws(ZError::class) - private external fun tryFromViaJNI(keyExpr: String): String - - @Throws(ZError::class) - private external fun autocanonizeViaJNI(keyExpr: String): String - - @Throws(ZError::class) - private external fun intersectsViaJNI(ptrA: Long, keyExprA: String, ptrB: Long, keyExprB: String): Boolean - - @Throws(ZError::class) - private external fun includesViaJNI(ptrA: Long, keyExprA: String, ptrB: Long, keyExprB: String): Boolean - - @Throws(ZError::class) - private external fun relationToViaJNI(ptrA: Long, keyExprA: String, ptrB: Long, keyExprB: String): Int - - @Throws(ZError::class) - private external fun joinViaJNI(ptrA: Long, keyExprA: String, other: String): String - - @Throws(ZError::class) - private external fun concatViaJNI(ptrA: Long, keyExprA: String, other: String): String - } - - fun close() { - freePtrViaJNI(ptr) - } - - /** Frees the underlying native KeyExpr. */ - private external fun freePtrViaJNI(ptr: Long) -} diff --git a/zenoh-kotlin/src/commonMain/kotlin/io/zenoh/jni/JNILiveliness.kt b/zenoh-kotlin/src/commonMain/kotlin/io/zenoh/jni/JNILiveliness.kt deleted file mode 100644 index 9434e257b..000000000 --- a/zenoh-kotlin/src/commonMain/kotlin/io/zenoh/jni/JNILiveliness.kt +++ /dev/null @@ -1,157 +0,0 @@ -// -// Copyright (c) 2023 ZettaScale Technology -// -// This program and the accompanying materials are made available under the -// terms of the Eclipse Public License 2.0 which is available at -// http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 -// which is available at https://www.apache.org/licenses/LICENSE-2.0. -// -// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 -// -// Contributors: -// ZettaScale Zenoh Team, -// - -package io.zenoh.jni - -import io.zenoh.bytes.Encoding -import io.zenoh.bytes.into -import io.zenoh.config.EntityGlobalId -import io.zenoh.config.ZenohId -import io.zenoh.handlers.Callback -import io.zenoh.jni.callbacks.JNIGetCallback -import io.zenoh.jni.callbacks.JNIOnCloseCallback -import io.zenoh.jni.callbacks.JNISubscriberCallback -import io.zenoh.keyexpr.KeyExpr -import io.zenoh.liveliness.LivelinessToken -import io.zenoh.pubsub.Subscriber -import io.zenoh.qos.CongestionControl -import io.zenoh.qos.Priority -import io.zenoh.qos.QoS -import io.zenoh.query.Reply -import io.zenoh.query.ReplyError -import io.zenoh.sample.Sample -import io.zenoh.sample.SampleKind -import org.apache.commons.net.ntp.TimeStamp -import java.time.Duration - -internal object JNILiveliness { - - fun get( - jniSession: JNISession, - keyExpr: KeyExpr, - callback: Callback, - receiver: R, - timeout: Duration, - onClose: () -> Unit - ): Result = runCatching { - val getCallback = JNIGetCallback { - replierZid: ByteArray?, - replierEid: Int, - success: Boolean, - keyExpr2: String?, - payload: ByteArray, - encodingId: Int, - encodingSchema: String?, - kind: Int, - timestampNTP64: Long, - timestampIsValid: Boolean, - attachmentBytes: ByteArray?, - express: Boolean, - priority: Int, - congestionControl: Int, - -> - val reply: Reply - if (success) { - val timestamp = if (timestampIsValid) TimeStamp(timestampNTP64) else null - val sample = Sample( - KeyExpr(keyExpr2!!, null), - payload.into(), - Encoding(encodingId, schema = encodingSchema), - SampleKind.fromInt(kind), - timestamp, - QoS(CongestionControl.fromInt(congestionControl), Priority.fromInt(priority), express), - attachmentBytes?.into() - ) - reply = Reply(replierZid?.let { EntityGlobalId(ZenohId(it), replierEid.toUInt()) }, Result.success(sample)) - } else { - reply = Reply( - replierZid?.let { EntityGlobalId(ZenohId(it), replierEid.toUInt()) }, Result.failure( - ReplyError( - payload.into(), - Encoding(encodingId, schema = encodingSchema) - ) - ) - ) - } - callback.run(reply) - } - getViaJNI( - jniSession.sessionPtr.get(), - keyExpr.jniKeyExpr?.ptr ?: 0, - keyExpr.keyExpr, - getCallback, - timeout.toMillis(), - onClose - ) - receiver - } - - fun declareToken(jniSession: JNISession, keyExpr: KeyExpr): LivelinessToken { - val ptr = declareTokenViaJNI(jniSession.sessionPtr.get(), keyExpr.jniKeyExpr?.ptr ?: 0, keyExpr.keyExpr) - return LivelinessToken(JNILivelinessToken(ptr)) - } - - fun declareSubscriber( - jniSession: JNISession, - keyExpr: KeyExpr, - callback: Callback, - receiver: R, - history: Boolean, - onClose: () -> Unit - ): Result> = runCatching { - val subCallback = - JNISubscriberCallback { keyExpr2, payload, encodingId, encodingSchema, kind, timestampNTP64, timestampIsValid, attachmentBytes, express: Boolean, priority: Int, congestionControl: Int -> - val timestamp = if (timestampIsValid) TimeStamp(timestampNTP64) else null - val sample = Sample( - KeyExpr(keyExpr2, null), - payload.into(), - Encoding(encodingId, schema = encodingSchema), - SampleKind.fromInt(kind), - timestamp, - QoS(CongestionControl.fromInt(congestionControl), Priority.fromInt(priority), express), - attachmentBytes?.into() - ) - callback.run(sample) - } - val ptr = declareSubscriberViaJNI( - jniSession.sessionPtr.get(), - keyExpr.jniKeyExpr?.ptr ?: 0, - keyExpr.keyExpr, - subCallback, - history, - onClose - ) - Subscriber(keyExpr, receiver, JNISubscriber(ptr)) - } - - private external fun getViaJNI( - sessionPtr: Long, - keyExprPtr: Long, - keyExprString: String, - callback: JNIGetCallback, - timeoutMs: Long, - onClose: JNIOnCloseCallback - ) - - private external fun declareTokenViaJNI(sessionPtr: Long, keyExprPtr: Long, keyExprString: String): Long - - private external fun declareSubscriberViaJNI( - sessionPtr: Long, - keyExprPtr: Long, - keyExprString: String, - callback: JNISubscriberCallback, - history: Boolean, - onClose: JNIOnCloseCallback - ): Long -} diff --git a/zenoh-kotlin/src/commonMain/kotlin/io/zenoh/jni/JNILivelinessToken.kt b/zenoh-kotlin/src/commonMain/kotlin/io/zenoh/jni/JNILivelinessToken.kt deleted file mode 100644 index 991c860ab..000000000 --- a/zenoh-kotlin/src/commonMain/kotlin/io/zenoh/jni/JNILivelinessToken.kt +++ /dev/null @@ -1,12 +0,0 @@ -package io.zenoh.jni - -internal class JNILivelinessToken(val ptr: Long) { - - fun undeclare() { - undeclareViaJNI(this.ptr) - } - - companion object { - external fun undeclareViaJNI(ptr: Long) - } -} diff --git a/zenoh-kotlin/src/commonMain/kotlin/io/zenoh/jni/JNIMatchingListener.kt b/zenoh-kotlin/src/commonMain/kotlin/io/zenoh/jni/JNIMatchingListener.kt deleted file mode 100644 index c28953786..000000000 --- a/zenoh-kotlin/src/commonMain/kotlin/io/zenoh/jni/JNIMatchingListener.kt +++ /dev/null @@ -1,37 +0,0 @@ -// -// Copyright (c) 2025 ZettaScale Technology -// -// This program and the accompanying materials are made available under the -// terms of the Eclipse Public License 2.0 which is available at -// http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 -// which is available at https://www.apache.org/licenses/LICENSE-2.0. -// -// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 -// -// Contributors: -// ZettaScale Zenoh Team, -// - -package io.zenoh.jni - -import io.zenoh.pubsub.MatchingListener - -/** - * Adapter class to handle the interactions with Zenoh through JNI for a [MatchingListener]. - * - * @property ptr: raw pointer to the underlying native Publisher. - */ -internal class JNIMatchingListener(private val ptr: Long) { - - /** - * Close and free the underlying matching listener pointer. - * - * Further operations with this publisher should not be performed anymore. - */ - fun close() { - freePtrViaJNI(ptr) - } - - private external fun freePtrViaJNI(ptr: Long) - -} diff --git a/zenoh-kotlin/src/commonMain/kotlin/io/zenoh/jni/JNIPublisher.kt b/zenoh-kotlin/src/commonMain/kotlin/io/zenoh/jni/JNIPublisher.kt deleted file mode 100644 index a5c3ea9c0..000000000 --- a/zenoh-kotlin/src/commonMain/kotlin/io/zenoh/jni/JNIPublisher.kt +++ /dev/null @@ -1,69 +0,0 @@ -// -// Copyright (c) 2023 ZettaScale Technology -// -// This program and the accompanying materials are made available under the -// terms of the Eclipse Public License 2.0 which is available at -// http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 -// which is available at https://www.apache.org/licenses/LICENSE-2.0. -// -// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 -// -// Contributors: -// ZettaScale Zenoh Team, -// - -package io.zenoh.jni - -import io.zenoh.exceptions.ZError -import io.zenoh.bytes.Encoding -import io.zenoh.bytes.IntoZBytes -import io.zenoh.pubsub.Publisher - -/** - * Adapter class to handle the interactions with Zenoh through JNI for a [Publisher]. - * - * @property ptr: raw pointer to the underlying native Publisher. - */ -internal class JNIPublisher(private val ptr: Long) { - - /** - * Put operation. - * - * @param payload Payload of the put. - * @param encoding Encoding of the payload. - * @param attachment Optional attachment. - */ - fun put(payload: IntoZBytes, encoding: Encoding?, attachment: IntoZBytes?): Result = runCatching { - val resolvedEncoding = encoding ?: Encoding.default() - putViaJNI(payload.into().bytes, resolvedEncoding.id, resolvedEncoding.schema, attachment?.into()?.bytes, ptr) - } - - /** - * Delete operation. - * - * @param attachment Optional attachment. - */ - fun delete(attachment: IntoZBytes?): Result = runCatching { - deleteViaJNI(attachment?.into()?.bytes, ptr) - } - - /** - * Close and free the underlying publisher pointer. - * - * Further operations with this publisher should not be performed anymore. - */ - fun close() { - freePtrViaJNI(ptr) - } - - @Throws(ZError::class) - private external fun putViaJNI( - valuePayload: ByteArray, encodingId: Int, encodingSchema: String?, attachment: ByteArray?, ptr: Long - ) - - @Throws(ZError::class) - private external fun deleteViaJNI(attachment: ByteArray?, ptr: Long) - - private external fun freePtrViaJNI(ptr: Long) - -} diff --git a/zenoh-kotlin/src/commonMain/kotlin/io/zenoh/jni/JNIQuerier.kt b/zenoh-kotlin/src/commonMain/kotlin/io/zenoh/jni/JNIQuerier.kt deleted file mode 100644 index 28a195e09..000000000 --- a/zenoh-kotlin/src/commonMain/kotlin/io/zenoh/jni/JNIQuerier.kt +++ /dev/null @@ -1,123 +0,0 @@ -// -// Copyright (c) 2023 ZettaScale Technology -// -// This program and the accompanying materials are made available under the -// terms of the Eclipse Public License 2.0 which is available at -// http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 -// which is available at https://www.apache.org/licenses/LICENSE-2.0. -// -// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 -// -// Contributors: -// ZettaScale Zenoh Team, -// - -package io.zenoh.jni - -import io.zenoh.bytes.Encoding -import io.zenoh.bytes.IntoZBytes -import io.zenoh.bytes.into -import io.zenoh.config.EntityGlobalId -import io.zenoh.config.ZenohId -import io.zenoh.exceptions.ZError -import io.zenoh.handlers.Callback -import io.zenoh.jni.callbacks.JNIGetCallback -import io.zenoh.jni.callbacks.JNIOnCloseCallback -import io.zenoh.keyexpr.KeyExpr -import io.zenoh.qos.CongestionControl -import io.zenoh.qos.Priority -import io.zenoh.qos.QoS -import io.zenoh.query.Parameters -import io.zenoh.query.Reply -import io.zenoh.query.ReplyError -import io.zenoh.sample.Sample -import io.zenoh.sample.SampleKind -import org.apache.commons.net.ntp.TimeStamp - -internal class JNIQuerier(val ptr: Long) { - - fun performGet( - keyExpr: KeyExpr, - parameters: Parameters?, - callback: Callback, - onClose: () -> Unit, - receiver: R, - attachment: IntoZBytes?, - payload: IntoZBytes?, - encoding: Encoding? - ): Result = runCatching { - val getCallback = JNIGetCallback { - replierZid: ByteArray?, - replierEid: Int, - success: Boolean, - keyExpr: String?, - payload: ByteArray, - encodingId: Int, - encodingSchema: String?, - kind: Int, - timestampNTP64: Long, - timestampIsValid: Boolean, - attachmentBytes: ByteArray?, - express: Boolean, - priority: Int, - congestionControl: Int, - -> - val reply: Reply - if (success) { - val timestamp = if (timestampIsValid) TimeStamp(timestampNTP64) else null - val sample = Sample( - KeyExpr(keyExpr!!, null), - payload.into(), - Encoding(encodingId, schema = encodingSchema), - SampleKind.fromInt(kind), - timestamp, - QoS(CongestionControl.fromInt(congestionControl), Priority.fromInt(priority), express), - attachmentBytes?.into() - ) - reply = Reply(replierZid?.let { EntityGlobalId(ZenohId(it), replierEid.toUInt()) }, Result.success(sample)) - } else { - reply = Reply( - replierZid?.let { EntityGlobalId(ZenohId(it), replierEid.toUInt()) }, Result.failure( - ReplyError( - payload.into(), - Encoding(encodingId, schema = encodingSchema) - ) - ) - ) - } - callback.run(reply) - } - getViaJNI(this.ptr, - keyExpr.jniKeyExpr?.ptr ?: 0, - keyExpr.keyExpr, - parameters?.toString(), - getCallback, - onClose, - attachment?.into()?.bytes, - payload?.into()?.bytes, - encoding?.id ?: Encoding.default().id, - encoding?.schema - ) - receiver - } - - fun close() { - freePtrViaJNI(ptr) - } - - @Throws(ZError::class) - private external fun getViaJNI( - querierPtr: Long, - keyExprPtr: Long, - keyExprString: String, - parameters: String?, - callback: JNIGetCallback, - onClose: JNIOnCloseCallback, - attachmentBytes: ByteArray?, - payload: ByteArray?, - encodingId: Int, - encodingSchema: String?, - ) - - private external fun freePtrViaJNI(ptr: Long) -} diff --git a/zenoh-kotlin/src/commonMain/kotlin/io/zenoh/jni/JNIQuery.kt b/zenoh-kotlin/src/commonMain/kotlin/io/zenoh/jni/JNIQuery.kt deleted file mode 100644 index b176b9705..000000000 --- a/zenoh-kotlin/src/commonMain/kotlin/io/zenoh/jni/JNIQuery.kt +++ /dev/null @@ -1,107 +0,0 @@ -// -// Copyright (c) 2023 ZettaScale Technology -// -// This program and the accompanying materials are made available under the -// terms of the Eclipse Public License 2.0 which is available at -// http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 -// which is available at https://www.apache.org/licenses/LICENSE-2.0. -// -// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 -// -// Contributors: -// ZettaScale Zenoh Team, -// - -package io.zenoh.jni - -import io.zenoh.exceptions.ZError -import io.zenoh.keyexpr.KeyExpr -import io.zenoh.bytes.Encoding -import io.zenoh.qos.QoS -import io.zenoh.bytes.IntoZBytes -import io.zenoh.sample.Sample -import org.apache.commons.net.ntp.TimeStamp - -/** - * Adapter class for interacting with a Query using JNI. - * - * This class serves as an adapter for interacting with a Query through JNI (Java Native Interface). - * - * @property ptr The raw pointer to the underlying native query. - */ -internal class JNIQuery(private val ptr: Long) { - - fun replySuccess(sample: Sample): Result = runCatching { - val timestampEnabled = sample.timestamp != null - replySuccessViaJNI( - ptr, - sample.keyExpr.jniKeyExpr?.ptr ?: 0, - sample.keyExpr.keyExpr, - sample.payload.bytes, - sample.encoding.id, - sample.encoding.schema, - timestampEnabled, - if (timestampEnabled) sample.timestamp!!.ntpValue() else 0, - sample.attachment?.bytes, - sample.qos.express, - ) - } - - fun replyError(error: IntoZBytes, encoding: Encoding): Result = runCatching { - replyErrorViaJNI(ptr, error.into().bytes, encoding.id, encoding.schema) - } - - fun replyDelete(keyExpr: KeyExpr, timestamp: TimeStamp?, attachment: IntoZBytes?, qos: QoS): Result = - runCatching { - val timestampEnabled = timestamp != null - replyDeleteViaJNI( - ptr, - keyExpr.jniKeyExpr?.ptr ?: 0, - keyExpr.keyExpr, - timestampEnabled, - if (timestampEnabled) timestamp!!.ntpValue() else 0, - attachment?.into()?.bytes, - qos.express, - ) - } - - fun close() { - freePtrViaJNI(ptr) - } - - @Throws(ZError::class) - private external fun replySuccessViaJNI( - queryPtr: Long, - keyExprPtr: Long, - keyExprString: String, - valuePayload: ByteArray, - valueEncodingId: Int, - valueEncodingSchema: String?, - timestampEnabled: Boolean, - timestampNtp64: Long, - attachment: ByteArray?, - qosExpress: Boolean, - ) - - @Throws(ZError::class) - private external fun replyErrorViaJNI( - queryPtr: Long, - errorValuePayload: ByteArray, - errorValueEncoding: Int, - encodingSchema: String?, - ) - - @Throws(ZError::class) - private external fun replyDeleteViaJNI( - queryPtr: Long, - keyExprPtr: Long, - keyExprString: String, - timestampEnabled: Boolean, - timestampNtp64: Long, - attachment: ByteArray?, - qosExpress: Boolean, - ) - - /** Frees the underlying native Query. */ - private external fun freePtrViaJNI(ptr: Long) -} diff --git a/zenoh-kotlin/src/commonMain/kotlin/io/zenoh/jni/JNIQueryable.kt b/zenoh-kotlin/src/commonMain/kotlin/io/zenoh/jni/JNIQueryable.kt deleted file mode 100644 index e5f7d3cee..000000000 --- a/zenoh-kotlin/src/commonMain/kotlin/io/zenoh/jni/JNIQueryable.kt +++ /dev/null @@ -1,30 +0,0 @@ -// -// Copyright (c) 2023 ZettaScale Technology -// -// This program and the accompanying materials are made available under the -// terms of the Eclipse Public License 2.0 which is available at -// http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 -// which is available at https://www.apache.org/licenses/LICENSE-2.0. -// -// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 -// -// Contributors: -// ZettaScale Zenoh Team, -// - -package io.zenoh.jni - -/** - * Adapter class to handle the interactions with Zenoh through JNI for a [io.zenoh.query.Queryable] - * - * @property ptr: raw pointer to the underlying native Queryable. - */ -internal class JNIQueryable(val ptr: Long) { - - fun close() { - freePtrViaJNI(ptr) - } - - /** Frees the underlying native Queryable. */ - private external fun freePtrViaJNI(ptr: Long) -} diff --git a/zenoh-kotlin/src/commonMain/kotlin/io/zenoh/jni/JNISampleMissListener.kt b/zenoh-kotlin/src/commonMain/kotlin/io/zenoh/jni/JNISampleMissListener.kt deleted file mode 100644 index b7f5b5ed0..000000000 --- a/zenoh-kotlin/src/commonMain/kotlin/io/zenoh/jni/JNISampleMissListener.kt +++ /dev/null @@ -1,37 +0,0 @@ -// -// Copyright (c) 2025 ZettaScale Technology -// -// This program and the accompanying materials are made available under the -// terms of the Eclipse Public License 2.0 which is available at -// http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 -// which is available at https://www.apache.org/licenses/LICENSE-2.0. -// -// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 -// -// Contributors: -// ZettaScale Zenoh Team, -// - -package io.zenoh.jni - -import io.zenoh.pubsub.SampleMissListener - -/** - * Adapter class to handle the interactions with Zenoh through JNI for a [SampleMissListener]. - * - * @property ptr: raw pointer to the underlying native Publisher. - */ -internal class JNISampleMissListener(private val ptr: Long) { - - /** - * Close and free the underlying matching listener pointer. - * - * Further operations with this publisher should not be performed anymore. - */ - fun close() { - freePtrViaJNI(ptr) - } - - private external fun freePtrViaJNI(ptr: Long) - -} diff --git a/zenoh-kotlin/src/commonMain/kotlin/io/zenoh/jni/JNIScout.kt b/zenoh-kotlin/src/commonMain/kotlin/io/zenoh/jni/JNIScout.kt deleted file mode 100644 index cdc3f5e27..000000000 --- a/zenoh-kotlin/src/commonMain/kotlin/io/zenoh/jni/JNIScout.kt +++ /dev/null @@ -1,65 +0,0 @@ -// -// Copyright (c) 2023 ZettaScale Technology -// -// This program and the accompanying materials are made available under the -// terms of the Eclipse Public License 2.0 which is available at -// http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 -// which is available at https://www.apache.org/licenses/LICENSE-2.0. -// -// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 -// -// Contributors: -// ZettaScale Zenoh Team, -// - -package io.zenoh.jni - -import io.zenoh.Config -import io.zenoh.exceptions.ZError -import io.zenoh.handlers.Callback -import io.zenoh.jni.callbacks.JNIScoutCallback -import io.zenoh.config.ZenohId -import io.zenoh.scouting.Hello -import io.zenoh.scouting.Scout -import io.zenoh.config.WhatAmI -import io.zenoh.jni.callbacks.JNIOnCloseCallback - -/** - * Adapter class to handle the interactions with Zenoh through JNI for a [io.zenoh.scouting.Scout] - * - * @property ptr: raw pointer to the underlying native scout. - */ -internal class JNIScout(private val ptr: Long) { - - companion object { - fun scout( - whatAmI: Set, - callback: Callback, - onClose: () -> Unit, - config: Config?, - receiver: R - ): Result> = runCatching { - val scoutCallback = JNIScoutCallback { whatAmI2: Int, id: ByteArray, locators: List -> - callback.run(Hello(WhatAmI.fromInt(whatAmI2), ZenohId(id), locators)) - } - val binaryWhatAmI: Int = whatAmI.map { it.value }.reduce { acc, it -> acc or it } - val ptr = scoutViaJNI(binaryWhatAmI, scoutCallback, onClose,config?.jniConfig?.ptr ?: 0) - Scout(receiver, JNIScout(ptr)) - } - - @Throws(ZError::class) - private external fun scoutViaJNI( - whatAmI: Int, - callback: JNIScoutCallback, - onCloseCallback: JNIOnCloseCallback, - configPtr: Long, - ): Long - - @Throws(ZError::class) - external fun freePtrViaJNI(ptr: Long) - } - - fun close() { - freePtrViaJNI(ptr) - } -} diff --git a/zenoh-kotlin/src/commonMain/kotlin/io/zenoh/jni/JNISession.kt b/zenoh-kotlin/src/commonMain/kotlin/io/zenoh/jni/JNISession.kt deleted file mode 100644 index 214abe068..000000000 --- a/zenoh-kotlin/src/commonMain/kotlin/io/zenoh/jni/JNISession.kt +++ /dev/null @@ -1,552 +0,0 @@ -// -// Copyright (c) 2023 ZettaScale Technology -// -// This program and the accompanying materials are made available under the -// terms of the Eclipse Public License 2.0 which is available at -// http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 -// which is available at https://www.apache.org/licenses/LICENSE-2.0. -// -// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 -// -// Contributors: -// ZettaScale Zenoh Team, -// - -package io.zenoh.jni - -import io.zenoh.* -import io.zenoh.bytes.Encoding -import io.zenoh.exceptions.ZError -import io.zenoh.handlers.Callback -import io.zenoh.jni.callbacks.JNIOnCloseCallback -import io.zenoh.jni.callbacks.JNIGetCallback -import io.zenoh.jni.callbacks.JNIQueryableCallback -import io.zenoh.jni.callbacks.JNISubscriberCallback -import io.zenoh.keyexpr.KeyExpr -import io.zenoh.bytes.IntoZBytes -import io.zenoh.config.ZenohId -import io.zenoh.bytes.into -import io.zenoh.Config -import io.zenoh.config.EntityGlobalId -import io.zenoh.pubsub.AdvancedSubscriber -import io.zenoh.pubsub.AdvancedPublisher -import io.zenoh.pubsub.Delete -import io.zenoh.pubsub.Publisher -import io.zenoh.pubsub.Put -import io.zenoh.pubsub.Subscriber -import io.zenoh.qos.CongestionControl -import io.zenoh.qos.Priority -import io.zenoh.qos.QoS -import io.zenoh.query.* -import io.zenoh.query.Query -import io.zenoh.query.Queryable -import io.zenoh.sample.Sample -import io.zenoh.query.Parameters -import io.zenoh.query.Selector -import io.zenoh.qos.Reliability -import io.zenoh.sample.SampleKind -import io.zenoh.ext.CacheConfig -import io.zenoh.ext.HeartbeatMode -import io.zenoh.ext.MissDetectionConfig -import io.zenoh.ext.HistoryConfig -import io.zenoh.ext.RecoveryConfig -import io.zenoh.ext.RecoveryMode -import org.apache.commons.net.ntp.TimeStamp -import java.time.Duration -import java.util.concurrent.atomic.AtomicLong - -/** Adapter class to handle the communication with the Zenoh JNI code for a [Session]. */ -internal class JNISession { - - companion object { - init { - ZenohLoad - } - } - - /* Pointer to the underlying Rust zenoh session. */ - internal var sessionPtr: AtomicLong = AtomicLong(0) - - fun open(config: Config): Result = runCatching { - val session = openSessionViaJNI(config.jniConfig.ptr) - sessionPtr.set(session) - } - - fun close(): Result = runCatching { - closeSessionViaJNI(sessionPtr.get()) - } - - fun declarePublisher(keyExpr: KeyExpr, qos: QoS, encoding: Encoding, reliability: Reliability): Result = runCatching { - val publisherRawPtr = declarePublisherViaJNI( - keyExpr.jniKeyExpr?.ptr ?: 0, - keyExpr.keyExpr, - sessionPtr.get(), - qos.congestionControl.value, - qos.priority.value, - qos.express, - reliability.ordinal - ) - Publisher( - keyExpr, - qos, - encoding, - JNIPublisher(publisherRawPtr), - ) - } - - fun declareAdvancedPublisher( - keyExpr: KeyExpr, - qos: QoS, - encoding: Encoding, - reliability: Reliability, - cache: CacheConfig?, - sampleMissDetection: MissDetectionConfig?, - publisherDetection: Boolean - ): Result = runCatching { - - val publisherRawPtr = declareAdvancedPublisherViaJNI( - keyExpr.jniKeyExpr?.ptr ?: 0, - keyExpr.keyExpr, - sessionPtr.get(), - qos.congestionControl.value, - qos.priority.value, - qos.express, - reliability.ordinal, - cache != null, - cache?.maxSamples ?: 0, - cache?.repliesQoS?.priority?.value ?: 0, - cache?.repliesQoS?.congestionControl?.value ?: 0, - cache?.repliesQoS?.express ?: false, - sampleMissDetection != null, - sampleMissDetection?.heartbeat != null, - when(val heartbeat = sampleMissDetection?.heartbeat) { - is HeartbeatMode.PeriodicHeartbeat -> heartbeat.milliseconds - is HeartbeatMode.SporadicHeartbeat -> heartbeat.milliseconds - null -> 0 - }, - when(sampleMissDetection?.heartbeat) { - is HeartbeatMode.PeriodicHeartbeat -> false - is HeartbeatMode.SporadicHeartbeat -> true - null -> false - }, - publisherDetection - ) - AdvancedPublisher( - keyExpr, - qos, - encoding, - JNIAdvancedPublisher(publisherRawPtr), - ) - } - - fun declareSubscriber( - keyExpr: KeyExpr, callback: Callback, onClose: () -> Unit, receiver: R - ): Result> = runCatching { - val subCallback = - JNISubscriberCallback { keyExpr, payload, encodingId, encodingSchema, kind, timestampNTP64, timestampIsValid, attachmentBytes, express: Boolean, priority: Int, congestionControl: Int -> - val timestamp = if (timestampIsValid) TimeStamp(timestampNTP64) else null - val sample = Sample( - KeyExpr(keyExpr, null), - payload.into(), - Encoding(encodingId, schema = encodingSchema), - SampleKind.fromInt(kind), - timestamp, - QoS(CongestionControl.fromInt(congestionControl), Priority.fromInt(priority), express), - attachmentBytes?.into() - ) - callback.run(sample) - } - val subscriberRawPtr = declareSubscriberViaJNI( - keyExpr.jniKeyExpr?.ptr ?: 0, keyExpr.keyExpr, sessionPtr.get(), subCallback, onClose - ) - Subscriber(keyExpr, receiver, JNISubscriber(subscriberRawPtr)) - } - - fun declareAdvancedSubscriber( - keyExpr: KeyExpr, - history: HistoryConfig?, - recovery: RecoveryConfig?, - subscriberDetection: Boolean, - callback: Callback, - onClose: () -> Unit, - receiver: R - ): Result> = runCatching { - val subCallback = - JNISubscriberCallback { keyExpr, payload, encodingId, encodingSchema, kind, timestampNTP64, timestampIsValid, attachmentBytes, express: Boolean, priority: Int, congestionControl: Int -> - val timestamp = if (timestampIsValid) TimeStamp(timestampNTP64) else null - val sample = Sample( - KeyExpr(keyExpr, null), - payload.into(), - Encoding(encodingId, schema = encodingSchema), - SampleKind.fromInt(kind), - timestamp, - QoS(CongestionControl.fromInt(congestionControl), Priority.fromInt(priority), express), - attachmentBytes?.into() - ) - callback.run(sample) - } - val subscriberRawPtr = declareAdvancedSubscriberViaJNI( - keyExpr.jniKeyExpr?.ptr ?: 0, - keyExpr.keyExpr, - sessionPtr.get(), - history != null, - history?.detectLatePublishers ?: false, - history?.maxSamples ?: 0, - history?.maxAgeSeconds ?: 0.0, - recovery != null, - when(recovery?.mode){ - is RecoveryMode.Heartbeat -> true - is RecoveryMode.PeriodicQuery -> false - null -> false - }, - when(val mode = recovery?.mode){ - is RecoveryMode.Heartbeat -> 0 - is RecoveryMode.PeriodicQuery -> mode.milliseconds - null -> 0 - }, - subscriberDetection, - subCallback, - onClose - ) - AdvancedSubscriber(keyExpr, receiver, JNIAdvancedSubscriber(subscriberRawPtr)) - } - - fun declareQueryable( - keyExpr: KeyExpr, callback: Callback, onClose: () -> Unit, receiver: R, complete: Boolean - ): Result> = runCatching { - val queryCallback = - JNIQueryableCallback { keyExpr: String, selectorParams: String, payload: ByteArray?, encodingId: Int, encodingSchema: String?, attachmentBytes: ByteArray?, queryPtr: Long -> - val jniQuery = JNIQuery(queryPtr) - val keyExpr2 = KeyExpr(keyExpr, null) - val selector = if (selectorParams.isEmpty()) { - Selector(keyExpr2) - } else { - Selector(keyExpr2, Parameters.from(selectorParams).getOrThrow()) - } - val query = Query( - keyExpr2, - selector, - payload?.into(), - payload?.let { Encoding(encodingId, schema = encodingSchema) }, - attachmentBytes?.into(), - jniQuery - ) - callback.run(query) - } - val queryableRawPtr = declareQueryableViaJNI( - keyExpr.jniKeyExpr?.ptr ?: 0, keyExpr.keyExpr, sessionPtr.get(), queryCallback, onClose, complete - ) - Queryable(keyExpr, receiver, JNIQueryable(queryableRawPtr)) - } - - fun declareQuerier( - keyExpr: KeyExpr, - target: QueryTarget, - consolidation: ConsolidationMode, - qos: QoS, - timeout: Duration, - acceptReplies: ReplyKeyExpr - ): Result = runCatching { - val querierRawPtr = declareQuerierViaJNI( - keyExpr.jniKeyExpr?.ptr ?: 0, keyExpr.keyExpr, sessionPtr.get(), target.ordinal, consolidation.ordinal, - qos.congestionControl.value, qos.priority.value, qos.express, timeout.toMillis(), acceptReplies.ordinal - ) - Querier(keyExpr, qos, JNIQuerier(querierRawPtr)) - } - - fun performGet( - selector: Selector, - callback: Callback, - onClose: () -> Unit, - receiver: R, - timeout: Duration, - target: QueryTarget, - consolidation: ConsolidationMode, - payload: IntoZBytes?, - encoding: Encoding?, - attachment: IntoZBytes?, - qos: QoS, - acceptReplies: ReplyKeyExpr - ): Result = runCatching { - val getCallback = JNIGetCallback { - replierZid: ByteArray?, - replierEid: Int, - success: Boolean, - keyExpr: String?, - payload: ByteArray, - encodingId: Int, - encodingSchema: String?, - kind: Int, - timestampNTP64: Long, - timestampIsValid: Boolean, - attachmentBytes: ByteArray?, - express: Boolean, - priority: Int, - congestionControl: Int, - -> - val reply: Reply - if (success) { - val timestamp = if (timestampIsValid) TimeStamp(timestampNTP64) else null - val sample = Sample( - KeyExpr(keyExpr!!, null), - payload.into(), - Encoding(encodingId, schema = encodingSchema), - SampleKind.fromInt(kind), - timestamp, - QoS(CongestionControl.fromInt(congestionControl), Priority.fromInt(priority), express), - attachmentBytes?.into() - ) - reply = Reply(replierZid?.let { EntityGlobalId(ZenohId(it), replierEid.toUInt()) }, Result.success(sample)) - } else { - reply = Reply( - replierZid?.let { EntityGlobalId(ZenohId(it), replierEid.toUInt()) }, Result.failure( - ReplyError( - payload.into(), - Encoding(encodingId, schema = encodingSchema) - ) - ) - ) - } - callback.run(reply) - } - - getViaJNI( - selector.keyExpr.jniKeyExpr?.ptr ?: 0, - selector.keyExpr.keyExpr, - selector.parameters?.toString(), - sessionPtr.get(), - getCallback, - onClose, - timeout.toMillis(), - target.ordinal, - consolidation.ordinal, - attachment?.into()?.bytes, - payload?.into()?.bytes, - encoding?.id ?: Encoding.default().id, - encoding?.schema, - qos.congestionControl.value, - qos.priority.value, - qos.express, - acceptReplies.ordinal, - ) - receiver - } - - fun declareKeyExpr(keyExpr: String): Result = runCatching { - val ptr = declareKeyExprViaJNI(sessionPtr.get(), keyExpr) - return Result.success(KeyExpr(keyExpr, JNIKeyExpr(ptr))) - } - - fun undeclareKeyExpr(keyExpr: KeyExpr): Result = runCatching { - keyExpr.jniKeyExpr?.run { - undeclareKeyExprViaJNI(sessionPtr.get(), this.ptr) - keyExpr.jniKeyExpr = null - } ?: throw ZError("Attempting to undeclare a non declared key expression.") - } - - @Throws(ZError::class) - fun performPut( - keyExpr: KeyExpr, - put: Put, - ) { - putViaJNI( - keyExpr.jniKeyExpr?.ptr ?: 0, - keyExpr.keyExpr, - sessionPtr.get(), - put.payload.bytes, - put.encoding.id, - put.encoding.schema, - put.qos.congestionControl.value, - put.qos.priority.value, - put.qos.express, - put.attachment?.bytes, - put.reliability.ordinal - ) - } - - @Throws(ZError::class) - fun performDelete( - keyExpr: KeyExpr, - delete: Delete, - ) { - deleteViaJNI( - keyExpr.jniKeyExpr?.ptr ?: 0, - keyExpr.keyExpr, - sessionPtr.get(), - delete.qos.congestionControl.value, - delete.qos.priority.value, - delete.qos.express, - delete.attachment?.bytes, - delete.reliability.ordinal - ) - } - - fun zid(): Result = runCatching { - ZenohId(getZidViaJNI(sessionPtr.get())) - } - - fun peersZid(): Result> = runCatching { - getPeersZidViaJNI(sessionPtr.get()).map { ZenohId(it) } - } - - fun routersZid(): Result> = runCatching { - getRoutersZidViaJNI(sessionPtr.get()).map { ZenohId(it) } - } - - @Throws(ZError::class) - private external fun getZidViaJNI(ptr: Long): ByteArray - - @Throws(ZError::class) - private external fun getPeersZidViaJNI(ptr: Long): List - - @Throws(ZError::class) - private external fun getRoutersZidViaJNI(ptr: Long): List - - @Throws(ZError::class) - private external fun openSessionViaJNI(configPtr: Long): Long - - @Throws(ZError::class) - private external fun closeSessionViaJNI(ptr: Long) - - @Throws(ZError::class) - private external fun declarePublisherViaJNI( - keyExprPtr: Long, - keyExprString: String, - sessionPtr: Long, - congestionControl: Int, - priority: Int, - express: Boolean, - reliability: Int - ): Long - - @Throws(ZError::class) - private external fun declareAdvancedPublisherViaJNI( - keyExprPtr: Long, - keyExprString: String, - sessionPtr: Long, - congestionControl: Int, - priority: Int, - express: Boolean, - reliability: Int, - // CacheConfig - cacheEnabled: Boolean, - cacheMaxSamples: Long, - cacheRepliesPriority: Int, - cacheRepliesCongestionControl: Int, - cacheRepliesIsExpress: Boolean, - // MissDetectionConfig - sampleMissDetectionEnabled: Boolean, - sampleMissDetectionEnableHeartbeat: Boolean, - sampleMissDetectionHeartbeatMs: Long, - sampleMissDetectionHeartbeatIsSporadic: Boolean, - - publisherDetection: Boolean, - ): Long - - @Throws(ZError::class) - private external fun declareSubscriberViaJNI( - keyExprPtr: Long, - keyExprString: String, - sessionPtr: Long, - callback: JNISubscriberCallback, - onClose: JNIOnCloseCallback, - ): Long - - @Throws(ZError::class) - private external fun declareAdvancedSubscriberViaJNI( - keyExprPtr: Long, - keyExprString: String, - sessionPtr: Long, - // HistoryConfig - historyConfigEnabled: Boolean, - historyDetectLatePublishers: Boolean, - historyMaxSamples: Long, - historyMaxAgeSeconds: Double, - // RecoveryConfig - recoveryConfigEnabled: Boolean, - recoveryConfigIsHeartbeat: Boolean, - recoveryQueryPeriodMs: Long, - - subscriberDetection: Boolean, - callback: JNISubscriberCallback, - onClose: JNIOnCloseCallback, - ): Long - - @Throws(ZError::class) - private external fun declareQueryableViaJNI( - keyExprPtr: Long, - keyExprString: String, - sessionPtr: Long, - callback: JNIQueryableCallback, - onClose: JNIOnCloseCallback, - complete: Boolean - ): Long - - @Throws(ZError::class) - private external fun declareQuerierViaJNI( - keyExprPtr: Long, - keyExprString: String, - sessionPtr: Long, - target: Int, - consolidation: Int, - congestionControl: Int, - priority: Int, - express: Boolean, - timeoutMs: Long, - acceptReplies: Int - ): Long - - @Throws(ZError::class) - private external fun declareKeyExprViaJNI(sessionPtr: Long, keyExpr: String): Long - - @Throws(ZError::class) - private external fun undeclareKeyExprViaJNI(sessionPtr: Long, keyExprPtr: Long) - - @Throws(ZError::class) - private external fun getViaJNI( - keyExprPtr: Long, - keyExprString: String, - selectorParams: String?, - sessionPtr: Long, - callback: JNIGetCallback, - onClose: JNIOnCloseCallback, - timeoutMs: Long, - target: Int, - consolidation: Int, - attachmentBytes: ByteArray?, - payload: ByteArray?, - encodingId: Int, - encodingSchema: String?, - congestionControl: Int, - priority: Int, - express: Boolean, - acceptReplies: Int, - ) - - @Throws(ZError::class) - private external fun putViaJNI( - keyExprPtr: Long, - keyExprString: String, - sessionPtr: Long, - valuePayload: ByteArray, - valueEncoding: Int, - valueEncodingSchema: String?, - congestionControl: Int, - priority: Int, - express: Boolean, - attachmentBytes: ByteArray?, - reliability: Int - ) - - @Throws(ZError::class) - private external fun deleteViaJNI( - keyExprPtr: Long, - keyExprString: String, - sessionPtr: Long, - congestionControl: Int, - priority: Int, - express: Boolean, - attachmentBytes: ByteArray?, - reliability: Int - ) -} diff --git a/zenoh-kotlin/src/commonMain/kotlin/io/zenoh/jni/JNISubscriber.kt b/zenoh-kotlin/src/commonMain/kotlin/io/zenoh/jni/JNISubscriber.kt deleted file mode 100644 index 957c13e10..000000000 --- a/zenoh-kotlin/src/commonMain/kotlin/io/zenoh/jni/JNISubscriber.kt +++ /dev/null @@ -1,33 +0,0 @@ -// -// Copyright (c) 2023 ZettaScale Technology -// -// This program and the accompanying materials are made available under the -// terms of the Eclipse Public License 2.0 which is available at -// http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 -// which is available at https://www.apache.org/licenses/LICENSE-2.0. -// -// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 -// -// Contributors: -// ZettaScale Zenoh Team, -// - -package io.zenoh.jni - -import io.zenoh.pubsub.Subscriber - -/** - * Adapter class to handle the interactions with Zenoh through JNI for a [Subscriber] - * - * @property ptr: raw pointer to the underlying native Subscriber. - */ -internal class JNISubscriber(private val ptr: Long) { - - fun close() { - freePtrViaJNI(ptr) - } - - /** Frees the underlying native Subscriber. */ - private external fun freePtrViaJNI(ptr: Long) - -} diff --git a/zenoh-kotlin/src/commonMain/kotlin/io/zenoh/jni/JNIZBytes.kt b/zenoh-kotlin/src/commonMain/kotlin/io/zenoh/jni/JNIZBytes.kt deleted file mode 100644 index 8e9e94b2f..000000000 --- a/zenoh-kotlin/src/commonMain/kotlin/io/zenoh/jni/JNIZBytes.kt +++ /dev/null @@ -1,31 +0,0 @@ -// -// Copyright (c) 2023 ZettaScale Technology -// -// This program and the accompanying materials are made available under the -// terms of the Eclipse Public License 2.0 which is available at -// http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 -// which is available at https://www.apache.org/licenses/LICENSE-2.0. -// -// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 -// -// Contributors: -// ZettaScale Zenoh Team, -// - -package io.zenoh.jni - -import io.zenoh.ZenohLoad -import io.zenoh.bytes.ZBytes -import kotlin.reflect.KType - -@PublishedApi -internal object JNIZBytes { - - init { - ZenohLoad - } - - external fun serializeViaJNI(any: Any, kType: KType): ZBytes - - external fun deserializeViaJNI(zBytes: ZBytes, kType: KType): Any -} diff --git a/zenoh-kotlin/src/commonMain/kotlin/io/zenoh/jni/JNIZenohId.kt b/zenoh-kotlin/src/commonMain/kotlin/io/zenoh/jni/JNIZenohId.kt deleted file mode 100644 index 53ecb5dc7..000000000 --- a/zenoh-kotlin/src/commonMain/kotlin/io/zenoh/jni/JNIZenohId.kt +++ /dev/null @@ -1,27 +0,0 @@ -// -// Copyright (c) 2023 ZettaScale Technology -// -// This program and the accompanying materials are made available under the -// terms of the Eclipse Public License 2.0 which is available at -// http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 -// which is available at https://www.apache.org/licenses/LICENSE-2.0. -// -// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 -// -// Contributors: -// ZettaScale Zenoh Team, -// - -package io.zenoh.jni - -import io.zenoh.ZenohLoad - -internal object JNIZenohId { - - init { - ZenohLoad - } - - external fun toStringViaJNI(bytes: ByteArray): String - -} diff --git a/zenoh-kotlin/src/commonMain/kotlin/io/zenoh/jni/callbacks/JNIGetCallback.kt b/zenoh-kotlin/src/commonMain/kotlin/io/zenoh/jni/callbacks/JNIGetCallback.kt deleted file mode 100644 index 14f3c8e98..000000000 --- a/zenoh-kotlin/src/commonMain/kotlin/io/zenoh/jni/callbacks/JNIGetCallback.kt +++ /dev/null @@ -1,35 +0,0 @@ -// -// Copyright (c) 2023 ZettaScale Technology -// -// This program and the accompanying materials are made available under the -// terms of the Eclipse Public License 2.0 which is available at -// http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 -// which is available at https://www.apache.org/licenses/LICENSE-2.0. -// -// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 -// -// Contributors: -// ZettaScale Zenoh Team, -// - -package io.zenoh.jni.callbacks - -internal fun interface JNIGetCallback { - - fun run( - replierZid: ByteArray?, - replierEid: Int, - success: Boolean, - keyExpr: String?, - payload: ByteArray, - encodingId: Int, - encodingSchema: String?, - kind: Int, - timestampNTP64: Long, - timestampIsValid: Boolean, - attachment: ByteArray?, - express: Boolean, - priority: Int, - congestionControl: Int, - ) -} diff --git a/zenoh-kotlin/src/commonMain/kotlin/io/zenoh/jni/callbacks/JNIMatchingListenerCallback.kt b/zenoh-kotlin/src/commonMain/kotlin/io/zenoh/jni/callbacks/JNIMatchingListenerCallback.kt deleted file mode 100644 index d056c47dd..000000000 --- a/zenoh-kotlin/src/commonMain/kotlin/io/zenoh/jni/callbacks/JNIMatchingListenerCallback.kt +++ /dev/null @@ -1,21 +0,0 @@ -// -// Copyright (c) 2025 ZettaScale Technology -// -// This program and the accompanying materials are made available under the -// terms of the Eclipse Public License 2.0 which is available at -// http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 -// which is available at https://www.apache.org/licenses/LICENSE-2.0. -// -// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 -// -// Contributors: -// ZettaScale Zenoh Team, -// - -package io.zenoh.jni.callbacks - -internal fun interface JNIMatchingListenerCallback { - fun run( - matching: Boolean, - ) -} diff --git a/zenoh-kotlin/src/commonMain/kotlin/io/zenoh/jni/callbacks/JNIOnCloseCallback.kt b/zenoh-kotlin/src/commonMain/kotlin/io/zenoh/jni/callbacks/JNIOnCloseCallback.kt deleted file mode 100644 index b58fa23da..000000000 --- a/zenoh-kotlin/src/commonMain/kotlin/io/zenoh/jni/callbacks/JNIOnCloseCallback.kt +++ /dev/null @@ -1,21 +0,0 @@ -// -// Copyright (c) 2023 ZettaScale Technology -// -// This program and the accompanying materials are made available under the -// terms of the Eclipse Public License 2.0 which is available at -// http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 -// which is available at https://www.apache.org/licenses/LICENSE-2.0. -// -// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 -// -// Contributors: -// ZettaScale Zenoh Team, -// - -package io.zenoh.jni.callbacks - -internal fun interface JNIOnCloseCallback { - - fun run() - -} diff --git a/zenoh-kotlin/src/commonMain/kotlin/io/zenoh/jni/callbacks/JNIQueryableCallback.kt b/zenoh-kotlin/src/commonMain/kotlin/io/zenoh/jni/callbacks/JNIQueryableCallback.kt deleted file mode 100644 index 31f5885f8..000000000 --- a/zenoh-kotlin/src/commonMain/kotlin/io/zenoh/jni/callbacks/JNIQueryableCallback.kt +++ /dev/null @@ -1,25 +0,0 @@ -// -// Copyright (c) 2023 ZettaScale Technology -// -// This program and the accompanying materials are made available under the -// terms of the Eclipse Public License 2.0 which is available at -// http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 -// which is available at https://www.apache.org/licenses/LICENSE-2.0. -// -// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 -// -// Contributors: -// ZettaScale Zenoh Team, -// - -package io.zenoh.jni.callbacks - -internal fun interface JNIQueryableCallback { - fun run(keyExpr: String, - selectorParams: String, - payload: ByteArray?, - encodingId: Int, - encodingSchema: String?, - attachmentBytes: ByteArray?, - queryPtr: Long) -} diff --git a/zenoh-kotlin/src/commonMain/kotlin/io/zenoh/jni/callbacks/JNIScoutCallback.kt b/zenoh-kotlin/src/commonMain/kotlin/io/zenoh/jni/callbacks/JNIScoutCallback.kt deleted file mode 100644 index 0a8b20e99..000000000 --- a/zenoh-kotlin/src/commonMain/kotlin/io/zenoh/jni/callbacks/JNIScoutCallback.kt +++ /dev/null @@ -1,20 +0,0 @@ -// -// Copyright (c) 2023 ZettaScale Technology -// -// This program and the accompanying materials are made available under the -// terms of the Eclipse Public License 2.0 which is available at -// http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 -// which is available at https://www.apache.org/licenses/LICENSE-2.0. -// -// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 -// -// Contributors: -// ZettaScale Zenoh Team, -// - -package io.zenoh.jni.callbacks - -internal fun interface JNIScoutCallback { - - fun run(whatAmI: Int, zid: ByteArray, locators: List) -} diff --git a/zenoh-kotlin/src/commonMain/kotlin/io/zenoh/jni/callbacks/JNISubscriberCallback.kt b/zenoh-kotlin/src/commonMain/kotlin/io/zenoh/jni/callbacks/JNISubscriberCallback.kt deleted file mode 100644 index 76373c72b..000000000 --- a/zenoh-kotlin/src/commonMain/kotlin/io/zenoh/jni/callbacks/JNISubscriberCallback.kt +++ /dev/null @@ -1,31 +0,0 @@ -// -// Copyright (c) 2023 ZettaScale Technology -// -// This program and the accompanying materials are made available under the -// terms of the Eclipse Public License 2.0 which is available at -// http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 -// which is available at https://www.apache.org/licenses/LICENSE-2.0. -// -// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 -// -// Contributors: -// ZettaScale Zenoh Team, -// - -package io.zenoh.jni.callbacks - -internal fun interface JNISubscriberCallback { - fun run( - keyExpr: String, - payload: ByteArray, - encodingId: Int, - encodingSchema: String?, - kind: Int, - timestampNTP64: Long, - timestampIsValid: Boolean, - attachment: ByteArray?, - express: Boolean, - priority: Int, - congestionControl: Int, - ) -} diff --git a/zenoh-kotlin/src/commonMain/kotlin/io/zenoh/keyexpr/KeyExpr.kt b/zenoh-kotlin/src/commonMain/kotlin/io/zenoh/keyexpr/KeyExpr.kt index 4a2cae3a2..445c5e0c9 100644 --- a/zenoh-kotlin/src/commonMain/kotlin/io/zenoh/keyexpr/KeyExpr.kt +++ b/zenoh-kotlin/src/commonMain/kotlin/io/zenoh/keyexpr/KeyExpr.kt @@ -15,8 +15,13 @@ package io.zenoh.keyexpr import io.zenoh.Session +import io.zenoh.exceptions.throwZError +import io.zenoh.exceptions.throwZError0 +import io.zenoh.exceptions.zCall +import io.zenoh.jni.ErrorHandler +import io.zenoh.jni.JniErrorHandler +import io.zenoh.jni.keyexpr.KeyExpr as JniKeyExpr import io.zenoh.session.SessionDeclaration -import io.zenoh.jni.JNIKeyExpr /** * # Address space @@ -46,18 +51,78 @@ import io.zenoh.jni.JNIKeyExpr * * # Declaring a key expression from a session. * - * A [KeyExpr] acts as a container for the string representation of a key expression. Operations like `intersects`, - * `includes`, and `equals` are processed at the native layer using this string representation. For improved performance, - * consider initializing a [KeyExpr] through [Session.declareKeyExpr]. This method associates the [KeyExpr] with a native - * instance, thereby optimizing operation execution. + * A [KeyExpr] is its validated string. A native instance exists ONLY behind a + * [Session.declareKeyExpr] result — the single case where zenoh attaches a wire + * declaration (a compact id replacing the string on the wire) worth carrying; + * such an instance should be [close]d (or `use`d) when no longer needed. + * Every other [KeyExpr] — constructed via [tryFrom]/[autocanonize] or received + * with a sample — is a plain value: nothing to close, no native resource. * * For more information, checkout the [key expressions RFC](https://github.com/eclipse-zenoh/roadmap/blob/main/rfcs/ALL/Key%20Expressions.md). */ -class KeyExpr internal constructor(internal val keyExpr: String, internal var jniKeyExpr: JNIKeyExpr? = null): AutoCloseable, - SessionDeclaration { +class KeyExpr internal constructor( + internal val keyExpr: String, + /** + * The owned native handle — non-null ONLY for [Session.declareKeyExpr] + * results, whose wire declaration makes sends through the declaring + * session compact. Everything else is string-backed. + */ + internal var jniKeyExpr: JniKeyExpr? = null +) : AutoCloseable, SessionDeclaration { + + /** Clone the native handle before passing it to a consuming Rust API. */ + internal fun cloneHandle(): JniKeyExpr? = jniKeyExpr?.newClone(throwZError0) + + /** + * A native handle to hand to a *consuming* Rust API, whichever backing + * this key expression has: the declared handle cloned, or a fresh one + * validated from the string. The callee takes ownership either way, so + * this never yields the shared declared handle itself. + * + * Unlike the [jniSel]/[jniStr]/[jniHandle] slot trio, this materializes a + * handle for a string-backed key expression rather than letting Rust + * rebuild it in-call - needed where the parameter is a whole value (a + * [io.zenoh.jni.query.Selector]) with no string arm to select. + */ + internal fun intoJniHandle(): JniKeyExpr = + cloneHandle() ?: JniKeyExpr.newTryFrom(keyExpr, throwZError0, throwZError) + + /** + * Run [body] with a native handle: the declared handle when present, + * else a transient one validated from the string (closed afterwards). + * Used by the native keyexpr algebra ops. + */ + private inline fun withHandle(body: (JniKeyExpr) -> R): R { + val h = jniKeyExpr + if (h != null) return body(h) + val tmp = JniKeyExpr.newTryFrom(keyExpr, throwZError0, throwZError) + try { + return body(tmp) + } finally { + tmp.close() + } + } companion object { + /** + * Builds a [KeyExpr] from the canonical string of a probe handle: + * the probe construction failure (native, via the sink) and the + * (binding-only) string read failure both surface as + * [Result.failure]. + */ + private inline fun fromProbe( + crossinline makeProbe: (JniErrorHandler, ErrorHandler) -> JniKeyExpr + ): Result = + zCall({ JniKeyExpr(0L) }) { onBindingError, onError -> makeProbe(onBindingError, onError) } + .mapCatching { probe -> + try { + KeyExpr(probe.asStr(throwZError0)) + } finally { + probe.close() + } + } + /** * Try from. * @@ -66,12 +131,21 @@ class KeyExpr internal constructor(internal val keyExpr: String, internal var jn * * You may use [autocanonize] instead if you are unsure if the expression you will use for construction will be canon. * + * The result is string-backed: the expression is validated natively + * once and the probe handle is released — an undeclared native + * keyexpr carries no state beyond its string. + * * @param keyExpr The intended key expression as a string. * @return a [Result] with the [KeyExpr] in case of success. */ - fun tryFrom(keyExpr: String) : Result { - return JNIKeyExpr.tryFrom(keyExpr) - } + fun tryFrom(keyExpr: String): Result = + zCall({ JniKeyExpr(0L) }) { onBindingError, onError -> + JniKeyExpr.newTryFrom(keyExpr, onBindingError, onError) + } + .map { probe -> + probe.close() + KeyExpr(keyExpr) + } /** * Autocanonize. @@ -82,9 +156,10 @@ class KeyExpr internal constructor(internal val keyExpr: String, internal var jn * @param keyExpr The intended key expression as a string. * @return a [Result] with the canonized [KeyExpr] in case of success. */ - fun autocanonize(keyExpr: String): Result { - return JNIKeyExpr.autocanonize(keyExpr) - } + fun autocanonize(keyExpr: String): Result = + fromProbe { onBindingError, onError -> + JniKeyExpr.newAutocanonize(keyExpr, onBindingError, onError) + } } /** @@ -92,8 +167,9 @@ class KeyExpr internal constructor(internal val keyExpr: String, internal var jn * * This method returns `True` if there exists at least one key that belongs to both sets defined by `this` and the `other` key expressions. */ - fun intersects(other: KeyExpr): Boolean { - return JNIKeyExpr.intersects(this, other) + fun intersects(other: KeyExpr): Boolean = withHandle { h -> + other.jniKeyExpr?.let { h.intersects(it, throwZError0) } + ?: h.intersects(other.keyExpr, throwZError0) } /** @@ -101,8 +177,9 @@ class KeyExpr internal constructor(internal val keyExpr: String, internal var jn * * This method returns `true` when all the keys defined by `other` also belong to the set defined by `this`. */ - fun includes(other: KeyExpr): Boolean { - return JNIKeyExpr.includes(this, other) + fun includes(other: KeyExpr): Boolean = withHandle { h -> + other.jniKeyExpr?.let { h.includes(it, throwZError0) } + ?: h.includes(other.keyExpr, throwZError0) } /** @@ -110,24 +187,28 @@ class KeyExpr internal constructor(internal val keyExpr: String, internal var jn * signifies that `this` includes other). Note that this is slower than [intersects] and [includes], * so you should favor these methods for most applications. */ - fun relationTo(other: KeyExpr): SetIntersectionLevel { - return JNIKeyExpr.relationTo(this, other) + fun relationTo(other: KeyExpr): SetIntersectionLevel = withHandle { h -> + val raw = other.jniKeyExpr?.let { h.relationTo(it, throwZError0) } + ?: h.relationTo(other.keyExpr, throwZError0) + SetIntersectionLevel.fromInt(raw.value) } /** * Joins both sides, inserting a `/` in between them. * This should be your preferred method when concatenating path segments. */ - fun join(other: String): Result { - return JNIKeyExpr.joinViaJNI(this, other) + fun join(other: String): Result = fromProbe { onBindingError, onError -> + jniKeyExpr?.let { JniKeyExpr.newJoin(it, other, onBindingError, onError) } + ?: JniKeyExpr.newJoin(keyExpr, other, onBindingError, onError) } /** * Performs string concatenation and returns the result as a `KeyExpr` if possible. * You should probably prefer [join] as Zenoh may then take advantage of the hierarchical separation it inserts. */ - fun concat(other: String): Result { - return JNIKeyExpr.concatViaJNI(this, other) + fun concat(other: String): Result = fromProbe { onBindingError, onError -> + jniKeyExpr?.let { JniKeyExpr.newConcat(it, other, onBindingError, onError) } + ?: JniKeyExpr.newConcat(keyExpr, other, onBindingError, onError) } override fun toString(): String { @@ -166,3 +247,19 @@ class KeyExpr internal constructor(internal val keyExpr: String, internal var jn return keyExpr.hashCode() } } + +// The three slots of the generated key-expr selector block (`keyExprSel, +// keyExpr0, keyExpr1`), computed from one [KeyExpr] in a single expression per +// slot so every send call site stays one flat call: declared -> the bare +// handle (arm 1), string-backed -> the validated string rebuilt Rust-side +// inside the same call (arm 0). For CONSUMING params use [KeyExpr.cloneHandle] +// in the third slot instead — the handle arm takes the value by move. + +internal val KeyExpr.jniSel: Int + get() = if (jniKeyExpr != null) 1 else 0 + +internal val KeyExpr.jniStr: String? + get() = if (jniKeyExpr == null) keyExpr else null + +internal val KeyExpr.jniHandle: JniKeyExpr? + get() = jniKeyExpr diff --git a/zenoh-kotlin/src/commonMain/kotlin/io/zenoh/liveliness/Liveliness.kt b/zenoh-kotlin/src/commonMain/kotlin/io/zenoh/liveliness/Liveliness.kt index 2a5b449a4..863438915 100644 --- a/zenoh-kotlin/src/commonMain/kotlin/io/zenoh/liveliness/Liveliness.kt +++ b/zenoh-kotlin/src/commonMain/kotlin/io/zenoh/liveliness/Liveliness.kt @@ -15,14 +15,22 @@ package io.zenoh.liveliness import io.zenoh.Session +import io.zenoh.exceptions.zCall +import io.zenoh.exceptions.zCallUnit import io.zenoh.handlers.Callback import io.zenoh.handlers.ChannelHandler import io.zenoh.handlers.Handler -import io.zenoh.jni.JNILiveliness +import io.zenoh.jni.liveliness.LivelinessToken as JniLivelinessToken +import io.zenoh.jni.pubsub.Subscriber as JniSubscriber import io.zenoh.keyexpr.KeyExpr +import io.zenoh.keyexpr.jniHandle +import io.zenoh.keyexpr.jniSel +import io.zenoh.keyexpr.jniStr import io.zenoh.pubsub.Subscriber import io.zenoh.query.Reply +import io.zenoh.replyCallbackOf import io.zenoh.sample.Sample +import io.zenoh.sampleCallbackOf import kotlinx.coroutines.channels.Channel import java.time.Duration @@ -41,9 +49,14 @@ class Liveliness internal constructor(private val session: Session) { /** * Create a LivelinessToken for the given key expression. */ - fun declareToken(keyExpr: KeyExpr): Result = runCatching { - val jniSession = session.jniSession ?: throw Session.sessionClosedException - JNILiveliness.declareToken(jniSession, keyExpr) + fun declareToken(keyExpr: KeyExpr): Result { + val jniSession = session.jniSession ?: return Result.failure(Session.sessionClosedException) + return zCall({ JniLivelinessToken(0L) }) { onBindingError, onError -> + jniSession.livelinessDeclareToken( + keyExpr.jniSel, keyExpr.jniStr, keyExpr.cloneHandle(), + onBindingError, onError + ) + }.map { LivelinessToken(it) } } /** @@ -56,11 +69,7 @@ class Liveliness internal constructor(private val session: Session) { */ fun get( keyExpr: KeyExpr, callback: Callback, timeout: Duration = Duration.ofMillis(10000) - ): Result = - runCatching { - val jniSession = session.jniSession ?: throw Session.sessionClosedException - return JNILiveliness.get(jniSession, keyExpr, callback, Unit, timeout, {}) - } + ): Result = performGet(keyExpr, callback, {}, Unit, timeout) /** * Query the liveliness tokens with matching key expressions. @@ -73,20 +82,7 @@ class Liveliness internal constructor(private val session: Session) { */ fun get( keyExpr: KeyExpr, handler: Handler, timeout: Duration = Duration.ofMillis(10000) - ): Result = - runCatching { - val jniSession = session.jniSession ?: throw Session.sessionClosedException - val callback = handler::handle - return JNILiveliness.get( - jniSession, - keyExpr, - callback, - handler.receiver(), - timeout, - onClose = handler::onClose - ) - - } + ): Result = performGet(keyExpr, handler::handle, handler::onClose, handler.receiver(), timeout) /** * Query the liveliness tokens with matching key expressions. @@ -102,16 +98,8 @@ class Liveliness internal constructor(private val session: Session) { channel: Channel, timeout: Duration = Duration.ofMillis(10000), ): Result> { - return session.jniSession?.let { - val channelHandler = ChannelHandler(channel) - JNILiveliness.get(it, - keyExpr, - channelHandler::handle, - receiver = channelHandler.receiver(), - timeout, - onClose = channelHandler::onClose - ) - } ?: Result.failure(Session.sessionClosedException) + val channelHandler = ChannelHandler(channel) + return performGet(keyExpr, channelHandler::handle, channelHandler::onClose, channelHandler.receiver(), timeout) } /** @@ -128,16 +116,8 @@ class Liveliness internal constructor(private val session: Session) { callback: Callback, history: Boolean = false, onClose: (() -> Unit)? = null - ): Result> = runCatching { - val jniSession = session.jniSession ?: throw Session.sessionClosedException - return JNILiveliness.declareSubscriber( - jniSession, - keyExpr, - callback, - Unit, - history, - fun() { onClose?.invoke() }) - } + ): Result> = + performDeclareSubscriber(keyExpr, callback, fun() { onClose?.invoke() }, Unit, history) /** * Create a [Subscriber] for liveliness changes matching the given key expression. @@ -154,19 +134,11 @@ class Liveliness internal constructor(private val session: Session) { handler: Handler, history: Boolean = false, onClose: (() -> Unit)? = null - ): Result> = runCatching { - val jniSession = session.jniSession ?: throw Session.sessionClosedException - return JNILiveliness.declareSubscriber( - jniSession, - keyExpr, - handler::handle, - handler.receiver(), - history, - fun() { - handler.onClose() - onClose?.invoke() - }) - } + ): Result> = + performDeclareSubscriber(keyExpr, handler::handle, fun() { + handler.onClose() + onClose?.invoke() + }, handler.receiver(), history) /** * Create a [Subscriber] for liveliness changes matching the given key expression. @@ -182,18 +154,49 @@ class Liveliness internal constructor(private val session: Session) { channel: Channel, history: Boolean = false, onClose: (() -> Unit)? = null - ): Result>> = runCatching { - val jniSession = session.jniSession ?: throw Session.sessionClosedException + ): Result>> { val channelHandler = ChannelHandler(channel) - return JNILiveliness.declareSubscriber( - jniSession, - keyExpr, - channelHandler::handle, - channelHandler.receiver(), - history, - fun() { - channelHandler.onClose() - onClose?.invoke() - }) + return performDeclareSubscriber(keyExpr, channelHandler::handle, fun() { + channelHandler.onClose() + onClose?.invoke() + }, channelHandler.receiver(), history) + } + + private fun performGet( + keyExpr: KeyExpr, + callback: Callback, + onClose: () -> Unit, + receiver: R, + timeout: Duration + ): Result { + val jniSession = session.jniSession ?: return Result.failure(Session.sessionClosedException) + return zCallUnit { onBindingError, onError -> + jniSession.livelinessGet( + keyExpr.jniSel, keyExpr.jniStr, keyExpr.jniHandle, + timeout.toMillis(), + replyCallbackOf { callback.run(it) }, + { onClose() }, + onBindingError, onError + ) + }.map { receiver } + } + + private fun performDeclareSubscriber( + keyExpr: KeyExpr, + callback: Callback, + onClose: () -> Unit, + receiver: R, + history: Boolean + ): Result> { + val jniSession = session.jniSession ?: return Result.failure(Session.sessionClosedException) + return zCall({ JniSubscriber(0L) }) { onBindingError, onError -> + jniSession.livelinessDeclareSubscriber( + keyExpr.jniSel, keyExpr.jniStr, keyExpr.cloneHandle(), + history, + sampleCallbackOf { callback.run(it) }, + { onClose() }, + onBindingError, onError + ) + }.map { Subscriber(keyExpr, receiver, it) } } } diff --git a/zenoh-kotlin/src/commonMain/kotlin/io/zenoh/liveliness/LivelinessToken.kt b/zenoh-kotlin/src/commonMain/kotlin/io/zenoh/liveliness/LivelinessToken.kt index 2e70f9b74..b92de5593 100644 --- a/zenoh-kotlin/src/commonMain/kotlin/io/zenoh/liveliness/LivelinessToken.kt +++ b/zenoh-kotlin/src/commonMain/kotlin/io/zenoh/liveliness/LivelinessToken.kt @@ -14,7 +14,7 @@ package io.zenoh.liveliness -import io.zenoh.jni.JNILivelinessToken +import io.zenoh.jni.liveliness.LivelinessToken as JniLivelinessToken import io.zenoh.session.SessionDeclaration /** @@ -37,13 +37,13 @@ import io.zenoh.session.SessionDeclaration * //.... * ``` */ -class LivelinessToken internal constructor(private var jniLivelinessToken: JNILivelinessToken?): SessionDeclaration, AutoCloseable { +class LivelinessToken internal constructor(private var jniLivelinessToken: JniLivelinessToken?): SessionDeclaration, AutoCloseable { /** * Undeclares the token. */ override fun undeclare() { - jniLivelinessToken?.undeclare() + jniLivelinessToken?.close() jniLivelinessToken = null } diff --git a/zenoh-kotlin/src/commonMain/kotlin/io/zenoh/pubsub/AdvancedPublisher.kt b/zenoh-kotlin/src/commonMain/kotlin/io/zenoh/pubsub/AdvancedPublisher.kt index 765ba6263..f6878b228 100644 --- a/zenoh-kotlin/src/commonMain/kotlin/io/zenoh/pubsub/AdvancedPublisher.kt +++ b/zenoh-kotlin/src/commonMain/kotlin/io/zenoh/pubsub/AdvancedPublisher.kt @@ -16,35 +16,49 @@ package io.zenoh.pubsub import io.zenoh.annotations.Unstable import io.zenoh.exceptions.ZError -import io.zenoh.jni.JNIAdvancedPublisher +import io.zenoh.exceptions.zCall +import io.zenoh.exceptions.zCallUnit import io.zenoh.keyexpr.KeyExpr import io.zenoh.bytes.Encoding +import io.zenoh.bytes.jniHandle +import io.zenoh.bytes.jniId +import io.zenoh.bytes.jniSchema +import io.zenoh.bytes.jniSel import io.zenoh.qos.QoS import io.zenoh.bytes.IntoZBytes import io.zenoh.bytes.ZBytes import io.zenoh.handlers.MatchingCallback import io.zenoh.handlers.MatchingChannelHandler import io.zenoh.handlers.MatchingHandler +import io.zenoh.jni.VoidCallback +import io.zenoh.jni.boolCallback +import io.zenoh.jni.pubsub.AdvancedPublisher as JniAdvancedPublisher +import io.zenoh.jni.pubsub.MatchingListener as JniMatchingListener import io.zenoh.session.SessionDeclaration import kotlinx.coroutines.channels.Channel /** * # Advanced Publisher * - * A [Publisher] with advanced features. + * A [Publisher] with advanced features: sample-miss detection, publisher + * detection so that advanced subscribers can discover it, a retransmission + * cache, and matching-status notifications. * * @see Publisher + * @see io.zenoh.Session.declareAdvancedPublisher */ @Unstable class AdvancedPublisher internal constructor( val keyExpr: KeyExpr, val qos: QoS, val encoding: Encoding, - private var jniPublisher: JNIAdvancedPublisher?, + private var jniAdvancedPublisher: JniAdvancedPublisher?, ) : SessionDeclaration, AutoCloseable { - inline fun invalidPublisherResult(): Result = - Result.failure(ZError("AdvancedPublisher is not valid.")) + companion object { + private fun invalidPublisherResult(): Result = + Result.failure(ZError("AdvancedPublisher is not valid.")) + } /** Get the congestion control applied when routing the data. */ fun congestionControl() = qos.congestionControl @@ -52,114 +66,141 @@ class AdvancedPublisher internal constructor( /** Get the priority of the written data. */ fun priority() = qos.priority - /** Declare [MatchingListener] for this publisher with callback + /** Declare a [MatchingListener] for this publisher with a callback. * - * @param callback: callback to be executed when matching status changes - * @param onClose: callback to be executed when associated matching listener will be closed - * */ - fun declareMatchingListener(callback: MatchingCallback, - onClose: (() -> Unit)? = null,): Result { - val resolvedOnClose = fun() { - onClose?.invoke() - } - return jniPublisher?.declareMatchingListener(callback, resolvedOnClose)?: invalidPublisherResult() - } + * @param callback callback to be executed when the matching status changes. + * @param onClose callback to be executed when the matching listener is closed. + */ + fun declareMatchingListener( + callback: MatchingCallback, + onClose: (() -> Unit)? = null, + ): Result = + resolveMatchingListener(callback, onClose ?: {}, background = false) - /** Declare [MatchingListener] for this publisher, specifying a handler to handle matching statuses. + /** Declare a [MatchingListener] for this publisher, specifying a handler. * * @param handler [MatchingHandler] implementation to handle the matching statuses. - * [MatchingHandler.onClose] will be called upon closing the associated [AdvancedPublisher] or dropping [MatchingListener]. - * @param onClose: callback to be executed when associated [AdvancedPublisher] is closed or [MatchingListener] is dropped. + * @param onClose callback to be executed when the matching listener is closed. */ - fun declareMatchingListener(handler: MatchingHandler, - onClose: (() -> Unit)? = null,): Result { - val resolvedOnClose = fun() { - handler.onClose() - onClose?.invoke() - } - val callback = MatchingCallback { matching: Boolean -> handler.handle(matching) } - return jniPublisher?.declareMatchingListener(callback, resolvedOnClose)?: invalidPublisherResult() - } - - /** Declare [MatchingListener] for this publisher, specifying a [Channel] to pipe the received matching statuses. - * - * @param channel [Channel] instance through which the matching statuses will be piped. - * Once the [AdvancedPublisher] is closed or [MatchingListener] is dropped, the channel is closed as well. - * @param onClose: callback to be executed when associated [AdvancedPublisher] is closed or [MatchingListener] is dropped - * */ - fun declareMatchingListener(channel: Channel, - onClose: (() -> Unit)? = null,): Result { - val channelHandler = MatchingChannelHandler(channel) - val resolvedOnClose = fun() { - channelHandler.onClose() - onClose?.invoke() - } - val callback = MatchingCallback { matching: Boolean -> channelHandler.handle(matching) } - return jniPublisher?.declareMatchingListener(callback, resolvedOnClose)?: invalidPublisherResult() - } - - /** Declare background matching status listener for this [AdvancedPublisher] with callback + fun declareMatchingListener( + handler: MatchingHandler, + onClose: (() -> Unit)? = null, + ): Result = + resolveMatchingListener( + MatchingCallback { handler.handle(it) }, + { handler.onClose(); onClose?.invoke() }, + background = false, + ) + + /** Declare a [MatchingListener] for this publisher, piping statuses to a [Channel]. * - * @param callback: callback to be executed when matching status changes - * @param onClose: callback to be executed when associated [AdvancedPublisher] will be closed - * */ - fun declareBackgroundMatchingListener(callback: MatchingCallback, - onClose: (() -> Unit)? = null,): Result { - val resolvedOnClose = fun() { - onClose?.invoke() - } - return jniPublisher?.declareBackgroundMatchingListener(callback, resolvedOnClose)?: invalidPublisherResult() + * @param channel [Channel] the matching statuses will be piped through. + * @param onClose callback to be executed when the matching listener is closed. + */ + fun declareMatchingListener( + channel: Channel, + onClose: (() -> Unit)? = null, + ): Result { + val handler = MatchingChannelHandler(channel) + return resolveMatchingListener( + MatchingCallback { handler.handle(it) }, + { handler.onClose(); onClose?.invoke() }, + background = false, + ) } - /** Declare background matching status listener for this [AdvancedPublisher], specifying a handler to handle matching statuses. + /** Declare a background matching status listener for this publisher with a callback. * - * @param handler [MatchingHandler] implementation to handle the received samples. [MatchingHandler.onClose] will be called upon closing associated [AdvancedPublisher]. - * @param onClose: callback to be executed when associated [AdvancedPublisher] will be closed - * */ - fun declareBackgroundMatchingListener(handler: MatchingHandler, - onClose: (() -> Unit)? = null,): Result { - val resolvedOnClose = fun() { - handler.onClose() - onClose?.invoke() - } - val callback = MatchingCallback { matching: Boolean -> handler.handle(matching) } - return jniPublisher?.declareBackgroundMatchingListener(callback, resolvedOnClose)?: invalidPublisherResult() + * The listener runs in the background until the [AdvancedPublisher] is undeclared. + */ + fun declareBackgroundMatchingListener( + callback: MatchingCallback, + onClose: (() -> Unit)? = null, + ): Result = + resolveMatchingListener(callback, onClose ?: {}, background = true).map {} + + /** Declare a background matching status listener for this publisher, specifying a handler. */ + fun declareBackgroundMatchingListener( + handler: MatchingHandler, + onClose: (() -> Unit)? = null, + ): Result = + resolveMatchingListener( + MatchingCallback { handler.handle(it) }, + { handler.onClose(); onClose?.invoke() }, + background = true, + ).map {} + + /** Declare a background matching status listener for this publisher, piping statuses to a [Channel]. */ + fun declareBackgroundMatchingListener( + channel: Channel, + onClose: (() -> Unit)? = null, + ): Result { + val handler = MatchingChannelHandler(channel) + return resolveMatchingListener( + MatchingCallback { handler.handle(it) }, + { handler.onClose(); onClose?.invoke() }, + background = true, + ).map {} } - /** Declare background matching status listener for this [AdvancedPublisher], specifying a [Channel] to pipe the received matching statuses. - * - * @param channel [Channel] instance through which the matching statuses will be piped. - * @param onClose: callback to be executed when associated [AdvancedPublisher] will be closed - * */ - fun declareBackgroundMatchingListener(channel: Channel, - onClose: (() -> Unit)? = null,): Result { - val channelHandler = MatchingChannelHandler(channel) - val resolvedOnClose = fun() { - channelHandler.onClose() - onClose?.invoke() + private fun resolveMatchingListener( + callback: MatchingCallback, + onClose: () -> Unit, + background: Boolean, + ): Result { + val p = jniAdvancedPublisher ?: return invalidPublisherResult() + val jniCallback = boolCallback { matching -> callback.run(matching) } + val jniOnClose = VoidCallback { onClose() } + return if (background) { + // Background listeners register no returnable handle — they live + // until the publisher ends, so the wrapper holds a null handle. + zCallUnit { onBindingError, onError -> + p.declareBackgroundMatchingListener(jniCallback, jniOnClose, onBindingError, onError) + }.map { MatchingListener(null) } + } else { + zCall({ JniMatchingListener(0L) }) { onBindingError, onError -> + p.declareMatchingListener(jniCallback, jniOnClose, onBindingError, onError) + }.map { MatchingListener(it) } } - val callback = MatchingCallback { matching: Boolean -> channelHandler.handle(matching) } - return jniPublisher?.declareBackgroundMatchingListener(callback, resolvedOnClose)?: invalidPublisherResult() } /** * Return the matching status of the [AdvancedPublisher]. * - * Will return true if there exist Subscribers matching the Publisher's key expression and false otherwise. + * Returns `true` if subscribers matching the publisher's key expression exist. */ - fun getMatchingStatus() = jniPublisher?.getMatchingStatus() ?: invalidPublisherResult() + fun getMatchingStatus(): Result { + val p = jniAdvancedPublisher ?: return invalidPublisherResult() + return zCall({ false }) { onBindingError, onError -> + p.matchingStatus(onBindingError, onError) + } + } - /** Performs a PUT operation on the specified [keyExpr] with the specified [payload]. */ - fun put(payload: IntoZBytes, encoding: Encoding? = null, attachment: IntoZBytes? = null) = - jniPublisher?.put(payload, encoding ?: this.encoding, attachment) ?: invalidPublisherResult() + /** Performs a PUT operation on the publisher's [keyExpr] with the specified [payload]. */ + fun put(payload: IntoZBytes, encoding: Encoding? = null, attachment: IntoZBytes? = null): Result { + val p = jniAdvancedPublisher ?: return invalidPublisherResult() + return zCallUnit { onBindingError, onError -> + p.put( + payload.into().bytes, + encoding.jniSel, encoding.jniId, encoding.jniSchema, encoding.jniHandle, + attachment?.into()?.bytes, + onBindingError, onError + ) + } + } fun put(payload: String, encoding: Encoding? = null, attachment: String? = null) = put(ZBytes.from(payload), encoding, attachment?.let { ZBytes.from(attachment) }) /** - * Performs a DELETE operation on the specified [keyExpr]. + * Performs a DELETE operation on the publisher's [keyExpr]. */ - fun delete(attachment: IntoZBytes? = null) = jniPublisher?.delete(attachment) ?: invalidPublisherResult() + fun delete(attachment: IntoZBytes? = null): Result { + val p = jniAdvancedPublisher ?: return invalidPublisherResult() + return zCallUnit { onBindingError, onError -> + p.delete(attachment?.into()?.bytes, onBindingError, onError) + } + } fun delete(attachment: String) = delete(ZBytes.from(attachment)) @@ -167,7 +208,7 @@ class AdvancedPublisher internal constructor( * Returns `true` if the publisher is still running. */ fun isValid(): Boolean { - return jniPublisher != null + return jniAdvancedPublisher != null } /** @@ -184,8 +225,8 @@ class AdvancedPublisher internal constructor( * Further operations performed with the publisher will not be valid anymore. */ override fun undeclare() { - jniPublisher?.close() - jniPublisher = null + jniAdvancedPublisher?.close() + jniAdvancedPublisher = null } protected fun finalize() { diff --git a/zenoh-kotlin/src/commonMain/kotlin/io/zenoh/pubsub/AdvancedSubscriber.kt b/zenoh-kotlin/src/commonMain/kotlin/io/zenoh/pubsub/AdvancedSubscriber.kt index 198061e0b..8e6c4f29c 100644 --- a/zenoh-kotlin/src/commonMain/kotlin/io/zenoh/pubsub/AdvancedSubscriber.kt +++ b/zenoh-kotlin/src/commonMain/kotlin/io/zenoh/pubsub/AdvancedSubscriber.kt @@ -16,32 +16,50 @@ package io.zenoh.pubsub import io.zenoh.annotations.Unstable import io.zenoh.exceptions.ZError +import io.zenoh.exceptions.zCall +import io.zenoh.exceptions.zCallUnit import io.zenoh.handlers.Callback import io.zenoh.handlers.ChannelHandler import io.zenoh.handlers.Handler import io.zenoh.handlers.SampleMissCallback import io.zenoh.handlers.SampleMissChannelHandler import io.zenoh.handlers.SampleMissHandler -import io.zenoh.jni.JNIAdvancedSubscriber +import io.zenoh.jni.VoidCallback +import io.zenoh.jni.pubsub.AdvancedSubscriber as JniAdvancedSubscriber +import io.zenoh.jni.pubsub.SampleMissListener as JniSampleMissListener +import io.zenoh.jni.pubsub.Subscriber as JniSubscriber import io.zenoh.keyexpr.KeyExpr import io.zenoh.sample.Sample +import io.zenoh.sampleCallbackOf +import io.zenoh.sampleMissCallbackOf import io.zenoh.session.SessionDeclaration import kotlinx.coroutines.channels.Channel /** * # Advanced Subscriber * - * A [Subscriber] with advanced capabilities. + * A [Subscriber] with advanced capabilities: it can query historical data, + * recover missed samples, detect the [AdvancedPublisher]s matching its key + * expression, and report missed samples via a [SampleMissListener]. + * + * A background detect-publishers subscriber or sample-miss listener (declared + * via the `declareBackground*` methods) has no returnable handle — it lives + * until this [AdvancedSubscriber] is undeclared. * * @see Subscriber + * @see io.zenoh.Session.declareAdvancedSubscriber */ @Unstable class AdvancedSubscriber internal constructor( - val keyExpr: KeyExpr, val receiver: R, private var jniSubscriber: JNIAdvancedSubscriber? + val keyExpr: KeyExpr, + val receiver: R, + private var jniAdvancedSubscriber: JniAdvancedSubscriber?, ) : AutoCloseable, SessionDeclaration { - inline fun invalidSubscriberResult(): Result = - Result.failure(ZError("AdvancedSubscriber is not valid.")) + companion object { + private fun invalidSubscriberResult(): Result = + Result.failure(ZError("AdvancedSubscriber is not valid.")) + } /** Declares a subscriber to detect matching publishers. * @@ -55,13 +73,8 @@ class AdvancedSubscriber internal constructor( callback: Callback, history: Boolean = false, onClose: (() -> Unit)? = null - ): Result> { - val resolvedOnClose = fun() { - onClose?.invoke() - } - return jniSubscriber?.declareDetectPublishersSubscriber(keyExpr, history, callback, resolvedOnClose, Unit)?: - invalidSubscriberResult() - } + ): Result> = + resolveDetectPublishersSubscriber(callback, history, onClose ?: {}, background = false, receiver = Unit) /** Declares a subscriber to detect matching publishers. * @@ -76,15 +89,14 @@ class AdvancedSubscriber internal constructor( history: Boolean = false, onClose: (() -> Unit)? = null, receiver: R - ): Result> { - val resolvedOnClose = fun() { - handler.onClose() - onClose?.invoke() - } - val callback = Callback { t: Sample -> handler.handle(t) } - return jniSubscriber?.declareDetectPublishersSubscriber(keyExpr, history,callback, resolvedOnClose, receiver)?: - invalidSubscriberResult() - } + ): Result> = + resolveDetectPublishersSubscriber( + Callback { handler.handle(it) }, + history, + { handler.onClose(); onClose?.invoke() }, + background = false, + receiver = receiver, + ) /** Declares a subscriber to detect matching publishers. * @@ -101,13 +113,13 @@ class AdvancedSubscriber internal constructor( onClose: (() -> Unit)? = null, ): Result>> { val channelHandler = ChannelHandler(channel) - val resolvedOnClose = fun() { - channelHandler.onClose() - onClose?.invoke() - } - val callback = Callback { t: Sample -> channelHandler.handle(t) } - return jniSubscriber?.declareDetectPublishersSubscriber(keyExpr, history,callback, resolvedOnClose, channelHandler.receiver())?: - invalidSubscriberResult() + return resolveDetectPublishersSubscriber( + Callback { channelHandler.handle(it) }, + history, + { channelHandler.onClose(); onClose?.invoke() }, + background = false, + receiver = channelHandler.receiver(), + ) } /** Declares a background subscriber to detect matching publishers. @@ -124,13 +136,8 @@ class AdvancedSubscriber internal constructor( callback: Callback, history: Boolean = false, onClose: (() -> Unit)? = null - ): Result { - val resolvedOnClose = fun() { - onClose?.invoke() - } - return jniSubscriber?.declareBackgroundDetectPublishersSubscriber(keyExpr, history, callback, resolvedOnClose)?: - invalidSubscriberResult() - } + ): Result = + resolveDetectPublishersSubscriber(callback, history, onClose ?: {}, background = true, receiver = Unit).map {} /** Declares a background subscriber to detect matching publishers. * @@ -146,15 +153,14 @@ class AdvancedSubscriber internal constructor( handler: Handler, history: Boolean = false, onClose: (() -> Unit)? = null - ): Result { - val resolvedOnClose = fun() { - handler.onClose() - onClose?.invoke() - } - val callback = Callback { t: Sample -> handler.handle(t) } - return jniSubscriber?.declareBackgroundDetectPublishersSubscriber(keyExpr, history,callback, resolvedOnClose)?: - invalidSubscriberResult() - } + ): Result = + resolveDetectPublishersSubscriber( + Callback { handler.handle(it) }, + history, + { handler.onClose(); onClose?.invoke() }, + background = true, + receiver = handler.receiver(), + ).map {} /** Declares a background subscriber to detect matching publishers. * @@ -173,13 +179,36 @@ class AdvancedSubscriber internal constructor( onClose: (() -> Unit)? = null, ): Result { val channelHandler = ChannelHandler(channel) - val resolvedOnClose = fun() { - channelHandler.onClose() - onClose?.invoke() + return resolveDetectPublishersSubscriber( + Callback { channelHandler.handle(it) }, + history, + { channelHandler.onClose(); onClose?.invoke() }, + background = true, + receiver = channelHandler.receiver(), + ).map {} + } + + private fun resolveDetectPublishersSubscriber( + callback: Callback, + history: Boolean, + onClose: () -> Unit, + background: Boolean, + receiver: R2, + ): Result> { + val s = jniAdvancedSubscriber ?: return invalidSubscriberResult() + val jniCallback = sampleCallbackOf { callback.run(it) } + val jniOnClose = VoidCallback { onClose() } + return if (background) { + // Background subscribers register no returnable handle — they live + // until the advanced subscriber ends, so the wrapper holds null. + zCallUnit { onBindingError, onError -> + s.declareBackgroundDetectPublishersSubscriber(jniCallback, jniOnClose, history, onBindingError, onError) + }.map { Subscriber(keyExpr, receiver, null) } + } else { + zCall({ JniSubscriber(0L) }) { onBindingError, onError -> + s.declareDetectPublishersSubscriber(jniCallback, jniOnClose, history, onBindingError, onError) + }.map { Subscriber(keyExpr, receiver, it) } } - val callback = Callback { t: Sample -> channelHandler.handle(t) } - return jniSubscriber?.declareBackgroundDetectPublishersSubscriber(keyExpr, history,callback, resolvedOnClose)?: - invalidSubscriberResult() } /** Declares a [SampleMissListener] to detect missed samples for ths [AdvancedSubscriber]. @@ -189,14 +218,11 @@ class AdvancedSubscriber internal constructor( * @param callback: callback to be executed when missed samples detected * @param onClose: callback to be executed when associated [SampleMissListener] will be closed * */ - fun declareSampleMissListener(callback: SampleMissCallback, - onClose: (() -> Unit)? = null,): Result { - val resolvedOnClose = fun() { - onClose?.invoke() - } - return jniSubscriber?.declareSampleMissListener(callback, resolvedOnClose)?: - invalidSubscriberResult() - } + fun declareSampleMissListener( + callback: SampleMissCallback, + onClose: (() -> Unit)? = null, + ): Result = + resolveSampleMissListener(callback, onClose ?: {}, background = false) /** Declares a [SampleMissListener] to detect missed samples for ths [AdvancedSubscriber]. * @@ -205,16 +231,15 @@ class AdvancedSubscriber internal constructor( * @param handler [Handler] implementation to handle the sample miss events. [Handler.onClose] will be called upon closing the [SampleMissListener]. * @param onClose: callback to be executed when associated [SampleMissListener] will be closed * */ - fun declareSampleMissListener(handler: SampleMissHandler, - onClose: (() -> Unit)? = null,): Result { - val resolvedOnClose = fun() { - handler.onClose() - onClose?.invoke() - } - val callback = SampleMissCallback { miss: SampleMiss -> handler.handle(miss) } - return jniSubscriber?.declareSampleMissListener(callback, resolvedOnClose)?: - invalidSubscriberResult() - } + fun declareSampleMissListener( + handler: SampleMissHandler, + onClose: (() -> Unit)? = null, + ): Result = + resolveSampleMissListener( + SampleMissCallback { handler.handle(it) }, + { handler.onClose(); onClose?.invoke() }, + background = false, + ) /** Declares a [SampleMissListener] to detect missed samples for ths [AdvancedSubscriber]. * @@ -224,16 +249,16 @@ class AdvancedSubscriber internal constructor( * Once the [SampleMissListener] is closed, the [Channel] is closed as well. * @param onClose: callback to be executed when associated [SampleMissListener] will be closed * */ - fun declareSampleMissListener(channel: Channel, - onClose: (() -> Unit)? = null,): Result { + fun declareSampleMissListener( + channel: Channel, + onClose: (() -> Unit)? = null, + ): Result { val channelHandler = SampleMissChannelHandler(channel) - val resolvedOnClose = fun() { - channelHandler.onClose() - onClose?.invoke() - } - val callback = SampleMissCallback { miss: SampleMiss -> channelHandler.handle(miss) } - return jniSubscriber?.declareSampleMissListener(callback, resolvedOnClose)?: - invalidSubscriberResult() + return resolveSampleMissListener( + SampleMissCallback { channelHandler.handle(it) }, + { channelHandler.onClose(); onClose?.invoke() }, + background = false, + ) } /** Declares a background sample miss listener to detect missed samples for ths [AdvancedSubscriber]. @@ -245,14 +270,11 @@ class AdvancedSubscriber internal constructor( * @param callback: callback to be executed when missed samples detected * @param onClose: callback to be executed when associated [AdvancedSubscriber] will be closed * */ - fun declareBackgroundSampleMissListener(callback: SampleMissCallback, - onClose: (() -> Unit)? = null,): Result { - val resolvedOnClose = fun() { - onClose?.invoke() - } - return jniSubscriber?.declareBackgroundSampleMissListener(callback, resolvedOnClose)?: - invalidSubscriberResult() - } + fun declareBackgroundSampleMissListener( + callback: SampleMissCallback, + onClose: (() -> Unit)? = null, + ): Result = + resolveSampleMissListener(callback, onClose ?: {}, background = true).map {} /** Declares a background sample miss listener to detect missed samples for ths [AdvancedSubscriber]. * @@ -264,16 +286,15 @@ class AdvancedSubscriber internal constructor( * [Handler.onClose] will be called upon closing the [AdvancedSubscriber]. * @param onClose: callback to be executed when associated [AdvancedSubscriber] will be closed * */ - fun declareBackgroundSampleMissListener(handler: SampleMissHandler, - onClose: (() -> Unit)? = null,): Result { - val resolvedOnClose = fun() { - handler.onClose() - onClose?.invoke() - } - val callback = SampleMissCallback { miss: SampleMiss -> handler.handle(miss) } - return jniSubscriber?.declareBackgroundSampleMissListener(callback, resolvedOnClose)?: - invalidSubscriberResult() - } + fun declareBackgroundSampleMissListener( + handler: SampleMissHandler, + onClose: (() -> Unit)? = null, + ): Result = + resolveSampleMissListener( + SampleMissCallback { handler.handle(it) }, + { handler.onClose(); onClose?.invoke() }, + background = true, + ).map {} /** Declares a background sample miss listener to detect missed samples for ths [AdvancedSubscriber]. * @@ -285,34 +306,50 @@ class AdvancedSubscriber internal constructor( * Once the [AdvancedSubscriber] is closed, the [Channel] is closed as well. * @param onClose: callback to be executed when associated [AdvancedSubscriber] will be closed * */ - fun declareBackgroundSampleMissListener(channel: Channel, - onClose: (() -> Unit)? = null,): Result { + fun declareBackgroundSampleMissListener( + channel: Channel, + onClose: (() -> Unit)? = null, + ): Result { val channelHandler = SampleMissChannelHandler(channel) - val resolvedOnClose = fun() { - channelHandler.onClose() - onClose?.invoke() - } - val callback = SampleMissCallback { miss: SampleMiss -> channelHandler.handle(miss) } - return jniSubscriber?.declareBackgroundSampleMissListener(callback, resolvedOnClose)?: - invalidSubscriberResult() + return resolveSampleMissListener( + SampleMissCallback { channelHandler.handle(it) }, + { channelHandler.onClose(); onClose?.invoke() }, + background = true, + ).map {} } + private fun resolveSampleMissListener( + callback: SampleMissCallback, + onClose: () -> Unit, + background: Boolean, + ): Result { + val s = jniAdvancedSubscriber ?: return invalidSubscriberResult() + val jniCallback = sampleMissCallbackOf { callback.run(it) } + val jniOnClose = VoidCallback { onClose() } + return if (background) { + zCallUnit { onBindingError, onError -> + s.declareBackgroundSampleMissListener(jniCallback, jniOnClose, onBindingError, onError) + }.map { SampleMissListener(null) } + } else { + zCall({ JniSampleMissListener(0L) }) { onBindingError, onError -> + s.declareSampleMissListener(jniCallback, jniOnClose, onBindingError, onError) + }.map { SampleMissListener(it) } + } + } - - /** * Returns `true` if the subscriber is still running. */ fun isValid(): Boolean { - return jniSubscriber != null + return jniAdvancedSubscriber != null } /** * Undeclares the subscriber. After calling this function, the subscriber won't be receiving messages anymore. */ override fun undeclare() { - jniSubscriber?.close() - jniSubscriber = null + jniAdvancedSubscriber?.close() + jniAdvancedSubscriber = null } /** diff --git a/zenoh-kotlin/src/commonMain/kotlin/io/zenoh/pubsub/MatchingListener.kt b/zenoh-kotlin/src/commonMain/kotlin/io/zenoh/pubsub/MatchingListener.kt index 62c404cd0..662ac573e 100644 --- a/zenoh-kotlin/src/commonMain/kotlin/io/zenoh/pubsub/MatchingListener.kt +++ b/zenoh-kotlin/src/commonMain/kotlin/io/zenoh/pubsub/MatchingListener.kt @@ -15,19 +15,24 @@ package io.zenoh.pubsub import io.zenoh.annotations.Unstable -import io.zenoh.jni.JNIMatchingListener +import io.zenoh.jni.pubsub.MatchingListener as JniMatchingListener import io.zenoh.session.SessionDeclaration /** * # MatchingListener * A listener that sends notifications when the matching status of a corresponding Zenoh entity changes. * - * Matching listeners will run in background until the corresponding Zenoh entity is undeclared, - * or until it is undeclared. + * Matching listeners run in the background until the corresponding Zenoh entity is undeclared, + * or until the listener itself is undeclared. + * + * A background matching listener (declared via + * [AdvancedPublisher.declareBackgroundMatchingListener]) has no handle to + * undeclare — its [jniMatchingListener] is `null` and it lives until the + * publisher ends. */ @Unstable class MatchingListener internal constructor( - private var jniMatchingListener: JNIMatchingListener?, + private var jniMatchingListener: JniMatchingListener?, ) : SessionDeclaration, AutoCloseable { /** diff --git a/zenoh-kotlin/src/commonMain/kotlin/io/zenoh/jni/callbacks/JNISampleMissListenerCallback.kt b/zenoh-kotlin/src/commonMain/kotlin/io/zenoh/pubsub/Miss.kt similarity index 55% rename from zenoh-kotlin/src/commonMain/kotlin/io/zenoh/jni/callbacks/JNISampleMissListenerCallback.kt rename to zenoh-kotlin/src/commonMain/kotlin/io/zenoh/pubsub/Miss.kt index a2111f4b7..70ddb653c 100644 --- a/zenoh-kotlin/src/commonMain/kotlin/io/zenoh/jni/callbacks/JNISampleMissListenerCallback.kt +++ b/zenoh-kotlin/src/commonMain/kotlin/io/zenoh/pubsub/Miss.kt @@ -12,13 +12,20 @@ // ZettaScale Zenoh Team, // -package io.zenoh.jni.callbacks +package io.zenoh.pubsub -internal fun interface JNISampleMissListenerCallback { - fun run( - zidLower: Long, - zidUpper: Long, - eid: Long, - missedCount: Long - ) -} +import io.zenoh.annotations.Unstable +import io.zenoh.config.EntityGlobalId + +/** + * # Miss + * A report of samples missed from one source. + * + * @param source: the global id (zenoh id + entity id) of the source of the missed samples. + * @param nb: number of missed samples + */ +@Unstable +data class Miss( + val source: EntityGlobalId, + val nb: Long +) diff --git a/zenoh-kotlin/src/commonMain/kotlin/io/zenoh/pubsub/Publisher.kt b/zenoh-kotlin/src/commonMain/kotlin/io/zenoh/pubsub/Publisher.kt index 1a3def4be..357a5dfba 100644 --- a/zenoh-kotlin/src/commonMain/kotlin/io/zenoh/pubsub/Publisher.kt +++ b/zenoh-kotlin/src/commonMain/kotlin/io/zenoh/pubsub/Publisher.kt @@ -16,9 +16,14 @@ package io.zenoh.pubsub import io.zenoh.* import io.zenoh.exceptions.ZError -import io.zenoh.jni.JNIPublisher +import io.zenoh.exceptions.zCallUnit +import io.zenoh.jni.pubsub.Publisher as JniPublisher import io.zenoh.keyexpr.KeyExpr import io.zenoh.bytes.Encoding +import io.zenoh.bytes.jniHandle +import io.zenoh.bytes.jniId +import io.zenoh.bytes.jniSchema +import io.zenoh.bytes.jniSel import io.zenoh.qos.QoS import io.zenoh.bytes.IntoZBytes import io.zenoh.bytes.ZBytes @@ -67,7 +72,7 @@ class Publisher internal constructor( val keyExpr: KeyExpr, val qos: QoS, val encoding: Encoding, - private var jniPublisher: JNIPublisher?, + private var jniPublisher: JniPublisher?, ) : SessionDeclaration, AutoCloseable { companion object { @@ -81,8 +86,20 @@ class Publisher internal constructor( fun priority() = qos.priority /** Performs a PUT operation on the specified [keyExpr] with the specified [payload]. */ - fun put(payload: IntoZBytes, encoding: Encoding? = null, attachment: IntoZBytes? = null) = - jniPublisher?.put(payload, encoding ?: this.encoding, attachment) ?: InvalidPublisherResult + fun put(payload: IntoZBytes, encoding: Encoding? = null, attachment: IntoZBytes? = null): Result { + val p = jniPublisher ?: return InvalidPublisherResult + // When no per-put encoding override is given, the publisher's default + // encoding — set NATIVELY at declare time — applies, so no encoding + // data crosses (the selector arm is "absent", -1). + return zCallUnit { onBindingError, onError -> + p.put( + payload.into().bytes, + encoding.jniSel, encoding.jniId, encoding.jniSchema, encoding.jniHandle, + attachment?.into()?.bytes, + onBindingError, onError + ) + } + } fun put(payload: String, encoding: Encoding? = null, attachment: String? = null) = put(ZBytes.from(payload), encoding, attachment?.let { ZBytes.from(attachment) }) @@ -90,7 +107,12 @@ class Publisher internal constructor( /** * Performs a DELETE operation on the specified [keyExpr]. */ - fun delete(attachment: IntoZBytes? = null) = jniPublisher?.delete(attachment) ?: InvalidPublisherResult + fun delete(attachment: IntoZBytes? = null): Result { + val p = jniPublisher ?: return InvalidPublisherResult + return zCallUnit { onBindingError, onError -> + p.delete(attachment?.into()?.bytes, onBindingError, onError) + } + } fun delete(attachment: String) = delete(ZBytes.from(attachment)) diff --git a/zenoh-kotlin/src/commonMain/kotlin/io/zenoh/pubsub/SampleMiss.kt b/zenoh-kotlin/src/commonMain/kotlin/io/zenoh/pubsub/SampleMiss.kt deleted file mode 100644 index 8ad5d4f4d..000000000 --- a/zenoh-kotlin/src/commonMain/kotlin/io/zenoh/pubsub/SampleMiss.kt +++ /dev/null @@ -1,34 +0,0 @@ -// -// Copyright (c) 2025 ZettaScale Technology -// -// This program and the accompanying materials are made available under the -// terms of the Eclipse Public License 2.0 which is available at -// http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 -// which is available at https://www.apache.org/licenses/LICENSE-2.0. -// -// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 -// -// Contributors: -// ZettaScale Zenoh Team, -// - -package io.zenoh.pubsub - -import io.zenoh.annotations.Unstable - -/** - * # SampleMiss - * A struct that represent missed samples. - * - * @param zidLower: lower 8 bytes of zenoh id (the Zenoh session) of the source of missed samples. - * @param zidUpper: upper 8 bytes of zenoh id (the Zenoh session) of the source of missed samples. - * @param eid: entity id (entity in a Zenoh session) of the source of missed samples. - * @param missedCount: number of missed samples - */ -@Unstable -data class SampleMiss( - val zidLower: Long, - val zidUpper: Long, - val eid: Long, - val missedCount: Long -) diff --git a/zenoh-kotlin/src/commonMain/kotlin/io/zenoh/pubsub/SampleMissListener.kt b/zenoh-kotlin/src/commonMain/kotlin/io/zenoh/pubsub/SampleMissListener.kt index 1ad072c42..234185b1c 100644 --- a/zenoh-kotlin/src/commonMain/kotlin/io/zenoh/pubsub/SampleMissListener.kt +++ b/zenoh-kotlin/src/commonMain/kotlin/io/zenoh/pubsub/SampleMissListener.kt @@ -15,17 +15,22 @@ package io.zenoh.pubsub import io.zenoh.annotations.Unstable -import io.zenoh.jni.JNISampleMissListener +import io.zenoh.jni.pubsub.SampleMissListener as JniSampleMissListener import io.zenoh.session.SessionDeclaration /** * A listener to detect missed samples. * * Missed samples can only be detected from [AdvancedPublisher] that enables miss detection config. + * + * A background sample-miss listener (declared via + * [AdvancedSubscriber.declareBackgroundSampleMissListener]) has no handle to + * undeclare — its [jniSampleMissListener] is `null` and it lives until the + * advanced subscriber ends. */ @Unstable class SampleMissListener internal constructor( - private var jniSampleMissListener: JNISampleMissListener?, + private var jniSampleMissListener: JniSampleMissListener?, ) : SessionDeclaration, AutoCloseable { /** diff --git a/zenoh-kotlin/src/commonMain/kotlin/io/zenoh/pubsub/Subscriber.kt b/zenoh-kotlin/src/commonMain/kotlin/io/zenoh/pubsub/Subscriber.kt index cf318278b..6a1d1d3a6 100644 --- a/zenoh-kotlin/src/commonMain/kotlin/io/zenoh/pubsub/Subscriber.kt +++ b/zenoh-kotlin/src/commonMain/kotlin/io/zenoh/pubsub/Subscriber.kt @@ -16,7 +16,7 @@ package io.zenoh.pubsub import io.zenoh.* import io.zenoh.handlers.Handler -import io.zenoh.jni.JNISubscriber +import io.zenoh.jni.pubsub.Subscriber as JniSubscriber import io.zenoh.keyexpr.KeyExpr import io.zenoh.session.SessionDeclaration @@ -46,7 +46,7 @@ import io.zenoh.session.SessionDeclaration * @see Session.declareSubscriber */ class Subscriber internal constructor( - val keyExpr: KeyExpr, val receiver: R, private var jniSubscriber: JNISubscriber? + val keyExpr: KeyExpr, val receiver: R, private var jniSubscriber: JniSubscriber? ) : AutoCloseable, SessionDeclaration { /** diff --git a/zenoh-kotlin/src/commonMain/kotlin/io/zenoh/qos/CongestionControl.kt b/zenoh-kotlin/src/commonMain/kotlin/io/zenoh/qos/CongestionControl.kt index 4fc72013f..9415afc79 100644 --- a/zenoh-kotlin/src/commonMain/kotlin/io/zenoh/qos/CongestionControl.kt +++ b/zenoh-kotlin/src/commonMain/kotlin/io/zenoh/qos/CongestionControl.kt @@ -26,7 +26,12 @@ enum class CongestionControl (internal val value: Int) { * Prevents the message from being dropped at all cost. * In the face of heavy congestion on a part of the network, this could result in your publisher node blocking. */ - BLOCK(1); + BLOCK(1), + + /** + * Blocks low-priority traffic first, then drops when needed. + */ + BLOCK_FIRST(2); internal companion object { fun fromInt(value: Int) = entries.first { it.value == value } diff --git a/zenoh-kotlin/src/commonMain/kotlin/io/zenoh/qos/Reliability.kt b/zenoh-kotlin/src/commonMain/kotlin/io/zenoh/qos/Reliability.kt index cfd124b4b..aafc5db4b 100644 --- a/zenoh-kotlin/src/commonMain/kotlin/io/zenoh/qos/Reliability.kt +++ b/zenoh-kotlin/src/commonMain/kotlin/io/zenoh/qos/Reliability.kt @@ -19,13 +19,13 @@ package io.zenoh.qos * * Used by subscribers to inform the network of the reliability it wishes to obtain. */ -enum class Reliability { +enum class Reliability(internal val value: Int) { /** * Best Effort * * Informs the network that dropping some messages is acceptable. */ - BEST_EFFORT, + BEST_EFFORT(0), /** * Reliable @@ -35,5 +35,9 @@ enum class Reliability { * Note that if a publisher puts a sample with the [CongestionControl.DROP] option, * this reliability requirement may be infringed to prevent slow readers from blocking the network. */ - RELIABLE, + RELIABLE(1); + + internal companion object { + fun fromInt(value: Int) = entries.first { it.value == value } + } } diff --git a/zenoh-kotlin/src/commonMain/kotlin/io/zenoh/query/ConsolidationMode.kt b/zenoh-kotlin/src/commonMain/kotlin/io/zenoh/query/ConsolidationMode.kt index f008dc7d9..cbcaa44bb 100644 --- a/zenoh-kotlin/src/commonMain/kotlin/io/zenoh/query/ConsolidationMode.kt +++ b/zenoh-kotlin/src/commonMain/kotlin/io/zenoh/query/ConsolidationMode.kt @@ -15,12 +15,12 @@ package io.zenoh.query /** The kind of consolidation. */ -enum class ConsolidationMode { +enum class ConsolidationMode(internal val value: Int) { /** Apply automatic consolidation based on queryable's preferences. */ - AUTO, + AUTO(0), /** No consolidation applied: multiple samples may be received for the same key-timestamp.*/ - NONE, + NONE(1), /** * Monotonic consolidation immediately forwards samples, except if one with an equal or more recent timestamp @@ -31,8 +31,8 @@ enum class ConsolidationMode { * Note that this doesn't cause re-ordering, but drops the samples for which a more recent timestamp has already * been observed with the same key. */ - MONOTONIC, + MONOTONIC(2), /** Holds back samples to only send the set of samples that had the highest timestamp for their key. */ - LATEST + LATEST(3) } diff --git a/zenoh-kotlin/src/commonMain/kotlin/io/zenoh/query/Parameters.kt b/zenoh-kotlin/src/commonMain/kotlin/io/zenoh/query/Parameters.kt index ceef69f1c..d2b96be67 100644 --- a/zenoh-kotlin/src/commonMain/kotlin/io/zenoh/query/Parameters.kt +++ b/zenoh-kotlin/src/commonMain/kotlin/io/zenoh/query/Parameters.kt @@ -14,87 +14,73 @@ package io.zenoh.query -import java.net.URLDecoder +import io.zenoh.jni.query.Parameters as JniParameters /** * Parameters of the [Selector]. * - * When in string form, the `parameters` should be encoded like the query section of a URL: - * - parameters are separated by `;`, + * A thin facade over the shared string-backed implementation + * ([io.zenoh.jni.query.Parameters]), which mirrors Rust's + * `zenoh::query::Parameters` exactly. The string form follows the + * `a=b;c=d|e;f=g` format: + * - parameters are separated by `;` (empty chunks are skipped), * - the parameter name and value are separated by the first `=`, * - in the absence of `=`, the parameter value is considered to be the empty string, - * - both name and value should use percent-encoding to escape characters, - * - defining a value for the same parameter name twice is considered undefined behavior and an - * error result is returned. + * - the value is taken verbatim (no percent-decoding), + * - a duplicated parameter name is allowed: [get] returns the FIRST + * occurrence, and the duplicates survive round-tripping until an + * [insert]/[remove] normalizes the string. * * @see Selector */ -data class Parameters internal constructor(private val params: MutableMap) : IntoParameters { +class Parameters internal constructor(internal val inner: JniParameters) : IntoParameters { companion object { - private const val LIST_SEPARATOR = ";" - private const val FIELD_SEPARATOR = "=" - private const val VALUE_SEPARATOR = "|" - /** * Creates an empty Parameters. */ - fun empty() = Parameters(mutableMapOf()) + fun empty() = Parameters(JniParameters.empty()) /** * Creates a [Parameters] instance from the provided map. */ - fun from(params: Map): Parameters = Parameters(params.toMutableMap()) + fun from(params: Map): Parameters = Parameters(JniParameters.fromMap(params)) /** - * Attempts to create a [Parameters] from a string. + * Creates a [Parameters] from a string in the `a=b;c=d|e;f=g` format. * - * When in string form, the `parameters` should be encoded like the query section of a URL: - * - parameters are separated by `;`, - * - the parameter name and value are separated by the first `=`, - * - in the absence of `=`, the parameter value is considered to be the empty string, - * - both name and value should use percent-encoding to escape characters, - * - defining a value for the same parameter name twice is considered undefined behavior and an - * error result is returned. + * Construction is infallible — any string is accepted, exactly as in + * the Rust layer (the result is always [Result.success]; the [Result] + * signature is kept for source compatibility). */ - fun from(params: String): Result = runCatching { - if (params.isBlank()) { - return@runCatching Parameters(mutableMapOf()) - } - params.split(LIST_SEPARATOR).fold(mutableMapOf()) { parametersMap, parameter -> - val (key, value) = parameter.split(FIELD_SEPARATOR).let { it[0] to it.getOrNull(1) } - require(!parametersMap.containsKey(key)) { "Duplicated parameter `$key` detected." } - parametersMap[key] = value?.let { URLDecoder.decode(it, Charsets.UTF_8.name()) } ?: "" - parametersMap - }.let { Parameters(it) } - } + fun from(params: String): Result = + Result.success(Parameters(JniParameters.fromString(params))) } - override fun toString(): String = - params.entries.joinToString(LIST_SEPARATOR) { "${it.key}$FIELD_SEPARATOR${it.value}" } + override fun toString(): String = inner.toString() override fun into(): Parameters = this /** * Returns empty if no parameters were provided. */ - fun isEmpty(): Boolean = params.isEmpty() + fun isEmpty(): Boolean = inner.isEmpty() /** * Returns true if the [key] is contained. */ - fun containsKey(key: String): Boolean = params.containsKey(key) + fun containsKey(key: String): Boolean = inner.containsKey(key) /** - * Returns the value of the [key] if present. + * Returns the value of the [key] if present (the first occurrence wins). */ - fun get(key: String): String? = params[key] + fun get(key: String): String? = inner.get(key) /** * Returns the value of the [key] if present, or if not, the [default] value provided. */ - fun getOrDefault(key: String, default: String): String = params.getOrDefault(key, default) + fun getOrDefault(key: String, default: String): String = inner.getOrDefault(key, default) /** * Returns the values of the [key] if present. @@ -105,25 +91,25 @@ data class Parameters internal constructor(private val params: MutableMap? = params[key]?.split(VALUE_SEPARATOR) + fun values(key: String): List? = if (inner.containsKey(key)) inner.values(key) else null /** * Inserts the key-value pair into the parameters, returning the old value * in case of it being already present. */ - fun insert(key: String, value: String): String? = params.put(key, value) + fun insert(key: String, value: String): String? = inner.insert(key, value) /** * Removes the [key] parameter, returning its value. */ - fun remove(key: String): String? = params.remove(key) + fun remove(key: String): String? = inner.remove(key) /** * Extends the parameters with the [parameters] provided, overwriting * any conflicting params. */ fun extend(parameters: IntoParameters) { - params.putAll(parameters.into().params) + inner.extend(parameters.into().inner) } /** @@ -131,13 +117,18 @@ data class Parameters internal constructor(private val params: MutableMap) { - params.putAll(parameters) + inner.extend(parameters) } /** - * Returns a map with the key value pairs of the parameters. + * Returns a map with the key value pairs of the parameters. For a + * duplicated key the last occurrence wins (unlike [get]). */ - fun toMap(): Map = params + fun toMap(): Map = inner.toMap() + + override fun equals(other: Any?): Boolean = other is Parameters && inner == other.inner + + override fun hashCode(): Int = inner.hashCode() } interface IntoParameters { diff --git a/zenoh-kotlin/src/commonMain/kotlin/io/zenoh/query/Querier.kt b/zenoh-kotlin/src/commonMain/kotlin/io/zenoh/query/Querier.kt index deeb3759e..5326fdee2 100644 --- a/zenoh-kotlin/src/commonMain/kotlin/io/zenoh/query/Querier.kt +++ b/zenoh-kotlin/src/commonMain/kotlin/io/zenoh/query/Querier.kt @@ -18,13 +18,19 @@ import io.zenoh.annotations.Unstable import io.zenoh.bytes.Encoding import io.zenoh.bytes.IntoZBytes import io.zenoh.bytes.ZBytes +import io.zenoh.bytes.jniHandle +import io.zenoh.bytes.jniId +import io.zenoh.bytes.jniSchema +import io.zenoh.bytes.jniSel import io.zenoh.exceptions.ZError +import io.zenoh.exceptions.zCallUnit import io.zenoh.handlers.Callback import io.zenoh.handlers.ChannelHandler import io.zenoh.handlers.Handler -import io.zenoh.jni.JNIQuerier +import io.zenoh.jni.query.Querier as JniQuerier import io.zenoh.keyexpr.KeyExpr import io.zenoh.qos.QoS +import io.zenoh.replyCallbackOf import io.zenoh.session.SessionDeclaration import kotlinx.coroutines.channels.Channel @@ -50,7 +56,7 @@ import kotlinx.coroutines.channels.Channel * ``` * */ -class Querier internal constructor(val keyExpr: KeyExpr, val qos: QoS, private var jniQuerier: JNIQuerier?) : +class Querier internal constructor(val keyExpr: KeyExpr, val qos: QoS, private var jniQuerier: JniQuerier?) : SessionDeclaration, AutoCloseable { /** @@ -72,16 +78,7 @@ class Querier internal constructor(val keyExpr: KeyExpr, val qos: QoS, private v attachment: IntoZBytes? = null ): Result> { val handler = ChannelHandler(channel) - return jniQuerier?.performGet( - keyExpr, - parameters, - handler::handle, - handler::onClose, - handler.receiver(), - attachment, - payload, - encoding - ) ?: throw ZError("Querier is not valid.") + return performGet(parameters, handler::handle, handler::onClose, handler.receiver(), attachment, payload, encoding) } fun get( @@ -110,16 +107,7 @@ class Querier internal constructor(val keyExpr: KeyExpr, val qos: QoS, private v encoding: Encoding? = null, attachment: IntoZBytes? = null ): Result { - return jniQuerier?.performGet( - keyExpr, - parameters, - callback, - {}, - Unit, - attachment, - payload, - encoding - ) ?: throw ZError("Querier is not valid.") + return performGet(parameters, callback, {}, Unit, attachment, payload, encoding) } fun get( @@ -148,16 +136,7 @@ class Querier internal constructor(val keyExpr: KeyExpr, val qos: QoS, private v encoding: Encoding? = null, attachment: IntoZBytes? = null ): Result { - return jniQuerier?.performGet( - keyExpr, - parameters, - handler::handle, - handler::onClose, - handler.receiver(), - attachment, - payload, - encoding - ) ?: throw ZError("Querier is not valid.") + return performGet(parameters, handler::handle, handler::onClose, handler.receiver(), attachment, payload, encoding) } fun get( @@ -199,4 +178,26 @@ class Querier internal constructor(val keyExpr: KeyExpr, val qos: QoS, private v undeclare() } + private fun performGet( + parameters: Parameters?, + callback: Callback, + onClose: () -> Unit, + receiver: R, + attachment: IntoZBytes?, + payload: IntoZBytes?, + encoding: Encoding? + ): Result { + val q = jniQuerier ?: return Result.failure(ZError("Querier is not valid.")) + return zCallUnit { onBindingError, onError -> + q.get( + parameters?.toString(), + payload?.into()?.bytes, + encoding.jniSel, encoding.jniId, encoding.jniSchema, encoding.jniHandle, + attachment?.into()?.bytes, + replyCallbackOf { callback.run(it) }, + { onClose() }, + onBindingError, onError + ) + }.map { receiver } + } } diff --git a/zenoh-kotlin/src/commonMain/kotlin/io/zenoh/query/Query.kt b/zenoh-kotlin/src/commonMain/kotlin/io/zenoh/query/Query.kt index 5d56aa554..4f62af1ae 100644 --- a/zenoh-kotlin/src/commonMain/kotlin/io/zenoh/query/Query.kt +++ b/zenoh-kotlin/src/commonMain/kotlin/io/zenoh/query/Query.kt @@ -16,16 +16,23 @@ package io.zenoh.query import io.zenoh.ZenohType import io.zenoh.exceptions.ZError -import io.zenoh.jni.JNIQuery +import io.zenoh.exceptions.zCallUnit +import io.zenoh.jni.query.Query as JniQuery import io.zenoh.keyexpr.KeyExpr +import io.zenoh.keyexpr.jniHandle +import io.zenoh.keyexpr.jniSel +import io.zenoh.keyexpr.jniStr import io.zenoh.bytes.Encoding +import io.zenoh.bytes.jniHandle +import io.zenoh.bytes.jniId +import io.zenoh.bytes.jniSchema +import io.zenoh.bytes.jniSel import io.zenoh.qos.QoS import io.zenoh.qos.ReplyQoS -import io.zenoh.sample.SampleKind import io.zenoh.bytes.IntoZBytes import io.zenoh.bytes.ZBytes import io.zenoh.sample.Sample -import org.apache.commons.net.ntp.TimeStamp +import io.zenoh.time.Timestamp /** * Represents a Zenoh Query in Kotlin. @@ -44,7 +51,8 @@ class Query internal constructor( val payload: ZBytes?, val encoding: Encoding?, val attachment: ZBytes?, - private var jniQuery: JNIQuery? + private var jniQuery: JniQuery?, + private val acceptRepliesValue: ReplyKeyExpr = ReplyKeyExpr.MATCHING_QUERY ) : AutoCloseable, ZenohType { /** Shortcut to the [selector]'s parameters. */ @@ -69,15 +77,27 @@ class Query internal constructor( payload: IntoZBytes, encoding: Encoding = Encoding.default(), qos: ReplyQoS = ReplyQoS.default, - timestamp: TimeStamp? = null, + timestamp: Timestamp? = null, attachment: IntoZBytes? = null ): Result { - val sample = Sample(keyExpr, payload.into(), encoding, SampleKind.PUT, timestamp, qos.toQoS(), attachment?.into()) - return jniQuery?.let { - val result = it.replySuccess(sample) - jniQuery = null - result - } ?: Result.failure(ZError("Query is invalid")) + val q = jniQuery ?: return Result.failure(ZError("Query is invalid")) + val result = zCallUnit { onBindingError, onError -> + q.replySuccess( + keyExpr.jniSel, keyExpr.jniStr, keyExpr.jniHandle, + payload.into().bytes, + encoding.jniSel, encoding.jniId, encoding.jniSchema, encoding.jniHandle, + timestamp?.toJni(), + attachment?.into()?.bytes, + qos.express, + onBindingError, onError + ) + } + // Single-reply model: dropping the native query finalizes the reply + // stream so the querier's get completes. Safe whether the query came + // straight from the callback or was carried across a channel. + q.close() + jniQuery = null + return result } fun reply( @@ -85,7 +105,7 @@ class Query internal constructor( payload: String, encoding: Encoding = Encoding.default(), qos: ReplyQoS = ReplyQoS.default, - timestamp: TimeStamp? = null, + timestamp: Timestamp? = null, attachment: String? = null ): Result = reply(keyExpr, ZBytes.from(payload), encoding, qos, timestamp, attachment?.let { ZBytes.from(it) }) @@ -99,7 +119,7 @@ class Query internal constructor( payload: IntoZBytes, encoding: Encoding = Encoding.default(), qos: QoS, - timestamp: TimeStamp? = null, + timestamp: Timestamp? = null, attachment: IntoZBytes? = null ): Result = reply(keyExpr, payload, encoding, ReplyQoS(express = qos.express), timestamp, attachment) @@ -112,7 +132,7 @@ class Query internal constructor( payload: String, encoding: Encoding = Encoding.default(), qos: QoS, - timestamp: TimeStamp? = null, + timestamp: Timestamp? = null, attachment: String? = null ): Result = reply(keyExpr, ZBytes.from(payload), encoding, ReplyQoS(express = qos.express), timestamp, attachment?.let { ZBytes.from(it) }) @@ -127,11 +147,17 @@ class Query internal constructor( * @param encoding The encoding of the [error]. */ fun replyErr(error: IntoZBytes, encoding: Encoding = Encoding.default()): Result { - return jniQuery?.let { - val result = it.replyError(error, encoding) - jniQuery = null - result - } ?: Result.failure(ZError("Query is invalid")) + val q = jniQuery ?: return Result.failure(ZError("Query is invalid")) + val result = zCallUnit { onBindingError, onError -> + q.replyError( + error.into().bytes, + encoding.jniSel, encoding.jniId, encoding.jniSchema, encoding.jniHandle, + onBindingError, onError + ) + } + q.close() + jniQuery = null + return result } @@ -153,33 +179,35 @@ class Query internal constructor( fun replyDel( keyExpr: KeyExpr, qos: ReplyQoS = ReplyQoS.default, - timestamp: TimeStamp? = null, + timestamp: Timestamp? = null, attachment: IntoZBytes? = null ): Result { - return jniQuery?.let { - val result = it.replyDelete(keyExpr, timestamp, attachment, qos.toQoS()) - jniQuery = null - result - } ?: Result.failure(ZError("Query is invalid")) + val q = jniQuery ?: return Result.failure(ZError("Query is invalid")) + val result = zCallUnit { onBindingError, onError -> + q.replyDelete( + keyExpr.jniSel, keyExpr.jniStr, keyExpr.jniHandle, + timestamp?.toJni(), + attachment?.into()?.bytes, + qos.express, + onBindingError, onError + ) + } + q.close() + jniQuery = null + return result } fun replyDel( keyExpr: KeyExpr, qos: ReplyQoS = ReplyQoS.default, - timestamp: TimeStamp? = null, + timestamp: Timestamp? = null, attachment: String, ): Result = replyDel(keyExpr, qos, timestamp, ZBytes.from(attachment)) /** * Returns the [ReplyKeyExpr] accepted by this query. */ - fun acceptsReplies(): ReplyKeyExpr { - return if (selector.parameters?.containsKey(REPLY_KEY_EXPR_ANY_SEL_PARAM) == true) { - ReplyKeyExpr.ANY - } else { - ReplyKeyExpr.MATCHING_QUERY - } - } + fun acceptsReplies(): ReplyKeyExpr = acceptRepliesValue @Deprecated( message = "Use replyDel with ReplyQoS instead of QoS. Priority and congestion control are not applicable to replies.", replaceWith = ReplaceWith("replyDel(keyExpr, ReplyQoS(express = qos.express), timestamp, attachment)", "io.zenoh.qos.ReplyQoS") @@ -187,7 +215,7 @@ class Query internal constructor( fun replyDel( keyExpr: KeyExpr, qos: QoS, - timestamp: TimeStamp? = null, + timestamp: Timestamp? = null, attachment: IntoZBytes? = null ): Result = replyDel(keyExpr, ReplyQoS(express = qos.express), timestamp, attachment) @@ -198,7 +226,7 @@ class Query internal constructor( fun replyDel( keyExpr: KeyExpr, qos: QoS, - timestamp: TimeStamp? = null, + timestamp: Timestamp? = null, attachment: String, ): Result = replyDel(keyExpr, ReplyQoS(express = qos.express), timestamp, ZBytes.from(attachment)) @@ -210,6 +238,39 @@ class Query internal constructor( } companion object { - private const val REPLY_KEY_EXPR_ANY_SEL_PARAM = "_anyke" + /** + * Builds a [Query] from the decomposed leaves delivered by the + * generated queryable callback in one JNI crossing, plus the owned + * native query handle `zq`, **retained** because the reply methods + * consume it (replying keeps working after the callback returns). + */ + internal fun fromParts( + keStr: String, + parameters: String, + payloadH: io.zenoh.jni.bytes.ZBytes?, + encId: Int?, + encSchema: ByteArray?, + attachH: io.zenoh.jni.bytes.ZBytes?, + acceptsRepliesInt: Int, + zq: JniQuery, + ): Query { + val ke = KeyExpr(keStr) + // The parameters string is ATTACKER-CONTROLLED (the Rust layer + // forwards any selector parameters untouched) — the shared + // string-backed Parameters accepts any input, never throwing. + val selector = if (parameters.isEmpty()) Selector(ke) + else Selector(ke, Parameters.from(parameters).getOrThrow()) + return Query( + ke, + selector, + payloadH?.let { ZBytes.fromHandle(it) }, + // Raw bytes on the wire; decoded lossily since this SDK's + // Encoding carries a String (see Sample.fromParts). + encId?.let { Encoding(it, schema = encSchema?.toString(Charsets.UTF_8)) }, + attachH?.let { ZBytes.fromHandle(it) }, + zq, + ReplyKeyExpr.fromInt(acceptsRepliesInt) + ) + } } } diff --git a/zenoh-kotlin/src/commonMain/kotlin/io/zenoh/query/QueryTarget.kt b/zenoh-kotlin/src/commonMain/kotlin/io/zenoh/query/QueryTarget.kt index 2711f6f60..8036cdd49 100644 --- a/zenoh-kotlin/src/commonMain/kotlin/io/zenoh/query/QueryTarget.kt +++ b/zenoh-kotlin/src/commonMain/kotlin/io/zenoh/query/QueryTarget.kt @@ -15,21 +15,21 @@ package io.zenoh.query /** The Queryables that should be targeted by a GET operation. */ -enum class QueryTarget { +enum class QueryTarget(internal val value: Int) { /** * Best Matching: the nearest complete queryable if any else all matching queryables. */ - BEST_MATCHING, + BEST_MATCHING(0), /** * All matching queryables. */ - ALL, + ALL(1), /** * All Complete queryables. */ - ALL_COMPLETE; + ALL_COMPLETE(2); } diff --git a/zenoh-kotlin/src/commonMain/kotlin/io/zenoh/query/Queryable.kt b/zenoh-kotlin/src/commonMain/kotlin/io/zenoh/query/Queryable.kt index 569655f29..072b5f33e 100644 --- a/zenoh-kotlin/src/commonMain/kotlin/io/zenoh/query/Queryable.kt +++ b/zenoh-kotlin/src/commonMain/kotlin/io/zenoh/query/Queryable.kt @@ -16,7 +16,7 @@ package io.zenoh.query import io.zenoh.* import io.zenoh.handlers.Handler -import io.zenoh.jni.JNIQueryable +import io.zenoh.jni.query.Queryable as JniQueryable import io.zenoh.keyexpr.KeyExpr import io.zenoh.session.SessionDeclaration import kotlinx.coroutines.channels.Channel @@ -51,7 +51,7 @@ import kotlinx.coroutines.channels.Channel * @see Session.declareQueryable */ class Queryable internal constructor( - val keyExpr: KeyExpr, val receiver: R, private var jniQueryable: JNIQueryable? + val keyExpr: KeyExpr, val receiver: R, private var jniQueryable: JniQueryable? ) : AutoCloseable, SessionDeclaration { /** diff --git a/zenoh-kotlin/src/commonMain/kotlin/io/zenoh/query/ReplyKeyExpr.kt b/zenoh-kotlin/src/commonMain/kotlin/io/zenoh/query/ReplyKeyExpr.kt index 7e6568c57..c7b420f44 100644 --- a/zenoh-kotlin/src/commonMain/kotlin/io/zenoh/query/ReplyKeyExpr.kt +++ b/zenoh-kotlin/src/commonMain/kotlin/io/zenoh/query/ReplyKeyExpr.kt @@ -15,15 +15,22 @@ package io.zenoh.query /** The key expressions accepted by a query for replies. */ -enum class ReplyKeyExpr { +// The wire values follow the flat bindings enum; they happen to match the +// declaration order, which is kept as it was so `ordinal`/`values()` do not +// change for existing consumers. +enum class ReplyKeyExpr(internal val value: Int) { /** * Replies may have any key expression. */ - ANY, + ANY(0), /** * Replies must have a key expression matching the query's. */ - MATCHING_QUERY; + MATCHING_QUERY(1); + + internal companion object { + fun fromInt(value: Int) = entries.first { it.value == value } + } } diff --git a/zenoh-kotlin/src/commonMain/kotlin/io/zenoh/query/Selector.kt b/zenoh-kotlin/src/commonMain/kotlin/io/zenoh/query/Selector.kt index 900a0a140..0a8549c69 100644 --- a/zenoh-kotlin/src/commonMain/kotlin/io/zenoh/query/Selector.kt +++ b/zenoh-kotlin/src/commonMain/kotlin/io/zenoh/query/Selector.kt @@ -28,13 +28,12 @@ import io.zenoh.keyexpr.KeyExpr * * When in string form, selectors look a lot like a URI, with similar semantics: * - the `key_expr` before the first `?` must be a valid key expression. - * - the `parameters` after the first `?` should be encoded like the query section of a URL: + * - the `parameters` after the first `?` follow the `a=b;c=d|e;f=g` format: * - parameters are separated by `;`, * - the parameter name and value are separated by the first `=`, * - in the absence of `=`, the parameter value is considered to be the empty string, - * - both name and value should use percent-encoding to escape characters, - * - defining a value for the same parameter name twice is considered undefined behavior, - * with the encouraged behaviour being to reject operations when a duplicate parameter is detected. + * - the value is taken verbatim (no percent-decoding is applied), + * - a duplicated parameter name is allowed; lookups return the first occurrence. * * Zenoh intends to standardize the usage of a set of parameter names. To avoid conflicting with RPC parameters, * the Zenoh team has settled on reserving the set of parameter names that start with non-alphanumeric characters. @@ -60,13 +59,12 @@ data class Selector(val keyExpr: KeyExpr, val parameters: Parameters? = null) : * * When in string form, selectors look a lot like a URI, with similar semantics: * - the `key_expr` before the first `?` must be a valid key expression. - * - the `parameters` after the first `?` should be encoded like the query section of a URL: + * - the `parameters` after the first `?` follow the `a=b;c=d|e;f=g` format: * - parameters are separated by `;`, * - the parameter name and value are separated by the first `=`, * - in the absence of `=`, the parameter value is considered to be the empty string, - * - both name and value should use percent-encoding to escape characters, - * - defining a value for the same parameter name twice is considered undefined behavior, - * with the encouraged behaviour being to reject operations when a duplicate parameter is detected. + * - the value is taken verbatim (no percent-decoding is applied), + * - a duplicated parameter name is allowed; lookups return the first occurrence. * * @param selector The selector expression as a String. * @return a Result with the constructed Selector. diff --git a/zenoh-kotlin/src/commonMain/kotlin/io/zenoh/sample/Sample.kt b/zenoh-kotlin/src/commonMain/kotlin/io/zenoh/sample/Sample.kt index 0f7a49e83..b59b93189 100644 --- a/zenoh-kotlin/src/commonMain/kotlin/io/zenoh/sample/Sample.kt +++ b/zenoh-kotlin/src/commonMain/kotlin/io/zenoh/sample/Sample.kt @@ -15,11 +15,13 @@ package io.zenoh.sample import io.zenoh.ZenohType +import io.zenoh.qos.CongestionControl +import io.zenoh.qos.Priority import io.zenoh.qos.QoS import io.zenoh.keyexpr.KeyExpr import io.zenoh.bytes.Encoding import io.zenoh.bytes.ZBytes -import org.apache.commons.net.ntp.TimeStamp +import io.zenoh.time.Timestamp /** * Class representing a Zenoh Sample. @@ -28,7 +30,7 @@ import org.apache.commons.net.ntp.TimeStamp * @property payload [ZBytes] with the payload of the sample. * @property encoding [Encoding] of the payload. * @property kind The [SampleKind] of the sample. - * @property timestamp Optional [TimeStamp]. + * @property timestamp Optional [Timestamp]. * @property qos The Quality of Service settings used to deliver the sample. * @property attachment Optional attachment. */ @@ -37,12 +39,49 @@ data class Sample( val payload: ZBytes, val encoding: Encoding, val kind: SampleKind, - val timestamp: TimeStamp?, + val timestamp: Timestamp?, val qos: QoS, val attachment: ZBytes? = null, ): ZenohType { - + val express = qos.express val congestionControl = qos.congestionControl val priority = qos.priority + + companion object { + /** + * Builds a [Sample] from the decomposed leaves delivered by a generated + * callback in one JNI crossing. The payload/attachment arrive as owned + * native handles whose bytes are read lazily (see [ZBytes]). The + * trailing `reliability` / `source*` leaves are part of the generated + * decomposition but are not surfaced on the public [Sample] type. + */ + @Suppress("UNUSED_PARAMETER") + internal fun fromParts( + keStr: String, + payloadH: io.zenoh.jni.bytes.ZBytes, + encId: Int, + encSchema: ByteArray?, + kindInt: Int, + timestamp: io.zenoh.jni.time.Timestamp?, + express: Boolean, + prioInt: Int, + ccInt: Int, + attachH: io.zenoh.jni.bytes.ZBytes?, + reliabilityInt: Int, + sourceInfo: io.zenoh.jni.sample.SourceInfo?, + ): Sample = Sample( + KeyExpr(keStr), + ZBytes.fromHandle(payloadH), + // A schema is raw bytes on the wire and zenoh does not require it + // to be UTF-8; this SDK's Encoding carries a String, so a schema + // that is not valid UTF-8 decodes lossily rather than throwing on + // a received message. + Encoding(encId, schema = encSchema?.toString(Charsets.UTF_8)), + SampleKind.fromInt(kindInt), + timestamp?.let { Timestamp.fromJni(it) }, + QoS(CongestionControl.fromInt(ccInt), Priority.fromInt(prioInt), express), + attachH?.let { ZBytes.fromHandle(it) } + ) + } } diff --git a/zenoh-kotlin/src/commonMain/kotlin/io/zenoh/scouting/Scout.kt b/zenoh-kotlin/src/commonMain/kotlin/io/zenoh/scouting/Scout.kt index 7c9f98b26..65f6060eb 100644 --- a/zenoh-kotlin/src/commonMain/kotlin/io/zenoh/scouting/Scout.kt +++ b/zenoh-kotlin/src/commonMain/kotlin/io/zenoh/scouting/Scout.kt @@ -14,7 +14,7 @@ package io.zenoh.scouting -import io.zenoh.jni.JNIScout +import io.zenoh.jni.scouting.Scout as JniScout /** * Scout for routers and/or peers. @@ -34,7 +34,7 @@ import io.zenoh.jni.JNIScout */ class Scout internal constructor( val receiver: R, - private var jniScout: JNIScout? + private var jniScout: JniScout? ) : AutoCloseable { /** diff --git a/zenoh-kotlin/src/commonMain/kotlin/io/zenoh/time/Timestamp.kt b/zenoh-kotlin/src/commonMain/kotlin/io/zenoh/time/Timestamp.kt new file mode 100644 index 000000000..ccca22fdb --- /dev/null +++ b/zenoh-kotlin/src/commonMain/kotlin/io/zenoh/time/Timestamp.kt @@ -0,0 +1,61 @@ +// +// Copyright (c) 2023 ZettaScale Technology +// +// This program and the accompanying materials are made available under the +// terms of the Eclipse Public License 2.0 which is available at +// http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 +// which is available at https://www.apache.org/licenses/LICENSE-2.0. +// +// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 +// +// Contributors: +// ZettaScale Zenoh Team, +// + +package io.zenoh.time + +import io.zenoh.config.ZenohId +import io.zenoh.jni.time.Timestamp as JniTimestamp + +/** + * A Zenoh timestamp: an NTP64 instant together with the identifier of the node + * whose clock produced it. + * + * Both halves are significant. Zenoh orders and de-duplicates on the whole + * `(ntp64, id)` pair, so a timestamp is only meaningful when its [id] is the + * real identifier of the originating node — obtain it from the session that + * will publish the value, via `session.info().zid()`. + * + * @property ntp64 The NTP64 instant. This is an *unsigned* 64-bit value whose + * high bit marks the current era; [getNtp64] returns the same 64 bits in the + * form `org.apache.commons.net.ntp.TimeStamp(long)` expects. + * @property id Identifier of the node whose clock produced [ntp64]. + */ +data class Timestamp( + // Rename default getter which is not accessible on Java due to unsigned type + @get:JvmName("getNtp64ULong") val ntp64: ULong, + val id: ZenohId, +) { + + @Suppress("unused") + // Manually defined getter for Java: the raw 64 bits, not a magnitude, so + // this reinterprets rather than clamping. + fun getNtp64(): Long = ntp64.toLong() + + internal fun toJni(): JniTimestamp = JniTimestamp(ntp64, id.bytes) + + companion object { + /** + * Build a timestamp from the raw 64 NTP bits — the constructor Java + * callers want, since the primary one takes an unsigned type Java + * cannot express. These are the same bits + * `org.apache.commons.net.ntp.TimeStamp(long)` takes, so + * `TimeStamp.getCurrentTime().ntpValue()` feeds straight in. + */ + @JvmStatic + fun ofNtp64(ntp64: Long, id: ZenohId): Timestamp = Timestamp(ntp64.toULong(), id) + + /** The native `(ntp64, id)` pair as delivered by a decomposed value. */ + internal fun fromJni(t: JniTimestamp): Timestamp = Timestamp(t.ntp64, ZenohId(t.id)) + } +} diff --git a/zenoh-kotlin/src/commonTest/kotlin/io/zenoh/AdvancedPubSubTest.kt b/zenoh-kotlin/src/commonTest/kotlin/io/zenoh/AdvancedPubSubTest.kt index 6f15da786..cb26e0861 100644 --- a/zenoh-kotlin/src/commonTest/kotlin/io/zenoh/AdvancedPubSubTest.kt +++ b/zenoh-kotlin/src/commonTest/kotlin/io/zenoh/AdvancedPubSubTest.kt @@ -27,6 +27,7 @@ import io.zenoh.pubsub.AdvancedSubscriber import io.zenoh.pubsub.MatchingListener import io.zenoh.pubsub.SampleMissListener import io.zenoh.pubsub.Subscriber +import java.lang.Thread.sleep import kotlin.test.* class AdvancedPubSubTest { @@ -51,6 +52,8 @@ class AdvancedPubSubTest { fun setUp() { session = Session.open(Config.default()).getOrThrow() keyExpr = "example/testing/keyexpr".intoKeyExpr().getOrThrow() + // Initialize the sink before declaring the subscriber whose callback appends to it. + receivedSamples = ArrayList() val missDetectionConfig = MissDetectionConfig(HeartbeatMode.PeriodicHeartbeat(100)) @@ -69,14 +72,12 @@ class AdvancedPubSubTest { matchingSubscriber = subscriber.declareDetectPublishersSubscriber(callback = {sample -> matchingSamples.add(sample)}, history = true).getOrThrow() sampleMissListener = subscriber.declareSampleMissListener(callback = {miss -> sampleMisses++}).getOrThrow() - receivedSamples = ArrayList() + // Give publisher/subscriber detection and matching-status propagation a moment. + sleep(1000) } @AfterTest fun tearDown() { - assertTrue(matchingSamples.count() != 0) - assertTrue(hasMatchingSubscribers) - matchingListener.close() publisher.close() @@ -86,6 +87,11 @@ class AdvancedPubSubTest { session.close() keyExpr.close() + + // session.close() should wait for pending callbacks to complete + // so checking the state of the test after closing the session + assertTrue(matchingSamples.count() != 0) + assertTrue(hasMatchingSubscribers) } @Test diff --git a/zenoh-kotlin/src/commonTest/kotlin/io/zenoh/AdvancedPublisherTest.kt b/zenoh-kotlin/src/commonTest/kotlin/io/zenoh/AdvancedPublisherTest.kt new file mode 100644 index 000000000..b00535c5c --- /dev/null +++ b/zenoh-kotlin/src/commonTest/kotlin/io/zenoh/AdvancedPublisherTest.kt @@ -0,0 +1,112 @@ +// +// Copyright (c) 2025 ZettaScale Technology +// +// This program and the accompanying materials are made available under the +// terms of the Eclipse Public License 2.0 which is available at +// http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 +// which is available at https://www.apache.org/licenses/LICENSE-2.0. +// +// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 +// +// Contributors: +// ZettaScale Zenoh Team, +// + +package io.zenoh + +import io.zenoh.keyexpr.KeyExpr +import io.zenoh.bytes.Encoding +import io.zenoh.keyexpr.intoKeyExpr +import io.zenoh.sample.SampleKind +import io.zenoh.ext.zSerialize +import io.zenoh.pubsub.AdvancedPublisher +import io.zenoh.pubsub.MatchingListener +import io.zenoh.sample.Sample +import io.zenoh.pubsub.Subscriber +import java.lang.Thread.sleep +import kotlin.test.* + +/** + * [AdvancedPublisher] + [MatchingListener] validated against a *regular* + * [Subscriber] on a single session (loopback) — an advanced publisher must + * serve plain subscribers too. [AdvancedPubSubTest] covers the same publisher + * against an [io.zenoh.pubsub.AdvancedSubscriber], with sample-miss detection + * and publisher detection on top. + */ +class AdvancedPublisherTest { + + lateinit var session: Session + lateinit var receivedSamples: ArrayList + lateinit var publisher: AdvancedPublisher + lateinit var matchingListener: MatchingListener + lateinit var subscriber: Subscriber + lateinit var keyExpr: KeyExpr + var hasMatchingSubscribers: Boolean = false + + @BeforeTest + fun setUp() { + session = Session.open(Config.default()).getOrThrow() + keyExpr = "example/testing/keyexpr".intoKeyExpr().getOrThrow() + receivedSamples = ArrayList() + // Declare the subscriber first, then the advanced publisher and its + // matching listener — so the matching status is already `true` when the + // listener is declared (no reliance on change-notification timing). + subscriber = session.declareSubscriber(keyExpr, callback = { sample -> + receivedSamples.add(sample) + }).getOrThrow() + publisher = session.declareAdvancedPublisher(keyExpr, encoding = Encoding.ZENOH_STRING).getOrThrow() + matchingListener = publisher.declareMatchingListener(callback = { matching -> + hasMatchingSubscribers = matching + }).getOrThrow() + // Give matching-status propagation a brief moment. + sleep(100) + } + + @AfterTest + fun tearDown() { + matchingListener.close() + publisher.close() + subscriber.close() + session.close() + keyExpr.close() + } + + @Test + fun putTest() { + val testPayloads = arrayListOf( + Pair(zSerialize("Test 1").getOrThrow(), Encoding.TEXT_PLAIN), + Pair(zSerialize("Test 2").getOrThrow(), Encoding.TEXT_JSON), + Pair(zSerialize("Test 3").getOrThrow(), Encoding.TEXT_CSV), + ) + + testPayloads.forEach { value -> publisher.put(value.first, encoding = value.second) } + + assertEquals(testPayloads.size, receivedSamples.size) + for ((index, sample) in receivedSamples.withIndex()) { + assertEquals(testPayloads[index].first, sample.payload) + assertEquals(testPayloads[index].second, sample.encoding) + } + } + + @Test + fun deleteTest() { + publisher.delete() + assertEquals(1, receivedSamples.size) + assertEquals(SampleKind.DELETE, receivedSamples[0].kind) + } + + @Test + fun matchingStatusTest() { + // A matching subscriber exists, so both the synchronous query and the + // listener callback must report matching. + assertTrue(publisher.getMatchingStatus().getOrThrow()) + assertTrue(hasMatchingSubscribers) + } + + @Test + fun `when encoding is not provided a put should fallback to the publisher encoding`() { + publisher.put(zSerialize("Test").getOrThrow()) + assertEquals(1, receivedSamples.size) + assertEquals(Encoding.ZENOH_STRING, receivedSamples[0].encoding) + } +} diff --git a/zenoh-kotlin/src/commonTest/kotlin/io/zenoh/GetTest.kt b/zenoh-kotlin/src/commonTest/kotlin/io/zenoh/GetTest.kt index 7f412e0cb..47ca5a9b4 100644 --- a/zenoh-kotlin/src/commonTest/kotlin/io/zenoh/GetTest.kt +++ b/zenoh-kotlin/src/commonTest/kotlin/io/zenoh/GetTest.kt @@ -22,6 +22,7 @@ import io.zenoh.query.Queryable import io.zenoh.query.Parameters import io.zenoh.query.Selector import io.zenoh.query.intoSelector +import io.zenoh.time.Timestamp import org.apache.commons.net.ntp.TimeStamp import java.lang.Thread.sleep import java.time.Duration @@ -32,17 +33,20 @@ class GetTest { companion object { val payload = zSerialize("Test").getOrThrow() - val timestamp = TimeStamp.getCurrentTime() + val timestampNtp64 = TimeStamp.getCurrentTime().ntpValue() val kind = SampleKind.PUT } private lateinit var session: Session private lateinit var selector: Selector private lateinit var queryable: Queryable + // Needs the session's id, so it cannot be a companion-object constant. + private lateinit var timestamp: Timestamp @BeforeTest fun setUp() { session = Session.open(Config.default()).getOrThrow() + timestamp = Timestamp.ofNtp64(timestampNtp64, session.info().zid().getOrThrow()) selector = "example/testing/keyexpr".intoSelector().getOrThrow() queryable = session.declareQueryable(selector.keyExpr, callback = { query -> query.reply(query.keyExpr, payload, timestamp = timestamp) diff --git a/zenoh-kotlin/src/commonTest/kotlin/io/zenoh/KeyExprTest.kt b/zenoh-kotlin/src/commonTest/kotlin/io/zenoh/KeyExprTest.kt index 87eadbabf..5f92efff3 100644 --- a/zenoh-kotlin/src/commonTest/kotlin/io/zenoh/KeyExprTest.kt +++ b/zenoh-kotlin/src/commonTest/kotlin/io/zenoh/KeyExprTest.kt @@ -126,6 +126,29 @@ class KeyExprTest { keyExpr2.close() } + @Test + fun sessionFailedUnDeclarationDetachesHandleTest() { + // Undeclaring through the WRONG session makes the native undeclare + // fail — and the flat wrapper consumes the handle even then. The + // KeyExpr must degrade to its string form (handle detached), not keep + // selecting a dead handle. + val session1 = Session.open(Config.default()).getOrThrow() + val session2 = Session.open(Config.default()).getOrThrow() + val keyExpr = session1.declareKeyExpr("a/b/c").getOrThrow() + + val undeclare = session2.undeclare(keyExpr) + assertTrue(undeclare.isFailure) + assertNull(keyExpr.jniKeyExpr) + + // String-backed operation keeps working after the failed undeclare. + assertTrue(keyExpr.intersects(KeyExpr.tryFrom("a/b/c").getOrThrow())) + assertTrue(session1.put(keyExpr, payload = "test").isSuccess) + + session1.close() + session2.close() + keyExpr.close() + } + @Test fun `relationTo returns includes test`() { val keyExprA = KeyExpr.tryFrom("A/**").getOrThrow() diff --git a/zenoh-kotlin/src/commonTest/kotlin/io/zenoh/ParametersTest.kt b/zenoh-kotlin/src/commonTest/kotlin/io/zenoh/ParametersTest.kt index 89be70eee..9961eee66 100644 --- a/zenoh-kotlin/src/commonTest/kotlin/io/zenoh/ParametersTest.kt +++ b/zenoh-kotlin/src/commonTest/kotlin/io/zenoh/ParametersTest.kt @@ -25,6 +25,34 @@ class ParametersTest { assertEquals("6", parameters.get("d")) } + @Test + fun `parsing accepts any remote input with Rust semantics`() { + // Parsing is total on attacker-controlled input, exactly as the Rust + // layer (a selector's parameters are an unvalidated string view). + + // Duplicated parameter name: accepted, the FIRST occurrence wins on get. + assertEquals("1", Parameters.from("a=1;a=2").getOrThrow().get("a")) + + // No percent-decoding: the value is kept verbatim. + assertEquals("%zz", Parameters.from("k=%zz").getOrThrow().get("k")) + assertEquals("%20", Parameters.from("k=%20").getOrThrow().get("k")) + + // Value containing '=': split on the FIRST '=' only. + assertEquals("b=c", Parameters.from("a=b=c").getOrThrow().get("a")) + + // Flag without a value, empty chunks, blank input: all accepted. + assertEquals("", Parameters.from("flag").getOrThrow().get("flag")) + assertEquals("1", Parameters.from(";;a=1;;").getOrThrow().get("a")) + assertTrue(Parameters.from("").getOrThrow().isEmpty()) + + // The string round-trips verbatim (duplicates preserved) until an + // insert/remove normalizes it. + val p = Parameters.from("a=1;a=2;flag").getOrThrow() + assertEquals("a=1;a=2;flag", p.toString()) + p.insert("a", "3") + assertEquals("flag;a=3", p.toString()) + } + @Test fun `should return list of values split by separator`() { val parameters = Parameters.from("a=1;b=2;c=3|4|5;d=6").getOrThrow() diff --git a/zenoh-kotlin/src/commonTest/kotlin/io/zenoh/QuerierTest.kt b/zenoh-kotlin/src/commonTest/kotlin/io/zenoh/QuerierTest.kt index e468f8fbe..0478530a6 100644 --- a/zenoh-kotlin/src/commonTest/kotlin/io/zenoh/QuerierTest.kt +++ b/zenoh-kotlin/src/commonTest/kotlin/io/zenoh/QuerierTest.kt @@ -24,6 +24,7 @@ import io.zenoh.query.Reply import io.zenoh.sample.Sample import io.zenoh.sample.SampleKind import kotlinx.coroutines.runBlocking +import io.zenoh.time.Timestamp import org.apache.commons.net.ntp.TimeStamp import java.lang.Thread.sleep import java.time.Instant @@ -59,7 +60,7 @@ class QuerierTest { testPayload, Encoding.default(), SampleKind.PUT, - TimeStamp(Date.from(Instant.now())), + Timestamp.ofNtp64(TimeStamp(Date.from(Instant.now())).ntpValue(), session.info().zid().getOrThrow()), QoS.defaultRequest ) val examplePayload = ZBytes.from("Example payload") diff --git a/zenoh-kotlin/src/commonTest/kotlin/io/zenoh/QueryableTest.kt b/zenoh-kotlin/src/commonTest/kotlin/io/zenoh/QueryableTest.kt index 9d65a6f2b..d2a9eff4a 100644 --- a/zenoh-kotlin/src/commonTest/kotlin/io/zenoh/QueryableTest.kt +++ b/zenoh-kotlin/src/commonTest/kotlin/io/zenoh/QueryableTest.kt @@ -27,11 +27,16 @@ import io.zenoh.query.Query import io.zenoh.sample.Sample import io.zenoh.sample.SampleKind import io.zenoh.query.Selector +import io.zenoh.exceptions.throwZError +import io.zenoh.exceptions.throwZError0 import kotlinx.coroutines.DelicateCoroutinesApi import kotlinx.coroutines.channels.Channel import kotlinx.coroutines.delay import kotlinx.coroutines.runBlocking import kotlinx.coroutines.time.withTimeout +import io.zenoh.config.ZenohId +import io.zenoh.jni.query.Selector as JniSelector +import io.zenoh.time.Timestamp import org.apache.commons.net.ntp.TimeStamp import java.lang.Thread.sleep import java.time.Duration @@ -68,7 +73,7 @@ class QueryableTest { testPayload, Encoding.default(), SampleKind.PUT, - TimeStamp(Date.from(Instant.now())), + Timestamp.ofNtp64(TimeStamp(Date.from(Instant.now())).ntpValue(), session.info().zid().getOrThrow()), QoS.defaultRequest ) val queryable = session.declareQueryable(testKeyExpr, callback = { query -> @@ -87,9 +92,57 @@ class QueryableTest { queryable.close() } + @Test + fun queryable_receivesMalformedParametersQuery() { + // A remote (non-Kotlin) client is free to send selector parameters + // that the strict Kotlin parser rejects (duplicated names, invalid + // percent-encoding) — the Rust layer forwards any string untouched. + // The queryable must still receive the query (lenient parse, first + // duplicate wins, undecodable value kept verbatim) and reply to it. + var received: Query? = null + val queryable = session.declareQueryable(testKeyExpr, callback = { query -> + received = query + query.reply(testKeyExpr, payload = "ok") + }).getOrThrow() + + var reply: Reply? = null + // Bypass the Kotlin-side Selector validation, as a remote client would. + session.jniSession!!.get( + // A Selector carries its parameters as an uninterpreted string, so + // constructing one directly is exactly the "remote client" path: + // nothing on the Kotlin side inspects them. + JniSelector(testKeyExpr.intoJniHandle(), "a=1;a=2;bad=%zz"), + 1000L, // timeoutMs + null, // target + null, // consolidation + null, // acceptReplies + null, // congestionControl + null, // priority + null, // express + null, // payload + -1, // encodingSel (absent) + null, // encoding00 + null, // encoding01 + null, // encoding1 + null, // attachment + replyCallbackOf { reply = it }, + {}, // onClose + throwZError0, throwZError, + ) + sleep(1000) + + assertNotNull(received) + assertEquals("1", received!!.parameters?.get("a")) + assertEquals("%zz", received!!.parameters?.get("bad")) + assertNotNull(reply) + assertTrue(reply!!.result.isSuccess) + + queryable.close() + } + @Test fun queryable_runsWithHandler() = runBlocking { - val handler = QueryHandler() + val handler = QueryHandler(session.info().zid().getOrThrow()) val queryable = session.declareQueryable(testKeyExpr, handler = handler).getOrThrow() delay(500) @@ -137,7 +190,7 @@ class QueryableTest { @Test fun queryReplySuccessTest() { val message = zSerialize("Test message").getOrThrow() - val timestamp = TimeStamp.getCurrentTime() + val timestamp = Timestamp.ofNtp64(TimeStamp.getCurrentTime().ntpValue(), session.info().zid().getOrThrow()) val queryable = session.declareQueryable(testKeyExpr, callback = { query -> query.reply(testKeyExpr, payload = message, timestamp = timestamp, qos = ReplyQoS(express = true)) }).getOrThrow() @@ -176,7 +229,7 @@ class QueryableTest { @Test fun queryReplyDeleteTest() { - val timestamp = TimeStamp.getCurrentTime() + val timestamp = Timestamp.ofNtp64(TimeStamp.getCurrentTime().ntpValue(), session.info().zid().getOrThrow()) val queryable = session.declareQueryable(testKeyExpr, callback = { query -> query.replyDel(testKeyExpr, timestamp = timestamp, qos = ReplyQoS(express = true)) @@ -208,7 +261,9 @@ class QueryableTest { } /** A dummy handler that replies "Hello queryable" followed by the count of replies performed. */ -private class QueryHandler : Handler { +// A timestamp is only meaningful paired with the id of the node whose clock +// produced it, so the handler is given the replying session's. +private class QueryHandler(private val zid: ZenohId) : Handler { private var counter = 0 @@ -232,7 +287,7 @@ private class QueryHandler : Handler { payload, Encoding.default(), SampleKind.PUT, - TimeStamp(Date.from(Instant.now())), + Timestamp.ofNtp64(TimeStamp(Date.from(Instant.now())).ntpValue(), zid), QoS() ) performedReplies.add(sample) diff --git a/zenoh-kotlin/src/commonTest/kotlin/io/zenoh/ScoutTest.kt b/zenoh-kotlin/src/commonTest/kotlin/io/zenoh/ScoutTest.kt index 26c41c1da..c14373af7 100644 --- a/zenoh-kotlin/src/commonTest/kotlin/io/zenoh/ScoutTest.kt +++ b/zenoh-kotlin/src/commonTest/kotlin/io/zenoh/ScoutTest.kt @@ -80,4 +80,12 @@ class ScoutTest { fun `scouting loads config successfully test`() { Zenoh.scout({}, config = Config.default()).getOrThrow() } + + @Test + fun `scouting with empty whatAmI returns failure test`() { + // Result contract: a JVM-side exception during argument preparation + // (reduce on an empty set) surfaces as Result.failure, never a throw. + val result = Zenoh.scout({}, whatAmI = emptySet()) + assertTrue(result.isFailure) + } } diff --git a/zenoh-kotlin/src/commonTest/kotlin/io/zenoh/SelectorTest.kt b/zenoh-kotlin/src/commonTest/kotlin/io/zenoh/SelectorTest.kt index 161496737..1162c12c4 100644 --- a/zenoh-kotlin/src/commonTest/kotlin/io/zenoh/SelectorTest.kt +++ b/zenoh-kotlin/src/commonTest/kotlin/io/zenoh/SelectorTest.kt @@ -2,6 +2,7 @@ package io.zenoh import io.zenoh.exceptions.ZError import io.zenoh.keyexpr.intoKeyExpr +import io.zenoh.query.ReplyKeyExpr import io.zenoh.query.Selector import io.zenoh.query.intoSelector import org.junit.jupiter.api.Assertions.assertNull @@ -37,6 +38,14 @@ class SelectorTest { } } + @Test + fun `ReplyKeyExpr keeps its declaration order`() { + // The constants carry the flat wire value in `value`, so the + // declaration order is free — and must stay as published, since + // reordering silently changes `ordinal` and `values()` for consumers. + assertEquals(listOf(ReplyKeyExpr.ANY, ReplyKeyExpr.MATCHING_QUERY), ReplyKeyExpr.entries) + } + /** * Check the queryable properly receives the query's selector with and without parameters. */ diff --git a/zenoh-kotlin/src/jvmAndAndroidMain/kotlin/io/zenoh/ext/KTypeSerde.kt b/zenoh-kotlin/src/jvmAndAndroidMain/kotlin/io/zenoh/ext/KTypeSerde.kt new file mode 100644 index 000000000..58a609df1 --- /dev/null +++ b/zenoh-kotlin/src/jvmAndAndroidMain/kotlin/io/zenoh/ext/KTypeSerde.kt @@ -0,0 +1,50 @@ +package io.zenoh.ext + +import io.zenoh.jni.bytes.SerializationCodec +import kotlin.reflect.KClass +import kotlin.reflect.KType + +/** + * Build a [SerializationCodec.SerdeType] from a Kotlin [KType] — the reflection adapter for + * the pure-Kotlin serializer. Inspects the type's classifier (a [KClass] with a + * qualified name) so it recognizes the Kotlin-only unsigned value classes and + * `Pair`/`Triple` that erase away in `java.lang.reflect.Type`. Mirrors the + * former native `decode_ktype`, in Kotlin. + * + * Lives in `jvmAndAndroidMain` because `typeOf()` and [KClass.qualifiedName] + * resolve through `kotlin.jvm.internal.Reflection`, which the JVM stdlib + * provides (no kotlin-reflect artifact needed) but Kotlin/Native and + * Kotlin/JS do not. + */ +internal fun serdeTypeOf(type: KType): SerializationCodec.SerdeType { + val classifier = type.classifier as? KClass<*> + ?: throw SerializationCodec.SerdeException("KType classifier is not a class: $type") + val name = classifier.qualifiedName + ?: throw SerializationCodec.SerdeException("KType classifier has no qualified name: $type") + return when (name) { + "kotlin.Boolean" -> SerializationCodec.SerdeType.Bool + "kotlin.Byte" -> SerializationCodec.SerdeType.I8 + "kotlin.Short" -> SerializationCodec.SerdeType.I16 + "kotlin.Int" -> SerializationCodec.SerdeType.I32 + "kotlin.Long" -> SerializationCodec.SerdeType.I64 + "kotlin.UByte" -> SerializationCodec.SerdeType.U8 + "kotlin.UShort" -> SerializationCodec.SerdeType.U16 + "kotlin.UInt" -> SerializationCodec.SerdeType.U32 + "kotlin.ULong" -> SerializationCodec.SerdeType.U64 + "kotlin.Float" -> SerializationCodec.SerdeType.F32 + "kotlin.Double" -> SerializationCodec.SerdeType.F64 + "kotlin.String" -> SerializationCodec.SerdeType.Str + "kotlin.ByteArray" -> SerializationCodec.SerdeType.Bytes + "kotlin.collections.List" -> SerializationCodec.SerdeType.ZList(arg(type, 0)) + "kotlin.collections.Map" -> SerializationCodec.SerdeType.ZMap(arg(type, 0), arg(type, 1)) + "kotlin.Pair" -> SerializationCodec.SerdeType.ZPair(arg(type, 0), arg(type, 1)) + "kotlin.Triple" -> SerializationCodec.SerdeType.ZTriple(arg(type, 0), arg(type, 1), arg(type, 2)) + else -> throw SerializationCodec.SerdeException("Unsupported type: $name") + } +} + +private fun arg(type: KType, idx: Int): SerializationCodec.SerdeType { + val argType = type.arguments[idx].type + ?: throw SerializationCodec.SerdeException("star projection unsupported at argument $idx of $type") + return serdeTypeOf(argType) +} diff --git a/zenoh-kotlin/src/commonMain/kotlin/io/zenoh/ext/ZDeserialize.kt b/zenoh-kotlin/src/jvmAndAndroidMain/kotlin/io/zenoh/ext/ZDeserialize.kt similarity index 69% rename from zenoh-kotlin/src/commonMain/kotlin/io/zenoh/ext/ZDeserialize.kt rename to zenoh-kotlin/src/jvmAndAndroidMain/kotlin/io/zenoh/ext/ZDeserialize.kt index 93d86a4c8..6e70fe42c 100644 --- a/zenoh-kotlin/src/commonMain/kotlin/io/zenoh/ext/ZDeserialize.kt +++ b/zenoh-kotlin/src/jvmAndAndroidMain/kotlin/io/zenoh/ext/ZDeserialize.kt @@ -1,7 +1,9 @@ package io.zenoh.ext import io.zenoh.bytes.ZBytes -import io.zenoh.jni.JNIZBytes.deserializeViaJNI +import io.zenoh.exceptions.zCall0 +import io.zenoh.jni.bytes.SerializationCodec +import kotlin.reflect.KType import kotlin.reflect.typeOf /** @@ -71,6 +73,16 @@ import kotlin.reflect.typeOf * @see ZBytes * @return a [Result] with the deserialization. */ -inline fun zDeserialize(zbytes: ZBytes): Result = runCatching { - deserializeViaJNI(zbytes, typeOf()) as T -} +inline fun zDeserialize(zbytes: ZBytes): Result = + zDeserializeImpl(zbytes, typeOf()).mapCatching { it as T } + +/** + * Implementation of [zDeserialize]: builds a [SerializationCodec.SerdeType] from the [KType] + * and runs the **pure-Kotlin** [SerializationCodec] deserializer — no JNI crossing. Supports + * the Kotlin-specific `UByte`/`UShort`/`UInt`/`ULong`/`Pair`/`Triple` types in + * addition to the signed/collection types. Wired through [zCall0] exactly like + * a generated wrapper. + */ +@PublishedApi +internal fun zDeserializeImpl(zbytes: ZBytes, type: KType): Result = + zCall0({ Unit }) { SerializationCodec.deserialize(zbytes.toBytes(), serdeTypeOf(type), it) } diff --git a/zenoh-kotlin/src/commonMain/kotlin/io/zenoh/ext/ZSerialize.kt b/zenoh-kotlin/src/jvmAndAndroidMain/kotlin/io/zenoh/ext/ZSerialize.kt similarity index 67% rename from zenoh-kotlin/src/commonMain/kotlin/io/zenoh/ext/ZSerialize.kt rename to zenoh-kotlin/src/jvmAndAndroidMain/kotlin/io/zenoh/ext/ZSerialize.kt index e4d9b224d..401112d54 100644 --- a/zenoh-kotlin/src/commonMain/kotlin/io/zenoh/ext/ZSerialize.kt +++ b/zenoh-kotlin/src/jvmAndAndroidMain/kotlin/io/zenoh/ext/ZSerialize.kt @@ -1,7 +1,9 @@ package io.zenoh.ext import io.zenoh.bytes.ZBytes -import io.zenoh.jni.JNIZBytes.serializeViaJNI +import io.zenoh.exceptions.zCall0 +import io.zenoh.jni.bytes.SerializationCodec +import kotlin.reflect.KType import kotlin.reflect.typeOf /** @@ -71,6 +73,17 @@ import kotlin.reflect.typeOf * @see ZBytes * @return a [Result] with the serialized [ZBytes]. */ -inline fun zSerialize(t: T): Result = runCatching { - serializeViaJNI(t, typeOf()) -} +inline fun zSerialize(t: T): Result = zSerializeImpl(t, typeOf()) + +/** + * Implementation of [zSerialize]: builds a [SerializationCodec.SerdeType] from the [KType] + * and runs the **pure-Kotlin** [SerializationCodec] serializer — no JNI crossing. Supports + * the Kotlin-specific `UByte`/`UShort`/`UInt`/`ULong`/`Pair`/`Triple` types in + * addition to the signed/collection types. Wired through [zCall0] exactly like + * a generated wrapper: the error-sink callback carries a serializer failure, and + * `serdeTypeOf` failures surface through the same `runCatching`. + */ +@PublishedApi +internal fun zSerializeImpl(t: Any, type: KType): Result = + zCall0({ ByteArray(0) }) { SerializationCodec.serialize(t, serdeTypeOf(type), it) } + .map { ZBytes.from(it) } diff --git a/zenoh-kotlin/src/jvmMain/kotlin/io/zenoh/Target.kt b/zenoh-kotlin/src/jvmMain/kotlin/io/zenoh/Target.kt deleted file mode 100644 index f3f282568..000000000 --- a/zenoh-kotlin/src/jvmMain/kotlin/io/zenoh/Target.kt +++ /dev/null @@ -1,35 +0,0 @@ -// -// Copyright (c) 2023 ZettaScale Technology -// -// This program and the accompanying materials are made available under the -// terms of the Eclipse Public License 2.0 which is available at -// http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 -// which is available at https://www.apache.org/licenses/LICENSE-2.0. -// -// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 -// -// Contributors: -// ZettaScale Zenoh Team, -// - -package io.zenoh - -internal enum class Target { - WINDOWS_X86_64_MSVC, - WINDOWS_AARCH64_MSVC, - LINUX_X86_64, - LINUX_AARCH64, - APPLE_AARCH64, - APPLE_X86_64; - - override fun toString(): String { - return when (this) { - WINDOWS_X86_64_MSVC -> "x86_64-pc-windows-msvc" - WINDOWS_AARCH64_MSVC -> "aarch64-pc-windows-msvc" - LINUX_X86_64 -> "x86_64-unknown-linux-gnu" - LINUX_AARCH64 -> "aarch64-unknown-linux-gnu" - APPLE_AARCH64 -> "aarch64-apple-darwin" - APPLE_X86_64 -> "x86_64-apple-darwin" - } - } -} diff --git a/zenoh-kotlin/src/jvmMain/kotlin/io/zenoh/Zenoh.kt b/zenoh-kotlin/src/jvmMain/kotlin/io/zenoh/Zenoh.kt deleted file mode 100644 index 8da476569..000000000 --- a/zenoh-kotlin/src/jvmMain/kotlin/io/zenoh/Zenoh.kt +++ /dev/null @@ -1,184 +0,0 @@ -// -// Copyright (c) 2023 ZettaScale Technology -// -// This program and the accompanying materials are made available under the -// terms of the Eclipse Public License 2.0 which is available at -// http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 -// which is available at https://www.apache.org/licenses/LICENSE-2.0. -// -// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 -// -// Contributors: -// ZettaScale Zenoh Team, -// - -package io.zenoh - -import java.io.File -import java.io.FileInputStream -import java.io.FileOutputStream -import java.io.InputStream -import java.util.zip.ZipInputStream - -/** - * Static singleton class to load the Zenoh native library once and only once, as well as the logger in function of the - * log level configuration. - */ -internal actual object ZenohLoad { - private const val ZENOH_LIB_NAME = "zenoh_jni" - - init { - // Try first to load the local native library for cases in which the module was built locally, - // otherwise try to load from the JAR. - if (tryLoadingLocalLibrary().isFailure) { - val target = determineTarget().getOrThrow() - tryLoadingLibraryFromJarPackage(target).getOrThrow() - } - } - - /** - * Determine target - * - * Determines the [Target] corresponding to the machine on top of which the native code will run. - * - * @return A result with the target. - */ - private fun determineTarget(): Result = runCatching { - val osName = System.getProperty("os.name").lowercase() - val osArch = System.getProperty("os.arch").lowercase() - - val target = when { - osName.contains("win") -> when { - osArch.contains("x86_64") || osArch.contains("amd64") || osArch.contains("x64") -> - Target.WINDOWS_X86_64_MSVC - - osArch.contains("aarch64") || osArch.contains("arm64") -> - Target.WINDOWS_AARCH64_MSVC - - else -> throw UnsupportedOperationException("Unsupported architecture on Windows: $osArch") - } - - osName.contains("mac") || osName.contains("darwin") || osName.contains("os x") -> when { - osArch.contains("x86_64") || osArch.contains("amd64") || osArch.contains("x64") -> - Target.APPLE_X86_64 - - osArch.contains("aarch64") || osArch.contains("arm64") -> - Target.APPLE_AARCH64 - - else -> throw UnsupportedOperationException("Unsupported architecture on macOS: $osArch") - } - - osName.contains("nix") || osName.contains("nux") || osName.contains("aix") -> when { - osArch.contains("x86_64") || osArch.contains("amd64") || osArch.contains("x64") -> - Target.LINUX_X86_64 - - osArch.contains("aarch64") || osArch.contains("arm64") -> - Target.LINUX_AARCH64 - - else -> throw UnsupportedOperationException("Unsupported architecture on Linux/Unix: $osArch") - } - - else -> throw UnsupportedOperationException("Unsupported platform: $osName") - } - return Result.success(target) - } - - /** - * Unzip library. - * - * The Zenoh libraries are stored within the JAR as compressed ZIP files. - * The location of the zipped files is expected to be under target/target.zip. - * It is expected that the zip file only contains the compressed library. - * - * The uncompressed library will be stored temporarily and deleted on exit. - * - * @param compressedLib Input stream pointing to the compressed library. - * @return A result with the uncompressed library file. - */ - private fun unzipLibrary(compressedLib: InputStream): Result = runCatching { - val zipInputStream = ZipInputStream(compressedLib) - val buffer = ByteArray(1024) - val zipEntry = zipInputStream.nextEntry - - val library = File.createTempFile(zipEntry!!.name, ".tmp") - library.deleteOnExit() - - val parent = library.parentFile - if (!parent.exists()) { - parent.mkdirs() - } - - val fileOutputStream = FileOutputStream(library) - var len: Int - while (zipInputStream.read(buffer).also { len = it } > 0) { - fileOutputStream.write(buffer, 0, len) - } - fileOutputStream.close() - - zipInputStream.closeEntry() - zipInputStream.close() - return Result.success(library) - } - - private fun loadLibraryAsInputStream(target: Target): Result = runCatching { - val targetName = "$target/$target.zip" - val libUrl = ClassLoader.getSystemClassLoader().getResourceAsStream(targetName) - ?: javaClass.classLoader.getResourceAsStream(targetName)!! - val uncompressedLibFile = unzipLibrary(libUrl) - return Result.success(FileInputStream(uncompressedLibFile.getOrThrow())) - } - - @Suppress("UnsafeDynamicallyLoadedCode") - private fun loadZenohJNI(inputStream: InputStream) { - val tempLib = File.createTempFile("tempLib", ".tmp") - tempLib.deleteOnExit() - - FileOutputStream(tempLib).use { output -> - inputStream.copyTo(output) - } - - System.load(tempLib.absolutePath) - } - - /** - * Load library from jar package. - * - * Attempts to load the library corresponding to the `target` specified from the zenoh kotlin jar. - * - * @param target - */ - private fun tryLoadingLibraryFromJarPackage(target: Target): Result = runCatching { - val lib: Result = loadLibraryAsInputStream(target) - lib.onSuccess { loadZenohJNI(it) }.onFailure { throw Exception("Unable to load Zenoh JNI: $it") } - } - - /** - * Try loading local library. - * - * This function aims to load the default library that is usually included when building the zenoh kotlin library - * locally. - */ - private fun tryLoadingLocalLibrary(): Result = runCatching { - val lib = ClassLoader.getSystemClassLoader().findLibraryStream(ZENOH_LIB_NAME) - ?: javaClass.classLoader.findLibraryStream( - ZENOH_LIB_NAME - ) - if (lib != null) { - loadZenohJNI(lib) - } else { - throw Exception("Unable to load local Zenoh JNI.") - } - } -} - -private fun ClassLoader.findLibraryStream(libraryName: String): InputStream? { - val libraryExtensions = listOf(".dylib", ".so", ".dll") - for (extension in libraryExtensions) { - val resourcePath = "lib$libraryName$extension" - val inputStream = getResourceAsStream(resourcePath) - if (inputStream != null) { - return inputStream - } - } - return null -}