diff --git a/Cargo.lock b/Cargo.lock index 52946850..bded33e4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -687,12 +687,6 @@ dependencies = [ "shlex 2.0.1", ] -[[package]] -name = "cesu8" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c" - [[package]] name = "cexpr" version = "0.6.0" @@ -2222,22 +2216,6 @@ dependencies = [ "syn 2.0.119", ] -[[package]] -name = "jni" -version = "0.21.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a87aa2bb7d2af34197c04845522473242e1aa17c12f4935d5856491a7fb8c97" -dependencies = [ - "cesu8", - "cfg-if", - "combine", - "jni-sys 0.3.1", - "log", - "thiserror 1.0.69", - "walkdir", - "windows-sys 0.45.0", -] - [[package]] name = "jni" version = "0.22.4" @@ -3500,7 +3478,7 @@ name = "punktfunk-client-android" version = "0.27.0" dependencies = [ "android_logger", - "jni 0.21.1", + "jni", "libc", "log", "mdns-sd", @@ -4238,7 +4216,7 @@ checksum = "26d1e2536ce4f35f4846aa13bff16bd0ff40157cdb14cc056c7b14ba41233ba0" dependencies = [ "core-foundation", "core-foundation-sys", - "jni 0.22.4", + "jni", "log", "once_cell", "rustls", @@ -5973,22 +5951,13 @@ dependencies = [ "windows-link 0.2.1 (git+https://github.com/microsoft/windows-rs?rev=acb5a1a7441033d9312b16842af02eb0c2b403dc)", ] -[[package]] -name = "windows-sys" -version = "0.45.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" -dependencies = [ - "windows-targets 0.42.2", -] - [[package]] name = "windows-sys" version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ - "windows-targets 0.52.6", + "windows-targets", ] [[package]] @@ -5997,7 +5966,7 @@ version = "0.59.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" dependencies = [ - "windows-targets 0.52.6", + "windows-targets", ] [[package]] @@ -6009,35 +5978,20 @@ dependencies = [ "windows-link 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", ] -[[package]] -name = "windows-targets" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" -dependencies = [ - "windows_aarch64_gnullvm 0.42.2", - "windows_aarch64_msvc 0.42.2", - "windows_i686_gnu 0.42.2", - "windows_i686_msvc 0.42.2", - "windows_x86_64_gnu 0.42.2", - "windows_x86_64_gnullvm 0.42.2", - "windows_x86_64_msvc 0.42.2", -] - [[package]] name = "windows-targets" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" dependencies = [ - "windows_aarch64_gnullvm 0.52.6", - "windows_aarch64_msvc 0.52.6", - "windows_i686_gnu 0.52.6", + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", "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", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", ] [[package]] @@ -6074,36 +6028,18 @@ dependencies = [ "windows-core 0.62.2 (git+https://github.com/microsoft/windows-rs?rev=acb5a1a7441033d9312b16842af02eb0c2b403dc)", ] -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" - [[package]] name = "windows_aarch64_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" -[[package]] -name = "windows_aarch64_msvc" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" - [[package]] name = "windows_aarch64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" -[[package]] -name = "windows_i686_gnu" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" - [[package]] name = "windows_i686_gnu" version = "0.52.6" @@ -6116,48 +6052,24 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" -[[package]] -name = "windows_i686_msvc" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" - [[package]] name = "windows_i686_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" -[[package]] -name = "windows_x86_64_gnu" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" - [[package]] name = "windows_x86_64_gnu" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" - [[package]] name = "windows_x86_64_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" -[[package]] -name = "windows_x86_64_msvc" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" - [[package]] name = "windows_x86_64_msvc" version = "0.52.6" diff --git a/THIRD-PARTY-NOTICES.txt b/THIRD-PARTY-NOTICES.txt index e1ebff12..5953b517 100644 --- a/THIRD-PARTY-NOTICES.txt +++ b/THIRD-PARTY-NOTICES.txt @@ -7,7 +7,7 @@ below. Each is distributed under its own permissive license; the full license te follow the manifest. This file is generated by scripts/gen-third-party-notices.py (or `cargo about`, see about.toml) — do not edit by hand. -Total third-party crates: 582 +Total third-party crates: 571 ---------------------------------------------------------------------------- VENDORED THIRD-PARTY SOURCE (inside first-party crates) @@ -90,7 +90,6 @@ MANIFEST (crate version — SPDX license — source) cbc 0.1.2 — MIT OR Apache-2.0 — https://github.com/RustCrypto/block-modes cbindgen 0.29.4 — MPL-2.0 — https://github.com/mozilla/cbindgen cc 1.4.2 — MIT OR Apache-2.0 — https://github.com/rust-lang/cc-rs - cesu8 1.1.0 — Apache-2.0/MIT — https://github.com/emk/cesu8-rs cexpr 0.6.0 — Apache-2.0/MIT — https://github.com/jethrogb/rust-cexpr cfg-expr 0.20.8 — MIT OR Apache-2.0 — https://github.com/EmbarkStudios/cfg-expr cfg-if 1.0.4 — MIT OR Apache-2.0 — https://github.com/rust-lang/cfg-if @@ -238,7 +237,6 @@ MANIFEST (crate version — SPDX license — source) jiff 0.2.35 — Unlicense OR MIT — https://github.com/BurntSushi/jiff jiff-core 0.1.0 — Unlicense OR MIT — https://github.com/BurntSushi/jiff jiff-static 0.2.35 — Unlicense OR MIT — https://github.com/BurntSushi/jiff - jni 0.21.1 — MIT/Apache-2.0 — https://github.com/jni-rs/jni-rs jni 0.22.4 — MIT OR Apache-2.0 — https://github.com/jni-rs/jni-rs jni-macros 0.22.4 — MIT OR Apache-2.0 — https://github.com/jni-rs/jni-rs jni-sys 0.3.1 — MIT OR Apache-2.0 — https://github.com/jni-rs/jni-sys @@ -556,30 +554,21 @@ MANIFEST (crate version — SPDX license — source) windows-service 0.7.0 — MIT OR Apache-2.0 — https://github.com/mullvad/windows-service-rs windows-strings 0.5.1 — MIT OR Apache-2.0 — https://github.com/microsoft/windows-rs windows-strings 0.5.1 — MIT OR Apache-2.0 — https://github.com/microsoft/windows-rs - windows-sys 0.45.0 — MIT OR Apache-2.0 — https://github.com/microsoft/windows-rs windows-sys 0.52.0 — MIT OR Apache-2.0 — https://github.com/microsoft/windows-rs windows-sys 0.59.0 — MIT OR Apache-2.0 — https://github.com/microsoft/windows-rs windows-sys 0.61.2 — MIT OR Apache-2.0 — https://github.com/microsoft/windows-rs - windows-targets 0.42.2 — MIT OR Apache-2.0 — https://github.com/microsoft/windows-rs windows-targets 0.52.6 — MIT OR Apache-2.0 — https://github.com/microsoft/windows-rs windows-threading 0.2.1 — MIT OR Apache-2.0 — https://github.com/microsoft/windows-rs windows-threading 0.2.1 — MIT OR Apache-2.0 — https://github.com/microsoft/windows-rs windows-time 0.1.0 — MIT OR Apache-2.0 — https://github.com/microsoft/windows-rs windows-window 0.0.0 — MIT OR Apache-2.0 — https://github.com/microsoft/windows-rs - windows_aarch64_gnullvm 0.42.2 — MIT OR Apache-2.0 — https://github.com/microsoft/windows-rs windows_aarch64_gnullvm 0.52.6 — MIT OR Apache-2.0 — https://github.com/microsoft/windows-rs - windows_aarch64_msvc 0.42.2 — MIT OR Apache-2.0 — https://github.com/microsoft/windows-rs windows_aarch64_msvc 0.52.6 — MIT OR Apache-2.0 — https://github.com/microsoft/windows-rs - windows_i686_gnu 0.42.2 — MIT OR Apache-2.0 — https://github.com/microsoft/windows-rs windows_i686_gnu 0.52.6 — MIT OR Apache-2.0 — https://github.com/microsoft/windows-rs windows_i686_gnullvm 0.52.6 — MIT OR Apache-2.0 — https://github.com/microsoft/windows-rs - windows_i686_msvc 0.42.2 — MIT OR Apache-2.0 — https://github.com/microsoft/windows-rs windows_i686_msvc 0.52.6 — MIT OR Apache-2.0 — https://github.com/microsoft/windows-rs - windows_x86_64_gnu 0.42.2 — MIT OR Apache-2.0 — https://github.com/microsoft/windows-rs windows_x86_64_gnu 0.52.6 — MIT OR Apache-2.0 — https://github.com/microsoft/windows-rs - windows_x86_64_gnullvm 0.42.2 — MIT OR Apache-2.0 — https://github.com/microsoft/windows-rs windows_x86_64_gnullvm 0.52.6 — MIT OR Apache-2.0 — https://github.com/microsoft/windows-rs - windows_x86_64_msvc 0.42.2 — MIT OR Apache-2.0 — https://github.com/microsoft/windows-rs windows_x86_64_msvc 0.52.6 — MIT OR Apache-2.0 — https://github.com/microsoft/windows-rs winnow 0.7.15 — MIT — https://github.com/winnow-rs/winnow winnow 1.0.4 — MIT — https://github.com/winnow-rs/winnow @@ -2257,7 +2246,7 @@ SOFTWARE. ---------------------------------------------------------------------------- -The following license (LICENSE-APACHE) applies to: asn1-rs 0.6.2, asn1-rs-derive 0.5.1, assert_matches 1.5.0, async-channel 2.5.0, async-executor 1.14.0, async-io 2.6.0, async-lock 3.4.2, async-process 2.5.0, async-recursion 1.1.1, async-signal 0.2.14, async-task 4.7.1, atomic-waker 1.1.2, autocfg 1.5.1, base64 0.22.1, base64 0.23.1, bitflags 1.3.2, bitflags 2.13.1, blocking 1.6.2, bumpalo 3.20.3, cast 0.3.0, cc 1.4.2, cexpr 0.6.0, cfg-if 1.0.4, cmake 0.1.58, concurrent-queue 2.5.0, core-foundation 0.10.1, core-foundation-sys 0.8.7, criterion 0.5.1, criterion-plot 0.5.0, crossbeam-deque 0.8.7, crossbeam-epoch 0.9.20, crossbeam-utils 0.8.22, curve25519-dalek-derive 0.1.1, der-parser 9.0.0, displaydoc 0.2.7, either 1.17.0, equivalent 1.0.2, errno 0.3.14, event-listener 5.4.2, event-listener-strategy 0.5.4, fastrand 2.5.0, filetime 0.2.29, find-msvc-tools 0.1.10, fixedbitset 0.5.7, flate2 1.1.9, fnv 1.0.7, form_urlencoded 1.2.2, fs-err 3.3.1, futures-lite 2.6.1, gethostname 1.1.0, gif 0.14.2, glob 0.3.4, hashbrown 0.16.1, hashbrown 0.17.1, heck 0.5.0, hermit-abi 0.5.2, httparse 1.10.1, indexmap 2.14.0, itertools 0.10.5, itertools 0.13.0, jni 0.21.1, jobserver 0.1.35, js-sys 0.3.104, khronos-egl 6.0.0, lazy_static 1.5.0, linux-raw-sys 0.12.1, lock_api 0.4.14, log 0.4.33, mime 0.3.17, nasm-rs 0.3.2, num-bigint 0.4.8, num-bigint-dig 0.8.6, num-derive 0.4.2, num-integer 0.1.47, num-iter 0.1.46, num-traits 0.2.19, num_cpus 1.17.0, oid-registry 0.7.1, once_cell 1.21.4, openssl-probe 0.2.1, opus 0.3.1, ordered-stream 0.2.0, parking 2.2.1, parking_lot 0.12.5, parking_lot_core 0.9.12, percent-encoding 2.3.2, piper 0.2.5, pkg-config 0.3.33, png 0.18.1, polling 3.11.0, proptest 1.11.0, rayon 1.12.0, rayon-core 1.13.0, regex 1.13.1, regex-automata 0.4.18, regex-syntax 0.8.11, roxmltree 0.21.1, rsa 0.9.10, rustc_version 0.4.1, rusticata-macros 4.1.0, rustix 1.1.4, rustls 0.23.43, rustls-native-certs 0.8.4, rusty-fork 0.3.1, scopeguard 1.2.0, security-framework 3.7.0, security-framework-sys 2.17.0, signal-hook-registry 1.4.8, simd_cesu8 1.2.0, smallvec 1.15.2, socket2 0.6.5, system-deps 7.0.8, tar 0.4.46, tempfile 3.27.0, thread_local 1.1.10, tinytemplate 1.2.1, unicode-segmentation 1.13.3, unicode-width 0.2.2, uuid 1.24.0, vcpkg 0.2.15, version_check 0.9.5, wait-timeout 0.2.1, wasi 0.11.1+wasi-snapshot-preview1, wasip2 1.0.4+wasi-0.2.12, wasm-bindgen 0.2.127, wasm-bindgen-macro 0.2.127, wasm-bindgen-macro-support 0.2.127, wasm-bindgen-shared 0.2.127, weezl 0.1.12, wit-bindgen 0.57.1, x509-parser 0.16.0, xattr 1.6.1 +The following license (LICENSE-APACHE) applies to: asn1-rs 0.6.2, asn1-rs-derive 0.5.1, assert_matches 1.5.0, async-channel 2.5.0, async-executor 1.14.0, async-io 2.6.0, async-lock 3.4.2, async-process 2.5.0, async-recursion 1.1.1, async-signal 0.2.14, async-task 4.7.1, atomic-waker 1.1.2, autocfg 1.5.1, base64 0.22.1, base64 0.23.1, bitflags 1.3.2, bitflags 2.13.1, blocking 1.6.2, bumpalo 3.20.3, cast 0.3.0, cc 1.4.2, cexpr 0.6.0, cfg-if 1.0.4, cmake 0.1.58, concurrent-queue 2.5.0, core-foundation 0.10.1, core-foundation-sys 0.8.7, criterion 0.5.1, criterion-plot 0.5.0, crossbeam-deque 0.8.7, crossbeam-epoch 0.9.20, crossbeam-utils 0.8.22, curve25519-dalek-derive 0.1.1, der-parser 9.0.0, displaydoc 0.2.7, either 1.17.0, equivalent 1.0.2, errno 0.3.14, event-listener 5.4.2, event-listener-strategy 0.5.4, fastrand 2.5.0, filetime 0.2.29, find-msvc-tools 0.1.10, fixedbitset 0.5.7, flate2 1.1.9, fnv 1.0.7, form_urlencoded 1.2.2, fs-err 3.3.1, futures-lite 2.6.1, gethostname 1.1.0, gif 0.14.2, glob 0.3.4, hashbrown 0.16.1, hashbrown 0.17.1, heck 0.5.0, hermit-abi 0.5.2, httparse 1.10.1, indexmap 2.14.0, itertools 0.10.5, itertools 0.13.0, jobserver 0.1.35, js-sys 0.3.104, khronos-egl 6.0.0, lazy_static 1.5.0, linux-raw-sys 0.12.1, lock_api 0.4.14, log 0.4.33, mime 0.3.17, nasm-rs 0.3.2, num-bigint 0.4.8, num-bigint-dig 0.8.6, num-derive 0.4.2, num-integer 0.1.47, num-iter 0.1.46, num-traits 0.2.19, num_cpus 1.17.0, oid-registry 0.7.1, once_cell 1.21.4, openssl-probe 0.2.1, opus 0.3.1, ordered-stream 0.2.0, parking 2.2.1, parking_lot 0.12.5, parking_lot_core 0.9.12, percent-encoding 2.3.2, piper 0.2.5, pkg-config 0.3.33, png 0.18.1, polling 3.11.0, proptest 1.11.0, rayon 1.12.0, rayon-core 1.13.0, regex 1.13.1, regex-automata 0.4.18, regex-syntax 0.8.11, roxmltree 0.21.1, rsa 0.9.10, rustc_version 0.4.1, rusticata-macros 4.1.0, rustix 1.1.4, rustls 0.23.43, rustls-native-certs 0.8.4, rusty-fork 0.3.1, scopeguard 1.2.0, security-framework 3.7.0, security-framework-sys 2.17.0, signal-hook-registry 1.4.8, simd_cesu8 1.2.0, smallvec 1.15.2, socket2 0.6.5, system-deps 7.0.8, tar 0.4.46, tempfile 3.27.0, thread_local 1.1.10, tinytemplate 1.2.1, unicode-segmentation 1.13.3, unicode-width 0.2.2, uuid 1.24.0, vcpkg 0.2.15, version_check 0.9.5, wait-timeout 0.2.1, wasi 0.11.1+wasi-snapshot-preview1, wasip2 1.0.4+wasi-0.2.12, wasm-bindgen 0.2.127, wasm-bindgen-macro 0.2.127, wasm-bindgen-macro-support 0.2.127, wasm-bindgen-shared 0.2.127, weezl 0.1.12, wit-bindgen 0.57.1, x509-parser 0.16.0, xattr 1.6.1 ---------------------------------------------------------------------------- Apache License Version 2.0, January 2004 @@ -4585,433 +4574,6 @@ IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ----------------------------------------------------------------------------- -The following license (COPYRIGHT-RUST.txt) applies to: cesu8 1.1.0 ----------------------------------------------------------------------------- -Short version for non-lawyers: - -The Rust Project is dual-licensed under Apache 2.0 and MIT -terms. - - -Longer version: - -The Rust Project is copyright 2014, The Rust Project -Developers (given in the file AUTHORS.txt). - -Licensed under the Apache License, Version 2.0 - or the MIT -license , -at your option. All files in the project carrying such -notice may not be copied, modified, or distributed except -according to those terms. - - -The Rust Project includes packages written by third parties. -The following third party packages are included, and carry -their own copyright notices and license terms: - -* Two header files that are part of the Valgrind - package. These files are found at src/rt/vg/valgrind.h and - src/rt/vg/memcheck.h, within this distribution. These files - are redistributed under the following terms, as noted in - them: - - for src/rt/vg/valgrind.h: - - This file is part of Valgrind, a dynamic binary - instrumentation framework. - - Copyright (C) 2000-2010 Julian Seward. All rights - reserved. - - Redistribution and use in source and binary forms, with - or without modification, are permitted provided that the - following conditions are met: - - 1. Redistributions of source code must retain the above - copyright notice, this list of conditions and the - following disclaimer. - - 2. The origin of this software must not be - misrepresented; you must not claim that you wrote the - original software. If you use this software in a - product, an acknowledgment in the product - documentation would be appreciated but is not - required. - - 3. Altered source versions must be plainly marked as - such, and must not be misrepresented as being the - original software. - - 4. The name of the author may not be used to endorse or - promote products derived from this software without - specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND - ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY - AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN - NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, - INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF - USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE - USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY - OF SUCH DAMAGE. - - for src/rt/vg/memcheck.h: - - This file is part of MemCheck, a heavyweight Valgrind - tool for detecting memory errors. - - Copyright (C) 2000-2010 Julian Seward. All rights - reserved. - - Redistribution and use in source and binary forms, with - or without modification, are permitted provided that the - following conditions are met: - - 1. Redistributions of source code must retain the above - copyright notice, this list of conditions and the - following disclaimer. - - 2. The origin of this software must not be - misrepresented; you must not claim that you wrote the - original software. If you use this software in a - product, an acknowledgment in the product - documentation would be appreciated but is not - required. - - 3. Altered source versions must be plainly marked as - such, and must not be misrepresented as being the - original software. - - 4. The name of the author may not be used to endorse or - promote products derived from this software without - specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND - ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY - AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN - NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, - INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF - USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE - USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY - OF SUCH DAMAGE. - -* The auxiliary file src/etc/pkg/modpath.iss contains a - library routine compiled, by Inno Setup, into the Windows - installer binary. This file is licensed under the LGPL, - version 3, but, in our legal interpretation, this does not - affect the aggregate "collected work" license of the Rust - distribution (MIT/ASL2) nor any other components of it. We - believe that the terms governing distribution of the - binary Windows installer built from modpath.iss are - therefore LGPL, but not the terms governing distribution - of any of the files installed by such an installer (such - as the Rust compiler or runtime libraries themselves). - -* The src/rt/miniz.c file, carrying an implementation of - RFC1950/RFC1951 DEFLATE, by Rich Geldreich - . All uses of this file are - permitted by the embedded "unlicense" notice - (effectively: public domain with warranty disclaimer). - -* LLVM. Code for this package is found in src/llvm. - - Copyright (c) 2003-2013 University of Illinois at - Urbana-Champaign. All rights reserved. - - Developed by: - - LLVM Team - - University of Illinois at Urbana-Champaign - - http://llvm.org - - Permission is hereby granted, free of charge, to any - person obtaining a copy of this software and associated - documentation files (the "Software"), to deal with the - Software without restriction, including without - limitation the rights to use, copy, modify, merge, - publish, distribute, sublicense, and/or sell copies of - the Software, and to permit persons to whom the Software - is furnished to do so, subject to the following - conditions: - - * Redistributions of source code must retain the - above copyright notice, this list of conditions - and the following disclaimers. - - * Redistributions in binary form must reproduce the - above copyright notice, this list of conditions - and the following disclaimers in the documentation - and/or other materials provided with the - distribution. - - * Neither the names of the LLVM Team, University of - Illinois at Urbana-Champaign, nor the names of its - contributors may be used to endorse or promote - products derived from this Software without - specific prior written permission. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF - ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED - TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A - PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT - SHALL THE CONTRIBUTORS OR COPYRIGHT HOLDERS BE LIABLE - FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN - ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT - OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR - OTHER DEALINGS WITH THE SOFTWARE. - -* Additional libraries included in LLVM carry separate - BSD-compatible licenses. See src/llvm/LICENSE.txt for - details. - -* compiler-rt, in src/compiler-rt is dual licensed under - LLVM's license and MIT: - - Copyright (c) 2009-2014 by the contributors listed in - CREDITS.TXT - - All rights reserved. - - Developed by: - - LLVM Team - - University of Illinois at Urbana-Champaign - - http://llvm.org - - Permission is hereby granted, free of charge, to any - person obtaining a copy of this software and associated - documentation files (the "Software"), to deal with the - Software without restriction, including without - limitation the rights to use, copy, modify, merge, - publish, distribute, sublicense, and/or sell copies of - the Software, and to permit persons to whom the Software - is furnished to do so, subject to the following - conditions: - - * Redistributions of source code must retain the - above copyright notice, this list of conditions - and the following disclaimers. - - * Redistributions in binary form must reproduce the - above copyright notice, this list of conditions - and the following disclaimers in the documentation - and/or other materials provided with the - distribution. - - * Neither the names of the LLVM Team, University of - Illinois at Urbana-Champaign, nor the names of its - contributors may be used to endorse or promote - products derived from this Software without - specific prior written permission. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF - ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED - TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A - PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT - SHALL THE CONTRIBUTORS OR COPYRIGHT HOLDERS BE LIABLE - FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN - ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT - OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR - OTHER DEALINGS WITH THE SOFTWARE. - - ======================================================== - - Copyright (c) 2009-2014 by the contributors listed in - CREDITS.TXT - - Permission is hereby granted, free of charge, to any - person obtaining a copy of this software and associated - documentation files (the "Software"), to deal in the - Software without restriction, including without - limitation the rights to use, copy, modify, merge, - publish, distribute, sublicense, and/or sell copies of - the Software, and to permit persons to whom the Software - is furnished to do so, subject to the following - conditions: - - The above copyright notice and this permission notice - shall be included in all copies or substantial portions - of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF - ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED - TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A - PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT - SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY - CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION - OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR - IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER - DEALINGS IN THE SOFTWARE. - -* Portions of the FFI code for interacting with the native ABI - is derived from the Clay programming language, which carries - the following license. - - Copyright (C) 2008-2010 Tachyon Technologies. - All rights reserved. - - Redistribution and use in source and binary forms, with - or without modification, are permitted provided that the - following conditions are met: - - 1. Redistributions of source code must retain the above - copyright notice, this list of conditions and the - following disclaimer. - - 2. Redistributions in binary form must reproduce the - above copyright notice, this list of conditions and - the following disclaimer in the documentation and/or - other materials provided with the distribution. - - THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR - IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A - PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE - DEVELOPERS AND CONTRIBUTORS BE LIABLE FOR ANY DIRECT, - INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF - USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE - USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY - OF SUCH DAMAGE. - -* Hoedown, the markdown parser, under src/rt/hoedown, is - licensed as follows. - - Copyright (c) 2008, Natacha Porté - Copyright (c) 2011, Vicent Martí - Copyright (c) 2013, Devin Torres and the Hoedown authors - - Permission to use, copy, modify, and distribute this - software for any purpose with or without fee is hereby - granted, provided that the above copyright notice and - this permission notice appear in all copies. - - THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR - DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE - INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND - FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY - SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR - ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA - OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE - OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN - CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - -* libbacktrace, under src/libbacktrace: - - Copyright (C) 2012-2014 Free Software Foundation, Inc. - Written by Ian Lance Taylor, Google. - - Redistribution and use in source and binary forms, with - or without modification, are permitted provided that the - following conditions are met: - - (1) Redistributions of source code must retain the - above copyright notice, this list of conditions and - the following disclaimer. - - (2) Redistributions in binary form must reproduce - the above copyright notice, this list of conditions - and the following disclaimer in the documentation - and/or other materials provided with the - distribution. - - (3) The name of the author may not be used to - endorse or promote products derived from this - software without specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND - ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY - AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN - NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, - INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF - USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE - USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY - OF SUCH DAMAGE. */ - -* jemalloc, under src/jemalloc: - - Copyright (C) 2002-2014 Jason Evans - . All rights reserved. - Copyright (C) 2007-2012 Mozilla Foundation. - All rights reserved. - Copyright (C) 2009-2014 Facebook, Inc. - All rights reserved. - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are met: - 1. Redistributions of source code must retain the above copyright notice(s), - this list of conditions and the following disclaimer. - 2. Redistributions in binary form must reproduce the above copyright notice(s), - this list of conditions and the following disclaimer in the documentation - and/or other materials provided with the distribution. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER(S) - ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, - INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF - MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER(S) - BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, - EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT - LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) - HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER - IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE - USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY - OF SUCH DAMAGE. - -* Additional copyright may be retained by contributors other - than Mozilla, the Rust Project Developers, or the parties - enumerated in this file. Such copyright can be determined - on a case-by-case basis by examining the author of each - portion of a file in the revision-control commit records - of the project, or by consulting representative comments - claiming copyright ownership for a file. - - For example, the text: - - "Copyright (c) 2011 Google Inc." - - appears in some files, and these files thereby denote - that their author and copyright-holder is Google Inc. - - In all such cases, the absence of explicit licensing text - indicates that the contributor chose to license their work - for distribution under identical terms to those Mozilla - has chosen for the collective work, enumerated at the top - of this file. The only difference is the retention of - copyright itself, held by the contributor. - - ---------------------------------------------------------------------------- The following license (LICENSE-MIT) applies to: cexpr 0.6.0 ---------------------------------------------------------------------------- @@ -8757,32 +8319,6 @@ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ----------------------------------------------------------------------------- -The following license (LICENSE-MIT) applies to: jni 0.21.1 ----------------------------------------------------------------------------- -The MIT License (MIT) - -Copyright (c) 2016 Prevoty, Inc. and jni-rs contributors - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. - - ---------------------------------------------------------------------------- The following license (LICENSE-MIT) applies to: jni-sys 0.3.1, jni-sys 0.4.1 ---------------------------------------------------------------------------- @@ -16424,7 +15960,7 @@ SOFTWARE. ---------------------------------------------------------------------------- -The following license (license-apache-2.0) applies to: windows 0.62.2, windows-canvas 0.0.0, windows-collections 0.3.2, windows-composition 0.0.0, windows-core 0.62.2, windows-future 0.3.2, windows-implement 0.60.2, windows-interface 0.59.3, windows-link 0.2.1, windows-numerics 0.3.1, windows-reactor 0.0.0, windows-reactor-setup 0.0.0, windows-reference 0.1.0, windows-result 0.4.1, windows-strings 0.5.1, windows-sys 0.45.0, windows-sys 0.52.0, windows-sys 0.59.0, windows-sys 0.61.2, windows-targets 0.42.2, windows-targets 0.52.6, windows-threading 0.2.1, windows-time 0.1.0, windows-window 0.0.0, windows_aarch64_gnullvm 0.42.2, windows_aarch64_gnullvm 0.52.6, windows_aarch64_msvc 0.42.2, windows_aarch64_msvc 0.52.6, windows_i686_gnu 0.42.2, windows_i686_gnu 0.52.6, windows_i686_gnullvm 0.52.6, windows_i686_msvc 0.42.2, windows_i686_msvc 0.52.6, windows_x86_64_gnu 0.42.2, windows_x86_64_gnu 0.52.6, windows_x86_64_gnullvm 0.42.2, windows_x86_64_gnullvm 0.52.6, windows_x86_64_msvc 0.42.2, windows_x86_64_msvc 0.52.6 +The following license (license-apache-2.0) applies to: windows 0.62.2, windows-canvas 0.0.0, windows-collections 0.3.2, windows-composition 0.0.0, windows-core 0.62.2, windows-future 0.3.2, windows-implement 0.60.2, windows-interface 0.59.3, windows-link 0.2.1, windows-numerics 0.3.1, windows-reactor 0.0.0, windows-reactor-setup 0.0.0, windows-reference 0.1.0, windows-result 0.4.1, windows-strings 0.5.1, windows-sys 0.52.0, windows-sys 0.59.0, windows-sys 0.61.2, windows-targets 0.52.6, windows-threading 0.2.1, windows-time 0.1.0, windows-window 0.0.0, windows_aarch64_gnullvm 0.52.6, windows_aarch64_msvc 0.52.6, windows_i686_gnu 0.52.6, windows_i686_gnullvm 0.52.6, 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 ---------------------------------------------------------------------------- Apache License Version 2.0, January 2004 @@ -16630,7 +16166,7 @@ Apache License ---------------------------------------------------------------------------- -The following license (license-mit) applies to: windows 0.62.2, windows-canvas 0.0.0, windows-collections 0.3.2, windows-composition 0.0.0, windows-core 0.62.2, windows-future 0.3.2, windows-implement 0.60.2, windows-interface 0.59.3, windows-link 0.2.1, windows-numerics 0.3.1, windows-reactor 0.0.0, windows-reactor-setup 0.0.0, windows-reference 0.1.0, windows-result 0.4.1, windows-strings 0.5.1, windows-sys 0.45.0, windows-sys 0.52.0, windows-sys 0.59.0, windows-sys 0.61.2, windows-targets 0.42.2, windows-targets 0.52.6, windows-threading 0.2.1, windows-time 0.1.0, windows-window 0.0.0, windows_aarch64_gnullvm 0.42.2, windows_aarch64_gnullvm 0.52.6, windows_aarch64_msvc 0.42.2, windows_aarch64_msvc 0.52.6, windows_i686_gnu 0.42.2, windows_i686_gnu 0.52.6, windows_i686_gnullvm 0.52.6, windows_i686_msvc 0.42.2, windows_i686_msvc 0.52.6, windows_x86_64_gnu 0.42.2, windows_x86_64_gnu 0.52.6, windows_x86_64_gnullvm 0.42.2, windows_x86_64_gnullvm 0.52.6, windows_x86_64_msvc 0.42.2, windows_x86_64_msvc 0.52.6 +The following license (license-mit) applies to: windows 0.62.2, windows-canvas 0.0.0, windows-collections 0.3.2, windows-composition 0.0.0, windows-core 0.62.2, windows-future 0.3.2, windows-implement 0.60.2, windows-interface 0.59.3, windows-link 0.2.1, windows-numerics 0.3.1, windows-reactor 0.0.0, windows-reactor-setup 0.0.0, windows-reference 0.1.0, windows-result 0.4.1, windows-strings 0.5.1, windows-sys 0.52.0, windows-sys 0.59.0, windows-sys 0.61.2, windows-targets 0.52.6, windows-threading 0.2.1, windows-time 0.1.0, windows-window 0.0.0, windows_aarch64_gnullvm 0.52.6, windows_aarch64_msvc 0.52.6, windows_i686_gnu 0.52.6, windows_i686_gnullvm 0.52.6, 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 ---------------------------------------------------------------------------- MIT License diff --git a/clients/android/native/Cargo.toml b/clients/android/native/Cargo.toml index f445a0b7..e6b629a6 100644 --- a/clients/android/native/Cargo.toml +++ b/clients/android/native/Cargo.toml @@ -18,7 +18,19 @@ crate-type = ["cdylib"] # the punktfunk/1 control plane, whose TLS runs on aws-lc-rs (see punktfunk-core/Cargo.toml) — # aws-lc-sys cross-compiles for all three ABIs with the NDK clang cargo-ndk already exports. punktfunk-core = { path = "../../../crates/punktfunk-core", features = ["quic"] } -jni = "0.21" +# 0.22, NOT 0.21 — and the version is load-bearing beyond currency: `rustls-platform-verifier` +# (via quinn-proto, for Android cert verification) already depends on jni 0.22, so pinning 0.21 +# here compiled TWO jni copies into the one .so. Matching it collapses them and, with them, the +# whole windows-rs 0.42 generation jni 0.21 dragged in behind `cfg(windows)` (windows-sys 0.45 — +# the oldest crate in the tree — plus windows-targets 0.42.2 and its seven per-arch import libs) +# and jni 0.21's `cesu8`: 11 crates, for a dependency that never even builds on Android. +# +# NOTE for whoever next tries to retire thiserror 1.0 or the jni-sys 0.3/0.4 split: jni is no +# longer why they are here. Both now come solely from `vendor/ndk` 0.9.0 (thiserror 1.0.23, +# jni-sys 0.3) and the crates.io `ndk-sys` 0.6 (jni-sys 0.3). jni-sys 0.3.1 is itself a facade +# over 0.4.1, so the "split" cannot close until ndk + ndk-sys move — and ndk is vendored for one +# visibility patch, so bumping its deps would mean rewriting the vendor rather than a version edit. +jni = "0.22" log = "0.4" # LAN host discovery: browse the host's `_punktfunk._udp` mDNS advert — the SAME crate + service the # Linux/Windows clients use (`crates/pf-client-core/src/discovery.rs`), replacing Android's per-OEM diff --git a/clients/android/native/src/discovery.rs b/clients/android/native/src/discovery.rs index 7c2f0fa7..7a32a58e 100644 --- a/clients/android/native/src/discovery.rs +++ b/clients/android/native/src/discovery.rs @@ -16,9 +16,10 @@ //! wrong, and 1 Hz is plenty for a host picker. use crate::session::jni_guard; -use jni::objects::JObject; +use jni::errors::LogErrorAndDefault; +use jni::objects::{JObject, JString}; use jni::sys::jlong; -use jni::JNIEnv; +use jni::EnvUnowned; use mdns_sd::{ResolvedService, ServiceDaemon, ServiceEvent}; use std::collections::HashMap; use std::sync::{Arc, Mutex}; @@ -202,7 +203,7 @@ fn resolve(info: &ResolvedService) -> Option { /// [`nativeDiscoveryStop`]: Java_io_unom_punktfunk_kit_NativeBridge_nativeDiscoveryStop #[unsafe(no_mangle)] pub extern "system" fn Java_io_unom_punktfunk_kit_NativeBridge_nativeDiscoveryStart( - _env: JNIEnv, + _env: EnvUnowned, _this: JObject, ) -> jlong { jni_guard(0, || match Discovery::start() { @@ -216,11 +217,14 @@ pub extern "system" fn Java_io_unom_punktfunk_kit_NativeBridge_nativeDiscoverySt /// `0` handle. Poll ~1 Hz from the UI thread (cheap: a mutex lock + string build). #[unsafe(no_mangle)] pub extern "system" fn Java_io_unom_punktfunk_kit_NativeBridge_nativeDiscoveryPoll<'local>( - env: JNIEnv<'local>, + mut env: EnvUnowned<'local>, _this: JObject<'local>, handle: jlong, -) -> jni::sys::jstring { - jni_guard(std::ptr::null_mut(), || { +) -> JString<'local> { + // `with_env` subsumes the `jni_guard` this used to carry: it catches panics at the boundary and + // `LogErrorAndDefault` logs then yields `JString::default()` — the null reference the old + // `std::ptr::null_mut()` default returned. Kotlin still sees a null String on failure. + env.with_env(|env| -> jni::errors::Result> { let out = if handle == 0 { String::new() } else { @@ -229,11 +233,9 @@ pub extern "system" fn Java_io_unom_punktfunk_kit_NativeBridge_nativeDiscoveryPo let d = unsafe { &*(handle as *const Discovery) }; d.snapshot() }; - match env.new_string(out) { - Ok(s) => s.into_raw(), - Err(_) => std::ptr::null_mut(), - } + env.new_string(out) }) + .resolve::() } /// `NativeBridge.nativeDiscoveryStop(handle)` — stop the browse, shut the daemon down and join its @@ -247,7 +249,7 @@ pub extern "system" fn Java_io_unom_punktfunk_kit_NativeBridge_nativeDiscoveryPo /// [`nativeDiscoveryPoll`]: Java_io_unom_punktfunk_kit_NativeBridge_nativeDiscoveryPoll #[unsafe(no_mangle)] pub extern "system" fn Java_io_unom_punktfunk_kit_NativeBridge_nativeDiscoveryStop( - _env: JNIEnv, + _env: EnvUnowned, _this: JObject, handle: jlong, ) { diff --git a/clients/android/native/src/feedback.rs b/clients/android/native/src/feedback.rs index 5b97ad37..8a3585bc 100644 --- a/clients/android/native/src/feedback.rs +++ b/clients/android/native/src/feedback.rs @@ -8,9 +8,10 @@ //! compile on the host build too (parity with the input shims in [`crate::session`]). use crate::session::{jni_guard, SessionHandle}; +use jni::errors::LogErrorAndDefault; use jni::objects::{JByteBuffer, JObject}; use jni::sys::{jint, jlong}; -use jni::JNIEnv; +use jni::EnvUnowned; use punktfunk_core::quic::HidOutput; use std::time::Duration; @@ -62,7 +63,7 @@ const TAG_HID_RAW: u8 = 0x05; /// poll thread. #[unsafe(no_mangle)] pub extern "system" fn Java_io_unom_punktfunk_kit_NativeBridge_nativeNextRumble( - _env: JNIEnv, + _env: EnvUnowned, _this: JObject, handle: jlong, ) -> jlong { @@ -101,94 +102,103 @@ pub extern "system" fn Java_io_unom_punktfunk_kit_NativeBridge_nativeNextRumble( /// Returns the byte count written, or `-1` on timeout / session closed / buffer too small. #[unsafe(no_mangle)] pub extern "system" fn Java_io_unom_punktfunk_kit_NativeBridge_nativeNextHidout( - env: JNIEnv, + mut env: EnvUnowned, _this: JObject, handle: jlong, buf: JByteBuffer, ) -> jint { // Runs on a Kotlin poll thread, so a panic here would abort the process; guard the boundary. + // + // Deliberately `with_env_no_catch` INSIDE `jni_guard`, not the usual `with_env`: every error + // policy resolves a failure to `T::default()`, and `jint::default()` is 0 — a *valid* byte + // count — whereas this method's contract says -1. Letting the panic travel out to `jni_guard` + // keeps the -1 sentinel exact. Every non-panic failure path below likewise returns `Ok(-1)` + // rather than `Err`, so the policy's default is unreachable by construction. jni_guard(-1, || { - if handle == 0 { - return -1; - } - // SAFETY: live handle per the contract; next_hidout is &self on the Sync connector. - let h = unsafe { &*(handle as *const SessionHandle) }; - let ev = match h.client.next_hidout(PULL_TIMEOUT) { - Ok(ev) => ev, - Err(_) => return -1, // timeout or closed — Kotlin loops - }; + env.with_env_no_catch(|env| -> jni::errors::Result { + if handle == 0 { + return Ok(-1); + } + // SAFETY: live handle per the contract; next_hidout is &self on the Sync connector. + let h = unsafe { &*(handle as *const SessionHandle) }; + let ev = match h.client.next_hidout(PULL_TIMEOUT) { + Ok(ev) => ev, + Err(_) => return Ok(-1), // timeout or closed — Kotlin loops + }; - // The caller passes a direct ByteBuffer (allocateDirect) so we write its backing store directly. - let cap = match env.get_direct_buffer_capacity(&buf) { - Ok(c) => c, - Err(_) => return -1, - }; - let ptr = match env.get_direct_buffer_address(&buf) { - Ok(p) if !p.is_null() => p, - _ => return -1, - }; - // SAFETY: `ptr`/`cap` describe the direct ByteBuffer's backing store, valid for this call. - let out = unsafe { std::slice::from_raw_parts_mut(ptr, cap) }; + // The caller passes a direct ByteBuffer (allocateDirect) so we write its backing store directly. + let cap = match env.get_direct_buffer_capacity(&buf) { + Ok(c) => c, + Err(_) => return Ok(-1), + }; + let ptr = match env.get_direct_buffer_address(&buf) { + Ok(p) if !p.is_null() => p, + _ => return Ok(-1), + }; + // SAFETY: `ptr`/`cap` describe the direct ByteBuffer's backing store, valid for this call. + let out = unsafe { std::slice::from_raw_parts_mut(ptr, cap) }; - // out[0] = wire pad index; out[1] = kind tag; the rest is the per-kind payload. - let n = match ev { - HidOutput::Led { pad, r, g, b } => { - if cap < 5 { - return -1; + // out[0] = wire pad index; out[1] = kind tag; the rest is the per-kind payload. + let n = match ev { + HidOutput::Led { pad, r, g, b } => { + if cap < 5 { + return Ok(-1); + } + out[0] = pad; + out[1] = TAG_LED; + out[2] = r; + out[3] = g; + out[4] = b; + 5 } - out[0] = pad; - out[1] = TAG_LED; - out[2] = r; - out[3] = g; - out[4] = b; - 5 - } - HidOutput::PlayerLeds { pad, bits } => { - if cap < 3 { - return -1; + HidOutput::PlayerLeds { pad, bits } => { + if cap < 3 { + return Ok(-1); + } + out[0] = pad; + out[1] = TAG_PLAYER_LEDS; + out[2] = bits; + 3 } - out[0] = pad; - out[1] = TAG_PLAYER_LEDS; - out[2] = bits; - 3 - } - HidOutput::Trigger { pad, which, effect } => { - let n = 3 + effect.len(); - if cap < n { - return -1; // the raw DS5 trigger block is ~11 bytes; Kotlin allocates 64 + HidOutput::Trigger { pad, which, effect } => { + let n = 3 + effect.len(); + if cap < n { + return Ok(-1); // the raw DS5 trigger block is ~11 bytes; Kotlin allocates 64 + } + out[0] = pad; + out[1] = TAG_TRIGGER; + out[2] = which; + out[3..n].copy_from_slice(&effect); + n } - out[0] = pad; - out[1] = TAG_TRIGGER; - out[2] = which; - out[3..n].copy_from_slice(&effect); - n - } - HidOutput::TrackpadHaptic { .. } => { - // Steam Controller trackpad-coil haptics — no Android equivalent; drop it (motor - // rumble already rides the universal 0xCA plane). - return -1; - } - HidOutput::HidRaw { pad, kind, data } => { - // As-is SC2 passthrough: the host's hidraw consumer (Steam) wrote this report to - // the virtual pad; Kotlin replays it verbatim on the physical controller. - // `[pad][0x05][kind][report…]` — kind 0 = output report, 1 = feature report. - let n = 3 + data.len(); - if cap < n { - return -1; // reports are ≤ 64 bytes; Kotlin allocates 128 + HidOutput::TrackpadHaptic { .. } => { + // Steam Controller trackpad-coil haptics — no Android equivalent; drop it (motor + // rumble already rides the universal 0xCA plane). + return Ok(-1); } - out[0] = pad; - out[1] = TAG_HID_RAW; - out[2] = kind; - out[3..n].copy_from_slice(&data); - n - } - HidOutput::AudioCtl { .. } => { - // DS5 pad-audio routing/volumes — no Android replay path yet (the 0xD1 sample - // plane isn't rendered here either); drop it like TrackpadHaptic. - return -1; - } - }; - n as jint + HidOutput::HidRaw { pad, kind, data } => { + // As-is SC2 passthrough: the host's hidraw consumer (Steam) wrote this report to + // the virtual pad; Kotlin replays it verbatim on the physical controller. + // `[pad][0x05][kind][report…]` — kind 0 = output report, 1 = feature report. + let n = 3 + data.len(); + if cap < n { + return Ok(-1); // reports are ≤ 64 bytes; Kotlin allocates 128 + } + out[0] = pad; + out[1] = TAG_HID_RAW; + out[2] = kind; + out[3..n].copy_from_slice(&data); + n + } + HidOutput::AudioCtl { .. } => { + // DS5 pad-audio routing/volumes — no Android replay path yet (the 0xD1 sample + // plane isn't rendered here either); drop it like TrackpadHaptic. + return Ok(-1); + } + }; + Ok(n as jint) + }) + .resolve::() }) } diff --git a/clients/android/native/src/lib.rs b/clients/android/native/src/lib.rs index 1ed2706e..79db972e 100644 --- a/clients/android/native/src/lib.rs +++ b/clients/android/native/src/lib.rs @@ -21,9 +21,10 @@ //! surface, the per-plane pumps (video → AMediaCodec, audio ↔ AAudio, mic uplink), input, and //! rumble/HID feedback ([`feedback`]). Mode renegotiation is still TODO (see [`session`]). -use jni::objects::JObject; +use jni::errors::LogErrorAndDefault; +use jni::objects::{JObject, JString}; use jni::sys::jint; -use jni::JNIEnv; +use jni::EnvUnowned; #[cfg(target_os = "android")] mod adpf; @@ -76,7 +77,7 @@ pub extern "system" fn JNI_OnLoad( /// linked `punktfunk-core` is the one we expect. #[unsafe(no_mangle)] pub extern "system" fn Java_io_unom_punktfunk_kit_NativeBridge_abiVersion( - _env: JNIEnv, + _env: EnvUnowned, _this: JObject, ) -> jint { punktfunk_core::ABI_VERSION as jint @@ -85,11 +86,9 @@ pub extern "system" fn Java_io_unom_punktfunk_kit_NativeBridge_abiVersion( /// `NativeBridge.coreVersion(): String` — the crate version, proving JNI string marshaling works. #[unsafe(no_mangle)] pub extern "system" fn Java_io_unom_punktfunk_kit_NativeBridge_coreVersion<'local>( - env: JNIEnv<'local>, + mut env: EnvUnowned<'local>, _this: JObject<'local>, -) -> jni::sys::jstring { - match env.new_string(env!("CARGO_PKG_VERSION")) { - Ok(s) => s.into_raw(), - Err(_) => JObject::null().into_raw(), - } +) -> JString<'local> { + env.with_env(|env| env.new_string(env!("CARGO_PKG_VERSION"))) + .resolve::() } diff --git a/clients/android/native/src/probe.rs b/clients/android/native/src/probe.rs index 5e24bd6f..f39dc6e8 100644 --- a/clients/android/native/src/probe.rs +++ b/clients/android/native/src/probe.rs @@ -5,9 +5,10 @@ //! advertise on mDNS (reached over Tailscale / VPN / another subnet) — the display-side companion //! to the dial-first connect fix. +use jni::errors::LogErrorAndDefault; use jni::objects::{JObject, JString}; use jni::sys::{jboolean, jint}; -use jni::JNIEnv; +use jni::EnvUnowned; use punktfunk_core::client::NativeClient; use std::time::Duration; @@ -16,21 +17,17 @@ use std::time::Duration; /// Blocking (builds its own runtime) — Kotlin runs it on `Dispatchers.IO`, never the main thread. #[unsafe(no_mangle)] pub extern "system" fn Java_io_unom_punktfunk_kit_NativeBridge_nativeProbe<'local>( - mut env: JNIEnv<'local>, + mut env: EnvUnowned<'local>, _this: JObject<'local>, host: JString<'local>, port: jint, timeout_ms: jint, ) -> jboolean { - let host: String = match env.get_string(&host) { - Ok(s) => s.into(), - Err(_) => return 0, - }; - let port = port.clamp(0, u16::MAX as jint) as u16; - let timeout = Duration::from_millis(timeout_ms.max(0) as u64); - if NativeClient::probe(&host, port, timeout) { - 1 - } else { - 0 - } + env.with_env(|env| -> jni::errors::Result { + let host: String = host.try_to_string(env)?; + let port = port.clamp(0, u16::MAX as jint) as u16; + let timeout = Duration::from_millis(timeout_ms.max(0) as u64); + Ok(NativeClient::probe(&host, port, timeout)) + }) + .resolve::() } diff --git a/clients/android/native/src/session/clipboard.rs b/clients/android/native/src/session/clipboard.rs index 1d12db42..dd02bb65 100644 --- a/clients/android/native/src/session/clipboard.rs +++ b/clients/android/native/src/session/clipboard.rs @@ -15,9 +15,10 @@ use std::time::Duration; +use jni::errors::LogErrorAndDefault; use jni::objects::{JObject, JString}; -use jni::sys::{jboolean, jint, jlong, jstring}; -use jni::JNIEnv; +use jni::sys::{jboolean, jint, jlong}; +use jni::EnvUnowned; use punktfunk_core::clipboard::ClipEventCore; use punktfunk_core::error::PunktfunkError; use punktfunk_core::quic::{ClipKind, CLIP_FILE_INDEX_NONE, HOST_CAP_CLIPBOARD}; @@ -42,26 +43,24 @@ fn client(handle: jlong) -> Option<&'static SessionHandle> { /// `NativeBridge.nativeClipSupported(handle)` — the host advertised `HOST_CAP_CLIPBOARD`. #[unsafe(no_mangle)] pub extern "system" fn Java_io_unom_punktfunk_kit_NativeBridge_nativeClipSupported( - _env: JNIEnv, + _env: EnvUnowned, _this: JObject, handle: jlong, ) -> jboolean { - client(handle).map_or(0, |h| { - u8::from(h.client.host_caps() & HOST_CAP_CLIPBOARD != 0) - }) + client(handle).is_some_and(|h| h.client.host_caps() & HOST_CAP_CLIPBOARD != 0) } /// `NativeBridge.nativeClipControl(handle, enabled)` — session-level opt-in/out. Nothing /// clipboard-related happens on either side until an `enabled: true` crosses. #[unsafe(no_mangle)] pub extern "system" fn Java_io_unom_punktfunk_kit_NativeBridge_nativeClipControl( - _env: JNIEnv, + _env: EnvUnowned, _this: JObject, handle: jlong, enabled: jboolean, ) { if let Some(h) = client(handle) { - let _ = h.client.clip_control(enabled != 0, 0); + let _ = h.client.clip_control(enabled, 0); } } @@ -70,7 +69,7 @@ pub extern "system" fn Java_io_unom_punktfunk_kit_NativeBridge_nativeClipControl /// counter, newest wins. #[unsafe(no_mangle)] pub extern "system" fn Java_io_unom_punktfunk_kit_NativeBridge_nativeClipOfferText( - _env: JNIEnv, + _env: EnvUnowned, _this: JObject, handle: jlong, seq: jint, @@ -90,7 +89,7 @@ pub extern "system" fn Java_io_unom_punktfunk_kit_NativeBridge_nativeClipOfferTe /// Returns the transfer id echoed on the matching `data:`/`error:` event, or −1. #[unsafe(no_mangle)] pub extern "system" fn Java_io_unom_punktfunk_kit_NativeBridge_nativeClipFetchText( - _env: JNIEnv, + _env: EnvUnowned, _this: JObject, handle: jlong, seq: jint, @@ -108,26 +107,32 @@ pub extern "system" fn Java_io_unom_punktfunk_kit_NativeBridge_nativeClipFetchTe /// clipboard's current text (the host is pasting our offer). #[unsafe(no_mangle)] pub extern "system" fn Java_io_unom_punktfunk_kit_NativeBridge_nativeClipServeText( - mut env: JNIEnv, + mut env: EnvUnowned, _this: JObject, handle: jlong, req_id: jint, text: JString, ) { - let Some(h) = client(handle) else { return }; - let Ok(s) = env.get_string(&text) else { - let _ = h.client.clip_cancel(req_id as u32); - return; - }; - let _ = h - .client - .clip_serve(req_id as u32, String::from(s).into_bytes(), true); + env.with_env(|env| -> jni::errors::Result<()> { + let Some(h) = client(handle) else { + return Ok(()); + }; + // An unreadable payload still has to answer the host's `fetch:` — leaving it unanswered + // stalls the paste — so cancel the transfer rather than propagating the error. + let Ok(s) = text.try_to_string(env) else { + let _ = h.client.clip_cancel(req_id as u32); + return Ok(()); + }; + let _ = h.client.clip_serve(req_id as u32, s.into_bytes(), true); + Ok(()) + }) + .resolve::() } /// `NativeBridge.nativeClipCancel(handle, id)` — abort a transfer (either direction). #[unsafe(no_mangle)] pub extern "system" fn Java_io_unom_punktfunk_kit_NativeBridge_nativeClipCancel( - _env: JNIEnv, + _env: EnvUnowned, _this: JObject, handle: jlong, id: jint, @@ -145,38 +150,41 @@ pub extern "system" fn Java_io_unom_punktfunk_kit_NativeBridge_nativeClipCancel( /// clipboard task delivers a whole payload in ONE event (`last = true`), so a chunk boundary /// can never split a UTF-8 sequence. #[unsafe(no_mangle)] -pub extern "system" fn Java_io_unom_punktfunk_kit_NativeBridge_nativeNextClip( - env: JNIEnv, - _this: JObject, +pub extern "system" fn Java_io_unom_punktfunk_kit_NativeBridge_nativeNextClip<'local>( + mut env: EnvUnowned<'local>, + _this: JObject<'local>, handle: jlong, -) -> jstring { - let Some(h) = client(handle) else { - return std::ptr::null_mut(); - }; - let msg = match h.client.next_clip(Duration::from_millis(250)) { - Ok(ClipEventCore::State { enabled, .. }) => format!("state:{}", u8::from(enabled)), - Ok(ClipEventCore::RemoteOffer { seq, kinds }) => { - let has_text = kinds.iter().any(|k| k.mime.starts_with("text/plain")); - format!("offer:{seq}:{}", u8::from(has_text)) - } - Ok(ClipEventCore::FetchRequest { req_id, mime, .. }) => { - if mime.starts_with("text/plain") { - format!("fetch:{req_id}") - } else { - // We only ever offer text; cancel anything else rather than stall the host. - let _ = h.client.clip_cancel(req_id); - return std::ptr::null_mut(); +) -> JString<'local> { + // `JString::default()` is the null reference the old `std::ptr::null_mut()` returned, so the + // "null on timeout" contract in the doc comment above is unchanged. + env.with_env(|env| -> jni::errors::Result> { + let Some(h) = client(handle) else { + return Ok(JString::default()); + }; + let msg = match h.client.next_clip(Duration::from_millis(250)) { + Ok(ClipEventCore::State { enabled, .. }) => format!("state:{}", u8::from(enabled)), + Ok(ClipEventCore::RemoteOffer { seq, kinds }) => { + let has_text = kinds.iter().any(|k| k.mime.starts_with("text/plain")); + format!("offer:{seq}:{}", u8::from(has_text)) } - } - Ok(ClipEventCore::Data { xfer_id, bytes, .. }) => { - format!("data:{xfer_id}:{}", String::from_utf8_lossy(&bytes)) - } - Ok(ClipEventCore::Cancelled { id }) => format!("cancel:{id}"), - Ok(ClipEventCore::Error { id, code }) => format!("error:{id}:{code}"), - Err(PunktfunkError::NoFrame) => return std::ptr::null_mut(), - Err(_) => "closed".into(), - }; - env.new_string(msg) - .map(|s| s.into_raw()) - .unwrap_or(std::ptr::null_mut()) + Ok(ClipEventCore::FetchRequest { req_id, mime, .. }) => { + if mime.starts_with("text/plain") { + format!("fetch:{req_id}") + } else { + // We only ever offer text; cancel anything else rather than stall the host. + let _ = h.client.clip_cancel(req_id); + return Ok(JString::default()); + } + } + Ok(ClipEventCore::Data { xfer_id, bytes, .. }) => { + format!("data:{xfer_id}:{}", String::from_utf8_lossy(&bytes)) + } + Ok(ClipEventCore::Cancelled { id }) => format!("cancel:{id}"), + Ok(ClipEventCore::Error { id, code }) => format!("error:{id}:{code}"), + Err(PunktfunkError::NoFrame) => return Ok(JString::default()), + Err(_) => "closed".into(), + }; + env.new_string(msg) + }) + .resolve::() } diff --git a/clients/android/native/src/session/connect.rs b/clients/android/native/src/session/connect.rs index 0b3fdf1d..df9c8525 100644 --- a/clients/android/native/src/session/connect.rs +++ b/clients/android/native/src/session/connect.rs @@ -1,9 +1,10 @@ //! Connect lifecycle + the trust surface: identity mint, connect (TOFU / pinned), close, //! host-fingerprint read, and the SPAKE2 PIN pairing ceremony. +use jni::errors::LogErrorAndDefault; use jni::objects::{JObject, JString}; use jni::sys::{jboolean, jint, jlong}; -use jni::JNIEnv; +use jni::EnvUnowned; use punktfunk_core::client::NativeClient; use punktfunk_core::config::{CompositorPref, GamepadPref, Mode}; use std::sync::{Arc, Mutex}; @@ -38,14 +39,12 @@ fn note_error(e: &punktfunk_core::error::PunktfunkError) { /// handle / `""` fingerprint. #[unsafe(no_mangle)] pub extern "system" fn Java_io_unom_punktfunk_kit_NativeBridge_nativeTakeLastError<'local>( - env: JNIEnv<'local>, + mut env: EnvUnowned<'local>, _this: JObject<'local>, -) -> jni::sys::jstring { +) -> JString<'local> { let token = std::mem::take(&mut *lock_recover(&LAST_ERROR)); - match env.new_string(token) { - Ok(s) => s.into_raw(), - Err(_) => JObject::null().into_raw(), - } + env.with_env(|env| env.new_string(token)) + .resolve::() } /// `NativeBridge.nativeGenerateIdentity(): String` — mint a fresh persistent self-signed identity. @@ -53,9 +52,9 @@ pub extern "system" fn Java_io_unom_punktfunk_kit_NativeBridge_nativeTakeLastErr /// persists it (Keystore-wrapped) and only calls this again when the store is genuinely empty. #[unsafe(no_mangle)] pub extern "system" fn Java_io_unom_punktfunk_kit_NativeBridge_nativeGenerateIdentity<'local>( - env: JNIEnv<'local>, + mut env: EnvUnowned<'local>, _this: JObject<'local>, -) -> jni::sys::jstring { +) -> JString<'local> { let out = match punktfunk_core::quic::endpoint::generate_identity() { Ok((cert, key)) => format!("{cert}\n-----PUNKTFUNK-KEY-----\n{key}"), Err(e) => { @@ -63,10 +62,8 @@ pub extern "system" fn Java_io_unom_punktfunk_kit_NativeBridge_nativeGenerateIde String::new() } }; - match env.new_string(out) { - Ok(s) => s.into_raw(), - Err(_) => JObject::null().into_raw(), - } + env.with_env(|env| env.new_string(out)) + .resolve::() } /// `NativeBridge.nativeSetLowLatencyMode(enabled)` — apply the user's "Low-latency mode @@ -76,11 +73,11 @@ pub extern "system" fn Java_io_unom_punktfunk_kit_NativeBridge_nativeGenerateIde /// toggle rides explicit per-session parameters (`nativeStartVideo` / `nativeStartAudio`). #[unsafe(no_mangle)] pub extern "system" fn Java_io_unom_punktfunk_kit_NativeBridge_nativeSetLowLatencyMode( - _env: JNIEnv, + _env: EnvUnowned, _this: JObject, enabled: jboolean, ) { - punktfunk_core::transport::set_dscp_default(enabled != 0); + punktfunk_core::transport::set_dscp_default(enabled); } /// `debug.punktfunk.force_parts` = 1: arm slice-progressive parts delivery even when the @@ -123,7 +120,7 @@ fn force_parts_sysprop() -> bool { #[unsafe(no_mangle)] #[allow(clippy::too_many_arguments)] pub extern "system" fn Java_io_unom_punktfunk_kit_NativeBridge_nativeConnect<'local>( - mut env: JNIEnv<'local>, + mut env: EnvUnowned<'local>, _this: JObject<'local>, host: JString<'local>, port: jint, @@ -147,31 +144,44 @@ pub extern "system" fn Java_io_unom_punktfunk_kit_NativeBridge_nativeConnect<'lo device_name: JString<'local>, pad_audio_ok: jboolean, ) -> jlong { - let host: String = match env.get_string(&host) { - Ok(s) => s.into(), - Err(_) => return 0, + // Every JNI string this method needs, read up front in the one `Env` scope jni 0.22 grants a + // native method; everything below is pure Rust over owned `String`s. `None` = the mandatory + // `host` could not be read, which is the old `Err(_) => return 0` arm. + type ConnectStrings = Option<( + String, + String, + String, + String, + Option, + Option, + )>; + let strings: ConnectStrings = env + .with_env(|env| -> jni::errors::Result { + let Ok(host) = host.try_to_string(env) else { + return Ok(None); + }; + let cert: String = cert_pem.try_to_string(env).unwrap_or_default(); + let key: String = key_pem.try_to_string(env).unwrap_or_default(); + let pin_hex: String = pin_hex.try_to_string(env).unwrap_or_default(); + // A store-qualified library id (`steam:` / `custom:`) to boot straight into a + // game; null / empty ⇒ None (a plain desktop connect). Rides the Hello as `launch`. + let launch: Option = launch + .try_to_string(env) + .ok() + .filter(|s: &String| !s.is_empty()); + // The host's approval-list / trust-store label for this device; null / blank ⇒ None (the + // host falls back to its fingerprint-derived "device abcd1234" placeholder). + let device_name: Option = device_name + .try_to_string(env) + .ok() + .map(|s: String| s.trim().to_string()) + .filter(|s| !s.is_empty()); + Ok(Some((host, cert, key, pin_hex, launch, device_name))) + }) + .resolve::(); + let Some((host, cert, key, pin_hex, launch, device_name)) = strings else { + return 0; }; - let cert: String = env - .get_string(&cert_pem) - .map(Into::into) - .unwrap_or_default(); - let key: String = env.get_string(&key_pem).map(Into::into).unwrap_or_default(); - let pin_hex: String = env.get_string(&pin_hex).map(Into::into).unwrap_or_default(); - // A store-qualified library id (`steam:` / `custom:`) to boot straight into a game; - // null / empty ⇒ None (a plain desktop connect). Rides the Hello as `launch`. - let launch: Option = env - .get_string(&launch) - .map(Into::into) - .ok() - .filter(|s: &String| !s.is_empty()); - // The host's approval-list / trust-store label for this device; null / blank ⇒ None (the host - // falls back to its fingerprint-derived "device abcd1234" placeholder). - let device_name: Option = env - .get_string(&device_name) - .map(Into::into) - .ok() - .map(|s: String| s.trim().to_string()) - .filter(|s| !s.is_empty()); let identity: Option<(String, String)> = if cert.is_empty() || key.is_empty() { None @@ -184,16 +194,16 @@ pub extern "system" fn Java_io_unom_punktfunk_kit_NativeBridge_nativeConnect<'lo // the feature? (`adb shell setprop debug.punktfunk.force_parts 1` + stream restart; a codec // that can't take parts errors recoverably and the reanchor gate + keyframe path recovers.) let force_parts = force_parts_sysprop(); - let frame_parts = frame_parts_ok != 0 || force_parts; + let frame_parts = frame_parts_ok || force_parts; // The connect-time capability readout (`adb logcat -s pf.caps`): the P2 slice pipeline is // inert client-side unless BOTH probes pass — this line is the one place that says which. log::info!( target: "pf.caps", "decoder caps: multi_slice={} partial_frame={}{} hdr={} codec_bits={:#x}", - multi_slice_ok != 0, - frame_parts_ok != 0, + multi_slice_ok, + frame_parts_ok, if force_parts { " (FORCED by sysprop)" } else { "" }, - hdr_enabled != 0, + hdr_enabled, video_codecs, ); let pin: Option<[u8; 32]> = if pin_hex.is_empty() { @@ -229,11 +239,11 @@ pub extern "system" fn Java_io_unom_punktfunk_kit_NativeBridge_nativeConnect<'lo // decoder this device would use (`VideoDecoders.multiSliceTolerant` — Amlogic wedges the // whole device on multi-slice AUs, the 0.17.0 field regression) and only then may the // host default to >1 slice per frame (its sub-frame readback / the P2 slice pipeline). - (if hdr_enabled != 0 { + (if hdr_enabled { punktfunk_core::quic::VIDEO_CAP_10BIT | punktfunk_core::quic::VIDEO_CAP_HDR } else { 0 - }) | (if multi_slice_ok != 0 { + }) | (if multi_slice_ok { punktfunk_core::quic::VIDEO_CAP_MULTI_SLICE } else { 0 @@ -274,7 +284,7 @@ pub extern "system" fn Java_io_unom_punktfunk_kit_NativeBridge_nativeConnect<'lo // so declaring a pad's render caps later would have nothing to gate. Gated on the // settings so a user with pad audio off does not make the host provision endpoints. punktfunk_core::quic::CLIENT_CAP_PHASE_LOCK - | if pad_audio_ok != 0 { + | if pad_audio_ok { punktfunk_core::quic::CLIENT_CAP_PAD_AUDIO } else { 0 @@ -324,7 +334,7 @@ pub extern "system" fn Java_io_unom_punktfunk_kit_NativeBridge_nativeConnect<'lo /// closed exactly once and not concurrently with other calls on the same handle (Kotlin owns this). #[unsafe(no_mangle)] pub extern "system" fn Java_io_unom_punktfunk_kit_NativeBridge_nativeClose( - _env: JNIEnv, + _env: EnvUnowned, _this: JObject, handle: jlong, ) { @@ -346,7 +356,7 @@ pub extern "system" fn Java_io_unom_punktfunk_kit_NativeBridge_nativeClose( /// not freed / closed concurrently with this call (Kotlin still owns it and closes it via `nativeClose`). #[unsafe(no_mangle)] pub extern "system" fn Java_io_unom_punktfunk_kit_NativeBridge_nativeDisconnectQuit( - _env: JNIEnv, + _env: EnvUnowned, _this: JObject, handle: jlong, ) { @@ -365,10 +375,10 @@ pub extern "system" fn Java_io_unom_punktfunk_kit_NativeBridge_nativeDisconnectQ /// connect. `""` on a `0` handle. #[unsafe(no_mangle)] pub extern "system" fn Java_io_unom_punktfunk_kit_NativeBridge_nativeHostFingerprint<'local>( - env: JNIEnv<'local>, + mut env: EnvUnowned<'local>, _this: JObject<'local>, handle: jlong, -) -> jni::sys::jstring { +) -> JString<'local> { let out = if handle == 0 { String::new() } else { @@ -376,10 +386,8 @@ pub extern "system" fn Java_io_unom_punktfunk_kit_NativeBridge_nativeHostFingerp let h = unsafe { &*(handle as *const SessionHandle) }; hex32(&h.client.host_fingerprint) }; - match env.new_string(out) { - Ok(s) => s.into_raw(), - Err(_) => JObject::null().into_raw(), - } + env.with_env(|env| env.new_string(out)) + .resolve::() } /// `NativeBridge.nativeSessionEnded(handle): Boolean` — has the underlying QUIC session ended? @@ -390,17 +398,17 @@ pub extern "system" fn Java_io_unom_punktfunk_kit_NativeBridge_nativeHostFingerp /// handle. Cheap (one atomic load); safe on the UI thread. #[unsafe(no_mangle)] pub extern "system" fn Java_io_unom_punktfunk_kit_NativeBridge_nativeSessionEnded( - _env: JNIEnv, + _env: EnvUnowned, _this: JObject, handle: jlong, ) -> jboolean { - jni_guard(0, || { + jni_guard(false, || { if handle == 0 { - return 0; + return false; } // SAFETY: live handle per the nativeConnect/nativeClose contract. let h = unsafe { &*(handle as *const SessionHandle) }; - jboolean::from(h.client.is_session_ended()) + h.client.is_session_ended() }) } @@ -415,7 +423,7 @@ pub extern "system" fn Java_io_unom_punktfunk_kit_NativeBridge_nativeSessionEnde /// atomic load); safe on the UI thread. #[unsafe(no_mangle)] pub extern "system" fn Java_io_unom_punktfunk_kit_NativeBridge_nativeEndReason( - _env: JNIEnv, + _env: EnvUnowned, _this: JObject, handle: jlong, ) -> jint { @@ -436,7 +444,7 @@ pub extern "system" fn Java_io_unom_punktfunk_kit_NativeBridge_nativeEndReason( #[unsafe(no_mangle)] #[allow(clippy::too_many_arguments)] pub extern "system" fn Java_io_unom_punktfunk_kit_NativeBridge_nativePair<'local>( - mut env: JNIEnv<'local>, + mut env: EnvUnowned<'local>, _this: JObject<'local>, host: JString<'local>, port: jint, @@ -444,40 +452,40 @@ pub extern "system" fn Java_io_unom_punktfunk_kit_NativeBridge_nativePair<'local key_pem: JString<'local>, pin: JString<'local>, name: JString<'local>, -) -> jni::sys::jstring { - let g = |e: &mut JNIEnv<'local>, j: &JString<'local>| -> String { - e.get_string(j).map(Into::into).unwrap_or_default() - }; - let host = g(&mut env, &host); - let cert = g(&mut env, &cert_pem); - let key = g(&mut env, &key_pem); - let pin = g(&mut env, &pin); - let name = g(&mut env, &name); +) -> JString<'local> { + env.with_env(|env| -> jni::errors::Result> { + let g = |e: &jni::Env<'local>, j: &JString<'local>| -> String { + j.try_to_string(e).unwrap_or_default() + }; + let host = g(env, &host); + let cert = g(env, &cert_pem); + let key = g(env, &key_pem); + let pin = g(env, &pin); + let name = g(env, &name); - let out = if host.is_empty() || cert.is_empty() || key.is_empty() { - log::error!("nativePair: missing host/identity"); - String::new() - } else { - match NativeClient::pair( - &host, - port as u16, - (&cert, &key), // borrowed identity - &pin, - &name, - Duration::from_secs(60), - ) { - Ok(host_fp) => hex32(&host_fp), - Err(e) => { - // Crypto error == wrong PIN / MITM; anything else == transport/host reject. - // The token lets Kotlin say WHICH (`nativeTakeLastError`). - log::error!("nativePair to {host}:{port} failed: {e}"); - note_error(&e); - String::new() + let out = if host.is_empty() || cert.is_empty() || key.is_empty() { + log::error!("nativePair: missing host/identity"); + String::new() + } else { + match NativeClient::pair( + &host, + port as u16, + (&cert, &key), // borrowed identity + &pin, + &name, + Duration::from_secs(60), + ) { + Ok(host_fp) => hex32(&host_fp), + Err(e) => { + // Crypto error == wrong PIN / MITM; anything else == transport/host reject. + // The token lets Kotlin say WHICH (`nativeTakeLastError`). + log::error!("nativePair to {host}:{port} failed: {e}"); + note_error(&e); + String::new() + } } - } - }; - match env.new_string(out) { - Ok(s) => s.into_raw(), - Err(_) => JObject::null().into_raw(), - } + }; + env.new_string(out) + }) + .resolve::() } diff --git a/clients/android/native/src/session/input.rs b/clients/android/native/src/session/input.rs index a2a051fb..23d89ecc 100644 --- a/clients/android/native/src/session/input.rs +++ b/clients/android/native/src/session/input.rs @@ -6,9 +6,10 @@ //! conventions: buttons 1=left/2=middle/3=right/4=X1/5=X2; scroll axis 0=vertical/1=horizontal, //! signed 120-unit delta, +=up/right; keys are Windows VK (mapped from KEYCODE_* on the Kotlin side). +use jni::errors::LogErrorAndDefault; use jni::objects::{JByteBuffer, JFloatArray, JObject, JString}; use jni::sys::{jboolean, jint, jlong}; -use jni::JNIEnv; +use jni::EnvUnowned; use punktfunk_core::input::{InputEvent, InputKind}; use punktfunk_core::quic::{ PenSample, PenTool, RichInput, HID_REPORT_MAX, HOST_CAP_PEN, HOST_CAP_TEXT_INPUT, @@ -37,7 +38,7 @@ fn send_event(handle: jlong, kind: InputKind, code: u32, x: i32, y: i32, flags: /// `NativeBridge.nativeSendPointerMove(handle, dx, dy)` — relative mouse motion (screen +y down). #[unsafe(no_mangle)] pub extern "system" fn Java_io_unom_punktfunk_kit_NativeBridge_nativeSendPointerMove( - _env: JNIEnv, + _env: EnvUnowned, _this: JObject, handle: jlong, dx: jint, @@ -53,7 +54,7 @@ pub extern "system" fn Java_io_unom_punktfunk_kit_NativeBridge_nativeSendPointer /// cursor jumps to the finger — and matches the Apple client's absolute touch forwarding. #[unsafe(no_mangle)] pub extern "system" fn Java_io_unom_punktfunk_kit_NativeBridge_nativeSendPointerAbs( - _env: JNIEnv, + _env: EnvUnowned, _this: JObject, handle: jlong, x: jint, @@ -70,13 +71,13 @@ pub extern "system" fn Java_io_unom_punktfunk_kit_NativeBridge_nativeSendPointer /// `button`: GameStream id (1=left, 2=middle, 3=right, 4=X1, 5=X2). `down`: 1=press, 0=release. #[unsafe(no_mangle)] pub extern "system" fn Java_io_unom_punktfunk_kit_NativeBridge_nativeSendPointerButton( - _env: JNIEnv, + _env: EnvUnowned, _this: JObject, handle: jlong, button: jint, down: jboolean, ) { - let kind = if down != 0 { + let kind = if down { InputKind::MouseButtonDown } else { InputKind::MouseButtonUp @@ -88,7 +89,7 @@ pub extern "system" fn Java_io_unom_punktfunk_kit_NativeBridge_nativeSendPointer /// 1=horizontal. `delta`: signed, WHEEL_DELTA(120)-scaled, +=up/right. #[unsafe(no_mangle)] pub extern "system" fn Java_io_unom_punktfunk_kit_NativeBridge_nativeSendScroll( - _env: JNIEnv, + _env: EnvUnowned, _this: JObject, handle: jlong, axis: jint, @@ -105,7 +106,7 @@ pub extern "system" fn Java_io_unom_punktfunk_kit_NativeBridge_nativeSendScroll( /// (libei touchscreen / wlroots / SendInput). #[unsafe(no_mangle)] pub extern "system" fn Java_io_unom_punktfunk_kit_NativeBridge_nativeSendTouch( - _env: JNIEnv, + _env: EnvUnowned, _this: JObject, handle: jlong, id: jint, @@ -130,7 +131,7 @@ pub extern "system" fn Java_io_unom_punktfunk_kit_NativeBridge_nativeSendTouch( /// bitmask (0 for now — the host folds modifiers from the L/R modifier key events themselves). #[unsafe(no_mangle)] pub extern "system" fn Java_io_unom_punktfunk_kit_NativeBridge_nativeSendKey( - _env: JNIEnv, + _env: EnvUnowned, _this: JObject, handle: jlong, vk: jint, @@ -140,7 +141,7 @@ pub extern "system" fn Java_io_unom_punktfunk_kit_NativeBridge_nativeSendKey( if vk == 0 { return; } - let kind = if down != 0 { + let kind = if down { InputKind::KeyDown } else { InputKind::KeyUp @@ -153,16 +154,16 @@ pub extern "system" fn Java_io_unom_punktfunk_kit_NativeBridge_nativeSendKey( /// the real IME `InputConnection` over the TYPE_NULL raw-key fallback. `0` handle → false. #[unsafe(no_mangle)] pub extern "system" fn Java_io_unom_punktfunk_kit_NativeBridge_nativeTextInputSupported( - _env: JNIEnv, + _env: EnvUnowned, _this: JObject, handle: jlong, ) -> jboolean { if handle == 0 { - return 0; + return false; } // SAFETY: live handle per the nativeConnect/nativeClose contract; host_caps is &self. let h = unsafe { &*(handle as *const SessionHandle) }; - u8::from(h.client.host_caps() & HOST_CAP_TEXT_INPUT != 0) + h.client.host_caps() & HOST_CAP_TEXT_INPUT != 0 } /// `NativeBridge.nativeHostSupportsPen(handle)` — the host advertised `HOST_CAP_PEN`, so the @@ -170,16 +171,16 @@ pub extern "system" fn Java_io_unom_punktfunk_kit_NativeBridge_nativeTextInputSu /// (design/pen-tablet-input.md §7). `0` handle → false. #[unsafe(no_mangle)] pub extern "system" fn Java_io_unom_punktfunk_kit_NativeBridge_nativeHostSupportsPen( - _env: JNIEnv, + _env: EnvUnowned, _this: JObject, handle: jlong, ) -> jboolean { if handle == 0 { - return 0; + return false; } // SAFETY: live handle per the nativeConnect/nativeClose contract; host_caps is &self. let h = unsafe { &*(handle as *const SessionHandle) }; - u8::from(h.client.host_caps() & HOST_CAP_PEN != 0) + h.client.host_caps() & HOST_CAP_PEN != 0 } /// Floats per sample in the `nativeSendPen` flat array. @@ -199,65 +200,69 @@ const PEN_JNI_MAX_SAMPLES: usize = PEN_BATCH_MAX * 8; /// while in range (Kotlin side — see `StylusStream`). #[unsafe(no_mangle)] pub extern "system" fn Java_io_unom_punktfunk_kit_NativeBridge_nativeSendPen( - env: JNIEnv, + mut env: EnvUnowned, _this: JObject, handle: jlong, samples: JFloatArray, count: jint, ) { - if handle == 0 || count <= 0 { - return; - } - let count = (count as usize).min(PEN_JNI_MAX_SAMPLES); - let mut buf = [0f32; PEN_JNI_MAX_SAMPLES * PEN_JNI_STRIDE]; - let flat = &mut buf[..count * PEN_JNI_STRIDE]; - if env.get_float_array_region(&samples, 0, flat).is_err() { - return; // short array — a bridge bug, never worth a crash on the input path - } - // SAFETY: live handle per the nativeConnect/nativeClose contract; send_pen is &self. - let h = unsafe { &*(handle as *const SessionHandle) }; - let mut batch = [PenSample::default(); PEN_BATCH_MAX]; - for run in flat.chunks(PEN_BATCH_MAX * PEN_JNI_STRIDE) { - let n = run.len() / PEN_JNI_STRIDE; - for (slot, s) in batch.iter_mut().zip(run.chunks_exact(PEN_JNI_STRIDE)) { - if !s[2].is_finite() || !s[3].is_finite() { - return; // never forward a NaN coordinate - } - *slot = PenSample { - state: s[0] as u8, - tool: if s[1] as u8 == 1 { - PenTool::Eraser - } else { - PenTool::Pen - }, - x: s[2].clamp(0.0, 1.0), - y: s[3].clamp(0.0, 1.0), - pressure: (s[4].clamp(0.0, 1.0) * 65535.0) as u16, - distance: if s[5] < 0.0 { - PEN_DISTANCE_UNKNOWN - } else { - (s[5].clamp(0.0, 1.0) * 65534.0) as u16 - }, - tilt_deg: if s[6] < 0.0 { - PEN_TILT_UNKNOWN - } else { - (s[6].clamp(0.0, 90.0)) as u8 - }, - azimuth_deg: if s[7] < 0.0 { - PEN_ANGLE_UNKNOWN - } else { - (s[7] as u16) % 360 - }, - roll_deg: if s[8] < 0.0 { - PEN_ANGLE_UNKNOWN - } else { - (s[8] as u16) % 360 - }, - dt_us: s[9].clamp(0.0, 65535.0) as u16, - }; + env.with_env(|env| -> jni::errors::Result<()> { + if handle == 0 || count <= 0 { + return Ok(()); } - let _ = h.client.send_pen(&batch[..n]); - } + let count = (count as usize).min(PEN_JNI_MAX_SAMPLES); + let mut buf = [0f32; PEN_JNI_MAX_SAMPLES * PEN_JNI_STRIDE]; + let flat = &mut buf[..count * PEN_JNI_STRIDE]; + if samples.get_region(env, 0, flat).is_err() { + return Ok(()); // short array — a bridge bug, never worth a crash on the input path + } + // SAFETY: live handle per the nativeConnect/nativeClose contract; send_pen is &self. + let h = unsafe { &*(handle as *const SessionHandle) }; + let mut batch = [PenSample::default(); PEN_BATCH_MAX]; + for run in flat.chunks(PEN_BATCH_MAX * PEN_JNI_STRIDE) { + let n = run.len() / PEN_JNI_STRIDE; + for (slot, s) in batch.iter_mut().zip(run.chunks_exact(PEN_JNI_STRIDE)) { + if !s[2].is_finite() || !s[3].is_finite() { + return Ok(()); // never forward a NaN coordinate + } + *slot = PenSample { + state: s[0] as u8, + tool: if s[1] as u8 == 1 { + PenTool::Eraser + } else { + PenTool::Pen + }, + x: s[2].clamp(0.0, 1.0), + y: s[3].clamp(0.0, 1.0), + pressure: (s[4].clamp(0.0, 1.0) * 65535.0) as u16, + distance: if s[5] < 0.0 { + PEN_DISTANCE_UNKNOWN + } else { + (s[5].clamp(0.0, 1.0) * 65534.0) as u16 + }, + tilt_deg: if s[6] < 0.0 { + PEN_TILT_UNKNOWN + } else { + (s[6].clamp(0.0, 90.0)) as u8 + }, + azimuth_deg: if s[7] < 0.0 { + PEN_ANGLE_UNKNOWN + } else { + (s[7] as u16) % 360 + }, + roll_deg: if s[8] < 0.0 { + PEN_ANGLE_UNKNOWN + } else { + (s[8] as u16) % 360 + }, + dt_us: s[9].clamp(0.0, 65535.0) as u16, + }; + } + let _ = h.client.send_pen(&batch[..n]); + } + Ok(()) + }) + .resolve::() } /// `NativeBridge.nativeSendText(handle, text)` — committed IME text, one `TextInput` event per @@ -266,20 +271,24 @@ pub extern "system" fn Java_io_unom_punktfunk_kit_NativeBridge_nativeSendPen( /// [`Java_io_unom_punktfunk_kit_NativeBridge_nativeTextInputSupported`] returned true. #[unsafe(no_mangle)] pub extern "system" fn Java_io_unom_punktfunk_kit_NativeBridge_nativeSendText( - mut env: JNIEnv, + mut env: EnvUnowned, _this: JObject, handle: jlong, text: JString, ) { - if handle == 0 { - return; - } - let Ok(s) = env.get_string(&text) else { - return; - }; - for ch in String::from(s).chars().filter(|c| !c.is_control()) { - send_event(handle, InputKind::TextInput, ch as u32, 0, 0, 0); - } + env.with_env(|env| -> jni::errors::Result<()> { + if handle == 0 { + return Ok(()); + } + let Ok(s) = text.try_to_string(env) else { + return Ok(()); + }; + for ch in s.chars().filter(|c| !c.is_control()) { + send_event(handle, InputKind::TextInput, ch as u32, 0, 0, 0); + } + Ok(()) + }) + .resolve::() } // ---- Gamepad: Kotlin captures (KeyEvent/MotionEvent) → NativeClient::send_input --------------- @@ -298,7 +307,7 @@ pub extern "system" fn Java_io_unom_punktfunk_kit_NativeBridge_nativeSendText( /// 0=release. `pad`: wire pad index 0..15 (rides `flags`). #[unsafe(no_mangle)] pub extern "system" fn Java_io_unom_punktfunk_kit_NativeBridge_nativeSendGamepadButton( - _env: JNIEnv, + _env: EnvUnowned, _this: JObject, handle: jlong, bit: jint, @@ -309,7 +318,7 @@ pub extern "system" fn Java_io_unom_punktfunk_kit_NativeBridge_nativeSendGamepad handle, InputKind::GamepadButton, bit as u32, - i32::from(down != 0), + i32::from(down), 0, pad as u32, ); @@ -320,7 +329,7 @@ pub extern "system" fn Java_io_unom_punktfunk_kit_NativeBridge_nativeSendGamepad /// (−32768..32767, +y=up) or trigger 0..255. `pad`: wire pad index 0..15 (rides `flags`). #[unsafe(no_mangle)] pub extern "system" fn Java_io_unom_punktfunk_kit_NativeBridge_nativeSendGamepadAxis( - _env: JNIEnv, + _env: EnvUnowned, _this: JObject, handle: jlong, axis_id: jint, @@ -345,7 +354,7 @@ pub extern "system" fn Java_io_unom_punktfunk_kit_NativeBridge_nativeSendGamepad /// the session-default kind from the handshake — the pre-existing single-pad behaviour on pad 0). #[unsafe(no_mangle)] pub extern "system" fn Java_io_unom_punktfunk_kit_NativeBridge_nativeSendGamepadArrival( - _env: JNIEnv, + _env: EnvUnowned, _this: JObject, handle: jlong, pref: jint, @@ -375,24 +384,24 @@ pub extern "system" fn Java_io_unom_punktfunk_kit_NativeBridge_nativeSendGamepad /// the `Auto` rule inside the predicate itself. #[unsafe(no_mangle)] pub extern "system" fn Java_io_unom_punktfunk_kit_NativeBridge_nativePadMotionReaches( - _env: JNIEnv, + _env: EnvUnowned, _this: JObject, handle: jlong, declared_pref: jint, ) -> jboolean { if handle == 0 { - return 1; + return true; } // SAFETY: live handle per the nativeConnect/nativeClose contract; both fields are plain Copy // values read behind `&self`. let h = unsafe { &*(handle as *const SessionHandle) }; let declared = punktfunk_core::config::GamepadPref::from_u8(declared_pref.clamp(0, u8::MAX as jint) as u8); - u8::from(punktfunk_core::config::pad_motion_reaches( + punktfunk_core::config::pad_motion_reaches( declared, h.client.requested_gamepad, h.client.resolved_gamepad, - )) + ) } /// `NativeBridge.nativeSendGamepadRemove(handle, pad)` — signal that wire pad index `pad` was @@ -401,7 +410,7 @@ pub extern "system" fn Java_io_unom_punktfunk_kit_NativeBridge_nativePadMotionRe /// pad) and arms a re-send burst against datagram loss. An older host ignores the unknown tag. #[unsafe(no_mangle)] pub extern "system" fn Java_io_unom_punktfunk_kit_NativeBridge_nativeSendGamepadRemove( - _env: JNIEnv, + _env: EnvUnowned, _this: JObject, handle: jlong, pad: jint, @@ -417,36 +426,40 @@ pub extern "system" fn Java_io_unom_punktfunk_kit_NativeBridge_nativeSendGamepad /// own report rate (~250–500 Hz) — the direct-buffer read avoids a JNI array copy per report. #[unsafe(no_mangle)] pub extern "system" fn Java_io_unom_punktfunk_kit_NativeBridge_nativeSendPadHidReport( - env: JNIEnv, + mut env: EnvUnowned, _this: JObject, handle: jlong, pad: jint, buf: JByteBuffer, len: jint, ) { - if handle == 0 || len <= 0 { - return; - } - let cap = match env.get_direct_buffer_capacity(&buf) { - Ok(c) => c, - Err(_) => return, - }; - let ptr = match env.get_direct_buffer_address(&buf) { - Ok(p) if !p.is_null() => p, - _ => return, - }; - let n = (len as usize).min(cap).min(HID_REPORT_MAX); - let mut data = [0u8; HID_REPORT_MAX]; - // SAFETY: `ptr`/`cap` describe the direct ByteBuffer's backing store, valid for this call; - // `n` is bounded by both the buffer capacity and the fixed wire body. - data[..n].copy_from_slice(unsafe { std::slice::from_raw_parts(ptr, n) }); - // SAFETY: live handle per the nativeConnect/nativeClose contract; send_rich_input is &self. - let h = unsafe { &*(handle as *const SessionHandle) }; - let _ = h.client.send_rich_input(RichInput::HidReport { - pad: (pad as u32 & 0xF) as u8, - len: n as u8, - data, - }); + env.with_env(|env| -> jni::errors::Result<()> { + if handle == 0 || len <= 0 { + return Ok(()); + } + let cap = match env.get_direct_buffer_capacity(&buf) { + Ok(c) => c, + Err(_) => return Ok(()), + }; + let ptr = match env.get_direct_buffer_address(&buf) { + Ok(p) if !p.is_null() => p, + _ => return Ok(()), + }; + let n = (len as usize).min(cap).min(HID_REPORT_MAX); + let mut data = [0u8; HID_REPORT_MAX]; + // SAFETY: `ptr`/`cap` describe the direct ByteBuffer's backing store, valid for this call; + // `n` is bounded by both the buffer capacity and the fixed wire body. + data[..n].copy_from_slice(unsafe { std::slice::from_raw_parts(ptr, n) }); + // SAFETY: live handle per the nativeConnect/nativeClose contract; send_rich_input is &self. + let h = unsafe { &*(handle as *const SessionHandle) }; + let _ = h.client.send_rich_input(RichInput::HidReport { + pad: (pad as u32 & 0xF) as u8, + len: n as u8, + data, + }); + Ok(()) + }) + .resolve::() } /// `NativeBridge.nativeSendPadTouch(handle, pad, finger, active, x, y)` — one touchpad contact @@ -457,7 +470,7 @@ pub extern "system" fn Java_io_unom_punktfunk_kit_NativeBridge_nativeSendPadHidR /// the capture diffs, the host holds per-slot state. #[unsafe(no_mangle)] pub extern "system" fn Java_io_unom_punktfunk_kit_NativeBridge_nativeSendPadTouch( - _env: JNIEnv, + _env: EnvUnowned, _this: JObject, handle: jlong, pad: jint, @@ -474,7 +487,7 @@ pub extern "system" fn Java_io_unom_punktfunk_kit_NativeBridge_nativeSendPadTouc let _ = h.client.send_rich_input(RichInput::Touchpad { pad: (pad as u32 & 0xF) as u8, finger: (finger as u32 & 0x1) as u8, - active: active != 0, + active, x: (x as i64).clamp(0, 65535) as u16, y: (y as i64).clamp(0, 65535) as u16, }); @@ -488,7 +501,7 @@ pub extern "system" fn Java_io_unom_punktfunk_kit_NativeBridge_nativeSendPadTouc #[unsafe(no_mangle)] #[allow(clippy::too_many_arguments)] pub extern "system" fn Java_io_unom_punktfunk_kit_NativeBridge_nativeSendPadMotion( - _env: JNIEnv, + _env: EnvUnowned, _this: JObject, handle: jlong, pad: jint, diff --git a/clients/android/native/src/session/planes.rs b/clients/android/native/src/session/planes.rs index 0ca4c80d..416bd7da 100644 --- a/clients/android/native/src/session/planes.rs +++ b/clients/android/native/src/session/planes.rs @@ -1,12 +1,10 @@ //! Plane start/stop: video (HEVC decode → Surface), host→client audio, mic uplink — plus the //! ~1 Hz decode-stats drain for the HUD. -use jni::objects::JObject; -// Used only by the android-gated `nativeStartVideo`; on the host build that fn is cfg'd out. -#[cfg(target_os = "android")] -use jni::objects::JString; -use jni::sys::{jboolean, jdoubleArray, jintArray, jlong, jsize, jstring}; -use jni::JNIEnv; +use jni::errors::LogErrorAndDefault; +use jni::objects::{JDoubleArray, JIntArray, JObject, JString}; +use jni::sys::{jboolean, jlong}; +use jni::EnvUnowned; use super::{jni_guard, lock_recover, SessionHandle}; @@ -21,7 +19,7 @@ use super::{jni_guard, lock_recover, SessionHandle}; #[cfg(target_os = "android")] #[unsafe(no_mangle)] pub extern "system" fn Java_io_unom_punktfunk_kit_NativeBridge_nativeStartVideo( - mut env: JNIEnv, + mut env: EnvUnowned, _this: JObject, handle: jlong, surface: JObject, @@ -37,53 +35,58 @@ pub extern "system" fn Java_io_unom_punktfunk_kit_NativeBridge_nativeStartVideo( use std::sync::atomic::AtomicBool; use std::sync::Arc; - if handle == 0 { - return; - } - // The decoder name Kotlin picked (empty string / read failure ⇒ None ⇒ default resolver). - let decoder = env - .get_string(&decoder_name) - .ok() - .map(String::from) - .filter(|s| !s.is_empty()); - // SAFETY: live handle per the nativeConnect/nativeClose contract. - let h = unsafe { &*(handle as *const SessionHandle) }; - let mut guard = lock_recover(&h.video); - if guard.is_some() { - return; // already streaming - } - // SAFETY: `env`/`surface` are valid JNI pointers for this call. `as *mut _` bridges any - // jni-sys version skew between the `jni` and `ndk` crates (both are raw `*mut _` pointers). - let window = match unsafe { - ndk::native_window::NativeWindow::from_surface( - env.get_native_interface() as *mut _, - surface.as_raw() as *mut _, - ) - } { - Some(w) => w, - None => { - log::error!("nativeStartVideo: no ANativeWindow from Surface"); - return; + env.with_env(|env| -> jni::errors::Result<()> { + if handle == 0 { + return Ok(()); } - }; - let shutdown = Arc::new(AtomicBool::new(false)); - let client = h.client.clone(); - let sd = shutdown.clone(); - let st = h.stats.clone(); // session-lifetime stats (gate survives surface recreate) - let opts = crate::decode::DecodeOptions { - decoder_name: decoder, - ll_feature: ll_feature != 0, - low_latency_mode: low_latency_mode != 0, - is_tv: is_tv != 0, - present_priority, - smooth_buffer, - panel_hz: panel_fps, - }; - let join = std::thread::Builder::new() - .name("pf-decode".into()) - .spawn(move || crate::decode::run(client, window, sd, st, opts)) - .ok(); - *guard = Some(VideoThread { shutdown, join }); + // The decoder name Kotlin picked (empty string / read failure ⇒ None ⇒ default resolver). + let decoder = decoder_name + .try_to_string(env) + .ok() + .filter(|s| !s.is_empty()); + // SAFETY: live handle per the nativeConnect/nativeClose contract. + let h = unsafe { &*(handle as *const SessionHandle) }; + let mut guard = lock_recover(&h.video); + if guard.is_some() { + return Ok(()); // already streaming + } + // SAFETY: `env`/`surface` are valid JNI pointers for this call. `as *mut _` bridges any + // jni-sys version skew between the `jni` and `ndk` crates (both are raw `*mut _` pointers) + // — a real skew here, not a hypothetical one: `jni` is on jni-sys 0.4 while the vendored + // `ndk` is still on 0.3. + let window = match unsafe { + ndk::native_window::NativeWindow::from_surface( + env.get_raw() as *mut _, + surface.as_raw() as *mut _, + ) + } { + Some(w) => w, + None => { + log::error!("nativeStartVideo: no ANativeWindow from Surface"); + return Ok(()); + } + }; + let shutdown = Arc::new(AtomicBool::new(false)); + let client = h.client.clone(); + let sd = shutdown.clone(); + let st = h.stats.clone(); // session-lifetime stats (gate survives surface recreate) + let opts = crate::decode::DecodeOptions { + decoder_name: decoder, + ll_feature, + low_latency_mode, + is_tv, + present_priority, + smooth_buffer, + panel_hz: panel_fps, + }; + let join = std::thread::Builder::new() + .name("pf-decode".into()) + .spawn(move || crate::decode::run(client, window, sd, st, opts)) + .ok(); + *guard = Some(VideoThread { shutdown, join }); + Ok(()) + }) + .resolve::() } /// `NativeBridge.nativeVideoMime(handle): String` — the MediaCodec MIME for the codec the host @@ -93,21 +96,19 @@ pub extern "system" fn Java_io_unom_punktfunk_kit_NativeBridge_nativeStartVideo( #[cfg(target_os = "android")] #[unsafe(no_mangle)] pub extern "system" fn Java_io_unom_punktfunk_kit_NativeBridge_nativeVideoMime<'local>( - env: JNIEnv<'local>, + mut env: EnvUnowned<'local>, _this: JObject<'local>, handle: jlong, -) -> jstring { - jni_guard(std::ptr::null_mut(), || { +) -> JString<'local> { + env.with_env(|env| -> jni::errors::Result> { if handle == 0 { - return std::ptr::null_mut(); + return Ok(JString::default()); } // SAFETY: live handle per the nativeConnect/nativeClose contract. let h = unsafe { &*(handle as *const SessionHandle) }; - match env.new_string(crate::decode::codec_mime(h.client.codec)) { - Ok(s) => s.into_raw(), - Err(_) => std::ptr::null_mut(), - } + env.new_string(crate::decode::codec_mime(h.client.codec)) }) + .resolve::() } /// `NativeBridge.nativeVideoCodecLabel(handle): String` — a short human label for the codec the @@ -118,21 +119,19 @@ pub extern "system" fn Java_io_unom_punktfunk_kit_NativeBridge_nativeVideoMime<' #[cfg(target_os = "android")] #[unsafe(no_mangle)] pub extern "system" fn Java_io_unom_punktfunk_kit_NativeBridge_nativeVideoCodecLabel<'local>( - env: JNIEnv<'local>, + mut env: EnvUnowned<'local>, _this: JObject<'local>, handle: jlong, -) -> jstring { - jni_guard(std::ptr::null_mut(), || { +) -> JString<'local> { + env.with_env(|env| -> jni::errors::Result> { if handle == 0 { - return std::ptr::null_mut(); + return Ok(JString::default()); } // SAFETY: live handle per the nativeConnect/nativeClose contract. let h = unsafe { &*(handle as *const SessionHandle) }; - match env.new_string(crate::decode::codec_label(h.client.codec)) { - Ok(s) => s.into_raw(), - Err(_) => std::ptr::null_mut(), - } + env.new_string(crate::decode::codec_label(h.client.codec)) }) + .resolve::() } /// `NativeBridge.nativeVideoDecoderLabel(handle): String` — the resolved decoder identity for the @@ -142,28 +141,26 @@ pub extern "system" fn Java_io_unom_punktfunk_kit_NativeBridge_nativeVideoCodecL /// device). #[unsafe(no_mangle)] pub extern "system" fn Java_io_unom_punktfunk_kit_NativeBridge_nativeVideoDecoderLabel<'local>( - env: JNIEnv<'local>, + mut env: EnvUnowned<'local>, _this: JObject<'local>, handle: jlong, -) -> jstring { - jni_guard(std::ptr::null_mut(), || { +) -> JString<'local> { + env.with_env(|env| -> jni::errors::Result> { if handle == 0 { - return std::ptr::null_mut(); + return Ok(JString::default()); } // SAFETY: live handle per the nativeConnect/nativeClose contract. let h = unsafe { &*(handle as *const SessionHandle) }; - match env.new_string(h.stats.decoder_label()) { - Ok(s) => s.into_raw(), - Err(_) => std::ptr::null_mut(), - } + env.new_string(h.stats.decoder_label()) }) + .resolve::() } /// `NativeBridge.nativeStopVideo(handle)` — stop + join the decode thread (without closing the /// session). No-op on `0`. #[unsafe(no_mangle)] pub extern "system" fn Java_io_unom_punktfunk_kit_NativeBridge_nativeStopVideo( - _env: JNIEnv, + _env: EnvUnowned, _this: JObject, handle: jlong, ) { @@ -211,19 +208,19 @@ pub extern "system" fn Java_io_unom_punktfunk_kit_NativeBridge_nativeStopVideo( /// resets the measurement window. Not android-gated — pure `jni` + connector reads, so it links on /// the host build too (Kotlin only ever calls it on device). #[unsafe(no_mangle)] -pub extern "system" fn Java_io_unom_punktfunk_kit_NativeBridge_nativeVideoStats( - env: JNIEnv, - _this: JObject, +pub extern "system" fn Java_io_unom_punktfunk_kit_NativeBridge_nativeVideoStats<'local>( + mut env: EnvUnowned<'local>, + _this: JObject<'local>, handle: jlong, -) -> jdoubleArray { - jni_guard(std::ptr::null_mut(), || { +) -> JDoubleArray<'local> { + env.with_env(|env| -> jni::errors::Result> { if handle == 0 { - return std::ptr::null_mut(); + return Ok(JDoubleArray::default()); } // SAFETY: live handle per the nativeConnect/nativeClose contract. let h = unsafe { &*(handle as *const SessionHandle) }; if lock_recover(&h.video).is_none() { - return std::ptr::null_mut(); // not streaming → no stats + return Ok(JDoubleArray::default()); // not streaming → no stats } let snap = h .stats @@ -294,15 +291,11 @@ pub extern "system" fn Java_io_unom_punktfunk_kit_NativeBridge_nativeVideoStats( h.client.audio_buffer_ms() as f64, h.client.audio_av_offset_ms() as f64, ]; - let arr = match env.new_double_array(buf.len() as jsize) { - Ok(a) => a, - Err(_) => return std::ptr::null_mut(), - }; - if env.set_double_array_region(&arr, 0, &buf).is_err() { - return std::ptr::null_mut(); - } - arr.into_raw() + let arr = env.new_double_array(buf.len())?; + arr.set_region(env, 0, &buf)?; + Ok(arr) }) + .resolve::() } /// `NativeBridge.nativeVideoSize(handle): IntArray?` — the negotiated video mode as @@ -313,14 +306,14 @@ pub extern "system" fn Java_io_unom_punktfunk_kit_NativeBridge_nativeVideoStats( /// on a `0` handle. Not android-gated — pure `jni` + a connector read, so it links on the host /// build too. Cheap; safe on the UI thread. #[unsafe(no_mangle)] -pub extern "system" fn Java_io_unom_punktfunk_kit_NativeBridge_nativeVideoSize( - env: JNIEnv, - _this: JObject, +pub extern "system" fn Java_io_unom_punktfunk_kit_NativeBridge_nativeVideoSize<'local>( + mut env: EnvUnowned<'local>, + _this: JObject<'local>, handle: jlong, -) -> jintArray { - jni_guard(std::ptr::null_mut(), || { +) -> JIntArray<'local> { + env.with_env(|env| -> jni::errors::Result> { if handle == 0 { - return std::ptr::null_mut(); + return Ok(JIntArray::default()); } // SAFETY: live handle per the nativeConnect/nativeClose contract. let h = unsafe { &*(handle as *const SessionHandle) }; @@ -330,15 +323,11 @@ pub extern "system" fn Java_io_unom_punktfunk_kit_NativeBridge_nativeVideoSize( mode.height as i32, mode.refresh_hz as i32, ]; - let arr = match env.new_int_array(buf.len() as jsize) { - Ok(a) => a, - Err(_) => return std::ptr::null_mut(), - }; - if env.set_int_array_region(&arr, 0, &buf).is_err() { - return std::ptr::null_mut(); - } - arr.into_raw() + let arr = env.new_int_array(buf.len())?; + arr.set_region(env, 0, &buf)?; + Ok(arr) }) + .resolve::() } /// `NativeBridge.nativeSetVideoStatsEnabled(handle, enabled)` — gate per-frame stats sampling on the @@ -348,7 +337,7 @@ pub extern "system" fn Java_io_unom_punktfunk_kit_NativeBridge_nativeVideoSize( /// pure `jni` + an atomic store, so it links on the host build too. #[unsafe(no_mangle)] pub extern "system" fn Java_io_unom_punktfunk_kit_NativeBridge_nativeSetVideoStatsEnabled( - _env: JNIEnv, + _env: EnvUnowned, _this: JObject, handle: jlong, enabled: jboolean, @@ -360,7 +349,7 @@ pub extern "system" fn Java_io_unom_punktfunk_kit_NativeBridge_nativeSetVideoSta // The current cumulative counters seed the window baselines, so the first snapshot's // `lost`/`FEC` cover only time the HUD was actually up. h.stats.set_enabled( - enabled != 0, + enabled, h.client.frames_dropped(), h.client.fec_recovered_shards(), ); @@ -375,7 +364,7 @@ pub extern "system" fn Java_io_unom_punktfunk_kit_NativeBridge_nativeSetVideoSta #[cfg(target_os = "android")] #[unsafe(no_mangle)] pub extern "system" fn Java_io_unom_punktfunk_kit_NativeBridge_nativeStartAudio( - _env: JNIEnv, + _env: EnvUnowned, _this: JObject, handle: jlong, low_latency_mode: jboolean, @@ -389,7 +378,7 @@ pub extern "system" fn Java_io_unom_punktfunk_kit_NativeBridge_nativeStartAudio( if guard.is_some() { return; // already playing } - match crate::audio::AudioPlayback::start(h.client.clone(), low_latency_mode != 0) { + match crate::audio::AudioPlayback::start(h.client.clone(), low_latency_mode) { Some(p) => *guard = Some(p), None => log::error!("nativeStartAudio: playback init failed (video unaffected)"), } @@ -400,7 +389,7 @@ pub extern "system" fn Java_io_unom_punktfunk_kit_NativeBridge_nativeStartAudio( #[cfg(target_os = "android")] #[unsafe(no_mangle)] pub extern "system" fn Java_io_unom_punktfunk_kit_NativeBridge_nativeStopAudio( - _env: JNIEnv, + _env: EnvUnowned, _this: JObject, handle: jlong, ) { @@ -424,7 +413,7 @@ pub extern "system" fn Java_io_unom_punktfunk_kit_NativeBridge_nativeStopAudio( #[cfg(target_os = "android")] #[unsafe(no_mangle)] pub extern "system" fn Java_io_unom_punktfunk_kit_NativeBridge_nativeStartMic( - _env: JNIEnv, + _env: EnvUnowned, _this: JObject, handle: jlong, echo_cancel: jboolean, @@ -440,7 +429,7 @@ pub extern "system" fn Java_io_unom_punktfunk_kit_NativeBridge_nativeStartMic( } // The capture SHARES the session's mute flag, so one started while muted stays muted (and // sends nothing) from its very first frame — see `SessionHandle::mic_muted`. - match crate::mic::MicCapture::start(h.client.clone(), echo_cancel != 0, h.mic_muted.clone()) { + match crate::mic::MicCapture::start(h.client.clone(), echo_cancel, h.mic_muted.clone()) { Some(m) => { let session_id = m.session_id(); *guard = Some(m); @@ -459,7 +448,7 @@ pub extern "system" fn Java_io_unom_punktfunk_kit_NativeBridge_nativeStartMic( #[cfg(target_os = "android")] #[unsafe(no_mangle)] pub extern "system" fn Java_io_unom_punktfunk_kit_NativeBridge_nativeStopMic( - _env: JNIEnv, + _env: EnvUnowned, _this: JObject, handle: jlong, ) { @@ -487,7 +476,7 @@ pub extern "system" fn Java_io_unom_punktfunk_kit_NativeBridge_nativeStopMic( #[unsafe(no_mangle)] #[cfg(target_os = "android")] pub extern "system" fn Java_io_unom_punktfunk_kit_NativeBridge_nativeStartPadAudio( - _env: JNIEnv, + _env: EnvUnowned, _this: JObject, handle: jlong, pad: jni::sys::jint, @@ -495,9 +484,9 @@ pub extern "system" fn Java_io_unom_punktfunk_kit_NativeBridge_nativeStartPadAud haptics: jboolean, speaker: jboolean, ) -> jboolean { - jni_guard(0, || { + jni_guard(false, || { if handle == 0 || fd < 0 || !(0..16).contains(&pad) { - return 0; + return false; } // SAFETY: live handle per the nativeConnect/nativeClose contract. let h = unsafe { &*(handle as *const SessionHandle) }; @@ -512,14 +501,14 @@ pub extern "system" fn Java_io_unom_punktfunk_kit_NativeBridge_nativeStartPadAud std::sync::Arc::clone(&h.client), pad as u8, fd, - haptics != 0, - speaker != 0, + haptics, + speaker, ) { Some(p) => { *lock_recover(&h.pad_audio) = Some(p); - 1 + true } - None => 0, + None => false, } }) } @@ -533,7 +522,7 @@ pub extern "system" fn Java_io_unom_punktfunk_kit_NativeBridge_nativeStartPadAud #[unsafe(no_mangle)] #[cfg(target_os = "android")] pub extern "system" fn Java_io_unom_punktfunk_kit_NativeBridge_nativePadAudioSelfTest( - _env: JNIEnv, + _env: EnvUnowned, _this: JObject, fd: jni::sys::jint, seconds: jni::sys::jint, @@ -556,7 +545,7 @@ pub extern "system" fn Java_io_unom_punktfunk_kit_NativeBridge_nativePadAudioSel #[unsafe(no_mangle)] #[cfg(target_os = "android")] pub extern "system" fn Java_io_unom_punktfunk_kit_NativeBridge_nativeStopPadAudio( - _env: JNIEnv, + _env: EnvUnowned, _this: JObject, handle: jlong, pad: jni::sys::jint, @@ -596,7 +585,7 @@ pub extern "system" fn Java_io_unom_punktfunk_kit_NativeBridge_nativeStopPadAudi /// no captured audio leaves the process. #[unsafe(no_mangle)] pub extern "system" fn Java_io_unom_punktfunk_kit_NativeBridge_nativeSetMicMuted( - _env: JNIEnv, + _env: EnvUnowned, _this: JObject, handle: jlong, muted: jboolean, @@ -606,7 +595,7 @@ pub extern "system" fn Java_io_unom_punktfunk_kit_NativeBridge_nativeSetMicMuted // SAFETY: live handle per the nativeConnect/nativeClose contract. let h = unsafe { &*(handle as *const SessionHandle) }; h.mic_muted - .store(muted != 0, std::sync::atomic::Ordering::Relaxed); + .store(muted, std::sync::atomic::Ordering::Relaxed); } }) } @@ -619,16 +608,16 @@ pub extern "system" fn Java_io_unom_punktfunk_kit_NativeBridge_nativeSetMicMuted #[cfg(target_os = "android")] #[unsafe(no_mangle)] pub extern "system" fn Java_io_unom_punktfunk_kit_NativeBridge_nativeMicActive( - _env: JNIEnv, + _env: EnvUnowned, _this: JObject, handle: jlong, ) -> jboolean { - jni_guard(0, || { + jni_guard(false, || { if handle == 0 { - return 0; + return false; } // SAFETY: live handle per the nativeConnect/nativeClose contract. let h = unsafe { &*(handle as *const SessionHandle) }; - jboolean::from(lock_recover(&h.mic).is_some()) + lock_recover(&h.mic).is_some() }) } diff --git a/clients/android/native/src/session/probe.rs b/clients/android/native/src/session/probe.rs index d3388497..428a4619 100644 --- a/clients/android/native/src/session/probe.rs +++ b/clients/android/native/src/session/probe.rs @@ -10,9 +10,10 @@ //! coroutine on the main thread the way it polls the stats HUD. use super::{jni_guard, SessionHandle}; -use jni::objects::JObject; -use jni::sys::{jboolean, jdoubleArray, jint, jlong}; -use jni::JNIEnv; +use jni::errors::LogErrorAndDefault; +use jni::objects::{JDoubleArray, JObject}; +use jni::sys::{jboolean, jint, jlong}; +use jni::EnvUnowned; /// The `DoubleArray` [`Java_io_unom_punktfunk_kit_NativeBridge_nativeProbeResult`] returns. Kept in /// one place because Kotlin indexes it positionally; see the Kotlin doc for the field order. @@ -25,25 +26,25 @@ const PROBE_RESULT_LEN: usize = 6; /// Starting a probe resets any prior measurement. `false` on a `0` handle or a closed session. #[unsafe(no_mangle)] pub extern "system" fn Java_io_unom_punktfunk_kit_NativeBridge_nativeSpeedTest( - _env: JNIEnv, + _env: EnvUnowned, _this: JObject, handle: jlong, target_kbps: jint, duration_ms: jint, ) -> jboolean { - jni_guard(0, || { + jni_guard(false, || { if handle == 0 { - return 0; + return false; } // SAFETY: live handle per the nativeConnect/nativeClose contract. let h = unsafe { &*(handle as *const SessionHandle) }; let target = target_kbps.clamp(0, i32::MAX) as u32; let duration = duration_ms.clamp(0, i32::MAX) as u32; match h.client.request_probe(target, duration) { - Ok(()) => 1, + Ok(()) => true, Err(e) => { log::warn!("speed test: could not ask the host to probe: {e:?}"); - 0 + false } } }) @@ -56,13 +57,15 @@ pub extern "system" fn Java_io_unom_punktfunk_kit_NativeBridge_nativeSpeedTest( /// `[done, throughputKbps, lossPct, hostDropPct, elapsedMs, recvBytes]`. #[unsafe(no_mangle)] pub extern "system" fn Java_io_unom_punktfunk_kit_NativeBridge_nativeProbeResult<'local>( - env: JNIEnv<'local>, + mut env: EnvUnowned<'local>, _this: JObject<'local>, handle: jlong, -) -> jdoubleArray { - jni_guard(JObject::null().into_raw(), || { +) -> JDoubleArray<'local> { + // `JDoubleArray::default()` is the null reference the old `JObject::null().into_raw()` returned, + // so Kotlin still reads `null` on every failure path. + env.with_env(|env| -> jni::errors::Result> { if handle == 0 { - return JObject::null().into_raw(); + return Ok(JDoubleArray::default()); } // SAFETY: live handle per the nativeConnect/nativeClose contract. let h = unsafe { &*(handle as *const SessionHandle) }; @@ -75,14 +78,9 @@ pub extern "system" fn Java_io_unom_punktfunk_kit_NativeBridge_nativeProbeResult f64::from(r.elapsed_ms), r.recv_bytes as f64, ]; - match env.new_double_array(PROBE_RESULT_LEN as i32) { - Ok(arr) => { - if env.set_double_array_region(&arr, 0, &values).is_err() { - return JObject::null().into_raw(); - } - arr.into_raw() - } - Err(_) => JObject::null().into_raw(), - } + let arr = env.new_double_array(PROBE_RESULT_LEN)?; + arr.set_region(env, 0, &values)?; + Ok(arr) }) + .resolve::() } diff --git a/clients/android/native/src/wol.rs b/clients/android/native/src/wol.rs index a37e95fd..e1855517 100644 --- a/clients/android/native/src/wol.rs +++ b/clients/android/native/src/wol.rs @@ -3,8 +3,9 @@ //! host has no ARP entry, so the broadcast the core sends is what wakes it, and Kotlin calls this //! just before connecting to an offline saved host. +use jni::errors::LogErrorAndDefault; use jni::objects::{JObject, JString}; -use jni::JNIEnv; +use jni::EnvUnowned; /// `NativeBridge.nativeWakeOnLan(macsCsv: String, lastIp: String): Boolean` — send a Wake-on-LAN /// magic packet. `macsCsv` is comma-separated MACs (`aa:bb:..,cc:dd:..`, learned from the host's @@ -12,29 +13,25 @@ use jni::JNIEnv; /// Returns true if at least one datagram went out. #[unsafe(no_mangle)] pub extern "system" fn Java_io_unom_punktfunk_kit_NativeBridge_nativeWakeOnLan<'local>( - mut env: JNIEnv<'local>, + mut env: EnvUnowned<'local>, _this: JObject<'local>, macs_csv: JString<'local>, last_ip: JString<'local>, ) -> jni::sys::jboolean { - let macs_csv: String = match env.get_string(&macs_csv) { - Ok(s) => s.into(), - Err(_) => return 0, - }; - let last_ip: String = env - .get_string(&last_ip) - .map(Into::::into) - .unwrap_or_default(); - let macs: Vec<[u8; 6]> = macs_csv - .split(',') - .filter_map(|s| punktfunk_core::wol::parse_mac(s.trim())) - .collect(); - if macs.is_empty() { - return 0; - } - let ip = last_ip.trim().parse::().ok(); - match punktfunk_core::wol::send_magic_packet(&macs, ip) { - Ok(()) => 1, - Err(_) => 0, - } + env.with_env(|env| -> jni::errors::Result { + let macs_csv: String = macs_csv.try_to_string(env)?; + // Unlike `macs_csv`, an unreadable `lastIp` is not fatal: the core falls back to the + // subnet broadcast when it has no address, so keep the old lenient default. + let last_ip: String = last_ip.try_to_string(env).unwrap_or_default(); + let macs: Vec<[u8; 6]> = macs_csv + .split(',') + .filter_map(|s| punktfunk_core::wol::parse_mac(s.trim())) + .collect(); + if macs.is_empty() { + return Ok(false); + } + let ip = last_ip.trim().parse::().ok(); + Ok(punktfunk_core::wol::send_magic_packet(&macs, ip).is_ok()) + }) + .resolve::() }