From f7875c99b6961a6951c6dce2c11d2492e86b60b5 Mon Sep 17 00:00:00 2001 From: akwizgran Date: Wed, 5 May 2021 17:52:37 +0100 Subject: [PATCH 01/77] Add DB method for getting amount of data to sync. --- .../bramble/api/db/DatabaseComponent.java | 10 ++++++ .../org/briarproject/bramble/db/Database.java | 10 ++++++ .../bramble/db/DatabaseComponentImpl.java | 9 ++++++ .../briarproject/bramble/db/JdbcDatabase.java | 31 +++++++++++++++++++ .../bramble/db/DatabaseComponentImplTest.java | 24 +++++++++----- .../bramble/db/JdbcDatabaseTest.java | 22 +++++++++++++ 6 files changed, 98 insertions(+), 8 deletions(-) diff --git a/bramble-api/src/main/java/org/briarproject/bramble/api/db/DatabaseComponent.java b/bramble-api/src/main/java/org/briarproject/bramble/api/db/DatabaseComponent.java index b5cf1d617..3c79254d3 100644 --- a/bramble-api/src/main/java/org/briarproject/bramble/api/db/DatabaseComponent.java +++ b/bramble-api/src/main/java/org/briarproject/bramble/api/db/DatabaseComponent.java @@ -292,6 +292,16 @@ public interface DatabaseComponent extends TransactionManager { */ Message getMessage(Transaction txn, MessageId m) throws DbException; + /** + * Returns the total length, including headers, of any messages that are + * eligible to be sent to the given contact via a transport with the given + * max latency. + *

+ * Read-only. + */ + long getMessageBytesToSend(Transaction txn, ContactId c, int maxLatency) + throws DbException; + /** * Returns the IDs of all delivered messages in the given group. *

diff --git a/bramble-core/src/main/java/org/briarproject/bramble/db/Database.java b/bramble-core/src/main/java/org/briarproject/bramble/db/Database.java index b6832d1e9..ab291fa30 100644 --- a/bramble-core/src/main/java/org/briarproject/bramble/db/Database.java +++ b/bramble-core/src/main/java/org/briarproject/bramble/db/Database.java @@ -347,6 +347,16 @@ interface Database { */ Message getMessage(T txn, MessageId m) throws DbException; + /** + * Returns the total length, including headers, of any messages that are + * eligible to be sent to the given contact via a transport with the given + * max latency. + *

+ * Read-only. + */ + long getMessageBytesToSend(T txn, ContactId c, int maxLatency) + throws DbException; + /** * Returns the IDs and states of all dependencies of the given message. * For missing dependencies and dependencies in other groups, the state diff --git a/bramble-core/src/main/java/org/briarproject/bramble/db/DatabaseComponentImpl.java b/bramble-core/src/main/java/org/briarproject/bramble/db/DatabaseComponentImpl.java index 3bb052fe9..9cd1acbc9 100644 --- a/bramble-core/src/main/java/org/briarproject/bramble/db/DatabaseComponentImpl.java +++ b/bramble-core/src/main/java/org/briarproject/bramble/db/DatabaseComponentImpl.java @@ -569,6 +569,15 @@ class DatabaseComponentImpl implements DatabaseComponent { return db.getMessage(txn, m); } + @Override + public long getMessageBytesToSend(Transaction transaction, ContactId c, + int maxLatency) throws DbException { + T txn = unbox(transaction); + if (!db.containsContact(txn, c)) + throw new NoSuchContactException(); + return db.getMessageBytesToSend(txn, c, maxLatency); + } + @Override public Collection getMessageIds(Transaction transaction, GroupId g) throws DbException { diff --git a/bramble-core/src/main/java/org/briarproject/bramble/db/JdbcDatabase.java b/bramble-core/src/main/java/org/briarproject/bramble/db/JdbcDatabase.java index 72ceec594..af54d4e9d 100644 --- a/bramble-core/src/main/java/org/briarproject/bramble/db/JdbcDatabase.java +++ b/bramble-core/src/main/java/org/briarproject/bramble/db/JdbcDatabase.java @@ -1758,6 +1758,37 @@ abstract class JdbcDatabase implements Database { } } + @Override + public long getMessageBytesToSend(Connection txn, ContactId c, + int maxLatency) throws DbException { + long now = clock.currentTimeMillis(); + long eta = now + maxLatency; + PreparedStatement ps = null; + ResultSet rs = null; + try { + String sql = "SELECT SUM(length) FROM statuses" + + " WHERE contactId = ? AND state = ?" + + " AND groupShared = TRUE AND messageShared = TRUE" + + " AND deleted = FALSE AND seen = FALSE" + + " AND (expiry <= ? OR eta > ?)"; + ps = txn.prepareStatement(sql); + ps.setInt(1, c.getInt()); + ps.setInt(2, DELIVERED.getValue()); + ps.setLong(3, now); + ps.setLong(4, eta); + rs = ps.executeQuery(); + rs.next(); + long total = rs.getInt(1); + rs.close(); + ps.close(); + return total; + } catch (SQLException e) { + tryToClose(rs, LOG, WARNING); + tryToClose(ps, LOG, WARNING); + throw new DbException(e); + } + } + @Override public Collection getMessageIds(Connection txn, GroupId g) throws DbException { diff --git a/bramble-core/src/test/java/org/briarproject/bramble/db/DatabaseComponentImplTest.java b/bramble-core/src/test/java/org/briarproject/bramble/db/DatabaseComponentImplTest.java index 39615b2e5..4b53a781d 100644 --- a/bramble-core/src/test/java/org/briarproject/bramble/db/DatabaseComponentImplTest.java +++ b/bramble-core/src/test/java/org/briarproject/bramble/db/DatabaseComponentImplTest.java @@ -298,11 +298,11 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase { throws Exception { context.checking(new Expectations() {{ // Check whether the contact is in the DB (which it's not) - exactly(18).of(database).startTransaction(); + exactly(19).of(database).startTransaction(); will(returnValue(txn)); - exactly(18).of(database).containsContact(txn, contactId); + exactly(19).of(database).containsContact(txn, contactId); will(returnValue(false)); - exactly(18).of(database).abortTransaction(txn); + exactly(19).of(database).abortTransaction(txn); }}); DatabaseComponent db = createDatabaseComponent(database, eventBus, eventExecutor, shutdownManager); @@ -349,7 +349,7 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase { } try { - db.transaction(false, transaction -> + db.transaction(true, transaction -> db.getContact(transaction, contactId)); fail(); } catch (NoSuchContactException expected) { @@ -357,7 +357,15 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase { } try { - db.transaction(false, transaction -> + db.transaction(true, transaction -> + db.getMessageBytesToSend(transaction, contactId, 123)); + fail(); + } catch (NoSuchContactException expected) { + // Expected + } + + try { + db.transaction(true, transaction -> db.getMessageStatus(transaction, contactId, groupId)); fail(); } catch (NoSuchContactException expected) { @@ -365,7 +373,7 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase { } try { - db.transaction(false, transaction -> + db.transaction(true, transaction -> db.getMessageStatus(transaction, contactId, messageId)); fail(); } catch (NoSuchContactException expected) { @@ -373,7 +381,7 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase { } try { - db.transaction(false, transaction -> + db.transaction(true, transaction -> db.getGroupVisibility(transaction, contactId, groupId)); fail(); } catch (NoSuchContactException expected) { @@ -381,7 +389,7 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase { } try { - db.transaction(false, transaction -> + db.transaction(true, transaction -> db.getSyncVersions(transaction, contactId)); fail(); } catch (NoSuchContactException expected) { diff --git a/bramble-core/src/test/java/org/briarproject/bramble/db/JdbcDatabaseTest.java b/bramble-core/src/test/java/org/briarproject/bramble/db/JdbcDatabaseTest.java index 4dfec5fce..69bc97671 100644 --- a/bramble-core/src/test/java/org/briarproject/bramble/db/JdbcDatabaseTest.java +++ b/bramble-core/src/test/java/org/briarproject/bramble/db/JdbcDatabaseTest.java @@ -227,6 +227,8 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase { assertEquals(singletonList(messageId), ids); ids = db.getMessagesToOffer(txn, contactId, 100, MAX_LATENCY); assertEquals(singletonList(messageId), ids); + assertEquals(message.getRawLength(), + db.getMessageBytesToSend(txn, contactId, MAX_LATENCY)); // Changing the status to seen = true should make the message unsendable db.raiseSeenFlag(txn, contactId, messageId); @@ -234,6 +236,7 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase { assertTrue(ids.isEmpty()); ids = db.getMessagesToOffer(txn, contactId, 100, MAX_LATENCY); assertTrue(ids.isEmpty()); + assertEquals(0, db.getMessageBytesToSend(txn, contactId, MAX_LATENCY)); db.commitTransaction(txn); db.close(); @@ -258,6 +261,7 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase { assertTrue(ids.isEmpty()); ids = db.getMessagesToOffer(txn, contactId, 100, MAX_LATENCY); assertTrue(ids.isEmpty()); + assertEquals(0, db.getMessageBytesToSend(txn, contactId, MAX_LATENCY)); // Marking the message delivered should make it sendable db.setMessageState(txn, messageId, DELIVERED); @@ -265,6 +269,8 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase { assertEquals(singletonList(messageId), ids); ids = db.getMessagesToOffer(txn, contactId, 100, MAX_LATENCY); assertEquals(singletonList(messageId), ids); + assertEquals(message.getRawLength(), + db.getMessageBytesToSend(txn, contactId, MAX_LATENCY)); // Marking the message invalid should make it unsendable db.setMessageState(txn, messageId, INVALID); @@ -272,6 +278,7 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase { assertTrue(ids.isEmpty()); ids = db.getMessagesToOffer(txn, contactId, 100, MAX_LATENCY); assertTrue(ids.isEmpty()); + assertEquals(0, db.getMessageBytesToSend(txn, contactId, MAX_LATENCY)); // Marking the message pending should make it unsendable db.setMessageState(txn, messageId, PENDING); @@ -279,6 +286,7 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase { assertTrue(ids.isEmpty()); ids = db.getMessagesToOffer(txn, contactId, 100, MAX_LATENCY); assertTrue(ids.isEmpty()); + assertEquals(0, db.getMessageBytesToSend(txn, contactId, MAX_LATENCY)); db.commitTransaction(txn); db.close(); @@ -302,6 +310,7 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase { assertTrue(ids.isEmpty()); ids = db.getMessagesToOffer(txn, contactId, 100, MAX_LATENCY); assertTrue(ids.isEmpty()); + assertEquals(0, db.getMessageBytesToSend(txn, contactId, MAX_LATENCY)); // Making the group visible should not make the message sendable db.addGroupVisibility(txn, contactId, groupId, false); @@ -309,6 +318,7 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase { assertTrue(ids.isEmpty()); ids = db.getMessagesToOffer(txn, contactId, 100, MAX_LATENCY); assertTrue(ids.isEmpty()); + assertEquals(0, db.getMessageBytesToSend(txn, contactId, MAX_LATENCY)); // Sharing the group should make the message sendable db.setGroupVisibility(txn, contactId, groupId, true); @@ -316,6 +326,8 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase { assertEquals(singletonList(messageId), ids); ids = db.getMessagesToOffer(txn, contactId, 100, MAX_LATENCY); assertEquals(singletonList(messageId), ids); + assertEquals(message.getRawLength(), + db.getMessageBytesToSend(txn, contactId, MAX_LATENCY)); // Unsharing the group should make the message unsendable db.setGroupVisibility(txn, contactId, groupId, false); @@ -323,6 +335,7 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase { assertTrue(ids.isEmpty()); ids = db.getMessagesToOffer(txn, contactId, 100, MAX_LATENCY); assertTrue(ids.isEmpty()); + assertEquals(0, db.getMessageBytesToSend(txn, contactId, MAX_LATENCY)); // Making the group invisible should make the message unsendable db.removeGroupVisibility(txn, contactId, groupId); @@ -330,6 +343,7 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase { assertTrue(ids.isEmpty()); ids = db.getMessagesToOffer(txn, contactId, 100, MAX_LATENCY); assertTrue(ids.isEmpty()); + assertEquals(0, db.getMessageBytesToSend(txn, contactId, MAX_LATENCY)); db.commitTransaction(txn); db.close(); @@ -354,6 +368,7 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase { assertTrue(ids.isEmpty()); ids = db.getMessagesToOffer(txn, contactId, 100, MAX_LATENCY); assertTrue(ids.isEmpty()); + assertEquals(0, db.getMessageBytesToSend(txn, contactId, MAX_LATENCY)); // Sharing the message should make it sendable db.setMessageShared(txn, messageId, true); @@ -361,6 +376,8 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase { assertEquals(singletonList(messageId), ids); ids = db.getMessagesToOffer(txn, contactId, 100, MAX_LATENCY); assertEquals(singletonList(messageId), ids); + assertEquals(message.getRawLength(), + db.getMessageBytesToSend(txn, contactId, MAX_LATENCY)); db.commitTransaction(txn); db.close(); @@ -384,10 +401,15 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase { db.getMessagesToSend(txn, contactId, message.getRawLength() - 1, MAX_LATENCY); assertTrue(ids.isEmpty()); + assertEquals(message.getRawLength(), + db.getMessageBytesToSend(txn, contactId, MAX_LATENCY)); + // The message is just the right size to send ids = db.getMessagesToSend(txn, contactId, message.getRawLength(), MAX_LATENCY); assertEquals(singletonList(messageId), ids); + assertEquals(message.getRawLength(), + db.getMessageBytesToSend(txn, contactId, MAX_LATENCY)); db.commitTransaction(txn); db.close(); From 588e05ce83cb23fa4acc43ea2732555280e082fd Mon Sep 17 00:00:00 2001 From: akwizgran Date: Thu, 6 May 2021 16:18:37 +0100 Subject: [PATCH 02/77] Update MessagesSentEvent to include amount of data sent. --- .../bramble/api/sync/event/MessagesSentEvent.java | 8 +++++++- .../bramble/db/DatabaseComponentImpl.java | 14 ++++++++++---- .../messaging/MessagingControllerImplTest.kt | 4 ++-- 3 files changed, 19 insertions(+), 7 deletions(-) diff --git a/bramble-api/src/main/java/org/briarproject/bramble/api/sync/event/MessagesSentEvent.java b/bramble-api/src/main/java/org/briarproject/bramble/api/sync/event/MessagesSentEvent.java index 1f392417d..179907a3a 100644 --- a/bramble-api/src/main/java/org/briarproject/bramble/api/sync/event/MessagesSentEvent.java +++ b/bramble-api/src/main/java/org/briarproject/bramble/api/sync/event/MessagesSentEvent.java @@ -18,11 +18,13 @@ public class MessagesSentEvent extends Event { private final ContactId contactId; private final Collection messageIds; + private final long totalLength; public MessagesSentEvent(ContactId contactId, - Collection messageIds) { + Collection messageIds, long totalLength) { this.contactId = contactId; this.messageIds = messageIds; + this.totalLength = totalLength; } public ContactId getContactId() { @@ -32,4 +34,8 @@ public class MessagesSentEvent extends Event { public Collection getMessageIds() { return messageIds; } + + public long getTotalLength() { + return totalLength; + } } diff --git a/bramble-core/src/main/java/org/briarproject/bramble/db/DatabaseComponentImpl.java b/bramble-core/src/main/java/org/briarproject/bramble/db/DatabaseComponentImpl.java index 3bb052fe9..f3e818644 100644 --- a/bramble-core/src/main/java/org/briarproject/bramble/db/DatabaseComponentImpl.java +++ b/bramble-core/src/main/java/org/briarproject/bramble/db/DatabaseComponentImpl.java @@ -415,14 +415,17 @@ class DatabaseComponentImpl implements DatabaseComponent { throw new NoSuchContactException(); Collection ids = db.getMessagesToSend(txn, c, maxLength, maxLatency); + long totalLength = 0; List messages = new ArrayList<>(ids.size()); for (MessageId m : ids) { - messages.add(db.getMessage(txn, m)); + Message message = db.getMessage(txn, m); + totalLength += message.getRawLength(); + messages.add(message); db.updateExpiryTimeAndEta(txn, c, m, maxLatency); } if (ids.isEmpty()) return null; db.lowerRequestedFlag(txn, c, ids); - transaction.attach(new MessagesSentEvent(c, ids)); + transaction.attach(new MessagesSentEvent(c, ids, totalLength)); return messages; } @@ -467,14 +470,17 @@ class DatabaseComponentImpl implements DatabaseComponent { throw new NoSuchContactException(); Collection ids = db.getRequestedMessagesToSend(txn, c, maxLength, maxLatency); + long totalLength = 0; List messages = new ArrayList<>(ids.size()); for (MessageId m : ids) { - messages.add(db.getMessage(txn, m)); + Message message = db.getMessage(txn, m); + totalLength += message.getRawLength(); + messages.add(message); db.updateExpiryTimeAndEta(txn, c, m, maxLatency); } if (ids.isEmpty()) return null; db.lowerRequestedFlag(txn, c, ids); - transaction.attach(new MessagesSentEvent(c, ids)); + transaction.attach(new MessagesSentEvent(c, ids, totalLength)); return messages; } diff --git a/briar-headless/src/test/java/org/briarproject/briar/headless/messaging/MessagingControllerImplTest.kt b/briar-headless/src/test/java/org/briarproject/briar/headless/messaging/MessagingControllerImplTest.kt index fe0120322..588caf0db 100644 --- a/briar-headless/src/test/java/org/briarproject/briar/headless/messaging/MessagingControllerImplTest.kt +++ b/briar-headless/src/test/java/org/briarproject/briar/headless/messaging/MessagingControllerImplTest.kt @@ -136,7 +136,7 @@ internal class MessagingControllerImplTest : ControllerTest() { val messageId1 = MessageId(getRandomId()) val messageId2 = MessageId(getRandomId()) val messageIds = listOf(messageId1, messageId2) - val event = MessagesSentEvent(contact.id, messageIds) + val event = MessagesSentEvent(contact.id, messageIds, 1234) every { webSocketController.sendEvent( @@ -274,7 +274,7 @@ internal class MessagingControllerImplTest : ControllerTest() { val messageId1 = MessageId(getRandomId()) val messageId2 = MessageId(getRandomId()) val messageIds = listOf(messageId1, messageId2) - val event = MessagesSentEvent(contact.id, messageIds) + val event = MessagesSentEvent(contact.id, messageIds, 1234) val json = """ { From 428269b31244dffb0200cee9fe1d194fa592ea19 Mon Sep 17 00:00:00 2001 From: akwizgran Date: Thu, 6 May 2021 16:47:11 +0100 Subject: [PATCH 03/77] Add removable drive plugin. --- .../api/plugin/RemovableDriveConstants.java | 6 ++ .../plugin/file/RemovableDrivePlugin.java | 96 +++++++++++++++++++ .../file/RemovableDrivePluginFactory.java | 36 +++++++ 3 files changed, 138 insertions(+) create mode 100644 bramble-api/src/main/java/org/briarproject/bramble/api/plugin/RemovableDriveConstants.java create mode 100644 bramble-core/src/main/java/org/briarproject/bramble/plugin/file/RemovableDrivePlugin.java create mode 100644 bramble-core/src/main/java/org/briarproject/bramble/plugin/file/RemovableDrivePluginFactory.java diff --git a/bramble-api/src/main/java/org/briarproject/bramble/api/plugin/RemovableDriveConstants.java b/bramble-api/src/main/java/org/briarproject/bramble/api/plugin/RemovableDriveConstants.java new file mode 100644 index 000000000..d9798721e --- /dev/null +++ b/bramble-api/src/main/java/org/briarproject/bramble/api/plugin/RemovableDriveConstants.java @@ -0,0 +1,6 @@ +package org.briarproject.bramble.api.plugin; + +public interface RemovableDriveConstants { + + TransportId ID = new TransportId("org.briarproject.bramble.drive"); +} diff --git a/bramble-core/src/main/java/org/briarproject/bramble/plugin/file/RemovableDrivePlugin.java b/bramble-core/src/main/java/org/briarproject/bramble/plugin/file/RemovableDrivePlugin.java new file mode 100644 index 000000000..3cb3d6c98 --- /dev/null +++ b/bramble-core/src/main/java/org/briarproject/bramble/plugin/file/RemovableDrivePlugin.java @@ -0,0 +1,96 @@ +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.TransportId; +import org.briarproject.bramble.api.properties.TransportProperties; + +import java.io.File; +import java.util.Collection; +import java.util.logging.Logger; + +import javax.annotation.concurrent.Immutable; + +import static java.util.logging.Level.INFO; +import static java.util.logging.Logger.getLogger; +import static org.briarproject.bramble.api.plugin.Plugin.State.ACTIVE; +import static org.briarproject.bramble.api.plugin.RemovableDriveConstants.ID; + +@Immutable +@NotNullByDefault +class RemovableDrivePlugin extends FilePlugin { + + private static final Logger LOG = + getLogger(RemovableDrivePlugin.class.getName()); + + RemovableDrivePlugin(PluginCallback callback, int maxLatency) { + super(callback, maxLatency); + } + + @Override + protected void writerFinished(File f, boolean exception) { + if (LOG.isLoggable(INFO)) { + LOG.info("Writer finished, exception: " + exception); + } + } + + @Override + protected void readerFinished(File f, boolean exception, + boolean recognised) { + if (LOG.isLoggable(INFO)) { + LOG.info("Reader finished, exception: " + exception + + ", recognised: " + recognised); + } + // Try to delete the file if the read finished successfully + if (recognised && !exception && !f.delete()) { + LOG.info("Failed to delete recognised file"); + } + } + + @Override + public TransportId getId() { + return ID; + } + + @Override + public void start() { + } + + @Override + public void stop() { + } + + @Override + public State getState() { + return ACTIVE; + } + + @Override + public int getReasonsDisabled() { + return 0; + } + + @Override + public int getMaxIdleTime() { + // Unused for simplex transports + throw new UnsupportedOperationException(); + } + + @Override + public boolean shouldPoll() { + return false; + } + + @Override + public int getPollingInterval() { + throw new UnsupportedOperationException(); + } + + @Override + public void poll( + Collection> properties) { + throw new UnsupportedOperationException(); + } +} diff --git a/bramble-core/src/main/java/org/briarproject/bramble/plugin/file/RemovableDrivePluginFactory.java b/bramble-core/src/main/java/org/briarproject/bramble/plugin/file/RemovableDrivePluginFactory.java new file mode 100644 index 000000000..6c597159c --- /dev/null +++ b/bramble-core/src/main/java/org/briarproject/bramble/plugin/file/RemovableDrivePluginFactory.java @@ -0,0 +1,36 @@ +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.annotation.concurrent.Immutable; + +import static java.util.concurrent.TimeUnit.DAYS; +import static org.briarproject.bramble.api.plugin.RemovableDriveConstants.ID; + +@Immutable +@NotNullByDefault +public class RemovableDrivePluginFactory implements SimplexPluginFactory { + + private final int MAX_LATENCY = (int) DAYS.toMillis(14); + + @Override + public TransportId getId() { + return ID; + } + + @Override + public int getMaxLatency() { + return MAX_LATENCY; + } + + @Nullable + @Override + public SimplexPlugin createPlugin(PluginCallback callback) { + return new RemovableDrivePlugin(callback, MAX_LATENCY); + } +} From c999f05cc7e3a84e535838ca805e0578545366d9 Mon Sep 17 00:00:00 2001 From: akwizgran Date: Thu, 6 May 2021 16:56:09 +0100 Subject: [PATCH 04/77] Configure removable drive plugin for Android. --- .../bramble/plugin/file/RemovableDrivePluginFactory.java | 5 +++++ .../java/org/briarproject/briar/android/AppModule.java | 7 ++++--- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/bramble-core/src/main/java/org/briarproject/bramble/plugin/file/RemovableDrivePluginFactory.java b/bramble-core/src/main/java/org/briarproject/bramble/plugin/file/RemovableDrivePluginFactory.java index 6c597159c..781902678 100644 --- a/bramble-core/src/main/java/org/briarproject/bramble/plugin/file/RemovableDrivePluginFactory.java +++ b/bramble-core/src/main/java/org/briarproject/bramble/plugin/file/RemovableDrivePluginFactory.java @@ -8,6 +8,7 @@ import org.briarproject.bramble.api.plugin.simplex.SimplexPluginFactory; import javax.annotation.Nullable; import javax.annotation.concurrent.Immutable; +import javax.inject.Inject; import static java.util.concurrent.TimeUnit.DAYS; import static org.briarproject.bramble.api.plugin.RemovableDriveConstants.ID; @@ -18,6 +19,10 @@ public class RemovableDrivePluginFactory implements SimplexPluginFactory { private final int MAX_LATENCY = (int) DAYS.toMillis(14); + @Inject + RemovableDrivePluginFactory() { + } + @Override public TransportId getId() { return ID; diff --git a/briar-android/src/main/java/org/briarproject/briar/android/AppModule.java b/briar-android/src/main/java/org/briarproject/briar/android/AppModule.java index 1eeded75f..6a29a00d9 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/AppModule.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/AppModule.java @@ -24,6 +24,7 @@ import org.briarproject.bramble.api.plugin.duplex.DuplexPluginFactory; 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.RemovableDrivePluginFactory; import org.briarproject.bramble.plugin.tcp.AndroidLanTcpPluginFactory; import org.briarproject.bramble.plugin.tor.AndroidTorPluginFactory; import org.briarproject.bramble.util.AndroidUtils; @@ -67,7 +68,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.reporting.ReportingConstants.DEV_ONION_ADDRESS; @@ -150,7 +150,8 @@ public class AppModule { @Provides PluginConfig providePluginConfig(AndroidBluetoothPluginFactory bluetooth, - AndroidTorPluginFactory tor, AndroidLanTcpPluginFactory lan) { + AndroidTorPluginFactory tor, AndroidLanTcpPluginFactory lan, + RemovableDrivePluginFactory drive) { @NotNullByDefault PluginConfig pluginConfig = new PluginConfig() { @@ -161,7 +162,7 @@ public class AppModule { @Override public Collection getSimplexFactories() { - return emptyList(); + return singletonList(drive); } @Override From 0bc06248edfd5c2924a9d495121a870d5bc892bd Mon Sep 17 00:00:00 2001 From: akwizgran Date: Thu, 6 May 2021 16:58:00 +0100 Subject: [PATCH 05/77] Clean up plugin injection code, remove unused module. --- .../AndroidBluetoothPluginFactory.java | 2 +- .../tcp/AndroidLanTcpPluginFactory.java | 2 +- .../plugin/tor/AndroidTorPluginFactory.java | 2 +- .../bramble/plugin/DesktopPluginModule.java | 61 ------------------- .../plugin/tor/UnixTorPluginFactory.java | 2 +- .../briarproject/briar/android/AppModule.java | 1 + .../briar/headless/HeadlessModule.kt | 1 + .../briar/headless/HeadlessTestModule.kt | 1 + 8 files changed, 7 insertions(+), 65 deletions(-) delete mode 100644 bramble-java/src/main/java/org/briarproject/bramble/plugin/DesktopPluginModule.java diff --git a/bramble-android/src/main/java/org/briarproject/bramble/plugin/bluetooth/AndroidBluetoothPluginFactory.java b/bramble-android/src/main/java/org/briarproject/bramble/plugin/bluetooth/AndroidBluetoothPluginFactory.java index 96f094d72..b7c7e5731 100644 --- a/bramble-android/src/main/java/org/briarproject/bramble/plugin/bluetooth/AndroidBluetoothPluginFactory.java +++ b/bramble-android/src/main/java/org/briarproject/bramble/plugin/bluetooth/AndroidBluetoothPluginFactory.java @@ -47,7 +47,7 @@ public class AndroidBluetoothPluginFactory implements DuplexPluginFactory { private final BackoffFactory backoffFactory; @Inject - public AndroidBluetoothPluginFactory(@IoExecutor Executor ioExecutor, + AndroidBluetoothPluginFactory(@IoExecutor Executor ioExecutor, @WakefulIoExecutor Executor wakefulIoExecutor, AndroidExecutor androidExecutor, AndroidWakeLockManager wakeLockManager, diff --git a/bramble-android/src/main/java/org/briarproject/bramble/plugin/tcp/AndroidLanTcpPluginFactory.java b/bramble-android/src/main/java/org/briarproject/bramble/plugin/tcp/AndroidLanTcpPluginFactory.java index beedd97a4..01922de50 100644 --- a/bramble-android/src/main/java/org/briarproject/bramble/plugin/tcp/AndroidLanTcpPluginFactory.java +++ b/bramble-android/src/main/java/org/briarproject/bramble/plugin/tcp/AndroidLanTcpPluginFactory.java @@ -37,7 +37,7 @@ public class AndroidLanTcpPluginFactory implements DuplexPluginFactory { private final Application app; @Inject - public AndroidLanTcpPluginFactory(@IoExecutor Executor ioExecutor, + AndroidLanTcpPluginFactory(@IoExecutor Executor ioExecutor, @WakefulIoExecutor Executor wakefulIoExecutor, EventBus eventBus, BackoffFactory backoffFactory, diff --git a/bramble-android/src/main/java/org/briarproject/bramble/plugin/tor/AndroidTorPluginFactory.java b/bramble-android/src/main/java/org/briarproject/bramble/plugin/tor/AndroidTorPluginFactory.java index 2a8bedfff..87552ed73 100644 --- a/bramble-android/src/main/java/org/briarproject/bramble/plugin/tor/AndroidTorPluginFactory.java +++ b/bramble-android/src/main/java/org/briarproject/bramble/plugin/tor/AndroidTorPluginFactory.java @@ -58,7 +58,7 @@ public class AndroidTorPluginFactory implements DuplexPluginFactory { private final File torDirectory; @Inject - public AndroidTorPluginFactory(@IoExecutor Executor ioExecutor, + AndroidTorPluginFactory(@IoExecutor Executor ioExecutor, @WakefulIoExecutor Executor wakefulIoExecutor, Application app, NetworkManager networkManager, diff --git a/bramble-java/src/main/java/org/briarproject/bramble/plugin/DesktopPluginModule.java b/bramble-java/src/main/java/org/briarproject/bramble/plugin/DesktopPluginModule.java deleted file mode 100644 index 6a33b933a..000000000 --- a/bramble-java/src/main/java/org/briarproject/bramble/plugin/DesktopPluginModule.java +++ /dev/null @@ -1,61 +0,0 @@ -package org.briarproject.bramble.plugin; - -import org.briarproject.bramble.api.nullsafety.NotNullByDefault; -import org.briarproject.bramble.api.plugin.BluetoothConstants; -import org.briarproject.bramble.api.plugin.LanTcpConstants; -import org.briarproject.bramble.api.plugin.PluginConfig; -import org.briarproject.bramble.api.plugin.TransportId; -import org.briarproject.bramble.api.plugin.duplex.DuplexPluginFactory; -import org.briarproject.bramble.api.plugin.simplex.SimplexPluginFactory; -import org.briarproject.bramble.plugin.bluetooth.JavaBluetoothPluginFactory; -import org.briarproject.bramble.plugin.modem.ModemPluginFactory; -import org.briarproject.bramble.plugin.tcp.LanTcpPluginFactory; -import org.briarproject.bramble.plugin.tcp.WanTcpPluginFactory; - -import java.util.Collection; -import java.util.List; -import java.util.Map; - -import dagger.Module; -import dagger.Provides; - -import static java.util.Arrays.asList; -import static java.util.Collections.emptyList; -import static java.util.Collections.singletonList; -import static java.util.Collections.singletonMap; - -@Module -public class DesktopPluginModule extends PluginModule { - - @Provides - PluginConfig getPluginConfig(JavaBluetoothPluginFactory bluetooth, - ModemPluginFactory modem, LanTcpPluginFactory lan, - WanTcpPluginFactory wan) { - @NotNullByDefault - PluginConfig pluginConfig = new PluginConfig() { - - @Override - public Collection getDuplexFactories() { - return asList(bluetooth, modem, lan, wan); - } - - @Override - public Collection getSimplexFactories() { - return emptyList(); - } - - @Override - public boolean shouldPoll() { - return true; - } - - @Override - public Map> getTransportPreferences() { - // Prefer LAN to Bluetooth - return singletonMap(BluetoothConstants.ID, - singletonList(LanTcpConstants.ID)); - } - }; - return pluginConfig; - } -} diff --git a/bramble-java/src/main/java/org/briarproject/bramble/plugin/tor/UnixTorPluginFactory.java b/bramble-java/src/main/java/org/briarproject/bramble/plugin/tor/UnixTorPluginFactory.java index d5efba41c..e621ad1dd 100644 --- a/bramble-java/src/main/java/org/briarproject/bramble/plugin/tor/UnixTorPluginFactory.java +++ b/bramble-java/src/main/java/org/briarproject/bramble/plugin/tor/UnixTorPluginFactory.java @@ -55,7 +55,7 @@ public class UnixTorPluginFactory implements DuplexPluginFactory { private final File torDirectory; @Inject - public UnixTorPluginFactory(@IoExecutor Executor ioExecutor, + UnixTorPluginFactory(@IoExecutor Executor ioExecutor, @IoExecutor Executor wakefulIoExecutor, NetworkManager networkManager, LocationUtils locationUtils, diff --git a/briar-android/src/main/java/org/briarproject/briar/android/AppModule.java b/briar-android/src/main/java/org/briarproject/briar/android/AppModule.java index 6a29a00d9..f5d13cf35 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/AppModule.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/AppModule.java @@ -149,6 +149,7 @@ public class AppModule { } @Provides + @Singleton PluginConfig providePluginConfig(AndroidBluetoothPluginFactory bluetooth, AndroidTorPluginFactory tor, AndroidLanTcpPluginFactory lan, RemovableDrivePluginFactory drive) { diff --git a/briar-headless/src/main/java/org/briarproject/briar/headless/HeadlessModule.kt b/briar-headless/src/main/java/org/briarproject/briar/headless/HeadlessModule.kt index 58c217d4a..90afe01c5 100644 --- a/briar-headless/src/main/java/org/briarproject/briar/headless/HeadlessModule.kt +++ b/briar-headless/src/main/java/org/briarproject/briar/headless/HeadlessModule.kt @@ -74,6 +74,7 @@ internal class HeadlessModule(private val appDir: File) { } @Provides + @Singleton internal fun providePluginConfig(tor: UnixTorPluginFactory): PluginConfig { val duplex: List = if (isLinux() || isMac()) listOf(tor) else emptyList() diff --git a/briar-headless/src/test/java/org/briarproject/briar/headless/HeadlessTestModule.kt b/briar-headless/src/test/java/org/briarproject/briar/headless/HeadlessTestModule.kt index c76212827..f31caf2e3 100644 --- a/briar-headless/src/test/java/org/briarproject/briar/headless/HeadlessTestModule.kt +++ b/briar-headless/src/test/java/org/briarproject/briar/headless/HeadlessTestModule.kt @@ -64,6 +64,7 @@ internal class HeadlessTestModule(private val appDir: File) { } @Provides + @Singleton internal fun providePluginConfig(): PluginConfig { return object : PluginConfig { override fun getDuplexFactories(): Collection = emptyList() From 7eccf7dac170b7d300036727d8684089ee4020cb Mon Sep 17 00:00:00 2001 From: akwizgran Date: Fri, 7 May 2021 17:36:10 +0100 Subject: [PATCH 06/77] Decouple removable drive plugin from java.io.File for portability. --- .../file/AbstractRemovableDrivePlugin.java | 114 ++++++++++++++++++ .../plugin/file/RemovableDrivePlugin.java | 94 +++------------ .../file/RemovableDrivePluginFactory.java | 2 +- .../file/TransportInputStreamReader.java | 34 ++++++ .../file/TransportOutputStreamWriter.java | 47 ++++++++ 5 files changed, 214 insertions(+), 77 deletions(-) create mode 100644 bramble-core/src/main/java/org/briarproject/bramble/plugin/file/AbstractRemovableDrivePlugin.java create mode 100644 bramble-core/src/main/java/org/briarproject/bramble/plugin/file/TransportInputStreamReader.java create mode 100644 bramble-core/src/main/java/org/briarproject/bramble/plugin/file/TransportOutputStreamWriter.java 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 new file mode 100644 index 000000000..3fdc9c908 --- /dev/null +++ b/bramble-core/src/main/java/org/briarproject/bramble/plugin/file/AbstractRemovableDrivePlugin.java @@ -0,0 +1,114 @@ +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.TransportConnectionReader; +import org.briarproject.bramble.api.plugin.TransportConnectionWriter; +import org.briarproject.bramble.api.plugin.TransportId; +import org.briarproject.bramble.api.plugin.simplex.SimplexPlugin; +import org.briarproject.bramble.api.properties.TransportProperties; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.Collection; +import java.util.logging.Logger; + +import javax.annotation.concurrent.Immutable; + +import static java.util.logging.Level.WARNING; +import static java.util.logging.Logger.getLogger; +import static org.briarproject.bramble.api.plugin.Plugin.State.ACTIVE; +import static org.briarproject.bramble.api.plugin.RemovableDriveConstants.ID; +import static org.briarproject.bramble.util.LogUtils.logException; + +@Immutable +@NotNullByDefault +abstract class AbstractRemovableDrivePlugin implements SimplexPlugin { + + private static final Logger LOG = + getLogger(AbstractRemovableDrivePlugin.class.getName()); + + private final int maxLatency; + + abstract InputStream openInputStream(TransportProperties p) + throws IOException; + + abstract OutputStream openOutputStream(TransportProperties p) + throws IOException; + + AbstractRemovableDrivePlugin(int maxLatency) { + this.maxLatency = maxLatency; + } + + @Override + public TransportId getId() { + return ID; + } + + @Override + public int getMaxLatency() { + return maxLatency; + } + + @Override + public int getMaxIdleTime() { + // Unused for simplex transports + throw new UnsupportedOperationException(); + } + + @Override + public void start() { + } + + @Override + public void stop() { + } + + @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> properties) { + throw new UnsupportedOperationException(); + } + + @Override + public TransportConnectionReader createReader(TransportProperties p) { + try { + return new TransportInputStreamReader(openInputStream(p)); + } catch (IOException e) { + logException(LOG, WARNING, e); + return null; + } + } + + @Override + public TransportConnectionWriter createWriter(TransportProperties p) { + try { + return new TransportOutputStreamWriter(this, openOutputStream(p)); + } catch (IOException e) { + logException(LOG, WARNING, e); + return null; + } + } +} diff --git a/bramble-core/src/main/java/org/briarproject/bramble/plugin/file/RemovableDrivePlugin.java b/bramble-core/src/main/java/org/briarproject/bramble/plugin/file/RemovableDrivePlugin.java index 3cb3d6c98..b59f4f85f 100644 --- a/bramble-core/src/main/java/org/briarproject/bramble/plugin/file/RemovableDrivePlugin.java +++ b/bramble-core/src/main/java/org/briarproject/bramble/plugin/file/RemovableDrivePlugin.java @@ -1,96 +1,38 @@ 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.TransportId; import org.briarproject.bramble.api.properties.TransportProperties; -import java.io.File; -import java.util.Collection; -import java.util.logging.Logger; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; import javax.annotation.concurrent.Immutable; -import static java.util.logging.Level.INFO; -import static java.util.logging.Logger.getLogger; -import static org.briarproject.bramble.api.plugin.Plugin.State.ACTIVE; -import static org.briarproject.bramble.api.plugin.RemovableDriveConstants.ID; +import static org.briarproject.bramble.api.plugin.FileConstants.PROP_PATH; +import static org.briarproject.bramble.util.StringUtils.isNullOrEmpty; @Immutable @NotNullByDefault -class RemovableDrivePlugin extends FilePlugin { +class RemovableDrivePlugin extends AbstractRemovableDrivePlugin { - private static final Logger LOG = - getLogger(RemovableDrivePlugin.class.getName()); - - RemovableDrivePlugin(PluginCallback callback, int maxLatency) { - super(callback, maxLatency); + RemovableDrivePlugin(int maxLatency) { + super(maxLatency); } @Override - protected void writerFinished(File f, boolean exception) { - if (LOG.isLoggable(INFO)) { - LOG.info("Writer finished, exception: " + exception); - } + InputStream openInputStream(TransportProperties p) throws IOException { + String path = p.get(PROP_PATH); + if (isNullOrEmpty(path)) throw new IllegalArgumentException(); + return new FileInputStream(path); } @Override - protected void readerFinished(File f, boolean exception, - boolean recognised) { - if (LOG.isLoggable(INFO)) { - LOG.info("Reader finished, exception: " + exception - + ", recognised: " + recognised); - } - // Try to delete the file if the read finished successfully - if (recognised && !exception && !f.delete()) { - LOG.info("Failed to delete recognised file"); - } - } - - @Override - public TransportId getId() { - return ID; - } - - @Override - public void start() { - } - - @Override - public void stop() { - } - - @Override - public State getState() { - return ACTIVE; - } - - @Override - public int getReasonsDisabled() { - return 0; - } - - @Override - public int getMaxIdleTime() { - // Unused for simplex transports - throw new UnsupportedOperationException(); - } - - @Override - public boolean shouldPoll() { - return false; - } - - @Override - public int getPollingInterval() { - throw new UnsupportedOperationException(); - } - - @Override - public void poll( - Collection> properties) { - throw new UnsupportedOperationException(); + OutputStream openOutputStream(TransportProperties p) throws IOException { + String path = p.get(PROP_PATH); + if (isNullOrEmpty(path)) throw new IllegalArgumentException(); + return new FileOutputStream(path); } } diff --git a/bramble-core/src/main/java/org/briarproject/bramble/plugin/file/RemovableDrivePluginFactory.java b/bramble-core/src/main/java/org/briarproject/bramble/plugin/file/RemovableDrivePluginFactory.java index 781902678..5d833805d 100644 --- a/bramble-core/src/main/java/org/briarproject/bramble/plugin/file/RemovableDrivePluginFactory.java +++ b/bramble-core/src/main/java/org/briarproject/bramble/plugin/file/RemovableDrivePluginFactory.java @@ -36,6 +36,6 @@ public class RemovableDrivePluginFactory implements SimplexPluginFactory { @Nullable @Override public SimplexPlugin createPlugin(PluginCallback callback) { - return new RemovableDrivePlugin(callback, MAX_LATENCY); + return new RemovableDrivePlugin(MAX_LATENCY); } } diff --git a/bramble-core/src/main/java/org/briarproject/bramble/plugin/file/TransportInputStreamReader.java b/bramble-core/src/main/java/org/briarproject/bramble/plugin/file/TransportInputStreamReader.java new file mode 100644 index 000000000..8971136c9 --- /dev/null +++ b/bramble-core/src/main/java/org/briarproject/bramble/plugin/file/TransportInputStreamReader.java @@ -0,0 +1,34 @@ +package org.briarproject.bramble.plugin.file; + +import org.briarproject.bramble.api.nullsafety.NotNullByDefault; +import org.briarproject.bramble.api.plugin.TransportConnectionReader; + +import java.io.InputStream; +import java.util.logging.Logger; + +import static java.util.logging.Level.WARNING; +import static java.util.logging.Logger.getLogger; +import static org.briarproject.bramble.util.IoUtils.tryToClose; + +@NotNullByDefault +class TransportInputStreamReader implements TransportConnectionReader { + + private static final Logger LOG = + getLogger(TransportInputStreamReader.class.getName()); + + private final InputStream in; + + TransportInputStreamReader(InputStream in) { + this.in = in; + } + + @Override + public InputStream getInputStream() { + return in; + } + + @Override + public void dispose(boolean exception, boolean recognised) { + tryToClose(in, LOG, WARNING); + } +} diff --git a/bramble-core/src/main/java/org/briarproject/bramble/plugin/file/TransportOutputStreamWriter.java b/bramble-core/src/main/java/org/briarproject/bramble/plugin/file/TransportOutputStreamWriter.java new file mode 100644 index 000000000..be40fbca0 --- /dev/null +++ b/bramble-core/src/main/java/org/briarproject/bramble/plugin/file/TransportOutputStreamWriter.java @@ -0,0 +1,47 @@ +package org.briarproject.bramble.plugin.file; + +import org.briarproject.bramble.api.nullsafety.NotNullByDefault; +import org.briarproject.bramble.api.plugin.Plugin; +import org.briarproject.bramble.api.plugin.TransportConnectionWriter; + +import java.io.OutputStream; +import java.util.logging.Logger; + +import static java.util.logging.Level.WARNING; +import static java.util.logging.Logger.getLogger; +import static org.briarproject.bramble.util.IoUtils.tryToClose; + +@NotNullByDefault +class TransportOutputStreamWriter implements TransportConnectionWriter { + + private static final Logger LOG = + getLogger(TransportOutputStreamWriter.class.getName()); + + private final Plugin plugin; + private final OutputStream out; + + TransportOutputStreamWriter(Plugin plugin, OutputStream out) { + this.plugin = plugin; + this.out = out; + } + + @Override + public int getMaxLatency() { + return plugin.getMaxLatency(); + } + + @Override + public int getMaxIdleTime() { + return plugin.getMaxIdleTime(); + } + + @Override + public OutputStream getOutputStream() { + return out; + } + + @Override + public void dispose(boolean exception) { + tryToClose(out, LOG, WARNING); + } +} From 524c8d26f82526101d092e3c40a71410468d4c2e Mon Sep 17 00:00:00 2001 From: akwizgran Date: Fri, 7 May 2021 17:48:39 +0100 Subject: [PATCH 07/77] Don't inject default RemovableDrivePluginFactory on Android. --- .../java/org/briarproject/briar/android/AppModule.java | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/briar-android/src/main/java/org/briarproject/briar/android/AppModule.java b/briar-android/src/main/java/org/briarproject/briar/android/AppModule.java index f5d13cf35..634c984b8 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/AppModule.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/AppModule.java @@ -24,7 +24,6 @@ import org.briarproject.bramble.api.plugin.duplex.DuplexPluginFactory; 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.RemovableDrivePluginFactory; import org.briarproject.bramble.plugin.tcp.AndroidLanTcpPluginFactory; import org.briarproject.bramble.plugin.tor.AndroidTorPluginFactory; import org.briarproject.bramble.util.AndroidUtils; @@ -68,6 +67,7 @@ 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.reporting.ReportingConstants.DEV_ONION_ADDRESS; @@ -151,8 +151,7 @@ public class AppModule { @Provides @Singleton PluginConfig providePluginConfig(AndroidBluetoothPluginFactory bluetooth, - AndroidTorPluginFactory tor, AndroidLanTcpPluginFactory lan, - RemovableDrivePluginFactory drive) { + AndroidTorPluginFactory tor, AndroidLanTcpPluginFactory lan) { @NotNullByDefault PluginConfig pluginConfig = new PluginConfig() { @@ -163,7 +162,7 @@ public class AppModule { @Override public Collection getSimplexFactories() { - return singletonList(drive); + return emptyList(); } @Override From 51d21bd669bf672d00c50537548f8d75c6b156d3 Mon Sep 17 00:00:00 2001 From: akwizgran Date: Mon, 10 May 2021 13:48:12 +0100 Subject: [PATCH 08/77] Decouple RemovableDrivePlugin from FileConstants. --- .../bramble/api/plugin/RemovableDriveConstants.java | 2 ++ .../briarproject/bramble/plugin/file/RemovableDrivePlugin.java | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/bramble-api/src/main/java/org/briarproject/bramble/api/plugin/RemovableDriveConstants.java b/bramble-api/src/main/java/org/briarproject/bramble/api/plugin/RemovableDriveConstants.java index d9798721e..00303714f 100644 --- a/bramble-api/src/main/java/org/briarproject/bramble/api/plugin/RemovableDriveConstants.java +++ b/bramble-api/src/main/java/org/briarproject/bramble/api/plugin/RemovableDriveConstants.java @@ -3,4 +3,6 @@ package org.briarproject.bramble.api.plugin; public interface RemovableDriveConstants { TransportId ID = new TransportId("org.briarproject.bramble.drive"); + + String PROP_PATH = "path"; } diff --git a/bramble-core/src/main/java/org/briarproject/bramble/plugin/file/RemovableDrivePlugin.java b/bramble-core/src/main/java/org/briarproject/bramble/plugin/file/RemovableDrivePlugin.java index b59f4f85f..9904ff834 100644 --- a/bramble-core/src/main/java/org/briarproject/bramble/plugin/file/RemovableDrivePlugin.java +++ b/bramble-core/src/main/java/org/briarproject/bramble/plugin/file/RemovableDrivePlugin.java @@ -11,7 +11,7 @@ import java.io.OutputStream; import javax.annotation.concurrent.Immutable; -import static org.briarproject.bramble.api.plugin.FileConstants.PROP_PATH; +import static org.briarproject.bramble.api.plugin.RemovableDriveConstants.PROP_PATH; import static org.briarproject.bramble.util.StringUtils.isNullOrEmpty; @Immutable From b4880af7e2a438809fb39e34e81da63c2a729ebd Mon Sep 17 00:00:00 2001 From: akwizgran Date: Mon, 10 May 2021 14:04:25 +0100 Subject: [PATCH 09/77] Add Android implementation of RemovableDrivePlugin. --- .../file/AndroidRemovableDrivePlugin.java | 42 +++++++++++++++++ .../AndroidRemovableDrivePluginFactory.java | 47 +++++++++++++++++++ .../api/plugin/RemovableDriveConstants.java | 1 + .../file/RemovableDrivePluginFactory.java | 2 +- .../briarproject/briar/android/AppModule.java | 7 +-- 5 files changed, 95 insertions(+), 4 deletions(-) create mode 100644 bramble-android/src/main/java/org/briarproject/bramble/plugin/file/AndroidRemovableDrivePlugin.java create mode 100644 bramble-android/src/main/java/org/briarproject/bramble/plugin/file/AndroidRemovableDrivePluginFactory.java diff --git a/bramble-android/src/main/java/org/briarproject/bramble/plugin/file/AndroidRemovableDrivePlugin.java b/bramble-android/src/main/java/org/briarproject/bramble/plugin/file/AndroidRemovableDrivePlugin.java new file mode 100644 index 000000000..bee061db1 --- /dev/null +++ b/bramble-android/src/main/java/org/briarproject/bramble/plugin/file/AndroidRemovableDrivePlugin.java @@ -0,0 +1,42 @@ +package org.briarproject.bramble.plugin.file; + +import android.app.Application; +import android.net.Uri; + +import org.briarproject.bramble.api.nullsafety.NotNullByDefault; +import org.briarproject.bramble.api.properties.TransportProperties; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +import javax.annotation.concurrent.Immutable; + +import static org.briarproject.bramble.api.plugin.RemovableDriveConstants.PROP_URI; +import static org.briarproject.bramble.util.StringUtils.isNullOrEmpty; + +@Immutable +@NotNullByDefault +class AndroidRemovableDrivePlugin extends RemovableDrivePlugin { + + private final Application app; + + AndroidRemovableDrivePlugin(Application app, int maxLatency) { + super(maxLatency); + this.app = app; + } + + @Override + InputStream openInputStream(TransportProperties p) throws IOException { + String uri = p.get(PROP_URI); + if (isNullOrEmpty(uri)) throw new IllegalArgumentException(); + return app.getContentResolver().openInputStream(Uri.parse(uri)); + } + + @Override + OutputStream openOutputStream(TransportProperties p) throws IOException { + String uri = p.get(PROP_URI); + if (isNullOrEmpty(uri)) throw new IllegalArgumentException(); + return app.getContentResolver().openOutputStream(Uri.parse(uri)); + } +} diff --git a/bramble-android/src/main/java/org/briarproject/bramble/plugin/file/AndroidRemovableDrivePluginFactory.java b/bramble-android/src/main/java/org/briarproject/bramble/plugin/file/AndroidRemovableDrivePluginFactory.java new file mode 100644 index 000000000..68d32d924 --- /dev/null +++ b/bramble-android/src/main/java/org/briarproject/bramble/plugin/file/AndroidRemovableDrivePluginFactory.java @@ -0,0 +1,47 @@ +package org.briarproject.bramble.plugin.file; + +import android.app.Application; + +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.annotation.concurrent.Immutable; +import javax.inject.Inject; + +import static java.util.concurrent.TimeUnit.DAYS; +import static org.briarproject.bramble.api.plugin.RemovableDriveConstants.ID; + +@Immutable +@NotNullByDefault +public class AndroidRemovableDrivePluginFactory implements + SimplexPluginFactory { + + private static final int MAX_LATENCY = (int) DAYS.toMillis(14); + + private final Application app; + + @Inject + AndroidRemovableDrivePluginFactory(Application app) { + this.app = app; + } + + @Override + public TransportId getId() { + return ID; + } + + @Override + public int getMaxLatency() { + return MAX_LATENCY; + } + + @Nullable + @Override + public SimplexPlugin createPlugin(PluginCallback callback) { + return new AndroidRemovableDrivePlugin(app, MAX_LATENCY); + } +} diff --git a/bramble-api/src/main/java/org/briarproject/bramble/api/plugin/RemovableDriveConstants.java b/bramble-api/src/main/java/org/briarproject/bramble/api/plugin/RemovableDriveConstants.java index 00303714f..98caf96c3 100644 --- a/bramble-api/src/main/java/org/briarproject/bramble/api/plugin/RemovableDriveConstants.java +++ b/bramble-api/src/main/java/org/briarproject/bramble/api/plugin/RemovableDriveConstants.java @@ -5,4 +5,5 @@ public interface RemovableDriveConstants { TransportId ID = new TransportId("org.briarproject.bramble.drive"); String PROP_PATH = "path"; + String PROP_URI = "uri"; } diff --git a/bramble-core/src/main/java/org/briarproject/bramble/plugin/file/RemovableDrivePluginFactory.java b/bramble-core/src/main/java/org/briarproject/bramble/plugin/file/RemovableDrivePluginFactory.java index 5d833805d..e43a89d93 100644 --- a/bramble-core/src/main/java/org/briarproject/bramble/plugin/file/RemovableDrivePluginFactory.java +++ b/bramble-core/src/main/java/org/briarproject/bramble/plugin/file/RemovableDrivePluginFactory.java @@ -17,7 +17,7 @@ import static org.briarproject.bramble.api.plugin.RemovableDriveConstants.ID; @NotNullByDefault public class RemovableDrivePluginFactory implements SimplexPluginFactory { - private final int MAX_LATENCY = (int) DAYS.toMillis(14); + private static final int MAX_LATENCY = (int) DAYS.toMillis(14); @Inject RemovableDrivePluginFactory() { diff --git a/briar-android/src/main/java/org/briarproject/briar/android/AppModule.java b/briar-android/src/main/java/org/briarproject/briar/android/AppModule.java index 634c984b8..3b942cdb0 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/AppModule.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/AppModule.java @@ -24,6 +24,7 @@ import org.briarproject.bramble.api.plugin.duplex.DuplexPluginFactory; 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.tcp.AndroidLanTcpPluginFactory; import org.briarproject.bramble.plugin.tor.AndroidTorPluginFactory; import org.briarproject.bramble.util.AndroidUtils; @@ -67,7 +68,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.reporting.ReportingConstants.DEV_ONION_ADDRESS; @@ -151,7 +151,8 @@ public class AppModule { @Provides @Singleton PluginConfig providePluginConfig(AndroidBluetoothPluginFactory bluetooth, - AndroidTorPluginFactory tor, AndroidLanTcpPluginFactory lan) { + AndroidTorPluginFactory tor, AndroidLanTcpPluginFactory lan, + AndroidRemovableDrivePluginFactory drive) { @NotNullByDefault PluginConfig pluginConfig = new PluginConfig() { @@ -162,7 +163,7 @@ public class AppModule { @Override public Collection getSimplexFactories() { - return emptyList(); + return singletonList(drive); } @Override From 8f4a0ef030bef6e43ebed652e5dc11bf5037bf90 Mon Sep 17 00:00:00 2001 From: akwizgran Date: Fri, 7 May 2021 12:15:51 +0100 Subject: [PATCH 10/77] Add removable drive manager with placeholder task implementations. --- .../file/AndroidRemovableDrivePlugin.java | 2 +- .../AndroidRemovableDrivePluginFactory.java | 2 +- .../briarproject/bramble/api/Consumer.java | 9 ++ .../api/plugin/{ => file}/FileConstants.java | 2 +- .../{ => file}/RemovableDriveConstants.java | 4 +- .../plugin/file/RemovableDriveManager.java | 40 +++++++++ .../api/plugin/file/RemovableDriveTask.java | 34 ++++++++ .../file/AbstractRemovableDrivePlugin.java | 2 +- .../bramble/plugin/file/FilePlugin.java | 2 +- .../file/RemovableDriveManagerImpl.java | 83 +++++++++++++++++++ .../plugin/file/RemovableDriveModule.java | 19 +++++ .../plugin/file/RemovableDrivePlugin.java | 2 +- .../file/RemovableDrivePluginFactory.java | 2 +- .../plugin/file/RemovableDriveTaskImpl.java | 54 ++++++++++++ .../file/RemovableDriveTaskRegistry.java | 13 +++ .../file/RemovableDriverReaderTask.java | 46 ++++++++++ .../file/RemovableDriverWriterTask.java | 46 ++++++++++ .../briar/android/AndroidComponent.java | 4 +- 18 files changed, 357 insertions(+), 9 deletions(-) create mode 100644 bramble-api/src/main/java/org/briarproject/bramble/api/Consumer.java rename bramble-api/src/main/java/org/briarproject/bramble/api/plugin/{ => file}/FileConstants.java (56%) rename bramble-api/src/main/java/org/briarproject/bramble/api/plugin/{ => file}/RemovableDriveConstants.java (61%) create mode 100644 bramble-api/src/main/java/org/briarproject/bramble/api/plugin/file/RemovableDriveManager.java create mode 100644 bramble-api/src/main/java/org/briarproject/bramble/api/plugin/file/RemovableDriveTask.java create mode 100644 bramble-core/src/main/java/org/briarproject/bramble/plugin/file/RemovableDriveManagerImpl.java create mode 100644 bramble-core/src/main/java/org/briarproject/bramble/plugin/file/RemovableDriveModule.java create mode 100644 bramble-core/src/main/java/org/briarproject/bramble/plugin/file/RemovableDriveTaskImpl.java create mode 100644 bramble-core/src/main/java/org/briarproject/bramble/plugin/file/RemovableDriveTaskRegistry.java create mode 100644 bramble-core/src/main/java/org/briarproject/bramble/plugin/file/RemovableDriverReaderTask.java create mode 100644 bramble-core/src/main/java/org/briarproject/bramble/plugin/file/RemovableDriverWriterTask.java diff --git a/bramble-android/src/main/java/org/briarproject/bramble/plugin/file/AndroidRemovableDrivePlugin.java b/bramble-android/src/main/java/org/briarproject/bramble/plugin/file/AndroidRemovableDrivePlugin.java index bee061db1..05e569f7c 100644 --- a/bramble-android/src/main/java/org/briarproject/bramble/plugin/file/AndroidRemovableDrivePlugin.java +++ b/bramble-android/src/main/java/org/briarproject/bramble/plugin/file/AndroidRemovableDrivePlugin.java @@ -12,7 +12,7 @@ import java.io.OutputStream; import javax.annotation.concurrent.Immutable; -import static org.briarproject.bramble.api.plugin.RemovableDriveConstants.PROP_URI; +import static org.briarproject.bramble.api.plugin.file.RemovableDriveConstants.PROP_URI; import static org.briarproject.bramble.util.StringUtils.isNullOrEmpty; @Immutable diff --git a/bramble-android/src/main/java/org/briarproject/bramble/plugin/file/AndroidRemovableDrivePluginFactory.java b/bramble-android/src/main/java/org/briarproject/bramble/plugin/file/AndroidRemovableDrivePluginFactory.java index 68d32d924..64f6cf534 100644 --- a/bramble-android/src/main/java/org/briarproject/bramble/plugin/file/AndroidRemovableDrivePluginFactory.java +++ b/bramble-android/src/main/java/org/briarproject/bramble/plugin/file/AndroidRemovableDrivePluginFactory.java @@ -13,7 +13,7 @@ import javax.annotation.concurrent.Immutable; import javax.inject.Inject; import static java.util.concurrent.TimeUnit.DAYS; -import static org.briarproject.bramble.api.plugin.RemovableDriveConstants.ID; +import static org.briarproject.bramble.api.plugin.file.RemovableDriveConstants.ID; @Immutable @NotNullByDefault diff --git a/bramble-api/src/main/java/org/briarproject/bramble/api/Consumer.java b/bramble-api/src/main/java/org/briarproject/bramble/api/Consumer.java new file mode 100644 index 000000000..4e025a728 --- /dev/null +++ b/bramble-api/src/main/java/org/briarproject/bramble/api/Consumer.java @@ -0,0 +1,9 @@ +package org.briarproject.bramble.api; + +import org.briarproject.bramble.api.nullsafety.NotNullByDefault; + +@NotNullByDefault +public interface Consumer { + + void accept(T t); +} diff --git a/bramble-api/src/main/java/org/briarproject/bramble/api/plugin/FileConstants.java b/bramble-api/src/main/java/org/briarproject/bramble/api/plugin/file/FileConstants.java similarity index 56% rename from bramble-api/src/main/java/org/briarproject/bramble/api/plugin/FileConstants.java rename to bramble-api/src/main/java/org/briarproject/bramble/api/plugin/file/FileConstants.java index bed296874..1564f415d 100644 --- a/bramble-api/src/main/java/org/briarproject/bramble/api/plugin/FileConstants.java +++ b/bramble-api/src/main/java/org/briarproject/bramble/api/plugin/file/FileConstants.java @@ -1,4 +1,4 @@ -package org.briarproject.bramble.api.plugin; +package org.briarproject.bramble.api.plugin.file; public interface FileConstants { diff --git a/bramble-api/src/main/java/org/briarproject/bramble/api/plugin/RemovableDriveConstants.java b/bramble-api/src/main/java/org/briarproject/bramble/api/plugin/file/RemovableDriveConstants.java similarity index 61% rename from bramble-api/src/main/java/org/briarproject/bramble/api/plugin/RemovableDriveConstants.java rename to bramble-api/src/main/java/org/briarproject/bramble/api/plugin/file/RemovableDriveConstants.java index 98caf96c3..927e197df 100644 --- a/bramble-api/src/main/java/org/briarproject/bramble/api/plugin/RemovableDriveConstants.java +++ b/bramble-api/src/main/java/org/briarproject/bramble/api/plugin/file/RemovableDriveConstants.java @@ -1,4 +1,6 @@ -package org.briarproject.bramble.api.plugin; +package org.briarproject.bramble.api.plugin.file; + +import org.briarproject.bramble.api.plugin.TransportId; public interface RemovableDriveConstants { diff --git a/bramble-api/src/main/java/org/briarproject/bramble/api/plugin/file/RemovableDriveManager.java b/bramble-api/src/main/java/org/briarproject/bramble/api/plugin/file/RemovableDriveManager.java new file mode 100644 index 000000000..241582dab --- /dev/null +++ b/bramble-api/src/main/java/org/briarproject/bramble/api/plugin/file/RemovableDriveManager.java @@ -0,0 +1,40 @@ +package org.briarproject.bramble.api.plugin.file; + +import org.briarproject.bramble.api.contact.ContactId; +import org.briarproject.bramble.api.nullsafety.NotNullByDefault; + +import java.io.File; + +import javax.annotation.Nullable; + +@NotNullByDefault +public interface RemovableDriveManager { + + /** + * Returns the currently running reader task for the given contact, + * or null if no task is running. + */ + @Nullable + RemovableDriveTask getCurrentReaderTask(ContactId c); + + /** + * Returns the currently running writer task for the given contact, + * or null if no task is running. + */ + @Nullable + RemovableDriveTask getCurrentWriterTask(ContactId c); + + /** + * Starts and returns a reader task for the given contact, reading from + * the given file. If a reader task for the contact is already running, + * it will be returned and the file argument will be ignored. + */ + RemovableDriveTask startReaderTask(ContactId c, File f); + + /** + * Starts and returns a writer task for the given contact, writing to + * the given file. If a writer task for the contact is already running, + * it will be returned and the file argument will be ignored. + */ + RemovableDriveTask startWriterTask(ContactId c, File f); +} diff --git a/bramble-api/src/main/java/org/briarproject/bramble/api/plugin/file/RemovableDriveTask.java b/bramble-api/src/main/java/org/briarproject/bramble/api/plugin/file/RemovableDriveTask.java new file mode 100644 index 000000000..08165c267 --- /dev/null +++ b/bramble-api/src/main/java/org/briarproject/bramble/api/plugin/file/RemovableDriveTask.java @@ -0,0 +1,34 @@ +package org.briarproject.bramble.api.plugin.file; + +import org.briarproject.bramble.api.event.EventExecutor; +import org.briarproject.bramble.api.nullsafety.NotNullByDefault; + +import java.io.File; + +@NotNullByDefault +public interface RemovableDriveTask { + + /** + * Returns the file that this task is reading from or writing to. + */ + File getFile(); + + /** + * Adds an observer to the task. + */ + void addObserver(Observer o); + + /** + * Removes an observer from the task. + */ + void removeObserver(Observer o); + + interface Observer { + + @EventExecutor + void onProgress(long written, long total); + + @EventExecutor + void onCompletion(boolean success); + } +} 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 3fdc9c908..356186355 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 @@ -20,7 +20,7 @@ import javax.annotation.concurrent.Immutable; import static java.util.logging.Level.WARNING; import static java.util.logging.Logger.getLogger; import static org.briarproject.bramble.api.plugin.Plugin.State.ACTIVE; -import static org.briarproject.bramble.api.plugin.RemovableDriveConstants.ID; +import static org.briarproject.bramble.api.plugin.file.RemovableDriveConstants.ID; import static org.briarproject.bramble.util.LogUtils.logException; @Immutable 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 8a2673a7a..34ca014f2 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 @@ -15,8 +15,8 @@ import java.util.logging.Logger; import static java.util.logging.Level.WARNING; import static java.util.logging.Logger.getLogger; -import static org.briarproject.bramble.api.plugin.FileConstants.PROP_PATH; import static org.briarproject.bramble.api.plugin.Plugin.State.ACTIVE; +import static org.briarproject.bramble.api.plugin.file.FileConstants.PROP_PATH; import static org.briarproject.bramble.util.LogUtils.logException; import static org.briarproject.bramble.util.StringUtils.isNullOrEmpty; diff --git a/bramble-core/src/main/java/org/briarproject/bramble/plugin/file/RemovableDriveManagerImpl.java b/bramble-core/src/main/java/org/briarproject/bramble/plugin/file/RemovableDriveManagerImpl.java new file mode 100644 index 000000000..4b6d9dffc --- /dev/null +++ b/bramble-core/src/main/java/org/briarproject/bramble/plugin/file/RemovableDriveManagerImpl.java @@ -0,0 +1,83 @@ +package org.briarproject.bramble.plugin.file; + +import org.briarproject.bramble.api.contact.ContactId; +import org.briarproject.bramble.api.event.EventExecutor; +import org.briarproject.bramble.api.lifecycle.IoExecutor; +import org.briarproject.bramble.api.nullsafety.NotNullByDefault; +import org.briarproject.bramble.api.plugin.file.RemovableDriveManager; +import org.briarproject.bramble.api.plugin.file.RemovableDriveTask; + +import java.io.File; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.Executor; + +import javax.annotation.Nullable; +import javax.annotation.concurrent.ThreadSafe; +import javax.inject.Inject; + +@ThreadSafe +@NotNullByDefault +class RemovableDriveManagerImpl + implements RemovableDriveManager, RemovableDriveTaskRegistry { + + private final Executor ioExecutor, eventExecutor; + private final ConcurrentHashMap + readers = new ConcurrentHashMap<>(); + private final ConcurrentHashMap + writers = new ConcurrentHashMap<>(); + + @Inject + RemovableDriveManagerImpl(@IoExecutor Executor ioExecutor, + @EventExecutor Executor eventExecutor) { + this.ioExecutor = ioExecutor; + this.eventExecutor = eventExecutor; + } + + @Nullable + @Override + public RemovableDriveTask getCurrentReaderTask(ContactId c) { + return readers.get(c); + } + + @Nullable + @Override + public RemovableDriveTask getCurrentWriterTask(ContactId c) { + return writers.get(c); + } + + @Override + public RemovableDriveTask startReaderTask(ContactId c, File f) { + RemovableDriverReaderTask task = + new RemovableDriverReaderTask(eventExecutor, this, c, f); + RemovableDriveTask old = readers.putIfAbsent(c, task); + if (old == null) { + ioExecutor.execute(task); + return task; + } else { + return old; + } + } + + @Override + public RemovableDriveTask startWriterTask(ContactId c, File f) { + RemovableDriverWriterTask task = + new RemovableDriverWriterTask(eventExecutor, this, c, f); + RemovableDriveTask old = writers.putIfAbsent(c, task); + if (old == null) { + ioExecutor.execute(task); + return task; + } else { + return old; + } + } + + @Override + public void removeReader(ContactId c, RemovableDriveTask task) { + readers.remove(c, task); + } + + @Override + public void removeWriter(ContactId c, RemovableDriveTask task) { + writers.remove(c, task); + } +} diff --git a/bramble-core/src/main/java/org/briarproject/bramble/plugin/file/RemovableDriveModule.java b/bramble-core/src/main/java/org/briarproject/bramble/plugin/file/RemovableDriveModule.java new file mode 100644 index 000000000..92455bb3a --- /dev/null +++ b/bramble-core/src/main/java/org/briarproject/bramble/plugin/file/RemovableDriveModule.java @@ -0,0 +1,19 @@ +package org.briarproject.bramble.plugin.file; + +import org.briarproject.bramble.api.plugin.file.RemovableDriveManager; + +import javax.inject.Singleton; + +import dagger.Module; +import dagger.Provides; + +@Module +public class RemovableDriveModule { + + @Provides + @Singleton + RemovableDriveManager provideRemovableDriveManager( + RemovableDriveManagerImpl removableDriveManager) { + return removableDriveManager; + } +} diff --git a/bramble-core/src/main/java/org/briarproject/bramble/plugin/file/RemovableDrivePlugin.java b/bramble-core/src/main/java/org/briarproject/bramble/plugin/file/RemovableDrivePlugin.java index 9904ff834..cb6653134 100644 --- a/bramble-core/src/main/java/org/briarproject/bramble/plugin/file/RemovableDrivePlugin.java +++ b/bramble-core/src/main/java/org/briarproject/bramble/plugin/file/RemovableDrivePlugin.java @@ -11,7 +11,7 @@ import java.io.OutputStream; import javax.annotation.concurrent.Immutable; -import static org.briarproject.bramble.api.plugin.RemovableDriveConstants.PROP_PATH; +import static org.briarproject.bramble.api.plugin.file.RemovableDriveConstants.PROP_PATH; import static org.briarproject.bramble.util.StringUtils.isNullOrEmpty; @Immutable diff --git a/bramble-core/src/main/java/org/briarproject/bramble/plugin/file/RemovableDrivePluginFactory.java b/bramble-core/src/main/java/org/briarproject/bramble/plugin/file/RemovableDrivePluginFactory.java index e43a89d93..6f1ad7564 100644 --- a/bramble-core/src/main/java/org/briarproject/bramble/plugin/file/RemovableDrivePluginFactory.java +++ b/bramble-core/src/main/java/org/briarproject/bramble/plugin/file/RemovableDrivePluginFactory.java @@ -11,7 +11,7 @@ import javax.annotation.concurrent.Immutable; import javax.inject.Inject; import static java.util.concurrent.TimeUnit.DAYS; -import static org.briarproject.bramble.api.plugin.RemovableDriveConstants.ID; +import static org.briarproject.bramble.api.plugin.file.RemovableDriveConstants.ID; @Immutable @NotNullByDefault diff --git a/bramble-core/src/main/java/org/briarproject/bramble/plugin/file/RemovableDriveTaskImpl.java b/bramble-core/src/main/java/org/briarproject/bramble/plugin/file/RemovableDriveTaskImpl.java new file mode 100644 index 000000000..0af720b77 --- /dev/null +++ b/bramble-core/src/main/java/org/briarproject/bramble/plugin/file/RemovableDriveTaskImpl.java @@ -0,0 +1,54 @@ +package org.briarproject.bramble.plugin.file; + +import org.briarproject.bramble.api.Consumer; +import org.briarproject.bramble.api.contact.ContactId; +import org.briarproject.bramble.api.nullsafety.NotNullByDefault; +import org.briarproject.bramble.api.plugin.file.RemovableDriveTask; + +import java.io.File; +import java.util.List; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.concurrent.Executor; + +import javax.annotation.concurrent.ThreadSafe; + +@ThreadSafe +@NotNullByDefault +abstract class RemovableDriveTaskImpl implements RemovableDriveTask, Runnable { + + private final Executor eventExecutor; + final RemovableDriveTaskRegistry registry; + final ContactId contactId; + final File file; + private final List observers = new CopyOnWriteArrayList<>(); + + RemovableDriveTaskImpl(Executor eventExecutor, + RemovableDriveTaskRegistry registry, ContactId contactId, + File file) { + this.contactId = contactId; + this.file = file; + this.registry = registry; + this.eventExecutor = eventExecutor; + } + + @Override + public File getFile() { + return file; + } + + @Override + public void addObserver(Observer o) { + observers.add(o); + } + + @Override + public void removeObserver(Observer o) { + observers.remove(o); + } + + void visitObservers(Consumer visitor) { + eventExecutor.execute(() -> { + for (Observer o : observers) visitor.accept(o); + }); + } +} diff --git a/bramble-core/src/main/java/org/briarproject/bramble/plugin/file/RemovableDriveTaskRegistry.java b/bramble-core/src/main/java/org/briarproject/bramble/plugin/file/RemovableDriveTaskRegistry.java new file mode 100644 index 000000000..84ee40092 --- /dev/null +++ b/bramble-core/src/main/java/org/briarproject/bramble/plugin/file/RemovableDriveTaskRegistry.java @@ -0,0 +1,13 @@ +package org.briarproject.bramble.plugin.file; + +import org.briarproject.bramble.api.contact.ContactId; +import org.briarproject.bramble.api.nullsafety.NotNullByDefault; +import org.briarproject.bramble.api.plugin.file.RemovableDriveTask; + +@NotNullByDefault +interface RemovableDriveTaskRegistry { + + void removeReader(ContactId c, RemovableDriveTask task); + + void removeWriter(ContactId c, RemovableDriveTask task); +} diff --git a/bramble-core/src/main/java/org/briarproject/bramble/plugin/file/RemovableDriverReaderTask.java b/bramble-core/src/main/java/org/briarproject/bramble/plugin/file/RemovableDriverReaderTask.java new file mode 100644 index 000000000..3cc53c7b4 --- /dev/null +++ b/bramble-core/src/main/java/org/briarproject/bramble/plugin/file/RemovableDriverReaderTask.java @@ -0,0 +1,46 @@ +package org.briarproject.bramble.plugin.file; + +import org.briarproject.bramble.api.contact.ContactId; +import org.briarproject.bramble.api.nullsafety.NotNullByDefault; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.concurrent.Executor; +import java.util.logging.Logger; + +import static java.util.logging.Level.WARNING; +import static java.util.logging.Logger.getLogger; +import static org.briarproject.bramble.util.IoUtils.tryToClose; +import static org.briarproject.bramble.util.LogUtils.logException; + +@NotNullByDefault +class RemovableDriverReaderTask extends RemovableDriveTaskImpl { + + private final static Logger LOG = + getLogger(RemovableDriverReaderTask.class.getName()); + + RemovableDriverReaderTask(Executor eventExecutor, + RemovableDriveTaskRegistry registry, ContactId contactId, + File file) { + super(eventExecutor, registry, contactId, file); + } + + @Override + public void run() { + // TODO + InputStream in = null; + try { + visitObservers(o -> o.onProgress(0, 100)); + in = new FileInputStream(file); + visitObservers(o -> o.onCompletion(true)); + } catch (IOException e) { + logException(LOG, WARNING, e); + visitObservers(o -> o.onCompletion(false)); + } finally { + tryToClose(in, LOG, WARNING); + registry.removeReader(contactId, this); + } + } +} diff --git a/bramble-core/src/main/java/org/briarproject/bramble/plugin/file/RemovableDriverWriterTask.java b/bramble-core/src/main/java/org/briarproject/bramble/plugin/file/RemovableDriverWriterTask.java new file mode 100644 index 000000000..ed0715d16 --- /dev/null +++ b/bramble-core/src/main/java/org/briarproject/bramble/plugin/file/RemovableDriverWriterTask.java @@ -0,0 +1,46 @@ +package org.briarproject.bramble.plugin.file; + +import org.briarproject.bramble.api.contact.ContactId; +import org.briarproject.bramble.api.nullsafety.NotNullByDefault; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.util.concurrent.Executor; +import java.util.logging.Logger; + +import static java.util.logging.Level.WARNING; +import static java.util.logging.Logger.getLogger; +import static org.briarproject.bramble.util.IoUtils.tryToClose; +import static org.briarproject.bramble.util.LogUtils.logException; + +@NotNullByDefault +class RemovableDriverWriterTask extends RemovableDriveTaskImpl { + + private static final Logger LOG = + getLogger(RemovableDriverWriterTask.class.getName()); + + RemovableDriverWriterTask(Executor eventExecutor, + RemovableDriveTaskRegistry registry, ContactId contactId, + File file) { + super(eventExecutor, registry, contactId, file); + } + + @Override + public void run() { + // TODO + OutputStream out = null; + try { + visitObservers(o -> o.onProgress(0, 100)); + out = new FileOutputStream(file); + visitObservers(o -> o.onCompletion(true)); + } catch (IOException e) { + logException(LOG, WARNING, e); + visitObservers(o -> o.onCompletion(false)); + } finally { + tryToClose(out, LOG, WARNING); + registry.removeWriter(contactId, this); + } + } +} diff --git a/briar-android/src/main/java/org/briarproject/briar/android/AndroidComponent.java b/briar-android/src/main/java/org/briarproject/briar/android/AndroidComponent.java index aeed5817f..f2b3d6197 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/AndroidComponent.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/AndroidComponent.java @@ -28,6 +28,7 @@ import org.briarproject.bramble.api.system.AndroidExecutor; import org.briarproject.bramble.api.system.AndroidWakeLockManager; import org.briarproject.bramble.api.system.Clock; import org.briarproject.bramble.api.system.LocationUtils; +import org.briarproject.bramble.plugin.file.RemovableDriveModule; import org.briarproject.bramble.plugin.tor.CircumventionProvider; import org.briarproject.bramble.system.ClockModule; import org.briarproject.briar.BriarCoreEagerSingletons; @@ -83,7 +84,8 @@ import dagger.Component; AppModule.class, AttachmentModule.class, ClockModule.class, - MediaModule.class + MediaModule.class, + RemovableDriveModule.class }) public interface AndroidComponent extends BrambleCoreEagerSingletons, BrambleAndroidEagerSingletons, From c9c6f3682c6ae3cd508eded4c234506c05e487a9 Mon Sep 17 00:00:00 2001 From: akwizgran Date: Fri, 7 May 2021 13:45:12 +0100 Subject: [PATCH 11/77] Add task factory. --- .../api/plugin/file/RemovableDriveTask.java | 2 +- .../file/RemovableDriveManagerImpl.java | 14 ++++---- .../plugin/file/RemovableDriveModule.java | 6 ++++ .../file/RemovableDriveTaskFactory.java | 17 +++++++++ .../file/RemovableDriveTaskFactoryImpl.java | 36 +++++++++++++++++++ .../plugin/file/RemovableDriveTaskImpl.java | 2 +- 6 files changed, 67 insertions(+), 10 deletions(-) create mode 100644 bramble-core/src/main/java/org/briarproject/bramble/plugin/file/RemovableDriveTaskFactory.java create mode 100644 bramble-core/src/main/java/org/briarproject/bramble/plugin/file/RemovableDriveTaskFactoryImpl.java diff --git a/bramble-api/src/main/java/org/briarproject/bramble/api/plugin/file/RemovableDriveTask.java b/bramble-api/src/main/java/org/briarproject/bramble/api/plugin/file/RemovableDriveTask.java index 08165c267..3bc4c1837 100644 --- a/bramble-api/src/main/java/org/briarproject/bramble/api/plugin/file/RemovableDriveTask.java +++ b/bramble-api/src/main/java/org/briarproject/bramble/api/plugin/file/RemovableDriveTask.java @@ -6,7 +6,7 @@ import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import java.io.File; @NotNullByDefault -public interface RemovableDriveTask { +public interface RemovableDriveTask extends Runnable { /** * Returns the file that this task is reading from or writing to. diff --git a/bramble-core/src/main/java/org/briarproject/bramble/plugin/file/RemovableDriveManagerImpl.java b/bramble-core/src/main/java/org/briarproject/bramble/plugin/file/RemovableDriveManagerImpl.java index 4b6d9dffc..3752449fa 100644 --- a/bramble-core/src/main/java/org/briarproject/bramble/plugin/file/RemovableDriveManagerImpl.java +++ b/bramble-core/src/main/java/org/briarproject/bramble/plugin/file/RemovableDriveManagerImpl.java @@ -1,7 +1,6 @@ package org.briarproject.bramble.plugin.file; import org.briarproject.bramble.api.contact.ContactId; -import org.briarproject.bramble.api.event.EventExecutor; import org.briarproject.bramble.api.lifecycle.IoExecutor; import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.plugin.file.RemovableDriveManager; @@ -20,7 +19,8 @@ import javax.inject.Inject; class RemovableDriveManagerImpl implements RemovableDriveManager, RemovableDriveTaskRegistry { - private final Executor ioExecutor, eventExecutor; + private final Executor ioExecutor; + private final RemovableDriveTaskFactory taskFactory; private final ConcurrentHashMap readers = new ConcurrentHashMap<>(); private final ConcurrentHashMap @@ -28,9 +28,9 @@ class RemovableDriveManagerImpl @Inject RemovableDriveManagerImpl(@IoExecutor Executor ioExecutor, - @EventExecutor Executor eventExecutor) { + RemovableDriveTaskFactory taskFactory) { this.ioExecutor = ioExecutor; - this.eventExecutor = eventExecutor; + this.taskFactory = taskFactory; } @Nullable @@ -47,8 +47,7 @@ class RemovableDriveManagerImpl @Override public RemovableDriveTask startReaderTask(ContactId c, File f) { - RemovableDriverReaderTask task = - new RemovableDriverReaderTask(eventExecutor, this, c, f); + RemovableDriveTask task = taskFactory.createReader(this, c, f); RemovableDriveTask old = readers.putIfAbsent(c, task); if (old == null) { ioExecutor.execute(task); @@ -60,8 +59,7 @@ class RemovableDriveManagerImpl @Override public RemovableDriveTask startWriterTask(ContactId c, File f) { - RemovableDriverWriterTask task = - new RemovableDriverWriterTask(eventExecutor, this, c, f); + RemovableDriveTask task = taskFactory.createWriter(this, c, f); RemovableDriveTask old = writers.putIfAbsent(c, task); if (old == null) { ioExecutor.execute(task); diff --git a/bramble-core/src/main/java/org/briarproject/bramble/plugin/file/RemovableDriveModule.java b/bramble-core/src/main/java/org/briarproject/bramble/plugin/file/RemovableDriveModule.java index 92455bb3a..3857cc338 100644 --- a/bramble-core/src/main/java/org/briarproject/bramble/plugin/file/RemovableDriveModule.java +++ b/bramble-core/src/main/java/org/briarproject/bramble/plugin/file/RemovableDriveModule.java @@ -16,4 +16,10 @@ public class RemovableDriveModule { RemovableDriveManagerImpl removableDriveManager) { return removableDriveManager; } + + @Provides + RemovableDriveTaskFactory provideTaskFactory( + RemovableDriveTaskFactoryImpl taskFactory) { + return taskFactory; + } } diff --git a/bramble-core/src/main/java/org/briarproject/bramble/plugin/file/RemovableDriveTaskFactory.java b/bramble-core/src/main/java/org/briarproject/bramble/plugin/file/RemovableDriveTaskFactory.java new file mode 100644 index 000000000..a81f88249 --- /dev/null +++ b/bramble-core/src/main/java/org/briarproject/bramble/plugin/file/RemovableDriveTaskFactory.java @@ -0,0 +1,17 @@ +package org.briarproject.bramble.plugin.file; + +import org.briarproject.bramble.api.contact.ContactId; +import org.briarproject.bramble.api.nullsafety.NotNullByDefault; +import org.briarproject.bramble.api.plugin.file.RemovableDriveTask; + +import java.io.File; + +@NotNullByDefault +interface RemovableDriveTaskFactory { + + RemovableDriveTask createReader(RemovableDriveTaskRegistry registry, + ContactId c, File f); + + RemovableDriveTask createWriter(RemovableDriveTaskRegistry registry, + ContactId c, File f); +} diff --git a/bramble-core/src/main/java/org/briarproject/bramble/plugin/file/RemovableDriveTaskFactoryImpl.java b/bramble-core/src/main/java/org/briarproject/bramble/plugin/file/RemovableDriveTaskFactoryImpl.java new file mode 100644 index 000000000..b46f659d4 --- /dev/null +++ b/bramble-core/src/main/java/org/briarproject/bramble/plugin/file/RemovableDriveTaskFactoryImpl.java @@ -0,0 +1,36 @@ +package org.briarproject.bramble.plugin.file; + +import org.briarproject.bramble.api.contact.ContactId; +import org.briarproject.bramble.api.event.EventExecutor; +import org.briarproject.bramble.api.nullsafety.NotNullByDefault; +import org.briarproject.bramble.api.plugin.file.RemovableDriveTask; + +import java.io.File; +import java.util.concurrent.Executor; + +import javax.annotation.concurrent.Immutable; +import javax.inject.Inject; + +@Immutable +@NotNullByDefault +class RemovableDriveTaskFactoryImpl implements RemovableDriveTaskFactory { + + private final Executor eventExecutor; + + @Inject + RemovableDriveTaskFactoryImpl(@EventExecutor Executor eventExecutor) { + this.eventExecutor = eventExecutor; + } + + @Override + public RemovableDriveTask createReader(RemovableDriveTaskRegistry registry, + ContactId c, File f) { + return new RemovableDriverReaderTask(eventExecutor, registry, c, f); + } + + @Override + public RemovableDriveTask createWriter(RemovableDriveTaskRegistry registry, + ContactId c, File f) { + return new RemovableDriverWriterTask(eventExecutor, registry, c, f); + } +} diff --git a/bramble-core/src/main/java/org/briarproject/bramble/plugin/file/RemovableDriveTaskImpl.java b/bramble-core/src/main/java/org/briarproject/bramble/plugin/file/RemovableDriveTaskImpl.java index 0af720b77..e5f09f971 100644 --- a/bramble-core/src/main/java/org/briarproject/bramble/plugin/file/RemovableDriveTaskImpl.java +++ b/bramble-core/src/main/java/org/briarproject/bramble/plugin/file/RemovableDriveTaskImpl.java @@ -14,7 +14,7 @@ import javax.annotation.concurrent.ThreadSafe; @ThreadSafe @NotNullByDefault -abstract class RemovableDriveTaskImpl implements RemovableDriveTask, Runnable { +abstract class RemovableDriveTaskImpl implements RemovableDriveTask { private final Executor eventExecutor; final RemovableDriveTaskRegistry registry; From 2c39b02644f59314b23323102cf6631efc9fbf48 Mon Sep 17 00:00:00 2001 From: akwizgran Date: Fri, 7 May 2021 14:20:30 +0100 Subject: [PATCH 12/77] Implement RemovableDriverReaderTask. --- .../api/plugin/file/RemovableDriveTask.java | 2 +- .../file/RemovableDriveTaskFactoryImpl.java | 21 ++++- .../plugin/file/RemovableDriveTaskImpl.java | 38 +++++++- .../file/RemovableDriverReaderTask.java | 92 +++++++++++++++---- .../file/RemovableDriverWriterTask.java | 15 ++- 5 files changed, 138 insertions(+), 30 deletions(-) diff --git a/bramble-api/src/main/java/org/briarproject/bramble/api/plugin/file/RemovableDriveTask.java b/bramble-api/src/main/java/org/briarproject/bramble/api/plugin/file/RemovableDriveTask.java index 3bc4c1837..c4f2e00a9 100644 --- a/bramble-api/src/main/java/org/briarproject/bramble/api/plugin/file/RemovableDriveTask.java +++ b/bramble-api/src/main/java/org/briarproject/bramble/api/plugin/file/RemovableDriveTask.java @@ -26,7 +26,7 @@ public interface RemovableDriveTask extends Runnable { interface Observer { @EventExecutor - void onProgress(long written, long total); + void onProgress(long done, long total); @EventExecutor void onCompletion(boolean success); diff --git a/bramble-core/src/main/java/org/briarproject/bramble/plugin/file/RemovableDriveTaskFactoryImpl.java b/bramble-core/src/main/java/org/briarproject/bramble/plugin/file/RemovableDriveTaskFactoryImpl.java index b46f659d4..d8021ff82 100644 --- a/bramble-core/src/main/java/org/briarproject/bramble/plugin/file/RemovableDriveTaskFactoryImpl.java +++ b/bramble-core/src/main/java/org/briarproject/bramble/plugin/file/RemovableDriveTaskFactoryImpl.java @@ -1,8 +1,11 @@ package org.briarproject.bramble.plugin.file; +import org.briarproject.bramble.api.connection.ConnectionManager; import org.briarproject.bramble.api.contact.ContactId; +import org.briarproject.bramble.api.event.EventBus; import org.briarproject.bramble.api.event.EventExecutor; import org.briarproject.bramble.api.nullsafety.NotNullByDefault; +import org.briarproject.bramble.api.plugin.PluginManager; import org.briarproject.bramble.api.plugin.file.RemovableDriveTask; import java.io.File; @@ -16,21 +19,33 @@ import javax.inject.Inject; class RemovableDriveTaskFactoryImpl implements RemovableDriveTaskFactory { private final Executor eventExecutor; + private final PluginManager pluginManager; + private final ConnectionManager connectionManager; + private final EventBus eventBus; @Inject - RemovableDriveTaskFactoryImpl(@EventExecutor Executor eventExecutor) { + RemovableDriveTaskFactoryImpl( + @EventExecutor Executor eventExecutor, + PluginManager pluginManager, + ConnectionManager connectionManager, + EventBus eventBus) { this.eventExecutor = eventExecutor; + this.pluginManager = pluginManager; + this.connectionManager = connectionManager; + this.eventBus = eventBus; } @Override public RemovableDriveTask createReader(RemovableDriveTaskRegistry registry, ContactId c, File f) { - return new RemovableDriverReaderTask(eventExecutor, registry, c, f); + return new RemovableDriverReaderTask(eventExecutor, pluginManager, + connectionManager, eventBus, registry, c, f); } @Override public RemovableDriveTask createWriter(RemovableDriveTaskRegistry registry, ContactId c, File f) { - return new RemovableDriverWriterTask(eventExecutor, registry, c, f); + return new RemovableDriverWriterTask(eventExecutor, pluginManager, + connectionManager, eventBus, registry, c, f); } } diff --git a/bramble-core/src/main/java/org/briarproject/bramble/plugin/file/RemovableDriveTaskImpl.java b/bramble-core/src/main/java/org/briarproject/bramble/plugin/file/RemovableDriveTaskImpl.java index e5f09f971..4984e3876 100644 --- a/bramble-core/src/main/java/org/briarproject/bramble/plugin/file/RemovableDriveTaskImpl.java +++ b/bramble-core/src/main/java/org/briarproject/bramble/plugin/file/RemovableDriveTaskImpl.java @@ -1,9 +1,14 @@ package org.briarproject.bramble.plugin.file; import org.briarproject.bramble.api.Consumer; +import org.briarproject.bramble.api.connection.ConnectionManager; import org.briarproject.bramble.api.contact.ContactId; +import org.briarproject.bramble.api.event.EventBus; import org.briarproject.bramble.api.nullsafety.NotNullByDefault; +import org.briarproject.bramble.api.plugin.PluginManager; import org.briarproject.bramble.api.plugin.file.RemovableDriveTask; +import org.briarproject.bramble.api.plugin.simplex.SimplexPlugin; +import org.briarproject.bramble.api.properties.TransportProperties; import java.io.File; import java.util.List; @@ -12,23 +17,38 @@ import java.util.concurrent.Executor; import javax.annotation.concurrent.ThreadSafe; +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.api.plugin.file.RemovableDriveConstants.ID; + @ThreadSafe @NotNullByDefault abstract class RemovableDriveTaskImpl implements RemovableDriveTask { private final Executor eventExecutor; + private final PluginManager pluginManager; + final ConnectionManager connectionManager; + final EventBus eventBus; final RemovableDriveTaskRegistry registry; final ContactId contactId; final File file; private final List observers = new CopyOnWriteArrayList<>(); - RemovableDriveTaskImpl(Executor eventExecutor, - RemovableDriveTaskRegistry registry, ContactId contactId, + RemovableDriveTaskImpl( + Executor eventExecutor, + PluginManager pluginManager, + ConnectionManager connectionManager, + EventBus eventBus, + RemovableDriveTaskRegistry registry, + ContactId contactId, File file) { + this.eventExecutor = eventExecutor; + this.pluginManager = pluginManager; + this.connectionManager = connectionManager; + this.eventBus = eventBus; + this.registry = registry; this.contactId = contactId; this.file = file; - this.registry = registry; - this.eventExecutor = eventExecutor; } @Override @@ -51,4 +71,14 @@ abstract class RemovableDriveTaskImpl implements RemovableDriveTask { for (Observer o : observers) visitor.accept(o); }); } + + SimplexPlugin getPlugin() { + return (SimplexPlugin) requireNonNull(pluginManager.getPlugin(ID)); + } + + TransportProperties createProperties() { + TransportProperties p = new TransportProperties(); + p.put(PROP_PATH, file.getAbsolutePath()); + return p; + } } diff --git a/bramble-core/src/main/java/org/briarproject/bramble/plugin/file/RemovableDriverReaderTask.java b/bramble-core/src/main/java/org/briarproject/bramble/plugin/file/RemovableDriverReaderTask.java index 3cc53c7b4..a7374fc53 100644 --- a/bramble-core/src/main/java/org/briarproject/bramble/plugin/file/RemovableDriverReaderTask.java +++ b/bramble-core/src/main/java/org/briarproject/bramble/plugin/file/RemovableDriverReaderTask.java @@ -1,46 +1,100 @@ package org.briarproject.bramble.plugin.file; +import org.briarproject.bramble.api.connection.ConnectionManager; import org.briarproject.bramble.api.contact.ContactId; +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.nullsafety.NotNullByDefault; +import org.briarproject.bramble.api.plugin.PluginManager; +import org.briarproject.bramble.api.plugin.TransportConnectionReader; +import org.briarproject.bramble.api.sync.event.MessageAddedEvent; import java.io.File; -import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.util.concurrent.Executor; +import java.util.concurrent.atomic.AtomicLong; import java.util.logging.Logger; -import static java.util.logging.Level.WARNING; +import static java.lang.Math.min; import static java.util.logging.Logger.getLogger; -import static org.briarproject.bramble.util.IoUtils.tryToClose; -import static org.briarproject.bramble.util.LogUtils.logException; +import static org.briarproject.bramble.api.plugin.file.RemovableDriveConstants.ID; @NotNullByDefault -class RemovableDriverReaderTask extends RemovableDriveTaskImpl { +class RemovableDriverReaderTask extends RemovableDriveTaskImpl + implements EventListener { private final static Logger LOG = getLogger(RemovableDriverReaderTask.class.getName()); - RemovableDriverReaderTask(Executor eventExecutor, - RemovableDriveTaskRegistry registry, ContactId contactId, + private final AtomicLong fileLength = new AtomicLong(0); + private final AtomicLong totalMessageLength = new AtomicLong(0); + + RemovableDriverReaderTask( + Executor eventExecutor, + PluginManager pluginManager, + ConnectionManager connectionManager, + EventBus eventBus, + RemovableDriveTaskRegistry registry, + ContactId contactId, File file) { - super(eventExecutor, registry, contactId, file); + super(eventExecutor, pluginManager, connectionManager, eventBus, + registry, contactId, file); } @Override public void run() { - // TODO - InputStream in = null; - try { - visitObservers(o -> o.onProgress(0, 100)); - in = new FileInputStream(file); - visitObservers(o -> o.onCompletion(true)); - } catch (IOException e) { - logException(LOG, WARNING, e); + TransportConnectionReader r = + getPlugin().createReader(createProperties()); + if (r == null) { + LOG.warning("Failed to create reader"); + registry.removeReader(contactId, RemovableDriverReaderTask.this); visitObservers(o -> o.onCompletion(false)); - } finally { - tryToClose(in, LOG, WARNING); - registry.removeReader(contactId, this); + return; + } + fileLength.set(file.length()); + eventBus.addListener(this); + connectionManager.manageIncomingConnection(ID, new DecoratedReader(r)); + } + + @Override + public void eventOccurred(Event e) { + if (e instanceof MessageAddedEvent) { + MessageAddedEvent m = (MessageAddedEvent) e; + if (contactId.equals(m.getContactId())) { + LOG.info("Message received"); + updateProgress(m.getMessage().getRawLength()); + } + } + } + + private void updateProgress(int messageLength) { + long done = totalMessageLength.addAndGet(messageLength); + long total = fileLength.get(); + visitObservers(o -> o.onProgress(min(done, total), total)); + } + + private class DecoratedReader implements TransportConnectionReader { + + private final TransportConnectionReader delegate; + + private DecoratedReader(TransportConnectionReader delegate) { + this.delegate = delegate; + } + + @Override + public InputStream getInputStream() throws IOException { + return delegate.getInputStream(); + } + + @Override + public void dispose(boolean exception, boolean recognised) + throws IOException { + delegate.dispose(exception, recognised); + registry.removeReader(contactId, RemovableDriverReaderTask.this); + eventBus.removeListener(RemovableDriverReaderTask.this); + visitObservers(o -> o.onCompletion(!exception && recognised)); } } } diff --git a/bramble-core/src/main/java/org/briarproject/bramble/plugin/file/RemovableDriverWriterTask.java b/bramble-core/src/main/java/org/briarproject/bramble/plugin/file/RemovableDriverWriterTask.java index ed0715d16..7394b1ac2 100644 --- a/bramble-core/src/main/java/org/briarproject/bramble/plugin/file/RemovableDriverWriterTask.java +++ b/bramble-core/src/main/java/org/briarproject/bramble/plugin/file/RemovableDriverWriterTask.java @@ -1,7 +1,10 @@ package org.briarproject.bramble.plugin.file; +import org.briarproject.bramble.api.connection.ConnectionManager; import org.briarproject.bramble.api.contact.ContactId; +import org.briarproject.bramble.api.event.EventBus; import org.briarproject.bramble.api.nullsafety.NotNullByDefault; +import org.briarproject.bramble.api.plugin.PluginManager; import java.io.File; import java.io.FileOutputStream; @@ -21,10 +24,16 @@ class RemovableDriverWriterTask extends RemovableDriveTaskImpl { private static final Logger LOG = getLogger(RemovableDriverWriterTask.class.getName()); - RemovableDriverWriterTask(Executor eventExecutor, - RemovableDriveTaskRegistry registry, ContactId contactId, + RemovableDriverWriterTask( + Executor eventExecutor, + PluginManager pluginManager, + ConnectionManager connectionManager, + EventBus eventBus, + RemovableDriveTaskRegistry registry, + ContactId contactId, File file) { - super(eventExecutor, registry, contactId, file); + super(eventExecutor, pluginManager, connectionManager, eventBus, + registry, contactId, file); } @Override From 03248d04e5c4df7713dc7be9eaa11d55ec8b05ba Mon Sep 17 00:00:00 2001 From: akwizgran Date: Fri, 7 May 2021 14:31:53 +0100 Subject: [PATCH 13/77] Fix typo in class names. --- ...ReaderTask.java => RemovableDriveReaderTask.java} | 12 ++++++------ .../plugin/file/RemovableDriveTaskFactoryImpl.java | 4 ++-- ...WriterTask.java => RemovableDriveWriterTask.java} | 6 +++--- 3 files changed, 11 insertions(+), 11 deletions(-) rename bramble-core/src/main/java/org/briarproject/bramble/plugin/file/{RemovableDriverReaderTask.java => RemovableDriveReaderTask.java} (89%) rename bramble-core/src/main/java/org/briarproject/bramble/plugin/file/{RemovableDriverWriterTask.java => RemovableDriveWriterTask.java} (91%) diff --git a/bramble-core/src/main/java/org/briarproject/bramble/plugin/file/RemovableDriverReaderTask.java b/bramble-core/src/main/java/org/briarproject/bramble/plugin/file/RemovableDriveReaderTask.java similarity index 89% rename from bramble-core/src/main/java/org/briarproject/bramble/plugin/file/RemovableDriverReaderTask.java rename to bramble-core/src/main/java/org/briarproject/bramble/plugin/file/RemovableDriveReaderTask.java index a7374fc53..2e0f02e0b 100644 --- a/bramble-core/src/main/java/org/briarproject/bramble/plugin/file/RemovableDriverReaderTask.java +++ b/bramble-core/src/main/java/org/briarproject/bramble/plugin/file/RemovableDriveReaderTask.java @@ -22,16 +22,16 @@ import static java.util.logging.Logger.getLogger; import static org.briarproject.bramble.api.plugin.file.RemovableDriveConstants.ID; @NotNullByDefault -class RemovableDriverReaderTask extends RemovableDriveTaskImpl +class RemovableDriveReaderTask extends RemovableDriveTaskImpl implements EventListener { private final static Logger LOG = - getLogger(RemovableDriverReaderTask.class.getName()); + getLogger(RemovableDriveReaderTask.class.getName()); private final AtomicLong fileLength = new AtomicLong(0); private final AtomicLong totalMessageLength = new AtomicLong(0); - RemovableDriverReaderTask( + RemovableDriveReaderTask( Executor eventExecutor, PluginManager pluginManager, ConnectionManager connectionManager, @@ -49,7 +49,7 @@ class RemovableDriverReaderTask extends RemovableDriveTaskImpl getPlugin().createReader(createProperties()); if (r == null) { LOG.warning("Failed to create reader"); - registry.removeReader(contactId, RemovableDriverReaderTask.this); + registry.removeReader(contactId, RemovableDriveReaderTask.this); visitObservers(o -> o.onCompletion(false)); return; } @@ -92,8 +92,8 @@ class RemovableDriverReaderTask extends RemovableDriveTaskImpl public void dispose(boolean exception, boolean recognised) throws IOException { delegate.dispose(exception, recognised); - registry.removeReader(contactId, RemovableDriverReaderTask.this); - eventBus.removeListener(RemovableDriverReaderTask.this); + registry.removeReader(contactId, RemovableDriveReaderTask.this); + eventBus.removeListener(RemovableDriveReaderTask.this); visitObservers(o -> o.onCompletion(!exception && recognised)); } } diff --git a/bramble-core/src/main/java/org/briarproject/bramble/plugin/file/RemovableDriveTaskFactoryImpl.java b/bramble-core/src/main/java/org/briarproject/bramble/plugin/file/RemovableDriveTaskFactoryImpl.java index d8021ff82..ad1f6ee06 100644 --- a/bramble-core/src/main/java/org/briarproject/bramble/plugin/file/RemovableDriveTaskFactoryImpl.java +++ b/bramble-core/src/main/java/org/briarproject/bramble/plugin/file/RemovableDriveTaskFactoryImpl.java @@ -38,14 +38,14 @@ class RemovableDriveTaskFactoryImpl implements RemovableDriveTaskFactory { @Override public RemovableDriveTask createReader(RemovableDriveTaskRegistry registry, ContactId c, File f) { - return new RemovableDriverReaderTask(eventExecutor, pluginManager, + return new RemovableDriveReaderTask(eventExecutor, pluginManager, connectionManager, eventBus, registry, c, f); } @Override public RemovableDriveTask createWriter(RemovableDriveTaskRegistry registry, ContactId c, File f) { - return new RemovableDriverWriterTask(eventExecutor, pluginManager, + return new RemovableDriveWriterTask(eventExecutor, pluginManager, connectionManager, eventBus, registry, c, f); } } diff --git a/bramble-core/src/main/java/org/briarproject/bramble/plugin/file/RemovableDriverWriterTask.java b/bramble-core/src/main/java/org/briarproject/bramble/plugin/file/RemovableDriveWriterTask.java similarity index 91% rename from bramble-core/src/main/java/org/briarproject/bramble/plugin/file/RemovableDriverWriterTask.java rename to bramble-core/src/main/java/org/briarproject/bramble/plugin/file/RemovableDriveWriterTask.java index 7394b1ac2..858d6a19c 100644 --- a/bramble-core/src/main/java/org/briarproject/bramble/plugin/file/RemovableDriverWriterTask.java +++ b/bramble-core/src/main/java/org/briarproject/bramble/plugin/file/RemovableDriveWriterTask.java @@ -19,12 +19,12 @@ import static org.briarproject.bramble.util.IoUtils.tryToClose; import static org.briarproject.bramble.util.LogUtils.logException; @NotNullByDefault -class RemovableDriverWriterTask extends RemovableDriveTaskImpl { +class RemovableDriveWriterTask extends RemovableDriveTaskImpl { private static final Logger LOG = - getLogger(RemovableDriverWriterTask.class.getName()); + getLogger(RemovableDriveWriterTask.class.getName()); - RemovableDriverWriterTask( + RemovableDriveWriterTask( Executor eventExecutor, PluginManager pluginManager, ConnectionManager connectionManager, From e420201b00f31f3ace4fb25a4d2a41b12aa0cb55 Mon Sep 17 00:00:00 2001 From: akwizgran Date: Fri, 7 May 2021 14:48:25 +0100 Subject: [PATCH 14/77] Implement RemovableDriveWriterTask, except for progress updates. --- .../plugin/file/RemovableDriveReaderTask.java | 15 +--- .../file/RemovableDriveTaskFactoryImpl.java | 6 +- .../plugin/file/RemovableDriveTaskImpl.java | 10 +++ .../plugin/file/RemovableDriveWriterTask.java | 84 +++++++++++++++---- 4 files changed, 85 insertions(+), 30 deletions(-) diff --git a/bramble-core/src/main/java/org/briarproject/bramble/plugin/file/RemovableDriveReaderTask.java b/bramble-core/src/main/java/org/briarproject/bramble/plugin/file/RemovableDriveReaderTask.java index 2e0f02e0b..e51743332 100644 --- a/bramble-core/src/main/java/org/briarproject/bramble/plugin/file/RemovableDriveReaderTask.java +++ b/bramble-core/src/main/java/org/briarproject/bramble/plugin/file/RemovableDriveReaderTask.java @@ -14,10 +14,8 @@ import java.io.File; import java.io.IOException; import java.io.InputStream; import java.util.concurrent.Executor; -import java.util.concurrent.atomic.AtomicLong; import java.util.logging.Logger; -import static java.lang.Math.min; import static java.util.logging.Logger.getLogger; import static org.briarproject.bramble.api.plugin.file.RemovableDriveConstants.ID; @@ -28,9 +26,6 @@ class RemovableDriveReaderTask extends RemovableDriveTaskImpl private final static Logger LOG = getLogger(RemovableDriveReaderTask.class.getName()); - private final AtomicLong fileLength = new AtomicLong(0); - private final AtomicLong totalMessageLength = new AtomicLong(0); - RemovableDriveReaderTask( Executor eventExecutor, PluginManager pluginManager, @@ -49,11 +44,11 @@ class RemovableDriveReaderTask extends RemovableDriveTaskImpl getPlugin().createReader(createProperties()); if (r == null) { LOG.warning("Failed to create reader"); - registry.removeReader(contactId, RemovableDriveReaderTask.this); + registry.removeReader(contactId, this); visitObservers(o -> o.onCompletion(false)); return; } - fileLength.set(file.length()); + progressTotal.set(file.length()); eventBus.addListener(this); connectionManager.manageIncomingConnection(ID, new DecoratedReader(r)); } @@ -69,12 +64,6 @@ class RemovableDriveReaderTask extends RemovableDriveTaskImpl } } - private void updateProgress(int messageLength) { - long done = totalMessageLength.addAndGet(messageLength); - long total = fileLength.get(); - visitObservers(o -> o.onProgress(min(done, total), total)); - } - private class DecoratedReader implements TransportConnectionReader { private final TransportConnectionReader delegate; diff --git a/bramble-core/src/main/java/org/briarproject/bramble/plugin/file/RemovableDriveTaskFactoryImpl.java b/bramble-core/src/main/java/org/briarproject/bramble/plugin/file/RemovableDriveTaskFactoryImpl.java index ad1f6ee06..dbf4b3bc7 100644 --- a/bramble-core/src/main/java/org/briarproject/bramble/plugin/file/RemovableDriveTaskFactoryImpl.java +++ b/bramble-core/src/main/java/org/briarproject/bramble/plugin/file/RemovableDriveTaskFactoryImpl.java @@ -2,6 +2,7 @@ package org.briarproject.bramble.plugin.file; import org.briarproject.bramble.api.connection.ConnectionManager; import org.briarproject.bramble.api.contact.ContactId; +import org.briarproject.bramble.api.db.DatabaseComponent; import org.briarproject.bramble.api.event.EventBus; import org.briarproject.bramble.api.event.EventExecutor; import org.briarproject.bramble.api.nullsafety.NotNullByDefault; @@ -18,6 +19,7 @@ import javax.inject.Inject; @NotNullByDefault class RemovableDriveTaskFactoryImpl implements RemovableDriveTaskFactory { + private final DatabaseComponent db; private final Executor eventExecutor; private final PluginManager pluginManager; private final ConnectionManager connectionManager; @@ -25,10 +27,12 @@ class RemovableDriveTaskFactoryImpl implements RemovableDriveTaskFactory { @Inject RemovableDriveTaskFactoryImpl( + DatabaseComponent db, @EventExecutor Executor eventExecutor, PluginManager pluginManager, ConnectionManager connectionManager, EventBus eventBus) { + this.db = db; this.eventExecutor = eventExecutor; this.pluginManager = pluginManager; this.connectionManager = connectionManager; @@ -45,7 +49,7 @@ class RemovableDriveTaskFactoryImpl implements RemovableDriveTaskFactory { @Override public RemovableDriveTask createWriter(RemovableDriveTaskRegistry registry, ContactId c, File f) { - return new RemovableDriveWriterTask(eventExecutor, pluginManager, + return new RemovableDriveWriterTask(db, eventExecutor, pluginManager, connectionManager, eventBus, registry, c, f); } } diff --git a/bramble-core/src/main/java/org/briarproject/bramble/plugin/file/RemovableDriveTaskImpl.java b/bramble-core/src/main/java/org/briarproject/bramble/plugin/file/RemovableDriveTaskImpl.java index 4984e3876..f7a919ed7 100644 --- a/bramble-core/src/main/java/org/briarproject/bramble/plugin/file/RemovableDriveTaskImpl.java +++ b/bramble-core/src/main/java/org/briarproject/bramble/plugin/file/RemovableDriveTaskImpl.java @@ -14,9 +14,11 @@ import java.io.File; import java.util.List; import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.Executor; +import java.util.concurrent.atomic.AtomicLong; import javax.annotation.concurrent.ThreadSafe; +import static java.lang.Math.min; 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.api.plugin.file.RemovableDriveConstants.ID; @@ -33,6 +35,8 @@ abstract class RemovableDriveTaskImpl implements RemovableDriveTask { final ContactId contactId; final File file; private final List observers = new CopyOnWriteArrayList<>(); + final AtomicLong progressTotal = new AtomicLong(0); + private final AtomicLong progressDone = new AtomicLong(0); RemovableDriveTaskImpl( Executor eventExecutor, @@ -81,4 +85,10 @@ abstract class RemovableDriveTaskImpl implements RemovableDriveTask { p.put(PROP_PATH, file.getAbsolutePath()); return p; } + + void updateProgress(long progress) { + long done = progressDone.addAndGet(progress); + long total = progressTotal.get(); + visitObservers(o -> o.onProgress(min(done, total), total)); + } } diff --git a/bramble-core/src/main/java/org/briarproject/bramble/plugin/file/RemovableDriveWriterTask.java b/bramble-core/src/main/java/org/briarproject/bramble/plugin/file/RemovableDriveWriterTask.java index 858d6a19c..671921607 100644 --- a/bramble-core/src/main/java/org/briarproject/bramble/plugin/file/RemovableDriveWriterTask.java +++ b/bramble-core/src/main/java/org/briarproject/bramble/plugin/file/RemovableDriveWriterTask.java @@ -2,29 +2,36 @@ package org.briarproject.bramble.plugin.file; import org.briarproject.bramble.api.connection.ConnectionManager; import org.briarproject.bramble.api.contact.ContactId; +import org.briarproject.bramble.api.db.DatabaseComponent; +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.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.plugin.PluginManager; +import org.briarproject.bramble.api.plugin.TransportConnectionWriter; +import org.briarproject.bramble.api.sync.event.MessagesSentEvent; import java.io.File; -import java.io.FileOutputStream; import java.io.IOException; import java.io.OutputStream; import java.util.concurrent.Executor; import java.util.logging.Logger; -import static java.util.logging.Level.WARNING; +import static java.util.logging.Level.INFO; import static java.util.logging.Logger.getLogger; -import static org.briarproject.bramble.util.IoUtils.tryToClose; -import static org.briarproject.bramble.util.LogUtils.logException; +import static org.briarproject.bramble.api.plugin.file.RemovableDriveConstants.ID; @NotNullByDefault -class RemovableDriveWriterTask extends RemovableDriveTaskImpl { +class RemovableDriveWriterTask extends RemovableDriveTaskImpl + implements EventListener { private static final Logger LOG = getLogger(RemovableDriveWriterTask.class.getName()); + private final DatabaseComponent db; + RemovableDriveWriterTask( + DatabaseComponent db, Executor eventExecutor, PluginManager pluginManager, ConnectionManager connectionManager, @@ -34,22 +41,67 @@ class RemovableDriveWriterTask extends RemovableDriveTaskImpl { File file) { super(eventExecutor, pluginManager, connectionManager, eventBus, registry, contactId, file); + this.db = db; } @Override public void run() { - // TODO - OutputStream out = null; - try { - visitObservers(o -> o.onProgress(0, 100)); - out = new FileOutputStream(file); - visitObservers(o -> o.onCompletion(true)); - } catch (IOException e) { - logException(LOG, WARNING, e); - visitObservers(o -> o.onCompletion(false)); - } finally { - tryToClose(out, LOG, WARNING); + TransportConnectionWriter w = + getPlugin().createWriter(createProperties()); + if (w == null) { + LOG.warning("Failed to create writer"); registry.removeWriter(contactId, this); + visitObservers(o -> o.onCompletion(false)); + return; + } + // TODO: Get total bytes to send from DB + eventBus.addListener(this); + connectionManager.manageOutgoingConnection(contactId, ID, + new DecoratedWriter(w)); + } + + @Override + public void eventOccurred(Event e) { + if (e instanceof MessagesSentEvent) { + MessagesSentEvent m = (MessagesSentEvent) e; + if (contactId.equals(m.getContactId())) { + if (LOG.isLoggable(INFO)) { + LOG.info(m.getMessageIds().size() + " messages sent"); + } + // TODO: Update progress + } + } + } + + private class DecoratedWriter implements TransportConnectionWriter { + + private final TransportConnectionWriter delegate; + + private DecoratedWriter(TransportConnectionWriter delegate) { + this.delegate = delegate; + } + + @Override + public int getMaxLatency() { + return delegate.getMaxLatency(); + } + + @Override + public int getMaxIdleTime() { + return delegate.getMaxIdleTime(); + } + + @Override + public OutputStream getOutputStream() throws IOException { + return delegate.getOutputStream(); + } + + @Override + public void dispose(boolean exception) throws IOException { + delegate.dispose(exception); + registry.removeWriter(contactId, RemovableDriveWriterTask.this); + eventBus.removeListener(RemovableDriveWriterTask.this); + visitObservers(o -> o.onCompletion(!exception)); } } } From bca6f1506ea23954d8e774f3fb65eda0e8f540c4 Mon Sep 17 00:00:00 2001 From: akwizgran Date: Fri, 7 May 2021 16:02:09 +0100 Subject: [PATCH 15/77] Add integration test for syncing via removable drives. --- .../file/RemovableDriveIntegrationTest.java | 177 ++++++++++++++++++ ...emovableDriveIntegrationTestComponent.java | 53 ++++++ .../RemovableDriveIntegrationTestModule.java | 81 ++++++++ 3 files changed, 311 insertions(+) create mode 100644 bramble-core/src/test/java/org/briarproject/bramble/plugin/file/RemovableDriveIntegrationTest.java create mode 100644 bramble-core/src/test/java/org/briarproject/bramble/plugin/file/RemovableDriveIntegrationTestComponent.java create mode 100644 bramble-core/src/test/java/org/briarproject/bramble/plugin/file/RemovableDriveIntegrationTestModule.java diff --git a/bramble-core/src/test/java/org/briarproject/bramble/plugin/file/RemovableDriveIntegrationTest.java b/bramble-core/src/test/java/org/briarproject/bramble/plugin/file/RemovableDriveIntegrationTest.java new file mode 100644 index 000000000..acd4844dc --- /dev/null +++ b/bramble-core/src/test/java/org/briarproject/bramble/plugin/file/RemovableDriveIntegrationTest.java @@ -0,0 +1,177 @@ +package org.briarproject.bramble.plugin.file; + +import org.briarproject.bramble.api.contact.ContactId; +import org.briarproject.bramble.api.contact.ContactManager; +import org.briarproject.bramble.api.crypto.SecretKey; +import org.briarproject.bramble.api.event.Event; +import org.briarproject.bramble.api.event.EventListener; +import org.briarproject.bramble.api.identity.Author; +import org.briarproject.bramble.api.identity.Identity; +import org.briarproject.bramble.api.identity.IdentityManager; +import org.briarproject.bramble.api.lifecycle.LifecycleManager; +import org.briarproject.bramble.api.nullsafety.NotNullByDefault; +import org.briarproject.bramble.api.plugin.file.RemovableDriveTask; +import org.briarproject.bramble.api.plugin.file.RemovableDriveTask.Observer; +import org.briarproject.bramble.api.sync.event.MessageStateChangedEvent; +import org.briarproject.bramble.test.BrambleTestCase; +import org.briarproject.bramble.test.TestDatabaseConfigModule; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import java.io.File; +import java.util.concurrent.CountDownLatch; + +import static java.util.concurrent.TimeUnit.MILLISECONDS; +import static org.briarproject.bramble.api.sync.validation.MessageState.DELIVERED; +import static org.briarproject.bramble.test.TestUtils.deleteTestDirectory; +import static org.briarproject.bramble.test.TestUtils.getSecretKey; +import static org.briarproject.bramble.test.TestUtils.getTestDirectory; +import static org.junit.Assert.assertTrue; + +public class RemovableDriveIntegrationTest extends BrambleTestCase { + + private static final int TIMEOUT_MS = 5_000; + + private final File testDir = getTestDirectory(); + private final File aliceDir = new File(testDir, "alice"); + private final File bobDir = new File(testDir, "bob"); + + private final SecretKey rootKey = getSecretKey(); + private final long timestamp = System.currentTimeMillis(); + + private RemovableDriveIntegrationTestComponent alice, bob; + + @Before + public void setUp() { + assertTrue(testDir.mkdirs()); + alice = DaggerRemovableDriveIntegrationTestComponent.builder() + .testDatabaseConfigModule( + new TestDatabaseConfigModule(aliceDir)).build(); + RemovableDriveIntegrationTestComponent.Helper + .injectEagerSingletons(alice); + bob = DaggerRemovableDriveIntegrationTestComponent.builder() + .testDatabaseConfigModule( + new TestDatabaseConfigModule(bobDir)).build(); + RemovableDriveIntegrationTestComponent.Helper + .injectEagerSingletons(bob); + } + + @Test + public void testWriteAndRead() throws Exception { + // Create the identities + Identity aliceIdentity = + alice.getIdentityManager().createIdentity("Alice"); + Identity bobIdentity = bob.getIdentityManager().createIdentity("Bob"); + // Set up the devices and get the contact IDs + ContactId bobId = setUp(alice, aliceIdentity, + bobIdentity.getLocalAuthor(), true); + ContactId aliceId = setUp(bob, bobIdentity, + aliceIdentity.getLocalAuthor(), false); + // Sync Alice's client versions and transport properties + read(bob, aliceId, write(alice, bobId), 2); + // Sync Bob's client versions and transport properties + read(alice, bobId, write(bob, aliceId), 2); + } + + private ContactId setUp(RemovableDriveIntegrationTestComponent device, + Identity local, Author remote, boolean alice) throws Exception { + // Add an identity for the user + IdentityManager identityManager = device.getIdentityManager(); + identityManager.registerIdentity(local); + // Start the lifecycle manager + LifecycleManager lifecycleManager = device.getLifecycleManager(); + lifecycleManager.startServices(getSecretKey()); + lifecycleManager.waitForStartup(); + // Add the other user as a contact + ContactManager contactManager = device.getContactManager(); + return contactManager.addContact(remote, local.getId(), rootKey, + timestamp, alice, true, true); + } + + @SuppressWarnings("SameParameterValue") + private void read(RemovableDriveIntegrationTestComponent device, + ContactId contactId, File file, int deliveries) throws Exception { + // Listen for message deliveries + MessageDeliveryListener listener = + new MessageDeliveryListener(deliveries); + device.getEventBus().addListener(listener); + // Read the incoming stream + RemovableDriveTask reader = device.getRemovableDriveManager() + .startReaderTask(contactId, file); + CountDownLatch disposedLatch = new CountDownLatch(1); + reader.addObserver(new Observer() { + @Override + public void onProgress(long done, long total) { + } + + @Override + public void onCompletion(boolean success) { + disposedLatch.countDown(); + } + }); + // Wait for the messages to be delivered + assertTrue(listener.delivered.await(TIMEOUT_MS, MILLISECONDS)); + // Clean up the listener + device.getEventBus().removeListener(listener); + // Wait for the reader to be disposed + disposedLatch.await(TIMEOUT_MS, MILLISECONDS); + } + + private File write(RemovableDriveIntegrationTestComponent device, + ContactId contactId) throws Exception { + // Write the outgoing stream to a file + File file = File.createTempFile("sync", ".tmp", testDir); + RemovableDriveTask writer = device.getRemovableDriveManager() + .startWriterTask(contactId, file); + CountDownLatch disposedLatch = new CountDownLatch(1); + writer.addObserver(new Observer() { + @Override + public void onProgress(long done, long total) { + } + + @Override + public void onCompletion(boolean success) { + disposedLatch.countDown(); + } + }); + // Wait for the writer to be disposed + disposedLatch.await(TIMEOUT_MS, MILLISECONDS); + // Return the file containing the stream + return file; + } + + private void tearDown(RemovableDriveIntegrationTestComponent device) + throws Exception { + // Stop the lifecycle manager + LifecycleManager lifecycleManager = device.getLifecycleManager(); + lifecycleManager.stopServices(); + lifecycleManager.waitForShutdown(); + } + + @After + public void tearDown() throws Exception { + // Tear down the devices + tearDown(alice); + tearDown(bob); + deleteTestDirectory(testDir); + } + + @NotNullByDefault + private static class MessageDeliveryListener implements EventListener { + + private final CountDownLatch delivered; + + private MessageDeliveryListener(int deliveries) { + delivered = new CountDownLatch(deliveries); + } + + @Override + public void eventOccurred(Event e) { + if (e instanceof MessageStateChangedEvent) { + MessageStateChangedEvent m = (MessageStateChangedEvent) e; + if (m.getState().equals(DELIVERED)) delivered.countDown(); + } + } + } +} diff --git a/bramble-core/src/test/java/org/briarproject/bramble/plugin/file/RemovableDriveIntegrationTestComponent.java b/bramble-core/src/test/java/org/briarproject/bramble/plugin/file/RemovableDriveIntegrationTestComponent.java new file mode 100644 index 000000000..9c5c3153d --- /dev/null +++ b/bramble-core/src/test/java/org/briarproject/bramble/plugin/file/RemovableDriveIntegrationTestComponent.java @@ -0,0 +1,53 @@ +package org.briarproject.bramble.plugin.file; + +import org.briarproject.bramble.BrambleCoreEagerSingletons; +import org.briarproject.bramble.BrambleCoreModule; +import org.briarproject.bramble.api.contact.ContactManager; +import org.briarproject.bramble.api.event.EventBus; +import org.briarproject.bramble.api.identity.IdentityManager; +import org.briarproject.bramble.api.lifecycle.LifecycleManager; +import org.briarproject.bramble.api.plugin.file.RemovableDriveManager; +import org.briarproject.bramble.battery.DefaultBatteryManagerModule; +import org.briarproject.bramble.event.DefaultEventExecutorModule; +import org.briarproject.bramble.system.DefaultWakefulIoExecutorModule; +import org.briarproject.bramble.system.TimeTravelModule; +import org.briarproject.bramble.test.TestDatabaseConfigModule; +import org.briarproject.bramble.test.TestSecureRandomModule; + +import javax.inject.Singleton; + +import dagger.Component; + +@Singleton +@Component(modules = { + BrambleCoreModule.class, + DefaultBatteryManagerModule.class, + DefaultEventExecutorModule.class, + DefaultWakefulIoExecutorModule.class, + TestDatabaseConfigModule.class, + RemovableDriveIntegrationTestModule.class, + RemovableDriveModule.class, + TestSecureRandomModule.class, + TimeTravelModule.class +}) +interface RemovableDriveIntegrationTestComponent + extends BrambleCoreEagerSingletons { + + ContactManager getContactManager(); + + EventBus getEventBus(); + + IdentityManager getIdentityManager(); + + LifecycleManager getLifecycleManager(); + + RemovableDriveManager getRemovableDriveManager(); + + class Helper { + + public static void injectEagerSingletons( + RemovableDriveIntegrationTestComponent c) { + BrambleCoreEagerSingletons.Helper.injectEagerSingletons(c); + } + } +} diff --git a/bramble-core/src/test/java/org/briarproject/bramble/plugin/file/RemovableDriveIntegrationTestModule.java b/bramble-core/src/test/java/org/briarproject/bramble/plugin/file/RemovableDriveIntegrationTestModule.java new file mode 100644 index 000000000..7b4699e10 --- /dev/null +++ b/bramble-core/src/test/java/org/briarproject/bramble/plugin/file/RemovableDriveIntegrationTestModule.java @@ -0,0 +1,81 @@ +package org.briarproject.bramble.plugin.file; + +import org.briarproject.bramble.api.FeatureFlags; +import org.briarproject.bramble.api.nullsafety.NotNullByDefault; +import org.briarproject.bramble.api.plugin.PluginConfig; +import org.briarproject.bramble.api.plugin.TransportId; +import org.briarproject.bramble.api.plugin.duplex.DuplexPluginFactory; +import org.briarproject.bramble.api.plugin.simplex.SimplexPluginFactory; + +import java.util.Collection; +import java.util.List; +import java.util.Map; + +import javax.inject.Singleton; + +import dagger.Module; +import dagger.Provides; + +import static java.util.Collections.emptyList; +import static java.util.Collections.emptyMap; +import static java.util.Collections.singletonList; + +@Module +class RemovableDriveIntegrationTestModule { + + @Provides + @Singleton + PluginConfig providePluginConfig(RemovableDrivePluginFactory drive) { + @NotNullByDefault + PluginConfig pluginConfig = new PluginConfig() { + + @Override + public Collection getDuplexFactories() { + return emptyList(); + } + + @Override + public Collection getSimplexFactories() { + return singletonList(drive); + } + + @Override + public boolean shouldPoll() { + return false; + } + + @Override + public Map> getTransportPreferences() { + return emptyMap(); + } + + }; + return pluginConfig; + } + + @Provides + FeatureFlags provideFeatureFlags() { + return new FeatureFlags() { + + @Override + public boolean shouldEnableImageAttachments() { + return true; + } + + @Override + public boolean shouldEnableProfilePictures() { + return true; + } + + @Override + public boolean shouldEnableDisappearingMessages() { + return true; + } + + @Override + public boolean shouldEnableConnectViaBluetooth() { + return true; + } + }; + } +} From a198e7d08eadd8ddb9bfa63afa542f7bbb4a24a1 Mon Sep 17 00:00:00 2001 From: akwizgran Date: Fri, 7 May 2021 16:30:53 +0100 Subject: [PATCH 16/77] Ensure that observers see the final state even if they're added late. --- .../api/plugin/file/RemovableDriveTask.java | 40 ++++++++--- .../plugin/file/RemovableDriveReaderTask.java | 8 +-- .../plugin/file/RemovableDriveTaskImpl.java | 72 ++++++++++++++----- .../plugin/file/RemovableDriveWriterTask.java | 4 +- .../file/RemovableDriveIntegrationTest.java | 23 ++---- 5 files changed, 94 insertions(+), 53 deletions(-) diff --git a/bramble-api/src/main/java/org/briarproject/bramble/api/plugin/file/RemovableDriveTask.java b/bramble-api/src/main/java/org/briarproject/bramble/api/plugin/file/RemovableDriveTask.java index c4f2e00a9..5388dab31 100644 --- a/bramble-api/src/main/java/org/briarproject/bramble/api/plugin/file/RemovableDriveTask.java +++ b/bramble-api/src/main/java/org/briarproject/bramble/api/plugin/file/RemovableDriveTask.java @@ -1,6 +1,6 @@ package org.briarproject.bramble.api.plugin.file; -import org.briarproject.bramble.api.event.EventExecutor; +import org.briarproject.bramble.api.Consumer; import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import java.io.File; @@ -14,21 +14,43 @@ public interface RemovableDriveTask extends Runnable { File getFile(); /** - * Adds an observer to the task. + * Adds an observer to the task. The observer will be notified of state + * changes on the event thread. If the task has already finished, the + * observer will be notified of its final state. */ - void addObserver(Observer o); + void addObserver(Consumer observer); /** * Removes an observer from the task. */ - void removeObserver(Observer o); + void removeObserver(Consumer observer); - interface Observer { + class State { - @EventExecutor - void onProgress(long done, long total); + private final long done, total; + private final boolean finished, success; - @EventExecutor - void onCompletion(boolean success); + public State(long done, long total, boolean finished, boolean success) { + this.done = done; + this.total = total; + this.finished = finished; + this.success = success; + } + + public long getDone() { + return done; + } + + public long getTotal() { + return total; + } + + public boolean isFinished() { + return finished; + } + + public boolean isSuccess() { + return success; + } } } diff --git a/bramble-core/src/main/java/org/briarproject/bramble/plugin/file/RemovableDriveReaderTask.java b/bramble-core/src/main/java/org/briarproject/bramble/plugin/file/RemovableDriveReaderTask.java index e51743332..cc93c9099 100644 --- a/bramble-core/src/main/java/org/briarproject/bramble/plugin/file/RemovableDriveReaderTask.java +++ b/bramble-core/src/main/java/org/briarproject/bramble/plugin/file/RemovableDriveReaderTask.java @@ -45,10 +45,10 @@ class RemovableDriveReaderTask extends RemovableDriveTaskImpl if (r == null) { LOG.warning("Failed to create reader"); registry.removeReader(contactId, this); - visitObservers(o -> o.onCompletion(false)); + setSuccess(false); return; } - progressTotal.set(file.length()); + setTotal(file.length()); eventBus.addListener(this); connectionManager.manageIncomingConnection(ID, new DecoratedReader(r)); } @@ -59,7 +59,7 @@ class RemovableDriveReaderTask extends RemovableDriveTaskImpl MessageAddedEvent m = (MessageAddedEvent) e; if (contactId.equals(m.getContactId())) { LOG.info("Message received"); - updateProgress(m.getMessage().getRawLength()); + addDone(m.getMessage().getRawLength()); } } } @@ -83,7 +83,7 @@ class RemovableDriveReaderTask extends RemovableDriveTaskImpl delegate.dispose(exception, recognised); registry.removeReader(contactId, RemovableDriveReaderTask.this); eventBus.removeListener(RemovableDriveReaderTask.this); - visitObservers(o -> o.onCompletion(!exception && recognised)); + setSuccess(!exception && recognised); } } } diff --git a/bramble-core/src/main/java/org/briarproject/bramble/plugin/file/RemovableDriveTaskImpl.java b/bramble-core/src/main/java/org/briarproject/bramble/plugin/file/RemovableDriveTaskImpl.java index f7a919ed7..de792f644 100644 --- a/bramble-core/src/main/java/org/briarproject/bramble/plugin/file/RemovableDriveTaskImpl.java +++ b/bramble-core/src/main/java/org/briarproject/bramble/plugin/file/RemovableDriveTaskImpl.java @@ -11,11 +11,11 @@ import org.briarproject.bramble.api.plugin.simplex.SimplexPlugin; import org.briarproject.bramble.api.properties.TransportProperties; import java.io.File; +import java.util.ArrayList; import java.util.List; -import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.Executor; -import java.util.concurrent.atomic.AtomicLong; +import javax.annotation.concurrent.GuardedBy; import javax.annotation.concurrent.ThreadSafe; import static java.lang.Math.min; @@ -34,9 +34,12 @@ abstract class RemovableDriveTaskImpl implements RemovableDriveTask { final RemovableDriveTaskRegistry registry; final ContactId contactId; final File file; - private final List observers = new CopyOnWriteArrayList<>(); - final AtomicLong progressTotal = new AtomicLong(0); - private final AtomicLong progressDone = new AtomicLong(0); + + private final Object lock = new Object(); + @GuardedBy("lock") + private final List> observers = new ArrayList<>(); + @GuardedBy("lock") + private State state = new State(0, 0, false, false); RemovableDriveTaskImpl( Executor eventExecutor, @@ -61,19 +64,22 @@ abstract class RemovableDriveTaskImpl implements RemovableDriveTask { } @Override - public void addObserver(Observer o) { - observers.add(o); + public void addObserver(Consumer o) { + State state; + synchronized (lock) { + observers.add(o); + state = this.state; + } + if (state.isFinished()) { + eventExecutor.execute(() -> o.accept(state)); + } } @Override - public void removeObserver(Observer o) { - observers.remove(o); - } - - void visitObservers(Consumer visitor) { - eventExecutor.execute(() -> { - for (Observer o : observers) visitor.accept(o); - }); + public void removeObserver(Consumer o) { + synchronized (lock) { + observers.remove(o); + } } SimplexPlugin getPlugin() { @@ -86,9 +92,37 @@ abstract class RemovableDriveTaskImpl implements RemovableDriveTask { return p; } - void updateProgress(long progress) { - long done = progressDone.addAndGet(progress); - long total = progressTotal.get(); - visitObservers(o -> o.onProgress(min(done, total), total)); + void setTotal(long total) { + synchronized (lock) { + state = new State(state.getDone(), total, state.isFinished(), + state.isSuccess()); + notifyObservers(); + } + } + + void addDone(long done) { + synchronized (lock) { + // Done and total come from different sources; make them consistent + done = min(state.getDone() + done, state.getTotal()); + state = new State(done, state.getTotal(), state.isFinished(), + state.isSuccess()); + } + notifyObservers(); + } + + void setSuccess(boolean success) { + synchronized (lock) { + state = new State(state.getDone(), state.getTotal(), true, success); + } + notifyObservers(); + } + + @GuardedBy("lock") + private void notifyObservers() { + List> observers = new ArrayList<>(this.observers); + State state = this.state; + eventExecutor.execute(() -> { + for (Consumer o : observers) o.accept(state); + }); } } diff --git a/bramble-core/src/main/java/org/briarproject/bramble/plugin/file/RemovableDriveWriterTask.java b/bramble-core/src/main/java/org/briarproject/bramble/plugin/file/RemovableDriveWriterTask.java index 671921607..ec02e417b 100644 --- a/bramble-core/src/main/java/org/briarproject/bramble/plugin/file/RemovableDriveWriterTask.java +++ b/bramble-core/src/main/java/org/briarproject/bramble/plugin/file/RemovableDriveWriterTask.java @@ -51,7 +51,7 @@ class RemovableDriveWriterTask extends RemovableDriveTaskImpl if (w == null) { LOG.warning("Failed to create writer"); registry.removeWriter(contactId, this); - visitObservers(o -> o.onCompletion(false)); + setSuccess(false); return; } // TODO: Get total bytes to send from DB @@ -101,7 +101,7 @@ class RemovableDriveWriterTask extends RemovableDriveTaskImpl delegate.dispose(exception); registry.removeWriter(contactId, RemovableDriveWriterTask.this); eventBus.removeListener(RemovableDriveWriterTask.this); - visitObservers(o -> o.onCompletion(!exception)); + setSuccess(!exception); } } } diff --git a/bramble-core/src/test/java/org/briarproject/bramble/plugin/file/RemovableDriveIntegrationTest.java b/bramble-core/src/test/java/org/briarproject/bramble/plugin/file/RemovableDriveIntegrationTest.java index acd4844dc..56a40bcbe 100644 --- a/bramble-core/src/test/java/org/briarproject/bramble/plugin/file/RemovableDriveIntegrationTest.java +++ b/bramble-core/src/test/java/org/briarproject/bramble/plugin/file/RemovableDriveIntegrationTest.java @@ -11,7 +11,6 @@ import org.briarproject.bramble.api.identity.IdentityManager; import org.briarproject.bramble.api.lifecycle.LifecycleManager; import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.plugin.file.RemovableDriveTask; -import org.briarproject.bramble.api.plugin.file.RemovableDriveTask.Observer; import org.briarproject.bramble.api.sync.event.MessageStateChangedEvent; import org.briarproject.bramble.test.BrambleTestCase; import org.briarproject.bramble.test.TestDatabaseConfigModule; @@ -100,15 +99,8 @@ public class RemovableDriveIntegrationTest extends BrambleTestCase { RemovableDriveTask reader = device.getRemovableDriveManager() .startReaderTask(contactId, file); CountDownLatch disposedLatch = new CountDownLatch(1); - reader.addObserver(new Observer() { - @Override - public void onProgress(long done, long total) { - } - - @Override - public void onCompletion(boolean success) { - disposedLatch.countDown(); - } + reader.addObserver(state -> { + if (state.isFinished()) disposedLatch.countDown(); }); // Wait for the messages to be delivered assertTrue(listener.delivered.await(TIMEOUT_MS, MILLISECONDS)); @@ -125,15 +117,8 @@ public class RemovableDriveIntegrationTest extends BrambleTestCase { RemovableDriveTask writer = device.getRemovableDriveManager() .startWriterTask(contactId, file); CountDownLatch disposedLatch = new CountDownLatch(1); - writer.addObserver(new Observer() { - @Override - public void onProgress(long done, long total) { - } - - @Override - public void onCompletion(boolean success) { - disposedLatch.countDown(); - } + writer.addObserver(state -> { + if (state.isFinished()) disposedLatch.countDown(); }); // Wait for the writer to be disposed disposedLatch.await(TIMEOUT_MS, MILLISECONDS); From 0ce0551f0d82ddaf92fb79dfbfa952cd0e54bb4e Mon Sep 17 00:00:00 2001 From: akwizgran Date: Mon, 10 May 2021 14:14:10 +0100 Subject: [PATCH 17/77] Update progress of writer task. --- .../plugin/file/RemovableDriveWriterTask.java | 21 +++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/bramble-core/src/main/java/org/briarproject/bramble/plugin/file/RemovableDriveWriterTask.java b/bramble-core/src/main/java/org/briarproject/bramble/plugin/file/RemovableDriveWriterTask.java index ec02e417b..d9a9ed75c 100644 --- a/bramble-core/src/main/java/org/briarproject/bramble/plugin/file/RemovableDriveWriterTask.java +++ b/bramble-core/src/main/java/org/briarproject/bramble/plugin/file/RemovableDriveWriterTask.java @@ -3,12 +3,14 @@ package org.briarproject.bramble.plugin.file; import org.briarproject.bramble.api.connection.ConnectionManager; import org.briarproject.bramble.api.contact.ContactId; import org.briarproject.bramble.api.db.DatabaseComponent; +import org.briarproject.bramble.api.db.DbException; 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.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.plugin.PluginManager; import org.briarproject.bramble.api.plugin.TransportConnectionWriter; +import org.briarproject.bramble.api.plugin.simplex.SimplexPlugin; import org.briarproject.bramble.api.sync.event.MessagesSentEvent; import java.io.File; @@ -18,8 +20,10 @@ import java.util.concurrent.Executor; import java.util.logging.Logger; import static java.util.logging.Level.INFO; +import static java.util.logging.Level.WARNING; import static java.util.logging.Logger.getLogger; import static org.briarproject.bramble.api.plugin.file.RemovableDriveConstants.ID; +import static org.briarproject.bramble.util.LogUtils.logException; @NotNullByDefault class RemovableDriveWriterTask extends RemovableDriveTaskImpl @@ -46,15 +50,24 @@ class RemovableDriveWriterTask extends RemovableDriveTaskImpl @Override public void run() { - TransportConnectionWriter w = - getPlugin().createWriter(createProperties()); + SimplexPlugin plugin = getPlugin(); + TransportConnectionWriter w = plugin.createWriter(createProperties()); if (w == null) { LOG.warning("Failed to create writer"); registry.removeWriter(contactId, this); setSuccess(false); return; } - // TODO: Get total bytes to send from DB + int maxLatency = plugin.getMaxLatency(); + try { + setTotal(db.transactionWithResult(true, txn -> + db.getMessageBytesToSend(txn, contactId, maxLatency))); + } catch (DbException e) { + logException(LOG, WARNING, e); + registry.removeWriter(contactId, this); + setSuccess(false); + return; + } eventBus.addListener(this); connectionManager.manageOutgoingConnection(contactId, ID, new DecoratedWriter(w)); @@ -68,7 +81,7 @@ class RemovableDriveWriterTask extends RemovableDriveTaskImpl if (LOG.isLoggable(INFO)) { LOG.info(m.getMessageIds().size() + " messages sent"); } - // TODO: Update progress + addDone(m.getTotalLength()); } } } From eae329cdfad5411e6851aeb2c1a0899682663e43 Mon Sep 17 00:00:00 2001 From: akwizgran Date: Mon, 10 May 2021 14:36:01 +0100 Subject: [PATCH 18/77] Refactor manager and tasks to remove reliance on files. --- .../api/plugin/file/RemovableDriveManager.java | 17 +++++++++-------- .../api/plugin/file/RemovableDriveTask.java | 17 +++++++++++++---- .../plugin/file/RemovableDriveManagerImpl.java | 12 +++++++----- .../plugin/file/RemovableDriveReaderTask.java | 9 ++++----- .../plugin/file/RemovableDriveTaskFactory.java | 7 +++---- .../file/RemovableDriveTaskFactoryImpl.java | 10 +++++----- .../plugin/file/RemovableDriveTaskImpl.java | 18 +++++------------- .../plugin/file/RemovableDriveWriterTask.java | 8 ++++---- .../file/RemovableDriveIntegrationTest.java | 10 ++++++++-- 9 files changed, 58 insertions(+), 50 deletions(-) diff --git a/bramble-api/src/main/java/org/briarproject/bramble/api/plugin/file/RemovableDriveManager.java b/bramble-api/src/main/java/org/briarproject/bramble/api/plugin/file/RemovableDriveManager.java index 241582dab..081b4362a 100644 --- a/bramble-api/src/main/java/org/briarproject/bramble/api/plugin/file/RemovableDriveManager.java +++ b/bramble-api/src/main/java/org/briarproject/bramble/api/plugin/file/RemovableDriveManager.java @@ -2,8 +2,7 @@ package org.briarproject.bramble.api.plugin.file; import org.briarproject.bramble.api.contact.ContactId; import org.briarproject.bramble.api.nullsafety.NotNullByDefault; - -import java.io.File; +import org.briarproject.bramble.api.properties.TransportProperties; import javax.annotation.Nullable; @@ -26,15 +25,17 @@ public interface RemovableDriveManager { /** * Starts and returns a reader task for the given contact, reading from - * the given file. If a reader task for the contact is already running, - * it will be returned and the file argument will be ignored. + * a stream described by the given transport properties. If a reader task + * for the contact is already running, it will be returned and the + * transport properties will be ignored. */ - RemovableDriveTask startReaderTask(ContactId c, File f); + RemovableDriveTask startReaderTask(ContactId c, TransportProperties p); /** * Starts and returns a writer task for the given contact, writing to - * the given file. If a writer task for the contact is already running, - * it will be returned and the file argument will be ignored. + * a stream described by the given transport properties. If a writer task + * for the contact is already running, it will be returned and the + * transport properties will be ignored. */ - RemovableDriveTask startWriterTask(ContactId c, File f); + RemovableDriveTask startWriterTask(ContactId c, TransportProperties p); } diff --git a/bramble-api/src/main/java/org/briarproject/bramble/api/plugin/file/RemovableDriveTask.java b/bramble-api/src/main/java/org/briarproject/bramble/api/plugin/file/RemovableDriveTask.java index 5388dab31..e27413a04 100644 --- a/bramble-api/src/main/java/org/briarproject/bramble/api/plugin/file/RemovableDriveTask.java +++ b/bramble-api/src/main/java/org/briarproject/bramble/api/plugin/file/RemovableDriveTask.java @@ -2,16 +2,16 @@ package org.briarproject.bramble.api.plugin.file; import org.briarproject.bramble.api.Consumer; import org.briarproject.bramble.api.nullsafety.NotNullByDefault; - -import java.io.File; +import org.briarproject.bramble.api.properties.TransportProperties; @NotNullByDefault public interface RemovableDriveTask extends Runnable { /** - * Returns the file that this task is reading from or writing to. + * Returns the {@link TransportProperties} that were used for creating + * this task. */ - File getFile(); + TransportProperties getTransportProperties(); /** * Adds an observer to the task. The observer will be notified of state @@ -37,10 +37,19 @@ public interface RemovableDriveTask extends Runnable { this.success = success; } + /** + * Returns the total length in bytes of the messages read or written + * so far. + */ public long getDone() { return done; } + /** + * Returns the total length in bytes of the messages that will have + * been read or written when the task is complete, or zero if the + * total is unknown. + */ public long getTotal() { return total; } diff --git a/bramble-core/src/main/java/org/briarproject/bramble/plugin/file/RemovableDriveManagerImpl.java b/bramble-core/src/main/java/org/briarproject/bramble/plugin/file/RemovableDriveManagerImpl.java index 3752449fa..262b33a69 100644 --- a/bramble-core/src/main/java/org/briarproject/bramble/plugin/file/RemovableDriveManagerImpl.java +++ b/bramble-core/src/main/java/org/briarproject/bramble/plugin/file/RemovableDriveManagerImpl.java @@ -5,8 +5,8 @@ import org.briarproject.bramble.api.lifecycle.IoExecutor; import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.plugin.file.RemovableDriveManager; import org.briarproject.bramble.api.plugin.file.RemovableDriveTask; +import org.briarproject.bramble.api.properties.TransportProperties; -import java.io.File; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.Executor; @@ -46,8 +46,9 @@ class RemovableDriveManagerImpl } @Override - public RemovableDriveTask startReaderTask(ContactId c, File f) { - RemovableDriveTask task = taskFactory.createReader(this, c, f); + public RemovableDriveTask startReaderTask(ContactId c, + TransportProperties p) { + RemovableDriveTask task = taskFactory.createReader(this, c, p); RemovableDriveTask old = readers.putIfAbsent(c, task); if (old == null) { ioExecutor.execute(task); @@ -58,8 +59,9 @@ class RemovableDriveManagerImpl } @Override - public RemovableDriveTask startWriterTask(ContactId c, File f) { - RemovableDriveTask task = taskFactory.createWriter(this, c, f); + public RemovableDriveTask startWriterTask(ContactId c, + TransportProperties p) { + RemovableDriveTask task = taskFactory.createWriter(this, c, p); RemovableDriveTask old = writers.putIfAbsent(c, task); if (old == null) { ioExecutor.execute(task); diff --git a/bramble-core/src/main/java/org/briarproject/bramble/plugin/file/RemovableDriveReaderTask.java b/bramble-core/src/main/java/org/briarproject/bramble/plugin/file/RemovableDriveReaderTask.java index cc93c9099..5e5b270af 100644 --- a/bramble-core/src/main/java/org/briarproject/bramble/plugin/file/RemovableDriveReaderTask.java +++ b/bramble-core/src/main/java/org/briarproject/bramble/plugin/file/RemovableDriveReaderTask.java @@ -8,9 +8,9 @@ import org.briarproject.bramble.api.event.EventListener; 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.properties.TransportProperties; import org.briarproject.bramble.api.sync.event.MessageAddedEvent; -import java.io.File; import java.io.IOException; import java.io.InputStream; import java.util.concurrent.Executor; @@ -33,22 +33,21 @@ class RemovableDriveReaderTask extends RemovableDriveTaskImpl EventBus eventBus, RemovableDriveTaskRegistry registry, ContactId contactId, - File file) { + TransportProperties transportProperties) { super(eventExecutor, pluginManager, connectionManager, eventBus, - registry, contactId, file); + registry, contactId, transportProperties); } @Override public void run() { TransportConnectionReader r = - getPlugin().createReader(createProperties()); + getPlugin().createReader(transportProperties); if (r == null) { LOG.warning("Failed to create reader"); registry.removeReader(contactId, this); setSuccess(false); return; } - setTotal(file.length()); eventBus.addListener(this); connectionManager.manageIncomingConnection(ID, new DecoratedReader(r)); } diff --git a/bramble-core/src/main/java/org/briarproject/bramble/plugin/file/RemovableDriveTaskFactory.java b/bramble-core/src/main/java/org/briarproject/bramble/plugin/file/RemovableDriveTaskFactory.java index a81f88249..e90019d76 100644 --- a/bramble-core/src/main/java/org/briarproject/bramble/plugin/file/RemovableDriveTaskFactory.java +++ b/bramble-core/src/main/java/org/briarproject/bramble/plugin/file/RemovableDriveTaskFactory.java @@ -3,15 +3,14 @@ package org.briarproject.bramble.plugin.file; import org.briarproject.bramble.api.contact.ContactId; import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.plugin.file.RemovableDriveTask; - -import java.io.File; +import org.briarproject.bramble.api.properties.TransportProperties; @NotNullByDefault interface RemovableDriveTaskFactory { RemovableDriveTask createReader(RemovableDriveTaskRegistry registry, - ContactId c, File f); + ContactId c, TransportProperties p); RemovableDriveTask createWriter(RemovableDriveTaskRegistry registry, - ContactId c, File f); + ContactId c, TransportProperties p); } diff --git a/bramble-core/src/main/java/org/briarproject/bramble/plugin/file/RemovableDriveTaskFactoryImpl.java b/bramble-core/src/main/java/org/briarproject/bramble/plugin/file/RemovableDriveTaskFactoryImpl.java index dbf4b3bc7..b493054a0 100644 --- a/bramble-core/src/main/java/org/briarproject/bramble/plugin/file/RemovableDriveTaskFactoryImpl.java +++ b/bramble-core/src/main/java/org/briarproject/bramble/plugin/file/RemovableDriveTaskFactoryImpl.java @@ -8,8 +8,8 @@ import org.briarproject.bramble.api.event.EventExecutor; import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.plugin.PluginManager; import org.briarproject.bramble.api.plugin.file.RemovableDriveTask; +import org.briarproject.bramble.api.properties.TransportProperties; -import java.io.File; import java.util.concurrent.Executor; import javax.annotation.concurrent.Immutable; @@ -41,15 +41,15 @@ class RemovableDriveTaskFactoryImpl implements RemovableDriveTaskFactory { @Override public RemovableDriveTask createReader(RemovableDriveTaskRegistry registry, - ContactId c, File f) { + ContactId c, TransportProperties p) { return new RemovableDriveReaderTask(eventExecutor, pluginManager, - connectionManager, eventBus, registry, c, f); + connectionManager, eventBus, registry, c, p); } @Override public RemovableDriveTask createWriter(RemovableDriveTaskRegistry registry, - ContactId c, File f) { + ContactId c, TransportProperties p) { return new RemovableDriveWriterTask(db, eventExecutor, pluginManager, - connectionManager, eventBus, registry, c, f); + connectionManager, eventBus, registry, c, p); } } diff --git a/bramble-core/src/main/java/org/briarproject/bramble/plugin/file/RemovableDriveTaskImpl.java b/bramble-core/src/main/java/org/briarproject/bramble/plugin/file/RemovableDriveTaskImpl.java index de792f644..442d716e0 100644 --- a/bramble-core/src/main/java/org/briarproject/bramble/plugin/file/RemovableDriveTaskImpl.java +++ b/bramble-core/src/main/java/org/briarproject/bramble/plugin/file/RemovableDriveTaskImpl.java @@ -10,7 +10,6 @@ import org.briarproject.bramble.api.plugin.file.RemovableDriveTask; import org.briarproject.bramble.api.plugin.simplex.SimplexPlugin; import org.briarproject.bramble.api.properties.TransportProperties; -import java.io.File; import java.util.ArrayList; import java.util.List; import java.util.concurrent.Executor; @@ -20,7 +19,6 @@ import javax.annotation.concurrent.ThreadSafe; import static java.lang.Math.min; 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.api.plugin.file.RemovableDriveConstants.ID; @ThreadSafe @@ -33,7 +31,7 @@ abstract class RemovableDriveTaskImpl implements RemovableDriveTask { final EventBus eventBus; final RemovableDriveTaskRegistry registry; final ContactId contactId; - final File file; + final TransportProperties transportProperties; private final Object lock = new Object(); @GuardedBy("lock") @@ -48,19 +46,19 @@ abstract class RemovableDriveTaskImpl implements RemovableDriveTask { EventBus eventBus, RemovableDriveTaskRegistry registry, ContactId contactId, - File file) { + TransportProperties transportProperties) { this.eventExecutor = eventExecutor; this.pluginManager = pluginManager; this.connectionManager = connectionManager; this.eventBus = eventBus; this.registry = registry; this.contactId = contactId; - this.file = file; + this.transportProperties = transportProperties; } @Override - public File getFile() { - return file; + public TransportProperties getTransportProperties() { + return transportProperties; } @Override @@ -86,12 +84,6 @@ abstract class RemovableDriveTaskImpl implements RemovableDriveTask { return (SimplexPlugin) requireNonNull(pluginManager.getPlugin(ID)); } - TransportProperties createProperties() { - TransportProperties p = new TransportProperties(); - p.put(PROP_PATH, file.getAbsolutePath()); - return p; - } - void setTotal(long total) { synchronized (lock) { state = new State(state.getDone(), total, state.isFinished(), diff --git a/bramble-core/src/main/java/org/briarproject/bramble/plugin/file/RemovableDriveWriterTask.java b/bramble-core/src/main/java/org/briarproject/bramble/plugin/file/RemovableDriveWriterTask.java index d9a9ed75c..7d2539557 100644 --- a/bramble-core/src/main/java/org/briarproject/bramble/plugin/file/RemovableDriveWriterTask.java +++ b/bramble-core/src/main/java/org/briarproject/bramble/plugin/file/RemovableDriveWriterTask.java @@ -11,9 +11,9 @@ import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.plugin.PluginManager; import org.briarproject.bramble.api.plugin.TransportConnectionWriter; import org.briarproject.bramble.api.plugin.simplex.SimplexPlugin; +import org.briarproject.bramble.api.properties.TransportProperties; import org.briarproject.bramble.api.sync.event.MessagesSentEvent; -import java.io.File; import java.io.IOException; import java.io.OutputStream; import java.util.concurrent.Executor; @@ -42,16 +42,16 @@ class RemovableDriveWriterTask extends RemovableDriveTaskImpl EventBus eventBus, RemovableDriveTaskRegistry registry, ContactId contactId, - File file) { + TransportProperties transportProperties) { super(eventExecutor, pluginManager, connectionManager, eventBus, - registry, contactId, file); + registry, contactId, transportProperties); this.db = db; } @Override public void run() { SimplexPlugin plugin = getPlugin(); - TransportConnectionWriter w = plugin.createWriter(createProperties()); + TransportConnectionWriter w = plugin.createWriter(transportProperties); if (w == null) { LOG.warning("Failed to create writer"); registry.removeWriter(contactId, this); diff --git a/bramble-core/src/test/java/org/briarproject/bramble/plugin/file/RemovableDriveIntegrationTest.java b/bramble-core/src/test/java/org/briarproject/bramble/plugin/file/RemovableDriveIntegrationTest.java index 56a40bcbe..ab3fa731f 100644 --- a/bramble-core/src/test/java/org/briarproject/bramble/plugin/file/RemovableDriveIntegrationTest.java +++ b/bramble-core/src/test/java/org/briarproject/bramble/plugin/file/RemovableDriveIntegrationTest.java @@ -11,6 +11,7 @@ import org.briarproject.bramble.api.identity.IdentityManager; import org.briarproject.bramble.api.lifecycle.LifecycleManager; import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.plugin.file.RemovableDriveTask; +import org.briarproject.bramble.api.properties.TransportProperties; import org.briarproject.bramble.api.sync.event.MessageStateChangedEvent; import org.briarproject.bramble.test.BrambleTestCase; import org.briarproject.bramble.test.TestDatabaseConfigModule; @@ -22,6 +23,7 @@ import java.io.File; import java.util.concurrent.CountDownLatch; import static java.util.concurrent.TimeUnit.MILLISECONDS; +import static org.briarproject.bramble.api.plugin.file.FileConstants.PROP_PATH; import static org.briarproject.bramble.api.sync.validation.MessageState.DELIVERED; import static org.briarproject.bramble.test.TestUtils.deleteTestDirectory; import static org.briarproject.bramble.test.TestUtils.getSecretKey; @@ -96,8 +98,10 @@ public class RemovableDriveIntegrationTest extends BrambleTestCase { new MessageDeliveryListener(deliveries); device.getEventBus().addListener(listener); // Read the incoming stream + TransportProperties p = new TransportProperties(); + p.put(PROP_PATH, file.getAbsolutePath()); RemovableDriveTask reader = device.getRemovableDriveManager() - .startReaderTask(contactId, file); + .startReaderTask(contactId, p); CountDownLatch disposedLatch = new CountDownLatch(1); reader.addObserver(state -> { if (state.isFinished()) disposedLatch.countDown(); @@ -114,8 +118,10 @@ public class RemovableDriveIntegrationTest extends BrambleTestCase { ContactId contactId) throws Exception { // Write the outgoing stream to a file File file = File.createTempFile("sync", ".tmp", testDir); + TransportProperties p = new TransportProperties(); + p.put(PROP_PATH, file.getAbsolutePath()); RemovableDriveTask writer = device.getRemovableDriveManager() - .startWriterTask(contactId, file); + .startWriterTask(contactId, p); CountDownLatch disposedLatch = new CountDownLatch(1); writer.addObserver(state -> { if (state.isFinished()) disposedLatch.countDown(); From 8a04d8edc41dbb912cd426e63c24dbdadd36a062 Mon Sep 17 00:00:00 2001 From: akwizgran Date: Mon, 31 May 2021 17:12:07 +0100 Subject: [PATCH 19/77] Allow sync clients to defer delivery of messages. --- .../api/client/BdfIncomingMessageHook.java | 37 +++++---- .../sync/validation/IncomingMessageHook.java | 61 ++++++++++---- .../TransportPropertyManagerImpl.java | 9 ++- .../validation/ValidationManagerImpl.java | 81 ++++++++++--------- .../ClientVersioningManagerImpl.java | 9 ++- .../TransportPropertyManagerImplTest.java | 10 ++- .../validation/ValidationManagerImplTest.java | 25 +++--- .../ClientVersioningManagerImplTest.java | 17 ++-- .../briar/avatar/AvatarManagerImpl.java | 9 ++- .../briar/blog/BlogManagerImpl.java | 11 ++- .../briar/forum/ForumManagerImpl.java | 9 ++- .../introduction/IntroductionManagerImpl.java | 8 +- .../briar/messaging/MessagingManagerImpl.java | 8 +- .../privategroup/PrivateGroupManagerImpl.java | 10 ++- .../GroupInvitationManagerImpl.java | 8 +- .../briar/sharing/SharingManagerImpl.java | 7 +- .../briar/avatar/AvatarManagerImplTest.java | 14 ++-- .../briar/blog/BlogManagerImplTest.java | 7 +- .../GroupInvitationManagerImplTest.java | 22 +++-- 19 files changed, 219 insertions(+), 143 deletions(-) diff --git a/bramble-api/src/main/java/org/briarproject/bramble/api/client/BdfIncomingMessageHook.java b/bramble-api/src/main/java/org/briarproject/bramble/api/client/BdfIncomingMessageHook.java index e99aec8ba..8597c0117 100644 --- a/bramble-api/src/main/java/org/briarproject/bramble/api/client/BdfIncomingMessageHook.java +++ b/bramble-api/src/main/java/org/briarproject/bramble/api/client/BdfIncomingMessageHook.java @@ -32,28 +32,31 @@ public abstract class BdfIncomingMessageHook implements IncomingMessageHook { /** * Called once for each incoming message that passes validation. + *

+ * If an unexpected exception occurs while handling data that is assumed + * to be valid (e.g. locally created metadata), it may be sensible to + * rethrow the unexpected exception as a DbException so that delivery is + * attempted again at next startup. This will allow delivery to succeed if + * the unexpected exception was caused by a bug that has subsequently been + * fixed. * * @param txn A read-write transaction - * @return Whether or not this message should be shared - * @throws DbException Should only be used for real database errors. - * If this is thrown, delivery will be attempted again at next startup, - * whereas if a FormatException is thrown, the message will be permanently - * invalidated. - * @throws FormatException Use this for any non-database error - * that occurs while handling remotely created data. - * This includes errors that occur while handling locally created data - * in a context controlled by remotely created data - * (for example, parsing the metadata of a dependency - * of an incoming message). - * Never rethrow DbException as FormatException! + * @throws DbException if a database error occurs while delivering the + * message. Delivery will be attempted again at next startup. Throwing + * this exception has the same effect as returning + * {@link DeliveryAction#DEFER}. + * @throws FormatException if the message is invalid in the context of its + * dependencies. The message and any dependents will be marked as invalid + * and deleted along with their metadata. Throwing this exception has the + * same effect as returning {@link DeliveryAction#REJECT}. */ - protected abstract boolean incomingMessage(Transaction txn, Message m, - BdfList body, BdfDictionary meta) throws DbException, - FormatException; + protected abstract DeliveryAction incomingMessage(Transaction txn, + Message m, BdfList body, BdfDictionary meta) + throws DbException, FormatException; @Override - public boolean incomingMessage(Transaction txn, Message m, Metadata meta) - throws DbException, InvalidMessageException { + public DeliveryAction incomingMessage(Transaction txn, Message m, + Metadata meta) throws DbException, InvalidMessageException { try { BdfList body = clientHelper.toList(m); BdfDictionary metaDictionary = metadataParser.parse(meta); diff --git a/bramble-api/src/main/java/org/briarproject/bramble/api/sync/validation/IncomingMessageHook.java b/bramble-api/src/main/java/org/briarproject/bramble/api/sync/validation/IncomingMessageHook.java index 356f54248..5683f1710 100644 --- a/bramble-api/src/main/java/org/briarproject/bramble/api/sync/validation/IncomingMessageHook.java +++ b/bramble-api/src/main/java/org/briarproject/bramble/api/sync/validation/IncomingMessageHook.java @@ -10,23 +10,54 @@ public interface IncomingMessageHook { /** * Called once for each incoming message that passes validation. + *

+ * If an unexpected exception occurs while handling data that is assumed + * to be valid (e.g. locally created metadata), it may be sensible to + * rethrow the unexpected exception as a DbException so that delivery is + * attempted again at next startup. This will allow delivery to succeed if + * the unexpected exception was caused by a bug that has subsequently been + * fixed. * * @param txn A read-write transaction - * @return Whether or not this message should be shared - * @throws DbException Should only be used for real database errors. - * If this is thrown, delivery will be attempted again at next startup, - * whereas if an InvalidMessageException is thrown, - * the message will be permanently invalidated. - * @throws InvalidMessageException for any non-database error - * that occurs while handling remotely created data. - * This includes errors that occur while handling locally created data - * in a context controlled by remotely created data - * (for example, parsing the metadata of a dependency - * of an incoming message). - * Throwing this will delete the incoming message and its metadata - * marking it as invalid in the database. - * Never rethrow DbException as InvalidMessageException! + * @throws DbException if a database error occurs while delivering the + * message. Delivery will be attempted again at next startup. Throwing + * this exception has the same effect as returning + * {@link DeliveryAction#DEFER}. + * @throws InvalidMessageException if the message is invalid in the context + * of its dependencies. The message and any dependents will be marked as + * invalid and deleted along with their metadata. Throwing this exception + * has the same effect as returning {@link DeliveryAction#REJECT}. */ - boolean incomingMessage(Transaction txn, Message m, Metadata meta) + DeliveryAction incomingMessage(Transaction txn, Message m, Metadata meta) throws DbException, InvalidMessageException; + + enum DeliveryAction { + + /** + * The message and any dependent messages will be moved to the + * {@link MessageState#INVALID INVALID state} and deleted, along with + * their metadata. + */ + REJECT, + + /** + * The message will be moved to the + * {@link MessageState#PENDING PENDING state}. Delivery will be + * attempted again at next startup. + */ + DEFER, + + /** + * The message will be moved to the + * {@link MessageState#DELIVERED DELIVERED state} and shared. + */ + ACCEPT_SHARE, + + /** + * The message will be moved to the + * {@link MessageState#DELIVERED DELIVERED state} and will not be + * shared. + */ + ACCEPT_DO_NOT_SHARE + } } diff --git a/bramble-core/src/main/java/org/briarproject/bramble/properties/TransportPropertyManagerImpl.java b/bramble-core/src/main/java/org/briarproject/bramble/properties/TransportPropertyManagerImpl.java index d5f2d4212..cabf9f95d 100644 --- a/bramble-core/src/main/java/org/briarproject/bramble/properties/TransportPropertyManagerImpl.java +++ b/bramble-core/src/main/java/org/briarproject/bramble/properties/TransportPropertyManagerImpl.java @@ -44,6 +44,7 @@ import static org.briarproject.bramble.api.properties.TransportPropertyConstants import static org.briarproject.bramble.api.properties.TransportPropertyConstants.MSG_KEY_TRANSPORT_ID; import static org.briarproject.bramble.api.properties.TransportPropertyConstants.MSG_KEY_VERSION; import static org.briarproject.bramble.api.properties.TransportPropertyConstants.REFLECTED_PROPERTY_PREFIX; +import static org.briarproject.bramble.api.sync.validation.IncomingMessageHook.DeliveryAction.ACCEPT_DO_NOT_SHARE; import static org.briarproject.bramble.util.StringUtils.isNullOrEmpty; @Immutable @@ -115,8 +116,8 @@ class TransportPropertyManagerImpl implements TransportPropertyManager, } @Override - public boolean incomingMessage(Transaction txn, Message m, Metadata meta) - throws DbException, InvalidMessageException { + public DeliveryAction incomingMessage(Transaction txn, Message m, + Metadata meta) throws DbException, InvalidMessageException { try { // Find the latest update for this transport, if any BdfDictionary d = metadataParser.parse(meta); @@ -131,14 +132,14 @@ class TransportPropertyManagerImpl implements TransportPropertyManager, // We've already received a newer update - delete this one db.deleteMessage(txn, m.getId()); db.deleteMessageMetadata(txn, m.getId()); - return false; + return ACCEPT_DO_NOT_SHARE; } } txn.attach(new RemoteTransportPropertiesUpdatedEvent(t)); } catch (FormatException e) { throw new InvalidMessageException(e); } - return false; + return ACCEPT_DO_NOT_SHARE; } @Override diff --git a/bramble-core/src/main/java/org/briarproject/bramble/sync/validation/ValidationManagerImpl.java b/bramble-core/src/main/java/org/briarproject/bramble/sync/validation/ValidationManagerImpl.java index d0d583195..cec81615d 100644 --- a/bramble-core/src/main/java/org/briarproject/bramble/sync/validation/ValidationManagerImpl.java +++ b/bramble-core/src/main/java/org/briarproject/bramble/sync/validation/ValidationManagerImpl.java @@ -20,6 +20,7 @@ import org.briarproject.bramble.api.sync.MessageContext; import org.briarproject.bramble.api.sync.MessageId; import org.briarproject.bramble.api.sync.event.MessageAddedEvent; import org.briarproject.bramble.api.sync.validation.IncomingMessageHook; +import org.briarproject.bramble.api.sync.validation.IncomingMessageHook.DeliveryAction; import org.briarproject.bramble.api.sync.validation.MessageState; import org.briarproject.bramble.api.sync.validation.MessageValidator; import org.briarproject.bramble.api.sync.validation.ValidationManager; @@ -40,6 +41,10 @@ import javax.inject.Inject; import static java.util.logging.Level.INFO; import static java.util.logging.Level.WARNING; +import static org.briarproject.bramble.api.sync.validation.IncomingMessageHook.DeliveryAction.ACCEPT_DO_NOT_SHARE; +import static org.briarproject.bramble.api.sync.validation.IncomingMessageHook.DeliveryAction.ACCEPT_SHARE; +import static org.briarproject.bramble.api.sync.validation.IncomingMessageHook.DeliveryAction.DEFER; +import static org.briarproject.bramble.api.sync.validation.IncomingMessageHook.DeliveryAction.REJECT; import static org.briarproject.bramble.api.sync.validation.MessageState.DELIVERED; import static org.briarproject.bramble.api.sync.validation.MessageState.INVALID; import static org.briarproject.bramble.api.sync.validation.MessageState.PENDING; @@ -185,16 +190,19 @@ class ValidationManagerImpl implements ValidationManager, Service, int majorVersion = g.getMajorVersion(); Metadata meta = db.getMessageMetadataForValidator(txn, id); - DeliveryResult result = + DeliveryAction action = deliverMessage(txn, m, c, majorVersion, meta); - if (result.valid) { - addPendingDependents(txn, id, pending); - if (result.share) { - db.setMessageShared(txn, id); - toShare.addAll(states.keySet()); - } - } else { + if (action == REJECT) { + invalidateMessage(txn, id); addDependentsToInvalidate(txn, id, invalidate); + } else if (action == ACCEPT_SHARE) { + db.setMessageState(txn, m.getId(), DELIVERED); + addPendingDependents(txn, id, pending); + db.setMessageShared(txn, id); + toShare.addAll(states.keySet()); + } else if (action == ACCEPT_DO_NOT_SHARE) { + db.setMessageState(txn, m.getId(), DELIVERED); + addPendingDependents(txn, id, pending); } } } @@ -275,16 +283,21 @@ class ValidationManagerImpl implements ValidationManager, Service, Metadata meta = context.getMetadata(); db.mergeMessageMetadata(txn, id, meta); if (allDelivered) { - DeliveryResult result = + DeliveryAction action = deliverMessage(txn, m, c, majorVersion, meta); - if (result.valid) { - addPendingDependents(txn, id, pending); - if (result.share) { - db.setMessageShared(txn, id); - toShare.addAll(dependencies); - } - } else { + if (action == REJECT) { + invalidateMessage(txn, id); addDependentsToInvalidate(txn, id, invalidate); + } else if (action == DEFER) { + db.setMessageState(txn, id, PENDING); + } else if (action == ACCEPT_SHARE) { + db.setMessageState(txn, id, DELIVERED); + addPendingDependents(txn, id, pending); + db.setMessageShared(txn, id); + toShare.addAll(dependencies); + } else if (action == ACCEPT_DO_NOT_SHARE) { + db.setMessageState(txn, id, DELIVERED); + addPendingDependents(txn, id, pending); } } else { db.setMessageState(txn, id, PENDING); @@ -304,23 +317,21 @@ class ValidationManagerImpl implements ValidationManager, Service, } @DatabaseExecutor - private DeliveryResult deliverMessage(Transaction txn, Message m, - ClientId c, int majorVersion, Metadata meta) throws DbException { - // Deliver the message to the client if it's registered a hook - boolean shareMsg = false; + private DeliveryAction deliverMessage(Transaction txn, Message m, + ClientId c, int majorVersion, Metadata meta) { + // Deliver the message to the client if it has registered a hook ClientMajorVersion cv = new ClientMajorVersion(c, majorVersion); IncomingMessageHook hook = hooks.get(cv); - if (hook != null) { - try { - shareMsg = hook.incomingMessage(txn, m, meta); - } catch (InvalidMessageException e) { - logException(LOG, INFO, e); - invalidateMessage(txn, m.getId()); - return new DeliveryResult(false, false); - } + if (hook == null) return ACCEPT_DO_NOT_SHARE; + try { + return hook.incomingMessage(txn, m, meta); + } catch (DbException e) { + logException(LOG, INFO, e); + return DEFER; + } catch (InvalidMessageException e) { + logException(LOG, INFO, e); + return REJECT; } - db.setMessageState(txn, m.getId(), DELIVERED); - return new DeliveryResult(true, shareMsg); } @DatabaseExecutor @@ -447,14 +458,4 @@ class ValidationManagerImpl implements ValidationManager, Service, logException(LOG, WARNING, e); } } - - private static class DeliveryResult { - - private final boolean valid, share; - - private DeliveryResult(boolean valid, boolean share) { - this.valid = valid; - this.share = share; - } - } } diff --git a/bramble-core/src/main/java/org/briarproject/bramble/versioning/ClientVersioningManagerImpl.java b/bramble-core/src/main/java/org/briarproject/bramble/versioning/ClientVersioningManagerImpl.java index 6aadf889a..fdaa6a80f 100644 --- a/bramble-core/src/main/java/org/briarproject/bramble/versioning/ClientVersioningManagerImpl.java +++ b/bramble-core/src/main/java/org/briarproject/bramble/versioning/ClientVersioningManagerImpl.java @@ -50,6 +50,7 @@ import static java.util.Collections.emptyList; import static org.briarproject.bramble.api.sync.Group.Visibility.INVISIBLE; import static org.briarproject.bramble.api.sync.Group.Visibility.SHARED; import static org.briarproject.bramble.api.sync.Group.Visibility.VISIBLE; +import static org.briarproject.bramble.api.sync.validation.IncomingMessageHook.DeliveryAction.ACCEPT_DO_NOT_SHARE; import static org.briarproject.bramble.versioning.ClientVersioningConstants.MSG_KEY_LOCAL; import static org.briarproject.bramble.versioning.ClientVersioningConstants.MSG_KEY_UPDATE_VERSION; @@ -173,8 +174,8 @@ class ClientVersioningManagerImpl implements ClientVersioningManager, } @Override - public boolean incomingMessage(Transaction txn, Message m, Metadata meta) - throws DbException, InvalidMessageException { + public DeliveryAction incomingMessage(Transaction txn, Message m, + Metadata meta) throws DbException, InvalidMessageException { try { // Parse the new remote update Update newRemoteUpdate = parseUpdate(clientHelper.toList(m)); @@ -187,7 +188,7 @@ class ClientVersioningManagerImpl implements ClientVersioningManager, && latest.remote.updateVersion > newRemoteUpdateVersion) { db.deleteMessage(txn, m.getId()); db.deleteMessageMetadata(txn, m.getId()); - return false; + return ACCEPT_DO_NOT_SHARE; } // Load and parse the latest local update if (latest.local == null) throw new DbException(); @@ -241,7 +242,7 @@ class ClientVersioningManagerImpl implements ClientVersioningManager, } catch (FormatException e) { throw new InvalidMessageException(e); } - return false; + return ACCEPT_DO_NOT_SHARE; } private void storeClientVersions(Transaction txn, diff --git a/bramble-core/src/test/java/org/briarproject/bramble/properties/TransportPropertyManagerImplTest.java b/bramble-core/src/test/java/org/briarproject/bramble/properties/TransportPropertyManagerImplTest.java index 9073917a2..0f9b63663 100644 --- a/bramble-core/src/test/java/org/briarproject/bramble/properties/TransportPropertyManagerImplTest.java +++ b/bramble-core/src/test/java/org/briarproject/bramble/properties/TransportPropertyManagerImplTest.java @@ -43,6 +43,7 @@ import static org.briarproject.bramble.api.properties.TransportPropertyConstants import static org.briarproject.bramble.api.properties.TransportPropertyManager.CLIENT_ID; import static org.briarproject.bramble.api.properties.TransportPropertyManager.MAJOR_VERSION; import static org.briarproject.bramble.api.sync.Group.Visibility.SHARED; +import static org.briarproject.bramble.api.sync.validation.IncomingMessageHook.DeliveryAction.ACCEPT_DO_NOT_SHARE; import static org.briarproject.bramble.test.TestUtils.getContact; import static org.briarproject.bramble.test.TestUtils.getGroup; import static org.briarproject.bramble.test.TestUtils.getMessage; @@ -230,7 +231,8 @@ public class TransportPropertyManagerImplTest extends BrambleMockTestCase { }}); TransportPropertyManagerImpl t = createInstance(); - assertFalse(t.incomingMessage(txn, message, meta)); + assertEquals(ACCEPT_DO_NOT_SHARE, + t.incomingMessage(txn, message, meta)); assertTrue(hasEvent(txn, RemoteTransportPropertiesUpdatedEvent.class)); } @@ -269,7 +271,8 @@ public class TransportPropertyManagerImplTest extends BrambleMockTestCase { }}); TransportPropertyManagerImpl t = createInstance(); - assertFalse(t.incomingMessage(txn, message, meta)); + assertEquals(ACCEPT_DO_NOT_SHARE, + t.incomingMessage(txn, message, meta)); assertTrue(hasEvent(txn, RemoteTransportPropertiesUpdatedEvent.class)); } @@ -308,7 +311,8 @@ public class TransportPropertyManagerImplTest extends BrambleMockTestCase { }}); TransportPropertyManagerImpl t = createInstance(); - assertFalse(t.incomingMessage(txn, message, meta)); + assertEquals(ACCEPT_DO_NOT_SHARE, + t.incomingMessage(txn, message, meta)); assertFalse(hasEvent(txn, RemoteTransportPropertiesUpdatedEvent.class)); } diff --git a/bramble-core/src/test/java/org/briarproject/bramble/sync/validation/ValidationManagerImplTest.java b/bramble-core/src/test/java/org/briarproject/bramble/sync/validation/ValidationManagerImplTest.java index 09018c36e..fea01c120 100644 --- a/bramble-core/src/test/java/org/briarproject/bramble/sync/validation/ValidationManagerImplTest.java +++ b/bramble-core/src/test/java/org/briarproject/bramble/sync/validation/ValidationManagerImplTest.java @@ -31,6 +31,8 @@ import static java.util.Arrays.asList; import static java.util.Collections.emptyMap; import static java.util.Collections.singletonList; import static java.util.Collections.singletonMap; +import static org.briarproject.bramble.api.sync.validation.IncomingMessageHook.DeliveryAction.ACCEPT_DO_NOT_SHARE; +import static org.briarproject.bramble.api.sync.validation.IncomingMessageHook.DeliveryAction.ACCEPT_SHARE; import static org.briarproject.bramble.api.sync.validation.MessageState.DELIVERED; import static org.briarproject.bramble.api.sync.validation.MessageState.INVALID; import static org.briarproject.bramble.api.sync.validation.MessageState.PENDING; @@ -111,7 +113,7 @@ public class ValidationManagerImplTest extends BrambleMockTestCase { oneOf(db).mergeMessageMetadata(txn1, messageId, metadata); // Deliver the first message oneOf(hook).incomingMessage(txn1, message, metadata); - will(returnValue(false)); + will(returnValue(ACCEPT_DO_NOT_SHARE)); oneOf(db).setMessageState(txn1, messageId, DELIVERED); // Get any pending dependents oneOf(db).getMessageDependents(txn1, messageId); @@ -167,7 +169,7 @@ public class ValidationManagerImplTest extends BrambleMockTestCase { will(returnValue(new Metadata())); // Deliver the message oneOf(hook).incomingMessage(txn, message, metadata); - will(returnValue(false)); + will(returnValue(ACCEPT_DO_NOT_SHARE)); oneOf(db).setMessageState(txn, messageId, DELIVERED); // Get any pending dependents oneOf(db).getMessageDependents(txn, messageId); @@ -187,7 +189,7 @@ public class ValidationManagerImplTest extends BrambleMockTestCase { will(returnValue(metadata)); // Deliver the dependent oneOf(hook).incomingMessage(txn1, message2, metadata); - will(returnValue(false)); + will(returnValue(ACCEPT_DO_NOT_SHARE)); oneOf(db).setMessageState(txn1, messageId2, DELIVERED); // Get any pending dependents oneOf(db).getMessageDependents(txn1, messageId2); @@ -247,7 +249,7 @@ public class ValidationManagerImplTest extends BrambleMockTestCase { oneOf(db).mergeMessageMetadata(txn1, messageId, metadata); // Deliver the message oneOf(hook).incomingMessage(txn1, message, metadata); - will(returnValue(true)); + will(returnValue(ACCEPT_SHARE)); oneOf(db).setMessageState(txn1, messageId, DELIVERED); // Get any pending dependents oneOf(db).getMessageDependents(txn1, messageId); @@ -367,7 +369,7 @@ public class ValidationManagerImplTest extends BrambleMockTestCase { oneOf(db).mergeMessageMetadata(txn1, messageId, metadata); // Deliver the message oneOf(hook).incomingMessage(txn1, message, metadata); - will(returnValue(false)); + will(returnValue(ACCEPT_DO_NOT_SHARE)); oneOf(db).setMessageState(txn1, messageId, DELIVERED); // Get any pending dependents oneOf(db).getMessageDependents(txn1, messageId); @@ -432,7 +434,7 @@ public class ValidationManagerImplTest extends BrambleMockTestCase { oneOf(db).mergeMessageMetadata(txn1, messageId, metadata); // Deliver the message oneOf(hook).incomingMessage(txn1, message, metadata); - will(returnValue(false)); + will(returnValue(ACCEPT_DO_NOT_SHARE)); oneOf(db).setMessageState(txn1, messageId, DELIVERED); // Get any pending dependents oneOf(db).getMessageDependents(txn1, messageId); @@ -602,7 +604,7 @@ public class ValidationManagerImplTest extends BrambleMockTestCase { oneOf(db).mergeMessageMetadata(txn1, messageId, metadata); // Deliver the message oneOf(hook).incomingMessage(txn1, message, metadata); - will(returnValue(false)); + will(returnValue(ACCEPT_DO_NOT_SHARE)); oneOf(db).setMessageState(txn1, messageId, DELIVERED); // The message has two pending dependents: 1 and 2 oneOf(db).getMessageDependents(txn1, messageId); @@ -622,7 +624,7 @@ public class ValidationManagerImplTest extends BrambleMockTestCase { will(returnValue(metadata)); // Deliver message 1 oneOf(hook).incomingMessage(txn2, message1, metadata); - will(returnValue(false)); + will(returnValue(ACCEPT_DO_NOT_SHARE)); oneOf(db).setMessageState(txn2, messageId1, DELIVERED); // Message 1 has one pending dependent: 3 oneOf(db).getMessageDependents(txn2, messageId1); @@ -642,7 +644,7 @@ public class ValidationManagerImplTest extends BrambleMockTestCase { will(returnValue(metadata)); // Deliver message 2 oneOf(hook).incomingMessage(txn3, message2, metadata); - will(returnValue(false)); + will(returnValue(ACCEPT_DO_NOT_SHARE)); oneOf(db).setMessageState(txn3, messageId2, DELIVERED); // Message 2 has one pending dependent: 3 (same dependent as 1) oneOf(db).getMessageDependents(txn3, messageId2); @@ -662,6 +664,7 @@ public class ValidationManagerImplTest extends BrambleMockTestCase { will(returnValue(metadata)); // Deliver message 3 oneOf(hook).incomingMessage(txn4, message3, metadata); + will(returnValue(ACCEPT_DO_NOT_SHARE)); oneOf(db).setMessageState(txn4, messageId3, DELIVERED); // Message 3 has one pending dependent: 4 oneOf(db).getMessageDependents(txn4, messageId3); @@ -685,7 +688,7 @@ public class ValidationManagerImplTest extends BrambleMockTestCase { will(returnValue(metadata)); // Deliver message 4 oneOf(hook).incomingMessage(txn6, message4, metadata); - will(returnValue(false)); + will(returnValue(ACCEPT_DO_NOT_SHARE)); oneOf(db).setMessageState(txn6, messageId4, DELIVERED); // Message 4 has no pending dependents oneOf(db).getMessageDependents(txn6, messageId4); @@ -717,7 +720,7 @@ public class ValidationManagerImplTest extends BrambleMockTestCase { oneOf(db).mergeMessageMetadata(txn1, messageId, metadata); // Deliver the message oneOf(hook).incomingMessage(txn1, message, metadata); - will(returnValue(false)); + will(returnValue(ACCEPT_DO_NOT_SHARE)); oneOf(db).setMessageState(txn1, messageId, DELIVERED); // Get any pending dependents oneOf(db).getMessageDependents(txn1, messageId); diff --git a/bramble-core/src/test/java/org/briarproject/bramble/versioning/ClientVersioningManagerImplTest.java b/bramble-core/src/test/java/org/briarproject/bramble/versioning/ClientVersioningManagerImplTest.java index 17d23087b..2a6d34405 100644 --- a/bramble-core/src/test/java/org/briarproject/bramble/versioning/ClientVersioningManagerImplTest.java +++ b/bramble-core/src/test/java/org/briarproject/bramble/versioning/ClientVersioningManagerImplTest.java @@ -31,6 +31,7 @@ import static java.util.Collections.singletonMap; import static org.briarproject.bramble.api.sync.Group.Visibility.INVISIBLE; import static org.briarproject.bramble.api.sync.Group.Visibility.SHARED; import static org.briarproject.bramble.api.sync.Group.Visibility.VISIBLE; +import static org.briarproject.bramble.api.sync.validation.IncomingMessageHook.DeliveryAction.ACCEPT_DO_NOT_SHARE; import static org.briarproject.bramble.api.versioning.ClientVersioningManager.CLIENT_ID; import static org.briarproject.bramble.api.versioning.ClientVersioningManager.MAJOR_VERSION; import static org.briarproject.bramble.test.TestUtils.getClientId; @@ -41,7 +42,6 @@ import static org.briarproject.bramble.test.TestUtils.getRandomId; import static org.briarproject.bramble.versioning.ClientVersioningConstants.MSG_KEY_LOCAL; import static org.briarproject.bramble.versioning.ClientVersioningConstants.MSG_KEY_UPDATE_VERSION; import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; public class ClientVersioningManagerImplTest extends BrambleMockTestCase { @@ -419,7 +419,8 @@ public class ClientVersioningManagerImplTest extends BrambleMockTestCase { ClientVersioningManagerImpl c = createInstance(); c.registerClient(clientId, 123, 234, hook); - assertFalse(c.incomingMessage(txn, newRemoteUpdate, new Metadata())); + assertEquals(ACCEPT_DO_NOT_SHARE, + c.incomingMessage(txn, newRemoteUpdate, new Metadata())); } @Test @@ -464,7 +465,8 @@ public class ClientVersioningManagerImplTest extends BrambleMockTestCase { ClientVersioningManagerImpl c = createInstance(); c.registerClient(clientId, 123, 234, hook); - assertFalse(c.incomingMessage(txn, newRemoteUpdate, new Metadata())); + assertEquals(ACCEPT_DO_NOT_SHARE, + c.incomingMessage(txn, newRemoteUpdate, new Metadata())); } @Test @@ -496,7 +498,8 @@ public class ClientVersioningManagerImplTest extends BrambleMockTestCase { ClientVersioningManagerImpl c = createInstance(); c.registerClient(clientId, 123, 234, hook); - assertFalse(c.incomingMessage(txn, newRemoteUpdate, new Metadata())); + assertEquals(ACCEPT_DO_NOT_SHARE, + c.incomingMessage(txn, newRemoteUpdate, new Metadata())); } @Test @@ -579,7 +582,8 @@ public class ClientVersioningManagerImplTest extends BrambleMockTestCase { ClientVersioningManagerImpl c = createInstance(); c.registerClient(clientId, 123, 234, hook); - assertFalse(c.incomingMessage(txn, newRemoteUpdate, new Metadata())); + assertEquals(ACCEPT_DO_NOT_SHARE, + c.incomingMessage(txn, newRemoteUpdate, new Metadata())); } @Test @@ -649,7 +653,8 @@ public class ClientVersioningManagerImplTest extends BrambleMockTestCase { ClientVersioningManagerImpl c = createInstance(); c.registerClient(clientId, 123, 234, hook); - assertFalse(c.incomingMessage(txn, newRemoteUpdate, new Metadata())); + assertEquals(ACCEPT_DO_NOT_SHARE, + c.incomingMessage(txn, newRemoteUpdate, new Metadata())); } @Test diff --git a/briar-core/src/main/java/org/briarproject/briar/avatar/AvatarManagerImpl.java b/briar-core/src/main/java/org/briarproject/briar/avatar/AvatarManagerImpl.java index 7c5967dab..ab3097bd2 100644 --- a/briar-core/src/main/java/org/briarproject/briar/avatar/AvatarManagerImpl.java +++ b/briar-core/src/main/java/org/briarproject/briar/avatar/AvatarManagerImpl.java @@ -40,6 +40,7 @@ import javax.annotation.Nullable; import javax.annotation.concurrent.Immutable; import javax.inject.Inject; +import static org.briarproject.bramble.api.sync.validation.IncomingMessageHook.DeliveryAction.ACCEPT_DO_NOT_SHARE; import static org.briarproject.briar.api.attachment.MediaConstants.MSG_KEY_CONTENT_TYPE; import static org.briarproject.briar.avatar.AvatarConstants.GROUP_KEY_CONTACT_ID; import static org.briarproject.briar.avatar.AvatarConstants.MSG_KEY_VERSION; @@ -124,8 +125,8 @@ class AvatarManagerImpl implements AvatarManager, OpenDatabaseHook, ContactHook, } @Override - public boolean incomingMessage(Transaction txn, Message m, Metadata meta) - throws DbException, InvalidMessageException { + public DeliveryAction incomingMessage(Transaction txn, Message m, + Metadata meta) throws DbException, InvalidMessageException { Group ourGroup = getOurGroup(txn); if (m.getGroupId().equals(ourGroup.getId())) { throw new InvalidMessageException( @@ -144,7 +145,7 @@ class AvatarManagerImpl implements AvatarManager, OpenDatabaseHook, ContactHook, // We've already received a newer update - delete this one db.deleteMessage(txn, m.getId()); db.deleteMessageMetadata(txn, m.getId()); - return false; // don't broadcast update + return ACCEPT_DO_NOT_SHARE; } } ContactId contactId = getContactId(txn, m.getGroupId()); @@ -155,7 +156,7 @@ class AvatarManagerImpl implements AvatarManager, OpenDatabaseHook, ContactHook, } catch (FormatException e) { throw new InvalidMessageException(e); } - return false; + return ACCEPT_DO_NOT_SHARE; } @Override diff --git a/briar-core/src/main/java/org/briarproject/briar/blog/BlogManagerImpl.java b/briar-core/src/main/java/org/briarproject/briar/blog/BlogManagerImpl.java index 68ea45863..c37c8c290 100644 --- a/briar-core/src/main/java/org/briarproject/briar/blog/BlogManagerImpl.java +++ b/briar-core/src/main/java/org/briarproject/briar/blog/BlogManagerImpl.java @@ -50,6 +50,8 @@ import java.util.concurrent.CopyOnWriteArrayList; import javax.annotation.Nullable; import javax.inject.Inject; +import static org.briarproject.bramble.api.sync.validation.IncomingMessageHook.DeliveryAction.ACCEPT_DO_NOT_SHARE; +import static org.briarproject.bramble.api.sync.validation.IncomingMessageHook.DeliveryAction.ACCEPT_SHARE; import static org.briarproject.briar.api.blog.BlogConstants.KEY_AUTHOR; import static org.briarproject.briar.api.blog.BlogConstants.KEY_COMMENT; import static org.briarproject.briar.api.blog.BlogConstants.KEY_ORIGINAL_MSG_ID; @@ -109,8 +111,9 @@ class BlogManagerImpl extends BdfIncomingMessageHook implements BlogManager, } @Override - protected boolean incomingMessage(Transaction txn, Message m, BdfList list, - BdfDictionary meta) throws DbException, FormatException { + protected DeliveryAction incomingMessage(Transaction txn, Message m, + BdfList list, BdfDictionary meta) + throws DbException, FormatException { GroupId groupId = m.getGroupId(); MessageType type = getMessageType(meta); @@ -138,7 +141,7 @@ class BlogManagerImpl extends BdfIncomingMessageHook implements BlogManager, txn.attach(event); // shares message and its dependencies - return true; + return ACCEPT_SHARE; } else if (type == WRAPPED_COMMENT) { // Check that the original message ID in the dependency's metadata // matches the original parent ID of the wrapped comment @@ -153,7 +156,7 @@ class BlogManagerImpl extends BdfIncomingMessageHook implements BlogManager, } } // don't share message until parent arrives - return false; + return ACCEPT_DO_NOT_SHARE; } @Override diff --git a/briar-core/src/main/java/org/briarproject/briar/forum/ForumManagerImpl.java b/briar-core/src/main/java/org/briarproject/briar/forum/ForumManagerImpl.java index 79c3d51d6..a5dc6cebc 100644 --- a/briar-core/src/main/java/org/briarproject/briar/forum/ForumManagerImpl.java +++ b/briar-core/src/main/java/org/briarproject/briar/forum/ForumManagerImpl.java @@ -45,6 +45,7 @@ import javax.annotation.Nullable; import javax.annotation.concurrent.ThreadSafe; import javax.inject.Inject; +import static org.briarproject.bramble.api.sync.validation.IncomingMessageHook.DeliveryAction.ACCEPT_SHARE; import static org.briarproject.briar.api.forum.ForumConstants.KEY_AUTHOR; import static org.briarproject.briar.api.forum.ForumConstants.KEY_LOCAL; import static org.briarproject.briar.api.forum.ForumConstants.KEY_PARENT; @@ -75,8 +76,9 @@ class ForumManagerImpl extends BdfIncomingMessageHook implements ForumManager { } @Override - protected boolean incomingMessage(Transaction txn, Message m, BdfList body, - BdfDictionary meta) throws DbException, FormatException { + protected DeliveryAction incomingMessage(Transaction txn, Message m, + BdfList body, BdfDictionary meta) + throws DbException, FormatException { messageTracker.trackIncomingMessage(txn, m); @@ -86,8 +88,7 @@ class ForumManagerImpl extends BdfIncomingMessageHook implements ForumManager { new ForumPostReceivedEvent(m.getGroupId(), header, text); txn.attach(event); - // share message - return true; + return ACCEPT_SHARE; } @Override diff --git a/briar-core/src/main/java/org/briarproject/briar/introduction/IntroductionManagerImpl.java b/briar-core/src/main/java/org/briarproject/briar/introduction/IntroductionManagerImpl.java index 4d58f49ef..2eb8dfbf5 100644 --- a/briar-core/src/main/java/org/briarproject/briar/introduction/IntroductionManagerImpl.java +++ b/briar-core/src/main/java/org/briarproject/briar/introduction/IntroductionManagerImpl.java @@ -56,6 +56,7 @@ import javax.annotation.Nullable; import javax.annotation.concurrent.Immutable; import javax.inject.Inject; +import static org.briarproject.bramble.api.sync.validation.IncomingMessageHook.DeliveryAction.ACCEPT_DO_NOT_SHARE; import static org.briarproject.briar.api.autodelete.AutoDeleteConstants.NO_AUTO_DELETE_TIMER; import static org.briarproject.briar.api.introduction.Role.INTRODUCEE; import static org.briarproject.briar.api.introduction.Role.INTRODUCER; @@ -171,8 +172,9 @@ class IntroductionManagerImpl extends ConversationClientImpl } @Override - protected boolean incomingMessage(Transaction txn, Message m, BdfList body, - BdfDictionary bdfMeta) throws DbException, FormatException { + protected DeliveryAction incomingMessage(Transaction txn, Message m, + BdfList body, BdfDictionary bdfMeta) + throws DbException, FormatException { // Parse the metadata MessageMetadata meta = messageParser.parseMetadata(bdfMeta); // set the clean-up timer that will be started when message gets read @@ -213,7 +215,7 @@ class IntroductionManagerImpl extends ConversationClientImpl } // Store the updated session storeSession(txn, storageId, session); - return false; + return ACCEPT_DO_NOT_SHARE; } private IntroduceeSession createNewIntroduceeSession(Transaction txn, diff --git a/briar-core/src/main/java/org/briarproject/briar/messaging/MessagingManagerImpl.java b/briar-core/src/main/java/org/briarproject/briar/messaging/MessagingManagerImpl.java index 06681ac18..950adaf5c 100644 --- a/briar-core/src/main/java/org/briarproject/briar/messaging/MessagingManagerImpl.java +++ b/briar-core/src/main/java/org/briarproject/briar/messaging/MessagingManagerImpl.java @@ -63,6 +63,7 @@ import static java.util.Collections.emptyList; import static java.util.logging.Logger.getLogger; import static org.briarproject.bramble.api.client.ContactGroupConstants.GROUP_KEY_CONTACT_ID; import static org.briarproject.bramble.api.sync.SyncConstants.MAX_MESSAGE_BODY_LENGTH; +import static org.briarproject.bramble.api.sync.validation.IncomingMessageHook.DeliveryAction.ACCEPT_DO_NOT_SHARE; import static org.briarproject.bramble.util.IoUtils.copyAndClose; import static org.briarproject.bramble.util.LogUtils.logDuration; import static org.briarproject.bramble.util.LogUtils.now; @@ -172,8 +173,8 @@ class MessagingManagerImpl implements MessagingManager, IncomingMessageHook, } @Override - public boolean incomingMessage(Transaction txn, Message m, Metadata meta) - throws DbException, InvalidMessageException { + public DeliveryAction incomingMessage(Transaction txn, Message m, + Metadata meta) throws DbException, InvalidMessageException { try { BdfDictionary metaDict = metadataParser.parse(meta); // Message type is null for version 0.0 private messages @@ -193,8 +194,7 @@ class MessagingManagerImpl implements MessagingManager, IncomingMessageHook, } catch (FormatException e) { throw new InvalidMessageException(e); } - // Don't share message - return false; + return ACCEPT_DO_NOT_SHARE; } private void incomingPrivateMessage(Transaction txn, Message m, diff --git a/briar-core/src/main/java/org/briarproject/briar/privategroup/PrivateGroupManagerImpl.java b/briar-core/src/main/java/org/briarproject/briar/privategroup/PrivateGroupManagerImpl.java index 5093963fe..185a513e6 100644 --- a/briar-core/src/main/java/org/briarproject/briar/privategroup/PrivateGroupManagerImpl.java +++ b/briar-core/src/main/java/org/briarproject/briar/privategroup/PrivateGroupManagerImpl.java @@ -55,6 +55,7 @@ import java.util.concurrent.CopyOnWriteArrayList; import javax.annotation.concurrent.ThreadSafe; import javax.inject.Inject; +import static org.briarproject.bramble.api.sync.validation.IncomingMessageHook.DeliveryAction.ACCEPT_SHARE; import static org.briarproject.briar.api.identity.AuthorInfo.Status.UNVERIFIED; import static org.briarproject.briar.api.identity.AuthorInfo.Status.VERIFIED; import static org.briarproject.briar.api.privategroup.MessageType.JOIN; @@ -518,18 +519,19 @@ class PrivateGroupManagerImpl extends BdfIncomingMessageHook } @Override - protected boolean incomingMessage(Transaction txn, Message m, BdfList body, - BdfDictionary meta) throws DbException, FormatException { + protected DeliveryAction incomingMessage(Transaction txn, Message m, + BdfList body, BdfDictionary meta) + throws DbException, FormatException { MessageType type = MessageType.valueOf(meta.getLong(KEY_TYPE).intValue()); switch (type) { case JOIN: handleJoinMessage(txn, m, meta); - return true; + return ACCEPT_SHARE; case POST: handleGroupMessage(txn, m, meta); - return true; + return ACCEPT_SHARE; default: // the validator should only let valid types pass throw new RuntimeException("Unknown MessageType"); diff --git a/briar-core/src/main/java/org/briarproject/briar/privategroup/invitation/GroupInvitationManagerImpl.java b/briar-core/src/main/java/org/briarproject/briar/privategroup/invitation/GroupInvitationManagerImpl.java index ece8ed827..3178d8ce8 100644 --- a/briar-core/src/main/java/org/briarproject/briar/privategroup/invitation/GroupInvitationManagerImpl.java +++ b/briar-core/src/main/java/org/briarproject/briar/privategroup/invitation/GroupInvitationManagerImpl.java @@ -54,6 +54,7 @@ import javax.annotation.concurrent.Immutable; import javax.inject.Inject; import static org.briarproject.bramble.api.sync.Group.Visibility.SHARED; +import static org.briarproject.bramble.api.sync.validation.IncomingMessageHook.DeliveryAction.ACCEPT_DO_NOT_SHARE; import static org.briarproject.briar.api.autodelete.AutoDeleteConstants.NO_AUTO_DELETE_TIMER; import static org.briarproject.briar.privategroup.invitation.CreatorState.START; import static org.briarproject.briar.privategroup.invitation.MessageType.ABORT; @@ -147,8 +148,9 @@ class GroupInvitationManagerImpl extends ConversationClientImpl } @Override - protected boolean incomingMessage(Transaction txn, Message m, BdfList body, - BdfDictionary bdfMeta) throws DbException, FormatException { + protected DeliveryAction incomingMessage(Transaction txn, Message m, + BdfList body, BdfDictionary bdfMeta) + throws DbException, FormatException { // Parse the metadata MessageMetadata meta = messageParser.parseMetadata(bdfMeta); // set the clean-up timer that will be started when message gets read @@ -171,7 +173,7 @@ class GroupInvitationManagerImpl extends ConversationClientImpl } // Store the updated session storeSession(txn, storageId, session); - return false; + return ACCEPT_DO_NOT_SHARE; } private SessionId getSessionId(GroupId privateGroupId) { diff --git a/briar-core/src/main/java/org/briarproject/briar/sharing/SharingManagerImpl.java b/briar-core/src/main/java/org/briarproject/briar/sharing/SharingManagerImpl.java index 4e47b3937..5ef16e12f 100644 --- a/briar-core/src/main/java/org/briarproject/briar/sharing/SharingManagerImpl.java +++ b/briar-core/src/main/java/org/briarproject/briar/sharing/SharingManagerImpl.java @@ -49,6 +49,7 @@ import java.util.Set; import javax.annotation.Nullable; import static org.briarproject.bramble.api.sync.Group.Visibility.SHARED; +import static org.briarproject.bramble.api.sync.validation.IncomingMessageHook.DeliveryAction.ACCEPT_DO_NOT_SHARE; import static org.briarproject.briar.api.autodelete.AutoDeleteConstants.NO_AUTO_DELETE_TIMER; import static org.briarproject.briar.sharing.MessageType.ABORT; import static org.briarproject.briar.sharing.MessageType.ACCEPT; @@ -133,8 +134,8 @@ abstract class SharingManagerImpl } @Override - protected boolean incomingMessage(Transaction txn, Message m, BdfList body, - BdfDictionary d) throws DbException, FormatException { + protected DeliveryAction incomingMessage(Transaction txn, Message m, + BdfList body, BdfDictionary d) throws DbException, FormatException { // Parse the metadata MessageMetadata meta = messageParser.parseMetadata(d); // set the clean-up timer that will be started when message gets read @@ -157,7 +158,7 @@ abstract class SharingManagerImpl } // Store the updated session storeSession(txn, storageId, session); - return false; + return ACCEPT_DO_NOT_SHARE; } /** diff --git a/briar-core/src/test/java/org/briarproject/briar/avatar/AvatarManagerImplTest.java b/briar-core/src/test/java/org/briarproject/briar/avatar/AvatarManagerImplTest.java index e8c32f13e..c02a09c8d 100644 --- a/briar-core/src/test/java/org/briarproject/briar/avatar/AvatarManagerImplTest.java +++ b/briar-core/src/test/java/org/briarproject/briar/avatar/AvatarManagerImplTest.java @@ -44,6 +44,7 @@ import javax.annotation.Nullable; import static org.briarproject.bramble.api.sync.Group.Visibility.INVISIBLE; import static org.briarproject.bramble.api.sync.Group.Visibility.SHARED; import static org.briarproject.bramble.api.sync.Group.Visibility.VISIBLE; +import static org.briarproject.bramble.api.sync.validation.IncomingMessageHook.DeliveryAction.ACCEPT_DO_NOT_SHARE; import static org.briarproject.bramble.test.TestUtils.getContact; import static org.briarproject.bramble.test.TestUtils.getGroup; import static org.briarproject.bramble.test.TestUtils.getLocalAuthor; @@ -59,7 +60,6 @@ import static org.briarproject.briar.api.avatar.AvatarManager.MAJOR_VERSION; import static org.briarproject.briar.avatar.AvatarConstants.GROUP_KEY_CONTACT_ID; import static org.briarproject.briar.avatar.AvatarConstants.MSG_KEY_VERSION; import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; public class AvatarManagerImplTest extends BrambleMockTestCase { @@ -196,7 +196,8 @@ public class AvatarManagerImplTest extends BrambleMockTestCase { null); expectGetContactId(txn, contactGroupId, contact.getId()); - assertFalse(avatarManager.incomingMessage(txn, contactMsg, meta)); + assertEquals(ACCEPT_DO_NOT_SHARE, + avatarManager.incomingMessage(txn, contactMsg, meta)); assertEquals(1, txn.getActions().size()); Event event = ((EventAction) txn.getActions().get(0)).getEvent(); AvatarUpdatedEvent avatarUpdatedEvent = (AvatarUpdatedEvent) event; @@ -230,7 +231,8 @@ public class AvatarManagerImplTest extends BrambleMockTestCase { expectFindLatest(txn, contactGroupId, latestMsgId, latest); expectGetContactId(txn, contactGroupId, contact.getId()); - assertFalse(avatarManager.incomingMessage(txn, contactMsg, meta)); + assertEquals(ACCEPT_DO_NOT_SHARE, + avatarManager.incomingMessage(txn, contactMsg, meta)); // event to broadcast assertEquals(1, txn.getActions().size()); @@ -260,7 +262,8 @@ public class AvatarManagerImplTest extends BrambleMockTestCase { }}); expectFindLatest(txn, contactGroupId, latestMsgId, latest); - assertFalse(avatarManager.incomingMessage(txn, contactMsg, meta)); + assertEquals(ACCEPT_DO_NOT_SHARE, + avatarManager.incomingMessage(txn, contactMsg, meta)); // no event to broadcast assertEquals(0, txn.getActions().size()); @@ -271,7 +274,8 @@ public class AvatarManagerImplTest extends BrambleMockTestCase { throws DbException, InvalidMessageException { Transaction txn = new Transaction(null, false); expectGetOurGroup(txn); - avatarManager.incomingMessage(txn, ourMsg, meta); + assertEquals(ACCEPT_DO_NOT_SHARE, + avatarManager.incomingMessage(txn, ourMsg, meta)); } @Test diff --git a/briar-core/src/test/java/org/briarproject/briar/blog/BlogManagerImplTest.java b/briar-core/src/test/java/org/briarproject/briar/blog/BlogManagerImplTest.java index fb700e667..818162ce6 100644 --- a/briar-core/src/test/java/org/briarproject/briar/blog/BlogManagerImplTest.java +++ b/briar-core/src/test/java/org/briarproject/briar/blog/BlogManagerImplTest.java @@ -33,6 +33,7 @@ import org.jmock.Expectations; import org.jmock.Mockery; import org.junit.Test; +import static org.briarproject.bramble.api.sync.validation.IncomingMessageHook.DeliveryAction.ACCEPT_SHARE; import static org.briarproject.bramble.test.TestUtils.getContact; import static org.briarproject.bramble.test.TestUtils.getGroup; import static org.briarproject.bramble.test.TestUtils.getLocalAuthor; @@ -184,7 +185,8 @@ public class BlogManagerImplTest extends BriarTestCase { will(returnValue(verifiedInfo)); }}); - blogManager.incomingMessage(txn, message, body, meta); + assertEquals(ACCEPT_SHARE, + blogManager.incomingMessage(txn, message, body, meta)); context.assertIsSatisfied(); assertEquals(1, txn.getActions().size()); @@ -225,7 +227,8 @@ public class BlogManagerImplTest extends BriarTestCase { will(returnValue(rssLocalAuthor)); }}); - blogManager.incomingMessage(txn, rssMessage, body, meta); + assertEquals(ACCEPT_SHARE, + blogManager.incomingMessage(txn, rssMessage, body, meta)); context.assertIsSatisfied(); assertEquals(1, txn.getActions().size()); diff --git a/briar-core/src/test/java/org/briarproject/briar/privategroup/invitation/GroupInvitationManagerImplTest.java b/briar-core/src/test/java/org/briarproject/briar/privategroup/invitation/GroupInvitationManagerImplTest.java index 7b873d897..4fa02970c 100644 --- a/briar-core/src/test/java/org/briarproject/briar/privategroup/invitation/GroupInvitationManagerImplTest.java +++ b/briar-core/src/test/java/org/briarproject/briar/privategroup/invitation/GroupInvitationManagerImplTest.java @@ -47,6 +47,7 @@ import javax.annotation.Nullable; import static java.util.Arrays.asList; import static junit.framework.TestCase.fail; import static org.briarproject.bramble.api.sync.Group.Visibility.SHARED; +import static org.briarproject.bramble.api.sync.validation.IncomingMessageHook.DeliveryAction.ACCEPT_DO_NOT_SHARE; import static org.briarproject.bramble.test.TestUtils.getAuthor; import static org.briarproject.bramble.test.TestUtils.getContact; import static org.briarproject.bramble.test.TestUtils.getGroup; @@ -275,43 +276,50 @@ public class GroupInvitationManagerImplTest extends BrambleMockTestCase { @Test public void testIncomingFirstInviteMessage() throws Exception { expectFirstIncomingMessage(Role.INVITEE, INVITE); - groupInvitationManager.incomingMessage(txn, message, body, meta); + assertEquals(ACCEPT_DO_NOT_SHARE, groupInvitationManager + .incomingMessage(txn, message, body, meta)); } @Test public void testIncomingFirstJoinMessage() throws Exception { expectFirstIncomingMessage(Role.PEER, JOIN); - groupInvitationManager.incomingMessage(txn, message, body, meta); + assertEquals(ACCEPT_DO_NOT_SHARE, groupInvitationManager + .incomingMessage(txn, message, body, meta)); } @Test public void testIncomingInviteMessage() throws Exception { expectIncomingMessage(Role.INVITEE, INVITE); - groupInvitationManager.incomingMessage(txn, message, body, meta); + assertEquals(ACCEPT_DO_NOT_SHARE, groupInvitationManager + .incomingMessage(txn, message, body, meta)); } @Test public void testIncomingJoinMessage() throws Exception { expectIncomingMessage(Role.INVITEE, JOIN); - groupInvitationManager.incomingMessage(txn, message, body, meta); + assertEquals(ACCEPT_DO_NOT_SHARE, groupInvitationManager + .incomingMessage(txn, message, body, meta)); } @Test public void testIncomingJoinMessageForCreator() throws Exception { expectIncomingMessage(Role.CREATOR, JOIN); - groupInvitationManager.incomingMessage(txn, message, body, meta); + assertEquals(ACCEPT_DO_NOT_SHARE, groupInvitationManager + .incomingMessage(txn, message, body, meta)); } @Test public void testIncomingLeaveMessage() throws Exception { expectIncomingMessage(Role.INVITEE, LEAVE); - groupInvitationManager.incomingMessage(txn, message, body, meta); + assertEquals(ACCEPT_DO_NOT_SHARE, groupInvitationManager + .incomingMessage(txn, message, body, meta)); } @Test public void testIncomingAbortMessage() throws Exception { expectIncomingMessage(Role.INVITEE, ABORT); - groupInvitationManager.incomingMessage(txn, message, body, meta); + assertEquals(ACCEPT_DO_NOT_SHARE, groupInvitationManager + .incomingMessage(txn, message, body, meta)); } private void expectFirstIncomingMessage(Role role, MessageType type) From e35ffe0cf007448ca8819d379bb19f5d9fa7ced9 Mon Sep 17 00:00:00 2001 From: akwizgran Date: Tue, 1 Jun 2021 11:33:06 +0100 Subject: [PATCH 20/77] Add javadocs for message states. --- .../api/sync/validation/MessageState.java | 27 ++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/bramble-api/src/main/java/org/briarproject/bramble/api/sync/validation/MessageState.java b/bramble-api/src/main/java/org/briarproject/bramble/api/sync/validation/MessageState.java index 42a1adb85..770d57617 100644 --- a/bramble-api/src/main/java/org/briarproject/bramble/api/sync/validation/MessageState.java +++ b/bramble-api/src/main/java/org/briarproject/bramble/api/sync/validation/MessageState.java @@ -1,8 +1,33 @@ package org.briarproject.bramble.api.sync.validation; +import org.briarproject.bramble.api.sync.validation.IncomingMessageHook.DeliveryAction; + public enum MessageState { - UNKNOWN(0), INVALID(1), PENDING(2), DELIVERED(3); + /** + * A remote message that has not yet been validated. + */ + UNKNOWN(0), + + /** + * A remote message that has failed validation, has been + * {@link DeliveryAction#REJECT rejected} by the local sync client, or + * depends on another message that has failed validation or been rejected. + */ + INVALID(1), + + /** + * A remote message that has passed validation and is awaiting delivery to + * the local sync client. The message will not be delivered until all its + * dependencies have been validated and delivered. + */ + PENDING(2), + + /** + * A local message, or a remote message that has passed validation and + * been delivered to the local sync client. + */ + DELIVERED(3); private final int value; From 2ac3bdd3aea9eace933cf05dc49a63bb3191a5e6 Mon Sep 17 00:00:00 2001 From: akwizgran Date: Mon, 17 May 2021 16:23:54 +0100 Subject: [PATCH 21/77] Add database method for getting transports with keys. --- .../bramble/api/db/DatabaseComponent.java | 10 ++++++ .../org/briarproject/bramble/db/Database.java | 10 ++++++ .../bramble/db/DatabaseComponentImpl.java | 7 ++++ .../briarproject/bramble/db/JdbcDatabase.java | 32 +++++++++++++++++++ .../bramble/db/JdbcDatabaseTest.java | 6 ++++ 5 files changed, 65 insertions(+) diff --git a/bramble-api/src/main/java/org/briarproject/bramble/api/db/DatabaseComponent.java b/bramble-api/src/main/java/org/briarproject/bramble/api/db/DatabaseComponent.java index 3c79254d3..f95f493a3 100644 --- a/bramble-api/src/main/java/org/briarproject/bramble/api/db/DatabaseComponent.java +++ b/bramble-api/src/main/java/org/briarproject/bramble/api/db/DatabaseComponent.java @@ -493,6 +493,16 @@ public interface DatabaseComponent extends TransactionManager { Collection getTransportKeys(Transaction txn, TransportId t) throws DbException; + /** + * Returns the contact IDs and transport IDs for which the DB contains + * at least one set of transport keys. Handshake mode and rotation mode + * keys are included, whether activated or not. + *

+ * Read-only. + */ + Map> getTransportsWithKeys( + Transaction txn) throws DbException; + /** * Increments the outgoing stream counter for the given transport keys. */ diff --git a/bramble-core/src/main/java/org/briarproject/bramble/db/Database.java b/bramble-core/src/main/java/org/briarproject/bramble/db/Database.java index ab291fa30..2744abe23 100644 --- a/bramble-core/src/main/java/org/briarproject/bramble/db/Database.java +++ b/bramble-core/src/main/java/org/briarproject/bramble/db/Database.java @@ -590,6 +590,16 @@ interface Database { Collection getTransportKeys(T txn, TransportId t) throws DbException; + /** + * Returns the contact IDs and transport IDs for which the DB contains + * at least one set of transport keys. Handshake mode and rotation mode + * keys are included, whether activated or not. + *

+ * Read-only. + */ + Map> getTransportsWithKeys(T txn) + throws DbException; + /** * Increments the outgoing stream counter for the given transport keys. */ diff --git a/bramble-core/src/main/java/org/briarproject/bramble/db/DatabaseComponentImpl.java b/bramble-core/src/main/java/org/briarproject/bramble/db/DatabaseComponentImpl.java index 40ba6fa31..776f02c6f 100644 --- a/bramble-core/src/main/java/org/briarproject/bramble/db/DatabaseComponentImpl.java +++ b/bramble-core/src/main/java/org/briarproject/bramble/db/DatabaseComponentImpl.java @@ -780,6 +780,13 @@ class DatabaseComponentImpl implements DatabaseComponent { return db.getTransportKeys(txn, t); } + @Override + public Map> getTransportsWithKeys( + Transaction transaction) throws DbException { + T txn = unbox(transaction); + return db.getTransportsWithKeys(txn); + } + @Override public void incrementStreamCounter(Transaction transaction, TransportId t, KeySetId k) throws DbException { diff --git a/bramble-core/src/main/java/org/briarproject/bramble/db/JdbcDatabase.java b/bramble-core/src/main/java/org/briarproject/bramble/db/JdbcDatabase.java index af54d4e9d..43f604e73 100644 --- a/bramble-core/src/main/java/org/briarproject/bramble/db/JdbcDatabase.java +++ b/bramble-core/src/main/java/org/briarproject/bramble/db/JdbcDatabase.java @@ -2605,6 +2605,38 @@ abstract class JdbcDatabase implements Database { } } + @Override + public Map> getTransportsWithKeys( + Connection txn) throws DbException { + Statement s = null; + ResultSet rs = null; + try { + String sql = "SELECT DISTINCT contactId, transportId" + + " FROM outgoingKeys"; + s = txn.createStatement(); + rs = s.executeQuery(sql); + Map> ids = new HashMap<>(); + while (rs.next()) { + ContactId c = new ContactId(rs.getInt(1)); + TransportId t = new TransportId(rs.getString(2)); + Collection transportIds = ids.get(c); + if (transportIds == null) { + transportIds = new ArrayList<>(); + ids.put(c, transportIds); + } + transportIds.add(t); + } + rs.close(); + s.close(); + return ids; + } catch (SQLException e) { + tryToClose(rs, LOG, WARNING); + tryToClose(s, LOG, WARNING); + tryToClose(s, LOG, WARNING); + throw new DbException(e); + } + } + @Override public void incrementStreamCounter(Connection txn, TransportId t, KeySetId k) throws DbException { diff --git a/bramble-core/src/test/java/org/briarproject/bramble/db/JdbcDatabaseTest.java b/bramble-core/src/test/java/org/briarproject/bramble/db/JdbcDatabaseTest.java index 69bc97671..9336bedd1 100644 --- a/bramble-core/src/test/java/org/briarproject/bramble/db/JdbcDatabaseTest.java +++ b/bramble-core/src/test/java/org/briarproject/bramble/db/JdbcDatabaseTest.java @@ -699,6 +699,7 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase { // Initially there should be no transport keys in the database assertEquals(emptyList(), db.getTransportKeys(txn, transportId)); + assertTrue(db.getTransportsWithKeys(txn).isEmpty()); // Add the contact, the transport and the transport keys db.addIdentity(txn, identity); @@ -721,6 +722,8 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase { assertKeysEquals(keys1, ks.getKeys()); } } + assertEquals(singletonMap(contactId, singletonList(transportId)), + db.getTransportsWithKeys(txn)); // Update the transport keys TransportKeys updated = createTransportKeys(timePeriod + 1, active); @@ -743,10 +746,13 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase { assertKeysEquals(updated1, ks.getKeys()); } } + assertEquals(singletonMap(contactId, singletonList(transportId)), + db.getTransportsWithKeys(txn)); // Removing the contact should remove the transport keys db.removeContact(txn, contactId); assertEquals(emptyList(), db.getTransportKeys(txn, transportId)); + assertTrue(db.getTransportsWithKeys(txn).isEmpty()); db.commitTransaction(txn); db.close(); From ee6f571c318adfac42c7dee1c7c0dcf851d7bd18 Mon Sep 17 00:00:00 2001 From: akwizgran Date: Wed, 19 May 2021 10:42:17 +0100 Subject: [PATCH 22/77] Add a DB method for checking whether transport keys exist. --- .../bramble/api/db/DatabaseComponent.java | 10 ++++++++ .../org/briarproject/bramble/db/Database.java | 10 ++++++++ .../bramble/db/DatabaseComponentImpl.java | 7 ++++++ .../briarproject/bramble/db/JdbcDatabase.java | 23 +++++++++++++++++++ .../bramble/db/JdbcDatabaseTest.java | 4 ++++ 5 files changed, 54 insertions(+) diff --git a/bramble-api/src/main/java/org/briarproject/bramble/api/db/DatabaseComponent.java b/bramble-api/src/main/java/org/briarproject/bramble/api/db/DatabaseComponent.java index f95f493a3..2771302a2 100644 --- a/bramble-api/src/main/java/org/briarproject/bramble/api/db/DatabaseComponent.java +++ b/bramble-api/src/main/java/org/briarproject/bramble/api/db/DatabaseComponent.java @@ -150,6 +150,16 @@ public interface DatabaseComponent extends TransactionManager { boolean containsPendingContact(Transaction txn, PendingContactId p) throws DbException; + /** + * Returns true if the database contains keys for communicating with the + * given contact over the given transport. Handshake mode and rotation mode + * keys are included, whether activated or not. + *

+ * Read-only. + */ + boolean containsTransportKeys(Transaction txn, ContactId c, TransportId t) + throws DbException; + /** * Deletes the message with the given ID. Unlike * {@link #removeMessage(Transaction, MessageId)}, the message ID, diff --git a/bramble-core/src/main/java/org/briarproject/bramble/db/Database.java b/bramble-core/src/main/java/org/briarproject/bramble/db/Database.java index 2744abe23..705913ee7 100644 --- a/bramble-core/src/main/java/org/briarproject/bramble/db/Database.java +++ b/bramble-core/src/main/java/org/briarproject/bramble/db/Database.java @@ -215,6 +215,16 @@ interface Database { */ boolean containsTransport(T txn, TransportId t) throws DbException; + /** + * Returns true if the database contains keys for communicating with the + * given contact over the given transport. Handshake mode and rotation mode + * keys are included, whether activated or not. + *

+ * Read-only. + */ + boolean containsTransportKeys(T txn, ContactId c, TransportId t) + throws DbException; + /** * Returns true if the database contains the given message, the message is * shared, and the visibility of the message's group to the given contact diff --git a/bramble-core/src/main/java/org/briarproject/bramble/db/DatabaseComponentImpl.java b/bramble-core/src/main/java/org/briarproject/bramble/db/DatabaseComponentImpl.java index 776f02c6f..b2422f9bc 100644 --- a/bramble-core/src/main/java/org/briarproject/bramble/db/DatabaseComponentImpl.java +++ b/bramble-core/src/main/java/org/briarproject/bramble/db/DatabaseComponentImpl.java @@ -371,6 +371,13 @@ class DatabaseComponentImpl implements DatabaseComponent { return db.containsPendingContact(txn, p); } + @Override + public boolean containsTransportKeys(Transaction transaction, ContactId c, + TransportId t) throws DbException { + T txn = unbox(transaction); + return db.containsTransportKeys(txn, c, t); + } + @Override public void deleteMessage(Transaction transaction, MessageId m) throws DbException { diff --git a/bramble-core/src/main/java/org/briarproject/bramble/db/JdbcDatabase.java b/bramble-core/src/main/java/org/briarproject/bramble/db/JdbcDatabase.java index 43f604e73..224b20bf1 100644 --- a/bramble-core/src/main/java/org/briarproject/bramble/db/JdbcDatabase.java +++ b/bramble-core/src/main/java/org/briarproject/bramble/db/JdbcDatabase.java @@ -1277,6 +1277,29 @@ abstract class JdbcDatabase implements Database { } } + @Override + public boolean containsTransportKeys(Connection txn, ContactId c, + TransportId t) throws DbException { + PreparedStatement ps = null; + ResultSet rs = null; + try { + String sql = "SELECT NULL FROM outgoingKeys" + + " WHERE contactId = ? AND transportId = ?"; + ps = txn.prepareStatement(sql); + ps.setInt(1, c.getInt()); + ps.setString(2, t.getString()); + rs = ps.executeQuery(); + boolean found = rs.next(); + rs.close(); + ps.close(); + return found; + } catch (SQLException e) { + tryToClose(rs, LOG, WARNING); + tryToClose(ps, LOG, WARNING); + throw new DbException(e); + } + } + @Override public boolean containsVisibleMessage(Connection txn, ContactId c, MessageId m) throws DbException { diff --git a/bramble-core/src/test/java/org/briarproject/bramble/db/JdbcDatabaseTest.java b/bramble-core/src/test/java/org/briarproject/bramble/db/JdbcDatabaseTest.java index 9336bedd1..2da33c18d 100644 --- a/bramble-core/src/test/java/org/briarproject/bramble/db/JdbcDatabaseTest.java +++ b/bramble-core/src/test/java/org/briarproject/bramble/db/JdbcDatabaseTest.java @@ -698,6 +698,7 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase { Connection txn = db.startTransaction(); // Initially there should be no transport keys in the database + assertFalse(db.containsTransportKeys(txn, contactId, transportId)); assertEquals(emptyList(), db.getTransportKeys(txn, transportId)); assertTrue(db.getTransportsWithKeys(txn).isEmpty()); @@ -710,6 +711,7 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase { assertEquals(keySetId1, db.addTransportKeys(txn, contactId, keys1)); // Retrieve the transport keys + assertTrue(db.containsTransportKeys(txn, contactId, transportId)); Collection allKeys = db.getTransportKeys(txn, transportId); assertEquals(2, allKeys.size()); @@ -735,6 +737,7 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase { null, updated1)); // Retrieve the transport keys again + assertTrue(db.containsTransportKeys(txn, contactId, transportId)); allKeys = db.getTransportKeys(txn, transportId); assertEquals(2, allKeys.size()); for (TransportKeySet ks : allKeys) { @@ -751,6 +754,7 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase { // Removing the contact should remove the transport keys db.removeContact(txn, contactId); + assertFalse(db.containsTransportKeys(txn, contactId, transportId)); assertEquals(emptyList(), db.getTransportKeys(txn, transportId)); assertTrue(db.getTransportsWithKeys(txn).isEmpty()); From 9cc8d4477842997e37db1c2869ff616a13383b35 Mon Sep 17 00:00:00 2001 From: akwizgran Date: Wed, 19 May 2021 10:42:47 +0100 Subject: [PATCH 23/77] Add a key manager method for adding a single set of transport keys. --- .../bramble/api/transport/KeyManager.java | 19 +++++++++++++++++-- .../bramble/transport/KeyManagerImpl.java | 16 ++++++++++++---- 2 files changed, 29 insertions(+), 6 deletions(-) diff --git a/bramble-api/src/main/java/org/briarproject/bramble/api/transport/KeyManager.java b/bramble-api/src/main/java/org/briarproject/bramble/api/transport/KeyManager.java index 50f7d8aa0..086e46416 100644 --- a/bramble-api/src/main/java/org/briarproject/bramble/api/transport/KeyManager.java +++ b/bramble-api/src/main/java/org/briarproject/bramble/api/transport/KeyManager.java @@ -22,8 +22,23 @@ public interface KeyManager { /** * Derives and stores a set of rotation mode transport keys for - * communicating with the given contact over each transport and returns the - * key set IDs. + * communicating with the given contact over the given transport and + * returns the key set ID. + *

+ * {@link StreamContext StreamContexts} for the contact can be created + * after this method has returned. + * + * @param alice True if the local party is Alice + * @param active Whether the derived keys can be used for outgoing streams + */ + KeySetId addRotationKeys(Transaction txn, ContactId c, TransportId t, + SecretKey rootKey, long timestamp, boolean alice, + boolean active) throws DbException; + + /** + * Derives and stores a set of rotation mode transport keys for + * communicating with the given contact over each supported transport and + * returns the key set IDs. *

* {@link StreamContext StreamContexts} for the contact can be created * after this method has returned. diff --git a/bramble-core/src/main/java/org/briarproject/bramble/transport/KeyManagerImpl.java b/bramble-core/src/main/java/org/briarproject/bramble/transport/KeyManagerImpl.java index 58aeb7cea..ded31ab50 100644 --- a/bramble-core/src/main/java/org/briarproject/bramble/transport/KeyManagerImpl.java +++ b/bramble-core/src/main/java/org/briarproject/bramble/transport/KeyManagerImpl.java @@ -101,9 +101,17 @@ class KeyManagerImpl implements KeyManager, Service, EventListener { } @Override - public Map addRotationKeys( - Transaction txn, ContactId c, SecretKey rootKey, long timestamp, - boolean alice, boolean active) throws DbException { + public KeySetId addRotationKeys(Transaction txn, ContactId c, + TransportId t, SecretKey rootKey, long timestamp, boolean alice, + boolean active) throws DbException { + return withManager(t, m -> + m.addRotationKeys(txn, c, rootKey, timestamp, alice, active)); + } + + @Override + public Map addRotationKeys(Transaction txn, + ContactId c, SecretKey rootKey, long timestamp, boolean alice, + boolean active) throws DbException { Map ids = new HashMap<>(); for (Entry e : managers.entrySet()) { TransportId t = e.getKey(); @@ -137,7 +145,7 @@ class KeyManagerImpl implements KeyManager, Service, EventListener { PendingContactId p, PublicKey theirPublicKey, KeyPair ourKeyPair) throws DbException, GeneralSecurityException { SecretKey staticMasterKey = transportCrypto - .deriveStaticMasterKey(theirPublicKey, ourKeyPair); + .deriveStaticMasterKey(theirPublicKey, ourKeyPair); SecretKey rootKey = transportCrypto.deriveHandshakeRootKey(staticMasterKey, true); boolean alice = transportCrypto.isAlice(theirPublicKey, ourKeyPair); From 6e6cadd3adb561dec5b85dc5c2f075f6140700ee Mon Sep 17 00:00:00 2001 From: akwizgran Date: Fri, 21 May 2021 13:18:59 +0100 Subject: [PATCH 24/77] Refactor KeyManager startup so managers are created earlier. --- .../bramble/api/transport/KeyManager.java | 3 +- .../bramble/transport/KeyManagerImpl.java | 38 ++++++++++--------- .../bramble/transport/KeyManagerImplTest.java | 23 +++++++---- 3 files changed, 38 insertions(+), 26 deletions(-) diff --git a/bramble-api/src/main/java/org/briarproject/bramble/api/transport/KeyManager.java b/bramble-api/src/main/java/org/briarproject/bramble/api/transport/KeyManager.java index 086e46416..db906e649 100644 --- a/bramble-api/src/main/java/org/briarproject/bramble/api/transport/KeyManager.java +++ b/bramble-api/src/main/java/org/briarproject/bramble/api/transport/KeyManager.java @@ -23,7 +23,7 @@ public interface KeyManager { /** * Derives and stores a set of rotation mode transport keys for * communicating with the given contact over the given transport and - * returns the key set ID. + * returns the key set ID, or null if the transport is not supported. *

* {@link StreamContext StreamContexts} for the contact can be created * after this method has returned. @@ -31,6 +31,7 @@ public interface KeyManager { * @param alice True if the local party is Alice * @param active Whether the derived keys can be used for outgoing streams */ + @Nullable KeySetId addRotationKeys(Transaction txn, ContactId c, TransportId t, SecretKey rootKey, long timestamp, boolean alice, boolean active) throws DbException; diff --git a/bramble-core/src/main/java/org/briarproject/bramble/transport/KeyManagerImpl.java b/bramble-core/src/main/java/org/briarproject/bramble/transport/KeyManagerImpl.java index ded31ab50..8d6ce998c 100644 --- a/bramble-core/src/main/java/org/briarproject/bramble/transport/KeyManagerImpl.java +++ b/bramble-core/src/main/java/org/briarproject/bramble/transport/KeyManagerImpl.java @@ -51,7 +51,6 @@ class KeyManagerImpl implements KeyManager, Service, EventListener { private final DatabaseComponent db; private final Executor dbExecutor; private final PluginConfig pluginConfig; - private final TransportKeyManagerFactory transportKeyManagerFactory; private final TransportCrypto transportCrypto; private final ConcurrentHashMap managers; @@ -61,34 +60,39 @@ class KeyManagerImpl implements KeyManager, Service, EventListener { KeyManagerImpl(DatabaseComponent db, @DatabaseExecutor Executor dbExecutor, PluginConfig pluginConfig, - TransportKeyManagerFactory transportKeyManagerFactory, - TransportCrypto transportCrypto) { + TransportCrypto transportCrypto, + TransportKeyManagerFactory transportKeyManagerFactory) { this.db = db; this.dbExecutor = dbExecutor; this.pluginConfig = pluginConfig; - this.transportKeyManagerFactory = transportKeyManagerFactory; this.transportCrypto = transportCrypto; managers = new ConcurrentHashMap<>(); + for (SimplexPluginFactory f : pluginConfig.getSimplexFactories()) { + TransportKeyManager m = transportKeyManagerFactory. + createTransportKeyManager(f.getId(), f.getMaxLatency()); + managers.put(f.getId(), m); + } + for (DuplexPluginFactory f : pluginConfig.getDuplexFactories()) { + TransportKeyManager m = transportKeyManagerFactory. + createTransportKeyManager(f.getId(), f.getMaxLatency()); + managers.put(f.getId(), m); + } } @Override public void startService() throws ServiceException { if (used.getAndSet(true)) throw new IllegalStateException(); - Map transports = new HashMap<>(); - for (SimplexPluginFactory f : pluginConfig.getSimplexFactories()) - transports.put(f.getId(), f.getMaxLatency()); - for (DuplexPluginFactory f : pluginConfig.getDuplexFactories()) - transports.put(f.getId(), f.getMaxLatency()); try { db.transaction(false, txn -> { - for (Entry e : transports.entrySet()) - db.addTransport(txn, e.getKey(), e.getValue()); - for (Entry e : transports.entrySet()) { - TransportKeyManager m = transportKeyManagerFactory - .createTransportKeyManager(e.getKey(), - e.getValue()); - managers.put(e.getKey(), m); - m.start(txn); + for (SimplexPluginFactory f : + pluginConfig.getSimplexFactories()) { + db.addTransport(txn, f.getId(), f.getMaxLatency()); + managers.get(f.getId()).start(txn); + } + for (DuplexPluginFactory f : + pluginConfig.getDuplexFactories()) { + db.addTransport(txn, f.getId(), f.getMaxLatency()); + managers.get(f.getId()).start(txn); } }); } catch (DbException e) { diff --git a/bramble-core/src/test/java/org/briarproject/bramble/transport/KeyManagerImplTest.java b/bramble-core/src/test/java/org/briarproject/bramble/transport/KeyManagerImplTest.java index 142f06538..02bb20244 100644 --- a/bramble-core/src/test/java/org/briarproject/bramble/transport/KeyManagerImplTest.java +++ b/bramble-core/src/test/java/org/briarproject/bramble/transport/KeyManagerImplTest.java @@ -25,6 +25,7 @@ import java.util.Collection; import java.util.Map; import java.util.Random; +import static java.util.Collections.emptyList; import static java.util.Collections.singletonList; import static java.util.Collections.singletonMap; import static org.briarproject.bramble.api.transport.TransportConstants.TAG_LENGTH; @@ -71,8 +72,7 @@ public class KeyManagerImplTest extends BrambleMockTestCase { private final SecretKey rootKey = getSecretKey(); private final Random random = new Random(); - private final KeyManagerImpl keyManager = new KeyManagerImpl(db, executor, - pluginConfig, transportKeyManagerFactory, transportCrypto); + private KeyManagerImpl keyManager; @Before public void testStartService() throws Exception { @@ -83,18 +83,25 @@ public class KeyManagerImplTest extends BrambleMockTestCase { singletonList(pluginFactory); int maxLatency = 1337; - context.checking(new DbExpectations() {{ - oneOf(pluginConfig).getSimplexFactories(); + context.checking(new Expectations() {{ + allowing(pluginConfig).getSimplexFactories(); will(returnValue(factories)); - oneOf(pluginFactory).getId(); + allowing(pluginFactory).getId(); will(returnValue(transportId)); - oneOf(pluginFactory).getMaxLatency(); + allowing(pluginFactory).getMaxLatency(); will(returnValue(maxLatency)); - oneOf(db).addTransport(txn, transportId, maxLatency); + allowing(pluginConfig).getDuplexFactories(); + will(returnValue(emptyList())); oneOf(transportKeyManagerFactory) .createTransportKeyManager(transportId, maxLatency); will(returnValue(transportKeyManager)); - oneOf(pluginConfig).getDuplexFactories(); + }}); + + keyManager = new KeyManagerImpl(db, executor, + pluginConfig, transportCrypto, transportKeyManagerFactory); + + context.checking(new DbExpectations() {{ + oneOf(db).addTransport(txn, transportId, maxLatency); oneOf(db).transaction(with(false), withDbRunnable(txn)); oneOf(transportKeyManager).start(txn); }}); From e228b9fcbf2e5e2707cb1a87a1d74e3fc9e2f06a Mon Sep 17 00:00:00 2001 From: akwizgran Date: Mon, 17 May 2021 16:24:41 +0100 Subject: [PATCH 25/77] Add transport key agreement client. --- .../TransportKeyAgreementManager.java | 24 + .../bramble/BrambleCoreEagerSingletons.java | 4 + .../bramble/BrambleCoreModule.java | 2 + .../transport/agreement/MessageEncoder.java | 22 + .../agreement/MessageEncoderImpl.java | 77 +++ .../transport/agreement/MessageType.java | 29 + .../bramble/transport/agreement/Session.java | 65 ++ .../transport/agreement/SessionEncoder.java | 13 + .../agreement/SessionEncoderImpl.java | 70 +++ .../transport/agreement/SessionParser.java | 11 + .../agreement/SessionParserImpl.java | 70 +++ .../bramble/transport/agreement/State.java | 43 ++ .../TransportKeyAgreementConstants.java | 28 + .../TransportKeyAgreementCrypto.java | 23 + .../TransportKeyAgreementCryptoImpl.java | 67 ++ .../TransportKeyAgreementManagerImpl.java | 411 ++++++++++++ .../TransportKeyAgreementModule.java | 83 +++ .../TransportKeyAgreementValidator.java | 77 +++ .../TransportKeyAgreementManagerImplTest.java | 589 ++++++++++++++++++ 19 files changed, 1708 insertions(+) create mode 100644 bramble-api/src/main/java/org/briarproject/bramble/api/transport/agreement/TransportKeyAgreementManager.java create mode 100644 bramble-core/src/main/java/org/briarproject/bramble/transport/agreement/MessageEncoder.java create mode 100644 bramble-core/src/main/java/org/briarproject/bramble/transport/agreement/MessageEncoderImpl.java create mode 100644 bramble-core/src/main/java/org/briarproject/bramble/transport/agreement/MessageType.java create mode 100644 bramble-core/src/main/java/org/briarproject/bramble/transport/agreement/Session.java create mode 100644 bramble-core/src/main/java/org/briarproject/bramble/transport/agreement/SessionEncoder.java create mode 100644 bramble-core/src/main/java/org/briarproject/bramble/transport/agreement/SessionEncoderImpl.java create mode 100644 bramble-core/src/main/java/org/briarproject/bramble/transport/agreement/SessionParser.java create mode 100644 bramble-core/src/main/java/org/briarproject/bramble/transport/agreement/SessionParserImpl.java create mode 100644 bramble-core/src/main/java/org/briarproject/bramble/transport/agreement/State.java create mode 100644 bramble-core/src/main/java/org/briarproject/bramble/transport/agreement/TransportKeyAgreementConstants.java create mode 100644 bramble-core/src/main/java/org/briarproject/bramble/transport/agreement/TransportKeyAgreementCrypto.java create mode 100644 bramble-core/src/main/java/org/briarproject/bramble/transport/agreement/TransportKeyAgreementCryptoImpl.java create mode 100644 bramble-core/src/main/java/org/briarproject/bramble/transport/agreement/TransportKeyAgreementManagerImpl.java create mode 100644 bramble-core/src/main/java/org/briarproject/bramble/transport/agreement/TransportKeyAgreementModule.java create mode 100644 bramble-core/src/main/java/org/briarproject/bramble/transport/agreement/TransportKeyAgreementValidator.java create mode 100644 bramble-core/src/test/java/org/briarproject/bramble/transport/agreement/TransportKeyAgreementManagerImplTest.java diff --git a/bramble-api/src/main/java/org/briarproject/bramble/api/transport/agreement/TransportKeyAgreementManager.java b/bramble-api/src/main/java/org/briarproject/bramble/api/transport/agreement/TransportKeyAgreementManager.java new file mode 100644 index 000000000..1b8fc6bc1 --- /dev/null +++ b/bramble-api/src/main/java/org/briarproject/bramble/api/transport/agreement/TransportKeyAgreementManager.java @@ -0,0 +1,24 @@ +package org.briarproject.bramble.api.transport.agreement; + +import org.briarproject.bramble.api.nullsafety.NotNullByDefault; +import org.briarproject.bramble.api.sync.ClientId; + +@NotNullByDefault +public interface TransportKeyAgreementManager { + + /** + * The unique ID of the transport key agreement client. + */ + ClientId CLIENT_ID = + new ClientId("org.briarproject.bramble.transport.agreement"); + + /** + * The current major version of the transport key agreement client. + */ + int MAJOR_VERSION = 0; + + /** + * The current minor version of the transport key agreement client. + */ + int MINOR_VERSION = 0; +} diff --git a/bramble-core/src/main/java/org/briarproject/bramble/BrambleCoreEagerSingletons.java b/bramble-core/src/main/java/org/briarproject/bramble/BrambleCoreEagerSingletons.java index c256759ff..6732f8001 100644 --- a/bramble-core/src/main/java/org/briarproject/bramble/BrambleCoreEagerSingletons.java +++ b/bramble-core/src/main/java/org/briarproject/bramble/BrambleCoreEagerSingletons.java @@ -11,6 +11,7 @@ import org.briarproject.bramble.properties.PropertiesModule; import org.briarproject.bramble.rendezvous.RendezvousModule; import org.briarproject.bramble.sync.validation.ValidationModule; import org.briarproject.bramble.transport.TransportModule; +import org.briarproject.bramble.transport.agreement.TransportKeyAgreementModule; import org.briarproject.bramble.versioning.VersioningModule; public interface BrambleCoreEagerSingletons { @@ -33,6 +34,8 @@ public interface BrambleCoreEagerSingletons { void inject(RendezvousModule.EagerSingletons init); + void inject(TransportKeyAgreementModule.EagerSingletons init); + void inject(TransportModule.EagerSingletons init); void inject(ValidationModule.EagerSingletons init); @@ -51,6 +54,7 @@ public interface BrambleCoreEagerSingletons { c.inject(new RendezvousModule.EagerSingletons()); c.inject(new PluginModule.EagerSingletons()); c.inject(new PropertiesModule.EagerSingletons()); + c.inject(new TransportKeyAgreementModule.EagerSingletons()); c.inject(new TransportModule.EagerSingletons()); c.inject(new ValidationModule.EagerSingletons()); c.inject(new VersioningModule.EagerSingletons()); diff --git a/bramble-core/src/main/java/org/briarproject/bramble/BrambleCoreModule.java b/bramble-core/src/main/java/org/briarproject/bramble/BrambleCoreModule.java index 447bd5cb6..51069c526 100644 --- a/bramble-core/src/main/java/org/briarproject/bramble/BrambleCoreModule.java +++ b/bramble-core/src/main/java/org/briarproject/bramble/BrambleCoreModule.java @@ -23,6 +23,7 @@ import org.briarproject.bramble.settings.SettingsModule; import org.briarproject.bramble.sync.SyncModule; import org.briarproject.bramble.sync.validation.ValidationModule; import org.briarproject.bramble.transport.TransportModule; +import org.briarproject.bramble.transport.agreement.TransportKeyAgreementModule; import org.briarproject.bramble.versioning.VersioningModule; import dagger.Module; @@ -49,6 +50,7 @@ import dagger.Module; RendezvousModule.class, SettingsModule.class, SyncModule.class, + TransportKeyAgreementModule.class, TransportModule.class, ValidationModule.class, VersioningModule.class diff --git a/bramble-core/src/main/java/org/briarproject/bramble/transport/agreement/MessageEncoder.java b/bramble-core/src/main/java/org/briarproject/bramble/transport/agreement/MessageEncoder.java new file mode 100644 index 000000000..058fc894d --- /dev/null +++ b/bramble-core/src/main/java/org/briarproject/bramble/transport/agreement/MessageEncoder.java @@ -0,0 +1,22 @@ +package org.briarproject.bramble.transport.agreement; + +import org.briarproject.bramble.api.crypto.PublicKey; +import org.briarproject.bramble.api.data.BdfDictionary; +import org.briarproject.bramble.api.nullsafety.NotNullByDefault; +import org.briarproject.bramble.api.plugin.TransportId; +import org.briarproject.bramble.api.sync.GroupId; +import org.briarproject.bramble.api.sync.Message; +import org.briarproject.bramble.api.sync.MessageId; + +@NotNullByDefault +interface MessageEncoder { + + Message encodeKeyMessage(GroupId contactGroupId, + TransportId transportId, PublicKey publicKey); + + Message encodeActivateMessage(GroupId contactGroupId, + TransportId transportId, MessageId previousMessageId); + + BdfDictionary encodeMessageMetadata(TransportId transportId, + MessageType type, boolean local); +} diff --git a/bramble-core/src/main/java/org/briarproject/bramble/transport/agreement/MessageEncoderImpl.java b/bramble-core/src/main/java/org/briarproject/bramble/transport/agreement/MessageEncoderImpl.java new file mode 100644 index 000000000..35d019a79 --- /dev/null +++ b/bramble-core/src/main/java/org/briarproject/bramble/transport/agreement/MessageEncoderImpl.java @@ -0,0 +1,77 @@ +package org.briarproject.bramble.transport.agreement; + +import org.briarproject.bramble.api.FormatException; +import org.briarproject.bramble.api.client.ClientHelper; +import org.briarproject.bramble.api.crypto.PublicKey; +import org.briarproject.bramble.api.data.BdfDictionary; +import org.briarproject.bramble.api.data.BdfEntry; +import org.briarproject.bramble.api.data.BdfList; +import org.briarproject.bramble.api.nullsafety.NotNullByDefault; +import org.briarproject.bramble.api.plugin.TransportId; +import org.briarproject.bramble.api.sync.GroupId; +import org.briarproject.bramble.api.sync.Message; +import org.briarproject.bramble.api.sync.MessageId; +import org.briarproject.bramble.api.system.Clock; + +import javax.annotation.concurrent.Immutable; +import javax.inject.Inject; + +import static org.briarproject.bramble.transport.agreement.MessageType.ACTIVATE; +import static org.briarproject.bramble.transport.agreement.MessageType.KEY; +import static org.briarproject.bramble.transport.agreement.TransportKeyAgreementConstants.MSG_KEY_IS_SESSION; +import static org.briarproject.bramble.transport.agreement.TransportKeyAgreementConstants.MSG_KEY_LOCAL; +import static org.briarproject.bramble.transport.agreement.TransportKeyAgreementConstants.MSG_KEY_MESSAGE_TYPE; +import static org.briarproject.bramble.transport.agreement.TransportKeyAgreementConstants.MSG_KEY_TRANSPORT_ID; + +@Immutable +@NotNullByDefault +class MessageEncoderImpl implements MessageEncoder { + + private final ClientHelper clientHelper; + private final Clock clock; + + @Inject + MessageEncoderImpl(ClientHelper clientHelper, Clock clock) { + this.clientHelper = clientHelper; + this.clock = clock; + } + + @Override + public Message encodeKeyMessage(GroupId contactGroupId, + TransportId transportId, PublicKey publicKey) { + BdfList body = BdfList.of( + KEY.getValue(), + transportId.getString(), + publicKey.getEncoded()); + return encodeMessage(contactGroupId, body); + } + + @Override + public Message encodeActivateMessage(GroupId contactGroupId, + TransportId transportId, MessageId previousMessageId) { + BdfList body = BdfList.of( + ACTIVATE.getValue(), + transportId.getString(), + previousMessageId); + return encodeMessage(contactGroupId, body); + } + + @Override + public BdfDictionary encodeMessageMetadata(TransportId transportId, + MessageType type, boolean local) { + return BdfDictionary.of( + new BdfEntry(MSG_KEY_IS_SESSION, false), + new BdfEntry(MSG_KEY_TRANSPORT_ID, transportId.getString()), + new BdfEntry(MSG_KEY_MESSAGE_TYPE, type.getValue()), + new BdfEntry(MSG_KEY_LOCAL, local)); + } + + private Message encodeMessage(GroupId contactGroupId, BdfList body) { + try { + return clientHelper.createMessage(contactGroupId, + clock.currentTimeMillis(), clientHelper.toByteArray(body)); + } catch (FormatException e) { + throw new AssertionError(); + } + } +} diff --git a/bramble-core/src/main/java/org/briarproject/bramble/transport/agreement/MessageType.java b/bramble-core/src/main/java/org/briarproject/bramble/transport/agreement/MessageType.java new file mode 100644 index 000000000..1e63464e3 --- /dev/null +++ b/bramble-core/src/main/java/org/briarproject/bramble/transport/agreement/MessageType.java @@ -0,0 +1,29 @@ +package org.briarproject.bramble.transport.agreement; + +import org.briarproject.bramble.api.FormatException; +import org.briarproject.bramble.api.nullsafety.NotNullByDefault; + +import javax.annotation.concurrent.Immutable; + +@Immutable +@NotNullByDefault +enum MessageType { + + KEY(0), + ACTIVATE(1); + + private final int value; + + MessageType(int value) { + this.value = value; + } + + int getValue() { + return value; + } + + static MessageType fromValue(int value) throws FormatException { + for (MessageType t : values()) if (t.value == value) return t; + throw new FormatException(); + } +} diff --git a/bramble-core/src/main/java/org/briarproject/bramble/transport/agreement/Session.java b/bramble-core/src/main/java/org/briarproject/bramble/transport/agreement/Session.java new file mode 100644 index 000000000..e8d03e5a6 --- /dev/null +++ b/bramble-core/src/main/java/org/briarproject/bramble/transport/agreement/Session.java @@ -0,0 +1,65 @@ +package org.briarproject.bramble.transport.agreement; + +import org.briarproject.bramble.api.crypto.KeyPair; +import org.briarproject.bramble.api.nullsafety.NotNullByDefault; +import org.briarproject.bramble.api.sync.MessageId; +import org.briarproject.bramble.api.transport.KeySetId; + +import javax.annotation.Nullable; +import javax.annotation.concurrent.Immutable; + +@Immutable +@NotNullByDefault +class Session { + + private final State state; + @Nullable + private final MessageId lastLocalMessageId; + @Nullable + private final KeyPair localKeyPair; + @Nullable + private final Long localTimestamp, remoteTimestamp; + @Nullable + private final KeySetId keySetId; + + Session(State state, @Nullable MessageId lastLocalMessageId, + @Nullable KeyPair localKeyPair, + @Nullable Long localTimestamp, @Nullable Long remoteTimestamp, + @Nullable KeySetId keySetId) { + this.state = state; + this.lastLocalMessageId = lastLocalMessageId; + this.localKeyPair = localKeyPair; + this.localTimestamp = localTimestamp; + this.remoteTimestamp = remoteTimestamp; + this.keySetId = keySetId; + } + + State getState() { + return state; + } + + @Nullable + MessageId getLastLocalMessageId() { + return lastLocalMessageId; + } + + @Nullable + KeyPair getLocalKeyPair() { + return localKeyPair; + } + + @Nullable + Long getLocalTimestamp() { + return localTimestamp; + } + + @Nullable + Long getRemoteTimestamp() { + return remoteTimestamp; + } + + @Nullable + KeySetId getKeySetId() { + return keySetId; + } +} diff --git a/bramble-core/src/main/java/org/briarproject/bramble/transport/agreement/SessionEncoder.java b/bramble-core/src/main/java/org/briarproject/bramble/transport/agreement/SessionEncoder.java new file mode 100644 index 000000000..949ed977b --- /dev/null +++ b/bramble-core/src/main/java/org/briarproject/bramble/transport/agreement/SessionEncoder.java @@ -0,0 +1,13 @@ +package org.briarproject.bramble.transport.agreement; + +import org.briarproject.bramble.api.data.BdfDictionary; +import org.briarproject.bramble.api.nullsafety.NotNullByDefault; +import org.briarproject.bramble.api.plugin.TransportId; + +@NotNullByDefault +interface SessionEncoder { + + BdfDictionary encodeSession(Session s, TransportId transportId); + + BdfDictionary getSessionQuery(TransportId transportId); +} diff --git a/bramble-core/src/main/java/org/briarproject/bramble/transport/agreement/SessionEncoderImpl.java b/bramble-core/src/main/java/org/briarproject/bramble/transport/agreement/SessionEncoderImpl.java new file mode 100644 index 000000000..ce68c42be --- /dev/null +++ b/bramble-core/src/main/java/org/briarproject/bramble/transport/agreement/SessionEncoderImpl.java @@ -0,0 +1,70 @@ +package org.briarproject.bramble.transport.agreement; + +import org.briarproject.bramble.api.crypto.KeyPair; +import org.briarproject.bramble.api.data.BdfDictionary; +import org.briarproject.bramble.api.data.BdfEntry; +import org.briarproject.bramble.api.nullsafety.NotNullByDefault; +import org.briarproject.bramble.api.plugin.TransportId; +import org.briarproject.bramble.api.transport.KeySetId; + +import javax.annotation.Nullable; +import javax.annotation.concurrent.Immutable; +import javax.inject.Inject; + +import static org.briarproject.bramble.api.data.BdfDictionary.NULL_VALUE; +import static org.briarproject.bramble.transport.agreement.TransportKeyAgreementConstants.MSG_KEY_IS_SESSION; +import static org.briarproject.bramble.transport.agreement.TransportKeyAgreementConstants.MSG_KEY_TRANSPORT_ID; +import static org.briarproject.bramble.transport.agreement.TransportKeyAgreementConstants.SESSION_KEY_KEY_SET_ID; +import static org.briarproject.bramble.transport.agreement.TransportKeyAgreementConstants.SESSION_KEY_LAST_LOCAL_MESSAGE_ID; +import static org.briarproject.bramble.transport.agreement.TransportKeyAgreementConstants.SESSION_KEY_LOCAL_PRIVATE_KEY; +import static org.briarproject.bramble.transport.agreement.TransportKeyAgreementConstants.SESSION_KEY_LOCAL_PUBLIC_KEY; +import static org.briarproject.bramble.transport.agreement.TransportKeyAgreementConstants.SESSION_KEY_LOCAL_TIMESTAMP; +import static org.briarproject.bramble.transport.agreement.TransportKeyAgreementConstants.SESSION_KEY_REMOTE_TIMESTAMP; +import static org.briarproject.bramble.transport.agreement.TransportKeyAgreementConstants.SESSION_KEY_STATE; + +@Immutable +@NotNullByDefault +class SessionEncoderImpl implements SessionEncoder { + + @Inject + SessionEncoderImpl() { + } + + @Override + public BdfDictionary encodeSession(Session s, TransportId transportId) { + BdfDictionary meta = new BdfDictionary(); + meta.put(MSG_KEY_IS_SESSION, true); + meta.put(MSG_KEY_TRANSPORT_ID, transportId.getString()); + meta.put(SESSION_KEY_STATE, s.getState().getValue()); + putNullable(meta, SESSION_KEY_LAST_LOCAL_MESSAGE_ID, + s.getLastLocalMessageId()); + KeyPair localKeyPair = s.getLocalKeyPair(); + if (localKeyPair == null) { + meta.put(SESSION_KEY_LOCAL_PUBLIC_KEY, NULL_VALUE); + meta.put(SESSION_KEY_LOCAL_PRIVATE_KEY, NULL_VALUE); + } else { + meta.put(SESSION_KEY_LOCAL_PUBLIC_KEY, + localKeyPair.getPublic().getEncoded()); + meta.put(SESSION_KEY_LOCAL_PRIVATE_KEY, + localKeyPair.getPrivate().getEncoded()); + } + putNullable(meta, SESSION_KEY_LOCAL_TIMESTAMP, s.getLocalTimestamp()); + putNullable(meta, SESSION_KEY_REMOTE_TIMESTAMP, s.getRemoteTimestamp()); + KeySetId keySetId = s.getKeySetId(); + if (keySetId == null) meta.put(SESSION_KEY_KEY_SET_ID, NULL_VALUE); + else meta.put(SESSION_KEY_KEY_SET_ID, keySetId.getInt()); + return meta; + } + + @Override + public BdfDictionary getSessionQuery(TransportId transportId) { + return BdfDictionary.of( + new BdfEntry(MSG_KEY_IS_SESSION, true), + new BdfEntry(MSG_KEY_TRANSPORT_ID, transportId.getString())); + } + + private void putNullable(BdfDictionary meta, String key, + @Nullable Object o) { + meta.put(key, o == null ? NULL_VALUE : o); + } +} diff --git a/bramble-core/src/main/java/org/briarproject/bramble/transport/agreement/SessionParser.java b/bramble-core/src/main/java/org/briarproject/bramble/transport/agreement/SessionParser.java new file mode 100644 index 000000000..7b9e2a3e8 --- /dev/null +++ b/bramble-core/src/main/java/org/briarproject/bramble/transport/agreement/SessionParser.java @@ -0,0 +1,11 @@ +package org.briarproject.bramble.transport.agreement; + +import org.briarproject.bramble.api.FormatException; +import org.briarproject.bramble.api.data.BdfDictionary; +import org.briarproject.bramble.api.nullsafety.NotNullByDefault; + +@NotNullByDefault +interface SessionParser { + + Session parseSession(BdfDictionary meta) throws FormatException; +} diff --git a/bramble-core/src/main/java/org/briarproject/bramble/transport/agreement/SessionParserImpl.java b/bramble-core/src/main/java/org/briarproject/bramble/transport/agreement/SessionParserImpl.java new file mode 100644 index 000000000..34736e9da --- /dev/null +++ b/bramble-core/src/main/java/org/briarproject/bramble/transport/agreement/SessionParserImpl.java @@ -0,0 +1,70 @@ +package org.briarproject.bramble.transport.agreement; + +import org.briarproject.bramble.api.FormatException; +import org.briarproject.bramble.api.crypto.KeyPair; +import org.briarproject.bramble.api.crypto.PrivateKey; +import org.briarproject.bramble.api.crypto.PublicKey; +import org.briarproject.bramble.api.data.BdfDictionary; +import org.briarproject.bramble.api.nullsafety.NotNullByDefault; +import org.briarproject.bramble.api.sync.MessageId; +import org.briarproject.bramble.api.transport.KeySetId; + +import javax.annotation.concurrent.Immutable; +import javax.inject.Inject; + +import static org.briarproject.bramble.transport.agreement.TransportKeyAgreementConstants.SESSION_KEY_KEY_SET_ID; +import static org.briarproject.bramble.transport.agreement.TransportKeyAgreementConstants.SESSION_KEY_LAST_LOCAL_MESSAGE_ID; +import static org.briarproject.bramble.transport.agreement.TransportKeyAgreementConstants.SESSION_KEY_LOCAL_PRIVATE_KEY; +import static org.briarproject.bramble.transport.agreement.TransportKeyAgreementConstants.SESSION_KEY_LOCAL_PUBLIC_KEY; +import static org.briarproject.bramble.transport.agreement.TransportKeyAgreementConstants.SESSION_KEY_LOCAL_TIMESTAMP; +import static org.briarproject.bramble.transport.agreement.TransportKeyAgreementConstants.SESSION_KEY_REMOTE_TIMESTAMP; +import static org.briarproject.bramble.transport.agreement.TransportKeyAgreementConstants.SESSION_KEY_STATE; + +@Immutable +@NotNullByDefault +class SessionParserImpl implements SessionParser { + + private final TransportKeyAgreementCrypto crypto; + + @Inject + SessionParserImpl(TransportKeyAgreementCrypto crypto) { + this.crypto = crypto; + } + + @Override + public Session parseSession(BdfDictionary meta) throws FormatException { + State state = + State.fromValue(meta.getLong(SESSION_KEY_STATE).intValue()); + + MessageId lastLocalMessageId = null; + byte[] lastLocalMessageIdBytes = + meta.getOptionalRaw(SESSION_KEY_LAST_LOCAL_MESSAGE_ID); + if (lastLocalMessageIdBytes != null) { + lastLocalMessageId = new MessageId(lastLocalMessageIdBytes); + } + + KeyPair localKeyPair = null; + byte[] localPublicKeyBytes = + meta.getOptionalRaw(SESSION_KEY_LOCAL_PUBLIC_KEY); + byte[] localPrivateKeyBytes = + meta.getOptionalRaw(SESSION_KEY_LOCAL_PRIVATE_KEY); + if (localPublicKeyBytes != null && localPrivateKeyBytes != null) { + PublicKey pub = crypto.parsePublicKey(localPublicKeyBytes); + PrivateKey priv = crypto.parsePrivateKey(localPrivateKeyBytes); + localKeyPair = new KeyPair(pub, priv); + } + + Long localTimestamp = meta.getOptionalLong(SESSION_KEY_LOCAL_TIMESTAMP); + Long remoteTimestamp = + meta.getOptionalLong(SESSION_KEY_REMOTE_TIMESTAMP); + + KeySetId keySetId = null; + Long keySetIdLong = meta.getOptionalLong(SESSION_KEY_KEY_SET_ID); + if (keySetIdLong != null) { + keySetId = new KeySetId(keySetIdLong.intValue()); + } + + return new Session(state, lastLocalMessageId, localKeyPair, + localTimestamp, remoteTimestamp, keySetId); + } +} diff --git a/bramble-core/src/main/java/org/briarproject/bramble/transport/agreement/State.java b/bramble-core/src/main/java/org/briarproject/bramble/transport/agreement/State.java new file mode 100644 index 000000000..9ab3f97da --- /dev/null +++ b/bramble-core/src/main/java/org/briarproject/bramble/transport/agreement/State.java @@ -0,0 +1,43 @@ +package org.briarproject.bramble.transport.agreement; + +import org.briarproject.bramble.api.FormatException; +import org.briarproject.bramble.api.nullsafety.NotNullByDefault; + +import javax.annotation.concurrent.Immutable; + +@Immutable +@NotNullByDefault +enum State { + + /** + * We've sent a key message and are awaiting the contact's key message. + */ + AWAIT_KEY(0), + + /** + * We've exchanged key messages, derived the transport keys and sent an + * activate message, and now we're awaiting the contact's activate message. + */ + AWAIT_ACTIVATE(1), + + /** + * We've exchanged key messages and activate messages, and have derived and + * activated the transport keys. This is the end state. + */ + ACTIVATED(2); + + private final int value; + + State(int value) { + this.value = value; + } + + int getValue() { + return value; + } + + static State fromValue(int value) throws FormatException { + for (State s : values()) if (s.value == value) return s; + throw new FormatException(); + } +} diff --git a/bramble-core/src/main/java/org/briarproject/bramble/transport/agreement/TransportKeyAgreementConstants.java b/bramble-core/src/main/java/org/briarproject/bramble/transport/agreement/TransportKeyAgreementConstants.java new file mode 100644 index 000000000..278e67303 --- /dev/null +++ b/bramble-core/src/main/java/org/briarproject/bramble/transport/agreement/TransportKeyAgreementConstants.java @@ -0,0 +1,28 @@ +package org.briarproject.bramble.transport.agreement; + +import org.briarproject.bramble.api.nullsafety.NotNullByDefault; + +@NotNullByDefault +interface TransportKeyAgreementConstants { + + String MSG_KEY_IS_SESSION = "isSession"; + String MSG_KEY_MESSAGE_TYPE = "messageType"; + String MSG_KEY_TRANSPORT_ID = "transportId"; + String MSG_KEY_PUBLIC_KEY = "publicKey"; + String MSG_KEY_LOCAL = "local"; + + String SESSION_KEY_STATE = "state"; + String SESSION_KEY_LAST_LOCAL_MESSAGE_ID = "lastLocalMessageId"; + String SESSION_KEY_LOCAL_PUBLIC_KEY = "localPublicKey"; + String SESSION_KEY_LOCAL_PRIVATE_KEY = "localPrivateKey"; + String SESSION_KEY_LOCAL_TIMESTAMP = "localTimestamp"; + String SESSION_KEY_REMOTE_TIMESTAMP = "remoteTimestamp"; + String SESSION_KEY_KEY_SET_ID = "keySetId"; + + /** + * Label for deriving the root key from key pairs. + */ + String ROOT_KEY_LABEL = + "org.briarproject.bramble.transport.agreement/ROOT_KEY"; + +} diff --git a/bramble-core/src/main/java/org/briarproject/bramble/transport/agreement/TransportKeyAgreementCrypto.java b/bramble-core/src/main/java/org/briarproject/bramble/transport/agreement/TransportKeyAgreementCrypto.java new file mode 100644 index 000000000..fc9b8a575 --- /dev/null +++ b/bramble-core/src/main/java/org/briarproject/bramble/transport/agreement/TransportKeyAgreementCrypto.java @@ -0,0 +1,23 @@ +package org.briarproject.bramble.transport.agreement; + +import org.briarproject.bramble.api.FormatException; +import org.briarproject.bramble.api.crypto.KeyPair; +import org.briarproject.bramble.api.crypto.PrivateKey; +import org.briarproject.bramble.api.crypto.PublicKey; +import org.briarproject.bramble.api.crypto.SecretKey; +import org.briarproject.bramble.api.nullsafety.NotNullByDefault; + +import java.security.GeneralSecurityException; + +@NotNullByDefault +interface TransportKeyAgreementCrypto { + + KeyPair generateKeyPair(); + + SecretKey deriveRootKey(KeyPair localKeyPair, PublicKey remotePublicKey, + long timestamp) throws GeneralSecurityException; + + PublicKey parsePublicKey(byte[] encoded) throws FormatException; + + PrivateKey parsePrivateKey(byte[] encoded) throws FormatException; +} diff --git a/bramble-core/src/main/java/org/briarproject/bramble/transport/agreement/TransportKeyAgreementCryptoImpl.java b/bramble-core/src/main/java/org/briarproject/bramble/transport/agreement/TransportKeyAgreementCryptoImpl.java new file mode 100644 index 000000000..5ae01dccc --- /dev/null +++ b/bramble-core/src/main/java/org/briarproject/bramble/transport/agreement/TransportKeyAgreementCryptoImpl.java @@ -0,0 +1,67 @@ +package org.briarproject.bramble.transport.agreement; + +import org.briarproject.bramble.api.FormatException; +import org.briarproject.bramble.api.crypto.CryptoComponent; +import org.briarproject.bramble.api.crypto.KeyPair; +import org.briarproject.bramble.api.crypto.PrivateKey; +import org.briarproject.bramble.api.crypto.PublicKey; +import org.briarproject.bramble.api.crypto.SecretKey; +import org.briarproject.bramble.api.nullsafety.NotNullByDefault; + +import java.security.GeneralSecurityException; + +import javax.annotation.concurrent.Immutable; +import javax.inject.Inject; + +import static org.briarproject.bramble.api.Bytes.compare; +import static org.briarproject.bramble.transport.agreement.TransportKeyAgreementConstants.ROOT_KEY_LABEL; + +@Immutable +@NotNullByDefault +class TransportKeyAgreementCryptoImpl implements TransportKeyAgreementCrypto { + + private final CryptoComponent crypto; + + @Inject + TransportKeyAgreementCryptoImpl(CryptoComponent crypto) { + this.crypto = crypto; + } + + @Override + public KeyPair generateKeyPair() { + return crypto.generateAgreementKeyPair(); + } + + @Override + public SecretKey deriveRootKey(KeyPair localKeyPair, + PublicKey remotePublicKey, long timestamp) + throws GeneralSecurityException { + byte[] theirPublic = remotePublicKey.getEncoded(); + byte[] ourPublic = localKeyPair.getPublic().getEncoded(); + boolean alice = compare(ourPublic, theirPublic) < 0; + byte[][] inputs = { + alice ? ourPublic : theirPublic, + alice ? theirPublic : ourPublic + }; + return crypto.deriveSharedSecret(ROOT_KEY_LABEL, remotePublicKey, + localKeyPair, inputs); + } + + @Override + public PublicKey parsePublicKey(byte[] encoded) throws FormatException { + try { + return crypto.getAgreementKeyParser().parsePublicKey(encoded); + } catch (GeneralSecurityException e) { + throw new FormatException(); + } + } + + @Override + public PrivateKey parsePrivateKey(byte[] encoded) throws FormatException { + try { + return crypto.getAgreementKeyParser().parsePrivateKey(encoded); + } catch (GeneralSecurityException e) { + throw new FormatException(); + } + } +} diff --git a/bramble-core/src/main/java/org/briarproject/bramble/transport/agreement/TransportKeyAgreementManagerImpl.java b/bramble-core/src/main/java/org/briarproject/bramble/transport/agreement/TransportKeyAgreementManagerImpl.java new file mode 100644 index 000000000..e937f98b7 --- /dev/null +++ b/bramble-core/src/main/java/org/briarproject/bramble/transport/agreement/TransportKeyAgreementManagerImpl.java @@ -0,0 +1,411 @@ +package org.briarproject.bramble.transport.agreement; + +import org.briarproject.bramble.api.FormatException; +import org.briarproject.bramble.api.client.BdfIncomingMessageHook; +import org.briarproject.bramble.api.client.ClientHelper; +import org.briarproject.bramble.api.client.ContactGroupFactory; +import org.briarproject.bramble.api.contact.Contact; +import org.briarproject.bramble.api.contact.ContactId; +import org.briarproject.bramble.api.contact.ContactManager.ContactHook; +import org.briarproject.bramble.api.crypto.KeyPair; +import org.briarproject.bramble.api.crypto.PublicKey; +import org.briarproject.bramble.api.crypto.SecretKey; +import org.briarproject.bramble.api.data.BdfDictionary; +import org.briarproject.bramble.api.data.BdfList; +import org.briarproject.bramble.api.data.MetadataParser; +import org.briarproject.bramble.api.db.DatabaseComponent; +import org.briarproject.bramble.api.db.DbException; +import org.briarproject.bramble.api.db.Metadata; +import org.briarproject.bramble.api.db.Transaction; +import org.briarproject.bramble.api.identity.Author; +import org.briarproject.bramble.api.identity.IdentityManager; +import org.briarproject.bramble.api.lifecycle.LifecycleManager.OpenDatabaseHook; +import org.briarproject.bramble.api.nullsafety.NotNullByDefault; +import org.briarproject.bramble.api.plugin.PluginConfig; +import org.briarproject.bramble.api.plugin.TransportId; +import org.briarproject.bramble.api.plugin.duplex.DuplexPluginFactory; +import org.briarproject.bramble.api.plugin.simplex.SimplexPluginFactory; +import org.briarproject.bramble.api.sync.Group; +import org.briarproject.bramble.api.sync.Group.Visibility; +import org.briarproject.bramble.api.sync.GroupId; +import org.briarproject.bramble.api.sync.Message; +import org.briarproject.bramble.api.sync.MessageId; +import org.briarproject.bramble.api.transport.KeyManager; +import org.briarproject.bramble.api.transport.KeySetId; +import org.briarproject.bramble.api.transport.agreement.TransportKeyAgreementManager; +import org.briarproject.bramble.api.versioning.ClientVersioningManager; +import org.briarproject.bramble.api.versioning.ClientVersioningManager.ClientVersioningHook; + +import java.security.GeneralSecurityException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.logging.Logger; + +import javax.annotation.Nullable; +import javax.annotation.concurrent.Immutable; +import javax.inject.Inject; + +import static java.lang.Math.min; +import static java.util.Collections.singletonMap; +import static java.util.logging.Level.INFO; +import static java.util.logging.Logger.getLogger; +import static org.briarproject.bramble.api.Bytes.compare; +import static org.briarproject.bramble.api.nullsafety.NullSafety.requireNonNull; +import static org.briarproject.bramble.api.sync.validation.IncomingMessageHook.DeliveryAction.ACCEPT_DO_NOT_SHARE; +import static org.briarproject.bramble.api.sync.validation.IncomingMessageHook.DeliveryAction.DEFER; +import static org.briarproject.bramble.api.sync.validation.IncomingMessageHook.DeliveryAction.REJECT; +import static org.briarproject.bramble.transport.agreement.MessageType.ACTIVATE; +import static org.briarproject.bramble.transport.agreement.MessageType.KEY; +import static org.briarproject.bramble.transport.agreement.State.ACTIVATED; +import static org.briarproject.bramble.transport.agreement.State.AWAIT_ACTIVATE; +import static org.briarproject.bramble.transport.agreement.State.AWAIT_KEY; +import static org.briarproject.bramble.transport.agreement.TransportKeyAgreementConstants.MSG_KEY_MESSAGE_TYPE; +import static org.briarproject.bramble.transport.agreement.TransportKeyAgreementConstants.MSG_KEY_PUBLIC_KEY; +import static org.briarproject.bramble.transport.agreement.TransportKeyAgreementConstants.MSG_KEY_TRANSPORT_ID; + +@Immutable +@NotNullByDefault +class TransportKeyAgreementManagerImpl extends BdfIncomingMessageHook + implements TransportKeyAgreementManager, OpenDatabaseHook, ContactHook, + ClientVersioningHook { + + private static final Logger LOG = + getLogger(TransportKeyAgreementManagerImpl.class.getName()); + + private final ContactGroupFactory contactGroupFactory; + private final ClientVersioningManager clientVersioningManager; + private final IdentityManager identityManager; + private final KeyManager keyManager; + private final MessageEncoder messageEncoder; + private final SessionEncoder sessionEncoder; + private final SessionParser sessionParser; + private final TransportKeyAgreementCrypto crypto; + + private final List transports; + private final Group localGroup; + + @Inject + TransportKeyAgreementManagerImpl( + DatabaseComponent db, + ClientHelper clientHelper, + MetadataParser metadataParser, + ContactGroupFactory contactGroupFactory, + ClientVersioningManager clientVersioningManager, + IdentityManager identityManager, + KeyManager keyManager, + MessageEncoder messageEncoder, + SessionEncoder sessionEncoder, + SessionParser sessionParser, + TransportKeyAgreementCrypto crypto, + PluginConfig config) { + super(db, clientHelper, metadataParser); + this.contactGroupFactory = contactGroupFactory; + this.clientVersioningManager = clientVersioningManager; + this.identityManager = identityManager; + this.keyManager = keyManager; + this.messageEncoder = messageEncoder; + this.sessionEncoder = sessionEncoder; + this.sessionParser = sessionParser; + this.crypto = crypto; + transports = new ArrayList<>(); + for (DuplexPluginFactory duplex : config.getDuplexFactories()) { + transports.add(duplex.getId()); + } + for (SimplexPluginFactory simplex : config.getSimplexFactories()) { + transports.add(simplex.getId()); + } + localGroup = contactGroupFactory.createLocalGroup(CLIENT_ID, + MAJOR_VERSION); + } + + @Override + public void onDatabaseOpened(Transaction txn) throws DbException { + Collection contacts = db.getContacts(txn); + if (!db.containsGroup(txn, localGroup.getId())) { + db.addGroup(txn, localGroup); + // Set things up for any pre-existing contacts + for (Contact c : contacts) addingContact(txn, c); + } + // Find any contacts and transports that need keys + Map> transportsWithKeys = + db.getTransportsWithKeys(txn); + for (Contact c : contacts) { + Collection withKeys = + transportsWithKeys.get(c.getId()); + for (TransportId t : transports) { + if (withKeys == null || !withKeys.contains(t)) { + // We need keys for this contact and transport + GroupId contactGroupId = getContactGroup(c).getId(); + SavedSession ss = loadSession(txn, contactGroupId, t); + if (ss == null) { + // Start a session by sending our key message + startSession(txn, contactGroupId, t); + } + } + } + } + } + + @Override + public void addingContact(Transaction txn, Contact c) throws DbException { + // Create a group to share with the contact + Group g = getContactGroup(c); + db.addGroup(txn, g); + // Attach the contact ID to the group + clientHelper.setContactId(txn, g.getId(), c.getId()); + // Apply the client's visibility to the contact group + Visibility client = clientVersioningManager.getClientVisibility(txn, + c.getId(), CLIENT_ID, MAJOR_VERSION); + db.setGroupVisibility(txn, c.getId(), g.getId(), client); + } + + @Override + public void removingContact(Transaction txn, Contact c) throws DbException { + db.removeGroup(txn, getContactGroup(c)); + } + + @Override + public void onClientVisibilityChanging(Transaction txn, Contact c, + Visibility v) throws DbException { + // Apply the client's visibility to the contact group + Group g = getContactGroup(c); + db.setGroupVisibility(txn, c.getId(), g.getId(), v); + } + + @Override + protected DeliveryAction incomingMessage(Transaction txn, Message m, + BdfList body, BdfDictionary meta) + throws DbException, FormatException { + MessageType type = MessageType.fromValue( + meta.getLong(MSG_KEY_MESSAGE_TYPE).intValue()); + TransportId t = new TransportId(meta.getString(MSG_KEY_TRANSPORT_ID)); + if (LOG.isLoggable(INFO)) { + LOG.info("Received " + type + " message for " + t); + } + if (!transports.contains(t)) { + // Defer handling the message until we support the transport + return DEFER; + } + SavedSession ss = loadSession(txn, m.getGroupId(), t); + if (type == KEY) return handleKeyMessage(txn, t, m, meta, ss); + else if (type == ACTIVATE) return handleActivateMessage(txn, t, ss); + else throw new AssertionError(); + } + + private DeliveryAction handleKeyMessage(Transaction txn, TransportId t, + Message m, BdfDictionary meta, @Nullable SavedSession ss) + throws DbException, FormatException { + ContactId c = clientHelper.getContactId(txn, m.getGroupId()); + boolean haveKeys = db.containsTransportKeys(txn, c, t); + if (ss == null) { + if (haveKeys) { + // We have keys but no session, so we must have derived keys + // when adding the contact. If the contact didn't support + // the transport when they added us, they wouldn't have + // derived keys at that time. If they later added support for + // the transport then they would have started a session, so a + // key message is valid in this case + return handleKeyMessageForNewSession(txn, c, t, m, meta); + } else { + // We don't have keys, so we should have created a session at + // startup + throw new IllegalStateException(); + } + } else if (ss.session.getState() == AWAIT_KEY) { + if (haveKeys) { + // We have keys, so we shouldn't be in the AWAIT_KEY state, + // even if the contact didn't derive keys when adding us and + // later started a session + throw new IllegalStateException(); + } else { + // This is the key message we're waiting for + return handleKeyMessageForExistingSession(txn, c, t, m, meta, + ss); + } + } else { + return REJECT; // Not valid in this state + } + } + + private DeliveryAction handleActivateMessage(Transaction txn, + TransportId t, @Nullable SavedSession ss) throws DbException { + if (ss != null && ss.session.getState() == AWAIT_ACTIVATE) { + // Activate the keys and finish the session + KeySetId keySetId = requireNonNull(ss.session.getKeySetId()); + keyManager.activateKeys(txn, singletonMap(t, keySetId)); + Session session = new Session(ACTIVATED, + ss.session.getLastLocalMessageId(), null, null, null, null); + saveSession(txn, t, ss.storageId, session); + return ACCEPT_DO_NOT_SHARE; + } else { + return REJECT; // Not valid in this state + } + } + + private DeliveryAction handleKeyMessageForNewSession(Transaction txn, + ContactId c, TransportId t, Message m, BdfDictionary meta) + throws DbException, FormatException { + KeyPair localKeyPair = crypto.generateKeyPair(); + PublicKey remotePublicKey = + crypto.parsePublicKey(meta.getRaw(MSG_KEY_PUBLIC_KEY)); + Message keyMessage = sendKeyMessage(txn, m.getGroupId(), t, + localKeyPair.getPublic()); + long minTimestamp = min(keyMessage.getTimestamp(), m.getTimestamp()); + SecretKey rootKey; + try { + rootKey = crypto.deriveRootKey(localKeyPair, remotePublicKey, + minTimestamp); + } catch (GeneralSecurityException e) { + return REJECT; // Invalid public key + } + boolean alice = isLocalPartyAlice(txn, db.getContact(txn, c)); + KeySetId keySetId = keyManager.addRotationKeys(txn, c, t, rootKey, + minTimestamp, alice, false); + Message activateMessage = + sendActivateMessage(txn, m.getGroupId(), t, keyMessage.getId()); + Session session = new Session(AWAIT_ACTIVATE, activateMessage.getId(), + null, null, null, keySetId); + saveNewSession(txn, m.getGroupId(), t, session); + return ACCEPT_DO_NOT_SHARE; + } + + private DeliveryAction handleKeyMessageForExistingSession(Transaction txn, + ContactId c, TransportId t, Message m, BdfDictionary meta, + SavedSession ss) throws DbException, FormatException { + KeyPair localKeyPair = requireNonNull(ss.session.getLocalKeyPair()); + PublicKey remotePublicKey = + crypto.parsePublicKey(meta.getRaw(MSG_KEY_PUBLIC_KEY)); + long localTimestamp = requireNonNull(ss.session.getLocalTimestamp()); + long minTimestamp = min(localTimestamp, m.getTimestamp()); + SecretKey rootKey; + try { + rootKey = crypto.deriveRootKey(localKeyPair, remotePublicKey, + minTimestamp); + } catch (GeneralSecurityException e) { + return REJECT; // Invalid public key + } + boolean alice = isLocalPartyAlice(txn, db.getContact(txn, c)); + KeySetId keySetId = keyManager.addRotationKeys(txn, c, t, rootKey, + minTimestamp, alice, false); + MessageId previousMessageId = + requireNonNull(ss.session.getLastLocalMessageId()); + Message activateMessage = + sendActivateMessage(txn, m.getGroupId(), t, previousMessageId); + Session session = new Session(AWAIT_ACTIVATE, activateMessage.getId(), + null, null, null, keySetId); + saveSession(txn, t, ss.storageId, session); + return ACCEPT_DO_NOT_SHARE; + } + + private void startSession(Transaction txn, GroupId contactGroupId, + TransportId t) throws DbException { + KeyPair localKeyPair = crypto.generateKeyPair(); + Message keyMessage = sendKeyMessage(txn, contactGroupId, t, + localKeyPair.getPublic()); + Session session = new Session(AWAIT_KEY, keyMessage.getId(), + localKeyPair, keyMessage.getTimestamp(), null, null); + saveNewSession(txn, contactGroupId, t, session); + } + + @Nullable + private SavedSession loadSession(Transaction txn, GroupId contactGroupId, + TransportId t) throws DbException { + try { + BdfDictionary query = sessionEncoder.getSessionQuery(t); + Collection ids = + clientHelper.getMessageIds(txn, contactGroupId, query); + if (ids.size() > 1) throw new DbException(); + if (ids.isEmpty()) { + if (LOG.isLoggable(INFO)) LOG.info("No session for " + t); + return null; + } + MessageId storageId = ids.iterator().next(); + BdfDictionary bdfSession = + clientHelper.getMessageMetadataAsDictionary(txn, storageId); + Session session = sessionParser.parseSession(bdfSession); + if (LOG.isLoggable(INFO)) { + LOG.info("Loaded session in state " + session.getState() + + " for " + t); + } + return new SavedSession(session, storageId); + } catch (FormatException e) { + throw new DbException(e); + } + } + + private void saveNewSession(Transaction txn, GroupId contactGroupId, + TransportId t, Session session) throws DbException { + Message m = + clientHelper.createMessageForStoringMetadata(contactGroupId); + db.addLocalMessage(txn, m, new Metadata(), false, false); + MessageId storageId = m.getId(); + saveSession(txn, t, storageId, session); + } + + private void saveSession(Transaction txn, TransportId t, + MessageId storageId, Session session) throws DbException { + if (LOG.isLoggable(INFO)) { + LOG.info("Saving session in state " + session.getState() + + " for " + t); + } + BdfDictionary meta = sessionEncoder.encodeSession(session, t); + try { + clientHelper.mergeMessageMetadata(txn, storageId, meta); + } catch (FormatException e) { + throw new AssertionError(); + } + } + + private Message sendKeyMessage(Transaction txn, GroupId contactGroupId, + TransportId t, PublicKey publicKey) throws DbException { + Message m = messageEncoder.encodeKeyMessage(contactGroupId, t, + publicKey); + sendMessage(txn, t, m, KEY); + return m; + } + + private Message sendActivateMessage(Transaction txn, + GroupId contactGroupId, TransportId t, MessageId previousMessageId) + throws DbException { + Message m = messageEncoder.encodeActivateMessage(contactGroupId, t, + previousMessageId); + sendMessage(txn, t, m, ACTIVATE); + return m; + } + + private void sendMessage(Transaction txn, TransportId t, Message m, + MessageType type) throws DbException { + BdfDictionary meta = + messageEncoder.encodeMessageMetadata(t, type, true); + try { + clientHelper.addLocalMessage(txn, m, meta, true, false); + } catch (FormatException e) { + throw new AssertionError(); + } + } + + private Group getContactGroup(Contact c) { + return contactGroupFactory.createContactGroup(CLIENT_ID, + MAJOR_VERSION, c); + } + + private boolean isLocalPartyAlice(Transaction txn, Contact c) + throws DbException { + Author local = identityManager.getLocalAuthor(txn); + Author remote = c.getAuthor(); + return compare(local.getId().getBytes(), remote.getId().getBytes()) < 0; + } + + private static class SavedSession { + + private final Session session; + private final MessageId storageId; + + private SavedSession(Session session, MessageId storageId) { + this.session = session; + this.storageId = storageId; + } + } +} diff --git a/bramble-core/src/main/java/org/briarproject/bramble/transport/agreement/TransportKeyAgreementModule.java b/bramble-core/src/main/java/org/briarproject/bramble/transport/agreement/TransportKeyAgreementModule.java new file mode 100644 index 000000000..8bff228fa --- /dev/null +++ b/bramble-core/src/main/java/org/briarproject/bramble/transport/agreement/TransportKeyAgreementModule.java @@ -0,0 +1,83 @@ +package org.briarproject.bramble.transport.agreement; + +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.lifecycle.LifecycleManager; +import org.briarproject.bramble.api.sync.validation.ValidationManager; +import org.briarproject.bramble.api.system.Clock; +import org.briarproject.bramble.api.transport.agreement.TransportKeyAgreementManager; +import org.briarproject.bramble.api.versioning.ClientVersioningManager; + +import javax.inject.Inject; +import javax.inject.Singleton; + +import dagger.Module; +import dagger.Provides; + +import static org.briarproject.bramble.api.transport.agreement.TransportKeyAgreementManager.CLIENT_ID; +import static org.briarproject.bramble.api.transport.agreement.TransportKeyAgreementManager.MAJOR_VERSION; +import static org.briarproject.bramble.api.transport.agreement.TransportKeyAgreementManager.MINOR_VERSION; + +@Module +public class TransportKeyAgreementModule { + + public static class EagerSingletons { + @Inject + TransportKeyAgreementManager transportKeyAgreementManager; + @Inject + TransportKeyAgreementValidator transportKeyAgreementValidator; + } + + @Provides + @Singleton + TransportKeyAgreementManager provideTransportKeyAgreementManager( + LifecycleManager lifecycleManager, + ValidationManager validationManager, + ContactManager contactManager, + ClientVersioningManager clientVersioningManager, + TransportKeyAgreementManagerImpl transportKeyAgreementManager) { + lifecycleManager.registerOpenDatabaseHook(transportKeyAgreementManager); + validationManager.registerIncomingMessageHook(CLIENT_ID, + MAJOR_VERSION, transportKeyAgreementManager); + contactManager.registerContactHook(transportKeyAgreementManager); + clientVersioningManager.registerClient(CLIENT_ID, MAJOR_VERSION, + MINOR_VERSION, transportKeyAgreementManager); + return transportKeyAgreementManager; + } + + @Provides + @Singleton + TransportKeyAgreementValidator provideTransportKeyAgreementValidator( + ClientHelper clientHelper, MetadataEncoder metadataEncoder, + Clock clock, MessageEncoder messageEncoder, + ValidationManager validationManager) { + TransportKeyAgreementValidator validator = + new TransportKeyAgreementValidator(clientHelper, + metadataEncoder, clock, messageEncoder); + validationManager.registerMessageValidator(CLIENT_ID, MAJOR_VERSION, + validator); + return validator; + } + + @Provides + MessageEncoder provideMessageEncoder(MessageEncoderImpl messageEncoder) { + return messageEncoder; + } + + @Provides + SessionEncoder provideSessionEncoder(SessionEncoderImpl sessionEncoder) { + return sessionEncoder; + } + + @Provides + SessionParser provideSessionParser(SessionParserImpl sessionParser) { + return sessionParser; + } + + @Provides + TransportKeyAgreementCrypto provideTransportKeyAgreementCrypto( + TransportKeyAgreementCryptoImpl transportKeyAgreementCrypto) { + return transportKeyAgreementCrypto; + } +} diff --git a/bramble-core/src/main/java/org/briarproject/bramble/transport/agreement/TransportKeyAgreementValidator.java b/bramble-core/src/main/java/org/briarproject/bramble/transport/agreement/TransportKeyAgreementValidator.java new file mode 100644 index 000000000..1f4053b2f --- /dev/null +++ b/bramble-core/src/main/java/org/briarproject/bramble/transport/agreement/TransportKeyAgreementValidator.java @@ -0,0 +1,77 @@ +package org.briarproject.bramble.transport.agreement; + +import org.briarproject.bramble.api.FormatException; +import org.briarproject.bramble.api.client.BdfMessageContext; +import org.briarproject.bramble.api.client.BdfMessageValidator; +import org.briarproject.bramble.api.client.ClientHelper; +import org.briarproject.bramble.api.data.BdfDictionary; +import org.briarproject.bramble.api.data.BdfList; +import org.briarproject.bramble.api.data.MetadataEncoder; +import org.briarproject.bramble.api.nullsafety.NotNullByDefault; +import org.briarproject.bramble.api.plugin.TransportId; +import org.briarproject.bramble.api.sync.Group; +import org.briarproject.bramble.api.sync.Message; +import org.briarproject.bramble.api.sync.MessageId; +import org.briarproject.bramble.api.system.Clock; + +import javax.annotation.concurrent.Immutable; + +import static java.util.Collections.singletonList; +import static org.briarproject.bramble.api.crypto.CryptoConstants.MAX_AGREEMENT_PUBLIC_KEY_BYTES; +import static org.briarproject.bramble.api.plugin.TransportId.MAX_TRANSPORT_ID_LENGTH; +import static org.briarproject.bramble.transport.agreement.MessageType.ACTIVATE; +import static org.briarproject.bramble.transport.agreement.MessageType.KEY; +import static org.briarproject.bramble.transport.agreement.TransportKeyAgreementConstants.MSG_KEY_PUBLIC_KEY; +import static org.briarproject.bramble.util.ValidationUtils.checkLength; +import static org.briarproject.bramble.util.ValidationUtils.checkSize; + +@Immutable +@NotNullByDefault +class TransportKeyAgreementValidator extends BdfMessageValidator { + + private final MessageEncoder messageEncoder; + + TransportKeyAgreementValidator(ClientHelper clientHelper, + MetadataEncoder metadataEncoder, Clock clock, + MessageEncoder messageEncoder) { + super(clientHelper, metadataEncoder, clock); + this.messageEncoder = messageEncoder; + } + + @Override + protected BdfMessageContext validateMessage(Message m, Group g, + BdfList body) throws FormatException { + MessageType type = MessageType.fromValue(body.getLong(0).intValue()); + if (type == KEY) return validateKeyMessage(body); + else if (type == ACTIVATE) return validateActivateMessage(body); + else throw new AssertionError(); + } + + private BdfMessageContext validateKeyMessage(BdfList body) + throws FormatException { + // Message type, transport ID, public key + checkSize(body, 3); + String transportId = body.getString(1); + checkLength(transportId, 1, MAX_TRANSPORT_ID_LENGTH); + byte[] publicKey = body.getRaw(2); + checkLength(publicKey, 1, MAX_AGREEMENT_PUBLIC_KEY_BYTES); + BdfDictionary meta = messageEncoder.encodeMessageMetadata( + new TransportId(transportId), KEY, false); + meta.put(MSG_KEY_PUBLIC_KEY, publicKey); + return new BdfMessageContext(meta); + } + + private BdfMessageContext validateActivateMessage(BdfList body) + throws FormatException { + // Message type, transport ID, previous message ID + checkSize(body, 3); + String transportId = body.getString(1); + checkLength(transportId, 1, MAX_TRANSPORT_ID_LENGTH); + byte[] previousMessageId = body.getRaw(2); + checkLength(previousMessageId, MessageId.LENGTH); + BdfDictionary meta = messageEncoder.encodeMessageMetadata( + new TransportId(transportId), ACTIVATE, false); + MessageId dependency = new MessageId(previousMessageId); + return new BdfMessageContext(meta, singletonList(dependency)); + } +} diff --git a/bramble-core/src/test/java/org/briarproject/bramble/transport/agreement/TransportKeyAgreementManagerImplTest.java b/bramble-core/src/test/java/org/briarproject/bramble/transport/agreement/TransportKeyAgreementManagerImplTest.java new file mode 100644 index 000000000..01c8dabf7 --- /dev/null +++ b/bramble-core/src/test/java/org/briarproject/bramble/transport/agreement/TransportKeyAgreementManagerImplTest.java @@ -0,0 +1,589 @@ +package org.briarproject.bramble.transport.agreement; + +import org.briarproject.bramble.api.client.ClientHelper; +import org.briarproject.bramble.api.client.ContactGroupFactory; +import org.briarproject.bramble.api.contact.Contact; +import org.briarproject.bramble.api.crypto.KeyPair; +import org.briarproject.bramble.api.crypto.PublicKey; +import org.briarproject.bramble.api.crypto.SecretKey; +import org.briarproject.bramble.api.data.BdfDictionary; +import org.briarproject.bramble.api.data.BdfEntry; +import org.briarproject.bramble.api.data.BdfList; +import org.briarproject.bramble.api.data.MetadataParser; +import org.briarproject.bramble.api.db.DatabaseComponent; +import org.briarproject.bramble.api.db.Metadata; +import org.briarproject.bramble.api.db.Transaction; +import org.briarproject.bramble.api.identity.IdentityManager; +import org.briarproject.bramble.api.identity.LocalAuthor; +import org.briarproject.bramble.api.plugin.PluginConfig; +import org.briarproject.bramble.api.plugin.TransportId; +import org.briarproject.bramble.api.plugin.duplex.DuplexPluginFactory; +import org.briarproject.bramble.api.plugin.simplex.SimplexPluginFactory; +import org.briarproject.bramble.api.sync.Group; +import org.briarproject.bramble.api.sync.Message; +import org.briarproject.bramble.api.transport.KeyManager; +import org.briarproject.bramble.api.transport.KeySetId; +import org.briarproject.bramble.api.versioning.ClientVersioningManager; +import org.briarproject.bramble.test.BrambleMockTestCase; +import org.briarproject.bramble.test.CaptureArgumentAction; +import org.jmock.Expectations; +import org.junit.Before; +import org.junit.Test; + +import java.util.concurrent.atomic.AtomicReference; + +import static java.lang.Math.min; +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.Bytes.compare; +import static org.briarproject.bramble.api.sync.Group.Visibility.VISIBLE; +import static org.briarproject.bramble.api.sync.validation.IncomingMessageHook.DeliveryAction.ACCEPT_DO_NOT_SHARE; +import static org.briarproject.bramble.api.sync.validation.IncomingMessageHook.DeliveryAction.DEFER; +import static org.briarproject.bramble.api.sync.validation.IncomingMessageHook.DeliveryAction.REJECT; +import static org.briarproject.bramble.api.transport.agreement.TransportKeyAgreementManager.CLIENT_ID; +import static org.briarproject.bramble.api.transport.agreement.TransportKeyAgreementManager.MAJOR_VERSION; +import static org.briarproject.bramble.test.TestUtils.getAgreementPrivateKey; +import static org.briarproject.bramble.test.TestUtils.getAgreementPublicKey; +import static org.briarproject.bramble.test.TestUtils.getContact; +import static org.briarproject.bramble.test.TestUtils.getGroup; +import static org.briarproject.bramble.test.TestUtils.getLocalAuthor; +import static org.briarproject.bramble.test.TestUtils.getMessage; +import static org.briarproject.bramble.test.TestUtils.getSecretKey; +import static org.briarproject.bramble.test.TestUtils.getTransportId; +import static org.briarproject.bramble.transport.agreement.MessageType.ACTIVATE; +import static org.briarproject.bramble.transport.agreement.MessageType.KEY; +import static org.briarproject.bramble.transport.agreement.State.ACTIVATED; +import static org.briarproject.bramble.transport.agreement.State.AWAIT_ACTIVATE; +import static org.briarproject.bramble.transport.agreement.State.AWAIT_KEY; +import static org.briarproject.bramble.transport.agreement.TransportKeyAgreementConstants.MSG_KEY_MESSAGE_TYPE; +import static org.briarproject.bramble.transport.agreement.TransportKeyAgreementConstants.MSG_KEY_PUBLIC_KEY; +import static org.briarproject.bramble.transport.agreement.TransportKeyAgreementConstants.MSG_KEY_TRANSPORT_ID; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; + +public class TransportKeyAgreementManagerImplTest extends BrambleMockTestCase { + + private final DatabaseComponent db = context.mock(DatabaseComponent.class); + private final ClientHelper clientHelper = context.mock(ClientHelper.class); + private final MetadataParser metadataParser = + context.mock(MetadataParser.class); + private final ContactGroupFactory contactGroupFactory = + context.mock(ContactGroupFactory.class); + private final ClientVersioningManager clientVersioningManager = + context.mock(ClientVersioningManager.class); + private final IdentityManager identityManager = + context.mock(IdentityManager.class); + private final KeyManager keyManager = context.mock(KeyManager.class); + private final MessageEncoder messageEncoder = + context.mock(MessageEncoder.class); + private final SessionEncoder sessionEncoder = + context.mock(SessionEncoder.class); + private final SessionParser sessionParser = + context.mock(SessionParser.class); + private final TransportKeyAgreementCrypto crypto = + context.mock(TransportKeyAgreementCrypto.class); + private final PluginConfig pluginConfig = context.mock(PluginConfig.class); + private final SimplexPluginFactory simplexFactory = + context.mock(SimplexPluginFactory.class); + private final DuplexPluginFactory duplexFactory = + context.mock(DuplexPluginFactory.class); + + private final TransportId simplexTransportId = getTransportId(); + private final TransportId duplexTransportId = getTransportId(); + private final Group localGroup = getGroup(CLIENT_ID, MAJOR_VERSION); + private final Group contactGroup = getGroup(CLIENT_ID, MAJOR_VERSION); + private final Contact contact = getContact(); + private final LocalAuthor localAuthor = getLocalAuthor(); + private final boolean alice = compare(localAuthor.getId().getBytes(), + contact.getAuthor().getId().getBytes()) < 0; + private final KeyPair localKeyPair = + new KeyPair(getAgreementPublicKey(), getAgreementPrivateKey()); + private final PublicKey remotePublicKey = getAgreementPublicKey(); + private final SecretKey rootKey = getSecretKey(); + private final KeySetId keySetId = new KeySetId(123); + + private final Message storageMessage = getMessage(contactGroup.getId()); + private final Message localKeyMessage = getMessage(contactGroup.getId()); + private final Message localActivateMessage = + getMessage(contactGroup.getId()); + private final Message remoteKeyMessage = getMessage(contactGroup.getId()); + private final Message remoteActivateMessage = + getMessage(contactGroup.getId()); + private final long localTimestamp = localKeyMessage.getTimestamp(); + private final long remoteTimestamp = remoteKeyMessage.getTimestamp(); + + // These query and metadata dictionaries are handled by the manager without + // inspecting their contents, so we can use empty dictionaries for testing + private final BdfDictionary sessionQuery = new BdfDictionary(); + private final BdfDictionary sessionMeta = new BdfDictionary(); + private final BdfDictionary localKeyMeta = new BdfDictionary(); + private final BdfDictionary localActivateMeta = new BdfDictionary(); + + // The manager doesn't use the incoming message body, so it can be empty + private final BdfList remoteMessageBody = new BdfList(); + + private final BdfDictionary remoteKeyMeta = BdfDictionary.of( + new BdfEntry(MSG_KEY_MESSAGE_TYPE, KEY.getValue()), + new BdfEntry(MSG_KEY_TRANSPORT_ID, + simplexTransportId.getString()), + new BdfEntry(MSG_KEY_PUBLIC_KEY, remotePublicKey.getEncoded())); + + private final BdfDictionary remoteActivateMeta = BdfDictionary.of( + new BdfEntry(MSG_KEY_MESSAGE_TYPE, ACTIVATE.getValue()), + new BdfEntry(MSG_KEY_TRANSPORT_ID, + simplexTransportId.getString())); + + private TransportKeyAgreementManagerImpl manager; + + @Before + public void setUp() { + context.checking(new Expectations() {{ + oneOf(pluginConfig).getSimplexFactories(); + will(returnValue(singletonList(simplexFactory))); + oneOf(simplexFactory).getId(); + will(returnValue(simplexTransportId)); + oneOf(pluginConfig).getDuplexFactories(); + will(returnValue(singletonList(duplexFactory))); + oneOf(duplexFactory).getId(); + will(returnValue(duplexTransportId)); + oneOf(contactGroupFactory) + .createLocalGroup(CLIENT_ID, MAJOR_VERSION); + will(returnValue(localGroup)); + }}); + + manager = new TransportKeyAgreementManagerImpl(db, clientHelper, + metadataParser, contactGroupFactory, clientVersioningManager, + identityManager, keyManager, messageEncoder, sessionEncoder, + sessionParser, crypto, pluginConfig); + } + + @Test + public void testCreatesContactGroupAtStartupIfLocalGroupDoesNotExist() + throws Exception { + Transaction txn = new Transaction(null, false); + + context.checking(new Expectations() {{ + oneOf(db).getContacts(txn); + will(returnValue(singletonList(contact))); + // The local group doesn't exist so we need to create contact groups + oneOf(db).containsGroup(txn, localGroup.getId()); + will(returnValue(false)); + oneOf(db).addGroup(txn, localGroup); + // Create the contact group and set it up + oneOf(contactGroupFactory).createContactGroup(CLIENT_ID, + MAJOR_VERSION, contact); + will(returnValue(contactGroup)); + oneOf(db).addGroup(txn, contactGroup); + oneOf(clientHelper) + .setContactId(txn, contactGroup.getId(), contact.getId()); + oneOf(clientVersioningManager).getClientVisibility(txn, + contact.getId(), CLIENT_ID, MAJOR_VERSION); + will(returnValue(VISIBLE)); + oneOf(db).setGroupVisibility(txn, contact.getId(), + contactGroup.getId(), VISIBLE); + // We already have keys for both transports + oneOf(db).getTransportsWithKeys(txn); + will(returnValue(singletonMap(contact.getId(), + asList(simplexTransportId, duplexTransportId)))); + }}); + + manager.onDatabaseOpened(txn); + } + + @Test + public void testDoesNotCreateContactGroupAtStartupIfLocalGroupExists() + throws Exception { + Transaction txn = new Transaction(null, false); + + context.checking(new Expectations() {{ + oneOf(db).getContacts(txn); + will(returnValue(singletonList(contact))); + // The local group exists so we don't need to create contact groups + oneOf(db).containsGroup(txn, localGroup.getId()); + will(returnValue(true)); + // We already have keys for both transports + oneOf(db).getTransportsWithKeys(txn); + will(returnValue(singletonMap(contact.getId(), + asList(simplexTransportId, duplexTransportId)))); + }}); + + manager.onDatabaseOpened(txn); + } + + @Test + public void testStartsSessionAtStartup() throws Exception { + Transaction txn = new Transaction(null, false); + AtomicReference savedSession = new AtomicReference<>(); + + context.checking(new Expectations() {{ + oneOf(db).getContacts(txn); + will(returnValue(singletonList(contact))); + // The local group exists so we don't need to create contact groups + oneOf(db).containsGroup(txn, localGroup.getId()); + will(returnValue(true)); + // We need keys for the simplex transport + oneOf(db).getTransportsWithKeys(txn); + will(returnValue(singletonMap(contact.getId(), + singletonList(duplexTransportId)))); + // Check whether a session exists - it doesn't + oneOf(contactGroupFactory) + .createContactGroup(CLIENT_ID, MAJOR_VERSION, contact); + will(returnValue(contactGroup)); + oneOf(sessionEncoder).getSessionQuery(simplexTransportId); + will(returnValue(sessionQuery)); + oneOf(clientHelper) + .getMessageIds(txn, contactGroup.getId(), sessionQuery); + will(returnValue(emptyList())); + // Send a key message + oneOf(crypto).generateKeyPair(); + will(returnValue(localKeyPair)); + oneOf(messageEncoder).encodeKeyMessage(contactGroup.getId(), + simplexTransportId, localKeyPair.getPublic()); + will(returnValue(localKeyMessage)); + oneOf(messageEncoder) + .encodeMessageMetadata(simplexTransportId, KEY, true); + will(returnValue(localKeyMeta)); + oneOf(clientHelper).addLocalMessage(txn, localKeyMessage, + localKeyMeta, true, false); + // Save the session + oneOf(clientHelper) + .createMessageForStoringMetadata(contactGroup.getId()); + will(returnValue(storageMessage)); + oneOf(db).addLocalMessage(txn, storageMessage, new Metadata(), + false, false); + oneOf(sessionEncoder).encodeSession(with(any(Session.class)), + with(simplexTransportId)); + will(doAll( + new CaptureArgumentAction<>(savedSession, Session.class, 0), + returnValue(sessionMeta))); + oneOf(clientHelper).mergeMessageMetadata(txn, + storageMessage.getId(), sessionMeta); + }}); + + manager.onDatabaseOpened(txn); + + assertEquals(AWAIT_KEY, savedSession.get().getState()); + assertEquals(localKeyMessage.getId(), + savedSession.get().getLastLocalMessageId()); + assertEquals(localKeyPair, savedSession.get().getLocalKeyPair()); + assertEquals(Long.valueOf(localTimestamp), + savedSession.get().getLocalTimestamp()); + assertNull(savedSession.get().getRemoteTimestamp()); + assertNull(savedSession.get().getKeySetId()); + } + + @Test + public void testDefersMessageIfTransportIsNotSupported() throws Exception { + Transaction txn = new Transaction(null, false); + TransportId unknownTransportId = getTransportId(); + BdfDictionary meta = new BdfDictionary(remoteKeyMeta); + meta.put(MSG_KEY_TRANSPORT_ID, unknownTransportId.getString()); + + assertEquals(DEFER, manager.incomingMessage(txn, remoteKeyMessage, + remoteMessageBody, meta)); + } + + @Test + public void testAcceptsKeyMessageInAwaitKeyState() throws Exception { + Transaction txn = new Transaction(null, false); + Session loadedSession = new Session(AWAIT_KEY, + localKeyMessage.getId(), localKeyPair, localTimestamp, + null, null); + AtomicReference savedSession = new AtomicReference<>(); + + context.checking(new Expectations() {{ + // Check whether a session exists - it does + oneOf(sessionEncoder).getSessionQuery(simplexTransportId); + will(returnValue(sessionQuery)); + oneOf(clientHelper) + .getMessageIds(txn, contactGroup.getId(), sessionQuery); + will(returnValue(singletonList(storageMessage.getId()))); + // Load the session + oneOf(clientHelper).getMessageMetadataAsDictionary(txn, + storageMessage.getId()); + will(returnValue(sessionMeta)); + oneOf(sessionParser).parseSession(sessionMeta); + will(returnValue(loadedSession)); + // Load the contact ID + oneOf(clientHelper).getContactId(txn, contactGroup.getId()); + will(returnValue(contact.getId())); + // Check whether we already have keys - we don't + oneOf(db).containsTransportKeys(txn, contact.getId(), + simplexTransportId); + will(returnValue(false)); + // Parse the remote public key + oneOf(crypto).parsePublicKey(remotePublicKey.getEncoded()); + will(returnValue(remotePublicKey)); + // Derive and store the transport keys + oneOf(crypto).deriveRootKey(localKeyPair, remotePublicKey, + min(localTimestamp, remoteTimestamp)); + will(returnValue(rootKey)); + oneOf(db).getContact(txn, contact.getId()); + will(returnValue(contact)); + oneOf(identityManager).getLocalAuthor(txn); + will(returnValue(localAuthor)); + oneOf(keyManager).addRotationKeys(txn, contact.getId(), + simplexTransportId, rootKey, + min(localTimestamp, remoteTimestamp), alice, false); + will(returnValue(keySetId)); + // Send an activate message + oneOf(messageEncoder).encodeActivateMessage(contactGroup.getId(), + simplexTransportId, localKeyMessage.getId()); + will(returnValue(localActivateMessage)); + oneOf(messageEncoder) + .encodeMessageMetadata(simplexTransportId, ACTIVATE, true); + will(returnValue(localActivateMeta)); + oneOf(clientHelper).addLocalMessage(txn, localActivateMessage, + localActivateMeta, true, false); + // Save the session + oneOf(sessionEncoder).encodeSession(with(any(Session.class)), + with(simplexTransportId)); + will(doAll( + new CaptureArgumentAction<>(savedSession, Session.class, 0), + returnValue(sessionMeta))); + oneOf(clientHelper).mergeMessageMetadata(txn, + storageMessage.getId(), sessionMeta); + }}); + + assertEquals(ACCEPT_DO_NOT_SHARE, manager.incomingMessage(txn, + remoteKeyMessage, remoteMessageBody, remoteKeyMeta)); + + assertEquals(AWAIT_ACTIVATE, savedSession.get().getState()); + assertEquals(localActivateMessage.getId(), + savedSession.get().getLastLocalMessageId()); + assertNull(savedSession.get().getLocalKeyPair()); + assertNull(savedSession.get().getLocalTimestamp()); + assertNull(savedSession.get().getRemoteTimestamp()); + assertEquals(keySetId, savedSession.get().getKeySetId()); + } + + @Test + public void testAcceptsKeyMessageIfWeHaveTransportKeysButNoSession() + throws Exception { + Transaction txn = new Transaction(null, false); + AtomicReference savedSession = new AtomicReference<>(); + + context.checking(new Expectations() {{ + // Check whether a session exists - it doesn't + oneOf(sessionEncoder).getSessionQuery(simplexTransportId); + will(returnValue(sessionQuery)); + oneOf(clientHelper) + .getMessageIds(txn, contactGroup.getId(), sessionQuery); + will(returnValue(emptyList())); + // Load the contact ID + oneOf(clientHelper).getContactId(txn, contactGroup.getId()); + will(returnValue(contact.getId())); + // Check whether we already have keys - we do + oneOf(db).containsTransportKeys(txn, contact.getId(), + simplexTransportId); + will(returnValue(true)); + // Generate the local key pair + oneOf(crypto).generateKeyPair(); + will(returnValue(localKeyPair)); + // Parse the remote public key + oneOf(crypto).parsePublicKey(remotePublicKey.getEncoded()); + will(returnValue(remotePublicKey)); + // Send a key message + oneOf(messageEncoder).encodeKeyMessage(contactGroup.getId(), + simplexTransportId, localKeyPair.getPublic()); + will(returnValue(localKeyMessage)); + oneOf(messageEncoder) + .encodeMessageMetadata(simplexTransportId, KEY, true); + will(returnValue(localKeyMeta)); + oneOf(clientHelper).addLocalMessage(txn, localKeyMessage, + localKeyMeta, true, false); + // Derive and store the transport keys + oneOf(crypto).deriveRootKey(localKeyPair, remotePublicKey, + min(localTimestamp, remoteTimestamp)); + will(returnValue(rootKey)); + oneOf(db).getContact(txn, contact.getId()); + will(returnValue(contact)); + oneOf(identityManager).getLocalAuthor(txn); + will(returnValue(localAuthor)); + oneOf(keyManager).addRotationKeys(txn, contact.getId(), + simplexTransportId, rootKey, + min(localTimestamp, remoteTimestamp), alice, false); + will(returnValue(keySetId)); + // Send an activate message + oneOf(messageEncoder).encodeActivateMessage(contactGroup.getId(), + simplexTransportId, localKeyMessage.getId()); + will(returnValue(localActivateMessage)); + oneOf(messageEncoder) + .encodeMessageMetadata(simplexTransportId, ACTIVATE, true); + will(returnValue(localActivateMeta)); + oneOf(clientHelper).addLocalMessage(txn, localActivateMessage, + localActivateMeta, true, false); + // Save the session + oneOf(clientHelper) + .createMessageForStoringMetadata(contactGroup.getId()); + will(returnValue(storageMessage)); + oneOf(db).addLocalMessage(txn, storageMessage, new Metadata(), + false, false); + oneOf(sessionEncoder).encodeSession(with(any(Session.class)), + with(simplexTransportId)); + will(doAll( + new CaptureArgumentAction<>(savedSession, Session.class, 0), + returnValue(sessionMeta))); + oneOf(clientHelper).mergeMessageMetadata(txn, + storageMessage.getId(), sessionMeta); + }}); + + assertEquals(ACCEPT_DO_NOT_SHARE, manager.incomingMessage(txn, + remoteKeyMessage, remoteMessageBody, remoteKeyMeta)); + + assertEquals(AWAIT_ACTIVATE, savedSession.get().getState()); + assertEquals(localActivateMessage.getId(), + savedSession.get().getLastLocalMessageId()); + assertNull(savedSession.get().getLocalKeyPair()); + assertNull(savedSession.get().getLocalTimestamp()); + assertNull(savedSession.get().getRemoteTimestamp()); + assertEquals(keySetId, savedSession.get().getKeySetId()); + } + + @Test + public void testRejectsKeyMessageInAwaitActivateState() throws Exception { + Session loadedSession = new Session(AWAIT_ACTIVATE, + localActivateMessage.getId(), null, null, null, keySetId); + testRejectsKeyMessageWithExistingSession(loadedSession); + } + + @Test + public void testRejectsKeyMessageInActivatedState() throws Exception { + Session loadedSession = new Session(ACTIVATED, + localActivateMessage.getId(), null, null, null, null); + testRejectsKeyMessageWithExistingSession(loadedSession); + } + + private void testRejectsKeyMessageWithExistingSession(Session loadedSession) + throws Exception { + Transaction txn = new Transaction(null, false); + + context.checking(new Expectations() {{ + // Check whether a session exists - it does + oneOf(sessionEncoder).getSessionQuery(simplexTransportId); + will(returnValue(sessionQuery)); + oneOf(clientHelper) + .getMessageIds(txn, contactGroup.getId(), sessionQuery); + will(returnValue(singletonList(storageMessage.getId()))); + // Load the session + oneOf(clientHelper).getMessageMetadataAsDictionary(txn, + storageMessage.getId()); + will(returnValue(sessionMeta)); + oneOf(sessionParser).parseSession(sessionMeta); + will(returnValue(loadedSession)); + // Load the contact ID + oneOf(clientHelper).getContactId(txn, contactGroup.getId()); + will(returnValue(contact.getId())); + // Check whether we already have keys - we don't + oneOf(db).containsTransportKeys(txn, contact.getId(), + simplexTransportId); + will(returnValue(false)); + }}); + + assertEquals(REJECT, manager.incomingMessage(txn, + remoteKeyMessage, remoteMessageBody, remoteKeyMeta)); + } + + @Test + public void testAcceptsActivateMessageInAwaitActivateState() + throws Exception { + Transaction txn = new Transaction(null, false); + Session loadedSession = new Session(AWAIT_ACTIVATE, + localActivateMessage.getId(), null, null, null, keySetId); + AtomicReference savedSession = new AtomicReference<>(); + + context.checking(new Expectations() {{ + // Check whether a session exists - it does + oneOf(sessionEncoder).getSessionQuery(simplexTransportId); + will(returnValue(sessionQuery)); + oneOf(clientHelper) + .getMessageIds(txn, contactGroup.getId(), sessionQuery); + will(returnValue(singletonList(storageMessage.getId()))); + // Load the session + oneOf(clientHelper).getMessageMetadataAsDictionary(txn, + storageMessage.getId()); + will(returnValue(sessionMeta)); + oneOf(sessionParser).parseSession(sessionMeta); + will(returnValue(loadedSession)); + // Activate the transport keys + oneOf(keyManager).activateKeys(txn, + singletonMap(simplexTransportId, keySetId)); + // Save the session + oneOf(sessionEncoder).encodeSession(with(any(Session.class)), + with(simplexTransportId)); + will(doAll( + new CaptureArgumentAction<>(savedSession, Session.class, 0), + returnValue(sessionMeta))); + oneOf(clientHelper).mergeMessageMetadata(txn, + storageMessage.getId(), sessionMeta); + }}); + + assertEquals(ACCEPT_DO_NOT_SHARE, manager.incomingMessage(txn, + remoteActivateMessage, remoteMessageBody, remoteActivateMeta)); + + assertEquals(ACTIVATED, savedSession.get().getState()); + assertEquals(localActivateMessage.getId(), + savedSession.get().getLastLocalMessageId()); + assertNull(savedSession.get().getLocalKeyPair()); + assertNull(savedSession.get().getLocalTimestamp()); + assertNull(savedSession.get().getRemoteTimestamp()); + assertNull(savedSession.get().getKeySetId()); + } + + @Test + public void testRejectsActivateMessageWithNoSession() throws Exception { + Transaction txn = new Transaction(null, false); + + context.checking(new Expectations() {{ + // Check whether a session exists - it doesn't + oneOf(sessionEncoder).getSessionQuery(simplexTransportId); + will(returnValue(sessionQuery)); + oneOf(clientHelper) + .getMessageIds(txn, contactGroup.getId(), sessionQuery); + will(returnValue(emptyList())); + }}); + + assertEquals(REJECT, manager.incomingMessage(txn, + remoteActivateMessage, remoteMessageBody, remoteActivateMeta)); + } + + @Test + public void testRejectsActivateMessageInAwaitKeyState() throws Exception { + Session loadedSession = new Session(AWAIT_KEY, + localActivateMessage.getId(), localKeyPair, localTimestamp, + null, null); + testRejectsActivateMessageWithExistingSession(loadedSession); + } + + @Test + public void testRejectsActivateMessageInActivatedState() throws Exception { + Session loadedSession = new Session(ACTIVATED, + localActivateMessage.getId(), null, null, null, null); + testRejectsActivateMessageWithExistingSession(loadedSession); + } + + private void testRejectsActivateMessageWithExistingSession( + Session loadedSession) throws Exception { + Transaction txn = new Transaction(null, false); + + context.checking(new Expectations() {{ + // Check whether a session exists - it does + oneOf(sessionEncoder).getSessionQuery(simplexTransportId); + will(returnValue(sessionQuery)); + oneOf(clientHelper) + .getMessageIds(txn, contactGroup.getId(), sessionQuery); + will(returnValue(singletonList(storageMessage.getId()))); + // Load the session + oneOf(clientHelper).getMessageMetadataAsDictionary(txn, + storageMessage.getId()); + will(returnValue(sessionMeta)); + oneOf(sessionParser).parseSession(sessionMeta); + will(returnValue(loadedSession)); + }}); + + assertEquals(REJECT, manager.incomingMessage(txn, + remoteActivateMessage, remoteMessageBody, remoteActivateMeta)); + } +} From c703d906363f4067c46155cc92f826ec50c887fd Mon Sep 17 00:00:00 2001 From: akwizgran Date: Tue, 1 Jun 2021 14:47:52 +0100 Subject: [PATCH 26/77] Remove unused remote timestamp from session. --- .../bramble/transport/agreement/Session.java | 11 ++--------- .../agreement/SessionEncoderImpl.java | 2 -- .../transport/agreement/SessionParserImpl.java | 5 +---- .../TransportKeyAgreementConstants.java | 1 - .../TransportKeyAgreementManagerImpl.java | 8 ++++---- .../TransportKeyAgreementManagerImplTest.java | 18 ++++++------------ 6 files changed, 13 insertions(+), 32 deletions(-) diff --git a/bramble-core/src/main/java/org/briarproject/bramble/transport/agreement/Session.java b/bramble-core/src/main/java/org/briarproject/bramble/transport/agreement/Session.java index e8d03e5a6..f431bdfd1 100644 --- a/bramble-core/src/main/java/org/briarproject/bramble/transport/agreement/Session.java +++ b/bramble-core/src/main/java/org/briarproject/bramble/transport/agreement/Session.java @@ -18,19 +18,17 @@ class Session { @Nullable private final KeyPair localKeyPair; @Nullable - private final Long localTimestamp, remoteTimestamp; + private final Long localTimestamp; @Nullable private final KeySetId keySetId; Session(State state, @Nullable MessageId lastLocalMessageId, - @Nullable KeyPair localKeyPair, - @Nullable Long localTimestamp, @Nullable Long remoteTimestamp, + @Nullable KeyPair localKeyPair, @Nullable Long localTimestamp, @Nullable KeySetId keySetId) { this.state = state; this.lastLocalMessageId = lastLocalMessageId; this.localKeyPair = localKeyPair; this.localTimestamp = localTimestamp; - this.remoteTimestamp = remoteTimestamp; this.keySetId = keySetId; } @@ -53,11 +51,6 @@ class Session { return localTimestamp; } - @Nullable - Long getRemoteTimestamp() { - return remoteTimestamp; - } - @Nullable KeySetId getKeySetId() { return keySetId; diff --git a/bramble-core/src/main/java/org/briarproject/bramble/transport/agreement/SessionEncoderImpl.java b/bramble-core/src/main/java/org/briarproject/bramble/transport/agreement/SessionEncoderImpl.java index ce68c42be..8b79f45fa 100644 --- a/bramble-core/src/main/java/org/briarproject/bramble/transport/agreement/SessionEncoderImpl.java +++ b/bramble-core/src/main/java/org/briarproject/bramble/transport/agreement/SessionEncoderImpl.java @@ -19,7 +19,6 @@ import static org.briarproject.bramble.transport.agreement.TransportKeyAgreement import static org.briarproject.bramble.transport.agreement.TransportKeyAgreementConstants.SESSION_KEY_LOCAL_PRIVATE_KEY; import static org.briarproject.bramble.transport.agreement.TransportKeyAgreementConstants.SESSION_KEY_LOCAL_PUBLIC_KEY; import static org.briarproject.bramble.transport.agreement.TransportKeyAgreementConstants.SESSION_KEY_LOCAL_TIMESTAMP; -import static org.briarproject.bramble.transport.agreement.TransportKeyAgreementConstants.SESSION_KEY_REMOTE_TIMESTAMP; import static org.briarproject.bramble.transport.agreement.TransportKeyAgreementConstants.SESSION_KEY_STATE; @Immutable @@ -49,7 +48,6 @@ class SessionEncoderImpl implements SessionEncoder { localKeyPair.getPrivate().getEncoded()); } putNullable(meta, SESSION_KEY_LOCAL_TIMESTAMP, s.getLocalTimestamp()); - putNullable(meta, SESSION_KEY_REMOTE_TIMESTAMP, s.getRemoteTimestamp()); KeySetId keySetId = s.getKeySetId(); if (keySetId == null) meta.put(SESSION_KEY_KEY_SET_ID, NULL_VALUE); else meta.put(SESSION_KEY_KEY_SET_ID, keySetId.getInt()); diff --git a/bramble-core/src/main/java/org/briarproject/bramble/transport/agreement/SessionParserImpl.java b/bramble-core/src/main/java/org/briarproject/bramble/transport/agreement/SessionParserImpl.java index 34736e9da..14ba99fb8 100644 --- a/bramble-core/src/main/java/org/briarproject/bramble/transport/agreement/SessionParserImpl.java +++ b/bramble-core/src/main/java/org/briarproject/bramble/transport/agreement/SessionParserImpl.java @@ -17,7 +17,6 @@ import static org.briarproject.bramble.transport.agreement.TransportKeyAgreement import static org.briarproject.bramble.transport.agreement.TransportKeyAgreementConstants.SESSION_KEY_LOCAL_PRIVATE_KEY; import static org.briarproject.bramble.transport.agreement.TransportKeyAgreementConstants.SESSION_KEY_LOCAL_PUBLIC_KEY; import static org.briarproject.bramble.transport.agreement.TransportKeyAgreementConstants.SESSION_KEY_LOCAL_TIMESTAMP; -import static org.briarproject.bramble.transport.agreement.TransportKeyAgreementConstants.SESSION_KEY_REMOTE_TIMESTAMP; import static org.briarproject.bramble.transport.agreement.TransportKeyAgreementConstants.SESSION_KEY_STATE; @Immutable @@ -55,8 +54,6 @@ class SessionParserImpl implements SessionParser { } Long localTimestamp = meta.getOptionalLong(SESSION_KEY_LOCAL_TIMESTAMP); - Long remoteTimestamp = - meta.getOptionalLong(SESSION_KEY_REMOTE_TIMESTAMP); KeySetId keySetId = null; Long keySetIdLong = meta.getOptionalLong(SESSION_KEY_KEY_SET_ID); @@ -65,6 +62,6 @@ class SessionParserImpl implements SessionParser { } return new Session(state, lastLocalMessageId, localKeyPair, - localTimestamp, remoteTimestamp, keySetId); + localTimestamp, keySetId); } } diff --git a/bramble-core/src/main/java/org/briarproject/bramble/transport/agreement/TransportKeyAgreementConstants.java b/bramble-core/src/main/java/org/briarproject/bramble/transport/agreement/TransportKeyAgreementConstants.java index 278e67303..ae2fd4474 100644 --- a/bramble-core/src/main/java/org/briarproject/bramble/transport/agreement/TransportKeyAgreementConstants.java +++ b/bramble-core/src/main/java/org/briarproject/bramble/transport/agreement/TransportKeyAgreementConstants.java @@ -16,7 +16,6 @@ interface TransportKeyAgreementConstants { String SESSION_KEY_LOCAL_PUBLIC_KEY = "localPublicKey"; String SESSION_KEY_LOCAL_PRIVATE_KEY = "localPrivateKey"; String SESSION_KEY_LOCAL_TIMESTAMP = "localTimestamp"; - String SESSION_KEY_REMOTE_TIMESTAMP = "remoteTimestamp"; String SESSION_KEY_KEY_SET_ID = "keySetId"; /** diff --git a/bramble-core/src/main/java/org/briarproject/bramble/transport/agreement/TransportKeyAgreementManagerImpl.java b/bramble-core/src/main/java/org/briarproject/bramble/transport/agreement/TransportKeyAgreementManagerImpl.java index e937f98b7..7bd479396 100644 --- a/bramble-core/src/main/java/org/briarproject/bramble/transport/agreement/TransportKeyAgreementManagerImpl.java +++ b/bramble-core/src/main/java/org/briarproject/bramble/transport/agreement/TransportKeyAgreementManagerImpl.java @@ -236,7 +236,7 @@ class TransportKeyAgreementManagerImpl extends BdfIncomingMessageHook KeySetId keySetId = requireNonNull(ss.session.getKeySetId()); keyManager.activateKeys(txn, singletonMap(t, keySetId)); Session session = new Session(ACTIVATED, - ss.session.getLastLocalMessageId(), null, null, null, null); + ss.session.getLastLocalMessageId(), null, null, null); saveSession(txn, t, ss.storageId, session); return ACCEPT_DO_NOT_SHARE; } else { @@ -266,7 +266,7 @@ class TransportKeyAgreementManagerImpl extends BdfIncomingMessageHook Message activateMessage = sendActivateMessage(txn, m.getGroupId(), t, keyMessage.getId()); Session session = new Session(AWAIT_ACTIVATE, activateMessage.getId(), - null, null, null, keySetId); + null, null, keySetId); saveNewSession(txn, m.getGroupId(), t, session); return ACCEPT_DO_NOT_SHARE; } @@ -294,7 +294,7 @@ class TransportKeyAgreementManagerImpl extends BdfIncomingMessageHook Message activateMessage = sendActivateMessage(txn, m.getGroupId(), t, previousMessageId); Session session = new Session(AWAIT_ACTIVATE, activateMessage.getId(), - null, null, null, keySetId); + null, null, keySetId); saveSession(txn, t, ss.storageId, session); return ACCEPT_DO_NOT_SHARE; } @@ -305,7 +305,7 @@ class TransportKeyAgreementManagerImpl extends BdfIncomingMessageHook Message keyMessage = sendKeyMessage(txn, contactGroupId, t, localKeyPair.getPublic()); Session session = new Session(AWAIT_KEY, keyMessage.getId(), - localKeyPair, keyMessage.getTimestamp(), null, null); + localKeyPair, keyMessage.getTimestamp(), null); saveNewSession(txn, contactGroupId, t, session); } diff --git a/bramble-core/src/test/java/org/briarproject/bramble/transport/agreement/TransportKeyAgreementManagerImplTest.java b/bramble-core/src/test/java/org/briarproject/bramble/transport/agreement/TransportKeyAgreementManagerImplTest.java index 01c8dabf7..f1563875b 100644 --- a/bramble-core/src/test/java/org/briarproject/bramble/transport/agreement/TransportKeyAgreementManagerImplTest.java +++ b/bramble-core/src/test/java/org/briarproject/bramble/transport/agreement/TransportKeyAgreementManagerImplTest.java @@ -270,7 +270,6 @@ public class TransportKeyAgreementManagerImplTest extends BrambleMockTestCase { assertEquals(localKeyPair, savedSession.get().getLocalKeyPair()); assertEquals(Long.valueOf(localTimestamp), savedSession.get().getLocalTimestamp()); - assertNull(savedSession.get().getRemoteTimestamp()); assertNull(savedSession.get().getKeySetId()); } @@ -289,8 +288,7 @@ public class TransportKeyAgreementManagerImplTest extends BrambleMockTestCase { public void testAcceptsKeyMessageInAwaitKeyState() throws Exception { Transaction txn = new Transaction(null, false); Session loadedSession = new Session(AWAIT_KEY, - localKeyMessage.getId(), localKeyPair, localTimestamp, - null, null); + localKeyMessage.getId(), localKeyPair, localTimestamp, null); AtomicReference savedSession = new AtomicReference<>(); context.checking(new Expectations() {{ @@ -355,7 +353,6 @@ public class TransportKeyAgreementManagerImplTest extends BrambleMockTestCase { savedSession.get().getLastLocalMessageId()); assertNull(savedSession.get().getLocalKeyPair()); assertNull(savedSession.get().getLocalTimestamp()); - assertNull(savedSession.get().getRemoteTimestamp()); assertEquals(keySetId, savedSession.get().getKeySetId()); } @@ -438,21 +435,20 @@ public class TransportKeyAgreementManagerImplTest extends BrambleMockTestCase { savedSession.get().getLastLocalMessageId()); assertNull(savedSession.get().getLocalKeyPair()); assertNull(savedSession.get().getLocalTimestamp()); - assertNull(savedSession.get().getRemoteTimestamp()); assertEquals(keySetId, savedSession.get().getKeySetId()); } @Test public void testRejectsKeyMessageInAwaitActivateState() throws Exception { Session loadedSession = new Session(AWAIT_ACTIVATE, - localActivateMessage.getId(), null, null, null, keySetId); + localActivateMessage.getId(), null, null, keySetId); testRejectsKeyMessageWithExistingSession(loadedSession); } @Test public void testRejectsKeyMessageInActivatedState() throws Exception { Session loadedSession = new Session(ACTIVATED, - localActivateMessage.getId(), null, null, null, null); + localActivateMessage.getId(), null, null, null); testRejectsKeyMessageWithExistingSession(loadedSession); } @@ -491,7 +487,7 @@ public class TransportKeyAgreementManagerImplTest extends BrambleMockTestCase { throws Exception { Transaction txn = new Transaction(null, false); Session loadedSession = new Session(AWAIT_ACTIVATE, - localActivateMessage.getId(), null, null, null, keySetId); + localActivateMessage.getId(), null, null, keySetId); AtomicReference savedSession = new AtomicReference<>(); context.checking(new Expectations() {{ @@ -528,7 +524,6 @@ public class TransportKeyAgreementManagerImplTest extends BrambleMockTestCase { savedSession.get().getLastLocalMessageId()); assertNull(savedSession.get().getLocalKeyPair()); assertNull(savedSession.get().getLocalTimestamp()); - assertNull(savedSession.get().getRemoteTimestamp()); assertNull(savedSession.get().getKeySetId()); } @@ -552,15 +547,14 @@ public class TransportKeyAgreementManagerImplTest extends BrambleMockTestCase { @Test public void testRejectsActivateMessageInAwaitKeyState() throws Exception { Session loadedSession = new Session(AWAIT_KEY, - localActivateMessage.getId(), localKeyPair, localTimestamp, - null, null); + localKeyMessage.getId(), localKeyPair, localTimestamp, null); testRejectsActivateMessageWithExistingSession(loadedSession); } @Test public void testRejectsActivateMessageInActivatedState() throws Exception { Session loadedSession = new Session(ACTIVATED, - localActivateMessage.getId(), null, null, null, null); + localActivateMessage.getId(), null, null, null); testRejectsActivateMessageWithExistingSession(loadedSession); } From 3ee516599d271f433907321f54b1014bf3f10c12 Mon Sep 17 00:00:00 2001 From: Daniel Lublin Date: Mon, 7 Jun 2021 13:17:50 +0200 Subject: [PATCH 27/77] Add initial RemovableDriveViewModel --- .../conversation/RemovableDriveViewModel.java | 102 ++++++++++++++++++ .../android/viewmodel/ViewModelModule.java | 7 ++ 2 files changed, 109 insertions(+) create mode 100644 briar-android/src/main/java/org/briarproject/briar/android/conversation/RemovableDriveViewModel.java diff --git a/briar-android/src/main/java/org/briarproject/briar/android/conversation/RemovableDriveViewModel.java b/briar-android/src/main/java/org/briarproject/briar/android/conversation/RemovableDriveViewModel.java new file mode 100644 index 000000000..e1e3edb20 --- /dev/null +++ b/briar-android/src/main/java/org/briarproject/briar/android/conversation/RemovableDriveViewModel.java @@ -0,0 +1,102 @@ +package org.briarproject.briar.android.conversation; + +import android.app.Application; +import android.net.Uri; + +import org.briarproject.bramble.api.Consumer; +import org.briarproject.bramble.api.contact.ContactId; +import org.briarproject.bramble.api.nullsafety.NotNullByDefault; +import org.briarproject.bramble.api.plugin.file.RemovableDriveManager; +import org.briarproject.bramble.api.plugin.file.RemovableDriveTask; +import org.briarproject.bramble.api.plugin.file.RemovableDriveTask.State; +import org.briarproject.bramble.api.properties.TransportProperties; + +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.Locale; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.logging.Logger; + +import javax.annotation.Nullable; +import javax.inject.Inject; + +import androidx.lifecycle.AndroidViewModel; +import androidx.lifecycle.LiveData; +import androidx.lifecycle.MutableLiveData; + +import static java.util.logging.Logger.getLogger; +import static org.briarproject.bramble.api.plugin.file.RemovableDriveConstants.PROP_URI; + +@NotNullByDefault +public class RemovableDriveViewModel extends AndroidViewModel { + + private static final Logger LOG = + getLogger(RemovableDriveViewModel.class.getName()); + + private final RemovableDriveManager manager; + + private final ConcurrentHashMap, RemovableDriveTask> + observers = new ConcurrentHashMap<>(); + + @Inject + RemovableDriveViewModel(Application app, + RemovableDriveManager removableDriveManager) { + super(app); + + this.manager = removableDriveManager; + } + + String getFileName() { + SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd_HHmmss_SSS", + Locale.getDefault()); + return sdf.format(new Date()); + } + + + LiveData write(ContactId contactId, Uri uri) { + TransportProperties p = new TransportProperties(); + p.put(PROP_URI, uri.toString()); + return observe(manager.startWriterTask(contactId, p)); + } + + LiveData read(ContactId contactId, Uri uri) { + TransportProperties p = new TransportProperties(); + p.put(PROP_URI, uri.toString()); + return observe(manager.startReaderTask(contactId, p)); + } + + @Nullable + LiveData ongoingWrite(ContactId contactId) { + RemovableDriveTask task = manager.getCurrentWriterTask(contactId); + if (task == null) { + return null; + } + return observe(task); + } + + @Nullable + LiveData ongoingRead(ContactId contactId) { + RemovableDriveTask task = manager.getCurrentReaderTask(contactId); + if (task == null) { + return null; + } + return observe(task); + } + + private LiveData observe(RemovableDriveTask task) { + MutableLiveData state = new MutableLiveData<>(); + Consumer observer = state::postValue; + task.addObserver(observer); + observers.put(observer, task); + return state; + } + + @Override + protected void onCleared() { + for (Map.Entry, RemovableDriveTask> entry + : observers.entrySet()) { + entry.getValue().removeObserver(entry.getKey()); + } + } +} diff --git a/briar-android/src/main/java/org/briarproject/briar/android/viewmodel/ViewModelModule.java b/briar-android/src/main/java/org/briarproject/briar/android/viewmodel/ViewModelModule.java index ce7899915..e75045eb4 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/viewmodel/ViewModelModule.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/viewmodel/ViewModelModule.java @@ -4,6 +4,7 @@ import org.briarproject.briar.android.contact.add.remote.AddContactViewModel; import org.briarproject.briar.android.contact.add.remote.PendingContactListViewModel; import org.briarproject.briar.android.conversation.ConversationViewModel; import org.briarproject.briar.android.conversation.ImageViewModel; +import org.briarproject.briar.android.conversation.RemovableDriveViewModel; import javax.inject.Singleton; @@ -40,6 +41,12 @@ public abstract class ViewModelModule { abstract ViewModel bindPendingRequestsViewModel( PendingContactListViewModel pendingContactListViewModel); + @Binds + @IntoMap + @ViewModelKey(RemovableDriveViewModel.class) + abstract ViewModel bindRemovableDriveViewModel( + RemovableDriveViewModel removableDriveViewModel); + @Binds @Singleton abstract ViewModelProvider.Factory bindViewModelFactory( From 3f5e131250ca121a211c41de7587913d09133762 Mon Sep 17 00:00:00 2001 From: Daniel Lublin Date: Tue, 8 Jun 2021 12:18:33 +0200 Subject: [PATCH 28/77] Use US locale for now --- .../briar/android/conversation/RemovableDriveViewModel.java | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/briar-android/src/main/java/org/briarproject/briar/android/conversation/RemovableDriveViewModel.java b/briar-android/src/main/java/org/briarproject/briar/android/conversation/RemovableDriveViewModel.java index e1e3edb20..e9aff76b7 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/conversation/RemovableDriveViewModel.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/conversation/RemovableDriveViewModel.java @@ -13,7 +13,6 @@ import org.briarproject.bramble.api.properties.TransportProperties; import java.text.SimpleDateFormat; import java.util.Date; -import java.util.Locale; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.logging.Logger; @@ -25,6 +24,7 @@ import androidx.lifecycle.AndroidViewModel; import androidx.lifecycle.LiveData; import androidx.lifecycle.MutableLiveData; +import static java.util.Locale.US; import static java.util.logging.Logger.getLogger; import static org.briarproject.bramble.api.plugin.file.RemovableDriveConstants.PROP_URI; @@ -48,8 +48,7 @@ public class RemovableDriveViewModel extends AndroidViewModel { } String getFileName() { - SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd_HHmmss_SSS", - Locale.getDefault()); + SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd_HHmmss_SSS", US); return sdf.format(new Date()); } From fd810f5c165e05a1d1105ccec6b4467c608563e3 Mon Sep 17 00:00:00 2001 From: Daniel Lublin Date: Tue, 8 Jun 2021 12:24:45 +0200 Subject: [PATCH 29/77] Move to new removabledrive package --- .../briarproject/briar/android/AppModule.java | 2 ++ .../removabledrive/RemovableDriveModule.java | 18 ++++++++++++++++++ .../RemovableDriveViewModel.java | 2 +- .../android/viewmodel/ViewModelModule.java | 8 +------- 4 files changed, 22 insertions(+), 8 deletions(-) create mode 100644 briar-android/src/main/java/org/briarproject/briar/android/removabledrive/RemovableDriveModule.java rename briar-android/src/main/java/org/briarproject/briar/android/{conversation => removabledrive}/RemovableDriveViewModel.java (98%) diff --git a/briar-android/src/main/java/org/briarproject/briar/android/AppModule.java b/briar-android/src/main/java/org/briarproject/briar/android/AppModule.java index 3b942cdb0..ec7e13f8a 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/AppModule.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/AppModule.java @@ -25,6 +25,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.RemovableDriveModule; import org.briarproject.bramble.plugin.tcp.AndroidLanTcpPluginFactory; import org.briarproject.bramble.plugin.tor.AndroidTorPluginFactory; import org.briarproject.bramble.util.AndroidUtils; @@ -92,6 +93,7 @@ import static org.briarproject.briar.android.TestingConstants.IS_DEBUG_BUILD; GroupListModule.class, GroupConversationModule.class, SharingModule.class, + RemovableDriveModule.class }) public class AppModule { diff --git a/briar-android/src/main/java/org/briarproject/briar/android/removabledrive/RemovableDriveModule.java b/briar-android/src/main/java/org/briarproject/briar/android/removabledrive/RemovableDriveModule.java new file mode 100644 index 000000000..44dd6e69a --- /dev/null +++ b/briar-android/src/main/java/org/briarproject/briar/android/removabledrive/RemovableDriveModule.java @@ -0,0 +1,18 @@ +package org.briarproject.briar.android.removabledrive; + +import org.briarproject.briar.android.viewmodel.ViewModelKey; + +import androidx.lifecycle.ViewModel; +import dagger.Binds; +import dagger.Module; +import dagger.multibindings.IntoMap; + +@Module +public interface RemovableDriveModule { + + @Binds + @IntoMap + @ViewModelKey(RemovableDriveViewModel.class) + ViewModel bindRemovableDriveViewModel(RemovableDriveViewModel removableDriveViewModel); + +} diff --git a/briar-android/src/main/java/org/briarproject/briar/android/conversation/RemovableDriveViewModel.java b/briar-android/src/main/java/org/briarproject/briar/android/removabledrive/RemovableDriveViewModel.java similarity index 98% rename from briar-android/src/main/java/org/briarproject/briar/android/conversation/RemovableDriveViewModel.java rename to briar-android/src/main/java/org/briarproject/briar/android/removabledrive/RemovableDriveViewModel.java index e9aff76b7..a17fe7a3e 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/conversation/RemovableDriveViewModel.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/removabledrive/RemovableDriveViewModel.java @@ -1,4 +1,4 @@ -package org.briarproject.briar.android.conversation; +package org.briarproject.briar.android.removabledrive; import android.app.Application; import android.net.Uri; diff --git a/briar-android/src/main/java/org/briarproject/briar/android/viewmodel/ViewModelModule.java b/briar-android/src/main/java/org/briarproject/briar/android/viewmodel/ViewModelModule.java index e75045eb4..6e8d295f1 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/viewmodel/ViewModelModule.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/viewmodel/ViewModelModule.java @@ -4,7 +4,7 @@ import org.briarproject.briar.android.contact.add.remote.AddContactViewModel; import org.briarproject.briar.android.contact.add.remote.PendingContactListViewModel; import org.briarproject.briar.android.conversation.ConversationViewModel; import org.briarproject.briar.android.conversation.ImageViewModel; -import org.briarproject.briar.android.conversation.RemovableDriveViewModel; +import org.briarproject.briar.android.removabledrive.RemovableDriveViewModel; import javax.inject.Singleton; @@ -41,12 +41,6 @@ public abstract class ViewModelModule { abstract ViewModel bindPendingRequestsViewModel( PendingContactListViewModel pendingContactListViewModel); - @Binds - @IntoMap - @ViewModelKey(RemovableDriveViewModel.class) - abstract ViewModel bindRemovableDriveViewModel( - RemovableDriveViewModel removableDriveViewModel); - @Binds @Singleton abstract ViewModelProvider.Factory bindViewModelFactory( From e05575b9562efea2328a6e5f12b15a38e8744c20 Mon Sep 17 00:00:00 2001 From: akwizgran Date: Tue, 8 Jun 2021 15:51:29 +0100 Subject: [PATCH 30/77] Add unit tests for addRotationKeys() methods. --- .../bramble/transport/KeyManagerImplTest.java | 33 +++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/bramble-core/src/test/java/org/briarproject/bramble/transport/KeyManagerImplTest.java b/bramble-core/src/test/java/org/briarproject/bramble/transport/KeyManagerImplTest.java index 02bb20244..5936b89e1 100644 --- a/bramble-core/src/test/java/org/briarproject/bramble/transport/KeyManagerImplTest.java +++ b/bramble-core/src/test/java/org/briarproject/bramble/transport/KeyManagerImplTest.java @@ -242,4 +242,37 @@ public class KeyManagerImplTest extends BrambleMockTestCase { keyManager.eventOccurred(event); executor.runUntilIdle(); } + + @Test + public void testAddMultipleRotationKeySets() throws Exception { + long timestamp = System.currentTimeMillis(); + boolean alice = random.nextBoolean(); + boolean active = random.nextBoolean(); + + context.checking(new Expectations() {{ + oneOf(transportKeyManager).addRotationKeys(txn, contactId, + rootKey, timestamp, alice, active); + will(returnValue(keySetId)); + }}); + + assertEquals(singletonMap(transportId, keySetId), + keyManager.addRotationKeys(txn, contactId, rootKey, timestamp, + alice, active)); + } + + @Test + public void testAddSingleRotationKeySet() throws Exception { + long timestamp = System.currentTimeMillis(); + boolean alice = random.nextBoolean(); + boolean active = random.nextBoolean(); + + context.checking(new Expectations() {{ + oneOf(transportKeyManager).addRotationKeys(txn, contactId, + rootKey, timestamp, alice, active); + will(returnValue(keySetId)); + }}); + + assertEquals(keySetId, keyManager.addRotationKeys(txn, contactId, + transportId, rootKey, timestamp, alice, active)); + } } From 5dfd9e35463d8369d050c325979eba0da412b536 Mon Sep 17 00:00:00 2001 From: akwizgran Date: Tue, 8 Jun 2021 17:13:18 +0100 Subject: [PATCH 31/77] Make tests more readable. --- .../TransportKeyAgreementManagerImplTest.java | 382 ++++++++---------- 1 file changed, 177 insertions(+), 205 deletions(-) diff --git a/bramble-core/src/test/java/org/briarproject/bramble/transport/agreement/TransportKeyAgreementManagerImplTest.java b/bramble-core/src/test/java/org/briarproject/bramble/transport/agreement/TransportKeyAgreementManagerImplTest.java index f1563875b..e2f786780 100644 --- a/bramble-core/src/test/java/org/briarproject/bramble/transport/agreement/TransportKeyAgreementManagerImplTest.java +++ b/bramble-core/src/test/java/org/briarproject/bramble/transport/agreement/TransportKeyAgreementManagerImplTest.java @@ -215,7 +215,6 @@ public class TransportKeyAgreementManagerImplTest extends BrambleMockTestCase { @Test public void testStartsSessionAtStartup() throws Exception { Transaction txn = new Transaction(null, false); - AtomicReference savedSession = new AtomicReference<>(); context.checking(new Expectations() {{ oneOf(db).getContacts(txn); @@ -227,41 +226,22 @@ public class TransportKeyAgreementManagerImplTest extends BrambleMockTestCase { oneOf(db).getTransportsWithKeys(txn); will(returnValue(singletonMap(contact.getId(), singletonList(duplexTransportId)))); - // Check whether a session exists - it doesn't + // Get the contact group ID oneOf(contactGroupFactory) .createContactGroup(CLIENT_ID, MAJOR_VERSION, contact); will(returnValue(contactGroup)); - oneOf(sessionEncoder).getSessionQuery(simplexTransportId); - will(returnValue(sessionQuery)); - oneOf(clientHelper) - .getMessageIds(txn, contactGroup.getId(), sessionQuery); - will(returnValue(emptyList())); - // Send a key message - oneOf(crypto).generateKeyPair(); - will(returnValue(localKeyPair)); - oneOf(messageEncoder).encodeKeyMessage(contactGroup.getId(), - simplexTransportId, localKeyPair.getPublic()); - will(returnValue(localKeyMessage)); - oneOf(messageEncoder) - .encodeMessageMetadata(simplexTransportId, KEY, true); - will(returnValue(localKeyMeta)); - oneOf(clientHelper).addLocalMessage(txn, localKeyMessage, - localKeyMeta, true, false); - // Save the session - oneOf(clientHelper) - .createMessageForStoringMetadata(contactGroup.getId()); - will(returnValue(storageMessage)); - oneOf(db).addLocalMessage(txn, storageMessage, new Metadata(), - false, false); - oneOf(sessionEncoder).encodeSession(with(any(Session.class)), - with(simplexTransportId)); - will(doAll( - new CaptureArgumentAction<>(savedSession, Session.class, 0), - returnValue(sessionMeta))); - oneOf(clientHelper).mergeMessageMetadata(txn, - storageMessage.getId(), sessionMeta); }}); + // Check whether a session exists - it doesn't + expectSessionDoesNotExist(txn); + // Generate the local key pair + expectGenerateLocalKeyPair(); + // Send a key message + expectSendKeyMessage(txn); + // Save the session + expectCreateStorageMessage(txn); + AtomicReference savedSession = expectSaveSession(txn); + manager.onDatabaseOpened(txn); assertEquals(AWAIT_KEY, savedSession.get().getState()); @@ -289,61 +269,21 @@ public class TransportKeyAgreementManagerImplTest extends BrambleMockTestCase { Transaction txn = new Transaction(null, false); Session loadedSession = new Session(AWAIT_KEY, localKeyMessage.getId(), localKeyPair, localTimestamp, null); - AtomicReference savedSession = new AtomicReference<>(); - context.checking(new Expectations() {{ - // Check whether a session exists - it does - oneOf(sessionEncoder).getSessionQuery(simplexTransportId); - will(returnValue(sessionQuery)); - oneOf(clientHelper) - .getMessageIds(txn, contactGroup.getId(), sessionQuery); - will(returnValue(singletonList(storageMessage.getId()))); - // Load the session - oneOf(clientHelper).getMessageMetadataAsDictionary(txn, - storageMessage.getId()); - will(returnValue(sessionMeta)); - oneOf(sessionParser).parseSession(sessionMeta); - will(returnValue(loadedSession)); - // Load the contact ID - oneOf(clientHelper).getContactId(txn, contactGroup.getId()); - will(returnValue(contact.getId())); - // Check whether we already have keys - we don't - oneOf(db).containsTransportKeys(txn, contact.getId(), - simplexTransportId); - will(returnValue(false)); - // Parse the remote public key - oneOf(crypto).parsePublicKey(remotePublicKey.getEncoded()); - will(returnValue(remotePublicKey)); - // Derive and store the transport keys - oneOf(crypto).deriveRootKey(localKeyPair, remotePublicKey, - min(localTimestamp, remoteTimestamp)); - will(returnValue(rootKey)); - oneOf(db).getContact(txn, contact.getId()); - will(returnValue(contact)); - oneOf(identityManager).getLocalAuthor(txn); - will(returnValue(localAuthor)); - oneOf(keyManager).addRotationKeys(txn, contact.getId(), - simplexTransportId, rootKey, - min(localTimestamp, remoteTimestamp), alice, false); - will(returnValue(keySetId)); - // Send an activate message - oneOf(messageEncoder).encodeActivateMessage(contactGroup.getId(), - simplexTransportId, localKeyMessage.getId()); - will(returnValue(localActivateMessage)); - oneOf(messageEncoder) - .encodeMessageMetadata(simplexTransportId, ACTIVATE, true); - will(returnValue(localActivateMeta)); - oneOf(clientHelper).addLocalMessage(txn, localActivateMessage, - localActivateMeta, true, false); - // Save the session - oneOf(sessionEncoder).encodeSession(with(any(Session.class)), - with(simplexTransportId)); - will(doAll( - new CaptureArgumentAction<>(savedSession, Session.class, 0), - returnValue(sessionMeta))); - oneOf(clientHelper).mergeMessageMetadata(txn, - storageMessage.getId(), sessionMeta); - }}); + // Check whether a session exists - it does + expectLoadSession(txn, loadedSession); + // Load the contact ID + expectLoadContactId(txn); + // Check whether we already have keys - we don't + expectKeysExist(txn, false); + // Parse the remote public key + expectParseRemotePublicKey(); + // Derive and store the transport keys + expectDeriveAndStoreTransportKeys(txn); + // Send an activate message + expectSendActivateMessage(txn); + // Save the session + AtomicReference savedSession = expectSaveSession(txn); assertEquals(ACCEPT_DO_NOT_SHARE, manager.incomingMessage(txn, remoteKeyMessage, remoteMessageBody, remoteKeyMeta)); @@ -360,72 +300,26 @@ public class TransportKeyAgreementManagerImplTest extends BrambleMockTestCase { public void testAcceptsKeyMessageIfWeHaveTransportKeysButNoSession() throws Exception { Transaction txn = new Transaction(null, false); - AtomicReference savedSession = new AtomicReference<>(); - context.checking(new Expectations() {{ - // Check whether a session exists - it doesn't - oneOf(sessionEncoder).getSessionQuery(simplexTransportId); - will(returnValue(sessionQuery)); - oneOf(clientHelper) - .getMessageIds(txn, contactGroup.getId(), sessionQuery); - will(returnValue(emptyList())); - // Load the contact ID - oneOf(clientHelper).getContactId(txn, contactGroup.getId()); - will(returnValue(contact.getId())); - // Check whether we already have keys - we do - oneOf(db).containsTransportKeys(txn, contact.getId(), - simplexTransportId); - will(returnValue(true)); - // Generate the local key pair - oneOf(crypto).generateKeyPair(); - will(returnValue(localKeyPair)); - // Parse the remote public key - oneOf(crypto).parsePublicKey(remotePublicKey.getEncoded()); - will(returnValue(remotePublicKey)); - // Send a key message - oneOf(messageEncoder).encodeKeyMessage(contactGroup.getId(), - simplexTransportId, localKeyPair.getPublic()); - will(returnValue(localKeyMessage)); - oneOf(messageEncoder) - .encodeMessageMetadata(simplexTransportId, KEY, true); - will(returnValue(localKeyMeta)); - oneOf(clientHelper).addLocalMessage(txn, localKeyMessage, - localKeyMeta, true, false); - // Derive and store the transport keys - oneOf(crypto).deriveRootKey(localKeyPair, remotePublicKey, - min(localTimestamp, remoteTimestamp)); - will(returnValue(rootKey)); - oneOf(db).getContact(txn, contact.getId()); - will(returnValue(contact)); - oneOf(identityManager).getLocalAuthor(txn); - will(returnValue(localAuthor)); - oneOf(keyManager).addRotationKeys(txn, contact.getId(), - simplexTransportId, rootKey, - min(localTimestamp, remoteTimestamp), alice, false); - will(returnValue(keySetId)); - // Send an activate message - oneOf(messageEncoder).encodeActivateMessage(contactGroup.getId(), - simplexTransportId, localKeyMessage.getId()); - will(returnValue(localActivateMessage)); - oneOf(messageEncoder) - .encodeMessageMetadata(simplexTransportId, ACTIVATE, true); - will(returnValue(localActivateMeta)); - oneOf(clientHelper).addLocalMessage(txn, localActivateMessage, - localActivateMeta, true, false); - // Save the session - oneOf(clientHelper) - .createMessageForStoringMetadata(contactGroup.getId()); - will(returnValue(storageMessage)); - oneOf(db).addLocalMessage(txn, storageMessage, new Metadata(), - false, false); - oneOf(sessionEncoder).encodeSession(with(any(Session.class)), - with(simplexTransportId)); - will(doAll( - new CaptureArgumentAction<>(savedSession, Session.class, 0), - returnValue(sessionMeta))); - oneOf(clientHelper).mergeMessageMetadata(txn, - storageMessage.getId(), sessionMeta); - }}); + // Check whether a session exists - it doesn't + expectSessionDoesNotExist(txn); + // Load the contact ID + expectLoadContactId(txn); + // Check whether we already have keys - we do + expectKeysExist(txn, true); + // Generate the local key pair + expectGenerateLocalKeyPair(); + // Parse the remote public key + expectParseRemotePublicKey(); + // Send a key message + expectSendKeyMessage(txn); + // Derive and store the transport keys + expectDeriveAndStoreTransportKeys(txn); + // Send an activate message + expectSendActivateMessage(txn); + // Save the session + expectCreateStorageMessage(txn); + AtomicReference savedSession = expectSaveSession(txn); assertEquals(ACCEPT_DO_NOT_SHARE, manager.incomingMessage(txn, remoteKeyMessage, remoteMessageBody, remoteKeyMeta)); @@ -456,27 +350,12 @@ public class TransportKeyAgreementManagerImplTest extends BrambleMockTestCase { throws Exception { Transaction txn = new Transaction(null, false); - context.checking(new Expectations() {{ - // Check whether a session exists - it does - oneOf(sessionEncoder).getSessionQuery(simplexTransportId); - will(returnValue(sessionQuery)); - oneOf(clientHelper) - .getMessageIds(txn, contactGroup.getId(), sessionQuery); - will(returnValue(singletonList(storageMessage.getId()))); - // Load the session - oneOf(clientHelper).getMessageMetadataAsDictionary(txn, - storageMessage.getId()); - will(returnValue(sessionMeta)); - oneOf(sessionParser).parseSession(sessionMeta); - will(returnValue(loadedSession)); - // Load the contact ID - oneOf(clientHelper).getContactId(txn, contactGroup.getId()); - will(returnValue(contact.getId())); - // Check whether we already have keys - we don't - oneOf(db).containsTransportKeys(txn, contact.getId(), - simplexTransportId); - will(returnValue(false)); - }}); + // Check whether a session exists - it does + expectLoadSession(txn, loadedSession); + // Load the contact ID + expectLoadContactId(txn); + // Check whether we already have keys - we don't + expectKeysExist(txn, false); assertEquals(REJECT, manager.incomingMessage(txn, remoteKeyMessage, remoteMessageBody, remoteKeyMeta)); @@ -488,34 +367,19 @@ public class TransportKeyAgreementManagerImplTest extends BrambleMockTestCase { Transaction txn = new Transaction(null, false); Session loadedSession = new Session(AWAIT_ACTIVATE, localActivateMessage.getId(), null, null, keySetId); - AtomicReference savedSession = new AtomicReference<>(); + // Check whether a session exists - it does + expectLoadSession(txn, loadedSession); + + // Activate the transport keys context.checking(new Expectations() {{ - // Check whether a session exists - it does - oneOf(sessionEncoder).getSessionQuery(simplexTransportId); - will(returnValue(sessionQuery)); - oneOf(clientHelper) - .getMessageIds(txn, contactGroup.getId(), sessionQuery); - will(returnValue(singletonList(storageMessage.getId()))); - // Load the session - oneOf(clientHelper).getMessageMetadataAsDictionary(txn, - storageMessage.getId()); - will(returnValue(sessionMeta)); - oneOf(sessionParser).parseSession(sessionMeta); - will(returnValue(loadedSession)); - // Activate the transport keys oneOf(keyManager).activateKeys(txn, singletonMap(simplexTransportId, keySetId)); - // Save the session - oneOf(sessionEncoder).encodeSession(with(any(Session.class)), - with(simplexTransportId)); - will(doAll( - new CaptureArgumentAction<>(savedSession, Session.class, 0), - returnValue(sessionMeta))); - oneOf(clientHelper).mergeMessageMetadata(txn, - storageMessage.getId(), sessionMeta); }}); + // Save the session + AtomicReference savedSession = expectSaveSession(txn); + assertEquals(ACCEPT_DO_NOT_SHARE, manager.incomingMessage(txn, remoteActivateMessage, remoteMessageBody, remoteActivateMeta)); @@ -531,14 +395,8 @@ public class TransportKeyAgreementManagerImplTest extends BrambleMockTestCase { public void testRejectsActivateMessageWithNoSession() throws Exception { Transaction txn = new Transaction(null, false); - context.checking(new Expectations() {{ - // Check whether a session exists - it doesn't - oneOf(sessionEncoder).getSessionQuery(simplexTransportId); - will(returnValue(sessionQuery)); - oneOf(clientHelper) - .getMessageIds(txn, contactGroup.getId(), sessionQuery); - will(returnValue(emptyList())); - }}); + // Check whether a session exists - it doesn't + expectSessionDoesNotExist(txn); assertEquals(REJECT, manager.incomingMessage(txn, remoteActivateMessage, remoteMessageBody, remoteActivateMeta)); @@ -562,22 +420,136 @@ public class TransportKeyAgreementManagerImplTest extends BrambleMockTestCase { Session loadedSession) throws Exception { Transaction txn = new Transaction(null, false); + // Check whether a session exists - it does + expectLoadSession(txn, loadedSession); + + assertEquals(REJECT, manager.incomingMessage(txn, + remoteActivateMessage, remoteMessageBody, remoteActivateMeta)); + } + + private void expectSessionDoesNotExist(Transaction txn) throws Exception { + context.checking(new Expectations() {{ + oneOf(sessionEncoder).getSessionQuery(simplexTransportId); + will(returnValue(sessionQuery)); + oneOf(clientHelper) + .getMessageIds(txn, contactGroup.getId(), sessionQuery); + will(returnValue(emptyList())); + }}); + } + + private void expectLoadSession(Transaction txn, Session loadedSession) + throws Exception { context.checking(new Expectations() {{ - // Check whether a session exists - it does oneOf(sessionEncoder).getSessionQuery(simplexTransportId); will(returnValue(sessionQuery)); oneOf(clientHelper) .getMessageIds(txn, contactGroup.getId(), sessionQuery); will(returnValue(singletonList(storageMessage.getId()))); - // Load the session oneOf(clientHelper).getMessageMetadataAsDictionary(txn, storageMessage.getId()); will(returnValue(sessionMeta)); oneOf(sessionParser).parseSession(sessionMeta); will(returnValue(loadedSession)); }}); + } - assertEquals(REJECT, manager.incomingMessage(txn, - remoteActivateMessage, remoteMessageBody, remoteActivateMeta)); + private void expectSendKeyMessage(Transaction txn) throws Exception { + context.checking(new Expectations() {{ + oneOf(messageEncoder).encodeKeyMessage(contactGroup.getId(), + simplexTransportId, localKeyPair.getPublic()); + will(returnValue(localKeyMessage)); + oneOf(messageEncoder) + .encodeMessageMetadata(simplexTransportId, KEY, true); + will(returnValue(localKeyMeta)); + oneOf(clientHelper).addLocalMessage(txn, localKeyMessage, + localKeyMeta, true, false); + }}); + } + + private void expectSendActivateMessage(Transaction txn) throws Exception { + context.checking(new Expectations() {{ + oneOf(messageEncoder).encodeActivateMessage(contactGroup.getId(), + simplexTransportId, localKeyMessage.getId()); + will(returnValue(localActivateMessage)); + oneOf(messageEncoder) + .encodeMessageMetadata(simplexTransportId, ACTIVATE, true); + will(returnValue(localActivateMeta)); + oneOf(clientHelper).addLocalMessage(txn, localActivateMessage, + localActivateMeta, true, false); + }}); + } + + private void expectCreateStorageMessage(Transaction txn) throws Exception { + context.checking(new Expectations() {{ + oneOf(clientHelper) + .createMessageForStoringMetadata(contactGroup.getId()); + will(returnValue(storageMessage)); + oneOf(db).addLocalMessage(txn, storageMessage, new Metadata(), + false, false); + }}); + } + + private AtomicReference expectSaveSession(Transaction txn) + throws Exception { + AtomicReference savedSession = new AtomicReference<>(); + + context.checking(new Expectations() {{ + oneOf(sessionEncoder).encodeSession(with(any(Session.class)), + with(simplexTransportId)); + will(doAll( + new CaptureArgumentAction<>(savedSession, Session.class, 0), + returnValue(sessionMeta))); + oneOf(clientHelper).mergeMessageMetadata(txn, + storageMessage.getId(), sessionMeta); + }}); + + return savedSession; + } + + private void expectLoadContactId(Transaction txn) throws Exception { + context.checking(new Expectations() {{ + oneOf(clientHelper).getContactId(txn, contactGroup.getId()); + will(returnValue(contact.getId())); + }}); + } + + private void expectGenerateLocalKeyPair() { + context.checking(new Expectations() {{ + oneOf(crypto).generateKeyPair(); + will(returnValue(localKeyPair)); + }}); + } + + private void expectParseRemotePublicKey() throws Exception { + context.checking(new Expectations() {{ + oneOf(crypto).parsePublicKey(remotePublicKey.getEncoded()); + will(returnValue(remotePublicKey)); + }}); + } + + private void expectDeriveAndStoreTransportKeys(Transaction txn) + throws Exception { + context.checking(new Expectations() {{ + oneOf(crypto).deriveRootKey(localKeyPair, remotePublicKey, + min(localTimestamp, remoteTimestamp)); + will(returnValue(rootKey)); + oneOf(db).getContact(txn, contact.getId()); + will(returnValue(contact)); + oneOf(identityManager).getLocalAuthor(txn); + will(returnValue(localAuthor)); + oneOf(keyManager).addRotationKeys(txn, contact.getId(), + simplexTransportId, rootKey, + min(localTimestamp, remoteTimestamp), alice, false); + will(returnValue(keySetId)); + }}); + } + + private void expectKeysExist(Transaction txn, boolean exist) + throws Exception { + context.checking(new Expectations() {{ + oneOf(db).containsTransportKeys(txn, contact.getId(), + simplexTransportId); + will(returnValue(exist)); + }}); } } From 1ddcd6cfffdd4ea5995128b6ae72b89ece2fa6ba Mon Sep 17 00:00:00 2001 From: Daniel Lublin Date: Tue, 8 Jun 2021 20:31:23 +0200 Subject: [PATCH 32/77] Make pkg private --- .../briar/android/removabledrive/RemovableDriveViewModel.java | 2 +- .../briarproject/briar/android/viewmodel/ViewModelModule.java | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/briar-android/src/main/java/org/briarproject/briar/android/removabledrive/RemovableDriveViewModel.java b/briar-android/src/main/java/org/briarproject/briar/android/removabledrive/RemovableDriveViewModel.java index a17fe7a3e..d9be940d6 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/removabledrive/RemovableDriveViewModel.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/removabledrive/RemovableDriveViewModel.java @@ -29,7 +29,7 @@ import static java.util.logging.Logger.getLogger; import static org.briarproject.bramble.api.plugin.file.RemovableDriveConstants.PROP_URI; @NotNullByDefault -public class RemovableDriveViewModel extends AndroidViewModel { +class RemovableDriveViewModel extends AndroidViewModel { private static final Logger LOG = getLogger(RemovableDriveViewModel.class.getName()); diff --git a/briar-android/src/main/java/org/briarproject/briar/android/viewmodel/ViewModelModule.java b/briar-android/src/main/java/org/briarproject/briar/android/viewmodel/ViewModelModule.java index 6e8d295f1..ce7899915 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/viewmodel/ViewModelModule.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/viewmodel/ViewModelModule.java @@ -4,7 +4,6 @@ import org.briarproject.briar.android.contact.add.remote.AddContactViewModel; import org.briarproject.briar.android.contact.add.remote.PendingContactListViewModel; import org.briarproject.briar.android.conversation.ConversationViewModel; import org.briarproject.briar.android.conversation.ImageViewModel; -import org.briarproject.briar.android.removabledrive.RemovableDriveViewModel; import javax.inject.Singleton; From a72e92de24957e758870d2a8e65a3279be2a7453 Mon Sep 17 00:00:00 2001 From: akwizgran Date: Wed, 9 Jun 2021 10:07:36 +0100 Subject: [PATCH 33/77] Timestamp isn't needed for deriving root key. --- .../transport/agreement/TransportKeyAgreementCrypto.java | 4 ++-- .../agreement/TransportKeyAgreementCryptoImpl.java | 3 +-- .../agreement/TransportKeyAgreementManagerImpl.java | 6 ++---- .../agreement/TransportKeyAgreementManagerImplTest.java | 3 +-- 4 files changed, 6 insertions(+), 10 deletions(-) diff --git a/bramble-core/src/main/java/org/briarproject/bramble/transport/agreement/TransportKeyAgreementCrypto.java b/bramble-core/src/main/java/org/briarproject/bramble/transport/agreement/TransportKeyAgreementCrypto.java index fc9b8a575..888e33d6e 100644 --- a/bramble-core/src/main/java/org/briarproject/bramble/transport/agreement/TransportKeyAgreementCrypto.java +++ b/bramble-core/src/main/java/org/briarproject/bramble/transport/agreement/TransportKeyAgreementCrypto.java @@ -14,8 +14,8 @@ interface TransportKeyAgreementCrypto { KeyPair generateKeyPair(); - SecretKey deriveRootKey(KeyPair localKeyPair, PublicKey remotePublicKey, - long timestamp) throws GeneralSecurityException; + SecretKey deriveRootKey(KeyPair localKeyPair, PublicKey remotePublicKey) + throws GeneralSecurityException; PublicKey parsePublicKey(byte[] encoded) throws FormatException; diff --git a/bramble-core/src/main/java/org/briarproject/bramble/transport/agreement/TransportKeyAgreementCryptoImpl.java b/bramble-core/src/main/java/org/briarproject/bramble/transport/agreement/TransportKeyAgreementCryptoImpl.java index 5ae01dccc..5fed461e1 100644 --- a/bramble-core/src/main/java/org/briarproject/bramble/transport/agreement/TransportKeyAgreementCryptoImpl.java +++ b/bramble-core/src/main/java/org/briarproject/bramble/transport/agreement/TransportKeyAgreementCryptoImpl.java @@ -34,8 +34,7 @@ class TransportKeyAgreementCryptoImpl implements TransportKeyAgreementCrypto { @Override public SecretKey deriveRootKey(KeyPair localKeyPair, - PublicKey remotePublicKey, long timestamp) - throws GeneralSecurityException { + PublicKey remotePublicKey) throws GeneralSecurityException { byte[] theirPublic = remotePublicKey.getEncoded(); byte[] ourPublic = localKeyPair.getPublic().getEncoded(); boolean alice = compare(ourPublic, theirPublic) < 0; diff --git a/bramble-core/src/main/java/org/briarproject/bramble/transport/agreement/TransportKeyAgreementManagerImpl.java b/bramble-core/src/main/java/org/briarproject/bramble/transport/agreement/TransportKeyAgreementManagerImpl.java index 7bd479396..4c52526f2 100644 --- a/bramble-core/src/main/java/org/briarproject/bramble/transport/agreement/TransportKeyAgreementManagerImpl.java +++ b/bramble-core/src/main/java/org/briarproject/bramble/transport/agreement/TransportKeyAgreementManagerImpl.java @@ -255,8 +255,7 @@ class TransportKeyAgreementManagerImpl extends BdfIncomingMessageHook long minTimestamp = min(keyMessage.getTimestamp(), m.getTimestamp()); SecretKey rootKey; try { - rootKey = crypto.deriveRootKey(localKeyPair, remotePublicKey, - minTimestamp); + rootKey = crypto.deriveRootKey(localKeyPair, remotePublicKey); } catch (GeneralSecurityException e) { return REJECT; // Invalid public key } @@ -281,8 +280,7 @@ class TransportKeyAgreementManagerImpl extends BdfIncomingMessageHook long minTimestamp = min(localTimestamp, m.getTimestamp()); SecretKey rootKey; try { - rootKey = crypto.deriveRootKey(localKeyPair, remotePublicKey, - minTimestamp); + rootKey = crypto.deriveRootKey(localKeyPair, remotePublicKey); } catch (GeneralSecurityException e) { return REJECT; // Invalid public key } diff --git a/bramble-core/src/test/java/org/briarproject/bramble/transport/agreement/TransportKeyAgreementManagerImplTest.java b/bramble-core/src/test/java/org/briarproject/bramble/transport/agreement/TransportKeyAgreementManagerImplTest.java index e2f786780..e05f70d41 100644 --- a/bramble-core/src/test/java/org/briarproject/bramble/transport/agreement/TransportKeyAgreementManagerImplTest.java +++ b/bramble-core/src/test/java/org/briarproject/bramble/transport/agreement/TransportKeyAgreementManagerImplTest.java @@ -530,8 +530,7 @@ public class TransportKeyAgreementManagerImplTest extends BrambleMockTestCase { private void expectDeriveAndStoreTransportKeys(Transaction txn) throws Exception { context.checking(new Expectations() {{ - oneOf(crypto).deriveRootKey(localKeyPair, remotePublicKey, - min(localTimestamp, remoteTimestamp)); + oneOf(crypto).deriveRootKey(localKeyPair, remotePublicKey); will(returnValue(rootKey)); oneOf(db).getContact(txn, contact.getId()); will(returnValue(contact)); From 07162cad8b115032031d4906aa8425e223c668d3 Mon Sep 17 00:00:00 2001 From: akwizgran Date: Tue, 15 Jun 2021 11:44:10 +0100 Subject: [PATCH 34/77] Refactor removable drive tasks. --- .../plugin/file/RemovableDriveManager.java | 25 ++++--- .../api/plugin/file/RemovableDriveTask.java | 2 +- .../file/RemovableDriveManagerImpl.java | 71 ++++++++++--------- .../plugin/file/RemovableDriveReaderTask.java | 27 ++----- .../file/RemovableDriveTaskFactory.java | 2 +- .../file/RemovableDriveTaskFactoryImpl.java | 4 +- .../plugin/file/RemovableDriveTaskImpl.java | 4 -- .../file/RemovableDriveTaskRegistry.java | 5 +- .../plugin/file/RemovableDriveWriterTask.java | 10 +-- .../file/RemovableDriveIntegrationTest.java | 10 +-- .../RemovableDriveViewModel.java | 12 ++-- 11 files changed, 78 insertions(+), 94 deletions(-) diff --git a/bramble-api/src/main/java/org/briarproject/bramble/api/plugin/file/RemovableDriveManager.java b/bramble-api/src/main/java/org/briarproject/bramble/api/plugin/file/RemovableDriveManager.java index 081b4362a..cc14a08bb 100644 --- a/bramble-api/src/main/java/org/briarproject/bramble/api/plugin/file/RemovableDriveManager.java +++ b/bramble-api/src/main/java/org/briarproject/bramble/api/plugin/file/RemovableDriveManager.java @@ -10,32 +10,31 @@ import javax.annotation.Nullable; public interface RemovableDriveManager { /** - * Returns the currently running reader task for the given contact, - * or null if no task is running. + * Returns the currently running reader task, or null if no reader task + * is running. */ @Nullable - RemovableDriveTask getCurrentReaderTask(ContactId c); + RemovableDriveTask getCurrentReaderTask(); /** - * Returns the currently running writer task for the given contact, - * or null if no task is running. + * Returns the currently running writer task, or null if no writer task + * is running. */ @Nullable - RemovableDriveTask getCurrentWriterTask(ContactId c); + RemovableDriveTask getCurrentWriterTask(); /** - * Starts and returns a reader task for the given contact, reading from - * a stream described by the given transport properties. If a reader task - * for the contact is already running, it will be returned and the - * transport properties will be ignored. + * Starts and returns a reader task, reading from a stream described by + * the given transport properties. If a reader task is already running, + * it will be returned and the argument will be ignored. */ - RemovableDriveTask startReaderTask(ContactId c, TransportProperties p); + RemovableDriveTask startReaderTask(TransportProperties p); /** * Starts and returns a writer task for the given contact, writing to * a stream described by the given transport properties. If a writer task - * for the contact is already running, it will be returned and the - * transport properties will be ignored. + * is already running, it will be returned and the arguments will be + * ignored. */ RemovableDriveTask startWriterTask(ContactId c, TransportProperties p); } diff --git a/bramble-api/src/main/java/org/briarproject/bramble/api/plugin/file/RemovableDriveTask.java b/bramble-api/src/main/java/org/briarproject/bramble/api/plugin/file/RemovableDriveTask.java index e27413a04..ad153d52d 100644 --- a/bramble-api/src/main/java/org/briarproject/bramble/api/plugin/file/RemovableDriveTask.java +++ b/bramble-api/src/main/java/org/briarproject/bramble/api/plugin/file/RemovableDriveTask.java @@ -39,7 +39,7 @@ public interface RemovableDriveTask extends Runnable { /** * Returns the total length in bytes of the messages read or written - * so far. + * so far, or zero if the total is unknown. */ public long getDone() { return done; diff --git a/bramble-core/src/main/java/org/briarproject/bramble/plugin/file/RemovableDriveManagerImpl.java b/bramble-core/src/main/java/org/briarproject/bramble/plugin/file/RemovableDriveManagerImpl.java index 262b33a69..78490a045 100644 --- a/bramble-core/src/main/java/org/briarproject/bramble/plugin/file/RemovableDriveManagerImpl.java +++ b/bramble-core/src/main/java/org/briarproject/bramble/plugin/file/RemovableDriveManagerImpl.java @@ -7,10 +7,10 @@ import org.briarproject.bramble.api.plugin.file.RemovableDriveManager; import org.briarproject.bramble.api.plugin.file.RemovableDriveTask; import org.briarproject.bramble.api.properties.TransportProperties; -import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.Executor; import javax.annotation.Nullable; +import javax.annotation.concurrent.GuardedBy; import javax.annotation.concurrent.ThreadSafe; import javax.inject.Inject; @@ -21,10 +21,12 @@ class RemovableDriveManagerImpl private final Executor ioExecutor; private final RemovableDriveTaskFactory taskFactory; - private final ConcurrentHashMap - readers = new ConcurrentHashMap<>(); - private final ConcurrentHashMap - writers = new ConcurrentHashMap<>(); + private final Object lock = new Object(); + + @GuardedBy("lock") + private RemovableDriveTask reader = null; + @GuardedBy("lock") + private RemovableDriveTask writer = null; @Inject RemovableDriveManagerImpl(@IoExecutor Executor ioExecutor, @@ -35,49 +37,54 @@ class RemovableDriveManagerImpl @Nullable @Override - public RemovableDriveTask getCurrentReaderTask(ContactId c) { - return readers.get(c); + public RemovableDriveTask getCurrentReaderTask() { + synchronized (lock) { + return reader; + } } @Nullable @Override - public RemovableDriveTask getCurrentWriterTask(ContactId c) { - return writers.get(c); + public RemovableDriveTask getCurrentWriterTask() { + synchronized (lock) { + return writer; + } } @Override - public RemovableDriveTask startReaderTask(ContactId c, - TransportProperties p) { - RemovableDriveTask task = taskFactory.createReader(this, c, p); - RemovableDriveTask old = readers.putIfAbsent(c, task); - if (old == null) { - ioExecutor.execute(task); - return task; - } else { - return old; + public RemovableDriveTask startReaderTask(TransportProperties p) { + RemovableDriveTask created; + synchronized (lock) { + if (reader != null) return reader; + reader = created = taskFactory.createReader(this, p); } + ioExecutor.execute(created); + return created; } @Override public RemovableDriveTask startWriterTask(ContactId c, TransportProperties p) { - RemovableDriveTask task = taskFactory.createWriter(this, c, p); - RemovableDriveTask old = writers.putIfAbsent(c, task); - if (old == null) { - ioExecutor.execute(task); - return task; - } else { - return old; + RemovableDriveTask created; + synchronized (lock) { + if (writer != null) return writer; + writer = created = taskFactory.createWriter(this, c, p); + } + ioExecutor.execute(created); + return created; + } + + @Override + public void removeReader(RemovableDriveTask task) { + synchronized (lock) { + if (reader == task) reader = null; } } @Override - public void removeReader(ContactId c, RemovableDriveTask task) { - readers.remove(c, task); - } - - @Override - public void removeWriter(ContactId c, RemovableDriveTask task) { - writers.remove(c, task); + public void removeWriter(RemovableDriveTask task) { + synchronized (lock) { + if (writer == task) writer = null; + } } } diff --git a/bramble-core/src/main/java/org/briarproject/bramble/plugin/file/RemovableDriveReaderTask.java b/bramble-core/src/main/java/org/briarproject/bramble/plugin/file/RemovableDriveReaderTask.java index 5e5b270af..5dc4b6762 100644 --- a/bramble-core/src/main/java/org/briarproject/bramble/plugin/file/RemovableDriveReaderTask.java +++ b/bramble-core/src/main/java/org/briarproject/bramble/plugin/file/RemovableDriveReaderTask.java @@ -1,15 +1,11 @@ package org.briarproject.bramble.plugin.file; import org.briarproject.bramble.api.connection.ConnectionManager; -import org.briarproject.bramble.api.contact.ContactId; -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.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.plugin.PluginManager; import org.briarproject.bramble.api.plugin.TransportConnectionReader; import org.briarproject.bramble.api.properties.TransportProperties; -import org.briarproject.bramble.api.sync.event.MessageAddedEvent; import java.io.IOException; import java.io.InputStream; @@ -20,8 +16,7 @@ import static java.util.logging.Logger.getLogger; import static org.briarproject.bramble.api.plugin.file.RemovableDriveConstants.ID; @NotNullByDefault -class RemovableDriveReaderTask extends RemovableDriveTaskImpl - implements EventListener { +class RemovableDriveReaderTask extends RemovableDriveTaskImpl { private final static Logger LOG = getLogger(RemovableDriveReaderTask.class.getName()); @@ -32,10 +27,9 @@ class RemovableDriveReaderTask extends RemovableDriveTaskImpl ConnectionManager connectionManager, EventBus eventBus, RemovableDriveTaskRegistry registry, - ContactId contactId, TransportProperties transportProperties) { super(eventExecutor, pluginManager, connectionManager, eventBus, - registry, contactId, transportProperties); + registry, transportProperties); } @Override @@ -44,25 +38,13 @@ class RemovableDriveReaderTask extends RemovableDriveTaskImpl getPlugin().createReader(transportProperties); if (r == null) { LOG.warning("Failed to create reader"); - registry.removeReader(contactId, this); + registry.removeReader(this); setSuccess(false); return; } - eventBus.addListener(this); connectionManager.manageIncomingConnection(ID, new DecoratedReader(r)); } - @Override - public void eventOccurred(Event e) { - if (e instanceof MessageAddedEvent) { - MessageAddedEvent m = (MessageAddedEvent) e; - if (contactId.equals(m.getContactId())) { - LOG.info("Message received"); - addDone(m.getMessage().getRawLength()); - } - } - } - private class DecoratedReader implements TransportConnectionReader { private final TransportConnectionReader delegate; @@ -80,8 +62,7 @@ class RemovableDriveReaderTask extends RemovableDriveTaskImpl public void dispose(boolean exception, boolean recognised) throws IOException { delegate.dispose(exception, recognised); - registry.removeReader(contactId, RemovableDriveReaderTask.this); - eventBus.removeListener(RemovableDriveReaderTask.this); + registry.removeReader(RemovableDriveReaderTask.this); setSuccess(!exception && recognised); } } diff --git a/bramble-core/src/main/java/org/briarproject/bramble/plugin/file/RemovableDriveTaskFactory.java b/bramble-core/src/main/java/org/briarproject/bramble/plugin/file/RemovableDriveTaskFactory.java index e90019d76..c6a126d81 100644 --- a/bramble-core/src/main/java/org/briarproject/bramble/plugin/file/RemovableDriveTaskFactory.java +++ b/bramble-core/src/main/java/org/briarproject/bramble/plugin/file/RemovableDriveTaskFactory.java @@ -9,7 +9,7 @@ import org.briarproject.bramble.api.properties.TransportProperties; interface RemovableDriveTaskFactory { RemovableDriveTask createReader(RemovableDriveTaskRegistry registry, - ContactId c, TransportProperties p); + TransportProperties p); RemovableDriveTask createWriter(RemovableDriveTaskRegistry registry, ContactId c, TransportProperties p); diff --git a/bramble-core/src/main/java/org/briarproject/bramble/plugin/file/RemovableDriveTaskFactoryImpl.java b/bramble-core/src/main/java/org/briarproject/bramble/plugin/file/RemovableDriveTaskFactoryImpl.java index b493054a0..596f0b11e 100644 --- a/bramble-core/src/main/java/org/briarproject/bramble/plugin/file/RemovableDriveTaskFactoryImpl.java +++ b/bramble-core/src/main/java/org/briarproject/bramble/plugin/file/RemovableDriveTaskFactoryImpl.java @@ -41,9 +41,9 @@ class RemovableDriveTaskFactoryImpl implements RemovableDriveTaskFactory { @Override public RemovableDriveTask createReader(RemovableDriveTaskRegistry registry, - ContactId c, TransportProperties p) { + TransportProperties p) { return new RemovableDriveReaderTask(eventExecutor, pluginManager, - connectionManager, eventBus, registry, c, p); + connectionManager, eventBus, registry, p); } @Override diff --git a/bramble-core/src/main/java/org/briarproject/bramble/plugin/file/RemovableDriveTaskImpl.java b/bramble-core/src/main/java/org/briarproject/bramble/plugin/file/RemovableDriveTaskImpl.java index 442d716e0..a94393cb6 100644 --- a/bramble-core/src/main/java/org/briarproject/bramble/plugin/file/RemovableDriveTaskImpl.java +++ b/bramble-core/src/main/java/org/briarproject/bramble/plugin/file/RemovableDriveTaskImpl.java @@ -2,7 +2,6 @@ package org.briarproject.bramble.plugin.file; import org.briarproject.bramble.api.Consumer; import org.briarproject.bramble.api.connection.ConnectionManager; -import org.briarproject.bramble.api.contact.ContactId; import org.briarproject.bramble.api.event.EventBus; import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.plugin.PluginManager; @@ -30,7 +29,6 @@ abstract class RemovableDriveTaskImpl implements RemovableDriveTask { final ConnectionManager connectionManager; final EventBus eventBus; final RemovableDriveTaskRegistry registry; - final ContactId contactId; final TransportProperties transportProperties; private final Object lock = new Object(); @@ -45,14 +43,12 @@ abstract class RemovableDriveTaskImpl implements RemovableDriveTask { ConnectionManager connectionManager, EventBus eventBus, RemovableDriveTaskRegistry registry, - ContactId contactId, TransportProperties transportProperties) { this.eventExecutor = eventExecutor; this.pluginManager = pluginManager; this.connectionManager = connectionManager; this.eventBus = eventBus; this.registry = registry; - this.contactId = contactId; this.transportProperties = transportProperties; } diff --git a/bramble-core/src/main/java/org/briarproject/bramble/plugin/file/RemovableDriveTaskRegistry.java b/bramble-core/src/main/java/org/briarproject/bramble/plugin/file/RemovableDriveTaskRegistry.java index 84ee40092..1f073bbf2 100644 --- a/bramble-core/src/main/java/org/briarproject/bramble/plugin/file/RemovableDriveTaskRegistry.java +++ b/bramble-core/src/main/java/org/briarproject/bramble/plugin/file/RemovableDriveTaskRegistry.java @@ -1,13 +1,12 @@ package org.briarproject.bramble.plugin.file; -import org.briarproject.bramble.api.contact.ContactId; import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.plugin.file.RemovableDriveTask; @NotNullByDefault interface RemovableDriveTaskRegistry { - void removeReader(ContactId c, RemovableDriveTask task); + void removeReader(RemovableDriveTask task); - void removeWriter(ContactId c, RemovableDriveTask task); + void removeWriter(RemovableDriveTask task); } diff --git a/bramble-core/src/main/java/org/briarproject/bramble/plugin/file/RemovableDriveWriterTask.java b/bramble-core/src/main/java/org/briarproject/bramble/plugin/file/RemovableDriveWriterTask.java index 7d2539557..26f5c4969 100644 --- a/bramble-core/src/main/java/org/briarproject/bramble/plugin/file/RemovableDriveWriterTask.java +++ b/bramble-core/src/main/java/org/briarproject/bramble/plugin/file/RemovableDriveWriterTask.java @@ -33,6 +33,7 @@ class RemovableDriveWriterTask extends RemovableDriveTaskImpl getLogger(RemovableDriveWriterTask.class.getName()); private final DatabaseComponent db; + private final ContactId contactId; RemovableDriveWriterTask( DatabaseComponent db, @@ -44,8 +45,9 @@ class RemovableDriveWriterTask extends RemovableDriveTaskImpl ContactId contactId, TransportProperties transportProperties) { super(eventExecutor, pluginManager, connectionManager, eventBus, - registry, contactId, transportProperties); + registry, transportProperties); this.db = db; + this.contactId = contactId; } @Override @@ -54,7 +56,7 @@ class RemovableDriveWriterTask extends RemovableDriveTaskImpl TransportConnectionWriter w = plugin.createWriter(transportProperties); if (w == null) { LOG.warning("Failed to create writer"); - registry.removeWriter(contactId, this); + registry.removeWriter(this); setSuccess(false); return; } @@ -64,7 +66,7 @@ class RemovableDriveWriterTask extends RemovableDriveTaskImpl db.getMessageBytesToSend(txn, contactId, maxLatency))); } catch (DbException e) { logException(LOG, WARNING, e); - registry.removeWriter(contactId, this); + registry.removeWriter(this); setSuccess(false); return; } @@ -112,7 +114,7 @@ class RemovableDriveWriterTask extends RemovableDriveTaskImpl @Override public void dispose(boolean exception) throws IOException { delegate.dispose(exception); - registry.removeWriter(contactId, RemovableDriveWriterTask.this); + registry.removeWriter(RemovableDriveWriterTask.this); eventBus.removeListener(RemovableDriveWriterTask.this); setSuccess(!exception); } diff --git a/bramble-core/src/test/java/org/briarproject/bramble/plugin/file/RemovableDriveIntegrationTest.java b/bramble-core/src/test/java/org/briarproject/bramble/plugin/file/RemovableDriveIntegrationTest.java index ab3fa731f..b924beb5f 100644 --- a/bramble-core/src/test/java/org/briarproject/bramble/plugin/file/RemovableDriveIntegrationTest.java +++ b/bramble-core/src/test/java/org/briarproject/bramble/plugin/file/RemovableDriveIntegrationTest.java @@ -70,9 +70,9 @@ public class RemovableDriveIntegrationTest extends BrambleTestCase { ContactId aliceId = setUp(bob, bobIdentity, aliceIdentity.getLocalAuthor(), false); // Sync Alice's client versions and transport properties - read(bob, aliceId, write(alice, bobId), 2); + read(bob, write(alice, bobId), 2); // Sync Bob's client versions and transport properties - read(alice, bobId, write(bob, aliceId), 2); + read(alice, write(bob, aliceId), 2); } private ContactId setUp(RemovableDriveIntegrationTestComponent device, @@ -92,7 +92,7 @@ public class RemovableDriveIntegrationTest extends BrambleTestCase { @SuppressWarnings("SameParameterValue") private void read(RemovableDriveIntegrationTestComponent device, - ContactId contactId, File file, int deliveries) throws Exception { + File file, int deliveries) throws Exception { // Listen for message deliveries MessageDeliveryListener listener = new MessageDeliveryListener(deliveries); @@ -100,8 +100,8 @@ public class RemovableDriveIntegrationTest extends BrambleTestCase { // Read the incoming stream TransportProperties p = new TransportProperties(); p.put(PROP_PATH, file.getAbsolutePath()); - RemovableDriveTask reader = device.getRemovableDriveManager() - .startReaderTask(contactId, p); + RemovableDriveTask reader = + device.getRemovableDriveManager().startReaderTask(p); CountDownLatch disposedLatch = new CountDownLatch(1); reader.addObserver(state -> { if (state.isFinished()) disposedLatch.countDown(); diff --git a/briar-android/src/main/java/org/briarproject/briar/android/removabledrive/RemovableDriveViewModel.java b/briar-android/src/main/java/org/briarproject/briar/android/removabledrive/RemovableDriveViewModel.java index d9be940d6..20765cf51 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/removabledrive/RemovableDriveViewModel.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/removabledrive/RemovableDriveViewModel.java @@ -59,15 +59,15 @@ class RemovableDriveViewModel extends AndroidViewModel { return observe(manager.startWriterTask(contactId, p)); } - LiveData read(ContactId contactId, Uri uri) { + LiveData read(Uri uri) { TransportProperties p = new TransportProperties(); p.put(PROP_URI, uri.toString()); - return observe(manager.startReaderTask(contactId, p)); + return observe(manager.startReaderTask(p)); } @Nullable - LiveData ongoingWrite(ContactId contactId) { - RemovableDriveTask task = manager.getCurrentWriterTask(contactId); + LiveData ongoingWrite() { + RemovableDriveTask task = manager.getCurrentWriterTask(); if (task == null) { return null; } @@ -75,8 +75,8 @@ class RemovableDriveViewModel extends AndroidViewModel { } @Nullable - LiveData ongoingRead(ContactId contactId) { - RemovableDriveTask task = manager.getCurrentReaderTask(contactId); + LiveData ongoingRead() { + RemovableDriveTask task = manager.getCurrentReaderTask(); if (task == null) { return null; } From faab80f0eaf26b7010425ff47164a2aeadb6b592 Mon Sep 17 00:00:00 2001 From: akwizgran Date: Tue, 15 Jun 2021 11:47:10 +0100 Subject: [PATCH 35/77] Hold lock while calling notifyObservers(). --- .../bramble/plugin/file/RemovableDriveTaskImpl.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bramble-core/src/main/java/org/briarproject/bramble/plugin/file/RemovableDriveTaskImpl.java b/bramble-core/src/main/java/org/briarproject/bramble/plugin/file/RemovableDriveTaskImpl.java index a94393cb6..cae0546b0 100644 --- a/bramble-core/src/main/java/org/briarproject/bramble/plugin/file/RemovableDriveTaskImpl.java +++ b/bramble-core/src/main/java/org/briarproject/bramble/plugin/file/RemovableDriveTaskImpl.java @@ -94,15 +94,15 @@ abstract class RemovableDriveTaskImpl implements RemovableDriveTask { done = min(state.getDone() + done, state.getTotal()); state = new State(done, state.getTotal(), state.isFinished(), state.isSuccess()); + notifyObservers(); } - notifyObservers(); } void setSuccess(boolean success) { synchronized (lock) { state = new State(state.getDone(), state.getTotal(), true, success); + notifyObservers(); } - notifyObservers(); } @GuardedBy("lock") From 9a58b37ce2fcdfa64253a209bb6cf003cfae41f9 Mon Sep 17 00:00:00 2001 From: akwizgran Date: Mon, 7 Jun 2021 17:27:46 +0100 Subject: [PATCH 36/77] Add database methods for sending unacked messages. --- .../bramble/api/db/DatabaseComponent.java | 23 +++++++++++ .../org/briarproject/bramble/db/Database.java | 17 +++++++++ .../bramble/db/DatabaseComponentImpl.java | 36 ++++++++++++++++++ .../briarproject/bramble/db/JdbcDatabase.java | 38 +++++++++++++++++++ 4 files changed, 114 insertions(+) diff --git a/bramble-api/src/main/java/org/briarproject/bramble/api/db/DatabaseComponent.java b/bramble-api/src/main/java/org/briarproject/bramble/api/db/DatabaseComponent.java index 2771302a2..53ed7416a 100644 --- a/bramble-api/src/main/java/org/briarproject/bramble/api/db/DatabaseComponent.java +++ b/bramble-api/src/main/java/org/briarproject/bramble/api/db/DatabaseComponent.java @@ -190,6 +190,18 @@ public interface DatabaseComponent extends TransactionManager { Collection generateBatch(Transaction txn, ContactId c, int maxLength, int maxLatency) throws DbException; + /** + * Returns a batch of messages for the given contact containing the + * messages with the given IDs, for transmission over a transport with + * the given maximum latency. + *

+ * If any of the given messages are not in the database or are not visible + * to the contact, they are omitted from the batch without throwing an + * exception. + */ + Collection generateBatch(Transaction txn, ContactId c, + Collection ids, int maxLatency) throws DbException; + /** * Returns an offer for the given contact for transmission over a * transport with the given maximum latency, or null if there are no @@ -446,6 +458,17 @@ public interface DatabaseComponent extends TransactionManager { MessageStatus getMessageStatus(Transaction txn, ContactId c, MessageId m) throws DbException; + /** + * Returns the IDs of all messages that are eligible to be sent to the + * given contact, together with their raw lengths. This may include + * messages that have already been sent and are not yet due for + * retransmission. + *

+ * Read-only. + */ + Map getUnackedMessagesToSend(Transaction txn, + ContactId c) throws DbException; + /** * Returns the next time (in milliseconds since the Unix epoch) when a * message is due to be deleted, or {@link #NO_CLEANUP_DEADLINE} diff --git a/bramble-core/src/main/java/org/briarproject/bramble/db/Database.java b/bramble-core/src/main/java/org/briarproject/bramble/db/Database.java index 705913ee7..cb386ab5c 100644 --- a/bramble-core/src/main/java/org/briarproject/bramble/db/Database.java +++ b/bramble-core/src/main/java/org/briarproject/bramble/db/Database.java @@ -496,11 +496,28 @@ interface Database { * Returns the IDs of some messages that are eligible to be sent to the * given contact, up to the given total length. *

+ * Unlike {@link #getUnackedMessagesToSend(Object, ContactId)} this method + * does not return messages that have already been sent unless they are + * due for retransmission. + *

* Read-only. */ Collection getMessagesToSend(T txn, ContactId c, int maxLength, int maxLatency) throws DbException; + /** + * Returns the IDs of all messages that are eligible to be sent to the + * given contact, together with their raw lengths. + *

+ * Unlike {@link #getMessagesToSend(Object, ContactId, int, int)} this + * method may return messages that have already been sent and are not yet + * due for retransmission. + *

+ * Read-only. + */ + Map getUnackedMessagesToSend(T txn, ContactId c) + throws DbException; + /** * Returns the IDs of any messages that need to be validated. *

diff --git a/bramble-core/src/main/java/org/briarproject/bramble/db/DatabaseComponentImpl.java b/bramble-core/src/main/java/org/briarproject/bramble/db/DatabaseComponentImpl.java index b2422f9bc..d4c8ef682 100644 --- a/bramble-core/src/main/java/org/briarproject/bramble/db/DatabaseComponentImpl.java +++ b/bramble-core/src/main/java/org/briarproject/bramble/db/DatabaseComponentImpl.java @@ -436,6 +436,32 @@ class DatabaseComponentImpl implements DatabaseComponent { return messages; } + @Override + public Collection generateBatch(Transaction transaction, + ContactId c, Collection ids, int maxLatency) + throws DbException { + if (transaction.isReadOnly()) throw new IllegalArgumentException(); + T txn = unbox(transaction); + if (!db.containsContact(txn, c)) + throw new NoSuchContactException(); + long totalLength = 0; + List messages = new ArrayList<>(ids.size()); + List sentIds = new ArrayList<>(ids.size()); + for (MessageId m : ids) { + if (db.containsVisibleMessage(txn, c, m)) { + Message message = db.getMessage(txn, m); + totalLength += message.getRawLength(); + messages.add(message); + sentIds.add(m); + db.updateExpiryTimeAndEta(txn, c, m, maxLatency); + } + } + if (messages.isEmpty()) return messages; + db.lowerRequestedFlag(txn, c, sentIds); + transaction.attach(new MessagesSentEvent(c, sentIds, totalLength)); + return messages; + } + @Nullable @Override public Offer generateOffer(Transaction transaction, ContactId c, @@ -714,6 +740,16 @@ class DatabaseComponentImpl implements DatabaseComponent { return status; } + @Override + public Map getUnackedMessagesToSend( + Transaction transaction, + ContactId c) throws DbException { + T txn = unbox(transaction); + if (!db.containsContact(txn, c)) + throw new NoSuchContactException(); + return db.getUnackedMessagesToSend(txn, c); + } + @Override public Map getMessageDependencies( Transaction transaction, MessageId m) throws DbException { diff --git a/bramble-core/src/main/java/org/briarproject/bramble/db/JdbcDatabase.java b/bramble-core/src/main/java/org/briarproject/bramble/db/JdbcDatabase.java index 224b20bf1..32212193b 100644 --- a/bramble-core/src/main/java/org/briarproject/bramble/db/JdbcDatabase.java +++ b/bramble-core/src/main/java/org/briarproject/bramble/db/JdbcDatabase.java @@ -51,6 +51,7 @@ import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; +import java.util.LinkedHashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; @@ -344,6 +345,11 @@ abstract class JdbcDatabase implements Database { "CREATE INDEX IF NOT EXISTS statusesByContactIdTimestamp" + " ON statuses (contactId, timestamp)"; + private static final String + INDEX_STATUSES_BY_CONTACT_ID_TX_COUNT_TIMESTAMP = + "CREATE INDEX IF NOT EXISTS statusesByContactIdTxCountTimestamp" + + " ON statuses (contactId, txCount, timestamp)"; + private static final String INDEX_MESSAGES_BY_CLEANUP_DEADLINE = "CREATE INDEX IF NOT EXISTS messagesByCleanupDeadline" + " ON messages (cleanupDeadline)"; @@ -570,6 +576,7 @@ abstract class JdbcDatabase implements Database { s.executeUpdate(INDEX_MESSAGE_DEPENDENCIES_BY_DEPENDENCY_ID); s.executeUpdate(INDEX_STATUSES_BY_CONTACT_ID_GROUP_ID); s.executeUpdate(INDEX_STATUSES_BY_CONTACT_ID_TIMESTAMP); + s.executeUpdate(INDEX_STATUSES_BY_CONTACT_ID_TX_COUNT_TIMESTAMP); s.executeUpdate(INDEX_MESSAGES_BY_CLEANUP_DEADLINE); s.close(); } catch (SQLException e) { @@ -2259,6 +2266,37 @@ abstract class JdbcDatabase implements Database { } } + @Override + public Map getUnackedMessagesToSend(Connection txn, + ContactId c) throws DbException { + PreparedStatement ps = null; + ResultSet rs = null; + try { + String sql = "SELECT length, messageId FROM statuses" + + " WHERE contactId = ? AND state = ?" + + " AND groupShared = TRUE AND messageShared = TRUE" + + " AND deleted = FALSE AND seen = FALSE" + + " ORDER BY txCount, timestamp"; + ps = txn.prepareStatement(sql); + ps.setInt(1, c.getInt()); + ps.setInt(2, DELIVERED.getValue()); + rs = ps.executeQuery(); + Map results = new LinkedHashMap<>(); + while (rs.next()) { + int length = rs.getInt(1); + MessageId id = new MessageId(rs.getBytes(2)); + results.put(id, length); + } + rs.close(); + ps.close(); + return results; + } catch (SQLException e) { + tryToClose(rs, LOG, WARNING); + tryToClose(ps, LOG, WARNING); + throw new DbException(e); + } + } + @Override public Collection getMessagesToValidate(Connection txn) throws DbException { From 77a3199aac5f87c0748826ac9a057d4cf82a596f Mon Sep 17 00:00:00 2001 From: akwizgran Date: Tue, 8 Jun 2021 12:10:52 +0100 Subject: [PATCH 37/77] Update SimplexOutgoingSession to support sending unacked messages. --- .../bramble/sync/SimplexOutgoingSession.java | 97 ++++++++++++++++++- .../bramble/sync/SyncSessionFactoryImpl.java | 2 +- .../sync/SimplexOutgoingSessionTest.java | 4 +- 3 files changed, 98 insertions(+), 5 deletions(-) diff --git a/bramble-core/src/main/java/org/briarproject/bramble/sync/SimplexOutgoingSession.java b/bramble-core/src/main/java/org/briarproject/bramble/sync/SimplexOutgoingSession.java index 1d32ca4ee..d069d38b0 100644 --- a/bramble-core/src/main/java/org/briarproject/bramble/sync/SimplexOutgoingSession.java +++ b/bramble-core/src/main/java/org/briarproject/bramble/sync/SimplexOutgoingSession.java @@ -15,6 +15,7 @@ import org.briarproject.bramble.api.plugin.TransportId; import org.briarproject.bramble.api.plugin.event.TransportInactiveEvent; import org.briarproject.bramble.api.sync.Ack; import org.briarproject.bramble.api.sync.Message; +import org.briarproject.bramble.api.sync.MessageId; import org.briarproject.bramble.api.sync.SyncRecordWriter; import org.briarproject.bramble.api.sync.SyncSession; import org.briarproject.bramble.api.sync.Versions; @@ -22,7 +23,11 @@ import org.briarproject.bramble.api.sync.event.CloseSyncConnectionsEvent; import org.briarproject.bramble.api.transport.StreamWriter; import java.io.IOException; +import java.util.ArrayList; import java.util.Collection; +import java.util.Iterator; +import java.util.Map; +import java.util.Map.Entry; import java.util.concurrent.BlockingQueue; import java.util.concurrent.Executor; import java.util.concurrent.LinkedBlockingQueue; @@ -61,6 +66,7 @@ class SimplexOutgoingSession implements SyncSession, EventListener { private final ContactId contactId; private final TransportId transportId; private final int maxLatency; + private final boolean eager; private final StreamWriter streamWriter; private final SyncRecordWriter recordWriter; private final AtomicInteger outstandingQueries; @@ -70,7 +76,7 @@ class SimplexOutgoingSession implements SyncSession, EventListener { SimplexOutgoingSession(DatabaseComponent db, Executor dbExecutor, EventBus eventBus, ContactId contactId, TransportId transportId, - int maxLatency, StreamWriter streamWriter, + int maxLatency, boolean eager, StreamWriter streamWriter, SyncRecordWriter recordWriter) { this.db = db; this.dbExecutor = dbExecutor; @@ -78,6 +84,7 @@ class SimplexOutgoingSession implements SyncSession, EventListener { this.contactId = contactId; this.transportId = transportId; this.maxLatency = maxLatency; + this.eager = eager; this.streamWriter = streamWriter; this.recordWriter = recordWriter; outstandingQueries = new AtomicInteger(2); // One per type of record @@ -93,7 +100,8 @@ class SimplexOutgoingSession implements SyncSession, EventListener { recordWriter.writeVersions(new Versions(SUPPORTED_VERSIONS)); // Start a query for each type of record dbExecutor.execute(new GenerateAck()); - dbExecutor.execute(new GenerateBatch()); + if (eager) dbExecutor.execute(new LoadUnackedMessageIds()); + else dbExecutor.execute(new GenerateBatch()); // Write records until interrupted or no more records to write try { while (!interrupted) { @@ -138,6 +146,91 @@ class SimplexOutgoingSession implements SyncSession, EventListener { } } + private class LoadUnackedMessageIds implements Runnable { + + @DatabaseExecutor + @Override + public void run() { + if (interrupted) return; + try { + Map ids = + db.transactionWithResult(true, txn -> + db.getUnackedMessagesToSend(txn, contactId)); + if (LOG.isLoggable(INFO)) { + LOG.info(ids.size() + " unacked messages to send"); + } + if (ids.isEmpty()) decrementOutstandingQueries(); + else dbExecutor.execute(new GenerateEagerBatch(ids)); + } catch (DbException e) { + logException(LOG, WARNING, e); + interrupt(); + } + } + } + + private class GenerateEagerBatch implements Runnable { + + private final Map ids; + + private GenerateEagerBatch(Map ids) { + this.ids = ids; + } + + @DatabaseExecutor + @Override + public void run() { + if (interrupted) return; + // Take some message IDs from `ids` to form a batch + Collection batchIds = new ArrayList<>(); + long totalLength = 0; + Iterator> it = + ids.entrySet().iterator(); + while (it.hasNext()) { + // Check whether the next message will fit in the batch + Entry e = it.next(); + int length = e.getValue(); + if (totalLength + length > MAX_RECORD_PAYLOAD_BYTES) break; + // Add the message to the batch + it.remove(); + batchIds.add(e.getKey()); + totalLength += length; + } + if (batchIds.isEmpty()) throw new AssertionError(); + try { + Collection batch = + db.transactionWithResult(false, txn -> + db.generateBatch(txn, contactId, batchIds, + maxLatency)); + writerTasks.add(new WriteEagerBatch(batch, ids)); + } catch (DbException e) { + logException(LOG, WARNING, e); + interrupt(); + } + } + } + + private class WriteEagerBatch implements ThrowingRunnable { + + private final Collection batch; + private final Map ids; + + private WriteEagerBatch(Collection batch, + Map ids) { + this.batch = batch; + this.ids = ids; + } + + @IoExecutor + @Override + public void run() throws IOException { + if (interrupted) return; + for (Message m : batch) recordWriter.writeMessage(m); + LOG.info("Sent eager batch"); + if (ids.isEmpty()) decrementOutstandingQueries(); + else dbExecutor.execute(new GenerateEagerBatch(ids)); + } + } + private class GenerateAck implements Runnable { @DatabaseExecutor diff --git a/bramble-core/src/main/java/org/briarproject/bramble/sync/SyncSessionFactoryImpl.java b/bramble-core/src/main/java/org/briarproject/bramble/sync/SyncSessionFactoryImpl.java index 4c590df73..74ec3e51a 100644 --- a/bramble-core/src/main/java/org/briarproject/bramble/sync/SyncSessionFactoryImpl.java +++ b/bramble-core/src/main/java/org/briarproject/bramble/sync/SyncSessionFactoryImpl.java @@ -65,7 +65,7 @@ class SyncSessionFactoryImpl implements SyncSessionFactory { SyncRecordWriter recordWriter = recordWriterFactory.createRecordWriter(out); return new SimplexOutgoingSession(db, dbExecutor, eventBus, c, t, - maxLatency, streamWriter, recordWriter); + maxLatency, false, streamWriter, recordWriter); } @Override diff --git a/bramble-core/src/test/java/org/briarproject/bramble/sync/SimplexOutgoingSessionTest.java b/bramble-core/src/test/java/org/briarproject/bramble/sync/SimplexOutgoingSessionTest.java index df8dc7b87..0dd7baf2f 100644 --- a/bramble-core/src/test/java/org/briarproject/bramble/sync/SimplexOutgoingSessionTest.java +++ b/bramble-core/src/test/java/org/briarproject/bramble/sync/SimplexOutgoingSessionTest.java @@ -46,7 +46,7 @@ public class SimplexOutgoingSessionTest extends BrambleMockTestCase { public void testNothingToSend() throws Exception { SimplexOutgoingSession session = new SimplexOutgoingSession(db, dbExecutor, eventBus, contactId, transportId, MAX_LATENCY, - streamWriter, recordWriter); + false, streamWriter, recordWriter); Transaction noAckTxn = new Transaction(null, false); Transaction noMsgTxn = new Transaction(null, false); @@ -80,7 +80,7 @@ public class SimplexOutgoingSessionTest extends BrambleMockTestCase { Ack ack = new Ack(singletonList(messageId)); SimplexOutgoingSession session = new SimplexOutgoingSession(db, dbExecutor, eventBus, contactId, transportId, MAX_LATENCY, - streamWriter, recordWriter); + false, streamWriter, recordWriter); Transaction ackTxn = new Transaction(null, false); Transaction noAckTxn = new Transaction(null, false); Transaction msgTxn = new Transaction(null, false); From 847650f280360d9f6bc3974f732cd30309748075 Mon Sep 17 00:00:00 2001 From: akwizgran Date: Thu, 10 Jun 2021 17:04:15 +0100 Subject: [PATCH 38/77] Replace inner classes with lambdas. --- .../bramble/sync/SimplexOutgoingSession.java | 240 +++++++----------- 1 file changed, 92 insertions(+), 148 deletions(-) diff --git a/bramble-core/src/main/java/org/briarproject/bramble/sync/SimplexOutgoingSession.java b/bramble-core/src/main/java/org/briarproject/bramble/sync/SimplexOutgoingSession.java index d069d38b0..f5c7fcbb6 100644 --- a/bramble-core/src/main/java/org/briarproject/bramble/sync/SimplexOutgoingSession.java +++ b/bramble-core/src/main/java/org/briarproject/bramble/sync/SimplexOutgoingSession.java @@ -99,9 +99,9 @@ class SimplexOutgoingSession implements SyncSession, EventListener { // Send our supported protocol versions recordWriter.writeVersions(new Versions(SUPPORTED_VERSIONS)); // Start a query for each type of record - dbExecutor.execute(new GenerateAck()); - if (eager) dbExecutor.execute(new LoadUnackedMessageIds()); - else dbExecutor.execute(new GenerateBatch()); + dbExecutor.execute(this::generateAck); + if (eager) dbExecutor.execute(this::loadUnackedMessageIds); + else dbExecutor.execute(this::generateBatch); // Write records until interrupted or no more records to write try { while (!interrupted) { @@ -146,166 +146,110 @@ class SimplexOutgoingSession implements SyncSession, EventListener { } } - private class LoadUnackedMessageIds implements Runnable { - - @DatabaseExecutor - @Override - public void run() { - if (interrupted) return; - try { - Map ids = - db.transactionWithResult(true, txn -> - db.getUnackedMessagesToSend(txn, contactId)); - if (LOG.isLoggable(INFO)) { - LOG.info(ids.size() + " unacked messages to send"); - } - if (ids.isEmpty()) decrementOutstandingQueries(); - else dbExecutor.execute(new GenerateEagerBatch(ids)); - } catch (DbException e) { - logException(LOG, WARNING, e); - interrupt(); + @DatabaseExecutor + private void loadUnackedMessageIds() { + if (interrupted) return; + try { + Map ids = db.transactionWithResult(true, txn -> + db.getUnackedMessagesToSend(txn, contactId)); + if (LOG.isLoggable(INFO)) { + LOG.info(ids.size() + " unacked messages to send"); } - } - } - - private class GenerateEagerBatch implements Runnable { - - private final Map ids; - - private GenerateEagerBatch(Map ids) { - this.ids = ids; - } - - @DatabaseExecutor - @Override - public void run() { - if (interrupted) return; - // Take some message IDs from `ids` to form a batch - Collection batchIds = new ArrayList<>(); - long totalLength = 0; - Iterator> it = - ids.entrySet().iterator(); - while (it.hasNext()) { - // Check whether the next message will fit in the batch - Entry e = it.next(); - int length = e.getValue(); - if (totalLength + length > MAX_RECORD_PAYLOAD_BYTES) break; - // Add the message to the batch - it.remove(); - batchIds.add(e.getKey()); - totalLength += length; - } - if (batchIds.isEmpty()) throw new AssertionError(); - try { - Collection batch = - db.transactionWithResult(false, txn -> - db.generateBatch(txn, contactId, batchIds, - maxLatency)); - writerTasks.add(new WriteEagerBatch(batch, ids)); - } catch (DbException e) { - logException(LOG, WARNING, e); - interrupt(); - } - } - } - - private class WriteEagerBatch implements ThrowingRunnable { - - private final Collection batch; - private final Map ids; - - private WriteEagerBatch(Collection batch, - Map ids) { - this.batch = batch; - this.ids = ids; - } - - @IoExecutor - @Override - public void run() throws IOException { - if (interrupted) return; - for (Message m : batch) recordWriter.writeMessage(m); - LOG.info("Sent eager batch"); if (ids.isEmpty()) decrementOutstandingQueries(); - else dbExecutor.execute(new GenerateEagerBatch(ids)); + else dbExecutor.execute(() -> generateEagerBatch(ids)); + } catch (DbException e) { + logException(LOG, WARNING, e); + interrupt(); } } - private class GenerateAck implements Runnable { - - @DatabaseExecutor - @Override - public void run() { - if (interrupted) return; - try { - Ack a = db.transactionWithNullableResult(false, txn -> - db.generateAck(txn, contactId, MAX_MESSAGE_IDS)); - if (LOG.isLoggable(INFO)) - LOG.info("Generated ack: " + (a != null)); - if (a == null) decrementOutstandingQueries(); - else writerTasks.add(new WriteAck(a)); - } catch (DbException e) { - logException(LOG, WARNING, e); - interrupt(); - } + @DatabaseExecutor + private void generateEagerBatch(Map ids) { + if (interrupted) return; + // Take some message IDs from `ids` to form a batch + Collection batchIds = new ArrayList<>(); + long totalLength = 0; + Iterator> it = ids.entrySet().iterator(); + while (it.hasNext()) { + // Check whether the next message will fit in the batch + Entry e = it.next(); + int length = e.getValue(); + if (totalLength + length > MAX_RECORD_PAYLOAD_BYTES) break; + // Add the message to the batch + it.remove(); + batchIds.add(e.getKey()); + totalLength += length; + } + if (batchIds.isEmpty()) throw new AssertionError(); + try { + Collection batch = + db.transactionWithResult(false, txn -> + db.generateBatch(txn, contactId, batchIds, + maxLatency)); + writerTasks.add(() -> writeEagerBatch(batch, ids)); + } catch (DbException e) { + logException(LOG, WARNING, e); + interrupt(); } } - private class WriteAck implements ThrowingRunnable { + @IoExecutor + private void writeEagerBatch(Collection batch, + Map ids) throws IOException { + if (interrupted) return; + for (Message m : batch) recordWriter.writeMessage(m); + LOG.info("Sent eager batch"); + if (ids.isEmpty()) decrementOutstandingQueries(); + else dbExecutor.execute(() -> generateEagerBatch(ids)); + } - private final Ack ack; - - private WriteAck(Ack ack) { - this.ack = ack; - } - - @IoExecutor - @Override - public void run() throws IOException { - if (interrupted) return; - recordWriter.writeAck(ack); - LOG.info("Sent ack"); - dbExecutor.execute(new GenerateAck()); + @DatabaseExecutor + private void generateAck() { + if (interrupted) return; + try { + Ack a = db.transactionWithNullableResult(false, txn -> + db.generateAck(txn, contactId, MAX_MESSAGE_IDS)); + if (LOG.isLoggable(INFO)) + LOG.info("Generated ack: " + (a != null)); + if (a == null) decrementOutstandingQueries(); + else writerTasks.add(() -> writeAck(a)); + } catch (DbException e) { + logException(LOG, WARNING, e); + interrupt(); } } - private class GenerateBatch implements Runnable { + @IoExecutor + private void writeAck(Ack ack) throws IOException { + if (interrupted) return; + recordWriter.writeAck(ack); + LOG.info("Sent ack"); + dbExecutor.execute(this::generateAck); + } - @DatabaseExecutor - @Override - public void run() { - if (interrupted) return; - try { - Collection b = - db.transactionWithNullableResult(false, txn -> - db.generateBatch(txn, contactId, - MAX_RECORD_PAYLOAD_BYTES, maxLatency)); - if (LOG.isLoggable(INFO)) - LOG.info("Generated batch: " + (b != null)); - if (b == null) decrementOutstandingQueries(); - else writerTasks.add(new WriteBatch(b)); - } catch (DbException e) { - logException(LOG, WARNING, e); - interrupt(); - } + @DatabaseExecutor + private void generateBatch() { + if (interrupted) return; + try { + Collection b = + db.transactionWithNullableResult(false, txn -> + db.generateBatch(txn, contactId, + MAX_RECORD_PAYLOAD_BYTES, maxLatency)); + if (LOG.isLoggable(INFO)) + LOG.info("Generated batch: " + (b != null)); + if (b == null) decrementOutstandingQueries(); + else writerTasks.add(() -> writeBatch(b)); + } catch (DbException e) { + logException(LOG, WARNING, e); + interrupt(); } } - private class WriteBatch implements ThrowingRunnable { - - private final Collection batch; - - private WriteBatch(Collection batch) { - this.batch = batch; - } - - @IoExecutor - @Override - public void run() throws IOException { - if (interrupted) return; - for (Message m : batch) recordWriter.writeMessage(m); - LOG.info("Sent batch"); - dbExecutor.execute(new GenerateBatch()); - } + @IoExecutor + private void writeBatch(Collection batch) throws IOException { + if (interrupted) return; + for (Message m : batch) recordWriter.writeMessage(m); + LOG.info("Sent batch"); + dbExecutor.execute(this::generateBatch); } } From a960bfb2c164de2a9ec897d575c4fa917ac37cc9 Mon Sep 17 00:00:00 2001 From: akwizgran Date: Thu, 10 Jun 2021 17:28:30 +0100 Subject: [PATCH 39/77] Add tests for eager retransmission. --- .../sync/SimplexOutgoingSessionTest.java | 120 ++++++++++++++++-- 1 file changed, 111 insertions(+), 9 deletions(-) diff --git a/bramble-core/src/test/java/org/briarproject/bramble/sync/SimplexOutgoingSessionTest.java b/bramble-core/src/test/java/org/briarproject/bramble/sync/SimplexOutgoingSessionTest.java index 0dd7baf2f..9bcf7cc3f 100644 --- a/bramble-core/src/test/java/org/briarproject/bramble/sync/SimplexOutgoingSessionTest.java +++ b/bramble-core/src/test/java/org/briarproject/bramble/sync/SimplexOutgoingSessionTest.java @@ -17,9 +17,14 @@ import org.briarproject.bramble.test.DbExpectations; import org.briarproject.bramble.test.ImmediateExecutor; import org.junit.Test; +import java.util.LinkedHashMap; +import java.util.Map; import java.util.concurrent.Executor; +import static java.util.Collections.emptyMap; import static java.util.Collections.singletonList; +import static org.briarproject.bramble.api.record.Record.MAX_RECORD_PAYLOAD_BYTES; +import static org.briarproject.bramble.api.sync.SyncConstants.MAX_MESSAGE_BODY_LENGTH; import static org.briarproject.bramble.api.sync.SyncConstants.MAX_MESSAGE_IDS; import static org.briarproject.bramble.test.TestUtils.getContactId; import static org.briarproject.bramble.test.TestUtils.getMessage; @@ -39,14 +44,19 @@ public class SimplexOutgoingSessionTest extends BrambleMockTestCase { private final Executor dbExecutor = new ImmediateExecutor(); private final ContactId contactId = getContactId(); private final TransportId transportId = getTransportId(); - private final Message message = getMessage(new GroupId(getRandomId())); - private final MessageId messageId = message.getId(); + private final Ack ack = + new Ack(singletonList(new MessageId(getRandomId()))); + private final Message message = getMessage(new GroupId(getRandomId()), + MAX_MESSAGE_BODY_LENGTH); + private final Message message1 = getMessage(new GroupId(getRandomId()), + MAX_MESSAGE_BODY_LENGTH); @Test public void testNothingToSend() throws Exception { SimplexOutgoingSession session = new SimplexOutgoingSession(db, dbExecutor, eventBus, contactId, transportId, MAX_LATENCY, false, streamWriter, recordWriter); + Transaction noAckTxn = new Transaction(null, false); Transaction noMsgTxn = new Transaction(null, false); @@ -63,8 +73,8 @@ public class SimplexOutgoingSessionTest extends BrambleMockTestCase { // No messages to send oneOf(db).transactionWithNullableResult(with(false), withNullableDbCallable(noMsgTxn)); - oneOf(db).generateBatch(with(noMsgTxn), with(contactId), - with(any(int.class)), with(MAX_LATENCY)); + oneOf(db).generateBatch(noMsgTxn, contactId, + MAX_RECORD_PAYLOAD_BYTES, MAX_LATENCY); will(returnValue(null)); // Send the end of stream marker oneOf(streamWriter).sendEndOfStream(); @@ -75,12 +85,45 @@ public class SimplexOutgoingSessionTest extends BrambleMockTestCase { session.run(); } + @Test + public void testNothingToSendEagerly() throws Exception { + SimplexOutgoingSession session = new SimplexOutgoingSession(db, + dbExecutor, eventBus, contactId, transportId, MAX_LATENCY, + true, streamWriter, recordWriter); + + Transaction noAckTxn = new Transaction(null, false); + Transaction noIdsTxn = new Transaction(null, true); + + context.checking(new DbExpectations() {{ + // Add listener + oneOf(eventBus).addListener(session); + // Send the protocol versions + oneOf(recordWriter).writeVersions(with(any(Versions.class))); + // No acks to send + oneOf(db).transactionWithNullableResult(with(false), + withNullableDbCallable(noAckTxn)); + oneOf(db).generateAck(noAckTxn, contactId, MAX_MESSAGE_IDS); + will(returnValue(null)); + // No messages to send + oneOf(db).transactionWithResult(with(true), + withDbCallable(noIdsTxn)); + oneOf(db).getUnackedMessagesToSend(noIdsTxn, contactId); + will(returnValue(emptyMap())); + // Send the end of stream marker + oneOf(streamWriter).sendEndOfStream(); + // Remove listener + oneOf(eventBus).removeListener(session); + }}); + + session.run(); + } + @Test public void testSomethingToSend() throws Exception { - Ack ack = new Ack(singletonList(messageId)); SimplexOutgoingSession session = new SimplexOutgoingSession(db, dbExecutor, eventBus, contactId, transportId, MAX_LATENCY, false, streamWriter, recordWriter); + Transaction ackTxn = new Transaction(null, false); Transaction noAckTxn = new Transaction(null, false); Transaction msgTxn = new Transaction(null, false); @@ -100,8 +143,8 @@ public class SimplexOutgoingSessionTest extends BrambleMockTestCase { // One message to send oneOf(db).transactionWithNullableResult(with(false), withNullableDbCallable(msgTxn)); - oneOf(db).generateBatch(with(msgTxn), with(contactId), - with(any(int.class)), with(MAX_LATENCY)); + oneOf(db).generateBatch(msgTxn, contactId, + MAX_RECORD_PAYLOAD_BYTES, MAX_LATENCY); will(returnValue(singletonList(message))); oneOf(recordWriter).writeMessage(message); // No more acks @@ -112,8 +155,8 @@ public class SimplexOutgoingSessionTest extends BrambleMockTestCase { // No more messages oneOf(db).transactionWithNullableResult(with(false), withNullableDbCallable(noMsgTxn)); - oneOf(db).generateBatch(with(noMsgTxn), with(contactId), - with(any(int.class)), with(MAX_LATENCY)); + oneOf(db).generateBatch(noMsgTxn, contactId, + MAX_RECORD_PAYLOAD_BYTES, MAX_LATENCY); will(returnValue(null)); // Send the end of stream marker oneOf(streamWriter).sendEndOfStream(); @@ -123,4 +166,63 @@ public class SimplexOutgoingSessionTest extends BrambleMockTestCase { session.run(); } + + @Test + public void testSomethingToSendEagerly() throws Exception { + SimplexOutgoingSession session = new SimplexOutgoingSession(db, + dbExecutor, eventBus, contactId, transportId, MAX_LATENCY, + true, streamWriter, recordWriter); + + Map unacked = new LinkedHashMap<>(); + unacked.put(message.getId(), message.getRawLength()); + unacked.put(message1.getId(), message1.getRawLength()); + + Transaction ackTxn = new Transaction(null, false); + Transaction noAckTxn = new Transaction(null, false); + Transaction idsTxn = new Transaction(null, true); + Transaction msgTxn = new Transaction(null, false); + Transaction msgTxn1 = new Transaction(null, false); + + context.checking(new DbExpectations() {{ + // Add listener + oneOf(eventBus).addListener(session); + // Send the protocol versions + oneOf(recordWriter).writeVersions(with(any(Versions.class))); + // One ack to send + oneOf(db).transactionWithNullableResult(with(false), + withNullableDbCallable(ackTxn)); + oneOf(db).generateAck(ackTxn, contactId, MAX_MESSAGE_IDS); + will(returnValue(ack)); + oneOf(recordWriter).writeAck(ack); + // No more acks + oneOf(db).transactionWithNullableResult(with(false), + withNullableDbCallable(noAckTxn)); + oneOf(db).generateAck(noAckTxn, contactId, MAX_MESSAGE_IDS); + will(returnValue(null)); + // Two messages to send + oneOf(db).transactionWithResult(with(true), withDbCallable(idsTxn)); + oneOf(db).getUnackedMessagesToSend(idsTxn, contactId); + will(returnValue(unacked)); + // Send the first message + oneOf(db).transactionWithResult(with(false), + withDbCallable(msgTxn)); + oneOf(db).generateBatch(msgTxn, contactId, + singletonList(message.getId()), MAX_LATENCY); + will(returnValue(singletonList(message))); + oneOf(recordWriter).writeMessage(message); + // Send the second message + oneOf(db).transactionWithResult(with(false), + withDbCallable(msgTxn1)); + oneOf(db).generateBatch(msgTxn1, contactId, + singletonList(message1.getId()), MAX_LATENCY); + will(returnValue(singletonList(message1))); + oneOf(recordWriter).writeMessage(message1); + // Send the end of stream marker + oneOf(streamWriter).sendEndOfStream(); + // Remove listener + oneOf(eventBus).removeListener(session); + }}); + + session.run(); + } } From a5ce40034185873e4ea3b762e1781bc172787db6 Mon Sep 17 00:00:00 2001 From: akwizgran Date: Thu, 10 Jun 2021 17:42:19 +0100 Subject: [PATCH 40/77] Use eager retransmission if the transport is lossy and cheap. --- .../bramble/api/plugin/TransportConnectionWriter.java | 5 +++++ .../duplex/AbstractDuplexTransportConnection.java | 5 +++++ .../bramble/api/plugin/simplex/SimplexPlugin.java | 6 ++++++ .../bramble/api/sync/SyncSessionFactory.java | 2 +- .../connection/OutgoingSimplexSyncConnection.java | 4 +++- .../plugin/file/AbstractRemovableDrivePlugin.java | 5 +++++ .../bramble/plugin/file/FileTransportWriter.java | 5 +++++ .../bramble/plugin/file/RemovableDriveWriterTask.java | 5 +++++ .../plugin/file/TransportOutputStreamWriter.java | 11 ++++++++--- .../bramble/sync/SyncSessionFactoryImpl.java | 4 ++-- .../bramble/test/TestTransportConnectionWriter.java | 5 +++++ 11 files changed, 50 insertions(+), 7 deletions(-) diff --git a/bramble-api/src/main/java/org/briarproject/bramble/api/plugin/TransportConnectionWriter.java b/bramble-api/src/main/java/org/briarproject/bramble/api/plugin/TransportConnectionWriter.java index 219f33efe..4ed5ba1ed 100644 --- a/bramble-api/src/main/java/org/briarproject/bramble/api/plugin/TransportConnectionWriter.java +++ b/bramble-api/src/main/java/org/briarproject/bramble/api/plugin/TransportConnectionWriter.java @@ -22,6 +22,11 @@ public interface TransportConnectionWriter { */ int getMaxIdleTime(); + /** + * Returns true if the transport is lossy and cheap. + */ + boolean isLossyAndCheap(); + /** * Returns an output stream for writing to the transport connection. */ diff --git a/bramble-api/src/main/java/org/briarproject/bramble/api/plugin/duplex/AbstractDuplexTransportConnection.java b/bramble-api/src/main/java/org/briarproject/bramble/api/plugin/duplex/AbstractDuplexTransportConnection.java index 27aad596a..64a13f5e1 100644 --- a/bramble-api/src/main/java/org/briarproject/bramble/api/plugin/duplex/AbstractDuplexTransportConnection.java +++ b/bramble-api/src/main/java/org/briarproject/bramble/api/plugin/duplex/AbstractDuplexTransportConnection.java @@ -79,6 +79,11 @@ public abstract class AbstractDuplexTransportConnection return plugin.getMaxIdleTime(); } + @Override + public boolean isLossyAndCheap() { + return false; + } + @Override public OutputStream getOutputStream() throws IOException { return AbstractDuplexTransportConnection.this.getOutputStream(); diff --git a/bramble-api/src/main/java/org/briarproject/bramble/api/plugin/simplex/SimplexPlugin.java b/bramble-api/src/main/java/org/briarproject/bramble/api/plugin/simplex/SimplexPlugin.java index 9df61968d..f7cf1e801 100644 --- a/bramble-api/src/main/java/org/briarproject/bramble/api/plugin/simplex/SimplexPlugin.java +++ b/bramble-api/src/main/java/org/briarproject/bramble/api/plugin/simplex/SimplexPlugin.java @@ -15,6 +15,12 @@ import javax.annotation.Nullable; @NotNullByDefault public interface SimplexPlugin extends Plugin { + /** + * Returns true if the transport is likely to lose streams and the cost of + * transmitting redundant copies of data is cheap. + */ + boolean isLossyAndCheap(); + /** * Attempts to create and return a reader for the given transport * properties. Returns null if a reader cannot be created. diff --git a/bramble-api/src/main/java/org/briarproject/bramble/api/sync/SyncSessionFactory.java b/bramble-api/src/main/java/org/briarproject/bramble/api/sync/SyncSessionFactory.java index a19e211fb..e863089ff 100644 --- a/bramble-api/src/main/java/org/briarproject/bramble/api/sync/SyncSessionFactory.java +++ b/bramble-api/src/main/java/org/briarproject/bramble/api/sync/SyncSessionFactory.java @@ -16,7 +16,7 @@ public interface SyncSessionFactory { PriorityHandler handler); SyncSession createSimplexOutgoingSession(ContactId c, TransportId t, - int maxLatency, StreamWriter streamWriter); + int maxLatency, boolean eager, StreamWriter streamWriter); SyncSession createDuplexOutgoingSession(ContactId c, TransportId t, int maxLatency, int maxIdleTime, StreamWriter streamWriter, diff --git a/bramble-core/src/main/java/org/briarproject/bramble/connection/OutgoingSimplexSyncConnection.java b/bramble-core/src/main/java/org/briarproject/bramble/connection/OutgoingSimplexSyncConnection.java index a5ad6dfc8..9bec08193 100644 --- a/bramble-core/src/main/java/org/briarproject/bramble/connection/OutgoingSimplexSyncConnection.java +++ b/bramble-core/src/main/java/org/briarproject/bramble/connection/OutgoingSimplexSyncConnection.java @@ -71,8 +71,10 @@ class OutgoingSimplexSyncConnection extends SyncConnection implements Runnable { StreamWriter streamWriter = streamWriterFactory.createStreamWriter( w.getOutputStream(), ctx); ContactId c = requireNonNull(ctx.getContactId()); + // Use eager retransmission if the transport is lossy and cheap return syncSessionFactory.createSimplexOutgoingSession(c, - ctx.getTransportId(), w.getMaxLatency(), streamWriter); + ctx.getTransportId(), w.getMaxLatency(), w.isLossyAndCheap(), + streamWriter); } } 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 356186355..8e8d35488 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 @@ -92,6 +92,11 @@ abstract class AbstractRemovableDrivePlugin implements SimplexPlugin { throw new UnsupportedOperationException(); } + @Override + public boolean isLossyAndCheap() { + return true; + } + @Override public TransportConnectionReader createReader(TransportProperties p) { try { 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 index 2ab164784..c1d9c6748 100644 --- 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 @@ -36,6 +36,11 @@ class FileTransportWriter implements TransportConnectionWriter { return plugin.getMaxIdleTime(); } + @Override + public boolean isLossyAndCheap() { + return plugin.isLossyAndCheap(); + } + @Override public OutputStream getOutputStream() { return out; diff --git a/bramble-core/src/main/java/org/briarproject/bramble/plugin/file/RemovableDriveWriterTask.java b/bramble-core/src/main/java/org/briarproject/bramble/plugin/file/RemovableDriveWriterTask.java index 26f5c4969..563f306fa 100644 --- a/bramble-core/src/main/java/org/briarproject/bramble/plugin/file/RemovableDriveWriterTask.java +++ b/bramble-core/src/main/java/org/briarproject/bramble/plugin/file/RemovableDriveWriterTask.java @@ -106,6 +106,11 @@ class RemovableDriveWriterTask extends RemovableDriveTaskImpl return delegate.getMaxIdleTime(); } + @Override + public boolean isLossyAndCheap() { + return delegate.isLossyAndCheap(); + } + @Override public OutputStream getOutputStream() throws IOException { return delegate.getOutputStream(); diff --git a/bramble-core/src/main/java/org/briarproject/bramble/plugin/file/TransportOutputStreamWriter.java b/bramble-core/src/main/java/org/briarproject/bramble/plugin/file/TransportOutputStreamWriter.java index be40fbca0..db7ba5555 100644 --- a/bramble-core/src/main/java/org/briarproject/bramble/plugin/file/TransportOutputStreamWriter.java +++ b/bramble-core/src/main/java/org/briarproject/bramble/plugin/file/TransportOutputStreamWriter.java @@ -1,8 +1,8 @@ package org.briarproject.bramble.plugin.file; import org.briarproject.bramble.api.nullsafety.NotNullByDefault; -import org.briarproject.bramble.api.plugin.Plugin; import org.briarproject.bramble.api.plugin.TransportConnectionWriter; +import org.briarproject.bramble.api.plugin.simplex.SimplexPlugin; import java.io.OutputStream; import java.util.logging.Logger; @@ -17,10 +17,10 @@ class TransportOutputStreamWriter implements TransportConnectionWriter { private static final Logger LOG = getLogger(TransportOutputStreamWriter.class.getName()); - private final Plugin plugin; + private final SimplexPlugin plugin; private final OutputStream out; - TransportOutputStreamWriter(Plugin plugin, OutputStream out) { + TransportOutputStreamWriter(SimplexPlugin plugin, OutputStream out) { this.plugin = plugin; this.out = out; } @@ -35,6 +35,11 @@ class TransportOutputStreamWriter implements TransportConnectionWriter { return plugin.getMaxIdleTime(); } + @Override + public boolean isLossyAndCheap() { + return plugin.isLossyAndCheap(); + } + @Override public OutputStream getOutputStream() { return out; diff --git a/bramble-core/src/main/java/org/briarproject/bramble/sync/SyncSessionFactoryImpl.java b/bramble-core/src/main/java/org/briarproject/bramble/sync/SyncSessionFactoryImpl.java index 74ec3e51a..37fe1f9d1 100644 --- a/bramble-core/src/main/java/org/briarproject/bramble/sync/SyncSessionFactoryImpl.java +++ b/bramble-core/src/main/java/org/briarproject/bramble/sync/SyncSessionFactoryImpl.java @@ -60,12 +60,12 @@ class SyncSessionFactoryImpl implements SyncSessionFactory { @Override public SyncSession createSimplexOutgoingSession(ContactId c, TransportId t, - int maxLatency, StreamWriter streamWriter) { + int maxLatency, boolean eager, StreamWriter streamWriter) { OutputStream out = streamWriter.getOutputStream(); SyncRecordWriter recordWriter = recordWriterFactory.createRecordWriter(out); return new SimplexOutgoingSession(db, dbExecutor, eventBus, c, t, - maxLatency, false, streamWriter, recordWriter); + maxLatency, eager, streamWriter, recordWriter); } @Override diff --git a/bramble-core/src/test/java/org/briarproject/bramble/test/TestTransportConnectionWriter.java b/bramble-core/src/test/java/org/briarproject/bramble/test/TestTransportConnectionWriter.java index 238208207..bf382e8e5 100644 --- a/bramble-core/src/test/java/org/briarproject/bramble/test/TestTransportConnectionWriter.java +++ b/bramble-core/src/test/java/org/briarproject/bramble/test/TestTransportConnectionWriter.java @@ -35,6 +35,11 @@ public class TestTransportConnectionWriter return 60_000; } + @Override + public boolean isLossyAndCheap() { + return false; + } + @Override public OutputStream getOutputStream() { return out; From 32e9bf01ec29b2c6e1d9e641d202e229b02a133a Mon Sep 17 00:00:00 2001 From: akwizgran Date: Wed, 16 Jun 2021 11:35:29 +0100 Subject: [PATCH 41/77] Update DB method that gets total size of messages to send. --- .../bramble/api/db/DatabaseComponent.java | 20 +++---- .../org/briarproject/bramble/db/Database.java | 19 +++---- .../bramble/db/DatabaseComponentImpl.java | 18 +++--- .../briarproject/bramble/db/JdbcDatabase.java | 57 +++++++++---------- .../plugin/file/RemovableDriveWriterTask.java | 3 +- .../bramble/db/DatabaseComponentImplTest.java | 2 +- .../bramble/db/JdbcDatabaseTest.java | 30 +++++----- 7 files changed, 71 insertions(+), 78 deletions(-) diff --git a/bramble-api/src/main/java/org/briarproject/bramble/api/db/DatabaseComponent.java b/bramble-api/src/main/java/org/briarproject/bramble/api/db/DatabaseComponent.java index 53ed7416a..0d055641a 100644 --- a/bramble-api/src/main/java/org/briarproject/bramble/api/db/DatabaseComponent.java +++ b/bramble-api/src/main/java/org/briarproject/bramble/api/db/DatabaseComponent.java @@ -314,16 +314,6 @@ public interface DatabaseComponent extends TransactionManager { */ Message getMessage(Transaction txn, MessageId m) throws DbException; - /** - * Returns the total length, including headers, of any messages that are - * eligible to be sent to the given contact via a transport with the given - * max latency. - *

- * Read-only. - */ - long getMessageBytesToSend(Transaction txn, ContactId c, int maxLatency) - throws DbException; - /** * Returns the IDs of all delivered messages in the given group. *

@@ -469,6 +459,16 @@ public interface DatabaseComponent extends TransactionManager { Map getUnackedMessagesToSend(Transaction txn, ContactId c) throws DbException; + /** + * Returns the total length, including headers, of all messages that are + * eligible to be sent to the given contact. This may include messages + * that have already been sent and are not yet due for retransmission. + *

+ * Read-only. + */ + long getUnackedMessageBytesToSend(Transaction txn, ContactId c) + throws DbException; + /** * Returns the next time (in milliseconds since the Unix epoch) when a * message is due to be deleted, or {@link #NO_CLEANUP_DEADLINE} diff --git a/bramble-core/src/main/java/org/briarproject/bramble/db/Database.java b/bramble-core/src/main/java/org/briarproject/bramble/db/Database.java index cb386ab5c..64f54f19b 100644 --- a/bramble-core/src/main/java/org/briarproject/bramble/db/Database.java +++ b/bramble-core/src/main/java/org/briarproject/bramble/db/Database.java @@ -357,16 +357,6 @@ interface Database { */ Message getMessage(T txn, MessageId m) throws DbException; - /** - * Returns the total length, including headers, of any messages that are - * eligible to be sent to the given contact via a transport with the given - * max latency. - *

- * Read-only. - */ - long getMessageBytesToSend(T txn, ContactId c, int maxLatency) - throws DbException; - /** * Returns the IDs and states of all dependencies of the given message. * For missing dependencies and dependencies in other groups, the state @@ -518,6 +508,15 @@ interface Database { Map getUnackedMessagesToSend(T txn, ContactId c) throws DbException; + /** + * Returns the total length, including headers, of all messages that are + * eligible to be sent to the given contact. This may include messages + * that have already been sent and are not yet due for retransmission. + *

+ * Read-only. + */ + long getUnackedMessageBytesToSend(T txn, ContactId c) throws DbException; + /** * Returns the IDs of any messages that need to be validated. *

diff --git a/bramble-core/src/main/java/org/briarproject/bramble/db/DatabaseComponentImpl.java b/bramble-core/src/main/java/org/briarproject/bramble/db/DatabaseComponentImpl.java index d4c8ef682..c92245acd 100644 --- a/bramble-core/src/main/java/org/briarproject/bramble/db/DatabaseComponentImpl.java +++ b/bramble-core/src/main/java/org/briarproject/bramble/db/DatabaseComponentImpl.java @@ -608,15 +608,6 @@ class DatabaseComponentImpl implements DatabaseComponent { return db.getMessage(txn, m); } - @Override - public long getMessageBytesToSend(Transaction transaction, ContactId c, - int maxLatency) throws DbException { - T txn = unbox(transaction); - if (!db.containsContact(txn, c)) - throw new NoSuchContactException(); - return db.getMessageBytesToSend(txn, c, maxLatency); - } - @Override public Collection getMessageIds(Transaction transaction, GroupId g) throws DbException { @@ -750,6 +741,15 @@ class DatabaseComponentImpl implements DatabaseComponent { return db.getUnackedMessagesToSend(txn, c); } + @Override + public long getUnackedMessageBytesToSend(Transaction transaction, + ContactId c) throws DbException { + T txn = unbox(transaction); + if (!db.containsContact(txn, c)) + throw new NoSuchContactException(); + return db.getUnackedMessageBytesToSend(txn, c); + } + @Override public Map getMessageDependencies( Transaction transaction, MessageId m) throws DbException { diff --git a/bramble-core/src/main/java/org/briarproject/bramble/db/JdbcDatabase.java b/bramble-core/src/main/java/org/briarproject/bramble/db/JdbcDatabase.java index 32212193b..c77010b82 100644 --- a/bramble-core/src/main/java/org/briarproject/bramble/db/JdbcDatabase.java +++ b/bramble-core/src/main/java/org/briarproject/bramble/db/JdbcDatabase.java @@ -1788,37 +1788,6 @@ abstract class JdbcDatabase implements Database { } } - @Override - public long getMessageBytesToSend(Connection txn, ContactId c, - int maxLatency) throws DbException { - long now = clock.currentTimeMillis(); - long eta = now + maxLatency; - PreparedStatement ps = null; - ResultSet rs = null; - try { - String sql = "SELECT SUM(length) FROM statuses" - + " WHERE contactId = ? AND state = ?" - + " AND groupShared = TRUE AND messageShared = TRUE" - + " AND deleted = FALSE AND seen = FALSE" - + " AND (expiry <= ? OR eta > ?)"; - ps = txn.prepareStatement(sql); - ps.setInt(1, c.getInt()); - ps.setInt(2, DELIVERED.getValue()); - ps.setLong(3, now); - ps.setLong(4, eta); - rs = ps.executeQuery(); - rs.next(); - long total = rs.getInt(1); - rs.close(); - ps.close(); - return total; - } catch (SQLException e) { - tryToClose(rs, LOG, WARNING); - tryToClose(ps, LOG, WARNING); - throw new DbException(e); - } - } - @Override public Collection getMessageIds(Connection txn, GroupId g) throws DbException { @@ -2297,6 +2266,32 @@ abstract class JdbcDatabase implements Database { } } + @Override + public long getUnackedMessageBytesToSend(Connection txn, ContactId c) + throws DbException { + PreparedStatement ps = null; + ResultSet rs = null; + try { + String sql = "SELECT SUM(length) FROM statuses" + + " WHERE contactId = ? AND state = ?" + + " AND groupShared = TRUE AND messageShared = TRUE" + + " AND deleted = FALSE AND seen = FALSE"; + ps = txn.prepareStatement(sql); + ps.setInt(1, c.getInt()); + ps.setInt(2, DELIVERED.getValue()); + rs = ps.executeQuery(); + rs.next(); + long total = rs.getInt(1); + rs.close(); + ps.close(); + return total; + } catch (SQLException e) { + tryToClose(rs, LOG, WARNING); + tryToClose(ps, LOG, WARNING); + throw new DbException(e); + } + } + @Override public Collection getMessagesToValidate(Connection txn) throws DbException { diff --git a/bramble-core/src/main/java/org/briarproject/bramble/plugin/file/RemovableDriveWriterTask.java b/bramble-core/src/main/java/org/briarproject/bramble/plugin/file/RemovableDriveWriterTask.java index 563f306fa..1055f9de9 100644 --- a/bramble-core/src/main/java/org/briarproject/bramble/plugin/file/RemovableDriveWriterTask.java +++ b/bramble-core/src/main/java/org/briarproject/bramble/plugin/file/RemovableDriveWriterTask.java @@ -60,10 +60,9 @@ class RemovableDriveWriterTask extends RemovableDriveTaskImpl setSuccess(false); return; } - int maxLatency = plugin.getMaxLatency(); try { setTotal(db.transactionWithResult(true, txn -> - db.getMessageBytesToSend(txn, contactId, maxLatency))); + db.getUnackedMessageBytesToSend(txn, contactId))); } catch (DbException e) { logException(LOG, WARNING, e); registry.removeWriter(this); diff --git a/bramble-core/src/test/java/org/briarproject/bramble/db/DatabaseComponentImplTest.java b/bramble-core/src/test/java/org/briarproject/bramble/db/DatabaseComponentImplTest.java index 4b53a781d..a46f893ea 100644 --- a/bramble-core/src/test/java/org/briarproject/bramble/db/DatabaseComponentImplTest.java +++ b/bramble-core/src/test/java/org/briarproject/bramble/db/DatabaseComponentImplTest.java @@ -358,7 +358,7 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase { try { db.transaction(true, transaction -> - db.getMessageBytesToSend(transaction, contactId, 123)); + db.getUnackedMessageBytesToSend(transaction, contactId)); fail(); } catch (NoSuchContactException expected) { // Expected diff --git a/bramble-core/src/test/java/org/briarproject/bramble/db/JdbcDatabaseTest.java b/bramble-core/src/test/java/org/briarproject/bramble/db/JdbcDatabaseTest.java index 2da33c18d..db6b3411b 100644 --- a/bramble-core/src/test/java/org/briarproject/bramble/db/JdbcDatabaseTest.java +++ b/bramble-core/src/test/java/org/briarproject/bramble/db/JdbcDatabaseTest.java @@ -228,7 +228,7 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase { ids = db.getMessagesToOffer(txn, contactId, 100, MAX_LATENCY); assertEquals(singletonList(messageId), ids); assertEquals(message.getRawLength(), - db.getMessageBytesToSend(txn, contactId, MAX_LATENCY)); + db.getUnackedMessageBytesToSend(txn, contactId)); // Changing the status to seen = true should make the message unsendable db.raiseSeenFlag(txn, contactId, messageId); @@ -236,7 +236,7 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase { assertTrue(ids.isEmpty()); ids = db.getMessagesToOffer(txn, contactId, 100, MAX_LATENCY); assertTrue(ids.isEmpty()); - assertEquals(0, db.getMessageBytesToSend(txn, contactId, MAX_LATENCY)); + assertEquals(0, db.getUnackedMessageBytesToSend(txn, contactId)); db.commitTransaction(txn); db.close(); @@ -261,7 +261,7 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase { assertTrue(ids.isEmpty()); ids = db.getMessagesToOffer(txn, contactId, 100, MAX_LATENCY); assertTrue(ids.isEmpty()); - assertEquals(0, db.getMessageBytesToSend(txn, contactId, MAX_LATENCY)); + assertEquals(0, db.getUnackedMessageBytesToSend(txn, contactId)); // Marking the message delivered should make it sendable db.setMessageState(txn, messageId, DELIVERED); @@ -270,7 +270,7 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase { ids = db.getMessagesToOffer(txn, contactId, 100, MAX_LATENCY); assertEquals(singletonList(messageId), ids); assertEquals(message.getRawLength(), - db.getMessageBytesToSend(txn, contactId, MAX_LATENCY)); + db.getUnackedMessageBytesToSend(txn, contactId)); // Marking the message invalid should make it unsendable db.setMessageState(txn, messageId, INVALID); @@ -278,7 +278,7 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase { assertTrue(ids.isEmpty()); ids = db.getMessagesToOffer(txn, contactId, 100, MAX_LATENCY); assertTrue(ids.isEmpty()); - assertEquals(0, db.getMessageBytesToSend(txn, contactId, MAX_LATENCY)); + assertEquals(0, db.getUnackedMessageBytesToSend(txn, contactId)); // Marking the message pending should make it unsendable db.setMessageState(txn, messageId, PENDING); @@ -286,7 +286,7 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase { assertTrue(ids.isEmpty()); ids = db.getMessagesToOffer(txn, contactId, 100, MAX_LATENCY); assertTrue(ids.isEmpty()); - assertEquals(0, db.getMessageBytesToSend(txn, contactId, MAX_LATENCY)); + assertEquals(0, db.getUnackedMessageBytesToSend(txn, contactId)); db.commitTransaction(txn); db.close(); @@ -310,7 +310,7 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase { assertTrue(ids.isEmpty()); ids = db.getMessagesToOffer(txn, contactId, 100, MAX_LATENCY); assertTrue(ids.isEmpty()); - assertEquals(0, db.getMessageBytesToSend(txn, contactId, MAX_LATENCY)); + assertEquals(0, db.getUnackedMessageBytesToSend(txn, contactId)); // Making the group visible should not make the message sendable db.addGroupVisibility(txn, contactId, groupId, false); @@ -318,7 +318,7 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase { assertTrue(ids.isEmpty()); ids = db.getMessagesToOffer(txn, contactId, 100, MAX_LATENCY); assertTrue(ids.isEmpty()); - assertEquals(0, db.getMessageBytesToSend(txn, contactId, MAX_LATENCY)); + assertEquals(0, db.getUnackedMessageBytesToSend(txn, contactId)); // Sharing the group should make the message sendable db.setGroupVisibility(txn, contactId, groupId, true); @@ -327,7 +327,7 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase { ids = db.getMessagesToOffer(txn, contactId, 100, MAX_LATENCY); assertEquals(singletonList(messageId), ids); assertEquals(message.getRawLength(), - db.getMessageBytesToSend(txn, contactId, MAX_LATENCY)); + db.getUnackedMessageBytesToSend(txn, contactId)); // Unsharing the group should make the message unsendable db.setGroupVisibility(txn, contactId, groupId, false); @@ -335,7 +335,7 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase { assertTrue(ids.isEmpty()); ids = db.getMessagesToOffer(txn, contactId, 100, MAX_LATENCY); assertTrue(ids.isEmpty()); - assertEquals(0, db.getMessageBytesToSend(txn, contactId, MAX_LATENCY)); + assertEquals(0, db.getUnackedMessageBytesToSend(txn, contactId)); // Making the group invisible should make the message unsendable db.removeGroupVisibility(txn, contactId, groupId); @@ -343,7 +343,7 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase { assertTrue(ids.isEmpty()); ids = db.getMessagesToOffer(txn, contactId, 100, MAX_LATENCY); assertTrue(ids.isEmpty()); - assertEquals(0, db.getMessageBytesToSend(txn, contactId, MAX_LATENCY)); + assertEquals(0, db.getUnackedMessageBytesToSend(txn, contactId)); db.commitTransaction(txn); db.close(); @@ -368,7 +368,7 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase { assertTrue(ids.isEmpty()); ids = db.getMessagesToOffer(txn, contactId, 100, MAX_LATENCY); assertTrue(ids.isEmpty()); - assertEquals(0, db.getMessageBytesToSend(txn, contactId, MAX_LATENCY)); + assertEquals(0, db.getUnackedMessageBytesToSend(txn, contactId)); // Sharing the message should make it sendable db.setMessageShared(txn, messageId, true); @@ -377,7 +377,7 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase { ids = db.getMessagesToOffer(txn, contactId, 100, MAX_LATENCY); assertEquals(singletonList(messageId), ids); assertEquals(message.getRawLength(), - db.getMessageBytesToSend(txn, contactId, MAX_LATENCY)); + db.getUnackedMessageBytesToSend(txn, contactId)); db.commitTransaction(txn); db.close(); @@ -402,14 +402,14 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase { MAX_LATENCY); assertTrue(ids.isEmpty()); assertEquals(message.getRawLength(), - db.getMessageBytesToSend(txn, contactId, MAX_LATENCY)); + db.getUnackedMessageBytesToSend(txn, contactId)); // The message is just the right size to send ids = db.getMessagesToSend(txn, contactId, message.getRawLength(), MAX_LATENCY); assertEquals(singletonList(messageId), ids); assertEquals(message.getRawLength(), - db.getMessageBytesToSend(txn, contactId, MAX_LATENCY)); + db.getUnackedMessageBytesToSend(txn, contactId)); db.commitTransaction(txn); db.close(); From d5853e84036842e24b2c17c54ac01fe69bc9c5f7 Mon Sep 17 00:00:00 2001 From: akwizgran Date: Wed, 16 Jun 2021 12:26:54 +0100 Subject: [PATCH 42/77] Add integration test for eager retransmission. --- .../test/TestDuplexTransportConnection.java | 2 +- .../test/TestTransportConnectionWriter.java | 7 +- .../SimplexMessagingIntegrationTest.java | 64 ++++++++++++++++--- .../briar/test/BriarIntegrationTest.java | 4 +- 4 files changed, 62 insertions(+), 15 deletions(-) diff --git a/bramble-core/src/test/java/org/briarproject/bramble/test/TestDuplexTransportConnection.java b/bramble-core/src/test/java/org/briarproject/bramble/test/TestDuplexTransportConnection.java index 0ed6f9045..83ec8129e 100644 --- a/bramble-core/src/test/java/org/briarproject/bramble/test/TestDuplexTransportConnection.java +++ b/bramble-core/src/test/java/org/briarproject/bramble/test/TestDuplexTransportConnection.java @@ -25,7 +25,7 @@ public class TestDuplexTransportConnection @SuppressWarnings("WeakerAccess") public TestDuplexTransportConnection(InputStream in, OutputStream out) { reader = new TestTransportConnectionReader(in); - writer = new TestTransportConnectionWriter(out); + writer = new TestTransportConnectionWriter(out, false); } @Override diff --git a/bramble-core/src/test/java/org/briarproject/bramble/test/TestTransportConnectionWriter.java b/bramble-core/src/test/java/org/briarproject/bramble/test/TestTransportConnectionWriter.java index bf382e8e5..44e570fd7 100644 --- a/bramble-core/src/test/java/org/briarproject/bramble/test/TestTransportConnectionWriter.java +++ b/bramble-core/src/test/java/org/briarproject/bramble/test/TestTransportConnectionWriter.java @@ -15,10 +15,13 @@ public class TestTransportConnectionWriter implements TransportConnectionWriter { private final OutputStream out; + private final boolean lossyAndCheap; private final CountDownLatch disposed = new CountDownLatch(1); - public TestTransportConnectionWriter(OutputStream out) { + public TestTransportConnectionWriter(OutputStream out, + boolean lossyAndCheap) { this.out = out; + this.lossyAndCheap = lossyAndCheap; } public CountDownLatch getDisposedLatch() { @@ -37,7 +40,7 @@ public class TestTransportConnectionWriter @Override public boolean isLossyAndCheap() { - return false; + return lossyAndCheap; } @Override diff --git a/briar-core/src/test/java/org/briarproject/briar/messaging/SimplexMessagingIntegrationTest.java b/briar-core/src/test/java/org/briarproject/briar/messaging/SimplexMessagingIntegrationTest.java index 16e540e95..aedefb63a 100644 --- a/briar-core/src/test/java/org/briarproject/briar/messaging/SimplexMessagingIntegrationTest.java +++ b/briar-core/src/test/java/org/briarproject/briar/messaging/SimplexMessagingIntegrationTest.java @@ -11,7 +11,9 @@ import org.briarproject.bramble.api.identity.IdentityManager; import org.briarproject.bramble.api.lifecycle.LifecycleManager; import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.sync.GroupId; +import org.briarproject.bramble.api.sync.MessageId; import org.briarproject.bramble.api.sync.event.MessageStateChangedEvent; +import org.briarproject.bramble.api.sync.event.MessagesSentEvent; import org.briarproject.bramble.test.TestDatabaseConfigModule; import org.briarproject.bramble.test.TestTransportConnectionReader; import org.briarproject.bramble.test.TestTransportConnectionWriter; @@ -71,7 +73,16 @@ public class SimplexMessagingIntegrationTest extends BriarTestCase { } @Test - public void testWriteAndRead() throws Exception { + public void testWriteAndReadWithLazyRetransmission() throws Exception { + testWriteAndRead(false); + } + + @Test + public void testWriteAndReadWithEagerRetransmission() throws Exception { + testWriteAndRead(true); + } + + private void testWriteAndRead(boolean eager) throws Exception { // Create the identities Identity aliceIdentity = alice.getIdentityManager().createIdentity("Alice"); @@ -86,16 +97,20 @@ public class SimplexMessagingIntegrationTest extends BriarTestCase { bob.getEventBus().addListener(listener); // Alice sends a private message to Bob sendMessage(alice, bobId); - // Sync Alice's client versions and transport properties - read(bob, write(alice, bobId), 2); - // Sync Bob's client versions and transport properties - read(alice, write(bob, aliceId), 2); - // Sync the private message and the attachment - read(bob, write(alice, bobId), 2); + // Sync Alice's client versions + read(bob, write(alice, bobId, eager, 1), 1); + // Sync Bob's client versions + read(alice, write(bob, aliceId, eager, 1), 1); + // Sync Alice's client versions, the private message and the attachment + read(bob, write(alice, bobId, eager, 3), 3); // Bob should have received the private message assertTrue(listener.messageAdded); // Bob should have received the attachment assertTrue(listener.attachmentAdded); + // Sync messages from Alice to Bob again. If using eager + // retransmission, the three unacked messages should be sent again. + // They're all duplicates, so no further deliveries should occur + read(bob, write(alice, bobId, eager, eager ? 3 : 0), 0); } private ContactId setUp(SimplexMessagingIntegrationTestComponent device, @@ -149,15 +164,24 @@ public class SimplexMessagingIntegrationTest extends BriarTestCase { } private byte[] write(SimplexMessagingIntegrationTestComponent device, - ContactId contactId) throws Exception { + ContactId contactId, boolean eager, int transmissions) + throws Exception { + // Listen for message transmissions + MessageTransmissionListener listener = + new MessageTransmissionListener(transmissions); + device.getEventBus().addListener(listener); // Write the outgoing stream ByteArrayOutputStream out = new ByteArrayOutputStream(); TestTransportConnectionWriter writer = - new TestTransportConnectionWriter(out); + new TestTransportConnectionWriter(out, eager); device.getConnectionManager().manageOutgoingConnection(contactId, SIMPLEX_TRANSPORT_ID, writer); // Wait for the writer to be disposed writer.getDisposedLatch().await(TIMEOUT_MS, MILLISECONDS); + // Check that the expected number of messages were sent + assertTrue(listener.sent.await(TIMEOUT_MS, MILLISECONDS)); + // Clean up the listener + device.getEventBus().removeListener(listener); // Return the contents of the stream return out.toByteArray(); } @@ -178,6 +202,24 @@ public class SimplexMessagingIntegrationTest extends BriarTestCase { deleteTestDirectory(testDir); } + @NotNullByDefault + private static class MessageTransmissionListener implements EventListener { + + private final CountDownLatch sent; + + private MessageTransmissionListener(int transmissions) { + sent = new CountDownLatch(transmissions); + } + + @Override + public void eventOccurred(Event e) { + if (e instanceof MessagesSentEvent) { + MessagesSentEvent m = (MessagesSentEvent) e; + for (MessageId ignored : m.getMessageIds()) sent.countDown(); + } + } + } + @NotNullByDefault private static class MessageDeliveryListener implements EventListener { @@ -191,7 +233,9 @@ public class SimplexMessagingIntegrationTest extends BriarTestCase { public void eventOccurred(Event e) { if (e instanceof MessageStateChangedEvent) { MessageStateChangedEvent m = (MessageStateChangedEvent) e; - if (m.getState().equals(DELIVERED)) delivered.countDown(); + if (!m.isLocal() && m.getState().equals(DELIVERED)) { + delivered.countDown(); + } } } } diff --git a/briar-core/src/test/java/org/briarproject/briar/test/BriarIntegrationTest.java b/briar-core/src/test/java/org/briarproject/briar/test/BriarIntegrationTest.java index 0bcdab2a9..e493b4fa5 100644 --- a/briar-core/src/test/java/org/briarproject/briar/test/BriarIntegrationTest.java +++ b/briar-core/src/test/java/org/briarproject/briar/test/BriarIntegrationTest.java @@ -429,7 +429,7 @@ public abstract class BriarIntegrationTest Date: Wed, 16 Jun 2021 16:25:11 +0100 Subject: [PATCH 43/77] Add DB method for checking whether there's anything to send --- .../bramble/api/db/DatabaseComponent.java | 12 + .../org/briarproject/bramble/db/Database.java | 12 + .../bramble/db/DatabaseComponentImpl.java | 9 + .../briarproject/bramble/db/JdbcDatabase.java | 49 +++++ .../bramble/db/JdbcDatabaseTest.java | 206 +++++++++--------- 5 files changed, 191 insertions(+), 97 deletions(-) diff --git a/bramble-api/src/main/java/org/briarproject/bramble/api/db/DatabaseComponent.java b/bramble-api/src/main/java/org/briarproject/bramble/api/db/DatabaseComponent.java index 0d055641a..f8e9d5a69 100644 --- a/bramble-api/src/main/java/org/briarproject/bramble/api/db/DatabaseComponent.java +++ b/bramble-api/src/main/java/org/briarproject/bramble/api/db/DatabaseComponent.java @@ -118,6 +118,18 @@ public interface DatabaseComponent extends TransactionManager { KeySetId addTransportKeys(Transaction txn, PendingContactId p, TransportKeys k) throws DbException; + /** + * Returns true if there are any acks or messages to send to the given + * contact over a transport with the given maximum latency. + *

+ * Read-only. + * + * @param eager True if messages that are not yet due for retransmission + * should be included + */ + boolean containsAnythingToSend(Transaction txn, ContactId c, + int maxLatency, boolean eager) throws DbException; + /** * Returns true if the database contains the given contact for the given * local pseudonym. diff --git a/bramble-core/src/main/java/org/briarproject/bramble/db/Database.java b/bramble-core/src/main/java/org/briarproject/bramble/db/Database.java index 64f54f19b..20afbb374 100644 --- a/bramble-core/src/main/java/org/briarproject/bramble/db/Database.java +++ b/bramble-core/src/main/java/org/briarproject/bramble/db/Database.java @@ -162,6 +162,18 @@ interface Database { KeySetId addTransportKeys(T txn, PendingContactId p, TransportKeys k) throws DbException; + /** + * Returns true if there are any acks or messages to send to the given + * contact over a transport with the given maximum latency. + *

+ * Read-only. + * + * @param eager True if messages that are not yet due for retransmission + * should be included + */ + boolean containsAnythingToSend(T txn, ContactId c, int maxLatency, + boolean eager) throws DbException; + /** * Returns true if the database contains the given contact for the given * local pseudonym. diff --git a/bramble-core/src/main/java/org/briarproject/bramble/db/DatabaseComponentImpl.java b/bramble-core/src/main/java/org/briarproject/bramble/db/DatabaseComponentImpl.java index c92245acd..9030e2c60 100644 --- a/bramble-core/src/main/java/org/briarproject/bramble/db/DatabaseComponentImpl.java +++ b/bramble-core/src/main/java/org/briarproject/bramble/db/DatabaseComponentImpl.java @@ -341,6 +341,15 @@ class DatabaseComponentImpl implements DatabaseComponent { return db.addTransportKeys(txn, p, k); } + @Override + public boolean containsAnythingToSend(Transaction transaction, ContactId c, + int maxLatency, boolean eager) throws DbException { + T txn = unbox(transaction); + if (!db.containsContact(txn, c)) + throw new NoSuchContactException(); + return db.containsAnythingToSend(txn, c, maxLatency, eager); + } + @Override public boolean containsContact(Transaction transaction, AuthorId remote, AuthorId local) throws DbException { diff --git a/bramble-core/src/main/java/org/briarproject/bramble/db/JdbcDatabase.java b/bramble-core/src/main/java/org/briarproject/bramble/db/JdbcDatabase.java index c77010b82..a9b42e235 100644 --- a/bramble-core/src/main/java/org/briarproject/bramble/db/JdbcDatabase.java +++ b/bramble-core/src/main/java/org/briarproject/bramble/db/JdbcDatabase.java @@ -1127,6 +1127,55 @@ abstract class JdbcDatabase implements Database { } } + @Override + public boolean containsAnythingToSend(Connection txn, ContactId c, + int maxLatency, boolean eager) throws DbException { + PreparedStatement ps = null; + ResultSet rs = null; + try { + String sql = "SELECT NULL FROM statuses" + + " WHERE contactId = ? AND ack = TRUE"; + ps = txn.prepareStatement(sql); + ps.setInt(1, c.getInt()); + rs = ps.executeQuery(); + boolean acksToSend = rs.next(); + rs.close(); + ps.close(); + if (acksToSend) return true; + if (eager) { + sql = "SELECT NULL from statuses" + + " WHERE contactId = ? AND state = ?" + + " AND groupShared = TRUE AND messageShared = TRUE" + + " AND deleted = FALSE AND seen = FALSE"; + ps = txn.prepareStatement(sql); + ps.setInt(1, c.getInt()); + ps.setInt(2, DELIVERED.getValue()); + } else { + long now = clock.currentTimeMillis(); + long eta = now + maxLatency; + sql = "SELECT NULL FROM statuses" + + " WHERE contactId = ? AND state = ?" + + " AND groupShared = TRUE AND messageShared = TRUE" + + " AND deleted = FALSE AND seen = FALSE" + + " AND (expiry <= ? OR eta > ?)"; + ps = txn.prepareStatement(sql); + ps.setInt(1, c.getInt()); + ps.setInt(2, DELIVERED.getValue()); + ps.setLong(3, now); + ps.setLong(4, eta); + } + rs = ps.executeQuery(); + boolean messagesToSend = rs.next(); + rs.close(); + ps.close(); + return messagesToSend; + } catch (SQLException e) { + tryToClose(rs, LOG, WARNING); + tryToClose(ps, LOG, WARNING); + throw new DbException(e); + } + } + @Override public boolean containsContact(Connection txn, AuthorId remote, AuthorId local) throws DbException { diff --git a/bramble-core/src/test/java/org/briarproject/bramble/db/JdbcDatabaseTest.java b/bramble-core/src/test/java/org/briarproject/bramble/db/JdbcDatabaseTest.java index db6b3411b..649b09369 100644 --- a/bramble-core/src/test/java/org/briarproject/bramble/db/JdbcDatabaseTest.java +++ b/bramble-core/src/test/java/org/briarproject/bramble/db/JdbcDatabaseTest.java @@ -57,6 +57,7 @@ import java.util.concurrent.atomic.AtomicLong; import static java.util.Arrays.asList; import static java.util.Collections.emptyList; +import static java.util.Collections.singleton; import static java.util.Collections.singletonList; import static java.util.Collections.singletonMap; import static java.util.concurrent.TimeUnit.SECONDS; @@ -222,21 +223,13 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase { db.addMessage(txn, message, DELIVERED, true, false, null); // The contact has not seen the message, so it should be sendable - Collection ids = - db.getMessagesToSend(txn, contactId, ONE_MEGABYTE, MAX_LATENCY); - assertEquals(singletonList(messageId), ids); - ids = db.getMessagesToOffer(txn, contactId, 100, MAX_LATENCY); - assertEquals(singletonList(messageId), ids); - assertEquals(message.getRawLength(), - db.getUnackedMessageBytesToSend(txn, contactId)); + assertOneMessageToSendEagerly(db, txn); + assertOneMessageToSendLazily(db, txn); // Changing the status to seen = true should make the message unsendable db.raiseSeenFlag(txn, contactId, messageId); - ids = db.getMessagesToSend(txn, contactId, ONE_MEGABYTE, MAX_LATENCY); - assertTrue(ids.isEmpty()); - ids = db.getMessagesToOffer(txn, contactId, 100, MAX_LATENCY); - assertTrue(ids.isEmpty()); - assertEquals(0, db.getUnackedMessageBytesToSend(txn, contactId)); + assertNothingToSendEagerly(db, txn); + assertNothingToSendLazily(db, txn); db.commitTransaction(txn); db.close(); @@ -256,37 +249,23 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase { db.addMessage(txn, message, UNKNOWN, true, false, null); // The message has not been validated, so it should not be sendable - Collection ids = db.getMessagesToSend(txn, contactId, - ONE_MEGABYTE, MAX_LATENCY); - assertTrue(ids.isEmpty()); - ids = db.getMessagesToOffer(txn, contactId, 100, MAX_LATENCY); - assertTrue(ids.isEmpty()); - assertEquals(0, db.getUnackedMessageBytesToSend(txn, contactId)); + assertNothingToSendLazily(db, txn); + assertNothingToSendEagerly(db, txn); // Marking the message delivered should make it sendable db.setMessageState(txn, messageId, DELIVERED); - ids = db.getMessagesToSend(txn, contactId, ONE_MEGABYTE, MAX_LATENCY); - assertEquals(singletonList(messageId), ids); - ids = db.getMessagesToOffer(txn, contactId, 100, MAX_LATENCY); - assertEquals(singletonList(messageId), ids); - assertEquals(message.getRawLength(), - db.getUnackedMessageBytesToSend(txn, contactId)); + assertOneMessageToSendLazily(db, txn); + assertOneMessageToSendEagerly(db, txn); // Marking the message invalid should make it unsendable db.setMessageState(txn, messageId, INVALID); - ids = db.getMessagesToSend(txn, contactId, ONE_MEGABYTE, MAX_LATENCY); - assertTrue(ids.isEmpty()); - ids = db.getMessagesToOffer(txn, contactId, 100, MAX_LATENCY); - assertTrue(ids.isEmpty()); - assertEquals(0, db.getUnackedMessageBytesToSend(txn, contactId)); + assertNothingToSendLazily(db, txn); + assertNothingToSendEagerly(db, txn); // Marking the message pending should make it unsendable db.setMessageState(txn, messageId, PENDING); - ids = db.getMessagesToSend(txn, contactId, ONE_MEGABYTE, MAX_LATENCY); - assertTrue(ids.isEmpty()); - ids = db.getMessagesToOffer(txn, contactId, 100, MAX_LATENCY); - assertTrue(ids.isEmpty()); - assertEquals(0, db.getUnackedMessageBytesToSend(txn, contactId)); + assertNothingToSendLazily(db, txn); + assertNothingToSendEagerly(db, txn); db.commitTransaction(txn); db.close(); @@ -305,45 +284,28 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase { db.addMessage(txn, message, DELIVERED, true, false, null); // The group is invisible, so the message should not be sendable - Collection ids = db.getMessagesToSend(txn, contactId, - ONE_MEGABYTE, MAX_LATENCY); - assertTrue(ids.isEmpty()); - ids = db.getMessagesToOffer(txn, contactId, 100, MAX_LATENCY); - assertTrue(ids.isEmpty()); - assertEquals(0, db.getUnackedMessageBytesToSend(txn, contactId)); + assertNothingToSendLazily(db, txn); + assertNothingToSendEagerly(db, txn); // Making the group visible should not make the message sendable db.addGroupVisibility(txn, contactId, groupId, false); - ids = db.getMessagesToSend(txn, contactId, ONE_MEGABYTE, MAX_LATENCY); - assertTrue(ids.isEmpty()); - ids = db.getMessagesToOffer(txn, contactId, 100, MAX_LATENCY); - assertTrue(ids.isEmpty()); - assertEquals(0, db.getUnackedMessageBytesToSend(txn, contactId)); + assertNothingToSendLazily(db, txn); + assertNothingToSendEagerly(db, txn); // Sharing the group should make the message sendable db.setGroupVisibility(txn, contactId, groupId, true); - ids = db.getMessagesToSend(txn, contactId, ONE_MEGABYTE, MAX_LATENCY); - assertEquals(singletonList(messageId), ids); - ids = db.getMessagesToOffer(txn, contactId, 100, MAX_LATENCY); - assertEquals(singletonList(messageId), ids); - assertEquals(message.getRawLength(), - db.getUnackedMessageBytesToSend(txn, contactId)); + assertOneMessageToSendEagerly(db, txn); + assertOneMessageToSendLazily(db, txn); // Unsharing the group should make the message unsendable db.setGroupVisibility(txn, contactId, groupId, false); - ids = db.getMessagesToSend(txn, contactId, ONE_MEGABYTE, MAX_LATENCY); - assertTrue(ids.isEmpty()); - ids = db.getMessagesToOffer(txn, contactId, 100, MAX_LATENCY); - assertTrue(ids.isEmpty()); - assertEquals(0, db.getUnackedMessageBytesToSend(txn, contactId)); + assertNothingToSendLazily(db, txn); + assertNothingToSendEagerly(db, txn); // Making the group invisible should make the message unsendable db.removeGroupVisibility(txn, contactId, groupId); - ids = db.getMessagesToSend(txn, contactId, ONE_MEGABYTE, MAX_LATENCY); - assertTrue(ids.isEmpty()); - ids = db.getMessagesToOffer(txn, contactId, 100, MAX_LATENCY); - assertTrue(ids.isEmpty()); - assertEquals(0, db.getUnackedMessageBytesToSend(txn, contactId)); + assertNothingToSendLazily(db, txn); + assertNothingToSendEagerly(db, txn); db.commitTransaction(txn); db.close(); @@ -363,21 +325,13 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase { db.addMessage(txn, message, DELIVERED, false, false, null); // The message is not shared, so it should not be sendable - Collection ids = db.getMessagesToSend(txn, contactId, - ONE_MEGABYTE, MAX_LATENCY); - assertTrue(ids.isEmpty()); - ids = db.getMessagesToOffer(txn, contactId, 100, MAX_LATENCY); - assertTrue(ids.isEmpty()); - assertEquals(0, db.getUnackedMessageBytesToSend(txn, contactId)); + assertNothingToSendLazily(db, txn); + assertNothingToSendEagerly(db, txn); // Sharing the message should make it sendable db.setMessageShared(txn, messageId, true); - ids = db.getMessagesToSend(txn, contactId, ONE_MEGABYTE, MAX_LATENCY); - assertEquals(singletonList(messageId), ids); - ids = db.getMessagesToOffer(txn, contactId, 100, MAX_LATENCY); - assertEquals(singletonList(messageId), ids); - assertEquals(message.getRawLength(), - db.getUnackedMessageBytesToSend(txn, contactId)); + assertOneMessageToSendLazily(db, txn); + assertOneMessageToSendEagerly(db, txn); db.commitTransaction(txn); db.close(); @@ -397,19 +351,17 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase { db.addMessage(txn, message, DELIVERED, true, false, null); // The message is sendable, but too large to send + assertOneMessageToSendLazily(db, txn); + assertOneMessageToSendEagerly(db, txn); Collection ids = db.getMessagesToSend(txn, contactId, message.getRawLength() - 1, MAX_LATENCY); assertTrue(ids.isEmpty()); - assertEquals(message.getRawLength(), - db.getUnackedMessageBytesToSend(txn, contactId)); // The message is just the right size to send ids = db.getMessagesToSend(txn, contactId, message.getRawLength(), MAX_LATENCY); assertEquals(singletonList(messageId), ids); - assertEquals(message.getRawLength(), - db.getUnackedMessageBytesToSend(txn, contactId)); db.commitTransaction(txn); db.close(); @@ -427,6 +379,12 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase { db.addGroup(txn, group); db.addGroupVisibility(txn, contactId, groupId, false); + // Initially there should be nothing to send + assertFalse( + db.containsAnythingToSend(txn, contactId, MAX_LATENCY, false)); + assertFalse( + db.containsAnythingToSend(txn, contactId, MAX_LATENCY, true)); + // Add some messages to ack Message message1 = getMessage(groupId); MessageId messageId1 = message1.getId(); @@ -434,6 +392,10 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase { db.addMessage(txn, message1, DELIVERED, true, false, contactId); // Both message IDs should be returned + assertTrue( + db.containsAnythingToSend(txn, contactId, MAX_LATENCY, false)); + assertTrue( + db.containsAnythingToSend(txn, contactId, MAX_LATENCY, true)); Collection ids = db.getMessagesToAck(txn, contactId, 1234); assertEquals(asList(messageId, messageId1), ids); @@ -441,6 +403,10 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase { db.lowerAckFlag(txn, contactId, asList(messageId, messageId1)); // Both message IDs should have been removed + assertFalse( + db.containsAnythingToSend(txn, contactId, MAX_LATENCY, false)); + assertFalse( + db.containsAnythingToSend(txn, contactId, MAX_LATENCY, true)); assertEquals(emptyList(), db.getMessagesToAck(txn, contactId, 1234)); @@ -449,6 +415,10 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase { db.raiseAckFlag(txn, contactId, messageId1); // Both message IDs should be returned + assertTrue( + db.containsAnythingToSend(txn, contactId, MAX_LATENCY, false)); + assertTrue( + db.containsAnythingToSend(txn, contactId, MAX_LATENCY, true)); ids = db.getMessagesToAck(txn, contactId, 1234); assertEquals(asList(messageId, messageId1), ids); @@ -469,22 +439,25 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase { db.addGroupVisibility(txn, contactId, groupId, true); db.addMessage(txn, message, DELIVERED, true, false, null); - // Retrieve the message from the database and mark it as sent - Collection ids = db.getMessagesToSend(txn, contactId, - ONE_MEGABYTE, MAX_LATENCY); - assertEquals(singletonList(messageId), ids); + // The message should be sendable via lazy or eager retransmission + assertOneMessageToSendLazily(db, txn); + assertOneMessageToSendEagerly(db, txn); + + // Mark the message as sent db.updateExpiryTimeAndEta(txn, contactId, messageId, MAX_LATENCY); - // The message should no longer be sendable - ids = db.getMessagesToSend(txn, contactId, ONE_MEGABYTE, MAX_LATENCY); - assertTrue(ids.isEmpty()); + // The message should no longer be sendable via lazy retransmission, + // but it should still be sendable via eager retransmission + assertNothingToSendLazily(db, txn); + assertOneMessageToSendEagerly(db, txn); - // Pretend that the message was acked + // Mark the message as acked db.raiseSeenFlag(txn, contactId, messageId); - // The message still should not be sendable - ids = db.getMessagesToSend(txn, contactId, ONE_MEGABYTE, MAX_LATENCY); - assertTrue(ids.isEmpty()); + // The message still should not be sendable via lazy or eager + // retransmission + assertNothingToSendLazily(db, txn); + assertNothingToSendEagerly(db, txn); db.commitTransaction(txn); db.close(); @@ -1957,11 +1930,8 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase { assertTrue(db.containsVisibleMessage(txn, contactId, messageId)); // The message should be sendable - Collection ids = db.getMessagesToSend(txn, contactId, - ONE_MEGABYTE, MAX_LATENCY); - assertEquals(singletonList(messageId), ids); - ids = db.getMessagesToOffer(txn, contactId, 100, MAX_LATENCY); - assertEquals(singletonList(messageId), ids); + assertOneMessageToSendLazily(db, txn); + assertOneMessageToSendEagerly(db, txn); // The message should be available Message m = db.getMessage(txn, messageId); @@ -1977,10 +1947,8 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase { assertTrue(db.containsVisibleMessage(txn, contactId, messageId)); // The message should not be sendable - ids = db.getMessagesToSend(txn, contactId, ONE_MEGABYTE, MAX_LATENCY); - assertTrue(ids.isEmpty()); - ids = db.getMessagesToOffer(txn, contactId, 100, MAX_LATENCY); - assertTrue(ids.isEmpty()); + assertNothingToSendLazily(db, txn); + assertNothingToSendEagerly(db, txn); // Requesting the message should throw an exception try { @@ -2609,6 +2577,50 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase { deleteTestDirectory(testDir); } + private void assertNothingToSendLazily(Database db, + Connection txn) throws Exception { + assertFalse( + db.containsAnythingToSend(txn, contactId, MAX_LATENCY, false)); + Collection ids = + db.getMessagesToSend(txn, contactId, ONE_MEGABYTE, MAX_LATENCY); + assertTrue(ids.isEmpty()); + ids = db.getMessagesToOffer(txn, contactId, 100, MAX_LATENCY); + assertTrue(ids.isEmpty()); + } + + private void assertOneMessageToSendLazily(Database db, + Connection txn) throws Exception { + assertTrue( + db.containsAnythingToSend(txn, contactId, MAX_LATENCY, false)); + Collection ids = + db.getMessagesToSend(txn, contactId, ONE_MEGABYTE, MAX_LATENCY); + assertEquals(singletonList(messageId), ids); + ids = db.getMessagesToOffer(txn, contactId, 100, MAX_LATENCY); + assertEquals(singletonList(messageId), ids); + } + + private void assertNothingToSendEagerly(Database db, + Connection txn) throws Exception { + assertFalse( + db.containsAnythingToSend(txn, contactId, MAX_LATENCY, true)); + Map unacked = + db.getUnackedMessagesToSend(txn, contactId); + assertTrue(unacked.isEmpty()); + assertEquals(0, db.getUnackedMessageBytesToSend(txn, contactId)); + } + + private void assertOneMessageToSendEagerly(Database db, + Connection txn) throws Exception { + assertTrue( + db.containsAnythingToSend(txn, contactId, MAX_LATENCY, true)); + Map unacked = + db.getUnackedMessagesToSend(txn, contactId); + assertEquals(singleton(messageId), unacked.keySet()); + assertEquals(message.getRawLength(), unacked.get(messageId).intValue()); + assertEquals(message.getRawLength(), + db.getUnackedMessageBytesToSend(txn, contactId)); + } + private static class StoppedClock implements Clock { private final long time; From 0bf59eec2091bebeaecffb0e8de9a0276c8b6a89 Mon Sep 17 00:00:00 2001 From: akwizgran Date: Wed, 16 Jun 2021 16:26:29 +0100 Subject: [PATCH 44/77] Add comment explaining second client versioning message. --- .../briar/messaging/SimplexMessagingIntegrationTest.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/briar-core/src/test/java/org/briarproject/briar/messaging/SimplexMessagingIntegrationTest.java b/briar-core/src/test/java/org/briarproject/briar/messaging/SimplexMessagingIntegrationTest.java index aedefb63a..2ac9fff1b 100644 --- a/briar-core/src/test/java/org/briarproject/briar/messaging/SimplexMessagingIntegrationTest.java +++ b/briar-core/src/test/java/org/briarproject/briar/messaging/SimplexMessagingIntegrationTest.java @@ -101,7 +101,8 @@ public class SimplexMessagingIntegrationTest extends BriarTestCase { read(bob, write(alice, bobId, eager, 1), 1); // Sync Bob's client versions read(alice, write(bob, aliceId, eager, 1), 1); - // Sync Alice's client versions, the private message and the attachment + // Sync Alice's second client versioning update (with the active flag + // raised), the private message and the attachment read(bob, write(alice, bobId, eager, 3), 3); // Bob should have received the private message assertTrue(listener.messageAdded); From 6b976df6a81bcee56a21032610eb2823847dbb62 Mon Sep 17 00:00:00 2001 From: akwizgran Date: Thu, 17 Jun 2021 13:01:33 +0100 Subject: [PATCH 45/77] Add RemovableDriveManager method. --- .../api/plugin/file/RemovableDriveManager.java | 6 ++++++ .../plugin/file/RemovableDriveManagerImpl.java | 14 +++++++++++++- .../plugin/file/RemovableDrivePluginFactory.java | 2 +- 3 files changed, 20 insertions(+), 2 deletions(-) diff --git a/bramble-api/src/main/java/org/briarproject/bramble/api/plugin/file/RemovableDriveManager.java b/bramble-api/src/main/java/org/briarproject/bramble/api/plugin/file/RemovableDriveManager.java index cc14a08bb..8c74497b0 100644 --- a/bramble-api/src/main/java/org/briarproject/bramble/api/plugin/file/RemovableDriveManager.java +++ b/bramble-api/src/main/java/org/briarproject/bramble/api/plugin/file/RemovableDriveManager.java @@ -1,6 +1,7 @@ package org.briarproject.bramble.api.plugin.file; 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.properties.TransportProperties; @@ -37,4 +38,9 @@ public interface RemovableDriveManager { * ignored. */ RemovableDriveTask startWriterTask(ContactId c, TransportProperties p); + + /** + * Returns true if there is anything to send to the given contact. + */ + boolean isWriterTaskNeeded(ContactId c) throws DbException; } diff --git a/bramble-core/src/main/java/org/briarproject/bramble/plugin/file/RemovableDriveManagerImpl.java b/bramble-core/src/main/java/org/briarproject/bramble/plugin/file/RemovableDriveManagerImpl.java index 78490a045..2c96d5d53 100644 --- a/bramble-core/src/main/java/org/briarproject/bramble/plugin/file/RemovableDriveManagerImpl.java +++ b/bramble-core/src/main/java/org/briarproject/bramble/plugin/file/RemovableDriveManagerImpl.java @@ -1,6 +1,8 @@ package org.briarproject.bramble.plugin.file; import org.briarproject.bramble.api.contact.ContactId; +import org.briarproject.bramble.api.db.DatabaseComponent; +import org.briarproject.bramble.api.db.DbException; import org.briarproject.bramble.api.lifecycle.IoExecutor; import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.plugin.file.RemovableDriveManager; @@ -14,12 +16,15 @@ import javax.annotation.concurrent.GuardedBy; import javax.annotation.concurrent.ThreadSafe; import javax.inject.Inject; +import static org.briarproject.bramble.plugin.file.RemovableDrivePluginFactory.MAX_LATENCY; + @ThreadSafe @NotNullByDefault class RemovableDriveManagerImpl implements RemovableDriveManager, RemovableDriveTaskRegistry { private final Executor ioExecutor; + private final DatabaseComponent db; private final RemovableDriveTaskFactory taskFactory; private final Object lock = new Object(); @@ -30,8 +35,9 @@ class RemovableDriveManagerImpl @Inject RemovableDriveManagerImpl(@IoExecutor Executor ioExecutor, - RemovableDriveTaskFactory taskFactory) { + DatabaseComponent db, RemovableDriveTaskFactory taskFactory) { this.ioExecutor = ioExecutor; + this.db = db; this.taskFactory = taskFactory; } @@ -74,6 +80,12 @@ class RemovableDriveManagerImpl return created; } + @Override + public boolean isWriterTaskNeeded(ContactId c) throws DbException { + return db.transactionWithResult(true, txn -> + db.containsAnythingToSend(txn, c, MAX_LATENCY, true)); + } + @Override public void removeReader(RemovableDriveTask task) { synchronized (lock) { diff --git a/bramble-core/src/main/java/org/briarproject/bramble/plugin/file/RemovableDrivePluginFactory.java b/bramble-core/src/main/java/org/briarproject/bramble/plugin/file/RemovableDrivePluginFactory.java index 6f1ad7564..d0bc374ed 100644 --- a/bramble-core/src/main/java/org/briarproject/bramble/plugin/file/RemovableDrivePluginFactory.java +++ b/bramble-core/src/main/java/org/briarproject/bramble/plugin/file/RemovableDrivePluginFactory.java @@ -17,7 +17,7 @@ import static org.briarproject.bramble.api.plugin.file.RemovableDriveConstants.I @NotNullByDefault public class RemovableDrivePluginFactory implements SimplexPluginFactory { - private static final int MAX_LATENCY = (int) DAYS.toMillis(14); + static final int MAX_LATENCY = (int) DAYS.toMillis(14); @Inject RemovableDrivePluginFactory() { From ce74fcaab5a4056416c41fb7e077bb3eb4dd8ae1 Mon Sep 17 00:00:00 2001 From: akwizgran Date: Mon, 21 Jun 2021 16:22:51 +0100 Subject: [PATCH 46/77] Store ID of message that triggered abort. --- .../IntroduceeProtocolEngine.java | 45 +++++----- .../IntroducerProtocolEngine.java | 84 +++++++++++-------- 2 files changed, 72 insertions(+), 57 deletions(-) diff --git a/briar-core/src/main/java/org/briarproject/briar/introduction/IntroduceeProtocolEngine.java b/briar-core/src/main/java/org/briarproject/briar/introduction/IntroduceeProtocolEngine.java index 7f55f1a2a..41a3d71af 100644 --- a/briar-core/src/main/java/org/briarproject/briar/introduction/IntroduceeProtocolEngine.java +++ b/briar-core/src/main/java/org/briarproject/briar/introduction/IntroduceeProtocolEngine.java @@ -154,7 +154,8 @@ class IntroduceeProtocolEngine case REMOTE_ACCEPTED: case AWAIT_AUTH: case AWAIT_ACTIVATE: - return abort(txn, session); // Invalid in these states + // Invalid in these states + return abort(txn, session, m.getMessageId()); default: throw new AssertionError(); } @@ -174,7 +175,8 @@ class IntroduceeProtocolEngine case REMOTE_ACCEPTED: case AWAIT_AUTH: case AWAIT_ACTIVATE: - return abort(txn, session); // Invalid in these states + // Invalid in these states + return abort(txn, session, m.getMessageId()); default: throw new AssertionError(); } @@ -194,7 +196,8 @@ class IntroduceeProtocolEngine case REMOTE_ACCEPTED: case AWAIT_AUTH: case AWAIT_ACTIVATE: - return abort(txn, session); // Invalid in these states + // Invalid in these states + return abort(txn, session, m.getMessageId()); default: throw new AssertionError(); } @@ -213,7 +216,8 @@ class IntroduceeProtocolEngine case LOCAL_ACCEPTED: case REMOTE_ACCEPTED: case AWAIT_ACTIVATE: - return abort(txn, session); // Invalid in these states + // Invalid in these states + return abort(txn, session, m.getMessageId()); default: throw new AssertionError(); } @@ -232,7 +236,8 @@ class IntroduceeProtocolEngine case LOCAL_ACCEPTED: case REMOTE_ACCEPTED: case AWAIT_AUTH: - return abort(txn, session); // Invalid in these states + // Invalid in these states + return abort(txn, session, m.getMessageId()); default: throw new AssertionError(); } @@ -248,7 +253,7 @@ class IntroduceeProtocolEngine IntroduceeSession s, RequestMessage m) throws DbException { // The dependency, if any, must be the last remote message if (isInvalidDependency(s, m.getPreviousMessageId())) - return abort(txn, s); + return abort(txn, s, m.getMessageId()); // Mark the request visible in the UI and available to answer markMessageVisibleInUi(txn, m.getMessageId()); @@ -343,10 +348,10 @@ class IntroduceeProtocolEngine IntroduceeSession s, AcceptMessage m) throws DbException { // The timestamp must be higher than the last request message if (m.getTimestamp() <= s.getRequestTimestamp()) - return abort(txn, s); + return abort(txn, s, m.getMessageId()); // The dependency, if any, must be the last remote message if (isInvalidDependency(s, m.getPreviousMessageId())) - return abort(txn, s); + return abort(txn, s, m.getMessageId()); // Determine next state IntroduceeState state = @@ -365,10 +370,10 @@ class IntroduceeProtocolEngine IntroduceeSession s, DeclineMessage m) throws DbException { // The timestamp must be higher than the last request message if (m.getTimestamp() <= s.getRequestTimestamp()) - return abort(txn, s); + return abort(txn, s, m.getMessageId()); // The dependency, if any, must be the last remote message if (isInvalidDependency(s, m.getPreviousMessageId())) - return abort(txn, s); + return abort(txn, s, m.getMessageId()); // Mark the response visible in the UI markMessageVisibleInUi(txn, m.getMessageId()); @@ -398,10 +403,10 @@ class IntroduceeProtocolEngine throws DbException { // The timestamp must be higher than the last request message if (m.getTimestamp() <= s.getRequestTimestamp()) - return abort(txn, s); + return abort(txn, s, m.getMessageId()); // The dependency, if any, must be the last remote message if (isInvalidDependency(s, m.getPreviousMessageId())) - return abort(txn, s); + return abort(txn, s, m.getMessageId()); // Move to START state return IntroduceeSession.clear(s, START, s.getLastLocalMessageId(), @@ -423,7 +428,7 @@ class IntroduceeProtocolEngine signature = crypto.sign(ourMacKey, localAuthor.getPrivateKey()); } catch (GeneralSecurityException e) { logException(LOG, WARNING, e); - return abort(txn, s); + return abort(txn, s, s.getLastRemoteMessageId()); } if (s.getState() != AWAIT_AUTH) throw new AssertionError(); long localTimestamp = getTimestampForInvisibleMessage(s); @@ -436,14 +441,14 @@ class IntroduceeProtocolEngine IntroduceeSession s, AuthMessage m) throws DbException { // The dependency, if any, must be the last remote message if (isInvalidDependency(s, m.getPreviousMessageId())) - return abort(txn, s); + return abort(txn, s, m.getMessageId()); LocalAuthor localAuthor = identityManager.getLocalAuthor(txn); try { crypto.verifyAuthMac(m.getMac(), s, localAuthor.getId()); crypto.verifySignature(m.getSignature(), s); } catch (GeneralSecurityException e) { - return abort(txn, s); + return abort(txn, s, m.getMessageId()); } long timestamp = Math.min(s.getLocal().acceptTimestamp, s.getRemote().acceptTimestamp); @@ -487,13 +492,13 @@ class IntroduceeProtocolEngine IntroduceeSession s, ActivateMessage m) throws DbException { // The dependency, if any, must be the last remote message if (isInvalidDependency(s, m.getPreviousMessageId())) - return abort(txn, s); + return abort(txn, s, m.getMessageId()); // Validate MAC try { crypto.verifyActivateMac(m.getMac(), s); } catch (GeneralSecurityException e) { - return abort(txn, s); + return abort(txn, s, m.getMessageId()); } // We might not have added transport keys @@ -522,8 +527,8 @@ class IntroduceeProtocolEngine s.getLocalTimestamp(), m.getMessageId()); } - private IntroduceeSession abort(Transaction txn, IntroduceeSession s) - throws DbException { + private IntroduceeSession abort(Transaction txn, IntroduceeSession s, + @Nullable MessageId lastRemoteMessageId) throws DbException { // Mark the request message unavailable to answer markRequestsUnavailableToAnswer(txn, s); @@ -536,7 +541,7 @@ class IntroduceeProtocolEngine // Reset the session back to initial state return IntroduceeSession.clear(s, START, sent.getId(), - sent.getTimestamp(), s.getLastRemoteMessageId()); + sent.getTimestamp(), lastRemoteMessageId); } private boolean isInvalidDependency(IntroduceeSession s, diff --git a/briar-core/src/main/java/org/briarproject/briar/introduction/IntroducerProtocolEngine.java b/briar-core/src/main/java/org/briarproject/briar/introduction/IntroducerProtocolEngine.java index f300c2d04..e2f511675 100644 --- a/briar-core/src/main/java/org/briarproject/briar/introduction/IntroducerProtocolEngine.java +++ b/briar-core/src/main/java/org/briarproject/briar/introduction/IntroducerProtocolEngine.java @@ -115,7 +115,7 @@ class IntroducerProtocolEngine @Override public IntroducerSession onRequestMessage(Transaction txn, IntroducerSession s, RequestMessage m) throws DbException { - return abort(txn, s); // Invalid in this role + return abort(txn, s, m); // Invalid in this role } @Override @@ -136,7 +136,7 @@ class IntroducerProtocolEngine case AWAIT_ACTIVATES: case AWAIT_ACTIVATE_A: case AWAIT_ACTIVATE_B: - return abort(txn, s); // Invalid in these states + return abort(txn, s, m); // Invalid in these states default: throw new AssertionError(); } @@ -160,7 +160,7 @@ class IntroducerProtocolEngine case AWAIT_ACTIVATES: case AWAIT_ACTIVATE_A: case AWAIT_ACTIVATE_B: - return abort(txn, s); // Invalid in these states + return abort(txn, s, m); // Invalid in these states default: throw new AssertionError(); } @@ -183,7 +183,7 @@ class IntroducerProtocolEngine case AWAIT_ACTIVATES: case AWAIT_ACTIVATE_A: case AWAIT_ACTIVATE_B: - return abort(txn, s); // Invalid in these states + return abort(txn, s, m); // Invalid in these states default: throw new AssertionError(); } @@ -206,7 +206,7 @@ class IntroducerProtocolEngine case AWAIT_AUTHS: case AWAIT_AUTH_A: case AWAIT_AUTH_B: - return abort(txn, s); // Invalid in these states + return abort(txn, s, m); // Invalid in these states default: throw new AssertionError(); } @@ -244,17 +244,17 @@ class IntroducerProtocolEngine IntroducerSession s, AcceptMessage m) throws DbException { // The timestamp must be higher than the last request message if (m.getTimestamp() <= s.getRequestTimestamp()) - return abort(txn, s); + return abort(txn, s, m); // The dependency, if any, must be the last remote message if (isInvalidDependency(s, m.getGroupId(), m.getPreviousMessageId())) - return abort(txn, s); + return abort(txn, s, m); // The message must be expected in the current state boolean senderIsAlice = senderIsAlice(s, m); if (s.getState() != AWAIT_RESPONSES) { if (senderIsAlice && s.getState() != AWAIT_RESPONSE_A) - return abort(txn, s); + return abort(txn, s, m); else if (!senderIsAlice && s.getState() != AWAIT_RESPONSE_B) - return abort(txn, s); + return abort(txn, s, m); } // Mark the response visible in the UI @@ -309,16 +309,16 @@ class IntroducerProtocolEngine IntroducerSession s, AcceptMessage m) throws DbException { // The timestamp must be higher than the last request message if (m.getTimestamp() <= s.getRequestTimestamp()) - return abort(txn, s); + return abort(txn, s, m); // The dependency, if any, must be the last remote message if (isInvalidDependency(s, m.getGroupId(), m.getPreviousMessageId())) - return abort(txn, s); + return abort(txn, s, m); // The message must be expected in the current state boolean senderIsAlice = senderIsAlice(s, m); if (senderIsAlice && s.getState() != B_DECLINED) - return abort(txn, s); + return abort(txn, s, m); else if (!senderIsAlice && s.getState() != A_DECLINED) - return abort(txn, s); + return abort(txn, s, m); // Mark the response visible in the UI markMessageVisibleInUi(txn, m.getMessageId()); @@ -362,17 +362,17 @@ class IntroducerProtocolEngine IntroducerSession s, DeclineMessage m) throws DbException { // The timestamp must be higher than the last request message if (m.getTimestamp() <= s.getRequestTimestamp()) - return abort(txn, s); + return abort(txn, s, m); // The dependency, if any, must be the last remote message if (isInvalidDependency(s, m.getGroupId(), m.getPreviousMessageId())) - return abort(txn, s); + return abort(txn, s, m); // The message must be expected in the current state boolean senderIsAlice = senderIsAlice(s, m); if (s.getState() != AWAIT_RESPONSES) { if (senderIsAlice && s.getState() != AWAIT_RESPONSE_A) - return abort(txn, s); + return abort(txn, s, m); else if (!senderIsAlice && s.getState() != AWAIT_RESPONSE_B) - return abort(txn, s); + return abort(txn, s, m); } // Mark the response visible in the UI @@ -419,16 +419,16 @@ class IntroducerProtocolEngine IntroducerSession s, DeclineMessage m) throws DbException { // The timestamp must be higher than the last request message if (m.getTimestamp() <= s.getRequestTimestamp()) - return abort(txn, s); + return abort(txn, s, m); // The dependency, if any, must be the last remote message if (isInvalidDependency(s, m.getGroupId(), m.getPreviousMessageId())) - return abort(txn, s); + return abort(txn, s, m); // The message must be expected in the current state boolean senderIsAlice = senderIsAlice(s, m); if (senderIsAlice && s.getState() != B_DECLINED) - return abort(txn, s); + return abort(txn, s, m); else if (!senderIsAlice && s.getState() != A_DECLINED) - return abort(txn, s); + return abort(txn, s, m); // Mark the response visible in the UI markMessageVisibleInUi(txn, m.getMessageId()); @@ -470,14 +470,14 @@ class IntroducerProtocolEngine IntroducerSession s, AuthMessage m) throws DbException { // The dependency, if any, must be the last remote message if (isInvalidDependency(s, m.getGroupId(), m.getPreviousMessageId())) - return abort(txn, s); + return abort(txn, s, m); // The message must be expected in the current state boolean senderIsAlice = senderIsAlice(s, m); if (s.getState() != AWAIT_AUTHS) { if (senderIsAlice && s.getState() != AWAIT_AUTH_A) - return abort(txn, s); + return abort(txn, s, m); else if (!senderIsAlice && s.getState() != AWAIT_AUTH_B) - return abort(txn, s); + return abort(txn, s, m); } // Forward AUTH message @@ -506,14 +506,14 @@ class IntroducerProtocolEngine IntroducerSession s, ActivateMessage m) throws DbException { // The dependency, if any, must be the last remote message if (isInvalidDependency(s, m.getGroupId(), m.getPreviousMessageId())) - return abort(txn, s); + return abort(txn, s, m); // The message must be expected in the current state boolean senderIsAlice = senderIsAlice(s, m); if (s.getState() != AWAIT_ACTIVATES) { if (senderIsAlice && s.getState() != AWAIT_ACTIVATE_A) - return abort(txn, s); + return abort(txn, s, m); else if (!senderIsAlice && s.getState() != AWAIT_ACTIVATE_B) - return abort(txn, s); + return abort(txn, s, m); } // Forward ACTIVATE message @@ -588,21 +588,31 @@ class IntroducerProtocolEngine s.getRequestTimestamp(), introduceeA, introduceeB); } - private IntroducerSession abort(Transaction txn, IntroducerSession s) - throws DbException { + private IntroducerSession abort(Transaction txn, IntroducerSession s, + AbstractIntroductionMessage lastRemoteMessage) throws DbException { // Broadcast abort event for testing txn.attach(new IntroductionAbortedEvent(s.getSessionId())); + // Record the message that triggered the abort + Introducee introduceeA = s.getIntroduceeA(); + Introducee introduceeB = s.getIntroduceeB(); + if (senderIsAlice(s, lastRemoteMessage)) { + introduceeA = new Introducee(introduceeA, + lastRemoteMessage.getMessageId()); + } else { + introduceeB = new Introducee(introduceeB, + lastRemoteMessage.getMessageId()); + } + // Send an ABORT message to both introducees - long timestampA = - getTimestampForInvisibleMessage(s, s.getIntroduceeA()); - Message sentA = sendAbortMessage(txn, s.getIntroduceeA(), timestampA); - long timestampB = - getTimestampForInvisibleMessage(s, s.getIntroduceeB()); - Message sentB = sendAbortMessage(txn, s.getIntroduceeB(), timestampB); + long timestampA = getTimestampForInvisibleMessage(s, introduceeA); + Message sentA = sendAbortMessage(txn, introduceeA, timestampA); + long timestampB = getTimestampForInvisibleMessage(s, introduceeB); + Message sentB = sendAbortMessage(txn, introduceeB, timestampB); + // Reset the session back to initial state - Introducee introduceeA = new Introducee(s.getIntroduceeA(), sentA); - Introducee introduceeB = new Introducee(s.getIntroduceeB(), sentB); + introduceeA = new Introducee(introduceeA, sentA); + introduceeB = new Introducee(introduceeB, sentB); return new IntroducerSession(s.getSessionId(), START, s.getRequestTimestamp(), introduceeA, introduceeB); } From 035c639aa066686bf7c8765e52c92b2d36298d55 Mon Sep 17 00:00:00 2001 From: Torsten Grote Date: Tue, 22 Jun 2021 17:20:47 -0300 Subject: [PATCH 47/77] Add TransportKeyAgreementValidatorTest --- .../TransportKeyAgreementValidatorTest.java | 270 ++++++++++++++++++ 1 file changed, 270 insertions(+) create mode 100644 bramble-core/src/test/java/org/briarproject/bramble/transport/agreement/TransportKeyAgreementValidatorTest.java diff --git a/bramble-core/src/test/java/org/briarproject/bramble/transport/agreement/TransportKeyAgreementValidatorTest.java b/bramble-core/src/test/java/org/briarproject/bramble/transport/agreement/TransportKeyAgreementValidatorTest.java new file mode 100644 index 000000000..0437a026c --- /dev/null +++ b/bramble-core/src/test/java/org/briarproject/bramble/transport/agreement/TransportKeyAgreementValidatorTest.java @@ -0,0 +1,270 @@ +package org.briarproject.bramble.transport.agreement; + +import org.briarproject.bramble.api.FormatException; +import org.briarproject.bramble.api.client.BdfMessageContext; +import org.briarproject.bramble.api.client.ClientHelper; +import org.briarproject.bramble.api.data.BdfDictionary; +import org.briarproject.bramble.api.data.BdfList; +import org.briarproject.bramble.api.data.MetadataEncoder; +import org.briarproject.bramble.api.plugin.TransportId; +import org.briarproject.bramble.api.sync.Group; +import org.briarproject.bramble.api.sync.Message; +import org.briarproject.bramble.api.sync.MessageId; +import org.briarproject.bramble.api.system.Clock; +import org.briarproject.bramble.test.BrambleMockTestCase; +import org.jmock.Expectations; +import org.junit.Test; + +import static java.util.Collections.emptyList; +import static java.util.Collections.singletonList; +import static org.briarproject.bramble.api.crypto.CryptoConstants.MAX_AGREEMENT_PUBLIC_KEY_BYTES; +import static org.briarproject.bramble.api.plugin.TransportId.MAX_TRANSPORT_ID_LENGTH; +import static org.briarproject.bramble.api.versioning.ClientVersioningManager.CLIENT_ID; +import static org.briarproject.bramble.api.versioning.ClientVersioningManager.MAJOR_VERSION; +import static org.briarproject.bramble.test.TestUtils.getGroup; +import static org.briarproject.bramble.test.TestUtils.getMessage; +import static org.briarproject.bramble.test.TestUtils.getRandomBytes; +import static org.briarproject.bramble.transport.agreement.MessageType.ACTIVATE; +import static org.briarproject.bramble.transport.agreement.MessageType.KEY; +import static org.briarproject.bramble.transport.agreement.TransportKeyAgreementConstants.MSG_KEY_PUBLIC_KEY; +import static org.briarproject.bramble.util.StringUtils.getRandomString; +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; + +public class TransportKeyAgreementValidatorTest extends BrambleMockTestCase { + + private final ClientHelper clientHelper = context.mock(ClientHelper.class); + private final MetadataEncoder metadataEncoder = + context.mock(MetadataEncoder.class); + private final Clock clock = context.mock(Clock.class); + private final MessageEncoder messageEncoder = + context.mock(MessageEncoder.class); + private final TransportKeyAgreementValidator validator = + new TransportKeyAgreementValidator(clientHelper, metadataEncoder, + clock, messageEncoder); + + private final Group group = getGroup(CLIENT_ID, MAJOR_VERSION); + private final Message message = getMessage(group.getId()); + + @Test(expected = FormatException.class) + public void testRejectsEmptyMessage() throws Exception { + BdfList body = BdfList.of(); + validator.validateMessage(message, group, body); + } + + @Test(expected = FormatException.class) + public void testRejectsNullType() throws Exception { + BdfList body = BdfList.of((Object) null); + validator.validateMessage(message, group, body); + } + + @Test(expected = FormatException.class) + public void testRejectsNonLongType() throws Exception { + BdfList body = BdfList.of("123"); + validator.validateMessage(message, group, body); + } + + @Test(expected = FormatException.class) + public void testRejectsUnknownLongType() throws Exception { + BdfList body = BdfList.of(ACTIVATE.getValue() + 1); + validator.validateMessage(message, group, body); + } + + @Test + public void testAcceptsKeyMsg() throws Exception { + TransportId transportId = new TransportId(getRandomString(1)); + context.checking(new Expectations() {{ + oneOf(messageEncoder) + .encodeMessageMetadata(transportId, KEY, false); + will(returnValue(new BdfDictionary())); + }}); + + byte[] publicKey = getRandomBytes(1); + BdfList body = + BdfList.of(KEY.getValue(), transportId.getString(), publicKey); + BdfMessageContext msgCtx = + validator.validateMessage(message, group, body); + assertEquals(emptyList(), msgCtx.getDependencies()); + BdfDictionary d = msgCtx.getDictionary(); + assertArrayEquals(publicKey, d.getRaw(MSG_KEY_PUBLIC_KEY)); + } + + @Test + public void testAcceptsKeyMsgMaxLengths() throws Exception { + TransportId transportId = + new TransportId(getRandomString(MAX_TRANSPORT_ID_LENGTH)); + context.checking(new Expectations() {{ + oneOf(messageEncoder) + .encodeMessageMetadata(transportId, KEY, false); + will(returnValue(new BdfDictionary())); + }}); + + byte[] publicKey = getRandomBytes(MAX_AGREEMENT_PUBLIC_KEY_BYTES); + BdfList body = + BdfList.of(KEY.getValue(), transportId.getString(), publicKey); + BdfMessageContext msgCtx = + validator.validateMessage(message, group, body); + assertEquals(emptyList(), msgCtx.getDependencies()); + BdfDictionary d = msgCtx.getDictionary(); + assertArrayEquals(publicKey, d.getRaw(MSG_KEY_PUBLIC_KEY)); + } + + @Test(expected = FormatException.class) + public void testRejectsTooLongKeyMsg() throws Exception { + BdfList body = BdfList.of(KEY.getValue(), getRandomString(1), + getRandomBytes(1), 1); + validator.validateMessage(message, group, body); + } + + @Test(expected = FormatException.class) + public void testRejectsTooShortKeyMsg() throws Exception { + BdfList body = BdfList.of(KEY.getValue(), getRandomString(1)); + validator.validateMessage(message, group, body); + } + + @Test(expected = FormatException.class) + public void testRejectsEmptyTransportIdKeyMsg() throws Exception { + BdfList body = BdfList.of(KEY.getValue(), "", getRandomBytes(1)); + validator.validateMessage(message, group, body); + } + + @Test(expected = FormatException.class) + public void testRejectsTooLongTransportIdKeyMsg() throws Exception { + BdfList body = BdfList.of(KEY.getValue(), + getRandomString(MAX_TRANSPORT_ID_LENGTH + 1), + getRandomBytes(1)); + validator.validateMessage(message, group, body); + } + + @Test(expected = FormatException.class) + public void testRejectsNonStringTransportIdKeyMsg() throws Exception { + BdfList body = BdfList.of(KEY.getValue(), + getRandomBytes(MAX_TRANSPORT_ID_LENGTH), + getRandomBytes(1)); + validator.validateMessage(message, group, body); + } + + @Test(expected = FormatException.class) + public void testRejectsEmptyPublicKeyKeyMsg() throws Exception { + BdfList body = BdfList.of(KEY.getValue(), + getRandomString(1), + getRandomBytes(0)); + validator.validateMessage(message, group, body); + } + + @Test(expected = FormatException.class) + public void testRejectsTooLongPublicKeyKeyMsg() throws Exception { + BdfList body = BdfList.of(KEY.getValue(), + getRandomString(1), + getRandomBytes(MAX_AGREEMENT_PUBLIC_KEY_BYTES + 1)); + validator.validateMessage(message, group, body); + } + + @Test(expected = FormatException.class) + public void testRejectsNonBytesPublicKeyKeyMsg() throws Exception { + BdfList body = BdfList.of(KEY.getValue(), + getRandomString(1), + getRandomString(MAX_AGREEMENT_PUBLIC_KEY_BYTES)); + validator.validateMessage(message, group, body); + } + + @Test + public void testAcceptsActivateMsg() throws Exception { + TransportId transportId = new TransportId(getRandomString(1)); + BdfDictionary meta = new BdfDictionary(); + context.checking(new Expectations() {{ + oneOf(messageEncoder) + .encodeMessageMetadata(transportId, ACTIVATE, false); + will(returnValue(meta)); + }}); + + MessageId msgId = new MessageId(getRandomBytes(MessageId.LENGTH)); + BdfList body = BdfList.of(ACTIVATE.getValue(), transportId.getString(), + msgId.getBytes()); + + BdfMessageContext msgCtx = + validator.validateMessage(message, group, body); + assertEquals(singletonList(msgId), msgCtx.getDependencies()); + assertEquals(meta, msgCtx.getDictionary()); + } + + @Test + public void testAcceptsActivateMsgMaxTransportIdLength() throws Exception { + TransportId transportId = + new TransportId(getRandomString(MAX_TRANSPORT_ID_LENGTH)); + BdfDictionary meta = new BdfDictionary(); + context.checking(new Expectations() {{ + oneOf(messageEncoder) + .encodeMessageMetadata(transportId, ACTIVATE, false); + will(returnValue(meta)); + }}); + + MessageId msgId = new MessageId(getRandomBytes(MessageId.LENGTH)); + BdfList body = BdfList.of(ACTIVATE.getValue(), transportId.getString(), + msgId.getBytes()); + + BdfMessageContext msgCtx = + validator.validateMessage(message, group, body); + assertEquals(singletonList(msgId), msgCtx.getDependencies()); + assertEquals(meta, msgCtx.getDictionary()); + } + + @Test(expected = FormatException.class) + public void testRejectsTooLongActivateMsg() throws Exception { + BdfList body = BdfList.of(ACTIVATE.getValue(), getRandomString(1), + getRandomBytes(MessageId.LENGTH), 1); + validator.validateMessage(message, group, body); + } + + @Test(expected = FormatException.class) + public void testRejectsTooShortActivateMsg() throws Exception { + BdfList body = BdfList.of(ACTIVATE.getValue(), getRandomString(1)); + validator.validateMessage(message, group, body); + } + + @Test(expected = FormatException.class) + public void testRejectsEmptyTransportIdActivateMsg() throws Exception { + BdfList body = BdfList.of(ACTIVATE.getValue(), "", + getRandomBytes(MessageId.LENGTH)); + validator.validateMessage(message, group, body); + } + + @Test(expected = FormatException.class) + public void testRejectsNonStringTransportIdActivateMsg() throws Exception { + BdfList body = BdfList.of(ACTIVATE.getValue(), 123, + getRandomBytes(MessageId.LENGTH)); + validator.validateMessage(message, group, body); + } + + @Test(expected = FormatException.class) + public void testRejectsTooLongTransportIdActivateMsg() throws Exception { + BdfList body = BdfList.of(ACTIVATE.getValue(), + getRandomString(MAX_TRANSPORT_ID_LENGTH + 1), + getRandomBytes(MessageId.LENGTH)); + validator.validateMessage(message, group, body); + } + + @Test(expected = FormatException.class) + public void testRejectsTooShortMsgIdActivateMsg() throws Exception { + BdfList body = BdfList.of(ACTIVATE.getValue(), + getRandomString(1), + getRandomBytes(MessageId.LENGTH - 1)); + validator.validateMessage(message, group, body); + } + + @Test(expected = FormatException.class) + public void testRejectsTooLongMsgIdActivateMsg() throws Exception { + BdfList body = BdfList.of(ACTIVATE.getValue(), + getRandomString(1), + getRandomBytes(MessageId.LENGTH + 1)); + validator.validateMessage(message, group, body); + } + + @Test(expected = FormatException.class) + public void testRejectsNonByteMsgIdActivateMsg() throws Exception { + BdfList body = BdfList.of(ACTIVATE.getValue(), + getRandomString(1), + getRandomString(MessageId.LENGTH)); + validator.validateMessage(message, group, body); + } +} From e285f21d1c9726368794b57268c40473148dca1a Mon Sep 17 00:00:00 2001 From: akwizgran Date: Wed, 23 Jun 2021 16:32:32 +0100 Subject: [PATCH 48/77] Check whether system clock is reasonable at startup. --- .../api/lifecycle/LifecycleManager.java | 21 ++++++++++++ .../lifecycle/LifecycleManagerImpl.java | 14 +++++++- .../lifecycle/LifecycleManagerImplTest.java | 32 ++++++++++++++++++- .../briar/android/StartupFailureActivity.java | 17 +++++----- briar-android/src/main/res/values/strings.xml | 1 + 5 files changed, 75 insertions(+), 10 deletions(-) diff --git a/bramble-api/src/main/java/org/briarproject/bramble/api/lifecycle/LifecycleManager.java b/bramble-api/src/main/java/org/briarproject/bramble/api/lifecycle/LifecycleManager.java index ab14b669f..dcf8e8ce9 100644 --- a/bramble-api/src/main/java/org/briarproject/bramble/api/lifecycle/LifecycleManager.java +++ b/bramble-api/src/main/java/org/briarproject/bramble/api/lifecycle/LifecycleManager.java @@ -22,6 +22,7 @@ public interface LifecycleManager { */ enum StartResult { ALREADY_RUNNING, + CLOCK_ERROR, DB_ERROR, DATA_TOO_OLD_ERROR, DATA_TOO_NEW_ERROR, @@ -29,6 +30,26 @@ public interface LifecycleManager { SUCCESS } + /** + * The minimum reasonable value for the system clock, in milliseconds + * since the Unix epoch. {@link #startServices(SecretKey)} will return + * {@link StartResult#CLOCK_ERROR} if the system clock reports an earlier + * time. + *

+ * 1 Jan 2021, 00:00:00 UTC + */ + long MIN_REASONABLE_TIME_MS = 1_609_459_200_000L; + + /** + * The maximum reasonable value for the system clock, in milliseconds + * since the Unix epoch. {@link #startServices(SecretKey)} will return + * {@link StartResult#CLOCK_ERROR} if the system clock reports a later + * time. + *

+ * 1 Jan 2121, 00:00:00 UTC + */ + long MAX_REASONABLE_TIME_MS = 4_765_132_800_000L; + /** * The state the lifecycle can be in. * Returned by {@link #getLifecycleState()} diff --git a/bramble-core/src/main/java/org/briarproject/bramble/lifecycle/LifecycleManagerImpl.java b/bramble-core/src/main/java/org/briarproject/bramble/lifecycle/LifecycleManagerImpl.java index fc1259d2a..5efab84ca 100644 --- a/bramble-core/src/main/java/org/briarproject/bramble/lifecycle/LifecycleManagerImpl.java +++ b/bramble-core/src/main/java/org/briarproject/bramble/lifecycle/LifecycleManagerImpl.java @@ -12,6 +12,7 @@ import org.briarproject.bramble.api.lifecycle.Service; import org.briarproject.bramble.api.lifecycle.ServiceException; import org.briarproject.bramble.api.lifecycle.event.LifecycleEvent; import org.briarproject.bramble.api.nullsafety.NotNullByDefault; +import org.briarproject.bramble.api.system.Clock; import java.util.List; import java.util.concurrent.CopyOnWriteArrayList; @@ -34,6 +35,7 @@ import static org.briarproject.bramble.api.lifecycle.LifecycleManager.LifecycleS import static org.briarproject.bramble.api.lifecycle.LifecycleManager.LifecycleState.STARTING_SERVICES; import static org.briarproject.bramble.api.lifecycle.LifecycleManager.LifecycleState.STOPPING; import static org.briarproject.bramble.api.lifecycle.LifecycleManager.StartResult.ALREADY_RUNNING; +import static org.briarproject.bramble.api.lifecycle.LifecycleManager.StartResult.CLOCK_ERROR; import static org.briarproject.bramble.api.lifecycle.LifecycleManager.StartResult.DATA_TOO_NEW_ERROR; import static org.briarproject.bramble.api.lifecycle.LifecycleManager.StartResult.DATA_TOO_OLD_ERROR; import static org.briarproject.bramble.api.lifecycle.LifecycleManager.StartResult.DB_ERROR; @@ -52,6 +54,7 @@ class LifecycleManagerImpl implements LifecycleManager, MigrationListener { private final DatabaseComponent db; private final EventBus eventBus; + private final Clock clock; private final List services; private final List openDatabaseHooks; private final List executors; @@ -63,9 +66,11 @@ class LifecycleManagerImpl implements LifecycleManager, MigrationListener { private volatile LifecycleState state = STARTING; @Inject - LifecycleManagerImpl(DatabaseComponent db, EventBus eventBus) { + LifecycleManagerImpl(DatabaseComponent db, EventBus eventBus, + Clock clock) { this.db = db; this.eventBus = eventBus; + this.clock = clock; services = new CopyOnWriteArrayList<>(); openDatabaseHooks = new CopyOnWriteArrayList<>(); executors = new CopyOnWriteArrayList<>(); @@ -99,6 +104,13 @@ class LifecycleManagerImpl implements LifecycleManager, MigrationListener { LOG.info("Already starting or stopping"); return ALREADY_RUNNING; } + long now = clock.currentTimeMillis(); + if (now < MIN_REASONABLE_TIME_MS || now > MAX_REASONABLE_TIME_MS) { + if (LOG.isLoggable(WARNING)) { + LOG.warning("System clock is unreasonable: " + now); + } + return CLOCK_ERROR; + } try { LOG.info("Opening database"); long start = now(); diff --git a/bramble-core/src/test/java/org/briarproject/bramble/lifecycle/LifecycleManagerImplTest.java b/bramble-core/src/test/java/org/briarproject/bramble/lifecycle/LifecycleManagerImplTest.java index 09ce697bf..079a02b5e 100644 --- a/bramble-core/src/test/java/org/briarproject/bramble/lifecycle/LifecycleManagerImplTest.java +++ b/bramble-core/src/test/java/org/briarproject/bramble/lifecycle/LifecycleManagerImplTest.java @@ -6,6 +6,7 @@ import org.briarproject.bramble.api.db.Transaction; import org.briarproject.bramble.api.event.EventBus; import org.briarproject.bramble.api.lifecycle.LifecycleManager.OpenDatabaseHook; import org.briarproject.bramble.api.lifecycle.event.LifecycleEvent; +import org.briarproject.bramble.api.system.Clock; import org.briarproject.bramble.test.BrambleMockTestCase; import org.briarproject.bramble.test.DbExpectations; import org.junit.Before; @@ -14,6 +15,9 @@ import org.junit.Test; import java.util.concurrent.atomic.AtomicBoolean; import static junit.framework.TestCase.assertTrue; +import static org.briarproject.bramble.api.lifecycle.LifecycleManager.MAX_REASONABLE_TIME_MS; +import static org.briarproject.bramble.api.lifecycle.LifecycleManager.MIN_REASONABLE_TIME_MS; +import static org.briarproject.bramble.api.lifecycle.LifecycleManager.StartResult.CLOCK_ERROR; import static org.briarproject.bramble.api.lifecycle.LifecycleManager.StartResult.SUCCESS; import static org.briarproject.bramble.test.TestUtils.getSecretKey; import static org.junit.Assert.assertEquals; @@ -22,6 +26,7 @@ public class LifecycleManagerImplTest extends BrambleMockTestCase { private final DatabaseComponent db = context.mock(DatabaseComponent.class); private final EventBus eventBus = context.mock(EventBus.class); + private final Clock clock = context.mock(Clock.class); private final SecretKey dbKey = getSecretKey(); @@ -29,16 +34,19 @@ public class LifecycleManagerImplTest extends BrambleMockTestCase { @Before public void setUp() { - lifecycleManager = new LifecycleManagerImpl(db, eventBus); + lifecycleManager = new LifecycleManagerImpl(db, eventBus, clock); } @Test public void testOpenDatabaseHooksAreCalledAtStartup() throws Exception { + long now = System.currentTimeMillis(); Transaction txn = new Transaction(null, false); AtomicBoolean called = new AtomicBoolean(false); OpenDatabaseHook hook = transaction -> called.set(true); context.checking(new DbExpectations() {{ + oneOf(clock).currentTimeMillis(); + will(returnValue(now)); oneOf(db).open(dbKey, lifecycleManager); will(returnValue(false)); oneOf(db).transaction(with(false), withDbRunnable(txn)); @@ -51,4 +59,26 @@ public class LifecycleManagerImplTest extends BrambleMockTestCase { assertEquals(SUCCESS, lifecycleManager.startServices(dbKey)); assertTrue(called.get()); } + + @Test + public void testStartupFailsIfClockIsUnreasonablyBehind() { + + context.checking(new DbExpectations() {{ + oneOf(clock).currentTimeMillis(); + will(returnValue(MIN_REASONABLE_TIME_MS - 1)); + }}); + + assertEquals(CLOCK_ERROR, lifecycleManager.startServices(dbKey)); + } + + @Test + public void testStartupFailsIfClockIsUnreasonablyAhead() { + + context.checking(new DbExpectations() {{ + oneOf(clock).currentTimeMillis(); + will(returnValue(MAX_REASONABLE_TIME_MS + 1)); + }}); + + assertEquals(CLOCK_ERROR, lifecycleManager.startServices(dbKey)); + } } diff --git a/briar-android/src/main/java/org/briarproject/briar/android/StartupFailureActivity.java b/briar-android/src/main/java/org/briarproject/briar/android/StartupFailureActivity.java index d92a2b58d..5f1e29fa2 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/StartupFailureActivity.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/StartupFailureActivity.java @@ -51,26 +51,27 @@ public class StartupFailureActivity extends BaseActivity implements } // show proper error message - String errorMsg; + int errorRes; switch (result) { + case CLOCK_ERROR: + errorRes = R.string.startup_failed_clock_error; + break; case DATA_TOO_OLD_ERROR: - errorMsg = - getString(R.string.startup_failed_data_too_old_error); + errorRes = R.string.startup_failed_data_too_old_error; break; case DATA_TOO_NEW_ERROR: - errorMsg = - getString(R.string.startup_failed_data_too_new_error); + errorRes = R.string.startup_failed_data_too_new_error; break; case DB_ERROR: - errorMsg = getString(R.string.startup_failed_db_error); + errorRes = R.string.startup_failed_db_error; break; case SERVICE_ERROR: - errorMsg = getString(R.string.startup_failed_service_error); + errorRes = R.string.startup_failed_service_error; break; default: throw new IllegalArgumentException(); } - showInitialFragment(ErrorFragment.newInstance(errorMsg)); + showInitialFragment(ErrorFragment.newInstance(getString(errorRes))); } @Override diff --git a/briar-android/src/main/res/values/strings.xml b/briar-android/src/main/res/values/strings.xml index 42af8d838..d22753a75 100644 --- a/briar-android/src/main/res/values/strings.xml +++ b/briar-android/src/main/res/values/strings.xml @@ -44,6 +44,7 @@ Briar could not start Tap for more information. Briar Startup Failure + Briar was unable to start because your device\'s clock is wrong. Please set your device\'s clock to the right time and try again. For some reason, your Briar database is corrupted beyond repair. Your account, your data and all your contacts are lost. Unfortunately, you need to reinstall Briar or set up a new account by choosing \'I have forgotten my password\' at the password prompt. Your account was created with an old version of this app and cannot be opened with this version. You must either reinstall the old version or set up a new account by choosing \'I have forgotten my password\' at the password prompt. This version of the app is too old. Please upgrade to the latest version and try again. From 22ea4ced0d488666e509f94445b78ab6ed3ee603 Mon Sep 17 00:00:00 2001 From: akwizgran Date: Mon, 28 Jun 2021 15:02:34 +0100 Subject: [PATCH 49/77] Add transport property to indicate support for removable drives. --- .../bramble/plugin/file/AndroidRemovableDrivePlugin.java | 6 ++++-- .../plugin/file/AndroidRemovableDrivePluginFactory.java | 2 +- .../bramble/api/plugin/file/RemovableDriveConstants.java | 1 + .../plugin/file/AbstractRemovableDrivePlugin.java | 9 ++++++++- .../bramble/plugin/file/RemovableDrivePlugin.java | 5 +++-- .../bramble/plugin/file/RemovableDrivePluginFactory.java | 2 +- 6 files changed, 18 insertions(+), 7 deletions(-) diff --git a/bramble-android/src/main/java/org/briarproject/bramble/plugin/file/AndroidRemovableDrivePlugin.java b/bramble-android/src/main/java/org/briarproject/bramble/plugin/file/AndroidRemovableDrivePlugin.java index 05e569f7c..964088367 100644 --- a/bramble-android/src/main/java/org/briarproject/bramble/plugin/file/AndroidRemovableDrivePlugin.java +++ b/bramble-android/src/main/java/org/briarproject/bramble/plugin/file/AndroidRemovableDrivePlugin.java @@ -4,6 +4,7 @@ import android.app.Application; import android.net.Uri; import org.briarproject.bramble.api.nullsafety.NotNullByDefault; +import org.briarproject.bramble.api.plugin.PluginCallback; import org.briarproject.bramble.api.properties.TransportProperties; import java.io.IOException; @@ -21,8 +22,9 @@ class AndroidRemovableDrivePlugin extends RemovableDrivePlugin { private final Application app; - AndroidRemovableDrivePlugin(Application app, int maxLatency) { - super(maxLatency); + AndroidRemovableDrivePlugin(Application app, PluginCallback callback, + int maxLatency) { + super(callback, maxLatency); this.app = app; } diff --git a/bramble-android/src/main/java/org/briarproject/bramble/plugin/file/AndroidRemovableDrivePluginFactory.java b/bramble-android/src/main/java/org/briarproject/bramble/plugin/file/AndroidRemovableDrivePluginFactory.java index 64f6cf534..7c82bd4ee 100644 --- a/bramble-android/src/main/java/org/briarproject/bramble/plugin/file/AndroidRemovableDrivePluginFactory.java +++ b/bramble-android/src/main/java/org/briarproject/bramble/plugin/file/AndroidRemovableDrivePluginFactory.java @@ -42,6 +42,6 @@ public class AndroidRemovableDrivePluginFactory implements @Nullable @Override public SimplexPlugin createPlugin(PluginCallback callback) { - return new AndroidRemovableDrivePlugin(app, MAX_LATENCY); + return new AndroidRemovableDrivePlugin(app, callback, MAX_LATENCY); } } diff --git a/bramble-api/src/main/java/org/briarproject/bramble/api/plugin/file/RemovableDriveConstants.java b/bramble-api/src/main/java/org/briarproject/bramble/api/plugin/file/RemovableDriveConstants.java index 927e197df..d414b88e0 100644 --- a/bramble-api/src/main/java/org/briarproject/bramble/api/plugin/file/RemovableDriveConstants.java +++ b/bramble-api/src/main/java/org/briarproject/bramble/api/plugin/file/RemovableDriveConstants.java @@ -8,4 +8,5 @@ public interface RemovableDriveConstants { String PROP_PATH = "path"; String PROP_URI = "uri"; + String PROP_SUPPORTED = "supported"; } 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 8e8d35488..33d0165a5 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 @@ -3,6 +3,7 @@ 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.TransportConnectionReader; import org.briarproject.bramble.api.plugin.TransportConnectionWriter; import org.briarproject.bramble.api.plugin.TransportId; @@ -17,10 +18,12 @@ import java.util.logging.Logger; import javax.annotation.concurrent.Immutable; +import static java.util.Collections.singletonMap; import static java.util.logging.Level.WARNING; import static java.util.logging.Logger.getLogger; import static org.briarproject.bramble.api.plugin.Plugin.State.ACTIVE; import static org.briarproject.bramble.api.plugin.file.RemovableDriveConstants.ID; +import static org.briarproject.bramble.api.plugin.file.RemovableDriveConstants.PROP_SUPPORTED; import static org.briarproject.bramble.util.LogUtils.logException; @Immutable @@ -31,6 +34,7 @@ abstract class AbstractRemovableDrivePlugin implements SimplexPlugin { getLogger(AbstractRemovableDrivePlugin.class.getName()); private final int maxLatency; + private final PluginCallback callback; abstract InputStream openInputStream(TransportProperties p) throws IOException; @@ -38,7 +42,8 @@ abstract class AbstractRemovableDrivePlugin implements SimplexPlugin { abstract OutputStream openOutputStream(TransportProperties p) throws IOException; - AbstractRemovableDrivePlugin(int maxLatency) { + AbstractRemovableDrivePlugin(PluginCallback callback, int maxLatency) { + this.callback = callback; this.maxLatency = maxLatency; } @@ -60,6 +65,8 @@ abstract class AbstractRemovableDrivePlugin implements SimplexPlugin { @Override public void start() { + callback.mergeLocalProperties( + new TransportProperties(singletonMap(PROP_SUPPORTED, "true"))); } @Override diff --git a/bramble-core/src/main/java/org/briarproject/bramble/plugin/file/RemovableDrivePlugin.java b/bramble-core/src/main/java/org/briarproject/bramble/plugin/file/RemovableDrivePlugin.java index cb6653134..6995371b9 100644 --- a/bramble-core/src/main/java/org/briarproject/bramble/plugin/file/RemovableDrivePlugin.java +++ b/bramble-core/src/main/java/org/briarproject/bramble/plugin/file/RemovableDrivePlugin.java @@ -1,6 +1,7 @@ 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.properties.TransportProperties; import java.io.FileInputStream; @@ -18,8 +19,8 @@ import static org.briarproject.bramble.util.StringUtils.isNullOrEmpty; @NotNullByDefault class RemovableDrivePlugin extends AbstractRemovableDrivePlugin { - RemovableDrivePlugin(int maxLatency) { - super(maxLatency); + RemovableDrivePlugin(PluginCallback callback, int maxLatency) { + super(callback, maxLatency); } @Override diff --git a/bramble-core/src/main/java/org/briarproject/bramble/plugin/file/RemovableDrivePluginFactory.java b/bramble-core/src/main/java/org/briarproject/bramble/plugin/file/RemovableDrivePluginFactory.java index d0bc374ed..921cbafa1 100644 --- a/bramble-core/src/main/java/org/briarproject/bramble/plugin/file/RemovableDrivePluginFactory.java +++ b/bramble-core/src/main/java/org/briarproject/bramble/plugin/file/RemovableDrivePluginFactory.java @@ -36,6 +36,6 @@ public class RemovableDrivePluginFactory implements SimplexPluginFactory { @Nullable @Override public SimplexPlugin createPlugin(PluginCallback callback) { - return new RemovableDrivePlugin(MAX_LATENCY); + return new RemovableDrivePlugin(callback, MAX_LATENCY); } } From 8c1f721015dc918e0d35273c70dd1e391f74c2e9 Mon Sep 17 00:00:00 2001 From: akwizgran Date: Mon, 28 Jun 2021 15:52:26 +0100 Subject: [PATCH 50/77] Add method for checking whether contact supports transport. --- .../plugin/file/RemovableDriveManager.java | 6 ++++++ .../file/RemovableDriveManagerImpl.java | 20 +++++++++++++++++-- 2 files changed, 24 insertions(+), 2 deletions(-) diff --git a/bramble-api/src/main/java/org/briarproject/bramble/api/plugin/file/RemovableDriveManager.java b/bramble-api/src/main/java/org/briarproject/bramble/api/plugin/file/RemovableDriveManager.java index 8c74497b0..2838f87cf 100644 --- a/bramble-api/src/main/java/org/briarproject/bramble/api/plugin/file/RemovableDriveManager.java +++ b/bramble-api/src/main/java/org/briarproject/bramble/api/plugin/file/RemovableDriveManager.java @@ -39,6 +39,12 @@ public interface RemovableDriveManager { */ RemovableDriveTask startWriterTask(ContactId c, TransportProperties p); + /** + * Returns true if the given contact has indicated support for the + * removable drive transport. + */ + boolean isTransportSupportedByContact(ContactId c) throws DbException; + /** * Returns true if there is anything to send to the given contact. */ diff --git a/bramble-core/src/main/java/org/briarproject/bramble/plugin/file/RemovableDriveManagerImpl.java b/bramble-core/src/main/java/org/briarproject/bramble/plugin/file/RemovableDriveManagerImpl.java index 2c96d5d53..a0d1848cd 100644 --- a/bramble-core/src/main/java/org/briarproject/bramble/plugin/file/RemovableDriveManagerImpl.java +++ b/bramble-core/src/main/java/org/briarproject/bramble/plugin/file/RemovableDriveManagerImpl.java @@ -8,6 +8,7 @@ import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.plugin.file.RemovableDriveManager; import org.briarproject.bramble.api.plugin.file.RemovableDriveTask; import org.briarproject.bramble.api.properties.TransportProperties; +import org.briarproject.bramble.api.properties.TransportPropertyManager; import java.util.concurrent.Executor; @@ -16,6 +17,8 @@ import javax.annotation.concurrent.GuardedBy; import javax.annotation.concurrent.ThreadSafe; import javax.inject.Inject; +import static org.briarproject.bramble.api.plugin.file.RemovableDriveConstants.ID; +import static org.briarproject.bramble.api.plugin.file.RemovableDriveConstants.PROP_SUPPORTED; import static org.briarproject.bramble.plugin.file.RemovableDrivePluginFactory.MAX_LATENCY; @ThreadSafe @@ -25,6 +28,7 @@ class RemovableDriveManagerImpl private final Executor ioExecutor; private final DatabaseComponent db; + private final TransportPropertyManager transportPropertyManager; private final RemovableDriveTaskFactory taskFactory; private final Object lock = new Object(); @@ -34,10 +38,14 @@ class RemovableDriveManagerImpl private RemovableDriveTask writer = null; @Inject - RemovableDriveManagerImpl(@IoExecutor Executor ioExecutor, - DatabaseComponent db, RemovableDriveTaskFactory taskFactory) { + RemovableDriveManagerImpl( + @IoExecutor Executor ioExecutor, + DatabaseComponent db, + TransportPropertyManager transportPropertyManager, + RemovableDriveTaskFactory taskFactory) { this.ioExecutor = ioExecutor; this.db = db; + this.transportPropertyManager = transportPropertyManager; this.taskFactory = taskFactory; } @@ -80,6 +88,14 @@ class RemovableDriveManagerImpl return created; } + @Override + public boolean isTransportSupportedByContact(ContactId c) + throws DbException { + TransportProperties p = + transportPropertyManager.getRemoteProperties(c, ID); + return "true".equals(p.get(PROP_SUPPORTED)); + } + @Override public boolean isWriterTaskNeeded(ContactId c) throws DbException { return db.transactionWithResult(true, txn -> From e4bd6fdf951d06bc1b0b33f8c0a806f47fda0663 Mon Sep 17 00:00:00 2001 From: Torsten Grote Date: Wed, 23 Jun 2021 14:34:22 -0300 Subject: [PATCH 51/77] Put FeatureFlags for tests into a TestFeatureFlagModule --- ...emovableDriveIntegrationTestComponent.java | 2 ++ .../RemovableDriveIntegrationTestModule.java | 27 --------------- .../BrambleCoreIntegrationTestModule.java | 28 +-------------- .../bramble/test/TestFeatureFlagModule.java | 34 +++++++++++++++++++ .../briar/headless/HeadlessTestModule.kt | 11 ++---- 5 files changed, 39 insertions(+), 63 deletions(-) create mode 100644 bramble-core/src/test/java/org/briarproject/bramble/test/TestFeatureFlagModule.java diff --git a/bramble-core/src/test/java/org/briarproject/bramble/plugin/file/RemovableDriveIntegrationTestComponent.java b/bramble-core/src/test/java/org/briarproject/bramble/plugin/file/RemovableDriveIntegrationTestComponent.java index 9c5c3153d..385e140da 100644 --- a/bramble-core/src/test/java/org/briarproject/bramble/plugin/file/RemovableDriveIntegrationTestComponent.java +++ b/bramble-core/src/test/java/org/briarproject/bramble/plugin/file/RemovableDriveIntegrationTestComponent.java @@ -12,6 +12,7 @@ import org.briarproject.bramble.event.DefaultEventExecutorModule; 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.TestSecureRandomModule; import javax.inject.Singleton; @@ -25,6 +26,7 @@ import dagger.Component; DefaultEventExecutorModule.class, DefaultWakefulIoExecutorModule.class, TestDatabaseConfigModule.class, + TestFeatureFlagModule.class, RemovableDriveIntegrationTestModule.class, RemovableDriveModule.class, TestSecureRandomModule.class, diff --git a/bramble-core/src/test/java/org/briarproject/bramble/plugin/file/RemovableDriveIntegrationTestModule.java b/bramble-core/src/test/java/org/briarproject/bramble/plugin/file/RemovableDriveIntegrationTestModule.java index 7b4699e10..5f3e19842 100644 --- a/bramble-core/src/test/java/org/briarproject/bramble/plugin/file/RemovableDriveIntegrationTestModule.java +++ b/bramble-core/src/test/java/org/briarproject/bramble/plugin/file/RemovableDriveIntegrationTestModule.java @@ -1,6 +1,5 @@ package org.briarproject.bramble.plugin.file; -import org.briarproject.bramble.api.FeatureFlags; import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.plugin.PluginConfig; import org.briarproject.bramble.api.plugin.TransportId; @@ -52,30 +51,4 @@ class RemovableDriveIntegrationTestModule { }; return pluginConfig; } - - @Provides - FeatureFlags provideFeatureFlags() { - return new FeatureFlags() { - - @Override - public boolean shouldEnableImageAttachments() { - return true; - } - - @Override - public boolean shouldEnableProfilePictures() { - return true; - } - - @Override - public boolean shouldEnableDisappearingMessages() { - return true; - } - - @Override - public boolean shouldEnableConnectViaBluetooth() { - return true; - } - }; - } } diff --git a/bramble-core/src/test/java/org/briarproject/bramble/test/BrambleCoreIntegrationTestModule.java b/bramble-core/src/test/java/org/briarproject/bramble/test/BrambleCoreIntegrationTestModule.java index 22dea21f6..b735dab7d 100644 --- a/bramble-core/src/test/java/org/briarproject/bramble/test/BrambleCoreIntegrationTestModule.java +++ b/bramble-core/src/test/java/org/briarproject/bramble/test/BrambleCoreIntegrationTestModule.java @@ -1,48 +1,22 @@ package org.briarproject.bramble.test; -import org.briarproject.bramble.api.FeatureFlags; import org.briarproject.bramble.battery.DefaultBatteryManagerModule; import org.briarproject.bramble.event.DefaultEventExecutorModule; import org.briarproject.bramble.system.DefaultWakefulIoExecutorModule; import org.briarproject.bramble.system.TimeTravelModule; import dagger.Module; -import dagger.Provides; @Module(includes = { DefaultBatteryManagerModule.class, DefaultEventExecutorModule.class, DefaultWakefulIoExecutorModule.class, TestDatabaseConfigModule.class, + TestFeatureFlagModule.class, TestPluginConfigModule.class, TestSecureRandomModule.class, TimeTravelModule.class }) public class BrambleCoreIntegrationTestModule { - @Provides - FeatureFlags provideFeatureFlags() { - return new FeatureFlags() { - - @Override - public boolean shouldEnableImageAttachments() { - return true; - } - - @Override - public boolean shouldEnableProfilePictures() { - return true; - } - - @Override - public boolean shouldEnableDisappearingMessages() { - return true; - } - - @Override - public boolean shouldEnableConnectViaBluetooth() { - return true; - } - }; - } } diff --git a/bramble-core/src/test/java/org/briarproject/bramble/test/TestFeatureFlagModule.java b/bramble-core/src/test/java/org/briarproject/bramble/test/TestFeatureFlagModule.java new file mode 100644 index 000000000..3bac40b0f --- /dev/null +++ b/bramble-core/src/test/java/org/briarproject/bramble/test/TestFeatureFlagModule.java @@ -0,0 +1,34 @@ +package org.briarproject.bramble.test; + +import org.briarproject.bramble.api.FeatureFlags; + +import dagger.Module; +import dagger.Provides; + +@Module +public class TestFeatureFlagModule { + @Provides + FeatureFlags provideFeatureFlags() { + return new FeatureFlags() { + @Override + public boolean shouldEnableImageAttachments() { + return true; + } + + @Override + public boolean shouldEnableProfilePictures() { + return true; + } + + @Override + public boolean shouldEnableDisappearingMessages() { + return true; + } + + @Override + public boolean shouldEnableConnectViaBluetooth() { + return true; + } + }; + } +} diff --git a/briar-headless/src/test/java/org/briarproject/briar/headless/HeadlessTestModule.kt b/briar-headless/src/test/java/org/briarproject/briar/headless/HeadlessTestModule.kt index f31caf2e3..67171a977 100644 --- a/briar-headless/src/test/java/org/briarproject/briar/headless/HeadlessTestModule.kt +++ b/briar-headless/src/test/java/org/briarproject/briar/headless/HeadlessTestModule.kt @@ -4,7 +4,6 @@ import com.fasterxml.jackson.databind.ObjectMapper import dagger.Module 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.plugin.PluginConfig import org.briarproject.bramble.api.plugin.TransportId @@ -18,6 +17,7 @@ import org.briarproject.bramble.system.ClockModule import org.briarproject.bramble.system.DefaultTaskSchedulerModule import org.briarproject.bramble.system.DefaultWakefulIoExecutorModule import org.briarproject.bramble.system.JavaSystemModule +import org.briarproject.bramble.test.TestFeatureFlagModule import org.briarproject.bramble.test.TestSecureRandomModule import org.briarproject.briar.api.test.TestAvatarCreator import org.briarproject.briar.headless.blogs.HeadlessBlogModule @@ -40,6 +40,7 @@ import javax.inject.Singleton DefaultTaskSchedulerModule::class, DefaultWakefulIoExecutorModule::class, SocksModule::class, + TestFeatureFlagModule::class, TestSecureRandomModule::class, HeadlessBlogModule::class, HeadlessContactModule::class, @@ -78,14 +79,6 @@ internal class HeadlessTestModule(private val appDir: File) { @Singleton internal fun provideObjectMapper() = ObjectMapper() - @Provides - internal fun provideFeatureFlags() = object : FeatureFlags { - override fun shouldEnableImageAttachments() = false - override fun shouldEnableProfilePictures() = false - override fun shouldEnableDisappearingMessages() = false - override fun shouldEnableConnectViaBluetooth() = false - } - @Provides internal fun provideTestAvatarCreator() = TestAvatarCreator { null } } From a93b1f18ac11bdf4b1605a66ac6904f251666c35 Mon Sep 17 00:00:00 2001 From: Torsten Grote Date: Wed, 23 Jun 2021 17:16:50 -0300 Subject: [PATCH 52/77] Refactor base of BriarIntegrationTest into BrambleIntegrationTest --- bramble-core/build.gradle | 1 + .../bramble/test/BrambleIntegrationTest.java | 306 ++++++++++++++++++ .../test/BrambleIntegrationTestComponent.java | 30 ++ bramble-core/witness.gradle | 1 + .../briar/test/BriarIntegrationTest.java | 288 +---------------- .../test/BriarIntegrationTestComponent.java | 15 +- 6 files changed, 350 insertions(+), 291 deletions(-) create mode 100644 bramble-core/src/test/java/org/briarproject/bramble/test/BrambleIntegrationTest.java create mode 100644 bramble-core/src/test/java/org/briarproject/bramble/test/BrambleIntegrationTestComponent.java diff --git a/bramble-core/build.gradle b/bramble-core/build.gradle index ce33f1ad9..e1384472e 100644 --- a/bramble-core/build.gradle +++ b/bramble-core/build.gradle @@ -21,6 +21,7 @@ dependencies { testImplementation project(path: ':bramble-api', configuration: 'testOutput') testImplementation 'org.hsqldb:hsqldb:2.3.5' // The last version that supports Java 1.6 + testImplementation 'net.jodah:concurrentunit:0.4.2' testImplementation 'junit:junit:4.12' testImplementation "org.jmock:jmock:2.8.2" testImplementation "org.jmock:jmock-junit4:2.8.2" diff --git a/bramble-core/src/test/java/org/briarproject/bramble/test/BrambleIntegrationTest.java b/bramble-core/src/test/java/org/briarproject/bramble/test/BrambleIntegrationTest.java new file mode 100644 index 000000000..861494b77 --- /dev/null +++ b/bramble-core/src/test/java/org/briarproject/bramble/test/BrambleIntegrationTest.java @@ -0,0 +1,306 @@ +package org.briarproject.bramble.test; + +import net.jodah.concurrentunit.Waiter; + +import org.briarproject.bramble.api.FormatException; +import org.briarproject.bramble.api.client.ClientHelper; +import org.briarproject.bramble.api.contact.ContactId; +import org.briarproject.bramble.api.data.BdfList; +import org.briarproject.bramble.api.data.BdfStringUtils; +import org.briarproject.bramble.api.db.DbException; +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.nullsafety.MethodsNotNullByDefault; +import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault; +import org.briarproject.bramble.api.sync.MessageId; +import org.briarproject.bramble.api.sync.event.MessageStateChangedEvent; +import org.briarproject.bramble.api.sync.event.MessagesAckedEvent; +import org.briarproject.bramble.api.sync.event.MessagesSentEvent; +import org.junit.After; +import org.junit.Before; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.util.HashSet; +import java.util.Set; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.Executor; +import java.util.concurrent.Semaphore; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.logging.Logger; + +import javax.annotation.Nonnull; + +import static java.util.concurrent.Executors.newSingleThreadExecutor; +import static java.util.concurrent.TimeUnit.MILLISECONDS; +import static java.util.concurrent.TimeUnit.MINUTES; +import static java.util.logging.Level.WARNING; +import static java.util.logging.Logger.getLogger; +import static org.briarproject.bramble.api.sync.validation.MessageState.DELIVERED; +import static org.briarproject.bramble.api.sync.validation.MessageState.INVALID; +import static org.briarproject.bramble.api.sync.validation.MessageState.PENDING; +import static org.briarproject.bramble.test.TestPluginConfigModule.SIMPLEX_TRANSPORT_ID; +import static org.briarproject.bramble.util.LogUtils.logException; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +@MethodsNotNullByDefault +@ParametersNotNullByDefault +public abstract class BrambleIntegrationTest + extends BrambleTestCase { + + private static final Logger LOG = + getLogger(BrambleIntegrationTest.class.getName()); + + private static final boolean DEBUG = false; + + protected final static int TIMEOUT = 15000; + + // objects accessed from background threads need to be volatile + private volatile Waiter validationWaiter; + private volatile Waiter deliveryWaiter; + private volatile Waiter ackWaiter; + private volatile boolean expectAck = false; + + private final Semaphore messageSemaphore = new Semaphore(0); + private final AtomicInteger deliveryCounter = new AtomicInteger(0); + private final AtomicInteger validationCounter = new AtomicInteger(0); + private final AtomicInteger ackCounter = new AtomicInteger(0); + + protected final File testDir = TestUtils.getTestDirectory(); + + @Before + public void setUp() throws Exception { + assertTrue(testDir.mkdirs()); + + // initialize waiters fresh for each test + validationWaiter = new Waiter(); + deliveryWaiter = new Waiter(); + ackWaiter = new Waiter(); + deliveryCounter.set(0); + validationCounter.set(0); + ackCounter.set(0); + } + + @After + public void tearDown() throws Exception { + TestUtils.deleteTestDirectory(testDir); + } + + protected void addEventListener(C c) { + c.getEventBus().addListener(new Listener(c)); + } + + private class Listener implements EventListener { + + private final ClientHelper clientHelper; + private final Executor executor; + + private Listener(C c) { + clientHelper = c.getClientHelper(); + executor = newSingleThreadExecutor(); + } + + @Override + public void eventOccurred(Event e) { + if (e instanceof MessageStateChangedEvent) { + MessageStateChangedEvent event = (MessageStateChangedEvent) e; + if (!event.isLocal()) { + if (event.getState() == DELIVERED) { + LOG.info("Delivered new message " + + event.getMessageId()); + deliveryCounter.addAndGet(1); + loadAndLogMessage(event.getMessageId()); + deliveryWaiter.resume(); + } else if (event.getState() == INVALID || + event.getState() == PENDING) { + LOG.info("Validated new " + event.getState().name() + + " message " + event.getMessageId()); + validationCounter.addAndGet(1); + loadAndLogMessage(event.getMessageId()); + validationWaiter.resume(); + } + } + } else if (e instanceof MessagesAckedEvent && expectAck) { + MessagesAckedEvent event = (MessagesAckedEvent) e; + ackCounter.addAndGet(event.getMessageIds().size()); + for (MessageId m : event.getMessageIds()) { + loadAndLogMessage(m); + ackWaiter.resume(); + } + } + } + + private void loadAndLogMessage(MessageId id) { + executor.execute(() -> { + if (DEBUG) { + try { + BdfList body = clientHelper.getMessageAsList(id); + LOG.info("Contents of " + id + ":\n" + + BdfStringUtils.toString(body)); + } catch (DbException | FormatException e) { + logException(LOG, WARNING, e); + } + } + messageSemaphore.release(); + }); + } + } + + + protected void syncMessage(BrambleIntegrationTestComponent fromComponent, + BrambleIntegrationTestComponent toComponent, ContactId toId, + int num, boolean valid) throws Exception { + syncMessage(fromComponent, toComponent, toId, num, 0, valid ? 0 : num, + valid ? num : 0); + } + + protected void syncMessage(BrambleIntegrationTestComponent fromComponent, + BrambleIntegrationTestComponent toComponent, ContactId toId, + int numNew, int numDupes, int numPendingOrInvalid, int numDelivered) + throws Exception { + + // Debug output + String from = + fromComponent.getIdentityManager().getLocalAuthor().getName(); + String to = toComponent.getIdentityManager().getLocalAuthor().getName(); + LOG.info("TEST: Sending " + (numNew + numDupes) + " message(s) from " + + from + " to " + to); + + // Listen for messages being sent + waitForEvents(fromComponent); + SendListener sendListener = new SendListener(); + fromComponent.getEventBus().addListener(sendListener); + + // Write the messages to a transport stream + ByteArrayOutputStream out = new ByteArrayOutputStream(); + TestTransportConnectionWriter writer = + new TestTransportConnectionWriter(out, false); + fromComponent.getConnectionManager().manageOutgoingConnection(toId, + SIMPLEX_TRANSPORT_ID, writer); + writer.getDisposedLatch().await(TIMEOUT, MILLISECONDS); + + // Check that the expected number of messages were sent + waitForEvents(fromComponent); + fromComponent.getEventBus().removeListener(sendListener); + assertEquals("Messages sent", numNew + numDupes, + sendListener.sent.size()); + + // Read the messages from the transport stream + ByteArrayInputStream in = new ByteArrayInputStream(out.toByteArray()); + TestTransportConnectionReader reader = + new TestTransportConnectionReader(in); + toComponent.getConnectionManager().manageIncomingConnection( + SIMPLEX_TRANSPORT_ID, reader); + + if (numPendingOrInvalid > 0) { + validationWaiter.await(TIMEOUT, numPendingOrInvalid); + } + assertEquals("Messages validated", numPendingOrInvalid, + validationCounter.getAndSet(0)); + + if (numDelivered > 0) { + deliveryWaiter.await(TIMEOUT, numDelivered); + } + assertEquals("Messages delivered", numDelivered, + deliveryCounter.getAndSet(0)); + + try { + messageSemaphore.tryAcquire(numNew, TIMEOUT, MILLISECONDS); + } catch (InterruptedException e) { + LOG.info("Interrupted while waiting for messages"); + Thread.currentThread().interrupt(); + fail(); + } + } + + protected void sendAcks(BrambleIntegrationTestComponent fromComponent, + BrambleIntegrationTestComponent toComponent, ContactId toId, + int num) throws Exception { + // Debug output + String from = + fromComponent.getIdentityManager().getLocalAuthor().getName(); + String to = toComponent.getIdentityManager().getLocalAuthor().getName(); + LOG.info("TEST: Sending " + num + " ACKs from " + from + " to " + to); + + expectAck = true; + + // Listen for messages being sent (none should be sent) + waitForEvents(fromComponent); + SendListener sendListener = new SendListener(); + fromComponent.getEventBus().addListener(sendListener); + + // start outgoing connection + ByteArrayOutputStream out = new ByteArrayOutputStream(); + TestTransportConnectionWriter writer = + new TestTransportConnectionWriter(out, false); + fromComponent.getConnectionManager().manageOutgoingConnection(toId, + SIMPLEX_TRANSPORT_ID, writer); + writer.getDisposedLatch().await(TIMEOUT, MILLISECONDS); + + // Check that no messages were sent + waitForEvents(fromComponent); + fromComponent.getEventBus().removeListener(sendListener); + assertEquals("Messages sent", 0, sendListener.sent.size()); + + // handle incoming connection + ByteArrayInputStream in = new ByteArrayInputStream(out.toByteArray()); + TestTransportConnectionReader reader = + new TestTransportConnectionReader(in); + toComponent.getConnectionManager().manageIncomingConnection( + SIMPLEX_TRANSPORT_ID, reader); + + ackWaiter.await(TIMEOUT, num); + assertEquals("ACKs delivered", num, ackCounter.getAndSet(0)); + assertEquals("No messages delivered", 0, deliveryCounter.get()); + try { + messageSemaphore.tryAcquire(num, TIMEOUT, MILLISECONDS); + } catch (InterruptedException e) { + LOG.info("Interrupted while waiting for messages"); + Thread.currentThread().interrupt(); + fail(); + } finally { + expectAck = false; + } + } + + /** + * Broadcasts a marker event and waits for it to be delivered, which + * indicates that all previously broadcast events have been delivered. + */ + public static void waitForEvents(BrambleIntegrationTestComponent component) + throws Exception { + CountDownLatch latch = new CountDownLatch(1); + MarkerEvent marker = new MarkerEvent(); + EventBus eventBus = component.getEventBus(); + eventBus.addListener(new EventListener() { + @Override + public void eventOccurred(@Nonnull Event e) { + if (e == marker) { + latch.countDown(); + eventBus.removeListener(this); + } + } + }); + eventBus.broadcast(marker); + if (!latch.await(1, MINUTES)) fail(); + } + + private static class MarkerEvent extends Event { + } + + private static class SendListener implements EventListener { + + private final Set sent = new HashSet<>(); + + @Override + public void eventOccurred(Event e) { + if (e instanceof MessagesSentEvent) { + sent.addAll(((MessagesSentEvent) e).getMessageIds()); + } + } + } +} diff --git a/bramble-core/src/test/java/org/briarproject/bramble/test/BrambleIntegrationTestComponent.java b/bramble-core/src/test/java/org/briarproject/bramble/test/BrambleIntegrationTestComponent.java new file mode 100644 index 000000000..c7b0d80c5 --- /dev/null +++ b/bramble-core/src/test/java/org/briarproject/bramble/test/BrambleIntegrationTestComponent.java @@ -0,0 +1,30 @@ +package org.briarproject.bramble.test; + +import org.briarproject.bramble.BrambleCoreIntegrationTestEagerSingletons; +import org.briarproject.bramble.BrambleCoreModule; +import org.briarproject.bramble.api.client.ClientHelper; +import org.briarproject.bramble.api.connection.ConnectionManager; +import org.briarproject.bramble.api.event.EventBus; +import org.briarproject.bramble.api.identity.IdentityManager; + +import javax.inject.Singleton; + +import dagger.Component; + +@Singleton +@Component(modules = { + BrambleCoreIntegrationTestModule.class, + BrambleCoreModule.class +}) +public interface BrambleIntegrationTestComponent + extends BrambleCoreIntegrationTestEagerSingletons { + + IdentityManager getIdentityManager(); + + EventBus getEventBus(); + + ConnectionManager getConnectionManager(); + + ClientHelper getClientHelper(); + +} diff --git a/bramble-core/witness.gradle b/bramble-core/witness.gradle index 9ab2fac6d..949566231 100644 --- a/bramble-core/witness.gradle +++ b/bramble-core/witness.gradle @@ -20,6 +20,7 @@ dependencyVerification { 'javax.inject:javax.inject:1:javax.inject-1.jar:91c77044a50c481636c32d916fd89c9118a72195390452c81065080f957de7ff', 'junit:junit:4.12:junit-4.12.jar:59721f0805e223d84b90677887d9ff567dc534d7c502ca903c0c2b17f05c116a', 'net.i2p.crypto:eddsa:0.2.0:eddsa-0.2.0.jar:a7cb1b85c16e2f0730b9204106929a1d9aaae1df728adc7041a8b8b605692140', + 'net.jodah:concurrentunit:0.4.2:concurrentunit-0.4.2.jar:5583078e1acf91734939e985bc9e7ee947b0e93a8eef679da6bb07bbeb47ced3', 'net.ltgt.gradle.incap:incap:0.2:incap-0.2.jar:b625b9806b0f1e4bc7a2e3457119488de3cd57ea20feedd513db070a573a4ffd', 'org.apache.ant:ant-launcher:1.9.4:ant-launcher-1.9.4.jar:7bccea20b41801ca17bcbc909a78c835d0f443f12d639c77bd6ae3d05861608d', 'org.apache.ant:ant:1.9.4:ant-1.9.4.jar:649ae0730251de07b8913f49286d46bba7b92d47c5f332610aa426c4f02161d8', diff --git a/briar-core/src/test/java/org/briarproject/briar/test/BriarIntegrationTest.java b/briar-core/src/test/java/org/briarproject/briar/test/BriarIntegrationTest.java index e493b4fa5..899565f1f 100644 --- a/briar-core/src/test/java/org/briarproject/briar/test/BriarIntegrationTest.java +++ b/briar-core/src/test/java/org/briarproject/briar/test/BriarIntegrationTest.java @@ -1,8 +1,5 @@ package org.briarproject.briar.test; -import net.jodah.concurrentunit.Waiter; - -import org.briarproject.bramble.api.FormatException; import org.briarproject.bramble.api.client.ClientHelper; import org.briarproject.bramble.api.client.ContactGroupFactory; import org.briarproject.bramble.api.contact.Contact; @@ -10,13 +7,8 @@ import org.briarproject.bramble.api.contact.ContactId; import org.briarproject.bramble.api.contact.ContactManager; import org.briarproject.bramble.api.crypto.CryptoComponent; import org.briarproject.bramble.api.crypto.SecretKey; -import org.briarproject.bramble.api.data.BdfList; -import org.briarproject.bramble.api.data.BdfStringUtils; import org.briarproject.bramble.api.db.DatabaseComponent; import org.briarproject.bramble.api.db.DbException; -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.identity.Identity; import org.briarproject.bramble.api.identity.IdentityManager; import org.briarproject.bramble.api.identity.LocalAuthor; @@ -25,11 +17,7 @@ import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault; import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault; import org.briarproject.bramble.api.sync.MessageFactory; import org.briarproject.bramble.api.sync.MessageId; -import org.briarproject.bramble.api.sync.event.MessageStateChangedEvent; -import org.briarproject.bramble.api.sync.event.MessagesAckedEvent; -import org.briarproject.bramble.api.sync.event.MessagesSentEvent; -import org.briarproject.bramble.test.TestTransportConnectionReader; -import org.briarproject.bramble.test.TestTransportConnectionWriter; +import org.briarproject.bramble.test.BrambleIntegrationTest; import org.briarproject.bramble.test.TestUtils; import org.briarproject.briar.api.autodelete.AutoDeleteManager; import org.briarproject.briar.api.blog.BlogFactory; @@ -43,48 +31,19 @@ import org.briarproject.briar.api.privategroup.invitation.GroupInvitationFactory import org.junit.After; import org.junit.Before; -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; import java.io.File; -import java.util.HashSet; -import java.util.Set; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.Executor; -import java.util.concurrent.Semaphore; -import java.util.concurrent.atomic.AtomicInteger; -import java.util.logging.Logger; -import javax.annotation.Nonnull; import javax.annotation.Nullable; import javax.inject.Inject; -import static java.util.concurrent.Executors.newSingleThreadExecutor; -import static java.util.concurrent.TimeUnit.MILLISECONDS; -import static java.util.concurrent.TimeUnit.MINUTES; -import static java.util.logging.Level.WARNING; -import static java.util.logging.Logger.getLogger; import static junit.framework.Assert.assertNotNull; -import static org.briarproject.bramble.api.sync.validation.MessageState.DELIVERED; -import static org.briarproject.bramble.api.sync.validation.MessageState.INVALID; -import static org.briarproject.bramble.api.sync.validation.MessageState.PENDING; -import static org.briarproject.bramble.test.TestPluginConfigModule.SIMPLEX_TRANSPORT_ID; import static org.briarproject.bramble.test.TestUtils.getSecretKey; -import static org.briarproject.bramble.util.LogUtils.logException; import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; -import static org.junit.Assert.fail; @MethodsNotNullByDefault @ParametersNotNullByDefault public abstract class BriarIntegrationTest - extends BriarTestCase { - - private static final Logger LOG = - getLogger(BriarIntegrationTest.class.getName()); - - private static final boolean DEBUG = false; - - protected final static int TIMEOUT = 15000; + extends BrambleIntegrationTest { @Nullable protected ContactId contactId1From2, contactId2From1; @@ -94,7 +53,7 @@ public abstract class BriarIntegrationTest { - if (DEBUG) { - try { - BdfList body = clientHelper.getMessageAsList(id); - LOG.info("Contents of " + id + ":\n" - + BdfStringUtils.toString(body)); - } catch (DbException | FormatException e) { - logException(LOG, WARNING, e); - } - } - messageSemaphore.release(); - }); - } + addEventListener(c0); + addEventListener(c1); + addEventListener(c2); } private void createAndRegisterIdentities() { @@ -335,9 +218,10 @@ public abstract class BriarIntegrationTest 0) { - validationWaiter.await(TIMEOUT, numPendingOrInvalid); - } - assertEquals("Messages validated", numPendingOrInvalid, - validationCounter.getAndSet(0)); - - if (numDelivered > 0) { - deliveryWaiter.await(TIMEOUT, numDelivered); - } - assertEquals("Messages delivered", numDelivered, - deliveryCounter.getAndSet(0)); - - try { - messageSemaphore.tryAcquire(numNew, TIMEOUT, MILLISECONDS); - } catch (InterruptedException e) { - LOG.info("Interrupted while waiting for messages"); - Thread.currentThread().interrupt(); - fail(); - } - } - - protected void sendAcks(BriarIntegrationTestComponent fromComponent, - BriarIntegrationTestComponent toComponent, ContactId toId, int num) - throws Exception { - // Debug output - String from = - fromComponent.getIdentityManager().getLocalAuthor().getName(); - String to = toComponent.getIdentityManager().getLocalAuthor().getName(); - LOG.info("TEST: Sending " + num + " ACKs from " + from + " to " + to); - - expectAck = true; - - // Listen for messages being sent (none should be sent) - waitForEvents(fromComponent); - SendListener sendListener = new SendListener(); - fromComponent.getEventBus().addListener(sendListener); - - // start outgoing connection - ByteArrayOutputStream out = new ByteArrayOutputStream(); - TestTransportConnectionWriter writer = - new TestTransportConnectionWriter(out, false); - fromComponent.getConnectionManager().manageOutgoingConnection(toId, - SIMPLEX_TRANSPORT_ID, writer); - writer.getDisposedLatch().await(TIMEOUT, MILLISECONDS); - - // Check that no messages were sent - waitForEvents(fromComponent); - fromComponent.getEventBus().removeListener(sendListener); - assertEquals("Messages sent", 0, sendListener.sent.size()); - - // handle incoming connection - ByteArrayInputStream in = new ByteArrayInputStream(out.toByteArray()); - TestTransportConnectionReader reader = - new TestTransportConnectionReader(in); - toComponent.getConnectionManager().manageIncomingConnection( - SIMPLEX_TRANSPORT_ID, reader); - - ackWaiter.await(TIMEOUT, num); - assertEquals("ACKs delivered", num, ackCounter.getAndSet(0)); - assertEquals("No messages delivered", 0, deliveryCounter.get()); - try { - messageSemaphore.tryAcquire(num, TIMEOUT, MILLISECONDS); - } catch (InterruptedException e) { - LOG.info("Interrupted while waiting for messages"); - Thread.currentThread().interrupt(); - fail(); - } finally { - expectAck = false; - } - } - protected void removeAllContacts() throws DbException { contactManager0.removeContact(contactId1From0); contactManager0.removeContact(contactId2From0); @@ -562,40 +330,4 @@ public abstract class BriarIntegrationTest db.setMessageShared(txn, messageId)); } - /** - * Broadcasts a marker event and waits for it to be delivered, which - * indicates that all previously broadcast events have been delivered. - */ - public static void waitForEvents(BriarIntegrationTestComponent component) - throws Exception { - CountDownLatch latch = new CountDownLatch(1); - MarkerEvent marker = new MarkerEvent(); - EventBus eventBus = component.getEventBus(); - eventBus.addListener(new EventListener() { - @Override - public void eventOccurred(@Nonnull Event e) { - if (e == marker) { - latch.countDown(); - eventBus.removeListener(this); - } - } - }); - eventBus.broadcast(marker); - if (!latch.await(1, MINUTES)) fail(); - } - - private static class MarkerEvent extends Event { - } - - private static class SendListener implements EventListener { - - private final Set sent = new HashSet<>(); - - @Override - public void eventOccurred(Event e) { - if (e instanceof MessagesSentEvent) { - sent.addAll(((MessagesSentEvent) e).getMessageIds()); - } - } - } } diff --git a/briar-core/src/test/java/org/briarproject/briar/test/BriarIntegrationTestComponent.java b/briar-core/src/test/java/org/briarproject/briar/test/BriarIntegrationTestComponent.java index 738b48e96..7c529d949 100644 --- a/briar-core/src/test/java/org/briarproject/briar/test/BriarIntegrationTestComponent.java +++ b/briar-core/src/test/java/org/briarproject/briar/test/BriarIntegrationTestComponent.java @@ -2,17 +2,14 @@ package org.briarproject.briar.test; import org.briarproject.bramble.BrambleCoreIntegrationTestEagerSingletons; import org.briarproject.bramble.BrambleCoreModule; -import org.briarproject.bramble.api.client.ClientHelper; -import org.briarproject.bramble.api.connection.ConnectionManager; import org.briarproject.bramble.api.contact.ContactManager; import org.briarproject.bramble.api.db.DatabaseComponent; -import org.briarproject.bramble.api.event.EventBus; import org.briarproject.bramble.api.identity.AuthorFactory; -import org.briarproject.bramble.api.identity.IdentityManager; import org.briarproject.bramble.api.lifecycle.LifecycleManager; import org.briarproject.bramble.api.properties.TransportPropertyManager; import org.briarproject.bramble.api.system.Clock; import org.briarproject.bramble.test.BrambleCoreIntegrationTestModule; +import org.briarproject.bramble.test.BrambleIntegrationTestComponent; import org.briarproject.bramble.test.TimeTravel; import org.briarproject.briar.api.attachment.AttachmentReader; import org.briarproject.briar.api.autodelete.AutoDeleteManager; @@ -67,7 +64,7 @@ import dagger.Component; SharingModule.class }) public interface BriarIntegrationTestComponent - extends BrambleCoreIntegrationTestEagerSingletons { + extends BrambleIntegrationTestComponent { void inject(BriarIntegrationTest init); @@ -95,16 +92,10 @@ public interface BriarIntegrationTestComponent LifecycleManager getLifecycleManager(); - EventBus getEventBus(); - - IdentityManager getIdentityManager(); - AttachmentReader getAttachmentReader(); AvatarManager getAvatarManager(); - ClientHelper getClientHelper(); - ContactManager getContactManager(); ConversationManager getConversationManager(); @@ -139,8 +130,6 @@ public interface BriarIntegrationTestComponent BlogFactory getBlogFactory(); - ConnectionManager getConnectionManager(); - AutoDeleteManager getAutoDeleteManager(); Clock getClock(); From abe570e905916964d715aa065a849acb18278fa6 Mon Sep 17 00:00:00 2001 From: Torsten Grote Date: Wed, 23 Jun 2021 14:34:53 -0300 Subject: [PATCH 53/77] Add first integration test for TransportKeyAgreementManager --- .../bramble/test/BrambleIntegrationTest.java | 15 ++ .../bramble/test/TestPluginConfigModule.java | 20 +- .../TransportKeyAgreementIntegrationTest.java | 196 ++++++++++++++++++ .../TransportKeyAgreementTestComponent.java | 29 +++ 4 files changed, 256 insertions(+), 4 deletions(-) create mode 100644 bramble-core/src/test/java/org/briarproject/bramble/transport/agreement/TransportKeyAgreementIntegrationTest.java create mode 100644 bramble-core/src/test/java/org/briarproject/bramble/transport/agreement/TransportKeyAgreementTestComponent.java diff --git a/bramble-core/src/test/java/org/briarproject/bramble/test/BrambleIntegrationTest.java b/bramble-core/src/test/java/org/briarproject/bramble/test/BrambleIntegrationTest.java index 861494b77..cc5f17ed2 100644 --- a/bramble-core/src/test/java/org/briarproject/bramble/test/BrambleIntegrationTest.java +++ b/bramble-core/src/test/java/org/briarproject/bramble/test/BrambleIntegrationTest.java @@ -28,6 +28,7 @@ import java.util.Set; import java.util.concurrent.CountDownLatch; import java.util.concurrent.Executor; import java.util.concurrent.Semaphore; +import java.util.concurrent.TimeoutException; import java.util.concurrent.atomic.AtomicInteger; import java.util.logging.Logger; @@ -217,6 +218,20 @@ public abstract class BrambleIntegrationTest { + + private final File aliceDir = new File(testDir, "alice"); + private final File bobDir = new File(testDir, "bob"); + private final SecretKey masterKey = getSecretKey(); + private final long timestamp = System.currentTimeMillis(); + private final TransportId newTransportId = + new TransportId(getRandomString(8)); + + private TransportKeyAgreementTestComponent alice, bob; + + @Before + @Override + public void setUp() throws Exception { + super.setUp(); + // Create the devices + alice = createComponent(aliceDir, false); + bob = createComponent(bobDir, false); + + // Start both lifecycles + startLifecycle(alice, "Alice"); + startLifecycle(bob, "Bob"); + } + + private TransportKeyAgreementTestComponent createComponent( + File dir, boolean useNewTransport) { + TestPluginConfigModule pluginConfigModule = useNewTransport ? + new TestPluginConfigModule(SIMPLEX_TRANSPORT_ID, newTransportId) + : new TestPluginConfigModule(); + TransportKeyAgreementTestComponent c = + DaggerTransportKeyAgreementTestComponent.builder() + .testDatabaseConfigModule( + new TestDatabaseConfigModule(dir)) + .testPluginConfigModule(pluginConfigModule) + .build(); + BrambleCoreIntegrationTestEagerSingletons.Helper + .injectEagerSingletons(c); + return c; + } + + private void startLifecycle( + TransportKeyAgreementTestComponent device, + String identityName) throws Exception { + // Listen to message related events first to not miss early ones + addEventListener(device); + // Add an identity for the user + IdentityManager identityManager = device.getIdentityManager(); + Identity identity = identityManager.createIdentity(identityName); + identityManager.registerIdentity(identity); + // Start the lifecycle manager + LifecycleManager lifecycleManager = device.getLifecycleManager(); + lifecycleManager.startServices(masterKey); // re-using masterKey here + lifecycleManager.waitForStartup(); + } + + @After + @Override + public void tearDown() throws Exception { + tearDown(alice); + tearDown(bob); + super.tearDown(); + } + + private void tearDown(TransportKeyAgreementTestComponent device) + throws Exception { + // Stop the lifecycle manager + LifecycleManager lifecycleManager = device.getLifecycleManager(); + lifecycleManager.stopServices(); + lifecycleManager.waitForShutdown(); + } + + @Test + public void testAliceAddsTransportBeforeBob() throws Exception { + // Alice and Bob add each other. + Pair contactIds = addContacts(); + ContactId aliceId = contactIds.getFirst(); + ContactId bobId = contactIds.getSecond(); + + // Alice restarts and comes back with the new transport. + alice = restartWithNewTransport(alice, aliceDir, "Alice"); + + // Alice can still send via the old simplex, + // but not via the new duplex transport + assertTrue(alice.getKeyManager() + .canSendOutgoingStreams(bobId, SIMPLEX_TRANSPORT_ID)); + assertFalse(alice.getKeyManager() + .canSendOutgoingStreams(bobId, newTransportId)); + + // Alice has started a session and sends KEY message to Bob + // which he can't read, as he doesn't support the new transport, yet. + syncMessage(alice, bob, bobId, 1, false); + + // Bob restarts and comes back with the new transport. + bob = restartWithNewTransport(bob, bobDir, "Bob"); + + // Alice's pending KEY message now gets delivered async, so wait for it + awaitPendingMessageDelivery(1); + + // Bobs now and sends his own KEY as well as his ACTIVATE message. + syncMessage(bob, alice, aliceId, 2, true); + + // Alice can already send over the new transport while Bob still can't. + assertTrue(alice.getKeyManager() + .canSendOutgoingStreams(bobId, newTransportId)); + assertFalse(bob.getKeyManager() + .canSendOutgoingStreams(aliceId, newTransportId)); + + // Now Alice sends her ACTIVATE message to Bob. + syncMessage(alice, bob, bobId, 1, true); + + // Now Bob can also send over the new transport. + assertTrue(alice.getKeyManager() + .canSendOutgoingStreams(bobId, newTransportId)); + assertTrue(bob.getKeyManager() + .canSendOutgoingStreams(aliceId, newTransportId)); + } + + private Pair addContacts() throws Exception { + ContactId bobId = addContact(alice, bob, true); + ContactId aliceId = addContact(bob, alice, false); + + // Alice and Bob can send messages via the default test transports + assertTrue(alice.getKeyManager() + .canSendOutgoingStreams(bobId, SIMPLEX_TRANSPORT_ID)); + assertTrue(alice.getKeyManager() + .canSendOutgoingStreams(bobId, DUPLEX_TRANSPORT_ID)); + assertTrue(bob.getKeyManager() + .canSendOutgoingStreams(aliceId, SIMPLEX_TRANSPORT_ID)); + assertTrue(bob.getKeyManager() + .canSendOutgoingStreams(aliceId, DUPLEX_TRANSPORT_ID)); + + // Sync initial client versioning updates + syncMessage(alice, bob, bobId, 1, true); + syncMessage(bob, alice, aliceId, 1, true); + syncMessage(alice, bob, bobId, 1, true); + sendAcks(bob, alice, aliceId, 1); + + return new Pair<>(aliceId, bobId); + } + + private ContactId addContact( + TransportKeyAgreementTestComponent device, + TransportKeyAgreementTestComponent remote, + boolean alice) throws Exception { + // Get remote Author + Author remoteAuthor = remote.getIdentityManager().getLocalAuthor(); + // Get ID of LocalAuthor + IdentityManager identityManager = device.getIdentityManager(); + AuthorId localAuthorId = identityManager.getLocalAuthor().getId(); + // Add the other user as a contact + ContactManager contactManager = device.getContactManager(); + return contactManager.addContact(remoteAuthor, localAuthorId, masterKey, + timestamp, alice, true, true); + } + + private TransportKeyAgreementTestComponent restartWithNewTransport( + TransportKeyAgreementTestComponent device, File dir, String name) + throws Exception { + tearDown(device); + TransportKeyAgreementTestComponent newDevice = + createComponent(dir, true); + startLifecycle(newDevice, name); + return newDevice; + } + +} diff --git a/bramble-core/src/test/java/org/briarproject/bramble/transport/agreement/TransportKeyAgreementTestComponent.java b/bramble-core/src/test/java/org/briarproject/bramble/transport/agreement/TransportKeyAgreementTestComponent.java new file mode 100644 index 000000000..6c8a5d0c6 --- /dev/null +++ b/bramble-core/src/test/java/org/briarproject/bramble/transport/agreement/TransportKeyAgreementTestComponent.java @@ -0,0 +1,29 @@ +package org.briarproject.bramble.transport.agreement; + +import org.briarproject.bramble.BrambleCoreModule; +import org.briarproject.bramble.api.contact.ContactManager; +import org.briarproject.bramble.api.lifecycle.LifecycleManager; +import org.briarproject.bramble.api.transport.KeyManager; +import org.briarproject.bramble.test.BrambleCoreIntegrationTestModule; +import org.briarproject.bramble.test.BrambleIntegrationTestComponent; + +import javax.inject.Singleton; + +import dagger.Component; + +@Singleton +@Component(modules = { + BrambleCoreIntegrationTestModule.class, + BrambleCoreModule.class +}) +interface TransportKeyAgreementTestComponent + extends BrambleIntegrationTestComponent { + + KeyManager getKeyManager(); + + TransportKeyAgreementManagerImpl getTransportKeyAgreementManager(); + + ContactManager getContactManager(); + + LifecycleManager getLifecycleManager(); +} From 195123e6696713e60295c0f303752f0485e32e5f Mon Sep 17 00:00:00 2001 From: Torsten Grote Date: Thu, 24 Jun 2021 11:22:47 -0300 Subject: [PATCH 54/77] Ensure that private key is not stored anymore --- .../TransportKeyAgreementIntegrationTest.java | 39 +++++++++++++++++++ .../TransportKeyAgreementTestComponent.java | 5 +++ 2 files changed, 44 insertions(+) diff --git a/bramble-core/src/test/java/org/briarproject/bramble/transport/agreement/TransportKeyAgreementIntegrationTest.java b/bramble-core/src/test/java/org/briarproject/bramble/transport/agreement/TransportKeyAgreementIntegrationTest.java index e37ebc353..44b400e60 100644 --- a/bramble-core/src/test/java/org/briarproject/bramble/transport/agreement/TransportKeyAgreementIntegrationTest.java +++ b/bramble-core/src/test/java/org/briarproject/bramble/transport/agreement/TransportKeyAgreementIntegrationTest.java @@ -2,15 +2,19 @@ package org.briarproject.bramble.transport.agreement; import org.briarproject.bramble.BrambleCoreIntegrationTestEagerSingletons; import org.briarproject.bramble.api.Pair; +import org.briarproject.bramble.api.contact.Contact; import org.briarproject.bramble.api.contact.ContactId; import org.briarproject.bramble.api.contact.ContactManager; import org.briarproject.bramble.api.crypto.SecretKey; +import org.briarproject.bramble.api.data.BdfDictionary; import org.briarproject.bramble.api.identity.Author; import org.briarproject.bramble.api.identity.AuthorId; import org.briarproject.bramble.api.identity.Identity; import org.briarproject.bramble.api.identity.IdentityManager; import org.briarproject.bramble.api.lifecycle.LifecycleManager; import org.briarproject.bramble.api.plugin.TransportId; +import org.briarproject.bramble.api.sync.Group; +import org.briarproject.bramble.api.sync.MessageId; import org.briarproject.bramble.test.BrambleIntegrationTest; import org.briarproject.bramble.test.TestDatabaseConfigModule; import org.briarproject.bramble.test.TestPluginConfigModule; @@ -19,12 +23,17 @@ import org.junit.Before; import org.junit.Test; import java.io.File; +import java.util.Map; +import static org.briarproject.bramble.api.transport.agreement.TransportKeyAgreementManager.CLIENT_ID; +import static org.briarproject.bramble.api.transport.agreement.TransportKeyAgreementManager.MAJOR_VERSION; import static org.briarproject.bramble.test.TestPluginConfigModule.DUPLEX_TRANSPORT_ID; import static org.briarproject.bramble.test.TestPluginConfigModule.SIMPLEX_TRANSPORT_ID; import static org.briarproject.bramble.test.TestUtils.getSecretKey; +import static org.briarproject.bramble.transport.agreement.TransportKeyAgreementConstants.MSG_KEY_IS_SESSION; import static org.briarproject.bramble.util.StringUtils.getRandomString; import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; public class TransportKeyAgreementIntegrationTest @@ -143,6 +152,10 @@ public class TransportKeyAgreementIntegrationTest .canSendOutgoingStreams(bobId, newTransportId)); assertTrue(bob.getKeyManager() .canSendOutgoingStreams(aliceId, newTransportId)); + + // Ensure that private key is not stored anymore. + assertLocalKeyPairIsNull(alice, bobId); + assertLocalKeyPairIsNull(bob, aliceId); } private Pair addContacts() throws Exception { @@ -193,4 +206,30 @@ public class TransportKeyAgreementIntegrationTest return newDevice; } + /** + * Asserts that the local key pair (specifically the private key) is removed + * from the session as intended when leaving the AWAIT_KEY state. + * If it remained on disk after the keys had been activated + * then we'd lose forward secrecy. + */ + private void assertLocalKeyPairIsNull( + TransportKeyAgreementTestComponent device, ContactId contactId) + throws Exception { + Contact contact = device.getContactManager().getContact(contactId); + Group group = getContactGroup(device, contact); + Map map = bob.getClientHelper() + .getMessageMetadataAsDictionary(group.getId()); + for (Map.Entry e : map.entrySet()) { + if (!e.getValue().getBoolean(MSG_KEY_IS_SESSION)) continue; + Session s = device.getSessionParser().parseSession(e.getValue()); + assertNull(s.getLocalKeyPair()); + } + } + + private Group getContactGroup(TransportKeyAgreementTestComponent device, + Contact c) { + return device.getContactGroupFactory().createContactGroup(CLIENT_ID, + MAJOR_VERSION, c); + } + } diff --git a/bramble-core/src/test/java/org/briarproject/bramble/transport/agreement/TransportKeyAgreementTestComponent.java b/bramble-core/src/test/java/org/briarproject/bramble/transport/agreement/TransportKeyAgreementTestComponent.java index 6c8a5d0c6..a2bd0d280 100644 --- a/bramble-core/src/test/java/org/briarproject/bramble/transport/agreement/TransportKeyAgreementTestComponent.java +++ b/bramble-core/src/test/java/org/briarproject/bramble/transport/agreement/TransportKeyAgreementTestComponent.java @@ -1,6 +1,7 @@ package org.briarproject.bramble.transport.agreement; import org.briarproject.bramble.BrambleCoreModule; +import org.briarproject.bramble.api.client.ContactGroupFactory; import org.briarproject.bramble.api.contact.ContactManager; import org.briarproject.bramble.api.lifecycle.LifecycleManager; import org.briarproject.bramble.api.transport.KeyManager; @@ -26,4 +27,8 @@ interface TransportKeyAgreementTestComponent ContactManager getContactManager(); LifecycleManager getLifecycleManager(); + + ContactGroupFactory getContactGroupFactory(); + + SessionParser getSessionParser(); } From e8428925ae5ac8b8999e213c5ddf54e6e371bc5a Mon Sep 17 00:00:00 2001 From: Torsten Grote Date: Thu, 24 Jun 2021 12:26:45 -0300 Subject: [PATCH 55/77] Add two more tests to TransportKeyAgreementIntegrationTest --- .../TransportKeyAgreementIntegrationTest.java | 117 ++++++++++++++++-- 1 file changed, 106 insertions(+), 11 deletions(-) diff --git a/bramble-core/src/test/java/org/briarproject/bramble/transport/agreement/TransportKeyAgreementIntegrationTest.java b/bramble-core/src/test/java/org/briarproject/bramble/transport/agreement/TransportKeyAgreementIntegrationTest.java index 44b400e60..c2dacedc6 100644 --- a/bramble-core/src/test/java/org/briarproject/bramble/transport/agreement/TransportKeyAgreementIntegrationTest.java +++ b/bramble-core/src/test/java/org/briarproject/bramble/transport/agreement/TransportKeyAgreementIntegrationTest.java @@ -108,10 +108,58 @@ public class TransportKeyAgreementIntegrationTest lifecycleManager.waitForShutdown(); } + @Test + public void testBothAddTransportAtTheSameTime() throws Exception { + // Alice and Bob add each other. + Pair contactIds = addContacts(true); + ContactId aliceId = contactIds.getFirst(); + ContactId bobId = contactIds.getSecond(); + + // Alice and Bob restart and come back with the new transport. + alice = restartWithNewTransport(alice, aliceDir, "Alice"); + bob = restartWithNewTransport(bob, bobDir, "Bob"); + + // They can still send via the old simplex, + // but not via the new duplex transport + assertTrue(alice.getKeyManager() + .canSendOutgoingStreams(bobId, SIMPLEX_TRANSPORT_ID)); + assertFalse(alice.getKeyManager() + .canSendOutgoingStreams(bobId, newTransportId)); + assertTrue(bob.getKeyManager() + .canSendOutgoingStreams(aliceId, SIMPLEX_TRANSPORT_ID)); + assertFalse(bob.getKeyManager() + .canSendOutgoingStreams(aliceId, newTransportId)); + + // Bobs has started a session and sends KEY message to Alice + syncMessage(bob, alice, aliceId, 1, true); + + // Alice now and sends her own KEY as well as her ACTIVATE message. + syncMessage(alice, bob, bobId, 2, true); + + // Bob can already send over the new transport while Alice still can't. + assertFalse(alice.getKeyManager() + .canSendOutgoingStreams(bobId, newTransportId)); + assertTrue(bob.getKeyManager() + .canSendOutgoingStreams(aliceId, newTransportId)); + + // Now Bob sends his ACTIVATE message to Alice. + syncMessage(bob, alice, aliceId, 1, true); + + // Now Alice can also send over the new transport. + assertTrue(alice.getKeyManager() + .canSendOutgoingStreams(bobId, newTransportId)); + assertTrue(bob.getKeyManager() + .canSendOutgoingStreams(aliceId, newTransportId)); + + // Ensure that private key is not stored anymore. + assertLocalKeyPairIsNull(alice, bobId); + assertLocalKeyPairIsNull(bob, aliceId); + } + @Test public void testAliceAddsTransportBeforeBob() throws Exception { // Alice and Bob add each other. - Pair contactIds = addContacts(); + Pair contactIds = addContacts(true); ContactId aliceId = contactIds.getFirst(); ContactId bobId = contactIds.getSecond(); @@ -135,7 +183,7 @@ public class TransportKeyAgreementIntegrationTest // Alice's pending KEY message now gets delivered async, so wait for it awaitPendingMessageDelivery(1); - // Bobs now and sends his own KEY as well as his ACTIVATE message. + // Bob now sends his own KEY as well as his ACTIVATE message. syncMessage(bob, alice, aliceId, 2, true); // Alice can already send over the new transport while Bob still can't. @@ -158,19 +206,66 @@ public class TransportKeyAgreementIntegrationTest assertLocalKeyPairIsNull(bob, aliceId); } - private Pair addContacts() throws Exception { + @Test + public void testAliceAlreadyHasTransportWhenAddingBob() throws Exception { + // Alice restarts and comes back with the new transport. + alice = restartWithNewTransport(alice, aliceDir, "Alice"); + + // Alice and Bob add each other. + Pair contactIds = addContacts(false); + ContactId aliceId = contactIds.getFirst(); + ContactId bobId = contactIds.getSecond(); + + // Alice can still send via the old simplex, + // but not via the new duplex transport + assertTrue(alice.getKeyManager() + .canSendOutgoingStreams(bobId, SIMPLEX_TRANSPORT_ID)); + // FIXME normally Alice should not be able to already send streams +// assertFalse(alice.getKeyManager() +// .canSendOutgoingStreams(bobId, newTransportId)); + + // Bob restarts and comes back with the new transport. + bob = restartWithNewTransport(bob, bobDir, "Bob"); + + // Bob sends his own KEY message. + syncMessage(bob, alice, aliceId, 1, true); + + // Alice can already send over the new transport while Bob still can't. + assertTrue(alice.getKeyManager() + .canSendOutgoingStreams(bobId, newTransportId)); + assertFalse(bob.getKeyManager() + .canSendOutgoingStreams(aliceId, newTransportId)); + + // Now Alice sends her KEY and her ACTIVATE message to Bob. + syncMessage(alice, bob, bobId, 2, true); + + // Now Bob can also send over the new transport. + assertTrue(alice.getKeyManager() + .canSendOutgoingStreams(bobId, newTransportId)); + assertTrue(bob.getKeyManager() + .canSendOutgoingStreams(aliceId, newTransportId)); + + // Ensure that private key is not stored anymore. + assertLocalKeyPairIsNull(alice, bobId); + assertLocalKeyPairIsNull(bob, aliceId); + } + + private Pair addContacts( + boolean assertOldDuplexSending) throws Exception { ContactId bobId = addContact(alice, bob, true); ContactId aliceId = addContact(bob, alice, false); // Alice and Bob can send messages via the default test transports - assertTrue(alice.getKeyManager() - .canSendOutgoingStreams(bobId, SIMPLEX_TRANSPORT_ID)); - assertTrue(alice.getKeyManager() - .canSendOutgoingStreams(bobId, DUPLEX_TRANSPORT_ID)); - assertTrue(bob.getKeyManager() - .canSendOutgoingStreams(aliceId, SIMPLEX_TRANSPORT_ID)); - assertTrue(bob.getKeyManager() - .canSendOutgoingStreams(aliceId, DUPLEX_TRANSPORT_ID)); + if (assertOldDuplexSending) { + assertTrue(alice.getKeyManager() + .canSendOutgoingStreams(bobId, SIMPLEX_TRANSPORT_ID)); + assertTrue(alice.getKeyManager() + .canSendOutgoingStreams(bobId, DUPLEX_TRANSPORT_ID)); + assertTrue(bob.getKeyManager() + .canSendOutgoingStreams(aliceId, SIMPLEX_TRANSPORT_ID)); + assertTrue(bob.getKeyManager() + .canSendOutgoingStreams(aliceId, DUPLEX_TRANSPORT_ID)); + } // Sync initial client versioning updates syncMessage(alice, bob, bobId, 1, true); From ccec17f28a2931e6a79941f5357c1544a941c5fd Mon Sep 17 00:00:00 2001 From: Torsten Grote Date: Thu, 24 Jun 2021 15:57:19 -0300 Subject: [PATCH 56/77] Also test that messages arrive and activate keys --- .../bramble/connection/Connection.java | 1 - .../bramble/test/BrambleIntegrationTest.java | 19 ++- .../TransportKeyAgreementIntegrationTest.java | 119 +++++++++++++++--- .../TransportKeyAgreementTestComponent.java | 6 + 4 files changed, 125 insertions(+), 20 deletions(-) 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 1efe2d480..9ca5c672e 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 @@ -45,7 +45,6 @@ abstract class Connection { @Nullable StreamContext recogniseTag(TransportConnectionReader reader, TransportId transportId) { - StreamContext ctx; try { byte[] tag = readTag(reader.getInputStream()); return keyManager.getStreamContext(transportId, tag); diff --git a/bramble-core/src/test/java/org/briarproject/bramble/test/BrambleIntegrationTest.java b/bramble-core/src/test/java/org/briarproject/bramble/test/BrambleIntegrationTest.java index cc5f17ed2..68e9fe39b 100644 --- a/bramble-core/src/test/java/org/briarproject/bramble/test/BrambleIntegrationTest.java +++ b/bramble-core/src/test/java/org/briarproject/bramble/test/BrambleIntegrationTest.java @@ -13,6 +13,7 @@ import org.briarproject.bramble.api.event.EventBus; import org.briarproject.bramble.api.event.EventListener; import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault; import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault; +import org.briarproject.bramble.api.plugin.TransportId; import org.briarproject.bramble.api.sync.MessageId; import org.briarproject.bramble.api.sync.event.MessageStateChangedEvent; import org.briarproject.bramble.api.sync.event.MessagesAckedEvent; @@ -152,6 +153,13 @@ public abstract class BrambleIntegrationTest 0) { validationWaiter.await(TIMEOUT, numPendingOrInvalid); diff --git a/bramble-core/src/test/java/org/briarproject/bramble/transport/agreement/TransportKeyAgreementIntegrationTest.java b/bramble-core/src/test/java/org/briarproject/bramble/transport/agreement/TransportKeyAgreementIntegrationTest.java index c2dacedc6..fef3e82e3 100644 --- a/bramble-core/src/test/java/org/briarproject/bramble/transport/agreement/TransportKeyAgreementIntegrationTest.java +++ b/bramble-core/src/test/java/org/briarproject/bramble/transport/agreement/TransportKeyAgreementIntegrationTest.java @@ -7,12 +7,14 @@ import org.briarproject.bramble.api.contact.ContactId; import org.briarproject.bramble.api.contact.ContactManager; import org.briarproject.bramble.api.crypto.SecretKey; import org.briarproject.bramble.api.data.BdfDictionary; +import org.briarproject.bramble.api.db.DatabaseComponent; import org.briarproject.bramble.api.identity.Author; import org.briarproject.bramble.api.identity.AuthorId; import org.briarproject.bramble.api.identity.Identity; import org.briarproject.bramble.api.identity.IdentityManager; import org.briarproject.bramble.api.lifecycle.LifecycleManager; import org.briarproject.bramble.api.plugin.TransportId; +import org.briarproject.bramble.api.properties.TransportProperties; import org.briarproject.bramble.api.sync.Group; import org.briarproject.bramble.api.sync.MessageId; import org.briarproject.bramble.test.BrambleIntegrationTest; @@ -47,6 +49,7 @@ public class TransportKeyAgreementIntegrationTest new TransportId(getRandomString(8)); private TransportKeyAgreementTestComponent alice, bob; + private Identity aliceIdentity, bobIdentity; @Before @Override @@ -56,9 +59,13 @@ public class TransportKeyAgreementIntegrationTest alice = createComponent(aliceDir, false); bob = createComponent(bobDir, false); + // Create identities + aliceIdentity = alice.getIdentityManager().createIdentity("Alice"); + bobIdentity = bob.getIdentityManager().createIdentity("Bob"); + // Start both lifecycles - startLifecycle(alice, "Alice"); - startLifecycle(bob, "Bob"); + startLifecycle(alice, aliceIdentity); + startLifecycle(bob, bobIdentity); } private TransportKeyAgreementTestComponent createComponent( @@ -79,13 +86,11 @@ public class TransportKeyAgreementIntegrationTest private void startLifecycle( TransportKeyAgreementTestComponent device, - String identityName) throws Exception { + Identity identity) throws Exception { // Listen to message related events first to not miss early ones addEventListener(device); - // Add an identity for the user - IdentityManager identityManager = device.getIdentityManager(); - Identity identity = identityManager.createIdentity(identityName); - identityManager.registerIdentity(identity); + // Register identity before starting lifecycle + device.getIdentityManager().registerIdentity(identity); // Start the lifecycle manager LifecycleManager lifecycleManager = device.getLifecycleManager(); lifecycleManager.startServices(masterKey); // re-using masterKey here @@ -116,8 +121,8 @@ public class TransportKeyAgreementIntegrationTest ContactId bobId = contactIds.getSecond(); // Alice and Bob restart and come back with the new transport. - alice = restartWithNewTransport(alice, aliceDir, "Alice"); - bob = restartWithNewTransport(bob, bobDir, "Bob"); + alice = restartWithNewTransport(alice, aliceDir, aliceIdentity); + bob = restartWithNewTransport(bob, bobDir, bobIdentity); // They can still send via the old simplex, // but not via the new duplex transport @@ -154,6 +159,10 @@ public class TransportKeyAgreementIntegrationTest // Ensure that private key is not stored anymore. assertLocalKeyPairIsNull(alice, bobId); assertLocalKeyPairIsNull(bob, aliceId); + + // Messages can be send over the new transport in both directions. + assertTransportMessageArrives(alice, bob, bobId, newTransportId); + assertTransportMessageArrives(bob, alice, aliceId, newTransportId); } @Test @@ -164,7 +173,7 @@ public class TransportKeyAgreementIntegrationTest ContactId bobId = contactIds.getSecond(); // Alice restarts and comes back with the new transport. - alice = restartWithNewTransport(alice, aliceDir, "Alice"); + alice = restartWithNewTransport(alice, aliceDir, aliceIdentity); // Alice can still send via the old simplex, // but not via the new duplex transport @@ -178,7 +187,7 @@ public class TransportKeyAgreementIntegrationTest syncMessage(alice, bob, bobId, 1, false); // Bob restarts and comes back with the new transport. - bob = restartWithNewTransport(bob, bobDir, "Bob"); + bob = restartWithNewTransport(bob, bobDir, bobIdentity); // Alice's pending KEY message now gets delivered async, so wait for it awaitPendingMessageDelivery(1); @@ -204,12 +213,16 @@ public class TransportKeyAgreementIntegrationTest // Ensure that private key is not stored anymore. assertLocalKeyPairIsNull(alice, bobId); assertLocalKeyPairIsNull(bob, aliceId); + + // Messages can be send over the new transport in both directions. + assertTransportMessageArrives(alice, bob, bobId, newTransportId); + assertTransportMessageArrives(bob, alice, aliceId, newTransportId); } @Test public void testAliceAlreadyHasTransportWhenAddingBob() throws Exception { // Alice restarts and comes back with the new transport. - alice = restartWithNewTransport(alice, aliceDir, "Alice"); + alice = restartWithNewTransport(alice, aliceDir, aliceIdentity); // Alice and Bob add each other. Pair contactIds = addContacts(false); @@ -225,7 +238,7 @@ public class TransportKeyAgreementIntegrationTest // .canSendOutgoingStreams(bobId, newTransportId)); // Bob restarts and comes back with the new transport. - bob = restartWithNewTransport(bob, bobDir, "Bob"); + bob = restartWithNewTransport(bob, bobDir, bobIdentity); // Bob sends his own KEY message. syncMessage(bob, alice, aliceId, 1, true); @@ -248,6 +261,68 @@ public class TransportKeyAgreementIntegrationTest // Ensure that private key is not stored anymore. assertLocalKeyPairIsNull(alice, bobId); assertLocalKeyPairIsNull(bob, aliceId); + + // Bobs still sends his ACTIVATE message. + syncMessage(bob, alice, aliceId, 1, true); + + // Messages can be send over the new transport in both directions. + assertTransportMessageArrives(alice, bob, bobId, newTransportId); + assertTransportMessageArrives(bob, alice, aliceId, newTransportId); + } + + @Test + public void testAliceActivatesKeysByIncomingMessage() throws Exception { + // Alice and Bob add each other. + Pair contactIds = addContacts(true); + ContactId aliceId = contactIds.getFirst(); + ContactId bobId = contactIds.getSecond(); + + // Alice and Bob restart and come back with the new transport. + alice = restartWithNewTransport(alice, aliceDir, aliceIdentity); + bob = restartWithNewTransport(bob, bobDir, bobIdentity); + + // They can still send via the old simplex, + // but not via the new duplex transport + assertTrue(alice.getKeyManager() + .canSendOutgoingStreams(bobId, SIMPLEX_TRANSPORT_ID)); + assertFalse(alice.getKeyManager() + .canSendOutgoingStreams(bobId, newTransportId)); + assertTrue(bob.getKeyManager() + .canSendOutgoingStreams(aliceId, SIMPLEX_TRANSPORT_ID)); + assertFalse(bob.getKeyManager() + .canSendOutgoingStreams(aliceId, newTransportId)); + + // Bobs has started a session and sends KEY message to Alice + syncMessage(bob, alice, aliceId, 1, true); + + // Alice now and sends her own KEY as well as her ACTIVATE message. + syncMessage(alice, bob, bobId, 2, true); + + // Bob can already send over the new transport while Alice still can't. + assertFalse(alice.getKeyManager() + .canSendOutgoingStreams(bobId, newTransportId)); + assertTrue(bob.getKeyManager() + .canSendOutgoingStreams(aliceId, newTransportId)); + + // Bob's database mysteriously loses the ACTIVATE message, + // so it won't be send to Alice. + Contact contact = bob.getContactManager().getContact(aliceId); + Group group = getContactGroup(bob, contact); + Map map = bob.getClientHelper() + .getMessageMetadataAsDictionary(group.getId()); + DatabaseComponent db = bob.getDatabaseComponent(); + for (Map.Entry e : map.entrySet()) { + if (e.getValue().getBoolean(MSG_KEY_IS_SESSION)) continue; + db.transaction(false, txn -> db.removeMessage(txn, e.getKey())); + } + + // Bob sends a message to Alice + assertTransportMessageArrives(bob, alice, aliceId, newTransportId); + + // Now without receiving the ACTIVATE, Alice can already send to Bob + assertTrue(alice.getKeyManager() + .canSendOutgoingStreams(bobId, newTransportId)); + assertTransportMessageArrives(alice, bob, bobId, newTransportId); } private Pair addContacts( @@ -292,12 +367,12 @@ public class TransportKeyAgreementIntegrationTest } private TransportKeyAgreementTestComponent restartWithNewTransport( - TransportKeyAgreementTestComponent device, File dir, String name) - throws Exception { + TransportKeyAgreementTestComponent device, File dir, + Identity identity) throws Exception { tearDown(device); TransportKeyAgreementTestComponent newDevice = createComponent(dir, true); - startLifecycle(newDevice, name); + startLifecycle(newDevice, identity); return newDevice; } @@ -312,7 +387,7 @@ public class TransportKeyAgreementIntegrationTest throws Exception { Contact contact = device.getContactManager().getContact(contactId); Group group = getContactGroup(device, contact); - Map map = bob.getClientHelper() + Map map = device.getClientHelper() .getMessageMetadataAsDictionary(group.getId()); for (Map.Entry e : map.entrySet()) { if (!e.getValue().getBoolean(MSG_KEY_IS_SESSION)) continue; @@ -327,4 +402,14 @@ public class TransportKeyAgreementIntegrationTest MAJOR_VERSION, c); } + private void assertTransportMessageArrives( + TransportKeyAgreementTestComponent from, + TransportKeyAgreementTestComponent to, ContactId toId, + TransportId transportId) throws Exception { + TransportProperties p = new TransportProperties(); + p.putBoolean("foo", true); + from.getTransportPropertyManager().mergeLocalProperties(transportId, p); + syncMessage(from, to, toId, transportId, 1, true); + } + } diff --git a/bramble-core/src/test/java/org/briarproject/bramble/transport/agreement/TransportKeyAgreementTestComponent.java b/bramble-core/src/test/java/org/briarproject/bramble/transport/agreement/TransportKeyAgreementTestComponent.java index a2bd0d280..a0de3c5e4 100644 --- a/bramble-core/src/test/java/org/briarproject/bramble/transport/agreement/TransportKeyAgreementTestComponent.java +++ b/bramble-core/src/test/java/org/briarproject/bramble/transport/agreement/TransportKeyAgreementTestComponent.java @@ -3,7 +3,9 @@ package org.briarproject.bramble.transport.agreement; import org.briarproject.bramble.BrambleCoreModule; import org.briarproject.bramble.api.client.ContactGroupFactory; import org.briarproject.bramble.api.contact.ContactManager; +import org.briarproject.bramble.api.db.DatabaseComponent; import org.briarproject.bramble.api.lifecycle.LifecycleManager; +import org.briarproject.bramble.api.properties.TransportPropertyManager; import org.briarproject.bramble.api.transport.KeyManager; import org.briarproject.bramble.test.BrambleCoreIntegrationTestModule; import org.briarproject.bramble.test.BrambleIntegrationTestComponent; @@ -31,4 +33,8 @@ interface TransportKeyAgreementTestComponent ContactGroupFactory getContactGroupFactory(); SessionParser getSessionParser(); + + TransportPropertyManager getTransportPropertyManager(); + + DatabaseComponent getDatabaseComponent(); } From be3700d3646d728b1c4a92d5e1512ec9658e9f72 Mon Sep 17 00:00:00 2001 From: Torsten Grote Date: Wed, 30 Jun 2021 16:57:32 -0300 Subject: [PATCH 57/77] Remove FIXME in test since we won't fix it this way --- .../agreement/TransportKeyAgreementIntegrationTest.java | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/bramble-core/src/test/java/org/briarproject/bramble/transport/agreement/TransportKeyAgreementIntegrationTest.java b/bramble-core/src/test/java/org/briarproject/bramble/transport/agreement/TransportKeyAgreementIntegrationTest.java index fef3e82e3..d16b6052c 100644 --- a/bramble-core/src/test/java/org/briarproject/bramble/transport/agreement/TransportKeyAgreementIntegrationTest.java +++ b/bramble-core/src/test/java/org/briarproject/bramble/transport/agreement/TransportKeyAgreementIntegrationTest.java @@ -233,9 +233,12 @@ public class TransportKeyAgreementIntegrationTest // but not via the new duplex transport assertTrue(alice.getKeyManager() .canSendOutgoingStreams(bobId, SIMPLEX_TRANSPORT_ID)); - // FIXME normally Alice should not be able to already send streams -// assertFalse(alice.getKeyManager() -// .canSendOutgoingStreams(bobId, newTransportId)); + // Normally, Alice should not be able to send streams already. + // However, she does already derive keys for the transport. + // The UI checks RemovableDriveManager#isTransportSupportedByContact() + // in practice to prevent sending streams that Bob can't decrypt. + assertTrue(alice.getKeyManager() + .canSendOutgoingStreams(bobId, newTransportId)); // Bob restarts and comes back with the new transport. bob = restartWithNewTransport(bob, bobDir, bobIdentity); From 7e3eb1201a0a81d085902e3b7c146d8f88bc5ba7 Mon Sep 17 00:00:00 2001 From: Daniel Lublin Date: Mon, 7 Jun 2021 13:20:28 +0200 Subject: [PATCH 58/77] Start of UI for transfer data feature --- briar-android/src/main/AndroidManifest.xml | 9 + .../android/activity/ActivityComponent.java | 3 + .../briar/android/activity/RequestCodes.java | 2 + .../conversation/ConversationActivity.java | 7 +- .../RemovableDriveActivity.java | 174 ++++++++++++++++++ .../res/layout/activity_removable_drive.xml | 52 ++++++ .../main/res/menu/conversation_actions.xml | 5 + 7 files changed, 251 insertions(+), 1 deletion(-) create mode 100644 briar-android/src/main/java/org/briarproject/briar/android/removabledrive/RemovableDriveActivity.java create mode 100644 briar-android/src/main/res/layout/activity_removable_drive.xml diff --git a/briar-android/src/main/AndroidManifest.xml b/briar-android/src/main/AndroidManifest.xml index feeec1655..5d41da90b 100644 --- a/briar-android/src/main/AndroidManifest.xml +++ b/briar-android/src/main/AndroidManifest.xml @@ -437,6 +437,15 @@ android:value="org.briarproject.briar.android.navdrawer.NavDrawerActivity" /> + + + + createDocument = + registerForActivityResult( + new ActivityResultContracts.CreateDocument(), + uri -> write(contactId, uri)); + writeButton.setOnClickListener( + v -> createDocument.launch(viewModel.getFileName())); + + readButton.setText("Read for contactId " + contactId); + ActivityResultLauncher getContent = + registerForActivityResult( + new ActivityResultContracts.GetContent(), + uri -> read(contactId, uri)); + readButton.setOnClickListener( + v -> getContent.launch("application/octet-stream")); + + LiveData state; + state = viewModel.ongoingWrite(new ContactId(contactId)); + if (state == null) { + writeButton.setEnabled(true); + } else { + say("\nOngoing write:"); + writeButton.setEnabled(false); + state.observe(this, (taskState) -> handleState("write", taskState)); + } + state = viewModel.ongoingRead(new ContactId(contactId)); + if (state == null) { + readButton.setEnabled(true); + } else { + say("\nOngoing read:"); + readButton.setEnabled(false); + state.observe(this, (taskState) -> handleState("read", taskState)); + } + } + + @Override + public boolean onOptionsItemSelected(@NonNull MenuItem item) { + if (item.getItemId() == android.R.id.home) { + onBackPressed(); + return true; + } + return super.onOptionsItemSelected(item); + } + + private void write(int contactId, @Nullable Uri uri) { + if (contactId == -1) { + throw new IllegalStateException(); + } + if (uri == null) { + say("no URI picked for write"); + return; + } + say("\nWriting to URI: " + uri); + writeButton.setEnabled(false); + LiveData state = viewModel.write(new ContactId(contactId), uri); + state.observe(this, (taskState) -> handleState("write", taskState)); + } + + private void read(int contactId, @Nullable Uri uri) { + if (contactId == -1) { + throw new IllegalStateException(); + } + if (uri == null) { + say("no URI picked for read"); + return; + } + say("\nReading from URI: " + uri); + readButton.setEnabled(false); + LiveData state = viewModel.read(new ContactId(contactId), uri); + state.observe(this, (taskState) -> handleState("read", taskState)); + } + + private void handleState(String action, State taskState) { + say(String.format(Locale.getDefault(), + "%s: bytes done: %d of %d. %s. %s.", + action, taskState.getDone(), taskState.getTotal(), + taskState.isFinished() ? "Finished" : "Ongoing", + taskState.isFinished() ? + (taskState.isSuccess() ? "Success" : "Failed") : "..")); + if (taskState.isFinished()) { + if (action.equals("write")) { + writeButton.setEnabled(true); + } else if (action.equals("read")) { + readButton.setEnabled(true); + } + } + } + + private void say(String txt) { + String time = new SimpleDateFormat("HH:mm:ss", Locale.getDefault()) + .format(new Date()); + txt = String.format("%s %s\n", time, txt); + text.setText(text.getText().toString().concat(txt)); + } +} diff --git a/briar-android/src/main/res/layout/activity_removable_drive.xml b/briar-android/src/main/res/layout/activity_removable_drive.xml new file mode 100644 index 000000000..3b91a49f2 --- /dev/null +++ b/briar-android/src/main/res/layout/activity_removable_drive.xml @@ -0,0 +1,52 @@ + + + + + +