diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 39e3832b..dd30cfe1 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -55,11 +55,9 @@ jobs:
- name: Setup Gradle
uses: gradle/actions/setup-gradle@v4
- with:
- gradle-version: 8.12.1
- name: Gradle Test
- run: gradle jvmTest --info
+ run: ./gradlew jvmTest --info
markdown_lint:
runs-on: ubuntu-latest
diff --git a/.github/workflows/publish-android.yml b/.github/workflows/publish-android.yml
index 77f9f611..380e5444 100644
--- a/.github/workflows/publish-android.yml
+++ b/.github/workflows/publish-android.yml
@@ -58,12 +58,6 @@ jobs:
- name: Setup Gradle
uses: gradle/actions/setup-gradle@v4
- with:
- gradle-version: 8.12.1
-
- - name: Gradle Wrapper
- run: |
- gradle wrapper
- name: Set pub mode env var
# Note: This step is intended to allow publishing snapshot packages.
diff --git a/.github/workflows/publish-dokka.yml b/.github/workflows/publish-dokka.yml
index b5fe106d..4964557b 100644
--- a/.github/workflows/publish-dokka.yml
+++ b/.github/workflows/publish-dokka.yml
@@ -27,15 +27,9 @@ jobs:
- name: Setup Gradle
uses: gradle/actions/setup-gradle@v4
- with:
- gradle-version: 8.12.1
-
- - name: Gradle Wrapper
- run: |
- gradle wrapper
- name: Build doc
- run: gradle dokkaGenerate
+ run: ./gradlew dokkaGenerate
- name: Deploy doc
if: ${{ inputs.live-run || false }}
diff --git a/.github/workflows/publish-jvm.yml b/.github/workflows/publish-jvm.yml
index 7caf1b48..ce0ba5e6 100644
--- a/.github/workflows/publish-jvm.yml
+++ b/.github/workflows/publish-jvm.yml
@@ -163,12 +163,6 @@ jobs:
- name: Setup Gradle
uses: gradle/actions/setup-gradle@v4
- with:
- gradle-version: 8.12.1
-
- - name: Gradle Wrapper
- run: |
- gradle wrapper
- name: Set pub mode env var
# Note: This step is intended to allow publishing snapshot packages.
diff --git a/.gitignore b/.gitignore
index 53d109f6..9784c16c 100644
--- a/.gitignore
+++ b/.gitignore
@@ -20,3 +20,6 @@
/build/
/.gradle/
*/build/
+
+# Generated Kotlin prototypes emitted by zenoh-jni's build.rs
+/zenoh-jni/generated-kotlin/
diff --git a/README.md b/README.md
index affbaa42..1c214a6d 100644
--- a/README.md
+++ b/README.md
@@ -114,12 +114,17 @@ Basically:
- Rust ([Installation guide](https://doc.rust-lang.org/cargo/getting-started/installation.html))
- Kotlin ([Installation guide](https://kotlinlang.org/docs/getting-started.html#backend))
-- Gradle ([Installation guide](https://gradle.org/install/))
and in case of targeting Android you'll also need:
- Android SDK ([Installation guide](https://developer.android.com/about/versions/11/setup-sdk))
+## Gradle wrapper
+
+This repository ships a [Gradle wrapper](https://docs.gradle.org/current/userguide/gradle_wrapper.html) (`./gradlew` / `gradlew.bat`), so no system-wide Gradle installation is required. The wrapper pins the build to **Gradle 8.12.1** and verifies the distribution checksum before use, ensuring a reproducible and tamper-evident build environment.
+
+Use `./gradlew` in place of `gradle` for all commands listed below.
+
##
JVM
To publish a library for a JVM project into Maven local, run
diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 00000000..a4b76b95
Binary files /dev/null and b/gradle/wrapper/gradle-wrapper.jar differ
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 00000000..63532c5e
--- /dev/null
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,8 @@
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-8.12.1-bin.zip
+distributionSha256Sum=8d97a97984f6cbd2b85fe4c60a743440a347544bf18818048e611f5288d46c94
+networkTimeout=10000
+validateDistributionUrl=true
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
diff --git a/gradlew b/gradlew
new file mode 100755
index 00000000..057afac5
--- /dev/null
+++ b/gradlew
@@ -0,0 +1,251 @@
+#!/bin/sh
+
+#
+# Copyright © 2015-2021 the original authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# SPDX-License-Identifier: Apache-2.0
+#
+
+##############################################################################
+#
+# Gradle start up script for POSIX generated by Gradle.
+#
+# Important for running:
+#
+# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
+# noncompliant, but you have some other compliant shell such as ksh or
+# bash, then to run this script, type that shell name before the whole
+# command line, like:
+#
+# ksh Gradle
+#
+# Busybox and similar reduced shells will NOT work, because this script
+# requires all of these POSIX shell features:
+# * functions;
+# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
+# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
+# * compound commands having a testable exit status, especially «case»;
+# * various built-in commands including «command», «set», and «ulimit».
+#
+# Important for patching:
+#
+# (2) This script targets any POSIX shell, so it avoids extensions provided
+# by Bash, Ksh, etc; in particular arrays are avoided.
+#
+# The "traditional" practice of packing multiple parameters into a
+# space-separated string is a well documented source of bugs and security
+# problems, so this is (mostly) avoided, by progressively accumulating
+# options in "$@", and eventually passing that to Java.
+#
+# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
+# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
+# see the in-line comments for details.
+#
+# There are tweaks for specific operating systems such as AIX, CygWin,
+# Darwin, MinGW, and NonStop.
+#
+# (3) This script is generated from the Groovy template
+# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
+# within the Gradle project.
+#
+# You can find Gradle at https://github.com/gradle/gradle/.
+#
+##############################################################################
+
+# Attempt to set APP_HOME
+
+# Resolve links: $0 may be a link
+app_path=$0
+
+# Need this for daisy-chained symlinks.
+while
+ APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
+ [ -h "$app_path" ]
+do
+ ls=$( ls -ld "$app_path" )
+ link=${ls#*' -> '}
+ case $link in #(
+ /*) app_path=$link ;; #(
+ *) app_path=$APP_HOME$link ;;
+ esac
+done
+
+# This is normally unused
+# shellcheck disable=SC2034
+APP_BASE_NAME=${0##*/}
+# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
+APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD=maximum
+
+warn () {
+ echo "$*"
+} >&2
+
+die () {
+ echo
+ echo "$*"
+ echo
+ exit 1
+} >&2
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+nonstop=false
+case "$( uname )" in #(
+ CYGWIN* ) cygwin=true ;; #(
+ Darwin* ) darwin=true ;; #(
+ MSYS* | MINGW* ) msys=true ;; #(
+ NONSTOP* ) nonstop=true ;;
+esac
+
+CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
+
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+ if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+ # IBM's JDK on AIX uses strange locations for the executables
+ JAVACMD=$JAVA_HOME/jre/sh/java
+ else
+ JAVACMD=$JAVA_HOME/bin/java
+ fi
+ if [ ! -x "$JAVACMD" ] ; then
+ die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+ fi
+else
+ JAVACMD=java
+ if ! command -v java >/dev/null 2>&1
+ then
+ die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+ fi
+fi
+
+# Increase the maximum file descriptors if we can.
+if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
+ case $MAX_FD in #(
+ max*)
+ # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
+ # shellcheck disable=SC2039,SC3045
+ MAX_FD=$( ulimit -H -n ) ||
+ warn "Could not query maximum file descriptor limit"
+ esac
+ case $MAX_FD in #(
+ '' | soft) :;; #(
+ *)
+ # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
+ # shellcheck disable=SC2039,SC3045
+ ulimit -n "$MAX_FD" ||
+ warn "Could not set maximum file descriptor limit to $MAX_FD"
+ esac
+fi
+
+# Collect all arguments for the java command, stacking in reverse order:
+# * args from the command line
+# * the main class name
+# * -classpath
+# * -D...appname settings
+# * --module-path (only if needed)
+# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
+
+# For Cygwin or MSYS, switch paths to Windows format before running java
+if "$cygwin" || "$msys" ; then
+ APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
+ CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
+
+ JAVACMD=$( cygpath --unix "$JAVACMD" )
+
+ # Now convert the arguments - kludge to limit ourselves to /bin/sh
+ for arg do
+ if
+ case $arg in #(
+ -*) false ;; # don't mess with options #(
+ /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
+ [ -e "$t" ] ;; #(
+ *) false ;;
+ esac
+ then
+ arg=$( cygpath --path --ignore --mixed "$arg" )
+ fi
+ # Roll the args list around exactly as many times as the number of
+ # args, so each arg winds up back in the position where it started, but
+ # possibly modified.
+ #
+ # NB: a `for` loop captures its iteration list before it begins, so
+ # changing the positional parameters here affects neither the number of
+ # iterations, nor the values presented in `arg`.
+ shift # remove old arg
+ set -- "$@" "$arg" # push replacement arg
+ done
+fi
+
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS='-Dfile.encoding=UTF-8 "-Xmx64m" "-Xms64m"'
+
+# Collect all arguments for the java command:
+# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
+# and any embedded shellness will be escaped.
+# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
+# treated as '${Hostname}' itself on the command line.
+
+set -- \
+ "-Dorg.gradle.appname=$APP_BASE_NAME" \
+ -classpath "$CLASSPATH" \
+ org.gradle.wrapper.GradleWrapperMain \
+ "$@"
+
+# Stop when "xargs" is not available.
+if ! command -v xargs >/dev/null 2>&1
+then
+ die "xargs is not available"
+fi
+
+# Use "xargs" to parse quoted args.
+#
+# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
+#
+# In Bash we could simply go:
+#
+# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
+# set -- "${ARGS[@]}" "$@"
+#
+# but POSIX shell has neither arrays nor command substitution, so instead we
+# post-process each arg (as a line of input to sed) to backslash-escape any
+# character that might be a shell metacharacter, then use eval to reverse
+# that process (while maintaining the separation between arguments), and wrap
+# the whole thing up as a single "set" statement.
+#
+# This will of course break if any of these variables contains a newline or
+# an unmatched quote.
+#
+
+eval "set -- $(
+ printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
+ xargs -n1 |
+ sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
+ tr '\n' ' '
+ )" '"$@"'
+
+exec "$JAVACMD" "$@"
diff --git a/gradlew.bat b/gradlew.bat
new file mode 100644
index 00000000..640d6868
--- /dev/null
+++ b/gradlew.bat
@@ -0,0 +1,94 @@
+@rem
+@rem Copyright 2015 the original author or authors.
+@rem
+@rem Licensed under the Apache License, Version 2.0 (the "License");
+@rem you may not use this file except in compliance with the License.
+@rem You may obtain a copy of the License at
+@rem
+@rem https://www.apache.org/licenses/LICENSE-2.0
+@rem
+@rem Unless required by applicable law or agreed to in writing, software
+@rem distributed under the License is distributed on an "AS IS" BASIS,
+@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+@rem See the License for the specific language governing permissions and
+@rem limitations under the License.
+@rem
+@rem SPDX-License-Identifier: Apache-2.0
+@rem
+
+@if "%DEBUG%"=="" @echo off
+@rem ##########################################################################
+@rem
+@rem Gradle startup script for Windows
+@rem
+@rem ##########################################################################
+
+@rem Set local scope for the variables with windows NT shell
+if "%OS%"=="Windows_NT" setlocal
+
+set DIRNAME=%~dp0
+if "%DIRNAME%"=="" set DIRNAME=.
+@rem This is normally unused
+set APP_BASE_NAME=%~n0
+set APP_HOME=%DIRNAME%
+
+@rem Resolve any "." and ".." in APP_HOME to make it shorter.
+for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
+
+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+set DEFAULT_JVM_OPTS=-Dfile.encoding=UTF-8 "-Xmx64m" "-Xms64m"
+
+@rem Find java.exe
+if defined JAVA_HOME goto findJavaFromJavaHome
+
+set JAVA_EXE=java.exe
+%JAVA_EXE% -version >NUL 2>&1
+if %ERRORLEVEL% equ 0 goto execute
+
+echo. 1>&2
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
+echo. 1>&2
+echo Please set the JAVA_HOME variable in your environment to match the 1>&2
+echo location of your Java installation. 1>&2
+
+goto fail
+
+:findJavaFromJavaHome
+set JAVA_HOME=%JAVA_HOME:"=%
+set JAVA_EXE=%JAVA_HOME%/bin/java.exe
+
+if exist "%JAVA_EXE%" goto execute
+
+echo. 1>&2
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
+echo. 1>&2
+echo Please set the JAVA_HOME variable in your environment to match the 1>&2
+echo location of your Java installation. 1>&2
+
+goto fail
+
+:execute
+@rem Setup the command line
+
+set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
+
+
+@rem Execute Gradle
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
+
+:end
+@rem End local scope for the variables with windows NT shell
+if %ERRORLEVEL% equ 0 goto mainEnd
+
+:fail
+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
+rem the _cmd.exe /c_ return code!
+set EXIT_CODE=%ERRORLEVEL%
+if %EXIT_CODE% equ 0 set EXIT_CODE=1
+if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
+exit /b %EXIT_CODE%
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega
diff --git a/prebindgen-ext/Cargo.lock b/prebindgen-ext/Cargo.lock
new file mode 100644
index 00000000..0821f335
--- /dev/null
+++ b/prebindgen-ext/Cargo.lock
@@ -0,0 +1,352 @@
+# This file is automatically @generated by Cargo.
+# It is not intended for manual editing.
+version = 4
+
+[[package]]
+name = "aho-corasick"
+version = "1.1.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301"
+dependencies = [
+ "memchr",
+]
+
+[[package]]
+name = "const_panic"
+version = "0.2.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e262cdaac42494e3ae34c43969f9cdeb7da178bdb4b66fa6a1ea2edb4c8ae652"
+dependencies = [
+ "typewit",
+]
+
+[[package]]
+name = "either"
+version = "1.15.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719"
+
+[[package]]
+name = "equivalent"
+version = "1.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
+
+[[package]]
+name = "hashbrown"
+version = "0.17.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4f467dd6dccf739c208452f8014c75c18bb8301b050ad1cfb27153803edb0f51"
+
+[[package]]
+name = "if_rust_version"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "46dbcb333e86939721589d25a3557e180b52778cb33c7fdfe9e0158ff790d5ec"
+
+[[package]]
+name = "indexmap"
+version = "2.14.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9"
+dependencies = [
+ "equivalent",
+ "hashbrown",
+]
+
+[[package]]
+name = "itertools"
+version = "0.12.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569"
+dependencies = [
+ "either",
+]
+
+[[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 = "konst"
+version = "0.3.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "97feab15b395d1860944abe6a8dd8ed9f8eadfae01750fada8427abda531d887"
+dependencies = [
+ "const_panic",
+ "konst_kernel",
+ "konst_proc_macros",
+ "typewit",
+]
+
+[[package]]
+name = "konst_kernel"
+version = "0.3.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e4b1eb7788f3824c629b1116a7a9060d6e898c358ebff59070093d51103dcc3c"
+dependencies = [
+ "typewit",
+]
+
+[[package]]
+name = "konst_proc_macros"
+version = "0.3.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "00af7901ba50898c9e545c24d5c580c96a982298134e8037d8978b6594782c07"
+
+[[package]]
+name = "memchr"
+version = "2.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79"
+
+[[package]]
+name = "prebindgen"
+version = "0.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4b005ec2d8a59f83ecc1b63ef25ab4d42fa33d9642e24d5d8dab5e750d5f6f46"
+dependencies = [
+ "if_rust_version",
+ "itertools 0.14.0",
+ "konst",
+ "prettyplease",
+ "proc-macro2",
+ "quote",
+ "regex",
+ "roxygen",
+ "serde",
+ "serde_json",
+ "syn",
+ "toml",
+]
+
+[[package]]
+name = "prebindgen-ext"
+version = "1.9.0"
+dependencies = [
+ "itertools 0.12.1",
+ "prebindgen",
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "prettyplease"
+version = "0.2.37"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b"
+dependencies = [
+ "proc-macro2",
+ "syn",
+]
+
+[[package]]
+name = "proc-macro2"
+version = "1.0.106"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934"
+dependencies = [
+ "unicode-ident",
+]
+
+[[package]]
+name = "quote"
+version = "1.0.45"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924"
+dependencies = [
+ "proc-macro2",
+]
+
+[[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 = "roxygen"
+version = "1.0.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fa650dd372f29f0a6be64b2896707f9536962ba28915e3b39bcafd5a6221873b"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[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_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 = "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 = "toml"
+version = "0.9.12+spec-1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cf92845e79fc2e2def6a5d828f0801e29a2f8acc037becc5ab08595c7d5e9863"
+dependencies = [
+ "indexmap",
+ "serde_core",
+ "serde_spanned",
+ "toml_datetime",
+ "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_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.2",
+]
+
+[[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 = "typewit"
+version = "1.15.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "214ca0b2191785cbc06209b9ca1861e048e39b5ba33574b3cedd58363d5bb5f6"
+dependencies = [
+ "typewit_proc_macros",
+]
+
+[[package]]
+name = "typewit_proc_macros"
+version = "1.8.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e36a83ea2b3c704935a01b4642946aadd445cea40b10935e3f8bd8052b8193d6"
+
+[[package]]
+name = "unicode-ident"
+version = "1.0.24"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75"
+
+[[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.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2ee1708bef14716a11bae175f579062d4554d95be2c6829f518df847b7b3fdd0"
+
+[[package]]
+name = "zmij"
+version = "1.0.21"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa"
diff --git a/prebindgen-ext/Cargo.toml b/prebindgen-ext/Cargo.toml
new file mode 100644
index 00000000..8af82cba
--- /dev/null
+++ b/prebindgen-ext/Cargo.toml
@@ -0,0 +1,28 @@
+#
+# 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]
+name = "prebindgen-ext"
+version = "1.9.0"
+edition = "2021"
+license = "EPL-2.0 OR Apache-2.0"
+description = "Prebindgen JNI extensions for Zenoh."
+repository = "https://github.com/eclipse-zenoh/zenoh"
+
+[dependencies]
+syn = { version = "2", features = ["full", "extra-traits"] }
+quote = "1"
+proc-macro2 = "1"
+prebindgen = "0.4.1"
+itertools = "0.12"
diff --git a/prebindgen-ext/src/jni_converter.rs b/prebindgen-ext/src/jni_converter.rs
new file mode 100644
index 00000000..e59e9cd4
--- /dev/null
+++ b/prebindgen-ext/src/jni_converter.rs
@@ -0,0 +1,859 @@
+//! JNI binding generators for items marked with `#[prebindgen]`.
+//!
+//! Two converters split the work along the natural axis:
+//!
+//! * [`JniStructConverter`] consumes `#[prebindgen]` `syn::ItemStruct`s,
+//! emits a JNI decoder + a Kotlin `data class`, and inserts an
+//! auto-generated [`TypeBinding`] into a shared [`JniTypeBinding`].
+//! * [`JniMethodsConverter`] consumes `#[prebindgen]` `syn::ItemFn`s,
+//! classifies each argument/return against the (now fully populated)
+//! [`JniTypeBinding`], and emits a `Java__ViaJNI` wrapper
+//! plus a matching Kotlin `external fun`.
+//!
+//! # Type registry
+//!
+//! Every Rust type-shape that appears in a `#[prebindgen]` function's
+//! signature must have an explicit row in the [`JniTypeBinding`] registry,
+//! keyed by the canonical `to_token_stream()` form of the type. There are
+//! no implicit fallbacks: missing row ⇒ panic with a clear "register ``"
+//! message.
+//!
+//! Built-in rows for `bool` and `Duration` are pre-registered by
+//! [`JniTypeBinding::with_builtins`] (applied automatically inside
+//! [`MethodsBuilder::default`]).
+
+use std::collections::{BTreeSet, VecDeque};
+use std::path::PathBuf;
+
+use proc_macro2::TokenStream;
+use quote::{format_ident, quote, ToTokens};
+
+use prebindgen::SourceLocation;
+
+use crate::jni_type_binding::{JniTypeBinding, ReturnEncode};
+pub use crate::jni_type_binding::{InlineFn, TypeBinding};
+
+// =====================================================================
+// JniStructConverter
+// =====================================================================
+
+/// Builder for [`JniStructConverter`].
+pub struct StructBuilder {
+ /// Module path used to fully-qualify the struct type in the generated
+ /// `decode_` function (e.g. `"zenoh_flat::ext"`).
+ source_module: syn::Path,
+ /// `ZResult` type used in the decoder return signature.
+ zresult: syn::Path,
+ /// Type registry that the struct converter mutates as it processes each
+ /// `#[prebindgen]` struct.
+ types: JniTypeBinding,
+}
+
+impl Default for StructBuilder {
+ fn default() -> Self {
+ Self {
+ source_module: syn::parse_str("crate").unwrap(),
+ zresult: syn::parse_str("ZResult").unwrap(),
+ types: JniTypeBinding::new().with_builtins(),
+ }
+ }
+}
+
+impl StructBuilder {
+ /// Module path that contains the `#[prebindgen]` struct types
+ /// (e.g. `"zenoh_flat::ext"`).
+ pub fn source_module(mut self, path: impl AsRef) -> Self {
+ self.source_module = syn::parse_str(path.as_ref()).expect("invalid source_module path");
+ self
+ }
+
+ /// Path of the `ZResult` type used in the decoder's return type.
+ pub fn zresult(mut self, path: impl AsRef) -> Self {
+ self.zresult = syn::parse_str(path.as_ref()).expect("invalid zresult path");
+ self
+ }
+
+ /// Register or replace a single [`TypeBinding`] in the type registry.
+ pub fn type_binding(mut self, binding: TypeBinding) -> Self {
+ self.types.types.insert(binding.name().to_string(), binding);
+ self
+ }
+
+ /// Merge a reusable [`JniTypeBinding`] into the type registry.
+ pub fn jni_type_binding(mut self, bindings: JniTypeBinding) -> Self {
+ self.types.types.extend(bindings.types);
+ self.types
+ .kotlin_data_classes
+ .extend(bindings.kotlin_data_classes);
+ self
+ }
+
+ pub fn build(self) -> JniStructConverter {
+ JniStructConverter {
+ cfg: self,
+ pending: VecDeque::new(),
+ buffered: false,
+ }
+ }
+}
+
+/// Converter that turns `#[prebindgen]`-marked Rust structs into JNI
+/// decoder functions and Kotlin `data class` strings, while populating a
+/// shared [`JniTypeBinding`] with one auto-generated entry per struct.
+pub struct JniStructConverter {
+ cfg: StructBuilder,
+ pending: VecDeque<(syn::Item, SourceLocation)>,
+ buffered: bool,
+}
+
+impl JniStructConverter {
+ pub fn builder() -> StructBuilder {
+ StructBuilder::default()
+ }
+
+ /// Drain `iter` on the first call, convert each struct item, and queue
+ /// the result for the next `pop` from this batching closure.
+ pub fn call(&mut self, iter: &mut I) -> Option<(syn::Item, SourceLocation)>
+ where
+ I: Iterator- ,
+ {
+ if !self.buffered {
+ self.buffered = true;
+ for (item, loc) in iter.by_ref() {
+ let converted = self.convert(item, &loc);
+ self.pending.push_back((converted, loc));
+ }
+ }
+ self.pending.pop_front()
+ }
+
+ /// Closure suitable for `itertools::batching`.
+ pub fn as_closure<'a, I>(
+ &'a mut self,
+ ) -> impl FnMut(&mut I) -> Option<(syn::Item, SourceLocation)> + 'a
+ where
+ I: Iterator
- ,
+ {
+ move |iter| self.call(iter)
+ }
+
+ /// Consume the converter and return the populated [`JniTypeBinding`].
+ pub fn into_jni_type_binding(self) -> JniTypeBinding {
+ self.cfg.types
+ }
+
+ fn convert(&mut self, item: syn::Item, loc: &SourceLocation) -> syn::Item {
+ match item {
+ syn::Item::Struct(s) => self.convert_struct(s, loc),
+ other => panic!(
+ "JniStructConverter received a non-struct item at {loc}: {}",
+ other.to_token_stream()
+ ),
+ }
+ }
+
+ /// Emit a JNI decoder for a `#[prebindgen]` struct and a matching Kotlin
+ /// `data class`, then auto-register a `TypeBinding` so the struct can
+ /// appear by value in a wrapped function's signature.
+ fn convert_struct(&mut self, s: syn::ItemStruct, loc: &SourceLocation) -> syn::Item {
+ let struct_name = s.ident.to_string();
+ let struct_ident = s.ident.clone();
+ let decoder_ident = format_ident!("decode_{}", struct_ident);
+ let zresult = self.cfg.zresult.clone();
+ let struct_module = self.cfg.source_module.clone();
+
+ let syn::Fields::Named(named) = &s.fields else {
+ panic!("tuple / unit structs are not supported at {loc}");
+ };
+
+ let mut field_preludes: Vec = Vec::new();
+ let mut field_init: Vec = Vec::new();
+ let mut kotlin_field_lines: Vec = Vec::new();
+
+ for field in &named.named {
+ let fname_ident = field
+ .ident
+ .as_ref()
+ .unwrap_or_else(|| panic!("unnamed field in struct `{struct_name}` at {loc}"))
+ .clone();
+ let fname = fname_ident.to_string();
+ let kotlin_fname = snake_to_camel(&fname);
+ let err_prefix = format!("{struct_name}.{kotlin_fname}: {{}}");
+
+ let binding = self.lookup_struct_field_binding(&field.ty).unwrap_or_else(|| {
+ panic!(
+ "unsupported field type `{}` for `{}.{}` at {loc}",
+ field.ty.to_token_stream(),
+ struct_name,
+ fname
+ )
+ });
+ let (jni_sig, jvalue_method) =
+ jni_primitive_signature(binding.jni_type()).unwrap_or_else(|| {
+ panic!(
+ "field `{}.{}` at {loc}: type `{}` has non-primitive JNI wire form `{}`",
+ struct_name,
+ fname,
+ field.ty.to_token_stream(),
+ binding.jni_type().to_token_stream()
+ )
+ });
+ let raw_ident = format_ident!("__{}_raw", fname_ident);
+ let jni_type = binding.jni_type();
+ let decode_expr = binding
+ .decode()
+ .expect("struct-field binding must have a decode")
+ .call(&raw_ident);
+ field_preludes.push(quote! {
+ let #raw_ident: #jni_type = env.get_field(obj, #kotlin_fname, #jni_sig)
+ .and_then(|v| v.#jvalue_method())
+ .map_err(|err| zerror!(#err_prefix, err))? as _;
+ let #fname_ident = #decode_expr;
+ });
+ field_init.push(quote! { #fname_ident });
+ kotlin_field_lines.push(format!(
+ " val {}: {},",
+ kotlin_fname,
+ binding.kotlin_type()
+ ));
+ }
+
+ let tokens = quote! {
+ #[allow(non_snake_case, unused_mut, unused_variables)]
+ pub(crate) fn #decoder_ident(
+ env: &mut jni::JNIEnv,
+ obj: &jni::objects::JObject,
+ ) -> #zresult<#struct_module::#struct_ident> {
+ #(#field_preludes)*
+ Ok(#struct_module::#struct_ident {
+ #(#field_init),*
+ })
+ }
+ };
+
+ let decoder_path = format!("decode_{struct_name}");
+ let row = TypeBinding::param(
+ &struct_name,
+ &struct_name,
+ "jni::objects::JObject",
+ InlineFn::env_ref_mut(&decoder_path),
+ );
+ self.cfg.types.types.insert(row.name().to_string(), row);
+
+ let block = format!(
+ "data class {}(\n{}\n)",
+ struct_name,
+ kotlin_field_lines.join("\n")
+ );
+ self.cfg.types.kotlin_data_classes.push(block);
+
+ syn::parse2(tokens).expect("generated struct decoder must parse")
+ }
+
+ /// Look up a `#[prebindgen]` struct field's type in the registry. Fields
+ /// must use the type's bare path-tail name (e.g. `bool`, `i64`,
+ /// `CongestionControl`) and must resolve to a registered binding whose
+ /// JNI wire form is one of the primitive `j*` types.
+ fn lookup_struct_field_binding(&self, ty: &syn::Type) -> Option<&TypeBinding> {
+ let syn::Type::Path(tp) = ty else { return None };
+ let last = tp.path.segments.last()?;
+ let name = last.ident.to_string();
+ self.cfg.types.types.get(&name)
+ }
+}
+
+// =====================================================================
+// JniMethodsConverter
+// =====================================================================
+
+/// Builder for [`JniMethodsConverter`].
+pub struct MethodsBuilder {
+ class_prefix: String,
+ function_suffix: String,
+ source_module: syn::Path,
+ zresult: syn::Path,
+ throw_exception: syn::Path,
+ types: JniTypeBinding,
+ kotlin: Option,
+}
+
+/// Settings for generating a companion Kotlin file with `external fun`
+/// prototypes. Enabled via [`MethodsBuilder::kotlin_output`].
+pub(crate) struct KotlinConfig {
+ output_path: PathBuf,
+ package: String,
+ class_name: String,
+ throws_class_fqn: Option,
+ init_load_fqn: Option,
+}
+
+impl Default for MethodsBuilder {
+ fn default() -> Self {
+ Self {
+ class_prefix: String::new(),
+ function_suffix: String::new(),
+ source_module: syn::parse_str("crate").unwrap(),
+ zresult: syn::parse_str("ZResult").unwrap(),
+ throw_exception: syn::parse_str("throw_exception").unwrap(),
+ types: JniTypeBinding::new().with_builtins(),
+ kotlin: None,
+ }
+ }
+}
+
+impl MethodsBuilder {
+ /// JNI class prefix prepended to each function name.
+ pub fn class_prefix(mut self, prefix: impl Into) -> Self {
+ self.class_prefix = prefix.into();
+ self
+ }
+
+ /// Suffix appended to the camel-case function name.
+ pub fn function_suffix(mut self, suffix: impl Into) -> Self {
+ self.function_suffix = suffix.into();
+ self
+ }
+
+ /// Fully-qualified path of the module that contains the original Rust
+ /// functions being wrapped.
+ pub fn source_module(mut self, path: impl AsRef) -> Self {
+ self.source_module = syn::parse_str(path.as_ref()).expect("invalid source_module path");
+ self
+ }
+
+ /// Path of the `ZResult` type used in the closure's return type.
+ pub fn zresult(mut self, path: impl AsRef) -> Self {
+ self.zresult = syn::parse_str(path.as_ref()).expect("invalid zresult path");
+ self
+ }
+
+ /// Path of the `throw_exception!` macro.
+ pub fn throw_exception(mut self, path: impl AsRef) -> Self {
+ self.throw_exception =
+ syn::parse_str(path.as_ref()).expect("invalid throw_exception path");
+ self
+ }
+
+ /// Register or replace a [`TypeBinding`] by name.
+ pub fn type_binding(mut self, binding: TypeBinding) -> Self {
+ self.types.types.insert(binding.name().to_string(), binding);
+ self
+ }
+
+ /// Merge a reusable [`JniTypeBinding`] into the type registry.
+ pub fn jni_type_binding(mut self, bindings: JniTypeBinding) -> Self {
+ self.types.types.extend(bindings.types);
+ self.types
+ .kotlin_data_classes
+ .extend(bindings.kotlin_data_classes);
+ self
+ }
+
+ /// Enable Kotlin-side prototype generation.
+ pub fn kotlin_output(mut self, path: impl Into) -> Self {
+ self.kotlin
+ .get_or_insert_with(KotlinConfig::default)
+ .output_path = path.into();
+ self
+ }
+
+ /// Kotlin `package` of the generated file.
+ pub fn kotlin_package(mut self, pkg: impl Into) -> Self {
+ self.kotlin.get_or_insert_with(KotlinConfig::default).package = pkg.into();
+ self
+ }
+
+ /// Name of the generated Kotlin `object`.
+ pub fn kotlin_class(mut self, name: impl Into) -> Self {
+ self.kotlin
+ .get_or_insert_with(KotlinConfig::default)
+ .class_name = name.into();
+ self
+ }
+
+ /// FQN of the exception type to annotate every `external fun` with via
+ /// `@Throws(::class)`.
+ pub fn kotlin_throws(mut self, fqn: impl Into) -> Self {
+ self.kotlin
+ .get_or_insert_with(KotlinConfig::default)
+ .throws_class_fqn = Some(fqn.into());
+ self
+ }
+
+ /// FQN of a singleton referenced from the generated `init { ... }` block.
+ pub fn kotlin_init(mut self, fqn: impl Into) -> Self {
+ self.kotlin
+ .get_or_insert_with(KotlinConfig::default)
+ .init_load_fqn = Some(fqn.into());
+ self
+ }
+
+ pub fn build(self) -> JniMethodsConverter {
+ JniMethodsConverter {
+ cfg: self,
+ pending: VecDeque::new(),
+ buffered: false,
+ kotlin_funs: Vec::new(),
+ kotlin_used_fqns: BTreeSet::new(),
+ }
+ }
+}
+
+impl Default for KotlinConfig {
+ fn default() -> Self {
+ Self {
+ output_path: PathBuf::new(),
+ package: String::new(),
+ class_name: String::new(),
+ throws_class_fqn: None,
+ init_load_fqn: None,
+ }
+ }
+}
+
+/// Converter that transforms `#[prebindgen]`-marked Rust functions into JNI
+/// `Java_*` wrappers and matching Kotlin `external fun` prototypes.
+pub struct JniMethodsConverter {
+ cfg: MethodsBuilder,
+ pending: VecDeque<(syn::Item, SourceLocation)>,
+ buffered: bool,
+ kotlin_funs: Vec,
+ kotlin_used_fqns: BTreeSet,
+}
+
+impl JniMethodsConverter {
+ pub fn builder() -> MethodsBuilder {
+ MethodsBuilder::default()
+ }
+
+ pub fn call(&mut self, iter: &mut I) -> Option<(syn::Item, SourceLocation)>
+ where
+ I: Iterator
- ,
+ {
+ if !self.buffered {
+ self.buffered = true;
+ for (item, loc) in iter.by_ref() {
+ let converted = self.convert(item, &loc);
+ self.pending.push_back((converted, loc));
+ }
+ }
+ self.pending.pop_front()
+ }
+
+ pub fn as_closure<'a, I>(
+ &'a mut self,
+ ) -> impl FnMut(&mut I) -> Option<(syn::Item, SourceLocation)> + 'a
+ where
+ I: Iterator
- ,
+ {
+ move |iter| self.call(iter)
+ }
+
+ /// Write the accumulated Kotlin file to the configured output path.
+ /// No-op when Kotlin output was not enabled.
+ pub fn write_kotlin(&self) -> std::io::Result<()> {
+ let Some(kt) = self.cfg.kotlin.as_ref() else {
+ return Ok(());
+ };
+ if kt.output_path.as_os_str().is_empty() {
+ return Ok(());
+ }
+ let contents = self.render_kotlin(kt);
+ if let Some(parent) = kt.output_path.parent() {
+ std::fs::create_dir_all(parent)?;
+ }
+ std::fs::write(&kt.output_path, contents)
+ }
+
+ fn render_kotlin(&self, kt: &KotlinConfig) -> String {
+ let mut used = self.kotlin_used_fqns.clone();
+ if let Some(fqn) = kt.init_load_fqn.as_ref() {
+ if fqn.contains('.') {
+ used.insert(fqn.clone());
+ }
+ }
+
+ let mut imports: Vec = used
+ .into_iter()
+ .filter(|fqn| {
+ let pkg = fqn.rsplit_once('.').map(|(p, _)| p).unwrap_or("");
+ !pkg.is_empty() && pkg != kt.package
+ })
+ .collect();
+ imports.sort();
+ imports.dedup();
+
+ let mut out = String::new();
+ out.push_str("// Auto-generated by JniConverter — do not edit by hand.\n");
+ if !kt.package.is_empty() {
+ out.push_str(&format!("package {}\n\n", kt.package));
+ }
+ for imp in &imports {
+ out.push_str(&format!("import {}\n", imp));
+ }
+ if !imports.is_empty() {
+ out.push('\n');
+ }
+ for block in &self.cfg.types.kotlin_data_classes {
+ out.push_str(block);
+ out.push_str("\n\n");
+ }
+ out.push_str(&format!("internal object {} {{\n", kt.class_name));
+ if let Some(fqn) = kt.init_load_fqn.as_ref() {
+ let short = fqn.rsplit('.').next().unwrap_or(fqn);
+ out.push_str(&format!(" init {{ {} }}\n\n", short));
+ }
+ for (i, block) in self.kotlin_funs.iter().enumerate() {
+ if i > 0 {
+ out.push('\n');
+ }
+ out.push_str(block);
+ out.push('\n');
+ }
+ out.push_str("}\n");
+ out
+ }
+
+ fn convert(&mut self, item: syn::Item, loc: &SourceLocation) -> syn::Item {
+ match item {
+ syn::Item::Fn(func) => syn::Item::Fn(self.convert_fn(func, loc)),
+ other => panic!(
+ "JniMethodsConverter received a non-fn item at {loc}: {}",
+ other.to_token_stream()
+ ),
+ }
+ }
+
+ fn convert_fn(&mut self, func: syn::ItemFn, loc: &SourceLocation) -> syn::ItemFn {
+ let original_name = func.sig.ident.to_string();
+ let camel = snake_to_camel(&original_name);
+ let jni_name = format_ident!(
+ "{}{}{}",
+ self.cfg.class_prefix,
+ camel,
+ self.cfg.function_suffix
+ );
+ let orig_ident = &func.sig.ident;
+ let source_module = self.cfg.source_module.clone();
+ let zresult = self.cfg.zresult.clone();
+ let throw_exception = self.cfg.throw_exception.clone();
+
+ let mut prelude: Vec = Vec::new();
+ let mut jni_params: Vec = Vec::new();
+ let mut call_args: Vec = Vec::new();
+ let mut kotlin_params: Vec = Vec::new();
+ let mut local_kotlin_fqns: BTreeSet = BTreeSet::new();
+ let kt_enabled = self.cfg.kotlin.is_some();
+
+ for input in &func.sig.inputs {
+ let syn::FnArg::Typed(pat_type) = input else {
+ panic!("receiver args not supported at {loc}");
+ };
+ let syn::Pat::Ident(pat_ident) = &*pat_type.pat else {
+ panic!("non-ident param pattern at {loc}");
+ };
+ let name = &pat_ident.ident;
+ let ty = &*pat_type.ty;
+
+ self.emit_arg(
+ name,
+ ty,
+ loc,
+ &mut prelude,
+ &mut jni_params,
+ &mut call_args,
+ &mut kotlin_params,
+ &mut local_kotlin_fqns,
+ kt_enabled,
+ );
+ }
+
+ // Return type.
+ let mut kotlin_ret: Option = None;
+ let (ret_ty_jni, wrap_ok, on_err, closure_ret): (
+ TokenStream,
+ TokenStream,
+ TokenStream,
+ TokenStream,
+ ) = match &func.sig.output {
+ syn::ReturnType::Type(_, ty) => {
+ let inner = extract_zresult_inner(ty).unwrap_or_else(|| {
+ panic!("return must be ZResult for `{original_name}` at {loc}")
+ });
+ if is_unit(&inner) {
+ (
+ quote! { () },
+ quote! { Ok(()) },
+ quote! { () },
+ quote! { #zresult<()> },
+ )
+ } else {
+ let key = ty.to_token_stream().to_string();
+ let binding = self.cfg.types.types.get(&key).unwrap_or_else(|| {
+ panic!(
+ "unsupported return type `{}` for `{}` at {loc}: \
+ register a TypeBinding keyed `{}`",
+ ty.to_token_stream(),
+ original_name,
+ key
+ )
+ });
+ let encode = binding.encode().unwrap_or_else(|| {
+ panic!(
+ "TypeBinding `{}` has no encode (return direction) at {loc}",
+ key
+ )
+ });
+ let default_expr = binding
+ .default_expr()
+ .expect("encode-bearing row must have default_expr");
+ let jni_type = binding.jni_type();
+
+ if kt_enabled {
+ let kt = binding.kotlin_type();
+ let short = kotlin_register_fqn(kt, &mut local_kotlin_fqns);
+ kotlin_ret = Some(short);
+ }
+
+ let wrap_ok_ts = match encode {
+ ReturnEncode::Wrapper(p) => quote! { #p(&mut env, __result) },
+ ReturnEncode::ArcIntoRaw => quote! {
+ Ok(std::sync::Arc::into_raw(std::sync::Arc::new(__result)))
+ },
+ };
+
+ (
+ quote! { #jni_type },
+ wrap_ok_ts,
+ quote! { #default_expr },
+ quote! { #zresult<#jni_type> },
+ )
+ }
+ }
+ syn::ReturnType::Default => (
+ quote! { () },
+ quote! { Ok(()) },
+ quote! { () },
+ quote! { #zresult<()> },
+ ),
+ };
+
+ let body = quote! {
+ {
+ (|| -> #closure_ret {
+ #(#prelude)*
+ let __result = #source_module::#orig_ident( #(#call_args),* )?;
+ #wrap_ok
+ })()
+ .unwrap_or_else(|err| {
+ #throw_exception!(env, err);
+ #on_err
+ })
+ }
+ };
+
+ let tokens = quote! {
+ #[no_mangle]
+ #[allow(non_snake_case, unused_mut, unused_variables)]
+ pub unsafe extern "C" fn #jni_name(
+ mut env: jni::JNIEnv,
+ _class: jni::objects::JClass,
+ #(#jni_params),*
+ ) -> #ret_ty_jni #body
+ };
+
+ // Assemble the Kotlin `external fun ...` block.
+ if kt_enabled {
+ let kt_fn_name = format!("{}{}", camel, self.cfg.function_suffix);
+ let ret_suffix = match &kotlin_ret {
+ Some(r) => format!(": {}", r),
+ None => String::new(),
+ };
+ let params_joined = if kotlin_params.is_empty() {
+ String::new()
+ } else {
+ format!("\n {},\n ", kotlin_params.join(",\n "))
+ };
+ let throws_line = if self
+ .cfg
+ .kotlin
+ .as_ref()
+ .and_then(|k| k.throws_class_fqn.as_ref())
+ .is_some()
+ {
+ let fqn = self
+ .cfg
+ .kotlin
+ .as_ref()
+ .unwrap()
+ .throws_class_fqn
+ .as_ref()
+ .unwrap();
+ let short = fqn.rsplit('.').next().unwrap_or(fqn);
+ format!("@Throws({}::class)\n ", short)
+ } else {
+ String::new()
+ };
+ let block = format!(
+ " @JvmStatic\n {}external fun {}({}){}",
+ throws_line, kt_fn_name, params_joined, ret_suffix
+ );
+ self.kotlin_funs.push(block);
+ self.kotlin_used_fqns.extend(local_kotlin_fqns);
+ if let Some(kt) = self.cfg.kotlin.as_ref() {
+ if let Some(fqn) = kt.throws_class_fqn.as_ref() {
+ if fqn.contains('.') {
+ self.kotlin_used_fqns.insert(fqn.clone());
+ }
+ }
+ }
+ }
+
+ syn::parse2(tokens).expect("generated JNI wrapper must parse")
+ }
+
+ #[allow(clippy::too_many_arguments)]
+ fn emit_arg(
+ &self,
+ name: &syn::Ident,
+ ty: &syn::Type,
+ loc: &SourceLocation,
+ prelude: &mut Vec,
+ jni_params: &mut Vec,
+ call_args: &mut Vec,
+ kotlin_params: &mut Vec,
+ local_kotlin_fqns: &mut BTreeSet,
+ kt_enabled: bool,
+ ) {
+ let key = ty.to_token_stream().to_string();
+ let binding = self.cfg.types.types.get(&key).unwrap_or_else(|| {
+ panic!(
+ "unsupported parameter type `{}` for `{}` at {loc}: \
+ register a TypeBinding keyed `{}`",
+ ty.to_token_stream(),
+ name,
+ key
+ )
+ });
+ let decode = binding.decode().unwrap_or_else(|| {
+ panic!(
+ "TypeBinding `{}` has no decode (param direction) at {loc}",
+ key
+ )
+ });
+
+ let pat = if binding.is_pointer() {
+ format_ident!("{}_ptr", name)
+ } else {
+ name.clone()
+ };
+ let jt = binding.jni_type();
+ jni_params.push(quote! { #pat: #jt });
+
+ let expr = decode.call(&pat);
+ prelude.push(quote! { let #name = #expr; });
+
+ if binding.is_borrow() {
+ call_args.push(quote! { name });
+ } else {
+ call_args.push(quote! { #name });
+ }
+
+ if kt_enabled {
+ let short = kotlin_register_fqn(binding.kotlin_type(), local_kotlin_fqns);
+ let suffix = if binding.is_option() { "?" } else { "" };
+ kotlin_params.push(format!(
+ "{}: {}{}",
+ kotlin_param_name(&name.to_string(), binding.is_pointer()),
+ short,
+ suffix
+ ));
+ }
+ }
+}
+
+// =====================================================================
+// Internal helpers
+// =====================================================================
+
+/// Map a primitive JNI wire type (`jni::sys::j*`) to the JVM field
+/// signature character and the matching `JValue` accessor method.
+/// Returns `None` for non-primitive (object-shaped) wire types.
+fn jni_primitive_signature(jni_type: &syn::Type) -> Option<(&'static str, syn::Ident)> {
+ let syn::Type::Path(tp) = jni_type else {
+ return None;
+ };
+ let last = tp.path.segments.last()?;
+ let (sig, accessor) = match last.ident.to_string().as_str() {
+ "jboolean" => ("Z", "z"),
+ "jbyte" => ("B", "b"),
+ "jchar" => ("C", "c"),
+ "jshort" => ("S", "s"),
+ "jint" => ("I", "i"),
+ "jlong" => ("J", "j"),
+ "jfloat" => ("F", "f"),
+ "jdouble" => ("D", "d"),
+ _ => return None,
+ };
+ Some((sig, format_ident!("{}", accessor)))
+}
+
+fn is_unit(ty: &syn::Type) -> bool {
+ matches!(ty, syn::Type::Tuple(t) if t.elems.is_empty())
+}
+
+fn extract_zresult_inner(ty: &syn::Type) -> Option {
+ let syn::Type::Path(tp) = ty else { return None };
+ let seg = tp.path.segments.last()?;
+ if seg.ident != "ZResult" {
+ return None;
+ }
+ let syn::PathArguments::AngleBracketed(args) = &seg.arguments else {
+ return None;
+ };
+ let arg = args.args.first()?;
+ let syn::GenericArgument::Type(inner) = arg else {
+ return None;
+ };
+ Some(inner.clone())
+}
+
+fn snake_to_camel(s: &str) -> String {
+ let mut out = String::with_capacity(s.len());
+ let mut upper_next = false;
+ for (i, c) in s.chars().enumerate() {
+ if c == '_' {
+ upper_next = true;
+ } else if upper_next {
+ out.extend(c.to_uppercase());
+ upper_next = false;
+ } else if i == 0 {
+ out.extend(c.to_lowercase());
+ } else {
+ out.push(c);
+ }
+ }
+ out
+}
+
+/// Map a Rust snake_case arg name to its Kotlin camelCase form, appending
+/// `"Ptr"` for raw-pointer slots.
+fn kotlin_param_name(rust_name: &str, is_pointer: bool) -> String {
+ let base = snake_to_camel(rust_name);
+ if is_pointer {
+ format!("{}Ptr", base)
+ } else {
+ base
+ }
+}
+
+/// Record `fqn` in `used` if it looks fully-qualified (contains `.`) and
+/// return the short name used at the emission site.
+fn kotlin_register_fqn(fqn: &str, used: &mut BTreeSet) -> String {
+ if fqn.contains('.') {
+ used.insert(fqn.to_string());
+ fqn.rsplit('.').next().unwrap_or(fqn).to_string()
+ } else {
+ fqn.to_string()
+ }
+}
diff --git a/prebindgen-ext/src/jni_type_binding.rs b/prebindgen-ext/src/jni_type_binding.rs
new file mode 100644
index 00000000..031b5a21
--- /dev/null
+++ b/prebindgen-ext/src/jni_type_binding.rs
@@ -0,0 +1,351 @@
+//! Flat JNI type registry.
+//!
+//! Each [`TypeBinding`] is a single row keyed by the canonical
+//! `to_token_stream()` form of a Rust type-shape, e.g. `"String"`,
+//! `"& Session"`, `"Vec < u8 >"`, `"Option < KeyExpr < 'static > >"`,
+//! `"ZResult < ZenohId >"`, `"impl Fn (Sample) + Send + Sync + 'static"`.
+//!
+//! A row carries:
+//!
+//! * `kotlin_type` — the Kotlin parameter or return type
+//! * `jni_type` — the on-the-wire JNI type emitted in the wrapper signature
+//! * `decode` — JNI value → Rust value (param-direction rows)
+//! * `encode` + `default_expr` — Rust value → JNI value (return-direction rows)
+//!
+//! Wrapper types (`&T`, `Vec`, `Option`, `ZResult`) are **not**
+//! decomposed by the classifier — each must have its own explicit row. The
+//! [`TypeBinding::opaque_borrow`], [`TypeBinding::opaque_arc_return`] and
+//! [`TypeBinding::option_of`] convenience constructors keep registration
+//! concise.
+
+use std::collections::HashMap;
+use std::sync::Arc;
+
+use proc_macro2::TokenStream;
+use quote::{quote, ToTokens};
+
+/// Clonable closure that produces a `TokenStream` from the JNI input ident.
+#[derive(Clone)]
+pub struct InlineFn(Arc TokenStream + Send + Sync>);
+
+impl InlineFn {
+ pub fn new(f: F) -> Self
+ where
+ F: Fn(&syn::Ident) -> TokenStream + Send + Sync + 'static,
+ {
+ InlineFn(Arc::new(f))
+ }
+
+ /// `()?` — pure conversion (e.g. enum decoders).
+ pub fn pure(path: impl AsRef) -> Self {
+ let s = path.as_ref().to_string();
+ InlineFn::new(move |input| {
+ let p: syn::Path = syn::parse_str(&s).expect("invalid InlineFn::pure path");
+ quote! { #p(#input)? }
+ })
+ }
+
+ /// `(&env, &)?` — decoder needing shared access to the JNI env.
+ pub fn env_ref(path: impl AsRef) -> Self {
+ let s = path.as_ref().to_string();
+ InlineFn::new(move |input| {
+ let p: syn::Path = syn::parse_str(&s).expect("invalid InlineFn::env_ref path");
+ quote! { #p(&env, input)? }
+ })
+ }
+
+ /// `(&mut env, &)?` — decoder needing mutable access to the JNI env.
+ pub fn env_ref_mut(path: impl AsRef) -> Self {
+ let s = path.as_ref().to_string();
+ InlineFn::new(move |input| {
+ let p: syn::Path =
+ syn::parse_str(&s).expect("invalid InlineFn::env_ref_mut path");
+ quote! { #p(&mut env, input)? }
+ })
+ }
+
+ pub(crate) fn call(&self, ident: &syn::Ident) -> TokenStream {
+ (self.0)(ident)
+ }
+}
+
+/// How a Rust return value is encoded into a JNI return.
+#[derive(Clone)]
+pub enum ReturnEncode {
+ /// `(&mut env, __result)` — wrapping function returns
+ /// `ZResult`.
+ Wrapper(syn::Path),
+ /// `Ok(Arc::into_raw(Arc::new(__result)))` — opaque Arc-handle return.
+ ArcIntoRaw,
+}
+
+impl ReturnEncode {
+ pub fn wrapper(path: impl AsRef) -> Self {
+ ReturnEncode::Wrapper(
+ syn::parse_str(path.as_ref()).expect("invalid ReturnEncode::wrapper path"),
+ )
+ }
+}
+
+/// Per-row binding from a Rust type-shape to its JNI/Kotlin representation.
+#[derive(Clone)]
+pub struct TypeBinding {
+ /// Canonical Rust type-shape (token-stream form). The lookup key.
+ pub(crate) rust_type: String,
+ /// Kotlin parameter / return type (FQN preferred for object types,
+ /// bare for primitives). For Option-shaped rows this is the inner
+ /// type's Kotlin name; the `?` suffix is added by the emitter from
+ /// the row's rust_type prefix.
+ pub(crate) kotlin_type: String,
+ /// On-the-wire JNI type emitted in the wrapper signature.
+ pub(crate) jni_type: syn::Type,
+ /// JNI value → Rust value. None for return-only rows.
+ pub(crate) decode: Option,
+ /// Rust value → JNI value. None for param-only rows.
+ pub(crate) encode: Option,
+ /// Default JNI value emitted on the throw-return path. Required when
+ /// `encode` is set.
+ pub(crate) default_expr: Option,
+}
+
+impl TypeBinding {
+ /// Param-direction row. `rust_type` is canonicalized via `syn::Type` parse.
+ pub fn param(
+ rust_type: impl AsRef,
+ kotlin_type: impl Into,
+ jni_type: impl AsRef,
+ decode: InlineFn,
+ ) -> Self {
+ Self {
+ rust_type: canon_type(rust_type.as_ref()),
+ kotlin_type: kotlin_type.into(),
+ jni_type: parse_type(jni_type.as_ref()),
+ decode: Some(decode),
+ encode: None,
+ default_expr: None,
+ }
+ }
+
+ /// Return-direction row.
+ pub fn returns(
+ rust_type: impl AsRef,
+ kotlin_type: impl Into,
+ jni_type: impl AsRef,
+ encode: ReturnEncode,
+ default_expr: impl AsRef,
+ ) -> Self {
+ Self {
+ rust_type: canon_type(rust_type.as_ref()),
+ kotlin_type: kotlin_type.into(),
+ jni_type: parse_type(jni_type.as_ref()),
+ decode: None,
+ encode: Some(encode),
+ default_expr: Some(
+ syn::parse_str(default_expr.as_ref())
+ .expect("invalid TypeBinding::returns default_expr"),
+ ),
+ }
+ }
+
+ /// Convenience: opaque borrow `&T` — JNI side passes raw `*const T`,
+ /// decoded via `::from_raw`. Because the row's key starts
+ /// with `&`, the wrapped fn receives `&name` automatically.
+ pub fn opaque_borrow(t: impl AsRef, owned_object: impl AsRef) -> Self {
+ let t = t.as_ref().to_string();
+ let owned_str = owned_object.as_ref().to_string();
+ // Validate the owner path parses now so errors surface at registration.
+ let _: syn::Path =
+ syn::parse_str(&owned_str).expect("opaque_borrow: invalid owned_object path");
+ Self::param(
+ format!("&{}", t),
+ "Long",
+ format!("*const {}", t),
+ InlineFn::new(move |input| {
+ let owned: syn::Path =
+ syn::parse_str(&owned_str).expect("owned_object must parse");
+ quote! { #owned::from_raw(#input) }
+ }),
+ )
+ }
+
+ /// Convenience: opaque Arc return for `ZResult` — encode via
+ /// `Arc::into_raw(Arc::new(__result))`, default to `std::ptr::null()`.
+ pub fn opaque_arc_return(t: impl AsRef) -> Self {
+ let t = t.as_ref();
+ Self::returns(
+ format!("ZResult<{}>", t),
+ "Long",
+ format!("*const {}", t),
+ ReturnEncode::ArcIntoRaw,
+ "std::ptr::null()",
+ )
+ }
+
+ /// Convenience: `Option` row that lifts `inner`'s decode with a
+ /// JNI-side null check. Inner's wire type must be JNI-object-shaped.
+ pub fn option_of(inner: &TypeBinding) -> Self {
+ let inner_decode = inner
+ .decode
+ .as_ref()
+ .expect("option_of: inner must be a param row")
+ .clone();
+ assert!(
+ jni_object_shaped(&inner.jni_type),
+ "option_of requires a JNI-object inner form, got `{}`",
+ inner.jni_type.to_token_stream()
+ );
+ Self {
+ rust_type: canon_type(&format!("Option<{}>", inner.rust_type)),
+ kotlin_type: inner.kotlin_type.clone(),
+ jni_type: inner.jni_type.clone(),
+ decode: Some(InlineFn::new(move |input| {
+ let inner_expr = inner_decode.call(input);
+ quote! {
+ if !#input.is_null() {
+ Some(#inner_expr)
+ } else {
+ None
+ }
+ }
+ })),
+ encode: None,
+ default_expr: None,
+ }
+ }
+
+ /// Canonical type-shape this binding is keyed under.
+ pub fn name(&self) -> &str {
+ &self.rust_type
+ }
+
+ pub(crate) fn jni_type(&self) -> &syn::Type {
+ &self.jni_type
+ }
+ pub(crate) fn kotlin_type(&self) -> &str {
+ &self.kotlin_type
+ }
+ pub(crate) fn decode(&self) -> Option<&InlineFn> {
+ self.decode.as_ref()
+ }
+ pub(crate) fn encode(&self) -> Option<&ReturnEncode> {
+ self.encode.as_ref()
+ }
+ pub(crate) fn default_expr(&self) -> Option<&syn::Expr> {
+ self.default_expr.as_ref()
+ }
+ /// `&T` row — wrapped fn receives `&name`.
+ pub(crate) fn is_borrow(&self) -> bool {
+ self.rust_type.starts_with('&')
+ }
+ /// `*const _` / `*mut _` wire type — Kotlin name gets `Ptr` suffix and
+ /// the Rust ident gets `_ptr` suffix.
+ pub(crate) fn is_pointer(&self) -> bool {
+ matches!(self.jni_type, syn::Type::Ptr(_))
+ }
+ /// `Option<_>` row — Kotlin emission appends `?`.
+ pub(crate) fn is_option(&self) -> bool {
+ self.rust_type.starts_with("Option <")
+ }
+}
+
+/// Reusable collection of [`TypeBinding`]s plus the Kotlin `data class`
+/// strings produced by struct processing.
+#[derive(Default, Clone)]
+pub struct JniTypeBinding {
+ pub(crate) types: HashMap,
+ pub(crate) kotlin_data_classes: Vec,
+}
+
+impl JniTypeBinding {
+ pub fn new() -> Self {
+ Self::default()
+ }
+
+ /// Add (or replace) a [`TypeBinding`] in this collection.
+ pub fn type_binding(mut self, binding: TypeBinding) -> Self {
+ self.types.insert(binding.rust_type.clone(), binding);
+ self
+ }
+
+ /// Look up a registered [`TypeBinding`] by its canonical type-shape key
+ /// (e.g. `"HistoryConfig"`, `"Vec < u8 >"`). The key is canonicalized
+ /// via `syn::Type` parse so callers can pass either spacing form.
+ pub fn type_by_key(&self, key: &str) -> Option<&TypeBinding> {
+ self.types.get(&canon_type(key))
+ }
+
+ /// Merge another [`JniTypeBinding`] into this one. Type entries in
+ /// `other` override entries with the same key in `self`; data-class
+ /// blocks are appended in order.
+ pub fn merge(mut self, other: JniTypeBinding) -> Self {
+ self.types.extend(other.types);
+ self.kotlin_data_classes.extend(other.kotlin_data_classes);
+ self
+ }
+
+ /// Pre-register built-in language types whose JNI form is fully described
+ /// without any project-specific decoder path: `bool`, `i64`, `f64`, and
+ /// `Duration`.
+ pub fn with_builtins(mut self) -> Self {
+ let bool_row = TypeBinding::param(
+ "bool",
+ "Boolean",
+ "jni::sys::jboolean",
+ InlineFn::new(|input| quote! { #input != 0 }),
+ );
+ self.types.insert(bool_row.rust_type.clone(), bool_row);
+
+ let i64_row = TypeBinding::param(
+ "i64",
+ "Long",
+ "jni::sys::jlong",
+ InlineFn::new(|input| quote! { #input }),
+ );
+ self.types.insert(i64_row.rust_type.clone(), i64_row);
+
+ let f64_row = TypeBinding::param(
+ "f64",
+ "Double",
+ "jni::sys::jdouble",
+ InlineFn::new(|input| quote! { #input }),
+ );
+ self.types.insert(f64_row.rust_type.clone(), f64_row);
+
+ let duration_row = TypeBinding::param(
+ "Duration",
+ "Long",
+ "jni::sys::jlong",
+ InlineFn::new(|input| {
+ quote! { std::time::Duration::from_millis(#input as u64) }
+ }),
+ );
+ self.types
+ .insert(duration_row.rust_type.clone(), duration_row);
+ self
+ }
+}
+
+/// Canonical type-shape string. Parses through `syn::Type` so whitespace
+/// variations in user input (`"Vec"` vs `"Vec < u8 >"`) match the form
+/// the classifier produces from AST nodes via `to_token_stream()`.
+pub(crate) fn canon_type(s: &str) -> String {
+ syn::parse_str::(s)
+ .map(|t| t.to_token_stream().to_string())
+ .unwrap_or_else(|e| panic!("TypeBinding: cannot parse `{}` as a type: {}", s, e))
+}
+
+fn parse_type(s: &str) -> syn::Type {
+ syn::parse_str(s).unwrap_or_else(|e| panic!("invalid JNI wire type `{}`: {}", s, e))
+}
+
+/// True if `ty` is a JNI object-shaped wire type that supports `is_null()`.
+pub(crate) fn jni_object_shaped(ty: &syn::Type) -> bool {
+ let syn::Type::Path(tp) = ty else { return false };
+ let Some(last) = tp.path.segments.last() else {
+ return false;
+ };
+ matches!(
+ last.ident.to_string().as_str(),
+ "JObject" | "JString" | "JByteArray"
+ )
+}
diff --git a/prebindgen-ext/src/lib.rs b/prebindgen-ext/src/lib.rs
new file mode 100644
index 00000000..c28323a8
--- /dev/null
+++ b/prebindgen-ext/src/lib.rs
@@ -0,0 +1,9 @@
+//! Prebindgen JNI extensions for Zenoh.
+//!
+//! This crate provides JNI binding generators for items marked with `#[prebindgen]`.
+
+pub mod jni_converter;
+pub mod jni_type_binding;
+
+pub use jni_converter::{JniStructConverter, JniMethodsConverter};
+pub use jni_type_binding::{JniTypeBinding, TypeBinding, InlineFn, ReturnEncode};
diff --git a/settings.gradle.kts b/settings.gradle.kts
index 2d17a6a4..776fcd77 100644
--- a/settings.gradle.kts
+++ b/settings.gradle.kts
@@ -22,6 +22,7 @@ pluginManagement {
rootProject.name = "zenoh-java"
include(":zenoh-java")
+include(":zenoh-jni-runtime")
include(":examples")
include(":zenoh-jni")
diff --git a/zenoh-flat/Cargo.lock b/zenoh-flat/Cargo.lock
new file mode 100644
index 00000000..c8945b8d
--- /dev/null
+++ b/zenoh-flat/Cargo.lock
@@ -0,0 +1,2827 @@
+# This file is automatically @generated by Cargo.
+# It is not intended for manual editing.
+version = 4
+
+[[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_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 = "async-trait"
+version = "0.1.89"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.117",
+]
+
+[[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 = "bincode"
+version = "1.3.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad"
+dependencies = [
+ "serde",
+]
+
+[[package]]
+name = "bitflags"
+version = "2.11.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c4512299f36f043ab09a583e57bceb5a5aab7a73db1805848e8fef3c9e8c78b3"
+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 = "bytes"
+version = "1.11.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33"
+
+[[package]]
+name = "cc"
+version = "1.2.60"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "43c5703da9466b66a946814e1adf53ea2c90f10063b86290cc9eb67ce3478a20"
+dependencies = [
+ "find-msvc-tools",
+ "shlex",
+]
+
+[[package]]
+name = "cfg-if"
+version = "1.0.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
+
+[[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 = "concurrent-queue"
+version = "2.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973"
+dependencies = [
+ "crossbeam-utils",
+]
+
+[[package]]
+name = "const_format"
+version = "0.2.36"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4481a617ad9a412be3b97c5d403fef8ed023103368908b9c50af598ff467cc1e"
+dependencies = [
+ "const_format_proc_macros",
+ "konst 0.2.20",
+]
+
+[[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 = "const_panic"
+version = "0.2.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e262cdaac42494e3ae34c43969f9cdeb7da178bdb4b66fa6a1ea2edb4c8ae652"
+dependencies = [
+ "typewit",
+]
+
+[[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",
+ "syn 2.0.117",
+]
+
+[[package]]
+name = "darling_macro"
+version = "0.23.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ac3984ec7bd6cfa798e62b4a642426a5be0e68f9401cfc2a01e3fa9ea2fcdb8d"
+dependencies = [
+ "darling_core",
+ "quote",
+ "syn 2.0.117",
+]
+
+[[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",
+ "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 = "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 = "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 = "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.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 = "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 2.0.117",
+]
+
+[[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",
+ "libc",
+ "r-efi",
+ "wasip2",
+]
+
+[[package]]
+name = "git-version"
+version = "0.3.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1ad568aa3db0fcbc81f2f116137f263d7304f512a1209b35b85150d3ef88ad19"
+dependencies = [
+ "git-version-macro",
+]
+
+[[package]]
+name = "git-version-macro"
+version = "0.3.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "53010ccb100b96a67bc32c0175f0ed1426b31b655d562898e57325f81c023ac0"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.117",
+]
+
+[[package]]
+name = "hashbrown"
+version = "0.12.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
+
+[[package]]
+name = "hashbrown"
+version = "0.14.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1"
+dependencies = [
+ "ahash",
+ "allocator-api2",
+]
+
+[[package]]
+name = "hashbrown"
+version = "0.15.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1"
+dependencies = [
+ "foldhash 0.1.5",
+]
+
+[[package]]
+name = "hashbrown"
+version = "0.16.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100"
+dependencies = [
+ "allocator-api2",
+ "equivalent",
+ "foldhash 0.2.0",
+]
+
+[[package]]
+name = "hashbrown"
+version = "0.17.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4f467dd6dccf739c208452f8014c75c18bb8301b050ad1cfb27153803edb0f51"
+
+[[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 = "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 = "ident_case"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39"
+
+[[package]]
+name = "if_rust_version"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "46dbcb333e86939721589d25a3557e180b52778cb33c7fdfe9e0158ff790d5ec"
+
+[[package]]
+name = "indexmap"
+version = "1.9.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99"
+dependencies = [
+ "autocfg",
+ "hashbrown 0.12.3",
+ "serde",
+]
+
+[[package]]
+name = "indexmap"
+version = "2.14.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9"
+dependencies = [
+ "equivalent",
+ "hashbrown 0.17.0",
+ "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.12.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569"
+dependencies = [
+ "either",
+]
+
+[[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 = "js-sys"
+version = "0.3.95"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2964e92d1d9dc3364cae4d718d93f227e3abb088e747d92e0395bfdedf1c12ca"
+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 = "konst"
+version = "0.2.20"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "128133ed7824fcd73d6e7b17957c5eb7bacb885649bd8c69708b2331a10bcefb"
+dependencies = [
+ "konst_macro_rules",
+]
+
+[[package]]
+name = "konst"
+version = "0.3.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "97feab15b395d1860944abe6a8dd8ed9f8eadfae01750fada8427abda531d887"
+dependencies = [
+ "const_panic",
+ "konst_kernel",
+ "konst_proc_macros",
+ "typewit",
+]
+
+[[package]]
+name = "konst_kernel"
+version = "0.3.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e4b1eb7788f3824c629b1116a7a9060d6e898c358ebff59070093d51103dcc3c"
+dependencies = [
+ "typewit",
+]
+
+[[package]]
+name = "konst_macro_rules"
+version = "0.2.19"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a4933f3f57a8e9d9da04db23fb153356ecaf00cbd14aee46279c33dc80925c37"
+
+[[package]]
+name = "konst_proc_macros"
+version = "0.3.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "00af7901ba50898c9e545c24d5c580c96a982298134e8037d8978b6594782c07"
+
+[[package]]
+name = "lazy_static"
+version = "1.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
+
+[[package]]
+name = "leb128"
+version = "0.2.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6cc46bac87ef8093eed6f272babb833b6443374399985ac8ed28471ee0918545"
+
+[[package]]
+name = "libc"
+version = "0.2.185"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "52ff2c0fe9bc6cb6b14a0592c2ff4fa9ceb83eea9db979b0487cd054946a2b8f"
+
+[[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 = "libredox"
+version = "0.1.16"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e02f3bb43d335493c96bf3fd3a321600bf6bd07ed34bc64118e9293bdffea46c"
+dependencies = [
+ "libc",
+]
+
+[[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 = "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 = "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 = "no-std-net"
+version = "0.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "43794a0ace135be66a25d3ae77d41b91615fb68ae937f904090203e81f755b65"
+
+[[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-conv"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c6673768db2d862beb9b39a78fdcb1a69439615d5794a1be50caa9bc92c81967"
+
+[[package]]
+name = "num-traits"
+version = "0.2.19"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
+dependencies = [
+ "autocfg",
+]
+
+[[package]]
+name = "num_cpus"
+version = "1.17.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "91df4bbde75afed763b708b7eee1e8e7651e02d97f6d5dd763e89367e957b23b"
+dependencies = [
+ "hermit-abi",
+ "libc",
+]
+
+[[package]]
+name = "once_cell"
+version = "1.21.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50"
+
+[[package]]
+name = "option-ext"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d"
+
+[[package]]
+name = "parking"
+version = "2.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba"
+
+[[package]]
+name = "paste"
+version = "1.0.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a"
+
+[[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 2.0.117",
+]
+
+[[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.14.0",
+ "serde",
+]
+
+[[package]]
+name = "phf"
+version = "0.13.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c1562dc717473dbaa4c1f85a36410e03c047b2e7df7f45ee938fbef64ae7fadf"
+dependencies = [
+ "phf_macros",
+ "phf_shared",
+ "serde",
+]
+
+[[package]]
+name = "phf_generator"
+version = "0.13.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "135ace3a761e564ec88c03a77317a7c6b80bb7f7135ef2544dbe054243b89737"
+dependencies = [
+ "fastrand",
+ "phf_shared",
+]
+
+[[package]]
+name = "phf_macros"
+version = "0.13.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "812f032b54b1e759ccd5f8b6677695d5268c588701effba24601f6932f8269ef"
+dependencies = [
+ "phf_generator",
+ "phf_shared",
+ "proc-macro2",
+ "quote",
+ "syn 2.0.117",
+]
+
+[[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-lite"
+version = "0.2.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd"
+
+[[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 = "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 = "prebindgen"
+version = "0.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4b005ec2d8a59f83ecc1b63ef25ab4d42fa33d9642e24d5d8dab5e750d5f6f46"
+dependencies = [
+ "if_rust_version",
+ "itertools 0.14.0",
+ "konst 0.3.17",
+ "prettyplease",
+ "proc-macro2",
+ "quote",
+ "regex",
+ "roxygen",
+ "serde",
+ "serde_json",
+ "syn 2.0.117",
+ "toml",
+]
+
+[[package]]
+name = "prebindgen-ext"
+version = "1.9.0"
+dependencies = [
+ "itertools 0.12.1",
+ "prebindgen",
+ "proc-macro2",
+ "quote",
+ "syn 2.0.117",
+]
+
+[[package]]
+name = "prebindgen-proc-macro"
+version = "0.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "47a68d8870ef9ba6f8abbe9d4bca5775e1d854d5fa6dac8c27dd1c79ecebe398"
+dependencies = [
+ "prebindgen",
+ "proc-macro2",
+ "quote",
+ "rand 0.9.4",
+ "serde",
+ "serde_json",
+ "syn 2.0.117",
+]
+
+[[package]]
+name = "prettyplease"
+version = "0.2.37"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b"
+dependencies = [
+ "proc-macro2",
+ "syn 2.0.117",
+]
+
+[[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 = "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 = "rand"
+version = "0.8.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5ca0ecfa931c29007047d1bc58e623ab12e5590e8c7cc53200d5202b69266d8a"
+dependencies = [
+ "libc",
+ "rand_chacha 0.3.1",
+ "rand_core 0.6.4",
+]
+
+[[package]]
+name = "rand"
+version = "0.9.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "44c5af06bb1b7d3216d91932aed5265164bf384dc89cd6ba05cf59a35f5f76ea"
+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 = "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",
+]
+
+[[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 2.0.117",
+]
+
+[[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 = "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",
+ "once_cell",
+ "serde",
+ "serde_derive",
+ "typeid",
+ "unicode-ident",
+]
+
+[[package]]
+name = "roxygen"
+version = "1.0.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fa650dd372f29f0a6be64b2896707f9536962ba28915e3b39bcafd5a6221873b"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.117",
+]
+
+[[package]]
+name = "rustc_version"
+version = "0.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92"
+dependencies = [
+ "semver",
+]
+
+[[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 = "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 2.0.117",
+]
+
+[[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 = "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 2.0.117",
+]
+
+[[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 2.0.117",
+]
+
+[[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.14.0",
+ "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 2.0.117",
+]
+
+[[package]]
+name = "serde_yaml"
+version = "0.9.34+deprecated"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6a8b1a1a2ebf674015cc02edccce75287f1a0130d394307b36743c2f5d504b47"
+dependencies = [
+ "indexmap 2.14.0",
+ "itoa",
+ "ryu",
+ "serde",
+ "unsafe-libyaml",
+]
+
+[[package]]
+name = "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.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "77fd7028345d415a4034cf8777cd4f8ab1851274233b45f84e3d955502d93874"
+dependencies = [
+ "digest",
+ "keccak",
+]
+
+[[package]]
+name = "sharded-slab"
+version = "0.1.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6"
+dependencies = [
+ "lazy_static",
+]
+
+[[package]]
+name = "shellexpand"
+version = "3.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "32824fab5e16e6c4d86dc1ba84489390419a39f97699852b66480bb87d297ed8"
+dependencies = [
+ "dirs",
+]
+
+[[package]]
+name = "shlex"
+version = "1.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
+
+[[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 = "stabby"
+version = "72.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "976399a0c48ea769ef7f5dc303bb88240ab8d84008647a6b2303eced3dab3945"
+dependencies = [
+ "rustversion",
+ "stabby-abi",
+]
+
+[[package]]
+name = "stabby-abi"
+version = "72.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f7b54832a9a1f92a0e55e74a5c0332744426edc515bb3fbad82f10b874a87f0d"
+dependencies = [
+ "rustc_version",
+ "rustversion",
+ "sha2-const-stable",
+ "stabby-macros",
+]
+
+[[package]]
+name = "stabby-macros"
+version = "72.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a768b1e51e4dbfa4fa52ae5c01241c0a41e2938fdffbb84add0c8238092f9091"
+dependencies = [
+ "proc-macro-crate",
+ "proc-macro2",
+ "quote",
+ "rand 0.8.6",
+ "syn 1.0.109",
+]
+
+[[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.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 = "1.0.109"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "unicode-ident",
+]
+
+[[package]]
+name = "syn"
+version = "2.0.117"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "unicode-ident",
+]
+
+[[package]]
+name = "thiserror"
+version = "2.0.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4"
+dependencies = [
+ "thiserror-impl",
+]
+
+[[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 2.0.117",
+]
+
+[[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 = "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.52.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b67dee974fe86fd92cc45b7a95fdd2f99a36a6d7b0d431a231178d3d670bbcc6"
+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 2.0.117",
+]
+
+[[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.14.0",
+ "serde_core",
+ "serde_spanned",
+ "toml_datetime 0.7.5+spec-1.1.0",
+ "toml_parser",
+ "toml_writer",
+ "winnow 0.7.15",
+]
+
+[[package]]
+name = "toml_datetime"
+version = "0.7.5+spec-1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "92e1cfed4a3038bc5a127e35a2d360f145e1f4b971b551a2ba5fd7aedf7e1347"
+dependencies = [
+ "serde_core",
+]
+
+[[package]]
+name = "toml_datetime"
+version = "1.1.1+spec-1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3165f65f62e28e0115a00b2ebdd37eb6f3b641855f9d636d3cd4103767159ad7"
+dependencies = [
+ "serde_core",
+]
+
+[[package]]
+name = "toml_edit"
+version = "0.25.11+spec-1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0b59c4d22ed448339746c59b905d24568fcbb3ab65a500494f7b8c3e97739f2b"
+dependencies = [
+ "indexmap 2.14.0",
+ "toml_datetime 1.1.1+spec-1.1.0",
+ "toml_parser",
+ "winnow 1.0.2",
+]
+
+[[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.2",
+]
+
+[[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 = [
+ "pin-project-lite",
+ "tracing-attributes",
+ "tracing-core",
+]
+
+[[package]]
+name = "tracing-attributes"
+version = "0.1.31"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.117",
+]
+
+[[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 = "twox-hash"
+version = "1.6.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "97fee6b57c6a41524a810daee9286c02d7752c4253064d0b05472833a438f675"
+dependencies = [
+ "cfg-if",
+ "static_assertions",
+]
+
+[[package]]
+name = "typeid"
+version = "1.0.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bc7d623258602320d5c55d1bc22793b57daff0ec7efc270ea7d55ce1d5f5471c"
+
+[[package]]
+name = "typenum"
+version = "1.20.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "40ce102ab67701b8526c123c1bab5cbe42d7040ccfd0f64af1a385808d2f43de"
+
+[[package]]
+name = "typewit"
+version = "1.15.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "214ca0b2191785cbc06209b9ca1861e048e39b5ba33574b3cedd58363d5bb5f6"
+dependencies = [
+ "typewit_proc_macros",
+]
+
+[[package]]
+name = "typewit_proc_macros"
+version = "1.8.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e36a83ea2b3c704935a01b4642946aadd445cea40b10935e3f8bd8052b8193d6"
+
+[[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.6",
+ "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 = "unzip-n"
+version = "0.1.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3b5bb2756c16fb66f80cfbf5fb0e0c09a7001e739f453c9ec241b9c8b1556fda"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.117",
+]
+
+[[package]]
+name = "validated_struct"
+version = "2.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "869a93e8a7286e339e1128630051d82babbcd75d585975af07b9f3327220e60e"
+dependencies = [
+ "json5",
+ "serde",
+ "serde_json",
+ "validated_struct_macros",
+]
+
+[[package]]
+name = "validated_struct_macros"
+version = "2.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8c44ce98e7227a04eeb4cf9c784109a5c9710e54849ceb4f09f8597247897f1e"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.117",
+ "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 = "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.3+wasi-0.2.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "20064672db26d7cdc89c7798c48a0fdfac8213434a1186e5ef29fd560ae223d6"
+dependencies = [
+ "wit-bindgen",
+]
+
+[[package]]
+name = "wasm-bindgen"
+version = "0.2.118"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0bf938a0bacb0469e83c1e148908bd7d5a6010354cf4fb73279b7447422e3a89"
+dependencies = [
+ "cfg-if",
+ "once_cell",
+ "rustversion",
+ "wasm-bindgen-macro",
+ "wasm-bindgen-shared",
+]
+
+[[package]]
+name = "wasm-bindgen-macro"
+version = "0.2.118"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "eeff24f84126c0ec2db7a449f0c2ec963c6a49efe0698c4242929da037ca28ed"
+dependencies = [
+ "quote",
+ "wasm-bindgen-macro-support",
+]
+
+[[package]]
+name = "wasm-bindgen-macro-support"
+version = "0.2.118"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9d08065faf983b2b80a79fd87d8254c409281cf7de75fc4b773019824196c904"
+dependencies = [
+ "bumpalo",
+ "proc-macro2",
+ "quote",
+ "syn 2.0.117",
+ "wasm-bindgen-shared",
+]
+
+[[package]]
+name = "wasm-bindgen-shared"
+version = "0.2.118"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5fd04d9e306f1907bd13c6361b5c6bfc7b3b3c095ed3f8a9246390f8dbdee129"
+dependencies = [
+ "unicode-ident",
+]
+
+[[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-x86_64-pc-windows-gnu"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
+
+[[package]]
+name = "windows-core"
+version = "0.62.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb"
+dependencies = [
+ "windows-implement",
+ "windows-interface",
+ "windows-link",
+ "windows-result",
+ "windows-strings",
+]
+
+[[package]]
+name = "windows-implement"
+version = "0.60.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.117",
+]
+
+[[package]]
+name = "windows-interface"
+version = "0.59.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.117",
+]
+
+[[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.52.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
+dependencies = [
+ "windows-targets",
+]
+
+[[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.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
+dependencies = [
+ "windows_aarch64_gnullvm",
+ "windows_aarch64_msvc",
+ "windows_i686_gnu",
+ "windows_i686_gnullvm",
+ "windows_i686_msvc",
+ "windows_x86_64_gnu",
+ "windows_x86_64_gnullvm",
+ "windows_x86_64_msvc",
+]
+
+[[package]]
+name = "windows_aarch64_gnullvm"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
+
+[[package]]
+name = "windows_aarch64_msvc"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
+
+[[package]]
+name = "windows_i686_gnu"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
+
+[[package]]
+name = "windows_i686_gnullvm"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
+
+[[package]]
+name = "windows_i686_msvc"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
+
+[[package]]
+name = "windows_x86_64_gnu"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
+
+[[package]]
+name = "windows_x86_64_gnullvm"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
+
+[[package]]
+name = "windows_x86_64_msvc"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
+
+[[package]]
+name = "winnow"
+version = "0.7.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "df79d97927682d2fd8adb29682d1140b343be4ac0f08fd68b7765d9c059d3945"
+
+[[package]]
+name = "winnow"
+version = "1.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2ee1708bef14716a11bae175f579062d4554d95be2c6829f518df847b7b3fdd0"
+dependencies = [
+ "memchr",
+]
+
+[[package]]
+name = "wit-bindgen"
+version = "0.57.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e"
+
+[[package]]
+name = "zenoh"
+version = "1.9.0"
+source = "git+https://github.com/eclipse-zenoh/zenoh.git?branch=main#7792ebbb2fb0c8311e51419994171cf91fb619d7"
+dependencies = [
+ "ahash",
+ "arc-swap",
+ "async-trait",
+ "bytes",
+ "const_format",
+ "flate2",
+ "flume",
+ "futures",
+ "git-version",
+ "itertools 0.14.0",
+ "json5",
+ "lazy_static",
+ "nonempty-collections",
+ "once_cell",
+ "petgraph",
+ "phf",
+ "rand 0.8.6",
+ "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#7792ebbb2fb0c8311e51419994171cf91fb619d7"
+dependencies = [
+ "zenoh-collections",
+]
+
+[[package]]
+name = "zenoh-codec"
+version = "1.9.0"
+source = "git+https://github.com/eclipse-zenoh/zenoh.git?branch=main#7792ebbb2fb0c8311e51419994171cf91fb619d7"
+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#7792ebbb2fb0c8311e51419994171cf91fb619d7"
+dependencies = [
+ "ahash",
+]
+
+[[package]]
+name = "zenoh-config"
+version = "1.9.0"
+source = "git+https://github.com/eclipse-zenoh/zenoh.git?branch=main#7792ebbb2fb0c8311e51419994171cf91fb619d7"
+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#7792ebbb2fb0c8311e51419994171cf91fb619d7"
+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#7792ebbb2fb0c8311e51419994171cf91fb619d7"
+dependencies = [
+ "aes",
+ "hmac",
+ "rand 0.8.6",
+ "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#7792ebbb2fb0c8311e51419994171cf91fb619d7"
+dependencies = [
+ "async-trait",
+ "bincode",
+ "flume",
+ "futures",
+ "leb128",
+ "serde",
+ "tokio",
+ "tracing",
+ "uhlc",
+ "zenoh",
+ "zenoh-macros",
+ "zenoh-util",
+]
+
+[[package]]
+name = "zenoh-flat"
+version = "1.9.0"
+dependencies = [
+ "json5",
+ "prebindgen",
+ "prebindgen-ext",
+ "prebindgen-proc-macro",
+ "serde_yaml",
+ "tracing",
+ "zenoh",
+ "zenoh-ext",
+]
+
+[[package]]
+name = "zenoh-keyexpr"
+version = "1.9.0"
+source = "git+https://github.com/eclipse-zenoh/zenoh.git?branch=main#7792ebbb2fb0c8311e51419994171cf91fb619d7"
+dependencies = [
+ "getrandom 0.2.17",
+ "hashbrown 0.16.1",
+ "keyed-set",
+ "rand 0.8.6",
+ "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#7792ebbb2fb0c8311e51419994171cf91fb619d7"
+dependencies = [
+ "zenoh-config",
+ "zenoh-link-commons",
+ "zenoh-protocol",
+ "zenoh-result",
+]
+
+[[package]]
+name = "zenoh-link-commons"
+version = "1.9.0"
+source = "git+https://github.com/eclipse-zenoh/zenoh.git?branch=main#7792ebbb2fb0c8311e51419994171cf91fb619d7"
+dependencies = [
+ "async-trait",
+ "flume",
+ "futures",
+ "serde",
+ "socket2 0.5.10",
+ "time",
+ "tokio",
+ "tokio-util",
+ "tracing",
+ "zenoh-buffers",
+ "zenoh-codec",
+ "zenoh-core",
+ "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#7792ebbb2fb0c8311e51419994171cf91fb619d7"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.117",
+ "zenoh-keyexpr",
+]
+
+[[package]]
+name = "zenoh-plugin-trait"
+version = "1.9.0"
+source = "git+https://github.com/eclipse-zenoh/zenoh.git?branch=main#7792ebbb2fb0c8311e51419994171cf91fb619d7"
+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#7792ebbb2fb0c8311e51419994171cf91fb619d7"
+dependencies = [
+ "const_format",
+ "rand 0.8.6",
+ "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#7792ebbb2fb0c8311e51419994171cf91fb619d7"
+dependencies = [
+ "anyhow",
+]
+
+[[package]]
+name = "zenoh-runtime"
+version = "1.9.0"
+source = "git+https://github.com/eclipse-zenoh/zenoh.git?branch=main#7792ebbb2fb0c8311e51419994171cf91fb619d7"
+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#7792ebbb2fb0c8311e51419994171cf91fb619d7"
+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#7792ebbb2fb0c8311e51419994171cf91fb619d7"
+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#7792ebbb2fb0c8311e51419994171cf91fb619d7"
+dependencies = [
+ "async-trait",
+ "crossbeam-utils",
+ "flume",
+ "futures",
+ "lazy_static",
+ "lz4_flex",
+ "rand 0.8.6",
+ "ringbuffer-spsc",
+ "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#7792ebbb2fb0c8311e51419994171cf91fb619d7"
+dependencies = [
+ "async-trait",
+ "const_format",
+ "flume",
+ "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 = "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 2.0.117",
+]
+
+[[package]]
+name = "zeroize"
+version = "1.8.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0"
+
+[[package]]
+name = "zmij"
+version = "1.0.21"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa"
diff --git a/zenoh-flat/Cargo.toml b/zenoh-flat/Cargo.toml
new file mode 100644
index 00000000..e247a48d
--- /dev/null
+++ b/zenoh-flat/Cargo.toml
@@ -0,0 +1,25 @@
+[package]
+name = "zenoh-flat"
+version = "1.9.0"
+edition = "2021"
+license = "EPL-2.0 OR Apache-2.0"
+description = "Zenoh flat data interchange support."
+repository = "https://github.com/eclipse-zenoh/zenoh"
+build = "build.rs"
+
+[features]
+default = []
+zenoh-ext = ["dep:zenoh-ext"]
+
+[dependencies]
+zenoh = { version = "1.9.0", git = "https://github.com/eclipse-zenoh/zenoh.git", branch = "main", features = ["unstable", "internal"], default-features = false }
+zenoh-ext = { version = "1.9.0", git = "https://github.com/eclipse-zenoh/zenoh.git", branch = "main", features = ["unstable", "internal"], default-features = false, optional = true }
+json5 = "0.4.1"
+serde_yaml = "0.9.19"
+tracing = "0.1"
+prebindgen-proc-macro = "0.4.1"
+prebindgen = "0.4.1"
+prebindgen-ext = { path = "../prebindgen-ext" }
+
+[build-dependencies]
+prebindgen = "0.4.1"
diff --git a/zenoh-flat/build.rs b/zenoh-flat/build.rs
new file mode 100644
index 00000000..cc8fe95d
--- /dev/null
+++ b/zenoh-flat/build.rs
@@ -0,0 +1,3 @@
+fn main() {
+ prebindgen::init_prebindgen_out_dir();
+}
diff --git a/zenoh-flat/src/config.rs b/zenoh-flat/src/config.rs
new file mode 100644
index 00000000..35328830
--- /dev/null
+++ b/zenoh-flat/src/config.rs
@@ -0,0 +1,33 @@
+// 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::zerror;
+use zenoh::config::Config;
+
+/// Load a Zenoh configuration from a JSON string.
+pub fn load_json_config(json: &str) -> crate::errors::ZResult {
+ let mut deserializer = json5::Deserializer::from_str(json).map_err(|err| zerror!(err))?;
+ Config::from_deserializer(&mut deserializer).map_err(|err| match err {
+ Ok(c) => zerror!("Invalid configuration: {}", c),
+ Err(e) => zerror!("JSON error: {}", e),
+ })
+}
+
+/// Load a Zenoh configuration from a YAML string.
+pub fn load_yaml_config(yaml: &str) -> crate::errors::ZResult {
+ let deserializer = serde_yaml::Deserializer::from_str(yaml);
+ Config::from_deserializer(deserializer).map_err(|err| match err {
+ Ok(c) => zerror!("Invalid configuration: {}", c),
+ Err(e) => zerror!("YAML error: {}", e),
+ })
+}
diff --git a/zenoh-flat/src/errors.rs b/zenoh-flat/src/errors.rs
new file mode 100644
index 00000000..7ac177ab
--- /dev/null
+++ b/zenoh-flat/src/errors.rs
@@ -0,0 +1,35 @@
+// 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;
+
+#[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 struct ZError(pub String);
+
+impl fmt::Display for ZError {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ write!(f, "{}", self.0)
+ }
+}
diff --git a/zenoh-flat/src/ext.rs b/zenoh-flat/src/ext.rs
new file mode 100644
index 00000000..390ec464
--- /dev/null
+++ b/zenoh-flat/src/ext.rs
@@ -0,0 +1,149 @@
+// 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,
+//
+
+//! Flat mirrors of the zenoh-ext advanced publisher/subscriber configs.
+//!
+//! Marked with `#[prebindgen]` so the JNI binding generator in
+//! [`crate::jni_converter`] can emit a matching Kotlin `data class` and a
+//! Rust JObject decoder for each one. The `TryFrom` impls encapsulate the
+//! flat → semantic conversion (enum decoding happens already in the
+//! auto-generated decoder, so these impls only deal with size/sign
+//! adjustments and the "flag fields decide which builder method to call"
+//! semantics).
+
+use std::time::Duration;
+
+use zenoh::qos::{CongestionControl, Priority};
+
+use crate::errors::{ZError, ZResult};
+use crate::zerror;
+
+/// Flat cache configuration for an [`zenoh_ext::AdvancedPublisher`].
+#[prebindgen_proc_macro::prebindgen("jni")]
+#[derive(Debug, Clone)]
+pub struct CacheConfig {
+ pub max_samples: i64,
+ pub replies_priority: Priority,
+ pub replies_congestion_control: CongestionControl,
+ pub replies_is_express: bool,
+}
+
+/// Flat history configuration for an [`zenoh_ext::AdvancedSubscriber`].
+///
+/// `max_samples <= 0` and `max_age_seconds <= 0.0` mean "unlimited".
+#[prebindgen_proc_macro::prebindgen("jni")]
+#[derive(Debug, Clone)]
+pub struct HistoryConfig {
+ pub detect_late_publishers: bool,
+ pub max_samples: i64,
+ pub max_age_seconds: f64,
+}
+
+/// Flat miss-detection configuration for an [`zenoh_ext::AdvancedPublisher`].
+///
+/// When `enable_heartbeat` is `true`, the heartbeat is configured with
+/// `period_ms` — sporadic if `is_sporadic`, regular otherwise. When
+/// `enable_heartbeat` is `false`, the other fields are ignored and
+/// [`zenoh_ext::MissDetectionConfig::default`] is used as-is.
+#[prebindgen_proc_macro::prebindgen("jni")]
+#[derive(Debug, Clone)]
+pub struct MissDetectionConfig {
+ pub enable_heartbeat: bool,
+ pub is_sporadic: bool,
+ pub period_ms: i64,
+}
+
+/// Flat recovery configuration for an [`zenoh_ext::AdvancedSubscriber`].
+///
+/// When `is_heartbeat` is `true`, `period_ms` is ignored; otherwise
+/// `period_ms` is the `periodic_queries` period in milliseconds.
+#[prebindgen_proc_macro::prebindgen("jni")]
+#[derive(Debug, Clone)]
+pub struct RecoveryConfig {
+ pub is_heartbeat: bool,
+ pub period_ms: i64,
+}
+
+impl TryFrom for zenoh_ext::CacheConfig {
+ type Error = ZError;
+
+ fn try_from(c: CacheConfig) -> ZResult {
+ let max_samples: usize = c
+ .max_samples
+ .try_into()
+ .map_err(|e: std::num::TryFromIntError| zerror!("CacheConfig.max_samples: {}", e))?;
+ let replies = zenoh_ext::RepliesConfig::default()
+ .priority(c.replies_priority)
+ .congestion_control(c.replies_congestion_control)
+ .express(c.replies_is_express);
+ Ok(zenoh_ext::CacheConfig::default()
+ .max_samples(max_samples)
+ .replies_config(replies))
+ }
+}
+
+impl TryFrom for zenoh_ext::HistoryConfig {
+ type Error = ZError;
+
+ fn try_from(c: HistoryConfig) -> ZResult {
+ let mut cfg = zenoh_ext::HistoryConfig::default();
+ if c.detect_late_publishers {
+ cfg = cfg.detect_late_publishers();
+ }
+ if c.max_samples > 0 {
+ let n: usize = c.max_samples.try_into().map_err(
+ |e: std::num::TryFromIntError| zerror!("HistoryConfig.max_samples: {}", e),
+ )?;
+ cfg = cfg.max_samples(n);
+ }
+ if c.max_age_seconds > 0.0 {
+ cfg = cfg.max_age(c.max_age_seconds);
+ }
+ Ok(cfg)
+ }
+}
+
+impl TryFrom for zenoh_ext::MissDetectionConfig {
+ type Error = ZError;
+
+ fn try_from(c: MissDetectionConfig) -> ZResult {
+ let mut cfg = zenoh_ext::MissDetectionConfig::default();
+ if c.enable_heartbeat {
+ let period_ms: u64 = c.period_ms.try_into().map_err(
+ |e: std::num::TryFromIntError| zerror!("MissDetectionConfig.period_ms: {}", e),
+ )?;
+ let dur = Duration::from_millis(period_ms);
+ cfg = if c.is_sporadic {
+ cfg.sporadic_heartbeat(dur)
+ } else {
+ cfg.heartbeat(dur)
+ };
+ }
+ Ok(cfg)
+ }
+}
+
+impl TryFrom for zenoh_ext::RecoveryConfig {
+ type Error = ZError;
+
+ fn try_from(c: RecoveryConfig) -> ZResult {
+ if c.is_heartbeat {
+ Ok(zenoh_ext::RecoveryConfig::default().heartbeat())
+ } else {
+ let period_ms: u64 = c.period_ms.try_into().map_err(
+ |e: std::num::TryFromIntError| zerror!("RecoveryConfig.period_ms: {}", e),
+ )?;
+ Ok(zenoh_ext::RecoveryConfig::default().periodic_queries(Duration::from_millis(period_ms)))
+ }
+ }
+}
diff --git a/zenoh-flat/src/lib.rs b/zenoh-flat/src/lib.rs
new file mode 100644
index 00000000..1c0d5ca4
--- /dev/null
+++ b/zenoh-flat/src/lib.rs
@@ -0,0 +1,13 @@
+//! `zenoh-flat` is a placeholder Rust crate for Zenoh flat data support.
+
+pub const PREBINDGEN_OUT_DIR: &str = prebindgen_proc_macro::prebindgen_out_dir!();
+pub const FEATURES: &str = prebindgen_proc_macro::features!();
+
+pub mod config;
+pub mod errors;
+#[cfg(feature = "zenoh-ext")]
+pub mod ext;
+pub mod session;
+
+pub use prebindgen_ext::{jni_converter, jni_type_binding};
+
diff --git a/zenoh-flat/src/session.rs b/zenoh-flat/src/session.rs
new file mode 100644
index 00000000..e4498d87
--- /dev/null
+++ b/zenoh-flat/src/session.rs
@@ -0,0 +1,508 @@
+// 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, zerror};
+use tracing::{error, trace};
+use std::time::Duration;
+
+use zenoh::{
+ bytes::Encoding,
+ config::Config,
+ key_expr::KeyExpr,
+ pubsub::{Publisher, Subscriber},
+ query::{ConsolidationMode, Query, QueryTarget, Queryable, Querier, Reply, ReplyKeyExpr, Selector},
+ sample::Sample,
+ qos::{CongestionControl, Priority, Reliability},
+ session::{Session, ZenohId},
+ Wait,
+};
+
+#[cfg(feature = "zenoh-ext")]
+use crate::ext::{CacheConfig, HistoryConfig, MissDetectionConfig, RecoveryConfig};
+#[cfg(feature = "zenoh-ext")]
+use zenoh_ext::{
+ AdvancedPublisher, AdvancedPublisherBuilderExt, AdvancedSubscriber,
+ AdvancedSubscriberBuilderExt,
+};
+
+/// Fires `f` exactly once when dropped. Used to bind an `on_close` callback
+/// to the lifetime of a data callback closure: when zenoh drops the data
+/// closure (subscription/queryable/get teardown), the guard's `Drop` runs
+/// `on_close`.
+struct CallOnDrop(core::mem::MaybeUninit);
+impl CallOnDrop {
+ fn new(f: F) -> Self {
+ Self(core::mem::MaybeUninit::new(f))
+ }
+}
+impl Drop for CallOnDrop {
+ fn drop(&mut self) {
+ let f = unsafe { self.0.assume_init_read() };
+ f();
+ }
+}
+
+/// Open a Zenoh session using a borrowed configuration.
+#[prebindgen_proc_macro::prebindgen("jni")]
+pub fn open_session(config: &Config) -> ZResult {
+ zenoh::open(config.clone())
+ .wait()
+ .map(|session| {
+ trace!("Opened Zenoh session.");
+ session
+ })
+ .map_err(|err| {
+ error!("Unable to open session: {}", err);
+ zerror!(err)
+ })
+}
+
+/// Declare a publisher through an existing Zenoh session.
+#[prebindgen_proc_macro::prebindgen("jni")]
+pub fn declare_publisher(
+ session: &Session,
+ key_expr: KeyExpr<'static>,
+ congestion_control: CongestionControl,
+ priority: Priority,
+ express: bool,
+ reliability: Reliability,
+) -> ZResult> {
+ let key_expr_string = key_expr.to_string();
+ session
+ .declare_publisher(key_expr)
+ .congestion_control(congestion_control)
+ .priority(priority)
+ .express(express)
+ .reliability(reliability)
+ .wait()
+ .map(|publisher| {
+ trace!("Declared publisher on '{}'.", key_expr_string);
+ publisher
+ })
+ .map_err(|err| {
+ error!("Unable to declare publisher on '{}': {}", key_expr_string, err);
+ zerror!(err)
+ })
+}
+
+/// Declare a key expression through an existing Zenoh session.
+#[prebindgen_proc_macro::prebindgen("jni")]
+pub fn declare_key_expr(session: &Session, key_expr: String) -> ZResult> {
+ let key_expr_clone = key_expr.clone();
+ session
+ .declare_keyexpr(key_expr)
+ .wait()
+ .map(|ke| {
+ trace!("Declared key expression '{}'.", key_expr_clone);
+ ke
+ })
+ .map_err(|err| {
+ error!(
+ "Unable to declare key expression '{}': {}",
+ key_expr_clone, err
+ );
+ zerror!(
+ "Unable to declare key expression '{}': {}",
+ key_expr_clone,
+ err
+ )
+ })
+}
+
+/// Undeclare a previously-declared key expression on a Zenoh session.
+///
+/// Takes the `KeyExpr` by value so the caller can relinquish ownership. After
+/// this call the original JNI-side raw pointer is invalid.
+#[prebindgen_proc_macro::prebindgen("jni")]
+pub fn undeclare_key_expr(session: &Session, key_expr: KeyExpr<'static>) -> ZResult<()> {
+ let key_expr_string = key_expr.to_string();
+ session
+ .undeclare(key_expr)
+ .wait()
+ .map(|_| {
+ trace!("Undeclared key expression '{}'.", key_expr_string);
+ })
+ .map_err(|err| {
+ error!(
+ "Unable to undeclare key expression '{}': {}",
+ key_expr_string, err
+ );
+ zerror!(
+ "Unable to undeclare key expression '{}': {}",
+ key_expr_string,
+ err
+ )
+ })
+}
+
+/// Declare a subscriber through an existing Zenoh session.
+#[prebindgen_proc_macro::prebindgen("jni")]
+pub fn declare_subscriber(
+ session: &Session,
+ key_expr: KeyExpr<'static>,
+ callback: impl Fn(Sample) + Send + Sync + 'static,
+ on_close: impl Fn() + Send + Sync + 'static,
+) -> ZResult> {
+ let key_expr_string = key_expr.to_string();
+ let guard = CallOnDrop::new(on_close);
+ session
+ .declare_subscriber(key_expr)
+ .callback(move |sample| {
+ let _ = &guard; // capture the guard
+ callback(sample);
+ })
+ .wait()
+ .map(|subscriber| {
+ trace!("Declared subscriber on '{}'.", key_expr_string);
+ subscriber
+ })
+ .map_err(|err| {
+ error!("Unable to declare subscriber on '{}': {}", key_expr_string, err);
+ zerror!(err)
+ })
+}
+
+/// Declare a querier through an existing Zenoh session.
+#[prebindgen_proc_macro::prebindgen("jni")]
+pub fn declare_querier(
+ session: &Session,
+ key_expr: KeyExpr<'static>,
+ query_target: QueryTarget,
+ consolidation: ConsolidationMode,
+ congestion_control: CongestionControl,
+ priority: Priority,
+ express: bool,
+ timeout: Duration,
+ reply_key_expr: ReplyKeyExpr,
+) -> ZResult> {
+ let key_expr_string = key_expr.to_string();
+ session
+ .declare_querier(key_expr)
+ .congestion_control(congestion_control)
+ .consolidation(consolidation)
+ .express(express)
+ .target(query_target)
+ .priority(priority)
+ .timeout(timeout)
+ .accept_replies(reply_key_expr)
+ .wait()
+ .map(|querier| {
+ trace!("Declared querier on '{}'.", key_expr_string);
+ querier
+ })
+ .map_err(|err| {
+ error!("Unable to declare querier on '{}': {}", key_expr_string, err);
+ zerror!(err)
+ })
+}
+
+/// Declare a queryable through an existing Zenoh session.
+#[prebindgen_proc_macro::prebindgen("jni")]
+pub fn declare_queryable(
+ session: &Session,
+ key_expr: KeyExpr<'static>,
+ callback: impl Fn(Query) + Send + Sync + 'static,
+ on_close: impl Fn() + Send + Sync + 'static,
+ complete: bool,
+) -> ZResult> {
+ let key_expr_string = key_expr.to_string();
+ let guard = CallOnDrop::new(on_close);
+ session
+ .declare_queryable(key_expr)
+ .callback(move |query| {
+ let _ = &guard; // capture the guard
+ callback(query);
+ })
+ .complete(complete)
+ .wait()
+ .map(|queryable| {
+ trace!("Declared queryable on '{}'.", key_expr_string);
+ queryable
+ })
+ .map_err(|err| {
+ error!("Unable to declare queryable on '{}': {}", key_expr_string, err);
+ zerror!(err)
+ })
+}
+
+/// Perform a get (query) through an existing Zenoh session.
+///
+/// Each [`Reply`] received from the network is delivered to the `callback`.
+/// `selector_params` is appended to the `key_expr` to form the query selector
+/// (pass `None` for no parameters). `payload` and `encoding` are coupled:
+/// if a payload is given, encoding is attached; if no payload is given,
+/// encoding is ignored.
+///
+/// Parameter order matches the JNI calling convention so the wrapper can be
+/// generated by `zenoh_flat::jni_converter` without reordering.
+#[prebindgen_proc_macro::prebindgen("jni")]
+pub fn get(
+ session: &Session,
+ key_expr: KeyExpr<'static>,
+ selector_params: Option,
+ callback: impl Fn(Reply) + Send + Sync + 'static,
+ on_close: impl Fn() + Send + Sync + 'static,
+ timeout: Duration,
+ query_target: QueryTarget,
+ consolidation: ConsolidationMode,
+ attachment: Option>,
+ payload: Option>,
+ encoding: Option,
+ congestion_control: CongestionControl,
+ priority: Priority,
+ express: bool,
+ reply_key_expr: ReplyKeyExpr,
+) -> ZResult<()> {
+ let key_expr_string = key_expr.to_string();
+ let selector = Selector::owned(&key_expr, selector_params.unwrap_or_default());
+ let guard = CallOnDrop::new(on_close);
+ let mut get_builder = session
+ .get(selector)
+ .callback(move |reply| {
+ let _ = &guard; // capture the guard
+ callback(reply);
+ })
+ .target(query_target)
+ .consolidation(consolidation)
+ .congestion_control(congestion_control)
+ .priority(priority)
+ .express(express)
+ .timeout(timeout)
+ .accept_replies(reply_key_expr);
+
+ if let Some(payload) = payload {
+ if let Some(encoding) = encoding {
+ get_builder = get_builder.encoding(encoding);
+ }
+ get_builder = get_builder.payload(payload);
+ }
+
+ if let Some(attachment) = attachment {
+ get_builder = get_builder.attachment::>(attachment);
+ }
+
+ get_builder
+ .wait()
+ .map(|_| {
+ trace!("Performing get on '{}'.", key_expr_string);
+ })
+ .map_err(|err| {
+ error!("Unable to perform get on '{}': {}", key_expr_string, err);
+ zerror!(err)
+ })
+}
+
+/// Perform a put operation through an existing Zenoh session.
+#[prebindgen_proc_macro::prebindgen("jni")]
+pub fn put(
+ session: &Session,
+ key_expr: KeyExpr<'static>,
+ payload: Vec,
+ encoding: Encoding,
+ congestion_control: CongestionControl,
+ priority: Priority,
+ express: bool,
+ attachment: Option>,
+ reliability: Reliability,
+) -> ZResult<()> {
+ let key_expr_string = key_expr.to_string();
+ let mut put_builder = session
+ .put(&key_expr, payload)
+ .congestion_control(congestion_control)
+ .encoding(encoding)
+ .express(express)
+ .priority(priority)
+ .reliability(reliability);
+
+ if let Some(attachment) = attachment {
+ put_builder = put_builder.attachment(attachment);
+ }
+
+ put_builder
+ .wait()
+ .map(|_| {
+ trace!("Put on '{}'.", key_expr_string);
+ })
+ .map_err(|err| {
+ error!("Unable to put on '{}': {}", key_expr_string, err);
+ zerror!(err)
+ })
+}
+
+/// Perform a delete operation through an existing Zenoh session.
+#[prebindgen_proc_macro::prebindgen("jni")]
+pub fn delete(
+ session: &Session,
+ key_expr: KeyExpr<'static>,
+ congestion_control: CongestionControl,
+ priority: Priority,
+ express: bool,
+ attachment: Option>,
+ reliability: Reliability,
+) -> ZResult<()> {
+ let key_expr_string = key_expr.to_string();
+ let mut delete_builder = session
+ .delete(&key_expr)
+ .congestion_control(congestion_control)
+ .express(express)
+ .priority(priority)
+ .reliability(reliability);
+
+ if let Some(attachment) = attachment {
+ delete_builder = delete_builder.attachment(attachment);
+ }
+
+ delete_builder
+ .wait()
+ .map(|_| {
+ trace!("Delete on '{}'.", key_expr_string);
+ })
+ .map_err(|err| {
+ error!("Unable to delete on '{}': {}", key_expr_string, err);
+ zerror!(err)
+ })
+}
+
+/// Return the Zenoh ID of the session.
+#[prebindgen_proc_macro::prebindgen("jni")]
+pub fn get_zid(session: &Session) -> ZResult {
+ Ok(session.info().zid().wait())
+}
+
+/// Return the Zenoh IDs of the peers connected to this session.
+#[prebindgen_proc_macro::prebindgen("jni")]
+pub fn get_peers_zid(session: &Session) -> ZResult> {
+ Ok(session.info().peers_zid().wait().collect())
+}
+
+/// Return the Zenoh IDs of the routers connected to this session.
+#[prebindgen_proc_macro::prebindgen("jni")]
+pub fn get_routers_zid(session: &Session) -> ZResult> {
+ Ok(session.info().routers_zid().wait().collect())
+}
+
+/// Close a Zenoh session using a reference to the session.
+#[prebindgen_proc_macro::prebindgen("jni")]
+pub fn close_session(session: &Session) -> ZResult<()> {
+ session
+ .close()
+ .wait()
+ .map(|_| {
+ trace!("Closed Zenoh session.");
+ })
+ .map_err(|err| {
+ error!("Unable to close session: {}", err);
+ zerror!(err)
+ })
+}
+
+/// Declare an advanced subscriber through an existing Zenoh session.
+///
+/// Builds on top of the regular subscriber chain and applies the supplied
+/// advanced configuration. The JNI wrapper is expected to assemble the
+/// `HistoryConfig` / `RecoveryConfig` from primitive arguments and pass them
+/// here.
+#[cfg(feature = "zenoh-ext")]
+#[prebindgen_proc_macro::prebindgen("jni")]
+pub fn declare_advanced_subscriber(
+ session: &Session,
+ key_expr: KeyExpr<'static>,
+ callback: impl Fn(Sample) + Send + Sync + 'static,
+ on_close: impl Fn() + Send + Sync + 'static,
+ history: Option,
+ recovery: Option,
+ subscriber_detection: bool,
+) -> ZResult> {
+ let key_expr_string = key_expr.to_string();
+ let guard = CallOnDrop::new(on_close);
+ let mut builder = session
+ .declare_subscriber(key_expr)
+ .callback(move |sample| {
+ let _ = &guard; // capture the guard
+ callback(sample);
+ })
+ .advanced();
+ if let Some(history) = history {
+ builder = builder.history(history.try_into()?);
+ }
+ if let Some(recovery) = recovery {
+ builder = builder.recovery(recovery.try_into()?);
+ }
+ if subscriber_detection {
+ builder = builder.subscriber_detection();
+ }
+ builder
+ .wait()
+ .map(|subscriber| {
+ trace!("Declared advanced subscriber on '{}'.", key_expr_string);
+ subscriber
+ })
+ .map_err(|err| {
+ error!(
+ "Unable to declare advanced subscriber on '{}': {}",
+ key_expr_string, err
+ );
+ zerror!(err)
+ })
+}
+
+/// Declare an advanced publisher through an existing Zenoh session.
+///
+/// Builds on top of the regular publisher chain and applies the supplied
+/// advanced configuration. The JNI wrapper is expected to assemble the
+/// `CacheConfig` / `MissDetectionConfig` from primitive arguments and pass
+/// them here.
+#[cfg(feature = "zenoh-ext")]
+#[prebindgen_proc_macro::prebindgen("jni")]
+pub fn declare_advanced_publisher(
+ session: &Session,
+ key_expr: KeyExpr<'static>,
+ congestion_control: CongestionControl,
+ priority: Priority,
+ express: bool,
+ reliability: Reliability,
+ cache: Option,
+ sample_miss_detection: Option,
+ publisher_detection: bool,
+) -> ZResult> {
+ let key_expr_string = key_expr.to_string();
+ let mut builder = session
+ .declare_publisher(key_expr)
+ .congestion_control(congestion_control)
+ .priority(priority)
+ .express(express)
+ .reliability(reliability)
+ .advanced();
+ if let Some(cache) = cache {
+ builder = builder.cache(cache.try_into()?);
+ }
+ if let Some(miss_detection) = sample_miss_detection {
+ builder = builder.sample_miss_detection(miss_detection.try_into()?);
+ }
+ if publisher_detection {
+ builder = builder.publisher_detection();
+ }
+ builder
+ .wait()
+ .map(|publisher| {
+ trace!("Declared advanced publisher on '{}'.", key_expr_string);
+ publisher
+ })
+ .map_err(|err| {
+ error!(
+ "Unable to declare advanced publisher on '{}': {}",
+ key_expr_string, err
+ );
+ zerror!(err)
+ })
+}
diff --git a/zenoh-java/build.gradle.kts b/zenoh-java/build.gradle.kts
index 123d7499..dfb36284 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,13 +28,11 @@ 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
+var buildMode = if (release) "release" else "debug"
if (androidEnabled) {
apply(plugin = "com.android.library")
- apply(plugin = "org.mozilla.rust-android-gradle.rust-android")
- configureCargo()
configureAndroid()
}
@@ -66,34 +62,35 @@ kotlin {
sourceSets {
val commonMain by getting {
dependencies {
+ api(project(":zenoh-jni-runtime"))
implementation("commons-net:commons-net:3.9.0")
implementation("com.google.guava:guava:33.3.1-jre")
}
}
+ // jvmAndAndroidMain is an intermediate source set between commonMain and both jvmMain/androidMain.
+ // ZSerializer and ZDeserializer use java.lang.reflect.Type (via Guava's TypeToken) —
+ // available on JVM and Android (ART), but absent on Kotlin/Native and Kotlin/JS targets.
+ // Placing them here (rather than commonMain) ensures those targets can be added in the
+ // future without compilation errors.
+ // Note: named jvmAndAndroidMain (not javaMain) to avoid conflicting with the DokkaSourceSetSpec
+ // that Dokka auto-registers for the Java compilation created by withJava().
+ val jvmAndAndroidMain by creating { dependsOn(commonMain) }
val commonTest by getting {
dependencies {
implementation(kotlin("test"))
}
}
if (androidEnabled) {
+ val androidMain by getting { dependsOn(jvmAndAndroidMain) }
val androidUnitTest by getting {
dependencies {
implementation(kotlin("test-junit"))
}
}
}
- val jvmMain by getting {
- if (isRemotePublication) {
- // The line below is intended to load the native libraries that are crosscompiled on GitHub actions when publishing a JVM package.
- resources.srcDir("../jni-libs").include("*/**")
- } else {
- resources.srcDir("../zenoh-jni/target/$buildMode").include(arrayListOf("*.dylib", "*.so", "*.dll"))
- }
- }
+ val jvmMain by getting { dependsOn(jvmAndAndroidMain) }
- val jvmTest by getting {
- resources.srcDir("../zenoh-jni/target/$buildMode").include(arrayListOf("*.dylib", "*.so", "*.dll"))
- }
+ val jvmTest by getting {}
}
val javadocJar by tasks.registering(Jar::class) {
@@ -161,55 +158,8 @@ tasks.withType {
}
}
-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"
- }
- }
+ dependsOn(":zenoh-jni-runtime:buildZenohJni")
}
fun Project.configureAndroid() {
@@ -249,20 +199,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/commonMain/kotlin/io/zenoh/Config.kt b/zenoh-java/src/commonMain/kotlin/io/zenoh/Config.kt
index 91bc0e01..a2a334cc 100644
--- a/zenoh-java/src/commonMain/kotlin/io/zenoh/Config.kt
+++ b/zenoh-java/src/commonMain/kotlin/io/zenoh/Config.kt
@@ -46,7 +46,7 @@ class Config internal constructor(internal val jniConfig: JNIConfig) {
*/
@JvmStatic
fun loadDefault(): Config {
- return JNIConfig.loadDefaultConfig()
+ return Config(JNIConfig.loadDefault())
}
/**
@@ -59,7 +59,7 @@ class Config internal constructor(internal val jniConfig: JNIConfig) {
@JvmStatic
@Throws(ZError::class)
fun fromFile(file: File): Config {
- return JNIConfig.loadConfigFile(file)
+ return Config(JNIConfig.loadFromFile(file.toString()))
}
/**
@@ -72,7 +72,7 @@ class Config internal constructor(internal val jniConfig: JNIConfig) {
@JvmStatic
@Throws(ZError::class)
fun fromFile(path: Path): Config {
- return JNIConfig.loadConfigFile(path)
+ return Config(JNIConfig.loadFromFile(path.toString()))
}
/**
@@ -87,7 +87,7 @@ class Config internal constructor(internal val jniConfig: JNIConfig) {
@JvmStatic
@Throws(ZError::class)
fun fromJson(config: String): Config {
- return JNIConfig.loadJsonConfig(config)
+ return Config(JNIConfig.loadFromJson(config))
}
/**
@@ -102,7 +102,7 @@ class Config internal constructor(internal val jniConfig: JNIConfig) {
@JvmStatic
@Throws(ZError::class)
fun fromJson5(config: String): Config {
- return JNIConfig.loadJson5Config(config)
+ return Config(JNIConfig.loadFromJson(config))
}
/**
@@ -117,7 +117,7 @@ class Config internal constructor(internal val jniConfig: JNIConfig) {
@JvmStatic
@Throws(ZError::class)
fun fromYaml(config: String): Config {
- return JNIConfig.loadYamlConfig(config)
+ return Config(JNIConfig.loadFromYaml(config))
}
/**
diff --git a/zenoh-java/src/commonMain/kotlin/io/zenoh/Logger.kt b/zenoh-java/src/commonMain/kotlin/io/zenoh/Logger.kt
index eba6681c..1b6101bf 100644
--- a/zenoh-java/src/commonMain/kotlin/io/zenoh/Logger.kt
+++ b/zenoh-java/src/commonMain/kotlin/io/zenoh/Logger.kt
@@ -15,6 +15,7 @@
package io.zenoh
import io.zenoh.exceptions.ZError
+import io.zenoh.jni.JNILogger
/** Logger class to redirect the Rust logs from Zenoh to the kotlin environment. */
internal class Logger {
@@ -23,11 +24,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 +31,6 @@ 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) = JNILogger.startLogs(filter)
}
}
diff --git a/zenoh-java/src/commonMain/kotlin/io/zenoh/Session.kt b/zenoh-java/src/commonMain/kotlin/io/zenoh/Session.kt
index d3b125f9..e19b102c 100644
--- a/zenoh-java/src/commonMain/kotlin/io/zenoh/Session.kt
+++ b/zenoh-java/src/commonMain/kotlin/io/zenoh/Session.kt
@@ -15,23 +15,40 @@
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.EntityGlobalId
import io.zenoh.config.ZenohId
import io.zenoh.exceptions.ZError
import io.zenoh.handlers.BlockingQueueHandler
import io.zenoh.handlers.Callback
import io.zenoh.handlers.Handler
+import io.zenoh.jni.JNIKeyExpr
+import io.zenoh.jni.JNIPublisher
+import io.zenoh.jni.JNIQuery
+import io.zenoh.jni.JNIQuerier
+import io.zenoh.jni.JNIQueryable
import io.zenoh.jni.JNISession
+import io.zenoh.jni.JNISubscriber
+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.liveliness.Liveliness
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.query.Query
import io.zenoh.query.Queryable
import io.zenoh.sample.Sample
+import io.zenoh.sample.SampleKind
import io.zenoh.session.SessionDeclaration
import io.zenoh.session.SessionInfo
+import org.apache.commons.net.ntp.TimeStamp
import java.lang.ref.WeakReference
import java.util.*
import java.util.concurrent.BlockingQueue
@@ -383,9 +400,9 @@ 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
+ val ke = KeyExpr(keyExpr, declareKeyExpr(keyExpr))
+ strongDeclarations.add(ke)
+ ke
} ?: throw sessionClosedException
}
@@ -400,8 +417,11 @@ class Session private constructor(private val config: Config) : AutoCloseable {
*/
@Throws(ZError::class)
fun undeclare(keyExpr: KeyExpr) {
- return jniSession?.run {
- undeclareKeyExpr(keyExpr)
+ jniSession?.run {
+ keyExpr.jniKeyExpr?.run {
+ undeclareKeyExpr(this)
+ keyExpr.jniKeyExpr = null
+ } ?: throw ZError("Attempting to undeclare a non declared key expression.")
} ?: throw (sessionClosedException)
}
@@ -576,7 +596,20 @@ 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)
+ val publisher = Publisher(
+ keyExpr,
+ options.congestionControl,
+ options.priority,
+ options.encoding,
+ declarePublisher(
+ keyExpr.jniKeyExpr,
+ keyExpr.keyExpr,
+ options.congestionControl.value,
+ options.priority.value,
+ options.express,
+ options.reliability.ordinal
+ )
+ )
weakDeclarations.add(WeakReference(publisher))
publisher
} ?: throw (sessionClosedException)
@@ -587,7 +620,22 @@ class Session private constructor(private val config: Config) : AutoCloseable {
keyExpr: KeyExpr, handler: Handler
): HandlerSubscriber {
return jniSession?.run {
- val subscriber = declareSubscriberWithHandler(keyExpr, handler)
+ val subCallback =
+ JNISubscriberCallback { keyExpr1, payload, encodingId, encodingSchema, kind, timestampNTP64, timestampIsValid, attachmentBytes, express, priority, congestionControl ->
+ val timestamp = if (timestampIsValid) TimeStamp(timestampNTP64) else null
+ handler.handle(
+ Sample(
+ KeyExpr(keyExpr1, null),
+ payload.into(),
+ Encoding(encodingId, schema = encodingSchema),
+ SampleKind.fromInt(kind),
+ timestamp,
+ QoS(CongestionControl.fromInt(congestionControl), Priority.fromInt(priority), express),
+ attachmentBytes?.into()
+ )
+ )
+ }
+ val subscriber = HandlerSubscriber(keyExpr, declareSubscriber(keyExpr.jniKeyExpr, keyExpr.keyExpr, subCallback, handler::onClose), handler.receiver())
strongDeclarations.add(subscriber)
subscriber
} ?: throw (sessionClosedException)
@@ -598,7 +646,22 @@ class Session private constructor(private val config: Config) : AutoCloseable {
keyExpr: KeyExpr, callback: Callback
): CallbackSubscriber {
return jniSession?.run {
- val subscriber = declareSubscriberWithCallback(keyExpr, callback)
+ val subCallback =
+ JNISubscriberCallback { keyExpr1, payload, encodingId, encodingSchema, kind, timestampNTP64, timestampIsValid, attachmentBytes, express, priority, congestionControl ->
+ val timestamp = if (timestampIsValid) TimeStamp(timestampNTP64) else null
+ callback.run(
+ Sample(
+ KeyExpr(keyExpr1, null),
+ payload.into(),
+ Encoding(encodingId, schema = encodingSchema),
+ SampleKind.fromInt(kind),
+ timestamp,
+ QoS(CongestionControl.fromInt(congestionControl), Priority.fromInt(priority), express),
+ attachmentBytes?.into()
+ )
+ )
+ }
+ val subscriber = CallbackSubscriber(keyExpr, declareSubscriber(keyExpr.jniKeyExpr, keyExpr.keyExpr, subCallback, fun() {}))
strongDeclarations.add(subscriber)
subscriber
} ?: throw (sessionClosedException)
@@ -609,7 +672,24 @@ class Session private constructor(private val config: Config) : AutoCloseable {
keyExpr: KeyExpr, handler: Handler, options: QueryableOptions
): HandlerQueryable {
return jniSession?.run {
- val queryable = declareQueryableWithHandler(keyExpr, handler, options)
+ val queryCallback =
+ JNIQueryableCallback { keyExpr1, selectorParams, payload, encodingId, encodingSchema, attachmentBytes, queryPtr, acceptReplies ->
+ val jniQuery = JNIQuery(queryPtr)
+ val keyExpr2 = KeyExpr(keyExpr1, null)
+ val selector = if (selectorParams.isEmpty()) Selector(keyExpr2) else Selector(keyExpr2, Parameters.from(selectorParams))
+ handler.handle(
+ Query(
+ keyExpr2,
+ selector,
+ payload?.into(),
+ payload?.let { Encoding(encodingId, schema = encodingSchema) },
+ attachmentBytes?.into(),
+ ReplyKeyExpr.entries[acceptReplies],
+ jniQuery
+ )
+ )
+ }
+ val queryable = HandlerQueryable(keyExpr, declareQueryable(keyExpr.jniKeyExpr, keyExpr.keyExpr, queryCallback, handler::onClose, options.complete), handler.receiver())
strongDeclarations.add(queryable)
queryable
} ?: throw (sessionClosedException)
@@ -620,18 +700,50 @@ class Session private constructor(private val config: Config) : AutoCloseable {
keyExpr: KeyExpr, callback: Callback, options: QueryableOptions
): CallbackQueryable {
return jniSession?.run {
- val queryable = declareQueryableWithCallback(keyExpr, callback, options)
+ val queryCallback =
+ JNIQueryableCallback { keyExpr1, selectorParams, payload, encodingId, encodingSchema, attachmentBytes, queryPtr, acceptReplies ->
+ val jniQuery = JNIQuery(queryPtr)
+ val keyExpr2 = KeyExpr(keyExpr1, null)
+ val selector = if (selectorParams.isEmpty()) Selector(keyExpr2) else Selector(keyExpr2, Parameters.from(selectorParams))
+ callback.run(
+ Query(
+ keyExpr2,
+ selector,
+ payload?.into(),
+ payload?.let { Encoding(encodingId, schema = encodingSchema) },
+ attachmentBytes?.into(),
+ ReplyKeyExpr.entries[acceptReplies],
+ jniQuery
+ )
+ )
+ }
+ val queryable = CallbackQueryable(keyExpr, declareQueryable(keyExpr.jniKeyExpr, keyExpr.keyExpr, queryCallback, fun() {}, options.complete))
strongDeclarations.add(queryable)
queryable
} ?: throw (sessionClosedException)
}
+ @OptIn(Unstable::class)
private fun resolveQuerier(
keyExpr: KeyExpr,
options: QuerierOptions
): Querier {
return jniSession?.run {
- val querier = declareQuerier(keyExpr, options)
+ val querier = Querier(
+ keyExpr,
+ QoS(congestionControl = options.congestionControl, priority = options.priority, express = options.express),
+ declareQuerier(
+ keyExpr.jniKeyExpr,
+ keyExpr.keyExpr,
+ options.target.ordinal,
+ options.consolidationMode.ordinal,
+ options.congestionControl.value,
+ options.priority.value,
+ options.express,
+ options.timeout.toMillis(),
+ options.acceptReplies.ordinal
+ )
+ )
weakDeclarations.add(WeakReference(querier))
querier
} ?: throw sessionClosedException
@@ -643,11 +755,47 @@ class Session private constructor(private val config: Config) : AutoCloseable {
handler: Handler,
options: GetOptions
): R {
- return jniSession?.performGetWithHandler(
- selector,
- handler,
- options
- ) ?: throw sessionClosedException
+ return jniSession?.run {
+ val getCallback = JNIGetCallback { replierZid, replierEid, success, keyExpr, payload, encodingId, encodingSchema, kind, timestampNTP64, timestampIsValid, attachmentBytes, express, priority, congestionControl ->
+ val reply: Reply = if (success) {
+ val timestamp = if (timestampIsValid) TimeStamp(timestampNTP64) else null
+ Reply.Success(
+ replierZid?.let { EntityGlobalId(ZenohId(it), replierEid.toUInt()) },
+ Sample(
+ KeyExpr(keyExpr!!, null),
+ payload.into(),
+ Encoding(encodingId, schema = encodingSchema),
+ SampleKind.fromInt(kind),
+ timestamp,
+ QoS(CongestionControl.fromInt(congestionControl), Priority.fromInt(priority), express),
+ attachmentBytes?.into()
+ )
+ )
+ } else {
+ Reply.Error(replierZid?.let { EntityGlobalId(ZenohId(it), replierEid.toUInt()) }, payload.into(), Encoding(encodingId, schema = encodingSchema))
+ }
+ handler.handle(reply)
+ }
+ val sel = selector.into()
+ get(
+ sel.keyExpr.jniKeyExpr,
+ sel.keyExpr.keyExpr,
+ sel.parameters?.toString(),
+ getCallback,
+ handler::onClose,
+ options.timeout.toMillis(),
+ options.target.ordinal,
+ options.consolidation.ordinal,
+ options.attachment?.into()?.bytes,
+ options.payload?.into()?.bytes,
+ options.encoding?.toJni(),
+ options.qos.congestionControl.value,
+ options.qos.priority.value,
+ options.qos.express,
+ options.acceptReplies.ordinal
+ )
+ handler.receiver()
+ } ?: throw sessionClosedException
}
@Throws(ZError::class)
@@ -656,42 +804,100 @@ class Session private constructor(private val config: Config) : AutoCloseable {
callback: Callback,
options: GetOptions
) {
- return jniSession?.performGetWithCallback(
- selector,
- callback,
- options
- ) ?: throw sessionClosedException
+ jniSession?.run {
+ val getCallback = JNIGetCallback { replierZid, replierEid, success, keyExpr, payload, encodingId, encodingSchema, kind, timestampNTP64, timestampIsValid, attachmentBytes, express, priority, congestionControl ->
+ val reply: Reply = if (success) {
+ val timestamp = if (timestampIsValid) TimeStamp(timestampNTP64) else null
+ Reply.Success(
+ replierZid?.let { EntityGlobalId(ZenohId(it), replierEid.toUInt()) },
+ Sample(
+ KeyExpr(keyExpr!!, null),
+ payload.into(),
+ Encoding(encodingId, schema = encodingSchema),
+ SampleKind.fromInt(kind),
+ timestamp,
+ QoS(CongestionControl.fromInt(congestionControl), Priority.fromInt(priority), express),
+ attachmentBytes?.into()
+ )
+ )
+ } else {
+ Reply.Error(replierZid?.let { EntityGlobalId(ZenohId(it), replierEid.toUInt()) }, payload.into(), Encoding(encodingId, schema = encodingSchema))
+ }
+ callback.run(reply)
+ }
+ val sel = selector.into()
+ get(
+ sel.keyExpr.jniKeyExpr,
+ sel.keyExpr.keyExpr,
+ sel.parameters?.toString(),
+ getCallback,
+ fun() {},
+ options.timeout.toMillis(),
+ options.target.ordinal,
+ options.consolidation.ordinal,
+ options.attachment?.into()?.bytes,
+ options.payload?.into()?.bytes,
+ options.encoding?.toJni(),
+ options.qos.congestionControl.value,
+ options.qos.priority.value,
+ options.qos.express,
+ options.acceptReplies.ordinal
+ )
+ } ?: throw sessionClosedException
}
@Throws(ZError::class)
internal fun resolvePut(keyExpr: KeyExpr, payload: IntoZBytes, putOptions: PutOptions) {
- jniSession?.run { performPut(keyExpr, payload, putOptions) }
+ jniSession?.run {
+ val encoding = (putOptions.encoding ?: Encoding.defaultEncoding()).toJni()
+ put(
+ keyExpr.jniKeyExpr,
+ keyExpr.keyExpr,
+ payload.into().bytes,
+ encoding,
+ putOptions.congestionControl.value,
+ putOptions.priority.value,
+ putOptions.express,
+ putOptions.attachment?.into()?.bytes,
+ putOptions.reliability.ordinal
+ )
+ }
}
@Throws(ZError::class)
internal fun resolveDelete(keyExpr: KeyExpr, deleteOptions: DeleteOptions) {
- jniSession?.run { performDelete(keyExpr, deleteOptions) }
+ jniSession?.run {
+ delete(
+ keyExpr.jniKeyExpr,
+ keyExpr.keyExpr,
+ deleteOptions.congestionControl.value,
+ deleteOptions.priority.value,
+ deleteOptions.express,
+ deleteOptions.attachment?.into()?.bytes,
+ deleteOptions.reliability.ordinal
+ )
+ }
}
@Throws(ZError::class)
internal fun zid(): ZenohId {
- return jniSession?.zid() ?: throw sessionClosedException
+ return jniSession?.run { ZenohId(getZid()) } ?: throw sessionClosedException
}
@Throws(ZError::class)
internal fun getPeersId(): List {
- return jniSession?.peersZid() ?: throw sessionClosedException
+ return jniSession?.run { getPeersZid().map { ZenohId(it) } } ?: throw sessionClosedException
}
@Throws(ZError::class)
internal fun getRoutersId(): List {
- return jniSession?.routersZid() ?: throw sessionClosedException
+ return jniSession?.run { getRoutersZid().map { ZenohId(it) } } ?: throw sessionClosedException
}
/** Launches the session through the jni session, returning the [Session] on success. */
@Throws(ZError::class)
private fun launch(): Session {
- this.jniSession = JNISession.open(config)
+ this.jniSession = JNISession.open(config.jniConfig)
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..e0c60c6c 100644
--- a/zenoh-java/src/commonMain/kotlin/io/zenoh/Zenoh.kt
+++ b/zenoh-java/src/commonMain/kotlin/io/zenoh/Zenoh.kt
@@ -15,11 +15,14 @@
package io.zenoh
import io.zenoh.Logger.Companion.LOG_ENV
+import io.zenoh.config.WhatAmI
+import io.zenoh.config.ZenohId
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.jni.callbacks.JNIScoutCallback
import io.zenoh.scouting.*
import java.util.*
import java.util.concurrent.BlockingQueue
@@ -53,10 +56,11 @@ object Zenoh {
@Throws(ZError::class)
fun scout(scoutOptions: ScoutOptions = ScoutOptions()): HandlerScout>> {
val handler = BlockingQueueHandler(LinkedBlockingDeque>())
- return JNIScout.scoutWithHandler(
- scoutOptions.whatAmI, handler::handle, fun() { handler.onClose() },
- receiver = handler.receiver(), config = scoutOptions.config
- )
+ val scoutCallback = JNIScoutCallback { whatAmI, id, locators ->
+ handler.handle(Hello(WhatAmI.fromInt(whatAmI), ZenohId(id), locators))
+ }
+ val binaryWhatAmI = scoutOptions.whatAmI.map { it.value }.reduce { acc, it -> acc or it }
+ return HandlerScout(JNIScout.scout(binaryWhatAmI, scoutCallback, handler::onClose, scoutOptions.config?.jniConfig), handler.receiver())
}
/**
@@ -74,10 +78,11 @@ object Zenoh {
@JvmStatic
@Throws(ZError::class)
fun scout(handler: Handler, scoutOptions: ScoutOptions = ScoutOptions()): HandlerScout {
- return JNIScout.scoutWithHandler(
- scoutOptions.whatAmI, handler::handle, fun() { handler.onClose() },
- receiver = handler.receiver(), config = scoutOptions.config
- )
+ val scoutCallback = JNIScoutCallback { whatAmI, id, locators ->
+ handler.handle(Hello(WhatAmI.fromInt(whatAmI), ZenohId(id), locators))
+ }
+ val binaryWhatAmI = scoutOptions.whatAmI.map { it.value }.reduce { acc, it -> acc or it }
+ return HandlerScout(JNIScout.scout(binaryWhatAmI, scoutCallback, handler::onClose, scoutOptions.config?.jniConfig), handler.receiver())
}
/**
@@ -94,9 +99,11 @@ object Zenoh {
@JvmStatic
@Throws(ZError::class)
fun scout(callback: Callback, scoutOptions: ScoutOptions = ScoutOptions()): CallbackScout {
- return JNIScout.scoutWithCallback(
- scoutOptions.whatAmI, callback, config = scoutOptions.config
- )
+ val scoutCallback = JNIScoutCallback { whatAmI, id, locators ->
+ callback.run(Hello(WhatAmI.fromInt(whatAmI), ZenohId(id), locators))
+ }
+ val binaryWhatAmI = scoutOptions.whatAmI.map { it.value }.reduce { acc, it -> acc or it }
+ return CallbackScout(JNIScout.scout(binaryWhatAmI, scoutCallback, fun() {}, scoutOptions.config?.jniConfig))
}
/**
@@ -136,9 +143,3 @@ object Zenoh {
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..35995ff9 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,8 @@
package io.zenoh.bytes
+import io.zenoh.jni.JNIEncoding
+
/**
* Default encoding values used by Zenoh.
*
@@ -499,4 +501,7 @@ class Encoding private constructor(
override fun hashCode(): Int {
return id.hashCode()
}
+
+ /** Project this public [Encoding] into a JNI-boundary holder. */
+ internal fun toJni(): JNIEncoding = JNIEncoding(id, schema)
}
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..53304190 100644
--- a/zenoh-java/src/commonMain/kotlin/io/zenoh/config/ZenohId.kt
+++ b/zenoh-java/src/commonMain/kotlin/io/zenoh/config/ZenohId.kt
@@ -23,7 +23,7 @@ import kotlin.math.absoluteValue
data class ZenohId internal constructor(internal val bytes: ByteArray) {
override fun toString(): String {
- return JNIZenohId.toStringViaJNI(bytes)
+ return JNIZenohId.toString(bytes)
}
override fun equals(other: Any?): Boolean {
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/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/keyexpr/KeyExpr.kt b/zenoh-java/src/commonMain/kotlin/io/zenoh/keyexpr/KeyExpr.kt
index b5fe6110..bf0461a2 100644
--- a/zenoh-java/src/commonMain/kotlin/io/zenoh/keyexpr/KeyExpr.kt
+++ b/zenoh-java/src/commonMain/kotlin/io/zenoh/keyexpr/KeyExpr.kt
@@ -79,7 +79,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)
+ return KeyExpr(JNIKeyExpr.tryFrom(keyExpr))
}
/**
@@ -95,7 +95,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)
+ return KeyExpr(JNIKeyExpr.autocanonize(keyExpr))
}
}
@@ -106,7 +106,7 @@ class KeyExpr internal constructor(internal val keyExpr: String, internal var jn
*/
@Throws(ZError::class)
fun intersects(other: KeyExpr): Boolean {
- return JNIKeyExpr.intersects(this, other)
+ return JNIKeyExpr.intersects(jniKeyExpr, keyExpr, other.jniKeyExpr, other.keyExpr)
}
/**
@@ -116,7 +116,7 @@ class KeyExpr internal constructor(internal val keyExpr: String, internal var jn
*/
@Throws(ZError::class)
fun includes(other: KeyExpr): Boolean {
- return JNIKeyExpr.includes(this, other)
+ return JNIKeyExpr.includes(jniKeyExpr, keyExpr, other.jniKeyExpr, other.keyExpr)
}
/**
@@ -126,7 +126,7 @@ class KeyExpr internal constructor(internal val keyExpr: String, internal var jn
*/
@Throws(ZError::class)
fun relationTo(other: KeyExpr): SetIntersectionLevel {
- return JNIKeyExpr.relationTo(this, other)
+ return SetIntersectionLevel.fromInt(JNIKeyExpr.relationTo(jniKeyExpr, keyExpr, other.jniKeyExpr, other.keyExpr))
}
/**
@@ -135,7 +135,7 @@ class KeyExpr internal constructor(internal val keyExpr: String, internal var jn
*/
@Throws(ZError::class)
fun join(other: String): KeyExpr {
- return JNIKeyExpr.joinViaJNI(this, other)
+ return KeyExpr(JNIKeyExpr.join(jniKeyExpr, keyExpr, other))
}
/**
@@ -144,7 +144,7 @@ class KeyExpr internal constructor(internal val keyExpr: String, internal var jn
*/
@Throws(ZError::class)
fun concat(other: String): KeyExpr {
- return JNIKeyExpr.concatViaJNI(this, other)
+ return KeyExpr(JNIKeyExpr.concat(jniKeyExpr, keyExpr, other))
}
override fun toString(): String {
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..62aff6c5 100644
--- a/zenoh-java/src/commonMain/kotlin/io/zenoh/liveliness/Liveliness.kt
+++ b/zenoh-java/src/commonMain/kotlin/io/zenoh/liveliness/Liveliness.kt
@@ -15,17 +15,27 @@
package io.zenoh.liveliness
import io.zenoh.Session
+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.BlockingQueueHandler
import io.zenoh.handlers.Callback
import io.zenoh.handlers.Handler
-import io.zenoh.jni.JNILiveliness
+import io.zenoh.jni.callbacks.JNIGetCallback
+import io.zenoh.jni.callbacks.JNISubscriberCallback
import io.zenoh.keyexpr.KeyExpr
import io.zenoh.pubsub.CallbackSubscriber
import io.zenoh.pubsub.HandlerSubscriber
import io.zenoh.pubsub.Subscriber
+import io.zenoh.qos.CongestionControl
+import io.zenoh.qos.Priority
+import io.zenoh.qos.QoS
import io.zenoh.query.Reply
import io.zenoh.sample.Sample
+import io.zenoh.sample.SampleKind
+import org.apache.commons.net.ntp.TimeStamp
import java.time.Duration
import java.util.*
import java.util.concurrent.BlockingQueue
@@ -49,7 +59,7 @@ 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)
+ return LivelinessToken(jniSession.declareLivelinessToken(keyExpr.jniKeyExpr, keyExpr.keyExpr))
}
/**
@@ -66,14 +76,15 @@ class Liveliness internal constructor(private val session: Session) {
): BlockingQueue> {
val jniSession = session.jniSession ?: throw Session.sessionClosedException
val handler = BlockingQueueHandler(LinkedBlockingDeque())
- return JNILiveliness.get(
- jniSession,
- keyExpr,
- handler::handle,
- receiver = handler.receiver(),
- timeout,
- onClose = handler::onClose
+ val getCallback = buildGetCallback(handler::handle)
+ jniSession.livelinessGet(
+ keyExpr.jniKeyExpr,
+ keyExpr.keyExpr,
+ getCallback,
+ timeout.toMillis(),
+ handler::onClose
)
+ return handler.receiver()
}
/**
@@ -89,7 +100,13 @@ class Liveliness internal constructor(private val session: Session) {
keyExpr: KeyExpr, callback: Callback, timeout: Duration = Duration.ofMillis(10000)
) {
val jniSession = session.jniSession ?: throw Session.sessionClosedException
- return JNILiveliness.get(jniSession, keyExpr, callback, Unit, timeout, {})
+ jniSession.livelinessGet(
+ keyExpr.jniKeyExpr,
+ keyExpr.keyExpr,
+ buildGetCallback(callback),
+ timeout.toMillis(),
+ fun() {}
+ )
}
/**
@@ -106,17 +123,38 @@ class Liveliness internal constructor(private val session: Session) {
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
+ jniSession.livelinessGet(
+ keyExpr.jniKeyExpr,
+ keyExpr.keyExpr,
+ buildGetCallback(handler::handle),
+ timeout.toMillis(),
+ handler::onClose
)
+ return handler.receiver()
}
+ private fun buildGetCallback(callback: Callback): JNIGetCallback =
+ JNIGetCallback { replierZid, replierEid, success, keyExpr2, payload, encodingId, encodingSchema, kind, timestampNTP64, timestampIsValid, attachmentBytes, express, priority, congestionControl ->
+ val reply: Reply = if (success) {
+ val timestamp = if (timestampIsValid) TimeStamp(timestampNTP64) else null
+ Reply.Success(
+ replierZid?.let { EntityGlobalId(ZenohId(it), replierEid.toUInt()) },
+ Sample(
+ KeyExpr(keyExpr2!!, null),
+ payload.into(),
+ Encoding(encodingId, schema = encodingSchema),
+ SampleKind.fromInt(kind),
+ timestamp,
+ QoS(CongestionControl.fromInt(congestionControl), Priority.fromInt(priority), express),
+ attachmentBytes?.into()
+ )
+ )
+ } else {
+ Reply.Error(replierZid?.let { EntityGlobalId(ZenohId(it), replierEid.toUInt()) }, payload.into(), Encoding(encodingId, schema = encodingSchema))
+ }
+ callback.run(reply)
+ }
+
/**
* Create a [Subscriber] for liveliness changes matching the given key expression.
*
@@ -131,14 +169,8 @@ class Liveliness internal constructor(private val session: Session) {
): HandlerSubscriber>> {
val handler = BlockingQueueHandler(LinkedBlockingDeque())
val jniSession = session.jniSession ?: throw Session.sessionClosedException
- return JNILiveliness.declareSubscriber(
- jniSession,
- keyExpr,
- handler::handle,
- handler.receiver(),
- options.history,
- handler::onClose
- )
+ val subCallback = buildSubscriberCallback(handler::handle)
+ return HandlerSubscriber(keyExpr, jniSession.declareLivelinessSubscriber(keyExpr.jniKeyExpr, keyExpr.keyExpr, subCallback, options.history, handler::onClose), handler.receiver())
}
/**
@@ -156,13 +188,8 @@ class Liveliness internal constructor(private val session: Session) {
options: LivelinessSubscriberOptions = LivelinessSubscriberOptions()
): CallbackSubscriber {
val jniSession = session.jniSession ?: throw Session.sessionClosedException
- return JNILiveliness.declareSubscriber(
- jniSession,
- keyExpr,
- callback,
- options.history,
- fun() {}
- )
+ val subCallback = buildSubscriberCallback(callback)
+ return CallbackSubscriber(keyExpr, jniSession.declareLivelinessSubscriber(keyExpr.jniKeyExpr, keyExpr.keyExpr, subCallback, options.history, fun() {}))
}
/**
@@ -181,15 +208,25 @@ class Liveliness internal constructor(private val session: Session) {
options: LivelinessSubscriberOptions = LivelinessSubscriberOptions()
): HandlerSubscriber {
val jniSession = session.jniSession ?: throw Session.sessionClosedException
- return JNILiveliness.declareSubscriber(
- jniSession,
- keyExpr,
- handler::handle,
- handler.receiver(),
- options.history,
- handler::onClose
- )
+ val subCallback = buildSubscriberCallback(handler::handle)
+ return HandlerSubscriber(keyExpr, jniSession.declareLivelinessSubscriber(keyExpr.jniKeyExpr, keyExpr.keyExpr, subCallback, options.history, handler::onClose), handler.receiver())
}
+
+ private fun buildSubscriberCallback(callback: Callback): JNISubscriberCallback =
+ JNISubscriberCallback { keyExpr2, payload, encodingId, encodingSchema, kind, timestampNTP64, timestampIsValid, attachmentBytes, express, priority, congestionControl ->
+ val timestamp = if (timestampIsValid) TimeStamp(timestampNTP64) else null
+ callback.run(
+ Sample(
+ KeyExpr(keyExpr2, null),
+ payload.into(),
+ Encoding(encodingId, schema = encodingSchema),
+ SampleKind.fromInt(kind),
+ timestamp,
+ QoS(CongestionControl.fromInt(congestionControl), Priority.fromInt(priority), express),
+ attachmentBytes?.into()
+ )
+ )
+ }
}
/**
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..fee4ca14 100644
--- a/zenoh-java/src/commonMain/kotlin/io/zenoh/pubsub/Publisher.kt
+++ b/zenoh-java/src/commonMain/kotlin/io/zenoh/pubsub/Publisher.kt
@@ -79,13 +79,14 @@ 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
+ jniPublisher?.put(payload.into().bytes, encoding.toJni(), null) ?: throw publisherNotValid
}
/** 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
+ val enc = options.encoding ?: this.encoding
+ jniPublisher?.put(payload.into().bytes, enc.toJni(), options.attachment?.into()?.bytes) ?: throw publisherNotValid
}
/** Performs a PUT operation on the specified [keyExpr] with the specified [payload]. */
@@ -102,7 +103,7 @@ class Publisher internal constructor(
@JvmOverloads
@Throws(ZError::class)
fun delete(options: DeleteOptions = DeleteOptions()) {
- jniPublisher?.delete(options.attachment) ?: throw(publisherNotValid)
+ jniPublisher?.delete(options.attachment?.into()?.bytes) ?: throw(publisherNotValid)
}
/**
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..b7be5c16 100644
--- a/zenoh-java/src/commonMain/kotlin/io/zenoh/query/Querier.kt
+++ b/zenoh-java/src/commonMain/kotlin/io/zenoh/query/Querier.kt
@@ -18,16 +18,23 @@ 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.EntityGlobalId
+import io.zenoh.config.ZenohId
import io.zenoh.exceptions.ZError
import io.zenoh.handlers.BlockingQueueHandler
import io.zenoh.handlers.Callback
import io.zenoh.handlers.Handler
import io.zenoh.jni.JNIQuerier
+import io.zenoh.jni.callbacks.JNIGetCallback
import io.zenoh.keyexpr.KeyExpr
import io.zenoh.qos.CongestionControl
import io.zenoh.qos.Priority
import io.zenoh.qos.QoS
+import io.zenoh.sample.Sample
+import io.zenoh.sample.SampleKind
import io.zenoh.session.SessionDeclaration
+import org.apache.commons.net.ntp.TimeStamp
import java.time.Duration
import java.util.Optional
import java.util.concurrent.BlockingQueue
@@ -143,11 +150,72 @@ class Querier internal constructor(val keyExpr: KeyExpr, val qos: QoS, private v
}
private fun resolveGetWithCallback(keyExpr: KeyExpr, callback: Callback, options: GetOptions) {
- jniQuerier?.performGetWithCallback(keyExpr, callback, options) ?: throw ZError("Querier is not valid.")
+ val jni = jniQuerier ?: throw ZError("Querier is not valid.")
+ val getCallback = JNIGetCallback { replierZid, replierEid, success, keyExpr2, payload, encodingId, encodingSchema, kind, timestampNTP64, timestampIsValid, attachmentBytes, express, priority, congestionControl ->
+ val reply: Reply = if (success) {
+ val timestamp = if (timestampIsValid) TimeStamp(timestampNTP64) else null
+ Reply.Success(
+ replierZid?.let { EntityGlobalId(ZenohId(it), replierEid.toUInt()) },
+ Sample(
+ KeyExpr(keyExpr2!!, null),
+ payload.into(),
+ Encoding(encodingId, schema = encodingSchema),
+ SampleKind.fromInt(kind),
+ timestamp,
+ QoS(CongestionControl.fromInt(congestionControl), Priority.fromInt(priority), express),
+ attachmentBytes?.into()
+ )
+ )
+ } else {
+ Reply.Error(replierZid?.let { EntityGlobalId(ZenohId(it), replierEid.toUInt()) }, payload.into(), Encoding(encodingId, schema = encodingSchema))
+ }
+ callback.run(reply)
+ }
+ jni.get(
+ keyExpr.jniKeyExpr,
+ keyExpr.keyExpr,
+ options.parameters?.toString(),
+ getCallback,
+ fun() {},
+ options.attachment?.into()?.bytes,
+ options.payload?.into()?.bytes,
+ options.encoding?.toJni()
+ )
}
private fun resolveGetWithHandler(keyExpr: KeyExpr, handler: Handler, options: GetOptions): R {
- return jniQuerier?.performGetWithHandler(keyExpr, handler, options) ?: throw ZError("Querier is not valid.")
+ val jni = jniQuerier ?: throw ZError("Querier is not valid.")
+ val getCallback = JNIGetCallback { replierZid, replierEid, success, keyExpr2, payload, encodingId, encodingSchema, kind, timestampNTP64, timestampIsValid, attachmentBytes, express, priority, congestionControl ->
+ val reply: Reply = if (success) {
+ val timestamp = if (timestampIsValid) TimeStamp(timestampNTP64) else null
+ Reply.Success(
+ replierZid?.let { EntityGlobalId(ZenohId(it), replierEid.toUInt()) },
+ Sample(
+ KeyExpr(keyExpr2!!, null),
+ payload.into(),
+ Encoding(encodingId, schema = encodingSchema),
+ SampleKind.fromInt(kind),
+ timestamp,
+ QoS(CongestionControl.fromInt(congestionControl), Priority.fromInt(priority), express),
+ attachmentBytes?.into()
+ )
+ )
+ } else {
+ Reply.Error(replierZid?.let { EntityGlobalId(ZenohId(it), replierEid.toUInt()) }, payload.into(), Encoding(encodingId, schema = encodingSchema))
+ }
+ handler.handle(reply)
+ }
+ jni.get(
+ keyExpr.jniKeyExpr,
+ keyExpr.keyExpr,
+ options.parameters?.toString(),
+ getCallback,
+ handler::onClose,
+ options.attachment?.into()?.bytes,
+ options.payload?.into()?.bytes,
+ options.encoding?.toJni()
+ )
+ 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..8902732a 100644
--- a/zenoh-java/src/commonMain/kotlin/io/zenoh/query/Query.kt
+++ b/zenoh-java/src/commonMain/kotlin/io/zenoh/query/Query.kt
@@ -24,6 +24,7 @@ import io.zenoh.keyexpr.KeyExpr
import io.zenoh.qos.QoS
import io.zenoh.sample.Sample
import io.zenoh.sample.SampleKind
+import org.apache.commons.net.ntp.TimeStamp
/**
* Represents a Zenoh Query in Kotlin.
@@ -61,17 +62,21 @@ 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 zbytes = payload.into()
+ val encoding = options.encoding
+ val timestamp = options.timeStamp
+ val timestampEnabled = timestamp != null
jniQuery?.apply {
- replySuccess(sample)
+ replySuccess(
+ keyExpr.jniKeyExpr,
+ keyExpr.keyExpr,
+ zbytes.bytes,
+ (encoding ?: Encoding.defaultEncoding()).toJni(),
+ timestampEnabled,
+ if (timestampEnabled) timestamp!!.ntpValue() else 0,
+ options.attachment?.into()?.bytes,
+ QoS(options.congestionControl, options.priority, options.express).express
+ )
jniQuery = null
} ?: throw (ZError("Query is invalid"))
}
@@ -98,12 +103,16 @@ class Query internal constructor(
@JvmOverloads
@Throws(ZError::class)
fun replyDel(keyExpr: KeyExpr, options: ReplyDelOptions = ReplyDelOptions()) {
+ val timestamp = options.timeStamp
+ val timestampEnabled = timestamp != null
jniQuery?.apply {
replyDelete(
- keyExpr,
- options.timeStamp,
- options.attachment,
- QoS(options.congestionControl, options.priority, options.express),
+ keyExpr.jniKeyExpr,
+ keyExpr.keyExpr,
+ timestampEnabled,
+ if (timestampEnabled) timestamp!!.ntpValue() else 0,
+ options.attachment?.into()?.bytes,
+ QoS(options.congestionControl, options.priority, options.express).express
)
jniQuery = null
} ?: throw (ZError("Query is invalid"))
@@ -118,8 +127,9 @@ class Query internal constructor(
@JvmOverloads
@Throws(ZError::class)
fun replyErr(message: IntoZBytes, options: ReplyErrOptions = ReplyErrOptions()) {
+ val encoding = options.encoding
jniQuery?.apply {
- replyError(message.into(), options.encoding)
+ replyError(message.into().bytes, (encoding ?: Encoding.defaultEncoding()).toJni())
jniQuery = null
} ?: throw (ZError("Query is invalid"))
}
diff --git a/zenoh-java/src/commonMain/kotlin/io/zenoh/ext/ZDeserializer.kt b/zenoh-java/src/jvmAndAndroidMain/kotlin/io/zenoh/ext/ZDeserializer.kt
similarity index 98%
rename from zenoh-java/src/commonMain/kotlin/io/zenoh/ext/ZDeserializer.kt
rename to zenoh-java/src/jvmAndAndroidMain/kotlin/io/zenoh/ext/ZDeserializer.kt
index 1d039cbe..fadbac89 100644
--- a/zenoh-java/src/commonMain/kotlin/io/zenoh/ext/ZDeserializer.kt
+++ b/zenoh-java/src/jvmAndAndroidMain/kotlin/io/zenoh/ext/ZDeserializer.kt
@@ -106,6 +106,6 @@ abstract class ZDeserializer: TypeToken() {
*/
fun deserialize(zbytes: IntoZBytes): T {
@Suppress("UNCHECKED_CAST")
- return JNIZBytes.deserializeViaJNI(zbytes.into(), this.type) as T
+ return JNIZBytes.deserialize(zbytes.into().bytes, this.type) as T
}
}
diff --git a/zenoh-java/src/commonMain/kotlin/io/zenoh/ext/ZSerializer.kt b/zenoh-java/src/jvmAndAndroidMain/kotlin/io/zenoh/ext/ZSerializer.kt
similarity index 98%
rename from zenoh-java/src/commonMain/kotlin/io/zenoh/ext/ZSerializer.kt
rename to zenoh-java/src/jvmAndAndroidMain/kotlin/io/zenoh/ext/ZSerializer.kt
index 8ee4a1b7..5f47b7eb 100644
--- a/zenoh-java/src/commonMain/kotlin/io/zenoh/ext/ZSerializer.kt
+++ b/zenoh-java/src/jvmAndAndroidMain/kotlin/io/zenoh/ext/ZSerializer.kt
@@ -104,6 +104,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(JNIZBytes.serialize(t as Any, this.type))
}
}
diff --git a/zenoh-jni-runtime/build.gradle.kts b/zenoh-jni-runtime/build.gradle.kts
new file mode 100644
index 00000000..716137b8
--- /dev/null
+++ b/zenoh-jni-runtime/build.gradle.kts
@@ -0,0 +1,254 @@
+//
+// 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,
+//
+
+import com.nishtahir.CargoExtension
+
+plugins {
+ kotlin("multiplatform")
+ `maven-publish`
+ signing
+}
+
+val androidEnabled = project.findProperty("android")?.toString()?.toBoolean() == true
+val release = project.findProperty("release")?.toString()?.toBoolean() == true
+
+// If the publication is meant to be done on a remote repository (Maven central).
+// Modifying this property will affect the release workflows!
+val isRemotePublication = project.findProperty("remotePublication")?.toString()?.toBoolean() == true
+
+var buildMode = if (release) BuildMode.RELEASE else BuildMode.DEBUG
+
+if (androidEnabled) {
+ apply(plugin = "com.android.library")
+ apply(plugin = "org.mozilla.rust-android-gradle.rust-android")
+
+ configureCargo()
+ configureAndroid()
+}
+
+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()
+ }
+ }
+ if (androidEnabled) {
+ androidTarget {
+ publishLibraryVariants("release")
+ }
+ }
+
+ @Suppress("Unused")
+ sourceSets {
+ val commonMain by getting {
+ kotlin.srcDir("$rootDir/zenoh-jni/generated-kotlin")
+ }
+ // jvmAndAndroidMain is an intermediate source set between commonMain and both jvmMain/androidMain.
+ // It holds code that uses java.lang.reflect.Type — available on JVM and Android (ART),
+ // but absent on Kotlin/Native and Kotlin/JS targets. Placing such code here (rather than
+ // commonMain) ensures those targets can be added in the future without compilation errors.
+ // Note: named jvmAndAndroidMain (not javaMain) to avoid conflicting with the DokkaSourceSetSpec
+ // that Dokka auto-registers for the Java compilation created by withJava().
+ val jvmAndAndroidMain by creating { dependsOn(commonMain) }
+ val commonTest by getting {
+ dependencies {
+ implementation(kotlin("test"))
+ }
+ }
+ if (androidEnabled) {
+ val androidMain by getting { dependsOn(jvmAndAndroidMain) }
+ val androidUnitTest by getting {
+ dependencies {
+ implementation(kotlin("test-junit"))
+ }
+ }
+ }
+ val jvmMain by getting {
+ dependsOn(jvmAndAndroidMain)
+ if (isRemotePublication) {
+ 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"))
+ dependencies {
+ implementation(kotlin("test-junit"))
+ }
+ }
+ }
+
+ publishing {
+ publications.withType {
+ groupId = "org.eclipse.zenoh"
+ artifactId = "zenoh-jni-runtime"
+ version = rootProject.version.toString()
+
+ pom {
+ name.set("Zenoh JNI Runtime")
+ description.set("The Eclipse Zenoh JNI runtime layer for zenoh-java and zenoh-kotlin.")
+ url.set("https://zenoh.io/")
+
+ licenses {
+ license {
+ name.set("Eclipse Public License 2.0 OR Apache License 2.0")
+ url.set("http://www.eclipse.org/legal/epl-2.0")
+ }
+ }
+ developers {
+ developer {
+ id.set("ZettaScale")
+ name.set("ZettaScale Zenoh Team")
+ email.set("zenoh@zettascale.tech")
+ }
+ }
+ scm {
+ connection.set("scm:git:https://github.com/eclipse-zenoh/zenoh-java.git")
+ developerConnection.set("scm:git:https://github.com/eclipse-zenoh/zenoh-java.git")
+ url.set("https://github.com/eclipse-zenoh/zenoh-java")
+ }
+ }
+ }
+ }
+}
+
+signing {
+ isRequired = isRemotePublication
+ useInMemoryPgpKeys(System.getenv("ORG_GPG_SUBKEY_ID"), System.getenv("ORG_GPG_PRIVATE_KEY"), System.getenv("ORG_GPG_PASSPHRASE"))
+ sign(publishing.publications)
+}
+
+tasks.withType().configureEach {
+ dependsOn(tasks.withType())
+}
+
+tasks.withType {
+ doFirst {
+ 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) {
+ 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.jni"
+ compileSdk = 30
+
+ ndkVersion = "26.0.10792818"
+
+ defaultConfig {
+ minSdk = 30
+ }
+
+ compileOptions {
+ sourceCompatibility = JavaVersion.VERSION_11
+ targetCompatibility = JavaVersion.VERSION_11
+ }
+
+ buildTypes {
+ getByName("release") {
+ isMinifyEnabled = false
+ }
+ getByName("debug") {
+ isMinifyEnabled = false
+ }
+ }
+ sourceSets {
+ getByName("main") {
+ manifest.srcFile("src/androidMain/AndroidManifest.xml")
+ }
+ }
+ publishing {
+ singleVariant("release") {
+ withSourcesJar()
+ withJavadocJar()
+ }
+ }
+ }
+}
+
+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-jni-runtime/src/androidMain/AndroidManifest.xml b/zenoh-jni-runtime/src/androidMain/AndroidManifest.xml
new file mode 100644
index 00000000..4fb03756
--- /dev/null
+++ b/zenoh-jni-runtime/src/androidMain/AndroidManifest.xml
@@ -0,0 +1,2 @@
+
+
diff --git a/zenoh-java/src/androidMain/kotlin/io.zenoh/Zenoh.kt b/zenoh-jni-runtime/src/androidMain/kotlin/io/zenoh/ZenohLoad.kt
similarity index 79%
rename from zenoh-java/src/androidMain/kotlin/io.zenoh/Zenoh.kt
rename to zenoh-jni-runtime/src/androidMain/kotlin/io/zenoh/ZenohLoad.kt
index 74d42e51..15de9e38 100644
--- a/zenoh-java/src/androidMain/kotlin/io.zenoh/Zenoh.kt
+++ b/zenoh-jni-runtime/src/androidMain/kotlin/io/zenoh/ZenohLoad.kt
@@ -1,5 +1,5 @@
//
-// Copyright (c) 2023 ZettaScale Technology
+// 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
@@ -15,10 +15,9 @@
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.
+ * Static singleton class to load the Zenoh native library once and only once.
*/
-internal actual object ZenohLoad {
+public actual object ZenohLoad {
private const val ZENOH_LIB_NAME = "zenoh_jni"
init {
diff --git a/zenoh-java/src/commonMain/kotlin/io/zenoh/jni/JNIZenohId.kt b/zenoh-jni-runtime/src/commonMain/kotlin/io/zenoh/ZenohLoad.kt
similarity index 65%
rename from zenoh-java/src/commonMain/kotlin/io/zenoh/jni/JNIZenohId.kt
rename to zenoh-jni-runtime/src/commonMain/kotlin/io/zenoh/ZenohLoad.kt
index 53ecb5dc..a2d21403 100644
--- a/zenoh-java/src/commonMain/kotlin/io/zenoh/jni/JNIZenohId.kt
+++ b/zenoh-jni-runtime/src/commonMain/kotlin/io/zenoh/ZenohLoad.kt
@@ -1,5 +1,5 @@
//
-// Copyright (c) 2023 ZettaScale Technology
+// 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
@@ -12,16 +12,9 @@
// ZettaScale Zenoh Team,
//
-package io.zenoh.jni
+package io.zenoh
-import io.zenoh.ZenohLoad
-
-internal object JNIZenohId {
-
- init {
- ZenohLoad
- }
-
- external fun toStringViaJNI(bytes: ByteArray): String
-
-}
+/**
+ * Static singleton object to load the Zenoh native library once and only once.
+ */
+public expect object ZenohLoad
diff --git a/zenoh-java/src/commonMain/kotlin/io/zenoh/exceptions/ZError.kt b/zenoh-jni-runtime/src/commonMain/kotlin/io/zenoh/exceptions/ZError.kt
similarity index 92%
rename from zenoh-java/src/commonMain/kotlin/io/zenoh/exceptions/ZError.kt
rename to zenoh-jni-runtime/src/commonMain/kotlin/io/zenoh/exceptions/ZError.kt
index c63a19ce..1984bd23 100644
--- a/zenoh-java/src/commonMain/kotlin/io/zenoh/exceptions/ZError.kt
+++ b/zenoh-jni-runtime/src/commonMain/kotlin/io/zenoh/exceptions/ZError.kt
@@ -1,5 +1,5 @@
//
-// Copyright (c) 2023 ZettaScale Technology
+// 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
diff --git a/zenoh-jni-runtime/src/commonMain/kotlin/io/zenoh/jni/JNIAdvancedPublisher.kt b/zenoh-jni-runtime/src/commonMain/kotlin/io/zenoh/jni/JNIAdvancedPublisher.kt
new file mode 100644
index 00000000..619a8ab5
--- /dev/null
+++ b/zenoh-jni-runtime/src/commonMain/kotlin/io/zenoh/jni/JNIAdvancedPublisher.kt
@@ -0,0 +1,75 @@
+//
+// 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.jni
+
+import io.zenoh.exceptions.ZError
+import io.zenoh.jni.callbacks.JNIMatchingListenerCallback
+import io.zenoh.jni.callbacks.JNIOnCloseCallback
+
+/**
+ * Adapter class for a native Zenoh AdvancedPublisher.
+ *
+ * @property ptr Raw pointer to the underlying native AdvancedPublisher.
+ */
+public class JNIAdvancedPublisher(private val ptr: Long) {
+
+ @Throws(ZError::class)
+ fun put(payload: ByteArray, encoding: JNIEncoding, attachment: ByteArray?) {
+ putViaJNI(ptr, payload, encoding, attachment)
+ }
+
+ @Throws(ZError::class)
+ fun delete(attachment: ByteArray?) {
+ deleteViaJNI(ptr, attachment)
+ }
+
+ @Throws(ZError::class)
+ fun declareMatchingListener(callback: JNIMatchingListenerCallback, onClose: JNIOnCloseCallback): JNIMatchingListener =
+ JNIMatchingListener(declareMatchingListenerViaJNI(ptr, callback, onClose))
+
+ @Throws(ZError::class)
+ fun declareBackgroundMatchingListener(callback: JNIMatchingListenerCallback, onClose: JNIOnCloseCallback) =
+ declareBackgroundMatchingListenerViaJNI(ptr, callback, onClose)
+
+ @Throws(ZError::class)
+ fun getMatchingStatus(): Boolean = getMatchingStatusViaJNI(ptr)
+
+ fun close() {
+ freePtrViaJNI(ptr)
+ }
+
+ @Throws(ZError::class)
+ private external fun putViaJNI(
+ ptr: Long, payload: ByteArray, encoding: JNIEncoding, attachment: ByteArray?
+ )
+
+ @Throws(ZError::class)
+ private external fun deleteViaJNI(ptr: Long, attachment: ByteArray?)
+
+ @Throws(ZError::class)
+ private external fun declareMatchingListenerViaJNI(
+ ptr: Long, callback: JNIMatchingListenerCallback, onClose: JNIOnCloseCallback
+ ): Long
+
+ @Throws(ZError::class)
+ private external fun declareBackgroundMatchingListenerViaJNI(
+ ptr: Long, callback: JNIMatchingListenerCallback, onClose: JNIOnCloseCallback
+ )
+
+ @Throws(ZError::class)
+ private external fun getMatchingStatusViaJNI(ptr: Long): Boolean
+
+ private external fun freePtrViaJNI(ptr: Long)
+}
diff --git a/zenoh-jni-runtime/src/commonMain/kotlin/io/zenoh/jni/JNIAdvancedSubscriber.kt b/zenoh-jni-runtime/src/commonMain/kotlin/io/zenoh/jni/JNIAdvancedSubscriber.kt
new file mode 100644
index 00000000..084b00f9
--- /dev/null
+++ b/zenoh-jni-runtime/src/commonMain/kotlin/io/zenoh/jni/JNIAdvancedSubscriber.kt
@@ -0,0 +1,80 @@
+//
+// 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.jni
+
+import io.zenoh.exceptions.ZError
+import io.zenoh.jni.callbacks.JNIOnCloseCallback
+import io.zenoh.jni.callbacks.JNISampleMissListenerCallback
+import io.zenoh.jni.callbacks.JNISubscriberCallback
+
+/**
+ * Adapter class for a native Zenoh AdvancedSubscriber.
+ *
+ * @property ptr Raw pointer to the underlying native AdvancedSubscriber.
+ */
+public class JNIAdvancedSubscriber(private val ptr: Long) {
+
+ @Throws(ZError::class)
+ fun declareDetectPublishersSubscriber(
+ history: Boolean,
+ callback: JNISubscriberCallback,
+ onClose: JNIOnCloseCallback,
+ ): JNISubscriber = JNISubscriber(declareDetectPublishersSubscriberViaJNI(ptr, history, callback, onClose))
+
+ @Throws(ZError::class)
+ fun declareBackgroundDetectPublishersSubscriber(
+ history: Boolean,
+ callback: JNISubscriberCallback,
+ onClose: JNIOnCloseCallback,
+ ) = declareBackgroundDetectPublishersSubscriberViaJNI(ptr, history, callback, onClose)
+
+ @Throws(ZError::class)
+ fun declareSampleMissListener(
+ callback: JNISampleMissListenerCallback,
+ onClose: JNIOnCloseCallback,
+ ): JNISampleMissListener = JNISampleMissListener(declareSampleMissListenerViaJNI(ptr, callback, onClose))
+
+ @Throws(ZError::class)
+ fun declareBackgroundSampleMissListener(
+ callback: JNISampleMissListenerCallback,
+ onClose: JNIOnCloseCallback,
+ ) = declareBackgroundSampleMissListenerViaJNI(ptr, callback, onClose)
+
+ fun close() {
+ freePtrViaJNI(ptr)
+ }
+
+ @Throws(ZError::class)
+ private external fun declareDetectPublishersSubscriberViaJNI(
+ ptr: Long, history: Boolean, callback: JNISubscriberCallback, onClose: JNIOnCloseCallback
+ ): Long
+
+ @Throws(ZError::class)
+ private external fun declareBackgroundDetectPublishersSubscriberViaJNI(
+ ptr: Long, history: Boolean, callback: JNISubscriberCallback, onClose: JNIOnCloseCallback
+ )
+
+ @Throws(ZError::class)
+ private external fun declareSampleMissListenerViaJNI(
+ ptr: Long, callback: JNISampleMissListenerCallback, onClose: JNIOnCloseCallback
+ ): Long
+
+ @Throws(ZError::class)
+ private external fun declareBackgroundSampleMissListenerViaJNI(
+ ptr: Long, callback: JNISampleMissListenerCallback, onClose: JNIOnCloseCallback
+ )
+
+ private external fun freePtrViaJNI(ptr: Long)
+}
diff --git a/zenoh-java/src/commonMain/kotlin/io/zenoh/jni/JNIConfig.kt b/zenoh-jni-runtime/src/commonMain/kotlin/io/zenoh/jni/JNIConfig.kt
similarity index 56%
rename from zenoh-java/src/commonMain/kotlin/io/zenoh/jni/JNIConfig.kt
rename to zenoh-jni-runtime/src/commonMain/kotlin/io/zenoh/jni/JNIConfig.kt
index ea278988..9cdd403c 100644
--- a/zenoh-java/src/commonMain/kotlin/io/zenoh/jni/JNIConfig.kt
+++ b/zenoh-jni-runtime/src/commonMain/kotlin/io/zenoh/jni/JNIConfig.kt
@@ -1,5 +1,5 @@
//
-// Copyright (c) 2023 ZettaScale Technology
+// 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
@@ -14,13 +14,11 @@
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) {
+/** Adapter for the native Zenoh config. */
+public class JNIConfig(internal val ptr: Long) {
companion object {
@@ -28,37 +26,17 @@ internal class JNIConfig(internal val ptr: Long) {
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))
- }
+ fun loadDefault(): JNIConfig = JNIConfig(loadDefaultConfigViaJNI())
@Throws(ZError::class)
- fun loadConfigFile(file: File): Config = loadConfigFile(file.toPath())
+ fun loadFromFile(path: String): JNIConfig = JNIConfig(loadConfigFileViaJNI(path))
@Throws(ZError::class)
- fun loadJsonConfig(rawConfig: String): Config {
- val cfgPtr = loadJsonConfigViaJNI(rawConfig)
- return Config(JNIConfig(cfgPtr))
- }
+ fun loadFromJson(rawConfig: String): JNIConfig = JNIConfig(loadJsonConfigViaJNI(rawConfig))
@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))
- }
+ fun loadFromYaml(rawConfig: String): JNIConfig = JNIConfig(loadYamlConfigViaJNI(rawConfig))
@Throws(ZError::class)
private external fun loadDefaultConfigViaJNI(): Long
@@ -78,7 +56,6 @@ internal class JNIConfig(internal val ptr: Long) {
@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)
@@ -90,12 +67,13 @@ internal class JNIConfig(internal val ptr: Long) {
}
@Throws(ZError::class)
- fun getJson(key: String): String {
- return getJsonViaJNI(ptr, key)
- }
+ fun getId(): ByteArray = getIdViaJNI(ptr)
+
+ @Throws(ZError::class)
+ fun getJson(key: String): String = getJsonViaJNI(ptr, key)
@Throws(ZError::class)
fun insertJson5(key: String, value: String) {
- insertJson5ViaJNI(this.ptr, key, value)
+ insertJson5ViaJNI(ptr, key, value)
}
}
diff --git a/zenoh-jni-runtime/src/commonMain/kotlin/io/zenoh/jni/JNIEncoding.kt b/zenoh-jni-runtime/src/commonMain/kotlin/io/zenoh/jni/JNIEncoding.kt
new file mode 100644
index 00000000..f4b7a6f3
--- /dev/null
+++ b/zenoh-jni-runtime/src/commonMain/kotlin/io/zenoh/jni/JNIEncoding.kt
@@ -0,0 +1,26 @@
+//
+// 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.jni
+
+/**
+ * JNI-boundary holder for a Zenoh encoding.
+ *
+ * Marshaled across JNI as a single object; the native side reads [id] and
+ * [schema] via `env.get_field(...)`. Higher layers (e.g. `zenoh-java`'s
+ * `io.zenoh.bytes.Encoding`) construct this wrapper at the JNI call
+ * boundary. Keeping it here preserves the layering rule that
+ * `zenoh-jni-runtime` must not depend on `zenoh-java`.
+ */
+data class JNIEncoding(val id: Int, val schema: String?)
diff --git a/zenoh-jni-runtime/src/commonMain/kotlin/io/zenoh/jni/JNIKeyExpr.kt b/zenoh-jni-runtime/src/commonMain/kotlin/io/zenoh/jni/JNIKeyExpr.kt
new file mode 100644
index 00000000..4afb60da
--- /dev/null
+++ b/zenoh-jni-runtime/src/commonMain/kotlin/io/zenoh/jni/JNIKeyExpr.kt
@@ -0,0 +1,105 @@
+//
+// 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.jni
+
+import io.zenoh.ZenohLoad
+import io.zenoh.exceptions.ZError
+
+/**
+ * Adapter for native Zenoh key expressions.
+ *
+ * Carries both the native-handle pointer and the source string so it can be
+ * passed across JNI as a single `JObject`. `ptr == 0L` means "not declared on
+ * a session — use [str] at the native side"; `ptr != 0L` means "declared —
+ * use the pointer and ignore [str]".
+ */
+public class JNIKeyExpr(internal val ptr: Long, internal val str: String) {
+
+ companion object {
+ init {
+ ZenohLoad
+ }
+
+ /**
+ * Build a JNI holder for an undeclared key expression (string-only).
+ * Used at JNI call boundaries where the caller may not have a
+ * declared [JNIKeyExpr] and needs to pass the raw string instead.
+ */
+ fun undeclared(keyExpr: String): JNIKeyExpr = JNIKeyExpr(0L, keyExpr)
+
+ /**
+ * Build a JNI holder from an optional declared expression and a
+ * source string. If [declared] is non-null its pointer is preserved
+ * (and the string is passed along for diagnostics / fallback);
+ * otherwise the result represents the undeclared [keyExpr].
+ */
+ fun of(declared: JNIKeyExpr?, keyExpr: String): JNIKeyExpr =
+ declared ?: JNIKeyExpr(0L, keyExpr)
+
+ @Throws(ZError::class)
+ fun tryFrom(keyExpr: String): String = tryFromViaJNI(keyExpr)
+
+ @Throws(ZError::class)
+ fun autocanonize(keyExpr: String): String = autocanonizeViaJNI(keyExpr)
+
+ @Throws(ZError::class)
+ private external fun tryFromViaJNI(keyExpr: String): String
+
+ @Throws(ZError::class)
+ private external fun autocanonizeViaJNI(keyExpr: String): String
+
+ @Throws(ZError::class)
+ fun intersects(a: JNIKeyExpr?, aStr: String, b: JNIKeyExpr?, bStr: String): Boolean =
+ intersectsViaJNI(a?.ptr ?: 0, aStr, b?.ptr ?: 0, bStr)
+
+ @Throws(ZError::class)
+ fun includes(a: JNIKeyExpr?, aStr: String, b: JNIKeyExpr?, bStr: String): Boolean =
+ includesViaJNI(a?.ptr ?: 0, aStr, b?.ptr ?: 0, bStr)
+
+ /** Returns SetIntersectionLevel ordinal as Int. Callers convert to SetIntersectionLevel. */
+ @Throws(ZError::class)
+ fun relationTo(a: JNIKeyExpr?, aStr: String, b: JNIKeyExpr?, bStr: String): Int =
+ relationToViaJNI(a?.ptr ?: 0, aStr, b?.ptr ?: 0, bStr)
+
+ @Throws(ZError::class)
+ fun join(a: JNIKeyExpr?, aStr: String, other: String): String =
+ joinViaJNI(a?.ptr ?: 0, aStr, other)
+
+ @Throws(ZError::class)
+ fun concat(a: JNIKeyExpr?, aStr: String, other: String): String =
+ concatViaJNI(a?.ptr ?: 0, aStr, other)
+
+ @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)
+ }
+
+ private external fun freePtrViaJNI(ptr: Long)
+}
diff --git a/zenoh-jni-runtime/src/commonMain/kotlin/io/zenoh/jni/JNILivelinessToken.kt b/zenoh-jni-runtime/src/commonMain/kotlin/io/zenoh/jni/JNILivelinessToken.kt
new file mode 100644
index 00000000..1f88d3ad
--- /dev/null
+++ b/zenoh-jni-runtime/src/commonMain/kotlin/io/zenoh/jni/JNILivelinessToken.kt
@@ -0,0 +1,25 @@
+//
+// 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.jni
+
+/** Adapter class for a native Zenoh LivelinessToken. */
+public class JNILivelinessToken(private val ptr: Long) {
+
+ fun undeclare() {
+ undeclareViaJNI(ptr)
+ }
+
+ private external fun undeclareViaJNI(ptr: Long)
+}
diff --git a/zenoh-jni-runtime/src/commonMain/kotlin/io/zenoh/jni/JNILogger.kt b/zenoh-jni-runtime/src/commonMain/kotlin/io/zenoh/jni/JNILogger.kt
new file mode 100644
index 00000000..47c0de53
--- /dev/null
+++ b/zenoh-jni-runtime/src/commonMain/kotlin/io/zenoh/jni/JNILogger.kt
@@ -0,0 +1,37 @@
+//
+// 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.jni
+
+import io.zenoh.ZenohLoad
+import io.zenoh.exceptions.ZError
+
+/** Adapter for initializing Rust logging through JNI. */
+public object JNILogger {
+
+ init {
+ ZenohLoad
+ }
+
+ /**
+ * Redirects Rust logs either to logcat (Android) or standard output.
+ *
+ * See https://docs.rs/env_logger/latest/env_logger/index.html for accepted filter format.
+ */
+ @Throws(ZError::class)
+ fun startLogs(filter: String) = startLogsViaJNI(filter)
+
+ @Throws(ZError::class)
+ private external fun startLogsViaJNI(filter: String)
+}
diff --git a/zenoh-jni-runtime/src/commonMain/kotlin/io/zenoh/jni/JNIMatchingListener.kt b/zenoh-jni-runtime/src/commonMain/kotlin/io/zenoh/jni/JNIMatchingListener.kt
new file mode 100644
index 00000000..7c58edee
--- /dev/null
+++ b/zenoh-jni-runtime/src/commonMain/kotlin/io/zenoh/jni/JNIMatchingListener.kt
@@ -0,0 +1,25 @@
+//
+// 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.jni
+
+/** Adapter class for a native Zenoh MatchingListener. */
+public class JNIMatchingListener(private val ptr: Long) {
+
+ fun close() {
+ freePtrViaJNI(ptr)
+ }
+
+ private external fun freePtrViaJNI(ptr: Long)
+}
diff --git a/zenoh-jni-runtime/src/commonMain/kotlin/io/zenoh/jni/JNIPublisher.kt b/zenoh-jni-runtime/src/commonMain/kotlin/io/zenoh/jni/JNIPublisher.kt
new file mode 100644
index 00000000..f220d369
--- /dev/null
+++ b/zenoh-jni-runtime/src/commonMain/kotlin/io/zenoh/jni/JNIPublisher.kt
@@ -0,0 +1,49 @@
+//
+// 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.jni
+
+import io.zenoh.exceptions.ZError
+
+/**
+ * Adapter class for a native Zenoh publisher. Uses primitive types for put/delete.
+ *
+ * @property ptr Raw pointer to the underlying native Publisher.
+ */
+public class JNIPublisher(private val ptr: Long) {
+
+ @Throws(ZError::class)
+ fun put(payload: ByteArray, encoding: JNIEncoding, attachment: ByteArray?) {
+ putViaJNI(ptr, payload, encoding, attachment)
+ }
+
+ @Throws(ZError::class)
+ fun delete(attachment: ByteArray?) {
+ deleteViaJNI(ptr, attachment)
+ }
+
+ fun close() {
+ freePtrViaJNI(ptr)
+ }
+
+ @Throws(ZError::class)
+ private external fun putViaJNI(
+ ptr: Long, valuePayload: ByteArray, encoding: JNIEncoding, attachment: ByteArray?
+ )
+
+ @Throws(ZError::class)
+ private external fun deleteViaJNI(ptr: Long, attachment: ByteArray?)
+
+ private external fun freePtrViaJNI(ptr: Long)
+}
diff --git a/zenoh-jni-runtime/src/commonMain/kotlin/io/zenoh/jni/JNIQuerier.kt b/zenoh-jni-runtime/src/commonMain/kotlin/io/zenoh/jni/JNIQuerier.kt
new file mode 100644
index 00000000..408b4307
--- /dev/null
+++ b/zenoh-jni-runtime/src/commonMain/kotlin/io/zenoh/jni/JNIQuerier.kt
@@ -0,0 +1,55 @@
+//
+// 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.jni
+
+import io.zenoh.exceptions.ZError
+import io.zenoh.jni.callbacks.JNIGetCallback
+import io.zenoh.jni.callbacks.JNIOnCloseCallback
+
+/** Adapter class for a native Zenoh querier. */
+public class JNIQuerier(private val ptr: Long) {
+
+ @Throws(ZError::class)
+ fun get(
+ jniKeyExpr: JNIKeyExpr?,
+ keyExprString: String,
+ parameters: String?,
+ callback: JNIGetCallback,
+ onClose: JNIOnCloseCallback,
+ attachmentBytes: ByteArray?,
+ payload: ByteArray?,
+ encoding: JNIEncoding?,
+ ) {
+ getViaJNI(ptr, JNIKeyExpr.of(jniKeyExpr, keyExprString), parameters, callback, onClose, attachmentBytes, payload, encoding)
+ }
+
+ @Throws(ZError::class)
+ private external fun getViaJNI(
+ querierPtr: Long,
+ keyExpr: JNIKeyExpr,
+ parameters: String?,
+ callback: JNIGetCallback,
+ onClose: JNIOnCloseCallback,
+ attachmentBytes: ByteArray?,
+ payload: ByteArray?,
+ encoding: JNIEncoding?,
+ )
+
+ private external fun freePtrViaJNI(ptr: Long)
+
+ fun close() {
+ freePtrViaJNI(ptr)
+ }
+}
diff --git a/zenoh-jni-runtime/src/commonMain/kotlin/io/zenoh/jni/JNIQuery.kt b/zenoh-jni-runtime/src/commonMain/kotlin/io/zenoh/jni/JNIQuery.kt
new file mode 100644
index 00000000..372da155
--- /dev/null
+++ b/zenoh-jni-runtime/src/commonMain/kotlin/io/zenoh/jni/JNIQuery.kt
@@ -0,0 +1,91 @@
+//
+// 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.jni
+
+import io.zenoh.exceptions.ZError
+
+/**
+ * Adapter class for interacting with a native Zenoh Query using JNI.
+ *
+ * @property ptr The raw pointer to the underlying native query.
+ */
+public class JNIQuery(private val ptr: Long) {
+
+ @Throws(ZError::class)
+ fun replySuccess(
+ jniKeyExpr: JNIKeyExpr?,
+ keyExprString: String,
+ payload: ByteArray,
+ encoding: JNIEncoding,
+ timestampEnabled: Boolean,
+ timestampNtp64: Long,
+ attachment: ByteArray?,
+ qosExpress: Boolean,
+ ) {
+ replySuccessViaJNI(ptr, JNIKeyExpr.of(jniKeyExpr, keyExprString), payload, encoding, timestampEnabled, timestampNtp64, attachment, qosExpress)
+ }
+
+ @Throws(ZError::class)
+ fun replyError(errorPayload: ByteArray, encoding: JNIEncoding) {
+ replyErrorViaJNI(ptr, errorPayload, encoding)
+ }
+
+ @Throws(ZError::class)
+ fun replyDelete(
+ jniKeyExpr: JNIKeyExpr?,
+ keyExprString: String,
+ timestampEnabled: Boolean,
+ timestampNtp64: Long,
+ attachment: ByteArray?,
+ qosExpress: Boolean,
+ ) {
+ replyDeleteViaJNI(ptr, JNIKeyExpr.of(jniKeyExpr, keyExprString), timestampEnabled, timestampNtp64, attachment, qosExpress)
+ }
+
+ fun close() {
+ freePtrViaJNI(ptr)
+ }
+
+ @Throws(ZError::class)
+ private external fun replySuccessViaJNI(
+ queryPtr: Long,
+ keyExpr: JNIKeyExpr,
+ valuePayload: ByteArray,
+ valueEncoding: JNIEncoding,
+ timestampEnabled: Boolean,
+ timestampNtp64: Long,
+ attachment: ByteArray?,
+ qosExpress: Boolean,
+ )
+
+ @Throws(ZError::class)
+ private external fun replyErrorViaJNI(
+ queryPtr: Long,
+ errorValuePayload: ByteArray,
+ errorValueEncoding: JNIEncoding,
+ )
+
+ @Throws(ZError::class)
+ private external fun replyDeleteViaJNI(
+ queryPtr: Long,
+ keyExpr: JNIKeyExpr,
+ timestampEnabled: Boolean,
+ timestampNtp64: Long,
+ attachment: ByteArray?,
+ qosExpress: Boolean,
+ )
+
+ private external fun freePtrViaJNI(ptr: Long)
+}
diff --git a/zenoh-java/src/commonMain/kotlin/io/zenoh/jni/JNIQueryable.kt b/zenoh-jni-runtime/src/commonMain/kotlin/io/zenoh/jni/JNIQueryable.kt
similarity index 79%
rename from zenoh-java/src/commonMain/kotlin/io/zenoh/jni/JNIQueryable.kt
rename to zenoh-jni-runtime/src/commonMain/kotlin/io/zenoh/jni/JNIQueryable.kt
index e5f7d3ce..ba64f7e9 100644
--- a/zenoh-java/src/commonMain/kotlin/io/zenoh/jni/JNIQueryable.kt
+++ b/zenoh-jni-runtime/src/commonMain/kotlin/io/zenoh/jni/JNIQueryable.kt
@@ -1,5 +1,5 @@
//
-// Copyright (c) 2023 ZettaScale Technology
+// 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
@@ -15,16 +15,15 @@
package io.zenoh.jni
/**
- * Adapter class to handle the interactions with Zenoh through JNI for a [io.zenoh.query.Queryable]
+ * Adapter class to handle the interactions with Zenoh through JNI for a Queryable.
*
* @property ptr: raw pointer to the underlying native Queryable.
*/
-internal class JNIQueryable(val ptr: Long) {
+public class JNIQueryable(private val ptr: Long) {
fun close() {
freePtrViaJNI(ptr)
}
- /** Frees the underlying native Queryable. */
private external fun freePtrViaJNI(ptr: Long)
}
diff --git a/zenoh-jni-runtime/src/commonMain/kotlin/io/zenoh/jni/JNISampleMissListener.kt b/zenoh-jni-runtime/src/commonMain/kotlin/io/zenoh/jni/JNISampleMissListener.kt
new file mode 100644
index 00000000..dea27edf
--- /dev/null
+++ b/zenoh-jni-runtime/src/commonMain/kotlin/io/zenoh/jni/JNISampleMissListener.kt
@@ -0,0 +1,25 @@
+//
+// 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.jni
+
+/** Adapter class for a native Zenoh SampleMissListener. */
+public class JNISampleMissListener(private val ptr: Long) {
+
+ fun close() {
+ freePtrViaJNI(ptr)
+ }
+
+ private external fun freePtrViaJNI(ptr: Long)
+}
diff --git a/zenoh-jni-runtime/src/commonMain/kotlin/io/zenoh/jni/JNIScout.kt b/zenoh-jni-runtime/src/commonMain/kotlin/io/zenoh/jni/JNIScout.kt
new file mode 100644
index 00000000..1b3d1bd0
--- /dev/null
+++ b/zenoh-jni-runtime/src/commonMain/kotlin/io/zenoh/jni/JNIScout.kt
@@ -0,0 +1,56 @@
+//
+// 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.jni
+
+import io.zenoh.ZenohLoad
+import io.zenoh.exceptions.ZError
+import io.zenoh.jni.callbacks.JNIOnCloseCallback
+import io.zenoh.jni.callbacks.JNIScoutCallback
+
+/**
+ * Adapter class to handle the interactions with Zenoh through JNI for a Scout.
+ *
+ * @property ptr: raw pointer to the underlying native scout.
+ */
+public class JNIScout(private val ptr: Long) {
+
+ companion object {
+ init {
+ ZenohLoad
+ }
+
+ @Throws(ZError::class)
+ fun scout(
+ whatAmI: Int,
+ callback: JNIScoutCallback,
+ onClose: JNIOnCloseCallback,
+ config: JNIConfig?,
+ ): JNIScout = JNIScout(scoutViaJNI(whatAmI, callback, onClose, config?.ptr ?: 0))
+
+ @Throws(ZError::class)
+ private external fun scoutViaJNI(
+ whatAmI: Int,
+ callback: JNIScoutCallback,
+ onClose: JNIOnCloseCallback,
+ configPtr: Long,
+ ): Long
+
+ private external fun freePtrViaJNI(ptr: Long)
+ }
+
+ fun close() {
+ freePtrViaJNI(ptr)
+ }
+}
diff --git a/zenoh-jni-runtime/src/commonMain/kotlin/io/zenoh/jni/JNISession.kt b/zenoh-jni-runtime/src/commonMain/kotlin/io/zenoh/jni/JNISession.kt
new file mode 100644
index 00000000..bac21ad5
--- /dev/null
+++ b/zenoh-jni-runtime/src/commonMain/kotlin/io/zenoh/jni/JNISession.kt
@@ -0,0 +1,248 @@
+//
+// 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.jni
+
+import io.zenoh.ZenohLoad
+import io.zenoh.exceptions.ZError
+import io.zenoh.jni.JNISessionNative.closeSessionViaJNI
+import io.zenoh.jni.JNISessionNative.declareAdvancedPublisherViaJNI
+import io.zenoh.jni.JNISessionNative.declareAdvancedSubscriberViaJNI
+import io.zenoh.jni.JNISessionNative.declareKeyExprViaJNI
+import io.zenoh.jni.JNISessionNative.declarePublisherViaJNI
+import io.zenoh.jni.JNISessionNative.declareQuerierViaJNI
+import io.zenoh.jni.JNISessionNative.declareQueryableViaJNI
+import io.zenoh.jni.JNISessionNative.declareSubscriberViaJNI
+import io.zenoh.jni.JNISessionNative.deleteViaJNI
+import io.zenoh.jni.JNISessionNative.getPeersZidViaJNI
+import io.zenoh.jni.JNISessionNative.getRoutersZidViaJNI
+import io.zenoh.jni.JNISessionNative.getViaJNI
+import io.zenoh.jni.JNISessionNative.getZidViaJNI
+import io.zenoh.jni.JNISessionNative.openSessionViaJNI
+import io.zenoh.jni.JNISessionNative.putViaJNI
+import io.zenoh.jni.JNISessionNative.undeclareKeyExprViaJNI
+import io.zenoh.jni.callbacks.JNIGetCallback
+import io.zenoh.jni.callbacks.JNIOnCloseCallback
+import io.zenoh.jni.callbacks.JNIQueryableCallback
+import io.zenoh.jni.callbacks.JNISubscriberCallback
+
+/** Adapter class to handle communication with the Zenoh JNI code for a Session. */
+public class JNISession(internal val sessionPtr: Long) {
+
+ companion object {
+ init {
+ ZenohLoad
+ }
+
+ @Throws(ZError::class)
+ fun open(config: JNIConfig): JNISession {
+ val sessionPtr = openSessionViaJNI(config.ptr)
+ return JNISession(sessionPtr)
+ }
+ }
+
+ @Throws(ZError::class)
+ fun declarePublisher(
+ jniKeyExpr: JNIKeyExpr?,
+ keyExprString: String,
+ congestionControl: Int,
+ priority: Int,
+ express: Boolean,
+ reliability: Int
+ ): JNIPublisher = JNIPublisher(declarePublisherViaJNI(sessionPtr, JNIKeyExpr.of(jniKeyExpr, keyExprString), congestionControl, priority, express, reliability))
+
+ @Throws(ZError::class)
+ fun declareSubscriber(
+ jniKeyExpr: JNIKeyExpr?,
+ keyExprString: String,
+ callback: JNISubscriberCallback,
+ onClose: JNIOnCloseCallback,
+ ): JNISubscriber = JNISubscriber(declareSubscriberViaJNI(sessionPtr, JNIKeyExpr.of(jniKeyExpr, keyExprString), callback, onClose))
+
+ @Throws(ZError::class)
+ fun declareQueryable(
+ jniKeyExpr: JNIKeyExpr?,
+ keyExprString: String,
+ callback: JNIQueryableCallback,
+ onClose: JNIOnCloseCallback,
+ complete: Boolean
+ ): JNIQueryable = JNIQueryable(declareQueryableViaJNI(sessionPtr, JNIKeyExpr.of(jniKeyExpr, keyExprString), callback, onClose, complete))
+
+ @Throws(ZError::class)
+ fun declareQuerier(
+ jniKeyExpr: JNIKeyExpr?,
+ keyExprString: String,
+ target: Int,
+ consolidation: Int,
+ congestionControl: Int,
+ priority: Int,
+ express: Boolean,
+ timeoutMs: Long,
+ acceptReplies: Int
+ ): JNIQuerier = JNIQuerier(declareQuerierViaJNI(sessionPtr, JNIKeyExpr.of(jniKeyExpr, keyExprString), target, consolidation, congestionControl, priority, express, timeoutMs, acceptReplies))
+
+ @Throws(ZError::class)
+ fun declareKeyExpr(keyExpr: String): JNIKeyExpr = JNIKeyExpr(declareKeyExprViaJNI(sessionPtr, keyExpr), keyExpr)
+
+ @Throws(ZError::class)
+ fun undeclareKeyExpr(jniKeyExpr: JNIKeyExpr) = undeclareKeyExprViaJNI(sessionPtr, jniKeyExpr)
+
+ @Throws(ZError::class)
+ fun get(
+ jniKeyExpr: JNIKeyExpr?,
+ keyExprString: String,
+ selectorParams: String?,
+ callback: JNIGetCallback,
+ onClose: JNIOnCloseCallback,
+ timeoutMs: Long,
+ target: Int,
+ consolidation: Int,
+ attachmentBytes: ByteArray?,
+ payload: ByteArray?,
+ encoding: JNIEncoding?,
+ congestionControl: Int,
+ priority: Int,
+ express: Boolean,
+ acceptReplies: Int,
+ ) = getViaJNI(sessionPtr, JNIKeyExpr.of(jniKeyExpr, keyExprString), selectorParams, callback, onClose, timeoutMs, target, consolidation, attachmentBytes, payload, encoding, congestionControl, priority, express, acceptReplies)
+
+ @Throws(ZError::class)
+ fun put(
+ jniKeyExpr: JNIKeyExpr?,
+ keyExprString: String,
+ valuePayload: ByteArray,
+ valueEncoding: JNIEncoding,
+ congestionControl: Int,
+ priority: Int,
+ express: Boolean,
+ attachmentBytes: ByteArray?,
+ reliability: Int
+ ) = putViaJNI(sessionPtr, JNIKeyExpr.of(jniKeyExpr, keyExprString), valuePayload, valueEncoding, congestionControl, priority, express, attachmentBytes, reliability)
+
+ @Throws(ZError::class)
+ fun delete(
+ jniKeyExpr: JNIKeyExpr?,
+ keyExprString: String,
+ congestionControl: Int,
+ priority: Int,
+ express: Boolean,
+ attachmentBytes: ByteArray?,
+ reliability: Int
+ ) = deleteViaJNI(sessionPtr, JNIKeyExpr.of(jniKeyExpr, keyExprString), congestionControl, priority, express, attachmentBytes, reliability)
+
+ @Throws(ZError::class)
+ fun getZid(): ByteArray = getZidViaJNI(sessionPtr)
+
+ @Throws(ZError::class)
+ fun getPeersZid(): List = getPeersZidViaJNI(sessionPtr)
+
+ @Throws(ZError::class)
+ fun getRoutersZid(): List = getRoutersZidViaJNI(sessionPtr)
+
+ @Throws(ZError::class)
+ fun declareAdvancedSubscriber(
+ jniKeyExpr: JNIKeyExpr?,
+ keyExprStr: String,
+ callback: JNISubscriberCallback,
+ onClose: JNIOnCloseCallback,
+ history: HistoryConfig?,
+ recovery: RecoveryConfig?,
+ subscriberDetection: Boolean,
+ ): JNIAdvancedSubscriber = JNIAdvancedSubscriber(
+ declareAdvancedSubscriberViaJNI(
+ sessionPtr,
+ JNIKeyExpr.of(jniKeyExpr, keyExprStr),
+ callback,
+ onClose,
+ history,
+ recovery,
+ subscriberDetection,
+ )
+ )
+
+ @Throws(ZError::class)
+ fun declareAdvancedPublisher(
+ jniKeyExpr: JNIKeyExpr?,
+ keyExprStr: String,
+ congestionControl: Int,
+ priority: Int,
+ isExpress: Boolean,
+ reliability: Int,
+ cache: CacheConfig?,
+ sampleMissDetection: MissDetectionConfig?,
+ publisherDetection: Boolean,
+ ): JNIAdvancedPublisher = JNIAdvancedPublisher(
+ declareAdvancedPublisherViaJNI(
+ sessionPtr,
+ JNIKeyExpr.of(jniKeyExpr, keyExprStr),
+ congestionControl,
+ priority,
+ isExpress,
+ reliability,
+ cache,
+ sampleMissDetection,
+ publisherDetection,
+ )
+ )
+
+ @Throws(ZError::class)
+ fun declareLivelinessToken(jniKeyExpr: JNIKeyExpr?, keyExprString: String): JNILivelinessToken =
+ JNILivelinessToken(declareLivelinessTokenViaJNI(sessionPtr, JNIKeyExpr.of(jniKeyExpr, keyExprString)))
+
+ @Throws(ZError::class)
+ private external fun declareLivelinessTokenViaJNI(sessionPtr: Long, keyExpr: JNIKeyExpr): Long
+
+ @Throws(ZError::class)
+ fun declareLivelinessSubscriber(
+ jniKeyExpr: JNIKeyExpr?,
+ keyExprString: String,
+ callback: JNISubscriberCallback,
+ history: Boolean,
+ onClose: JNIOnCloseCallback,
+ ): JNISubscriber = JNISubscriber(declareLivelinessSubscriberViaJNI(sessionPtr, JNIKeyExpr.of(jniKeyExpr, keyExprString), callback, history, onClose))
+
+ @Throws(ZError::class)
+ private external fun declareLivelinessSubscriberViaJNI(
+ sessionPtr: Long,
+ keyExpr: JNIKeyExpr,
+ callback: JNISubscriberCallback,
+ history: Boolean,
+ onClose: JNIOnCloseCallback,
+ ): Long
+
+ @Throws(ZError::class)
+ fun livelinessGet(
+ jniKeyExpr: JNIKeyExpr?,
+ keyExprString: String,
+ callback: JNIGetCallback,
+ timeoutMs: Long,
+ onClose: JNIOnCloseCallback,
+ ) = livelinessGetViaJNI(sessionPtr, JNIKeyExpr.of(jniKeyExpr, keyExprString), callback, timeoutMs, onClose)
+
+ @Throws(ZError::class)
+ private external fun livelinessGetViaJNI(
+ sessionPtr: Long,
+ keyExpr: JNIKeyExpr,
+ callback: JNIGetCallback,
+ timeoutMs: Long,
+ onClose: JNIOnCloseCallback,
+ )
+
+ fun close() {
+ closeSessionViaJNI(sessionPtr)
+ freePtrViaJNI(sessionPtr)
+ }
+
+ private external fun freePtrViaJNI(ptr: Long)
+}
diff --git a/zenoh-java/src/commonMain/kotlin/io/zenoh/jni/JNISubscriber.kt b/zenoh-jni-runtime/src/commonMain/kotlin/io/zenoh/jni/JNISubscriber.kt
similarity index 78%
rename from zenoh-java/src/commonMain/kotlin/io/zenoh/jni/JNISubscriber.kt
rename to zenoh-jni-runtime/src/commonMain/kotlin/io/zenoh/jni/JNISubscriber.kt
index 1bb80543..b9877d70 100644
--- a/zenoh-java/src/commonMain/kotlin/io/zenoh/jni/JNISubscriber.kt
+++ b/zenoh-jni-runtime/src/commonMain/kotlin/io/zenoh/jni/JNISubscriber.kt
@@ -1,5 +1,5 @@
//
-// Copyright (c) 2023 ZettaScale Technology
+// 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
@@ -15,17 +15,15 @@
package io.zenoh.jni
/**
- * Adapter class to handle the interactions with Zenoh through JNI for a [io.zenoh.pubsub.Subscriber]
+ * Adapter class to handle the interactions with Zenoh through JNI for a Subscriber.
*
* @property ptr: raw pointer to the underlying native Subscriber.
*/
-internal class JNISubscriber(private val ptr: Long) {
+public 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-jni-runtime/src/commonMain/kotlin/io/zenoh/jni/JNIZenohId.kt b/zenoh-jni-runtime/src/commonMain/kotlin/io/zenoh/jni/JNIZenohId.kt
new file mode 100644
index 00000000..0eb1f394
--- /dev/null
+++ b/zenoh-jni-runtime/src/commonMain/kotlin/io/zenoh/jni/JNIZenohId.kt
@@ -0,0 +1,29 @@
+//
+// 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.jni
+
+import io.zenoh.ZenohLoad
+
+/** Adapter object for interacting with Zenoh IDs through JNI. */
+public object JNIZenohId {
+
+ init {
+ ZenohLoad
+ }
+
+ fun toString(bytes: ByteArray): String = toStringViaJNI(bytes)
+
+ private external fun toStringViaJNI(bytes: ByteArray): String
+}
diff --git a/zenoh-java/src/commonMain/kotlin/io/zenoh/jni/callbacks/JNIGetCallback.kt b/zenoh-jni-runtime/src/commonMain/kotlin/io/zenoh/jni/callbacks/JNIGetCallback.kt
similarity index 91%
rename from zenoh-java/src/commonMain/kotlin/io/zenoh/jni/callbacks/JNIGetCallback.kt
rename to zenoh-jni-runtime/src/commonMain/kotlin/io/zenoh/jni/callbacks/JNIGetCallback.kt
index 14f3c8e9..fe7da688 100644
--- a/zenoh-java/src/commonMain/kotlin/io/zenoh/jni/callbacks/JNIGetCallback.kt
+++ b/zenoh-jni-runtime/src/commonMain/kotlin/io/zenoh/jni/callbacks/JNIGetCallback.kt
@@ -1,5 +1,5 @@
//
-// Copyright (c) 2023 ZettaScale Technology
+// 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
@@ -14,7 +14,7 @@
package io.zenoh.jni.callbacks
-internal fun interface JNIGetCallback {
+public fun interface JNIGetCallback {
fun run(
replierZid: ByteArray?,
diff --git a/zenoh-jni-runtime/src/commonMain/kotlin/io/zenoh/jni/callbacks/JNIMatchingListenerCallback.kt b/zenoh-jni-runtime/src/commonMain/kotlin/io/zenoh/jni/callbacks/JNIMatchingListenerCallback.kt
new file mode 100644
index 00000000..4b133b08
--- /dev/null
+++ b/zenoh-jni-runtime/src/commonMain/kotlin/io/zenoh/jni/callbacks/JNIMatchingListenerCallback.kt
@@ -0,0 +1,20 @@
+//
+// 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.jni.callbacks
+
+/** Callback for matching listener notifications; receives true when matching subscribers exist. */
+public fun interface JNIMatchingListenerCallback {
+ fun run(matching: Boolean)
+}
diff --git a/zenoh-java/src/commonMain/kotlin/io/zenoh/jni/callbacks/JNIOnCloseCallback.kt b/zenoh-jni-runtime/src/commonMain/kotlin/io/zenoh/jni/callbacks/JNIOnCloseCallback.kt
similarity index 84%
rename from zenoh-java/src/commonMain/kotlin/io/zenoh/jni/callbacks/JNIOnCloseCallback.kt
rename to zenoh-jni-runtime/src/commonMain/kotlin/io/zenoh/jni/callbacks/JNIOnCloseCallback.kt
index b58fa23d..62760b54 100644
--- a/zenoh-java/src/commonMain/kotlin/io/zenoh/jni/callbacks/JNIOnCloseCallback.kt
+++ b/zenoh-jni-runtime/src/commonMain/kotlin/io/zenoh/jni/callbacks/JNIOnCloseCallback.kt
@@ -1,5 +1,5 @@
//
-// Copyright (c) 2023 ZettaScale Technology
+// 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
@@ -14,7 +14,7 @@
package io.zenoh.jni.callbacks
-internal fun interface JNIOnCloseCallback {
+public fun interface JNIOnCloseCallback {
fun run()
diff --git a/zenoh-java/src/commonMain/kotlin/io/zenoh/jni/callbacks/JNIQueryableCallback.kt b/zenoh-jni-runtime/src/commonMain/kotlin/io/zenoh/jni/callbacks/JNIQueryableCallback.kt
similarity index 56%
rename from zenoh-java/src/commonMain/kotlin/io/zenoh/jni/callbacks/JNIQueryableCallback.kt
rename to zenoh-jni-runtime/src/commonMain/kotlin/io/zenoh/jni/callbacks/JNIQueryableCallback.kt
index addf1430..8dc3bce8 100644
--- a/zenoh-java/src/commonMain/kotlin/io/zenoh/jni/callbacks/JNIQueryableCallback.kt
+++ b/zenoh-jni-runtime/src/commonMain/kotlin/io/zenoh/jni/callbacks/JNIQueryableCallback.kt
@@ -1,5 +1,5 @@
//
-// Copyright (c) 2023 ZettaScale Technology
+// 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
@@ -14,13 +14,15 @@
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)
+public 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-jni-runtime/src/commonMain/kotlin/io/zenoh/jni/callbacks/JNISampleMissListenerCallback.kt b/zenoh-jni-runtime/src/commonMain/kotlin/io/zenoh/jni/callbacks/JNISampleMissListenerCallback.kt
new file mode 100644
index 00000000..bc5f09cc
--- /dev/null
+++ b/zenoh-jni-runtime/src/commonMain/kotlin/io/zenoh/jni/callbacks/JNISampleMissListenerCallback.kt
@@ -0,0 +1,20 @@
+//
+// 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.jni.callbacks
+
+/** Callback for sample miss listener notifications. Parameters encode the source entity global id and missed count. */
+public fun interface JNISampleMissListenerCallback {
+ fun run(zidLower: Long, zidUpper: Long, eid: Long, nb: Long)
+}
diff --git a/zenoh-java/src/commonMain/kotlin/io/zenoh/jni/callbacks/JNIScoutCallback.kt b/zenoh-jni-runtime/src/commonMain/kotlin/io/zenoh/jni/callbacks/JNIScoutCallback.kt
similarity index 85%
rename from zenoh-java/src/commonMain/kotlin/io/zenoh/jni/callbacks/JNIScoutCallback.kt
rename to zenoh-jni-runtime/src/commonMain/kotlin/io/zenoh/jni/callbacks/JNIScoutCallback.kt
index 0a8b20e9..57e2390f 100644
--- a/zenoh-java/src/commonMain/kotlin/io/zenoh/jni/callbacks/JNIScoutCallback.kt
+++ b/zenoh-jni-runtime/src/commonMain/kotlin/io/zenoh/jni/callbacks/JNIScoutCallback.kt
@@ -1,5 +1,5 @@
//
-// Copyright (c) 2023 ZettaScale Technology
+// 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
@@ -14,7 +14,7 @@
package io.zenoh.jni.callbacks
-internal fun interface JNIScoutCallback {
+public 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-jni-runtime/src/commonMain/kotlin/io/zenoh/jni/callbacks/JNISubscriberCallback.kt
similarity index 89%
rename from zenoh-java/src/commonMain/kotlin/io/zenoh/jni/callbacks/JNISubscriberCallback.kt
rename to zenoh-jni-runtime/src/commonMain/kotlin/io/zenoh/jni/callbacks/JNISubscriberCallback.kt
index 76373c72..013fc6ac 100644
--- a/zenoh-java/src/commonMain/kotlin/io/zenoh/jni/callbacks/JNISubscriberCallback.kt
+++ b/zenoh-jni-runtime/src/commonMain/kotlin/io/zenoh/jni/callbacks/JNISubscriberCallback.kt
@@ -1,5 +1,5 @@
//
-// Copyright (c) 2023 ZettaScale Technology
+// 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
@@ -14,7 +14,7 @@
package io.zenoh.jni.callbacks
-internal fun interface JNISubscriberCallback {
+public fun interface JNISubscriberCallback {
fun run(
keyExpr: String,
payload: ByteArray,
diff --git a/zenoh-java/src/commonMain/kotlin/io/zenoh/jni/JNIZBytes.kt b/zenoh-jni-runtime/src/jvmAndAndroidMain/kotlin/io/zenoh/jni/JNIZBytes.kt
similarity index 64%
rename from zenoh-java/src/commonMain/kotlin/io/zenoh/jni/JNIZBytes.kt
rename to zenoh-jni-runtime/src/jvmAndAndroidMain/kotlin/io/zenoh/jni/JNIZBytes.kt
index 799b9cac..f9690eec 100644
--- a/zenoh-java/src/commonMain/kotlin/io/zenoh/jni/JNIZBytes.kt
+++ b/zenoh-jni-runtime/src/jvmAndAndroidMain/kotlin/io/zenoh/jni/JNIZBytes.kt
@@ -15,19 +15,21 @@
package io.zenoh.jni
import io.zenoh.ZenohLoad
-import io.zenoh.bytes.ZBytes
import java.lang.reflect.Type
-@PublishedApi
-internal object JNIZBytes {
+object JNIZBytes {
init {
ZenohLoad
}
+ fun serialize(any: Any, type: Type): ByteArray = serializeViaJNI(any, type)
+
+ fun deserialize(bytes: ByteArray, type: Type): Any = deserializeViaJNI(bytes, type)
+
@JvmStatic
- external fun serializeViaJNI(any: Any, type: Type): ZBytes
+ private external fun serializeViaJNI(any: Any, type: Type): ByteArray
@JvmStatic
- external fun deserializeViaJNI(zBytes: ZBytes, type: Type): Any
+ private external fun deserializeViaJNI(bytes: ByteArray, type: Type): Any
}
diff --git a/zenoh-jni-runtime/src/jvmAndAndroidMain/kotlin/io/zenoh/jni/JNIZBytesKotlin.kt b/zenoh-jni-runtime/src/jvmAndAndroidMain/kotlin/io/zenoh/jni/JNIZBytesKotlin.kt
new file mode 100644
index 00000000..3570b9e6
--- /dev/null
+++ b/zenoh-jni-runtime/src/jvmAndAndroidMain/kotlin/io/zenoh/jni/JNIZBytesKotlin.kt
@@ -0,0 +1,49 @@
+//
+// 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.jni
+
+import io.zenoh.ZenohLoad
+import kotlin.reflect.KType
+
+/**
+ * JNI bridge for Kotlin-type-aware serialization/deserialization.
+ *
+ * Uses [KType] (obtained via `typeOf()` with reified generics) instead of
+ * [java.lang.reflect.Type], making this bridge usable from commonMain Kotlin
+ * code and compatible with Kotlin-specific types (unsigned integers, Pair, Triple).
+ *
+ * Supported types:
+ * - Primitives: Boolean, Byte, Short, Int, Long, Float, Double
+ * - Unsigned (Kotlin-only): UByte, UShort, UInt, ULong
+ * - Text/Binary: String, ByteArray
+ * - Collections: List, Map (recursive)
+ * - Tuples: Pair, Triple
+ */
+object JNIZBytesKotlin {
+
+ init {
+ ZenohLoad
+ }
+
+ fun serialize(any: Any, kType: KType): ByteArray = serializeViaJNI(any, kType)
+
+ fun deserialize(bytes: ByteArray, kType: KType): Any = deserializeViaJNI(bytes, kType)
+
+ @JvmStatic
+ private external fun serializeViaJNI(any: Any, kType: KType): ByteArray
+
+ @JvmStatic
+ private external fun deserializeViaJNI(bytes: ByteArray, kType: KType): Any
+}
diff --git a/zenoh-java/src/jvmMain/kotlin/io/zenoh/Target.kt b/zenoh-jni-runtime/src/jvmMain/kotlin/io/zenoh/Target.kt
similarity index 95%
rename from zenoh-java/src/jvmMain/kotlin/io/zenoh/Target.kt
rename to zenoh-jni-runtime/src/jvmMain/kotlin/io/zenoh/Target.kt
index f3f28256..6460f7ce 100644
--- a/zenoh-java/src/jvmMain/kotlin/io/zenoh/Target.kt
+++ b/zenoh-jni-runtime/src/jvmMain/kotlin/io/zenoh/Target.kt
@@ -1,5 +1,5 @@
//
-// Copyright (c) 2023 ZettaScale Technology
+// 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
diff --git a/zenoh-java/src/jvmMain/kotlin/io/zenoh/Zenoh.kt b/zenoh-jni-runtime/src/jvmMain/kotlin/io/zenoh/ZenohLoad.kt
similarity index 99%
rename from zenoh-java/src/jvmMain/kotlin/io/zenoh/Zenoh.kt
rename to zenoh-jni-runtime/src/jvmMain/kotlin/io/zenoh/ZenohLoad.kt
index 8da47656..aa542844 100644
--- a/zenoh-java/src/jvmMain/kotlin/io/zenoh/Zenoh.kt
+++ b/zenoh-jni-runtime/src/jvmMain/kotlin/io/zenoh/ZenohLoad.kt
@@ -24,7 +24,7 @@ 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 {
+public actual object ZenohLoad {
private const val ZENOH_LIB_NAME = "zenoh_jni"
init {
diff --git a/zenoh-jni-runtime/src/jvmTest/kotlin/io/zenoh/ZBytesInteropTests.kt b/zenoh-jni-runtime/src/jvmTest/kotlin/io/zenoh/ZBytesInteropTests.kt
new file mode 100644
index 00000000..5e8f6bd5
--- /dev/null
+++ b/zenoh-jni-runtime/src/jvmTest/kotlin/io/zenoh/ZBytesInteropTests.kt
@@ -0,0 +1,308 @@
+//
+// 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.jni.JNIZBytes
+import io.zenoh.jni.JNIZBytesKotlin
+import kotlin.reflect.typeOf
+import kotlin.test.Test
+import kotlin.test.assertContentEquals
+import kotlin.test.assertEquals
+
+/**
+ * Tests for Java/Kotlin serialization interoperability at the JNI bridge layer.
+ *
+ * Two groups:
+ * 1. Kotlin round-trips — serialize and deserialize via [JNIZBytesKotlin] (KType path), covering
+ * all supported KotlinType variants including unsigned integers and Pair/Triple.
+ * 2. Cross-path interop — verify that [JNIZBytes] (Java Type path) and [JNIZBytesKotlin]
+ * (KType path) produce identical wire bytes for the common types they share.
+ */
+class ZBytesInteropTests {
+
+ // -------------------------------------------------------------------------
+ // Group 1: Kotlin round-trips via JNIZBytesKotlin
+ // -------------------------------------------------------------------------
+
+ @Test
+ fun testBooleanKotlinRoundTrip() {
+ val input = true
+ val bytes = JNIZBytesKotlin.serialize(input, typeOf())
+ assertEquals(input, JNIZBytesKotlin.deserialize(bytes, typeOf()) as Boolean)
+ }
+
+ @Test
+ fun testByteKotlinRoundTrip() {
+ val input: Byte = 42
+ val bytes = JNIZBytesKotlin.serialize(input, typeOf())
+ assertEquals(input, JNIZBytesKotlin.deserialize(bytes, typeOf()) as Byte)
+ }
+
+ @Test
+ fun testShortKotlinRoundTrip() {
+ val input: Short = 1234
+ val bytes = JNIZBytesKotlin.serialize(input, typeOf())
+ assertEquals(input, JNIZBytesKotlin.deserialize(bytes, typeOf()) as Short)
+ }
+
+ @Test
+ fun testIntKotlinRoundTrip() {
+ val input = 1234567
+ val bytes = JNIZBytesKotlin.serialize(input, typeOf())
+ assertEquals(input, JNIZBytesKotlin.deserialize(bytes, typeOf()) as Int)
+ }
+
+ @Test
+ fun testLongKotlinRoundTrip() {
+ val input = 123456789012345L
+ val bytes = JNIZBytesKotlin.serialize(input, typeOf())
+ assertEquals(input, JNIZBytesKotlin.deserialize(bytes, typeOf()) as Long)
+ }
+
+ @Test
+ fun testFloatKotlinRoundTrip() {
+ val input = 3.1415f
+ val bytes = JNIZBytesKotlin.serialize(input, typeOf())
+ assertEquals(input, JNIZBytesKotlin.deserialize(bytes, typeOf()) as Float, 0.0001f)
+ }
+
+ @Test
+ fun testDoubleKotlinRoundTrip() {
+ val input = 2.718281828
+ val bytes = JNIZBytesKotlin.serialize(input, typeOf())
+ assertEquals(input, JNIZBytesKotlin.deserialize(bytes, typeOf()) as Double, 0.000000001)
+ }
+
+ @Test
+ fun testStringKotlinRoundTrip() {
+ val input = "hello zenoh"
+ val bytes = JNIZBytesKotlin.serialize(input, typeOf())
+ assertEquals(input, JNIZBytesKotlin.deserialize(bytes, typeOf()) as String)
+ }
+
+ @Test
+ fun testByteArrayKotlinRoundTrip() {
+ val input = byteArrayOf(1, 2, 3, 4, 5)
+ val bytes = JNIZBytesKotlin.serialize(input, typeOf())
+ assertContentEquals(input, JNIZBytesKotlin.deserialize(bytes, typeOf()) as ByteArray)
+ }
+
+ // Kotlin-only unsigned types
+
+ @Test
+ fun testUByteKotlinRoundTrip() {
+ val input: UByte = 200u
+ val bytes = JNIZBytesKotlin.serialize(input, typeOf())
+ assertEquals(input, JNIZBytesKotlin.deserialize(bytes, typeOf