diff --git a/briar-android-tests/src/test/java/org/briarproject/PrivateGroupManagerTest.java b/briar-android-tests/src/test/java/org/briarproject/PrivateGroupManagerTest.java index e37000febf79b9d576f0619dc753b4d16aaa1b6f..d7c9c051dceeaf287d8c8e916892998f37448b90 100644 --- a/briar-android-tests/src/test/java/org/briarproject/PrivateGroupManagerTest.java +++ b/briar-android-tests/src/test/java/org/briarproject/PrivateGroupManagerTest.java @@ -66,6 +66,7 @@ import static org.briarproject.api.privategroup.Visibility.REVEALED_BY_CONTACT; import static org.briarproject.api.privategroup.Visibility.REVEALED_BY_US; import static org.briarproject.api.privategroup.Visibility.VISIBLE; import static org.briarproject.api.privategroup.invitation.GroupInvitationManager.CLIENT_ID; +import static org.briarproject.api.sync.Group.Visibility.SHARED; import static org.briarproject.api.sync.ValidationManager.State.DELIVERED; import static org.briarproject.api.sync.ValidationManager.State.INVALID; import static org.briarproject.api.sync.ValidationManager.State.PENDING; @@ -348,9 +349,9 @@ public class PrivateGroupManagerTest extends BriarIntegrationTest { assertEquals(joinMsg0.getMessage().getId(), groupManager0.getPreviousMsgId(groupId0)); - // make group visible to 1 + // share the group with 1 Transaction txn0 = db0.startTransaction(false); - db0.setVisibleToContact(txn0, contactId1, privateGroup0.getId(), true); + db0.setGroupVisibility(txn0, contactId1, privateGroup0.getId(), SHARED); db0.commitTransaction(txn0); db0.endTransaction(txn0); @@ -371,9 +372,10 @@ public class PrivateGroupManagerTest extends BriarIntegrationTest { assertEquals(joinMsg1.getMessage().getId(), groupManager1.getPreviousMsgId(groupId0)); - // make group visible to 0 + // share the group with 0 Transaction txn1 = db1.startTransaction(false); - db1.setVisibleToContact(txn1, contactId01, privateGroup0.getId(), true); + db1.setGroupVisibility(txn1, contactId01, privateGroup0.getId(), + SHARED); db1.commitTransaction(txn1); db1.endTransaction(txn1); @@ -415,9 +417,9 @@ public class PrivateGroupManagerTest extends BriarIntegrationTest { assertEquals(joinMsg0.getMessage().getId(), groupManager0.getPreviousMsgId(groupId0)); - // make group visible to 1 + // share the group with 1 Transaction txn0 = db0.startTransaction(false); - db0.setVisibleToContact(txn0, contactId1, privateGroup0.getId(), true); + db0.setGroupVisibility(txn0, contactId1, privateGroup0.getId(), SHARED); db0.commitTransaction(txn0); db0.endTransaction(txn0); @@ -438,9 +440,9 @@ public class PrivateGroupManagerTest extends BriarIntegrationTest { assertEquals(joinMsg1.getMessage().getId(), groupManager1.getPreviousMsgId(groupId0)); - // make group visible to 0 + // share the group with 0 Transaction txn1 = db1.startTransaction(false); - db1.setVisibleToContact(txn1, contactId01, privateGroup0.getId(), true); + db1.setGroupVisibility(txn1, contactId01, privateGroup0.getId(), SHARED); db1.commitTransaction(txn1); db1.endTransaction(txn1); @@ -518,9 +520,9 @@ public class PrivateGroupManagerTest extends BriarIntegrationTest { public void testRevealingRelationships() throws Exception { defaultInit(); - // make group visible to 2 + // share the group with 2 Transaction txn0 = db0.startTransaction(false); - db0.setVisibleToContact(txn0, contactId2, privateGroup0.getId(), true); + db0.setGroupVisibility(txn0, contactId2, privateGroup0.getId(), SHARED); db0.commitTransaction(txn0); db0.endTransaction(txn0); @@ -540,8 +542,9 @@ public class PrivateGroupManagerTest extends BriarIntegrationTest { Transaction txn2 = db2.startTransaction(false); groupManager2.addPrivateGroup(txn2, privateGroup0, joinMsg2, false); - // make group visible to 0 - db2.setVisibleToContact(txn2, contactId01, privateGroup0.getId(), true); + // share the group with 0 + db2.setGroupVisibility(txn2, contactId01, privateGroup0.getId(), + SHARED); db2.commitTransaction(txn2); db2.endTransaction(txn2); @@ -741,9 +744,9 @@ public class PrivateGroupManagerTest extends BriarIntegrationTest { assertEquals(joinMsg0.getMessage().getId(), groupManager0.getPreviousMsgId(groupId0)); - // make group visible to 1 + // share the group with 1 Transaction txn0 = db0.startTransaction(false); - db0.setVisibleToContact(txn0, contactId1, privateGroup0.getId(), true); + db0.setGroupVisibility(txn0, contactId1, privateGroup0.getId(), SHARED); db0.commitTransaction(txn0); db0.endTransaction(txn0); @@ -762,9 +765,10 @@ public class PrivateGroupManagerTest extends BriarIntegrationTest { inviteTime, creatorSignature); groupManager1.addPrivateGroup(privateGroup0, joinMsg1, false); - // make group visible to 0 + // share the group with 0 Transaction txn1 = db1.startTransaction(false); - db1.setVisibleToContact(txn1, contactId01, privateGroup0.getId(), true); + db1.setGroupVisibility(txn1, contactId01, privateGroup0.getId(), + SHARED); db1.commitTransaction(txn1); db1.endTransaction(txn1); assertEquals(joinMsg1.getMessage().getId(), diff --git a/briar-api/src/org/briarproject/api/db/DatabaseComponent.java b/briar-api/src/org/briarproject/api/db/DatabaseComponent.java index b428e096bbdf2d562b2073e1504bd9505ec01df9..96ec6f17d38387157db5ee39c382912375485e5b 100644 --- a/briar-api/src/org/briarproject/api/db/DatabaseComponent.java +++ b/briar-api/src/org/briarproject/api/db/DatabaseComponent.java @@ -11,6 +11,7 @@ import org.briarproject.api.settings.Settings; import org.briarproject.api.sync.Ack; import org.briarproject.api.sync.ClientId; import org.briarproject.api.sync.Group; +import org.briarproject.api.sync.Group.Visibility; import org.briarproject.api.sync.GroupId; import org.briarproject.api.sync.Message; import org.briarproject.api.sync.MessageId; @@ -232,6 +233,15 @@ public interface DatabaseComponent { */ Collection<Group> getGroups(Transaction txn, ClientId c) throws DbException; + /** + * Returns the given group's visibility to the given contact, or + * {@link Visibility INVISIBLE} if the group is not in the database. + * <p/> + * Read-only. + */ + Visibility getGroupVisibility(Transaction txn, ContactId c, GroupId g) + throws DbException; + /** * Returns the local pseudonym with the given ID. * <p/> @@ -388,14 +398,6 @@ public interface DatabaseComponent { void incrementStreamCounter(Transaction txn, ContactId c, TransportId t, long rotationPeriod) throws DbException; - /** - * Returns true if the given group is visible to the given contact. - * <p/> - * Read-only. - */ - boolean isVisibleToContact(Transaction txn, ContactId c, GroupId g) - throws DbException; - /** * Merges the given metadata with the existing metadata for the given * group. @@ -470,6 +472,12 @@ public interface DatabaseComponent { void setContactActive(Transaction txn, ContactId c, boolean active) throws DbException; + /** + * Sets the given group's visibility to the given contact. + */ + void setGroupVisibility(Transaction txn, ContactId c, GroupId g, + Visibility v) throws DbException; + /** * Marks the given message as shared. */ @@ -494,12 +502,6 @@ public interface DatabaseComponent { void setReorderingWindow(Transaction txn, ContactId c, TransportId t, long rotationPeriod, long base, byte[] bitmap) throws DbException; - /** - * Makes a group visible or invisible to a contact. - */ - void setVisibleToContact(Transaction txn, ContactId c, GroupId g, - boolean visible) throws DbException; - /** * Stores the given transport keys, deleting any keys they have replaced. */ diff --git a/briar-api/src/org/briarproject/api/sync/Group.java b/briar-api/src/org/briarproject/api/sync/Group.java index 0a1bc1ee3d350ea39492b27a57663e792ee58132..9ffd3b9cac2464316c4eb7a353e5c274a252dd1e 100644 --- a/briar-api/src/org/briarproject/api/sync/Group.java +++ b/briar-api/src/org/briarproject/api/sync/Group.java @@ -2,6 +2,12 @@ package org.briarproject.api.sync; 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 + } + private final GroupId id; private final ClientId clientId; private final byte[] descriptor; diff --git a/briar-core/src/org/briarproject/blogs/BlogManagerImpl.java b/briar-core/src/org/briarproject/blogs/BlogManagerImpl.java index cfb4706894501bf75fdbd07f6791e0478660eb4a..b593dfce74807f1b5820aa627b97fb81029a31ae 100644 --- a/briar-core/src/org/briarproject/blogs/BlogManagerImpl.java +++ b/briar-core/src/org/briarproject/blogs/BlogManagerImpl.java @@ -12,7 +12,6 @@ import org.briarproject.api.blogs.MessageType; import org.briarproject.api.clients.Client; import org.briarproject.api.clients.ClientHelper; import org.briarproject.api.contact.Contact; -import org.briarproject.api.contact.ContactId; import org.briarproject.api.contact.ContactManager; import org.briarproject.api.data.BdfDictionary; import org.briarproject.api.data.BdfEntry; @@ -68,6 +67,7 @@ import static org.briarproject.api.blogs.MessageType.WRAPPED_COMMENT; import static org.briarproject.api.blogs.MessageType.WRAPPED_POST; import static org.briarproject.api.contact.ContactManager.AddContactHook; import static org.briarproject.api.contact.ContactManager.RemoveContactHook; +import static org.briarproject.api.sync.Group.Visibility.SHARED; import static org.briarproject.blogs.BlogPostValidator.authorToBdfDictionary; @NotNullByDefault @@ -96,35 +96,24 @@ class BlogManagerImpl extends BdfIncomingMessageHook implements BlogManager, @Override public void createLocalState(Transaction txn) throws DbException { - // Ensure that the local identity has its own personal blog - LocalAuthor la = identityManager.getLocalAuthor(txn); - Blog b = blogFactory.createBlog(la); - Group g = b.getGroup(); - if (!db.containsGroup(txn, g.getId())) { - db.addGroup(txn, g); - for (ContactId c : db.getContacts(txn, la.getId())) { - db.setVisibleToContact(txn, c, g.getId(), true); - } - } - // Ensure that we have the personal blogs of all pre-existing contacts + // Create our personal blog if necessary + LocalAuthor a = identityManager.getLocalAuthor(txn); + Blog b = blogFactory.createBlog(a); + db.addGroup(txn, b.getGroup()); + // Ensure that we have the personal blogs of all contacts for (Contact c : db.getContacts(txn)) addingContact(txn, c); } @Override public void addingContact(Transaction txn, Contact c) throws DbException { - // get personal blog of the contact + // Add the personal blog of the contact and share it with the contact Blog b = blogFactory.createBlog(c.getAuthor()); - Group g = b.getGroup(); - if (!db.containsGroup(txn, g.getId())) { - // add the personal blog of the contact - db.addGroup(txn, g); - db.setVisibleToContact(txn, c.getId(), g.getId(), true); - - // share our personal blog with the new contact - LocalAuthor a = db.getLocalAuthor(txn, c.getLocalAuthorId()); - Blog b2 = blogFactory.createBlog(a); - db.setVisibleToContact(txn, c.getId(), b2.getId(), true); - } + db.addGroup(txn, b.getGroup()); + db.setGroupVisibility(txn, c.getId(), b.getId(), SHARED); + // Share our personal blog with the contact + LocalAuthor a = identityManager.getLocalAuthor(txn); + Blog b2 = blogFactory.createBlog(a); + db.setGroupVisibility(txn, c.getId(), b2.getId(), SHARED); } @Override diff --git a/briar-core/src/org/briarproject/db/Database.java b/briar-core/src/org/briarproject/db/Database.java index fe16ac391c9ea39391d44d52f679a78ab103a1e2..1eb5c484d5045dcdbafb96808f52d4eb0bac9c81 100644 --- a/briar-core/src/org/briarproject/db/Database.java +++ b/briar-core/src/org/briarproject/db/Database.java @@ -12,6 +12,7 @@ import org.briarproject.api.identity.LocalAuthor; import org.briarproject.api.settings.Settings; import org.briarproject.api.sync.ClientId; import org.briarproject.api.sync.Group; +import org.briarproject.api.sync.Group.Visibility; import org.briarproject.api.sync.GroupId; import org.briarproject.api.sync.Message; import org.briarproject.api.sync.MessageId; @@ -72,6 +73,13 @@ interface Database<T> { */ void addGroup(T txn, Group g) throws DbException; + /** + * Sets the given group's visibility to the given contact to either + * {@link Visibility VISIBLE} or {@link Visibility SHARED}. + */ + void addGroupVisibility(T txn, ContactId c, GroupId g, boolean shared) + throws DbException; + /** * Stores a local pseudonym. */ @@ -98,7 +106,7 @@ interface Database<T> { * Initialises the status of the given message with respect to the given * contact. * - * @param ack whether the message needs to be acknowledged. + * @param ack whether the message needs to be acknowledged. * @param seen whether the contact has seen the message. */ void addStatus(T txn, ContactId c, MessageId m, boolean ack, boolean seen) @@ -116,11 +124,6 @@ interface Database<T> { void addTransportKeys(T txn, ContactId c, TransportKeys k) throws DbException; - /** - * Makes a group visible to the given contact. - */ - void addVisibility(T txn, ContactId c, GroupId g) throws DbException; - /** * Returns true if the database contains the given contact for the given * local pseudonym. @@ -166,17 +169,9 @@ interface Database<T> { boolean containsTransport(T txn, TransportId t) throws DbException; /** - * Returns true if the database contains the given group and the group is - * visible to the given contact. - * <p/> - * Read-only. - */ - boolean containsVisibleGroup(T txn, ContactId c, GroupId g) - throws DbException; - - /** - * Returns true if the database contains the given message and the message - * is visible to the given contact. + * Returns true if the database contains the given message, the message is + * shared, and the visibility of the message's group to the given contact + * is either {@link Visibility VISIBLE} or {@link Visibility SHARED}. * <p/> * Read-only. */ @@ -267,6 +262,24 @@ interface Database<T> { */ Collection<Group> getGroups(T txn, ClientId c) throws DbException; + /** + * Returns the given group's visibility to the given contact, or + * {@link Visibility INVISIBLE} if the group is not in the database. + * <p/> + * Read-only. + */ + Visibility getGroupVisibility(T txn, ContactId c, GroupId g) + throws DbException; + + /** + * Returns the IDs of all contacts to which the given group's visibility is + * either {@link Visibility VISIBLE} or {@link Visibility SHARED}. + * <p/> + * Read-only. + */ + Collection<ContactId> getGroupVisibility(T txn, GroupId g) + throws DbException; + /** * Returns the local pseudonym with the given ID. * <p/> @@ -477,13 +490,6 @@ interface Database<T> { Map<ContactId, TransportKeys> getTransportKeys(T txn, TransportId t) throws DbException; - /** - * Returns the IDs of all contacts to which the given group is visible. - * <p/> - * Read-only. - */ - Collection<ContactId> getVisibility(T txn, GroupId g) throws DbException; - /** * Increments the outgoing stream counter for the given contact and * transport in the given rotation period. @@ -550,6 +556,13 @@ interface Database<T> { */ void removeGroup(T txn, GroupId g) throws DbException; + /** + * Sets the given group's visibility to the given contact to + * {@link Visibility INVISIBLE}. + */ + void removeGroupVisibility(T txn, ContactId c, GroupId g) + throws DbException; + /** * Removes a local pseudonym (and all associated state) from the database. */ @@ -585,11 +598,6 @@ interface Database<T> { */ void removeTransport(T txn, TransportId t) throws DbException; - /** - * Makes a group invisible to the given contact. - */ - void removeVisibility(T txn, ContactId c, GroupId g) throws DbException; - /** * Resets the transmission count and expiry time of the given message with * respect to the given contact. @@ -607,6 +615,13 @@ interface Database<T> { void setContactActive(T txn, ContactId c, boolean active) throws DbException; + /** + * Sets the given group's visibility to the given contact to either + * {@link Visibility VISIBLE} or {@link Visibility SHARED}. + */ + void setGroupVisibility(T txn, ContactId c, GroupId g, boolean shared) + throws DbException; + /** * Marks the given message as shared. */ diff --git a/briar-core/src/org/briarproject/db/DatabaseComponentImpl.java b/briar-core/src/org/briarproject/db/DatabaseComponentImpl.java index 36ff8b29dbdaf0633ac51553ec3b7ef29f9385f8..392c0b90b18a086ee7c529ffcb858ed1318e04c6 100644 --- a/briar-core/src/org/briarproject/db/DatabaseComponentImpl.java +++ b/briar-core/src/org/briarproject/db/DatabaseComponentImpl.java @@ -42,6 +42,7 @@ import org.briarproject.api.settings.Settings; import org.briarproject.api.sync.Ack; import org.briarproject.api.sync.ClientId; import org.briarproject.api.sync.Group; +import org.briarproject.api.sync.Group.Visibility; import org.briarproject.api.sync.GroupId; import org.briarproject.api.sync.Message; import org.briarproject.api.sync.MessageId; @@ -66,6 +67,8 @@ import java.util.logging.Logger; import javax.inject.Inject; import static java.util.logging.Level.WARNING; +import static org.briarproject.api.sync.Group.Visibility.INVISIBLE; +import static org.briarproject.api.sync.Group.Visibility.SHARED; import static org.briarproject.api.sync.ValidationManager.State.DELIVERED; import static org.briarproject.api.sync.ValidationManager.State.UNKNOWN; import static org.briarproject.db.DatabaseConstants.MAX_OFFERED_MESSAGES; @@ -97,6 +100,7 @@ class DatabaseComponentImpl<T> implements DatabaseComponent { @Override public boolean open() throws DbException { Runnable shutdownHook = new Runnable() { + @Override public void run() { try { close(); @@ -223,7 +227,7 @@ class DatabaseComponentImpl<T> implements DatabaseComponent { private void addMessage(T txn, Message m, State state, boolean shared, @Nullable ContactId sender) throws DbException { db.addMessage(txn, m, state, shared); - for (ContactId c : db.getVisibility(txn, m.getGroupId())) { + for (ContactId c : db.getGroupVisibility(txn, m.getGroupId())) { boolean offered = db.removeOfferedMessage(txn, c, m.getId()); boolean seen = offered || c.equals(sender); db.addStatus(txn, c, m.getId(), seen, seen); @@ -441,6 +445,15 @@ class DatabaseComponentImpl<T> implements DatabaseComponent { return db.getGroups(txn, c); } + @Override + public Visibility getGroupVisibility(Transaction transaction, ContactId c, + GroupId g) throws DbException { + T txn = unbox(transaction); + if (!db.containsContact(txn, c)) + throw new NoSuchContactException(); + return db.getGroupVisibility(txn, c, g); + } + @Override public LocalAuthor getLocalAuthor(Transaction transaction, AuthorId a) throws DbException { @@ -602,17 +615,6 @@ class DatabaseComponentImpl<T> implements DatabaseComponent { db.incrementStreamCounter(txn, c, t, rotationPeriod); } - @Override - public boolean isVisibleToContact(Transaction transaction, ContactId c, - GroupId g) throws DbException { - T txn = unbox(transaction); - if (!db.containsContact(txn, c)) - throw new NoSuchContactException(); - if (!db.containsGroup(txn, g)) - throw new NoSuchGroupException(); - return db.containsVisibleGroup(txn, c, g); - } - @Override public void mergeGroupMetadata(Transaction transaction, GroupId g, Metadata meta) throws DbException { @@ -672,7 +674,7 @@ class DatabaseComponentImpl<T> implements DatabaseComponent { T txn = unbox(transaction); if (!db.containsContact(txn, c)) throw new NoSuchContactException(); - if (db.containsVisibleGroup(txn, c, m.getGroupId())) { + if (db.getGroupVisibility(txn, c, m.getGroupId()) != INVISIBLE) { if (db.containsMessage(txn, m.getId())) { db.raiseSeenFlag(txn, c, m.getId()); db.raiseAckFlag(txn, c, m.getId()); @@ -745,7 +747,7 @@ class DatabaseComponentImpl<T> implements DatabaseComponent { GroupId id = g.getId(); if (!db.containsGroup(txn, id)) throw new NoSuchGroupException(); - Collection<ContactId> affected = db.getVisibility(txn, id); + Collection<ContactId> affected = db.getGroupVisibility(txn, id); db.removeGroup(txn, id); transaction.attach(new GroupRemovedEvent(g)); transaction.attach(new GroupVisibilityUpdatedEvent(affected)); @@ -794,6 +796,34 @@ class DatabaseComponentImpl<T> implements DatabaseComponent { transaction.attach(new ContactStatusChangedEvent(c, active)); } + @Override + public void setGroupVisibility(Transaction transaction, ContactId c, + GroupId g, Visibility v) throws DbException { + if (transaction.isReadOnly()) throw new IllegalArgumentException(); + T txn = unbox(transaction); + if (!db.containsContact(txn, c)) + throw new NoSuchContactException(); + if (!db.containsGroup(txn, g)) + throw new NoSuchGroupException(); + Visibility old = db.getGroupVisibility(txn, c, g); + if (old == v) return; + if (old == INVISIBLE) { + db.addGroupVisibility(txn, c, g, v == SHARED); + for (MessageId m : db.getMessageIds(txn, g)) { + boolean seen = db.removeOfferedMessage(txn, c, m); + db.addStatus(txn, c, m, seen, seen); + } + } else if (v == INVISIBLE) { + db.removeGroupVisibility(txn, c, g); + for (MessageId m : db.getMessageIds(txn, g)) + db.removeStatus(txn, c, m); + } else { + db.setGroupVisibility(txn, c, g, v == SHARED); + } + List<ContactId> affected = Collections.singletonList(c); + transaction.attach(new GroupVisibilityUpdatedEvent(affected)); + } + @Override public void setMessageShared(Transaction transaction, MessageId m) throws DbException { @@ -845,33 +875,6 @@ class DatabaseComponentImpl<T> implements DatabaseComponent { db.setReorderingWindow(txn, c, t, rotationPeriod, base, bitmap); } - @Override - public void setVisibleToContact(Transaction transaction, ContactId c, - GroupId g, boolean visible) throws DbException { - if (transaction.isReadOnly()) throw new IllegalArgumentException(); - T txn = unbox(transaction); - if (!db.containsContact(txn, c)) - throw new NoSuchContactException(); - if (!db.containsGroup(txn, g)) - throw new NoSuchGroupException(); - boolean wasVisible = db.containsVisibleGroup(txn, c, g); - if (visible && !wasVisible) { - db.addVisibility(txn, c, g); - for (MessageId m : db.getMessageIds(txn, g)) { - boolean seen = db.removeOfferedMessage(txn, c, m); - db.addStatus(txn, c, m, seen, seen); - } - } else if (!visible && wasVisible) { - db.removeVisibility(txn, c, g); - for (MessageId m : db.getMessageIds(txn, g)) - db.removeStatus(txn, c, m); - } - if (visible != wasVisible) { - List<ContactId> affected = Collections.singletonList(c); - transaction.attach(new GroupVisibilityUpdatedEvent(affected)); - } - } - @Override public void updateTransportKeys(Transaction transaction, Map<ContactId, TransportKeys> keys) throws DbException { diff --git a/briar-core/src/org/briarproject/db/JdbcDatabase.java b/briar-core/src/org/briarproject/db/JdbcDatabase.java index 3ee7fadd6fe9c336d5dd31c6663c3793b26c6e30..cce35cacad8b07d966f0246ef8cef5bd285337be 100644 --- a/briar-core/src/org/briarproject/db/JdbcDatabase.java +++ b/briar-core/src/org/briarproject/db/JdbcDatabase.java @@ -15,6 +15,7 @@ import org.briarproject.api.identity.LocalAuthor; import org.briarproject.api.settings.Settings; import org.briarproject.api.sync.ClientId; import org.briarproject.api.sync.Group; +import org.briarproject.api.sync.Group.Visibility; import org.briarproject.api.sync.GroupId; import org.briarproject.api.sync.Message; import org.briarproject.api.sync.MessageId; @@ -50,6 +51,9 @@ import java.util.logging.Logger; import static java.util.logging.Level.WARNING; import static org.briarproject.api.db.Metadata.REMOVE; +import static org.briarproject.api.sync.Group.Visibility.INVISIBLE; +import static org.briarproject.api.sync.Group.Visibility.SHARED; +import static org.briarproject.api.sync.Group.Visibility.VISIBLE; import static org.briarproject.api.sync.ValidationManager.State.DELIVERED; import static org.briarproject.api.sync.ValidationManager.State.INVALID; import static org.briarproject.api.sync.ValidationManager.State.PENDING; @@ -67,8 +71,8 @@ import static org.briarproject.db.ExponentialBackoff.calculateExpiry; */ abstract class JdbcDatabase implements Database<Connection> { - private static final int SCHEMA_VERSION = 28; - private static final int MIN_SCHEMA_VERSION = 28; + private static final int SCHEMA_VERSION = 29; + private static final int MIN_SCHEMA_VERSION = 29; private static final String CREATE_SETTINGS = "CREATE TABLE settings" @@ -121,6 +125,7 @@ abstract class JdbcDatabase implements Database<Connection> { "CREATE TABLE groupVisibilities" + " (contactId INT NOT NULL," + " groupId HASH NOT NULL," + + " shared BOOLEAN NOT NULL," + " PRIMARY KEY (contactId, groupId)," + " FOREIGN KEY (contactId)" + " REFERENCES contacts (contactId)" @@ -356,6 +361,7 @@ abstract class JdbcDatabase implements Database<Connection> { return s; } + @Override public Connection startTransaction() throws DbException { Connection txn = null; connectionsLock.lock(); @@ -384,6 +390,7 @@ abstract class JdbcDatabase implements Database<Connection> { return txn; } + @Override public void abortTransaction(Connection txn) { try { txn.rollback(); @@ -414,6 +421,7 @@ abstract class JdbcDatabase implements Database<Connection> { } } + @Override public void commitTransaction(Connection txn) throws DbException { try { txn.commit(); @@ -429,7 +437,7 @@ abstract class JdbcDatabase implements Database<Connection> { } } - protected void closeAllConnections() throws SQLException { + void closeAllConnections() throws SQLException { boolean interrupted = false; connectionsLock.lock(); try { @@ -455,11 +463,13 @@ abstract class JdbcDatabase implements Database<Connection> { if (interrupted) Thread.currentThread().interrupt(); } + @Override public DeviceId getDeviceId(Connection txn) throws DbException { Settings s = getSettings(txn, DEVICE_SETTINGS_NAMESPACE); return new DeviceId(StringUtils.fromHexString(s.get(DEVICE_ID_KEY))); } + @Override public ContactId addContact(Connection txn, Author remote, AuthorId local, boolean verified, boolean active) throws DbException { PreparedStatement ps = null; @@ -497,6 +507,7 @@ abstract class JdbcDatabase implements Database<Connection> { } } + @Override public void addGroup(Connection txn, Group g) throws DbException { PreparedStatement ps = null; try { @@ -515,6 +526,28 @@ abstract class JdbcDatabase implements Database<Connection> { } } + @Override + public void addGroupVisibility(Connection txn, ContactId c, GroupId g, + boolean shared) throws DbException { + PreparedStatement ps = null; + try { + String sql = "INSERT INTO groupVisibilities" + + " (contactId, groupId, shared)" + + " VALUES (?, ?, ?)"; + ps = txn.prepareStatement(sql); + ps.setInt(1, c.getInt()); + ps.setBytes(2, g.getBytes()); + ps.setBoolean(3, shared); + int affected = ps.executeUpdate(); + if (affected != 1) throw new DbStateException(); + ps.close(); + } catch (SQLException e) { + tryToClose(ps); + throw new DbException(e); + } + } + + @Override public void addLocalAuthor(Connection txn, LocalAuthor a) throws DbException { PreparedStatement ps = null; @@ -537,6 +570,7 @@ abstract class JdbcDatabase implements Database<Connection> { } } + @Override public void addMessage(Connection txn, Message m, State state, boolean shared) throws DbException { PreparedStatement ps = null; @@ -562,6 +596,7 @@ abstract class JdbcDatabase implements Database<Connection> { } } + @Override public void addOfferedMessage(Connection txn, ContactId c, MessageId m) throws DbException { PreparedStatement ps = null; @@ -592,6 +627,7 @@ abstract class JdbcDatabase implements Database<Connection> { } } + @Override public void addStatus(Connection txn, ContactId c, MessageId m, boolean ack, boolean seen) throws DbException { PreparedStatement ps = null; @@ -613,6 +649,7 @@ abstract class JdbcDatabase implements Database<Connection> { } } + @Override public void addMessageDependency(Connection txn, GroupId g, MessageId dependent, MessageId dependency) throws DbException { PreparedStatement ps = null; @@ -633,6 +670,7 @@ abstract class JdbcDatabase implements Database<Connection> { } } + @Override public void addTransport(Connection txn, TransportId t, int maxLatency) throws DbException { PreparedStatement ps = null; @@ -651,6 +689,7 @@ abstract class JdbcDatabase implements Database<Connection> { } } + @Override public void addTransportKeys(Connection txn, ContactId c, TransportKeys k) throws DbException { PreparedStatement ps = null; @@ -712,24 +751,7 @@ abstract class JdbcDatabase implements Database<Connection> { } } - public void addVisibility(Connection txn, ContactId c, GroupId g) - throws DbException { - PreparedStatement ps = null; - try { - String sql = "INSERT INTO groupVisibilities (contactId, groupId)" - + " VALUES (?, ?)"; - ps = txn.prepareStatement(sql); - ps.setInt(1, c.getInt()); - ps.setBytes(2, g.getBytes()); - int affected = ps.executeUpdate(); - if (affected != 1) throw new DbStateException(); - ps.close(); - } catch (SQLException e) { - tryToClose(ps); - throw new DbException(e); - } - } - + @Override public boolean containsContact(Connection txn, AuthorId remote, AuthorId local) throws DbException { PreparedStatement ps = null; @@ -753,6 +775,7 @@ abstract class JdbcDatabase implements Database<Connection> { } } + @Override public boolean containsContact(Connection txn, ContactId c) throws DbException { PreparedStatement ps = null; @@ -774,6 +797,7 @@ abstract class JdbcDatabase implements Database<Connection> { } } + @Override public boolean containsGroup(Connection txn, GroupId g) throws DbException { PreparedStatement ps = null; @@ -795,6 +819,7 @@ abstract class JdbcDatabase implements Database<Connection> { } } + @Override public boolean containsLocalAuthor(Connection txn, AuthorId a) throws DbException { PreparedStatement ps = null; @@ -816,6 +841,7 @@ abstract class JdbcDatabase implements Database<Connection> { } } + @Override public boolean containsMessage(Connection txn, MessageId m) throws DbException { PreparedStatement ps = null; @@ -837,6 +863,7 @@ abstract class JdbcDatabase implements Database<Connection> { } } + @Override public boolean containsTransport(Connection txn, TransportId t) throws DbException { PreparedStatement ps = null; @@ -858,29 +885,7 @@ abstract class JdbcDatabase implements Database<Connection> { } } - public boolean containsVisibleGroup(Connection txn, ContactId c, GroupId g) - throws DbException { - PreparedStatement ps = null; - ResultSet rs = null; - try { - String sql = "SELECT NULL FROM groupVisibilities" - + " WHERE contactId = ? AND groupId = ?"; - ps = txn.prepareStatement(sql); - ps.setInt(1, c.getInt()); - ps.setBytes(2, g.getBytes()); - rs = ps.executeQuery(); - boolean found = rs.next(); - if (rs.next()) throw new DbStateException(); - rs.close(); - ps.close(); - return found; - } catch (SQLException e) { - tryToClose(rs); - tryToClose(ps); - throw new DbException(e); - } - } - + @Override public boolean containsVisibleMessage(Connection txn, ContactId c, MessageId m) throws DbException { PreparedStatement ps = null; @@ -891,7 +896,7 @@ abstract class JdbcDatabase implements Database<Connection> { + " ON m.groupId = gv.groupId" + " WHERE messageId = ?" + " AND contactId = ?" - + " AND shared = TRUE"; + + " AND m.shared = TRUE"; ps = txn.prepareStatement(sql); ps.setBytes(1, m.getBytes()); ps.setInt(2, c.getInt()); @@ -908,6 +913,7 @@ abstract class JdbcDatabase implements Database<Connection> { } } + @Override public int countOfferedMessages(Connection txn, ContactId c) throws DbException { PreparedStatement ps = null; @@ -931,6 +937,7 @@ abstract class JdbcDatabase implements Database<Connection> { } } + @Override public void deleteMessage(Connection txn, MessageId m) throws DbException { PreparedStatement ps = null; try { @@ -947,6 +954,7 @@ abstract class JdbcDatabase implements Database<Connection> { } } + @Override public void deleteMessageMetadata(Connection txn, MessageId m) throws DbException { PreparedStatement ps = null; @@ -963,6 +971,7 @@ abstract class JdbcDatabase implements Database<Connection> { } } + @Override public Contact getContact(Connection txn, ContactId c) throws DbException { PreparedStatement ps = null; ResultSet rs = null; @@ -992,6 +1001,7 @@ abstract class JdbcDatabase implements Database<Connection> { } } + @Override public Collection<Contact> getContacts(Connection txn) throws DbException { PreparedStatement ps = null; @@ -1017,7 +1027,7 @@ abstract class JdbcDatabase implements Database<Connection> { } rs.close(); ps.close(); - return Collections.unmodifiableList(contacts); + return contacts; } catch (SQLException e) { tryToClose(rs); tryToClose(ps); @@ -1025,6 +1035,7 @@ abstract class JdbcDatabase implements Database<Connection> { } } + @Override public Collection<ContactId> getContacts(Connection txn, AuthorId local) throws DbException { PreparedStatement ps = null; @@ -1039,7 +1050,7 @@ abstract class JdbcDatabase implements Database<Connection> { while (rs.next()) ids.add(new ContactId(rs.getInt(1))); rs.close(); ps.close(); - return Collections.unmodifiableList(ids); + return ids; } catch (SQLException e) { tryToClose(rs); tryToClose(ps); @@ -1047,6 +1058,7 @@ abstract class JdbcDatabase implements Database<Connection> { } } + @Override public Collection<Contact> getContactsByAuthorId(Connection txn, AuthorId remote) throws DbException { PreparedStatement ps = null; @@ -1073,7 +1085,7 @@ abstract class JdbcDatabase implements Database<Connection> { } rs.close(); ps.close(); - return Collections.unmodifiableList(contacts); + return contacts; } catch (SQLException e) { tryToClose(rs); tryToClose(ps); @@ -1081,6 +1093,7 @@ abstract class JdbcDatabase implements Database<Connection> { } } + @Override public Group getGroup(Connection txn, GroupId g) throws DbException { PreparedStatement ps = null; ResultSet rs = null; @@ -1103,6 +1116,7 @@ abstract class JdbcDatabase implements Database<Connection> { } } + @Override public Collection<Group> getGroups(Connection txn, ClientId c) throws DbException { PreparedStatement ps = null; @@ -1121,7 +1135,33 @@ abstract class JdbcDatabase implements Database<Connection> { } rs.close(); ps.close(); - return Collections.unmodifiableList(groups); + return groups; + } catch (SQLException e) { + tryToClose(rs); + tryToClose(ps); + throw new DbException(e); + } + } + + @Override + public Visibility getGroupVisibility(Connection txn, ContactId c, GroupId g) + throws DbException { + PreparedStatement ps = null; + ResultSet rs = null; + try { + String sql = "SELECT shared FROM groupVisibilities" + + " WHERE contactId = ? AND groupId = ?"; + ps = txn.prepareStatement(sql); + ps.setInt(1, c.getInt()); + ps.setBytes(2, g.getBytes()); + rs = ps.executeQuery(); + Visibility v; + if (rs.next()) v = rs.getBoolean(1) ? SHARED : VISIBLE; + else v = INVISIBLE; + if (rs.next()) throw new DbStateException(); + rs.close(); + ps.close(); + return v; } catch (SQLException e) { tryToClose(rs); tryToClose(ps); @@ -1129,6 +1169,30 @@ abstract class JdbcDatabase implements Database<Connection> { } } + @Override + public Collection<ContactId> getGroupVisibility(Connection txn, GroupId g) + throws DbException { + PreparedStatement ps = null; + ResultSet rs = null; + try { + String sql = "SELECT contactId FROM groupVisibilities" + + " WHERE groupId = ?"; + ps = txn.prepareStatement(sql); + ps.setBytes(1, g.getBytes()); + rs = ps.executeQuery(); + List<ContactId> visible = new ArrayList<ContactId>(); + while (rs.next()) visible.add(new ContactId(rs.getInt(1))); + rs.close(); + ps.close(); + return visible; + } catch (SQLException e) { + tryToClose(rs); + tryToClose(ps); + throw new DbException(e); + } + } + + @Override public LocalAuthor getLocalAuthor(Connection txn, AuthorId a) throws DbException { PreparedStatement ps = null; @@ -1158,6 +1222,7 @@ abstract class JdbcDatabase implements Database<Connection> { } } + @Override public Collection<LocalAuthor> getLocalAuthors(Connection txn) throws DbException { PreparedStatement ps = null; @@ -1179,7 +1244,7 @@ abstract class JdbcDatabase implements Database<Connection> { } rs.close(); ps.close(); - return Collections.unmodifiableList(authors); + return authors; } catch (SQLException e) { tryToClose(rs); tryToClose(ps); @@ -1187,6 +1252,7 @@ abstract class JdbcDatabase implements Database<Connection> { } } + @Override public Collection<MessageId> getMessageIds(Connection txn, GroupId g) throws DbException { PreparedStatement ps = null; @@ -1200,7 +1266,7 @@ abstract class JdbcDatabase implements Database<Connection> { while (rs.next()) ids.add(new MessageId(rs.getBytes(1))); rs.close(); ps.close(); - return Collections.unmodifiableList(ids); + return ids; } catch (SQLException e) { tryToClose(rs); tryToClose(ps); @@ -1223,7 +1289,7 @@ abstract class JdbcDatabase implements Database<Connection> { while (rs.next()) ids.add(new MessageId(rs.getBytes(1))); rs.close(); ps.close(); - return Collections.unmodifiableList(ids); + return ids; } catch (SQLException e) { tryToClose(rs); tryToClose(ps); @@ -1231,6 +1297,7 @@ abstract class JdbcDatabase implements Database<Connection> { } } + @Override public Collection<MessageId> getMessageIds(Connection txn, GroupId g, Metadata query) throws DbException { // If there are no query terms, return all delivered messages @@ -1262,8 +1329,8 @@ abstract class JdbcDatabase implements Database<Connection> { // Return early if there are no matches if (intersection.isEmpty()) return Collections.emptySet(); } - if (intersection == null) throw new IllegalStateException(); - return Collections.unmodifiableSet(intersection); + if (intersection == null) throw new AssertionError(); + return intersection; } catch (SQLException e) { tryToClose(rs); tryToClose(ps); @@ -1271,6 +1338,7 @@ abstract class JdbcDatabase implements Database<Connection> { } } + @Override public Map<MessageId, Metadata> getMessageMetadata(Connection txn, GroupId g) throws DbException { PreparedStatement ps = null; @@ -1300,7 +1368,7 @@ abstract class JdbcDatabase implements Database<Connection> { } rs.close(); ps.close(); - return Collections.unmodifiableMap(all); + return all; } catch (SQLException e) { tryToClose(rs); tryToClose(ps); @@ -1308,6 +1376,7 @@ abstract class JdbcDatabase implements Database<Connection> { } } + @Override public Map<MessageId, Metadata> getMessageMetadata(Connection txn, GroupId g, Metadata query) throws DbException { // Retrieve the matching message IDs @@ -1317,9 +1386,10 @@ abstract class JdbcDatabase implements Database<Connection> { Map<MessageId, Metadata> all = new HashMap<MessageId, Metadata>( matches.size()); for (MessageId m : matches) all.put(m, getMessageMetadata(txn, m)); - return Collections.unmodifiableMap(all); + return all; } + @Override public Metadata getGroupMetadata(Connection txn, GroupId g) throws DbException { PreparedStatement ps = null; @@ -1342,6 +1412,7 @@ abstract class JdbcDatabase implements Database<Connection> { } } + @Override public Metadata getMessageMetadata(Connection txn, MessageId m) throws DbException { PreparedStatement ps = null; @@ -1367,6 +1438,7 @@ abstract class JdbcDatabase implements Database<Connection> { } } + @Override public Metadata getMessageMetadataForValidator(Connection txn, MessageId m) throws DbException { PreparedStatement ps = null; @@ -1394,6 +1466,7 @@ abstract class JdbcDatabase implements Database<Connection> { } } + @Override public Collection<MessageStatus> getMessageStatus(Connection txn, ContactId c, GroupId g) throws DbException { PreparedStatement ps = null; @@ -1418,7 +1491,7 @@ abstract class JdbcDatabase implements Database<Connection> { } rs.close(); ps.close(); - return Collections.unmodifiableList(statuses); + return statuses; } catch (SQLException e) { tryToClose(rs); tryToClose(ps); @@ -1426,6 +1499,7 @@ abstract class JdbcDatabase implements Database<Connection> { } } + @Override public MessageStatus getMessageStatus(Connection txn, ContactId c, MessageId m) throws DbException { PreparedStatement ps = null; @@ -1453,6 +1527,7 @@ abstract class JdbcDatabase implements Database<Connection> { } } + @Override public Map<MessageId, State> getMessageDependencies(Connection txn, MessageId m) throws DbException { PreparedStatement ps = null; @@ -1482,7 +1557,7 @@ abstract class JdbcDatabase implements Database<Connection> { } rs.close(); ps.close(); - return Collections.unmodifiableMap(dependencies); + return dependencies; } catch (SQLException e) { tryToClose(rs); tryToClose(ps); @@ -1490,6 +1565,7 @@ abstract class JdbcDatabase implements Database<Connection> { } } + @Override public Map<MessageId, State> getMessageDependents(Connection txn, MessageId m) throws DbException { PreparedStatement ps = null; @@ -1511,7 +1587,7 @@ abstract class JdbcDatabase implements Database<Connection> { } rs.close(); ps.close(); - return Collections.unmodifiableMap(dependents); + return dependents; } catch (SQLException e) { tryToClose(rs); tryToClose(ps); @@ -1519,6 +1595,7 @@ abstract class JdbcDatabase implements Database<Connection> { } } + @Override public State getMessageState(Connection txn, MessageId m) throws DbException { PreparedStatement ps = null; @@ -1541,6 +1618,7 @@ abstract class JdbcDatabase implements Database<Connection> { } } + @Override public Collection<MessageId> getMessagesToAck(Connection txn, ContactId c, int maxMessages) throws DbException { PreparedStatement ps = null; @@ -1557,7 +1635,7 @@ abstract class JdbcDatabase implements Database<Connection> { while (rs.next()) ids.add(new MessageId(rs.getBytes(1))); rs.close(); ps.close(); - return Collections.unmodifiableList(ids); + return ids; } catch (SQLException e) { tryToClose(rs); tryToClose(ps); @@ -1565,6 +1643,7 @@ abstract class JdbcDatabase implements Database<Connection> { } } + @Override public Collection<MessageId> getMessagesToOffer(Connection txn, ContactId c, int maxMessages) throws DbException { long now = clock.currentTimeMillis(); @@ -1572,10 +1651,13 @@ abstract class JdbcDatabase implements Database<Connection> { ResultSet rs = null; try { String sql = "SELECT m.messageId FROM messages AS m" + + " JOIN groupVisibilities AS gv" + + " ON m.groupId = gv.groupId" + " JOIN statuses AS s" + " ON m.messageId = s.messageId" - + " WHERE contactId = ?" - + " AND state = ? AND shared = TRUE AND raw IS NOT NULL" + + " AND gv.contactId = s.contactId" + + " WHERE gv.contactId = ? AND gv.shared = TRUE" + + " AND state = ? AND m.shared = TRUE AND raw IS NOT NULL" + " AND seen = FALSE AND requested = FALSE" + " AND expiry < ?" + " ORDER BY timestamp LIMIT ?"; @@ -1589,7 +1671,7 @@ abstract class JdbcDatabase implements Database<Connection> { while (rs.next()) ids.add(new MessageId(rs.getBytes(1))); rs.close(); ps.close(); - return Collections.unmodifiableList(ids); + return ids; } catch (SQLException e) { tryToClose(rs); tryToClose(ps); @@ -1597,6 +1679,7 @@ abstract class JdbcDatabase implements Database<Connection> { } } + @Override public Collection<MessageId> getMessagesToRequest(Connection txn, ContactId c, int maxMessages) throws DbException { PreparedStatement ps = null; @@ -1613,7 +1696,7 @@ abstract class JdbcDatabase implements Database<Connection> { while (rs.next()) ids.add(new MessageId(rs.getBytes(1))); rs.close(); ps.close(); - return Collections.unmodifiableList(ids); + return ids; } catch (SQLException e) { tryToClose(rs); tryToClose(ps); @@ -1621,6 +1704,7 @@ abstract class JdbcDatabase implements Database<Connection> { } } + @Override public Collection<MessageId> getMessagesToSend(Connection txn, ContactId c, int maxLength) throws DbException { long now = clock.currentTimeMillis(); @@ -1628,10 +1712,13 @@ abstract class JdbcDatabase implements Database<Connection> { ResultSet rs = null; try { String sql = "SELECT length, m.messageId FROM messages AS m" + + " JOIN groupVisibilities AS gv" + + " ON m.groupId = gv.groupId" + " JOIN statuses AS s" + " ON m.messageId = s.messageId" - + " WHERE contactId = ?" - + " AND state = ? AND shared = TRUE AND raw IS NOT NULL" + + " AND gv.contactId = s.contactId" + + " WHERE gv.contactId = ? AND gv.shared = TRUE" + + " AND state = ? AND m.shared = TRUE AND raw IS NOT NULL" + " AND seen = FALSE" + " AND expiry < ?" + " ORDER BY timestamp"; @@ -1650,7 +1737,7 @@ abstract class JdbcDatabase implements Database<Connection> { } rs.close(); ps.close(); - return Collections.unmodifiableList(ids); + return ids; } catch (SQLException e) { tryToClose(rs); tryToClose(ps); @@ -1658,11 +1745,13 @@ abstract class JdbcDatabase implements Database<Connection> { } } + @Override public Collection<MessageId> getMessagesToValidate(Connection txn, ClientId c) throws DbException { return getMessagesInState(txn, c, UNKNOWN); } + @Override public Collection<MessageId> getPendingMessages(Connection txn, ClientId c) throws DbException { return getMessagesInState(txn, c, PENDING); @@ -1684,7 +1773,7 @@ abstract class JdbcDatabase implements Database<Connection> { while (rs.next()) ids.add(new MessageId(rs.getBytes(1))); rs.close(); ps.close(); - return Collections.unmodifiableList(ids); + return ids; } catch (SQLException e) { tryToClose(rs); tryToClose(ps); @@ -1692,6 +1781,7 @@ abstract class JdbcDatabase implements Database<Connection> { } } + @Override public Collection<MessageId> getMessagesToShare( Connection txn, ClientId c) throws DbException { PreparedStatement ps = null; @@ -1713,7 +1803,7 @@ abstract class JdbcDatabase implements Database<Connection> { while (rs.next()) ids.add(new MessageId(rs.getBytes(1))); rs.close(); ps.close(); - return Collections.unmodifiableList(ids); + return ids; } catch (SQLException e) { tryToClose(rs); tryToClose(ps); @@ -1721,6 +1811,7 @@ abstract class JdbcDatabase implements Database<Connection> { } } + @Override @Nullable public byte[] getRawMessage(Connection txn, MessageId m) throws DbException { @@ -1744,6 +1835,7 @@ abstract class JdbcDatabase implements Database<Connection> { } } + @Override public Collection<MessageId> getRequestedMessagesToSend(Connection txn, ContactId c, int maxLength) throws DbException { long now = clock.currentTimeMillis(); @@ -1751,10 +1843,13 @@ abstract class JdbcDatabase implements Database<Connection> { ResultSet rs = null; try { String sql = "SELECT length, m.messageId FROM messages AS m" + + " JOIN groupVisibilities AS gv" + + " ON m.groupId = gv.groupId" + " JOIN statuses AS s" + " ON m.messageId = s.messageId" - + " WHERE contactId = ?" - + " AND state = ? AND shared = TRUE AND raw IS NOT NULL" + + " AND gv.contactId = s.contactId" + + " WHERE gv.contactId = ? AND gv.shared = TRUE" + + " AND state = ? AND m.shared = TRUE AND raw IS NOT NULL" + " AND seen = FALSE AND requested = TRUE" + " AND expiry < ?" + " ORDER BY timestamp"; @@ -1773,7 +1868,7 @@ abstract class JdbcDatabase implements Database<Connection> { } rs.close(); ps.close(); - return Collections.unmodifiableList(ids); + return ids; } catch (SQLException e) { tryToClose(rs); tryToClose(ps); @@ -1781,6 +1876,7 @@ abstract class JdbcDatabase implements Database<Connection> { } } + @Override public Settings getSettings(Connection txn, String namespace) throws DbException { PreparedStatement ps = null; @@ -1802,6 +1898,7 @@ abstract class JdbcDatabase implements Database<Connection> { } } + @Override public Map<ContactId, TransportKeys> getTransportKeys(Connection txn, TransportId t) throws DbException { PreparedStatement ps = null; @@ -1855,29 +1952,7 @@ abstract class JdbcDatabase implements Database<Connection> { } rs.close(); ps.close(); - return Collections.unmodifiableMap(keys); - } catch (SQLException e) { - tryToClose(rs); - tryToClose(ps); - throw new DbException(e); - } - } - - public Collection<ContactId> getVisibility(Connection txn, GroupId g) - throws DbException { - PreparedStatement ps = null; - ResultSet rs = null; - try { - String sql = "SELECT contactId FROM groupVisibilities" - + " WHERE groupId = ?"; - ps = txn.prepareStatement(sql); - ps.setBytes(1, g.getBytes()); - rs = ps.executeQuery(); - List<ContactId> visible = new ArrayList<ContactId>(); - while (rs.next()) visible.add(new ContactId(rs.getInt(1))); - rs.close(); - ps.close(); - return Collections.unmodifiableList(visible); + return keys; } catch (SQLException e) { tryToClose(rs); tryToClose(ps); @@ -1885,6 +1960,7 @@ abstract class JdbcDatabase implements Database<Connection> { } } + @Override public void incrementStreamCounter(Connection txn, ContactId c, TransportId t, long rotationPeriod) throws DbException { PreparedStatement ps = null; @@ -1904,6 +1980,7 @@ abstract class JdbcDatabase implements Database<Connection> { } } + @Override public void lowerAckFlag(Connection txn, ContactId c, Collection<MessageId> acked) throws DbException { PreparedStatement ps = null; @@ -1930,6 +2007,7 @@ abstract class JdbcDatabase implements Database<Connection> { } } + @Override public void lowerRequestedFlag(Connection txn, ContactId c, Collection<MessageId> requested) throws DbException { PreparedStatement ps = null; @@ -1956,11 +2034,13 @@ abstract class JdbcDatabase implements Database<Connection> { } } + @Override public void mergeGroupMetadata(Connection txn, GroupId g, Metadata meta) throws DbException { mergeMetadata(txn, g.getBytes(), meta, "groupMetadata", "groupId"); } + @Override public void mergeMessageMetadata(Connection txn, MessageId m, Metadata meta) throws DbException { mergeMetadata(txn, m.getBytes(), meta, "messageMetadata", "messageId"); @@ -2041,6 +2121,7 @@ abstract class JdbcDatabase implements Database<Connection> { } } + @Override public void mergeSettings(Connection txn, Settings s, String namespace) throws DbException { PreparedStatement ps = null; @@ -2087,6 +2168,7 @@ abstract class JdbcDatabase implements Database<Connection> { } } + @Override public void raiseAckFlag(Connection txn, ContactId c, MessageId m) throws DbException { PreparedStatement ps = null; @@ -2105,6 +2187,7 @@ abstract class JdbcDatabase implements Database<Connection> { } } + @Override public void raiseRequestedFlag(Connection txn, ContactId c, MessageId m) throws DbException { PreparedStatement ps = null; @@ -2123,6 +2206,7 @@ abstract class JdbcDatabase implements Database<Connection> { } } + @Override public void raiseSeenFlag(Connection txn, ContactId c, MessageId m) throws DbException { PreparedStatement ps = null; @@ -2141,6 +2225,7 @@ abstract class JdbcDatabase implements Database<Connection> { } } + @Override public void removeContact(Connection txn, ContactId c) throws DbException { PreparedStatement ps = null; @@ -2157,6 +2242,7 @@ abstract class JdbcDatabase implements Database<Connection> { } } + @Override public void removeGroup(Connection txn, GroupId g) throws DbException { PreparedStatement ps = null; try { @@ -2172,6 +2258,26 @@ abstract class JdbcDatabase implements Database<Connection> { } } + @Override + public void removeGroupVisibility(Connection txn, ContactId c, GroupId g) + throws DbException { + PreparedStatement ps = null; + try { + String sql = "DELETE FROM groupVisibilities" + + " WHERE contactId = ? AND groupId = ?"; + ps = txn.prepareStatement(sql); + ps.setInt(1, c.getInt()); + ps.setBytes(2, g.getBytes()); + int affected = ps.executeUpdate(); + if (affected != 1) throw new DbStateException(); + ps.close(); + } catch (SQLException e) { + tryToClose(ps); + throw new DbException(e); + } + } + + @Override public void removeLocalAuthor(Connection txn, AuthorId a) throws DbException { PreparedStatement ps = null; @@ -2188,6 +2294,7 @@ abstract class JdbcDatabase implements Database<Connection> { } } + @Override public void removeMessage(Connection txn, MessageId m) throws DbException { PreparedStatement ps = null; try { @@ -2203,6 +2310,7 @@ abstract class JdbcDatabase implements Database<Connection> { } } + @Override public boolean removeOfferedMessage(Connection txn, ContactId c, MessageId m) throws DbException { PreparedStatement ps = null; @@ -2222,6 +2330,7 @@ abstract class JdbcDatabase implements Database<Connection> { } } + @Override public void removeOfferedMessages(Connection txn, ContactId c, Collection<MessageId> requested) throws DbException { PreparedStatement ps = null; @@ -2246,6 +2355,7 @@ abstract class JdbcDatabase implements Database<Connection> { } } + @Override public void removeStatus(Connection txn, ContactId c, MessageId m) throws DbException { PreparedStatement ps = null; @@ -2264,6 +2374,7 @@ abstract class JdbcDatabase implements Database<Connection> { } } + @Override public void removeTransport(Connection txn, TransportId t) throws DbException { PreparedStatement ps = null; @@ -2280,17 +2391,18 @@ abstract class JdbcDatabase implements Database<Connection> { } } - public void removeVisibility(Connection txn, ContactId c, GroupId g) + @Override + public void resetExpiryTime(Connection txn, ContactId c, MessageId m) throws DbException { PreparedStatement ps = null; try { - String sql = "DELETE FROM groupVisibilities" - + " WHERE contactId = ? AND groupId = ?"; + String sql = "UPDATE statuses SET expiry = 0, txCount = 0" + + " WHERE messageId = ? AND contactId = ?"; ps = txn.prepareStatement(sql); - ps.setInt(1, c.getInt()); - ps.setBytes(2, g.getBytes()); + ps.setBytes(1, m.getBytes()); + ps.setInt(2, c.getInt()); int affected = ps.executeUpdate(); - if (affected != 1) throw new DbStateException(); + if (affected < 0 || affected > 1) throw new DbStateException(); ps.close(); } catch (SQLException e) { tryToClose(ps); @@ -2298,14 +2410,14 @@ abstract class JdbcDatabase implements Database<Connection> { } } - public void resetExpiryTime(Connection txn, ContactId c, MessageId m) + @Override + public void setContactVerified(Connection txn, ContactId c) throws DbException { PreparedStatement ps = null; try { - String sql = "UPDATE statuses SET expiry = 0, txCount = 0" - + " WHERE messageId = ? AND contactId = ?"; + String sql = "UPDATE contacts SET verified = ? WHERE contactId = ?"; ps = txn.prepareStatement(sql); - ps.setBytes(1, m.getBytes()); + ps.setBoolean(1, true); ps.setInt(2, c.getInt()); int affected = ps.executeUpdate(); if (affected < 0 || affected > 1) throw new DbStateException(); @@ -2316,13 +2428,14 @@ abstract class JdbcDatabase implements Database<Connection> { } } - public void setContactVerified(Connection txn, ContactId c) + @Override + public void setContactActive(Connection txn, ContactId c, boolean active) throws DbException { PreparedStatement ps = null; try { - String sql = "UPDATE contacts SET verified = ? WHERE contactId = ?"; + String sql = "UPDATE contacts SET active = ? WHERE contactId = ?"; ps = txn.prepareStatement(sql); - ps.setBoolean(1, true); + ps.setBoolean(1, active); ps.setInt(2, c.getInt()); int affected = ps.executeUpdate(); if (affected < 0 || affected > 1) throw new DbStateException(); @@ -2333,14 +2446,17 @@ abstract class JdbcDatabase implements Database<Connection> { } } - public void setContactActive(Connection txn, ContactId c, boolean active) - throws DbException { + @Override + public void setGroupVisibility(Connection txn, ContactId c, GroupId g, + boolean shared) throws DbException { PreparedStatement ps = null; try { - String sql = "UPDATE contacts SET active = ? WHERE contactId = ?"; + String sql = "UPDATE groupVisibilities SET shared = ?" + + " WHERE contactId = ? AND groupId = ?"; ps = txn.prepareStatement(sql); - ps.setBoolean(1, active); + ps.setBoolean(1, shared); ps.setInt(2, c.getInt()); + ps.setBytes(3, g.getBytes()); int affected = ps.executeUpdate(); if (affected < 0 || affected > 1) throw new DbStateException(); ps.close(); @@ -2350,6 +2466,7 @@ abstract class JdbcDatabase implements Database<Connection> { } } + @Override public void setMessageShared(Connection txn, MessageId m) throws DbException { PreparedStatement ps = null; try { @@ -2366,6 +2483,7 @@ abstract class JdbcDatabase implements Database<Connection> { } } + @Override public void setMessageState(Connection txn, MessageId m, State state) throws DbException { PreparedStatement ps = null; @@ -2383,6 +2501,7 @@ abstract class JdbcDatabase implements Database<Connection> { } } + @Override public void setReorderingWindow(Connection txn, ContactId c, TransportId t, long rotationPeriod, long base, byte[] bitmap) throws DbException { PreparedStatement ps = null; @@ -2404,6 +2523,7 @@ abstract class JdbcDatabase implements Database<Connection> { } } + @Override public void updateExpiryTime(Connection txn, ContactId c, MessageId m, int maxLatency) throws DbException { PreparedStatement ps = null; @@ -2437,6 +2557,7 @@ abstract class JdbcDatabase implements Database<Connection> { } } + @Override public void updateTransportKeys(Connection txn, Map<ContactId, TransportKeys> keys) throws DbException { PreparedStatement ps = null; diff --git a/briar-core/src/org/briarproject/introduction/IntroductionManagerImpl.java b/briar-core/src/org/briarproject/introduction/IntroductionManagerImpl.java index e59f34e8498ac4de2b74db755a520fd929e0fa42..e3f7981a5d16ed3485d6e12f1967e5d59e736dd3 100644 --- a/briar-core/src/org/briarproject/introduction/IntroductionManagerImpl.java +++ b/briar-core/src/org/briarproject/introduction/IntroductionManagerImpl.java @@ -73,6 +73,7 @@ import static org.briarproject.api.introduction.IntroductionConstants.TYPE_ABORT import static org.briarproject.api.introduction.IntroductionConstants.TYPE_ACK; import static org.briarproject.api.introduction.IntroductionConstants.TYPE_REQUEST; import static org.briarproject.api.introduction.IntroductionConstants.TYPE_RESPONSE; +import static org.briarproject.api.sync.Group.Visibility.SHARED; import static org.briarproject.clients.BdfConstants.MSG_KEY_READ; @NotNullByDefault @@ -116,7 +117,7 @@ class IntroductionManagerImpl extends ConversationClientImpl if (db.containsGroup(txn, g.getId())) return; // Store the group and share it with the contact db.addGroup(txn, g); - db.setVisibleToContact(txn, c.getId(), g.getId(), true); + db.setGroupVisibility(txn, c.getId(), g.getId(), SHARED); // Attach the contact ID to the group BdfDictionary gm = new BdfDictionary(); gm.put(CONTACT, c.getId().getInt()); diff --git a/briar-core/src/org/briarproject/messaging/MessagingManagerImpl.java b/briar-core/src/org/briarproject/messaging/MessagingManagerImpl.java index 233682600d938251ffc597a747f5be787fedc192..90fbe5c79f5e6887aae9a33d2cf8325bb7d04f72 100644 --- a/briar-core/src/org/briarproject/messaging/MessagingManagerImpl.java +++ b/briar-core/src/org/briarproject/messaging/MessagingManagerImpl.java @@ -33,6 +33,7 @@ import java.util.Map; import javax.inject.Inject; +import static org.briarproject.api.sync.Group.Visibility.SHARED; import static org.briarproject.clients.BdfConstants.MSG_KEY_READ; @NotNullByDefault @@ -64,7 +65,7 @@ class MessagingManagerImpl extends ConversationClientImpl if (db.containsGroup(txn, g.getId())) return; // Store the group and share it with the contact db.addGroup(txn, g); - db.setVisibleToContact(txn, c.getId(), g.getId(), true); + 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()); diff --git a/briar-core/src/org/briarproject/privategroup/invitation/AbstractProtocolEngine.java b/briar-core/src/org/briarproject/privategroup/invitation/AbstractProtocolEngine.java index ddf3b5ae20c304b3dfae078dc27461e3651e902a..6eb230247f248f7475d2ca113757051357fa2911 100644 --- a/briar-core/src/org/briarproject/privategroup/invitation/AbstractProtocolEngine.java +++ b/briar-core/src/org/briarproject/privategroup/invitation/AbstractProtocolEngine.java @@ -18,6 +18,7 @@ import org.briarproject.api.privategroup.PrivateGroup; import org.briarproject.api.privategroup.PrivateGroupFactory; import org.briarproject.api.privategroup.PrivateGroupManager; import org.briarproject.api.sync.Group; +import org.briarproject.api.sync.Group.Visibility; import org.briarproject.api.sync.GroupId; import org.briarproject.api.sync.Message; import org.briarproject.api.sync.MessageId; @@ -90,11 +91,10 @@ abstract class AbstractProtocolEngine<S extends Session> return dependency.equals(expected); } - void syncPrivateGroupWithContact(Transaction txn, S session, boolean sync) + void setPrivateGroupVisibility(Transaction txn, S session, Visibility v) throws DbException, FormatException { ContactId contactId = getContactId(txn, session.getContactGroupId()); - db.setVisibleToContact(txn, contactId, session.getPrivateGroupId(), - sync); + db.setGroupVisibility(txn, contactId, session.getPrivateGroupId(), v); } Message sendInviteMessage(Transaction txn, S session, diff --git a/briar-core/src/org/briarproject/privategroup/invitation/CreatorProtocolEngine.java b/briar-core/src/org/briarproject/privategroup/invitation/CreatorProtocolEngine.java index f58630c4c8b73c6a00d439648c945ae241ef96fb..26f1715797a515b0ad8e156dbd86a8ceead788be 100644 --- a/briar-core/src/org/briarproject/privategroup/invitation/CreatorProtocolEngine.java +++ b/briar-core/src/org/briarproject/privategroup/invitation/CreatorProtocolEngine.java @@ -22,6 +22,8 @@ import org.briarproject.api.system.Clock; import javax.annotation.Nullable; import javax.annotation.concurrent.Immutable; +import static org.briarproject.api.sync.Group.Visibility.INVISIBLE; +import static org.briarproject.api.sync.Group.Visibility.SHARED; import static org.briarproject.privategroup.invitation.CreatorState.DISSOLVED; import static org.briarproject.privategroup.invitation.CreatorState.ERROR; import static org.briarproject.privategroup.invitation.CreatorState.INVITED; @@ -158,8 +160,8 @@ class CreatorProtocolEngine extends AbstractProtocolEngine<CreatorSession> { private CreatorSession onLocalLeave(Transaction txn, CreatorSession s) throws DbException { try { - // Stop syncing the private group with the contact - syncPrivateGroupWithContact(txn, s, false); + // Make the private group invisible to the contact + setPrivateGroupVisibility(txn, s, INVISIBLE); } catch (FormatException e) { throw new DbException(e); // Invalid group metadata } @@ -183,8 +185,8 @@ class CreatorProtocolEngine extends AbstractProtocolEngine<CreatorSession> { // Track the message messageTracker.trackMessage(txn, m.getContactGroupId(), m.getTimestamp(), false); - // Start syncing the private group with the contact - syncPrivateGroupWithContact(txn, s, true); + // Share the private group with the contact + setPrivateGroupVisibility(txn, s, SHARED); // Broadcast an event ContactId contactId = getContactId(txn, m.getContactGroupId()); txn.attach(new GroupInvitationResponseReceivedEvent(contactId, @@ -224,8 +226,8 @@ class CreatorProtocolEngine extends AbstractProtocolEngine<CreatorSession> { // The dependency, if any, must be the last remote message if (!isValidDependency(s, m.getPreviousMessageId())) return abort(txn, s); - // Stop syncing the private group with the contact - syncPrivateGroupWithContact(txn, s, false); + // Make the private group invisible to the contact + setPrivateGroupVisibility(txn, s, INVISIBLE); // Move to the INVITEE_LEFT state return new CreatorSession(s.getContactGroupId(), s.getPrivateGroupId(), s.getLastLocalMessageId(), m.getId(), s.getLocalTimestamp(), @@ -236,9 +238,9 @@ class CreatorProtocolEngine extends AbstractProtocolEngine<CreatorSession> { throws DbException, FormatException { // If the session has already been aborted, do nothing if (s.getState() == ERROR) return s; - // If we subscribe, stop syncing the private group with the contact + // If we subscribe, make the private group invisible to the contact if (isSubscribedPrivateGroup(txn, s.getPrivateGroupId())) - syncPrivateGroupWithContact(txn, s, false); + setPrivateGroupVisibility(txn, s, INVISIBLE); // Send an ABORT message Message sent = sendAbortMessage(txn, s); // Move to the ERROR state diff --git a/briar-core/src/org/briarproject/privategroup/invitation/GroupInvitationManagerImpl.java b/briar-core/src/org/briarproject/privategroup/invitation/GroupInvitationManagerImpl.java index d90668e6d39f62fb4029f7d7c7204a0c30a85492..5be3b4573143e41cb97234ffbebcba73e9ca8723 100644 --- a/briar-core/src/org/briarproject/privategroup/invitation/GroupInvitationManagerImpl.java +++ b/briar-core/src/org/briarproject/privategroup/invitation/GroupInvitationManagerImpl.java @@ -45,6 +45,7 @@ import java.util.Map.Entry; import javax.annotation.concurrent.Immutable; import javax.inject.Inject; +import static org.briarproject.api.sync.Group.Visibility.SHARED; import static org.briarproject.privategroup.invitation.CreatorState.START; import static org.briarproject.privategroup.invitation.GroupInvitationConstants.GROUP_KEY_CONTACT_ID; import static org.briarproject.privategroup.invitation.MessageType.ABORT; @@ -110,7 +111,7 @@ class GroupInvitationManagerImpl extends ConversationClientImpl if (db.containsGroup(txn, g.getId())) return; // Store the group and share it with the contact db.addGroup(txn, g); - db.setVisibleToContact(txn, c.getId(), g.getId(), true); + 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()); diff --git a/briar-core/src/org/briarproject/privategroup/invitation/InviteeProtocolEngine.java b/briar-core/src/org/briarproject/privategroup/invitation/InviteeProtocolEngine.java index 60579d46f5c4339a441092788c5d418a2c535664..4398badbf23598d191f182e9971a6ec37113fb1f 100644 --- a/briar-core/src/org/briarproject/privategroup/invitation/InviteeProtocolEngine.java +++ b/briar-core/src/org/briarproject/privategroup/invitation/InviteeProtocolEngine.java @@ -25,6 +25,8 @@ import org.briarproject.api.system.Clock; import javax.annotation.Nullable; import javax.annotation.concurrent.Immutable; +import static org.briarproject.api.sync.Group.Visibility.INVISIBLE; +import static org.briarproject.api.sync.Group.Visibility.SHARED; import static org.briarproject.privategroup.invitation.InviteeState.DISSOLVED; import static org.briarproject.privategroup.invitation.InviteeState.ERROR; import static org.briarproject.privategroup.invitation.InviteeState.INVITED; @@ -157,8 +159,8 @@ class InviteeProtocolEngine extends AbstractProtocolEngine<InviteeSession> { try { // Subscribe to the private group subscribeToPrivateGroup(txn, inviteId); - // Start syncing the private group with the contact - syncPrivateGroupWithContact(txn, s, true); + // Share the private group with the contact + setPrivateGroupVisibility(txn, s, SHARED); } catch (FormatException e) { throw new DbException(e); // Invalid group metadata } @@ -228,8 +230,8 @@ class InviteeProtocolEngine extends AbstractProtocolEngine<InviteeSession> { if (!isValidDependency(s, m.getPreviousMessageId())) return abort(txn, s); try { - // Stop syncing the private group with the contact - syncPrivateGroupWithContact(txn, s, false); + // Make the private group invisible to the contact + setPrivateGroupVisibility(txn, s, INVISIBLE); } catch (FormatException e) { throw new DbException(e); // Invalid group metadata } @@ -247,9 +249,9 @@ class InviteeProtocolEngine extends AbstractProtocolEngine<InviteeSession> { if (s.getState() == ERROR) return s; // Mark any invite messages in the session unavailable to answer markInvitesUnavailableToAnswer(txn, s); - // Stop syncing the private group with the contact, if we subscribe + // If we subscribe, make the private group invisible to the contact if (isSubscribedPrivateGroup(txn, s.getPrivateGroupId())) - syncPrivateGroupWithContact(txn, s, false); + setPrivateGroupVisibility(txn, s, INVISIBLE); // Send an ABORT message Message sent = sendAbortMessage(txn, s); // Move to the ERROR state diff --git a/briar-core/src/org/briarproject/privategroup/invitation/PeerProtocolEngine.java b/briar-core/src/org/briarproject/privategroup/invitation/PeerProtocolEngine.java index d6674f841227097c2bd695f35810c91ae394a25c..06b019649ea27b23d6f3f6e0baedc0f5cf1cc5fc 100644 --- a/briar-core/src/org/briarproject/privategroup/invitation/PeerProtocolEngine.java +++ b/briar-core/src/org/briarproject/privategroup/invitation/PeerProtocolEngine.java @@ -20,6 +20,8 @@ import org.briarproject.api.system.Clock; import javax.annotation.Nullable; import javax.annotation.concurrent.Immutable; +import static org.briarproject.api.sync.Group.Visibility.INVISIBLE; +import static org.briarproject.api.sync.Group.Visibility.SHARED; import static org.briarproject.privategroup.invitation.PeerState.AWAIT_MEMBER; import static org.briarproject.privategroup.invitation.PeerState.BOTH_JOINED; import static org.briarproject.privategroup.invitation.PeerState.ERROR; @@ -178,8 +180,8 @@ class PeerProtocolEngine extends AbstractProtocolEngine<PeerSession> { // Send a JOIN message Message sent = sendJoinMessage(txn, s, false); try { - // Start syncing the private group with the contact - syncPrivateGroupWithContact(txn, s, true); + // Share the private group with the contact + setPrivateGroupVisibility(txn, s, SHARED); } catch (FormatException e) { throw new DbException(e); // Invalid group metadata } @@ -195,8 +197,8 @@ class PeerProtocolEngine extends AbstractProtocolEngine<PeerSession> { // Send a LEAVE message Message sent = sendLeaveMessage(txn, s, false); try { - // Stop syncing the private group with the contact - syncPrivateGroupWithContact(txn, s, false); + // Make the private group invisible to the contact + setPrivateGroupVisibility(txn, s, INVISIBLE); } catch (FormatException e) { throw new DbException(e); // Invalid group metadata } @@ -228,8 +230,8 @@ class PeerProtocolEngine extends AbstractProtocolEngine<PeerSession> { // Send a JOIN message Message sent = sendJoinMessage(txn, s, false); try { - // Start syncing the private group with the contact - syncPrivateGroupWithContact(txn, s, true); + // Share the private group with the contact + setPrivateGroupVisibility(txn, s, SHARED); } catch (FormatException e) { throw new DbException(e); // Invalid group metadata } @@ -263,8 +265,8 @@ class PeerProtocolEngine extends AbstractProtocolEngine<PeerSession> { return abort(txn, s); // Send a JOIN message Message sent = sendJoinMessage(txn, s, false); - // Start syncing the private group with the contact - syncPrivateGroupWithContact(txn, s, true); + // Share the private group with the contact + setPrivateGroupVisibility(txn, s, SHARED); // Mark the relationship visible to the group, revealed by contact relationshipRevealed(txn, s, true); // Move to the BOTH_JOINED state @@ -277,8 +279,8 @@ class PeerProtocolEngine extends AbstractProtocolEngine<PeerSession> { // The dependency, if any, must be the last remote message if (!isValidDependency(s, m.getPreviousMessageId())) return abort(txn, s); - // Start syncing the private group with the contact - syncPrivateGroupWithContact(txn, s, true); + // Share the private group with the contact + setPrivateGroupVisibility(txn, s, SHARED); // Mark the relationship visible to the group, revealed by us relationshipRevealed(txn, s, false); // Move to the BOTH_JOINED state @@ -314,8 +316,8 @@ class PeerProtocolEngine extends AbstractProtocolEngine<PeerSession> { // The dependency, if any, must be the last remote message if (!isValidDependency(s, m.getPreviousMessageId())) return abort(txn, s); - // Stop syncing the private group with the contact - syncPrivateGroupWithContact(txn, s, false); + // Make the private group invisible to the contact + setPrivateGroupVisibility(txn, s, INVISIBLE); // Move to the LOCAL_JOINED state return new PeerSession(s.getContactGroupId(), s.getPrivateGroupId(), s.getLastLocalMessageId(), m.getId(), s.getLocalTimestamp(), @@ -326,9 +328,9 @@ class PeerProtocolEngine extends AbstractProtocolEngine<PeerSession> { throws DbException, FormatException { // If the session has already been aborted, do nothing if (s.getState() == ERROR) return s; - // Stop syncing the private group with the contact, if we subscribe + // If we subscribe, make the private group invisible to the contact if (isSubscribedPrivateGroup(txn, s.getPrivateGroupId())) - syncPrivateGroupWithContact(txn, s, false); + setPrivateGroupVisibility(txn, s, INVISIBLE); // Send an ABORT message Message sent = sendAbortMessage(txn, s); // Move to the ERROR state diff --git a/briar-core/src/org/briarproject/properties/TransportPropertyManagerImpl.java b/briar-core/src/org/briarproject/properties/TransportPropertyManagerImpl.java index b0900795e1204a754139f0ae9cd26fd115e28cf7..405d252b927027ce845ad401f50e93e6388b7c92 100644 --- a/briar-core/src/org/briarproject/properties/TransportPropertyManagerImpl.java +++ b/briar-core/src/org/briarproject/properties/TransportPropertyManagerImpl.java @@ -29,6 +29,8 @@ import java.util.Map.Entry; import javax.inject.Inject; +import static org.briarproject.api.sync.Group.Visibility.SHARED; + class TransportPropertyManagerImpl implements TransportPropertyManager, Client, AddContactHook, RemoveContactHook { @@ -64,7 +66,7 @@ class TransportPropertyManagerImpl implements TransportPropertyManager, if (db.containsGroup(txn, g.getId())) return; // Store the group and share it with the contact db.addGroup(txn, g); - db.setVisibleToContact(txn, c.getId(), g.getId(), true); + db.setGroupVisibility(txn, c.getId(), g.getId(), SHARED); // Copy the latest local properties into the group Map<TransportId, TransportProperties> local = getLocalProperties(txn); for (Entry<TransportId, TransportProperties> e : local.entrySet()) { diff --git a/briar-core/src/org/briarproject/sharing/SharingManagerImpl.java b/briar-core/src/org/briarproject/sharing/SharingManagerImpl.java index 95697ffa40a493b0cbcb678c0ff0e39f0f4edc0d..2768d4a7eda5e88b1640c8f0e7452820980a8535 100644 --- a/briar-core/src/org/briarproject/sharing/SharingManagerImpl.java +++ b/briar-core/src/org/briarproject/sharing/SharingManagerImpl.java @@ -87,6 +87,8 @@ import static org.briarproject.api.sharing.SharingConstants.TO_BE_SHARED_BY_US; import static org.briarproject.api.sharing.SharingConstants.TYPE; import static org.briarproject.api.sharing.SharingMessage.BaseMessage; import static org.briarproject.api.sharing.SharingMessage.Invitation; +import static org.briarproject.api.sync.Group.Visibility.INVISIBLE; +import static org.briarproject.api.sync.Group.Visibility.SHARED; import static org.briarproject.clients.BdfConstants.MSG_KEY_READ; import static org.briarproject.sharing.InviteeSessionState.State.AWAIT_LOCAL_RESPONSE; @@ -159,7 +161,7 @@ abstract class SharingManagerImpl<S extends Shareable, I extends Invitation, IS if (db.containsGroup(txn, g.getId())) return; // Store the group and share it with the contact db.addGroup(txn, g); - db.setVisibleToContact(txn, c.getId(), g.getId(), true); + db.setGroupVisibility(txn, c.getId(), g.getId(), SHARED); // Attach the contact ID to the group BdfDictionary meta = new BdfDictionary(); meta.put(CONTACT_ID, c.getId().getInt()); @@ -908,20 +910,20 @@ abstract class SharingManagerImpl<S extends Shareable, I extends Invitation, IS // TODO we might want to call the add() method of the respective // manager here, because blogs add a description for example db.addGroup(txn, f.getGroup()); - db.setVisibleToContact(txn, contactId, f.getId(), true); + db.setGroupVisibility(txn, contactId, f.getId(), SHARED); } else if (task == TASK_ADD_SHAREABLE_TO_LIST_TO_BE_SHARED_BY_US) { addToList(txn, groupId, TO_BE_SHARED_BY_US, f); } else if (task == TASK_REMOVE_SHAREABLE_FROM_LIST_TO_BE_SHARED_BY_US) { removeFromList(txn, groupId, TO_BE_SHARED_BY_US, f); } else if (task == TASK_SHARE_SHAREABLE) { - db.setVisibleToContact(txn, contactId, f.getId(), true); + db.setGroupVisibility(txn, contactId, f.getId(), SHARED); removeFromList(txn, groupId, TO_BE_SHARED_BY_US, f); addToList(txn, groupId, SHARED_BY_US, f); } else if (task == TASK_UNSHARE_SHAREABLE_SHARED_BY_US) { - db.setVisibleToContact(txn, contactId, f.getId(), false); + db.setGroupVisibility(txn, contactId, f.getId(), INVISIBLE); removeFromList(txn, groupId, SHARED_BY_US, f); } else if (task == TASK_UNSHARE_SHAREABLE_SHARED_WITH_US) { - db.setVisibleToContact(txn, contactId, f.getId(), false); + db.setGroupVisibility(txn, contactId, f.getId(), INVISIBLE); removeFromList(txn, groupId, SHARED_WITH_US, f); } } diff --git a/briar-tests/src/org/briarproject/blogs/BlogManagerImplTest.java b/briar-tests/src/org/briarproject/blogs/BlogManagerImplTest.java index 552ebaf37bc8c9901bd2c5c6681cae394cb708e8..7d661e8ac20f5c717f83c2772e2318f6283a20de 100644 --- a/briar-tests/src/org/briarproject/blogs/BlogManagerImplTest.java +++ b/briar-tests/src/org/briarproject/blogs/BlogManagerImplTest.java @@ -49,6 +49,7 @@ import static org.briarproject.api.blogs.BlogConstants.KEY_TYPE; import static org.briarproject.api.blogs.MessageType.POST; import static org.briarproject.api.identity.Author.Status.VERIFIED; import static org.briarproject.api.identity.AuthorConstants.MAX_PUBLIC_KEY_LENGTH; +import static org.briarproject.api.sync.Group.Visibility.SHARED; import static org.briarproject.blogs.BlogManagerImpl.CLIENT_ID; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; @@ -102,25 +103,18 @@ public class BlogManagerImplTest extends BriarTestCase { will(returnValue(blog1.getAuthor())); oneOf(blogFactory).createBlog(blog1.getAuthor()); will(returnValue(blog1)); - oneOf(db).containsGroup(txn, blog1.getId()); - will(returnValue(false)); oneOf(db).addGroup(txn, blog1.getGroup()); - oneOf(db).getContacts(txn, blog1.getAuthor().getId()); - will(returnValue(contactIds)); - oneOf(db).setVisibleToContact(txn, contactId, blog1.getId(), true); oneOf(db).getContacts(txn); will(returnValue(contacts)); oneOf(blogFactory).createBlog(blog2.getAuthor()); will(returnValue(blog2)); - oneOf(db).containsGroup(txn, blog2.getId()); - will(returnValue(false)); oneOf(db).addGroup(txn, blog2.getGroup()); - oneOf(db).setVisibleToContact(txn, contactId, blog2.getId(), true); - oneOf(db).getLocalAuthor(txn, blog1.getAuthor().getId()); + oneOf(db).setGroupVisibility(txn, contactId, blog2.getId(), SHARED); + oneOf(identityManager).getLocalAuthor(txn); will(returnValue(blog1.getAuthor())); oneOf(blogFactory).createBlog(blog1.getAuthor()); will(returnValue(blog1)); - oneOf(db).setVisibleToContact(txn, contactId, blog1.getId(), true); + oneOf(db).setGroupVisibility(txn, contactId, blog1.getId(), SHARED); }}); blogManager.createLocalState(txn); diff --git a/briar-tests/src/org/briarproject/db/DatabaseComponentImplTest.java b/briar-tests/src/org/briarproject/db/DatabaseComponentImplTest.java index 44c62675cb207a2352b6cfd9fb8bd4e30f0f90b5..d61f0cce936f28491e9221121962454eb3b5a83a 100644 --- a/briar-tests/src/org/briarproject/db/DatabaseComponentImplTest.java +++ b/briar-tests/src/org/briarproject/db/DatabaseComponentImplTest.java @@ -60,6 +60,9 @@ import java.util.Collections; import java.util.Map; import static org.briarproject.api.identity.AuthorConstants.MAX_PUBLIC_KEY_LENGTH; +import static org.briarproject.api.sync.Group.Visibility.INVISIBLE; +import static org.briarproject.api.sync.Group.Visibility.SHARED; +import static org.briarproject.api.sync.Group.Visibility.VISIBLE; import static org.briarproject.api.sync.ValidationManager.State.DELIVERED; import static org.briarproject.api.sync.ValidationManager.State.UNKNOWN; import static org.briarproject.api.transport.TransportConstants.REORDERING_WINDOW_SIZE; @@ -170,7 +173,7 @@ public class DatabaseComponentImplTest extends BriarTestCase { // removeGroup() oneOf(database).containsGroup(txn, groupId); will(returnValue(true)); - oneOf(database).getVisibility(txn, groupId); + oneOf(database).getGroupVisibility(txn, groupId); will(returnValue(Collections.emptyList())); oneOf(database).removeGroup(txn, groupId); oneOf(eventBus).broadcast(with(any(GroupRemovedEvent.class))); @@ -267,7 +270,7 @@ public class DatabaseComponentImplTest extends BriarTestCase { will(returnValue(false)); oneOf(database).addMessage(txn, message, DELIVERED, true); oneOf(database).mergeMessageMetadata(txn, messageId, metadata); - oneOf(database).getVisibility(txn, groupId); + oneOf(database).getGroupVisibility(txn, groupId); will(returnValue(Collections.singletonList(contactId))); oneOf(database).removeOfferedMessage(txn, contactId, messageId); will(returnValue(false)); @@ -403,7 +406,7 @@ public class DatabaseComponentImplTest extends BriarTestCase { transaction = db.startTransaction(false); try { - db.isVisibleToContact(transaction, contactId, groupId); + db.getGroupVisibility(transaction, contactId, groupId); fail(); } catch (NoSuchContactException expected) { // Expected @@ -487,7 +490,7 @@ public class DatabaseComponentImplTest extends BriarTestCase { transaction = db.startTransaction(false); try { - db.setVisibleToContact(transaction, contactId, groupId, true); + db.setGroupVisibility(transaction, contactId, groupId, SHARED); fail(); } catch (NoSuchContactException expected) { // Expected @@ -560,14 +563,13 @@ public class DatabaseComponentImplTest extends BriarTestCase { final EventBus eventBus = context.mock(EventBus.class); context.checking(new Expectations() {{ // Check whether the group is in the DB (which it's not) - exactly(9).of(database).startTransaction(); + exactly(8).of(database).startTransaction(); will(returnValue(txn)); - exactly(9).of(database).containsGroup(txn, groupId); + exactly(8).of(database).containsGroup(txn, groupId); will(returnValue(false)); - exactly(9).of(database).abortTransaction(txn); - // This is needed for getMessageStatus(), isVisibleToContact(), and - // setVisibleToContact() to proceed - exactly(3).of(database).containsContact(txn, contactId); + exactly(8).of(database).abortTransaction(txn); + // This is needed for getMessageStatus() and setGroupVisibility() + exactly(2).of(database).containsContact(txn, contactId); will(returnValue(true)); }}); DatabaseComponent db = createDatabaseComponent(database, eventBus, @@ -623,16 +625,6 @@ public class DatabaseComponentImplTest extends BriarTestCase { db.endTransaction(transaction); } - transaction = db.startTransaction(false); - try { - db.isVisibleToContact(transaction, contactId, groupId); - fail(); - } catch (NoSuchGroupException expected) { - // Expected - } finally { - db.endTransaction(transaction); - } - transaction = db.startTransaction(false); try { db.mergeGroupMetadata(transaction, groupId, metadata); @@ -655,7 +647,7 @@ public class DatabaseComponentImplTest extends BriarTestCase { transaction = db.startTransaction(false); try { - db.setVisibleToContact(transaction, contactId, groupId, true); + db.setGroupVisibility(transaction, contactId, groupId, SHARED); fail(); } catch (NoSuchGroupException expected) { // Expected @@ -924,6 +916,7 @@ public class DatabaseComponentImplTest extends BriarTestCase { Transaction transaction = db.startTransaction(false); try { Ack a = db.generateAck(transaction, contactId, 123); + assertNotNull(a); assertEquals(messagesToAck, a.getMessageIds()); db.commitTransaction(transaction); } finally { @@ -1005,6 +998,7 @@ public class DatabaseComponentImplTest extends BriarTestCase { Transaction transaction = db.startTransaction(false); try { Offer o = db.generateOffer(transaction, contactId, 123, maxLatency); + assertNotNull(o); assertEquals(ids, o.getMessageIds()); db.commitTransaction(transaction); } finally { @@ -1039,6 +1033,7 @@ public class DatabaseComponentImplTest extends BriarTestCase { Transaction transaction = db.startTransaction(false); try { Request r = db.generateRequest(transaction, contactId, 123); + assertNotNull(r); assertEquals(ids, r.getMessageIds()); db.commitTransaction(transaction); } finally { @@ -1139,12 +1134,12 @@ public class DatabaseComponentImplTest extends BriarTestCase { // First time oneOf(database).containsContact(txn, contactId); will(returnValue(true)); - oneOf(database).containsVisibleGroup(txn, contactId, groupId); - will(returnValue(true)); + oneOf(database).getGroupVisibility(txn, contactId, groupId); + will(returnValue(VISIBLE)); oneOf(database).containsMessage(txn, messageId); will(returnValue(false)); oneOf(database).addMessage(txn, message, UNKNOWN, false); - oneOf(database).getVisibility(txn, groupId); + oneOf(database).getGroupVisibility(txn, groupId); will(returnValue(Collections.singletonList(contactId))); oneOf(database).removeOfferedMessage(txn, contactId, messageId); will(returnValue(false)); @@ -1152,8 +1147,8 @@ public class DatabaseComponentImplTest extends BriarTestCase { // Second time oneOf(database).containsContact(txn, contactId); will(returnValue(true)); - oneOf(database).containsVisibleGroup(txn, contactId, groupId); - will(returnValue(true)); + oneOf(database).getGroupVisibility(txn, contactId, groupId); + will(returnValue(VISIBLE)); oneOf(database).containsMessage(txn, messageId); will(returnValue(true)); oneOf(database).raiseSeenFlag(txn, contactId, messageId); @@ -1195,8 +1190,8 @@ public class DatabaseComponentImplTest extends BriarTestCase { will(returnValue(true)); oneOf(database).containsMessage(txn, messageId); will(returnValue(true)); - oneOf(database).containsVisibleGroup(txn, contactId, groupId); - will(returnValue(true)); + oneOf(database).getGroupVisibility(txn, contactId, groupId); + will(returnValue(VISIBLE)); // The message wasn't stored but it must still be acked oneOf(database).raiseSeenFlag(txn, contactId, messageId); oneOf(database).raiseAckFlag(txn, contactId, messageId); @@ -1230,8 +1225,8 @@ public class DatabaseComponentImplTest extends BriarTestCase { will(returnValue(txn)); oneOf(database).containsContact(txn, contactId); will(returnValue(true)); - oneOf(database).containsVisibleGroup(txn, contactId, groupId); - will(returnValue(false)); + oneOf(database).getGroupVisibility(txn, contactId, groupId); + will(returnValue(INVISIBLE)); oneOf(database).commitTransaction(txn); }}); DatabaseComponent db = createDatabaseComponent(database, eventBus, @@ -1350,9 +1345,9 @@ public class DatabaseComponentImplTest extends BriarTestCase { will(returnValue(true)); oneOf(database).containsGroup(txn, groupId); will(returnValue(true)); - oneOf(database).containsVisibleGroup(txn, contactId, groupId); - will(returnValue(false)); // Not yet visible - oneOf(database).addVisibility(txn, contactId, groupId); + oneOf(database).getGroupVisibility(txn, contactId, groupId); + will(returnValue(INVISIBLE)); // Not yet visible + oneOf(database).addGroupVisibility(txn, contactId, groupId, false); oneOf(database).getMessageIds(txn, groupId); will(returnValue(Collections.singletonList(messageId))); oneOf(database).removeOfferedMessage(txn, contactId, messageId); @@ -1367,7 +1362,7 @@ public class DatabaseComponentImplTest extends BriarTestCase { Transaction transaction = db.startTransaction(false); try { - db.setVisibleToContact(transaction, contactId, groupId, true); + db.setGroupVisibility(transaction, contactId, groupId, VISIBLE); db.commitTransaction(transaction); } finally { db.endTransaction(transaction); @@ -1391,8 +1386,8 @@ public class DatabaseComponentImplTest extends BriarTestCase { will(returnValue(true)); oneOf(database).containsGroup(txn, groupId); will(returnValue(true)); - oneOf(database).containsVisibleGroup(txn, contactId, groupId); - will(returnValue(true)); // Already visible + oneOf(database).getGroupVisibility(txn, contactId, groupId); + will(returnValue(VISIBLE)); // Already visible oneOf(database).commitTransaction(txn); }}); DatabaseComponent db = createDatabaseComponent(database, eventBus, @@ -1400,7 +1395,7 @@ public class DatabaseComponentImplTest extends BriarTestCase { Transaction transaction = db.startTransaction(false); try { - db.setVisibleToContact(transaction, contactId, groupId, true); + db.setGroupVisibility(transaction, contactId, groupId, VISIBLE); db.commitTransaction(transaction); } finally { db.endTransaction(transaction); @@ -1666,7 +1661,7 @@ public class DatabaseComponentImplTest extends BriarTestCase { oneOf(database).containsMessage(txn, messageId); will(returnValue(false)); oneOf(database).addMessage(txn, message, DELIVERED, true); - oneOf(database).getVisibility(txn, groupId); + oneOf(database).getGroupVisibility(txn, groupId); will(returnValue(Collections.singletonList(contactId))); oneOf(database).mergeMessageMetadata(txn, messageId, metadata); oneOf(database).removeOfferedMessage(txn, contactId, messageId); diff --git a/briar-tests/src/org/briarproject/db/H2DatabaseTest.java b/briar-tests/src/org/briarproject/db/H2DatabaseTest.java index e5f368a7c28b9ccb62ebe75487199175847f245e..0b092c3d9a494abe0ebcac3003e71bcd230c3623 100644 --- a/briar-tests/src/org/briarproject/db/H2DatabaseTest.java +++ b/briar-tests/src/org/briarproject/db/H2DatabaseTest.java @@ -45,6 +45,9 @@ import java.util.concurrent.atomic.AtomicBoolean; import static java.util.concurrent.TimeUnit.SECONDS; import static org.briarproject.api.db.Metadata.REMOVE; import static org.briarproject.api.identity.AuthorConstants.MAX_PUBLIC_KEY_LENGTH; +import static org.briarproject.api.sync.Group.Visibility.INVISIBLE; +import static org.briarproject.api.sync.Group.Visibility.SHARED; +import static org.briarproject.api.sync.Group.Visibility.VISIBLE; import static org.briarproject.api.sync.SyncConstants.MAX_MESSAGE_LENGTH; import static org.briarproject.api.sync.ValidationManager.State.DELIVERED; import static org.briarproject.api.sync.ValidationManager.State.INVALID; @@ -171,12 +174,12 @@ public class H2DatabaseTest extends BriarTestCase { Database<Connection> db = open(false); Connection txn = db.startTransaction(); - // Add a contact, a group and a message + // Add a contact, a shared group and a shared message db.addLocalAuthor(txn, localAuthor); assertEquals(contactId, db.addContact(txn, author, localAuthorId, true, true)); db.addGroup(txn, group); - db.addVisibility(txn, contactId, groupId); + db.addGroupVisibility(txn, contactId, groupId, true); db.addMessage(txn, message, DELIVERED, true); // The message has no status yet, so it should not be sendable @@ -209,12 +212,12 @@ public class H2DatabaseTest extends BriarTestCase { Database<Connection> db = open(false); Connection txn = db.startTransaction(); - // Add a contact, a group and an unvalidated message + // Add a contact, a shared group and a shared but unvalidated message db.addLocalAuthor(txn, localAuthor); assertEquals(contactId, db.addContact(txn, author, localAuthorId, true, true)); db.addGroup(txn, group); - db.addVisibility(txn, contactId, groupId); + db.addGroupVisibility(txn, contactId, groupId, true); db.addMessage(txn, message, UNKNOWN, true); db.addStatus(txn, contactId, messageId, false, false); @@ -250,17 +253,69 @@ public class H2DatabaseTest extends BriarTestCase { db.close(); } + @Test + public void testSendableMessagesMustHaveSharedGroup() throws Exception { + Database<Connection> db = open(false); + Connection txn = db.startTransaction(); + + // Add a contact, an invisible group and a shared message + db.addLocalAuthor(txn, localAuthor); + assertEquals(contactId, db.addContact(txn, author, localAuthorId, + true, true)); + db.addGroup(txn, group); + db.addMessage(txn, message, DELIVERED, true); + db.addStatus(txn, contactId, messageId, false, false); + + // The group is invisible, so the message should not be sendable + Collection<MessageId> ids = db.getMessagesToSend(txn, contactId, + ONE_MEGABYTE); + assertTrue(ids.isEmpty()); + ids = db.getMessagesToOffer(txn, contactId, 100); + assertTrue(ids.isEmpty()); + + // Making the group visible should not make the message sendable + db.addGroupVisibility(txn, contactId, groupId, false); + ids = db.getMessagesToSend(txn, contactId, ONE_MEGABYTE); + assertTrue(ids.isEmpty()); + ids = db.getMessagesToOffer(txn, contactId, 100); + assertTrue(ids.isEmpty()); + + // Sharing the group should make the message sendable + db.setGroupVisibility(txn, contactId, groupId, true); + ids = db.getMessagesToSend(txn, contactId, ONE_MEGABYTE); + assertEquals(Collections.singletonList(messageId), ids); + ids = db.getMessagesToOffer(txn, contactId, 100); + assertEquals(Collections.singletonList(messageId), ids); + + // Unsharing the group should make the message unsendable + db.setGroupVisibility(txn, contactId, groupId, false); + ids = db.getMessagesToSend(txn, contactId, ONE_MEGABYTE); + assertTrue(ids.isEmpty()); + ids = db.getMessagesToOffer(txn, contactId, 100); + assertTrue(ids.isEmpty()); + + // Making the group invisible should make the message unsendable + db.removeGroupVisibility(txn, contactId, groupId); + ids = db.getMessagesToSend(txn, contactId, ONE_MEGABYTE); + assertTrue(ids.isEmpty()); + ids = db.getMessagesToOffer(txn, contactId, 100); + assertTrue(ids.isEmpty()); + + db.commitTransaction(txn); + db.close(); + } + @Test public void testSendableMessagesMustBeShared() throws Exception { Database<Connection> db = open(false); Connection txn = db.startTransaction(); - // Add a contact, a group and an unshared message + // Add a contact, a shared group and an unshared message db.addLocalAuthor(txn, localAuthor); assertEquals(contactId, db.addContact(txn, author, localAuthorId, true, true)); db.addGroup(txn, group); - db.addVisibility(txn, contactId, groupId); + db.addGroupVisibility(txn, contactId, groupId, true); db.addMessage(txn, message, DELIVERED, false); db.addStatus(txn, contactId, messageId, false, false); @@ -287,12 +342,12 @@ public class H2DatabaseTest extends BriarTestCase { Database<Connection> db = open(false); Connection txn = db.startTransaction(); - // Add a contact, a group and a message + // Add a contact, a shared group and a shared message db.addLocalAuthor(txn, localAuthor); assertEquals(contactId, db.addContact(txn, author, localAuthorId, true, true)); db.addGroup(txn, group); - db.addVisibility(txn, contactId, groupId); + db.addGroupVisibility(txn, contactId, groupId, true); db.addMessage(txn, message, DELIVERED, true); db.addStatus(txn, contactId, messageId, false, false); @@ -314,12 +369,12 @@ public class H2DatabaseTest extends BriarTestCase { Database<Connection> db = open(false); Connection txn = db.startTransaction(); - // Add a contact and a group + // Add a contact and a visible group db.addLocalAuthor(txn, localAuthor); assertEquals(contactId, db.addContact(txn, author, localAuthorId, true, true)); db.addGroup(txn, group); - db.addVisibility(txn, contactId, groupId); + db.addGroupVisibility(txn, contactId, groupId, false); // Add some messages to ack MessageId messageId1 = new MessageId(TestUtils.getRandomId()); @@ -351,12 +406,12 @@ public class H2DatabaseTest extends BriarTestCase { Database<Connection> db = open(false); Connection txn = db.startTransaction(); - // Add a contact, a group and a message + // Add a contact, a shared group and a shared message db.addLocalAuthor(txn, localAuthor); assertEquals(contactId, db.addContact(txn, author, localAuthorId, true, true)); db.addGroup(txn, group); - db.addVisibility(txn, contactId, groupId); + db.addGroupVisibility(txn, contactId, groupId, true); db.addMessage(txn, message, DELIVERED, true); db.addStatus(txn, contactId, messageId, false, false); @@ -515,12 +570,12 @@ public class H2DatabaseTest extends BriarTestCase { Database<Connection> db = open(false); Connection txn = db.startTransaction(); - // Add a contact and a group + // Add a contact and a shared group db.addLocalAuthor(txn, localAuthor); assertEquals(contactId, db.addContact(txn, author, localAuthorId, true, true)); db.addGroup(txn, group); - db.addVisibility(txn, contactId, groupId); + db.addGroupVisibility(txn, contactId, groupId, true); // The message is not in the database assertFalse(db.containsVisibleMessage(txn, contactId, messageId)); @@ -569,7 +624,7 @@ public class H2DatabaseTest extends BriarTestCase { } @Test - public void testVisibility() throws Exception { + public void testGroupVisibility() throws Exception { Database<Connection> db = open(false); Connection txn = db.startTransaction(); @@ -580,52 +635,33 @@ public class H2DatabaseTest extends BriarTestCase { db.addGroup(txn, group); // The group should not be visible to the contact - assertEquals(Collections.emptyList(), db.getVisibility(txn, groupId)); + assertEquals(INVISIBLE, db.getGroupVisibility(txn, contactId, groupId)); + assertEquals(Collections.emptyList(), + db.getGroupVisibility(txn, groupId)); // Make the group visible to the contact - db.addVisibility(txn, contactId, groupId); + db.addGroupVisibility(txn, contactId, groupId, false); + assertEquals(VISIBLE, db.getGroupVisibility(txn, contactId, groupId)); assertEquals(Collections.singletonList(contactId), - db.getVisibility(txn, groupId)); - - // Make the group invisible again - db.removeVisibility(txn, contactId, groupId); - assertEquals(Collections.emptyList(), db.getVisibility(txn, groupId)); + db.getGroupVisibility(txn, groupId)); - db.commitTransaction(txn); - db.close(); - } - - @Test - public void testMultipleGroupChanges() throws Exception { - // Create some groups - List<Group> groups = new ArrayList<>(); - for (int i = 0; i < 100; i++) { - GroupId id = new GroupId(TestUtils.getRandomId()); - ClientId clientId = new ClientId(TestUtils.getRandomString(5)); - byte[] descriptor = new byte[0]; - groups.add(new Group(id, clientId, descriptor)); - } + // Share the group with the contact + db.setGroupVisibility(txn, contactId, groupId, true); + assertEquals(SHARED, db.getGroupVisibility(txn, contactId, groupId)); + assertEquals(Collections.singletonList(contactId), + db.getGroupVisibility(txn, groupId)); - Database<Connection> db = open(false); - Connection txn = db.startTransaction(); + // Unshare the group with the contact + db.setGroupVisibility(txn, contactId, groupId, false); + assertEquals(VISIBLE, db.getGroupVisibility(txn, contactId, groupId)); + assertEquals(Collections.singletonList(contactId), + db.getGroupVisibility(txn, groupId)); - // Add a contact and the groups - db.addLocalAuthor(txn, localAuthor); - assertEquals(contactId, db.addContact(txn, author, localAuthorId, - true, true)); - for (Group g : groups) db.addGroup(txn, g); - - // Make the groups visible to the contact - Collections.shuffle(groups); - for (Group g : groups) db.addVisibility(txn, contactId, g.getId()); - - // Make some of the groups invisible to the contact and remove them all - Collections.shuffle(groups); - for (Group g : groups) { - if (Math.random() < 0.5) - db.removeVisibility(txn, contactId, g.getId()); - db.removeGroup(txn, g.getId()); - } + // Make the group invisible again + db.removeGroupVisibility(txn, contactId, groupId); + assertEquals(INVISIBLE, db.getGroupVisibility(txn, contactId, groupId)); + assertEquals(Collections.emptyList(), + db.getGroupVisibility(txn, groupId)); db.commitTransaction(txn); db.close(); @@ -1144,15 +1180,15 @@ public class H2DatabaseTest extends BriarTestCase { metadata1.put("foo", new byte[]{'b', 'a', 'r'}); db.mergeMessageMetadata(txn, messageId1, metadata1); - for (int i = 1; i <= 2; i++) { + for (int i = 0; i < 2; i++) { Metadata query; - if (i == 1) { + if (i == 0) { // Query the metadata with an empty query query = new Metadata(); } else { // Query for foo query = new Metadata(); - query.put("foo", metadata.get("foo")); + query.put("foo", new byte[]{'b', 'a', 'r'}); } db.setMessageState(txn, messageId, DELIVERED); @@ -1160,32 +1196,39 @@ public class H2DatabaseTest extends BriarTestCase { Map<MessageId, Metadata> all = db.getMessageMetadata(txn, groupId, query); assertEquals(2, all.size()); - assertEquals(2, all.get(messageId).size()); - assertEquals(1, all.get(messageId1).size()); + assertMetadataEquals(metadata, all.get(messageId)); + assertMetadataEquals(metadata1, all.get(messageId1)); // No metadata for unknown messages db.setMessageState(txn, messageId, UNKNOWN); - db.setMessageState(txn, messageId1, UNKNOWN); all = db.getMessageMetadata(txn, groupId, query); - assertTrue(all.isEmpty()); + assertEquals(1, all.size()); + assertMetadataEquals(metadata1, all.get(messageId1)); // No metadata for invalid messages db.setMessageState(txn, messageId, INVALID); - db.setMessageState(txn, messageId1, INVALID); all = db.getMessageMetadata(txn, groupId, query); - assertTrue(all.isEmpty()); + assertEquals(1, all.size()); + assertMetadataEquals(metadata1, all.get(messageId1)); // No metadata for pending messages db.setMessageState(txn, messageId, PENDING); - db.setMessageState(txn, messageId1, PENDING); all = db.getMessageMetadata(txn, groupId, query); - assertTrue(all.isEmpty()); + assertEquals(1, all.size()); + assertMetadataEquals(metadata1, all.get(messageId1)); } db.commitTransaction(txn); db.close(); } + private void assertMetadataEquals(Metadata m1, Metadata m2) { + assertEquals(m1.keySet(), m2.keySet()); + for (Entry<String, byte[]> e : m1.entrySet()) { + assertArrayEquals(e.getValue(), m2.get(e.getKey())); + } + } + @Test public void testMessageDependencies() throws Exception { MessageId messageId1 = new MessageId(TestUtils.getRandomId()); @@ -1398,16 +1441,12 @@ public class H2DatabaseTest extends BriarTestCase { Database<Connection> db = open(false); Connection txn = db.startTransaction(); - // Add a contact + // Add a contact, a shared group and a shared message db.addLocalAuthor(txn, localAuthor); assertEquals(contactId, db.addContact(txn, author, localAuthorId, true, true)); - - // Add a group and make it visible to the contact db.addGroup(txn, group); - db.addVisibility(txn, contactId, groupId); - - // Add a message to the group + db.addGroupVisibility(txn, contactId, groupId, true); db.addMessage(txn, message, DELIVERED, true); db.addStatus(txn, contactId, messageId, false, false); @@ -1471,33 +1510,7 @@ public class H2DatabaseTest extends BriarTestCase { } @Test - public void testGroupsVisibleToContacts() throws Exception { - Database<Connection> db = open(false); - Connection txn = db.startTransaction(); - - // Add a contact and a group - db.addLocalAuthor(txn, localAuthor); - assertEquals(contactId, db.addContact(txn, author, localAuthorId, - true, true)); - db.addGroup(txn, group); - - // The group should not be visible to the contact - assertFalse(db.containsVisibleGroup(txn, contactId, groupId)); - - // Make the group visible to the contact - db.addVisibility(txn, contactId, groupId); - assertTrue(db.containsVisibleGroup(txn, contactId, groupId)); - - // Make the group invisible to the contact - db.removeVisibility(txn, contactId, groupId); - assertFalse(db.containsVisibleGroup(txn, contactId, groupId)); - - db.commitTransaction(txn); - db.close(); - } - - @Test - public void testDifferentLocalPseudonymsCanHaveTheSameContact() + public void testDifferentLocalAuthorsCanHaveTheSameContact() throws Exception { AuthorId localAuthorId1 = new AuthorId(TestUtils.getRandomId()); LocalAuthor localAuthor1 = new LocalAuthor(localAuthorId1, "Carol", @@ -1506,11 +1519,11 @@ public class H2DatabaseTest extends BriarTestCase { Database<Connection> db = open(false); Connection txn = db.startTransaction(); - // Add two local pseudonyms + // Add two local authors db.addLocalAuthor(txn, localAuthor); db.addLocalAuthor(txn, localAuthor1); - // Add the same contact for each local pseudonym + // Add the same contact for each local author ContactId contactId = db.addContact(txn, author, localAuthorId, true, true); ContactId contactId1 = @@ -1531,12 +1544,12 @@ public class H2DatabaseTest extends BriarTestCase { Database<Connection> db = open(false); Connection txn = db.startTransaction(); - // Add a contact, a group and a message + // Add a contact, a shared group and a shared message db.addLocalAuthor(txn, localAuthor); assertEquals(contactId, db.addContact(txn, author, localAuthorId, true, true)); db.addGroup(txn, group); - db.addVisibility(txn, contactId, groupId); + db.addGroupVisibility(txn, contactId, groupId, true); db.addMessage(txn, message, DELIVERED, true); db.addStatus(txn, contactId, messageId, false, false);