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 5f4f27398849c27a29f78020db7f743557c39fdc..08b5859d9376c71c6c38ed06856019ec3f36fb02 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 @@ -648,15 +648,11 @@ interface Database<T> { void setTransportKeysActive(T txn, TransportId t, KeySetId k) throws DbException; - void updateLastSentTime(Connection txn, ContactId c, MessageId m) - throws DbException; - /** - * Updates the transmission count and expiry time of the given message - * with respect to the given contact, using the latency of the transport - * over which it was sent. + * Updates the transmission count and the lastSentTme of the given message + * with respect to the given contact. */ - void updateExpiryTime(T txn, ContactId c, MessageId m, int maxLatency) + void updateLastSentTime(T txn, ContactId c, MessageId m) throws DbException; /** 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 bae66c12caf616e656451b78736723f20a02f31d..45e7ed3e86fd521599746fae3e8937993474a17b 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 @@ -313,11 +313,12 @@ class DatabaseComponentImpl<T> implements DatabaseComponent { T txn = unbox(transaction); if (!db.containsContact(txn, c)) throw new NoSuchContactException(); - Collection<MessageId> ids = db.getMessagesToSend(txn, c, maxLength, maxLatency); + Collection<MessageId> ids = + db.getMessagesToSend(txn, c, maxLength, maxLatency); List<byte[]> messages = new ArrayList<>(ids.size()); for (MessageId m : ids) { messages.add(db.getRawMessage(txn, m)); - db.updateExpiryTime(txn, c, m, maxLatency); + db.updateLastSentTime(txn, c, m); } if (ids.isEmpty()) return null; db.lowerRequestedFlag(txn, c, ids); @@ -333,9 +334,10 @@ class DatabaseComponentImpl<T> implements DatabaseComponent { T txn = unbox(transaction); if (!db.containsContact(txn, c)) throw new NoSuchContactException(); - Collection<MessageId> ids = db.getMessagesToOffer(txn, c, maxMessages, maxLatency); + Collection<MessageId> ids = + db.getMessagesToOffer(txn, c, maxMessages, maxLatency); if (ids.isEmpty()) return null; - for (MessageId m : ids) db.updateExpiryTime(txn, c, m, maxLatency); + for (MessageId m : ids) db.updateLastSentTime(txn, c, m); return new Offer(ids); } @@ -367,7 +369,7 @@ class DatabaseComponentImpl<T> implements DatabaseComponent { List<byte[]> messages = new ArrayList<>(ids.size()); for (MessageId m : ids) { messages.add(db.getRawMessage(txn, m)); - db.updateExpiryTime(txn, c, m, maxLatency); + db.updateLastSentTime(txn, c, m); } if (ids.isEmpty()) return null; db.lowerRequestedFlag(txn, c, ids); @@ -593,7 +595,8 @@ class DatabaseComponentImpl<T> implements DatabaseComponent { } @Override - public long getNextSendTime(Transaction transaction, ContactId c, int maxLatency) + public long getNextSendTime(Transaction transaction, ContactId c, + int maxLatency) throws DbException { T txn = unbox(transaction); return db.getNextSendTime(txn, c, maxLatency); diff --git a/bramble-core/src/main/java/org/briarproject/bramble/db/ExponentialBackoff.java b/bramble-core/src/main/java/org/briarproject/bramble/db/ExponentialBackoff.java index 0e2f89bcfa857148d1baa1be9cdedfcf5664e832..b1a9506e93933e7f00a47eeddae0293d68549f7c 100644 --- a/bramble-core/src/main/java/org/briarproject/bramble/db/ExponentialBackoff.java +++ b/bramble-core/src/main/java/org/briarproject/bramble/db/ExponentialBackoff.java @@ -15,6 +15,7 @@ class ExponentialBackoff { if (now < 0) throw new IllegalArgumentException(); if (maxLatency <= 0) throw new IllegalArgumentException(); if (txCount < 0) throw new IllegalArgumentException(); + if(now == 0) return 0; // The maximum round-trip time is twice the maximum latency long roundTrip = maxLatency * 2L; // The interval between transmissions is roundTrip * 2 ^ txCount 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 53a34fe4b30f0069b29120e3cd5dcaae0562ad54..afb8f1c32686a75a3348d66e3be8dd1ecdab5842 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 @@ -36,6 +36,7 @@ import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.HashMap; @@ -76,7 +77,7 @@ import static org.briarproject.bramble.util.LogUtils.logException; abstract class JdbcDatabase implements Database<Connection> { // Package access for testing - static final int CODE_SCHEMA_VERSION = 39; + static final int CODE_SCHEMA_VERSION = 40; // Rotation period offsets for incoming transport keys private static final int OFFSET_PREV = -1; @@ -214,7 +215,7 @@ abstract class JdbcDatabase implements Database<Connection> { + " ack BOOLEAN NOT NULL," + " seen BOOLEAN NOT NULL," + " requested BOOLEAN NOT NULL," - + " expiry BIGINT NOT NULL," + + " lastSentTime BIGINT NOT NULL," + " txCount INT NOT NULL," + " PRIMARY KEY (messageId, contactId)," + " FOREIGN KEY (messageId)" @@ -391,7 +392,7 @@ abstract class JdbcDatabase implements Database<Connection> { // Package access for testing List<Migration<Connection>> getMigrations() { - return singletonList(new Migration38_39()); + return Arrays.asList(new Migration38_39(), new Migration39_40()); } private void storeSchemaVersion(Connection txn, int version) @@ -799,7 +800,7 @@ abstract class JdbcDatabase implements Database<Connection> { try { String sql = "INSERT INTO statuses (messageId, contactId, groupId," + " timestamp, length, state, groupShared, messageShared," - + " deleted, ack, seen, requested, expiry, txCount)" + + " deleted, ack, seen, requested, lastSentTime, txCount)" + " VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, FALSE, 0, 0)"; ps = txn.prepareStatement(sql); ps.setBytes(1, m.getBytes()); @@ -1844,7 +1845,7 @@ abstract class JdbcDatabase implements Database<Connection> { + " AND groupShared = TRUE AND messageShared = TRUE" + " AND deleted = FALSE" + " AND seen = FALSE AND requested = FALSE" - + " AND (lastSentTime + ? * POWER(2,txCount+1)) > ?" + + " AND ( lastSentTime = 0 OR (lastSentTime + ? * POWER(2,txCount+1)) < ?)" + " ORDER BY timestamp LIMIT ?"; ps = txn.prepareStatement(sql); ps.setInt(1, c.getInt()); @@ -1902,7 +1903,7 @@ abstract class JdbcDatabase implements Database<Connection> { + " AND groupShared = TRUE AND messageShared = TRUE" + " AND deleted = FALSE" + " AND seen = FALSE" - + " AND (lastSentTime + ? * POWER(2,txCount+1)) > ?" + + " AND ( lastSentTime = 0 OR (lastSentTime + ? * POWER(2,txCount+1)) < ?)" + " ORDER BY timestamp"; ps = txn.prepareStatement(sql); ps.setInt(1, c.getInt()); @@ -1996,25 +1997,25 @@ abstract class JdbcDatabase implements Database<Connection> { PreparedStatement ps = null; ResultSet rs = null; try { - String sql = "SELECT lastSendTime, txCount FROM statuses" + String sql = "SELECT lastSentTime, txCount FROM statuses" + " WHERE contactId = ? AND state = ?" + " AND groupShared = TRUE AND messageShared = TRUE" + " AND deleted = FALSE AND seen = FALSE" - + " ORDER BY lastSendTime LIMIT 1"; + + " ORDER BY lastSentTime LIMIT 1"; ps = txn.prepareStatement(sql); ps.setInt(1, c.getInt()); ps.setInt(2, DELIVERED.getValue()); rs = ps.executeQuery(); - long lastSendTime = Long.MAX_VALUE; + long lastSentTime = Long.MAX_VALUE; int txCount = Integer.MAX_VALUE; if (rs.next()) { - lastSendTime = rs.getLong(1); + lastSentTime = rs.getLong(1); txCount = rs.getInt(2); if (rs.next()) throw new AssertionError(); } rs.close(); ps.close(); - return calculateExpiry(lastSendTime, maxLatency, txCount); + return calculateExpiry(lastSentTime, maxLatency, txCount); } catch (SQLException e) { tryToClose(rs); tryToClose(ps); @@ -2058,7 +2059,7 @@ abstract class JdbcDatabase implements Database<Connection> { + " AND groupShared = TRUE AND messageShared = TRUE" + " AND deleted = FALSE" + " AND seen = FALSE AND requested = TRUE" - + " AND expiry < ?" + + " AND lastSendTime < ?" + " ORDER BY timestamp"; ps = txn.prepareStatement(sql); ps.setInt(1, c.getInt()); @@ -2672,7 +2673,7 @@ abstract class JdbcDatabase implements Database<Connection> { throws DbException { PreparedStatement ps = null; try { - String sql = "UPDATE statuses SET expiry = 0, txCount = 0" + String sql = "UPDATE statuses SET lastSentTime = 0, txCount = 0" + " WHERE messageId = ? AND contactId = ?"; ps = txn.prepareStatement(sql); ps.setBytes(1, m.getBytes()); @@ -2894,41 +2895,6 @@ abstract class JdbcDatabase implements Database<Connection> { } } - - @Override - public void updateExpiryTime(Connection txn, ContactId c, MessageId m, - int maxLatency) throws DbException { - PreparedStatement ps = null; - ResultSet rs = null; - try { - String sql = "SELECT txCount FROM statuses" - + " WHERE messageId = ? AND contactId = ?"; - ps = txn.prepareStatement(sql); - ps.setBytes(1, m.getBytes()); - ps.setInt(2, c.getInt()); - rs = ps.executeQuery(); - if (!rs.next()) throw new DbStateException(); - int txCount = rs.getInt(1); - if (rs.next()) throw new DbStateException(); - rs.close(); - ps.close(); - sql = "UPDATE statuses SET expiry = ?, txCount = txCount + 1" - + " WHERE messageId = ? AND contactId = ?"; - ps = txn.prepareStatement(sql); - long now = clock.currentTimeMillis(); - ps.setLong(1, calculateExpiry(now, maxLatency, txCount)); - ps.setBytes(2, m.getBytes()); - ps.setInt(3, c.getInt()); - int affected = ps.executeUpdate(); - if (affected != 1) throw new DbStateException(); - ps.close(); - } catch (SQLException e) { - tryToClose(rs); - tryToClose(ps); - throw new DbException(e); - } - } - @Override public void updateTransportKeys(Connection txn, KeySet ks) throws DbException { diff --git a/bramble-core/src/main/java/org/briarproject/bramble/db/Migration39_40.java b/bramble-core/src/main/java/org/briarproject/bramble/db/Migration39_40.java new file mode 100644 index 0000000000000000000000000000000000000000..9cf32a54e7503267de6479de34aec3cabb3c7607 --- /dev/null +++ b/bramble-core/src/main/java/org/briarproject/bramble/db/Migration39_40.java @@ -0,0 +1,51 @@ +package org.briarproject.bramble.db; + +import org.briarproject.bramble.api.db.DbException; + +import java.sql.Connection; +import java.sql.SQLException; +import java.sql.Statement; +import java.util.logging.Logger; + +import javax.annotation.Nullable; + +import static java.util.logging.Level.WARNING; +import static org.briarproject.bramble.util.LogUtils.logException; + +class Migration39_40 implements Migration<Connection> { + + private static final Logger LOG = + Logger.getLogger(Migration39_40.class.getName()); + + @Override + public int getStartVersion() { + return 39; + } + + @Override + public int getEndVersion() { + return 40; + } + + @Override + public void migrate(Connection txn) throws DbException { + Statement s = null; + try { + s = txn.createStatement(); + // Add not null constraints + s.execute("ALTER TABLE state" + + " ALTER COLUMN expiry RENAME TO lastSentTime"); + } catch (SQLException e) { + tryToClose(s); + throw new DbException(e); + } + } + + private void tryToClose(@Nullable Statement s) { + try { + if (s != null) s.close(); + } catch (SQLException e) { + logException(LOG, WARNING, e); + } + } +} diff --git a/bramble-core/src/main/java/org/briarproject/bramble/sync/DuplexOutgoingSession.java b/bramble-core/src/main/java/org/briarproject/bramble/sync/DuplexOutgoingSession.java index f4109a48899f06af331f454931d1d21b1ef162ec..c65e563761c752193ab2672399a5394608768598 100644 --- a/bramble-core/src/main/java/org/briarproject/bramble/sync/DuplexOutgoingSession.java +++ b/bramble-core/src/main/java/org/briarproject/bramble/sync/DuplexOutgoingSession.java @@ -279,7 +279,7 @@ class DuplexOutgoingSession implements SyncSession, EventListener { try { b = db.generateRequestedBatch(txn, contactId, MAX_RECORD_PAYLOAD_BYTES, maxLatency); - setNextSendTime(db.getNextSendTime(txn, contactId)); + setNextSendTime(db.getNextSendTime(txn, contactId, maxLatency)); db.commitTransaction(txn); } finally { db.endTransaction(txn); @@ -326,7 +326,7 @@ class DuplexOutgoingSession implements SyncSession, EventListener { try { o = db.generateOffer(txn, contactId, MAX_MESSAGE_IDS, maxLatency); - setNextSendTime(db.getNextSendTime(txn, contactId)); + setNextSendTime(db.getNextSendTime(txn, contactId, maxLatency)); db.commitTransaction(txn); } finally { db.endTransaction(txn); 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 676cac2e3708b0465afe2bc08ccaf3ff7a308988..ecaa30a7b6b4c0727d6bf67973ea42e4a74aeb52 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 @@ -873,16 +873,14 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase { will(returnValue(txn)); oneOf(database).containsContact(txn, contactId); will(returnValue(true)); - oneOf(database).getMessagesToSend(txn, contactId, size * 2); + oneOf(database).getMessagesToSend(txn, contactId, size * 2, maxLatency); will(returnValue(ids)); oneOf(database).getRawMessage(txn, messageId); will(returnValue(raw)); - oneOf(database).updateExpiryTime(txn, contactId, messageId, - maxLatency); + oneOf(database).updateLastSentTime(txn, contactId, messageId); oneOf(database).getRawMessage(txn, messageId1); will(returnValue(raw1)); - oneOf(database).updateExpiryTime(txn, contactId, messageId1, - maxLatency); + oneOf(database).updateLastSentTime(txn, contactId, messageId1); oneOf(database).lowerRequestedFlag(txn, contactId, ids); oneOf(database).commitTransaction(txn); oneOf(eventBus).broadcast(with(any(MessagesSentEvent.class))); @@ -909,12 +907,10 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase { will(returnValue(txn)); oneOf(database).containsContact(txn, contactId); will(returnValue(true)); - oneOf(database).getMessagesToOffer(txn, contactId, 123); + oneOf(database).getMessagesToOffer(txn, contactId, 123, maxLatency); will(returnValue(ids)); - oneOf(database).updateExpiryTime(txn, contactId, messageId, - maxLatency); - oneOf(database).updateExpiryTime(txn, contactId, messageId1, - maxLatency); + oneOf(database).updateLastSentTime(txn, contactId, messageId); + oneOf(database).updateLastSentTime(txn, contactId, messageId1); oneOf(database).commitTransaction(txn); }}); DatabaseComponent db = createDatabaseComponent(database, eventBus, @@ -974,12 +970,10 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase { will(returnValue(ids)); oneOf(database).getRawMessage(txn, messageId); will(returnValue(raw)); - oneOf(database).updateExpiryTime(txn, contactId, messageId, - maxLatency); + oneOf(database).updateLastSentTime(txn, contactId, messageId); oneOf(database).getRawMessage(txn, messageId1); will(returnValue(raw1)); - oneOf(database).updateExpiryTime(txn, contactId, messageId1, - maxLatency); + oneOf(database).updateLastSentTime(txn, contactId, messageId1); oneOf(database).lowerRequestedFlag(txn, contactId, ids); oneOf(database).commitTransaction(txn); oneOf(eventBus).broadcast(with(any(MessagesSentEvent.class))); diff --git a/bramble-core/src/test/java/org/briarproject/bramble/db/DatabasePerformanceTest.java b/bramble-core/src/test/java/org/briarproject/bramble/db/DatabasePerformanceTest.java index e46a75b68460ba81166b20aabbfcddcbca1fe4b8..9fb1fd07c451edd579024d3e3af093c917887924 100644 --- a/bramble-core/src/test/java/org/briarproject/bramble/db/DatabasePerformanceTest.java +++ b/bramble-core/src/test/java/org/briarproject/bramble/db/DatabasePerformanceTest.java @@ -96,6 +96,11 @@ public abstract class DatabasePerformanceTest extends BrambleTestCase { */ private static final int STEADY_STATE_BLOCKS = 5; + /** + * Max latency for the database + */ + private static final int MAX_LATENCY = 1000; + protected final File testDir = getTestDirectory(); private final File resultsFile = new File(getTestName() + ".tsv"); protected final Random random = new Random(); @@ -448,7 +453,7 @@ public abstract class DatabasePerformanceTest extends BrambleTestCase { benchmark(name, db -> { Connection txn = db.startTransaction(); db.getMessagesToOffer(txn, pickRandom(contacts).getId(), - MAX_MESSAGE_IDS); + MAX_MESSAGE_IDS, MAX_LATENCY); db.commitTransaction(txn); }); } @@ -470,7 +475,7 @@ public abstract class DatabasePerformanceTest extends BrambleTestCase { benchmark(name, db -> { Connection txn = db.startTransaction(); db.getMessagesToSend(txn, pickRandom(contacts).getId(), - MAX_MESSAGE_IDS); + MAX_MESSAGE_IDS, MAX_LATENCY); db.commitTransaction(txn); }); } 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 e88038258ad7007b909eb499cc5243eaee11d398..68725cabdea6e6d114b61cafe97582d113cc74a5 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 static org.briarproject.bramble.api.sync.ValidationManager.State.DELIVERE import static org.briarproject.bramble.api.sync.ValidationManager.State.INVALID; import static org.briarproject.bramble.api.sync.ValidationManager.State.PENDING; import static org.briarproject.bramble.api.sync.ValidationManager.State.UNKNOWN; +import static org.briarproject.bramble.db.ExponentialBackoff.calculateExpiry; import static org.briarproject.bramble.test.TestUtils.getAuthor; import static org.briarproject.bramble.test.TestUtils.getClientId; import static org.briarproject.bramble.test.TestUtils.getGroup; @@ -79,6 +80,7 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase { private static final int ONE_MEGABYTE = 1024 * 1024; private static final int MAX_SIZE = 5 * ONE_MEGABYTE; + private static final int MAX_LATENCY = 1000; private final SecretKey key = getSecretKey(); private final File testDir = getTestDirectory(); @@ -202,16 +204,16 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase { // The contact has not seen the message, so it should be sendable Collection<MessageId> ids = - db.getMessagesToSend(txn, contactId, ONE_MEGABYTE); + db.getMessagesToSend(txn, contactId, ONE_MEGABYTE, MAX_LATENCY); assertEquals(singletonList(messageId), ids); - ids = db.getMessagesToOffer(txn, contactId, 100); + ids = db.getMessagesToOffer(txn, contactId, 100, MAX_LATENCY); assertEquals(singletonList(messageId), ids); // Changing the status to seen = true should make the message unsendable db.raiseSeenFlag(txn, contactId, messageId); - ids = db.getMessagesToSend(txn, contactId, ONE_MEGABYTE); + ids = db.getMessagesToSend(txn, contactId, ONE_MEGABYTE, MAX_LATENCY); assertTrue(ids.isEmpty()); - ids = db.getMessagesToOffer(txn, contactId, 100); + ids = db.getMessagesToOffer(txn, contactId, 100, MAX_LATENCY); assertTrue(ids.isEmpty()); db.commitTransaction(txn); @@ -233,30 +235,30 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase { // The message has not been validated, so it should not be sendable Collection<MessageId> ids = db.getMessagesToSend(txn, contactId, - ONE_MEGABYTE); + ONE_MEGABYTE, MAX_LATENCY); assertTrue(ids.isEmpty()); - ids = db.getMessagesToOffer(txn, contactId, 100); + ids = db.getMessagesToOffer(txn, contactId, 100, MAX_LATENCY); assertTrue(ids.isEmpty()); // Marking the message delivered should make it sendable db.setMessageState(txn, messageId, DELIVERED); - ids = db.getMessagesToSend(txn, contactId, ONE_MEGABYTE); + ids = db.getMessagesToSend(txn, contactId, ONE_MEGABYTE, MAX_LATENCY); assertEquals(singletonList(messageId), ids); - ids = db.getMessagesToOffer(txn, contactId, 100); + ids = db.getMessagesToOffer(txn, contactId, 100, MAX_LATENCY); assertEquals(singletonList(messageId), ids); // Marking the message invalid should make it unsendable db.setMessageState(txn, messageId, INVALID); - ids = db.getMessagesToSend(txn, contactId, ONE_MEGABYTE); + ids = db.getMessagesToSend(txn, contactId, ONE_MEGABYTE, MAX_LATENCY); assertTrue(ids.isEmpty()); - ids = db.getMessagesToOffer(txn, contactId, 100); + ids = db.getMessagesToOffer(txn, contactId, 100, MAX_LATENCY); assertTrue(ids.isEmpty()); // Marking the message pending should make it unsendable db.setMessageState(txn, messageId, PENDING); - ids = db.getMessagesToSend(txn, contactId, ONE_MEGABYTE); + ids = db.getMessagesToSend(txn, contactId, ONE_MEGABYTE, MAX_LATENCY); assertTrue(ids.isEmpty()); - ids = db.getMessagesToOffer(txn, contactId, 100); + ids = db.getMessagesToOffer(txn, contactId, 100, MAX_LATENCY); assertTrue(ids.isEmpty()); db.commitTransaction(txn); @@ -277,37 +279,37 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase { // The group is invisible, so the message should not be sendable Collection<MessageId> ids = db.getMessagesToSend(txn, contactId, - ONE_MEGABYTE); + ONE_MEGABYTE, MAX_LATENCY); assertTrue(ids.isEmpty()); - ids = db.getMessagesToOffer(txn, contactId, 100); + ids = db.getMessagesToOffer(txn, contactId, 100, MAX_LATENCY); assertTrue(ids.isEmpty()); // Making the group visible should not make the message sendable db.addGroupVisibility(txn, contactId, groupId, false); - ids = db.getMessagesToSend(txn, contactId, ONE_MEGABYTE); + ids = db.getMessagesToSend(txn, contactId, ONE_MEGABYTE, MAX_LATENCY); assertTrue(ids.isEmpty()); - ids = db.getMessagesToOffer(txn, contactId, 100); + ids = db.getMessagesToOffer(txn, contactId, 100, MAX_LATENCY); assertTrue(ids.isEmpty()); // Sharing the group should make the message sendable db.setGroupVisibility(txn, contactId, groupId, true); - ids = db.getMessagesToSend(txn, contactId, ONE_MEGABYTE); + ids = db.getMessagesToSend(txn, contactId, ONE_MEGABYTE, MAX_LATENCY); assertEquals(singletonList(messageId), ids); - ids = db.getMessagesToOffer(txn, contactId, 100); + ids = db.getMessagesToOffer(txn, contactId, 100, MAX_LATENCY); assertEquals(singletonList(messageId), ids); // Unsharing the group should make the message unsendable db.setGroupVisibility(txn, contactId, groupId, false); - ids = db.getMessagesToSend(txn, contactId, ONE_MEGABYTE); + ids = db.getMessagesToSend(txn, contactId, ONE_MEGABYTE, MAX_LATENCY); assertTrue(ids.isEmpty()); - ids = db.getMessagesToOffer(txn, contactId, 100); + ids = db.getMessagesToOffer(txn, contactId, 100, MAX_LATENCY); assertTrue(ids.isEmpty()); // Making the group invisible should make the message unsendable db.removeGroupVisibility(txn, contactId, groupId); - ids = db.getMessagesToSend(txn, contactId, ONE_MEGABYTE); + ids = db.getMessagesToSend(txn, contactId, ONE_MEGABYTE, MAX_LATENCY); assertTrue(ids.isEmpty()); - ids = db.getMessagesToOffer(txn, contactId, 100); + ids = db.getMessagesToOffer(txn, contactId, 100, MAX_LATENCY); assertTrue(ids.isEmpty()); db.commitTransaction(txn); @@ -329,16 +331,16 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase { // The message is not shared, so it should not be sendable Collection<MessageId> ids = db.getMessagesToSend(txn, contactId, - ONE_MEGABYTE); + ONE_MEGABYTE, MAX_LATENCY); assertTrue(ids.isEmpty()); - ids = db.getMessagesToOffer(txn, contactId, 100); + ids = db.getMessagesToOffer(txn, contactId, 100, MAX_LATENCY); assertTrue(ids.isEmpty()); // Sharing the message should make it sendable db.setMessageShared(txn, messageId); - ids = db.getMessagesToSend(txn, contactId, ONE_MEGABYTE); + ids = db.getMessagesToSend(txn, contactId, ONE_MEGABYTE, MAX_LATENCY); assertEquals(singletonList(messageId), ids); - ids = db.getMessagesToOffer(txn, contactId, 100); + ids = db.getMessagesToOffer(txn, contactId, 100, MAX_LATENCY); assertEquals(singletonList(messageId), ids); db.commitTransaction(txn); @@ -360,11 +362,11 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase { // The message is sendable, but too large to send Collection<MessageId> ids = db.getMessagesToSend(txn, contactId, - size - 1); + size - 1, MAX_LATENCY); assertTrue(ids.isEmpty()); // The message is just the right size to send - ids = db.getMessagesToSend(txn, contactId, size); + ids = db.getMessagesToSend(txn, contactId, size, MAX_LATENCY); assertEquals(singletonList(messageId), ids); db.commitTransaction(txn); @@ -427,25 +429,66 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase { // Retrieve the message from the database and mark it as sent Collection<MessageId> ids = db.getMessagesToSend(txn, contactId, - ONE_MEGABYTE); + ONE_MEGABYTE, MAX_LATENCY); assertEquals(singletonList(messageId), ids); - db.updateExpiryTime(txn, contactId, messageId, Integer.MAX_VALUE); + db.updateLastSentTime(txn, contactId, messageId); // The message should no longer be sendable - ids = db.getMessagesToSend(txn, contactId, ONE_MEGABYTE); + ids = db.getMessagesToSend(txn, contactId, ONE_MEGABYTE, MAX_LATENCY); assertTrue(ids.isEmpty()); // Pretend that the message was acked db.raiseSeenFlag(txn, contactId, messageId); // The message still should not be sendable - ids = db.getMessagesToSend(txn, contactId, ONE_MEGABYTE); + ids = db.getMessagesToSend(txn, contactId, ONE_MEGABYTE, MAX_LATENCY); assertTrue(ids.isEmpty()); db.commitTransaction(txn); db.close(); } + @Test + public void testFasterMessageRetransmission() throws Exception { + long now = System.currentTimeMillis(); + long steps[] = {now, now, calculateExpiry(now, MAX_LATENCY / 2, 1), + calculateExpiry(now, MAX_LATENCY / 2, 1)}; + Database<Connection> db = + open(false, new ArrayClock(steps)); + Connection txn = db.startTransaction(); + + // Add a contact, a shared group and a shared message + db.addLocalAuthor(txn, localAuthor); + assertEquals(contactId, db.addContact(txn, author, localAuthor.getId(), + true, true)); + db.addGroup(txn, group); + db.addGroupVisibility(txn, contactId, groupId, true); + db.addMessage(txn, message, DELIVERED, true, null); + + // Retrieve the message from the database and mark it as sent + // Time: now + Collection<MessageId> ids = db.getMessagesToSend(txn, contactId, + ONE_MEGABYTE, MAX_LATENCY); + assertEquals(singletonList(messageId), ids); + // Time: now + db.updateLastSentTime(txn, contactId, messageId); + + // The message should no longer be sendable via transports with + // latencies >= MAX_LATENCY + // Time: now + MAX_LATENCY * 2 + ids = db.getMessagesToSend(txn, contactId, ONE_MEGABYTE, MAX_LATENCY); + assertTrue(ids.isEmpty()); + + // The message should be sendable via a transport with lower latency + // Time: now + MAX_LATENCY * 2 + ids = db.getMessagesToSend(txn, contactId, ONE_MEGABYTE, + MAX_LATENCY / 4); + assertEquals(singletonList(messageId), ids); + + db.commitTransaction(txn); + db.close(); + } + @Test public void testGetFreeSpace() throws Exception { byte[] largeBody = new byte[MAX_MESSAGE_LENGTH]; @@ -1530,7 +1573,7 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase { assertFalse(status.isSeen()); // Pretend the message was sent to the contact - db.updateExpiryTime(txn, contactId, messageId, Integer.MAX_VALUE); + db.updateLastSentTime(txn, contactId, messageId); // The message should be sent but not seen status = db.getMessageStatus(txn, contactId, messageId); @@ -1649,9 +1692,9 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase { // The message should be sendable Collection<MessageId> ids = db.getMessagesToSend(txn, contactId, - ONE_MEGABYTE); + ONE_MEGABYTE, MAX_LATENCY); assertEquals(singletonList(messageId), ids); - ids = db.getMessagesToOffer(txn, contactId, 100); + ids = db.getMessagesToOffer(txn, contactId, 100, MAX_LATENCY); assertEquals(singletonList(messageId), ids); // The raw message should not be null @@ -1664,9 +1707,9 @@ 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); + ids = db.getMessagesToSend(txn, contactId, ONE_MEGABYTE, MAX_LATENCY); assertTrue(ids.isEmpty()); - ids = db.getMessagesToOffer(txn, contactId, 100); + ids = db.getMessagesToOffer(txn, contactId, 100, MAX_LATENCY); assertTrue(ids.isEmpty()); // The raw message should be null @@ -1744,37 +1787,43 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase { db.addMessage(txn, message, UNKNOWN, false, null); // There should be no messages to send - assertEquals(Long.MAX_VALUE, db.getNextSendTime(txn, contactId)); + assertEquals(Long.MAX_VALUE, + db.getNextSendTime(txn, contactId, MAX_LATENCY)); // Share the group with the contact - still no messages to send db.addGroupVisibility(txn, contactId, groupId, true); - assertEquals(Long.MAX_VALUE, db.getNextSendTime(txn, contactId)); + assertEquals(Long.MAX_VALUE, + db.getNextSendTime(txn, contactId, MAX_LATENCY)); // Set the message's state to DELIVERED - still no messages to send db.setMessageState(txn, messageId, DELIVERED); - assertEquals(Long.MAX_VALUE, db.getNextSendTime(txn, contactId)); + assertEquals(Long.MAX_VALUE, + db.getNextSendTime(txn, contactId, MAX_LATENCY)); // Share the message - now it should be sendable immediately db.setMessageShared(txn, messageId); - assertEquals(0, db.getNextSendTime(txn, contactId)); + assertEquals(0, db.getNextSendTime(txn, contactId, MAX_LATENCY)); // Mark the message as requested - it should still be sendable db.raiseRequestedFlag(txn, contactId, messageId); - assertEquals(0, db.getNextSendTime(txn, contactId)); + assertEquals(0, db.getNextSendTime(txn, contactId, MAX_LATENCY)); - // Update the message's expiry time as though we sent it - now the + // Update the message's lastSentTime as though we sent it - now the // message should be sendable after one round-trip - db.updateExpiryTime(txn, contactId, messageId, 1000); - assertEquals(now + 2000, db.getNextSendTime(txn, contactId)); + db.updateLastSentTime(txn, contactId, messageId); + assertEquals(calculateExpiry(now, MAX_LATENCY, 1), + db.getNextSendTime(txn, contactId, MAX_LATENCY)); // Update the message's expiry time again - now it should be sendable // after two round-trips - db.updateExpiryTime(txn, contactId, messageId, 1000); - assertEquals(now + 4000, db.getNextSendTime(txn, contactId)); + db.updateLastSentTime(txn, contactId, messageId); + assertEquals(calculateExpiry(now, MAX_LATENCY, 2), + db.getNextSendTime(txn, contactId, MAX_LATENCY)); // Delete the message - there should be no messages to send db.deleteMessage(txn, messageId); - assertEquals(Long.MAX_VALUE, db.getNextSendTime(txn, contactId)); + assertEquals(Long.MAX_VALUE, + db.getNextSendTime(txn, contactId, MAX_LATENCY)); db.commitTransaction(txn); db.close(); @@ -1869,4 +1918,24 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase { Thread.sleep(milliseconds); } } + + private static class ArrayClock implements Clock { + + private final long[] times; + private int index = 0; + + private ArrayClock(long... times) { + this.times = times; + } + + @Override + public long currentTimeMillis() { + return times[index++]; + } + + @Override + public void sleep(long milliseconds) throws InterruptedException { + Thread.sleep(milliseconds); + } + } }