diff --git a/.github/workflows/checks.yaml b/.github/workflows/checks.yaml index d539874de0a982e3d650d4d74c8d88251a07105c..ba100da3888e4d506badeca4dffa0501b07d96c7 100644 --- a/.github/workflows/checks.yaml +++ b/.github/workflows/checks.yaml @@ -31,3 +31,15 @@ jobs: java-version: '17' - name: Run Gradle tests run: ./gradlew check --info --stacktrace + build-macos: + runs-on: macos-latest + steps: + - name: Checkout repository + uses: actions/checkout@v3 + - name: Setup Java + uses: actions/setup-java@v3 + with: + distribution: 'temurin' + java-version: '17' + - name: Run Gradle tests + run: ./gradlew check --info --stacktrace diff --git a/onionwrapper-core/src/main/java/org/briarproject/onionwrapper/AbstractTorWrapper.java b/onionwrapper-core/src/main/java/org/briarproject/onionwrapper/AbstractTorWrapper.java index b2dbb22f555fa8efd1a887de6c873d59fc7d3d07..3916c221a73ffbbe79bfc5f34ff0690a5b07b639 100644 --- a/onionwrapper-core/src/main/java/org/briarproject/onionwrapper/AbstractTorWrapper.java +++ b/onionwrapper-core/src/main/java/org/briarproject/onionwrapper/AbstractTorWrapper.java @@ -72,8 +72,9 @@ abstract class AbstractTorWrapper implements EventHandler, TorWrapper { protected final Executor ioExecutor; protected final Executor eventExecutor; - private final String architecture; - private final File torDirectory, configFile, doneFile, cookieFile; + protected final String architecture; + protected final File torDirectory; + private final File configFile, doneFile, cookieFile; private final int torSocksPort; private final int torControlPort; @@ -243,7 +244,7 @@ abstract class AbstractTorWrapper implements EventHandler, TorWrapper { if (!snowflakeFile.setExecutable(true, true)) throw new IOException(); } - private InputStream getExecutableInputStream(String basename) { + protected InputStream getExecutableInputStream(String basename) { String ext = getExecutableExtension(); return requireNonNull(getResourceInputStream(architecture + "/" + basename, ext)); } diff --git a/onionwrapper-core/src/main/java/org/briarproject/onionwrapper/util/OsUtils.java b/onionwrapper-core/src/main/java/org/briarproject/onionwrapper/util/OsUtils.java new file mode 100644 index 0000000000000000000000000000000000000000..67373dd98d64c998410f3a10ff1ca3aa8b27bfb6 --- /dev/null +++ b/onionwrapper-core/src/main/java/org/briarproject/onionwrapper/util/OsUtils.java @@ -0,0 +1,32 @@ +package org.briarproject.onionwrapper.util; + +import org.briarproject.nullsafety.NotNullByDefault; + +import javax.annotation.Nullable; + +import static org.briarproject.onionwrapper.util.StringUtils.startsWithIgnoreCase; + +@NotNullByDefault +public class OsUtils { + + @Nullable + private static final String os = System.getProperty("os.name"); + @Nullable + private static final String vendor = System.getProperty("java.vendor"); + + public static boolean isWindows() { + return os != null && startsWithIgnoreCase(os, "Win"); + } + + public static boolean isMac() { + return os != null && os.equalsIgnoreCase("Mac OS X"); + } + + public static boolean isLinux() { + return os != null && startsWithIgnoreCase(os, "Linux") && !isAndroid(); + } + + public static boolean isAndroid() { + return vendor != null && vendor.contains("Android"); + } +} diff --git a/onionwrapper-core/src/main/java/org/briarproject/onionwrapper/util/StringUtils.java b/onionwrapper-core/src/main/java/org/briarproject/onionwrapper/util/StringUtils.java new file mode 100644 index 0000000000000000000000000000000000000000..8964d41ac5651579eeb80de976ca5d7cd4a0b99d --- /dev/null +++ b/onionwrapper-core/src/main/java/org/briarproject/onionwrapper/util/StringUtils.java @@ -0,0 +1,12 @@ +package org.briarproject.onionwrapper.util; + +import org.briarproject.nullsafety.NotNullByDefault; + +@NotNullByDefault +public class StringUtils { + + // see https://stackoverflow.com/a/38947571 + static boolean startsWithIgnoreCase(String s, String prefix) { + return s.regionMatches(true, 0, prefix, 0, prefix.length()); + } +} diff --git a/onionwrapper-core/src/test/java/org/briarproject/onionwrapper/TestUtils.java b/onionwrapper-core/src/test/java/org/briarproject/onionwrapper/TestUtils.java index 2c6b65e1e62a466edef7e5223ce8691d7983f1a5..9c7d1de34473a8cc1a1c6392c53bd0cf7aeedd94 100644 --- a/onionwrapper-core/src/test/java/org/briarproject/onionwrapper/TestUtils.java +++ b/onionwrapper-core/src/test/java/org/briarproject/onionwrapper/TestUtils.java @@ -12,7 +12,9 @@ import javax.annotation.concurrent.ThreadSafe; import static java.util.Arrays.asList; import static java.util.logging.Level.WARNING; import static java.util.logging.Logger.getLogger; -import static org.briarproject.onionwrapper.StringUtils.startsWithIgnoreCase; +import static org.briarproject.onionwrapper.util.OsUtils.isLinux; +import static org.briarproject.onionwrapper.util.OsUtils.isMac; +import static org.briarproject.onionwrapper.util.OsUtils.isWindows; @ThreadSafe @NotNullByDefault @@ -54,29 +56,21 @@ public class TestUtils { } } - public static boolean isLinux() { - String os = System.getProperty("os.name"); - return os != null && os.contains("Linux"); - } - - public static boolean isWindows() { - String os = System.getProperty("os.name"); - return os != null && startsWithIgnoreCase(os, "Win"); - } - - public static boolean isMac() { - String os = System.getProperty("os.name"); - return os != null && os.equalsIgnoreCase("Mac OS X"); - } - @Nullable public static String getArchitectureForTorBinary() { String arch = System.getProperty("os.arch"); if (arch == null) return null; - //noinspection IfCanBeSwitch - if (arch.equals("amd64")) return "x86_64"; - else if (arch.equals("aarch64")) return "aarch64"; - else if (arch.equals("arm")) return "armhf"; + if (isLinux()) { + //noinspection IfCanBeSwitch + if (arch.equals("amd64")) return "x86_64"; + else if (arch.equals("aarch64")) return "aarch64"; + else if (arch.equals("arm")) return "armhf"; + } else if (isWindows()) { + if (arch.equals("amd64")) return "x86_64"; + } else if (isMac()) { + if (arch.equals("amd64")) return "x86_64"; + else if (arch.equals("aarch64")) return "aarch64"; + } return null; } diff --git a/onionwrapper-java/build.gradle b/onionwrapper-java/build.gradle index 913f000b25e6e8ecdefdbca470d2a10acf790042..5d742636a2c8de9c021c2ca8c4652b3b73b13132 100644 --- a/onionwrapper-java/build.gradle +++ b/onionwrapper-java/build.gradle @@ -1,5 +1,3 @@ -import static org.briarproject.onionwrapper.OS.Linux -import static org.briarproject.onionwrapper.OS.Windows import static org.briarproject.onionwrapper.OsUtils.currentOS plugins { @@ -19,17 +17,15 @@ checkstyle { dependencies { api project(':onionwrapper-core') - def jna_version = '4.5.2' + def jna_version = '5.13.0' implementation "net.java.dev.jna:jna:$jna_version" implementation "net.java.dev.jna:jna-platform:$jna_version" testImplementation project(path: ':onionwrapper-core', configuration: 'testOutput') testImplementation 'junit:junit:4.13.2' - if (currentOS == Linux || currentOS == Windows) { - testImplementation "org.briarproject:tor-$currentOS.id:0.4.7.13-2" - testImplementation "org.briarproject:obfs4proxy-$currentOS.id:0.0.14-tor2" - testImplementation "org.briarproject:snowflake-$currentOS.id:2.5.1" - } + testImplementation "org.briarproject:tor-$currentOS.id:0.4.7.13-2" + testImplementation "org.briarproject:obfs4proxy-$currentOS.id:0.0.14-tor2" + testImplementation "org.briarproject:snowflake-$currentOS.id:2.5.1" } mavenPublishing { diff --git a/onionwrapper-java/src/main/java/org/briarproject/onionwrapper/MacTorWrapper.java b/onionwrapper-java/src/main/java/org/briarproject/onionwrapper/MacTorWrapper.java new file mode 100644 index 0000000000000000000000000000000000000000..48fe817da0fa4493d658b3b9491f36d2d2ba9e23 --- /dev/null +++ b/onionwrapper-java/src/main/java/org/briarproject/onionwrapper/MacTorWrapper.java @@ -0,0 +1,68 @@ +package org.briarproject.onionwrapper; + +import org.briarproject.nullsafety.NotNullByDefault; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.util.concurrent.Executor; + +import static java.util.logging.Level.INFO; + +@NotNullByDefault +public class MacTorWrapper extends UnixTorWrapper { + + static final String LIB_EVENT_VERSION = "2.1.7"; + + /** + * @param ioExecutor The wrapper will use this executor to run IO tasks, + * some of which may run for the lifetime of the wrapper, so the executor + * should have an unlimited thread pool. + * @param eventExecutor The wrapper will use this executor to call the + * {@link Observer observer} (if any). To ensure that events are observed + * in the order they occur, this executor should have a single thread (eg + * the app's main thread). + * @param architecture The processor architecture of the Tor and pluggable + * transport binaries. + * @param torDirectory The directory where the Tor process should keep its + * state. + * @param torSocksPort The port number to use for Tor's SOCKS port. + * @param torControlPort The port number to use for Tor's control port. + */ + public MacTorWrapper(Executor ioExecutor, + Executor eventExecutor, + String architecture, + File torDirectory, + int torSocksPort, + int torControlPort) { + super(ioExecutor, eventExecutor, architecture, torDirectory, torSocksPort, torControlPort); + } + + @Override + protected void installTorExecutable() throws IOException { + super.installTorExecutable(); + installLibEvent(); + } + + private void installLibEvent() throws IOException { + if (LOG.isLoggable(INFO)) { + LOG.info("Installing libevent binary for " + architecture); + } + File libEventFile = getLibEventFile(); + extract(getExecutableInputStream("libevent-" + LIB_EVENT_VERSION + ".dylib"), + libEventFile); + } + + private File getLibEventFile() { + return new File(torDirectory, "libevent-" + LIB_EVENT_VERSION + ".dylib"); + } + + @Override + protected void extract(InputStream in, File dest) throws IOException { + // Important: delete file to prevent problems on macOS in case the file signature changed + // for binaries. + //noinspection ResultOfMethodCallIgnored + dest.delete(); + super.extract(in, dest); + } +} diff --git a/onionwrapper-java/src/main/java/org/briarproject/onionwrapper/UnixTorWrapper.java b/onionwrapper-java/src/main/java/org/briarproject/onionwrapper/UnixTorWrapper.java index 3d196f98b70d07ae31bb81628d6644121fceea8d..eaea8158d4a93892141cf84186f37ae788d2a190 100644 --- a/onionwrapper-java/src/main/java/org/briarproject/onionwrapper/UnixTorWrapper.java +++ b/onionwrapper-java/src/main/java/org/briarproject/onionwrapper/UnixTorWrapper.java @@ -46,7 +46,7 @@ public class UnixTorWrapper extends JavaTorWrapper { private interface CLibrary extends Library { - CLibrary INSTANCE = Native.loadLibrary("c", CLibrary.class); + CLibrary INSTANCE = Native.load("c", CLibrary.class); int getpid(); } diff --git a/onionwrapper-java/src/test/java/org/briarproject/onionwrapper/BootstrapTest.java b/onionwrapper-java/src/test/java/org/briarproject/onionwrapper/BootstrapTest.java index 63d56d87dd83fa4d5a5c0f2a7490d6986ce97625..98bb14658c83423ecb0fa4c54a27e50b7903de8c 100644 --- a/onionwrapper-java/src/test/java/org/briarproject/onionwrapper/BootstrapTest.java +++ b/onionwrapper-java/src/test/java/org/briarproject/onionwrapper/BootstrapTest.java @@ -15,11 +15,12 @@ import static org.briarproject.nullsafety.NullSafety.requireNonNull; import static org.briarproject.onionwrapper.TestUtils.deleteTestDirectory; import static org.briarproject.onionwrapper.TestUtils.getArchitectureForTorBinary; import static org.briarproject.onionwrapper.TestUtils.getTestDirectory; -import static org.briarproject.onionwrapper.TestUtils.isLinux; -import static org.briarproject.onionwrapper.TestUtils.isWindows; import static org.briarproject.onionwrapper.TorWrapper.TorState.CONNECTED; import static org.briarproject.onionwrapper.TorWrapper.TorState.STARTED; import static org.briarproject.onionwrapper.TorWrapper.TorState.STOPPED; +import static org.briarproject.onionwrapper.util.OsUtils.isLinux; +import static org.briarproject.onionwrapper.util.OsUtils.isMac; +import static org.briarproject.onionwrapper.util.OsUtils.isWindows; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; import static org.junit.Assume.assumeNotNull; @@ -38,7 +39,7 @@ public class BootstrapTest extends BaseTest { @Before public void setUp() { - assumeTrue(isLinux() || isWindows()); + assumeTrue(isLinux() || isWindows() || isMac()); assumeNotNull(getArchitectureForTorBinary()); } @@ -55,6 +56,9 @@ public class BootstrapTest extends BaseTest { if (isLinux()) { tor = new UnixTorWrapper(executor, executor, architecture, torDir, CONTROL_PORT, SOCKS_PORT); + } else if (isMac()) { + tor = new MacTorWrapper(executor, executor, architecture, torDir, + CONTROL_PORT, SOCKS_PORT); } else if (isWindows()) { tor = new WindowsTorWrapper(executor, executor, architecture, torDir, CONTROL_PORT, SOCKS_PORT); diff --git a/onionwrapper-java/src/test/java/org/briarproject/onionwrapper/BridgeTest.java b/onionwrapper-java/src/test/java/org/briarproject/onionwrapper/BridgeTest.java index 1c822f53e62ae7853ae2860b682deaaa03c99000..5401107e5e8f46c82cc2e9562e0db4bba43bf8b8 100644 --- a/onionwrapper-java/src/test/java/org/briarproject/onionwrapper/BridgeTest.java +++ b/onionwrapper-java/src/test/java/org/briarproject/onionwrapper/BridgeTest.java @@ -32,9 +32,9 @@ import static org.briarproject.onionwrapper.CircumventionProvider.BridgeType.VAN import static org.briarproject.onionwrapper.TestUtils.deleteTestDirectory; import static org.briarproject.onionwrapper.TestUtils.getArchitectureForTorBinary; import static org.briarproject.onionwrapper.TestUtils.getTestDirectory; -import static org.briarproject.onionwrapper.TestUtils.isLinux; import static org.briarproject.onionwrapper.TestUtils.isOptionalTestEnabled; import static org.briarproject.onionwrapper.TorWrapper.TorState.CONNECTED; +import static org.briarproject.onionwrapper.util.OsUtils.isLinux; import static org.junit.Assert.fail; import static org.junit.Assume.assumeNotNull; import static org.junit.Assume.assumeTrue; diff --git a/onionwrapper-java/src/test/java/org/briarproject/onionwrapper/ResourcesLinuxTest.java b/onionwrapper-java/src/test/java/org/briarproject/onionwrapper/ResourcesLinuxTest.java index 7ebbf50a754d7c948e64041bb33dc630f005f6fb..666f627bd3da974793117229a0feb98de208d195 100644 --- a/onionwrapper-java/src/test/java/org/briarproject/onionwrapper/ResourcesLinuxTest.java +++ b/onionwrapper-java/src/test/java/org/briarproject/onionwrapper/ResourcesLinuxTest.java @@ -3,7 +3,7 @@ package org.briarproject.onionwrapper; import org.junit.Before; import org.junit.Test; -import static org.briarproject.onionwrapper.TestUtils.isLinux; +import static org.briarproject.onionwrapper.util.OsUtils.isLinux; import static org.junit.Assert.assertNotNull; import static org.junit.Assume.assumeTrue; diff --git a/onionwrapper-java/src/test/java/org/briarproject/onionwrapper/ResourcesMacTest.java b/onionwrapper-java/src/test/java/org/briarproject/onionwrapper/ResourcesMacTest.java new file mode 100644 index 0000000000000000000000000000000000000000..213f2f62020e71fa57da523b6e37e97a3f3c4d84 --- /dev/null +++ b/onionwrapper-java/src/test/java/org/briarproject/onionwrapper/ResourcesMacTest.java @@ -0,0 +1,43 @@ +package org.briarproject.onionwrapper; + +import org.junit.Before; +import org.junit.Test; + +import static org.briarproject.onionwrapper.MacTorWrapper.LIB_EVENT_VERSION; +import static org.briarproject.onionwrapper.util.OsUtils.isMac; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assume.assumeTrue; + +public class ResourcesMacTest { + + @Before + public void setUp() { + assumeTrue(isMac()); + } + + @Test + public void testCanLoadTor() { + testCanLoadResource("x86_64/tor"); + } + + @Test + public void testCanLoadLibEvent() { + testCanLoadResource("x86_64/libevent-" + LIB_EVENT_VERSION + ".dylib"); + } + + @Test + public void testCanLoadObfs4() { + testCanLoadResource("x86_64/obfs4proxy"); + } + + @Test + public void testCanLoadSnowflake() { + testCanLoadResource("x86_64/snowflake"); + } + + private void testCanLoadResource(String name) { + ClassLoader classLoader = + Thread.currentThread().getContextClassLoader(); + assertNotNull(classLoader.getResourceAsStream(name)); + } +} diff --git a/onionwrapper-java/src/test/java/org/briarproject/onionwrapper/ResourcesWindowsTest.java b/onionwrapper-java/src/test/java/org/briarproject/onionwrapper/ResourcesWindowsTest.java index 01a013786170089f6af09618edeb3e3c63c38fa6..51c8ddfac2c66035f88679196fec79b9d5439641 100644 --- a/onionwrapper-java/src/test/java/org/briarproject/onionwrapper/ResourcesWindowsTest.java +++ b/onionwrapper-java/src/test/java/org/briarproject/onionwrapper/ResourcesWindowsTest.java @@ -3,7 +3,7 @@ package org.briarproject.onionwrapper; import org.junit.Before; import org.junit.Test; -import static org.briarproject.onionwrapper.TestUtils.isWindows; +import static org.briarproject.onionwrapper.util.OsUtils.isWindows; import static org.junit.Assert.assertNotNull; import static org.junit.Assume.assumeTrue;