diff --git a/briar-api/src/net/sf/briar/api/db/DatabaseComponent.java b/briar-api/src/net/sf/briar/api/db/DatabaseComponent.java index ec074b8d37c874f20ac6c161fd62e4ffda063a91..993af1b6e9dd13c815d95f2ef2e6006543631165 100644 --- a/briar-api/src/net/sf/briar/api/db/DatabaseComponent.java +++ b/briar-api/src/net/sf/briar/api/db/DatabaseComponent.java @@ -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 diff --git a/briar-api/src/net/sf/briar/api/messaging/MessagingConstants.java b/briar-api/src/net/sf/briar/api/messaging/MessagingConstants.java index e95c7462f1c0e58be3e2e7d6bf299da6bf3b0f1f..5790af212c2eb0f14c9eda975d509e64681737f8 100644 --- a/briar-api/src/net/sf/briar/api/messaging/MessagingConstants.java +++ b/briar-api/src/net/sf/briar/api/messaging/MessagingConstants.java @@ -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; diff --git a/briar-core/src/net/sf/briar/db/Database.java b/briar-core/src/net/sf/briar/db/Database.java index 9caffadad56cd42875d0f3fbac036c22fd184a69..dfc9d00d7809f6d4ff85c63b1c6a04ced20c7c89 100644 --- a/briar-core/src/net/sf/briar/db/Database.java +++ b/briar-core/src/net/sf/briar/db/Database.java @@ -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. diff --git a/briar-core/src/net/sf/briar/db/DatabaseComponentImpl.java b/briar-core/src/net/sf/briar/db/DatabaseComponentImpl.java index a1d766df65aa79431a0bd13c14593632ce8af710..e18601d06d3fc1f223c17a61e0ee7feb5d290edc 100644 --- a/briar-core/src/net/sf/briar/db/DatabaseComponentImpl.java +++ b/briar-core/src/net/sf/briar/db/DatabaseComponentImpl.java @@ -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; diff --git a/briar-core/src/net/sf/briar/db/JdbcDatabase.java b/briar-core/src/net/sf/briar/db/JdbcDatabase.java index dfa50aef8f2c317abb89d3fd2adf866a60803dcb..78d5adfe279c869986854a4edacbafb8e1795d9c 100644 --- a/briar-core/src/net/sf/briar/db/JdbcDatabase.java +++ b/briar-core/src/net/sf/briar/db/JdbcDatabase.java @@ -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); diff --git a/briar-core/src/net/sf/briar/messaging/PacketReaderImpl.java b/briar-core/src/net/sf/briar/messaging/PacketReaderImpl.java index 85244e31174f9c51963b3f31f82bd32f99a2fbb5..9795dfe78132d562f7f440e782f851c6ecfd209c 100644 --- a/briar-core/src/net/sf/briar/messaging/PacketReaderImpl.java +++ b/briar-core/src/net/sf/briar/messaging/PacketReaderImpl.java @@ -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(); diff --git a/briar-core/src/net/sf/briar/messaging/SubscriptionUpdateReader.java b/briar-core/src/net/sf/briar/messaging/SubscriptionUpdateReader.java index d73b86b44ed1011b989b5e3e39de0695d15de55f..6312e4ea31896d8682d5aa0e0533dbc3f4e73337 100644 --- a/briar-core/src/net/sf/briar/messaging/SubscriptionUpdateReader.java +++ b/briar-core/src/net/sf/briar/messaging/SubscriptionUpdateReader.java @@ -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(); diff --git a/briar-tests/src/net/sf/briar/messaging/ConstantsTest.java b/briar-tests/src/net/sf/briar/messaging/ConstantsTest.java index 91209dadcbcfd7a1d706b712fe7bd41b46fd844e..ca6a804605759e34a8fbbfcf4d819f1ea1efeb3b 100644 --- a/briar-tests/src/net/sf/briar/messaging/ConstantsTest.java +++ b/briar-tests/src/net/sf/briar/messaging/ConstantsTest.java @@ -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);