원클릭으로
rust-jni-bridge
Rust JNI functions, Kotlin bindings, jni crate usage, and raw JNI versus UniFFI choices.
Codex 또는 Claude로 설치 이 Prompt를 복사해 Codex, Claude 또는 다른 어시스턴트에 붙여 넣으면 Skill 페이지를 검토하고 설치를 진행할 수 있습니다.
메뉴
Rust JNI functions, Kotlin bindings, jni crate usage, and raw JNI versus UniFFI choices.
Codex 또는 Claude로 설치 이 Prompt를 복사해 Codex, Claude 또는 다른 어시스턴트에 붙여 넣으면 Skill 페이지를 검토하고 설치를 진행할 수 있습니다.
SOC 직업 분류 기준
Use this skill to generate well-branded interfaces and assets for RIPDPI (an Android-native, Compose-first VPN and DPI-bypass app), either for production or throwaway prototypes/mocks/etc. Contains essential design guidelines, colors, type, fonts, brand-ship icons, and an interactive UI kit of every public composable from the live ui/components tree.
Use when managing the Rust workspace, adding/removing crates, editing workspace dependencies, running cargo nextest/audit/deny, configuring Cargo profiles for Android cross-compilation, debugging Cargo.lock churn, migrating crate edition, or wiring Gradle to cargo via the ripdpi.android.rust-native plugin.
Use when modifying the diagnostics scan pipeline, ScanRequest/ScanReport types, ProbeTask families, ripdpi-monitor-engine / ripdpi-diagnostics-* crates, strategy-probe candidates, the diagnostics catalog (packs/profiles), wire-schema contracts between Rust and Kotlin, DIAGNOSTICS_ENGINE_SCHEMA_VERSION, golden contract tests, or adding a new probe type / profile. Triggers on diagnostics scans, strategy probes, automatic audit, dpi-detector profiles, or anything in core/diagnostics or native/rust/crates/ripdpi-monitor-*.
Android-specific Rust build, verification, and packaging — per-target 16 KiB page alignment, size-optimized release profile, ELF symbol allowlist, .so size budgets, NDK 29 specifics. Use when modifying .cargo/config.toml for Android targets, the workspace [profile.release] / [profile.android-jni] block, or when verifying a built .so before release.
Telemetry and observability discipline for the RIPDPI Android Rust stack — control-plane vs data-plane logging, bounded flume event queues, atomic counters, snapshot polling, readiness callbacks, and deterministic JSON contracts. Use when authoring or modifying telemetry emission code, the bounded event ring, the Kotlin-side telemetry consumer, or any per-packet logging.
Custom detekt rules, DI/privacy/suppression guardrails, detekt.yml configuration, and false-positive triage.
| name | rust-jni-bridge |
| description | Rust JNI functions, Kotlin bindings, jni crate usage, and raw JNI versus UniFFI choices. |
Two approaches for Rust-to-Kotlin bindings on Android: raw jni crate (manual, full control) and UniFFI (auto-generated, higher-level). Choose based on complexity and how many functions you expose.
Raw jni crate | UniFFI | |
|---|---|---|
| Control | Full | Framework-managed |
| Boilerplate | High (manual signatures) | Low (generated from UDL/proc-macro) |
| Error handling | Manual JNI exception throwing | Automatic mapping |
| Best for | Few functions, performance-critical | Many functions, complex types |
| Kotlin code | Write manually (external fun) | Auto-generated bindings |
| Crate | jni = "0.22" | uniffi = "0.28" |
For RIPDPI's small handle-based JNI surface, raw jni crate is simpler. Consider UniFFI only if the API grows well beyond the current proxy and tunnel session methods.
use jni::JNIEnv;
use jni::objects::{JObject, JString};
use jni::sys::{jint, jlong};
// Function name must match: Java_<package>_<Class>_<method>
// Package: com.poyka.ripdpi.core, Class: RipDpiProxyNativeBindings
#[unsafe(no_mangle)]
pub extern "system" fn Java_com_poyka_ripdpi_core_RipDpiProxyNativeBindings_jniStart(
mut env: JNIEnv,
_thiz: JObject,
handle: jlong,
) -> jint {
match start_proxy(handle) {
Ok(result) => result,
Err(e) => {
let _ = env.throw_new("java/io/IOException", e.to_string());
-1
}
}
}
// String parameter handling
#[unsafe(no_mangle)]
pub extern "system" fn Java_com_poyka_ripdpi_core_RipDpiProxyNativeBindings_jniCreate(
mut env: JNIEnv,
_thiz: JObject,
config_json: JString,
) -> jlong {
let config_json: String = match env.get_string(&config_json) {
Ok(s) => s.into(),
Err(_) => return 0,
};
create_session(&config_json).unwrap_or(0)
}
class RipDpiProxyNativeBindings @Inject constructor() : RipDpiProxyBindings {
companion object {
init {
RipDpiNativeLoader.ensureLoaded()
}
}
override fun create(configJson: String): Long = jniCreate(configJson)
override fun start(handle: Long): Int = jniStart(handle)
private external fun jniCreate(configJson: String): Long
private external fun jniStart(handle: Long): Int
}
Java_com_poyka_ripdpi_core_RipDpiProxyNativeBindings_<methodName>
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^
package (dots -> underscores) method name
Must match exactly. Use #[unsafe(no_mangle)] and extern "system" on every exported function.
RIPDPI currently exports JNI entry points on RipDpiProxyNativeBindings, Tun2SocksNativeBindings, and NetworkDiagnosticsNativeBindings.
// src/main/rust/src/ripdpi.udl
namespace ripdpi {
[Throws=ProxyError]
i32 create_socket(string ip, i32 port);
[Throws=ProxyError]
i32 start_proxy(i32 fd);
[Throws=ProxyError]
i32 stop_proxy(i32 fd);
};
[Error]
enum ProxyError {
"SocketError",
"BindError",
"InvalidAddress",
};
uniffi::setup_scaffolding!();
#[derive(Debug, thiserror::Error, uniffi::Error)]
pub enum ProxyError {
#[error("Socket error")]
SocketError,
#[error("Bind error")]
BindError,
#[error("Invalid address")]
InvalidAddress,
}
#[uniffi::export]
pub fn create_socket(ip: String, port: i32) -> Result<i32, ProxyError> {
// implementation
}
cargo run --locked --bin uniffi-bindgen generate \
--library target/debug/libripdpi.so \
--language kotlin \
--out-dir core/engine/src/main/kotlin/com/poyka/ripdpi/rust
UniFFI generates Kotlin classes, enums, and error types automatically. No external fun declarations needed. RIPDPI does not currently use UniFFI for the Android bridge.
| Kotlin/Java | JNI type | Rust (jni crate) | Rust (UniFFI) |
|---|---|---|---|
Int | jint | jint (i32) | i32 |
Long | jlong | jlong (i64) | i64 |
Boolean | jboolean | jboolean (u8) | bool |
String | JString | JString -> String | String |
ByteArray | jbyteArray | JByteArray -> Vec<u8> | Vec<u8> |
Array<String> | JObjectArray | iterate with get_object_array_element | Vec<String> |
| Current function | Parameters | Returns | Notes |
|---|---|---|---|
jniCreate | String (JSON) | Long (handle) | Creates a proxy or tunnel session |
jniStart | Long / Long, Int | Int or Unit | Proxy start blocks; tunnel start owns its worker thread |
jniStop | Long | Unit | Graceful shutdown |
jniPollTelemetry / jniGetTelemetry | Long | String? | Proxy and tunnel runtime telemetry snapshots |
jniPollProgress / jniTakeReport / jniPollPassiveEvents | Long | String? | Diagnostics progress, final report, and passive events |
jniUpdateNetworkSnapshot | Long, String | Unit | Updates the active proxy session with network metadata |
jniDestroy | Long | Unit | Releases native session state |
| Mistake | Fix |
|---|---|
Missing #[unsafe(no_mangle)] | Required for JNI to find the function by name |
Using extern "C" instead of extern "system" | Use extern "system" for JNI on Android |
| Panicking across FFI boundary | Catch panics with std::panic::catch_unwind; never let panics cross into JVM |
| Leaking JNI local references | Use env.auto_local() or delete_local_ref in loops |
| Not handling JNI exceptions | Check env.exception_check() after calling Java methods from Rust |
rust-code-style -- Rust code style rules for the native workspacerust-lint-config -- Clippy, rustfmt, and cargo-deny configurationrust-crate-architecture -- Crate layering and dependency rules