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