diff --git a/.gitignore b/.gitignore index 86cb1e5f..53d109f6 100644 --- a/.gitignore +++ b/.gitignore @@ -19,3 +19,4 @@ /examples/build/ /build/ /.gradle/ +*/build/ diff --git a/examples/build.gradle.kts b/examples/build.gradle.kts index 6afd9c21..af98040a 100644 --- a/examples/build.gradle.kts +++ b/examples/build.gradle.kts @@ -14,6 +14,7 @@ plugins { kotlin("jvm") + kotlin("plugin.serialization") version "1.9.0" } kotlin { @@ -23,17 +24,28 @@ kotlin { dependencies { implementation(project(":zenoh-java")) implementation("commons-net:commons-net:3.9.0") + implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.6.0") + implementation("info.picocli:picocli:4.7.4") + implementation("com.google.guava:guava:33.3.1-jre") } tasks { val examples = listOf( + "ZBytesExamples", "ZDelete", "ZGet", + "ZGetLiveliness", + "ZInfo", + "ZLiveliness", + "ZPing", + "ZPong", "ZPub", "ZPubThr", "ZPut", "ZQueryable", + "ZScout", "ZSub", + "ZSubLiveliness", "ZSubThr" ) diff --git a/examples/src/main/java/io/zenoh/Config.kt b/examples/src/main/java/io/zenoh/Config.kt new file mode 100644 index 00000000..c8102728 --- /dev/null +++ b/examples/src/main/java/io/zenoh/Config.kt @@ -0,0 +1,74 @@ +// +// Copyright (c) 2023 ZettaScale Technology +// +// This program and the accompanying materials are made available under the +// terms of the Eclipse Public License 2.0 which is available at +// http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 +// which is available at https://www.apache.org/licenses/LICENSE-2.0. +// +// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 +// +// Contributors: +// ZettaScale Zenoh Team, +// + +package io.zenoh + +import kotlinx.serialization.Serializable +import kotlinx.serialization.SerialName +import kotlinx.serialization.json.Json +import kotlinx.serialization.json.encodeToJsonElement +import kotlin.io.path.Path + +@Serializable +data class ConfigData( + @SerialName("connect") var connect: Connect? = null, + @SerialName("listen") var listen: Listen? = null, + @SerialName("mode") var mode: String? = null, + @SerialName("scouting") var scouting: Scouting? = null, +) + +@Serializable +data class Connect( + @SerialName("endpoints") var endpoints: List +) + +@Serializable +data class Listen( + @SerialName("endpoints") var endpoints: List +) + +@Serializable +data class Scouting( + @SerialName("multicast") var multicast: Multicast, +) + +@Serializable +data class Multicast( + @SerialName("enabled") var enabled: Boolean, +) + +internal fun loadConfig( + emptyArgs: Boolean, + configFile: String?, + connectEndpoints: List?, + listenEndpoints: List?, + noMulticastScouting: Boolean?, + mode: String? +): Config { + return if (emptyArgs) { + Config.loadDefault() + } else { + configFile?.let { + Config.fromFile(path = Path(it)) + } ?: run { + val connect = connectEndpoints?.let {Connect(it)} + val listen = listenEndpoints?.let {Listen(it)} + val scouting = noMulticastScouting?.let { Scouting(Multicast(!it)) } + val configData = ConfigData(connect, listen, mode, scouting) + val jsonConfig = Json.encodeToJsonElement(configData).toString() + println(jsonConfig) + Config.fromJson(jsonConfig) + } + } +} diff --git a/examples/src/main/java/io/zenoh/QueueHandler.java b/examples/src/main/java/io/zenoh/QueueHandler.java new file mode 100644 index 00000000..cf56fbf4 --- /dev/null +++ b/examples/src/main/java/io/zenoh/QueueHandler.java @@ -0,0 +1,33 @@ +package io.zenoh; + +import io.zenoh.handlers.Handler; + +import java.util.ArrayDeque; + +/** + * Sample handler for the sake of the examples. + * + * A custom handler can be implemented to handle incoming samples, queries or replies for + * subscribers, get operations, query operations or queryables. + * + * The example below shows a queue handler, in which an ArrayDeque is specified as the receiver type. + * The function handle will be called everytime an element of type T is received and in our example + * implementation, elements are simply enqueued into the queue, which can later be retrieved. + */ +class QueueHandler implements Handler> { + + final ArrayDeque queue = new ArrayDeque<>(); + + @Override + public void handle(T t) { + queue.add(t); + } + + @Override + public ArrayDeque receiver() { + return queue; + } + + @Override + public void onClose() {} +} diff --git a/examples/src/main/java/io/zenoh/ZBytesExamples.java b/examples/src/main/java/io/zenoh/ZBytesExamples.java new file mode 100644 index 00000000..2021cf2c --- /dev/null +++ b/examples/src/main/java/io/zenoh/ZBytesExamples.java @@ -0,0 +1,155 @@ +// +// Copyright (c) 2023 ZettaScale Technology +// +// This program and the accompanying materials are made available under the +// terms of the Eclipse Public License 2.0 which is available at +// http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 +// which is available at https://www.apache.org/licenses/LICENSE-2.0. +// +// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 +// +// Contributors: +// ZettaScale Zenoh Team, +// + +package io.zenoh; + +import com.google.common.reflect.TypeToken; +import io.zenoh.bytes.ZBytes; + +import java.util.Arrays; +import java.util.List; +import java.util.Map; + +import static io.zenoh.ext.ZDeserializerKt.zDeserialize; +import static io.zenoh.ext.ZSerializerKt.zSerialize; + +public class ZBytesExamples { + + public static void main(String[] args) { + + /* + * ZBytes + * + * A ZBytes instance can be created from a String and from a Byte Array with the `ZBytes.from(string: String)` + * and `ZBytes.from(bytes: byte[])` functions. + * + * A ZBytes can be converted back into a [String] with the functions [ZBytes.toString] and [ZBytes.tryToString]. + * Similarly, with [ZBytes.toBytes] you can get the inner byte representation. + */ + + var exampleString = "example string"; + var zbytesA = ZBytes.from(exampleString); + var outputA = zbytesA.toString(); + assert exampleString.equals(outputA); + + var exampleBytes = new byte[]{1, 2, 3, 4, 5}; + var zbytesB = ZBytes.from(exampleBytes); + var outputB = zbytesB.toBytes(); + assert Arrays.equals(exampleBytes, outputB); + + /* + * Serialization and deserialization. + * + * Additionally, the Zenoh API provides a series of serialization and deserialization utilities for processing + * the received payloads. + * + * Serialization and deserialization supports the following types: + * - Boolean + * - Byte + * - Byte Array + * - Short + * - Int + * - Long + * - Float + * - Double + * - String + * - List + * - Map + * + * For List and Map, the inner types can be a combination of the above types, including themselves. + * + * These serialization and deserialization utilities can be used across the Zenoh ecosystem with Zenoh + * versions based on other supported languages such as Rust, Python, C and C++. + * This works when the types are equivalent (a `Byte` corresponds to an `i8` in Rust, a `Short` to an `i16`, etc). + */ + + // Boolean example + Boolean input1 = true; + var zbytes1 = zSerialize(input1, new TypeToken<>() {}); + Boolean output1 = zDeserialize(zbytes1, new TypeToken<>() {}); + assert input1.equals(output1); + + // Byte example + Byte input2 = 126; + var zbytes2 = zSerialize(input2, new TypeToken<>() {}); + Byte output2 = zDeserialize(zbytes2, new TypeToken<>() {}); + assert input2.equals(output2); + + // Short example + Short input3 = 256; + var zbytes3 = zSerialize(input3, new TypeToken<>() {}); + Short output3 = zDeserialize(zbytes3, new TypeToken<>() {}); + assert input3.equals(output3); + + // Int example + Integer input4 = 123456; + var zbytes4 = zSerialize(input4, new TypeToken<>() {}); + Integer output4 = zDeserialize(zbytes4, new TypeToken<>() {}); + assert input4.equals(output4); + + // Long example + Long input5 = 123456789L; + var zbytes5 = zSerialize(input5, new TypeToken<>() {}); + Long output5 = zDeserialize(zbytes5, new TypeToken<>() {}); + assert input5.equals(output5); + + // Float example + Float input6 = 123.45f; + var zbytes6 = zSerialize(input6, new TypeToken<>() {}); + Float output6 = zDeserialize(zbytes6, new TypeToken<>() {}); + assert input6.equals(output6); + + // Double example + Double input7 = 12345.6789; + var zbytes7 = zSerialize(input7, new TypeToken<>() {}); + Double output7 = zDeserialize(zbytes7, new TypeToken<>() {}); + assert input7.equals(output7); + + // List example + List input12 = List.of(1, 2, 3, 4, 5); + var zbytes12 = zSerialize(input12, new TypeToken<>() {}); + List output12 = zDeserialize(zbytes12, new TypeToken<>() {}); + assert input12.equals(output12); + + // String example + String input13 = "Hello, World!"; + var zbytes13 = zSerialize(input13, new TypeToken<>() {}); + String output13 = zDeserialize(zbytes13, new TypeToken<>() {}); + assert input13.equals(output13); + + // ByteArray example + byte[] input14 = new byte[]{1, 2, 3, 4, 5}; + var zbytes14 = zSerialize(input14, new TypeToken<>() {}); + byte[] output14 = zDeserialize(zbytes14, new TypeToken<>() {}); + assert Arrays.equals(input14, output14); + + // Map example + Map input15 = Map.of("one", 1, "two", 2, "three", 3); + var zbytes15 = zSerialize(input15, new TypeToken<>() {}); + Map output15 = zDeserialize(zbytes15, new TypeToken<>() {}); + assert input15.equals(output15); + + // Nested List example + List> input18 = List.of(List.of(1, 2, 3)); + var zbytes18 = zSerialize(input18, new TypeToken<>() {}); + List> output18 = zDeserialize(zbytes18, new TypeToken<>() {}); + assert input18.equals(output18); + + // Combined types example + List> input19 = List.of(Map.of("a", 1, "b", 2)); + var zbytes19 = zSerialize(input19, new TypeToken<>() {}); + List> output19 = zDeserialize(zbytes19, new TypeToken<>() {}); + assert input19.equals(output19); + } +} diff --git a/examples/src/main/java/io/zenoh/ZDelete.java b/examples/src/main/java/io/zenoh/ZDelete.java index 7f7e726c..03afce62 100644 --- a/examples/src/main/java/io/zenoh/ZDelete.java +++ b/examples/src/main/java/io/zenoh/ZDelete.java @@ -14,17 +14,89 @@ package io.zenoh; -import io.zenoh.exceptions.ZenohException; +import io.zenoh.exceptions.ZError; import io.zenoh.keyexpr.KeyExpr; +import picocli.CommandLine; -public class ZDelete { - public static void main(String[] args) throws ZenohException { +import java.util.List; +import java.util.concurrent.Callable; + +import static io.zenoh.ConfigKt.loadConfig; + +@CommandLine.Command( + name = "ZDelete", + mixinStandardHelpOptions = true, + description = "Zenoh Delete example" +) +public class ZDelete implements Callable { + + @Override + public Integer call() throws ZError { + Zenoh.initLogFromEnvOr("error"); System.out.println("Opening session..."); - try (Session session = Session.open()) { - try (KeyExpr keyExpr = KeyExpr.tryFrom("demo/example/zenoh-java-put")) { - System.out.println("Deleting resources matching '" + keyExpr + "'..."); - session.delete(keyExpr).res(); - } + Config config = loadConfig(emptyArgs, configFile, connect, listen, noMulticastScouting, mode); + try (Session session = Zenoh.open(config)) { + KeyExpr keyExpr = KeyExpr.tryFrom(key); + System.out.println("Deleting resources matching '" + keyExpr + "'..."); + session.delete(keyExpr); } + return 0; + } + + + /** + * ----- Example CLI arguments and private fields ----- + */ + + private final Boolean emptyArgs; + + ZDelete(Boolean emptyArgs) { + this.emptyArgs = emptyArgs; + } + + @CommandLine.Option( + names = {"-e", "--connect"}, + description = "Endpoints to connect to.", + split = "," + ) + private List connect; + + @CommandLine.Option( + names = {"-l", "--listen"}, + description = "Endpoints to listen on.", + split = "," + ) + private List listen; + + @CommandLine.Option( + names = {"-c", "--config"}, + description = "A configuration file." + ) + private String configFile; + + @CommandLine.Option( + names = {"-k", "--key"}, + description = "The key expression to delete [default: demo/example/zenoh-java-delete].", + defaultValue = "demo/example/zenoh-java-delete" + ) + private String key; + + @CommandLine.Option( + names = {"-m", "--mode"}, + description = "The session mode. Default: peer. Possible values: [peer, client, router].", + defaultValue = "peer" + ) + private String mode; + + @CommandLine.Option( + names = {"--no-multicast-scouting"}, + description = "Disable the multicast-based scouting mechanism.", + defaultValue = "false" + ) + private boolean noMulticastScouting; + + public static void main(String[] args) { + int exitCode = new CommandLine(new ZDelete(args.length == 0)).execute(args); + System.exit(exitCode); } } diff --git a/examples/src/main/java/io/zenoh/ZGet.java b/examples/src/main/java/io/zenoh/ZGet.java index a8dbb262..f8491cf1 100644 --- a/examples/src/main/java/io/zenoh/ZGet.java +++ b/examples/src/main/java/io/zenoh/ZGet.java @@ -14,37 +14,203 @@ package io.zenoh; -import io.zenoh.exceptions.ZenohException; +import io.zenoh.bytes.Encoding; +import io.zenoh.bytes.ZBytes; +import io.zenoh.exceptions.ZError; +import io.zenoh.query.GetOptions; +import io.zenoh.query.Selector; import io.zenoh.query.Reply; -import io.zenoh.selector.Selector; +import io.zenoh.sample.SampleKind; +import picocli.CommandLine; +import java.time.Duration; +import java.util.List; import java.util.Optional; import java.util.concurrent.BlockingQueue; +import java.util.concurrent.Callable; -public class ZGet { +import static io.zenoh.ConfigKt.loadConfig; - public static void main(String[] args) throws ZenohException, InterruptedException { +@CommandLine.Command( + name = "ZGet", + mixinStandardHelpOptions = true, + description = "Zenoh Get example" +) +public class ZGet implements Callable { + + @Override + public Integer call() throws ZError, InterruptedException { + Zenoh.initLogFromEnvOr("error"); System.out.println("Opening session..."); - try (Session session = Session.open()) { - try (Selector selector = Selector.tryFrom("demo/example/**")) { - System.out.println("Performing Get on '" + selector + "'..."); - BlockingQueue> receiver = session.get(selector).res(); - assert receiver != null; - while (true) { - Optional wrapper = receiver.take(); - if (wrapper.isEmpty()) { - break; - } - Reply reply = wrapper.get(); - if (reply instanceof Reply.Success) { - Reply.Success successReply = (Reply.Success) reply; - System.out.println("Received ('" + successReply.getSample().getKeyExpr() + "': '" + successReply.getSample().getValue() + "')"); - } else { - Reply.Error errorReply = (Reply.Error) reply; - System.out.println("Received (ERROR: '" + errorReply.getError() + "')"); - } + + Config config = loadConfig(emptyArgs, configFile, connect, listen, noMulticastScouting, mode); + Selector selector = Selector.tryFrom(this.selectorOpt); + + // A GET query can be performed in different ways, by default (using a blocking queue), using a callback + // or providing a handler. Uncomment one of the function calls below to try out different implementations: + // Implementation with a blocking queue + getExampleDefault(config, selector); + // getExampleWithCallback(config, selector); + // getExampleWithHandler(config, selector); + // getExampleProvidingConfig(config, selector); + + return 0; + } + + private void getExampleDefault(Config config, Selector selector) throws ZError, InterruptedException { + try (Session session = Zenoh.open(config)) { + System.out.println("Performing Get on '" + selector + "'..."); + BlockingQueue> receiver = session.get(selector); + + while (true) { + Optional wrapper = receiver.take(); + if (wrapper.isEmpty()) { + break; } + Reply reply = wrapper.get(); + handleReply(reply); } } } + + /** + * Example using a simple callback for handling the replies. + * @see io.zenoh.handlers.Callback + */ + private void getExampleWithCallback(Config config, Selector selector) throws ZError { + try (Session session = Zenoh.open(config)) { + System.out.println("Performing Get on '" + selector + "'..."); + session.get(selector, this::handleReply); + } + } + + /** + * Example using a custom implementation of a Handler. + * @see QueueHandler + * @see io.zenoh.handlers.Handler + */ + private void getExampleWithHandler(Config config, Selector selector) throws ZError { + try (Session session = Zenoh.open(config)) { + System.out.println("Performing Get on '" + selector + "'..."); + QueueHandler queueHandler = new QueueHandler<>(); + session.get(selector, queueHandler); + } + } + + /** + * The purpose of this example is to show how to provide configuration parameters + * to the get query. For this, you can optionally provide a GetConfig parameter. + * @see GetOptions + */ + private void getExampleProvidingConfig(Config config, Selector selector) throws ZError { + try (Session session = Zenoh.open(config)) { + System.out.println("Performing Get on '" + selector + "'..."); + + // Build the config + GetOptions getOptions = new GetOptions(); + getOptions.setTimeout(Duration.ofMillis(1000)); + getOptions.setEncoding(Encoding.ZENOH_STRING); + getOptions.setPayload(ZBytes.from("Example payload")); + + // Apply the config + session.get(selector, this::handleReply, getOptions); + } + } + + private void handleReply(Reply reply) { + if (reply instanceof Reply.Success) { + Reply.Success successReply = (Reply.Success) reply; + if (successReply.getSample().getKind() == SampleKind.PUT) { + System.out.println("Received ('" + successReply.getSample().getKeyExpr() + "': '" + successReply.getSample().getPayload() + "')"); + } else if (successReply.getSample().getKind() == SampleKind.DELETE) { + System.out.println("Received (DELETE '" + successReply.getSample().getKeyExpr() + "')"); + } + } else { + Reply.Error errorReply = (Reply.Error) reply; + System.out.println("Received (ERROR: '" + errorReply.getError() + "')"); + } + } + + + /** + * ----- Example CLI arguments and private fields ----- + */ + + private final Boolean emptyArgs; + + ZGet(Boolean emptyArgs) { + this.emptyArgs = emptyArgs; + } + + @CommandLine.Option( + names = {"-s", "--selector"}, + description = "The selection of resources to query [default: demo/example/**].", + defaultValue = "demo/example/**" + ) + private String selectorOpt; + + @CommandLine.Option( + names = {"-p", "--payload"}, + description = "An optional payload to put in the query." + ) + private String payload; + + @CommandLine.Option( + names = {"-t", "--target"}, + description = "The target queryables of the query. Default: BEST_MATCHING. " + + "[possible values: BEST_MATCHING, ALL, ALL_COMPLETE]" + ) + private String target; + + @CommandLine.Option( + names = {"-o", "--timeout"}, + description = "The query timeout in milliseconds [default: 10000].", + defaultValue = "10000" + ) + private long timeout; + + @CommandLine.Option( + names = {"-c", "--config"}, + description = "A configuration file." + ) + private String configFile; + + @CommandLine.Option( + names = {"-m", "--mode"}, + description = "The session mode. Default: peer. Possible values: [peer, client, router].", + defaultValue = "peer" + ) + private String mode; + + @CommandLine.Option( + names = {"-e", "--connect"}, + description = "Endpoints to connect to.", + split = "," + ) + private List connect; + + @CommandLine.Option( + names = {"-l", "--listen"}, + description = "Endpoints to listen on.", + split = "," + ) + private List listen; + + @CommandLine.Option( + names = {"-a", "--attach"}, + description = "The attachment to add to the get. The key-value pairs are &-separated, and = serves as the separator between key and value." + ) + private String attachment; + + @CommandLine.Option( + names = {"--no-multicast-scouting"}, + description = "Disable the multicast-based scouting mechanism.", + defaultValue = "false" + ) + private boolean noMulticastScouting; + + public static void main(String[] args) { + int exitCode = new CommandLine(new ZGet(args.length == 0)).execute(args); + System.exit(exitCode); + } } diff --git a/examples/src/main/java/io/zenoh/ZGetLiveliness.java b/examples/src/main/java/io/zenoh/ZGetLiveliness.java new file mode 100644 index 00000000..423bf402 --- /dev/null +++ b/examples/src/main/java/io/zenoh/ZGetLiveliness.java @@ -0,0 +1,162 @@ +// +// 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. +// +// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 +// +// Contributors: +// ZettaScale Zenoh Team, +// + +package io.zenoh; + +import io.zenoh.exceptions.ZError; +import io.zenoh.keyexpr.KeyExpr; +import io.zenoh.query.Reply; +import picocli.CommandLine; + +import java.time.Duration; +import java.util.List; +import java.util.Optional; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.Callable; + +import static io.zenoh.ConfigKt.loadConfig; + +@CommandLine.Command( + name = "ZGetLiveliness", + mixinStandardHelpOptions = true, + description = "Zenoh Get Liveliness example" +) +public class ZGetLiveliness implements Callable { + + @Override + public Integer call() throws Exception { + Zenoh.initLogFromEnvOr("error"); + + Config config = loadConfig(emptyArgs, configFile, connect, listen, noMulticastScouting, mode); + KeyExpr keyExpr = KeyExpr.tryFrom(this.key); + + // Uncomment one of the lines below to try out different implementations: + getLivelinessWithBlockingQueue(config, keyExpr); + // getLivelinessWithCallback(config, keyExpr); + // getLivelinessWithHandler(config, keyExpr); + + return 0; + } + + /** + * Default implementation using a blocking queue to handle replies. + */ + private void getLivelinessWithBlockingQueue(Config config, KeyExpr keyExpr) throws ZError, InterruptedException { + try (Session session = Zenoh.open(config)) { + BlockingQueue> replyQueue = session.liveliness().get(keyExpr, Duration.ofMillis(timeout)); + + while (true) { + Optional wrapper = replyQueue.take(); + if (wrapper.isEmpty()) { + break; + } + handleReply(wrapper.get()); + } + } + } + + /** + * Example using a callback to handle liveliness replies asynchronously. + * @see io.zenoh.handlers.Callback + */ + private void getLivelinessWithCallback(Config config, KeyExpr keyExpr) throws ZError { + try (Session session = Zenoh.open(config)) { + session.liveliness().get(keyExpr, this::handleReply, Duration.ofMillis(timeout)); + } + } + + /** + * Example using a custom handler to process liveliness replies. + * @see QueueHandler + * @see io.zenoh.handlers.Handler + */ + private void getLivelinessWithHandler(Config config, KeyExpr keyExpr) throws ZError { + try (Session session = Zenoh.open(config)) { + QueueHandler queueHandler = new QueueHandler<>(); + session.liveliness().get(keyExpr, queueHandler, Duration.ofMillis(timeout)); + } + } + + private void handleReply(Reply reply) { + if (reply instanceof Reply.Success) { + Reply.Success successReply = (Reply.Success) reply; + System.out.println(">> Alive token ('" + successReply.getSample().getKeyExpr() + "')"); + } else if (reply instanceof Reply.Error) { + Reply.Error errorReply = (Reply.Error) reply; + System.out.println(">> Received (ERROR: '" + errorReply.getError() + "')"); + } + } + + /** + * ----- Example arguments and private fields ----- + */ + + private final Boolean emptyArgs; + + ZGetLiveliness(Boolean emptyArgs) { + this.emptyArgs = emptyArgs; + } + + @CommandLine.Option( + names = {"-c", "--config"}, + description = "A configuration file." + ) + private String configFile; + + @CommandLine.Option( + names = {"-k", "--key"}, + description = "The key expression matching liveliness tokens to query. [default: group1/**].", + defaultValue = "group1/**" + ) + private String key; + + @CommandLine.Option( + names = {"-o", "--timeout"}, + description = "The query timeout in milliseconds [default: 10000].", + defaultValue = "10000" + ) + private long timeout; + + @CommandLine.Option( + names = {"-e", "--connect"}, + description = "Endpoints to connect to.", + split = "," + ) + private List connect; + + @CommandLine.Option( + names = {"-l", "--listen"}, + description = "Endpoints to listen on.", + split = "," + ) + private List listen; + + @CommandLine.Option( + names = {"-m", "--mode"}, + description = "The session mode. Default: peer. Possible values: [peer, client, router].", + defaultValue = "peer" + ) + private String mode; + + @CommandLine.Option( + names = {"--no-multicast-scouting"}, + description = "Disable the multicast-based scouting mechanism.", + defaultValue = "false" + ) + private boolean noMulticastScouting; + + public static void main(String[] args) { + int exitCode = new CommandLine(new ZGetLiveliness(args.length == 0)).execute(args); + System.exit(exitCode); + } +} diff --git a/examples/src/main/java/io/zenoh/ZInfo.java b/examples/src/main/java/io/zenoh/ZInfo.java new file mode 100644 index 00000000..4d4bdb53 --- /dev/null +++ b/examples/src/main/java/io/zenoh/ZInfo.java @@ -0,0 +1,101 @@ +// +// Copyright (c) 2023 ZettaScale Technology +// +// This program and the accompanying materials are made available under the +// terms of the Eclipse Public License 2.0 which is available at +// http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 +// which is available at https://www.apache.org/licenses/LICENSE-2.0. +// +// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 +// +// Contributors: +// ZettaScale Zenoh Team, +// + +package io.zenoh; + +import io.zenoh.exceptions.ZError; +import io.zenoh.session.SessionInfo; +import picocli.CommandLine; + +import java.util.List; +import java.util.concurrent.Callable; + +import static io.zenoh.ConfigKt.loadConfig; + +@CommandLine.Command( + name = "ZInfo", + mixinStandardHelpOptions = true, + description = "Zenoh Info example" +) +public class ZInfo implements Callable { + + + @Override + public Integer call() throws Exception { + Zenoh.initLogFromEnvOr("error"); + + Config config = loadConfig(emptyArgs, configFile, connect, listen, noMulticastScouting, mode); + + System.out.println("Opening session..."); + try (Session session = Zenoh.open(config)) { + SessionInfo info = session.info(); + System.out.println("zid: " + info.zid()); + System.out.println("routers zid: " + info.routersZid()); + System.out.println("peers zid: " + info.peersZid()); + } catch (ZError e) { + System.err.println("Error during Zenoh operation: " + e.getMessage()); + return 1; + } + return 0; + } + + /** + * ----- Example CLI arguments and private fields ----- + */ + + private final Boolean emptyArgs; + + ZInfo(Boolean emptyArgs) { + this.emptyArgs = emptyArgs; + } + + @CommandLine.Option( + names = {"-c", "--config"}, + description = "A configuration file." + ) + private String configFile; + + @CommandLine.Option( + names = {"-e", "--connect"}, + description = "Endpoints to connect to.", + split = "," + ) + private List connect; + + @CommandLine.Option( + names = {"-l", "--listen"}, + description = "Endpoints to listen on.", + split = "," + ) + private List listen; + + @CommandLine.Option( + names = {"-m", "--mode"}, + description = "The session mode. Default: peer. Possible values: [peer, client, router].", + defaultValue = "peer" + ) + private String mode; + + @CommandLine.Option( + names = {"--no-multicast-scouting"}, + description = "Disable the multicast-based scouting mechanism.", + defaultValue = "false" + ) + private boolean noMulticastScouting; + + public static void main(String[] args) { + int exitCode = new CommandLine(new ZInfo(args.length == 0)).execute(args); + System.exit(exitCode); + } +} diff --git a/examples/src/main/java/io/zenoh/ZLiveliness.java b/examples/src/main/java/io/zenoh/ZLiveliness.java new file mode 100644 index 00000000..b1df3c29 --- /dev/null +++ b/examples/src/main/java/io/zenoh/ZLiveliness.java @@ -0,0 +1,113 @@ +// +// Copyright (c) 2023 ZettaScale Technology +// +// This program and the accompanying materials are made available under the +// terms of the Eclipse Public License 2.0 which is available at +// http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 +// which is available at https://www.apache.org/licenses/LICENSE-2.0. +// +// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 +// +// Contributors: +// ZettaScale Zenoh Team, +// + +package io.zenoh; + +import io.zenoh.exceptions.ZError; +import io.zenoh.keyexpr.KeyExpr; +import picocli.CommandLine; + +import java.util.List; +import java.util.concurrent.Callable; + +import static io.zenoh.ConfigKt.loadConfig; + +@CommandLine.Command( + name = "ZLiveliness", + mixinStandardHelpOptions = true, + description = "Zenoh Liveliness example" +) +public class ZLiveliness implements Callable { + + + @Override + public Integer call() throws Exception { + Zenoh.initLogFromEnvOr("error"); + + Config config = loadConfig(emptyArgs, configFile, connect, listen, noMulticastScouting, mode); + + System.out.println("Opening session..."); + try (Session session = Zenoh.open(config)) { + KeyExpr keyExpr = KeyExpr.tryFrom(key); + session.liveliness().declareToken(keyExpr); + System.out.println("Liveliness token declared for key: " + key); + + while (true) { + Thread.sleep(1000); + } + + } catch (ZError e) { + System.err.println("Error during Zenoh operation: " + e.getMessage()); + return 1; + } + } + + + /** + * ----- Example CLI arguments and private fields ----- + */ + + private final Boolean emptyArgs; + + ZLiveliness(Boolean emptyArgs) { + this.emptyArgs = emptyArgs; + } + + @CommandLine.Option( + names = {"-c", "--config"}, + description = "A configuration file." + ) + private String configFile; + + @CommandLine.Option( + names = {"-k", "--key"}, + description = "The key expression to declare liveliness tokens for [default: group1/zenoh-java].", + defaultValue = "group1/zenoh-java" + ) + private String key; + + @CommandLine.Option( + names = {"-e", "--connect"}, + description = "Endpoints to connect to.", + split = "," + ) + private List connect; + + @CommandLine.Option( + names = {"-l", "--listen"}, + description = "Endpoints to listen on.", + split = "," + ) + private List listen; + + @CommandLine.Option( + names = {"-m", "--mode"}, + description = "The session mode. Default: peer. Possible values: [peer, client, router].", + defaultValue = "peer" + ) + private String mode; + + @CommandLine.Option( + names = {"--no-multicast-scouting"}, + description = "Disable the multicast-based scouting mechanism.", + defaultValue = "false" + ) + private boolean noMulticastScouting; + + + public static void main(String[] args) { + int exitCode = new CommandLine(new ZLiveliness(args.length == 0)).execute(args); + System.exit(exitCode); + } +} diff --git a/examples/src/main/java/io/zenoh/ZPing.java b/examples/src/main/java/io/zenoh/ZPing.java new file mode 100644 index 00000000..76b3017c --- /dev/null +++ b/examples/src/main/java/io/zenoh/ZPing.java @@ -0,0 +1,167 @@ +// +// Copyright (c) 2023 ZettaScale Technology +// +// This program and the accompanying materials are made available under the +// terms of the Eclipse Public License 2.0 which is available at +// http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 +// which is available at https://www.apache.org/licenses/LICENSE-2.0. +// +// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 +// +// Contributors: +// ZettaScale Zenoh Team, +// + +package io.zenoh; + +import io.zenoh.bytes.ZBytes; +import io.zenoh.exceptions.ZError; +import io.zenoh.keyexpr.KeyExpr; +import io.zenoh.pubsub.Publisher; +import io.zenoh.pubsub.PublisherOptions; +import io.zenoh.qos.CongestionControl; +import io.zenoh.sample.Sample; +import picocli.CommandLine; + +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.Callable; + +import static io.zenoh.ConfigKt.loadConfig; + +@CommandLine.Command( + name = "ZPing", + mixinStandardHelpOptions = true, + description = "Zenoh Ping example" +) +public class ZPing implements Callable { + + @Override + public Integer call() throws Exception { + Zenoh.initLogFromEnvOr("error"); + + // Load Zenoh configuration + Config config = loadConfig(true, configFile, connect, listen, noMulticastScouting, mode); + + System.out.println("Opening session..."); + try (Session session = Zenoh.open(config)) { + KeyExpr keyExprPing = KeyExpr.tryFrom("test/ping"); + KeyExpr keyExprPong = KeyExpr.tryFrom("test/pong"); + + BlockingQueue> receiverQueue = + session.declareSubscriber(keyExprPong).getReceiver(); + + var publisherOptions = new PublisherOptions(); + publisherOptions.setCongestionControl(CongestionControl.BLOCK); + publisherOptions.setExpress(!noExpress); + Publisher publisher = session.declarePublisher(keyExprPing, publisherOptions); + + byte[] data = new byte[payloadSize]; + for (int i = 0; i < payloadSize; i++) { + data[i] = (byte) (i % 10); + } + ZBytes payload = ZBytes.from(data); + + // Warm-up + System.out.println("Warming up for " + warmup + " seconds..."); + long warmupEnd = System.currentTimeMillis() + (long) (warmup * 1000); + while (System.currentTimeMillis() < warmupEnd) { + publisher.put(payload); + receiverQueue.take(); + } + + List samples = new ArrayList<>(); + for (int i = 0; i < n; i++) { + long startTime = System.nanoTime(); + publisher.put(payload); + receiverQueue.take(); + long elapsedTime = (System.nanoTime() - startTime) / 1000; // Convert to microseconds + samples.add(elapsedTime); + } + + for (int i = 0; i < samples.size(); i++) { + long rtt = samples.get(i); + System.out.printf("%d bytes: seq=%d rtt=%dµs lat=%dµs%n", payloadSize, i, rtt, rtt / 2); + } + } catch (ZError e) { + System.err.println("Error: " + e.getMessage()); + return 1; + } + + return 0; + } + + + /** + * ----- Example CLI arguments and private fields ----- + */ + + @CommandLine.Parameters( + paramLabel = "payload_size", + description = "Sets the size of the payload to publish [default: 8].", + defaultValue = "8" + ) + private int payloadSize; + + @CommandLine.Option( + names = "--no-express", + description = "Express for sending data.", + defaultValue = "false" + ) + private boolean noExpress; + + @CommandLine.Option( + names = {"-w", "--warmup"}, + description = "The number of seconds to warm up [default: 1.0].", + defaultValue = "1.0" + ) + private double warmup; + + @CommandLine.Option( + names = {"-n", "--samples"}, + description = "The number of round-trips to measure [default: 100].", + defaultValue = "100" + ) + private int n; + + @CommandLine.Option( + names = {"-c", "--config"}, + description = "A configuration file." + ) + private String configFile; + + @CommandLine.Option( + names = {"-e", "--connect"}, + description = "Endpoints to connect to.", + split = "," + ) + private List connect; + + @CommandLine.Option( + names = {"-l", "--listen"}, + description = "Endpoints to listen on.", + split = "," + ) + private List listen; + + @CommandLine.Option( + names = {"-m", "--mode"}, + description = "The session mode. Default: peer. Possible values: [peer, client, router].", + defaultValue = "peer" + ) + private String mode; + + @CommandLine.Option( + names = {"--no-multicast-scouting"}, + description = "Disable the multicast-based scouting mechanism.", + defaultValue = "false" + ) + private boolean noMulticastScouting; + + public static void main(String[] args) { + int exitCode = new CommandLine(new ZPing()).execute(args); + System.exit(exitCode); + } +} diff --git a/examples/src/main/java/io/zenoh/ZPong.java b/examples/src/main/java/io/zenoh/ZPong.java new file mode 100644 index 00000000..580bd998 --- /dev/null +++ b/examples/src/main/java/io/zenoh/ZPong.java @@ -0,0 +1,127 @@ +// +// Copyright (c) 2023 ZettaScale Technology +// +// This program and the accompanying materials are made available under the +// terms of the Eclipse Public License 2.0 which is available at +// http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 +// which is available at https://www.apache.org/licenses/LICENSE-2.0. +// +// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 +// +// Contributors: +// ZettaScale Zenoh Team, +// + +package io.zenoh; + +import io.zenoh.exceptions.ZError; +import io.zenoh.keyexpr.KeyExpr; +import io.zenoh.pubsub.Publisher; +import io.zenoh.pubsub.PublisherOptions; +import io.zenoh.qos.CongestionControl; +import picocli.CommandLine; + +import java.util.List; +import java.util.concurrent.Callable; +import java.util.concurrent.CountDownLatch; + +import static io.zenoh.ConfigKt.loadConfig; + +@CommandLine.Command( + name = "ZPong", + mixinStandardHelpOptions = true, + description = "Zenoh ZPong example" +) +public class ZPong implements Callable { + + @Override + public Integer call() throws Exception { + Zenoh.initLogFromEnvOr("error"); + + Config config = loadConfig(true, configFile, connect, listen, noMulticastScouting, mode); + + System.out.println("Opening session..."); + try (Session session = Zenoh.open(config)) { + KeyExpr keyExprPing = KeyExpr.tryFrom("test/ping"); + KeyExpr keyExprPong = KeyExpr.tryFrom("test/pong"); + + var publisherOptions = new PublisherOptions(); + publisherOptions.setCongestionControl(CongestionControl.BLOCK); + publisherOptions.setExpress(!noExpress); + + Publisher publisher = session.declarePublisher(keyExprPong, publisherOptions); + + session.declareSubscriber(keyExprPing, sample -> { + try { + publisher.put(sample.getPayload()); + } catch (ZError e) { + System.err.println("Error responding to ping: " + e.getMessage()); + } + }); + + latch.await(); + } catch (ZError e) { + System.err.println("Error: " + e.getMessage()); + return 1; + } + return 0; + } + + + /** + * ----- Example CLI arguments and private fields ----- + */ + + @CommandLine.Option( + names = "--no-express", + description = "Express for sending data.", + defaultValue = "false" + ) + private boolean noExpress; + + @CommandLine.Option( + names = {"-c", "--config"}, + description = "A configuration file." + ) + private String configFile; + + @CommandLine.Option( + names = {"-e", "--connect"}, + description = "Endpoints to connect to.", + split = "," + ) + private List connect; + + @CommandLine.Option( + names = {"-l", "--listen"}, + description = "Endpoints to listen on.", + split = "," + ) + private List listen; + + @CommandLine.Option( + names = {"-m", "--mode"}, + description = "The session mode. Default: peer. Possible values: [peer, client, router].", + defaultValue = "peer" + ) + private String mode; + + @CommandLine.Option( + names = {"--no-multicast-scouting"}, + description = "Disable the multicast-based scouting mechanism.", + defaultValue = "false" + ) + private boolean noMulticastScouting; + + private static final CountDownLatch latch = new CountDownLatch(1); + + public static void main(String[] args) { + Runtime.getRuntime().addShutdownHook(new Thread(() -> { + System.out.println("Shutting down..."); + latch.countDown(); + })); + + int exitCode = new CommandLine(new ZPong()).execute(args); + System.exit(exitCode); + } +} diff --git a/examples/src/main/java/io/zenoh/ZPub.java b/examples/src/main/java/io/zenoh/ZPub.java index 24965fc8..319772a9 100644 --- a/examples/src/main/java/io/zenoh/ZPub.java +++ b/examples/src/main/java/io/zenoh/ZPub.java @@ -14,28 +14,137 @@ package io.zenoh; -import io.zenoh.exceptions.ZenohException; +import io.zenoh.bytes.Encoding; +import io.zenoh.bytes.ZBytes; +import io.zenoh.exceptions.ZError; import io.zenoh.keyexpr.KeyExpr; -import io.zenoh.publication.Publisher; +import io.zenoh.pubsub.Publisher; +import io.zenoh.pubsub.PublisherOptions; +import io.zenoh.pubsub.PutOptions; +import io.zenoh.qos.CongestionControl; +import io.zenoh.qos.Reliability; +import picocli.CommandLine; + +import java.util.List; +import java.util.concurrent.Callable; + +import static io.zenoh.ConfigKt.loadConfig; + +@CommandLine.Command( + name = "ZPub", + mixinStandardHelpOptions = true, + description = "Zenoh Pub example" +) +public class ZPub implements Callable { + + @Override + public Integer call() throws ZError { + Zenoh.initLogFromEnvOr("error"); + Config config = loadConfig(emptyArgs, configFile, connect, listen, noMulticastScouting, mode); -public class ZPub { - public static void main(String[] args) throws ZenohException, InterruptedException { System.out.println("Opening session..."); - try (Session session = Session.open()) { - try (KeyExpr keyExpr = KeyExpr.tryFrom("demo/example/zenoh-java-pub")) { - System.out.println("Declaring publisher on '" + keyExpr + "'..."); - try (Publisher publisher = session.declarePublisher(keyExpr).res()) { - System.out.println("Press CTRL-C to quit..."); - int idx = 0; - while (true) { - Thread.sleep(1000); - String payload = String.format("[%4s] Pub from Java!", idx); - System.out.println("Putting Data ('" + keyExpr + "': '"+payload+"')..."); - publisher.put(payload).res(); - idx++; - } + try (Session session = Zenoh.open(config)) { + KeyExpr keyExpr = KeyExpr.tryFrom(key); + System.out.println("Declaring publisher on '" + keyExpr + "'..."); + + // A publisher config can optionally be provided. + PublisherOptions publisherOptions = new PublisherOptions(); + publisherOptions.setEncoding(Encoding.ZENOH_STRING); + publisherOptions.setCongestionControl(CongestionControl.BLOCK); + publisherOptions.setReliability(Reliability.RELIABLE); + + // Declare the publisher + Publisher publisher = session.declarePublisher(keyExpr, publisherOptions); + + System.out.println("Press CTRL-C to quit..."); + ZBytes attachmentBytes = attachment != null ? ZBytes.from(attachment) : null; + int idx = 0; + while (true) { + Thread.sleep(1000); + String payload = String.format("[%4d] %s", idx, value); + System.out.println("Putting Data ('" + keyExpr + "': '" + payload + "')..."); + if (attachmentBytes != null) { + PutOptions putOptions = new PutOptions(); + putOptions.setAttachment(attachmentBytes); + publisher.put(ZBytes.from(payload), putOptions); + } else { + publisher.put(ZBytes.from(payload)); } + idx++; } + } catch (Exception e) { + System.err.println("Error: " + e.getMessage()); + return 1; } } + + + /** + * ----- Example CLI arguments and private fields ----- + */ + + private final Boolean emptyArgs; + + ZPub(Boolean emptyArgs) { + this.emptyArgs = emptyArgs; + } + + @CommandLine.Option( + names = {"-k", "--key"}, + description = "The key expression to write to [default: demo/example/zenoh-java-pub].", + defaultValue = "demo/example/zenoh-java-pub" + ) + private String key; + + @CommandLine.Option( + names = {"-c", "--config"}, + description = "A configuration file." + ) + private String configFile; + + @CommandLine.Option( + names = {"-e", "--connect"}, + description = "Endpoints to connect to.", + split = "," + ) + private List connect; + + @CommandLine.Option( + names = {"-l", "--listen"}, + description = "Endpoints to listen on.", + split = "," + ) + private List listen; + + @CommandLine.Option( + names = {"-m", "--mode"}, + description = "The session mode. Default: peer. Possible values: [peer, client, router].", + defaultValue = "peer" + ) + private String mode; + + @CommandLine.Option( + names = {"-v", "--value"}, + description = "The value to write. [default: 'Pub from Java!']", + defaultValue = "Pub from Java!" + ) + private String value; + + @CommandLine.Option( + names = {"-a", "--attach"}, + description = "The attachments to add to each put. The key-value pairs are &-separated, and = serves as the separator between key and value." + ) + private String attachment; + + @CommandLine.Option( + names = {"--no-multicast-scouting"}, + description = "Disable the multicast-based scouting mechanism.", + defaultValue = "false" + ) + private boolean noMulticastScouting; + + public static void main(String[] args) { + int exitCode = new CommandLine(new ZPub(args.length == 0)).execute(args); + System.exit(exitCode); + } } diff --git a/examples/src/main/java/io/zenoh/ZPubThr.java b/examples/src/main/java/io/zenoh/ZPubThr.java index 1d1b42d9..8482f2b5 100644 --- a/examples/src/main/java/io/zenoh/ZPubThr.java +++ b/examples/src/main/java/io/zenoh/ZPubThr.java @@ -14,32 +14,142 @@ package io.zenoh; -import io.zenoh.exceptions.ZenohException; +import io.zenoh.bytes.ZBytes; import io.zenoh.keyexpr.KeyExpr; -import io.zenoh.prelude.CongestionControl; -import io.zenoh.prelude.Encoding; -import io.zenoh.publication.Publisher; -import io.zenoh.value.Value; +import io.zenoh.pubsub.Publisher; +import io.zenoh.pubsub.PublisherOptions; +import io.zenoh.qos.CongestionControl; +import io.zenoh.qos.Priority; +import picocli.CommandLine; -public class ZPubThr { +import java.util.List; +import java.util.concurrent.Callable; - public static void main(String[] args) throws ZenohException { - int size = 8; - byte[] data = new byte[size]; - for (int i = 0; i < size; i++) { +import static io.zenoh.ConfigKt.loadConfig; + +@CommandLine.Command( + name = "ZPubThr", + mixinStandardHelpOptions = true, + description = "Zenoh Throughput example" +) +public class ZPubThr implements Callable { + + @Override + public Integer call() throws Exception { + Zenoh.initLogFromEnvOr("error"); + + byte[] data = new byte[payloadSize]; + for (int i = 0; i < payloadSize; i++) { data[i] = (byte) (i % 10); } - Value value = new Value(data, new Encoding(Encoding.ID.ZENOH_BYTES, null)); - try (Session session = Session.open()) { - try (KeyExpr keyExpr = KeyExpr.tryFrom("test/thr")) { - try (Publisher publisher = session.declarePublisher(keyExpr).congestionControl(CongestionControl.BLOCK).res()) { - System.out.println("Publisher declared on test/thr."); - System.out.println("Press CTRL-C to quit..."); - while (true) { - publisher.put(value).res(); + ZBytes payload = ZBytes.from(data); + + Config config = loadConfig(emptyArgs, configFile, connect, listen, noMulticastScouting, mode); + + try (Session session = Zenoh.open(config)) { + KeyExpr keyExpr = KeyExpr.tryFrom("test/thr"); + var publisherOptions = new PublisherOptions(); + publisherOptions.setCongestionControl(CongestionControl.BLOCK); + publisherOptions.setPriority(priorityInput != null ? Priority.getEntries().get(priorityInput) : Priority.DATA); + try (Publisher publisher = session.declarePublisher(keyExpr, publisherOptions)) { + System.out.println("Publisher declared on test/thr."); + long count = 0; + long start = System.currentTimeMillis(); + System.out.println("Press CTRL-C to quit..."); + + while (true) { + publisher.put(payload); + + if (statsPrint) { + if (count < number) { + count++; + } else { + long elapsedTime = System.currentTimeMillis() - start; + long throughput = (count * 1000) / elapsedTime; + System.out.println(throughput + " msgs/s"); + count = 0; + start = System.currentTimeMillis(); + } } } } } } + + + /** + * ----- Example CLI arguments and private fields ----- + */ + + private final Boolean emptyArgs; + + ZPubThr(Boolean emptyArgs) { + this.emptyArgs = emptyArgs; + } + + @CommandLine.Parameters( + index = "0", + description = "Sets the size of the payload to publish [default: 8].", + defaultValue = "8" + ) + private int payloadSize; + + @CommandLine.Option( + names = {"-p", "--priority"}, + description = "Priority for sending data." + ) + private Integer priorityInput; + + @CommandLine.Option( + names = {"-n", "--number"}, + description = "Number of messages in each throughput measurement [default: 100000].", + defaultValue = "100000" + ) + private long number; + + @CommandLine.Option( + names = {"-t", "--print"}, + description = "Print the statistics.", + defaultValue = "true" + ) + private boolean statsPrint; + + @CommandLine.Option( + names = {"-c", "--config"}, + description = "A configuration file." + ) + private String configFile; + + @CommandLine.Option( + names = {"-e", "--connect"}, + description = "Endpoints to connect to.", + split = "," + ) + private List connect; + + @CommandLine.Option( + names = {"-l", "--listen"}, + description = "Endpoints to listen on.", + split = "," + ) + private List listen; + + @CommandLine.Option( + names = {"-m", "--mode"}, + description = "The session mode. Default: peer. Possible values: [peer, client, router].", + defaultValue = "peer" + ) + private String mode; + + @CommandLine.Option( + names = {"--no-multicast-scouting"}, + description = "Disable the multicast-based scouting mechanism.", + defaultValue = "false" + ) + private boolean noMulticastScouting; + + public static void main(String[] args) { + int exitCode = new CommandLine(new ZPubThr(args.length == 0)).execute(args); + System.exit(exitCode); + } } diff --git a/examples/src/main/java/io/zenoh/ZPut.java b/examples/src/main/java/io/zenoh/ZPut.java index 60317b81..11172062 100644 --- a/examples/src/main/java/io/zenoh/ZPut.java +++ b/examples/src/main/java/io/zenoh/ZPut.java @@ -14,24 +14,116 @@ package io.zenoh; -import io.zenoh.exceptions.ZenohException; +import io.zenoh.bytes.ZBytes; +import io.zenoh.exceptions.ZError; import io.zenoh.keyexpr.KeyExpr; -import io.zenoh.prelude.SampleKind; -import io.zenoh.prelude.CongestionControl; -import io.zenoh.prelude.Priority; +import io.zenoh.pubsub.PutOptions; +import picocli.CommandLine; + +import java.util.List; +import java.util.concurrent.Callable; + +import static io.zenoh.ConfigKt.loadConfig; + +@CommandLine.Command( + name = "ZPut", + mixinStandardHelpOptions = true, + description = "Zenoh Put example" +) +public class ZPut implements Callable { + + @Override + public Integer call() throws Exception { + Zenoh.initLogFromEnvOr("error"); + + Config config = loadConfig(emptyArgs, configFile, connect, listen, noMulticastScouting, mode); -public class ZPut { - public static void main(String[] args) throws ZenohException { System.out.println("Opening session..."); - try (Session session = Session.open()) { - try (KeyExpr keyExpr = KeyExpr.tryFrom("demo/example/zenoh-java-put")) { - String value = "Put from Java!"; - session.put(keyExpr, value) - .congestionControl(CongestionControl.BLOCK) - .priority(Priority.REALTIME) - .res(); - System.out.println("Putting Data ('" + keyExpr + "': '" + value + "')..."); + try (Session session = Zenoh.open(config)) { + KeyExpr keyExpr = KeyExpr.tryFrom(key); + System.out.println("Putting Data ('" + keyExpr + "': '" + value + "')..."); + if (attachment != null) { + var putOptions = new PutOptions(); + putOptions.setAttachment(ZBytes.from(attachment)); + session.put(keyExpr, ZBytes.from(value), putOptions); + } else { + session.put(keyExpr, ZBytes.from(value)); } + } catch (ZError e) { + System.err.println("Error during Zenoh operation: " + e.getMessage()); + return 1; } + + return 0; + } + + + /** + * ----- Example CLI arguments and private fields ----- + */ + + private final Boolean emptyArgs; + + ZPut(Boolean emptyArgs) { + this.emptyArgs = emptyArgs; + } + + @CommandLine.Option( + names = {"-c", "--config"}, + description = "A configuration file." + ) + private String configFile; + + @CommandLine.Option( + names = {"-k", "--key"}, + description = "The key expression to write to [default: demo/example/zenoh-java-put].", + defaultValue = "demo/example/zenoh-java-put" + ) + private String key; + + @CommandLine.Option( + names = {"-e", "--connect"}, + description = "Endpoints to connect to.", + split = "," + ) + private List connect; + + @CommandLine.Option( + names = {"-l", "--listen"}, + description = "Endpoints to listen on.", + split = "," + ) + private List listen; + + @CommandLine.Option( + names = {"-m", "--mode"}, + description = "The session mode. Default: peer. Possible values: [peer, client, router].", + defaultValue = "peer" + ) + private String mode; + + @CommandLine.Option( + names = {"-v", "--value"}, + description = "The value to write. [default: 'Put from Java!'].", + defaultValue = "Put from Java!" + ) + private String value; + + @CommandLine.Option( + names = {"-a", "--attach"}, + description = "The attachment to add to the put. The key-value pairs are &-separated, and = serves as the separator between key and value." + ) + private String attachment; + + @CommandLine.Option( + names = {"--no-multicast-scouting"}, + description = "Disable the multicast-based scouting mechanism.", + defaultValue = "false" + ) + private boolean noMulticastScouting; + + public static void main(String[] args) { + int exitCode = new CommandLine(new ZPut(args.length == 0)).execute(args); + System.exit(exitCode); } } diff --git a/examples/src/main/java/io/zenoh/ZQueryable.java b/examples/src/main/java/io/zenoh/ZQueryable.java index c5a9c872..6cb68e00 100644 --- a/examples/src/main/java/io/zenoh/ZQueryable.java +++ b/examples/src/main/java/io/zenoh/ZQueryable.java @@ -14,47 +14,158 @@ package io.zenoh; -import io.zenoh.exceptions.ZenohException; +import io.zenoh.bytes.ZBytes; +import io.zenoh.exceptions.ZError; import io.zenoh.keyexpr.KeyExpr; -import io.zenoh.prelude.SampleKind; -import io.zenoh.queryable.Query; -import io.zenoh.queryable.Queryable; +import io.zenoh.query.Query; +import io.zenoh.query.QueryableOptions; +import io.zenoh.query.ReplyOptions; import org.apache.commons.net.ntp.TimeStamp; +import picocli.CommandLine; +import java.util.List; import java.util.Optional; import java.util.concurrent.BlockingQueue; +import java.util.concurrent.Callable; -public class ZQueryable { - - public static void main(String[] args) throws ZenohException, InterruptedException { - String keyExprString = "demo/example/zenoh-java-queryable"; - try (Session session = Session.open()) { - try (KeyExpr keyExpr = KeyExpr.tryFrom(keyExprString)) { - System.out.println("Declaring Queryable on " + keyExprString + "..."); - try (Queryable>> queryable = session.declareQueryable(keyExpr).res()) { - BlockingQueue> receiver = queryable.getReceiver(); - assert receiver != null; - System.out.println("Press CTRL-C to quit..."); - handleRequests(receiver, keyExpr); +import static io.zenoh.ConfigKt.loadConfig; + +@CommandLine.Command( + name = "ZQueryable", + mixinStandardHelpOptions = true, + description = "Zenoh Queryable example" +) +public class ZQueryable implements Callable { + + @Override + public Integer call() throws Exception { + Zenoh.initLogFromEnvOr("error"); + + Config config = loadConfig(emptyArgs, configFile, connect, listen, noMulticastScouting, mode); + KeyExpr keyExpr = KeyExpr.tryFrom(this.key); + + // A Queryable can be implemented in multiple ways. Uncomment one to try: + declareQueryableWithBlockingQueue(config, keyExpr); + // declareQueryableWithCallback(config, keyExpr); + // declareQueryableProvidingConfig(config, keyExpr); + + return 0; + } + + /** + * Default implementation using a blocking queue to handle incoming queries. + */ + private void declareQueryableWithBlockingQueue(Config config, KeyExpr keyExpr) throws ZError, InterruptedException { + try (Session session = Zenoh.open(config)) { + var queryable = session.declareQueryable(keyExpr); + BlockingQueue> receiver = queryable.getReceiver(); + while (true) { + Optional wrapper = receiver.take(); + if (wrapper.isEmpty()) { + break; } + Query query = wrapper.get(); + handleQuery(query); } } } - private static void handleRequests(BlockingQueue> receiver, KeyExpr keyExpr) throws InterruptedException { - while (true) { - Optional wrapper = receiver.take(); - if (wrapper.isEmpty()) { - break; - } - Query query = wrapper.get(); - String valueInfo = query.getValue() != null ? " with value '" + query.getValue() + "'" : ""; + /** + * Example using a callback to handle incoming queries asynchronously. + * + * @see io.zenoh.handlers.Callback + */ + private void declareQueryableWithCallback(Config config, KeyExpr keyExpr) throws ZError { + try (Session session = Zenoh.open(config)) { + session.declareQueryable(keyExpr, this::handleQuery); + } + } + + /** + * Example demonstrating the use of QueryableConfig to declare a Queryable. + * + * @see QueryableOptions + */ + private void declareQueryableProvidingConfig(Config config, KeyExpr keyExpr) throws ZError { + try (Session session = Zenoh.open(config)) { + QueryableOptions queryableOptions = new QueryableOptions(); + queryableOptions.setComplete(true); + session.declareQueryable(keyExpr, this::handleQuery, queryableOptions); + } + } + + private void handleQuery(Query query) { + try { + String valueInfo = query.getPayload() != null ? " with value '" + query.getPayload() + "'" : ""; System.out.println(">> [Queryable] Received Query '" + query.getSelector() + "'" + valueInfo); - try { - query.reply(keyExpr).success("Queryable from Java!").timestamp(TimeStamp.getCurrentTime()).res(); - } catch (Exception e) { - System.out.println(">> [Queryable] Error sending reply: " + e); - } + var options = new ReplyOptions(); + options.setTimeStamp(TimeStamp.getCurrentTime()); + query.reply(query.getKeyExpr(), ZBytes.from(value), options); + } catch (Exception e) { + System.err.println(">> [Queryable] Error sending reply: " + e.getMessage()); } } + + /** + * ----- Example arguments and private fields ----- + */ + + private final Boolean emptyArgs; + + ZQueryable(Boolean emptyArgs) { + this.emptyArgs = emptyArgs; + } + + @CommandLine.Option( + names = {"-k", "--key"}, + description = "The key expression to write to [default: demo/example/zenoh-java-queryable].", + defaultValue = "demo/example/zenoh-java-queryable" + ) + private String key; + + @CommandLine.Option( + names = {"-v", "--value"}, + description = "The value to reply to queries [default: 'Queryable from Java!'].", + defaultValue = "Queryable from Java!" + ) + private String value; + + @CommandLine.Option( + names = {"-c", "--config"}, + description = "A configuration file." + ) + private String configFile; + + @CommandLine.Option( + names = {"-m", "--mode"}, + description = "The session mode. Default: peer. Possible values: [peer, client, router].", + defaultValue = "peer" + ) + private String mode; + + @CommandLine.Option( + names = {"-e", "--connect"}, + description = "Endpoints to connect to.", + split = "," + ) + private List connect; + + @CommandLine.Option( + names = {"-l", "--listen"}, + description = "Endpoints to listen on.", + split = "," + ) + private List listen; + + @CommandLine.Option( + names = {"--no-multicast-scouting"}, + description = "Disable the multicast-based scouting mechanism.", + defaultValue = "false" + ) + private boolean noMulticastScouting; + + public static void main(String[] args) { + int exitCode = new CommandLine(new ZQueryable(args.length == 0)).execute(args); + System.exit(exitCode); + } } diff --git a/examples/src/main/java/io/zenoh/ZScout.java b/examples/src/main/java/io/zenoh/ZScout.java new file mode 100644 index 00000000..89f2be1b --- /dev/null +++ b/examples/src/main/java/io/zenoh/ZScout.java @@ -0,0 +1,67 @@ +// +// Copyright (c) 2023 ZettaScale Technology +// +// This program and the accompanying materials are made available under the +// terms of the Eclipse Public License 2.0 which is available at +// http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 +// which is available at https://www.apache.org/licenses/LICENSE-2.0. +// +// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 +// +// Contributors: +// ZettaScale Zenoh Team, +// + +package io.zenoh; + +import io.zenoh.config.WhatAmI; +import io.zenoh.scouting.Hello; +import io.zenoh.scouting.ScoutOptions; +import picocli.CommandLine; + +import java.util.Optional; +import java.util.Set; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.Callable; + +@CommandLine.Command( + name = "ZScout", + mixinStandardHelpOptions = true, + description = "Zenoh Scouting example" +) +public class ZScout implements Callable { + + @Override + public Integer call() throws Exception { + Zenoh.initLogFromEnvOr("error"); + + System.out.println("Scouting..."); + + var scoutOptions = new ScoutOptions(); + scoutOptions.setWhatAmI(Set.of(WhatAmI.Peer, WhatAmI.Router)); + var scout = Zenoh.scout(scoutOptions); + BlockingQueue> receiver = scout.getReceiver(); + assert receiver != null; + + try { + while (true) { + Optional wrapper = receiver.take(); + if (wrapper.isEmpty()) { + break; + } + + Hello hello = wrapper.get(); + System.out.println(hello); + } + } finally { + scout.stop(); + } + + return 0; + } + + public static void main(String[] args) { + int exitCode = new CommandLine(new ZScout()).execute(args); + System.exit(exitCode); + } +} diff --git a/examples/src/main/java/io/zenoh/ZSub.java b/examples/src/main/java/io/zenoh/ZSub.java index 883139f7..2379e70f 100644 --- a/examples/src/main/java/io/zenoh/ZSub.java +++ b/examples/src/main/java/io/zenoh/ZSub.java @@ -14,35 +14,149 @@ package io.zenoh; -import io.zenoh.exceptions.ZenohException; +import io.zenoh.exceptions.ZError; +import io.zenoh.handlers.Handler; import io.zenoh.keyexpr.KeyExpr; +import io.zenoh.pubsub.HandlerSubscriber; import io.zenoh.sample.Sample; -import io.zenoh.subscriber.Subscriber; +import picocli.CommandLine; +import java.util.List; import java.util.Optional; import java.util.concurrent.BlockingQueue; +import java.util.concurrent.Callable; -public class ZSub { - - public static void main(String[] args) throws ZenohException, InterruptedException { - System.out.println("Opening session..."); - try (Session session = Session.open()) { - try (KeyExpr keyExpr = KeyExpr.tryFrom("demo/example/**")) { - System.out.println("Declaring Subscriber on '" + keyExpr + "'..."); - try (Subscriber>> subscriber = session.declareSubscriber(keyExpr).res()) { - BlockingQueue> receiver = subscriber.getReceiver(); - assert receiver != null; - System.out.println("Press CTRL-C to quit..."); - while (true) { - Optional wrapper = receiver.take(); - if (wrapper.isEmpty()) { - break; - } - Sample sample = wrapper.get(); - System.out.println(">> [Subscriber] Received " + sample.getKind() + " ('" + sample.getKeyExpr() + "': '" + sample.getValue() + "')"); +import static io.zenoh.ConfigKt.loadConfig; + +@CommandLine.Command( + name = "ZSub", + mixinStandardHelpOptions = true, + description = "Zenoh Sub example" +) +public class ZSub implements Callable { + + @Override + public Integer call() throws Exception { + Zenoh.initLogFromEnvOr("error"); + + Config config = loadConfig(emptyArgs, configFile, connect, listen, noMulticastScouting, mode); + KeyExpr keyExpr = KeyExpr.tryFrom(this.key); + + // Subscribers can be declared in different ways. + // Uncomment one of the lines below to try out different implementations: + subscribeWithBlockingQueue(config, keyExpr); + // subscribeWithCallback(config, keyExpr); + // subscribeWithHandler(config, keyExpr); + + return 0; + } + + /** + * Default implementation using a blocking queue to handle incoming samples. + */ + private void subscribeWithBlockingQueue(Config config, KeyExpr keyExpr) throws ZError, InterruptedException { + try (Session session = Zenoh.open(config)) { + try (HandlerSubscriber>> subscriber = session.declareSubscriber(keyExpr)) { + BlockingQueue> receiver = subscriber.getReceiver(); + assert receiver != null; + while (true) { + Optional wrapper = receiver.take(); + if (wrapper.isEmpty()) { + break; } + handleSample(wrapper.get()); } } } } + + /** + * Example using a callback to handle incoming samples asynchronously. + * @see io.zenoh.handlers.Callback + */ + private void subscribeWithCallback(Config config, KeyExpr keyExpr) throws ZError { + try (Session session = Zenoh.open(config)) { + session.declareSubscriber(keyExpr, this::handleSample); + } + } + + /** + * Example using a custom implementation of the Handler. + * @see QueueHandler + * @see Handler + */ + private void subscribeWithHandler(Config config, KeyExpr keyExpr) throws ZError { + try (Session session = Zenoh.open(config)) { + QueueHandler queueHandler = new QueueHandler<>(); + var subscriber = session.declareSubscriber(keyExpr, queueHandler); + for (Sample sample : subscriber.getReceiver()) { + System.out.println(sample); + } + } + } + + /** + * Handles a single Sample and prints relevant information. + */ + private void handleSample(Sample sample) { + String attachment = sample.getAttachment() != null ? ", with attachment: " + sample.getAttachment() : ""; + System.out.println(">> [Subscriber] Received " + sample.getKind() + + " ('" + sample.getKeyExpr() + "': '" + sample.getPayload() + "'" + attachment + ")"); + } + + /** + * ----- Example arguments and private fields ----- + */ + + private final Boolean emptyArgs; + + ZSub(Boolean emptyArgs) { + this.emptyArgs = emptyArgs; + } + + @CommandLine.Option( + names = {"-c", "--config"}, + description = "A configuration file." + ) + private String configFile; + + @CommandLine.Option( + names = {"-k", "--key"}, + description = "The key expression to subscribe to [default: demo/example/**].", + defaultValue = "demo/example/**" + ) + private String key; + + @CommandLine.Option( + names = {"-e", "--connect"}, + description = "Endpoints to connect to.", + split = "," + ) + private List connect; + + @CommandLine.Option( + names = {"-l", "--listen"}, + description = "Endpoints to listen on.", + split = "," + ) + private List listen; + + @CommandLine.Option( + names = {"-m", "--mode"}, + description = "The session mode. Default: peer. Possible values: [peer, client, router].", + defaultValue = "peer" + ) + private String mode; + + @CommandLine.Option( + names = {"--no-multicast-scouting"}, + description = "Disable the multicast-based scouting mechanism.", + defaultValue = "false" + ) + private boolean noMulticastScouting; + + public static void main(String[] args) { + int exitCode = new CommandLine(new ZSub(args.length == 0)).execute(args); + System.exit(exitCode); + } } diff --git a/examples/src/main/java/io/zenoh/ZSubLiveliness.java b/examples/src/main/java/io/zenoh/ZSubLiveliness.java new file mode 100644 index 00000000..cf76c9e2 --- /dev/null +++ b/examples/src/main/java/io/zenoh/ZSubLiveliness.java @@ -0,0 +1,181 @@ +// +// Copyright (c) 2023 ZettaScale Technology +// +// This program and the accompanying materials are made available under the +// terms of the Eclipse Public License 2.0 which is available at +// http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 +// which is available at https://www.apache.org/licenses/LICENSE-2.0. +// +// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 +// +// Contributors: +// ZettaScale Zenoh Team, +// + +package io.zenoh; + +import io.zenoh.exceptions.ZError; +import io.zenoh.keyexpr.KeyExpr; +import io.zenoh.liveliness.LivelinessSubscriberOptions; +import io.zenoh.sample.Sample; +import io.zenoh.sample.SampleKind; +import picocli.CommandLine; + +import java.util.List; +import java.util.Optional; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.Callable; + +import static io.zenoh.ConfigKt.loadConfig; + +@CommandLine.Command( + name = "ZSubLiveliness", + mixinStandardHelpOptions = true, + description = "Zenoh Sub Liveliness example" +) +public class ZSubLiveliness implements Callable { + + @Override + public Integer call() throws Exception { + Zenoh.initLogFromEnvOr("error"); + + Config config = loadConfig(emptyArgs, configFile, connect, listen, noMulticastScouting, mode); + KeyExpr keyExpr = KeyExpr.tryFrom(this.key); + + // Subscribing to liveliness tokens can be implemented in multiple ways. + // Uncomment the desired implementation: + subscribeToLivelinessWithBlockingQueue(config, keyExpr); + // subscribeToLivelinessWithCallback(config, keyExpr); + // subscribeToLivelinessWithHandler(config, keyExpr); + + return 0; + } + + /** + * Default implementation using a blocking queue to handle incoming liveliness tokens. + */ + private void subscribeToLivelinessWithBlockingQueue(Config config, KeyExpr keyExpr) throws ZError, InterruptedException { + try (Session session = Zenoh.open(config)) { + var options = new LivelinessSubscriberOptions(history); + var subscriber = session.liveliness().declareSubscriber(keyExpr, options); + + BlockingQueue> receiver = subscriber.getReceiver(); + System.out.println("Listening for liveliness tokens..."); + while (true) { + Optional wrapper = receiver.take(); + if (wrapper.isEmpty()) { + break; + } + handleLivelinessSample(wrapper.get()); + } + } + } + + /** + * Example using a callback to handle incoming liveliness tokens asynchronously. + * + * @see io.zenoh.handlers.Callback + */ + private void subscribeToLivelinessWithCallback(Config config, KeyExpr keyExpr) throws ZError { + try (Session session = Zenoh.open(config)) { + var options = new LivelinessSubscriberOptions(history); + session.liveliness().declareSubscriber( + keyExpr, + this::handleLivelinessSample, + options + ); + } + } + + /** + * Example using a handler to handle incoming liveliness tokens asynchronously. + * + * @see io.zenoh.handlers.Handler + * @see QueueHandler + */ + private void subscribeToLivelinessWithHandler(Config config, KeyExpr keyExpr) throws ZError { + try (Session session = Zenoh.open(config)) { + QueueHandler queueHandler = new QueueHandler<>(); + var options = new LivelinessSubscriberOptions(history); + session.liveliness().declareSubscriber( + keyExpr, + queueHandler, + options + ); + } + } + + /** + * Handles a single liveliness token sample. + */ + private void handleLivelinessSample(Sample sample) { + if (sample.getKind() == SampleKind.PUT) { + System.out.println(">> [LivelinessSubscriber] New alive token ('" + sample.getKeyExpr() + "')"); + } else if (sample.getKind() == SampleKind.DELETE) { + System.out.println(">> [LivelinessSubscriber] Dropped token ('" + sample.getKeyExpr() + "')"); + } + } + + /** + * ----- Example arguments and private fields ----- + */ + + private final Boolean emptyArgs; + + ZSubLiveliness(Boolean emptyArgs) { + this.emptyArgs = emptyArgs; + } + + @CommandLine.Option( + names = {"-c", "--config"}, + description = "A configuration file." + ) + private String configFile; + + @CommandLine.Option( + names = {"-k", "--key"}, + description = "The key expression to subscribe to [default: group1/**].", + defaultValue = "group1/**" + ) + private String key; + + @CommandLine.Option( + names = {"-e", "--connect"}, + description = "Endpoints to connect to.", + split = "," + ) + private List connect; + + @CommandLine.Option( + names = {"-l", "--listen"}, + description = "Endpoints to listen on.", + split = "," + ) + private List listen; + + @CommandLine.Option( + names = {"-m", "--mode"}, + description = "The session mode. Default: peer. Possible values: [peer, client, router].", + defaultValue = "peer" + ) + private String mode; + + @CommandLine.Option( + names = {"--history"}, + description = "Get historical liveliness tokens.", + defaultValue = "false" + ) + private boolean history; + + @CommandLine.Option( + names = {"--no-multicast-scouting"}, + description = "Disable the multicast-based scouting mechanism.", + defaultValue = "false" + ) + private boolean noMulticastScouting; + + public static void main(String[] args) { + int exitCode = new CommandLine(new ZSubLiveliness(args.length == 0)).execute(args); + System.exit(exitCode); + } +} diff --git a/examples/src/main/java/io/zenoh/ZSubThr.java b/examples/src/main/java/io/zenoh/ZSubThr.java index d7afc00a..670967ee 100644 --- a/examples/src/main/java/io/zenoh/ZSubThr.java +++ b/examples/src/main/java/io/zenoh/ZSubThr.java @@ -14,61 +14,164 @@ package io.zenoh; -import io.zenoh.exceptions.ZenohException; +import io.zenoh.exceptions.ZError; import io.zenoh.keyexpr.KeyExpr; -import io.zenoh.subscriber.Subscriber; -import kotlin.Unit; +import io.zenoh.pubsub.Subscriber; +import picocli.CommandLine; -public class ZSubThr { +import java.util.List; +import java.util.concurrent.Callable; - private static final long NANOS_TO_SEC = 1_000_000_000L; - private static final long n = 50000L; - private static int batchCount = 0; - private static int count = 0; - private static long startTimestampNs = 0; - private static long globalStartTimestampNs = 0; +import static io.zenoh.ConfigKt.loadConfig; + +@CommandLine.Command( + name = "ZSubThr", + mixinStandardHelpOptions = true, + description = "Zenoh Subscriber Throughput test" +) +public class ZSubThr implements Callable { + + @Override + public Integer call() throws Exception { + Zenoh.initLogFromEnvOr("error"); + + Config config = loadConfig(emptyArgs, configFile, connect, listen, noMulticastScouting, mode); + + System.out.println("Opening Session"); + try (Session session = Zenoh.open(config)) { + try (KeyExpr keyExpr = KeyExpr.tryFrom("test/thr")) { + subscriber = session.declareSubscriber(keyExpr, sample -> listener(number)); + System.out.println("Press CTRL-C to quit..."); + + while (subscriber.isValid()) { + Thread.sleep(1000); + } + } + } catch (ZError e) { + System.err.println("Error during Zenoh operation: " + e.getMessage()); + return 1; + } + return 0; + } + + private void listener(long number) { + if (batchCount > samples) { + closeSubscriber(); + report(); + return; + } - public static void listener() { if (count == 0) { startTimestampNs = System.nanoTime(); - if (globalStartTimestampNs == 0L) { + if (globalStartTimestampNs == 0) { globalStartTimestampNs = startTimestampNs; } count++; return; } - if (count < n) { + + if (count < number) { count++; return; } + long stop = System.nanoTime(); - double msgs = (double) (n * NANOS_TO_SEC) / (stop - startTimestampNs); - System.out.println(msgs + " msgs/sec"); + double elapsedTimeSecs = (double) (stop - startTimestampNs) / NANOS_TO_SEC; + double messagesPerSec = number / elapsedTimeSecs; + System.out.printf("%.2f msgs/sec%n", messagesPerSec); batchCount++; count = 0; } - // TODO: perform report at end of measurement - public static void report() { + private void report() { long end = System.nanoTime(); - long total = batchCount * n + count; - double msgs = (double) (end - globalStartTimestampNs) / NANOS_TO_SEC; - double avg = (double) (total * NANOS_TO_SEC) / (end - globalStartTimestampNs); - System.out.println("Received " + total + " messages in " + msgs + - ": averaged " + avg + " msgs/sec"); + long totalMessages = batchCount * number + count; + double elapsedTimeSecs = (double) (end - globalStartTimestampNs) / NANOS_TO_SEC; + double averageMessagesPerSec = totalMessages / elapsedTimeSecs; + + System.out.printf("Received %d messages in %.2f seconds: averaged %.2f msgs/sec%n", + totalMessages, elapsedTimeSecs, averageMessagesPerSec); } - public static void main(String[] args) throws ZenohException, InterruptedException { - System.out.println("Opening Session"); - try (Session session = Session.open()) { - try (KeyExpr keyExpr = KeyExpr.tryFrom("test/thr")) { - try (Subscriber subscriber = session.declareSubscriber(keyExpr).with(sample -> listener()).res()) { - System.out.println("Press CTRL-C to quit..."); - while (true) { - Thread.sleep(1000); - } - } + private void closeSubscriber() { + if (subscriber != null && subscriber.isValid()) { + try { + subscriber.close(); + } catch (Exception e) { + System.err.println("Error closing subscriber: " + e.getMessage()); } } } + + + /** + * ----- Example arguments and private fields ----- + */ + + private final Boolean emptyArgs; + + ZSubThr(Boolean emptyArgs) { + this.emptyArgs = emptyArgs; + } + + private static final long NANOS_TO_SEC = 1_000_000_000L; + private long batchCount = 0; + private long count = 0; + private long startTimestampNs = 0; + private long globalStartTimestampNs = 0; + + @CommandLine.Option( + names = {"-s", "--samples"}, + description = "Number of throughput measurements [default: 10].", + defaultValue = "10" + ) + private long samples; + + @CommandLine.Option( + names = {"-n", "--number"}, + description = "Number of messages in each throughput measurement [default: 100000].", + defaultValue = "100000" + ) + private long number; + + @CommandLine.Option( + names = {"-c", "--config"}, + description = "A configuration file." + ) + private String configFile; + + @CommandLine.Option( + names = {"-e", "--connect"}, + description = "Endpoints to connect to.", + split = "," + ) + private List connect; + + @CommandLine.Option( + names = {"-l", "--listen"}, + description = "Endpoints to listen on.", + split = "," + ) + private List listen; + + @CommandLine.Option( + names = {"-m", "--mode"}, + description = "The session mode. Default: peer. Possible values: [peer, client, router].", + defaultValue = "peer" + ) + private String mode; + + @CommandLine.Option( + names = {"--no-multicast-scouting"}, + description = "Disable the multicast-based scouting mechanism.", + defaultValue = "false" + ) + private boolean noMulticastScouting; + + private Subscriber subscriber; + + public static void main(String[] args) { + int exitCode = new CommandLine(new ZSubThr(args.length == 0)).execute(args); + System.exit(exitCode); + } } diff --git a/zenoh-java/build.gradle.kts b/zenoh-java/build.gradle.kts index f5841d0a..acd3d724 100644 --- a/zenoh-java/build.gradle.kts +++ b/zenoh-java/build.gradle.kts @@ -46,6 +46,7 @@ kotlin { val zenohPaths = "../zenoh-jni/target/$buildMode" jvmArgs("-Djava.library.path=$zenohPaths") } + withJava() } if (androidEnabled) { androidTarget { @@ -58,7 +59,7 @@ kotlin { val commonMain by getting { dependencies { implementation("commons-net:commons-net:3.9.0") - implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.6.0") + implementation("com.google.guava:guava:33.3.1-jre") } } val commonTest by getting { diff --git a/zenoh-java/src/androidMain/kotlin/io.zenoh/Zenoh.kt b/zenoh-java/src/androidMain/kotlin/io.zenoh/Zenoh.kt index c8d9ad08..74d42e51 100644 --- a/zenoh-java/src/androidMain/kotlin/io.zenoh/Zenoh.kt +++ b/zenoh-java/src/androidMain/kotlin/io.zenoh/Zenoh.kt @@ -18,24 +18,10 @@ package io.zenoh * Static singleton class to load the Zenoh native library once and only once, as well as the logger in function of the * log level configuration. */ -internal actual class Zenoh private actual constructor() { - - actual companion object { - private const val ZENOH_LIB_NAME = "zenoh_jni" - private const val ZENOH_LOGS_PROPERTY = "zenoh.logger" - - private var instance: Zenoh? = null - - actual fun load() { - instance ?: Zenoh().also { instance = it } - } - } +internal actual object ZenohLoad { + private const val ZENOH_LIB_NAME = "zenoh_jni" init { System.loadLibrary(ZENOH_LIB_NAME) - val logLevel = System.getProperty(ZENOH_LOGS_PROPERTY) - if (logLevel != null) { - Logger.start(logLevel) - } } } diff --git a/zenoh-java/src/commonMain/kotlin/io/zenoh/Config.kt b/zenoh-java/src/commonMain/kotlin/io/zenoh/Config.kt index 2e2964a7..91bc0e01 100644 --- a/zenoh-java/src/commonMain/kotlin/io/zenoh/Config.kt +++ b/zenoh-java/src/commonMain/kotlin/io/zenoh/Config.kt @@ -14,56 +14,146 @@ package io.zenoh +import io.zenoh.exceptions.ZError +import io.zenoh.jni.JNIConfig import java.io.File import java.nio.file.Path -import kotlinx.serialization.json.Json -import kotlinx.serialization.json.JsonElement - /** - * Config class to set the Zenoh configuration to be used through a [Session]. + * # Config + * + * Config class to set the Zenoh configuration to be used through a [io.zenoh.Session]. + * + * The configuration can be specified in two different ways: + * - By providing a file or a path to a file with the configuration + * - By providing a raw string configuration. + * + * Either way, the supported formats are `yaml`, `json` and `json5`. * - * @property path The path to the configuration file. - * @constructor Create empty Config + * A default configuration can be loaded using [Config.loadDefault]. + * + * Visit the [default configuration](https://github.com/eclipse-zenoh/zenoh/blob/main/DEFAULT_CONFIG.json5) for more + * information on the Zenoh config parameters. */ -class Config private constructor(internal val path: Path? = null, internal val jsonConfig: JsonElement? = null) { +class Config internal constructor(internal val jniConfig: JNIConfig) { companion object { + private const val CONFIG_ENV = "ZENOH_CONFIG" + /** - * Loads the default zenoh configuration. + * Returns the default config. */ - fun default(): Config { - return Config() + @JvmStatic + fun loadDefault(): Config { + return JNIConfig.loadDefaultConfig() } /** * Loads the configuration from the [File] specified. * - * @param file The zenoh config file. + * @param file The Zenoh config file. Supported types are: JSON, JSON5 and YAML. + * Note the format is determined after the file extension. + * @return The [Config]. */ - fun from(file: File): Config { - return Config(file.toPath()) + @JvmStatic + @Throws(ZError::class) + fun fromFile(file: File): Config { + return JNIConfig.loadConfigFile(file) } /** * Loads the configuration from the [Path] specified. * - * @param path The zenoh config file path. + * @param path Path to the Zenoh config file. Supported types are: JSON, JSON5 and YAML. + * Note the format is determined after the file extension. + * @return The [Config]. */ - fun from(path: Path): Config { - return Config(path) + @JvmStatic + @Throws(ZError::class) + fun fromFile(path: Path): Config { + return JNIConfig.loadConfigFile(path) } /** - * Loads the configuration from the [json] specified. + * Loads the configuration from json-formatted string. * - * @param json The zenoh raw zenoh config. + * Visit the [default configuration](https://github.com/eclipse-zenoh/zenoh/blob/main/DEFAULT_CONFIG.json5) for more + * information on the Zenoh config parameters. + * + * @param config Json formatted config. + * @return The [Config]. */ - fun from(json: String): Config { - return Config(jsonConfig = Json.decodeFromString(json)) + @JvmStatic + @Throws(ZError::class) + fun fromJson(config: String): Config { + return JNIConfig.loadJsonConfig(config) } + + /** + * Loads the configuration from json5-formatted string. + * + * Visit the [default configuration](https://github.com/eclipse-zenoh/zenoh/blob/main/DEFAULT_CONFIG.json5) for more + * information on the Zenoh config parameters. + * + * @param config Json5 formatted config + * @return The [Config]. + */ + @JvmStatic + @Throws(ZError::class) + fun fromJson5(config: String): Config { + return JNIConfig.loadJson5Config(config) + } + + /** + * Loads the configuration from yaml-formatted string. + * + * Visit the [default configuration](https://github.com/eclipse-zenoh/zenoh/blob/main/DEFAULT_CONFIG.json5) for more + * information on the Zenoh config parameters. + * + * @param config Yaml formatted config + * @return The [Config]. + */ + @JvmStatic + @Throws(ZError::class) + fun fromYaml(config: String): Config { + return JNIConfig.loadYamlConfig(config) + } + + /** + * Loads the configuration from the env variable [CONFIG_ENV]. + * + * @return The config. + */ + @JvmStatic + @Throws(ZError::class) + fun fromEnv(): Config { + val envValue = System.getenv(CONFIG_ENV) + if (envValue != null) { + return fromFile(File(envValue)) + } else { + throw Exception("Couldn't load env variable: $CONFIG_ENV.") + } + } + } + + /** + * The json value associated to the [key]. + */ + @Throws(ZError::class) + fun getJson(key: String): String { + return jniConfig.getJson(key) } - constructor(jsonConfig: JsonElement) : this(null, jsonConfig = jsonConfig) + /** + * Inserts a json5 value associated to the [key] into the Config. + */ + @Throws(ZError::class) + fun insertJson5(key: String, value: String) { + jniConfig.insertJson5(key, value) + } + + protected fun finalize() { + jniConfig.close() + } } diff --git a/zenoh-java/src/commonMain/kotlin/io/zenoh/Logger.kt b/zenoh-java/src/commonMain/kotlin/io/zenoh/Logger.kt index db25cf2f..eba6681c 100644 --- a/zenoh-java/src/commonMain/kotlin/io/zenoh/Logger.kt +++ b/zenoh-java/src/commonMain/kotlin/io/zenoh/Logger.kt @@ -14,14 +14,27 @@ package io.zenoh +import io.zenoh.exceptions.ZError + /** Logger class to redirect the Rust logs from Zenoh to the kotlin environment. */ -class Logger { +internal class Logger { companion object { + + 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). @param logLevel must be either "info", "debug", "warn", "trace" or "error". + * systems). + * + * See https://docs.rs/env_logger/latest/env_logger/index.html for accepted filter format. */ - external fun start(logLevel: String) + @Throws(ZError::class) + private external fun startLogsViaJNI(filter: String) } -} \ No newline at end of file +} diff --git a/zenoh-java/src/commonMain/kotlin/io/zenoh/Session.kt b/zenoh-java/src/commonMain/kotlin/io/zenoh/Session.kt index c5e50d67..c944e83c 100644 --- a/zenoh-java/src/commonMain/kotlin/io/zenoh/Session.kt +++ b/zenoh-java/src/commonMain/kotlin/io/zenoh/Session.kt @@ -14,26 +14,25 @@ package io.zenoh -import io.zenoh.exceptions.SessionException -import io.zenoh.exceptions.ZenohException +import io.zenoh.bytes.IntoZBytes +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.JNISession import io.zenoh.keyexpr.KeyExpr -import io.zenoh.prelude.QoS -import io.zenoh.publication.Delete -import io.zenoh.publication.Publisher -import io.zenoh.publication.Put +import io.zenoh.liveliness.Liveliness +import io.zenoh.pubsub.* import io.zenoh.query.* -import io.zenoh.queryable.Query -import io.zenoh.queryable.Queryable +import io.zenoh.query.Query +import io.zenoh.query.Queryable import io.zenoh.sample.Sample -import io.zenoh.selector.Selector -import io.zenoh.subscriber.Reliability -import io.zenoh.subscriber.Subscriber -import io.zenoh.value.Value -import java.time.Duration +import io.zenoh.session.SessionDeclaration +import io.zenoh.session.SessionInfo import java.util.* import java.util.concurrent.BlockingQueue +import java.util.concurrent.LinkedBlockingDeque /** * A Zenoh Session, the core interaction point with a Zenoh network. @@ -41,7 +40,6 @@ import java.util.concurrent.BlockingQueue * A session is typically associated with declarations such as [Publisher]s, [Subscriber]s, or [Queryable]s, which are * declared using [declarePublisher], [declareSubscriber], and [declareQueryable], respectively. * Other operations such as simple Put, Get or Delete can be performed from a session using [put], [get] and [delete]. - * Finally, it's possible to declare key expressions ([KeyExpr]) as well. * * Sessions are open upon creation and can be closed manually by calling [close]. Alternatively, the session will be * automatically closed when used with Java's try-with-resources statement or its Kotlin counterpart, [use]. @@ -52,44 +50,28 @@ import java.util.concurrent.BlockingQueue */ class Session private constructor(private val config: Config) : AutoCloseable { - private var jniSession: JNISession? = JNISession() + internal var jniSession: JNISession? = JNISession() + + private var declarations = mutableListOf() companion object { - private val sessionClosedException = SessionException("Session is closed.") - - /** - * Open a [Session] with the default [Config]. - * - * @return The opened [Session]. - * @throws [SessionException] in the case of a failure. - */ - @JvmStatic - @Throws(SessionException::class) - fun open(): Session { - val session = Session(Config.default()) - return session.launch() - } + internal val sessionClosedException = ZError("Session is closed.") /** * Open a [Session] with the provided [Config]. * * @param config The configuration for the session. * @return The opened [Session]. - * @throws [SessionException] in the case of a failure. + * @throws [ZError] in the case of a failure. */ - @JvmStatic - @Throws(SessionException::class) - fun open(config: Config): Session { + @Throws(ZError::class) + internal fun open(config: Config): Session { val session = Session(config) return session.launch() } } - init { - Zenoh.load() - } - /** * Close the session. * @@ -98,15 +80,18 @@ class Session private constructor(private val config: Config) : AutoCloseable { * * However, any session declaration that was still alive and bound to the session previous to closing it, will still be alive. */ - @Throws(SessionException::class) override fun close() { + declarations.removeIf { + it.undeclare() + true + } + jniSession?.close() jniSession = null } - @Suppress("removal") protected fun finalize() { - jniSession?.close() + close() } /** @@ -114,93 +99,227 @@ class Session private constructor(private val config: Config) : AutoCloseable { * * Example: * ```java - * try (Session session = Session.open()) { - * try (KeyExpr keyExpr = KeyExpr.tryFrom("demo/example/greeting")) { - * try (Publisher publisher = session.declarePublisher(keyExpr) - * .priority(Priority.REALTIME) - * .congestionControl(CongestionControl.DROP) - * .res()) { - * int idx = 0; - * while (true) { - * String payload = "Hello for the " + idx + "th time!"; - * publisher.put(payload).res(); - * Thread.sleep(1000); - * idx++; - * } - * } + * try (Session session = Zenoh.open(config)) { + * // A publisher config can optionally be provided. + * PublisherOptions publisherOptions = new PublisherOptions(); + * publisherOptions.setEncoding(Encoding.ZENOH_STRING); + * publisherOptions.setCongestionControl(CongestionControl.BLOCK); + * publisherOptions.setReliability(Reliability.RELIABLE); + * + * // Declare the publisher + * Publisher publisher = session.declarePublisher(keyExpr, publisherOptions); + * + * int idx = 0; + * while (true) { + * Thread.sleep(1000); + * String payload = String.format("[%4d] %s", idx, value); + * System.out.println("Putting Data ('" + keyExpr + "': '" + payload + "')..."); + * publisher.put(ZBytes.from(payload)); + * idx++; * } * } * ``` * * @param keyExpr The [KeyExpr] the publisher will be associated to. - * @return A resolvable [Publisher.Builder] + * @param publisherOptions Optional [PublisherOptions] to configure the publisher. + * @return The declared [Publisher]. */ - fun declarePublisher(keyExpr: KeyExpr): Publisher.Builder = Publisher.Builder(this, keyExpr) + @JvmOverloads + @Throws(ZError::class) + fun declarePublisher(keyExpr: KeyExpr, publisherOptions: PublisherOptions = PublisherOptions()): Publisher { + return resolvePublisher(keyExpr, publisherOptions) + } /** * Declare a [Subscriber] on the session. * - * The default receiver is a [BlockingQueue], but can be changed with the [Subscriber.Builder.with] functions. - * - * Example: - * + * Example with blocking queue (default receiver): * ```java - * try (Session session = Session.open()) { - * try (KeyExpr keyExpr = KeyExpr.tryFrom("demo/example/sub")) { - * try (Subscriber>> subscriber = session.declareSubscriber(keyExpr).res()) { - * BlockingQueue> receiver = subscriber.getReceiver(); - * assert receiver != null; - * while (true) { - * Optional sample = receiver.take(); - * if (sample.isEmpty()) { - * break; - * } - * System.out.println(sample.get()); + * try (Session session = Zenoh.open(config)) { + * try (HandlerSubscriber>> subscriber = session.declareSubscriber(keyExpr)) { + * BlockingQueue> receiver = subscriber.getReceiver(); + * assert receiver != null; + * while (true) { + * Optional wrapper = receiver.take(); + * if (wrapper.isEmpty()) { + * break; * } + * System.out.println(wrapper.get()); + * handleSample(wrapper.get()); * } * } * } * ``` * * @param keyExpr The [KeyExpr] the subscriber will be associated to. - * @return A [Subscriber.Builder] with a [BlockingQueue] receiver. + * @return [HandlerSubscriber] with a [BlockingQueue] as a receiver. */ - fun declareSubscriber(keyExpr: KeyExpr): Subscriber.Builder>> = Subscriber.newBuilder(this, keyExpr) + @Throws(ZError::class) + fun declareSubscriber(keyExpr: KeyExpr): HandlerSubscriber>> { + return resolveSubscriberWithHandler( + keyExpr, + BlockingQueueHandler(LinkedBlockingDeque()) + ) + } /** - * Declare a [Queryable] on the session. + * Declare a [Subscriber] on the session using a handler. * - * The default receiver is a [BlockingQueue], but can be changed with the [Queryable.Builder.with] functions. + * Example with a custom handler: + * ```java + * // Example handler that stores the received samples into a queue. + * class QueueHandler implements Handler> { * - * Example: + * final ArrayDeque queue = new ArrayDeque<>(); + * + * @Override + * public void handle(Sample t) { + * queue.add(t); + * } + * + * @Override + * public ArrayDeque receiver() { + * return queue; + * } + * + * @Override + * public void onClose() {} + * } + * + * // ... + * + * try (Session session = Zenoh.open(config)) { + * QueueHandler queueHandler = new QueueHandler(); + * var subscriber = session.declareSubscriber(keyExpr, queueHandler); + * // ... + * } + * ``` + * + * @param R the [handler]'s receiver type. + * @param keyExpr The [KeyExpr] the subscriber will be associated to. + * @param handler The [Handler] to process the incoming [Sample]s received by the subscriber. + * @return A [HandlerSubscriber] with the [handler]'s receiver. + */ + @Throws(ZError::class) + fun declareSubscriber(keyExpr: KeyExpr, handler: Handler): HandlerSubscriber { + return resolveSubscriberWithHandler(keyExpr, handler) + } + + /** + * Declare a [Subscriber] on the session using a callback. + * + * Example with a callback: * ```java - * try (Session session = Session.open()) { - * try (KeyExpr keyExpr = KeyExpr.tryFrom("demo/example/greeting")) { - * System.out.println("Declaring Queryable"); - * try (Queryable>> queryable = session.declareQueryable(keyExpr).res()) { - * BlockingQueue> receiver = queryable.getReceiver(); - * while (true) { - * Optional wrapper = receiver.take(); - * if (wrapper.isEmpty()) { - * break; - * } - * Query query = wrapper.get(); - * System.out.println("Received query at " + query.getSelector()); - * query.reply(keyExpr) - * .success("Hello!") - * .withKind(SampleKind.PUT) - * .withTimeStamp(TimeStamp.getCurrentTime()) - * .res(); - * } + * try (Session session = Zenoh.open(config)) { + * var subscriber = session.declareSubscriber(keyExpr, sample -> System.out.println(sample)); + * // ... + * } + * ``` + * + * @param keyExpr The [KeyExpr] the subscriber will be associated to. + * @param callback [Callback] for handling the incoming samples. + * @return A [CallbackSubscriber]. + */ + @Throws(ZError::class) + fun declareSubscriber(keyExpr: KeyExpr, callback: Callback): CallbackSubscriber { + return resolveSubscriberWithCallback(keyExpr, callback) + } + + /** + * Declare a [Queryable] on the session. + * + * Example using a blocking queue (default receiver): + * ```java + * try (Session session = Zenoh.open(config)) { + * var queryable = session.declareQueryable(keyExpr); + * var receiver = queryable.getReceiver(); + * while (true) { + * Optional wrapper = receiver.take(); + * if (wrapper.isEmpty()) { + * break; * } + * Query query = wrapper.get(); + * query.reply(query.getKeyExpr(), ZBytes.from("Example reply)); * } * } * ``` * * @param keyExpr The [KeyExpr] the queryable will be associated to. - * @return A [Queryable.Builder] with a [BlockingQueue] receiver. + * @param options Optional [QueryableOptions] for configuring the queryable. + * @return A [HandlerQueryable] with a [BlockingQueue] receiver. + */ + @Throws(ZError::class) + @JvmOverloads + fun declareQueryable( + keyExpr: KeyExpr, + options: QueryableOptions = QueryableOptions() + ): HandlerQueryable>> { + return resolveQueryableWithHandler(keyExpr, BlockingQueueHandler(LinkedBlockingDeque()), options) + } + + /** + * Declare a [Queryable] on the session. + * + * Example using a custom [Handler]: + * ```java + * // Example handler that replies with the amount of queries received. + * class QueryHandler implements Handler { + * + * private Int counter = 0; + * + * @Override + * public void handle(Query query) { + * var keyExpr = query.getKeyExpr(); + * query.reply(keyExpr, ZBytes.from("Reply #" + counter + "!")); + * counter++; + * } + * + * @Override + * public Void receiver() {} + * + * @Override + * public void onClose() {} + * } + * + * // ... + * try (Session session = Zenoh.open(config)) { + * var queryable = session.declareQueryable(keyExpr, new QueryHandler()); + * //... + * } + * ``` + * + * @param R The type of the [handler]'s receiver. + * @param keyExpr The [KeyExpr] the queryable will be associated to. + * @param handler The [Handler] to handle the incoming queries. + * @param options Optional [QueryableOptions] for configuring the queryable. + * @return A [HandlerQueryable] with the handler's receiver. + */ + @Throws(ZError::class) + @JvmOverloads + fun declareQueryable(keyExpr: KeyExpr, handler: Handler, options: QueryableOptions = QueryableOptions()): HandlerQueryable { + return resolveQueryableWithHandler(keyExpr, handler, options) + } + + /** + * Declare a [Queryable] on the session. + * + * ```java + * try (Session session = Zenoh.open(config)) { + * var queryable = session.declareQueryable(keyExpr, query -> query.reply(keyExpr, ZBytes.from("Example reply"))); + * //... + * } + * ``` + * + * @param keyExpr The [KeyExpr] the queryable will be associated to. + * @param callback The [Callback] to handle the incoming queries. + * @param options Optional [QueryableOptions] for configuring the queryable. + * @return A [CallbackQueryable]. */ - fun declareQueryable(keyExpr: KeyExpr): Queryable.Builder>> = Queryable.newBuilder(this, keyExpr) + @Throws(ZError::class) + @JvmOverloads + fun declareQueryable(keyExpr: KeyExpr, callback: Callback, options: QueryableOptions = QueryableOptions()): CallbackQueryable { + return resolveQueryableWithCallback(keyExpr, callback, options) + } /** * Declare a [KeyExpr]. @@ -211,22 +330,15 @@ class Session private constructor(private val config: Config) : AutoCloseable { * a queryable, or a publisher will also inform Zenoh of your intent to use their * key expressions repeatedly. * - * Example: - * ```java - * try (Session session = session.open()) { - * try (KeyExpr keyExpr = session.declareKeyExpr("demo/java/example").res()) { - * Publisher publisher = session.declarePublisher(keyExpr).res(); - * // ... - * } - * } - * ``` - * * @param keyExpr The intended Key expression. - * @return A resolvable returning an optimized representation of the passed `keyExpr`. + * @return The declared [KeyExpr]. */ - fun declareKeyExpr(keyExpr: String): Resolvable = Resolvable { - return@Resolvable jniSession?.run { - declareKeyExpr(keyExpr) + @Throws(ZError::class) + fun declareKeyExpr(keyExpr: String): KeyExpr { + return jniSession?.run { + val keyexpr = declareKeyExpr(keyExpr) + declarations.add(keyexpr) + keyexpr } ?: throw sessionClosedException } @@ -239,180 +351,270 @@ class Session private constructor(private val config: Config) : AutoCloseable { * @param keyExpr The key expression to undeclare. * @return A resolvable returning the status of the undeclare operation. */ - fun undeclare(keyExpr: KeyExpr): Resolvable = Resolvable { - return@Resolvable jniSession?.run { + @Throws(ZError::class) + fun undeclare(keyExpr: KeyExpr) { + return jniSession?.run { undeclareKeyExpr(keyExpr) } ?: throw (sessionClosedException) } /** - * Declare a [Get] with a [BlockingQueue] receiver. + * Perform a get query handling the replies through a [BlockingQueue]. * + * Example using the default blocking queue receiver: * ```java - * try (Session session = Session.open()) { - * try (Selector selector = Selector.tryFrom("demo/java/example")) { - * session.get(selector) - * .consolidation(ConsolidationMode.NONE) - * .withValue("Get value example") - * .with(reply -> System.out.println("Received reply " + reply)) - * .res() + * try (Session session = Zenoh.open(config)) { + * System.out.println("Performing Get on '" + selector + "'..."); + * BlockingQueue> receiver = session.get(Selector.from("a/b/c")); + * + * while (true) { + * Optional wrapper = receiver.take(); + * if (wrapper.isEmpty()) { + * break; + * } + * Reply reply = wrapper.get(); + * System.out.println(reply); * } * } * ``` * - * @param selector The [KeyExpr] to be used for the get operation. - * @return a resolvable [Get.Builder] with a [BlockingQueue] receiver. + * @param selector The [Selector] for the get query. + * @param options Optional [GetOptions] to configure the get query. + * @return A [BlockingQueue] with the received replies. */ - fun get(selector: Selector): Get.Builder>> = Get.newBuilder(this, selector) + @JvmOverloads + @Throws(ZError::class) + fun get(selector: IntoSelector, options: GetOptions = GetOptions()): BlockingQueue> { + val handler = BlockingQueueHandler(LinkedBlockingDeque()) + return resolveGetWithHandler( + selector, + handler, + options + ) + } /** - * Declare a [Get] with a [BlockingQueue] receiver as default. + * Perform a get query handling the replies through a [Handler]. * + * Example using a custom handler: * ```java - * try (Session session = Session.open()) { - * try (KeyExpr keyExpr = KeyExpr.tryFrom("demo/java/example")) { - * session.get(keyExpr) - * .consolidation(ConsolidationMode.NONE) - * .withValue("Get value example") - * .with(reply -> System.out.println("Received reply " + reply)) - * .res() + * // Example handler that prints the replies along with a counter: + * class GetHandler implements Handler { + * + * private Int counter = 0; + * + * @Override + * public void handle(Reply reply) { + * System.out.println("Reply #" + counter + ": " + reply); + * counter++; * } + * + * @Override + * public Void receiver() {} + * + * @Override + * public void onClose() {} + * } + * + * //... + * try (Session session = Zenoh.open(config)) { + * System.out.println("Performing Get on '" + selector + "'..."); + * session.get(Selector.from("a/b/c"), new GetHandler()); + * //... * } * ``` * - * @param keyExpr The [KeyExpr] to be used for the get operation. - * @return a resolvable [Get.Builder] with a [BlockingQueue] receiver. + * @param R The type of the [handler]'s receiver. + * @param selector The [Selector] for the get query. + * @param handler The [Handler] to handle the incoming replies. + * @param options Optional [GetOptions] to configure the query. + * @return The handler's receiver. */ - fun get(keyExpr: KeyExpr): Get.Builder>> = Get.newBuilder(this, Selector(keyExpr)) + @JvmOverloads + @Throws(ZError::class) + fun get(selector: IntoSelector, handler: Handler, options: GetOptions = GetOptions()): R { + return resolveGetWithHandler(selector, handler, options) + } /** - * Declare a [Put] with the provided value on the specified key expression. + * Perform a get query, handling the replies with a [Callback]. * * Example: * ```java - * try (Session session = Session.open()) { - * try (KeyExpr keyExpr = KeyExpr.tryFrom("demo/example/greeting")) { - * session.put(keyExpr, Value("Hello!")) - * .congestionControl(CongestionControl.BLOCK) - * .priority(Priority.REALTIME) - * .kind(SampleKind.PUT) - * .res(); - * System.out.println("Put 'Hello' on " + keyExpr + "."); - * } + * try (Session session = Zenoh.open(config)) { + * session.get(Selector.from("a/b/c"), reply -> System.out.println(reply)); + * //... * } * ``` * - * @param keyExpr The [KeyExpr] to be used for the put operation. - * @param value The [Value] to be put. - * @return A resolvable [Put.Builder]. + * @param selector The [Selector] for the get query. + * @param callback The [Callback] to handle the incoming replies. + * @param options Optional [GetOptions] to configure the query. */ - fun put(keyExpr: KeyExpr, value: Value): Put.Builder = Put.newBuilder(this, keyExpr, value) + @JvmOverloads + @Throws(ZError::class) + fun get(selector: IntoSelector, callback: Callback, options: GetOptions = GetOptions()) { + return resolveGetWithCallback(selector, callback, options) + } /** - * Declare a [Put] with the provided value on the specified key expression. + * Perform a put with the provided [payload] to the specified [keyExpr]. * * Example: * ```java - * try (Session session = Session.open()) { - * try (KeyExpr keyExpr = KeyExpr.tryFrom("demo/example/greeting")) { - * session.put(keyExpr, "Hello!") - * .congestionControl(CongestionControl.BLOCK) - * .priority(Priority.REALTIME) - * .kind(SampleKind.PUT) - * .res(); - * System.out.println("Put 'Hello' on " + keyExpr + "."); - * } - * } + * session.put(KeyExpr.from("a/b/c"), ZBytes.from("Example payload")); + * //... * ``` * - * @param keyExpr The [KeyExpr] to be used for the put operation. - * @param message The message to be put. - * @return A resolvable [Put.Builder]. + * @param keyExpr The [KeyExpr] for performing the put. + * @param payload The payload to put. + * @param options Optional [PutOptions] to configure the put. */ - fun put(keyExpr: KeyExpr, message: String): Put.Builder = Put.newBuilder(this, keyExpr, Value(message)) + @JvmOverloads + @Throws(ZError::class) + fun put(keyExpr: KeyExpr, payload: IntoZBytes, options: PutOptions = PutOptions()) { + resolvePut(keyExpr, payload, options) + } /** - * Declare a [Delete]. + * Perform a delete operation to the specified [keyExpr]. * - * Example: - * - * ```java - * try (Session session = Session.open()) { - * try (KeyExpr keyExpr = KeyExpr.tryFrom("demo/example")) { - * session.delete(keyExpr).res(); - * System.out.println("Performed delete on " + keyExpr + "."); - * } - * } - * ``` - * - * @param keyExpr The [KeyExpr] to be used for the delete operation. - * @return a resolvable [Delete.Builder]. + * @param keyExpr The [KeyExpr] for performing the delete operation. + * @param options Optional [DeleteOptions] to configure the delete operation. */ - fun delete(keyExpr: KeyExpr): Delete.Builder = Delete.newBuilder(this, keyExpr) + @JvmOverloads + @Throws(ZError::class) + fun delete(keyExpr: KeyExpr, options: DeleteOptions = DeleteOptions()) { + resolveDelete(keyExpr, options) + } /** Returns if session is open or has been closed. */ - fun isOpen(): Boolean { - return jniSession != null + fun isClosed(): Boolean { + return jniSession == null + } + + /** + * Returns the [SessionInfo] of this session. + */ + fun info(): SessionInfo { + return SessionInfo(this) } - @Throws(SessionException::class) - internal fun resolvePublisher(keyExpr: KeyExpr, qos: QoS): Publisher { + /** + * Obtain a [Liveliness] instance tied to this Zenoh session. + */ + fun liveliness(): Liveliness { + return Liveliness(this) + } + + @Throws(ZError::class) + internal fun resolvePublisher(keyExpr: KeyExpr, options: PublisherOptions): Publisher { return jniSession?.run { - declarePublisher(keyExpr, qos) - } ?: throw(sessionClosedException) + val publisher = declarePublisher(keyExpr, options) + declarations.add(publisher) + publisher + } ?: throw (sessionClosedException) } - @Throws(ZenohException::class) - internal fun resolveSubscriber( - keyExpr: KeyExpr, callback: Callback, onClose: () -> Unit, receiver: R?, reliability: Reliability - ): Subscriber { + @Throws(ZError::class) + internal fun resolveSubscriberWithHandler( + keyExpr: KeyExpr, handler: Handler + ): HandlerSubscriber { return jniSession?.run { - declareSubscriber(keyExpr, callback, onClose, receiver, reliability) + val subscriber = declareSubscriberWithHandler(keyExpr, handler) + declarations.add(subscriber) + subscriber } ?: throw (sessionClosedException) } - @Throws(ZenohException::class) - internal fun resolveQueryable( - keyExpr: KeyExpr, callback: Callback, onClose: () -> Unit, receiver: R?, complete: Boolean - ): Queryable { + @Throws(ZError::class) + internal fun resolveSubscriberWithCallback( + keyExpr: KeyExpr, callback: Callback + ): CallbackSubscriber { return jniSession?.run { - declareQueryable(keyExpr, callback, onClose, receiver, complete) + val subscriber = declareSubscriberWithCallback(keyExpr, callback) + declarations.add(subscriber) + subscriber } ?: throw (sessionClosedException) } - @Throws(ZenohException::class) - internal fun resolveGet( - selector: Selector, + @Throws(ZError::class) + internal fun resolveQueryableWithHandler( + keyExpr: KeyExpr, handler: Handler, options: QueryableOptions + ): HandlerQueryable { + return jniSession?.run { + val queryable = declareQueryableWithHandler(keyExpr, handler, options) + declarations.add(queryable) + queryable + } ?: throw (sessionClosedException) + } + + @Throws(ZError::class) + internal fun resolveQueryableWithCallback( + keyExpr: KeyExpr, callback: Callback, options: QueryableOptions + ): CallbackQueryable { + return jniSession?.run { + val queryable = declareQueryableWithCallback(keyExpr, callback, options) + declarations.add(queryable) + queryable + } ?: throw (sessionClosedException) + } + + @Throws(ZError::class) + internal fun resolveGetWithHandler( + selector: IntoSelector, + handler: Handler, + options: GetOptions + ): R { + return jniSession?.performGetWithHandler( + selector, + handler, + options + ) ?: throw sessionClosedException + } + + @Throws(ZError::class) + internal fun resolveGetWithCallback( + selector: IntoSelector, callback: Callback, - onClose: () -> Unit, - receiver: R?, - timeout: Duration, - target: QueryTarget, - consolidation: ConsolidationMode, - value: Value?, - attachment: ByteArray?, - ): R? { - if (jniSession == null) { - throw sessionClosedException - } - return jniSession?.performGet(selector, callback, onClose, receiver, timeout, target, consolidation, value, attachment) + options: GetOptions + ) { + return jniSession?.performGetWithCallback( + selector, + callback, + options + ) ?: throw sessionClosedException + } + + @Throws(ZError::class) + internal fun resolvePut(keyExpr: KeyExpr, payload: IntoZBytes, putOptions: PutOptions) { + jniSession?.run { performPut(keyExpr, payload, putOptions) } + } + + @Throws(ZError::class) + internal fun resolveDelete(keyExpr: KeyExpr, deleteOptions: DeleteOptions) { + jniSession?.run { performDelete(keyExpr, deleteOptions) } + } + + @Throws(ZError::class) + internal fun zid(): ZenohId { + return jniSession?.zid() ?: throw sessionClosedException } - @Throws(ZenohException::class) - internal fun resolvePut(keyExpr: KeyExpr, put: Put) { - jniSession?.run { performPut(keyExpr, put) } + @Throws(ZError::class) + internal fun getPeersId(): List { + return jniSession?.peersZid() ?: throw sessionClosedException } - @Throws(ZenohException::class) - internal fun resolveDelete(keyExpr: KeyExpr, delete: Delete) { - jniSession?.run { performDelete(keyExpr, delete) } + @Throws(ZError::class) + internal fun getRoutersId(): List { + return jniSession?.routersZid() ?: throw sessionClosedException } /** Launches the session through the jni session, returning the [Session] on success. */ - @Throws(SessionException::class) + @Throws(ZError::class) private fun launch(): Session { jniSession!!.open(config) 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 ee156130..7a5931f8 100644 --- a/zenoh-java/src/commonMain/kotlin/io/zenoh/Zenoh.kt +++ b/zenoh-java/src/commonMain/kotlin/io/zenoh/Zenoh.kt @@ -14,12 +14,131 @@ package io.zenoh +import io.zenoh.Logger.Companion.LOG_ENV +import io.zenoh.exceptions.ZError +import io.zenoh.handlers.BlockingQueueHandler +import io.zenoh.handlers.Callback +import io.zenoh.handlers.Handler +import io.zenoh.jni.JNIScout +import io.zenoh.scouting.* +import java.util.* +import java.util.concurrent.BlockingQueue +import java.util.concurrent.LinkedBlockingDeque + +object Zenoh { + + /** + * Open a [Session] with the provided [Config]. + * + * @param config The configuration for the session. + * @return The [Session] on success. + */ + @JvmStatic + @Throws(ZError::class) + fun open(config: Config): Session { + return Session.open(config) + } + + /** + * Scout for routers and/or peers. + * + * Scout spawns a task that periodically sends scout messages and waits for Hello replies. + * Drop the returned Scout to stop the scouting task or explicitly call [Scout.stop] or [Scout.close]. + * + * @param scoutOptions Optional [ScoutOptions] to configure the scouting. + * @return A [HandlerScout] with a [BlockingQueue] receiver. + */ + @JvmOverloads + @JvmStatic + @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 + ) + } + + /** + * Scout for routers and/or peers. + * + * Scout spawns a task that periodically sends scout messages and waits for Hello replies. + * Drop the returned Scout to stop the scouting task or explicitly call [Scout.stop] or [Scout.close]. + * + * @param R The [handler]'s receiver type. + * @param handler [Handler] to handle the incoming [Hello] messages. + * @param scoutOptions Optional [ScoutOptions] to configure the scouting. + * @return A [HandlerScout] with the handler's receiver. + */ + @JvmOverloads + @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 + ) + } + + /** + * Scout for routers and/or peers. + * + * Scout spawns a task that periodically sends scout messages and waits for Hello replies. + * Drop the returned Scout to stop the scouting task or explicitly call [Scout.stop] or [Scout.close]. + * + * @param callback [Callback] to handle the incoming [Hello] messages. + * @param scoutOptions Optional [ScoutOptions] to configure the scouting. + * @return A [CallbackScout] with the handler's receiver. + */ + @JvmOverloads + @JvmStatic + @Throws(ZError::class) + fun scout(callback: Callback, scoutOptions: ScoutOptions = ScoutOptions()): CallbackScout { + return JNIScout.scoutWithCallback( + scoutOptions.whatAmI, callback, config = scoutOptions.config + ) + } + + /** + * Initializes the zenoh runtime logger, using rust environment settings. + * E.g.: `RUST_LOG=info` will enable logging at info level. Similarly, you can set the variable to `error` or `debug`. + * + * Note that if the environment variable is not set, then logging will not be enabled. + * See https://docs.rs/env_logger/latest/env_logger/index.html for accepted filter format. + * + * @see Logger + */ + @JvmStatic + @Throws(ZError::class) + fun tryInitLogFromEnv() { + val logEnv = System.getenv(LOG_ENV) + if (logEnv != null) { + ZenohLoad + Logger.start(logEnv) + } + } + + /** + * Initializes the zenoh runtime logger, using rust environment settings or the provided fallback level. + * E.g.: `RUST_LOG=info` will enable logging at info level. Similarly, you can set the variable to `error` or `debug`. + * + * Note that if the environment variable is not set, then [fallbackFilter] will be used instead. + * See https://docs.rs/env_logger/latest/env_logger/index.html for accepted filter format. + * + * @param fallbackFilter: The fallback filter if the `RUST_LOG` environment variable is not set. + * @see Logger + */ + @JvmStatic + @Throws(ZError::class) + fun initLogFromEnvOr(fallbackFilter: String) { + ZenohLoad + val logLevelProp = System.getenv(LOG_ENV) + logLevelProp?.let { Logger.start(it) } ?: Logger.start(fallbackFilter) + } +} + /** * Static singleton class to load the Zenoh native library once and only once, as well as the logger in function of the * log level configuration. */ -internal expect class Zenoh private constructor() { - companion object { - fun load() - } -} +internal expect object ZenohLoad diff --git a/zenoh-java/src/commonMain/kotlin/io/zenoh/ZenohType.kt b/zenoh-java/src/commonMain/kotlin/io/zenoh/ZenohType.kt index 3089ea3c..b8940500 100644 --- a/zenoh-java/src/commonMain/kotlin/io/zenoh/ZenohType.kt +++ b/zenoh-java/src/commonMain/kotlin/io/zenoh/ZenohType.kt @@ -1,8 +1,7 @@ package io.zenoh /** - * Zenoh type. An empty interface to regroup elements of type [io.zenoh.sample.Sample], - * [io.zenoh.query.Reply] and [io.zenoh.queryable.Query]. + * Zenoh type. * * This kind of elements have in common that they can be received through the Zenoh network. */ diff --git a/zenoh-java/src/commonMain/kotlin/io/zenoh/bytes/Encoding.kt b/zenoh-java/src/commonMain/kotlin/io/zenoh/bytes/Encoding.kt new file mode 100644 index 00000000..a4f77815 --- /dev/null +++ b/zenoh-java/src/commonMain/kotlin/io/zenoh/bytes/Encoding.kt @@ -0,0 +1,502 @@ +// +// 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.bytes + +/** + * Default encoding values used by Zenoh. + * + * An encoding has a similar role to Content-type in HTTP: it indicates, when present, how data should be interpreted by the application. + * + * Please note the Zenoh protocol does not impose any encoding value, nor it operates on it. + * It can be seen as some optional metadata that is carried over by Zenoh in such a way the application may perform different operations depending on the encoding value. + * + * A set of associated constants are provided to cover the most common encodings for user convenience. + * This is particularly useful in helping Zenoh to perform additional network optimizations. + */ +class Encoding private constructor( + internal val id: Int, + internal val schema: String? = null, + private val description: String? = null +) { + internal constructor(id: Int, schema: String? = null) : this(id, schema, null) + + companion object { + + /** + * Just some bytes. + * + * Constant alias for string: `"zenoh/bytes"`. + * + * Usually used for types: `ByteArray`, `List`. + */ + @JvmField + val ZENOH_BYTES = Encoding(0, description = "zenoh/bytes") + + /** + * A UTF-8 string. + * + * Constant alias for string: `"zenoh/string"`. + * + * Usually used for type: `String`. + */ + @JvmField + val ZENOH_STRING = Encoding(1, description = "zenoh/string") + + /** + * Zenoh serialized data. + * + * Constant alias for string: `"zenoh/serialized"`. + */ + @JvmField + val ZENOH_SERIALIZED = Encoding(2, description = "zenoh/serialized") + + /** + * An application-specific stream of bytes. + * + * Constant alias for string: `"application/octet-stream"`. + */ + @JvmField + val APPLICATION_OCTET_STREAM = Encoding(3, description = "application/octet-stream") + + /** + * A textual file. + * + * Constant alias for string: `"text/plain"`. + */ + @JvmField + val TEXT_PLAIN = Encoding(4, description = "text/plain") + + /** + * JSON data intended to be consumed by an application. + * + * Constant alias for string: `"application/json"`. + */ + @JvmField + val APPLICATION_JSON = Encoding(5, description = "application/json") + + /** + * JSON data intended to be human readable. + * + * Constant alias for string: `"text/json"`. + */ + @JvmField + val TEXT_JSON = Encoding(6, description = "text/json") + + /** + * A Common Data Representation (CDR)-encoded data. + * + * Constant alias for string: `"application/cdr"`. + */ + @JvmField + val APPLICATION_CDR = Encoding(7, description = "application/cdr") + + /** + * A Concise Binary Object Representation (CBOR)-encoded data. + * + * Constant alias for string: `"application/cbor"`. + */ + @JvmField + val APPLICATION_CBOR = Encoding(8, description = "application/cbor") + + /** + * YAML data intended to be consumed by an application. + * + * Constant alias for string: `"application/yaml"`. + */ + @JvmField + val APPLICATION_YAML = Encoding(9, description = "application/yaml") + + /** + * YAML data intended to be human readable. + * + * Constant alias for string: `"text/yaml"`. + */ + @JvmField + val TEXT_YAML = Encoding(10, description = "text/yaml") + + /** + * JSON5 encoded data that are human readable. + * + * Constant alias for string: `"text/json5"`. + */ + @JvmField + val TEXT_JSON5 = Encoding(11, description = "text/json5") + + /** + * A Python object serialized using [pickle](https://docs.python.org/3/library/pickle.html). + * + * Constant alias for string: `"application/python-serialized-object"`. + */ + @JvmField + val APPLICATION_PYTHON_SERIALIZED_OBJECT = + Encoding(12, description = "application/python-serialized-object") + + /** + * An application-specific protobuf-encoded data. + * + * Constant alias for string: `"application/protobuf"`. + */ + @JvmField + val APPLICATION_PROTOBUF = Encoding(13, description = "application/protobuf") + + /** + * A Java serialized object. + * + * Constant alias for string: `"application/java-serialized-object"`. + */ + @JvmField + val APPLICATION_JAVA_SERIALIZED_OBJECT = + Encoding(14, description = "application/java-serialized-object") + + /** + * OpenMetrics data, commonly used by [Prometheus](https://prometheus.io/). + * + * Constant alias for string: `"application/openmetrics-text"`. + */ + @JvmField + val APPLICATION_OPENMETRICS_TEXT = + Encoding(15, description = "application/openmetrics-text") + + /** + * A Portable Network Graphics (PNG) image. + * + * Constant alias for string: `"image/png"`. + */ + @JvmField + val IMAGE_PNG = Encoding(16, description = "image/png") + + /** + * A Joint Photographic Experts Group (JPEG) image. + * + * Constant alias for string: `"image/jpeg"`. + */ + @JvmField + val IMAGE_JPEG = Encoding(17, description = "image/jpeg") + + /** + * A Graphics Interchange Format (GIF) image. + * + * Constant alias for string: `"image/gif"`. + */ + @JvmField + val IMAGE_GIF = Encoding(18, description = "image/gif") + + /** + * A BitMap (BMP) image. + * + * Constant alias for string: `"image/bmp"`. + */ + @JvmField + val IMAGE_BMP = Encoding(19, description = "image/bmp") + + /** + * A WebP image. + * + * Constant alias for string: `"image/webp"`. + */ + @JvmField + val IMAGE_WEBP = Encoding(20, description = "image/webp") + + /** + * An XML file intended to be consumed by an application. + * + * Constant alias for string: `"application/xml"`. + */ + @JvmField + val APPLICATION_XML = Encoding(21, description = "application/xml") + + /** + * A list of tuples, each consisting of a name and a value. + * + * Constant alias for string: `"application/x-www-form-urlencoded"`. + */ + @JvmField + val APPLICATION_X_WWW_FORM_URLENCODED = + Encoding(22, description = "application/x-www-form-urlencoded") + + /** + * An HTML file. + * + * Constant alias for string: `"text/html"`. + */ + @JvmField + val TEXT_HTML = Encoding(23, description = "text/html") + + /** + * An XML file that is human readable. + * + * Constant alias for string: `"text/xml"`. + */ + @JvmField + val TEXT_XML = Encoding(24, description = "text/xml") + + /** + * A CSS file. + * + * Constant alias for string: `"text/css"`. + */ + @JvmField + val TEXT_CSS = Encoding(25, description = "text/css") + + /** + * A JavaScript file. + * + * Constant alias for string: `"text/javascript"`. + */ + @JvmField + val TEXT_JAVASCRIPT = Encoding(26, description = "text/javascript") + + /** + * A Markdown file. + * + * Constant alias for string: `"text/markdown"`. + */ + @JvmField + val TEXT_MARKDOWN = Encoding(27, description = "text/markdown") + + /** + * A CSV file. + * + * Constant alias for string: `"text/csv"`. + */ + @JvmField + val TEXT_CSV = Encoding(28, description = "text/csv") + + /** + * An application-specific SQL query. + * + * Constant alias for string: `"application/sql"`. + */ + @JvmField + val APPLICATION_SQL = Encoding(29, description = "application/sql") + + /** + * Constrained Application Protocol (CoAP) data intended for CoAP-to-HTTP and HTTP-to-CoAP proxies. + * + * Constant alias for string: `"application/coap-payload"`. + */ + @JvmField + val APPLICATION_COAP_PAYLOAD = Encoding(30, description = "application/coap-payload") + + /** + * Defines a JSON document structure for expressing a sequence of operations to apply to a JSON document. + * + * Constant alias for string: `"application/json-patch+json"`. + */ + @JvmField + val APPLICATION_JSON_PATCH_JSON = Encoding(31, description = "application/json-patch+json") + + /** + * A JSON text sequence consists of any number of JSON texts, all encoded in UTF-8. + * + * Constant alias for string: `"application/json-seq"`. + */ + @JvmField + val APPLICATION_JSON_SEQ = Encoding(32, description = "application/json-seq") + + /** + * A JSONPath defines a string syntax for selecting and extracting JSON values from within a given JSON value. + * + * Constant alias for string: `"application/jsonpath"`. + */ + @JvmField + val APPLICATION_JSONPATH = Encoding(33, description = "application/jsonpath") + + /** + * A JSON Web Token (JWT). + * + * Constant alias for string: `"application/jwt"`. + */ + @JvmField + val APPLICATION_JWT = Encoding(34, description = "application/jwt") + + /** + * An application-specific MPEG-4 encoded data, either audio or video. + * + * Constant alias for string: `"application/mp4"`. + */ + @JvmField + val APPLICATION_MP4 = Encoding(35, description = "application/mp4") + + /** + * A SOAP 1.2 message serialized as XML 1.0. + * + * Constant alias for string: `"application/soap+xml"`. + */ + @JvmField + val APPLICATION_SOAP_XML = Encoding(36, description = "application/soap+xml") + + /** + * A YANG-encoded data commonly used by the Network Configuration Protocol (NETCONF). + * + * Constant alias for string: `"application/yang"`. + */ + @JvmField + val APPLICATION_YANG = Encoding(37, description = "application/yang") + + /** + * A MPEG-4 Advanced Audio Coding (AAC) media. + * + * Constant alias for string: `"audio/aac"`. + */ + @JvmField + val AUDIO_AAC = Encoding(38, description = "audio/aac") + + /** + * A Free Lossless Audio Codec (FLAC) media. + * + * Constant alias for string: `"audio/flac"`. + */ + @JvmField + val AUDIO_FLAC = Encoding(39, description = "audio/flac") + + /** + * An audio codec defined in MPEG-1, MPEG-2, MPEG-4, or registered at the MP4 registration authority. + * + * Constant alias for string: `"audio/mp4"`. + */ + @JvmField + val AUDIO_MP4 = Encoding(40, description = "audio/mp4") + + /** + * An Ogg-encapsulated audio stream. + * + * Constant alias for string: `"audio/ogg"`. + */ + @JvmField + val AUDIO_OGG = Encoding(41, description = "audio/ogg") + + /** + * A Vorbis-encoded audio stream. + * + * Constant alias for string: `"audio/vorbis"`. + */ + @JvmField + val AUDIO_VORBIS = Encoding(42, description = "audio/vorbis") + + /** + * A h261-encoded video stream. + * + * Constant alias for string: `"video/h261"`. + */ + @JvmField + val VIDEO_H261 = Encoding(43, description = "video/h261") + + /** + * A h263-encoded video stream. + * + * Constant alias for string: `"video/h263"`. + */ + @JvmField + val VIDEO_H263 = Encoding(44, description = "video/h263") + + /** + * A h264-encoded video stream. + * + * Constant alias for string: `"video/h264"`. + */ + @JvmField + val VIDEO_H264 = Encoding(45, description = "video/h264") + + /** + * A h265-encoded video stream. + * + * Constant alias for string: `"video/h265"`. + */ + @JvmField + val VIDEO_H265 = Encoding(46, description = "video/h265") + + /** + * A h266-encoded video stream. + * + * Constant alias for string: `"video/h266"`. + */ + @JvmField + val VIDEO_H266 = Encoding(47, description = "video/h266") + + /** + * A video codec defined in MPEG-1, MPEG-2, MPEG-4, or registered at the MP4 registration authority. + * + * Constant alias for string: `"video/mp4"`. + */ + @JvmField + val VIDEO_MP4 = Encoding(48, description = "video/mp4") + + /** + * An Ogg-encapsulated video stream. + * + * Constant alias for string: `"video/ogg"`. + */ + @JvmField + val VIDEO_OGG = Encoding(49, description = "video/ogg") + + /** + * An uncompressed, studio-quality video stream. + * + * Constant alias for string: `"video/raw"`. + */ + @JvmField + val VIDEO_RAW = Encoding(50, description = "video/raw") + + /** + * A VP8-encoded video stream. + * + * Constant alias for string: `"video/vp8"`. + */ + @JvmField + val VIDEO_VP8 = Encoding(51, description = "video/vp8") + + /** + * A VP9-encoded video stream. + * + * Constant alias for string: `"video/vp9"`. + */ + @JvmField + val VIDEO_VP9 = Encoding(52, description = "video/vp9") + + /** + * The default [Encoding] is [ZENOH_BYTES]. + */ + @JvmStatic + fun defaultEncoding() = ZENOH_BYTES + } + + /** + * Set a schema to this encoding. Zenoh does not define what a schema is and its semantics is left to the implementer. + * E.g. a common schema for `text/plain` encoding is `utf-8`. + */ + fun withSchema(schema: String): Encoding { + return Encoding(this.id, schema, this.description) + } + + override fun toString(): String { + val base = description ?: "unknown(${this.id})" + val schemaInfo = schema?.let { ";$it" } ?: "" + return "$base$schemaInfo" + } + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + + other as Encoding + + return id == other.id && schema == other.schema + } + + override fun hashCode(): Int { + return id.hashCode() + } +} diff --git a/zenoh-java/src/commonMain/kotlin/io/zenoh/bytes/IntoZBytes.kt b/zenoh-java/src/commonMain/kotlin/io/zenoh/bytes/IntoZBytes.kt new file mode 100644 index 00000000..a988c34f --- /dev/null +++ b/zenoh-java/src/commonMain/kotlin/io/zenoh/bytes/IntoZBytes.kt @@ -0,0 +1,42 @@ +// +// 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.bytes + +/** + * IntoZBytes interface. + * + * Classes implementing this interface can be serialized into a ZBytes object. + * + * Example: + * ```java + * class Foo implements IntoZBytes { + * + * private final String content; + * + * Foo(String content) { + * this.content = content; + * } + * + * @NotNull + * @Override + * public ZBytes into() { + * return ZBytes.from(content); + * } + * } + * ``` + */ +interface IntoZBytes { + fun into(): ZBytes +} diff --git a/zenoh-java/src/commonMain/kotlin/io/zenoh/bytes/ZBytes.kt b/zenoh-java/src/commonMain/kotlin/io/zenoh/bytes/ZBytes.kt new file mode 100644 index 00000000..1fd22d7c --- /dev/null +++ b/zenoh-java/src/commonMain/kotlin/io/zenoh/bytes/ZBytes.kt @@ -0,0 +1,69 @@ +// +// 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.bytes + +/** + * ZBytes contains the serialized bytes of user data. + * + * It provides convenient methods to the user for serialization/deserialization. + * + * **NOTE** + * + * Zenoh semantic and protocol take care of sending and receiving bytes + * without restricting the actual data types. Default (de)serializers are provided for + * convenience to the users to deal with primitives data types via a simple + * out-of-the-box encoding. They are NOT by any means the only (de)serializers + * users can use nor a limitation to the types supported by Zenoh. Users are free and + * encouraged to use any data format of their choice like JSON, protobuf, + * flatbuffers, etc. + * + */ +class ZBytes internal constructor(internal val bytes: ByteArray) : IntoZBytes { + + companion object { + + /** + * Creates a [ZBytes] instance from a [String]. + */ + @JvmStatic + fun from(string: String) = ZBytes(string.encodeToByteArray()) + + /** + * Creates a [ZBytes] instance from a [ByteArray]. + */ + @JvmStatic + fun from(bytes: ByteArray) = ZBytes(bytes) + } + + /** Returns the internal byte representation of the [ZBytes]. */ + fun toBytes(): ByteArray = bytes + + /** Attempts to decode the [ZBytes] into a string with UTF-8 encoding. */ + @Throws + fun tryToString(): String = + bytes.decodeToString(throwOnInvalidSequence = true) + + override fun toString(): String = bytes.decodeToString() + + override fun into(): ZBytes = this + + override fun equals(other: Any?) = other is ZBytes && bytes.contentEquals(other.bytes) + + override fun hashCode() = bytes.contentHashCode() +} + +internal fun ByteArray.into(): ZBytes { + return ZBytes(this) +} diff --git a/zenoh-java/src/commonMain/kotlin/io/zenoh/keyexpr/IntoKeyExpr.kt b/zenoh-java/src/commonMain/kotlin/io/zenoh/config/WhatAmI.kt similarity index 62% rename from zenoh-java/src/commonMain/kotlin/io/zenoh/keyexpr/IntoKeyExpr.kt rename to zenoh-java/src/commonMain/kotlin/io/zenoh/config/WhatAmI.kt index 74ab2d80..983c9221 100644 --- a/zenoh-java/src/commonMain/kotlin/io/zenoh/keyexpr/IntoKeyExpr.kt +++ b/zenoh-java/src/commonMain/kotlin/io/zenoh/config/WhatAmI.kt @@ -12,15 +12,19 @@ // ZettaScale Zenoh Team, // -package io.zenoh.keyexpr +package io.zenoh.config -import io.zenoh.exceptions.KeyExprException +/** + * WhatAmI + * + * The role of the node sending the `hello` message. + */ +enum class WhatAmI(internal val value: Int) { + Router(1), + Peer(2), + Client(4); -@Throws(KeyExprException::class) -fun String.intoKeyExpr(): KeyExpr { - if (this.isEmpty()) { - throw(KeyExprException("Attempting to create a KeyExpr from an empty string.")) + companion object { + internal fun fromInt(value: Int) = entries.first { value == it.value } } - return KeyExpr.autocanonize(this) } - diff --git a/zenoh-java/src/commonMain/kotlin/io/zenoh/config/ZenohId.kt b/zenoh-java/src/commonMain/kotlin/io/zenoh/config/ZenohId.kt new file mode 100644 index 00000000..62fefac5 --- /dev/null +++ b/zenoh-java/src/commonMain/kotlin/io/zenoh/config/ZenohId.kt @@ -0,0 +1,40 @@ +// +// 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.config + +import io.zenoh.jni.JNIZenohId + +/** + * The global unique id of a Zenoh peer. + */ +data class ZenohId internal constructor(internal val bytes: ByteArray) { + + override fun toString(): String { + return JNIZenohId.toStringViaJNI(bytes) + } + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + + other as ZenohId + + return bytes.contentEquals(other.bytes) + } + + override fun hashCode(): Int { + return bytes.contentHashCode() + } +} diff --git a/zenoh-java/src/commonMain/kotlin/io/zenoh/exceptions/ZenohException.kt b/zenoh-java/src/commonMain/kotlin/io/zenoh/exceptions/ZError.kt similarity index 82% rename from zenoh-java/src/commonMain/kotlin/io/zenoh/exceptions/ZenohException.kt rename to zenoh-java/src/commonMain/kotlin/io/zenoh/exceptions/ZError.kt index c94a2dac..c63a19ce 100644 --- a/zenoh-java/src/commonMain/kotlin/io/zenoh/exceptions/ZenohException.kt +++ b/zenoh-java/src/commonMain/kotlin/io/zenoh/exceptions/ZError.kt @@ -15,6 +15,6 @@ package io.zenoh.exceptions /** - * A Zenoh exception. + * A Zenoh Error. */ -abstract class ZenohException(override val message: String? = null) : Exception() +class ZError(override val message: String? = null): Exception() diff --git a/zenoh-java/src/commonMain/kotlin/io/zenoh/ext/ZDeserializer.kt b/zenoh-java/src/commonMain/kotlin/io/zenoh/ext/ZDeserializer.kt new file mode 100644 index 00000000..95aaa8d8 --- /dev/null +++ b/zenoh-java/src/commonMain/kotlin/io/zenoh/ext/ZDeserializer.kt @@ -0,0 +1,24 @@ +// +// 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.ext + +import com.google.common.reflect.TypeToken +import io.zenoh.bytes.ZBytes +import io.zenoh.jni.JNIZBytes + +fun zDeserialize(zbytes: ZBytes, type: TypeToken): T { + @Suppress("UNCHECKED_CAST") + return JNIZBytes.deserializeViaJNI(zbytes, type.type) as T +} diff --git a/zenoh-java/src/commonMain/kotlin/io/zenoh/exceptions/SessionException.kt b/zenoh-java/src/commonMain/kotlin/io/zenoh/ext/ZSerializer.kt similarity index 66% rename from zenoh-java/src/commonMain/kotlin/io/zenoh/exceptions/SessionException.kt rename to zenoh-java/src/commonMain/kotlin/io/zenoh/ext/ZSerializer.kt index 02027e0f..3358ed61 100644 --- a/zenoh-java/src/commonMain/kotlin/io/zenoh/exceptions/SessionException.kt +++ b/zenoh-java/src/commonMain/kotlin/io/zenoh/ext/ZSerializer.kt @@ -12,11 +12,12 @@ // ZettaScale Zenoh Team, // -package io.zenoh.exceptions +package io.zenoh.ext -/** - * Session exception. - * - * This kind of exceptions are thrown from the native code when something goes wrong with a Zenoh session. - */ -class SessionException(message: String?) : ZenohException(message) +import com.google.common.reflect.TypeToken +import io.zenoh.bytes.ZBytes +import io.zenoh.jni.JNIZBytes + +fun zSerialize(t: T, type: TypeToken): ZBytes { + return JNIZBytes.serializeViaJNI(t as Any, type.type) +} diff --git a/zenoh-java/src/commonMain/kotlin/io/zenoh/handlers/BlockingQueueHandler.kt b/zenoh-java/src/commonMain/kotlin/io/zenoh/handlers/BlockingQueueHandler.kt index 1bedd7fa..93a64182 100644 --- a/zenoh-java/src/commonMain/kotlin/io/zenoh/handlers/BlockingQueueHandler.kt +++ b/zenoh-java/src/commonMain/kotlin/io/zenoh/handlers/BlockingQueueHandler.kt @@ -21,9 +21,7 @@ import java.util.concurrent.BlockingQueue /** * Blocking queue handler * - * Implementation of a [Handler] with a [BlockingQueue] receiver. This handler is intended to be used - * as the default handler by the [io.zenoh.queryable.Queryable], [io.zenoh.subscriber.Subscriber] and [io.zenoh.query.Get], - * allowing us to send the incoming elements through a [BlockingQueue]. + * Implementation of a [Handler] with a [BlockingQueue] receiver. * * The way to tell no more elements of type [T] will be received is when an empty element is put (see [onClose]). * @@ -31,7 +29,7 @@ import java.util.concurrent.BlockingQueue * @property queue * @constructor Create empty Queue handler */ -class BlockingQueueHandler(private val queue: BlockingQueue>) : Handler>> { +internal class BlockingQueueHandler(private val queue: BlockingQueue>) : Handler>> { override fun handle(t: T) { queue.put(Optional.of(t)) diff --git a/zenoh-java/src/commonMain/kotlin/io/zenoh/handlers/Handler.kt b/zenoh-java/src/commonMain/kotlin/io/zenoh/handlers/Handler.kt index 0647e852..422d6d95 100644 --- a/zenoh-java/src/commonMain/kotlin/io/zenoh/handlers/Handler.kt +++ b/zenoh-java/src/commonMain/kotlin/io/zenoh/handlers/Handler.kt @@ -21,6 +21,9 @@ import io.zenoh.ZenohType * incoming [T] elements. * * **Example**: + * In this example we implement a handler that stores the received elements into an ArrayDeque, + * which can then be retrieved: + * * ```java * public class QueueHandler implements Handler> { * @@ -46,12 +49,12 @@ import io.zenoh.ZenohType * * That `QueueHandler` could then be used as follows, for instance for a subscriber: * ```java - * QueueHandler handler = new QueueHandler(); - * session.declareSubscriber(keyExpr).with(handler).res(); + * var queue = session.declareSubscriber(keyExpr, new QueueHandler()); * ... * ``` + * where the `queue` returned is the receiver from the handler. * - * @param T A receiving [ZenohType], either a [io.zenoh.sample.Sample], a [io.zenoh.query.Reply] or a [io.zenoh.queryable.Query]. + * @param T A receiving [ZenohType]. * @param R An arbitrary receiver. */ interface Handler { diff --git a/zenoh-java/src/commonMain/kotlin/io/zenoh/jni/JNIConfig.kt b/zenoh-java/src/commonMain/kotlin/io/zenoh/jni/JNIConfig.kt new file mode 100644 index 00000000..ea278988 --- /dev/null +++ b/zenoh-java/src/commonMain/kotlin/io/zenoh/jni/JNIConfig.kt @@ -0,0 +1,101 @@ +// +// Copyright (c) 2023 ZettaScale Technology +// +// This program and the accompanying materials are made available under the +// terms of the Eclipse Public License 2.0 which is available at +// http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 +// which is available at https://www.apache.org/licenses/LICENSE-2.0. +// +// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 +// +// Contributors: +// ZettaScale Zenoh Team, +// + +package io.zenoh.jni + +import io.zenoh.Config +import io.zenoh.ZenohLoad +import io.zenoh.exceptions.ZError +import java.io.File +import java.nio.file.Path + +internal class JNIConfig(internal val ptr: Long) { + + companion object { + + init { + ZenohLoad + } + + fun loadDefaultConfig(): Config { + val cfgPtr = loadDefaultConfigViaJNI() + return Config(JNIConfig(cfgPtr)) + } + + @Throws(ZError::class) + fun loadConfigFile(path: Path): Config { + val cfgPtr = loadConfigFileViaJNI(path.toString()) + return Config(JNIConfig(cfgPtr)) + } + + @Throws(ZError::class) + fun loadConfigFile(file: File): Config = loadConfigFile(file.toPath()) + + @Throws(ZError::class) + fun loadJsonConfig(rawConfig: String): Config { + val cfgPtr = loadJsonConfigViaJNI(rawConfig) + return Config(JNIConfig(cfgPtr)) + } + + @Throws(ZError::class) + fun loadJson5Config(rawConfig: String): Config { + val cfgPtr = loadJsonConfigViaJNI(rawConfig) + return Config(JNIConfig(cfgPtr)) + } + + @Throws(ZError::class) + fun loadYamlConfig(rawConfig: String): Config { + val cfgPtr = loadYamlConfigViaJNI(rawConfig) + return Config(JNIConfig(cfgPtr)) + } + + @Throws(ZError::class) + private external fun loadDefaultConfigViaJNI(): Long + + @Throws(ZError::class) + private external fun loadConfigFileViaJNI(path: String): Long + + @Throws(ZError::class) + private external fun loadJsonConfigViaJNI(rawConfig: String): Long + + @Throws(ZError::class) + private external fun loadYamlConfigViaJNI(rawConfig: String): Long + + @Throws(ZError::class) + private external fun getIdViaJNI(ptr: Long): ByteArray + + @Throws(ZError::class) + private external fun insertJson5ViaJNI(ptr: Long, key: String, value: String): Long + + /** Frees the underlying native config. */ + private external fun freePtrViaJNI(ptr: Long) + + @Throws(ZError::class) + private external fun getJsonViaJNI(ptr: Long, key: String): String + } + + fun close() { + freePtrViaJNI(ptr) + } + + @Throws(ZError::class) + fun getJson(key: String): String { + return getJsonViaJNI(ptr, key) + } + + @Throws(ZError::class) + fun insertJson5(key: String, value: String) { + insertJson5ViaJNI(this.ptr, key, value) + } +} diff --git a/zenoh-java/src/commonMain/kotlin/io/zenoh/jni/JNIKeyExpr.kt b/zenoh-java/src/commonMain/kotlin/io/zenoh/jni/JNIKeyExpr.kt index a8e62cc4..29e419e3 100644 --- a/zenoh-java/src/commonMain/kotlin/io/zenoh/jni/JNIKeyExpr.kt +++ b/zenoh-java/src/commonMain/kotlin/io/zenoh/jni/JNIKeyExpr.kt @@ -14,25 +14,29 @@ package io.zenoh.jni -import io.zenoh.Zenoh -import io.zenoh.exceptions.ZenohException +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 { - @Throws(ZenohException::class) + init { + ZenohLoad + } + + @Throws(ZError::class) fun tryFrom(keyExpr: String): KeyExpr { - Zenoh.load() // It may happen the zenoh library is not yet loaded when creating a key expression. return KeyExpr(tryFromViaJNI(keyExpr)) } - @Throws(ZenohException::class) + @Throws(ZError::class) fun autocanonize(keyExpr: String): KeyExpr { - Zenoh.load() return KeyExpr(autocanonizeViaJNI(keyExpr)) } + @Throws(ZError::class) fun intersects(keyExprA: KeyExpr, keyExprB: KeyExpr): Boolean = intersectsViaJNI( keyExprA.jniKeyExpr?.ptr ?: 0, keyExprA.keyExpr, @@ -40,6 +44,7 @@ internal class JNIKeyExpr(internal val ptr: Long) { keyExprB.keyExpr ) + @Throws(ZError::class) fun includes(keyExprA: KeyExpr, keyExprB: KeyExpr): Boolean = includesViaJNI( keyExprA.jniKeyExpr?.ptr ?: 0, keyExprA.keyExpr, @@ -47,17 +52,47 @@ internal class JNIKeyExpr(internal val ptr: Long) { keyExprB.keyExpr ) - @Throws(Exception::class) + @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(Exception::class) + @Throws(ZError::class) private external fun autocanonizeViaJNI(keyExpr: String): String - @Throws(Exception::class) + @Throws(ZError::class) private external fun intersectsViaJNI(ptrA: Long, keyExprA: String, ptrB: Long, keyExprB: String): Boolean - @Throws(Exception::class) + @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() { diff --git a/zenoh-java/src/commonMain/kotlin/io/zenoh/jni/JNILiveliness.kt b/zenoh-java/src/commonMain/kotlin/io/zenoh/jni/JNILiveliness.kt new file mode 100644 index 00000000..6d6a5aef --- /dev/null +++ b/zenoh-java/src/commonMain/kotlin/io/zenoh/jni/JNILiveliness.kt @@ -0,0 +1,186 @@ +// +// 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.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 { + replierId: ByteArray?, + 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(replierId?.let { ZenohId(it) }, sample) + } else { + reply = Reply.Error( + replierId?.let { ZenohId(it) }, + payload.into(), + Encoding(encodingId, schema = encodingSchema) + ) + } + callback.run(reply) + } + getViaJNI( + jniSession.sessionPtr.get(), + 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.get(), 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.get(), + 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.get(), + 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 new file mode 100644 index 00000000..991c860a --- /dev/null +++ b/zenoh-java/src/commonMain/kotlin/io/zenoh/jni/JNILivelinessToken.kt @@ -0,0 +1,12 @@ +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 index b2eb478f..6e694271 100644 --- a/zenoh-java/src/commonMain/kotlin/io/zenoh/jni/JNIPublisher.kt +++ b/zenoh-java/src/commonMain/kotlin/io/zenoh/jni/JNIPublisher.kt @@ -14,11 +14,12 @@ package io.zenoh.jni -import io.zenoh.exceptions.ZenohException -import io.zenoh.value.Value +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.publication.Publisher]. + * 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. */ @@ -27,12 +28,14 @@ internal class JNIPublisher(private val ptr: Long) { /** * Put operation. * - * @param value The [Value] to be put. + * @param payload Payload of the put. + * @param encoding Encoding of the payload. * @param attachment Optional attachment. */ - @Throws(ZenohException::class) - fun put(value: Value, attachment: ByteArray?) { - putViaJNI(value.payload, value.encoding.id.ordinal, value.encoding.schema, attachment, ptr) + @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) } /** @@ -40,9 +43,9 @@ internal class JNIPublisher(private val ptr: Long) { * * @param attachment Optional attachment. */ - @Throws(ZenohException::class) - fun delete(attachment: ByteArray?) { - deleteViaJNI(attachment, ptr) + @Throws(ZError::class) + fun delete(attachment: IntoZBytes?) { + deleteViaJNI(attachment?.into()?.bytes, ptr) } /** @@ -54,12 +57,12 @@ internal class JNIPublisher(private val ptr: Long) { freePtrViaJNI(ptr) } - @Throws(ZenohException::class) + @Throws(ZError::class) private external fun putViaJNI( valuePayload: ByteArray, encodingId: Int, encodingSchema: String?, attachment: ByteArray?, ptr: Long ) - @Throws(ZenohException::class) + @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/JNIQuery.kt b/zenoh-java/src/commonMain/kotlin/io/zenoh/jni/JNIQuery.kt index 20b8fb87..afda43e7 100644 --- a/zenoh-java/src/commonMain/kotlin/io/zenoh/jni/JNIQuery.kt +++ b/zenoh-java/src/commonMain/kotlin/io/zenoh/jni/JNIQuery.kt @@ -14,11 +14,12 @@ package io.zenoh.jni -import io.zenoh.exceptions.ZenohException +import io.zenoh.exceptions.ZError import io.zenoh.keyexpr.KeyExpr -import io.zenoh.prelude.QoS +import io.zenoh.bytes.Encoding +import io.zenoh.qos.QoS +import io.zenoh.bytes.IntoZBytes import io.zenoh.sample.Sample -import io.zenoh.value.Value import org.apache.commons.net.ntp.TimeStamp /** @@ -30,32 +31,29 @@ import org.apache.commons.net.ntp.TimeStamp */ internal class JNIQuery(private val ptr: Long) { - @Throws(ZenohException::class) fun replySuccess(sample: Sample) { val timestampEnabled = sample.timestamp != null replySuccessViaJNI( ptr, sample.keyExpr.jniKeyExpr?.ptr ?: 0, sample.keyExpr.keyExpr, - sample.value.payload, - sample.value.encoding.id.ordinal, - sample.value.encoding.schema, + sample.payload.bytes, + sample.encoding.id, + sample.encoding.schema, timestampEnabled, if (timestampEnabled) sample.timestamp!!.ntpValue() else 0, - sample.attachment, + sample.attachment?.bytes, sample.qos.express, sample.qos.priority.value, sample.qos.congestionControl.value ) } - @Throws(ZenohException::class) - fun replyError(errorValue: Value) { - replyErrorViaJNI(ptr, errorValue.payload, errorValue.encoding.id.ordinal, errorValue.encoding.schema) + fun replyError(error: IntoZBytes, encoding: Encoding) { + replyErrorViaJNI(ptr, error.into().bytes, encoding.id, encoding.schema) } - @Throws(ZenohException::class) - fun replyDelete(keyExpr: KeyExpr, timestamp: TimeStamp?, attachment: ByteArray?, qos: QoS) { + fun replyDelete(keyExpr: KeyExpr, timestamp: TimeStamp?, attachment: IntoZBytes?, qos: QoS) { val timestampEnabled = timestamp != null replyDeleteViaJNI( ptr, @@ -63,7 +61,7 @@ internal class JNIQuery(private val ptr: Long) { keyExpr.keyExpr, timestampEnabled, if (timestampEnabled) timestamp!!.ntpValue() else 0, - attachment, + attachment?.into()?.bytes, qos.express, qos.priority.value, qos.congestionControl.value @@ -74,7 +72,7 @@ internal class JNIQuery(private val ptr: Long) { freePtrViaJNI(ptr) } - @Throws(ZenohException::class) + @Throws(ZError::class) private external fun replySuccessViaJNI( queryPtr: Long, keyExprPtr: Long, @@ -90,7 +88,7 @@ internal class JNIQuery(private val ptr: Long) { qosCongestionControl: Int, ) - @Throws(ZenohException::class) + @Throws(ZError::class) private external fun replyErrorViaJNI( queryPtr: Long, errorValuePayload: ByteArray, @@ -98,7 +96,7 @@ internal class JNIQuery(private val ptr: Long) { encodingSchema: String?, ) - @Throws(ZenohException::class) + @Throws(ZError::class) private external fun replyDeleteViaJNI( queryPtr: Long, keyExprPtr: Long, diff --git a/zenoh-java/src/commonMain/kotlin/io/zenoh/jni/JNIQueryable.kt b/zenoh-java/src/commonMain/kotlin/io/zenoh/jni/JNIQueryable.kt index f17df868..e5f7d3ce 100644 --- a/zenoh-java/src/commonMain/kotlin/io/zenoh/jni/JNIQueryable.kt +++ b/zenoh-java/src/commonMain/kotlin/io/zenoh/jni/JNIQueryable.kt @@ -15,7 +15,7 @@ package io.zenoh.jni /** - * Adapter class to handle the interactions with Zenoh through JNI for a [Queryable] + * Adapter class to handle the interactions with Zenoh through JNI for a [io.zenoh.query.Queryable] * * @property ptr: raw pointer to the underlying native Queryable. */ diff --git a/zenoh-java/src/commonMain/kotlin/io/zenoh/jni/JNIScout.kt b/zenoh-java/src/commonMain/kotlin/io/zenoh/jni/JNIScout.kt new file mode 100644 index 00000000..f2e1c49c --- /dev/null +++ b/zenoh-java/src/commonMain/kotlin/io/zenoh/jni/JNIScout.kt @@ -0,0 +1,87 @@ +// +// 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 index e12b2948..f67f9889 100644 --- a/zenoh-java/src/commonMain/kotlin/io/zenoh/jni/JNISession.kt +++ b/zenoh-java/src/commonMain/kotlin/io/zenoh/jni/JNISession.kt @@ -15,130 +15,201 @@ package io.zenoh.jni import io.zenoh.* -import io.zenoh.prelude.Encoding -import io.zenoh.prelude.Encoding.ID -import io.zenoh.exceptions.SessionException -import io.zenoh.exceptions.ZenohException +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.prelude.* -import io.zenoh.protocol.ZenohID -import io.zenoh.publication.Delete -import io.zenoh.publication.Publisher -import io.zenoh.publication.Put +import io.zenoh.bytes.IntoZBytes +import io.zenoh.config.ZenohId +import io.zenoh.bytes.into +import io.zenoh.Config +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.queryable.Query -import io.zenoh.queryable.Queryable import io.zenoh.sample.Sample -import io.zenoh.selector.Selector -import io.zenoh.subscriber.Reliability -import io.zenoh.subscriber.Subscriber -import io.zenoh.value.Value +import io.zenoh.sample.SampleKind import org.apache.commons.net.ntp.TimeStamp -import java.time.Duration import java.util.concurrent.atomic.AtomicLong /** Adapter class to handle the communication with the Zenoh JNI code for a [Session]. */ internal class JNISession { + companion object { + init { + ZenohLoad + } + } + /* Pointer to the underlying Rust zenoh session. */ - private var sessionPtr: AtomicLong = AtomicLong(0) + internal var sessionPtr: AtomicLong = AtomicLong(0) - @Throws(ZenohException::class) + @Throws(ZError::class) fun open(config: Config) { - config.jsonConfig?.let { jsonConfig -> - sessionPtr.set(openSessionWithJsonConfigViaJNI(jsonConfig.toString())) - } ?: run { - sessionPtr.set(openSessionViaJNI(config.path?.toString())) - } + val session = openSessionViaJNI(config.jniConfig.ptr) + sessionPtr.set(session) } - @Throws(ZenohException::class) fun close() { closeSessionViaJNI(sessionPtr.get()) } - @Throws(ZenohException::class) - fun declarePublisher(keyExpr: KeyExpr, qos: QoS): Publisher { + @Throws(ZError::class) + fun declarePublisher(keyExpr: KeyExpr, publisherOptions: PublisherOptions): Publisher { val publisherRawPtr = declarePublisherViaJNI( keyExpr.jniKeyExpr?.ptr ?: 0, keyExpr.keyExpr, sessionPtr.get(), - qos.congestionControl.value, - qos.priority.value, - qos.express + publisherOptions.qos.congestionControl.value, + publisherOptions.qos.priority.value, + publisherOptions.qos.express, + publisherOptions.reliability.ordinal ) return Publisher( keyExpr, - qos, + publisherOptions.qos, + publisherOptions.encoding, JNIPublisher(publisherRawPtr), ) } - @Throws(ZenohException::class) - fun declareSubscriber( - keyExpr: KeyExpr, callback: Callback, onClose: () -> Unit, receiver: R?, reliability: Reliability - ): Subscriber { + @Throws(ZError::class) + fun declareSubscriberWithHandler( + keyExpr: KeyExpr, handler: Handler + ): HandlerSubscriber { val subCallback = - JNISubscriberCallback { keyExpr2, payload, encodingId, encodingSchema, kind, timestampNTP64, timestampIsValid, attachmentBytes, express: Boolean, priority: Int, congestionControl: Int -> + 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(keyExpr2, null), - Value(payload, Encoding(ID.fromId(encodingId)!!, encodingSchema)), + KeyExpr(keyExpr1, null), + payload.into(), + Encoding(encodingId, schema = encodingSchema), SampleKind.fromInt(kind), timestamp, - QoS(express, congestionControl, priority), - attachmentBytes + QoS(CongestionControl.fromInt(congestionControl), Priority.fromInt(priority), express), + attachmentBytes?.into() + ) + handler.handle(sample) + } + val subscriberRawPtr = declareSubscriberViaJNI( + keyExpr.jniKeyExpr?.ptr ?: 0, keyExpr.keyExpr, sessionPtr.get(), 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.get(), subCallback, onClose, reliability.ordinal + keyExpr.jniKeyExpr?.ptr ?: 0, + keyExpr.keyExpr, + sessionPtr.get(), + subCallback, + fun() {} ) - return Subscriber(keyExpr, receiver, JNISubscriber(subscriberRawPtr)) + return CallbackSubscriber(keyExpr, JNISubscriber(subscriberRawPtr)) } - @Throws(ZenohException::class) - fun declareQueryable( - keyExpr: KeyExpr, callback: Callback, onClose: () -> Unit, receiver: R?, complete: Boolean - ): Queryable { + @Throws(ZError::class) + fun declareQueryableWithCallback( + keyExpr: KeyExpr, callback: Callback, config: QueryableOptions + ): CallbackQueryable { val queryCallback = - JNIQueryableCallback { keyExprStr: String, selectorParams: String, withValue: Boolean, payload: ByteArray?, encodingId: Int, encodingSchema: String?, attachmentBytes: ByteArray?, queryPtr: Long -> + JNIQueryableCallback { keyExpr1: String, selectorParams: String, payload: ByteArray?, encodingId: Int, encodingSchema: String?, attachmentBytes: ByteArray?, queryPtr: Long -> val jniQuery = JNIQuery(queryPtr) - val keyExpr2 = KeyExpr(keyExprStr, null) - val selector = Selector(keyExpr2, selectorParams) - val value: Value? = - if (withValue) Value(payload!!, Encoding(ID.fromId(encodingId)!!, encodingSchema)) else null - val query = Query(keyExpr2, selector, value, attachmentBytes, jniQuery) + val keyExpr2 = KeyExpr(keyExpr1, null) + val selector = if (selectorParams.isEmpty()) { + Selector(keyExpr2) + } else { + Selector(keyExpr2, Parameters.from(selectorParams)) + } + val query = Query( + keyExpr2, + selector, + payload?.into(), + payload?.let { Encoding(encodingId, schema = encodingSchema) }, + attachmentBytes?.into(), + jniQuery + ) callback.run(query) } val queryableRawPtr = declareQueryableViaJNI( - keyExpr.jniKeyExpr?.ptr ?: 0, keyExpr.keyExpr, sessionPtr.get(), queryCallback, onClose, complete + keyExpr.jniKeyExpr?.ptr ?: 0, + keyExpr.keyExpr, + sessionPtr.get(), + 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 -> + val jniQuery = JNIQuery(queryPtr) + val keyExpr2 = KeyExpr(keyExpr1, null) + val selector = if (selectorParams.isEmpty()) { + Selector(keyExpr2) + } else { + Selector(keyExpr2, Parameters.from(selectorParams)) + } + val query = Query( + keyExpr2, + selector, + payload?.into(), + payload?.let { Encoding(encodingId, schema = encodingSchema) }, + attachmentBytes?.into(), + jniQuery + ) + handler.handle(query) + } + val queryableRawPtr = declareQueryableViaJNI( + keyExpr.jniKeyExpr?.ptr ?: 0, + keyExpr.keyExpr, + sessionPtr.get(), + queryCallback, + handler::onClose, + config.complete ) - return Queryable(keyExpr, receiver, JNIQueryable(queryableRawPtr)) + return HandlerQueryable(keyExpr, JNIQueryable(queryableRawPtr), handler.receiver()) } - @Throws(ZenohException::class) - fun performGet( - selector: Selector, + @Throws(ZError::class) + fun performGetWithCallback( + intoSelector: IntoSelector, callback: Callback, - onClose: () -> Unit, - receiver: R?, - timeout: Duration, - target: QueryTarget, - consolidation: ConsolidationMode, - value: Value?, - attachment: ByteArray? - ): R? { + options: GetOptions + ) { val getCallback = JNIGetCallback { - replierId: String?, + replierId: ByteArray?, success: Boolean, keyExpr: String?, - payload: ByteArray, + payload1: ByteArray, encodingId: Int, encodingSchema: String?, kind: Int, @@ -152,133 +223,212 @@ internal class JNISession { val reply: Reply if (success) { val timestamp = if (timestampIsValid) TimeStamp(timestampNTP64) else null - when (SampleKind.fromInt(kind)) { - SampleKind.PUT -> { - val sample = Sample( - KeyExpr(keyExpr!!, null), - Value(payload, Encoding(ID.fromId(encodingId)!!, encodingSchema)), - SampleKind.fromInt(kind), - timestamp, - QoS(express, congestionControl, priority), - attachmentBytes - ) - reply = Reply.Success(replierId?.let { ZenohID(it) }, sample) - } - - SampleKind.DELETE -> { - reply = Reply.Delete( - replierId?.let { ZenohID(it) }, - KeyExpr(keyExpr!!, null), - timestamp, - attachmentBytes, - QoS(express, congestionControl, priority) - ) - } - } + 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(replierId?.let { ZenohId(it) }, sample) } else { - reply = Reply.Error(replierId?.let { ZenohID(it) }, Value(payload, Encoding(ID.fromId(encodingId)!!, encodingSchema))) + reply = Reply.Error( + replierId?.let { ZenohId(it) }, + payload1.into(), + Encoding(encodingId, schema = encodingSchema) + ) } callback.run(reply) } + val selector = intoSelector.into() getViaJNI( selector.keyExpr.jniKeyExpr?.ptr ?: 0, selector.keyExpr.keyExpr, - selector.parameters, + selector.parameters.toString(), sessionPtr.get(), getCallback, - onClose, - timeout.toMillis(), - target.ordinal, - consolidation.ordinal, - attachment, - value != null, - value?.payload, - value?.encoding?.id?.ordinal ?: 0, - value?.encoding?.schema + 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 ) - return receiver } - @Throws(ZenohException::class) + @Throws(ZError::class) + fun performGetWithHandler( + intoSelector: IntoSelector, + handler: Handler, + options: GetOptions + ): R { + val getCallback = JNIGetCallback { + replierId: ByteArray?, + 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(replierId?.let { ZenohId(it) }, sample) + } else { + reply = Reply.Error( + replierId?.let { ZenohId(it) }, + 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.get(), + 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 + ) + return handler.receiver() + } + + + @Throws(ZError::class) fun declareKeyExpr(keyExpr: String): KeyExpr { val ptr = declareKeyExprViaJNI(sessionPtr.get(), keyExpr) return KeyExpr(keyExpr, JNIKeyExpr(ptr)) } - @Throws(ZenohException::class) + @Throws(ZError::class) fun undeclareKeyExpr(keyExpr: KeyExpr) { keyExpr.jniKeyExpr?.run { undeclareKeyExprViaJNI(sessionPtr.get(), this.ptr) keyExpr.jniKeyExpr = null - } ?: throw SessionException("Attempting to undeclare a non declared key expression.") + } ?: throw ZError("Attempting to undeclare a non declared key expression.") } - @Throws(Exception::class) + @Throws(ZError::class) fun performPut( keyExpr: KeyExpr, - put: Put, + payload: IntoZBytes, + options: PutOptions, ) { + val encoding = options.encoding ?: Encoding.defaultEncoding() putViaJNI( keyExpr.jniKeyExpr?.ptr ?: 0, keyExpr.keyExpr, sessionPtr.get(), - put.value.payload, - put.value.encoding.id.ordinal, - put.value.encoding.schema, - put.qos.congestionControl.value, - put.qos.priority.value, - put.qos.express, - put.attachment + payload.into().bytes, + encoding.id, + encoding.schema, + options.qos.congestionControl.value, + options.qos.priority.value, + options.qos.express, + options.attachment?.into()?.bytes, + options.reliability.ordinal ) } - @Throws(Exception::class) + @Throws(ZError::class) fun performDelete( keyExpr: KeyExpr, - delete: Delete, + options: DeleteOptions, ) { deleteViaJNI( keyExpr.jniKeyExpr?.ptr ?: 0, keyExpr.keyExpr, sessionPtr.get(), - delete.qos.congestionControl.value, - delete.qos.priority.value, - delete.qos.express, - delete.attachment + options.qos.congestionControl.value, + options.qos.priority.value, + options.qos.express, + options.attachment?.into()?.bytes, + options.reliability.ordinal ) } - @Throws(Exception::class) - private external fun openSessionViaJNI(configFilePath: String?): Long + @Throws(ZError::class) + fun zid(): ZenohId { + return ZenohId(getZidViaJNI(sessionPtr.get())) + } + + @Throws(ZError::class) + fun peersZid(): List { + return getPeersZidViaJNI(sessionPtr.get()).map { ZenohId(it) } + } + + @Throws(ZError::class) + fun routersZid(): List { + return getRoutersZidViaJNI(sessionPtr.get()).map { ZenohId(it) } + } + + @Throws(ZError::class) + private external fun getZidViaJNI(ptr: Long): ByteArray + + @Throws(ZError::class) + private external fun getPeersZidViaJNI(ptr: Long): List - @Throws(Exception::class) - private external fun openSessionWithJsonConfigViaJNI(jsonConfig: String?): Long + @Throws(ZError::class) + private external fun getRoutersZidViaJNI(ptr: Long): List - @Throws(Exception::class) + @Throws(ZError::class) + private external fun openSessionViaJNI(configPtr: Long): Long + + @Throws(ZError::class) private external fun closeSessionViaJNI(ptr: Long) - @Throws(Exception::class) + @Throws(ZError::class) private external fun declarePublisherViaJNI( keyExprPtr: Long, keyExprString: String, sessionPtr: Long, congestionControl: Int, priority: Int, - express: Boolean + express: Boolean, + reliability: Int ): Long - @Throws(Exception::class) + @Throws(ZError::class) private external fun declareSubscriberViaJNI( keyExprPtr: Long, keyExprString: String, sessionPtr: Long, callback: JNISubscriberCallback, onClose: JNIOnCloseCallback, - reliability: Int ): Long - @Throws(Exception::class) + @Throws(ZError::class) private external fun declareQueryableViaJNI( keyExprPtr: Long, keyExprString: String, @@ -288,17 +438,17 @@ internal class JNISession { complete: Boolean ): Long - @Throws(Exception::class) + @Throws(ZError::class) private external fun declareKeyExprViaJNI(sessionPtr: Long, keyExpr: String): Long - @Throws(Exception::class) + @Throws(ZError::class) private external fun undeclareKeyExprViaJNI(sessionPtr: Long, keyExprPtr: Long) - @Throws(Exception::class) + @Throws(ZError::class) private external fun getViaJNI( keyExprPtr: Long, keyExprString: String, - selectorParams: String, + selectorParams: String?, sessionPtr: Long, callback: JNIGetCallback, onClose: JNIOnCloseCallback, @@ -306,13 +456,12 @@ internal class JNISession { target: Int, consolidation: Int, attachmentBytes: ByteArray?, - withValue: Boolean, payload: ByteArray?, encodingId: Int, encodingSchema: String?, ) - @Throws(Exception::class) + @Throws(ZError::class) private external fun putViaJNI( keyExprPtr: Long, keyExprString: String, @@ -323,10 +472,11 @@ internal class JNISession { congestionControl: Int, priority: Int, express: Boolean, - attachmentBytes: ByteArray? + attachmentBytes: ByteArray?, + reliability: Int ) - @Throws(Exception::class) + @Throws(ZError::class) private external fun deleteViaJNI( keyExprPtr: Long, keyExprString: String, @@ -334,6 +484,7 @@ internal class JNISession { congestionControl: Int, priority: Int, express: Boolean, - attachmentBytes: ByteArray? + attachmentBytes: ByteArray?, + reliability: Int ) } diff --git a/zenoh-java/src/commonMain/kotlin/io/zenoh/jni/JNISubscriber.kt b/zenoh-java/src/commonMain/kotlin/io/zenoh/jni/JNISubscriber.kt index 73bd2dad..1bb80543 100644 --- a/zenoh-java/src/commonMain/kotlin/io/zenoh/jni/JNISubscriber.kt +++ b/zenoh-java/src/commonMain/kotlin/io/zenoh/jni/JNISubscriber.kt @@ -15,7 +15,7 @@ package io.zenoh.jni /** - * Adapter class to handle the interactions with Zenoh through JNI for a [io.zenoh.subscriber.Subscriber] + * Adapter class to handle the interactions with Zenoh through JNI for a [io.zenoh.pubsub.Subscriber] * * @property ptr: raw pointer to the underlying native Subscriber. */ diff --git a/zenoh-java/src/commonMain/kotlin/io/zenoh/jni/JNIZBytes.kt b/zenoh-java/src/commonMain/kotlin/io/zenoh/jni/JNIZBytes.kt new file mode 100644 index 00000000..799b9cac --- /dev/null +++ b/zenoh-java/src/commonMain/kotlin/io/zenoh/jni/JNIZBytes.kt @@ -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, +// + +package io.zenoh.jni + +import io.zenoh.ZenohLoad +import io.zenoh.bytes.ZBytes +import java.lang.reflect.Type + +@PublishedApi +internal object JNIZBytes { + + init { + ZenohLoad + } + + @JvmStatic + external fun serializeViaJNI(any: Any, type: Type): ZBytes + + @JvmStatic + external fun deserializeViaJNI(zBytes: ZBytes, type: Type): Any +} diff --git a/zenoh-java/src/commonMain/kotlin/io/zenoh/Resolvable.kt b/zenoh-java/src/commonMain/kotlin/io/zenoh/jni/JNIZenohId.kt similarity index 73% rename from zenoh-java/src/commonMain/kotlin/io/zenoh/Resolvable.kt rename to zenoh-java/src/commonMain/kotlin/io/zenoh/jni/JNIZenohId.kt index bc0012fe..53ecb5dc 100644 --- a/zenoh-java/src/commonMain/kotlin/io/zenoh/Resolvable.kt +++ b/zenoh-java/src/commonMain/kotlin/io/zenoh/jni/JNIZenohId.kt @@ -12,12 +12,16 @@ // ZettaScale Zenoh Team, // -package io.zenoh +package io.zenoh.jni -/** - * A resolvable function interface meant to be used by Zenoh builders. - */ -fun interface Resolvable { +import io.zenoh.ZenohLoad + +internal object JNIZenohId { + + init { + ZenohLoad + } + + external fun toStringViaJNI(bytes: ByteArray): String - fun res(): R } diff --git a/zenoh-java/src/commonMain/kotlin/io/zenoh/jni/callbacks/JNIGetCallback.kt b/zenoh-java/src/commonMain/kotlin/io/zenoh/jni/callbacks/JNIGetCallback.kt index b78fc33f..ae978471 100644 --- a/zenoh-java/src/commonMain/kotlin/io/zenoh/jni/callbacks/JNIGetCallback.kt +++ b/zenoh-java/src/commonMain/kotlin/io/zenoh/jni/callbacks/JNIGetCallback.kt @@ -17,7 +17,7 @@ package io.zenoh.jni.callbacks internal fun interface JNIGetCallback { fun run( - replierId: String?, + replierId: ByteArray?, success: Boolean, keyExpr: String?, payload: ByteArray, diff --git a/zenoh-java/src/commonMain/kotlin/io/zenoh/jni/callbacks/JNIQueryableCallback.kt b/zenoh-java/src/commonMain/kotlin/io/zenoh/jni/callbacks/JNIQueryableCallback.kt index 477d0dd0..31f5885f 100644 --- a/zenoh-java/src/commonMain/kotlin/io/zenoh/jni/callbacks/JNIQueryableCallback.kt +++ b/zenoh-java/src/commonMain/kotlin/io/zenoh/jni/callbacks/JNIQueryableCallback.kt @@ -15,14 +15,11 @@ package io.zenoh.jni.callbacks internal fun interface JNIQueryableCallback { - fun run( - keyExpr: String, - selectorParams: String, - withValue: Boolean, - payload: ByteArray?, - encodingId: Int, - encodingSchema: String?, - attachmentBytes: ByteArray?, - queryPtr: Long - ) + fun run(keyExpr: String, + selectorParams: String, + payload: ByteArray?, + encodingId: Int, + encodingSchema: String?, + attachmentBytes: ByteArray?, + queryPtr: Long) } diff --git a/zenoh-java/src/commonMain/kotlin/io/zenoh/protocol/ZenohID.kt b/zenoh-java/src/commonMain/kotlin/io/zenoh/jni/callbacks/JNIScoutCallback.kt similarity index 76% rename from zenoh-java/src/commonMain/kotlin/io/zenoh/protocol/ZenohID.kt rename to zenoh-java/src/commonMain/kotlin/io/zenoh/jni/callbacks/JNIScoutCallback.kt index 953fb226..0a8b20e9 100644 --- a/zenoh-java/src/commonMain/kotlin/io/zenoh/protocol/ZenohID.kt +++ b/zenoh-java/src/commonMain/kotlin/io/zenoh/jni/callbacks/JNIScoutCallback.kt @@ -12,9 +12,9 @@ // ZettaScale Zenoh Team, // -package io.zenoh.protocol +package io.zenoh.jni.callbacks -/** - * The global unique id of a Zenoh peer. - */ -class ZenohID(val id: String) +internal fun interface JNIScoutCallback { + + fun run(whatAmI: Int, zid: ByteArray, locators: List) +} 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 ec138385..b5fe6110 100644 --- a/zenoh-java/src/commonMain/kotlin/io/zenoh/keyexpr/KeyExpr.kt +++ b/zenoh-java/src/commonMain/kotlin/io/zenoh/keyexpr/KeyExpr.kt @@ -14,10 +14,12 @@ package io.zenoh.keyexpr -import io.zenoh.Resolvable import io.zenoh.Session -import io.zenoh.exceptions.KeyExprException +import io.zenoh.session.SessionDeclaration +import io.zenoh.exceptions.ZError import io.zenoh.jni.JNIKeyExpr +import io.zenoh.query.IntoSelector +import io.zenoh.query.Selector /** * # Address space @@ -56,11 +58,9 @@ import io.zenoh.jni.JNIKeyExpr * As an alternative, employing a try-with-resources pattern using Kotlin's `use` block is recommended. This approach * ensures that [close] is automatically called, safely managing the lifecycle of the [KeyExpr] instance. * - * @param keyExpr The string representation of the key expression. - * @param jniKeyExpr An optional [JNIKeyExpr] instance, present when the key expression was declared through [Session.declareKeyExpr], - * it represents the native instance of the key expression. */ -class KeyExpr internal constructor(internal val keyExpr: String, internal var jniKeyExpr: JNIKeyExpr? = null): AutoCloseable { +class KeyExpr internal constructor(internal val keyExpr: String, internal var jniKeyExpr: JNIKeyExpr? = null): AutoCloseable, IntoSelector, + SessionDeclaration { companion object { @@ -74,10 +74,10 @@ class KeyExpr internal constructor(internal val keyExpr: String, internal var jn * * @param keyExpr The intended key expression as a string. * @return The [KeyExpr] in case of success. - * @throws KeyExprException in the case of failure. + * @throws ZError in the case of failure. */ @JvmStatic - @Throws(KeyExprException::class) + @Throws(ZError::class) fun tryFrom(keyExpr: String): KeyExpr { return JNIKeyExpr.tryFrom(keyExpr) } @@ -90,10 +90,10 @@ class KeyExpr internal constructor(internal val keyExpr: String, internal var jn * * @param keyExpr The intended key expression as a string. * @return The canonized [KeyExpr]. - * @throws KeyExprException in the case of failure. + * @throws ZError in the case of failure. */ @JvmStatic - @Throws(KeyExprException::class) + @Throws(ZError::class) fun autocanonize(keyExpr: String): KeyExpr { return JNIKeyExpr.autocanonize(keyExpr) } @@ -104,6 +104,7 @@ class KeyExpr internal constructor(internal val keyExpr: String, internal var jn * defined by `this` and `other`. * Will return false as well if the key expression is not valid anymore. */ + @Throws(ZError::class) fun intersects(other: KeyExpr): Boolean { return JNIKeyExpr.intersects(this, other) } @@ -113,25 +114,37 @@ class KeyExpr internal constructor(internal val keyExpr: String, internal var jn * defined by `this`. * Will return false as well if the key expression is not valid anymore. */ + @Throws(ZError::class) fun includes(other: KeyExpr): Boolean { return JNIKeyExpr.includes(this, other) } /** - * Undeclare the key expression if it was previously declared on the specified [session]. - * - * @param session The session from which the key expression was previously declared. - * @return An empty [Resolvable]. + * Returns the relation between 'this' and other from 'this''s point of view ([SetIntersectionLevel.INCLUDES] + * signifies that self includes other). Note that this is slower than [intersects] and [includes], + * so you should favor these methods for most applications. + */ + @Throws(ZError::class) + fun relationTo(other: KeyExpr): SetIntersectionLevel { + return JNIKeyExpr.relationTo(this, other) + } + + /** + * Joins both sides, inserting a / in between them. + * This should be your preferred method when concatenating path segments. */ - fun undeclare(session: Session): Resolvable { - return session.undeclare(this) + @Throws(ZError::class) + fun join(other: String): KeyExpr { + return JNIKeyExpr.joinViaJNI(this, other) } /** - * Returns true if the [KeyExpr] has still associated a native key expression allowing it to perform operations. + * Performs string concatenation and returns the result as a KeyExpr if possible. + * You should probably prefer [join] as Zenoh may then take advantage of the hierarchical separation it inserts. */ - fun isValid(): Boolean { - return jniKeyExpr != null + @Throws(ZError::class) + fun concat(other: String): KeyExpr { + return JNIKeyExpr.concatViaJNI(this, other) } override fun toString(): String { @@ -139,13 +152,26 @@ class KeyExpr internal constructor(internal val keyExpr: String, internal var jn } /** - * Closes the key expression. Operations performed on this key expression won't be valid anymore after this call. + * Equivalent to [undeclare]. This function is automatically called when using try with resources. + * + * @see undeclare */ override fun close() { + undeclare() + } + + /** + * If the key expression was declared from a [Session], then [undeclare] frees the native key expression associated + * to this instance. The KeyExpr instance is downgraded into a normal KeyExpr, which still allows performing + * operations on it, but without the inner optimizations. + */ + override fun undeclare() { jniKeyExpr?.close() jniKeyExpr = null } + override fun into(): Selector = Selector(this) + override fun equals(other: Any?): Boolean { if (this === other) return true if (javaClass != other?.javaClass) return false diff --git a/zenoh-java/src/commonMain/kotlin/io/zenoh/exceptions/KeyExprException.kt b/zenoh-java/src/commonMain/kotlin/io/zenoh/keyexpr/SetIntersectionLevel.kt similarity index 54% rename from zenoh-java/src/commonMain/kotlin/io/zenoh/exceptions/KeyExprException.kt rename to zenoh-java/src/commonMain/kotlin/io/zenoh/keyexpr/SetIntersectionLevel.kt index 5836e02c..981cc4b1 100644 --- a/zenoh-java/src/commonMain/kotlin/io/zenoh/exceptions/KeyExprException.kt +++ b/zenoh-java/src/commonMain/kotlin/io/zenoh/keyexpr/SetIntersectionLevel.kt @@ -12,13 +12,20 @@ // ZettaScale Zenoh Team, // -package io.zenoh.exceptions +package io.zenoh.keyexpr /** - * Key expression exception. + * The possible relations between two sets. * - * This kind of exceptions are thrown from the native code when something goes wrong regarding a key expression, - * for instance when attempting to create a [io.zenoh.keyexpr.KeyExpr] from a string that does not respect the - * key expression conventions. + * Note that [EQUALS] implies [INCLUDES], which itself implies [INTERSECTS]. */ -class KeyExprException(msg: String) : ZenohException(msg) +enum class SetIntersectionLevel(internal val value: Int) { + DISJOINT(0), + INTERSECTS(1), + INCLUDES(2), + EQUALS(3); + + companion object { + internal fun fromInt(value: Int) = SetIntersectionLevel.entries.first { it.value == value } + } +} diff --git a/zenoh-java/src/commonMain/kotlin/io/zenoh/liveliness/Liveliness.kt b/zenoh-java/src/commonMain/kotlin/io/zenoh/liveliness/Liveliness.kt new file mode 100644 index 00000000..96711557 --- /dev/null +++ b/zenoh-java/src/commonMain/kotlin/io/zenoh/liveliness/Liveliness.kt @@ -0,0 +1,198 @@ +// +// 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.liveliness + +import io.zenoh.Session +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.keyexpr.KeyExpr +import io.zenoh.pubsub.CallbackSubscriber +import io.zenoh.pubsub.HandlerSubscriber +import io.zenoh.pubsub.Subscriber +import io.zenoh.query.Reply +import io.zenoh.sample.Sample +import java.time.Duration +import java.util.* +import java.util.concurrent.BlockingQueue +import java.util.concurrent.LinkedBlockingDeque + +/** + * A structure with functions to declare a [LivelinessToken], + * query existing [LivelinessToken]s and subscribe to liveliness changes. + * + * A [LivelinessToken] is a token which liveliness is tied + * to the Zenoh [Session] and can be monitored by remote applications. + * + * The [Liveliness] instance can be obtained with the [Session.liveliness] function + * of the [Session] instance. + */ +class Liveliness internal constructor(private val session: Session) { + + /** + * Create a LivelinessToken for the given key expression. + */ + @Throws(ZError::class) + fun declareToken(keyExpr: KeyExpr): LivelinessToken { + val jniSession = session.jniSession ?: throw Session.sessionClosedException + return JNILiveliness.declareToken(jniSession, keyExpr) + } + + /** + * Query the liveliness tokens with matching key expressions. + * + * @param keyExpr The [KeyExpr] for the query. + * @param timeout Optional timeout of the query, defaults to 10 secs. + */ + @JvmOverloads + @Throws(ZError::class) + fun get( + keyExpr: KeyExpr, + timeout: Duration = Duration.ofMillis(10000), + ): 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 + ) + } + + /** + * Query the liveliness tokens with matching key expressions. + * + * @param keyExpr The [KeyExpr] for the query. + * @param callback [Callback] to handle the incoming replies. + * @param timeout Optional timeout of the query, defaults to 10 secs. + */ + @JvmOverloads + @Throws(ZError::class) + fun get( + keyExpr: KeyExpr, callback: Callback, timeout: Duration = Duration.ofMillis(10000) + ) { + val jniSession = session.jniSession ?: throw Session.sessionClosedException + return JNILiveliness.get(jniSession, keyExpr, callback, Unit, timeout, {}) + } + + /** + * Query the liveliness tokens with matching key expressions. + * + * @param R The [Handler.receiver] type. + * @param keyExpr The [KeyExpr] for the query. + * @param handler [Handler] to deal with the incoming replies. + * @param timeout Optional timeout of the query, defaults to 10 secs. + */ + @JvmOverloads + @Throws(ZError::class) + fun get( + keyExpr: KeyExpr, handler: Handler, timeout: Duration = Duration.ofMillis(10000) + ): R { + val jniSession = session.jniSession ?: throw Session.sessionClosedException + val callback = handler::handle + return JNILiveliness.get( + jniSession, + keyExpr, + callback, + handler.receiver(), + timeout, + onClose = handler::onClose + ) + } + + /** + * Create a [Subscriber] for liveliness changes matching the given key expression. + * + * @param keyExpr The [KeyExpr] the subscriber will be listening to. + * @param options Optional [LivelinessSubscriberOptions] parameter for subscriber configuration. + */ + @JvmOverloads + @Throws(ZError::class) + fun declareSubscriber( + keyExpr: KeyExpr, + options: LivelinessSubscriberOptions = LivelinessSubscriberOptions() + ): 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 + ) + } + + /** + * Create a [Subscriber] for liveliness changes matching the given key expression. + * + * @param keyExpr The [KeyExpr] the subscriber will be listening to. + * @param callback The [Callback] to be run when a liveliness change is received. + * @param options Optional [LivelinessSubscriberOptions] parameter for subscriber configuration. + */ + @JvmOverloads + @Throws(ZError::class) + fun declareSubscriber( + keyExpr: KeyExpr, + callback: Callback, + options: LivelinessSubscriberOptions = LivelinessSubscriberOptions() + ): CallbackSubscriber { + val jniSession = session.jniSession ?: throw Session.sessionClosedException + return JNILiveliness.declareSubscriber( + jniSession, + keyExpr, + callback, + options.history, + fun() {} + ) + } + + /** + * Create a [Subscriber] for liveliness changes matching the given key expression. + * + * @param R The [Handler.receiver] type. + * @param keyExpr The [KeyExpr] the subscriber will be listening to. + * @param handler [Handler] to handle liveliness changes events. + * @param options Optional [LivelinessSubscriberOptions] parameter for subscriber configuration. + */ + @JvmOverloads + @Throws(ZError::class) + fun declareSubscriber( + keyExpr: KeyExpr, + handler: Handler, + options: LivelinessSubscriberOptions = LivelinessSubscriberOptions() + ): HandlerSubscriber { + val jniSession = session.jniSession ?: throw Session.sessionClosedException + return JNILiveliness.declareSubscriber( + jniSession, + keyExpr, + handler::handle, + handler.receiver(), + options.history, + handler::onClose + ) + } +} + +/** + * Options for the [Liveliness] subscriber. + */ +data class LivelinessSubscriberOptions(var history: Boolean = false) diff --git a/zenoh-java/src/commonMain/kotlin/io/zenoh/liveliness/LivelinessToken.kt b/zenoh-java/src/commonMain/kotlin/io/zenoh/liveliness/LivelinessToken.kt new file mode 100644 index 00000000..5d95b8fa --- /dev/null +++ b/zenoh-java/src/commonMain/kotlin/io/zenoh/liveliness/LivelinessToken.kt @@ -0,0 +1,53 @@ +// +// 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.liveliness + +import io.zenoh.jni.JNILivelinessToken +import io.zenoh.session.SessionDeclaration + +/** + * A token whose liveliness is tied to the Zenoh [io.zenoh.Session]. + * + * A declared liveliness token will be seen as alive by any other Zenoh + * application in the system that monitors it while the liveliness token + * is not undeclared or dropped, while the Zenoh application that declared + * it is alive (didn't stop or crashed) and while the Zenoh application + * that declared the token has Zenoh connectivity with the Zenoh application + * that monitors it. + * + * Liveliness tokens are automatically undeclared when dropped. + */ +class LivelinessToken internal constructor(private var jniLivelinessToken: JNILivelinessToken?): SessionDeclaration, AutoCloseable { + + /** + * Undeclares the token. + */ + override fun undeclare() { + jniLivelinessToken?.undeclare() + jniLivelinessToken = null + } + + /** + * Closes the token. This function is equivalent to [undeclare]. + * When using try-with-resources, this function is called automatically. + */ + override fun close() { + undeclare() + } + + protected fun finalize() { + undeclare() + } +} diff --git a/zenoh-java/src/commonMain/kotlin/io/zenoh/prelude/Encoding.kt b/zenoh-java/src/commonMain/kotlin/io/zenoh/prelude/Encoding.kt deleted file mode 100644 index 22e3fdc5..00000000 --- a/zenoh-java/src/commonMain/kotlin/io/zenoh/prelude/Encoding.kt +++ /dev/null @@ -1,119 +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.prelude - -/** - * Default encoding values used by Zenoh. - * - * An encoding has a similar role to Content-type in HTTP: it indicates, when present, how data should be interpreted by the application. - * - * Please note the Zenoh protocol does not impose any encoding value, nor it operates on it. - * It can be seen as some optional metadata that is carried over by Zenoh in such a way the application may perform different operations depending on the encoding value. - * - * A set of associated constants are provided to cover the most common encodings for user convenience. - * This is particularly useful in helping Zenoh to perform additional network optimizations. - * - */ -class Encoding(val id: ID, val schema: String? = null) { - - override fun equals(other: Any?): Boolean { - if (this === other) return true - if (javaClass != other?.javaClass) return false - - other as Encoding - - if (id != other.id) return false - return schema == other.schema - } - - override fun hashCode(): Int { - var result = id.hashCode() - result = 31 * result + schema.hashCode() - return result - } - - /** - * The ID of the encoding. - * - * @property id The id of the encoding. - * @property encoding The encoding name. - */ - enum class ID(val id: Int, val encoding: String) { - ZENOH_BYTES(0, "zenoh/bytes"), - ZENOH_INT(1, "zenoh/int"), - ZENOH_UINT(2, "zenoh/uint"), - ZENOH_FLOAT(3, "zenoh/float"), - ZENOH_BOOL(4, "zenoh/bool"), - ZENOH_STRING(5, "zenoh/string"), - ZENOH_ERROR(6, "zenoh/error"), - APPLICATION_OCTET_STREAM(7, "application/octet-stream"), - TEXT_PLAIN(8, "text/plain"), - APPLICATION_JSON(9, "application/json"), - TEXT_JSON(10, "text/json"), - APPLICATION_CDR(11, "application/cdr"), - APPLICATION_CBOR(12, "application/cbor"), - APPLICATION_YAML(13, "application/yaml"), - TEXT_YAML(14, "text/yaml"), - TEXT_JSON5(15, "text/json5"), - APPLICATION_PYTHON_SERIALIZED_OBJECT(16, "application/python-serialized-object"), - APPLICATION_PROTOBUF(17, "application/protobuf"), - APPLICATION_JAVA_SERIALIZED_OBJECT(18, "application/java-serialized-object"), - APPLICATION_OPENMETRICS_TEXT(19, "application/openmetrics-text"), - IMAGE_PNG(20, "image/png"), - IMAGE_JPEG(21, "image/jpeg"), - IMAGE_GIF(22, "image/gif"), - IMAGE_BMP(23, "image/bmp"), - IMAGE_WEBP(24, "image/webp"), - APPLICATION_XML(25, "application/xml"), - APPLICATION_X_WWW_FORM_URLENCODED(26, "application/x-www-form-urlencoded"), - TEXT_HTML(27, "text/html"), - TEXT_XML(28, "text/xml"), - TEXT_CSS(29, "text/css"), - TEXT_JAVASCRIPT(30, "text/javascript"), - TEXT_MARKDOWN(31, "text/markdown"), - TEXT_CSV(32, "text/csv"), - APPLICATION_SQL(33, "application/sql"), - APPLICATION_COAP_PAYLOAD(34, "application/coap-payload"), - APPLICATION_JSON_PATCH_JSON(35, "application/json-patch+json"), - APPLICATION_JSON_SEQ(36, "application/json-seq"), - APPLICATION_JSONPATH(37, "application/jsonpath"), - APPLICATION_JWT(38, "application/jwt"), - APPLICATION_MP4(39, "application/mp4"), - APPLICATION_SOAP_XML(40, "application/soap+xml"), - APPLICATION_YANG(41, "application/yang"), - AUDIO_AAC(42, "audio/aac"), - AUDIO_FLAC(43, "audio/flac"), - AUDIO_MP4(44, "audio/mp4"), - AUDIO_OGG(45, "audio/ogg"), - AUDIO_VORBIS(46, "audio/vorbis"), - VIDEO_H261(47, "video/h261"), - VIDEO_H263(48, "video/h263"), - VIDEO_H264(49, "video/h264"), - VIDEO_H265(50, "video/h265"), - VIDEO_H266(51, "video/h266"), - VIDEO_MP4(52, "video/mp4"), - VIDEO_OGG(53, "video/ogg"), - VIDEO_RAW(54, "video/raw"), - VIDEO_VP8(55, "video/vp8"), - VIDEO_VP9(56, "video/vp9"); - - companion object { - private val idToEnum = entries.associateBy(ID::id) - fun fromId(id: Int): ID? = idToEnum[id] - } - } -} - - diff --git a/zenoh-java/src/commonMain/kotlin/io/zenoh/prelude/QoS.kt b/zenoh-java/src/commonMain/kotlin/io/zenoh/prelude/QoS.kt deleted file mode 100644 index 1984006a..00000000 --- a/zenoh-java/src/commonMain/kotlin/io/zenoh/prelude/QoS.kt +++ /dev/null @@ -1,68 +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.prelude - -/** - * Quality of service settings used to send zenoh message. - * - * @property express If true, the message is not batched in order to reduce the latency. - * @property congestionControl [CongestionControl] policy used for the message. - * @property priority [Priority] policy used for the message. - */ -class QoS internal constructor( - internal val express: Boolean, - internal val congestionControl: CongestionControl, - internal val priority: Priority -) { - - internal constructor(express: Boolean, congestionControl: Int, priority: Int) : this( - express, CongestionControl.fromInt(congestionControl), Priority.fromInt(priority) - ) - - /** - * Returns priority of the message. - */ - fun priority(): Priority = priority - - /** - * Returns congestion control setting of the message. - */ - fun congestionControl(): CongestionControl = congestionControl - - /** - * Returns express flag. If it is true, the message is not batched to reduce the latency. - */ - fun isExpress(): Boolean = express - - companion object { - fun default() = QoS(false, CongestionControl.default(), Priority.default()) - } - - internal class Builder( - private var express: Boolean = false, - private var congestionControl: CongestionControl = CongestionControl.default(), - private var priority: Priority = Priority.default(), - ) { - - fun express(value: Boolean) = apply { this.express = value } - - fun priority(priority: Priority) = apply { this.priority = priority } - - fun congestionControl(congestionControl: CongestionControl) = - apply { this.congestionControl = congestionControl } - - fun build() = QoS(express, congestionControl, priority) - } -} diff --git a/zenoh-java/src/commonMain/kotlin/io/zenoh/publication/Delete.kt b/zenoh-java/src/commonMain/kotlin/io/zenoh/publication/Delete.kt deleted file mode 100644 index 22cf7f60..00000000 --- a/zenoh-java/src/commonMain/kotlin/io/zenoh/publication/Delete.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.publication - -import io.zenoh.Resolvable -import io.zenoh.Session -import io.zenoh.exceptions.ZenohException -import io.zenoh.keyexpr.KeyExpr -import io.zenoh.prelude.CongestionControl -import io.zenoh.prelude.Priority -import io.zenoh.prelude.QoS -import kotlin.Throws - -/** - * Delete operation to perform on Zenoh on a key expression. - * - * Example: - * ```java - * public void deleteExample() throws ZenohException { - * System.out.println("Opening session..."); - * try (Session session = Session.open()) { - * try (KeyExpr keyExpr = KeyExpr.tryFrom("demo/java/example")) { - * session.delete(keyExpr).res(); - * System.out.println("Performed a delete on '" + keyExpr); - * } - * } - * } - * ``` - * - * A delete operation is a special case of a Put operation, it is analogous to perform a Put with an empty value and - * specifying the sample kind to be `DELETE`. - */ -class Delete private constructor( - val keyExpr: KeyExpr, val qos: QoS, val attachment: ByteArray? -) { - - companion object { - /** - * Creates a new [Builder] associated with the specified [session] and [keyExpr]. - * - * @param session The [Session] from which the Delete will be performed. - * @param keyExpr The [KeyExpr] upon which the Delete will be performed. - * @return A [Delete] operation [Builder]. - */ - fun newBuilder(session: Session, keyExpr: KeyExpr): Builder { - return Builder(session, keyExpr) - } - } - - /** - * Builder to construct a [Delete] operation. - * - * @property session The [Session] from which the Delete will be performed - * @property keyExpr The [KeyExpr] from which the Delete will be performed - * @constructor Create a [Delete] builder. - */ - class Builder internal constructor( - val session: Session, - val keyExpr: KeyExpr, - ) : Resolvable { - - private var qosBuilder: QoS.Builder = QoS.Builder() - private var attachment: ByteArray? = null - - /** Change the [CongestionControl] to apply when routing the data. */ - fun congestionControl(congestionControl: CongestionControl) = - apply { this.qosBuilder.congestionControl(congestionControl) } - - /** Change the [Priority] of the written data. */ - fun priority(priority: Priority) = apply { this.qosBuilder.priority(priority) } - - /** - * Sets the express flag. If true, the reply won't be batched in order to reduce the latency. - */ - fun express(isExpress: Boolean) = apply { this.qosBuilder.express(isExpress) } - - /** Set an attachment to the put operation. */ - fun withAttachment(attachment: ByteArray) = apply { this.attachment = attachment } - - /** - * Performs a DELETE operation on the specified [keyExpr]. - * - * A successful [Result] only states the Delete request was properly sent through the network, it doesn't mean it - * was properly executed remotely. - */ - @Throws(ZenohException::class) - override fun res() { - val delete = Delete(this.keyExpr, qosBuilder.build(), attachment) - session.resolveDelete(keyExpr, delete) - } - } -} diff --git a/zenoh-java/src/commonMain/kotlin/io/zenoh/publication/Publisher.kt b/zenoh-java/src/commonMain/kotlin/io/zenoh/publication/Publisher.kt deleted file mode 100644 index e22298bc..00000000 --- a/zenoh-java/src/commonMain/kotlin/io/zenoh/publication/Publisher.kt +++ /dev/null @@ -1,170 +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.publication - -import io.zenoh.* -import io.zenoh.exceptions.SessionException -import io.zenoh.exceptions.ZenohException -import io.zenoh.jni.JNIPublisher -import io.zenoh.keyexpr.KeyExpr -import io.zenoh.prelude.CongestionControl -import io.zenoh.prelude.Priority -import io.zenoh.prelude.QoS -import io.zenoh.value.Value -import kotlin.Throws - -/** - * A Zenoh Publisher. - * - * A publisher is automatically dropped when using it with the 'try-with-resources' statement (i.e. 'use' in Kotlin). - * The session from which it was declared will also keep a reference to it and undeclare it once the session is closed. - * - * In order to declare a publisher, [Session.declarePublisher] must be called, which returns a [Publisher.Builder] from - * which we can specify the [Priority], and the [CongestionControl]. - * - * Example: - * ```java - * try (Session session = Session.open()) { - * try (KeyExpr keyExpr = KeyExpr.tryFrom("demo/java/greeting")) { - * System.out.println("Declaring publisher on '" + keyExpr + "'..."); - * try (Publisher publisher = session.declarePublisher(keyExpr).res()) { - * int i = 0; - * while (true) { - * publisher.put("Hello for the " + i + "th time!").res(); - * Thread.sleep(1000); - * i++; - * } - * } - * } - * } catch (ZenohException | InterruptedException e) { - * System.out.println("Error: " + e); - * } - * ``` - * - * The publisher configuration parameters can be later changed using the setter functions. - * - * @property keyExpr The key expression the publisher will be associated to. - * @property qos [QoS] configuration of the publisher. - * @property jniPublisher Delegate class handling the communication with the native code. - * @constructor Create empty Publisher with the default configuration. - */ -class Publisher internal constructor( - val keyExpr: KeyExpr, - private var qos: QoS, - private var jniPublisher: JNIPublisher?, -) : SessionDeclaration, AutoCloseable { - - companion object { - private val sessionException = SessionException("Publisher is not valid.") - } - - /** Performs a PUT operation on the specified [keyExpr] with the specified [value]. */ - fun put(value: Value) = Put(jniPublisher, value) - - /** Performs a PUT operation on the specified [keyExpr] with the specified string [value]. */ - fun put(value: String) = Put(jniPublisher, Value(value)) - - /** - * Performs a DELETE operation on the specified [keyExpr] - * - * @return A [Resolvable] operation. - */ - fun delete() = Delete(jniPublisher) - - /** Get congestion control policy. */ - fun getCongestionControl(): CongestionControl { - return qos.congestionControl() - } - - /** Get priority policy. */ - fun getPriority(): Priority { - return qos.priority() - } - - override fun isValid(): Boolean { - return jniPublisher != null - } - - override fun close() { - undeclare() - } - - override fun undeclare() { - jniPublisher?.close() - jniPublisher = null - } - - @Suppress("removal") - protected fun finalize() { - jniPublisher?.close() - } - - class Put internal constructor( - private var jniPublisher: JNIPublisher?, - val value: Value, - var attachment: ByteArray? = null - ) : Resolvable { - - fun withAttachment(attachment: ByteArray) = apply { this.attachment = attachment } - - override fun res() { - jniPublisher?.put(value, attachment) ?: throw(sessionException) - } - } - - class Delete internal constructor( - private var jniPublisher: JNIPublisher?, - var attachment: ByteArray? = null - ) : Resolvable { - - fun withAttachment(attachment: ByteArray) = apply { this.attachment = attachment } - - @Throws(ZenohException::class) - override fun res() { - jniPublisher?.delete(attachment) ?: throw(sessionException) - } - } - - /** - * Publisher Builder. - * - * @property session The [Session] from which the publisher is declared. - * @property keyExpr The key expression the publisher will be associated to. - * @constructor Create empty Builder. - */ - class Builder internal constructor( - internal val session: Session, - internal val keyExpr: KeyExpr, - ) { - private var qosBuilder: QoS.Builder = QoS.Builder() - - /** Change the [CongestionControl] to apply when routing the data. */ - fun congestionControl(congestionControl: CongestionControl) = - apply { this.qosBuilder.congestionControl(congestionControl) } - - /** Change the [Priority] of the written data. */ - fun priority(priority: Priority) = apply { this.qosBuilder.priority(priority) } - - /** - * Sets the express flag. If true, the reply won't be batched in order to reduce the latency. - */ - fun express(isExpress: Boolean) = apply { this.qosBuilder.express(isExpress) } - - fun res(): Publisher { - return session.run { resolvePublisher(keyExpr, qosBuilder.build()) } - } - } -} - diff --git a/zenoh-java/src/commonMain/kotlin/io/zenoh/publication/Put.kt b/zenoh-java/src/commonMain/kotlin/io/zenoh/publication/Put.kt deleted file mode 100644 index 94266a25..00000000 --- a/zenoh-java/src/commonMain/kotlin/io/zenoh/publication/Put.kt +++ /dev/null @@ -1,117 +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.publication - -import io.zenoh.Resolvable -import io.zenoh.Session -import io.zenoh.exceptions.ZenohException -import io.zenoh.keyexpr.KeyExpr -import io.zenoh.prelude.* -import io.zenoh.value.Value - -/** - * Put operation. - * - * A put puts a [io.zenoh.sample.Sample] into the specified key expression. - * - * Example: - * ```java - * try (Session session = Session.open()) { - * try (KeyExpr keyExpr = KeyExpr.tryFrom("demo/example/zenoh-java-put")) { - * String value = "Put from Java!"; - * session.put(keyExpr, value) - * .congestionControl(CongestionControl.BLOCK) - * .priority(Priority.REALTIME) - * .kind(SampleKind.PUT) - * .res(); - * System.out.println("Putting Data ('" + keyExpr + "': '" + value + "')..."); - * } - * } - * ``` - * - * This class is an open class for the sake of the [Delete] operation, which is a special case of [Put] operation. - * - * @property keyExpr The [KeyExpr] to which the put operation will be performed. - * @property value The [Value] to put. - * @property qos The [QoS] configuration. - * @property attachment An optional user attachment. - */ -class Put private constructor( - val keyExpr: KeyExpr, - val value: Value, - val qos: QoS, - val attachment: ByteArray? -) { - - companion object { - - /** - * Creates a bew [Builder] associated to the specified [session] and [keyExpr]. - * - * @param session The [Session] from which the put will be performed. - * @param keyExpr The [KeyExpr] upon which the put will be performed. - * @param value The [Value] to put. - * @return A [Put] operation [Builder]. - */ - internal fun newBuilder(session: Session, keyExpr: KeyExpr, value: Value): Builder { - return Builder(session, keyExpr, value) - } - } - - /** - * Builder to construct a [Put] operation. - * - * @property session The [Session] from which the put operation will be performed. - * @property keyExpr The [KeyExpr] upon which the put operation will be performed. - * @property value The [Value] to put. - * @constructor Create a [Put] builder. - */ - class Builder internal constructor( - private val session: Session, - private val keyExpr: KeyExpr, - private var value: Value, - ): Resolvable { - - private var qosBuilder: QoS.Builder = QoS.Builder() - private var attachment: ByteArray? = null - - /** Change the [Encoding] of the written data. */ - fun encoding(encoding: Encoding) = apply { - this.value = Value(value.payload, encoding) - } - - /** Change the [CongestionControl] to apply when routing the data. */ - fun congestionControl(congestionControl: CongestionControl) = - apply { this.qosBuilder.congestionControl(congestionControl) } - - /** Change the [Priority] of the written data. */ - fun priority(priority: Priority) = apply { this.qosBuilder.priority(priority) } - - /** - * Sets the express flag. If true, the reply won't be batched in order to reduce the latency. - */ - fun express(isExpress: Boolean) = apply { this.qosBuilder.express(isExpress) } - - /** Set an attachment to the put operation. */ - fun withAttachment(attachment: ByteArray) = apply { this.attachment = attachment } - - /** Resolves the put operation, returning a [Result]. */ - @Throws(ZenohException::class) - override fun res() { - val put = Put(keyExpr, value, qosBuilder.build(), attachment) - session.run { resolvePut(keyExpr, put) } - } - } -} diff --git a/zenoh-java/src/commonMain/kotlin/io/zenoh/pubsub/DeleteOptions.kt b/zenoh-java/src/commonMain/kotlin/io/zenoh/pubsub/DeleteOptions.kt new file mode 100644 index 00000000..cc488ce9 --- /dev/null +++ b/zenoh-java/src/commonMain/kotlin/io/zenoh/pubsub/DeleteOptions.kt @@ -0,0 +1,32 @@ +// +// 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.pubsub + +import io.zenoh.bytes.IntoZBytes +import io.zenoh.qos.QoS +import io.zenoh.qos.Reliability + +/** + * Options for delete operations. + * + * @param qos The [QoS] (Quality of Service) desired. + * @param reliability The [Reliability] desired. + * @param attachment Optional attachment for the delete operation. + */ +data class DeleteOptions( + var qos: QoS = QoS.defaultQoS(), + var reliability: Reliability = Reliability.RELIABLE, + var attachment: IntoZBytes? = null +) diff --git a/zenoh-java/src/commonMain/kotlin/io/zenoh/pubsub/Publisher.kt b/zenoh-java/src/commonMain/kotlin/io/zenoh/pubsub/Publisher.kt new file mode 100644 index 00000000..00b4d0b5 --- /dev/null +++ b/zenoh-java/src/commonMain/kotlin/io/zenoh/pubsub/Publisher.kt @@ -0,0 +1,118 @@ +// +// 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.pubsub + +import io.zenoh.* +import io.zenoh.bytes.Encoding +import io.zenoh.bytes.IntoZBytes +import io.zenoh.exceptions.ZError +import io.zenoh.jni.JNIPublisher +import io.zenoh.keyexpr.KeyExpr +import io.zenoh.qos.QoS +import io.zenoh.session.SessionDeclaration +import kotlin.Throws + +/** + * A Zenoh Publisher. + * + * A publisher is automatically dropped when using it with the 'try-with-resources' statement (i.e. 'use' in Kotlin). + * The session from which it was declared will also keep a reference to it and undeclare it once the session is closed. + * + * In order to declare a publisher, [Session.declarePublisher] must be called. + * + * Example: + * ```java + * try (Session session = Session.open()) { + * try (KeyExpr keyExpr = KeyExpr.tryFrom("demo/java/greeting")) { + * System.out.println("Declaring publisher on '" + keyExpr + "'..."); + * try (Publisher publisher = session.declarePublisher(keyExpr)) { + * int i = 0; + * while (true) { + * var payload = ZBytes.from("Hello for the " + i + "th time!"); + * publisher.put(payload); + * Thread.sleep(1000); + * i++; + * } + * } + * } + * } catch (ZError | InterruptedException e) { + * System.out.println("Error: " + e); + * } + * ``` + * + * The publisher configuration parameters can be later changed using the setter functions. + * + * @property keyExpr The key expression the publisher will be associated to. + * @property encoding The encoding user by the publisher. + */ +class Publisher internal constructor( + val keyExpr: KeyExpr, + private var qos: QoS, + val encoding: Encoding, + private var jniPublisher: JNIPublisher?, +) : SessionDeclaration, AutoCloseable { + + companion object { + private val publisherNotValid = ZError("Publisher is not valid.") + } + + /** Get the congestion control applied when routing the data. */ + fun congestionControl() = qos.congestionControl + + /** Get the priority of the written data. */ + fun priority() = qos.priority + + /** 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 + } + + /** 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 + } + + /** + * Performs a DELETE operation on the specified [keyExpr] + */ + @JvmOverloads + @Throws(ZError::class) + fun delete(options: DeleteOptions = DeleteOptions()) { + jniPublisher?.delete(options.attachment) ?: throw(publisherNotValid) + } + + /** + * Returns `true` if the publisher is still running. + */ + fun isValid(): Boolean { + return jniPublisher != null + } + + override fun close() { + undeclare() + } + + override fun undeclare() { + jniPublisher?.close() + jniPublisher = null + } + + @Suppress("removal") + protected fun finalize() { + jniPublisher?.close() + } +} diff --git a/zenoh-java/src/commonMain/kotlin/io/zenoh/pubsub/PublisherOptions.kt b/zenoh-java/src/commonMain/kotlin/io/zenoh/pubsub/PublisherOptions.kt new file mode 100644 index 00000000..a0461699 --- /dev/null +++ b/zenoh-java/src/commonMain/kotlin/io/zenoh/pubsub/PublisherOptions.kt @@ -0,0 +1,62 @@ +// +// Copyright (c) 2023 ZettaScale Technology +// +// This program and the accompanying materials are made available under the +// terms of the Eclipse Public License 2.0 which is available at +// http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 +// which is available at https://www.apache.org/licenses/LICENSE-2.0. +// +// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 +// +// Contributors: +// ZettaScale Zenoh Team, +// + +package io.zenoh.pubsub + +import io.zenoh.bytes.Encoding +import io.zenoh.qos.* + +/** + * Options for the publisher. + * + * @param encoding The encoding of the payload. + * @param reliability The desired reliability. + * @param qos The quality of service desired. + */ +data class PublisherOptions(var reliability: Reliability = Reliability.RELIABLE, + var qos: QoS = QoS.defaultQoS(), + var encoding: Encoding = Encoding.defaultEncoding()) { + + fun getQoS(): QoS { + return qos + } + + fun getCongestionControl(): CongestionControl { + return this.qos.congestionControl + } + + fun getExpress(): Boolean { + return this.qos.express + } + + fun getPriority(): Priority { + return this.qos.priority + } + + fun setQoS(qos: QoS) { + this.qos = qos + } + + fun setCongestionControl(congestionControl: CongestionControl) { + this.qos.congestionControl = congestionControl + } + + fun setExpress(express: Boolean) { + this.qos.express = express + } + + fun setPriority(priority: Priority) { + this.qos.priority = priority + } +} diff --git a/zenoh-java/src/commonMain/kotlin/io/zenoh/pubsub/PutOptions.kt b/zenoh-java/src/commonMain/kotlin/io/zenoh/pubsub/PutOptions.kt new file mode 100644 index 00000000..c2b747f3 --- /dev/null +++ b/zenoh-java/src/commonMain/kotlin/io/zenoh/pubsub/PutOptions.kt @@ -0,0 +1,76 @@ +// +// 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.pubsub + +import io.zenoh.bytes.Encoding +import io.zenoh.bytes.IntoZBytes +import io.zenoh.qos.* + +/** + * Options for the PUT operations. + * + * @param encoding The encoding of the payload. + * @param reliability The desired reliability. + * @param attachment Optional attachment. + * @param qos The quality of service desired. + */ +data class PutOptions( + var encoding: Encoding? = null, + var qos: QoS = QoS.defaultQoS(), + var reliability: Reliability = Reliability.RELIABLE, + var attachment: IntoZBytes? = null +) { + /** + * Get the congestion control. + */ + fun getCongestionControl(): CongestionControl { + return this.qos.congestionControl + } + + /** + * Get the express value of the QoS. + */ + fun getExpress(): Boolean { + return this.qos.express + } + + /** + * Get the priority. + */ + fun getPriority(): Priority { + return this.qos.priority + } + + /** + * Set the QoS congestion control. + */ + fun setCongestionControl(congestionControl: CongestionControl) { + this.qos.congestionControl = congestionControl + } + + /** + * Set the QoS express value. + */ + fun setExpress(express: Boolean) { + this.qos.express = express + } + + /** + * Set the priority desired. + */ + fun setPriority(priority: Priority) { + this.qos.priority = priority + } +} diff --git a/zenoh-java/src/commonMain/kotlin/io/zenoh/pubsub/Subscriber.kt b/zenoh-java/src/commonMain/kotlin/io/zenoh/pubsub/Subscriber.kt new file mode 100644 index 00000000..a3688303 --- /dev/null +++ b/zenoh-java/src/commonMain/kotlin/io/zenoh/pubsub/Subscriber.kt @@ -0,0 +1,122 @@ +// +// 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.pubsub + +import io.zenoh.handlers.BlockingQueueHandler +import io.zenoh.jni.JNISubscriber +import io.zenoh.keyexpr.KeyExpr +import io.zenoh.session.SessionDeclaration + +/** + * A subscriber that allows listening to updates on a key expression and reacting to changes. + * + * Its main purpose is to keep the subscription active as long as it exists. + * + * Example using the default [BlockingQueueHandler] handler: + * + * ```java + * var queue = session.declareSubscriber("a/b/c"); + * try (Session session = Zenoh.open(config)) { + * try (var subscriber = session.declareSubscriber(keyExpr)) { + * var receiver = subscriber.getReceiver(); + * assert receiver != null; + * while (true) { + * Optional wrapper = receiver.take(); + * if (wrapper.isEmpty()) { + * break; + * } + * System.out.println(wrapper.get()); + * } + * } + * } + * ``` + * + * Example using a callback: + * ```java + * try (Session session = Zenoh.open(config)) { + * session.declareSubscriber(keyExpr, System.out::println); + * } + * ``` + * + * Example using a handler: + * ```java + * class MyHandler implements Handler> {...} + * + * //... + * try (Session session = Zenoh.open(config)) { + * var handler = new MyHandler(); + * var arraylist = session.declareSubscriber(keyExpr, handler); + * // ... + * } + * ``` + */ +sealed class Subscriber( + val keyExpr: KeyExpr, private var jniSubscriber: JNISubscriber? +) : AutoCloseable, SessionDeclaration { + + fun isValid(): Boolean { + return jniSubscriber != null + } + + override fun undeclare() { + jniSubscriber?.close() + jniSubscriber = null + } + + override fun close() { + undeclare() + } + + protected fun finalize() { + jniSubscriber?.close() + } +} + +/** + * Subscriber using a callback to handle incoming samples. + * + * Example: + * ```java + * try (Session session = Zenoh.open(config)) { + * session.declareSubscriber(keyExpr, System.out::println); + * } + * ``` + */ +class CallbackSubscriber internal constructor(keyExpr: KeyExpr, jniSubscriber: JNISubscriber?): Subscriber(keyExpr, jniSubscriber) + +/** + * Subscriber using a [io.zenoh.handlers.Handler] for handling incoming samples. + * + * Example using the default handler: + * ```java + * try (Session session = Zenoh.open(config)) { + * try (HandlerSubscriber>> subscriber = session.declareSubscriber(keyExpr)) { + * BlockingQueue> receiver = subscriber.getReceiver(); + * assert receiver != null; + * while (true) { + * Optional wrapper = receiver.take(); + * if (wrapper.isEmpty()) { + * break; + * } + * System.out.println(wrapper.get()); + * } + * } + * } + * ``` + * + * @param R The type of the receiver. + * @param receiver The receiver of the subscriber's handler. + */ +class HandlerSubscriber internal constructor(keyExpr: KeyExpr, jniSubscriber: JNISubscriber?, val receiver: R): Subscriber(keyExpr, jniSubscriber) diff --git a/zenoh-java/src/commonMain/kotlin/io/zenoh/prelude/CongestionControl.kt b/zenoh-java/src/commonMain/kotlin/io/zenoh/qos/CongestionControl.kt similarity index 94% rename from zenoh-java/src/commonMain/kotlin/io/zenoh/prelude/CongestionControl.kt rename to zenoh-java/src/commonMain/kotlin/io/zenoh/qos/CongestionControl.kt index 64d981c1..82b3463a 100644 --- a/zenoh-java/src/commonMain/kotlin/io/zenoh/prelude/CongestionControl.kt +++ b/zenoh-java/src/commonMain/kotlin/io/zenoh/qos/CongestionControl.kt @@ -12,7 +12,7 @@ // ZettaScale Zenoh Team, // -package io.zenoh.prelude +package io.zenoh.qos /** The congestion control to be applied when routing the data. */ enum class CongestionControl (val value: Int) { @@ -30,7 +30,5 @@ enum class CongestionControl (val value: Int) { companion object { fun fromInt(value: Int) = entries.first { it.value == value } - - fun default() = DROP } } diff --git a/zenoh-java/src/commonMain/kotlin/io/zenoh/prelude/Priority.kt b/zenoh-java/src/commonMain/kotlin/io/zenoh/qos/Priority.kt similarity index 94% rename from zenoh-java/src/commonMain/kotlin/io/zenoh/prelude/Priority.kt rename to zenoh-java/src/commonMain/kotlin/io/zenoh/qos/Priority.kt index 6907565d..0e27780e 100644 --- a/zenoh-java/src/commonMain/kotlin/io/zenoh/prelude/Priority.kt +++ b/zenoh-java/src/commonMain/kotlin/io/zenoh/qos/Priority.kt @@ -12,7 +12,7 @@ // ZettaScale Zenoh Team, // -package io.zenoh.prelude +package io.zenoh.qos /** * The Priority of Zenoh messages. @@ -33,8 +33,5 @@ enum class Priority(val value: Int) { companion object { fun fromInt(value: Int) = entries.first { it.value == value } - - fun default() = DATA } } - diff --git a/zenoh-java/src/commonMain/kotlin/io/zenoh/qos/QoS.kt b/zenoh-java/src/commonMain/kotlin/io/zenoh/qos/QoS.kt new file mode 100644 index 00000000..56a0ad7c --- /dev/null +++ b/zenoh-java/src/commonMain/kotlin/io/zenoh/qos/QoS.kt @@ -0,0 +1,36 @@ +// +// 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.qos + +/** + * Quality of service settings used to send zenoh message. + * + * @property congestionControl [CongestionControl] policy used for the message. + * @property priority [Priority] policy used for the message. + * @property express If true, the message is not batched in order to reduce the latency. + */ +data class QoS ( + var congestionControl: CongestionControl = CongestionControl.DROP, + var priority: Priority = Priority.DATA, + var express: Boolean = false +) { + + companion object { + private val defaultQoS = QoS() + + @JvmStatic + fun defaultQoS() = defaultQoS + } +} diff --git a/zenoh-java/src/commonMain/kotlin/io/zenoh/subscriber/Reliability.kt b/zenoh-java/src/commonMain/kotlin/io/zenoh/qos/Reliability.kt similarity index 97% rename from zenoh-java/src/commonMain/kotlin/io/zenoh/subscriber/Reliability.kt rename to zenoh-java/src/commonMain/kotlin/io/zenoh/qos/Reliability.kt index d675758e..d574f27c 100644 --- a/zenoh-java/src/commonMain/kotlin/io/zenoh/subscriber/Reliability.kt +++ b/zenoh-java/src/commonMain/kotlin/io/zenoh/qos/Reliability.kt @@ -12,7 +12,7 @@ // ZettaScale Zenoh Team, // -package io.zenoh.subscriber +package io.zenoh.qos /** * The reliability policy. diff --git a/zenoh-java/src/commonMain/kotlin/io/zenoh/query/ConsolidationMode.kt b/zenoh-java/src/commonMain/kotlin/io/zenoh/query/ConsolidationMode.kt index a95fa0d3..f4c1b29c 100644 --- a/zenoh-java/src/commonMain/kotlin/io/zenoh/query/ConsolidationMode.kt +++ b/zenoh-java/src/commonMain/kotlin/io/zenoh/query/ConsolidationMode.kt @@ -35,8 +35,4 @@ enum class ConsolidationMode { /** Holds back samples to only send the set of samples that had the highest timestamp for their key. */ LATEST; - - companion object { - fun default() = AUTO - } } diff --git a/zenoh-java/src/commonMain/kotlin/io/zenoh/query/Get.kt b/zenoh-java/src/commonMain/kotlin/io/zenoh/query/Get.kt index fcd74e9e..08d7a506 100644 --- a/zenoh-java/src/commonMain/kotlin/io/zenoh/query/Get.kt +++ b/zenoh-java/src/commonMain/kotlin/io/zenoh/query/Get.kt @@ -14,181 +14,25 @@ package io.zenoh.query -import io.zenoh.handlers.Callback -import io.zenoh.Session -import io.zenoh.exceptions.ZenohException -import io.zenoh.handlers.BlockingQueueHandler -import io.zenoh.handlers.Handler -import io.zenoh.selector.Selector -import io.zenoh.value.Value +import io.zenoh.bytes.Encoding +import io.zenoh.bytes.IntoZBytes import java.time.Duration -import java.util.* -import java.util.concurrent.BlockingQueue -import java.util.concurrent.LinkedBlockingDeque /** * Get to query data from the matching queryables in the system. * - * Example with a [Callback]: - * ```java - * try (Session session = Session.open()) { - * try (KeyExpr keyExpr = KeyExpr.tryFrom("demo/java/example")) { - * session.get(keyExpr) - * .consolidation(ConsolidationMode.NONE) - * .withValue("Get value example") - * .with(reply -> System.out.println("Received reply " + reply)) - * .res() - * } - * } - * ``` - * - * @param R Receiver type of the [Handler] implementation. If no handler is provided to the builder, R will be [Unit]. + * @param timeout Timeout of the query. + * @param target The [QueryTarget] of the query. + * @param consolidation The [ConsolidationMode] of the query. + * @param payload Optional payload. + * @param encoding Encoding of the payload. + * @param attachment Optional attachment. */ -class Get private constructor() { - - companion object { - /** - * Creates a bew [Builder] associated to the specified [session] and [keyExpr]. - * - * @param session The [Session] from which the query will be triggered. - * @param selector The [Selector] with which the query will be performed. - * @return A [Builder] with a default [BlockingQueueHandler] to handle any incoming [Reply]. - */ - fun newBuilder(session: Session, selector: Selector): Builder>> { - return Builder(session, selector, handler = BlockingQueueHandler(LinkedBlockingDeque())) - } - } - - /** - * Builder to construct a [Get]. - * - * Either a [Handler] or a [Callback] must be specified. Note neither of them are stackable and are mutually exclusive, - * meaning that it is not possible to specify multiple callbacks and/or handlers, the builder only considers the - * last one specified. - * - * @param R The receiver type of the [Handler] implementation, defaults to [Unit] when no handler is specified. - * @property session The [Session] from which the query will be performed. - * @property selector The [Selector] with which the get query will be performed. - * @constructor Creates a Builder. This constructor is internal and should not be called directly. Instead, this - * builder should be obtained through the [Session] after calling [Session.get]. - */ - class Builder internal constructor( - private val session: Session, - private val selector: Selector, - private var callback: Callback? = null, - private var handler: Handler? = null, - ) { - - private var timeout = Duration.ofMillis(10000) - private var target: QueryTarget = QueryTarget.BEST_MATCHING - private var consolidation: ConsolidationMode = ConsolidationMode.default() - private var value: Value? = null - private var attachment: ByteArray? = null - private var onClose: (() -> Unit)? = null - - private constructor(other: Builder<*>, handler: Handler?) : this(other.session, other.selector) { - this.handler = handler - copyParams(other) - } - - private constructor(other: Builder<*>, callback: Callback?) : this(other.session, other.selector) { - this.callback = callback - copyParams(other) - } - - private fun copyParams(other: Builder<*>) { - this.timeout = other.timeout - this.target = other.target - this.consolidation = other.consolidation - this.value = other.value - this.attachment = other.attachment - this.onClose = other.onClose - } - - /** Specify the [QueryTarget]. */ - fun target(target: QueryTarget): Builder { - this.target = target - return this - } - - /** Specify the [ConsolidationMode]. */ - fun consolidation(consolidation: ConsolidationMode): Builder { - this.consolidation = consolidation - return this - } - - /** Specify the timeout. */ - fun timeout(timeout: Duration): Builder { - this.timeout = timeout - return this - } - - /** - * Specify a string value. A [Value] is generated with the provided message, therefore - * this method is equivalent to calling `withValue(Value(message))`. - */ - fun withValue(message: String): Builder { - this.value = Value(message) - return this - } - - /** Specify a [Value]. */ - fun withValue(value: Value): Builder { - this.value = value - return this - } - - /** Specify an attachment. */ - fun withAttachment(attachment: ByteArray): Builder { - this.attachment = attachment - return this - } - - /** - * Specify an action to be invoked when the Get operation is over. - * - * Zenoh will trigger ths specified action once no more replies are to be expected. - */ - fun onClose(action: () -> Unit): Builder { - this.onClose = action - return this - } - - /** Specify a [Callback]. Overrides any previously specified callback or handler. */ - fun with(callback: Callback): Builder = Builder(this, callback) - - /** Specify a [Handler]. Overrides any previously specified callback or handler. */ - fun with(handler: Handler): Builder = Builder(this, handler) - - /** Specify a [BlockingQueue]. Overrides any previously specified callback or handler. */ - fun with(blockingQueue: BlockingQueue>): Builder>> = Builder(this, BlockingQueueHandler(blockingQueue)) - - /** - * Resolve the builder triggering the query. - * - * @return The receiver [R] from the specified [Handler] (if specified). - */ - @Throws(ZenohException::class) - fun res(): R? { - require(callback != null || handler != null) { "Either a callback or a handler must be provided." } - val resolvedCallback = callback ?: Callback { t: Reply -> handler?.handle(t) } - val resolvedOnClose = fun() { - onClose?.invoke() - handler?.onClose() - } - return session.run { - resolveGet( - selector, - resolvedCallback, - resolvedOnClose, - handler?.receiver(), - timeout, - target, - consolidation, - value, - attachment - ) - } - } - } -} +data class GetOptions( + var timeout: Duration = Duration.ofMillis(10000), + var target: QueryTarget = QueryTarget.BEST_MATCHING, + var consolidation: ConsolidationMode = ConsolidationMode.AUTO, + var payload: IntoZBytes? = null, + var encoding: Encoding? = null, + var attachment: IntoZBytes? = null +) diff --git a/zenoh-java/src/commonMain/kotlin/io/zenoh/query/Parameters.kt b/zenoh-java/src/commonMain/kotlin/io/zenoh/query/Parameters.kt new file mode 100644 index 00000000..241ade35 --- /dev/null +++ b/zenoh-java/src/commonMain/kotlin/io/zenoh/query/Parameters.kt @@ -0,0 +1,148 @@ +// +// 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.query + +import java.net.URLDecoder + +/** + * Parameters of the [Selector]. + * + * When in string form, the `parameters` should be encoded like the query section of a URL: + * - parameters are separated by `;`, + * - the parameter name and value are separated by the first `=`, + * - in the absence of `=`, the parameter value is considered to be the empty string, + * - both name and value should use percent-encoding to escape characters, + * - defining a value for the same parameter name twice is considered undefined behavior and an + * error result is returned. + * + * @see Selector + */ +data class Parameters internal constructor(private val params: MutableMap) : IntoParameters { + + companion object { + + private const val LIST_SEPARATOR = ";" + private const val FIELD_SEPARATOR = "=" + private const val VALUE_SEPARATOR = "|" + + /** + * Creates an empty Parameters. + */ + @JvmStatic + fun empty() = Parameters(mutableMapOf()) + + /** + * Creates a [Parameters] instance from the provided map. + */ + @JvmStatic + fun from(params: Map): Parameters = Parameters(params.toMutableMap()) + + /** + * Attempts to create a [Parameters] from a string. + * + * When in string form, the `parameters` should be encoded like the query section of a URL: + * - parameters are separated by `;`, + * - the parameter name and value are separated by the first `=`, + * - in the absence of `=`, the parameter value is considered to be the empty string, + * - both name and value should use percent-encoding to escape characters, + * - defining a value for the same parameter name twice is considered undefined behavior and an + * error result is returned. + */ + @JvmStatic + fun from(params: String): Parameters { + if (params.isBlank()) { + return Parameters(mutableMapOf()) + } + return params.split(LIST_SEPARATOR).fold(mutableMapOf()) { parametersMap, parameter -> + val (key, value) = parameter.split(FIELD_SEPARATOR).let { it[0] to it.getOrNull(1) } + require(!parametersMap.containsKey(key)) { "Duplicated parameter `$key` detected." } + parametersMap[key] = value?.let { URLDecoder.decode(it, Charsets.UTF_8.name()) } ?: "" + parametersMap + }.let { Parameters(it) } + } + } + + override fun toString(): String = + params.entries.joinToString(LIST_SEPARATOR) { "${it.key}$FIELD_SEPARATOR${it.value}" } + + override fun into(): Parameters = this + + /** + * Returns empty if no parameters were provided. + */ + fun isEmpty(): Boolean = params.isEmpty() + + /** + * Returns true if the [key] is contained. + */ + fun containsKey(key: String): Boolean = params.containsKey(key) + + /** + * Returns the value of the [key] if present. + */ + fun get(key: String): String? = params[key] + + /** + * Returns the value of the [key] if present, or if not, the [default] value provided. + */ + fun getOrDefault(key: String, default: String): String = params.getOrDefault(key, default) + + /** + * Returns the values of the [key] if present. + * + * Example: + * ```java + * var parameters = Parameters.from("a=1;b=2;c=1|2|3"); + * assertEquals(List.of("1", "2", "3"), parameters.values("c")) + * ``` + */ + fun values(key: String): List? = params[key]?.split(VALUE_SEPARATOR) + + /** + * Inserts the key-value pair into the parameters, returning the old value + * in case of it being already present. + */ + fun insert(key: String, value: String): String? = params.put(key, value) + + /** + * Removes the [key] parameter, returning its value. + */ + fun remove(key: String): String? = params.remove(key) + + /** + * Extends the parameters with the [parameters] provided, overwriting + * any conflicting params. + */ + fun extend(parameters: IntoParameters) { + params.putAll(parameters.into().params) + } + + /** + * Extends the parameters with the [parameters] provided, overwriting + * any conflicting params. + */ + fun extend(parameters: Map) { + params.putAll(parameters) + } + + /** + * Returns a map with the key value pairs of the parameters. + */ + fun toMap(): Map = params +} + +interface IntoParameters { + fun into(): Parameters +} diff --git a/zenoh-java/src/commonMain/kotlin/io/zenoh/query/Query.kt b/zenoh-java/src/commonMain/kotlin/io/zenoh/query/Query.kt new file mode 100644 index 00000000..0d4de0af --- /dev/null +++ b/zenoh-java/src/commonMain/kotlin/io/zenoh/query/Query.kt @@ -0,0 +1,122 @@ +// +// 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.query + +import io.zenoh.ZenohType +import io.zenoh.bytes.Encoding +import io.zenoh.bytes.IntoZBytes +import io.zenoh.bytes.ZBytes +import io.zenoh.exceptions.ZError +import io.zenoh.jni.JNIQuery +import io.zenoh.keyexpr.KeyExpr +import io.zenoh.sample.Sample +import io.zenoh.sample.SampleKind + +/** + * Represents a Zenoh Query in Kotlin. + * + * A Query is generated within the context of a [Queryable], when receiving a [Query] request. + * + * @property keyExpr The key expression to which the query is associated. + * @property selector The selector + * @property payload Optional payload in case the received query was declared using "with query". + * @property encoding Encoding of the [payload]. + * @property attachment Optional attachment. + */ +class Query internal constructor( + val keyExpr: KeyExpr, + val selector: Selector, + val payload: ZBytes?, + val encoding: Encoding?, + val attachment: ZBytes?, + private var jniQuery: JNIQuery? +) : AutoCloseable, ZenohType { + + /** Shortcut to the [selector]'s parameters. */ + val parameters = selector.parameters + + /** + * Reply to the specified key expression. + * + * @param keyExpr Key expression to reply to. This parameter must not be necessarily the same + * as the key expression from the Query, however it must intersect with the query key. + * @param options Optional options for configuring the reply. + */ + @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, + options.qos, + options.attachment + ) + jniQuery?.apply { + replySuccess(sample) + jniQuery = null + } ?: throw (ZError("Query is invalid")) + } + + /** + * Reply "delete" to the specified key expression. + * + * @param keyExpr Key expression to reply to. This parameter must not be necessarily the same + * as the key expression from the Query, however it must intersect with the query key. + * @param options Optional options for configuring the reply. + */ + @JvmOverloads + @Throws(ZError::class) + fun replyDel(keyExpr: KeyExpr, options: ReplyDelOptions = ReplyDelOptions()) { + jniQuery?.apply { + replyDelete( + keyExpr, + options.timeStamp, + options.attachment, + options.qos + ) + jniQuery = null + } ?: throw (ZError("Query is invalid")) + } + + /** + * Reply "error" to the specified key expression. + * + * @param message The error message. + * @param options Optional options for configuring the reply. + */ + @JvmOverloads + @Throws(ZError::class) + fun replyErr(message: IntoZBytes, options: ReplyErrOptions = ReplyErrOptions()) { + jniQuery?.apply { + replyError(message.into(), options.encoding) + jniQuery = null + } ?: throw (ZError("Query is invalid")) + } + + override fun close() { + jniQuery?.apply { + this.close() + jniQuery = null + } + } + + @Suppress("removal") + protected fun finalize() { + close() + } +} diff --git a/zenoh-java/src/commonMain/kotlin/io/zenoh/query/Queryable.kt b/zenoh-java/src/commonMain/kotlin/io/zenoh/query/Queryable.kt new file mode 100644 index 00000000..eab11c56 --- /dev/null +++ b/zenoh-java/src/commonMain/kotlin/io/zenoh/query/Queryable.kt @@ -0,0 +1,127 @@ +// +// 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.query + +import io.zenoh.handlers.BlockingQueueHandler +import io.zenoh.handlers.Handler +import io.zenoh.jni.JNIQueryable +import io.zenoh.keyexpr.KeyExpr +import io.zenoh.session.SessionDeclaration + +/** + * A queryable that allows to perform multiple queries on the specified [KeyExpr]. + * + * Its main purpose is to keep the queryable active as long as it exists. + * + * Example using the default [BlockingQueueHandler] handler: + * ```java + * try (Session session = Zenoh.open(config)) { + * var queryable = session.declareQueryable(keyExpr); + * BlockingQueue> receiver = queryable.getReceiver(); + * assert receiver != null; + * while (true) { + * Optional wrapper = receiver.take(); + * if (wrapper.isEmpty()) { + * break; + * } + * Query query = wrapper.get(); + * query.reply(query.getKeyExpr(), ZBytes.from("Example reply"); + * } + * } + * ``` + * + * Example using a [io.zenoh.handlers.Callback]: + * ```java + * try (Session session = Zenoh.open(config)) { + * var queryable = session.declareQueryable(keyExpr, query -> query.reply(query.getKeyExpr(), ZBytes.from("Example reply")); + * } + * ``` + * + * @property keyExpr The [KeyExpr] to which the subscriber is associated. + * @property jniQueryable Delegate object in charge of communicating with the underlying native code. + * @see CallbackQueryable + * @see HandlerQueryable + */ +sealed class Queryable( + val keyExpr: KeyExpr, private var jniQueryable: JNIQueryable? +) : AutoCloseable, SessionDeclaration { + + fun isValid(): Boolean { + return jniQueryable != null + } + + /** + * Undeclares the queryable. + */ + override fun undeclare() { + jniQueryable?.close() + jniQueryable = null + } + + /** + * Closes the queryable, equivalent to [undeclare]. This function is automatically called + * when using try with resources. + */ + override fun close() { + undeclare() + } + + protected fun finalize() { + jniQueryable?.close() + } +} + +/** + * [Queryable] receiving replies through a callback. + * + * Example + * ```java + * try (Session session = Zenoh.open(config)) { + * CallbackQueryable queryable = session.declareQueryable(keyExpr, query -> query.reply(query.getKeyExpr(), ZBytes.from("Example reply")); + * } + * ``` + */ +class CallbackQueryable internal constructor(keyExpr: KeyExpr, jniQueryable: JNIQueryable?): Queryable(keyExpr, jniQueryable) + +/** + * [Queryable] receiving replies through a [Handler]. + * + * Example using the default receiver: + * ```java + * try (Session session = Zenoh.open(config)) { + * Queryable>> queryable = session.declareQueryable(keyExpr); + * BlockingQueue> receiver = queryable.getReceiver(); + * while (true) { + * Optional wrapper = receiver.take(); + * if (wrapper.isEmpty()) { + * break; + * } + * Query query = wrapper.get(); + * query.reply(query.getKeyExpr(), ZBytes.from("Example reply"); + * } + * } + * ``` + * + * @param R The type of the handler's receiver. + * @param receiver The receiver of the queryable's handler. + */ +class HandlerQueryable internal constructor(keyExpr: KeyExpr, jniQueryable: JNIQueryable?, val receiver: R): Queryable(keyExpr, jniQueryable) + +/** + * Options for configuring a [Queryable]. + * + * @param complete The completeness of the information the queryable provides. + */ +data class QueryableOptions(var complete: Boolean = false) diff --git a/zenoh-java/src/commonMain/kotlin/io/zenoh/query/Reply.kt b/zenoh-java/src/commonMain/kotlin/io/zenoh/query/Reply.kt index 01d3ed98..a5b48aef 100644 --- a/zenoh-java/src/commonMain/kotlin/io/zenoh/query/Reply.kt +++ b/zenoh-java/src/commonMain/kotlin/io/zenoh/query/Reply.kt @@ -14,161 +14,29 @@ package io.zenoh.query -import io.zenoh.Resolvable import io.zenoh.ZenohType -import io.zenoh.exceptions.ZenohException +import io.zenoh.bytes.Encoding +import io.zenoh.bytes.ZBytes +import io.zenoh.config.ZenohId import io.zenoh.sample.Sample -import io.zenoh.prelude.SampleKind -import io.zenoh.value.Value -import io.zenoh.keyexpr.KeyExpr -import io.zenoh.prelude.CongestionControl -import io.zenoh.prelude.Priority -import io.zenoh.prelude.QoS -import io.zenoh.protocol.ZenohID -import io.zenoh.queryable.Query +import io.zenoh.qos.CongestionControl +import io.zenoh.qos.Priority +import io.zenoh.qos.QoS import org.apache.commons.net.ntp.TimeStamp /** - * Class to represent a Zenoh Reply to a [Get] operation and to a remote [Query]. - * - * A reply can be either successful ([Success]) or an error ([Error]), both having different information. For instance, - * the successful reply will contain a [Sample] while the error reply will only contain a [Value] with the error information. - * - * Replies can either be automatically created when receiving a remote reply after performing a [Get] (in which case the - * [replierId] shows the id of the replier) or created through the builders while answering to a remote [Query] (in that - * case the replier ID is automatically added by Zenoh). - * - * Generating a reply only makes sense within the context of a [Query], therefore builders below are meant to only - * be accessible from [Query.reply]. - * - * Example: - * ```java - * session.declareQueryable(keyExpr).with { query -> - * query.reply(keyExpr) - * .success(Value("Hello")) - * .timestamp(TimeStamp(Date.from(Instant.now()))) - * .res() - * }.res() - * ... - * ``` + * Class to represent a Zenoh Reply to a remote query. * * @property replierId: unique ID identifying the replier. + * @see Success + * @see Error */ -sealed class Reply private constructor(val replierId: ZenohID?) : ZenohType { +sealed class Reply private constructor(val replierId: ZenohId?) : ZenohType { /** - * Builder to construct a [Reply]. - * - * This builder allows you to construct [Success] and [Error] replies. - * - * @property query The received [Query] to reply to. - * @property keyExpr The [KeyExpr] from the queryable, which is at least an intersection of the query's key expression. - * @constructor Create empty Builder + * A Success reply. */ - class Builder internal constructor(val query: Query, val keyExpr: KeyExpr) { - - /** - * Returns a [Success.Builder] with the provided [value]. - * - * @param value The [Value] of the reply. - */ - fun success(value: Value) = Success.Builder(query, keyExpr, value) - - /** - * Returns a [Success.Builder] with a [Value] containing the provided [message]. - * - * It is equivalent to calling `success(Value(message))`. - * - * @param message A string message for the reply. - */ - fun success(message: String) = success(Value(message)) - - /** - * Returns an [Error.Builder] with the provided [value]. - * - * @param value The [Value] of the error reply. - */ - fun error(value: Value) = Error.Builder(query, value) - - /** - * Returns an [Error.Builder] with a [Value] containing the provided [message]. - * - * It is equivalent to calling `error(Value(message))`. - * - * @param message A string message for the error reply. - */ - fun error(message: String) = error(Value(message)) - - /** - * Returns a [Delete.Builder]. - */ - fun delete() = Delete.Builder(query, keyExpr) - - } - - /** - * A successful [Reply]. - * - * @property sample The [Sample] of the reply. - * @constructor Internal constructor, since replies are only meant to be generated upon receiving a remote reply - * or by calling [Query.reply] to reply to the specified [Query]. - * - * @param replierId The replierId of the remotely generated reply. - */ - class Success internal constructor(replierId: ZenohID?, val sample: Sample) : Reply(replierId) { - - /** - * Builder for the [Success] reply. - * - * @property query The [Query] to reply to. - * @property keyExpr The [KeyExpr] of the queryable. - * @property value The [Value] with the reply information. - */ - class Builder internal constructor(val query: Query, val keyExpr: KeyExpr, val value: Value) : - Resolvable { - - private val kind = SampleKind.PUT - private var timeStamp: TimeStamp? = null - private var attachment: ByteArray? = null - private var qosBuilder = QoS.Builder() - - /** - * Sets the [TimeStamp] of the replied [Sample]. - */ - fun timestamp(timeStamp: TimeStamp) = apply { this.timeStamp = timeStamp } - - /** - * Appends an attachment to the reply. - */ - fun attachment(attachment: ByteArray) = apply { this.attachment = attachment } - - /** - * Sets the express flag. If true, the reply won't be batched in order to reduce the latency. - */ - fun express(express: Boolean) = apply { qosBuilder.express(express) } - - /** - * Sets the [Priority] of the reply. - */ - fun priority(priority: Priority) = apply { qosBuilder.priority(priority) } - - /** - * Sets the [CongestionControl] of the reply. - * - * @param congestionControl - */ - fun congestionControl(congestionControl: CongestionControl) = - apply { qosBuilder.congestionControl(congestionControl) } - - /** - * Constructs the reply sample with the provided parameters and triggers the reply to the query. - */ - @Throws(ZenohException::class) - override fun res() { - val sample = Sample(keyExpr, value, kind, timeStamp, qosBuilder.build(), attachment) - return query.reply(Success(null, sample)).res() - } - } + class Success internal constructor(replierId: ZenohId?, val sample: Sample) : Reply(replierId) { override fun toString(): String { return "Success(sample=$sample)" @@ -177,40 +45,20 @@ sealed class Reply private constructor(val replierId: ZenohID?) : ZenohType { override fun equals(other: Any?): Boolean { if (this === other) return true if (other !is Success) return false - return sample == other.sample } override fun hashCode(): Int { return sample.hashCode() } + } /** * An Error reply. - * - * @property error: value with the error information. - * @constructor The constructor is private since reply instances are created through JNI when receiving a reply to a query. - * - * @param replierId: unique ID identifying the replier. */ - class Error internal constructor(replierId: ZenohID?, val error: Value) : Reply(replierId) { - - /** - * Builder for the [Error] reply. - * - * @property query The [Query] to reply to. - * @property value The [Value] with the reply information. - */ - class Builder internal constructor(val query: Query, val value: Value) : Resolvable { - - /** - * Triggers the error reply. - */ - override fun res() { - return query.reply(Error(null, value)).res() - } - } + class Error internal constructor(replierId: ZenohId?, val error: ZBytes, val encoding: Encoding) : + Reply(replierId) { override fun toString(): String { return "Error(error=$error)" @@ -227,64 +75,117 @@ sealed class Reply private constructor(val replierId: ZenohID?) : ZenohType { return error.hashCode() } } +} + +/** + * Options for performing a [Reply] to a [Query]. + * + * @param encoding [Encoding] of the payload of the reply. + * @param timeStamp Optional timestamp. + * @param attachment Optional attachment. + * @param qos The [QoS] for the reply. + */ +data class ReplyOptions( + var encoding: Encoding = Encoding.defaultEncoding(), + var timeStamp: TimeStamp? = null, + var attachment: ZBytes? = null, + var qos: QoS = QoS.defaultQoS() +) { /** - * A Delete reply. - * - * @property keyExpr - * @constructor - * - * @param replierId + * Get the QoS express value. */ - class Delete internal constructor( - replierId: ZenohID?, - val keyExpr: KeyExpr, - val timestamp: TimeStamp?, - val attachment: ByteArray?, - val qos: QoS - ) : Reply(replierId) { + fun getExpress(): Boolean { + return qos.express + } - class Builder internal constructor(val query: Query, val keyExpr: KeyExpr) : Resolvable { + /** + * Get the QoS Priority value. + */ + fun getPriority(): Priority { + return qos.priority + } - private val kind = SampleKind.DELETE - private var timeStamp: TimeStamp? = null - private var attachment: ByteArray? = null - private var qosBuilder = QoS.Builder() + /** + * Get the congestion control value. + */ + fun getCongestionControl(): CongestionControl { + return qos.congestionControl + } - /** - * Sets the [TimeStamp] of the replied [Sample]. - */ - fun timestamp(timeStamp: TimeStamp) = apply { this.timeStamp = timeStamp } + /** + * Sets the express flag. If true, the reply won't be batched in order to reduce the latency. + */ + fun setExpress(express: Boolean) { qos.express = express } - /** - * Appends an attachment to the reply. - */ - fun attachment(attachment: ByteArray) = apply { this.attachment = attachment } + /** + * Sets the [Priority] of the reply. + */ + fun setPriority(priority: Priority) { qos.priority = priority } - /** - * Sets the express flag. If true, the reply won't be batched in order to reduce the latency. - */ - fun express(express: Boolean) = apply { qosBuilder.express(express) } + /** + * Sets the [CongestionControl] of the reply. + * + * @param congestionControl + */ + fun setCongestionControl(congestionControl: CongestionControl) { qos.congestionControl = congestionControl } +} - /** - * Sets the [Priority] of the reply. - */ - fun priority(priority: Priority) = apply { qosBuilder.priority(priority) } +/** + * Options for performing a Reply Delete to a [Query]. + * + * @param timeStamp Optional timestamp. + * @param attachment Optional attachment. + * @param qos The [QoS] for the reply. + */ +data class ReplyDelOptions( + var timeStamp: TimeStamp? = null, + var attachment: ZBytes? = null, + var qos: QoS = QoS.defaultQoS() +) { + /** + * Get the QoS express value. + */ + fun getExpress(): Boolean { + return qos.express + } - /** - * Sets the [CongestionControl] of the reply. - * - * @param congestionControl - */ - fun congestionControl(congestionControl: CongestionControl) = - apply { qosBuilder.congestionControl(congestionControl) } + /** + * Get the QoS Priority value. + */ + fun getPriority(): Priority { + return qos.priority + } - /** - * Triggers the delete reply. - */ - override fun res() { - return query.reply(Delete(null, keyExpr, timeStamp, attachment, qosBuilder.build())).res() - } - } + /** + * Get the congestion control value. + */ + fun getCongestionControl(): CongestionControl { + return qos.congestionControl } -} \ No newline at end of file + + /** + * Sets the express flag. If true, the reply won't be batched in order to reduce the latency. + */ + fun setExpress(express: Boolean) { qos.express = express } + + /** + * Sets the [Priority] of the reply. + */ + fun setPriority(priority: Priority) { qos.priority = priority } + + /** + * Sets the [CongestionControl] of the reply. + * + * @param congestionControl + */ + fun setCongestionControl(congestionControl: CongestionControl) { qos.congestionControl = congestionControl } +} + + +/** + * Options for performing a Reply Err to a [Query]. + * + * @param encoding The encoding of the error message. + */ +data class ReplyErrOptions(var encoding: Encoding = Encoding.defaultEncoding()) diff --git a/zenoh-java/src/commonMain/kotlin/io/zenoh/query/Selector.kt b/zenoh-java/src/commonMain/kotlin/io/zenoh/query/Selector.kt new file mode 100644 index 00000000..34e5cd4f --- /dev/null +++ b/zenoh-java/src/commonMain/kotlin/io/zenoh/query/Selector.kt @@ -0,0 +1,103 @@ +// +// 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.query + +import io.zenoh.exceptions.ZError +import io.zenoh.keyexpr.KeyExpr + +/** + * A selector is the combination of a [KeyExpr], which defines the + * set of keys that are relevant to an operation, and a set of parameters + * with a few intended uses: + * - specifying arguments to a queryable, allowing the passing of Remote Procedure Call parameters + * - filtering by value, + * - filtering by metadata, such as the timestamp of a value, + * - specifying arguments to zenoh when using the REST API. + * + * When in string form, selectors look a lot like a URI, with similar semantics: + * - the `key_expr` before the first `?` must be a valid key expression. + * - the `parameters` after the first `?` should be encoded like the query section of a URL: + * - parameters are separated by `;`, + * - the parameter name and value are separated by the first `=`, + * - in the absence of `=`, the parameter value is considered to be the empty string, + * - both name and value should use percent-encoding to escape characters, + * - defining a value for the same parameter name twice is considered undefined behavior, + * with the encouraged behaviour being to reject operations when a duplicate parameter is detected. + * + * Zenoh intends to standardize the usage of a set of parameter names. To avoid conflicting with RPC parameters, + * the Zenoh team has settled on reserving the set of parameter names that start with non-alphanumeric characters. + * + * The full specification for selectors is available [here](https://github.com/eclipse-zenoh/roadmap/tree/main/rfcs/ALL/Selectors), + * it includes standardized parameters. + * + * Queryable implementers are encouraged to prefer these standardized parameter names when implementing their + * associated features, and to prefix their own parameter names to avoid having conflicting parameter names with other + * queryables. + * + * @property keyExpr The [KeyExpr] of the selector. + * @property parameters The [Parameters] of the selector. + */ +data class Selector(val keyExpr: KeyExpr, val parameters: Parameters? = null) : AutoCloseable, IntoSelector { + + companion object { + + /** + * Try from. + * + * The default way to construct a Selector. + * + * When in string form, selectors look a lot like a URI, with similar semantics: + * - the `key_expr` before the first `?` must be a valid key expression. + * - the `parameters` after the first `?` should be encoded like the query section of a URL: + * - parameters are separated by `;`, + * - the parameter name and value are separated by the first `=`, + * - in the absence of `=`, the parameter value is considered to be the empty string, + * - both name and value should use percent-encoding to escape characters, + * - defining a value for the same parameter name twice is considered undefined behavior, + * with the encouraged behaviour being to reject operations when a duplicate parameter is detected. + * + * @param selector The selector expression as a String. + * @return An instance [Selector]. + * @throws ZError in case of failure processing the selector. + */ + @Throws(ZError::class) + @JvmStatic + fun tryFrom(selector: String): Selector { + if (selector.isEmpty()) { + throw ZError("Attempting to create a selector from an empty string.") + } + val result = selector.split('?', limit = 2) + val keyExpr = KeyExpr.autocanonize(result[0]) + val params = if (result.size == 2) Parameters.from(result[1]) else null + + return Selector(keyExpr, params) + } + } + + override fun into(): Selector = this + + override fun toString(): String { + return parameters?.let { "$keyExpr?$parameters" } ?: keyExpr.toString() + } + + /** Closes the selector's [KeyExpr]. */ + override fun close() { + keyExpr.close() + } +} + +interface IntoSelector { + fun into(): Selector +} diff --git a/zenoh-java/src/commonMain/kotlin/io/zenoh/queryable/Query.kt b/zenoh-java/src/commonMain/kotlin/io/zenoh/queryable/Query.kt deleted file mode 100644 index cd4b7c80..00000000 --- a/zenoh-java/src/commonMain/kotlin/io/zenoh/queryable/Query.kt +++ /dev/null @@ -1,99 +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.queryable - -import io.zenoh.Resolvable -import io.zenoh.ZenohType -import io.zenoh.selector.Selector -import io.zenoh.value.Value -import io.zenoh.exceptions.SessionException -import io.zenoh.jni.JNIQuery -import io.zenoh.keyexpr.KeyExpr -import io.zenoh.query.Reply - -/** - * Represents a Zenoh Query in Kotlin. - * - * A Query is generated within the context of a [Queryable], when receiving a [Query] request. - * - * @property keyExpr The key expression to which the query is associated. - * @property selector The selector - * @property value Optional value in case the received query was declared using "with query". - * @property attachment Optional attachment. - * @property jniQuery Delegate object in charge of communicating with the underlying native code. - * @constructor Instances of Query objects are only meant to be created through the JNI upon receiving - * a query request. Therefore, the constructor is private. - */ -class Query internal constructor( - val keyExpr: KeyExpr, - val selector: Selector, - val value: Value?, - val attachment: ByteArray?, - private var jniQuery: JNIQuery? -) : AutoCloseable, ZenohType { - - /** Shortcut to the [selector]'s parameters. */ - val parameters = selector.parameters - - /** - * Reply to the specified key expression. - * - * @param keyExpr Key expression to reply to. This parameter must not be necessarily the same - * as the key expression from the Query, however it must intersect with the query key. - * @return a [Reply.Builder] - */ - fun reply(keyExpr: KeyExpr) = Reply.Builder(this, keyExpr) - - override fun close() { - jniQuery?.apply { - this.close() - jniQuery = null - } - } - - @Suppress("removal") - protected fun finalize() { - close() - } - - /** - * Perform a reply operation to the remote [Query]. - * - * A query can not be replied more than once. After the reply is performed, the query is considered - * to be no more valid and further attempts to reply to it will fail. - * - * @param reply The [Reply] to the Query. - * @return A [Resolvable] that returns a [Result] with the status of the reply operation. - */ - internal fun reply(reply: Reply): Resolvable = Resolvable { - jniQuery?.apply { - val result = when (reply) { - is Reply.Success -> { - replySuccess(reply.sample) - } - is Reply.Error -> { - replyError(reply.error) - } - is Reply.Delete -> { - replyDelete(reply.keyExpr, reply.timestamp, reply.attachment, reply.qos) - } - } - jniQuery = null - return@Resolvable result - } - throw(SessionException("Query is invalid")) - } -} - diff --git a/zenoh-java/src/commonMain/kotlin/io/zenoh/queryable/Queryable.kt b/zenoh-java/src/commonMain/kotlin/io/zenoh/queryable/Queryable.kt deleted file mode 100644 index 1c13f44d..00000000 --- a/zenoh-java/src/commonMain/kotlin/io/zenoh/queryable/Queryable.kt +++ /dev/null @@ -1,176 +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.queryable - -import io.zenoh.* -import io.zenoh.exceptions.ZenohException -import io.zenoh.handlers.Callback -import io.zenoh.handlers.BlockingQueueHandler -import io.zenoh.handlers.Handler -import io.zenoh.jni.JNIQueryable -import io.zenoh.keyexpr.KeyExpr -import java.util.* -import java.util.concurrent.BlockingQueue -import java.util.concurrent.LinkedBlockingDeque - -/** - * A queryable that allows to perform multiple queries on the specified [KeyExpr]. - * - * Its main purpose is to keep the queryable active as long as it exists. - * - * Example using the default [BlockingQueueHandler] handler: - * ```java - * try (Session session = Session.open()) { - * try (KeyExpr keyExpr = KeyExpr.tryFrom("demo/example/zenoh-java-queryable")) { - * System.out.println("Declaring Queryable"); - * try (Queryable>> queryable = session.declareQueryable(keyExpr).res()) { - * BlockingQueue> receiver = queryable.getReceiver(); - * while (true) { - * Optional wrapper = receiver.take(); - * if (wrapper.isEmpty()) { - * break; - * } - * Query query = wrapper.get(); - * String valueInfo = query.getValue() != null ? " with value '" + query.getValue() + "'" : ""; - * System.out.println(">> [Queryable] Received Query '" + query.getSelector() + "'" + valueInfo); - * try { - * query.reply(keyExpr) - * .success("Queryable from Java!") - * .withKind(SampleKind.PUT) - * .withTimeStamp(TimeStamp.getCurrentTime()) - * .res(); - * } catch (Exception e) { - * System.out.println(">> [Queryable] Error sending reply: " + e); - * } - * } - * } - * } - * } - * ``` - * - * @param R Receiver type of the [Handler] implementation. If no handler is provided to the builder, [R] will be [Unit]. - * @property keyExpr The [KeyExpr] to which the subscriber is associated. - * @property receiver Optional [R] that is provided when specifying a [Handler] for the subscriber. - * @property jniQueryable Delegate object in charge of communicating with the underlying native code. - * @constructor Internal constructor. Instances of Queryable must be created through the [Builder] obtained after - * calling [Session.declareQueryable] or alternatively through [newBuilder]. - */ -class Queryable internal constructor( - val keyExpr: KeyExpr, val receiver: R?, private var jniQueryable: JNIQueryable? -) : AutoCloseable, SessionDeclaration { - - override fun isValid(): Boolean { - return jniQueryable != null - } - - override fun undeclare() { - jniQueryable?.close() - jniQueryable = null - } - - override fun close() { - undeclare() - } - - protected fun finalize() { - jniQueryable?.close() - } - - companion object { - - /** - * Creates a new [Builder] associated to the specified [session] and [keyExpr]. - * - * @param session The [Session] from which the queryable will be declared. - * @param keyExpr The [KeyExpr] associated to the queryable. - * @return An empty [Builder] with a default [BlockingQueueHandler] to handle the incoming samples. - */ - fun newBuilder(session: Session, keyExpr: KeyExpr): Builder>> { - return Builder(session, keyExpr, handler = BlockingQueueHandler(queue = LinkedBlockingDeque())) - } - } - - /** - * Builder to construct a [Queryable]. - * - * Either a [Handler] or a [Callback] must be specified. Note neither of them are stackable and are mutually exclusive, - * meaning that it is not possible to specify multiple callbacks and/or handlers, the builder only considers the - * last one specified. - * - * @param R Receiver type of the [Handler] implementation. If no handler is provided to the builder, R will be [Unit]. - * @property session [Session] to which the [Queryable] will be bound to. - * @property keyExpr The [KeyExpr] to which the queryable is associated. - * @property callback Optional callback that will be triggered upon receiving a [Query]. - * @property handler Optional handler to receive the incoming queries. - * @constructor Creates a Builder. This constructor is internal and should not be called directly. Instead, this - * builder should be obtained through the [Session] after calling [Session.declareQueryable]. - */ - class Builder internal constructor( - private val session: Session, - private val keyExpr: KeyExpr, - private var callback: Callback? = null, - private var handler: Handler? = null - ): Resolvable> { - private var complete: Boolean = false - private var onClose: (() -> Unit)? = null - - private constructor(other: Builder<*>, handler: Handler?) : this(other.session, other.keyExpr) { - this.handler = handler - this.complete = other.complete - this.onClose = other.onClose - } - - private constructor(other: Builder<*>, callback: Callback?) : this(other.session, other.keyExpr) { - this.callback = callback - this.complete = other.complete - this.onClose = other.onClose - } - - /** Change queryable completeness. */ - fun complete(complete: Boolean) = apply { this.complete = complete } - - /** Specify an action to be invoked when the [Queryable] is undeclared. */ - fun onClose(action: () -> Unit): Builder { - this.onClose = action - return this - } - - /** Specify a [Callback]. Overrides any previously specified callback or handler. */ - fun with(callback: Callback): Builder = Builder(this, callback) - - /** Specify a [Handler]. Overrides any previously specified callback or handler. */ - fun with(handler: Handler): Builder = Builder(this, handler) - - /** Specify a [BlockingQueue]. Overrides any previously specified callback or handler. */ - fun with(blockingQueue: BlockingQueue>): Builder>> = Builder(this, BlockingQueueHandler(blockingQueue)) - - /** - * Resolve the builder, creating a [Queryable] with the provided parameters. - * - * @return The newly created [Queryable]. - */ - @Throws(ZenohException::class) - override fun res(): Queryable { - require(callback != null || handler != null) { "Either a callback or a handler must be provided." } - val resolvedCallback = callback ?: Callback { t: Query -> handler?.handle(t) } - val resolvedOnClose = fun() { - handler?.onClose() - onClose?.invoke() - } - return session.run { resolveQueryable(keyExpr, resolvedCallback, resolvedOnClose, handler?.receiver(), complete) } - } - } -} - diff --git a/zenoh-java/src/commonMain/kotlin/io/zenoh/sample/Sample.kt b/zenoh-java/src/commonMain/kotlin/io/zenoh/sample/Sample.kt index e9420678..adb99cd8 100644 --- a/zenoh-java/src/commonMain/kotlin/io/zenoh/sample/Sample.kt +++ b/zenoh-java/src/commonMain/kotlin/io/zenoh/sample/Sample.kt @@ -15,54 +15,37 @@ package io.zenoh.sample import io.zenoh.ZenohType -import io.zenoh.prelude.SampleKind -import io.zenoh.prelude.QoS +import io.zenoh.qos.QoS import io.zenoh.keyexpr.KeyExpr -import io.zenoh.value.Value +import io.zenoh.bytes.Encoding +import io.zenoh.bytes.ZBytes import org.apache.commons.net.ntp.TimeStamp /** * Class representing a Zenoh Sample. * - * A sample consists of a [KeyExpr]-[Value] pair, annotated with the [SampleKind] (PUT or DELETE) of the publication - * used to emit it and a timestamp. - * * @property keyExpr The [KeyExpr] of the sample. - * @property value The [Value] of the sample. + * @property payload [ZBytes] with the payload of the sample. + * @property encoding [Encoding] of the payload. * @property kind The [SampleKind] of the sample. * @property timestamp Optional [TimeStamp]. * @property qos The Quality of Service settings used to deliver the sample. * @property attachment Optional attachment. + * @property express The express value in the [qos]. + * @property congestionControl The congestion control in the [qos]. + * @property priority The priority in the [qos]. */ -class Sample( +data class Sample( val keyExpr: KeyExpr, - val value: Value, + val payload: ZBytes, + val encoding: Encoding, val kind: SampleKind, val timestamp: TimeStamp?, val qos: QoS, - val attachment: ByteArray? = null + val attachment: ZBytes? = null, ): ZenohType { - override fun toString(): String { - return if (kind == SampleKind.DELETE) "$kind($keyExpr)" else "$kind($keyExpr: $value)" - } - - override fun equals(other: Any?): Boolean { - if (this === other) return true - if (javaClass != other?.javaClass) return false - - other as Sample - - if (keyExpr != other.keyExpr) return false - if (value != other.value) return false - if (kind != other.kind) return false - return timestamp == other.timestamp - } - override fun hashCode(): Int { - var result = keyExpr.hashCode() - result = 31 * result + value.hashCode() - result = 31 * result + kind.hashCode() - result = 31 * result + (timestamp?.hashCode() ?: 0) - return result - } + val express = qos.express + val congestionControl = qos.congestionControl + val priority = qos.priority } diff --git a/zenoh-java/src/commonMain/kotlin/io/zenoh/prelude/SampleKind.kt b/zenoh-java/src/commonMain/kotlin/io/zenoh/sample/SampleKind.kt similarity index 96% rename from zenoh-java/src/commonMain/kotlin/io/zenoh/prelude/SampleKind.kt rename to zenoh-java/src/commonMain/kotlin/io/zenoh/sample/SampleKind.kt index 6e057c50..cea02a02 100644 --- a/zenoh-java/src/commonMain/kotlin/io/zenoh/prelude/SampleKind.kt +++ b/zenoh-java/src/commonMain/kotlin/io/zenoh/sample/SampleKind.kt @@ -12,7 +12,7 @@ // ZettaScale Zenoh Team, // -package io.zenoh.prelude +package io.zenoh.sample /** The kind of sample. */ enum class SampleKind { diff --git a/zenoh-java/src/commonMain/kotlin/io/zenoh/scouting/Hello.kt b/zenoh-java/src/commonMain/kotlin/io/zenoh/scouting/Hello.kt new file mode 100644 index 00000000..ec9ccb82 --- /dev/null +++ b/zenoh-java/src/commonMain/kotlin/io/zenoh/scouting/Hello.kt @@ -0,0 +1,29 @@ +// +// 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.scouting + +import io.zenoh.ZenohType +import io.zenoh.config.WhatAmI +import io.zenoh.config.ZenohId + +/** + * Hello message received while scouting. + * + * @property whatAmI [WhatAmI] configuration: it indicates the role of the zenoh node sending the HELLO message. + * @property zid [ZenohId] of the node sending the hello message. + * @property locators The locators of this hello message. + * @see Scout + */ +data class Hello(val whatAmI: WhatAmI, val zid: ZenohId, val locators: List): ZenohType diff --git a/zenoh-java/src/commonMain/kotlin/io/zenoh/scouting/Scout.kt b/zenoh-java/src/commonMain/kotlin/io/zenoh/scouting/Scout.kt new file mode 100644 index 00000000..8034a851 --- /dev/null +++ b/zenoh-java/src/commonMain/kotlin/io/zenoh/scouting/Scout.kt @@ -0,0 +1,109 @@ +// +// 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.scouting + +import io.zenoh.jni.JNIScout + +/** + * Scout for routers and/or peers. + * + * Scout spawns a task that periodically sends scout messages and waits for Hello replies. + * Drop the returned Scout to stop the scouting task. + * + * To launch a scout, use [io.zenoh.Zenoh.scout]: + * + * Example using the default blocking queue handler: + * ```java + * + * var scoutOptions = new ScoutOptions(); + * scoutOptions.setWhatAmI(Set.of(WhatAmI.Peer, WhatAmI.Router)); + * + * var scout = Zenoh.scout(scoutOptions); + * BlockingQueue> receiver = scout.getReceiver(); + * + * try { + * while (true) { + * Optional wrapper = receiver.take(); + * if (wrapper.isEmpty()) { + * break; + * } + * + * Hello hello = wrapper.get(); + * System.out.println(hello); + * } + * } finally { + * scout.stop(); + * } + * ``` + * + * Example using a callback: + * ```java + * var scoutOptions = new ScoutOptions(); + * scoutOptions.setWhatAmI(Set.of(WhatAmI.Peer, WhatAmI.Router)); + * Zenoh.scout(hello -> { + * //... + * System.out.println(hello); + * }, scoutOptions); + * ``` + * + * @see CallbackScout + * @see HandlerScout + */ +sealed class Scout ( + private var jniScout: JNIScout? +) : AutoCloseable { + + /** + * Stops the scouting. + */ + fun stop() { + jniScout?.close() + jniScout = null + } + + /** + * Equivalent to [stop]. + */ + override fun close() { + stop() + } + + protected fun finalize() { + stop() + } +} + +/** + * Scout using a callback to handle incoming [Hello] messages. + * + * Example: + * ```java + * CallbackScout scout = Zenoh.scout(hello -> {...}); + * ``` + */ +class CallbackScout internal constructor(jniScout: JNIScout?) : Scout(jniScout) + +/** + * Scout using a handler to handle incoming [Hello] messages. + * + * Example + * ```java + * HandlerScout>> scout = Zenoh.scout(); + * ``` + * + * @param R The type of the receiver. + * @param receiver The receiver of the scout's handler. + */ +class HandlerScout internal constructor(jniScout: JNIScout?, val receiver: R) : Scout(jniScout) diff --git a/zenoh-java/src/commonMain/kotlin/io/zenoh/exceptions/JNIException.kt b/zenoh-java/src/commonMain/kotlin/io/zenoh/scouting/ScoutOptions.kt similarity index 60% rename from zenoh-java/src/commonMain/kotlin/io/zenoh/exceptions/JNIException.kt rename to zenoh-java/src/commonMain/kotlin/io/zenoh/scouting/ScoutOptions.kt index dd06cda7..3f3f9f13 100644 --- a/zenoh-java/src/commonMain/kotlin/io/zenoh/exceptions/JNIException.kt +++ b/zenoh-java/src/commonMain/kotlin/io/zenoh/scouting/ScoutOptions.kt @@ -12,12 +12,18 @@ // ZettaScale Zenoh Team, // -package io.zenoh.exceptions +package io.zenoh.scouting + +import io.zenoh.Config +import io.zenoh.config.WhatAmI /** - * JNI (Java native interface) exception. + * Options for scouting. * - * This type of exception is thrown from the native code when something goes wrong regarding the - * communication between the Java/Kotlin layer and the native layer through the JNI. + * @param config A [Config] for scouting. + * @param whatAmI [WhatAmI] parameters. */ -class JNIException(msg: String?) : ZenohException(msg) +data class ScoutOptions( + var config: Config? = null, + var whatAmI: Set = setOf(WhatAmI.Peer, WhatAmI.Router) +) diff --git a/zenoh-java/src/commonMain/kotlin/io/zenoh/selector/IntoSelector.kt b/zenoh-java/src/commonMain/kotlin/io/zenoh/selector/IntoSelector.kt deleted file mode 100644 index fe47ac68..00000000 --- a/zenoh-java/src/commonMain/kotlin/io/zenoh/selector/IntoSelector.kt +++ /dev/null @@ -1,30 +0,0 @@ -// -// Copyright (c) 2023 ZettaScale Technology -// -// This program and the accompanying materials are made available under the -// terms of the Eclipse Public License 2.0 which is available at -// http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 -// which is available at https://www.apache.org/licenses/LICENSE-2.0. -// -// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 -// -// Contributors: -// ZettaScale Zenoh Team, -// - -package io.zenoh.selector - -import io.zenoh.exceptions.KeyExprException -import io.zenoh.keyexpr.KeyExpr - -@Throws(KeyExprException::class) -fun String.intoSelector(): Selector { - if (this.isEmpty()) { - throw(KeyExprException("Attempting to create a KeyExpr from an empty string.")) - } - val result = this.split('?', limit = 2) - val keyExpr = KeyExpr.autocanonize(result[0]) - val params = if (result.size == 2) result[1] else "" - return Selector(keyExpr, params) -} - diff --git a/zenoh-java/src/commonMain/kotlin/io/zenoh/selector/Selector.kt b/zenoh-java/src/commonMain/kotlin/io/zenoh/selector/Selector.kt deleted file mode 100644 index 133bcc04..00000000 --- a/zenoh-java/src/commonMain/kotlin/io/zenoh/selector/Selector.kt +++ /dev/null @@ -1,65 +0,0 @@ -// -// Copyright (c) 2023 ZettaScale Technology -// -// This program and the accompanying materials are made available under the -// terms of the Eclipse Public License 2.0 which is available at -// http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 -// which is available at https://www.apache.org/licenses/LICENSE-2.0. -// -// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 -// -// Contributors: -// ZettaScale Zenoh Team, -// - -package io.zenoh.selector - -import io.zenoh.exceptions.KeyExprException -import io.zenoh.keyexpr.KeyExpr - -/** - * A selector is the combination of a [KeyExpr], which defines the - * set of keys that are relevant to an operation, and a [parameters], a set of key-value pairs with a few uses: - * - * * specifying arguments to a queryable, allowing the passing of Remote Procedure Call parameters - * * filtering by value, - * * filtering by metadata, such as the timestamp of a value - * - * @property keyExpr The [KeyExpr] of the selector. - * @property parameters The parameters of the selector. - */ -class Selector(val keyExpr: KeyExpr, val parameters: String = ""): AutoCloseable { - - companion object { - - /** - * Try from. - * - * Equivalent constructor to [String.intoSelector], generates a selector from a string. - * - * @param expression A string with the form "?". - * @return A [Selector] in case of success. - * @throws KeyExprException in case of failure generating the key expression. - */ - @JvmStatic - @Throws(KeyExprException::class) - fun tryFrom(expression: String): Selector { - if (expression.isEmpty()) { - throw(KeyExprException("Attempting to create a KeyExpr from an empty string.")) - } - val result = expression.split('?', limit = 2) - val keyExpr = KeyExpr.autocanonize(result[0]) - val params = if (result.size == 2) result[1] else "" - return Selector(keyExpr, params) - } - } - - override fun toString(): String { - return if (parameters.isEmpty()) "$keyExpr" else "$keyExpr?$parameters" - } - - /** Closes the selector's [KeyExpr]. */ - override fun close() { - keyExpr.close() - } -} diff --git a/zenoh-java/src/commonMain/kotlin/io/zenoh/SessionDeclaration.kt b/zenoh-java/src/commonMain/kotlin/io/zenoh/session/SessionDeclaration.kt similarity index 69% rename from zenoh-java/src/commonMain/kotlin/io/zenoh/SessionDeclaration.kt rename to zenoh-java/src/commonMain/kotlin/io/zenoh/session/SessionDeclaration.kt index 1c747e40..2c3a9224 100644 --- a/zenoh-java/src/commonMain/kotlin/io/zenoh/SessionDeclaration.kt +++ b/zenoh-java/src/commonMain/kotlin/io/zenoh/session/SessionDeclaration.kt @@ -12,19 +12,16 @@ // ZettaScale Zenoh Team, // -package io.zenoh +package io.zenoh.session /** * Session declaration. * - * A session declaration is either a [io.zenoh.publication.Publisher], - * a [io.zenoh.subscriber.Subscriber] or a [io.zenoh.queryable.Queryable] declared from a [Session]. + * A session declaration is either a [io.zenoh.pubsub.Publisher], + * a [io.zenoh.pubsub.Subscriber] or a [io.zenoh.query.Queryable] declared from a [io.zenoh.Session]. */ interface SessionDeclaration { - /** Returns true if the declaration has not been undeclared. */ - fun isValid(): Boolean - /** Undeclare a declaration. No further operations should be performed after calling this function. */ fun undeclare() } diff --git a/zenoh-java/src/commonMain/kotlin/io/zenoh/session/SessionInfo.kt b/zenoh-java/src/commonMain/kotlin/io/zenoh/session/SessionInfo.kt new file mode 100644 index 00000000..9215daa4 --- /dev/null +++ b/zenoh-java/src/commonMain/kotlin/io/zenoh/session/SessionInfo.kt @@ -0,0 +1,49 @@ +// +// 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.session + +import io.zenoh.Session +import io.zenoh.config.ZenohId +import io.zenoh.exceptions.ZError + +/** + * Class allowing to obtain the information of a [Session]. + */ +class SessionInfo(private val session: Session) { + + /** + * Return the [ZenohId] of the current Zenoh [Session] + */ + @Throws(ZError::class) + fun zid(): ZenohId { + return session.zid() + } + + /** + * Return the [ZenohId] of the zenoh peers the session is currently connected to. + */ + @Throws(ZError::class) + fun peersZid(): List { + return session.getPeersId() + } + + /** + * Return the [ZenohId] of the zenoh routers the session is currently connected to. + */ + @Throws(ZError::class) + fun routersZid(): List { + return session.getRoutersId() + } +} diff --git a/zenoh-java/src/commonMain/kotlin/io/zenoh/subscriber/Subscriber.kt b/zenoh-java/src/commonMain/kotlin/io/zenoh/subscriber/Subscriber.kt deleted file mode 100644 index 53b41f3a..00000000 --- a/zenoh-java/src/commonMain/kotlin/io/zenoh/subscriber/Subscriber.kt +++ /dev/null @@ -1,184 +0,0 @@ -// -// Copyright (c) 2023 ZettaScale Technology -// -// This program and the accompanying materials are made available under the -// terms of the Eclipse Public License 2.0 which is available at -// http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 -// which is available at https://www.apache.org/licenses/LICENSE-2.0. -// -// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 -// -// Contributors: -// ZettaScale Zenoh Team, -// - -package io.zenoh.subscriber - -import io.zenoh.* -import io.zenoh.exceptions.ZenohException -import io.zenoh.handlers.Callback -import io.zenoh.handlers.BlockingQueueHandler -import io.zenoh.handlers.Handler -import io.zenoh.subscriber.Subscriber.Builder -import io.zenoh.jni.JNISubscriber -import io.zenoh.keyexpr.KeyExpr -import io.zenoh.sample.Sample -import java.util.* -import java.util.concurrent.BlockingQueue -import java.util.concurrent.LinkedBlockingDeque - -/** - * A subscriber that allows listening to updates on a key expression and reacting to changes. - * - * Its main purpose is to keep the subscription active as long as it exists. - * - * Example using the default [BlockingQueueHandler] handler: - * - * ```java - * System.out.println("Opening session..."); - * try (Session session = Session.open()) { - * try (KeyExpr keyExpr = KeyExpr.tryFrom("demo/example")) { - * System.out.println("Declaring Subscriber on '" + keyExpr + "'..."); - * try (Subscriber>> subscriber = session.declareSubscriber(keyExpr).res()) { - * BlockingQueue> receiver = subscriber.getReceiver(); - * assert receiver != null; - * while (true) { - * Optional wrapper = receiver.take(); - * if (wrapper.isEmpty()) { - * break; - * } - * Sample sample = wrapper.get(); - * System.out.println(">> [Subscriber] Received " + sample.getKind() + " ('" + sample.getKeyExpr() + "': '" + sample.getValue() + "')"); - * } - * } - * } - * } - * ``` - * - * @param R Receiver type of the [Handler] implementation. If no handler is provided to the builder, R will be [Unit]. - * @property keyExpr The [KeyExpr] to which the subscriber is associated. - * @property receiver Optional [R] that is provided when specifying a [Handler] for the subscriber. - * @property jniSubscriber Delegate object in charge of communicating with the underlying native code. - * @constructor Internal constructor. Instances of Subscriber must be created through the [Builder] obtained after - * calling [Session.declareSubscriber] or alternatively through [newBuilder]. - */ -class Subscriber internal constructor( - val keyExpr: KeyExpr, val receiver: R?, private var jniSubscriber: JNISubscriber? -) : AutoCloseable, SessionDeclaration { - - override fun isValid(): Boolean { - return jniSubscriber != null - } - - override fun undeclare() { - jniSubscriber?.close() - jniSubscriber = null - } - - override fun close() { - undeclare() - } - - protected fun finalize() { - jniSubscriber?.close() - } - - companion object { - - /** - * Creates a new [Builder] associated to the specified [session] and [keyExpr]. - * - * @param session The [Session] from which the subscriber will be declared. - * @param keyExpr The [KeyExpr] associated to the subscriber. - * @return An empty [Builder] with a default [BlockingQueueHandler] to handle the incoming samples. - */ - fun newBuilder(session: Session, keyExpr: KeyExpr): Builder>> { - return Builder(session, keyExpr, handler = BlockingQueueHandler(queue = LinkedBlockingDeque())) - } - } - - /** - * Builder to construct a [Subscriber]. - * - * Either a [Handler] or a [Callback] must be specified. Note neither of them are stackable and are mutually exclusive, - * meaning that it is not possible to specify multiple callbacks and/or handlers, the builder only considers the - * last one specified. - * - * @param R Receiver type of the [Handler] implementation. If no handler is provided to the builder, R will be [Unit]. - * @property session [Session] to which the [Subscriber] will be bound to. - * @property keyExpr The [KeyExpr] to which the subscriber is associated. - * @constructor Creates a Builder. This constructor is internal and should not be called directly. Instead, this - * builder should be obtained through the [Session] after calling [Session.declareSubscriber]. - */ - class Builder internal constructor( - private val session: Session, - private val keyExpr: KeyExpr, - private var callback: Callback? = null, - private var handler: Handler? = null - ): Resolvable> { - - private var reliability: Reliability = Reliability.BEST_EFFORT - private var onClose: (() -> Unit)? = null - - private constructor(other: Builder<*>, handler: Handler?): this(other.session, other.keyExpr) { - this.handler = handler - copyParams(other) - } - - private constructor(other: Builder<*>, callback: Callback?) : this(other.session, other.keyExpr) { - this.callback = callback - copyParams(other) - } - - private fun copyParams(other: Builder<*>) { - this.reliability = other.reliability - this.onClose = other.onClose - } - - /** Sets the [Reliability]. */ - fun reliability(reliability: Reliability): Builder = apply { - this.reliability = reliability - } - - /** Sets the reliability to [Reliability.RELIABLE]. */ - fun reliable(): Builder = apply { - this.reliability = Reliability.RELIABLE - } - - /** Sets the reliability to [Reliability.BEST_EFFORT]. */ - fun bestEffort(): Builder = apply { - this.reliability = Reliability.BEST_EFFORT - } - - /** Specify an action to be invoked when the [Subscriber] is undeclared. */ - fun onClose(action: () -> Unit): Builder { - this.onClose = action - return this - } - - /** Specify a [Callback]. Overrides any previously specified callback or handler. */ - fun with(callback: Callback): Builder = Builder(this, callback) - - /** Specify a [Handler]. Overrides any previously specified callback or handler. */ - fun with(handler: Handler): Builder = Builder(this, handler) - - /** Specify a [BlockingQueue]. Overrides any previously specified callback or handler. */ - fun with(blockingQueue: BlockingQueue>): Builder>> = Builder(this, BlockingQueueHandler(blockingQueue)) - - /** - * Resolve the builder, creating a [Subscriber] with the provided parameters. - * - * @return The newly created [Subscriber]. - */ - @Throws(ZenohException::class) - override fun res(): Subscriber { - require(callback != null || handler != null) { "Either a callback or a handler must be provided." } - val resolvedCallback = callback ?: Callback { t: Sample -> handler?.handle(t) } - val resolvedOnClose = fun() { - handler?.onClose() - onClose?.invoke() - } - return session.run { resolveSubscriber(keyExpr, resolvedCallback, resolvedOnClose, handler?.receiver(), reliability) } - } - } -} diff --git a/zenoh-java/src/commonMain/kotlin/io/zenoh/value/Value.kt b/zenoh-java/src/commonMain/kotlin/io/zenoh/value/Value.kt deleted file mode 100644 index ba787bcb..00000000 --- a/zenoh-java/src/commonMain/kotlin/io/zenoh/value/Value.kt +++ /dev/null @@ -1,66 +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.value - -import io.zenoh.prelude.Encoding - -/** - * A Zenoh value. - * - * A Value is a pair of a binary payload, and a mime-type-like encoding string. - * - * @property payload The payload of this Value. - * @property encoding An encoding description indicating how the associated payload is encoded. - */ -class Value(val payload: ByteArray, val encoding: Encoding) { - - /** - * Constructs a value with the provided message, using [Encoding.ID.TEXT_PLAIN] for encoding. - */ - constructor(message: String): this(message.toByteArray(), Encoding(Encoding.ID.TEXT_PLAIN)) - - /** - * Constructs a value with the provided message and encoding. - */ - constructor(message: String, encoding: Encoding): this(message.toByteArray(), encoding) - - companion object { - - /** Return an empty value. */ - fun empty(): Value { - return Value(ByteArray(0), Encoding(Encoding.ID.ZENOH_BYTES)) - } - } - - override fun toString(): String { - return payload.decodeToString() - } - - override fun equals(other: Any?): Boolean { - if (this === other) return true - if (javaClass != other?.javaClass) return false - - other as Value - - if (!payload.contentEquals(other.payload)) return false - return encoding == other.encoding - } - - override fun hashCode(): Int { - var result = payload.contentHashCode() - result = 31 * result + encoding.hashCode() - return result - } -} diff --git a/zenoh-java/src/commonTest/kotlin/io/zenoh/DeleteTest.kt b/zenoh-java/src/commonTest/kotlin/io/zenoh/DeleteTest.kt deleted file mode 100644 index 083e210a..00000000 --- a/zenoh-java/src/commonTest/kotlin/io/zenoh/DeleteTest.kt +++ /dev/null @@ -1,39 +0,0 @@ -// -// Copyright (c) 2023 ZettaScale Technology -// -// This program and the accompanying materials are made available under the -// terms of the Eclipse Public License 2.0 which is available at -// http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 -// which is available at https://www.apache.org/licenses/LICENSE-2.0. -// -// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 -// -// Contributors: -// ZettaScale Zenoh Team, -// - -package io.zenoh - -import io.zenoh.keyexpr.intoKeyExpr -import io.zenoh.prelude.SampleKind -import io.zenoh.sample.Sample -import kotlin.test.Test -import kotlin.test.assertEquals -import kotlin.test.assertNotNull - -class DeleteTest { - - @Test - fun delete_isProperlyReceivedBySubscriber() { - val session = Session.open() - var receivedSample: Sample? = null - val keyExpr = "example/testing/keyexpr".intoKeyExpr() - val subscriber = session.declareSubscriber(keyExpr).with { sample -> receivedSample = sample }.res() - session.delete(keyExpr).res() - subscriber.close() - keyExpr.close() - session.close() - assertNotNull(receivedSample) - assertEquals(receivedSample!!.kind, SampleKind.DELETE) - } -} diff --git a/zenoh-java/src/commonTest/kotlin/io/zenoh/EncodingTest.kt b/zenoh-java/src/commonTest/kotlin/io/zenoh/EncodingTest.kt deleted file mode 100644 index 5f344e06..00000000 --- a/zenoh-java/src/commonTest/kotlin/io/zenoh/EncodingTest.kt +++ /dev/null @@ -1,168 +0,0 @@ -package io.zenoh - -import io.zenoh.keyexpr.intoKeyExpr -import io.zenoh.prelude.Encoding -import io.zenoh.query.Reply -import io.zenoh.sample.Sample -import io.zenoh.value.Value -import kotlin.test.* - -class EncodingTest { - - @Test - fun encoding_subscriberTest() { - val session = Session.open() - val keyExpr = "example/testing/keyexpr".intoKeyExpr() - - // Testing non null schema - var receivedSample: Sample? = null - val subscriber = session.declareSubscriber(keyExpr).with { sample -> - receivedSample = sample - }.res() - var value = Value("test", Encoding(Encoding.ID.TEXT_CSV, "test_schema")) - session.put(keyExpr, value).res() - Thread.sleep(200) - - assertNotNull(receivedSample) - assertEquals(Encoding.ID.TEXT_CSV, receivedSample!!.value.encoding.id) - assertEquals("test_schema", receivedSample!!.value.encoding.schema) - - // Testing null schema - receivedSample = null - value = Value("test2", Encoding(Encoding.ID.ZENOH_STRING, null)) - session.put(keyExpr, value).res() - Thread.sleep(200) - - assertNotNull(receivedSample) - assertEquals(Encoding.ID.ZENOH_STRING, receivedSample!!.value.encoding.id) - assertNull(receivedSample!!.value.encoding.schema) - - subscriber.close() - session.close() - } - - @Test - fun encoding_replySuccessTest() { - val session = Session.open() - val keyExpr = "example/testing/**".intoKeyExpr() - val test1 = "example/testing/reply_success".intoKeyExpr() - val test2 = "example/testing/reply_success_with_schema".intoKeyExpr() - - val testValueA = Value("test", Encoding(Encoding.ID.TEXT_CSV, null)) - val testValueB = Value("test", Encoding(Encoding.ID.TEXT_CSV, "test_schema")) - - val queryable = session.declareQueryable(keyExpr).with { query -> - when (query.keyExpr) { - test1 -> query.reply(query.keyExpr).success(testValueA).res() - test2 -> query.reply(query.keyExpr).success(testValueB).res() - } - }.res() - - // Testing with null schema on a reply success scenario. - var receivedSample: Sample? = null - session.get(test1).with { reply -> - assertTrue(reply is Reply.Success) - receivedSample = reply.sample - }.res() - Thread.sleep(200) - - assertNotNull(receivedSample) - assertEquals(Encoding.ID.TEXT_CSV, receivedSample!!.value.encoding.id) - assertNull(receivedSample!!.value.encoding.schema) - - // Testing with non-null schema on a reply success scenario. - receivedSample = null - session.get(test2).with { reply -> - assertTrue(reply is Reply.Success) - receivedSample = reply.sample - }.res() - Thread.sleep(200) - - assertNotNull(receivedSample) - assertEquals(Encoding.ID.TEXT_CSV, receivedSample!!.value.encoding.id) - assertEquals("test_schema", receivedSample!!.value.encoding.schema) - - queryable.close() - session.close() - } - - @Test - fun encoding_replyErrorTest() { - val session = Session.open() - val keyExpr = "example/testing/**".intoKeyExpr() - - val test1 = "example/testing/reply_error".intoKeyExpr() - val test2 = "example/testing/reply_error_with_schema".intoKeyExpr() - - val testValueA = Value("test", Encoding(Encoding.ID.TEXT_CSV, null)) - val testValueB = Value("test", Encoding(Encoding.ID.TEXT_CSV, "test_schema")) - - val queryable = session.declareQueryable(keyExpr).with { query -> - when (query.keyExpr) { - test1 -> query.reply(query.keyExpr).error(testValueA).res() - test2 -> query.reply(query.keyExpr).error(testValueB).res() - } - }.res() - - // Testing with null schema on a reply error scenario. - var errorValue: Value? = null - session.get(test1).with { reply -> - assertTrue(reply is Reply.Error) - errorValue = reply.error - }.res() - Thread.sleep(200) - - assertNotNull(errorValue) - assertEquals(Encoding.ID.TEXT_CSV, errorValue!!.encoding.id) - assertNull(errorValue!!.encoding.schema) - - // Testing with non-null schema on a reply error scenario. - errorValue = null - session.get(test2).with { reply -> - assertTrue(reply is Reply.Error) - errorValue = reply.error - }.res() - Thread.sleep(200) - - assertNotNull(errorValue) - assertEquals(Encoding.ID.TEXT_CSV, errorValue!!.encoding.id) - assertEquals("test_schema", errorValue!!.encoding.schema) - - queryable.close() - session.close() - } - - @Test - fun encoding_queryTest() { - val session = Session.open() - val keyExpr = "example/testing/keyexpr".intoKeyExpr() - val testValueA = Value("test", Encoding(Encoding.ID.TEXT_CSV, null)) - val testValueB = Value("test", Encoding(Encoding.ID.TEXT_CSV, "test_schema")) - - var receivedValue: Value? = null - val queryable = session.declareQueryable(keyExpr).with { query -> - receivedValue = query.value - query.close() - }.res() - - // Testing with null schema - session.get(keyExpr).withValue(testValueA).res() - Thread.sleep(200) - - assertNotNull(receivedValue) - assertEquals(Encoding.ID.TEXT_CSV, receivedValue!!.encoding.id) - assertNull(receivedValue!!.encoding.schema) - - // Testing non-null schema - receivedValue = null - session.get(keyExpr).withValue(testValueB).res() - Thread.sleep(200) - - assertNotNull(receivedValue) - assertEquals(Encoding.ID.TEXT_CSV, receivedValue!!.encoding.id) - assertEquals("test_schema", receivedValue!!.encoding.schema) - - queryable.close() - session.close() - } -} diff --git a/zenoh-java/src/commonTest/kotlin/io/zenoh/GetTest.kt b/zenoh-java/src/commonTest/kotlin/io/zenoh/GetTest.kt deleted file mode 100644 index 89d645b0..00000000 --- a/zenoh-java/src/commonTest/kotlin/io/zenoh/GetTest.kt +++ /dev/null @@ -1,121 +0,0 @@ -// -// Copyright (c) 2023 ZettaScale Technology -// -// This program and the accompanying materials are made available under the -// terms of the Eclipse Public License 2.0 which is available at -// http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 -// which is available at https://www.apache.org/licenses/LICENSE-2.0. -// -// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 -// -// Contributors: -// ZettaScale Zenoh Team, -// - -package io.zenoh - -import io.zenoh.handlers.Handler -import io.zenoh.keyexpr.KeyExpr -import io.zenoh.keyexpr.intoKeyExpr -import io.zenoh.prelude.SampleKind -import io.zenoh.query.Reply -import io.zenoh.queryable.Queryable -import io.zenoh.selector.Selector -import io.zenoh.value.Value -import org.apache.commons.net.ntp.TimeStamp -import java.time.Duration -import java.util.* -import kotlin.test.* - -class GetTest { - - companion object { - val value = Value("Test") - val timestamp = TimeStamp.getCurrentTime() - val kind = SampleKind.PUT - } - - private lateinit var session: Session - private lateinit var keyExpr: KeyExpr - private lateinit var queryable: Queryable - - @BeforeTest - fun setUp() { - session = Session.open() - keyExpr = "example/testing/keyexpr".intoKeyExpr() - queryable = session.declareQueryable(keyExpr).with { query -> - query.reply(query.keyExpr) - .success(value) - .timestamp(timestamp) - .res() - }.res() - } - - @AfterTest - fun tearDown() { - keyExpr.close() - queryable.close() - session.close() - } - - @Test - fun get_runsWithCallback() { - var reply: Reply? = null - session.get(keyExpr).with { reply = it }.timeout(Duration.ofMillis(1000)).res() - - assertTrue(reply is Reply.Success) - val sample = (reply as Reply.Success).sample - assertEquals(value, sample.value) - assertEquals(kind, sample.kind) - assertEquals(keyExpr, sample.keyExpr) - assertEquals(timestamp, sample.timestamp) - } - - @Test - fun get_runsWithHandler() { - val receiver: ArrayList = session.get(keyExpr).with(TestHandler()) - .timeout(Duration.ofMillis(1000)).res()!! - - for (reply in receiver) { - reply as Reply.Success - val receivedSample = reply.sample - assertEquals(value, receivedSample.value) - assertEquals(SampleKind.PUT, receivedSample.kind) - assertEquals(timestamp, receivedSample.timestamp) - } - } - - @Test - fun getWithSelectorParamsTest() { - var receivedParams = String() - val queryable = session.declareQueryable(keyExpr).with { - it.use { query -> - receivedParams = query.parameters - } - }.res() - - val params = "arg1=val1,arg2=val2" - val selector = Selector(keyExpr, params) - session.get(selector).with {}.timeout(Duration.ofMillis(1000)).res() - Thread.sleep(1000) - - queryable.close() - assertEquals(params, receivedParams) - } -} - -/** A dummy handler for get operations. */ -private class TestHandler : Handler> { - - val performedReplies: ArrayList = ArrayList() - - override fun handle(t: Reply) { - performedReplies.add(t) - } - - override fun receiver(): ArrayList { - return performedReplies - } - - override fun onClose() {} -} diff --git a/zenoh-java/src/commonTest/kotlin/io/zenoh/KeyExprTest.kt b/zenoh-java/src/commonTest/kotlin/io/zenoh/KeyExprTest.kt deleted file mode 100644 index bb0b0ec2..00000000 --- a/zenoh-java/src/commonTest/kotlin/io/zenoh/KeyExprTest.kt +++ /dev/null @@ -1,126 +0,0 @@ -// -// Copyright (c) 2023 ZettaScale Technology -// -// This program and the accompanying materials are made available under the -// terms of the Eclipse Public License 2.0 which is available at -// http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 -// which is available at https://www.apache.org/licenses/LICENSE-2.0. -// -// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 -// -// Contributors: -// ZettaScale Zenoh Team, -// - -package io.zenoh - -import io.zenoh.exceptions.SessionException -import io.zenoh.keyexpr.KeyExpr -import io.zenoh.keyexpr.intoKeyExpr -import org.junit.Assert.assertThrows -import kotlin.test.* - -class KeyExprTest { - - init { - Zenoh.load() - } - - @Test - fun creation_TryFromTest() { - // A couple of examples of valid and invalid key expressions. - - KeyExpr.tryFrom("example/test") // Should not throw exception - - assertThrows(Exception::class.java) { KeyExpr.tryFrom("example/test?param='test'") } - - KeyExpr.tryFrom("example/*/test") // Should not throw exception - - assertThrows(Exception::class.java) { KeyExpr.tryFrom("example/!*/test") } - } - - @Test - fun equalizationTest() { - val keyExpr1 = KeyExpr.tryFrom("example/test") - val keyExpr2 = KeyExpr.tryFrom("example/test") - - assertEquals(keyExpr1, keyExpr2) - - val keyExpr3 = KeyExpr.tryFrom("different/key/expr") - assertNotEquals(keyExpr1, keyExpr3) - - // Despite being undeclared, the equals operation should still work. - keyExpr2.close() - assertEquals(keyExpr1, keyExpr2) - - keyExpr1.close() - assertEquals(keyExpr1, keyExpr2) - } - - @Test - fun creation_autocanonizeTest() { - val keyExpr1 = KeyExpr.autocanonize("example/**/test") - val keyExpr2 = KeyExpr.autocanonize("example/**/**/test") - assertEquals(keyExpr1, keyExpr2) - } - - @Test - fun intersectionTest() { - val keyExprA = KeyExpr.tryFrom("example/*/test") - - val keyExprB = KeyExpr.tryFrom("example/B/test") - assertTrue(keyExprA.intersects(keyExprB)) - - val keyExprC = KeyExpr.tryFrom("example/B/C/test") - assertFalse(keyExprA.intersects(keyExprC)) - - val keyExprA2 = KeyExpr.tryFrom("example/**") - assertTrue(keyExprA2.intersects(keyExprC)) - } - - @Test - fun includesTest() { - val keyExpr = KeyExpr.tryFrom("example/**") - val includedKeyExpr = KeyExpr.tryFrom("example/A/B/C/D") - assertTrue(keyExpr.includes(includedKeyExpr)) - - val notIncludedKeyExpr = KeyExpr.tryFrom("C/D") - assertFalse(keyExpr.includes(notIncludedKeyExpr)) - } - - @Test - fun sessionDeclarationTest() { - val session = Session.open() - val keyExpr = session.declareKeyExpr("a/b/c").res() - assertEquals("a/b/c", keyExpr.toString()) - session.close() - keyExpr.close() - } - - @Test - fun sessionUnDeclarationTest() { - val session = Session.open() - val keyExpr = session.declareKeyExpr("a/b/c").res() - assertEquals("a/b/c", keyExpr.toString()) - - session.undeclare(keyExpr).res() // Should not throw exception - - // Undeclaring a key expr that was not declared through a session. - val keyExpr2 = "x/y/z".intoKeyExpr() - assertThrows(SessionException::class.java) {session.undeclare(keyExpr2).res()} - - session.close() - keyExpr.close() - keyExpr2.close() - } - - @Test - fun keyExprIsValidAfterClosingSession() { - val session = Session.open() - val keyExpr = session.declareKeyExpr("a/b/c").res() - session.close() - - assertTrue(keyExpr.isValid()) - assertFalse(keyExpr.toString().isEmpty()) // An operation such as toString that goes through JNI is still valid. - } -} diff --git a/zenoh-java/src/commonTest/kotlin/io/zenoh/PublisherTest.kt b/zenoh-java/src/commonTest/kotlin/io/zenoh/PublisherTest.kt deleted file mode 100644 index ee5786a9..00000000 --- a/zenoh-java/src/commonTest/kotlin/io/zenoh/PublisherTest.kt +++ /dev/null @@ -1,77 +0,0 @@ -// -// Copyright (c) 2023 ZettaScale Technology -// -// This program and the accompanying materials are made available under the -// terms of the Eclipse Public License 2.0 which is available at -// http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 -// which is available at https://www.apache.org/licenses/LICENSE-2.0. -// -// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 -// -// Contributors: -// ZettaScale Zenoh Team, -// - -package io.zenoh - -import io.zenoh.keyexpr.KeyExpr -import io.zenoh.keyexpr.intoKeyExpr -import io.zenoh.prelude.Encoding -import io.zenoh.prelude.SampleKind -import io.zenoh.publication.Publisher -import io.zenoh.sample.Sample -import io.zenoh.subscriber.Subscriber -import io.zenoh.value.Value -import kotlin.test.* - -class PublisherTest { - - lateinit var session: Session - lateinit var receivedSamples: ArrayList - lateinit var publisher: Publisher - lateinit var subscriber: Subscriber - lateinit var keyExpr: KeyExpr - - @BeforeTest - fun setUp() { - session = Session.open() - keyExpr = "example/testing/keyexpr".intoKeyExpr() - publisher = session.declarePublisher(keyExpr).res() - subscriber = session.declareSubscriber(keyExpr).with { sample -> - receivedSamples.add(sample) - }.res() - receivedSamples = ArrayList() - } - - @AfterTest - fun tearDown() { - publisher.close() - subscriber.close() - session.close() - keyExpr.close() - } - - @Test - fun putTest() { - - val testValues = arrayListOf( - Value("Test 1".encodeToByteArray(), Encoding(Encoding.ID.TEXT_PLAIN)), - Value("Test 2".encodeToByteArray(), Encoding(Encoding.ID.TEXT_JSON)), - Value("Test 3".encodeToByteArray(), Encoding(Encoding.ID.TEXT_CSV)) - ) - - testValues.forEach() { value -> publisher.put(value).res() } - - assertEquals(receivedSamples.size, testValues.size) - for ((index, sample) in receivedSamples.withIndex()) { - assertEquals(sample.value, testValues[index]) - } - } - - @Test - fun deleteTest() { - publisher.delete().res() - assertEquals(1, receivedSamples.size) - assertEquals(SampleKind.DELETE, receivedSamples[0].kind) - } -} diff --git a/zenoh-java/src/commonTest/kotlin/io/zenoh/PutTest.kt b/zenoh-java/src/commonTest/kotlin/io/zenoh/PutTest.kt deleted file mode 100644 index 2c34d2e9..00000000 --- a/zenoh-java/src/commonTest/kotlin/io/zenoh/PutTest.kt +++ /dev/null @@ -1,47 +0,0 @@ -// -// Copyright (c) 2023 ZettaScale Technology -// -// This program and the accompanying materials are made available under the -// terms of the Eclipse Public License 2.0 which is available at -// http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 -// which is available at https://www.apache.org/licenses/LICENSE-2.0. -// -// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 -// -// Contributors: -// ZettaScale Zenoh Team, -// - -package io.zenoh - -import io.zenoh.keyexpr.intoKeyExpr -import io.zenoh.prelude.Encoding -import io.zenoh.sample.Sample -import io.zenoh.value.Value -import kotlin.test.Test -import kotlin.test.assertEquals -import kotlin.test.assertNotNull - -class PutTest { - - companion object { - const val TEST_KEY_EXP = "example/testing/keyexpr" - const val TEST_PAYLOAD = "Hello" - } - - @Test - fun putTest() { - val session = Session.open() - var receivedSample: Sample? = null - val keyExpr = TEST_KEY_EXP.intoKeyExpr() - val subscriber = session.declareSubscriber(keyExpr).with { sample -> receivedSample = sample }.res() - val value = Value(TEST_PAYLOAD.toByteArray(), Encoding(Encoding.ID.TEXT_PLAIN)) - session.put(keyExpr, value).res() - subscriber.close() - session.close() - keyExpr.close() - assertNotNull(receivedSample) - assertEquals(value, receivedSample!!.value) - } -} - diff --git a/zenoh-java/src/commonTest/kotlin/io/zenoh/QueryableTest.kt b/zenoh-java/src/commonTest/kotlin/io/zenoh/QueryableTest.kt deleted file mode 100644 index f4f724a3..00000000 --- a/zenoh-java/src/commonTest/kotlin/io/zenoh/QueryableTest.kt +++ /dev/null @@ -1,241 +0,0 @@ -// -// Copyright (c) 2023 ZettaScale Technology -// -// This program and the accompanying materials are made available under the -// terms of the Eclipse Public License 2.0 which is available at -// http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 -// which is available at https://www.apache.org/licenses/LICENSE-2.0. -// -// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 -// -// Contributors: -// ZettaScale Zenoh Team, -// - -package io.zenoh - -import io.zenoh.handlers.Handler -import io.zenoh.keyexpr.KeyExpr -import io.zenoh.keyexpr.intoKeyExpr -import io.zenoh.prelude.CongestionControl -import io.zenoh.prelude.Priority -import io.zenoh.prelude.QoS -import io.zenoh.prelude.SampleKind -import io.zenoh.query.Reply -import io.zenoh.queryable.Query -import io.zenoh.sample.Sample -import io.zenoh.value.Value -import org.apache.commons.net.ntp.TimeStamp -import java.time.Duration -import java.time.Instant -import java.util.* -import java.util.concurrent.BlockingQueue -import kotlin.test.* - -class QueryableTest { - - companion object { - val TEST_KEY_EXP = "example/testing/keyexpr".intoKeyExpr() - const val testPayload = "Hello queryable" - } - - private lateinit var session: Session - private lateinit var testKeyExpr: KeyExpr - - @BeforeTest - fun setUp() { - session = Session.open() - testKeyExpr = "example/testing/keyexpr".intoKeyExpr() - } - - @AfterTest - fun tearDown() { - session.close() - testKeyExpr.close() - } - - /** Test validating both Queryable and get operations. */ - @Test - fun queryable_runsWithCallback() { - val sample = Sample( - testKeyExpr, Value(testPayload), SampleKind.PUT, TimeStamp(Date.from(Instant.now())), QoS.default() - ) - val queryable = session.declareQueryable(testKeyExpr).with { query -> - query.reply(testKeyExpr).success(sample.value).timestamp(sample.timestamp!!).res() - }.res() - - var reply: Reply? = null - val delay = Duration.ofMillis(1000) - session.get(testKeyExpr).with { reply = it }.timeout(delay).res() - - Thread.sleep(1000) - - assertTrue(reply is Reply.Success) - assertEquals((reply as Reply.Success).sample, sample) - - queryable.close() - } - - @Test - fun queryable_runsWithHandler() { - val handler = QueryHandler() - val queryable = session.declareQueryable(testKeyExpr).with(handler).res() - - val receivedReplies = ArrayList() - session.get(testKeyExpr).with { reply: Reply -> - receivedReplies.add(reply) - }.res() - - Thread.sleep(500) - - queryable.close() - assertTrue(receivedReplies.all { it is Reply.Success }) - assertEquals(handler.performedReplies.size, receivedReplies.size) - } - - @Test - fun queryableBuilder_queueHandlerIsTheDefaultHandler() { - val queryable = session.declareQueryable(TEST_KEY_EXP).res() - assertTrue(queryable.receiver is BlockingQueue>) - queryable.close() - } - - @Test - fun queryTest() { - var receivedQuery: Query? = null - val queryable = session.declareQueryable(testKeyExpr).with { query -> receivedQuery = query }.res() - - session.get(testKeyExpr).res() - - Thread.sleep(1000) - queryable.close() - assertNotNull(receivedQuery) - assertNull(receivedQuery!!.value) - } - - @Test - fun queryWithValueTest() { - var receivedQuery: Query? = null - val queryable = session.declareQueryable(testKeyExpr).with { query -> receivedQuery = query }.res() - - session.get(testKeyExpr).withValue("Test value").res() - - Thread.sleep(1000) - queryable.close() - assertNotNull(receivedQuery) - assertEquals(Value("Test value"), receivedQuery!!.value) - } - - @Test - fun queryReplySuccessTest() { - val message = "Test message" - val timestamp = TimeStamp.getCurrentTime() - val priority = Priority.DATA_HIGH - val express = true - val congestionControl = CongestionControl.DROP - val queryable = session.declareQueryable(testKeyExpr).with { - it.use { query -> - query.reply(testKeyExpr).success(message).timestamp(timestamp).priority(priority).express(express) - .congestionControl(congestionControl).res() - } - }.res() - - var receivedReply: Reply? = null - session.get(testKeyExpr).with { receivedReply = it }.timeout(Duration.ofMillis(10)).res() - - queryable.close() - - assertTrue(receivedReply is Reply.Success) - val reply = receivedReply as Reply.Success - assertEquals(message, reply.sample.value.payload.decodeToString()) - assertEquals(timestamp, reply.sample.timestamp) - assertEquals(priority, reply.sample.qos.priority) - assertEquals(express, reply.sample.qos.express) - assertEquals(congestionControl, reply.sample.qos.congestionControl) - } - - @Test - fun queryReplyErrorTest() { - val message = "Error message" - val queryable = session.declareQueryable(testKeyExpr).with { - it.use { query -> - query.reply(testKeyExpr).error(Value(message)).res() - } - }.res() - - var receivedReply: Reply? = null - session.get(testKeyExpr).with { receivedReply = it }.timeout(Duration.ofMillis(10)).res() - - Thread.sleep(1000) - queryable.close() - - assertNotNull(receivedReply) - assertTrue(receivedReply is Reply.Error) - val reply = receivedReply as Reply.Error - assertEquals(message, reply.error.payload.decodeToString()) - } - - @Test - fun queryReplyDeleteTest() { - val timestamp = TimeStamp.getCurrentTime() - val priority = Priority.DATA_HIGH - val express = true - val congestionControl = CongestionControl.DROP - val queryable = session.declareQueryable(testKeyExpr).with { - it.use { query -> - query.reply(testKeyExpr).delete().timestamp(timestamp).priority(priority).express(express) - .congestionControl(congestionControl).res() - } - }.res() - - var receivedReply: Reply? = null - session.get(testKeyExpr).with { receivedReply = it }.timeout(Duration.ofMillis(10)).res() - - queryable.close() - - assertNotNull(receivedReply) - assertTrue(receivedReply is Reply.Delete) - val reply = receivedReply as Reply.Delete - assertEquals(timestamp, reply.timestamp) - assertEquals(priority, reply.qos.priority) - assertEquals(express, reply.qos.express) - assertEquals(congestionControl, reply.qos.congestionControl) - } - - @Test - fun onCloseTest() { - var onCloseWasCalled = false - val queryable = session.declareQueryable(testKeyExpr).onClose { onCloseWasCalled = true }.res() - queryable.undeclare() - - assertTrue(onCloseWasCalled) - } -} - -/** A dummy handler that replies "Hello queryable" followed by the count of replies performed. */ -private class QueryHandler : Handler { - - private var counter = 0 - - val performedReplies: ArrayList = ArrayList() - - override fun handle(t: Query) { - reply(t) - } - - override fun receiver(): QueryHandler { - return this - } - - override fun onClose() {} - - fun reply(query: Query) { - val payload = "Hello queryable $counter!" - counter++ - val sample = Sample( - query.keyExpr, Value(payload), SampleKind.PUT, TimeStamp(Date.from(Instant.now())), QoS.default() - ) - performedReplies.add(sample) - query.reply(query.keyExpr).success(sample.value).timestamp(sample.timestamp!!).res() - } -} diff --git a/zenoh-java/src/commonTest/kotlin/io/zenoh/SelectorTest.kt b/zenoh-java/src/commonTest/kotlin/io/zenoh/SelectorTest.kt deleted file mode 100644 index 99d133f4..00000000 --- a/zenoh-java/src/commonTest/kotlin/io/zenoh/SelectorTest.kt +++ /dev/null @@ -1,30 +0,0 @@ -package io.zenoh - -import io.zenoh.exceptions.KeyExprException -import io.zenoh.selector.Selector -import io.zenoh.selector.intoSelector -import kotlin.test.Test -import kotlin.test.assertEquals -import kotlin.test.assertFailsWith - -class SelectorTest { - - init { - Zenoh.load() - } - - @Test - fun selectorFromStringTest() { - "a/b/c?arg1=val1".intoSelector().use { selector: Selector -> - assertEquals("a/b/c", selector.keyExpr.toString()) - assertEquals("arg1=val1", selector.parameters) - } - - "a/b/c".intoSelector().use { selector: Selector -> - assertEquals("a/b/c", selector.keyExpr.toString()) - assertEquals("", selector.parameters) - } - - assertFailsWith { "".intoSelector() } - } -} diff --git a/zenoh-java/src/commonTest/kotlin/io/zenoh/SessionTest.kt b/zenoh-java/src/commonTest/kotlin/io/zenoh/SessionTest.kt deleted file mode 100644 index 89d1a47b..00000000 --- a/zenoh-java/src/commonTest/kotlin/io/zenoh/SessionTest.kt +++ /dev/null @@ -1,65 +0,0 @@ -// -// Copyright (c) 2023 ZettaScale Technology -// -// This program and the accompanying materials are made available under the -// terms of the Eclipse Public License 2.0 which is available at -// http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 -// which is available at https://www.apache.org/licenses/LICENSE-2.0. -// -// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 -// -// Contributors: -// ZettaScale Zenoh Team, -// - -package io.zenoh - -import io.zenoh.exceptions.SessionException -import io.zenoh.keyexpr.intoKeyExpr -import io.zenoh.sample.Sample -import kotlin.test.* - -class SessionTest { - - companion object { - val TEST_KEY_EXP = "example/testing/keyexpr".intoKeyExpr() - } - - @Test - fun sessionStartCloseTest() { - val session = Session.open() - assertTrue(session.isOpen()) - session.close() - assertFalse(session.isOpen()) - } - - @Test - fun sessionStop_stopUnopenedSessionIsNoOp() { - val session = Session.open() - session.close() - } - - @Test - fun sessionClose_doesNotThrowExceptionWhenStoppingSessionWithActiveDeclarations() { - val session = Session.open() - session.declarePublisher(TEST_KEY_EXP) - session.close() - } - - @Test - fun sessionDeclare_sessionIsOpenFromInitialization() { - val session = Session.open() - assertTrue(session.isOpen()) - session.close() - } - - @Test - fun sessionClose_newDeclarationsReturnNullAfterClosingSession() { - val session = Session.open() - session.close() - assertFailsWith { session.declarePublisher(TEST_KEY_EXP).res() } - assertFailsWith { session.declareSubscriber(TEST_KEY_EXP).with {}.res() } - assertFailsWith { session.declareQueryable(TEST_KEY_EXP).with {}.res() } - } - -} diff --git a/zenoh-java/src/commonTest/kotlin/io/zenoh/SubscriberTest.kt b/zenoh-java/src/commonTest/kotlin/io/zenoh/SubscriberTest.kt deleted file mode 100644 index 051faaab..00000000 --- a/zenoh-java/src/commonTest/kotlin/io/zenoh/SubscriberTest.kt +++ /dev/null @@ -1,150 +0,0 @@ -// -// Copyright (c) 2023 ZettaScale Technology -// -// This program and the accompanying materials are made available under the -// terms of the Eclipse Public License 2.0 which is available at -// http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 -// which is available at https://www.apache.org/licenses/LICENSE-2.0. -// -// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 -// -// Contributors: -// ZettaScale Zenoh Team, -// - -package io.zenoh - -import io.zenoh.handlers.Handler -import io.zenoh.keyexpr.KeyExpr -import io.zenoh.keyexpr.intoKeyExpr -import io.zenoh.prelude.CongestionControl -import io.zenoh.prelude.Encoding -import io.zenoh.prelude.Priority -import io.zenoh.sample.Sample -import io.zenoh.value.Value -import java.util.* -import java.util.concurrent.BlockingQueue -import kotlin.collections.ArrayDeque -import kotlin.collections.ArrayList -import kotlin.test.* - -class SubscriberTest { - - companion object { - val TEST_PRIORITY = Priority.DATA_HIGH; - val TEST_CONGESTION_CONTROL = CongestionControl.BLOCK; - - val testValues = arrayListOf( - Value("Test 1".encodeToByteArray(), Encoding(Encoding.ID.TEXT_PLAIN)), - Value("Test 2".encodeToByteArray(), Encoding(Encoding.ID.TEXT_JSON)), - Value("Test 3".encodeToByteArray(), Encoding(Encoding.ID.TEXT_CSV)) - ) - } - - private lateinit var session: Session - private lateinit var testKeyExpr: KeyExpr - - @BeforeTest - fun setUp() { - session = Session.open() - testKeyExpr = "example/testing/keyexpr".intoKeyExpr() - } - - @AfterTest - fun tearDown() { - session.close() - testKeyExpr.close() - } - - @Test - fun subscriber_runsWithCallback() { - val receivedSamples = ArrayList() - val subscriber = - session.declareSubscriber(testKeyExpr).with { sample -> receivedSamples.add(sample) }.res() - - testValues.forEach { value -> - session.put(testKeyExpr, value) - .priority(TEST_PRIORITY) - .congestionControl(TEST_CONGESTION_CONTROL) - .res() - } - assertEquals(receivedSamples.size, testValues.size) - - receivedSamples.zip(testValues).forEach { (sample, value) -> - assertEquals(sample.value, value) - assertEquals(sample.qos.priority(), TEST_PRIORITY) - assertEquals(sample.qos.congestionControl(), TEST_CONGESTION_CONTROL) - } - - subscriber.close() - } - - @Test - fun subscriber_runsWithHandler() { - val handler = QueueHandler() - val subscriber = session.declareSubscriber(testKeyExpr).with(handler).res() - - testValues.forEach { value -> - session.put(testKeyExpr, value) - .priority(TEST_PRIORITY) - .congestionControl(TEST_CONGESTION_CONTROL) - .res() - } - assertEquals(handler.queue.size, testValues.size) - - handler.queue.zip(testValues).forEach { (sample, value) -> - assertEquals(sample.value, value) - assertEquals(sample.qos.priority(), TEST_PRIORITY) - assertEquals(sample.qos.congestionControl(), TEST_CONGESTION_CONTROL) - } - - subscriber.close() - } - - @Test - fun subscriberBuilder_queueHandlerIsTheDefaultHandler() { - val subscriber = session.declareSubscriber(testKeyExpr).res() - subscriber.close() - assertTrue(subscriber.receiver is BlockingQueue>) - } - - @Test - fun subscriber_isDeclaredWithNonDeclaredKeyExpression() { - // Declaring a subscriber with an undeclared key expression and verifying it properly receives samples. - val keyExpr = KeyExpr("example/**") - val session = Session.open() - - val receivedSamples = ArrayList() - val subscriber = session.declareSubscriber(keyExpr).with { sample -> receivedSamples.add(sample) }.res() - testValues.forEach { value -> session.put(testKeyExpr, value).res() } - subscriber.close() - - assertEquals(receivedSamples.size, testValues.size) - - for ((index, sample) in receivedSamples.withIndex()) { - assertEquals(sample.value, testValues[index]) - } - } - - @Test - fun onCloseTest() { - var onCloseWasCalled = false - val subscriber = session.declareSubscriber(testKeyExpr).onClose { onCloseWasCalled = true }.res() - subscriber.undeclare() - assertTrue(onCloseWasCalled) - } -} - -private class QueueHandler : Handler> { - - val queue: ArrayDeque = ArrayDeque() - override fun handle(t: T) { - queue.add(t) - } - - override fun receiver(): ArrayDeque { - return queue - } - - override fun onClose() {} -} diff --git a/zenoh-java/src/jvmMain/kotlin/io/zenoh/Zenoh.kt b/zenoh-java/src/jvmMain/kotlin/io/zenoh/Zenoh.kt index d6b7dd9b..11ae638c 100644 --- a/zenoh-java/src/jvmMain/kotlin/io/zenoh/Zenoh.kt +++ b/zenoh-java/src/jvmMain/kotlin/io/zenoh/Zenoh.kt @@ -15,155 +15,140 @@ package io.zenoh import java.io.File +import java.io.FileInputStream import java.io.FileOutputStream import java.io.InputStream -import java.io.FileInputStream 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 class Zenoh private actual constructor() { - - actual companion object { - private const val ZENOH_LIB_NAME = "zenoh_jni" - private const val ZENOH_LOGS_PROPERTY = "zenoh.logger" - - private var instance: Zenoh? = null +internal actual object ZenohLoad { + private const val ZENOH_LIB_NAME = "zenoh_jni" - actual fun load() { - instance ?: Zenoh().also { instance = it } + init { + // Try first to load the local native library for cases in which the module was built locally, + // otherwise try to load from the JAR. + if (tryLoadingLocalLibrary().isFailure) { + val target = determineTarget().getOrThrow() + tryLoadingLibraryFromJarPackage(target).getOrThrow() } + } - /** - * Determine target - * - * Determines the [Target] corresponding to the machine on top of which the native code will run. - * - * @return A result with the target. - */ - private fun determineTarget(): Result = runCatching { - val osName = System.getProperty("os.name").lowercase() - val osArch = System.getProperty("os.arch") - - val target = when { - osName.contains("win") -> when { - osArch.contains("x86_64") || osArch.contains("amd64") -> Target.WINDOWS_X86_64_MSVC - else -> throw UnsupportedOperationException("Unsupported architecture: $osArch") - } - - osName.contains("mac") -> when { - osArch.contains("x86_64") || osArch.contains("amd64") -> Target.APPLE_X86_64 - osArch.contains("aarch64") -> Target.APPLE_AARCH64 - else -> throw UnsupportedOperationException("Unsupported architecture: $osArch") - } - - osName.contains("nix") || osName.contains("nux") || osName.contains("aix") -> when { - osArch.contains("x86_64") || osArch.contains("amd64") -> Target.LINUX_X86_64 - osArch.contains("aarch64") -> Target.LINUX_AARCH64 - else -> throw UnsupportedOperationException("Unsupported architecture: $osArch") - } - - else -> throw UnsupportedOperationException("Unsupported platform: $osName") + /** + * Determine target + * + * Determines the [Target] corresponding to the machine on top of which the native code will run. + * + * @return A result with the target. + */ + private fun determineTarget(): Result = runCatching { + val osName = System.getProperty("os.name").lowercase() + val osArch = System.getProperty("os.arch") + + val target = when { + osName.contains("win") -> when { + osArch.contains("x86_64") || osArch.contains("amd64") -> Target.WINDOWS_X86_64_MSVC + else -> throw UnsupportedOperationException("Unsupported architecture: $osArch") } - return Result.success(target) - } - /** - * Unzip library. - * - * The Zenoh libraries are stored within the JAR as compressed ZIP files. - * The location of the zipped files is expected to be under target/target.zip. - * It is expected that the zip file only contains the compressed library. - * - * The uncompressed library will be stored temporarily and deleted on exit. - * - * @param compressedLib Input stream pointing to the compressed library. - * @return A result with the uncompressed library file. - */ - private fun unzipLibrary(compressedLib: InputStream): Result = runCatching { - val zipInputStream = ZipInputStream(compressedLib) - val buffer = ByteArray(1024) - val zipEntry = zipInputStream.nextEntry - - val library = File.createTempFile(zipEntry!!.name, ".tmp") - library.deleteOnExit() - - val parent = library.parentFile - if (!parent.exists()) { - parent.mkdirs() + osName.contains("mac") -> when { + osArch.contains("x86_64") || osArch.contains("amd64") -> Target.APPLE_X86_64 + osArch.contains("aarch64") -> Target.APPLE_AARCH64 + else -> throw UnsupportedOperationException("Unsupported architecture: $osArch") } - val fileOutputStream = FileOutputStream(library) - var len: Int - while (zipInputStream.read(buffer).also { len = it } > 0) { - fileOutputStream.write(buffer, 0, len) + osName.contains("nix") || osName.contains("nux") || osName.contains("aix") -> when { + osArch.contains("x86_64") || osArch.contains("amd64") -> Target.LINUX_X86_64 + osArch.contains("aarch64") -> Target.LINUX_AARCH64 + else -> throw UnsupportedOperationException("Unsupported architecture: $osArch") } - fileOutputStream.close() - zipInputStream.closeEntry() - zipInputStream.close() - return Result.success(library) + else -> throw UnsupportedOperationException("Unsupported platform: $osName") } + return Result.success(target) + } - private fun loadLibraryAsInputStream(target: Target): Result = runCatching { - val libUrl = ClassLoader.getSystemClassLoader().getResourceAsStream("$target/$target.zip")!! - val uncompressedLibFile = unzipLibrary(libUrl) - return Result.success(FileInputStream(uncompressedLibFile.getOrThrow())) + /** + * Unzip library. + * + * The Zenoh libraries are stored within the JAR as compressed ZIP files. + * The location of the zipped files is expected to be under target/target.zip. + * It is expected that the zip file only contains the compressed library. + * + * The uncompressed library will be stored temporarily and deleted on exit. + * + * @param compressedLib Input stream pointing to the compressed library. + * @return A result with the uncompressed library file. + */ + private fun unzipLibrary(compressedLib: InputStream): Result = runCatching { + val zipInputStream = ZipInputStream(compressedLib) + val buffer = ByteArray(1024) + val zipEntry = zipInputStream.nextEntry + + val library = File.createTempFile(zipEntry!!.name, ".tmp") + library.deleteOnExit() + + val parent = library.parentFile + if (!parent.exists()) { + parent.mkdirs() } - @Suppress("UnsafeDynamicallyLoadedCode") - private fun loadZenohJNI(inputStream: InputStream) { - val tempLib = File.createTempFile("tempLib", ".tmp") - tempLib.deleteOnExit() + val fileOutputStream = FileOutputStream(library) + var len: Int + while (zipInputStream.read(buffer).also { len = it } > 0) { + fileOutputStream.write(buffer, 0, len) + } + fileOutputStream.close() - FileOutputStream(tempLib).use { output -> - inputStream.copyTo(output) - } + zipInputStream.closeEntry() + zipInputStream.close() + return Result.success(library) + } - System.load(tempLib.absolutePath) - } + private fun loadLibraryAsInputStream(target: Target): Result = runCatching { + val libUrl = ClassLoader.getSystemClassLoader().getResourceAsStream("$target/$target.zip")!! + val uncompressedLibFile = unzipLibrary(libUrl) + return Result.success(FileInputStream(uncompressedLibFile.getOrThrow())) + } - /** - * Load library from jar package. - * - * Attempts to load the library corresponding to the `target` specified from the zenoh kotlin jar. - * - * @param target - */ - private fun tryLoadingLibraryFromJarPackage(target: Target): Result = runCatching { - val lib: Result = loadLibraryAsInputStream(target) - lib.onSuccess { loadZenohJNI(it) }.onFailure { throw Exception("Unable to load Zenoh JNI: $it") } - } + @Suppress("UnsafeDynamicallyLoadedCode") + private fun loadZenohJNI(inputStream: InputStream) { + val tempLib = File.createTempFile("tempLib", ".tmp") + tempLib.deleteOnExit() - /** - * Try loading local library. - * - * This function aims to load the default library that is usually included when building the zenoh kotlin library - * locally. - */ - private fun tryLoadingLocalLibrary(): Result = runCatching { - val lib = ClassLoader.getSystemClassLoader().findLibraryStream(ZENOH_LIB_NAME) - if (lib != null) { - loadZenohJNI(lib) - } else { - throw Exception("Unable to load local Zenoh JNI.") - } + FileOutputStream(tempLib).use { output -> + inputStream.copyTo(output) } + + System.load(tempLib.absolutePath) } - init { - // Try first to load the local native library for cases in which the module was built locally, - // otherwise try to load from the JAR. - if (tryLoadingLocalLibrary().isFailure) { - val target = determineTarget().getOrThrow() - tryLoadingLibraryFromJarPackage(target).getOrThrow() - } + /** + * Load library from jar package. + * + * Attempts to load the library corresponding to the `target` specified from the zenoh kotlin jar. + * + * @param target + */ + private fun tryLoadingLibraryFromJarPackage(target: Target): Result = runCatching { + val lib: Result = loadLibraryAsInputStream(target) + lib.onSuccess { loadZenohJNI(it) }.onFailure { throw Exception("Unable to load Zenoh JNI: $it") } + } - val logLevel = System.getProperty(ZENOH_LOGS_PROPERTY) - if (logLevel != null) { - Logger.start(logLevel) + /** + * Try loading local library. + * + * This function aims to load the default library that is usually included when building the zenoh kotlin library + * locally. + */ + private fun tryLoadingLocalLibrary(): Result = runCatching { + val lib = ClassLoader.getSystemClassLoader().findLibraryStream(ZENOH_LIB_NAME) + if (lib != null) { + loadZenohJNI(lib) + } else { + throw Exception("Unable to load local Zenoh JNI.") } } } diff --git a/zenoh-java/src/jvmTest/java/io/zenoh/ConfigTest.java b/zenoh-java/src/jvmTest/java/io/zenoh/ConfigTest.java new file mode 100644 index 00000000..990c5452 --- /dev/null +++ b/zenoh-java/src/jvmTest/java/io/zenoh/ConfigTest.java @@ -0,0 +1,352 @@ +// +// Copyright (c) 2023 ZettaScale Technology +// +// This program and the accompanying materials are made available under the +// terms of the Eclipse Public License 2.0 which is available at +// http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 +// which is available at https://www.apache.org/licenses/LICENSE-2.0. +// +// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 +// +// Contributors: +// ZettaScale Zenoh Team, +// +package io.zenoh; + +import io.zenoh.bytes.ZBytes; +import io.zenoh.exceptions.ZError; +import io.zenoh.keyexpr.KeyExpr; +import io.zenoh.pubsub.Subscriber; +import io.zenoh.sample.Sample; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; + +import static org.junit.Assert.*; + +@RunWith(JUnit4.class) +public class ConfigTest { + + private static final String json5ClientConfigString = + "{\n" + + " mode: \"peer\",\n" + + " connect: {\n" + + " endpoints: [\"tcp/localhost:7450\"]\n" + + " },\n" + + " scouting: {\n" + + " multicast: {\n" + + " enabled: false\n" + + " }\n" + + " }\n" + + "}"; + + private static final String json5ServerConfigString = + "{\n" + + " mode: \"peer\",\n" + + " listen: {\n" + + " endpoints: [\"tcp/localhost:7450\"],\n" + + " },\n" + + " scouting: {\n" + + " multicast: {\n" + + " enabled: false,\n" + + " }\n" + + " }\n" + + "}"; + + private static final String jsonClientConfigString = + "{\n" + + " \"mode\": \"peer\",\n" + + " \"connect\": {\n" + + " \"endpoints\": [\"tcp/localhost:7450\"]\n" + + " },\n" + + " \"scouting\": {\n" + + " \"multicast\": {\n" + + " \"enabled\": false\n" + + " }\n" + + " }\n" + + "}"; + + private static final String jsonServerConfigString = + "{\n" + + " \"mode\": \"peer\",\n" + + " \"listen\": {\n" + + " \"endpoints\": [\"tcp/localhost:7450\"]\n" + + " },\n" + + " \"scouting\": {\n" + + " \"multicast\": {\n" + + " \"enabled\": false\n" + + " }\n" + + " }\n" + + "}"; + + private static final String yamlClientConfigString = + "mode: peer\n" + + "connect:\n" + + " endpoints:\n" + + " - tcp/localhost:7450\n" + + "scouting:\n" + + " multicast:\n" + + " enabled: false\n"; + + private static final String yamlServerConfigString = + "mode: peer\n" + + "listen:\n" + + " endpoints:\n" + + " - tcp/localhost:7450\n" + + "scouting:\n" + + " multicast:\n" + + " enabled: false\n"; + + private static final KeyExpr TEST_KEY_EXP; + private static final Config json5ClientConfig; + private static final Config json5ServerConfig; + private static final Config jsonClientConfig; + private static final Config jsonServerConfig; + private static final Config yamlClientConfig; + private static final Config yamlServerConfig; + + static { + try { + TEST_KEY_EXP = KeyExpr.tryFrom("example/testing/keyexpr"); + json5ClientConfig = Config.fromJson5(json5ClientConfigString); + json5ServerConfig = Config.fromJson5(json5ServerConfigString); + + jsonClientConfig = Config.fromJson(jsonClientConfigString); + jsonServerConfig = Config.fromJson(jsonServerConfigString); + + yamlClientConfig = Config.fromYaml(yamlClientConfigString); + yamlServerConfig = Config.fromYaml(yamlServerConfigString); + } catch (ZError e) { + throw new RuntimeException(e); + } + } + + private void runSessionTest(Config clientConfig, Config serverConfig) throws ZError, InterruptedException { + Session sessionClient = Zenoh.open(clientConfig); + Session sessionServer = Zenoh.open(serverConfig); + + final Sample[] receivedSample = new Sample[1]; + + Subscriber subscriber = + sessionClient.declareSubscriber(TEST_KEY_EXP, sample -> receivedSample[0] = sample); + ZBytes payload = ZBytes.from("example message"); + sessionClient.put(TEST_KEY_EXP, payload); + + Thread.sleep(1000); + + subscriber.close(); + sessionClient.close(); + sessionServer.close(); + + assertNotNull(receivedSample[0]); + assertEquals(receivedSample[0].getPayload(), payload); + } + + @Test + public void testConfigWithJSON5() throws ZError, InterruptedException { + runSessionTest(json5ClientConfig, json5ServerConfig); + } + + @Test + public void testConfigLoadsFromJSONString() throws ZError, InterruptedException { + runSessionTest(jsonClientConfig, jsonServerConfig); + } + + + @Test + public void testConfigLoadsFromYAMLString() throws ZError, InterruptedException { + runSessionTest(yamlClientConfig, yamlServerConfig); + } + + @Test + public void testDefaultConfig() throws ZError { + Config config = Config.loadDefault(); + Session session = Zenoh.open(config); + session.close(); + } + + @Test + public void configFailsWithIllFormatedJsonTest() throws ZError { + String illFormattedConfig = + "{\n" + + " mode: \"peer\",\n" + + " connect: {\n" + + " endpoints: [\"tcp/localhost:7450\"],\n" + + // missing '}' character here + "}"; + + assertThrows(ZError.class, () -> Config.fromJson(illFormattedConfig)); + } + + @Test + public void configFailsWithIllFormatedYAMLTest() { + String illFormattedConfig = + "mode: peer\n" + + "connect:\n" + + " endpoints:\n" + + " - tcp/localhost:7450\n" + + "scouting\n"; + + assertThrows(ZError.class, () -> Config.fromJson(illFormattedConfig)); + } + + @Test + public void configLoadsFromJSONFileTest() throws IOException, ZError, InterruptedException { + File clientConfigFile = File.createTempFile("clientConfig", ".json"); + File serverConfigFile = File.createTempFile("serverConfig", ".json"); + + try { + // Writing text to the files + Files.write(clientConfigFile.toPath(), jsonClientConfigString.getBytes()); + Files.write(serverConfigFile.toPath(), jsonServerConfigString.getBytes()); + + Config loadedClientConfig = Config.fromFile(clientConfigFile); + Config loadedServerConfig = Config.fromFile(serverConfigFile); + + runSessionTest(loadedClientConfig, loadedServerConfig); + } finally { + clientConfigFile.delete(); + serverConfigFile.delete(); + } + } + + @Test + public void configLoadsFromYAMLFileTest() throws IOException, ZError, InterruptedException { + File clientConfigFile = File.createTempFile("clientConfig", ".yaml"); + File serverConfigFile = File.createTempFile("serverConfig", ".yaml"); + + try { + // Writing text to the files + Files.write(clientConfigFile.toPath(), yamlClientConfigString.getBytes()); + Files.write(serverConfigFile.toPath(), yamlServerConfigString.getBytes()); + + Config loadedClientConfig = Config.fromFile(clientConfigFile); + Config loadedServerConfig = Config.fromFile(serverConfigFile); + + runSessionTest(loadedClientConfig, loadedServerConfig); + } finally { + clientConfigFile.delete(); + serverConfigFile.delete(); + } + } + + @Test + public void configLoadsFromJSON5FileTest() throws IOException, ZError, InterruptedException { + File clientConfigFile = File.createTempFile("clientConfig", ".json5"); + File serverConfigFile = File.createTempFile("serverConfig", ".json5"); + + try { + // Writing text to the files + Files.write(clientConfigFile.toPath(), json5ClientConfigString.getBytes()); + Files.write(serverConfigFile.toPath(), json5ServerConfigString.getBytes()); + + Config loadedClientConfig = Config.fromFile(clientConfigFile); + Config loadedServerConfig = Config.fromFile(serverConfigFile); + + runSessionTest(loadedClientConfig, loadedServerConfig); + } finally { + clientConfigFile.delete(); + serverConfigFile.delete(); + } + } + + @Test + public void configLoadsFromJSON5FileProvidingPathTest() throws IOException, ZError, InterruptedException { + File clientConfigFile = File.createTempFile("clientConfig", ".json5"); + File serverConfigFile = File.createTempFile("serverConfig", ".json5"); + + try { + // Writing text to the files + Files.write(clientConfigFile.toPath(), json5ClientConfigString.getBytes()); + Files.write(serverConfigFile.toPath(), json5ServerConfigString.getBytes()); + + Config loadedClientConfig = Config.fromFile(clientConfigFile.toPath()); + Config loadedServerConfig = Config.fromFile(serverConfigFile.toPath()); + + runSessionTest(loadedClientConfig, loadedServerConfig); + } finally { + clientConfigFile.delete(); + serverConfigFile.delete(); + } + } + + @Test + public void getJsonFunctionTest() throws ZError { + String jsonConfig = + "{\n" + + " mode: \"peer\",\n" + + " connect: {\n" + + " endpoints: [\"tcp/localhost:7450\"],\n" + + " },\n" + + " scouting: {\n" + + " multicast: {\n" + + " enabled: false,\n" + + " }\n" + + " }\n" + + "}"; + + Config config = Config.fromJson(jsonConfig); + + String value = config.getJson("connect"); + assertTrue(value.contains("\"endpoints\":[\"tcp/localhost:7450\"]")); + + String value2 = config.getJson("mode"); + assertEquals("\"peer\"", value2); + } + + @Test + public void configShouldRemainValidDespiteFailingToGetJsonValue() throws ZError { + String jsonConfig = + "{\n" + + " mode: \"peer\",\n" + + " connect: {\n" + + " endpoints: [\"tcp/localhost:7450\"],\n" + + " },\n" + + " scouting: {\n" + + " multicast: {\n" + + " enabled: false,\n" + + " }\n" + + " }\n" + + "}"; + + Config config = Config.fromJson(jsonConfig); + + assertThrows(ZError.class, () -> { + config.getJson("non_existent_key"); + }); + + String mode = config.getJson("mode"); + assertEquals("\"peer\"", mode); + } + + @Test + public void insertJson5FunctionTest() throws ZError { + Config config = Config.loadDefault(); + String endpoints = "[\"tcp/8.8.8.8:8\", \"tcp/8.8.8.8:9\"]"; + + config.insertJson5("listen/endpoints", endpoints); + + String jsonValue = config.getJson("listen/endpoints"); + assertTrue(jsonValue.contains("8.8.8.8")); + } + + @Test + public void insertIllFormattedJson5ShouldFailTest() throws ZError { + Config config = Config.loadDefault(); + + String illFormattedEndpoints = "[\"tcp/8.8.8.8:8\""; + assertThrows(ZError.class, () -> { + config.insertJson5("listen/endpoints", illFormattedEndpoints); + }); + + String correctEndpoints = "[\"tcp/8.8.8.8:8\", \"tcp/8.8.8.8:9\"]"; + config.insertJson5("listen/endpoints", correctEndpoints); + String retrievedEndpoints = config.getJson("listen/endpoints"); + + assertTrue(retrievedEndpoints.contains("8.8.8.8")); + } +} diff --git a/zenoh-java/src/jvmTest/java/io/zenoh/DeleteTest.java b/zenoh-java/src/jvmTest/java/io/zenoh/DeleteTest.java new file mode 100644 index 00000000..6b31ae6f --- /dev/null +++ b/zenoh-java/src/jvmTest/java/io/zenoh/DeleteTest.java @@ -0,0 +1,47 @@ +// +// Copyright (c) 2023 ZettaScale Technology +// +// This program and the accompanying materials are made available under the +// terms of the Eclipse Public License 2.0 which is available at +// http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 +// which is available at https://www.apache.org/licenses/LICENSE-2.0. +// +// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 +// +// Contributors: +// ZettaScale Zenoh Team, +// + +package io.zenoh; + +import io.zenoh.exceptions.ZError; +import io.zenoh.keyexpr.KeyExpr; +import io.zenoh.pubsub.Subscriber; +import io.zenoh.sample.SampleKind; +import io.zenoh.sample.Sample; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; + +@RunWith(JUnit4.class) +public class DeleteTest { + + @Test + public void deleteIsProperlyReceivedBySubscriberTest() throws ZError, InterruptedException { + Session session = Zenoh.open(Config.loadDefault()); + final Sample[] receivedSample = new Sample[1]; + KeyExpr keyExpr = KeyExpr.tryFrom("example/testing/keyexpr"); + Subscriber subscriber = + session.declareSubscriber(keyExpr, sample -> receivedSample[0] = sample); + session.delete(keyExpr); + + Thread.sleep(1000); + subscriber.close(); + session.close(); + assertNotNull(receivedSample[0]); + assertEquals(receivedSample[0].getKind(), SampleKind.DELETE); + } +} diff --git a/zenoh-java/src/jvmTest/java/io/zenoh/EncodingTest.java b/zenoh-java/src/jvmTest/java/io/zenoh/EncodingTest.java new file mode 100644 index 00000000..a524672b --- /dev/null +++ b/zenoh-java/src/jvmTest/java/io/zenoh/EncodingTest.java @@ -0,0 +1,217 @@ +// +// Copyright (c) 2023 ZettaScale Technology +// +// This program and the accompanying materials are made available under the +// terms of the Eclipse Public License 2.0 which is available at +// http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 +// which is available at https://www.apache.org/licenses/LICENSE-2.0. +// +// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 +// +// Contributors: +// ZettaScale Zenoh Team, +// + +package io.zenoh; + +import io.zenoh.bytes.Encoding; +import io.zenoh.bytes.ZBytes; +import io.zenoh.exceptions.ZError; +import io.zenoh.keyexpr.KeyExpr; +import io.zenoh.pubsub.PutOptions; +import io.zenoh.pubsub.Subscriber; +import io.zenoh.query.*; +import io.zenoh.sample.Sample; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +import static org.junit.Assert.*; + +@RunWith(JUnit4.class) +public class EncodingTest { + + private static final Encoding without_schema = Encoding.TEXT_CSV; + private static final Encoding with_schema = Encoding.APPLICATION_JSON.withSchema("test_schema"); + private ZBytes payload = ZBytes.from("test"); + + @Test + public void encoding_subscriberTest() throws ZError, InterruptedException { + Session session = Zenoh.open(Config.loadDefault()); + KeyExpr keyExpr = KeyExpr.tryFrom("example/testing/keyexpr"); + + // Testing non null schema + Sample[] receivedSample = new Sample[1]; + + Subscriber subscriber = + session.declareSubscriber(keyExpr, sample -> receivedSample[0] = sample); + + var putOptions = new PutOptions(); + putOptions.setEncoding(with_schema); + session.put(keyExpr, payload, putOptions); + Thread.sleep(200); + + assertNotNull(receivedSample[0]); + assertEquals(receivedSample[0].getEncoding(), with_schema); + + // Testing null schema + receivedSample[0] = null; + putOptions.setEncoding(without_schema); + session.put(keyExpr, payload, putOptions); + Thread.sleep(200); + + assertEquals(receivedSample[0].getEncoding(), without_schema); + + subscriber.close(); + session.close(); + } + + @Test + public void encoding_replySuccessTest() throws ZError, InterruptedException { + Session session = Zenoh.open(Config.loadDefault()); + KeyExpr keyExpr = KeyExpr.tryFrom("example/testing/**"); + Selector test1 = Selector.tryFrom("example/testing/reply_success"); + Selector test2 = Selector.tryFrom("example/testing/reply_success_with_schema"); + + var queryable = session.declareQueryable(keyExpr, query -> + { + try { + KeyExpr queryKeyExpr = query.getKeyExpr(); + var options = new ReplyOptions(); + if (queryKeyExpr.equals(test1.getKeyExpr())) { + options.setEncoding(without_schema); + query.reply(queryKeyExpr, payload, options); + } else if (queryKeyExpr.equals(test2.getKeyExpr())) { + options.setEncoding(with_schema); + query.reply(queryKeyExpr, payload, options); + } + } catch (Exception e) { + throw new RuntimeException(e); + } + } + ); + + // Testing with null schema on a reply success scenario. + Sample[] receivedSample = new Sample[1]; + session.get(test1, reply -> { + assertTrue(reply instanceof Reply.Success); + receivedSample[0] = ((Reply.Success) reply).getSample(); + }); + Thread.sleep(200); + + assertNotNull(receivedSample[0]); + assertEquals(receivedSample[0].getEncoding(), without_schema); + + // Testing with non-null schema on a reply success scenario. + receivedSample[0] = null; + session.get(test2, reply -> { + assertTrue(reply instanceof Reply.Success); + receivedSample[0] = ((Reply.Success) reply).getSample(); + }); + Thread.sleep(200); + + assertNotNull(receivedSample[0]); + assertEquals(receivedSample[0].getEncoding(), with_schema); + + queryable.close(); + session.close(); + } + + @Test + public void encoding_replyErrorTest() throws ZError, InterruptedException { + Session session = Zenoh.open(Config.loadDefault()); + KeyExpr keyExpr = KeyExpr.tryFrom("example/testing/**"); + Selector test1 = Selector.tryFrom("example/testing/reply_error"); + Selector test2 = Selector.tryFrom("example/testing/reply_error_with_schema"); + + ZBytes replyPayload = ZBytes.from("test"); + var queryable = session.declareQueryable(keyExpr, query -> + { + KeyExpr keyExpr1 = query.getKeyExpr(); + var options = new ReplyErrOptions(); + try { + if (keyExpr1.equals(test1.getKeyExpr())) { + options.setEncoding(without_schema); + query.replyErr(replyPayload, options); + } else if (keyExpr1.equals(test2.getKeyExpr())) { + options.setEncoding(with_schema); + query.replyErr(replyPayload, options); + } + } catch (Exception e) { + throw new RuntimeException(e); + } + }); + + // Testing with null schema on a reply error scenario. + ZBytes[] errorMessage = new ZBytes[1]; + Encoding[] errorEncoding = new Encoding[1]; + session.get(test1, reply -> + { + assertTrue(reply instanceof Reply.Error); + Reply.Error reply1 = (Reply.Error) reply; + errorMessage[0] = reply1.getError(); + errorEncoding[0] = reply1.getEncoding(); + } + ); + Thread.sleep(200); + + assertNotNull(errorMessage[0]); + assertEquals(errorEncoding[0], without_schema); + + Thread.sleep(200); + + // Testing with non-null schema on a reply error scenario. + errorMessage[0] = null; + errorEncoding[0] = null; + session.get(test2, reply -> + { + assertTrue(reply instanceof Reply.Error); + Reply.Error error = (Reply.Error) reply; + errorMessage[0] = error.getError(); + errorEncoding[0] = error.getEncoding(); + }); + Thread.sleep(200); + + assertNotNull(errorMessage[0]); + assertEquals(errorEncoding[0], with_schema); + + queryable.close(); + session.close(); + } + + @Test + public void encoding_queryTest() throws ZError, InterruptedException { + Session session = Zenoh.open(Config.loadDefault()); + KeyExpr keyExpr = KeyExpr.tryFrom("example/testing/keyexpr"); + Selector selector = Selector.tryFrom("example/testing/keyexpr"); + + Encoding[] receivedEncoding = new Encoding[1]; + var queryable = session.declareQueryable(keyExpr, query -> + { + receivedEncoding[0] = query.getEncoding(); + query.close(); + }); + + // Testing with null schema + var getOptions = new GetOptions(); + getOptions.setPayload(payload); + getOptions.setEncoding(without_schema); + session.get(selector, getOptions); + Thread.sleep(200); + + assertEquals(receivedEncoding[0], without_schema); + + Thread.sleep(200); + + // Testing non-null schema + receivedEncoding[0] = null; + getOptions.setEncoding(with_schema); + session.get(selector, getOptions); + Thread.sleep(200); + + assertEquals(receivedEncoding[0], with_schema); + + queryable.close(); + session.close(); + } +} diff --git a/zenoh-java/src/jvmTest/java/io/zenoh/GetTest.java b/zenoh-java/src/jvmTest/java/io/zenoh/GetTest.java new file mode 100644 index 00000000..b929bb92 --- /dev/null +++ b/zenoh-java/src/jvmTest/java/io/zenoh/GetTest.java @@ -0,0 +1,125 @@ +package io.zenoh; + +import io.zenoh.bytes.ZBytes; +import io.zenoh.exceptions.ZError; +import io.zenoh.handlers.Handler; +import io.zenoh.query.*; +import io.zenoh.sample.Sample; +import io.zenoh.sample.SampleKind; +import org.apache.commons.net.ntp.TimeStamp; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +import java.time.Duration; +import java.util.ArrayList; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; + +@RunWith(JUnit4.class) +public class GetTest { + + static final ZBytes payload = ZBytes.from("test"); + static final TimeStamp timestamp = TimeStamp.getCurrentTime(); + static final SampleKind kind = SampleKind.PUT; + + private Session session; + private Selector selector; + private Queryable queryable; + + @Before + public void setUp() throws ZError { + session = Zenoh.open(Config.loadDefault()); + selector = Selector.tryFrom("example/testing/keyexpr"); + queryable = session.declareQueryable(selector.getKeyExpr(), query -> + { + try { + var options = new ReplyOptions(); + options.setTimeStamp(timestamp); + query.reply(query.getKeyExpr(), payload, options); + } catch (ZError e) { + throw new RuntimeException(e); + } + } + ); + } + + @After + public void tearDown() throws ZError { + session.close(); + selector.close(); + queryable.close(); + } + + @Test + public void get_runsWithCallbackTest() throws ZError { + Reply[] reply = new Reply[1]; + + var getOptions = new GetOptions(); + getOptions.setTimeout(Duration.ofMillis(1000)); + session.get(selector, reply1 -> reply[0] = reply1, getOptions); + + assertNotNull(reply[0]); + Sample sample = ((Reply.Success) reply[0]).getSample(); + assertEquals(payload, sample.getPayload()); + assertEquals(kind, sample.getKind()); + assertEquals(selector.getKeyExpr(), sample.getKeyExpr()); + assertEquals(timestamp, sample.getTimestamp()); + } + + @Test + public void get_runsWithHandlerTest() throws ZError { + var getOptions = new GetOptions(); + getOptions.setTimeout(Duration.ofMillis(1000)); + ArrayList receiver = session.get(selector, new TestHandler(), getOptions); + for (Reply reply : receiver) { + Sample sample = ((Reply.Success) reply).getSample(); + assertEquals(payload, sample.getPayload()); + assertEquals(SampleKind.PUT, sample.getKind()); + } + } + + @Test + public void getWithSelectorParamsTest() throws ZError { + Parameters[] receivedParams = new Parameters[1]; + + Queryable queryable = session.declareQueryable(selector.getKeyExpr(), query -> + receivedParams[0] = query.getParameters() + ); + + Parameters params = Parameters.from("arg1=val1&arg2=val2&arg3"); + Selector selectorWithParams = new Selector(selector.getKeyExpr(), params); + var getOptions = new GetOptions(); + getOptions.setTimeout(Duration.ofMillis(1000)); + session.get(selectorWithParams, getOptions); + + queryable.close(); + + assertEquals(params, receivedParams[0]); + } +} + +/** + * A dummy handler for get operations. + */ +class TestHandler implements Handler> { + + static final ArrayList performedReplies = new ArrayList<>(); + + @Override + public void handle(Reply t) { + performedReplies.add(t); + } + + @Override + public ArrayList receiver() { + return performedReplies; + } + + @Override + public void onClose() { + } +} diff --git a/zenoh-java/src/jvmTest/java/io/zenoh/KeyExprTest.java b/zenoh-java/src/jvmTest/java/io/zenoh/KeyExprTest.java new file mode 100644 index 00000000..eca1aba4 --- /dev/null +++ b/zenoh-java/src/jvmTest/java/io/zenoh/KeyExprTest.java @@ -0,0 +1,166 @@ +// +// Copyright (c) 2023 ZettaScale Technology +// +// This program and the accompanying materials are made available under the +// terms of the Eclipse Public License 2.0 which is available at +// http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 +// which is available at https://www.apache.org/licenses/LICENSE-2.0. +// +// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 +// +// Contributors: +// ZettaScale Zenoh Team, +// + +package io.zenoh; + +import io.zenoh.exceptions.ZError; +import io.zenoh.keyexpr.KeyExpr; +import io.zenoh.keyexpr.SetIntersectionLevel; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +import static org.junit.Assert.*; + +@RunWith(JUnit4.class) +public class KeyExprTest { + + @Test + public void creation_TryFromTest() throws ZError { + // A couple of examples of valid and invalid key expressions. + KeyExpr keyExpr = KeyExpr.tryFrom("example/test"); + + assertThrows(ZError.class, () -> KeyExpr.tryFrom("example/test?param='test'")); + + KeyExpr keyExpr3 = KeyExpr.tryFrom("example/*/test"); + + assertThrows(ZError.class, () -> KeyExpr.tryFrom("example/!*/test")); + } + + @Test + public void equalizationTest() throws ZError { + KeyExpr keyExpr1 = KeyExpr.tryFrom("example/test"); + KeyExpr keyExpr2 = KeyExpr.tryFrom("example/test"); + assertEquals(keyExpr1, keyExpr2); + + KeyExpr keyExpr3 = KeyExpr.tryFrom("different/key/expr"); + assertNotEquals(keyExpr1, keyExpr3); + } + + @Test + public void creation_autocanonizeTest() throws ZError { + KeyExpr keyExpr1 = KeyExpr.autocanonize("example/**/test"); + KeyExpr keyExpr2 = KeyExpr.autocanonize("example/**/**/test"); + assertEquals(keyExpr1, keyExpr2); + } + + @Test + public void toStringTest() throws ZError { + String keyExprStr = "example/test/a/b/c"; + KeyExpr keyExpr = KeyExpr.tryFrom(keyExprStr); + assertEquals(keyExprStr, keyExpr.toString()); + assertEquals(keyExprStr, keyExpr.toString()); + } + + @Test + public void intersectionTest() throws ZError { + KeyExpr keyExprA = KeyExpr.tryFrom("example/*/test"); + + KeyExpr keyExprB = KeyExpr.tryFrom("example/B/test"); + assertTrue(keyExprA.intersects(keyExprB)); + + KeyExpr keyExprC = KeyExpr.tryFrom("example/B/C/test"); + assertFalse(keyExprA.intersects(keyExprC)); + + KeyExpr keyExprA2 = KeyExpr.tryFrom("example/**"); + assertTrue(keyExprA2.intersects(keyExprC)); + } + + @Test + public void includesTest() throws ZError { + KeyExpr keyExpr = KeyExpr.tryFrom("example/**"); + KeyExpr includedKeyExpr = KeyExpr.tryFrom("example/A/B/C/D"); + assertTrue(keyExpr.includes(includedKeyExpr)); + + KeyExpr notIncludedKeyExpr = KeyExpr.tryFrom("C/D"); + assertFalse(keyExpr.includes(notIncludedKeyExpr)); + } + + @Test + public void sessionDeclarationTest() throws ZError { + Session session = Zenoh.open(Config.loadDefault()); + KeyExpr keyExpr = session.declareKeyExpr("a/b/c"); + assertEquals("a/b/c", keyExpr.toString()); + session.close(); + keyExpr.close(); + } + + @Test + public void sessionUnDeclarationTest() throws ZError { + Session session = Zenoh.open(Config.loadDefault()); + KeyExpr keyExpr = session.declareKeyExpr("a/b/c"); + assertEquals("a/b/c", keyExpr.toString()); + + session.undeclare(keyExpr); + + // Undeclaring twice a key expression shall fail. + assertThrows(ZError.class, () -> session.undeclare(keyExpr)); + + // Undeclaring a key expr that was not declared through a session. + KeyExpr keyExpr2 = KeyExpr.tryFrom("x/y/z"); + assertThrows(ZError.class, () -> session.undeclare(keyExpr2)); + + session.close(); + } + + @Test + public void relationTo_includesTest() throws ZError { + KeyExpr keyExprA = KeyExpr.tryFrom("A/**"); + KeyExpr keyExprB = KeyExpr.tryFrom("A/B/C"); + + assertEquals(SetIntersectionLevel.INCLUDES, keyExprA.relationTo(keyExprB)); + } + + @Test + public void relationTo_intersectsTest() throws ZError { + KeyExpr keyExprA = KeyExpr.tryFrom("A/*/C/D"); + KeyExpr keyExprB = KeyExpr.tryFrom("A/B/C/*"); + + assertEquals(SetIntersectionLevel.INTERSECTS, keyExprA.relationTo(keyExprB)); + } + + @Test + public void relationTo_equalsTest() throws ZError { + KeyExpr keyExprA = KeyExpr.tryFrom("A/B/C"); + KeyExpr keyExprB = KeyExpr.tryFrom("A/B/C"); + + assertEquals(SetIntersectionLevel.EQUALS, keyExprA.relationTo(keyExprB)); + } + + @Test + public void relationTo_disjointTest() throws ZError { + KeyExpr keyExprA = KeyExpr.tryFrom("A/B/C"); + KeyExpr keyExprB = KeyExpr.tryFrom("D/E/F"); + + assertEquals(SetIntersectionLevel.DISJOINT, keyExprA.relationTo(keyExprB)); + } + + @Test + public void joinTest() throws ZError { + KeyExpr keyExprA = KeyExpr.tryFrom("A/B"); + KeyExpr keyExprExpected = KeyExpr.tryFrom("A/B/C/D"); + + KeyExpr keyExprJoined = keyExprA.join("C/D"); + assertEquals(keyExprExpected, keyExprJoined); + } + + @Test + public void concatTest() throws ZError { + KeyExpr keyExprA = KeyExpr.tryFrom("A/B"); + KeyExpr keyExprExpected = KeyExpr.tryFrom("A/B/C/D"); + + KeyExpr keyExprConcat = keyExprA.concat("/C/D"); + assertEquals(keyExprExpected, keyExprConcat); + } +} diff --git a/zenoh-java/src/jvmTest/java/io/zenoh/LivelinessTest.java b/zenoh-java/src/jvmTest/java/io/zenoh/LivelinessTest.java new file mode 100644 index 00000000..a43741c6 --- /dev/null +++ b/zenoh-java/src/jvmTest/java/io/zenoh/LivelinessTest.java @@ -0,0 +1,70 @@ +// +// Copyright (c) 2023 ZettaScale Technology +// +// This program and the accompanying materials are made available under the +// terms of the Eclipse Public License 2.0 which is available at +// http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 +// which is available at https://www.apache.org/licenses/LICENSE-2.0. +// +// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 +// +// Contributors: +// ZettaScale Zenoh Team, +// + +package io.zenoh; + +import io.zenoh.exceptions.ZError; +import io.zenoh.keyexpr.KeyExpr; +import io.zenoh.liveliness.LivelinessToken; +import io.zenoh.query.Reply; +import io.zenoh.sample.Sample; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +import static org.junit.Assert.assertNotNull; + +@RunWith(JUnit4.class) +public class LivelinessTest { + + @Test + public void getLivelinessTest() throws ZError, InterruptedException { + Session sessionA = Zenoh.open(Config.loadDefault()); + Session sessionB = Zenoh.open(Config.loadDefault()); + + var keyExpr = KeyExpr.tryFrom("test/liveliness"); + LivelinessToken token = sessionA.liveliness().declareToken(keyExpr); + + Reply[] receivedReply = new Reply[1]; + sessionB.liveliness().get(KeyExpr.tryFrom("test/**"), reply -> receivedReply[0] = reply); + + Thread.sleep(1000); + + assertNotNull(receivedReply[0]); + token.close(); + sessionA.close(); + sessionB.close(); + } + + @Test + public void livelinessSubscriberTest() throws ZError, InterruptedException { + Session sessionA = Zenoh.open(Config.loadDefault()); + Session sessionB = Zenoh.open(Config.loadDefault()); + + Sample[] receivedSample = new Sample[1]; + + var subscriber = sessionA.liveliness().declareSubscriber(KeyExpr.tryFrom("test/**"), sample -> receivedSample[0] = sample); + + var token = sessionB.liveliness().declareToken(KeyExpr.tryFrom("test/liveliness")); + + Thread.sleep(1000); + + assertNotNull(receivedSample[0]); + + token.close(); + subscriber.close(); + sessionA.close(); + sessionB.close(); + } +} diff --git a/zenoh-java/src/jvmTest/java/io/zenoh/ParametersTest.java b/zenoh-java/src/jvmTest/java/io/zenoh/ParametersTest.java new file mode 100644 index 00000000..0b6ab63b --- /dev/null +++ b/zenoh-java/src/jvmTest/java/io/zenoh/ParametersTest.java @@ -0,0 +1,125 @@ +package io.zenoh; + +import io.zenoh.query.Parameters; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +import java.util.List; +import java.util.Map; + +import static org.junit.Assert.*; + +@RunWith(JUnit4.class) +public class ParametersTest { + + @Test + public void shouldCreateEmptyParametersFromEmptyString() { + var result = Parameters.from(""); + + assertTrue(result.isEmpty()); + } + + @Test + public void shouldParseParametersFromFormattedString() { + var parameters = Parameters.from("a=1;b=2;c=3|4|5;d=6"); + + assertEquals("1", parameters.get("a")); + assertEquals("2", parameters.get("b")); + assertEquals("3|4|5", parameters.get("c")); + assertEquals("6", parameters.get("d")); + } + + @Test + public void shouldReturnListOfValuesSplitBySeparator() { + var parameters = Parameters.from("a=1;b=2;c=3|4|5;d=6"); + + assertEquals(List.of("3", "4", "5"), parameters.values("c")); + } + + @Test + public void containsKeyTest() { + var parameters = Parameters.from("a=1;b=2;c=3|4|5;d=6"); + + assertTrue(parameters.containsKey("a")); + assertFalse(parameters.containsKey("e")); + } + + @Test + public void getTest() { + var parameters = Parameters.from("a=1;b=2;c=3|4|5;d=6"); + + assertEquals("1", parameters.get("a")); + assertEquals("2", parameters.get("b")); + assertEquals("3|4|5", parameters.get("c")); + assertEquals("6", parameters.get("d")); + } + + @Test + public void getOrDefaultTest() { + var parameters = Parameters.from("a=1;b=2;c=3|4|5;d=6"); + + assertEquals("1", parameters.get("a")); + assertEquals("None", parameters.getOrDefault("e", "None")); + } + + @Test + public void toMapTest() { + var parameters = Parameters.from("a=1;b=2;c=3|4|5;d=6"); + + assertEquals(Map.of("a", "1", "b", "2", "c", "3|4|5", "d", "6"), parameters.toMap()); + } + + @Test + public void insertShouldReturnPreviouslyContainedValue() { + var parameters = Parameters.from("a=1"); + var oldValue = parameters.insert("a", "3"); + assertEquals("1", oldValue); + } + + @Test + public void insertShouldReturnNullIfNotAlreadyPresent() { + var parameters = Parameters.empty(); + var oldValue = parameters.insert("a", "1"); + assertNull(oldValue); + } + + @Test + public void removeShouldReturnOldValueIfPresent() { + var parameters = Parameters.from("a=1"); + var oldValue = parameters.remove("a"); + assertEquals("1", oldValue); + } + + @Test + public void removeShouldReturnNullIfNotAlreadyPresent() { + var parameters = Parameters.empty(); + var oldValue = parameters.remove("a"); + assertNull(oldValue); + } + + @Test + public void extendTest() { + var parameters = Parameters.from("a=1;b=2"); + parameters.extend(Parameters.from("c=3;d=4")); + + assertEquals(Parameters.from("a=1;b=2;c=3;d=4"), parameters); + + parameters.extend(Map.of("e", "5")); + assertEquals(Parameters.from("a=1;b=2;c=3;d=4;e=5"), parameters); + } + + @Test + public void extendOverwritesConflictingKeysTest() { + var parameters = Parameters.from("a=1;b=2"); + parameters.extend(Parameters.from("b=3;d=4")); + + assertEquals(Parameters.from("a=1;b=3;d=4"), parameters); + } + + @Test + public void emptyParametersToStringTest() { + var parameters = Parameters.empty(); + assertEquals("", parameters.toString()); + } +} diff --git a/zenoh-java/src/jvmTest/java/io/zenoh/PublisherTest.java b/zenoh-java/src/jvmTest/java/io/zenoh/PublisherTest.java new file mode 100644 index 00000000..d1e158f1 --- /dev/null +++ b/zenoh-java/src/jvmTest/java/io/zenoh/PublisherTest.java @@ -0,0 +1,99 @@ +package io.zenoh; + +import io.zenoh.bytes.ZBytes; +import io.zenoh.exceptions.ZError; +import io.zenoh.keyexpr.KeyExpr; +import io.zenoh.bytes.Encoding; +import io.zenoh.pubsub.PublisherOptions; +import io.zenoh.pubsub.PutOptions; +import io.zenoh.qos.QoS; +import io.zenoh.qos.Reliability; +import io.zenoh.sample.SampleKind; +import io.zenoh.pubsub.Publisher; +import io.zenoh.sample.Sample; +import io.zenoh.pubsub.Subscriber; +import kotlin.Pair; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +import java.util.ArrayList; +import java.util.List; + +import static org.junit.Assert.assertEquals; + +@RunWith(JUnit4.class) +public class PublisherTest { + + private Session session; + private ArrayList receivedSamples; + private Publisher publisher; + private Subscriber subscriber; + private KeyExpr keyExpr; + + @Before + public void setUp() throws ZError { + session = Zenoh.open(Config.loadDefault()); + keyExpr = KeyExpr.tryFrom("example/testing/keyexpr"); + + var config = new PublisherOptions(); + config.setReliability(Reliability.RELIABLE); + config.setEncoding(Encoding.ZENOH_STRING); + publisher = session.declarePublisher(keyExpr, config); + + receivedSamples = new ArrayList<>(); + + subscriber = session.declareSubscriber(keyExpr, receivedSamples::add); + } + + @After + public void tearDown() throws ZError { + publisher.close(); + subscriber.close(); + session.close(); + keyExpr.close(); + } + + @Test + public void putTest() { + + List> testPayloads = List.of( + new Pair<>(ZBytes.from("Test 1"), Encoding.TEXT_PLAIN), + new Pair<>(ZBytes.from("Test 2"), Encoding.TEXT_JSON), + new Pair<>(ZBytes.from("Test 3"), Encoding.TEXT_CSV) + ); + + testPayloads.forEach(value -> { + try { + var putOptions = new PutOptions(); + putOptions.setEncoding(value.getSecond()); + publisher.put(value.getFirst(), putOptions); + } catch (ZError e) { + throw new RuntimeException(e); + } + }); + + assertEquals(testPayloads.size(), receivedSamples.size()); + for (int index = 0; index < receivedSamples.size(); index++) { + var sample = receivedSamples.get(index); + assertEquals(testPayloads.get(index).getFirst(), sample.getPayload()); + assertEquals(testPayloads.get(index).getSecond(), sample.getEncoding()); + } + } + + @Test + public void deleteTest() throws ZError { + publisher.delete(); + assertEquals(1, receivedSamples.size()); + assertEquals(SampleKind.DELETE, receivedSamples.get(0).getKind()); + } + + @Test + public void shouldFallbackToPublisherEncodingWhenEncodingNotProvided() throws ZError { + publisher.put(ZBytes.from("Test")); + assertEquals(1, receivedSamples.size()); + assertEquals(Encoding.ZENOH_STRING, receivedSamples.get(0).getEncoding()); + } +} diff --git a/zenoh-java/src/jvmTest/java/io/zenoh/PutTest.java b/zenoh-java/src/jvmTest/java/io/zenoh/PutTest.java new file mode 100644 index 00000000..71b8cb5c --- /dev/null +++ b/zenoh-java/src/jvmTest/java/io/zenoh/PutTest.java @@ -0,0 +1,54 @@ +// +// Copyright (c) 2023 ZettaScale Technology +// +// This program and the accompanying materials are made available under the +// terms of the Eclipse Public License 2.0 which is available at +// http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 +// which is available at https://www.apache.org/licenses/LICENSE-2.0. +// +// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 +// +// Contributors: +// ZettaScale Zenoh Team, +// + +package io.zenoh; + +import io.zenoh.bytes.ZBytes; +import io.zenoh.bytes.Encoding; +import io.zenoh.exceptions.ZError; +import io.zenoh.keyexpr.KeyExpr; +import io.zenoh.pubsub.PutOptions; +import io.zenoh.pubsub.Subscriber; +import io.zenoh.sample.Sample; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; + +@RunWith(JUnit4.class) +public class PutTest { + + public static final String TEST_KEY_EXP = "example/testing/keyexpr"; + public static final ZBytes TEST_PAYLOAD = ZBytes.from("Hello"); + + @Test + public void putTest() throws ZError { + Session session = Zenoh.open(Config.loadDefault()); + Sample[] receivedSample = new Sample[1]; + var keyExpr = KeyExpr.tryFrom(TEST_KEY_EXP); + + Subscriber subscriber = + session.declareSubscriber(keyExpr, sample -> receivedSample[0] = sample); + + var putOptions = new PutOptions(); + putOptions.setEncoding(Encoding.TEXT_PLAIN); + session.put(keyExpr, TEST_PAYLOAD, putOptions); + subscriber.close(); + session.close(); + assertNotNull(receivedSample[0]); + assertEquals(TEST_PAYLOAD, receivedSample[0].getPayload()); + } +} diff --git a/zenoh-java/src/jvmTest/java/io/zenoh/QueryableTest.java b/zenoh-java/src/jvmTest/java/io/zenoh/QueryableTest.java new file mode 100644 index 00000000..d56501e6 --- /dev/null +++ b/zenoh-java/src/jvmTest/java/io/zenoh/QueryableTest.java @@ -0,0 +1,267 @@ +package io.zenoh; + +import io.zenoh.bytes.Encoding; +import io.zenoh.bytes.ZBytes; +import io.zenoh.exceptions.ZError; +import io.zenoh.handlers.Handler; +import io.zenoh.keyexpr.KeyExpr; +import io.zenoh.query.*; +import io.zenoh.qos.CongestionControl; +import io.zenoh.qos.Priority; +import io.zenoh.qos.QoS; +import io.zenoh.sample.Sample; +import io.zenoh.sample.SampleKind; +import org.apache.commons.net.ntp.TimeStamp; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +import java.time.Instant; +import java.util.ArrayList; +import java.util.Date; + +import static org.junit.Assert.*; + +@RunWith(JUnit4.class) +public class QueryableTest { + + private static final ZBytes testPayload = ZBytes.from("Hello queryable"); + private Session session; + private KeyExpr testKeyExpr; + + @Before + public void setUp() throws ZError { + session = Zenoh.open(Config.loadDefault()); + testKeyExpr = KeyExpr.tryFrom("example/testing/keyexpr"); + } + + @After + public void tearDown() throws ZError { + session.close(); + testKeyExpr.close(); + } + + @Test + public void queryableRunsWithCallback() throws ZError { + var timestamp = new TimeStamp(Date.from(Instant.now())); + + var sample = new Sample( + testKeyExpr, + testPayload, + Encoding.defaultEncoding(), + SampleKind.PUT, + timestamp, + QoS.defaultQoS(), + null + ); + + var queryable = session.declareQueryable(testKeyExpr, query -> + { + try { + var options = new ReplyOptions(); + options.setTimeStamp(timestamp); + query.reply(testKeyExpr, testPayload, options); + } catch (ZError e) { + throw new RuntimeException(e); + } + }); + + Reply[] reply = new Reply[1]; + session.get(testKeyExpr.into(), reply1 -> reply[0] = reply1); + + assertNotNull(reply[0]); + Sample receivedSample = ((Reply.Success) reply[0]).getSample(); + assertEquals(sample, receivedSample); + queryable.close(); + } + + @Test + public void queryableRunsWithHandler() throws ZError, InterruptedException { + var queryable = session.declareQueryable(testKeyExpr, new QueryHandler()); + + Thread.sleep(500); + + Reply[] reply = new Reply[1]; + session.get(testKeyExpr.into(), reply1 -> reply[0] = reply1, new GetOptions()); + + Thread.sleep(500); + + queryable.close(); + assertTrue(reply[0] instanceof Reply.Success); + } + + @Test + public void queryTest() throws ZError, InterruptedException { + Query[] receivedQuery = new Query[1]; + var queryable = session.declareQueryable(testKeyExpr, query -> receivedQuery[0] = query); + + session.get(testKeyExpr); + + Thread.sleep(100); + + Query query = receivedQuery[0]; + assertNotNull(query); + assertNull(query.getPayload()); + assertNull(query.getEncoding()); + assertNull(query.getAttachment()); + + receivedQuery[0] = null; + var payload = ZBytes.from("Test value"); + var attachment = ZBytes.from("Attachment"); + + var getOptions = new GetOptions(); + getOptions.setAttachment(attachment); + getOptions.setPayload(payload); + getOptions.setEncoding(Encoding.ZENOH_STRING); + getOptions.setAttachment(attachment); + session.get(testKeyExpr, getOptions); + + Thread.sleep(100); + + query = receivedQuery[0]; + assertNotNull(query); + assertEquals(payload, query.getPayload()); + assertEquals(Encoding.ZENOH_STRING, query.getEncoding()); + assertEquals(attachment, query.getAttachment()); + + queryable.close(); + } + + @Test + public void queryReplySuccessTest() throws ZError { + var message = ZBytes.from("Test message"); + var timestamp = TimeStamp.getCurrentTime(); + + Queryable queryable = session.declareQueryable(testKeyExpr, query -> { + var options = new ReplyOptions(); + options.setTimeStamp(timestamp); + options.setPriority(Priority.DATA_HIGH); + options.setCongestionControl(CongestionControl.DROP); + options.setExpress(true); + try { + query.reply(testKeyExpr, message, options); + } catch (ZError e) { + throw new RuntimeException(e); + } + }); + + Reply[] receivedReply = new Reply[1]; + session.get(testKeyExpr, reply -> receivedReply[0] = reply); + + queryable.close(); + + assertNotNull(receivedReply[0]); + assertTrue(receivedReply[0] instanceof Reply.Success); + + var sample = ((Reply.Success) receivedReply[0]).getSample(); + assertEquals(message, sample.getPayload()); + assertEquals(timestamp, sample.getTimestamp()); + assertEquals(Priority.DATA_HIGH, sample.getQos().getPriority()); + assertTrue(sample.getQos().getExpress()); + assertEquals(CongestionControl.DROP, sample.getQos().getCongestionControl()); + } + + @Test + public void queryReplyErrorTest() throws ZError, InterruptedException { + var errorMessage = ZBytes.from("Error message"); + + var queryable = session.declareQueryable(testKeyExpr, query -> + { + try { + query.replyErr(errorMessage); + } catch (ZError e) { + throw new RuntimeException(e); + } + } + ); + + Reply[] receivedReply = new Reply[1]; + session.get(testKeyExpr, reply -> receivedReply[0] = reply); + + Thread.sleep(1000); + queryable.close(); + + assertNotNull(receivedReply[0]); + assertTrue(receivedReply[0] instanceof Reply.Error); + + var errorReply = (Reply.Error) receivedReply[0]; + assertEquals(errorMessage, errorReply.getError()); + } + + @Test + public void queryReplyDeleteTest() throws ZError, InterruptedException { + var timestamp = TimeStamp.getCurrentTime(); + + var queryable = session.declareQueryable(testKeyExpr, query -> { + try { + var config = new ReplyDelOptions(); + config.setTimeStamp(timestamp); + query.replyDel(testKeyExpr, config); + } catch (ZError e) { + throw new RuntimeException(e); + } + }); + + Reply[] receivedReply = new Reply[1]; + session.get(testKeyExpr, reply -> receivedReply[0] = reply); + + Thread.sleep(1000); + queryable.close(); + + assertNotNull(receivedReply[0]); + assertTrue(receivedReply[0] instanceof Reply.Success); + + var sample = ((Reply.Success) receivedReply[0]).getSample(); + assertEquals(SampleKind.DELETE, sample.getKind()); + assertEquals(timestamp, sample.getTimestamp()); + } +} + +class QueryHandler implements Handler { + + private int counter = 0; + private final ArrayList performedReplies = new ArrayList<>(); + + public ArrayList getPerformedReplies() { + return performedReplies; + } + + @Override + public void handle(Query query) { + try { + reply(query); + } catch (ZError e) { + throw new RuntimeException(e); + } + } + + @Override + public QueryHandler receiver() { + return this; + } + + @Override + public void onClose() { + // No action needed on close + } + + public void reply(Query query) throws ZError { + ZBytes payload = ZBytes.from("Hello queryable " + counter + "!"); + counter++; + Sample sample = new Sample( + query.getKeyExpr(), + payload, + Encoding.defaultEncoding(), + SampleKind.PUT, + new TimeStamp(Date.from(Instant.now())), + new QoS(), + null + ); + performedReplies.add(sample); + var config = new ReplyOptions(); + config.setTimeStamp(sample.getTimestamp()); + query.reply(query.getKeyExpr(), payload, config); + } +} diff --git a/zenoh-java/src/jvmTest/java/io/zenoh/ScoutTest.java b/zenoh-java/src/jvmTest/java/io/zenoh/ScoutTest.java new file mode 100644 index 00000000..80530b1f --- /dev/null +++ b/zenoh-java/src/jvmTest/java/io/zenoh/ScoutTest.java @@ -0,0 +1,90 @@ +// +// Copyright (c) 2023 ZettaScale Technology +// +// This program and the accompanying materials are made available under the +// terms of the Eclipse Public License 2.0 which is available at +// http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 +// which is available at https://www.apache.org/licenses/LICENSE-2.0. +// +// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 +// +// Contributors: +// ZettaScale Zenoh Team, +// + +package io.zenoh; + +import io.zenoh.config.WhatAmI; +import io.zenoh.exceptions.ZError; +import io.zenoh.scouting.Hello; +import io.zenoh.scouting.Scout; +import io.zenoh.scouting.ScoutOptions; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +import java.util.ArrayList; +import java.util.Optional; +import java.util.Set; +import java.util.concurrent.BlockingQueue; + +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +@RunWith(JUnit4.class) +public class ScoutTest { + + @Test + public void scouting_queueTest() throws ZError, InterruptedException { + Session session = Zenoh.open(Config.loadDefault()); + + Thread.sleep(1000); + + var scout = Zenoh.scout(); + + Thread.sleep(1000); + scout.close(); + + ArrayList> helloList = new ArrayList<>(); + scout.getReceiver().drainTo(helloList); + + assertTrue(helloList.size() > 1); + for (int i = 0; i < helloList.size() - 1; i++) { + assertTrue(helloList.get(i).isPresent()); + } + assertTrue(helloList.get(helloList.size() - 1).isEmpty()); + session.close(); + } + + @Test + public void scouting_callbackTest() throws ZError, InterruptedException { + Session session = Zenoh.open(Config.loadDefault()); + + Hello[] hello = new Hello[1]; + Zenoh.scout(hello1 -> hello[0] = hello1); + + Thread.sleep(1000); + + assertNotNull(hello[0]); + session.close(); + } + + @Test + public void scouting_whatAmITest() throws ZError { + var scoutOptions = new ScoutOptions(); + scoutOptions.setWhatAmI(Set.of(WhatAmI.Client, WhatAmI.Peer)); + var scout = Zenoh.scout(scoutOptions); + scout.close(); + } + + @Test + public void scouting_onCloseTest() throws ZError { + var scout = Zenoh.scout(); + var receiver = scout.getReceiver(); + + scout.close(); + var element = receiver.poll(); + assertNotNull(element); + assertTrue(element.isEmpty()); + } +} diff --git a/zenoh-java/src/jvmTest/java/io/zenoh/SelectorTest.java b/zenoh-java/src/jvmTest/java/io/zenoh/SelectorTest.java new file mode 100644 index 00000000..9252dd3f --- /dev/null +++ b/zenoh-java/src/jvmTest/java/io/zenoh/SelectorTest.java @@ -0,0 +1,50 @@ +// +// Copyright (c) 2023 ZettaScale Technology +// +// This program and the accompanying materials are made available under the +// terms of the Eclipse Public License 2.0 which is available at +// http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 +// which is available at https://www.apache.org/licenses/LICENSE-2.0. +// +// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 +// +// Contributors: +// ZettaScale Zenoh Team, +// + +package io.zenoh; + +import io.zenoh.exceptions.ZError; +import io.zenoh.query.Parameters; +import io.zenoh.query.Selector; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +import java.util.List; + +import static org.junit.Assert.*; + +@RunWith(JUnit4.class) +public class SelectorTest { + + @Test + public void selector_fromStringTest() throws ZError { + var selector = Selector.tryFrom("a/b/c?arg1=val1"); + assertEquals("a/b/c", selector.getKeyExpr().toString()); + assertNotNull(selector.getParameters()); + assertEquals("arg1=val1", selector.getParameters().toString()); + + var selector2 = Selector.tryFrom("a/b/c"); + assertEquals("a/b/c", selector2.getKeyExpr().toString()); + assertNull(selector2.getParameters()); + + assertThrows(ZError.class, () -> Selector.tryFrom("")); + } + + @Test + public void parametersTest() { + var parameters = Parameters.from("a=1;b=2;c=1|2|3"); + assertEquals(List.of("1", "2", "3"), parameters.values("c")); + } +} diff --git a/zenoh-java/src/jvmTest/java/io/zenoh/SessionInfoTest.java b/zenoh-java/src/jvmTest/java/io/zenoh/SessionInfoTest.java new file mode 100644 index 00000000..7a3c9a89 --- /dev/null +++ b/zenoh-java/src/jvmTest/java/io/zenoh/SessionInfoTest.java @@ -0,0 +1,111 @@ +// +// Copyright (c) 2023 ZettaScale Technology +// +// This program and the accompanying materials are made available under the +// terms of the Eclipse Public License 2.0 which is available at +// http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 +// which is available at https://www.apache.org/licenses/LICENSE-2.0. +// +// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 +// +// Contributors: +// ZettaScale Zenoh Team, +// + +package io.zenoh; + +import io.zenoh.config.ZenohId; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +@RunWith(JUnit4.class) +public class SessionInfoTest { + + @Test + public void peersZidTest() throws Exception { + String jsonConfig = "{\n" + + " mode: \"peer\",\n" + + " connect: {\n" + + " endpoints: [\"tcp/localhost:7450\"]\n" + + " }\n" + + "}"; + + Config listenConfig = Config.fromJson("{\n" + + " mode: \"peer\",\n" + + " listen: {\n" + + " endpoints: [\"tcp/localhost:7450\"]\n" + + " }\n" + + "}"); + + Session sessionC = Zenoh.open(listenConfig); + Session sessionA = Zenoh.open(Config.fromJson(jsonConfig)); + Session sessionB = Zenoh.open(Config.fromJson(jsonConfig)); + + ZenohId idA = sessionA.info().zid(); + ZenohId idB = sessionB.info().zid(); + var peers = sessionC.info().peersZid(); + assertTrue(peers.contains(idA)); + assertTrue(peers.contains(idB)); + + sessionA.close(); + sessionB.close(); + sessionC.close(); + } + + @Test + public void routersZidTest() throws Exception { + Session session = Zenoh.open(Config.fromJson("{\n" + + " mode: \"router\",\n" + + " listen: {\n" + + " endpoints: [\"tcp/localhost:7450\"]\n" + + " }\n" + + "}")); + + Session connectedRouterA = Zenoh.open(Config.fromJson("{\n" + + " mode: \"router\",\n" + + " connect: {\n" + + " endpoints: [\"tcp/localhost:7450\"]\n" + + " },\n" + + " listen: {\n" + + " endpoints: [\"tcp/localhost:7451\"]\n" + + " }\n" + + "}")); + + Session connectedRouterB = Zenoh.open(Config.fromJson("{\n" + + " mode: \"router\",\n" + + " connect: {\n" + + " endpoints: [\"tcp/localhost:7450\"]\n" + + " },\n" + + " listen: {\n" + + " endpoints: [\"tcp/localhost:7452\"]\n" + + " }\n" + + "}")); + + ZenohId idA = connectedRouterA.info().zid(); + ZenohId idB = connectedRouterB.info().zid(); + + var routers = session.info().routersZid(); + + assertTrue(routers.contains(idA)); + assertTrue(routers.contains(idB)); + + connectedRouterA.close(); + connectedRouterB.close(); + session.close(); + } + + @Test + public void zidTest() throws Exception { + String jsonConfig = "{\n" + + " id: \"123456\"\n" + + "}"; + + Session session = Zenoh.open(Config.fromJson(jsonConfig)); + assertEquals("123456", session.info().zid().toString()); + session.close(); + } +} diff --git a/zenoh-java/src/jvmTest/java/io/zenoh/SessionTest.java b/zenoh-java/src/jvmTest/java/io/zenoh/SessionTest.java new file mode 100644 index 00000000..18e3b855 --- /dev/null +++ b/zenoh-java/src/jvmTest/java/io/zenoh/SessionTest.java @@ -0,0 +1,72 @@ +// +// Copyright (c) 2023 ZettaScale Technology +// +// This program and the accompanying materials are made available under the +// terms of the Eclipse Public License 2.0 which is available at +// http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 +// which is available at https://www.apache.org/licenses/LICENSE-2.0. +// +// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 +// +// Contributors: +// ZettaScale Zenoh Team, +// + +package io.zenoh; + +import io.zenoh.bytes.ZBytes; +import io.zenoh.exceptions.ZError; +import io.zenoh.keyexpr.KeyExpr; +import io.zenoh.pubsub.Publisher; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +import static org.junit.Assert.*; + +@RunWith(JUnit4.class) +public class SessionTest { + + private static final KeyExpr testKeyExpr; + + static { + try { + testKeyExpr = KeyExpr.tryFrom("example/testing/keyexpr"); + } catch (ZError e) { + throw new RuntimeException(e); + } + } + + @Test + public void sessionStartCloseTest() throws ZError { + Session session = Zenoh.open(Config.loadDefault()); + assertFalse(session.isClosed()); + session.close(); + assertTrue(session.isClosed()); + } + + @Test + public void sessionClose_declarationsAreUndeclaredAfterClosingSessionTest() throws ZError, InterruptedException { + Session session = Zenoh.open(Config.loadDefault()); + + Publisher publisher = session.declarePublisher(testKeyExpr); + var subscriber = session.declareSubscriber(testKeyExpr); + session.close(); + + Thread.sleep(1000); + + assertFalse(subscriber.isValid()); + assertFalse(publisher.isValid()); + + assertThrows(ZError.class, () -> publisher.put(ZBytes.from("Test"))); + } + + @Test + public void sessionClose_newDeclarationsReturnNullAfterClosingSession() throws ZError { + Session session = Zenoh.open(Config.loadDefault()); + session.close(); + assertThrows(ZError.class, () -> session.declarePublisher(testKeyExpr)); + assertThrows(ZError.class, () -> session.declareQueryable(testKeyExpr)); + assertThrows(ZError.class, () -> session.declareSubscriber(testKeyExpr)); + } +} diff --git a/zenoh-java/src/jvmTest/java/io/zenoh/SubscriberTest.java b/zenoh-java/src/jvmTest/java/io/zenoh/SubscriberTest.java new file mode 100644 index 00000000..809c4bc0 --- /dev/null +++ b/zenoh-java/src/jvmTest/java/io/zenoh/SubscriberTest.java @@ -0,0 +1,155 @@ +// +// Copyright (c) 2023 ZettaScale Technology +// +// This program and the accompanying materials are made available under the +// terms of the Eclipse Public License 2.0 which is available at +// http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 +// which is available at https://www.apache.org/licenses/LICENSE-2.0. +// +// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 +// +// Contributors: +// ZettaScale Zenoh Team, +// + +package io.zenoh; + +import io.zenoh.bytes.Encoding; +import io.zenoh.bytes.ZBytes; +import io.zenoh.exceptions.ZError; +import io.zenoh.handlers.Handler; +import io.zenoh.keyexpr.KeyExpr; +import io.zenoh.pubsub.PutOptions; +import io.zenoh.qos.CongestionControl; +import io.zenoh.qos.Priority; +import io.zenoh.sample.Sample; +import kotlin.Pair; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +import java.util.ArrayDeque; +import java.util.ArrayList; + +import static org.junit.Assert.assertEquals; + +@RunWith(JUnit4.class) +public class SubscriberTest { + + private static final Priority TEST_PRIORITY = Priority.DATA_HIGH; + private static final CongestionControl TEST_CONGESTION_CONTROL = CongestionControl.BLOCK; + private static final ArrayList> TEST_VALUES = new ArrayList<>(); + private static final KeyExpr testKeyExpr; + + static { + TEST_VALUES.add(new Pair<>(ZBytes.from("Test 1"), Encoding.TEXT_PLAIN)); + TEST_VALUES.add(new Pair<>(ZBytes.from("Test 2"), Encoding.TEXT_JSON)); + TEST_VALUES.add(new Pair<>(ZBytes.from("Test 3"), Encoding.TEXT_CSV)); + try { + testKeyExpr = KeyExpr.tryFrom("example/testing/keyexpr"); + } catch (ZError e) { + throw new RuntimeException(e); + } + } + + private Session session = null; + + @Before + public void setUp() throws ZError { + session = Zenoh.open(Config.loadDefault()); + } + + @After + public void tearDown() { + session.close(); + } + + @Test + public void subscriber_runsWithCallback() throws ZError { + var receivedSamples = new ArrayList(); + + var subscriber = + session.declareSubscriber(testKeyExpr, receivedSamples::add); + + TEST_VALUES.forEach(value -> { + try { + var putOptions = new PutOptions(); + putOptions.setEncoding(value.getSecond()); + putOptions.setCongestionControl(TEST_CONGESTION_CONTROL); + putOptions.setPriority(TEST_PRIORITY); + session.put(testKeyExpr, value.getFirst(), putOptions); + } catch (ZError e) { + throw new RuntimeException(e); + } + } + ); + assertEquals(receivedSamples.size(), TEST_VALUES.size()); + + for (int i = 0; i < TEST_VALUES.size(); i++) { + var valueSent = TEST_VALUES.get(i); + var valueRecv = receivedSamples.get(i); + + assertEquals(valueRecv.getPayload(), valueSent.getFirst()); + assertEquals(valueRecv.getEncoding(), valueSent.getSecond()); + assertEquals(valueRecv.getPriority(), TEST_PRIORITY); + assertEquals(valueRecv.getCongestionControl(), TEST_CONGESTION_CONTROL); + } + + subscriber.close(); + } + + @Test + public void subscriber_runsWithHandler() throws ZError { + var handler = new QueueHandler(); + var subscriber = + session.declareSubscriber(testKeyExpr, handler); + + TEST_VALUES.forEach(value -> { + try { + var putOptions = new PutOptions(); + putOptions.setEncoding(value.getSecond()); + putOptions.setCongestionControl(TEST_CONGESTION_CONTROL); + putOptions.setPriority(TEST_PRIORITY); + + session.put(testKeyExpr, value.getFirst(), putOptions); + } catch (ZError e) { + throw new RuntimeException(e); + } + } + ); + assertEquals(handler.queue.size(), TEST_VALUES.size()); + + for (int i = 0; i < TEST_VALUES.size(); i++) { + var valueSent = TEST_VALUES.get(i); + var valueRecv = handler.queue.poll(); + + assert valueRecv != null; + assertEquals(valueRecv.getPayload(), valueSent.getFirst()); + assertEquals(valueRecv.getEncoding(), valueSent.getSecond()); + assertEquals(valueRecv.getPriority(), TEST_PRIORITY); + assertEquals(valueRecv.getCongestionControl(), TEST_CONGESTION_CONTROL); + } + + subscriber.close(); + } +} + +class QueueHandler implements Handler> { + + final ArrayDeque queue = new ArrayDeque<>(); + + @Override + public void handle(T t) { + queue.add(t); + } + + @Override + public ArrayDeque receiver() { + return queue; + } + + @Override + public void onClose() {} +} diff --git a/zenoh-java/src/jvmTest/java/io/zenoh/UserAttachmentTest.java b/zenoh-java/src/jvmTest/java/io/zenoh/UserAttachmentTest.java new file mode 100644 index 00000000..1f0a4d2f --- /dev/null +++ b/zenoh-java/src/jvmTest/java/io/zenoh/UserAttachmentTest.java @@ -0,0 +1,219 @@ +// +// Copyright (c) 2023 ZettaScale Technology +// +// This program and the accompanying materials are made available under the +// terms of the Eclipse Public License 2.0 which is available at +// http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 +// which is available at https://www.apache.org/licenses/LICENSE-2.0. +// +// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 +// +// Contributors: +// ZettaScale Zenoh Team, +// + +package io.zenoh; + +import io.zenoh.exceptions.ZError; +import io.zenoh.keyexpr.KeyExpr; +import io.zenoh.bytes.ZBytes; +import io.zenoh.pubsub.DeleteOptions; +import io.zenoh.pubsub.PutOptions; +import io.zenoh.pubsub.Subscriber; +import io.zenoh.query.GetOptions; +import io.zenoh.query.Reply; +import io.zenoh.query.ReplyOptions; +import io.zenoh.sample.Sample; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +import java.time.Duration; + +import static org.junit.Assert.*; + +@RunWith(JUnit4.class) +public class UserAttachmentTest { + + static final KeyExpr keyExpr; + static final ZBytes payload = ZBytes.from("test payload"); + static final ZBytes attachment = ZBytes.from("mock_attachment"); + static { + try { + keyExpr = KeyExpr.tryFrom("example/testing/attachment"); + } catch (ZError e) { + throw new RuntimeException(e); + } + } + + Session session; + + @Before + public void setup() throws ZError { + session = Zenoh.open(Config.loadDefault()); + } + + @After + public void tearDown() { + session.close(); + } + + @Test + public void putWithAttachmentTest() throws ZError { + Sample[] receivedSample = new Sample[1]; + Subscriber subscriber = + session.declareSubscriber(keyExpr, sample -> receivedSample[0] = sample); + + var putOptions = new PutOptions(); + putOptions.setAttachment(attachment); + session.put(keyExpr, payload, putOptions); + + subscriber.close(); + + assertNotNull(receivedSample[0]); + ZBytes receivedAttachment = receivedSample[0].getAttachment(); + assertEquals(attachment, receivedAttachment); + } + + @Test + public void publisherPutWithAttachmentTest() throws ZError { + Sample[] receivedSample = new Sample[1]; + var publisher = session.declarePublisher(keyExpr); + Subscriber subscriber = + session.declareSubscriber(keyExpr, sample -> receivedSample[0] = sample); + + var putOptions = new PutOptions(); + putOptions.setAttachment(attachment); + publisher.put(payload, putOptions); + + publisher.close(); + subscriber.close(); + + assertNotNull(receivedSample[0]); + ZBytes receivedAttachment = receivedSample[0].getAttachment(); + assertEquals(attachment, receivedAttachment); + } + + @Test + public void publisherPutWithoutAttachmentTest() throws ZError { + Sample[] receivedSample = new Sample[1]; + var publisher = session.declarePublisher(keyExpr); + Subscriber subscriber = + session.declareSubscriber(keyExpr, sample -> receivedSample[0] = sample); + + publisher.put(payload); + + publisher.close(); + subscriber.close(); + + assertNotNull(receivedSample[0]); + assertNull(receivedSample[0].getAttachment()); + } + + @Test + public void publisherDeleteWithAttachmentTest() throws ZError { + Sample[] receivedSample = new Sample[1]; + var publisher = session.declarePublisher(keyExpr); + Subscriber subscriber = + session.declareSubscriber(keyExpr, sample -> receivedSample[0] = sample); + + var deleteOptions = new DeleteOptions(); + deleteOptions.setAttachment(attachment); + publisher.delete(deleteOptions); + + publisher.close(); + subscriber.close(); + + assertNotNull(receivedSample[0]); + ZBytes receivedAttachment = receivedSample[0].getAttachment(); + assertEquals(attachment, receivedAttachment); + } + + @Test + public void publisherDeleteWithoutAttachmentTest() throws ZError { + Sample[] receivedSample = new Sample[1]; + var publisher = session.declarePublisher(keyExpr); + Subscriber subscriber = + session.declareSubscriber(keyExpr, sample -> receivedSample[0] = sample); + + publisher.delete(); + + publisher.close(); + subscriber.close(); + + assertNotNull(receivedSample[0]); + assertNull(receivedSample[0].getAttachment()); + } + + @Test + public void queryWithAttachmentTest() throws ZError { + ZBytes[] receivedAttachment = new ZBytes[1]; + var queryable = session.declareQueryable(keyExpr, query -> { + receivedAttachment[0] = query.getAttachment(); + try { + query.reply(keyExpr, payload); + } catch (ZError e) { + throw new RuntimeException(e); + } + }); + + var getOptions = new GetOptions(); + getOptions.setTimeout(Duration.ofMillis(1000)); + getOptions.setAttachment(attachment); + session.get(keyExpr, getOptions); + + queryable.close(); + + assertNotNull(receivedAttachment[0]); + assertEquals(attachment, receivedAttachment[0]); + } + + @Test + public void queryReplyWithAttachmentTest() throws ZError { + Reply[] reply = new Reply[1]; + var queryable = session.declareQueryable(keyExpr, query -> { + try { + var options = new ReplyOptions(); + options.setAttachment(attachment); + query.reply(keyExpr, payload, options); + } catch (ZError e) { + throw new RuntimeException(e); + } + }); + + + var getOptions = new GetOptions(); + getOptions.setTimeout(Duration.ofMillis(1000)); + getOptions.setAttachment(attachment); + session.get(keyExpr, reply1 -> reply[0] = reply1, getOptions); + + queryable.close(); + + Reply receivedReply = reply[0]; + assertNotNull(receivedReply); + ZBytes receivedAttachment = ((Reply.Success) receivedReply).getSample().getAttachment(); + assertEquals(attachment, receivedAttachment); + } + + @Test + public void queryReplyWithoutAttachmentTest() throws ZError { + Reply[] reply = new Reply[1]; + var queryable = session.declareQueryable(keyExpr, query -> { + try { + query.reply(keyExpr, payload); + } catch (ZError e) { + throw new RuntimeException(e); + } + }); + session.get(keyExpr, reply1 -> reply[0] = reply1); + + queryable.close(); + + Reply receivedReply = reply[0]; + assertNotNull(receivedReply); + ZBytes receivedAttachment = ((Reply.Success) receivedReply).getSample().getAttachment(); + assertNull(receivedAttachment); + } +} diff --git a/zenoh-java/src/jvmTest/java/io/zenoh/ZBytesTests.java b/zenoh-java/src/jvmTest/java/io/zenoh/ZBytesTests.java new file mode 100644 index 00000000..8bf98c49 --- /dev/null +++ b/zenoh-java/src/jvmTest/java/io/zenoh/ZBytesTests.java @@ -0,0 +1,131 @@ +package io.zenoh; + +import com.google.common.reflect.TypeToken; +import io.zenoh.ext.ZDeserializerKt; +import io.zenoh.ext.ZSerializerKt; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +@RunWith(JUnit4.class) +public class ZBytesTests { + + /*********************************************** + * Standard serialization and deserialization. * + ***********************************************/ + + @Test + public void testIntSerializationAndDeserialization() { + int intInput = 1234; + var payload = ZSerializerKt.zSerialize(intInput, new TypeToken<>() {}); + int intOutput = ZDeserializerKt.zDeserialize(payload, new TypeToken<>() {}); + assertEquals(intInput, intOutput); + } + + @Test + public void testFloatSerializationAndDeserialization() { + float floatInput = 3.1415f; + var payload = ZSerializerKt.zSerialize(floatInput, new TypeToken<>() {}); + float floatOutput = ZDeserializerKt.zDeserialize(payload, new TypeToken<>() {}); + assertEquals(floatInput, floatOutput, 0.0001); + } + + @Test + public void testStringSerializationAndDeserialization() { + String stringInput = "example"; + var payload = ZSerializerKt.zSerialize(stringInput, new TypeToken<>() {}); + String stringOutput = ZDeserializerKt.zDeserialize(payload, new TypeToken<>() {}); + assertEquals(stringInput, stringOutput); + } + + @Test + public void testByteArraySerializationAndDeserialization() { + byte[] byteArrayInput = "example".getBytes(); + var payload = ZSerializerKt.zSerialize(byteArrayInput, new TypeToken<>() {}); + byte[] byteArrayOutput = ZDeserializerKt.zDeserialize(payload, new TypeToken<>() {}); + assertTrue(Arrays.equals(byteArrayInput, byteArrayOutput)); + } + + @Test + public void testListOfStringsSerializationAndDeserialization() { + List inputList = List.of("sample1", "sample2", "sample3"); + var payload = ZSerializerKt.zSerialize(inputList, new TypeToken<>() {}); + List outputList = ZDeserializerKt.zDeserialize(payload, new TypeToken<>() {}); + assertEquals(inputList, outputList); + } + + @Test + public void testListOfByteArraysSerializationAndDeserialization() { + List inputList = Stream.of("sample1", "sample2", "sample3") + .map(String::getBytes) + .collect(Collectors.toList()); + var payload = ZSerializerKt.zSerialize(inputList, new TypeToken<>() {}); + List outputList = ZDeserializerKt.zDeserialize(payload, new TypeToken<>() {}); + assertTrue(compareByteArrayLists(inputList, outputList)); + } + + @Test + public void testMapOfStringsSerializationAndDeserialization() { + Map inputMap = Map.of("key1", "value1", "key2", "value2", "key3", "value3"); + var payload = ZSerializerKt.zSerialize(inputMap, new TypeToken<>() {}); + Map outputMap = ZDeserializerKt.zDeserialize(payload, new TypeToken<>() {}); + assertEquals(inputMap, outputMap); + } + + /********************************************** + * Additional test cases for new Kotlin types * + **********************************************/ + + @Test + public void testBooleanSerializationAndDeserialization() { + boolean booleanInput = true; + var payload = ZSerializerKt.zSerialize(booleanInput, new TypeToken<>() {}); + boolean booleanOutput = ZDeserializerKt.zDeserialize(payload, new TypeToken<>() {}); + assertEquals(booleanInput, booleanOutput); + } + + /********************************************** + * Tests for collections with new types * + **********************************************/ + + @Test + public void testListOfBooleansSerializationAndDeserialization() { + List listBooleanInput = List.of(true, false, true); + var payload = ZSerializerKt.zSerialize(listBooleanInput, new TypeToken<>() {}); + List listBooleanOutput = ZDeserializerKt.zDeserialize(payload, new TypeToken<>() {}); + assertEquals(listBooleanInput, listBooleanOutput); + } + + @Test + public void testMapOfStringToListOfIntSerializationAndDeserialization() { + Map> mapOfListInput = Map.of("numbers", List.of(1, 2, 3, 4, 5)); + var payload = ZSerializerKt.zSerialize(mapOfListInput, new TypeToken<>() {}); + Map> mapOfListOutput = ZDeserializerKt.zDeserialize(payload, new TypeToken<>() {}); + assertEquals(mapOfListInput, mapOfListOutput); + } + + /***************** + * Testing utils * + *****************/ + + private boolean compareByteArrayLists(List list1, List list2) { + if (list1.size() != list2.size()) { + return false; + } + for (int i = 0; i < list1.size(); i++) { + if (!Arrays.equals(list1.get(i), list2.get(i))) { + return false; + } + } + return true; + } +} diff --git a/zenoh-jni/Cargo.lock b/zenoh-jni/Cargo.lock index 068de54a..e9937b70 100644 --- a/zenoh-jni/Cargo.lock +++ b/zenoh-jni/Cargo.lock @@ -4,18 +4,18 @@ version = 3 [[package]] name = "addr2line" -version = "0.21.0" +version = "0.24.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a30b2e23b9e17a9f90641c7ab1549cd9b44f296d3ccbf309d2863cfe398a0cb" +checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1" dependencies = [ "gimli", ] [[package]] -name = "adler" -version = "1.0.2" +name = "adler2" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" +checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" [[package]] name = "aes" @@ -43,24 +43,24 @@ dependencies = [ [[package]] name = "aho-corasick" -version = "1.0.5" +version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c378d78423fdad8089616f827526ee33c19f2fddbd5de1629152c9593ba4783" +checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" dependencies = [ "memchr", ] [[package]] name = "allocator-api2" -version = "0.2.16" +version = "0.2.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0942ffc6dcaadf03badf6e6a2d0228460359d5e34b57ccdc720b7382dfbd5ec5" +checksum = "45862d1c77f2228b9e10bc609d5bc203d86ebc9b87ad8d5d5167a6c9abf739d9" [[package]] name = "android-logd-logger" -version = "0.4.3" +version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09fe8042a3174caeafdad8ee1337788db51833e7f8649c07c6d6de70048adef4" +checksum = "0483169d5fac0887f85c2fa8fecfe08669791712d8260de1a6ec30630a62932f" dependencies = [ "bytes", "env_logger", @@ -69,16 +69,16 @@ dependencies = [ "log", "parking_lot", "redox_syscall 0.4.1", - "thiserror", + "thiserror 1.0.69", "time", "winapi", ] [[package]] name = "anyhow" -version = "1.0.86" +version = "1.0.93" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3d1d046238990b9cf5bcde22a3fb3584ee5cf65fb2765f454ed428c7a0063da" +checksum = "4c95c10ba0b00a02636238b814946408b1322d5ac4760326e6fb8ec956d85775" [[package]] name = "array-init" @@ -88,9 +88,9 @@ checksum = "3d62b7694a562cdf5a74227903507c56ab2cc8bdd1f781ed5cb4cf9c9f810bfc" [[package]] name = "asn1-rs" -version = "0.6.1" +version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22ad1373757efa0f70ec53939aabc7152e1591cb485208052993070ac8d2429d" +checksum = "5493c3bedbacf7fd7382c6346bbd66687d12bbaad3a89a2d2c303ee6cf20b048" dependencies = [ "asn1-rs-derive", "asn1-rs-impl", @@ -98,19 +98,19 @@ dependencies = [ "nom", "num-traits", "rusticata-macros", - "thiserror", + "thiserror 1.0.69", "time", ] [[package]] name = "asn1-rs-derive" -version = "0.5.0" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7378575ff571966e99a744addeff0bff98b8ada0dedf1956d59e634db95eaac1" +checksum = "965c2d33e53cb6b267e148a4cb0760bc01f4904c1cd4bb4002a085bb016d1490" dependencies = [ "proc-macro2", "quote", - "syn 2.0.52", + "syn 2.0.89", "synstructure", ] @@ -122,7 +122,7 @@ checksum = "7b18050c2cd6fe86c3a76584ef5e0baf286d038cda203eb6223df2cc413565f7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.52", + "syn 2.0.89", ] [[package]] @@ -133,13 +133,13 @@ checksum = "62565bb4402e926b29953c785397c6dc0391b7b446e45008b0049eb43cec6f5d" [[package]] name = "async-trait" -version = "0.1.81" +version = "0.1.83" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e0c28dcc82d7c8ead5cb13beb15405b57b8546e93215673ff8ca0349a028107" +checksum = "721cae7de5c34fbb2acd27e21e6d2cf7b886dce0c27388d46c4e6c47ea4318dd" dependencies = [ "proc-macro2", "quote", - "syn 2.0.52", + "syn 2.0.89", ] [[package]] @@ -155,30 +155,30 @@ dependencies = [ [[package]] name = "autocfg" -version = "1.1.0" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" +checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" [[package]] name = "backtrace" -version = "0.3.69" +version = "0.3.74" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2089b7e3f35b9dd2d0ed921ead4f6d318c27680d4a5bd167b3ee120edb105837" +checksum = "8d82cb332cdfaed17ae235a638438ac4d4839913cc2af585c3c6746e8f8bee1a" dependencies = [ "addr2line", - "cc", "cfg-if", "libc", "miniz_oxide", "object", "rustc-demangle", + "windows-targets 0.52.6", ] [[package]] name = "base64" -version = "0.21.4" +version = "0.21.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ba43ea6f343b788c8764558649e08df62f86c6ef251fdaeb1ffd010a9ae50a2" +checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" [[package]] name = "base64" @@ -209,9 +209,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.5.0" +version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf4b9d6a944f767f8e5e0db018570623c85f3d925ac718db4e06d0187adb21c1" +checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" dependencies = [ "serde", ] @@ -227,21 +227,21 @@ dependencies = [ [[package]] name = "bumpalo" -version = "3.14.0" +version = "3.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f30e7476521f6f8af1a1c4c0b8cc94f0bee37d91763d0ca2665f299b6cd8aec" +checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" [[package]] name = "byteorder" -version = "1.4.3" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "bytes" -version = "1.7.1" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8318a53db07bb3f8dca91a600466bdb3f2eaadeedfdbcf02e1accbad9271ba50" +checksum = "325918d6fe32f23b19878fe4b34794ae41fc19ddbe53b10571a4874d44ffd39b" [[package]] name = "cache-padded" @@ -251,11 +251,11 @@ checksum = "981520c98f422fcc584dc1a95c334e6953900b9106bc47a9839b81790009eb21" [[package]] name = "cc" -version = "1.0.83" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0" +checksum = "fd9de9f2205d5ef3fd67e685b0df337994ddd4495e2a28d185500d0e1edfea47" dependencies = [ - "libc", + "shlex", ] [[package]] @@ -331,24 +331,24 @@ dependencies = [ [[package]] name = "const-oid" -version = "0.9.5" +version = "0.9.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28c122c3980598d243d63d9a704629a2d748d101f278052ff068be5a4423ab6f" +checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" [[package]] name = "const_format" -version = "0.2.32" +version = "0.2.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3a214c7af3d04997541b18d432afaff4c455e79e2029079647e72fc2bd27673" +checksum = "50c655d81ff1114fb0dcdea9225ea9f0cc712a6f8d189378e82bdf62a473a64b" dependencies = [ "const_format_proc_macros", ] [[package]] name = "const_format_proc_macros" -version = "0.2.32" +version = "0.2.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7f6ff08fd20f4f299298a28e2dfa8a8ba1036e6cd2460ac1de7b425d76f2500" +checksum = "eff1a44b93f47b1bac19a27932f5c591e43d1ba357ee4f61526c8a25603f0eb1" dependencies = [ "proc-macro2", "quote", @@ -367,15 +367,15 @@ dependencies = [ [[package]] name = "core-foundation-sys" -version = "0.8.6" +version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" [[package]] name = "cpufeatures" -version = "0.2.9" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a17b76ff3a4162b0b27f354a0c87015ddad39d35f9c0c36607a3bdd175dde1f1" +checksum = "16b80225097f2e5ae4e7179dd2266824648f3e2f49d9134d584b76389d31c4c3" dependencies = [ "libc", ] @@ -398,15 +398,15 @@ dependencies = [ [[package]] name = "data-encoding" -version = "2.4.0" +version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2e66c9d817f1720209181c316d28635c050fa304f9c79e47a520882661b7308" +checksum = "e8566979429cf69b49a5c740c60791108e86440e8be149bbea4fe54d2c32d6e2" [[package]] name = "der" -version = "0.7.8" +version = "0.7.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fffa369a668c8af7dbf8b5e56c9f744fbd399949ed171606040001947de40b1c" +checksum = "f55bf8e7b65898637379c1b74eb1551107c8294ed26d855ceb9fd1a09cfc9bc0" dependencies = [ "const-oid", "pem-rfc7468", @@ -471,20 +471,20 @@ dependencies = [ [[package]] name = "displaydoc" -version = "0.2.4" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "487585f4d0c6655fe74905e2504d8ad6908e4db67f744eb140876906c2f3175d" +checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.52", + "syn 2.0.89", ] [[package]] name = "dyn-clone" -version = "1.0.13" +version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbfc4744c1b8f2a09adc0e55242f60b1af195d88596bd8700be74418c056c555" +checksum = "0d6ef0072f8a535281e4876be788938b528e9a1d43900b82c2569af7da799125" [[package]] name = "either" @@ -540,9 +540,9 @@ dependencies = [ [[package]] name = "flume" -version = "0.11.0" +version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55ac459de2512911e4b674ce33cf20befaba382d05b62b008afc1c8b57cbf181" +checksum = "da0e4dd2a88388a1f4ccc7c9ce104604dab68d9f408dc34cd45823d5a9069095" dependencies = [ "futures-core", "futures-sink", @@ -567,9 +567,9 @@ dependencies = [ [[package]] name = "futures" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "645c6916888f6cb6350d2550b80fb63e734897a8498abe35cfb732b6487804b0" +checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876" dependencies = [ "futures-channel", "futures-core", @@ -582,9 +582,9 @@ dependencies = [ [[package]] name = "futures-channel" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eac8f7d7865dcb88bd4373ab671c8cf4508703796caa2b1985a9ca867b3fcb78" +checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" dependencies = [ "futures-core", "futures-sink", @@ -592,15 +592,15 @@ dependencies = [ [[package]] name = "futures-core" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d" +checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" [[package]] name = "futures-executor" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a576fc72ae164fca6b9db127eaa9a9dda0d61316034f33a0a0d4eda41f02b01d" +checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f" dependencies = [ "futures-core", "futures-task", @@ -609,38 +609,38 @@ dependencies = [ [[package]] name = "futures-io" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a44623e20b9681a318efdd71c299b6b222ed6f231972bfe2f224ebad6311f0c1" +checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" [[package]] name = "futures-macro" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" +checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" dependencies = [ "proc-macro2", "quote", - "syn 2.0.52", + "syn 2.0.89", ] [[package]] name = "futures-sink" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fb8e00e87438d937621c1c6269e53f536c14d3fbd6a042bb24879e57d474fb5" +checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" [[package]] name = "futures-task" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004" +checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" [[package]] name = "futures-util" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48" +checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" dependencies = [ "futures-channel", "futures-core", @@ -666,9 +666,9 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.10" +version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be4136b2a15dd319360be1c07d9933517ccf0be8f16bf62a3bee4f0d618df427" +checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" dependencies = [ "cfg-if", "js-sys", @@ -679,9 +679,9 @@ dependencies = [ [[package]] name = "gimli" -version = "0.28.0" +version = "0.31.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6fb8d784f27acf97159b40fc4db5ecd8aa23b9ad5ef69cdd136d3bc80665f0c0" +checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" [[package]] name = "git-version" @@ -700,15 +700,9 @@ checksum = "53010ccb100b96a67bc32c0175f0ed1426b31b655d562898e57325f81c023ac0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.52", + "syn 2.0.89", ] -[[package]] -name = "half" -version = "1.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eabb4a44450da02c90444cf74558da904edde8fb4e9035a9a6a4e15445af0bd7" - [[package]] name = "hashbrown" version = "0.12.3" @@ -725,6 +719,12 @@ dependencies = [ "allocator-api2", ] +[[package]] +name = "hashbrown" +version = "0.15.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" + [[package]] name = "hermit-abi" version = "0.1.19" @@ -760,9 +760,9 @@ dependencies = [ [[package]] name = "http" -version = "1.0.0" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b32afd38673a8016f7c9ae69e5af41a58f81b1d31689040f2f1959594ce194ea" +checksum = "21b9ddb458710bc376481b842f5da65cdf31522de232c1ca8146abce2a358258" dependencies = [ "bytes", "fnv", @@ -771,9 +771,9 @@ dependencies = [ [[package]] name = "httparse" -version = "1.8.0" +version = "1.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904" +checksum = "7d71d3574edd2771538b901e6549113b4006ece66150fb69c0fb6d9a2adae946" [[package]] name = "humantime" @@ -781,14 +781,143 @@ version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" +[[package]] +name = "icu_collections" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db2fa452206ebee18c4b5c2274dbf1de17008e874b4dc4f0aea9d01ca79e4526" +dependencies = [ + "displaydoc", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_locid" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13acbb8371917fc971be86fc8057c41a64b521c184808a698c02acc242dbf637" +dependencies = [ + "displaydoc", + "litemap", + "tinystr", + "writeable", + "zerovec", +] + +[[package]] +name = "icu_locid_transform" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01d11ac35de8e40fdeda00d9e1e9d92525f3f9d887cdd7aa81d727596788b54e" +dependencies = [ + "displaydoc", + "icu_locid", + "icu_locid_transform_data", + "icu_provider", + "tinystr", + "zerovec", +] + +[[package]] +name = "icu_locid_transform_data" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fdc8ff3388f852bede6b579ad4e978ab004f139284d7b28715f773507b946f6e" + +[[package]] +name = "icu_normalizer" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19ce3e0da2ec68599d193c93d088142efd7f9c5d6fc9b803774855747dc6a84f" +dependencies = [ + "displaydoc", + "icu_collections", + "icu_normalizer_data", + "icu_properties", + "icu_provider", + "smallvec", + "utf16_iter", + "utf8_iter", + "write16", + "zerovec", +] + +[[package]] +name = "icu_normalizer_data" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8cafbf7aa791e9b22bec55a167906f9e1215fd475cd22adfcf660e03e989516" + +[[package]] +name = "icu_properties" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93d6020766cfc6302c15dbbc9c8778c37e62c14427cb7f6e601d849e092aeef5" +dependencies = [ + "displaydoc", + "icu_collections", + "icu_locid_transform", + "icu_properties_data", + "icu_provider", + "tinystr", + "zerovec", +] + +[[package]] +name = "icu_properties_data" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67a8effbc3dd3e4ba1afa8ad918d5684b8868b3b26500753effea8d2eed19569" + +[[package]] +name = "icu_provider" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ed421c8a8ef78d3e2dbc98a973be2f3770cb42b606e3ab18d6237c4dfde68d9" +dependencies = [ + "displaydoc", + "icu_locid", + "icu_provider_macros", + "stable_deref_trait", + "tinystr", + "writeable", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_provider_macros" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.89", +] + [[package]] name = "idna" -version = "0.5.0" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6" +checksum = "686f825264d630750a544639377bae737628043f20d38bbc029e8f29ea968a7e" dependencies = [ - "unicode-bidi", - "unicode-normalization", + "idna_adapter", + "smallvec", + "utf8_iter", +] + +[[package]] +name = "idna_adapter" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "daca1df1c957320b2cf139ac61e7bd64fed304c5040df000a745aa1de3b4ef71" +dependencies = [ + "icu_normalizer", + "icu_properties", ] [[package]] @@ -803,12 +932,12 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.4.0" +version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93ead53efc7ea8ed3cfb0c79fc8023fbb782a5432b52830b6518941cebe6505c" +checksum = "707907fe3c25f5424cce2cb7e1cbcafee6bdbe735ca90ef77c29e84591e5b9da" dependencies = [ "equivalent", - "hashbrown 0.14.5", + "hashbrown 0.15.2", ] [[package]] @@ -829,12 +958,6 @@ dependencies = [ "serde", ] -[[package]] -name = "iter-read" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c397ca3ea05ad509c4ec451fea28b4771236a376ca1c69fd5143aae0cf8f93c4" - [[package]] name = "itertools" version = "0.13.0" @@ -846,9 +969,9 @@ dependencies = [ [[package]] name = "itoa" -version = "1.0.9" +version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38" +checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674" [[package]] name = "jni" @@ -860,7 +983,7 @@ dependencies = [ "combine", "jni-sys", "log", - "thiserror", + "thiserror 1.0.69", "walkdir", ] @@ -875,7 +998,7 @@ dependencies = [ "combine", "jni-sys", "log", - "thiserror", + "thiserror 1.0.69", "walkdir", "windows-sys 0.45.0", ] @@ -888,9 +1011,9 @@ checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130" [[package]] name = "js-sys" -version = "0.3.64" +version = "0.3.72" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c5f195fe497f702db0f318b07fdd68edb16955aed830df8363d837542f8f935a" +checksum = "6a88f1bda2bd75b0452a14784937d796722fdebfe50df998aeb3f0b7603019a9" dependencies = [ "wasm-bindgen", ] @@ -908,9 +1031,9 @@ dependencies = [ [[package]] name = "keccak" -version = "0.1.4" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f6d5ed8676d904364de097082f4e7d240b571b67989ced0240f08b7f966f940" +checksum = "ecc2af9a1119c51f12a14607e783cb977bde58bc069ff0c3da1095e635d70654" dependencies = [ "cpufeatures", ] @@ -933,33 +1056,55 @@ dependencies = [ "spin", ] +[[package]] +name = "leb128" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "884e2677b40cc8c339eaefcb701c32ef1fd2493d71118dc0ca4b6a736c93bd67" + [[package]] name = "libc" -version = "0.2.158" +version = "0.2.166" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8adc4bb1803a324070e64a98ae98f38934d91957a99cfb3a43dcbc01bc56439" +checksum = "c2ccc108bbc0b1331bd061864e7cd823c0cab660bbe6970e66e2c0614decde36" [[package]] name = "libloading" -version = "0.8.0" +version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d580318f95776505201b28cf98eb1fa5e4be3b689633ba6a3e6cd880ff22d8cb" +checksum = "4979f22fdb869068da03c9f7528f8297c6fd2606bc3a4affe42e6a823fdb8da4" dependencies = [ "cfg-if", - "windows-sys 0.48.0", + "windows-targets 0.52.6", ] [[package]] name = "libm" -version = "0.2.7" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8355be11b20d696c8f18f6cc018c4e372165b1fa8126cef092399c9951984ffa" + +[[package]] +name = "libredox" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" +dependencies = [ + "bitflags 2.6.0", + "libc", +] + +[[package]] +name = "litemap" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7012b1bbb0719e1097c47611d3898568c546d597c2e74d66f6087edd5233ff4" +checksum = "4ee93343901ab17bd981295f2cf0026d4ad018c7c31ba84549a4ddbb47a45104" [[package]] name = "lock_api" -version = "0.4.10" +version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1cc9717a20b1bb222f333e6a92fd32f7d8a18ddc5a3191a11af45dcbf4dcd16" +checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" dependencies = [ "autocfg", "scopeguard", @@ -967,15 +1112,15 @@ dependencies = [ [[package]] name = "log" -version = "0.4.20" +version = "0.4.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" +checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" [[package]] name = "lz4_flex" -version = "0.11.1" +version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ea9b256699eda7b0387ffbc776dd625e28bde3918446381781245b7a50349d8" +checksum = "75761162ae2b0e580d7e7c390558127e5f01b4194debd6221fd8c207fc80e3f5" dependencies = [ "twox-hash", ] @@ -1003,11 +1148,11 @@ checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" [[package]] name = "miniz_oxide" -version = "0.7.1" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7" +checksum = "e2d80299ef12ff69b16a84bb182e3b9df68b5a91574d3d4fa6e41b65deec4df1" dependencies = [ - "adler", + "adler2", ] [[package]] @@ -1037,7 +1182,7 @@ version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46" dependencies = [ - "bitflags 2.5.0", + "bitflags 2.6.0", "cfg-if", "cfg_aliases", "libc", @@ -1071,11 +1216,10 @@ dependencies = [ [[package]] name = "num-bigint" -version = "0.4.4" +version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "608e7659b5c3d7cba262d894801b9ec9d00de989e8a82bd4bef91d08da45cdc0" +checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" dependencies = [ - "autocfg", "num-integer", "num-traits", ] @@ -1145,27 +1289,27 @@ dependencies = [ [[package]] name = "object" -version = "0.32.1" +version = "0.36.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9cf5f9dd3933bd50a9e1f149ec995f39ae2c496d31fd772c1fd45ebc27e902b0" +checksum = "aedf0a2d09c573ed1d8d85b30c119153926a2b36dce0ab28322c09a117a4683e" dependencies = [ "memchr", ] [[package]] name = "oid-registry" -version = "0.7.0" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c958dd45046245b9c3c2547369bb634eb461670b2e7e0de552905801a648d1d" +checksum = "a8d8034d9489cdaf79228eb9f6a3b8d7bb32ba00d6645ebd48eef4077ceb5bd9" dependencies = [ "asn1-rs", ] [[package]] name = "once_cell" -version = "1.19.0" +version = "1.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" +checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" [[package]] name = "openssl-probe" @@ -1193,15 +1337,15 @@ checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" [[package]] name = "parking" -version = "2.1.0" +version = "2.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14f2252c834a40ed9bb5422029649578e63aa341ac401f74e719dd1afda8394e" +checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba" [[package]] name = "parking_lot" -version = "0.12.1" +version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" +checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" dependencies = [ "lock_api", "parking_lot_core", @@ -1209,15 +1353,15 @@ dependencies = [ [[package]] name = "parking_lot_core" -version = "0.9.8" +version = "0.9.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93f00c865fe7cabf650081affecd3871070f26767e7b2070a3ffae14c654b447" +checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" dependencies = [ "cfg-if", "libc", - "redox_syscall 0.3.5", + "redox_syscall 0.5.7", "smallvec", - "windows-targets 0.48.5", + "windows-targets 0.52.6", ] [[package]] @@ -1243,20 +1387,20 @@ checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" [[package]] name = "pest" -version = "2.7.3" +version = "2.7.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7a4d085fd991ac8d5b05a147b437791b4260b76326baf0fc60cf7c9c27ecd33" +checksum = "879952a81a83930934cbf1786752d6dedc3b1f29e8f8fb2ad1d0a36f377cf442" dependencies = [ "memchr", - "thiserror", + "thiserror 1.0.69", "ucd-trie", ] [[package]] name = "pest_derive" -version = "2.7.3" +version = "2.7.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2bee7be22ce7918f641a33f08e3f43388c7656772244e2bbb2477f44cc9021a" +checksum = "d214365f632b123a47fd913301e14c946c61d1c183ee245fa76eb752e59a02dd" dependencies = [ "pest", "pest_generator", @@ -1264,22 +1408,22 @@ dependencies = [ [[package]] name = "pest_generator" -version = "2.7.3" +version = "2.7.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d1511785c5e98d79a05e8a6bc34b4ac2168a0e3e92161862030ad84daa223141" +checksum = "eb55586734301717aea2ac313f50b2eb8f60d2fc3dc01d190eefa2e625f60c4e" dependencies = [ "pest", "pest_meta", "proc-macro2", "quote", - "syn 2.0.52", + "syn 2.0.89", ] [[package]] name = "pest_meta" -version = "2.7.3" +version = "2.7.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b42f0394d3123e33353ca5e1e89092e533d2cc490389f2bd6131c43c634ebc5f" +checksum = "b75da2a70cf4d9cb76833c990ac9cd3923c9a8905a8929789ce347c84564d03d" dependencies = [ "once_cell", "pest", @@ -1293,7 +1437,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b4c5cc86750666a3ed20bdaf5ca2a0344f9c67674cae0515bec2da16fbaa47db" dependencies = [ "fixedbitset", - "indexmap 2.4.0", + "indexmap 2.6.0", ] [[package]] @@ -1326,7 +1470,7 @@ dependencies = [ "phf_shared", "proc-macro2", "quote", - "syn 2.0.52", + "syn 2.0.89", ] [[package]] @@ -1340,29 +1484,29 @@ dependencies = [ [[package]] name = "pin-project" -version = "1.1.3" +version = "1.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fda4ed1c6c173e3fc7a83629421152e01d7b1f9b7f65fb301e490e8cfc656422" +checksum = "be57f64e946e500c8ee36ef6331845d40a93055567ec57e8fae13efd33759b95" dependencies = [ "pin-project-internal", ] [[package]] name = "pin-project-internal" -version = "1.1.3" +version = "1.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4359fd9c9171ec6e8c62926d6faaf553a8dc3f64e1507e76da7911b4f6a04405" +checksum = "3c0f5fad0874fc7abcd4d750e76917eaebbecaa2c20bde22e1dbeeba8beb758c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.52", + "syn 2.0.89", ] [[package]] name = "pin-project-lite" -version = "0.2.13" +version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58" +checksum = "915a1e146535de9163f3987b8944ed8cf49a18bb0056bcebcdcece385cece4ff" [[package]] name = "pin-utils" @@ -1431,24 +1575,27 @@ checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" [[package]] name = "ppv-lite86" -version = "0.2.17" +version = "0.2.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" +checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04" +dependencies = [ + "zerocopy", +] [[package]] name = "proc-macro2" -version = "1.0.86" +version = "1.0.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77" +checksum = "37d3544b3f2748c54e147655edb5025752e2303145b5aefb3c3ea2c78b973bb0" dependencies = [ "unicode-ident", ] [[package]] name = "quinn" -version = "0.11.3" +version = "0.11.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b22d8e7369034b9a7132bc2008cac12f2013c8132b45e0554e6e20e2617f2156" +checksum = "62e96808277ec6f97351a2380e6c25114bc9e67037775464979f3037c92d05ef" dependencies = [ "bytes", "pin-project-lite", @@ -1457,40 +1604,44 @@ dependencies = [ "rustc-hash", "rustls", "socket2", - "thiserror", + "thiserror 2.0.3", "tokio", "tracing", ] [[package]] name = "quinn-proto" -version = "0.11.8" +version = "0.11.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fadfaed2cd7f389d0161bb73eeb07b7b78f8691047a6f3e73caaeae55310a4a6" +checksum = "a2fe5ef3495d7d2e377ff17b1a8ce2ee2ec2a18cde8b6ad6619d65d0701c135d" dependencies = [ "bytes", + "getrandom", "rand", "ring", "rustc-hash", "rustls", + "rustls-pki-types", "rustls-platform-verifier", "slab", - "thiserror", + "thiserror 2.0.3", "tinyvec", "tracing", + "web-time", ] [[package]] name = "quinn-udp" -version = "0.5.4" +version = "0.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8bffec3605b73c6f1754535084a85229fa8a30f86014e6c81aeec4abb68b0285" +checksum = "7d5a626c6807713b15cac82a6acaccd6043c9a5408c24baae07611fec3f243da" dependencies = [ + "cfg_aliases", "libc", "once_cell", "socket2", "tracing", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -1534,52 +1685,43 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.2.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" -dependencies = [ - "bitflags 1.3.2", -] - -[[package]] -name = "redox_syscall" -version = "0.3.5" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29" +checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa" dependencies = [ "bitflags 1.3.2", ] [[package]] name = "redox_syscall" -version = "0.4.1" +version = "0.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa" +checksum = "9b6dfecf2c74bce2466cabf93f6664d6998a69eb21e39f4207930065b27b771f" dependencies = [ - "bitflags 1.3.2", + "bitflags 2.6.0", ] [[package]] name = "redox_users" -version = "0.4.3" +version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b033d837a7cf162d7993aded9304e30a83213c648b6e389db233191f891e5c2b" +checksum = "ba009ff324d1fc1b900bd1fdb31564febe58a8ccc8a6fdbb93b543d33b13ca43" dependencies = [ "getrandom", - "redox_syscall 0.2.16", - "thiserror", + "libredox", + "thiserror 1.0.69", ] [[package]] name = "regex" -version = "1.10.6" +version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4219d74c6b67a3654a9fbebc4b419e22126d13d2f3c4a07ee0cb61ff79a79619" +checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" dependencies = [ "aho-corasick", "memchr", - "regex-automata 0.4.7", - "regex-syntax 0.8.4", + "regex-automata 0.4.9", + "regex-syntax 0.8.5", ] [[package]] @@ -1593,13 +1735,13 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.7" +version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38caf58cc5ef2fed281f89292ef23f6365465ed9a41b7a7754eb4e26496c92df" +checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" dependencies = [ "aho-corasick", "memchr", - "regex-syntax 0.8.4", + "regex-syntax 0.8.5", ] [[package]] @@ -1610,22 +1752,23 @@ checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" [[package]] name = "regex-syntax" -version = "0.8.4" +version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a66a03ae7c801facd77a29370b4faec201768915ac14a721ba36f20bc9c209b" +checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" [[package]] name = "ring" -version = "0.17.6" +version = "0.17.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "684d5e6e18f669ccebf64a92236bb7db9a34f07be010e3627368182027180866" +checksum = "c17fa4cb658e3583423e915b9f3acc01cceaee1860e33d59ebae66adc3a2dc0d" dependencies = [ "cc", + "cfg-if", "getrandom", "libc", "spin", "untrusted", - "windows-sys 0.48.0", + "windows-sys 0.52.0", ] [[package]] @@ -1644,24 +1787,22 @@ version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b91f7eff05f748767f183df4320a63d6936e9c6107d97c9e6bdd9784f4289c94" dependencies = [ - "base64 0.21.4", - "bitflags 2.5.0", + "base64 0.21.7", + "bitflags 2.6.0", "serde", "serde_derive", ] [[package]] name = "rsa" -version = "0.9.2" +version = "0.9.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ab43bb47d23c1a631b4b680199a45255dce26fa9ab2fa902581f624ff13e6a8" +checksum = "47c75d7c5c6b673e58bf54d8544a9f432e3a925b0e80f7cd3602ab5c50c55519" dependencies = [ - "byteorder", "const-oid", "digest", "num-bigint-dig", "num-integer", - "num-iter", "num-traits", "pkcs1", "pkcs8", @@ -1674,9 +1815,9 @@ dependencies = [ [[package]] name = "rustc-demangle" -version = "0.1.23" +version = "0.1.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" +checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" [[package]] name = "rustc-hash" @@ -1704,9 +1845,9 @@ dependencies = [ [[package]] name = "rustls" -version = "0.23.12" +version = "0.23.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c58f8c84392efc0a126acce10fa59ff7b3d2ac06ab451a33f2741989b806b044" +checksum = "934b404430bb06b3fae2cba809eb45a1ab1aecd64491213d7c3301b88393f8d1" dependencies = [ "log", "once_cell", @@ -1719,9 +1860,9 @@ dependencies = [ [[package]] name = "rustls-native-certs" -version = "0.7.0" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f1fb85efa936c42c6d5fc28d2629bb51e4b2f4b8a5211e297d599cc5a093792" +checksum = "e5bfb394eeed242e909609f56089eecfe5fda225042e8b171791b9c95f5931e5" dependencies = [ "openssl-probe", "rustls-pemfile", @@ -1732,25 +1873,27 @@ dependencies = [ [[package]] name = "rustls-pemfile" -version = "2.1.3" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "196fe16b00e106300d3e45ecfcb764fa292a535d7326a29a5875c579c7417425" +checksum = "dce314e5fee3f39953d46bb63bb8a46d40c2f8fb7cc5a3b6cab2bde9721d6e50" dependencies = [ - "base64 0.22.1", "rustls-pki-types", ] [[package]] name = "rustls-pki-types" -version = "1.8.0" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc0a2ce646f8655401bb81e7927b812614bd5d91dbc968696be50603510fcaf0" +checksum = "16f1201b3c9a7ee8039bcadc17b7e605e2945b27eee7631788c1bd2b0643674b" +dependencies = [ + "web-time", +] [[package]] name = "rustls-platform-verifier" -version = "0.3.1" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5f0d26fa1ce3c790f9590868f0109289a044acb954525f933e2aa3b871c157d" +checksum = "a4c7dc240fec5517e6c4eab3310438636cfe6391dfc345ba013109909a90d136" dependencies = [ "core-foundation", "core-foundation-sys", @@ -1763,21 +1906,21 @@ dependencies = [ "rustls-webpki", "security-framework", "security-framework-sys", - "webpki-roots", - "winapi", + "webpki-root-certs", + "windows-sys 0.52.0", ] [[package]] name = "rustls-platform-verifier-android" -version = "0.1.0" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "84e217e7fdc8466b5b35d30f8c0a30febd29173df4a3a0c2115d306b9c4117ad" +checksum = "f87165f0995f63a9fbeea62b64d10b4d9d8e78ec6d7d51fb2125fda7bb36788f" [[package]] name = "rustls-webpki" -version = "0.102.7" +version = "0.102.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "84678086bd54edf2b415183ed7a94d0efb049f1b646a33e22a36f3794be6ae56" +checksum = "64ca1bc8749bd4cf37b5ce386cc146580777b4e8572c7b97baf22c83f444bee9" dependencies = [ "ring", "rustls-pki-types", @@ -1786,9 +1929,9 @@ dependencies = [ [[package]] name = "ryu" -version = "1.0.15" +version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741" +checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" [[package]] name = "same-file" @@ -1801,11 +1944,11 @@ dependencies = [ [[package]] name = "schannel" -version = "0.1.22" +version = "0.1.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c3733bf4cf7ea0880754e19cb5a462007c4a8c1914bff372ccc95b464f1df88" +checksum = "1f29ebaa345f945cec9fbbc532eb307f0fdad8161f281b6369539c8d84876b3d" dependencies = [ - "windows-sys 0.48.0", + "windows-sys 0.59.0", ] [[package]] @@ -1830,7 +1973,7 @@ dependencies = [ "proc-macro2", "quote", "serde_derive_internals", - "syn 2.0.52", + "syn 2.0.89", ] [[package]] @@ -1851,11 +1994,11 @@ dependencies = [ [[package]] name = "security-framework" -version = "2.11.0" +version = "2.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c627723fd09706bacdb5cf41499e95098555af3c3c29d014dc3c458ef6be11c0" +checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" dependencies = [ - "bitflags 2.5.0", + "bitflags 2.6.0", "core-foundation", "core-foundation-sys", "libc", @@ -1865,9 +2008,9 @@ dependencies = [ [[package]] name = "security-framework-sys" -version = "2.11.0" +version = "2.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "317936bbbd05227752583946b9e66d7ce3b489f84e11a94a510b4437fef407d7" +checksum = "fa39c7303dc58b5543c94d22c1766b0d31f2ee58306363ea622b10bbc075eaa2" dependencies = [ "core-foundation-sys", "libc", @@ -1875,51 +2018,28 @@ dependencies = [ [[package]] name = "semver" -version = "1.0.18" +version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0293b4b29daaf487284529cc2f5675b8e57c61f70167ba415a463651fd6a918" +checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b" [[package]] name = "serde" -version = "1.0.209" +version = "1.0.215" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99fce0ffe7310761ca6bf9faf5115afbc19688edd00171d81b1bb1b116c63e09" +checksum = "6513c1ad0b11a9376da888e3e0baa0077f1aed55c17f50e7b2397136129fb88f" dependencies = [ "serde_derive", ] -[[package]] -name = "serde-pickle" -version = "1.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c762ad136a26407c6a80825813600ceeab5e613660d93d79a41f0ec877171e71" -dependencies = [ - "byteorder", - "iter-read", - "num-bigint", - "num-traits", - "serde", -] - -[[package]] -name = "serde_cbor" -version = "0.11.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2bef2ebfde456fb76bbcf9f59315333decc4fda0b2b44b420243c11e0f5ec1f5" -dependencies = [ - "half", - "serde", -] - [[package]] name = "serde_derive" -version = "1.0.209" +version = "1.0.215" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a5831b979fd7b5439637af1752d535ff49f4860c0f341d1baeb6faf0f4242170" +checksum = "ad1e866f866923f252f05c889987993144fb74e722403468a4ebd70c3cd756c0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.52", + "syn 2.0.89", ] [[package]] @@ -1930,14 +2050,14 @@ checksum = "18d26a20a969b9e3fdf2fc2d9f21eda6c40e2de84c9408bb5d3b05d499aae711" dependencies = [ "proc-macro2", "quote", - "syn 2.0.52", + "syn 2.0.89", ] [[package]] name = "serde_json" -version = "1.0.127" +version = "1.0.133" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8043c06d9f82bd7271361ed64f415fe5e12a77fdb52e573e7f06a516dea329ad" +checksum = "c7fceb2473b9166b2294ef05efcb65a3db80803f0b03ef86a5fc88a2b85ee377" dependencies = [ "itoa", "memchr", @@ -1951,7 +2071,7 @@ version = "0.9.34+deprecated" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6a8b1a1a2ebf674015cc02edccce75287f1a0130d394307b36743c2f5d504b47" dependencies = [ - "indexmap 2.4.0", + "indexmap 2.6.0", "itoa", "ryu", "serde", @@ -1960,9 +2080,9 @@ dependencies = [ [[package]] name = "sha1" -version = "0.10.5" +version = "0.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f04293dc80c3993519f2d7f6f511707ee7094fe0c6d3406feb330cdb3540eba3" +checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" dependencies = [ "cfg-if", "cpufeatures", @@ -1971,9 +2091,9 @@ dependencies = [ [[package]] name = "sha2" -version = "0.10.7" +version = "0.10.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "479fb9d862239e610720565ca91403019f2f00410f1864c5aa7479b950a76ed8" +checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" dependencies = [ "cfg-if", "cpufeatures", @@ -2008,11 +2128,17 @@ dependencies = [ "dirs", ] +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + [[package]] name = "signature" -version = "2.1.0" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e1788eed21689f9cf370582dfc467ef36ed9c707f073528ddafa8d83e3b8500" +checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" dependencies = [ "digest", "rand_core", @@ -2041,9 +2167,9 @@ checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" [[package]] name = "socket2" -version = "0.5.7" +version = "0.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce305eb0b4296696835b71df73eb912e0f1ffd2556a501fcede6e0c50349191c" +checksum = "c970269d99b64e60ec3bd6ad27270092a5394c4e309314b18ae3fe575695fbe8" dependencies = [ "libc", "windows-sys 0.52.0", @@ -2060,14 +2186,20 @@ dependencies = [ [[package]] name = "spki" -version = "0.7.2" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d1e996ef02c474957d681f1b05213dfb0abab947b446a62d37770b23500184a" +checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" dependencies = [ "base64ct", "der", ] +[[package]] +name = "stable_deref_trait" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" + [[package]] name = "static_assertions" version = "1.1.0" @@ -2082,9 +2214,9 @@ checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" [[package]] name = "subtle" -version = "2.5.0" +version = "2.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" [[package]] name = "syn" @@ -2099,9 +2231,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.52" +version = "2.0.89" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b699d15b36d1f02c3e7c69f8ffef53de37aefae075d8488d4ba1a7788d574a07" +checksum = "44d46482f1c1c87acd84dea20c1bf5ebff4c757009ed6bf19cfd36fb10e92c4e" dependencies = [ "proc-macro2", "quote", @@ -2116,7 +2248,7 @@ checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971" dependencies = [ "proc-macro2", "quote", - "syn 2.0.52", + "syn 2.0.89", ] [[package]] @@ -2136,22 +2268,42 @@ checksum = "23d434d3f8967a09480fb04132ebe0a3e088c173e6d0ee7897abbdf4eab0f8b9" [[package]] name = "thiserror" -version = "1.0.48" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" +dependencies = [ + "thiserror-impl 1.0.69", +] + +[[package]] +name = "thiserror" +version = "2.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d6d7a740b8a666a7e828dd00da9c0dc290dff53154ea77ac109281de90589b7" +checksum = "c006c85c7651b3cf2ada4584faa36773bd07bac24acfb39f3c431b36d7e667aa" dependencies = [ - "thiserror-impl", + "thiserror-impl 2.0.3", ] [[package]] name = "thiserror-impl" -version = "1.0.48" +version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49922ecae66cc8a249b77e68d1d0623c1b2c514f0060c27cdc68bd62a1219d35" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.52", + "syn 2.0.89", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f077553d607adc1caf65430528a576c757a71ed73944b66ebb58ef2bbd243568" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.89", ] [[package]] @@ -2195,11 +2347,21 @@ dependencies = [ "time-core", ] +[[package]] +name = "tinystr" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9117f5d4db391c1cf6927e7bea3db74b9a1c1add8f7eda9ffd5364f40f57b82f" +dependencies = [ + "displaydoc", + "zerovec", +] + [[package]] name = "tinyvec" -version = "1.6.0" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" +checksum = "445e881f4f6d382d5f27c034e25eb92edd7c784ceab92a0937db7f2e9471b938" dependencies = [ "tinyvec_macros", ] @@ -2210,6 +2372,19 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" +[[package]] +name = "tls-listener" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f1d8809f604e448c7bc53a5a0e4c2a0a20ba44cb1fb407314c8eeccb92127f9" +dependencies = [ + "futures-util", + "pin-project-lite", + "thiserror 1.0.69", + "tokio", + "tokio-rustls", +] + [[package]] name = "token-cell" version = "1.5.0" @@ -2221,9 +2396,9 @@ dependencies = [ [[package]] name = "tokio" -version = "1.39.3" +version = "1.41.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9babc99b9923bfa4804bd74722ff02c0381021eafa4db9949217e3be8e84fff5" +checksum = "22cfb5bee7a6a52939ca9224d6ac897bb669134078daa8735560897f69de4d33" dependencies = [ "backtrace", "bytes", @@ -2243,7 +2418,7 @@ checksum = "693d596312e88961bc67d7f1f97af8a70227d9f90c31bba5806eec004978d752" dependencies = [ "proc-macro2", "quote", - "syn 2.0.52", + "syn 2.0.89", ] [[package]] @@ -2259,9 +2434,9 @@ dependencies = [ [[package]] name = "tokio-tungstenite" -version = "0.23.1" +version = "0.24.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6989540ced10490aaf14e6bad2e3d33728a2813310a0c71d1574304c49631cd" +checksum = "edc5f74e248dc973e0dbb7b74c7e0d6fcc301c694ff50049504004ef4d0cdcd9" dependencies = [ "futures-util", "log", @@ -2271,9 +2446,9 @@ dependencies = [ [[package]] name = "tokio-util" -version = "0.7.11" +version = "0.7.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9cf6b47b3771c49ac75ad09a6162f53ad4b8088b76ac60e8ec1455b31a189fe1" +checksum = "61e7c3654c13bcd040d4a03abee2c75b1d14a37b423cf5a813ceae1cc903ec6a" dependencies = [ "bytes", "futures-core", @@ -2286,11 +2461,10 @@ dependencies = [ [[package]] name = "tracing" -version = "0.1.37" +version = "0.1.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ce8c33a8d48bd45d624a6e523445fd21ec13d3653cd51f681abf67418f54eb8" +checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" dependencies = [ - "cfg-if", "log", "pin-project-lite", "tracing-attributes", @@ -2299,20 +2473,20 @@ dependencies = [ [[package]] name = "tracing-attributes" -version = "0.1.26" +version = "0.1.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f4f31f56159e98206da9efd823404b79b6ef3143b4a7ab76e67b1751b25a4ab" +checksum = "395ae124c09f9e6918a2310af6038fba074bcf474ac352496d5910dd59a2226d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.52", + "syn 2.0.89", ] [[package]] name = "tracing-core" -version = "0.1.31" +version = "0.1.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0955b8137a1df6f1a2e9a37d8a6656291ff0297c1a97c24e0d8425fe2312f79a" +checksum = "e672c95779cf947c5311f83787af4fa8fffd12fb27e4993211a84bdfd9610f9c" dependencies = [ "once_cell", "valuable", @@ -2362,9 +2536,9 @@ dependencies = [ [[package]] name = "tungstenite" -version = "0.23.0" +version = "0.24.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e2e2ce1e47ed2994fd43b04c8f618008d4cabdd5ee34027cf14f9d918edd9c8" +checksum = "18e5b8366ee7a95b16d32197d0b2604b43a0be89dc5fac9f8e96ccafbaedda8a" dependencies = [ "byteorder", "bytes", @@ -2374,7 +2548,7 @@ dependencies = [ "log", "rand", "sha1", - "thiserror", + "thiserror 1.0.69", "utf-8", ] @@ -2390,15 +2564,15 @@ dependencies = [ [[package]] name = "typenum" -version = "1.16.0" +version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba" +checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" [[package]] name = "ucd-trie" -version = "0.1.6" +version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed646292ffc8188ef8ea4d1e0e0150fb15a5c2e12ad9b8fc191ae7a8a7f3c4b9" +checksum = "2896d95c02a80c6d6a5d6e953d479f5ddf2dfdb6a244441010e373ac0fb88971" [[package]] name = "uhlc" @@ -2414,32 +2588,17 @@ dependencies = [ "spin", ] -[[package]] -name = "unicode-bidi" -version = "0.3.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08f95100a766bf4f8f28f90d77e0a5461bbdb219042e7679bebe79004fed8d75" - [[package]] name = "unicode-ident" -version = "1.0.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" - -[[package]] -name = "unicode-normalization" -version = "0.1.23" +version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a56d1686db2308d901306f92a263857ef59ea39678a5458e7cb17f01415101f5" -dependencies = [ - "tinyvec", -] +checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83" [[package]] name = "unicode-xid" -version = "0.2.4" +version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" [[package]] name = "unsafe-libyaml" @@ -2453,12 +2612,6 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" -[[package]] -name = "unwrap-infallible" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "151ac09978d3c2862c4e39b557f4eceee2cc72150bc4cb4f16abf061b6e381fb" - [[package]] name = "unzip-n" version = "0.1.2" @@ -2472,9 +2625,9 @@ dependencies = [ [[package]] name = "url" -version = "2.5.2" +version = "2.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22784dbdf76fdde8af1aeda5622b546b422b6fc585325248a2bf9f5e41e94d6c" +checksum = "32f8b686cadd1473f4bd0117a5d28d36b1ade384ea9b5069a1c40aefed7fda60" dependencies = [ "form_urlencoded", "idna", @@ -2487,11 +2640,23 @@ version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" +[[package]] +name = "utf16_iter" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8232dd3cdaed5356e0f716d285e4b40b932ac434100fe9b7e0e8e935b9e6246" + +[[package]] +name = "utf8_iter" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" + [[package]] name = "uuid" -version = "1.10.0" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81dfa00651efa65069b0b6b651f4aaa31ba9e3c3ce0137aaad053604ee7e0314" +checksum = "f8c5f0a0af699448548ad1a2fbf920fb4bee257eae39953ba95cb84891a0446a" dependencies = [ "getrandom", ] @@ -2534,15 +2699,15 @@ checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191" [[package]] name = "version_check" -version = "0.9.4" +version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" [[package]] name = "walkdir" -version = "2.4.0" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d71d857dc86794ca4c280d616f7da00d2dbfd8cd788846559a6813e6aa4b54ee" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" dependencies = [ "same-file", "winapi-util", @@ -2556,34 +2721,35 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" -version = "0.2.87" +version = "0.2.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7706a72ab36d8cb1f80ffbf0e071533974a60d0a308d01a5d0375bf60499a342" +checksum = "128d1e363af62632b8eb57219c8fd7877144af57558fb2ef0368d0087bddeb2e" dependencies = [ "cfg-if", + "once_cell", "wasm-bindgen-macro", ] [[package]] name = "wasm-bindgen-backend" -version = "0.2.87" +version = "0.2.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ef2b6d3c510e9625e5fe6f509ab07d66a760f0885d858736483c32ed7809abd" +checksum = "cb6dd4d3ca0ddffd1dd1c9c04f94b868c37ff5fac97c30b97cff2d74fce3a358" dependencies = [ "bumpalo", "log", "once_cell", "proc-macro2", "quote", - "syn 2.0.52", + "syn 2.0.89", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-macro" -version = "0.2.87" +version = "0.2.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dee495e55982a3bd48105a7b947fd2a9b4a8ae3010041b9e0faab3f9cd028f1d" +checksum = "e79384be7f8f5a9dd5d7167216f022090cf1f9ec128e6e6a482a2cb5c5422c56" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -2591,28 +2757,47 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.87" +version = "0.2.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "54681b18a46765f095758388f2d0cf16eb8d4169b639ab575a8f5693af210c7b" +checksum = "26c6ab57572f7a24a4985830b120de1594465e5d500f24afe89e16b4e833ef68" dependencies = [ "proc-macro2", "quote", - "syn 2.0.52", + "syn 2.0.89", "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.87" +version = "0.2.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca6ad05a4870b2bf5fe995117d3728437bd27d7cd5f06f13c17443ef369775a1" +checksum = "65fc09f10666a9f147042251e0dda9c18f166ff7de300607007e96bdebc1068d" + +[[package]] +name = "web-time" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "webpki-root-certs" +version = "0.26.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9cd5da49bdf1f30054cfe0b8ce2958b8fbeb67c4d82c8967a598af481bef255c" +dependencies = [ + "rustls-pki-types", +] [[package]] name = "webpki-roots" -version = "0.26.3" +version = "0.26.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd7c23921eeb1713a4e851530e9b9756e4fb0e89978582942612524cf09f01cd" +checksum = "5d642ff16b7e79272ae451b7322067cdc17cadf68c23264be9d94a32319efe7e" dependencies = [ "rustls-pki-types", ] @@ -2635,11 +2820,11 @@ checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" [[package]] name = "winapi-util" -version = "0.1.5" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" +checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" dependencies = [ - "winapi", + "windows-sys 0.59.0", ] [[package]] @@ -2672,7 +2857,16 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ - "windows-targets 0.52.0", + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets 0.52.6", ] [[package]] @@ -2707,17 +2901,18 @@ dependencies = [ [[package]] name = "windows-targets" -version = "0.52.0" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a18201040b24831fbb9e4eb208f8892e1f50a37feb53cc7ff887feb8f50e7cd" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" dependencies = [ - "windows_aarch64_gnullvm 0.52.0", - "windows_aarch64_msvc 0.52.0", - "windows_i686_gnu 0.52.0", - "windows_i686_msvc 0.52.0", - "windows_x86_64_gnu 0.52.0", - "windows_x86_64_gnullvm 0.52.0", - "windows_x86_64_msvc 0.52.0", + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", + "windows_i686_gnullvm", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", ] [[package]] @@ -2734,9 +2929,9 @@ checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" [[package]] name = "windows_aarch64_gnullvm" -version = "0.52.0" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb7764e35d4db8a7921e09562a0304bf2f93e0a51bfccee0bd0bb0b666b015ea" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" [[package]] name = "windows_aarch64_msvc" @@ -2752,9 +2947,9 @@ checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" [[package]] name = "windows_aarch64_msvc" -version = "0.52.0" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbaa0368d4f1d2aaefc55b6fcfee13f41544ddf36801e793edbbfd7d7df075ef" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" [[package]] name = "windows_i686_gnu" @@ -2770,9 +2965,15 @@ checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" [[package]] name = "windows_i686_gnu" -version = "0.52.0" +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 = "a28637cb1fa3560a16915793afb20081aba2c92ee8af57b4d5f28e4b3e7df313" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" [[package]] name = "windows_i686_msvc" @@ -2788,9 +2989,9 @@ checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" [[package]] name = "windows_i686_msvc" -version = "0.52.0" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ffe5e8e31046ce6230cc7215707b816e339ff4d4d67c65dffa206fd0f7aa7b9a" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" [[package]] name = "windows_x86_64_gnu" @@ -2806,9 +3007,9 @@ checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" [[package]] name = "windows_x86_64_gnu" -version = "0.52.0" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d6fa32db2bc4a2f5abeacf2b69f7992cd09dca97498da74a151a3132c26befd" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" [[package]] name = "windows_x86_64_gnullvm" @@ -2824,9 +3025,9 @@ checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" [[package]] name = "windows_x86_64_gnullvm" -version = "0.52.0" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a657e1e9d3f514745a572a6846d3c7aa7dbe1658c056ed9c3344c4109a6949e" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" [[package]] name = "windows_x86_64_msvc" @@ -2842,9 +3043,21 @@ checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" [[package]] name = "windows_x86_64_msvc" -version = "0.52.0" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "write16" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1890f4022759daae28ed4fe62859b1236caebfc61ede2f63ed4e695f3f6d936" + +[[package]] +name = "writeable" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51" [[package]] name = "x509-parser" @@ -2859,22 +3072,47 @@ dependencies = [ "nom", "oid-registry", "rusticata-macros", - "thiserror", + "thiserror 1.0.69", "time", ] +[[package]] +name = "yoke" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "120e6aef9aa629e3d4f52dc8cc43a015c7724194c97dfaf45180d2daf2b77f40" +dependencies = [ + "serde", + "stable_deref_trait", + "yoke-derive", + "zerofrom", +] + +[[package]] +name = "yoke-derive" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2380878cad4ac9aac1e2435f3eb4020e8374b5f13c296cb75b4620ff8e229154" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.89", + "synstructure", +] + [[package]] name = "zenoh" version = "1.0.0-dev" -source = "git+https://github.com/eclipse-zenoh/zenoh.git?branch=main#e72e4d46cc21f3eea7d9f25591407d66c96265b7" +source = "git+https://github.com/eclipse-zenoh/zenoh.git?branch=main#7e044ad16729af35ee0981c362369c60012cfa11" dependencies = [ "ahash", "async-trait", "bytes", - "flume 0.11.0", + "flume 0.11.1", "futures", "git-version", "itertools", + "json5", "lazy_static", "once_cell", "paste", @@ -2883,16 +3121,12 @@ dependencies = [ "rand", "rustc_version", "serde", - "serde-pickle", - "serde_cbor", "serde_json", - "serde_yaml", "socket2", "tokio", "tokio-util", "tracing", "uhlc", - "unwrap-infallible", "vec_map", "zenoh-buffers", "zenoh-codec", @@ -2915,7 +3149,7 @@ dependencies = [ [[package]] name = "zenoh-buffers" version = "1.0.0-dev" -source = "git+https://github.com/eclipse-zenoh/zenoh.git?branch=main#e72e4d46cc21f3eea7d9f25591407d66c96265b7" +source = "git+https://github.com/eclipse-zenoh/zenoh.git?branch=main#7e044ad16729af35ee0981c362369c60012cfa11" dependencies = [ "zenoh-collections", ] @@ -2923,7 +3157,7 @@ dependencies = [ [[package]] name = "zenoh-codec" version = "1.0.0-dev" -source = "git+https://github.com/eclipse-zenoh/zenoh.git?branch=main#e72e4d46cc21f3eea7d9f25591407d66c96265b7" +source = "git+https://github.com/eclipse-zenoh/zenoh.git?branch=main#7e044ad16729af35ee0981c362369c60012cfa11" dependencies = [ "tracing", "uhlc", @@ -2934,14 +3168,13 @@ dependencies = [ [[package]] name = "zenoh-collections" version = "1.0.0-dev" -source = "git+https://github.com/eclipse-zenoh/zenoh.git?branch=main#e72e4d46cc21f3eea7d9f25591407d66c96265b7" +source = "git+https://github.com/eclipse-zenoh/zenoh.git?branch=main#7e044ad16729af35ee0981c362369c60012cfa11" [[package]] name = "zenoh-config" version = "1.0.0-dev" -source = "git+https://github.com/eclipse-zenoh/zenoh.git?branch=main#e72e4d46cc21f3eea7d9f25591407d66c96265b7" +source = "git+https://github.com/eclipse-zenoh/zenoh.git?branch=main#7e044ad16729af35ee0981c362369c60012cfa11" dependencies = [ - "flume 0.11.0", "json5", "num_cpus", "secrecy", @@ -2961,7 +3194,7 @@ dependencies = [ [[package]] name = "zenoh-core" version = "1.0.0-dev" -source = "git+https://github.com/eclipse-zenoh/zenoh.git?branch=main#e72e4d46cc21f3eea7d9f25591407d66c96265b7" +source = "git+https://github.com/eclipse-zenoh/zenoh.git?branch=main#7e044ad16729af35ee0981c362369c60012cfa11" dependencies = [ "lazy_static", "tokio", @@ -2972,7 +3205,7 @@ dependencies = [ [[package]] name = "zenoh-crypto" version = "1.0.0-dev" -source = "git+https://github.com/eclipse-zenoh/zenoh.git?branch=main#e72e4d46cc21f3eea7d9f25591407d66c96265b7" +source = "git+https://github.com/eclipse-zenoh/zenoh.git?branch=main#7e044ad16729af35ee0981c362369c60012cfa11" dependencies = [ "aes", "hmac", @@ -2985,11 +3218,12 @@ dependencies = [ [[package]] name = "zenoh-ext" version = "1.0.0-dev" -source = "git+https://github.com/eclipse-zenoh/zenoh.git?branch=main#e72e4d46cc21f3eea7d9f25591407d66c96265b7" +source = "git+https://github.com/eclipse-zenoh/zenoh.git?branch=main#7e044ad16729af35ee0981c362369c60012cfa11" dependencies = [ "bincode", - "flume 0.11.0", + "flume 0.11.1", "futures", + "leb128", "serde", "tokio", "tracing", @@ -3001,7 +3235,7 @@ dependencies = [ [[package]] name = "zenoh-keyexpr" version = "1.0.0-dev" -source = "git+https://github.com/eclipse-zenoh/zenoh.git?branch=main#e72e4d46cc21f3eea7d9f25591407d66c96265b7" +source = "git+https://github.com/eclipse-zenoh/zenoh.git?branch=main#7e044ad16729af35ee0981c362369c60012cfa11" dependencies = [ "hashbrown 0.14.5", "keyed-set", @@ -3015,7 +3249,7 @@ dependencies = [ [[package]] name = "zenoh-link" version = "1.0.0-dev" -source = "git+https://github.com/eclipse-zenoh/zenoh.git?branch=main#e72e4d46cc21f3eea7d9f25591407d66c96265b7" +source = "git+https://github.com/eclipse-zenoh/zenoh.git?branch=main#7e044ad16729af35ee0981c362369c60012cfa11" dependencies = [ "zenoh-config", "zenoh-link-commons", @@ -3032,14 +3266,15 @@ dependencies = [ [[package]] name = "zenoh-link-commons" version = "1.0.0-dev" -source = "git+https://github.com/eclipse-zenoh/zenoh.git?branch=main#e72e4d46cc21f3eea7d9f25591407d66c96265b7" +source = "git+https://github.com/eclipse-zenoh/zenoh.git?branch=main#7e044ad16729af35ee0981c362369c60012cfa11" dependencies = [ "async-trait", - "flume 0.11.0", + "flume 0.11.1", "futures", "rustls", "rustls-webpki", "serde", + "time", "tokio", "tokio-util", "tracing", @@ -3055,7 +3290,7 @@ dependencies = [ [[package]] name = "zenoh-link-quic" version = "1.0.0-dev" -source = "git+https://github.com/eclipse-zenoh/zenoh.git?branch=main#e72e4d46cc21f3eea7d9f25591407d66c96265b7" +source = "git+https://github.com/eclipse-zenoh/zenoh.git?branch=main#7e044ad16729af35ee0981c362369c60012cfa11" dependencies = [ "async-trait", "base64 0.22.1", @@ -3065,6 +3300,7 @@ dependencies = [ "rustls-pki-types", "rustls-webpki", "secrecy", + "time", "tokio", "tokio-util", "tracing", @@ -3080,7 +3316,7 @@ dependencies = [ [[package]] name = "zenoh-link-tcp" version = "1.0.0-dev" -source = "git+https://github.com/eclipse-zenoh/zenoh.git?branch=main#e72e4d46cc21f3eea7d9f25591407d66c96265b7" +source = "git+https://github.com/eclipse-zenoh/zenoh.git?branch=main#7e044ad16729af35ee0981c362369c60012cfa11" dependencies = [ "async-trait", "socket2", @@ -3097,7 +3333,7 @@ dependencies = [ [[package]] name = "zenoh-link-tls" version = "1.0.0-dev" -source = "git+https://github.com/eclipse-zenoh/zenoh.git?branch=main#e72e4d46cc21f3eea7d9f25591407d66c96265b7" +source = "git+https://github.com/eclipse-zenoh/zenoh.git?branch=main#7e044ad16729af35ee0981c362369c60012cfa11" dependencies = [ "async-trait", "base64 0.22.1", @@ -3107,6 +3343,8 @@ dependencies = [ "rustls-webpki", "secrecy", "socket2", + "time", + "tls-listener", "tokio", "tokio-rustls", "tokio-util", @@ -3124,7 +3362,7 @@ dependencies = [ [[package]] name = "zenoh-link-udp" version = "1.0.0-dev" -source = "git+https://github.com/eclipse-zenoh/zenoh.git?branch=main#e72e4d46cc21f3eea7d9f25591407d66c96265b7" +source = "git+https://github.com/eclipse-zenoh/zenoh.git?branch=main#7e044ad16729af35ee0981c362369c60012cfa11" dependencies = [ "async-trait", "socket2", @@ -3143,7 +3381,7 @@ dependencies = [ [[package]] name = "zenoh-link-unixsock_stream" version = "1.0.0-dev" -source = "git+https://github.com/eclipse-zenoh/zenoh.git?branch=main#e72e4d46cc21f3eea7d9f25591407d66c96265b7" +source = "git+https://github.com/eclipse-zenoh/zenoh.git?branch=main#7e044ad16729af35ee0981c362369c60012cfa11" dependencies = [ "async-trait", "nix", @@ -3161,7 +3399,7 @@ dependencies = [ [[package]] name = "zenoh-link-ws" version = "1.0.0-dev" -source = "git+https://github.com/eclipse-zenoh/zenoh.git?branch=main#e72e4d46cc21f3eea7d9f25591407d66c96265b7" +source = "git+https://github.com/eclipse-zenoh/zenoh.git?branch=main#7e044ad16729af35ee0981c362369c60012cfa11" dependencies = [ "async-trait", "futures-util", @@ -3181,23 +3419,24 @@ dependencies = [ [[package]] name = "zenoh-macros" version = "1.0.0-dev" -source = "git+https://github.com/eclipse-zenoh/zenoh.git?branch=main#e72e4d46cc21f3eea7d9f25591407d66c96265b7" +source = "git+https://github.com/eclipse-zenoh/zenoh.git?branch=main#7e044ad16729af35ee0981c362369c60012cfa11" dependencies = [ "proc-macro2", "quote", - "syn 2.0.52", + "syn 2.0.89", "zenoh-keyexpr", ] [[package]] name = "zenoh-plugin-trait" version = "1.0.0-dev" -source = "git+https://github.com/eclipse-zenoh/zenoh.git?branch=main#e72e4d46cc21f3eea7d9f25591407d66c96265b7" +source = "git+https://github.com/eclipse-zenoh/zenoh.git?branch=main#7e044ad16729af35ee0981c362369c60012cfa11" dependencies = [ "git-version", "libloading", "serde", "tracing", + "zenoh-config", "zenoh-keyexpr", "zenoh-macros", "zenoh-result", @@ -3207,7 +3446,7 @@ dependencies = [ [[package]] name = "zenoh-protocol" version = "1.0.0-dev" -source = "git+https://github.com/eclipse-zenoh/zenoh.git?branch=main#e72e4d46cc21f3eea7d9f25591407d66c96265b7" +source = "git+https://github.com/eclipse-zenoh/zenoh.git?branch=main#7e044ad16729af35ee0981c362369c60012cfa11" dependencies = [ "const_format", "rand", @@ -3221,7 +3460,7 @@ dependencies = [ [[package]] name = "zenoh-result" version = "1.0.0-dev" -source = "git+https://github.com/eclipse-zenoh/zenoh.git?branch=main#e72e4d46cc21f3eea7d9f25591407d66c96265b7" +source = "git+https://github.com/eclipse-zenoh/zenoh.git?branch=main#7e044ad16729af35ee0981c362369c60012cfa11" dependencies = [ "anyhow", ] @@ -3229,7 +3468,7 @@ dependencies = [ [[package]] name = "zenoh-runtime" version = "1.0.0-dev" -source = "git+https://github.com/eclipse-zenoh/zenoh.git?branch=main#e72e4d46cc21f3eea7d9f25591407d66c96265b7" +source = "git+https://github.com/eclipse-zenoh/zenoh.git?branch=main#7e044ad16729af35ee0981c362369c60012cfa11" dependencies = [ "lazy_static", "ron", @@ -3242,7 +3481,7 @@ dependencies = [ [[package]] name = "zenoh-sync" version = "1.0.0-dev" -source = "git+https://github.com/eclipse-zenoh/zenoh.git?branch=main#e72e4d46cc21f3eea7d9f25591407d66c96265b7" +source = "git+https://github.com/eclipse-zenoh/zenoh.git?branch=main#7e044ad16729af35ee0981c362369c60012cfa11" dependencies = [ "event-listener", "futures", @@ -3255,7 +3494,7 @@ dependencies = [ [[package]] name = "zenoh-task" version = "1.0.0-dev" -source = "git+https://github.com/eclipse-zenoh/zenoh.git?branch=main#e72e4d46cc21f3eea7d9f25591407d66c96265b7" +source = "git+https://github.com/eclipse-zenoh/zenoh.git?branch=main#7e044ad16729af35ee0981c362369c60012cfa11" dependencies = [ "futures", "tokio", @@ -3268,11 +3507,11 @@ dependencies = [ [[package]] name = "zenoh-transport" version = "1.0.0-dev" -source = "git+https://github.com/eclipse-zenoh/zenoh.git?branch=main#e72e4d46cc21f3eea7d9f25591407d66c96265b7" +source = "git+https://github.com/eclipse-zenoh/zenoh.git?branch=main#7e044ad16729af35ee0981c362369c60012cfa11" dependencies = [ "async-trait", "crossbeam-utils", - "flume 0.11.0", + "flume 0.11.1", "lazy_static", "lz4_flex", "paste", @@ -3301,11 +3540,11 @@ dependencies = [ [[package]] name = "zenoh-util" version = "1.0.0-dev" -source = "git+https://github.com/eclipse-zenoh/zenoh.git?branch=main#e72e4d46cc21f3eea7d9f25591407d66c96265b7" +source = "git+https://github.com/eclipse-zenoh/zenoh.git?branch=main#7e044ad16729af35ee0981c362369c60012cfa11" dependencies = [ "async-trait", "const_format", - "flume 0.11.0", + "flume 0.11.1", "home", "humantime", "lazy_static", @@ -3334,35 +3573,79 @@ dependencies = [ "jni 0.21.1", "json5", "rustc_version", + "serde_yaml", "tracing", "uhlc", "zenoh", "zenoh-ext", - "zenoh-protocol", ] [[package]] name = "zerocopy" -version = "0.7.32" +version = "0.7.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74d4d3961e53fa4c9a25a8637fc2bfaf2595b3d3ae34875568a5cf64787716be" +checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" dependencies = [ + "byteorder", "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.7.32" +version = "0.7.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.89", +] + +[[package]] +name = "zerofrom" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cff3ee08c995dee1859d998dea82f7374f2826091dd9cd47def953cae446cd2e" +dependencies = [ + "zerofrom-derive", +] + +[[package]] +name = "zerofrom-derive" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ce1b18ccd8e73a9321186f97e46f9f04b778851177567b1975109d26a08d2a6" +checksum = "595eed982f7d355beb85837f651fa22e90b3c044842dc7f2c2842c086f295808" dependencies = [ "proc-macro2", "quote", - "syn 2.0.52", + "syn 2.0.89", + "synstructure", ] [[package]] name = "zeroize" -version = "1.7.0" +version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "525b4ec142c6b68a2d10f01f7bbf6755599ca3f81ea53b8431b7dd348f5fdb2d" +checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" + +[[package]] +name = "zerovec" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa2b893d79df23bfb12d5461018d408ea19dfafe76c2c7ef6d4eba614f8ff079" +dependencies = [ + "yoke", + "zerofrom", + "zerovec-derive", +] + +[[package]] +name = "zerovec-derive" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.89", +] diff --git a/zenoh-jni/Cargo.toml b/zenoh-jni/Cargo.toml index c5f62f83..08246e28 100644 --- a/zenoh-jni/Cargo.toml +++ b/zenoh-jni/Cargo.toml @@ -24,7 +24,7 @@ description = "Zenoh: Zero Overhead Pub/sub, Store/Query and Compute." name = "zenoh_jni" [features] -default = ["zenoh/default", "zenoh-ext/default"] +default = ["zenoh/default", "zenoh-ext"] # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] @@ -35,10 +35,10 @@ jni = "0.21.1" flume = "0.10.14" uhlc = "0.8.0" json5 = "0.4.1" -zenoh = { version = "1.0.0-dev", git = "https://github.com/eclipse-zenoh/zenoh.git", branch = "main", default-features = false } -zenoh-ext = { version = "1.0.0-dev", git = "https://github.com/eclipse-zenoh/zenoh.git", branch = "main", default-features = false } -zenoh-protocol = { version = "1.0.0-dev", git = "https://github.com/eclipse-zenoh/zenoh.git", branch = "main", default-features = false } -tracing = "0.1" +serde_yaml = "0.9.19" +zenoh = { version = "1.0.0-dev", git = "https://github.com/eclipse-zenoh/zenoh.git", branch = "main", features = ["unstable", "internal"], default-features = false } +zenoh-ext = { version = "1.0.0-dev", git = "https://github.com/eclipse-zenoh/zenoh.git", branch = "main", features = ["internal"], default-features = false, optional = true } +tracing = { version = "0.1" , features = ["log"] } [lib] name = "zenoh_jni" crate_type = ["staticlib", "dylib"] diff --git a/zenoh-jni/src/config.rs b/zenoh-jni/src/config.rs new file mode 100644 index 00000000..0ada1340 --- /dev/null +++ b/zenoh-jni/src/config.rs @@ -0,0 +1,185 @@ +// +// Copyright (c) 2023 ZettaScale Technology +// +// This program and the accompanying materials are made available under the +// terms of the Eclipse Public License 2.0 which is available at +// http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 +// which is available at https://www.apache.org/licenses/LICENSE-2.0. +// +// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 +// +// Contributors: +// ZettaScale Zenoh Team, +// + +use std::{ptr::null, sync::Arc}; + +use jni::{ + objects::{JClass, JString}, + sys::jstring, + JNIEnv, +}; +use zenoh::Config; + +use crate::{errors::ZResult, zerror}; +use crate::{throw_exception, utils::decode_string}; + +/// Loads the default configuration, returning a raw pointer to it. +/// +/// The pointer to the config is expected to be freed later on upon the destruction of the +/// Kotlin Config instance. +/// +#[no_mangle] +#[allow(non_snake_case)] +pub extern "C" fn Java_io_zenoh_jni_JNIConfig_00024Companion_loadDefaultConfigViaJNI( + _env: JNIEnv, + _class: JClass, +) -> *const Config { + let config = Config::default(); + Arc::into_raw(Arc::new(config)) +} + +/// Loads the config from a file, returning a pointer to the loaded config in case of success. +/// In case of failure, an exception is thrown via JNI. +/// +/// The pointer to the config is expected to be freed later on upon the destruction of the +/// Kotlin Config instance. +/// +#[no_mangle] +#[allow(non_snake_case)] +pub extern "C" fn Java_io_zenoh_jni_JNIConfig_00024Companion_loadConfigFileViaJNI( + mut env: JNIEnv, + _class: JClass, + config_path: JString, +) -> *const Config { + || -> ZResult<*const Config> { + let config_file_path = decode_string(&mut env, &config_path)?; + let config = Config::from_file(config_file_path).map_err(|err| zerror!(err))?; + Ok(Arc::into_raw(Arc::new(config))) + }() + .unwrap_or_else(|err| { + throw_exception!(env, err); + null() + }) +} + +/// Loads the config from a json/json5 formatted string, returning a pointer to the loaded config +/// in case of success. In case of failure, an exception is thrown via JNI. +/// +/// The pointer to the config is expected to be freed later on upon the destruction of the +/// Kotlin Config instance. +/// +#[no_mangle] +#[allow(non_snake_case)] +pub extern "C" fn Java_io_zenoh_jni_JNIConfig_00024Companion_loadJsonConfigViaJNI( + mut env: JNIEnv, + _class: JClass, + json_config: JString, +) -> *const Config { + || -> ZResult<*const Config> { + let json_config = decode_string(&mut env, &json_config)?; + let mut deserializer = + json5::Deserializer::from_str(&json_config).map_err(|err| zerror!(err))?; + let config = Config::from_deserializer(&mut deserializer).map_err(|err| match err { + Ok(c) => zerror!("Invalid configuration: {}", c), + Err(e) => zerror!("JSON error: {}", e), + })?; + Ok(Arc::into_raw(Arc::new(config))) + }() + .unwrap_or_else(|err| { + throw_exception!(env, err); + null() + }) +} + +/// Loads the config from a yaml-formatted string, returning a pointer to the loaded config +/// in case of success. In case of failure, an exception is thrown via JNI. +/// +/// The pointer to the config is expected to be freed later on upon the destruction of the +/// Kotlin Config instance. +/// +#[no_mangle] +#[allow(non_snake_case)] +pub extern "C" fn Java_io_zenoh_jni_JNIConfig_00024Companion_loadYamlConfigViaJNI( + mut env: JNIEnv, + _class: JClass, + yaml_config: JString, +) -> *const Config { + || -> ZResult<*const Config> { + let yaml_config = decode_string(&mut env, &yaml_config)?; + let deserializer = serde_yaml::Deserializer::from_str(&yaml_config); + let config = Config::from_deserializer(deserializer).map_err(|err| match err { + Ok(c) => zerror!("Invalid configuration: {}", c), + Err(e) => zerror!("YAML error: {}", e), + })?; + Ok(Arc::into_raw(Arc::new(config))) + }() + .unwrap_or_else(|err| { + throw_exception!(env, err); + null() + }) +} + +/// Returns the json value associated to the provided [key]. May throw an exception in case of failure, which must be handled +/// on the kotlin layer. +#[no_mangle] +#[allow(non_snake_case)] +pub unsafe extern "C" fn Java_io_zenoh_jni_JNIConfig_00024Companion_getJsonViaJNI( + mut env: JNIEnv, + _class: JClass, + cfg_ptr: *const Config, + key: JString, +) -> jstring { + let arc_cfg: Arc = Arc::from_raw(cfg_ptr); + let result = || -> ZResult { + let key = decode_string(&mut env, &key)?; + let json = arc_cfg.get_json(&key).map_err(|err| zerror!(err))?; + let java_json = env.new_string(json).map_err(|err| zerror!(err))?; + Ok(java_json.as_raw()) + }() + .unwrap_or_else(|err| { + throw_exception!(env, err); + JString::default().as_raw() + }); + std::mem::forget(arc_cfg); + result +} + +/// Inserts a json5 value associated to the provided [key]. May throw an exception in case of failure, which must be handled +/// on the kotlin layer. +#[no_mangle] +#[allow(non_snake_case)] +pub unsafe extern "C" fn Java_io_zenoh_jni_JNIConfig_00024Companion_insertJson5ViaJNI( + mut env: JNIEnv, + _class: JClass, + cfg_ptr: *const Config, + key: JString, + value: JString, +) { + || -> ZResult<()> { + let key = decode_string(&mut env, &key)?; + let value = decode_string(&mut env, &value)?; + let mut config = core::ptr::read(cfg_ptr); + let insert_result = config + .insert_json5(&key, &value) + .map_err(|err| zerror!(err)); + core::ptr::write(cfg_ptr as *mut _, config); + insert_result + }() + .unwrap_or_else(|err| { + throw_exception!(env, err); + }) +} + +/// Frees the pointer to the config. The pointer should be valid and should have been obtained through +/// one of the preceding `load` functions. This function should be called upon destruction of the kotlin +/// Config instance. +#[no_mangle] +#[allow(non_snake_case)] +pub(crate) unsafe extern "C" fn Java_io_zenoh_jni_JNIConfig_00024Companion_freePtrViaJNI( + _env: JNIEnv, + _: JClass, + config_ptr: *const Config, +) { + Arc::from_raw(config_ptr); +} diff --git a/zenoh-jni/src/errors.rs b/zenoh-jni/src/errors.rs index edca4ab8..23687d4c 100644 --- a/zenoh-jni/src/errors.rs +++ b/zenoh-jni/src/errors.rs @@ -26,71 +26,34 @@ macro_rules! throw_exception { } #[macro_export] -macro_rules! jni_error { +macro_rules! zerror { ($arg:expr) => { - Error::Jni($arg.to_string()) + $crate::errors::ZError($arg.to_string()) }; ($fmt:expr, $($arg:tt)*) => { - Error::Jni(format!($fmt, $($arg)*)) + $crate::errors::ZError(format!($fmt, $($arg)*)) }; } -#[macro_export] -macro_rules! session_error { - ($arg:expr) => { - $crate::errors::Error::Session($arg.to_string()) - }; - ($fmt:expr, $($arg:tt)*) => { - Error::Session(format!($fmt, $($arg)*)) - }; - -} - -#[macro_export] -macro_rules! key_expr_error { - ($arg:expr) => { - Error::KeyExpr($arg.to_string()) - }; - ($fmt:expr, $($arg:tt)*) => { - Error::KeyExpr(format!($fmt, $($arg)*)) - }; -} - -pub(crate) type Result = core::result::Result; +pub(crate) type ZResult = core::result::Result; #[derive(Debug)] -pub(crate) enum Error { - Session(String), - KeyExpr(String), - Jni(String), -} +pub(crate) struct ZError(pub String); -impl fmt::Display for Error { +impl fmt::Display for ZError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - Error::Session(msg) => write!(f, "{}", msg), - Error::KeyExpr(msg) => write!(f, "{}", msg), - Error::Jni(msg) => write!(f, "{}", msg), - } + write!(f, "{}", self.0) } } -impl Error { - fn get_associated_kotlin_exception(&self) -> String { - let class = match self { - Error::Session(_) => "io/zenoh/exceptions/SessionException", - Error::KeyExpr(_) => "io/zenoh/exceptions/KeyExprException", - Error::Jni(_) => "io/zenoh/exceptions/JNIException", - }; - class.to_string() - } +impl ZError { + const KOTLIN_EXCEPTION_NAME: &'static str = "io/zenoh/exceptions/ZError"; - pub fn throw_on_jvm(&self, env: &mut JNIEnv) -> Result<()> { - let exception_name = self.get_associated_kotlin_exception(); + pub fn throw_on_jvm(&self, env: &mut JNIEnv) -> ZResult<()> { let exception_class = env - .find_class(&exception_name) - .map_err(|err| jni_error!("Failed to retrieve exception class: {}", err))?; + .find_class(Self::KOTLIN_EXCEPTION_NAME) + .map_err(|err| zerror!("Failed to retrieve exception class: {}", err))?; env.throw_new(exception_class, self.to_string()) - .map_err(|err| jni_error!("Failed to throw exception: {}", err)) + .map_err(|err| zerror!("Failed to throw exception: {}", err)) } } diff --git a/zenoh-jni/src/key_expr.rs b/zenoh-jni/src/key_expr.rs index b4fd0fe9..06a1b6c6 100644 --- a/zenoh-jni/src/key_expr.rs +++ b/zenoh-jni/src/key_expr.rs @@ -16,14 +16,13 @@ use std::ops::Deref; use std::sync::Arc; use jni::objects::JClass; -use jni::sys::{jboolean, jstring}; +use jni::sys::{jboolean, jint, jstring}; use jni::{objects::JString, JNIEnv}; use zenoh::key_expr::KeyExpr; -use crate::errors::Error; -use crate::errors::Result; +use crate::errors::ZResult; use crate::utils::decode_string; -use crate::{jni_error, key_expr_error, throw_exception}; +use crate::{throw_exception, zerror}; /// Validates the provided `key_expr` to be a valid key expression, returning it back /// in case of success or throwing an exception in case of failure. @@ -40,7 +39,7 @@ pub extern "C" fn Java_io_zenoh_jni_JNIKeyExpr_00024Companion_tryFromViaJNI( _class: JClass, key_expr: JString, ) -> jstring { - decode_key_expr(&mut env, &key_expr) + validate_key_expr(&mut env, &key_expr) .map(|_| **key_expr) .unwrap_or_else(|err| { throw_exception!(env, err); @@ -67,7 +66,7 @@ pub extern "C" fn Java_io_zenoh_jni_JNIKeyExpr_00024Companion_autocanonizeViaJNI .and_then(|key_expr| { env.new_string(key_expr.to_string()) .map(|kexp| kexp.as_raw()) - .map_err(|err| jni_error!(err)) + .map_err(|err| zerror!(err)) }) .unwrap_or_else(|err| { throw_exception!(env, err); @@ -79,9 +78,9 @@ pub extern "C" fn Java_io_zenoh_jni_JNIKeyExpr_00024Companion_autocanonizeViaJNI /// /// # Params: /// - `key_expr_ptr_1`: Pointer to the key expression 1, differs from null only if it's a declared key expr. -/// - `key_expr_ptr_1`: String representation of the key expression 1. +/// - `key_expr_str_1`: String representation of the key expression 1. /// - `key_expr_ptr_2`: Pointer to the key expression 2, differs from null only if it's a declared key expr. -/// - `key_expr_ptr_2`: String representation of the key expression 2. +/// - `key_expr_str_2`: String representation of the key expression 2. /// /// # Safety /// - This function is marked as unsafe due to raw pointer manipulation, which happens only when providing @@ -99,7 +98,7 @@ pub unsafe extern "C" fn Java_io_zenoh_jni_JNIKeyExpr_00024Companion_intersectsV key_expr_ptr_2: /*nullable*/ *const KeyExpr<'static>, key_expr_str_2: JString, ) -> jboolean { - || -> Result { + || -> ZResult { let key_expr_1 = process_kotlin_key_expr(&mut env, &key_expr_str_1, key_expr_ptr_1)?; let key_expr_2 = process_kotlin_key_expr(&mut env, &key_expr_str_2, key_expr_ptr_2)?; Ok(key_expr_1.intersects(&key_expr_2) as jboolean) @@ -114,9 +113,9 @@ pub unsafe extern "C" fn Java_io_zenoh_jni_JNIKeyExpr_00024Companion_intersectsV /// /// # Params: /// - `key_expr_ptr_1`: Pointer to the key expression 1, differs from null only if it's a declared key expr. -/// - `key_expr_ptr_1`: String representation of the key expression 1. +/// - `key_expr_str_1`: String representation of the key expression 1. /// - `key_expr_ptr_2`: Pointer to the key expression 2, differs from null only if it's a declared key expr. -/// - `key_expr_ptr_2`: String representation of the key expression 2. +/// - `key_expr_str_2`: String representation of the key expression 2. /// /// # Safety /// - This function is marked as unsafe due to raw pointer manipulation, which happens only when providing @@ -134,7 +133,7 @@ pub unsafe extern "C" fn Java_io_zenoh_jni_JNIKeyExpr_00024Companion_includesVia key_expr_ptr_2: /*nullable*/ *const KeyExpr<'static>, key_expr_str_2: JString, ) -> jboolean { - || -> Result { + || -> ZResult { let key_expr_1 = process_kotlin_key_expr(&mut env, &key_expr_str_1, key_expr_ptr_1)?; let key_expr_2 = process_kotlin_key_expr(&mut env, &key_expr_str_2, key_expr_ptr_2)?; Ok(key_expr_1.includes(&key_expr_2) as jboolean) @@ -145,6 +144,120 @@ pub unsafe extern "C" fn Java_io_zenoh_jni_JNIKeyExpr_00024Companion_includesVia }) } +/// Returns the integer representation of the intersection level of the key expression 1 and key expression 2, +/// from the perspective of key expression 1. +/// +/// # Params: +/// - `key_expr_ptr_1`: Pointer to the key expression 1, differs from null only if it's a declared key expr. +/// - `key_expr_str_1`: String representation of the key expression 1. +/// - `key_expr_ptr_2`: Pointer to the key expression 2, differs from null only if it's a declared key expr. +/// - `key_expr_str_2`: String representation of the key expression 2. +/// +/// # Safety +/// - This function is marked as unsafe due to raw pointer manipulation, which happens only when providing +/// key expressions that were declared from a session (in that case the key expression has a pointer associated). +/// In that case, this function assumes the pointers are valid pointers to key expressions and those pointers +/// remain valid after the call to this function. +/// +#[no_mangle] +#[allow(non_snake_case)] +pub unsafe extern "C" fn Java_io_zenoh_jni_JNIKeyExpr_00024Companion_relationToViaJNI( + mut env: JNIEnv, + _: JClass, + key_expr_ptr_1: /*nullable*/ *const KeyExpr<'static>, + key_expr_str_1: JString, + key_expr_ptr_2: /*nullable*/ *const KeyExpr<'static>, + key_expr_str_2: JString, +) -> jint { + || -> ZResult { + let key_expr_1 = process_kotlin_key_expr(&mut env, &key_expr_str_1, key_expr_ptr_1)?; + let key_expr_2 = process_kotlin_key_expr(&mut env, &key_expr_str_2, key_expr_ptr_2)?; + Ok(key_expr_1.relation_to(&key_expr_2) as jint) + }() + .unwrap_or_else(|err| { + throw_exception!(env, err); + -1 as jint + }) +} + +/// Joins key expression 1 with key expression 2, where key_expr_2 is a string. Returns the string representation +/// of the result, or throws an exception in case of failure. +/// +/// # Params: +/// - `key_expr_ptr_1`: Pointer to the key expression 1, differs from null only if it's a declared key expr. +/// - `key_expr_ptr_1`: String representation of the key expression 1. +/// - `key_expr_2`: String representation of the key expression 2. +/// +/// # Safety +/// - This function is marked as unsafe due to raw pointer manipulation, which happens only when providing +/// key expressions that were declared from a session (in that case the key expression has a pointer associated). +/// In that case, this function assumes the pointers are valid pointers to key expressions and those pointers +/// remain valid after the call to this function. +/// +#[no_mangle] +#[allow(non_snake_case)] +pub unsafe extern "C" fn Java_io_zenoh_jni_JNIKeyExpr_00024Companion_joinViaJNI( + mut env: JNIEnv, + _class: JClass, + key_expr_ptr_1: /*nullable*/ *const KeyExpr<'static>, + key_expr_str_1: JString, + key_expr_2: JString, +) -> jstring { + || -> ZResult { + let key_expr_1 = process_kotlin_key_expr(&mut env, &key_expr_str_1, key_expr_ptr_1)?; + let key_expr_2_str = decode_string(&mut env, &key_expr_2)?; + let result = key_expr_1 + .join(key_expr_2_str.as_str()) + .map_err(|err| zerror!(err))?; + env.new_string(result.to_string()) + .map(|kexp| kexp.as_raw()) + .map_err(|err| zerror!(err)) + }() + .unwrap_or_else(|err| { + throw_exception!(env, err); + JString::default().as_raw() + }) +} + +/// Concats key_expr_1 with key_expr_2, where key_expr_2 is a string. Returns the string representation +/// of the result, or throws an exception in case of failure. +/// +/// # Params: +/// - `key_expr_ptr_1`: Pointer to the key expression 1, differs from null only if it's a declared key expr. +/// - `key_expr_ptr_1`: String representation of the key expression 1. +/// - `key_expr_2`: String representation of the key expression 2. +/// +/// # Safety +/// - This function is marked as unsafe due to raw pointer manipulation, which happens only when providing +/// key expressions that were declared from a session (in that case the key expression has a pointer associated). +/// In that case, this function assumes the pointers are valid pointers to key expressions and those pointers +/// remain valid after the call to this function. +/// +#[no_mangle] +#[allow(non_snake_case)] +pub unsafe extern "C" fn Java_io_zenoh_jni_JNIKeyExpr_00024Companion_concatViaJNI( + mut env: JNIEnv, + _class: JClass, + key_expr_ptr_1: /*nullable*/ *const KeyExpr<'static>, + key_expr_str_1: JString, + key_expr_2: JString, +) -> jstring { + || -> ZResult { + let key_expr_1 = process_kotlin_key_expr(&mut env, &key_expr_str_1, key_expr_ptr_1)?; + let key_expr_2_str = decode_string(&mut env, &key_expr_2)?; + let result = key_expr_1 + .concat(key_expr_2_str.as_str()) + .map_err(|err| zerror!(err))?; + env.new_string(result.to_string()) + .map(|kexp| kexp.as_raw()) + .map_err(|err| zerror!(err)) + }() + .unwrap_or_else(|err| { + throw_exception!(env, err); + JString::default().as_raw() + }) +} + /// Frees a declared key expression. /// /// # Parameters @@ -167,20 +280,20 @@ pub unsafe extern "C" fn Java_io_zenoh_jni_JNIKeyExpr_freePtrViaJNI( Arc::from_raw(key_expr_ptr); } -fn decode_key_expr(env: &mut JNIEnv, key_expr: &JString) -> Result> { +fn validate_key_expr(env: &mut JNIEnv, key_expr: &JString) -> ZResult> { let key_expr_str = decode_string(env, key_expr) - .map_err(|err| jni_error!("Unable to get key expression string value: '{}'.", err))?; + .map_err(|err| zerror!("Unable to get key expression string value: '{}'.", err))?; KeyExpr::try_from(key_expr_str) - .map_err(|err| key_expr_error!("Unable to create key expression: '{}'.", err)) + .map_err(|err| zerror!("Unable to create key expression: '{}'.", err)) } -fn autocanonize_key_expr(env: &mut JNIEnv, key_expr: &JString) -> Result> { +fn autocanonize_key_expr(env: &mut JNIEnv, key_expr: &JString) -> ZResult> { decode_string(env, key_expr) - .map_err(|err| jni_error!("Unable to get key expression string value: '{}'.", err)) + .map_err(|err| zerror!("Unable to get key expression string value: '{}'.", err)) .and_then(|key_expr_str| { KeyExpr::autocanonize(key_expr_str) - .map_err(|err| key_expr_error!("Unable to create key expression: '{}'", err)) + .map_err(|err| zerror!("Unable to create key expression: '{}'", err)) }) } @@ -192,14 +305,20 @@ fn autocanonize_key_expr(env: &mut JNIEnv, key_expr: &JString) -> Result, -) -> Result> { +) -> ZResult> { if key_expr_ptr.is_null() { - decode_key_expr(env, key_expr_str) - .map_err(|err| jni_error!("Unable to process key expression: '{}'.", err)) + let key_expr = decode_string(env, key_expr_str) + .map_err(|err| zerror!("Unable to get key expression string value: '{}'.", err))?; + Ok(KeyExpr::from_string_unchecked(key_expr)) } else { let key_expr = Arc::from_raw(key_expr_ptr); let key_expr_clone = key_expr.deref().clone(); diff --git a/zenoh-jni/src/lib.rs b/zenoh-jni/src/lib.rs index edfba47a..9faa3546 100644 --- a/zenoh-jni/src/lib.rs +++ b/zenoh-jni/src/lib.rs @@ -12,15 +12,20 @@ // ZettaScale Zenoh Team, // +mod config; mod errors; mod key_expr; +mod liveliness; mod logger; mod publisher; mod query; mod queryable; +mod scouting; mod session; mod subscriber; mod utils; +mod zbytes; +mod zenoh_id; // Test should be runned with `cargo test --no-default-features` #[test] diff --git a/zenoh-jni/src/liveliness.rs b/zenoh-jni/src/liveliness.rs new file mode 100644 index 00000000..8b05c925 --- /dev/null +++ b/zenoh-jni/src/liveliness.rs @@ -0,0 +1,242 @@ +// +// Copyright (c) 2023 ZettaScale Technology +// +// This program and the accompanying materials are made available under the +// terms of the Eclipse Public License 2.0 which is available at +// http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 +// which is available at https://www.apache.org/licenses/LICENSE-2.0. +// +// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 +// +// Contributors: +// ZettaScale Zenoh Team, +// + +use std::{ptr::null, sync::Arc, time::Duration}; + +use jni::{ + objects::{JByteArray, JClass, JObject, JString, JValue}, + sys::{jboolean, jint, jlong}, + JNIEnv, +}; + +use zenoh::{ + internal::runtime::ZRuntime, key_expr::KeyExpr, liveliness::LivelinessToken, + pubsub::Subscriber, sample::Sample, Session, Wait, +}; + +use crate::{ + errors::ZResult, + key_expr::process_kotlin_key_expr, + session::{on_reply_error, on_reply_success}, + throw_exception, + utils::{ + bytes_to_java_array, get_callback_global_ref, get_java_vm, load_on_close, + slice_to_java_string, + }, + zerror, +}; + +#[no_mangle] +#[allow(non_snake_case)] +pub extern "C" fn Java_io_zenoh_jni_JNILiveliness_getViaJNI( + mut env: JNIEnv, + _class: JClass, + session_ptr: *const Session, + key_expr_ptr: /*nullable*/ *const KeyExpr<'static>, + key_expr_str: JString, + callback: JObject, + timeout_ms: jlong, + on_close: JObject, +) { + let session = unsafe { Arc::from_raw(session_ptr) }; + let _ = || -> ZResult<()> { + let key_expr = unsafe { process_kotlin_key_expr(&mut env, &key_expr_str, key_expr_ptr) }?; + let java_vm = Arc::new(get_java_vm(&mut env)?); + let callback_global_ref = get_callback_global_ref(&mut env, callback)?; + let on_close_global_ref = get_callback_global_ref(&mut env, on_close)?; + let on_close = load_on_close(&java_vm, on_close_global_ref); + let timeout = Duration::from_millis(timeout_ms as u64); + let replies = session + .liveliness() + .get(key_expr.to_owned()) + .timeout(timeout) + .wait() + .map_err(|err| zerror!(err))?; + + ZRuntime::Application.spawn(async move { + on_close.noop(); // Does nothing, but moves `on_close` inside the closure so it gets destroyed with the closure + while let Ok(reply) = replies.recv_async().await { + || -> ZResult<()> { + tracing::debug!("Receiving liveliness reply through JNI: {:?}", reply); + let mut env = java_vm.attach_current_thread_as_daemon().map_err(|err| { + zerror!( + "Unable to attach thread for GET liveliness query callback: {}.", + err + ) + })?; + match reply.result() { + Ok(sample) => on_reply_success( + &mut env, + reply.replier_id(), + sample, + &callback_global_ref, + ), + Err(error) => on_reply_error( + &mut env, + reply.replier_id(), + error, + &callback_global_ref, + ), + } + }() + .unwrap_or_else(|err| tracing::error!("Error on get liveliness callback: {err}.")); + } + }); + Ok(()) + }() + .map_err(|err| { + throw_exception!(env, err); + }); + std::mem::forget(session); +} + +#[no_mangle] +#[allow(non_snake_case)] +pub extern "C" fn Java_io_zenoh_jni_JNILiveliness_declareTokenViaJNI( + mut env: JNIEnv, + _class: JClass, + session_ptr: *const Session, + key_expr_ptr: /*nullable*/ *const KeyExpr<'static>, + key_expr_str: JString, +) -> *const LivelinessToken { + let session = unsafe { Arc::from_raw(session_ptr) }; + let ptr = || -> ZResult<*const LivelinessToken> { + let key_expr = unsafe { process_kotlin_key_expr(&mut env, &key_expr_str, key_expr_ptr) }?; + tracing::trace!("Declaring liveliness token on '{key_expr}'."); + let token = session + .liveliness() + .declare_token(key_expr) + .wait() + .map_err(|err| zerror!(err))?; + Ok(Arc::into_raw(Arc::new(token))) + }() + .unwrap_or_else(|err| { + throw_exception!(env, err); + null() + }); + std::mem::forget(session); + ptr +} + +#[no_mangle] +#[allow(non_snake_case)] +pub extern "C" fn Java_io_zenoh_jni_JNILivelinessToken_00024Companion_undeclareViaJNI( + _env: JNIEnv, + _: JClass, + token_ptr: *const LivelinessToken, +) { + unsafe { Arc::from_raw(token_ptr) }; +} + +#[no_mangle] +#[allow(non_snake_case)] +pub extern "C" fn Java_io_zenoh_jni_JNILiveliness_declareSubscriberViaJNI( + mut env: JNIEnv, + _class: JClass, + session_ptr: *const Session, + key_expr_ptr: /*nullable*/ *const KeyExpr<'static>, + key_expr_str: JString, + callback: JObject, + history: jboolean, + on_close: JObject, +) -> *const Subscriber<()> { + let session = unsafe { Arc::from_raw(session_ptr) }; + || -> ZResult<*const Subscriber<()>> { + let java_vm = Arc::new(get_java_vm(&mut env)?); + let callback_global_ref = get_callback_global_ref(&mut env, callback)?; + let on_close_global_ref = get_callback_global_ref(&mut env, on_close)?; + let on_close = load_on_close(&java_vm, on_close_global_ref); + + let key_expr = unsafe { process_kotlin_key_expr(&mut env, &key_expr_str, key_expr_ptr) }?; + tracing::debug!("Declaring liveliness subscriber on '{}'...", key_expr); + + let result = session + .liveliness() + .declare_subscriber(key_expr.to_owned()) + .history(history != 0) + .callback(move |sample: Sample| { + let _ = || -> ZResult<()> { + on_close.noop(); // Does nothing, but moves `on_close` inside the closure so it gets destroyed with the closure + let mut env = java_vm.attach_current_thread_as_daemon().map_err(|err| { + zerror!("Unable to attach thread for liveliness subscriber: {}", err) + })?; + let byte_array = bytes_to_java_array(&env, sample.payload()) + .map(|array| env.auto_local(array))?; + + let encoding_id: jint = sample.encoding().id() as jint; + let encoding_schema = match sample.encoding().schema() { + Some(schema) => slice_to_java_string(&env, schema)?, + None => JString::default(), + }; + let kind = sample.kind() as jint; + let (timestamp, is_valid) = sample + .timestamp() + .map(|timestamp| (timestamp.get_time().as_u64(), true)) + .unwrap_or((0, false)); + + let attachment_bytes = sample + .attachment() + .map_or_else( + || Ok(JByteArray::default()), + |attachment| bytes_to_java_array(&env, attachment), + ) + .map(|array| env.auto_local(array)) + .map_err(|err| zerror!("Error processing attachment: {}", err))?; + + let key_expr_str = env.auto_local( + env.new_string(sample.key_expr().to_string()) + .map_err(|err| zerror!("Error processing sample key expr: {}", err))?, + ); + + let express = sample.express(); + let priority = sample.priority() as jint; + let cc = sample.congestion_control() as jint; + + env.call_method( + &callback_global_ref, + "run", + "(Ljava/lang/String;[BILjava/lang/String;IJZ[BZII)V", + &[ + JValue::from(&key_expr_str), + JValue::from(&byte_array), + JValue::from(encoding_id), + JValue::from(&encoding_schema), + JValue::from(kind), + JValue::from(timestamp as i64), + JValue::from(is_valid), + JValue::from(&attachment_bytes), + JValue::from(express), + JValue::from(priority), + JValue::from(cc), + ], + ) + .map_err(|err| zerror!(err))?; + Ok(()) + }() + .map_err(|err| tracing::error!("On liveliness subscriber callback error: {err}")); + }) + .wait(); + + let subscriber = + result.map_err(|err| zerror!("Unable to declare liveliness subscriber: {}", err))?; + + tracing::debug!("Subscriber declared on '{}'.", key_expr); + std::mem::forget(session); + Ok(Arc::into_raw(Arc::new(subscriber))) + }() + .unwrap_or_else(|err| { + throw_exception!(env, err); + null() + }) +} diff --git a/zenoh-jni/src/logger.rs b/zenoh-jni/src/logger.rs index 780e693e..73f5112c 100644 --- a/zenoh-jni/src/logger.rs +++ b/zenoh-jni/src/logger.rs @@ -17,34 +17,32 @@ use jni::{ JNIEnv, }; -use crate::{ - errors::{Error, Result}, - jni_error, throw_exception, -}; +use crate::{errors::ZResult, throw_exception, zerror}; /// Redirects the Rust logs either to logcat for Android systems or to the standard output (for non-Android systems). /// -/// This function is meant to be called from Java/Kotlin code through JNI. It takes a `log_level` -/// indicating the desired log level, which must be one of the following: "info", "debug", "warn", -/// "trace", or "error". +/// This function is meant to be called from Java/Kotlin code through JNI. It takes a `filter` +/// indicating the desired log level. +/// +/// See https://docs.rs/env_logger/latest/env_logger/index.html for accepted filter format. /// /// # Parameters: /// - `env`: The JNI environment. /// - `_class`: The JNI class. -/// - `log_level`: The log level java string indicating the desired log level. +/// - `filter`: The logs filter. /// /// # Errors: /// - If there is an error parsing the log level string, a `JNIException` is thrown on the JVM. /// #[no_mangle] #[allow(non_snake_case)] -pub extern "C" fn Java_io_zenoh_Logger_00024Companion_start( +pub extern "C" fn Java_io_zenoh_Logger_00024Companion_startLogsViaJNI( mut env: JNIEnv, _class: JClass, - log_level: JString, + filter: JString, ) { - || -> Result<()> { - let log_level = parse_log_level(&mut env, log_level)?; + || -> ZResult<()> { + let log_level = parse_filter(&mut env, filter)?; android_logd_logger::builder() .parse_filters(log_level.as_str()) .tag_target_strip() @@ -55,10 +53,10 @@ pub extern "C" fn Java_io_zenoh_Logger_00024Companion_start( .unwrap_or_else(|err| throw_exception!(env, err)) } -fn parse_log_level(env: &mut JNIEnv, log_level: JString) -> Result { - let log_level = env.get_string(&log_level).map_err(|err| jni_error!(err))?; +fn parse_filter(env: &mut JNIEnv, log_level: JString) -> ZResult { + let log_level = env.get_string(&log_level).map_err(|err| zerror!(err))?; log_level .to_str() .map(|level| Ok(level.to_string())) - .map_err(|err| jni_error!(err))? + .map_err(|err| zerror!(err))? } diff --git a/zenoh-jni/src/publisher.rs b/zenoh-jni/src/publisher.rs index c6607cd0..ead60c3f 100644 --- a/zenoh-jni/src/publisher.rs +++ b/zenoh-jni/src/publisher.rs @@ -21,11 +21,12 @@ use jni::{ }; use zenoh::{pubsub::Publisher, Wait}; +use crate::throw_exception; use crate::{ - errors::Result, + errors::ZResult, utils::{decode_byte_array, decode_encoding}, + zerror, }; -use crate::{session_error, throw_exception}; /// Performs a PUT operation on a Zenoh publisher via JNI. /// @@ -56,7 +57,7 @@ pub unsafe extern "C" fn Java_io_zenoh_jni_JNIPublisher_putViaJNI( publisher_ptr: *const Publisher<'static>, ) { let publisher = Arc::from_raw(publisher_ptr); - let _ = || -> Result<()> { + let _ = || -> ZResult<()> { let payload = decode_byte_array(&env, payload)?; let mut publication = publisher.put(payload); let encoding = decode_encoding(&mut env, encoding_id, &encoding_schema)?; @@ -65,7 +66,7 @@ pub unsafe extern "C" fn Java_io_zenoh_jni_JNIPublisher_putViaJNI( let attachment = decode_byte_array(&env, attachment)?; publication = publication.attachment::>(attachment) }; - publication.wait().map_err(|err| session_error!(err)) + publication.wait().map_err(|err| zerror!(err)) }() .map_err(|err| throw_exception!(env, err)); std::mem::forget(publisher); @@ -94,13 +95,13 @@ pub unsafe extern "C" fn Java_io_zenoh_jni_JNIPublisher_deleteViaJNI( publisher_ptr: *const Publisher<'static>, ) { let publisher = Arc::from_raw(publisher_ptr); - let _ = || -> Result<()> { + let _ = || -> ZResult<()> { let mut delete = publisher.delete(); if !attachment.is_null() { let attachment = decode_byte_array(&env, attachment)?; delete = delete.attachment::>(attachment) }; - delete.wait().map_err(|err| session_error!(err)) + delete.wait().map_err(|err| zerror!(err)) }() .map_err(|err| throw_exception!(env, err)); std::mem::forget(publisher) diff --git a/zenoh-jni/src/query.rs b/zenoh-jni/src/query.rs index f7b557ec..031b1d93 100644 --- a/zenoh-jni/src/query.rs +++ b/zenoh-jni/src/query.rs @@ -14,11 +14,9 @@ use std::sync::Arc; -use crate::{errors::Result, key_expr::process_kotlin_key_expr, throw_exception}; -use crate::{ - session_error, - utils::{decode_byte_array, decode_encoding}, -}; +use crate::utils::{decode_byte_array, decode_encoding}; +use crate::zerror; +use crate::{errors::ZResult, key_expr::process_kotlin_key_expr, throw_exception}; use jni::{ objects::{JByteArray, JClass, JString}, sys::{jboolean, jint, jlong}, @@ -27,10 +25,10 @@ use jni::{ use uhlc::ID; use zenoh::{ key_expr::KeyExpr, - prelude::Wait, qos::{CongestionControl, Priority}, query::Query, time::{Timestamp, NTP64}, + Wait, }; /// Replies with `success` to a Zenoh [Query] via JNI, freeing the query in the process. @@ -76,7 +74,7 @@ pub(crate) unsafe extern "C" fn Java_io_zenoh_jni_JNIQuery_replySuccessViaJNI( qos_priority: jint, qos_congestion_control: jint, ) { - let _ = || -> Result<()> { + let _ = || -> ZResult<()> { let query = Arc::from_raw(query_ptr); let key_expr = process_kotlin_key_expr(&mut env, &key_expr_str, key_expr_ptr)?; let payload = decode_byte_array(&env, payload)?; @@ -97,7 +95,7 @@ pub(crate) unsafe extern "C" fn Java_io_zenoh_jni_JNIQuery_replySuccessViaJNI( } else { reply_builder.congestion_control(CongestionControl::Drop) }; - reply_builder.wait().map_err(|err| session_error!(err)) + reply_builder.wait().map_err(|err| zerror!(err)) }() .map_err(|err| throw_exception!(env, err)); } @@ -129,14 +127,14 @@ pub(crate) unsafe extern "C" fn Java_io_zenoh_jni_JNIQuery_replyErrorViaJNI( encoding_id: jint, encoding_schema: /*nullable*/ JString, ) { - let _ = || -> Result<()> { + let _ = || -> ZResult<()> { let query = Arc::from_raw(query_ptr); let encoding = decode_encoding(&mut env, encoding_id, &encoding_schema)?; query .reply_err(decode_byte_array(&env, payload)?) .encoding(encoding) .wait() - .map_err(|err| session_error!(err)) + .map_err(|err| zerror!(err)) }() .map_err(|err| throw_exception!(env, err)); } @@ -178,7 +176,7 @@ pub(crate) unsafe extern "C" fn Java_io_zenoh_jni_JNIQuery_replyDeleteViaJNI( qos_priority: jint, qos_congestion_control: jint, ) { - let _ = || -> Result<()> { + let _ = || -> ZResult<()> { let query = Arc::from_raw(query_ptr); let key_expr = process_kotlin_key_expr(&mut env, &key_expr_str, key_expr_ptr)?; let mut reply_builder = query.reply_del(key_expr); @@ -196,7 +194,7 @@ pub(crate) unsafe extern "C" fn Java_io_zenoh_jni_JNIQuery_replyDeleteViaJNI( } else { reply_builder.congestion_control(CongestionControl::Drop) }; - reply_builder.wait().map_err(|err| session_error!(err)) + reply_builder.wait().map_err(|err| zerror!(err)) }() .map_err(|err| throw_exception!(env, err)); } diff --git a/zenoh-jni/src/queryable.rs b/zenoh-jni/src/queryable.rs index 07ed6e1b..5d2ddb1d 100644 --- a/zenoh-jni/src/queryable.rs +++ b/zenoh-jni/src/queryable.rs @@ -35,7 +35,7 @@ use zenoh::query::Queryable; pub(crate) unsafe extern "C" fn Java_io_zenoh_jni_JNIQueryable_freePtrViaJNI( _env: JNIEnv, _: JClass, - queryable_ptr: *const Queryable<'_, ()>, + queryable_ptr: *const Queryable<()>, ) { Arc::from_raw(queryable_ptr); } diff --git a/zenoh-jni/src/scouting.rs b/zenoh-jni/src/scouting.rs new file mode 100644 index 00000000..b0a665c1 --- /dev/null +++ b/zenoh-jni/src/scouting.rs @@ -0,0 +1,113 @@ +// +// Copyright (c) 2023 ZettaScale Technology +// +// This program and the accompanying materials are made available under the +// terms of the Eclipse Public License 2.0 which is available at +// http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 +// which is available at https://www.apache.org/licenses/LICENSE-2.0. +// +// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 +// +// Contributors: +// ZettaScale Zenoh Team, +// + +use std::{ptr::null, sync::Arc}; + +use jni::{ + objects::{GlobalRef, JClass, JList, JObject, JValue}, + sys::jint, + JNIEnv, +}; +use zenoh::{config::WhatAmIMatcher, Wait}; +use zenoh::{scouting::Scout, Config}; + +use crate::utils::{get_callback_global_ref, get_java_vm, load_on_close}; +use crate::{errors::ZResult, throw_exception, zerror}; + +/// Start a scout. +/// +/// # Params +/// - `whatAmI`: Ordinal value of the WhatAmI enum. +/// - `callback`: Callback to be executed whenever a hello message is received. +/// - `config_ptr`: Optional config pointer. +/// +/// Returns a pointer to the scout, which must be freed afterwards. +/// If starting the scout fails, an exception is thrown on the JVM, and a null pointer is returned. +/// +#[no_mangle] +#[allow(non_snake_case)] +pub unsafe extern "C" fn Java_io_zenoh_jni_JNIScout_00024Companion_scoutViaJNI( + mut env: JNIEnv, + _class: JClass, + whatAmI: jint, + callback: JObject, + on_close: JObject, + config_ptr: /*nullable=*/ *const Config, +) -> *const Scout<()> { + || -> ZResult<*const Scout<()>> { + let callback_global_ref = get_callback_global_ref(&mut env, callback)?; + let java_vm = Arc::new(get_java_vm(&mut env)?); + let on_close_global_ref: GlobalRef = get_callback_global_ref(&mut env, on_close)?; + let on_close = load_on_close(&java_vm, on_close_global_ref); + let whatAmIMatcher: WhatAmIMatcher = (whatAmI as u8).try_into().unwrap(); // The validity of the operation is guaranteed on the kotlin layer. + let config = if config_ptr.is_null() { + Config::default() + } else { + let arc_cfg = Arc::from_raw(config_ptr); + let config_clone = arc_cfg.as_ref().clone(); + std::mem::forget(arc_cfg); + config_clone + }; + zenoh::scout(whatAmIMatcher, config) + .callback(move |hello| { + on_close.noop(); // Moves `on_close` inside the closure so it gets destroyed with the closure + tracing::debug!("Received hello: {hello}"); + let _ = || -> jni::errors::Result<()> { + let mut env = java_vm.attach_current_thread_as_daemon()?; + let whatami = hello.whatami() as jint; + let zenoh_id = env + .byte_array_from_slice(&hello.zid().to_le_bytes()) + .map(|it| env.auto_local(it))?; + let locators = env + .new_object("java/util/ArrayList", "()V", &[]) + .map(|it| env.auto_local(it))?; + let jlist = JList::from_env(&mut env, &locators)?; + for value in hello.locators() { + let locator = env.new_string(value.as_str())?; + jlist.add(&mut env, &locator)?; + } + env.call_method( + &callback_global_ref, + "run", + "(I[BLjava/util/List;)V", + &[ + JValue::from(whatami), + JValue::from(&zenoh_id), + JValue::from(&locators), + ], + )?; + Ok(()) + }() + .map_err(|err| tracing::error!("Error while scouting: ${err}")); + }) + .wait() + .map(|scout| Arc::into_raw(Arc::new(scout))) + .map_err(|err| zerror!(err)) + }() + .unwrap_or_else(|err| { + throw_exception!(env, err); + null() + }) +} + +/// Frees the scout. +#[no_mangle] +#[allow(non_snake_case)] +pub(crate) unsafe extern "C" fn Java_io_zenoh_jni_JNIScout_00024Companion_freePtrViaJNI( + _env: JNIEnv, + _: JClass, + scout_ptr: *const Scout<()>, +) { + Arc::from_raw(scout_ptr); +} diff --git a/zenoh-jni/src/session.rs b/zenoh-jni/src/session.rs index 9240d82a..43017c0d 100644 --- a/zenoh-jni/src/session.rs +++ b/zenoh-jni/src/session.rs @@ -12,26 +12,26 @@ // ZettaScale Zenoh Team, // -use crate::errors::{Error, Result}; -use crate::key_expr::process_kotlin_key_expr; -use crate::{jni_error, utils::*}; -use crate::{session_error, throw_exception}; - -use jni::objects::{GlobalRef, JByteArray, JClass, JObject, JString, JValue}; -use jni::sys::{jboolean, jint, jlong}; -use jni::JNIEnv; -use std::mem; -use std::ops::Deref; -use std::ptr::null; -use std::sync::Arc; -use std::time::Duration; -use zenoh::config::{Config, ZenohId}; -use zenoh::key_expr::KeyExpr; -use zenoh::prelude::Wait; -use zenoh::pubsub::{Publisher, Subscriber}; -use zenoh::query::{Query, Queryable, ReplyError, Selector}; -use zenoh::sample::Sample; -use zenoh::session::{Session, SessionDeclarations}; +use std::{mem, ops::Deref, ptr::null, sync::Arc, time::Duration}; + +use jni::{ + objects::{GlobalRef, JByteArray, JClass, JList, JObject, JString, JValue}, + sys::{jboolean, jbyteArray, jint, jlong, jobject}, + JNIEnv, +}; +use zenoh::{ + config::Config, + key_expr::KeyExpr, + pubsub::{Publisher, Subscriber}, + query::{Query, Queryable, ReplyError, Selector}, + sample::Sample, + session::{Session, ZenohId}, + Wait, +}; + +use crate::{ + errors::ZResult, key_expr::process_kotlin_key_expr, throw_exception, utils::*, zerror, +}; /// Open a Zenoh session via JNI. /// @@ -48,17 +48,17 @@ use zenoh::session::{Session, SessionDeclarations}; /// #[no_mangle] #[allow(non_snake_case)] -pub extern "C" fn Java_io_zenoh_jni_JNISession_openSessionViaJNI( +pub unsafe extern "C" fn Java_io_zenoh_jni_JNISession_openSessionViaJNI( mut env: JNIEnv, _class: JClass, - config_path: /*nullable*/ JString, + config_ptr: *const Config, ) -> *const Session { - let session = open_session(&mut env, config_path); + let session = open_session(config_ptr); match session { Ok(session) => Arc::into_raw(Arc::new(session)), Err(err) => { tracing::error!("Unable to open session: {}", err); - throw_exception!(env, session_error!(err)); + throw_exception!(env, zerror!(err)); null() } } @@ -68,16 +68,13 @@ pub extern "C" fn Java_io_zenoh_jni_JNISession_openSessionViaJNI( /// /// If the config path provided is null then the default configuration is loaded. /// -fn open_session(env: &mut JNIEnv, config_path: JString) -> Result { - let config = if config_path.is_null() { - Config::default() - } else { - let config_file_path = decode_string(env, &config_path)?; - Config::from_file(config_file_path).map_err(|err| session_error!(err))? - }; - zenoh::open(config) +unsafe fn open_session(config_ptr: *const Config) -> ZResult { + let config = Arc::from_raw(config_ptr); + let result = zenoh::open(config.as_ref().clone()) .wait() - .map_err(|err| session_error!(err)) + .map_err(|err| zerror!(err)); + mem::forget(config); + result } /// Open a Zenoh session with a JSON configuration. @@ -91,7 +88,7 @@ fn open_session(env: &mut JNIEnv, config_path: JString) -> Result { /// # Parameters: /// - `env`: The JNI environment. /// - `_class`: The JNI class (parameter required by the JNI interface but unused). -/// - `json_config`: Nullable configuration as a JSON string. If null, the default configuration will be loaded. +/// - `json_config`: Configuration as a JSON string. /// #[no_mangle] #[allow(non_snake_case)] @@ -105,7 +102,7 @@ pub extern "C" fn Java_io_zenoh_jni_JNISession_openSessionWithJsonConfigViaJNI( Ok(session) => Arc::into_raw(Arc::new(session)), Err(err) => { tracing::error!("Unable to open session: {}", err); - throw_exception!(env, session_error!(err)); + throw_exception!(env, zerror!(err)); null() } } @@ -113,23 +110,58 @@ pub extern "C" fn Java_io_zenoh_jni_JNISession_openSessionWithJsonConfigViaJNI( /// Open a Zenoh session with the provided json configuration. /// -/// If the provided json config is null, then the default config is loaded. +fn open_session_with_json_config(env: &mut JNIEnv, json_config: JString) -> ZResult { + let json_config = decode_string(env, &json_config)?; + let mut deserializer = + json5::Deserializer::from_str(&json_config).map_err(|err| zerror!(err))?; + let config = Config::from_deserializer(&mut deserializer).map_err(|err| match err { + Ok(c) => zerror!("Invalid configuration: {}", c), + Err(e) => zerror!("JSON error: {}", e), + })?; + zenoh::open(config).wait().map_err(|err| zerror!(err)) +} + +/// Open a Zenoh session with a YAML configuration. /// -fn open_session_with_json_config(env: &mut JNIEnv, json_config: JString) -> Result { - let config = if json_config.is_null() { - Config::default() - } else { - let json_config = decode_string(env, &json_config)?; - let mut deserializer = - json5::Deserializer::from_str(&json_config).map_err(|err| session_error!(err))?; - Config::from_deserializer(&mut deserializer).map_err(|err| match err { - Ok(c) => session_error!("Invalid configuration: {}", c), - Err(e) => session_error!("JSON error: {}", e), - })? - }; - zenoh::open(config) - .wait() - .map_err(|err| session_error!(err)) +/// It returns an [Arc] raw pointer to the Zenoh Session, which should be stored as a private read-only attribute +/// of the session object in the Java/Kotlin code. Subsequent calls to other session functions will require +/// this raw pointer to retrieve the [Session] using `Arc::from_raw`. +/// +/// If opening the session fails, an exception is thrown on the JVM, and a null pointer is returned. +/// +/// # Parameters: +/// - `env`: The JNI environment. +/// - `_class`: The JNI class (parameter required by the JNI interface but unused). +/// - `yaml_config`: Configuration as a YAML string. +/// +#[no_mangle] +#[allow(non_snake_case)] +pub extern "C" fn Java_io_zenoh_jni_JNISession_openSessionWithYamlConfigViaJNI( + mut env: JNIEnv, + _class: JClass, + yaml_config: JString, +) -> *const Session { + let session = open_session_with_yaml_config(&mut env, yaml_config); + match session { + Ok(session) => Arc::into_raw(Arc::new(session)), + Err(err) => { + tracing::error!("Unable to open session: {}", err); + throw_exception!(env, zerror!(err)); + null() + } + } +} + +/// Open a Zenoh session with the provided yaml configuration. +/// +fn open_session_with_yaml_config(env: &mut JNIEnv, yaml_config: JString) -> ZResult { + let yaml_config = decode_string(env, &yaml_config)?; + let deserializer = serde_yaml::Deserializer::from_str(&yaml_config); + let config = Config::from_deserializer(deserializer).map_err(|err| match err { + Ok(c) => zerror!("Invalid configuration: {}", c), + Err(e) => zerror!("YAML error: {}", e), + })?; + zenoh::open(config).wait().map_err(|err| zerror!(err)) } /// Closes a Zenoh session via JNI. @@ -152,21 +184,7 @@ pub unsafe extern "C" fn Java_io_zenoh_jni_JNISession_closeSessionViaJNI( _class: JClass, session_ptr: *const Session, ) { - let ptr = Arc::try_unwrap(Arc::from_raw(session_ptr)); - match ptr { - Ok(session) => { - // Do nothing, the pointer will be freed. - } - Err(arc_session) => { - let ref_count = Arc::strong_count(&arc_session); - throw_exception!(env, session_error!( - "Attempted to close the session, but at least one strong reference to it is still alive - (ref count: {}). All the declared publishers, subscribers, and queryables need to be - dropped first.", - ref_count - )); - } - }; + Arc::from_raw(session_ptr); } /// Declare a Zenoh publisher via JNI. @@ -182,6 +200,7 @@ pub unsafe extern "C" fn Java_io_zenoh_jni_JNISession_closeSessionViaJNI( /// - `congestion_control`: The [zenoh::publisher::CongestionControl] configuration as an ordinal. /// - `priority`: The [zenoh::core::Priority] configuration as an ordinal. /// - `is_express`: The express config of the publisher (see [zenoh::prelude::QoSBuilderTrait]). +/// - `reliability`: The reliability value as an ordinal. /// /// # Returns: /// - A raw pointer to the declared Zenoh publisher or null in case of failure. @@ -204,21 +223,24 @@ pub unsafe extern "C" fn Java_io_zenoh_jni_JNISession_declarePublisherViaJNI( congestion_control: jint, priority: jint, is_express: jboolean, + reliability: jint, ) -> *const Publisher<'static> { let session = Arc::from_raw(session_ptr); - let publisher_ptr = || -> Result<*const Publisher<'static>> { + let publisher_ptr = || -> ZResult<*const Publisher<'static>> { let key_expr = process_kotlin_key_expr(&mut env, &key_expr_str, key_expr_ptr)?; let congestion_control = decode_congestion_control(congestion_control)?; let priority = decode_priority(priority)?; + let reliability = decode_reliability(reliability)?; let result = session .declare_publisher(key_expr) .congestion_control(congestion_control) .priority(priority) .express(is_express != 0) + .reliability(reliability) .wait(); match result { Ok(publisher) => Ok(Arc::into_raw(Arc::new(publisher))), - Err(err) => Err(session_error!(err)), + Err(err) => Err(zerror!(err)), } }() .unwrap_or_else(|err| { @@ -246,6 +268,7 @@ pub unsafe extern "C" fn Java_io_zenoh_jni_JNISession_declarePublisherViaJNI( /// - `priority`: The [Priority] mechanism specified. /// - `is_express`: The express flag. /// - `attachment`: Optional attachment encoded into a byte array. May be null. +/// - `reliability`: The reliability value as an ordinal. /// /// Safety: /// - The function is marked as unsafe due to raw pointer manipulation and JNI interaction. @@ -269,21 +292,24 @@ pub unsafe extern "C" fn Java_io_zenoh_jni_JNISession_putViaJNI( priority: jint, is_express: jboolean, attachment: JByteArray, + reliability: jint, ) { let session = Arc::from_raw(session_ptr); - let _ = || -> Result<()> { + let _ = || -> ZResult<()> { let key_expr = process_kotlin_key_expr(&mut env, &key_expr_str, key_expr_ptr)?; let payload = decode_byte_array(&env, payload)?; let encoding = decode_encoding(&mut env, encoding_id, &encoding_schema)?; let congestion_control = decode_congestion_control(congestion_control)?; let priority = decode_priority(priority)?; + let reliability = decode_reliability(reliability)?; let mut put_builder = session .put(&key_expr, payload) .congestion_control(congestion_control) .encoding(encoding) .express(is_express != 0) - .priority(priority); + .priority(priority) + .reliability(reliability); if !attachment.is_null() { let attachment = decode_byte_array(&env, attachment)?; @@ -293,7 +319,7 @@ pub unsafe extern "C" fn Java_io_zenoh_jni_JNISession_putViaJNI( put_builder .wait() .map(|_| tracing::trace!("Put on '{key_expr}'")) - .map_err(|err| session_error!(err)) + .map_err(|err| zerror!(err)) }() .map_err(|err| throw_exception!(env, err)); std::mem::forget(session); @@ -313,6 +339,7 @@ pub unsafe extern "C" fn Java_io_zenoh_jni_JNISession_putViaJNI( /// - `priority`: The [Priority] mechanism specified. /// - `is_express`: The express flag. /// - `attachment`: Optional attachment encoded into a byte array. May be null. +/// - `reliability`: The reliability value as an ordinal. /// /// Safety: /// - The function is marked as unsafe due to raw pointer manipulation and JNI interaction. @@ -334,18 +361,21 @@ pub unsafe extern "C" fn Java_io_zenoh_jni_JNISession_deleteViaJNI( priority: jint, is_express: jboolean, attachment: JByteArray, + reliability: jint, ) { let session = Arc::from_raw(session_ptr); - let _ = || -> Result<()> { + let _ = || -> ZResult<()> { let key_expr = process_kotlin_key_expr(&mut env, &key_expr_str, key_expr_ptr)?; let congestion_control = decode_congestion_control(congestion_control)?; let priority = decode_priority(priority)?; + let reliability = decode_reliability(reliability)?; let mut delete_builder = session .delete(&key_expr) .congestion_control(congestion_control) .express(is_express != 0) - .priority(priority); + .priority(priority) + .reliability(reliability); if !attachment.is_null() { let attachment = decode_byte_array(&env, attachment)?; @@ -355,7 +385,7 @@ pub unsafe extern "C" fn Java_io_zenoh_jni_JNISession_deleteViaJNI( delete_builder .wait() .map(|_| tracing::trace!("Delete on '{key_expr}'")) - .map_err(|err| session_error!(err)) + .map_err(|err| zerror!(err)) }() .map_err(|err| throw_exception!(env, err)); std::mem::forget(session); @@ -373,7 +403,6 @@ pub unsafe extern "C" fn Java_io_zenoh_jni_JNISession_deleteViaJNI( /// - `session_ptr`: The raw pointer to the Zenoh session. /// - `callback`: The callback function as an instance of the `JNISubscriberCallback` interface in Java/Kotlin. /// - `on_close`: A Java/Kotlin `JNIOnCloseCallback` function interface to be called upon closing the subscriber. -/// - `reliability`: The reliability value as an ordinal. /// /// Returns: /// - A raw pointer to the declared Zenoh subscriber. In case of failure, an exception is thrown and null is returned. @@ -397,14 +426,12 @@ pub unsafe extern "C" fn Java_io_zenoh_jni_JNISession_declareSubscriberViaJNI( session_ptr: *const Session, callback: JObject, on_close: JObject, - reliability: jint, -) -> *const Subscriber<'static, ()> { +) -> *const Subscriber<()> { let session = Arc::from_raw(session_ptr); - || -> Result<*const Subscriber<'static, ()>> { + || -> ZResult<*const Subscriber<()>> { let java_vm = Arc::new(get_java_vm(&mut env)?); let callback_global_ref = get_callback_global_ref(&mut env, callback)?; let on_close_global_ref = get_callback_global_ref(&mut env, on_close)?; - let reliability = decode_reliability(reliability)?; let on_close = load_on_close(&java_vm, on_close_global_ref); let key_expr = process_kotlin_key_expr(&mut env, &key_expr_str, key_expr_ptr)?; @@ -412,11 +439,11 @@ pub unsafe extern "C" fn Java_io_zenoh_jni_JNISession_declareSubscriberViaJNI( let result = session .declare_subscriber(key_expr.to_owned()) - .callback(move |sample| { + .callback(move |sample: Sample| { on_close.noop(); // Moves `on_close` inside the closure so it gets destroyed with the closure - let _ = || -> Result<()> { + let _ = || -> ZResult<()> { let mut env = java_vm.attach_current_thread_as_daemon().map_err(|err| { - jni_error!("Unable to attach thread for subscriber: {}", err) + zerror!("Unable to attach thread for subscriber: {}", err) })?; let byte_array = bytes_to_java_array(&env, sample.payload()) .map(|array| env.auto_local(array))?; @@ -439,12 +466,12 @@ pub unsafe extern "C" fn Java_io_zenoh_jni_JNISession_declareSubscriberViaJNI( |attachment| bytes_to_java_array(&env, attachment), ) .map(|array| env.auto_local(array)) - .map_err(|err| jni_error!("Error processing attachment: {}", err))?; + .map_err(|err| zerror!("Error processing attachment: {}", err))?; - let key_expr_str = - env.auto_local(env.new_string(sample.key_expr().to_string()).map_err( - |err| jni_error!("Error processing sample key expr: {}", err), - )?); + let key_expr_str = env.auto_local( + env.new_string(sample.key_expr().to_string()) + .map_err(|err| zerror!("Error processing sample key expr: {}", err))?, + ); let express = sample.express(); let priority = sample.priority() as jint; @@ -468,22 +495,16 @@ pub unsafe extern "C" fn Java_io_zenoh_jni_JNISession_declareSubscriberViaJNI( JValue::from(cc), ], ) - .map_err(|err| jni_error!(err))?; + .map_err(|err| zerror!(err))?; Ok(()) }() .map_err(|err| tracing::error!("On subscriber callback error: {err}")); }) - .reliability(reliability) .wait(); - let subscriber = - result.map_err(|err| session_error!("Unable to declare subscriber: {}", err))?; + let subscriber = result.map_err(|err| zerror!("Unable to declare subscriber: {}", err))?; - tracing::debug!( - "Subscriber declared on '{}' with reliability '{:?}'.", - key_expr, - reliability - ); + tracing::debug!("Subscriber declared on '{}'.", key_expr); std::mem::forget(session); Ok(Arc::into_raw(Arc::new(subscriber))) }() @@ -532,9 +553,9 @@ pub unsafe extern "C" fn Java_io_zenoh_jni_JNISession_declareQueryableViaJNI( callback: JObject, on_close: JObject, complete: jboolean, -) -> *const Queryable<'static, ()> { +) -> *const Queryable<()> { let session = Arc::from_raw(session_ptr); - let query_ptr = || -> Result<*const Queryable<'static, ()>> { + let query_ptr = || -> ZResult<*const Queryable<()>> { let java_vm = Arc::new(get_java_vm(&mut env)?); let callback_global_ref = get_callback_global_ref(&mut env, callback)?; let on_close_global_ref = get_callback_global_ref(&mut env, on_close)?; @@ -544,7 +565,7 @@ pub unsafe extern "C" fn Java_io_zenoh_jni_JNISession_declareQueryableViaJNI( tracing::debug!("Declaring queryable through JNI on {}", key_expr); let builder = session .declare_queryable(key_expr) - .callback(move |query| { + .callback(move |query: Query| { on_close.noop(); // Does nothing, but moves `on_close` inside the closure so it gets destroyed with the closure let env = match java_vm.attach_current_thread_as_daemon() { Ok(env) => env, @@ -564,7 +585,7 @@ pub unsafe extern "C" fn Java_io_zenoh_jni_JNISession_declareQueryableViaJNI( let queryable = builder .wait() - .map_err(|err| session_error!("Error declaring queryable: {}", err))?; + .map_err(|err| zerror!("Error declaring queryable: {}", err))?; Ok(Arc::into_raw(Arc::new(queryable))) }() .unwrap_or_else(|err| { @@ -575,19 +596,18 @@ pub unsafe extern "C" fn Java_io_zenoh_jni_JNISession_declareQueryableViaJNI( query_ptr } -fn on_query(mut env: JNIEnv, query: Query, callback_global_ref: &GlobalRef) -> Result<()> { +fn on_query(mut env: JNIEnv, query: Query, callback_global_ref: &GlobalRef) -> ZResult<()> { let selector_params_jstr = env .new_string(query.parameters().to_string()) .map(|value| env.auto_local(value)) .map_err(|err| { - jni_error!( + zerror!( "Could not create a JString through JNI for the Query key expression. {}", err ) })?; - let (with_value, payload, encoding_id, encoding_schema) = if let Some(payload) = query.payload() - { + let (payload, encoding_id, encoding_schema) = if let Some(payload) = query.payload() { let encoding = query.encoding().unwrap(); //If there is payload, there is encoding. let encoding_id = encoding.id() as jint; let encoding_schema = encoding @@ -598,10 +618,9 @@ fn on_query(mut env: JNIEnv, query: Query, callback_global_ref: &GlobalRef) -> R ) .map(|value| env.auto_local(value))?; let byte_array = bytes_to_java_array(&env, payload).map(|value| env.auto_local(value))?; - (true, byte_array, encoding_id, encoding_schema) + (byte_array, encoding_id, encoding_schema) } else { ( - false, env.auto_local(JByteArray::default()), 0, env.auto_local(JString::default()), @@ -615,13 +634,13 @@ fn on_query(mut env: JNIEnv, query: Query, callback_global_ref: &GlobalRef) -> R |attachment| bytes_to_java_array(&env, attachment), ) .map(|value| env.auto_local(value)) - .map_err(|err| jni_error!("Error processing attachment of reply: {}.", err))?; + .map_err(|err| zerror!("Error processing attachment of reply: {}.", err))?; let key_expr_str = env .new_string(&query.key_expr().to_string()) .map(|key_expr| env.auto_local(key_expr)) .map_err(|err| { - jni_error!( + zerror!( "Could not create a JString through JNI for the Query key expression: {}.", err ) @@ -633,11 +652,10 @@ fn on_query(mut env: JNIEnv, query: Query, callback_global_ref: &GlobalRef) -> R .call_method( callback_global_ref, "run", - "(Ljava/lang/String;Ljava/lang/String;Z[BILjava/lang/String;[BJ)V", + "(Ljava/lang/String;Ljava/lang/String;[BILjava/lang/String;[BJ)V", &[ JValue::from(&key_expr_str), JValue::from(&selector_params_jstr), - JValue::from(with_value), JValue::from(&payload), JValue::from(encoding_id), JValue::from(&encoding_schema), @@ -655,7 +673,7 @@ fn on_query(mut env: JNIEnv, query: Query, callback_global_ref: &GlobalRef) -> R Arc::from_raw(query_ptr); }; _ = env.exception_describe(); - jni_error!(err) + zerror!(err) }); result } @@ -687,13 +705,13 @@ pub unsafe extern "C" fn Java_io_zenoh_jni_JNISession_declareKeyExprViaJNI( key_expr_str: JString, ) -> *const KeyExpr<'static> { let session: Arc = Arc::from_raw(session_ptr); - let key_expr_ptr = || -> Result<*const KeyExpr<'static>> { + let key_expr_ptr = || -> ZResult<*const KeyExpr<'static>> { let key_expr_str = decode_string(&mut env, &key_expr_str)?; let key_expr = session .declare_keyexpr(key_expr_str.to_owned()) .wait() .map_err(|err| { - session_error!( + zerror!( "Unable to declare key expression '{}': {}", key_expr_str, err @@ -745,7 +763,7 @@ pub unsafe extern "C" fn Java_io_zenoh_jni_JNISession_undeclareKeyExprViaJNI( Err(err) => { throw_exception!( env, - session_error!("Unable to declare key expression '{}': {}", key_expr, err) + zerror!("Unable to declare key expression '{}': {}", key_expr, err) ); } } @@ -762,7 +780,7 @@ pub unsafe extern "C" fn Java_io_zenoh_jni_JNISession_undeclareKeyExprViaJNI( /// of using a non declared key expression, in which case the `key_expr_str` parameter will be used instead. /// - `key_expr_str`: String representation of the key expression to be used to declare the query. It is not /// considered if a `key_expr_ptr` is provided. -/// - `selector_params`: Parameters of the selector. +/// - `selector_params`: Optional parameters of the selector. /// - `session_ptr`: A raw pointer to the Zenoh [Session]. /// - `callback`: A Java/Kotlin callback to be called upon receiving a reply. /// - `on_close`: A Java/Kotlin `JNIOnCloseCallback` function interface to be called when no more replies will be received. @@ -770,11 +788,9 @@ pub unsafe extern "C" fn Java_io_zenoh_jni_JNISession_undeclareKeyExprViaJNI( /// - `target`: The query target as the ordinal of the enum. /// - `consolidation`: The consolidation mode as the ordinal of the enum. /// - `attachment`: An optional attachment encoded into a byte array. -/// - `with_value`: Boolean value to tell if a value must be included in the get operation. If true, -/// then the next params are valid. -/// - `payload`: The payload of the value. -/// - `encoding_id`: The encoding of the value payload. -/// - `encoding_schema`: The encoding schema of the value payload, may be null. +/// - `payload`: Optional payload for the query. +/// - `encoding_id`: The encoding of the payload. +/// - `encoding_schema`: The encoding schema of the payload, may be null. /// /// Safety: /// - The function is marked as unsafe due to raw pointer manipulation and JNI interaction. @@ -793,7 +809,7 @@ pub unsafe extern "C" fn Java_io_zenoh_jni_JNISession_getViaJNI( _class: JClass, key_expr_ptr: /*nullable*/ *const KeyExpr<'static>, key_expr_str: JString, - selector_params: JString, + selector_params: /*nullable*/ JString, session_ptr: *const Session, callback: JObject, on_close: JObject, @@ -801,31 +817,34 @@ pub unsafe extern "C" fn Java_io_zenoh_jni_JNISession_getViaJNI( target: jint, consolidation: jint, attachment: /*nullable*/ JByteArray, - with_value: jboolean, payload: /*nullable*/ JByteArray, encoding_id: jint, encoding_schema: /*nullable*/ JString, ) { let session = Arc::from_raw(session_ptr); - let _ = || -> Result<()> { + let _ = || -> ZResult<()> { let key_expr = process_kotlin_key_expr(&mut env, &key_expr_str, key_expr_ptr)?; let java_vm = Arc::new(get_java_vm(&mut env)?); let callback_global_ref = get_callback_global_ref(&mut env, callback)?; let on_close_global_ref = get_callback_global_ref(&mut env, on_close)?; let query_target = decode_query_target(target)?; let consolidation = decode_consolidation(consolidation)?; - let selector_params = decode_string(&mut env, &selector_params)?; let timeout = Duration::from_millis(timeout_ms as u64); let on_close = load_on_close(&java_vm, on_close_global_ref); - let selector = Selector::owned(&key_expr, &*selector_params); + let selector_params = if selector_params.is_null() { + String::new() + } else { + decode_string(&mut env, &selector_params)? + }; + let selector = Selector::owned(&key_expr, selector_params); let mut get_builder = session .get(selector) .callback(move |reply| { - || -> Result<()> { + || -> ZResult<()> { on_close.noop(); // Does nothing, but moves `on_close` inside the closure so it gets destroyed with the closure tracing::debug!("Receiving reply through JNI: {:?}", reply); let mut env = java_vm.attach_current_thread_as_daemon().map_err(|err| { - jni_error!("Unable to attach thread for GET query callback: {}.", err) + zerror!("Unable to attach thread for GET query callback: {}.", err) })?; match reply.result() { @@ -849,7 +868,7 @@ pub unsafe extern "C" fn Java_io_zenoh_jni_JNISession_getViaJNI( .timeout(timeout) .consolidation(consolidation); - if with_value != 0 { + if !payload.is_null() { let encoding = decode_encoding(&mut env, encoding_id, &encoding_schema)?; get_builder = get_builder.encoding(encoding); get_builder = get_builder.payload(decode_byte_array(&env, payload)?); @@ -863,24 +882,24 @@ pub unsafe extern "C" fn Java_io_zenoh_jni_JNISession_getViaJNI( get_builder .wait() .map(|_| tracing::trace!("Performing get on '{key_expr}'.",)) - .map_err(|err| session_error!(err)) + .map_err(|err| zerror!(err)) }() .map_err(|err| throw_exception!(env, err)); std::mem::forget(session); } -fn on_reply_success( +pub(crate) fn on_reply_success( env: &mut JNIEnv, replier_id: Option, sample: &Sample, callback_global_ref: &GlobalRef, -) -> Result<()> { +) -> ZResult<()> { let zenoh_id = replier_id .map_or_else( - || Ok(JString::default()), + || Ok(JByteArray::default()), |replier_id| { - env.new_string(replier_id.to_string()) - .map_err(|err| jni_error!(err)) + env.byte_array_from_slice(&replier_id.to_le_bytes()) + .map_err(|err| zerror!(err)) }, ) .map(|value| env.auto_local(value))?; @@ -910,13 +929,13 @@ fn on_reply_success( |attachment| bytes_to_java_array(env, attachment), ) .map(|value| env.auto_local(value)) - .map_err(|err| jni_error!("Error processing attachment of reply: {}.", err))?; + .map_err(|err| zerror!("Error processing attachment of reply: {}.", err))?; let key_expr_str = env .new_string(sample.key_expr().to_string()) .map(|value| env.auto_local(value)) .map_err(|err| { - jni_error!( + zerror!( "Could not create a JString through JNI for the Sample key expression. {}", err ) @@ -929,7 +948,7 @@ fn on_reply_success( let result = match env.call_method( callback_global_ref, "run", - "(Ljava/lang/String;ZLjava/lang/String;[BILjava/lang/String;IJZ[BZII)V", + "([BZLjava/lang/String;[BILjava/lang/String;IJZ[BZII)V", &[ JValue::from(&zenoh_id), JValue::from(true), @@ -949,24 +968,24 @@ fn on_reply_success( Ok(_) => Ok(()), Err(err) => { _ = env.exception_describe(); - Err(jni_error!("On GET callback error: {}", err)) + Err(zerror!("On GET callback error: {}", err)) } }; result } -fn on_reply_error( +pub(crate) fn on_reply_error( env: &mut JNIEnv, replier_id: Option, reply_error: &ReplyError, callback_global_ref: &GlobalRef, -) -> Result<()> { +) -> ZResult<()> { let zenoh_id = replier_id .map_or_else( - || Ok(JString::default()), + || Ok(JByteArray::default()), |replier_id| { - env.new_string(replier_id.to_string()) - .map_err(|err| jni_error!(err)) + env.byte_array_from_slice(&replier_id.to_le_bytes()) + .map_err(|err| zerror!(err)) }, ) .map(|value| env.auto_local(value))?; @@ -985,7 +1004,7 @@ fn on_reply_error( let result = match env.call_method( callback_global_ref, "run", - "(Ljava/lang/String;ZLjava/lang/String;[BILjava/lang/String;IJZ[BZII)V", + "([BZLjava/lang/String;[BILjava/lang/String;IJZ[BZII)V", &[ JValue::from(&zenoh_id), JValue::from(false), @@ -1006,8 +1025,87 @@ fn on_reply_error( Ok(_) => Ok(()), Err(err) => { _ = env.exception_describe(); - Err(jni_error!("On GET callback error: {}", err)) + Err(zerror!("On GET callback error: {}", err)) } }; result } + +/// Returns a list of zenoh ids as byte arrays corresponding to the peers connected to the session provided. +/// +#[no_mangle] +#[allow(non_snake_case)] +pub unsafe extern "C" fn Java_io_zenoh_jni_JNISession_getPeersZidViaJNI( + mut env: JNIEnv, + _class: JClass, + session_ptr: *const Session, +) -> jobject { + let session = Arc::from_raw(session_ptr); + let ids = { + let peers_zid = session.info().peers_zid().wait(); + let ids = peers_zid.collect::>(); + ids_to_java_list(&mut env, ids).map_err(|err| zerror!(err)) + } + .unwrap_or_else(|err| { + throw_exception!(env, err); + JObject::default().as_raw() + }); + std::mem::forget(session); + ids +} + +/// Returns a list of zenoh ids as byte arrays corresponding to the routers connected to the session provided. +/// +#[no_mangle] +#[allow(non_snake_case)] +pub unsafe extern "C" fn Java_io_zenoh_jni_JNISession_getRoutersZidViaJNI( + mut env: JNIEnv, + _class: JClass, + session_ptr: *const Session, +) -> jobject { + let session = Arc::from_raw(session_ptr); + let ids = { + let peers_zid = session.info().routers_zid().wait(); + let ids = peers_zid.collect::>(); + ids_to_java_list(&mut env, ids).map_err(|err| zerror!(err)) + } + .unwrap_or_else(|err| { + throw_exception!(env, err); + JObject::default().as_raw() + }); + std::mem::forget(session); + ids +} + +/// Returns the Zenoh ID as a byte array of the session. +#[no_mangle] +#[allow(non_snake_case)] +pub unsafe extern "C" fn Java_io_zenoh_jni_JNISession_getZidViaJNI( + mut env: JNIEnv, + _class: JClass, + session_ptr: *const Session, +) -> jbyteArray { + let session = Arc::from_raw(session_ptr); + let ids = { + let zid = session.info().zid().wait(); + env.byte_array_from_slice(&zid.to_le_bytes()) + .map(|x| x.as_raw()) + .map_err(|err| zerror!(err)) + } + .unwrap_or_else(|err| { + throw_exception!(env, err); + JByteArray::default().as_raw() + }); + std::mem::forget(session); + ids +} + +fn ids_to_java_list(env: &mut JNIEnv, ids: Vec) -> jni::errors::Result { + let array_list = env.new_object("java/util/ArrayList", "()V", &[])?; + let jlist = JList::from_env(env, &array_list)?; + for id in ids { + let value = &mut env.byte_array_from_slice(&id.to_le_bytes())?; + jlist.add(env, value)?; + } + Ok(array_list.as_raw()) +} diff --git a/zenoh-jni/src/utils.rs b/zenoh-jni/src/utils.rs index 96c705f9..aa8d0176 100644 --- a/zenoh-jni/src/utils.rs +++ b/zenoh-jni/src/utils.rs @@ -14,10 +14,7 @@ use std::sync::Arc; -use crate::{ - errors::{Error, Result}, - jni_error, session_error, throw_exception, -}; +use crate::{errors::ZResult, throw_exception, zerror}; use jni::{ objects::{JByteArray, JObject, JString}, sys::jint, @@ -26,19 +23,18 @@ use jni::{ use zenoh::{ bytes::{Encoding, ZBytes}, internal::buffers::ZSlice, - pubsub::Reliability, - qos::{CongestionControl, Priority}, + qos::{CongestionControl, Priority, Reliability}, query::{ConsolidationMode, QueryTarget}, }; /// Converts a JString into a rust String. -pub(crate) fn decode_string(env: &mut JNIEnv, string: &JString) -> Result { +pub(crate) fn decode_string(env: &mut JNIEnv, string: &JString) -> ZResult { let binding = env .get_string(string) - .map_err(|err| jni_error!("Error while retrieving JString: {}", err))?; + .map_err(|err| zerror!("Error while retrieving JString: {}", err))?; let value = binding .to_str() - .map_err(|err| jni_error!("Error decoding JString: {}", err))?; + .map_err(|err| zerror!("Error decoding JString: {}", err))?; Ok(value.to_string()) } @@ -46,99 +42,93 @@ pub(crate) fn decode_encoding( env: &mut JNIEnv, encoding: jint, schema: &JString, -) -> Result { +) -> ZResult { let schema: Option = if schema.is_null() { None } else { Some(decode_string(env, schema)?.into_bytes().into()) }; let encoding_id = - u16::try_from(encoding).map_err(|err| jni_error!("Failed to decode encoding: {}", err))?; + u16::try_from(encoding).map_err(|err| zerror!("Failed to decode encoding: {}", err))?; Ok(Encoding::new(encoding_id, schema)) } -pub(crate) fn get_java_vm(env: &mut JNIEnv) -> Result { +pub(crate) fn get_java_vm(env: &mut JNIEnv) -> ZResult { env.get_java_vm() - .map_err(|err| jni_error!("Unable to retrieve JVM reference: {}", err)) + .map_err(|err| zerror!("Unable to retrieve JVM reference: {}", err)) } pub(crate) fn get_callback_global_ref( env: &mut JNIEnv, callback: JObject, -) -> crate::errors::Result { +) -> crate::errors::ZResult { env.new_global_ref(callback) - .map_err(|err| jni_error!("Unable to get reference to the provided callback: {}", err)) + .map_err(|err| zerror!("Unable to get reference to the provided callback: {}", err)) } /// Helper function to convert a JByteArray into a Vec. -pub(crate) fn decode_byte_array(env: &JNIEnv<'_>, payload: JByteArray) -> Result> { +pub(crate) fn decode_byte_array(env: &JNIEnv<'_>, payload: JByteArray) -> ZResult> { let payload_len = env .get_array_length(&payload) .map(|length| length as usize) - .map_err(|err| jni_error!(err))?; + .map_err(|err| zerror!(err))?; let mut buff = vec![0; payload_len]; env.get_byte_array_region(payload, 0, &mut buff[..]) - .map_err(|err| jni_error!(err))?; + .map_err(|err| zerror!(err))?; let buff: Vec = unsafe { std::mem::transmute::, Vec>(buff) }; Ok(buff) } -pub(crate) fn decode_priority(priority: jint) -> Result { - Priority::try_from(priority as u8) - .map_err(|err| session_error!("Error retrieving priority: {}.", err)) +pub(crate) fn decode_priority(priority: jint) -> ZResult { + Priority::try_from(priority as u8).map_err(|err| zerror!("Error retrieving priority: {}.", err)) } -pub(crate) fn decode_congestion_control(congestion_control: jint) -> Result { +pub(crate) fn decode_congestion_control(congestion_control: jint) -> ZResult { match congestion_control { 1 => Ok(CongestionControl::Block), 0 => Ok(CongestionControl::Drop), - value => Err(session_error!("Unknown congestion control '{}'.", value)), + value => Err(zerror!("Unknown congestion control '{}'.", value)), } } -pub(crate) fn decode_query_target(target: jint) -> Result { +pub(crate) fn decode_query_target(target: jint) -> ZResult { match target { 0 => Ok(QueryTarget::BestMatching), 1 => Ok(QueryTarget::All), 2 => Ok(QueryTarget::AllComplete), - value => Err(session_error!("Unable to decode QueryTarget '{}'.", value)), + value => Err(zerror!("Unable to decode QueryTarget '{}'.", value)), } } -pub(crate) fn decode_consolidation(consolidation: jint) -> Result { +pub(crate) fn decode_consolidation(consolidation: jint) -> ZResult { match consolidation { 0 => Ok(ConsolidationMode::Auto), 1 => Ok(ConsolidationMode::None), 2 => Ok(ConsolidationMode::Monotonic), 3 => Ok(ConsolidationMode::Latest), - value => Err(session_error!("Unable to decode consolidation '{}'", value)), + value => Err(zerror!("Unable to decode consolidation '{}'", value)), } } -pub(crate) fn decode_reliability(reliability: jint) -> Result { +pub(crate) fn decode_reliability(reliability: jint) -> ZResult { match reliability { 0 => Ok(Reliability::BestEffort), 1 => Ok(Reliability::Reliable), - value => Err(session_error!("Unable to decode reliability '{}'", value)), + value => Err(zerror!("Unable to decode reliability '{}'", value)), } } -pub(crate) fn bytes_to_java_array<'a>(env: &JNIEnv<'a>, slice: &ZBytes) -> Result> { - env.byte_array_from_slice( - slice - .deserialize::>() - .map_err(|err| session_error!("Unable to deserialize slice: {}", err))? - .as_ref(), - ) - .map_err(|err| jni_error!(err)) +pub(crate) fn bytes_to_java_array<'a>(env: &JNIEnv<'a>, slice: &ZBytes) -> ZResult> { + env.byte_array_from_slice(&slice.to_bytes()) + .map_err(|err| zerror!(err)) } -pub(crate) fn slice_to_java_string<'a>(env: &JNIEnv<'a>, slice: &ZSlice) -> Result> { +pub(crate) fn slice_to_java_string<'a>(env: &JNIEnv<'a>, slice: &ZSlice) -> ZResult> { env.new_string( String::from_utf8(slice.to_vec()) - .map_err(|err| session_error!("Unable to decode string: {}", err))?, + .map_err(|err| zerror!("Unable to decode string: {}", err))?, ) - .map_err(|err| jni_error!(err)) + .map_err(|err| zerror!(err)) } /// A type that calls a function when dropped @@ -182,7 +172,7 @@ pub(crate) fn load_on_close( _ = env.exception_describe(); throw_exception!( env, - jni_error!("Error while running 'onClose' callback: {}", err) + zerror!("Error while running 'onClose' callback: {}", err) ); } } diff --git a/zenoh-jni/src/zbytes.rs b/zenoh-jni/src/zbytes.rs new file mode 100644 index 00000000..627bb8b0 --- /dev/null +++ b/zenoh-jni/src/zbytes.rs @@ -0,0 +1,451 @@ +// +// Copyright (c) 2023 ZettaScale Technology +// +// This program and the accompanying materials are made available under the +// terms of the Eclipse Public License 2.0 which is available at +// http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 +// which is available at https://www.apache.org/licenses/LICENSE-2.0. +// +// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 +// +// Contributors: +// ZettaScale Zenoh Team, +// + +use jni::{ + objects::{JByteArray, JClass, JList, JMap, JObject, JObjectArray, JString, JValue}, + sys::jobject, + JNIEnv, +}; +use zenoh::bytes::ZBytes; +use zenoh_ext::{VarInt, ZDeserializeError, ZDeserializer, ZSerializer}; + +use crate::{ + errors::ZResult, + throw_exception, + utils::{bytes_to_java_array, decode_byte_array}, + zerror, +}; + +enum JavaType { + Boolean, + String, + ByteArray, + Byte, + Short, + Int, + Long, + Float, + Double, + List(Box), + Map(Box, Box), +} + +fn decode_token_type(env: &mut JNIEnv, type_obj: JObject) -> ZResult { + let type_name_jobject = env + .call_method(&type_obj, "getTypeName", "()Ljava/lang/String;", &[]) + .map_err(|err| zerror!(err))? + .l() + .map_err(|err| zerror!(err))?; + + let qualified_name: String = env + .get_string(&JString::from(type_name_jobject)) + .map_err(|err| zerror!(err))? + .into(); + + match qualified_name.as_str() { + "java.lang.Boolean" => Ok(JavaType::Boolean), + "java.lang.String" => Ok(JavaType::String), + "byte[]" => Ok(JavaType::ByteArray), + "java.lang.Byte" => Ok(JavaType::Byte), + "java.lang.Short" => Ok(JavaType::Short), + "java.lang.Integer" => Ok(JavaType::Int), + "java.lang.Long" => Ok(JavaType::Long), + "java.lang.Float" => Ok(JavaType::Float), + "java.lang.Double" => Ok(JavaType::Double), + _ => { + let type_token_class = env + .find_class("com/google/common/reflect/TypeToken") + .map_err(|err| zerror!(err))?; + let token_type = env + .call_static_method( + type_token_class, + "of", + "(Ljava/lang/reflect/Type;)Lcom/google/common/reflect/TypeToken;", + &[JValue::Object(&type_obj)], + ) + .map_err(|err| zerror!(err))? + .l() + .map_err(|err| zerror!(err))?; + let map_class: JObject = env + .find_class("java/util/Map") + .map_err(|err| zerror!(err))? + .into(); + let is_map_subtype = env + .call_method( + &token_type, + "isSubtypeOf", + "(Ljava/lang/reflect/Type;)Z", + &[JValue::Object(&map_class)], + ) + .map_err(|err| zerror!(err))? + .z() + .map_err(|err| zerror!(err))?; + + if is_map_subtype { + let args = env + .call_method( + &type_obj, + "getActualTypeArguments", + "()[Ljava/lang/reflect/Type;", + &[], + ) + .map_err(|err| zerror!(err))? + .l() + .map_err(|err| zerror!(err))?; + let jobject_array = JObjectArray::from(args); + let arg1 = env + .get_object_array_element(&jobject_array, 0) + .map_err(|err| zerror!(err))?; + let arg2 = env + .get_object_array_element(&jobject_array, 1) + .map_err(|err| zerror!(err))?; + + return Ok(JavaType::Map( + Box::new(decode_token_type(env, arg1)?), + Box::new(decode_token_type(env, arg2)?), + )); + } + + let list_class: JObject = env + .find_class("java/util/List") + .map_err(|err| zerror!(err))? + .into(); + let is_list_subtype = env + .call_method( + &token_type, + "isSubtypeOf", + "(Ljava/lang/reflect/Type;)Z", + &[JValue::Object(&list_class)], + ) + .map_err(|err| zerror!(err))? + .z() + .map_err(|err| zerror!(err))?; + + if is_list_subtype { + let args = env + .call_method( + &type_obj, + "getActualTypeArguments", + "()[Ljava/lang/reflect/Type;", + &[], + ) + .map_err(|err| zerror!(err))? + .l() + .map_err(|err| zerror!(err))?; + let jobject_array = JObjectArray::from(args); + let arg1 = env + .get_object_array_element(&jobject_array, 0) + .map_err(|err| zerror!(err))?; + + return Ok(JavaType::List(Box::new(decode_token_type(env, arg1)?))); + } + + Err(zerror!("Unsupported type: {}", qualified_name)) + } + } +} + +#[no_mangle] +#[allow(non_snake_case)] +pub extern "C" fn Java_io_zenoh_jni_JNIZBytes_serializeViaJNI( + mut env: JNIEnv, + _class: JClass, + any: JObject, + token_type: JObject, +) -> jobject { + || -> ZResult { + let mut serializer = ZSerializer::new(); + let jtype = decode_token_type(&mut env, token_type)?; + serialize(&mut env, &mut serializer, any, &jtype)?; + let zbytes = serializer.finish(); + + let byte_array = bytes_to_java_array(&env, &zbytes).map_err(|err| zerror!(err))?; + let zbytes_obj = env + .new_object( + "io/zenoh/bytes/ZBytes", + "([B)V", + &[JValue::Object(&JObject::from(byte_array))], + ) + .map_err(|err| zerror!(err))?; + + Ok(zbytes_obj.as_raw()) + }() + .unwrap_or_else(|err| { + throw_exception!(env, err); + JObject::default().as_raw() + }) +} + +fn serialize( + env: &mut JNIEnv, + serializer: &mut ZSerializer, + any: JObject, + jtype: &JavaType, +) -> ZResult<()> { + match jtype { + JavaType::Byte => { + let byte_value = env + .call_method(any, "byteValue", "()B", &[]) + .map_err(|err| zerror!(err))? + .b() + .map_err(|err| zerror!(err))?; + serializer.serialize(byte_value); + } + JavaType::Short => { + let short_value = env + .call_method(any, "shortValue", "()S", &[]) + .map_err(|err| zerror!(err))? + .s() + .map_err(|err| zerror!(err))?; + serializer.serialize(short_value); + } + JavaType::Int => { + let int_value = env + .call_method(any, "intValue", "()I", &[]) + .map_err(|err| zerror!(err))? + .i() + .map_err(|err| zerror!(err))?; + serializer.serialize(int_value); + } + JavaType::Long => { + let long_value = env + .call_method(any, "longValue", "()J", &[]) + .map_err(|err| zerror!(err))? + .j() + .map_err(|err| zerror!(err))?; + serializer.serialize(long_value); + } + JavaType::Float => { + let float_value = env + .call_method(any, "floatValue", "()F", &[]) + .map_err(|err| zerror!(err))? + .f() + .map_err(|err| zerror!(err))?; + serializer.serialize(float_value); + } + JavaType::Double => { + let double_value = env + .call_method(any, "doubleValue", "()D", &[]) + .map_err(|err| zerror!(err))? + .d() + .map_err(|err| zerror!(err))?; + serializer.serialize(double_value); + } + JavaType::Boolean => { + let boolean_value = env + .call_method(any, "booleanValue", "()Z", &[]) + .map_err(|err| zerror!(err))? + .z() + .map_err(|err| zerror!(err))?; + serializer.serialize(boolean_value); + } + JavaType::String => { + let jstring = JString::from(any); + let string_value: String = env.get_string(&jstring).map_err(|err| zerror!(err))?.into(); + serializer.serialize(string_value); + } + JavaType::ByteArray => { + let jbyte_array = JByteArray::from(any); + let bytes = decode_byte_array(env, jbyte_array).map_err(|err| zerror!(err))?; + serializer.serialize(bytes); + } + JavaType::List(kotlin_type) => { + let jlist: JList<'_, '_, '_> = + JList::from_env(env, &any).map_err(|err| zerror!(err))?; + let mut iterator = jlist.iter(env).map_err(|err| zerror!(err))?; + let list_size = jlist.size(env).map_err(|err| zerror!(err))?; + serializer.serialize(zenoh_ext::VarInt(list_size as usize)); + while let Some(value) = iterator.next(env).map_err(|err| zerror!(err))? { + serialize(env, serializer, value, kotlin_type)?; + } + } + JavaType::Map(key_type, value_type) => { + let jmap = JMap::from_env(env, &any).map_err(|err| zerror!(err))?; + + let map_size = env + .call_method(&jmap, "size", "()I", &[]) + .map_err(|err| zerror!(err))? + .i() + .map_err(|err| zerror!(err))?; + + serializer.serialize(zenoh_ext::VarInt(map_size as usize)); + + let mut iterator = jmap.iter(env).map_err(|err| zerror!(err))?; + while let Some((key, value)) = iterator.next(env).map_err(|err| zerror!(err))? { + serialize(env, serializer, key, key_type)?; + serialize(env, serializer, value, value_type)?; + } + } + } + Ok(()) +} + +#[no_mangle] +#[allow(non_snake_case)] +pub extern "C" fn Java_io_zenoh_jni_JNIZBytes_deserializeViaJNI( + mut env: JNIEnv, + _class: JClass, + zbytes: JObject, + jtype: JObject, +) -> jobject { + || -> ZResult { + let payload = env + .get_field(zbytes, "bytes", "[B") + .map_err(|err| zerror!(err))?; + let decoded_bytes: Vec = decode_byte_array( + &env, + JByteArray::from(payload.l().map_err(|err| zerror!(err))?), + )?; + let zbytes = ZBytes::from(decoded_bytes); + let mut deserializer = ZDeserializer::new(&zbytes); + let jtype = decode_token_type(&mut env, jtype)?; + let obj = deserialize(&mut env, &mut deserializer, &jtype)?; + if !deserializer.done() { + return Err(zerror!(ZDeserializeError)); + } + Ok(obj) + }() + .unwrap_or_else(|err| { + throw_exception!(env, err); + JObject::default().as_raw() + }) +} + +fn deserialize( + env: &mut JNIEnv, + deserializer: &mut ZDeserializer, + jtype: &JavaType, +) -> ZResult { + match jtype { + JavaType::Byte => { + let byte = deserializer + .deserialize::() + .map_err(|err| zerror!(err))?; + let byte_obj = env + .new_object("java/lang/Byte", "(B)V", &[JValue::Byte(byte)]) + .map_err(|err| zerror!(err))?; + Ok(byte_obj.as_raw()) + } + JavaType::Short => { + let short = deserializer + .deserialize::() + .map_err(|err| zerror!(err))?; + let short_obj = env + .new_object("java/lang/Short", "(S)V", &[JValue::Short(short)]) + .map_err(|err| zerror!(err))?; + Ok(short_obj.as_raw()) + } + JavaType::Int => { + let integer = deserializer + .deserialize::() + .map_err(|err| zerror!(err))?; + let integer_obj = env + .new_object("java/lang/Integer", "(I)V", &[JValue::Int(integer)]) + .map_err(|err| zerror!(err))?; + Ok(integer_obj.as_raw()) + } + JavaType::Long => { + let long = deserializer + .deserialize::() + .map_err(|err| zerror!(err))?; + let long_obj = env + .new_object("java/lang/Long", "(J)V", &[JValue::Long(long)]) + .map_err(|err| zerror!(err))?; + Ok(long_obj.as_raw()) + } + JavaType::Float => { + let float = deserializer + .deserialize::() + .map_err(|err| zerror!(err))?; + let float_obj = env + .new_object("java/lang/Float", "(F)V", &[JValue::Float(float)]) + .map_err(|err| zerror!(err))?; + Ok(float_obj.as_raw()) + } + JavaType::Double => { + let double = deserializer + .deserialize::() + .map_err(|err| zerror!(err))?; + let double_obj = env + .new_object("java/lang/Double", "(D)V", &[JValue::Double(double)]) + .map_err(|err| zerror!(err))?; + Ok(double_obj.as_raw()) + } + JavaType::Boolean => { + let boolean_value = deserializer + .deserialize::() + .map_err(|err| zerror!(err))?; + let jboolean = if boolean_value { 1u8 } else { 0u8 }; + let boolean_obj = env + .new_object("java/lang/Boolean", "(Z)V", &[JValue::Bool(jboolean)]) + .map_err(|err| zerror!(err))?; + Ok(boolean_obj.as_raw()) + } + JavaType::String => { + let deserialized_string = deserializer + .deserialize::() + .map_err(|err| zerror!(err))?; + let jstring = env + .new_string(&deserialized_string) + .map_err(|err| zerror!(err))?; + Ok(jstring.into_raw()) + } + JavaType::ByteArray => { + let deserialized_bytes = deserializer + .deserialize::>() + .map_err(|err| zerror!(err))?; + let jbytes = env + .byte_array_from_slice(deserialized_bytes.as_slice()) + .map_err(|err| zerror!(err))?; + Ok(jbytes.into_raw()) + } + JavaType::List(kotlin_type) => { + let list_size = deserializer + .deserialize::>() + .map_err(|err| zerror!(err))? + .0; + let array_list = env + .new_object("java/util/ArrayList", "()V", &[]) + .map_err(|err| zerror!(err))?; + let jlist = JList::from_env(env, &array_list).map_err(|err| zerror!(err))?; + + for _ in 0..list_size { + let item = deserialize(env, deserializer, kotlin_type)?; + let item_obj = unsafe { JObject::from_raw(item) }; + jlist.add(env, &item_obj).map_err(|err| zerror!(err))?; + } + Ok(array_list.as_raw()) + } + JavaType::Map(key_type, value_type) => { + let map_size = deserializer + .deserialize::>() + .map_err(|err| zerror!(err))? + .0; + let map = env + .new_object("java/util/HashMap", "()V", &[]) + .map_err(|err| zerror!(err))?; + let jmap = JMap::from_env(env, &map).map_err(|err| zerror!(err))?; + + for _ in 0..map_size { + let key = deserialize(env, deserializer, key_type)?; + let key_obj = unsafe { JObject::from_raw(key) }; + let value = deserialize(env, deserializer, value_type)?; + let value_obj = unsafe { JObject::from_raw(value) }; + jmap.put(env, &key_obj, &value_obj) + .map_err(|err| zerror!(err))?; + } + Ok(map.as_raw()) + } + } +} diff --git a/zenoh-jni/src/zenoh_id.rs b/zenoh-jni/src/zenoh_id.rs new file mode 100644 index 00000000..6647f86f --- /dev/null +++ b/zenoh-jni/src/zenoh_id.rs @@ -0,0 +1,42 @@ +// +// Copyright (c) 2023 ZettaScale Technology +// +// This program and the accompanying materials are made available under the +// terms of the Eclipse Public License 2.0 which is available at +// http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 +// which is available at https://www.apache.org/licenses/LICENSE-2.0. +// +// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 +// +// Contributors: +// ZettaScale Zenoh Team, +// + +use crate::{errors::ZResult, throw_exception, utils::decode_byte_array, zerror}; +use jni::{ + objects::{JByteArray, JClass, JString}, + sys::jstring, + JNIEnv, +}; +use zenoh::session::ZenohId; + +/// Returns the string representation of a ZenohID. +#[no_mangle] +#[allow(non_snake_case)] +pub extern "C" fn Java_io_zenoh_jni_JNIZenohId_toStringViaJNI( + mut env: JNIEnv, + _class: JClass, + zenoh_id: JByteArray, +) -> jstring { + || -> ZResult { + let bytes = decode_byte_array(&env, zenoh_id)?; + let zenohid = ZenohId::try_from(bytes.as_slice()).map_err(|err| zerror!(err))?; + env.new_string(zenohid.to_string()) + .map_err(|err| zerror!(err)) + }() + .unwrap_or_else(|err| { + throw_exception!(env, err); + JString::default() + }) + .as_raw() +}