From 07c107173bea3a07f906805e6fae379b8a1addc3 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Sebastian=20K=C3=BCrten?= <sebastian@mobanisto.de>
Date: Fri, 14 Apr 2023 13:30:32 +0200
Subject: [PATCH] Add support for macOS

---
 .github/workflows/checks.yaml                 | 12 ++++++
 .../onionwrapper/AbstractTorWrapper.java      | 16 +++++++
 .../onionwrapper/util/OsUtils.java            | 32 ++++++++++++++
 .../onionwrapper/util/StringUtils.java        | 12 ++++++
 .../briarproject/onionwrapper/TestUtils.java  | 31 +++++---------
 onionwrapper-java/build.gradle                |  9 ++--
 .../onionwrapper/UnixTorWrapper.java          |  2 +-
 .../onionwrapper/BootstrapTest.java           |  9 ++--
 .../briarproject/onionwrapper/BridgeTest.java |  2 +-
 .../onionwrapper/ResourcesLinuxTest.java      |  2 +-
 .../onionwrapper/ResourcesMacTest.java        | 42 +++++++++++++++++++
 .../onionwrapper/ResourcesWindowsTest.java    |  2 +-
 settings.gradle                               |  1 +
 13 files changed, 141 insertions(+), 31 deletions(-)
 create mode 100644 onionwrapper-core/src/main/java/org/briarproject/onionwrapper/util/OsUtils.java
 create mode 100644 onionwrapper-core/src/main/java/org/briarproject/onionwrapper/util/StringUtils.java
 create mode 100644 onionwrapper-java/src/test/java/org/briarproject/onionwrapper/ResourcesMacTest.java

diff --git a/.github/workflows/checks.yaml b/.github/workflows/checks.yaml
index d539874..ba100da 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 b2dbb22..9973338 100644
--- a/onionwrapper-core/src/main/java/org/briarproject/onionwrapper/AbstractTorWrapper.java
+++ b/onionwrapper-core/src/main/java/org/briarproject/onionwrapper/AbstractTorWrapper.java
@@ -50,6 +50,7 @@ import static org.briarproject.onionwrapper.TorWrapper.TorState.STARTED;
 import static org.briarproject.onionwrapper.TorWrapper.TorState.STARTING;
 import static org.briarproject.onionwrapper.TorWrapper.TorState.STOPPED;
 import static org.briarproject.onionwrapper.TorWrapper.TorState.STOPPING;
+import static org.briarproject.onionwrapper.util.OsUtils.isMac;
 
 @InterfaceNotNullByDefault
 abstract class AbstractTorWrapper implements EventHandler, TorWrapper {
@@ -110,6 +111,10 @@ abstract class AbstractTorWrapper implements EventHandler, TorWrapper {
 		return new File(torDirectory, "tor");
 	}
 
+	protected File getLibEventFile() {
+		return new File(torDirectory, "libevent-2.1.7.dylib");
+	}
+
 	@Override
 	public File getObfs4ExecutableFile() {
 		return new File(torDirectory, "obfs4proxy");
@@ -202,6 +207,9 @@ abstract class AbstractTorWrapper implements EventHandler, TorWrapper {
 		//noinspection ResultOfMethodCallIgnored
 		doneFile.delete();
 		installTorExecutable();
+		if (isMac()) {
+			installLibEvent();
+		}
 		installObfs4Executable();
 		installSnowflakeExecutable();
 		extract(getConfigInputStream(), configFile);
@@ -225,6 +233,14 @@ abstract class AbstractTorWrapper implements EventHandler, TorWrapper {
 		if (!torFile.setExecutable(true, true)) throw new IOException();
 	}
 
+	protected void installLibEvent() throws IOException {
+		if (LOG.isLoggable(INFO)) {
+			LOG.info("Installing libevent binary for " + architecture);
+		}
+		File libEventFile = getLibEventFile();
+		extract(getExecutableInputStream("libevent-2.1.7.dylib"), libEventFile);
+	}
+
 	protected void installObfs4Executable() throws IOException {
 		if (LOG.isLoggable(INFO)) {
 			LOG.info("Installing obfs4proxy binary for " + architecture);
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 0000000..67373dd
--- /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 0000000..8964d41
--- /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 2c6b65e..82ad9bd 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,18 @@ 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() || isWindows()) {
+			//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 (isMac()) {
+			return "any";
+		}
 		return null;
 	}
 
diff --git a/onionwrapper-java/build.gradle b/onionwrapper-java/build.gradle
index 913f000..0226df4 100644
--- a/onionwrapper-java/build.gradle
+++ b/onionwrapper-java/build.gradle
@@ -1,5 +1,4 @@
-import static org.briarproject.onionwrapper.OS.Linux
-import static org.briarproject.onionwrapper.OS.Windows
+import static org.briarproject.onionwrapper.OS.*
 import static org.briarproject.onionwrapper.OsUtils.currentOS
 
 plugins {
@@ -19,7 +18,7 @@ checkstyle {
 
 dependencies {
     api project(':onionwrapper-core')
-    def jna_version = '4.5.2'
+    def jna_version = '5.10.0'
     implementation "net.java.dev.jna:jna:$jna_version"
     implementation "net.java.dev.jna:jna-platform:$jna_version"
 
@@ -29,6 +28,10 @@ dependencies {
         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"
+    } else if (currentOS == MacOS) {
+        testImplementation "org.briarproject:tor-macos-torbrowser:0.4.7.13"
+        testImplementation "org.briarproject:obfs4proxy-macos-torbrowser:0.0.14"
+        testImplementation "org.briarproject:snowflake-macos-torbrowser:2.5.1"
     }
 }
 
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 3d196f9..eaea815 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 63d56d8..e8d9cc6 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());
 	}
 
@@ -52,7 +53,7 @@ public class BootstrapTest extends BaseTest {
 	public void testBootstrapping() throws Exception {
 		String architecture = requireNonNull(getArchitectureForTorBinary());
 		TorWrapper tor;
-		if (isLinux()) {
+		if (isLinux() || isMac()) {
 			tor = new UnixTorWrapper(executor, executor, architecture, torDir,
 					CONTROL_PORT, SOCKS_PORT);
 		} else if (isWindows()) {
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 1c822f5..5401107 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 7ebbf50..666f627 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 0000000..884225b
--- /dev/null
+++ b/onionwrapper-java/src/test/java/org/briarproject/onionwrapper/ResourcesMacTest.java
@@ -0,0 +1,42 @@
+package org.briarproject.onionwrapper;
+
+import org.junit.Before;
+import org.junit.Test;
+
+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("any/tor");
+	}
+
+	@Test
+	public void testCanLoadLibEvent() {
+		testCanLoadResource("any/libevent-2.1.7.dylib");
+	}
+
+	@Test
+	public void testCanLoadObfs4() {
+		testCanLoadResource("any/obfs4proxy");
+	}
+
+	@Test
+	public void testCanLoadSnowflake() {
+		testCanLoadResource("any/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 01a0137..51c8ddf 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;
 
diff --git a/settings.gradle b/settings.gradle
index 2bb9f2a..7412b9f 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -10,6 +10,7 @@ dependencyResolutionManagement {
     repositories {
         google()
         mavenCentral()
+        maven { url "https://mvntmp.mobanisto.de" }
     }
 }
 rootProject.name = "Onion Wrapper"
-- 
GitLab