Merge branch '2292-mailbox-file-manager' into 'master'

Add mailbox plugin and file manager for downloads

See merge request briar/briar!1655
This commit is contained in:
Torsten Grote
2022-06-06 11:51:22 +00:00
22 changed files with 689 additions and 117 deletions

View File

@@ -16,6 +16,17 @@ public interface ConnectionManager {
*/
void manageIncomingConnection(TransportId t, TransportConnectionReader r);
/**
* Manages an incoming connection from a contact via a mailbox.
* <p>
* 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);
}
}

View File

@@ -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.

View File

@@ -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 {
}

View File

@@ -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;

View File

@@ -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

View File

@@ -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);
}
}
}
}

View File

@@ -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);
}

View File

@@ -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");
}
}
}
}
}

View File

@@ -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;
}
}

View File

@@ -67,6 +67,7 @@ abstract class AbstractRemovableDrivePlugin implements SimplexPlugin {
public void start() {
callback.mergeLocalProperties(
new TransportProperties(singletonMap(PROP_SUPPORTED, "true")));
callback.pluginStateChanged(ACTIVE);
}
@Override

View File

@@ -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;

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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<Pair<TransportProperties, ConnectionHandler>> properties) {
throw new UnsupportedOperationException();
}
@Override
public boolean isLossyAndCheap() {
return false;
}
}

View File

@@ -0,0 +1,39 @@
package org.briarproject.bramble.plugin.file;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.plugin.PluginCallback;
import org.briarproject.bramble.api.plugin.TransportId;
import org.briarproject.bramble.api.plugin.simplex.SimplexPlugin;
import org.briarproject.bramble.api.plugin.simplex.SimplexPluginFactory;
import javax.annotation.Nullable;
import javax.inject.Inject;
import static java.util.concurrent.TimeUnit.DAYS;
import static org.briarproject.bramble.api.mailbox.MailboxConstants.ID;
@NotNullByDefault
public class MailboxPluginFactory implements SimplexPluginFactory {
private static final long MAX_LATENCY = DAYS.toMillis(14);
@Inject
MailboxPluginFactory() {
}
@Override
public TransportId getId() {
return ID;
}
@Override
public long getMaxLatency() {
return MAX_LATENCY;
}
@Nullable
@Override
public SimplexPlugin createPlugin(PluginCallback callback) {
return new MailboxPlugin(callback, MAX_LATENCY);
}
}

View File

@@ -0,0 +1,194 @@
package org.briarproject.bramble.mailbox;
import org.briarproject.bramble.api.connection.ConnectionManager;
import org.briarproject.bramble.api.connection.ConnectionManager.TagController;
import org.briarproject.bramble.api.event.EventBus;
import org.briarproject.bramble.api.lifecycle.LifecycleManager;
import org.briarproject.bramble.api.lifecycle.LifecycleManager.LifecycleState;
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 org.briarproject.bramble.test.BrambleMockTestCase;
import org.briarproject.bramble.test.CaptureArgumentAction;
import org.briarproject.bramble.test.RunAction;
import org.jmock.Expectations;
import org.jmock.lib.action.DoAllAction;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import java.io.File;
import java.util.concurrent.Executor;
import java.util.concurrent.atomic.AtomicReference;
import static org.briarproject.bramble.api.lifecycle.LifecycleManager.LifecycleState.RUNNING;
import static org.briarproject.bramble.api.lifecycle.LifecycleManager.LifecycleState.STOPPING;
import static org.briarproject.bramble.api.mailbox.MailboxConstants.ID;
import static org.briarproject.bramble.api.plugin.file.FileConstants.PROP_PATH;
import static org.briarproject.bramble.mailbox.MailboxFileManagerImpl.DOWNLOAD_DIR_NAME;
import static org.briarproject.bramble.test.TestUtils.deleteTestDirectory;
import static org.briarproject.bramble.test.TestUtils.getTestDirectory;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
public class MailboxFileManagerImplTest extends BrambleMockTestCase {
private final Executor ioExecutor = context.mock(Executor.class);
private final PluginManager pluginManager =
context.mock(PluginManager.class);
private final ConnectionManager connectionManager =
context.mock(ConnectionManager.class);
private final LifecycleManager lifecycleManager =
context.mock(LifecycleManager.class);
private final EventBus eventBus = context.mock(EventBus.class);
private final SimplexPlugin plugin = context.mock(SimplexPlugin.class);
private final TransportConnectionReader transportConnectionReader =
context.mock(TransportConnectionReader.class);
private File mailboxDir;
private MailboxFileManagerImpl manager;
@Before
public void setUp() {
mailboxDir = getTestDirectory();
manager = new MailboxFileManagerImpl(ioExecutor, pluginManager,
connectionManager, lifecycleManager, mailboxDir, eventBus);
}
@After
public void tearDown() {
deleteTestDirectory(mailboxDir);
}
@Test
public void testHandlesOrphanedFilesAtStartup() throws Exception {
// Create an orphaned file, left behind at the previous shutdown
File downloadDir = new File(mailboxDir, DOWNLOAD_DIR_NAME);
//noinspection ResultOfMethodCallIgnored
downloadDir.mkdirs();
File orphan = new File(downloadDir, "orphan");
assertTrue(orphan.createNewFile());
TransportProperties props = new TransportProperties();
props.put(PROP_PATH, orphan.getAbsolutePath());
// When the plugin becomes active the orphaned file should be handled
context.checking(new Expectations() {{
oneOf(ioExecutor).execute(with(any(Runnable.class)));
will(new RunAction());
oneOf(eventBus).removeListener(manager);
oneOf(pluginManager).getPlugin(ID);
will(returnValue(plugin));
oneOf(plugin).createReader(props);
will(returnValue(transportConnectionReader));
oneOf(connectionManager).manageIncomingConnection(with(ID),
with(any(TransportConnectionReader.class)),
with(any(TagController.class)));
}});
manager.eventOccurred(new TransportActiveEvent(ID));
}
@Test
public void testDeletesFileWhenReadSucceeds() throws Exception {
expectCheckForOrphans();
manager.eventOccurred(new TransportActiveEvent(ID));
File f = manager.createTempFileForDownload();
AtomicReference<TransportConnectionReader> reader =
new AtomicReference<>(null);
AtomicReference<TagController> controller = new AtomicReference<>(null);
expectPassFileToConnectionManager(f, reader, controller);
manager.handleDownloadedFile(f);
// The read is successful, so the tag controller should allow the tag
// to be marked as read and the reader should delete the file
context.checking(new Expectations() {{
oneOf(transportConnectionReader).dispose(false, true);
}});
assertTrue(controller.get().shouldMarkTagAsRecognised(false));
reader.get().dispose(false, true);
assertFalse(f.exists());
}
@Test
public void testDeletesFileWhenTagIsNotRecognised() throws Exception {
testDeletesFile(false, RUNNING, false);
}
@Test
public void testDeletesFileWhenReadFails() throws Exception {
testDeletesFile(true, RUNNING, false);
}
@Test
public void testDoesNotDeleteFileWhenTagIsNotRecognisedAtShutdown()
throws Exception {
testDeletesFile(false, STOPPING, true);
}
@Test
public void testDoesNotDeleteFileWhenReadFailsAtShutdown()
throws Exception {
testDeletesFile(true, STOPPING, true);
}
private void testDeletesFile(boolean recognised, LifecycleState state,
boolean fileExists) throws Exception {
expectCheckForOrphans();
manager.eventOccurred(new TransportActiveEvent(ID));
File f = manager.createTempFileForDownload();
AtomicReference<TransportConnectionReader> reader =
new AtomicReference<>(null);
AtomicReference<TagController> controller = new AtomicReference<>(null);
expectPassFileToConnectionManager(f, reader, controller);
manager.handleDownloadedFile(f);
context.checking(new Expectations() {{
oneOf(transportConnectionReader).dispose(true, recognised);
oneOf(lifecycleManager).getLifecycleState();
will(returnValue(state));
}});
reader.get().dispose(true, recognised);
assertEquals(fileExists, f.exists());
}
private void expectCheckForOrphans() {
context.checking(new Expectations() {{
oneOf(ioExecutor).execute(with(any(Runnable.class)));
will(new RunAction());
oneOf(eventBus).removeListener(manager);
}});
}
private void expectPassFileToConnectionManager(File f,
AtomicReference<TransportConnectionReader> reader,
AtomicReference<TagController> controller) {
TransportProperties props = new TransportProperties();
props.put(PROP_PATH, f.getAbsolutePath());
context.checking(new Expectations() {{
oneOf(pluginManager).getPlugin(ID);
will(returnValue(plugin));
oneOf(plugin).createReader(props);
will(returnValue(transportConnectionReader));
oneOf(connectionManager).manageIncomingConnection(with(ID),
with(any(TransportConnectionReader.class)),
with(any(TagController.class)));
will(new DoAllAction(
new CaptureArgumentAction<>(reader,
TransportConnectionReader.class, 1),
new CaptureArgumentAction<>(controller,
TagController.class, 2)
));
}});
}
}

View File

@@ -13,6 +13,7 @@ import org.briarproject.bramble.system.DefaultWakefulIoExecutorModule;
import org.briarproject.bramble.system.TimeTravelModule;
import org.briarproject.bramble.test.TestDatabaseConfigModule;
import org.briarproject.bramble.test.TestFeatureFlagModule;
import org.briarproject.bramble.test.TestMailboxDirectoryModule;
import org.briarproject.bramble.test.TestSecureRandomModule;
import javax.inject.Singleton;
@@ -27,6 +28,7 @@ import dagger.Component;
DefaultWakefulIoExecutorModule.class,
TestDatabaseConfigModule.class,
TestFeatureFlagModule.class,
TestMailboxDirectoryModule.class,
RemovableDriveIntegrationTestModule.class,
RemovableDriveModule.class,
TestSecureRandomModule.class,

View File

@@ -13,6 +13,7 @@ import dagger.Module;
DefaultWakefulIoExecutorModule.class,
TestDatabaseConfigModule.class,
TestFeatureFlagModule.class,
TestMailboxDirectoryModule.class,
TestPluginConfigModule.class,
TestSecureRandomModule.class,
TimeTravelModule.class

View File

@@ -0,0 +1,18 @@
package org.briarproject.bramble.test;
import org.briarproject.bramble.api.mailbox.MailboxDirectory;
import java.io.File;
import dagger.Module;
import dagger.Provides;
@Module
public class TestMailboxDirectoryModule {
@Provides
@MailboxDirectory
File provideMailboxDirectory() {
return new File("mailbox");
}
}

View File

@@ -14,6 +14,7 @@ import org.briarproject.bramble.api.crypto.PublicKey;
import org.briarproject.bramble.api.db.DatabaseConfig;
import org.briarproject.bramble.api.event.EventBus;
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.BluetoothConstants;
import org.briarproject.bramble.api.plugin.LanTcpConstants;
@@ -27,6 +28,7 @@ import org.briarproject.bramble.api.plugin.simplex.SimplexPluginFactory;
import org.briarproject.bramble.api.reporting.DevConfig;
import org.briarproject.bramble.plugin.bluetooth.AndroidBluetoothPluginFactory;
import org.briarproject.bramble.plugin.file.AndroidRemovableDrivePluginFactory;
import org.briarproject.bramble.plugin.file.MailboxPluginFactory;
import org.briarproject.bramble.plugin.tcp.AndroidLanTcpPluginFactory;
import org.briarproject.bramble.plugin.tor.AndroidTorPluginFactory;
import org.briarproject.bramble.util.AndroidUtils;
@@ -63,6 +65,7 @@ import org.briarproject.briar.api.test.TestAvatarCreator;
import java.io.File;
import java.security.GeneralSecurityException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
@@ -76,7 +79,6 @@ import dagger.Provides;
import static android.content.Context.MODE_PRIVATE;
import static android.os.Build.VERSION.SDK_INT;
import static java.util.Arrays.asList;
import static java.util.Collections.emptyList;
import static java.util.Collections.singletonList;
import static java.util.Collections.singletonMap;
import static org.briarproject.bramble.api.plugin.TorConstants.DEFAULT_CONTROL_PORT;
@@ -156,6 +158,13 @@ public class AppModule {
return new AndroidDatabaseConfig(dbDir, keyDir, keyStrengthener);
}
@Provides
@Singleton
@MailboxDirectory
File provideMailboxDirectory(Application app) {
return app.getDir("mailbox", MODE_PRIVATE);
}
@Provides
@Singleton
@TorDirectory
@@ -190,7 +199,7 @@ public class AppModule {
PluginConfig providePluginConfig(AndroidBluetoothPluginFactory bluetooth,
AndroidTorPluginFactory tor, AndroidLanTcpPluginFactory lan,
AndroidRemovableDrivePluginFactory drive,
FeatureFlags featureFlags) {
MailboxPluginFactory mailbox, FeatureFlags featureFlags) {
@NotNullByDefault
PluginConfig pluginConfig = new PluginConfig() {
@@ -201,8 +210,10 @@ public class AppModule {
@Override
public Collection<SimplexPluginFactory> getSimplexFactories() {
if (SDK_INT >= 19) return singletonList(drive);
else return emptyList();
List<SimplexPluginFactory> simplex = new ArrayList<>();
if (featureFlags.shouldEnableMailbox()) simplex.add(mailbox);
if (SDK_INT >= 19) simplex.add(drive);
return simplex;
}
@Override

View File

@@ -6,6 +6,7 @@ import dagger.Provides
import org.briarproject.bramble.account.AccountModule
import org.briarproject.bramble.api.FeatureFlags
import org.briarproject.bramble.api.db.DatabaseConfig
import org.briarproject.bramble.api.mailbox.MailboxDirectory
import org.briarproject.bramble.api.plugin.PluginConfig
import org.briarproject.bramble.api.plugin.TorConstants.DEFAULT_CONTROL_PORT
import org.briarproject.bramble.api.plugin.TorConstants.DEFAULT_SOCKS_PORT
@@ -71,6 +72,12 @@ internal class HeadlessModule(private val appDir: File) {
return HeadlessDatabaseConfig(dbDir, keyDir)
}
@Provides
@MailboxDirectory
internal fun provideMailboxDirectory(): File {
return File(appDir, "mailbox")
}
@Provides
@TorDirectory
internal fun provideTorDirectory(): File {

View File

@@ -5,6 +5,7 @@ import dagger.Module
import dagger.Provides
import org.briarproject.bramble.account.AccountModule
import org.briarproject.bramble.api.db.DatabaseConfig
import org.briarproject.bramble.api.mailbox.MailboxDirectory
import org.briarproject.bramble.api.plugin.PluginConfig
import org.briarproject.bramble.api.plugin.TorConstants.DEFAULT_CONTROL_PORT
import org.briarproject.bramble.api.plugin.TorConstants.DEFAULT_SOCKS_PORT
@@ -68,6 +69,12 @@ internal class HeadlessTestModule(private val appDir: File) {
return HeadlessDatabaseConfig(dbDir, keyDir)
}
@Provides
@MailboxDirectory
internal fun provideMailboxDirectory(): File {
return File(appDir, "mailbox")
}
@Provides
@TorSocksPort
internal fun provideTorSocksPort(): Int = DEFAULT_SOCKS_PORT