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 a5d8dc653..58f8a8f06 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 @@ -29,6 +29,7 @@ import org.briarproject.bramble.api.transport.TransportKeySet; import org.briarproject.bramble.api.transport.TransportKeys; import java.util.Collection; +import java.util.List; import java.util.Map; import javax.annotation.Nullable; @@ -427,6 +428,13 @@ public interface DatabaseComponent extends TransactionManager { */ Settings getSettings(Transaction txn, String namespace) throws DbException; + /** + * Returns the versions of the sync protocol supported by the given contact. + *

+ * Read-only. + */ + List getSyncVersions(Transaction txn, ContactId c) throws DbException; + /** * Returns all transport keys for the given transport. *

@@ -579,6 +587,12 @@ public interface DatabaseComponent extends TransactionManager { void setReorderingWindow(Transaction txn, KeySetId k, TransportId t, long timePeriod, long base, byte[] bitmap) throws DbException; + /** + * Sets the versions of the sync protocol supported by the given contact. + */ + void setSyncVersions(Transaction txn, ContactId c, List supported) + throws DbException; + /** * Marks the given transport keys as usable for outgoing streams. */ diff --git a/bramble-api/src/main/java/org/briarproject/bramble/api/sync/event/SyncVersionsUpdatedEvent.java b/bramble-api/src/main/java/org/briarproject/bramble/api/sync/event/SyncVersionsUpdatedEvent.java new file mode 100644 index 000000000..00d1f5b7b --- /dev/null +++ b/bramble-api/src/main/java/org/briarproject/bramble/api/sync/event/SyncVersionsUpdatedEvent.java @@ -0,0 +1,34 @@ +package org.briarproject.bramble.api.sync.event; + +import org.briarproject.bramble.api.contact.ContactId; +import org.briarproject.bramble.api.event.Event; +import org.briarproject.bramble.api.nullsafety.NotNullByDefault; + +import java.util.List; + +import javax.annotation.concurrent.Immutable; + +/** + * An event that is broadcast when the versions of the sync protocol supported + * by a contact are updated. + */ +@Immutable +@NotNullByDefault +public class SyncVersionsUpdatedEvent extends Event { + + private final ContactId contactId; + private final List supported; + + public SyncVersionsUpdatedEvent(ContactId contactId, List supported) { + this.contactId = contactId; + this.supported = supported; + } + + public ContactId getContactId() { + return contactId; + } + + public List getSupportedVersions() { + return supported; + } +} 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 395800eac..469204c67 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 @@ -33,6 +33,7 @@ import org.briarproject.bramble.api.transport.TransportKeySet; import org.briarproject.bramble.api.transport.TransportKeys; import java.util.Collection; +import java.util.List; import java.util.Map; import javax.annotation.Nullable; @@ -528,6 +529,13 @@ interface Database { */ Settings getSettings(T txn, String namespace) throws DbException; + /** + * Returns the versions of the sync protocol supported by the given contact. + *

+ * Read-only. + */ + List getSyncVersions(T txn, ContactId c) throws DbException; + /** * Returns all transport keys for the given transport. *

@@ -700,6 +708,12 @@ interface Database { void setReorderingWindow(T txn, KeySetId k, TransportId t, long timePeriod, long base, byte[] bitmap) throws DbException; + /** + * Sets the versions of the sync protocol supported by the given contact. + */ + void setSyncVersions(T txn, ContactId c, List supported) + throws DbException; + /** * Marks the given transport keys as usable for outgoing streams. */ 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 8023763b4..1a73c89f0 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 @@ -65,6 +65,7 @@ import org.briarproject.bramble.api.sync.event.MessageToAckEvent; import org.briarproject.bramble.api.sync.event.MessageToRequestEvent; import org.briarproject.bramble.api.sync.event.MessagesAckedEvent; import org.briarproject.bramble.api.sync.event.MessagesSentEvent; +import org.briarproject.bramble.api.sync.event.SyncVersionsUpdatedEvent; import org.briarproject.bramble.api.sync.validation.MessageState; import org.briarproject.bramble.api.transport.KeySetId; import org.briarproject.bramble.api.transport.TransportKeySet; @@ -716,6 +717,15 @@ class DatabaseComponentImpl implements DatabaseComponent { return db.getSettings(txn, namespace); } + @Override + public List getSyncVersions(Transaction transaction, ContactId c) + throws DbException { + T txn = unbox(transaction); + if (!db.containsContact(txn, c)) + throw new NoSuchContactException(); + return db.getSyncVersions(txn, c); + } + @Override public Collection getTransportKeys(Transaction transaction, TransportId t) throws DbException { @@ -1046,6 +1056,17 @@ class DatabaseComponentImpl implements DatabaseComponent { db.setReorderingWindow(txn, k, t, timePeriod, base, bitmap); } + @Override + public void setSyncVersions(Transaction transaction, ContactId c, + List supported) throws DbException { + if (transaction.isReadOnly()) throw new IllegalArgumentException(); + T txn = unbox(transaction); + if (!db.containsContact(txn, c)) + throw new NoSuchContactException(); + db.setSyncVersions(txn, c, supported); + transaction.attach(new SyncVersionsUpdatedEvent(c, supported)); + } + @Override public void setTransportKeysActive(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 377ba90a3..f1e0d013c 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 @@ -2330,6 +2330,32 @@ abstract class JdbcDatabase implements Database { } } + @Override + public List getSyncVersions(Connection txn, ContactId c) + throws DbException { + PreparedStatement ps = null; + ResultSet rs = null; + try { + String sql = "SELECT syncVersions FROM contacts" + + " WHERE contactId = ?"; + ps = txn.prepareStatement(sql); + ps.setInt(1, c.getInt()); + rs = ps.executeQuery(); + if (!rs.next()) throw new DbStateException(); + byte[] bytes = rs.getBytes(1); + List supported = new ArrayList<>(bytes.length); + for (byte b : bytes) supported.add(b); + if (rs.next()) throw new DbStateException(); + rs.close(); + ps.close(); + return supported; + } catch (SQLException e) { + tryToClose(rs, LOG, WARNING); + tryToClose(ps, LOG, WARNING); + throw new DbException(e); + } + } + @Override public Collection getTransportKeys(Connection txn, TransportId t) throws DbException { @@ -3163,6 +3189,29 @@ abstract class JdbcDatabase implements Database { } } + @Override + public void setSyncVersions(Connection txn, ContactId c, + List supported) throws DbException { + PreparedStatement ps = null; + try { + String sql = "UPDATE contacts SET syncVersions = ?" + + " WHERE contactId = ?"; + ps = txn.prepareStatement(sql); + byte[] bytes = new byte[supported.size()]; + for (int i = 0; i < bytes.length; i++) { + bytes[i] = supported.get(i); + } + ps.setBytes(1, bytes); + ps.setInt(2, c.getInt()); + int affected = ps.executeUpdate(); + if (affected < 0 || affected > 1) throw new DbStateException(); + ps.close(); + } catch (SQLException e) { + tryToClose(ps, LOG, WARNING); + throw new DbException(e); + } + } + @Override public void setTransportKeysActive(Connection txn, TransportId t, KeySetId k) 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 8ef5f4d67..b8fb04bc8 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 @@ -66,6 +66,7 @@ import java.util.concurrent.Executor; import java.util.concurrent.atomic.AtomicReference; import static java.util.Arrays.asList; +import static java.util.Collections.emptyList; import static java.util.Collections.emptyMap; import static java.util.Collections.singletonList; import static org.briarproject.bramble.api.sync.Group.Visibility.INVISIBLE; @@ -294,11 +295,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(16).of(database).startTransaction(); + exactly(18).of(database).startTransaction(); will(returnValue(txn)); - exactly(16).of(database).containsContact(txn, contactId); + exactly(18).of(database).containsContact(txn, contactId); will(returnValue(false)); - exactly(16).of(database).abortTransaction(txn); + exactly(18).of(database).abortTransaction(txn); }}); DatabaseComponent db = createDatabaseComponent(database, eventBus, eventExecutor, shutdownManager); @@ -376,6 +377,14 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase { // Expected } + try { + db.transaction(false, transaction -> + db.getSyncVersions(transaction, contactId)); + fail(); + } catch (NoSuchContactException expected) { + // Expected + } + try { Ack a = new Ack(singletonList(messageId)); db.transaction(false, transaction -> @@ -435,6 +444,14 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase { } catch (NoSuchContactException expected) { // Expected } + + try { + db.transaction(false, transaction -> + db.setSyncVersions(transaction, contactId, emptyList())); + fail(); + } catch (NoSuchContactException expected) { + // Expected + } } @Test 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 006c1e67c..b8e246013 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 @@ -41,7 +41,6 @@ import org.junit.Test; import java.io.File; import java.sql.Connection; import java.util.ArrayList; -import java.util.Arrays; import java.util.Collection; import java.util.List; import java.util.Map; @@ -51,6 +50,7 @@ import java.util.concurrent.CountDownLatch; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicLong; +import static java.util.Arrays.asList; import static java.util.Collections.emptyList; import static java.util.Collections.emptyMap; import static java.util.Collections.singletonList; @@ -407,10 +407,10 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase { // Both message IDs should be returned Collection ids = db.getMessagesToAck(txn, contactId, 1234); - assertEquals(Arrays.asList(messageId, messageId1), ids); + assertEquals(asList(messageId, messageId1), ids); // Remove both message IDs - db.lowerAckFlag(txn, contactId, Arrays.asList(messageId, messageId1)); + db.lowerAckFlag(txn, contactId, asList(messageId, messageId1)); // Both message IDs should have been removed assertEquals(emptyList(), db.getMessagesToAck(txn, @@ -422,7 +422,7 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase { // Both message IDs should be returned ids = db.getMessagesToAck(txn, contactId, 1234); - assertEquals(Arrays.asList(messageId, messageId1), ids); + assertEquals(asList(messageId, messageId1), ids); db.commitTransaction(txn); db.close(); @@ -2286,6 +2286,29 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase { db.close(); } + @Test + public void testSyncVersions() throws Exception { + Database db = open(false); + Connection txn = db.startTransaction(); + + // Add a contact + db.addIdentity(txn, identity); + assertEquals(contactId, + db.addContact(txn, author, localAuthor.getId(), null, true)); + + // Only sync version 0 should be supported by default + List defaultSupported = singletonList((byte) 0); + assertEquals(defaultSupported, db.getSyncVersions(txn, contactId)); + + // Set the supported versions and check that they're returned + List supported = asList((byte) 0, (byte) 1); + db.setSyncVersions(txn, contactId, supported); + assertEquals(supported, db.getSyncVersions(txn, contactId)); + + db.commitTransaction(txn); + db.close(); + } + private Database open(boolean resume) throws Exception { return open(resume, new TestMessageFactory(), new SystemClock()); }