diff --git a/bramble-api/src/main/java/org/briarproject/bramble/api/sync/ClientVersioningManager.java b/bramble-api/src/main/java/org/briarproject/bramble/api/sync/ClientVersioningManager.java new file mode 100644 index 000000000..a44923373 --- /dev/null +++ b/bramble-api/src/main/java/org/briarproject/bramble/api/sync/ClientVersioningManager.java @@ -0,0 +1,32 @@ +package org.briarproject.bramble.api.sync; + +import org.briarproject.bramble.api.contact.Contact; +import org.briarproject.bramble.api.db.DbException; +import org.briarproject.bramble.api.db.Transaction; +import org.briarproject.bramble.api.nullsafety.NotNullByDefault; +import org.briarproject.bramble.api.sync.Group.Visibility; + +@NotNullByDefault +public interface ClientVersioningManager { + + /** + * The unique ID of the versioning client. + */ + ClientId CLIENT_ID = new ClientId("org.briarproject.bramble.versioning"); + + /** + * The current version of the versioning client. + */ + int CLIENT_VERSION = 0; + + void registerClient(ClientId clientId, int clientVersion); + + void registerClientVersioningHook(ClientId clientId, int clientVersion, + ClientVersioningHook hook); + + interface ClientVersioningHook { + + void onClientVisibilityChanging(Transaction txn, Contact c, + Visibility v) throws DbException; + } +} diff --git a/bramble-api/src/main/java/org/briarproject/bramble/api/sync/SyncConstants.java b/bramble-api/src/main/java/org/briarproject/bramble/api/sync/SyncConstants.java index 80f48da66..5f196f147 100644 --- a/bramble-api/src/main/java/org/briarproject/bramble/api/sync/SyncConstants.java +++ b/bramble-api/src/main/java/org/briarproject/bramble/api/sync/SyncConstants.java @@ -19,7 +19,9 @@ public interface SyncConstants { */ int MAX_RECORD_PAYLOAD_LENGTH = 48 * 1024; // 48 KiB - /** The maximum length of a group descriptor in bytes. */ + /** + * The maximum length of a group descriptor in bytes. + */ int MAX_GROUP_DESCRIPTOR_LENGTH = 16 * 1024; // 16 KiB /** diff --git a/bramble-core/src/main/java/org/briarproject/bramble/sync/ClientVersioningConstants.java b/bramble-core/src/main/java/org/briarproject/bramble/sync/ClientVersioningConstants.java new file mode 100644 index 000000000..4578fa88d --- /dev/null +++ b/bramble-core/src/main/java/org/briarproject/bramble/sync/ClientVersioningConstants.java @@ -0,0 +1,10 @@ +package org.briarproject.bramble.sync; + +interface ClientVersioningConstants { + + // Metadata keys + String MSG_KEY_UPDATE_VERSION = "version"; + String MSG_KEY_LOCAL = "local"; + String GROUP_KEY_CONTACT_ID = "contactId"; +} + diff --git a/bramble-core/src/main/java/org/briarproject/bramble/sync/ClientVersioningManagerImpl.java b/bramble-core/src/main/java/org/briarproject/bramble/sync/ClientVersioningManagerImpl.java new file mode 100644 index 000000000..9cb4d9c63 --- /dev/null +++ b/bramble-core/src/main/java/org/briarproject/bramble/sync/ClientVersioningManagerImpl.java @@ -0,0 +1,566 @@ +package org.briarproject.bramble.sync; + +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; +import org.briarproject.bramble.api.contact.ContactId; +import org.briarproject.bramble.api.contact.ContactManager.ContactHook; +import org.briarproject.bramble.api.data.BdfDictionary; +import org.briarproject.bramble.api.data.BdfList; +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.lifecycle.Service; +import org.briarproject.bramble.api.lifecycle.ServiceException; +import org.briarproject.bramble.api.nullsafety.NotNullByDefault; +import org.briarproject.bramble.api.sync.Client; +import org.briarproject.bramble.api.sync.ClientId; +import org.briarproject.bramble.api.sync.ClientVersioningManager; +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.InvalidMessageException; +import org.briarproject.bramble.api.sync.Message; +import org.briarproject.bramble.api.sync.MessageId; +import org.briarproject.bramble.api.sync.ValidationManager.IncomingMessageHook; +import org.briarproject.bramble.api.system.Clock; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; +import java.util.TreeSet; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.CopyOnWriteArrayList; + +import javax.annotation.Nullable; +import javax.inject.Inject; + +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.sync.ClientVersioningConstants.GROUP_KEY_CONTACT_ID; +import static org.briarproject.bramble.sync.ClientVersioningConstants.MSG_KEY_LOCAL; +import static org.briarproject.bramble.sync.ClientVersioningConstants.MSG_KEY_UPDATE_VERSION; + +@NotNullByDefault +class ClientVersioningManagerImpl implements ClientVersioningManager, Client, + Service, ContactHook, IncomingMessageHook { + + private final DatabaseComponent db; + private final ClientHelper clientHelper; + private final ContactGroupFactory contactGroupFactory; + private final Clock clock; + private final Group localGroup; + + private final Collection clients = + new CopyOnWriteArrayList<>(); + private final Map hooks = + new ConcurrentHashMap<>(); + + @Inject + ClientVersioningManagerImpl(DatabaseComponent db, + ClientHelper clientHelper, ContactGroupFactory contactGroupFactory, + Clock clock) { + this.db = db; + this.clientHelper = clientHelper; + this.contactGroupFactory = contactGroupFactory; + this.clock = clock; + localGroup = contactGroupFactory.createLocalGroup(CLIENT_ID, + CLIENT_VERSION); + } + + @Override + public void registerClient(ClientId clientId, int clientVersion) { + clients.add(new ClientVersion(clientId, clientVersion)); + } + + @Override + public void registerClientVersioningHook(ClientId clientId, + int clientVersion, ClientVersioningHook hook) { + hooks.put(new ClientVersion(clientId, clientVersion), hook); + } + + @Override + public void createLocalState(Transaction txn) throws DbException { + if (db.containsGroup(txn, localGroup.getId())) return; + db.addGroup(txn, localGroup); + // Set things up for any pre-existing contacts + for (Contact c : db.getContacts(txn)) addingContact(txn, c); + } + + @Override + public void startService() throws ServiceException { + List versions = new ArrayList<>(clients); + Collections.sort(versions); + try { + Transaction txn = db.startTransaction(false); + try { + if (updateClientVersions(txn, versions)) { + for (Contact c : db.getContacts(txn)) + clientVersionsUpdated(txn, c, versions); + } + db.commitTransaction(txn); + } finally { + db.endTransaction(txn); + } + } catch (DbException e) { + throw new ServiceException(e); + } + } + + @Override + public void stopService() throws ServiceException { + } + + @Override + public void addingContact(Transaction txn, Contact c) throws DbException { + // Create a group and share it with the contact + Group g = getContactGroup(c); + db.addGroup(txn, g); + db.setGroupVisibility(txn, c.getId(), g.getId(), SHARED); + // Attach the contact ID to the group + BdfDictionary meta = new BdfDictionary(); + meta.put(GROUP_KEY_CONTACT_ID, c.getId().getInt()); + try { + clientHelper.mergeGroupMetadata(txn, g.getId(), meta); + } catch (FormatException e) { + throw new AssertionError(e); + } + // Create and store the first local update + List versions = new ArrayList<>(clients); + Collections.sort(versions); + storeFirstUpdate(txn, g.getId(), versions); + } + + @Override + public void removingContact(Transaction txn, Contact c) throws DbException { + db.removeGroup(txn, getContactGroup(c)); + } + + @Override + public boolean incomingMessage(Transaction txn, Message m, Metadata meta) + throws DbException, InvalidMessageException { + try { + // Parse the new remote update + Update newRemoteUpdate = parseUpdate(clientHelper.toList(m)); + List newRemoteStates = newRemoteUpdate.states; + long newRemoteUpdateVersion = newRemoteUpdate.updateVersion; + // Find the latest local and remote updates, if any + LatestUpdates latest = findLatestUpdates(txn, m.getGroupId()); + // If this update is obsolete, delete it and return + if (latest.remote != null + && latest.remote.updateVersion > newRemoteUpdateVersion) { + db.deleteMessage(txn, m.getId()); + db.deleteMessageMetadata(txn, m.getId()); + return false; + } + // Load and parse the latest local update + if (latest.local == null) throw new DbException(); + Update oldLocalUpdate = loadUpdate(txn, latest.local.messageId); + List oldLocalStates = oldLocalUpdate.states; + long oldLocalUpdateVersion = oldLocalUpdate.updateVersion; + // Load and parse the previous remote update, if any + List oldRemoteStates; + if (latest.remote == null) { + oldRemoteStates = emptyList(); + } else { + oldRemoteStates = + loadUpdate(txn, latest.remote.messageId).states; + // Delete the previous remote update + db.deleteMessage(txn, latest.remote.messageId); + db.deleteMessageMetadata(txn, latest.remote.messageId); + } + // Update the local states from the remote states if necessary + List newLocalStates = updateStatesFromRemoteStates( + oldLocalStates, newRemoteStates); + if (!oldLocalStates.equals(newLocalStates)) { + // Delete the latest local update + db.deleteMessage(txn, latest.local.messageId); + db.deleteMessageMetadata(txn, latest.local.messageId); + // Store a new local update + storeUpdate(txn, m.getGroupId(), newLocalStates, + oldLocalUpdateVersion + 1); + } + // Calculate the old and new client visibilities + Map before = + getVisibilities(oldLocalStates, oldRemoteStates); + Map after = + getVisibilities(newLocalStates, newRemoteStates); + // Call hooks for any visibilities that have changed + Contact c = getContact(txn, m.getGroupId()); + callVisibilityHooks(txn, c, before, after); + } catch (FormatException e) { + throw new InvalidMessageException(e); + } + return false; + } + + private void storeClientVersions(Transaction txn, + List versions) throws DbException { + long now = clock.currentTimeMillis(); + BdfList body = encodeClientVersions(versions); + try { + Message m = clientHelper.createMessage(localGroup.getId(), now, + body); + db.addLocalMessage(txn, m, new Metadata(), false); + } catch (FormatException e) { + throw new AssertionError(e); + } + } + + private BdfList encodeClientVersions(List versions) { + BdfList encoded = new BdfList(); + for (ClientVersion cv : versions) + encoded.add(BdfList.of(cv.clientId.getString(), cv.clientVersion)); + return encoded; + } + + private boolean updateClientVersions(Transaction txn, + List newVersions) throws DbException { + Collection ids = db.getMessageIds(txn, localGroup.getId()); + if (ids.isEmpty()) { + storeClientVersions(txn, newVersions); + return true; + } + MessageId m = ids.iterator().next(); + List oldVersions = loadClientVersions(txn, m); + if (oldVersions.equals(newVersions)) return false; + db.removeMessage(txn, m); + storeClientVersions(txn, newVersions); + return true; + } + + private List loadClientVersions(Transaction txn, MessageId m) + throws DbException { + try { + BdfList body = clientHelper.getMessageAsList(txn, m); + if (body == null) throw new DbException(); + return parseClientVersions(body); + } catch (FormatException e) { + throw new DbException(e); + } + } + + private List parseClientVersions(BdfList body) + throws FormatException { + int size = body.size(); + List parsed = new ArrayList<>(size); + for (int i = 0; i < size; i++) { + BdfList cv = body.getList(i); + ClientId clientId = new ClientId(cv.getString(0)); + int clientVersion = cv.getLong(1).intValue(); + parsed.add(new ClientVersion(clientId, clientVersion)); + } + return parsed; + } + + private void clientVersionsUpdated(Transaction txn, Contact c, + List versions) throws DbException { + try { + // Find the latest local and remote updates + Group g = getContactGroup(c); + LatestUpdates latest = findLatestUpdates(txn, g.getId()); + // Load and parse the latest local update + if (latest.local == null) throw new DbException(); + Update oldLocalUpdate = loadUpdate(txn, latest.local.messageId); + List oldLocalStates = oldLocalUpdate.states; + long oldLocalUpdateVersion = oldLocalUpdate.updateVersion; + // Delete the latest local update + db.deleteMessage(txn, latest.local.messageId); + db.deleteMessageMetadata(txn, latest.local.messageId); + // Store a new local update + List newLocalStates = + updateStatesFromLocalVersions(oldLocalStates, versions); + storeUpdate(txn, g.getId(), newLocalStates, + oldLocalUpdateVersion + 1); + // Load and parse the latest remote update, if any + List remoteStates; + if (latest.remote == null) remoteStates = emptyList(); + else remoteStates = loadUpdate(txn, latest.remote.messageId).states; + // Calculate the old and new client visibilities + Map before = + getVisibilities(oldLocalStates, remoteStates); + Map after = + getVisibilities(newLocalStates, remoteStates); + // Call hooks for any visibilities that have changed + callVisibilityHooks(txn, c, before, after); + } catch (FormatException e) { + throw new DbException(e); + } + } + + private Group getContactGroup(Contact c) { + return contactGroupFactory.createContactGroup(CLIENT_ID, + CLIENT_VERSION, c); + } + + private LatestUpdates findLatestUpdates(Transaction txn, GroupId g) + throws DbException, FormatException { + Map metadata = + clientHelper.getMessageMetadataAsDictionary(txn, g); + LatestUpdate local = null, remote = null; + for (Entry e : metadata.entrySet()) { + BdfDictionary meta = e.getValue(); + long updateVersion = meta.getLong(MSG_KEY_UPDATE_VERSION); + if (meta.getBoolean(MSG_KEY_LOCAL)) + local = new LatestUpdate(e.getKey(), updateVersion); + else remote = new LatestUpdate(e.getKey(), updateVersion); + } + return new LatestUpdates(local, remote); + } + + private Update loadUpdate(Transaction txn, MessageId m) throws DbException { + try { + BdfList body = clientHelper.getMessageAsList(txn, m); + if (body == null) throw new DbException(); + return parseUpdate(body); + } catch (FormatException e) { + throw new DbException(e); + } + } + + private Update parseUpdate(BdfList body) throws FormatException { + List states = parseClientStates(body); + long updateVersion = parseUpdateVersion(body); + return new Update(states, updateVersion); + } + + private List parseClientStates(BdfList body) + throws FormatException { + // Client states, update version + BdfList states = body.getList(0); + int size = states.size(); + List parsed = new ArrayList<>(size); + for (int i = 0; i < size; i++) + parsed.add(parseClientState(states.getList(i))); + return parsed; + } + + private ClientState parseClientState(BdfList clientState) + throws FormatException { + // Client ID, client version, active + ClientId clientId = new ClientId(clientState.getString(0)); + int clientVersion = clientState.getLong(1).intValue(); + boolean active = clientState.getBoolean(2); + return new ClientState(clientId, clientVersion, active); + } + + private long parseUpdateVersion(BdfList body) throws FormatException { + // Client states, update version + return body.getLong(1); + } + + private List updateStatesFromLocalVersions( + List oldStates, List newVersions) { + Map oldMap = new HashMap<>(); + for (ClientState cs : oldStates) oldMap.put(cs.version, cs); + List newStates = new ArrayList<>(newVersions.size()); + for (ClientVersion newVersion : newVersions) { + ClientState oldState = oldMap.get(newVersion); + boolean active = oldState != null && oldState.active; + newStates.add(new ClientState(newVersion, active)); + } + return newStates; + } + + private void storeUpdate(Transaction txn, GroupId g, + List states, long updateVersion) throws DbException { + try { + BdfList body = encodeUpdate(states, updateVersion); + long now = clock.currentTimeMillis(); + Message m = clientHelper.createMessage(g, now, body); + BdfDictionary meta = new BdfDictionary(); + meta.put(MSG_KEY_UPDATE_VERSION, updateVersion); + meta.put(MSG_KEY_LOCAL, true); + clientHelper.addLocalMessage(txn, m, meta, true); + } catch (FormatException e) { + throw new RuntimeException(e); + } + } + + private BdfList encodeUpdate(List states, long updateVersion) { + BdfList encoded = new BdfList(); + for (ClientState cs : states) encoded.add(encodeClientState(cs)); + return BdfList.of(encoded, updateVersion); + } + + private BdfList encodeClientState(ClientState cs) { + return BdfList.of(cs.version.clientId.getString(), + cs.version.clientVersion, cs.active); + } + + private Map getVisibilities( + List localStates, List remoteStates) { + Map remoteMap = new HashMap<>(); + for (ClientState remote : remoteStates) + remoteMap.put(remote.version, remote); + Map visibilities = new HashMap<>(); + for (ClientState local : localStates) { + ClientState remote = remoteMap.get(local.version); + if (remote == null) visibilities.put(local.version, INVISIBLE); + else if (remote.active) visibilities.put(local.version, SHARED); + else visibilities.put(local.version, VISIBLE); + } + return visibilities; + } + + private void callVisibilityHooks(Transaction txn, Contact c, + Map before, + Map after) throws DbException { + Set keys = new TreeSet<>(); + keys.addAll(before.keySet()); + keys.addAll(after.keySet()); + for (ClientVersion cv : keys) { + Visibility vBefore = before.get(cv), vAfter = after.get(cv); + if (vAfter == null) { + callVisibilityHook(txn, cv, c, INVISIBLE); + } else if (vBefore == null || !vBefore.equals(vAfter)) { + callVisibilityHook(txn, cv, c, vAfter); + } + } + } + + private void callVisibilityHook(Transaction txn, ClientVersion cv, + Contact c, Visibility v) throws DbException { + ClientVersioningHook hook = hooks.get(cv); + if (hook != null) hook.onClientVisibilityChanging(txn, c, v); + } + + private void storeFirstUpdate(Transaction txn, GroupId g, + List versions) throws DbException { + List states = new ArrayList<>(versions.size()); + for (ClientVersion cv : versions) + states.add(new ClientState(cv, false)); + storeUpdate(txn, g, states, 1); + } + + private Contact getContact(Transaction txn, GroupId g) throws DbException { + try { + BdfDictionary meta = + clientHelper.getGroupMetadataAsDictionary(txn, g); + int id = meta.getLong(GROUP_KEY_CONTACT_ID).intValue(); + return db.getContact(txn, new ContactId(id)); + } catch (FormatException e) { + throw new DbException(e); + } + } + + private List updateStatesFromRemoteStates( + List oldLocalStates, List remoteStates) { + Set remoteSet = new HashSet<>(); + for (ClientState remote : remoteStates) remoteSet.add(remote.version); + List newLocalStates = + new ArrayList<>(oldLocalStates.size()); + for (ClientState oldState : oldLocalStates) { + boolean active = remoteSet.contains(oldState.version); + newLocalStates.add(new ClientState(oldState.version, active)); + } + return newLocalStates; + } + + private static class Update { + + private final List states; + private final long updateVersion; + + private Update(List states, long updateVersion) { + this.states = states; + this.updateVersion = updateVersion; + } + } + + private static class LatestUpdate { + + private final MessageId messageId; + private final long updateVersion; + + private LatestUpdate(MessageId messageId, long updateVersion) { + this.messageId = messageId; + this.updateVersion = updateVersion; + } + } + + private static class LatestUpdates { + + @Nullable + private final LatestUpdate local, remote; + + private LatestUpdates(@Nullable LatestUpdate local, + @Nullable LatestUpdate remote) { + this.local = local; + this.remote = remote; + } + } + + private static class ClientVersion implements Comparable { + + private final ClientId clientId; + private final int clientVersion; + + private ClientVersion(ClientId clientId, int clientVersion) { + this.clientId = clientId; + this.clientVersion = clientVersion; + } + + @Override + public boolean equals(Object o) { + if (o instanceof ClientVersion) { + ClientVersion cv = (ClientVersion) o; + return clientId.equals(cv.clientId) + && clientVersion == cv.clientVersion; + } + return false; + } + + @Override + public int hashCode() { + return (clientId.hashCode() << 16) + clientVersion; + } + + @Override + public int compareTo(ClientVersion c) { + int compare = clientId.compareTo(c.clientId); + if (compare != 0) return compare; + return clientVersion - c.clientVersion; + } + } + + private static class ClientState { + + private final ClientVersion version; + private final boolean active; + + private ClientState(ClientVersion version, boolean active) { + this.version = version; + this.active = active; + } + + private ClientState(ClientId clientId, int clientVersion, + boolean active) { + this(new ClientVersion(clientId, clientVersion), active); + } + + @Override + public boolean equals(Object o) { + if (o instanceof ClientState) { + ClientState cs = (ClientState) o; + return version.equals(cs.version) && active == cs.active; + } + return false; + } + + @Override + public int hashCode() { + return version.hashCode(); + } + } +} diff --git a/bramble-core/src/main/java/org/briarproject/bramble/sync/ClientVersioningValidator.java b/bramble-core/src/main/java/org/briarproject/bramble/sync/ClientVersioningValidator.java new file mode 100644 index 000000000..2795523a1 --- /dev/null +++ b/bramble-core/src/main/java/org/briarproject/bramble/sync/ClientVersioningValidator.java @@ -0,0 +1,59 @@ +package org.briarproject.bramble.sync; + +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.sync.Group; +import org.briarproject.bramble.api.sync.Message; +import org.briarproject.bramble.api.system.Clock; + +import javax.annotation.concurrent.Immutable; + +import static org.briarproject.bramble.api.sync.ClientId.MAX_CLIENT_ID_LENGTH; +import static org.briarproject.bramble.sync.ClientVersioningConstants.MSG_KEY_LOCAL; +import static org.briarproject.bramble.sync.ClientVersioningConstants.MSG_KEY_UPDATE_VERSION; +import static org.briarproject.bramble.util.ValidationUtils.checkLength; +import static org.briarproject.bramble.util.ValidationUtils.checkSize; + +@Immutable +@NotNullByDefault +class ClientVersioningValidator extends BdfMessageValidator { + + ClientVersioningValidator(ClientHelper clientHelper, + MetadataEncoder metadataEncoder, Clock clock) { + super(clientHelper, metadataEncoder, clock); + } + + @Override + protected BdfMessageContext validateMessage(Message m, Group g, + BdfList body) throws FormatException { + // Client states, update version + checkSize(body, 2); + // Client states + BdfList states = body.getList(0); + int size = states.size(); + for (int i = 0; i < size; i++) { + BdfList clientState = states.getList(i); + // Client ID, client version, active + checkSize(clientState, 3); + String clientId = clientState.getString(0); + checkLength(clientId, 1, MAX_CLIENT_ID_LENGTH); + int clientVersion = clientState.getLong(1).intValue(); + if (clientVersion < 0) throw new FormatException(); + boolean active = clientState.getBoolean(2); + } + // Update version + long updateVersion = body.getLong(1); + if (updateVersion < 0) throw new FormatException(); + // Return the metadata + BdfDictionary meta = new BdfDictionary(); + meta.put(MSG_KEY_UPDATE_VERSION, updateVersion); + meta.put(MSG_KEY_LOCAL, false); + return new BdfMessageContext(meta); + } +} diff --git a/bramble-core/src/main/java/org/briarproject/bramble/sync/SyncModule.java b/bramble-core/src/main/java/org/briarproject/bramble/sync/SyncModule.java index ca6ec897a..0acf9e4e8 100644 --- a/bramble-core/src/main/java/org/briarproject/bramble/sync/SyncModule.java +++ b/bramble-core/src/main/java/org/briarproject/bramble/sync/SyncModule.java @@ -1,12 +1,16 @@ package org.briarproject.bramble.sync; import org.briarproject.bramble.PoliteExecutor; +import org.briarproject.bramble.api.client.ClientHelper; +import org.briarproject.bramble.api.contact.ContactManager; import org.briarproject.bramble.api.crypto.CryptoComponent; import org.briarproject.bramble.api.crypto.CryptoExecutor; +import org.briarproject.bramble.api.data.MetadataEncoder; import org.briarproject.bramble.api.db.DatabaseComponent; import org.briarproject.bramble.api.db.DatabaseExecutor; import org.briarproject.bramble.api.event.EventBus; import org.briarproject.bramble.api.lifecycle.LifecycleManager; +import org.briarproject.bramble.api.sync.ClientVersioningManager; import org.briarproject.bramble.api.sync.GroupFactory; import org.briarproject.bramble.api.sync.MessageFactory; import org.briarproject.bramble.api.sync.RecordReaderFactory; @@ -23,12 +27,18 @@ import javax.inject.Singleton; import dagger.Module; import dagger.Provides; +import static org.briarproject.bramble.api.sync.ClientVersioningManager.CLIENT_ID; + @Module public class SyncModule { public static class EagerSingletons { @Inject ValidationManager validationManager; + @Inject + ClientVersioningManager clientVersioningManager; + @Inject + ClientVersioningValidator clientVersioningValidator; } /** @@ -90,4 +100,29 @@ public class SyncModule { return new PoliteExecutor("ValidationExecutor", cryptoExecutor, MAX_CONCURRENT_VALIDATION_TASKS); } + + @Provides + @Singleton + ClientVersioningManager provideClientVersioningManager( + ClientVersioningManagerImpl clientVersioningManager, + LifecycleManager lifecycleManager, ContactManager contactManager, + ValidationManager validationManager) { + lifecycleManager.registerClient(clientVersioningManager); + lifecycleManager.registerService(clientVersioningManager); + contactManager.registerContactHook(clientVersioningManager); + validationManager.registerIncomingMessageHook(CLIENT_ID, + clientVersioningManager); + return clientVersioningManager; + } + + @Provides + @Singleton + ClientVersioningValidator provideClientVersioningValidator( + ClientHelper clientHelper, MetadataEncoder metadataEncoder, + Clock clock, ValidationManager validationManager) { + ClientVersioningValidator validator = new ClientVersioningValidator( + clientHelper, metadataEncoder, clock); + validationManager.registerMessageValidator(CLIENT_ID, validator); + return validator; + } } 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 3c2138b61..ebd3f2238 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 @@ -159,10 +159,9 @@ public abstract class BriarIntegrationTest