mirror of
https://code.briarproject.org/briar/briar.git
synced 2026-02-17 13:19:52 +01:00
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:
@@ -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();
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
Reference in New Issue
Block a user