Skip to content

Commit

Permalink
Config utilities (#14)
Browse files Browse the repository at this point in the history
* Config utilities - WIP

* [Config utilities] Passing json config file to Rust layer. WIP

* Testing programmatical configuration with mTLS.

* Failing tests - fix bug.

* Fix clippy error

* feat(config utilities): replacing config data classes by json loading.

* feat(config utilities): Fix Cargo.toml dependencies after rebase.
  • Loading branch information
DariusIMP authored Dec 15, 2023
1 parent 3d95776 commit 7fd8fe6
Show file tree
Hide file tree
Showing 8 changed files with 168 additions and 2 deletions.
1 change: 1 addition & 0 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ plugins {
id("org.mozilla.rust-android-gradle.rust-android") version "0.9.3" apply false
id("org.jetbrains.dokka") version "1.8.20" apply false
id("com.adarshr.test-logger") version "3.2.0" apply false
kotlin("plugin.serialization") version "1.9.0" apply false
}

subprojects {
Expand Down
1 change: 1 addition & 0 deletions zenoh-jni/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions zenoh-jni/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ clap = "3.2.23"
jni = "0.21.1"
flume = "0.10.14"
uhlc = "0.6.0"
json5 = "0.4.1"
zenoh = { git = "https://github.com/eclipse-zenoh/zenoh.git", branch = "master", default-features = false }
zenoh-ext = { git = "https://github.com/eclipse-zenoh/zenoh.git", branch = "master", default-features = false }

Expand Down
65 changes: 65 additions & 0 deletions zenoh-jni/src/session.rs
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,44 @@ pub extern "C" fn Java_io_zenoh_jni_JNISession_openSessionViaJNI(
}
}

/// Open a Zenoh session via JNI.
///
/// This function is meant to be called from Java/Kotlin through JNI.
///
/// Parameters:
/// - `env`: The JNI environment.
/// - `_class`: The JNI class (parameter required by the JNI interface but unused).
/// - `config`: The path to the Zenoh config file.
///
/// 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, a `SessionException` is thrown on the JVM, and a null pointer is returned.
///
#[no_mangle]
#[allow(non_snake_case)]
pub extern "C" fn Java_io_zenoh_jni_JNISession_openSessionWithJsonConfigViaJNI(
mut env: JNIEnv,
_class: JClass,
json_config: JString,
) -> *const zenoh::Session {
let session = open_session_with_json_config(&mut env, json_config);
match session {
Ok(session) => Arc::into_raw(Arc::new(session)),
Err(err) => {
log::error!("Unable to open session: {}", err);
_ = Error::Session(err.to_string())
.throw_on_jvm(&mut env)
.map_err(|err| {
log::error!("Unable to throw exception on session failure: {}", err)
});
null()
}
}
}

/// Open a Zenoh session.
///
/// Loads the session with the provided by [config_path]. If the config path provided is empty then
Expand All @@ -90,6 +128,33 @@ fn open_session(env: &mut JNIEnv, config_path: JString) -> Result<zenoh::Session
.map_err(|err| Error::Session(err.to_string()))
}

/// Open a Zenoh session.
///
/// Loads the session with the provided by [config_path]. If the config path provided is empty then
/// the default configuration is loaded.
///
/// Returns:
/// - A [Result] with a [zenoh::Session] in case of success or an [Error::Session] in case of failure.
///
fn open_session_with_json_config(env: &mut JNIEnv, json_config: JString) -> Result<zenoh::Session> {
let json_config = decode_string(env, json_config)?;
let config = if json_config.is_empty() {
Config::default()
} else {
let mut deserializer = match json5::Deserializer::from_str(&json_config) {
Ok(deserializer) => Ok(deserializer),
Err(err) => Err(Error::Session(err.to_string())),
}?;
Config::from_deserializer(&mut deserializer).map_err(|err| match err {
Ok(c) => Error::Session(format!("Invalid configuration: {}", c)),
Err(e) => Error::Session(format!("JSON error: {}", e)),
})?
};
zenoh::open(config)
.res()
.map_err(|err| Error::Session(err.to_string()))
}

/// Closes a Zenoh session via JNI.
///
/// This function is meant to be called from Java/Kotlin code through JNI.
Expand Down
2 changes: 2 additions & 0 deletions zenoh-kotlin/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ version = "0.11.0-dev"
plugins {
id("com.android.library")
kotlin("multiplatform")
kotlin("plugin.serialization")
id("com.adarshr.test-logger")
id("org.jetbrains.dokka")
id("org.mozilla.rust-android-gradle.rust-android")
Expand Down Expand Up @@ -96,6 +97,7 @@ kotlin {
dependencies {
implementation("commons-net:commons-net:3.9.0")
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.3")
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.6.0")
}
}
val commonTest by getting {
Expand Down
16 changes: 15 additions & 1 deletion zenoh-kotlin/src/commonMain/kotlin/io/zenoh/Config.kt
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,17 @@ package io.zenoh

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].
*
* @property path The path to the configuration file.
* @constructor Create empty Config
*/
class Config private constructor(internal val path: Path? = null) {
class Config private constructor(internal val path: Path? = null, internal val jsonConfig: JsonElement? = null) {

companion object {

Expand Down Expand Up @@ -51,5 +54,16 @@ class Config private constructor(internal val path: Path? = null) {
fun from(path: Path): Config {
return Config(path)
}

/**
* Loads the configuration from the [json] specified.
*
* @param json The zenoh raw zenoh config.
*/
fun from(json: String): Config {
return Config(jsonConfig = Json.decodeFromString(json))
}
}

constructor(jsonConfig: JsonElement) : this(null, jsonConfig = jsonConfig)
}
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,11 @@ internal class JNISession {
private var sessionPtr: AtomicLong = AtomicLong(0)

fun open(config: Config): Result<Unit> = runCatching {
sessionPtr.set(openSessionViaJNI(config.path?.toString().orEmpty()))
config.jsonConfig?.let { jsonConfig ->
sessionPtr.set(openSessionWithJsonConfigViaJNI(jsonConfig.toString()))
} ?: run {
sessionPtr.set(openSessionViaJNI(config.path?.toString().orEmpty()))
}
}

fun close(): Result<Unit> = runCatching {
Expand Down Expand Up @@ -188,6 +192,9 @@ internal class JNISession {
@Throws(Exception::class)
private external fun openSessionViaJNI(configFilePath: String): Long

@Throws(Exception::class)
private external fun openSessionWithJsonConfigViaJNI(jsonConfig: String): Long

@Throws(Exception::class)
private external fun closeSessionViaJNI(ptr: Long)

Expand Down
75 changes: 75 additions & 0 deletions zenoh-kotlin/src/commonTest/kotlin/io/zenoh/ConfigTest.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
//
// 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, <[email protected]>
//

package io.zenoh

import io.zenoh.keyexpr.intoKeyExpr
import io.zenoh.sample.Sample
import io.zenoh.value.Value
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertNotNull

class ConfigTest {
companion object {
val TEST_KEY_EXP = "example/testing/keyexpr".intoKeyExpr().getOrThrow()
}

@Test
fun configLoadsJsonTest() {
// There are a bunch of different possible configurations.
// For this test we do the following:
// - Modifying the default endpoints.
// - Disabling multicast scouting to avoid unintended connections.
val clientCfg = "{\n" +
" \"mode\": \"peer\",\n" +
" \"connect\": {\n" +
" \"endpoints\": [\"tcp/localhost:7450\"]\n" +
" },\n" +
" \"scouting\": {\n" +
" \"multicast\": {\n" +
" \"enabled\": false\n" +
" }\n" +
" }\n" +
"}\n"

val serverCfg = "{\n" +
" \"mode\": \"peer\",\n" +
" \"listen\": {\n" +
" \"endpoints\": [\"tcp/localhost:7450\"]\n" +
" },\n" +
" \"scouting\": {\n" +
" \"multicast\": {\n" +
" \"enabled\": false\n" +
" }\n" +
" }\n" +
"}\n"

val sessionClient = Session.open(Config.from(clientCfg)).getOrThrow()
val sessionServer = Session.open(Config.from(serverCfg)).getOrThrow()
var receivedSample: Sample? = null
val subscriber = sessionClient.declareSubscriber(TEST_KEY_EXP).with { sample -> receivedSample = sample }.res().getOrThrow()

val value = Value("encrypted_message")
sessionServer.put(TEST_KEY_EXP, value).res()
Thread.sleep(1000)

subscriber.close()
sessionClient.close()
sessionServer.close()

assertNotNull(receivedSample)
assertEquals(receivedSample!!.value, value)
}
}

0 comments on commit 7fd8fe6

Please sign in to comment.