mirror of
https://code.briarproject.org/briar/briar.git
synced 2026-02-20 14:49:53 +01:00
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:
@@ -297,8 +297,11 @@ public interface DatabaseComponent {
|
|||||||
void setVisibility(GroupId g, Collection<ContactId> visible)
|
void setVisibility(GroupId g, Collection<ContactId> visible)
|
||||||
throws DbException;
|
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
|
* Unsubscribes from the given group. Any messages belonging to the group
|
||||||
|
|||||||
@@ -11,6 +11,9 @@ public interface MessagingConstants {
|
|||||||
*/
|
*/
|
||||||
int MAX_PACKET_LENGTH = MIN_CONNECTION_LENGTH / 2;
|
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. */
|
/** The maximum number of properties per transport. */
|
||||||
int MAX_PROPERTIES_PER_TRANSPORT = 100;
|
int MAX_PROPERTIES_PER_TRANSPORT = 100;
|
||||||
|
|
||||||
|
|||||||
@@ -132,11 +132,12 @@ interface Database<T> {
|
|||||||
throws DbException;
|
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>
|
* <p>
|
||||||
* Locking: subscription write.
|
* 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.
|
* Adds a new transport to the database.
|
||||||
|
|||||||
@@ -1715,14 +1715,16 @@ DatabaseCleaner.Callback {
|
|||||||
callListeners(new LocalSubscriptionsUpdatedEvent(affected));
|
callListeners(new LocalSubscriptionsUpdatedEvent(affected));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void subscribe(Group g) throws DbException {
|
public boolean subscribe(Group g) throws DbException {
|
||||||
subscriptionLock.writeLock().lock();
|
subscriptionLock.writeLock().lock();
|
||||||
try {
|
try {
|
||||||
T txn = db.startTransaction();
|
T txn = db.startTransaction();
|
||||||
try {
|
try {
|
||||||
|
boolean added = false;
|
||||||
if(!db.containsSubscription(txn, g.getId()))
|
if(!db.containsSubscription(txn, g.getId()))
|
||||||
db.addSubscription(txn, g);
|
added = db.addSubscription(txn, g);
|
||||||
db.commitTransaction(txn);
|
db.commitTransaction(txn);
|
||||||
|
return added;
|
||||||
} catch(DbException e) {
|
} catch(DbException e) {
|
||||||
db.abortTransaction(txn);
|
db.abortTransaction(txn);
|
||||||
throw e;
|
throw e;
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import static java.sql.Types.BINARY;
|
|||||||
import static java.util.logging.Level.INFO;
|
import static java.util.logging.Level.INFO;
|
||||||
import static java.util.logging.Level.WARNING;
|
import static java.util.logging.Level.WARNING;
|
||||||
import static net.sf.briar.api.Rating.UNRATED;
|
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.DatabaseConstants.RETENTION_MODULUS;
|
||||||
import static net.sf.briar.db.ExponentialBackoff.calculateExpiry;
|
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;
|
PreparedStatement ps = null;
|
||||||
|
ResultSet rs = null;
|
||||||
try {
|
try {
|
||||||
String sql = "INSERT INTO groups"
|
String sql = "SELECT COUNT (groupId) from GROUPS";
|
||||||
+ " (groupId, name, key) VALUES (?, ?, ?)";
|
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 = txn.prepareStatement(sql);
|
||||||
ps.setBytes(1, g.getId().getBytes());
|
ps.setBytes(1, g.getId().getBytes());
|
||||||
ps.setString(2, g.getName());
|
ps.setString(2, g.getName());
|
||||||
@@ -721,6 +732,7 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
int affected = ps.executeUpdate();
|
int affected = ps.executeUpdate();
|
||||||
if(affected != 1) throw new DbStateException();
|
if(affected != 1) throw new DbStateException();
|
||||||
ps.close();
|
ps.close();
|
||||||
|
return true;
|
||||||
} catch(SQLException e) {
|
} catch(SQLException e) {
|
||||||
tryToClose(ps);
|
tryToClose(ps);
|
||||||
throw new DbException(e);
|
throw new DbException(e);
|
||||||
|
|||||||
@@ -221,8 +221,7 @@ class PacketReaderImpl implements PacketReader {
|
|||||||
r.setMaxStringLength(MAX_PROPERTY_LENGTH);
|
r.setMaxStringLength(MAX_PROPERTY_LENGTH);
|
||||||
Map<String, String> m = r.readMap(String.class, String.class);
|
Map<String, String> m = r.readMap(String.class, String.class);
|
||||||
r.resetMaxStringLength();
|
r.resetMaxStringLength();
|
||||||
if(m.size() > MAX_PROPERTIES_PER_TRANSPORT)
|
if(m.size() > MAX_PROPERTIES_PER_TRANSPORT) throw new FormatException();
|
||||||
throw new FormatException();
|
|
||||||
// Read the version number
|
// Read the version number
|
||||||
long version = r.readInt64();
|
long version = r.readInt64();
|
||||||
if(version < 0) throw new FormatException();
|
if(version < 0) throw new FormatException();
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package net.sf.briar.messaging;
|
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_PACKET_LENGTH;
|
||||||
|
import static net.sf.briar.api.messaging.MessagingConstants.MAX_SUBSCRIPTIONS;
|
||||||
import static net.sf.briar.api.messaging.Types.SUBSCRIPTION_UPDATE;
|
import static net.sf.briar.api.messaging.Types.SUBSCRIPTION_UPDATE;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
@@ -31,7 +32,8 @@ class SubscriptionUpdateReader implements StructReader<SubscriptionUpdate> {
|
|||||||
// Read the subscriptions
|
// Read the subscriptions
|
||||||
List<Group> subs = new ArrayList<Group>();
|
List<Group> subs = new ArrayList<Group>();
|
||||||
r.readListStart();
|
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();
|
r.readListEnd();
|
||||||
// Read the version number
|
// Read the version number
|
||||||
long version = r.readInt64();
|
long version = r.readInt64();
|
||||||
|
|||||||
@@ -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_BODY_LENGTH;
|
||||||
import static net.sf.briar.api.messaging.MessagingConstants.MAX_GROUP_NAME_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_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_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_SUBJECT_LENGTH;
|
||||||
|
import static net.sf.briar.api.messaging.MessagingConstants.MAX_SUBSCRIPTIONS;
|
||||||
|
|
||||||
import java.io.ByteArrayOutputStream;
|
import java.io.ByteArrayOutputStream;
|
||||||
|
import java.security.KeyPair;
|
||||||
import java.security.PrivateKey;
|
import java.security.PrivateKey;
|
||||||
|
import java.security.Signature;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
|
import java.util.Random;
|
||||||
|
|
||||||
import net.sf.briar.BriarTestCase;
|
import net.sf.briar.BriarTestCase;
|
||||||
import net.sf.briar.TestUtils;
|
import net.sf.briar.TestUtils;
|
||||||
|
import net.sf.briar.api.TransportProperties;
|
||||||
import net.sf.briar.api.crypto.CryptoComponent;
|
import net.sf.briar.api.crypto.CryptoComponent;
|
||||||
import net.sf.briar.api.messaging.Ack;
|
import net.sf.briar.api.messaging.Ack;
|
||||||
import net.sf.briar.api.messaging.Author;
|
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.Offer;
|
||||||
import net.sf.briar.api.messaging.PacketWriter;
|
import net.sf.briar.api.messaging.PacketWriter;
|
||||||
import net.sf.briar.api.messaging.PacketWriterFactory;
|
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.api.messaging.UniqueId;
|
||||||
import net.sf.briar.clock.ClockModule;
|
import net.sf.briar.clock.ClockModule;
|
||||||
import net.sf.briar.crypto.CryptoModule;
|
import net.sf.briar.crypto.CryptoModule;
|
||||||
import net.sf.briar.messaging.MessagingModule;
|
|
||||||
import net.sf.briar.serial.SerialModule;
|
import net.sf.briar.serial.SerialModule;
|
||||||
|
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
@@ -56,6 +66,37 @@ public class ConstantsTest extends BriarTestCase {
|
|||||||
packetWriterFactory = i.getInstance(PacketWriterFactory.class);
|
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
|
@Test
|
||||||
public void testMessageIdsFitIntoLargeAck() throws Exception {
|
public void testMessageIdsFitIntoLargeAck() throws Exception {
|
||||||
testMessageIdsFitIntoAck(MAX_PACKET_LENGTH);
|
testMessageIdsFitIntoAck(MAX_PACKET_LENGTH);
|
||||||
@@ -66,21 +107,6 @@ public class ConstantsTest extends BriarTestCase {
|
|||||||
testMessageIdsFitIntoAck(1000);
|
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
|
@Test
|
||||||
public void testMessageFitsIntoPacket() throws Exception {
|
public void testMessageFitsIntoPacket() throws Exception {
|
||||||
// Create a maximum-length group
|
// Create a maximum-length group
|
||||||
@@ -92,8 +118,10 @@ public class ConstantsTest extends BriarTestCase {
|
|||||||
byte[] authorPublic = new byte[MAX_PUBLIC_KEY_LENGTH];
|
byte[] authorPublic = new byte[MAX_PUBLIC_KEY_LENGTH];
|
||||||
Author author = authorFactory.createAuthor(authorName, authorPublic);
|
Author author = authorFactory.createAuthor(authorName, authorPublic);
|
||||||
// Create a maximum-length message
|
// Create a maximum-length message
|
||||||
PrivateKey groupPrivate = crypto.generateSignatureKeyPair().getPrivate();
|
PrivateKey groupPrivate =
|
||||||
PrivateKey authorPrivate = crypto.generateSignatureKeyPair().getPrivate();
|
crypto.generateSignatureKeyPair().getPrivate();
|
||||||
|
PrivateKey authorPrivate =
|
||||||
|
crypto.generateSignatureKeyPair().getPrivate();
|
||||||
String subject = createRandomString(MAX_SUBJECT_LENGTH);
|
String subject = createRandomString(MAX_SUBJECT_LENGTH);
|
||||||
byte[] body = new byte[MAX_BODY_LENGTH];
|
byte[] body = new byte[MAX_BODY_LENGTH];
|
||||||
Message message = messageFactory.createMessage(null, group,
|
Message message = messageFactory.createMessage(null, group,
|
||||||
@@ -116,16 +144,66 @@ public class ConstantsTest extends BriarTestCase {
|
|||||||
testMessageIdsFitIntoOffer(1000);
|
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 {
|
private void testMessageIdsFitIntoOffer(int length) throws Exception {
|
||||||
// Create an offer with as many message IDs as possible
|
// Create an offer with as many message IDs as possible
|
||||||
ByteArrayOutputStream out = new ByteArrayOutputStream(length);
|
ByteArrayOutputStream out = new ByteArrayOutputStream(length);
|
||||||
PacketWriter writer = packetWriterFactory.createPacketWriter(out,
|
PacketWriter writer = packetWriterFactory.createPacketWriter(out, true);
|
||||||
true);
|
|
||||||
int maxMessages = writer.getMaxMessagesForOffer(length);
|
int maxMessages = writer.getMaxMessagesForOffer(length);
|
||||||
Collection<MessageId> offered = new ArrayList<MessageId>();
|
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()));
|
offered.add(new MessageId(TestUtils.getRandomId()));
|
||||||
}
|
|
||||||
writer.writeOffer(new Offer(offered));
|
writer.writeOffer(new Offer(offered));
|
||||||
// Check the size of the serialised offer
|
// Check the size of the serialised offer
|
||||||
assertTrue(out.size() <= length);
|
assertTrue(out.size() <= length);
|
||||||
|
|||||||
Reference in New Issue
Block a user