diff --git a/briar-core/src/net/sf/briar/db/Database.java b/briar-core/src/net/sf/briar/db/Database.java index fa6226172873d9530ce8ecc73482390410cd0b27..6e5a890629c5f98ea8005fd60a70580d0d85f521 100644 --- a/briar-core/src/net/sf/briar/db/Database.java +++ b/briar-core/src/net/sf/briar/db/Database.java @@ -220,6 +220,13 @@ interface Database<T> { boolean containsVisibleMessage(T txn, ContactId c, MessageId m) throws DbException; + /** + * Returns the number of messages offered by the given contact. + * <p> + * Locking: message read. + */ + int countOfferedMessages(T txn, ContactId c) throws DbException; + /** * Returns the status of all groups to which the user subscribes or can * subscribe, excluding inbox groups. @@ -634,8 +641,8 @@ interface Database<T> { void removeMessage(T txn, MessageId m) throws DbException; /** - * Removes an offered message ID that was offered by the given contact, or - * returns false if there is no such message ID. + * Removes an offered message that was offered by the given contact, or + * returns false if there is no such message. * <p> * Locking: message write. */ @@ -643,7 +650,7 @@ interface Database<T> { throws DbException; /** - * Removes the given offered message IDs that were offered by the given + * Removes the given offered messages that were offered by the given * contact. * <p> * Locking: message write. diff --git a/briar-core/src/net/sf/briar/db/DatabaseComponentImpl.java b/briar-core/src/net/sf/briar/db/DatabaseComponentImpl.java index 66ad996d7ae13587dc9a92c372d363122e89583d..79f39e86c900f23765a4e5300f8ecbcdbb4d7f14 100644 --- a/briar-core/src/net/sf/briar/db/DatabaseComponentImpl.java +++ b/briar-core/src/net/sf/briar/db/DatabaseComponentImpl.java @@ -6,6 +6,7 @@ import static net.sf.briar.db.DatabaseConstants.BYTES_PER_SWEEP; import static net.sf.briar.db.DatabaseConstants.CRITICAL_FREE_SPACE; import static net.sf.briar.db.DatabaseConstants.MAX_BYTES_BETWEEN_SPACE_CHECKS; import static net.sf.briar.db.DatabaseConstants.MAX_MS_BETWEEN_SPACE_CHECKS; +import static net.sf.briar.db.DatabaseConstants.MAX_OFFERED_MESSAGES; import static net.sf.briar.db.DatabaseConstants.MIN_FREE_SPACE; import java.io.IOException; @@ -1363,14 +1364,16 @@ DatabaseCleaner.Callback { try { if(!db.containsContact(txn, c)) throw new NoSuchContactException(); + int count = db.countOfferedMessages(txn, c); for(MessageId m : o.getMessageIds()) { if(db.containsVisibleMessage(txn, c, m)) { db.raiseSeenFlag(txn, c, m); db.raiseAckFlag(txn, c, m); ack = true; - } else { + } else if(count < MAX_OFFERED_MESSAGES) { db.addOfferedMessage(txn, c, m); request = true; + count++; } } db.commitTransaction(txn); diff --git a/briar-core/src/net/sf/briar/db/DatabaseConstants.java b/briar-core/src/net/sf/briar/db/DatabaseConstants.java index 4a8a5dfeb5b037b2dc74549a63c8f0b74e65d4ed..53a55111091004735daa7dbb6aad7382f9039ff1 100644 --- a/briar-core/src/net/sf/briar/db/DatabaseConstants.java +++ b/briar-core/src/net/sf/briar/db/DatabaseConstants.java @@ -2,6 +2,13 @@ package net.sf.briar.db; interface DatabaseConstants { + /** + * The maximum number of offered messages from each contact that will be + * stored. If offers arrive more quickly than requests can be sent and this + * limit is reached, additional offers will not be stored. + */ + int MAX_OFFERED_MESSAGES = 1000; + // FIXME: These should be configurable /** diff --git a/briar-core/src/net/sf/briar/db/JdbcDatabase.java b/briar-core/src/net/sf/briar/db/JdbcDatabase.java index a962f87ffbcc1981cd91d512a0b86befe28d0898..a896416c9895ccdd9b5a84d11f5bf046d30e5438 100644 --- a/briar-core/src/net/sf/briar/db/JdbcDatabase.java +++ b/briar-core/src/net/sf/briar/db/JdbcDatabase.java @@ -668,10 +668,20 @@ abstract class JdbcDatabase implements Database<Connection> { } public boolean addGroup(Connection txn, Group g) throws DbException { - if(maximumSubscriptionsReached(txn)) return false; PreparedStatement ps = null; + ResultSet rs = null; try { - String sql = "INSERT INTO groups" + String sql = "SELECT COUNT (groupId) FROM groups"; + ps = txn.prepareStatement(sql); + rs = ps.executeQuery(); + if(!rs.next()) throw new DbStateException(); + int count = rs.getInt(1); + if(rs.next()) throw new DbStateException(); + rs.close(); + ps.close(); + if(count > MAX_SUBSCRIPTIONS) throw new DbStateException(); + if(count == MAX_SUBSCRIPTIONS) return false; + sql = "INSERT INTO groups" + " (groupId, name, salt, visibleToAll)" + " VALUES (?, ?, ?, FALSE)"; ps = txn.prepareStatement(sql); @@ -682,27 +692,6 @@ abstract class JdbcDatabase implements Database<Connection> { if(affected != 1) throw new DbStateException(); ps.close(); return true; - } catch(SQLException e) { - tryToClose(ps); - throw new DbException(e); - } - } - - private boolean maximumSubscriptionsReached(Connection txn) - throws DbException { - PreparedStatement ps = null; - ResultSet rs = null; - try { - String sql = "SELECT COUNT (NULL) FROM groups"; - ps = txn.prepareStatement(sql); - rs = ps.executeQuery(); - if(!rs.next()) throw new DbStateException(); - int count = rs.getInt(1); - if(rs.next()) throw new DbStateException(); - rs.close(); - ps.close(); - if(count > MAX_SUBSCRIPTIONS) throw new DbStateException(); - return count == MAX_SUBSCRIPTIONS; } catch(SQLException e) { tryToClose(rs); tryToClose(ps); @@ -772,7 +761,6 @@ abstract class JdbcDatabase implements Database<Connection> { } } - // FIXME: Limit the number of offers per contact public void addOfferedMessage(Connection txn, ContactId c, MessageId m) throws DbException { PreparedStatement ps = null; @@ -1130,6 +1118,29 @@ abstract class JdbcDatabase implements Database<Connection> { } } + public int countOfferedMessages(Connection txn, ContactId c) + throws DbException { + PreparedStatement ps = null; + ResultSet rs = null; + try { + String sql = "SELECT COUNT (messageId) FROM offers " + + " WHERE contactId = ?"; + ps = txn.prepareStatement(sql); + ps.setInt(1, c.getInt()); + rs = ps.executeQuery(); + if(!rs.next()) throw new DbException(); + int count = rs.getInt(1); + if(rs.next()) throw new DbException(); + rs.close(); + ps.close(); + return count; + } catch(SQLException e) { + tryToClose(rs); + tryToClose(ps); + throw new DbException(e); + } + } + public Collection<GroupStatus> getAvailableGroups(Connection txn) throws DbException { PreparedStatement ps = null; diff --git a/briar-tests/src/net/sf/briar/db/DatabaseComponentTest.java b/briar-tests/src/net/sf/briar/db/DatabaseComponentTest.java index ff8a65a2bc0f7f316f8b326d304112e1ec75dfe8..d6311265f2bfcb04ce5cf06c2457e28da4bdb3cf 100644 --- a/briar-tests/src/net/sf/briar/db/DatabaseComponentTest.java +++ b/briar-tests/src/net/sf/briar/db/DatabaseComponentTest.java @@ -2,9 +2,9 @@ package net.sf.briar.db; import static net.sf.briar.api.AuthorConstants.MAX_PUBLIC_KEY_LENGTH; import static net.sf.briar.api.messaging.MessagingConstants.GROUP_SALT_LENGTH; +import static net.sf.briar.db.DatabaseConstants.MAX_OFFERED_MESSAGES; import java.util.Arrays; -import java.util.BitSet; import java.util.Collection; import java.util.Collections; @@ -1087,9 +1087,7 @@ public abstract class DatabaseComponentTest extends BriarTestCase { public void testReceiveOffer() throws Exception { final MessageId messageId1 = new MessageId(TestUtils.getRandomId()); final MessageId messageId2 = new MessageId(TestUtils.getRandomId()); - final BitSet expectedRequest = new BitSet(3); - expectedRequest.set(0); - expectedRequest.set(2); + final MessageId messageId3 = new MessageId(TestUtils.getRandomId()); Mockery context = new Mockery(); @SuppressWarnings("unchecked") final Database<Object> database = context.mock(Database.class); @@ -1101,16 +1099,25 @@ public abstract class DatabaseComponentTest extends BriarTestCase { will(returnValue(txn)); oneOf(database).containsContact(txn, contactId); will(returnValue(true)); + // There's room for two more offered messages + oneOf(database).countOfferedMessages(txn, contactId); + will(returnValue(MAX_OFFERED_MESSAGES - 2)); + // The first message isn't visible - request it oneOf(database).containsVisibleMessage(txn, contactId, messageId); - will(returnValue(false)); // Not visible - request message # 0 + will(returnValue(false)); oneOf(database).addOfferedMessage(txn, contactId, messageId); + // The second message is visible - ack it oneOf(database).containsVisibleMessage(txn, contactId, messageId1); - will(returnValue(true)); // Visible - ack message # 1 + will(returnValue(true)); oneOf(database).raiseSeenFlag(txn, contactId, messageId1); oneOf(database).raiseAckFlag(txn, contactId, messageId1); + // The third message isn't visible - request it oneOf(database).containsVisibleMessage(txn, contactId, messageId2); - will(returnValue(false)); // Not visible - request message # 2 + will(returnValue(false)); oneOf(database).addOfferedMessage(txn, contactId, messageId2); + // The fourth message isn't visible, but there's no room to store it + oneOf(database).containsVisibleMessage(txn, contactId, messageId3); + will(returnValue(false)); oneOf(database).commitTransaction(txn); oneOf(listener).eventOccurred(with(any(MessageToAckEvent.class))); oneOf(listener).eventOccurred(with(any( @@ -1120,7 +1127,8 @@ public abstract class DatabaseComponentTest extends BriarTestCase { shutdown); db.addListener(listener); - Offer o = new Offer(Arrays.asList(messageId, messageId1, messageId2)); + Offer o = new Offer(Arrays.asList(messageId, messageId1, messageId2, + messageId3)); db.receiveOffer(contactId, o); context.assertIsSatisfied(); } diff --git a/briar-tests/src/net/sf/briar/db/H2DatabaseTest.java b/briar-tests/src/net/sf/briar/db/H2DatabaseTest.java index b7b6b5c29c722049491112ab94dc9558d99892e4..29d3381e0796b94126f0a627ac37aa1959a80b9f 100644 --- a/briar-tests/src/net/sf/briar/db/H2DatabaseTest.java +++ b/briar-tests/src/net/sf/briar/db/H2DatabaseTest.java @@ -1509,6 +1509,35 @@ public class H2DatabaseTest extends BriarTestCase { db.close(); } + @Test + public void testOfferedMessages() throws Exception { + Database<Connection> db = open(false); + Connection txn = db.startTransaction(); + + // Add a contact - initially there should be no offered messages + db.addLocalAuthor(txn, localAuthor); + assertEquals(contactId, db.addContact(txn, author, localAuthorId)); + assertEquals(0, db.countOfferedMessages(txn, contactId)); + + // Add some offered messages and count them + List<MessageId> ids = new ArrayList<MessageId>(); + for(int i = 0; i < 10; i++) { + MessageId m = new MessageId(TestUtils.getRandomId()); + db.addOfferedMessage(txn, contactId, m); + ids.add(m); + } + assertEquals(10, db.countOfferedMessages(txn, contactId)); + + // Remove some of the offered messages and count again + List<MessageId> half = ids.subList(0, 5); + db.removeOfferedMessages(txn, contactId, half); + assertTrue(db.removeOfferedMessage(txn, contactId, ids.get(5))); + assertEquals(4, db.countOfferedMessages(txn, contactId)); + + db.commitTransaction(txn); + db.close(); + } + @Test public void testExceptionHandling() throws Exception { Database<Connection> db = open(false);