diff --git a/bramble-api/src/main/java/org/briarproject/bramble/api/client/ContactGroupFactory.java b/bramble-api/src/main/java/org/briarproject/bramble/api/client/ContactGroupFactory.java index 6b4ba6b4c23d022475c6f436050316c48ca996c7..896acb221fe9bb07d7b8f69a07172bdb4bff4dbd 100644 --- a/bramble-api/src/main/java/org/briarproject/bramble/api/client/ContactGroupFactory.java +++ b/bramble-api/src/main/java/org/briarproject/bramble/api/client/ContactGroupFactory.java @@ -12,19 +12,19 @@ public interface ContactGroupFactory { /** * Creates a group that is not shared with any contacts. */ - Group createLocalGroup(ClientId clientId, int clientVersion); + Group createLocalGroup(ClientId clientId, int majorVersion); /** * Creates a group for the given client to share with the given contact. */ - Group createContactGroup(ClientId clientId, int clientVersion, + Group createContactGroup(ClientId clientId, int majorVersion, Contact contact); /** * Creates a group for the given client to share between the given authors * identified by their AuthorIds. */ - Group createContactGroup(ClientId clientId, int clientVersion, + Group createContactGroup(ClientId clientId, int majorVersion, AuthorId authorId1, AuthorId authorId2); } 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 ea05938d49c5a24fe0d0da14bd40d718e2605824..3715674413fe008428753cbad66bcd4f2601c3cd 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 @@ -241,7 +241,8 @@ public interface DatabaseComponent { * <p/> * Read-only. */ - Collection<Group> getGroups(Transaction txn, ClientId c) throws DbException; + Collection<Group> getGroups(Transaction txn, ClientId c, int majorVersion) + throws DbException; /** * Returns the given group's visibility to the given contact, or @@ -266,6 +267,14 @@ public interface DatabaseComponent { */ Collection<LocalAuthor> getLocalAuthors(Transaction txn) throws DbException; + /** + * Returns the IDs of all messages in the given group. + * <p/> + * Read-only. + */ + Collection<MessageId> getMessageIds(Transaction txn, GroupId g) + throws DbException; + /** * Returns the IDs of any messages that need to be validated. * <p/> diff --git a/bramble-api/src/main/java/org/briarproject/bramble/api/lifecycle/LifecycleManager.java b/bramble-api/src/main/java/org/briarproject/bramble/api/lifecycle/LifecycleManager.java index 5a036a3b6cc2005a36c28bbaacb67638256941a1..d3cd1bc245f33b855d522f47b32f0501689668b7 100644 --- a/bramble-api/src/main/java/org/briarproject/bramble/api/lifecycle/LifecycleManager.java +++ b/bramble-api/src/main/java/org/briarproject/bramble/api/lifecycle/LifecycleManager.java @@ -43,17 +43,20 @@ public interface LifecycleManager { } /** - * Registers a {@link Service} to be started and stopped. + * Registers a {@link Service} to be started and stopped. This method + * should be called before {@link #startServices(String)}. */ void registerService(Service s); /** - * Registers a {@link Client} to be started. + * Registers a {@link Client} to be started. This method should be called + * before {@link #startServices(String)}. */ void registerClient(Client c); /** - * Registers an {@link ExecutorService} to be shut down. + * Registers an {@link ExecutorService} to be shut down. This method + * should be called before {@link #startServices(String)}. */ void registerForShutdown(ExecutorService e); diff --git a/bramble-api/src/main/java/org/briarproject/bramble/api/properties/TransportPropertyManager.java b/bramble-api/src/main/java/org/briarproject/bramble/api/properties/TransportPropertyManager.java index fe33328a358b9feda1c21fe4eda9f9411cb8e791..a634bfaa74abac3057cdb548e01c4f8dee5e85d3 100644 --- a/bramble-api/src/main/java/org/briarproject/bramble/api/properties/TransportPropertyManager.java +++ b/bramble-api/src/main/java/org/briarproject/bramble/api/properties/TransportPropertyManager.java @@ -15,12 +15,17 @@ public interface TransportPropertyManager { /** * The unique ID of the transport property client. */ - ClientId CLIENT_ID = new ClientId("org.briarproject.briar.properties"); + ClientId CLIENT_ID = new ClientId("org.briarproject.bramble.properties"); /** - * The current version of the transport property client. + * The current major version of the transport property client. */ - int CLIENT_VERSION = 0; + int MAJOR_VERSION = 0; + + /** + * The current minor version of the transport property client. + */ + int MINOR_VERSION = 0; /** * Stores the given properties received while adding a contact - they will @@ -37,8 +42,8 @@ public interface TransportPropertyManager { /** * Returns the local transport properties for all transports. - * <br/> - * TODO: Transaction can be read-only when code is simplified + * <p/> + * Read-only. */ Map<TransportId, TransportProperties> getLocalProperties(Transaction txn) throws DbException; diff --git a/bramble-api/src/main/java/org/briarproject/bramble/api/sync/Group.java b/bramble-api/src/main/java/org/briarproject/bramble/api/sync/Group.java index 5ffe422c2ac7dd369a57ff3673335ddfb88eb25a..2d395cce55e59a5703423f0c43c3540c7f91dc80 100644 --- a/bramble-api/src/main/java/org/briarproject/bramble/api/sync/Group.java +++ b/bramble-api/src/main/java/org/briarproject/bramble/api/sync/Group.java @@ -5,9 +5,24 @@ import static org.briarproject.bramble.api.sync.SyncConstants.MAX_GROUP_DESCRIPT public class Group { public enum Visibility { - INVISIBLE, // The group is not visible - VISIBLE, // The group is visible but messages are not shared - SHARED // The group is visible and messages are shared + + INVISIBLE(0), // The group is not visible + VISIBLE(1), // The group is visible, messages are accepted but not sent + SHARED(2); // The group is visible, messages are accepted and sent + + private final int value; + + Visibility(int value) { + this.value = value; + } + + public int getValue() { + return value; + } + + public static Visibility min(Visibility a, Visibility b) { + return a.getValue() < b.getValue() ? a : b; + } } /** @@ -17,13 +32,16 @@ public class Group { private final GroupId id; private final ClientId clientId; + private final int majorVersion; private final byte[] descriptor; - public Group(GroupId id, ClientId clientId, byte[] descriptor) { + public Group(GroupId id, ClientId clientId, int majorVersion, + byte[] descriptor) { if (descriptor.length > MAX_GROUP_DESCRIPTOR_LENGTH) throw new IllegalArgumentException(); this.id = id; this.clientId = clientId; + this.majorVersion = majorVersion; this.descriptor = descriptor; } @@ -41,6 +59,13 @@ public class Group { return clientId; } + /** + * Returns the major version of the client to which the group belongs. + */ + public int getMajorVersion() { + return majorVersion; + } + /** * Returns the group's descriptor. */ diff --git a/bramble-api/src/main/java/org/briarproject/bramble/api/sync/GroupFactory.java b/bramble-api/src/main/java/org/briarproject/bramble/api/sync/GroupFactory.java index 844ded0e14863172165246aa13d534433c91df87..9924c51b8507a861e9439bdf625ed69bad785618 100644 --- a/bramble-api/src/main/java/org/briarproject/bramble/api/sync/GroupFactory.java +++ b/bramble-api/src/main/java/org/briarproject/bramble/api/sync/GroupFactory.java @@ -6,7 +6,7 @@ import org.briarproject.bramble.api.nullsafety.NotNullByDefault; public interface GroupFactory { /** - * Creates a group with the given client ID, client version and descriptor. + * Creates a group with the given client ID, major version and descriptor. */ - Group createGroup(ClientId c, int clientVersion, byte[] descriptor); + Group createGroup(ClientId c, int majorVersion, byte[] descriptor); } 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 80f48da6661ad3d924a0f85e7901a169b4b0e16e..5f196f147060a08fee698f2e4747c6f0609dc074 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-api/src/main/java/org/briarproject/bramble/api/sync/ValidationManager.java b/bramble-api/src/main/java/org/briarproject/bramble/api/sync/ValidationManager.java index d80042b41521aada66365f1bd21cdb1c620c114b..1718cac8101fc0b9a03719219b5bdaad23f72d84 100644 --- a/bramble-api/src/main/java/org/briarproject/bramble/api/sync/ValidationManager.java +++ b/bramble-api/src/main/java/org/briarproject/bramble/api/sync/ValidationManager.java @@ -3,6 +3,7 @@ package org.briarproject.bramble.api.sync; 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.LifecycleManager; import org.briarproject.bramble.api.nullsafety.NotNullByDefault; /** @@ -33,15 +34,20 @@ public interface ValidationManager { } /** - * Sets the message validator for the given client. + * Registers the message validator for the given client. This method + * should be called before {@link LifecycleManager#startServices(String)}. */ - void registerMessageValidator(ClientId c, MessageValidator v); + void registerMessageValidator(ClientId c, int majorVersion, + MessageValidator v); /** - * Sets the incoming message hook for the given client. The hook will be - * called once for each incoming message that passes validation. + * Registers the incoming message hook for the given client. The hook will + * be called once for each incoming message that passes validation. This + * method should be called before + * {@link LifecycleManager#startServices(String)}. */ - void registerIncomingMessageHook(ClientId c, IncomingMessageHook hook); + void registerIncomingMessageHook(ClientId c, int majorVersion, + IncomingMessageHook hook); interface MessageValidator { diff --git a/bramble-api/src/main/java/org/briarproject/bramble/api/versioning/ClientMajorVersion.java b/bramble-api/src/main/java/org/briarproject/bramble/api/versioning/ClientMajorVersion.java new file mode 100644 index 0000000000000000000000000000000000000000..d57eb987ebae611a494828be80a4a1fb57044eb0 --- /dev/null +++ b/bramble-api/src/main/java/org/briarproject/bramble/api/versioning/ClientMajorVersion.java @@ -0,0 +1,50 @@ +package org.briarproject.bramble.api.versioning; + +import org.briarproject.bramble.api.nullsafety.NotNullByDefault; +import org.briarproject.bramble.api.sync.ClientId; + +import javax.annotation.concurrent.Immutable; + +@Immutable +@NotNullByDefault +public class ClientMajorVersion implements Comparable<ClientMajorVersion> { + + private final ClientId clientId; + private final int majorVersion; + + public ClientMajorVersion(ClientId clientId, int majorVersion) { + this.clientId = clientId; + this.majorVersion = majorVersion; + } + + public ClientId getClientId() { + return clientId; + } + + public int getMajorVersion() { + return majorVersion; + } + + @Override + public boolean equals(Object o) { + if (o instanceof ClientMajorVersion) { + ClientMajorVersion cv = (ClientMajorVersion) o; + return clientId.equals(cv.clientId) + && majorVersion == cv.majorVersion; + } + return false; + } + + @Override + public int hashCode() { + return (clientId.hashCode() << 16) + majorVersion; + } + + @Override + public int compareTo(ClientMajorVersion cv) { + int compare = clientId.compareTo(cv.clientId); + if (compare != 0) return compare; + return majorVersion - cv.majorVersion; + } +} + diff --git a/bramble-api/src/main/java/org/briarproject/bramble/api/versioning/ClientVersioningManager.java b/bramble-api/src/main/java/org/briarproject/bramble/api/versioning/ClientVersioningManager.java new file mode 100644 index 0000000000000000000000000000000000000000..0cb2fc478d34206fa5b3fa3420d2c0486b747a42 --- /dev/null +++ b/bramble-api/src/main/java/org/briarproject/bramble/api/versioning/ClientVersioningManager.java @@ -0,0 +1,45 @@ +package org.briarproject.bramble.api.versioning; + +import org.briarproject.bramble.api.contact.Contact; +import org.briarproject.bramble.api.contact.ContactId; +import org.briarproject.bramble.api.db.DbException; +import org.briarproject.bramble.api.db.Transaction; +import org.briarproject.bramble.api.lifecycle.LifecycleManager; +import org.briarproject.bramble.api.nullsafety.NotNullByDefault; +import org.briarproject.bramble.api.sync.ClientId; +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 major version of the versioning client. + */ + int MAJOR_VERSION = 0; + + /** + * Registers a client that will be advertised to contacts. The hook will + * be called when the visibility of the client changes. This method should + * be called before {@link LifecycleManager#startServices(String)}. + */ + void registerClient(ClientId clientId, int majorVersion, int minorVersion, + ClientVersioningHook hook); + + /** + * Returns the visibility of the given client with respect to the given + * contact. + */ + Visibility getClientVisibility(Transaction txn, ContactId contactId, + ClientId clientId, int majorVersion) throws DbException; + + interface ClientVersioningHook { + + void onClientVisibilityChanging(Transaction txn, Contact c, + Visibility v) throws DbException; + } +} diff --git a/bramble-api/src/test/java/org/briarproject/bramble/test/TestUtils.java b/bramble-api/src/test/java/org/briarproject/bramble/test/TestUtils.java index 3ce8d154996355b2a96a6703fc854318c8a9d951..c77fde6e9921f5b5c337a29ef3873ce2b96c7456 100644 --- a/bramble-api/src/test/java/org/briarproject/bramble/test/TestUtils.java +++ b/bramble-api/src/test/java/org/briarproject/bramble/test/TestUtils.java @@ -117,15 +117,16 @@ public class TestUtils { return new Author(id, FORMAT_VERSION, name, publicKey); } - public static Group getGroup(ClientId clientId) { + public static Group getGroup(ClientId clientId, int majorVersion) { int descriptorLength = 1 + random.nextInt(MAX_GROUP_DESCRIPTOR_LENGTH); - return getGroup(clientId, descriptorLength); + return getGroup(clientId, majorVersion, descriptorLength); } - public static Group getGroup(ClientId clientId, int descriptorLength) { + public static Group getGroup(ClientId clientId, int majorVersion, + int descriptorLength) { GroupId groupId = new GroupId(getRandomId()); byte[] descriptor = getRandomBytes(descriptorLength); - return new Group(groupId, clientId, descriptor); + return new Group(groupId, clientId, majorVersion, descriptor); } public static Message getMessage(GroupId groupId) { diff --git a/bramble-core/src/main/java/org/briarproject/bramble/BrambleCoreEagerSingletons.java b/bramble-core/src/main/java/org/briarproject/bramble/BrambleCoreEagerSingletons.java index e7a211e294761124ebb5313dfa2adee1bf9c3381..367e7785f06f264896d024a89b9798a5b9b6bd16 100644 --- a/bramble-core/src/main/java/org/briarproject/bramble/BrambleCoreEagerSingletons.java +++ b/bramble-core/src/main/java/org/briarproject/bramble/BrambleCoreEagerSingletons.java @@ -1,7 +1,7 @@ package org.briarproject.bramble; import org.briarproject.bramble.contact.ContactModule; -import org.briarproject.bramble.crypto.CryptoModule; +import org.briarproject.bramble.crypto.CryptoExecutorModule; import org.briarproject.bramble.db.DatabaseExecutorModule; import org.briarproject.bramble.identity.IdentityModule; import org.briarproject.bramble.lifecycle.LifecycleModule; @@ -10,12 +10,13 @@ import org.briarproject.bramble.properties.PropertiesModule; import org.briarproject.bramble.sync.SyncModule; import org.briarproject.bramble.system.SystemModule; import org.briarproject.bramble.transport.TransportModule; +import org.briarproject.bramble.versioning.VersioningModule; public interface BrambleCoreEagerSingletons { void inject(ContactModule.EagerSingletons init); - void inject(CryptoModule.EagerSingletons init); + void inject(CryptoExecutorModule.EagerSingletons init); void inject(DatabaseExecutorModule.EagerSingletons init); @@ -32,4 +33,6 @@ public interface BrambleCoreEagerSingletons { void inject(SystemModule.EagerSingletons init); void inject(TransportModule.EagerSingletons init); + + void inject(VersioningModule.EagerSingletons init); } diff --git a/bramble-core/src/main/java/org/briarproject/bramble/BrambleCoreModule.java b/bramble-core/src/main/java/org/briarproject/bramble/BrambleCoreModule.java index 365b50de915b71198f674cdaf961ad99246c5500..b92329b187ea87e63ae703849c4de08f8d255a35 100644 --- a/bramble-core/src/main/java/org/briarproject/bramble/BrambleCoreModule.java +++ b/bramble-core/src/main/java/org/briarproject/bramble/BrambleCoreModule.java @@ -2,6 +2,7 @@ package org.briarproject.bramble; import org.briarproject.bramble.client.ClientModule; import org.briarproject.bramble.contact.ContactModule; +import org.briarproject.bramble.crypto.CryptoExecutorModule; import org.briarproject.bramble.crypto.CryptoModule; import org.briarproject.bramble.data.DataModule; import org.briarproject.bramble.db.DatabaseExecutorModule; @@ -19,6 +20,7 @@ import org.briarproject.bramble.socks.SocksModule; import org.briarproject.bramble.sync.SyncModule; import org.briarproject.bramble.system.SystemModule; import org.briarproject.bramble.transport.TransportModule; +import org.briarproject.bramble.versioning.VersioningModule; import dagger.Module; @@ -26,6 +28,7 @@ import dagger.Module; ClientModule.class, ContactModule.class, CryptoModule.class, + CryptoExecutorModule.class, DataModule.class, DatabaseModule.class, DatabaseExecutorModule.class, @@ -41,13 +44,14 @@ import dagger.Module; SocksModule.class, SyncModule.class, SystemModule.class, - TransportModule.class + TransportModule.class, + VersioningModule.class }) public class BrambleCoreModule { public static void initEagerSingletons(BrambleCoreEagerSingletons c) { c.inject(new ContactModule.EagerSingletons()); - c.inject(new CryptoModule.EagerSingletons()); + c.inject(new CryptoExecutorModule.EagerSingletons()); c.inject(new DatabaseExecutorModule.EagerSingletons()); c.inject(new IdentityModule.EagerSingletons()); c.inject(new LifecycleModule.EagerSingletons()); @@ -56,5 +60,6 @@ public class BrambleCoreModule { c.inject(new SyncModule.EagerSingletons()); c.inject(new SystemModule.EagerSingletons()); c.inject(new TransportModule.EagerSingletons()); + c.inject(new VersioningModule.EagerSingletons()); } } diff --git a/bramble-core/src/main/java/org/briarproject/bramble/client/ContactGroupFactoryImpl.java b/bramble-core/src/main/java/org/briarproject/bramble/client/ContactGroupFactoryImpl.java index fba2aa745a72fca908df892714ee9305df9ef02b..6b35c9281853010344150d0fb1d6771a6cb3e1ac 100644 --- a/bramble-core/src/main/java/org/briarproject/bramble/client/ContactGroupFactoryImpl.java +++ b/bramble-core/src/main/java/org/briarproject/bramble/client/ContactGroupFactoryImpl.java @@ -32,25 +32,25 @@ class ContactGroupFactoryImpl implements ContactGroupFactory { } @Override - public Group createLocalGroup(ClientId clientId, int clientVersion) { - return groupFactory.createGroup(clientId, clientVersion, + public Group createLocalGroup(ClientId clientId, int majorVersion) { + return groupFactory.createGroup(clientId, majorVersion, LOCAL_GROUP_DESCRIPTOR); } @Override - public Group createContactGroup(ClientId clientId, int clientVersion, + public Group createContactGroup(ClientId clientId, int majorVersion, Contact contact) { AuthorId local = contact.getLocalAuthorId(); AuthorId remote = contact.getAuthor().getId(); byte[] descriptor = createGroupDescriptor(local, remote); - return groupFactory.createGroup(clientId, clientVersion, descriptor); + return groupFactory.createGroup(clientId, majorVersion, descriptor); } @Override - public Group createContactGroup(ClientId clientId, int clientVersion, + public Group createContactGroup(ClientId clientId, int majorVersion, AuthorId authorId1, AuthorId authorId2) { byte[] descriptor = createGroupDescriptor(authorId1, authorId2); - return groupFactory.createGroup(clientId, clientVersion, descriptor); + return groupFactory.createGroup(clientId, majorVersion, descriptor); } private byte[] createGroupDescriptor(AuthorId local, AuthorId remote) { diff --git a/bramble-core/src/main/java/org/briarproject/bramble/crypto/CryptoExecutorModule.java b/bramble-core/src/main/java/org/briarproject/bramble/crypto/CryptoExecutorModule.java new file mode 100644 index 0000000000000000000000000000000000000000..ab0e4114f5672469fd0c26b8061389c1e1b7172c --- /dev/null +++ b/bramble-core/src/main/java/org/briarproject/bramble/crypto/CryptoExecutorModule.java @@ -0,0 +1,67 @@ +package org.briarproject.bramble.crypto; + +import org.briarproject.bramble.TimeLoggingExecutor; +import org.briarproject.bramble.api.crypto.CryptoExecutor; +import org.briarproject.bramble.api.lifecycle.LifecycleManager; + +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.Executor; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.RejectedExecutionHandler; +import java.util.concurrent.ThreadPoolExecutor; + +import javax.inject.Inject; +import javax.inject.Singleton; + +import dagger.Module; +import dagger.Provides; + +import static java.util.concurrent.TimeUnit.SECONDS; + +@Module +public class CryptoExecutorModule { + + public static class EagerSingletons { + @Inject + @CryptoExecutor + ExecutorService cryptoExecutor; + } + + /** + * The maximum number of executor threads. + * <p> + * The number of available processors can change during the lifetime of the + * JVM, so this is just a reasonable guess. + */ + private static final int MAX_EXECUTOR_THREADS = + Math.max(1, Runtime.getRuntime().availableProcessors() - 1); + + private final ExecutorService cryptoExecutor; + + public CryptoExecutorModule() { + // Use an unbounded queue + BlockingQueue<Runnable> queue = new LinkedBlockingQueue<>(); + // Discard tasks that are submitted during shutdown + RejectedExecutionHandler policy = + new ThreadPoolExecutor.DiscardPolicy(); + // Create a limited # of threads and keep them in the pool for 60 secs + cryptoExecutor = new TimeLoggingExecutor("CryptoExecutor", 0, + MAX_EXECUTOR_THREADS, 60, SECONDS, queue, policy); + } + + @Provides + @Singleton + @CryptoExecutor + ExecutorService provideCryptoExecutorService( + LifecycleManager lifecycleManager) { + lifecycleManager.registerForShutdown(cryptoExecutor); + return cryptoExecutor; + } + + @Provides + @CryptoExecutor + Executor provideCryptoExecutor() { + return cryptoExecutor; + } +} diff --git a/bramble-core/src/main/java/org/briarproject/bramble/crypto/CryptoModule.java b/bramble-core/src/main/java/org/briarproject/bramble/crypto/CryptoModule.java index 25d24995d5ddf45b5a9aefea69fe1c5c101d0acd..680391bd001e41389c941f30663fcbb7994dfa63 100644 --- a/bramble-core/src/main/java/org/briarproject/bramble/crypto/CryptoModule.java +++ b/bramble-core/src/main/java/org/briarproject/bramble/crypto/CryptoModule.java @@ -1,64 +1,24 @@ package org.briarproject.bramble.crypto; -import org.briarproject.bramble.TimeLoggingExecutor; import org.briarproject.bramble.api.crypto.CryptoComponent; -import org.briarproject.bramble.api.crypto.CryptoExecutor; import org.briarproject.bramble.api.crypto.KeyAgreementCrypto; import org.briarproject.bramble.api.crypto.PasswordStrengthEstimator; import org.briarproject.bramble.api.crypto.StreamDecrypterFactory; import org.briarproject.bramble.api.crypto.StreamEncrypterFactory; import org.briarproject.bramble.api.crypto.TransportCrypto; -import org.briarproject.bramble.api.lifecycle.LifecycleManager; import org.briarproject.bramble.api.system.SecureRandomProvider; import java.security.SecureRandom; -import java.util.concurrent.BlockingQueue; -import java.util.concurrent.Executor; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.LinkedBlockingQueue; -import java.util.concurrent.RejectedExecutionHandler; -import java.util.concurrent.ThreadPoolExecutor; -import javax.inject.Inject; import javax.inject.Provider; import javax.inject.Singleton; import dagger.Module; import dagger.Provides; -import static java.util.concurrent.TimeUnit.SECONDS; - @Module public class CryptoModule { - public static class EagerSingletons { - @Inject - @CryptoExecutor - ExecutorService cryptoExecutor; - } - - /** - * The maximum number of executor threads. - * <p> - * The number of available processors can change during the lifetime of the - * JVM, so this is just a reasonable guess. - */ - private static final int MAX_EXECUTOR_THREADS = - Math.max(1, Runtime.getRuntime().availableProcessors() - 1); - - private final ExecutorService cryptoExecutor; - - public CryptoModule() { - // Use an unbounded queue - BlockingQueue<Runnable> queue = new LinkedBlockingQueue<>(); - // Discard tasks that are submitted during shutdown - RejectedExecutionHandler policy = - new ThreadPoolExecutor.DiscardPolicy(); - // Create a limited # of threads and keep them in the pool for 60 secs - cryptoExecutor = new TimeLoggingExecutor("CryptoExecutor", 0, - MAX_EXECUTOR_THREADS, 60, SECONDS, queue, policy); - } - @Provides AuthenticatedCipher provideAuthenticatedCipher() { return new XSalsa20Poly1305AuthenticatedCipher(); @@ -103,21 +63,6 @@ public class CryptoModule { return keyAgreementCrypto; } - @Provides - @Singleton - @CryptoExecutor - ExecutorService getCryptoExecutorService( - LifecycleManager lifecycleManager) { - lifecycleManager.registerForShutdown(cryptoExecutor); - return cryptoExecutor; - } - - @Provides - @CryptoExecutor - Executor getCryptoExecutor() { - return cryptoExecutor; - } - @Provides SecureRandom getSecureRandom(CryptoComponent crypto) { return crypto.getSecureRandom(); 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 0c3b2b1bccb0681af813a7e0582ac5bcc7766f52..f20c0da5edf4ad11d411b959139cadf36ea4d7e5 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 @@ -266,7 +266,8 @@ interface Database<T> { * <p/> * Read-only. */ - Collection<Group> getGroups(T txn, ClientId c) throws DbException; + Collection<Group> getGroups(T txn, ClientId c, int majorVersion) + throws DbException; /** * Returns the given group's visibility to the given contact, or 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 aaab4b9dd8ae51dbaf516a757066ec3d23e7f7ba..1534a9d81bfb47d68d03f4ab90620f2b1397d0b4 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 @@ -435,10 +435,10 @@ class DatabaseComponentImpl<T> implements DatabaseComponent { } @Override - public Collection<Group> getGroups(Transaction transaction, ClientId c) - throws DbException { + public Collection<Group> getGroups(Transaction transaction, ClientId c, + int majorVersion) throws DbException { T txn = unbox(transaction); - return db.getGroups(txn, c); + return db.getGroups(txn, c, majorVersion); } @Override @@ -466,6 +466,15 @@ class DatabaseComponentImpl<T> implements DatabaseComponent { return db.getLocalAuthors(txn); } + @Override + public Collection<MessageId> getMessageIds(Transaction transaction, + GroupId g) throws DbException { + T txn = unbox(transaction); + if (!db.containsGroup(txn, g)) + throw new NoSuchGroupException(); + return db.getMessageIds(txn, g); + } + @Override public Collection<MessageId> getMessagesToValidate(Transaction transaction) 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 dd0e928a8569c2c52c985fea2a9acb87470ae267..7115808e289bd8fc51a871b10970f70bc7579605 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 @@ -74,7 +74,7 @@ import static org.briarproject.bramble.db.ExponentialBackoff.calculateExpiry; abstract class JdbcDatabase implements Database<Connection> { // Package access for testing - static final int CODE_SCHEMA_VERSION = 37; + static final int CODE_SCHEMA_VERSION = 38; // Rotation period offsets for incoming transport keys private static final int OFFSET_PREV = -1; @@ -117,6 +117,7 @@ abstract class JdbcDatabase implements Database<Connection> { "CREATE TABLE groups" + " (groupId _HASH NOT NULL," + " clientId _STRING NOT NULL," + + " majorVersion INT NOT NULL," + " descriptor _BINARY NOT NULL," + " PRIMARY KEY (groupId))"; @@ -275,9 +276,9 @@ abstract class JdbcDatabase implements Database<Connection> { "CREATE INDEX IF NOT EXISTS contactsByAuthorId" + " ON contacts (authorId)"; - private static final String INDEX_GROUPS_BY_CLIENT_ID = - "CREATE INDEX IF NOT EXISTS groupsByClientId" - + " ON groups (clientId)"; + private static final String INDEX_GROUPS_BY_CLIENT_ID_MAJOR_VERSION = + "CREATE INDEX IF NOT EXISTS groupsByClientIdMajorVersion" + + " ON groups (clientId, majorVersion)"; private static final String INDEX_MESSAGE_METADATA_BY_GROUP_ID_STATE = "CREATE INDEX IF NOT EXISTS messageMetadataByGroupIdState" @@ -444,7 +445,7 @@ abstract class JdbcDatabase implements Database<Connection> { try { s = txn.createStatement(); s.executeUpdate(INDEX_CONTACTS_BY_AUTHOR_ID); - s.executeUpdate(INDEX_GROUPS_BY_CLIENT_ID); + s.executeUpdate(INDEX_GROUPS_BY_CLIENT_ID_MAJOR_VERSION); s.executeUpdate(INDEX_MESSAGE_METADATA_BY_GROUP_ID_STATE); s.executeUpdate(INDEX_MESSAGE_DEPENDENCIES_BY_DEPENDENCY_ID); s.executeUpdate(INDEX_STATUSES_BY_CONTACT_ID_GROUP_ID); @@ -612,12 +613,14 @@ abstract class JdbcDatabase implements Database<Connection> { public void addGroup(Connection txn, Group g) throws DbException { PreparedStatement ps = null; try { - String sql = "INSERT INTO groups (groupId, clientId, descriptor)" - + " VALUES (?, ?, ?)"; + String sql = "INSERT INTO groups" + + " (groupId, clientId, majorVersion, descriptor)" + + " VALUES (?, ?, ?, ?)"; ps = txn.prepareStatement(sql); ps.setBytes(1, g.getId().getBytes()); ps.setString(2, g.getClientId().getString()); - ps.setBytes(3, g.getDescriptor()); + ps.setInt(3, g.getMajorVersion()); + ps.setBytes(4, g.getDescriptor()); int affected = ps.executeUpdate(); if (affected != 1) throw new DbStateException(); ps.close(); @@ -1346,17 +1349,18 @@ abstract class JdbcDatabase implements Database<Connection> { PreparedStatement ps = null; ResultSet rs = null; try { - String sql = "SELECT clientId, descriptor FROM groups" - + " WHERE groupId = ?"; + String sql = "SELECT clientId, majorVersion, descriptor" + + " FROM groups WHERE groupId = ?"; ps = txn.prepareStatement(sql); ps.setBytes(1, g.getBytes()); rs = ps.executeQuery(); if (!rs.next()) throw new DbStateException(); ClientId clientId = new ClientId(rs.getString(1)); - byte[] descriptor = rs.getBytes(2); + int majorVersion = rs.getInt(2); + byte[] descriptor = rs.getBytes(3); rs.close(); ps.close(); - return new Group(g, clientId, descriptor); + return new Group(g, clientId, majorVersion, descriptor); } catch (SQLException e) { tryToClose(rs); tryToClose(ps); @@ -1365,21 +1369,22 @@ abstract class JdbcDatabase implements Database<Connection> { } @Override - public Collection<Group> getGroups(Connection txn, ClientId c) - throws DbException { + public Collection<Group> getGroups(Connection txn, ClientId c, + int majorVersion) throws DbException { PreparedStatement ps = null; ResultSet rs = null; try { String sql = "SELECT groupId, descriptor FROM groups" - + " WHERE clientId = ?"; + + " WHERE clientId = ? AND majorVersion = ?"; ps = txn.prepareStatement(sql); ps.setString(1, c.getString()); + ps.setInt(2, majorVersion); rs = ps.executeQuery(); List<Group> groups = new ArrayList<>(); while (rs.next()) { GroupId id = new GroupId(rs.getBytes(1)); byte[] descriptor = rs.getBytes(2); - groups.add(new Group(id, c, descriptor)); + groups.add(new Group(id, c, majorVersion, descriptor)); } rs.close(); ps.close(); diff --git a/bramble-core/src/main/java/org/briarproject/bramble/properties/PropertiesModule.java b/bramble-core/src/main/java/org/briarproject/bramble/properties/PropertiesModule.java index 7f7da60cec2b8574786fa8ab63a901b676fdb940..866f95da2643b998e9e4583c90bec6ca7cc896a8 100644 --- a/bramble-core/src/main/java/org/briarproject/bramble/properties/PropertiesModule.java +++ b/bramble-core/src/main/java/org/briarproject/bramble/properties/PropertiesModule.java @@ -7,6 +7,7 @@ import org.briarproject.bramble.api.lifecycle.LifecycleManager; import org.briarproject.bramble.api.properties.TransportPropertyManager; import org.briarproject.bramble.api.sync.ValidationManager; import org.briarproject.bramble.api.system.Clock; +import org.briarproject.bramble.api.versioning.ClientVersioningManager; import javax.inject.Inject; import javax.inject.Singleton; @@ -15,6 +16,8 @@ import dagger.Module; import dagger.Provides; import static org.briarproject.bramble.api.properties.TransportPropertyManager.CLIENT_ID; +import static org.briarproject.bramble.api.properties.TransportPropertyManager.MAJOR_VERSION; +import static org.briarproject.bramble.api.properties.TransportPropertyManager.MINOR_VERSION; @Module public class PropertiesModule { @@ -33,7 +36,8 @@ public class PropertiesModule { Clock clock) { TransportPropertyValidator validator = new TransportPropertyValidator( clientHelper, metadataEncoder, clock); - validationManager.registerMessageValidator(CLIENT_ID, validator); + validationManager.registerMessageValidator(CLIENT_ID, MAJOR_VERSION, + validator); return validator; } @@ -42,11 +46,14 @@ public class PropertiesModule { TransportPropertyManager getTransportPropertyManager( LifecycleManager lifecycleManager, ValidationManager validationManager, ContactManager contactManager, + ClientVersioningManager clientVersioningManager, TransportPropertyManagerImpl transportPropertyManager) { lifecycleManager.registerClient(transportPropertyManager); - validationManager.registerIncomingMessageHook(CLIENT_ID, + validationManager.registerIncomingMessageHook(CLIENT_ID, MAJOR_VERSION, transportPropertyManager); contactManager.registerContactHook(transportPropertyManager); + clientVersioningManager.registerClient(CLIENT_ID, MAJOR_VERSION, + MINOR_VERSION, transportPropertyManager); return transportPropertyManager; } } diff --git a/bramble-core/src/main/java/org/briarproject/bramble/properties/TransportPropertyManagerImpl.java b/bramble-core/src/main/java/org/briarproject/bramble/properties/TransportPropertyManagerImpl.java index 1296f03bcb1bb5f4b2d1c43b2de4cfb215121412..4b4c360dbd4c2a07c02860e9427c17b8a75a9888 100644 --- a/bramble-core/src/main/java/org/briarproject/bramble/properties/TransportPropertyManagerImpl.java +++ b/bramble-core/src/main/java/org/briarproject/bramble/properties/TransportPropertyManagerImpl.java @@ -19,12 +19,15 @@ import org.briarproject.bramble.api.properties.TransportProperties; import org.briarproject.bramble.api.properties.TransportPropertyManager; import org.briarproject.bramble.api.sync.Client; 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 org.briarproject.bramble.api.versioning.ClientVersioningManager; +import org.briarproject.bramble.api.versioning.ClientVersioningManager.ClientVersioningHook; import java.util.HashMap; import java.util.Map; @@ -34,15 +37,14 @@ import javax.annotation.Nullable; import javax.annotation.concurrent.Immutable; import javax.inject.Inject; -import static org.briarproject.bramble.api.sync.Group.Visibility.SHARED; - @Immutable @NotNullByDefault class TransportPropertyManagerImpl implements TransportPropertyManager, - Client, ContactHook, IncomingMessageHook { + Client, ContactHook, ClientVersioningHook, IncomingMessageHook { private final DatabaseComponent db; private final ClientHelper clientHelper; + private final ClientVersioningManager clientVersioningManager; private final MetadataParser metadataParser; private final ContactGroupFactory contactGroupFactory; private final Clock clock; @@ -50,22 +52,25 @@ class TransportPropertyManagerImpl implements TransportPropertyManager, @Inject TransportPropertyManagerImpl(DatabaseComponent db, - ClientHelper clientHelper, MetadataParser metadataParser, + ClientHelper clientHelper, + ClientVersioningManager clientVersioningManager, + MetadataParser metadataParser, ContactGroupFactory contactGroupFactory, Clock clock) { this.db = db; this.clientHelper = clientHelper; + this.clientVersioningManager = clientVersioningManager; this.metadataParser = metadataParser; this.contactGroupFactory = contactGroupFactory; this.clock = clock; localGroup = contactGroupFactory.createLocalGroup(CLIENT_ID, - CLIENT_VERSION); + MAJOR_VERSION); } @Override public void createLocalState(Transaction txn) throws DbException { if (db.containsGroup(txn, localGroup.getId())) return; db.addGroup(txn, localGroup); - // Ensure we've set things up for any pre-existing contacts + // Set things up for any pre-existing contacts for (Contact c : db.getContacts(txn)) addingContact(txn, c); } @@ -73,11 +78,11 @@ class TransportPropertyManagerImpl implements TransportPropertyManager, public void addingContact(Transaction txn, Contact c) throws DbException { // Create a group to share with the contact Group g = getContactGroup(c); - // Return if we've already set things up for this contact - if (db.containsGroup(txn, g.getId())) return; - // Store the group and share it with the contact db.addGroup(txn, g); - db.setGroupVisibility(txn, c.getId(), g.getId(), SHARED); + // Apply the client's visibility to the contact group + Visibility client = clientVersioningManager.getClientVisibility(txn, + c.getId(), CLIENT_ID, MAJOR_VERSION); + db.setGroupVisibility(txn, c.getId(), g.getId(), client); // Copy the latest local properties into the group Map<TransportId, TransportProperties> local = getLocalProperties(txn); for (Entry<TransportId, TransportProperties> e : local.entrySet()) { @@ -91,6 +96,14 @@ class TransportPropertyManagerImpl implements TransportPropertyManager, db.removeGroup(txn, getContactGroup(c)); } + @Override + public void onClientVisibilityChanging(Transaction txn, Contact c, + Visibility v) throws DbException { + // Apply the client's visibility to the contact group + Group g = getContactGroup(c); + db.setGroupVisibility(txn, c.getId(), g.getId(), v); + } + @Override public boolean incomingMessage(Transaction txn, Message m, Metadata meta) throws DbException, InvalidMessageException { @@ -289,7 +302,7 @@ class TransportPropertyManagerImpl implements TransportPropertyManager, private Group getContactGroup(Contact c) { return contactGroupFactory.createContactGroup(CLIENT_ID, - CLIENT_VERSION, c); + MAJOR_VERSION, c); } private void storeMessage(Transaction txn, GroupId g, TransportId t, diff --git a/bramble-core/src/main/java/org/briarproject/bramble/sync/GroupFactoryImpl.java b/bramble-core/src/main/java/org/briarproject/bramble/sync/GroupFactoryImpl.java index e7bc4e16e6dca75ee7622f0d42c21d69a56d02b1..5a46a4aad5287d4e896778834681b703ae0336aa 100644 --- a/bramble-core/src/main/java/org/briarproject/bramble/sync/GroupFactoryImpl.java +++ b/bramble-core/src/main/java/org/briarproject/bramble/sync/GroupFactoryImpl.java @@ -28,12 +28,12 @@ class GroupFactoryImpl implements GroupFactory { } @Override - public Group createGroup(ClientId c, int clientVersion, byte[] descriptor) { - byte[] clientVersionBytes = new byte[INT_32_BYTES]; - ByteUtils.writeUint32(clientVersion, clientVersionBytes, 0); + public Group createGroup(ClientId c, int majorVersion, byte[] descriptor) { + byte[] majorVersionBytes = new byte[INT_32_BYTES]; + ByteUtils.writeUint32(majorVersion, majorVersionBytes, 0); byte[] hash = crypto.hash(LABEL, new byte[] {FORMAT_VERSION}, - StringUtils.toUtf8(c.getString()), clientVersionBytes, + StringUtils.toUtf8(c.getString()), majorVersionBytes, descriptor); - return new Group(new GroupId(hash), c, descriptor); + return new Group(new GroupId(hash), c, majorVersion, descriptor); } } diff --git a/bramble-core/src/main/java/org/briarproject/bramble/sync/ValidationManagerImpl.java b/bramble-core/src/main/java/org/briarproject/bramble/sync/ValidationManagerImpl.java index a9c79c33215ca42702a6fed86fbff31429adc527..b8438530579997fc9cad4a33bec8633785dfc502 100644 --- a/bramble-core/src/main/java/org/briarproject/bramble/sync/ValidationManagerImpl.java +++ b/bramble-core/src/main/java/org/briarproject/bramble/sync/ValidationManagerImpl.java @@ -20,6 +20,7 @@ import org.briarproject.bramble.api.sync.MessageFactory; import org.briarproject.bramble.api.sync.MessageId; import org.briarproject.bramble.api.sync.ValidationManager; import org.briarproject.bramble.api.sync.event.MessageAddedEvent; +import org.briarproject.bramble.api.versioning.ClientMajorVersion; import java.util.Collection; import java.util.LinkedList; @@ -51,8 +52,8 @@ class ValidationManagerImpl implements ValidationManager, Service, private final DatabaseComponent db; private final Executor dbExecutor, validationExecutor; private final MessageFactory messageFactory; - private final Map<ClientId, MessageValidator> validators; - private final Map<ClientId, IncomingMessageHook> hooks; + private final Map<ClientMajorVersion, MessageValidator> validators; + private final Map<ClientMajorVersion, IncomingMessageHook> hooks; private final AtomicBoolean used = new AtomicBoolean(false); @Inject @@ -81,14 +82,15 @@ class ValidationManagerImpl implements ValidationManager, Service, } @Override - public void registerMessageValidator(ClientId c, MessageValidator v) { - validators.put(c, v); + public void registerMessageValidator(ClientId c, int majorVersion, + MessageValidator v) { + validators.put(new ClientMajorVersion(c, majorVersion), v); } @Override - public void registerIncomingMessageHook(ClientId c, + public void registerIncomingMessageHook(ClientId c, int majorVersion, IncomingMessageHook hook) { - hooks.put(c, hook); + hooks.put(new ClientMajorVersion(c, majorVersion), hook); } private void validateOutstandingMessagesAsync() { @@ -199,9 +201,11 @@ class ValidationManagerImpl implements ValidationManager, Service, Message m = messageFactory.createMessage(id, raw); Group g = db.getGroup(txn, m.getGroupId()); ClientId c = g.getClientId(); + int majorVersion = g.getMajorVersion(); Metadata meta = db.getMessageMetadataForValidator(txn, id); - DeliveryResult result = deliverMessage(txn, m, c, meta); + DeliveryResult result = + deliverMessage(txn, m, c, majorVersion, meta); if (result.valid) { pending.addAll(getPendingDependents(txn, id)); if (result.share) { @@ -237,14 +241,16 @@ class ValidationManagerImpl implements ValidationManager, Service, @ValidationExecutor private void validateMessage(Message m, Group g) { - MessageValidator v = validators.get(g.getClientId()); + ClientMajorVersion cv = + new ClientMajorVersion(g.getClientId(), g.getMajorVersion()); + MessageValidator v = validators.get(cv); if (v == null) { - if (LOG.isLoggable(WARNING)) - LOG.warning("No validator for " + g.getClientId().getString()); + if (LOG.isLoggable(WARNING)) LOG.warning("No validator for " + cv); } else { try { MessageContext context = v.validateMessage(m, g); - storeMessageContextAsync(m, g.getClientId(), context); + storeMessageContextAsync(m, g.getClientId(), + g.getMajorVersion(), context); } catch (InvalidMessageException e) { if (LOG.isLoggable(INFO)) LOG.log(INFO, e.toString(), e); @@ -256,12 +262,13 @@ class ValidationManagerImpl implements ValidationManager, Service, } private void storeMessageContextAsync(Message m, ClientId c, - MessageContext result) { - dbExecutor.execute(() -> storeMessageContext(m, c, result)); + int majorVersion, MessageContext result) { + dbExecutor.execute(() -> + storeMessageContext(m, c, majorVersion, result)); } @DatabaseExecutor - private void storeMessageContext(Message m, ClientId c, + private void storeMessageContext(Message m, ClientId c, int majorVersion, MessageContext context) { try { MessageId id = m.getId(); @@ -292,7 +299,8 @@ class ValidationManagerImpl implements ValidationManager, Service, Metadata meta = context.getMetadata(); db.mergeMessageMetadata(txn, id, meta); if (allDelivered) { - DeliveryResult result = deliverMessage(txn, m, c, meta); + DeliveryResult result = + deliverMessage(txn, m, c, majorVersion, meta); if (result.valid) { pending = getPendingDependents(txn, id); if (result.share) { @@ -324,10 +332,11 @@ class ValidationManagerImpl implements ValidationManager, Service, @DatabaseExecutor private DeliveryResult deliverMessage(Transaction txn, Message m, - ClientId c, Metadata meta) throws DbException { + ClientId c, int majorVersion, Metadata meta) throws DbException { // Deliver the message to the client if it's registered a hook boolean shareMsg = false; - IncomingMessageHook hook = hooks.get(c); + ClientMajorVersion cv = new ClientMajorVersion(c, majorVersion); + IncomingMessageHook hook = hooks.get(cv); if (hook != null) { try { shareMsg = hook.incomingMessage(txn, m, meta); diff --git a/bramble-core/src/main/java/org/briarproject/bramble/versioning/ClientVersioningConstants.java b/bramble-core/src/main/java/org/briarproject/bramble/versioning/ClientVersioningConstants.java new file mode 100644 index 0000000000000000000000000000000000000000..e68ecc4688f59ece7843eb20f5a464ffcbd79aad --- /dev/null +++ b/bramble-core/src/main/java/org/briarproject/bramble/versioning/ClientVersioningConstants.java @@ -0,0 +1,10 @@ +package org.briarproject.bramble.versioning; + +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/versioning/ClientVersioningManagerImpl.java b/bramble-core/src/main/java/org/briarproject/bramble/versioning/ClientVersioningManagerImpl.java new file mode 100644 index 0000000000000000000000000000000000000000..4c73abfe3787137a4a73231d86a27f32379be9d6 --- /dev/null +++ b/bramble-core/src/main/java/org/briarproject/bramble/versioning/ClientVersioningManagerImpl.java @@ -0,0 +1,622 @@ +package org.briarproject.bramble.versioning; + +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.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 org.briarproject.bramble.api.versioning.ClientMajorVersion; +import org.briarproject.bramble.api.versioning.ClientVersioningManager; + +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.versioning.ClientVersioningConstants.GROUP_KEY_CONTACT_ID; +import static org.briarproject.bramble.versioning.ClientVersioningConstants.MSG_KEY_LOCAL; +import static org.briarproject.bramble.versioning.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 List<ClientVersion> clients = new CopyOnWriteArrayList<>(); + private final Map<ClientMajorVersion, ClientVersioningHook> 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, + MAJOR_VERSION); + } + + @Override + public void registerClient(ClientId clientId, int majorVersion, + int minorVersion, ClientVersioningHook hook) { + ClientMajorVersion cv = new ClientMajorVersion(clientId, majorVersion); + clients.add(new ClientVersion(cv, minorVersion)); + hooks.put(cv, hook); + } + + @Override + public Visibility getClientVisibility(Transaction txn, ContactId contactId, + ClientId clientId, int majorVersion) throws DbException { + try { + Contact contact = db.getContact(txn, contactId); + Group g = getContactGroup(contact); + // Contact may be in the process of being added or removed, so + // contact group may not exist + if (!db.containsGroup(txn, g.getId())) return INVISIBLE; + LatestUpdates latest = findLatestUpdates(txn, g.getId()); + if (latest.local == null) throw new DbException(); + if (latest.remote == null) return INVISIBLE; + Update localUpdate = loadUpdate(txn, latest.local.messageId); + Update remoteUpdate = loadUpdate(txn, latest.remote.messageId); + Map<ClientMajorVersion, Visibility> visibilities = + getVisibilities(localUpdate.states, remoteUpdate.states); + ClientMajorVersion cv = + new ClientMajorVersion(clientId, majorVersion); + Visibility v = visibilities.get(cv); + return v == null ? INVISIBLE : v; + } catch (FormatException e) { + throw new DbException(e); + } + } + + @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<ClientVersion> 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<ClientVersion> 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<ClientState> 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<ClientState> oldLocalStates = oldLocalUpdate.states; + long oldLocalUpdateVersion = oldLocalUpdate.updateVersion; + // Load and parse the previous remote update, if any + List<ClientState> 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<ClientState> 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<ClientMajorVersion, Visibility> before = + getVisibilities(oldLocalStates, oldRemoteStates); + Map<ClientMajorVersion, Visibility> after = + getVisibilities(newLocalStates, newRemoteStates); + // Call hooks for any visibilities that have changed + if (!before.equals(after)) { + 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<ClientVersion> 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<ClientVersion> versions) { + BdfList encoded = new BdfList(); + for (ClientVersion cv : versions) encoded.add(encodeClientVersion(cv)); + return encoded; + } + + private BdfList encodeClientVersion(ClientVersion cv) { + return BdfList.of(cv.majorVersion.getClientId().getString(), + cv.majorVersion.getMajorVersion(), cv.minorVersion); + } + + /** + * Stores the local client versions and returns true if an update needs to + * be sent to contacts. + */ + private boolean updateClientVersions(Transaction txn, + List<ClientVersion> newVersions) throws DbException { + Collection<MessageId> ids = db.getMessageIds(txn, localGroup.getId()); + if (ids.isEmpty()) { + storeClientVersions(txn, newVersions); + return true; + } + if (ids.size() != 1) throw new DbException(); + MessageId m = ids.iterator().next(); + List<ClientVersion> oldVersions = loadClientVersions(txn, m); + if (oldVersions.equals(newVersions)) return false; + db.removeMessage(txn, m); + storeClientVersions(txn, newVersions); + return true; + } + + private List<ClientVersion> 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<ClientVersion> parseClientVersions(BdfList body) + throws FormatException { + int size = body.size(); + List<ClientVersion> parsed = new ArrayList<>(size); + for (int i = 0; i < size; i++) { + BdfList cv = body.getList(i); + ClientId clientId = new ClientId(cv.getString(0)); + int majorVersion = cv.getLong(1).intValue(); + int minorVersion = cv.getLong(2).intValue(); + parsed.add(new ClientVersion(clientId, majorVersion, + minorVersion)); + } + return parsed; + } + + private void clientVersionsUpdated(Transaction txn, Contact c, + List<ClientVersion> 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<ClientState> oldLocalStates = oldLocalUpdate.states; + long oldLocalUpdateVersion = oldLocalUpdate.updateVersion; + // Load and parse the latest remote update, if any + List<ClientState> remoteStates; + if (latest.remote == null) remoteStates = emptyList(); + else remoteStates = loadUpdate(txn, latest.remote.messageId).states; + // Update the local states if necessary + List<ClientState> newLocalStates = + updateStatesFromLocalVersions(oldLocalStates, versions); + newLocalStates = updateStatesFromRemoteStates(newLocalStates, + remoteStates); + 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, g.getId(), newLocalStates, + oldLocalUpdateVersion + 1); + } + // Calculate the old and new client visibilities + Map<ClientMajorVersion, Visibility> before = + getVisibilities(oldLocalStates, remoteStates); + Map<ClientMajorVersion, Visibility> 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, + MAJOR_VERSION, c); + } + + private LatestUpdates findLatestUpdates(Transaction txn, GroupId g) + throws DbException, FormatException { + Map<MessageId, BdfDictionary> metadata = + clientHelper.getMessageMetadataAsDictionary(txn, g); + LatestUpdate local = null, remote = null; + for (Entry<MessageId, BdfDictionary> 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<ClientState> states = parseClientStates(body); + long updateVersion = parseUpdateVersion(body); + return new Update(states, updateVersion); + } + + private List<ClientState> parseClientStates(BdfList body) + throws FormatException { + // Client states, update version + BdfList states = body.getList(0); + int size = states.size(); + List<ClientState> 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, major version, minor version, active + ClientId clientId = new ClientId(clientState.getString(0)); + int majorVersion = clientState.getLong(1).intValue(); + int minorVersion = clientState.getLong(2).intValue(); + boolean active = clientState.getBoolean(3); + return new ClientState(clientId, majorVersion, minorVersion, active); + } + + private long parseUpdateVersion(BdfList body) throws FormatException { + // Client states, update version + return body.getLong(1); + } + + private List<ClientState> updateStatesFromLocalVersions( + List<ClientState> oldStates, List<ClientVersion> newVersions) { + Map<ClientMajorVersion, ClientState> oldMap = new HashMap<>(); + for (ClientState cs : oldStates) oldMap.put(cs.majorVersion, cs); + List<ClientState> newStates = new ArrayList<>(newVersions.size()); + for (ClientVersion newVersion : newVersions) { + ClientState oldState = oldMap.get(newVersion.majorVersion); + boolean active = oldState != null && oldState.active; + newStates.add(new ClientState(newVersion.majorVersion, + newVersion.minorVersion, active)); + } + return newStates; + } + + private void storeUpdate(Transaction txn, GroupId g, + List<ClientState> 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<ClientState> 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.majorVersion.getClientId().getString(), + cs.majorVersion.getMajorVersion(), cs.minorVersion, cs.active); + } + + private Map<ClientMajorVersion, Visibility> getVisibilities( + List<ClientState> localStates, List<ClientState> remoteStates) { + Map<ClientMajorVersion, ClientState> remoteMap = new HashMap<>(); + for (ClientState cs : remoteStates) remoteMap.put(cs.majorVersion, cs); + Map<ClientMajorVersion, Visibility> visibilities = new HashMap<>(); + for (ClientState local : localStates) { + ClientState remote = remoteMap.get(local.majorVersion); + if (remote == null) visibilities.put(local.majorVersion, INVISIBLE); + else if (remote.active) + visibilities.put(local.majorVersion, SHARED); + else visibilities.put(local.majorVersion, VISIBLE); + } + return visibilities; + } + + private void callVisibilityHooks(Transaction txn, Contact c, + Map<ClientMajorVersion, Visibility> before, + Map<ClientMajorVersion, Visibility> after) throws DbException { + Set<ClientMajorVersion> keys = new TreeSet<>(); + keys.addAll(before.keySet()); + keys.addAll(after.keySet()); + for (ClientMajorVersion 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, ClientMajorVersion 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<ClientVersion> versions) throws DbException { + List<ClientState> states = new ArrayList<>(versions.size()); + for (ClientVersion cv : versions) { + states.add(new ClientState(cv.majorVersion, cv.minorVersion, + 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<ClientState> updateStatesFromRemoteStates( + List<ClientState> oldLocalStates, List<ClientState> remoteStates) { + Set<ClientMajorVersion> remoteSet = new HashSet<>(); + for (ClientState cs : remoteStates) remoteSet.add(cs.majorVersion); + List<ClientState> newLocalStates = + new ArrayList<>(oldLocalStates.size()); + for (ClientState oldState : oldLocalStates) { + boolean active = remoteSet.contains(oldState.majorVersion); + newLocalStates.add(new ClientState(oldState.majorVersion, + oldState.minorVersion, active)); + } + return newLocalStates; + } + + private static class Update { + + private final List<ClientState> states; + private final long updateVersion; + + private Update(List<ClientState> 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<ClientVersion> { + + private final ClientMajorVersion majorVersion; + private final int minorVersion; + + private ClientVersion(ClientMajorVersion majorVersion, + int minorVersion) { + this.majorVersion = majorVersion; + this.minorVersion = minorVersion; + } + + private ClientVersion(ClientId clientId, int majorVersion, + int minorVersion) { + this(new ClientMajorVersion(clientId, majorVersion), minorVersion); + } + + @Override + public boolean equals(Object o) { + if (o instanceof ClientVersion) { + ClientVersion cv = (ClientVersion) o; + return majorVersion.equals(cv.majorVersion) + && minorVersion == cv.minorVersion; + } + return false; + } + + @Override + public int hashCode() { + return majorVersion.hashCode(); + } + + @Override + public int compareTo(ClientVersion cv) { + int compare = majorVersion.compareTo(cv.majorVersion); + if (compare != 0) return compare; + return minorVersion - cv.minorVersion; + } + } + + private static class ClientState { + + private final ClientMajorVersion majorVersion; + private final int minorVersion; + private final boolean active; + + private ClientState(ClientMajorVersion majorVersion, int minorVersion, + boolean active) { + this.majorVersion = majorVersion; + this.minorVersion = minorVersion; + this.active = active; + } + + private ClientState(ClientId clientId, int majorVersion, + int minorVersion, boolean active) { + this(new ClientMajorVersion(clientId, majorVersion), minorVersion, + active); + } + + @Override + public boolean equals(Object o) { + if (o instanceof ClientState) { + ClientState cs = (ClientState) o; + return majorVersion.equals(cs.majorVersion) + && minorVersion == cs.minorVersion + && active == cs.active; + } + return false; + } + + @Override + public int hashCode() { + return majorVersion.hashCode(); + } + } +} diff --git a/bramble-core/src/main/java/org/briarproject/bramble/versioning/ClientVersioningValidator.java b/bramble-core/src/main/java/org/briarproject/bramble/versioning/ClientVersioningValidator.java new file mode 100644 index 0000000000000000000000000000000000000000..e2fc20913b996e2565bb6e5d1f66862a32690800 --- /dev/null +++ b/bramble-core/src/main/java/org/briarproject/bramble/versioning/ClientVersioningValidator.java @@ -0,0 +1,61 @@ +package org.briarproject.bramble.versioning; + +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.util.ValidationUtils.checkLength; +import static org.briarproject.bramble.util.ValidationUtils.checkSize; +import static org.briarproject.bramble.versioning.ClientVersioningConstants.MSG_KEY_LOCAL; +import static org.briarproject.bramble.versioning.ClientVersioningConstants.MSG_KEY_UPDATE_VERSION; + +@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, major version, minor version, active + checkSize(clientState, 4); + String clientId = clientState.getString(0); + checkLength(clientId, 1, MAX_CLIENT_ID_LENGTH); + int majorVersion = clientState.getLong(1).intValue(); + if (majorVersion < 0) throw new FormatException(); + int minorVersion = clientState.getLong(2).intValue(); + if (minorVersion < 0) throw new FormatException(); + clientState.getBoolean(3); + } + // 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/versioning/VersioningModule.java b/bramble-core/src/main/java/org/briarproject/bramble/versioning/VersioningModule.java new file mode 100644 index 0000000000000000000000000000000000000000..f018ffd76c72cf7f085dd8dabc396119a81cda48 --- /dev/null +++ b/bramble-core/src/main/java/org/briarproject/bramble/versioning/VersioningModule.java @@ -0,0 +1,56 @@ +package org.briarproject.bramble.versioning; + +import org.briarproject.bramble.api.client.ClientHelper; +import org.briarproject.bramble.api.contact.ContactManager; +import org.briarproject.bramble.api.data.MetadataEncoder; +import org.briarproject.bramble.api.lifecycle.LifecycleManager; +import org.briarproject.bramble.api.sync.ValidationManager; +import org.briarproject.bramble.api.system.Clock; +import org.briarproject.bramble.api.versioning.ClientVersioningManager; + +import javax.inject.Inject; +import javax.inject.Singleton; + +import dagger.Module; +import dagger.Provides; + +import static org.briarproject.bramble.api.versioning.ClientVersioningManager.CLIENT_ID; +import static org.briarproject.bramble.api.versioning.ClientVersioningManager.MAJOR_VERSION; + +@Module +public class VersioningModule { + + public static class EagerSingletons { + @Inject + ClientVersioningManager clientVersioningManager; + @Inject + ClientVersioningValidator clientVersioningValidator; + } + + + @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, MAJOR_VERSION, + 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, MAJOR_VERSION, + validator); + return validator; + } +} 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 ae3e203405e8006461a7d979bc7b82ee50dd7085..c228bf67ebc20ab3ad35ce8fa7300cc2ee848bd5 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 @@ -89,6 +89,7 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase { private final Object txn = new Object(); private final ClientId clientId; + private final int majorVersion; private final GroupId groupId; private final Group group; private final Author author; @@ -106,7 +107,8 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase { public DatabaseComponentImplTest() { clientId = getClientId(); - group = getGroup(clientId); + majorVersion = 123; + group = getGroup(clientId, majorVersion); groupId = group.getId(); author = getAuthor(); localAuthor = getLocalAuthor(); @@ -175,7 +177,7 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase { oneOf(database).containsGroup(txn, groupId); will(returnValue(true)); // getGroups() - oneOf(database).getGroups(txn, clientId); + oneOf(database).getGroups(txn, clientId, majorVersion); will(returnValue(singletonList(group))); // removeGroup() oneOf(database).containsGroup(txn, groupId); @@ -215,7 +217,7 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase { db.addGroup(transaction, group); // First time - listeners called db.addGroup(transaction, group); // Second time - not called assertEquals(singletonList(group), - db.getGroups(transaction, clientId)); + db.getGroups(transaction, clientId, majorVersion)); db.removeGroup(transaction, group); db.removeContact(transaction, contactId); db.removeLocalAuthor(transaction, localAuthor.getId()); 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 389f7c1c3a5e50b3152f046578b0a038022f3d60..e46a75b68460ba81166b20aabbfcddcbca1fe4b8 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 @@ -267,7 +267,7 @@ public abstract class DatabasePerformanceTest extends BrambleTestCase { String name = "getGroups(T, ClientId)"; benchmark(name, db -> { Connection txn = db.startTransaction(); - db.getGroups(txn, pickRandom(clientIds)); + db.getGroups(txn, pickRandom(clientIds), 123); db.commitTransaction(txn); }); } @@ -550,7 +550,7 @@ public abstract class DatabasePerformanceTest extends BrambleTestCase { contacts.add(db.getContact(txn, c)); contactGroups.put(c, new ArrayList<>()); for (int j = 0; j < GROUPS_PER_CONTACT; j++) { - Group g = getGroup(clientIds.get(j % CLIENTS)); + Group g = getGroup(clientIds.get(j % CLIENTS), 123); groups.add(g); messageMeta.put(g.getId(), new ArrayList<>()); contactGroups.get(c).add(g); @@ -584,7 +584,7 @@ public abstract class DatabasePerformanceTest extends BrambleTestCase { } } for (int i = 0; i < LOCAL_GROUPS; i++) { - Group g = getGroup(clientIds.get(i % CLIENTS)); + Group g = getGroup(clientIds.get(i % CLIENTS), 123); groups.add(g); messageMeta.put(g.getId(), new ArrayList<>()); groupMessages.put(g.getId(), new ArrayList<>()); 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 97268d16b3e551b28525517e9d6fb5c7a76b2f1e..d5b29dc65775931bc492247c265071478f83d1f9 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 @@ -82,6 +82,7 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase { private final File testDir = TestUtils.getTestDirectory(); private final GroupId groupId; private final ClientId clientId; + private final int majorVersion; private final Group group; private final Author author; private final LocalAuthor localAuthor; @@ -96,7 +97,8 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase { JdbcDatabaseTest() throws Exception { clientId = getClientId(); - group = getGroup(clientId); + majorVersion = 123; + group = getGroup(clientId, majorVersion); groupId = group.getId(); author = getAuthor(); localAuthor = getLocalAuthor(); @@ -1460,7 +1462,7 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase { db.addMessage(txn, message, PENDING, true, contactId); // Add a second group - Group group1 = getGroup(clientId); + Group group1 = getGroup(clientId, 123); GroupId groupId1 = group1.getId(); db.addGroup(txn, group1); @@ -1828,6 +1830,22 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase { db.close(); } + @Test + public void testGetGroups() throws Exception { + Database<Connection> db = open(false); + Connection txn = db.startTransaction(); + + assertEquals(emptyList(), db.getGroups(txn, clientId, majorVersion)); + db.addGroup(txn, group); + assertEquals(singletonList(group), + db.getGroups(txn, clientId, majorVersion)); + db.removeGroup(txn, groupId); + assertEquals(emptyList(), db.getGroups(txn, clientId, majorVersion)); + + db.commitTransaction(txn); + db.close(); + } + @Test public void testExceptionHandling() throws Exception { Database<Connection> db = open(false); diff --git a/bramble-core/src/test/java/org/briarproject/bramble/properties/TransportPropertyManagerImplTest.java b/bramble-core/src/test/java/org/briarproject/bramble/properties/TransportPropertyManagerImplTest.java index 9702c2d5111d37713fdeff3d1775355f42aa343e..90aba549dba4d350db7706b12d35b80f361ca857 100644 --- a/bramble-core/src/test/java/org/briarproject/bramble/properties/TransportPropertyManagerImplTest.java +++ b/bramble-core/src/test/java/org/briarproject/bramble/properties/TransportPropertyManagerImplTest.java @@ -19,6 +19,7 @@ import org.briarproject.bramble.api.sync.GroupId; import org.briarproject.bramble.api.sync.Message; import org.briarproject.bramble.api.sync.MessageId; import org.briarproject.bramble.api.system.Clock; +import org.briarproject.bramble.api.versioning.ClientVersioningManager; import org.briarproject.bramble.test.BrambleMockTestCase; import org.jmock.Expectations; import org.junit.Test; @@ -29,8 +30,9 @@ import java.util.LinkedHashMap; import java.util.List; import java.util.Map; +import static java.util.Collections.singletonList; import static org.briarproject.bramble.api.properties.TransportPropertyManager.CLIENT_ID; -import static org.briarproject.bramble.api.properties.TransportPropertyManager.CLIENT_VERSION; +import static org.briarproject.bramble.api.properties.TransportPropertyManager.MAJOR_VERSION; import static org.briarproject.bramble.api.sync.Group.Visibility.SHARED; import static org.briarproject.bramble.api.sync.SyncConstants.MAX_MESSAGE_BODY_LENGTH; import static org.briarproject.bramble.test.TestUtils.getAuthor; @@ -45,13 +47,15 @@ public class TransportPropertyManagerImplTest extends BrambleMockTestCase { private final DatabaseComponent db = context.mock(DatabaseComponent.class); private final ClientHelper clientHelper = context.mock(ClientHelper.class); + private final ClientVersioningManager clientVersioningManager = + context.mock(ClientVersioningManager.class); private final MetadataParser metadataParser = context.mock(MetadataParser.class); private final ContactGroupFactory contactGroupFactory = context.mock(ContactGroupFactory.class); private final Clock clock = context.mock(Clock.class); - private final Group localGroup = getGroup(CLIENT_ID); + private final Group localGroup = getGroup(CLIENT_ID, MAJOR_VERSION); private final LocalAuthor localAuthor = getLocalAuthor(); private final BdfDictionary fooPropertiesDict = BdfDictionary.of( new BdfEntry("fooKey1", "fooValue1"), @@ -77,49 +81,41 @@ public class TransportPropertyManagerImplTest extends BrambleMockTestCase { private TransportPropertyManagerImpl createInstance() { context.checking(new Expectations() {{ oneOf(contactGroupFactory).createLocalGroup(CLIENT_ID, - CLIENT_VERSION); + MAJOR_VERSION); will(returnValue(localGroup)); }}); return new TransportPropertyManagerImpl(db, clientHelper, - metadataParser, contactGroupFactory, clock); + clientVersioningManager, metadataParser, contactGroupFactory, + clock); } @Test public void testCreatesGroupsAtStartup() throws Exception { Transaction txn = new Transaction(null, false); - Contact contact1 = getContact(true); - Contact contact2 = getContact(true); - List<Contact> contacts = Arrays.asList(contact1, contact2); - Group contactGroup1 = getGroup(CLIENT_ID); - Group contactGroup2 = getGroup(CLIENT_ID); + Contact contact = getContact(true); + Group contactGroup = getGroup(CLIENT_ID, MAJOR_VERSION); context.checking(new Expectations() {{ oneOf(db).containsGroup(txn, localGroup.getId()); will(returnValue(false)); oneOf(db).addGroup(txn, localGroup); oneOf(db).getContacts(txn); - will(returnValue(contacts)); - // The first contact's group has already been set up + will(returnValue(singletonList(contact))); oneOf(contactGroupFactory).createContactGroup(CLIENT_ID, - CLIENT_VERSION, contact1); - will(returnValue(contactGroup1)); - oneOf(db).containsGroup(txn, contactGroup1.getId()); - will(returnValue(true)); - // The second contact's group hasn't been set up - oneOf(contactGroupFactory).createContactGroup(CLIENT_ID, - CLIENT_VERSION, contact2); - will(returnValue(contactGroup2)); - oneOf(db).containsGroup(txn, contactGroup2.getId()); - will(returnValue(false)); - oneOf(db).addGroup(txn, contactGroup2); - oneOf(db).setGroupVisibility(txn, contact2.getId(), - contactGroup2.getId(), SHARED); + MAJOR_VERSION, contact); + will(returnValue(contactGroup)); + oneOf(db).addGroup(txn, contactGroup); + oneOf(clientVersioningManager).getClientVisibility(txn, + contact.getId(), CLIENT_ID, MAJOR_VERSION); + will(returnValue(SHARED)); + oneOf(db).setGroupVisibility(txn, contact.getId(), + contactGroup.getId(), SHARED); }}); // Copy the latest local properties into the group expectGetLocalProperties(txn); - expectStoreMessage(txn, contactGroup2.getId(), "foo", fooPropertiesDict, + expectStoreMessage(txn, contactGroup.getId(), "foo", fooPropertiesDict, 1, true, true); - expectStoreMessage(txn, contactGroup2.getId(), "bar", barPropertiesDict, + expectStoreMessage(txn, contactGroup.getId(), "bar", barPropertiesDict, 1, true, true); TransportPropertyManagerImpl t = createInstance(); @@ -144,16 +140,17 @@ public class TransportPropertyManagerImplTest extends BrambleMockTestCase { public void testCreatesContactGroupWhenAddingContact() throws Exception { Transaction txn = new Transaction(null, false); Contact contact = getContact(true); - Group contactGroup = getGroup(CLIENT_ID); + Group contactGroup = getGroup(CLIENT_ID, MAJOR_VERSION); context.checking(new Expectations() {{ // Create the group and share it with the contact oneOf(contactGroupFactory).createContactGroup(CLIENT_ID, - CLIENT_VERSION, contact); + MAJOR_VERSION, contact); will(returnValue(contactGroup)); - oneOf(db).containsGroup(txn, contactGroup.getId()); - will(returnValue(false)); oneOf(db).addGroup(txn, contactGroup); + oneOf(clientVersioningManager).getClientVisibility(txn, + contact.getId(), CLIENT_ID, MAJOR_VERSION); + will(returnValue(SHARED)); oneOf(db).setGroupVisibility(txn, contact.getId(), contactGroup.getId(), SHARED); }}); @@ -172,11 +169,11 @@ public class TransportPropertyManagerImplTest extends BrambleMockTestCase { public void testRemovesGroupWhenRemovingContact() throws Exception { Transaction txn = new Transaction(null, false); Contact contact = getContact(true); - Group contactGroup = getGroup(CLIENT_ID); + Group contactGroup = getGroup(CLIENT_ID, MAJOR_VERSION); context.checking(new Expectations() {{ oneOf(contactGroupFactory).createContactGroup(CLIENT_ID, - CLIENT_VERSION, contact); + MAJOR_VERSION, contact); will(returnValue(contactGroup)); oneOf(db).removeGroup(txn, contactGroup); }}); @@ -307,7 +304,7 @@ public class TransportPropertyManagerImplTest extends BrambleMockTestCase { @Test public void testStoresRemotePropertiesWithVersion0() throws Exception { Contact contact = getContact(true); - Group contactGroup = getGroup(CLIENT_ID); + Group contactGroup = getGroup(CLIENT_ID, MAJOR_VERSION); Transaction txn = new Transaction(null, false); Map<TransportId, TransportProperties> properties = new LinkedHashMap<>(); @@ -318,7 +315,7 @@ public class TransportPropertyManagerImplTest extends BrambleMockTestCase { oneOf(db).getContact(txn, contact.getId()); will(returnValue(contact)); oneOf(contactGroupFactory).createContactGroup(CLIENT_ID, - CLIENT_VERSION, contact); + MAJOR_VERSION, contact); will(returnValue(contactGroup)); }}); expectStoreMessage(txn, contactGroup.getId(), "foo", fooPropertiesDict, @@ -421,8 +418,8 @@ public class TransportPropertyManagerImplTest extends BrambleMockTestCase { Contact contact3 = getContact(true); List<Contact> contacts = Arrays.asList(contact1, contact2, contact3); - Group contactGroup2 = getGroup(CLIENT_ID); - Group contactGroup3 = getGroup(CLIENT_ID); + Group contactGroup2 = getGroup(CLIENT_ID, MAJOR_VERSION); + Group contactGroup3 = getGroup(CLIENT_ID, MAJOR_VERSION); Map<MessageId, BdfDictionary> messageMetadata3 = new LinkedHashMap<>(); // A remote update for another transport should be ignored @@ -456,14 +453,14 @@ public class TransportPropertyManagerImplTest extends BrambleMockTestCase { // First contact: skipped because not active // Second contact: no updates oneOf(contactGroupFactory).createContactGroup(CLIENT_ID, - CLIENT_VERSION, contact2); + MAJOR_VERSION, contact2); will(returnValue(contactGroup2)); oneOf(clientHelper).getMessageMetadataAsDictionary(txn, contactGroup2.getId()); will(returnValue(Collections.emptyMap())); // Third contact: returns an update oneOf(contactGroupFactory).createContactGroup(CLIENT_ID, - CLIENT_VERSION, contact3); + MAJOR_VERSION, contact3); will(returnValue(contactGroup3)); oneOf(clientHelper).getMessageMetadataAsDictionary(txn, contactGroup3.getId()); @@ -524,7 +521,7 @@ public class TransportPropertyManagerImplTest extends BrambleMockTestCase { public void testMergingNewPropertiesCreatesUpdate() throws Exception { Transaction txn = new Transaction(null, false); Contact contact = getContact(true); - Group contactGroup = getGroup(CLIENT_ID); + Group contactGroup = getGroup(CLIENT_ID, MAJOR_VERSION); context.checking(new Expectations() {{ oneOf(db).startTransaction(false); @@ -538,9 +535,9 @@ public class TransportPropertyManagerImplTest extends BrambleMockTestCase { fooPropertiesDict, 1, true, false); // Store the new properties in each contact's group, version 1 oneOf(db).getContacts(txn); - will(returnValue(Collections.singletonList(contact))); + will(returnValue(singletonList(contact))); oneOf(contactGroupFactory).createContactGroup(CLIENT_ID, - CLIENT_VERSION, contact); + MAJOR_VERSION, contact); will(returnValue(contactGroup)); oneOf(clientHelper).getMessageMetadataAsDictionary(txn, contactGroup.getId()); @@ -559,7 +556,7 @@ public class TransportPropertyManagerImplTest extends BrambleMockTestCase { public void testMergingUpdatedPropertiesCreatesUpdate() throws Exception { Transaction txn = new Transaction(null, false); Contact contact = getContact(true); - Group contactGroup = getGroup(CLIENT_ID); + Group contactGroup = getGroup(CLIENT_ID, MAJOR_VERSION); BdfDictionary oldMetadata = BdfDictionary.of( new BdfEntry("transportId", "foo"), new BdfEntry("version", 1), @@ -597,9 +594,9 @@ public class TransportPropertyManagerImplTest extends BrambleMockTestCase { oneOf(db).removeMessage(txn, localGroupUpdateId); // Store the merged properties in each contact's group, version 2 oneOf(db).getContacts(txn); - will(returnValue(Collections.singletonList(contact))); + will(returnValue(singletonList(contact))); oneOf(contactGroupFactory).createContactGroup(CLIENT_ID, - CLIENT_VERSION, contact); + MAJOR_VERSION, contact); will(returnValue(contactGroup)); oneOf(clientHelper).getMessageMetadataAsDictionary(txn, contactGroup.getId()); diff --git a/bramble-core/src/test/java/org/briarproject/bramble/properties/TransportPropertyValidatorTest.java b/bramble-core/src/test/java/org/briarproject/bramble/properties/TransportPropertyValidatorTest.java index cd8424897f69431fa886d4109f62765a592058f9..717e5370d0f5dbfb8bcb2c6530695624b39d2588 100644 --- a/bramble-core/src/test/java/org/briarproject/bramble/properties/TransportPropertyValidatorTest.java +++ b/bramble-core/src/test/java/org/briarproject/bramble/properties/TransportPropertyValidatorTest.java @@ -18,7 +18,8 @@ import org.junit.Test; import java.io.IOException; import static org.briarproject.bramble.api.plugin.TransportId.MAX_TRANSPORT_ID_LENGTH; -import static org.briarproject.bramble.test.TestUtils.getClientId; +import static org.briarproject.bramble.api.properties.TransportPropertyManager.CLIENT_ID; +import static org.briarproject.bramble.api.properties.TransportPropertyManager.MAJOR_VERSION; import static org.briarproject.bramble.test.TestUtils.getGroup; import static org.briarproject.bramble.test.TestUtils.getMessage; import static org.briarproject.bramble.test.TestUtils.getTransportId; @@ -42,7 +43,7 @@ public class TransportPropertyValidatorTest extends BrambleMockTestCase { transportProperties = new TransportProperties(); transportProperties.put("foo", "bar"); - group = getGroup(getClientId()); + group = getGroup(CLIENT_ID, MAJOR_VERSION); message = getMessage(group.getId()); MetadataEncoder metadataEncoder = context.mock(MetadataEncoder.class); diff --git a/bramble-core/src/test/java/org/briarproject/bramble/sync/SyncIntegrationTest.java b/bramble-core/src/test/java/org/briarproject/bramble/sync/SyncIntegrationTest.java index 593a8ef71eeb034799dcd22d80a2e53e145ebf9a..1ceedbb20e3102ca52db0812ca866546acd44bce 100644 --- a/bramble-core/src/test/java/org/briarproject/bramble/sync/SyncIntegrationTest.java +++ b/bramble-core/src/test/java/org/briarproject/bramble/sync/SyncIntegrationTest.java @@ -81,9 +81,9 @@ public class SyncIntegrationTest extends BrambleTestCase { streamNumber = 123; // Create a group ClientId clientId = getClientId(); - int clientVersion = 1234567890; + int majorVersion = 1234567890; byte[] descriptor = new byte[MAX_GROUP_DESCRIPTOR_LENGTH]; - Group group = groupFactory.createGroup(clientId, clientVersion, + Group group = groupFactory.createGroup(clientId, majorVersion, descriptor); // Add two messages to the group long timestamp = System.currentTimeMillis(); diff --git a/bramble-core/src/test/java/org/briarproject/bramble/sync/ValidationManagerImplTest.java b/bramble-core/src/test/java/org/briarproject/bramble/sync/ValidationManagerImplTest.java index a3a23d4aef7a613b74f28f98ef72c6da1bd8ef46..e3d7520e5ea8c67c277fe6ba6d1efe6f2c49373b 100644 --- a/bramble-core/src/test/java/org/briarproject/bramble/sync/ValidationManagerImplTest.java +++ b/bramble-core/src/test/java/org/briarproject/bramble/sync/ValidationManagerImplTest.java @@ -53,10 +53,11 @@ public class ValidationManagerImplTest extends BrambleMockTestCase { private final Executor dbExecutor = new ImmediateExecutor(); private final Executor validationExecutor = new ImmediateExecutor(); private final ClientId clientId = getClientId(); + private final int majorVersion = 123; private final MessageId messageId = new MessageId(getRandomId()); private final MessageId messageId1 = new MessageId(getRandomId()); private final MessageId messageId2 = new MessageId(getRandomId()); - private final Group group = getGroup(clientId); + private final Group group = getGroup(clientId, majorVersion); private final GroupId groupId = group.getId(); private final long timestamp = System.currentTimeMillis(); private final byte[] raw = new byte[123]; @@ -85,8 +86,8 @@ public class ValidationManagerImplTest extends BrambleMockTestCase { public void setUp() { vm = new ValidationManagerImpl(db, dbExecutor, validationExecutor, messageFactory); - vm.registerMessageValidator(clientId, validator); - vm.registerIncomingMessageHook(clientId, hook); + vm.registerMessageValidator(clientId, majorVersion, validator); + vm.registerIncomingMessageHook(clientId, majorVersion, hook); } @Test diff --git a/bramble-core/src/test/java/org/briarproject/bramble/test/TestCryptoExecutorModule.java b/bramble-core/src/test/java/org/briarproject/bramble/test/TestCryptoExecutorModule.java new file mode 100644 index 0000000000000000000000000000000000000000..3fabc08d5a7086efc96bd9aca703532b57b75272 --- /dev/null +++ b/bramble-core/src/test/java/org/briarproject/bramble/test/TestCryptoExecutorModule.java @@ -0,0 +1,21 @@ +package org.briarproject.bramble.test; + +import org.briarproject.bramble.api.crypto.CryptoExecutor; + +import java.util.concurrent.Executor; + +import javax.inject.Singleton; + +import dagger.Module; +import dagger.Provides; + +@Module +public class TestCryptoExecutorModule { + + @Provides + @Singleton + @CryptoExecutor + Executor provideCryptoExecutor() { + return new ImmediateExecutor(); + } +} diff --git a/bramble-core/src/test/java/org/briarproject/bramble/test/ValidatorTestCase.java b/bramble-core/src/test/java/org/briarproject/bramble/test/ValidatorTestCase.java index 66a4a3b5630f14dd5b8f6448b320ebcd67894be8..9958573de8d1fd2565df261deda486111aaa4dde 100644 --- a/bramble-core/src/test/java/org/briarproject/bramble/test/ValidatorTestCase.java +++ b/bramble-core/src/test/java/org/briarproject/bramble/test/ValidatorTestCase.java @@ -24,7 +24,7 @@ public abstract class ValidatorTestCase extends BrambleMockTestCase { context.mock(MetadataEncoder.class); protected final Clock clock = context.mock(Clock.class); - protected final Group group = getGroup(getClientId()); + protected final Group group = getGroup(getClientId(), 123); protected final GroupId groupId = group.getId(); protected final byte[] descriptor = group.getDescriptor(); protected final Message message = getMessage(groupId); diff --git a/bramble-core/src/test/java/org/briarproject/bramble/versioning/ClientVersioningManagerImplTest.java b/bramble-core/src/test/java/org/briarproject/bramble/versioning/ClientVersioningManagerImplTest.java new file mode 100644 index 0000000000000000000000000000000000000000..7494873d9c724fea0a516c96d358bf6902ce90cf --- /dev/null +++ b/bramble-core/src/test/java/org/briarproject/bramble/versioning/ClientVersioningManagerImplTest.java @@ -0,0 +1,669 @@ +package org.briarproject.bramble.versioning; + +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.data.BdfDictionary; +import org.briarproject.bramble.api.data.BdfEntry; +import org.briarproject.bramble.api.data.BdfList; +import org.briarproject.bramble.api.db.DatabaseComponent; +import org.briarproject.bramble.api.db.Metadata; +import org.briarproject.bramble.api.db.Transaction; +import org.briarproject.bramble.api.sync.ClientId; +import org.briarproject.bramble.api.sync.Group; +import org.briarproject.bramble.api.sync.Group.Visibility; +import org.briarproject.bramble.api.sync.Message; +import org.briarproject.bramble.api.sync.MessageId; +import org.briarproject.bramble.api.system.Clock; +import org.briarproject.bramble.api.versioning.ClientVersioningManager.ClientVersioningHook; +import org.briarproject.bramble.test.BrambleMockTestCase; +import org.jmock.Expectations; +import org.junit.Test; + +import java.util.HashMap; +import java.util.Map; + +import static java.util.Collections.emptyList; +import static java.util.Collections.singletonList; +import static java.util.Collections.singletonMap; +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.api.versioning.ClientVersioningManager.CLIENT_ID; +import static org.briarproject.bramble.api.versioning.ClientVersioningManager.MAJOR_VERSION; +import static org.briarproject.bramble.test.TestUtils.getAuthor; +import static org.briarproject.bramble.test.TestUtils.getClientId; +import static org.briarproject.bramble.test.TestUtils.getGroup; +import static org.briarproject.bramble.test.TestUtils.getLocalAuthor; +import static org.briarproject.bramble.test.TestUtils.getMessage; +import static org.briarproject.bramble.test.TestUtils.getRandomId; +import static org.briarproject.bramble.versioning.ClientVersioningConstants.GROUP_KEY_CONTACT_ID; +import static org.briarproject.bramble.versioning.ClientVersioningConstants.MSG_KEY_LOCAL; +import static org.briarproject.bramble.versioning.ClientVersioningConstants.MSG_KEY_UPDATE_VERSION; +import static org.junit.Assert.assertFalse; + +public class ClientVersioningManagerImplTest extends BrambleMockTestCase { + + private final DatabaseComponent db = context.mock(DatabaseComponent.class); + private final ClientHelper clientHelper = context.mock(ClientHelper.class); + private final ContactGroupFactory contactGroupFactory = + context.mock(ContactGroupFactory.class); + private final Clock clock = context.mock(Clock.class); + private final ClientVersioningHook hook = + context.mock(ClientVersioningHook.class); + + private final Group localGroup = getGroup(CLIENT_ID, MAJOR_VERSION); + private final Group contactGroup = getGroup(CLIENT_ID, MAJOR_VERSION); + private final Contact contact = new Contact(new ContactId(123), + getAuthor(), getLocalAuthor().getId(), true, true); + private final ClientId clientId = getClientId(); + private final long now = System.currentTimeMillis(); + private final Transaction txn = new Transaction(null, false); + + private ClientVersioningManagerImpl createInstance() throws Exception { + context.checking(new Expectations() {{ + oneOf(contactGroupFactory).createLocalGroup(CLIENT_ID, + MAJOR_VERSION); + will(returnValue(localGroup)); + }}); + return new ClientVersioningManagerImpl(db, clientHelper, + contactGroupFactory, clock); + } + + @Test + public void testCreatesGroupsAtStartup() throws Exception { + context.checking(new Expectations() {{ + oneOf(db).containsGroup(txn, localGroup.getId()); + will(returnValue(false)); + oneOf(db).addGroup(txn, localGroup); + oneOf(db).getContacts(txn); + will(returnValue(singletonList(contact))); + }}); + expectAddingContact(); + + ClientVersioningManagerImpl c = createInstance(); + c.createLocalState(txn); + } + + @Test + public void testDoesNotCreateGroupsAtStartupIfAlreadyCreated() + throws Exception { + context.checking(new Expectations() {{ + oneOf(db).containsGroup(txn, localGroup.getId()); + will(returnValue(true)); + }}); + + ClientVersioningManagerImpl c = createInstance(); + c.createLocalState(txn); + } + + @Test + public void testCreatesContactGroupWhenAddingContact() throws Exception { + expectAddingContact(); + + ClientVersioningManagerImpl c = createInstance(); + c.addingContact(txn, contact); + } + + private void expectAddingContact() throws Exception { + BdfDictionary groupMeta = BdfDictionary.of( + new BdfEntry(GROUP_KEY_CONTACT_ID, contact.getId().getInt())); + long now = System.currentTimeMillis(); + BdfList localUpdateBody = BdfList.of(new BdfList(), 1L); + Message localUpdate = getMessage(contactGroup.getId()); + BdfDictionary localUpdateMeta = BdfDictionary.of( + new BdfEntry(MSG_KEY_UPDATE_VERSION, 1L), + new BdfEntry(MSG_KEY_LOCAL, true)); + + context.checking(new Expectations() {{ + oneOf(contactGroupFactory).createContactGroup(CLIENT_ID, + MAJOR_VERSION, contact); + will(returnValue(contactGroup)); + oneOf(db).addGroup(txn, contactGroup); + oneOf(db).setGroupVisibility(txn, contact.getId(), + contactGroup.getId(), SHARED); + oneOf(clientHelper).mergeGroupMetadata(txn, contactGroup.getId(), + groupMeta); + oneOf(clock).currentTimeMillis(); + will(returnValue(now)); + oneOf(clientHelper).createMessage(contactGroup.getId(), now, + localUpdateBody); + will(returnValue(localUpdate)); + oneOf(clientHelper).addLocalMessage(txn, localUpdate, + localUpdateMeta, true); + }}); + } + + @Test + public void testRemovesGroupWhenRemovingContact() throws Exception { + context.checking(new Expectations() {{ + oneOf(contactGroupFactory).createContactGroup(CLIENT_ID, + MAJOR_VERSION, contact); + will(returnValue(contactGroup)); + oneOf(db).removeGroup(txn, contactGroup); + }}); + + ClientVersioningManagerImpl c = createInstance(); + c.removingContact(txn, contact); + } + + @Test + public void testStoresClientVersionsAtFirstStartup() throws Exception { + BdfList localVersionsBody = + BdfList.of(BdfList.of(clientId.getString(), 123, 234)); + Message localVersions = getMessage(localGroup.getId()); + MessageId localUpdateId = new MessageId(getRandomId()); + BdfDictionary localUpdateMeta = BdfDictionary.of( + new BdfEntry(MSG_KEY_UPDATE_VERSION, 1L), + new BdfEntry(MSG_KEY_LOCAL, true)); + BdfList localUpdateBody = BdfList.of(BdfList.of( + BdfList.of(clientId.getString(), 123, 234, false)), 1L); + + context.checking(new Expectations() {{ + oneOf(db).startTransaction(false); + will(returnValue(txn)); + // No client versions have been stored yet + oneOf(db).getMessageIds(txn, localGroup.getId()); + will(returnValue(emptyList())); + // Store the client versions + oneOf(clock).currentTimeMillis(); + will(returnValue(now)); + oneOf(clientHelper).createMessage(localGroup.getId(), now, + localVersionsBody); + will(returnValue(localVersions)); + oneOf(db).addLocalMessage(txn, localVersions, new Metadata(), + false); + // Inform contacts that client versions have changed + oneOf(db).getContacts(txn); + will(returnValue(singletonList(contact))); + oneOf(contactGroupFactory).createContactGroup(CLIENT_ID, + MAJOR_VERSION, contact); + will(returnValue(contactGroup)); + // Find the latest local and remote updates (no remote update) + oneOf(clientHelper).getMessageMetadataAsDictionary(txn, + contactGroup.getId()); + will(returnValue(singletonMap(localUpdateId, localUpdateMeta))); + // Load the latest local update + oneOf(clientHelper).getMessageAsList(txn, localUpdateId); + will(returnValue(localUpdateBody)); + // Latest local update is up-to-date, no visibilities have changed + oneOf(db).commitTransaction(txn); + oneOf(db).endTransaction(txn); + }}); + + ClientVersioningManagerImpl c = createInstance(); + c.registerClient(clientId, 123, 234, hook); + c.startService(); + } + + @Test + public void testComparesClientVersionsAtSubsequentStartup() + throws Exception { + MessageId localVersionsId = new MessageId(getRandomId()); + BdfList localVersionsBody = + BdfList.of(BdfList.of(clientId.getString(), 123, 234)); + + context.checking(new Expectations() {{ + oneOf(db).startTransaction(false); + will(returnValue(txn)); + // Load the old client versions + oneOf(db).getMessageIds(txn, localGroup.getId()); + will(returnValue(singletonList(localVersionsId))); + oneOf(clientHelper).getMessageAsList(txn, localVersionsId); + will(returnValue(localVersionsBody)); + // Client versions are up-to-date + oneOf(db).commitTransaction(txn); + oneOf(db).endTransaction(txn); + }}); + + ClientVersioningManagerImpl c = createInstance(); + c.registerClient(clientId, 123, 234, hook); + c.startService(); + } + + @Test + public void testStoresClientVersionsAtSubsequentStartupIfChanged() + throws Exception { + // The client had minor version 234 in the old client versions + BdfList oldLocalVersionsBody = + BdfList.of(BdfList.of(clientId.getString(), 123, 234)); + // The client has minor version 345 in the new client versions + BdfList newLocalVersionsBody = + BdfList.of(BdfList.of(clientId.getString(), 123, 345)); + // The client had minor version 234 in the old local update + BdfList oldLocalUpdateBody = BdfList.of(BdfList.of( + BdfList.of(clientId.getString(), 123, 234, false)), 1L); + // The client has minor version 345 in the new local update + BdfList newLocalUpdateBody = BdfList.of(BdfList.of( + BdfList.of(clientId.getString(), 123, 345, false)), 2L); + + MessageId oldLocalVersionsId = new MessageId(getRandomId()); + Message newLocalVersions = getMessage(localGroup.getId()); + MessageId oldLocalUpdateId = new MessageId(getRandomId()); + BdfDictionary oldLocalUpdateMeta = BdfDictionary.of( + new BdfEntry(MSG_KEY_UPDATE_VERSION, 1L), + new BdfEntry(MSG_KEY_LOCAL, true)); + Message newLocalUpdate = getMessage(contactGroup.getId()); + BdfDictionary newLocalUpdateMeta = BdfDictionary.of( + new BdfEntry(MSG_KEY_UPDATE_VERSION, 2L), + new BdfEntry(MSG_KEY_LOCAL, true)); + + context.checking(new Expectations() {{ + oneOf(db).startTransaction(false); + will(returnValue(txn)); + // Load the old client versions + oneOf(db).getMessageIds(txn, localGroup.getId()); + will(returnValue(singletonList(oldLocalVersionsId))); + oneOf(clientHelper).getMessageAsList(txn, oldLocalVersionsId); + will(returnValue(oldLocalVersionsBody)); + // Delete the old client versions + oneOf(db).removeMessage(txn, oldLocalVersionsId); + // Store the new client versions + oneOf(clock).currentTimeMillis(); + will(returnValue(now)); + oneOf(clientHelper).createMessage(localGroup.getId(), now, + newLocalVersionsBody); + will(returnValue(newLocalVersions)); + oneOf(db).addLocalMessage(txn, newLocalVersions, new Metadata(), + false); + // Inform contacts that client versions have changed + oneOf(db).getContacts(txn); + will(returnValue(singletonList(contact))); + oneOf(contactGroupFactory).createContactGroup(CLIENT_ID, + MAJOR_VERSION, contact); + will(returnValue(contactGroup)); + // Find the latest local and remote updates (no remote update) + oneOf(clientHelper).getMessageMetadataAsDictionary(txn, + contactGroup.getId()); + will(returnValue(singletonMap(oldLocalUpdateId, + oldLocalUpdateMeta))); + // Load the latest local update + oneOf(clientHelper).getMessageAsList(txn, oldLocalUpdateId); + will(returnValue(oldLocalUpdateBody)); + // Delete the latest local update + oneOf(db).deleteMessage(txn, oldLocalUpdateId); + oneOf(db).deleteMessageMetadata(txn, oldLocalUpdateId); + // Store the new local update + oneOf(clock).currentTimeMillis(); + will(returnValue(now)); + oneOf(clientHelper).createMessage(contactGroup.getId(), now, + newLocalUpdateBody); + will(returnValue(newLocalUpdate)); + oneOf(clientHelper).addLocalMessage(txn, newLocalUpdate, + newLocalUpdateMeta, true); + // No visibilities have changed + oneOf(db).commitTransaction(txn); + oneOf(db).endTransaction(txn); + }}); + + ClientVersioningManagerImpl c = createInstance(); + c.registerClient(clientId, 123, 345, hook); + c.startService(); + } + + @Test + public void testActivatesNewClientAtStartupIfAlreadyAdvertisedByContact() + throws Exception { + testActivatesNewClientAtStartup(false, VISIBLE); + } + + @Test + public void testActivatesNewClientAtStartupIfAlreadyActivatedByContact() + throws Exception { + testActivatesNewClientAtStartup(true, SHARED); + } + + private void testActivatesNewClientAtStartup(boolean remoteActive, + Visibility visibility) throws Exception { + // The client was missing from the old client versions + BdfList oldLocalVersionsBody = new BdfList(); + // The client is included in the new client versions + BdfList newLocalVersionsBody = + BdfList.of(BdfList.of(clientId.getString(), 123, 234)); + // The client was missing from the old local update + BdfList oldLocalUpdateBody = BdfList.of(new BdfList(), 1L); + // The client was included in the old remote update + BdfList oldRemoteUpdateBody = BdfList.of(BdfList.of( + BdfList.of(clientId.getString(), 123, 345, remoteActive)), 1L); + // The client is active in the new local update + BdfList newLocalUpdateBody = BdfList.of(BdfList.of( + BdfList.of(clientId.getString(), 123, 234, true)), 2L); + + MessageId oldLocalVersionsId = new MessageId(getRandomId()); + Message newLocalVersions = getMessage(localGroup.getId()); + MessageId oldLocalUpdateId = new MessageId(getRandomId()); + BdfDictionary oldLocalUpdateMeta = BdfDictionary.of( + new BdfEntry(MSG_KEY_UPDATE_VERSION, 1L), + new BdfEntry(MSG_KEY_LOCAL, true)); + MessageId oldRemoteUpdateId = new MessageId(getRandomId()); + BdfDictionary oldRemoteUpdateMeta = BdfDictionary.of( + new BdfEntry(MSG_KEY_UPDATE_VERSION, 1L), + new BdfEntry(MSG_KEY_LOCAL, false)); + Map<MessageId, BdfDictionary> messageMetadata = new HashMap<>(); + messageMetadata.put(oldLocalUpdateId, oldLocalUpdateMeta); + messageMetadata.put(oldRemoteUpdateId, oldRemoteUpdateMeta); + Message newLocalUpdate = getMessage(localGroup.getId()); + BdfDictionary newLocalUpdateMeta = BdfDictionary.of( + new BdfEntry(MSG_KEY_UPDATE_VERSION, 2L), + new BdfEntry(MSG_KEY_LOCAL, true)); + + context.checking(new Expectations() {{ + oneOf(db).startTransaction(false); + will(returnValue(txn)); + // Load the old client versions + oneOf(db).getMessageIds(txn, localGroup.getId()); + will(returnValue(singletonList(oldLocalVersionsId))); + oneOf(clientHelper).getMessageAsList(txn, oldLocalVersionsId); + will(returnValue(oldLocalVersionsBody)); + // Delete the old client versions + oneOf(db).removeMessage(txn, oldLocalVersionsId); + // Store the new client versions + oneOf(clock).currentTimeMillis(); + will(returnValue(now)); + oneOf(clientHelper).createMessage(localGroup.getId(), now, + newLocalVersionsBody); + will(returnValue(newLocalVersions)); + oneOf(db).addLocalMessage(txn, newLocalVersions, new Metadata(), + false); + // Inform contacts that client versions have changed + oneOf(db).getContacts(txn); + will(returnValue(singletonList(contact))); + oneOf(contactGroupFactory).createContactGroup(CLIENT_ID, + MAJOR_VERSION, contact); + will(returnValue(contactGroup)); + // Find the latest local and remote updates + oneOf(clientHelper).getMessageMetadataAsDictionary(txn, + contactGroup.getId()); + will(returnValue(messageMetadata)); + // Load the latest local update + oneOf(clientHelper).getMessageAsList(txn, oldLocalUpdateId); + will(returnValue(oldLocalUpdateBody)); + // Load the latest remote update + oneOf(clientHelper).getMessageAsList(txn, oldRemoteUpdateId); + will(returnValue(oldRemoteUpdateBody)); + // Delete the latest local update + oneOf(db).deleteMessage(txn, oldLocalUpdateId); + oneOf(db).deleteMessageMetadata(txn, oldLocalUpdateId); + // Store the new local update + oneOf(clock).currentTimeMillis(); + will(returnValue(now)); + oneOf(clientHelper).createMessage(contactGroup.getId(), now, + newLocalUpdateBody); + will(returnValue(newLocalUpdate)); + oneOf(clientHelper).addLocalMessage(txn, newLocalUpdate, + newLocalUpdateMeta, true); + // The client's visibility has changed + oneOf(hook).onClientVisibilityChanging(txn, contact, visibility); + oneOf(db).commitTransaction(txn); + oneOf(db).endTransaction(txn); + }}); + + ClientVersioningManagerImpl c = createInstance(); + c.registerClient(clientId, 123, 234, hook); + c.startService(); + } + + @Test + public void testDeletesObsoleteRemoteUpdate() throws Exception { + Message newRemoteUpdate = getMessage(contactGroup.getId()); + BdfList newRemoteUpdateBody = BdfList.of(new BdfList(), 1L); + MessageId oldLocalUpdateId = new MessageId(getRandomId()); + BdfDictionary oldLocalUpdateMeta = BdfDictionary.of( + new BdfEntry(MSG_KEY_UPDATE_VERSION, 1L), + new BdfEntry(MSG_KEY_LOCAL, true)); + MessageId oldRemoteUpdateId = new MessageId(getRandomId()); + BdfDictionary oldRemoteUpdateMeta = BdfDictionary.of( + new BdfEntry(MSG_KEY_UPDATE_VERSION, 2L), + new BdfEntry(MSG_KEY_LOCAL, false)); + Map<MessageId, BdfDictionary> messageMetadata = new HashMap<>(); + messageMetadata.put(oldLocalUpdateId, oldLocalUpdateMeta); + messageMetadata.put(oldRemoteUpdateId, oldRemoteUpdateMeta); + + context.checking(new Expectations() {{ + oneOf(clientHelper).toList(newRemoteUpdate); + will(returnValue(newRemoteUpdateBody)); + // Find the latest local and remote updates + oneOf(clientHelper).getMessageMetadataAsDictionary(txn, + contactGroup.getId()); + will(returnValue(messageMetadata)); + // Delete the new remote update, which is obsolete + oneOf(db).deleteMessage(txn, newRemoteUpdate.getId()); + oneOf(db).deleteMessageMetadata(txn, newRemoteUpdate.getId()); + }}); + + ClientVersioningManagerImpl c = createInstance(); + c.registerClient(clientId, 123, 234, hook); + assertFalse(c.incomingMessage(txn, newRemoteUpdate, new Metadata())); + } + + @Test + public void testDeletesPreviousRemoteUpdate() throws Exception { + Message newRemoteUpdate = getMessage(contactGroup.getId()); + BdfList newRemoteUpdateBody = BdfList.of(new BdfList(), 2L); + MessageId oldLocalUpdateId = new MessageId(getRandomId()); + BdfDictionary oldLocalUpdateMeta = BdfDictionary.of( + new BdfEntry(MSG_KEY_UPDATE_VERSION, 1L), + new BdfEntry(MSG_KEY_LOCAL, true)); + MessageId oldRemoteUpdateId = new MessageId(getRandomId()); + BdfDictionary oldRemoteUpdateMeta = BdfDictionary.of( + new BdfEntry(MSG_KEY_UPDATE_VERSION, 1L), + new BdfEntry(MSG_KEY_LOCAL, false)); + Map<MessageId, BdfDictionary> messageMetadata = new HashMap<>(); + messageMetadata.put(oldLocalUpdateId, oldLocalUpdateMeta); + messageMetadata.put(oldRemoteUpdateId, oldRemoteUpdateMeta); + BdfList oldLocalUpdateBody = BdfList.of(new BdfList(), 1L); + BdfList oldRemoteUpdateBody = BdfList.of(new BdfList(), 1L); + + context.checking(new Expectations() {{ + oneOf(clientHelper).toList(newRemoteUpdate); + will(returnValue(newRemoteUpdateBody)); + // Find the latest local and remote updates + oneOf(clientHelper).getMessageMetadataAsDictionary(txn, + contactGroup.getId()); + will(returnValue(messageMetadata)); + // Load the latest local update + oneOf(clientHelper).getMessageAsList(txn, oldLocalUpdateId); + will(returnValue(oldLocalUpdateBody)); + // Load the latest remote update + oneOf(clientHelper).getMessageAsList(txn, oldRemoteUpdateId); + will(returnValue(oldRemoteUpdateBody)); + // Delete the old remote update + oneOf(db).deleteMessage(txn, oldRemoteUpdateId); + oneOf(db).deleteMessageMetadata(txn, oldRemoteUpdateId); + // No states or visibilities have changed + }}); + + ClientVersioningManagerImpl c = createInstance(); + c.registerClient(clientId, 123, 234, hook); + assertFalse(c.incomingMessage(txn, newRemoteUpdate, new Metadata())); + } + + @Test + public void testAcceptsFirstRemoteUpdate() throws Exception { + Message newRemoteUpdate = getMessage(contactGroup.getId()); + BdfList newRemoteUpdateBody = BdfList.of(new BdfList(), 1L); + MessageId oldLocalUpdateId = new MessageId(getRandomId()); + BdfDictionary oldLocalUpdateMeta = BdfDictionary.of( + new BdfEntry(MSG_KEY_UPDATE_VERSION, 1L), + new BdfEntry(MSG_KEY_LOCAL, true)); + BdfList oldLocalUpdateBody = BdfList.of(new BdfList(), 1L); + + context.checking(new Expectations() {{ + oneOf(clientHelper).toList(newRemoteUpdate); + will(returnValue(newRemoteUpdateBody)); + // Find the latest local and remote updates (no remote update) + oneOf(clientHelper).getMessageMetadataAsDictionary(txn, + contactGroup.getId()); + will(returnValue(singletonMap(oldLocalUpdateId, + oldLocalUpdateMeta))); + // Load the latest local update + oneOf(clientHelper).getMessageAsList(txn, oldLocalUpdateId); + will(returnValue(oldLocalUpdateBody)); + // No states or visibilities have changed + }}); + + ClientVersioningManagerImpl c = createInstance(); + c.registerClient(clientId, 123, 234, hook); + assertFalse(c.incomingMessage(txn, newRemoteUpdate, new Metadata())); + } + + @Test + public void testActivatesClientOnIncomingMessageWhenAdvertisedByContact() + throws Exception { + testActivatesClientOnIncomingMessage(false, VISIBLE); + } + + @Test + public void testActivatesClientOnIncomingMessageWhenActivatedByContact() + throws Exception { + testActivatesClientOnIncomingMessage(true, SHARED); + } + + private void testActivatesClientOnIncomingMessage(boolean remoteActive, + Visibility visibility) throws Exception { + // The client was missing from the old remote update + BdfList oldRemoteUpdateBody = BdfList.of(new BdfList(), 1L); + // The client was inactive in the old local update + BdfList oldLocalUpdateBody = BdfList.of(BdfList.of( + BdfList.of(clientId.getString(), 123, 234, false)), 1L); + // The client is included in the new remote update + BdfList newRemoteUpdateBody = BdfList.of(BdfList.of( + BdfList.of(clientId.getString(), 123, 234, remoteActive)), 2L); + // The client is active in the new local update + BdfList newLocalUpdateBody = BdfList.of(BdfList.of( + BdfList.of(clientId.getString(), 123, 234, true)), 2L); + + Message newRemoteUpdate = getMessage(contactGroup.getId()); + MessageId oldLocalUpdateId = new MessageId(getRandomId()); + BdfDictionary oldLocalUpdateMeta = BdfDictionary.of( + new BdfEntry(MSG_KEY_UPDATE_VERSION, 1L), + new BdfEntry(MSG_KEY_LOCAL, true)); + MessageId oldRemoteUpdateId = new MessageId(getRandomId()); + BdfDictionary oldRemoteUpdateMeta = BdfDictionary.of( + new BdfEntry(MSG_KEY_UPDATE_VERSION, 1L), + new BdfEntry(MSG_KEY_LOCAL, false)); + Map<MessageId, BdfDictionary> messageMetadata = new HashMap<>(); + messageMetadata.put(oldLocalUpdateId, oldLocalUpdateMeta); + messageMetadata.put(oldRemoteUpdateId, oldRemoteUpdateMeta); + Message newLocalUpdate = getMessage(contactGroup.getId()); + BdfDictionary newLocalUpdateMeta = BdfDictionary.of( + new BdfEntry(MSG_KEY_UPDATE_VERSION, 2L), + new BdfEntry(MSG_KEY_LOCAL, true)); + BdfDictionary groupMeta = BdfDictionary.of( + new BdfEntry(GROUP_KEY_CONTACT_ID, contact.getId().getInt())); + + context.checking(new Expectations() {{ + oneOf(clientHelper).toList(newRemoteUpdate); + will(returnValue(newRemoteUpdateBody)); + // Find the latest local and remote updates + oneOf(clientHelper).getMessageMetadataAsDictionary(txn, + contactGroup.getId()); + will(returnValue(messageMetadata)); + // Load the latest local update + oneOf(clientHelper).getMessageAsList(txn, oldLocalUpdateId); + will(returnValue(oldLocalUpdateBody)); + // Load the latest remote update + oneOf(clientHelper).getMessageAsList(txn, oldRemoteUpdateId); + will(returnValue(oldRemoteUpdateBody)); + // Delete the old remote update + oneOf(db).deleteMessage(txn, oldRemoteUpdateId); + oneOf(db).deleteMessageMetadata(txn, oldRemoteUpdateId); + // Delete the old local update + oneOf(db).deleteMessage(txn, oldLocalUpdateId); + oneOf(db).deleteMessageMetadata(txn, oldLocalUpdateId); + // Store the new local update + oneOf(clock).currentTimeMillis(); + will(returnValue(now)); + oneOf(clientHelper).createMessage(contactGroup.getId(), now, + newLocalUpdateBody); + will(returnValue(newLocalUpdate)); + oneOf(clientHelper).addLocalMessage(txn, newLocalUpdate, + newLocalUpdateMeta, true); + // The client's visibility has changed + oneOf(clientHelper).getGroupMetadataAsDictionary(txn, + contactGroup.getId()); + will(returnValue(groupMeta)); + oneOf(db).getContact(txn, contact.getId()); + will(returnValue(contact)); + oneOf(hook).onClientVisibilityChanging(txn, contact, visibility); + }}); + + ClientVersioningManagerImpl c = createInstance(); + c.registerClient(clientId, 123, 234, hook); + assertFalse(c.incomingMessage(txn, newRemoteUpdate, new Metadata())); + } + + @Test + public void testDeactivatesClientOnIncomingMessage() throws Exception { + // The client was active in the old local and remote updates + BdfList oldLocalUpdateBody = BdfList.of(BdfList.of( + BdfList.of(clientId.getString(), 123, 234, true)), 1L); + BdfList oldRemoteUpdateBody = BdfList.of(BdfList.of( + BdfList.of(clientId.getString(), 123, 234, true)), 1L); + // The client is missing from the new remote update + BdfList newRemoteUpdateBody = BdfList.of(new BdfList(), 2L); + // The client is inactive in the new local update + BdfList newLocalUpdateBody = BdfList.of(BdfList.of( + BdfList.of(clientId.getString(), 123, 234, false)), 2L); + + Message newRemoteUpdate = getMessage(contactGroup.getId()); + MessageId oldLocalUpdateId = new MessageId(getRandomId()); + BdfDictionary oldLocalUpdateMeta = BdfDictionary.of( + new BdfEntry(MSG_KEY_UPDATE_VERSION, 1L), + new BdfEntry(MSG_KEY_LOCAL, true)); + MessageId oldRemoteUpdateId = new MessageId(getRandomId()); + BdfDictionary oldRemoteUpdateMeta = BdfDictionary.of( + new BdfEntry(MSG_KEY_UPDATE_VERSION, 1L), + new BdfEntry(MSG_KEY_LOCAL, false)); + Map<MessageId, BdfDictionary> messageMetadata = new HashMap<>(); + messageMetadata.put(oldLocalUpdateId, oldLocalUpdateMeta); + messageMetadata.put(oldRemoteUpdateId, oldRemoteUpdateMeta); + Message newLocalUpdate = getMessage(contactGroup.getId()); + BdfDictionary newLocalUpdateMeta = BdfDictionary.of( + new BdfEntry(MSG_KEY_UPDATE_VERSION, 2L), + new BdfEntry(MSG_KEY_LOCAL, true)); + BdfDictionary groupMeta = BdfDictionary.of( + new BdfEntry(GROUP_KEY_CONTACT_ID, contact.getId().getInt())); + + context.checking(new Expectations() {{ + oneOf(clientHelper).toList(newRemoteUpdate); + will(returnValue(newRemoteUpdateBody)); + // Find the latest local and remote updates + oneOf(clientHelper).getMessageMetadataAsDictionary(txn, + contactGroup.getId()); + will(returnValue(messageMetadata)); + // Load the latest local update + oneOf(clientHelper).getMessageAsList(txn, oldLocalUpdateId); + will(returnValue(oldLocalUpdateBody)); + // Load the latest remote update + oneOf(clientHelper).getMessageAsList(txn, oldRemoteUpdateId); + will(returnValue(oldRemoteUpdateBody)); + // Delete the old remote update + oneOf(db).deleteMessage(txn, oldRemoteUpdateId); + oneOf(db).deleteMessageMetadata(txn, oldRemoteUpdateId); + // Delete the old local update + oneOf(db).deleteMessage(txn, oldLocalUpdateId); + oneOf(db).deleteMessageMetadata(txn, oldLocalUpdateId); + // Store the new local update + oneOf(clock).currentTimeMillis(); + will(returnValue(now)); + oneOf(clientHelper).createMessage(contactGroup.getId(), now, + newLocalUpdateBody); + will(returnValue(newLocalUpdate)); + oneOf(clientHelper).addLocalMessage(txn, newLocalUpdate, + newLocalUpdateMeta, true); + // The client's visibility has changed + oneOf(clientHelper).getGroupMetadataAsDictionary(txn, + contactGroup.getId()); + will(returnValue(groupMeta)); + oneOf(db).getContact(txn, contact.getId()); + will(returnValue(contact)); + oneOf(hook).onClientVisibilityChanging(txn, contact, INVISIBLE); + }}); + + ClientVersioningManagerImpl c = createInstance(); + c.registerClient(clientId, 123, 234, hook); + assertFalse(c.incomingMessage(txn, newRemoteUpdate, new Metadata())); + } +} diff --git a/bramble-core/src/test/java/org/briarproject/bramble/versioning/ClientVersioningValidatorTest.java b/bramble-core/src/test/java/org/briarproject/bramble/versioning/ClientVersioningValidatorTest.java new file mode 100644 index 0000000000000000000000000000000000000000..d13be23405cf1b1e5b2e904e9d52bd64a0adee39 --- /dev/null +++ b/bramble-core/src/test/java/org/briarproject/bramble/versioning/ClientVersioningValidatorTest.java @@ -0,0 +1,274 @@ +package org.briarproject.bramble.versioning; + +import org.briarproject.bramble.api.FormatException; +import org.briarproject.bramble.api.client.BdfMessageContext; +import org.briarproject.bramble.api.client.ClientHelper; +import org.briarproject.bramble.api.data.BdfDictionary; +import org.briarproject.bramble.api.data.BdfEntry; +import org.briarproject.bramble.api.data.BdfList; +import org.briarproject.bramble.api.data.MetadataEncoder; +import org.briarproject.bramble.api.sync.ClientId; +import org.briarproject.bramble.api.sync.Group; +import org.briarproject.bramble.api.sync.Message; +import org.briarproject.bramble.api.system.Clock; +import org.briarproject.bramble.test.BrambleMockTestCase; +import org.junit.Test; + +import static java.util.Collections.emptyList; +import static org.briarproject.bramble.api.sync.ClientId.MAX_CLIENT_ID_LENGTH; +import static org.briarproject.bramble.api.versioning.ClientVersioningManager.CLIENT_ID; +import static org.briarproject.bramble.api.versioning.ClientVersioningManager.MAJOR_VERSION; +import static org.briarproject.bramble.test.TestUtils.getClientId; +import static org.briarproject.bramble.test.TestUtils.getGroup; +import static org.briarproject.bramble.test.TestUtils.getMessage; +import static org.briarproject.bramble.test.TestUtils.getRandomBytes; +import static org.briarproject.bramble.util.StringUtils.getRandomString; +import static org.briarproject.bramble.versioning.ClientVersioningConstants.MSG_KEY_LOCAL; +import static org.briarproject.bramble.versioning.ClientVersioningConstants.MSG_KEY_UPDATE_VERSION; +import static org.junit.Assert.assertEquals; + +public class ClientVersioningValidatorTest extends BrambleMockTestCase { + + private final ClientHelper clientHelper = context.mock(ClientHelper.class); + private final MetadataEncoder metadataEncoder = + context.mock(MetadataEncoder.class); + private final Clock clock = context.mock(Clock.class); + private final ClientVersioningValidator validator = + new ClientVersioningValidator(clientHelper, metadataEncoder, clock); + + private final Group group = getGroup(CLIENT_ID, MAJOR_VERSION); + private final Message message = getMessage(group.getId()); + private final ClientId clientId = getClientId(); + + @Test(expected = FormatException.class) + public void testRejectsTooShortBody() throws Exception { + BdfList body = BdfList.of(new BdfList()); + validator.validateMessage(message, group, body); + } + + @Test(expected = FormatException.class) + public void testRejectsTooLongBody() throws Exception { + BdfList body = BdfList.of(new BdfList(), 123, null); + validator.validateMessage(message, group, body); + } + + @Test(expected = FormatException.class) + public void testRejectsNullStatesList() throws Exception { + BdfList body = BdfList.of(null, 123); + validator.validateMessage(message, group, body); + } + + @Test(expected = FormatException.class) + public void testRejectsNonListStatesList() throws Exception { + BdfList body = BdfList.of("", 123); + validator.validateMessage(message, group, body); + } + + @Test + public void testAcceptsEmptyStatesList() throws Exception { + BdfList body = BdfList.of(new BdfList(), 123); + BdfMessageContext context = + validator.validateMessage(message, group, body); + assertEquals(emptyList(), context.getDependencies()); + BdfDictionary expectedMeta = BdfDictionary.of( + new BdfEntry(MSG_KEY_UPDATE_VERSION, 123L), + new BdfEntry(MSG_KEY_LOCAL, false)); + assertEquals(expectedMeta, context.getDictionary()); + } + + @Test(expected = FormatException.class) + public void testRejectsNullUpdateVersion() throws Exception { + BdfList body = BdfList.of(new BdfList(), null); + validator.validateMessage(message, group, body); + } + + @Test(expected = FormatException.class) + public void testRejectsNonLongUpdateVersion() throws Exception { + BdfList body = BdfList.of(new BdfList(), "123"); + validator.validateMessage(message, group, body); + } + + @Test(expected = FormatException.class) + public void testRejectsNegativeUpdateVersion() throws Exception { + BdfList body = BdfList.of(new BdfList(), -1); + validator.validateMessage(message, group, body); + } + + @Test + public void testAcceptsZeroUpdateVersion() throws Exception { + BdfList body = BdfList.of(new BdfList(), 0); + BdfMessageContext context = + validator.validateMessage(message, group, body); + assertEquals(emptyList(), context.getDependencies()); + BdfDictionary expectedMeta = BdfDictionary.of( + new BdfEntry(MSG_KEY_UPDATE_VERSION, 0L), + new BdfEntry(MSG_KEY_LOCAL, false)); + assertEquals(expectedMeta, context.getDictionary()); + } + + @Test(expected = FormatException.class) + public void testRejectsTooShortClientState() throws Exception { + BdfList state = BdfList.of(clientId.getString(), 123, 234); + BdfList body = BdfList.of(BdfList.of(state), 345); + validator.validateMessage(message, group, body); + } + + @Test(expected = FormatException.class) + public void testRejectsTooLongClientState() throws Exception { + BdfList state = BdfList.of(clientId.getString(), 123, 234, true, null); + BdfList body = BdfList.of(BdfList.of(state), 345); + validator.validateMessage(message, group, body); + } + + @Test(expected = FormatException.class) + public void testRejectsNullClientId() throws Exception { + BdfList state = BdfList.of(null, 123, 234, true); + BdfList body = BdfList.of(BdfList.of(state), 345); + validator.validateMessage(message, group, body); + } + + @Test(expected = FormatException.class) + public void testRejectsNonStringClientId() throws Exception { + byte[] id = getRandomBytes(MAX_CLIENT_ID_LENGTH); + BdfList state = BdfList.of(id, 123, 234, true); + BdfList body = BdfList.of(BdfList.of(state), 345); + validator.validateMessage(message, group, body); + } + + @Test(expected = FormatException.class) + public void testRejectsTooShortClientId() throws Exception { + BdfList state = BdfList.of("", 123, 234, true); + BdfList body = BdfList.of(BdfList.of(state), 345); + validator.validateMessage(message, group, body); + } + + @Test + public void testAcceptsMinLengthClientId() throws Exception { + BdfList state = BdfList.of(getRandomString(1), 123, 234, true); + BdfList body = BdfList.of(BdfList.of(state), 345); + BdfMessageContext context = + validator.validateMessage(message, group, body); + assertEquals(emptyList(), context.getDependencies()); + BdfDictionary expectedMeta = BdfDictionary.of( + new BdfEntry(MSG_KEY_UPDATE_VERSION, 345L), + new BdfEntry(MSG_KEY_LOCAL, false)); + assertEquals(expectedMeta, context.getDictionary()); + } + + @Test(expected = FormatException.class) + public void testRejectsTooLongClientId() throws Exception { + String id = getRandomString(MAX_CLIENT_ID_LENGTH + 1); + BdfList state = BdfList.of(id, 123, 234, true); + BdfList body = BdfList.of(BdfList.of(state), 345); + validator.validateMessage(message, group, body); + } + + @Test + public void testAcceptsMaxLengthClientId() throws Exception { + String id = getRandomString(MAX_CLIENT_ID_LENGTH); + BdfList state = BdfList.of(id, 123, 234, true); + BdfList body = BdfList.of(BdfList.of(state), 345); + BdfMessageContext context = + validator.validateMessage(message, group, body); + assertEquals(emptyList(), context.getDependencies()); + BdfDictionary expectedMeta = BdfDictionary.of( + new BdfEntry(MSG_KEY_UPDATE_VERSION, 345L), + new BdfEntry(MSG_KEY_LOCAL, false)); + assertEquals(expectedMeta, context.getDictionary()); + } + + @Test(expected = FormatException.class) + public void testRejectsNullMajorVersion() throws Exception { + BdfList state = BdfList.of(clientId.getString(), null, 234, true); + BdfList body = BdfList.of(BdfList.of(state), 345); + validator.validateMessage(message, group, body); + } + + @Test(expected = FormatException.class) + public void testRejectsNonLongMajorVersion() throws Exception { + BdfList state = BdfList.of(clientId.getString(), "123", 234, true); + BdfList body = BdfList.of(BdfList.of(state), 345); + validator.validateMessage(message, group, body); + } + + @Test(expected = FormatException.class) + public void testRejectsNegativeMajorVersion() throws Exception { + BdfList state = BdfList.of(clientId.getString(), -1, 234, true); + BdfList body = BdfList.of(BdfList.of(state), 345); + validator.validateMessage(message, group, body); + } + + @Test + public void testAcceptsZeroMajorVersion() throws Exception { + BdfList state = BdfList.of(clientId.getString(), 0, 234, true); + BdfList body = BdfList.of(BdfList.of(state), 345); + BdfMessageContext context = + validator.validateMessage(message, group, body); + assertEquals(emptyList(), context.getDependencies()); + BdfDictionary expectedMeta = BdfDictionary.of( + new BdfEntry(MSG_KEY_UPDATE_VERSION, 345L), + new BdfEntry(MSG_KEY_LOCAL, false)); + assertEquals(expectedMeta, context.getDictionary()); + } + + @Test(expected = FormatException.class) + public void testRejectsNullMinorVersion() throws Exception { + BdfList state = BdfList.of(clientId.getString(), 123, null, true); + BdfList body = BdfList.of(BdfList.of(state), 345); + validator.validateMessage(message, group, body); + } + + @Test(expected = FormatException.class) + public void testRejectsNonLongMinorVersion() throws Exception { + BdfList state = BdfList.of(clientId.getString(), 123, "234", true); + BdfList body = BdfList.of(BdfList.of(state), 345); + validator.validateMessage(message, group, body); + } + + @Test(expected = FormatException.class) + public void testRejectsNegativeMinorVersion() throws Exception { + BdfList state = BdfList.of(clientId.getString(), 123, -1, true); + BdfList body = BdfList.of(BdfList.of(state), 345); + validator.validateMessage(message, group, body); + } + + @Test + public void testAcceptsZeroMinorVersion() throws Exception { + BdfList state = BdfList.of(clientId.getString(), 123, 0, true); + BdfList body = BdfList.of(BdfList.of(state), 345); + BdfMessageContext context = + validator.validateMessage(message, group, body); + assertEquals(emptyList(), context.getDependencies()); + BdfDictionary expectedMeta = BdfDictionary.of( + new BdfEntry(MSG_KEY_UPDATE_VERSION, 345L), + new BdfEntry(MSG_KEY_LOCAL, false)); + assertEquals(expectedMeta, context.getDictionary()); + } + + @Test(expected = FormatException.class) + public void testRejectsNullActiveFlag() throws Exception { + BdfList state = BdfList.of(clientId.getString(), 123, 234, null); + BdfList body = BdfList.of(BdfList.of(state), 345); + validator.validateMessage(message, group, body); + } + + @Test(expected = FormatException.class) + public void testRejectsNonBooleanActiveFlag() throws Exception { + BdfList state = BdfList.of(clientId.getString(), 123, 234, "true"); + BdfList body = BdfList.of(BdfList.of(state), 345); + validator.validateMessage(message, group, body); + } + + @Test + public void testAcceptsNegativeActiveFlag() throws Exception { + BdfList state = BdfList.of(clientId.getString(), 123, 234, false); + BdfList body = BdfList.of(BdfList.of(state), 345); + BdfMessageContext context = + validator.validateMessage(message, group, body); + assertEquals(emptyList(), context.getDependencies()); + BdfDictionary expectedMeta = BdfDictionary.of( + new BdfEntry(MSG_KEY_UPDATE_VERSION, 345L), + new BdfEntry(MSG_KEY_LOCAL, false)); + assertEquals(expectedMeta, context.getDictionary()); + } +} diff --git a/briar-api/src/main/java/org/briarproject/briar/api/blog/BlogManager.java b/briar-api/src/main/java/org/briarproject/briar/api/blog/BlogManager.java index bbf1da0944381d3d290a682a29c7405285cd4525..5381dd4901275e88f5c718484dafc751e72dbfe5 100644 --- a/briar-api/src/main/java/org/briarproject/briar/api/blog/BlogManager.java +++ b/briar-api/src/main/java/org/briarproject/briar/api/blog/BlogManager.java @@ -22,9 +22,14 @@ public interface BlogManager { ClientId CLIENT_ID = new ClientId("org.briarproject.briar.blog"); /** - * The current version of the blog client. + * The current major version of the blog client. */ - int CLIENT_VERSION = 0; + int MAJOR_VERSION = 0; + + /** + * The current minor version of the blog client. + */ + int MINOR_VERSION = 0; /** * Adds the given {@link Blog).} diff --git a/briar-api/src/main/java/org/briarproject/briar/api/blog/BlogSharingManager.java b/briar-api/src/main/java/org/briarproject/briar/api/blog/BlogSharingManager.java index e4b68bd6b42cf4576414e4f01ace74aacc66cde8..f2f04afc54939a91c15e71b3c3d18e1ec0723f6c 100644 --- a/briar-api/src/main/java/org/briarproject/briar/api/blog/BlogSharingManager.java +++ b/briar-api/src/main/java/org/briarproject/briar/api/blog/BlogSharingManager.java @@ -11,7 +11,12 @@ public interface BlogSharingManager extends SharingManager<Blog> { ClientId CLIENT_ID = new ClientId("org.briarproject.briar.blog.sharing"); /** - * The current version of the blog sharing client. + * The current major version of the blog sharing client. */ - int CLIENT_VERSION = 0; + int MAJOR_VERSION = 0; + + /** + * The current minor version of the blog sharing client. + */ + int MINOR_VERSION = 0; } diff --git a/briar-api/src/main/java/org/briarproject/briar/api/feed/FeedManager.java b/briar-api/src/main/java/org/briarproject/briar/api/feed/FeedManager.java index 50f67963a5e2d0bdc9367e068df28d05ed39d92e..98adcea29e040f1c73d03ec8e7b6ad8994bf9e53 100644 --- a/briar-api/src/main/java/org/briarproject/briar/api/feed/FeedManager.java +++ b/briar-api/src/main/java/org/briarproject/briar/api/feed/FeedManager.java @@ -16,9 +16,9 @@ public interface FeedManager { ClientId CLIENT_ID = new ClientId("org.briarproject.briar.feed"); /** - * The current version of the RSS feed client. + * The current major version of the RSS feed client. */ - int CLIENT_VERSION = 0; + int MAJOR_VERSION = 0; /** * Adds an RSS feed as a new dedicated blog. diff --git a/briar-api/src/main/java/org/briarproject/briar/api/forum/ForumManager.java b/briar-api/src/main/java/org/briarproject/briar/api/forum/ForumManager.java index 8eb7eb1ad3504f31b38ccae8228873250574cfe8..96f44777680116348bf6eb5fe25d891376336b56 100644 --- a/briar-api/src/main/java/org/briarproject/briar/api/forum/ForumManager.java +++ b/briar-api/src/main/java/org/briarproject/briar/api/forum/ForumManager.java @@ -23,9 +23,14 @@ public interface ForumManager { ClientId CLIENT_ID = new ClientId("org.briarproject.briar.forum"); /** - * The current version of the forum client. + * The current major version of the forum client. */ - int CLIENT_VERSION = 0; + int MAJOR_VERSION = 0; + + /** + * The current minor version of the forum client. + */ + int MINOR_VERSION = 0; /** * Subscribes to a forum. diff --git a/briar-api/src/main/java/org/briarproject/briar/api/forum/ForumSharingManager.java b/briar-api/src/main/java/org/briarproject/briar/api/forum/ForumSharingManager.java index 5620e024c11f99af1886f0fe51be03ae3a58138a..29b2468d0f1c396b11c609013a97ad2a241579a0 100644 --- a/briar-api/src/main/java/org/briarproject/briar/api/forum/ForumSharingManager.java +++ b/briar-api/src/main/java/org/briarproject/briar/api/forum/ForumSharingManager.java @@ -11,7 +11,12 @@ public interface ForumSharingManager extends SharingManager<Forum> { ClientId CLIENT_ID = new ClientId("org.briarproject.briar.forum.sharing"); /** - * The current version of the forum sharing client. + * The current major version of the forum sharing client. */ - int CLIENT_VERSION = 0; + int MAJOR_VERSION = 0; + + /** + * The current minor version of the forum sharing client. + */ + int MINOR_VERSION = 0; } diff --git a/briar-api/src/main/java/org/briarproject/briar/api/introduction/IntroductionManager.java b/briar-api/src/main/java/org/briarproject/briar/api/introduction/IntroductionManager.java index 9a267c5c205304087f62c4add918d73bd1613c2c..532d633539e3e33b89a386383e9d9cc79122395f 100644 --- a/briar-api/src/main/java/org/briarproject/briar/api/introduction/IntroductionManager.java +++ b/briar-api/src/main/java/org/briarproject/briar/api/introduction/IntroductionManager.java @@ -21,15 +21,20 @@ public interface IntroductionManager extends ConversationClient { ClientId CLIENT_ID = new ClientId("org.briarproject.briar.introduction"); /** - * The current version of the introduction client. + * The current major version of the introduction client. */ - int CLIENT_VERSION = 1; + int MAJOR_VERSION = 1; /** * Returns true if both contacts can be introduced at this moment. */ boolean canIntroduce(Contact c1, Contact c2) throws DbException; + /** + * The current minor version of the introduction client. + */ + int MINOR_VERSION = 0; + /** * Sends two initial introduction messages. */ diff --git a/briar-api/src/main/java/org/briarproject/briar/api/messaging/MessagingManager.java b/briar-api/src/main/java/org/briarproject/briar/api/messaging/MessagingManager.java index 65f406a6faa8bb9274110ee54af624136982fff4..f29d2c9e451468169d15dbcfb177fe779aac3329 100644 --- a/briar-api/src/main/java/org/briarproject/briar/api/messaging/MessagingManager.java +++ b/briar-api/src/main/java/org/briarproject/briar/api/messaging/MessagingManager.java @@ -19,9 +19,14 @@ public interface MessagingManager extends ConversationClient { ClientId CLIENT_ID = new ClientId("org.briarproject.briar.messaging"); /** - * The current version of the messaging client. + * The current major version of the messaging client. */ - int CLIENT_VERSION = 0; + int MAJOR_VERSION = 0; + + /** + * The current minor version of the messaging client. + */ + int MINOR_VERSION = 0; /** * Stores a local private message. diff --git a/briar-api/src/main/java/org/briarproject/briar/api/privategroup/PrivateGroupManager.java b/briar-api/src/main/java/org/briarproject/briar/api/privategroup/PrivateGroupManager.java index 7d08ae016dfd8e6be85c03eafe7cd35dbb9f6017..de4caecfc6a15d91e0eb130efbdb474800ffbfeb 100644 --- a/briar-api/src/main/java/org/briarproject/briar/api/privategroup/PrivateGroupManager.java +++ b/briar-api/src/main/java/org/briarproject/briar/api/privategroup/PrivateGroupManager.java @@ -22,9 +22,14 @@ public interface PrivateGroupManager { ClientId CLIENT_ID = new ClientId("org.briarproject.briar.privategroup"); /** - * The current version of the private group client. + * The current major version of the private group client. */ - int CLIENT_VERSION = 0; + int MAJOR_VERSION = 0; + + /** + * The current minor version of the private group client. + */ + int MINOR_VERSION = 0; /** * Adds a new private group and joins it. diff --git a/briar-api/src/main/java/org/briarproject/briar/api/privategroup/invitation/GroupInvitationManager.java b/briar-api/src/main/java/org/briarproject/briar/api/privategroup/invitation/GroupInvitationManager.java index fc3ca339f30ba6f9c9822dcc9944c6728e3493bf..1062d0ce1127f828425532cb56fd504ea6e89380 100644 --- a/briar-api/src/main/java/org/briarproject/briar/api/privategroup/invitation/GroupInvitationManager.java +++ b/briar-api/src/main/java/org/briarproject/briar/api/privategroup/invitation/GroupInvitationManager.java @@ -26,9 +26,14 @@ public interface GroupInvitationManager extends ConversationClient { new ClientId("org.briarproject.briar.privategroup.invitation"); /** - * The current version of the private group invitation client. + * The current major version of the private group invitation client. */ - int CLIENT_VERSION = 0; + int MAJOR_VERSION = 0; + + /** + * The current minor version of the private group invitation client. + */ + int MINOR_VERSION = 0; /** * Sends an invitation to share the given private group with the given diff --git a/briar-core/src/main/java/org/briarproject/briar/blog/BlogFactoryImpl.java b/briar-core/src/main/java/org/briarproject/briar/blog/BlogFactoryImpl.java index aecc020f93d868d56bab87ddc3482aa7563aa7e8..ca7887f0d76707b9b7c3fde120bbd71d39a34938 100644 --- a/briar-core/src/main/java/org/briarproject/briar/blog/BlogFactoryImpl.java +++ b/briar-core/src/main/java/org/briarproject/briar/blog/BlogFactoryImpl.java @@ -15,7 +15,7 @@ import javax.inject.Inject; import static org.briarproject.bramble.util.ValidationUtils.checkSize; import static org.briarproject.briar.api.blog.BlogManager.CLIENT_ID; -import static org.briarproject.briar.api.blog.BlogManager.CLIENT_VERSION; +import static org.briarproject.briar.api.blog.BlogManager.MAJOR_VERSION; @Immutable @NotNullByDefault @@ -45,7 +45,7 @@ class BlogFactoryImpl implements BlogFactory { try { BdfList blog = BdfList.of(clientHelper.toList(a), rssFeed); byte[] descriptor = clientHelper.toByteArray(blog); - Group g = groupFactory.createGroup(CLIENT_ID, CLIENT_VERSION, + Group g = groupFactory.createGroup(CLIENT_ID, MAJOR_VERSION, descriptor); return new Blog(g, a, rssFeed); } catch (FormatException e) { diff --git a/briar-core/src/main/java/org/briarproject/briar/blog/BlogManagerImpl.java b/briar-core/src/main/java/org/briarproject/briar/blog/BlogManagerImpl.java index c7b119ef0c220342866992c2b5af710da0e09922..6a05b9a1542b00d463ddad795f864b97079a2d7e 100644 --- a/briar-core/src/main/java/org/briarproject/briar/blog/BlogManagerImpl.java +++ b/briar-core/src/main/java/org/briarproject/briar/blog/BlogManagerImpl.java @@ -425,7 +425,7 @@ class BlogManagerImpl extends BdfIncomingMessageHook implements BlogManager, Collection<Group> groups; Transaction txn = db.startTransaction(true); try { - groups = db.getGroups(txn, CLIENT_ID); + groups = db.getGroups(txn, CLIENT_ID, MAJOR_VERSION); for (Group g : groups) { blogs.add(blogFactory.parseBlog(g)); } diff --git a/briar-core/src/main/java/org/briarproject/briar/blog/BlogModule.java b/briar-core/src/main/java/org/briarproject/briar/blog/BlogModule.java index af39a8fcd9134af40874bae4fa19b977d2aa1689..dcc829d392c01cbd817a32a658b1dc10298f91ad 100644 --- a/briar-core/src/main/java/org/briarproject/briar/blog/BlogModule.java +++ b/briar-core/src/main/java/org/briarproject/briar/blog/BlogModule.java @@ -18,7 +18,8 @@ import javax.inject.Singleton; import dagger.Module; import dagger.Provides; -import static org.briarproject.briar.blog.BlogManagerImpl.CLIENT_ID; +import static org.briarproject.briar.api.blog.BlogManager.CLIENT_ID; +import static org.briarproject.briar.api.blog.BlogManager.MAJOR_VERSION; @Module public class BlogModule { @@ -35,10 +36,10 @@ public class BlogModule { BlogManager provideBlogManager(BlogManagerImpl blogManager, LifecycleManager lifecycleManager, ContactManager contactManager, ValidationManager validationManager) { - lifecycleManager.registerClient(blogManager); contactManager.registerContactHook(blogManager); - validationManager.registerIncomingMessageHook(CLIENT_ID, blogManager); + validationManager.registerIncomingMessageHook(CLIENT_ID, MAJOR_VERSION, + blogManager); return blogManager; } @@ -60,12 +61,11 @@ public class BlogModule { MessageFactory messageFactory, BlogFactory blogFactory, ClientHelper clientHelper, MetadataEncoder metadataEncoder, Clock clock) { - BlogPostValidator validator = new BlogPostValidator(groupFactory, messageFactory, blogFactory, clientHelper, metadataEncoder, clock); - validationManager.registerMessageValidator(CLIENT_ID, validator); - + validationManager.registerMessageValidator(CLIENT_ID, MAJOR_VERSION, + validator); return validator; } diff --git a/briar-core/src/main/java/org/briarproject/briar/blog/BlogPostValidator.java b/briar-core/src/main/java/org/briarproject/briar/blog/BlogPostValidator.java index 6dc845623967bf04289ba1f7b187bd18bf3c2132..0b53468df84f2c1efab4669b08f8c9c465531925 100644 --- a/briar-core/src/main/java/org/briarproject/briar/blog/BlogPostValidator.java +++ b/briar-core/src/main/java/org/briarproject/briar/blog/BlogPostValidator.java @@ -42,7 +42,7 @@ import static org.briarproject.briar.api.blog.BlogConstants.KEY_TYPE; import static org.briarproject.briar.api.blog.BlogConstants.MAX_BLOG_COMMENT_LENGTH; import static org.briarproject.briar.api.blog.BlogConstants.MAX_BLOG_POST_BODY_LENGTH; import static org.briarproject.briar.api.blog.BlogManager.CLIENT_ID; -import static org.briarproject.briar.api.blog.BlogManager.CLIENT_VERSION; +import static org.briarproject.briar.api.blog.BlogManager.MAJOR_VERSION; import static org.briarproject.briar.api.blog.BlogPostFactory.SIGNING_LABEL_COMMENT; import static org.briarproject.briar.api.blog.BlogPostFactory.SIGNING_LABEL_POST; import static org.briarproject.briar.api.blog.MessageType.COMMENT; @@ -195,7 +195,7 @@ class BlogPostValidator extends BdfMessageValidator { checkLength(signature, 1, MAX_SIGNATURE_LENGTH); // Get and Validate the Wrapped Message - Group wGroup = groupFactory.createGroup(CLIENT_ID, CLIENT_VERSION, + Group wGroup = groupFactory.createGroup(CLIENT_ID, MAJOR_VERSION, descriptor); Blog wBlog = blogFactory.parseBlog(wGroup); BdfList wBodyList = BdfList.of(POST.getInt(), content, signature); @@ -258,7 +258,7 @@ class BlogPostValidator extends BdfMessageValidator { MessageId parentId = new MessageId(parentIdBytes); // Get and Validate the Wrapped Comment - Group wGroup = groupFactory.createGroup(CLIENT_ID, CLIENT_VERSION, + Group wGroup = groupFactory.createGroup(CLIENT_ID, MAJOR_VERSION, descriptor); BdfList wBodyList = BdfList.of(COMMENT.getInt(), comment, pOriginalId, oldId, signature); diff --git a/briar-core/src/main/java/org/briarproject/briar/feed/FeedManagerImpl.java b/briar-core/src/main/java/org/briarproject/briar/feed/FeedManagerImpl.java index f4d94413d9634fd6861b5799a77e849682c55d99..a4d58d3614a07c8486318e876a176303836b7b37 100644 --- a/briar-core/src/main/java/org/briarproject/briar/feed/FeedManagerImpl.java +++ b/briar-core/src/main/java/org/briarproject/briar/feed/FeedManagerImpl.java @@ -496,7 +496,7 @@ class FeedManagerImpl implements FeedManager, Client, EventListener, } private Group getLocalGroup() { - return contactGroupFactory.createLocalGroup(CLIENT_ID, CLIENT_VERSION); + return contactGroupFactory.createLocalGroup(CLIENT_ID, MAJOR_VERSION); } } diff --git a/briar-core/src/main/java/org/briarproject/briar/forum/ForumFactoryImpl.java b/briar-core/src/main/java/org/briarproject/briar/forum/ForumFactoryImpl.java index 4588db4c8e185edce9365403e38879f4e24b4414..e49acc5eef62ac7809b2d9a6c1589feb0bdedadf 100644 --- a/briar-core/src/main/java/org/briarproject/briar/forum/ForumFactoryImpl.java +++ b/briar-core/src/main/java/org/briarproject/briar/forum/ForumFactoryImpl.java @@ -18,7 +18,7 @@ import javax.inject.Inject; import static org.briarproject.briar.api.forum.ForumConstants.FORUM_SALT_LENGTH; import static org.briarproject.briar.api.forum.ForumConstants.MAX_FORUM_NAME_LENGTH; import static org.briarproject.briar.api.forum.ForumManager.CLIENT_ID; -import static org.briarproject.briar.api.forum.ForumManager.CLIENT_VERSION; +import static org.briarproject.briar.api.forum.ForumManager.MAJOR_VERSION; @Immutable @NotNullByDefault @@ -52,7 +52,7 @@ class ForumFactoryImpl implements ForumFactory { try { BdfList forum = BdfList.of(name, salt); byte[] descriptor = clientHelper.toByteArray(forum); - Group g = groupFactory.createGroup(CLIENT_ID, CLIENT_VERSION, + Group g = groupFactory.createGroup(CLIENT_ID, MAJOR_VERSION, descriptor); return new Forum(g, name, salt); } catch (FormatException e) { diff --git a/briar-core/src/main/java/org/briarproject/briar/forum/ForumManagerImpl.java b/briar-core/src/main/java/org/briarproject/briar/forum/ForumManagerImpl.java index cdd2ba064bfd135673ef33014d5dbab4dfb42d3f..3a386420801b0a7645b7cb5d81d9e717bf7cae95 100644 --- a/briar-core/src/main/java/org/briarproject/briar/forum/ForumManagerImpl.java +++ b/briar-core/src/main/java/org/briarproject/briar/forum/ForumManagerImpl.java @@ -188,7 +188,7 @@ class ForumManagerImpl extends BdfIncomingMessageHook implements ForumManager { Collection<Group> groups; Transaction txn = db.startTransaction(true); try { - groups = db.getGroups(txn, CLIENT_ID); + groups = db.getGroups(txn, CLIENT_ID, MAJOR_VERSION); db.commitTransaction(txn); } finally { db.endTransaction(txn); diff --git a/briar-core/src/main/java/org/briarproject/briar/forum/ForumModule.java b/briar-core/src/main/java/org/briarproject/briar/forum/ForumModule.java index 9050dc272af580613adc29c430885636b33b60cb..fd1703232b06c5f933683a17895aef72aaa7bc12 100644 --- a/briar-core/src/main/java/org/briarproject/briar/forum/ForumModule.java +++ b/briar-core/src/main/java/org/briarproject/briar/forum/ForumModule.java @@ -15,6 +15,7 @@ import dagger.Module; import dagger.Provides; import static org.briarproject.briar.api.forum.ForumManager.CLIENT_ID; +import static org.briarproject.briar.api.forum.ForumManager.MAJOR_VERSION; @Module public class ForumModule { @@ -30,7 +31,7 @@ public class ForumModule { @Singleton ForumManager provideForumManager(ForumManagerImpl forumManager, ValidationManager validationManager) { - validationManager.registerIncomingMessageHook(CLIENT_ID, + validationManager.registerIncomingMessageHook(CLIENT_ID, MAJOR_VERSION, forumManager); return forumManager; } @@ -53,7 +54,8 @@ public class ForumModule { MetadataEncoder metadataEncoder, Clock clock) { ForumPostValidator validator = new ForumPostValidator(clientHelper, metadataEncoder, clock); - validationManager.registerMessageValidator(CLIENT_ID, validator); + validationManager.registerMessageValidator(CLIENT_ID, MAJOR_VERSION, + validator); return validator; } diff --git a/briar-core/src/main/java/org/briarproject/briar/introduction/IntroductionCryptoImpl.java b/briar-core/src/main/java/org/briarproject/briar/introduction/IntroductionCryptoImpl.java index f9d53323b08aeb90b6f81ed46018051ce6418c6e..d892c58b3cac4376c0586b0b8f6f4facb6c844c4 100644 --- a/briar-core/src/main/java/org/briarproject/briar/introduction/IntroductionCryptoImpl.java +++ b/briar-core/src/main/java/org/briarproject/briar/introduction/IntroductionCryptoImpl.java @@ -29,7 +29,7 @@ import static org.briarproject.briar.api.introduction.IntroductionConstants.LABE import static org.briarproject.briar.api.introduction.IntroductionConstants.LABEL_BOB_MAC_KEY; import static org.briarproject.briar.api.introduction.IntroductionConstants.LABEL_MASTER_KEY; import static org.briarproject.briar.api.introduction.IntroductionConstants.LABEL_SESSION_ID; -import static org.briarproject.briar.api.introduction.IntroductionManager.CLIENT_VERSION; +import static org.briarproject.briar.api.introduction.IntroductionManager.MAJOR_VERSION; import static org.briarproject.briar.introduction.IntroduceeSession.Local; @Immutable @@ -94,7 +94,7 @@ class IntroductionCryptoImpl implements IntroductionCrypto { LABEL_MASTER_KEY, remoteEphemeralPublicKey, keyPair, - new byte[] {CLIENT_VERSION}, + new byte[] {MAJOR_VERSION}, alice ? publicKey : remotePublicKey, alice ? remotePublicKey : publicKey ); diff --git a/briar-core/src/main/java/org/briarproject/briar/introduction/IntroductionManagerImpl.java b/briar-core/src/main/java/org/briarproject/briar/introduction/IntroductionManagerImpl.java index efb857a0f57699ae41d9bacd541656085fc80eb7..fd39a865fefbb6861a7d29a65c17a7cd1c116004 100644 --- a/briar-core/src/main/java/org/briarproject/briar/introduction/IntroductionManagerImpl.java +++ b/briar-core/src/main/java/org/briarproject/briar/introduction/IntroductionManagerImpl.java @@ -20,10 +20,13 @@ import org.briarproject.bramble.api.identity.LocalAuthor; import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.sync.Client; 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.Message; import org.briarproject.bramble.api.sync.MessageId; import org.briarproject.bramble.api.sync.MessageStatus; +import org.briarproject.bramble.api.versioning.ClientVersioningManager; +import org.briarproject.bramble.api.versioning.ClientVersioningManager.ClientVersioningHook; import org.briarproject.briar.api.client.MessageTracker; import org.briarproject.briar.api.client.SessionId; import org.briarproject.briar.api.introduction.IntroductionManager; @@ -44,7 +47,6 @@ import javax.annotation.Nullable; import javax.annotation.concurrent.Immutable; import javax.inject.Inject; -import static org.briarproject.bramble.api.sync.Group.Visibility.SHARED; import static org.briarproject.briar.api.introduction.Role.INTRODUCEE; import static org.briarproject.briar.api.introduction.Role.INTRODUCER; import static org.briarproject.briar.introduction.IntroducerState.START; @@ -59,8 +61,10 @@ import static org.briarproject.briar.introduction.MessageType.REQUEST; @Immutable @NotNullByDefault class IntroductionManagerImpl extends ConversationClientImpl - implements IntroductionManager, Client, ContactHook { + implements IntroductionManager, Client, ContactHook, + ClientVersioningHook { + private final ClientVersioningManager clientVersioningManager; private final ContactGroupFactory contactGroupFactory; private final ContactManager contactManager; private final MessageParser messageParser; @@ -77,6 +81,7 @@ class IntroductionManagerImpl extends ConversationClientImpl IntroductionManagerImpl( DatabaseComponent db, ClientHelper clientHelper, + ClientVersioningManager clientVersioningManager, MetadataParser metadataParser, MessageTracker messageTracker, ContactGroupFactory contactGroupFactory, @@ -89,6 +94,7 @@ class IntroductionManagerImpl extends ConversationClientImpl IntroductionCrypto crypto, IdentityManager identityManager) { super(db, clientHelper, metadataParser, messageTracker); + this.clientVersioningManager = clientVersioningManager; this.contactGroupFactory = contactGroupFactory; this.contactManager = contactManager; this.messageParser = messageParser; @@ -99,7 +105,7 @@ class IntroductionManagerImpl extends ConversationClientImpl this.crypto = crypto; this.identityManager = identityManager; this.localGroup = - contactGroupFactory.createLocalGroup(CLIENT_ID, CLIENT_VERSION); + contactGroupFactory.createLocalGroup(CLIENT_ID, MAJOR_VERSION); } @Override @@ -112,13 +118,14 @@ class IntroductionManagerImpl extends ConversationClientImpl } @Override - // TODO adapt to use upcoming ClientVersioning client public void addingContact(Transaction txn, Contact c) throws DbException { // Create a group to share with the contact Group g = getContactGroup(c); - // Store the group and share it with the contact db.addGroup(txn, g); - db.setGroupVisibility(txn, c.getId(), g.getId(), SHARED); + // Apply the client's visibility to the contact group + Visibility client = clientVersioningManager.getClientVisibility(txn, + c.getId(), CLIENT_ID, MAJOR_VERSION); + db.setGroupVisibility(txn, c.getId(), g.getId(), client); // Attach the contact ID to the group BdfDictionary meta = new BdfDictionary(); meta.put(GROUP_KEY_CONTACT_ID, c.getId().getInt()); @@ -138,10 +145,18 @@ class IntroductionManagerImpl extends ConversationClientImpl db.removeGroup(txn, getContactGroup(c)); } + @Override + public void onClientVisibilityChanging(Transaction txn, Contact c, + Visibility v) throws DbException { + // Apply the client's visibility to the contact group + Group g = getContactGroup(c); + db.setGroupVisibility(txn, c.getId(), g.getId(), v); + } + @Override public Group getContactGroup(Contact c) { return contactGroupFactory - .createContactGroup(CLIENT_ID, CLIENT_VERSION, c); + .createContactGroup(CLIENT_ID, MAJOR_VERSION, c); } @Override diff --git a/briar-core/src/main/java/org/briarproject/briar/introduction/IntroductionModule.java b/briar-core/src/main/java/org/briarproject/briar/introduction/IntroductionModule.java index 24c649c5fd6cbb0e4a1fe7ad838af2ff13ac4d20..932de34083f318ad926995870a5d6d7f3e543a42 100644 --- a/briar-core/src/main/java/org/briarproject/briar/introduction/IntroductionModule.java +++ b/briar-core/src/main/java/org/briarproject/briar/introduction/IntroductionModule.java @@ -6,6 +6,7 @@ import org.briarproject.bramble.api.data.MetadataEncoder; import org.briarproject.bramble.api.lifecycle.LifecycleManager; import org.briarproject.bramble.api.sync.ValidationManager; import org.briarproject.bramble.api.system.Clock; +import org.briarproject.bramble.api.versioning.ClientVersioningManager; import org.briarproject.briar.api.introduction.IntroductionManager; import org.briarproject.briar.api.messaging.ConversationManager; @@ -16,6 +17,8 @@ import dagger.Module; import dagger.Provides; import static org.briarproject.briar.api.introduction.IntroductionManager.CLIENT_ID; +import static org.briarproject.briar.api.introduction.IntroductionManager.MAJOR_VERSION; +import static org.briarproject.briar.api.introduction.IntroductionManager.MINOR_VERSION; @Module public class IntroductionModule { @@ -32,13 +35,11 @@ public class IntroductionModule { IntroductionValidator provideValidator(ValidationManager validationManager, MessageEncoder messageEncoder, MetadataEncoder metadataEncoder, ClientHelper clientHelper, Clock clock) { - IntroductionValidator introductionValidator = new IntroductionValidator(messageEncoder, clientHelper, metadataEncoder, clock); - validationManager.registerMessageValidator(CLIENT_ID, + validationManager.registerMessageValidator(CLIENT_ID, MAJOR_VERSION, introductionValidator); - return introductionValidator; } @@ -48,13 +49,15 @@ public class IntroductionModule { LifecycleManager lifecycleManager, ContactManager contactManager, ValidationManager validationManager, ConversationManager conversationManager, + ClientVersioningManager clientVersioningManager, IntroductionManagerImpl introductionManager) { lifecycleManager.registerClient(introductionManager); contactManager.registerContactHook(introductionManager); validationManager.registerIncomingMessageHook(CLIENT_ID, - introductionManager); + MAJOR_VERSION, introductionManager); conversationManager.registerConversationClient(introductionManager); - + clientVersioningManager.registerClient(CLIENT_ID, MAJOR_VERSION, + MINOR_VERSION, introductionManager); return introductionManager; } diff --git a/briar-core/src/main/java/org/briarproject/briar/messaging/MessagingManagerImpl.java b/briar-core/src/main/java/org/briarproject/briar/messaging/MessagingManagerImpl.java index c81766492f98ea749ca83b4d38a67f0105fc032b..555a209905a810fd57df4dd4df78691a427d170b 100644 --- a/briar-core/src/main/java/org/briarproject/briar/messaging/MessagingManagerImpl.java +++ b/briar-core/src/main/java/org/briarproject/briar/messaging/MessagingManagerImpl.java @@ -15,10 +15,13 @@ import org.briarproject.bramble.api.db.Transaction; import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.sync.Client; 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.Message; import org.briarproject.bramble.api.sync.MessageId; import org.briarproject.bramble.api.sync.MessageStatus; +import org.briarproject.bramble.api.versioning.ClientVersioningManager; +import org.briarproject.bramble.api.versioning.ClientVersioningManager.ClientVersioningHook; import org.briarproject.briar.api.client.MessageTracker; import org.briarproject.briar.api.messaging.MessagingManager; import org.briarproject.briar.api.messaging.PrivateMessage; @@ -33,21 +36,23 @@ import java.util.Map; import javax.annotation.concurrent.Immutable; import javax.inject.Inject; -import static org.briarproject.bramble.api.sync.Group.Visibility.SHARED; import static org.briarproject.briar.client.MessageTrackerConstants.MSG_KEY_READ; @Immutable @NotNullByDefault class MessagingManagerImpl extends ConversationClientImpl - implements MessagingManager, Client, ContactHook { + implements MessagingManager, Client, ContactHook, ClientVersioningHook { + private final ClientVersioningManager clientVersioningManager; private final ContactGroupFactory contactGroupFactory; @Inject MessagingManagerImpl(DatabaseComponent db, ClientHelper clientHelper, + ClientVersioningManager clientVersioningManager, MetadataParser metadataParser, MessageTracker messageTracker, ContactGroupFactory contactGroupFactory) { super(db, clientHelper, metadataParser, messageTracker); + this.clientVersioningManager = clientVersioningManager; this.contactGroupFactory = contactGroupFactory; } @@ -55,36 +60,36 @@ class MessagingManagerImpl extends ConversationClientImpl public void createLocalState(Transaction txn) throws DbException { // Create a local group to indicate that we've set this client up Group localGroup = contactGroupFactory.createLocalGroup(CLIENT_ID, - CLIENT_VERSION); + MAJOR_VERSION); if (db.containsGroup(txn, localGroup.getId())) return; db.addGroup(txn, localGroup); - // Ensure we've set things up for any pre-existing contacts + // Set things up for any pre-existing contacts for (Contact c : db.getContacts(txn)) addingContact(txn, c); } @Override public void addingContact(Transaction txn, Contact c) throws DbException { + // Create a group to share with the contact + Group g = getContactGroup(c); + db.addGroup(txn, g); + // Apply the client's visibility to the contact group + Visibility client = clientVersioningManager.getClientVisibility(txn, + c.getId(), CLIENT_ID, MAJOR_VERSION); + db.setGroupVisibility(txn, c.getId(), g.getId(), client); + // Attach the contact ID to the group + BdfDictionary d = new BdfDictionary(); + d.put("contactId", c.getId().getInt()); try { - // Create a group to share with the contact - Group g = getContactGroup(c); - // Return if we've already set things up for this contact - if (db.containsGroup(txn, g.getId())) return; - // Store the group and share it with the contact - db.addGroup(txn, g); - db.setGroupVisibility(txn, c.getId(), g.getId(), SHARED); - // Attach the contact ID to the group - BdfDictionary d = new BdfDictionary(); - d.put("contactId", c.getId().getInt()); clientHelper.mergeGroupMetadata(txn, g.getId(), d); } catch (FormatException e) { - throw new RuntimeException(e); + throw new AssertionError(e); } } @Override public Group getContactGroup(Contact c) { return contactGroupFactory.createContactGroup(CLIENT_ID, - CLIENT_VERSION, c); + MAJOR_VERSION, c); } @Override @@ -92,6 +97,14 @@ class MessagingManagerImpl extends ConversationClientImpl db.removeGroup(txn, getContactGroup(c)); } + @Override + public void onClientVisibilityChanging(Transaction txn, Contact c, + Visibility v) throws DbException { + // Apply the client's visibility to the contact group + Group g = getContactGroup(c); + db.setGroupVisibility(txn, c.getId(), g.getId(), v); + } + @Override protected boolean incomingMessage(Transaction txn, Message m, BdfList body, BdfDictionary meta) throws DbException, FormatException { diff --git a/briar-core/src/main/java/org/briarproject/briar/messaging/MessagingModule.java b/briar-core/src/main/java/org/briarproject/briar/messaging/MessagingModule.java index 492d3c4fac7e4c9ddde40f8843a5ae79054d26d7..016f1225a2f554b6d0879cd7bab05f74372f3c6c 100644 --- a/briar-core/src/main/java/org/briarproject/briar/messaging/MessagingModule.java +++ b/briar-core/src/main/java/org/briarproject/briar/messaging/MessagingModule.java @@ -6,6 +6,7 @@ import org.briarproject.bramble.api.data.MetadataEncoder; import org.briarproject.bramble.api.lifecycle.LifecycleManager; import org.briarproject.bramble.api.sync.ValidationManager; import org.briarproject.bramble.api.system.Clock; +import org.briarproject.bramble.api.versioning.ClientVersioningManager; import org.briarproject.briar.api.messaging.ConversationManager; import org.briarproject.briar.api.messaging.MessagingManager; import org.briarproject.briar.api.messaging.PrivateMessageFactory; @@ -16,7 +17,9 @@ import javax.inject.Singleton; import dagger.Module; import dagger.Provides; -import static org.briarproject.briar.messaging.MessagingManagerImpl.CLIENT_ID; +import static org.briarproject.briar.api.messaging.MessagingManager.CLIENT_ID; +import static org.briarproject.briar.api.messaging.MessagingManager.MAJOR_VERSION; +import static org.briarproject.briar.api.messaging.MessagingManager.MINOR_VERSION; @Module public class MessagingModule { @@ -43,7 +46,8 @@ public class MessagingModule { Clock clock) { PrivateMessageValidator validator = new PrivateMessageValidator( clientHelper, metadataEncoder, clock); - validationManager.registerMessageValidator(CLIENT_ID, validator); + validationManager.registerMessageValidator(CLIENT_ID, MAJOR_VERSION, + validator); return validator; } @@ -52,12 +56,15 @@ public class MessagingModule { MessagingManager getMessagingManager(LifecycleManager lifecycleManager, ContactManager contactManager, ValidationManager validationManager, ConversationManager conversationManager, + ClientVersioningManager clientVersioningManager, MessagingManagerImpl messagingManager) { lifecycleManager.registerClient(messagingManager); contactManager.registerContactHook(messagingManager); - validationManager - .registerIncomingMessageHook(CLIENT_ID, messagingManager); + validationManager.registerIncomingMessageHook(CLIENT_ID, MAJOR_VERSION, + messagingManager); conversationManager.registerConversationClient(messagingManager); + clientVersioningManager.registerClient(CLIENT_ID, MAJOR_VERSION, + MINOR_VERSION, messagingManager); return messagingManager; } diff --git a/briar-core/src/main/java/org/briarproject/briar/privategroup/PrivateGroupFactoryImpl.java b/briar-core/src/main/java/org/briarproject/briar/privategroup/PrivateGroupFactoryImpl.java index 4cff80dd94e03c0ddb64071712760fb8f683a485..864ad23aba1d802980e3b8f09b29758a3fe74608 100644 --- a/briar-core/src/main/java/org/briarproject/briar/privategroup/PrivateGroupFactoryImpl.java +++ b/briar-core/src/main/java/org/briarproject/briar/privategroup/PrivateGroupFactoryImpl.java @@ -21,7 +21,7 @@ import static org.briarproject.bramble.util.ValidationUtils.checkSize; import static org.briarproject.briar.api.privategroup.PrivateGroupConstants.GROUP_SALT_LENGTH; import static org.briarproject.briar.api.privategroup.PrivateGroupConstants.MAX_GROUP_NAME_LENGTH; import static org.briarproject.briar.api.privategroup.PrivateGroupManager.CLIENT_ID; -import static org.briarproject.briar.api.privategroup.PrivateGroupManager.CLIENT_VERSION; +import static org.briarproject.briar.api.privategroup.PrivateGroupManager.MAJOR_VERSION; @Immutable @NotNullByDefault @@ -57,7 +57,7 @@ class PrivateGroupFactoryImpl implements PrivateGroupFactory { BdfList creatorList = clientHelper.toList(creator); BdfList group = BdfList.of(creatorList, name, salt); byte[] descriptor = clientHelper.toByteArray(group); - Group g = groupFactory.createGroup(CLIENT_ID, CLIENT_VERSION, + Group g = groupFactory.createGroup(CLIENT_ID, MAJOR_VERSION, descriptor); return new PrivateGroup(g, name, creator, salt); } catch (FormatException e) { diff --git a/briar-core/src/main/java/org/briarproject/briar/privategroup/PrivateGroupManagerImpl.java b/briar-core/src/main/java/org/briarproject/briar/privategroup/PrivateGroupManagerImpl.java index 20ea827a31167b90a2dfb858c7c00a99adc1079d..531282fdf000f129f642a93126b4106b7238fdc6 100644 --- a/briar-core/src/main/java/org/briarproject/briar/privategroup/PrivateGroupManagerImpl.java +++ b/briar-core/src/main/java/org/briarproject/briar/privategroup/PrivateGroupManagerImpl.java @@ -271,7 +271,7 @@ class PrivateGroupManagerImpl extends BdfIncomingMessageHook Collection<Group> groups; Transaction txn = db.startTransaction(true); try { - groups = db.getGroups(txn, CLIENT_ID); + groups = db.getGroups(txn, CLIENT_ID, MAJOR_VERSION); db.commitTransaction(txn); } finally { db.endTransaction(txn); diff --git a/briar-core/src/main/java/org/briarproject/briar/privategroup/PrivateGroupModule.java b/briar-core/src/main/java/org/briarproject/briar/privategroup/PrivateGroupModule.java index 8b2bb3b66e69d034978db9cb0d4e85f3cbb60712..61901bc8bc101ae6d3ce8e9cac2d6d37e3e250eb 100644 --- a/briar-core/src/main/java/org/briarproject/briar/privategroup/PrivateGroupModule.java +++ b/briar-core/src/main/java/org/briarproject/briar/privategroup/PrivateGroupModule.java @@ -16,6 +16,7 @@ import dagger.Module; import dagger.Provides; import static org.briarproject.briar.api.privategroup.PrivateGroupManager.CLIENT_ID; +import static org.briarproject.briar.api.privategroup.PrivateGroupManager.MAJOR_VERSION; @Module public class PrivateGroupModule { @@ -32,7 +33,8 @@ public class PrivateGroupModule { PrivateGroupManager provideGroupManager( PrivateGroupManagerImpl groupManager, ValidationManager validationManager) { - validationManager.registerIncomingMessageHook(CLIENT_ID, groupManager); + validationManager.registerIncomingMessageHook(CLIENT_ID, MAJOR_VERSION, + groupManager); return groupManager; } @@ -58,7 +60,8 @@ public class PrivateGroupModule { GroupMessageValidator validator = new GroupMessageValidator( privateGroupFactory, clientHelper, metadataEncoder, clock, groupInvitationFactory); - validationManager.registerMessageValidator(CLIENT_ID, validator); + validationManager.registerMessageValidator(CLIENT_ID, MAJOR_VERSION, + validator); return validator; } diff --git a/briar-core/src/main/java/org/briarproject/briar/privategroup/invitation/AbstractProtocolEngine.java b/briar-core/src/main/java/org/briarproject/briar/privategroup/invitation/AbstractProtocolEngine.java index c68f7b662513be4804cfbabc8eee086ad6b6205d..d3bd73943c5ae155c905d1dc6c1e69479b9eaa39 100644 --- a/briar-core/src/main/java/org/briarproject/briar/privategroup/invitation/AbstractProtocolEngine.java +++ b/briar-core/src/main/java/org/briarproject/briar/privategroup/invitation/AbstractProtocolEngine.java @@ -16,6 +16,7 @@ import org.briarproject.bramble.api.sync.GroupId; import org.briarproject.bramble.api.sync.Message; import org.briarproject.bramble.api.sync.MessageId; import org.briarproject.bramble.api.system.Clock; +import org.briarproject.bramble.api.versioning.ClientVersioningManager; import org.briarproject.briar.api.client.MessageTracker; import org.briarproject.briar.api.privategroup.GroupMessage; import org.briarproject.briar.api.privategroup.GroupMessageFactory; @@ -28,6 +29,8 @@ import java.util.Map; import javax.annotation.Nullable; import javax.annotation.concurrent.Immutable; +import static org.briarproject.briar.api.privategroup.PrivateGroupManager.CLIENT_ID; +import static org.briarproject.briar.api.privategroup.PrivateGroupManager.MAJOR_VERSION; import static org.briarproject.briar.privategroup.invitation.GroupInvitationConstants.GROUP_KEY_CONTACT_ID; import static org.briarproject.briar.privategroup.invitation.MessageType.ABORT; import static org.briarproject.briar.privategroup.invitation.MessageType.INVITE; @@ -45,6 +48,7 @@ abstract class AbstractProtocolEngine<S extends Session> protected final PrivateGroupFactory privateGroupFactory; protected final MessageTracker messageTracker; + private final ClientVersioningManager clientVersioningManager; private final GroupMessageFactory groupMessageFactory; private final IdentityManager identityManager; private final MessageParser messageParser; @@ -52,6 +56,7 @@ abstract class AbstractProtocolEngine<S extends Session> private final Clock clock; AbstractProtocolEngine(DatabaseComponent db, ClientHelper clientHelper, + ClientVersioningManager clientVersioningManager, PrivateGroupManager privateGroupManager, PrivateGroupFactory privateGroupFactory, GroupMessageFactory groupMessageFactory, @@ -60,6 +65,7 @@ abstract class AbstractProtocolEngine<S extends Session> Clock clock) { this.db = db; this.clientHelper = clientHelper; + this.clientVersioningManager = clientVersioningManager; this.privateGroupManager = privateGroupManager; this.privateGroupFactory = privateGroupFactory; this.groupMessageFactory = groupMessageFactory; @@ -90,10 +96,14 @@ abstract class AbstractProtocolEngine<S extends Session> return expected != null && dependency.equals(expected); } - void setPrivateGroupVisibility(Transaction txn, S session, Visibility v) - throws DbException, FormatException { + void setPrivateGroupVisibility(Transaction txn, S session, + Visibility preferred) throws DbException, FormatException { + // Apply min of preferred visibility and client's visibility ContactId contactId = getContactId(txn, session.getContactGroupId()); - db.setGroupVisibility(txn, contactId, session.getPrivateGroupId(), v); + Visibility client = clientVersioningManager.getClientVisibility(txn, + contactId, CLIENT_ID, MAJOR_VERSION); + Visibility min = Visibility.min(preferred, client); + db.setGroupVisibility(txn, contactId, session.getPrivateGroupId(), min); } Message sendInviteMessage(Transaction txn, S session, diff --git a/briar-core/src/main/java/org/briarproject/briar/privategroup/invitation/CreatorProtocolEngine.java b/briar-core/src/main/java/org/briarproject/briar/privategroup/invitation/CreatorProtocolEngine.java index ec5315a6d07745b2552156a99905b306889d6c43..d043fbad260f7e00ce42c7d2172d68773b6ba54f 100644 --- a/briar-core/src/main/java/org/briarproject/briar/privategroup/invitation/CreatorProtocolEngine.java +++ b/briar-core/src/main/java/org/briarproject/briar/privategroup/invitation/CreatorProtocolEngine.java @@ -10,6 +10,7 @@ import org.briarproject.bramble.api.identity.IdentityManager; import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.sync.Message; import org.briarproject.bramble.api.system.Clock; +import org.briarproject.bramble.api.versioning.ClientVersioningManager; import org.briarproject.briar.api.client.MessageTracker; import org.briarproject.briar.api.client.ProtocolStateException; import org.briarproject.briar.api.client.SessionId; @@ -36,15 +37,16 @@ import static org.briarproject.briar.privategroup.invitation.CreatorState.START; class CreatorProtocolEngine extends AbstractProtocolEngine<CreatorSession> { CreatorProtocolEngine(DatabaseComponent db, ClientHelper clientHelper, + ClientVersioningManager clientVersioningManager, PrivateGroupManager privateGroupManager, PrivateGroupFactory privateGroupFactory, GroupMessageFactory groupMessageFactory, IdentityManager identityManager, MessageParser messageParser, MessageEncoder messageEncoder, MessageTracker messageTracker, Clock clock) { - super(db, clientHelper, privateGroupManager, privateGroupFactory, - groupMessageFactory, identityManager, messageParser, - messageEncoder, messageTracker, clock); + super(db, clientHelper, clientVersioningManager, privateGroupManager, + privateGroupFactory, groupMessageFactory, identityManager, + messageParser, messageEncoder, messageTracker, clock); } @Override diff --git a/briar-core/src/main/java/org/briarproject/briar/privategroup/invitation/CreatorState.java b/briar-core/src/main/java/org/briarproject/briar/privategroup/invitation/CreatorState.java index 4afe89c4d8319b410f1ab4be1b945446e2db30a0..6fe69a08c9227bf9e9433813aa9c354a6fa2a70b 100644 --- a/briar-core/src/main/java/org/briarproject/briar/privategroup/invitation/CreatorState.java +++ b/briar-core/src/main/java/org/briarproject/briar/privategroup/invitation/CreatorState.java @@ -2,19 +2,30 @@ package org.briarproject.briar.privategroup.invitation; import org.briarproject.bramble.api.FormatException; import org.briarproject.bramble.api.nullsafety.NotNullByDefault; +import org.briarproject.bramble.api.sync.Group.Visibility; import javax.annotation.concurrent.Immutable; +import static org.briarproject.bramble.api.sync.Group.Visibility.INVISIBLE; +import static org.briarproject.bramble.api.sync.Group.Visibility.SHARED; + @Immutable @NotNullByDefault enum CreatorState implements State { - START(0), INVITED(1), JOINED(2), LEFT(3), DISSOLVED(4), ERROR(5); + START(0, INVISIBLE), + INVITED(1, INVISIBLE), + JOINED(2, SHARED), + LEFT(3, INVISIBLE), + DISSOLVED(4, INVISIBLE), + ERROR(5, INVISIBLE); private final int value; + private final Visibility visibility; - CreatorState(int value) { + CreatorState(int value, Visibility visibility) { this.value = value; + this.visibility = visibility; } @Override @@ -22,6 +33,11 @@ enum CreatorState implements State { return value; } + @Override + public Visibility getVisibility() { + return visibility; + } + static CreatorState fromValue(int value) throws FormatException { for (CreatorState s : values()) if (s.value == value) return s; throw new FormatException(); diff --git a/briar-core/src/main/java/org/briarproject/briar/privategroup/invitation/GroupInvitationConstants.java b/briar-core/src/main/java/org/briarproject/briar/privategroup/invitation/GroupInvitationConstants.java index a9a6ef0929eadae2009d368e7bb51c8c9ecfff75..90c0fcd29935080e3bd6ed89405ae7ce74cd95b2 100644 --- a/briar-core/src/main/java/org/briarproject/briar/privategroup/invitation/GroupInvitationConstants.java +++ b/briar-core/src/main/java/org/briarproject/briar/privategroup/invitation/GroupInvitationConstants.java @@ -15,6 +15,7 @@ interface GroupInvitationConstants { String MSG_KEY_INVITATION_ACCEPTED = "invitationAccepted"; // Session keys + String SESSION_KEY_IS_SESSION = "isSession"; String SESSION_KEY_SESSION_ID = "sessionId"; String SESSION_KEY_PRIVATE_GROUP_ID = "privateGroupId"; String SESSION_KEY_LAST_LOCAL_MESSAGE_ID = "lastLocalMessageId"; diff --git a/briar-core/src/main/java/org/briarproject/briar/privategroup/invitation/GroupInvitationFactoryImpl.java b/briar-core/src/main/java/org/briarproject/briar/privategroup/invitation/GroupInvitationFactoryImpl.java index 29ca211f6debe399d920fe954aebc30be956fef1..c8fc2b4ba5c3a6541eeda14286f659393591166e 100644 --- a/briar-core/src/main/java/org/briarproject/briar/privategroup/invitation/GroupInvitationFactoryImpl.java +++ b/briar-core/src/main/java/org/briarproject/briar/privategroup/invitation/GroupInvitationFactoryImpl.java @@ -17,7 +17,7 @@ import javax.annotation.concurrent.Immutable; import javax.inject.Inject; import static org.briarproject.briar.api.privategroup.invitation.GroupInvitationManager.CLIENT_ID; -import static org.briarproject.briar.api.privategroup.invitation.GroupInvitationManager.CLIENT_VERSION; +import static org.briarproject.briar.api.privategroup.invitation.GroupInvitationManager.MAJOR_VERSION; @Immutable @NotNullByDefault @@ -53,7 +53,7 @@ class GroupInvitationFactoryImpl implements GroupInvitationFactory { public BdfList createInviteToken(AuthorId creatorId, AuthorId memberId, GroupId privateGroupId, long timestamp) { Group contactGroup = contactGroupFactory.createContactGroup(CLIENT_ID, - CLIENT_VERSION, creatorId, memberId); + MAJOR_VERSION, creatorId, memberId); return BdfList.of( timestamp, contactGroup.getId(), diff --git a/briar-core/src/main/java/org/briarproject/briar/privategroup/invitation/GroupInvitationManagerImpl.java b/briar-core/src/main/java/org/briarproject/briar/privategroup/invitation/GroupInvitationManagerImpl.java index 3c02de9f4653064f98fbb943b104bb961b21ef9d..a4aef1eca325f79485c255c311510cd9d58219d4 100644 --- a/briar-core/src/main/java/org/briarproject/briar/privategroup/invitation/GroupInvitationManagerImpl.java +++ b/briar-core/src/main/java/org/briarproject/briar/privategroup/invitation/GroupInvitationManagerImpl.java @@ -17,10 +17,13 @@ import org.briarproject.bramble.api.identity.Author; import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.sync.Client; 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.Message; import org.briarproject.bramble.api.sync.MessageId; import org.briarproject.bramble.api.sync.MessageStatus; +import org.briarproject.bramble.api.versioning.ClientVersioningManager; +import org.briarproject.bramble.api.versioning.ClientVersioningManager.ClientVersioningHook; import org.briarproject.briar.api.client.MessageTracker; import org.briarproject.briar.api.client.SessionId; import org.briarproject.briar.api.privategroup.PrivateGroup; @@ -36,6 +39,7 @@ import org.briarproject.briar.client.ConversationClientImpl; import java.util.ArrayList; import java.util.Collection; +import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Map.Entry; @@ -59,8 +63,9 @@ import static org.briarproject.briar.privategroup.invitation.Role.PEER; @NotNullByDefault class GroupInvitationManagerImpl extends ConversationClientImpl implements GroupInvitationManager, Client, ContactHook, - PrivateGroupHook { + PrivateGroupHook, ClientVersioningHook { + private final ClientVersioningManager clientVersioningManager; private final ContactGroupFactory contactGroupFactory; private final PrivateGroupFactory privateGroupFactory; private final PrivateGroupManager privateGroupManager; @@ -73,8 +78,9 @@ class GroupInvitationManagerImpl extends ConversationClientImpl @Inject GroupInvitationManagerImpl(DatabaseComponent db, - ClientHelper clientHelper, MetadataParser metadataParser, - MessageTracker messageTracker, + ClientHelper clientHelper, + ClientVersioningManager clientVersioningManager, + MetadataParser metadataParser, MessageTracker messageTracker, ContactGroupFactory contactGroupFactory, PrivateGroupFactory privateGroupFactory, PrivateGroupManager privateGroupManager, @@ -82,6 +88,7 @@ class GroupInvitationManagerImpl extends ConversationClientImpl SessionEncoder sessionEncoder, ProtocolEngineFactory engineFactory) { super(db, clientHelper, metadataParser, messageTracker); + this.clientVersioningManager = clientVersioningManager; this.contactGroupFactory = contactGroupFactory; this.privateGroupFactory = privateGroupFactory; this.privateGroupManager = privateGroupManager; @@ -97,10 +104,10 @@ class GroupInvitationManagerImpl extends ConversationClientImpl public void createLocalState(Transaction txn) throws DbException { // Create a local group to indicate that we've set this client up Group localGroup = contactGroupFactory.createLocalGroup(CLIENT_ID, - CLIENT_VERSION); + MAJOR_VERSION); if (db.containsGroup(txn, localGroup.getId())) return; db.addGroup(txn, localGroup); - // Ensure we've set things up for any pre-existing contacts + // Set things up for any pre-existing contacts for (Contact c : db.getContacts(txn)) addingContact(txn, c); } @@ -108,11 +115,10 @@ class GroupInvitationManagerImpl extends ConversationClientImpl public void addingContact(Transaction txn, Contact c) throws DbException { // Create a group to share with the contact Group g = getContactGroup(c); - // Return if we've already set things up for this contact - if (db.containsGroup(txn, g.getId())) return; - // Store the group and share it with the contact db.addGroup(txn, g); - db.setGroupVisibility(txn, c.getId(), g.getId(), SHARED); + Visibility client = clientVersioningManager.getClientVisibility(txn, + c.getId(), CLIENT_ID, MAJOR_VERSION); + db.setGroupVisibility(txn, c.getId(), g.getId(), client); // Attach the contact ID to the group BdfDictionary meta = new BdfDictionary(); meta.put(GROUP_KEY_CONTACT_ID, c.getId().getInt()); @@ -122,7 +128,8 @@ class GroupInvitationManagerImpl extends ConversationClientImpl throw new AssertionError(e); } // If the contact belongs to any private groups, create a peer session - for (Group pg : db.getGroups(txn, PrivateGroupManager.CLIENT_ID)) { + for (Group pg : db.getGroups(txn, PrivateGroupManager.CLIENT_ID, + PrivateGroupManager.MAJOR_VERSION)) { if (privateGroupManager.isMember(txn, pg.getId(), c.getAuthor())) addingMember(txn, pg.getId(), c); } @@ -137,7 +144,7 @@ class GroupInvitationManagerImpl extends ConversationClientImpl @Override public Group getContactGroup(Contact c) { return contactGroupFactory.createContactGroup(CLIENT_ID, - CLIENT_VERSION, c); + MAJOR_VERSION, c); } @Override @@ -461,8 +468,13 @@ class GroupInvitationManagerImpl extends ConversationClientImpl SessionId sessionId = getSessionId(privateGroupId); Transaction txn = db.startTransaction(true); try { + Visibility client = clientVersioningManager.getClientVisibility(txn, + c.getId(), PrivateGroupManager.CLIENT_ID, + PrivateGroupManager.MAJOR_VERSION); StoredSession ss = getSession(txn, contactGroupId, sessionId); db.commitTransaction(txn); + // The group can't be shared unless the contact supports the client + if (client != SHARED) return false; // If there's no session, the contact can be invited if (ss == null) return true; // If the session's in the start state, the contact can be invited @@ -566,6 +578,65 @@ class GroupInvitationManagerImpl extends ConversationClientImpl } } + @Override + public void onClientVisibilityChanging(Transaction txn, Contact c, + Visibility v) throws DbException { + // Apply the client's visibility to the contact group + Group g = getContactGroup(c); + db.setGroupVisibility(txn, c.getId(), g.getId(), v); + } + + ClientVersioningHook getPrivateGroupClientVersioningHook() { + return this::onPrivateGroupClientVisibilityChanging; + } + + private void onPrivateGroupClientVisibilityChanging(Transaction txn, + Contact c, Visibility client) throws DbException { + try { + Collection<Group> shareables = + db.getGroups(txn, PrivateGroupManager.CLIENT_ID, + PrivateGroupManager.MAJOR_VERSION); + Map<GroupId, Visibility> m = getPreferredVisibilities(txn, c); + for (Group g : shareables) { + Visibility preferred = m.get(g.getId()); + if (preferred == null) continue; // No session for this group + // Apply min of preferred visibility and client's visibility + Visibility min = Visibility.min(preferred, client); + db.setGroupVisibility(txn, c.getId(), g.getId(), min); + } + } catch (FormatException e) { + throw new DbException(e); + } + } + + private Map<GroupId, Visibility> getPreferredVisibilities(Transaction txn, + Contact c) throws DbException, FormatException { + GroupId contactGroupId = getContactGroup(c).getId(); + BdfDictionary query = sessionParser.getAllSessionsQuery(); + Map<MessageId, BdfDictionary> results = clientHelper + .getMessageMetadataAsDictionary(txn, contactGroupId, query); + Map<GroupId, Visibility> m = new HashMap<>(); + for (BdfDictionary d : results.values()) { + Role role = sessionParser.getRole(d); + if (role == CREATOR) { + CreatorSession s = + sessionParser.parseCreatorSession(contactGroupId, d); + m.put(s.getPrivateGroupId(), s.getState().getVisibility()); + } else if (role == INVITEE) { + InviteeSession s = + sessionParser.parseInviteeSession(contactGroupId, d); + m.put(s.getPrivateGroupId(), s.getState().getVisibility()); + } else if (role == PEER) { + PeerSession s = + sessionParser.parsePeerSession(contactGroupId, d); + m.put(s.getPrivateGroupId(), s.getState().getVisibility()); + } else { + throw new AssertionError(); + } + } + return m; + } + private static class StoredSession { private final MessageId storageId; diff --git a/briar-core/src/main/java/org/briarproject/briar/privategroup/invitation/GroupInvitationModule.java b/briar-core/src/main/java/org/briarproject/briar/privategroup/invitation/GroupInvitationModule.java index fbd5efbc132ff187344f5cc07a897a60a1e07ad0..9dc9c84ad1edd564a22c4bb5a3367de2297235ce 100644 --- a/briar-core/src/main/java/org/briarproject/briar/privategroup/invitation/GroupInvitationModule.java +++ b/briar-core/src/main/java/org/briarproject/briar/privategroup/invitation/GroupInvitationModule.java @@ -6,6 +6,7 @@ import org.briarproject.bramble.api.data.MetadataEncoder; import org.briarproject.bramble.api.lifecycle.LifecycleManager; import org.briarproject.bramble.api.sync.ValidationManager; import org.briarproject.bramble.api.system.Clock; +import org.briarproject.bramble.api.versioning.ClientVersioningManager; import org.briarproject.briar.api.messaging.ConversationManager; import org.briarproject.briar.api.privategroup.PrivateGroupFactory; import org.briarproject.briar.api.privategroup.PrivateGroupManager; @@ -19,6 +20,8 @@ import dagger.Module; import dagger.Provides; import static org.briarproject.briar.api.privategroup.invitation.GroupInvitationManager.CLIENT_ID; +import static org.briarproject.briar.api.privategroup.invitation.GroupInvitationManager.MAJOR_VERSION; +import static org.briarproject.briar.api.privategroup.invitation.GroupInvitationManager.MINOR_VERSION; @Module public class GroupInvitationModule { @@ -37,13 +40,22 @@ public class GroupInvitationModule { LifecycleManager lifecycleManager, ValidationManager validationManager, ContactManager contactManager, PrivateGroupManager privateGroupManager, - ConversationManager conversationManager) { + ConversationManager conversationManager, + ClientVersioningManager clientVersioningManager) { lifecycleManager.registerClient(groupInvitationManager); - validationManager.registerIncomingMessageHook(CLIENT_ID, + validationManager.registerIncomingMessageHook(CLIENT_ID, MAJOR_VERSION, groupInvitationManager); contactManager.registerContactHook(groupInvitationManager); privateGroupManager.registerPrivateGroupHook(groupInvitationManager); conversationManager.registerConversationClient(groupInvitationManager); + clientVersioningManager.registerClient(CLIENT_ID, MAJOR_VERSION, + MINOR_VERSION, groupInvitationManager); + // The group invitation manager handles client visibility changes for + // the private group manager + clientVersioningManager.registerClient(PrivateGroupManager.CLIENT_ID, + PrivateGroupManager.MAJOR_VERSION, + PrivateGroupManager.MINOR_VERSION, + groupInvitationManager.getPrivateGroupClientVersioningHook()); return groupInvitationManager; } @@ -57,7 +69,8 @@ public class GroupInvitationModule { GroupInvitationValidator validator = new GroupInvitationValidator( clientHelper, metadataEncoder, clock, privateGroupFactory, messageEncoder); - validationManager.registerMessageValidator(CLIENT_ID, validator); + validationManager.registerMessageValidator(CLIENT_ID, MAJOR_VERSION, + validator); return validator; } diff --git a/briar-core/src/main/java/org/briarproject/briar/privategroup/invitation/InviteeProtocolEngine.java b/briar-core/src/main/java/org/briarproject/briar/privategroup/invitation/InviteeProtocolEngine.java index f181d1f7764a5507ad6e8725bf56902e4e02eece..a0e73d9b05d43f7a8f3ec227b03b37b6c5ef7b4c 100644 --- a/briar-core/src/main/java/org/briarproject/briar/privategroup/invitation/InviteeProtocolEngine.java +++ b/briar-core/src/main/java/org/briarproject/briar/privategroup/invitation/InviteeProtocolEngine.java @@ -12,6 +12,7 @@ import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.sync.Message; import org.briarproject.bramble.api.sync.MessageId; import org.briarproject.bramble.api.system.Clock; +import org.briarproject.bramble.api.versioning.ClientVersioningManager; import org.briarproject.briar.api.client.MessageTracker; import org.briarproject.briar.api.client.ProtocolStateException; import org.briarproject.briar.api.client.SessionId; @@ -41,15 +42,16 @@ import static org.briarproject.briar.privategroup.invitation.InviteeState.START; class InviteeProtocolEngine extends AbstractProtocolEngine<InviteeSession> { InviteeProtocolEngine(DatabaseComponent db, ClientHelper clientHelper, + ClientVersioningManager clientVersioningManager, PrivateGroupManager privateGroupManager, PrivateGroupFactory privateGroupFactory, GroupMessageFactory groupMessageFactory, IdentityManager identityManager, MessageParser messageParser, MessageEncoder messageEncoder, MessageTracker messageTracker, Clock clock) { - super(db, clientHelper, privateGroupManager, privateGroupFactory, - groupMessageFactory, identityManager, messageParser, - messageEncoder, messageTracker, clock); + super(db, clientHelper, clientVersioningManager, privateGroupManager, + privateGroupFactory, groupMessageFactory, identityManager, + messageParser, messageEncoder, messageTracker, clock); } @Override @@ -212,6 +214,12 @@ class InviteeProtocolEngine extends AbstractProtocolEngine<InviteeSession> { throws DbException { // Send a LEAVE message Message sent = sendLeaveMessage(txn, s, false); + try { + // Make the private group invisible to the contact + setPrivateGroupVisibility(txn, s, INVISIBLE); + } catch (FormatException e) { + throw new DbException(e); // Invalid group metadata + } // Move to the LEFT state return new InviteeSession(s.getContactGroupId(), s.getPrivateGroupId(), sent.getId(), s.getLastRemoteMessageId(), sent.getTimestamp(), diff --git a/briar-core/src/main/java/org/briarproject/briar/privategroup/invitation/InviteeState.java b/briar-core/src/main/java/org/briarproject/briar/privategroup/invitation/InviteeState.java index 7fa69e6af9c1c6113a70a92edf86b48d31b80dd6..f347ca06188cae611e91fa3c08700f7bdae7bdbf 100644 --- a/briar-core/src/main/java/org/briarproject/briar/privategroup/invitation/InviteeState.java +++ b/briar-core/src/main/java/org/briarproject/briar/privategroup/invitation/InviteeState.java @@ -2,20 +2,32 @@ package org.briarproject.briar.privategroup.invitation; import org.briarproject.bramble.api.FormatException; import org.briarproject.bramble.api.nullsafety.NotNullByDefault; +import org.briarproject.bramble.api.sync.Group.Visibility; import javax.annotation.concurrent.Immutable; +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; + @Immutable @NotNullByDefault enum InviteeState implements State { - START(0), INVITED(1), ACCEPTED(2), JOINED(3), LEFT(4), DISSOLVED(5), - ERROR(6); + START(0, INVISIBLE), + INVITED(1, INVISIBLE), + ACCEPTED(2, VISIBLE), + JOINED(3, SHARED), + LEFT(4, INVISIBLE), + DISSOLVED(5, INVISIBLE), + ERROR(6, INVISIBLE); private final int value; + private final Visibility visibility; - InviteeState(int value) { + InviteeState(int value, Visibility visibility) { this.value = value; + this.visibility = visibility; } @Override @@ -23,6 +35,11 @@ enum InviteeState implements State { return value; } + @Override + public Visibility getVisibility() { + return visibility; + } + static InviteeState fromValue(int value) throws FormatException { for (InviteeState s : values()) if (s.value == value) return s; throw new FormatException(); diff --git a/briar-core/src/main/java/org/briarproject/briar/privategroup/invitation/PeerProtocolEngine.java b/briar-core/src/main/java/org/briarproject/briar/privategroup/invitation/PeerProtocolEngine.java index c1908735fde51d986fd5646dcc79296bae0c13d5..9b7096b63b7ac48f2eea99fb5b1522b095f46f06 100644 --- a/briar-core/src/main/java/org/briarproject/briar/privategroup/invitation/PeerProtocolEngine.java +++ b/briar-core/src/main/java/org/briarproject/briar/privategroup/invitation/PeerProtocolEngine.java @@ -11,6 +11,7 @@ import org.briarproject.bramble.api.identity.IdentityManager; import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.sync.Message; import org.briarproject.bramble.api.system.Clock; +import org.briarproject.bramble.api.versioning.ClientVersioningManager; import org.briarproject.briar.api.client.MessageTracker; import org.briarproject.briar.api.client.ProtocolStateException; import org.briarproject.briar.api.privategroup.GroupMessageFactory; @@ -36,15 +37,16 @@ import static org.briarproject.briar.privategroup.invitation.PeerState.START; class PeerProtocolEngine extends AbstractProtocolEngine<PeerSession> { PeerProtocolEngine(DatabaseComponent db, ClientHelper clientHelper, + ClientVersioningManager clientVersioningManager, PrivateGroupManager privateGroupManager, PrivateGroupFactory privateGroupFactory, GroupMessageFactory groupMessageFactory, IdentityManager identityManager, MessageParser messageParser, MessageEncoder messageEncoder, MessageTracker messageTracker, Clock clock) { - super(db, clientHelper, privateGroupManager, privateGroupFactory, - groupMessageFactory, identityManager, messageParser, - messageEncoder, messageTracker, clock); + super(db, clientHelper, clientVersioningManager, privateGroupManager, + privateGroupFactory, groupMessageFactory, identityManager, + messageParser, messageEncoder, messageTracker, clock); } @Override diff --git a/briar-core/src/main/java/org/briarproject/briar/privategroup/invitation/PeerState.java b/briar-core/src/main/java/org/briarproject/briar/privategroup/invitation/PeerState.java index 94f9d19afe624a57645bde631c98219c062761e8..041528266eb99a42dfa40fd39584b7b783e0a9cc 100644 --- a/briar-core/src/main/java/org/briarproject/briar/privategroup/invitation/PeerState.java +++ b/briar-core/src/main/java/org/briarproject/briar/privategroup/invitation/PeerState.java @@ -2,20 +2,32 @@ package org.briarproject.briar.privategroup.invitation; import org.briarproject.bramble.api.FormatException; import org.briarproject.bramble.api.nullsafety.NotNullByDefault; +import org.briarproject.bramble.api.sync.Group.Visibility; import javax.annotation.concurrent.Immutable; +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; + @Immutable @NotNullByDefault enum PeerState implements State { - START(0), AWAIT_MEMBER(1), NEITHER_JOINED(2), LOCAL_JOINED(3), - BOTH_JOINED(4), LOCAL_LEFT(5), ERROR(6); + START(0, INVISIBLE), + AWAIT_MEMBER(1, INVISIBLE), + NEITHER_JOINED(2, INVISIBLE), + LOCAL_JOINED(3, VISIBLE), + BOTH_JOINED(4, SHARED), + LOCAL_LEFT(5, INVISIBLE), + ERROR(6, INVISIBLE); private final int value; + private final Visibility visibility; - PeerState(int value) { + PeerState(int value, Visibility visibility) { this.value = value; + this.visibility = visibility; } @Override @@ -23,6 +35,11 @@ enum PeerState implements State { return value; } + @Override + public Visibility getVisibility() { + return visibility; + } + static PeerState fromValue(int value) throws FormatException { for (PeerState s : values()) if (s.value == value) return s; throw new FormatException(); diff --git a/briar-core/src/main/java/org/briarproject/briar/privategroup/invitation/ProtocolEngineFactoryImpl.java b/briar-core/src/main/java/org/briarproject/briar/privategroup/invitation/ProtocolEngineFactoryImpl.java index af3bbe81673944079a40db128630757507390689..d5403102909b8f2c14b355d529bd526bcf330566 100644 --- a/briar-core/src/main/java/org/briarproject/briar/privategroup/invitation/ProtocolEngineFactoryImpl.java +++ b/briar-core/src/main/java/org/briarproject/briar/privategroup/invitation/ProtocolEngineFactoryImpl.java @@ -5,6 +5,7 @@ import org.briarproject.bramble.api.db.DatabaseComponent; import org.briarproject.bramble.api.identity.IdentityManager; import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.system.Clock; +import org.briarproject.bramble.api.versioning.ClientVersioningManager; import org.briarproject.briar.api.client.MessageTracker; import org.briarproject.briar.api.privategroup.GroupMessageFactory; import org.briarproject.briar.api.privategroup.PrivateGroupFactory; @@ -19,6 +20,7 @@ class ProtocolEngineFactoryImpl implements ProtocolEngineFactory { private final DatabaseComponent db; private final ClientHelper clientHelper; + private final ClientVersioningManager clientVersioningManager; private final PrivateGroupManager privateGroupManager; private final PrivateGroupFactory privateGroupFactory; private final GroupMessageFactory groupMessageFactory; @@ -30,6 +32,7 @@ class ProtocolEngineFactoryImpl implements ProtocolEngineFactory { @Inject ProtocolEngineFactoryImpl(DatabaseComponent db, ClientHelper clientHelper, + ClientVersioningManager clientVersioningManager, PrivateGroupManager privateGroupManager, PrivateGroupFactory privateGroupFactory, GroupMessageFactory groupMessageFactory, @@ -38,6 +41,7 @@ class ProtocolEngineFactoryImpl implements ProtocolEngineFactory { Clock clock) { this.db = db; this.clientHelper = clientHelper; + this.clientVersioningManager = clientVersioningManager; this.privateGroupManager = privateGroupManager; this.privateGroupFactory = privateGroupFactory; this.groupMessageFactory = groupMessageFactory; @@ -50,21 +54,24 @@ class ProtocolEngineFactoryImpl implements ProtocolEngineFactory { @Override public ProtocolEngine<CreatorSession> createCreatorEngine() { - return new CreatorProtocolEngine(db, clientHelper, privateGroupManager, + return new CreatorProtocolEngine(db, clientHelper, + clientVersioningManager, privateGroupManager, privateGroupFactory, groupMessageFactory, identityManager, messageParser, messageEncoder, messageTracker, clock); } @Override public ProtocolEngine<InviteeSession> createInviteeEngine() { - return new InviteeProtocolEngine(db, clientHelper, privateGroupManager, + return new InviteeProtocolEngine(db, clientHelper, + clientVersioningManager, privateGroupManager, privateGroupFactory, groupMessageFactory, identityManager, messageParser, messageEncoder, messageTracker, clock); } @Override public ProtocolEngine<PeerSession> createPeerEngine() { - return new PeerProtocolEngine(db, clientHelper, privateGroupManager, + return new PeerProtocolEngine(db, clientHelper, + clientVersioningManager, privateGroupManager, privateGroupFactory, groupMessageFactory, identityManager, messageParser, messageEncoder, messageTracker, clock); } diff --git a/briar-core/src/main/java/org/briarproject/briar/privategroup/invitation/SessionEncoderImpl.java b/briar-core/src/main/java/org/briarproject/briar/privategroup/invitation/SessionEncoderImpl.java index 78e36bf3a5b15732e6298678cc71d10b026d003e..86fe5e3a044e52dc295e1660726812ac76d8a328 100644 --- a/briar-core/src/main/java/org/briarproject/briar/privategroup/invitation/SessionEncoderImpl.java +++ b/briar-core/src/main/java/org/briarproject/briar/privategroup/invitation/SessionEncoderImpl.java @@ -9,6 +9,7 @@ import javax.inject.Inject; import static org.briarproject.bramble.api.data.BdfDictionary.NULL_VALUE; import static org.briarproject.briar.privategroup.invitation.GroupInvitationConstants.SESSION_KEY_INVITE_TIMESTAMP; +import static org.briarproject.briar.privategroup.invitation.GroupInvitationConstants.SESSION_KEY_IS_SESSION; import static org.briarproject.briar.privategroup.invitation.GroupInvitationConstants.SESSION_KEY_LAST_LOCAL_MESSAGE_ID; import static org.briarproject.briar.privategroup.invitation.GroupInvitationConstants.SESSION_KEY_LAST_REMOTE_MESSAGE_ID; import static org.briarproject.briar.privategroup.invitation.GroupInvitationConstants.SESSION_KEY_LOCAL_TIMESTAMP; @@ -28,6 +29,7 @@ class SessionEncoderImpl implements SessionEncoder { @Override public BdfDictionary encodeSession(Session s) { BdfDictionary d = new BdfDictionary(); + d.put(SESSION_KEY_IS_SESSION, true); d.put(SESSION_KEY_SESSION_ID, s.getPrivateGroupId()); d.put(SESSION_KEY_PRIVATE_GROUP_ID, s.getPrivateGroupId()); MessageId lastLocalMessageId = s.getLastLocalMessageId(); diff --git a/briar-core/src/main/java/org/briarproject/briar/privategroup/invitation/SessionParser.java b/briar-core/src/main/java/org/briarproject/briar/privategroup/invitation/SessionParser.java index baf0de80e2f392f270c1e57ce52e94d6de5dd061..152ed067e32c879d111df13e7f5c2442d6fe7e65 100644 --- a/briar-core/src/main/java/org/briarproject/briar/privategroup/invitation/SessionParser.java +++ b/briar-core/src/main/java/org/briarproject/briar/privategroup/invitation/SessionParser.java @@ -11,6 +11,8 @@ interface SessionParser { BdfDictionary getSessionQuery(SessionId s); + BdfDictionary getAllSessionsQuery(); + Role getRole(BdfDictionary d) throws FormatException; CreatorSession parseCreatorSession(GroupId contactGroupId, BdfDictionary d) diff --git a/briar-core/src/main/java/org/briarproject/briar/privategroup/invitation/SessionParserImpl.java b/briar-core/src/main/java/org/briarproject/briar/privategroup/invitation/SessionParserImpl.java index e8813b6dcd50082ea577c4229c5f6a9a1d2795fd..19a424d707e460cf8f023e42d1cb7a4dab028543 100644 --- a/briar-core/src/main/java/org/briarproject/briar/privategroup/invitation/SessionParserImpl.java +++ b/briar-core/src/main/java/org/briarproject/briar/privategroup/invitation/SessionParserImpl.java @@ -13,6 +13,7 @@ import javax.annotation.concurrent.Immutable; import javax.inject.Inject; import static org.briarproject.briar.privategroup.invitation.GroupInvitationConstants.SESSION_KEY_INVITE_TIMESTAMP; +import static org.briarproject.briar.privategroup.invitation.GroupInvitationConstants.SESSION_KEY_IS_SESSION; import static org.briarproject.briar.privategroup.invitation.GroupInvitationConstants.SESSION_KEY_LAST_LOCAL_MESSAGE_ID; import static org.briarproject.briar.privategroup.invitation.GroupInvitationConstants.SESSION_KEY_LAST_REMOTE_MESSAGE_ID; import static org.briarproject.briar.privategroup.invitation.GroupInvitationConstants.SESSION_KEY_LOCAL_TIMESTAMP; @@ -37,6 +38,11 @@ class SessionParserImpl implements SessionParser { return BdfDictionary.of(new BdfEntry(SESSION_KEY_SESSION_ID, s)); } + @Override + public BdfDictionary getAllSessionsQuery() { + return BdfDictionary.of(new BdfEntry(SESSION_KEY_IS_SESSION, true)); + } + @Override public Role getRole(BdfDictionary d) throws FormatException { return Role.fromValue(d.getLong(SESSION_KEY_ROLE).intValue()); diff --git a/briar-core/src/main/java/org/briarproject/briar/privategroup/invitation/State.java b/briar-core/src/main/java/org/briarproject/briar/privategroup/invitation/State.java index 46df85efdc5dff661db8862eb557b514fe16a6e9..2313f96c7e0cf5c3620b116ebceef9d6b18d2650 100644 --- a/briar-core/src/main/java/org/briarproject/briar/privategroup/invitation/State.java +++ b/briar-core/src/main/java/org/briarproject/briar/privategroup/invitation/State.java @@ -1,6 +1,10 @@ package org.briarproject.briar.privategroup.invitation; +import org.briarproject.bramble.api.sync.Group.Visibility; + interface State { int getValue(); + + Visibility getVisibility(); } diff --git a/briar-core/src/main/java/org/briarproject/briar/sharing/BlogProtocolEngineImpl.java b/briar-core/src/main/java/org/briarproject/briar/sharing/BlogProtocolEngineImpl.java index 3083387b6bf1f76f393b6dc6d2066c67f32e8e77..25e0d0e8e1ec57b194cd8fe5f08e615ba5a98b88 100644 --- a/briar-core/src/main/java/org/briarproject/briar/sharing/BlogProtocolEngineImpl.java +++ b/briar-core/src/main/java/org/briarproject/briar/sharing/BlogProtocolEngineImpl.java @@ -11,6 +11,7 @@ import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.sync.ClientId; import org.briarproject.bramble.api.sync.MessageId; import org.briarproject.bramble.api.system.Clock; +import org.briarproject.bramble.api.versioning.ClientVersioningManager; import org.briarproject.briar.api.blog.Blog; import org.briarproject.briar.api.blog.BlogInvitationResponse; import org.briarproject.briar.api.blog.BlogManager; @@ -22,6 +23,9 @@ import org.briarproject.briar.api.sharing.InvitationRequest; import javax.annotation.concurrent.Immutable; import javax.inject.Inject; +import static org.briarproject.briar.api.blog.BlogManager.CLIENT_ID; +import static org.briarproject.briar.api.blog.BlogManager.MAJOR_VERSION; + @Immutable @NotNullByDefault class BlogProtocolEngineImpl extends ProtocolEngineImpl<Blog> { @@ -31,13 +35,14 @@ class BlogProtocolEngineImpl extends ProtocolEngineImpl<Blog> { invitationFactory; @Inject - BlogProtocolEngineImpl(DatabaseComponent db, - ClientHelper clientHelper, MessageEncoder messageEncoder, - MessageParser<Blog> messageParser, MessageTracker messageTracker, - Clock clock, BlogManager blogManager, + BlogProtocolEngineImpl(DatabaseComponent db, ClientHelper clientHelper, + ClientVersioningManager clientVersioningManager, + MessageEncoder messageEncoder, MessageParser<Blog> messageParser, + MessageTracker messageTracker, Clock clock, BlogManager blogManager, InvitationFactory<Blog, BlogInvitationResponse> invitationFactory) { - super(db, clientHelper, messageEncoder, messageParser, messageTracker, - clock); + super(db, clientHelper, clientVersioningManager, messageEncoder, + messageParser, messageTracker, clock, CLIENT_ID, + MAJOR_VERSION); this.blogManager = blogManager; this.invitationFactory = invitationFactory; } @@ -46,8 +51,8 @@ class BlogProtocolEngineImpl extends ProtocolEngineImpl<Blog> { Event getInvitationRequestReceivedEvent(InviteMessage<Blog> m, ContactId contactId, boolean available, boolean canBeOpened) { InvitationRequest<Blog> request = invitationFactory - .createInvitationRequest(false, false, true, false, m, - contactId, available, canBeOpened); + .createInvitationRequest(false, false, true, false, m, + contactId, available, canBeOpened); return new BlogInvitationRequestReceivedEvent(m.getShareable(), contactId, request); } @@ -74,7 +79,7 @@ class BlogProtocolEngineImpl extends ProtocolEngineImpl<Blog> { @Override protected ClientId getShareableClientId() { - return BlogManager.CLIENT_ID; + return CLIENT_ID; } @Override diff --git a/briar-core/src/main/java/org/briarproject/briar/sharing/BlogSharingManagerImpl.java b/briar-core/src/main/java/org/briarproject/briar/sharing/BlogSharingManagerImpl.java index f0086e856c0a12c99fa85a2394592d6b3a2333cc..5c77c7be942df58dd618bfc5fd70d055699d4bc9 100644 --- a/briar-core/src/main/java/org/briarproject/briar/sharing/BlogSharingManagerImpl.java +++ b/briar-core/src/main/java/org/briarproject/briar/sharing/BlogSharingManagerImpl.java @@ -12,6 +12,7 @@ import org.briarproject.bramble.api.identity.IdentityManager; import org.briarproject.bramble.api.identity.LocalAuthor; import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.sync.ClientId; +import org.briarproject.bramble.api.versioning.ClientVersioningManager; import org.briarproject.briar.api.blog.Blog; import org.briarproject.briar.api.blog.BlogInvitationResponse; import org.briarproject.briar.api.blog.BlogManager; @@ -32,6 +33,7 @@ class BlogSharingManagerImpl extends SharingManagerImpl<Blog> @Inject BlogSharingManagerImpl(DatabaseComponent db, ClientHelper clientHelper, + ClientVersioningManager clientVersioningManager, MetadataParser metadataParser, MessageParser<Blog> messageParser, SessionEncoder sessionEncoder, SessionParser sessionParser, MessageTracker messageTracker, @@ -39,9 +41,9 @@ class BlogSharingManagerImpl extends SharingManagerImpl<Blog> ProtocolEngine<Blog> engine, InvitationFactory<Blog, BlogInvitationResponse> invitationFactory, IdentityManager identityManager, BlogManager blogManager) { - super(db, clientHelper, metadataParser, messageParser, sessionEncoder, - sessionParser, messageTracker, contactGroupFactory, engine, - invitationFactory); + super(db, clientHelper, clientVersioningManager, metadataParser, + messageParser, sessionEncoder, sessionParser, messageTracker, + contactGroupFactory, engine, invitationFactory); this.identityManager = identityManager; this.blogManager = blogManager; } @@ -52,27 +54,31 @@ class BlogSharingManagerImpl extends SharingManagerImpl<Blog> } @Override - protected int getClientVersion() { - return CLIENT_VERSION; + protected int getMajorVersion() { + return MAJOR_VERSION; } - /** - * This is called during each startup for each existing Contact. - */ @Override - public void addingContact(Transaction txn, Contact c) throws DbException { - // Return if we've already set things up for this contact - if (db.containsGroup(txn, getContactGroup(c).getId())) return; + protected ClientId getShareableClientId() { + return BlogManager.CLIENT_ID; + } - // creates a group to share with the contact + @Override + protected int getShareableMajorVersion() { + return BlogManager.MAJOR_VERSION; + } + + @Override + public void addingContact(Transaction txn, Contact c) throws DbException { + // Create a group to share with the contact super.addingContact(txn, c); - // get our blog and that of Contact c + // Get our blog and that of the contact LocalAuthor localAuthor = identityManager.getLocalAuthor(txn); Blog ourBlog = blogManager.getPersonalBlog(localAuthor); Blog theirBlog = blogManager.getPersonalBlog(c.getAuthor()); - // pre-share both blogs, if they have not been shared already + // Pre-share both blogs, if they have not been shared already try { preShareShareable(txn, c, ourBlog); preShareShareable(txn, c, theirBlog); diff --git a/briar-core/src/main/java/org/briarproject/briar/sharing/ForumProtocolEngineImpl.java b/briar-core/src/main/java/org/briarproject/briar/sharing/ForumProtocolEngineImpl.java index 9fb695e50718ca5686841a8a133184bdd0a0d7a2..7b2a1c7854d2bb56e8802d65929dd7bc3fa164d0 100644 --- a/briar-core/src/main/java/org/briarproject/briar/sharing/ForumProtocolEngineImpl.java +++ b/briar-core/src/main/java/org/briarproject/briar/sharing/ForumProtocolEngineImpl.java @@ -11,6 +11,7 @@ import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.sync.ClientId; import org.briarproject.bramble.api.sync.MessageId; import org.briarproject.bramble.api.system.Clock; +import org.briarproject.bramble.api.versioning.ClientVersioningManager; import org.briarproject.briar.api.client.MessageTracker; import org.briarproject.briar.api.forum.Forum; import org.briarproject.briar.api.forum.ForumInvitationResponse; @@ -22,6 +23,9 @@ import org.briarproject.briar.api.sharing.InvitationRequest; import javax.annotation.concurrent.Immutable; import javax.inject.Inject; +import static org.briarproject.briar.api.forum.ForumManager.CLIENT_ID; +import static org.briarproject.briar.api.forum.ForumManager.MAJOR_VERSION; + @Immutable @NotNullByDefault class ForumProtocolEngineImpl extends ProtocolEngineImpl<Forum> { @@ -32,12 +36,15 @@ class ForumProtocolEngineImpl extends ProtocolEngineImpl<Forum> { @Inject ForumProtocolEngineImpl(DatabaseComponent db, - ClientHelper clientHelper, MessageEncoder messageEncoder, - MessageParser<Forum> messageParser, MessageTracker messageTracker, - Clock clock, ForumManager forumManager, + ClientHelper clientHelper, + ClientVersioningManager clientVersioningManager, + MessageEncoder messageEncoder, MessageParser<Forum> messageParser, + MessageTracker messageTracker, Clock clock, + ForumManager forumManager, InvitationFactory<Forum, ForumInvitationResponse> invitationFactory) { - super(db, clientHelper, messageEncoder, messageParser, messageTracker, - clock); + super(db, clientHelper, clientVersioningManager, messageEncoder, + messageParser, messageTracker, clock, CLIENT_ID, + MAJOR_VERSION); this.forumManager = forumManager; this.invitationFactory = invitationFactory; } @@ -46,8 +53,8 @@ class ForumProtocolEngineImpl extends ProtocolEngineImpl<Forum> { Event getInvitationRequestReceivedEvent(InviteMessage<Forum> m, ContactId contactId, boolean available, boolean canBeOpened) { InvitationRequest<Forum> request = invitationFactory - .createInvitationRequest(false, false, true, false, m, - contactId, available, canBeOpened); + .createInvitationRequest(false, false, true, false, m, + contactId, available, canBeOpened); return new ForumInvitationRequestReceivedEvent(m.getShareable(), contactId, request); } @@ -74,7 +81,7 @@ class ForumProtocolEngineImpl extends ProtocolEngineImpl<Forum> { @Override protected ClientId getShareableClientId() { - return ForumManager.CLIENT_ID; + return CLIENT_ID; } @Override diff --git a/briar-core/src/main/java/org/briarproject/briar/sharing/ForumSharingManagerImpl.java b/briar-core/src/main/java/org/briarproject/briar/sharing/ForumSharingManagerImpl.java index 528743b9cc6ba65d939f8db3c27ab7014d8e3b36..9b3cd97e16dd9c3893dcf26be9381473f4a53b46 100644 --- a/briar-core/src/main/java/org/briarproject/briar/sharing/ForumSharingManagerImpl.java +++ b/briar-core/src/main/java/org/briarproject/briar/sharing/ForumSharingManagerImpl.java @@ -8,9 +8,11 @@ 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.ClientId; +import org.briarproject.bramble.api.versioning.ClientVersioningManager; import org.briarproject.briar.api.client.MessageTracker; import org.briarproject.briar.api.forum.Forum; import org.briarproject.briar.api.forum.ForumInvitationResponse; +import org.briarproject.briar.api.forum.ForumManager; import org.briarproject.briar.api.forum.ForumManager.RemoveForumHook; import org.briarproject.briar.api.forum.ForumSharingManager; @@ -22,15 +24,16 @@ class ForumSharingManagerImpl extends SharingManagerImpl<Forum> @Inject ForumSharingManagerImpl(DatabaseComponent db, ClientHelper clientHelper, + ClientVersioningManager clientVersioningManager, MetadataParser metadataParser, MessageParser<Forum> messageParser, SessionEncoder sessionEncoder, SessionParser sessionParser, MessageTracker messageTracker, ContactGroupFactory contactGroupFactory, ProtocolEngine<Forum> engine, InvitationFactory<Forum, ForumInvitationResponse> invitationFactory) { - super(db, clientHelper, metadataParser, messageParser, sessionEncoder, - sessionParser, messageTracker, contactGroupFactory, engine, - invitationFactory); + super(db, clientHelper, clientVersioningManager, metadataParser, + messageParser, sessionEncoder, sessionParser, messageTracker, + contactGroupFactory, engine, invitationFactory); } @Override @@ -39,8 +42,18 @@ class ForumSharingManagerImpl extends SharingManagerImpl<Forum> } @Override - protected int getClientVersion() { - return CLIENT_VERSION; + protected int getMajorVersion() { + return MAJOR_VERSION; + } + + @Override + protected ClientId getShareableClientId() { + return ForumManager.CLIENT_ID; + } + + @Override + protected int getShareableMajorVersion() { + return ForumManager.MAJOR_VERSION; } @Override diff --git a/briar-core/src/main/java/org/briarproject/briar/sharing/ProtocolEngineImpl.java b/briar-core/src/main/java/org/briarproject/briar/sharing/ProtocolEngineImpl.java index e6a7976d5829b21478ae15dbb11d476cb740b7bf..0a672f57810a031537abae10dfc11c7a7db9b20a 100644 --- a/briar-core/src/main/java/org/briarproject/briar/sharing/ProtocolEngineImpl.java +++ b/briar-core/src/main/java/org/briarproject/briar/sharing/ProtocolEngineImpl.java @@ -17,6 +17,7 @@ import org.briarproject.bramble.api.sync.GroupId; import org.briarproject.bramble.api.sync.Message; import org.briarproject.bramble.api.sync.MessageId; import org.briarproject.bramble.api.system.Clock; +import org.briarproject.bramble.api.versioning.ClientVersioningManager; import org.briarproject.briar.api.client.MessageTracker; import org.briarproject.briar.api.client.ProtocolStateException; import org.briarproject.briar.api.sharing.Shareable; @@ -52,19 +53,27 @@ abstract class ProtocolEngineImpl<S extends Shareable> protected final ClientHelper clientHelper; protected final MessageParser<S> messageParser; + private final ClientVersioningManager clientVersioningManager; private final MessageEncoder messageEncoder; private final MessageTracker messageTracker; private final Clock clock; + private final ClientId shareableClientId; + private final int shareableClientVersion; ProtocolEngineImpl(DatabaseComponent db, ClientHelper clientHelper, + ClientVersioningManager clientVersioningManager, MessageEncoder messageEncoder, MessageParser<S> messageParser, - MessageTracker messageTracker, Clock clock) { + MessageTracker messageTracker, Clock clock, + ClientId shareableClientId, int shareableClientVersion) { this.db = db; this.clientHelper = clientHelper; + this.clientVersioningManager = clientVersioningManager; this.messageEncoder = messageEncoder; this.messageParser = messageParser; this.messageTracker = messageTracker; this.clock = clock; + this.shareableClientId = shareableClientId; + this.shareableClientVersion = shareableClientVersion; } @Override @@ -598,9 +607,13 @@ abstract class ProtocolEngineImpl<S extends Shareable> } private void setShareableVisibility(Transaction txn, Session session, - Visibility v) throws DbException, FormatException { + Visibility preferred) throws DbException, FormatException { + // Apply min of preferred visibility and client's visibility ContactId contactId = getContactId(txn, session.getContactGroupId()); - db.setGroupVisibility(txn, contactId, session.getShareableId(), v); + Visibility client = clientVersioningManager.getClientVisibility(txn, + contactId, shareableClientId, shareableClientVersion); + Visibility min = Visibility.min(preferred, client); + db.setGroupVisibility(txn, contactId, session.getShareableId(), min); } private ContactId getContactId(Transaction txn, GroupId contactGroupId) diff --git a/briar-core/src/main/java/org/briarproject/briar/sharing/SessionEncoderImpl.java b/briar-core/src/main/java/org/briarproject/briar/sharing/SessionEncoderImpl.java index 7cf2bf139d67495092dec01dc158674aec217a63..641731a4aa9180cbb7dcb62545866ddce137b014 100644 --- a/briar-core/src/main/java/org/briarproject/briar/sharing/SessionEncoderImpl.java +++ b/briar-core/src/main/java/org/briarproject/briar/sharing/SessionEncoderImpl.java @@ -9,6 +9,7 @@ import javax.inject.Inject; import static org.briarproject.bramble.api.data.BdfDictionary.NULL_VALUE; import static org.briarproject.briar.sharing.SharingConstants.SESSION_KEY_INVITE_TIMESTAMP; +import static org.briarproject.briar.sharing.SharingConstants.SESSION_KEY_IS_SESSION; import static org.briarproject.briar.sharing.SharingConstants.SESSION_KEY_LAST_LOCAL_MESSAGE_ID; import static org.briarproject.briar.sharing.SharingConstants.SESSION_KEY_LAST_REMOTE_MESSAGE_ID; import static org.briarproject.briar.sharing.SharingConstants.SESSION_KEY_LOCAL_TIMESTAMP; @@ -27,6 +28,7 @@ class SessionEncoderImpl implements SessionEncoder { @Override public BdfDictionary encodeSession(Session s) { BdfDictionary d = new BdfDictionary(); + d.put(SESSION_KEY_IS_SESSION, true); d.put(SESSION_KEY_SESSION_ID, s.getShareableId()); d.put(SESSION_KEY_SHAREABLE_ID, s.getShareableId()); MessageId lastLocalMessageId = s.getLastLocalMessageId(); diff --git a/briar-core/src/main/java/org/briarproject/briar/sharing/SessionParser.java b/briar-core/src/main/java/org/briarproject/briar/sharing/SessionParser.java index a82aed3eca199a2eb8eb2924375595a88ba42a0e..eec97355f4db745ea8fb54712cd73717698de051 100644 --- a/briar-core/src/main/java/org/briarproject/briar/sharing/SessionParser.java +++ b/briar-core/src/main/java/org/briarproject/briar/sharing/SessionParser.java @@ -11,6 +11,8 @@ interface SessionParser { BdfDictionary getSessionQuery(SessionId s); + BdfDictionary getAllSessionsQuery(); + Session parseSession(GroupId contactGroupId, BdfDictionary d) throws FormatException; diff --git a/briar-core/src/main/java/org/briarproject/briar/sharing/SessionParserImpl.java b/briar-core/src/main/java/org/briarproject/briar/sharing/SessionParserImpl.java index 62d9c44b12b276ea27655471b916451597ceedcb..ee0c06f61f32d6acb9149d6664b927cde30eb14f 100644 --- a/briar-core/src/main/java/org/briarproject/briar/sharing/SessionParserImpl.java +++ b/briar-core/src/main/java/org/briarproject/briar/sharing/SessionParserImpl.java @@ -13,6 +13,7 @@ import javax.annotation.concurrent.Immutable; import javax.inject.Inject; import static org.briarproject.briar.sharing.SharingConstants.SESSION_KEY_INVITE_TIMESTAMP; +import static org.briarproject.briar.sharing.SharingConstants.SESSION_KEY_IS_SESSION; import static org.briarproject.briar.sharing.SharingConstants.SESSION_KEY_LAST_LOCAL_MESSAGE_ID; import static org.briarproject.briar.sharing.SharingConstants.SESSION_KEY_LAST_REMOTE_MESSAGE_ID; import static org.briarproject.briar.sharing.SharingConstants.SESSION_KEY_LOCAL_TIMESTAMP; @@ -33,6 +34,11 @@ class SessionParserImpl implements SessionParser { return BdfDictionary.of(new BdfEntry(SESSION_KEY_SESSION_ID, s)); } + @Override + public BdfDictionary getAllSessionsQuery() { + return BdfDictionary.of(new BdfEntry(SESSION_KEY_IS_SESSION, true)); + } + @Override public Session parseSession(GroupId contactGroupId, BdfDictionary d) throws FormatException { diff --git a/briar-core/src/main/java/org/briarproject/briar/sharing/SharingConstants.java b/briar-core/src/main/java/org/briarproject/briar/sharing/SharingConstants.java index d98099e27b2ff30a024ce7a2efbe4b72c01c26e1..f2522d1a9bb05a51a2d68d2a7f29db9153d832a4 100644 --- a/briar-core/src/main/java/org/briarproject/briar/sharing/SharingConstants.java +++ b/briar-core/src/main/java/org/briarproject/briar/sharing/SharingConstants.java @@ -18,6 +18,7 @@ interface SharingConstants { String MSG_KEY_INVITATION_ACCEPTED = "invitationAccepted"; // Session keys + String SESSION_KEY_IS_SESSION = "isSession"; String SESSION_KEY_STATE = "state"; String SESSION_KEY_SESSION_ID = "sessionId"; String SESSION_KEY_SHAREABLE_ID = "shareableId"; diff --git a/briar-core/src/main/java/org/briarproject/briar/sharing/SharingManagerImpl.java b/briar-core/src/main/java/org/briarproject/briar/sharing/SharingManagerImpl.java index f26996438d7bce6a75e2e276f90f8020d4668c3c..fc57a6daace13c60480ea5e1c4e2518b4920ad1f 100644 --- a/briar-core/src/main/java/org/briarproject/briar/sharing/SharingManagerImpl.java +++ b/briar-core/src/main/java/org/briarproject/briar/sharing/SharingManagerImpl.java @@ -17,10 +17,13 @@ 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.Group; +import org.briarproject.bramble.api.sync.Group.Visibility; import org.briarproject.bramble.api.sync.GroupId; import org.briarproject.bramble.api.sync.Message; import org.briarproject.bramble.api.sync.MessageId; import org.briarproject.bramble.api.sync.MessageStatus; +import org.briarproject.bramble.api.versioning.ClientVersioningManager; +import org.briarproject.bramble.api.versioning.ClientVersioningManager.ClientVersioningHook; import org.briarproject.briar.api.client.MessageTracker; import org.briarproject.briar.api.client.SessionId; import org.briarproject.briar.api.sharing.InvitationMessage; @@ -52,8 +55,10 @@ import static org.briarproject.briar.sharing.State.SHARING; @NotNullByDefault abstract class SharingManagerImpl<S extends Shareable> extends ConversationClientImpl - implements SharingManager<S>, Client, ContactHook { + implements SharingManager<S>, Client, ContactHook, + ClientVersioningHook { + private final ClientVersioningManager clientVersioningManager; private final MessageParser<S> messageParser; private final SessionEncoder sessionEncoder; private final SessionParser sessionParser; @@ -62,12 +67,14 @@ abstract class SharingManagerImpl<S extends Shareable> private final InvitationFactory<S, ?> invitationFactory; SharingManagerImpl(DatabaseComponent db, ClientHelper clientHelper, + ClientVersioningManager clientVersioningManager, MetadataParser metadataParser, MessageParser<S> messageParser, SessionEncoder sessionEncoder, SessionParser sessionParser, MessageTracker messageTracker, ContactGroupFactory contactGroupFactory, ProtocolEngine<S> engine, InvitationFactory<S, ?> invitationFactory) { super(db, clientHelper, metadataParser, messageTracker); + this.clientVersioningManager = clientVersioningManager; this.messageParser = messageParser; this.sessionEncoder = sessionEncoder; this.sessionParser = sessionParser; @@ -78,48 +85,51 @@ abstract class SharingManagerImpl<S extends Shareable> protected abstract ClientId getClientId(); - protected abstract int getClientVersion(); + protected abstract int getMajorVersion(); + + protected abstract ClientId getShareableClientId(); + + protected abstract int getShareableMajorVersion(); @Override public void createLocalState(Transaction txn) throws DbException { // Create a local group to indicate that we've set this client up Group localGroup = contactGroupFactory.createLocalGroup(getClientId(), - getClientVersion()); + getMajorVersion()); if (db.containsGroup(txn, localGroup.getId())) return; db.addGroup(txn, localGroup); - // Ensure we've set things up for any pre-existing contacts + // Set things up for any pre-existing contacts for (Contact c : db.getContacts(txn)) addingContact(txn, c); } @Override public void addingContact(Transaction txn, Contact c) throws DbException { + // Create a group to share with the contact + Group g = getContactGroup(c); + db.addGroup(txn, g); + Visibility client = clientVersioningManager.getClientVisibility(txn, + c.getId(), getClientId(), getMajorVersion()); + db.setGroupVisibility(txn, c.getId(), g.getId(), client); + // Attach the contact ID to the group + BdfDictionary meta = new BdfDictionary(); + meta.put(GROUP_KEY_CONTACT_ID, c.getId().getInt()); try { - // Create a group to share with the contact - Group g = getContactGroup(c); - // Return if we've already set things up for this contact - if (db.containsGroup(txn, g.getId())) return; - // Store the group and share it with the contact - 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()); clientHelper.mergeGroupMetadata(txn, g.getId(), meta); } catch (FormatException e) { - throw new DbException(e); + throw new AssertionError(e); } } @Override public void removingContact(Transaction txn, Contact c) throws DbException { - // remove the contact group (all messages will be removed with it) + // Remove the contact group (all messages will be removed with it) db.removeGroup(txn, getContactGroup(c)); } @Override public Group getContactGroup(Contact c) { return contactGroupFactory.createContactGroup(getClientId(), - getClientVersion(), c); + getMajorVersion(), c); } @Override @@ -152,17 +162,21 @@ abstract class SharingManagerImpl<S extends Shareable> */ void preShareShareable(Transaction txn, Contact c, S shareable) throws DbException, FormatException { - // return if a session already exists with that Contact + // Return if a session already exists with the contact GroupId contactGroupId = getContactGroup(c).getId(); StoredSession existingSession = getSession(txn, contactGroupId, getSessionId(shareable.getId())); if (existingSession != null) return; - // add and shares the shareable with the Contact + // Add the shareable db.addGroup(txn, shareable.getGroup()); - db.setGroupVisibility(txn, c.getId(), shareable.getId(), SHARED); - // initialize session in sharing state + // Apply the client's visibility + Visibility client = clientVersioningManager.getClientVisibility(txn, + c.getId(), getShareableClientId(), getShareableMajorVersion()); + db.setGroupVisibility(txn, c.getId(), shareable.getId(), client); + + // Initialize session in sharing state Session session = new Session(SHARING, contactGroupId, shareable.getId(), null, null, 0, 0); MessageId storageId = createStorageId(txn, contactGroupId); @@ -446,6 +460,10 @@ abstract class SharingManagerImpl<S extends Shareable> private boolean canBeShared(Transaction txn, GroupId g, Contact c) throws DbException { + // The group can't be shared unless the contact supports the client + Visibility client = clientVersioningManager.getClientVisibility(txn, + c.getId(), getShareableClientId(), getShareableMajorVersion()); + if (client != SHARED) return false; GroupId contactGroupId = getContactGroup(c).getId(); SessionId sessionId = getSessionId(g); try { @@ -482,6 +500,51 @@ abstract class SharingManagerImpl<S extends Shareable> } } + @Override + public void onClientVisibilityChanging(Transaction txn, Contact c, + Visibility v) throws DbException { + // Apply the client's visibility to the contact group + Group g = getContactGroup(c); + db.setGroupVisibility(txn, c.getId(), g.getId(), v); + } + + ClientVersioningHook getShareableClientVersioningHook() { + return this::onShareableClientVisibilityChanging; + } + + // Versioning hook for the shareable client + private void onShareableClientVisibilityChanging(Transaction txn, Contact c, + Visibility client) throws DbException { + try { + Collection<Group> shareables = db.getGroups(txn, + getShareableClientId(), getShareableMajorVersion()); + Map<GroupId, Visibility> m = getPreferredVisibilities(txn, c); + for (Group g : shareables) { + Visibility preferred = m.get(g.getId()); + if (preferred == null) continue; // No session for this group + // Apply min of preferred visibility and client's visibility + Visibility min = Visibility.min(preferred, client); + db.setGroupVisibility(txn, c.getId(), g.getId(), min); + } + } catch (FormatException e) { + throw new DbException(e); + } + } + + private Map<GroupId, Visibility> getPreferredVisibilities(Transaction txn, + Contact c) throws DbException, FormatException { + GroupId contactGroupId = getContactGroup(c).getId(); + BdfDictionary query = sessionParser.getAllSessionsQuery(); + Map<MessageId, BdfDictionary> results = clientHelper + .getMessageMetadataAsDictionary(txn, contactGroupId, query); + Map<GroupId, Visibility> m = new HashMap<>(); + for (BdfDictionary d : results.values()) { + Session s = sessionParser.parseSession(contactGroupId, d); + m.put(s.getShareableId(), s.getState().getVisibility()); + } + return m; + } + private static class StoredSession { private final MessageId storageId; diff --git a/briar-core/src/main/java/org/briarproject/briar/sharing/SharingModule.java b/briar-core/src/main/java/org/briarproject/briar/sharing/SharingModule.java index 64d4d0d37d6fdeb10feb672c9bbe59b6ae2ad4dd..c5c1baccef09ed2fafe1ae89e96431d2ce1c1d6d 100644 --- a/briar-core/src/main/java/org/briarproject/briar/sharing/SharingModule.java +++ b/briar-core/src/main/java/org/briarproject/briar/sharing/SharingModule.java @@ -6,6 +6,7 @@ import org.briarproject.bramble.api.data.MetadataEncoder; import org.briarproject.bramble.api.lifecycle.LifecycleManager; import org.briarproject.bramble.api.sync.ValidationManager; import org.briarproject.bramble.api.system.Clock; +import org.briarproject.bramble.api.versioning.ClientVersioningManager; import org.briarproject.briar.api.blog.Blog; import org.briarproject.briar.api.blog.BlogFactory; import org.briarproject.briar.api.blog.BlogInvitationResponse; @@ -59,11 +60,11 @@ public class SharingModule { ValidationManager validationManager, MessageEncoder messageEncoder, ClientHelper clientHelper, MetadataEncoder metadataEncoder, Clock clock, BlogFactory blogFactory) { - BlogSharingValidator validator = - new BlogSharingValidator(messageEncoder, clientHelper, - metadataEncoder, clock, blogFactory); + BlogSharingValidator validator = new BlogSharingValidator( + messageEncoder, clientHelper, metadataEncoder, clock, + blogFactory); validationManager.registerMessageValidator(BlogSharingManager.CLIENT_ID, - validator); + BlogSharingManager.MAJOR_VERSION, validator); return validator; } @@ -73,14 +74,23 @@ public class SharingModule { LifecycleManager lifecycleManager, ContactManager contactManager, ValidationManager validationManager, ConversationManager conversationManager, BlogManager blogManager, + ClientVersioningManager clientVersioningManager, BlogSharingManagerImpl blogSharingManager) { lifecycleManager.registerClient(blogSharingManager); contactManager.registerContactHook(blogSharingManager); validationManager.registerIncomingMessageHook( - BlogSharingManager.CLIENT_ID, blogSharingManager); + BlogSharingManager.CLIENT_ID, BlogSharingManager.MAJOR_VERSION, + blogSharingManager); conversationManager.registerConversationClient(blogSharingManager); blogManager.registerRemoveBlogHook(blogSharingManager); - + clientVersioningManager.registerClient(BlogSharingManager.CLIENT_ID, + BlogSharingManager.MAJOR_VERSION, + BlogSharingManager.MINOR_VERSION, blogSharingManager); + // The blog sharing manager handles client visibility changes for the + // blog manager + clientVersioningManager.registerClient(BlogManager.CLIENT_ID, + BlogManager.MAJOR_VERSION, BlogManager.MINOR_VERSION, + blogSharingManager.getShareableClientVersioningHook()); return blogSharingManager; } @@ -108,12 +118,12 @@ public class SharingModule { ValidationManager validationManager, MessageEncoder messageEncoder, ClientHelper clientHelper, MetadataEncoder metadataEncoder, Clock clock, ForumFactory forumFactory) { - ForumSharingValidator validator = - new ForumSharingValidator(messageEncoder, clientHelper, - metadataEncoder, clock, forumFactory); - validationManager - .registerMessageValidator(ForumSharingManager.CLIENT_ID, - validator); + ForumSharingValidator validator = new ForumSharingValidator( + messageEncoder, clientHelper, metadataEncoder, clock, + forumFactory); + validationManager.registerMessageValidator( + ForumSharingManager.CLIENT_ID, + ForumSharingManager.MAJOR_VERSION, validator); return validator; } @@ -123,15 +133,23 @@ public class SharingModule { LifecycleManager lifecycleManager, ContactManager contactManager, ValidationManager validationManager, ConversationManager conversationManager, ForumManager forumManager, + ClientVersioningManager clientVersioningManager, ForumSharingManagerImpl forumSharingManager) { - lifecycleManager.registerClient(forumSharingManager); contactManager.registerContactHook(forumSharingManager); validationManager.registerIncomingMessageHook( - ForumSharingManager.CLIENT_ID, forumSharingManager); + ForumSharingManager.CLIENT_ID, + ForumSharingManager.MAJOR_VERSION, forumSharingManager); conversationManager.registerConversationClient(forumSharingManager); forumManager.registerRemoveForumHook(forumSharingManager); - + clientVersioningManager.registerClient(ForumSharingManager.CLIENT_ID, + ForumSharingManager.MAJOR_VERSION, + ForumSharingManager.MINOR_VERSION, forumSharingManager); + // The forum sharing manager handles client visibility changes for the + // forum manager + clientVersioningManager.registerClient(ForumManager.CLIENT_ID, + ForumManager.MAJOR_VERSION, ForumManager.MINOR_VERSION, + forumSharingManager.getShareableClientVersioningHook()); return forumSharingManager; } diff --git a/briar-core/src/main/java/org/briarproject/briar/sharing/State.java b/briar-core/src/main/java/org/briarproject/briar/sharing/State.java index ade7d7a248787123e6a10bd8cfcc43cc4ba51210..2d0ed97660d9af2cbb2c79b4e47cde5e2537b01c 100644 --- a/briar-core/src/main/java/org/briarproject/briar/sharing/State.java +++ b/briar-core/src/main/java/org/briarproject/briar/sharing/State.java @@ -2,26 +2,41 @@ package org.briarproject.briar.sharing; import org.briarproject.bramble.api.FormatException; import org.briarproject.bramble.api.nullsafety.NotNullByDefault; +import org.briarproject.bramble.api.sync.Group.Visibility; import javax.annotation.concurrent.Immutable; +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; + @Immutable @NotNullByDefault enum State { - START(0), LOCAL_INVITED(1), REMOTE_INVITED(2), SHARING(3), LOCAL_LEFT(4), - REMOTE_HANGING(5); + START(0, INVISIBLE), + LOCAL_INVITED(1, INVISIBLE), + REMOTE_INVITED(2, VISIBLE), + SHARING(3, SHARED), + LOCAL_LEFT(4, INVISIBLE), + REMOTE_HANGING(5, INVISIBLE); private final int value; + private final Visibility visibility; - State(int value) { + State(int value, Visibility visibility) { this.value = value; + this.visibility = visibility; } public int getValue() { return value; } + public Visibility getVisibility() { + return visibility; + } + public boolean canInvite() { return this == START; } diff --git a/briar-core/src/test/java/org/briarproject/briar/blog/BlogManagerImplTest.java b/briar-core/src/test/java/org/briarproject/briar/blog/BlogManagerImplTest.java index bbd905705a69572321662a4be9e79edbc69b9c80..524b7ea39b3909758252bd5595c396b78ba3fa05 100644 --- a/briar-core/src/test/java/org/briarproject/briar/blog/BlogManagerImplTest.java +++ b/briar-core/src/test/java/org/briarproject/briar/blog/BlogManagerImplTest.java @@ -14,6 +14,7 @@ import org.briarproject.bramble.api.db.Transaction; import org.briarproject.bramble.api.identity.Author; import org.briarproject.bramble.api.identity.IdentityManager; import org.briarproject.bramble.api.identity.LocalAuthor; +import org.briarproject.bramble.api.sync.Group; import org.briarproject.bramble.api.sync.Message; import org.briarproject.bramble.api.sync.MessageId; import org.briarproject.briar.api.blog.Blog; @@ -49,6 +50,7 @@ import static org.briarproject.briar.api.blog.BlogConstants.KEY_TIME_RECEIVED; import static org.briarproject.briar.api.blog.BlogConstants.KEY_TYPE; import static org.briarproject.briar.api.blog.BlogConstants.MAX_BLOG_COMMENT_LENGTH; import static org.briarproject.briar.api.blog.BlogManager.CLIENT_ID; +import static org.briarproject.briar.api.blog.BlogManager.MAJOR_VERSION; import static org.briarproject.briar.api.blog.MessageType.COMMENT; import static org.briarproject.briar.api.blog.MessageType.POST; import static org.briarproject.briar.api.blog.MessageType.WRAPPED_COMMENT; @@ -866,7 +868,8 @@ public class BlogManagerImplTest extends BriarTestCase { } private Blog createBlog(LocalAuthor localAuthor, boolean rssFeed) { - return new Blog(getGroup(CLIENT_ID), localAuthor, rssFeed); + Group group = getGroup(CLIENT_ID, MAJOR_VERSION); + return new Blog(group, localAuthor, rssFeed); } private BdfList authorToBdfList(Author a) { diff --git a/briar-core/src/test/java/org/briarproject/briar/blog/BlogPostValidatorTest.java b/briar-core/src/test/java/org/briarproject/briar/blog/BlogPostValidatorTest.java index f2ca321d7f5c872ed0824db6e06dc1a7e4a2f6ca..7d00fb4138d7c4ed580f4459e79cdd4c9f3838ff 100644 --- a/briar-core/src/test/java/org/briarproject/briar/blog/BlogPostValidatorTest.java +++ b/briar-core/src/test/java/org/briarproject/briar/blog/BlogPostValidatorTest.java @@ -36,7 +36,7 @@ import static org.briarproject.briar.api.blog.BlogConstants.KEY_PARENT_MSG_ID; import static org.briarproject.briar.api.blog.BlogConstants.KEY_READ; import static org.briarproject.briar.api.blog.BlogConstants.KEY_RSS_FEED; import static org.briarproject.briar.api.blog.BlogManager.CLIENT_ID; -import static org.briarproject.briar.api.blog.BlogManager.CLIENT_VERSION; +import static org.briarproject.briar.api.blog.BlogManager.MAJOR_VERSION; import static org.briarproject.briar.api.blog.BlogPostFactory.SIGNING_LABEL_COMMENT; import static org.briarproject.briar.api.blog.BlogPostFactory.SIGNING_LABEL_POST; import static org.briarproject.briar.api.blog.MessageType.COMMENT; @@ -64,7 +64,7 @@ public class BlogPostValidatorTest extends BriarTestCase { private final String body = getRandomString(42); public BlogPostValidatorTest() { - group = getGroup(CLIENT_ID); + group = getGroup(CLIENT_ID, MAJOR_VERSION); descriptor = group.getDescriptor(); author = getAuthor(); authorList = BdfList.of( @@ -206,7 +206,7 @@ public class BlogPostValidatorTest extends BriarTestCase { byte[] originalBody = getRandomBytes(42); context.checking(new Expectations() {{ - oneOf(groupFactory).createGroup(CLIENT_ID, CLIENT_VERSION, + oneOf(groupFactory).createGroup(CLIENT_ID, MAJOR_VERSION, descriptor); will(returnValue(b.getGroup())); oneOf(blogFactory).parseBlog(b.getGroup()); @@ -250,7 +250,7 @@ public class BlogPostValidatorTest extends BriarTestCase { byte[] originalBody = getRandomBytes(42); context.checking(new Expectations() {{ - oneOf(groupFactory).createGroup(CLIENT_ID, CLIENT_VERSION, + oneOf(groupFactory).createGroup(CLIENT_ID, MAJOR_VERSION, descriptor); will(returnValue(blog.getGroup())); oneOf(clientHelper).toByteArray(originalList); diff --git a/briar-core/src/test/java/org/briarproject/briar/feed/FeedManagerImplTest.java b/briar-core/src/test/java/org/briarproject/briar/feed/FeedManagerImplTest.java index 05a17fee5f776a51997a6a92cd93036b5d936108..4f104feb9b46016c44e344d76267669755657174 100644 --- a/briar-core/src/test/java/org/briarproject/briar/feed/FeedManagerImplTest.java +++ b/briar-core/src/test/java/org/briarproject/briar/feed/FeedManagerImplTest.java @@ -43,7 +43,7 @@ import static org.briarproject.bramble.test.TestUtils.getRandomBytes; import static org.briarproject.bramble.test.TestUtils.getRandomId; import static org.briarproject.briar.api.feed.FeedConstants.KEY_FEEDS; import static org.briarproject.briar.api.feed.FeedManager.CLIENT_ID; -import static org.briarproject.briar.api.feed.FeedManager.CLIENT_VERSION; +import static org.briarproject.briar.api.feed.FeedManager.MAJOR_VERSION; public class FeedManagerImplTest extends BrambleMockTestCase { @@ -61,9 +61,10 @@ public class FeedManagerImplTest extends BrambleMockTestCase { private final Clock clock = context.mock(Clock.class); private final Dns noDnsLookups = context.mock(Dns.class); - private final Group localGroup = getGroup(CLIENT_ID); + private final Group localGroup = getGroup(CLIENT_ID, MAJOR_VERSION); private final GroupId localGroupId = localGroup.getId(); - private final Group blogGroup = getGroup(BlogManager.CLIENT_ID); + private final Group blogGroup = + getGroup(BlogManager.CLIENT_ID, BlogManager.MAJOR_VERSION); private final GroupId blogGroupId = blogGroup.getId(); private final LocalAuthor localAuthor = getLocalAuthor(); private final Blog blog = new Blog(blogGroup, localAuthor, true); @@ -131,7 +132,7 @@ public class FeedManagerImplTest extends BrambleMockTestCase { private void expectGetLocalGroup() { context.checking(new Expectations() {{ oneOf(contactGroupFactory).createLocalGroup(CLIENT_ID, - CLIENT_VERSION); + MAJOR_VERSION); will(returnValue(localGroup)); }}); } diff --git a/briar-core/src/test/java/org/briarproject/briar/feed/FeedManagerIntegrationTest.java b/briar-core/src/test/java/org/briarproject/briar/feed/FeedManagerIntegrationTest.java index 2fc24aae58f3f082491e5ca6011ca7ca05d9100a..24c7334e72210cc96fa46ac4dfb4a379d46542ba 100644 --- a/briar-core/src/test/java/org/briarproject/briar/feed/FeedManagerIntegrationTest.java +++ b/briar-core/src/test/java/org/briarproject/briar/feed/FeedManagerIntegrationTest.java @@ -2,7 +2,7 @@ package org.briarproject.briar.feed; import org.briarproject.bramble.api.lifecycle.LifecycleManager; import org.briarproject.bramble.contact.ContactModule; -import org.briarproject.bramble.crypto.CryptoModule; +import org.briarproject.bramble.crypto.CryptoExecutorModule; import org.briarproject.bramble.identity.IdentityModule; import org.briarproject.bramble.lifecycle.LifecycleModule; import org.briarproject.bramble.sync.SyncModule; @@ -10,6 +10,7 @@ import org.briarproject.bramble.system.SystemModule; import org.briarproject.bramble.test.TestDatabaseModule; import org.briarproject.bramble.test.TestUtils; import org.briarproject.bramble.transport.TransportModule; +import org.briarproject.bramble.versioning.VersioningModule; import org.briarproject.briar.api.blog.Blog; import org.briarproject.briar.api.blog.BlogManager; import org.briarproject.briar.api.blog.BlogPostHeader; @@ -113,15 +114,16 @@ public class FeedManagerIntegrationTest extends BriarTestCase { protected void injectEagerSingletons( FeedManagerIntegrationTestComponent component) { - component.inject(new FeedModule.EagerSingletons()); component.inject(new BlogModule.EagerSingletons()); component.inject(new ContactModule.EagerSingletons()); - component.inject(new CryptoModule.EagerSingletons()); + component.inject(new CryptoExecutorModule.EagerSingletons()); + component.inject(new FeedModule.EagerSingletons()); component.inject(new IdentityModule.EagerSingletons()); component.inject(new LifecycleModule.EagerSingletons()); component.inject(new SyncModule.EagerSingletons()); component.inject(new SystemModule.EagerSingletons()); component.inject(new TransportModule.EagerSingletons()); + component.inject(new VersioningModule.EagerSingletons()); } } diff --git a/briar-core/src/test/java/org/briarproject/briar/feed/FeedManagerIntegrationTestComponent.java b/briar-core/src/test/java/org/briarproject/briar/feed/FeedManagerIntegrationTestComponent.java index 0a8fbfbe62db6c661dcfbe89008aa432ede1b055..76445fe03d18bc67993a40f2aa2a2a9d3ccf6225 100644 --- a/briar-core/src/test/java/org/briarproject/briar/feed/FeedManagerIntegrationTestComponent.java +++ b/briar-core/src/test/java/org/briarproject/briar/feed/FeedManagerIntegrationTestComponent.java @@ -3,6 +3,7 @@ package org.briarproject.briar.feed; import org.briarproject.bramble.api.lifecycle.LifecycleManager; import org.briarproject.bramble.client.ClientModule; import org.briarproject.bramble.contact.ContactModule; +import org.briarproject.bramble.crypto.CryptoExecutorModule; import org.briarproject.bramble.crypto.CryptoModule; import org.briarproject.bramble.data.DataModule; import org.briarproject.bramble.db.DatabaseModule; @@ -16,6 +17,7 @@ import org.briarproject.bramble.test.TestPluginConfigModule; import org.briarproject.bramble.test.TestSecureRandomModule; import org.briarproject.bramble.test.TestSocksModule; import org.briarproject.bramble.transport.TransportModule; +import org.briarproject.bramble.versioning.VersioningModule; import org.briarproject.briar.api.blog.BlogManager; import org.briarproject.briar.api.feed.FeedManager; import org.briarproject.briar.blog.BlogModule; @@ -33,32 +35,34 @@ import dagger.Component; TestSecureRandomModule.class, TestSocksModule.class, TestDnsModule.class, - LifecycleModule.class, BriarClientModule.class, ClientModule.class, ContactModule.class, CryptoModule.class, + CryptoExecutorModule.class, BlogModule.class, FeedModule.class, DataModule.class, DatabaseModule.class, EventModule.class, IdentityModule.class, + LifecycleModule.class, SyncModule.class, SystemModule.class, - TransportModule.class + TransportModule.class, + VersioningModule.class }) interface FeedManagerIntegrationTestComponent { void inject(FeedManagerIntegrationTest testCase); - void inject(FeedModule.EagerSingletons init); - void inject(BlogModule.EagerSingletons init); void inject(ContactModule.EagerSingletons init); - void inject(CryptoModule.EagerSingletons init); + void inject(CryptoExecutorModule.EagerSingletons init); + + void inject(FeedModule.EagerSingletons init); void inject(IdentityModule.EagerSingletons init); @@ -70,6 +74,8 @@ interface FeedManagerIntegrationTestComponent { void inject(TransportModule.EagerSingletons init); + void inject(VersioningModule.EagerSingletons init); + LifecycleManager getLifecycleManager(); FeedManager getFeedManager(); diff --git a/briar-core/src/test/java/org/briarproject/briar/introduction/IntroductionIntegrationTest.java b/briar-core/src/test/java/org/briarproject/briar/introduction/IntroductionIntegrationTest.java index 7441186e8ba3bc017e294bf826ee11bf7596085a..f860e702e0ad032579ef1e528b2377879d8c083d 100644 --- a/briar-core/src/test/java/org/briarproject/briar/introduction/IntroductionIntegrationTest.java +++ b/briar-core/src/test/java/org/briarproject/briar/introduction/IntroductionIntegrationTest.java @@ -47,7 +47,7 @@ import static org.briarproject.bramble.test.TestUtils.getRandomBytes; import static org.briarproject.bramble.test.TestUtils.getTransportProperties; import static org.briarproject.bramble.test.TestUtils.getTransportPropertiesMap; import static org.briarproject.briar.api.introduction.IntroductionManager.CLIENT_ID; -import static org.briarproject.briar.api.introduction.IntroductionManager.CLIENT_VERSION; +import static org.briarproject.briar.api.introduction.IntroductionManager.MAJOR_VERSION; import static org.briarproject.briar.introduction.IntroduceeState.AWAIT_RESPONSES; import static org.briarproject.briar.introduction.IntroduceeState.LOCAL_DECLINED; import static org.briarproject.briar.introduction.IntroducerState.A_DECLINED; @@ -1284,7 +1284,7 @@ public class IntroductionIntegrationTest } private Group getLocalGroup() { - return contactGroupFactory.createLocalGroup(CLIENT_ID, CLIENT_VERSION); + return contactGroupFactory.createLocalGroup(CLIENT_ID, MAJOR_VERSION); } } diff --git a/briar-core/src/test/java/org/briarproject/briar/introduction/IntroductionIntegrationTestComponent.java b/briar-core/src/test/java/org/briarproject/briar/introduction/IntroductionIntegrationTestComponent.java index 3a90d7d148af021f0744cf0a3f67502c324e2f78..160d80e534c9d3b234835ad0f08b8293c4724056 100644 --- a/briar-core/src/test/java/org/briarproject/briar/introduction/IntroductionIntegrationTestComponent.java +++ b/briar-core/src/test/java/org/briarproject/briar/introduction/IntroductionIntegrationTestComponent.java @@ -2,6 +2,7 @@ package org.briarproject.briar.introduction; import org.briarproject.bramble.client.ClientModule; import org.briarproject.bramble.contact.ContactModule; +import org.briarproject.bramble.crypto.CryptoExecutorModule; import org.briarproject.bramble.crypto.CryptoModule; import org.briarproject.bramble.data.DataModule; import org.briarproject.bramble.db.DatabaseModule; @@ -15,6 +16,7 @@ import org.briarproject.bramble.test.TestDatabaseModule; import org.briarproject.bramble.test.TestPluginConfigModule; import org.briarproject.bramble.test.TestSecureRandomModule; import org.briarproject.bramble.transport.TransportModule; +import org.briarproject.bramble.versioning.VersioningModule; import org.briarproject.briar.blog.BlogModule; import org.briarproject.briar.client.BriarClientModule; import org.briarproject.briar.forum.ForumModule; @@ -38,6 +40,7 @@ import dagger.Component; ClientModule.class, ContactModule.class, CryptoModule.class, + CryptoExecutorModule.class, DataModule.class, DatabaseModule.class, EventModule.class, @@ -52,7 +55,8 @@ import dagger.Component; SharingModule.class, SyncModule.class, SystemModule.class, - TransportModule.class + TransportModule.class, + VersioningModule.class }) interface IntroductionIntegrationTestComponent extends BriarIntegrationTestComponent { diff --git a/briar-core/src/test/java/org/briarproject/briar/introduction/MessageEncoderParserIntegrationTest.java b/briar-core/src/test/java/org/briarproject/briar/introduction/MessageEncoderParserIntegrationTest.java index 7b15b6ab43e665f52c99af222f833298a758c161..a29f7944577f3b494ba3eb50a6ea44dcf26290c1 100644 --- a/briar-core/src/test/java/org/briarproject/briar/introduction/MessageEncoderParserIntegrationTest.java +++ b/briar-core/src/test/java/org/briarproject/briar/introduction/MessageEncoderParserIntegrationTest.java @@ -25,12 +25,14 @@ import javax.inject.Inject; import static org.briarproject.bramble.api.crypto.CryptoConstants.MAC_BYTES; import static org.briarproject.bramble.api.crypto.CryptoConstants.MAX_SIGNATURE_BYTES; import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_PUBLIC_KEY_LENGTH; +import static org.briarproject.bramble.test.TestUtils.getGroup; import static org.briarproject.bramble.test.TestUtils.getRandomBytes; import static org.briarproject.bramble.test.TestUtils.getRandomId; import static org.briarproject.bramble.test.TestUtils.getTransportPropertiesMap; import static org.briarproject.bramble.util.StringUtils.getRandomString; import static org.briarproject.briar.api.introduction.IntroductionConstants.MAX_REQUEST_MESSAGE_LENGTH; import static org.briarproject.briar.api.introduction.IntroductionManager.CLIENT_ID; +import static org.briarproject.briar.api.introduction.IntroductionManager.MAJOR_VERSION; import static org.briarproject.briar.introduction.MessageType.ABORT; import static org.briarproject.briar.introduction.MessageType.REQUEST; import static org.briarproject.briar.test.BriarTestUtils.getRealAuthor; @@ -57,8 +59,8 @@ public class MessageEncoderParserIntegrationTest extends BrambleTestCase { private final MessageParser messageParser; private final IntroductionValidator validator; - private final GroupId groupId = new GroupId(getRandomId()); - private final Group group = new Group(groupId, CLIENT_ID, getRandomId()); + private final Group group = getGroup(CLIENT_ID, MAJOR_VERSION); + private final GroupId groupId = group.getId(); private final long timestamp = 42L; private final SessionId sessionId = new SessionId(getRandomId()); private final MessageId previousMsgId = new MessageId(getRandomId()); diff --git a/briar-core/src/test/java/org/briarproject/briar/messaging/MessageSizeIntegrationTest.java b/briar-core/src/test/java/org/briarproject/briar/messaging/MessageSizeIntegrationTest.java index b56d42d7fa993e4929361bf760faa85845eae326..c5a1f1851cb99e3acf668e9d400cd6d031e465c1 100644 --- a/briar-core/src/test/java/org/briarproject/briar/messaging/MessageSizeIntegrationTest.java +++ b/briar-core/src/test/java/org/briarproject/briar/messaging/MessageSizeIntegrationTest.java @@ -7,11 +7,18 @@ import org.briarproject.bramble.api.identity.AuthorFactory; import org.briarproject.bramble.api.identity.LocalAuthor; import org.briarproject.bramble.api.sync.GroupId; import org.briarproject.bramble.api.sync.MessageId; +import org.briarproject.bramble.contact.ContactModule; +import org.briarproject.bramble.crypto.CryptoExecutorModule; +import org.briarproject.bramble.identity.IdentityModule; +import org.briarproject.bramble.sync.SyncModule; import org.briarproject.bramble.system.SystemModule; +import org.briarproject.bramble.transport.TransportModule; +import org.briarproject.bramble.versioning.VersioningModule; import org.briarproject.briar.api.forum.ForumPost; import org.briarproject.briar.api.forum.ForumPostFactory; import org.briarproject.briar.api.messaging.PrivateMessage; import org.briarproject.briar.api.messaging.PrivateMessageFactory; +import org.briarproject.briar.forum.ForumModule; import org.briarproject.briar.test.BriarTestCase; import org.junit.Test; @@ -85,6 +92,14 @@ public class MessageSizeIntegrationTest extends BriarTestCase { private static void injectEagerSingletons( MessageSizeIntegrationTestComponent component) { + component.inject(new ContactModule.EagerSingletons()); + component.inject(new CryptoExecutorModule.EagerSingletons()); + component.inject(new ForumModule.EagerSingletons()); + component.inject(new IdentityModule.EagerSingletons()); + component.inject(new MessagingModule.EagerSingletons()); + component.inject(new SyncModule.EagerSingletons()); component.inject(new SystemModule.EagerSingletons()); + component.inject(new TransportModule.EagerSingletons()); + component.inject(new VersioningModule.EagerSingletons()); } } diff --git a/briar-core/src/test/java/org/briarproject/briar/messaging/MessageSizeIntegrationTestComponent.java b/briar-core/src/test/java/org/briarproject/briar/messaging/MessageSizeIntegrationTestComponent.java index 42b27da6f3c4265d021bb376316f65cc2f1944ab..01caa2e4b442bd8a1aeec9ac2eb5dabd87ed6b8b 100644 --- a/briar-core/src/test/java/org/briarproject/briar/messaging/MessageSizeIntegrationTestComponent.java +++ b/briar-core/src/test/java/org/briarproject/briar/messaging/MessageSizeIntegrationTestComponent.java @@ -1,6 +1,8 @@ package org.briarproject.briar.messaging; import org.briarproject.bramble.client.ClientModule; +import org.briarproject.bramble.contact.ContactModule; +import org.briarproject.bramble.crypto.CryptoExecutorModule; import org.briarproject.bramble.crypto.CryptoModule; import org.briarproject.bramble.data.DataModule; import org.briarproject.bramble.db.DatabaseModule; @@ -10,7 +12,10 @@ import org.briarproject.bramble.sync.SyncModule; import org.briarproject.bramble.system.SystemModule; import org.briarproject.bramble.test.TestDatabaseModule; import org.briarproject.bramble.test.TestLifecycleModule; +import org.briarproject.bramble.test.TestPluginConfigModule; import org.briarproject.bramble.test.TestSecureRandomModule; +import org.briarproject.bramble.transport.TransportModule; +import org.briarproject.bramble.versioning.VersioningModule; import org.briarproject.briar.client.BriarClientModule; import org.briarproject.briar.forum.ForumModule; @@ -22,10 +27,13 @@ import dagger.Component; @Component(modules = { TestDatabaseModule.class, TestLifecycleModule.class, + TestPluginConfigModule.class, TestSecureRandomModule.class, BriarClientModule.class, ClientModule.class, + ContactModule.class, CryptoModule.class, + CryptoExecutorModule.class, DataModule.class, DatabaseModule.class, EventModule.class, @@ -33,11 +41,29 @@ import dagger.Component; IdentityModule.class, MessagingModule.class, SyncModule.class, - SystemModule.class + SystemModule.class, + TransportModule.class, + VersioningModule.class }) interface MessageSizeIntegrationTestComponent { void inject(MessageSizeIntegrationTest testCase); + void inject(ContactModule.EagerSingletons init); + + void inject(CryptoExecutorModule.EagerSingletons init); + + void inject(ForumModule.EagerSingletons init); + + void inject(IdentityModule.EagerSingletons init); + + void inject(MessagingModule.EagerSingletons init); + + void inject(SyncModule.EagerSingletons init); + void inject(SystemModule.EagerSingletons init); + + void inject(TransportModule.EagerSingletons init); + + void inject(VersioningModule.EagerSingletons init); } diff --git a/briar-core/src/test/java/org/briarproject/briar/messaging/SimplexMessagingIntegrationTest.java b/briar-core/src/test/java/org/briarproject/briar/messaging/SimplexMessagingIntegrationTest.java index 8967961ceef7703a7bb38d07b0d346a0b1e61560..879b763daec59a93251164b0cfbdaa151b3cde66 100644 --- a/briar-core/src/test/java/org/briarproject/briar/messaging/SimplexMessagingIntegrationTest.java +++ b/briar-core/src/test/java/org/briarproject/briar/messaging/SimplexMessagingIntegrationTest.java @@ -5,6 +5,7 @@ import org.briarproject.bramble.api.contact.ContactManager; import org.briarproject.bramble.api.crypto.SecretKey; import org.briarproject.bramble.api.event.Event; import org.briarproject.bramble.api.event.EventListener; +import org.briarproject.bramble.api.identity.Author; import org.briarproject.bramble.api.identity.IdentityManager; import org.briarproject.bramble.api.identity.LocalAuthor; import org.briarproject.bramble.api.lifecycle.LifecycleManager; @@ -12,17 +13,23 @@ import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.sync.GroupId; import org.briarproject.bramble.api.sync.SyncSession; import org.briarproject.bramble.api.sync.SyncSessionFactory; -import org.briarproject.bramble.api.sync.event.MessageAddedEvent; import org.briarproject.bramble.api.transport.KeyManager; import org.briarproject.bramble.api.transport.StreamContext; import org.briarproject.bramble.api.transport.StreamReaderFactory; import org.briarproject.bramble.api.transport.StreamWriterFactory; +import org.briarproject.bramble.contact.ContactModule; +import org.briarproject.bramble.identity.IdentityModule; +import org.briarproject.bramble.lifecycle.LifecycleModule; +import org.briarproject.bramble.sync.SyncModule; import org.briarproject.bramble.system.SystemModule; import org.briarproject.bramble.test.TestDatabaseModule; import org.briarproject.bramble.test.TestUtils; +import org.briarproject.bramble.transport.TransportModule; +import org.briarproject.bramble.versioning.VersioningModule; import org.briarproject.briar.api.messaging.MessagingManager; import org.briarproject.briar.api.messaging.PrivateMessage; import org.briarproject.briar.api.messaging.PrivateMessageFactory; +import org.briarproject.briar.api.messaging.event.PrivateMessageReceivedEvent; import org.briarproject.briar.test.BriarTestCase; import org.junit.After; import org.junit.Before; @@ -41,7 +48,6 @@ import static org.briarproject.bramble.test.TestUtils.getLocalAuthor; import static org.briarproject.bramble.test.TestUtils.getSecretKey; import static org.briarproject.bramble.test.TestUtils.getTestDirectory; import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; @@ -71,103 +77,105 @@ public class SimplexMessagingIntegrationTest extends BriarTestCase { @Test public void testWriteAndRead() throws Exception { - read(write()); + // Set up the devices and get the contact IDs + ContactId bobId = setUp(alice, aliceAuthor, bobAuthor, true); + ContactId aliceId = setUp(bob, bobAuthor, aliceAuthor, false); + // Add a private message listener + PrivateMessageListener listener = new PrivateMessageListener(); + bob.getEventBus().addListener(listener); + // Alice sends a private message to Bob + sendMessage(alice, bobId); + // Send three simplex streams to exchange client versions and the + // private message + read(bob, aliceId, write(alice, bobId)); + read(alice, bobId, write(bob, aliceId)); + read(bob, aliceId, write(alice, bobId)); + // Tear down the devices + tearDown(alice); + tearDown(bob); + // Bob should have received the private message + assertTrue(listener.messageAdded); } - - private byte[] write() throws Exception { - // Instantiate Alice's services - LifecycleManager lifecycleManager = alice.getLifecycleManager(); - IdentityManager identityManager = alice.getIdentityManager(); - ContactManager contactManager = alice.getContactManager(); - MessagingManager messagingManager = alice.getMessagingManager(); - KeyManager keyManager = alice.getKeyManager(); - PrivateMessageFactory privateMessageFactory = - alice.getPrivateMessageFactory(); - StreamWriterFactory streamWriterFactory = - alice.getStreamWriterFactory(); - SyncSessionFactory syncSessionFactory = alice.getSyncSessionFactory(); - + private ContactId setUp(SimplexMessagingIntegrationTestComponent device, + LocalAuthor local, Author remote, boolean alice) throws Exception { // Start the lifecycle manager + LifecycleManager lifecycleManager = device.getLifecycleManager(); lifecycleManager.startServices(null); lifecycleManager.waitForStartup(); - // Add an identity for Alice - identityManager.registerLocalAuthor(aliceAuthor); - // Add Bob as a contact - ContactId contactId = contactManager.addContact(bobAuthor, - aliceAuthor.getId(), master, timestamp, true, true, true); + // Add an identity for the user + IdentityManager identityManager = device.getIdentityManager(); + identityManager.registerLocalAuthor(local); + // Add the other user as a contact + ContactManager contactManager = device.getContactManager(); + return contactManager.addContact(remote, local.getId(), master, + timestamp, alice, true, true); + } + private void sendMessage(SimplexMessagingIntegrationTestComponent device, + ContactId contactId) throws Exception { // Send Bob a message + MessagingManager messagingManager = device.getMessagingManager(); GroupId groupId = messagingManager.getConversationId(contactId); - String body = "Hi Bob!"; + PrivateMessageFactory privateMessageFactory = + device.getPrivateMessageFactory(); PrivateMessage message = privateMessageFactory.createPrivateMessage( - groupId, timestamp, body); + groupId, System.currentTimeMillis(), "Hi!"); messagingManager.addLocalMessage(message); - // Get a stream context - StreamContext ctx = keyManager.getStreamContext(contactId, - TRANSPORT_ID); - assertNotNull(ctx); - // Create a stream writer - ByteArrayOutputStream out = new ByteArrayOutputStream(); - OutputStream streamWriter = streamWriterFactory.createStreamWriter( - out, ctx); - // Create an outgoing sync session - SyncSession session = syncSessionFactory.createSimplexOutgoingSession( - contactId, MAX_LATENCY, streamWriter); - // Write whatever needs to be written - session.run(); - streamWriter.close(); - - // Clean up - lifecycleManager.stopServices(); - lifecycleManager.waitForShutdown(); - - // Return the contents of the stream - return out.toByteArray(); } - private void read(byte[] stream) throws Exception { - // Instantiate Bob's services - LifecycleManager lifecycleManager = bob.getLifecycleManager(); - IdentityManager identityManager = bob.getIdentityManager(); - ContactManager contactManager = bob.getContactManager(); - KeyManager keyManager = bob.getKeyManager(); - StreamReaderFactory streamReaderFactory = bob.getStreamReaderFactory(); - SyncSessionFactory syncSessionFactory = bob.getSyncSessionFactory(); - - // Start the lifecyle manager - lifecycleManager.startServices(null); - lifecycleManager.waitForStartup(); - // Add an identity for Bob - identityManager.registerLocalAuthor(bobAuthor); - // Add Alice as a contact - ContactId contactId = contactManager.addContact(aliceAuthor, - bobAuthor.getId(), master, timestamp, false, true, true); - // Set up an event listener - MessageListener listener = new MessageListener(); - bob.getEventBus().addListener(listener); + private void read(SimplexMessagingIntegrationTestComponent device, + ContactId contactId, byte[] stream) throws Exception { // Read and recognise the tag ByteArrayInputStream in = new ByteArrayInputStream(stream); byte[] tag = new byte[TAG_LENGTH]; int read = in.read(tag); assertEquals(tag.length, read); + KeyManager keyManager = device.getKeyManager(); StreamContext ctx = keyManager.getStreamContext(TRANSPORT_ID, tag); assertNotNull(ctx); // Create a stream reader + StreamReaderFactory streamReaderFactory = + device.getStreamReaderFactory(); InputStream streamReader = streamReaderFactory.createStreamReader( in, ctx); // Create an incoming sync session + SyncSessionFactory syncSessionFactory = device.getSyncSessionFactory(); SyncSession session = syncSessionFactory.createIncomingSession( contactId, streamReader); - // No messages should have been added yet - assertFalse(listener.messageAdded); // Read whatever needs to be read session.run(); streamReader.close(); - // The private message from Alice should have been added - assertTrue(listener.messageAdded); + } + + private byte[] write(SimplexMessagingIntegrationTestComponent device, + ContactId contactId) throws Exception { + ByteArrayOutputStream out = new ByteArrayOutputStream(); + // Get a stream context + KeyManager keyManager = device.getKeyManager(); + StreamContext ctx = keyManager.getStreamContext(contactId, + TRANSPORT_ID); + assertNotNull(ctx); + // Create a stream writer + StreamWriterFactory streamWriterFactory = + device.getStreamWriterFactory(); + OutputStream streamWriter = + streamWriterFactory.createStreamWriter(out, ctx); + // Create an outgoing sync session + SyncSessionFactory syncSessionFactory = device.getSyncSessionFactory(); + SyncSession session = syncSessionFactory.createSimplexOutgoingSession( + contactId, MAX_LATENCY, streamWriter); + // Write whatever needs to be written + session.run(); + streamWriter.close(); + // Return the contents of the stream + return out.toByteArray(); + } - // Clean up + private void tearDown(SimplexMessagingIntegrationTestComponent device) + throws Exception { + // Stop the lifecycle manager + LifecycleManager lifecycleManager = device.getLifecycleManager(); lifecycleManager.stopServices(); lifecycleManager.waitForShutdown(); } @@ -179,18 +187,24 @@ public class SimplexMessagingIntegrationTest extends BriarTestCase { private static void injectEagerSingletons( SimplexMessagingIntegrationTestComponent component) { + component.inject(new ContactModule.EagerSingletons()); + component.inject(new IdentityModule.EagerSingletons()); + component.inject(new LifecycleModule.EagerSingletons()); component.inject(new MessagingModule.EagerSingletons()); + component.inject(new SyncModule.EagerSingletons()); component.inject(new SystemModule.EagerSingletons()); + component.inject(new TransportModule.EagerSingletons()); + component.inject(new VersioningModule.EagerSingletons()); } @NotNullByDefault - private static class MessageListener implements EventListener { + private static class PrivateMessageListener implements EventListener { private volatile boolean messageAdded = false; @Override public void eventOccurred(Event e) { - if (e instanceof MessageAddedEvent) messageAdded = true; + if (e instanceof PrivateMessageReceivedEvent) messageAdded = true; } } } diff --git a/briar-core/src/test/java/org/briarproject/briar/messaging/SimplexMessagingIntegrationTestComponent.java b/briar-core/src/test/java/org/briarproject/briar/messaging/SimplexMessagingIntegrationTestComponent.java index d740859aa130141e1c1fe5fee92fa3ad2e3ee8e0..f3d6f039977361459b26333f5ade3d6538db3657 100644 --- a/briar-core/src/test/java/org/briarproject/briar/messaging/SimplexMessagingIntegrationTestComponent.java +++ b/briar-core/src/test/java/org/briarproject/briar/messaging/SimplexMessagingIntegrationTestComponent.java @@ -18,10 +18,12 @@ import org.briarproject.bramble.identity.IdentityModule; import org.briarproject.bramble.lifecycle.LifecycleModule; import org.briarproject.bramble.sync.SyncModule; import org.briarproject.bramble.system.SystemModule; +import org.briarproject.bramble.test.TestCryptoExecutorModule; import org.briarproject.bramble.test.TestDatabaseModule; import org.briarproject.bramble.test.TestPluginConfigModule; import org.briarproject.bramble.test.TestSecureRandomModule; import org.briarproject.bramble.transport.TransportModule; +import org.briarproject.bramble.versioning.VersioningModule; import org.briarproject.briar.api.messaging.MessagingManager; import org.briarproject.briar.api.messaging.PrivateMessageFactory; import org.briarproject.briar.client.BriarClientModule; @@ -32,6 +34,7 @@ import dagger.Component; @Singleton @Component(modules = { + TestCryptoExecutorModule.class, TestDatabaseModule.class, TestPluginConfigModule.class, TestSecureRandomModule.class, @@ -47,14 +50,27 @@ import dagger.Component; MessagingModule.class, SyncModule.class, SystemModule.class, - TransportModule.class + TransportModule.class, + VersioningModule.class }) interface SimplexMessagingIntegrationTestComponent { + void inject(ContactModule.EagerSingletons init); + + void inject(IdentityModule.EagerSingletons init); + + void inject(LifecycleModule.EagerSingletons init); + void inject(MessagingModule.EagerSingletons init); + void inject(SyncModule.EagerSingletons init); + void inject(SystemModule.EagerSingletons init); + void inject(TransportModule.EagerSingletons init); + + void inject(VersioningModule.EagerSingletons init); + LifecycleManager getLifecycleManager(); IdentityManager getIdentityManager(); diff --git a/briar-core/src/test/java/org/briarproject/briar/privategroup/invitation/AbstractProtocolEngineTest.java b/briar-core/src/test/java/org/briarproject/briar/privategroup/invitation/AbstractProtocolEngineTest.java index f7c630f655d37a903406ae069915c127e71f3c92..ebcaf70bb2dcf9112780f78041d2c3d4a6fc724b 100644 --- a/briar-core/src/test/java/org/briarproject/briar/privategroup/invitation/AbstractProtocolEngineTest.java +++ b/briar-core/src/test/java/org/briarproject/briar/privategroup/invitation/AbstractProtocolEngineTest.java @@ -11,10 +11,12 @@ import org.briarproject.bramble.api.identity.Author; import org.briarproject.bramble.api.identity.AuthorId; import org.briarproject.bramble.api.identity.IdentityManager; 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.Message; import org.briarproject.bramble.api.sync.MessageId; import org.briarproject.bramble.api.system.Clock; +import org.briarproject.bramble.api.versioning.ClientVersioningManager; import org.briarproject.bramble.test.BrambleMockTestCase; import org.briarproject.briar.api.client.MessageTracker; import org.briarproject.briar.api.privategroup.GroupMessageFactory; @@ -24,6 +26,7 @@ import org.briarproject.briar.api.privategroup.PrivateGroupManager; import org.jmock.Expectations; import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_SIGNATURE_LENGTH; +import static org.briarproject.bramble.api.sync.Group.Visibility.SHARED; import static org.briarproject.bramble.test.TestUtils.getAuthor; import static org.briarproject.bramble.test.TestUtils.getGroup; import static org.briarproject.bramble.test.TestUtils.getRandomBytes; @@ -33,6 +36,7 @@ import static org.briarproject.briar.api.privategroup.PrivateGroupConstants.GROU import static org.briarproject.briar.api.privategroup.PrivateGroupConstants.MAX_GROUP_INVITATION_MSG_LENGTH; import static org.briarproject.briar.api.privategroup.PrivateGroupConstants.MAX_GROUP_NAME_LENGTH; import static org.briarproject.briar.api.privategroup.PrivateGroupManager.CLIENT_ID; +import static org.briarproject.briar.api.privategroup.PrivateGroupManager.MAJOR_VERSION; import static org.briarproject.briar.privategroup.invitation.GroupInvitationConstants.GROUP_KEY_CONTACT_ID; import static org.briarproject.briar.privategroup.invitation.MessageType.ABORT; import static org.briarproject.briar.privategroup.invitation.MessageType.INVITE; @@ -46,6 +50,8 @@ public abstract class AbstractProtocolEngineTest extends BrambleMockTestCase { context.mock(DatabaseComponent.class); protected final ClientHelper clientHelper = context.mock(ClientHelper.class); + protected final ClientVersioningManager clientVersioningManager = + context.mock(ClientVersioningManager.class); protected final PrivateGroupFactory privateGroupFactory = context.mock(PrivateGroupFactory.class); protected final PrivateGroupManager privateGroupManager = @@ -64,7 +70,8 @@ public abstract class AbstractProtocolEngineTest extends BrambleMockTestCase { protected final Transaction txn = new Transaction(null, false); protected final GroupId contactGroupId = new GroupId(getRandomId()); - protected final Group privateGroupGroup = getGroup(CLIENT_ID); + protected final Group privateGroupGroup = + getGroup(CLIENT_ID, MAJOR_VERSION); protected final GroupId privateGroupId = privateGroupGroup.getId(); protected final Author author = getAuthor(); protected final PrivateGroup privateGroup = @@ -179,10 +186,13 @@ public abstract class AbstractProtocolEngineTest extends BrambleMockTestCase { }}); } - protected void expectSetPrivateGroupVisibility(Group.Visibility v) + protected void expectSetPrivateGroupVisibility(Visibility v) throws Exception { expectGetContactId(); context.checking(new Expectations() {{ + oneOf(clientVersioningManager).getClientVisibility(txn, contactId, + CLIENT_ID, MAJOR_VERSION); + will(returnValue(SHARED)); oneOf(db).setGroupVisibility(txn, contactId, privateGroupId, v); }}); } diff --git a/briar-core/src/test/java/org/briarproject/briar/privategroup/invitation/CreatorProtocolEngineTest.java b/briar-core/src/test/java/org/briarproject/briar/privategroup/invitation/CreatorProtocolEngineTest.java index 22e64a9f98378e79d0ee242d9f24dbcca51474e3..c1b2bd9b2718b124e10455022425241c4a805dd8 100644 --- a/briar-core/src/test/java/org/briarproject/briar/privategroup/invitation/CreatorProtocolEngineTest.java +++ b/briar-core/src/test/java/org/briarproject/briar/privategroup/invitation/CreatorProtocolEngineTest.java @@ -19,9 +19,10 @@ import static org.junit.Assert.assertEquals; public class CreatorProtocolEngineTest extends AbstractProtocolEngineTest { private final CreatorProtocolEngine engine = - new CreatorProtocolEngine(db, clientHelper, privateGroupManager, - privateGroupFactory, groupMessageFactory, identityManager, - messageParser, messageEncoder, messageTracker, clock); + new CreatorProtocolEngine(db, clientHelper, clientVersioningManager, + privateGroupManager, privateGroupFactory, + groupMessageFactory, identityManager, messageParser, + messageEncoder, messageTracker, clock); private CreatorSession getDefaultSession(CreatorState state) { return new CreatorSession(contactGroupId, privateGroupId, diff --git a/briar-core/src/test/java/org/briarproject/briar/privategroup/invitation/GroupInvitationManagerImplTest.java b/briar-core/src/test/java/org/briarproject/briar/privategroup/invitation/GroupInvitationManagerImplTest.java index c225d056dc7f3436c0ec6e2c5fbdbc11437e1b96..d36fc02e399edfbf63980f186d518bf1c6a15608 100644 --- a/briar-core/src/test/java/org/briarproject/briar/privategroup/invitation/GroupInvitationManagerImplTest.java +++ b/briar-core/src/test/java/org/briarproject/briar/privategroup/invitation/GroupInvitationManagerImplTest.java @@ -19,6 +19,7 @@ import org.briarproject.bramble.api.sync.Group; import org.briarproject.bramble.api.sync.GroupId; import org.briarproject.bramble.api.sync.Message; import org.briarproject.bramble.api.sync.MessageId; +import org.briarproject.bramble.api.versioning.ClientVersioningManager; import org.briarproject.bramble.test.BrambleMockTestCase; import org.briarproject.bramble.test.TestUtils; import org.briarproject.briar.api.client.MessageTracker; @@ -55,7 +56,7 @@ import static org.briarproject.bramble.util.StringUtils.getRandomString; import static org.briarproject.briar.api.privategroup.PrivateGroupConstants.GROUP_SALT_LENGTH; import static org.briarproject.briar.api.privategroup.PrivateGroupConstants.MAX_GROUP_NAME_LENGTH; import static org.briarproject.briar.api.privategroup.invitation.GroupInvitationManager.CLIENT_ID; -import static org.briarproject.briar.api.privategroup.invitation.GroupInvitationManager.CLIENT_VERSION; +import static org.briarproject.briar.api.privategroup.invitation.GroupInvitationManager.MAJOR_VERSION; import static org.briarproject.briar.privategroup.invitation.GroupInvitationConstants.GROUP_KEY_CONTACT_ID; import static org.briarproject.briar.privategroup.invitation.MessageType.ABORT; import static org.briarproject.briar.privategroup.invitation.MessageType.INVITE; @@ -69,6 +70,8 @@ public class GroupInvitationManagerImplTest extends BrambleMockTestCase { private final DatabaseComponent db = context.mock(DatabaseComponent.class); private final ClientHelper clientHelper = context.mock(ClientHelper.class); + private final ClientVersioningManager clientVersioningManager = + context.mock(ClientVersioningManager.class); private final ContactGroupFactory contactGroupFactory = context.mock(ContactGroupFactory.class); private final PrivateGroupFactory privateGroupFactory = @@ -99,9 +102,9 @@ public class GroupInvitationManagerImplTest extends BrambleMockTestCase { private final Author author = getAuthor(); private final Contact contact = new Contact(contactId, author, new AuthorId(getRandomId()), true, true); - private final Group localGroup = getGroup(CLIENT_ID); - private final Group contactGroup = getGroup(CLIENT_ID); - private final Group privateGroup = getGroup(CLIENT_ID); + private final Group localGroup = getGroup(CLIENT_ID, MAJOR_VERSION); + private final Group contactGroup = getGroup(CLIENT_ID, MAJOR_VERSION); + private final Group privateGroup = getGroup(CLIENT_ID, MAJOR_VERSION); private final BdfDictionary meta = BdfDictionary.of(new BdfEntry("m", "e")); private final Message message = new Message(new MessageId(getRandomId()), contactGroup.getId(), @@ -140,18 +143,18 @@ public class GroupInvitationManagerImplTest extends BrambleMockTestCase { }}); MetadataParser metadataParser = context.mock(MetadataParser.class); MessageTracker messageTracker = context.mock(MessageTracker.class); - groupInvitationManager = - new GroupInvitationManagerImpl(db, clientHelper, metadataParser, - messageTracker, contactGroupFactory, - privateGroupFactory, privateGroupManager, messageParser, - sessionParser, sessionEncoder, engineFactory); + groupInvitationManager = new GroupInvitationManagerImpl(db, + clientHelper, clientVersioningManager, metadataParser, + messageTracker, contactGroupFactory, privateGroupFactory, + privateGroupManager, messageParser, sessionParser, + sessionEncoder, engineFactory); } @Test public void testCreateLocalStateFirstTime() throws Exception { context.checking(new Expectations() {{ oneOf(contactGroupFactory).createLocalGroup(CLIENT_ID, - CLIENT_VERSION); + MAJOR_VERSION); will(returnValue(localGroup)); oneOf(db).containsGroup(txn, localGroup.getId()); will(returnValue(false)); @@ -159,7 +162,7 @@ public class GroupInvitationManagerImplTest extends BrambleMockTestCase { oneOf(db).getContacts(txn); will(returnValue(Collections.singletonList(contact))); }}); - expectAddingContact(contact, true); + expectAddingContact(contact); groupInvitationManager.createLocalState(txn); } @@ -167,7 +170,7 @@ public class GroupInvitationManagerImplTest extends BrambleMockTestCase { public void testCreateLocalStateSubsequentTime() throws Exception { context.checking(new Expectations() {{ oneOf(contactGroupFactory).createLocalGroup(CLIENT_ID, - CLIENT_VERSION); + MAJOR_VERSION); will(returnValue(localGroup)); oneOf(db).containsGroup(txn, localGroup.getId()); will(returnValue(true)); @@ -175,29 +178,27 @@ public class GroupInvitationManagerImplTest extends BrambleMockTestCase { groupInvitationManager.createLocalState(txn); } - private void expectAddingContact(Contact c, boolean contactExists) - throws Exception { - context.checking(new Expectations() {{ - oneOf(contactGroupFactory).createContactGroup(CLIENT_ID, - CLIENT_VERSION, c); - will(returnValue(contactGroup)); - oneOf(db).containsGroup(txn, contactGroup.getId()); - will(returnValue(contactExists)); - }}); - if (contactExists) return; - + private void expectAddingContact(Contact c) throws Exception { BdfDictionary meta = BdfDictionary .of(new BdfEntry(GROUP_KEY_CONTACT_ID, c.getId().getInt())); + context.checking(new Expectations() {{ + oneOf(contactGroupFactory).createContactGroup(CLIENT_ID, + MAJOR_VERSION, c); + will(returnValue(contactGroup)); oneOf(db).addGroup(txn, contactGroup); + oneOf(clientVersioningManager).getClientVisibility(txn, contactId, + CLIENT_ID, MAJOR_VERSION); + will(returnValue(SHARED)); oneOf(db).setGroupVisibility(txn, c.getId(), contactGroup.getId(), SHARED); oneOf(clientHelper) .mergeGroupMetadata(txn, contactGroup.getId(), meta); - oneOf(db).getGroups(txn, PrivateGroupManager.CLIENT_ID); + oneOf(db).getGroups(txn, PrivateGroupManager.CLIENT_ID, + PrivateGroupManager.MAJOR_VERSION); will(returnValue(Collections.singletonList(privateGroup))); - oneOf(privateGroupManager) - .isMember(txn, privateGroup.getId(), c.getAuthor()); + oneOf(privateGroupManager).isMember(txn, privateGroup.getId(), + c.getAuthor()); will(returnValue(true)); }}); expectAddingMember(privateGroup.getId(), c); @@ -206,7 +207,7 @@ public class GroupInvitationManagerImplTest extends BrambleMockTestCase { private void expectAddingMember(GroupId g, Contact c) throws Exception { context.checking(new Expectations() {{ oneOf(contactGroupFactory).createContactGroup(CLIENT_ID, - CLIENT_VERSION, c); + MAJOR_VERSION, c); will(returnValue(contactGroup)); }}); expectGetSession(noResults, new SessionId(g.getBytes()), @@ -254,7 +255,7 @@ public class GroupInvitationManagerImplTest extends BrambleMockTestCase { @Test public void testAddingContact() throws Exception { - expectAddingContact(contact, false); + expectAddingContact(contact); groupInvitationManager.addingContact(txn, contact); } @@ -262,7 +263,7 @@ public class GroupInvitationManagerImplTest extends BrambleMockTestCase { public void testRemovingContact() throws Exception { context.checking(new Expectations() {{ oneOf(contactGroupFactory).createContactGroup(CLIENT_ID, - CLIENT_VERSION, contact); + MAJOR_VERSION, contact); will(returnValue(contactGroup)); oneOf(db).removeGroup(txn, contactGroup); }}); @@ -477,7 +478,7 @@ public class GroupInvitationManagerImplTest extends BrambleMockTestCase { oneOf(db).getContact(txn, contactId); will(returnValue(contact)); oneOf(contactGroupFactory).createContactGroup(CLIENT_ID, - CLIENT_VERSION, contact); + MAJOR_VERSION, contact); will(returnValue(contactGroup)); }}); expectCreateStorageId(); @@ -509,7 +510,7 @@ public class GroupInvitationManagerImplTest extends BrambleMockTestCase { oneOf(db).getContact(txn, contactId); will(returnValue(contact)); oneOf(contactGroupFactory).createContactGroup(CLIENT_ID, - CLIENT_VERSION, contact); + MAJOR_VERSION, contact); will(returnValue(contactGroup)); oneOf(sessionParser) .parseCreatorSession(contactGroup.getId(), bdfSession); @@ -538,7 +539,7 @@ public class GroupInvitationManagerImplTest extends BrambleMockTestCase { oneOf(db).getContact(txn, contactId); will(returnValue(contact)); oneOf(contactGroupFactory).createContactGroup(CLIENT_ID, - CLIENT_VERSION, contact); + MAJOR_VERSION, contact); will(returnValue(contactGroup)); oneOf(db).endTransaction(txn); }}); @@ -590,7 +591,7 @@ public class GroupInvitationManagerImplTest extends BrambleMockTestCase { oneOf(db).getContact(txn, contactId); will(returnValue(contact)); oneOf(contactGroupFactory).createContactGroup(CLIENT_ID, - CLIENT_VERSION, contact); + MAJOR_VERSION, contact); will(returnValue(contactGroup)); oneOf(sessionParser) .parseInviteeSession(contactGroup.getId(), bdfSession); @@ -612,7 +613,7 @@ public class GroupInvitationManagerImplTest extends BrambleMockTestCase { oneOf(db).getContact(txn, contactId); will(returnValue(contact)); oneOf(contactGroupFactory).createContactGroup(CLIENT_ID, - CLIENT_VERSION, contact); + MAJOR_VERSION, contact); will(returnValue(contactGroup)); oneOf(sessionParser) .parsePeerSession(contactGroup.getId(), bdfSession); @@ -637,7 +638,7 @@ public class GroupInvitationManagerImplTest extends BrambleMockTestCase { oneOf(db).getContact(txn, contactId); will(returnValue(contact)); oneOf(contactGroupFactory).createContactGroup(CLIENT_ID, - CLIENT_VERSION, contact); + MAJOR_VERSION, contact); will(returnValue(contactGroup)); oneOf(db).endTransaction(txn); }}); @@ -676,7 +677,7 @@ public class GroupInvitationManagerImplTest extends BrambleMockTestCase { oneOf(db).getContact(txn, contactId); will(returnValue(contact)); oneOf(contactGroupFactory).createContactGroup(CLIENT_ID, - CLIENT_VERSION, contact); + MAJOR_VERSION, contact); will(returnValue(contactGroup)); oneOf(messageParser).getMessagesVisibleInUiQuery(); will(returnValue(query)); @@ -753,7 +754,7 @@ public class GroupInvitationManagerImplTest extends BrambleMockTestCase { oneOf(db).getContacts(txn); will(returnValue(Collections.singletonList(contact))); oneOf(contactGroupFactory).createContactGroup(CLIENT_ID, - CLIENT_VERSION, contact); + MAJOR_VERSION, contact); will(returnValue(contactGroup)); oneOf(clientHelper).getMessageMetadataAsDictionary(txn, contactGroup.getId(), query); @@ -821,10 +822,14 @@ public class GroupInvitationManagerImplTest extends BrambleMockTestCase { expectGetSession(oneResult, sessionId, contactGroup.getId()); context.checking(new Expectations() {{ oneOf(contactGroupFactory).createContactGroup(CLIENT_ID, - CLIENT_VERSION, contact); + MAJOR_VERSION, contact); will(returnValue(contactGroup)); oneOf(db).startTransaction(true); will(returnValue(txn)); + oneOf(clientVersioningManager).getClientVisibility(txn, contactId, + PrivateGroupManager.CLIENT_ID, + PrivateGroupManager.MAJOR_VERSION); + will(returnValue(SHARED)); oneOf(sessionParser) .parseCreatorSession(contactGroup.getId(), bdfSession); will(returnValue(creatorSession)); @@ -854,8 +859,8 @@ public class GroupInvitationManagerImplTest extends BrambleMockTestCase { Collection<Contact> contacts = Arrays.asList(contact, contact2, contact3); - Group contactGroup2 = getGroup(CLIENT_ID); - Group contactGroup3 = getGroup(CLIENT_ID); + Group contactGroup2 = getGroup(CLIENT_ID, MAJOR_VERSION); + Group contactGroup3 = getGroup(CLIENT_ID, MAJOR_VERSION); MessageId storageId2 = new MessageId(getRandomId()); MessageId storageId3 = new MessageId(getRandomId()); @@ -874,13 +879,13 @@ public class GroupInvitationManagerImplTest extends BrambleMockTestCase { oneOf(db).getContacts(txn); will(returnValue(contacts)); oneOf(contactGroupFactory).createContactGroup(CLIENT_ID, - CLIENT_VERSION, contact); + MAJOR_VERSION, contact); will(returnValue(contactGroup)); oneOf(contactGroupFactory).createContactGroup(CLIENT_ID, - CLIENT_VERSION, contact2); + MAJOR_VERSION, contact2); will(returnValue(contactGroup2)); oneOf(contactGroupFactory).createContactGroup(CLIENT_ID, - CLIENT_VERSION, contact3); + MAJOR_VERSION, contact3); will(returnValue(contactGroup3)); // session 1 oneOf(sessionParser).getRole(bdfSession); diff --git a/briar-core/src/test/java/org/briarproject/briar/privategroup/invitation/InviteeProtocolEngineTest.java b/briar-core/src/test/java/org/briarproject/briar/privategroup/invitation/InviteeProtocolEngineTest.java index 0030945428d217b1cca9b42cfc186636259373cc..f398d3a7fd4711ffd6b9cecebc206fe2930f6133 100644 --- a/briar-core/src/test/java/org/briarproject/briar/privategroup/invitation/InviteeProtocolEngineTest.java +++ b/briar-core/src/test/java/org/briarproject/briar/privategroup/invitation/InviteeProtocolEngineTest.java @@ -38,9 +38,10 @@ import static org.junit.Assert.assertTrue; public class InviteeProtocolEngineTest extends AbstractProtocolEngineTest { private final InviteeProtocolEngine engine = - new InviteeProtocolEngine(db, clientHelper, privateGroupManager, - privateGroupFactory, groupMessageFactory, identityManager, - messageParser, messageEncoder, messageTracker, clock); + new InviteeProtocolEngine(db, clientHelper, clientVersioningManager, + privateGroupManager, privateGroupFactory, + groupMessageFactory, identityManager, messageParser, + messageEncoder, messageTracker, clock); private final LocalAuthor localAuthor = getLocalAuthor(); private InviteeSession getDefaultSession(InviteeState state) { @@ -238,6 +239,7 @@ public class InviteeProtocolEngineTest extends AbstractProtocolEngineTest { @Test public void testOnLeaveActionFromAccepted() throws Exception { expectSendLeaveMessage(false); + expectSetPrivateGroupVisibility(INVISIBLE); InviteeSession session = getDefaultSession(ACCEPTED); InviteeSession newSession = engine.onLeaveAction(txn, session); @@ -249,6 +251,7 @@ public class InviteeProtocolEngineTest extends AbstractProtocolEngineTest { @Test public void testOnLeaveActionFromJoined() throws Exception { expectSendLeaveMessage(false); + expectSetPrivateGroupVisibility(INVISIBLE); InviteeSession session = getDefaultSession(JOINED); InviteeSession newSession = engine.onLeaveAction(txn, session); diff --git a/briar-core/src/test/java/org/briarproject/briar/privategroup/invitation/PeerProtocolEngineTest.java b/briar-core/src/test/java/org/briarproject/briar/privategroup/invitation/PeerProtocolEngineTest.java index d5d53168fa57a672bcdd48e187b08b9bbb6065df..05c0920c545c3b97c83f9553ab2034cee77a8774 100644 --- a/briar-core/src/test/java/org/briarproject/briar/privategroup/invitation/PeerProtocolEngineTest.java +++ b/briar-core/src/test/java/org/briarproject/briar/privategroup/invitation/PeerProtocolEngineTest.java @@ -24,9 +24,10 @@ import static org.junit.Assert.assertTrue; public class PeerProtocolEngineTest extends AbstractProtocolEngineTest { private final PeerProtocolEngine engine = - new PeerProtocolEngine(db, clientHelper, privateGroupManager, - privateGroupFactory, groupMessageFactory, identityManager, - messageParser, messageEncoder, messageTracker, clock); + new PeerProtocolEngine(db, clientHelper, clientVersioningManager, + privateGroupManager, privateGroupFactory, + groupMessageFactory, identityManager, messageParser, + messageEncoder, messageTracker, clock); private PeerSession getDefaultSession(PeerState state) { return new PeerSession(contactGroupId, privateGroupId, diff --git a/briar-core/src/test/java/org/briarproject/briar/sharing/BlogSharingIntegrationTest.java b/briar-core/src/test/java/org/briarproject/briar/sharing/BlogSharingIntegrationTest.java index 37bbe16e19261f980bee6d036b07a0f50254e2c6..1fbc64387bb0bca36ed5819ec69d683286a634c8 100644 --- a/briar-core/src/test/java/org/briarproject/briar/sharing/BlogSharingIntegrationTest.java +++ b/briar-core/src/test/java/org/briarproject/briar/sharing/BlogSharingIntegrationTest.java @@ -32,7 +32,7 @@ import java.util.Collection; import java.util.List; import static org.briarproject.briar.api.blog.BlogSharingManager.CLIENT_ID; -import static org.briarproject.briar.api.blog.BlogSharingManager.CLIENT_VERSION; +import static org.briarproject.briar.api.blog.BlogSharingManager.MAJOR_VERSION; import static org.briarproject.briar.test.BriarTestUtils.assertGroupCount; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; @@ -126,7 +126,7 @@ public class BlogSharingIntegrationTest // get sharing group and assert group message count GroupId g = contactGroupFactory.createContactGroup(CLIENT_ID, - CLIENT_VERSION, contact1From0).getId(); + MAJOR_VERSION, contact1From0).getId(); assertGroupCount(messageTracker0, g, 1, 0); // sync first request message @@ -201,7 +201,7 @@ public class BlogSharingIntegrationTest // get sharing group and assert group message count GroupId g = contactGroupFactory.createContactGroup(CLIENT_ID, - CLIENT_VERSION, contact1From0).getId(); + MAJOR_VERSION, contact1From0).getId(); assertGroupCount(messageTracker0, g, 1, 0); // sync first request message diff --git a/briar-core/src/test/java/org/briarproject/briar/sharing/BlogSharingManagerImplTest.java b/briar-core/src/test/java/org/briarproject/briar/sharing/BlogSharingManagerImplTest.java index 46a9cb5e969119cce46a669750b7dc24bb03bec3..8b72b4aff33c3ee14ac063e5ac95da395a597c86 100644 --- a/briar-core/src/test/java/org/briarproject/briar/sharing/BlogSharingManagerImplTest.java +++ b/briar-core/src/test/java/org/briarproject/briar/sharing/BlogSharingManagerImplTest.java @@ -17,6 +17,7 @@ import org.briarproject.bramble.api.identity.LocalAuthor; import org.briarproject.bramble.api.sync.Group; import org.briarproject.bramble.api.sync.Message; import org.briarproject.bramble.api.sync.MessageId; +import org.briarproject.bramble.api.versioning.ClientVersioningManager; import org.briarproject.bramble.test.BrambleMockTestCase; import org.briarproject.briar.api.blog.Blog; import org.briarproject.briar.api.blog.BlogInvitationResponse; @@ -24,7 +25,6 @@ import org.briarproject.briar.api.blog.BlogManager; import org.briarproject.briar.api.client.MessageTracker; import org.briarproject.briar.api.client.SessionId; import org.jmock.Expectations; -import org.jmock.Mockery; import org.junit.Test; import java.util.Collection; @@ -39,17 +39,18 @@ import static org.briarproject.bramble.test.TestUtils.getLocalAuthor; import static org.briarproject.bramble.test.TestUtils.getRandomBytes; import static org.briarproject.bramble.test.TestUtils.getRandomId; import static org.briarproject.briar.api.blog.BlogSharingManager.CLIENT_ID; -import static org.briarproject.briar.api.blog.BlogSharingManager.CLIENT_VERSION; +import static org.briarproject.briar.api.blog.BlogSharingManager.MAJOR_VERSION; import static org.briarproject.briar.sharing.SharingConstants.GROUP_KEY_CONTACT_ID; public class BlogSharingManagerImplTest extends BrambleMockTestCase { - private final Mockery context = new Mockery(); private final BlogSharingManagerImpl blogSharingManager; private final DatabaseComponent db = context.mock(DatabaseComponent.class); private final IdentityManager identityManager = context.mock(IdentityManager.class); private final ClientHelper clientHelper = context.mock(ClientHelper.class); + private final ClientVersioningManager clientVersioningManager = + context.mock(ClientVersioningManager.class); private final SessionEncoder sessionEncoder = context.mock(SessionEncoder.class); private final SessionParser sessionParser = @@ -65,11 +66,13 @@ public class BlogSharingManagerImplTest extends BrambleMockTestCase { new Contact(contactId, author, localAuthor.getId(), true, true); private final Collection<Contact> contacts = Collections.singletonList(contact); - private final Group localGroup = getGroup(CLIENT_ID); - private final Group contactGroup = getGroup(CLIENT_ID); - private final Group blogGroup = getGroup(BlogManager.CLIENT_ID); + private final Group localGroup = getGroup(CLIENT_ID, MAJOR_VERSION); + private final Group contactGroup = getGroup(CLIENT_ID, MAJOR_VERSION); + private final Group blogGroup = + getGroup(BlogManager.CLIENT_ID, BlogManager.MAJOR_VERSION); private final Blog blog = new Blog(blogGroup, author, false); - private final Group localBlogGroup = getGroup(BlogManager.CLIENT_ID); + private final Group localBlogGroup = + getGroup(BlogManager.CLIENT_ID, BlogManager.MAJOR_VERSION); private final Blog localBlog = new Blog(localBlogGroup, localAuthor, false); @SuppressWarnings("unchecked") private final ProtocolEngine<Blog> engine = @@ -78,25 +81,26 @@ public class BlogSharingManagerImplTest extends BrambleMockTestCase { @SuppressWarnings("unchecked") public BlogSharingManagerImplTest() { MetadataParser metadataParser = context.mock(MetadataParser.class); - MessageTracker messageTracker = context.mock(MessageTracker.class); MessageParser<Blog> messageParser = context.mock(MessageParser.class); + MessageTracker messageTracker = context.mock(MessageTracker.class); InvitationFactory<Blog, BlogInvitationResponse> invitationFactory = context.mock(InvitationFactory.class); blogSharingManager = new BlogSharingManagerImpl(db, clientHelper, - metadataParser, messageParser, sessionEncoder, sessionParser, - messageTracker, contactGroupFactory, engine, invitationFactory, - identityManager, blogManager); + clientVersioningManager, metadataParser, messageParser, + sessionEncoder, sessionParser, messageTracker, + contactGroupFactory, engine, invitationFactory, identityManager, + blogManager); } @Test - public void testCreateLocalStateFirstTimeWithExistingContactNotSetUp() + public void testCreateLocalStateFirstTimeWithExistingContact() throws Exception { Transaction txn = new Transaction(null, false); context.checking(new Expectations() {{ // The local group doesn't exist - we need to set things up oneOf(contactGroupFactory).createLocalGroup(CLIENT_ID, - CLIENT_VERSION); + MAJOR_VERSION); will(returnValue(localGroup)); oneOf(db).containsGroup(txn, localGroup.getId()); will(returnValue(false)); @@ -117,20 +121,14 @@ public class BlogSharingManagerImplTest extends BrambleMockTestCase { Map<MessageId, BdfDictionary> sessions = Collections.emptyMap(); context.checking(new Expectations() {{ - // Check for contact group in BlogSharingManagerImpl - oneOf(contactGroupFactory).createContactGroup(CLIENT_ID, - CLIENT_VERSION, contact); - will(returnValue(contactGroup)); - oneOf(db).containsGroup(txn, contactGroup.getId()); - will(returnValue(false)); - // Check for contact group again in SharingManagerImpl + // Create the contact group and share it with the contact oneOf(contactGroupFactory).createContactGroup(CLIENT_ID, - CLIENT_VERSION, contact); + MAJOR_VERSION, contact); will(returnValue(contactGroup)); - oneOf(db).containsGroup(txn, contactGroup.getId()); - will(returnValue(false)); - // Create the contact group and share it with the contact oneOf(db).addGroup(txn, contactGroup); + oneOf(clientVersioningManager).getClientVisibility(txn, contactId, + CLIENT_ID, MAJOR_VERSION); + will(returnValue(SHARED)); oneOf(db).setGroupVisibility(txn, contactId, contactGroup.getId(), SHARED); // Attach the contact ID to the group @@ -149,33 +147,6 @@ public class BlogSharingManagerImplTest extends BrambleMockTestCase { expectPreShareShareable(txn, contact, blog, sessions); } - @Test - public void testCreateLocalStateFirstTimeWithExistingContactAlreadySetUp() - throws Exception { - Transaction txn = new Transaction(null, false); - - context.checking(new Expectations() {{ - // The local group doesn't exist - we need to set things up - oneOf(contactGroupFactory).createLocalGroup(CLIENT_ID, - CLIENT_VERSION); - will(returnValue(localGroup)); - oneOf(db).containsGroup(txn, localGroup.getId()); - will(returnValue(false)); - oneOf(db).addGroup(txn, localGroup); - // Get contacts - oneOf(db).getContacts(txn); - will(returnValue(contacts)); - // The contact has already been set up - oneOf(contactGroupFactory).createContactGroup(CLIENT_ID, - CLIENT_VERSION, contact); - will(returnValue(contactGroup)); - oneOf(db).containsGroup(txn, contactGroup.getId()); - will(returnValue(true)); - }}); - - blogSharingManager.createLocalState(txn); - } - @Test public void testCreateLocalStateSubsequentTime() throws Exception { Transaction txn = new Transaction(null, false); @@ -183,7 +154,7 @@ public class BlogSharingManagerImplTest extends BrambleMockTestCase { context.checking(new Expectations() {{ // The local group exists - everything has been set up oneOf(contactGroupFactory).createLocalGroup(CLIENT_ID, - CLIENT_VERSION); + MAJOR_VERSION); will(returnValue(localGroup)); oneOf(db).containsGroup(txn, localGroup.getId()); will(returnValue(true)); @@ -225,13 +196,13 @@ public class BlogSharingManagerImplTest extends BrambleMockTestCase { private void expectPreShareShareable(Transaction txn, Contact contact, Blog blog, Map<MessageId, BdfDictionary> sessions) throws Exception { - Group contactGroup = getGroup(CLIENT_ID); + Group contactGroup = getGroup(CLIENT_ID, MAJOR_VERSION); BdfDictionary sessionDict = new BdfDictionary(); Message message = new Message(new MessageId(getRandomId()), contactGroup.getId(), 42L, getRandomBytes(1337)); context.checking(new Expectations() {{ oneOf(contactGroupFactory).createContactGroup(CLIENT_ID, - CLIENT_VERSION, contact); + MAJOR_VERSION, contact); will(returnValue(contactGroup)); oneOf(sessionParser) .getSessionQuery(new SessionId(blog.getId().getBytes())); @@ -241,6 +212,10 @@ public class BlogSharingManagerImplTest extends BrambleMockTestCase { will(returnValue(sessions)); if (sessions.size() == 0) { oneOf(db).addGroup(txn, blog.getGroup()); + oneOf(clientVersioningManager).getClientVisibility(txn, + contactId, BlogManager.CLIENT_ID, + BlogManager.MAJOR_VERSION); + will(returnValue(SHARED)); oneOf(db).setGroupVisibility(txn, contact.getId(), blog.getGroup().getId(), SHARED); oneOf(clientHelper) @@ -265,7 +240,7 @@ public class BlogSharingManagerImplTest extends BrambleMockTestCase { oneOf(db).getContacts(txn); will(returnValue(contacts)); oneOf(contactGroupFactory).createContactGroup(CLIENT_ID, - CLIENT_VERSION, contact); + MAJOR_VERSION, contact); will(returnValue(contactGroup)); oneOf(sessionParser) .getSessionQuery(new SessionId(blog.getId().getBytes())); diff --git a/briar-core/src/test/java/org/briarproject/briar/sharing/ForumSharingIntegrationTest.java b/briar-core/src/test/java/org/briarproject/briar/sharing/ForumSharingIntegrationTest.java index bbc83660a006de207252894a6088eb52cd373308..2683ea715bdf6e17aefdfe72ad2c95cb1d7ae41d 100644 --- a/briar-core/src/test/java/org/briarproject/briar/sharing/ForumSharingIntegrationTest.java +++ b/briar-core/src/test/java/org/briarproject/briar/sharing/ForumSharingIntegrationTest.java @@ -39,7 +39,7 @@ import java.util.List; import static junit.framework.Assert.assertNotNull; import static org.briarproject.bramble.util.StringUtils.getRandomString; import static org.briarproject.briar.api.forum.ForumSharingManager.CLIENT_ID; -import static org.briarproject.briar.api.forum.ForumSharingManager.CLIENT_VERSION; +import static org.briarproject.briar.api.forum.ForumSharingManager.MAJOR_VERSION; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; @@ -402,7 +402,7 @@ public class ForumSharingIntegrationTest // response and invitation got tracked Group group = contactGroupFactory.createContactGroup(CLIENT_ID, - CLIENT_VERSION, contact0From1); + MAJOR_VERSION, contact0From1); assertEquals(2, c1.getMessageTracker().getGroupCount(group.getId()) .getMsgCount()); @@ -434,7 +434,7 @@ public class ForumSharingIntegrationTest // assert that the invitation arrived Group group = contactGroupFactory.createContactGroup(CLIENT_ID, - CLIENT_VERSION, contact0From1); + MAJOR_VERSION, contact0From1); assertEquals(1, c1.getMessageTracker().getGroupCount(group.getId()) .getMsgCount()); 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 3c2138b61392fbc77e5a67066dfb8ee5d3574269..7175cdb776d4cde3fe896e014e8110afd827d654 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 @@ -24,7 +24,7 @@ import org.briarproject.bramble.api.sync.SyncSessionFactory; import org.briarproject.bramble.api.sync.event.MessageStateChangedEvent; import org.briarproject.bramble.api.system.Clock; import org.briarproject.bramble.contact.ContactModule; -import org.briarproject.bramble.crypto.CryptoModule; +import org.briarproject.bramble.crypto.CryptoExecutorModule; import org.briarproject.bramble.identity.IdentityModule; import org.briarproject.bramble.lifecycle.LifecycleModule; import org.briarproject.bramble.properties.PropertiesModule; @@ -32,6 +32,7 @@ import org.briarproject.bramble.sync.SyncModule; import org.briarproject.bramble.system.SystemModule; import org.briarproject.bramble.test.TestUtils; import org.briarproject.bramble.transport.TransportModule; +import org.briarproject.bramble.versioning.VersioningModule; import org.briarproject.briar.api.blog.BlogFactory; import org.briarproject.briar.api.blog.BlogPostFactory; import org.briarproject.briar.api.client.MessageTracker; @@ -159,10 +160,9 @@ public abstract class BriarIntegrationTest<C extends BriarIntegrationTestCompone deliveryWaiter = new Waiter(); startLifecycles(); - getDefaultIdentities(); - addDefaultContacts(); listenToEvents(); + addDefaultContacts(); } abstract protected void createComponents(); @@ -171,7 +171,7 @@ public abstract class BriarIntegrationTest<C extends BriarIntegrationTestCompone BriarIntegrationTestComponent component) { component.inject(new BlogModule.EagerSingletons()); component.inject(new ContactModule.EagerSingletons()); - component.inject(new CryptoModule.EagerSingletons()); + component.inject(new CryptoExecutorModule.EagerSingletons()); component.inject(new ForumModule.EagerSingletons()); component.inject(new GroupInvitationModule.EagerSingletons()); component.inject(new IdentityModule.EagerSingletons()); @@ -184,10 +184,11 @@ public abstract class BriarIntegrationTest<C extends BriarIntegrationTestCompone component.inject(new SyncModule.EagerSingletons()); component.inject(new SystemModule.EagerSingletons()); component.inject(new TransportModule.EagerSingletons()); + component.inject(new VersioningModule.EagerSingletons()); } private void startLifecycles() throws InterruptedException { - // Start the lifecycle manager and wait for it to finish + // Start the lifecycle manager and wait for it to finish starting lifecycleManager0 = c0.getLifecycleManager(); lifecycleManager1 = c1.getLifecycleManager(); lifecycleManager2 = c2.getLifecycleManager(); @@ -234,7 +235,7 @@ public abstract class BriarIntegrationTest<C extends BriarIntegrationTestCompone author2 = identityManager2.getLocalAuthor(); } - protected void addDefaultContacts() throws DbException { + protected void addDefaultContacts() throws Exception { contactId1From0 = contactManager0 .addContact(author1, author0.getId(), getSecretKey(), clock.currentTimeMillis(), true, true, true); @@ -251,15 +252,28 @@ public abstract class BriarIntegrationTest<C extends BriarIntegrationTestCompone .addContact(author0, author2.getId(), getSecretKey(), clock.currentTimeMillis(), true, true, true); contact0From2 = contactManager2.getContact(contactId0From2); + + // Sync initial client versioning updates + sync0To1(1, true); + sync0To2(1, true); + sync1To0(1, true); + sync2To0(1, true); + sync0To1(1, true); + sync0To2(1, true); } - protected void addContacts1And2() throws DbException { + protected void addContacts1And2() throws Exception { contactId2From1 = contactManager1 .addContact(author2, author1.getId(), getSecretKey(), clock.currentTimeMillis(), true, true, true); contactId1From2 = contactManager2 .addContact(author1, author2.getId(), getSecretKey(), clock.currentTimeMillis(), true, true, true); + + // Sync initial client versioning updates + sync1To2(1, true); + sync2To1(1, true); + sync1To2(1, true); } @After diff --git a/briar-core/src/test/java/org/briarproject/briar/test/BriarIntegrationTestComponent.java b/briar-core/src/test/java/org/briarproject/briar/test/BriarIntegrationTestComponent.java index ecaf60d1b9763e069018b243312948c3906dc80f..131f92ea8b8b910ab7f49ca9f00466900fd9c15c 100644 --- a/briar-core/src/test/java/org/briarproject/briar/test/BriarIntegrationTestComponent.java +++ b/briar-core/src/test/java/org/briarproject/briar/test/BriarIntegrationTestComponent.java @@ -11,6 +11,7 @@ import org.briarproject.bramble.api.properties.TransportPropertyManager; import org.briarproject.bramble.api.sync.SyncSessionFactory; import org.briarproject.bramble.client.ClientModule; import org.briarproject.bramble.contact.ContactModule; +import org.briarproject.bramble.crypto.CryptoExecutorModule; import org.briarproject.bramble.crypto.CryptoModule; import org.briarproject.bramble.data.DataModule; import org.briarproject.bramble.db.DatabaseModule; @@ -24,6 +25,7 @@ import org.briarproject.bramble.test.TestDatabaseModule; import org.briarproject.bramble.test.TestPluginConfigModule; import org.briarproject.bramble.test.TestSecureRandomModule; import org.briarproject.bramble.transport.TransportModule; +import org.briarproject.bramble.versioning.VersioningModule; import org.briarproject.briar.api.blog.BlogFactory; import org.briarproject.briar.api.blog.BlogManager; import org.briarproject.briar.api.blog.BlogSharingManager; @@ -59,6 +61,7 @@ import dagger.Component; ClientModule.class, ContactModule.class, CryptoModule.class, + CryptoExecutorModule.class, DataModule.class, DatabaseModule.class, EventModule.class, @@ -73,7 +76,8 @@ import dagger.Component; SharingModule.class, SyncModule.class, SystemModule.class, - TransportModule.class + TransportModule.class, + VersioningModule.class }) public interface BriarIntegrationTestComponent { @@ -83,7 +87,7 @@ public interface BriarIntegrationTestComponent { void inject(ContactModule.EagerSingletons init); - void inject(CryptoModule.EagerSingletons init); + void inject(CryptoExecutorModule.EagerSingletons init); void inject(ForumModule.EagerSingletons init); @@ -109,6 +113,8 @@ public interface BriarIntegrationTestComponent { void inject(TransportModule.EagerSingletons init); + void inject(VersioningModule.EagerSingletons init); + LifecycleManager getLifecycleManager(); EventBus getEventBus();