diff --git a/Cargo.lock b/Cargo.lock
index e3931af71..45f2a3f1a 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -2007,6 +2007,25 @@ dependencies = [
"syn-mid",
]
+[[package]]
+name = "libsignal-bridge-testing"
+version = "0.1.0"
+dependencies = [
+ "bytemuck",
+ "futures-util",
+ "jni 0.21.1",
+ "libsignal-bridge-macros",
+ "libsignal-bridge-types",
+ "libsignal-message-backup",
+ "linkme",
+ "neon",
+ "paste",
+ "scopeguard",
+ "signal-neon-futures",
+ "strum",
+ "thiserror",
+]
+
[[package]]
name = "libsignal-bridge-types"
version = "0.1.0"
@@ -2108,6 +2127,19 @@ dependencies = [
"signal-crypto",
]
+[[package]]
+name = "libsignal-jni-testing"
+version = "0.52.1"
+dependencies = [
+ "async-trait",
+ "cfg-if",
+ "jni 0.21.1",
+ "libsignal-bridge-testing",
+ "log",
+ "log-panics",
+ "rand",
+]
+
[[package]]
name = "libsignal-message-backup"
version = "0.1.0"
diff --git a/Cargo.toml b/Cargo.toml
index 023dea88e..34870366a 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -16,6 +16,7 @@ members = [
"rust/zkgroup",
"rust/bridge/ffi",
"rust/bridge/jni",
+ "rust/bridge/jni/testing",
"rust/bridge/node",
]
default-members = [
diff --git a/acknowledgments/acknowledgments.html b/acknowledgments/acknowledgments.html
index f4f1208d2..2128840ec 100644
--- a/acknowledgments/acknowledgments.html
+++ b/acknowledgments/acknowledgments.html
@@ -47,7 +47,7 @@
Third Party Licenses
Overview of licenses:
- MIT License (315)
- - GNU Affero General Public License v3.0 (25)
+ - GNU Affero General Public License v3.0 (27)
- Apache License 2.0 (12)
- BSD 3-Clause "New" or "Revised" License (8)
- ISC License (6)
@@ -736,11 +736,13 @@ Used by:
- attest 0.1.0
- libsignal-ffi 0.52.1
- libsignal-jni 0.52.1
+ - libsignal-jni-testing 0.52.1
- libsignal-node 0.52.1
- signal-neon-futures 0.1.0
- signal-neon-futures-tests 0.1.0
- libsignal-bridge 0.1.0
- libsignal-bridge-macros 0.1.0
+ - libsignal-bridge-testing 0.1.0
- libsignal-bridge-types 0.1.0
- libsignal-core 0.1.0
- signal-crypto 0.1.0
diff --git a/acknowledgments/acknowledgments.md b/acknowledgments/acknowledgments.md
index 3aed74613..314431f6c 100644
--- a/acknowledgments/acknowledgments.md
+++ b/acknowledgments/acknowledgments.md
@@ -669,7 +669,7 @@ For more information on this, and how to apply and follow the GNU AGPL, see
```
-## attest 0.1.0, libsignal-ffi 0.52.1, libsignal-jni 0.52.1, libsignal-node 0.52.1, signal-neon-futures 0.1.0, signal-neon-futures-tests 0.1.0, libsignal-bridge 0.1.0, libsignal-bridge-macros 0.1.0, libsignal-bridge-types 0.1.0, libsignal-core 0.1.0, signal-crypto 0.1.0, device-transfer 0.1.0, signal-media 0.1.0, libsignal-message-backup 0.1.0, libsignal-message-backup-macros 0.1.0, libsignal-net 0.1.0, signal-pin 0.1.0, poksho 0.7.0, libsignal-protocol 0.1.0, libsignal-svr3 0.1.0, usernames 0.1.0, zkcredential 0.1.0, zkgroup 0.9.0
+## attest 0.1.0, libsignal-ffi 0.52.1, libsignal-jni 0.52.1, libsignal-jni-testing 0.52.1, libsignal-node 0.52.1, signal-neon-futures 0.1.0, signal-neon-futures-tests 0.1.0, libsignal-bridge 0.1.0, libsignal-bridge-macros 0.1.0, libsignal-bridge-testing 0.1.0, libsignal-bridge-types 0.1.0, libsignal-core 0.1.0, signal-crypto 0.1.0, device-transfer 0.1.0, signal-media 0.1.0, libsignal-message-backup 0.1.0, libsignal-message-backup-macros 0.1.0, libsignal-net 0.1.0, signal-pin 0.1.0, poksho 0.7.0, libsignal-protocol 0.1.0, libsignal-svr3 0.1.0, usernames 0.1.0, zkcredential 0.1.0, zkgroup 0.9.0
```
GNU AFFERO GENERAL PUBLIC LICENSE
diff --git a/acknowledgments/acknowledgments.plist b/acknowledgments/acknowledgments.plist
index acc045c1c..0d4105bfe 100644
--- a/acknowledgments/acknowledgments.plist
+++ b/acknowledgments/acknowledgments.plist
@@ -924,7 +924,7 @@ You should also get your employer (if you work as a programmer) or school, if an
License
GNU Affero General Public License v3.0
Title
- attest 0.1.0, libsignal-ffi 0.52.1, libsignal-jni 0.52.1, libsignal-node 0.52.1, signal-neon-futures 0.1.0, signal-neon-futures-tests 0.1.0, libsignal-bridge 0.1.0, libsignal-bridge-macros 0.1.0, libsignal-bridge-types 0.1.0, libsignal-core 0.1.0, signal-crypto 0.1.0, device-transfer 0.1.0, signal-media 0.1.0, libsignal-message-backup 0.1.0, libsignal-message-backup-macros 0.1.0, libsignal-net 0.1.0, signal-pin 0.1.0, poksho 0.7.0, libsignal-protocol 0.1.0, libsignal-svr3 0.1.0, usernames 0.1.0, zkcredential 0.1.0, zkgroup 0.9.0
+ attest 0.1.0, libsignal-ffi 0.52.1, libsignal-jni 0.52.1, libsignal-jni-testing 0.52.1, libsignal-node 0.52.1, signal-neon-futures 0.1.0, signal-neon-futures-tests 0.1.0, libsignal-bridge 0.1.0, libsignal-bridge-macros 0.1.0, libsignal-bridge-testing 0.1.0, libsignal-bridge-types 0.1.0, libsignal-core 0.1.0, signal-crypto 0.1.0, device-transfer 0.1.0, signal-media 0.1.0, libsignal-message-backup 0.1.0, libsignal-message-backup-macros 0.1.0, libsignal-net 0.1.0, signal-pin 0.1.0, poksho 0.7.0, libsignal-protocol 0.1.0, libsignal-svr3 0.1.0, usernames 0.1.0, zkcredential 0.1.0, zkgroup 0.9.0
Type
PSGroupSpecifier
diff --git a/bin/update_versions.py b/bin/update_versions.py
index aea6e2ddf..66b99c9c3 100755
--- a/bin/update_versions.py
+++ b/bin/update_versions.py
@@ -36,8 +36,8 @@ def update_version(file, pattern, new_version):
RUST_PATTERN = re.compile(r'^(pub const VERSION: &str = ")(.*)(")')
-def bridge_path(bridge):
- return os.path.join('rust', 'bridge', bridge, 'Cargo.toml')
+def bridge_path(*bridge):
+ return os.path.join('rust', 'bridge', *bridge, 'Cargo.toml')
VERSION_FILES = [
@@ -47,6 +47,7 @@ def bridge_path(bridge):
(os.path.join('rust', 'core', 'src', 'version.rs'), RUST_PATTERN),
(bridge_path('ffi'), CARGO_PATTERN),
(bridge_path('jni'), CARGO_PATTERN),
+ (bridge_path('jni', 'testing'), CARGO_PATTERN),
(bridge_path('node'), CARGO_PATTERN),
]
diff --git a/java/android/build.gradle b/java/android/build.gradle
index 767d4e9c1..edba9df79 100644
--- a/java/android/build.gradle
+++ b/java/android/build.gradle
@@ -52,6 +52,7 @@ android {
packagingOptions {
jniLibs {
pickFirst 'lib/*/libsignal_jni.so'
+ pickFirst 'lib/*/libsignal_jni_testing.so'
}
}
diff --git a/java/build.gradle b/java/build.gradle
index 3b7427f84..c864821b5 100644
--- a/java/build.gradle
+++ b/java/build.gradle
@@ -35,7 +35,7 @@ subprojects {
spotless {
java {
target('**/*.java')
- targetExclude('**/Native.java')
+ targetExclude('**/Native.java', '**/NativeTesting.java')
importOrder()
removeUnusedImports()
@@ -76,7 +76,7 @@ clean.dependsOn([cargoClean, cleanJni])
// PUBLISHING
-ext.setUpSigningKey = { signingExt ->
+ext.setUpSigningKey = { signingExt ->
def signingKeyId = findProperty("signingKeyId")
def signingKey = findProperty("signingKey")
def signingPassword = findProperty("signingPassword")
diff --git a/java/build_jni.sh b/java/build_jni.sh
index eb3bfe8b9..00b497be9 100755
--- a/java/build_jni.sh
+++ b/java/build_jni.sh
@@ -53,9 +53,10 @@ while [ "${1:-}" != "" ]; do
# https://github.com/rust-lang/rfcs/issues/2771
export CARGO_PROFILE_RELEASE_LTO=thin
FEATURES+=("testing-fns")
- echo_then_run cargo build -p libsignal-jni --release --features "${FEATURES[*]}"
+ echo_then_run cargo build -p libsignal-jni -p libsignal-jni-testing --release ${FEATURES:+--features "${FEATURES[*]}"}
if [[ -z "${CARGO_BUILD_TARGET:-}" ]]; then
copy_built_library target/release signal_jni "${DESKTOP_LIB_DIR}/"
+ copy_built_library target/release signal_jni_testing "${DESKTOP_LIB_DIR}/"
fi
exit
;;
@@ -136,5 +137,5 @@ target_for_abi() {
for abi in "${android_abis[@]}"; do
rust_target=$(target_for_abi "$abi")
- echo_then_run cargo build -p libsignal-jni --release ${FEATURES:+--features "${FEATURES[*]}"} -Z unstable-options --target "$rust_target" --out-dir "${ANDROID_LIB_DIR}/$abi"
+ echo_then_run cargo build -p libsignal-jni -p libsignal-jni-testing --release ${FEATURES:+--features "${FEATURES[*]}"} -Z unstable-options --target "$rust_target" --out-dir "${ANDROID_LIB_DIR}/$abi"
done
diff --git a/java/shared/java/org/signal/libsignal/internal/Native.java b/java/shared/java/org/signal/libsignal/internal/Native.java
index 67f62aa41..64aecdb05 100644
--- a/java/shared/java/org/signal/libsignal/internal/Native.java
+++ b/java/shared/java/org/signal/libsignal/internal/Native.java
@@ -24,13 +24,23 @@
import java.io.OutputStream;
import java.nio.ByteBuffer;
import java.nio.file.Files;
+import java.nio.file.Path;
import java.util.concurrent.Future;
import java.util.UUID;
import java.util.Map;
public final class Native {
- private static void copyToTempFileAndLoad(InputStream in, String name) throws IOException {
- File tempFile = Files.createTempFile(null, name).toFile();
+ private static Path tempDir;
+
+ private static void copyToTempDirAndLoad(InputStream in, String name) throws IOException {
+ // This isn't thread-safe but that's okay because it's only ever called from
+ // static initializers, which are themselves thread-safe.
+ if (tempDir == null) {
+ tempDir = Files.createTempDirectory("libsignal");
+ tempDir.toFile().deleteOnExit();
+ }
+
+ File tempFile = Files.createFile(tempDir.resolve(name)).toFile();
tempFile.deleteOnExit();
try (OutputStream out = new FileOutputStream(tempFile)) {
@@ -44,28 +54,35 @@ private static void copyToTempFileAndLoad(InputStream in, String name) throws IO
System.load(tempFile.getAbsolutePath());
}
- /*
- If libsignal_jni is embedded within this jar as a resource file, attempt
- to copy it to a temporary file and then load it. This allows the jar to be
- used even without a shared library existing on the filesystem.
- */
- private static void loadLibrary() {
- try {
- String libraryName = System.mapLibraryName("signal_jni");
- try (InputStream in = Native.class.getResourceAsStream("/" + libraryName)) {
- if (in != null) {
- copyToTempFileAndLoad(in, libraryName);
- } else {
- System.loadLibrary("signal_jni");
- }
+ /**
+ * If the library is embedded within this jar as a resource file, attempt to
+ * copy it to a temporary file and then load it. This allows the jar to be
+ * used even without a shared library existing on the filesystem.
+ *
+ * Package-private to allow the NativeTest class to load its shared library.
+ * This method should only be called from a static initializer.
+ */
+ static void loadLibrary(String name) throws IOException {
+ String libraryName = System.mapLibraryName(name);
+ try (InputStream in = Native.class.getResourceAsStream("/" + libraryName)) {
+ if (in != null) {
+ copyToTempDirAndLoad(in, libraryName);
+ } else {
+ System.loadLibrary(name);
}
+ }
+ }
+
+ private static void loadNativeCode() {
+ try {
+ loadLibrary("signal_jni");
} catch (Exception e) {
throw new RuntimeException(e);
}
}
static {
- loadLibrary();
+ loadNativeCode();
initializeLibrary();
}
diff --git a/java/shared/java/org/signal/libsignal/internal/NativeTesting.java b/java/shared/java/org/signal/libsignal/internal/NativeTesting.java
new file mode 100644
index 000000000..ad6d64610
--- /dev/null
+++ b/java/shared/java/org/signal/libsignal/internal/NativeTesting.java
@@ -0,0 +1,48 @@
+//
+// Copyright (C) 2024 Signal Messenger, LLC.
+// SPDX-License-Identifier: AGPL-3.0-only
+//
+
+// WARNING: this file was automatically generated
+
+package org.signal.libsignal.internal;
+
+import org.signal.libsignal.protocol.message.CiphertextMessage;
+import org.signal.libsignal.protocol.state.IdentityKeyStore;
+import org.signal.libsignal.protocol.state.SessionStore;
+import org.signal.libsignal.protocol.state.PreKeyStore;
+import org.signal.libsignal.protocol.state.SignedPreKeyStore;
+import org.signal.libsignal.protocol.state.KyberPreKeyStore;
+import org.signal.libsignal.protocol.groups.state.SenderKeyStore;
+import org.signal.libsignal.protocol.logging.Log;
+import org.signal.libsignal.protocol.logging.SignalProtocolLogger;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.nio.ByteBuffer;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.concurrent.Future;
+import java.util.UUID;
+import java.util.Map;
+
+public final class NativeTesting {
+ private static void loadNativeCode() {
+ try {
+ Native.loadLibrary("signal_jni_testing");
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ static {
+ loadNativeCode();
+ }
+
+ private NativeTesting() {}
+
+ public static native int test_only_fn_returns_123();
+}
diff --git a/java/shared/test/java/org/signal/libsignal/internal/NativeTestingTest.java b/java/shared/test/java/org/signal/libsignal/internal/NativeTestingTest.java
new file mode 100644
index 000000000..7f0764703
--- /dev/null
+++ b/java/shared/test/java/org/signal/libsignal/internal/NativeTestingTest.java
@@ -0,0 +1,18 @@
+//
+// Copyright 2024 Signal Messenger, LLC.
+// SPDX-License-Identifier: AGPL-3.0-only
+//
+
+package org.signal.libsignal.internal;
+
+import static org.junit.Assert.*;
+
+import org.junit.Test;
+
+public class NativeTestingTest {
+ @Test
+ public void canCallNativeTestingFns() throws Exception {
+ int result = NativeTesting.test_only_fn_returns_123();
+ assertEquals(123, result);
+ }
+}
diff --git a/rust/bridge/jni/bin/Native.java.in b/rust/bridge/jni/bin/Native.java.in
index fd5477a0f..b9a4ba678 100644
--- a/rust/bridge/jni/bin/Native.java.in
+++ b/rust/bridge/jni/bin/Native.java.in
@@ -24,13 +24,23 @@ import java.io.InputStream;
import java.io.OutputStream;
import java.nio.ByteBuffer;
import java.nio.file.Files;
+import java.nio.file.Path;
import java.util.concurrent.Future;
import java.util.UUID;
import java.util.Map;
public final class Native {
- private static void copyToTempFileAndLoad(InputStream in, String name) throws IOException {
- File tempFile = Files.createTempFile(null, name).toFile();
+ private static Path tempDir;
+
+ private static void copyToTempDirAndLoad(InputStream in, String name) throws IOException {
+ // This isn't thread-safe but that's okay because it's only ever called from
+ // static initializers, which are themselves thread-safe.
+ if (tempDir == null) {
+ tempDir = Files.createTempDirectory("libsignal");
+ tempDir.toFile().deleteOnExit();
+ }
+
+ File tempFile = Files.createFile(tempDir.resolve(name)).toFile();
tempFile.deleteOnExit();
try (OutputStream out = new FileOutputStream(tempFile)) {
@@ -44,28 +54,35 @@ public final class Native {
System.load(tempFile.getAbsolutePath());
}
- /*
- If libsignal_jni is embedded within this jar as a resource file, attempt
- to copy it to a temporary file and then load it. This allows the jar to be
- used even without a shared library existing on the filesystem.
- */
- private static void loadLibrary() {
- try {
- String libraryName = System.mapLibraryName("signal_jni");
- try (InputStream in = Native.class.getResourceAsStream("/" + libraryName)) {
- if (in != null) {
- copyToTempFileAndLoad(in, libraryName);
- } else {
- System.loadLibrary("signal_jni");
- }
+ /**
+ * If the library is embedded within this jar as a resource file, attempt to
+ * copy it to a temporary file and then load it. This allows the jar to be
+ * used even without a shared library existing on the filesystem.
+ *
+ * Package-private to allow the NativeTest class to load its shared library.
+ * This method should only be called from a static initializer.
+ */
+ static void loadLibrary(String name) throws IOException {
+ String libraryName = System.mapLibraryName(name);
+ try (InputStream in = Native.class.getResourceAsStream("/" + libraryName)) {
+ if (in != null) {
+ copyToTempDirAndLoad(in, libraryName);
+ } else {
+ System.loadLibrary(name);
}
+ }
+ }
+
+ private static void loadNativeCode() {
+ try {
+ loadLibrary("signal_jni");
} catch (Exception e) {
throw new RuntimeException(e);
}
}
static {
- loadLibrary();
+ loadNativeCode();
initializeLibrary();
}
diff --git a/rust/bridge/jni/bin/NativeTesting.java.in b/rust/bridge/jni/bin/NativeTesting.java.in
new file mode 100644
index 000000000..c39461cc9
--- /dev/null
+++ b/rust/bridge/jni/bin/NativeTesting.java.in
@@ -0,0 +1,48 @@
+//
+// Copyright (C) 2024 Signal Messenger, LLC.
+// SPDX-License-Identifier: AGPL-3.0-only
+//
+
+// WARNING: this file was automatically generated
+
+package org.signal.libsignal.internal;
+
+import org.signal.libsignal.protocol.message.CiphertextMessage;
+import org.signal.libsignal.protocol.state.IdentityKeyStore;
+import org.signal.libsignal.protocol.state.SessionStore;
+import org.signal.libsignal.protocol.state.PreKeyStore;
+import org.signal.libsignal.protocol.state.SignedPreKeyStore;
+import org.signal.libsignal.protocol.state.KyberPreKeyStore;
+import org.signal.libsignal.protocol.groups.state.SenderKeyStore;
+import org.signal.libsignal.protocol.logging.Log;
+import org.signal.libsignal.protocol.logging.SignalProtocolLogger;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.nio.ByteBuffer;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.concurrent.Future;
+import java.util.UUID;
+import java.util.Map;
+
+public final class NativeTesting {
+ private static void loadNativeCode() {
+ try {
+ Native.loadLibrary("signal_jni_testing");
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ static {
+ loadNativeCode();
+ }
+
+ private NativeTesting() {}
+
+ // INSERT DECLS HERE
+}
diff --git a/rust/bridge/jni/bin/gen_java_decl.py b/rust/bridge/jni/bin/gen_java_decl.py
index 7631601a1..51e59a890 100755
--- a/rust/bridge/jni/bin/gen_java_decl.py
+++ b/rust/bridge/jni/bin/gen_java_decl.py
@@ -128,12 +128,12 @@ def translate_to_java(typ):
JAVA_DECL = re.compile(r"""
- ([a-zA-Z0-9]+(?:<.+>)?)[ ] # (0) A possibly-generic return type
- Java_org_signal_libsignal_internal_Native_ # The required JNI prefix
- (([a-zA-Z0-9]+) # (1) The method name, with (2) a grouping prefix
- (?:_1[a-zA-Z0-9_]*)?) # ...possibly followed by an underscore and then more name
- \(JNIEnv[ ].?env,[ ]JClass[ ]class_ # and then the required JNI args,
- (,[ ].*)?\); # then (3) actual args
+ ([a-zA-Z0-9]+(?:<.+>)?)[ ] # (0) A possibly-generic return type
+ Java_org_signal_libsignal_internal_Native(?:Testing)?_ # The required JNI prefix
+ (([a-zA-Z0-9]+) # (1) The method name, with (2) a grouping prefix
+ (?:_1[a-zA-Z0-9_]*)?) # ...possibly followed by an underscore and then more name
+ \(JNIEnv[ ].?env,[ ]JClass[ ]class_ # and then the required JNI args,
+ (,[ ].*)?\); # then (3) actual args
""", re.VERBOSE)
@@ -217,6 +217,13 @@ def main():
verify=args.verify,
)
+ convert_to_java(
+ rust_crate_dir=os.path.join(our_abs_dir, '..', 'testing'),
+ java_in_path=os.path.join(our_abs_dir, 'NativeTesting.java.in'),
+ java_out_path=os.path.join(our_abs_dir, '..', '..', '..', '..', 'java', 'shared', 'java', 'org', 'signal', 'libsignal', 'internal', 'NativeTesting.java'),
+ verify=args.verify,
+ )
+
if __name__ == "__main__":
main()
diff --git a/rust/bridge/jni/testing/Cargo.toml b/rust/bridge/jni/testing/Cargo.toml
new file mode 100644
index 000000000..e38c3dfac
--- /dev/null
+++ b/rust/bridge/jni/testing/Cargo.toml
@@ -0,0 +1,25 @@
+#
+# Copyright (C) 2024 Signal Messenger, LLC.
+# SPDX-License-Identifier: AGPL-3.0-only
+#
+
+[package]
+name = "libsignal-jni-testing"
+version = "0.52.1"
+authors = ["Signal Messenger LLC"]
+edition = "2021"
+license = "AGPL-3.0-only"
+
+[lib]
+name = "signal_jni_testing"
+crate-type = ["cdylib"]
+
+[dependencies]
+libsignal-bridge-testing = { path = "../../shared/testing", features = ["jni"] }
+
+async-trait = "0.1.41"
+cfg-if = "1.0.0"
+jni = "0.21.0"
+log = { version = "0.4", features = ["release_max_level_info"] }
+log-panics = { version = "2.1.0", features = ["with-backtrace"] }
+rand = "0.8"
diff --git a/rust/bridge/jni/testing/cbindgen.toml b/rust/bridge/jni/testing/cbindgen.toml
new file mode 100644
index 000000000..479015b83
--- /dev/null
+++ b/rust/bridge/jni/testing/cbindgen.toml
@@ -0,0 +1,28 @@
+#
+# Copyright (C) 2024 Signal Messenger, LLC.
+# SPDX-License-Identifier: AGPL-3.0-only
+#
+
+language = "C"
+no_includes = true
+documentation = false
+
+[export]
+item_types = ["functions"]
+
+[export.rename]
+"JavaSyncInputStream" = "JavaInputStream"
+
+[fn]
+args = "horizontal"
+sort_by = "Name"
+rename_args = "camelCase"
+
+[parse]
+parse_deps = true
+include = []
+extra_bindings = ["libsignal-bridge", "libsignal-bridge-testing"]
+
+[parse.expand]
+crates = ["libsignal-jni-testing", "libsignal-bridge-testing"]
+features = ["libsignal-bridge-testing/jni"]
diff --git a/rust/bridge/jni/testing/src/lib.rs b/rust/bridge/jni/testing/src/lib.rs
new file mode 100644
index 000000000..04b295408
--- /dev/null
+++ b/rust/bridge/jni/testing/src/lib.rs
@@ -0,0 +1,9 @@
+//
+// Copyright 2024 Signal Messenger, LLC.
+// SPDX-License-Identifier: AGPL-3.0-only
+//
+
+// Import bridged functions. Without this, the compiler and/or linker are too
+// smart and don't include the symbols in the library.
+#[allow(unused_imports)]
+use libsignal_bridge_testing::*;
diff --git a/rust/bridge/shared/macros/src/ffi.rs b/rust/bridge/shared/macros/src/ffi.rs
index e61be1685..b1fad5249 100644
--- a/rust/bridge/shared/macros/src/ffi.rs
+++ b/rust/bridge/shared/macros/src/ffi.rs
@@ -90,6 +90,7 @@ fn bridge_fn_body(
// "Support" async operations by requiring them to complete synchronously.
let await_if_needed = sig.asyncness.map(|_| {
quote! {
+ use ::futures_util::future::FutureExt as _;
let __result = __result.now_or_never().unwrap();
}
});
diff --git a/rust/bridge/shared/macros/src/jni.rs b/rust/bridge/shared/macros/src/jni.rs
index 45d1f657f..d2b15a63d 100644
--- a/rust/bridge/shared/macros/src/jni.rs
+++ b/rust/bridge/shared/macros/src/jni.rs
@@ -94,6 +94,8 @@ fn bridge_fn_body(
let await_if_needed = await_needed.then(|| {
quote! {
+ #[allow(unused)]
+ use ::futures_util::future::FutureExt as _;
let __result = __result.now_or_never().unwrap();
}
});
diff --git a/rust/bridge/shared/src/message_backup.rs b/rust/bridge/shared/src/message_backup.rs
index 2d82787f8..89f243502 100644
--- a/rust/bridge/shared/src/message_backup.rs
+++ b/rust/bridge/shared/src/message_backup.rs
@@ -3,8 +3,6 @@
// SPDX-License-Identifier: AGPL-3.0-only
//
-#[cfg(any(feature = "jni", feature = "ffi"))]
-use futures_util::FutureExt as _;
use libsignal_bridge_macros::*;
use libsignal_bridge_types::message_backup::*;
use libsignal_message_backup::backup::Purpose;
diff --git a/rust/bridge/shared/testing/Cargo.toml b/rust/bridge/shared/testing/Cargo.toml
new file mode 100644
index 000000000..5a56ef2b1
--- /dev/null
+++ b/rust/bridge/shared/testing/Cargo.toml
@@ -0,0 +1,34 @@
+#
+# Copyright (C) 2024 Signal Messenger, LLC.
+# SPDX-License-Identifier: AGPL-3.0-only
+#
+
+[package]
+name = "libsignal-bridge-testing"
+version = "0.1.0"
+authors = ["Signal Messenger LLC"]
+edition = "2021"
+license = "AGPL-3.0-only"
+
+[dependencies]
+libsignal-bridge-macros = { path = "../macros" }
+libsignal-bridge-types = { path = "../types" }
+libsignal-message-backup = { path = "../../../message-backup" }
+
+futures-util = "0.3.7"
+paste = "1.0"
+scopeguard = "1.0"
+thiserror = "1.0.50"
+
+bytemuck = { version = "1.13.0", optional = true }
+jni = { version = "0.21", package = "jni", optional = true }
+linkme = { version = "0.3.9", optional = true }
+neon = { version = "1.0.0", optional = true, default-features = false, features = ["napi-6"] }
+signal-neon-futures = { path = "../../node/futures", optional = true }
+strum = { version = "0.26", features = ["derive"] }
+
+[features]
+ffi = ["libsignal-bridge-types/ffi"]
+jni = ["dep:jni", "libsignal-bridge-types/jni"]
+node = ["libsignal-bridge-types/node"]
+signal-media = ["libsignal-bridge-types/signal-media"]
diff --git a/rust/bridge/shared/testing/build.rs b/rust/bridge/shared/testing/build.rs
new file mode 100644
index 000000000..2796d812c
--- /dev/null
+++ b/rust/bridge/shared/testing/build.rs
@@ -0,0 +1,14 @@
+//
+// Copyright 2024 Signal Messenger, LLC.
+// SPDX-License-Identifier: AGPL-3.0-only
+//
+
+fn main() {
+ // Set environment variables for bridge_fn to produce correctly-named symbols for FFI and JNI.
+ println!("cargo:rustc-env=LIBSIGNAL_BRIDGE_FN_PREFIX_FFI=signal_");
+ // This naming convention comes from JNI:
+ // https://docs.oracle.com/en/java/javase/20/docs/specs/jni/design.html#resolving-native-method-names
+ println!(
+ "cargo:rustc-env=LIBSIGNAL_BRIDGE_FN_PREFIX_JNI=Java_org_signal_libsignal_internal_NativeTesting_"
+ );
+}
diff --git a/rust/bridge/shared/testing/src/lib.rs b/rust/bridge/shared/testing/src/lib.rs
new file mode 100644
index 000000000..7c9ead858
--- /dev/null
+++ b/rust/bridge/shared/testing/src/lib.rs
@@ -0,0 +1,16 @@
+//
+// Copyright 2024 Signal Messenger, LLC.
+// SPDX-License-Identifier: AGPL-3.0-only
+//
+
+#[cfg(not(any(feature = "ffi", feature = "jni", feature = "node")))]
+compile_error!("Feature \"ffi\", \"jni\", or \"node\" must be enabled for this crate.");
+
+use libsignal_bridge_macros::bridge_fn;
+use libsignal_bridge_types::support::*;
+use libsignal_bridge_types::*;
+
+#[bridge_fn]
+pub fn test_only_fn_returns_123() -> u32 {
+ 123
+}