From 796cbcaf4be71e2be8da23e842abda443ce737c8 Mon Sep 17 00:00:00 2001 From: akwizgran Date: Wed, 16 Jun 2021 16:25:11 +0100 Subject: [PATCH] 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;