From 3601b3a5af0772abddba8527b73841ea1951043a Mon Sep 17 00:00:00 2001 From: Michael Ilyin Date: Thu, 16 Jul 2026 13:26:38 +0200 Subject: [PATCH 1/9] Open the zenoh-flat transition integration branch Tracking document for the rebuild of zenoh-java on the generated zenoh-flat-jni bindings. Constituent PRs target this branch; it merges to main as a whole when the transition completes (removing this file). --- ZENOH_FLAT_TRANSITION.md | 38 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) create mode 100644 ZENOH_FLAT_TRANSITION.md diff --git a/ZENOH_FLAT_TRANSITION.md b/ZENOH_FLAT_TRANSITION.md new file mode 100644 index 00000000..e03f18d5 --- /dev/null +++ b/ZENOH_FLAT_TRANSITION.md @@ -0,0 +1,38 @@ +# zenoh-flat transition + +This branch (`zenoh-flat-transition`) is the **integration branch** for rebuilding +zenoh-java on top of the generated JNI/Kotlin bindings, replacing the hand-written +`zenoh-jni` layer. It exists so the transition can land as a series of reviewable +PRs targeting this branch instead of `main`; when the transition is complete, this +branch merges to `main` as a whole (and this file is removed). + +## Architecture + +``` +zenoh (Rust) + └─ zenoh-flat flat #[prebindgen]-annotated Rust API + └─ zenoh-flat-jni generated JNI externs + Kotlin classes (prebindgen lang::JniGen) + └─ zenoh-java Kotlin SDK wrapper (this repo) +``` + +- **prebindgen** — (generator) +- **zenoh-flat** — (flat Rust API) +- **zenoh-flat-jni** — (generated bindings, + consumed as a sibling checkout in CI and via Gradle composite build locally; + as a Maven artifact once published) + +## Constituent PRs + +| PR | Scope | Status | +| --- | --- | --- | +| [#481](https://github.com/eclipse-zenoh/zenoh-java/pull/481) | Use receiver-style zenoh-flat-jni bindings (generated Session/Query/Publisher methods, split key-expr overloads, de-prefixed callback names); +61–83% subscriber throughput | CI green | + +Companion PRs in the upstream repos are coordinated per constituent PR (e.g. +[ZettaScaleLabs/zenoh-flat-jni#3](https://github.com/ZettaScaleLabs/zenoh-flat-jni/pull/3) +pairs with #481 and merges first; CI here pins the exact upstream commits). + +## CI pinning + +`.github/workflows/ci.yml` on the constituent branches pins the exact +`zenoh-flat-jni` / `zenoh-flat` commits the code was written against, while +`prebindgen` resolves from its `main`. Pins are bumped as the upstream PRs land. From 94c885afc7f481c878a5b51bcb48220e13e5738d Mon Sep 17 00:00:00 2001 From: Michael Ilyin Date: Thu, 16 Jul 2026 13:32:21 +0200 Subject: [PATCH 2/9] Use receiver-style zenoh-flat-jni bindings (#481) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * initial code generation * templates in separate files in prebindgen-ext * mangle harness * dependency on zenoh-flat-jni added * switch to keyexprValidate, package support * zenoh-jni reexported by zenoh-flat-jni * No load generator * output only declared in prebindgen-ext * better error reporting * reporting fix * report unresolved descendants from required roots BFS from each required-but-unresolved type through struct fields, enum variants, generic args, and impl Fn args, surfacing transitively unresolved entries that propagate_required cannot reach (because subs edges don't exist past an unresolved parent). Without this, a missing declaration in build.rs for a type only referenced as a field of another unresolved struct went unreported. Co-Authored-By: Claude Opus 4.7 * refactored, test added * ptr class added * qualify non-fqn types * filter undeclared fns out of JNINative/JNIOrphaned emit Co-Authored-By: Claude Opus 4.7 * drop JNIOrphaned bucket; reuse declared_functions trait method .method() is a namespace declaration — every #[prebindgen] fn must be assigned to a typed handle, data class, enum, or kotlin_package. There is no third "orphan" bucket, so JNIOrphaned was always emitted empty and the duplicate declared_function_names helper just repeated the PrebindgenExt::declared_functions trait method. - delete write_jni_orphaned, jni_orphaned_class_name, the stale generated JNIOrphaned.kt file - rename render_jni_orphaned_source -> render_jni_package_source, drop the now-redundant include_only_promoted / wrap_in_object / declared / class_name params - drop declared_function_names; render_jni_native_source now takes &HashSet from declared_functions() directly Co-Authored-By: Claude Opus 4.7 * missing functions added to autogeneration * into in build.rs * universal post-emit qualification via VisitMut visitor Replace the five scattered self.qualify_emitted_type() calls (build_input_fn, build_output_fn, opaque_handle_input, build_handle_destructor_items, option_input) with a single AST visitor pass applied once at write time. The previous design qualified only at specific emit sites — each new emit site had to remember the call. emit_into_dispatcher missed it, so the impl-Into dispatcher body emitted bare KeyExpr/ZKeyExpr references that failed to resolve in the binding crate's scope. Architecture: - New PrebindgenExt::post_process_item(item: &mut syn::Item) trait hook (default no-op), invoked by write_rust on every emitted item. - JniExt implements it via qualify_item, which runs QualifyEmittedTypes — a syn::visit_mut::VisitMut that walks the full AST (signatures, generic args, type ascriptions in let bindings, *mut/*const in cast expressions) and prefixes every bare single-segment Type::Path matching a registered source-module name with source_module. - option_input no longer takes &JniExt; the body uses the bare t1 token and the post-emit visitor qualifies it later. Also fixes the pre-existing TypeEntry field-name drift in final_invariant_reports_unresolved_field_of_unresolved_struct. Co-Authored-By: Claude Opus 4.7 * transition to zenoh-flat keyexpr for zenoh-java * ZKeyExpr improved * closeable data classes * universa close for data classes * throwable data class * config in zenoh-flat * scouting api * JNIScout removed * whatami from jni * value type * zenoh id is a wrapper * &str as input parameter * logging api * structured methods * buildrs api renamr * scouting package * packaging fixes * set intersection level * zenoh-flat as mods * qos enums ported * enum validation in prebindgen * wrap enums * bytearray in zenoh-flat * zenoh-java zbytes * jni synchronized * enum return fix * take and ptr wraper in callback * manually implemented serialization * publisher ported * attachment into * encoding in zenoh-flat * encoding constants * renaming, fromstring added * zencoding * subscriber ported * querier/queriable ported * query and reply methods ported * query structure * renaming * get and corresponding structures * full timestamp representation in zenoh-flat * old log init removed * liveliness migrated, codegenerator fixed * value type return fix * sesstion migrated * valur type handling fix * niche fix * wrappers directly in Session and. Liveliness * bytes form zenoh_id to string added * zenoh-jni removed * jni generator and zenoh-flat moved out * wrapper lock function * blob type * vlaue blob zzenohid * missing api surface added * switch to wrapped functions * single into * gtom struct from callback * recursive call * flatten callbacks * create java object directly * flatten input * no array for locks * removed expanded code * expand constructor support * types expanded * use accessors * sample attachment accessor * nested output * vec unfold * comment added * naming shortened * converter, renamings * process error as normal type * zerror declared * keyexpr default constructor * accessor_fun * full expand * recursive expamd demo * new api migration * zbytes support callback error interface * expand callbacks * output kotlin one file per package * flattened structure * named callback parameters * regenrate kotlin code * generated files reformatted * cargo fmt * callback formatting * no throws in generated code * hello and query unwrap * tests fixed * query close in reply operations * reply unwrap * multiline long calls * sample attributes added * typed interface callback * generated code insignificant change * derfaults in error * proxy interface * accelerated onerror handler * unfold encoding for acceleration * renamings in builder * refactor: Remove local zenoh-flat-jni subproject, consume as Maven dependency - Remove :zenoh-flat-jni from gradle subprojects (moved to separate repo) - Replace local source dependency with Maven: org.eclipse.zenoh:zenoh-flat-jni:1.9.0 - Remove Cargo build orchestration (buildZenohFlatJni, buildZenohFlatJNI, BuildMode) - Remove build paths for zenoh-flat-jni libraries (now bundled in Maven JAR) - Remove Cargo-related Gradle plugins and tasks - Native libraries are now obtained from the zenoh-flat-jni Maven artifact - Kotlin sources are now imported from the Maven JAR as a dependency - Supports optional local development via: includeBuild("../zenoh-flat-jni") - Simplifies zenoh-java build: Gradle now only compiles Kotlin, not Rust Migration notes: - zenoh-flat-jni is now at https://github.com/ZettaScaleLabs/zenoh-flat-jni - Versions will be synchronized with Zenoh releases (1.9.0, etc.) - For local development, clone both repos and use composite builds if needed Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * build: Enable Gradle composite build for local zenoh-flat-jni development - Uncomment includeBuild for ../zenoh-flat-jni since it's now in the PREBINDGEN workspace - zenoh-flat-jni is at /Users/milyin/PREBINDGEN/zenoh-flat-jni - Gradle will prefer local build over Maven artifact when composite build is enabled - Allows coordinated development of zenoh-java and zenoh-flat-jni Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * chore: Remove zenoh-flat-jni directory from zenoh-java The zenoh-flat-jni project has been moved to: - GitHub: https://github.com/ZettaScaleLabs/zenoh-flat-jni - Local workspace: /Users/milyin/PREBINDGEN/zenoh-flat-jni zenoh-java now consumes zenoh-flat-jni via: 1. Maven artifact (org.eclipse.zenoh:zenoh-flat-jni:1.9.0) in production 2. Gradle composite build (../zenoh-flat-jni) for local development Removing the local copy avoids duplication and confusion. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * api change ported * serialize fixes * loading in zenoh-flat-jni * import predefined encodings as consts * Use typed zenoh-flat-jni key expression overloads * Install CI lint components for the pinned Rust toolchain * Run flat JNI clippy without a tracked lockfile * Preserve consuming Rust API semantics * Limit rustfmt CI to the JNI crate * Remove stale local task lock * Use generated JNI receiver methods * Pin receiver-style JNI bindings in CI * Track de-prefixed zenoh-flat type names in JNI bindings zenoh-flat's consolidated API dropped the Z prefix from its Rust types (ZSample -> Sample, ...), and the generated callback interface names follow the type name mechanically: ZSampleCallback -> SampleCallback etc. Rename the four callback references in FlatCallbacks.kt (signatures are unchanged) and follow the encoding id accessor rename (id -> getId). Pin CI to the updated zenoh-flat-jni (receiver-style bindings regenerated against prebindgen main and zenoh-flat main) and to zenoh-flat main. Verified locally: jvmTest 101 tests, 0 failures against the composite build. * Use derived JNI factory and getter names zenoh-flat-jni now derives all regular method names from the flat crate's identifiers (explicit .name() only where a clash forces it), so the internal calls follow: JniKeyExpr.newTryFrom/newAutocanonize/newJoin/newConcat, JniConfig.newFrom*, JniEncoding.newFromString/newWithSchema, JniZBytes.newFromVec, zSession.getPeersZid/getRoutersZid. SDK public API unchanged. CI pin bumped to the matching zenoh-flat-jni commit. --------- Co-authored-by: Claude Opus 4.7 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .github/workflows/ci.yml | 51 +- examples/build.gradle.kts | 10 +- examples/src/main/java/io/zenoh/ZSubThr.java | 15 +- settings.gradle.kts | 6 +- zenoh-java/build.gradle.kts | 103 +- .../src/androidMain/kotlin/io.zenoh/Zenoh.kt | 27 - .../src/commonMain/kotlin/io/zenoh/Config.kt | 43 +- .../kotlin/io/zenoh/FlatCallbacks.kt | 86 + .../src/commonMain/kotlin/io/zenoh/Logger.kt | 11 +- .../src/commonMain/kotlin/io/zenoh/Session.kt | 270 +- .../src/commonMain/kotlin/io/zenoh/Zenoh.kt | 21 +- .../kotlin/io/zenoh/bytes/Encoding.kt | 749 ++- .../kotlin/io/zenoh/bytes/ZBytes.kt | 44 +- .../kotlin/io/zenoh/config/WhatAmI.kt | 10 +- .../kotlin/io/zenoh/config/ZenohId.kt | 11 +- .../io/zenoh/exceptions/JNIErrorHandlers.kt | 42 + .../kotlin/io/zenoh/ext/ZDeserializer.kt | 5 +- .../kotlin/io/zenoh/ext/ZSerializer.kt | 5 +- .../kotlin/io/zenoh/jni/JNIConfig.kt | 101 - .../kotlin/io/zenoh/jni/JNIKeyExpr.kt | 104 - .../kotlin/io/zenoh/jni/JNILiveliness.kt | 188 - .../kotlin/io/zenoh/jni/JNILivelinessToken.kt | 12 - .../kotlin/io/zenoh/jni/JNIPublisher.kt | 70 - .../kotlin/io/zenoh/jni/JNIQuerier.kt | 137 - .../kotlin/io/zenoh/jni/JNIQuery.kt | 106 - .../kotlin/io/zenoh/jni/JNIQueryable.kt | 30 - .../kotlin/io/zenoh/jni/JNIScout.kt | 87 - .../kotlin/io/zenoh/jni/JNISession.kt | 548 -- .../kotlin/io/zenoh/jni/JNISubscriber.kt | 31 - .../kotlin/io/zenoh/jni/JNIZBytes.kt | 33 - .../kotlin/io/zenoh/jni/JNIZenohId.kt | 27 - .../io/zenoh/jni/callbacks/JNIGetCallback.kt | 35 - .../zenoh/jni/callbacks/JNIOnCloseCallback.kt | 21 - .../jni/callbacks/JNIQueryableCallback.kt | 26 - .../zenoh/jni/callbacks/JNIScoutCallback.kt | 20 - .../jni/callbacks/JNISubscriberCallback.kt | 31 - .../kotlin/io/zenoh/keyexpr/KeyExpr.kt | 71 +- .../io/zenoh/keyexpr/SetIntersectionLevel.kt | 13 +- .../kotlin/io/zenoh/liveliness/Liveliness.kt | 92 +- .../io/zenoh/liveliness/LivelinessToken.kt | 7 +- .../kotlin/io/zenoh/pubsub/Publisher.kt | 33 +- .../kotlin/io/zenoh/pubsub/Subscriber.kt | 15 +- .../kotlin/io/zenoh/qos/CongestionControl.kt | 16 +- .../kotlin/io/zenoh/qos/Priority.kt | 21 +- .../kotlin/io/zenoh/qos/Reliability.kt | 13 + .../io/zenoh/query/ConsolidationMode.kt | 7 + .../kotlin/io/zenoh/query/Querier.kt | 48 +- .../commonMain/kotlin/io/zenoh/query/Query.kt | 101 +- .../kotlin/io/zenoh/query/QueryTarget.kt | 6 + .../kotlin/io/zenoh/query/Queryable.kt | 15 +- .../kotlin/io/zenoh/query/ReplyKeyExpr.kt | 10 + .../kotlin/io/zenoh/sample/Sample.kt | 43 + .../kotlin/io/zenoh/sample/SampleKind.kt | 5 + .../kotlin/io/zenoh/scouting/Scout.kt | 48 +- .../src/jvmMain/kotlin/io/zenoh/Target.kt | 35 - .../src/jvmMain/kotlin/io/zenoh/Zenoh.kt | 184 - .../jvmTest/java/io/zenoh/QueryableTest.java | 17 + .../jvmTest/java/io/zenoh/SessionTest.java | 13 + .../jvmTest/java/io/zenoh/SubscriberTest.java | 50 + zenoh-jni/Cargo.lock | 4404 ----------------- zenoh-jni/Cargo.toml | 54 - zenoh-jni/src/config.rs | 185 - zenoh-jni/src/errors.rs | 59 - zenoh-jni/src/key_expr.rs | 333 -- zenoh-jni/src/lib.rs | 37 - zenoh-jni/src/liveliness.rs | 242 - zenoh-jni/src/logger.rs | 64 - zenoh-jni/src/publisher.rs | 130 - zenoh-jni/src/querier.rs | 137 - zenoh-jni/src/query.rs | 206 - zenoh-jni/src/queryable.rs | 41 - zenoh-jni/src/scouting.rs | 113 - zenoh-jni/src/session.rs | 1201 ----- zenoh-jni/src/subscriber.rs | 41 - zenoh-jni/src/utils.rs | 189 - zenoh-jni/src/zbytes.rs | 451 -- zenoh-jni/src/zenoh_id.rs | 42 - 77 files changed, 1259 insertions(+), 10649 deletions(-) delete mode 100644 zenoh-java/src/androidMain/kotlin/io.zenoh/Zenoh.kt create mode 100644 zenoh-java/src/commonMain/kotlin/io/zenoh/FlatCallbacks.kt create mode 100644 zenoh-java/src/commonMain/kotlin/io/zenoh/exceptions/JNIErrorHandlers.kt delete mode 100644 zenoh-java/src/commonMain/kotlin/io/zenoh/jni/JNIConfig.kt delete mode 100644 zenoh-java/src/commonMain/kotlin/io/zenoh/jni/JNIKeyExpr.kt delete mode 100644 zenoh-java/src/commonMain/kotlin/io/zenoh/jni/JNILiveliness.kt delete mode 100644 zenoh-java/src/commonMain/kotlin/io/zenoh/jni/JNILivelinessToken.kt delete mode 100644 zenoh-java/src/commonMain/kotlin/io/zenoh/jni/JNIPublisher.kt delete mode 100644 zenoh-java/src/commonMain/kotlin/io/zenoh/jni/JNIQuerier.kt delete mode 100644 zenoh-java/src/commonMain/kotlin/io/zenoh/jni/JNIQuery.kt delete mode 100644 zenoh-java/src/commonMain/kotlin/io/zenoh/jni/JNIQueryable.kt delete mode 100644 zenoh-java/src/commonMain/kotlin/io/zenoh/jni/JNIScout.kt delete mode 100644 zenoh-java/src/commonMain/kotlin/io/zenoh/jni/JNISession.kt delete mode 100644 zenoh-java/src/commonMain/kotlin/io/zenoh/jni/JNISubscriber.kt delete mode 100644 zenoh-java/src/commonMain/kotlin/io/zenoh/jni/JNIZBytes.kt delete mode 100644 zenoh-java/src/commonMain/kotlin/io/zenoh/jni/JNIZenohId.kt delete mode 100644 zenoh-java/src/commonMain/kotlin/io/zenoh/jni/callbacks/JNIGetCallback.kt delete mode 100644 zenoh-java/src/commonMain/kotlin/io/zenoh/jni/callbacks/JNIOnCloseCallback.kt delete mode 100644 zenoh-java/src/commonMain/kotlin/io/zenoh/jni/callbacks/JNIQueryableCallback.kt delete mode 100644 zenoh-java/src/commonMain/kotlin/io/zenoh/jni/callbacks/JNIScoutCallback.kt delete mode 100644 zenoh-java/src/commonMain/kotlin/io/zenoh/jni/callbacks/JNISubscriberCallback.kt delete mode 100644 zenoh-java/src/jvmMain/kotlin/io/zenoh/Target.kt delete mode 100644 zenoh-java/src/jvmMain/kotlin/io/zenoh/Zenoh.kt delete mode 100644 zenoh-jni/Cargo.lock delete mode 100644 zenoh-jni/Cargo.toml delete mode 100644 zenoh-jni/src/config.rs delete mode 100644 zenoh-jni/src/errors.rs delete mode 100644 zenoh-jni/src/key_expr.rs delete mode 100644 zenoh-jni/src/lib.rs delete mode 100644 zenoh-jni/src/liveliness.rs delete mode 100644 zenoh-jni/src/logger.rs delete mode 100644 zenoh-jni/src/publisher.rs delete mode 100644 zenoh-jni/src/querier.rs delete mode 100644 zenoh-jni/src/query.rs delete mode 100644 zenoh-jni/src/queryable.rs delete mode 100644 zenoh-jni/src/scouting.rs delete mode 100644 zenoh-jni/src/session.rs delete mode 100644 zenoh-jni/src/subscriber.rs delete mode 100644 zenoh-jni/src/utils.rs delete mode 100644 zenoh-jni/src/zbytes.rs delete mode 100644 zenoh-jni/src/zenoh_id.rs diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index dd30cfe1..28a2fab3 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -22,7 +22,34 @@ jobs: os: [ubuntu-latest, macos-latest] steps: - - uses: actions/checkout@v4 + - name: Check out zenoh-java + uses: actions/checkout@v4 + with: + path: zenoh-java + + - name: Check out zenoh-flat-jni + uses: actions/checkout@v4 + with: + repository: ZettaScaleLabs/zenoh-flat-jni + ref: c82656b2f41093d04921ead50516219ae74c7e96 + path: zenoh-flat-jni + + - name: Check out zenoh-flat + uses: actions/checkout@v4 + with: + repository: ZettaScaleLabs/zenoh-flat + ref: 7b151d7808583ed15884fada2fd40d5db387c982 + path: zenoh-flat + + - name: Use prebindgen from GitHub main + shell: bash + working-directory: zenoh-flat-jni + run: | + sed -i.bak \ + -e 's|prebindgen = { path = "../prebindgen/prebindgen" }|prebindgen = { git = "https://github.com/milyin/prebindgen.git", branch = "main" }|g' \ + -e 's|prebindgen-proc-macro = { path = "../prebindgen/prebindgen-proc-macro",|prebindgen-proc-macro = { git = "https://github.com/milyin/prebindgen.git", branch = "main",|g' \ + Cargo.toml ../zenoh-flat/Cargo.toml + rm Cargo.toml.bak ../zenoh-flat/Cargo.toml.bak - uses: actions/setup-java@v4 with: @@ -32,31 +59,29 @@ jobs: - name: Install Rust toolchain run: | rustup show - rustup component add rustfmt clippy + rustup component add rustfmt clippy --toolchain 1.93.0 - name: Cargo Format - working-directory: zenoh-jni - run: cargo fmt --all --check + working-directory: zenoh-flat-jni + run: cargo fmt --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: Clippy Check + working-directory: zenoh-flat-jni + run: cargo clippy --all-targets --all-features -- -D warnings - name: Check for feature leaks - working-directory: zenoh-jni + working-directory: zenoh-flat-jni run: cargo test --no-default-features - - name: Build Zenoh-JNI - working-directory: zenoh-jni + - name: Build zenoh-flat-jni + working-directory: zenoh-flat-jni run: cargo build - name: Setup Gradle uses: gradle/actions/setup-gradle@v4 - name: Gradle Test + working-directory: zenoh-java run: ./gradlew jvmTest --info markdown_lint: diff --git a/examples/build.gradle.kts b/examples/build.gradle.kts index dc5684d9..de7fc4ea 100644 --- a/examples/build.gradle.kts +++ b/examples/build.gradle.kts @@ -78,19 +78,13 @@ tasks { examples.forEach { example -> register(example, JavaExec::class) { - dependsOn("CompileZenohJNI") + dependsOn(":zenoh-java:buildZenohFlatJni") description = "Run the $example example" mainClass.set("io.zenoh.$example") classpath(sourceSets["main"].runtimeClasspath) - val zenohPaths = "../zenoh-jni/target/release" + val zenohPaths = "../zenoh-flat-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/java/io/zenoh/ZSubThr.java b/examples/src/main/java/io/zenoh/ZSubThr.java index 9c3d6a72..35fc033e 100644 --- a/examples/src/main/java/io/zenoh/ZSubThr.java +++ b/examples/src/main/java/io/zenoh/ZSubThr.java @@ -17,6 +17,7 @@ import io.zenoh.exceptions.ZError; import io.zenoh.keyexpr.KeyExpr; import io.zenoh.pubsub.Subscriber; +import io.zenoh.sample.Sample; import picocli.CommandLine; import java.util.List; @@ -40,7 +41,7 @@ public Integer call() throws Exception { System.out.println("Opening Session"); try (Session session = Zenoh.open(config)) { try (KeyExpr keyExpr = KeyExpr.tryFrom("test/thr")) { - subscriber = session.declareSubscriber(keyExpr, sample -> listener(number)); + subscriber = session.declareSubscriber(keyExpr, sample -> listener(sample, number)); System.out.println("Press CTRL-C to quit..."); while (subscriber.isValid()) { @@ -51,7 +52,12 @@ public Integer call() throws Exception { return 0; } - private void listener(long number) { + private void listener(Sample sample, long number) { + // Touch the delivered data so it actually reaches the Java surface + // (payload bytes + key-expression string): keeps lazy bindings honest + // and prevents either side from skipping materialization costs. + dataSink += sample.getPayload().toBytes().length + + sample.getKeyExpr().toString().length(); if (batchCount > samples) { closeSubscriber(); report(); @@ -86,8 +92,8 @@ private void report() { double elapsedTimeSecs = (double) (end - globalStartTimestampNs) / NANOS_TO_SEC; double averageMessagesPerSec = totalMessages / elapsedTimeSecs; - System.out.printf("Received %d messages in %.2f seconds: averaged %.2f msgs/sec%n", - totalMessages, elapsedTimeSecs, averageMessagesPerSec); + System.out.printf("Received %d messages in %.2f seconds: averaged %.2f msgs/sec (sink %d)%n", + totalMessages, elapsedTimeSecs, averageMessagesPerSec, dataSink); } private void closeSubscriber() { @@ -114,6 +120,7 @@ private void closeSubscriber() { private static final long NANOS_TO_SEC = 1_000_000_000L; private long batchCount = 0; private long count = 0; + private long dataSink = 0; private long startTimestampNs = 0; private long globalStartTimestampNs = 0; diff --git a/settings.gradle.kts b/settings.gradle.kts index 2d17a6a4..3f04b1a3 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -23,7 +23,11 @@ rootProject.name = "zenoh-java" include(":zenoh-java") include(":examples") -include(":zenoh-jni") + +// zenoh-flat-jni is now a separate repository (https://github.com/ZettaScaleLabs/zenoh-flat-jni) +// and is consumed as a Maven artifact: org.eclipse.zenoh:zenoh-flat-jni:VERSION +// For local development with coordinated changes, use Gradle composite build: +includeBuild("../zenoh-flat-jni") plugins { id("org.gradle.toolchains.foojay-resolver-convention") version("0.4.0") diff --git a/zenoh-java/build.gradle.kts b/zenoh-java/build.gradle.kts index 123d7499..625e9033 100644 --- a/zenoh-java/build.gradle.kts +++ b/zenoh-java/build.gradle.kts @@ -12,8 +12,6 @@ // ZettaScale Zenoh Team, // -import com.nishtahir.CargoExtension - plugins { kotlin("multiplatform") kotlin("plugin.serialization") @@ -30,26 +28,21 @@ val release = project.findProperty("release")?.toString()?.toBoolean() == true // 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 +// zenoh-flat-jni is now consumed as a Maven artifact: org.eclipse.zenoh:zenoh-flat-jni:VERSION +// Native libraries are bundled in the JAR by the zenoh-flat-jni module if (androidEnabled) { apply(plugin = "com.android.library") - apply(plugin = "org.mozilla.rust-android-gradle.rust-android") - - configureCargo() configureAndroid() } + kotlin { jvmToolchain(11) jvm { compilations.all { kotlinOptions.jvmTarget = "11" } - testRuns["test"].executionTask.configure { - val zenohPaths = "../zenoh-jni/target/$buildMode" - jvmArgs("-Djava.library.path=$zenohPaths") - } if (!androidEnabled) { withJava() // Adding java to a kotlin lib targeting android is incompatible // The java code is only meant for testing and is non-critical for the android publication. @@ -66,6 +59,8 @@ kotlin { sourceSets { val commonMain by getting { dependencies { + // Zenoh Flat JNI - includes Kotlin sources and native libraries + implementation("org.eclipse.zenoh:zenoh-flat-jni:1.9.0") implementation("commons-net:commons-net:3.9.0") implementation("com.google.guava:guava:33.3.1-jre") } @@ -82,18 +77,6 @@ kotlin { } } } - 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) { @@ -153,65 +136,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" @@ -249,20 +173,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-java/src/androidMain/kotlin/io.zenoh/Zenoh.kt b/zenoh-java/src/androidMain/kotlin/io.zenoh/Zenoh.kt deleted file mode 100644 index 74d42e51..00000000 --- a/zenoh-java/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-java/src/commonMain/kotlin/io/zenoh/Config.kt b/zenoh-java/src/commonMain/kotlin/io/zenoh/Config.kt index 91bc0e01..d18977d6 100644 --- a/zenoh-java/src/commonMain/kotlin/io/zenoh/Config.kt +++ b/zenoh-java/src/commonMain/kotlin/io/zenoh/Config.kt @@ -15,7 +15,9 @@ package io.zenoh import io.zenoh.exceptions.ZError -import io.zenoh.jni.JNIConfig +import io.zenoh.exceptions.throwZError +import io.zenoh.exceptions.throwZError0 +import io.zenoh.jni.config.Config as JniConfig import java.io.File import java.nio.file.Path @@ -35,7 +37,7 @@ import java.nio.file.Path * 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 zConfig: JniConfig) { companion object { @@ -45,9 +47,7 @@ class Config internal constructor(internal val jniConfig: JNIConfig) { * Returns the default config. */ @JvmStatic - fun loadDefault(): Config { - return JNIConfig.loadDefaultConfig() - } + fun loadDefault(): Config = Config(JniConfig.newDefault(throwZError0)) /** * Loads the configuration from the [File] specified. @@ -58,9 +58,7 @@ class Config internal constructor(internal val jniConfig: JNIConfig) { */ @JvmStatic @Throws(ZError::class) - fun fromFile(file: File): Config { - return JNIConfig.loadConfigFile(file) - } + fun fromFile(file: File): Config = fromFile(file.toPath()) /** * Loads the configuration from the [Path] specified. @@ -71,9 +69,7 @@ class Config internal constructor(internal val jniConfig: JNIConfig) { */ @JvmStatic @Throws(ZError::class) - fun fromFile(path: Path): Config { - return JNIConfig.loadConfigFile(path) - } + fun fromFile(path: Path): Config = Config(JniConfig.newFromFile(path.toString(), throwZError)) /** * Loads the configuration from json-formatted string. @@ -86,9 +82,7 @@ class Config internal constructor(internal val jniConfig: JNIConfig) { */ @JvmStatic @Throws(ZError::class) - fun fromJson(config: String): Config { - return JNIConfig.loadJsonConfig(config) - } + fun fromJson(config: String): Config = Config(JniConfig.newFromJson(config, throwZError)) /** * Loads the configuration from json5-formatted string. @@ -101,9 +95,7 @@ class Config internal constructor(internal val jniConfig: JNIConfig) { */ @JvmStatic @Throws(ZError::class) - fun fromJson5(config: String): Config { - return JNIConfig.loadJson5Config(config) - } + fun fromJson5(config: String): Config = Config(JniConfig.newFromJson5(config, throwZError)) /** * Loads the configuration from yaml-formatted string. @@ -116,9 +108,7 @@ class Config internal constructor(internal val jniConfig: JNIConfig) { */ @JvmStatic @Throws(ZError::class) - fun fromYaml(config: String): Config { - return JNIConfig.loadYamlConfig(config) - } + fun fromYaml(config: String): Config = Config(JniConfig.newFromYaml(config, throwZError)) /** * Loads the configuration from the env variable [CONFIG_ENV]. @@ -141,19 +131,12 @@ class Config internal constructor(internal val jniConfig: JNIConfig) { * The json value associated to the [key]. */ @Throws(ZError::class) - fun getJson(key: String): String { - return jniConfig.getJson(key) - } + fun getJson(key: String): String = zConfig.getJson(key, throwZError) /** * Inserts a json5 value associated to the [key] into the Config. */ @Throws(ZError::class) - fun insertJson5(key: String, value: String) { - jniConfig.insertJson5(key, value) - } - - protected fun finalize() { - jniConfig.close() - } + fun insertJson5(key: String, value: String) = + zConfig.insertJson5(key, value, throwZError) } diff --git a/zenoh-java/src/commonMain/kotlin/io/zenoh/FlatCallbacks.kt b/zenoh-java/src/commonMain/kotlin/io/zenoh/FlatCallbacks.kt new file mode 100644 index 00000000..63d401e3 --- /dev/null +++ b/zenoh-java/src/commonMain/kotlin/io/zenoh/FlatCallbacks.kt @@ -0,0 +1,86 @@ +// +// 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 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.query.Query +import io.zenoh.query.Reply +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 { keH, payloadH, encH, encId, kindInt, ntp64, express, prioInt, ccInt, attachH, reliabilityInt, sourceZid, sourceEid, sourceSn -> + f(Sample.fromParts(keH, payloadH, encH, encId, kindInt, ntp64, express, prioInt, ccInt, attachH, reliabilityInt, sourceZid, sourceEid, sourceSn)) + } + +internal fun queryCallbackOf( + f: (Query) -> Unit +): io.zenoh.jni.query.QueryCallback = + io.zenoh.jni.query.QueryCallback { keH, parameters, payloadH, encH, encId, attachH, acceptsReplies, zq -> + // The decomposed leaves — including the cloned `keH` key-expr handle 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. + f(Query.fromParts(keH, parameters, payloadH, encH, encId, attachH, acceptsReplies, zq)) + } + +internal fun replyCallbackOf( + f: (Reply) -> Unit +): io.zenoh.jni.query.ReplyCallback = + io.zenoh.jni.query.ReplyCallback { zid, eid, isOk, keH, payloadH, encH, encId, kindInt, ntp64, express, prioInt, ccInt, attachH, reliabilityInt, sourceZid, sourceEid, sourceSn, errPayloadH, errEncH, errEncId -> + val replierId = zid?.let { EntityGlobalId(ZenohId(it), eid.toUInt()) } + f( + if (isOk) { + Reply.Success( + replierId, + Sample.fromParts(keH!!, payloadH!!, encH!!, encId!!, kindInt!!, ntp64, express!!, prioInt!!, ccInt!!, attachH, reliabilityInt!!, sourceZid, sourceEid!!, sourceSn!!) + ) + } else { + Reply.Error( + replierId, + ZBytes.fromHandle(errPayloadH!!), + errEncH?.let { Encoding.fromParts(it, errEncId!!) } ?: Encoding.defaultEncoding() + ) + } + ) + } + +internal fun helloCallbackOf( + f: (Hello) -> Unit +): io.zenoh.jni.scouting.HelloCallback = + io.zenoh.jni.scouting.HelloCallback { whatamiInt, zid, locators -> + f(Hello(WhatAmI.fromJni(io.zenoh.jni.config.WhatAmI.fromInt(whatamiInt)), ZenohId(zid), locators)) + } diff --git a/zenoh-java/src/commonMain/kotlin/io/zenoh/Logger.kt b/zenoh-java/src/commonMain/kotlin/io/zenoh/Logger.kt index eba6681c..9ea81c9f 100644 --- a/zenoh-java/src/commonMain/kotlin/io/zenoh/Logger.kt +++ b/zenoh-java/src/commonMain/kotlin/io/zenoh/Logger.kt @@ -15,6 +15,8 @@ package io.zenoh import io.zenoh.exceptions.ZError +import io.zenoh.exceptions.throwZError0 +import io.zenoh.jni.logger.initAndroidLogs /** Logger class to redirect the Rust logs from Zenoh to the kotlin environment. */ internal class Logger { @@ -23,11 +25,6 @@ internal class Logger { internal const val LOG_ENV: String = "RUST_LOG" - @Throws(ZError::class) - fun start(filter: String) { - startLogsViaJNI(filter) - } - /** * Redirects the rust logs either to logcat for Android systems or to the standard output (for non-android * systems). @@ -35,6 +32,8 @@ internal class Logger { * 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) { + initAndroidLogs(filter, throwZError0) + } } } diff --git a/zenoh-java/src/commonMain/kotlin/io/zenoh/Session.kt b/zenoh-java/src/commonMain/kotlin/io/zenoh/Session.kt index d3b125f9..10473468 100644 --- a/zenoh-java/src/commonMain/kotlin/io/zenoh/Session.kt +++ b/zenoh-java/src/commonMain/kotlin/io/zenoh/Session.kt @@ -15,17 +15,21 @@ package io.zenoh import io.zenoh.annotations.Unstable +import io.zenoh.bytes.Encoding import io.zenoh.bytes.IntoZBytes import io.zenoh.bytes.ZBytes +import io.zenoh.bytes.into import io.zenoh.config.ZenohId import io.zenoh.exceptions.ZError +import io.zenoh.exceptions.throwZError +import io.zenoh.exceptions.throwZError0 import io.zenoh.handlers.BlockingQueueHandler import io.zenoh.handlers.Callback import io.zenoh.handlers.Handler -import io.zenoh.jni.JNISession import io.zenoh.keyexpr.KeyExpr import io.zenoh.liveliness.Liveliness import io.zenoh.pubsub.* +import io.zenoh.qos.QoS import io.zenoh.query.* import io.zenoh.query.Query import io.zenoh.query.Queryable @@ -53,7 +57,7 @@ import java.util.concurrent.LinkedBlockingDeque */ class Session private constructor(private val config: Config) : AutoCloseable { - internal var jniSession: JNISession? = null + internal var zSession: io.zenoh.jni.session.Session? = null // Subscribers and Queryables that keep running despite losing references to them. private var strongDeclarations = mutableListOf() @@ -98,8 +102,8 @@ class Session private constructor(private val config: Config) : AutoCloseable { true } - jniSession?.close() - jniSession = null + zSession?.close() + zSession = null } protected fun finalize() { @@ -382,11 +386,12 @@ class Session private constructor(private val config: Config) : AutoCloseable { */ @Throws(ZError::class) fun declareKeyExpr(keyExpr: String): KeyExpr { - return jniSession?.run { - val keyexpr = declareKeyExpr(keyExpr) - strongDeclarations.add(keyexpr) - keyexpr - } ?: throw sessionClosedException + val zSession = zSession ?: throw sessionClosedException + val keyexpr = run { + KeyExpr(zSession.declareKeyexpr(keyExpr, throwZError)) + } + strongDeclarations.add(keyexpr) + return keyexpr } /** @@ -400,9 +405,14 @@ class Session private constructor(private val config: Config) : AutoCloseable { */ @Throws(ZError::class) fun undeclare(keyExpr: KeyExpr) { - return jniSession?.run { - undeclareKeyExpr(keyExpr) - } ?: throw (sessionClosedException) + val zSession = zSession ?: throw sessionClosedException + val handle = keyExpr.flat + if (handle.isClosed()) { + throw ZError("Attempting to undeclare a non declared key expression.") + } + run { + zSession.undeclareKeyexpr(handle, throwZError) + } } /** @@ -556,7 +566,7 @@ class Session private constructor(private val config: Config) : AutoCloseable { /** Returns if session is open or has been closed. */ fun isClosed(): Boolean { - return jniSession == null + return zSession == null } /** @@ -575,66 +585,132 @@ class Session private constructor(private val config: Config) : AutoCloseable { @Throws(ZError::class) internal fun resolvePublisher(keyExpr: KeyExpr, options: PublisherOptions): Publisher { - return jniSession?.run { - val publisher = declarePublisher(keyExpr, options) - weakDeclarations.add(WeakReference(publisher)) - publisher - } ?: throw (sessionClosedException) + val zSession = zSession ?: throw sessionClosedException + val publisher = run { + val zPublisher = zSession.declarePublisher( + keyExpr.cloneFlat(), + options.congestionControl.jni, + options.priority.jni, + options.express, + options.reliability.jni, + throwZError + ) + Publisher( + keyExpr, + options.congestionControl, + options.priority, + options.encoding, + zPublisher, + ) + } + weakDeclarations.add(WeakReference(publisher)) + return publisher } @Throws(ZError::class) internal fun resolveSubscriberWithHandler( keyExpr: KeyExpr, handler: Handler ): HandlerSubscriber { - return jniSession?.run { - val subscriber = declareSubscriberWithHandler(keyExpr, handler) - strongDeclarations.add(subscriber) - subscriber - } ?: throw (sessionClosedException) + val zSession = zSession ?: throw sessionClosedException + val subscriber = run { + val zSubscriber = zSession.declareSubscriber( + keyExpr.cloneFlat(), + sampleCallbackOf { handler.handle(it) }, + { handler.onClose() }, + throwZError + ) + HandlerSubscriber(keyExpr, zSubscriber, handler.receiver()) + } + strongDeclarations.add(subscriber) + return subscriber } @Throws(ZError::class) internal fun resolveSubscriberWithCallback( keyExpr: KeyExpr, callback: Callback ): CallbackSubscriber { - return jniSession?.run { - val subscriber = declareSubscriberWithCallback(keyExpr, callback) - strongDeclarations.add(subscriber) - subscriber - } ?: throw (sessionClosedException) + val zSession = zSession ?: throw sessionClosedException + val subscriber = run { + val zSubscriber = zSession.declareSubscriber( + keyExpr.cloneFlat(), + sampleCallbackOf { callback.run(it) }, + { }, + throwZError + ) + CallbackSubscriber(keyExpr, zSubscriber) + } + strongDeclarations.add(subscriber) + return subscriber } @Throws(ZError::class) internal fun resolveQueryableWithHandler( keyExpr: KeyExpr, handler: Handler, options: QueryableOptions ): HandlerQueryable { - return jniSession?.run { - val queryable = declareQueryableWithHandler(keyExpr, handler, options) - strongDeclarations.add(queryable) - queryable - } ?: throw (sessionClosedException) + val zSession = zSession ?: throw sessionClosedException + val queryable = run { + val zQueryable = zSession.declareQueryable( + keyExpr.cloneFlat(), + options.complete, + queryCallbackOf { handler.handle(it) }, + { handler.onClose() }, + throwZError + ) + HandlerQueryable(keyExpr, zQueryable, handler.receiver()) + } + strongDeclarations.add(queryable) + return queryable } @Throws(ZError::class) internal fun resolveQueryableWithCallback( keyExpr: KeyExpr, callback: Callback, options: QueryableOptions ): CallbackQueryable { - return jniSession?.run { - val queryable = declareQueryableWithCallback(keyExpr, callback, options) - strongDeclarations.add(queryable) - queryable - } ?: throw (sessionClosedException) + val zSession = zSession ?: throw sessionClosedException + val queryable = run { + val zQueryable = zSession.declareQueryable( + keyExpr.cloneFlat(), + options.complete, + queryCallbackOf { callback.run(it) }, + { }, + throwZError + ) + CallbackQueryable(keyExpr, zQueryable) + } + strongDeclarations.add(queryable) + return queryable } + @OptIn(Unstable::class) private fun resolveQuerier( keyExpr: KeyExpr, options: QuerierOptions ): Querier { - return jniSession?.run { - val querier = declareQuerier(keyExpr, options) - weakDeclarations.add(WeakReference(querier)) - querier - } ?: throw sessionClosedException + val zSession = zSession ?: throw sessionClosedException + val querier = run { + val zQuerier = zSession.declareQuerier( + keyExpr.cloneFlat(), + options.target.toFlat(), + options.consolidationMode.toFlat(), + options.congestionControl.jni, + options.priority.jni, + options.express, + options.timeout.toMillis(), + options.acceptReplies.toFlat(), + throwZError + ) + Querier( + keyExpr, + QoS( + congestionControl = options.congestionControl, + priority = options.priority, + express = options.express + ), + zQuerier + ) + } + weakDeclarations.add(WeakReference(querier)) + return querier } @Throws(ZError::class) @@ -643,11 +719,31 @@ class Session private constructor(private val config: Config) : AutoCloseable { handler: Handler, options: GetOptions ): R { - return jniSession?.performGetWithHandler( - selector, - handler, - options - ) ?: throw sessionClosedException + val zSession = zSession ?: throw sessionClosedException + return run { + val sel = selector.into() + val enc = options.encoding ?: Encoding.defaultEncoding() + zSession.get( + sel.keyExpr.flat, + sel.parameters?.toString(), + options.timeout.toMillis(), + options.target.toFlat(), + options.consolidation.toFlat(), + options.acceptReplies.toFlat(), + options.qos.congestionControl.jni, + options.qos.priority.jni, + options.qos.express, + options.payload?.into()?.bytes, + true, + enc.idForWire(), + enc.schemaForWire(), + options.attachment?.into()?.bytes, + replyCallbackOf { handler.handle(it) }, + { handler.onClose() }, + throwZError + ) + handler.receiver() + } } @Throws(ZError::class) @@ -656,42 +752,96 @@ class Session private constructor(private val config: Config) : AutoCloseable { callback: Callback, options: GetOptions ) { - return jniSession?.performGetWithCallback( - selector, - callback, - options - ) ?: throw sessionClosedException + val zSession = zSession ?: throw sessionClosedException + run { + val sel = selector.into() + val enc = options.encoding ?: Encoding.defaultEncoding() + zSession.get( + sel.keyExpr.flat, + sel.parameters?.toString(), + options.timeout.toMillis(), + options.target.toFlat(), + options.consolidation.toFlat(), + options.acceptReplies.toFlat(), + options.qos.congestionControl.jni, + options.qos.priority.jni, + options.qos.express, + options.payload?.into()?.bytes, + true, + enc.idForWire(), + enc.schemaForWire(), + options.attachment?.into()?.bytes, + replyCallbackOf { callback.run(it) }, + { }, + throwZError + ) + } } @Throws(ZError::class) internal fun resolvePut(keyExpr: KeyExpr, payload: IntoZBytes, putOptions: PutOptions) { - jniSession?.run { performPut(keyExpr, payload, putOptions) } + val zSession = zSession ?: return + run { + val enc = putOptions.encoding ?: Encoding.defaultEncoding() + zSession.put( + keyExpr.flat, + payload.into().bytes, + true, + enc.idForWire(), + enc.schemaForWire(), + putOptions.congestionControl.jni, + putOptions.priority.jni, + putOptions.express, + putOptions.attachment?.into()?.bytes, + putOptions.reliability.jni, + throwZError + ) + } } @Throws(ZError::class) internal fun resolveDelete(keyExpr: KeyExpr, deleteOptions: DeleteOptions) { - jniSession?.run { performDelete(keyExpr, deleteOptions) } + val zSession = zSession ?: return + run { + zSession.delete( + keyExpr.flat, + deleteOptions.congestionControl.jni, + deleteOptions.priority.jni, + deleteOptions.express, + deleteOptions.attachment?.into()?.bytes, + deleteOptions.reliability.jni, + throwZError + ) + } } @Throws(ZError::class) internal fun zid(): ZenohId { - return jniSession?.zid() ?: throw sessionClosedException + val zSession = zSession ?: throw sessionClosedException + return ZenohId(zSession.getZid(throwZError0)) } @Throws(ZError::class) internal fun getPeersId(): List { - return jniSession?.peersZid() ?: throw sessionClosedException + val zSession = zSession ?: throw sessionClosedException + // `io.zenoh.jni.config.ZenohId` is a value class, so the native fn + // returns `List` directly; wrap each as ZenohId. + return zSession.getPeersZid(throwZError0).map { ZenohId(it) } } @Throws(ZError::class) internal fun getRoutersId(): List { - return jniSession?.routersZid() ?: throw sessionClosedException + val zSession = zSession ?: throw sessionClosedException + return zSession.getRoutersZid(throwZError0).map { ZenohId(it) } } - /** Launches the session through the jni session, returning the [Session] on success. */ + /** Launches the session, returning the [Session] on success. */ @Throws(ZError::class) private fun launch(): Session { - this.jniSession = JNISession.open(config) + this.zSession = io.zenoh.jni.session.Session.open( + config.zConfig.newClone(throwZError0), + throwZError, + ) return this } } diff --git a/zenoh-java/src/commonMain/kotlin/io/zenoh/Zenoh.kt b/zenoh-java/src/commonMain/kotlin/io/zenoh/Zenoh.kt index 7a5931f8..c9ae004e 100644 --- a/zenoh-java/src/commonMain/kotlin/io/zenoh/Zenoh.kt +++ b/zenoh-java/src/commonMain/kotlin/io/zenoh/Zenoh.kt @@ -19,8 +19,11 @@ import io.zenoh.exceptions.ZError import io.zenoh.handlers.BlockingQueueHandler import io.zenoh.handlers.Callback import io.zenoh.handlers.Handler -import io.zenoh.jni.JNIScout -import io.zenoh.scouting.* +import io.zenoh.scouting.CallbackScout +import io.zenoh.scouting.HandlerScout +import io.zenoh.scouting.Hello +import io.zenoh.scouting.Scout +import io.zenoh.scouting.ScoutOptions import java.util.* import java.util.concurrent.BlockingQueue import java.util.concurrent.LinkedBlockingDeque @@ -53,7 +56,7 @@ object Zenoh { @Throws(ZError::class) fun scout(scoutOptions: ScoutOptions = ScoutOptions()): HandlerScout>> { val handler = BlockingQueueHandler(LinkedBlockingDeque>()) - return JNIScout.scoutWithHandler( + return Scout.scoutWithHandler( scoutOptions.whatAmI, handler::handle, fun() { handler.onClose() }, receiver = handler.receiver(), config = scoutOptions.config ) @@ -74,7 +77,7 @@ object Zenoh { @JvmStatic @Throws(ZError::class) fun scout(handler: Handler, scoutOptions: ScoutOptions = ScoutOptions()): HandlerScout { - return JNIScout.scoutWithHandler( + return Scout.scoutWithHandler( scoutOptions.whatAmI, handler::handle, fun() { handler.onClose() }, receiver = handler.receiver(), config = scoutOptions.config ) @@ -94,7 +97,7 @@ object Zenoh { @JvmStatic @Throws(ZError::class) fun scout(callback: Callback, scoutOptions: ScoutOptions = ScoutOptions()): CallbackScout { - return JNIScout.scoutWithCallback( + return Scout.scoutWithCallback( scoutOptions.whatAmI, callback, config = scoutOptions.config ) } @@ -113,7 +116,6 @@ object Zenoh { fun tryInitLogFromEnv() { val logEnv = System.getenv(LOG_ENV) if (logEnv != null) { - ZenohLoad Logger.start(logEnv) } } @@ -131,14 +133,7 @@ object Zenoh { @JvmStatic @Throws(ZError::class) fun initLogFromEnvOr(fallbackFilter: String) { - ZenohLoad val logLevelProp = System.getenv(LOG_ENV) logLevelProp?.let { Logger.start(it) } ?: Logger.start(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-java/src/commonMain/kotlin/io/zenoh/bytes/Encoding.kt b/zenoh-java/src/commonMain/kotlin/io/zenoh/bytes/Encoding.kt index a4f77815..affd91ed 100644 --- a/zenoh-java/src/commonMain/kotlin/io/zenoh/bytes/Encoding.kt +++ b/zenoh-java/src/commonMain/kotlin/io/zenoh/bytes/Encoding.kt @@ -14,6 +14,115 @@ package io.zenoh.bytes +import io.zenoh.exceptions.throwZError0 +import io.zenoh.jni.bytes.ENCODING_APPLICATION_CBOR +import io.zenoh.jni.bytes.ENCODING_APPLICATION_CBOR_ID +import io.zenoh.jni.bytes.ENCODING_APPLICATION_CDR +import io.zenoh.jni.bytes.ENCODING_APPLICATION_CDR_ID +import io.zenoh.jni.bytes.ENCODING_APPLICATION_COAP_PAYLOAD +import io.zenoh.jni.bytes.ENCODING_APPLICATION_COAP_PAYLOAD_ID +import io.zenoh.jni.bytes.ENCODING_APPLICATION_JAVA_SERIALIZED_OBJECT +import io.zenoh.jni.bytes.ENCODING_APPLICATION_JAVA_SERIALIZED_OBJECT_ID +import io.zenoh.jni.bytes.ENCODING_APPLICATION_JSON +import io.zenoh.jni.bytes.ENCODING_APPLICATION_JSONPATH +import io.zenoh.jni.bytes.ENCODING_APPLICATION_JSONPATH_ID +import io.zenoh.jni.bytes.ENCODING_APPLICATION_JSON_ID +import io.zenoh.jni.bytes.ENCODING_APPLICATION_JSON_PATCH_JSON +import io.zenoh.jni.bytes.ENCODING_APPLICATION_JSON_PATCH_JSON_ID +import io.zenoh.jni.bytes.ENCODING_APPLICATION_JSON_SEQ +import io.zenoh.jni.bytes.ENCODING_APPLICATION_JSON_SEQ_ID +import io.zenoh.jni.bytes.ENCODING_APPLICATION_JWT +import io.zenoh.jni.bytes.ENCODING_APPLICATION_JWT_ID +import io.zenoh.jni.bytes.ENCODING_APPLICATION_MP4 +import io.zenoh.jni.bytes.ENCODING_APPLICATION_MP4_ID +import io.zenoh.jni.bytes.ENCODING_APPLICATION_OCTET_STREAM +import io.zenoh.jni.bytes.ENCODING_APPLICATION_OCTET_STREAM_ID +import io.zenoh.jni.bytes.ENCODING_APPLICATION_OPENMETRICS_TEXT +import io.zenoh.jni.bytes.ENCODING_APPLICATION_OPENMETRICS_TEXT_ID +import io.zenoh.jni.bytes.ENCODING_APPLICATION_PROTOBUF +import io.zenoh.jni.bytes.ENCODING_APPLICATION_PROTOBUF_ID +import io.zenoh.jni.bytes.ENCODING_APPLICATION_PYTHON_SERIALIZED_OBJECT +import io.zenoh.jni.bytes.ENCODING_APPLICATION_PYTHON_SERIALIZED_OBJECT_ID +import io.zenoh.jni.bytes.ENCODING_APPLICATION_SOAP_XML +import io.zenoh.jni.bytes.ENCODING_APPLICATION_SOAP_XML_ID +import io.zenoh.jni.bytes.ENCODING_APPLICATION_SQL +import io.zenoh.jni.bytes.ENCODING_APPLICATION_SQL_ID +import io.zenoh.jni.bytes.ENCODING_APPLICATION_XML +import io.zenoh.jni.bytes.ENCODING_APPLICATION_XML_ID +import io.zenoh.jni.bytes.ENCODING_APPLICATION_X_WWW_FORM_URLENCODED +import io.zenoh.jni.bytes.ENCODING_APPLICATION_X_WWW_FORM_URLENCODED_ID +import io.zenoh.jni.bytes.ENCODING_APPLICATION_YAML +import io.zenoh.jni.bytes.ENCODING_APPLICATION_YAML_ID +import io.zenoh.jni.bytes.ENCODING_APPLICATION_YANG +import io.zenoh.jni.bytes.ENCODING_APPLICATION_YANG_ID +import io.zenoh.jni.bytes.ENCODING_AUDIO_AAC +import io.zenoh.jni.bytes.ENCODING_AUDIO_AAC_ID +import io.zenoh.jni.bytes.ENCODING_AUDIO_FLAC +import io.zenoh.jni.bytes.ENCODING_AUDIO_FLAC_ID +import io.zenoh.jni.bytes.ENCODING_AUDIO_MP4 +import io.zenoh.jni.bytes.ENCODING_AUDIO_MP4_ID +import io.zenoh.jni.bytes.ENCODING_AUDIO_OGG +import io.zenoh.jni.bytes.ENCODING_AUDIO_OGG_ID +import io.zenoh.jni.bytes.ENCODING_AUDIO_VORBIS +import io.zenoh.jni.bytes.ENCODING_AUDIO_VORBIS_ID +import io.zenoh.jni.bytes.ENCODING_IMAGE_BMP +import io.zenoh.jni.bytes.ENCODING_IMAGE_BMP_ID +import io.zenoh.jni.bytes.ENCODING_IMAGE_GIF +import io.zenoh.jni.bytes.ENCODING_IMAGE_GIF_ID +import io.zenoh.jni.bytes.ENCODING_IMAGE_JPEG +import io.zenoh.jni.bytes.ENCODING_IMAGE_JPEG_ID +import io.zenoh.jni.bytes.ENCODING_IMAGE_PNG +import io.zenoh.jni.bytes.ENCODING_IMAGE_PNG_ID +import io.zenoh.jni.bytes.ENCODING_IMAGE_WEBP +import io.zenoh.jni.bytes.ENCODING_IMAGE_WEBP_ID +import io.zenoh.jni.bytes.ENCODING_TEXT_CSS +import io.zenoh.jni.bytes.ENCODING_TEXT_CSS_ID +import io.zenoh.jni.bytes.ENCODING_TEXT_CSV +import io.zenoh.jni.bytes.ENCODING_TEXT_CSV_ID +import io.zenoh.jni.bytes.ENCODING_TEXT_HTML +import io.zenoh.jni.bytes.ENCODING_TEXT_HTML_ID +import io.zenoh.jni.bytes.ENCODING_TEXT_JAVASCRIPT +import io.zenoh.jni.bytes.ENCODING_TEXT_JAVASCRIPT_ID +import io.zenoh.jni.bytes.ENCODING_TEXT_JSON +import io.zenoh.jni.bytes.ENCODING_TEXT_JSON5 +import io.zenoh.jni.bytes.ENCODING_TEXT_JSON5_ID +import io.zenoh.jni.bytes.ENCODING_TEXT_JSON_ID +import io.zenoh.jni.bytes.ENCODING_TEXT_MARKDOWN +import io.zenoh.jni.bytes.ENCODING_TEXT_MARKDOWN_ID +import io.zenoh.jni.bytes.ENCODING_TEXT_PLAIN +import io.zenoh.jni.bytes.ENCODING_TEXT_PLAIN_ID +import io.zenoh.jni.bytes.ENCODING_TEXT_XML +import io.zenoh.jni.bytes.ENCODING_TEXT_XML_ID +import io.zenoh.jni.bytes.ENCODING_TEXT_YAML +import io.zenoh.jni.bytes.ENCODING_TEXT_YAML_ID +import io.zenoh.jni.bytes.ENCODING_VIDEO_H261 +import io.zenoh.jni.bytes.ENCODING_VIDEO_H261_ID +import io.zenoh.jni.bytes.ENCODING_VIDEO_H263 +import io.zenoh.jni.bytes.ENCODING_VIDEO_H263_ID +import io.zenoh.jni.bytes.ENCODING_VIDEO_H264 +import io.zenoh.jni.bytes.ENCODING_VIDEO_H264_ID +import io.zenoh.jni.bytes.ENCODING_VIDEO_H265 +import io.zenoh.jni.bytes.ENCODING_VIDEO_H265_ID +import io.zenoh.jni.bytes.ENCODING_VIDEO_H266 +import io.zenoh.jni.bytes.ENCODING_VIDEO_H266_ID +import io.zenoh.jni.bytes.ENCODING_VIDEO_MP4 +import io.zenoh.jni.bytes.ENCODING_VIDEO_MP4_ID +import io.zenoh.jni.bytes.ENCODING_VIDEO_OGG +import io.zenoh.jni.bytes.ENCODING_VIDEO_OGG_ID +import io.zenoh.jni.bytes.ENCODING_VIDEO_RAW +import io.zenoh.jni.bytes.ENCODING_VIDEO_RAW_ID +import io.zenoh.jni.bytes.ENCODING_VIDEO_VP8 +import io.zenoh.jni.bytes.ENCODING_VIDEO_VP8_ID +import io.zenoh.jni.bytes.ENCODING_VIDEO_VP9 +import io.zenoh.jni.bytes.ENCODING_VIDEO_VP9_ID +import io.zenoh.jni.bytes.ENCODING_ZENOH_BYTES +import io.zenoh.jni.bytes.ENCODING_ZENOH_BYTES_ID +import io.zenoh.jni.bytes.ENCODING_ZENOH_SERIALIZED +import io.zenoh.jni.bytes.ENCODING_ZENOH_SERIALIZED_ID +import io.zenoh.jni.bytes.ENCODING_ZENOH_STRING +import io.zenoh.jni.bytes.ENCODING_ZENOH_STRING_ID +import io.zenoh.jni.bytes.Encoding as JniEncoding + /** * Default encoding values used by Zenoh. * @@ -23,480 +132,222 @@ package io.zenoh.bytes * It can be seen as some optional metadata that is carried over by Zenoh in such a way the application may perform different operations depending on the encoding value. * * A set of associated constants are provided to cover the most common encodings for user convenience. - * This is particularly useful in helping Zenoh to perform additional network optimizations. + * + * Internally the encoding is kept as its canonical textual form ([repr], e.g. + * `"text/plain"` or `"text/plain;utf-8"`); a native `ZEncoding` handle is built + * on demand for each native crossing (the raw `z_*` API takes the encoding by + * reference, so the transient handle is closed by the caller after the call). */ class Encoding private constructor( - internal val id: Int, - internal val schema: String? = null, - private val description: String? = null + private var reprLazy: String?, + internal val id: Int?, + private val handle: JniEncoding?, ) { - internal constructor(id: Int, schema: String? = null) : this(id, schema, null) - - companion object { - - /** - * Just some bytes. - * - * Constant alias for string: `"zenoh/bytes"`. - * - * Usually used for types: `ByteArray`, `List`. - */ - @JvmField - val ZENOH_BYTES = Encoding(0, description = "zenoh/bytes") - - /** - * A UTF-8 string. - * - * Constant alias for string: `"zenoh/string"`. - * - * Usually used for type: `String`. - */ - @JvmField - val ZENOH_STRING = Encoding(1, description = "zenoh/string") - - /** - * Zenoh serialized data. - * - * Constant alias for string: `"zenoh/serialized"`. - */ - @JvmField - val ZENOH_SERIALIZED = Encoding(2, description = "zenoh/serialized") - - /** - * An application-specific stream of bytes. - * - * Constant alias for string: `"application/octet-stream"`. - */ - @JvmField - val APPLICATION_OCTET_STREAM = Encoding(3, description = "application/octet-stream") - - /** - * A textual file. - * - * Constant alias for string: `"text/plain"`. - */ - @JvmField - val TEXT_PLAIN = Encoding(4, description = "text/plain") - - /** - * JSON data intended to be consumed by an application. - * - * Constant alias for string: `"application/json"`. - */ - @JvmField - val APPLICATION_JSON = Encoding(5, description = "application/json") - - /** - * JSON data intended to be human readable. - * - * Constant alias for string: `"text/json"`. - */ - @JvmField - val TEXT_JSON = Encoding(6, description = "text/json") - - /** - * A Common Data Representation (CDR)-encoded data. - * - * Constant alias for string: `"application/cdr"`. - */ - @JvmField - val APPLICATION_CDR = Encoding(7, description = "application/cdr") - - /** - * A Concise Binary Object Representation (CBOR)-encoded data. - * - * Constant alias for string: `"application/cbor"`. - */ - @JvmField - val APPLICATION_CBOR = Encoding(8, description = "application/cbor") - - /** - * YAML data intended to be consumed by an application. - * - * Constant alias for string: `"application/yaml"`. - */ - @JvmField - val APPLICATION_YAML = Encoding(9, description = "application/yaml") - - /** - * YAML data intended to be human readable. - * - * Constant alias for string: `"text/yaml"`. - */ - @JvmField - val TEXT_YAML = Encoding(10, description = "text/yaml") - - /** - * JSON5 encoded data that are human readable. - * - * Constant alias for string: `"text/json5"`. - */ - @JvmField - val TEXT_JSON5 = Encoding(11, description = "text/json5") - - /** - * A Python object serialized using [pickle](https://docs.python.org/3/library/pickle.html). - * - * Constant alias for string: `"application/python-serialized-object"`. - */ - @JvmField - val APPLICATION_PYTHON_SERIALIZED_OBJECT = - Encoding(12, description = "application/python-serialized-object") - - /** - * An application-specific protobuf-encoded data. - * - * Constant alias for string: `"application/protobuf"`. - */ - @JvmField - val APPLICATION_PROTOBUF = Encoding(13, description = "application/protobuf") - - /** - * A Java serialized object. - * - * Constant alias for string: `"application/java-serialized-object"`. - */ - @JvmField - val APPLICATION_JAVA_SERIALIZED_OBJECT = - Encoding(14, description = "application/java-serialized-object") - - /** - * OpenMetrics data, commonly used by [Prometheus](https://prometheus.io/). - * - * Constant alias for string: `"application/openmetrics-text"`. - */ - @JvmField - val APPLICATION_OPENMETRICS_TEXT = - Encoding(15, description = "application/openmetrics-text") - - /** - * A Portable Network Graphics (PNG) image. - * - * Constant alias for string: `"image/png"`. - */ - @JvmField - val IMAGE_PNG = Encoding(16, description = "image/png") - - /** - * A Joint Photographic Experts Group (JPEG) image. - * - * Constant alias for string: `"image/jpeg"`. - */ - @JvmField - val IMAGE_JPEG = Encoding(17, description = "image/jpeg") - - /** - * A Graphics Interchange Format (GIF) image. - * - * Constant alias for string: `"image/gif"`. - */ - @JvmField - val IMAGE_GIF = Encoding(18, description = "image/gif") - - /** - * A BitMap (BMP) image. - * - * Constant alias for string: `"image/bmp"`. - */ - @JvmField - val IMAGE_BMP = Encoding(19, description = "image/bmp") - - /** - * A WebP image. - * - * Constant alias for string: `"image/webp"`. - */ - @JvmField - val IMAGE_WEBP = Encoding(20, description = "image/webp") - - /** - * An XML file intended to be consumed by an application. - * - * Constant alias for string: `"application/xml"`. - */ - @JvmField - val APPLICATION_XML = Encoding(21, description = "application/xml") - - /** - * A list of tuples, each consisting of a name and a value. - * - * Constant alias for string: `"application/x-www-form-urlencoded"`. - */ - @JvmField - val APPLICATION_X_WWW_FORM_URLENCODED = - Encoding(22, description = "application/x-www-form-urlencoded") - - /** - * An HTML file. - * - * Constant alias for string: `"text/html"`. - */ - @JvmField - val TEXT_HTML = Encoding(23, description = "text/html") - - /** - * An XML file that is human readable. - * - * Constant alias for string: `"text/xml"`. - */ - @JvmField - val TEXT_XML = Encoding(24, description = "text/xml") - /** - * A CSS file. - * - * Constant alias for string: `"text/css"`. - */ - @JvmField - val TEXT_CSS = Encoding(25, description = "text/css") + internal constructor(repr: String) : this(repr, null, null) - /** - * A JavaScript file. - * - * Constant alias for string: `"text/javascript"`. - */ - @JvmField - val TEXT_JAVASCRIPT = Encoding(26, description = "text/javascript") + private var schemaLazy: String? = null + private var schemaKnown: Boolean = false + private var idCached: Int = id ?: 0 + private var idKnown: Boolean = id != null - /** - * A Markdown file. - * - * Constant alias for string: `"text/markdown"`. - */ - @JvmField - val TEXT_MARKDOWN = Encoding(27, description = "text/markdown") - - /** - * A CSV file. - * - * Constant alias for string: `"text/csv"`. - */ - @JvmField - val TEXT_CSV = Encoding(28, description = "text/csv") - - /** - * An application-specific SQL query. - * - * Constant alias for string: `"application/sql"`. - */ - @JvmField - val APPLICATION_SQL = Encoding(29, description = "application/sql") - - /** - * Constrained Application Protocol (CoAP) data intended for CoAP-to-HTTP and HTTP-to-CoAP proxies. - * - * Constant alias for string: `"application/coap-payload"`. - */ - @JvmField - val APPLICATION_COAP_PAYLOAD = Encoding(30, description = "application/coap-payload") - - /** - * Defines a JSON document structure for expressing a sequence of operations to apply to a JSON document. - * - * Constant alias for string: `"application/json-patch+json"`. - */ - @JvmField - val APPLICATION_JSON_PATCH_JSON = Encoding(31, description = "application/json-patch+json") - - /** - * A JSON text sequence consists of any number of JSON texts, all encoded in UTF-8. - * - * Constant alias for string: `"application/json-seq"`. - */ - @JvmField - val APPLICATION_JSON_SEQ = Encoding(32, description = "application/json-seq") - - /** - * A JSONPath defines a string syntax for selecting and extracting JSON values from within a given JSON value. - * - * Constant alias for string: `"application/jsonpath"`. - */ - @JvmField - val APPLICATION_JSONPATH = Encoding(33, description = "application/jsonpath") - - /** - * A JSON Web Token (JWT). - * - * Constant alias for string: `"application/jwt"`. - */ - @JvmField - val APPLICATION_JWT = Encoding(34, description = "application/jwt") - - /** - * An application-specific MPEG-4 encoded data, either audio or video. - * - * Constant alias for string: `"application/mp4"`. - */ - @JvmField - val APPLICATION_MP4 = Encoding(35, description = "application/mp4") - - /** - * A SOAP 1.2 message serialized as XML 1.0. - * - * Constant alias for string: `"application/soap+xml"`. - */ - @JvmField - val APPLICATION_SOAP_XML = Encoding(36, description = "application/soap+xml") - - /** - * A YANG-encoded data commonly used by the Network Configuration Protocol (NETCONF). - * - * Constant alias for string: `"application/yang"`. - */ - @JvmField - val APPLICATION_YANG = Encoding(37, description = "application/yang") - - /** - * A MPEG-4 Advanced Audio Coding (AAC) media. - * - * Constant alias for string: `"audio/aac"`. - */ - @JvmField - val AUDIO_AAC = Encoding(38, description = "audio/aac") - - /** - * A Free Lossless Audio Codec (FLAC) media. - * - * Constant alias for string: `"audio/flac"`. - */ - @JvmField - val AUDIO_FLAC = Encoding(39, description = "audio/flac") - - /** - * An audio codec defined in MPEG-1, MPEG-2, MPEG-4, or registered at the MP4 registration authority. - * - * Constant alias for string: `"audio/mp4"`. - */ - @JvmField - val AUDIO_MP4 = Encoding(40, description = "audio/mp4") - - /** - * An Ogg-encapsulated audio stream. - * - * Constant alias for string: `"audio/ogg"`. - */ - @JvmField - val AUDIO_OGG = Encoding(41, description = "audio/ogg") - - /** - * A Vorbis-encoded audio stream. - * - * Constant alias for string: `"audio/vorbis"`. - */ - @JvmField - val AUDIO_VORBIS = Encoding(42, description = "audio/vorbis") - - /** - * A h261-encoded video stream. - * - * Constant alias for string: `"video/h261"`. - */ - @JvmField - val VIDEO_H261 = Encoding(43, description = "video/h261") - - /** - * A h263-encoded video stream. - * - * Constant alias for string: `"video/h263"`. - */ - @JvmField - val VIDEO_H263 = Encoding(44, description = "video/h263") - - /** - * A h264-encoded video stream. - * - * Constant alias for string: `"video/h264"`. - */ - @JvmField - val VIDEO_H264 = Encoding(45, description = "video/h264") - - /** - * A h265-encoded video stream. - * - * Constant alias for string: `"video/h265"`. - */ - @JvmField - val VIDEO_H265 = Encoding(46, description = "video/h265") - - /** - * A h266-encoded video stream. - * - * Constant alias for string: `"video/h266"`. - */ - @JvmField - val VIDEO_H266 = Encoding(47, description = "video/h266") - - /** - * A video codec defined in MPEG-1, MPEG-2, MPEG-4, or registered at the MP4 registration authority. - * - * Constant alias for string: `"video/mp4"`. - */ - @JvmField - val VIDEO_MP4 = Encoding(48, description = "video/mp4") - - /** - * An Ogg-encapsulated video stream. - * - * Constant alias for string: `"video/ogg"`. - */ - @JvmField - val VIDEO_OGG = Encoding(49, description = "video/ogg") + /** + * Ensure the lossless decomposed form `(id, schema)` is cached. Zenoh's + * encoding IS `(id, schema)`, so this loses nothing. A handle-backed + * (received) Encoding already knows its [id]; its schema is read LAZILY + * through the handle on first need. A predefined constant is born fully + * decomposed (repr + id from the generated consts, schema known-absent) and + * never enters this path. A repr-primary Encoding built by [from] derives + * BOTH once from a transient handle built off [repr], then frees it — + * caching pure JVM values and retaining no native handle. The cache is + * reused across every native crossing, so a reused + * encoding (the normal case — a publisher publishes one data type) never + * re-parses its string per call. + */ + private fun ensureDecomposed() { + if (idKnown && schemaKnown) return + synchronized(this) { + if (idKnown && schemaKnown) return + if (handle != null) { + if (!schemaKnown) { + schemaLazy = handle.getSchema(throwZError0) + schemaKnown = true + } + } else { + val h = JniEncoding.newFromString(repr, throwZError0) + try { + if (!idKnown) { + idCached = h.getId(throwZError0) + idKnown = true + } + if (!schemaKnown) { + schemaLazy = h.getSchema(throwZError0) + schemaKnown = true + } + } finally { + h.close() + } + } + } + } - /** - * An uncompressed, studio-quality video stream. - * - * Constant alias for string: `"video/raw"`. - */ - @JvmField - val VIDEO_RAW = Encoding(50, description = "video/raw") + /** + * Encoding id for the OUTBOUND native crossing. Cached once (see + * [ensureDecomposed]) and reused across every put/get/reply — the wire + * carries this cheap primitive instead of a freshly parsed string. + */ + internal fun idForWire(): Int { + ensureDecomposed() + return idCached + } - /** - * A VP8-encoded video stream. - * - * Constant alias for string: `"video/vp8"`. - */ - @JvmField - val VIDEO_VP8 = Encoding(51, description = "video/vp8") + /** Optional schema for the OUTBOUND native crossing. Cached once. */ + internal fun schemaForWire(): String? { + ensureDecomposed() + return schemaLazy + } - /** - * A VP9-encoded video stream. - * - * Constant alias for string: `"video/vp9"`. - */ - @JvmField - val VIDEO_VP9 = Encoding(52, description = "video/vp9") + /** + * Canonical display string. A handle-backed (received) Encoding + * materializes it LAZILY on first use (toString/equals) via the native + * accessor — received encodings are usually only forwarded or compared by + * id, so the common path never builds the string (forward-extraction rule). + */ + internal val repr: String + get() = reprLazy ?: synchronized(this) { + reprLazy + ?: handle!!.toStr(throwZError0) + .also { reprLazy = it } + } + companion object { /** - * The default [Encoding] is [ZENOH_BYTES]. - */ - @JvmStatic - fun defaultEncoding() = ZENOH_BYTES + * A predefined constant: its canonical string and wire id come from + * the generated `ENCODING_*` consts (single source of truth in Rust), + * and a predefined encoding never carries a schema — the decomposed + * `(id, schema)` form is fully known up front, so these constants + * never touch native for decomposition (see [ensureDecomposed]). + */ + private fun predefined(repr: String, id: Int): Encoding = + Encoding(repr, id, null).apply { schemaKnown = true } + + @JvmField val ZENOH_BYTES = predefined(ENCODING_ZENOH_BYTES, ENCODING_ZENOH_BYTES_ID) + @JvmField val ZENOH_STRING = predefined(ENCODING_ZENOH_STRING, ENCODING_ZENOH_STRING_ID) + @JvmField val ZENOH_SERIALIZED = predefined(ENCODING_ZENOH_SERIALIZED, ENCODING_ZENOH_SERIALIZED_ID) + @JvmField val APPLICATION_OCTET_STREAM = predefined(ENCODING_APPLICATION_OCTET_STREAM, ENCODING_APPLICATION_OCTET_STREAM_ID) + @JvmField val TEXT_PLAIN = predefined(ENCODING_TEXT_PLAIN, ENCODING_TEXT_PLAIN_ID) + @JvmField val APPLICATION_JSON = predefined(ENCODING_APPLICATION_JSON, ENCODING_APPLICATION_JSON_ID) + @JvmField val TEXT_JSON = predefined(ENCODING_TEXT_JSON, ENCODING_TEXT_JSON_ID) + @JvmField val APPLICATION_CDR = predefined(ENCODING_APPLICATION_CDR, ENCODING_APPLICATION_CDR_ID) + @JvmField val APPLICATION_CBOR = predefined(ENCODING_APPLICATION_CBOR, ENCODING_APPLICATION_CBOR_ID) + @JvmField val APPLICATION_YAML = predefined(ENCODING_APPLICATION_YAML, ENCODING_APPLICATION_YAML_ID) + @JvmField val TEXT_YAML = predefined(ENCODING_TEXT_YAML, ENCODING_TEXT_YAML_ID) + @JvmField val TEXT_JSON5 = predefined(ENCODING_TEXT_JSON5, ENCODING_TEXT_JSON5_ID) + @JvmField val APPLICATION_PYTHON_SERIALIZED_OBJECT = predefined(ENCODING_APPLICATION_PYTHON_SERIALIZED_OBJECT, ENCODING_APPLICATION_PYTHON_SERIALIZED_OBJECT_ID) + @JvmField val APPLICATION_PROTOBUF = predefined(ENCODING_APPLICATION_PROTOBUF, ENCODING_APPLICATION_PROTOBUF_ID) + @JvmField val APPLICATION_JAVA_SERIALIZED_OBJECT = predefined(ENCODING_APPLICATION_JAVA_SERIALIZED_OBJECT, ENCODING_APPLICATION_JAVA_SERIALIZED_OBJECT_ID) + @JvmField val APPLICATION_OPENMETRICS_TEXT = predefined(ENCODING_APPLICATION_OPENMETRICS_TEXT, ENCODING_APPLICATION_OPENMETRICS_TEXT_ID) + @JvmField val IMAGE_PNG = predefined(ENCODING_IMAGE_PNG, ENCODING_IMAGE_PNG_ID) + @JvmField val IMAGE_JPEG = predefined(ENCODING_IMAGE_JPEG, ENCODING_IMAGE_JPEG_ID) + @JvmField val IMAGE_GIF = predefined(ENCODING_IMAGE_GIF, ENCODING_IMAGE_GIF_ID) + @JvmField val IMAGE_BMP = predefined(ENCODING_IMAGE_BMP, ENCODING_IMAGE_BMP_ID) + @JvmField val IMAGE_WEBP = predefined(ENCODING_IMAGE_WEBP, ENCODING_IMAGE_WEBP_ID) + @JvmField val APPLICATION_XML = predefined(ENCODING_APPLICATION_XML, ENCODING_APPLICATION_XML_ID) + @JvmField val APPLICATION_X_WWW_FORM_URLENCODED = predefined(ENCODING_APPLICATION_X_WWW_FORM_URLENCODED, ENCODING_APPLICATION_X_WWW_FORM_URLENCODED_ID) + @JvmField val TEXT_HTML = predefined(ENCODING_TEXT_HTML, ENCODING_TEXT_HTML_ID) + @JvmField val TEXT_XML = predefined(ENCODING_TEXT_XML, ENCODING_TEXT_XML_ID) + @JvmField val TEXT_CSS = predefined(ENCODING_TEXT_CSS, ENCODING_TEXT_CSS_ID) + @JvmField val TEXT_JAVASCRIPT = predefined(ENCODING_TEXT_JAVASCRIPT, ENCODING_TEXT_JAVASCRIPT_ID) + @JvmField val TEXT_MARKDOWN = predefined(ENCODING_TEXT_MARKDOWN, ENCODING_TEXT_MARKDOWN_ID) + @JvmField val TEXT_CSV = predefined(ENCODING_TEXT_CSV, ENCODING_TEXT_CSV_ID) + @JvmField val APPLICATION_SQL = predefined(ENCODING_APPLICATION_SQL, ENCODING_APPLICATION_SQL_ID) + @JvmField val APPLICATION_COAP_PAYLOAD = predefined(ENCODING_APPLICATION_COAP_PAYLOAD, ENCODING_APPLICATION_COAP_PAYLOAD_ID) + @JvmField val APPLICATION_JSON_PATCH_JSON = predefined(ENCODING_APPLICATION_JSON_PATCH_JSON, ENCODING_APPLICATION_JSON_PATCH_JSON_ID) + @JvmField val APPLICATION_JSON_SEQ = predefined(ENCODING_APPLICATION_JSON_SEQ, ENCODING_APPLICATION_JSON_SEQ_ID) + @JvmField val APPLICATION_JSONPATH = predefined(ENCODING_APPLICATION_JSONPATH, ENCODING_APPLICATION_JSONPATH_ID) + @JvmField val APPLICATION_JWT = predefined(ENCODING_APPLICATION_JWT, ENCODING_APPLICATION_JWT_ID) + @JvmField val APPLICATION_MP4 = predefined(ENCODING_APPLICATION_MP4, ENCODING_APPLICATION_MP4_ID) + @JvmField val APPLICATION_SOAP_XML = predefined(ENCODING_APPLICATION_SOAP_XML, ENCODING_APPLICATION_SOAP_XML_ID) + @JvmField val APPLICATION_YANG = predefined(ENCODING_APPLICATION_YANG, ENCODING_APPLICATION_YANG_ID) + @JvmField val AUDIO_AAC = predefined(ENCODING_AUDIO_AAC, ENCODING_AUDIO_AAC_ID) + @JvmField val AUDIO_FLAC = predefined(ENCODING_AUDIO_FLAC, ENCODING_AUDIO_FLAC_ID) + @JvmField val AUDIO_MP4 = predefined(ENCODING_AUDIO_MP4, ENCODING_AUDIO_MP4_ID) + @JvmField val AUDIO_OGG = predefined(ENCODING_AUDIO_OGG, ENCODING_AUDIO_OGG_ID) + @JvmField val AUDIO_VORBIS = predefined(ENCODING_AUDIO_VORBIS, ENCODING_AUDIO_VORBIS_ID) + @JvmField val VIDEO_H261 = predefined(ENCODING_VIDEO_H261, ENCODING_VIDEO_H261_ID) + @JvmField val VIDEO_H263 = predefined(ENCODING_VIDEO_H263, ENCODING_VIDEO_H263_ID) + @JvmField val VIDEO_H264 = predefined(ENCODING_VIDEO_H264, ENCODING_VIDEO_H264_ID) + @JvmField val VIDEO_H265 = predefined(ENCODING_VIDEO_H265, ENCODING_VIDEO_H265_ID) + @JvmField val VIDEO_H266 = predefined(ENCODING_VIDEO_H266, ENCODING_VIDEO_H266_ID) + @JvmField val VIDEO_MP4 = predefined(ENCODING_VIDEO_MP4, ENCODING_VIDEO_MP4_ID) + @JvmField val VIDEO_OGG = predefined(ENCODING_VIDEO_OGG, ENCODING_VIDEO_OGG_ID) + @JvmField val VIDEO_RAW = predefined(ENCODING_VIDEO_RAW, ENCODING_VIDEO_RAW_ID) + @JvmField val VIDEO_VP8 = predefined(ENCODING_VIDEO_VP8, ENCODING_VIDEO_VP8_ID) + @JvmField val VIDEO_VP9 = predefined(ENCODING_VIDEO_VP9, ENCODING_VIDEO_VP9_ID) + + /** The default [Encoding] is [ZENOH_BYTES]. */ + @JvmStatic fun defaultEncoding() = ZENOH_BYTES + + /** + * Parse a textual encoding (e.g. `"text/plain"`, `"text/plain;utf-8"`, + * `"my_encoding"`). Well-known names resolve to their canonical id; + * everything else is preserved as a custom encoding. + */ + @JvmStatic fun from(s: String): Encoding = Encoding(s) + + /** + * Decodes a native `ZEncoding` handle into a value [Encoding] and frees + * the handle. Used when an accessor / callback hands back an encoding. + */ + internal fun fromHandle(handle: JniEncoding): Encoding { + try { + return Encoding(handle.toStr(throwZError0)) + } finally { + handle.close() + } + } + + /** Wrap the decomposed `(handle, id)` leaves. Schema and the + * canonical string stay lazy through the handle. */ + internal fun fromParts(encH: JniEncoding, id: Int): Encoding = + Encoding(null, id, encH) } + /** + * Builds a fresh native `ZEncoding` handle from [repr]. The raw `z_*` + * encoding parameters take it **by reference** (not consumed), so the + * caller MUST close the returned handle after the native call. + */ + internal fun toZEncoding(): JniEncoding = + if (handle != null) { + handle.newClone(throwZError0) + } else { + JniEncoding.newFromString(repr, throwZError0) + } + /** * Set a schema to this encoding. Zenoh does not define what a schema is and its semantics is left to the implementer. * E.g. a common schema for `text/plain` encoding is `utf-8`. */ fun withSchema(schema: String): Encoding { - return Encoding(this.id, schema, this.description) + // `withSchema` takes the base encoding flattened to `(id, schema)`; this + // Encoding already exposes that decomposition lazily. + val withSchema = JniEncoding.newWithSchema(idForWire(), schemaForWire(), schema, throwZError0) + try { + return Encoding(withSchema.toStr(throwZError0)) + } finally { + withSchema.close() + } } - override fun toString(): String { - val base = description ?: "unknown(${this.id})" - val schemaInfo = schema?.let { ";$it" } ?: "" - return "$base$schemaInfo" - } + override fun toString(): String = repr override fun equals(other: Any?): Boolean { if (this === other) return true if (javaClass != other?.javaClass) return false - other as Encoding - - return id == other.id && schema == other.schema + return repr == other.repr } - override fun hashCode(): Int { - return id.hashCode() - } + override fun hashCode(): Int = repr.hashCode() } diff --git a/zenoh-java/src/commonMain/kotlin/io/zenoh/bytes/ZBytes.kt b/zenoh-java/src/commonMain/kotlin/io/zenoh/bytes/ZBytes.kt index 1fd22d7c..9b91cb24 100644 --- a/zenoh-java/src/commonMain/kotlin/io/zenoh/bytes/ZBytes.kt +++ b/zenoh-java/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. * @@ -30,7 +33,30 @@ package io.zenoh.bytes * flatbuffers, etc. * */ -class ZBytes internal constructor(internal val bytes: ByteArray) : IntoZBytes { +class ZBytes private constructor( + private var eager: ByteArray?, + private var handle: JniZBytes?, +) : IntoZBytes { + + 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 via + * `zZbytesAsBytes` — 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.asBytes(throwZError0) + eager = b + handle = null + h.close() + b + } + } companion object { @@ -45,8 +71,24 @@ class ZBytes internal constructor(internal val bytes: ByteArray) : IntoZBytes { */ @JvmStatic fun from(bytes: ByteArray) = ZBytes(bytes) + + /** + * Decodes a native `ZZBytes` handle into a value [ZBytes] and frees the + * handle. Used when an accessor / callback hands back an owned buffer. + */ + /** 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-java/src/commonMain/kotlin/io/zenoh/config/WhatAmI.kt b/zenoh-java/src/commonMain/kotlin/io/zenoh/config/WhatAmI.kt index 983c9221..c6d3161c 100644 --- a/zenoh-java/src/commonMain/kotlin/io/zenoh/config/WhatAmI.kt +++ b/zenoh-java/src/commonMain/kotlin/io/zenoh/config/WhatAmI.kt @@ -19,12 +19,12 @@ package io.zenoh.config * * The role of the node sending the `hello` message. */ -enum class WhatAmI(internal val value: Int) { - Router(1), - Peer(2), - Client(4); +enum class WhatAmI(internal val jni: io.zenoh.jni.config.WhatAmI) { + Router(io.zenoh.jni.config.WhatAmI.ROUTER), + Peer(io.zenoh.jni.config.WhatAmI.PEER), + Client(io.zenoh.jni.config.WhatAmI.CLIENT); companion object { - internal fun fromInt(value: Int) = entries.first { value == it.value } + internal fun fromJni(jni: io.zenoh.jni.config.WhatAmI): WhatAmI = entries.first { it.jni == jni } } } diff --git a/zenoh-java/src/commonMain/kotlin/io/zenoh/config/ZenohId.kt b/zenoh-java/src/commonMain/kotlin/io/zenoh/config/ZenohId.kt index 9787fced..ff2b59f9 100644 --- a/zenoh-java/src/commonMain/kotlin/io/zenoh/config/ZenohId.kt +++ b/zenoh-java/src/commonMain/kotlin/io/zenoh/config/ZenohId.kt @@ -14,16 +14,17 @@ package io.zenoh.config -import io.zenoh.jni.JNIZenohId +import io.zenoh.exceptions.throwZError0 +import io.zenoh.jni.config.ZenohId as JniZenohId import kotlin.math.absoluteValue /** * The global unique id of a Zenoh peer. */ -data class ZenohId internal constructor(internal val bytes: ByteArray) { +data class ZenohId internal constructor(internal val inner: JniZenohId) { override fun toString(): String { - return JNIZenohId.toStringViaJNI(bytes) + return inner.toStr(throwZError0) } override fun equals(other: Any?): Boolean { @@ -32,11 +33,11 @@ data class ZenohId internal constructor(internal val bytes: ByteArray) { other as ZenohId - return bytes.contentEquals(other.bytes) + return inner.bytes.contentEquals(other.inner.bytes) } override fun hashCode(): Int { - return bytes.contentHashCode() + return inner.bytes.contentHashCode() } } diff --git a/zenoh-java/src/commonMain/kotlin/io/zenoh/exceptions/JNIErrorHandlers.kt b/zenoh-java/src/commonMain/kotlin/io/zenoh/exceptions/JNIErrorHandlers.kt new file mode 100644 index 00000000..d4a6954e --- /dev/null +++ b/zenoh-java/src/commonMain/kotlin/io/zenoh/exceptions/JNIErrorHandlers.kt @@ -0,0 +1,42 @@ +// +// 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.JniErrorHandler +import io.zenoh.jni.errors.ErrorHandler + +/** + * Error-callback handlers passed to the generated flat-jni wrappers. In the + * canonical model a wrapper never throws from native code — on failure it + * invokes its trailing `onError` handler (a generated typed `fun interface`). + * These handlers throw [ZError] directly, so the SDK no longer needs a + * `try/catch` translation layer. + * + * `je` is the binding-layer error (UTF-8 decode, closed handle, …); `ze` is the + * library (zenoh) error message. Exactly one is set. The handlers bind the + * interface's `out R` to [Nothing], which is a subtype of every `R`, so a + * single instance satisfies every wrapper's `onError` regardless of its + * return type (declaration-site covariance). + */ + +/** Handler for a fallible wrapper (`Result<_, ZError>`): `run(je, message)`. + * `message` is builder-typed (non-null) — on a binding error (`je != null`) + * the native side fills it with the default `""`. */ +internal val throwZError: ErrorHandler = + ErrorHandler { je, message -> throw ZError(je ?: message) } + +/** Handler for an infallible wrapper (binding errors only): `run(je)`. */ +internal val throwZError0: JniErrorHandler = + JniErrorHandler { je -> throw ZError(je ?: "native binding error") } diff --git a/zenoh-java/src/commonMain/kotlin/io/zenoh/ext/ZDeserializer.kt b/zenoh-java/src/commonMain/kotlin/io/zenoh/ext/ZDeserializer.kt index 1d039cbe..984778b0 100644 --- a/zenoh-java/src/commonMain/kotlin/io/zenoh/ext/ZDeserializer.kt +++ b/zenoh-java/src/commonMain/kotlin/io/zenoh/ext/ZDeserializer.kt @@ -17,7 +17,8 @@ package io.zenoh.ext import com.google.common.reflect.TypeToken import io.zenoh.bytes.IntoZBytes import io.zenoh.bytes.ZBytes -import io.zenoh.jni.JNIZBytes +import io.zenoh.exceptions.throwZError0 +import io.zenoh.jni.bytes.deserializeViaJNI /** * Zenoh deserializer. @@ -106,6 +107,6 @@ abstract class ZDeserializer: TypeToken() { */ fun deserialize(zbytes: IntoZBytes): T { @Suppress("UNCHECKED_CAST") - return JNIZBytes.deserializeViaJNI(zbytes.into(), this.type) as T + return deserializeViaJNI(zbytes.into().bytes, this.type, throwZError0) as T } } diff --git a/zenoh-java/src/commonMain/kotlin/io/zenoh/ext/ZSerializer.kt b/zenoh-java/src/commonMain/kotlin/io/zenoh/ext/ZSerializer.kt index 8ee4a1b7..8a7a1b6e 100644 --- a/zenoh-java/src/commonMain/kotlin/io/zenoh/ext/ZSerializer.kt +++ b/zenoh-java/src/commonMain/kotlin/io/zenoh/ext/ZSerializer.kt @@ -16,7 +16,8 @@ package io.zenoh.ext import com.google.common.reflect.TypeToken import io.zenoh.bytes.ZBytes -import io.zenoh.jni.JNIZBytes +import io.zenoh.exceptions.throwZError0 +import io.zenoh.jni.bytes.serializeViaJNI /** * Zenoh serializer. @@ -104,6 +105,6 @@ abstract class ZSerializer: TypeToken() { * Serialize [t] into a [ZBytes]. */ fun serialize(t: T): ZBytes { - return JNIZBytes.serializeViaJNI(t as Any, this.type) + return ZBytes.from(serializeViaJNI(t as Any, this.type, throwZError0)) } } diff --git a/zenoh-java/src/commonMain/kotlin/io/zenoh/jni/JNIConfig.kt b/zenoh-java/src/commonMain/kotlin/io/zenoh/jni/JNIConfig.kt deleted file mode 100644 index ea278988..00000000 --- a/zenoh-java/src/commonMain/kotlin/io/zenoh/jni/JNIConfig.kt +++ /dev/null @@ -1,101 +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)) - } - - @Throws(ZError::class) - fun loadConfigFile(path: Path): Config { - val cfgPtr = loadConfigFileViaJNI(path.toString()) - return Config(JNIConfig(cfgPtr)) - } - - @Throws(ZError::class) - fun loadConfigFile(file: File): Config = loadConfigFile(file.toPath()) - - @Throws(ZError::class) - fun loadJsonConfig(rawConfig: String): Config { - val cfgPtr = loadJsonConfigViaJNI(rawConfig) - return Config(JNIConfig(cfgPtr)) - } - - @Throws(ZError::class) - fun loadJson5Config(rawConfig: String): Config { - val cfgPtr = loadJsonConfigViaJNI(rawConfig) - return Config(JNIConfig(cfgPtr)) - } - - @Throws(ZError::class) - fun loadYamlConfig(rawConfig: String): Config { - val cfgPtr = loadYamlConfigViaJNI(rawConfig) - return 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) - } - - @Throws(ZError::class) - fun getJson(key: String): String { - return getJsonViaJNI(ptr, key) - } - - @Throws(ZError::class) - fun insertJson5(key: String, value: String) { - insertJson5ViaJNI(this.ptr, key, value) - } -} diff --git a/zenoh-java/src/commonMain/kotlin/io/zenoh/jni/JNIKeyExpr.kt b/zenoh-java/src/commonMain/kotlin/io/zenoh/jni/JNIKeyExpr.kt deleted file mode 100644 index 29e419e3..00000000 --- a/zenoh-java/src/commonMain/kotlin/io/zenoh/jni/JNIKeyExpr.kt +++ /dev/null @@ -1,104 +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 - } - - @Throws(ZError::class) - fun tryFrom(keyExpr: String): KeyExpr { - return KeyExpr(tryFromViaJNI(keyExpr)) - } - - @Throws(ZError::class) - fun autocanonize(keyExpr: String): KeyExpr { - return KeyExpr(autocanonizeViaJNI(keyExpr)) - } - - @Throws(ZError::class) - fun intersects(keyExprA: KeyExpr, keyExprB: KeyExpr): Boolean = intersectsViaJNI( - keyExprA.jniKeyExpr?.ptr ?: 0, - keyExprA.keyExpr, - keyExprB.jniKeyExpr?.ptr ?: 0, - keyExprB.keyExpr - ) - - @Throws(ZError::class) - fun includes(keyExprA: KeyExpr, keyExprB: KeyExpr): Boolean = includesViaJNI( - keyExprA.jniKeyExpr?.ptr ?: 0, - keyExprA.keyExpr, - keyExprB.jniKeyExpr?.ptr ?: 0, - keyExprB.keyExpr - ) - - @Throws(ZError::class) - 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) - } - - @Throws(ZError::class) - fun joinViaJNI(keyExpr: KeyExpr, other: String): KeyExpr { - return KeyExpr(joinViaJNI(keyExpr.jniKeyExpr?.ptr ?: 0, keyExpr.keyExpr, other)) - } - - @Throws(ZError::class) - fun concatViaJNI(keyExpr: KeyExpr, other: String): KeyExpr { - return 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-java/src/commonMain/kotlin/io/zenoh/jni/JNILiveliness.kt b/zenoh-java/src/commonMain/kotlin/io/zenoh/jni/JNILiveliness.kt deleted file mode 100644 index 0225c32b..00000000 --- a/zenoh-java/src/commonMain/kotlin/io/zenoh/jni/JNILiveliness.kt +++ /dev/null @@ -1,188 +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.exceptions.ZError -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.CallbackSubscriber -import io.zenoh.pubsub.HandlerSubscriber -import io.zenoh.qos.CongestionControl -import io.zenoh.qos.Priority -import io.zenoh.qos.QoS -import io.zenoh.query.Reply -import io.zenoh.sample.Sample -import io.zenoh.sample.SampleKind -import org.apache.commons.net.ntp.TimeStamp -import java.time.Duration - -internal object JNILiveliness { - - @Throws(ZError::class) - fun get( - jniSession: JNISession, - keyExpr: KeyExpr, - callback: Callback, - receiver: R, - timeout: Duration, - onClose: Runnable - ): R { - 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.Success(replierZid?.let { EntityGlobalId(ZenohId(it), replierEid.toUInt()) }, sample) - } else { - reply = Reply.Error( - replierZid?.let { EntityGlobalId(ZenohId(it), replierEid.toUInt()) }, - payload.into(), - Encoding(encodingId, schema = encodingSchema) - ) - } - callback.run(reply) - } - getViaJNI( - jniSession.sessionPtr, - keyExpr.jniKeyExpr?.ptr ?: 0, - keyExpr.keyExpr, - getCallback, - timeout.toMillis(), - onClose::run - ) - return receiver - } - - fun declareToken(jniSession: JNISession, keyExpr: KeyExpr): LivelinessToken { - val ptr = declareTokenViaJNI(jniSession.sessionPtr, keyExpr.jniKeyExpr?.ptr ?: 0, keyExpr.keyExpr) - return LivelinessToken(JNILivelinessToken(ptr)) - } - - fun declareSubscriber( - jniSession: JNISession, - keyExpr: KeyExpr, - callback: Callback, - history: Boolean, - onClose: () -> Unit - ): CallbackSubscriber { - 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, - keyExpr.jniKeyExpr?.ptr ?: 0, - keyExpr.keyExpr, - subCallback, - history, - onClose - ) - return CallbackSubscriber(keyExpr, JNISubscriber(ptr)) - } - - fun declareSubscriber( - jniSession: JNISession, - keyExpr: KeyExpr, - callback: Callback, - receiver: R, - history: Boolean, - onClose: () -> Unit - ): HandlerSubscriber { - 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, - keyExpr.jniKeyExpr?.ptr ?: 0, - keyExpr.keyExpr, - subCallback, - history, - onClose - ) - return HandlerSubscriber(keyExpr, JNISubscriber(ptr), receiver) - } - - 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-java/src/commonMain/kotlin/io/zenoh/jni/JNILivelinessToken.kt b/zenoh-java/src/commonMain/kotlin/io/zenoh/jni/JNILivelinessToken.kt deleted file mode 100644 index 991c860a..00000000 --- a/zenoh-java/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-java/src/commonMain/kotlin/io/zenoh/jni/JNIPublisher.kt b/zenoh-java/src/commonMain/kotlin/io/zenoh/jni/JNIPublisher.kt deleted file mode 100644 index 6e694271..00000000 --- a/zenoh-java/src/commonMain/kotlin/io/zenoh/jni/JNIPublisher.kt +++ /dev/null @@ -1,70 +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 - -/** - * Adapter class to handle the interactions with Zenoh through JNI for a [io.zenoh.pubsub.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. - */ - @Throws(ZError::class) - fun put(payload: IntoZBytes, encoding: Encoding?, attachment: IntoZBytes?) { - val resolvedEncoding = encoding ?: Encoding.defaultEncoding() - putViaJNI(payload.into().bytes, resolvedEncoding.id, resolvedEncoding.schema, attachment?.into()?.bytes, ptr) - } - - /** - * Delete operation. - * - * @param attachment Optional attachment. - */ - @Throws(ZError::class) - fun delete(attachment: IntoZBytes?) { - 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-java/src/commonMain/kotlin/io/zenoh/jni/JNIQuerier.kt b/zenoh-java/src/commonMain/kotlin/io/zenoh/jni/JNIQuerier.kt deleted file mode 100644 index c026ed1c..00000000 --- a/zenoh-java/src/commonMain/kotlin/io/zenoh/jni/JNIQuerier.kt +++ /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, -// - -package io.zenoh.jni - -import io.zenoh.annotations.Unstable -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.handlers.Handler -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.Querier -import io.zenoh.query.Reply -import io.zenoh.sample.Sample -import io.zenoh.sample.SampleKind -import org.apache.commons.net.ntp.TimeStamp - -internal class JNIQuerier(val ptr: Long) { - - @OptIn(Unstable::class) - @Throws(ZError::class) - fun performGetWithCallback(keyExpr: KeyExpr, callback: Callback, options: Querier.GetOptions) { - performGet(keyExpr, options.parameters, callback, fun() {}, Unit, options.attachment, options.payload, options.encoding) - } - - @OptIn(Unstable::class) - @Throws(ZError::class) - fun performGetWithHandler(keyExpr: KeyExpr, handler: Handler, options: Querier.GetOptions): R { - return performGet(keyExpr, options.parameters, handler::handle, handler::onClose, handler.receiver(), options.attachment, options.payload, options.encoding) - } - - @Throws(ZError::class) - private fun performGet( - keyExpr: KeyExpr, - parameters: Parameters?, - callback: Callback, - onClose: () -> Unit, - receiver: R, - attachment: IntoZBytes?, - payload: IntoZBytes?, - encoding: Encoding? - ): R { - val getCallback = JNIGetCallback { - replierZid: ByteArray?, - replierEid: Int, - success: Boolean, - keyExpr2: String?, - payload2: 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), - payload2.into(), - Encoding(encodingId, schema = encodingSchema), - SampleKind.fromInt(kind), - timestamp, - QoS(CongestionControl.fromInt(congestionControl), Priority.fromInt(priority), express), - attachmentBytes?.into() - ) - reply = Reply.Success(replierZid?.let { EntityGlobalId(ZenohId(it), replierEid.toUInt()) }, sample) - } else { - reply = Reply.Error( - replierZid?.let { EntityGlobalId(ZenohId(it), replierEid.toUInt()) }, - payload2.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.defaultEncoding().id, - encoding?.schema - ) - return 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-java/src/commonMain/kotlin/io/zenoh/jni/JNIQuery.kt b/zenoh-java/src/commonMain/kotlin/io/zenoh/jni/JNIQuery.kt deleted file mode 100644 index 0b1683f0..00000000 --- a/zenoh-java/src/commonMain/kotlin/io/zenoh/jni/JNIQuery.kt +++ /dev/null @@ -1,106 +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) { - 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) { - replyErrorViaJNI(ptr, error.into().bytes, encoding.id, encoding.schema) - } - - fun replyDelete(keyExpr: KeyExpr, timestamp: TimeStamp?, attachment: IntoZBytes?, qos: QoS) { - 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-java/src/commonMain/kotlin/io/zenoh/jni/JNIQueryable.kt b/zenoh-java/src/commonMain/kotlin/io/zenoh/jni/JNIQueryable.kt deleted file mode 100644 index e5f7d3ce..00000000 --- a/zenoh-java/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-java/src/commonMain/kotlin/io/zenoh/jni/JNIScout.kt b/zenoh-java/src/commonMain/kotlin/io/zenoh/jni/JNIScout.kt deleted file mode 100644 index f2e1c49c..00000000 --- a/zenoh-java/src/commonMain/kotlin/io/zenoh/jni/JNIScout.kt +++ /dev/null @@ -1,87 +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 io.zenoh.handlers.Callback -import io.zenoh.jni.callbacks.JNIScoutCallback -import io.zenoh.config.ZenohId -import io.zenoh.scouting.Hello -import io.zenoh.config.WhatAmI -import io.zenoh.jni.callbacks.JNIOnCloseCallback -import io.zenoh.scouting.CallbackScout -import io.zenoh.scouting.HandlerScout - -/** - * 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 { - - init { - ZenohLoad - } - - @Throws(ZError::class) - fun scoutWithHandler( - whatAmI: Set, - callback: Callback, - onClose: () -> Unit, - config: Config?, - receiver: R - ): HandlerScout { - 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) - return HandlerScout(JNIScout(ptr), receiver) - } - - @Throws(ZError::class) - fun scoutWithCallback( - whatAmI: Set, - callback: Callback, - config: Config?, - ): CallbackScout { - 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, fun() {},config?.jniConfig?.ptr ?: 0) - return CallbackScout(JNIScout(ptr)) - } - - @Throws(ZError::class) - private external fun scoutViaJNI( - whatAmI: Int, - callback: JNIScoutCallback, - onClose: JNIOnCloseCallback, - configPtr: Long, - ): Long - - @Throws(ZError::class) - external fun freePtrViaJNI(ptr: Long) - } - - fun close() { - freePtrViaJNI(ptr) - } -} diff --git a/zenoh-java/src/commonMain/kotlin/io/zenoh/jni/JNISession.kt b/zenoh-java/src/commonMain/kotlin/io/zenoh/jni/JNISession.kt deleted file mode 100644 index 096a6c59..00000000 --- a/zenoh-java/src/commonMain/kotlin/io/zenoh/jni/JNISession.kt +++ /dev/null @@ -1,548 +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.annotations.Unstable -import io.zenoh.config.EntityGlobalId -import io.zenoh.handlers.Handler -import io.zenoh.pubsub.* -import io.zenoh.qos.CongestionControl -import io.zenoh.qos.Priority -import io.zenoh.qos.QoS -import io.zenoh.query.* -import io.zenoh.sample.Sample -import io.zenoh.sample.SampleKind -import org.apache.commons.net.ntp.TimeStamp - -/** Adapter class to handle the communication with the Zenoh JNI code for a [Session]. */ -internal class JNISession(val sessionPtr: Long) { - - companion object { - init { - ZenohLoad - } - - @Throws(ZError::class) - fun open(config: Config): JNISession { - val sessionPtr = openSessionViaJNI(config.jniConfig.ptr) - return JNISession(sessionPtr) - } - - @Throws(ZError::class) - private external fun openSessionViaJNI(configPtr: Long): Long - } - - fun close() { - closeSessionViaJNI(sessionPtr) - } - - @Throws(ZError::class) - fun declarePublisher(keyExpr: KeyExpr, publisherOptions: PublisherOptions): Publisher { - val publisherRawPtr = declarePublisherViaJNI( - keyExpr.jniKeyExpr?.ptr ?: 0, - keyExpr.keyExpr, - sessionPtr, - publisherOptions.congestionControl.value, - publisherOptions.priority.value, - publisherOptions.express, - publisherOptions.reliability.ordinal - ) - return Publisher( - keyExpr, - publisherOptions.congestionControl, - publisherOptions.priority, - publisherOptions.encoding, - JNIPublisher(publisherRawPtr), - ) - } - - @Throws(ZError::class) - fun declareSubscriberWithHandler( - keyExpr: KeyExpr, handler: Handler - ): HandlerSubscriber { - val subCallback = - JNISubscriberCallback { keyExpr1, 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(keyExpr1, null), - payload.into(), - Encoding(encodingId, schema = encodingSchema), - SampleKind.fromInt(kind), - timestamp, - QoS(CongestionControl.fromInt(congestionControl), Priority.fromInt(priority), express), - attachmentBytes?.into() - ) - handler.handle(sample) - } - val subscriberRawPtr = declareSubscriberViaJNI( - keyExpr.jniKeyExpr?.ptr ?: 0, keyExpr.keyExpr, sessionPtr, subCallback, handler::onClose - ) - return HandlerSubscriber(keyExpr, JNISubscriber(subscriberRawPtr), handler.receiver()) - } - - @Throws(ZError::class) - fun declareSubscriberWithCallback( - keyExpr: KeyExpr, callback: Callback - ): CallbackSubscriber { - val subCallback = - JNISubscriberCallback { keyExpr1, 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(keyExpr1, 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, - subCallback, - fun() {} - ) - return CallbackSubscriber(keyExpr, JNISubscriber(subscriberRawPtr)) - } - - @Throws(ZError::class) - fun declareQueryableWithCallback( - keyExpr: KeyExpr, callback: Callback, config: QueryableOptions - ): CallbackQueryable { - val queryCallback = - JNIQueryableCallback { keyExpr1: String, selectorParams: String, payload: ByteArray?, encodingId: Int, encodingSchema: String?, attachmentBytes: ByteArray?, queryPtr: Long, acceptReplies: Int -> - val jniQuery = JNIQuery(queryPtr) - val keyExpr2 = KeyExpr(keyExpr1, null) - val selector = if (selectorParams.isEmpty()) { - Selector(keyExpr2) - } else { - Selector(keyExpr2, Parameters.from(selectorParams)) - } - val replyKeyExpr = ReplyKeyExpr.entries[acceptReplies] - val query = Query( - keyExpr2, - selector, - payload?.into(), - payload?.let { Encoding(encodingId, schema = encodingSchema) }, - attachmentBytes?.into(), - replyKeyExpr, - jniQuery - ) - callback.run(query) - } - val queryableRawPtr = declareQueryableViaJNI( - keyExpr.jniKeyExpr?.ptr ?: 0, - keyExpr.keyExpr, - sessionPtr, - queryCallback, - fun() {}, - config.complete - ) - return CallbackQueryable(keyExpr, JNIQueryable(queryableRawPtr)) - } - - @Throws(ZError::class) - fun declareQueryableWithHandler( - keyExpr: KeyExpr, handler: Handler, config: QueryableOptions - ): HandlerQueryable { - val queryCallback = - JNIQueryableCallback { keyExpr1: String, selectorParams: String, payload: ByteArray?, encodingId: Int, encodingSchema: String?, attachmentBytes: ByteArray?, queryPtr: Long, acceptReplies: Int -> - val jniQuery = JNIQuery(queryPtr) - val keyExpr2 = KeyExpr(keyExpr1, null) - val selector = if (selectorParams.isEmpty()) { - Selector(keyExpr2) - } else { - Selector(keyExpr2, Parameters.from(selectorParams)) - } - val replyKeyExpr = ReplyKeyExpr.entries[acceptReplies] - val query = Query( - keyExpr2, - selector, - payload?.into(), - payload?.let { Encoding(encodingId, schema = encodingSchema) }, - attachmentBytes?.into(), - replyKeyExpr, - jniQuery - ) - handler.handle(query) - } - val queryableRawPtr = declareQueryableViaJNI( - keyExpr.jniKeyExpr?.ptr ?: 0, - keyExpr.keyExpr, - sessionPtr, - queryCallback, - handler::onClose, - config.complete - ) - return HandlerQueryable(keyExpr, JNIQueryable(queryableRawPtr), handler.receiver()) - } - - @OptIn(Unstable::class) - fun declareQuerier( - keyExpr: KeyExpr, - options: QuerierOptions - ): Querier { - val querierRawPtr = declareQuerierViaJNI( - keyExpr.jniKeyExpr?.ptr ?: 0, - keyExpr.keyExpr, - sessionPtr, - options.target.ordinal, - options.consolidationMode.ordinal, - options.congestionControl.value, - options.priority.value, - options.express, - options.timeout.toMillis(), - options.acceptReplies.ordinal - ) - return Querier( - keyExpr, - QoS( - congestionControl = options.congestionControl, - priority = options.priority, - express = options.express - ), - JNIQuerier(querierRawPtr) - ) - } - - @Throws(ZError::class) - fun performGetWithCallback( - intoSelector: IntoSelector, - callback: Callback, - options: GetOptions - ) { - val getCallback = JNIGetCallback { - replierZid: ByteArray?, - replierEid: Int, - success: Boolean, - keyExpr: String?, - payload1: 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), - payload1.into(), - Encoding(encodingId, schema = encodingSchema), - SampleKind.fromInt(kind), - timestamp, - QoS(CongestionControl.fromInt(congestionControl), Priority.fromInt(priority), express), - attachmentBytes?.into() - ) - reply = Reply.Success(replierZid?.let { EntityGlobalId(ZenohId(it), replierEid.toUInt()) }, sample) - } else { - reply = Reply.Error( - replierZid?.let { EntityGlobalId(ZenohId(it), replierEid.toUInt()) }, - payload1.into(), - Encoding(encodingId, schema = encodingSchema) - ) - } - callback.run(reply) - } - - val selector = intoSelector.into() - getViaJNI( - selector.keyExpr.jniKeyExpr?.ptr ?: 0, - selector.keyExpr.keyExpr, - selector.parameters?.toString(), - sessionPtr, - getCallback, - fun() {}, - options.timeout.toMillis(), - options.target.ordinal, - options.consolidation.ordinal, - options.attachment?.into()?.bytes, - options.payload?.into()?.bytes, - options.encoding?.id ?: Encoding.defaultEncoding().id, - options.encoding?.schema, - options.qos.congestionControl.value, - options.qos.priority.value, - options.qos.express, - options.acceptReplies.ordinal - ) - } - - @Throws(ZError::class) - fun performGetWithHandler( - intoSelector: IntoSelector, - handler: Handler, - options: GetOptions - ): R { - val getCallback = JNIGetCallback { - replierZid: ByteArray?, - replierEid: Int, - success: Boolean, - keyExpr: String?, - payload1: 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), - payload1.into(), - Encoding(encodingId, schema = encodingSchema), - SampleKind.fromInt(kind), - timestamp, - QoS(CongestionControl.fromInt(congestionControl), Priority.fromInt(priority), express), - attachmentBytes?.into() - ) - reply = Reply.Success(replierZid?.let { EntityGlobalId(ZenohId(it), replierEid.toUInt()) }, sample) - } else { - reply = Reply.Error( - replierZid?.let { EntityGlobalId(ZenohId(it), replierEid.toUInt()) }, - payload1.into(), - Encoding(encodingId, schema = encodingSchema) - ) - } - handler.handle(reply) - } - - val selector = intoSelector.into() - getViaJNI( - selector.keyExpr.jniKeyExpr?.ptr ?: 0, - selector.keyExpr.keyExpr, - selector.parameters?.toString(), - sessionPtr, - getCallback, - handler::onClose, - options.timeout.toMillis(), - options.target.ordinal, - options.consolidation.ordinal, - options.attachment?.into()?.bytes, - options.payload?.into()?.bytes, - options.encoding?.id ?: Encoding.defaultEncoding().id, - options.encoding?.schema, - options.qos.congestionControl.value, - options.qos.priority.value, - options.qos.express, - options.acceptReplies.ordinal - ) - return handler.receiver() - } - - @Throws(ZError::class) - fun declareKeyExpr(keyExpr: String): KeyExpr { - val ptr = declareKeyExprViaJNI(sessionPtr, keyExpr) - return KeyExpr(keyExpr, JNIKeyExpr(ptr)) - } - - @Throws(ZError::class) - fun undeclareKeyExpr(keyExpr: KeyExpr) { - keyExpr.jniKeyExpr?.run { - undeclareKeyExprViaJNI(sessionPtr, this.ptr) - keyExpr.jniKeyExpr = null - } ?: throw ZError("Attempting to undeclare a non declared key expression.") - } - - @Throws(ZError::class) - fun performPut( - keyExpr: KeyExpr, - payload: IntoZBytes, - options: PutOptions, - ) { - val encoding = options.encoding ?: Encoding.defaultEncoding() - putViaJNI( - keyExpr.jniKeyExpr?.ptr ?: 0, - keyExpr.keyExpr, - sessionPtr, - payload.into().bytes, - encoding.id, - encoding.schema, - options.congestionControl.value, - options.priority.value, - options.express, - options.attachment?.into()?.bytes, - options.reliability.ordinal - ) - } - - @Throws(ZError::class) - fun performDelete( - keyExpr: KeyExpr, - options: DeleteOptions, - ) { - deleteViaJNI( - keyExpr.jniKeyExpr?.ptr ?: 0, - keyExpr.keyExpr, - sessionPtr, - options.congestionControl.value, - options.priority.value, - options.express, - options.attachment?.into()?.bytes, - options.reliability.ordinal - ) - } - - @Throws(ZError::class) - fun zid(): ZenohId { - return ZenohId(getZidViaJNI(sessionPtr)) - } - - @Throws(ZError::class) - fun peersZid(): List { - return getPeersZidViaJNI(sessionPtr).map { ZenohId(it) } - } - - @Throws(ZError::class) - fun routersZid(): List { - return getRoutersZidViaJNI(sessionPtr).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 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 declareSubscriberViaJNI( - keyExprPtr: Long, - keyExprString: String, - sessionPtr: Long, - 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-java/src/commonMain/kotlin/io/zenoh/jni/JNISubscriber.kt b/zenoh-java/src/commonMain/kotlin/io/zenoh/jni/JNISubscriber.kt deleted file mode 100644 index 1bb80543..00000000 --- a/zenoh-java/src/commonMain/kotlin/io/zenoh/jni/JNISubscriber.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 - -/** - * Adapter class to handle the interactions with Zenoh through JNI for a [io.zenoh.pubsub.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-java/src/commonMain/kotlin/io/zenoh/jni/JNIZBytes.kt b/zenoh-java/src/commonMain/kotlin/io/zenoh/jni/JNIZBytes.kt deleted file mode 100644 index 799b9cac..00000000 --- a/zenoh-java/src/commonMain/kotlin/io/zenoh/jni/JNIZBytes.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.ZenohLoad -import io.zenoh.bytes.ZBytes -import java.lang.reflect.Type - -@PublishedApi -internal object JNIZBytes { - - init { - ZenohLoad - } - - @JvmStatic - external fun serializeViaJNI(any: Any, type: Type): ZBytes - - @JvmStatic - external fun deserializeViaJNI(zBytes: ZBytes, type: Type): Any -} diff --git a/zenoh-java/src/commonMain/kotlin/io/zenoh/jni/JNIZenohId.kt b/zenoh-java/src/commonMain/kotlin/io/zenoh/jni/JNIZenohId.kt deleted file mode 100644 index 53ecb5dc..00000000 --- a/zenoh-java/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-java/src/commonMain/kotlin/io/zenoh/jni/callbacks/JNIGetCallback.kt b/zenoh-java/src/commonMain/kotlin/io/zenoh/jni/callbacks/JNIGetCallback.kt deleted file mode 100644 index 14f3c8e9..00000000 --- a/zenoh-java/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-java/src/commonMain/kotlin/io/zenoh/jni/callbacks/JNIOnCloseCallback.kt b/zenoh-java/src/commonMain/kotlin/io/zenoh/jni/callbacks/JNIOnCloseCallback.kt deleted file mode 100644 index b58fa23d..00000000 --- a/zenoh-java/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-java/src/commonMain/kotlin/io/zenoh/jni/callbacks/JNIQueryableCallback.kt b/zenoh-java/src/commonMain/kotlin/io/zenoh/jni/callbacks/JNIQueryableCallback.kt deleted file mode 100644 index addf1430..00000000 --- a/zenoh-java/src/commonMain/kotlin/io/zenoh/jni/callbacks/JNIQueryableCallback.kt +++ /dev/null @@ -1,26 +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, - acceptReplies: Int) -} diff --git a/zenoh-java/src/commonMain/kotlin/io/zenoh/jni/callbacks/JNIScoutCallback.kt b/zenoh-java/src/commonMain/kotlin/io/zenoh/jni/callbacks/JNIScoutCallback.kt deleted file mode 100644 index 0a8b20e9..00000000 --- a/zenoh-java/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-java/src/commonMain/kotlin/io/zenoh/jni/callbacks/JNISubscriberCallback.kt b/zenoh-java/src/commonMain/kotlin/io/zenoh/jni/callbacks/JNISubscriberCallback.kt deleted file mode 100644 index 76373c72..00000000 --- a/zenoh-java/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-java/src/commonMain/kotlin/io/zenoh/keyexpr/KeyExpr.kt b/zenoh-java/src/commonMain/kotlin/io/zenoh/keyexpr/KeyExpr.kt index b5fe6110..cb581d3c 100644 --- a/zenoh-java/src/commonMain/kotlin/io/zenoh/keyexpr/KeyExpr.kt +++ b/zenoh-java/src/commonMain/kotlin/io/zenoh/keyexpr/KeyExpr.kt @@ -17,7 +17,9 @@ package io.zenoh.keyexpr import io.zenoh.Session import io.zenoh.session.SessionDeclaration import io.zenoh.exceptions.ZError -import io.zenoh.jni.JNIKeyExpr +import io.zenoh.exceptions.throwZError +import io.zenoh.exceptions.throwZError0 +import io.zenoh.jni.keyexpr.KeyExpr as JniKeyExpr import io.zenoh.query.IntoSelector import io.zenoh.query.Selector @@ -59,8 +61,26 @@ import io.zenoh.query.Selector * ensures that [close] is automatically called, safely managing the lifecycle of the [KeyExpr] instance. * */ -class KeyExpr internal constructor(internal val keyExpr: String, internal var jniKeyExpr: JNIKeyExpr? = null): AutoCloseable, IntoSelector, - SessionDeclaration { +class KeyExpr internal constructor( + /** The owned native handle, passed (by reference) to raw `keyexpr_*` ops. */ + internal val flat: JniKeyExpr, + keyExprString: String? = null, +) : AutoCloseable, IntoSelector, SessionDeclaration { + + private var keyExprStringLazy: String? = keyExprString + + /** + * The string form, read LAZILY from the native handle on first use + * (toString/equals) — a received key expression is usually only matched + * or echoed by handle, so the common path never crosses for the string + * (forward-extraction rule: handle eager, string on demand). + */ + private val keyExprString: String + get() = keyExprStringLazy + ?: flat.getStr(throwZError0).also { keyExprStringLazy = it } + + /** Clone the native handle before passing it to a consuming Rust API. */ + internal fun cloneFlat(): JniKeyExpr = flat.newClone(throwZError0) companion object { @@ -78,9 +98,7 @@ class KeyExpr internal constructor(internal val keyExpr: String, internal var jn */ @JvmStatic @Throws(ZError::class) - fun tryFrom(keyExpr: String): KeyExpr { - return JNIKeyExpr.tryFrom(keyExpr) - } + fun tryFrom(keyExpr: String): KeyExpr = KeyExpr(JniKeyExpr.newTryFrom(keyExpr, throwZError)) /** * Autocanonize. @@ -94,9 +112,7 @@ class KeyExpr internal constructor(internal val keyExpr: String, internal var jn */ @JvmStatic @Throws(ZError::class) - fun autocanonize(keyExpr: String): KeyExpr { - return JNIKeyExpr.autocanonize(keyExpr) - } + fun autocanonize(keyExpr: String): KeyExpr = KeyExpr(JniKeyExpr.newAutocanonize(keyExpr, throwZError)) } /** @@ -105,9 +121,8 @@ class KeyExpr internal constructor(internal val keyExpr: String, internal var jn * Will return false as well if the key expression is not valid anymore. */ @Throws(ZError::class) - fun intersects(other: KeyExpr): Boolean { - return JNIKeyExpr.intersects(this, other) - } + fun intersects(other: KeyExpr): Boolean = + this.flat.intersects(other.flat, throwZError0) /** * Includes operation. This method returns `true` when all the keys defined by `other` also belong to the set @@ -115,9 +130,8 @@ class KeyExpr internal constructor(internal val keyExpr: String, internal var jn * Will return false as well if the key expression is not valid anymore. */ @Throws(ZError::class) - fun includes(other: KeyExpr): Boolean { - return JNIKeyExpr.includes(this, other) - } + fun includes(other: KeyExpr): Boolean = + this.flat.includes(other.flat, throwZError0) /** * Returns the relation between 'this' and other from 'this''s point of view ([SetIntersectionLevel.INCLUDES] @@ -125,31 +139,26 @@ class KeyExpr internal constructor(internal val keyExpr: String, internal var jn * so you should favor these methods for most applications. */ @Throws(ZError::class) - fun relationTo(other: KeyExpr): SetIntersectionLevel { - return JNIKeyExpr.relationTo(this, other) - } + fun relationTo(other: KeyExpr): SetIntersectionLevel = + SetIntersectionLevel.fromJni(this.flat.relationTo(other.flat, throwZError0)) /** * Joins both sides, inserting a / in between them. * This should be your preferred method when concatenating path segments. */ @Throws(ZError::class) - fun join(other: String): KeyExpr { - return JNIKeyExpr.joinViaJNI(this, other) - } + fun join(other: String): KeyExpr = + KeyExpr(JniKeyExpr.newJoin(this.flat, other, throwZError)) /** * 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. */ @Throws(ZError::class) - fun concat(other: String): KeyExpr { - return JNIKeyExpr.concatViaJNI(this, other) - } + fun concat(other: String): KeyExpr = + KeyExpr(JniKeyExpr.newConcat(this.flat, other, throwZError)) - override fun toString(): String { - return keyExpr - } + override fun toString(): String = keyExprString /** * Equivalent to [undeclare]. This function is automatically called when using try with resources. @@ -166,8 +175,8 @@ class KeyExpr internal constructor(internal val keyExpr: String, internal var jn * operations on it, but without the inner optimizations. */ override fun undeclare() { - jniKeyExpr?.close() - jniKeyExpr = null + // Frees the owned native key-expression handle. + flat.close() } override fun into(): Selector = Selector(this) @@ -178,10 +187,10 @@ class KeyExpr internal constructor(internal val keyExpr: String, internal var jn other as KeyExpr - return keyExpr == other.keyExpr + return keyExprString == other.keyExprString } override fun hashCode(): Int { - return keyExpr.hashCode() + return keyExprString.hashCode() } } diff --git a/zenoh-java/src/commonMain/kotlin/io/zenoh/keyexpr/SetIntersectionLevel.kt b/zenoh-java/src/commonMain/kotlin/io/zenoh/keyexpr/SetIntersectionLevel.kt index 981cc4b1..56c7e3ed 100644 --- a/zenoh-java/src/commonMain/kotlin/io/zenoh/keyexpr/SetIntersectionLevel.kt +++ b/zenoh-java/src/commonMain/kotlin/io/zenoh/keyexpr/SetIntersectionLevel.kt @@ -19,13 +19,14 @@ package io.zenoh.keyexpr * * Note that [EQUALS] implies [INCLUDES], which itself implies [INTERSECTS]. */ -enum class SetIntersectionLevel(internal val value: Int) { - DISJOINT(0), - INTERSECTS(1), - INCLUDES(2), - EQUALS(3); +enum class SetIntersectionLevel(internal val jni: io.zenoh.jni.keyexpr.SetIntersectionLevel) { + DISJOINT(io.zenoh.jni.keyexpr.SetIntersectionLevel.DISJOINT), + INTERSECTS(io.zenoh.jni.keyexpr.SetIntersectionLevel.INTERSECTS), + INCLUDES(io.zenoh.jni.keyexpr.SetIntersectionLevel.INCLUDES), + EQUALS(io.zenoh.jni.keyexpr.SetIntersectionLevel.EQUALS); companion object { - internal fun fromInt(value: Int) = SetIntersectionLevel.entries.first { it.value == value } + internal fun fromJni(jni: io.zenoh.jni.keyexpr.SetIntersectionLevel): SetIntersectionLevel = + entries.first { it.jni == jni } } } diff --git a/zenoh-java/src/commonMain/kotlin/io/zenoh/liveliness/Liveliness.kt b/zenoh-java/src/commonMain/kotlin/io/zenoh/liveliness/Liveliness.kt index 96711557..5bd332df 100644 --- a/zenoh-java/src/commonMain/kotlin/io/zenoh/liveliness/Liveliness.kt +++ b/zenoh-java/src/commonMain/kotlin/io/zenoh/liveliness/Liveliness.kt @@ -15,11 +15,13 @@ package io.zenoh.liveliness import io.zenoh.Session +import io.zenoh.replyCallbackOf +import io.zenoh.sampleCallbackOf import io.zenoh.exceptions.ZError +import io.zenoh.exceptions.throwZError import io.zenoh.handlers.BlockingQueueHandler import io.zenoh.handlers.Callback import io.zenoh.handlers.Handler -import io.zenoh.jni.JNILiveliness import io.zenoh.keyexpr.KeyExpr import io.zenoh.pubsub.CallbackSubscriber import io.zenoh.pubsub.HandlerSubscriber @@ -48,8 +50,8 @@ class Liveliness internal constructor(private val session: Session) { */ @Throws(ZError::class) fun declareToken(keyExpr: KeyExpr): LivelinessToken { - val jniSession = session.jniSession ?: throw Session.sessionClosedException - return JNILiveliness.declareToken(jniSession, keyExpr) + val zSession = session.zSession ?: throw Session.sessionClosedException + return LivelinessToken(zSession.livelinessDeclareToken(keyExpr.cloneFlat(), throwZError)) } /** @@ -64,16 +66,16 @@ class Liveliness internal constructor(private val session: Session) { keyExpr: KeyExpr, timeout: Duration = Duration.ofMillis(10000), ): BlockingQueue> { - val jniSession = session.jniSession ?: throw Session.sessionClosedException + val zSession = session.zSession ?: throw Session.sessionClosedException val handler = BlockingQueueHandler(LinkedBlockingDeque()) - return JNILiveliness.get( - jniSession, - keyExpr, - handler::handle, - receiver = handler.receiver(), - timeout, - onClose = handler::onClose + zSession.livelinessGet( + keyExpr.flat, + timeout.toMillis(), + replyCallbackOf { handler.handle(it) }, + { handler.onClose() }, + throwZError ) + return handler.receiver() } /** @@ -88,8 +90,14 @@ class Liveliness internal constructor(private val session: Session) { fun get( keyExpr: KeyExpr, callback: Callback, timeout: Duration = Duration.ofMillis(10000) ) { - val jniSession = session.jniSession ?: throw Session.sessionClosedException - return JNILiveliness.get(jniSession, keyExpr, callback, Unit, timeout, {}) + val zSession = session.zSession ?: throw Session.sessionClosedException + zSession.livelinessGet( + keyExpr.flat, + timeout.toMillis(), + replyCallbackOf { callback.run(it) }, + { }, + throwZError + ) } /** @@ -105,16 +113,15 @@ class Liveliness internal constructor(private val session: Session) { fun get( keyExpr: KeyExpr, handler: Handler, timeout: Duration = Duration.ofMillis(10000) ): R { - val jniSession = session.jniSession ?: throw Session.sessionClosedException - val callback = handler::handle - return JNILiveliness.get( - jniSession, - keyExpr, - callback, - handler.receiver(), - timeout, - onClose = handler::onClose + val zSession = session.zSession ?: throw Session.sessionClosedException + zSession.livelinessGet( + keyExpr.flat, + timeout.toMillis(), + replyCallbackOf { handler.handle(it) }, + { handler.onClose() }, + throwZError ) + return handler.receiver() } /** @@ -130,15 +137,15 @@ class Liveliness internal constructor(private val session: Session) { options: LivelinessSubscriberOptions = LivelinessSubscriberOptions() ): HandlerSubscriber>> { val handler = BlockingQueueHandler(LinkedBlockingDeque()) - val jniSession = session.jniSession ?: throw Session.sessionClosedException - return JNILiveliness.declareSubscriber( - jniSession, - keyExpr, - handler::handle, - handler.receiver(), + val zSession = session.zSession ?: throw Session.sessionClosedException + val zSubscriber = zSession.livelinessDeclareSubscriber( + keyExpr.cloneFlat(), options.history, - handler::onClose + sampleCallbackOf { handler.handle(it) }, + { handler.onClose() }, + throwZError ) + return HandlerSubscriber(keyExpr, zSubscriber, handler.receiver()) } /** @@ -155,14 +162,15 @@ class Liveliness internal constructor(private val session: Session) { callback: Callback, options: LivelinessSubscriberOptions = LivelinessSubscriberOptions() ): CallbackSubscriber { - val jniSession = session.jniSession ?: throw Session.sessionClosedException - return JNILiveliness.declareSubscriber( - jniSession, - keyExpr, - callback, + val zSession = session.zSession ?: throw Session.sessionClosedException + val zSubscriber = zSession.livelinessDeclareSubscriber( + keyExpr.cloneFlat(), options.history, - fun() {} + sampleCallbackOf { callback.run(it) }, + { }, + throwZError ) + return CallbackSubscriber(keyExpr, zSubscriber) } /** @@ -180,15 +188,15 @@ class Liveliness internal constructor(private val session: Session) { handler: Handler, options: LivelinessSubscriberOptions = LivelinessSubscriberOptions() ): HandlerSubscriber { - val jniSession = session.jniSession ?: throw Session.sessionClosedException - return JNILiveliness.declareSubscriber( - jniSession, - keyExpr, - handler::handle, - handler.receiver(), + val zSession = session.zSession ?: throw Session.sessionClosedException + val zSubscriber = zSession.livelinessDeclareSubscriber( + keyExpr.cloneFlat(), options.history, - handler::onClose + sampleCallbackOf { handler.handle(it) }, + { handler.onClose() }, + throwZError ) + return HandlerSubscriber(keyExpr, zSubscriber, handler.receiver()) } } diff --git a/zenoh-java/src/commonMain/kotlin/io/zenoh/liveliness/LivelinessToken.kt b/zenoh-java/src/commonMain/kotlin/io/zenoh/liveliness/LivelinessToken.kt index 5d95b8fa..194b8c16 100644 --- a/zenoh-java/src/commonMain/kotlin/io/zenoh/liveliness/LivelinessToken.kt +++ b/zenoh-java/src/commonMain/kotlin/io/zenoh/liveliness/LivelinessToken.kt @@ -14,7 +14,6 @@ package io.zenoh.liveliness -import io.zenoh.jni.JNILivelinessToken import io.zenoh.session.SessionDeclaration /** @@ -29,14 +28,14 @@ import io.zenoh.session.SessionDeclaration * * Liveliness tokens are automatically undeclared when dropped. */ -class LivelinessToken internal constructor(private var jniLivelinessToken: JNILivelinessToken?): SessionDeclaration, AutoCloseable { +class LivelinessToken internal constructor(private var token: io.zenoh.jni.liveliness.LivelinessToken?): SessionDeclaration, AutoCloseable { /** * Undeclares the token. */ override fun undeclare() { - jniLivelinessToken?.undeclare() - jniLivelinessToken = null + token?.close() + token = null } /** diff --git a/zenoh-java/src/commonMain/kotlin/io/zenoh/pubsub/Publisher.kt b/zenoh-java/src/commonMain/kotlin/io/zenoh/pubsub/Publisher.kt index d0a9de2e..523b1cae 100644 --- a/zenoh-java/src/commonMain/kotlin/io/zenoh/pubsub/Publisher.kt +++ b/zenoh-java/src/commonMain/kotlin/io/zenoh/pubsub/Publisher.kt @@ -19,7 +19,8 @@ import io.zenoh.bytes.Encoding import io.zenoh.bytes.IntoZBytes import io.zenoh.bytes.ZBytes import io.zenoh.exceptions.ZError -import io.zenoh.jni.JNIPublisher +import io.zenoh.exceptions.throwZError +import io.zenoh.jni.pubsub.Publisher as JniPublisher import io.zenoh.keyexpr.KeyExpr import io.zenoh.qos.CongestionControl import io.zenoh.qos.Priority @@ -63,7 +64,7 @@ class Publisher internal constructor( private var congestionControl: CongestionControl, private var priority: Priority, val encoding: Encoding, - private var jniPublisher: JNIPublisher?, + private var zPublisher: JniPublisher?, ) : SessionDeclaration, AutoCloseable { companion object { @@ -79,13 +80,13 @@ class Publisher internal constructor( /** Performs a PUT operation on the specified [keyExpr] with the specified [payload]. */ @Throws(ZError::class) fun put(payload: IntoZBytes) { - jniPublisher?.put(payload, encoding, null) ?: throw publisherNotValid + performPut(payload, encoding, null) } /** Performs a PUT operation on the specified [keyExpr] with the specified [payload]. */ @Throws(ZError::class) fun put(payload: IntoZBytes, options: PutOptions) { - jniPublisher?.put(payload, options.encoding ?: this.encoding, options.attachment) ?: throw publisherNotValid + performPut(payload, options.encoding ?: this.encoding, options.attachment) } /** Performs a PUT operation on the specified [keyExpr] with the specified [payload]. */ @@ -102,14 +103,15 @@ class Publisher internal constructor( @JvmOverloads @Throws(ZError::class) fun delete(options: DeleteOptions = DeleteOptions()) { - jniPublisher?.delete(options.attachment) ?: throw(publisherNotValid) + val p = zPublisher ?: throw publisherNotValid + p.delete(options.attachment?.into()?.bytes, throwZError) } /** * Returns `true` if the publisher is still running. */ fun isValid(): Boolean { - return jniPublisher != null + return zPublisher != null } override fun close() { @@ -117,12 +119,25 @@ class Publisher internal constructor( } override fun undeclare() { - jniPublisher?.close() - jniPublisher = null + zPublisher?.close() + zPublisher = null } @Suppress("removal") protected fun finalize() { - jniPublisher?.close() + zPublisher?.close() + } + + @Throws(ZError::class) + private fun performPut(payload: IntoZBytes, encoding: Encoding, attachment: IntoZBytes?) { + val p = zPublisher ?: throw publisherNotValid + p.put( + payload.into().bytes, + true, + encoding.idForWire(), + encoding.schemaForWire(), + attachment?.into()?.bytes, + throwZError, + ) } } diff --git a/zenoh-java/src/commonMain/kotlin/io/zenoh/pubsub/Subscriber.kt b/zenoh-java/src/commonMain/kotlin/io/zenoh/pubsub/Subscriber.kt index a3688303..874a28ab 100644 --- a/zenoh-java/src/commonMain/kotlin/io/zenoh/pubsub/Subscriber.kt +++ b/zenoh-java/src/commonMain/kotlin/io/zenoh/pubsub/Subscriber.kt @@ -15,7 +15,6 @@ package io.zenoh.pubsub import io.zenoh.handlers.BlockingQueueHandler -import io.zenoh.jni.JNISubscriber import io.zenoh.keyexpr.KeyExpr import io.zenoh.session.SessionDeclaration @@ -63,16 +62,16 @@ import io.zenoh.session.SessionDeclaration * ``` */ sealed class Subscriber( - val keyExpr: KeyExpr, private var jniSubscriber: JNISubscriber? + val keyExpr: KeyExpr, private var zSubscriber: io.zenoh.jni.pubsub.Subscriber? ) : AutoCloseable, SessionDeclaration { fun isValid(): Boolean { - return jniSubscriber != null + return zSubscriber != null } override fun undeclare() { - jniSubscriber?.close() - jniSubscriber = null + zSubscriber?.close() + zSubscriber = null } override fun close() { @@ -80,7 +79,7 @@ sealed class Subscriber( } protected fun finalize() { - jniSubscriber?.close() + zSubscriber?.close() } } @@ -94,7 +93,7 @@ sealed class Subscriber( * } * ``` */ -class CallbackSubscriber internal constructor(keyExpr: KeyExpr, jniSubscriber: JNISubscriber?): Subscriber(keyExpr, jniSubscriber) +class CallbackSubscriber internal constructor(keyExpr: KeyExpr, zSubscriber: io.zenoh.jni.pubsub.Subscriber?): Subscriber(keyExpr, zSubscriber) /** * Subscriber using a [io.zenoh.handlers.Handler] for handling incoming samples. @@ -119,4 +118,4 @@ class CallbackSubscriber internal constructor(keyExpr: KeyExpr, jniSubscriber: J * @param R The type of the receiver. * @param receiver The receiver of the subscriber's handler. */ -class HandlerSubscriber internal constructor(keyExpr: KeyExpr, jniSubscriber: JNISubscriber?, val receiver: R): Subscriber(keyExpr, jniSubscriber) +class HandlerSubscriber internal constructor(keyExpr: KeyExpr, zSubscriber: io.zenoh.jni.pubsub.Subscriber?, val receiver: R): Subscriber(keyExpr, zSubscriber) diff --git a/zenoh-java/src/commonMain/kotlin/io/zenoh/qos/CongestionControl.kt b/zenoh-java/src/commonMain/kotlin/io/zenoh/qos/CongestionControl.kt index 82b3463a..4a45f8cb 100644 --- a/zenoh-java/src/commonMain/kotlin/io/zenoh/qos/CongestionControl.kt +++ b/zenoh-java/src/commonMain/kotlin/io/zenoh/qos/CongestionControl.kt @@ -15,20 +15,30 @@ package io.zenoh.qos /** The congestion control to be applied when routing the data. */ -enum class CongestionControl (val value: Int) { +enum class CongestionControl (internal val jni: io.zenoh.jni.qos.CongestionControl) { /** * Allows the message to be dropped if all buffers are full. */ - DROP(0), + DROP(io.zenoh.jni.qos.CongestionControl.DROP), /** * 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(io.zenoh.jni.qos.CongestionControl.BLOCK), + + /** + * Blocks low-priority traffic first, then drops when needed. + */ + BLOCK_FIRST(io.zenoh.jni.qos.CongestionControl.BLOCK_FIRST); + + val value: Int + get() = jni.value companion object { fun fromInt(value: Int) = entries.first { it.value == value } + + internal fun fromJni(jni: io.zenoh.jni.qos.CongestionControl): CongestionControl = fromInt(jni.value) } } diff --git a/zenoh-java/src/commonMain/kotlin/io/zenoh/qos/Priority.kt b/zenoh-java/src/commonMain/kotlin/io/zenoh/qos/Priority.kt index 0e27780e..4ba4c7e7 100644 --- a/zenoh-java/src/commonMain/kotlin/io/zenoh/qos/Priority.kt +++ b/zenoh-java/src/commonMain/kotlin/io/zenoh/qos/Priority.kt @@ -22,16 +22,21 @@ package io.zenoh.qos * - Highest priority: 1 ([REALTIME]) * - Lowest priority: 7 ([BACKGROUND]) */ -enum class Priority(val value: Int) { - REALTIME(1), - INTERACTIVE_HIGH(2), - INTERACTIVE_LOW(3), - DATA_HIGH(4), - DATA(5), - DATA_LOW(6), - BACKGROUND(7); +enum class Priority(internal val jni: io.zenoh.jni.qos.Priority) { + REALTIME(io.zenoh.jni.qos.Priority.REAL_TIME), + INTERACTIVE_HIGH(io.zenoh.jni.qos.Priority.INTERACTIVE_HIGH), + INTERACTIVE_LOW(io.zenoh.jni.qos.Priority.INTERACTIVE_LOW), + DATA_HIGH(io.zenoh.jni.qos.Priority.DATA_HIGH), + DATA(io.zenoh.jni.qos.Priority.DATA), + DATA_LOW(io.zenoh.jni.qos.Priority.DATA_LOW), + BACKGROUND(io.zenoh.jni.qos.Priority.BACKGROUND); + + val value: Int + get() = jni.value companion object { fun fromInt(value: Int) = entries.first { it.value == value } + + internal fun fromJni(jni: io.zenoh.jni.qos.Priority): Priority = fromInt(jni.value) } } diff --git a/zenoh-java/src/commonMain/kotlin/io/zenoh/qos/Reliability.kt b/zenoh-java/src/commonMain/kotlin/io/zenoh/qos/Reliability.kt index d574f27c..fe009d62 100644 --- a/zenoh-java/src/commonMain/kotlin/io/zenoh/qos/Reliability.kt +++ b/zenoh-java/src/commonMain/kotlin/io/zenoh/qos/Reliability.kt @@ -36,4 +36,17 @@ enum class Reliability { * this reliability requirement may be infringed to prevent slow readers from blocking the network. */ RELIABLE, + + ; + + internal val jni: io.zenoh.jni.qos.Reliability + get() = when (this) { + BEST_EFFORT -> io.zenoh.jni.qos.Reliability.BEST_EFFORT + RELIABLE -> io.zenoh.jni.qos.Reliability.RELIABLE + } + + companion object { + internal fun fromJni(jni: io.zenoh.jni.qos.Reliability): Reliability = + entries.first { it.jni == jni } + } } diff --git a/zenoh-java/src/commonMain/kotlin/io/zenoh/query/ConsolidationMode.kt b/zenoh-java/src/commonMain/kotlin/io/zenoh/query/ConsolidationMode.kt index f4c1b29c..7a0797d7 100644 --- a/zenoh-java/src/commonMain/kotlin/io/zenoh/query/ConsolidationMode.kt +++ b/zenoh-java/src/commonMain/kotlin/io/zenoh/query/ConsolidationMode.kt @@ -36,3 +36,10 @@ enum class ConsolidationMode { /** Holds back samples to only send the set of samples that had the highest timestamp for their key. */ LATEST; } + +internal fun ConsolidationMode.toFlat(): io.zenoh.jni.query.ConsolidationMode = when (this) { + ConsolidationMode.AUTO -> io.zenoh.jni.query.ConsolidationMode.AUTO + ConsolidationMode.NONE -> io.zenoh.jni.query.ConsolidationMode.NONE + ConsolidationMode.MONOTONIC -> io.zenoh.jni.query.ConsolidationMode.MONOTONIC + ConsolidationMode.LATEST -> io.zenoh.jni.query.ConsolidationMode.LATEST +} diff --git a/zenoh-java/src/commonMain/kotlin/io/zenoh/query/Querier.kt b/zenoh-java/src/commonMain/kotlin/io/zenoh/query/Querier.kt index 541d056b..37945776 100644 --- a/zenoh-java/src/commonMain/kotlin/io/zenoh/query/Querier.kt +++ b/zenoh-java/src/commonMain/kotlin/io/zenoh/query/Querier.kt @@ -15,14 +15,15 @@ package io.zenoh.query import io.zenoh.annotations.Unstable +import io.zenoh.replyCallbackOf import io.zenoh.bytes.Encoding import io.zenoh.bytes.IntoZBytes import io.zenoh.bytes.ZBytes import io.zenoh.exceptions.ZError +import io.zenoh.exceptions.throwZError import io.zenoh.handlers.BlockingQueueHandler import io.zenoh.handlers.Callback import io.zenoh.handlers.Handler -import io.zenoh.jni.JNIQuerier import io.zenoh.keyexpr.KeyExpr import io.zenoh.qos.CongestionControl import io.zenoh.qos.Priority @@ -54,7 +55,7 @@ import java.util.concurrent.LinkedBlockingDeque * @param keyExpr The [KeyExpr] of the querier. * @param qos The [QoS] configuration of the querier. */ -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 zQuerier: io.zenoh.jni.query.Querier?) : SessionDeclaration, AutoCloseable { /** @@ -78,7 +79,7 @@ class Querier internal constructor(val keyExpr: KeyExpr, val qos: QoS, private v options: GetOptions ): BlockingQueue> { val handler = BlockingQueueHandler(LinkedBlockingDeque()) - return resolveGetWithHandler(keyExpr, handler, options) + return resolveGetWithHandler(handler, options) } /** @@ -93,7 +94,7 @@ class Querier internal constructor(val keyExpr: KeyExpr, val qos: QoS, private v callback: Callback, options: GetOptions ) { - resolveGetWithCallback(keyExpr, callback, options) + resolveGetWithCallback(callback, options) } /** @@ -108,7 +109,7 @@ class Querier internal constructor(val keyExpr: KeyExpr, val qos: QoS, private v handler: Handler, options: GetOptions ): R { - return resolveGetWithHandler(keyExpr, handler, options) + return resolveGetWithHandler(handler, options) } /** @@ -126,8 +127,8 @@ class Querier internal constructor(val keyExpr: KeyExpr, val qos: QoS, private v * performed on it will fail. */ override fun undeclare() { - jniQuerier?.close() - jniQuerier = null + zQuerier?.close() + zQuerier = null } /** @@ -142,12 +143,37 @@ class Querier internal constructor(val keyExpr: KeyExpr, val qos: QoS, private v undeclare() } - private fun resolveGetWithCallback(keyExpr: KeyExpr, callback: Callback, options: GetOptions) { - jniQuerier?.performGetWithCallback(keyExpr, callback, options) ?: throw ZError("Querier is not valid.") + private fun resolveGetWithCallback(callback: Callback, options: GetOptions) { + val q = zQuerier ?: throw ZError("Querier is not valid.") + val enc = options.encoding ?: Encoding.defaultEncoding() + q.get( + options.parameters?.toString(), + options.payload?.into()?.bytes, + true, + enc.idForWire(), + enc.schemaForWire(), + options.attachment?.into()?.bytes, + replyCallbackOf { callback.run(it) }, + { }, + throwZError + ) } - private fun resolveGetWithHandler(keyExpr: KeyExpr, handler: Handler, options: GetOptions): R { - return jniQuerier?.performGetWithHandler(keyExpr, handler, options) ?: throw ZError("Querier is not valid.") + private fun resolveGetWithHandler(handler: Handler, options: GetOptions): R { + val q = zQuerier ?: throw ZError("Querier is not valid.") + val enc = options.encoding ?: Encoding.defaultEncoding() + q.get( + options.parameters?.toString(), + options.payload?.into()?.bytes, + true, + enc.idForWire(), + enc.schemaForWire(), + options.attachment?.into()?.bytes, + replyCallbackOf { handler.handle(it) }, + { handler.onClose() }, + throwZError + ) + return handler.receiver() } } diff --git a/zenoh-java/src/commonMain/kotlin/io/zenoh/query/Query.kt b/zenoh-java/src/commonMain/kotlin/io/zenoh/query/Query.kt index 3a8184da..e523bf8c 100644 --- a/zenoh-java/src/commonMain/kotlin/io/zenoh/query/Query.kt +++ b/zenoh-java/src/commonMain/kotlin/io/zenoh/query/Query.kt @@ -19,11 +19,8 @@ import io.zenoh.bytes.Encoding import io.zenoh.bytes.IntoZBytes import io.zenoh.bytes.ZBytes import io.zenoh.exceptions.ZError -import io.zenoh.jni.JNIQuery +import io.zenoh.exceptions.throwZError import io.zenoh.keyexpr.KeyExpr -import io.zenoh.qos.QoS -import io.zenoh.sample.Sample -import io.zenoh.sample.SampleKind /** * Represents a Zenoh Query in Kotlin. @@ -44,12 +41,45 @@ class Query internal constructor( val encoding: Encoding?, val attachment: ZBytes?, val acceptsReplies: ReplyKeyExpr, - private var jniQuery: JNIQuery? + private var zQuery: io.zenoh.jni.query.Query? ) : AutoCloseable, ZenohType { /** Shortcut to the [selector]'s parameters. */ val parameters = selector.parameters + internal companion object { + /** + * Builds an SDK [Query] from a queryable callback's natively-decomposed + * leaves (delivered in ONE JNI crossing as raw handles + primitives — + * heavy data is read lazily on demand). `zq` is the owned query + * handle, **retained** because the reply methods consume it (replying + * keeps working after the callback returns). + */ + fun fromParts( + keH: io.zenoh.jni.keyexpr.KeyExpr, + parameters: String, + payloadH: io.zenoh.jni.bytes.ZBytes?, + encH: io.zenoh.jni.bytes.Encoding?, + encId: Int?, + attachH: io.zenoh.jni.bytes.ZBytes?, + acceptsRepliesInt: Int, + zq: io.zenoh.jni.query.Query, + ): Query { + val ke = KeyExpr(keH) + val selector = if (parameters.isEmpty()) Selector(ke) + else Selector(ke, Parameters.from(parameters)) + return Query( + ke, + selector, + payloadH?.let { ZBytes.fromHandle(it) }, + encH?.let { Encoding.fromParts(it, encId!!) }, + attachH?.let { ZBytes.fromHandle(it) }, + io.zenoh.jni.query.ReplyKeyExpr.fromInt(acceptsRepliesInt).toPublic(), + zq + ) + } + } + /** * Reply to the specified key expression. * @@ -61,19 +91,23 @@ class Query internal constructor( @Throws(ZError::class) @JvmOverloads fun reply(keyExpr: KeyExpr, payload: IntoZBytes, options: ReplyOptions = ReplyOptions()) { - val sample = Sample( - keyExpr, - payload.into(), - options.encoding, - SampleKind.PUT, - options.timeStamp, - QoS(options.congestionControl, options.priority, options.express), - options.attachment?.into() + val q = zQuery ?: throw ZError("Query is invalid") + q.replySuccess( + keyExpr.flat, + payload.into().bytes, + true, + options.encoding.idForWire(), + options.encoding.schemaForWire(), + options.timeStamp?.ntpValue(), + options.attachment?.into()?.bytes, + options.express, + throwZError ) - jniQuery?.apply { - replySuccess(sample) - jniQuery = null - } ?: throw (ZError("Query is invalid")) + // 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() + zQuery = null } /** @@ -98,15 +132,16 @@ class Query internal constructor( @JvmOverloads @Throws(ZError::class) fun replyDel(keyExpr: KeyExpr, options: ReplyDelOptions = ReplyDelOptions()) { - jniQuery?.apply { - replyDelete( - keyExpr, - options.timeStamp, - options.attachment, - QoS(options.congestionControl, options.priority, options.express), - ) - jniQuery = null - } ?: throw (ZError("Query is invalid")) + val q = zQuery ?: throw ZError("Query is invalid") + q.replyDelete( + keyExpr.flat, + options.timeStamp?.ntpValue(), + options.attachment?.into()?.bytes, + options.express, + throwZError + ) + q.close() + zQuery = null } /** @@ -118,10 +153,10 @@ class Query internal constructor( @JvmOverloads @Throws(ZError::class) fun replyErr(message: IntoZBytes, options: ReplyErrOptions = ReplyErrOptions()) { - jniQuery?.apply { - replyError(message.into(), options.encoding) - jniQuery = null - } ?: throw (ZError("Query is invalid")) + val q = zQuery ?: throw ZError("Query is invalid") + q.replyError(message.into().bytes, true, options.encoding.idForWire(), options.encoding.schemaForWire(), throwZError) + q.close() + zQuery = null } /** @@ -135,10 +170,8 @@ class Query internal constructor( fun replyErr(message: String, options: ReplyErrOptions = ReplyErrOptions()) = replyErr(ZBytes.from(message), options) override fun close() { - jniQuery?.apply { - this.close() - jniQuery = null - } + zQuery?.close() + zQuery = null } @Suppress("removal") diff --git a/zenoh-java/src/commonMain/kotlin/io/zenoh/query/QueryTarget.kt b/zenoh-java/src/commonMain/kotlin/io/zenoh/query/QueryTarget.kt index 2711f6f6..9261c835 100644 --- a/zenoh-java/src/commonMain/kotlin/io/zenoh/query/QueryTarget.kt +++ b/zenoh-java/src/commonMain/kotlin/io/zenoh/query/QueryTarget.kt @@ -33,3 +33,9 @@ enum class QueryTarget { ALL_COMPLETE; } +internal fun QueryTarget.toFlat(): io.zenoh.jni.query.QueryTarget = when (this) { + QueryTarget.BEST_MATCHING -> io.zenoh.jni.query.QueryTarget.BEST_MATCHING + QueryTarget.ALL -> io.zenoh.jni.query.QueryTarget.ALL + QueryTarget.ALL_COMPLETE -> io.zenoh.jni.query.QueryTarget.ALL_COMPLETE +} + diff --git a/zenoh-java/src/commonMain/kotlin/io/zenoh/query/Queryable.kt b/zenoh-java/src/commonMain/kotlin/io/zenoh/query/Queryable.kt index f8159cd9..d5e2909e 100644 --- a/zenoh-java/src/commonMain/kotlin/io/zenoh/query/Queryable.kt +++ b/zenoh-java/src/commonMain/kotlin/io/zenoh/query/Queryable.kt @@ -16,7 +16,6 @@ package io.zenoh.query import io.zenoh.handlers.BlockingQueueHandler import io.zenoh.handlers.Handler -import io.zenoh.jni.JNIQueryable import io.zenoh.keyexpr.KeyExpr import io.zenoh.session.SessionDeclaration @@ -55,19 +54,19 @@ import io.zenoh.session.SessionDeclaration * @see HandlerQueryable */ sealed class Queryable( - val keyExpr: KeyExpr, private var jniQueryable: JNIQueryable? + val keyExpr: KeyExpr, private var zQueryable: io.zenoh.jni.query.Queryable? ) : AutoCloseable, SessionDeclaration { fun isValid(): Boolean { - return jniQueryable != null + return zQueryable != null } /** * Undeclares the queryable. */ override fun undeclare() { - jniQueryable?.close() - jniQueryable = null + zQueryable?.close() + zQueryable = null } /** @@ -79,7 +78,7 @@ sealed class Queryable( } protected fun finalize() { - jniQueryable?.close() + zQueryable?.close() } } @@ -93,7 +92,7 @@ sealed class Queryable( * } * ``` */ -class CallbackQueryable internal constructor(keyExpr: KeyExpr, jniQueryable: JNIQueryable?): Queryable(keyExpr, jniQueryable) +class CallbackQueryable internal constructor(keyExpr: KeyExpr, zQueryable: io.zenoh.jni.query.Queryable?): Queryable(keyExpr, zQueryable) /** * [Queryable] receiving replies through a [Handler]. @@ -117,7 +116,7 @@ class CallbackQueryable internal constructor(keyExpr: KeyExpr, jniQueryable: JNI * @param R The type of the handler's receiver. * @param receiver The receiver of the queryable's handler. */ -class HandlerQueryable internal constructor(keyExpr: KeyExpr, jniQueryable: JNIQueryable?, val receiver: R): Queryable(keyExpr, jniQueryable) +class HandlerQueryable internal constructor(keyExpr: KeyExpr, zQueryable: io.zenoh.jni.query.Queryable?, val receiver: R): Queryable(keyExpr, zQueryable) /** * Options for configuring a [Queryable]. diff --git a/zenoh-java/src/commonMain/kotlin/io/zenoh/query/ReplyKeyExpr.kt b/zenoh-java/src/commonMain/kotlin/io/zenoh/query/ReplyKeyExpr.kt index 08591819..2cc2dfc8 100644 --- a/zenoh-java/src/commonMain/kotlin/io/zenoh/query/ReplyKeyExpr.kt +++ b/zenoh-java/src/commonMain/kotlin/io/zenoh/query/ReplyKeyExpr.kt @@ -32,3 +32,13 @@ enum class ReplyKeyExpr { */ ANY; } + +internal fun io.zenoh.jni.query.ReplyKeyExpr.toPublic(): ReplyKeyExpr = when (this) { + io.zenoh.jni.query.ReplyKeyExpr.ANY -> ReplyKeyExpr.ANY + io.zenoh.jni.query.ReplyKeyExpr.MATCHING_QUERY -> ReplyKeyExpr.MATCHING_QUERY +} + +internal fun ReplyKeyExpr.toFlat(): io.zenoh.jni.query.ReplyKeyExpr = when (this) { + ReplyKeyExpr.MATCHING_QUERY -> io.zenoh.jni.query.ReplyKeyExpr.MATCHING_QUERY + ReplyKeyExpr.ANY -> io.zenoh.jni.query.ReplyKeyExpr.ANY +} diff --git a/zenoh-java/src/commonMain/kotlin/io/zenoh/sample/Sample.kt b/zenoh-java/src/commonMain/kotlin/io/zenoh/sample/Sample.kt index 216d67e0..86476eda 100644 --- a/zenoh-java/src/commonMain/kotlin/io/zenoh/sample/Sample.kt +++ b/zenoh-java/src/commonMain/kotlin/io/zenoh/sample/Sample.kt @@ -16,6 +16,8 @@ package io.zenoh.sample import io.zenoh.ZenohType import io.zenoh.qos.QoS +import io.zenoh.qos.CongestionControl +import io.zenoh.qos.Priority import io.zenoh.keyexpr.KeyExpr import io.zenoh.bytes.Encoding import io.zenoh.bytes.ZBytes @@ -48,4 +50,45 @@ data class Sample( val express = qos.express val congestionControl = qos.congestionControl val priority = qos.priority + + internal companion object { + /** + * Builds an SDK [Sample] from the 14 leaves of the ZSample canonical + * output — the lambda parameter list every decomposed sample delivery + * uses (the `zReplySample` builder and the subscriber/liveliness + * callbacks), in record order. The whole graph arrives in ONE JNI + * crossing; the [KeyExpr] retains the delivered handle leaf. The + * trailing `reliability` / `source*` leaves are part of the generated + * decomposition but are not surfaced on the public [Sample] type. + */ + @Suppress("UNUSED_PARAMETER") + fun fromParts( + keH: io.zenoh.jni.keyexpr.KeyExpr, + payloadH: io.zenoh.jni.bytes.ZBytes, + encH: io.zenoh.jni.bytes.Encoding, + encId: Int, + kindInt: Int, + ntp64: Long?, + express: Boolean, + prioInt: Int, + ccInt: Int, + attachH: io.zenoh.jni.bytes.ZBytes?, + reliabilityInt: Int, + sourceZid: io.zenoh.jni.config.ZenohId?, + sourceEid: Int, + sourceSn: Long, + ): Sample = Sample( + KeyExpr(keH), + ZBytes.fromHandle(payloadH), + Encoding.fromParts(encH, encId), + io.zenoh.jni.sample.SampleKind.fromInt(kindInt).toPublic(), + ntp64?.let { TimeStamp(it) }, + QoS( + CongestionControl.fromJni(io.zenoh.jni.qos.CongestionControl.fromInt(ccInt)), + Priority.fromJni(io.zenoh.jni.qos.Priority.fromInt(prioInt)), + express + ), + attachH?.let { ZBytes.fromHandle(it) } + ) + } } diff --git a/zenoh-java/src/commonMain/kotlin/io/zenoh/sample/SampleKind.kt b/zenoh-java/src/commonMain/kotlin/io/zenoh/sample/SampleKind.kt index cea02a02..488450c6 100644 --- a/zenoh-java/src/commonMain/kotlin/io/zenoh/sample/SampleKind.kt +++ b/zenoh-java/src/commonMain/kotlin/io/zenoh/sample/SampleKind.kt @@ -23,3 +23,8 @@ enum class SampleKind { fun fromInt(value: Int) = entries.first { it.ordinal == value } } } + +internal fun io.zenoh.jni.sample.SampleKind.toPublic(): SampleKind = when (this) { + io.zenoh.jni.sample.SampleKind.PUT -> SampleKind.PUT + io.zenoh.jni.sample.SampleKind.DELETE -> SampleKind.DELETE +} diff --git a/zenoh-java/src/commonMain/kotlin/io/zenoh/scouting/Scout.kt b/zenoh-java/src/commonMain/kotlin/io/zenoh/scouting/Scout.kt index 8034a851..6b94f66f 100644 --- a/zenoh-java/src/commonMain/kotlin/io/zenoh/scouting/Scout.kt +++ b/zenoh-java/src/commonMain/kotlin/io/zenoh/scouting/Scout.kt @@ -14,7 +14,12 @@ package io.zenoh.scouting -import io.zenoh.jni.JNIScout +import io.zenoh.Config +import io.zenoh.config.WhatAmI +import io.zenoh.config.ZenohId +import io.zenoh.exceptions.ZError +import io.zenoh.exceptions.throwZError +import io.zenoh.handlers.Callback /** * Scout for routers and/or peers. @@ -62,15 +67,46 @@ import io.zenoh.jni.JNIScout * @see HandlerScout */ sealed class Scout ( - private var jniScout: JNIScout? + private var zScout: io.zenoh.jni.scouting.Scout? ) : AutoCloseable { + companion object { + + @Throws(ZError::class) + internal fun scoutWithHandler( + whatAmI: Set, + callback: Callback, + onClose: () -> Unit, + config: Config?, + receiver: R, + ): HandlerScout = HandlerScout(runScout(whatAmI, config, callback, onClose), receiver) + + @Throws(ZError::class) + internal fun scoutWithCallback( + whatAmI: Set, + callback: Callback, + config: Config?, + ): CallbackScout = CallbackScout(runScout(whatAmI, config, callback) {}) + + private fun runScout( + whatAmI: Set, + config: Config?, + callback: Callback, + onClose: () -> Unit, + ): io.zenoh.jni.scouting.Scout { + val bitfield = whatAmI.map { it.jni.value }.reduce { acc, v -> acc or v } + val helloCallback = io.zenoh.helloCallbackOf { callback.run(it) } + val onCloseCallback = { onClose() } + return io.zenoh.jni.scouting.scout(bitfield, config?.zConfig, helloCallback, onCloseCallback, throwZError) + } + } + /** * Stops the scouting. */ fun stop() { - jniScout?.close() - jniScout = null + zScout?.close() + zScout = null } /** @@ -93,7 +129,7 @@ sealed class Scout ( * CallbackScout scout = Zenoh.scout(hello -> {...}); * ``` */ -class CallbackScout internal constructor(jniScout: JNIScout?) : Scout(jniScout) +class CallbackScout internal constructor(zScout: io.zenoh.jni.scouting.Scout) : Scout(zScout) /** * Scout using a handler to handle incoming [Hello] messages. @@ -106,4 +142,4 @@ class CallbackScout internal constructor(jniScout: JNIScout?) : Scout(jniScout) * @param R The type of the receiver. * @param receiver The receiver of the scout's handler. */ -class HandlerScout internal constructor(jniScout: JNIScout?, val receiver: R) : Scout(jniScout) +class HandlerScout internal constructor(zScout: io.zenoh.jni.scouting.Scout, val receiver: R) : Scout(zScout) diff --git a/zenoh-java/src/jvmMain/kotlin/io/zenoh/Target.kt b/zenoh-java/src/jvmMain/kotlin/io/zenoh/Target.kt deleted file mode 100644 index f3f28256..00000000 --- a/zenoh-java/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-java/src/jvmMain/kotlin/io/zenoh/Zenoh.kt b/zenoh-java/src/jvmMain/kotlin/io/zenoh/Zenoh.kt deleted file mode 100644 index 8da47656..00000000 --- a/zenoh-java/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 -} diff --git a/zenoh-java/src/jvmTest/java/io/zenoh/QueryableTest.java b/zenoh-java/src/jvmTest/java/io/zenoh/QueryableTest.java index adb59541..79cdd1c7 100644 --- a/zenoh-java/src/jvmTest/java/io/zenoh/QueryableTest.java +++ b/zenoh-java/src/jvmTest/java/io/zenoh/QueryableTest.java @@ -196,6 +196,23 @@ public void queryReplyErrorTest() throws ZError, InterruptedException { assertEquals(errorMessage, errorReply.getError()); } + /** + * A queryable callback that throws must not crash the JVM: the migrated + * queryable path swallows/logs the exception. (Regression guard for the + * former legacy `on_query` double-free.) + */ + @Test + public void throwingQueryableCallbackDoesNotCrashJvm() throws ZError, InterruptedException { + try (var queryable = session.declareQueryable(testKeyExpr, query -> { + throw new RuntimeException("boom from queryable callback"); + })) { + session.get(testKeyExpr); + Thread.sleep(500); + } + // Reaching here (no JVM abort) means the queryable path handles a + // throwing callback gracefully. + } + @Test public void queryReplyDeleteTest() throws ZError, InterruptedException { var timestamp = TimeStamp.getCurrentTime(); diff --git a/zenoh-java/src/jvmTest/java/io/zenoh/SessionTest.java b/zenoh-java/src/jvmTest/java/io/zenoh/SessionTest.java index 18e3b855..7afc0e07 100644 --- a/zenoh-java/src/jvmTest/java/io/zenoh/SessionTest.java +++ b/zenoh-java/src/jvmTest/java/io/zenoh/SessionTest.java @@ -45,6 +45,19 @@ public void sessionStartCloseTest() throws ZError { assertTrue(session.isClosed()); } + @Test + public void configCanOpenMultipleSessions() throws ZError { + Config config = Config.loadDefault(); + Session sessionA = Zenoh.open(config); + Session sessionB = Zenoh.open(config); + + assertFalse(sessionA.isClosed()); + assertFalse(sessionB.isClosed()); + + sessionA.close(); + sessionB.close(); + } + @Test public void sessionClose_declarationsAreUndeclaredAfterClosingSessionTest() throws ZError, InterruptedException { Session session = Zenoh.open(Config.loadDefault()); diff --git a/zenoh-java/src/jvmTest/java/io/zenoh/SubscriberTest.java b/zenoh-java/src/jvmTest/java/io/zenoh/SubscriberTest.java index 809c4bc0..a899d60b 100644 --- a/zenoh-java/src/jvmTest/java/io/zenoh/SubscriberTest.java +++ b/zenoh-java/src/jvmTest/java/io/zenoh/SubscriberTest.java @@ -34,6 +34,8 @@ import java.util.ArrayList; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; @RunWith(JUnit4.class) public class SubscriberTest { @@ -100,6 +102,54 @@ public void subscriber_runsWithCallback() throws ZError { subscriber.close(); } + /** + * Output (data) expansion smoke test: a received sample's key expression is + * built by the native `zSampleKeyExpr` builder, which delivers BOTH the + * key-expr handle and its string form in one JNI crossing. This asserts + * both leaves arrived intact: + * - the string leaf: {@code keyExpr.toString()} matches the published key; + * - the handle leaf: {@code keyExpr.intersects(...)} runs a native op on + * the delivered handle (the combined-constructor identity arm), which + * would fail if the handle were missing/dangling. + */ + @Test + public void subscriber_sampleKeyExprDeliversHandleAndString() throws ZError { + var receivedSamples = new ArrayList(); + var subscriber = session.declareSubscriber(testKeyExpr, receivedSamples::add); + + session.put(testKeyExpr, ZBytes.from("output-expansion")); + + assertEquals(1, receivedSamples.size()); + KeyExpr recvKe = receivedSamples.get(0).getKeyExpr(); + // String leaf. + assertEquals(testKeyExpr.toString(), recvKe.toString()); + // Handle leaf: native op over the delivered handle. + assertTrue(recvKe.intersects(testKeyExpr)); + + subscriber.close(); + } + + /** + * Output (data) expansion, nullable path: a sample published without a + * timestamp / attachment exercises the `Option<&T>` "None ⇒ null result" + * branch of zSampleTimestamp / zSampleAttachment (builder skipped). The + * default session adds no timestamp, so both must come back null. + */ + @Test + public void subscriber_sampleNullableFieldsAreNull() throws ZError { + var receivedSamples = new ArrayList(); + var subscriber = session.declareSubscriber(testKeyExpr, receivedSamples::add); + + session.put(testKeyExpr, ZBytes.from("no-timestamp")); + + assertEquals(1, receivedSamples.size()); + Sample s = receivedSamples.get(0); + assertNull(s.getTimestamp()); + assertNull(s.getAttachment()); + + subscriber.close(); + } + @Test public void subscriber_runsWithHandler() throws ZError { var handler = new QueueHandler(); diff --git a/zenoh-jni/Cargo.lock b/zenoh-jni/Cargo.lock deleted file mode 100644 index 79468f30..00000000 --- a/zenoh-jni/Cargo.lock +++ /dev/null @@ -1,4404 +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", -] - -[[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.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" -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.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" -dependencies = [ - "libc", -] - -[[package]] -name = "anyhow" -version = "1.0.102" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" - -[[package]] -name = "arc-swap" -version = "1.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a3a1fd6f75306b68087b831f025c712524bcb19aad54e557b1129cfa0a2b207" -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.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56624a96882bb8c26d61312ae18cb45868e5a9992ea73c58e45c3101e56a1e60" -dependencies = [ - "asn1-rs-derive", - "asn1-rs-impl", - "displaydoc", - "nom", - "num-traits", - "rusticata-macros", - "thiserror 2.0.18", - "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", - "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", -] - -[[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.89" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[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.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" - -[[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 = "bitflags" -version = "1.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" - -[[package]] -name = "bitflags" -version = "2.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "843867be96c8daad0d758b57df9392b6d8d271134fce549de6ce169ff98a92af" -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 = "bumpalo" -version = "3.20.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb" - -[[package]] -name = "byteorder" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" - -[[package]] -name = "bytes" -version = "1.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33" - -[[package]] -name = "cc" -version = "1.2.59" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7a4d3ec6524d28a329fc53654bbadc9bdd7b0431f5d65f1a56ffb28a1ee5283" -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.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" - -[[package]] -name = "chrono" -version = "0.4.44" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c673075a2e0e5f4a1dde27ce9dee1ea4558c7ffe648f576438a20ca1d2acc4b0" -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 = "concurrent-queue" -version = "2.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973" -dependencies = [ - "crossbeam-utils", -] - -[[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.35" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7faa7469a93a566e9ccc1c73fe783b4a65c274c5ace346038dca9c39fe0030ad" -dependencies = [ - "const_format_proc_macros", -] - -[[package]] -name = "const_format_proc_macros" -version = "0.2.34" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d57c2eccfb16dbac1f4e61e206105db5820c9d26c3c472bc17c774259ef7744" -dependencies = [ - "proc-macro2", - "quote", - "unicode-xid", -] - -[[package]] -name = "core-foundation" -version = "0.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 = "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.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82b8f8f868b36967f9606790d1903570de9ceaf870a7bf9fbbd3016d636a2cb2" -dependencies = [ - "crossbeam-utils", -] - -[[package]] -name = "crossbeam-deque" -version = "0.8.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51" -dependencies = [ - "crossbeam-epoch", - "crossbeam-utils", -] - -[[package]] -name = "crossbeam-epoch" -version = "0.9.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" -dependencies = [ - "crossbeam-utils", -] - -[[package]] -name = "crossbeam-queue" -version = "0.3.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f58bbc28f91df819d0aa2a2c00cd19754769c2fad90579b3592b1c9ba7a3115" -dependencies = [ - "crossbeam-utils", -] - -[[package]] -name = "crossbeam-utils" -version = "0.8.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" - -[[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", -] - -[[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", -] - -[[package]] -name = "data-encoding" -version = "2.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7a1e2f27636f116493b8b860f5546edb47c8d8f8ea73e1d2a20be88e28d1fea" - -[[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 = [ - "powerfmt", - "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.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[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.15.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" - -[[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.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e13b66accf52311f30a0db42147dadea9850cb48cd070028831ae5f5d4b856ab" -dependencies = [ - "concurrent-queue", - "parking", - "pin-project-lite", -] - -[[package]] -name = "fastbloom" -version = "0.14.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e7f34442dbe69c60fe8eaf58a8cafff81a1f278816d8ab4db255b3bef4ac3c4" -dependencies = [ - "getrandom 0.3.4", - "libm", - "rand 0.9.2", - "siphasher", -] - -[[package]] -name = "fastrand" -version = "2.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f1f227452a390804cdb637b74a86990f2a7d7ba4b7d5693aac9b4dd6defd8d6" - -[[package]] -name = "find-msvc-tools" -version = "0.1.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" - -[[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.8", -] - -[[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.8", -] - -[[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.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b147ee9d1f6d097cef9ce628cd2ee62288d963e16fb287bd9286455b241382d" -dependencies = [ - "futures-channel", - "futures-core", - "futures-executor", - "futures-io", - "futures-sink", - "futures-task", - "futures-util", -] - -[[package]] -name = "futures-channel" -version = "0.3.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07bbe89c50d7a535e539b8c17bc0b49bdb77747034daa8087407d655f3f7cc1d" -dependencies = [ - "futures-core", - "futures-sink", -] - -[[package]] -name = "futures-core" -version = "0.3.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d" - -[[package]] -name = "futures-executor" -version = "0.3.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "baf29c38818342a3b26b5b923639e7b1f4a61fc5e76102d4b1981c6dc7a7579d" -dependencies = [ - "futures-core", - "futures-task", - "futures-util", -] - -[[package]] -name = "futures-io" -version = "0.3.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cecba35d7ad927e23624b22ad55235f2239cfa44fd10428eecbeba6d6a717718" - -[[package]] -name = "futures-macro" -version = "0.3.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e835b70203e41293343137df5c0664546da5745f82ec9b84d40be8336958447b" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "futures-sink" -version = "0.3.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c39754e157331b013978ec91992bde1ac089843443c49cbc7f46150b0fad0893" - -[[package]] -name = "futures-task" -version = "0.3.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "037711b3d59c33004d3856fbdc83b99d4ff37a24768fa1be9ce3538a1cde4393" - -[[package]] -name = "futures-util" -version = "0.3.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6" -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", - "js-sys", - "libc", - "r-efi 5.3.0", - "wasip2", - "wasm-bindgen", -] - -[[package]] -name = "getrandom" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0de51e6874e94e7bf76d726fc5d13ba782deca734ff60d5bb2fb2607c7406555" -dependencies = [ - "cfg-if", - "libc", - "r-efi 6.0.0", - "wasip2", - "wasip3", -] - -[[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", -] - -[[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 = "heck" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" - -[[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.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3ba2a386d7f85a81f119ad7498ebe444d2e22c2af0b86b069416ace48b3311a" -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.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "135b12329e5e3ce057a9f972339ea52bc954fe1e9358ef27f95e89716fbc5424" - -[[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 = "id-arena" -version = "2.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954" - -[[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.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3acae9609540aa318d1bc588455225fb2085b9ed0c4f6bd0d9d5bcd86f1a0344" -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.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "45a8a2b9cb3e0b0c1803dbb0758ffac5de2f425b23c28f518faabd9d805342ff" -dependencies = [ - "equivalent", - "hashbrown 0.16.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-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", -] - -[[package]] -name = "js-sys" -version = "0.3.94" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e04e2ef80ce82e13552136fabeef8a5ed1f985a96805761cbb9a2c34e7664d9" -dependencies = [ - "once_cell", - "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", -] - -[[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 = "lazy_static" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" -dependencies = [ - "spin 0.9.8", -] - -[[package]] -name = "leb128" -version = "0.2.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "884e2677b40cc8c339eaefcb701c32ef1fd2493d71118dc0ca4b6a736c93bd67" - -[[package]] -name = "leb128fmt" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" - -[[package]] -name = "libc" -version = "0.2.184" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48f5d2a454e16a5ea0f4ced81bd44e4cfc7bd3a507b61887c99fd3538b28e4af" - -[[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.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ddbf48fd451246b1f8c2610bd3b4ac0cc6e149d89832867093ab69a17194f08" -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.29" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" - -[[package]] -name = "lru" -version = "0.16.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96051b46fc183dc9cd4a223960ef37b9af631b55191852a8274bfef064cda20f" -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.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" - -[[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.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50b7e5b27aa02a74bac8c3f23f448f8d87ff11f92d3aac1a6ed369ee08cc56c1" -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.11.0", - "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.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" -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.5", - "smallvec", - "zeroize", -] - -[[package]] -name = "num-conv" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6673768db2d862beb9b39a78fdcb1a69439615d5794a1be50caa9bc92c81967" - -[[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.45" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1429034a0490724d0075ebb2bc9e875d6503c3cf69e235a8941aa757d83ef5bf" -dependencies = [ - "autocfg", - "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.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0848c601009d37dfa3430c4666e147e49cdcf1b92ecd3e63657d8a5f19da662" -dependencies = [ - "memchr", - "ucd-trie", -] - -[[package]] -name = "pest_derive" -version = "2.8.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11f486f1ea21e6c10ed15d5a7c77165d0ee443402f0780849d1768e7d9d6fe77" -dependencies = [ - "pest", - "pest_generator", -] - -[[package]] -name = "pest_generator" -version = "2.8.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8040c4647b13b210a963c1ed407c1ff4fdfa01c31d6d2a098218702e6664f94f" -dependencies = [ - "pest", - "pest_meta", - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "pest_meta" -version = "2.8.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89815c69d36021a140146f26659a81d6c2afa33d216d736dd4be5381a7362220" -dependencies = [ - "pest", - "sha2", -] - -[[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.13.1", - "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", -] - -[[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", -] - -[[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.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c33a9471896f1c69cecef8d20cbe2f7accd12527ce60845ff44c153bb2a21b49" - -[[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 = "prettyplease" -version = "0.2.37" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" -dependencies = [ - "proc-macro2", - "syn", -] - -[[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.106" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" -dependencies = [ - "unicode-ident", -] - -[[package]] -name = "quinn" -version = "0.11.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9e20a958963c291dc322d98411f541009df2ced7b5a4f2bd52337638cfccf20" -dependencies = [ - "bytes", - "cfg_aliases", - "pin-project-lite", - "quinn-proto", - "quinn-udp", - "rustc-hash", - "rustls", - "socket2 0.6.3", - "thiserror 2.0.18", - "tokio", - "tracing", - "web-time", -] - -[[package]] -name = "quinn-proto" -version = "0.11.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "434b42fec591c96ef50e21e886936e66d3cc3f737104fdb9b737c40ffb94c098" -dependencies = [ - "bytes", - "fastbloom", - "getrandom 0.3.4", - "lru-slab", - "rand 0.9.2", - "ring", - "rustc-hash", - "rustls", - "rustls-pki-types", - "rustls-platform-verifier", - "slab", - "thiserror 2.0.18", - "tinyvec", - "tracing", - "web-time", -] - -[[package]] -name = "quinn-udp" -version = "0.5.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "addec6a0dcad8a8d96a771f815f0eaf55f9d1805756410b39f5fa81332574cbd" -dependencies = [ - "cfg_aliases", - "libc", - "once_cell", - "socket2 0.6.3", - "tracing", - "windows-sys 0.60.2", -] - -[[package]] -name = "quote" -version = "1.0.45" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924" -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.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" -dependencies = [ - "libc", - "rand_chacha 0.3.1", - "rand_core 0.6.4", -] - -[[package]] -name = "rand" -version = "0.9.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" -dependencies = [ - "rand_chacha 0.9.0", - "rand_core 0.9.5", -] - -[[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_chacha" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" -dependencies = [ - "ppv-lite86", - "rand_core 0.9.5", -] - -[[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.9.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c" -dependencies = [ - "getrandom 0.3.4", -] - -[[package]] -name = "rcgen" -version = "0.14.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10b99e0098aa4082912d4c649628623db6aba77335e4f4569ff5083a6448b32e" -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.11.0", -] - -[[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.18", -] - -[[package]] -name = "ref-cast" -version = "1.0.25" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f354300ae66f76f1c85c5f84693f0ce81d747e2c3f21a45fef496d89c960bf7d" -dependencies = [ - "ref-cast-impl", -] - -[[package]] -name = "ref-cast-impl" -version = "1.0.25" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7186006dcb21920990093f30e3dea63b7d6e977bf1256be20c3563a5db070da" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "regex" -version = "1.12.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e10754a14b9137dd7b1e3e5b0493cc9171fdd105e0ab477f51b72e7f3ac0e276" -dependencies = [ - "aho-corasick", - "memchr", - "regex-automata", - "regex-syntax", -] - -[[package]] -name = "regex-automata" -version = "0.4.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f" -dependencies = [ - "aho-corasick", - "memchr", - "regex-syntax", -] - -[[package]] -name = "regex-syntax" -version = "0.8.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a" - -[[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.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4147b952f3f819eca0e99527022f7d6a8d05f111aeb0a62960c74eb283bec8fc" -dependencies = [ - "bitflags 2.11.0", - "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.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94300abf3f1ae2e2b8ffb7b58043de3d399c73fa6f4b73826402a5c457614dbe" - -[[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.37" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "758025cb5fccfd3bc2fd74708fd4682be41d99e5dff73c377c0646c6012c73a4" -dependencies = [ - "log", - "once_cell", - "ring", - "rustls-pki-types", - "rustls-webpki", - "subtle", - "zeroize", -] - -[[package]] -name = "rustls-native-certs" -version = "0.8.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "612460d5f7bea540c490b2b6395d8e34a953e52b491accd6c86c8164c5932a63" -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.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be040f8b0a225e40375822a563fa9524378b9d63112f53e19ffff34df5d33fdd" -dependencies = [ - "web-time", - "zeroize", -] - -[[package]] -name = "rustls-platform-verifier" -version = "0.6.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d99feebc72bae7ab76ba994bb5e121b8d83d910ca40b36e0921f53becc41784" -dependencies = [ - "core-foundation", - "core-foundation-sys", - "jni", - "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.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df33b2b81ac578cabaf06b89b0631153a3f416b0a886e8a7a1707fb51abbd1ef" -dependencies = [ - "ring", - "rustls-pki-types", - "untrusted", -] - -[[package]] -name = "rustversion" -version = "1.0.22" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" - -[[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.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2b42f36aa1cd011945615b92222f6bf73c599a102a300334cd7f8dbeec726cc" -dependencies = [ - "dyn-clone", - "either", - "ref-cast", - "schemars_derive", - "serde", - "serde_json", -] - -[[package]] -name = "schemars_derive" -version = "1.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d115b50f4aaeea07e79c1912f645c7513d81715d0420f8bc77a18c6260b307f" -dependencies = [ - "proc-macro2", - "quote", - "serde_derive_internals", - "syn", -] - -[[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.11.0", - "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.228" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" -dependencies = [ - "serde_core", - "serde_derive", -] - -[[package]] -name = "serde_core" -version = "1.0.228" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" -dependencies = [ - "serde_derive", -] - -[[package]] -name = "serde_derive" -version = "1.0.228" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "serde_derive_internals" -version = "0.29.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18d26a20a969b9e3fdf2fc2d9f21eda6c40e2de84c9408bb5d3b05d499aae711" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "serde_json" -version = "1.0.149" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" -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.18.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd5414fad8e6907dbdd5bc441a50ae8d6e26151a03b1de04d89a5576de61d01f" -dependencies = [ - "base64", - "chrono", - "hex", - "indexmap 1.9.3", - "indexmap 2.13.1", - "schemars 0.9.0", - "schemars 1.2.1", - "serde_core", - "serde_json", - "serde_with_macros", - "time", -] - -[[package]] -name = "serde_with_macros" -version = "3.18.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3db8978e608f1fe7357e211969fd9abdcae80bac1ba7a3369bb7eb6b404eb65" -dependencies = [ - "darling", - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "serde_yaml" -version = "0.9.34+deprecated" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a8b1a1a2ebf674015cc02edccce75287f1a0130d394307b36743c2f5d504b47" -dependencies = [ - "indexmap 2.13.1", - "itoa", - "ryu", - "serde", - "unsafe-libyaml", -] - -[[package]] -name = "sha1" -version = "0.10.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" -dependencies = [ - "cfg-if", - "cpufeatures", - "digest", -] - -[[package]] -name = "sha2" -version = "0.10.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" -dependencies = [ - "cfg-if", - "cpufeatures", - "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.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75872d278a8f37ef87fa0ddbda7802605cb18344497949862c0d4dcb291eba60" -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 = "1.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" - -[[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.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "703d5c7ef118737c72f1af64ad2f6f8c5e1921f818cdcb97b8fe6fc69bf66214" - -[[package]] -name = "siphasher" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2aa850e253778c88a04c3d7323b043aeda9d3e30d5971937c1855769763678e" - -[[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.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" - -[[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.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a766e1110788c36f4fa1c2b71b387a7815aa65f88ce0229841826633d93723e" -dependencies = [ - "libc", - "windows-sys 0.61.2", -] - -[[package]] -name = "spin" -version = "0.9.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" -dependencies = [ - "lock_api", -] - -[[package]] -name = "spin" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d5fe4ccb98d9c292d56fec89a5e07da7fc4cf0dc11e156b41793132775d3e591" - -[[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.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7b834ec7ced12095fea1e4b07dcb7e8cf2b59b18afa3eac52494d835965a5ec" -dependencies = [ - "rustversion", - "stabby-abi", -] - -[[package]] -name = "stabby-abi" -version = "72.1.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff1a4f477858a5bdf927c9fab7f579899de9b13e39f8b3b3b300c89fbab632f4" -dependencies = [ - "rustc_version", - "rustversion", - "sha2-const-stable", - "stabby-macros", -] - -[[package]] -name = "stabby-macros" -version = "72.1.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b31c4b2434980b67ad83f300a58088ba14d59454dcd79ba3d87419bbd924d31e" -dependencies = [ - "proc-macro-crate", - "proc-macro2", - "quote", - "syn", -] - -[[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.117" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99" -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", -] - -[[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.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" -dependencies = [ - "thiserror-impl 2.0.18", -] - -[[package]] -name = "thiserror-impl" -version = "1.0.69" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "thiserror-impl" -version = "2.0.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "thread_local" -version = "1.1.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f60246a4944f24f6e018aa17cdeffb7818b76356965d03b07d6a9886e8962185" -dependencies = [ - "cfg-if", -] - -[[package]] -name = "time" -version = "0.3.47" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "743bd48c283afc0388f9b8827b976905fb217ad9e647fae3a379a9283c4def2c" -dependencies = [ - "deranged", - "itoa", - "num-conv", - "powerfmt", - "serde_core", - "time-core", - "time-macros", -] - -[[package]] -name = "time-core" -version = "0.1.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7694e1cfe791f8d31026952abf09c69ca6f6fa4e1a1229e18988f06a04a12dca" - -[[package]] -name = "time-macros" -version = "0.2.27" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e70e4c5a0e0a8a4823ad65dfe1a6930e4f4d756dcd9dd7939022b5e8c501215" -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.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e61e67053d25a4e82c844e8424039d9745781b3fc4f32b8d55ed50f5f667ef3" -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.18", - "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.51.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2bd1c4c0fc4a7ab90fc15ef6daaa3ec3b893f004f915f2392557ed23237820cd" -dependencies = [ - "bytes", - "libc", - "mio", - "pin-project-lite", - "socket2 0.6.3", - "tokio-macros", - "windows-sys 0.61.2", -] - -[[package]] -name = "tokio-macros" -version = "2.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "385a6cb71ab9ab790c5fe8d67f1645e6c450a7ce006a33de03daa956cf70a496" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[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.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ae9cec805b01e8fc3fd2fe289f89149a9b66dd16786abd8b19cfa7b48cb0098" -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.13.1", - "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.11+spec-1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b59c4d22ed448339746c59b905d24568fcbb3ab65a500494f7b8c3e97739f2b" -dependencies = [ - "indexmap 2.13.1", - "toml_datetime 1.1.1+spec-1.1.0", - "toml_parser", - "winnow 1.0.1", -] - -[[package]] -name = "toml_parser" -version = "1.1.2+spec-1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2abe9b86193656635d2411dc43050282ca48aa31c2451210f4202550afb7526" -dependencies = [ - "winnow 1.0.1", -] - -[[package]] -name = "toml_writer" -version = "1.1.1+spec-1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "756daf9b1013ebe47a8776667b466417e2d4c5679d441c26230efd9ef78692db" - -[[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", -] - -[[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.5", - "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.19.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" - -[[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.5", - "serde", - "spin 0.10.0", -] - -[[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", -] - -[[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.23.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ac8b6f42ead25368cf5b098aeb3dc8a1a2c05a3eee8a9a1a68c640edbfc79d9" -dependencies = [ - "getrandom 0.4.2", - "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", - "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.2+wasi-0.2.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9517f9239f02c069db75e65f174b3da828fe5f5b945c4dd26bd25d89c03ebcf5" -dependencies = [ - "wit-bindgen", -] - -[[package]] -name = "wasip3" -version = "0.4.0+wasi-0.3.0-rc-2026-01-06" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5428f8bf88ea5ddc08faddef2ac4a67e390b88186c703ce6dbd955e1c145aca5" -dependencies = [ - "wit-bindgen", -] - -[[package]] -name = "wasm-bindgen" -version = "0.2.117" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0551fc1bb415591e3372d0bc4780db7e587d84e2a7e79da121051c5c4b89d0b0" -dependencies = [ - "cfg-if", - "once_cell", - "rustversion", - "wasm-bindgen-macro", - "wasm-bindgen-shared", -] - -[[package]] -name = "wasm-bindgen-macro" -version = "0.2.117" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fbdf9a35adf44786aecd5ff89b4563a90325f9da0923236f6104e603c7e86be" -dependencies = [ - "quote", - "wasm-bindgen-macro-support", -] - -[[package]] -name = "wasm-bindgen-macro-support" -version = "0.2.117" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dca9693ef2bab6d4e6707234500350d8dad079eb508dca05530c85dc3a529ff2" -dependencies = [ - "bumpalo", - "proc-macro2", - "quote", - "syn", - "wasm-bindgen-shared", -] - -[[package]] -name = "wasm-bindgen-shared" -version = "0.2.117" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39129a682a6d2d841b6c429d0c51e5cb0ed1a03829d8b3d1e69a011e62cb3d3b" -dependencies = [ - "unicode-ident", -] - -[[package]] -name = "wasm-encoder" -version = "0.244.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "990065f2fe63003fe337b932cfb5e3b80e0b4d0f5ff650e6985b1048f62c8319" -dependencies = [ - "leb128fmt", - "wasmparser", -] - -[[package]] -name = "wasm-metadata" -version = "0.244.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb0e353e6a2fbdc176932bbaab493762eb1255a7900fe0fea1a2f96c296cc909" -dependencies = [ - "anyhow", - "indexmap 2.13.1", - "wasm-encoder", - "wasmparser", -] - -[[package]] -name = "wasmparser" -version = "0.244.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe" -dependencies = [ - "bitflags 2.11.0", - "hashbrown 0.15.5", - "indexmap 2.13.1", - "semver", -] - -[[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.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "804f18a4ac2676ffb4e8b5b5fa9ae38af06df08162314f96a68d2a363e21a8ca" -dependencies = [ - "rustls-pki-types", -] - -[[package]] -name = "webpki-roots" -version = "1.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22cfaf3c063993ff62e73cb4311efde4db1efb31ab78a3e5c457939ad5cc0bed" -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", -] - -[[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", -] - -[[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.60.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" -dependencies = [ - "windows-targets 0.53.5", -] - -[[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 0.52.6", - "windows_i686_msvc 0.52.6", - "windows_x86_64_gnu 0.52.6", - "windows_x86_64_gnullvm 0.52.6", - "windows_x86_64_msvc 0.52.6", -] - -[[package]] -name = "windows-targets" -version = "0.53.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3" -dependencies = [ - "windows-link", - "windows_aarch64_gnullvm 0.53.1", - "windows_aarch64_msvc 0.53.1", - "windows_i686_gnu 0.53.1", - "windows_i686_gnullvm 0.53.1", - "windows_i686_msvc 0.53.1", - "windows_x86_64_gnu 0.53.1", - "windows_x86_64_gnullvm 0.53.1", - "windows_x86_64_msvc 0.53.1", -] - -[[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_gnullvm" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53" - -[[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_aarch64_msvc" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006" - -[[package]] -name = "windows_i686_gnu" -version = "0.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_gnu" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3" - -[[package]] -name = "windows_i686_gnullvm" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" - -[[package]] -name = "windows_i686_gnullvm" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c" - -[[package]] -name = "windows_i686_msvc" -version = "0.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_i686_msvc" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.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_gnu" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499" - -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.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_gnullvm" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1" - -[[package]] -name = "windows_x86_64_msvc" -version = "0.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 = "windows_x86_64_msvc" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" - -[[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.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09dac053f1cd375980747450bfc7250c264eaae0583872e845c0c7cd578872b5" -dependencies = [ - "memchr", -] - -[[package]] -name = "wit-bindgen" -version = "0.51.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5" -dependencies = [ - "wit-bindgen-rust-macro", -] - -[[package]] -name = "wit-bindgen-core" -version = "0.51.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea61de684c3ea68cb082b7a88508a8b27fcc8b797d738bfc99a82facf1d752dc" -dependencies = [ - "anyhow", - "heck", - "wit-parser", -] - -[[package]] -name = "wit-bindgen-rust" -version = "0.51.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7c566e0f4b284dd6561c786d9cb0142da491f46a9fbed79ea69cdad5db17f21" -dependencies = [ - "anyhow", - "heck", - "indexmap 2.13.1", - "prettyplease", - "syn", - "wasm-metadata", - "wit-bindgen-core", - "wit-component", -] - -[[package]] -name = "wit-bindgen-rust-macro" -version = "0.51.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c0f9bfd77e6a48eccf51359e3ae77140a7f50b1e2ebfe62422d8afdaffab17a" -dependencies = [ - "anyhow", - "prettyplease", - "proc-macro2", - "quote", - "syn", - "wit-bindgen-core", - "wit-bindgen-rust", -] - -[[package]] -name = "wit-component" -version = "0.244.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2" -dependencies = [ - "anyhow", - "bitflags 2.11.0", - "indexmap 2.13.1", - "log", - "serde", - "serde_derive", - "serde_json", - "wasm-encoder", - "wasm-metadata", - "wasmparser", - "wit-parser", -] - -[[package]] -name = "wit-parser" -version = "0.244.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ecc8ac4bc1dc3381b7f59c34f00b67e18f910c2c0f50015669dde7def656a736" -dependencies = [ - "anyhow", - "id-arena", - "indexmap 2.13.1", - "log", - "semver", - "serde", - "serde_derive", - "serde_json", - "unicode-xid", - "wasmparser", -] - -[[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.18", - "time", -] - -[[package]] -name = "yasna" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e17bb3549cc1321ae1296b9cdc2698e2b6cb1992adfa19a8c72e5b7a738f44cd" -dependencies = [ - "time", -] - -[[package]] -name = "yoke" -version = "0.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "abe8c5fda708d9ca3df187cae8bfb9ceda00dd96231bed36e445a1a48e66f9ca" -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", - "synstructure", -] - -[[package]] -name = "zenoh" -version = "1.9.0" -source = "git+https://github.com/eclipse-zenoh/zenoh.git?branch=main#55102d943b4d2263e17b350115055be764d11ea5" -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.5", - "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#55102d943b4d2263e17b350115055be764d11ea5" -dependencies = [ - "zenoh-collections", -] - -[[package]] -name = "zenoh-codec" -version = "1.9.0" -source = "git+https://github.com/eclipse-zenoh/zenoh.git?branch=main#55102d943b4d2263e17b350115055be764d11ea5" -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#55102d943b4d2263e17b350115055be764d11ea5" -dependencies = [ - "ahash", -] - -[[package]] -name = "zenoh-config" -version = "1.9.0" -source = "git+https://github.com/eclipse-zenoh/zenoh.git?branch=main#55102d943b4d2263e17b350115055be764d11ea5" -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#55102d943b4d2263e17b350115055be764d11ea5" -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#55102d943b4d2263e17b350115055be764d11ea5" -dependencies = [ - "aes", - "hmac", - "rand 0.8.5", - "rand_chacha 0.3.1", - "sha3", - "zenoh-result", -] - -[[package]] -name = "zenoh-ext" -version = "1.9.0" -source = "git+https://github.com/eclipse-zenoh/zenoh.git?branch=main#55102d943b4d2263e17b350115055be764d11ea5" -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#55102d943b4d2263e17b350115055be764d11ea5" -dependencies = [ - "getrandom 0.2.17", - "hashbrown 0.16.1", - "keyed-set", - "rand 0.8.5", - "schemars 1.2.1", - "serde", - "token-cell", - "zenoh-result", -] - -[[package]] -name = "zenoh-link" -version = "1.9.0" -source = "git+https://github.com/eclipse-zenoh/zenoh.git?branch=main#55102d943b4d2263e17b350115055be764d11ea5" -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#55102d943b4d2263e17b350115055be764d11ea5" -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#55102d943b4d2263e17b350115055be764d11ea5" -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#55102d943b4d2263e17b350115055be764d11ea5" -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#55102d943b4d2263e17b350115055be764d11ea5" -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#55102d943b4d2263e17b350115055be764d11ea5" -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#55102d943b4d2263e17b350115055be764d11ea5" -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#55102d943b4d2263e17b350115055be764d11ea5" -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#55102d943b4d2263e17b350115055be764d11ea5" -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#55102d943b4d2263e17b350115055be764d11ea5" -dependencies = [ - "proc-macro2", - "quote", - "syn", - "zenoh-keyexpr", -] - -[[package]] -name = "zenoh-plugin-trait" -version = "1.9.0" -source = "git+https://github.com/eclipse-zenoh/zenoh.git?branch=main#55102d943b4d2263e17b350115055be764d11ea5" -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#55102d943b4d2263e17b350115055be764d11ea5" -dependencies = [ - "const_format", - "rand 0.8.5", - "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#55102d943b4d2263e17b350115055be764d11ea5" -dependencies = [ - "anyhow", -] - -[[package]] -name = "zenoh-runtime" -version = "1.9.0" -source = "git+https://github.com/eclipse-zenoh/zenoh.git?branch=main#55102d943b4d2263e17b350115055be764d11ea5" -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#55102d943b4d2263e17b350115055be764d11ea5" -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#55102d943b4d2263e17b350115055be764d11ea5" -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#55102d943b4d2263e17b350115055be764d11ea5" -dependencies = [ - "async-trait", - "crossbeam-utils", - "flume 0.11.1", - "futures", - "lazy_static", - "lz4_flex", - "rand 0.8.5", - "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#55102d943b4d2263e17b350115055be764d11ea5" -dependencies = [ - "async-trait", - "const_format", - "flume 0.11.1", - "home", - "humantime", - "lazy_static", - "libc", - "libloading", - "pnet_datalink", - "schemars 1.2.1", - "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", - "json5", - "rustc_version", - "serde_yaml", - "tracing", - "uhlc", - "zenoh", - "zenoh-ext", -] - -[[package]] -name = "zerocopy" -version = "0.8.48" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eed437bf9d6692032087e337407a86f04cd8d6a16a37199ed57949d415bd68e9" -dependencies = [ - "zerocopy-derive", -] - -[[package]] -name = "zerocopy-derive" -version = "0.8.48" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70e3cd084b1788766f53af483dd21f93881ff30d7320490ec3ef7526d203bad4" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "zerofrom" -version = "0.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69faa1f2a1ea75661980b013019ed6687ed0e83d069bc1114e2cc74c6c04c4df" -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", - "synstructure", -] - -[[package]] -name = "zeroize" -version = "1.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0" - -[[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", -] - -[[package]] -name = "zmij" -version = "1.0.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa" diff --git a/zenoh-jni/Cargo.toml b/zenoh-jni/Cargo.toml deleted file mode 100644 index a88e31ec..00000000 --- a/zenoh-jni/Cargo.toml +++ /dev/null @@ -1,54 +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 = ["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 0ada1340..00000000 --- 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 23687d4c..00000000 --- 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/key_expr.rs b/zenoh-jni/src/key_expr.rs deleted file mode 100644 index 11d39d47..00000000 --- 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 ff3981a4..00000000 --- a/zenoh-jni/src/lib.rs +++ /dev/null @@ -1,37 +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; -mod key_expr; -mod liveliness; -mod logger; -mod publisher; -mod querier; -mod query; -mod queryable; -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 8b05c925..00000000 --- a/zenoh-jni/src/liveliness.rs +++ /dev/null @@ -1,242 +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::{JByteArray, JClass, JObject, JString, JValue}, - sys::{jboolean, jint, jlong}, - JNIEnv, -}; - -use zenoh::{ - internal::runtime::ZRuntime, key_expr::KeyExpr, liveliness::LivelinessToken, - pubsub::Subscriber, sample::Sample, Session, Wait, -}; - -use crate::{ - errors::ZResult, - key_expr::process_kotlin_key_expr, - session::{on_reply_error, on_reply_success}, - throw_exception, - utils::{ - bytes_to_java_array, get_callback_global_ref, get_java_vm, load_on_close, - slice_to_java_string, - }, - 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 { Arc::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); - }); - std::mem::forget(session); -} - -#[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 { Arc::from_raw(session_ptr) }; - let 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() - }); - std::mem::forget(session); - ptr -} - -#[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 { Arc::from_raw(session_ptr) }; - || -> ZResult<*const Subscriber<()>> { - 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 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 = session - .liveliness() - .declare_subscriber(key_expr.to_owned()) - .history(history != 0) - .callback(move |sample: Sample| { - let _ = || -> ZResult<()> { - on_close.noop(); // Does nothing, but moves `on_close` inside the closure so it gets destroyed with the closure - let mut env = java_vm.attach_current_thread_as_daemon().map_err(|err| { - zerror!("Unable to attach thread for liveliness subscriber: {}", 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 liveliness subscriber callback error: {err}")); - }) - .wait(); - - let subscriber = - result.map_err(|err| zerror!("Unable to declare liveliness subscriber: {}", err))?; - - tracing::debug!("Subscriber declared on '{}'.", key_expr); - std::mem::forget(session); - 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 785a6cd1..00000000 --- a/zenoh-jni/src/logger.rs +++ /dev/null @@ -1,64 +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)?; - 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/publisher.rs b/zenoh-jni/src/publisher.rs deleted file mode 100644 index ead60c3f..00000000 --- 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 8c239498..00000000 --- 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 9c843892..00000000 --- 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_*`: QoS parameters for the reply. -/// -/// # 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_*`: QoS parameters for the reply. -/// -/// # 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 5d2ddb1d..00000000 --- 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/scouting.rs b/zenoh-jni/src/scouting.rs deleted file mode 100644 index b0a665c1..00000000 --- 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 5f0848b2..00000000 --- a/zenoh-jni/src/session.rs +++ /dev/null @@ -1,1201 +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, - key_expr::KeyExpr, - pubsub::{Publisher, Subscriber}, - query::{Querier, Query, Queryable, ReplyError, ReplyKeyExpr, Selector}, - sample::Sample, - session::{EntityGlobalId, Session, ZenohId}, - Wait, -}; - -use crate::{ - errors::ZResult, key_expr::process_kotlin_key_expr, 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_00024Companion_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 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>> { - 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 result = session - .declare_publisher(key_expr) - .congestion_control(congestion_control) - .priority(priority) - .express(is_express != 0) - .reliability(reliability) - .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() - }); - std::mem::forget(session); - publisher_ptr -} - -/// 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); - || -> ZResult<*const Subscriber<()>> { - 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 key_expr = process_kotlin_key_expr(&mut env, &key_expr_str, key_expr_ptr)?; - tracing::debug!("Declaring subscriber on '{}'...", key_expr); - - let result = session - .declare_subscriber(key_expr.to_owned()) - .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 subscriber: {}", 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 subscriber callback error: {err}")); - }) - .wait(); - - let subscriber = result.map_err(|err| zerror!("Unable to declare subscriber: {}", err))?; - - tracing::debug!("Subscriber declared on '{}'.", key_expr); - std::mem::forget(session); - Ok(Arc::into_raw(Arc::new(subscriber))) - }() - .unwrap_or_else(|err| { - throw_exception!(env, err); - null() - }) -} - -/// 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 accepts_replies: jint = match query.accepts_replies() { - ReplyKeyExpr::MatchingQuery => 0, - ReplyKeyExpr::Any => 1, - }; - - 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;[BJI)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), - JValue::from(accepts_replies), - ], - ) - .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 3462ec7e..00000000 --- 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 a2c6a9f4..00000000 --- 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::MatchingQuery), - 1 => Ok(ReplyKeyExpr::Any), - 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 627bb8b0..00000000 --- a/zenoh-jni/src/zbytes.rs +++ /dev/null @@ -1,451 +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::{JByteArray, JClass, JList, JMap, JObject, JObjectArray, 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 JavaType { - Boolean, - String, - ByteArray, - Byte, - Short, - Int, - Long, - Float, - Double, - List(Box), - Map(Box, Box), -} - -fn decode_token_type(env: &mut JNIEnv, type_obj: JObject) -> ZResult { - let type_name_jobject = env - .call_method(&type_obj, "getTypeName", "()Ljava/lang/String;", &[]) - .map_err(|err| zerror!(err))? - .l() - .map_err(|err| zerror!(err))?; - - let qualified_name: String = env - .get_string(&JString::from(type_name_jobject)) - .map_err(|err| zerror!(err))? - .into(); - - match qualified_name.as_str() { - "java.lang.Boolean" => Ok(JavaType::Boolean), - "java.lang.String" => Ok(JavaType::String), - "byte[]" => Ok(JavaType::ByteArray), - "java.lang.Byte" => Ok(JavaType::Byte), - "java.lang.Short" => Ok(JavaType::Short), - "java.lang.Integer" => Ok(JavaType::Int), - "java.lang.Long" => Ok(JavaType::Long), - "java.lang.Float" => Ok(JavaType::Float), - "java.lang.Double" => Ok(JavaType::Double), - _ => { - let type_token_class = env - .find_class("com/google/common/reflect/TypeToken") - .map_err(|err| zerror!(err))?; - let token_type = env - .call_static_method( - type_token_class, - "of", - "(Ljava/lang/reflect/Type;)Lcom/google/common/reflect/TypeToken;", - &[JValue::Object(&type_obj)], - ) - .map_err(|err| zerror!(err))? - .l() - .map_err(|err| zerror!(err))?; - let map_class: JObject = env - .find_class("java/util/Map") - .map_err(|err| zerror!(err))? - .into(); - let is_map_subtype = env - .call_method( - &token_type, - "isSubtypeOf", - "(Ljava/lang/reflect/Type;)Z", - &[JValue::Object(&map_class)], - ) - .map_err(|err| zerror!(err))? - .z() - .map_err(|err| zerror!(err))?; - - if is_map_subtype { - let args = env - .call_method( - &type_obj, - "getActualTypeArguments", - "()[Ljava/lang/reflect/Type;", - &[], - ) - .map_err(|err| zerror!(err))? - .l() - .map_err(|err| zerror!(err))?; - let jobject_array = JObjectArray::from(args); - let arg1 = env - .get_object_array_element(&jobject_array, 0) - .map_err(|err| zerror!(err))?; - let arg2 = env - .get_object_array_element(&jobject_array, 1) - .map_err(|err| zerror!(err))?; - - return Ok(JavaType::Map( - Box::new(decode_token_type(env, arg1)?), - Box::new(decode_token_type(env, arg2)?), - )); - } - - let list_class: JObject = env - .find_class("java/util/List") - .map_err(|err| zerror!(err))? - .into(); - let is_list_subtype = env - .call_method( - &token_type, - "isSubtypeOf", - "(Ljava/lang/reflect/Type;)Z", - &[JValue::Object(&list_class)], - ) - .map_err(|err| zerror!(err))? - .z() - .map_err(|err| zerror!(err))?; - - if is_list_subtype { - let args = env - .call_method( - &type_obj, - "getActualTypeArguments", - "()[Ljava/lang/reflect/Type;", - &[], - ) - .map_err(|err| zerror!(err))? - .l() - .map_err(|err| zerror!(err))?; - let jobject_array = JObjectArray::from(args); - let arg1 = env - .get_object_array_element(&jobject_array, 0) - .map_err(|err| zerror!(err))?; - - return Ok(JavaType::List(Box::new(decode_token_type(env, arg1)?))); - } - - Err(zerror!("Unsupported type: {}", qualified_name)) - } - } -} - -#[no_mangle] -#[allow(non_snake_case)] -pub extern "C" fn Java_io_zenoh_jni_JNIZBytes_serializeViaJNI( - mut env: JNIEnv, - _class: JClass, - any: JObject, - token_type: JObject, -) -> jobject { - || -> ZResult { - let mut serializer = ZSerializer::new(); - let jtype = decode_token_type(&mut env, token_type)?; - serialize(&mut env, &mut serializer, any, &jtype)?; - 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, - jtype: &JavaType, -) -> ZResult<()> { - match jtype { - JavaType::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); - } - JavaType::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); - } - JavaType::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); - } - JavaType::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); - } - JavaType::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); - } - JavaType::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); - } - JavaType::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); - } - JavaType::String => { - let jstring = JString::from(any); - let string_value: String = env.get_string(&jstring).map_err(|err| zerror!(err))?.into(); - serializer.serialize(string_value); - } - JavaType::ByteArray => { - let jbyte_array = JByteArray::from(any); - let bytes = decode_byte_array(env, jbyte_array).map_err(|err| zerror!(err))?; - serializer.serialize(bytes); - } - JavaType::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).map_err(|err| zerror!(err))?; - 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)?; - } - } - JavaType::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)?; - } - } - } - Ok(()) -} - -#[no_mangle] -#[allow(non_snake_case)] -pub extern "C" fn Java_io_zenoh_jni_JNIZBytes_deserializeViaJNI( - mut env: JNIEnv, - _class: JClass, - zbytes: JObject, - jtype: 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().map_err(|err| zerror!(err))?), - )?; - let zbytes = ZBytes::from(decoded_bytes); - let mut deserializer = ZDeserializer::new(&zbytes); - let jtype = decode_token_type(&mut env, jtype)?; - let obj = deserialize(&mut env, &mut deserializer, &jtype)?; - 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, - jtype: &JavaType, -) -> ZResult { - match jtype { - JavaType::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()) - } - JavaType::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()) - } - JavaType::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()) - } - JavaType::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()) - } - JavaType::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()) - } - JavaType::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()) - } - JavaType::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()) - } - JavaType::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()) - } - JavaType::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()) - } - JavaType::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()) - } - JavaType::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()) - } - } -} diff --git a/zenoh-jni/src/zenoh_id.rs b/zenoh-jni/src/zenoh_id.rs deleted file mode 100644 index 6647f86f..00000000 --- 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() -} From aa2a8bde8328060f37764d9f7bc26d5b7cbeb4a8 Mon Sep 17 00:00:00 2001 From: Michael Ilyin Date: Thu, 16 Jul 2026 16:31:45 +0200 Subject: [PATCH 3/9] Encoding as a pure JVM value + native publisher default + pinned per-put override (#484) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Make Encoding and ZenohId.toString pure JVM values An encoding IS its (id, schema) pair — Zenoh's own representation; the textual form is derived from a fixed table. With the id<->name table already JVM-side (the generated ENCODING_* constants) and the companion zenoh-flat-jni change crossing Encoding by value in both directions, the SDK Encoding becomes a plain immutable value: no native handle, no native calls, no lazy caches, no locking, nothing to close. This also removes a native memory leak: received Sample/Query/ReplyError previously retained a cloned Encoding handle with no close path. - Encoding.kt: immutable (id, schema); from()/toString()/withSchema() implement Zenoh's exact conversion rules (incl. the custom-encoding name-preserving withSchema and the render-only CUSTOM<->"" table row); equality on (id, schema), matching Zenoh core. - ZenohId.toString: the little-endian lowercase-hex rule in pure Kotlin. - Callback/fromParts leaves follow the new (encId, encSchema) decomposition; outbound sites pass .id/.schema directly. - Correspondence tests (EncodingCorrespondenceTest, ZenohIdCorrespondenceTest) verify the pure implementations against the native ones across the whole predefined id range, parse/render edge shapes, and random ids — the contract for any JVM-side reimplementation of zenoh-flat API. One test caught and fixed a real divergence (custom-encoding withSchema). Pairs with ZettaScaleLabs/zenoh-flat-jni#4 (CI pin bumped). jvmTest: 105 tests, 0 failures. * Publisher default encoding at declare + PinnedEncoding per-put override Two encoding fast paths over the dual-arm expansion (prebindgen#80, zenoh-flat#3, zenoh-flat-jni#4): - The publisher's default encoding is set NATIVELY once at declare time (PublisherOptions.encoding -> declarePublisher's encoding arm); plain put(payload) passes absent (sel -1), so no encoding data crosses per call. Previously the JVM tier re-sent (id, schema) on every put. - PinnedEncoding (Encoding.pinned()) preallocates the native form; passing it in PutOptions/ReplyOptions/GetOptions crosses only a borrowed handle (native clone = Arc bump) — no schema-string traffic in hot publish loops. close() releases the handle (falls back to the plain (id, schema) arm); a finalizer backstops leaks. A PinnedEncoding equals its plain counterpart. - All outbound sites route through one Encoding?.forWire() helper emitting the selector tuple (-1 absent / 0 value / 1 pinned handle). Tests: PinnedEncodingTest (publisher-default applies natively end-to-end; pinned override reused across puts; post-close fallback), correspondence tests updated to the dual-arm newWithSchema. jvmTest: 107 tests, 0 failures. CI pins: zenoh-flat-jni cbbca40, zenoh-flat d792547. * Move encoding/zid logic to the shared bindings tier; drop pinning zenoh-flat-jni is the shared tier reused by both zenoh-java and zenoh-kotlin, so the pure-JVM conversion logic moves there (EncodingCodec, ZenohId.zidString — next to the generated constants they are built from). io.zenoh.bytes.Encoding shrinks to a thin facade: the established constants and API delegating to the shared codec, zero logic. Drop PinnedEncoding and the selector-tuple wire helper: with the publisher's default encoding set natively at declare time, the hot publish loop crosses no encoding data at all, and per-call overrides crossing (id, schema) cost only a short string decode — not worth a resource class in the public API. Encoding params revert to the plain (present, id, schema) crossing. jvmTest: 106 tests, 0 failures (correspondence suites now exercise the shared codec; PublisherEncodingTest verifies the native declare-time default + per-put override end-to-end). CI pins: zenoh-flat-jni 9bc155b, zenoh-flat main (6d22091, #3 merged). --- .github/workflows/ci.yml | 4 +- ZENOH_FLAT_TRANSITION.md | 3 +- .../kotlin/io/zenoh/FlatCallbacks.kt | 14 +- .../src/commonMain/kotlin/io/zenoh/Session.kt | 30 +- .../kotlin/io/zenoh/bytes/Encoding.kt | 367 ++++-------------- .../kotlin/io/zenoh/config/ZenohId.kt | 11 +- .../kotlin/io/zenoh/pubsub/Publisher.kt | 16 +- .../kotlin/io/zenoh/query/Querier.kt | 16 +- .../commonMain/kotlin/io/zenoh/query/Query.kt | 14 +- .../kotlin/io/zenoh/sample/Sample.kt | 4 +- .../io/zenoh/EncodingCorrespondenceTest.kt | 116 ++++++ .../kotlin/io/zenoh/PublisherEncodingTest.kt | 62 +++ .../io/zenoh/ZenohIdCorrespondenceTest.kt | 58 +++ 13 files changed, 372 insertions(+), 343 deletions(-) create mode 100644 zenoh-java/src/jvmTest/kotlin/io/zenoh/EncodingCorrespondenceTest.kt create mode 100644 zenoh-java/src/jvmTest/kotlin/io/zenoh/PublisherEncodingTest.kt create mode 100644 zenoh-java/src/jvmTest/kotlin/io/zenoh/ZenohIdCorrespondenceTest.kt diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 28a2fab3..ef67473a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -31,14 +31,14 @@ jobs: uses: actions/checkout@v4 with: repository: ZettaScaleLabs/zenoh-flat-jni - ref: c82656b2f41093d04921ead50516219ae74c7e96 + ref: 9bc155b3272c8c905bf95614cb7eb7e6d656c72f path: zenoh-flat-jni - name: Check out zenoh-flat uses: actions/checkout@v4 with: repository: ZettaScaleLabs/zenoh-flat - ref: 7b151d7808583ed15884fada2fd40d5db387c982 + ref: 6d22091d75912be1c14330b0f78fa1d4bf258ea3 path: zenoh-flat - name: Use prebindgen from GitHub main diff --git a/ZENOH_FLAT_TRANSITION.md b/ZENOH_FLAT_TRANSITION.md index e03f18d5..492d969a 100644 --- a/ZENOH_FLAT_TRANSITION.md +++ b/ZENOH_FLAT_TRANSITION.md @@ -25,7 +25,8 @@ zenoh (Rust) | PR | Scope | Status | | --- | --- | --- | -| [#481](https://github.com/eclipse-zenoh/zenoh-java/pull/481) | Use receiver-style zenoh-flat-jni bindings (generated Session/Query/Publisher methods, split key-expr overloads, de-prefixed callback names); +61–83% subscriber throughput | CI green | +| [#481](https://github.com/eclipse-zenoh/zenoh-java/pull/481) | Use receiver-style zenoh-flat-jni bindings (generated Session/Query/Publisher methods, split key-expr overloads, de-prefixed callback names); +61–83% subscriber throughput | merged | +| [#484](https://github.com/eclipse-zenoh/zenoh-java/pull/484) | `Encoding`/`ZenohId.toString` become pure JVM values (fixes a per-message native leak); the conversion logic lives in the **shared tier** (`EncodingCodec`/`zidString` in zenoh-flat-jni, correspondence-tested against native, reusable by zenoh-kotlin) with only a thin facade here; publisher **default encoding set natively at declare** (plain puts cross no encoding data). Pairs with [zenoh-flat-jni#4](https://github.com/ZettaScaleLabs/zenoh-flat-jni/pull/4), [zenoh-flat#3](https://github.com/ZettaScaleLabs/zenoh-flat/pull/3) (merged), [prebindgen#80](https://github.com/milyin/prebindgen/pull/80) (merged) | in progress | Companion PRs in the upstream repos are coordinated per constituent PR (e.g. [ZettaScaleLabs/zenoh-flat-jni#3](https://github.com/ZettaScaleLabs/zenoh-flat-jni/pull/3) diff --git a/zenoh-java/src/commonMain/kotlin/io/zenoh/FlatCallbacks.kt b/zenoh-java/src/commonMain/kotlin/io/zenoh/FlatCallbacks.kt index 63d401e3..4e35a7cf 100644 --- a/zenoh-java/src/commonMain/kotlin/io/zenoh/FlatCallbacks.kt +++ b/zenoh-java/src/commonMain/kotlin/io/zenoh/FlatCallbacks.kt @@ -39,14 +39,14 @@ import io.zenoh.scouting.Hello internal fun sampleCallbackOf( f: (Sample) -> Unit ): io.zenoh.jni.sample.SampleCallback = - io.zenoh.jni.sample.SampleCallback { keH, payloadH, encH, encId, kindInt, ntp64, express, prioInt, ccInt, attachH, reliabilityInt, sourceZid, sourceEid, sourceSn -> - f(Sample.fromParts(keH, payloadH, encH, encId, kindInt, ntp64, express, prioInt, ccInt, attachH, reliabilityInt, sourceZid, sourceEid, sourceSn)) + io.zenoh.jni.sample.SampleCallback { keH, payloadH, encId, encSchema, kindInt, ntp64, express, prioInt, ccInt, attachH, reliabilityInt, sourceZid, sourceEid, sourceSn -> + f(Sample.fromParts(keH, payloadH, encId, encSchema, kindInt, ntp64, express, prioInt, ccInt, attachH, reliabilityInt, sourceZid, sourceEid, sourceSn)) } internal fun queryCallbackOf( f: (Query) -> Unit ): io.zenoh.jni.query.QueryCallback = - io.zenoh.jni.query.QueryCallback { keH, parameters, payloadH, encH, encId, attachH, acceptsReplies, zq -> + io.zenoh.jni.query.QueryCallback { keH, parameters, payloadH, encId, encSchema, attachH, acceptsReplies, zq -> // The decomposed leaves — including the cloned `keH` key-expr handle 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 @@ -54,25 +54,25 @@ internal fun queryCallbackOf( // 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. - f(Query.fromParts(keH, parameters, payloadH, encH, encId, attachH, acceptsReplies, zq)) + f(Query.fromParts(keH, parameters, payloadH, encId, encSchema, attachH, acceptsReplies, zq)) } internal fun replyCallbackOf( f: (Reply) -> Unit ): io.zenoh.jni.query.ReplyCallback = - io.zenoh.jni.query.ReplyCallback { zid, eid, isOk, keH, payloadH, encH, encId, kindInt, ntp64, express, prioInt, ccInt, attachH, reliabilityInt, sourceZid, sourceEid, sourceSn, errPayloadH, errEncH, errEncId -> + io.zenoh.jni.query.ReplyCallback { zid, eid, isOk, keH, payloadH, encId, encSchema, kindInt, ntp64, express, prioInt, ccInt, attachH, reliabilityInt, sourceZid, sourceEid, sourceSn, errPayloadH, errEncId, errEncSchema -> val replierId = zid?.let { EntityGlobalId(ZenohId(it), eid.toUInt()) } f( if (isOk) { Reply.Success( replierId, - Sample.fromParts(keH!!, payloadH!!, encH!!, encId!!, kindInt!!, ntp64, express!!, prioInt!!, ccInt!!, attachH, reliabilityInt!!, sourceZid, sourceEid!!, sourceSn!!) + Sample.fromParts(keH!!, payloadH!!, encId!!, encSchema, kindInt!!, ntp64, express!!, prioInt!!, ccInt!!, attachH, reliabilityInt!!, sourceZid, sourceEid!!, sourceSn!!) ) } else { Reply.Error( replierId, ZBytes.fromHandle(errPayloadH!!), - errEncH?.let { Encoding.fromParts(it, errEncId!!) } ?: Encoding.defaultEncoding() + errEncId?.let { Encoding(it, errEncSchema) } ?: Encoding.defaultEncoding() ) } ) diff --git a/zenoh-java/src/commonMain/kotlin/io/zenoh/Session.kt b/zenoh-java/src/commonMain/kotlin/io/zenoh/Session.kt index 10473468..846dec59 100644 --- a/zenoh-java/src/commonMain/kotlin/io/zenoh/Session.kt +++ b/zenoh-java/src/commonMain/kotlin/io/zenoh/Session.kt @@ -587,8 +587,14 @@ class Session private constructor(private val config: Config) : AutoCloseable { internal fun resolvePublisher(keyExpr: KeyExpr, options: PublisherOptions): Publisher { val zSession = zSession ?: throw sessionClosedException val publisher = run { + // The publisher's default encoding is set NATIVELY here, once — + // plain puts then cross no encoding data at all (see Publisher). + val enc = options.encoding val zPublisher = zSession.declarePublisher( keyExpr.cloneFlat(), + true, + enc.id, + enc.schema, options.congestionControl.jni, options.priority.jni, options.express, @@ -722,7 +728,7 @@ class Session private constructor(private val config: Config) : AutoCloseable { val zSession = zSession ?: throw sessionClosedException return run { val sel = selector.into() - val enc = options.encoding ?: Encoding.defaultEncoding() + val enc = options.encoding zSession.get( sel.keyExpr.flat, sel.parameters?.toString(), @@ -734,9 +740,9 @@ class Session private constructor(private val config: Config) : AutoCloseable { options.qos.priority.jni, options.qos.express, options.payload?.into()?.bytes, - true, - enc.idForWire(), - enc.schemaForWire(), + enc != null, + enc?.id ?: 0, + enc?.schema, options.attachment?.into()?.bytes, replyCallbackOf { handler.handle(it) }, { handler.onClose() }, @@ -755,7 +761,7 @@ class Session private constructor(private val config: Config) : AutoCloseable { val zSession = zSession ?: throw sessionClosedException run { val sel = selector.into() - val enc = options.encoding ?: Encoding.defaultEncoding() + val enc = options.encoding zSession.get( sel.keyExpr.flat, sel.parameters?.toString(), @@ -767,9 +773,9 @@ class Session private constructor(private val config: Config) : AutoCloseable { options.qos.priority.jni, options.qos.express, options.payload?.into()?.bytes, - true, - enc.idForWire(), - enc.schemaForWire(), + enc != null, + enc?.id ?: 0, + enc?.schema, options.attachment?.into()?.bytes, replyCallbackOf { callback.run(it) }, { }, @@ -782,13 +788,13 @@ class Session private constructor(private val config: Config) : AutoCloseable { internal fun resolvePut(keyExpr: KeyExpr, payload: IntoZBytes, putOptions: PutOptions) { val zSession = zSession ?: return run { - val enc = putOptions.encoding ?: Encoding.defaultEncoding() + val enc = putOptions.encoding zSession.put( keyExpr.flat, payload.into().bytes, - true, - enc.idForWire(), - enc.schemaForWire(), + enc != null, + enc?.id ?: 0, + enc?.schema, putOptions.congestionControl.jni, putOptions.priority.jni, putOptions.express, diff --git a/zenoh-java/src/commonMain/kotlin/io/zenoh/bytes/Encoding.kt b/zenoh-java/src/commonMain/kotlin/io/zenoh/bytes/Encoding.kt index affd91ed..dfa0e19c 100644 --- a/zenoh-java/src/commonMain/kotlin/io/zenoh/bytes/Encoding.kt +++ b/zenoh-java/src/commonMain/kotlin/io/zenoh/bytes/Encoding.kt @@ -14,114 +14,7 @@ package io.zenoh.bytes -import io.zenoh.exceptions.throwZError0 -import io.zenoh.jni.bytes.ENCODING_APPLICATION_CBOR -import io.zenoh.jni.bytes.ENCODING_APPLICATION_CBOR_ID -import io.zenoh.jni.bytes.ENCODING_APPLICATION_CDR -import io.zenoh.jni.bytes.ENCODING_APPLICATION_CDR_ID -import io.zenoh.jni.bytes.ENCODING_APPLICATION_COAP_PAYLOAD -import io.zenoh.jni.bytes.ENCODING_APPLICATION_COAP_PAYLOAD_ID -import io.zenoh.jni.bytes.ENCODING_APPLICATION_JAVA_SERIALIZED_OBJECT -import io.zenoh.jni.bytes.ENCODING_APPLICATION_JAVA_SERIALIZED_OBJECT_ID -import io.zenoh.jni.bytes.ENCODING_APPLICATION_JSON -import io.zenoh.jni.bytes.ENCODING_APPLICATION_JSONPATH -import io.zenoh.jni.bytes.ENCODING_APPLICATION_JSONPATH_ID -import io.zenoh.jni.bytes.ENCODING_APPLICATION_JSON_ID -import io.zenoh.jni.bytes.ENCODING_APPLICATION_JSON_PATCH_JSON -import io.zenoh.jni.bytes.ENCODING_APPLICATION_JSON_PATCH_JSON_ID -import io.zenoh.jni.bytes.ENCODING_APPLICATION_JSON_SEQ -import io.zenoh.jni.bytes.ENCODING_APPLICATION_JSON_SEQ_ID -import io.zenoh.jni.bytes.ENCODING_APPLICATION_JWT -import io.zenoh.jni.bytes.ENCODING_APPLICATION_JWT_ID -import io.zenoh.jni.bytes.ENCODING_APPLICATION_MP4 -import io.zenoh.jni.bytes.ENCODING_APPLICATION_MP4_ID -import io.zenoh.jni.bytes.ENCODING_APPLICATION_OCTET_STREAM -import io.zenoh.jni.bytes.ENCODING_APPLICATION_OCTET_STREAM_ID -import io.zenoh.jni.bytes.ENCODING_APPLICATION_OPENMETRICS_TEXT -import io.zenoh.jni.bytes.ENCODING_APPLICATION_OPENMETRICS_TEXT_ID -import io.zenoh.jni.bytes.ENCODING_APPLICATION_PROTOBUF -import io.zenoh.jni.bytes.ENCODING_APPLICATION_PROTOBUF_ID -import io.zenoh.jni.bytes.ENCODING_APPLICATION_PYTHON_SERIALIZED_OBJECT -import io.zenoh.jni.bytes.ENCODING_APPLICATION_PYTHON_SERIALIZED_OBJECT_ID -import io.zenoh.jni.bytes.ENCODING_APPLICATION_SOAP_XML -import io.zenoh.jni.bytes.ENCODING_APPLICATION_SOAP_XML_ID -import io.zenoh.jni.bytes.ENCODING_APPLICATION_SQL -import io.zenoh.jni.bytes.ENCODING_APPLICATION_SQL_ID -import io.zenoh.jni.bytes.ENCODING_APPLICATION_XML -import io.zenoh.jni.bytes.ENCODING_APPLICATION_XML_ID -import io.zenoh.jni.bytes.ENCODING_APPLICATION_X_WWW_FORM_URLENCODED -import io.zenoh.jni.bytes.ENCODING_APPLICATION_X_WWW_FORM_URLENCODED_ID -import io.zenoh.jni.bytes.ENCODING_APPLICATION_YAML -import io.zenoh.jni.bytes.ENCODING_APPLICATION_YAML_ID -import io.zenoh.jni.bytes.ENCODING_APPLICATION_YANG -import io.zenoh.jni.bytes.ENCODING_APPLICATION_YANG_ID -import io.zenoh.jni.bytes.ENCODING_AUDIO_AAC -import io.zenoh.jni.bytes.ENCODING_AUDIO_AAC_ID -import io.zenoh.jni.bytes.ENCODING_AUDIO_FLAC -import io.zenoh.jni.bytes.ENCODING_AUDIO_FLAC_ID -import io.zenoh.jni.bytes.ENCODING_AUDIO_MP4 -import io.zenoh.jni.bytes.ENCODING_AUDIO_MP4_ID -import io.zenoh.jni.bytes.ENCODING_AUDIO_OGG -import io.zenoh.jni.bytes.ENCODING_AUDIO_OGG_ID -import io.zenoh.jni.bytes.ENCODING_AUDIO_VORBIS -import io.zenoh.jni.bytes.ENCODING_AUDIO_VORBIS_ID -import io.zenoh.jni.bytes.ENCODING_IMAGE_BMP -import io.zenoh.jni.bytes.ENCODING_IMAGE_BMP_ID -import io.zenoh.jni.bytes.ENCODING_IMAGE_GIF -import io.zenoh.jni.bytes.ENCODING_IMAGE_GIF_ID -import io.zenoh.jni.bytes.ENCODING_IMAGE_JPEG -import io.zenoh.jni.bytes.ENCODING_IMAGE_JPEG_ID -import io.zenoh.jni.bytes.ENCODING_IMAGE_PNG -import io.zenoh.jni.bytes.ENCODING_IMAGE_PNG_ID -import io.zenoh.jni.bytes.ENCODING_IMAGE_WEBP -import io.zenoh.jni.bytes.ENCODING_IMAGE_WEBP_ID -import io.zenoh.jni.bytes.ENCODING_TEXT_CSS -import io.zenoh.jni.bytes.ENCODING_TEXT_CSS_ID -import io.zenoh.jni.bytes.ENCODING_TEXT_CSV -import io.zenoh.jni.bytes.ENCODING_TEXT_CSV_ID -import io.zenoh.jni.bytes.ENCODING_TEXT_HTML -import io.zenoh.jni.bytes.ENCODING_TEXT_HTML_ID -import io.zenoh.jni.bytes.ENCODING_TEXT_JAVASCRIPT -import io.zenoh.jni.bytes.ENCODING_TEXT_JAVASCRIPT_ID -import io.zenoh.jni.bytes.ENCODING_TEXT_JSON -import io.zenoh.jni.bytes.ENCODING_TEXT_JSON5 -import io.zenoh.jni.bytes.ENCODING_TEXT_JSON5_ID -import io.zenoh.jni.bytes.ENCODING_TEXT_JSON_ID -import io.zenoh.jni.bytes.ENCODING_TEXT_MARKDOWN -import io.zenoh.jni.bytes.ENCODING_TEXT_MARKDOWN_ID -import io.zenoh.jni.bytes.ENCODING_TEXT_PLAIN -import io.zenoh.jni.bytes.ENCODING_TEXT_PLAIN_ID -import io.zenoh.jni.bytes.ENCODING_TEXT_XML -import io.zenoh.jni.bytes.ENCODING_TEXT_XML_ID -import io.zenoh.jni.bytes.ENCODING_TEXT_YAML -import io.zenoh.jni.bytes.ENCODING_TEXT_YAML_ID -import io.zenoh.jni.bytes.ENCODING_VIDEO_H261 -import io.zenoh.jni.bytes.ENCODING_VIDEO_H261_ID -import io.zenoh.jni.bytes.ENCODING_VIDEO_H263 -import io.zenoh.jni.bytes.ENCODING_VIDEO_H263_ID -import io.zenoh.jni.bytes.ENCODING_VIDEO_H264 -import io.zenoh.jni.bytes.ENCODING_VIDEO_H264_ID -import io.zenoh.jni.bytes.ENCODING_VIDEO_H265 -import io.zenoh.jni.bytes.ENCODING_VIDEO_H265_ID -import io.zenoh.jni.bytes.ENCODING_VIDEO_H266 -import io.zenoh.jni.bytes.ENCODING_VIDEO_H266_ID -import io.zenoh.jni.bytes.ENCODING_VIDEO_MP4 -import io.zenoh.jni.bytes.ENCODING_VIDEO_MP4_ID -import io.zenoh.jni.bytes.ENCODING_VIDEO_OGG -import io.zenoh.jni.bytes.ENCODING_VIDEO_OGG_ID -import io.zenoh.jni.bytes.ENCODING_VIDEO_RAW -import io.zenoh.jni.bytes.ENCODING_VIDEO_RAW_ID -import io.zenoh.jni.bytes.ENCODING_VIDEO_VP8 -import io.zenoh.jni.bytes.ENCODING_VIDEO_VP8_ID -import io.zenoh.jni.bytes.ENCODING_VIDEO_VP9 -import io.zenoh.jni.bytes.ENCODING_VIDEO_VP9_ID -import io.zenoh.jni.bytes.ENCODING_ZENOH_BYTES -import io.zenoh.jni.bytes.ENCODING_ZENOH_BYTES_ID -import io.zenoh.jni.bytes.ENCODING_ZENOH_SERIALIZED -import io.zenoh.jni.bytes.ENCODING_ZENOH_SERIALIZED_ID -import io.zenoh.jni.bytes.ENCODING_ZENOH_STRING -import io.zenoh.jni.bytes.ENCODING_ZENOH_STRING_ID -import io.zenoh.jni.bytes.Encoding as JniEncoding +import io.zenoh.jni.bytes.* /** * Default encoding values used by Zenoh. @@ -133,157 +26,72 @@ import io.zenoh.jni.bytes.Encoding as JniEncoding * * A set of associated constants are provided to cover the most common encodings for user convenience. * - * Internally the encoding is kept as its canonical textual form ([repr], e.g. - * `"text/plain"` or `"text/plain;utf-8"`); a native `ZEncoding` handle is built - * on demand for each native crossing (the raw `z_*` API takes the encoding by - * reference, so the transient handle is closed by the caller after the call). + * An encoding **is** its decomposed pair `(id, schema)` — Zenoh's own + * representation. This class is a plain immutable value; all conversion logic + * (the id↔name table and Zenoh's parse/render rules) lives in the shared + * bindings tier ([EncodingCodec]), verified against the native implementation + * by `EncodingCorrespondenceTest`. No native handle, no native calls, nothing + * to close. */ -class Encoding private constructor( - private var reprLazy: String?, - internal val id: Int?, - private val handle: JniEncoding?, +class Encoding internal constructor( + internal val id: Int, + internal val schema: String? = null, ) { - internal constructor(repr: String) : this(repr, null, null) - - private var schemaLazy: String? = null - private var schemaKnown: Boolean = false - private var idCached: Int = id ?: 0 - private var idKnown: Boolean = id != null - - /** - * Ensure the lossless decomposed form `(id, schema)` is cached. Zenoh's - * encoding IS `(id, schema)`, so this loses nothing. A handle-backed - * (received) Encoding already knows its [id]; its schema is read LAZILY - * through the handle on first need. A predefined constant is born fully - * decomposed (repr + id from the generated consts, schema known-absent) and - * never enters this path. A repr-primary Encoding built by [from] derives - * BOTH once from a transient handle built off [repr], then frees it — - * caching pure JVM values and retaining no native handle. The cache is - * reused across every native crossing, so a reused - * encoding (the normal case — a publisher publishes one data type) never - * re-parses its string per call. - */ - private fun ensureDecomposed() { - if (idKnown && schemaKnown) return - synchronized(this) { - if (idKnown && schemaKnown) return - if (handle != null) { - if (!schemaKnown) { - schemaLazy = handle.getSchema(throwZError0) - schemaKnown = true - } - } else { - val h = JniEncoding.newFromString(repr, throwZError0) - try { - if (!idKnown) { - idCached = h.getId(throwZError0) - idKnown = true - } - if (!schemaKnown) { - schemaLazy = h.getSchema(throwZError0) - schemaKnown = true - } - } finally { - h.close() - } - } - } - } - - /** - * Encoding id for the OUTBOUND native crossing. Cached once (see - * [ensureDecomposed]) and reused across every put/get/reply — the wire - * carries this cheap primitive instead of a freshly parsed string. - */ - internal fun idForWire(): Int { - ensureDecomposed() - return idCached - } - - /** Optional schema for the OUTBOUND native crossing. Cached once. */ - internal fun schemaForWire(): String? { - ensureDecomposed() - return schemaLazy - } - - /** - * Canonical display string. A handle-backed (received) Encoding - * materializes it LAZILY on first use (toString/equals) via the native - * accessor — received encodings are usually only forwarded or compared by - * id, so the common path never builds the string (forward-extraction rule). - */ - internal val repr: String - get() = reprLazy ?: synchronized(this) { - reprLazy - ?: handle!!.toStr(throwZError0) - .also { reprLazy = it } - } - companion object { - /** - * A predefined constant: its canonical string and wire id come from - * the generated `ENCODING_*` consts (single source of truth in Rust), - * and a predefined encoding never carries a schema — the decomposed - * `(id, schema)` form is fully known up front, so these constants - * never touch native for decomposition (see [ensureDecomposed]). - */ - private fun predefined(repr: String, id: Int): Encoding = - Encoding(repr, id, null).apply { schemaKnown = true } - - @JvmField val ZENOH_BYTES = predefined(ENCODING_ZENOH_BYTES, ENCODING_ZENOH_BYTES_ID) - @JvmField val ZENOH_STRING = predefined(ENCODING_ZENOH_STRING, ENCODING_ZENOH_STRING_ID) - @JvmField val ZENOH_SERIALIZED = predefined(ENCODING_ZENOH_SERIALIZED, ENCODING_ZENOH_SERIALIZED_ID) - @JvmField val APPLICATION_OCTET_STREAM = predefined(ENCODING_APPLICATION_OCTET_STREAM, ENCODING_APPLICATION_OCTET_STREAM_ID) - @JvmField val TEXT_PLAIN = predefined(ENCODING_TEXT_PLAIN, ENCODING_TEXT_PLAIN_ID) - @JvmField val APPLICATION_JSON = predefined(ENCODING_APPLICATION_JSON, ENCODING_APPLICATION_JSON_ID) - @JvmField val TEXT_JSON = predefined(ENCODING_TEXT_JSON, ENCODING_TEXT_JSON_ID) - @JvmField val APPLICATION_CDR = predefined(ENCODING_APPLICATION_CDR, ENCODING_APPLICATION_CDR_ID) - @JvmField val APPLICATION_CBOR = predefined(ENCODING_APPLICATION_CBOR, ENCODING_APPLICATION_CBOR_ID) - @JvmField val APPLICATION_YAML = predefined(ENCODING_APPLICATION_YAML, ENCODING_APPLICATION_YAML_ID) - @JvmField val TEXT_YAML = predefined(ENCODING_TEXT_YAML, ENCODING_TEXT_YAML_ID) - @JvmField val TEXT_JSON5 = predefined(ENCODING_TEXT_JSON5, ENCODING_TEXT_JSON5_ID) - @JvmField val APPLICATION_PYTHON_SERIALIZED_OBJECT = predefined(ENCODING_APPLICATION_PYTHON_SERIALIZED_OBJECT, ENCODING_APPLICATION_PYTHON_SERIALIZED_OBJECT_ID) - @JvmField val APPLICATION_PROTOBUF = predefined(ENCODING_APPLICATION_PROTOBUF, ENCODING_APPLICATION_PROTOBUF_ID) - @JvmField val APPLICATION_JAVA_SERIALIZED_OBJECT = predefined(ENCODING_APPLICATION_JAVA_SERIALIZED_OBJECT, ENCODING_APPLICATION_JAVA_SERIALIZED_OBJECT_ID) - @JvmField val APPLICATION_OPENMETRICS_TEXT = predefined(ENCODING_APPLICATION_OPENMETRICS_TEXT, ENCODING_APPLICATION_OPENMETRICS_TEXT_ID) - @JvmField val IMAGE_PNG = predefined(ENCODING_IMAGE_PNG, ENCODING_IMAGE_PNG_ID) - @JvmField val IMAGE_JPEG = predefined(ENCODING_IMAGE_JPEG, ENCODING_IMAGE_JPEG_ID) - @JvmField val IMAGE_GIF = predefined(ENCODING_IMAGE_GIF, ENCODING_IMAGE_GIF_ID) - @JvmField val IMAGE_BMP = predefined(ENCODING_IMAGE_BMP, ENCODING_IMAGE_BMP_ID) - @JvmField val IMAGE_WEBP = predefined(ENCODING_IMAGE_WEBP, ENCODING_IMAGE_WEBP_ID) - @JvmField val APPLICATION_XML = predefined(ENCODING_APPLICATION_XML, ENCODING_APPLICATION_XML_ID) - @JvmField val APPLICATION_X_WWW_FORM_URLENCODED = predefined(ENCODING_APPLICATION_X_WWW_FORM_URLENCODED, ENCODING_APPLICATION_X_WWW_FORM_URLENCODED_ID) - @JvmField val TEXT_HTML = predefined(ENCODING_TEXT_HTML, ENCODING_TEXT_HTML_ID) - @JvmField val TEXT_XML = predefined(ENCODING_TEXT_XML, ENCODING_TEXT_XML_ID) - @JvmField val TEXT_CSS = predefined(ENCODING_TEXT_CSS, ENCODING_TEXT_CSS_ID) - @JvmField val TEXT_JAVASCRIPT = predefined(ENCODING_TEXT_JAVASCRIPT, ENCODING_TEXT_JAVASCRIPT_ID) - @JvmField val TEXT_MARKDOWN = predefined(ENCODING_TEXT_MARKDOWN, ENCODING_TEXT_MARKDOWN_ID) - @JvmField val TEXT_CSV = predefined(ENCODING_TEXT_CSV, ENCODING_TEXT_CSV_ID) - @JvmField val APPLICATION_SQL = predefined(ENCODING_APPLICATION_SQL, ENCODING_APPLICATION_SQL_ID) - @JvmField val APPLICATION_COAP_PAYLOAD = predefined(ENCODING_APPLICATION_COAP_PAYLOAD, ENCODING_APPLICATION_COAP_PAYLOAD_ID) - @JvmField val APPLICATION_JSON_PATCH_JSON = predefined(ENCODING_APPLICATION_JSON_PATCH_JSON, ENCODING_APPLICATION_JSON_PATCH_JSON_ID) - @JvmField val APPLICATION_JSON_SEQ = predefined(ENCODING_APPLICATION_JSON_SEQ, ENCODING_APPLICATION_JSON_SEQ_ID) - @JvmField val APPLICATION_JSONPATH = predefined(ENCODING_APPLICATION_JSONPATH, ENCODING_APPLICATION_JSONPATH_ID) - @JvmField val APPLICATION_JWT = predefined(ENCODING_APPLICATION_JWT, ENCODING_APPLICATION_JWT_ID) - @JvmField val APPLICATION_MP4 = predefined(ENCODING_APPLICATION_MP4, ENCODING_APPLICATION_MP4_ID) - @JvmField val APPLICATION_SOAP_XML = predefined(ENCODING_APPLICATION_SOAP_XML, ENCODING_APPLICATION_SOAP_XML_ID) - @JvmField val APPLICATION_YANG = predefined(ENCODING_APPLICATION_YANG, ENCODING_APPLICATION_YANG_ID) - @JvmField val AUDIO_AAC = predefined(ENCODING_AUDIO_AAC, ENCODING_AUDIO_AAC_ID) - @JvmField val AUDIO_FLAC = predefined(ENCODING_AUDIO_FLAC, ENCODING_AUDIO_FLAC_ID) - @JvmField val AUDIO_MP4 = predefined(ENCODING_AUDIO_MP4, ENCODING_AUDIO_MP4_ID) - @JvmField val AUDIO_OGG = predefined(ENCODING_AUDIO_OGG, ENCODING_AUDIO_OGG_ID) - @JvmField val AUDIO_VORBIS = predefined(ENCODING_AUDIO_VORBIS, ENCODING_AUDIO_VORBIS_ID) - @JvmField val VIDEO_H261 = predefined(ENCODING_VIDEO_H261, ENCODING_VIDEO_H261_ID) - @JvmField val VIDEO_H263 = predefined(ENCODING_VIDEO_H263, ENCODING_VIDEO_H263_ID) - @JvmField val VIDEO_H264 = predefined(ENCODING_VIDEO_H264, ENCODING_VIDEO_H264_ID) - @JvmField val VIDEO_H265 = predefined(ENCODING_VIDEO_H265, ENCODING_VIDEO_H265_ID) - @JvmField val VIDEO_H266 = predefined(ENCODING_VIDEO_H266, ENCODING_VIDEO_H266_ID) - @JvmField val VIDEO_MP4 = predefined(ENCODING_VIDEO_MP4, ENCODING_VIDEO_MP4_ID) - @JvmField val VIDEO_OGG = predefined(ENCODING_VIDEO_OGG, ENCODING_VIDEO_OGG_ID) - @JvmField val VIDEO_RAW = predefined(ENCODING_VIDEO_RAW, ENCODING_VIDEO_RAW_ID) - @JvmField val VIDEO_VP8 = predefined(ENCODING_VIDEO_VP8, ENCODING_VIDEO_VP8_ID) - @JvmField val VIDEO_VP9 = predefined(ENCODING_VIDEO_VP9, ENCODING_VIDEO_VP9_ID) + @JvmField val ZENOH_BYTES = Encoding(ENCODING_ZENOH_BYTES_ID) + @JvmField val ZENOH_STRING = Encoding(ENCODING_ZENOH_STRING_ID) + @JvmField val ZENOH_SERIALIZED = Encoding(ENCODING_ZENOH_SERIALIZED_ID) + @JvmField val APPLICATION_OCTET_STREAM = Encoding(ENCODING_APPLICATION_OCTET_STREAM_ID) + @JvmField val TEXT_PLAIN = Encoding(ENCODING_TEXT_PLAIN_ID) + @JvmField val APPLICATION_JSON = Encoding(ENCODING_APPLICATION_JSON_ID) + @JvmField val TEXT_JSON = Encoding(ENCODING_TEXT_JSON_ID) + @JvmField val APPLICATION_CDR = Encoding(ENCODING_APPLICATION_CDR_ID) + @JvmField val APPLICATION_CBOR = Encoding(ENCODING_APPLICATION_CBOR_ID) + @JvmField val APPLICATION_YAML = Encoding(ENCODING_APPLICATION_YAML_ID) + @JvmField val TEXT_YAML = Encoding(ENCODING_TEXT_YAML_ID) + @JvmField val TEXT_JSON5 = Encoding(ENCODING_TEXT_JSON5_ID) + @JvmField val APPLICATION_PYTHON_SERIALIZED_OBJECT = Encoding(ENCODING_APPLICATION_PYTHON_SERIALIZED_OBJECT_ID) + @JvmField val APPLICATION_PROTOBUF = Encoding(ENCODING_APPLICATION_PROTOBUF_ID) + @JvmField val APPLICATION_JAVA_SERIALIZED_OBJECT = Encoding(ENCODING_APPLICATION_JAVA_SERIALIZED_OBJECT_ID) + @JvmField val APPLICATION_OPENMETRICS_TEXT = Encoding(ENCODING_APPLICATION_OPENMETRICS_TEXT_ID) + @JvmField val IMAGE_PNG = Encoding(ENCODING_IMAGE_PNG_ID) + @JvmField val IMAGE_JPEG = Encoding(ENCODING_IMAGE_JPEG_ID) + @JvmField val IMAGE_GIF = Encoding(ENCODING_IMAGE_GIF_ID) + @JvmField val IMAGE_BMP = Encoding(ENCODING_IMAGE_BMP_ID) + @JvmField val IMAGE_WEBP = Encoding(ENCODING_IMAGE_WEBP_ID) + @JvmField val APPLICATION_XML = Encoding(ENCODING_APPLICATION_XML_ID) + @JvmField val APPLICATION_X_WWW_FORM_URLENCODED = Encoding(ENCODING_APPLICATION_X_WWW_FORM_URLENCODED_ID) + @JvmField val TEXT_HTML = Encoding(ENCODING_TEXT_HTML_ID) + @JvmField val TEXT_XML = Encoding(ENCODING_TEXT_XML_ID) + @JvmField val TEXT_CSS = Encoding(ENCODING_TEXT_CSS_ID) + @JvmField val TEXT_JAVASCRIPT = Encoding(ENCODING_TEXT_JAVASCRIPT_ID) + @JvmField val TEXT_MARKDOWN = Encoding(ENCODING_TEXT_MARKDOWN_ID) + @JvmField val TEXT_CSV = Encoding(ENCODING_TEXT_CSV_ID) + @JvmField val APPLICATION_SQL = Encoding(ENCODING_APPLICATION_SQL_ID) + @JvmField val APPLICATION_COAP_PAYLOAD = Encoding(ENCODING_APPLICATION_COAP_PAYLOAD_ID) + @JvmField val APPLICATION_JSON_PATCH_JSON = Encoding(ENCODING_APPLICATION_JSON_PATCH_JSON_ID) + @JvmField val APPLICATION_JSON_SEQ = Encoding(ENCODING_APPLICATION_JSON_SEQ_ID) + @JvmField val APPLICATION_JSONPATH = Encoding(ENCODING_APPLICATION_JSONPATH_ID) + @JvmField val APPLICATION_JWT = Encoding(ENCODING_APPLICATION_JWT_ID) + @JvmField val APPLICATION_MP4 = Encoding(ENCODING_APPLICATION_MP4_ID) + @JvmField val APPLICATION_SOAP_XML = Encoding(ENCODING_APPLICATION_SOAP_XML_ID) + @JvmField val APPLICATION_YANG = Encoding(ENCODING_APPLICATION_YANG_ID) + @JvmField val AUDIO_AAC = Encoding(ENCODING_AUDIO_AAC_ID) + @JvmField val AUDIO_FLAC = Encoding(ENCODING_AUDIO_FLAC_ID) + @JvmField val AUDIO_MP4 = Encoding(ENCODING_AUDIO_MP4_ID) + @JvmField val AUDIO_OGG = Encoding(ENCODING_AUDIO_OGG_ID) + @JvmField val AUDIO_VORBIS = Encoding(ENCODING_AUDIO_VORBIS_ID) + @JvmField val VIDEO_H261 = Encoding(ENCODING_VIDEO_H261_ID) + @JvmField val VIDEO_H263 = Encoding(ENCODING_VIDEO_H263_ID) + @JvmField val VIDEO_H264 = Encoding(ENCODING_VIDEO_H264_ID) + @JvmField val VIDEO_H265 = Encoding(ENCODING_VIDEO_H265_ID) + @JvmField val VIDEO_H266 = Encoding(ENCODING_VIDEO_H266_ID) + @JvmField val VIDEO_MP4 = Encoding(ENCODING_VIDEO_MP4_ID) + @JvmField val VIDEO_OGG = Encoding(ENCODING_VIDEO_OGG_ID) + @JvmField val VIDEO_RAW = Encoding(ENCODING_VIDEO_RAW_ID) + @JvmField val VIDEO_VP8 = Encoding(ENCODING_VIDEO_VP8_ID) + @JvmField val VIDEO_VP9 = Encoding(ENCODING_VIDEO_VP9_ID) /** The default [Encoding] is [ZENOH_BYTES]. */ @JvmStatic fun defaultEncoding() = ZENOH_BYTES @@ -293,61 +101,30 @@ class Encoding private constructor( * `"my_encoding"`). Well-known names resolve to their canonical id; * everything else is preserved as a custom encoding. */ - @JvmStatic fun from(s: String): Encoding = Encoding(s) - - /** - * Decodes a native `ZEncoding` handle into a value [Encoding] and frees - * the handle. Used when an accessor / callback hands back an encoding. - */ - internal fun fromHandle(handle: JniEncoding): Encoding { - try { - return Encoding(handle.toStr(throwZError0)) - } finally { - handle.close() - } + @JvmStatic fun from(s: String): Encoding { + val (id, schema) = EncodingCodec.parse(s) + return Encoding(id, schema) } - - /** Wrap the decomposed `(handle, id)` leaves. Schema and the - * canonical string stay lazy through the handle. */ - internal fun fromParts(encH: JniEncoding, id: Int): Encoding = - Encoding(null, id, encH) } - /** - * Builds a fresh native `ZEncoding` handle from [repr]. The raw `z_*` - * encoding parameters take it **by reference** (not consumed), so the - * caller MUST close the returned handle after the native call. - */ - internal fun toZEncoding(): JniEncoding = - if (handle != null) { - handle.newClone(throwZError0) - } else { - JniEncoding.newFromString(repr, throwZError0) - } - /** * Set a schema to this encoding. Zenoh does not define what a schema is and its semantics is left to the implementer. * E.g. a common schema for `text/plain` encoding is `utf-8`. */ fun withSchema(schema: String): Encoding { - // `withSchema` takes the base encoding flattened to `(id, schema)`; this - // Encoding already exposes that decomposition lazily. - val withSchema = JniEncoding.newWithSchema(idForWire(), schemaForWire(), schema, throwZError0) - try { - return Encoding(withSchema.toStr(throwZError0)) - } finally { - withSchema.close() - } + val (nid, nschema) = EncodingCodec.withSchema(id, this.schema, schema) + return Encoding(nid, nschema) } - override fun toString(): String = repr + /** Canonical textual form. */ + override fun toString(): String = EncodingCodec.render(id, schema) override fun equals(other: Any?): Boolean { if (this === other) return true if (javaClass != other?.javaClass) return false other as Encoding - return repr == other.repr + return id == other.id && schema == other.schema } - override fun hashCode(): Int = repr.hashCode() + override fun hashCode(): Int = 31 * id + (schema?.hashCode() ?: 0) } diff --git a/zenoh-java/src/commonMain/kotlin/io/zenoh/config/ZenohId.kt b/zenoh-java/src/commonMain/kotlin/io/zenoh/config/ZenohId.kt index ff2b59f9..5f3c34ae 100644 --- a/zenoh-java/src/commonMain/kotlin/io/zenoh/config/ZenohId.kt +++ b/zenoh-java/src/commonMain/kotlin/io/zenoh/config/ZenohId.kt @@ -14,8 +14,8 @@ package io.zenoh.config -import io.zenoh.exceptions.throwZError0 import io.zenoh.jni.config.ZenohId as JniZenohId +import io.zenoh.jni.config.zidString import kotlin.math.absoluteValue /** @@ -23,9 +23,12 @@ import kotlin.math.absoluteValue */ data class ZenohId internal constructor(internal val inner: JniZenohId) { - override fun toString(): String { - return inner.toStr(throwZError0) - } + /** + * The standard string form, computed in pure Kotlin by the shared + * bindings tier ([zidString]); correspondence with the native formatter + * is verified by `ZenohIdCorrespondenceTest`. + */ + override fun toString(): String = inner.zidString() override fun equals(other: Any?): Boolean { if (this === other) return true diff --git a/zenoh-java/src/commonMain/kotlin/io/zenoh/pubsub/Publisher.kt b/zenoh-java/src/commonMain/kotlin/io/zenoh/pubsub/Publisher.kt index 523b1cae..039dc1ac 100644 --- a/zenoh-java/src/commonMain/kotlin/io/zenoh/pubsub/Publisher.kt +++ b/zenoh-java/src/commonMain/kotlin/io/zenoh/pubsub/Publisher.kt @@ -80,13 +80,15 @@ class Publisher internal constructor( /** Performs a PUT operation on the specified [keyExpr] with the specified [payload]. */ @Throws(ZError::class) fun put(payload: IntoZBytes) { - performPut(payload, encoding, null) + // No per-call encoding: the publisher's default encoding — set + // NATIVELY at declare time — applies, so no encoding data crosses. + performPut(payload, null, null) } /** Performs a PUT operation on the specified [keyExpr] with the specified [payload]. */ @Throws(ZError::class) fun put(payload: IntoZBytes, options: PutOptions) { - performPut(payload, options.encoding ?: this.encoding, options.attachment) + performPut(payload, options.encoding, options.attachment) } /** Performs a PUT operation on the specified [keyExpr] with the specified [payload]. */ @@ -129,13 +131,15 @@ class Publisher internal constructor( } @Throws(ZError::class) - private fun performPut(payload: IntoZBytes, encoding: Encoding, attachment: IntoZBytes?) { + private fun performPut(payload: IntoZBytes, encoding: Encoding?, attachment: IntoZBytes?) { val p = zPublisher ?: throw publisherNotValid + // `null` encoding = absent: the publisher's default encoding — set + // NATIVELY at declare time — applies, and no encoding data crosses. p.put( payload.into().bytes, - true, - encoding.idForWire(), - encoding.schemaForWire(), + encoding != null, + encoding?.id ?: 0, + encoding?.schema, attachment?.into()?.bytes, throwZError, ) diff --git a/zenoh-java/src/commonMain/kotlin/io/zenoh/query/Querier.kt b/zenoh-java/src/commonMain/kotlin/io/zenoh/query/Querier.kt index 37945776..65fde3ca 100644 --- a/zenoh-java/src/commonMain/kotlin/io/zenoh/query/Querier.kt +++ b/zenoh-java/src/commonMain/kotlin/io/zenoh/query/Querier.kt @@ -145,13 +145,13 @@ class Querier internal constructor(val keyExpr: KeyExpr, val qos: QoS, private v private fun resolveGetWithCallback(callback: Callback, options: GetOptions) { val q = zQuerier ?: throw ZError("Querier is not valid.") - val enc = options.encoding ?: Encoding.defaultEncoding() + val enc = options.encoding q.get( options.parameters?.toString(), options.payload?.into()?.bytes, - true, - enc.idForWire(), - enc.schemaForWire(), + enc != null, + enc?.id ?: 0, + enc?.schema, options.attachment?.into()?.bytes, replyCallbackOf { callback.run(it) }, { }, @@ -161,13 +161,13 @@ class Querier internal constructor(val keyExpr: KeyExpr, val qos: QoS, private v private fun resolveGetWithHandler(handler: Handler, options: GetOptions): R { val q = zQuerier ?: throw ZError("Querier is not valid.") - val enc = options.encoding ?: Encoding.defaultEncoding() + val enc = options.encoding q.get( options.parameters?.toString(), options.payload?.into()?.bytes, - true, - enc.idForWire(), - enc.schemaForWire(), + enc != null, + enc?.id ?: 0, + enc?.schema, options.attachment?.into()?.bytes, replyCallbackOf { handler.handle(it) }, { handler.onClose() }, diff --git a/zenoh-java/src/commonMain/kotlin/io/zenoh/query/Query.kt b/zenoh-java/src/commonMain/kotlin/io/zenoh/query/Query.kt index e523bf8c..895540bb 100644 --- a/zenoh-java/src/commonMain/kotlin/io/zenoh/query/Query.kt +++ b/zenoh-java/src/commonMain/kotlin/io/zenoh/query/Query.kt @@ -59,8 +59,8 @@ class Query internal constructor( keH: io.zenoh.jni.keyexpr.KeyExpr, parameters: String, payloadH: io.zenoh.jni.bytes.ZBytes?, - encH: io.zenoh.jni.bytes.Encoding?, encId: Int?, + encSchema: String?, attachH: io.zenoh.jni.bytes.ZBytes?, acceptsRepliesInt: Int, zq: io.zenoh.jni.query.Query, @@ -72,7 +72,7 @@ class Query internal constructor( ke, selector, payloadH?.let { ZBytes.fromHandle(it) }, - encH?.let { Encoding.fromParts(it, encId!!) }, + encId?.let { Encoding(it, encSchema) }, attachH?.let { ZBytes.fromHandle(it) }, io.zenoh.jni.query.ReplyKeyExpr.fromInt(acceptsRepliesInt).toPublic(), zq @@ -92,12 +92,13 @@ class Query internal constructor( @JvmOverloads fun reply(keyExpr: KeyExpr, payload: IntoZBytes, options: ReplyOptions = ReplyOptions()) { val q = zQuery ?: throw ZError("Query is invalid") + val enc = options.encoding q.replySuccess( keyExpr.flat, payload.into().bytes, - true, - options.encoding.idForWire(), - options.encoding.schemaForWire(), + enc != null, + enc?.id ?: 0, + enc?.schema, options.timeStamp?.ntpValue(), options.attachment?.into()?.bytes, options.express, @@ -154,7 +155,8 @@ class Query internal constructor( @Throws(ZError::class) fun replyErr(message: IntoZBytes, options: ReplyErrOptions = ReplyErrOptions()) { val q = zQuery ?: throw ZError("Query is invalid") - q.replyError(message.into().bytes, true, options.encoding.idForWire(), options.encoding.schemaForWire(), throwZError) + val enc = options.encoding + q.replyError(message.into().bytes, enc != null, enc?.id ?: 0, enc?.schema, throwZError) q.close() zQuery = null } diff --git a/zenoh-java/src/commonMain/kotlin/io/zenoh/sample/Sample.kt b/zenoh-java/src/commonMain/kotlin/io/zenoh/sample/Sample.kt index 86476eda..d7489659 100644 --- a/zenoh-java/src/commonMain/kotlin/io/zenoh/sample/Sample.kt +++ b/zenoh-java/src/commonMain/kotlin/io/zenoh/sample/Sample.kt @@ -65,8 +65,8 @@ data class Sample( fun fromParts( keH: io.zenoh.jni.keyexpr.KeyExpr, payloadH: io.zenoh.jni.bytes.ZBytes, - encH: io.zenoh.jni.bytes.Encoding, encId: Int, + encSchema: String?, kindInt: Int, ntp64: Long?, express: Boolean, @@ -80,7 +80,7 @@ data class Sample( ): Sample = Sample( KeyExpr(keH), ZBytes.fromHandle(payloadH), - Encoding.fromParts(encH, encId), + Encoding(encId, encSchema), io.zenoh.jni.sample.SampleKind.fromInt(kindInt).toPublic(), ntp64?.let { TimeStamp(it) }, QoS( diff --git a/zenoh-java/src/jvmTest/kotlin/io/zenoh/EncodingCorrespondenceTest.kt b/zenoh-java/src/jvmTest/kotlin/io/zenoh/EncodingCorrespondenceTest.kt new file mode 100644 index 00000000..8c3468db --- /dev/null +++ b/zenoh-java/src/jvmTest/kotlin/io/zenoh/EncodingCorrespondenceTest.kt @@ -0,0 +1,116 @@ +// +// 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 io.zenoh.bytes.Encoding +import io.zenoh.exceptions.throwZError0 +import io.zenoh.jni.bytes.Encoding as JniEncoding +import org.junit.Assert.assertEquals +import org.junit.Test + +/** + * Correspondence tests for the pure-JVM [Encoding] implementation. + * + * The SDK implements the encoding string ↔ `(id, schema)` conversion in pure + * Kotlin (no JNI crossing), on the contract that any JVM-side reimplementation + * of zenoh-flat API must be verified against the native implementation. These + * tests drive both implementations — the pure one and the native oracle (the + * generated `Encoding` handle methods) — over the whole predefined id range + * plus the edge shapes of the parse/render rules, asserting equal results. + */ +class EncodingCorrespondenceTest { + + /** Native oracle: the canonical string of `(id, schema)`. */ + private fun nativeRender(id: Int, schema: String?): String { + val h = JniEncoding.newFromId(id, schema, throwZError0) + try { + return h.toStr(throwZError0) + } finally { + h.close() + } + } + + /** Native oracle: parse a string into `(id, schema, canonical string)`. */ + private fun nativeParse(s: String): Triple { + val h = JniEncoding.newFromString(s, throwZError0) + try { + return Triple(h.getId(throwZError0), h.getSchema(throwZError0), h.toStr(throwZError0)) + } finally { + h.close() + } + } + + @Test + fun renderMatchesNativeAcrossIdRange() { + // The whole predefined range, a gap past it (unknown ids), and the + // custom id — with and without a schema. + val ids = (0..64) + listOf(1000, 0xFFFE, 0xFFFF) + for (id in ids) { + for (schema in listOf(null, "utf-8")) { + assertEquals( + "render mismatch for (id=$id, schema=$schema)", + nativeRender(id, schema), + Encoding(id, schema).toString(), + ) + } + } + } + + @Test + fun parseMatchesNativeOnNamesAndEdges() { + // Every predefined canonical name (obtained FROM the native table so the + // corpus can't drift), plus the parse-rule edge shapes. + val names = (0..52).map { nativeRender(it, null) } + val edges = listOf( + "", + "text/plain", + "text/plain;utf-8", + "text/plain;", + "my_custom_encoding", + "custom;with;semicolons", + ";leading_separator", + "unknown_name;schema", + "zenoh/bytes;s", + ) + for (s in names + names.map { "$it;schema" } + edges) { + val (nid, nschema, nstr) = nativeParse(s) + val pure = Encoding.from(s) + assertEquals("id mismatch for \"$s\"", nid, pure.id) + assertEquals("schema mismatch for \"$s\"", nschema, pure.schema) + assertEquals("render mismatch for \"$s\"", nstr, pure.toString()) + } + } + + @Test + fun withSchemaMatchesNative() { + val bases = listOf( + Encoding.TEXT_PLAIN, + Encoding.ZENOH_BYTES, + Encoding.from("my_custom_encoding"), + Encoding.from("text/plain;old-schema"), + ) + for (base in bases) { + val pure = base.withSchema("new-schema") + // The `e` param crosses as its decomposed (id, schema) value. + val w = JniEncoding.newWithSchema(base.id, base.schema, "new-schema", throwZError0) + val nativeStr = try { + w.toStr(throwZError0) + } finally { + w.close() + } + assertEquals("withSchema mismatch for $base", nativeStr, pure.toString()) + } + } +} diff --git a/zenoh-java/src/jvmTest/kotlin/io/zenoh/PublisherEncodingTest.kt b/zenoh-java/src/jvmTest/kotlin/io/zenoh/PublisherEncodingTest.kt new file mode 100644 index 00000000..b53daf69 --- /dev/null +++ b/zenoh-java/src/jvmTest/kotlin/io/zenoh/PublisherEncodingTest.kt @@ -0,0 +1,62 @@ +// +// 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 io.zenoh.bytes.Encoding +import io.zenoh.bytes.ZBytes +import io.zenoh.keyexpr.KeyExpr +import io.zenoh.pubsub.PublisherOptions +import io.zenoh.pubsub.PutOptions +import io.zenoh.sample.Sample +import org.junit.Assert.assertEquals +import org.junit.Test + +/** + * End-to-end test of the publisher's **default encoding**: set natively once + * at declare time ([PublisherOptions.encoding]), applied by zenoh itself to + * every plain `put` — which crosses **no encoding data at all** — and + * overridden per call via [PutOptions.encoding]. + */ +class PublisherEncodingTest { + + private val custom = Encoding.from("application/custom;my-schema") + + @Test + fun publisherDefaultAppliesNativelyAndPerPutOverrides() { + val session = Zenoh.open(Config.loadDefault()) + val keyExpr = KeyExpr.tryFrom("example/testing/publisher/encoding") + val received = mutableListOf() + val subscriber = session.declareSubscriber(keyExpr) { received.add(it) } + + val options = PublisherOptions() + options.encoding = custom + val publisher = session.declarePublisher(keyExpr, options) + + // Plain put: NO encoding crosses — the native publisher default applies. + publisher.put(ZBytes.from("default")) + // Per-put override still works. + val putOptions = PutOptions() + putOptions.encoding = Encoding.TEXT_PLAIN + publisher.put(ZBytes.from("override"), putOptions) + Thread.sleep(500) + + publisher.close() + subscriber.close() + session.close() + assertEquals(2, received.size) + assertEquals(custom, received[0].encoding) + assertEquals(Encoding.TEXT_PLAIN, received[1].encoding) + } +} diff --git a/zenoh-java/src/jvmTest/kotlin/io/zenoh/ZenohIdCorrespondenceTest.kt b/zenoh-java/src/jvmTest/kotlin/io/zenoh/ZenohIdCorrespondenceTest.kt new file mode 100644 index 00000000..f531c240 --- /dev/null +++ b/zenoh-java/src/jvmTest/kotlin/io/zenoh/ZenohIdCorrespondenceTest.kt @@ -0,0 +1,58 @@ +// +// 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 io.zenoh.config.ZenohId +import io.zenoh.exceptions.throwZError0 +import io.zenoh.jni.config.ZenohId as JniZenohId +import kotlin.random.Random +import org.junit.Assert.assertEquals +import org.junit.Test + +/** + * Correspondence test for the pure-JVM [ZenohId.toString] — the id bytes read + * as a little-endian integer, rendered as lowercase hex without leading zeros + * (`"0"` for the zero id). Verified against the native formatter + * (`zenoh_id_to_string`) over edge patterns and a deterministic random corpus. + */ +class ZenohIdCorrespondenceTest { + + private fun assertCorresponds(bytes: ByteArray) { + val jni = JniZenohId(bytes) + assertEquals( + "zid render mismatch for ${bytes.joinToString(",")}", + jni.toStr(throwZError0), + ZenohId(jni).toString(), + ) + } + + @Test + fun rendersLikeNative() { + val edges = listOf( + ByteArray(16), // zero id + ByteArray(16).also { it[0] = 1 }, // smallest nonzero (LE low byte) + ByteArray(16).also { it[15] = 1 }, // highest byte only + ByteArray(16).also { it[0] = 0x0F }, // sub-0x10 low byte + ByteArray(16) { 0xFF.toByte() }, // all ones + ByteArray(16) { i -> i.toByte() }, // ascending with leading zero byte + ) + edges.forEach(::assertCorresponds) + + val rng = Random(20260716) + repeat(32) { + assertCorresponds(rng.nextBytes(16)) + } + } +} From 6731c28d7693282abb3b2a902f69824d30e75c99 Mon Sep 17 00:00:00 2001 From: Michael Ilyin Date: Fri, 17 Jul 2026 01:59:41 +0200 Subject: [PATCH 4/9] Encoding: native handle where it is born, value where it is cheapest (#485) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Encoding: native handle where it is born, value where it is cheapest Minimize JNI crossings for every encoding flow (consumes zenoh-flat-jni PR #5): - Predefined constants stay VALUE-ONLY: a send carries just their id inside the send call itself — no native handle ever exists for them, no extra crossing. - Custom (schema-carrying) encodings create their native handle at construction — the one natural crossing — and every send after passes a bare jlong (borrowed and cloned natively, reusable forever). - Received encodings (sample/query/reply) arrive send-ready: the delivery decomposition now includes the owned handle, so the save-and-republish scenario never rebuilds the native value from its schema string. Handle release is GC-managed (EncodingCleaner in the shared tier); Encoding stays a plain non-closeable value with unchanged (id, schema) equality/rendering. Send call sites drive the generated encoding selector block through the internal Encoding?.jniSel/jniId/jniSchema/jniHandle helpers — one flat call, zero extra crossings in every case. New EncodingHandleTest covers the ownership model end-to-end; 110 jvm tests green. Co-Authored-By: Claude Fable 5 * Lifecycle backstops moved into the gc_managed JNI tier zenoh-flat-jni's Encoding, Session, Publisher, Subscriber, Queryable, Querier, Scout, LivelinessToken and Query handles are now gc_managed (milyin/prebindgen#82): a shared Cleaner frees an unreachable handle whose owner never released it, with explicit close/undeclare settling the release ticket first. Delete the 8 deprecated-for-removal finalize() nets (JEP 421) — the JNI handle is its own backstop now — and drop Encoding's EncodingCleaner registration (superseded by the gc_managed class; the hand-written helper is deleted in zenoh-flat-jni). Co-Authored-By: Claude Fable 5 * CI: bump zenoh-flat-jni pin to the merged encoding-handle commit The encoding selector wire shape and gc_managed lifecycles this branch consumes landed in ZettaScaleLabs/zenoh-flat-jni#5 (e802317); the old pin predates it, so CI compiled against the previous (present, id, schema) encoding signatures. Co-Authored-By: Claude Fable 5 --------- Co-authored-by: Claude Fable 5 --- .github/workflows/ci.yml | 2 +- .../kotlin/io/zenoh/FlatCallbacks.kt | 14 +-- .../src/commonMain/kotlin/io/zenoh/Session.kt | 26 ++-- .../kotlin/io/zenoh/bytes/Encoding.kt | 60 ++++++++- .../io/zenoh/liveliness/LivelinessToken.kt | 4 - .../kotlin/io/zenoh/pubsub/Publisher.kt | 15 ++- .../kotlin/io/zenoh/pubsub/Subscriber.kt | 4 - .../kotlin/io/zenoh/query/Querier.kt | 18 +-- .../commonMain/kotlin/io/zenoh/query/Query.kt | 18 ++- .../kotlin/io/zenoh/query/Queryable.kt | 4 - .../kotlin/io/zenoh/sample/Sample.kt | 9 +- .../kotlin/io/zenoh/scouting/Scout.kt | 4 - .../io/zenoh/EncodingCorrespondenceTest.kt | 5 +- .../kotlin/io/zenoh/EncodingHandleTest.kt | 119 ++++++++++++++++++ 14 files changed, 221 insertions(+), 81 deletions(-) create mode 100644 zenoh-java/src/jvmTest/kotlin/io/zenoh/EncodingHandleTest.kt diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ef67473a..c2122f30 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -31,7 +31,7 @@ jobs: uses: actions/checkout@v4 with: repository: ZettaScaleLabs/zenoh-flat-jni - ref: 9bc155b3272c8c905bf95614cb7eb7e6d656c72f + ref: e8023170c0be3d8e5cd2cee17487870b27af6728 path: zenoh-flat-jni - name: Check out zenoh-flat diff --git a/zenoh-java/src/commonMain/kotlin/io/zenoh/FlatCallbacks.kt b/zenoh-java/src/commonMain/kotlin/io/zenoh/FlatCallbacks.kt index 4e35a7cf..94741393 100644 --- a/zenoh-java/src/commonMain/kotlin/io/zenoh/FlatCallbacks.kt +++ b/zenoh-java/src/commonMain/kotlin/io/zenoh/FlatCallbacks.kt @@ -39,14 +39,14 @@ import io.zenoh.scouting.Hello internal fun sampleCallbackOf( f: (Sample) -> Unit ): io.zenoh.jni.sample.SampleCallback = - io.zenoh.jni.sample.SampleCallback { keH, payloadH, encId, encSchema, kindInt, ntp64, express, prioInt, ccInt, attachH, reliabilityInt, sourceZid, sourceEid, sourceSn -> - f(Sample.fromParts(keH, payloadH, encId, encSchema, kindInt, ntp64, express, prioInt, ccInt, attachH, reliabilityInt, sourceZid, sourceEid, sourceSn)) + io.zenoh.jni.sample.SampleCallback { keH, payloadH, encId, encSchema, encH, kindInt, ntp64, express, prioInt, ccInt, attachH, reliabilityInt, sourceZid, sourceEid, sourceSn -> + f(Sample.fromParts(keH, payloadH, encId, encSchema, encH, kindInt, ntp64, express, prioInt, ccInt, attachH, reliabilityInt, sourceZid, sourceEid, sourceSn)) } internal fun queryCallbackOf( f: (Query) -> Unit ): io.zenoh.jni.query.QueryCallback = - io.zenoh.jni.query.QueryCallback { keH, parameters, payloadH, encId, encSchema, attachH, acceptsReplies, zq -> + io.zenoh.jni.query.QueryCallback { keH, parameters, payloadH, encId, encSchema, encH, attachH, acceptsReplies, zq -> // The decomposed leaves — including the cloned `keH` key-expr handle 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 @@ -54,25 +54,25 @@ internal fun queryCallbackOf( // 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. - f(Query.fromParts(keH, parameters, payloadH, encId, encSchema, attachH, acceptsReplies, zq)) + f(Query.fromParts(keH, parameters, payloadH, encId, encSchema, encH, attachH, acceptsReplies, zq)) } internal fun replyCallbackOf( f: (Reply) -> Unit ): io.zenoh.jni.query.ReplyCallback = - io.zenoh.jni.query.ReplyCallback { zid, eid, isOk, keH, payloadH, encId, encSchema, kindInt, ntp64, express, prioInt, ccInt, attachH, reliabilityInt, sourceZid, sourceEid, sourceSn, errPayloadH, errEncId, errEncSchema -> + io.zenoh.jni.query.ReplyCallback { zid, eid, isOk, keH, payloadH, encId, encSchema, encH, kindInt, ntp64, express, prioInt, ccInt, attachH, reliabilityInt, sourceZid, sourceEid, sourceSn, errPayloadH, errEncId, errEncSchema, errEncH -> val replierId = zid?.let { EntityGlobalId(ZenohId(it), eid.toUInt()) } f( if (isOk) { Reply.Success( replierId, - Sample.fromParts(keH!!, payloadH!!, encId!!, encSchema, kindInt!!, ntp64, express!!, prioInt!!, ccInt!!, attachH, reliabilityInt!!, sourceZid, sourceEid!!, sourceSn!!) + Sample.fromParts(keH!!, payloadH!!, encId!!, encSchema, encH!!, kindInt!!, ntp64, express!!, prioInt!!, ccInt!!, attachH, reliabilityInt!!, sourceZid, sourceEid!!, sourceSn!!) ) } else { Reply.Error( replierId, ZBytes.fromHandle(errPayloadH!!), - errEncId?.let { Encoding(it, errEncSchema) } ?: Encoding.defaultEncoding() + errEncId?.let { Encoding(it, errEncSchema, errEncH) } ?: Encoding.defaultEncoding() ) } ) diff --git a/zenoh-java/src/commonMain/kotlin/io/zenoh/Session.kt b/zenoh-java/src/commonMain/kotlin/io/zenoh/Session.kt index 846dec59..49b12215 100644 --- a/zenoh-java/src/commonMain/kotlin/io/zenoh/Session.kt +++ b/zenoh-java/src/commonMain/kotlin/io/zenoh/Session.kt @@ -19,6 +19,10 @@ import io.zenoh.bytes.Encoding import io.zenoh.bytes.IntoZBytes import io.zenoh.bytes.ZBytes import io.zenoh.bytes.into +import io.zenoh.bytes.jniHandle +import io.zenoh.bytes.jniId +import io.zenoh.bytes.jniSchema +import io.zenoh.bytes.jniSel import io.zenoh.config.ZenohId import io.zenoh.exceptions.ZError import io.zenoh.exceptions.throwZError @@ -106,10 +110,6 @@ class Session private constructor(private val config: Config) : AutoCloseable { zSession = null } - protected fun finalize() { - close() - } - /** * Declare a [Publisher] on the session. * @@ -592,9 +592,7 @@ class Session private constructor(private val config: Config) : AutoCloseable { val enc = options.encoding val zPublisher = zSession.declarePublisher( keyExpr.cloneFlat(), - true, - enc.id, - enc.schema, + enc.jniSel, enc.jniId, enc.jniSchema, enc.jniHandle, options.congestionControl.jni, options.priority.jni, options.express, @@ -728,7 +726,6 @@ class Session private constructor(private val config: Config) : AutoCloseable { val zSession = zSession ?: throw sessionClosedException return run { val sel = selector.into() - val enc = options.encoding zSession.get( sel.keyExpr.flat, sel.parameters?.toString(), @@ -740,9 +737,7 @@ class Session private constructor(private val config: Config) : AutoCloseable { options.qos.priority.jni, options.qos.express, options.payload?.into()?.bytes, - enc != null, - enc?.id ?: 0, - enc?.schema, + options.encoding.jniSel, options.encoding.jniId, options.encoding.jniSchema, options.encoding.jniHandle, options.attachment?.into()?.bytes, replyCallbackOf { handler.handle(it) }, { handler.onClose() }, @@ -761,7 +756,6 @@ class Session private constructor(private val config: Config) : AutoCloseable { val zSession = zSession ?: throw sessionClosedException run { val sel = selector.into() - val enc = options.encoding zSession.get( sel.keyExpr.flat, sel.parameters?.toString(), @@ -773,9 +767,7 @@ class Session private constructor(private val config: Config) : AutoCloseable { options.qos.priority.jni, options.qos.express, options.payload?.into()?.bytes, - enc != null, - enc?.id ?: 0, - enc?.schema, + options.encoding.jniSel, options.encoding.jniId, options.encoding.jniSchema, options.encoding.jniHandle, options.attachment?.into()?.bytes, replyCallbackOf { callback.run(it) }, { }, @@ -792,9 +784,7 @@ class Session private constructor(private val config: Config) : AutoCloseable { zSession.put( keyExpr.flat, payload.into().bytes, - enc != null, - enc?.id ?: 0, - enc?.schema, + enc.jniSel, enc.jniId, enc.jniSchema, enc.jniHandle, putOptions.congestionControl.jni, putOptions.priority.jni, putOptions.express, diff --git a/zenoh-java/src/commonMain/kotlin/io/zenoh/bytes/Encoding.kt b/zenoh-java/src/commonMain/kotlin/io/zenoh/bytes/Encoding.kt index dfa0e19c..e22b4b1a 100644 --- a/zenoh-java/src/commonMain/kotlin/io/zenoh/bytes/Encoding.kt +++ b/zenoh-java/src/commonMain/kotlin/io/zenoh/bytes/Encoding.kt @@ -15,6 +15,7 @@ package io.zenoh.bytes import io.zenoh.jni.bytes.* +import io.zenoh.jni.bytes.Encoding as JniEncoding /** * Default encoding values used by Zenoh. @@ -30,12 +31,26 @@ import io.zenoh.jni.bytes.* * representation. This class is a plain immutable value; all conversion logic * (the id↔name table and Zenoh's parse/render rules) lives in the shared * bindings tier ([EncodingCodec]), verified against the native implementation - * by `EncodingCorrespondenceTest`. No native handle, no native calls, nothing - * to close. + * by `EncodingCorrespondenceTest`. Nothing to close: identity, equality and + * rendering never touch the native side. + * + * Sends are shaped to MINIMIZE JNI CROSSINGS. The predefined constants stay + * value-only: a send carries just their id inside the send call itself — no + * native handle ever exists for them. An encoding that already owns a native + * [handle] sends it as a bare `jlong` instead (the native side borrows and + * clones it, so the handle is reusable forever). Handles exist only where + * they are born without an extra crossing: custom (schema-carrying) encodings + * create one at construction, and received encodings (sample/query/reply) + * arrive with one in the same delivery crossing. Handle release is GC-managed + * (the JNI `Encoding` class is `gc_managed` — a shared Cleaner frees the + * native box once the handle is unreachable) — the value stays non-closeable + * either way. */ class Encoding internal constructor( internal val id: Int, internal val schema: String? = null, + /** The owned native handle, when this encoding was born with one. */ + internal val handle: JniEncoding? = null, ) { companion object { @@ -100,20 +115,34 @@ class Encoding internal constructor( * Parse a textual encoding (e.g. `"text/plain"`, `"text/plain;utf-8"`, * `"my_encoding"`). Well-known names resolve to their canonical id; * everything else is preserved as a custom encoding. + * + * A plain well-known name yields a value-only encoding (sends carry + * just the id); a schema-carrying/custom result creates its native + * handle here — construction is the one crossing, every send after is + * a bare `jlong`. */ @JvmStatic fun from(s: String): Encoding { val (id, schema) = EncodingCodec.parse(s) - return Encoding(id, schema) + return Encoding(id, schema, schema?.let { createHandle(id, it) }) } + + /** Native handle for a custom encoding — construction is the one crossing. */ + private fun createHandle(id: Int, schema: String?): JniEncoding = + JniEncoding.newFromId(id, schema) { je -> + throw IllegalStateException("encoding creation failed: $je") + } } /** * Set a schema to this encoding. Zenoh does not define what a schema is and its semantics is left to the implementer. * E.g. a common schema for `text/plain` encoding is `utf-8`. + * + * The result is a custom encoding, so its native handle is created here — + * construction is the one crossing, every send after is a bare `jlong`. */ fun withSchema(schema: String): Encoding { val (nid, nschema) = EncodingCodec.withSchema(id, this.schema, schema) - return Encoding(nid, nschema) + return Encoding(nid, nschema, createHandle(nid, nschema)) } /** Canonical textual form. */ @@ -128,3 +157,26 @@ class Encoding internal constructor( override fun hashCode(): Int = 31 * id + (schema?.hashCode() ?: 0) } + +// 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: absent -> -1, handle-owning -> the bare +// handle (arm 1), value-only -> (id, schema) built Rust-side inside the same +// call (arm 0). + +internal val Encoding?.jniSel: Int + get() = when { + this == null -> -1 + handle != null -> 1 + else -> 0 + } + +internal val Encoding?.jniId: Int? + get() = if (this != null && handle == null) id else null + +internal val Encoding?.jniSchema: String? + get() = if (this != null && handle == null) schema else null + +internal val Encoding?.jniHandle: JniEncoding? + get() = this?.handle diff --git a/zenoh-java/src/commonMain/kotlin/io/zenoh/liveliness/LivelinessToken.kt b/zenoh-java/src/commonMain/kotlin/io/zenoh/liveliness/LivelinessToken.kt index 194b8c16..8ce31f12 100644 --- a/zenoh-java/src/commonMain/kotlin/io/zenoh/liveliness/LivelinessToken.kt +++ b/zenoh-java/src/commonMain/kotlin/io/zenoh/liveliness/LivelinessToken.kt @@ -45,8 +45,4 @@ class LivelinessToken internal constructor(private var token: io.zenoh.jni.livel override fun close() { undeclare() } - - protected fun finalize() { - undeclare() - } } diff --git a/zenoh-java/src/commonMain/kotlin/io/zenoh/pubsub/Publisher.kt b/zenoh-java/src/commonMain/kotlin/io/zenoh/pubsub/Publisher.kt index 039dc1ac..4aefe6fe 100644 --- a/zenoh-java/src/commonMain/kotlin/io/zenoh/pubsub/Publisher.kt +++ b/zenoh-java/src/commonMain/kotlin/io/zenoh/pubsub/Publisher.kt @@ -16,6 +16,10 @@ package io.zenoh.pubsub import io.zenoh.* 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.bytes.IntoZBytes import io.zenoh.bytes.ZBytes import io.zenoh.exceptions.ZError @@ -125,21 +129,16 @@ class Publisher internal constructor( zPublisher = null } - @Suppress("removal") - protected fun finalize() { - zPublisher?.close() - } - @Throws(ZError::class) private fun performPut(payload: IntoZBytes, encoding: Encoding?, attachment: IntoZBytes?) { val p = zPublisher ?: throw publisherNotValid // `null` encoding = absent: the publisher's default encoding — set // NATIVELY at declare time — applies, and no encoding data crosses. + // A per-put override rides this same call: bare handle, or (id, + // schema) for a value-only (predefined) encoding. p.put( payload.into().bytes, - encoding != null, - encoding?.id ?: 0, - encoding?.schema, + encoding.jniSel, encoding.jniId, encoding.jniSchema, encoding.jniHandle, attachment?.into()?.bytes, throwZError, ) diff --git a/zenoh-java/src/commonMain/kotlin/io/zenoh/pubsub/Subscriber.kt b/zenoh-java/src/commonMain/kotlin/io/zenoh/pubsub/Subscriber.kt index 874a28ab..5dc69e16 100644 --- a/zenoh-java/src/commonMain/kotlin/io/zenoh/pubsub/Subscriber.kt +++ b/zenoh-java/src/commonMain/kotlin/io/zenoh/pubsub/Subscriber.kt @@ -77,10 +77,6 @@ sealed class Subscriber( override fun close() { undeclare() } - - protected fun finalize() { - zSubscriber?.close() - } } /** diff --git a/zenoh-java/src/commonMain/kotlin/io/zenoh/query/Querier.kt b/zenoh-java/src/commonMain/kotlin/io/zenoh/query/Querier.kt index 65fde3ca..51a8ac0d 100644 --- a/zenoh-java/src/commonMain/kotlin/io/zenoh/query/Querier.kt +++ b/zenoh-java/src/commonMain/kotlin/io/zenoh/query/Querier.kt @@ -17,6 +17,10 @@ package io.zenoh.query import io.zenoh.annotations.Unstable import io.zenoh.replyCallbackOf 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.bytes.IntoZBytes import io.zenoh.bytes.ZBytes import io.zenoh.exceptions.ZError @@ -139,19 +143,12 @@ class Querier internal constructor(val keyExpr: KeyExpr, val qos: QoS, private v undeclare() } - protected fun finalize() { - undeclare() - } - private fun resolveGetWithCallback(callback: Callback, options: GetOptions) { val q = zQuerier ?: throw ZError("Querier is not valid.") - val enc = options.encoding q.get( options.parameters?.toString(), options.payload?.into()?.bytes, - enc != null, - enc?.id ?: 0, - enc?.schema, + options.encoding.jniSel, options.encoding.jniId, options.encoding.jniSchema, options.encoding.jniHandle, options.attachment?.into()?.bytes, replyCallbackOf { callback.run(it) }, { }, @@ -161,13 +158,10 @@ class Querier internal constructor(val keyExpr: KeyExpr, val qos: QoS, private v private fun resolveGetWithHandler(handler: Handler, options: GetOptions): R { val q = zQuerier ?: throw ZError("Querier is not valid.") - val enc = options.encoding q.get( options.parameters?.toString(), options.payload?.into()?.bytes, - enc != null, - enc?.id ?: 0, - enc?.schema, + options.encoding.jniSel, options.encoding.jniId, options.encoding.jniSchema, options.encoding.jniHandle, options.attachment?.into()?.bytes, replyCallbackOf { handler.handle(it) }, { handler.onClose() }, diff --git a/zenoh-java/src/commonMain/kotlin/io/zenoh/query/Query.kt b/zenoh-java/src/commonMain/kotlin/io/zenoh/query/Query.kt index 895540bb..8ddebb98 100644 --- a/zenoh-java/src/commonMain/kotlin/io/zenoh/query/Query.kt +++ b/zenoh-java/src/commonMain/kotlin/io/zenoh/query/Query.kt @@ -16,6 +16,10 @@ package io.zenoh.query import io.zenoh.ZenohType 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.bytes.IntoZBytes import io.zenoh.bytes.ZBytes import io.zenoh.exceptions.ZError @@ -61,6 +65,7 @@ class Query internal constructor( payloadH: io.zenoh.jni.bytes.ZBytes?, encId: Int?, encSchema: String?, + encH: io.zenoh.jni.bytes.Encoding?, attachH: io.zenoh.jni.bytes.ZBytes?, acceptsRepliesInt: Int, zq: io.zenoh.jni.query.Query, @@ -72,7 +77,7 @@ class Query internal constructor( ke, selector, payloadH?.let { ZBytes.fromHandle(it) }, - encId?.let { Encoding(it, encSchema) }, + encId?.let { Encoding(it, encSchema, encH) }, attachH?.let { ZBytes.fromHandle(it) }, io.zenoh.jni.query.ReplyKeyExpr.fromInt(acceptsRepliesInt).toPublic(), zq @@ -96,9 +101,7 @@ class Query internal constructor( q.replySuccess( keyExpr.flat, payload.into().bytes, - enc != null, - enc?.id ?: 0, - enc?.schema, + enc.jniSel, enc.jniId, enc.jniSchema, enc.jniHandle, options.timeStamp?.ntpValue(), options.attachment?.into()?.bytes, options.express, @@ -156,7 +159,7 @@ class Query internal constructor( fun replyErr(message: IntoZBytes, options: ReplyErrOptions = ReplyErrOptions()) { val q = zQuery ?: throw ZError("Query is invalid") val enc = options.encoding - q.replyError(message.into().bytes, enc != null, enc?.id ?: 0, enc?.schema, throwZError) + q.replyError(message.into().bytes, enc.jniSel, enc.jniId, enc.jniSchema, enc.jniHandle, throwZError) q.close() zQuery = null } @@ -175,9 +178,4 @@ class Query internal constructor( zQuery?.close() zQuery = null } - - @Suppress("removal") - protected fun finalize() { - close() - } } diff --git a/zenoh-java/src/commonMain/kotlin/io/zenoh/query/Queryable.kt b/zenoh-java/src/commonMain/kotlin/io/zenoh/query/Queryable.kt index d5e2909e..c70a5ce9 100644 --- a/zenoh-java/src/commonMain/kotlin/io/zenoh/query/Queryable.kt +++ b/zenoh-java/src/commonMain/kotlin/io/zenoh/query/Queryable.kt @@ -76,10 +76,6 @@ sealed class Queryable( override fun close() { undeclare() } - - protected fun finalize() { - zQueryable?.close() - } } /** diff --git a/zenoh-java/src/commonMain/kotlin/io/zenoh/sample/Sample.kt b/zenoh-java/src/commonMain/kotlin/io/zenoh/sample/Sample.kt index d7489659..78edc600 100644 --- a/zenoh-java/src/commonMain/kotlin/io/zenoh/sample/Sample.kt +++ b/zenoh-java/src/commonMain/kotlin/io/zenoh/sample/Sample.kt @@ -53,11 +53,13 @@ data class Sample( internal companion object { /** - * Builds an SDK [Sample] from the 14 leaves of the ZSample canonical + * Builds an SDK [Sample] from the 15 leaves of the ZSample canonical * output — the lambda parameter list every decomposed sample delivery * uses (the `zReplySample` builder and the subscriber/liveliness * callbacks), in record order. The whole graph arrives in ONE JNI - * crossing; the [KeyExpr] retains the delivered handle leaf. The + * crossing; the [KeyExpr] retains the delivered handle leaf, and the + * [Encoding] retains its delivered handle (`encH`) so re-sending it + * crosses a bare `jlong` instead of rebuilding the native value. The * trailing `reliability` / `source*` leaves are part of the generated * decomposition but are not surfaced on the public [Sample] type. */ @@ -67,6 +69,7 @@ data class Sample( payloadH: io.zenoh.jni.bytes.ZBytes, encId: Int, encSchema: String?, + encH: io.zenoh.jni.bytes.Encoding, kindInt: Int, ntp64: Long?, express: Boolean, @@ -80,7 +83,7 @@ data class Sample( ): Sample = Sample( KeyExpr(keH), ZBytes.fromHandle(payloadH), - Encoding(encId, encSchema), + Encoding(encId, encSchema, encH), io.zenoh.jni.sample.SampleKind.fromInt(kindInt).toPublic(), ntp64?.let { TimeStamp(it) }, QoS( diff --git a/zenoh-java/src/commonMain/kotlin/io/zenoh/scouting/Scout.kt b/zenoh-java/src/commonMain/kotlin/io/zenoh/scouting/Scout.kt index 6b94f66f..4a2a4725 100644 --- a/zenoh-java/src/commonMain/kotlin/io/zenoh/scouting/Scout.kt +++ b/zenoh-java/src/commonMain/kotlin/io/zenoh/scouting/Scout.kt @@ -115,10 +115,6 @@ sealed class Scout ( override fun close() { stop() } - - protected fun finalize() { - stop() - } } /** diff --git a/zenoh-java/src/jvmTest/kotlin/io/zenoh/EncodingCorrespondenceTest.kt b/zenoh-java/src/jvmTest/kotlin/io/zenoh/EncodingCorrespondenceTest.kt index 8c3468db..dd61a10c 100644 --- a/zenoh-java/src/jvmTest/kotlin/io/zenoh/EncodingCorrespondenceTest.kt +++ b/zenoh-java/src/jvmTest/kotlin/io/zenoh/EncodingCorrespondenceTest.kt @@ -103,8 +103,9 @@ class EncodingCorrespondenceTest { ) for (base in bases) { val pure = base.withSchema("new-schema") - // The `e` param crosses as its decomposed (id, schema) value. - val w = JniEncoding.newWithSchema(base.id, base.schema, "new-schema", throwZError0) + // The `e` param crosses on the selector's value arm as its + // decomposed (id, schema) pair. + val w = JniEncoding.newWithSchema(0, base.id, base.schema, null, "new-schema", throwZError0) val nativeStr = try { w.toStr(throwZError0) } finally { diff --git a/zenoh-java/src/jvmTest/kotlin/io/zenoh/EncodingHandleTest.kt b/zenoh-java/src/jvmTest/kotlin/io/zenoh/EncodingHandleTest.kt new file mode 100644 index 00000000..d7685186 --- /dev/null +++ b/zenoh-java/src/jvmTest/kotlin/io/zenoh/EncodingHandleTest.kt @@ -0,0 +1,119 @@ +// +// 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.keyexpr.KeyExpr +import io.zenoh.pubsub.PutOptions +import io.zenoh.sample.Sample +import org.junit.Assert.assertEquals +import org.junit.Assert.assertNotNull +import org.junit.Assert.assertNull +import org.junit.Assert.assertSame +import org.junit.Test + +/** + * The encoding native-handle ownership model, shaped to minimize JNI + * crossings: + * + * - predefined constants are VALUE-ONLY — a send carries just their id inside + * the send call itself, no handle ever exists; + * - custom (schema-carrying) encodings own a handle from construction — + * construction is the one crossing, every send after is a bare jlong; + * - received encodings arrive WITH their handle in the same delivery + * crossing, so re-sending one (the save-and-republish scenario) never + * rebuilds the native value from its schema string. + */ +class EncodingHandleTest { + + @Test + fun predefinedEncodingsAreValueOnly() { + assertNull(Encoding.TEXT_PLAIN.handle) + assertNull(Encoding.APPLICATION_JSON.handle) + assertNull(Encoding.defaultEncoding().handle) + // Parsing a plain well-known name yields the same value-only form. + assertNull(Encoding.from("text/plain").handle) + } + + @Test + fun customEncodingsOwnAHandleFromConstruction() { + assertNotNull(Encoding.from("text/plain;utf-8").handle) + assertNotNull(Encoding.from("my_custom_encoding").handle) + assertNotNull(Encoding.TEXT_PLAIN.withSchema("utf-8").handle) + } + + @Test + fun receivedEncodingIsSendReadyAndResendsByHandle() { + val custom = Encoding.from("application/custom;my-schema") + val session = Zenoh.open(Config.loadDefault()) + val keyExpr = KeyExpr.tryFrom("example/testing/encoding/handle") + val received = mutableListOf() + val subscriber = session.declareSubscriber(keyExpr) { received.add(it) } + + // Send 1: user-created custom encoding — crosses by handle. + val putOptions = PutOptions() + putOptions.encoding = custom + session.put(keyExpr, ZBytes.from("one"), putOptions) + Thread.sleep(500) + + assertEquals(1, received.size) + val saved = received[0].encoding + assertEquals(custom, saved) + // Delivered send-ready: the handle arrived with the sample. + assertNotNull(saved.handle) + + // Send 2: the user's scenario — re-publish with the SAVED encoding. + // The retained handle is what crosses; nothing is rebuilt. + val handleBefore = saved.handle + val resendOptions = PutOptions() + resendOptions.encoding = saved + session.put(keyExpr, ZBytes.from("two"), resendOptions) + Thread.sleep(500) + + assertEquals(2, received.size) + assertEquals(custom, received[1].encoding) + // The saved encoding still owns the same reusable handle (borrowed, + // never consumed, by the send). + assertSame(handleBefore, saved.handle) + assertNotNull(received[1].encoding.handle) + + subscriber.close() + session.close() + } + + @Test + fun predefinedRoundTripStaysValueOnSendAndHandleOnReceive() { + val session = Zenoh.open(Config.loadDefault()) + val keyExpr = KeyExpr.tryFrom("example/testing/encoding/preset") + val received = mutableListOf() + val subscriber = session.declareSubscriber(keyExpr) { received.add(it) } + + val putOptions = PutOptions() + putOptions.encoding = Encoding.TEXT_PLAIN + session.put(keyExpr, ZBytes.from("plain"), putOptions) + Thread.sleep(500) + + // The preset itself never grew a handle from being sent… + assertNull(Encoding.TEXT_PLAIN.handle) + assertEquals(1, received.size) + assertEquals(Encoding.TEXT_PLAIN, received[0].encoding) + // …while the received copy arrived send-ready. + assertNotNull(received[0].encoding.handle) + + subscriber.close() + session.close() + } +} From 3ec8f2ec7178d5f1fc730ef959fc564a5e061469 Mon Sep 17 00:00:00 2001 From: Michael Ilyin Date: Fri, 17 Jul 2026 16:10:19 +0200 Subject: [PATCH 5/9] Received preset encodings are value-only end to end (#488) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Received preset encodings are value-only end to end zenoh-flat-jni#6 makes the delivered encoding handle CONDITIONAL on schema presence (binding-local encoding_if_schema behind prebindgen#84's field! leaf): a received preset re-sends through the id arm for free, so no per-message native handle — clone + Box + JVM wrapper + gc-Cleaner registration + Cleaner free — is ever materialized for it. Schema- carrying encodings keep the send-ready handle. Adapt the delivery plumbing: Sample.fromParts encH becomes nullable (Query's already was), the reply callback drops its non-null assertion, and EncodingHandleTest's preset round-trip flips to assert the received copy has NO handle — the new contract. CI pin bumped to the flat-jni#6 tip. Co-Authored-By: Claude Fable 5 * CI: bump zenoh-flat-jni pin to the #6 merge commit The previous pin was a branch commit deleted on merge; 1e040c9 is ZettaScaleLabs/zenoh-flat-jni#6 on main (conditional encoding handle, final fun!+sig! syntax). Co-Authored-By: Claude Fable 5 --------- Co-authored-by: Claude Fable 5 --- .github/workflows/ci.yml | 2 +- .../commonMain/kotlin/io/zenoh/FlatCallbacks.kt | 2 +- .../commonMain/kotlin/io/zenoh/sample/Sample.kt | 6 ++++-- .../kotlin/io/zenoh/EncodingHandleTest.kt | 16 ++++++++++------ 4 files changed, 16 insertions(+), 10 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c2122f30..d28833d5 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -31,7 +31,7 @@ jobs: uses: actions/checkout@v4 with: repository: ZettaScaleLabs/zenoh-flat-jni - ref: e8023170c0be3d8e5cd2cee17487870b27af6728 + ref: 1e040c96d0729c387d411a2356f361e9ea420877 path: zenoh-flat-jni - name: Check out zenoh-flat diff --git a/zenoh-java/src/commonMain/kotlin/io/zenoh/FlatCallbacks.kt b/zenoh-java/src/commonMain/kotlin/io/zenoh/FlatCallbacks.kt index 94741393..c8581fbe 100644 --- a/zenoh-java/src/commonMain/kotlin/io/zenoh/FlatCallbacks.kt +++ b/zenoh-java/src/commonMain/kotlin/io/zenoh/FlatCallbacks.kt @@ -66,7 +66,7 @@ internal fun replyCallbackOf( if (isOk) { Reply.Success( replierId, - Sample.fromParts(keH!!, payloadH!!, encId!!, encSchema, encH!!, kindInt!!, ntp64, express!!, prioInt!!, ccInt!!, attachH, reliabilityInt!!, sourceZid, sourceEid!!, sourceSn!!) + Sample.fromParts(keH!!, payloadH!!, encId!!, encSchema, encH, kindInt!!, ntp64, express!!, prioInt!!, ccInt!!, attachH, reliabilityInt!!, sourceZid, sourceEid!!, sourceSn!!) ) } else { Reply.Error( diff --git a/zenoh-java/src/commonMain/kotlin/io/zenoh/sample/Sample.kt b/zenoh-java/src/commonMain/kotlin/io/zenoh/sample/Sample.kt index 78edc600..e3cc70a4 100644 --- a/zenoh-java/src/commonMain/kotlin/io/zenoh/sample/Sample.kt +++ b/zenoh-java/src/commonMain/kotlin/io/zenoh/sample/Sample.kt @@ -59,7 +59,9 @@ data class Sample( * callbacks), in record order. The whole graph arrives in ONE JNI * crossing; the [KeyExpr] retains the delivered handle leaf, and the * [Encoding] retains its delivered handle (`encH`) so re-sending it - * crosses a bare `jlong` instead of rebuilding the native value. The + * crosses a bare `jlong` instead of rebuilding the native value — + * delivered ONLY for schema-carrying encodings (a preset re-sends + * through the id arm for free, so it arrives value-only). The * trailing `reliability` / `source*` leaves are part of the generated * decomposition but are not surfaced on the public [Sample] type. */ @@ -69,7 +71,7 @@ data class Sample( payloadH: io.zenoh.jni.bytes.ZBytes, encId: Int, encSchema: String?, - encH: io.zenoh.jni.bytes.Encoding, + encH: io.zenoh.jni.bytes.Encoding?, kindInt: Int, ntp64: Long?, express: Boolean, diff --git a/zenoh-java/src/jvmTest/kotlin/io/zenoh/EncodingHandleTest.kt b/zenoh-java/src/jvmTest/kotlin/io/zenoh/EncodingHandleTest.kt index d7685186..ac835c0e 100644 --- a/zenoh-java/src/jvmTest/kotlin/io/zenoh/EncodingHandleTest.kt +++ b/zenoh-java/src/jvmTest/kotlin/io/zenoh/EncodingHandleTest.kt @@ -33,9 +33,11 @@ import org.junit.Test * the send call itself, no handle ever exists; * - custom (schema-carrying) encodings own a handle from construction — * construction is the one crossing, every send after is a bare jlong; - * - received encodings arrive WITH their handle in the same delivery - * crossing, so re-sending one (the save-and-republish scenario) never - * rebuilds the native value from its schema string. + * - received SCHEMA-CARRYING encodings arrive WITH their handle in the same + * delivery crossing, so re-sending one (the save-and-republish scenario) + * never rebuilds the native value from its schema string; a received + * PRESET arrives value-only — its handle would buy nothing (the id arm is + * free), so the binding never materializes one (`encoding_if_schema`). */ class EncodingHandleTest { @@ -95,7 +97,7 @@ class EncodingHandleTest { } @Test - fun predefinedRoundTripStaysValueOnSendAndHandleOnReceive() { + fun predefinedRoundTripStaysValueOnlyEndToEnd() { val session = Zenoh.open(Config.loadDefault()) val keyExpr = KeyExpr.tryFrom("example/testing/encoding/preset") val received = mutableListOf() @@ -110,8 +112,10 @@ class EncodingHandleTest { assertNull(Encoding.TEXT_PLAIN.handle) assertEquals(1, received.size) assertEquals(Encoding.TEXT_PLAIN, received[0].encoding) - // …while the received copy arrived send-ready. - assertNotNull(received[0].encoding.handle) + // …and the received copy is value-only too: a schema-less encoding + // re-sends through the id arm for free, so no per-message native + // handle (clone + Box + wrapper + Cleaner) is ever materialized. + assertNull(received[0].encoding.handle) subscriber.close() session.close() From 6fa73b81d9b31fc06eb3ce120881651c1d16b264 Mon Sep 17 00:00:00 2001 From: Michael Ilyin Date: Fri, 17 Jul 2026 19:39:56 +0200 Subject: [PATCH 6/9] Receive path goes value-only: KeyExpr string-backed except declared, Encoding always (id, schema) (#489) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Received preset encodings are value-only end to end zenoh-flat-jni#6 makes the delivered encoding handle CONDITIONAL on schema presence (binding-local encoding_if_schema behind prebindgen#84's field! leaf): a received preset re-sends through the id arm for free, so no per-message native handle — clone + Box + JVM wrapper + gc-Cleaner registration + Cleaner free — is ever materialized for it. Schema- carrying encodings keep the send-ready handle. Adapt the delivery plumbing: Sample.fromParts encH becomes nullable (Query's already was), the reply callback drops its non-null assertion, and EncodingHandleTest's preset round-trip flips to assert the received copy has NO handle — the new contract. CI pin bumped to the flat-jni#6 tip. Co-Authored-By: Claude Fable 5 * CI: bump zenoh-flat-jni pin to the #6 merge commit The previous pin was a branch commit deleted on merge; 1e040c9 is ZettaScaleLabs/zenoh-flat-jni#6 on main (conditional encoding handle, final fun!+sig! syntax). Co-Authored-By: Claude Fable 5 * KeyExpr: string-backed except declared Consumes zenoh-flat-jni's string-delivering KeyExpr output. Ownership model (the Encoding model transplanted): a native handle exists ONLY behind Session.declareKeyExpr — the single case zenoh attaches a wire declaration (the only state beyond the string, paying off only on send through the declaring session). Everything else is a plain string value: - tryFrom validates natively once and releases the probe handle; autocanonize reads the canonized string and releases; - RECEIVED keyexprs (sample/query/reply) arrive as ONE eager string — no native allocation, nothing to free; the old per-message handle never carried a declaration (zenoh's RX path builds declaration-less) so it bought nothing on re-send while costing an unfreeable alloc and a second getStr crossing for every string-reading consumer; - sends pick the string or handle selector arm via KeyExpr.jniSel/jniStr/jniHandle (cloneHandle for consuming params); - algebra ops use the declared handle or a transient probe; - undeclare demotes to string-backed; close() is meaningful only for declared instances (doc matches upstream again). 113 jvmTest green incl. the new KeyExprHandleTest (constructed/declared/ received ownership + saved-keyexpr re-publish). CI pin bumped. Co-Authored-By: Claude Fable 5 * CI: bump flat-jni pin to the gc_managed-KeyExpr commit Co-Authored-By: Claude Fable 5 * examples: ping-pong exercises the receive-then-resend encoding path ZPing puts carry a custom schema-carrying encoding and verify (after warmup) that the echoed sample brings it back; ZPong retransmits the RECEIVED payload AND encoding — a pong that drops metadata is not a faithful echo, and the round trip now measures the real receive-then-resend path the send-ready Encoding handle was built for. Co-Authored-By: Claude Fable 5 * Encoding: received encodings are value-only (id, schema) Adapts to zenoh-flat-jni aecffac, which dropped the receive-side conditional handle leaf: the ping-pong A/B (ZPing/ZPong with a custom schema-carrying encoding, real payload+encoding retransmission) showed the per-receive handle lifecycle costs more than the schema re-decode it saves on resend (flat medians 68/68us handle vs 58/67us value-only at 8B/1KB, value-only never slower). Custom encodings still own a construction-born handle and send as a bare jlong; a saved received encoding re-sends through the (id, schema) value arm. Callback lambdas and Sample/Query.fromParts lose the encH leaf; EncodingHandleTest asserts value-only delivery; CI pin bumped. Co-Authored-By: Claude Fable 5 * ci: bump zenoh-flat-jni pin to e4a8ed7 Co-Authored-By: Claude Fable 5 * examples: restore upstream ZPing/ZPong The custom-encoding echo instrumentation was measurement scaffolding for the Encoding handle A/B (verdict shipped: received encodings are value-only); the examples keep their upstream form. Co-Authored-By: Claude Fable 5 --------- Co-authored-by: Claude Fable 5 --- .github/workflows/ci.yml | 2 +- .../kotlin/io/zenoh/FlatCallbacks.kt | 16 +-- .../src/commonMain/kotlin/io/zenoh/Session.kt | 33 +++-- .../kotlin/io/zenoh/bytes/Encoding.kt | 16 ++- .../kotlin/io/zenoh/keyexpr/KeyExpr.kt | 132 +++++++++++++----- .../kotlin/io/zenoh/liveliness/Liveliness.kt | 17 ++- .../commonMain/kotlin/io/zenoh/query/Query.kt | 14 +- .../kotlin/io/zenoh/sample/Sample.kt | 19 ++- .../kotlin/io/zenoh/EncodingHandleTest.kt | 31 ++-- .../kotlin/io/zenoh/KeyExprHandleTest.kt | 92 ++++++++++++ 10 files changed, 266 insertions(+), 106 deletions(-) create mode 100644 zenoh-java/src/jvmTest/kotlin/io/zenoh/KeyExprHandleTest.kt diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d28833d5..1524d036 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -31,7 +31,7 @@ jobs: uses: actions/checkout@v4 with: repository: ZettaScaleLabs/zenoh-flat-jni - ref: 1e040c96d0729c387d411a2356f361e9ea420877 + ref: e4a8ed7a354f6f44ab1e44f70771298dc82c9c67 path: zenoh-flat-jni - name: Check out zenoh-flat diff --git a/zenoh-java/src/commonMain/kotlin/io/zenoh/FlatCallbacks.kt b/zenoh-java/src/commonMain/kotlin/io/zenoh/FlatCallbacks.kt index c8581fbe..20dbae09 100644 --- a/zenoh-java/src/commonMain/kotlin/io/zenoh/FlatCallbacks.kt +++ b/zenoh-java/src/commonMain/kotlin/io/zenoh/FlatCallbacks.kt @@ -39,40 +39,40 @@ import io.zenoh.scouting.Hello internal fun sampleCallbackOf( f: (Sample) -> Unit ): io.zenoh.jni.sample.SampleCallback = - io.zenoh.jni.sample.SampleCallback { keH, payloadH, encId, encSchema, encH, kindInt, ntp64, express, prioInt, ccInt, attachH, reliabilityInt, sourceZid, sourceEid, sourceSn -> - f(Sample.fromParts(keH, payloadH, encId, encSchema, encH, kindInt, ntp64, express, prioInt, ccInt, attachH, reliabilityInt, sourceZid, sourceEid, sourceSn)) + io.zenoh.jni.sample.SampleCallback { keStr, payloadH, encId, encSchema, kindInt, ntp64, express, prioInt, ccInt, attachH, reliabilityInt, sourceZid, sourceEid, sourceSn -> + f(Sample.fromParts(keStr, payloadH, encId, encSchema, kindInt, ntp64, express, prioInt, ccInt, attachH, reliabilityInt, sourceZid, sourceEid, sourceSn)) } internal fun queryCallbackOf( f: (Query) -> Unit ): io.zenoh.jni.query.QueryCallback = - io.zenoh.jni.query.QueryCallback { keH, parameters, payloadH, encId, encSchema, encH, attachH, acceptsReplies, zq -> - // The decomposed leaves — including the cloned `keH` key-expr handle and + 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. - f(Query.fromParts(keH, parameters, payloadH, encId, encSchema, encH, attachH, acceptsReplies, zq)) + f(Query.fromParts(keStr, parameters, payloadH, encId, encSchema, attachH, acceptsReplies, zq)) } internal fun replyCallbackOf( f: (Reply) -> Unit ): io.zenoh.jni.query.ReplyCallback = - io.zenoh.jni.query.ReplyCallback { zid, eid, isOk, keH, payloadH, encId, encSchema, encH, kindInt, ntp64, express, prioInt, ccInt, attachH, reliabilityInt, sourceZid, sourceEid, sourceSn, errPayloadH, errEncId, errEncSchema, errEncH -> + io.zenoh.jni.query.ReplyCallback { zid, eid, isOk, keStr, payloadH, encId, encSchema, kindInt, ntp64, express, prioInt, ccInt, attachH, reliabilityInt, sourceZid, sourceEid, sourceSn, errPayloadH, errEncId, errEncSchema -> val replierId = zid?.let { EntityGlobalId(ZenohId(it), eid.toUInt()) } f( if (isOk) { Reply.Success( replierId, - Sample.fromParts(keH!!, payloadH!!, encId!!, encSchema, encH, kindInt!!, ntp64, express!!, prioInt!!, ccInt!!, attachH, reliabilityInt!!, sourceZid, sourceEid!!, sourceSn!!) + Sample.fromParts(keStr!!, payloadH!!, encId!!, encSchema, kindInt!!, ntp64, express!!, prioInt!!, ccInt!!, attachH, reliabilityInt!!, sourceZid, sourceEid!!, sourceSn!!) ) } else { Reply.Error( replierId, ZBytes.fromHandle(errPayloadH!!), - errEncId?.let { Encoding(it, errEncSchema, errEncH) } ?: Encoding.defaultEncoding() + errEncId?.let { Encoding(it, errEncSchema) } ?: Encoding.defaultEncoding() ) } ) diff --git a/zenoh-java/src/commonMain/kotlin/io/zenoh/Session.kt b/zenoh-java/src/commonMain/kotlin/io/zenoh/Session.kt index 49b12215..693af037 100644 --- a/zenoh-java/src/commonMain/kotlin/io/zenoh/Session.kt +++ b/zenoh-java/src/commonMain/kotlin/io/zenoh/Session.kt @@ -31,6 +31,9 @@ import io.zenoh.handlers.BlockingQueueHandler import io.zenoh.handlers.Callback import io.zenoh.handlers.Handler import io.zenoh.keyexpr.KeyExpr +import io.zenoh.keyexpr.jniSel +import io.zenoh.keyexpr.jniStr +import io.zenoh.keyexpr.jniHandle import io.zenoh.liveliness.Liveliness import io.zenoh.pubsub.* import io.zenoh.qos.QoS @@ -388,7 +391,10 @@ class Session private constructor(private val config: Config) : AutoCloseable { fun declareKeyExpr(keyExpr: String): KeyExpr { val zSession = zSession ?: throw sessionClosedException val keyexpr = run { - KeyExpr(zSession.declareKeyexpr(keyExpr, throwZError)) + // The one place a KeyExpr carries a native handle: the wire + // declaration attached here makes sends through this session + // compact, so the handle is worth holding. + KeyExpr(keyExpr, zSession.declareKeyexpr(keyExpr, throwZError)) } strongDeclarations.add(keyexpr) return keyexpr @@ -406,13 +412,14 @@ class Session private constructor(private val config: Config) : AutoCloseable { @Throws(ZError::class) fun undeclare(keyExpr: KeyExpr) { val zSession = zSession ?: throw sessionClosedException - val handle = keyExpr.flat - if (handle.isClosed()) { + val handle = keyExpr.handle + if (handle == null || handle.isClosed()) { throw ZError("Attempting to undeclare a non declared key expression.") } run { zSession.undeclareKeyexpr(handle, throwZError) } + keyExpr.handle = null } /** @@ -591,7 +598,7 @@ class Session private constructor(private val config: Config) : AutoCloseable { // plain puts then cross no encoding data at all (see Publisher). val enc = options.encoding val zPublisher = zSession.declarePublisher( - keyExpr.cloneFlat(), + keyExpr.jniSel, keyExpr.jniStr, keyExpr.cloneHandle(), enc.jniSel, enc.jniId, enc.jniSchema, enc.jniHandle, options.congestionControl.jni, options.priority.jni, @@ -618,7 +625,7 @@ class Session private constructor(private val config: Config) : AutoCloseable { val zSession = zSession ?: throw sessionClosedException val subscriber = run { val zSubscriber = zSession.declareSubscriber( - keyExpr.cloneFlat(), + keyExpr.jniSel, keyExpr.jniStr, keyExpr.cloneHandle(), sampleCallbackOf { handler.handle(it) }, { handler.onClose() }, throwZError @@ -636,7 +643,7 @@ class Session private constructor(private val config: Config) : AutoCloseable { val zSession = zSession ?: throw sessionClosedException val subscriber = run { val zSubscriber = zSession.declareSubscriber( - keyExpr.cloneFlat(), + keyExpr.jniSel, keyExpr.jniStr, keyExpr.cloneHandle(), sampleCallbackOf { callback.run(it) }, { }, throwZError @@ -654,7 +661,7 @@ class Session private constructor(private val config: Config) : AutoCloseable { val zSession = zSession ?: throw sessionClosedException val queryable = run { val zQueryable = zSession.declareQueryable( - keyExpr.cloneFlat(), + keyExpr.jniSel, keyExpr.jniStr, keyExpr.cloneHandle(), options.complete, queryCallbackOf { handler.handle(it) }, { handler.onClose() }, @@ -673,7 +680,7 @@ class Session private constructor(private val config: Config) : AutoCloseable { val zSession = zSession ?: throw sessionClosedException val queryable = run { val zQueryable = zSession.declareQueryable( - keyExpr.cloneFlat(), + keyExpr.jniSel, keyExpr.jniStr, keyExpr.cloneHandle(), options.complete, queryCallbackOf { callback.run(it) }, { }, @@ -693,7 +700,7 @@ class Session private constructor(private val config: Config) : AutoCloseable { val zSession = zSession ?: throw sessionClosedException val querier = run { val zQuerier = zSession.declareQuerier( - keyExpr.cloneFlat(), + keyExpr.jniSel, keyExpr.jniStr, keyExpr.cloneHandle(), options.target.toFlat(), options.consolidationMode.toFlat(), options.congestionControl.jni, @@ -727,7 +734,7 @@ class Session private constructor(private val config: Config) : AutoCloseable { return run { val sel = selector.into() zSession.get( - sel.keyExpr.flat, + sel.keyExpr.jniSel, sel.keyExpr.jniStr, sel.keyExpr.jniHandle, sel.parameters?.toString(), options.timeout.toMillis(), options.target.toFlat(), @@ -757,7 +764,7 @@ class Session private constructor(private val config: Config) : AutoCloseable { run { val sel = selector.into() zSession.get( - sel.keyExpr.flat, + sel.keyExpr.jniSel, sel.keyExpr.jniStr, sel.keyExpr.jniHandle, sel.parameters?.toString(), options.timeout.toMillis(), options.target.toFlat(), @@ -782,7 +789,7 @@ class Session private constructor(private val config: Config) : AutoCloseable { run { val enc = putOptions.encoding zSession.put( - keyExpr.flat, + keyExpr.jniSel, keyExpr.jniStr, keyExpr.jniHandle, payload.into().bytes, enc.jniSel, enc.jniId, enc.jniSchema, enc.jniHandle, putOptions.congestionControl.jni, @@ -800,7 +807,7 @@ class Session private constructor(private val config: Config) : AutoCloseable { val zSession = zSession ?: return run { zSession.delete( - keyExpr.flat, + keyExpr.jniSel, keyExpr.jniStr, keyExpr.jniHandle, deleteOptions.congestionControl.jni, deleteOptions.priority.jni, deleteOptions.express, diff --git a/zenoh-java/src/commonMain/kotlin/io/zenoh/bytes/Encoding.kt b/zenoh-java/src/commonMain/kotlin/io/zenoh/bytes/Encoding.kt index e22b4b1a..27cf33d8 100644 --- a/zenoh-java/src/commonMain/kotlin/io/zenoh/bytes/Encoding.kt +++ b/zenoh-java/src/commonMain/kotlin/io/zenoh/bytes/Encoding.kt @@ -38,13 +38,15 @@ import io.zenoh.jni.bytes.Encoding as JniEncoding * value-only: a send carries just their id inside the send call itself — no * native handle ever exists for them. An encoding that already owns a native * [handle] sends it as a bare `jlong` instead (the native side borrows and - * clones it, so the handle is reusable forever). Handles exist only where - * they are born without an extra crossing: custom (schema-carrying) encodings - * create one at construction, and received encodings (sample/query/reply) - * arrive with one in the same delivery crossing. Handle release is GC-managed - * (the JNI `Encoding` class is `gc_managed` — a shared Cleaner frees the - * native box once the handle is unreachable) — the value stays non-closeable - * either way. + * clones it, so the handle is reusable forever). Handles are born ONLY at + * construction of a custom (schema-carrying) encoding — construction is the + * one crossing. Received encodings (sample/query/reply) are always + * value-only `(id, schema)`: delivering a handle with each message + * measurably cost more than the schema re-decode it saved when one was + * re-sent, so a re-sent received encoding crosses through the `(id, schema)` + * value arm instead. Handle release is GC-managed (the JNI `Encoding` class + * is `gc_managed` — a shared Cleaner frees the native box once the handle is + * unreachable) — the value stays non-closeable either way. */ class Encoding internal constructor( internal val id: Int, diff --git a/zenoh-java/src/commonMain/kotlin/io/zenoh/keyexpr/KeyExpr.kt b/zenoh-java/src/commonMain/kotlin/io/zenoh/keyexpr/KeyExpr.kt index cb581d3c..999da47d 100644 --- a/zenoh-java/src/commonMain/kotlin/io/zenoh/keyexpr/KeyExpr.kt +++ b/zenoh-java/src/commonMain/kotlin/io/zenoh/keyexpr/KeyExpr.kt @@ -51,36 +51,41 @@ import io.zenoh.query.Selector * * # 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. However, it is crucial to manually invoke [close] on each [KeyExpr] - * instance before it is garbage collected to prevent memory leaks. - * - * As an alternative, employing a try-with-resources pattern using Kotlin's `use` block is recommended. This approach - * ensures that [close] is automatically called, safely managing the lifecycle of the [KeyExpr] instance. - * + * 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. */ class KeyExpr internal constructor( - /** The owned native handle, passed (by reference) to raw `keyexpr_*` ops. */ - internal val flat: JniKeyExpr, - keyExprString: String? = null, + private val keyExprString: 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 handle: JniKeyExpr? = null, ) : AutoCloseable, IntoSelector, SessionDeclaration { - private var keyExprStringLazy: String? = keyExprString + /** Clone the native handle before passing it to a consuming Rust API. */ + internal fun cloneHandle(): JniKeyExpr? = handle?.newClone(throwZError0) /** - * The string form, read LAZILY from the native handle on first use - * (toString/equals) — a received key expression is usually only matched - * or echoed by handle, so the common path never crosses for the string - * (forward-extraction rule: handle eager, string on demand). + * 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 val keyExprString: String - get() = keyExprStringLazy - ?: flat.getStr(throwZError0).also { keyExprStringLazy = it } - - /** Clone the native handle before passing it to a consuming Rust API. */ - internal fun cloneFlat(): JniKeyExpr = flat.newClone(throwZError0) + private inline fun withHandle(body: (JniKeyExpr) -> R): R { + val h = handle + if (h != null) return body(h) + val tmp = JniKeyExpr.newTryFrom(keyExprString, throwZError) + try { + return body(tmp) + } finally { + tmp.close() + } + } companion object { @@ -92,13 +97,20 @@ class KeyExpr internal constructor( * * 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 The [KeyExpr] in case of success. * @throws ZError in the case of failure. */ @JvmStatic @Throws(ZError::class) - fun tryFrom(keyExpr: String): KeyExpr = KeyExpr(JniKeyExpr.newTryFrom(keyExpr, throwZError)) + fun tryFrom(keyExpr: String): KeyExpr { + JniKeyExpr.newTryFrom(keyExpr, throwZError).close() + return KeyExpr(keyExpr) + } /** * Autocanonize. @@ -112,7 +124,14 @@ class KeyExpr internal constructor( */ @JvmStatic @Throws(ZError::class) - fun autocanonize(keyExpr: String): KeyExpr = KeyExpr(JniKeyExpr.newAutocanonize(keyExpr, throwZError)) + fun autocanonize(keyExpr: String): KeyExpr { + val probe = JniKeyExpr.newAutocanonize(keyExpr, throwZError) + try { + return KeyExpr(probe.getStr(throwZError0)) + } finally { + probe.close() + } + } } /** @@ -121,8 +140,10 @@ class KeyExpr internal constructor( * Will return false as well if the key expression is not valid anymore. */ @Throws(ZError::class) - fun intersects(other: KeyExpr): Boolean = - this.flat.intersects(other.flat, throwZError0) + fun intersects(other: KeyExpr): Boolean = withHandle { h -> + other.handle?.let { h.intersects(it, throwZError0) } + ?: h.intersects(other.keyExprString, throwZError0) + } /** * Includes operation. This method returns `true` when all the keys defined by `other` also belong to the set @@ -130,8 +151,10 @@ class KeyExpr internal constructor( * Will return false as well if the key expression is not valid anymore. */ @Throws(ZError::class) - fun includes(other: KeyExpr): Boolean = - this.flat.includes(other.flat, throwZError0) + fun includes(other: KeyExpr): Boolean = withHandle { h -> + other.handle?.let { h.includes(it, throwZError0) } + ?: h.includes(other.keyExprString, throwZError0) + } /** * Returns the relation between 'this' and other from 'this''s point of view ([SetIntersectionLevel.INCLUDES] @@ -139,24 +162,43 @@ class KeyExpr internal constructor( * so you should favor these methods for most applications. */ @Throws(ZError::class) - fun relationTo(other: KeyExpr): SetIntersectionLevel = - SetIntersectionLevel.fromJni(this.flat.relationTo(other.flat, throwZError0)) + fun relationTo(other: KeyExpr): SetIntersectionLevel = withHandle { h -> + val raw = other.handle?.let { h.relationTo(it, throwZError0) } + ?: h.relationTo(other.keyExprString, throwZError0) + SetIntersectionLevel.fromJni(raw) + } /** * Joins both sides, inserting a / in between them. * This should be your preferred method when concatenating path segments. */ @Throws(ZError::class) - fun join(other: String): KeyExpr = - KeyExpr(JniKeyExpr.newJoin(this.flat, other, throwZError)) + fun join(other: String): KeyExpr { + // The companion factory validates and joins natively; the result is a + // fresh (declaration-less) handle — keep its canonical string only. + val probe = handle?.let { JniKeyExpr.newJoin(it, other, throwZError) } + ?: JniKeyExpr.newJoin(keyExprString, other, throwZError) + try { + return KeyExpr(probe.getStr(throwZError0)) + } finally { + probe.close() + } + } /** * 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. */ @Throws(ZError::class) - fun concat(other: String): KeyExpr = - KeyExpr(JniKeyExpr.newConcat(this.flat, other, throwZError)) + fun concat(other: String): KeyExpr { + val probe = handle?.let { JniKeyExpr.newConcat(it, other, throwZError) } + ?: JniKeyExpr.newConcat(keyExprString, other, throwZError) + try { + return KeyExpr(probe.getStr(throwZError0)) + } finally { + probe.close() + } + } override fun toString(): String = keyExprString @@ -175,8 +217,8 @@ class KeyExpr internal constructor( * operations on it, but without the inner optimizations. */ override fun undeclare() { - // Frees the owned native key-expression handle. - flat.close() + handle?.close() + handle = null } override fun into(): Selector = Selector(this) @@ -194,3 +236,19 @@ class KeyExpr internal constructor( return keyExprString.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 (handle != null) 1 else 0 + +internal val KeyExpr.jniStr: String? + get() = if (handle == null) toString() else null + +internal val KeyExpr.jniHandle: JniKeyExpr? + get() = handle diff --git a/zenoh-java/src/commonMain/kotlin/io/zenoh/liveliness/Liveliness.kt b/zenoh-java/src/commonMain/kotlin/io/zenoh/liveliness/Liveliness.kt index 5bd332df..15205f20 100644 --- a/zenoh-java/src/commonMain/kotlin/io/zenoh/liveliness/Liveliness.kt +++ b/zenoh-java/src/commonMain/kotlin/io/zenoh/liveliness/Liveliness.kt @@ -23,6 +23,9 @@ import io.zenoh.handlers.BlockingQueueHandler import io.zenoh.handlers.Callback import io.zenoh.handlers.Handler import io.zenoh.keyexpr.KeyExpr +import io.zenoh.keyexpr.jniSel +import io.zenoh.keyexpr.jniStr +import io.zenoh.keyexpr.jniHandle import io.zenoh.pubsub.CallbackSubscriber import io.zenoh.pubsub.HandlerSubscriber import io.zenoh.pubsub.Subscriber @@ -51,7 +54,7 @@ class Liveliness internal constructor(private val session: Session) { @Throws(ZError::class) fun declareToken(keyExpr: KeyExpr): LivelinessToken { val zSession = session.zSession ?: throw Session.sessionClosedException - return LivelinessToken(zSession.livelinessDeclareToken(keyExpr.cloneFlat(), throwZError)) + return LivelinessToken(zSession.livelinessDeclareToken(keyExpr.jniSel, keyExpr.jniStr, keyExpr.cloneHandle(), throwZError)) } /** @@ -69,7 +72,7 @@ class Liveliness internal constructor(private val session: Session) { val zSession = session.zSession ?: throw Session.sessionClosedException val handler = BlockingQueueHandler(LinkedBlockingDeque()) zSession.livelinessGet( - keyExpr.flat, + keyExpr.jniSel, keyExpr.jniStr, keyExpr.jniHandle, timeout.toMillis(), replyCallbackOf { handler.handle(it) }, { handler.onClose() }, @@ -92,7 +95,7 @@ class Liveliness internal constructor(private val session: Session) { ) { val zSession = session.zSession ?: throw Session.sessionClosedException zSession.livelinessGet( - keyExpr.flat, + keyExpr.jniSel, keyExpr.jniStr, keyExpr.jniHandle, timeout.toMillis(), replyCallbackOf { callback.run(it) }, { }, @@ -115,7 +118,7 @@ class Liveliness internal constructor(private val session: Session) { ): R { val zSession = session.zSession ?: throw Session.sessionClosedException zSession.livelinessGet( - keyExpr.flat, + keyExpr.jniSel, keyExpr.jniStr, keyExpr.jniHandle, timeout.toMillis(), replyCallbackOf { handler.handle(it) }, { handler.onClose() }, @@ -139,7 +142,7 @@ class Liveliness internal constructor(private val session: Session) { val handler = BlockingQueueHandler(LinkedBlockingDeque()) val zSession = session.zSession ?: throw Session.sessionClosedException val zSubscriber = zSession.livelinessDeclareSubscriber( - keyExpr.cloneFlat(), + keyExpr.jniSel, keyExpr.jniStr, keyExpr.cloneHandle(), options.history, sampleCallbackOf { handler.handle(it) }, { handler.onClose() }, @@ -164,7 +167,7 @@ class Liveliness internal constructor(private val session: Session) { ): CallbackSubscriber { val zSession = session.zSession ?: throw Session.sessionClosedException val zSubscriber = zSession.livelinessDeclareSubscriber( - keyExpr.cloneFlat(), + keyExpr.jniSel, keyExpr.jniStr, keyExpr.cloneHandle(), options.history, sampleCallbackOf { callback.run(it) }, { }, @@ -190,7 +193,7 @@ class Liveliness internal constructor(private val session: Session) { ): HandlerSubscriber { val zSession = session.zSession ?: throw Session.sessionClosedException val zSubscriber = zSession.livelinessDeclareSubscriber( - keyExpr.cloneFlat(), + keyExpr.jniSel, keyExpr.jniStr, keyExpr.cloneHandle(), options.history, sampleCallbackOf { handler.handle(it) }, { handler.onClose() }, diff --git a/zenoh-java/src/commonMain/kotlin/io/zenoh/query/Query.kt b/zenoh-java/src/commonMain/kotlin/io/zenoh/query/Query.kt index 8ddebb98..283aacb4 100644 --- a/zenoh-java/src/commonMain/kotlin/io/zenoh/query/Query.kt +++ b/zenoh-java/src/commonMain/kotlin/io/zenoh/query/Query.kt @@ -25,6 +25,9 @@ import io.zenoh.bytes.ZBytes import io.zenoh.exceptions.ZError import io.zenoh.exceptions.throwZError import io.zenoh.keyexpr.KeyExpr +import io.zenoh.keyexpr.jniSel +import io.zenoh.keyexpr.jniStr +import io.zenoh.keyexpr.jniHandle /** * Represents a Zenoh Query in Kotlin. @@ -60,24 +63,23 @@ class Query internal constructor( * keeps working after the callback returns). */ fun fromParts( - keH: io.zenoh.jni.keyexpr.KeyExpr, + keStr: String, parameters: String, payloadH: io.zenoh.jni.bytes.ZBytes?, encId: Int?, encSchema: String?, - encH: io.zenoh.jni.bytes.Encoding?, attachH: io.zenoh.jni.bytes.ZBytes?, acceptsRepliesInt: Int, zq: io.zenoh.jni.query.Query, ): Query { - val ke = KeyExpr(keH) + val ke = KeyExpr(keStr) val selector = if (parameters.isEmpty()) Selector(ke) else Selector(ke, Parameters.from(parameters)) return Query( ke, selector, payloadH?.let { ZBytes.fromHandle(it) }, - encId?.let { Encoding(it, encSchema, encH) }, + encId?.let { Encoding(it, encSchema) }, attachH?.let { ZBytes.fromHandle(it) }, io.zenoh.jni.query.ReplyKeyExpr.fromInt(acceptsRepliesInt).toPublic(), zq @@ -99,7 +101,7 @@ class Query internal constructor( val q = zQuery ?: throw ZError("Query is invalid") val enc = options.encoding q.replySuccess( - keyExpr.flat, + keyExpr.jniSel, keyExpr.jniStr, keyExpr.jniHandle, payload.into().bytes, enc.jniSel, enc.jniId, enc.jniSchema, enc.jniHandle, options.timeStamp?.ntpValue(), @@ -138,7 +140,7 @@ class Query internal constructor( fun replyDel(keyExpr: KeyExpr, options: ReplyDelOptions = ReplyDelOptions()) { val q = zQuery ?: throw ZError("Query is invalid") q.replyDelete( - keyExpr.flat, + keyExpr.jniSel, keyExpr.jniStr, keyExpr.jniHandle, options.timeStamp?.ntpValue(), options.attachment?.into()?.bytes, options.express, diff --git a/zenoh-java/src/commonMain/kotlin/io/zenoh/sample/Sample.kt b/zenoh-java/src/commonMain/kotlin/io/zenoh/sample/Sample.kt index e3cc70a4..4438c3ec 100644 --- a/zenoh-java/src/commonMain/kotlin/io/zenoh/sample/Sample.kt +++ b/zenoh-java/src/commonMain/kotlin/io/zenoh/sample/Sample.kt @@ -53,25 +53,24 @@ data class Sample( internal companion object { /** - * Builds an SDK [Sample] from the 15 leaves of the ZSample canonical + * Builds an SDK [Sample] from the 14 leaves of the ZSample canonical * output — the lambda parameter list every decomposed sample delivery * uses (the `zReplySample` builder and the subscriber/liveliness * callbacks), in record order. The whole graph arrives in ONE JNI - * crossing; the [KeyExpr] retains the delivered handle leaf, and the - * [Encoding] retains its delivered handle (`encH`) so re-sending it - * crosses a bare `jlong` instead of rebuilding the native value — - * delivered ONLY for schema-carrying encodings (a preset re-sends - * through the id arm for free, so it arrives value-only). The + * crossing; the [KeyExpr] is string-backed (a received keyexpr never + * carries a wire declaration, so a native handle would buy nothing), + * and the [Encoding] is value-only `(id, schema?)` — a delivered + * native handle measurably cost more per receive than the + * schema-string re-decode it saved on the resent fraction. The * trailing `reliability` / `source*` leaves are part of the generated * decomposition but are not surfaced on the public [Sample] type. */ @Suppress("UNUSED_PARAMETER") fun fromParts( - keH: io.zenoh.jni.keyexpr.KeyExpr, + keStr: String, payloadH: io.zenoh.jni.bytes.ZBytes, encId: Int, encSchema: String?, - encH: io.zenoh.jni.bytes.Encoding?, kindInt: Int, ntp64: Long?, express: Boolean, @@ -83,9 +82,9 @@ data class Sample( sourceEid: Int, sourceSn: Long, ): Sample = Sample( - KeyExpr(keH), + KeyExpr(keStr), ZBytes.fromHandle(payloadH), - Encoding(encId, encSchema, encH), + Encoding(encId, encSchema), io.zenoh.jni.sample.SampleKind.fromInt(kindInt).toPublic(), ntp64?.let { TimeStamp(it) }, QoS( diff --git a/zenoh-java/src/jvmTest/kotlin/io/zenoh/EncodingHandleTest.kt b/zenoh-java/src/jvmTest/kotlin/io/zenoh/EncodingHandleTest.kt index ac835c0e..132b38a7 100644 --- a/zenoh-java/src/jvmTest/kotlin/io/zenoh/EncodingHandleTest.kt +++ b/zenoh-java/src/jvmTest/kotlin/io/zenoh/EncodingHandleTest.kt @@ -22,7 +22,6 @@ import io.zenoh.sample.Sample import org.junit.Assert.assertEquals import org.junit.Assert.assertNotNull import org.junit.Assert.assertNull -import org.junit.Assert.assertSame import org.junit.Test /** @@ -33,11 +32,11 @@ import org.junit.Test * the send call itself, no handle ever exists; * - custom (schema-carrying) encodings own a handle from construction — * construction is the one crossing, every send after is a bare jlong; - * - received SCHEMA-CARRYING encodings arrive WITH their handle in the same - * delivery crossing, so re-sending one (the save-and-republish scenario) - * never rebuilds the native value from its schema string; a received - * PRESET arrives value-only — its handle would buy nothing (the id arm is - * free), so the binding never materializes one (`encoding_if_schema`). + * - received encodings are ALWAYS value-only `(id, schema)`: a ping-pong A/B + * on the save-and-republish scenario showed the per-receive handle + * lifecycle (clone + Box + wrapper + Cleaner) costs more than the schema + * re-decode it saves on the resent fraction, so no handle is ever + * delivered — a re-sent received encoding crosses through the value arm. */ class EncodingHandleTest { @@ -58,14 +57,15 @@ class EncodingHandleTest { } @Test - fun receivedEncodingIsSendReadyAndResendsByHandle() { + fun receivedEncodingIsValueOnlyAndResendsThroughValueArm() { val custom = Encoding.from("application/custom;my-schema") val session = Zenoh.open(Config.loadDefault()) val keyExpr = KeyExpr.tryFrom("example/testing/encoding/handle") val received = mutableListOf() val subscriber = session.declareSubscriber(keyExpr) { received.add(it) } - // Send 1: user-created custom encoding — crosses by handle. + // Send 1: user-created custom encoding — crosses by its + // construction-born handle. val putOptions = PutOptions() putOptions.encoding = custom session.put(keyExpr, ZBytes.from("one"), putOptions) @@ -74,12 +74,12 @@ class EncodingHandleTest { assertEquals(1, received.size) val saved = received[0].encoding assertEquals(custom, saved) - // Delivered send-ready: the handle arrived with the sample. - assertNotNull(saved.handle) + // Value-only delivery: no per-message native handle materialized. + assertNull(saved.handle) - // Send 2: the user's scenario — re-publish with the SAVED encoding. - // The retained handle is what crosses; nothing is rebuilt. - val handleBefore = saved.handle + // Send 2: the save-and-republish scenario — the SAVED encoding + // re-sends through the (id, schema) value arm, rebuilt natively + // inside the same send crossing. val resendOptions = PutOptions() resendOptions.encoding = saved session.put(keyExpr, ZBytes.from("two"), resendOptions) @@ -87,10 +87,7 @@ class EncodingHandleTest { assertEquals(2, received.size) assertEquals(custom, received[1].encoding) - // The saved encoding still owns the same reusable handle (borrowed, - // never consumed, by the send). - assertSame(handleBefore, saved.handle) - assertNotNull(received[1].encoding.handle) + assertNull(received[1].encoding.handle) subscriber.close() session.close() diff --git a/zenoh-java/src/jvmTest/kotlin/io/zenoh/KeyExprHandleTest.kt b/zenoh-java/src/jvmTest/kotlin/io/zenoh/KeyExprHandleTest.kt new file mode 100644 index 00000000..afae4ab3 --- /dev/null +++ b/zenoh-java/src/jvmTest/kotlin/io/zenoh/KeyExprHandleTest.kt @@ -0,0 +1,92 @@ +// +// 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.ZBytes +import io.zenoh.keyexpr.KeyExpr +import io.zenoh.sample.Sample +import org.junit.Assert.assertEquals +import org.junit.Assert.assertNotNull +import org.junit.Assert.assertNull +import org.junit.Assert.assertTrue +import org.junit.Test + +/** + * The key-expression ownership model: a native handle exists ONLY behind + * `Session.declareKeyExpr` — the single case zenoh attaches a wire + * declaration worth carrying. Everything else — `tryFrom`, `autocanonize`, + * and every RECEIVED keyexpr (zenoh's RX path never attaches a declaration) + * — is a plain string-backed value: nothing to close, no native resource, + * no per-message allocation or leak. + */ +class KeyExprHandleTest { + + @Test + fun constructedKeyExprsAreStringBacked() { + val ke = KeyExpr.tryFrom("example/testing/keyexpr") + assertNull(ke.handle) + assertEquals("example/testing/keyexpr", ke.toString()) + // Canonization still happens natively; the result keeps the string. + val canon = KeyExpr.autocanonize("example/**/**") + assertNull(canon.handle) + assertEquals("example/**", canon.toString()) + // Algebra ops work on string-backed instances (transient native probe). + assertTrue(ke.intersects(KeyExpr.tryFrom("example/testing/*"))) + assertTrue(KeyExpr.tryFrom("example/**").includes(ke)) + assertEquals("example/testing/keyexpr/sub", ke.join("sub").toString()) + } + + @Test + fun declaredKeyExprOwnsAHandleUntilUndeclared() { + val session = Zenoh.open(Config.loadDefault()) + val declared = session.declareKeyExpr("example/testing/keyexpr/declared") + assertNotNull(declared.handle) + // Ops route through the declared handle. + assertTrue(declared.intersects(KeyExpr.tryFrom("example/testing/**"))) + // Undeclare demotes to string-backed; the string survives. + session.undeclare(declared) + assertNull(declared.handle) + assertEquals("example/testing/keyexpr/declared", declared.toString()) + session.close() + } + + @Test + fun receivedKeyExprIsStringBackedAndReusable() { + val session = Zenoh.open(Config.loadDefault()) + val keyExpr = KeyExpr.tryFrom("example/testing/keyexpr/received") + val received = mutableListOf() + val subscriber = session.declareSubscriber(keyExpr) { received.add(it) } + + session.put(keyExpr, ZBytes.from("one")) + Thread.sleep(500) + + assertEquals(1, received.size) + val saved = received[0].keyExpr + // Delivered as ONE eager string: no native allocation, nothing to + // free — a received keyexpr never carries a wire declaration, so a + // handle would buy nothing on re-send. + assertNull(saved.handle) + assertEquals(keyExpr, saved) + + // The user scenario: re-publish with the SAVED keyexpr. + session.put(saved, ZBytes.from("two")) + Thread.sleep(500) + assertEquals(2, received.size) + assertEquals(saved, received[1].keyExpr) + + subscriber.close() + session.close() + } +} From 2ae6e6700619d0ecc42a3423abe8b4e5caf7279e Mon Sep 17 00:00:00 2001 From: Michael Ilyin Date: Fri, 17 Jul 2026 20:19:01 +0200 Subject: [PATCH 7/9] docs: align lifecycle KDoc with actual GC management (#490) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Every handle-owning class except ZBytes is now GC-backstopped (Config joined in zenoh-flat-jni PR#8), and the docs now say exactly what happens to forgotten instances: - Session: close() KDoc contradicted the code — close undeclares every live declaration, it does not leave them alive; class KDoc notes the GC backstop. - ZBytes: new lifecycle section — created ZBytes are plain values; received ones free their native buffer on first read; a received payload/attachment that is never read keeps it (deliberately NOT GC-managed: per-message Cleaner cost measured -23% thr @8B). - Publisher/Querier: session reference is weak — forgotten instances are undeclared by the GC backstop, not at session close only. - Subscriber/Queryable: session reference is strong — dropping the reference does NOT stop them; active until undeclare or session close. - Scout/LivelinessToken: 'dropped' now spelled out as GC-collected, non-deterministic; close for prompt effect. - KeyExpr (declared) / Config: backstop notes. CI pin bumped to flat-jni dbb1f8c (Config gc_managed, PR#8). Co-authored-by: Claude Fable 5 --- .github/workflows/ci.yml | 2 +- .../src/commonMain/kotlin/io/zenoh/Config.kt | 3 +++ .../src/commonMain/kotlin/io/zenoh/Session.kt | 7 ++++++- .../src/commonMain/kotlin/io/zenoh/bytes/ZBytes.kt | 14 ++++++++++++++ .../commonMain/kotlin/io/zenoh/keyexpr/KeyExpr.kt | 4 +++- .../kotlin/io/zenoh/liveliness/LivelinessToken.kt | 3 ++- .../commonMain/kotlin/io/zenoh/pubsub/Publisher.kt | 4 +++- .../kotlin/io/zenoh/pubsub/Subscriber.kt | 4 ++++ .../commonMain/kotlin/io/zenoh/query/Querier.kt | 4 +++- .../commonMain/kotlin/io/zenoh/query/Queryable.kt | 4 ++++ .../commonMain/kotlin/io/zenoh/scouting/Scout.kt | 3 ++- 11 files changed, 45 insertions(+), 7 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1524d036..b20aac79 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -31,7 +31,7 @@ jobs: uses: actions/checkout@v4 with: repository: ZettaScaleLabs/zenoh-flat-jni - ref: e4a8ed7a354f6f44ab1e44f70771298dc82c9c67 + ref: dbb1f8c6509e333f49d1c03ecb84dcf07b7af7b8 path: zenoh-flat-jni - name: Check out zenoh-flat diff --git a/zenoh-java/src/commonMain/kotlin/io/zenoh/Config.kt b/zenoh-java/src/commonMain/kotlin/io/zenoh/Config.kt index d18977d6..d45049d7 100644 --- a/zenoh-java/src/commonMain/kotlin/io/zenoh/Config.kt +++ b/zenoh-java/src/commonMain/kotlin/io/zenoh/Config.kt @@ -34,6 +34,9 @@ import java.nio.file.Path * * A default configuration can be loaded using [Config.loadDefault]. * + * The native configuration a Config wraps is consumed by [Zenoh.open] (or [Zenoh.scout]); a Config that is + * never used is released by the garbage-collection backstop. + * * Visit the [default configuration](https://github.com/eclipse-zenoh/zenoh/blob/main/DEFAULT_CONFIG.json5) for more * information on the Zenoh config parameters. */ diff --git a/zenoh-java/src/commonMain/kotlin/io/zenoh/Session.kt b/zenoh-java/src/commonMain/kotlin/io/zenoh/Session.kt index 693af037..1101ece5 100644 --- a/zenoh-java/src/commonMain/kotlin/io/zenoh/Session.kt +++ b/zenoh-java/src/commonMain/kotlin/io/zenoh/Session.kt @@ -58,6 +58,10 @@ import java.util.concurrent.LinkedBlockingDeque * Sessions are open upon creation and can be closed manually by calling [close]. Alternatively, the session will be * automatically closed when used with Java's try-with-resources statement or its Kotlin counterpart, [use]. * + * Native resources held by a session (and by its declarations) that is simply forgotten are released by a + * garbage-collection backstop once the objects become unreachable; still, explicit [close] is recommended for + * deterministic release. + * * For optimal performance and adherence to good practices, it is recommended to have only one running session, which * is sufficient for most use cases. You should _never_ construct one session per publisher/subscriber, as this will * significantly increase the size of your Zenoh network, while preventing potential locality-based optimizations. @@ -96,7 +100,8 @@ class Session private constructor(private val config: Config) : AutoCloseable { * Closing the session invalidates any attempt to perform a declaration or to perform an operation such as Put or Delete. * Attempting to do so will result in a failure. * - * However, any session declaration that was still alive and bound to the session previous to closing it, will still be alive. + * Every declaration still bound to the session (subscribers, queryables, publishers, queriers, …) is undeclared + * as part of closing it. */ override fun close() { strongDeclarations.removeIf { diff --git a/zenoh-java/src/commonMain/kotlin/io/zenoh/bytes/ZBytes.kt b/zenoh-java/src/commonMain/kotlin/io/zenoh/bytes/ZBytes.kt index 9b91cb24..4d9fab88 100644 --- a/zenoh-java/src/commonMain/kotlin/io/zenoh/bytes/ZBytes.kt +++ b/zenoh-java/src/commonMain/kotlin/io/zenoh/bytes/ZBytes.kt @@ -32,6 +32,20 @@ import io.zenoh.jni.bytes.ZBytes as JniZBytes * 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. */ class ZBytes private constructor( private var eager: ByteArray?, diff --git a/zenoh-java/src/commonMain/kotlin/io/zenoh/keyexpr/KeyExpr.kt b/zenoh-java/src/commonMain/kotlin/io/zenoh/keyexpr/KeyExpr.kt index 999da47d..f8846b05 100644 --- a/zenoh-java/src/commonMain/kotlin/io/zenoh/keyexpr/KeyExpr.kt +++ b/zenoh-java/src/commonMain/kotlin/io/zenoh/keyexpr/KeyExpr.kt @@ -54,7 +54,9 @@ import io.zenoh.query.Selector * 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. + * such an instance should be [close]d (or `use`d) when no longer needed — + * the declaring session keeps a strong reference to it and undeclares it when + * the session closes, with a garbage-collection backstop after that. * Every other [KeyExpr] — constructed via [tryFrom]/[autocanonize] or received * with a sample — is a plain value: nothing to close, no native resource. */ diff --git a/zenoh-java/src/commonMain/kotlin/io/zenoh/liveliness/LivelinessToken.kt b/zenoh-java/src/commonMain/kotlin/io/zenoh/liveliness/LivelinessToken.kt index 8ce31f12..2945208d 100644 --- a/zenoh-java/src/commonMain/kotlin/io/zenoh/liveliness/LivelinessToken.kt +++ b/zenoh-java/src/commonMain/kotlin/io/zenoh/liveliness/LivelinessToken.kt @@ -26,7 +26,8 @@ import io.zenoh.session.SessionDeclaration * that declared the token has Zenoh connectivity with the Zenoh application * that monitors it. * - * Liveliness tokens are automatically undeclared when dropped. + * A token whose last reference is dropped is undeclared by the garbage-collection backstop + * (non-deterministic — call [undeclare] or [close] to withdraw the liveliness promptly). */ class LivelinessToken internal constructor(private var token: io.zenoh.jni.liveliness.LivelinessToken?): SessionDeclaration, AutoCloseable { diff --git a/zenoh-java/src/commonMain/kotlin/io/zenoh/pubsub/Publisher.kt b/zenoh-java/src/commonMain/kotlin/io/zenoh/pubsub/Publisher.kt index 4aefe6fe..c1400660 100644 --- a/zenoh-java/src/commonMain/kotlin/io/zenoh/pubsub/Publisher.kt +++ b/zenoh-java/src/commonMain/kotlin/io/zenoh/pubsub/Publisher.kt @@ -35,7 +35,9 @@ import kotlin.Throws * A Zenoh Publisher. * * A publisher is automatically dropped when using it with the 'try-with-resources' statement (i.e. 'use' in Kotlin). - * The session from which it was declared will also keep a reference to it and undeclare it once the session is closed. + * The session keeps only a *weak* reference to it: a publisher whose last reference is dropped is undeclared by the + * garbage-collection backstop (non-deterministic — call [close] for prompt effect), and closing the session + * undeclares any publisher still reachable. * * In order to declare a publisher, [Session.declarePublisher] must be called. * diff --git a/zenoh-java/src/commonMain/kotlin/io/zenoh/pubsub/Subscriber.kt b/zenoh-java/src/commonMain/kotlin/io/zenoh/pubsub/Subscriber.kt index 5dc69e16..bc536d1c 100644 --- a/zenoh-java/src/commonMain/kotlin/io/zenoh/pubsub/Subscriber.kt +++ b/zenoh-java/src/commonMain/kotlin/io/zenoh/pubsub/Subscriber.kt @@ -23,6 +23,10 @@ import io.zenoh.session.SessionDeclaration * * Its main purpose is to keep the subscription active as long as it exists. * + * The declaring session holds a *strong* reference to it: dropping your own reference does NOT stop the + * subscription — it stays active until [close] (or `undeclare`) is called or the session is closed, whichever + * comes first. Only after that does it become eligible for garbage collection. + * * Example using the default [BlockingQueueHandler] handler: * * ```java diff --git a/zenoh-java/src/commonMain/kotlin/io/zenoh/query/Querier.kt b/zenoh-java/src/commonMain/kotlin/io/zenoh/query/Querier.kt index 51a8ac0d..aa51663f 100644 --- a/zenoh-java/src/commonMain/kotlin/io/zenoh/query/Querier.kt +++ b/zenoh-java/src/commonMain/kotlin/io/zenoh/query/Querier.kt @@ -41,7 +41,9 @@ import java.util.concurrent.LinkedBlockingDeque /** * A querier that allows to send queries to a [Queryable]. * - * Queriers are automatically undeclared when dropped. + * A querier whose last reference is dropped is undeclared by the garbage-collection backstop + * (non-deterministic — call `close` for prompt effect); closing the session undeclares any querier + * still reachable. * * Example: * ```java diff --git a/zenoh-java/src/commonMain/kotlin/io/zenoh/query/Queryable.kt b/zenoh-java/src/commonMain/kotlin/io/zenoh/query/Queryable.kt index c70a5ce9..5cb9dbf6 100644 --- a/zenoh-java/src/commonMain/kotlin/io/zenoh/query/Queryable.kt +++ b/zenoh-java/src/commonMain/kotlin/io/zenoh/query/Queryable.kt @@ -24,6 +24,10 @@ import io.zenoh.session.SessionDeclaration * * Its main purpose is to keep the queryable active as long as it exists. * + * The declaring session holds a *strong* reference to it: dropping your own reference does NOT stop the + * queryable — it stays active until [close] (or `undeclare`) is called or the session is closed, whichever + * comes first. Only after that does it become eligible for garbage collection. + * * Example using the default [BlockingQueueHandler] handler: * ```java * try (Session session = Zenoh.open(config)) { diff --git a/zenoh-java/src/commonMain/kotlin/io/zenoh/scouting/Scout.kt b/zenoh-java/src/commonMain/kotlin/io/zenoh/scouting/Scout.kt index 4a2a4725..fb7af94b 100644 --- a/zenoh-java/src/commonMain/kotlin/io/zenoh/scouting/Scout.kt +++ b/zenoh-java/src/commonMain/kotlin/io/zenoh/scouting/Scout.kt @@ -25,7 +25,8 @@ import io.zenoh.handlers.Callback * Scout for routers and/or peers. * * Scout spawns a task that periodically sends scout messages and waits for Hello replies. - * Drop the returned Scout to stop the scouting task. + * Call `stop` (or `close`) on the returned Scout to stop the scouting task; a Scout whose last + * reference is dropped is stopped by the garbage-collection backstop (non-deterministic). * * To launch a scout, use [io.zenoh.Zenoh.scout]: * From ea88bf140d01def09458a92e44b78b17f7741b58 Mon Sep 17 00:00:00 2001 From: Michael Ilyin Date: Fri, 17 Jul 2026 23:59:17 +0200 Subject: [PATCH 8/9] Session.undeclare: detach the key-expr handle even when undeclare fails (#491) * Session.undeclare: detach the key-expr handle even when undeclare fails The generated wrapper consumes the handle even when the native undeclare errors (the Rust side takes it by value). Clearing keyExpr.handle only after a successful call left a dead handle attached on failure: every later operation selected the closed handle and failed instead of degrading to the string form. Detach in a finally; regression-tested by undeclaring through the wrong session. Found while porting zenoh-kotlin (eclipse-zenoh/zenoh-kotlin#668), which had inherited the same pattern. Co-Authored-By: Claude Opus 4.8 * Record PR #491 in the transition table Co-Authored-By: Claude Opus 4.8 --------- Co-authored-by: Claude Opus 4.8 --- ZENOH_FLAT_TRANSITION.md | 1 + .../src/commonMain/kotlin/io/zenoh/Session.kt | 9 +++++-- .../kotlin/io/zenoh/KeyExprHandleTest.kt | 25 +++++++++++++++++++ 3 files changed, 33 insertions(+), 2 deletions(-) diff --git a/ZENOH_FLAT_TRANSITION.md b/ZENOH_FLAT_TRANSITION.md index 492d969a..f3b79d31 100644 --- a/ZENOH_FLAT_TRANSITION.md +++ b/ZENOH_FLAT_TRANSITION.md @@ -27,6 +27,7 @@ zenoh (Rust) | --- | --- | --- | | [#481](https://github.com/eclipse-zenoh/zenoh-java/pull/481) | Use receiver-style zenoh-flat-jni bindings (generated Session/Query/Publisher methods, split key-expr overloads, de-prefixed callback names); +61–83% subscriber throughput | merged | | [#484](https://github.com/eclipse-zenoh/zenoh-java/pull/484) | `Encoding`/`ZenohId.toString` become pure JVM values (fixes a per-message native leak); the conversion logic lives in the **shared tier** (`EncodingCodec`/`zidString` in zenoh-flat-jni, correspondence-tested against native, reusable by zenoh-kotlin) with only a thin facade here; publisher **default encoding set natively at declare** (plain puts cross no encoding data). Pairs with [zenoh-flat-jni#4](https://github.com/ZettaScaleLabs/zenoh-flat-jni/pull/4), [zenoh-flat#3](https://github.com/ZettaScaleLabs/zenoh-flat/pull/3) (merged), [prebindgen#80](https://github.com/milyin/prebindgen/pull/80) (merged) | in progress | +| [#491](https://github.com/eclipse-zenoh/zenoh-java/pull/491) | `Session.undeclare` detaches the key-expr handle even when the native undeclare fails (the generated wrapper consumes it either way); found on the zenoh-kotlin port ([zenoh-kotlin#668](https://github.com/eclipse-zenoh/zenoh-kotlin/pull/668)) | open | Companion PRs in the upstream repos are coordinated per constituent PR (e.g. [ZettaScaleLabs/zenoh-flat-jni#3](https://github.com/ZettaScaleLabs/zenoh-flat-jni/pull/3) diff --git a/zenoh-java/src/commonMain/kotlin/io/zenoh/Session.kt b/zenoh-java/src/commonMain/kotlin/io/zenoh/Session.kt index 1101ece5..b28291c2 100644 --- a/zenoh-java/src/commonMain/kotlin/io/zenoh/Session.kt +++ b/zenoh-java/src/commonMain/kotlin/io/zenoh/Session.kt @@ -421,10 +421,15 @@ class Session private constructor(private val config: Config) : AutoCloseable { if (handle == null || handle.isClosed()) { throw ZError("Attempting to undeclare a non declared key expression.") } - run { + try { zSession.undeclareKeyexpr(handle, throwZError) + } finally { + // The generated wrapper consumes the handle even when the native + // undeclare fails (the Rust side takes it by value) — detach it + // either way, degrading the KeyExpr to its string form instead of + // leaving a dead handle to be selected by later operations. + keyExpr.handle = null } - keyExpr.handle = null } /** diff --git a/zenoh-java/src/jvmTest/kotlin/io/zenoh/KeyExprHandleTest.kt b/zenoh-java/src/jvmTest/kotlin/io/zenoh/KeyExprHandleTest.kt index afae4ab3..e9a3c2e0 100644 --- a/zenoh-java/src/jvmTest/kotlin/io/zenoh/KeyExprHandleTest.kt +++ b/zenoh-java/src/jvmTest/kotlin/io/zenoh/KeyExprHandleTest.kt @@ -15,6 +15,7 @@ package io.zenoh import io.zenoh.bytes.ZBytes +import io.zenoh.exceptions.ZError import io.zenoh.keyexpr.KeyExpr import io.zenoh.sample.Sample import org.junit.Assert.assertEquals @@ -62,6 +63,30 @@ class KeyExprHandleTest { session.close() } + @Test + fun failedUndeclarationStillDetachesTheConsumedHandle() { + // Undeclaring through the WRONG session makes the native undeclare + // fail — and the generated wrapper consumes the handle even then. The + // KeyExpr must degrade to its string form (handle detached), not keep + // selecting a dead handle. + val session1 = Zenoh.open(Config.loadDefault()) + val session2 = Zenoh.open(Config.loadDefault()) + val declared = session1.declareKeyExpr("example/testing/keyexpr/wrongsession") + var failed = false + try { + session2.undeclare(declared) + } catch (e: ZError) { + failed = true + } + assertTrue(failed) + assertNull(declared.handle) + // String-backed operation keeps working after the failed undeclare. + assertTrue(declared.intersects(KeyExpr.tryFrom("example/testing/**"))) + session1.put(declared, ZBytes.from("test")) + session1.close() + session2.close() + } + @Test fun receivedKeyExprIsStringBackedAndReusable() { val session = Zenoh.open(Config.loadDefault()) From 146d75dca0424db00b7f29a8feedf72143529840 Mon Sep 17 00:00:00 2001 From: Michael Ilyin Date: Sat, 18 Jul 2026 12:28:58 +0200 Subject: [PATCH 9/9] Parameters becomes a thin facade over the shared string-backed implementation (#492) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Parameters becomes a thin facade over the shared string-backed implementation The per-SDK map-backed parser duplicated zenoh-kotlin's and diverged from Rust: it percent-decoded values, rejected duplicated keys (throwing on attacker-controlled selector parameters in the queryable upcall), and normalized eagerly. io.zenoh.query.Parameters now delegates every operation to the shared io.zenoh.jni.query.Parameters (zenoh-flat-jni), a pure-Kotlin string-backed mirror of Rust's zenoh-protocol parameters.rs — construction is infallible on any input with zero JNI crossings. Behavior changes, all aligning with Rust: no percent-decoding; from(String) never throws; duplicated keys accepted with first-match-wins get (last wins in toMap, as Rust's HashMap conversion); trailing ';'/'='/'|' trimmed at construction; toString round-trips the stored string; equality is string equality. New ParametersCorrespondenceTest validates the shared implementation against the native parameters_get/insert/is_well_formed oracle (zenoh-flat#4) over edge shapes and 500 randomized inputs — it caught the trailing-separator trim rule. remove is correspondence-exempt: upstream zenoh's parameters::remove has an iterator-consumption bug (find advances past entries preceding the first match before filter runs, dropping them; removing an absent key erases everything); the shared implementation follows the documented 'preserving the insertion order' contract instead, and nativeRemoveBugCanary pins the buggy native behavior so the exemption is removed when upstream fixes it. Also folds in the RX hardening that motivated the unification: queryCallbackOf frees the owned native leaves and finalizes the query if decomposition ever throws, and QueryParametersTest sends a=1;a=2;bad=%zz through the raw bindings end to end. CI pins bump to zenoh-flat#4 and zenoh-flat-jni#9. Co-Authored-By: Claude Opus 4.8 * Extend parameters correspondence to the full API; reframe the adaptation zenoh-flat's parameters functions are regular API (values, contains_key and extend now declared too); the JVM runs the same semantics in pure Kotlin because crossing JNI per string operation is expensive — a JNI peculiarity, not a zenoh-flat design choice. Correspondence now covers get/values/ containsKey/insert/extend/isWellFormed over the edge shapes and 500 randomized inputs; comments and test names updated to say 'native implementation' (remove stays exempt behind nativeRemoveBugCanary until eclipse-zenoh/zenoh#2687 lands). CI pins bump to the reworked zenoh-flat and zenoh-flat-jni heads. Co-Authored-By: Claude Opus 4.8 * ci: pin zenoh-flat to merged main (parameters API, zenoh-flat#4) Co-Authored-By: Claude Opus 4.8 --------- Co-authored-by: Claude Opus 4.8 --- .github/workflows/ci.yml | 4 +- ZENOH_FLAT_TRANSITION.md | 3 +- .../kotlin/io/zenoh/FlatCallbacks.kt | 16 +- .../kotlin/io/zenoh/query/Parameters.kt | 81 ++++----- .../commonMain/kotlin/io/zenoh/query/Query.kt | 3 + .../kotlin/io/zenoh/query/Selector.kt | 14 +- .../io/zenoh/EncodingCorrespondenceTest.kt | 6 +- .../io/zenoh/ParametersCorrespondenceTest.kt | 167 ++++++++++++++++++ .../kotlin/io/zenoh/QueryParametersTest.kt | 106 +++++++++++ 9 files changed, 339 insertions(+), 61 deletions(-) create mode 100644 zenoh-java/src/jvmTest/kotlin/io/zenoh/ParametersCorrespondenceTest.kt create mode 100644 zenoh-java/src/jvmTest/kotlin/io/zenoh/QueryParametersTest.kt diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b20aac79..ef4e60f8 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -31,14 +31,14 @@ jobs: uses: actions/checkout@v4 with: repository: ZettaScaleLabs/zenoh-flat-jni - ref: dbb1f8c6509e333f49d1c03ecb84dcf07b7af7b8 + ref: 757cc6ad7e346f428ffc5e3252ba01ff4d19ba9b path: zenoh-flat-jni - name: Check out zenoh-flat uses: actions/checkout@v4 with: repository: ZettaScaleLabs/zenoh-flat - ref: 6d22091d75912be1c14330b0f78fa1d4bf258ea3 + ref: 1ece3df5e64db63f980a43e8ae661bdd5ed760c2 path: zenoh-flat - name: Use prebindgen from GitHub main diff --git a/ZENOH_FLAT_TRANSITION.md b/ZENOH_FLAT_TRANSITION.md index f3b79d31..ba9fa709 100644 --- a/ZENOH_FLAT_TRANSITION.md +++ b/ZENOH_FLAT_TRANSITION.md @@ -27,7 +27,8 @@ zenoh (Rust) | --- | --- | --- | | [#481](https://github.com/eclipse-zenoh/zenoh-java/pull/481) | Use receiver-style zenoh-flat-jni bindings (generated Session/Query/Publisher methods, split key-expr overloads, de-prefixed callback names); +61–83% subscriber throughput | merged | | [#484](https://github.com/eclipse-zenoh/zenoh-java/pull/484) | `Encoding`/`ZenohId.toString` become pure JVM values (fixes a per-message native leak); the conversion logic lives in the **shared tier** (`EncodingCodec`/`zidString` in zenoh-flat-jni, correspondence-tested against native, reusable by zenoh-kotlin) with only a thin facade here; publisher **default encoding set natively at declare** (plain puts cross no encoding data). Pairs with [zenoh-flat-jni#4](https://github.com/ZettaScaleLabs/zenoh-flat-jni/pull/4), [zenoh-flat#3](https://github.com/ZettaScaleLabs/zenoh-flat/pull/3) (merged), [prebindgen#80](https://github.com/milyin/prebindgen/pull/80) (merged) | in progress | -| [#491](https://github.com/eclipse-zenoh/zenoh-java/pull/491) | `Session.undeclare` detaches the key-expr handle even when the native undeclare fails (the generated wrapper consumes it either way); found on the zenoh-kotlin port ([zenoh-kotlin#668](https://github.com/eclipse-zenoh/zenoh-kotlin/pull/668)) | open | +| [#491](https://github.com/eclipse-zenoh/zenoh-java/pull/491) | `Session.undeclare` detaches the key-expr handle even when the native undeclare fails (the generated wrapper consumes it either way); found on the zenoh-kotlin port ([zenoh-kotlin#668](https://github.com/eclipse-zenoh/zenoh-kotlin/pull/668)) | merged | +| shared-parameters | `Parameters` becomes a thin facade over the shared string-backed implementation in zenoh-flat-jni (Rust `parameters.rs` semantics: no percent-decoding, infallible parse, first-match-wins get) + `ParametersCorrespondenceTest` vs the native oracle. Pairs with [zenoh-flat#4](https://github.com/ZettaScaleLabs/zenoh-flat/pull/4), [zenoh-flat-jni#9](https://github.com/ZettaScaleLabs/zenoh-flat-jni/pull/9) | open | Companion PRs in the upstream repos are coordinated per constituent PR (e.g. [ZettaScaleLabs/zenoh-flat-jni#3](https://github.com/ZettaScaleLabs/zenoh-flat-jni/pull/3) diff --git a/zenoh-java/src/commonMain/kotlin/io/zenoh/FlatCallbacks.kt b/zenoh-java/src/commonMain/kotlin/io/zenoh/FlatCallbacks.kt index 20dbae09..aec2274a 100644 --- a/zenoh-java/src/commonMain/kotlin/io/zenoh/FlatCallbacks.kt +++ b/zenoh-java/src/commonMain/kotlin/io/zenoh/FlatCallbacks.kt @@ -54,7 +54,21 @@ internal fun queryCallbackOf( // 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. - f(Query.fromParts(keStr, parameters, payloadH, encId, encSchema, attachH, acceptsReplies, zq)) + 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( diff --git a/zenoh-java/src/commonMain/kotlin/io/zenoh/query/Parameters.kt b/zenoh-java/src/commonMain/kotlin/io/zenoh/query/Parameters.kt index 241ade35..aa27be94 100644 --- a/zenoh-java/src/commonMain/kotlin/io/zenoh/query/Parameters.kt +++ b/zenoh-java/src/commonMain/kotlin/io/zenoh/query/Parameters.kt @@ -14,90 +14,74 @@ 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. */ @JvmStatic - fun empty() = Parameters(mutableMapOf()) + fun empty() = Parameters(JniParameters.empty()) /** * Creates a [Parameters] instance from the provided map. */ @JvmStatic - 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. */ @JvmStatic - fun from(params: String): Parameters { - if (params.isBlank()) { - return Parameters(mutableMapOf()) - } - return 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): Parameters = 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. @@ -108,25 +92,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) } /** @@ -134,13 +118,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-java/src/commonMain/kotlin/io/zenoh/query/Query.kt b/zenoh-java/src/commonMain/kotlin/io/zenoh/query/Query.kt index 283aacb4..14132c2e 100644 --- a/zenoh-java/src/commonMain/kotlin/io/zenoh/query/Query.kt +++ b/zenoh-java/src/commonMain/kotlin/io/zenoh/query/Query.kt @@ -73,6 +73,9 @@ class Query internal constructor( zq: io.zenoh.jni.query.Query, ): 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)) return Query( diff --git a/zenoh-java/src/commonMain/kotlin/io/zenoh/query/Selector.kt b/zenoh-java/src/commonMain/kotlin/io/zenoh/query/Selector.kt index 34e5cd4f..9dd549ca 100644 --- a/zenoh-java/src/commonMain/kotlin/io/zenoh/query/Selector.kt +++ b/zenoh-java/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 An instance [Selector]. diff --git a/zenoh-java/src/jvmTest/kotlin/io/zenoh/EncodingCorrespondenceTest.kt b/zenoh-java/src/jvmTest/kotlin/io/zenoh/EncodingCorrespondenceTest.kt index dd61a10c..143002f3 100644 --- a/zenoh-java/src/jvmTest/kotlin/io/zenoh/EncodingCorrespondenceTest.kt +++ b/zenoh-java/src/jvmTest/kotlin/io/zenoh/EncodingCorrespondenceTest.kt @@ -26,13 +26,13 @@ import org.junit.Test * The SDK implements the encoding string ↔ `(id, schema)` conversion in pure * Kotlin (no JNI crossing), on the contract that any JVM-side reimplementation * of zenoh-flat API must be verified against the native implementation. These - * tests drive both implementations — the pure one and the native oracle (the + * tests drive both implementations — the pure one and the native one (the * generated `Encoding` handle methods) — over the whole predefined id range * plus the edge shapes of the parse/render rules, asserting equal results. */ class EncodingCorrespondenceTest { - /** Native oracle: the canonical string of `(id, schema)`. */ + /** Native implementation: the canonical string of `(id, schema)`. */ private fun nativeRender(id: Int, schema: String?): String { val h = JniEncoding.newFromId(id, schema, throwZError0) try { @@ -42,7 +42,7 @@ class EncodingCorrespondenceTest { } } - /** Native oracle: parse a string into `(id, schema, canonical string)`. */ + /** Native implementation: parse a string into `(id, schema, canonical string)`. */ private fun nativeParse(s: String): Triple { val h = JniEncoding.newFromString(s, throwZError0) try { diff --git a/zenoh-java/src/jvmTest/kotlin/io/zenoh/ParametersCorrespondenceTest.kt b/zenoh-java/src/jvmTest/kotlin/io/zenoh/ParametersCorrespondenceTest.kt new file mode 100644 index 00000000..b56bfcf7 --- /dev/null +++ b/zenoh-java/src/jvmTest/kotlin/io/zenoh/ParametersCorrespondenceTest.kt @@ -0,0 +1,167 @@ +// +// 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.exceptions.throwZError0 +import io.zenoh.jni.query.Parameters as JniParameters +import io.zenoh.jni.query.parametersContainsKey +import io.zenoh.jni.query.parametersExtend +import io.zenoh.jni.query.parametersGet +import io.zenoh.jni.query.parametersInsert +import io.zenoh.jni.query.parametersIsWellFormed +import io.zenoh.jni.query.parametersRemove +import io.zenoh.jni.query.parametersValues +import org.junit.Assert.assertEquals +import org.junit.Test +import kotlin.random.Random + +/** + * Correspondence tests for the shared pure-JVM + * [io.zenoh.jni.query.Parameters] implementation. + * + * zenoh-flat exposes parameters processing as regular API (the `parameters*` + * functions in `io.zenoh.jni.query`, thin wrappers over + * `zenoh::query::Parameters`); the JVM production path runs the same + * semantics in pure Kotlin instead, because crossing JNI per string + * operation is expensive — a JNI peculiarity, not a zenoh-flat design + * choice. On the contract that any pure reimplementation of native + * semantics must be verified against the native implementation, these tests + * drive both over edge shapes and randomized inputs, asserting equal + * results. + */ +class ParametersCorrespondenceTest { + + /** Edge shapes of the format rules. */ + private val edgeCases = listOf( + "", + ";", + ";;", + "a", + "a=", + "=v", + "=", + "a=1", + "a=1;b=2", + "a=1;a=2", + "a=b=c", + "a==", + "k=%zz", + "k=%20", + "flag;a=1", + ";;a=1;;b=2;;", + "c=1|2|3", + "c=|", + "c=ified|", + "ключ=значение", + "a=1;=2;b=3", + " a = 1 ; b = 2 ", + "a;a;a", + "a=1;b;a=2", + ) + + private val keys = listOf("a", "b", "c", "k", "flag", "", "missing", "ключ", " a ") + + private fun assertCorrespondence(s: String) { + val pure = JniParameters.fromString(s) + for (k in keys) { + assertEquals( + "get(\"$k\") diverges for input \"$s\"", + parametersGet(s, k, throwZError0), + pure.get(k), + ) + assertEquals( + "values(\"$k\") diverges for input \"$s\"", + parametersValues(s, k, throwZError0), + pure.values(k), + ) + assertEquals( + "containsKey(\"$k\") diverges for input \"$s\"", + parametersContainsKey(s, k, throwZError0), + pure.containsKey(k), + ) + } + assertEquals( + "isWellFormed diverges for input \"$s\"", + parametersIsWellFormed(s, throwZError0), + pure.isWellFormed(), + ) + for (k in keys) { + assertEquals( + "insert(\"$k\", \"val\") diverges for input \"$s\"", + parametersInsert(s, k, "val", throwZError0), + JniParameters.fromString(s).also { it.insert(k, "val") }.asString(), + ) + } + assertEquals( + "extend diverges for input \"$s\"", + parametersExtend(s, "zk1=zv1;zk2", throwZError0), + JniParameters.fromString(s) + .also { it.extend(JniParameters.fromString("zk1=zv1;zk2")) } + .asString(), + ) + // `remove` is deliberately NOT compared against the native + // implementation: see [nativeRemoveBugCanary]. + } + + @Test + fun edgeCasesMatchNative() { + for (s in edgeCases) { + assertCorrespondence(s) + } + } + + @Test + fun randomizedInputsMatchNative() { + // Random strings over an alphabet dense in separators, so structural + // collisions (duplicate keys, empty chunks, '=' in values) are common. + val alphabet = "ab;=|%; =;" + val rng = Random(20260718) + repeat(500) { + val s = buildString { + repeat(rng.nextInt(0, 24)) { append(alphabet[rng.nextInt(alphabet.length)]) } + } + assertCorrespondence(s) + } + } + + /** The shared implementation follows Rust's DOCUMENTED remove contract + * ("preserving the insertion order"). */ + @Test + fun removeFollowsTheDocumentedContract() { + fun removed(s: String, k: String): String = + JniParameters.fromString(s).also { it.remove(k) }.asString() + + assertEquals("b=2;c=3", removed("b=2;a=1;c=3", "a")) + assertEquals("b=2", removed("a=1;b=2;a=3", "a")) + assertEquals("x=1;y=2", removed("x=1;y=2", "missing")) + assertEquals("", removed("a=1", "a")) + assertEquals("a=1", removed("flag;a=1", "flag")) + assertEquals("1", JniParameters.fromString("b=2;a=1").remove("a")) + } + + /** CANARY: upstream zenoh's `parameters::remove` has an + * iterator-consumption bug — `find` advances past every entry up to and + * including the first match before `filter` builds the result, so + * entries PRECEDING the match are dropped, and removing an absent key + * erases the whole string. The shared implementation intentionally + * diverges (see [removeFollowsTheDocumentedContract]). This test pins + * the buggy native behavior: when upstream fixes it, this test fails — + * then delete it and fold `remove` into [assertCorrespondence]. */ + @Test + fun nativeRemoveBugCanary() { + assertEquals("c=3", parametersRemove("b=2;a=1;c=3", "a", throwZError0)) + assertEquals("", parametersRemove("x=1;y=2", "missing", throwZError0)) + } +} diff --git a/zenoh-java/src/jvmTest/kotlin/io/zenoh/QueryParametersTest.kt b/zenoh-java/src/jvmTest/kotlin/io/zenoh/QueryParametersTest.kt new file mode 100644 index 00000000..ffd3b180 --- /dev/null +++ b/zenoh-java/src/jvmTest/kotlin/io/zenoh/QueryParametersTest.kt @@ -0,0 +1,106 @@ +// +// 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.exceptions.throwZError +import io.zenoh.keyexpr.KeyExpr +import io.zenoh.query.Parameters +import io.zenoh.query.Query +import io.zenoh.query.Reply +import org.junit.Assert.assertEquals +import org.junit.Assert.assertNotNull +import org.junit.Assert.assertTrue +import org.junit.Test + +/** + * The receive path must be as safe against malicious external data as the + * Rust layer: there a selector's parameters are an unvalidated string view — + * any string is valid and forwarded untouched — so [Parameters] accepts + * anything, with Rust semantics. + */ +class QueryParametersTest { + + @Test + fun parsingAcceptsAnyRemoteInputWithRustSemantics() { + // Duplicated parameter name: accepted, the FIRST occurrence wins on get. + assertEquals("1", Parameters.from("a=1;a=2").get("a")) + + // No percent-decoding: the value is kept verbatim. + assertEquals("%zz", Parameters.from("k=%zz").get("k")) + assertEquals("%20", Parameters.from("k=%20").get("k")) + + // Value containing '=': split on the FIRST '=' only. + assertEquals("b=c", Parameters.from("a=b=c").get("a")) + + // Flag without a value, empty chunks, blank input: all accepted. + assertEquals("", Parameters.from("flag").get("flag")) + assertEquals("1", Parameters.from(";;a=1;;").get("a")) + assertTrue(Parameters.from("").isEmpty()) + + // The string round-trips verbatim (duplicates preserved) until an + // insert/remove normalizes it. + val p = Parameters.from("a=1;a=2;flag") + assertEquals("a=1;a=2;flag", p.toString()) + p.insert("a", "3") + assertEquals("flag;a=3", p.toString()) + } + + @Test + fun queryableReceivesMalformedParametersQuery() { + // A remote (non-JVM) client is free to send selector parameters that + // a URL-style parser would reject — the Rust layer forwards any + // string untouched. The queryable must still receive the query and + // be able to reply. + val session = Zenoh.open(Config.loadDefault()) + val keyExpr = KeyExpr.tryFrom("example/testing/malformed/params") + var received: Query? = null + val queryable = session.declareQueryable(keyExpr, callback = { query -> + received = query + query.reply(keyExpr, "ok") + }) + + var reply: Reply? = null + // Bypass the JVM-side Selector validation, as a remote client would. + session.zSession!!.get( + keyExpr.toString(), // s + "a=1;a=2;bad=%zz", // parameters + 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 + throwZError, + ) + Thread.sleep(1000) + + assertNotNull(received) + assertEquals("1", received!!.parameters?.get("a")) + assertEquals("%zz", received!!.parameters?.get("bad")) + assertNotNull(reply) + + queryable.close() + session.close() + } +}