diff --git a/bramble-api/src/main/java/org/briarproject/bramble/api/connection/ConnectionManager.java b/bramble-api/src/main/java/org/briarproject/bramble/api/connection/ConnectionManager.java index e08a73e9b..3d7df9bea 100644 --- a/bramble-api/src/main/java/org/briarproject/bramble/api/connection/ConnectionManager.java +++ b/bramble-api/src/main/java/org/briarproject/bramble/api/connection/ConnectionManager.java @@ -16,6 +16,17 @@ public interface ConnectionManager { */ void manageIncomingConnection(TransportId t, TransportConnectionReader r); + /** + * Manages an incoming connection from a contact via a mailbox. + *
+ * This method does not mark the tag as recognised until after the data
+ * has been read from the {@link TransportConnectionReader}, at which
+ * point the {@link TagController} is called to decide whether the tag
+ * should be marked as recognised.
+ */
+ void manageIncomingConnection(TransportId t, TransportConnectionReader r,
+ TagController c);
+
/**
* Manages an incoming connection from a contact over a duplex transport.
*/
@@ -46,4 +57,21 @@ public interface ConnectionManager {
*/
void manageOutgoingConnection(PendingContactId p, TransportId t,
DuplexTransportConnection d);
+
+ /**
+ * An interface for controlling whether a tag should be marked as
+ * recognised.
+ */
+ interface TagController {
+ /**
+ * This method is only called if a tag was read from the corresponding
+ * {@link TransportConnectionReader} and recognised.
+ *
+ * @param exception True if an exception was thrown while reading from
+ * the {@link TransportConnectionReader}, after successfully reading
+ * and recognising the tag.
+ * @return True if the tag should be marked as recognised.
+ */
+ boolean shouldMarkTagAsRecognised(boolean exception);
+ }
}
diff --git a/bramble-api/src/main/java/org/briarproject/bramble/api/mailbox/MailboxConstants.java b/bramble-api/src/main/java/org/briarproject/bramble/api/mailbox/MailboxConstants.java
index 8cc6b5fc0..51d7f0a7c 100644
--- a/bramble-api/src/main/java/org/briarproject/bramble/api/mailbox/MailboxConstants.java
+++ b/bramble-api/src/main/java/org/briarproject/bramble/api/mailbox/MailboxConstants.java
@@ -1,5 +1,7 @@
package org.briarproject.bramble.api.mailbox;
+import org.briarproject.bramble.api.plugin.TransportId;
+
import static java.util.concurrent.TimeUnit.HOURS;
import static org.briarproject.bramble.api.transport.TransportConstants.MAX_FRAME_LENGTH;
import static org.briarproject.bramble.api.transport.TransportConstants.MAX_PAYLOAD_LENGTH;
@@ -8,6 +10,11 @@ import static org.briarproject.bramble.api.transport.TransportConstants.TAG_LENG
public interface MailboxConstants {
+ /**
+ * The transport ID of the mailbox plugin.
+ */
+ TransportId ID = new TransportId("org.briarproject.bramble.mailbox");
+
/**
* The maximum length of a file that can be uploaded to or downloaded from
* a mailbox.
diff --git a/bramble-api/src/main/java/org/briarproject/bramble/api/mailbox/MailboxDirectory.java b/bramble-api/src/main/java/org/briarproject/bramble/api/mailbox/MailboxDirectory.java
new file mode 100644
index 000000000..28151dd15
--- /dev/null
+++ b/bramble-api/src/main/java/org/briarproject/bramble/api/mailbox/MailboxDirectory.java
@@ -0,0 +1,22 @@
+package org.briarproject.bramble.api.mailbox;
+
+import java.io.File;
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+import javax.inject.Qualifier;
+
+import static java.lang.annotation.ElementType.FIELD;
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.ElementType.PARAMETER;
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+/**
+ * Annotation for injecting the {@link File directory} where the Mailbox plugin
+ * should store its state.
+ */
+@Qualifier
+@Target({FIELD, METHOD, PARAMETER})
+@Retention(RUNTIME)
+public @interface MailboxDirectory {
+}
diff --git a/bramble-core/src/main/java/org/briarproject/bramble/connection/Connection.java b/bramble-core/src/main/java/org/briarproject/bramble/connection/Connection.java
index 9ca5c672e..9db61721c 100644
--- a/bramble-core/src/main/java/org/briarproject/bramble/connection/Connection.java
+++ b/bramble-core/src/main/java/org/briarproject/bramble/connection/Connection.java
@@ -54,7 +54,7 @@ abstract class Connection {
}
}
- private byte[] readTag(InputStream in) throws IOException {
+ byte[] readTag(InputStream in) throws IOException {
byte[] tag = new byte[TAG_LENGTH];
read(in, tag);
return tag;
diff --git a/bramble-core/src/main/java/org/briarproject/bramble/connection/ConnectionManagerImpl.java b/bramble-core/src/main/java/org/briarproject/bramble/connection/ConnectionManagerImpl.java
index 2a50033b1..2e37ce7cd 100644
--- a/bramble-core/src/main/java/org/briarproject/bramble/connection/ConnectionManagerImpl.java
+++ b/bramble-core/src/main/java/org/briarproject/bramble/connection/ConnectionManagerImpl.java
@@ -67,7 +67,15 @@ class ConnectionManagerImpl implements ConnectionManager {
TransportConnectionReader r) {
ioExecutor.execute(new IncomingSimplexSyncConnection(keyManager,
connectionRegistry, streamReaderFactory, streamWriterFactory,
- syncSessionFactory, transportPropertyManager, t, r));
+ syncSessionFactory, transportPropertyManager, t, r, null));
+ }
+
+ @Override
+ public void manageIncomingConnection(TransportId t,
+ TransportConnectionReader r, TagController c) {
+ ioExecutor.execute(new IncomingSimplexSyncConnection(keyManager,
+ connectionRegistry, streamReaderFactory, streamWriterFactory,
+ syncSessionFactory, transportPropertyManager, t, r, c));
}
@Override
diff --git a/bramble-core/src/main/java/org/briarproject/bramble/connection/IncomingSimplexSyncConnection.java b/bramble-core/src/main/java/org/briarproject/bramble/connection/IncomingSimplexSyncConnection.java
index b41fb33e0..68b9d969c 100644
--- a/bramble-core/src/main/java/org/briarproject/bramble/connection/IncomingSimplexSyncConnection.java
+++ b/bramble-core/src/main/java/org/briarproject/bramble/connection/IncomingSimplexSyncConnection.java
@@ -1,7 +1,9 @@
package org.briarproject.bramble.connection;
+import org.briarproject.bramble.api.connection.ConnectionManager.TagController;
import org.briarproject.bramble.api.connection.ConnectionRegistry;
import org.briarproject.bramble.api.contact.ContactId;
+import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.plugin.TransportConnectionReader;
import org.briarproject.bramble.api.plugin.TransportId;
@@ -15,6 +17,8 @@ import org.briarproject.bramble.api.transport.StreamWriterFactory;
import java.io.IOException;
+import javax.annotation.Nullable;
+
import static java.util.logging.Level.WARNING;
import static org.briarproject.bramble.util.LogUtils.logException;
@@ -23,6 +27,8 @@ class IncomingSimplexSyncConnection extends SyncConnection implements Runnable {
private final TransportId transportId;
private final TransportConnectionReader reader;
+ @Nullable
+ private final TagController tagController;
IncomingSimplexSyncConnection(KeyManager keyManager,
ConnectionRegistry connectionRegistry,
@@ -30,33 +36,50 @@ class IncomingSimplexSyncConnection extends SyncConnection implements Runnable {
StreamWriterFactory streamWriterFactory,
SyncSessionFactory syncSessionFactory,
TransportPropertyManager transportPropertyManager,
- TransportId transportId, TransportConnectionReader reader) {
+ TransportId transportId,
+ TransportConnectionReader reader,
+ @Nullable TagController tagController) {
super(keyManager, connectionRegistry, streamReaderFactory,
streamWriterFactory, syncSessionFactory,
transportPropertyManager);
this.transportId = transportId;
this.reader = reader;
+ this.tagController = tagController;
}
@Override
public void run() {
// Read and recognise the tag
- StreamContext ctx = recogniseTag(reader, transportId);
+ byte[] tag;
+ StreamContext ctx;
+ try {
+ tag = readTag(reader.getInputStream());
+ // If we have a tag controller, defer marking the tag as recognised
+ if (tagController == null) {
+ ctx = keyManager.getStreamContext(transportId, tag);
+ } else {
+ ctx = keyManager.getStreamContextOnly(transportId, tag);
+ }
+ } catch (IOException | DbException e) {
+ logException(LOG, WARNING, e);
+ onError();
+ return;
+ }
if (ctx == null) {
LOG.info("Unrecognised tag");
- onError(false);
+ onError();
return;
}
ContactId contactId = ctx.getContactId();
if (contactId == null) {
LOG.warning("Received rendezvous stream, expected contact");
- onError(true);
+ onError(tag);
return;
}
if (ctx.isHandshakeMode()) {
// TODO: Support handshake mode for contacts
LOG.warning("Received handshake tag, expected rotation mode");
- onError(true);
+ onError(tag);
return;
}
try {
@@ -65,15 +88,33 @@ class IncomingSimplexSyncConnection extends SyncConnection implements Runnable {
LOG.info("Ignoring priority for simplex connection");
// Create and run the incoming session
createIncomingSession(ctx, reader, handler).run();
+ // Success
+ markTagAsRecognisedIfRequired(false, tag);
reader.dispose(false, true);
} catch (IOException e) {
logException(LOG, WARNING, e);
- onError(true);
+ onError(tag);
}
}
- private void onError(boolean recognised) {
- disposeOnError(reader, recognised);
+ private void onError() {
+ disposeOnError(reader, false);
+ }
+
+ private void onError(byte[] tag) {
+ markTagAsRecognisedIfRequired(true, tag);
+ disposeOnError(reader, true);
+ }
+
+ private void markTagAsRecognisedIfRequired(boolean exception, byte[] tag) {
+ if (tagController != null &&
+ tagController.shouldMarkTagAsRecognised(exception)) {
+ try {
+ keyManager.markTagAsRecognised(transportId, tag);
+ } catch (DbException e) {
+ logException(LOG, WARNING, e);
+ }
+ }
}
}
diff --git a/bramble-core/src/main/java/org/briarproject/bramble/mailbox/MailboxFileManager.java b/bramble-core/src/main/java/org/briarproject/bramble/mailbox/MailboxFileManager.java
new file mode 100644
index 000000000..0320b1bd5
--- /dev/null
+++ b/bramble-core/src/main/java/org/briarproject/bramble/mailbox/MailboxFileManager.java
@@ -0,0 +1,24 @@
+package org.briarproject.bramble.mailbox;
+
+import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
+
+import java.io.File;
+import java.io.IOException;
+
+import javax.annotation.concurrent.ThreadSafe;
+
+@ThreadSafe
+@NotNullByDefault
+interface MailboxFileManager {
+
+ /**
+ * Creates an empty file for storing a download.
+ */
+ File createTempFileForDownload() throws IOException;
+
+ /**
+ * Handles a file that has been downloaded. The file should be created
+ * with {@link #createTempFileForDownload()}.
+ */
+ void handleDownloadedFile(File f);
+}
diff --git a/bramble-core/src/main/java/org/briarproject/bramble/mailbox/MailboxFileManagerImpl.java b/bramble-core/src/main/java/org/briarproject/bramble/mailbox/MailboxFileManagerImpl.java
new file mode 100644
index 000000000..b3d84837c
--- /dev/null
+++ b/bramble-core/src/main/java/org/briarproject/bramble/mailbox/MailboxFileManagerImpl.java
@@ -0,0 +1,174 @@
+package org.briarproject.bramble.mailbox;
+
+import org.briarproject.bramble.api.connection.ConnectionManager;
+import org.briarproject.bramble.api.event.Event;
+import org.briarproject.bramble.api.event.EventBus;
+import org.briarproject.bramble.api.event.EventListener;
+import org.briarproject.bramble.api.lifecycle.IoExecutor;
+import org.briarproject.bramble.api.lifecycle.LifecycleManager;
+import org.briarproject.bramble.api.mailbox.MailboxDirectory;
+import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
+import org.briarproject.bramble.api.plugin.PluginManager;
+import org.briarproject.bramble.api.plugin.TransportConnectionReader;
+import org.briarproject.bramble.api.plugin.event.TransportActiveEvent;
+import org.briarproject.bramble.api.plugin.simplex.SimplexPlugin;
+import org.briarproject.bramble.api.properties.TransportProperties;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.Executor;
+import java.util.logging.Logger;
+
+import javax.annotation.concurrent.ThreadSafe;
+import javax.inject.Inject;
+
+import static java.util.logging.Level.WARNING;
+import static java.util.logging.Logger.getLogger;
+import static org.briarproject.bramble.api.lifecycle.LifecycleManager.LifecycleState.RUNNING;
+import static org.briarproject.bramble.api.mailbox.MailboxConstants.ID;
+import static org.briarproject.bramble.api.nullsafety.NullSafety.requireNonNull;
+import static org.briarproject.bramble.api.plugin.file.FileConstants.PROP_PATH;
+import static org.briarproject.bramble.util.LogUtils.logException;
+
+@ThreadSafe
+@NotNullByDefault
+class MailboxFileManagerImpl implements MailboxFileManager, EventListener {
+
+ private static final Logger LOG =
+ getLogger(MailboxFileManagerImpl.class.getName());
+
+ // Package access for testing
+ static final String DOWNLOAD_DIR_NAME = "downloads";
+
+ private final Executor ioExecutor;
+ private final PluginManager pluginManager;
+ private final ConnectionManager connectionManager;
+ private final LifecycleManager lifecycleManager;
+ private final File mailboxDir;
+ private final EventBus eventBus;
+ private final CountDownLatch orphanLatch = new CountDownLatch(1);
+
+ @Inject
+ MailboxFileManagerImpl(@IoExecutor Executor ioExecutor,
+ PluginManager pluginManager,
+ ConnectionManager connectionManager,
+ LifecycleManager lifecycleManager,
+ @MailboxDirectory File mailboxDir,
+ EventBus eventBus) {
+ this.ioExecutor = ioExecutor;
+ this.pluginManager = pluginManager;
+ this.connectionManager = connectionManager;
+ this.lifecycleManager = lifecycleManager;
+ this.mailboxDir = mailboxDir;
+ this.eventBus = eventBus;
+ }
+
+ @Override
+ public File createTempFileForDownload() throws IOException {
+ // Wait for orphaned files to be handled before creating new files
+ try {
+ orphanLatch.await();
+ } catch (InterruptedException e) {
+ throw new IOException(e);
+ }
+ File downloadDir = createDirectoryIfNeeded(DOWNLOAD_DIR_NAME);
+ return File.createTempFile("mailbox", ".tmp", downloadDir);
+ }
+
+ private File createDirectoryIfNeeded(String name) throws IOException {
+ File dir = new File(mailboxDir, name);
+ //noinspection ResultOfMethodCallIgnored
+ dir.mkdirs();
+ if (!dir.isDirectory()) {
+ throw new IOException("Failed to create directory '" + name + "'");
+ }
+ return dir;
+ }
+
+ @Override
+ public void handleDownloadedFile(File f) {
+ // We shouldn't reach this point until the plugin has been started
+ SimplexPlugin plugin =
+ (SimplexPlugin) requireNonNull(pluginManager.getPlugin(ID));
+ TransportProperties p = new TransportProperties();
+ p.put(PROP_PATH, f.getAbsolutePath());
+ TransportConnectionReader reader = plugin.createReader(p);
+ if (reader == null) {
+ LOG.warning("Failed to create reader for downloaded file");
+ return;
+ }
+ TransportConnectionReader decorated = new MailboxFileReader(reader, f);
+ LOG.info("Reading downloaded file");
+ connectionManager.manageIncomingConnection(ID, decorated,
+ exception -> isHandlingComplete(exception, true));
+ }
+
+ private boolean isHandlingComplete(boolean exception, boolean recognised) {
+ // If we've successfully read the file then we're done
+ if (!exception && recognised) return true;
+ // If the app is shutting down we may get spurious IO exceptions
+ // due to executors being shut down. Leave the file in the download
+ // directory and we'll try to read it again at the next startup
+ return !lifecycleManager.getLifecycleState().isAfter(RUNNING);
+ }
+
+ @Override
+ public void eventOccurred(Event e) {
+ if (e instanceof TransportActiveEvent) {
+ TransportActiveEvent t = (TransportActiveEvent) e;
+ if (t.getTransportId().equals(ID)) {
+ ioExecutor.execute(this::handleOrphanedFiles);
+ eventBus.removeListener(this);
+ }
+ }
+ }
+
+ /**
+ * This method is called at startup, as soon as the plugin is started, to
+ * handle any files that were left in the download directory at the last
+ * shutdown.
+ */
+ @IoExecutor
+ private void handleOrphanedFiles() {
+ try {
+ File downloadDir = createDirectoryIfNeeded(DOWNLOAD_DIR_NAME);
+ File[] orphans = downloadDir.listFiles();
+ // Now that we've got the list of orphans, new files can be created
+ orphanLatch.countDown();
+ if (orphans != null) for (File f : orphans) handleDownloadedFile(f);
+ } catch (IOException e) {
+ logException(LOG, WARNING, e);
+ }
+ }
+
+ private class MailboxFileReader implements TransportConnectionReader {
+
+ private final TransportConnectionReader delegate;
+ private final File file;
+
+ private MailboxFileReader(TransportConnectionReader delegate,
+ File file) {
+ this.delegate = delegate;
+ this.file = file;
+ }
+
+ @Override
+ public InputStream getInputStream() throws IOException {
+ return delegate.getInputStream();
+ }
+
+ @Override
+ public void dispose(boolean exception, boolean recognised)
+ throws IOException {
+ delegate.dispose(exception, recognised);
+ if (isHandlingComplete(exception, recognised)) {
+ LOG.info("Deleting downloaded file");
+ if (!file.delete()) {
+ LOG.warning("Failed to delete downloaded file");
+ }
+ }
+ }
+ }
+}
diff --git a/bramble-core/src/main/java/org/briarproject/bramble/mailbox/MailboxModule.java b/bramble-core/src/main/java/org/briarproject/bramble/mailbox/MailboxModule.java
index 590a7f857..3a675f774 100644
--- a/bramble-core/src/main/java/org/briarproject/bramble/mailbox/MailboxModule.java
+++ b/bramble-core/src/main/java/org/briarproject/bramble/mailbox/MailboxModule.java
@@ -4,6 +4,7 @@ import org.briarproject.bramble.api.FeatureFlags;
import org.briarproject.bramble.api.client.ClientHelper;
import org.briarproject.bramble.api.contact.ContactManager;
import org.briarproject.bramble.api.data.MetadataEncoder;
+import org.briarproject.bramble.api.event.EventBus;
import org.briarproject.bramble.api.lifecycle.LifecycleManager;
import org.briarproject.bramble.api.mailbox.MailboxManager;
import org.briarproject.bramble.api.mailbox.MailboxSettingsManager;
@@ -34,6 +35,8 @@ public class MailboxModule {
MailboxUpdateValidator mailboxUpdateValidator;
@Inject
MailboxUpdateManager mailboxUpdateManager;
+ @Inject
+ MailboxFileManager mailboxFileManager;
}
@Provides
@@ -101,4 +104,14 @@ public class MailboxModule {
}
return mailboxUpdateManager;
}
+
+ @Provides
+ @Singleton
+ MailboxFileManager provideMailboxFileManager(FeatureFlags featureFlags,
+ EventBus eventBus, MailboxFileManagerImpl mailboxFileManager) {
+ if (featureFlags.shouldEnableMailbox()) {
+ eventBus.addListener(mailboxFileManager);
+ }
+ return mailboxFileManager;
+ }
}
diff --git a/bramble-core/src/main/java/org/briarproject/bramble/plugin/file/AbstractRemovableDrivePlugin.java b/bramble-core/src/main/java/org/briarproject/bramble/plugin/file/AbstractRemovableDrivePlugin.java
index ff4b9c048..ccdf7f56b 100644
--- a/bramble-core/src/main/java/org/briarproject/bramble/plugin/file/AbstractRemovableDrivePlugin.java
+++ b/bramble-core/src/main/java/org/briarproject/bramble/plugin/file/AbstractRemovableDrivePlugin.java
@@ -67,6 +67,7 @@ abstract class AbstractRemovableDrivePlugin implements SimplexPlugin {
public void start() {
callback.mergeLocalProperties(
new TransportProperties(singletonMap(PROP_SUPPORTED, "true")));
+ callback.pluginStateChanged(ACTIVE);
}
@Override
diff --git a/bramble-core/src/main/java/org/briarproject/bramble/plugin/file/FilePlugin.java b/bramble-core/src/main/java/org/briarproject/bramble/plugin/file/FilePlugin.java
index 69b368a55..54587dc9c 100644
--- a/bramble-core/src/main/java/org/briarproject/bramble/plugin/file/FilePlugin.java
+++ b/bramble-core/src/main/java/org/briarproject/bramble/plugin/file/FilePlugin.java
@@ -11,6 +11,7 @@ import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
+import java.io.OutputStream;
import java.util.logging.Logger;
import static java.util.logging.Level.WARNING;
@@ -29,11 +30,6 @@ abstract class FilePlugin implements SimplexPlugin {
protected final PluginCallback callback;
protected final long maxLatency;
- protected abstract void writerFinished(File f, boolean exception);
-
- protected abstract void readerFinished(File f, boolean exception,
- boolean recognised);
-
FilePlugin(PluginCallback callback, long maxLatency) {
this.callback = callback;
this.maxLatency = maxLatency;
@@ -50,9 +46,8 @@ abstract class FilePlugin implements SimplexPlugin {
String path = p.get(PROP_PATH);
if (isNullOrEmpty(path)) return null;
try {
- File file = new File(path);
- FileInputStream in = new FileInputStream(file);
- return new FileTransportReader(file, in, this);
+ FileInputStream in = new FileInputStream(path);
+ return new TransportInputStreamReader(in);
} catch (IOException e) {
logException(LOG, WARNING, e);
return null;
@@ -70,8 +65,8 @@ abstract class FilePlugin implements SimplexPlugin {
LOG.info("Failed to create file");
return null;
}
- FileOutputStream out = new FileOutputStream(file);
- return new FileTransportWriter(file, out, this);
+ OutputStream out = new FileOutputStream(file);
+ return new TransportOutputStreamWriter(this, out);
} catch (IOException e) {
logException(LOG, WARNING, e);
return null;
diff --git a/bramble-core/src/main/java/org/briarproject/bramble/plugin/file/FileTransportReader.java b/bramble-core/src/main/java/org/briarproject/bramble/plugin/file/FileTransportReader.java
deleted file mode 100644
index 07a84d294..000000000
--- a/bramble-core/src/main/java/org/briarproject/bramble/plugin/file/FileTransportReader.java
+++ /dev/null
@@ -1,39 +0,0 @@
-package org.briarproject.bramble.plugin.file;
-
-import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
-import org.briarproject.bramble.api.plugin.TransportConnectionReader;
-
-import java.io.File;
-import java.io.InputStream;
-import java.util.logging.Logger;
-
-import static java.util.logging.Level.WARNING;
-import static org.briarproject.bramble.util.IoUtils.tryToClose;
-
-@NotNullByDefault
-class FileTransportReader implements TransportConnectionReader {
-
- private static final Logger LOG =
- Logger.getLogger(FileTransportReader.class.getName());
-
- private final File file;
- private final InputStream in;
- private final FilePlugin plugin;
-
- FileTransportReader(File file, InputStream in, FilePlugin plugin) {
- this.file = file;
- this.in = in;
- this.plugin = plugin;
- }
-
- @Override
- public InputStream getInputStream() {
- return in;
- }
-
- @Override
- public void dispose(boolean exception, boolean recognised) {
- tryToClose(in, LOG, WARNING);
- plugin.readerFinished(file, exception, recognised);
- }
-}
diff --git a/bramble-core/src/main/java/org/briarproject/bramble/plugin/file/FileTransportWriter.java b/bramble-core/src/main/java/org/briarproject/bramble/plugin/file/FileTransportWriter.java
deleted file mode 100644
index 3dc6fc428..000000000
--- a/bramble-core/src/main/java/org/briarproject/bramble/plugin/file/FileTransportWriter.java
+++ /dev/null
@@ -1,54 +0,0 @@
-package org.briarproject.bramble.plugin.file;
-
-import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
-import org.briarproject.bramble.api.plugin.TransportConnectionWriter;
-
-import java.io.File;
-import java.io.OutputStream;
-import java.util.logging.Logger;
-
-import static java.util.logging.Level.WARNING;
-import static org.briarproject.bramble.util.IoUtils.tryToClose;
-
-@NotNullByDefault
-class FileTransportWriter implements TransportConnectionWriter {
-
- private static final Logger LOG =
- Logger.getLogger(FileTransportWriter.class.getName());
-
- private final File file;
- private final OutputStream out;
- private final FilePlugin plugin;
-
- FileTransportWriter(File file, OutputStream out, FilePlugin plugin) {
- this.file = file;
- this.out = out;
- this.plugin = plugin;
- }
-
- @Override
- public long getMaxLatency() {
- return plugin.getMaxLatency();
- }
-
- @Override
- public int getMaxIdleTime() {
- return plugin.getMaxIdleTime();
- }
-
- @Override
- public boolean isLossyAndCheap() {
- return plugin.isLossyAndCheap();
- }
-
- @Override
- public OutputStream getOutputStream() {
- return out;
- }
-
- @Override
- public void dispose(boolean exception) {
- tryToClose(out, LOG, WARNING);
- plugin.writerFinished(file, exception);
- }
-}
diff --git a/bramble-core/src/main/java/org/briarproject/bramble/plugin/file/MailboxPlugin.java b/bramble-core/src/main/java/org/briarproject/bramble/plugin/file/MailboxPlugin.java
new file mode 100644
index 000000000..a70e65708
--- /dev/null
+++ b/bramble-core/src/main/java/org/briarproject/bramble/plugin/file/MailboxPlugin.java
@@ -0,0 +1,73 @@
+package org.briarproject.bramble.plugin.file;
+
+import org.briarproject.bramble.api.Pair;
+import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
+import org.briarproject.bramble.api.plugin.ConnectionHandler;
+import org.briarproject.bramble.api.plugin.PluginCallback;
+import org.briarproject.bramble.api.plugin.PluginException;
+import org.briarproject.bramble.api.plugin.TransportId;
+import org.briarproject.bramble.api.properties.TransportProperties;
+
+import java.util.Collection;
+
+import static org.briarproject.bramble.api.mailbox.MailboxConstants.ID;
+import static org.briarproject.bramble.api.plugin.Plugin.State.ACTIVE;
+
+@NotNullByDefault
+class MailboxPlugin extends FilePlugin {
+
+ MailboxPlugin(PluginCallback callback, long maxLatency) {
+ super(callback, maxLatency);
+ }
+
+ @Override
+ public TransportId getId() {
+ return ID;
+ }
+
+ @Override
+ public int getMaxIdleTime() {
+ // Unused for simplex transports
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void start() throws PluginException {
+ callback.pluginStateChanged(ACTIVE);
+ }
+
+ @Override
+ public void stop() throws PluginException {
+ }
+
+ @Override
+ public State getState() {
+ return ACTIVE;
+ }
+
+ @Override
+ public int getReasonsDisabled() {
+ return 0;
+ }
+
+ @Override
+ public boolean shouldPoll() {
+ return false;
+ }
+
+ @Override
+ public int getPollingInterval() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void poll(
+ Collection