zenoh-flat transition (integration branch) - #669
Conversation
…n-zenoh-jni-runtime
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…t wiring - Remove :zenoh-jni from settings.gradle.kts, add gated composite build for zenoh-java submodule - Remove rust-android-gradle plugin from root build.gradle.kts - Rewrite zenoh-kotlin/build.gradle.kts: add zenoh-jni-runtime dependency, add jvmAndAndroidMain source set, remove all Cargo/NDK task wiring Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Kotlin reflection (typeOf<T>()) is not available on Kotlin/Native or Kotlin/JS. Moving these functions to jvmAndAndroidMain mirrors the intentional design in zenoh-jni-runtime where JNIZBytesKotlin is also in jvmAndAndroidMain. Update call sites to use runtime's JNIZBytesKotlin instead of the deleted local JNIZBytes adapter. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…classes Delete all JNI adapter files (except JNILiveliness.kt which will be adapted to use runtime's public JNISession methods): - All callbacks in jni/callbacks/ - JNISession, JNIPublisher, JNISubscriber, JNIQuerier, etc. - JNIZBytes (replaced by runtime's JNIZBytesKotlin) Delete duplicate classes that conflict with zenoh-jni-runtime: - exceptions/ZError.kt (runtime provides io.zenoh.exceptions.ZError) - jvmMain/Target.kt (runtime provides io.zenoh.Target) - jvmMain/Zenoh.kt and androidMain/Zenoh.kt (actual ZenohLoad impls, runtime provides io.zenoh.ZenohLoad) - commonMain/Zenoh.kt expect ZenohLoad declaration will be removed next Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…t to runtime API
- Zenoh.kt: remove expect ZenohLoad declaration; adapt scout methods to use
JNIScout.scout(Int, JNIScoutCallback, JNIOnCloseCallback, JNIConfig?)
- Config.kt: loadDefault/loadFromFile/loadFromJson/loadFromYaml replace old
methods; wrap runtime calls in runCatching where needed
- KeyExpr.kt: tryFrom/autocanonize now return String from runtime, wrap in
runCatching { KeyExpr(...) }; intersects/includes/relationTo/join/concat
updated to pass JNIKeyExpr? primitives explicitly
- ZenohId.kt: toStringViaJNI → toString (runtime method)
- Logger.kt: replace private external fun with JNILogger.startLogs(filter)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
All JNI functionality is now provided by zenoh-jni-runtime from the zenoh-java submodule. The local Rust crate is no longer needed. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The examples no longer need to build a local Rust JNI library since zenoh-jni-runtime is now the JNI provider via Maven dependency. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- ci.yml: add submodules: recursive checkout, remove cargo fmt/clippy/build steps - publish-jvm.yml: remove 6-platform cross-compilation matrix, simplify to single publish job that depends on zenoh-jni-runtime via Maven - publish-android.yml: remove NDK setup, Rust cross-compilation for Android ABIs Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Fix ntpValue property access to ntpValue() function call in Query.kt - Reorder ReplyKeyExpr enum so MATCHING_QUERY=0, ANY=1 to match Rust mapping - Fix Session.undeclare(KeyExpr) to null out jniKeyExpr after undeclaring and return failure when already undeclared (prevents double-free SIGABRT) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The zenoh-jni Rust crate was deleted; update the release automation script to stop editing the now-nonexistent Cargo.toml and update README to reflect that zenoh-kotlin now depends on zenoh-jni-runtime rather than building its own native JNI library. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ion property
- Gate includeBuild("zenoh-java") behind zenoh.useLocalJniRuntime property so
publication resolves against the published Maven artifact, not a local submodule build
- Add zenohJniRuntimeVersion in gradle.properties to independently track the
zenoh-jni-runtime release version, decoupling it from zenoh-kotlin's own version.txt
- Remove submodule checkout and Rust toolchain from publish-jvm and publish-android
workflows; publish path no longer needs them
- Pass -Pzenoh.useLocalJniRuntime=true in ci.yml test step so CI still builds against
the local submodule, and remove now-unnecessary Rust toolchain install step
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Clarify that the default build/test path resolves zenoh-jni-runtime from Maven and requires no Rust toolchain, while the opt-in local submodule path (-Pzenoh.useLocalJniRuntime=true) builds from source and does require one. Fixes the documentation to match the actual opt-in build wiring. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…or params - Query now stores acceptReplies from the JNI callback instead of inferring from selector parameters - resolveQueryable passes ReplyKeyExpr.entries[acceptReplies] to Query constructor - Selector param parsing now uses getOrThrow() so malformed data surfaces as a failure Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
zenoh-kotlin no longer contains any Rust crates (zenoh-jni/ directory was removed as part of the migration to zenoh-jni-runtime). The publish-github job that invoked publish-crates-github@main is now obsolete and would fail at release time. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Update to latest common-jni commit with markdownlint fix. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…oh-kotlin-depend-on-zenoh-jni-runtime Zbobr fix 72 make zenoh kotlin depend on zenoh jni runtime
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* Port zenoh-kotlin to zenoh-flat-jni generated bindings Replace the external zenoh-jni-runtime dependency (zenoh-java common-jni, git submodule + conditional composite build) with the generated zenoh-flat-jni bindings, consumed via an unconditional Gradle composite build against a sibling checkout — the architecture proven by zenoh-java's zenoh-flat-transition branch. The flat bindings never throw: every fallible generated wrapper takes a trailing error sink and returns onError.run(...) on failure. Since zenoh-kotlin's public API is Result-based, errors become Result.failure directly inside the sink (exceptions/ResultHandlers.kt zCall* helpers, born-closed sentinel handles on the error path) — zero try/catch and zero runCatching on the JNI path. Value models follow zenoh-java #484-#489: Encoding stays a pure JVM value with the (id, schema) selector block; KeyExpr is string-backed except declared handles; received ZBytes materialize lazily; ZenohId renders via the shared-tier zidString codec; callbacks arrive value-decomposed in one JNI crossing (FlatCallbacks.kt + fromParts factories). Query.reply* now closes the native query (leak fix), Config.fromJson5 actually parses JSON5 (bugfix), CongestionControl.BLOCK_FIRST added for receive-path totality. Advanced pub/sub is not yet exposed by zenoh-flat/zenoh-flat-jni: public signatures are kept but Session.declareAdvanced* return Result.failure and AdvancedPubSubTest is disabled. zSerialize/zDeserialize bridge KType to java.lang.reflect.Type (top-level primitives boxed); the Kotlin-specific unsigned/Pair/Triple types are disabled pending a KType-aware serializer in the shared tier. CI mirrors zenoh-java's: sibling checkouts of zenoh-flat-jni@dbb1f8c and zenoh-flat@6d22091 (same pins), prebindgen from git main, cargo build, then gradle jvmTest. 113 tests, 0 failed, 12 skipped by design. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * Querier: invalid-querier error is a Result.failure, not a throw performGet returns Result<R>, but an invalid (undeclared) querier threw an unchecked ZError — a pre-existing inconsistency carried over from the old runtime-based code. Align it with the Result-based API: Publisher and Session already report their closed-handle state as Result.failure. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * Preserve the Result contract for JVM-side exceptions The zCall* helpers captured only native errors (reported via the sink) and let exceptions thrown by the block itself escape from Result-returning functions — unlike main, whose runCatching also converted argument preparation, user IntoZBytes.into() conversions, and native-library loading failures into Result.failure. Concretely, scout(whatAmI = emptySet()) threw from reduce despite returning Result. Run each helper block inside runCatching (the sink capture takes precedence and is still exception-free for native errors), move scout's whatAmI reduction inside the captured block, make the zDeserialize cast mapCatching, rebuild KeyExpr.fromProbe on zCall, and add a regression test for the empty-whatAmI case. ZENOH_FLAT_TRANSITION.md now states the two-channel contract precisely. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * 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), and on a pre-call guard failure the handle instead stays live. Clearing jniKeyExpr only on success left a dead handle attached: every later operation selected the closed handle and failed instead of degrading to the string form. Close (no-op if consumed) and detach unconditionally; regression-tested by undeclaring through the wrong session. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * RX path: never fault on attacker-controlled selector parameters In the Rust layer a selector's parameters are an unvalidated string view — any string is valid and forwarded untouched. The Kotlin queryable path instead parsed them with the strict Parameters.from, whose failures (duplicated names, invalid percent-encoding — both trivially producible by any remote client) threw inside the JNI upcall: the exception is swallowed by the callback bridge, but the query's owned payload/attachment buffers (no GC backstop) leaked and the query was silently dropped — a remotely repeatable leak, strictly weaker than the Rust layer. Parse leniently on receive (Parameters.fromLenient: first duplicate wins, split on the first '=', undecodable values kept verbatim), and add defense-in-depth to queryCallbackOf: if decomposition ever throws, free the owned native leaves and finalize the query before rethrowing. E2E-tested by sending 'a=1;a=2;bad=%zz' through the raw bindings, as a remote non-Kotlin client would. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
…entation (#670) * Parameters becomes a thin facade over the shared string-backed implementation The per-SDK map-backed parser (strict from + fromLenient) duplicated zenoh-java's and diverged from Rust: it percent-decoded values, rejected duplicated keys, 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 (remote, attacker-controlled) input with zero JNI crossings. Behavior changes, all aligning with Rust: - values are no longer percent-decoded (the URL-encoding claim was a zenoh-jni legacy; docs updated), - from(String) never fails (the Result signature is kept for source compatibility and is always success); duplicated keys are accepted with first-match-wins get, - toString round-trips the stored string verbatim; insert/remove normalize, - equality is string equality. fromLenient is deleted (superseded — the shared parse is inherently total). CI pins bump to zenoh-flat#4 (native parameters oracle) and zenoh-flat-jni#9 (shared implementation + oracle declarations). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * Bump zenoh-flat-jni pin: trailing-separator trim in shared Parameters Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * Bump pins: parameters API reworked as regular zenoh-flat API Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * ci: pin zenoh-flat to merged main (parameters API, zenoh-flat#4) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
* Migrate to the split error-handler API (zenoh-flat-jni #45) prebindgen split the generated JNI error callback into two channels: the binding `JniErrorHandler.run(je)` (unchanged) and the typed domain `ErrorHandler.run(message)` (`je` removed, called only on a domain `Err`). A fallible flat wrapper now takes both — `onBindingError` then `onError`. `ResultHandlers.kt`: - `zCall` / `zCallUnit` blocks change from `(ErrorHandler<T>) -> T` to `(JniErrorHandler<T>, ErrorHandler<T>) -> T`; the helper supplies both handlers (each records into the shared `err` local; both produce the sentinel where the return type demands one). - `throwZError` becomes the 1-arg domain handler (`ErrorHandler { message -> throw ZError(message) }`). - `zCall0` / `zCallUnit0` / `throwZError0` (binding-only) are unchanged. Call sites: every `zCall`/`zCallUnit` block threads both handlers into its flat wrapper call across Config/Session/Zenoh/KeyExpr/Liveliness/Publisher/ Querier/Query; `KeyExpr.fromProbe` gains the binding param and its callers (`join`/`concat`/`tryFrom`/`autocanonize`/`withHandle`) thread both. The two direct `throwZError` passes (`KeyExpr.withHandle`, a `commonTest` reply) gain `throwZError0`. Binding-only calls (`zCall0`/`throwZError0`/`newClone`/algebra ops) are untouched. Builds against the local composite zenoh-flat-jni (split-error-handler); 117 jvmTest tests pass (12 skipped), including the Result error paths. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * ci: repoint zenoh-flat-jni pin to the split regen + ownership marker The split error-handler regeneration (zenoh-flat-jni #10) plus the merged Kotlin ownership-marker fix (zenoh-flat-jni #11, 249fe9e on shared-parameters). CI pinned the pre-split 757cc6a, whose single-channel `ErrorHandler.run(je, message)` wrappers are incompatible with this branch's two-caller `zCall`/`zCallUnit` sites; #11's marker is also required for the composite `cargo build` to regenerate at all (write_kotlin refuses a non-empty output root without it). Point CI at the merged commit so the composite build both matches the source and regenerates. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
* Enable unsigned + Pair/Triple serialization via the KType path zenoh-flat-jni now exposes a KType-aware serializer. Switch the zSerialize/zDeserialize bridge from the erased `.javaType` (`serializeViaJNI`/`deserializeViaJNI`) to passing the full `KType` (`serializeViaJNIKType`/`deserializeViaJNIKType`), so `UByte`/`UShort`/ `UInt`/`ULong`/`Pair`/`Triple` work. Delete the `javaBoxedType()` bridge (the KType is passed directly now) and the transition TODOs; update the supported-types KDoc. Un-`@Ignore` the 9 ZBytesTest cases (unsigned/Pair/Triple/nested). Full jvmTest: 117 pass (advanced pub/sub still the only skipped suite). Depends on the zenoh-flat-jni KType-serializer commit. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * ci: bump zenoh-flat-jni pin to the KType-serializer commit The unsigned/Pair/Triple serialization enabled here needs zenoh-flat-jni's new KType externs (serializeViaJNIKType/deserializeViaJNIKType, ZettaScaleLabs/ zenoh-flat-jni#12, 9503f73). Point CI at that commit so the composite build has the KType serializer. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * Serialize via the pure-Kotlin SerializationCodec (no JNI) Replace the per-element JNI serialization (serializeViaJNIKType) with the shared pure-Kotlin SerializationCodec. `serdeTypeOf(KType)` (jvmAndAndroidMain, kotlin-reflect) builds a SerializationCodec.SerdeType from the KType classifier — recognizing the unsigned value classes and Pair/Triple — and zSerialize/ zDeserialize call the codec through the SAME zCall0 error-sink wiring used for generated wrappers (the codec takes a JniErrorHandler and never throws), so the hand-written serializer is indistinguishable from a generated one at the call site. Adds SerializationCorrespondenceTest (Parameters-style): the pure output is asserted byte-identical to the native serializeViaJNIKType oracle across scalars, unsigned, strings/bytes, containers, tuples and nested types, plus RFC golden vectors, plus a perf comparison. Measured pure-Kotlin speedup on small payloads: ~156x (Int), ~320x (List<Int>(4)), ~279x (Map<String,Int>(2)). Full jvmTest passes. Depends on the zenoh-flat-jni SerializationCodec commit. The native KType serializer (serializeViaJNIKType) is kept temporarily as the correspondence oracle. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * ci: bump zenoh-flat-jni pin to the pure-Kotlin serializer commit The pure-Kotlin SerializationCodec this branch delegates to lives in zenoh-flat-jni#13 (db4fb2d). Point CI at it so the composite build has the shared codec. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * ci: repoint zenoh-flat-jni pin to the rebased serializer commit The zenoh-flat-jni serialization PR was rebased onto main (conflict resolution), changing the SerializationCodec commit SHA. Repoint CI at the current commit. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * Drop SerializationCorrespondenceTest moved down to zenoh-flat-jni The serialization correspondence test (pure SerializationCodec vs the native serializer oracle) moved into zenoh-flat-jni's own test suite. The native serializer externs relocated to the internal io.zenoh.jni.test package; SDK production uses the pure io.zenoh.jni.bytes.SerializationCodec, unchanged. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * ci: bump zenoh-flat-jni pin to the test-package/self-verify commit zenoh-flat-jni moved its native oracle to the internal io.zenoh.jni.test package and added self-verifying correspondence tests. Point CI at that commit. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * ci: bump zenoh-flat-jni pin to merged main (71736f2) zenoh-flat-jni #13 squash-merged to main as 71736f2. Re-pin from the pre-merge branch commit 2755c06 (an orphan once the pure-kotlin-serde branch is deleted) to the permanent main commit, which also carries the final merged SerializationCodec (strict UTF-8). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
zenoh-kotlin had no committed Gradle wrapper (gradlew/gradle-wrapper.jar
were gitignored), so CI installed gradle via setup-gradle's gradle-version
pin and the publish workflows generated a throwaway wrapper with
`gradle wrapper` before calling ./gradlew. Commit the wrapper (copied
verbatim from zenoh-java, Gradle 8.12.1) and drop those workarounds:
- .gitignore: stop ignoring gradle/, gradlew, gradlew.bat (keep .gradle cache).
- Add gradlew, gradlew.bat, gradle/wrapper/{gradle-wrapper.jar,.properties}.
- Workflows: drop `gradle-version: 8.12.1` (wrapper supplies it) and the
`gradle wrapper` generation steps; invoke ./gradlew everywhere (ci.yml and
publish-dokka switched from bare `gradle`). Matches zenoh-java's CI.
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
…s listeners (#678) * Enable advanced publisher + matching listener (Round 1) Un-stub AdvancedPublisher and MatchingListener over the generated io.zenoh.jni.pubsub bindings from zenoh-flat-jni's advanced-pubsub branch. - Session.declareAdvancedPublisher now calls resolveAdvancedPublisher, lowering the pure-Kotlin config holders (MissDetectionConfig heartbeat mode, CacheConfig maxSamples, publisherDetection) to the generated scalar params. - AdvancedPublisher: put/delete/getMatchingStatus and matching listeners (callback/handler/channel + background variants) over the never-throw sink (zCall/zCallUnit), mirroring the regular Publisher. - MatchingListener wraps the generated handle (null for background listeners). - AdvancedPublisherTest (Round 1): advanced publisher + matching listener vs a regular subscriber on a single loopback session — put/delete round-trip, matching status, encoding fallback. 4 tests pass. The advanced subscriber / sample-miss / detect-publishers stubs and the full AdvancedPubSubTest remain for Round 2. Requires zenoh-flat + zenoh-flat-jni advanced-pubsub branches. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * Enable advanced subscriber + sample-miss (Round 2) Un-stub the subscriber side of advanced pub/sub over the generated io.zenoh.jni.pubsub bindings from zenoh-flat-jni (advanced pub/sub, #14), and re-align the Round 1 publisher lowering to the current typed data-class holders. - Session: declareAdvancedSubscriber (callback/handler/channel) now wires a new private resolveAdvancedSubscriber, mirroring resolveSubscriber (zCall + sampleCallbackOf + strong-ref registration). HistoryConfig / RecoveryConfig / subscriberDetection lower to the generated HistoryConfig / RecoveryConfig holders (queryTimeout is not surfaced). resolveAdvancedPublisher is re-aligned to build the generated MissDetectionConfig / CacheConfig(RepliesConfig) holders — #14 replaced the old scalar params — which also restores CacheConfig.repliesQoS (Round 1 dropped it). Removed the advancedUnsupported stub error. - AdvancedSubscriber: carries the JNI handle; the 12 declared methods fold into two private resolvers (detect-publishers → Subscriber, sample-miss → SampleMissListener), foreground/background as in Round 1's matching listener. - SampleMiss is modernized to `SampleMiss(source: EntityGlobalId, missedCount)` (was four raw Longs), matching how Sample.sourceId already exposes the identity; FlatCallbacks gains a sampleMissCallbackOf bridge (generated ZenohId → SDK EntityGlobalId). SampleMissListener mirrors MatchingListener. - AdvancedPubSubTest: removed @ignore; initialize receivedSamples before the subscriber that appends to it; sleep for publisher/subscriber-detection propagation. - CI: pin zenoh-flat-jni -> d57837a (main, #14) and zenoh-flat -> b6b0ecf (main, #5 advanced pub/sub). Verified against zenoh-flat-jni main + zenoh-flat main (composite build, Gradle 8.12.1): AdvancedPubSubTest 3/3 + AdvancedPublisherTest 4/4 pass; full jvmTest 121/121 (no regressions). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * Rename SampleMiss data type to Miss (follow zenoh naming) zenoh-flat renamed its sample-miss report struct to `Miss` (zenoh's own name) carrying an `EntityGlobalId` source. Track that in the SDK data type. - pubsub/SampleMiss.kt -> pubsub/Miss.kt: `data class Miss(source: EntityGlobalId, nb: Long)` (field missedCount -> nb, matching zenoh_ext::Miss). - FlatCallbacks.sampleMissCallbackOf now returns the generated `MissCallback` and reads the nested `miss.source.zid`/`miss.source.eid`. - The SampleMiss* handler/callback surface keeps its names (SampleMissListener is zenoh's own name); only the payload type changes to `Miss`. The rename stays a data-type change: declareSampleMissListener and the listener class are unchanged. Compiles; AdvancedPubSubTest 3/3 + AdvancedPublisherTest 4/4 + full jvmTest 121/121 pass. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
zenoh-flat renamed part of its surface and moved several handle types to
value forms (ZettaScaleLabs/zenoh-flat-jni#16). Mostly mechanical, with one
change that reaches the public API. Mirrors the same realignment in
zenoh-java.
## A reply timestamp needs a real originating-node id
`Query.reply(timestamp = ...)` took an Apache commons-net `TimeStamp`: an
NTP64 instant and nothing else. Zenoh's timestamp is the pair `(ntp64, id)`,
and it orders and de-duplicates on the pair — so the missing half was supplied
by the JNI layer, which built one with a **random** id:
Timestamp::new(NTP64(timestamp_ntp_64 as u64), ID::rand()) // zenoh-jni/src/query.rs:82
The time looked right while the id was untethered from any node. zenoh-flat
removed that (ZettaScaleLabs/zenoh-flat#47) and `query_reply_success` now takes
a whole `Timestamp`, so there is no longer anywhere for a fabricated id to
come from — which is the point.
`io.zenoh.time.Timestamp` is that pair, mirroring `zenoh::time::Timestamp`.
The reply/replyDel `timestamp` parameters and `Sample.timestamp` carry it
instead of the commons-net type. **This is source-breaking**: a caller now
writes
query.reply(keyExpr, payload, timestamp = Timestamp.ofNtp64(ntp64, zid))
`ofNtp64` exists because the primary constructor takes `ULong`, which Java
cannot express; it takes the same 64 bits `TimeStamp(long)` does, so
`TimeStamp.getCurrentTime().ntpValue()` still feeds it. The id is the
replying session's — the reply does originate here — and `Sample.timestamp`
now surfaces the sender's id, which was previously discarded on receive.
The library no longer depends on commons-net; the tests and the ZQueryable
example still use it, as an NTP64 clock.
## Mechanical
* `keyexpr_get_str` -> `asStr`, `zbytes_as_bytes` -> `toBytes`.
* `session_get` takes a whole `Selector`, so the folded (key_expr, parameters)
pair is gone. A Selector holds an owned key-expr handle with no string arm,
so `KeyExpr.intoJniHandle()` materializes one for a string-backed key
expression — the slot trio cannot express this case.
* An encoding schema is raw bytes: zenoh transmits it verbatim and does not
require UTF-8. This SDK's `Encoding` carries a String, so it is encoded at
the boundary on send and decoded lossily on receive, rather than throwing on
a received message.
* A sample's source and a reply's replier arrive as whole `SourceInfo` /
`EntityGlobalId` values, so optionality lives on the value rather than on a
leading zid leaf.
* `RecoveryMode` is a data-carrying enum, so the choice and its payload are one
value rather than a `(period, flag)` pair in which only one of the two was
ever meaningful. `RecoveryConfig.retentionPeriod` has no counterpart on the
SDK config and stays absent, as it effectively was before.
* `config_new_from_json` is gone as an invented constructor (base zenoh has no
`from_json`). `Config.fromJson` parses via JSON5, of which JSON is a subset,
so every input accepted before still parses to the same config.
CI pins move to zenoh-flat 3f431b6b and zenoh-flat-jni 5e0ae509.
121 JVM tests pass; examples compile.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Review of the branch as of
|
…695) * Release preparation: repair the release path and document publishing zenoh-kotlin no longer contains Rust, but its release path still assumed it did, and its dependency on zenoh-flat-jni was unreleasable as written. - Resolve zenoh-flat-jni from Maven Central by default, at a version held in gradle.properties, instead of hardcoding 1.9.0 and always substituting a sibling checkout. The composite build becomes opt-in (-PuseLocalFlatJni=true); CI passes it explicitly, a release must not. - Declare the Central snapshot repository conditionally, so a rehearsal can build against a zenoh-flat-jni snapshot while a release provably cannot. - Merge publish-jvm.yml and publish-android.yml into one publish.yml: both coordinates now come from a single Gradle invocation, so they share one staging repository and are released together or not at all. - Teach bump-and-tag.bash the zenoh-flat-jni version, refusing a snapshot on a live run; drop the BUMP_DEPS_* inputs, dead since the Cargo crate was removed. - Add PUBLISHING.md, and correct the README where it described the composite build as unconditional. * Correct the JVM coordinate in README.md The publications set artifactId explicitly, so the JVM one is zenoh-kotlin, not the zenoh-kotlin-jvm that plain Kotlin Multiplatform naming would give. Every JVM copy/paste snippet named the latter, which does not exist.
|
@copilot resolve the merge conflicts in this pull request |
|
@copilot resolve the merge conflicts in this pull request |
Co-authored-by: milyin <1909657+milyin@users.noreply.github.com>
Resolved by merging
Merge conflicts are resolved. I merged |
- `com.google.guava:guava` is dead weight in `commonMain`. Nothing in this repo references `TypeToken`, and since #675 serialization runs through the pure-Kotlin `SerializationCodec`. The only `TypeToken` user is zenoh-flat-jni's own `io.zenoh.jni.test.Serialization`, which declares guava in its `jvmTest` set — so it was never ours to declare. Removed; `jvmTest` stays at 122 green. - The `jvmAndAndroidMain` rationale said the source set "carries kotlin-reflect", but the same change removed that dependency. `typeOf<T>()` and `KClass.qualifiedName` resolve through `kotlin.jvm.internal.Reflection` in the JVM stdlib — which is a real JVM/Android-only constraint, just not the stated one. Corrected in both the build script and KTypeSerde's KDoc. - `ZENOH_FLAT_TRANSITION.md` still pointed at the `ZettaScaleLabs` forks (README and settings.gradle.kts were already corrected in #695), listed constituent PRs only up to #668, described the superseded KType-over-JNI serializer as the approach taken, and listed advanced pub/sub as planned. Table synced with the branch, serializer note rewritten, and the remaining-work list is now what is actually left. Also: `prebindgen` resolves from crates.io since #692, and the error model has had two sink channels since #673.
|
The five loose ends from the review above are addressed in #696 (which also
Verified with |
The file was always meant to be deleted when this branch merges, and keeping it current in the meantime costs a refresh on every constituent PR — the previous commit is proof. Nothing in the repository links to it; the umbrella PR #669 carries the same architecture, error-model and constituent-PR descriptions in its body, which is where they stay accurate for free.
|
Correction to item 2 above: |
* Review fixes: ZBytes publication race, ReplyKeyExpr order, build script Three defects found reviewing #669, plus one stale comment. - `ZBytes.bytes` is double-checked locking over a non-volatile field. A received ZBytes is materialized lazily under a lock but read outside it, and it routinely crosses threads (a sample piped through a Channel), so a reader could see the array reference published before its copied contents. `eager` becomes `@Volatile`; it moves out of the constructor parameter list because Kotlin only allows the annotation on a body property. - `ReplyKeyExpr` had its constants swapped so that MATCHING_QUERY is declared first. The wire values live in `value`, so the declaration order buys nothing — and reordering an enum silently changes `ordinal()` and the order of `values()`/`entries` for every consumer. Restored, with a test that pins it. - Two lines in `build.gradle.kts` lost their indentation when the rust-android-gradle entries next to them were deleted. - AdvancedPublisherTest's KDoc claimed AdvancedPubSubTest "remains @ignore'd"; it is not ignored and it passes. Replaced with what the file actually adds: the same advanced publisher against a *plain* subscriber. Verified with `./gradlew jvmTest -PuseLocalFlatJni=true` against the zenoh-flat-jni / zenoh-flat commits CI pins: 122 tests, all green. * Address the #669 review: drop dead guava, refresh the transition doc - `com.google.guava:guava` is dead weight in `commonMain`. Nothing in this repo references `TypeToken`, and since #675 serialization runs through the pure-Kotlin `SerializationCodec`. The only `TypeToken` user is zenoh-flat-jni's own `io.zenoh.jni.test.Serialization`, which declares guava in its `jvmTest` set — so it was never ours to declare. Removed; `jvmTest` stays at 122 green. - The `jvmAndAndroidMain` rationale said the source set "carries kotlin-reflect", but the same change removed that dependency. `typeOf<T>()` and `KClass.qualifiedName` resolve through `kotlin.jvm.internal.Reflection` in the JVM stdlib — which is a real JVM/Android-only constraint, just not the stated one. Corrected in both the build script and KTypeSerde's KDoc. - `ZENOH_FLAT_TRANSITION.md` still pointed at the `ZettaScaleLabs` forks (README and settings.gradle.kts were already corrected in #695), listed constituent PRs only up to #668, described the superseded KType-over-JNI serializer as the approach taken, and listed advanced pub/sub as planned. Table synced with the branch, serializer note rewritten, and the remaining-work list is now what is actually left. Also: `prebindgen` resolves from crates.io since #692, and the error model has had two sink channels since #673. * Remove ZENOH_FLAT_TRANSITION.md The file was always meant to be deleted when this branch merges, and keeping it current in the meantime costs a refresh on every constituent PR — the previous commit is proof. Nothing in the repository links to it; the umbrella PR #669 carries the same architecture, error-model and constituent-PR descriptions in its body, which is where they stay accurate for free.
zenoh-flat carried a timestamp's node id trimmed to its significant bytes while `ZenohId` carries the full zero-padded width, so a received sample's `timestamp.id` compared unequal to the session zid that stamped it whenever the identifier had a high-order zero byte - about once in 256 sessions, both sides rendering identically. That is the flaky `QueryableTest.queryable_runsWithCallback` failure; the visible difference in its message, the encoding, is cosmetic (`Encoding.equals` ignores the description). Fixed in eclipse-zenoh/zenoh-flat#86 and picked up by eclipse-zenoh/zenoh-flat-jni#35, which this pins. Drop the zenoh-flat checkout: nothing reads it, since zenoh-flat-jni resolves zenoh-flat from git and its Cargo.lock is the rev that decides - a pin that pinned nothing while reading as if it did.
Both SDKs are being rebuilt on the generated zenoh-flat-jni bindings (eclipse-zenoh/zenoh-java#482, eclipse-zenoh/zenoh-kotlin#669). On those branches the in-repo `zenoh-jni` crate is gone, and with it rust-toolchain.toml and every Cargo manifest - the SDKs carry no Rust at all. Once that lands on main, their legs of this matrix fail at the toolchain step, on a rust-toolchain.toml that no longer exists. They are not losing the ABI alignment the sync exists for, they are inheriting it one level down: zenoh-flat and zenoh-flat-jni, added above, reach zenoh for them. Their departure also takes the crate-path workaround with it. It existed solely because those two kept their manifest under zenoh-jni/; every remaining dependant has it at the toplevel, so the path is just `.`.
…699) * CI: let Gradle drive the zenoh-flat-jni native build The workflow added rustfmt and clippy to a hardcoded 1.93.0 toolchain and ran `cargo build` in the zenoh-flat-jni checkout. Neither is needed here: zenoh-flat-jni pins its own toolchain in rust-toolchain.toml, and the composite build's test task already depends on its native build, so Gradle drives cargo. Verified by deleting the built dylib and running `jvmTest`, which rebuilt it and passed. Naming a toolchain version this repo does not own is also how the same step broke in zenoh-java: the components landed on 1.93.0 while the checks ran on the pinned 1.97.1. `rustup show`, run from the zenoh-flat-jni directory, installs whatever that repo pins. * CI: pin zenoh-flat-jni to the merged commit #698 pinned the PR branch commit, which is reachable but not on main. eclipse-zenoh/zenoh-flat-jni#35 has since merged as e75529c with an identical tree, so this only makes the pin name a commit that main actually carries. * CI: track zenoh-flat-jni main instead of a pinned commit A pinned SHA has to be hand-edited for every upstream fix, and that hop - zenoh-flat-jni to this SDK - is the one no bot covers. The timestamp node-id flake is what that costs: the fix sat in zenoh-flat for a day while CI kept testing the commit the pin named. Below zenoh-flat-jni the chain is automatic (eclipse-zenoh/ci#465 puts it and zenoh-flat on the lockfile sync), so following its default branch makes the whole chain automatic. The trade is deliberate: a run is no longer reproducible from this repository's commit alone, and a broken zenoh-flat-jni main breaks CI here - which, for a branch whose entire purpose is to track those bindings, is the signal we want.
Umbrella PR for rebuilding zenoh-kotlin on top of the generated JNI/Kotlin bindings
(
zenoh-flat-jni, produced by prebindgen fromzenoh-flat). It removes the embedded
zenoh-jniRust crate and the hand-writtenio.zenoh.jniadapter layer that sat ontop of it. This is the zenoh-kotlin counterpart of eclipse-zenoh/zenoh-java#482 — both
SDKs converge on the same shared bindings tier:
The branch also carries the earlier
external-jniseries (#651), which had alreadyreplaced the in-repo Rust crate with the
zenoh-jni-runtimeartifact; that interimdependency is gone too.
The
zenoh-jnicrate (25 files, ~9.1k lines) and everyJNI*.ktadapter are deleted;what remains in
zenoh-kotlin/srcis the publicResult-based SDK facade plus threesmall adapter files (
FlatCallbacks.kt,FlatEnums.kt,ResultHandlers.kt).Error model
The generated bindings never throw across JNI: a fallible wrapper takes trailing
error-sink callbacks and returns the sink's value on failure. zenoh-kotlin's public
API is
Result-based, so the failure is recorded directly inside the sink — the SDKhas zero
try/catchand zerorunCatchingaround native errors on the JNI path.ResultHandlers.ktfolds two channels into oneResult:onBindingError(
JniErrorHandler— UTF-8 decode, closed handle, …) andonError(the typed domainErrorHandler, carrying the decomposed zenoh message). Call sites usezCall/zCallUnit(both channels) orzCall0/zCallUnit0(binding-only). Theenclosing
runCatchingexists solely for JVM-side throws — argument preparation,user
IntoZBytes.into()conversions, native-library loading during class init — sothe public
Resultcontract of the pre-flat API is preserved exactly.Callbacks are value-decomposed: a
Sample,Query,HelloorReplyarrives asits leaves in one JNI crossing, with no per-field accessor calls.
Serialization
zSerialize/zDeserializeno longer cross JNI at all. They build aSerializationCodec.SerdeTypefrom the fullKType(KTypeSerde.kt) and run theshared pure-Kotlin codec in zenoh-flat-jni, which is byte-identical to the native
serializer (correspondence tested upstream against the native oracle). Measured
speedup on small payloads: ~156× (
Int), ~320× (List<Int>), ~279×(
Map<String,Int>).UByte/UShort/UInt/ULong/Pair/Tripleare supported.Public API changes
Source-breaking, all tracking zenoh/zenoh-flat semantics:
Timestamp—Sample.timestampandQuery.reply(timestamp = …)carryio.zenoh.time.Timestamp(the(ntp64, id)pair, mirroringzenoh::time::Timestamp)instead of commons-net's
TimeStamp. The old JNI layer fabricated the missing id withID::rand(); a reply now carries the replying session's real id, and a receivedsample surfaces the sender's, which was previously discarded. Use
Timestamp.ofNtp64(ntp64, zid). commons-net drops out of the library's dependencies(tests and the
ZQueryableexample still use it as an NTP64 clock).Parameters— a thin facade over the shared string-backed implementation(Rust
parameters.rssemantics): values are no longer percent-decoded,fromneverfails (the
Resultsignature is kept for source compatibility), duplicate keys areaccepted with first-match-wins
get, andtoStringround-trips verbatim.Config.fromJsonparses via JSON5 (of which JSON is a subset — every inputaccepted before parses to the same config); the invented
config_new_from_jsonentry point is gone.
SampleMiss→Missas the payload type (Miss(source: EntityGlobalId, nb: Long)),following zenoh's own naming. The
SampleMissListenersurface is unchanged.SourceInfo/EntityGlobalIdvalues, so optionality lives on the value rather than on a leadingzid leaf.
KeyExpris the only one holding a native handle; every other keyexpression is a plain validated string with nothing to close.
Build & CI
cargoBuildwiring in this repo; thenative library ships with
org.eclipse.zenoh:zenoh-flat-jni.gradle-versionpin andthe throwaway wrapper the publish workflows used to generate.
zenoh-flat-jni/zenoh-flatat pinned commits and buildsthrough the Gradle composite build;
prebindgenresolves from crates.io. Pins arebumped as the upstream PRs land.
Constituent PRs
external-jni: drop the in-repozenoh-jnicrate for an external runtime artifactParametersbecomes a facade over the shared string-backed implementationSerializationCodec(no JNI)AdvancedPublisher/AdvancedSubscriber, matching + sample-miss listeners5977bb1Process
This PR stays draft while the transition is in progress. Individual changes land as
reviewable PRs targeting the
zenoh-flat-transitionbranch; this PR accumulatesthem and merges to
mainas a whole. Upstream companion PRs (prebindgen → zenoh-flat →zenoh-flat-jni) merge first; the zenoh-kotlin PR then pins the exact upstream commits in
CI and merges once green. This description is the single record of the architecture
and the constituent-PR list.
🤖 Generated with Claude Code