Replaced private messages with private groups.

Private messages are now the same as group messages, but groups can be
private or public. When a contact is added, a private group is created
and designated as the inbox for exchanging private messages with the
contact.
This commit is contained in:
akwizgran
2013-12-19 21:53:26 +00:00
parent 1d4213e9c6
commit 0dc869228b
61 changed files with 1717 additions and 2329 deletions

View File

@@ -64,6 +64,7 @@ class CryptoComponentImpl implements CryptoComponent {
// Labels for secret derivation
private static final byte[] MASTER = { 'M', 'A', 'S', 'T', 'E', 'R', '\0' };
private static final byte[] SALT = { 'S', 'A', 'L', 'T', '\0' };
private static final byte[] FIRST = { 'F', 'I', 'R', 'S', 'T', '\0' };
private static final byte[] ROTATE = { 'R', 'O', 'T', 'A', 'T', 'E', '\0' };
// Label for confirmation code derivation
@@ -235,6 +236,14 @@ class CryptoComponentImpl implements CryptoComponent {
return agreement.calculateAgreement(ecPub).toByteArray();
}
public byte[] deriveGroupSalt(byte[] secret) {
if(secret.length != CIPHER_KEY_BYTES)
throw new IllegalArgumentException();
if(Arrays.equals(secret, BLANK_SECRET))
throw new IllegalArgumentException();
return counterModeKdf(secret, SALT, 0);
}
public byte[] deriveInitialSecret(byte[] secret, int transportIndex) {
if(secret.length != CIPHER_KEY_BYTES)
throw new IllegalArgumentException();

View File

@@ -13,8 +13,7 @@ import net.sf.briar.api.TransportConfig;
import net.sf.briar.api.TransportId;
import net.sf.briar.api.TransportProperties;
import net.sf.briar.api.db.DbException;
import net.sf.briar.api.db.GroupMessageHeader;
import net.sf.briar.api.db.PrivateMessageHeader;
import net.sf.briar.api.db.MessageHeader;
import net.sf.briar.api.messaging.Group;
import net.sf.briar.api.messaging.GroupId;
import net.sf.briar.api.messaging.GroupStatus;
@@ -79,8 +78,8 @@ interface Database<T> {
void commitTransaction(T txn) throws DbException;
/**
* Stores a contact with the given pseudonym, associated with the given
* local pseudonym, and returns an ID for the contact.
* Stores a contact associated with the given local and remote pseudonyms,
* and returns an ID for the contact.
* <p>
* Locking: contact write, message write, retention write,
* subscription write, transport write, window write.
@@ -96,13 +95,12 @@ interface Database<T> {
void addEndpoint(T txn, Endpoint ep) throws DbException;
/**
* Stores the given message, or returns false if the message is already in
* the database.
* Subscribes to a group, or returns false if the user already has the
* maximum number of subscriptions.
* <p>
* Locking: message write.
* Locking: message write, subscription write.
*/
boolean addGroupMessage(T txn, Message m, boolean incoming)
throws DbException;
boolean addGroup(T txn, Group g) throws DbException;
/**
* Stores a local pseudonym.
@@ -112,6 +110,13 @@ interface Database<T> {
*/
void addLocalAuthor(T txn, LocalAuthor a) throws DbException;
/**
* Stores a message.
* <p>
* Locking: message write.
*/
void addMessage(T txn, Message m, boolean incoming) throws DbException;
/**
* Records a received message as needing to be acknowledged.
* <p>
@@ -119,15 +124,6 @@ interface Database<T> {
*/
void addMessageToAck(T txn, ContactId c, MessageId m) throws DbException;
/**
* Stores the given message, or returns false if the message is already in
* the database.
* <p>
* Locking: message write.
*/
boolean addPrivateMessage(T txn, Message m, ContactId c, boolean incoming)
throws DbException;
/**
* Stores the given temporary secrets and deletes any secrets that have
* been made obsolete.
@@ -146,14 +142,6 @@ interface Database<T> {
void addStatus(T txn, ContactId c, MessageId m, boolean seen)
throws DbException;
/**
* Subscribes to the given group, or returns false if the user already has
* the maximum number of subscriptions.
* <p>
* Locking: subscription write.
*/
boolean addSubscription(T txn, Group g) throws DbException;
/**
* Stores a transport and returns true if the transport was not previously
* in the database.
@@ -184,6 +172,13 @@ interface Database<T> {
*/
boolean containsContact(T txn, ContactId c) throws DbException;
/**
* Returns true if the user subscribes to the given group.
* <p>
* Locking: subscription read.
*/
boolean containsGroup(T txn, GroupId g) throws DbException;
/**
* Returns true if the database contains the given local pseudonym.
* <p>
@@ -199,11 +194,11 @@ interface Database<T> {
boolean containsMessage(T txn, MessageId m) throws DbException;
/**
* Returns true if the user subscribes to the given group.
* Returns true if any messages are sendable to the given contact.
* <p>
* Locking: subscription read.
* Locking: message read, subscription read.
*/
boolean containsSubscription(T txn, GroupId g) throws DbException;
boolean containsSendableMessages(T txn, ContactId c) throws DbException;
/**
* Returns true if the database contains the given transport.
@@ -213,12 +208,12 @@ interface Database<T> {
boolean containsTransport(T txn, TransportId t) throws DbException;
/**
* Returns true if the user subscribes to the given group and the
* subscription is visible to the given contact.
* Returns true if the user subscribes to the given group and the group is
* visible to the given contact.
* <p>
* Locking: subscription read.
*/
boolean containsVisibleSubscription(T txn, ContactId c, GroupId g)
boolean containsVisibleGroup(T txn, ContactId c, GroupId g)
throws DbException;
/**
@@ -279,25 +274,34 @@ interface Database<T> {
/**
* Returns the group with the given ID, if the user subscribes to it.
* <p>
* Locking: subscription read.
*/
Group getGroup(T txn, GroupId g) throws DbException;
/**
* Returns the headers of all messages in the given group.
* Returns all groups to which the user subscribes.
* <p>
* Locking: message read.
* Locking: subscription read.
*/
Collection<GroupMessageHeader> getGroupMessageHeaders(T txn, GroupId g)
throws DbException;
Collection<Group> getGroups(T txn) throws DbException;
/**
* Returns the parent of the given group message, or null if either the
* message has no parent, or the parent is absent from the database, or the
* parent belongs to a different group.
* Returns the ID of the inbox group for the given contact, or null if no
* inbox group has been set.
* <p>
* Locking: message read.
* Locking: contact read, subscription read.
*/
MessageId getGroupMessageParent(T txn, MessageId m) throws DbException;
GroupId getInboxGroup(T txn, ContactId c) throws DbException;
/**
* Returns the headers of all messages in the inbox group for the given
* contact, or null if no inbox group has been set.
* <p>
* Locking: contact read, identity read, message read, subscription read.
*/
Collection<MessageHeader> getInboxMessageHeaders(T txn, ContactId c)
throws DbException;
/**
* Returns the time at which a connection to each contact was last opened
@@ -345,17 +349,16 @@ interface Database<T> {
byte[] getMessageBody(T txn, MessageId m) throws DbException;
/**
* Returns the headers of all private messages to or from the given
* contact.
* Returns the headers of all messages in the given group.
* <p>
* Locking: contact read, identity read, message read.
* Locking: message read.
*/
Collection<PrivateMessageHeader> getPrivateMessageHeaders(T txn,
ContactId c) throws DbException;
Collection<MessageHeader> getMessageHeaders(T txn, GroupId g)
throws DbException;
/**
* Returns the IDs of some messages received from the given contact that
* need to be acknowledged, up to the given number of messages.
* Returns the IDs of messages received from the given contact that need
* to be acknowledged, up to the given number of messages.
* <p>
* Locking: message read.
*/
@@ -371,6 +374,23 @@ interface Database<T> {
Collection<MessageId> getMessagesToOffer(T txn, ContactId c,
int maxMessages) throws DbException;
/**
* Returns the IDs of the oldest messages in the database, with a total
* size less than or equal to the given size.
* <p>
* Locking: message read.
*/
Collection<MessageId> getOldMessages(T txn, int size) throws DbException;
/**
* Returns the parent of the given message, or null if either the message
* has no parent, or the parent is absent from the database, or the parent
* belongs to a different group.
* <p>
* Locking: message read.
*/
MessageId getParent(T txn, MessageId m) throws DbException;
/**
* Returns the message identified by the given ID, in serialised form.
* <p>
@@ -388,14 +408,6 @@ interface Database<T> {
byte[] getRawMessageIfSendable(T txn, ContactId c, MessageId m)
throws DbException;
/**
* Returns the IDs of the oldest messages in the database, with a total
* size less than or equal to the given size.
* <p>
* Locking: message read.
*/
Collection<MessageId> getOldMessages(T txn, int size) throws DbException;
/**
* Returns true if the given message has been read.
* <p>
@@ -444,20 +456,6 @@ interface Database<T> {
Collection<MessageId> getSendableMessages(T txn, ContactId c, int maxLength)
throws DbException;
/**
* Returns the groups to which the user subscribes.
* <p>
* Locking: subscription read.
*/
Collection<Group> getSubscriptions(T txn) throws DbException;
/**
* Returns the groups to which the given contact subscribes.
* <p>
* Locking: subscription read.
*/
Collection<Group> getSubscriptions(T txn, ContactId c) throws DbException;
/**
* Returns a subscription ack for the given contact, or null if no ack is
* due.
@@ -518,27 +516,21 @@ interface Database<T> {
Map<GroupId, Integer> getUnreadMessageCounts(T txn) throws DbException;
/**
* Returns the contacts to which the given group is visible.
* Returns the IDs of all contacts to which the given group is visible.
* <p>
* Locking: subscription read.
*/
Collection<ContactId> getVisibility(T txn, GroupId g) throws DbException;
/**
* Returns the subscriptions that are visible to the given contact.
* Returns the IDs of all private groups that are visible to the given
* contact.
* <p>
* Locking: subscription read.
*/
Collection<GroupId> getVisibleSubscriptions(T txn, ContactId c)
Collection<GroupId> getVisiblePrivateGroups(T txn, ContactId c)
throws DbException;
/**
* Returns true if any messages are sendable to the given contact.
* <p>
* Locking: message read, subscription read.
*/
boolean hasSendableMessages(T txn, ContactId c) throws DbException;
/**
* Increments the outgoing connection counter for the given endpoint
* in the given rotation period and returns the old value, or -1 if the
@@ -576,7 +568,7 @@ interface Database<T> {
throws DbException;
/**
* Removes a contact (and all associated state) from the database.
* Removes a contact from the database.
* <p>
* Locking: contact write, message write, retention write,
* subscription write, transport write, window write.
@@ -584,8 +576,16 @@ interface Database<T> {
void removeContact(T txn, ContactId c) throws DbException;
/**
* Removes the local pseudonym with the given ID (and all associated
* state) from the database.
* Unsubscribes from a group. Any messages belonging to the group are
* deleted from the database.
* <p>
* Locking: message write, subscription write.
*/
void removeGroup(T txn, GroupId g) throws DbException;
/**
* Removes a local pseudonym (and all associated contacts) from the
* database.
* <p>
* Locking: contact write, identity write, message write, retention write,
* subscription write, transport write, window write.
@@ -608,23 +608,6 @@ interface Database<T> {
void removeMessagesToAck(T txn, ContactId c, Collection<MessageId> acked)
throws DbException;
/**
* Marks any of the given messages that are considered outstanding with
* respect to the given contact as seen by the contact.
* <p>
* Locking: message write.
*/
void removeOutstandingMessages(T txn, ContactId c,
Collection<MessageId> acked) throws DbException;
/**
* Unsubscribes from the given group. Any messages belonging to the group
* are deleted from the database.
* <p>
* Locking: message write, subscription write.
*/
void removeSubscription(T txn, GroupId g) throws DbException;
/**
* Removes a transport (and all associated state) from the database.
* <p>
@@ -648,6 +631,24 @@ interface Database<T> {
void setConnectionWindow(T txn, ContactId c, TransportId t, long period,
long centre, byte[] bitmap) throws DbException;
/**
* Updates the groups to which the given contact subscribes and returns
* true, unless an update with an equal or higher version number has
* already been received from the contact.
* <p>
* Locking: subscription write.
*/
boolean setGroups(T txn, ContactId c, Collection<Group> groups,
long version) throws DbException;
/**
* Makes a private group visible to the given contact, adds it to the
* contact's subscriptions, and sets it as the inbox group for the contact.
* <p>
* Locking: contact read, message write, subscription write.
*/
public void setInboxGroup(T txn, ContactId c, Group g) throws DbException;
/**
* Sets the time at which a connection to the given contact was last made.
* <p>
@@ -656,8 +657,8 @@ interface Database<T> {
void setLastConnected(T txn, ContactId c, long now) throws DbException;
/**
* Marks the given message read or unread and returns true if it was
* previously read.
* Marks a message read or unread and returns true if it was previously
* read.
* <p>
* Locking: message write.
*/
@@ -703,16 +704,6 @@ interface Database<T> {
boolean setStatusSeenIfVisible(T txn, ContactId c, MessageId m)
throws DbException;
/**
* Updates the groups to which the given contact subscribes and returns
* true, unless an update with an equal or higher version number has
* already been received from the contact.
* <p>
* Locking: subscription write.
*/
boolean setSubscriptions(T txn, ContactId c, Collection<Group> subs,
long version) throws DbException;
/**
* Records a retention ack from the given contact for the given version,
* unless the contact has already acked an equal or higher version.

View File

@@ -2,7 +2,6 @@ package net.sf.briar.db;
import static java.util.logging.Level.INFO;
import static java.util.logging.Level.WARNING;
import static net.sf.briar.api.transport.TransportConstants.MAX_CLOCK_DIFFERENCE;
import static net.sf.briar.db.DatabaseConstants.BYTES_PER_SWEEP;
import static net.sf.briar.db.DatabaseConstants.CRITICAL_FREE_SPACE;
import static net.sf.briar.db.DatabaseConstants.MAX_BYTES_BETWEEN_SPACE_CHECKS;
@@ -37,26 +36,24 @@ import net.sf.briar.api.db.AckAndRequest;
import net.sf.briar.api.db.ContactExistsException;
import net.sf.briar.api.db.DatabaseComponent;
import net.sf.briar.api.db.DbException;
import net.sf.briar.api.db.GroupMessageHeader;
import net.sf.briar.api.db.LocalAuthorExistsException;
import net.sf.briar.api.db.MessageHeader;
import net.sf.briar.api.db.NoSuchContactException;
import net.sf.briar.api.db.NoSuchLocalAuthorException;
import net.sf.briar.api.db.NoSuchMessageException;
import net.sf.briar.api.db.NoSuchSubscriptionException;
import net.sf.briar.api.db.NoSuchTransportException;
import net.sf.briar.api.db.PrivateMessageHeader;
import net.sf.briar.api.db.event.ContactAddedEvent;
import net.sf.briar.api.db.event.ContactRemovedEvent;
import net.sf.briar.api.db.event.DatabaseEvent;
import net.sf.briar.api.db.event.DatabaseListener;
import net.sf.briar.api.db.event.GroupMessageAddedEvent;
import net.sf.briar.api.db.event.LocalAuthorAddedEvent;
import net.sf.briar.api.db.event.LocalAuthorRemovedEvent;
import net.sf.briar.api.db.event.LocalSubscriptionsUpdatedEvent;
import net.sf.briar.api.db.event.LocalTransportsUpdatedEvent;
import net.sf.briar.api.db.event.MessageAddedEvent;
import net.sf.briar.api.db.event.MessageExpiredEvent;
import net.sf.briar.api.db.event.MessageReceivedEvent;
import net.sf.briar.api.db.event.PrivateMessageAddedEvent;
import net.sf.briar.api.db.event.RemoteRetentionTimeUpdatedEvent;
import net.sf.briar.api.db.event.RemoteSubscriptionsUpdatedEvent;
import net.sf.briar.api.db.event.RemoteTransportsUpdatedEvent;
@@ -274,6 +271,31 @@ DatabaseCleaner.Callback {
}
}
public boolean addGroup(Group g) throws DbException {
boolean added = false;
messageLock.writeLock().lock();
try {
subscriptionLock.writeLock().lock();
try {
T txn = db.startTransaction();
try {
if(!db.containsGroup(txn, g.getId()))
added = db.addGroup(txn, g);
db.commitTransaction(txn);
} catch(DbException e) {
db.abortTransaction(txn);
throw e;
}
} finally {
subscriptionLock.writeLock().unlock();
}
} finally {
messageLock.writeLock().unlock();
}
if(added) callListeners(new SubscriptionAddedEvent(g));
return added;
}
public void addLocalAuthor(LocalAuthor a) throws DbException {
contactLock.writeLock().lock();
try {
@@ -323,8 +345,8 @@ DatabaseCleaner.Callback {
callListeners(new LocalAuthorAddedEvent(a.getId()));
}
public void addLocalGroupMessage(Message m) throws DbException {
boolean added = false;
public void addLocalMessage(Message m) throws DbException {
boolean duplicate;
contactLock.readLock().lock();
try {
messageLock.writeLock().lock();
@@ -333,11 +355,12 @@ DatabaseCleaner.Callback {
try {
T txn = db.startTransaction();
try {
// Don't store the message if the user has
// unsubscribed from the group
GroupId g = m.getGroup().getId();
if(db.containsSubscription(txn, g))
added = storeGroupMessage(txn, m, null);
duplicate = db.containsMessage(txn, m.getId());
if(!duplicate) {
GroupId g = m.getGroup().getId();
if(db.containsGroup(txn, g))
addMessage(txn, m, null);
}
db.commitTransaction(txn);
} catch(DbException e) {
db.abortTransaction(txn);
@@ -352,91 +375,30 @@ DatabaseCleaner.Callback {
} finally {
contactLock.readLock().unlock();
}
if(added)
callListeners(new GroupMessageAddedEvent(m.getGroup(), false));
if(!duplicate)
callListeners(new MessageAddedEvent(m.getGroup(), null));
}
/**
* If the given message is already in the database, marks it as seen by the
* sender and returns false. Otherwise stores the message, marks it as seen
* by the sender and unseen by all other contacts, and returns true.
* Stores the given message, marks it as read if it was locally generated,
* otherwise marks it as seen by the sender, and marks it as unseen by all
* other contacts.
* <p>
* Locking: contact read, message write.
* @param sender is null for a locally generated message.
* Locking: contact read, message write, subscription read.
* @param sender null for a locally generated message.
*/
private boolean storeGroupMessage(T txn, Message m, ContactId sender)
private void addMessage(T txn, Message m, ContactId sender)
throws DbException {
if(m.getGroup() == null) throw new IllegalArgumentException();
boolean stored = db.addGroupMessage(txn, m, sender != null);
if(stored && sender == null) db.setReadFlag(txn, m.getId(), true);
// Mark the message as seen by the sender
db.addMessage(txn, m, sender != null);
MessageId id = m.getId();
if(sender != null) db.addStatus(txn, sender, id, true);
if(stored) {
// Mark the message as unseen by other contacts
for(ContactId c : db.getContactIds(txn))
if(!c.equals(sender)) db.addStatus(txn, c, id, false);
// Count the bytes stored
synchronized(spaceLock) {
bytesStoredSinceLastCheck += m.getSerialised().length;
}
} else {
if(LOG.isLoggable(INFO))
LOG.info("Duplicate group message not stored");
}
return stored;
}
public void addLocalPrivateMessage(Message m, ContactId c)
throws DbException {
boolean added;
contactLock.readLock().lock();
try {
messageLock.writeLock().lock();
try {
T txn = db.startTransaction();
try {
if(!db.containsContact(txn, c))
throw new NoSuchContactException();
added = storePrivateMessage(txn, m, c, false);
db.commitTransaction(txn);
} catch(DbException e) {
db.abortTransaction(txn);
throw e;
}
} finally {
messageLock.writeLock().unlock();
}
} finally {
contactLock.readLock().unlock();
}
if(added) callListeners(new PrivateMessageAddedEvent(c, false));
}
/**
* If the given message is already in the database, returns false.
* Otherwise stores the message and marks it as seen or unseen with respect
* to the given contact, depending on whether the message is incoming or
* outgoing, respectively.
* <p>
* Locking: message write.
*/
private boolean storePrivateMessage(T txn, Message m, ContactId c,
boolean incoming) throws DbException {
if(m.getGroup() != null) throw new IllegalArgumentException();
if(m.getAuthor() != null) throw new IllegalArgumentException();
if(!db.addPrivateMessage(txn, m, c, incoming)) {
if(LOG.isLoggable(INFO))
LOG.info("Duplicate private message not stored");
return false;
}
if(!incoming) db.setReadFlag(txn, m.getId(), true);
db.addStatus(txn, c, m.getId(), incoming);
if(sender == null) db.setReadFlag(txn, id, true);
else db.addStatus(txn, sender, id, true);
for(ContactId c : db.getContactIds(txn))
if(!c.equals(sender)) db.addStatus(txn, c, id, false);
// Count the bytes stored
synchronized(spaceLock) {
bytesStoredSinceLastCheck += m.getSerialised().length;
}
return true;
}
public void addSecrets(Collection<TemporarySecret> secrets)
@@ -500,6 +462,35 @@ DatabaseCleaner.Callback {
return added;
}
public boolean containsSendableMessages(ContactId c) throws DbException {
contactLock.readLock().lock();
try {
messageLock.readLock().lock();
try {
subscriptionLock.readLock().lock();
try {
T txn = db.startTransaction();
try {
if(!db.containsContact(txn, c))
throw new NoSuchContactException();
boolean has = db.containsSendableMessages(txn, c);
db.commitTransaction(txn);
return has;
} catch(DbException e) {
db.abortTransaction(txn);
throw e;
}
} finally {
subscriptionLock.readLock().unlock();
}
} finally {
messageLock.readLock().unlock();
}
} finally {
contactLock.readLock().unlock();
}
}
public Ack generateAck(ContactId c, int maxMessages) throws DbException {
Collection<MessageId> acked;
contactLock.readLock().lock();
@@ -924,7 +915,7 @@ DatabaseCleaner.Callback {
try {
T txn = db.startTransaction();
try {
if(!db.containsSubscription(txn, g))
if(!db.containsGroup(txn, g))
throw new NoSuchSubscriptionException();
Group group = db.getGroup(txn, g);
db.commitTransaction(txn);
@@ -938,20 +929,35 @@ DatabaseCleaner.Callback {
}
}
public Collection<GroupMessageHeader> getGroupMessageHeaders(GroupId g)
throws DbException {
messageLock.readLock().lock();
public Collection<Group> getGroups() throws DbException {
subscriptionLock.readLock().lock();
try {
T txn = db.startTransaction();
try {
Collection<Group> groups = db.getGroups(txn);
db.commitTransaction(txn);
return groups;
} catch(DbException e) {
db.abortTransaction(txn);
throw e;
}
} finally {
subscriptionLock.readLock().unlock();
}
}
public GroupId getInboxGroup(ContactId c) throws DbException {
contactLock.readLock().lock();
try {
subscriptionLock.readLock().lock();
try {
T txn = db.startTransaction();
try {
if(!db.containsSubscription(txn, g))
throw new NoSuchSubscriptionException();
Collection<GroupMessageHeader> headers =
db.getGroupMessageHeaders(txn, g);
if(!db.containsContact(txn, c))
throw new NoSuchContactException();
GroupId inbox = db.getInboxGroup(txn, c);
db.commitTransaction(txn);
return headers;
return inbox;
} catch(DbException e) {
db.abortTransaction(txn);
throw e;
@@ -960,7 +966,43 @@ DatabaseCleaner.Callback {
subscriptionLock.readLock().unlock();
}
} finally {
messageLock.readLock().unlock();
contactLock.readLock().unlock();
}
}
public Collection<MessageHeader> getInboxMessageHeaders(ContactId c)
throws DbException {
contactLock.readLock().lock();
try {
identityLock.readLock().lock();
try {
messageLock.readLock().lock();
try {
subscriptionLock.readLock().lock();
try {
T txn = db.startTransaction();
try {
if(!db.containsContact(txn, c))
throw new NoSuchContactException();
Collection<MessageHeader> headers =
db.getInboxMessageHeaders(txn, c);
db.commitTransaction(txn);
return headers;
} catch(DbException e) {
db.abortTransaction(txn);
throw e;
}
} finally {
subscriptionLock.readLock().unlock();
}
} finally {
messageLock.readLock().unlock();
}
} finally {
identityLock.readLock().unlock();
}
} finally {
contactLock.readLock().unlock();
}
}
@@ -1075,32 +1117,29 @@ DatabaseCleaner.Callback {
}
}
public Collection<PrivateMessageHeader> getPrivateMessageHeaders(
ContactId c) throws DbException {
contactLock.readLock().lock();
public Collection<MessageHeader> getMessageHeaders(GroupId g)
throws DbException {
messageLock.readLock().lock();
try {
identityLock.readLock().lock();
subscriptionLock.readLock().lock();
try {
messageLock.readLock().lock();
T txn = db.startTransaction();
try {
T txn = db.startTransaction();
try {
Collection<PrivateMessageHeader> headers =
db.getPrivateMessageHeaders(txn, c);
db.commitTransaction(txn);
return headers;
} catch(DbException e) {
db.abortTransaction(txn);
throw e;
}
} finally {
messageLock.readLock().unlock();
if(!db.containsGroup(txn, g))
throw new NoSuchSubscriptionException();
Collection<MessageHeader> headers =
db.getMessageHeaders(txn, g);
db.commitTransaction(txn);
return headers;
} catch(DbException e) {
db.abortTransaction(txn);
throw e;
}
} finally {
identityLock.readLock().unlock();
subscriptionLock.readLock().unlock();
}
} finally {
contactLock.readLock().unlock();
messageLock.readLock().unlock();
}
}
@@ -1159,23 +1198,6 @@ DatabaseCleaner.Callback {
}
}
public Collection<Group> getSubscriptions() throws DbException {
subscriptionLock.readLock().lock();
try {
T txn = db.startTransaction();
try {
Collection<Group> subs = db.getSubscriptions(txn);
db.commitTransaction(txn);
return subs;
} catch(DbException e) {
db.abortTransaction(txn);
throw e;
}
} finally {
subscriptionLock.readLock().unlock();
}
}
public Map<TransportId, Long> getTransportLatencies() throws DbException {
transportLock.readLock().lock();
try {
@@ -1216,7 +1238,7 @@ DatabaseCleaner.Callback {
try {
T txn = db.startTransaction();
try {
if(!db.containsSubscription(txn, g))
if(!db.containsGroup(txn, g))
throw new NoSuchSubscriptionException();
Collection<ContactId> visible = db.getVisibility(txn, g);
db.commitTransaction(txn);
@@ -1230,61 +1252,6 @@ DatabaseCleaner.Callback {
}
}
public Collection<GroupId> getVisibleSubscriptions(ContactId c)
throws DbException {
contactLock.readLock().lock();
try {
subscriptionLock.readLock().lock();
try {
T txn = db.startTransaction();
try {
if(!db.containsContact(txn, c))
throw new NoSuchContactException();
Collection<GroupId> visible =
db.getVisibleSubscriptions(txn, c);
db.commitTransaction(txn);
return visible;
} catch(DbException e) {
db.abortTransaction(txn);
throw e;
}
} finally {
subscriptionLock.readLock().unlock();
}
} finally {
contactLock.readLock().unlock();
}
}
public boolean hasSendableMessages(ContactId c) throws DbException {
contactLock.readLock().lock();
try {
messageLock.readLock().lock();
try {
subscriptionLock.readLock().lock();
try {
T txn = db.startTransaction();
try {
if(!db.containsContact(txn, c))
throw new NoSuchContactException();
boolean has = db.hasSendableMessages(txn, c);
db.commitTransaction(txn);
return has;
} catch(DbException e) {
db.abortTransaction(txn);
throw e;
}
} finally {
subscriptionLock.readLock().unlock();
}
} finally {
messageLock.readLock().unlock();
}
} finally {
contactLock.readLock().unlock();
}
}
public long incrementConnectionCounter(ContactId c, TransportId t,
long period) throws DbException {
contactLock.readLock().lock();
@@ -1374,7 +1341,8 @@ DatabaseCleaner.Callback {
try {
if(!db.containsContact(txn, c))
throw new NoSuchContactException();
db.removeOutstandingMessages(txn, c, a.getMessageIds());
for(MessageId m : a.getMessageIds())
db.setStatusSeenIfVisible(txn, c, m);
db.commitTransaction(txn);
} catch(DbException e) {
db.abortTransaction(txn);
@@ -1389,7 +1357,7 @@ DatabaseCleaner.Callback {
}
public void receiveMessage(ContactId c, Message m) throws DbException {
boolean added = false;
boolean duplicate, visible;
contactLock.readLock().lock();
try {
messageLock.writeLock().lock();
@@ -1400,8 +1368,11 @@ DatabaseCleaner.Callback {
try {
if(!db.containsContact(txn, c))
throw new NoSuchContactException();
added = storeMessage(txn, c, m);
db.addMessageToAck(txn, c, m.getId());
duplicate = db.containsMessage(txn, m.getId());
GroupId g = m.getGroup().getId();
visible = db.containsVisibleGroup(txn, c, g);
if(!duplicate && visible) addMessage(txn, m, c);
if(visible) db.addMessageToAck(txn, c, m.getId());
db.commitTransaction(txn);
} catch(DbException e) {
db.abortTransaction(txn);
@@ -1416,36 +1387,8 @@ DatabaseCleaner.Callback {
} finally {
contactLock.readLock().unlock();
}
callListeners(new MessageReceivedEvent(c));
if(added) {
Group g = m.getGroup();
if(g == null) callListeners(new PrivateMessageAddedEvent(c, true));
else callListeners(new GroupMessageAddedEvent(g, true));
}
}
/**
* Attempts to store a message received from the given contact, and returns
* true if it was stored.
* <p>
* Locking: contact read, message write, subscription read.
*/
private boolean storeMessage(T txn, ContactId c, Message m)
throws DbException {
long now = clock.currentTimeMillis();
if(m.getTimestamp() > now + MAX_CLOCK_DIFFERENCE) {
if(LOG.isLoggable(INFO))
LOG.info("Discarding message with future timestamp");
return false;
}
Group g = m.getGroup();
if(g == null) return storePrivateMessage(txn, m, c, true);
if(!db.containsVisibleSubscription(txn, c, g.getId())) {
if(LOG.isLoggable(INFO))
LOG.info("Discarding message without visible subscription");
return false;
}
return storeGroupMessage(txn, m, c);
if(visible) callListeners(new MessageReceivedEvent(c));
if(!duplicate) callListeners(new MessageAddedEvent(m.getGroup(), c));
}
public AckAndRequest receiveOffer(ContactId c, Offer o) throws DbException {
@@ -1573,7 +1516,7 @@ DatabaseCleaner.Callback {
throw new NoSuchContactException();
Collection<Group> groups = u.getGroups();
long version = u.getVersion();
updated = db.setSubscriptions(txn, c, groups, version);
updated = db.setGroups(txn, c, groups, version);
db.commitTransaction(txn);
} catch(DbException e) {
db.abortTransaction(txn);
@@ -1689,6 +1632,34 @@ DatabaseCleaner.Callback {
callListeners(new ContactRemovedEvent(c));
}
public void removeGroup(Group g) throws DbException {
Collection<ContactId> affected;
messageLock.writeLock().lock();
try {
subscriptionLock.writeLock().lock();
try {
T txn = db.startTransaction();
try {
GroupId id = g.getId();
if(!db.containsGroup(txn, id))
throw new NoSuchSubscriptionException();
affected = db.getVisibility(txn, id);
db.removeGroup(txn, id);
db.commitTransaction(txn);
} catch(DbException e) {
db.abortTransaction(txn);
throw e;
}
} finally {
subscriptionLock.writeLock().unlock();
}
} finally {
messageLock.writeLock().unlock();
}
callListeners(new SubscriptionRemovedEvent(g));
callListeners(new LocalSubscriptionsUpdatedEvent(affected));
}
public void removeLocalAuthor(AuthorId a) throws DbException {
Collection<ContactId> affected;
contactLock.writeLock().lock();
@@ -1798,6 +1769,35 @@ DatabaseCleaner.Callback {
}
}
public void setInboxGroup(ContactId c, Group g) throws DbException {
if(!g.isPrivate()) throw new IllegalArgumentException();
contactLock.readLock().lock();
try {
messageLock.writeLock().lock();
try {
subscriptionLock.writeLock().lock();
try {
T txn = db.startTransaction();
try {
if(!db.containsContact(txn, c))
throw new NoSuchContactException();
db.setInboxGroup(txn, c, g);
db.commitTransaction(txn);
} catch(DbException e) {
db.abortTransaction(txn);
throw e;
}
} finally {
subscriptionLock.writeLock().unlock();
}
} finally {
messageLock.writeLock().unlock();
}
} finally {
contactLock.readLock().unlock();
}
}
public boolean setReadFlag(MessageId m, boolean read) throws DbException {
messageLock.writeLock().lock();
try {
@@ -1880,7 +1880,7 @@ DatabaseCleaner.Callback {
try {
T txn = db.startTransaction();
try {
if(!db.containsSubscription(txn, g))
if(!db.containsGroup(txn, g))
throw new NoSuchSubscriptionException();
// Use HashSets for O(1) lookups, O(n) overall running time
HashSet<ContactId> now = new HashSet<ContactId>(visible);
@@ -1923,7 +1923,7 @@ DatabaseCleaner.Callback {
try {
T txn = db.startTransaction();
try {
if(!db.containsSubscription(txn, g))
if(!db.containsGroup(txn, g))
throw new NoSuchSubscriptionException();
// Make the group visible or invisible to future contacts
db.setVisibleToAll(txn, g, all);
@@ -1953,59 +1953,6 @@ DatabaseCleaner.Callback {
callListeners(new LocalSubscriptionsUpdatedEvent(affected));
}
public boolean subscribe(Group g) throws DbException {
boolean added = false;
subscriptionLock.writeLock().lock();
try {
T txn = db.startTransaction();
try {
if(!db.containsSubscription(txn, g.getId()))
added = db.addSubscription(txn, g);
db.commitTransaction(txn);
} catch(DbException e) {
db.abortTransaction(txn);
throw e;
}
} finally {
subscriptionLock.writeLock().unlock();
}
if(added) callListeners(new SubscriptionAddedEvent(g));
return added;
}
public void unsubscribe(Group g) throws DbException {
Collection<ContactId> affected;
identityLock.writeLock().lock();
try {
messageLock.writeLock().lock();
try {
subscriptionLock.writeLock().lock();
try {
T txn = db.startTransaction();
try {
GroupId id = g.getId();
if(!db.containsSubscription(txn, id))
throw new NoSuchSubscriptionException();
affected = db.getVisibility(txn, id);
db.removeSubscription(txn, id);
db.commitTransaction(txn);
} catch(DbException e) {
db.abortTransaction(txn);
throw e;
}
} finally {
subscriptionLock.writeLock().unlock();
}
} finally {
messageLock.writeLock().unlock();
}
} finally {
identityLock.writeLock().unlock();
}
callListeners(new SubscriptionRemovedEvent(g));
callListeners(new LocalSubscriptionsUpdatedEvent(affected));
}
public void checkFreeSpaceAndClean() throws DbException {
long freeSpace = db.getFreeSpace();
if(LOG.isLoggable(INFO)) LOG.info(freeSpace + " bytes free space");

File diff suppressed because it is too large Load Diff

View File

@@ -21,6 +21,7 @@ import net.sf.briar.api.crypto.KeyManager;
import net.sf.briar.api.crypto.PseudoRandom;
import net.sf.briar.api.db.DatabaseComponent;
import net.sf.briar.api.db.DbException;
import net.sf.briar.api.messaging.GroupFactory;
import net.sf.briar.api.plugins.duplex.DuplexPlugin;
import net.sf.briar.api.plugins.duplex.DuplexTransportConnection;
import net.sf.briar.api.serial.Reader;
@@ -43,15 +44,16 @@ class AliceConnector extends Connector {
ReaderFactory readerFactory, WriterFactory writerFactory,
ConnectionReaderFactory connectionReaderFactory,
ConnectionWriterFactory connectionWriterFactory,
AuthorFactory authorFactory, KeyManager keyManager,
ConnectionDispatcher connectionDispatcher, Clock clock,
ConnectorGroup group, DuplexPlugin plugin, LocalAuthor localAuthor,
AuthorFactory authorFactory, GroupFactory groupFactory,
KeyManager keyManager, ConnectionDispatcher connectionDispatcher,
Clock clock, ConnectorGroup group, DuplexPlugin plugin,
LocalAuthor localAuthor,
Map<TransportId, TransportProperties> localProps,
PseudoRandom random) {
super(crypto, db, readerFactory, writerFactory, connectionReaderFactory,
connectionWriterFactory, authorFactory, keyManager,
connectionDispatcher, clock, group, plugin, localAuthor,
localProps, random);
connectionWriterFactory, authorFactory, groupFactory,
keyManager, connectionDispatcher, clock, group, plugin,
localAuthor, localProps, random);
}
@Override

View File

@@ -21,6 +21,7 @@ import net.sf.briar.api.crypto.KeyManager;
import net.sf.briar.api.crypto.PseudoRandom;
import net.sf.briar.api.db.DatabaseComponent;
import net.sf.briar.api.db.DbException;
import net.sf.briar.api.messaging.GroupFactory;
import net.sf.briar.api.plugins.duplex.DuplexPlugin;
import net.sf.briar.api.plugins.duplex.DuplexTransportConnection;
import net.sf.briar.api.serial.Reader;
@@ -43,15 +44,16 @@ class BobConnector extends Connector {
ReaderFactory readerFactory, WriterFactory writerFactory,
ConnectionReaderFactory connectionReaderFactory,
ConnectionWriterFactory connectionWriterFactory,
AuthorFactory authorFactory, KeyManager keyManager,
ConnectionDispatcher connectionDispatcher, Clock clock,
ConnectorGroup group, DuplexPlugin plugin, LocalAuthor localAuthor,
AuthorFactory authorFactory, GroupFactory groupFactory,
KeyManager keyManager, ConnectionDispatcher connectionDispatcher,
Clock clock, ConnectorGroup group, DuplexPlugin plugin,
LocalAuthor localAuthor,
Map<TransportId, TransportProperties> localProps,
PseudoRandom random) {
super(crypto, db, readerFactory, writerFactory, connectionReaderFactory,
connectionWriterFactory, authorFactory, keyManager,
connectionDispatcher, clock, group, plugin, localAuthor,
localProps, random);
connectionWriterFactory, authorFactory, groupFactory,
keyManager, connectionDispatcher, clock, group, plugin,
localAuthor, localProps, random);
}
@Override

View File

@@ -41,6 +41,8 @@ import net.sf.briar.api.crypto.Signature;
import net.sf.briar.api.db.DatabaseComponent;
import net.sf.briar.api.db.DbException;
import net.sf.briar.api.db.NoSuchTransportException;
import net.sf.briar.api.messaging.Group;
import net.sf.briar.api.messaging.GroupFactory;
import net.sf.briar.api.plugins.duplex.DuplexPlugin;
import net.sf.briar.api.plugins.duplex.DuplexTransportConnection;
import net.sf.briar.api.serial.Reader;
@@ -64,6 +66,7 @@ abstract class Connector extends Thread {
protected final ConnectionReaderFactory connectionReaderFactory;
protected final ConnectionWriterFactory connectionWriterFactory;
protected final AuthorFactory authorFactory;
protected final GroupFactory groupFactory;
protected final KeyManager keyManager;
protected final ConnectionDispatcher connectionDispatcher;
protected final Clock clock;
@@ -84,9 +87,10 @@ abstract class Connector extends Thread {
ReaderFactory readerFactory, WriterFactory writerFactory,
ConnectionReaderFactory connectionReaderFactory,
ConnectionWriterFactory connectionWriterFactory,
AuthorFactory authorFactory, KeyManager keyManager,
ConnectionDispatcher connectionDispatcher, Clock clock,
ConnectorGroup group, DuplexPlugin plugin, LocalAuthor localAuthor,
AuthorFactory authorFactory, GroupFactory groupFactory,
KeyManager keyManager, ConnectionDispatcher connectionDispatcher,
Clock clock, ConnectorGroup group, DuplexPlugin plugin,
LocalAuthor localAuthor,
Map<TransportId, TransportProperties> localProps,
PseudoRandom random) {
super("Connector");
@@ -97,6 +101,7 @@ abstract class Connector extends Thread {
this.connectionReaderFactory = connectionReaderFactory;
this.connectionWriterFactory = connectionWriterFactory;
this.authorFactory = authorFactory;
this.groupFactory = groupFactory;
this.keyManager = keyManager;
this.connectionDispatcher = connectionDispatcher;
this.clock = clock;
@@ -267,6 +272,11 @@ abstract class Connector extends Thread {
long epoch, boolean alice) throws DbException {
// Add the contact to the database
contactId = db.addContact(remoteAuthor, localAuthor.getId());
// Create and store the inbox group
byte[] salt = crypto.deriveGroupSalt(secret);
Group inbox = groupFactory.createGroup("Inbox", salt, true);
db.addGroup(inbox);
db.setInboxGroup(contactId, inbox);
// Store the remote transport properties
db.setRemoteProperties(contactId, remoteProps);
// Create an endpoint for each transport shared with the contact

View File

@@ -27,6 +27,7 @@ import net.sf.briar.api.db.DbException;
import net.sf.briar.api.invitation.InvitationListener;
import net.sf.briar.api.invitation.InvitationState;
import net.sf.briar.api.invitation.InvitationTask;
import net.sf.briar.api.messaging.GroupFactory;
import net.sf.briar.api.plugins.PluginManager;
import net.sf.briar.api.plugins.duplex.DuplexPlugin;
import net.sf.briar.api.serial.ReaderFactory;
@@ -48,6 +49,7 @@ class ConnectorGroup extends Thread implements InvitationTask {
private final ConnectionReaderFactory connectionReaderFactory;
private final ConnectionWriterFactory connectionWriterFactory;
private final AuthorFactory authorFactory;
private final GroupFactory groupFactory;
private final KeyManager keyManager;
private final ConnectionDispatcher connectionDispatcher;
private final Clock clock;
@@ -74,9 +76,9 @@ class ConnectorGroup extends Thread implements InvitationTask {
ReaderFactory readerFactory, WriterFactory writerFactory,
ConnectionReaderFactory connectionReaderFactory,
ConnectionWriterFactory connectionWriterFactory,
AuthorFactory authorFactory, KeyManager keyManager,
ConnectionDispatcher connectionDispatcher, Clock clock,
PluginManager pluginManager, AuthorId localAuthorId,
AuthorFactory authorFactory, GroupFactory groupFactory,
KeyManager keyManager, ConnectionDispatcher connectionDispatcher,
Clock clock, PluginManager pluginManager, AuthorId localAuthorId,
int localInvitationCode, int remoteInvitationCode) {
super("ConnectorGroup");
this.crypto = crypto;
@@ -86,6 +88,7 @@ class ConnectorGroup extends Thread implements InvitationTask {
this.connectionReaderFactory = connectionReaderFactory;
this.connectionWriterFactory = connectionWriterFactory;
this.authorFactory = authorFactory;
this.groupFactory = groupFactory;
this.keyManager = keyManager;
this.connectionDispatcher = connectionDispatcher;
this.clock = clock;
@@ -171,8 +174,8 @@ class ConnectorGroup extends Thread implements InvitationTask {
remoteInvitationCode);
return new AliceConnector(crypto, db, readerFactory, writerFactory,
connectionReaderFactory, connectionWriterFactory, authorFactory,
keyManager, connectionDispatcher, clock, this, plugin,
localAuthor, localProps, random);
groupFactory, keyManager, connectionDispatcher, clock, this,
plugin, localAuthor, localProps, random);
}
private Connector createBobConnector(DuplexPlugin plugin,
@@ -182,8 +185,8 @@ class ConnectorGroup extends Thread implements InvitationTask {
localInvitationCode);
return new BobConnector(crypto, db, readerFactory, writerFactory,
connectionReaderFactory, connectionWriterFactory, authorFactory,
keyManager, connectionDispatcher, clock, this, plugin,
localAuthor, localProps, random);
groupFactory, keyManager, connectionDispatcher, clock, this,
plugin, localAuthor, localProps, random);
}
public void localConfirmationSucceeded() {

View File

@@ -10,6 +10,7 @@ import net.sf.briar.api.crypto.KeyManager;
import net.sf.briar.api.db.DatabaseComponent;
import net.sf.briar.api.invitation.InvitationTask;
import net.sf.briar.api.invitation.InvitationTaskFactory;
import net.sf.briar.api.messaging.GroupFactory;
import net.sf.briar.api.plugins.PluginManager;
import net.sf.briar.api.serial.ReaderFactory;
import net.sf.briar.api.serial.WriterFactory;
@@ -26,6 +27,7 @@ class InvitationTaskFactoryImpl implements InvitationTaskFactory {
private final ConnectionReaderFactory connectionReaderFactory;
private final ConnectionWriterFactory connectionWriterFactory;
private final AuthorFactory authorFactory;
private final GroupFactory groupFactory;
private final KeyManager keyManager;
private final ConnectionDispatcher connectionDispatcher;
private final Clock clock;
@@ -36,9 +38,9 @@ class InvitationTaskFactoryImpl implements InvitationTaskFactory {
ReaderFactory readerFactory, WriterFactory writerFactory,
ConnectionReaderFactory connectionReaderFactory,
ConnectionWriterFactory connectionWriterFactory,
AuthorFactory authorFactory, KeyManager keyManager,
ConnectionDispatcher connectionDispatcher, Clock clock,
PluginManager pluginManager) {
AuthorFactory authorFactory, GroupFactory groupFactory,
KeyManager keyManager, ConnectionDispatcher connectionDispatcher,
Clock clock, PluginManager pluginManager) {
this.crypto = crypto;
this.db = db;
this.readerFactory = readerFactory;
@@ -46,6 +48,7 @@ class InvitationTaskFactoryImpl implements InvitationTaskFactory {
this.connectionReaderFactory = connectionReaderFactory;
this.connectionWriterFactory = connectionWriterFactory;
this.authorFactory = authorFactory;
this.groupFactory = groupFactory;
this.keyManager = keyManager;
this.connectionDispatcher = connectionDispatcher;
this.clock = clock;
@@ -56,7 +59,7 @@ class InvitationTaskFactoryImpl implements InvitationTaskFactory {
int remoteCode) {
return new ConnectorGroup(crypto, db, readerFactory, writerFactory,
connectionReaderFactory, connectionWriterFactory,
authorFactory, keyManager, connectionDispatcher, clock,
pluginManager, localAuthorId, localCode, remoteCode);
authorFactory, groupFactory, keyManager, connectionDispatcher,
clock, pluginManager, localAuthorId, localCode, remoteCode);
}
}

View File

@@ -27,24 +27,28 @@ class AuthorFactoryImpl implements AuthorFactory {
this.writerFactory = writerFactory;
}
public Author createAuthor(String name, byte[] publicKey)
throws IOException {
public Author createAuthor(String name, byte[] publicKey) {
return new Author(getId(name, publicKey), name, publicKey);
}
public LocalAuthor createLocalAuthor(String name, byte[] publicKey,
byte[] privateKey) throws IOException {
byte[] privateKey) {
return new LocalAuthor(getId(name, publicKey), name, publicKey,
privateKey);
}
private AuthorId getId(String name, byte[] publicKey) throws IOException {
private AuthorId getId(String name, byte[] publicKey) {
ByteArrayOutputStream out = new ByteArrayOutputStream();
Writer w = writerFactory.createWriter(out);
w.writeStructStart(AUTHOR);
w.writeString(name);
w.writeBytes(publicKey);
w.writeStructEnd();
try {
w.writeStructStart(AUTHOR);
w.writeString(name);
w.writeBytes(publicKey);
w.writeStructEnd();
} catch(IOException e) {
// Shouldn't happen with ByteArrayOutputStream
throw new RuntimeException();
}
MessageDigest messageDigest = crypto.getMessageDigest();
messageDigest.update(out.toByteArray());
return new AuthorId(messageDigest.digest());

View File

@@ -27,22 +27,28 @@ class GroupFactoryImpl implements GroupFactory {
this.writerFactory = writerFactory;
}
public Group createGroup(String name) throws IOException {
public Group createGroup(String name, boolean isPrivate) {
byte[] salt = new byte[GROUP_SALT_LENGTH];
crypto.getSecureRandom().nextBytes(salt);
return createGroup(name, salt);
return createGroup(name, salt, isPrivate);
}
public Group createGroup(String name, byte[] salt) throws IOException {
public Group createGroup(String name, byte[] salt, boolean isPrivate) {
ByteArrayOutputStream out = new ByteArrayOutputStream();
Writer w = writerFactory.createWriter(out);
w.writeStructStart(GROUP);
w.writeString(name);
w.writeBytes(salt);
w.writeStructEnd();
try {
w.writeStructStart(GROUP);
w.writeString(name);
w.writeBytes(salt);
w.writeBoolean(isPrivate);
w.writeStructEnd();
} catch(IOException e) {
// Shouldn't happen with ByteArrayOutputStream
throw new RuntimeException();
}
MessageDigest messageDigest = crypto.getMessageDigest();
messageDigest.update(out.toByteArray());
GroupId id = new GroupId(messageDigest.digest());
return new Group(id, name, salt);
return new Group(id, name, salt, isPrivate);
}
}

View File

@@ -31,10 +31,11 @@ class GroupReader implements StructReader<Group> {
byte[] publicKey = null;
if(r.hasNull()) r.readNull();
else publicKey = r.readBytes(MAX_PUBLIC_KEY_LENGTH);
boolean isPrivate = r.readBoolean();
r.readStructEnd();
r.removeConsumer(digesting);
// Build and return the group
GroupId id = new GroupId(messageDigest.digest());
return new Group(id, name, publicKey);
return new Group(id, name, publicKey, isPrivate);
}
}

View File

@@ -47,13 +47,6 @@ class MessageFactoryImpl implements MessageFactory {
this.writerFactory = writerFactory;
}
public Message createPrivateMessage(MessageId parent, String contentType,
long timestamp, byte[] body) throws IOException,
GeneralSecurityException {
return createMessage(parent, null, null, null, contentType, timestamp,
body);
}
public Message createAnonymousMessage(MessageId parent, Group group,
String contentType, long timestamp, byte[] body) throws IOException,
GeneralSecurityException {
@@ -97,8 +90,7 @@ class MessageFactoryImpl implements MessageFactory {
w.writeStructStart(MESSAGE);
if(parent == null) w.writeNull();
else w.writeBytes(parent.getBytes());
if(group == null) w.writeNull();
else writeGroup(w, group);
writeGroup(w, group);
if(author == null) w.writeNull();
else writeAuthor(w, author);
w.writeString(contentType);
@@ -130,6 +122,7 @@ class MessageFactoryImpl implements MessageFactory {
w.writeStructStart(GROUP);
w.writeString(g.getName());
w.writeBytes(g.getSalt());
w.writeBoolean(g.isPrivate());
w.writeStructEnd();
}

View File

@@ -4,14 +4,10 @@ import static net.sf.briar.api.AuthorConstants.MAX_SIGNATURE_LENGTH;
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_PACKET_LENGTH;
import static net.sf.briar.api.messaging.MessagingConstants.MAX_SUBJECT_LENGTH;
import static net.sf.briar.api.messaging.MessagingConstants.MESSAGE_SALT_LENGTH;
import static net.sf.briar.api.messaging.Types.MESSAGE;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.charset.Charset;
import java.nio.charset.CharsetDecoder;
import net.sf.briar.api.Author;
import net.sf.briar.api.FormatException;
@@ -28,13 +24,11 @@ class MessageReader implements StructReader<UnverifiedMessage> {
private final StructReader<Group> groupReader;
private final StructReader<Author> authorReader;
private final CharsetDecoder decoder;
MessageReader(StructReader<Group> groupReader,
StructReader<Author> authorReader) {
this.groupReader = groupReader;
this.authorReader = authorReader;
decoder = Charset.forName("UTF-8").newDecoder();
}
public UnverifiedMessage readStruct(Reader r) throws IOException {
@@ -53,10 +47,8 @@ class MessageReader implements StructReader<UnverifiedMessage> {
if(b.length < UniqueId.LENGTH) throw new FormatException();
parent = new MessageId(b);
}
// Read the group, if there is one
Group group = null;
if(r.hasNull()) r.readNull();
else group = groupReader.readStruct(r);
// Read the group
Group group = groupReader.readStruct(r);
// Read the author, if there is one
Author author = null;
if(r.hasNull()) r.readNull();
@@ -71,16 +63,6 @@ class MessageReader implements StructReader<UnverifiedMessage> {
if(salt.length < MESSAGE_SALT_LENGTH) throw new FormatException();
// Read the message body
byte[] body = r.readBytes(MAX_BODY_LENGTH);
// If the content type is text/plain, extract a subject line
String subject;
if(contentType.equals("text/plain")) {
byte[] start = new byte[Math.min(MAX_SUBJECT_LENGTH, body.length)];
System.arraycopy(body, 0, start, 0, start.length);
decoder.reset();
subject = decoder.decode(ByteBuffer.wrap(start)).toString();
} else {
subject = "";
}
// Record the offset of the body within the message
int bodyStart = (int) counting.getCount() - body.length;
// Record the length of the data covered by the author's signature
@@ -96,7 +78,7 @@ class MessageReader implements StructReader<UnverifiedMessage> {
r.removeConsumer(copying);
byte[] raw = copying.getCopy();
return new UnverifiedMessage(parent, group, author, contentType,
subject, timestamp, raw, signature, bodyStart, body.length,
timestamp, raw, signature, bodyStart, body.length,
signedLength);
}
}

View File

@@ -1,10 +1,13 @@
package net.sf.briar.messaging;
import static net.sf.briar.api.transport.TransportConstants.MAX_CLOCK_DIFFERENCE;
import java.security.GeneralSecurityException;
import javax.inject.Inject;
import net.sf.briar.api.Author;
import net.sf.briar.api.clock.Clock;
import net.sf.briar.api.crypto.CryptoComponent;
import net.sf.briar.api.crypto.KeyParser;
import net.sf.briar.api.crypto.MessageDigest;
@@ -18,11 +21,13 @@ import net.sf.briar.api.messaging.UnverifiedMessage;
class MessageVerifierImpl implements MessageVerifier {
private final CryptoComponent crypto;
private final Clock clock;
private final KeyParser keyParser;
@Inject
MessageVerifierImpl(CryptoComponent crypto) {
MessageVerifierImpl(CryptoComponent crypto, Clock clock) {
this.crypto = crypto;
this.clock = clock;
keyParser = crypto.getSignatureKeyParser();
}
@@ -30,7 +35,11 @@ class MessageVerifierImpl implements MessageVerifier {
throws GeneralSecurityException {
MessageDigest messageDigest = crypto.getMessageDigest();
Signature signature = crypto.getSignature();
// Hash the message, including the signature, to get the message ID
// Reject the message if it's too far in the future
long now = clock.currentTimeMillis();
if(m.getTimestamp() > now + MAX_CLOCK_DIFFERENCE)
throw new GeneralSecurityException();
// Hash the message to get the message ID
byte[] raw = m.getSerialised();
messageDigest.update(raw);
MessageId id = new MessageId(messageDigest.digest());

View File

@@ -129,6 +129,7 @@ class PacketWriterImpl implements PacketWriter {
w.writeStructStart(GROUP);
w.writeString(g.getName());
w.writeBytes(g.getSalt());
w.writeBoolean(g.isPrivate());
w.writeStructEnd();
}
w.writeListEnd();

View File

@@ -32,10 +32,10 @@ class SubscriptionUpdateReader implements StructReader<SubscriptionUpdate> {
// Read the start of the struct
r.readStructStart(SUBSCRIPTION_UPDATE);
// Read the subscriptions
List<Group> subs = new ArrayList<Group>();
List<Group> groups = new ArrayList<Group>();
r.readListStart();
for(int i = 0; i < MAX_SUBSCRIPTIONS && !r.hasListEnd(); i++)
subs.add(groupReader.readStruct(r));
groups.add(groupReader.readStruct(r));
r.readListEnd();
// Read the version number
long version = r.readIntAny();
@@ -45,7 +45,7 @@ class SubscriptionUpdateReader implements StructReader<SubscriptionUpdate> {
// Reset the reader
r.removeConsumer(counting);
// Build and return the subscription update
subs = Collections.unmodifiableList(subs);
return new SubscriptionUpdate(subs, version);
groups = Collections.unmodifiableList(groups);
return new SubscriptionUpdate(groups, version);
}
}

View File

@@ -25,12 +25,11 @@ import net.sf.briar.api.db.DbException;
import net.sf.briar.api.db.event.ContactRemovedEvent;
import net.sf.briar.api.db.event.DatabaseEvent;
import net.sf.briar.api.db.event.DatabaseListener;
import net.sf.briar.api.db.event.GroupMessageAddedEvent;
import net.sf.briar.api.db.event.LocalSubscriptionsUpdatedEvent;
import net.sf.briar.api.db.event.LocalTransportsUpdatedEvent;
import net.sf.briar.api.db.event.MessageAddedEvent;
import net.sf.briar.api.db.event.MessageExpiredEvent;
import net.sf.briar.api.db.event.MessageReceivedEvent;
import net.sf.briar.api.db.event.PrivateMessageAddedEvent;
import net.sf.briar.api.db.event.RemoteRetentionTimeUpdatedEvent;
import net.sf.briar.api.db.event.RemoteSubscriptionsUpdatedEvent;
import net.sf.briar.api.db.event.RemoteTransportsUpdatedEvent;
@@ -129,7 +128,7 @@ abstract class DuplexConnection implements DatabaseListener {
if(e instanceof ContactRemovedEvent) {
ContactRemovedEvent c = (ContactRemovedEvent) e;
if(contactId.equals(c.getContactId())) writerTasks.add(CLOSE);
} else if(e instanceof GroupMessageAddedEvent) {
} else if(e instanceof MessageAddedEvent) {
if(canSendOffer.getAndSet(false))
dbExecutor.execute(new GenerateOffer());
} else if(e instanceof MessageExpiredEvent) {
@@ -147,12 +146,6 @@ abstract class DuplexConnection implements DatabaseListener {
} else if(e instanceof MessageReceivedEvent) {
if(((MessageReceivedEvent) e).getContactId().equals(contactId))
dbExecutor.execute(new GenerateAcks());
} else if(e instanceof PrivateMessageAddedEvent) {
PrivateMessageAddedEvent p = (PrivateMessageAddedEvent) e;
if(!p.isIncoming() && p.getContactId().equals(contactId)) {
if(canSendOffer.getAndSet(false))
dbExecutor.execute(new GenerateOffer());
}
} else if(e instanceof RemoteRetentionTimeUpdatedEvent) {
dbExecutor.execute(new GenerateRetentionAck());
} else if(e instanceof RemoteSubscriptionsUpdatedEvent) {