Use UNIX Sockets for IPC

This commit is contained in:
Sebastian Stenzel
2021-07-13 18:18:38 +02:00
parent 97222d3d67
commit 755eb70ae8
27 changed files with 606 additions and 380 deletions
@@ -3,7 +3,6 @@ package org.cryptomator.common;
import org.hamcrest.MatcherAssert;
import org.hamcrest.Matchers;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Nested;
@@ -14,7 +13,6 @@ import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
@DisplayName("Environment Variables Test")
public class EnvironmentTest {
@@ -43,7 +41,7 @@ public class EnvironmentTest {
public void testIpcPortPath() {
System.setProperty("cryptomator.ipcPortPath", "~/.config/Cryptomator/ipcPort.bin:~/.Cryptomator/ipcPort.bin");
List<Path> result = env.getIpcPortPath().toList();
List<Path> result = env.ipcSocketPath().toList();
MatcherAssert.assertThat(result, Matchers.hasSize(2));
MatcherAssert.assertThat(result, Matchers.contains(Paths.get("/home/testuser/.config/Cryptomator/ipcPort.bin"), //
Paths.get("/home/testuser/.Cryptomator/ipcPort.bin")));
@@ -0,0 +1,46 @@
package org.cryptomator.ipc;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;
import java.io.IOException;
import java.nio.channels.FileChannel;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
import java.util.List;
public class HandleLaunchArgsMessageTest {
@Test
public void testSendAndReceive(@TempDir Path tmpDir) throws IOException {
var message = new HandleLaunchArgsMessage(List.of("hello world", "foo bar"));
var file = tmpDir.resolve("tmp.file");
try (var ch = FileChannel.open(file, StandardOpenOption.CREATE_NEW, StandardOpenOption.READ, StandardOpenOption.WRITE)) {
message.send(ch);
ch.position(0);
if (IpcMessage.receive(ch) instanceof HandleLaunchArgsMessage received) {
Assertions.assertArrayEquals(message.args().toArray(), received.args().toArray());
} else {
Assertions.fail("Received message of unexpected class");
}
}
}
@Test
public void testSendAndReceiveEmpty(@TempDir Path tmpDir) throws IOException {
var message = new HandleLaunchArgsMessage(List.of());
var file = tmpDir.resolve("tmp.file");
try (var ch = FileChannel.open(file, StandardOpenOption.CREATE_NEW, StandardOpenOption.READ, StandardOpenOption.WRITE)) {
message.send(ch);
ch.position(0);
if (IpcMessage.receive(ch) instanceof HandleLaunchArgsMessage received) {
Assertions.assertArrayEquals(message.args().toArray(), received.args().toArray());
} else {
Assertions.fail("Received message of unexpected class");
}
}
}
}
@@ -0,0 +1,46 @@
package org.cryptomator.ipc;
import com.google.common.util.concurrent.MoreExecutors;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.function.Executable;
import org.junit.jupiter.api.io.TempDir;
import org.mockito.Mockito;
import java.io.IOException;
import java.nio.file.Path;
import java.time.Duration;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Executors;
public class IpcCommunicatorTest {
@Test
public void testSendAndReceive(@TempDir Path tmpDir) throws IOException, InterruptedException {
var socketPath = tmpDir.resolve("foo.sock");
try (var server = IpcCommunicator.create(List.of(socketPath));
var client = IpcCommunicator.create(List.of(socketPath))) {
Assertions.assertNotSame(server, client);
var cdl = new CountDownLatch(1);
var executor = Executors.newSingleThreadExecutor();
server.listen(new IpcMessageListener() {
@Override
public void revealRunningApp() {
cdl.countDown();
}
@Override
public void handleLaunchArgs(List<String> args) {
}
}, executor);
client.sendRevealRunningApp();
Assertions.assertTimeoutPreemptively(Duration.ofMillis(300), (Executable) cdl::await);
executor.shutdown();
}
}
}
@@ -0,0 +1,37 @@
package org.cryptomator.ipc;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.function.Executable;
import java.time.Duration;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Executors;
public class LoopbackCommunicatorTest {
@Test
public void testSendAndReceive() {
try (var communicator = new LoopbackCommunicator()) {
var cdl = new CountDownLatch(1);
var executor = Executors.newSingleThreadExecutor();
communicator.listen(new IpcMessageListener() {
@Override
public void revealRunningApp() {
cdl.countDown();
}
@Override
public void handleLaunchArgs(List<String> args) {
}
}, executor);
communicator.sendRevealRunningApp();
Assertions.assertTimeoutPreemptively(Duration.ofMillis(300), (Executable) cdl::await);
executor.shutdown();
}
}
}
@@ -0,0 +1,30 @@
package org.cryptomator.ipc;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;
import java.io.IOException;
import java.nio.channels.FileChannel;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
import java.util.List;
public class RevealRunningAppMessageTest {
@Test
public void testSendAndReceive(@TempDir Path tmpDir) throws IOException {
var message = new RevealRunningAppMessage();
var file = tmpDir.resolve("tmp.file");
try (var ch = FileChannel.open(file, StandardOpenOption.CREATE_NEW, StandardOpenOption.READ, StandardOpenOption.WRITE)) {
message.send(ch);
ch.position(0);
if (IpcMessage.receive(ch) instanceof RevealRunningAppMessage received) {
Assertions.assertNotNull(received);
} else {
Assertions.fail("Received message of unexpected class");
}
}
}
}
@@ -21,6 +21,7 @@ import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
@@ -38,7 +39,7 @@ public class FileOpenRequestHandlerTest {
@Test
@DisplayName("./cryptomator.exe foo bar")
public void testOpenArgsWithCorrectPaths() {
inTest.handleLaunchArgs(new String[]{"foo", "bar"});
inTest.handleLaunchArgs(List.of("foo", "bar"));
AppLaunchEvent evt = queue.poll();
Assertions.assertNotNull(evt);
@@ -51,7 +52,7 @@ public class FileOpenRequestHandlerTest {
public void testOpenArgsWithIncorrectPaths() {
FileSystem fs = Mockito.mock(FileSystem.class);
Mockito.when(fs.getPath("foo")).thenThrow(new InvalidPathException("foo", "foo is not a path"));
inTest.handleLaunchArgs(fs, new String[]{"foo"});
inTest.handleLaunchArgs(fs, List.of("foo"));
AppLaunchEvent evt = queue.poll();
Assertions.assertNull(evt);
@@ -63,7 +64,7 @@ public class FileOpenRequestHandlerTest {
queue.add(new AppLaunchEvent(AppLaunchEvent.EventType.OPEN_FILE, Collections.emptyList()));
Assumptions.assumeTrue(queue.remainingCapacity() == 0);
inTest.handleLaunchArgs(new String[]{"foo"});
inTest.handleLaunchArgs(List.of("foo"));
}
}
@@ -1,71 +0,0 @@
/*******************************************************************************
* Copyright (c) 2017 Skymatic UG (haftungsbeschränkt).
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the accompanying LICENSE file.
*******************************************************************************/
package org.cryptomator.launcher;
import org.cryptomator.common.Environment;
import org.cryptomator.launcher.IpcFactory.IpcEndpoint;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;
import org.mockito.Mockito;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.stream.Stream;
public class IpcFactoryTest {
private Environment environment = Mockito.mock(Environment.class);
private IpcProtocolImpl protocolHandler = Mockito.mock(IpcProtocolImpl.class);
@Test
@DisplayName("Without IPC port files")
public void testNoIpcWithoutPortFile() throws IOException {
IpcFactory inTest = new IpcFactory(environment, protocolHandler);
Mockito.when(environment.getIpcPortPath()).thenReturn(Stream.empty());
try (IpcEndpoint endpoint1 = inTest.create()) {
Assertions.assertEquals(IpcFactory.SelfEndpoint.class, endpoint1.getClass());
Assertions.assertFalse(endpoint1.isConnectedToRemote());
Assertions.assertSame(protocolHandler, endpoint1.getRemote());
try (IpcEndpoint endpoint2 = inTest.create()) {
Assertions.assertEquals(IpcFactory.SelfEndpoint.class, endpoint2.getClass());
Assertions.assertNotSame(endpoint1, endpoint2);
Assertions.assertFalse(endpoint2.isConnectedToRemote());
Assertions.assertSame(protocolHandler, endpoint2.getRemote());
}
}
}
@Test
@DisplayName("Start server and client with port shared via file")
public void testInterProcessCommunication(@TempDir Path tmpDir) throws IOException {
Path portFile = tmpDir.resolve("testPortFile");
Mockito.when(environment.getIpcPortPath()).thenReturn(Stream.of(portFile));
IpcFactory inTest = new IpcFactory(environment, protocolHandler);
Assertions.assertFalse(Files.exists(portFile));
try (IpcEndpoint endpoint1 = inTest.create()) {
Assertions.assertEquals(IpcFactory.ServerEndpoint.class, endpoint1.getClass());
Assertions.assertFalse(endpoint1.isConnectedToRemote());
Assertions.assertTrue(Files.exists(portFile));
Assertions.assertSame(protocolHandler, endpoint1.getRemote());
Mockito.verifyZeroInteractions(protocolHandler);
try (IpcEndpoint endpoint2 = inTest.create()) {
Assertions.assertEquals(IpcFactory.ClientEndpoint.class, endpoint2.getClass());
Assertions.assertNotSame(endpoint1, endpoint2);
Assertions.assertTrue(endpoint2.isConnectedToRemote());
Assertions.assertNotSame(protocolHandler, endpoint2.getRemote());
Mockito.verifyZeroInteractions(protocolHandler);
endpoint2.getRemote().handleLaunchArgs(new String[]{"foo"});
Mockito.verify(protocolHandler).handleLaunchArgs(new String[]{"foo"});
}
}
}
}