Limit the number of subscriptions per user.

This limit is necessary to limit the size of subscription update
packets; it can be lifted when incremental subscription updates are
implemented.
This commit is contained in:
akwizgran
2013-02-07 18:13:58 +00:00
parent f4675c3edd
commit 78d6100262
8 changed files with 134 additions and 34 deletions

View File

@@ -297,8 +297,11 @@ public interface DatabaseComponent {
void setVisibility(GroupId g, Collection<ContactId> visible)
throws DbException;
/** Subscribes to the given group. */
void subscribe(Group g) throws DbException;
/**
* Subscribes to the given group, or returns false if the user already has
* the maximum number of subscriptions.
*/
boolean subscribe(Group g) throws DbException;
/**
* Unsubscribes from the given group. Any messages belonging to the group

View File

@@ -11,6 +11,9 @@ public interface MessagingConstants {
*/
int MAX_PACKET_LENGTH = MIN_CONNECTION_LENGTH / 2;
/** The maximum number of groups a user may subscribe to. */
int MAX_SUBSCRIPTIONS = 3000;
/** The maximum number of properties per transport. */
int MAX_PROPERTIES_PER_TRANSPORT = 100;

View File

@@ -132,11 +132,12 @@ interface Database<T> {
throws DbException;
/**
* Subscribes to the given group.
* Subscribes to the given group, or returns false if the user already has
* the maximum number of subscriptions.
* <p>
* Locking: subscription write.
*/
void addSubscription(T txn, Group g) throws DbException;
boolean addSubscription(T txn, Group g) throws DbException;
/**
* Adds a new transport to the database.

View File

@@ -1715,14 +1715,16 @@ DatabaseCleaner.Callback {
callListeners(new LocalSubscriptionsUpdatedEvent(affected));
}
public void subscribe(Group g) throws DbException {
public boolean subscribe(Group g) throws DbException {
subscriptionLock.writeLock().lock();
try {
T txn = db.startTransaction();
try {
boolean added = false;
if(!db.containsSubscription(txn, g.getId()))
db.addSubscription(txn, g);
added = db.addSubscription(txn, g);
db.commitTransaction(txn);
return added;
} catch(DbException e) {
db.abortTransaction(txn);
throw e;

View File

@@ -4,6 +4,7 @@ import static java.sql.Types.BINARY;
import static java.util.logging.Level.INFO;
import static java.util.logging.Level.WARNING;
import static net.sf.briar.api.Rating.UNRATED;
import static net.sf.briar.api.messaging.MessagingConstants.MAX_SUBSCRIPTIONS;
import static net.sf.briar.db.DatabaseConstants.RETENTION_MODULUS;
import static net.sf.briar.db.ExponentialBackoff.calculateExpiry;
@@ -709,11 +710,21 @@ abstract class JdbcDatabase implements Database<Connection> {
}
}
public void addSubscription(Connection txn, Group g) throws DbException {
public boolean addSubscription(Connection txn, Group g) throws DbException {
PreparedStatement ps = null;
ResultSet rs = null;
try {
String sql = "INSERT INTO groups"
+ " (groupId, name, key) VALUES (?, ?, ?)";
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, key) VALUES (?, ?, ?)";
ps = txn.prepareStatement(sql);
ps.setBytes(1, g.getId().getBytes());
ps.setString(2, g.getName());
@@ -721,6 +732,7 @@ abstract class JdbcDatabase implements Database<Connection> {
int affected = ps.executeUpdate();
if(affected != 1) throw new DbStateException();
ps.close();
return true;
} catch(SQLException e) {
tryToClose(ps);
throw new DbException(e);

View File

@@ -221,8 +221,7 @@ class PacketReaderImpl implements PacketReader {
r.setMaxStringLength(MAX_PROPERTY_LENGTH);
Map<String, String> m = r.readMap(String.class, String.class);
r.resetMaxStringLength();
if(m.size() > MAX_PROPERTIES_PER_TRANSPORT)
throw new FormatException();
if(m.size() > MAX_PROPERTIES_PER_TRANSPORT) throw new FormatException();
// Read the version number
long version = r.readInt64();
if(version < 0) throw new FormatException();

View File

@@ -1,6 +1,7 @@
package net.sf.briar.messaging;
import static net.sf.briar.api.messaging.MessagingConstants.MAX_PACKET_LENGTH;
import static net.sf.briar.api.messaging.MessagingConstants.MAX_SUBSCRIPTIONS;
import static net.sf.briar.api.messaging.Types.SUBSCRIPTION_UPDATE;
import java.io.IOException;
@@ -31,7 +32,8 @@ class SubscriptionUpdateReader implements StructReader<SubscriptionUpdate> {
// Read the subscriptions
List<Group> subs = new ArrayList<Group>();
r.readListStart();
while(!r.hasListEnd()) subs.add(groupReader.readStruct(r));
for(int i = 0; i < MAX_SUBSCRIPTIONS && !r.hasListEnd(); i++)
subs.add(groupReader.readStruct(r));
r.readListEnd();
// Read the version number
long version = r.readInt64();

View File

@@ -4,16 +4,24 @@ import static net.sf.briar.api.messaging.MessagingConstants.MAX_AUTHOR_NAME_LENG
import static net.sf.briar.api.messaging.MessagingConstants.MAX_BODY_LENGTH;
import static net.sf.briar.api.messaging.MessagingConstants.MAX_GROUP_NAME_LENGTH;
import static net.sf.briar.api.messaging.MessagingConstants.MAX_PACKET_LENGTH;
import static net.sf.briar.api.messaging.MessagingConstants.MAX_PROPERTIES_PER_TRANSPORT;
import static net.sf.briar.api.messaging.MessagingConstants.MAX_PROPERTY_LENGTH;
import static net.sf.briar.api.messaging.MessagingConstants.MAX_PUBLIC_KEY_LENGTH;
import static net.sf.briar.api.messaging.MessagingConstants.MAX_SIGNATURE_LENGTH;
import static net.sf.briar.api.messaging.MessagingConstants.MAX_SUBJECT_LENGTH;
import static net.sf.briar.api.messaging.MessagingConstants.MAX_SUBSCRIPTIONS;
import java.io.ByteArrayOutputStream;
import java.security.KeyPair;
import java.security.PrivateKey;
import java.security.Signature;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Random;
import net.sf.briar.BriarTestCase;
import net.sf.briar.TestUtils;
import net.sf.briar.api.TransportProperties;
import net.sf.briar.api.crypto.CryptoComponent;
import net.sf.briar.api.messaging.Ack;
import net.sf.briar.api.messaging.Author;
@@ -26,10 +34,12 @@ import net.sf.briar.api.messaging.MessageId;
import net.sf.briar.api.messaging.Offer;
import net.sf.briar.api.messaging.PacketWriter;
import net.sf.briar.api.messaging.PacketWriterFactory;
import net.sf.briar.api.messaging.SubscriptionUpdate;
import net.sf.briar.api.messaging.TransportId;
import net.sf.briar.api.messaging.TransportUpdate;
import net.sf.briar.api.messaging.UniqueId;
import net.sf.briar.clock.ClockModule;
import net.sf.briar.crypto.CryptoModule;
import net.sf.briar.messaging.MessagingModule;
import net.sf.briar.serial.SerialModule;
import org.junit.Test;
@@ -56,6 +66,37 @@ public class ConstantsTest extends BriarTestCase {
packetWriterFactory = i.getInstance(PacketWriterFactory.class);
}
@Test
public void testAgreementPublicKeys() throws Exception {
// Generate 10 agreement key pairs
for(int i = 0; i < 10; i++) {
KeyPair keyPair = crypto.generateSignatureKeyPair();
// Check the length of the public key
byte[] publicKey = keyPair.getPublic().getEncoded();
assertTrue(publicKey.length <= MAX_PUBLIC_KEY_LENGTH);
}
}
@Test
public void testSignaturePublicKeys() throws Exception {
Random random = new Random();
Signature sig = crypto.getSignature();
// Generate 10 signature key pairs
for(int i = 0; i < 10; i++) {
KeyPair keyPair = crypto.generateSignatureKeyPair();
// Check the length of the public key
byte[] publicKey = keyPair.getPublic().getEncoded();
assertTrue(publicKey.length <= MAX_PUBLIC_KEY_LENGTH);
// Sign some random data and check the length of the signature
byte[] toBeSigned = new byte[100];
random.nextBytes(toBeSigned);
sig.initSign(keyPair.getPrivate());
sig.update(toBeSigned);
byte[] signature = sig.sign();
assertTrue(signature.length <= MAX_SIGNATURE_LENGTH);
}
}
@Test
public void testMessageIdsFitIntoLargeAck() throws Exception {
testMessageIdsFitIntoAck(MAX_PACKET_LENGTH);
@@ -66,21 +107,6 @@ public class ConstantsTest extends BriarTestCase {
testMessageIdsFitIntoAck(1000);
}
private void testMessageIdsFitIntoAck(int length) throws Exception {
// Create an ack with as many message IDs as possible
ByteArrayOutputStream out = new ByteArrayOutputStream(length);
PacketWriter writer = packetWriterFactory.createPacketWriter(out,
true);
int maxMessages = writer.getMaxMessagesForAck(length);
Collection<MessageId> acked = new ArrayList<MessageId>();
for(int i = 0; i < maxMessages; i++) {
acked.add(new MessageId(TestUtils.getRandomId()));
}
writer.writeAck(new Ack(acked));
// Check the size of the serialised ack
assertTrue(out.size() <= length);
}
@Test
public void testMessageFitsIntoPacket() throws Exception {
// Create a maximum-length group
@@ -92,8 +118,10 @@ public class ConstantsTest extends BriarTestCase {
byte[] authorPublic = new byte[MAX_PUBLIC_KEY_LENGTH];
Author author = authorFactory.createAuthor(authorName, authorPublic);
// Create a maximum-length message
PrivateKey groupPrivate = crypto.generateSignatureKeyPair().getPrivate();
PrivateKey authorPrivate = crypto.generateSignatureKeyPair().getPrivate();
PrivateKey groupPrivate =
crypto.generateSignatureKeyPair().getPrivate();
PrivateKey authorPrivate =
crypto.generateSignatureKeyPair().getPrivate();
String subject = createRandomString(MAX_SUBJECT_LENGTH);
byte[] body = new byte[MAX_BODY_LENGTH];
Message message = messageFactory.createMessage(null, group,
@@ -116,16 +144,66 @@ public class ConstantsTest extends BriarTestCase {
testMessageIdsFitIntoOffer(1000);
}
@Test
public void testPropertiesFitIntoTransportUpdate() throws Exception {
// Create the maximum number of properties with the maximum length
TransportProperties p = new TransportProperties();
for(int i = 0; i < MAX_PROPERTIES_PER_TRANSPORT; i++) {
String key = createRandomString(MAX_PROPERTY_LENGTH);
String value = createRandomString(MAX_PROPERTY_LENGTH);
p.put(key, value);
}
// Create a maximum-length transport update
TransportId id = new TransportId(TestUtils.getRandomId());
TransportUpdate u = new TransportUpdate(id, p, Long.MAX_VALUE);
// Serialise the update
ByteArrayOutputStream out = new ByteArrayOutputStream();
PacketWriter writer = packetWriterFactory.createPacketWriter(out, true);
writer.writeTransportUpdate(u);
// Check the size of the serialised transport update
assertTrue(out.size() <= MAX_PACKET_LENGTH);
}
@Test
public void testGroupsFitIntoSubscriptionUpdate() throws Exception {
// Create the maximum number of maximum-length groups
Collection<Group> subs = new ArrayList<Group>();
for(int i = 0; i < MAX_SUBSCRIPTIONS; i++) {
String groupName = createRandomString(MAX_GROUP_NAME_LENGTH);
byte[] groupPublic = new byte[MAX_PUBLIC_KEY_LENGTH];
subs.add(groupFactory.createGroup(groupName, groupPublic));
}
// Create a maximum-length subscription update
SubscriptionUpdate u = new SubscriptionUpdate(subs, Long.MAX_VALUE);
// Serialise the update
ByteArrayOutputStream out = new ByteArrayOutputStream();
PacketWriter writer = packetWriterFactory.createPacketWriter(out, true);
writer.writeSubscriptionUpdate(u);
// Check the size of the serialised subscription update
assertTrue(out.size() <= MAX_PACKET_LENGTH);
}
private void testMessageIdsFitIntoAck(int length) throws Exception {
// Create an ack with as many message IDs as possible
ByteArrayOutputStream out = new ByteArrayOutputStream(length);
PacketWriter writer = packetWriterFactory.createPacketWriter(out, true);
int maxMessages = writer.getMaxMessagesForAck(length);
Collection<MessageId> acked = new ArrayList<MessageId>();
for(int i = 0; i < maxMessages; i++)
acked.add(new MessageId(TestUtils.getRandomId()));
writer.writeAck(new Ack(acked));
// Check the size of the serialised ack
assertTrue(out.size() <= length);
}
private void testMessageIdsFitIntoOffer(int length) throws Exception {
// Create an offer with as many message IDs as possible
ByteArrayOutputStream out = new ByteArrayOutputStream(length);
PacketWriter writer = packetWriterFactory.createPacketWriter(out,
true);
PacketWriter writer = packetWriterFactory.createPacketWriter(out, true);
int maxMessages = writer.getMaxMessagesForOffer(length);
Collection<MessageId> offered = new ArrayList<MessageId>();
for(int i = 0; i < maxMessages; i++) {
for(int i = 0; i < maxMessages; i++)
offered.add(new MessageId(TestUtils.getRandomId()));
}
writer.writeOffer(new Offer(offered));
// Check the size of the serialised offer
assertTrue(out.size() <= length);