Limit the number of offered messages per contact.

Also fixed a bug in addGroup(): SELECT COUNT (NULL) doesn't work.
This commit is contained in:
akwizgran
2014-01-04 21:57:13 +00:00
parent fc827c191e
commit 3779f6ea8b
6 changed files with 101 additions and 36 deletions

View File

@@ -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.

View File

@@ -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);

View File

@@ -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
/**

View File

@@ -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;

View File

@@ -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();
}

View File

@@ -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);