Removed restricted groups (may be restored after beta testing).

This commit is contained in:
akwizgran
2013-09-27 15:11:04 +01:00
parent 1e5e067df7
commit b94954544d
31 changed files with 219 additions and 630 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 948 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 558 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 752 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 918 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

View File

@@ -140,7 +140,6 @@ OnItemClickListener {
long now = System.currentTimeMillis(); long now = System.currentTimeMillis();
for(GroupStatus s : db.getAvailableGroups()) { for(GroupStatus s : db.getAvailableGroups()) {
Group g = s.getGroup(); Group g = s.getGroup();
if(g.isRestricted()) continue;
if(s.isSubscribed()) { if(s.isSubscribed()) {
try { try {
Collection<GroupMessageHeader> headers = Collection<GroupMessageHeader> headers =
@@ -242,11 +241,8 @@ OnItemClickListener {
public void eventOccurred(DatabaseEvent e) { public void eventOccurred(DatabaseEvent e) {
if(e instanceof GroupMessageAddedEvent) { if(e instanceof GroupMessageAddedEvent) {
Group g = ((GroupMessageAddedEvent) e).getGroup(); if(LOG.isLoggable(INFO)) LOG.info("Message added, reloading");
if(!g.isRestricted()) { loadHeaders(((GroupMessageAddedEvent) e).getGroup());
if(LOG.isLoggable(INFO)) LOG.info("Message added, reloading");
loadHeaders(g);
}
} else if(e instanceof MessageExpiredEvent) { } else if(e instanceof MessageExpiredEvent) {
if(LOG.isLoggable(INFO)) LOG.info("Message expired, reloading"); if(LOG.isLoggable(INFO)) LOG.info("Message expired, reloading");
loadHeaders(); loadHeaders();
@@ -255,18 +251,12 @@ OnItemClickListener {
LOG.info("Remote subscriptions changed, reloading"); LOG.info("Remote subscriptions changed, reloading");
loadAvailable(); loadAvailable();
} else if(e instanceof SubscriptionAddedEvent) { } else if(e instanceof SubscriptionAddedEvent) {
Group g = ((SubscriptionAddedEvent) e).getGroup(); if(LOG.isLoggable(INFO)) LOG.info("Group added, reloading");
if(!g.isRestricted()) { loadHeaders();
if(LOG.isLoggable(INFO)) LOG.info("Group added, reloading");
loadHeaders();
}
} else if(e instanceof SubscriptionRemovedEvent) { } else if(e instanceof SubscriptionRemovedEvent) {
Group g = ((SubscriptionRemovedEvent) e).getGroup(); // Reload the group, expecting NoSuchSubscriptionException
if(!g.isRestricted()) { if(LOG.isLoggable(INFO)) LOG.info("Group removed, reloading");
// Reload the group, expecting NoSuchSubscriptionException loadHeaders(((SubscriptionRemovedEvent) e).getGroup());
if(LOG.isLoggable(INFO)) LOG.info("Group removed, reloading");
loadHeaders(g);
}
} }
} }
@@ -317,10 +307,8 @@ OnItemClickListener {
lifecycleManager.waitForDatabase(); lifecycleManager.waitForDatabase();
int available = 0; int available = 0;
long now = System.currentTimeMillis(); long now = System.currentTimeMillis();
for(GroupStatus s : db.getAvailableGroups()) { for(GroupStatus s : db.getAvailableGroups())
if(!s.getGroup().isRestricted() && !s.isSubscribed()) if(!s.isSubscribed()) available++;
available++;
}
long duration = System.currentTimeMillis() - now; long duration = System.currentTimeMillis() - now;
if(LOG.isLoggable(INFO)) if(LOG.isLoggable(INFO))
LOG.info("Loading available took " + duration + " ms"); LOG.info("Loading available took " + duration + " ms");

View File

@@ -5,11 +5,8 @@ import static java.util.logging.Level.WARNING;
import static net.sf.briar.android.groups.ManageGroupsItem.NONE; import static net.sf.briar.android.groups.ManageGroupsItem.NONE;
import static net.sf.briar.android.util.CommonLayoutParams.MATCH_MATCH; import static net.sf.briar.android.util.CommonLayoutParams.MATCH_MATCH;
import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.Collections;
import java.util.Comparator; import java.util.Comparator;
import java.util.List;
import java.util.concurrent.Executor; import java.util.concurrent.Executor;
import java.util.logging.Logger; import java.util.logging.Logger;
@@ -78,13 +75,10 @@ implements DatabaseListener, OnItemClickListener {
try { try {
lifecycleManager.waitForDatabase(); lifecycleManager.waitForDatabase();
long now = System.currentTimeMillis(); long now = System.currentTimeMillis();
List<GroupStatus> available = new ArrayList<GroupStatus>(); Collection<GroupStatus> available = db.getAvailableGroups();
for(GroupStatus s : db.getAvailableGroups())
if(!s.getGroup().isRestricted()) available.add(s);
long duration = System.currentTimeMillis() - now; long duration = System.currentTimeMillis() - now;
if(LOG.isLoggable(INFO)) if(LOG.isLoggable(INFO))
LOG.info("Load took " + duration + " ms"); LOG.info("Load took " + duration + " ms");
available = Collections.unmodifiableList(available);
displayAvailableGroups(available); displayAvailableGroups(available);
} catch(DbException e) { } catch(DbException e) {
if(LOG.isLoggable(WARNING)) if(LOG.isLoggable(WARNING))
@@ -124,17 +118,11 @@ implements DatabaseListener, OnItemClickListener {
LOG.info("Remote subscriptions changed, reloading"); LOG.info("Remote subscriptions changed, reloading");
loadAvailableGroups(); loadAvailableGroups();
} else if(e instanceof SubscriptionAddedEvent) { } else if(e instanceof SubscriptionAddedEvent) {
Group g = ((SubscriptionAddedEvent) e).getGroup(); if(LOG.isLoggable(INFO)) LOG.info("Group added, reloading");
if(g.isRestricted()) { loadAvailableGroups();
if(LOG.isLoggable(INFO)) LOG.info("Group added, reloading");
loadAvailableGroups();
}
} else if(e instanceof SubscriptionRemovedEvent) { } else if(e instanceof SubscriptionRemovedEvent) {
Group g = ((SubscriptionRemovedEvent) e).getGroup(); if(LOG.isLoggable(INFO)) LOG.info("Group removed, reloading");
if(g.isRestricted()) { loadAvailableGroups();
if(LOG.isLoggable(INFO)) LOG.info("Group removed, reloading");
loadAvailableGroups();
}
} }
} }

View File

@@ -11,10 +11,7 @@ import static net.sf.briar.android.util.CommonLayoutParams.MATCH_WRAP;
import java.io.IOException; import java.io.IOException;
import java.security.GeneralSecurityException; import java.security.GeneralSecurityException;
import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.Executor; import java.util.concurrent.Executor;
import java.util.logging.Logger; import java.util.logging.Logger;
@@ -213,14 +210,12 @@ implements OnItemSelectedListener, OnClickListener {
public void run() { public void run() {
try { try {
lifecycleManager.waitForDatabase(); lifecycleManager.waitForDatabase();
List<Group> groups = new ArrayList<Group>();
long now = System.currentTimeMillis(); long now = System.currentTimeMillis();
for(Group g : db.getSubscriptions()) Collection<Group> groups = db.getSubscriptions();
if(!g.isRestricted()) groups.add(g);
long duration = System.currentTimeMillis() - now; long duration = System.currentTimeMillis() - now;
if(LOG.isLoggable(INFO)) if(LOG.isLoggable(INFO))
LOG.info("Loading groups took " + duration + " ms"); LOG.info("Loading groups took " + duration + " ms");
displayGroups(Collections.unmodifiableList(groups)); displayGroups(groups);
} catch(DbException e) { } catch(DbException e) {
if(LOG.isLoggable(WARNING)) if(LOG.isLoggable(WARNING))
LOG.log(WARNING, e.toString(), e); LOG.log(WARNING, e.toString(), e);

View File

@@ -17,7 +17,6 @@ import net.sf.briar.api.messaging.Ack;
import net.sf.briar.api.messaging.Group; import net.sf.briar.api.messaging.Group;
import net.sf.briar.api.messaging.GroupId; import net.sf.briar.api.messaging.GroupId;
import net.sf.briar.api.messaging.GroupStatus; import net.sf.briar.api.messaging.GroupStatus;
import net.sf.briar.api.messaging.LocalGroup;
import net.sf.briar.api.messaging.Message; import net.sf.briar.api.messaging.Message;
import net.sf.briar.api.messaging.MessageId; import net.sf.briar.api.messaging.MessageId;
import net.sf.briar.api.messaging.Offer; import net.sf.briar.api.messaging.Offer;
@@ -62,12 +61,6 @@ public interface DatabaseComponent {
/** Stores a pseudonym that the user can use to sign messages. */ /** Stores a pseudonym that the user can use to sign messages. */
void addLocalAuthor(LocalAuthor a) throws DbException; void addLocalAuthor(LocalAuthor a) throws DbException;
/**
* Stores a restricted group to which the user can post messages. Storing
* a group does not create a subscription to it.
*/
void addLocalGroup(LocalGroup g) throws DbException;
/** Stores a locally generated group message. */ /** Stores a locally generated group message. */
void addLocalGroupMessage(Message m) throws DbException; void addLocalGroupMessage(Message m) throws DbException;
@@ -194,9 +187,6 @@ public interface DatabaseComponent {
/** Returns all pseudonyms that the user can use to sign messages. */ /** Returns all pseudonyms that the user can use to sign messages. */
Collection<LocalAuthor> getLocalAuthors() throws DbException; Collection<LocalAuthor> getLocalAuthors() throws DbException;
/** Returns all restricted groups to which the user can post messages. */
Collection<LocalGroup> getLocalGroups() throws DbException;
/** Returns the local transport properties for all transports. */ /** Returns the local transport properties for all transports. */
Map<TransportId, TransportProperties> getLocalProperties() Map<TransportId, TransportProperties> getLocalProperties()
throws DbException; throws DbException;

View File

@@ -5,12 +5,12 @@ public class Group {
private final GroupId id; private final GroupId id;
private final String name; private final String name;
private final byte[] publicKey; private final byte[] salt;
public Group(GroupId id, String name, byte[] publicKey) { public Group(GroupId id, String name, byte[] salt) {
this.id = id; this.id = id;
this.name = name; this.name = name;
this.publicKey = publicKey; this.salt = salt;
} }
/** Returns the group's unique identifier. */ /** Returns the group's unique identifier. */
@@ -23,18 +23,12 @@ public class Group {
return name; return name;
} }
/** Returns true if the group is restricted. */
public boolean isRestricted() {
return publicKey != null;
}
/** /**
* If the group is restricted, returns the public key used to verify the * Returns the salt used to distinguish the group from other groups with
* signatures on all messages sent to the group. If the group is * the same name.
* unrestricted, returns null.
*/ */
public byte[] getPublicKey() { public byte[] getSalt() {
return publicKey; return salt;
} }
@Override @Override

View File

@@ -4,13 +4,9 @@ import java.io.IOException;
public interface GroupFactory { public interface GroupFactory {
/** Creates an unrestricted group. */ /** Creates a group with the given name and a random salt. */
Group createGroup(String name) throws IOException; Group createGroup(String name) throws IOException;
/** Creates a restricted group. */ /** Creates a group with the given name and salt. */
Group createGroup(String name, byte[] publicKey) throws IOException; Group createGroup(String name, byte[] salt) throws IOException;
/** Creates a restricted group to which the local user can post messages. */
LocalGroup createLocalGroup(String name, byte[] publicKey,
byte[] privateKey) throws IOException;
} }

View File

@@ -1,18 +0,0 @@
package net.sf.briar.api.messaging;
/** A restricted group to which the local user can post messages. */
public class LocalGroup extends Group {
private final byte[] privateKey;
public LocalGroup(GroupId id, String name, byte[] publicKey,
byte[] privateKey) {
super(id, name, publicKey);
this.privateKey = privateKey;
}
/** Returns the private key used to sign all messages sent to the group. */
public byte[] getPrivateKey() {
return privateKey;
}
}

View File

@@ -12,24 +12,13 @@ public interface MessageFactory {
Message createPrivateMessage(MessageId parent, String contentType, Message createPrivateMessage(MessageId parent, String contentType,
byte[] body) throws IOException, GeneralSecurityException; byte[] body) throws IOException, GeneralSecurityException;
/** Creates an anonymous message to an unrestricted group. */ /** Creates an anonymous group message. */
Message createAnonymousMessage(MessageId parent, Group group, Message createAnonymousMessage(MessageId parent, Group group,
String contentType, byte[] body) throws IOException, String contentType, byte[] body) throws IOException,
GeneralSecurityException; GeneralSecurityException;
/** Creates an anonymous message to a restricted group. */ /** Creates a pseudonymous group message. */
Message createAnonymousMessage(MessageId parent, Group group,
PrivateKey groupKey, String contentType, byte[] body)
throws IOException, GeneralSecurityException;
/** Creates a pseudonymous message to an unrestricted group. */
Message createPseudonymousMessage(MessageId parent, Group group, Message createPseudonymousMessage(MessageId parent, Group group,
Author author, PrivateKey authorKey, String contentType, Author author, PrivateKey privateKey, String contentType,
byte[] body) throws IOException, GeneralSecurityException; byte[] body) throws IOException, GeneralSecurityException;
/** Creates a pseudonymous message to a restricted group. */
Message createPseudonymousMessage(MessageId parent, Group group,
PrivateKey groupKey, Author author, PrivateKey authorKey,
String contentType, byte[] body) throws IOException,
GeneralSecurityException;
} }

View File

@@ -17,6 +17,9 @@ public interface MessagingConstants {
/** The maximum length of a group's name in UTF-8 bytes. */ /** The maximum length of a group's name in UTF-8 bytes. */
int MAX_GROUP_NAME_LENGTH = 50; int MAX_GROUP_NAME_LENGTH = 50;
/** The length of a group's random salt in bytes. */
int GROUP_SALT_LENGTH = 32;
/** /**
* The maximum length of a message body in bytes. To allow for future * The maximum length of a message body in bytes. To allow for future
* changes in the protocol, this is smaller than the maximum packet length * changes in the protocol, this is smaller than the maximum packet length
@@ -31,7 +34,7 @@ public interface MessagingConstants {
int MAX_SUBJECT_LENGTH = 100; int MAX_SUBJECT_LENGTH = 100;
/** The length of a message's random salt in bytes. */ /** The length of a message's random salt in bytes. */
int SALT_LENGTH = 8; int MESSAGE_SALT_LENGTH = 32;
/** /**
* The timestamp of the oldest message in the database is rounded using * The timestamp of the oldest message in the database is rounded using

View File

@@ -10,13 +10,12 @@ public class UnverifiedMessage {
private final Author author; private final Author author;
private final String contentType, subject; private final String contentType, subject;
private final long timestamp; private final long timestamp;
private final byte[] raw, authorSig, groupSig; private final byte[] raw, signature;
private final int bodyStart, bodyLength, signedByAuthor, signedByGroup; private final int bodyStart, bodyLength, signedLength;
public UnverifiedMessage(MessageId parent, Group group, Author author, public UnverifiedMessage(MessageId parent, Group group, Author author,
String contentType, String subject, long timestamp, byte[] raw, String contentType, String subject, long timestamp, byte[] raw,
byte[] authorSig, byte[] groupSig, int bodyStart, int bodyLength, byte[] signature, int bodyStart, int bodyLength, int signedLength) {
int signedByAuthor, int signedByGroup) {
this.parent = parent; this.parent = parent;
this.group = group; this.group = group;
this.author = author; this.author = author;
@@ -24,12 +23,10 @@ public class UnverifiedMessage {
this.subject = subject; this.subject = subject;
this.timestamp = timestamp; this.timestamp = timestamp;
this.raw = raw; this.raw = raw;
this.authorSig = authorSig; this.signature = signature;
this.groupSig = groupSig;
this.bodyStart = bodyStart; this.bodyStart = bodyStart;
this.bodyLength = bodyLength; this.bodyLength = bodyLength;
this.signedByAuthor = signedByAuthor; this.signedLength = signedLength;
this.signedByGroup = signedByGroup;
} }
/** /**
@@ -83,16 +80,8 @@ public class UnverifiedMessage {
/** /**
* Returns the author's signature, or null if this is an anonymous message. * Returns the author's signature, or null if this is an anonymous message.
*/ */
public byte[] getAuthorSignature() { public byte[] getSignature() {
return authorSig; return signature;
}
/**
* Returns the group's signature, or null if this is a private message or
* a message belonging to an unrestricted group.
*/
public byte[] getGroupSignature() {
return groupSig;
} }
/** Returns the offset of the message body within the serialised message. */ /** Returns the offset of the message body within the serialised message. */
@@ -109,15 +98,7 @@ public class UnverifiedMessage {
* Returns the length in bytes of the data covered by the author's * Returns the length in bytes of the data covered by the author's
* signature. * signature.
*/ */
public int getLengthSignedByAuthor() { public int getSignedLength() {
return signedByAuthor; return signedLength;
}
/**
* Returns the length in bytes of the data covered by the group's
* signature.
*/
public int getLengthSignedByGroup() {
return signedByGroup;
} }
} }

View File

@@ -18,7 +18,6 @@ import net.sf.briar.api.db.PrivateMessageHeader;
import net.sf.briar.api.messaging.Group; import net.sf.briar.api.messaging.Group;
import net.sf.briar.api.messaging.GroupId; import net.sf.briar.api.messaging.GroupId;
import net.sf.briar.api.messaging.GroupStatus; import net.sf.briar.api.messaging.GroupStatus;
import net.sf.briar.api.messaging.LocalGroup;
import net.sf.briar.api.messaging.Message; import net.sf.briar.api.messaging.Message;
import net.sf.briar.api.messaging.MessageId; import net.sf.briar.api.messaging.MessageId;
import net.sf.briar.api.messaging.Rating; import net.sf.briar.api.messaging.Rating;
@@ -112,14 +111,6 @@ interface Database<T> {
*/ */
void addLocalAuthor(T txn, LocalAuthor a) throws DbException; void addLocalAuthor(T txn, LocalAuthor a) throws DbException;
/**
* Stores a restricted group to which the user can post messages. Storing
* a group does not create a subscription to it.
* <p>
* Locking: identity write.
*/
void addLocalGroup(T txn, LocalGroup g) throws DbException;
/** /**
* Records a received message as needing to be acknowledged. * Records a received message as needing to be acknowledged.
* <p> * <p>
@@ -192,14 +183,6 @@ interface Database<T> {
*/ */
boolean containsContact(T txn, ContactId c) throws DbException; boolean containsContact(T txn, ContactId c) throws DbException;
/**
* Returns true if the database contains the given restricted group to
* which the user can post messages.
* <p>
* Locking: identity read.
*/
boolean containsLocalGroup(T txn, GroupId g) throws DbException;
/** /**
* Returns true if the database contains the given message. * Returns true if the database contains the given message.
* <p> * <p>
@@ -301,6 +284,14 @@ interface Database<T> {
*/ */
MessageId getGroupMessageParent(T txn, MessageId m) throws DbException; MessageId getGroupMessageParent(T txn, MessageId m) throws DbException;
/**
* Returns the IDs of all group messages posted by the given author.
* <p>
* Locking: message read.
*/
Collection<MessageId> getGroupMessages(T txn, AuthorId a)
throws DbException;
/** /**
* Returns the time at which a connection to each contact was last opened * Returns the time at which a connection to each contact was last opened
* or closed. * or closed.
@@ -323,13 +314,6 @@ interface Database<T> {
*/ */
Collection<LocalAuthor> getLocalAuthors(T txn) throws DbException; Collection<LocalAuthor> getLocalAuthors(T txn) throws DbException;
/**
* Returns all restricted groups to which the user can post messages.
* <p>
* Locking: identity read.
*/
Collection<LocalGroup> getLocalGroups(T txn) throws DbException;
/** /**
* Returns the local transport properties for all transports. * Returns the local transport properties for all transports.
* <p> * <p>
@@ -556,15 +540,6 @@ interface Database<T> {
*/ */
Map<GroupId, Integer> getUnreadMessageCounts(T txn) throws DbException; Map<GroupId, Integer> getUnreadMessageCounts(T txn) throws DbException;
/**
* Returns the IDs of all messages posted by the given author to
* unrestricted groups.
* <p>
* Locking: message read.
*/
Collection<MessageId> getUnrestrictedGroupMessages(T txn, AuthorId a)
throws DbException;
/** /**
* Returns the contacts to which the given group is visible. * Returns the contacts to which the given group is visible.
* <p> * <p>
@@ -631,13 +606,6 @@ interface Database<T> {
*/ */
void removeContact(T txn, ContactId c) throws DbException; void removeContact(T txn, ContactId c) throws DbException;
/**
* Removes the given restricted group to which the user can post messages.
* <p>
* Locking: identity write.
*/
void removeLocalGroup(T txn, GroupId g) throws DbException;
/** /**
* Removes a message (and all associated state) from the database. * Removes a message (and all associated state) from the database.
* <p> * <p>

View File

@@ -65,7 +65,6 @@ import net.sf.briar.api.messaging.Ack;
import net.sf.briar.api.messaging.Group; import net.sf.briar.api.messaging.Group;
import net.sf.briar.api.messaging.GroupId; import net.sf.briar.api.messaging.GroupId;
import net.sf.briar.api.messaging.GroupStatus; import net.sf.briar.api.messaging.GroupStatus;
import net.sf.briar.api.messaging.LocalGroup;
import net.sf.briar.api.messaging.Message; import net.sf.briar.api.messaging.Message;
import net.sf.briar.api.messaging.MessageId; import net.sf.briar.api.messaging.MessageId;
import net.sf.briar.api.messaging.Offer; import net.sf.briar.api.messaging.Offer;
@@ -285,22 +284,6 @@ DatabaseCleaner.Callback {
} }
} }
public void addLocalGroup(LocalGroup g) throws DbException {
identityLock.writeLock().lock();
try {
T txn = db.startTransaction();
try {
db.addLocalGroup(txn, g);
db.commitTransaction(txn);
} catch(DbException e) {
db.abortTransaction(txn);
throw e;
}
} finally {
identityLock.writeLock().unlock();
}
}
public void addLocalGroupMessage(Message m) throws DbException { public void addLocalGroupMessage(Message m) throws DbException {
boolean added = false; boolean added = false;
contactLock.readLock().lock(); contactLock.readLock().lock();
@@ -362,13 +345,9 @@ DatabaseCleaner.Callback {
if(!c.equals(sender)) db.addStatus(txn, c, id, false); if(!c.equals(sender)) db.addStatus(txn, c, id, false);
} }
// Calculate and store the message's sendability // Calculate and store the message's sendability
if(m.getGroup().isRestricted()) { int sendability = calculateSendability(txn, m);
db.setSendability(txn, id, 1); db.setSendability(txn, id, sendability);
} else { if(sendability > 0) updateAncestorSendability(txn, id, true);
int sendability = calculateSendability(txn, m);
db.setSendability(txn, id, sendability);
if(sendability > 0) updateAncestorSendability(txn, id, true);
}
// Count the bytes stored // Count the bytes stored
synchronized(spaceLock) { synchronized(spaceLock) {
bytesStoredSinceLastCheck += m.getSerialised().length; bytesStoredSinceLastCheck += m.getSerialised().length;
@@ -1066,23 +1045,6 @@ DatabaseCleaner.Callback {
} }
} }
public Collection<LocalGroup> getLocalGroups() throws DbException {
identityLock.readLock().lock();
try {
T txn = db.startTransaction();
try {
Collection<LocalGroup> groups = db.getLocalGroups(txn);
db.commitTransaction(txn);
return groups;
} catch(DbException e) {
db.abortTransaction(txn);
throw e;
}
} finally {
identityLock.readLock().unlock();
}
}
public Map<TransportId, TransportProperties> getLocalProperties() public Map<TransportId, TransportProperties> getLocalProperties()
throws DbException { throws DbException {
transportLock.readLock().lock(); transportLock.readLock().lock();
@@ -1964,8 +1926,8 @@ DatabaseCleaner.Callback {
} }
/** /**
* Updates the sendability of all messages posted by the given author to * Updates the sendability of all group messages posted by the given
* unrestricted groups, and the ancestors of those messages if necessary. * author, and the ancestors of those messages if necessary.
* <p> * <p>
* Locking: message write. * Locking: message write.
* @param increment true if the user's rating for the author has changed * @param increment true if the user's rating for the author has changed
@@ -1973,7 +1935,7 @@ DatabaseCleaner.Callback {
*/ */
private void updateAuthorSendability(T txn, AuthorId a, boolean increment) private void updateAuthorSendability(T txn, AuthorId a, boolean increment)
throws DbException { throws DbException {
for(MessageId id : db.getUnrestrictedGroupMessages(txn, a)) { for(MessageId id : db.getGroupMessages(txn, a)) {
int sendability = db.getSendability(txn, id); int sendability = db.getSendability(txn, id);
if(increment) { if(increment) {
db.setSendability(txn, id, sendability + 1); db.setSendability(txn, id, sendability + 1);
@@ -2125,8 +2087,6 @@ DatabaseCleaner.Callback {
throw new NoSuchSubscriptionException(); throw new NoSuchSubscriptionException();
affected = db.getVisibility(txn, id); affected = db.getVisibility(txn, id);
db.removeSubscription(txn, id); db.removeSubscription(txn, id);
if(db.containsLocalGroup(txn, id))
db.removeLocalGroup(txn, id);
db.commitTransaction(txn); db.commitTransaction(txn);
} catch(DbException e) { } catch(DbException e) {
db.abortTransaction(txn); db.abortTransaction(txn);

View File

@@ -43,7 +43,6 @@ import net.sf.briar.api.db.PrivateMessageHeader;
import net.sf.briar.api.messaging.Group; import net.sf.briar.api.messaging.Group;
import net.sf.briar.api.messaging.GroupId; import net.sf.briar.api.messaging.GroupId;
import net.sf.briar.api.messaging.GroupStatus; import net.sf.briar.api.messaging.GroupStatus;
import net.sf.briar.api.messaging.LocalGroup;
import net.sf.briar.api.messaging.Message; import net.sf.briar.api.messaging.Message;
import net.sf.briar.api.messaging.MessageId; import net.sf.briar.api.messaging.MessageId;
import net.sf.briar.api.messaging.Rating; import net.sf.briar.api.messaging.Rating;
@@ -71,15 +70,6 @@ abstract class JdbcDatabase implements Database<Connection> {
+ " privateKey BINARY NOT NULL," + " privateKey BINARY NOT NULL,"
+ " PRIMARY KEY (authorId))"; + " PRIMARY KEY (authorId))";
// Locking: identity
private static final String CREATE_LOCAL_GROUPS =
"CREATE TABLE localGroups"
+ " (groupId HASH NOT NULL,"
+ " name VARCHAR NOT NULL,"
+ " publicKey BINARY NOT NULL,"
+ " privateKey BINARY NOT NULL,"
+ " PRIMARY KEY (groupId))";
// Locking: contact // Locking: contact
// Dependents: message, retention, subscription, transport, window // Dependents: message, retention, subscription, transport, window
private static final String CREATE_CONTACTS = private static final String CREATE_CONTACTS =
@@ -104,7 +94,7 @@ abstract class JdbcDatabase implements Database<Connection> {
"CREATE TABLE groups" "CREATE TABLE groups"
+ " (groupId HASH NOT NULL," + " (groupId HASH NOT NULL,"
+ " name VARCHAR NOT NULL," + " name VARCHAR NOT NULL,"
+ " publicKey BINARY," // Null for unrestricted groups + " salt BINARY NOT NULL,"
+ " visibleToAll BOOLEAN NOT NULL," + " visibleToAll BOOLEAN NOT NULL,"
+ " PRIMARY KEY (groupId))"; + " PRIMARY KEY (groupId))";
@@ -126,7 +116,7 @@ abstract class JdbcDatabase implements Database<Connection> {
+ " (contactId INT NOT NULL," + " (contactId INT NOT NULL,"
+ " groupId HASH NOT NULL," // Not a foreign key + " groupId HASH NOT NULL," // Not a foreign key
+ " name VARCHAR NOT NULL," + " name VARCHAR NOT NULL,"
+ " publicKey BINARY," // Null for unrestricted groups + " salt BINARY NOT NULL,"
+ " PRIMARY KEY (contactId, groupId)," + " PRIMARY KEY (contactId, groupId),"
+ " FOREIGN KEY (contactId)" + " FOREIGN KEY (contactId)"
+ " REFERENCES contacts (contactId)" + " REFERENCES contacts (contactId)"
@@ -406,7 +396,6 @@ abstract class JdbcDatabase implements Database<Connection> {
try { try {
s = txn.createStatement(); s = txn.createStatement();
s.executeUpdate(insertTypeNames(CREATE_LOCAL_AUTHORS)); s.executeUpdate(insertTypeNames(CREATE_LOCAL_AUTHORS));
s.executeUpdate(insertTypeNames(CREATE_LOCAL_GROUPS));
s.executeUpdate(insertTypeNames(CREATE_CONTACTS)); s.executeUpdate(insertTypeNames(CREATE_CONTACTS));
s.executeUpdate(INDEX_CONTACTS_BY_AUTHOR); s.executeUpdate(INDEX_CONTACTS_BY_AUTHOR);
s.executeUpdate(insertTypeNames(CREATE_GROUPS)); s.executeUpdate(insertTypeNames(CREATE_GROUPS));
@@ -750,26 +739,6 @@ abstract class JdbcDatabase implements Database<Connection> {
} }
} }
public void addLocalGroup(Connection txn, LocalGroup g) throws DbException {
PreparedStatement ps = null;
try {
String sql = "INSERT INTO localGroups"
+ " (groupId, name, publicKey, privateKey)"
+ " VALUES (?, ?, ?, ?)";
ps = txn.prepareStatement(sql);
ps.setBytes(1, g.getId().getBytes());
ps.setString(2, g.getName());
ps.setBytes(3, g.getPublicKey());
ps.setBytes(4, g.getPrivateKey());
int affected = ps.executeUpdate();
if(affected != 1) throw new DbStateException();
ps.close();
} catch(SQLException e) {
tryToClose(ps);
throw new DbException(e);
}
}
public void addMessageToAck(Connection txn, ContactId c, MessageId m) public void addMessageToAck(Connection txn, ContactId c, MessageId m)
throws DbException { throws DbException {
PreparedStatement ps = null; PreparedStatement ps = null;
@@ -916,13 +885,12 @@ abstract class JdbcDatabase implements Database<Connection> {
ps.close(); ps.close();
if(count > MAX_SUBSCRIPTIONS) throw new DbStateException(); if(count > MAX_SUBSCRIPTIONS) throw new DbStateException();
if(count == MAX_SUBSCRIPTIONS) return false; if(count == MAX_SUBSCRIPTIONS) return false;
sql = "INSERT INTO groups (groupId, name, publicKey, visibleToAll)" sql = "INSERT INTO groups (groupId, name, salt, visibleToAll)"
+ " VALUES (?, ?, ?, FALSE)"; + " VALUES (?, ?, ?, FALSE)";
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());
if(g.isRestricted()) ps.setBytes(3, g.getPublicKey()); ps.setBytes(3, g.getSalt());
else ps.setNull(3, BINARY);
int affected = ps.executeUpdate(); int affected = ps.executeUpdate();
if(affected != 1) throw new DbStateException(); if(affected != 1) throw new DbStateException();
ps.close(); ps.close();
@@ -1059,27 +1027,6 @@ abstract class JdbcDatabase implements Database<Connection> {
} }
} }
public boolean containsLocalGroup(Connection txn, GroupId g)
throws DbException {
PreparedStatement ps = null;
ResultSet rs = null;
try {
String sql = "SELECT NULL FROM localGroups WHERE groupId = ?";
ps = txn.prepareStatement(sql);
ps.setBytes(1, g.getBytes());
rs = ps.executeQuery();
boolean found = rs.next();
if(rs.next()) throw new DbStateException();
rs.close();
ps.close();
return found;
} catch(SQLException e) {
tryToClose(rs);
tryToClose(ps);
throw new DbException(e);
}
}
public boolean containsMessage(Connection txn, MessageId m) public boolean containsMessage(Connection txn, MessageId m)
throws DbException { throws DbException {
PreparedStatement ps = null; PreparedStatement ps = null;
@@ -1172,8 +1119,7 @@ abstract class JdbcDatabase implements Database<Connection> {
ResultSet rs = null; ResultSet rs = null;
try { try {
// Add all subscribed groups to the list // Add all subscribed groups to the list
String sql = "SELECT groupId, name, publicKey, visibleToAll" String sql = "SELECT groupId, name, salt, visibleToAll FROM groups";
+ " FROM groups";
ps = txn.prepareStatement(sql); ps = txn.prepareStatement(sql);
rs = ps.executeQuery(); rs = ps.executeQuery();
List<GroupStatus> groups = new ArrayList<GroupStatus>(); List<GroupStatus> groups = new ArrayList<GroupStatus>();
@@ -1182,24 +1128,23 @@ abstract class JdbcDatabase implements Database<Connection> {
GroupId id = new GroupId(rs.getBytes(1)); GroupId id = new GroupId(rs.getBytes(1));
subscribed.add(id); subscribed.add(id);
String name = rs.getString(2); String name = rs.getString(2);
byte[] publicKey = rs.getBytes(3); byte[] salt = rs.getBytes(3);
Group group = new Group(id, name, publicKey); Group group = new Group(id, name, salt);
boolean visibleToAll = rs.getBoolean(4); boolean visibleToAll = rs.getBoolean(4);
groups.add(new GroupStatus(group, true, visibleToAll)); groups.add(new GroupStatus(group, true, visibleToAll));
} }
rs.close(); rs.close();
ps.close(); ps.close();
// Add all contact groups to the list, unless already added // Add all contact groups to the list, unless already added
sql = "SELECT DISTINCT groupId, name, publicKey" sql = "SELECT DISTINCT groupId, name, salt FROM contactGroups";
+ " FROM contactGroups";
ps = txn.prepareStatement(sql); ps = txn.prepareStatement(sql);
rs = ps.executeQuery(); rs = ps.executeQuery();
while(rs.next()) { while(rs.next()) {
GroupId id = new GroupId(rs.getBytes(1)); GroupId id = new GroupId(rs.getBytes(1));
if(subscribed.contains(id)) continue; if(subscribed.contains(id)) continue;
String name = rs.getString(2); String name = rs.getString(2);
byte[] publicKey = rs.getBytes(3); byte[] salt = rs.getBytes(3);
Group group = new Group(id, name, publicKey); Group group = new Group(id, name, salt);
groups.add(new GroupStatus(group, false, false)); groups.add(new GroupStatus(group, false, false));
} }
rs.close(); rs.close();
@@ -1340,16 +1285,16 @@ abstract class JdbcDatabase implements Database<Connection> {
PreparedStatement ps = null; PreparedStatement ps = null;
ResultSet rs = null; ResultSet rs = null;
try { try {
String sql = "SELECT name, publicKey FROM groups WHERE groupId = ?"; String sql = "SELECT name, salt FROM groups WHERE groupId = ?";
ps = txn.prepareStatement(sql); ps = txn.prepareStatement(sql);
ps.setBytes(1, g.getBytes()); ps.setBytes(1, g.getBytes());
rs = ps.executeQuery(); rs = ps.executeQuery();
if(!rs.next()) throw new DbStateException(); if(!rs.next()) throw new DbStateException();
String name = rs.getString(1); String name = rs.getString(1);
byte[] publicKey = rs.getBytes(2); byte[] salt = rs.getBytes(2);
rs.close(); rs.close();
ps.close(); ps.close();
return new Group(g, name, publicKey); return new Group(g, name, salt);
} catch(SQLException e) { } catch(SQLException e) {
tryToClose(rs); tryToClose(rs);
tryToClose(ps); tryToClose(ps);
@@ -1439,6 +1384,31 @@ abstract class JdbcDatabase implements Database<Connection> {
} }
} }
public Collection<MessageId> getGroupMessages(Connection txn,
AuthorId a) throws DbException {
PreparedStatement ps = null;
ResultSet rs = null;
try {
String sql = "SELECT messageId"
+ " FROM messages AS m"
+ " JOIN groups AS g"
+ " ON m.groupId = g.groupId"
+ " WHERE authorId = ?";
ps = txn.prepareStatement(sql);
ps.setBytes(1, a.getBytes());
rs = ps.executeQuery();
List<MessageId> ids = new ArrayList<MessageId>();
while(rs.next()) ids.add(new MessageId(rs.getBytes(1)));
rs.close();
ps.close();
return Collections.unmodifiableList(ids);
} catch(SQLException e) {
tryToClose(rs);
tryToClose(ps);
throw new DbException(e);
}
}
public Map<ContactId, Long> getLastConnected(Connection txn) public Map<ContactId, Long> getLastConnected(Connection txn)
throws DbException { throws DbException {
PreparedStatement ps = null; PreparedStatement ps = null;
@@ -1512,34 +1482,6 @@ abstract class JdbcDatabase implements Database<Connection> {
} }
} }
public Collection<LocalGroup> getLocalGroups(Connection txn)
throws DbException {
PreparedStatement ps = null;
ResultSet rs = null;
try {
String sql = "SELECT groupId, name, publicKey, privateKey"
+ " FROM localGroups";
ps = txn.prepareStatement(sql);
rs = ps.executeQuery();
List<LocalGroup> groups = new ArrayList<LocalGroup>();
while(rs.next()) {
GroupId groupId = new GroupId(rs.getBytes(1));
String name = rs.getString(2);
byte[] publicKey = rs.getBytes(3);
byte[] privateKey = rs.getBytes(4);
groups.add(new LocalGroup(groupId, name, publicKey,
privateKey));
}
rs.close();
ps.close();
return Collections.unmodifiableList(groups);
} catch(SQLException e) {
tryToClose(rs);
tryToClose(ps);
throw new DbException(e);
}
}
public Map<TransportId, TransportProperties> getLocalProperties( public Map<TransportId, TransportProperties> getLocalProperties(
Connection txn) throws DbException { Connection txn) throws DbException {
PreparedStatement ps = null; PreparedStatement ps = null;
@@ -2223,7 +2165,8 @@ abstract class JdbcDatabase implements Database<Connection> {
} }
} }
public boolean getStarredFlag(Connection txn, MessageId m) throws DbException { public boolean getStarredFlag(Connection txn, MessageId m)
throws DbException {
PreparedStatement ps = null; PreparedStatement ps = null;
ResultSet rs = null; ResultSet rs = null;
try { try {
@@ -2249,15 +2192,15 @@ abstract class JdbcDatabase implements Database<Connection> {
PreparedStatement ps = null; PreparedStatement ps = null;
ResultSet rs = null; ResultSet rs = null;
try { try {
String sql = "SELECT groupId, name, publicKey FROM groups"; String sql = "SELECT groupId, name, salt FROM groups";
ps = txn.prepareStatement(sql); ps = txn.prepareStatement(sql);
rs = ps.executeQuery(); rs = ps.executeQuery();
List<Group> subs = new ArrayList<Group>(); List<Group> subs = new ArrayList<Group>();
while(rs.next()) { while(rs.next()) {
GroupId groupId = new GroupId(rs.getBytes(1)); GroupId groupId = new GroupId(rs.getBytes(1));
String name = rs.getString(2); String name = rs.getString(2);
byte[] publicKey = rs.getBytes(3); byte[] salt = rs.getBytes(3);
subs.add(new Group(groupId, name, publicKey)); subs.add(new Group(groupId, name, salt));
} }
rs.close(); rs.close();
ps.close(); ps.close();
@@ -2274,7 +2217,7 @@ abstract class JdbcDatabase implements Database<Connection> {
PreparedStatement ps = null; PreparedStatement ps = null;
ResultSet rs = null; ResultSet rs = null;
try { try {
String sql = "SELECT groupId, name, publicKey FROM contactGroups" String sql = "SELECT groupId, name, salt FROM contactGroups"
+ " WHERE contactId = ?"; + " WHERE contactId = ?";
ps = txn.prepareStatement(sql); ps = txn.prepareStatement(sql);
ps.setInt(1, c.getInt()); ps.setInt(1, c.getInt());
@@ -2283,8 +2226,8 @@ abstract class JdbcDatabase implements Database<Connection> {
while(rs.next()) { while(rs.next()) {
GroupId groupId = new GroupId(rs.getBytes(1)); GroupId groupId = new GroupId(rs.getBytes(1));
String name = rs.getString(2); String name = rs.getString(2);
byte[] publicKey = rs.getBytes(3); byte[] salt = rs.getBytes(3);
subs.add(new Group(groupId, name, publicKey)); subs.add(new Group(groupId, name, salt));
} }
rs.close(); rs.close();
ps.close(); ps.close();
@@ -2336,7 +2279,7 @@ abstract class JdbcDatabase implements Database<Connection> {
PreparedStatement ps = null; PreparedStatement ps = null;
ResultSet rs = null; ResultSet rs = null;
try { try {
String sql = "SELECT g.groupId, name, publicKey," String sql = "SELECT g.groupId, name, salt,"
+ " localVersion, txCount" + " localVersion, txCount"
+ " FROM groups AS g" + " FROM groups AS g"
+ " JOIN groupVisibilities AS vis" + " JOIN groupVisibilities AS vis"
@@ -2356,8 +2299,8 @@ abstract class JdbcDatabase implements Database<Connection> {
while(rs.next()) { while(rs.next()) {
GroupId groupId = new GroupId(rs.getBytes(1)); GroupId groupId = new GroupId(rs.getBytes(1));
String name = rs.getString(2); String name = rs.getString(2);
byte[] key = rs.getBytes(3); byte[] salt = rs.getBytes(3);
subs.add(new Group(groupId, name, key)); subs.add(new Group(groupId, name, salt));
version = rs.getLong(4); version = rs.getLong(4);
txCount = rs.getInt(5); txCount = rs.getInt(5);
} }
@@ -2564,32 +2507,6 @@ abstract class JdbcDatabase implements Database<Connection> {
} }
} }
public Collection<MessageId> getUnrestrictedGroupMessages(Connection txn,
AuthorId a) throws DbException {
PreparedStatement ps = null;
ResultSet rs = null;
try {
String sql = "SELECT messageId"
+ " FROM messages AS m"
+ " JOIN groups AS g"
+ " ON m.groupId = g.groupId"
+ " WHERE authorId = ?"
+ " AND publicKey IS NULL";
ps = txn.prepareStatement(sql);
ps.setBytes(1, a.getBytes());
rs = ps.executeQuery();
List<MessageId> ids = new ArrayList<MessageId>();
while(rs.next()) ids.add(new MessageId(rs.getBytes(1)));
rs.close();
ps.close();
return Collections.unmodifiableList(ids);
} catch(SQLException e) {
tryToClose(rs);
tryToClose(ps);
throw new DbException(e);
}
}
public Collection<ContactId> getVisibility(Connection txn, GroupId g) public Collection<ContactId> getVisibility(Connection txn, GroupId g)
throws DbException { throws DbException {
PreparedStatement ps = null; PreparedStatement ps = null;
@@ -2808,21 +2725,6 @@ abstract class JdbcDatabase implements Database<Connection> {
} }
} }
public void removeLocalGroup(Connection txn, GroupId g) throws DbException {
PreparedStatement ps = null;
try {
String sql = "DELETE FROM localGroups WHERE groupId = ?";
ps = txn.prepareStatement(sql);
ps.setBytes(1, g.getBytes());
int affected = ps.executeUpdate();
if(affected != 1) throw new DbStateException();
ps.close();
} catch(SQLException e) {
tryToClose(ps);
throw new DbException(e);
}
}
public void removeMessage(Connection txn, MessageId m) throws DbException { public void removeMessage(Connection txn, MessageId m) throws DbException {
PreparedStatement ps = null; PreparedStatement ps = null;
try { try {
@@ -3395,15 +3297,14 @@ abstract class JdbcDatabase implements Database<Connection> {
// Store the new subscriptions, if any // Store the new subscriptions, if any
if(subs.isEmpty()) return true; if(subs.isEmpty()) return true;
sql = "INSERT INTO contactGroups" sql = "INSERT INTO contactGroups"
+ " (contactId, groupId, name, publicKey)" + " (contactId, groupId, name, salt)"
+ " VALUES (?, ?, ?, ?)"; + " VALUES (?, ?, ?, ?)";
ps = txn.prepareStatement(sql); ps = txn.prepareStatement(sql);
ps.setInt(1, c.getInt()); ps.setInt(1, c.getInt());
for(Group g : subs) { for(Group g : subs) {
ps.setBytes(2, g.getId().getBytes()); ps.setBytes(2, g.getId().getBytes());
ps.setString(3, g.getName()); ps.setString(3, g.getName());
if(g.isRestricted()) ps.setBytes(4, g.getPublicKey()); ps.setBytes(4, g.getSalt());
else ps.setNull(4, BINARY);
ps.addBatch(); ps.addBatch();
} }
int[] batchAffected = ps.executeBatch(); int[] batchAffected = ps.executeBatch();

View File

@@ -1,5 +1,6 @@
package net.sf.briar.messaging; package net.sf.briar.messaging;
import static net.sf.briar.api.messaging.MessagingConstants.GROUP_SALT_LENGTH;
import static net.sf.briar.api.messaging.Types.GROUP; import static net.sf.briar.api.messaging.Types.GROUP;
import java.io.ByteArrayOutputStream; import java.io.ByteArrayOutputStream;
@@ -10,7 +11,6 @@ import net.sf.briar.api.crypto.MessageDigest;
import net.sf.briar.api.messaging.Group; import net.sf.briar.api.messaging.Group;
import net.sf.briar.api.messaging.GroupFactory; import net.sf.briar.api.messaging.GroupFactory;
import net.sf.briar.api.messaging.GroupId; import net.sf.briar.api.messaging.GroupId;
import net.sf.briar.api.messaging.LocalGroup;
import net.sf.briar.api.serial.Writer; import net.sf.briar.api.serial.Writer;
import net.sf.briar.api.serial.WriterFactory; import net.sf.briar.api.serial.WriterFactory;
@@ -28,29 +28,20 @@ class GroupFactoryImpl implements GroupFactory {
} }
public Group createGroup(String name) throws IOException { public Group createGroup(String name) throws IOException {
return createGroup(name, null); byte[] salt = new byte[GROUP_SALT_LENGTH];
crypto.getSecureRandom().nextBytes(salt);
return createGroup(name, salt);
} }
public Group createGroup(String name, byte[] publicKey) throws IOException { public Group createGroup(String name, byte[] salt) throws IOException {
GroupId id = getId(name, publicKey);
return new Group(id, name, publicKey);
}
public LocalGroup createLocalGroup(String name, byte[] publicKey,
byte[] privateKey) throws IOException {
GroupId id = getId(name, publicKey);
return new LocalGroup(id, name, publicKey, privateKey);
}
private GroupId getId(String name, byte[] publicKey) throws IOException {
ByteArrayOutputStream out = new ByteArrayOutputStream(); ByteArrayOutputStream out = new ByteArrayOutputStream();
Writer w = writerFactory.createWriter(out); Writer w = writerFactory.createWriter(out);
w.writeStructId(GROUP); w.writeStructId(GROUP);
w.writeString(name); w.writeString(name);
if(publicKey == null) w.writeNull(); w.writeBytes(salt);
else w.writeBytes(publicKey);
MessageDigest messageDigest = crypto.getMessageDigest(); MessageDigest messageDigest = crypto.getMessageDigest();
messageDigest.update(out.toByteArray()); messageDigest.update(out.toByteArray());
return new GroupId(messageDigest.digest()); GroupId id = new GroupId(messageDigest.digest());
return new Group(id, name, salt);
} }
} }

View File

@@ -5,7 +5,7 @@ import static net.sf.briar.api.messaging.MessagingConstants.MAX_BODY_LENGTH;
import static net.sf.briar.api.messaging.MessagingConstants.MAX_CONTENT_TYPE_LENGTH; import static net.sf.briar.api.messaging.MessagingConstants.MAX_CONTENT_TYPE_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_SUBJECT_LENGTH; import static net.sf.briar.api.messaging.MessagingConstants.MAX_SUBJECT_LENGTH;
import static net.sf.briar.api.messaging.MessagingConstants.SALT_LENGTH; import static net.sf.briar.api.messaging.MessagingConstants.MESSAGE_SALT_LENGTH;
import static net.sf.briar.api.messaging.Types.AUTHOR; import static net.sf.briar.api.messaging.Types.AUTHOR;
import static net.sf.briar.api.messaging.Types.GROUP; import static net.sf.briar.api.messaging.Types.GROUP;
import static net.sf.briar.api.messaging.Types.MESSAGE; import static net.sf.briar.api.messaging.Types.MESSAGE;
@@ -39,7 +39,7 @@ import com.google.inject.Inject;
class MessageFactoryImpl implements MessageFactory { class MessageFactoryImpl implements MessageFactory {
private final Signature authorSignature, groupSignature; private final Signature signature;
private final SecureRandom random; private final SecureRandom random;
private final MessageDigest messageDigest; private final MessageDigest messageDigest;
private final WriterFactory writerFactory; private final WriterFactory writerFactory;
@@ -49,8 +49,7 @@ class MessageFactoryImpl implements MessageFactory {
@Inject @Inject
MessageFactoryImpl(CryptoComponent crypto, WriterFactory writerFactory, MessageFactoryImpl(CryptoComponent crypto, WriterFactory writerFactory,
Clock clock) { Clock clock) {
authorSignature = crypto.getSignature(); signature = crypto.getSignature();
groupSignature = crypto.getSignature();
random = crypto.getSecureRandom(); random = crypto.getSecureRandom();
messageDigest = crypto.getMessageDigest(); messageDigest = crypto.getMessageDigest();
this.writerFactory = writerFactory; this.writerFactory = writerFactory;
@@ -60,46 +59,27 @@ class MessageFactoryImpl implements MessageFactory {
public Message createPrivateMessage(MessageId parent, String contentType, public Message createPrivateMessage(MessageId parent, String contentType,
byte[] body) throws IOException, GeneralSecurityException { byte[] body) throws IOException, GeneralSecurityException {
return createMessage(parent, null, null, null, null, contentType, body); return createMessage(parent, null, null, null, contentType, body);
} }
public Message createAnonymousMessage(MessageId parent, Group group, public Message createAnonymousMessage(MessageId parent, Group group,
String contentType, byte[] body) throws IOException, String contentType, byte[] body) throws IOException,
GeneralSecurityException { GeneralSecurityException {
return createMessage(parent, group, null, null, null, contentType, return createMessage(parent, group, null, null, contentType, body);
body);
}
public Message createAnonymousMessage(MessageId parent, Group group,
PrivateKey groupKey, String contentType, byte[] body)
throws IOException, GeneralSecurityException {
return createMessage(parent, group, groupKey, null, null, contentType,
body);
} }
public Message createPseudonymousMessage(MessageId parent, Group group, public Message createPseudonymousMessage(MessageId parent, Group group,
Author author, PrivateKey authorKey, String contentType, Author author, PrivateKey privateKey, String contentType,
byte[] body) throws IOException, GeneralSecurityException { byte[] body) throws IOException, GeneralSecurityException {
return createMessage(parent, group, null, author, authorKey, return createMessage(parent, group, author, privateKey, contentType,
contentType, body); body);
} }
public Message createPseudonymousMessage(MessageId parent, Group group, private Message createMessage(MessageId parent, Group group, Author author,
PrivateKey groupKey, Author author, PrivateKey authorKey, PrivateKey privateKey, String contentType, byte[] body)
String contentType, byte[] body) throws IOException, throws IOException, GeneralSecurityException {
GeneralSecurityException {
return createMessage(parent, group, groupKey, author, authorKey,
contentType, body);
}
private Message createMessage(MessageId parent, Group group,
PrivateKey groupKey, Author author, PrivateKey authorKey,
String contentType, byte[] body) throws IOException,
GeneralSecurityException {
// Validate the arguments // Validate the arguments
if((author == null) != (authorKey == null)) if((author == null) != (privateKey == null))
throw new IllegalArgumentException();
if((group == null || !group.isRestricted()) != (groupKey == null))
throw new IllegalArgumentException(); throw new IllegalArgumentException();
if(contentType.getBytes("UTF-8").length > MAX_CONTENT_TYPE_LENGTH) if(contentType.getBytes("UTF-8").length > MAX_CONTENT_TYPE_LENGTH)
throw new IllegalArgumentException(); throw new IllegalArgumentException();
@@ -113,17 +93,11 @@ class MessageFactoryImpl implements MessageFactory {
w.addConsumer(counting); w.addConsumer(counting);
Consumer digestingConsumer = new DigestingConsumer(messageDigest); Consumer digestingConsumer = new DigestingConsumer(messageDigest);
w.addConsumer(digestingConsumer); w.addConsumer(digestingConsumer);
Consumer authorConsumer = null; Consumer signingConsumer = null;
if(authorKey != null) { if(privateKey != null) {
authorSignature.initSign(authorKey); signature.initSign(privateKey);
authorConsumer = new SigningConsumer(authorSignature); signingConsumer = new SigningConsumer(signature);
w.addConsumer(authorConsumer); w.addConsumer(signingConsumer);
}
Consumer groupConsumer = null;
if(groupKey != null) {
groupSignature.initSign(groupKey);
groupConsumer = new SigningConsumer(groupSignature);
w.addConsumer(groupConsumer);
} }
// Write the message // Write the message
w.writeStructId(MESSAGE); w.writeStructId(MESSAGE);
@@ -136,32 +110,22 @@ class MessageFactoryImpl implements MessageFactory {
w.writeString(contentType); w.writeString(contentType);
long timestamp = clock.currentTimeMillis(); long timestamp = clock.currentTimeMillis();
w.writeInt64(timestamp); w.writeInt64(timestamp);
byte[] salt = new byte[SALT_LENGTH]; byte[] salt = new byte[MESSAGE_SALT_LENGTH];
random.nextBytes(salt); random.nextBytes(salt);
w.writeBytes(salt); w.writeBytes(salt);
w.writeBytes(body); w.writeBytes(body);
int bodyStart = (int) counting.getCount() - body.length; int bodyStart = (int) counting.getCount() - body.length;
// Sign the message with the author's private key, if there is one // Sign the message with the author's private key, if there is one
if(authorKey == null) { if(privateKey == null) {
w.writeNull(); w.writeNull();
} else { } else {
w.removeConsumer(authorConsumer); w.removeConsumer(signingConsumer);
byte[] sig = authorSignature.sign(); byte[] sig = signature.sign();
if(sig.length > MAX_SIGNATURE_LENGTH) if(sig.length > MAX_SIGNATURE_LENGTH)
throw new IllegalArgumentException(); throw new IllegalArgumentException();
w.writeBytes(sig); w.writeBytes(sig);
} }
// Sign the message with the group's private key, if there is one // Hash the message, including the signature, to get the message ID
if(groupKey == null) {
w.writeNull();
} else {
w.removeConsumer(groupConsumer);
byte[] sig = groupSignature.sign();
if(sig.length > MAX_SIGNATURE_LENGTH)
throw new IllegalArgumentException();
w.writeBytes(sig);
}
// Hash the message, including the signatures, to get the message ID
w.removeConsumer(digestingConsumer); w.removeConsumer(digestingConsumer);
MessageId id = new MessageId(messageDigest.digest()); MessageId id = new MessageId(messageDigest.digest());
// If the content type is text/plain, extract a subject line // If the content type is text/plain, extract a subject line
@@ -181,8 +145,7 @@ class MessageFactoryImpl implements MessageFactory {
private void writeGroup(Writer w, Group g) throws IOException { private void writeGroup(Writer w, Group g) throws IOException {
w.writeStructId(GROUP); w.writeStructId(GROUP);
w.writeString(g.getName()); w.writeString(g.getName());
if(g.isRestricted()) w.writeBytes(g.getPublicKey()); w.writeBytes(g.getSalt());
else w.writeNull();
} }
private void writeAuthor(Writer w, Author a) throws IOException { private void writeAuthor(Writer w, Author a) throws IOException {

View File

@@ -5,7 +5,7 @@ import static net.sf.briar.api.messaging.MessagingConstants.MAX_BODY_LENGTH;
import static net.sf.briar.api.messaging.MessagingConstants.MAX_CONTENT_TYPE_LENGTH; import static net.sf.briar.api.messaging.MessagingConstants.MAX_CONTENT_TYPE_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_SUBJECT_LENGTH; import static net.sf.briar.api.messaging.MessagingConstants.MAX_SUBJECT_LENGTH;
import static net.sf.briar.api.messaging.MessagingConstants.SALT_LENGTH; import static net.sf.briar.api.messaging.MessagingConstants.MESSAGE_SALT_LENGTH;
import static net.sf.briar.api.messaging.Types.MESSAGE; import static net.sf.briar.api.messaging.Types.MESSAGE;
import java.io.IOException; import java.io.IOException;
@@ -67,8 +67,8 @@ class MessageReader implements StructReader<UnverifiedMessage> {
long timestamp = r.readInt64(); long timestamp = r.readInt64();
if(timestamp < 0) throw new FormatException(); if(timestamp < 0) throw new FormatException();
// Read the salt // Read the salt
byte[] salt = r.readBytes(SALT_LENGTH); byte[] salt = r.readBytes(MESSAGE_SALT_LENGTH);
if(salt.length < SALT_LENGTH) throw new FormatException(); if(salt.length < MESSAGE_SALT_LENGTH) throw new FormatException();
// Read the message body // Read the message body
byte[] body = r.readBytes(MAX_BODY_LENGTH); byte[] body = r.readBytes(MAX_BODY_LENGTH);
// If the content type is text/plain, extract a subject line // If the content type is text/plain, extract a subject line
@@ -84,23 +84,17 @@ class MessageReader implements StructReader<UnverifiedMessage> {
// Record the offset of the body within the message // Record the offset of the body within the message
int bodyStart = (int) counting.getCount() - body.length; int bodyStart = (int) counting.getCount() - body.length;
// Record the length of the data covered by the author's signature // Record the length of the data covered by the author's signature
int signedByAuthor = (int) counting.getCount(); int signedLength = (int) counting.getCount();
// Read the author's signature, if there is one // Read the author's signature, if there is one
byte[] authorSig = null; byte[] signature = null;
if(author == null) r.readNull(); if(author == null) r.readNull();
else authorSig = r.readBytes(MAX_SIGNATURE_LENGTH); else signature = r.readBytes(MAX_SIGNATURE_LENGTH);
// Record the length of the data covered by the group's signature // The signature will be verified later
int signedByGroup = (int) counting.getCount();
// Read the group's signature, if there is one
byte[] groupSig = null;
if(group == null || !group.isRestricted()) r.readNull();
else groupSig = r.readBytes(MAX_SIGNATURE_LENGTH);
// That's all, folks
r.removeConsumer(counting); r.removeConsumer(counting);
r.removeConsumer(copying); r.removeConsumer(copying);
byte[] raw = copying.getCopy(); byte[] raw = copying.getCopy();
return new UnverifiedMessage(parent, group, author, contentType, return new UnverifiedMessage(parent, group, author, contentType,
subject, timestamp, raw, authorSig, groupSig, bodyStart, subject, timestamp, raw, signature, bodyStart, body.length,
body.length, signedByAuthor, signedByGroup); signedLength);
} }
} }

View File

@@ -8,7 +8,6 @@ import net.sf.briar.api.crypto.KeyParser;
import net.sf.briar.api.crypto.MessageDigest; import net.sf.briar.api.crypto.MessageDigest;
import net.sf.briar.api.crypto.PublicKey; import net.sf.briar.api.crypto.PublicKey;
import net.sf.briar.api.crypto.Signature; import net.sf.briar.api.crypto.Signature;
import net.sf.briar.api.messaging.Group;
import net.sf.briar.api.messaging.Message; import net.sf.briar.api.messaging.Message;
import net.sf.briar.api.messaging.MessageId; import net.sf.briar.api.messaging.MessageId;
import net.sf.briar.api.messaging.MessageVerifier; import net.sf.briar.api.messaging.MessageVerifier;
@@ -31,7 +30,7 @@ class MessageVerifierImpl implements MessageVerifier {
throws GeneralSecurityException { throws GeneralSecurityException {
MessageDigest messageDigest = crypto.getMessageDigest(); MessageDigest messageDigest = crypto.getMessageDigest();
Signature signature = crypto.getSignature(); Signature signature = crypto.getSignature();
// Hash the message, including the signatures, to get the message ID // Hash the message, including the signature, to get the message ID
byte[] raw = m.getSerialised(); byte[] raw = m.getSerialised();
messageDigest.update(raw); messageDigest.update(raw);
MessageId id = new MessageId(messageDigest.digest()); MessageId id = new MessageId(messageDigest.digest());
@@ -40,20 +39,11 @@ class MessageVerifierImpl implements MessageVerifier {
if(author != null) { if(author != null) {
PublicKey k = keyParser.parsePublicKey(author.getPublicKey()); PublicKey k = keyParser.parsePublicKey(author.getPublicKey());
signature.initVerify(k); signature.initVerify(k);
signature.update(raw, 0, m.getLengthSignedByAuthor()); signature.update(raw, 0, m.getSignedLength());
if(!signature.verify(m.getAuthorSignature())) if(!signature.verify(m.getSignature()))
throw new GeneralSecurityException(); throw new GeneralSecurityException();
} }
// Verify the group's signature, if there is one return new MessageImpl(id, m.getParent(), m.getGroup(), author,
Group group = m.getGroup();
if(group != null && group.isRestricted()) {
PublicKey k = keyParser.parsePublicKey(group.getPublicKey());
signature.initVerify(k);
signature.update(raw, 0, m.getLengthSignedByGroup());
if(!signature.verify(m.getGroupSignature()))
throw new GeneralSecurityException();
}
return new MessageImpl(id, m.getParent(), group, author,
m.getContentType(), m.getSubject(), m.getTimestamp(), raw, m.getContentType(), m.getSubject(), m.getTimestamp(), raw,
m.getBodyStart(), m.getBodyLength()); m.getBodyStart(), m.getBodyLength());
} }

View File

@@ -133,8 +133,7 @@ class PacketWriterImpl implements PacketWriter {
for(Group g : u.getGroups()) { for(Group g : u.getGroups()) {
w.writeStructId(GROUP); w.writeStructId(GROUP);
w.writeString(g.getName()); w.writeString(g.getName());
if(g.isRestricted()) w.writeBytes(g.getPublicKey()); w.writeBytes(g.getSalt());
else w.writeNull();
} }
w.writeListEnd(); w.writeListEnd();
w.writeInt64(u.getVersion()); w.writeInt64(u.getVersion());

View File

@@ -68,8 +68,8 @@ public class ProtocolIntegrationTest extends BriarTestCase {
private final ContactId contactId; private final ContactId contactId;
private final byte[] secret; private final byte[] secret;
private final Author author; private final Author author;
private final Group group, group1; private final Group group;
private final Message message, message1, message2, message3; private final Message message, message1;
private final String authorName = "Alice"; private final String authorName = "Alice";
private final String contentType = "text/plain"; private final String contentType = "text/plain";
private final String messageBody = "Hello world"; private final String messageBody = "Hello world";
@@ -93,33 +93,23 @@ public class ProtocolIntegrationTest extends BriarTestCase {
// Create a shared secret // Create a shared secret
secret = new byte[32]; secret = new byte[32];
new Random().nextBytes(secret); new Random().nextBytes(secret);
// Create two groups: one restricted, one unrestricted // Create a group
GroupFactory groupFactory = i.getInstance(GroupFactory.class); GroupFactory groupFactory = i.getInstance(GroupFactory.class);
group = groupFactory.createGroup("Unrestricted group"); group = groupFactory.createGroup("Group");
CryptoComponent crypto = i.getInstance(CryptoComponent.class);
KeyPair groupKeyPair = crypto.generateSignatureKeyPair();
group1 = groupFactory.createGroup("Restricted group",
groupKeyPair.getPublic().getEncoded());
// Create an author // Create an author
AuthorFactory authorFactory = i.getInstance(AuthorFactory.class); AuthorFactory authorFactory = i.getInstance(AuthorFactory.class);
CryptoComponent crypto = i.getInstance(CryptoComponent.class);
KeyPair authorKeyPair = crypto.generateSignatureKeyPair(); KeyPair authorKeyPair = crypto.generateSignatureKeyPair();
author = authorFactory.createAuthor(authorName, author = authorFactory.createAuthor(authorName,
authorKeyPair.getPublic().getEncoded()); authorKeyPair.getPublic().getEncoded());
// Create two messages to each group: one anonymous, one pseudonymous // Create two messages to the group: one anonymous, one pseudonymous
MessageFactory messageFactory = i.getInstance(MessageFactory.class); MessageFactory messageFactory = i.getInstance(MessageFactory.class);
message = messageFactory.createAnonymousMessage(null, group, message = messageFactory.createAnonymousMessage(null, group,
contentType, messageBody.getBytes("UTF-8")); contentType, messageBody.getBytes("UTF-8"));
message1 = messageFactory.createAnonymousMessage(null, group1, message1 = messageFactory.createPseudonymousMessage(null, group,
groupKeyPair.getPrivate(), contentType,
messageBody.getBytes("UTF-8"));
message2 = messageFactory.createPseudonymousMessage(null, group,
author, authorKeyPair.getPrivate(), contentType, author, authorKeyPair.getPrivate(), contentType,
messageBody.getBytes("UTF-8")); messageBody.getBytes("UTF-8"));
message3 = messageFactory.createPseudonymousMessage(null, group1, messageIds = Arrays.asList(message.getId(), message1.getId());
groupKeyPair.getPrivate(), author, authorKeyPair.getPrivate(),
contentType, messageBody.getBytes("UTF-8"));
messageIds = Arrays.asList(message.getId(), message1.getId(),
message2.getId(), message3.getId());
// Create some transport properties // Create some transport properties
transportId = new TransportId(TestUtils.getRandomId()); transportId = new TransportId(TestUtils.getRandomId());
transportProperties = new TransportProperties(Collections.singletonMap( transportProperties = new TransportProperties(Collections.singletonMap(
@@ -145,18 +135,14 @@ public class ProtocolIntegrationTest extends BriarTestCase {
writer.writeMessage(message.getSerialised()); writer.writeMessage(message.getSerialised());
writer.writeMessage(message1.getSerialised()); writer.writeMessage(message1.getSerialised());
writer.writeMessage(message2.getSerialised());
writer.writeMessage(message3.getSerialised());
writer.writeOffer(new Offer(messageIds)); writer.writeOffer(new Offer(messageIds));
BitSet requested = new BitSet(4); BitSet requested = new BitSet(2);
requested.set(1); requested.set(1);
requested.set(3); writer.writeRequest(new Request(requested, 2));
writer.writeRequest(new Request(requested, 4));
SubscriptionUpdate su = new SubscriptionUpdate( SubscriptionUpdate su = new SubscriptionUpdate(Arrays.asList(group), 1);
Arrays.asList(group, group1), 1);
writer.writeSubscriptionUpdate(su); writer.writeSubscriptionUpdate(su);
TransportUpdate tu = new TransportUpdate(transportId, TransportUpdate tu = new TransportUpdate(transportId,
@@ -191,12 +177,7 @@ public class ProtocolIntegrationTest extends BriarTestCase {
assertTrue(reader.hasMessage()); assertTrue(reader.hasMessage());
m = reader.readMessage(); m = reader.readMessage();
checkMessageEquality(message1, messageVerifier.verifyMessage(m)); checkMessageEquality(message1, messageVerifier.verifyMessage(m));
assertTrue(reader.hasMessage()); assertFalse(reader.hasMessage());
m = reader.readMessage();
checkMessageEquality(message2, messageVerifier.verifyMessage(m));
assertTrue(reader.hasMessage());
m = reader.readMessage();
checkMessageEquality(message3, messageVerifier.verifyMessage(m));
// Read the offer // Read the offer
assertTrue(reader.hasOffer()); assertTrue(reader.hasOffer());
@@ -209,15 +190,13 @@ public class ProtocolIntegrationTest extends BriarTestCase {
BitSet requested = req.getBitmap(); BitSet requested = req.getBitmap();
assertFalse(requested.get(0)); assertFalse(requested.get(0));
assertTrue(requested.get(1)); assertTrue(requested.get(1));
assertFalse(requested.get(2));
assertTrue(requested.get(3));
// If there are any padding bits, they should all be zero // If there are any padding bits, they should all be zero
assertEquals(2, requested.cardinality()); assertEquals(1, requested.cardinality());
// Read the subscription update // Read the subscription update
assertTrue(reader.hasSubscriptionUpdate()); assertTrue(reader.hasSubscriptionUpdate());
SubscriptionUpdate su = reader.readSubscriptionUpdate(); SubscriptionUpdate su = reader.readSubscriptionUpdate();
assertEquals(Arrays.asList(group, group1), su.getGroups()); assertEquals(Arrays.asList(group), su.getGroups());
assertEquals(1, su.getVersion()); assertEquals(1, su.getVersion());
// Read the transport update // Read the transport update

View File

@@ -15,7 +15,7 @@ public class PasswordBasedKdfTest extends BriarTestCase {
public void testEncryptionAndDecryption() { public void testEncryptionAndDecryption() {
CryptoComponent crypto = new CryptoComponentImpl(); CryptoComponent crypto = new CryptoComponentImpl();
Random random = new Random(); Random random = new Random();
byte[] input = new byte[123]; byte[] input = new byte[1234];
random.nextBytes(input); random.nextBytes(input);
char[] password = "password".toCharArray(); char[] password = "password".toCharArray();
byte[] ciphertext = crypto.encryptWithPassword(input, password); byte[] ciphertext = crypto.encryptWithPassword(input, password);
@@ -27,7 +27,7 @@ public class PasswordBasedKdfTest extends BriarTestCase {
public void testInvalidCiphertextReturnsNull() { public void testInvalidCiphertextReturnsNull() {
CryptoComponent crypto = new CryptoComponentImpl(); CryptoComponent crypto = new CryptoComponentImpl();
Random random = new Random(); Random random = new Random();
byte[] input = new byte[123]; byte[] input = new byte[1234];
random.nextBytes(input); random.nextBytes(input);
char[] password = "password".toCharArray(); char[] password = "password".toCharArray();
byte[] ciphertext = crypto.encryptWithPassword(input, password); byte[] ciphertext = crypto.encryptWithPassword(input, password);

View File

@@ -1,5 +1,7 @@
package net.sf.briar.db; 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.api.messaging.Rating.GOOD; import static net.sf.briar.api.messaging.Rating.GOOD;
import static net.sf.briar.api.messaging.Rating.UNRATED; import static net.sf.briar.api.messaging.Rating.UNRATED;
@@ -59,8 +61,8 @@ import org.junit.Test;
public abstract class DatabaseComponentTest extends BriarTestCase { public abstract class DatabaseComponentTest extends BriarTestCase {
protected final Object txn = new Object(); protected final Object txn = new Object();
protected final GroupId groupId, restrictedGroupId; protected final GroupId groupId;
protected final Group group, restrictedGroup; protected final Group group;
protected final AuthorId authorId; protected final AuthorId authorId;
protected final Author author; protected final Author author;
protected final AuthorId localAuthorId; protected final AuthorId localAuthorId;
@@ -80,15 +82,12 @@ public abstract class DatabaseComponentTest extends BriarTestCase {
public DatabaseComponentTest() { public DatabaseComponentTest() {
groupId = new GroupId(TestUtils.getRandomId()); groupId = new GroupId(TestUtils.getRandomId());
restrictedGroupId = new GroupId(TestUtils.getRandomId()); group = new Group(groupId, "Group", new byte[GROUP_SALT_LENGTH]);
group = new Group(groupId, "Group name", null);
restrictedGroup = new Group(restrictedGroupId, "Restricted group name",
new byte[60]);
authorId = new AuthorId(TestUtils.getRandomId()); authorId = new AuthorId(TestUtils.getRandomId());
author = new Author(authorId, "Alice", new byte[60]); author = new Author(authorId, "Alice", new byte[MAX_PUBLIC_KEY_LENGTH]);
localAuthorId = new AuthorId(TestUtils.getRandomId()); localAuthorId = new AuthorId(TestUtils.getRandomId());
localAuthor = new LocalAuthor(localAuthorId, "Bob", new byte[60], localAuthor = new LocalAuthor(localAuthorId, "Bob",
new byte[60]); new byte[MAX_PUBLIC_KEY_LENGTH], new byte[100]);
messageId = new MessageId(TestUtils.getRandomId()); messageId = new MessageId(TestUtils.getRandomId());
messageId1 = new MessageId(TestUtils.getRandomId()); messageId1 = new MessageId(TestUtils.getRandomId());
privateMessageId = new MessageId(TestUtils.getRandomId()); privateMessageId = new MessageId(TestUtils.getRandomId());
@@ -142,7 +141,7 @@ public abstract class DatabaseComponentTest extends BriarTestCase {
// setRating(authorId, GOOD) // setRating(authorId, GOOD)
oneOf(database).setRating(txn, authorId, GOOD); oneOf(database).setRating(txn, authorId, GOOD);
will(returnValue(UNRATED)); will(returnValue(UNRATED));
oneOf(database).getUnrestrictedGroupMessages(txn, authorId); oneOf(database).getGroupMessages(txn, authorId);
will(returnValue(Collections.emptyList())); will(returnValue(Collections.emptyList()));
oneOf(listener).eventOccurred(with(any(RatingChangedEvent.class))); oneOf(listener).eventOccurred(with(any(RatingChangedEvent.class)));
// setRating(authorId, GOOD) again // setRating(authorId, GOOD) again
@@ -186,8 +185,6 @@ public abstract class DatabaseComponentTest extends BriarTestCase {
oneOf(database).getVisibility(txn, groupId); oneOf(database).getVisibility(txn, groupId);
will(returnValue(Collections.emptyList())); will(returnValue(Collections.emptyList()));
oneOf(database).removeSubscription(txn, groupId); oneOf(database).removeSubscription(txn, groupId);
oneOf(database).containsLocalGroup(txn, groupId);
will(returnValue(false));
oneOf(listener).eventOccurred(with(any( oneOf(listener).eventOccurred(with(any(
SubscriptionRemovedEvent.class))); SubscriptionRemovedEvent.class)));
oneOf(listener).eventOccurred(with(any( oneOf(listener).eventOccurred(with(any(
@@ -228,59 +225,6 @@ public abstract class DatabaseComponentTest extends BriarTestCase {
context.assertIsSatisfied(); context.assertIsSatisfied();
} }
@Test
public void testRestrictedGroupMessagesAreAlwaysSendable()
throws Exception {
final Message groupMessage = new TestMessage(messageId, null,
restrictedGroup, author, contentType, subject, timestamp, raw);
final Message groupMessage1 = new TestMessage(messageId1, null,
restrictedGroup, null, contentType, subject, timestamp, raw);
Mockery context = new Mockery();
@SuppressWarnings("unchecked")
final Database<Object> database = context.mock(Database.class);
final DatabaseCleaner cleaner = context.mock(DatabaseCleaner.class);
final ShutdownManager shutdown = context.mock(ShutdownManager.class);
context.checking(new Expectations() {{
// addLocalGroupMessage(groupMessage)
oneOf(database).startTransaction();
will(returnValue(txn));
oneOf(database).containsSubscription(txn, restrictedGroupId);
will(returnValue(true));
oneOf(database).addGroupMessage(txn, groupMessage, false);
will(returnValue(true));
oneOf(database).setReadFlag(txn, messageId, true);
oneOf(database).getContactIds(txn);
will(returnValue(Arrays.asList(contactId)));
oneOf(database).addStatus(txn, contactId, messageId, false);
oneOf(database).setSendability(txn, messageId, 1);
oneOf(database).commitTransaction(txn);
// receiveMessage(groupMessage1)
oneOf(database).startTransaction();
will(returnValue(txn));
oneOf(database).containsContact(txn, contactId);
will(returnValue(true));
oneOf(database).containsVisibleSubscription(txn, contactId,
restrictedGroupId);
will(returnValue(true));
oneOf(database).addGroupMessage(txn, groupMessage1, true);
will(returnValue(true));
oneOf(database).addStatus(txn, contactId, messageId1, true);
oneOf(database).getContactIds(txn);
will(returnValue(Arrays.asList(contactId)));
oneOf(database).setSendability(txn, messageId1, 1);
oneOf(database).addMessageToAck(txn, contactId, messageId1);
oneOf(database).commitTransaction(txn);
}});
DatabaseComponent db = createDatabaseComponent(database, cleaner,
shutdown);
db.addLocalGroupMessage(groupMessage);
db.receiveMessage(contactId, groupMessage1);
context.assertIsSatisfied();
}
@Test @Test
public void testNullParentStopsBackwardInclusion() throws Exception { public void testNullParentStopsBackwardInclusion() throws Exception {
Mockery context = new Mockery(); Mockery context = new Mockery();
@@ -295,7 +239,7 @@ public abstract class DatabaseComponentTest extends BriarTestCase {
oneOf(database).setRating(txn, authorId, GOOD); oneOf(database).setRating(txn, authorId, GOOD);
will(returnValue(UNRATED)); will(returnValue(UNRATED));
// The sendability of the author's messages should be incremented // The sendability of the author's messages should be incremented
oneOf(database).getUnrestrictedGroupMessages(txn, authorId); oneOf(database).getGroupMessages(txn, authorId);
will(returnValue(Arrays.asList(messageId))); will(returnValue(Arrays.asList(messageId)));
oneOf(database).getSendability(txn, messageId); oneOf(database).getSendability(txn, messageId);
will(returnValue(0)); will(returnValue(0));
@@ -327,7 +271,7 @@ public abstract class DatabaseComponentTest extends BriarTestCase {
oneOf(database).setRating(txn, authorId, GOOD); oneOf(database).setRating(txn, authorId, GOOD);
will(returnValue(UNRATED)); will(returnValue(UNRATED));
// The sendability of the author's messages should be incremented // The sendability of the author's messages should be incremented
oneOf(database).getUnrestrictedGroupMessages(txn, authorId); oneOf(database).getGroupMessages(txn, authorId);
will(returnValue(Arrays.asList(messageId))); will(returnValue(Arrays.asList(messageId)));
oneOf(database).getSendability(txn, messageId); oneOf(database).getSendability(txn, messageId);
will(returnValue(0)); will(returnValue(0));
@@ -364,7 +308,7 @@ public abstract class DatabaseComponentTest extends BriarTestCase {
oneOf(database).setRating(txn, authorId, GOOD); oneOf(database).setRating(txn, authorId, GOOD);
will(returnValue(UNRATED)); will(returnValue(UNRATED));
// The sendability of the author's messages should be incremented // The sendability of the author's messages should be incremented
oneOf(database).getUnrestrictedGroupMessages(txn, authorId); oneOf(database).getGroupMessages(txn, authorId);
will(returnValue(Arrays.asList(messageId))); will(returnValue(Arrays.asList(messageId)));
oneOf(database).getSendability(txn, messageId); oneOf(database).getSendability(txn, messageId);
will(returnValue(0)); will(returnValue(0));

View File

@@ -1,6 +1,8 @@
package net.sf.briar.db; package net.sf.briar.db;
import static java.util.concurrent.TimeUnit.SECONDS; import static java.util.concurrent.TimeUnit.SECONDS;
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.api.messaging.Rating.GOOD; import static net.sf.briar.api.messaging.Rating.GOOD;
import static net.sf.briar.api.messaging.Rating.UNRATED; import static net.sf.briar.api.messaging.Rating.UNRATED;
import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertArrayEquals;
@@ -71,12 +73,12 @@ public class H2DatabaseTest extends BriarTestCase {
public H2DatabaseTest() throws Exception { public H2DatabaseTest() throws Exception {
groupId = new GroupId(TestUtils.getRandomId()); groupId = new GroupId(TestUtils.getRandomId());
group = new Group(groupId, "Group name", null); group = new Group(groupId, "Group", new byte[GROUP_SALT_LENGTH]);
authorId = new AuthorId(TestUtils.getRandomId()); authorId = new AuthorId(TestUtils.getRandomId());
author = new Author(authorId, "Alice", new byte[60]); author = new Author(authorId, "Alice", new byte[MAX_PUBLIC_KEY_LENGTH]);
localAuthorId = new AuthorId(TestUtils.getRandomId()); localAuthorId = new AuthorId(TestUtils.getRandomId());
localAuthor = new LocalAuthor(localAuthorId, "Bob", new byte[60], localAuthor = new LocalAuthor(localAuthorId, "Bob",
new byte[60]); new byte[MAX_PUBLIC_KEY_LENGTH], new byte[100]);
messageId = new MessageId(TestUtils.getRandomId()); messageId = new MessageId(TestUtils.getRandomId());
messageId1 = new MessageId(TestUtils.getRandomId()); messageId1 = new MessageId(TestUtils.getRandomId());
contentType = "text/plain"; contentType = "text/plain";
@@ -535,38 +537,28 @@ public class H2DatabaseTest extends BriarTestCase {
} }
@Test @Test
public void testGetUnrestrictedGroupMessages() throws Exception { public void testGetGroupMessages() throws Exception {
AuthorId authorId1 = new AuthorId(TestUtils.getRandomId()); AuthorId authorId1 = new AuthorId(TestUtils.getRandomId());
Author author1 = new Author(authorId1, "Bob", new byte[60]); Author author1 = new Author(authorId1, "Bob",
new byte[MAX_PUBLIC_KEY_LENGTH]);
MessageId messageId1 = new MessageId(TestUtils.getRandomId()); MessageId messageId1 = new MessageId(TestUtils.getRandomId());
Message message1 = new TestMessage(messageId1, null, group, author1, Message message1 = new TestMessage(messageId1, null, group, author1,
contentType, subject, timestamp, raw); contentType, subject, timestamp, raw);
GroupId groupId1 = new GroupId(TestUtils.getRandomId());
Group group1 = new Group(groupId1, "Restricted group name",
new byte[60]);
MessageId messageId2 = new MessageId(TestUtils.getRandomId());
Message message2 = new TestMessage(messageId2, null, group1, author,
contentType, subject, timestamp, raw);
Database<Connection> db = open(false); Database<Connection> db = open(false);
Connection txn = db.startTransaction(); Connection txn = db.startTransaction();
// Subscribe to an unrestricted group and store two messages // Subscribe to a group and store two messages
db.addSubscription(txn, group); db.addSubscription(txn, group);
db.addGroupMessage(txn, message, false); db.addGroupMessage(txn, message, false);
db.addGroupMessage(txn, message1, false); db.addGroupMessage(txn, message1, false);
// Subscribe to a restricted group and store a message // Check that both messages are retrievable by their authors
db.addSubscription(txn, group1); Collection<MessageId> ids = db.getGroupMessages(txn, authorId);
db.addGroupMessage(txn, message2, false);
// Check that only the messages in the unrestricted group are retrieved
Collection<MessageId> ids = db.getUnrestrictedGroupMessages(txn,
authorId);
Iterator<MessageId> it = ids.iterator(); Iterator<MessageId> it = ids.iterator();
assertTrue(it.hasNext()); assertTrue(it.hasNext());
assertEquals(messageId, it.next()); assertEquals(messageId, it.next());
assertFalse(it.hasNext()); assertFalse(it.hasNext());
ids = db.getUnrestrictedGroupMessages(txn, authorId1); ids = db.getGroupMessages(txn, authorId1);
it = ids.iterator(); it = ids.iterator();
assertTrue(it.hasNext()); assertTrue(it.hasNext());
assertEquals(messageId1, it.next()); assertEquals(messageId1, it.next());
@@ -582,7 +574,8 @@ public class H2DatabaseTest extends BriarTestCase {
MessageId childId2 = new MessageId(TestUtils.getRandomId()); MessageId childId2 = new MessageId(TestUtils.getRandomId());
MessageId childId3 = new MessageId(TestUtils.getRandomId()); MessageId childId3 = new MessageId(TestUtils.getRandomId());
GroupId groupId1 = new GroupId(TestUtils.getRandomId()); GroupId groupId1 = new GroupId(TestUtils.getRandomId());
Group group1 = new Group(groupId1, "Group name", null); Group group1 = new Group(groupId1, "Another group",
new byte[GROUP_SALT_LENGTH]);
Message child1 = new TestMessage(childId1, messageId, group, author, Message child1 = new TestMessage(childId1, messageId, group, author,
contentType, subject, timestamp, raw); contentType, subject, timestamp, raw);
Message child2 = new TestMessage(childId2, messageId, group, author, Message child2 = new TestMessage(childId2, messageId, group, author,
@@ -1193,7 +1186,8 @@ public class H2DatabaseTest extends BriarTestCase {
public void testGetGroupMessageParentWithParentInAnotherGroup() public void testGetGroupMessageParentWithParentInAnotherGroup()
throws Exception { throws Exception {
GroupId groupId1 = new GroupId(TestUtils.getRandomId()); GroupId groupId1 = new GroupId(TestUtils.getRandomId());
Group group1 = new Group(groupId1, "Group name", null); Group group1 = new Group(groupId1, "Another group",
new byte[GROUP_SALT_LENGTH]);
Database<Connection> db = open(false); Database<Connection> db = open(false);
Connection txn = db.startTransaction(); Connection txn = db.startTransaction();
@@ -1446,7 +1440,8 @@ public class H2DatabaseTest extends BriarTestCase {
// Subscribe to a couple of groups // Subscribe to a couple of groups
db.addSubscription(txn, group); db.addSubscription(txn, group);
GroupId groupId1 = new GroupId(TestUtils.getRandomId()); GroupId groupId1 = new GroupId(TestUtils.getRandomId());
Group group1 = new Group(groupId1, "Group name", null); Group group1 = new Group(groupId1, "Another group",
new byte[GROUP_SALT_LENGTH]);
db.addSubscription(txn, group1); db.addSubscription(txn, group1);
// Store two messages in the first group // Store two messages in the first group
@@ -1499,7 +1494,8 @@ public class H2DatabaseTest extends BriarTestCase {
List<Group> groups = new ArrayList<Group>(); List<Group> groups = new ArrayList<Group>();
for(int i = 0; i < 100; i++) { for(int i = 0; i < 100; i++) {
GroupId id = new GroupId(TestUtils.getRandomId()); GroupId id = new GroupId(TestUtils.getRandomId());
groups.add(new Group(id, "Group name", null)); String name = "Group " + i;
groups.add(new Group(id, name, new byte[GROUP_SALT_LENGTH]));
} }
Database<Connection> db = open(false); Database<Connection> db = open(false);
@@ -1834,7 +1830,8 @@ public class H2DatabaseTest extends BriarTestCase {
public void testGetAvailableGroups() throws Exception { public void testGetAvailableGroups() throws Exception {
ContactId contactId1 = new ContactId(2); ContactId contactId1 = new ContactId(2);
AuthorId authorId1 = new AuthorId(TestUtils.getRandomId()); AuthorId authorId1 = new AuthorId(TestUtils.getRandomId());
Author author1 = new Author(authorId1, "Carol", new byte[60]); Author author1 = new Author(authorId1, "Carol",
new byte[MAX_PUBLIC_KEY_LENGTH]);
Database<Connection> db = open(false); Database<Connection> db = open(false);
Connection txn = db.startTransaction(); Connection txn = db.startTransaction();

View File

@@ -96,7 +96,7 @@ public class ConstantsTest extends BriarTestCase {
byte[] publicKey = keyPair.getPublic().getEncoded(); byte[] publicKey = keyPair.getPublic().getEncoded();
assertTrue(publicKey.length <= MAX_PUBLIC_KEY_LENGTH); assertTrue(publicKey.length <= MAX_PUBLIC_KEY_LENGTH);
// Sign some random data and check the length of the signature // Sign some random data and check the length of the signature
byte[] toBeSigned = new byte[1000]; byte[] toBeSigned = new byte[1234];
random.nextBytes(toBeSigned); random.nextBytes(toBeSigned);
sig.initSign(keyPair.getPrivate()); sig.initSign(keyPair.getPrivate());
sig.update(toBeSigned); sig.update(toBeSigned);
@@ -120,23 +120,19 @@ public class ConstantsTest extends BriarTestCase {
MessageId parent = new MessageId(TestUtils.getRandomId()); MessageId parent = new MessageId(TestUtils.getRandomId());
// Create a maximum-length group // Create a maximum-length group
String groupName = TestUtils.createRandomString(MAX_GROUP_NAME_LENGTH); String groupName = TestUtils.createRandomString(MAX_GROUP_NAME_LENGTH);
byte[] groupPublic = new byte[MAX_PUBLIC_KEY_LENGTH]; Group group = groupFactory.createGroup(groupName);
Group group = groupFactory.createGroup(groupName, groupPublic);
// Create a maximum-length author // Create a maximum-length author
String authorName = String authorName =
TestUtils.createRandomString(MAX_AUTHOR_NAME_LENGTH); TestUtils.createRandomString(MAX_AUTHOR_NAME_LENGTH);
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 = PrivateKey privateKey = crypto.generateSignatureKeyPair().getPrivate();
crypto.generateSignatureKeyPair().getPrivate();
PrivateKey authorPrivate =
crypto.generateSignatureKeyPair().getPrivate();
String contentType = String contentType =
TestUtils.createRandomString(MAX_CONTENT_TYPE_LENGTH); TestUtils.createRandomString(MAX_CONTENT_TYPE_LENGTH);
byte[] body = new byte[MAX_BODY_LENGTH]; byte[] body = new byte[MAX_BODY_LENGTH];
Message message = messageFactory.createPseudonymousMessage(parent, Message message = messageFactory.createPseudonymousMessage(parent,
group, groupPrivate, author, authorPrivate, contentType, body); group, author, privateKey, contentType, body);
// Check the size of the serialised message // Check the size of the serialised message
int length = message.getSerialised().length; int length = message.getSerialised().length;
assertTrue(length > UniqueId.LENGTH + MAX_GROUP_NAME_LENGTH assertTrue(length > UniqueId.LENGTH + MAX_GROUP_NAME_LENGTH
@@ -181,10 +177,8 @@ public class ConstantsTest extends BriarTestCase {
// Create the maximum number of maximum-length groups // Create the maximum number of maximum-length groups
Collection<Group> subs = new ArrayList<Group>(); Collection<Group> subs = new ArrayList<Group>();
for(int i = 0; i < MAX_SUBSCRIPTIONS; i++) { for(int i = 0; i < MAX_SUBSCRIPTIONS; i++) {
String groupName = String name = TestUtils.createRandomString(MAX_GROUP_NAME_LENGTH);
TestUtils.createRandomString(MAX_GROUP_NAME_LENGTH); subs.add(groupFactory.createGroup(name));
byte[] groupPublic = new byte[MAX_PUBLIC_KEY_LENGTH];
subs.add(groupFactory.createGroup(groupName, groupPublic));
} }
// Create a maximum-length subscription update // Create a maximum-length subscription update
SubscriptionUpdate u = new SubscriptionUpdate(subs, Long.MAX_VALUE); SubscriptionUpdate u = new SubscriptionUpdate(subs, Long.MAX_VALUE);

View File

@@ -1,5 +1,6 @@
package net.sf.briar.messaging.simplex; package net.sf.briar.messaging.simplex;
import static net.sf.briar.api.AuthorConstants.MAX_PUBLIC_KEY_LENGTH;
import static net.sf.briar.api.transport.TransportConstants.TAG_LENGTH; import static net.sf.briar.api.transport.TransportConstants.TAG_LENGTH;
import java.io.ByteArrayInputStream; import java.io.ByteArrayInputStream;
@@ -108,11 +109,12 @@ public class SimplexMessagingIntegrationTest extends BriarTestCase {
// Add a local pseudonym for Alice // Add a local pseudonym for Alice
AuthorId aliceId = new AuthorId(TestUtils.getRandomId()); AuthorId aliceId = new AuthorId(TestUtils.getRandomId());
LocalAuthor aliceAuthor = new LocalAuthor(aliceId, "Alice", LocalAuthor aliceAuthor = new LocalAuthor(aliceId, "Alice",
new byte[60], new byte[60]); new byte[MAX_PUBLIC_KEY_LENGTH], new byte[100]);
db.addLocalAuthor(aliceAuthor); db.addLocalAuthor(aliceAuthor);
// Add Bob as a contact // Add Bob as a contact
AuthorId bobId = new AuthorId(TestUtils.getRandomId()); AuthorId bobId = new AuthorId(TestUtils.getRandomId());
Author bobAuthor = new Author(bobId, "Bob", new byte[60]); Author bobAuthor = new Author(bobId, "Bob",
new byte[MAX_PUBLIC_KEY_LENGTH]);
ContactId contactId = db.addContact(bobAuthor, aliceId); ContactId contactId = db.addContact(bobAuthor, aliceId);
// Add the transport and the endpoint // Add the transport and the endpoint
db.addTransport(transportId, LATENCY); db.addTransport(transportId, LATENCY);
@@ -161,12 +163,13 @@ public class SimplexMessagingIntegrationTest extends BriarTestCase {
km.start(); km.start();
// Add a local pseudonym for Bob // Add a local pseudonym for Bob
AuthorId bobId = new AuthorId(TestUtils.getRandomId()); AuthorId bobId = new AuthorId(TestUtils.getRandomId());
LocalAuthor bobAuthor = new LocalAuthor(bobId, "Bob", new byte[60], LocalAuthor bobAuthor = new LocalAuthor(bobId, "Bob",
new byte[60]); new byte[MAX_PUBLIC_KEY_LENGTH], new byte[100]);
db.addLocalAuthor(bobAuthor); db.addLocalAuthor(bobAuthor);
// Add Alice as a contact // Add Alice as a contact
AuthorId aliceId = new AuthorId(TestUtils.getRandomId()); AuthorId aliceId = new AuthorId(TestUtils.getRandomId());
Author aliceAuthor = new Author(aliceId, "Alice", new byte[60]); Author aliceAuthor = new Author(aliceId, "Alice",
new byte[MAX_PUBLIC_KEY_LENGTH]);
ContactId contactId = db.addContact(aliceAuthor, bobId); ContactId contactId = db.addContact(aliceAuthor, bobId);
// Add the transport and the endpoint // Add the transport and the endpoint
db.addTransport(transportId, LATENCY); db.addTransport(transportId, LATENCY);

View File

@@ -263,13 +263,13 @@ public class RemovableDrivePluginTest extends BriarTestCase {
assertEquals(0, files[0].length()); assertEquals(0, files[0].length());
// Writing to the output stream should increase the size of the file // Writing to the output stream should increase the size of the file
OutputStream out = writer.getOutputStream(); OutputStream out = writer.getOutputStream();
out.write(new byte[123]); out.write(new byte[1234]);
out.flush(); out.flush();
out.close(); out.close();
// Disposing of the writer should not delete the file // Disposing of the writer should not delete the file
writer.dispose(false); writer.dispose(false);
assertTrue(files[0].exists()); assertTrue(files[0].exists());
assertEquals(123, files[0].length()); assertEquals(1234, files[0].length());
context.assertIsSatisfied(); context.assertIsSatisfied();
} }