diff --git a/briar-api/src/org/briarproject/api/db/DatabaseComponent.java b/briar-api/src/org/briarproject/api/db/DatabaseComponent.java index 852affa2db17b4edef84ff581f5bd8fdedefd0a7..0e51240b20753856455beee07dd86036c881736a 100644 --- a/briar-api/src/org/briarproject/api/db/DatabaseComponent.java +++ b/briar-api/src/org/briarproject/api/db/DatabaseComponent.java @@ -56,7 +56,7 @@ public interface DatabaseComponent { void addLocalAuthor(LocalAuthor a) throws DbException; /** Stores a local message. */ - void addLocalMessage(Message m, ClientId c, Metadata meta) + void addLocalMessage(Message m, ClientId c, Metadata meta, boolean shared) throws DbException; /** @@ -277,8 +277,11 @@ public interface DatabaseComponent { void setLocalAuthorStatus(AuthorId a, StorageStatus s) throws DbException; + /** Marks the given message as shared or unshared. */ + void setMessageShared(Message m, boolean shared) throws DbException; + /** Marks the given message as valid or invalid. */ - void setMessageValidity(Message m, ClientId c, boolean valid) + void setMessageValid(Message m, ClientId c, boolean valid) throws DbException; /** diff --git a/briar-api/src/org/briarproject/api/event/MessageSharedEvent.java b/briar-api/src/org/briarproject/api/event/MessageSharedEvent.java new file mode 100644 index 0000000000000000000000000000000000000000..d011e8c3ebe5394e2dccb13c22f423c827db725e --- /dev/null +++ b/briar-api/src/org/briarproject/api/event/MessageSharedEvent.java @@ -0,0 +1,17 @@ +package org.briarproject.api.event; + +import org.briarproject.api.sync.Message; + +/** An event that is broadcast when a message is shared. */ +public class MessageSharedEvent extends Event { + + private final Message message; + + public MessageSharedEvent(Message message) { + this.message = message; + } + + public Message getMessage() { + return message; + } +} diff --git a/briar-api/src/org/briarproject/api/sync/ValidationManager.java b/briar-api/src/org/briarproject/api/sync/ValidationManager.java index 4de322bc0c793d13f4cfd80220a81f47448a5302..88be9d4c82de2b427fce92f1f55264785f5c0841 100644 --- a/briar-api/src/org/briarproject/api/sync/ValidationManager.java +++ b/briar-api/src/org/briarproject/api/sync/ValidationManager.java @@ -6,13 +6,13 @@ package org.briarproject.api.sync; */ public interface ValidationManager { - enum Status { + enum Validity { UNKNOWN(0), INVALID(1), VALID(2); private final int value; - Status(int value) { + Validity(int value) { this.value = value; } @@ -20,8 +20,8 @@ public interface ValidationManager { return value; } - public static Status fromValue(int value) { - for (Status s : values()) if (s.value == value) return s; + public static Validity fromValue(int value) { + for (Validity s : values()) if (s.value == value) return s; throw new IllegalArgumentException(); } } diff --git a/briar-core/src/org/briarproject/db/Database.java b/briar-core/src/org/briarproject/db/Database.java index f8901b01abb3f22a7247b41ef1451705a5ddef23..22cc9357f61a4065de80172b6cfad88449d67c8f 100644 --- a/briar-core/src/org/briarproject/db/Database.java +++ b/briar-core/src/org/briarproject/db/Database.java @@ -19,6 +19,7 @@ import org.briarproject.api.sync.MessageId; import org.briarproject.api.sync.MessageStatus; import org.briarproject.api.sync.SubscriptionAck; import org.briarproject.api.sync.SubscriptionUpdate; +import org.briarproject.api.sync.ValidationManager.Validity; import org.briarproject.api.transport.TransportKeys; import java.io.IOException; @@ -111,7 +112,8 @@ interface Database<T> { * <p> * Locking: write. */ - void addMessage(T txn, Message m, boolean local) throws DbException; + void addMessage(T txn, Message m, Validity validity, boolean shared) + throws DbException; /** * Records that a message has been offered by the given contact. @@ -618,13 +620,20 @@ interface Database<T> { throws DbException; /** - * Marks the given message as valid or invalid. + * Marks the given message as shared or unshared. * <p> * Locking: write. */ - void setMessageValidity(T txn, MessageId m, boolean valid) + void setMessageShared(T txn, MessageId m, boolean shared) throws DbException; + /** + * Marks the given message as valid or invalid. + * <p> + * Locking: write. + */ + void setMessageValid(T txn, MessageId m, boolean valid) throws DbException; + /** * Sets the reordering window for the given contact and transport in the * given rotation period. diff --git a/briar-core/src/org/briarproject/db/DatabaseComponentImpl.java b/briar-core/src/org/briarproject/db/DatabaseComponentImpl.java index 9cce3d314766f18b3db3371d000e2e11bec5befa..b303939cd3e813826af029b06966332b1ec1efd7 100644 --- a/briar-core/src/org/briarproject/db/DatabaseComponentImpl.java +++ b/briar-core/src/org/briarproject/db/DatabaseComponentImpl.java @@ -20,6 +20,7 @@ import org.briarproject.api.event.EventBus; import org.briarproject.api.event.LocalSubscriptionsUpdatedEvent; import org.briarproject.api.event.MessageAddedEvent; import org.briarproject.api.event.MessageRequestedEvent; +import org.briarproject.api.event.MessageSharedEvent; import org.briarproject.api.event.MessageToAckEvent; import org.briarproject.api.event.MessageToRequestEvent; import org.briarproject.api.event.MessageValidatedEvent; @@ -47,6 +48,7 @@ import org.briarproject.api.sync.Offer; import org.briarproject.api.sync.Request; import org.briarproject.api.sync.SubscriptionAck; import org.briarproject.api.sync.SubscriptionUpdate; +import org.briarproject.api.sync.ValidationManager.Validity; import org.briarproject.api.transport.TransportKeys; import java.io.IOException; @@ -64,6 +66,8 @@ import java.util.logging.Logger; import javax.inject.Inject; import static java.util.logging.Level.WARNING; +import static org.briarproject.api.sync.ValidationManager.Validity.UNKNOWN; +import static org.briarproject.api.sync.ValidationManager.Validity.VALID; import static org.briarproject.db.DatabaseConstants.MAX_OFFERED_MESSAGES; /** @@ -214,8 +218,8 @@ class DatabaseComponentImpl<T> implements DatabaseComponent { } } - public void addLocalMessage(Message m, ClientId c, Metadata meta) - throws DbException { + public void addLocalMessage(Message m, ClientId c, Metadata meta, + boolean shared) throws DbException { lock.writeLock().lock(); try { T txn = db.startTransaction(); @@ -224,7 +228,7 @@ class DatabaseComponentImpl<T> implements DatabaseComponent { throw new MessageExistsException(); if (!db.containsGroup(txn, m.getGroupId())) throw new NoSuchSubscriptionException(); - addMessage(txn, m, null); + addMessage(txn, m, VALID, shared, null); db.mergeMessageMetadata(txn, m.getId(), meta); db.commitTransaction(txn); } catch (DbException e) { @@ -244,9 +248,9 @@ class DatabaseComponentImpl<T> implements DatabaseComponent { * Locking: write. * @param sender null for a locally generated message. */ - private void addMessage(T txn, Message m, ContactId sender) - throws DbException { - db.addMessage(txn, m, sender == null); + private void addMessage(T txn, Message m, Validity validity, boolean shared, + ContactId sender) throws DbException { + db.addMessage(txn, m, validity, shared); GroupId g = m.getGroupId(); Collection<ContactId> visibility = db.getVisibility(txn, g); visibility = new HashSet<ContactId>(visibility); @@ -982,7 +986,7 @@ class DatabaseComponentImpl<T> implements DatabaseComponent { duplicate = db.containsMessage(txn, m.getId()); visible = db.containsVisibleGroup(txn, c, m.getGroupId()); if (visible) { - if (!duplicate) addMessage(txn, m, c); + if (!duplicate) addMessage(txn, m, UNKNOWN, true, c); db.raiseAckFlag(txn, c, m.getId()); } db.commitTransaction(txn); @@ -1214,7 +1218,27 @@ class DatabaseComponentImpl<T> implements DatabaseComponent { } } - public void setMessageValidity(Message m, ClientId c, boolean valid) + public void setMessageShared(Message m, boolean shared) + throws DbException { + lock.writeLock().lock(); + try { + T txn = db.startTransaction(); + try { + if (!db.containsMessage(txn, m.getId())) + throw new NoSuchMessageException(); + db.setMessageShared(txn, m.getId(), shared); + db.commitTransaction(txn); + } catch (DbException e) { + db.abortTransaction(txn); + throw e; + } + } finally { + lock.writeLock().unlock(); + } + if (shared) eventBus.broadcast(new MessageSharedEvent(m)); + } + + public void setMessageValid(Message m, ClientId c, boolean valid) throws DbException { lock.writeLock().lock(); try { @@ -1222,7 +1246,7 @@ class DatabaseComponentImpl<T> implements DatabaseComponent { try { if (!db.containsMessage(txn, m.getId())) throw new NoSuchMessageException(); - db.setMessageValidity(txn, m.getId(), valid); + db.setMessageValid(txn, m.getId(), valid); db.commitTransaction(txn); } catch (DbException e) { db.abortTransaction(txn); diff --git a/briar-core/src/org/briarproject/db/JdbcDatabase.java b/briar-core/src/org/briarproject/db/JdbcDatabase.java index e79e51932a4902b7014a315d85b47a6f4fbd55c2..045cb198f8a56bc972d2bb14ccce354e353f62ed 100644 --- a/briar-core/src/org/briarproject/db/JdbcDatabase.java +++ b/briar-core/src/org/briarproject/db/JdbcDatabase.java @@ -22,6 +22,7 @@ import org.briarproject.api.sync.MessageId; import org.briarproject.api.sync.MessageStatus; import org.briarproject.api.sync.SubscriptionAck; import org.briarproject.api.sync.SubscriptionUpdate; +import org.briarproject.api.sync.ValidationManager.Validity; import org.briarproject.api.system.Clock; import org.briarproject.api.transport.IncomingKeys; import org.briarproject.api.transport.OutgoingKeys; @@ -54,9 +55,9 @@ import static java.util.logging.Level.WARNING; import static org.briarproject.api.db.Metadata.REMOVE; import static org.briarproject.api.db.StorageStatus.ADDING; import static org.briarproject.api.sync.SyncConstants.MAX_SUBSCRIPTIONS; -import static org.briarproject.api.sync.ValidationManager.Status.INVALID; -import static org.briarproject.api.sync.ValidationManager.Status.UNKNOWN; -import static org.briarproject.api.sync.ValidationManager.Status.VALID; +import static org.briarproject.api.sync.ValidationManager.Validity.INVALID; +import static org.briarproject.api.sync.ValidationManager.Validity.UNKNOWN; +import static org.briarproject.api.sync.ValidationManager.Validity.VALID; import static org.briarproject.db.DatabaseConstants.DB_SETTINGS_NAMESPACE; import static org.briarproject.db.DatabaseConstants.DEVICE_ID_KEY; import static org.briarproject.db.DatabaseConstants.DEVICE_SETTINGS_NAMESPACE; @@ -70,8 +71,8 @@ import static org.briarproject.db.ExponentialBackoff.calculateExpiry; */ abstract class JdbcDatabase implements Database<Connection> { - private static final int SCHEMA_VERSION = 18; - private static final int MIN_SCHEMA_VERSION = 18; + private static final int SCHEMA_VERSION = 19; + private static final int MIN_SCHEMA_VERSION = 19; private static final String CREATE_SETTINGS = "CREATE TABLE settings" @@ -164,8 +165,8 @@ abstract class JdbcDatabase implements Database<Connection> { + " (messageId HASH NOT NULL," + " groupId HASH NOT NULL," + " timestamp BIGINT NOT NULL," - + " local BOOLEAN NOT NULL," + " valid INT NOT NULL," + + " shared BOOLEAN NOT NULL," + " length INT NOT NULL," + " raw BLOB NOT NULL," + " PRIMARY KEY (messageId)," @@ -674,19 +675,19 @@ abstract class JdbcDatabase implements Database<Connection> { } } - public void addMessage(Connection txn, Message m, boolean local) - throws DbException { + public void addMessage(Connection txn, Message m, Validity validity, + boolean shared) throws DbException { PreparedStatement ps = null; try { String sql = "INSERT INTO messages (messageId, groupId, timestamp," - + " local, valid, length, raw)" + + " valid, shared, length, raw)" + " VALUES (?, ?, ?, ?, ?, ?, ?)"; ps = txn.prepareStatement(sql); ps.setBytes(1, m.getId().getBytes()); ps.setBytes(2, m.getGroupId().getBytes()); ps.setLong(3, m.getTimestamp()); - ps.setBoolean(4, local); - ps.setInt(5, local ? VALID.getValue() : UNKNOWN.getValue()); + ps.setInt(4, validity.getValue()); + ps.setBoolean(5, shared); byte[] raw = m.getRaw(); ps.setInt(6, raw.length); ps.setBytes(7, raw); @@ -1031,7 +1032,8 @@ abstract class JdbcDatabase implements Database<Connection> { + " JOIN groupVisibilities AS gv" + " ON m.groupId = gv.groupId" + " WHERE messageId = ?" - + " AND contactId = ?"; + + " AND contactId = ?" + + " AND shared = TRUE"; ps = txn.prepareStatement(sql); ps.setBytes(1, m.getBytes()); ps.setInt(2, c.getInt()); @@ -1482,7 +1484,7 @@ abstract class JdbcDatabase implements Database<Connection> { + " ON m.messageId = s.messageId" + " AND cg.contactId = s.contactId" + " WHERE cg.contactId = ?" - + " AND valid = ?" + + " AND valid = ? AND shared = TRUE" + " AND seen = FALSE AND requested = FALSE" + " AND s.expiry < ?" + " ORDER BY timestamp DESC LIMIT ?"; @@ -1544,7 +1546,7 @@ abstract class JdbcDatabase implements Database<Connection> { + " ON m.messageId = s.messageId" + " AND cg.contactId = s.contactId" + " WHERE cg.contactId = ?" - + " AND valid = ?" + + " AND valid = ? AND shared = TRUE" + " AND seen = FALSE" + " AND s.expiry < ?" + " ORDER BY timestamp DESC"; @@ -1633,7 +1635,7 @@ abstract class JdbcDatabase implements Database<Connection> { + " ON m.messageId = s.messageId" + " AND cg.contactId = s.contactId" + " WHERE cg.contactId = ?" - + " AND valid = ?" + + " AND valid = ? AND shared = TRUE" + " AND seen = FALSE AND requested = TRUE" + " AND s.expiry < ?" + " ORDER BY timestamp DESC"; @@ -1660,7 +1662,8 @@ abstract class JdbcDatabase implements Database<Connection> { } } - public Settings getSettings(Connection txn, String namespace) throws DbException { + public Settings getSettings(Connection txn, String namespace) + throws DbException { PreparedStatement ps = null; ResultSet rs = null; try { @@ -2397,7 +2400,24 @@ abstract class JdbcDatabase implements Database<Connection> { } } - public void setMessageValidity(Connection txn, MessageId m, boolean valid) + public void setMessageShared(Connection txn, MessageId m, boolean shared) + throws DbException { + PreparedStatement ps = null; + try { + String sql = "UPDATE messages SET shared = ? WHERE messageId = ?"; + ps = txn.prepareStatement(sql); + ps.setBoolean(1, shared); + ps.setBytes(2, m.getBytes()); + int affected = ps.executeUpdate(); + if (affected < 0) throw new DbStateException(); + ps.close(); + } catch (SQLException e) { + tryToClose(ps); + throw new DbException(e); + } + } + + public void setMessageValid(Connection txn, MessageId m, boolean valid) throws DbException { PreparedStatement ps = null; try { diff --git a/briar-core/src/org/briarproject/forum/ForumManagerImpl.java b/briar-core/src/org/briarproject/forum/ForumManagerImpl.java index ba604e93809ff82de3d26a2503268bcbe3834c62..6b846d6714153d21a1b8ed36c01ab4ace2dfc633 100644 --- a/briar-core/src/org/briarproject/forum/ForumManagerImpl.java +++ b/briar-core/src/org/briarproject/forum/ForumManagerImpl.java @@ -135,7 +135,7 @@ class ForumManagerImpl implements ForumManager { d.put("read", true); try { Metadata meta = metadataEncoder.encode(d); - db.addLocalMessage(p.getMessage(), CLIENT_ID, meta); + db.addLocalMessage(p.getMessage(), CLIENT_ID, meta, true); } catch (FormatException e) { if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e); } diff --git a/briar-core/src/org/briarproject/messaging/MessagingManagerImpl.java b/briar-core/src/org/briarproject/messaging/MessagingManagerImpl.java index 90c8f426e2f6e7b680991e07f659e8fde2d8f98d..ea1a852a90303d5f8072982f81e3fb9450fb8825 100644 --- a/briar-core/src/org/briarproject/messaging/MessagingManagerImpl.java +++ b/briar-core/src/org/briarproject/messaging/MessagingManagerImpl.java @@ -116,7 +116,7 @@ class MessagingManagerImpl implements MessagingManager, AddContactHook, d.put("read", true); try { Metadata meta = metadataEncoder.encode(d); - db.addLocalMessage(m.getMessage(), CLIENT_ID, meta); + db.addLocalMessage(m.getMessage(), CLIENT_ID, meta, true); } catch (FormatException e) { throw new RuntimeException(e); } diff --git a/briar-core/src/org/briarproject/properties/TransportPropertyManagerImpl.java b/briar-core/src/org/briarproject/properties/TransportPropertyManagerImpl.java index 10328e3c26c3bc23b39131a88f368fbe79c9da89..9d42269ada92b2e8c8f643e261d617fd5570300b 100644 --- a/briar-core/src/org/briarproject/properties/TransportPropertyManagerImpl.java +++ b/briar-core/src/org/briarproject/properties/TransportPropertyManagerImpl.java @@ -130,7 +130,7 @@ class TransportPropertyManagerImpl implements TransportPropertyManager, d.put("transportId", t.getString()); d.put("version", version); d.put("local", true); - db.addLocalMessage(m, CLIENT_ID, metadataEncoder.encode(d)); + db.addLocalMessage(m, CLIENT_ID, metadataEncoder.encode(d), true); } private byte[] encodeProperties(DeviceId dev, TransportId t, diff --git a/briar-core/src/org/briarproject/sync/DuplexOutgoingSession.java b/briar-core/src/org/briarproject/sync/DuplexOutgoingSession.java index 52528de6012720b86f5f6ee2d50190d4859569d8..29725fe81741c293093578e302692231cee403e3 100644 --- a/briar-core/src/org/briarproject/sync/DuplexOutgoingSession.java +++ b/briar-core/src/org/briarproject/sync/DuplexOutgoingSession.java @@ -10,6 +10,7 @@ import org.briarproject.api.event.EventBus; import org.briarproject.api.event.EventListener; import org.briarproject.api.event.LocalSubscriptionsUpdatedEvent; import org.briarproject.api.event.MessageRequestedEvent; +import org.briarproject.api.event.MessageSharedEvent; import org.briarproject.api.event.MessageToAckEvent; import org.briarproject.api.event.MessageToRequestEvent; import org.briarproject.api.event.MessageValidatedEvent; @@ -154,6 +155,8 @@ class DuplexOutgoingSession implements SyncSession, EventListener { if (e instanceof ContactRemovedEvent) { ContactRemovedEvent c = (ContactRemovedEvent) e; if (c.getContactId().equals(contactId)) interrupt(); + } else if (e instanceof MessageSharedEvent) { + dbExecutor.execute(new GenerateOffer()); } else if (e instanceof MessageValidatedEvent) { if (((MessageValidatedEvent) e).isValid()) dbExecutor.execute(new GenerateOffer()); diff --git a/briar-core/src/org/briarproject/sync/ValidationManagerImpl.java b/briar-core/src/org/briarproject/sync/ValidationManagerImpl.java index 28e248ae42fbd79b59f777978db4e8fa6ebf9109..3dcd616e047e03324f8be610a36e411330c3f33b 100644 --- a/briar-core/src/org/briarproject/sync/ValidationManagerImpl.java +++ b/briar-core/src/org/briarproject/sync/ValidationManagerImpl.java @@ -117,10 +117,10 @@ class ValidationManagerImpl implements ValidationManager, Service, public void run() { try { if (meta == null) { - db.setMessageValidity(m, c, false); + db.setMessageValid(m, c, false); } else { db.mergeMessageMetadata(m.getId(), meta); - db.setMessageValidity(m, c, true); + db.setMessageValid(m, c, true); } } catch (NoSuchMessageException e) { LOG.info("Message removed during validation"); diff --git a/briar-tests/src/org/briarproject/db/DatabaseComponentImplTest.java b/briar-tests/src/org/briarproject/db/DatabaseComponentImplTest.java index 37998807a7bf4e3117215c0076e1eddc6fa1e834..eb0d8315c05224507ae5828488c055ad2040b25c 100644 --- a/briar-tests/src/org/briarproject/db/DatabaseComponentImplTest.java +++ b/briar-tests/src/org/briarproject/db/DatabaseComponentImplTest.java @@ -53,6 +53,8 @@ import java.util.Collections; import static org.briarproject.api.identity.AuthorConstants.MAX_PUBLIC_KEY_LENGTH; import static org.briarproject.api.sync.SyncConstants.MAX_GROUP_DESCRIPTOR_LENGTH; +import static org.briarproject.api.sync.ValidationManager.Validity.UNKNOWN; +import static org.briarproject.api.sync.ValidationManager.Validity.VALID; import static org.briarproject.db.DatabaseConstants.MAX_OFFERED_MESSAGES; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; @@ -211,7 +213,7 @@ public class DatabaseComponentImplTest extends BriarTestCase { shutdown); try { - db.addLocalMessage(message, clientId, metadata); + db.addLocalMessage(message, clientId, metadata, true); fail(); } catch (MessageExistsException expected) { // Expected @@ -241,7 +243,7 @@ public class DatabaseComponentImplTest extends BriarTestCase { shutdown); try { - db.addLocalMessage(message, clientId, metadata); + db.addLocalMessage(message, clientId, metadata, true); fail(); } catch (NoSuchSubscriptionException expected) { // Expected @@ -264,7 +266,7 @@ public class DatabaseComponentImplTest extends BriarTestCase { will(returnValue(false)); oneOf(database).containsGroup(txn, groupId); will(returnValue(true)); - oneOf(database).addMessage(txn, message, true); + oneOf(database).addMessage(txn, message, VALID, true); oneOf(database).mergeMessageMetadata(txn, messageId, metadata); oneOf(database).getVisibility(txn, groupId); will(returnValue(Collections.singletonList(contactId))); @@ -281,7 +283,7 @@ public class DatabaseComponentImplTest extends BriarTestCase { DatabaseComponent db = createDatabaseComponent(database, eventBus, shutdown); - db.addLocalMessage(message, clientId, metadata); + db.addLocalMessage(message, clientId, metadata, true); context.assertIsSatisfied(); } @@ -559,11 +561,11 @@ public class DatabaseComponentImplTest extends BriarTestCase { final EventBus eventBus = context.mock(EventBus.class); context.checking(new Expectations() {{ // Check whether the message is in the DB (which it's not) - exactly(4).of(database).startTransaction(); + exactly(6).of(database).startTransaction(); will(returnValue(txn)); - exactly(4).of(database).containsMessage(txn, messageId); + exactly(6).of(database).containsMessage(txn, messageId); will(returnValue(false)); - exactly(4).of(database).abortTransaction(txn); + exactly(6).of(database).abortTransaction(txn); // This is needed for getMessageStatus() to proceed exactly(1).of(database).containsContact(txn, contactId); will(returnValue(true)); @@ -599,6 +601,20 @@ public class DatabaseComponentImplTest extends BriarTestCase { // Expected } + try { + db.setMessageShared(message, true); + fail(); + } catch (NoSuchMessageException expected) { + // Expected + } + + try { + db.setMessageValid(message, clientId, true); + fail(); + } catch (NoSuchMessageException expected) { + // Expected + } + context.assertIsSatisfied(); } @@ -932,7 +948,7 @@ public class DatabaseComponentImplTest extends BriarTestCase { will(returnValue(false)); oneOf(database).containsVisibleGroup(txn, contactId, groupId); will(returnValue(true)); - oneOf(database).addMessage(txn, message, false); + oneOf(database).addMessage(txn, message, UNKNOWN, true); oneOf(database).getVisibility(txn, groupId); will(returnValue(Collections.singletonList(contactId))); oneOf(database).getContactIds(txn); diff --git a/briar-tests/src/org/briarproject/db/H2DatabaseTest.java b/briar-tests/src/org/briarproject/db/H2DatabaseTest.java index 5dd1208986b9edbfecd0f191385263bbf5d7336a..eb1bbb336c2ac938128919e927655c3e18c8e302 100644 --- a/briar-tests/src/org/briarproject/db/H2DatabaseTest.java +++ b/briar-tests/src/org/briarproject/db/H2DatabaseTest.java @@ -34,7 +34,6 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; -import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Map.Entry; @@ -47,6 +46,8 @@ 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.SyncConstants.MAX_GROUP_DESCRIPTOR_LENGTH; import static org.briarproject.api.sync.SyncConstants.MAX_MESSAGE_LENGTH; +import static org.briarproject.api.sync.ValidationManager.Validity.UNKNOWN; +import static org.briarproject.api.sync.ValidationManager.Validity.VALID; import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; @@ -113,7 +114,7 @@ public class H2DatabaseTest extends BriarTestCase { db.addGroup(txn, group); assertTrue(db.containsGroup(txn, groupId)); assertFalse(db.containsMessage(txn, messageId)); - db.addMessage(txn, message, true); + db.addMessage(txn, message, VALID, true); assertTrue(db.containsMessage(txn, messageId)); db.commitTransaction(txn); db.close(); @@ -126,6 +127,7 @@ public class H2DatabaseTest extends BriarTestCase { assertTrue(db.containsMessage(txn, messageId)); byte[] raw1 = db.getRawMessage(txn, messageId); assertArrayEquals(raw, raw1); + // Delete the records db.removeMessage(txn, messageId); db.removeContact(txn, contactId); @@ -150,7 +152,7 @@ public class H2DatabaseTest extends BriarTestCase { // Subscribe to a group and store a message db.addGroup(txn, group); - db.addMessage(txn, message, true); + db.addMessage(txn, message, VALID, true); // Unsubscribing from the group should remove the message assertTrue(db.containsMessage(txn, messageId)); @@ -172,26 +174,106 @@ public class H2DatabaseTest extends BriarTestCase { db.addGroup(txn, group); db.addVisibility(txn, contactId, groupId); db.setGroups(txn, contactId, Collections.singletonList(group), 1); - db.addMessage(txn, message, true); + db.addMessage(txn, message, VALID, true); // The message has no status yet, so it 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()); // Adding a status with seen = false should make the message sendable db.addStatus(txn, contactId, messageId, false, false); ids = db.getMessagesToSend(txn, contactId, ONE_MEGABYTE); - assertFalse(ids.isEmpty()); - Iterator<MessageId> it = ids.iterator(); - assertTrue(it.hasNext()); - assertEquals(messageId, it.next()); - assertFalse(it.hasNext()); + assertEquals(Collections.singletonList(messageId), ids); + ids = db.getMessagesToOffer(txn, contactId, 100); + assertEquals(Collections.singletonList(messageId), ids); // Changing the status to seen = true should make the message unsendable db.raiseSeenFlag(txn, contactId, messageId); 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 testSendableMessagesMustBeValid() throws Exception { + Database<Connection> db = open(false); + Connection txn = db.startTransaction(); + + // Add a contact, subscribe to a group and store an unvalidated message + db.addLocalAuthor(txn, localAuthor); + assertEquals(contactId, db.addContact(txn, author, localAuthorId)); + db.addGroup(txn, group); + db.addVisibility(txn, contactId, groupId); + db.setGroups(txn, contactId, Collections.singletonList(group), 1); + db.addMessage(txn, message, UNKNOWN, true); + db.addStatus(txn, contactId, messageId, false, false); + + // The message has not been validated, so it 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()); + + // Marking the message valid should make it sendable + db.setMessageValid(txn, messageId, true); + ids = db.getMessagesToSend(txn, contactId, ONE_MEGABYTE); + assertEquals(Collections.singletonList(messageId), ids); + ids = db.getMessagesToOffer(txn, contactId, 100); + assertEquals(Collections.singletonList(messageId), ids); + + // Marking the message invalid should make it unsendable + db.setMessageValid(txn, messageId, false); + 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, subscribe to a group and store an unshared message + db.addLocalAuthor(txn, localAuthor); + assertEquals(contactId, db.addContact(txn, author, localAuthorId)); + db.addGroup(txn, group); + db.addVisibility(txn, contactId, groupId); + db.setGroups(txn, contactId, Collections.singletonList(group), 1); + db.addMessage(txn, message, VALID, false); + db.addStatus(txn, contactId, messageId, false, false); + + // The message is not shared, so it 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()); + + // Sharing the message should make it sendable + db.setMessageShared(txn, messageId, 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 message should make it unsendable + db.setMessageShared(txn, messageId, false); + ids = db.getMessagesToSend(txn, contactId, ONE_MEGABYTE); + assertTrue(ids.isEmpty()); + ids = db.getMessagesToOffer(txn, contactId, 100); + assertTrue(ids.isEmpty()); db.commitTransaction(txn); db.close(); @@ -207,27 +289,29 @@ public class H2DatabaseTest extends BriarTestCase { assertEquals(contactId, db.addContact(txn, author, localAuthorId)); db.addGroup(txn, group); db.addVisibility(txn, contactId, groupId); - db.addMessage(txn, message, true); + db.addMessage(txn, message, VALID, true); db.addStatus(txn, contactId, messageId, false, false); // The contact is not subscribed, 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()); // The contact subscribing should make the message sendable db.setGroups(txn, contactId, Collections.singletonList(group), 1); ids = db.getMessagesToSend(txn, contactId, ONE_MEGABYTE); - assertFalse(ids.isEmpty()); - Iterator<MessageId> it = ids.iterator(); - assertTrue(it.hasNext()); - assertEquals(messageId, it.next()); - assertFalse(it.hasNext()); + assertEquals(Collections.singletonList(messageId), ids); + ids = db.getMessagesToOffer(txn, contactId, 100); + assertEquals(Collections.singletonList(messageId), ids); // The contact unsubscribing should make the message unsendable db.setGroups(txn, contactId, Collections.<Group>emptyList(), 2); ids = db.getMessagesToSend(txn, contactId, ONE_MEGABYTE); assertTrue(ids.isEmpty()); + ids = db.getMessagesToOffer(txn, contactId, 100); + assertTrue(ids.isEmpty()); db.commitTransaction(txn); db.close(); @@ -244,7 +328,7 @@ public class H2DatabaseTest extends BriarTestCase { db.addGroup(txn, group); db.addVisibility(txn, contactId, groupId); db.setGroups(txn, contactId, Collections.singletonList(group), 1); - db.addMessage(txn, message, true); + db.addMessage(txn, message, VALID, true); db.addStatus(txn, contactId, messageId, false, false); // The message is sendable, but too large to send @@ -254,11 +338,7 @@ public class H2DatabaseTest extends BriarTestCase { // The message is just the right size to send ids = db.getMessagesToSend(txn, contactId, size); - assertFalse(ids.isEmpty()); - Iterator<MessageId> it = ids.iterator(); - assertTrue(it.hasNext()); - assertEquals(messageId, it.next()); - assertFalse(it.hasNext()); + assertEquals(Collections.singletonList(messageId), ids); db.commitTransaction(txn); db.close(); @@ -274,7 +354,7 @@ public class H2DatabaseTest extends BriarTestCase { assertEquals(contactId, db.addContact(txn, author, localAuthorId)); db.addGroup(txn, group); db.setGroups(txn, contactId, Collections.singletonList(group), 1); - db.addMessage(txn, message, true); + db.addMessage(txn, message, VALID, true); db.addStatus(txn, contactId, messageId, false, false); // The subscription is not visible to the contact, so the message @@ -282,15 +362,22 @@ public class H2DatabaseTest extends BriarTestCase { Collection<MessageId> ids = db.getMessagesToSend(txn, contactId, ONE_MEGABYTE); assertTrue(ids.isEmpty()); + ids = db.getMessagesToOffer(txn, contactId, 100); + assertTrue(ids.isEmpty()); // Making the subscription visible should make the message sendable db.addVisibility(txn, contactId, groupId); ids = db.getMessagesToSend(txn, contactId, ONE_MEGABYTE); - assertFalse(ids.isEmpty()); - Iterator<MessageId> it = ids.iterator(); - assertTrue(it.hasNext()); - assertEquals(messageId, it.next()); - assertFalse(it.hasNext()); + assertEquals(Collections.singletonList(messageId), ids); + ids = db.getMessagesToOffer(txn, contactId, 100); + assertEquals(Collections.singletonList(messageId), ids); + + // Making the subscription invisible should make the message unsendable + db.removeVisibility(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(); @@ -310,16 +397,16 @@ public class H2DatabaseTest extends BriarTestCase { // Add some messages to ack MessageId messageId1 = new MessageId(TestUtils.getRandomId()); Message message1 = new Message(messageId1, groupId, timestamp, raw); - db.addMessage(txn, message, true); + db.addMessage(txn, message, VALID, true); db.addStatus(txn, contactId, messageId, false, true); db.raiseAckFlag(txn, contactId, messageId); - db.addMessage(txn, message1, true); + db.addMessage(txn, message1, VALID, true); db.addStatus(txn, contactId, messageId1, false, true); db.raiseAckFlag(txn, contactId, messageId1); // Both message IDs should be returned - Collection<MessageId> ids = Arrays.asList(messageId, messageId1); - assertEquals(ids, db.getMessagesToAck(txn, contactId, 1234)); + Collection<MessageId> ids = db.getMessagesToAck(txn, contactId, 1234); + assertEquals(Arrays.asList(messageId, messageId1), ids); // Remove both message IDs db.lowerAckFlag(txn, contactId, Arrays.asList(messageId, messageId1)); @@ -332,38 +419,6 @@ public class H2DatabaseTest extends BriarTestCase { db.close(); } - @Test - public void testDuplicateMessageReceived() throws Exception { - Database<Connection> db = open(false); - Connection txn = db.startTransaction(); - - // Add a contact and subscribe to a group - db.addLocalAuthor(txn, localAuthor); - assertEquals(contactId, db.addContact(txn, author, localAuthorId)); - db.addGroup(txn, group); - db.setGroups(txn, contactId, Collections.singletonList(group), 1); - - // Receive the same message twice - db.addMessage(txn, message, true); - db.addStatus(txn, contactId, messageId, false, true); - db.raiseAckFlag(txn, contactId, messageId); - db.raiseAckFlag(txn, contactId, messageId); - - // The message ID should only be returned once - Collection<MessageId> ids = db.getMessagesToAck(txn, contactId, 1234); - assertEquals(Collections.singletonList(messageId), ids); - - // Remove the message ID - db.lowerAckFlag(txn, contactId, Collections.singletonList(messageId)); - - // The message ID should have been removed - assertEquals(Collections.emptyList(), db.getMessagesToAck(txn, - contactId, 1234)); - - db.commitTransaction(txn); - db.close(); - } - @Test public void testOutstandingMessageAcked() throws Exception { Database<Connection> db = open(false); @@ -375,27 +430,25 @@ public class H2DatabaseTest extends BriarTestCase { db.addGroup(txn, group); db.addVisibility(txn, contactId, groupId); db.setGroups(txn, contactId, Collections.singletonList(group), 1); - db.addMessage(txn, message, true); + db.addMessage(txn, message, VALID, true); db.addStatus(txn, contactId, messageId, false, false); // Retrieve the message from the database and mark it as sent - Iterator<MessageId> it = - db.getMessagesToSend(txn, contactId, ONE_MEGABYTE).iterator(); - assertTrue(it.hasNext()); - assertEquals(messageId, it.next()); - assertFalse(it.hasNext()); + Collection<MessageId> ids = db.getMessagesToSend(txn, contactId, + ONE_MEGABYTE); + assertEquals(Collections.singletonList(messageId), ids); db.updateExpiryTime(txn, contactId, messageId, Integer.MAX_VALUE); // The message should no longer be sendable - it = db.getMessagesToSend(txn, contactId, ONE_MEGABYTE).iterator(); - assertFalse(it.hasNext()); + ids = db.getMessagesToSend(txn, contactId, ONE_MEGABYTE); + assertTrue(ids.isEmpty()); // Pretend that the message was acked db.raiseSeenFlag(txn, contactId, messageId); // The message still should not be sendable - it = db.getMessagesToSend(txn, contactId, ONE_MEGABYTE).iterator(); - assertFalse(it.hasNext()); + ids = db.getMessagesToSend(txn, contactId, ONE_MEGABYTE); + assertTrue(ids.isEmpty()); db.commitTransaction(txn); db.close(); @@ -419,7 +472,7 @@ public class H2DatabaseTest extends BriarTestCase { // Storing a message should reduce the free space Connection txn = db.startTransaction(); db.addGroup(txn, group); - db.addMessage(txn, message, true); + db.addMessage(txn, message, VALID, true); db.commitTransaction(txn); assertTrue(db.getFreeSpace() < free); @@ -579,7 +632,7 @@ public class H2DatabaseTest extends BriarTestCase { assertEquals(contactId, db.addContact(txn, author, localAuthorId)); db.addGroup(txn, group); db.setGroups(txn, contactId, Collections.singletonList(group), 1); - db.addMessage(txn, message, true); + db.addMessage(txn, message, VALID, true); db.addStatus(txn, contactId, messageId, false, false); // The subscription is not visible @@ -904,7 +957,7 @@ public class H2DatabaseTest extends BriarTestCase { db.addVisibility(txn, contactId, groupId); // Add a message - it should be sendable to the contact - db.addMessage(txn, message, true); + db.addMessage(txn, message, VALID, true); db.addStatus(txn, contactId, messageId, false, false); Collection<MessageId> sendable = db.getMessagesToSend(txn, contactId, ONE_MEGABYTE); @@ -974,7 +1027,7 @@ public class H2DatabaseTest extends BriarTestCase { // Add a group and a message db.addGroup(txn, group); - db.addMessage(txn, message, true); + db.addMessage(txn, message, VALID, true); // Attach some metadata to the message Metadata metadata = new Metadata(); @@ -1042,7 +1095,7 @@ public class H2DatabaseTest extends BriarTestCase { db.addVisibility(txn, contactId, groupId); // Add a message to the group - db.addMessage(txn, message, true); + db.addMessage(txn, message, VALID, true); db.addStatus(txn, contactId, messageId, false, false); // The message should not be sent or seen