Merge branch 'client-transactions' into 'master'

Transactions for clients

This patch moves transactions out of the database component, allowing clients to perform multiple database calls in a single transaction. This should improve efficiency and reliability, at the cost of increased boilerplate for database calls.

Operations that allow hooks, such as adding and removing contacts, pass their transactions to their hooks. This ensures the whole operation is atomic and isolated, so StorageStatus is no longer needed, hooks don't need to be idempotent, and locks can be removed from clients that were using them for isolation.

This merge request is marked WIP because it will conflict with !74.

See merge request !101
This commit is contained in:
akwizgran
2016-02-17 17:24:18 +00:00
38 changed files with 1982 additions and 2283 deletions

View File

@@ -1,6 +1,5 @@
package org.briarproject.api.contact; package org.briarproject.api.contact;
import org.briarproject.api.db.StorageStatus;
import org.briarproject.api.identity.Author; import org.briarproject.api.identity.Author;
import org.briarproject.api.identity.AuthorId; import org.briarproject.api.identity.AuthorId;
@@ -9,14 +8,11 @@ public class Contact {
private final ContactId id; private final ContactId id;
private final Author author; private final Author author;
private final AuthorId localAuthorId; private final AuthorId localAuthorId;
private final StorageStatus status;
public Contact(ContactId id, Author author, AuthorId localAuthorId, public Contact(ContactId id, Author author, AuthorId localAuthorId) {
StorageStatus status) {
this.id = id; this.id = id;
this.author = author; this.author = author;
this.localAuthorId = localAuthorId; this.localAuthorId = localAuthorId;
this.status = status;
} }
public ContactId getId() { public ContactId getId() {
@@ -31,10 +27,6 @@ public class Contact {
return localAuthorId; return localAuthorId;
} }
public StorageStatus getStatus() {
return status;
}
@Override @Override
public int hashCode() { public int hashCode() {
return id.hashCode(); return id.hashCode();

View File

@@ -1,6 +1,7 @@
package org.briarproject.api.contact; package org.briarproject.api.contact;
import org.briarproject.api.db.DbException; import org.briarproject.api.db.DbException;
import org.briarproject.api.db.Transaction;
import org.briarproject.api.identity.Author; import org.briarproject.api.identity.Author;
import org.briarproject.api.identity.AuthorId; import org.briarproject.api.identity.AuthorId;
@@ -30,10 +31,10 @@ public interface ContactManager {
void removeContact(ContactId c) throws DbException; void removeContact(ContactId c) throws DbException;
interface AddContactHook { interface AddContactHook {
void addingContact(ContactId c); void addingContact(Transaction txn, Contact c) throws DbException;
} }
interface RemoveContactHook { interface RemoveContactHook {
void removingContact(ContactId c); void removingContact(Transaction txn, Contact c) throws DbException;
} }
} }

View File

@@ -26,57 +26,85 @@ import java.util.Map;
/** /**
* Encapsulates the database implementation and exposes high-level operations * Encapsulates the database implementation and exposes high-level operations
* to other components. * to other components.
* <p> * <p/>
* This interface's methods are blocking, but they do not call out into other * This interface's methods are blocking, but they do not call out into other
* components except to broadcast {@link org.briarproject.api.event.Event * components except to broadcast {@link org.briarproject.api.event.Event
* Events}, so they can safely be called while holding locks. * Events}, so they can safely be called while holding locks.
*/ */
public interface DatabaseComponent { public interface DatabaseComponent {
/** Opens the database and returns true if the database already existed. */ /**
* Opens the database and returns true if the database already existed.
*/
boolean open() throws DbException; boolean open() throws DbException;
/** Waits for any open transactions to finish and closes the database. */ /**
* Waits for any open transactions to finish and closes the database.
*/
void close() throws DbException, IOException; void close() throws DbException, IOException;
/**
* Starts a new transaction and returns an object representing it.
*/
Transaction startTransaction() throws DbException;
/**
* Ends a transaction. If the transaction is marked as complete, the
* transaction is committed and any events attached to the transaction are
* broadcast; otherwise the transaction is aborted.
*/
void endTransaction(Transaction txn) throws DbException;
/** /**
* Stores a contact associated with the given local and remote pseudonyms, * Stores a contact associated with the given local and remote pseudonyms,
* and returns an ID for the contact. * and returns an ID for the contact.
*/ */
ContactId addContact(Author remote, AuthorId local) throws DbException; ContactId addContact(Transaction txn, Author remote, AuthorId local)
/** Stores a group. */
void addGroup(Group g) throws DbException;
/** Stores a local pseudonym. */
void addLocalAuthor(LocalAuthor a) throws DbException;
/** Stores a local message. */
void addLocalMessage(Message m, ClientId c, Metadata meta, boolean shared)
throws DbException; throws DbException;
/** Stores a transport. */ /**
void addTransport(TransportId t, int maxLatency) throws DbException; * Stores a group.
*/
void addGroup(Transaction txn, Group g) throws DbException;
/**
* Stores a local pseudonym.
*/
void addLocalAuthor(Transaction txn, LocalAuthor a) throws DbException;
/**
* Stores a local message.
*/
void addLocalMessage(Transaction txn, Message m, ClientId c, Metadata meta,
boolean shared) throws DbException;
/**
* Stores a transport.
*/
void addTransport(Transaction txn, TransportId t, int maxLatency)
throws DbException;
/** /**
* Stores transport keys for a newly added contact. * Stores transport keys for a newly added contact.
*/ */
void addTransportKeys(ContactId c, TransportKeys k) throws DbException; void addTransportKeys(Transaction txn, ContactId c, TransportKeys k)
throws DbException;
/** /**
* Deletes the message with the given ID. The message ID and any other * Deletes the message with the given ID. The message ID and any other
* associated data are not deleted. * associated data are not deleted.
*/ */
void deleteMessage(MessageId m) throws DbException; void deleteMessage(Transaction txn, MessageId m) throws DbException;
/** Deletes any metadata associated with the given message. */ /** Deletes any metadata associated with the given message. */
void deleteMessageMetadata(MessageId m) throws DbException; void deleteMessageMetadata(Transaction txn, MessageId m) throws DbException;
/** /**
* Returns an acknowledgement for the given contact, or null if there are * Returns an acknowledgement for the given contact, or null if there are
* no messages to acknowledge. * no messages to acknowledge.
*/ */
Ack generateAck(ContactId c, int maxMessages) throws DbException; Ack generateAck(Transaction txn, ContactId c, int maxMessages)
throws DbException;
/** /**
* Returns a batch of raw messages for the given contact, with a total * Returns a batch of raw messages for the given contact, with a total
@@ -84,22 +112,23 @@ public interface DatabaseComponent {
* transport with the given maximum latency. Returns null if there are no * transport with the given maximum latency. Returns null if there are no
* sendable messages that fit in the given length. * sendable messages that fit in the given length.
*/ */
Collection<byte[]> generateBatch(ContactId c, int maxLength, Collection<byte[]> generateBatch(Transaction txn, ContactId c,
int maxLatency) throws DbException; int maxLength, int maxLatency) throws DbException;
/** /**
* Returns an offer for the given contact for transmission over a * Returns an offer for the given contact for transmission over a
* transport with the given maximum latency, or null if there are no * transport with the given maximum latency, or null if there are no
* messages to offer. * messages to offer.
*/ */
Offer generateOffer(ContactId c, int maxMessages, int maxLatency) Offer generateOffer(Transaction txn, ContactId c, int maxMessages,
throws DbException; int maxLatency) throws DbException;
/** /**
* Returns a request for the given contact, or null if there are no * Returns a request for the given contact, or null if there are no
* messages to request. * messages to request.
*/ */
Request generateRequest(ContactId c, int maxMessages) throws DbException; Request generateRequest(Transaction txn, ContactId c, int maxMessages)
throws DbException;
/** /**
* Returns a batch of raw messages for the given contact, with a total * Returns a batch of raw messages for the given contact, with a total
@@ -108,168 +137,214 @@ public interface DatabaseComponent {
* requested by the contact are returned. Returns null if there are no * requested by the contact are returned. Returns null if there are no
* sendable messages that fit in the given length. * sendable messages that fit in the given length.
*/ */
Collection<byte[]> generateRequestedBatch(ContactId c, int maxLength, Collection<byte[]> generateRequestedBatch(Transaction txn, ContactId c,
int maxLatency) throws DbException; int maxLength, int maxLatency) throws DbException;
/** Returns the contact with the given ID. */ /**
Contact getContact(ContactId c) throws DbException; * Returns the contact with the given ID.
*/
Contact getContact(Transaction txn, ContactId c) throws DbException;
/** Returns all contacts. */ /**
Collection<Contact> getContacts() throws DbException; * Returns all contacts.
*/
Collection<Contact> getContacts(Transaction txn) throws DbException;
/** Returns all contacts associated with the given local pseudonym. */ /**
Collection<ContactId> getContacts(AuthorId a) throws DbException; * Returns all contacts associated with the given local pseudonym.
*/
Collection<ContactId> getContacts(Transaction txn, AuthorId a)
throws DbException;
/** Returns the unique ID for this device. */ /**
DeviceId getDeviceId() throws DbException; * Returns the unique ID for this device.
*/
DeviceId getDeviceId(Transaction txn) throws DbException;
/** Returns the group with the given ID. */ /**
Group getGroup(GroupId g) throws DbException; * Returns the group with the given ID.
*/
Group getGroup(Transaction txn, GroupId g) throws DbException;
/** Returns the metadata for the given group. */ /**
Metadata getGroupMetadata(GroupId g) throws DbException; * Returns the metadata for the given group.
*/
Metadata getGroupMetadata(Transaction txn, GroupId g) throws DbException;
/** Returns all groups belonging to the given client. */ /**
Collection<Group> getGroups(ClientId c) throws DbException; * Returns all groups belonging to the given client.
*/
Collection<Group> getGroups(Transaction txn, ClientId c) throws DbException;
/** Returns the local pseudonym with the given ID. */ /**
LocalAuthor getLocalAuthor(AuthorId a) throws DbException; * Returns the local pseudonym with the given ID.
*/
LocalAuthor getLocalAuthor(Transaction txn, AuthorId a) throws DbException;
/** Returns all local pseudonyms. */ /**
Collection<LocalAuthor> getLocalAuthors() throws DbException; * Returns all local pseudonyms.
*/
Collection<LocalAuthor> getLocalAuthors(Transaction txn) throws DbException;
/** /**
* Returns the IDs of any messages that need to be validated by the given * Returns the IDs of any messages that need to be validated by the given
* client. * client.
*/ */
Collection<MessageId> getMessagesToValidate(ClientId c) throws DbException; Collection<MessageId> getMessagesToValidate(Transaction txn, ClientId c)
/** Returns the message with the given ID, in serialised form. */
byte[] getRawMessage(MessageId m) throws DbException;
/** Returns the metadata for all messages in the given group. */
Map<MessageId, Metadata> getMessageMetadata(GroupId g)
throws DbException; throws DbException;
/** Returns the metadata for the given message. */ /**
Metadata getMessageMetadata(MessageId m) throws DbException; * Returns the message with the given ID, in serialised form.
*/
byte[] getRawMessage(Transaction txn, MessageId m) throws DbException;
/**
* Returns the metadata for all messages in the given group.
*/
Map<MessageId, Metadata> getMessageMetadata(Transaction txn, GroupId g)
throws DbException;
/**
* Returns the metadata for the given message.
*/
Metadata getMessageMetadata(Transaction txn, MessageId m)
throws DbException;
/** /**
* Returns the status of all messages in the given group with respect to * Returns the status of all messages in the given group with respect to
* the given contact. * the given contact.
*/ */
Collection<MessageStatus> getMessageStatus(ContactId c, GroupId g) Collection<MessageStatus> getMessageStatus(Transaction txn, ContactId c,
throws DbException; GroupId g) throws DbException;
/** /**
* Returns the status of the given message with respect to the given * Returns the status of the given message with respect to the given
* contact. * contact.
*/ */
MessageStatus getMessageStatus(ContactId c, MessageId m) MessageStatus getMessageStatus(Transaction txn, ContactId c, MessageId m)
throws DbException; throws DbException;
/** Returns all settings in the given namespace. */ /**
Settings getSettings(String namespace) throws DbException; * Returns all settings in the given namespace.
*/
Settings getSettings(Transaction txn, String namespace) throws DbException;
/** Returns all transport keys for the given transport. */ /**
Map<ContactId, TransportKeys> getTransportKeys(TransportId t) * Returns all transport keys for the given transport.
*/
Map<ContactId, TransportKeys> getTransportKeys(Transaction txn,
TransportId t) throws DbException;
/**
* Returns the maximum latencies in milliseconds of all transports.
*/
Map<TransportId, Integer> getTransportLatencies(Transaction txn)
throws DbException; throws DbException;
/** Returns the maximum latencies in milliseconds of all transports. */
Map<TransportId, Integer> getTransportLatencies() throws DbException;
/** Returns the IDs of all contacts to which the given group is visible. */
Collection<ContactId> getVisibility(GroupId g) throws DbException;
/** /**
* Increments the outgoing stream counter for the given contact and * Increments the outgoing stream counter for the given contact and
* transport in the given rotation period . * transport in the given rotation period .
*/ */
void incrementStreamCounter(ContactId c, TransportId t, long rotationPeriod) void incrementStreamCounter(Transaction txn, ContactId c, TransportId t,
throws DbException; long rotationPeriod) throws DbException;
/** Returns true if the given group is visible to the given contact. */ /**
boolean isVisibleToContact(ContactId c, GroupId g) throws DbException; * Returns true if the given group is visible to the given contact.
*/
boolean isVisibleToContact(Transaction txn, ContactId c, GroupId g)
throws DbException;
/** /**
* Merges the given metadata with the existing metadata for the given * Merges the given metadata with the existing metadata for the given
* group. * group.
*/ */
void mergeGroupMetadata(GroupId g, Metadata meta) throws DbException; void mergeGroupMetadata(Transaction txn, GroupId g, Metadata meta)
throws DbException;
/** /**
* Merges the given metadata with the existing metadata for the given * Merges the given metadata with the existing metadata for the given
* message. * message.
*/ */
void mergeMessageMetadata(MessageId m, Metadata meta) throws DbException; void mergeMessageMetadata(Transaction txn, MessageId m, Metadata meta)
throws DbException;
/** /**
* Merges the given settings with the existing settings in the given * Merges the given settings with the existing settings in the given
* namespace. * namespace.
*/ */
void mergeSettings(Settings s, String namespace) throws DbException; void mergeSettings(Transaction txn, Settings s, String namespace)
throws DbException;
/** Processes an ack from the given contact. */ /**
void receiveAck(ContactId c, Ack a) throws DbException; * Processes an ack from the given contact.
*/
void receiveAck(Transaction txn, ContactId c, Ack a) throws DbException;
/** Processes a message from the given contact. */ /**
void receiveMessage(ContactId c, Message m) throws DbException; * Processes a message from the given contact.
*/
void receiveMessage(Transaction txn, ContactId c, Message m)
throws DbException;
/** Processes an offer from the given contact. */ /**
void receiveOffer(ContactId c, Offer o) throws DbException; * Processes an offer from the given contact.
*/
void receiveOffer(Transaction txn, ContactId c, Offer o) throws DbException;
/** Processes a request from the given contact. */ /**
void receiveRequest(ContactId c, Request r) throws DbException; * Processes a request from the given contact.
*/
void receiveRequest(Transaction txn, ContactId c, Request r)
throws DbException;
/** Removes a contact (and all associated state) from the database. */ /**
void removeContact(ContactId c) throws DbException; * Removes a contact (and all associated state) from the database.
*/
void removeContact(Transaction txn, ContactId c) throws DbException;
/** Removes a group (and all associated state) from the database. */ /**
void removeGroup(Group g) throws DbException; * Removes a group (and all associated state) from the database.
*/
void removeGroup(Transaction txn, Group g) throws DbException;
/** /**
* Removes a local pseudonym (and all associated state) from the database. * Removes a local pseudonym (and all associated state) from the database.
*/ */
void removeLocalAuthor(AuthorId a) throws DbException; void removeLocalAuthor(Transaction txn, AuthorId a) throws DbException;
/** Removes a transport (and all associated state) from the database. */ /**
void removeTransport(TransportId t) throws DbException; * Removes a transport (and all associated state) from the database.
*/
void removeTransport(Transaction txn, TransportId t) throws DbException;
/** Sets the status of the given contact. */ /**
void setContactStatus(ContactId c, StorageStatus s) throws DbException; * Marks the given message as shared or unshared.
*/
void setMessageShared(Transaction txn, Message m, boolean shared)
throws DbException;
/** Sets the status of the given local pseudonym. */ /**
void setLocalAuthorStatus(AuthorId a, StorageStatus s) * Marks the given message as valid or invalid.
throws DbException; */
void setMessageValid(Transaction txn, Message m, ClientId c, boolean valid)
/** Marks the given message as shared or unshared. */
void setMessageShared(Message m, boolean shared) throws DbException;
/** Marks the given message as valid or invalid. */
void setMessageValid(Message m, ClientId c, boolean valid)
throws DbException; throws DbException;
/** /**
* Sets the reordering window for the given contact and transport in the * Sets the reordering window for the given contact and transport in the
* given rotation period. * given rotation period.
*/ */
void setReorderingWindow(ContactId c, TransportId t, long rotationPeriod, void setReorderingWindow(Transaction txn, ContactId c, TransportId t,
long base, byte[] bitmap) throws DbException; long rotationPeriod, long base, byte[] bitmap) throws DbException;
/** /**
* Makes a group visible to the given set of contacts and invisible to any * Makes a group visible or invisible to a contact.
* other contacts.
*/ */
void setVisibility(GroupId g, Collection<ContactId> visible) void setVisibleToContact(Transaction txn, ContactId c, GroupId g,
throws DbException; boolean visible) throws DbException;
/** Makes a group visible or invisible to a contact. */
void setVisibleToContact(ContactId c, GroupId g, boolean visible)
throws DbException;
/** /**
* Stores the given transport keys, deleting any keys they have replaced. * Stores the given transport keys, deleting any keys they have replaced.
*/ */
void updateTransportKeys(Map<ContactId, TransportKeys> keys) void updateTransportKeys(Transaction txn,
throws DbException; Map<ContactId, TransportKeys> keys) throws DbException;
} }

View File

@@ -1,21 +0,0 @@
package org.briarproject.api.db;
public enum StorageStatus {
ADDING(0), ACTIVE(1), REMOVING(2);
private final int value;
StorageStatus(int value) {
this.value = value;
}
public int getValue() {
return value;
}
public static StorageStatus fromValue(int value) {
for (StorageStatus s : values()) if (s.value == value) return s;
throw new IllegalArgumentException();
}
}

View File

@@ -0,0 +1,63 @@
package org.briarproject.api.db;
import org.briarproject.api.event.Event;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/**
* A wrapper around a database transaction. Transactions are not thread-safe.
*/
public class Transaction {
private final Object txn;
private List<Event> events = null;
private boolean complete = false;
public Transaction(Object txn) {
this.txn = txn;
}
/**
* Returns the database transaction. The type of the returned object
* depends on the database implementation.
*/
public Object unbox() {
return txn;
}
/**
* Attaches an event to be broadcast when the transaction has been
* committed.
*/
public void attach(Event e) {
if (events == null) events = new ArrayList<Event>();
events.add(e);
}
/**
* Returns any events attached to the transaction.
*/
public List<Event> getEvents() {
if (events == null) return Collections.emptyList();
return events;
}
/**
* Returns true if the transaction is ready to be committed.
*/
public boolean isComplete() {
return complete;
}
/**
* Marks the transaction as ready to be committed. This method must not be
* called more than once.
*/
public void setComplete() {
if (complete) throw new IllegalStateException();
complete = true;
}
}

View File

@@ -1,6 +1,7 @@
package org.briarproject.api.identity; package org.briarproject.api.identity;
import org.briarproject.api.db.DbException; import org.briarproject.api.db.DbException;
import org.briarproject.api.db.Transaction;
import java.util.Collection; import java.util.Collection;
@@ -25,10 +26,11 @@ public interface IdentityManager {
void removeLocalAuthor(AuthorId a) throws DbException; void removeLocalAuthor(AuthorId a) throws DbException;
interface AddIdentityHook { interface AddIdentityHook {
void addingIdentity(AuthorId a); void addingIdentity(Transaction txn, LocalAuthor a) throws DbException;
} }
interface RemoveIdentityHook { interface RemoveIdentityHook {
void removingIdentity(AuthorId a); void removingIdentity(Transaction txn, LocalAuthor a)
throws DbException;
} }
} }

View File

@@ -1,20 +1,16 @@
package org.briarproject.api.identity; package org.briarproject.api.identity;
import org.briarproject.api.db.StorageStatus;
/** A pseudonym for the local user. */ /** A pseudonym for the local user. */
public class LocalAuthor extends Author { public class LocalAuthor extends Author {
private final byte[] privateKey; private final byte[] privateKey;
private final long created; private final long created;
private final StorageStatus status;
public LocalAuthor(AuthorId id, String name, byte[] publicKey, public LocalAuthor(AuthorId id, String name, byte[] publicKey,
byte[] privateKey, long created, StorageStatus status) { byte[] privateKey, long created) {
super(id, name, publicKey); super(id, name, publicKey);
this.privateKey = privateKey; this.privateKey = privateKey;
this.created = created; this.created = created;
this.status = status;
} }
/** Returns the private key used to generate the pseudonym's signatures. */ /** Returns the private key used to generate the pseudonym's signatures. */
@@ -29,9 +25,4 @@ public class LocalAuthor extends Author {
public long getTimeCreated() { public long getTimeCreated() {
return created; return created;
} }
/** Returns the status of the pseudonym. */
public StorageStatus getStatus() {
return status;
}
} }

View File

@@ -1,6 +1,8 @@
package org.briarproject.api.sync; package org.briarproject.api.sync;
import org.briarproject.api.db.DbException;
import org.briarproject.api.db.Metadata; import org.briarproject.api.db.Metadata;
import org.briarproject.api.db.Transaction;
/** /**
* Responsible for managing message validators and passing them messages to * Responsible for managing message validators and passing them messages to
@@ -35,6 +37,7 @@ public interface ValidationManager {
void registerValidationHook(ValidationHook hook); void registerValidationHook(ValidationHook hook);
interface ValidationHook { interface ValidationHook {
void validatingMessage(Message m, ClientId c, Metadata meta); void validatingMessage(Transaction txn, Message m, ClientId c,
Metadata meta) throws DbException;
} }
} }

View File

@@ -7,75 +7,29 @@ import org.briarproject.api.contact.ContactId;
import org.briarproject.api.contact.ContactManager; import org.briarproject.api.contact.ContactManager;
import org.briarproject.api.db.DatabaseComponent; import org.briarproject.api.db.DatabaseComponent;
import org.briarproject.api.db.DbException; import org.briarproject.api.db.DbException;
import org.briarproject.api.db.NoSuchContactException; import org.briarproject.api.db.Transaction;
import org.briarproject.api.event.ContactAddedEvent;
import org.briarproject.api.event.ContactRemovedEvent;
import org.briarproject.api.event.EventBus;
import org.briarproject.api.identity.Author; import org.briarproject.api.identity.Author;
import org.briarproject.api.identity.AuthorId; import org.briarproject.api.identity.AuthorId;
import org.briarproject.api.identity.IdentityManager.RemoveIdentityHook; import org.briarproject.api.identity.IdentityManager.RemoveIdentityHook;
import org.briarproject.api.lifecycle.Service; import org.briarproject.api.identity.LocalAuthor;
import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.CopyOnWriteArrayList;
import java.util.logging.Logger;
import static java.util.logging.Level.WARNING; class ContactManagerImpl implements ContactManager, RemoveIdentityHook {
import static org.briarproject.api.db.StorageStatus.ACTIVE;
import static org.briarproject.api.db.StorageStatus.ADDING;
import static org.briarproject.api.db.StorageStatus.REMOVING;
class ContactManagerImpl implements ContactManager, Service,
RemoveIdentityHook {
private static final Logger LOG =
Logger.getLogger(ContactManagerImpl.class.getName());
private final DatabaseComponent db; private final DatabaseComponent db;
private final EventBus eventBus;
private final List<AddContactHook> addHooks; private final List<AddContactHook> addHooks;
private final List<RemoveContactHook> removeHooks; private final List<RemoveContactHook> removeHooks;
@Inject @Inject
ContactManagerImpl(DatabaseComponent db, EventBus eventBus) { ContactManagerImpl(DatabaseComponent db) {
this.db = db; this.db = db;
this.eventBus = eventBus;
addHooks = new CopyOnWriteArrayList<AddContactHook>(); addHooks = new CopyOnWriteArrayList<AddContactHook>();
removeHooks = new CopyOnWriteArrayList<RemoveContactHook>(); removeHooks = new CopyOnWriteArrayList<RemoveContactHook>();
} }
@Override
public boolean start() {
// Finish adding/removing any partly added/removed contacts
try {
for (Contact c : db.getContacts()) {
if (c.getStatus().equals(ADDING)) {
for (AddContactHook hook : addHooks)
hook.addingContact(c.getId());
db.setContactStatus(c.getId(), ACTIVE);
eventBus.broadcast(new ContactAddedEvent(c.getId()));
} else if (c.getStatus().equals(REMOVING)) {
for (RemoveContactHook hook : removeHooks)
hook.removingContact(c.getId());
db.removeContact(c.getId());
eventBus.broadcast(new ContactRemovedEvent(c.getId()));
}
}
return true;
} catch (DbException e) {
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
return false;
}
}
@Override
public boolean stop() {
return true;
}
@Override @Override
public void registerAddContactHook(AddContactHook hook) { public void registerAddContactHook(AddContactHook hook) {
addHooks.add(hook); addHooks.add(hook);
@@ -89,45 +43,70 @@ class ContactManagerImpl implements ContactManager, Service,
@Override @Override
public ContactId addContact(Author remote, AuthorId local) public ContactId addContact(Author remote, AuthorId local)
throws DbException { throws DbException {
ContactId c = db.addContact(remote, local); ContactId c;
for (AddContactHook hook : addHooks) hook.addingContact(c); Transaction txn = db.startTransaction();
db.setContactStatus(c, ACTIVE); try {
eventBus.broadcast(new ContactAddedEvent(c)); c = db.addContact(txn, remote, local);
Contact contact = db.getContact(txn, c);
for (AddContactHook hook : addHooks)
hook.addingContact(txn, contact);
txn.setComplete();
} finally {
db.endTransaction(txn);
}
return c; return c;
} }
@Override @Override
public Contact getContact(ContactId c) throws DbException { public Contact getContact(ContactId c) throws DbException {
Contact contact = db.getContact(c); Contact contact;
if (contact.getStatus().equals(ACTIVE)) return contact; Transaction txn = db.startTransaction();
throw new NoSuchContactException(); try {
contact = db.getContact(txn, c);
txn.setComplete();
} finally {
db.endTransaction(txn);
}
return contact;
} }
@Override @Override
public Collection<Contact> getContacts() throws DbException { public Collection<Contact> getContacts() throws DbException {
Collection<Contact> contacts = db.getContacts(); Collection<Contact> contacts;
// Filter out any contacts that are being added or removed Transaction txn = db.startTransaction();
List<Contact> active = new ArrayList<Contact>(contacts.size()); try {
for (Contact c : contacts) contacts = db.getContacts(txn);
if (c.getStatus().equals(ACTIVE)) active.add(c); txn.setComplete();
return Collections.unmodifiableList(active); } finally {
db.endTransaction(txn);
}
return contacts;
} }
@Override @Override
public void removeContact(ContactId c) throws DbException { public void removeContact(ContactId c) throws DbException {
db.setContactStatus(c, REMOVING); Transaction txn = db.startTransaction();
for (RemoveContactHook hook : removeHooks) hook.removingContact(c); try {
db.removeContact(c); removeContact(txn, c);
eventBus.broadcast(new ContactRemovedEvent(c)); txn.setComplete();
} finally {
db.endTransaction(txn);
}
}
private void removeContact(Transaction txn, ContactId c)
throws DbException {
Contact contact = db.getContact(txn, c);
for (RemoveContactHook hook : removeHooks)
hook.removingContact(txn, contact);
db.removeContact(txn, c);
} }
@Override @Override
public void removingIdentity(AuthorId a) { public void removingIdentity(Transaction txn, LocalAuthor a)
throws DbException {
// Remove any contacts of the local pseudonym that's being removed // Remove any contacts of the local pseudonym that's being removed
try { for (ContactId c : db.getContacts(txn, a.getId()))
for (ContactId c : db.getContacts(a)) removeContact(c); removeContact(txn, c);
} catch (DbException e) {
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
}
} }
} }

View File

@@ -5,7 +5,6 @@ import com.google.inject.Provides;
import org.briarproject.api.contact.ContactManager; import org.briarproject.api.contact.ContactManager;
import org.briarproject.api.identity.IdentityManager; import org.briarproject.api.identity.IdentityManager;
import org.briarproject.api.lifecycle.LifecycleManager;
import javax.inject.Singleton; import javax.inject.Singleton;
@@ -15,10 +14,8 @@ public class ContactModule extends AbstractModule {
protected void configure() {} protected void configure() {}
@Provides @Singleton @Provides @Singleton
ContactManager getContactManager(LifecycleManager lifecycleManager, ContactManager getContactManager(IdentityManager identityManager,
IdentityManager identityManager,
ContactManagerImpl contactManager) { ContactManagerImpl contactManager) {
lifecycleManager.register(contactManager);
identityManager.registerRemoveIdentityHook(contactManager); identityManager.registerRemoveIdentityHook(contactManager);
return contactManager; return contactManager;
} }

View File

@@ -6,7 +6,6 @@ import org.briarproject.api.contact.Contact;
import org.briarproject.api.contact.ContactId; import org.briarproject.api.contact.ContactId;
import org.briarproject.api.db.DbException; import org.briarproject.api.db.DbException;
import org.briarproject.api.db.Metadata; import org.briarproject.api.db.Metadata;
import org.briarproject.api.db.StorageStatus;
import org.briarproject.api.identity.Author; import org.briarproject.api.identity.Author;
import org.briarproject.api.identity.AuthorId; import org.briarproject.api.identity.AuthorId;
import org.briarproject.api.identity.LocalAuthor; import org.briarproject.api.identity.LocalAuthor;
@@ -30,27 +29,23 @@ import java.util.Map;
* obtained by calling {@link #startTransaction()}. Every transaction must be * obtained by calling {@link #startTransaction()}. Every transaction must be
* terminated by calling either {@link #abortTransaction(T)} or * terminated by calling either {@link #abortTransaction(T)} or
* {@link #commitTransaction(T)}, even if an exception is thrown. * {@link #commitTransaction(T)}, even if an exception is thrown.
* <p>
* Read-write locking is provided by the DatabaseComponent implementation.
*/ */
interface Database<T> { interface Database<T> {
/** /**
* Opens the database and returns true if the database already existed. * Opens the database and returns true if the database already existed.
* <p>
* Locking: write.
*/ */
boolean open() throws DbException; boolean open() throws DbException;
/** /**
* Prevents new transactions from starting, waits for all current * Prevents new transactions from starting, waits for all current
* transactions to finish, and closes the database. * transactions to finish, and closes the database.
* <p>
* Locking: write.
*/ */
void close() throws DbException, IOException; void close() throws DbException, IOException;
/** Starts a new transaction and returns an object representing it. */ /**
* Starts a new transaction and returns an object representing it.
*/
T startTransaction() throws DbException; T startTransaction() throws DbException;
/** /**
@@ -65,59 +60,39 @@ interface Database<T> {
*/ */
void commitTransaction(T txn) throws DbException; void commitTransaction(T txn) throws DbException;
/**
* Returns the number of transactions started since the transaction count
* was last reset.
*/
int getTransactionCount();
/** Resets the transaction count. */
void resetTransactionCount();
/** /**
* Stores a contact associated with the given local and remote pseudonyms, * Stores a contact associated with the given local and remote pseudonyms,
* and returns an ID for the contact. * and returns an ID for the contact.
* <p>
* Locking: write.
*/ */
ContactId addContact(T txn, Author remote, AuthorId local) ContactId addContact(T txn, Author remote, AuthorId local)
throws DbException; throws DbException;
/** /**
* Stores a group. * Stores a group.
* <p>
* Locking: write.
*/ */
void addGroup(T txn, Group g) throws DbException; void addGroup(T txn, Group g) throws DbException;
/** /**
* Stores a local pseudonym. * Stores a local pseudonym.
* <p>
* Locking: write.
*/ */
void addLocalAuthor(T txn, LocalAuthor a) throws DbException; void addLocalAuthor(T txn, LocalAuthor a) throws DbException;
/** /**
* Stores a message. * Stores a message.
* <p>
* Locking: write.
*/ */
void addMessage(T txn, Message m, Validity validity, boolean shared) void addMessage(T txn, Message m, Validity validity, boolean shared)
throws DbException; throws DbException;
/** /**
* Records that a message has been offered by the given contact. * Records that a message has been offered by the given contact.
* <p>
* Locking: write.
*/ */
void addOfferedMessage(T txn, ContactId c, MessageId m) throws DbException; void addOfferedMessage(T txn, ContactId c, MessageId m) throws DbException;
/** /**
* Initialises the status of the given message with respect to the given * Initialises the status of the given message with respect to the given
* contact. * contact.
* <p> *
* Locking: write. * @param ack whether the message needs to be acknowledged.
* @param ack whether the message needs to be acknowledged.
* @param seen whether the contact has seen the message. * @param seen whether the contact has seen the message.
*/ */
void addStatus(T txn, ContactId c, MessageId m, boolean ack, boolean seen) void addStatus(T txn, ContactId c, MessageId m, boolean ack, boolean seen)
@@ -125,76 +100,56 @@ interface Database<T> {
/** /**
* Stores a transport. * Stores a transport.
* <p>
* Locking: write.
*/ */
void addTransport(T txn, TransportId t, int maxLatency) void addTransport(T txn, TransportId t, int maxLatency)
throws DbException; throws DbException;
/** /**
* Stores transport keys for a newly added contact. * Stores transport keys for a newly added contact.
* <p>
* Locking: write.
*/ */
void addTransportKeys(T txn, ContactId c, TransportKeys k) void addTransportKeys(T txn, ContactId c, TransportKeys k)
throws DbException; throws DbException;
/** /**
* Makes a group visible to the given contact. * Makes a group visible to the given contact.
* <p>
* Locking: write.
*/ */
void addVisibility(T txn, ContactId c, GroupId g) throws DbException; void addVisibility(T txn, ContactId c, GroupId g) throws DbException;
/** /**
* Returns true if the database contains the given contact for the given * Returns true if the database contains the given contact for the given
* local pseudonym. * local pseudonym.
* <p>
* Locking: read.
*/ */
boolean containsContact(T txn, AuthorId remote, AuthorId local) boolean containsContact(T txn, AuthorId remote, AuthorId local)
throws DbException; throws DbException;
/** /**
* Returns true if the database contains the given contact. * Returns true if the database contains the given contact.
* <p>
* Locking: read.
*/ */
boolean containsContact(T txn, ContactId c) throws DbException; boolean containsContact(T txn, ContactId c) throws DbException;
/** /**
* Returns true if the database contains the given group. * Returns true if the database contains the given group.
* <p>
* Locking: read.
*/ */
boolean containsGroup(T txn, GroupId g) throws DbException; boolean containsGroup(T txn, GroupId g) throws DbException;
/** /**
* Returns true if the database contains the given local pseudonym. * Returns true if the database contains the given local pseudonym.
* <p>
* Locking: read.
*/ */
boolean containsLocalAuthor(T txn, AuthorId a) throws DbException; boolean containsLocalAuthor(T txn, AuthorId a) throws DbException;
/** /**
* Returns true if the database contains the given message. * Returns true if the database contains the given message.
* <p>
* Locking: read.
*/ */
boolean containsMessage(T txn, MessageId m) throws DbException; boolean containsMessage(T txn, MessageId m) throws DbException;
/** /**
* Returns true if the database contains the given transport. * Returns true if the database contains the given transport.
* <p>
* Locking: read.
*/ */
boolean containsTransport(T txn, TransportId t) throws DbException; boolean containsTransport(T txn, TransportId t) throws DbException;
/** /**
* Returns true if the database contains the given group and the group is * Returns true if the database contains the given group and the group is
* visible to the given contact. * visible to the given contact.
* <p>
* Locking: read.
*/ */
boolean containsVisibleGroup(T txn, ContactId c, GroupId g) boolean containsVisibleGroup(T txn, ContactId c, GroupId g)
throws DbException; throws DbException;
@@ -202,16 +157,12 @@ interface Database<T> {
/** /**
* Returns true if the database contains the given message and the message * Returns true if the database contains the given message and the message
* is visible to the given contact. * is visible to the given contact.
* <p>
* Locking: read.
*/ */
boolean containsVisibleMessage(T txn, ContactId c, MessageId m) boolean containsVisibleMessage(T txn, ContactId c, MessageId m)
throws DbException; throws DbException;
/** /**
* Returns the number of messages offered by the given contact. * Returns the number of messages offered by the given contact.
* <p>
* Locking: read.
*/ */
int countOfferedMessages(T txn, ContactId c) throws DbException; int countOfferedMessages(T txn, ContactId c) throws DbException;
@@ -234,36 +185,26 @@ interface Database<T> {
/** /**
* Returns the contact with the given ID. * Returns the contact with the given ID.
* <p>
* Locking: read.
*/ */
Contact getContact(T txn, ContactId c) throws DbException; Contact getContact(T txn, ContactId c) throws DbException;
/** /**
* Returns the IDs of all contacts. * Returns the IDs of all contacts.
* <p>
* Locking: read.
*/ */
Collection<ContactId> getContactIds(T txn) throws DbException; Collection<ContactId> getContactIds(T txn) throws DbException;
/** /**
* Returns all contacts. * Returns all contacts.
* <p>
* Locking: read.
*/ */
Collection<Contact> getContacts(T txn) throws DbException; Collection<Contact> getContacts(T txn) throws DbException;
/** /**
* Returns all contacts associated with the given local pseudonym. * Returns all contacts associated with the given local pseudonym.
* <p>
* Locking: read.
*/ */
Collection<ContactId> getContacts(T txn, AuthorId a) throws DbException; Collection<ContactId> getContacts(T txn, AuthorId a) throws DbException;
/** /**
* Returns the unique ID for this device. * Returns the unique ID for this device.
* <p>
* Locking: read.
*/ */
DeviceId getDeviceId(T txn) throws DbException; DeviceId getDeviceId(T txn) throws DbException;
@@ -276,59 +217,43 @@ interface Database<T> {
/** /**
* Returns the group with the given ID. * Returns the group with the given ID.
* <p>
* Locking: read.
*/ */
Group getGroup(T txn, GroupId g) throws DbException; Group getGroup(T txn, GroupId g) throws DbException;
/** /**
* Returns the metadata for the given group. * Returns the metadata for the given group.
* <p>
* Locking: read.
*/ */
Metadata getGroupMetadata(T txn, GroupId g) throws DbException; Metadata getGroupMetadata(T txn, GroupId g) throws DbException;
/** /**
* Returns all groups belonging to the given client. * Returns all groups belonging to the given client.
* <p>
* Locking: read.
*/ */
Collection<Group> getGroups(T txn, ClientId c) throws DbException; Collection<Group> getGroups(T txn, ClientId c) throws DbException;
/** /**
* Returns the local pseudonym with the given ID. * Returns the local pseudonym with the given ID.
* <p>
* Locking: read.
*/ */
LocalAuthor getLocalAuthor(T txn, AuthorId a) throws DbException; LocalAuthor getLocalAuthor(T txn, AuthorId a) throws DbException;
/** /**
* Returns all local pseudonyms. * Returns all local pseudonyms.
* <p>
* Locking: read.
*/ */
Collection<LocalAuthor> getLocalAuthors(T txn) throws DbException; Collection<LocalAuthor> getLocalAuthors(T txn) throws DbException;
/** /**
* Returns the metadata for all messages in the given group. * Returns the metadata for all messages in the given group.
* <p>
* Locking: read.
*/ */
Map<MessageId, Metadata> getMessageMetadata(T txn, GroupId g) Map<MessageId, Metadata> getMessageMetadata(T txn, GroupId g)
throws DbException; throws DbException;
/** /**
* Returns the metadata for the given message. * Returns the metadata for the given message.
* <p>
* Locking: read.
*/ */
Metadata getMessageMetadata(T txn, MessageId m) throws DbException; Metadata getMessageMetadata(T txn, MessageId m) throws DbException;
/** /**
* Returns the status of all messages in the given group with respect * Returns the status of all messages in the given group with respect
* to the given contact. * to the given contact.
* <p>
* Locking: read
*/ */
Collection<MessageStatus> getMessageStatus(T txn, ContactId c, GroupId g) Collection<MessageStatus> getMessageStatus(T txn, ContactId c, GroupId g)
throws DbException; throws DbException;
@@ -336,8 +261,6 @@ interface Database<T> {
/** /**
* Returns the status of the given message with respect to the given * Returns the status of the given message with respect to the given
* contact. * contact.
* <p>
* Locking: read
*/ */
MessageStatus getMessageStatus(T txn, ContactId c, MessageId m) MessageStatus getMessageStatus(T txn, ContactId c, MessageId m)
throws DbException; throws DbException;
@@ -345,8 +268,6 @@ interface Database<T> {
/** /**
* Returns the IDs of some messages received from the given contact that * Returns the IDs of some messages received from the given contact that
* need to be acknowledged, up to the given number of messages. * need to be acknowledged, up to the given number of messages.
* <p>
* Locking: read.
*/ */
Collection<MessageId> getMessagesToAck(T txn, ContactId c, int maxMessages) Collection<MessageId> getMessagesToAck(T txn, ContactId c, int maxMessages)
throws DbException; throws DbException;
@@ -354,8 +275,6 @@ interface Database<T> {
/** /**
* Returns the IDs of some messages that are eligible to be offered to the * Returns the IDs of some messages that are eligible to be offered to the
* given contact, up to the given number of messages. * given contact, up to the given number of messages.
* <p>
* Locking: read.
*/ */
Collection<MessageId> getMessagesToOffer(T txn, ContactId c, Collection<MessageId> getMessagesToOffer(T txn, ContactId c,
int maxMessages) throws DbException; int maxMessages) throws DbException;
@@ -363,8 +282,6 @@ interface Database<T> {
/** /**
* Returns the IDs of some messages that are eligible to be sent to the * Returns the IDs of some messages that are eligible to be sent to the
* given contact, up to the given total length. * given contact, up to the given total length.
* <p>
* Locking: read.
*/ */
Collection<MessageId> getMessagesToSend(T txn, ContactId c, int maxLength) Collection<MessageId> getMessagesToSend(T txn, ContactId c, int maxLength)
throws DbException; throws DbException;
@@ -372,8 +289,6 @@ interface Database<T> {
/** /**
* Returns the IDs of some messages that are eligible to be requested from * Returns the IDs of some messages that are eligible to be requested from
* the given contact, up to the given number of messages. * the given contact, up to the given number of messages.
* <p>
* Locking: read.
*/ */
Collection<MessageId> getMessagesToRequest(T txn, ContactId c, Collection<MessageId> getMessagesToRequest(T txn, ContactId c,
int maxMessages) throws DbException; int maxMessages) throws DbException;
@@ -381,16 +296,12 @@ interface Database<T> {
/** /**
* Returns the IDs of any messages that need to be validated by the given * Returns the IDs of any messages that need to be validated by the given
* client. * client.
* <p>
* Locking: read.
*/ */
Collection<MessageId> getMessagesToValidate(T txn, ClientId c) Collection<MessageId> getMessagesToValidate(T txn, ClientId c)
throws DbException; throws DbException;
/** /**
* Returns the message with the given ID, in serialised form. * Returns the message with the given ID, in serialised form.
* <p>
* Locking: read.
*/ */
byte[] getRawMessage(T txn, MessageId m) throws DbException; byte[] getRawMessage(T txn, MessageId m) throws DbException;
@@ -398,46 +309,34 @@ interface Database<T> {
* Returns the IDs of some messages that are eligible to be sent to the * Returns the IDs of some messages that are eligible to be sent to the
* given contact and have been requested by the contact, up to the given * given contact and have been requested by the contact, up to the given
* total length. * total length.
* <p>
* Locking: read.
*/ */
Collection<MessageId> getRequestedMessagesToSend(T txn, ContactId c, Collection<MessageId> getRequestedMessagesToSend(T txn, ContactId c,
int maxLength) throws DbException; int maxLength) throws DbException;
/** /**
* Returns all settings in the given namespace. * Returns all settings in the given namespace.
* <p>
* Locking: read.
*/ */
Settings getSettings(T txn, String namespace) throws DbException; Settings getSettings(T txn, String namespace) throws DbException;
/** /**
* Returns all transport keys for the given transport. * Returns all transport keys for the given transport.
* <p>
* Locking: read.
*/ */
Map<ContactId, TransportKeys> getTransportKeys(T txn, TransportId t) Map<ContactId, TransportKeys> getTransportKeys(T txn, TransportId t)
throws DbException; throws DbException;
/** /**
* Returns the maximum latencies in milliseconds of all transports. * Returns the maximum latencies in milliseconds of all transports.
* <p>
* Locking: read.
*/ */
Map<TransportId, Integer> getTransportLatencies(T txn) throws DbException; Map<TransportId, Integer> getTransportLatencies(T txn) throws DbException;
/** /**
* Returns the IDs of all contacts to which the given group is visible. * Returns the IDs of all contacts to which the given group is visible.
* <p>
* Locking: read.
*/ */
Collection<ContactId> getVisibility(T txn, GroupId g) throws DbException; Collection<ContactId> getVisibility(T txn, GroupId g) throws DbException;
/** /**
* Increments the outgoing stream counter for the given contact and * Increments the outgoing stream counter for the given contact and
* transport in the given rotation period. * transport in the given rotation period.
* <p>
* Locking: write.
*/ */
void incrementStreamCounter(T txn, ContactId c, TransportId t, void incrementStreamCounter(T txn, ContactId c, TransportId t,
long rotationPeriod) throws DbException; long rotationPeriod) throws DbException;
@@ -445,8 +344,6 @@ interface Database<T> {
/** /**
* Marks the given messages as not needing to be acknowledged to the * Marks the given messages as not needing to be acknowledged to the
* given contact. * given contact.
* <p>
* Locking: write.
*/ */
void lowerAckFlag(T txn, ContactId c, Collection<MessageId> acked) void lowerAckFlag(T txn, ContactId c, Collection<MessageId> acked)
throws DbException; throws DbException;
@@ -454,8 +351,6 @@ interface Database<T> {
/** /**
* Marks the given messages as not having been requested by the given * Marks the given messages as not having been requested by the given
* contact. * contact.
* <p>
* Locking: write.
*/ */
void lowerRequestedFlag(T txn, ContactId c, Collection<MessageId> requested) void lowerRequestedFlag(T txn, ContactId c, Collection<MessageId> requested)
throws DbException; throws DbException;
@@ -463,8 +358,6 @@ interface Database<T> {
/* /*
* Merges the given metadata with the existing metadata for the given * Merges the given metadata with the existing metadata for the given
* group. * group.
* <p>
* Locking: write.
*/ */
void mergeGroupMetadata(T txn, GroupId g, Metadata meta) void mergeGroupMetadata(T txn, GroupId g, Metadata meta)
throws DbException; throws DbException;
@@ -472,8 +365,6 @@ interface Database<T> {
/* /*
* Merges the given metadata with the existing metadata for the given * Merges the given metadata with the existing metadata for the given
* message. * message.
* <p>
* Locking: write.
*/ */
void mergeMessageMetadata(T txn, MessageId m, Metadata meta) void mergeMessageMetadata(T txn, MessageId m, Metadata meta)
throws DbException; throws DbException;
@@ -481,65 +372,47 @@ interface Database<T> {
/** /**
* Merges the given settings with the existing settings in the given * Merges the given settings with the existing settings in the given
* namespace. * namespace.
* <p>
* Locking: write.
*/ */
void mergeSettings(T txn, Settings s, String namespace) throws DbException; void mergeSettings(T txn, Settings s, String namespace) throws DbException;
/** /**
* Marks a message as needing to be acknowledged to the given contact. * Marks a message as needing to be acknowledged to the given contact.
* <p>
* Locking: write.
*/ */
void raiseAckFlag(T txn, ContactId c, MessageId m) throws DbException; void raiseAckFlag(T txn, ContactId c, MessageId m) throws DbException;
/** /**
* Marks a message as having been requested by the given contact. * Marks a message as having been requested by the given contact.
* <p>
* Locking: write.
*/ */
void raiseRequestedFlag(T txn, ContactId c, MessageId m) throws DbException; void raiseRequestedFlag(T txn, ContactId c, MessageId m) throws DbException;
/** /**
* Marks a message as having been seen by the given contact. * Marks a message as having been seen by the given contact.
* <p>
* Locking: write.
*/ */
void raiseSeenFlag(T txn, ContactId c, MessageId m) throws DbException; void raiseSeenFlag(T txn, ContactId c, MessageId m) throws DbException;
/** /**
* Removes a contact from the database. * Removes a contact from the database.
* <p>
* Locking: write.
*/ */
void removeContact(T txn, ContactId c) throws DbException; void removeContact(T txn, ContactId c) throws DbException;
/** /**
* Removes a group (and all associated state) from the database. * Removes a group (and all associated state) from the database.
* <p>
* Locking: write.
*/ */
void removeGroup(T txn, GroupId g) throws DbException; void removeGroup(T txn, GroupId g) throws DbException;
/** /**
* Removes a local pseudonym (and all associated state) from the database. * Removes a local pseudonym (and all associated state) from the database.
* <p>
* Locking: write.
*/ */
void removeLocalAuthor(T txn, AuthorId a) throws DbException; void removeLocalAuthor(T txn, AuthorId a) throws DbException;
/** /**
* Removes a message (and all associated state) from the database. * Removes a message (and all associated state) from the database.
* <p>
* Locking: write.
*/ */
void removeMessage(T txn, MessageId m) throws DbException; void removeMessage(T txn, MessageId m) throws DbException;
/** /**
* Removes an offered message that was offered by the given contact, or * Removes an offered message that was offered by the given contact, or
* returns false if there is no such message. * returns false if there is no such message.
* <p>
* Locking: write.
*/ */
boolean removeOfferedMessage(T txn, ContactId c, MessageId m) boolean removeOfferedMessage(T txn, ContactId c, MessageId m)
throws DbException; throws DbException;
@@ -547,70 +420,40 @@ interface Database<T> {
/** /**
* Removes the given offered messages that were offered by the given * Removes the given offered messages that were offered by the given
* contact. * contact.
* <p>
* Locking: write.
*/ */
void removeOfferedMessages(T txn, ContactId c, void removeOfferedMessages(T txn, ContactId c,
Collection<MessageId> requested) throws DbException; Collection<MessageId> requested) throws DbException;
/** /**
* Removes a transport (and all associated state) from the database. * Removes a transport (and all associated state) from the database.
* <p>
* Locking: write.
*/ */
void removeTransport(T txn, TransportId t) throws DbException; void removeTransport(T txn, TransportId t) throws DbException;
/** /**
* Makes a group invisible to the given contact. * Makes a group invisible to the given contact.
* <p>
* Locking: write.
*/ */
void removeVisibility(T txn, ContactId c, GroupId g) throws DbException; void removeVisibility(T txn, ContactId c, GroupId g) throws DbException;
/** /**
* Resets the transmission count and expiry time of the given message with * Resets the transmission count and expiry time of the given message with
* respect to the given contact. * respect to the given contact.
* <p>
* Locking: write.
*/ */
void resetExpiryTime(T txn, ContactId c, MessageId m) throws DbException; void resetExpiryTime(T txn, ContactId c, MessageId m) throws DbException;
/**
* Sets the status of the given contact.
* <p>
* Locking: write.
*/
void setContactStatus(T txn, ContactId c, StorageStatus s)
throws DbException;
/**
* Sets the status of the given local pseudonym.
* <p>
* Locking: write.
*/
void setLocalAuthorStatus(T txn, AuthorId a, StorageStatus s)
throws DbException;
/** /**
* Marks the given message as shared or unshared. * Marks the given message as shared or unshared.
* <p>
* Locking: write.
*/ */
void setMessageShared(T txn, MessageId m, boolean shared) void setMessageShared(T txn, MessageId m, boolean shared)
throws DbException; throws DbException;
/** /**
* Marks the given message as valid or invalid. * Marks the given message as valid or invalid.
* <p>
* Locking: write.
*/ */
void setMessageValid(T txn, MessageId m, boolean valid) throws DbException; void setMessageValid(T txn, MessageId m, boolean valid) throws DbException;
/** /**
* Sets the reordering window for the given contact and transport in the * Sets the reordering window for the given contact and transport in the
* given rotation period. * given rotation period.
* <p>
* Locking: write.
*/ */
void setReorderingWindow(T txn, ContactId c, TransportId t, void setReorderingWindow(T txn, ContactId c, TransportId t,
long rotationPeriod, long base, byte[] bitmap) throws DbException; long rotationPeriod, long base, byte[] bitmap) throws DbException;
@@ -619,16 +462,12 @@ interface Database<T> {
* Updates the transmission count and expiry time of the given message * Updates the transmission count and expiry time of the given message
* with respect to the given contact, using the latency of the transport * with respect to the given contact, using the latency of the transport
* over which it was sent. * over which it was sent.
* <p>
* Locking: write.
*/ */
void updateExpiryTime(T txn, ContactId c, MessageId m, int maxLatency) void updateExpiryTime(T txn, ContactId c, MessageId m, int maxLatency)
throws DbException; throws DbException;
/** /**
* Stores the given transport keys, deleting any keys they have replaced. * Stores the given transport keys, deleting any keys they have replaced.
* <p>
* Locking: write.
*/ */
void updateTransportKeys(T txn, Map<ContactId, TransportKeys> keys) void updateTransportKeys(T txn, Map<ContactId, TransportKeys> keys)
throws DbException; throws DbException;

File diff suppressed because it is too large Load Diff

View File

@@ -51,7 +51,8 @@ public class DatabaseModule extends AbstractModule {
@Provides @Singleton @Provides @Singleton
DatabaseComponent getDatabaseComponent(Database<Connection> db, DatabaseComponent getDatabaseComponent(Database<Connection> db,
EventBus eventBus, ShutdownManager shutdown) { EventBus eventBus, ShutdownManager shutdown) {
return new DatabaseComponentImpl<Connection>(db, eventBus, shutdown); return new DatabaseComponentImpl<Connection>(db, Connection.class,
eventBus, shutdown);
} }
@Provides @Singleton @DatabaseExecutor @Provides @Singleton @DatabaseExecutor

View File

@@ -9,7 +9,6 @@ import org.briarproject.api.crypto.SecretKey;
import org.briarproject.api.db.DbClosedException; import org.briarproject.api.db.DbClosedException;
import org.briarproject.api.db.DbException; import org.briarproject.api.db.DbException;
import org.briarproject.api.db.Metadata; import org.briarproject.api.db.Metadata;
import org.briarproject.api.db.StorageStatus;
import org.briarproject.api.identity.Author; import org.briarproject.api.identity.Author;
import org.briarproject.api.identity.AuthorId; import org.briarproject.api.identity.AuthorId;
import org.briarproject.api.identity.LocalAuthor; import org.briarproject.api.identity.LocalAuthor;
@@ -41,7 +40,6 @@ import java.util.LinkedList;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Map.Entry; import java.util.Map.Entry;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock; import java.util.concurrent.locks.ReentrantLock;
@@ -49,7 +47,6 @@ import java.util.logging.Logger;
import static java.util.logging.Level.WARNING; import static java.util.logging.Level.WARNING;
import static org.briarproject.api.db.Metadata.REMOVE; import static org.briarproject.api.db.Metadata.REMOVE;
import static org.briarproject.api.db.StorageStatus.ADDING;
import static org.briarproject.api.sync.ValidationManager.Validity.INVALID; import static org.briarproject.api.sync.ValidationManager.Validity.INVALID;
import static org.briarproject.api.sync.ValidationManager.Validity.UNKNOWN; import static org.briarproject.api.sync.ValidationManager.Validity.UNKNOWN;
import static org.briarproject.api.sync.ValidationManager.Validity.VALID; import static org.briarproject.api.sync.ValidationManager.Validity.VALID;
@@ -66,8 +63,8 @@ import static org.briarproject.db.ExponentialBackoff.calculateExpiry;
*/ */
abstract class JdbcDatabase implements Database<Connection> { abstract class JdbcDatabase implements Database<Connection> {
private static final int SCHEMA_VERSION = 20; private static final int SCHEMA_VERSION = 21;
private static final int MIN_SCHEMA_VERSION = 20; private static final int MIN_SCHEMA_VERSION = 21;
private static final String CREATE_SETTINGS = private static final String CREATE_SETTINGS =
"CREATE TABLE settings" "CREATE TABLE settings"
@@ -83,7 +80,6 @@ abstract class JdbcDatabase implements Database<Connection> {
+ " publicKey BINARY NOT NULL," + " publicKey BINARY NOT NULL,"
+ " privateKey BINARY NOT NULL," + " privateKey BINARY NOT NULL,"
+ " created BIGINT NOT NULL," + " created BIGINT NOT NULL,"
+ " status INT NOT NULL,"
+ " PRIMARY KEY (authorId))"; + " PRIMARY KEY (authorId))";
private static final String CREATE_CONTACTS = private static final String CREATE_CONTACTS =
@@ -93,7 +89,6 @@ abstract class JdbcDatabase implements Database<Connection> {
+ " name VARCHAR NOT NULL," + " name VARCHAR NOT NULL,"
+ " publicKey BINARY NOT NULL," + " publicKey BINARY NOT NULL,"
+ " localAuthorId HASH NOT NULL," + " localAuthorId HASH NOT NULL,"
+ " status INT NOT NULL,"
+ " PRIMARY KEY (contactId)," + " PRIMARY KEY (contactId),"
+ " FOREIGN KEY (localAuthorId)" + " FOREIGN KEY (localAuthorId)"
+ " REFERENCES localAuthors (authorId)" + " REFERENCES localAuthors (authorId)"
@@ -228,8 +223,6 @@ abstract class JdbcDatabase implements Database<Connection> {
private final LinkedList<Connection> connections = private final LinkedList<Connection> connections =
new LinkedList<Connection>(); // Locking: connectionsLock new LinkedList<Connection>(); // Locking: connectionsLock
private final AtomicInteger transactionCount = new AtomicInteger(0);
private int openConnections = 0; // Locking: connectionsLock private int openConnections = 0; // Locking: connectionsLock
private boolean closed = false; // Locking: connectionsLock private boolean closed = false; // Locking: connectionsLock
@@ -369,7 +362,6 @@ abstract class JdbcDatabase implements Database<Connection> {
} catch (SQLException e) { } catch (SQLException e) {
throw new DbException(e); throw new DbException(e);
} }
transactionCount.incrementAndGet();
return txn; return txn;
} }
@@ -418,14 +410,6 @@ abstract class JdbcDatabase implements Database<Connection> {
} }
} }
public int getTransactionCount() {
return transactionCount.get();
}
public void resetTransactionCount() {
transactionCount.set(0);
}
protected void closeAllConnections() throws SQLException { protected void closeAllConnections() throws SQLException {
boolean interrupted = false; boolean interrupted = false;
connectionsLock.lock(); connectionsLock.lock();
@@ -464,14 +448,13 @@ abstract class JdbcDatabase implements Database<Connection> {
try { try {
// Create a contact row // Create a contact row
String sql = "INSERT INTO contacts" String sql = "INSERT INTO contacts"
+ " (authorId, name, publicKey, localAuthorId, status)" + " (authorId, name, publicKey, localAuthorId)"
+ " VALUES (?, ?, ?, ?, ?)"; + " VALUES (?, ?, ?, ?)";
ps = txn.prepareStatement(sql); ps = txn.prepareStatement(sql);
ps.setBytes(1, remote.getId().getBytes()); ps.setBytes(1, remote.getId().getBytes());
ps.setString(2, remote.getName()); ps.setString(2, remote.getName());
ps.setBytes(3, remote.getPublicKey()); ps.setBytes(3, remote.getPublicKey());
ps.setBytes(4, local.getBytes()); ps.setBytes(4, local.getBytes());
ps.setInt(5, ADDING.getValue());
int affected = ps.executeUpdate(); int affected = ps.executeUpdate();
if (affected != 1) throw new DbStateException(); if (affected != 1) throw new DbStateException();
ps.close(); ps.close();
@@ -540,16 +523,15 @@ abstract class JdbcDatabase implements Database<Connection> {
throws DbException { throws DbException {
PreparedStatement ps = null; PreparedStatement ps = null;
try { try {
String sql = "INSERT INTO localAuthors (authorId, name, publicKey," String sql = "INSERT INTO localAuthors"
+ " privateKey, created, status)" + " (authorId, name, publicKey, privateKey, created)"
+ " VALUES (?, ?, ?, ?, ?, ?)"; + " VALUES (?, ?, ?, ?, ?)";
ps = txn.prepareStatement(sql); ps = txn.prepareStatement(sql);
ps.setBytes(1, a.getId().getBytes()); ps.setBytes(1, a.getId().getBytes());
ps.setString(2, a.getName()); ps.setString(2, a.getName());
ps.setBytes(3, a.getPublicKey()); ps.setBytes(3, a.getPublicKey());
ps.setBytes(4, a.getPrivateKey()); ps.setBytes(4, a.getPrivateKey());
ps.setLong(5, a.getTimeCreated()); ps.setLong(5, a.getTimeCreated());
ps.setInt(6, a.getStatus().getValue());
int affected = ps.executeUpdate(); int affected = ps.executeUpdate();
if (affected != 1) throw new DbStateException(); if (affected != 1) throw new DbStateException();
ps.close(); ps.close();
@@ -969,8 +951,7 @@ abstract class JdbcDatabase implements Database<Connection> {
PreparedStatement ps = null; PreparedStatement ps = null;
ResultSet rs = null; ResultSet rs = null;
try { try {
String sql = "SELECT authorId, name, publicKey, localAuthorId," String sql = "SELECT authorId, name, publicKey, localAuthorId"
+ " status"
+ " FROM contacts" + " FROM contacts"
+ " WHERE contactId = ?"; + " WHERE contactId = ?";
ps = txn.prepareStatement(sql); ps = txn.prepareStatement(sql);
@@ -981,11 +962,10 @@ abstract class JdbcDatabase implements Database<Connection> {
String name = rs.getString(2); String name = rs.getString(2);
byte[] publicKey = rs.getBytes(3); byte[] publicKey = rs.getBytes(3);
AuthorId localAuthorId = new AuthorId(rs.getBytes(4)); AuthorId localAuthorId = new AuthorId(rs.getBytes(4));
StorageStatus status = StorageStatus.fromValue(rs.getInt(5));
rs.close(); rs.close();
ps.close(); ps.close();
Author author = new Author(authorId, name, publicKey); Author author = new Author(authorId, name, publicKey);
return new Contact(c, author, localAuthorId, status); return new Contact(c, author, localAuthorId);
} catch (SQLException e) { } catch (SQLException e) {
tryToClose(rs); tryToClose(rs);
tryToClose(ps); tryToClose(ps);
@@ -1019,7 +999,7 @@ abstract class JdbcDatabase implements Database<Connection> {
ResultSet rs = null; ResultSet rs = null;
try { try {
String sql = "SELECT contactId, authorId, name, publicKey," String sql = "SELECT contactId, authorId, name, publicKey,"
+ " localAuthorId, status" + " localAuthorId"
+ " FROM contacts"; + " FROM contacts";
ps = txn.prepareStatement(sql); ps = txn.prepareStatement(sql);
rs = ps.executeQuery(); rs = ps.executeQuery();
@@ -1031,9 +1011,7 @@ abstract class JdbcDatabase implements Database<Connection> {
byte[] publicKey = rs.getBytes(4); byte[] publicKey = rs.getBytes(4);
Author author = new Author(authorId, name, publicKey); Author author = new Author(authorId, name, publicKey);
AuthorId localAuthorId = new AuthorId(rs.getBytes(5)); AuthorId localAuthorId = new AuthorId(rs.getBytes(5));
StorageStatus status = StorageStatus.fromValue(rs.getInt(6)); contacts.add(new Contact(contactId, author, localAuthorId));
contacts.add(new Contact(contactId, author, localAuthorId,
status));
} }
rs.close(); rs.close();
ps.close(); ps.close();
@@ -1120,7 +1098,7 @@ abstract class JdbcDatabase implements Database<Connection> {
PreparedStatement ps = null; PreparedStatement ps = null;
ResultSet rs = null; ResultSet rs = null;
try { try {
String sql = "SELECT name, publicKey, privateKey, created, status" String sql = "SELECT name, publicKey, privateKey, created"
+ " FROM localAuthors" + " FROM localAuthors"
+ " WHERE authorId = ?"; + " WHERE authorId = ?";
ps = txn.prepareStatement(sql); ps = txn.prepareStatement(sql);
@@ -1131,9 +1109,8 @@ abstract class JdbcDatabase implements Database<Connection> {
byte[] publicKey = rs.getBytes(2); byte[] publicKey = rs.getBytes(2);
byte[] privateKey = rs.getBytes(3); byte[] privateKey = rs.getBytes(3);
long created = rs.getLong(4); long created = rs.getLong(4);
StorageStatus status = StorageStatus.fromValue(rs.getInt(5));
LocalAuthor localAuthor = new LocalAuthor(a, name, publicKey, LocalAuthor localAuthor = new LocalAuthor(a, name, publicKey,
privateKey, created, status); privateKey, created);
if (rs.next()) throw new DbStateException(); if (rs.next()) throw new DbStateException();
rs.close(); rs.close();
ps.close(); ps.close();
@@ -1150,8 +1127,7 @@ abstract class JdbcDatabase implements Database<Connection> {
PreparedStatement ps = null; PreparedStatement ps = null;
ResultSet rs = null; ResultSet rs = null;
try { try {
String sql = "SELECT authorId, name, publicKey, privateKey," String sql = "SELECT authorId, name, publicKey, privateKey, created"
+ " created, status"
+ " FROM localAuthors"; + " FROM localAuthors";
ps = txn.prepareStatement(sql); ps = txn.prepareStatement(sql);
rs = ps.executeQuery(); rs = ps.executeQuery();
@@ -1162,9 +1138,8 @@ abstract class JdbcDatabase implements Database<Connection> {
byte[] publicKey = rs.getBytes(3); byte[] publicKey = rs.getBytes(3);
byte[] privateKey = rs.getBytes(4); byte[] privateKey = rs.getBytes(4);
long created = rs.getLong(5); long created = rs.getLong(5);
StorageStatus status = StorageStatus.fromValue(rs.getInt(6));
authors.add(new LocalAuthor(authorId, name, publicKey, authors.add(new LocalAuthor(authorId, name, publicKey,
privateKey, created, status)); privateKey, created));
} }
rs.close(); rs.close();
ps.close(); ps.close();
@@ -2053,41 +2028,6 @@ abstract class JdbcDatabase implements Database<Connection> {
} }
} }
public void setContactStatus(Connection txn, ContactId c, StorageStatus s)
throws DbException {
PreparedStatement ps = null;
try {
String sql = "UPDATE contacts SET status = ? WHERE contactId = ?";
ps = txn.prepareStatement(sql);
ps.setInt(1, s.getValue());
ps.setInt(2, c.getInt());
int affected = ps.executeUpdate();
if (affected < 0 || affected > 1) throw new DbStateException();
ps.close();
} catch (SQLException e) {
tryToClose(ps);
throw new DbException(e);
}
}
public void setLocalAuthorStatus(Connection txn, AuthorId a,
StorageStatus s) throws DbException {
PreparedStatement ps = null;
try {
String sql = "UPDATE localAuthors SET status = ?"
+ " WHERE authorId = ?";
ps = txn.prepareStatement(sql);
ps.setInt(1, s.getValue());
ps.setBytes(2, a.getBytes());
int affected = ps.executeUpdate();
if (affected < 0 || affected > 1) throw new DbStateException();
ps.close();
} catch (SQLException e) {
tryToClose(ps);
throw new DbException(e);
}
}
public void setMessageShared(Connection txn, MessageId m, boolean shared) public void setMessageShared(Connection txn, MessageId m, boolean shared)
throws DbException { throws DbException {
PreparedStatement ps = null; PreparedStatement ps = null;

View File

@@ -4,7 +4,6 @@ import com.google.inject.Inject;
import org.briarproject.api.FormatException; import org.briarproject.api.FormatException;
import org.briarproject.api.contact.Contact; import org.briarproject.api.contact.Contact;
import org.briarproject.api.contact.ContactManager;
import org.briarproject.api.data.BdfDictionary; import org.briarproject.api.data.BdfDictionary;
import org.briarproject.api.data.BdfReader; import org.briarproject.api.data.BdfReader;
import org.briarproject.api.data.BdfReaderFactory; import org.briarproject.api.data.BdfReaderFactory;
@@ -13,6 +12,7 @@ import org.briarproject.api.data.MetadataParser;
import org.briarproject.api.db.DatabaseComponent; import org.briarproject.api.db.DatabaseComponent;
import org.briarproject.api.db.DbException; import org.briarproject.api.db.DbException;
import org.briarproject.api.db.Metadata; import org.briarproject.api.db.Metadata;
import org.briarproject.api.db.Transaction;
import org.briarproject.api.forum.Forum; import org.briarproject.api.forum.Forum;
import org.briarproject.api.forum.ForumManager; import org.briarproject.api.forum.ForumManager;
import org.briarproject.api.forum.ForumPost; import org.briarproject.api.forum.ForumPost;
@@ -36,7 +36,6 @@ import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Map.Entry; import java.util.Map.Entry;
import java.util.Set; import java.util.Set;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import java.util.logging.Logger; import java.util.logging.Logger;
import static java.util.logging.Level.WARNING; import static java.util.logging.Level.WARNING;
@@ -58,20 +57,14 @@ class ForumManagerImpl implements ForumManager {
Logger.getLogger(ForumManagerImpl.class.getName()); Logger.getLogger(ForumManagerImpl.class.getName());
private final DatabaseComponent db; private final DatabaseComponent db;
private final ContactManager contactManager;
private final BdfReaderFactory bdfReaderFactory; private final BdfReaderFactory bdfReaderFactory;
private final MetadataEncoder metadataEncoder; private final MetadataEncoder metadataEncoder;
private final MetadataParser metadataParser; private final MetadataParser metadataParser;
/** Ensures isolation between database reads and writes. */
private final ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
@Inject @Inject
ForumManagerImpl(DatabaseComponent db, ContactManager contactManager, ForumManagerImpl(DatabaseComponent db, BdfReaderFactory bdfReaderFactory,
BdfReaderFactory bdfReaderFactory, MetadataEncoder metadataEncoder, MetadataEncoder metadataEncoder, MetadataParser metadataParser) {
MetadataParser metadataParser) {
this.db = db; this.db = db;
this.contactManager = contactManager;
this.bdfReaderFactory = bdfReaderFactory; this.bdfReaderFactory = bdfReaderFactory;
this.metadataEncoder = metadataEncoder; this.metadataEncoder = metadataEncoder;
this.metadataParser = metadataParser; this.metadataParser = metadataParser;
@@ -84,7 +77,6 @@ class ForumManagerImpl implements ForumManager {
@Override @Override
public void addLocalPost(ForumPost p) throws DbException { public void addLocalPost(ForumPost p) throws DbException {
lock.writeLock().lock();
try { try {
BdfDictionary d = new BdfDictionary(); BdfDictionary d = new BdfDictionary();
d.put("timestamp", p.getMessage().getTimestamp()); d.put("timestamp", p.getMessage().getTimestamp());
@@ -102,45 +94,65 @@ class ForumManagerImpl implements ForumManager {
d.put("local", true); d.put("local", true);
d.put("read", true); d.put("read", true);
Metadata meta = metadataEncoder.encode(d); Metadata meta = metadataEncoder.encode(d);
db.addLocalMessage(p.getMessage(), CLIENT_ID, meta, true); Transaction txn = db.startTransaction();
try {
db.addLocalMessage(txn, p.getMessage(), CLIENT_ID, meta, true);
txn.setComplete();
} finally {
db.endTransaction(txn);
}
} catch (FormatException e) { } catch (FormatException e) {
throw new RuntimeException(e); throw new RuntimeException(e);
} finally {
lock.writeLock().unlock();
} }
} }
@Override @Override
public Forum getForum(GroupId g) throws DbException { public Forum getForum(GroupId g) throws DbException {
lock.readLock().lock();
try { try {
return parseForum(db.getGroup(g)); Group group;
Transaction txn = db.startTransaction();
try {
group = db.getGroup(txn, g);
txn.setComplete();
} finally {
db.endTransaction(txn);
}
return parseForum(group);
} catch (FormatException e) { } catch (FormatException e) {
throw new DbException(e); throw new DbException(e);
} finally {
lock.readLock().unlock();
} }
} }
@Override @Override
public Collection<Forum> getForums() throws DbException { public Collection<Forum> getForums() throws DbException {
lock.readLock().lock();
try { try {
Collection<Group> groups;
Transaction txn = db.startTransaction();
try {
groups = db.getGroups(txn, CLIENT_ID);
txn.setComplete();
} finally {
db.endTransaction(txn);
}
List<Forum> forums = new ArrayList<Forum>(); List<Forum> forums = new ArrayList<Forum>();
for (Group g : db.getGroups(CLIENT_ID)) forums.add(parseForum(g)); for (Group g : groups) forums.add(parseForum(g));
return Collections.unmodifiableList(forums); return Collections.unmodifiableList(forums);
} catch (FormatException e) { } catch (FormatException e) {
throw new DbException(e); throw new DbException(e);
} finally {
lock.readLock().unlock();
} }
} }
@Override @Override
public byte[] getPostBody(MessageId m) throws DbException { public byte[] getPostBody(MessageId m) throws DbException {
lock.readLock().lock();
try { try {
byte[] raw = db.getRawMessage(m); byte[] raw;
Transaction txn = db.startTransaction();
try {
raw = db.getRawMessage(txn, m);
txn.setComplete();
} finally {
db.endTransaction(txn);
}
ByteArrayInputStream in = new ByteArrayInputStream(raw, ByteArrayInputStream in = new ByteArrayInputStream(raw,
MESSAGE_HEADER_LENGTH, raw.length - MESSAGE_HEADER_LENGTH); MESSAGE_HEADER_LENGTH, raw.length - MESSAGE_HEADER_LENGTH);
BdfReader r = bdfReaderFactory.createReader(in); BdfReader r = bdfReaderFactory.createReader(in);
@@ -161,74 +173,76 @@ class ForumManagerImpl implements ForumManager {
} catch (IOException e) { } catch (IOException e) {
// Shouldn't happen with ByteArrayInputStream // Shouldn't happen with ByteArrayInputStream
throw new RuntimeException(e); throw new RuntimeException(e);
} finally {
lock.readLock().unlock();
} }
} }
@Override @Override
public Collection<ForumPostHeader> getPostHeaders(GroupId g) public Collection<ForumPostHeader> getPostHeaders(GroupId g)
throws DbException { throws DbException {
lock.readLock().lock(); Set<AuthorId> localAuthorIds = new HashSet<AuthorId>();
Set<AuthorId> contactAuthorIds = new HashSet<AuthorId>();
Map<MessageId, Metadata> metadata;
Transaction txn = db.startTransaction();
try { try {
// Load the IDs of the user's identities // Load the IDs of the user's identities
Set<AuthorId> localAuthorIds = new HashSet<AuthorId>(); for (LocalAuthor a : db.getLocalAuthors(txn))
for (LocalAuthor a : db.getLocalAuthors())
localAuthorIds.add(a.getId()); localAuthorIds.add(a.getId());
// Load the IDs of contacts' identities // Load the IDs of contacts' identities
Set<AuthorId> contactAuthorIds = new HashSet<AuthorId>(); for (Contact c : db.getContacts(txn))
for (Contact c : contactManager.getContacts())
contactAuthorIds.add(c.getAuthor().getId()); contactAuthorIds.add(c.getAuthor().getId());
// Load and parse the metadata // Load the metadata
Map<MessageId, Metadata> metadata = db.getMessageMetadata(g); metadata = db.getMessageMetadata(txn, g);
Collection<ForumPostHeader> headers = txn.setComplete();
new ArrayList<ForumPostHeader>();
for (Entry<MessageId, Metadata> e : metadata.entrySet()) {
MessageId messageId = e.getKey();
Metadata meta = e.getValue();
try {
BdfDictionary d = metadataParser.parse(meta);
long timestamp = d.getInteger("timestamp");
Author author = null;
Author.Status authorStatus = ANONYMOUS;
BdfDictionary d1 = d.getDictionary("author", null);
if (d1 != null) {
AuthorId authorId = new AuthorId(d1.getRaw("id"));
String name = d1.getString("name");
byte[] publicKey = d1.getRaw("publicKey");
author = new Author(authorId, name, publicKey);
if (localAuthorIds.contains(authorId))
authorStatus = VERIFIED;
else if (contactAuthorIds.contains(authorId))
authorStatus = VERIFIED;
else authorStatus = UNKNOWN;
}
String contentType = d.getString("contentType");
boolean read = d.getBoolean("read");
headers.add(new ForumPostHeader(messageId, timestamp,
author, authorStatus, contentType, read));
} catch (FormatException ex) {
if (LOG.isLoggable(WARNING))
LOG.log(WARNING, ex.toString(), ex);
}
}
return headers;
} finally { } finally {
lock.readLock().unlock(); db.endTransaction(txn);
} }
// Parse the metadata
Collection<ForumPostHeader> headers = new ArrayList<ForumPostHeader>();
for (Entry<MessageId, Metadata> e : metadata.entrySet()) {
try {
BdfDictionary d = metadataParser.parse(e.getValue());
long timestamp = d.getInteger("timestamp");
Author author = null;
Author.Status authorStatus = ANONYMOUS;
BdfDictionary d1 = d.getDictionary("author", null);
if (d1 != null) {
AuthorId authorId = new AuthorId(d1.getRaw("id"));
String name = d1.getString("name");
byte[] publicKey = d1.getRaw("publicKey");
author = new Author(authorId, name, publicKey);
if (localAuthorIds.contains(authorId))
authorStatus = VERIFIED;
else if (contactAuthorIds.contains(authorId))
authorStatus = VERIFIED;
else authorStatus = UNKNOWN;
}
String contentType = d.getString("contentType");
boolean read = d.getBoolean("read");
headers.add(new ForumPostHeader(e.getKey(), timestamp, author,
authorStatus, contentType, read));
} catch (FormatException ex) {
if (LOG.isLoggable(WARNING))
LOG.log(WARNING, ex.toString(), ex);
}
}
return headers;
} }
@Override @Override
public void setReadFlag(MessageId m, boolean read) throws DbException { public void setReadFlag(MessageId m, boolean read) throws DbException {
lock.writeLock().lock();
try { try {
BdfDictionary d = new BdfDictionary(); BdfDictionary d = new BdfDictionary();
d.put("read", read); d.put("read", read);
db.mergeMessageMetadata(m, metadataEncoder.encode(d)); Metadata meta = metadataEncoder.encode(d);
Transaction txn = db.startTransaction();
try {
db.mergeMessageMetadata(txn, m, meta);
txn.setComplete();
} finally {
db.endTransaction(txn);
}
} catch (FormatException e) { } catch (FormatException e) {
throw new RuntimeException(e); throw new RuntimeException(e);
} finally {
lock.writeLock().unlock();
} }
} }

View File

@@ -5,7 +5,6 @@ import com.google.inject.Inject;
import org.briarproject.api.FormatException; import org.briarproject.api.FormatException;
import org.briarproject.api.contact.Contact; import org.briarproject.api.contact.Contact;
import org.briarproject.api.contact.ContactId; import org.briarproject.api.contact.ContactId;
import org.briarproject.api.contact.ContactManager;
import org.briarproject.api.contact.ContactManager.AddContactHook; import org.briarproject.api.contact.ContactManager.AddContactHook;
import org.briarproject.api.contact.ContactManager.RemoveContactHook; import org.briarproject.api.contact.ContactManager.RemoveContactHook;
import org.briarproject.api.data.BdfDictionary; import org.briarproject.api.data.BdfDictionary;
@@ -18,6 +17,7 @@ import org.briarproject.api.data.MetadataParser;
import org.briarproject.api.db.DatabaseComponent; import org.briarproject.api.db.DatabaseComponent;
import org.briarproject.api.db.DbException; import org.briarproject.api.db.DbException;
import org.briarproject.api.db.Metadata; import org.briarproject.api.db.Metadata;
import org.briarproject.api.db.Transaction;
import org.briarproject.api.forum.Forum; import org.briarproject.api.forum.Forum;
import org.briarproject.api.forum.ForumManager; import org.briarproject.api.forum.ForumManager;
import org.briarproject.api.forum.ForumSharingManager; import org.briarproject.api.forum.ForumSharingManager;
@@ -45,10 +45,7 @@ import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Map.Entry; import java.util.Map.Entry;
import java.util.Set; import java.util.Set;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import java.util.logging.Logger;
import static java.util.logging.Level.WARNING;
import static org.briarproject.api.forum.ForumConstants.FORUM_SALT_LENGTH; import static org.briarproject.api.forum.ForumConstants.FORUM_SALT_LENGTH;
import static org.briarproject.api.forum.ForumConstants.MAX_FORUM_NAME_LENGTH; import static org.briarproject.api.forum.ForumConstants.MAX_FORUM_NAME_LENGTH;
import static org.briarproject.api.sync.SyncConstants.MESSAGE_HEADER_LENGTH; import static org.briarproject.api.sync.SyncConstants.MESSAGE_HEADER_LENGTH;
@@ -62,11 +59,7 @@ class ForumSharingManagerImpl implements ForumSharingManager, AddContactHook,
private static final byte[] LOCAL_GROUP_DESCRIPTOR = new byte[0]; private static final byte[] LOCAL_GROUP_DESCRIPTOR = new byte[0];
private static final Logger LOG =
Logger.getLogger(ForumSharingManagerImpl.class.getName());
private final DatabaseComponent db; private final DatabaseComponent db;
private final ContactManager contactManager;
private final ForumManager forumManager; private final ForumManager forumManager;
private final GroupFactory groupFactory; private final GroupFactory groupFactory;
private final PrivateGroupFactory privateGroupFactory; private final PrivateGroupFactory privateGroupFactory;
@@ -79,18 +72,14 @@ class ForumSharingManagerImpl implements ForumSharingManager, AddContactHook,
private final Clock clock; private final Clock clock;
private final Group localGroup; private final Group localGroup;
/** Ensures isolation between database reads and writes. */
private final ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
@Inject @Inject
ForumSharingManagerImpl(DatabaseComponent db, ForumSharingManagerImpl(DatabaseComponent db,
ContactManager contactManager, ForumManager forumManager, ForumManager forumManager, GroupFactory groupFactory,
GroupFactory groupFactory, PrivateGroupFactory privateGroupFactory, PrivateGroupFactory privateGroupFactory,
MessageFactory messageFactory, BdfReaderFactory bdfReaderFactory, MessageFactory messageFactory, BdfReaderFactory bdfReaderFactory,
BdfWriterFactory bdfWriterFactory, MetadataEncoder metadataEncoder, BdfWriterFactory bdfWriterFactory, MetadataEncoder metadataEncoder,
MetadataParser metadataParser, SecureRandom random, Clock clock) { MetadataParser metadataParser, SecureRandom random, Clock clock) {
this.db = db; this.db = db;
this.contactManager = contactManager;
this.forumManager = forumManager; this.forumManager = forumManager;
this.groupFactory = groupFactory; this.groupFactory = groupFactory;
this.privateGroupFactory = privateGroupFactory; this.privateGroupFactory = privateGroupFactory;
@@ -106,57 +95,39 @@ class ForumSharingManagerImpl implements ForumSharingManager, AddContactHook,
} }
@Override @Override
public void addingContact(ContactId c) { public void addingContact(Transaction txn, Contact c) throws DbException {
lock.writeLock().lock();
try { try {
// Create a group to share with the contact // Create a group to share with the contact
Group g = getContactGroup(db.getContact(c)); Group g = getContactGroup(c);
// Store the group and share it with the contact // Store the group and share it with the contact
db.addGroup(g); db.addGroup(txn, g);
db.setVisibility(g.getId(), Collections.singletonList(c)); db.setVisibleToContact(txn, c.getId(), g.getId(), true);
// Attach the contact ID to the group // Attach the contact ID to the group
BdfDictionary d = new BdfDictionary(); BdfDictionary d = new BdfDictionary();
d.put("contactId", c.getInt()); d.put("contactId", c.getId().getInt());
db.mergeGroupMetadata(g.getId(), metadataEncoder.encode(d)); db.mergeGroupMetadata(txn, g.getId(), metadataEncoder.encode(d));
// Share any forums that are shared with all contacts // Share any forums that are shared with all contacts
List<Forum> shared = getForumsSharedWithAllContacts(); List<Forum> shared = getForumsSharedWithAllContacts(txn);
storeMessage(g.getId(), shared, 0); storeMessage(txn, g.getId(), shared, 0);
} catch (DbException e) {
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
} catch (FormatException e) { } catch (FormatException e) {
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e); throw new DbException(e);
} finally {
lock.writeLock().unlock();
} }
} }
@Override @Override
public void removingContact(ContactId c) { public void removingContact(Transaction txn, Contact c) throws DbException {
lock.writeLock().lock(); db.removeGroup(txn, getContactGroup(c));
try {
db.removeGroup(getContactGroup(db.getContact(c)));
} catch (DbException e) {
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
} finally {
lock.writeLock().unlock();
}
} }
@Override @Override
public void validatingMessage(Message m, ClientId c, Metadata meta) { public void validatingMessage(Transaction txn, Message m, ClientId c,
Metadata meta) throws DbException {
if (c.equals(CLIENT_ID)) { if (c.equals(CLIENT_ID)) {
lock.writeLock().lock();
try { try {
ContactId contactId = getContactId(m.getGroupId()); ContactId contactId = getContactId(txn, m.getGroupId());
setForumVisibility(contactId, getVisibleForums(m)); setForumVisibility(txn, contactId, getVisibleForums(txn, m));
} catch (DbException e) {
if (LOG.isLoggable(WARNING))
LOG.log(WARNING, e.toString(), e);
} catch (FormatException e) { } catch (FormatException e) {
if (LOG.isLoggable(WARNING)) throw new DbException(e);
LOG.log(WARNING, e.toString(), e);
} finally {
lock.writeLock().unlock();
} }
} }
} }
@@ -179,149 +150,162 @@ class ForumSharingManagerImpl implements ForumSharingManager, AddContactHook,
@Override @Override
public void addForum(Forum f) throws DbException { public void addForum(Forum f) throws DbException {
lock.writeLock().lock(); Transaction txn = db.startTransaction();
try { try {
db.addGroup(f.getGroup()); db.addGroup(txn, f.getGroup());
txn.setComplete();
} finally { } finally {
lock.writeLock().unlock(); db.endTransaction(txn);
} }
} }
@Override @Override
public void removeForum(Forum f) throws DbException { public void removeForum(Forum f) throws DbException {
lock.writeLock().lock();
try { try {
// Update the list of forums shared with each contact // Update the list shared with each contact
for (Contact c : contactManager.getContacts()) { Transaction txn = db.startTransaction();
Group contactGroup = getContactGroup(c); try {
removeFromList(contactGroup.getId(), f); for (Contact c : db.getContacts(txn))
removeFromList(txn, getContactGroup(c).getId(), f);
db.removeGroup(txn, f.getGroup());
txn.setComplete();
} finally {
db.endTransaction(txn);
} }
db.removeGroup(f.getGroup());
} catch (IOException e) { } catch (IOException e) {
throw new DbException(e); throw new DbException(e);
} finally {
lock.writeLock().unlock();
} }
} }
@Override @Override
public Collection<Forum> getAvailableForums() throws DbException { public Collection<Forum> getAvailableForums() throws DbException {
lock.readLock().lock();
try { try {
// Get any forums we subscribe to
Set<Group> subscribed = new HashSet<Group>(db.getGroups(
forumManager.getClientId()));
// Get all forums shared by contacts
Set<Forum> available = new HashSet<Forum>(); Set<Forum> available = new HashSet<Forum>();
for (Contact c : contactManager.getContacts()) { Transaction txn = db.startTransaction();
Group g = getContactGroup(c); try {
// Find the latest update version // Get any forums we subscribe to
LatestUpdate latest = findLatest(g.getId(), false); Set<Group> subscribed = new HashSet<Group>(db.getGroups(txn,
if (latest != null) { forumManager.getClientId()));
// Retrieve and parse the latest update // Get all forums shared by contacts
byte[] raw = db.getRawMessage(latest.messageId); for (Contact c : db.getContacts(txn)) {
for (Forum f : parseForumList(raw)) { Group g = getContactGroup(c);
if (!subscribed.contains(f.getGroup())) // Find the latest update version
available.add(f); LatestUpdate latest = findLatest(txn, g.getId(), false);
if (latest != null) {
// Retrieve and parse the latest update
byte[] raw = db.getRawMessage(txn, latest.messageId);
for (Forum f : parseForumList(raw)) {
if (!subscribed.contains(f.getGroup()))
available.add(f);
}
} }
} }
txn.setComplete();
} finally {
db.endTransaction(txn);
} }
return Collections.unmodifiableSet(available); return Collections.unmodifiableSet(available);
} catch (IOException e) { } catch (IOException e) {
throw new DbException(e); throw new DbException(e);
} finally {
lock.readLock().unlock();
} }
} }
@Override @Override
public Collection<Contact> getSharedBy(GroupId g) throws DbException { public Collection<Contact> getSharedBy(GroupId g) throws DbException {
lock.readLock().lock();
try { try {
List<Contact> subscribers = new ArrayList<Contact>(); List<Contact> subscribers = new ArrayList<Contact>();
for (Contact c : contactManager.getContacts()) { Transaction txn = db.startTransaction();
Group contactGroup = getContactGroup(c); try {
if (listContains(contactGroup.getId(), g, false)) for (Contact c : db.getContacts(txn)) {
subscribers.add(c); if (listContains(txn, getContactGroup(c).getId(), g, false))
subscribers.add(c);
}
txn.setComplete();
} finally {
db.endTransaction(txn);
} }
return Collections.unmodifiableList(subscribers); return Collections.unmodifiableList(subscribers);
} catch (IOException e) { } catch (IOException e) {
throw new DbException(e); throw new DbException(e);
} finally {
lock.readLock().unlock();
} }
} }
@Override @Override
public Collection<ContactId> getSharedWith(GroupId g) throws DbException { public Collection<ContactId> getSharedWith(GroupId g) throws DbException {
lock.readLock().lock();
try { try {
List<ContactId> shared = new ArrayList<ContactId>(); List<ContactId> shared = new ArrayList<ContactId>();
for (Contact c : contactManager.getContacts()) { Transaction txn = db.startTransaction();
Group contactGroup = getContactGroup(c); try {
if (listContains(contactGroup.getId(), g, true)) for (Contact c : db.getContacts(txn)) {
shared.add(c.getId()); if (listContains(txn, getContactGroup(c).getId(), g, true))
shared.add(c.getId());
}
txn.setComplete();
} finally {
db.endTransaction(txn);
} }
return Collections.unmodifiableList(shared); return Collections.unmodifiableList(shared);
} catch (FormatException e) { } catch (FormatException e) {
throw new DbException(e); throw new DbException(e);
} finally {
lock.readLock().unlock();
} }
} }
@Override @Override
public void setSharedWith(GroupId g, Collection<ContactId> shared) public void setSharedWith(GroupId g, Collection<ContactId> shared)
throws DbException { throws DbException {
lock.writeLock().lock();
try { try {
// Retrieve the forum Transaction txn = db.startTransaction();
Forum f = parseForum(db.getGroup(g)); try {
// Remove the forum from the list of forums shared with all contacts // Retrieve the forum
removeFromList(localGroup.getId(), f); Forum f = parseForum(db.getGroup(txn, g));
// Update the list of forums shared with each contact // Remove the forum from the list shared with all contacts
shared = new HashSet<ContactId>(shared); removeFromList(txn, localGroup.getId(), f);
for (Contact c : contactManager.getContacts()) { // Update the list shared with each contact
Group contactGroup = getContactGroup(c); shared = new HashSet<ContactId>(shared);
if (shared.contains(c.getId())) { for (Contact c : db.getContacts(txn)) {
if (addToList(contactGroup.getId(), f)) { Group cg = getContactGroup(c);
// If the contact is sharing the forum, make it visible if (shared.contains(c.getId())) {
if (listContains(contactGroup.getId(), g, false)) if (addToList(txn, cg.getId(), f)) {
db.setVisibleToContact(c.getId(), g, true); if (listContains(txn, cg.getId(), g, false))
db.setVisibleToContact(txn, c.getId(), g, true);
}
} else {
removeFromList(txn, cg.getId(), f);
db.setVisibleToContact(txn, c.getId(), g, false);
} }
} else {
removeFromList(contactGroup.getId(), f);
db.setVisibleToContact(c.getId(), g, false);
} }
txn.setComplete();
} finally {
db.endTransaction(txn);
} }
} catch (FormatException e) { } catch (FormatException e) {
throw new DbException(e); throw new DbException(e);
} finally {
lock.writeLock().unlock();
} }
} }
@Override @Override
public void setSharedWithAll(GroupId g) throws DbException { public void setSharedWithAll(GroupId g) throws DbException {
lock.writeLock().lock();
try { try {
// Retrieve the forum Transaction txn = db.startTransaction();
Forum f = parseForum(db.getGroup(g)); try {
// Add the forum to the list of forums shared with all contacts // Retrieve the forum
addToList(localGroup.getId(), f); Forum f = parseForum(db.getGroup(txn, g));
// Add the forum to the list of forums shared with each contact // Add the forum to the list shared with all contacts
for (Contact c : contactManager.getContacts()) { addToList(txn, localGroup.getId(), f);
Group contactGroup = getContactGroup(c); // Add the forum to the list shared with each contact
if (addToList(contactGroup.getId(), f)) { for (Contact c : db.getContacts(txn)) {
// If the contact is sharing the forum, make it visible Group cg = getContactGroup(c);
if (listContains(contactGroup.getId(), g, false)) if (addToList(txn, cg.getId(), f)) {
db.setVisibleToContact(getContactId(g), g, true); if (listContains(txn, cg.getId(), g, false))
db.setVisibleToContact(txn, c.getId(), g, true);
}
} }
txn.setComplete();
} finally {
db.endTransaction(txn);
} }
} catch (FormatException e) { } catch (FormatException e) {
throw new DbException(e); throw new DbException(e);
} finally {
lock.writeLock().unlock();
} }
} }
@@ -329,23 +313,21 @@ class ForumSharingManagerImpl implements ForumSharingManager, AddContactHook,
return privateGroupFactory.createPrivateGroup(CLIENT_ID, c); return privateGroupFactory.createPrivateGroup(CLIENT_ID, c);
} }
// Locking: lock.writeLock private List<Forum> getForumsSharedWithAllContacts(Transaction txn)
private List<Forum> getForumsSharedWithAllContacts() throws DbException, throws DbException, FormatException {
FormatException {
// Ensure the local group exists // Ensure the local group exists
db.addGroup(localGroup); db.addGroup(txn, localGroup);
// Find the latest update in the local group // Find the latest update in the local group
LatestUpdate latest = findLatest(localGroup.getId(), true); LatestUpdate latest = findLatest(txn, localGroup.getId(), true);
if (latest == null) return Collections.emptyList(); if (latest == null) return Collections.emptyList();
// Retrieve and parse the latest update // Retrieve and parse the latest update
return parseForumList(db.getRawMessage(latest.messageId)); return parseForumList(db.getRawMessage(txn, latest.messageId));
} }
// Locking: lock.readLock private LatestUpdate findLatest(Transaction txn, GroupId g, boolean local)
private LatestUpdate findLatest(GroupId g, boolean local)
throws DbException, FormatException { throws DbException, FormatException {
LatestUpdate latest = null; LatestUpdate latest = null;
Map<MessageId, Metadata> metadata = db.getMessageMetadata(g); Map<MessageId, Metadata> metadata = db.getMessageMetadata(txn, g);
for (Entry<MessageId, Metadata> e : metadata.entrySet()) { for (Entry<MessageId, Metadata> e : metadata.entrySet()) {
BdfDictionary d = metadataParser.parse(e.getValue()); BdfDictionary d = metadataParser.parse(e.getValue());
if (d.getBoolean("local") != local) continue; if (d.getBoolean("local") != local) continue;
@@ -384,16 +366,20 @@ class ForumSharingManagerImpl implements ForumSharingManager, AddContactHook,
} }
} }
// Locking: lock.writeLock private void storeMessage(Transaction txn, GroupId g, List<Forum> forums,
private void storeMessage(GroupId g, List<Forum> forums, long version) long version) throws DbException {
throws DbException, FormatException { try {
byte[] body = encodeForumList(forums, version); byte[] body = encodeForumList(forums, version);
long now = clock.currentTimeMillis(); long now = clock.currentTimeMillis();
Message m = messageFactory.createMessage(g, now, body); Message m = messageFactory.createMessage(g, now, body);
BdfDictionary d = new BdfDictionary(); BdfDictionary d = new BdfDictionary();
d.put("version", version); d.put("version", version);
d.put("local", true); d.put("local", true);
db.addLocalMessage(m, CLIENT_ID, metadataEncoder.encode(d), true); Metadata meta = metadataEncoder.encode(d);
db.addLocalMessage(txn, m, CLIENT_ID, meta, true);
} catch (FormatException e) {
throw new RuntimeException(e);
}
} }
private byte[] encodeForumList(List<Forum> forums, long version) { private byte[] encodeForumList(List<Forum> forums, long version) {
@@ -418,23 +404,21 @@ class ForumSharingManagerImpl implements ForumSharingManager, AddContactHook,
return out.toByteArray(); return out.toByteArray();
} }
// Locking: lock.readLock private ContactId getContactId(Transaction txn, GroupId contactGroupId)
private ContactId getContactId(GroupId contactGroupId) throws DbException, throws DbException, FormatException {
FormatException { Metadata meta = db.getGroupMetadata(txn, contactGroupId);
Metadata meta = db.getGroupMetadata(contactGroupId);
BdfDictionary d = metadataParser.parse(meta); BdfDictionary d = metadataParser.parse(meta);
return new ContactId(d.getInteger("contactId").intValue()); return new ContactId(d.getInteger("contactId").intValue());
} }
// Locking: lock.readLock private Set<GroupId> getVisibleForums(Transaction txn,
private Set<GroupId> getVisibleForums(Message remoteUpdate) Message remoteUpdate) throws DbException, FormatException {
throws DbException, FormatException {
// Get the latest local update // Get the latest local update
LatestUpdate local = findLatest(remoteUpdate.getGroupId(), true); LatestUpdate local = findLatest(txn, remoteUpdate.getGroupId(), true);
// If there's no local update, no forums are visible // If there's no local update, no forums are visible
if (local == null) return Collections.emptySet(); if (local == null) return Collections.emptySet();
// Intersect the sets of shared forums // Intersect the sets of shared forums
byte[] localRaw = db.getRawMessage(local.messageId); byte[] localRaw = db.getRawMessage(txn, local.messageId);
Set<Forum> shared = new HashSet<Forum>(parseForumList(localRaw)); Set<Forum> shared = new HashSet<Forum>(parseForumList(localRaw));
shared.retainAll(parseForumList(remoteUpdate.getRaw())); shared.retainAll(parseForumList(remoteUpdate.getRaw()));
// Forums in the intersection should be visible // Forums in the intersection should be visible
@@ -443,16 +427,15 @@ class ForumSharingManagerImpl implements ForumSharingManager, AddContactHook,
return visible; return visible;
} }
// Locking: lock.writeLock private void setForumVisibility(Transaction txn, ContactId c,
private void setForumVisibility(ContactId c, Set<GroupId> visible) Set<GroupId> visible) throws DbException {
throws DbException { for (Group g : db.getGroups(txn, forumManager.getClientId())) {
for (Group g : db.getGroups(forumManager.getClientId())) { boolean isVisible = db.isVisibleToContact(txn, c, g.getId());
boolean isVisible = db.isVisibleToContact(c, g.getId());
boolean shouldBeVisible = visible.contains(g.getId()); boolean shouldBeVisible = visible.contains(g.getId());
if (isVisible && !shouldBeVisible) if (isVisible && !shouldBeVisible)
db.setVisibleToContact(c, g.getId(), false); db.setVisibleToContact(txn, c, g.getId(), false);
else if (!isVisible && shouldBeVisible) else if (!isVisible && shouldBeVisible)
db.setVisibleToContact(c, g.getId(), true); db.setVisibleToContact(txn, c, g.getId(), true);
} }
} }
@@ -491,38 +474,38 @@ class ForumSharingManagerImpl implements ForumSharingManager, AddContactHook,
} }
} }
// Locking: lock.readLock private boolean listContains(Transaction txn, GroupId g, GroupId forum,
private boolean listContains(GroupId g, GroupId forum, boolean local) boolean local) throws DbException, FormatException {
throws DbException, FormatException { LatestUpdate latest = findLatest(txn, g, local);
LatestUpdate latest = findLatest(g, local);
if (latest == null) return false; if (latest == null) return false;
List<Forum> list = parseForumList(db.getRawMessage(latest.messageId)); byte[] raw = db.getRawMessage(txn, latest.messageId);
List<Forum> list = parseForumList(raw);
for (Forum f : list) if (f.getId().equals(forum)) return true; for (Forum f : list) if (f.getId().equals(forum)) return true;
return false; return false;
} }
// Locking: lock.writeLock private boolean addToList(Transaction txn, GroupId g, Forum f)
private boolean addToList(GroupId g, Forum f) throws DbException, throws DbException, FormatException {
FormatException { LatestUpdate latest = findLatest(txn, g, true);
LatestUpdate latest = findLatest(g, true);
if (latest == null) { if (latest == null) {
storeMessage(g, Collections.singletonList(f), 0); storeMessage(txn, g, Collections.singletonList(f), 0);
return true; return true;
} }
List<Forum> list = parseForumList(db.getRawMessage(latest.messageId)); byte[] raw = db.getRawMessage(txn, latest.messageId);
List<Forum> list = parseForumList(raw);
if (list.contains(f)) return false; if (list.contains(f)) return false;
list.add(f); list.add(f);
storeMessage(g, list, latest.version + 1); storeMessage(txn, g, list, latest.version + 1);
return true; return true;
} }
// Locking: lock.writeLock private void removeFromList(Transaction txn, GroupId g, Forum f)
private void removeFromList(GroupId g, Forum f) throws DbException, throws DbException, FormatException {
FormatException { LatestUpdate latest = findLatest(txn, g, true);
LatestUpdate latest = findLatest(g, true);
if (latest == null) return; if (latest == null) return;
List<Forum> list = parseForumList(db.getRawMessage(latest.messageId)); byte[] raw = db.getRawMessage(txn, latest.messageId);
if (list.remove(f)) storeMessage(g, list, latest.version + 1); List<Forum> list = parseForumList(raw);
if (list.remove(f)) storeMessage(txn, g, list, latest.version + 1);
} }
private static class LatestUpdate { private static class LatestUpdate {

View File

@@ -1,4 +1,4 @@
package org.briarproject.sync; package org.briarproject.identity;
import org.briarproject.api.crypto.CryptoComponent; import org.briarproject.api.crypto.CryptoComponent;
import org.briarproject.api.data.BdfWriter; import org.briarproject.api.data.BdfWriter;
@@ -14,9 +14,6 @@ import java.io.IOException;
import javax.inject.Inject; import javax.inject.Inject;
import static org.briarproject.api.db.StorageStatus.ADDING;
// TODO: Move this class to the identity package
class AuthorFactoryImpl implements AuthorFactory { class AuthorFactoryImpl implements AuthorFactory {
private final CryptoComponent crypto; private final CryptoComponent crypto;
@@ -38,7 +35,7 @@ class AuthorFactoryImpl implements AuthorFactory {
public LocalAuthor createLocalAuthor(String name, byte[] publicKey, public LocalAuthor createLocalAuthor(String name, byte[] publicKey,
byte[] privateKey) { byte[] privateKey) {
return new LocalAuthor(getId(name, publicKey), name, publicKey, return new LocalAuthor(getId(name, publicKey), name, publicKey,
privateKey, clock.currentTimeMillis(), ADDING); privateKey, clock.currentTimeMillis());
} }
private AuthorId getId(String name, byte[] publicKey) { private AuthorId getId(String name, byte[] publicKey) {

View File

@@ -1,4 +1,4 @@
package org.briarproject.sync; package org.briarproject.identity;
import org.briarproject.api.FormatException; import org.briarproject.api.FormatException;
import org.briarproject.api.data.BdfReader; import org.briarproject.api.data.BdfReader;
@@ -11,7 +11,6 @@ import java.io.IOException;
import static org.briarproject.api.identity.AuthorConstants.MAX_AUTHOR_NAME_LENGTH; import static org.briarproject.api.identity.AuthorConstants.MAX_AUTHOR_NAME_LENGTH;
import static org.briarproject.api.identity.AuthorConstants.MAX_PUBLIC_KEY_LENGTH; import static org.briarproject.api.identity.AuthorConstants.MAX_PUBLIC_KEY_LENGTH;
// TODO: Move this class to the identity package
class AuthorReader implements ObjectReader<Author> { class AuthorReader implements ObjectReader<Author> {
private final AuthorFactory authorFactory; private final AuthorFactory authorFactory;

View File

@@ -4,74 +4,28 @@ import com.google.inject.Inject;
import org.briarproject.api.db.DatabaseComponent; import org.briarproject.api.db.DatabaseComponent;
import org.briarproject.api.db.DbException; import org.briarproject.api.db.DbException;
import org.briarproject.api.db.NoSuchLocalAuthorException; import org.briarproject.api.db.Transaction;
import org.briarproject.api.event.EventBus;
import org.briarproject.api.event.LocalAuthorAddedEvent;
import org.briarproject.api.event.LocalAuthorRemovedEvent;
import org.briarproject.api.identity.AuthorId; import org.briarproject.api.identity.AuthorId;
import org.briarproject.api.identity.IdentityManager; import org.briarproject.api.identity.IdentityManager;
import org.briarproject.api.identity.LocalAuthor; import org.briarproject.api.identity.LocalAuthor;
import org.briarproject.api.lifecycle.Service;
import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.CopyOnWriteArrayList;
import java.util.logging.Logger;
import static java.util.logging.Level.WARNING; class IdentityManagerImpl implements IdentityManager {
import static org.briarproject.api.db.StorageStatus.ACTIVE;
import static org.briarproject.api.db.StorageStatus.ADDING;
import static org.briarproject.api.db.StorageStatus.REMOVING;
class IdentityManagerImpl implements IdentityManager, Service {
private static final Logger LOG =
Logger.getLogger(IdentityManagerImpl.class.getName());
private final DatabaseComponent db; private final DatabaseComponent db;
private final EventBus eventBus;
private final List<AddIdentityHook> addHooks; private final List<AddIdentityHook> addHooks;
private final List<RemoveIdentityHook> removeHooks; private final List<RemoveIdentityHook> removeHooks;
@Inject @Inject
IdentityManagerImpl(DatabaseComponent db, EventBus eventBus) { IdentityManagerImpl(DatabaseComponent db) {
this.db = db; this.db = db;
this.eventBus = eventBus;
addHooks = new CopyOnWriteArrayList<AddIdentityHook>(); addHooks = new CopyOnWriteArrayList<AddIdentityHook>();
removeHooks = new CopyOnWriteArrayList<RemoveIdentityHook>(); removeHooks = new CopyOnWriteArrayList<RemoveIdentityHook>();
} }
@Override
public boolean start() {
// Finish adding/removing any partly added/removed pseudonyms
try {
for (LocalAuthor a : db.getLocalAuthors()) {
if (a.getStatus().equals(ADDING)) {
for (AddIdentityHook hook : addHooks)
hook.addingIdentity(a.getId());
db.setLocalAuthorStatus(a.getId(), ACTIVE);
eventBus.broadcast(new LocalAuthorAddedEvent(a.getId()));
} else if (a.getStatus().equals(REMOVING)) {
for (RemoveIdentityHook hook : removeHooks)
hook.removingIdentity(a.getId());
db.removeLocalAuthor(a.getId());
eventBus.broadcast(new LocalAuthorRemovedEvent(a.getId()));
}
}
return true;
} catch (DbException e) {
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
return false;
}
}
@Override
public boolean stop() {
return false;
}
@Override @Override
public void registerAddIdentityHook(AddIdentityHook hook) { public void registerAddIdentityHook(AddIdentityHook hook) {
addHooks.add(hook); addHooks.add(hook);
@@ -83,35 +37,55 @@ class IdentityManagerImpl implements IdentityManager, Service {
} }
@Override @Override
public void addLocalAuthor(LocalAuthor a) throws DbException { public void addLocalAuthor(LocalAuthor localAuthor) throws DbException {
db.addLocalAuthor(a); Transaction txn = db.startTransaction();
for (AddIdentityHook hook : addHooks) hook.addingIdentity(a.getId()); try {
db.setLocalAuthorStatus(a.getId(), ACTIVE); db.addLocalAuthor(txn, localAuthor);
eventBus.broadcast(new LocalAuthorAddedEvent(a.getId())); for (AddIdentityHook hook : addHooks)
hook.addingIdentity(txn, localAuthor);
txn.setComplete();
} finally {
db.endTransaction(txn);
}
} }
@Override @Override
public LocalAuthor getLocalAuthor(AuthorId a) throws DbException { public LocalAuthor getLocalAuthor(AuthorId a) throws DbException {
LocalAuthor author = db.getLocalAuthor(a); LocalAuthor author;
if (author.getStatus().equals(ACTIVE)) return author; Transaction txn = db.startTransaction();
throw new NoSuchLocalAuthorException(); try {
author = db.getLocalAuthor(txn, a);
txn.setComplete();
} finally {
db.endTransaction(txn);
}
return author;
} }
@Override @Override
public Collection<LocalAuthor> getLocalAuthors() throws DbException { public Collection<LocalAuthor> getLocalAuthors() throws DbException {
Collection<LocalAuthor> authors = db.getLocalAuthors(); Collection<LocalAuthor> authors;
// Filter out any pseudonyms that are being added or removed Transaction txn = db.startTransaction();
List<LocalAuthor> active = new ArrayList<LocalAuthor>(authors.size()); try {
for (LocalAuthor a : authors) authors = db.getLocalAuthors(txn);
if (a.getStatus().equals(ACTIVE)) active.add(a); txn.setComplete();
return Collections.unmodifiableList(active); } finally {
db.endTransaction(txn);
}
return authors;
} }
@Override @Override
public void removeLocalAuthor(AuthorId a) throws DbException { public void removeLocalAuthor(AuthorId a) throws DbException {
db.setLocalAuthorStatus(a, REMOVING); Transaction txn = db.startTransaction();
for (RemoveIdentityHook hook : removeHooks) hook.removingIdentity(a); try {
db.removeLocalAuthor(a); LocalAuthor localAuthor = db.getLocalAuthor(txn, a);
eventBus.broadcast(new LocalAuthorRemovedEvent(a)); for (RemoveIdentityHook hook : removeHooks)
hook.removingIdentity(txn, localAuthor);
db.removeLocalAuthor(txn, a);
txn.setComplete();
} finally {
db.endTransaction(txn);
}
} }
} }

View File

@@ -1,13 +1,23 @@
package org.briarproject.identity; package org.briarproject.identity;
import com.google.inject.AbstractModule; import com.google.inject.AbstractModule;
import com.google.inject.Provides;
import org.briarproject.api.data.ObjectReader;
import org.briarproject.api.identity.Author;
import org.briarproject.api.identity.AuthorFactory;
import org.briarproject.api.identity.IdentityManager; import org.briarproject.api.identity.IdentityManager;
public class IdentityModule extends AbstractModule { public class IdentityModule extends AbstractModule {
@Override @Override
protected void configure() { protected void configure() {
bind(AuthorFactory.class).to(AuthorFactoryImpl.class);
bind(IdentityManager.class).to(IdentityManagerImpl.class); bind(IdentityManager.class).to(IdentityManagerImpl.class);
} }
@Provides
ObjectReader<Author> getAuthorReader(AuthorFactory authorFactory) {
return new AuthorReader(authorFactory);
}
} }

View File

@@ -5,7 +5,6 @@ import com.google.inject.Inject;
import org.briarproject.api.FormatException; import org.briarproject.api.FormatException;
import org.briarproject.api.contact.Contact; import org.briarproject.api.contact.Contact;
import org.briarproject.api.contact.ContactId; import org.briarproject.api.contact.ContactId;
import org.briarproject.api.contact.ContactManager;
import org.briarproject.api.contact.ContactManager.AddContactHook; import org.briarproject.api.contact.ContactManager.AddContactHook;
import org.briarproject.api.contact.ContactManager.RemoveContactHook; import org.briarproject.api.contact.ContactManager.RemoveContactHook;
import org.briarproject.api.data.BdfDictionary; import org.briarproject.api.data.BdfDictionary;
@@ -16,7 +15,7 @@ import org.briarproject.api.data.MetadataParser;
import org.briarproject.api.db.DatabaseComponent; import org.briarproject.api.db.DatabaseComponent;
import org.briarproject.api.db.DbException; import org.briarproject.api.db.DbException;
import org.briarproject.api.db.Metadata; import org.briarproject.api.db.Metadata;
import org.briarproject.api.db.NoSuchContactException; import org.briarproject.api.db.Transaction;
import org.briarproject.api.messaging.MessagingManager; import org.briarproject.api.messaging.MessagingManager;
import org.briarproject.api.messaging.PrivateMessage; import org.briarproject.api.messaging.PrivateMessage;
import org.briarproject.api.messaging.PrivateMessageHeader; import org.briarproject.api.messaging.PrivateMessageHeader;
@@ -32,7 +31,6 @@ import java.io.ByteArrayInputStream;
import java.io.IOException; import java.io.IOException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.Collections;
import java.util.Map; import java.util.Map;
import java.util.logging.Logger; import java.util.logging.Logger;
@@ -51,19 +49,17 @@ class MessagingManagerImpl implements MessagingManager, AddContactHook,
Logger.getLogger(MessagingManagerImpl.class.getName()); Logger.getLogger(MessagingManagerImpl.class.getName());
private final DatabaseComponent db; private final DatabaseComponent db;
private final ContactManager contactManager;
private final PrivateGroupFactory privateGroupFactory; private final PrivateGroupFactory privateGroupFactory;
private final BdfReaderFactory bdfReaderFactory; private final BdfReaderFactory bdfReaderFactory;
private final MetadataEncoder metadataEncoder; private final MetadataEncoder metadataEncoder;
private final MetadataParser metadataParser; private final MetadataParser metadataParser;
@Inject @Inject
MessagingManagerImpl(DatabaseComponent db, ContactManager contactManager, MessagingManagerImpl(DatabaseComponent db,
PrivateGroupFactory privateGroupFactory, PrivateGroupFactory privateGroupFactory,
BdfReaderFactory bdfReaderFactory, MetadataEncoder metadataEncoder, BdfReaderFactory bdfReaderFactory, MetadataEncoder metadataEncoder,
MetadataParser metadataParser) { MetadataParser metadataParser) {
this.db = db; this.db = db;
this.contactManager = contactManager;
this.privateGroupFactory = privateGroupFactory; this.privateGroupFactory = privateGroupFactory;
this.bdfReaderFactory = bdfReaderFactory; this.bdfReaderFactory = bdfReaderFactory;
this.metadataEncoder = metadataEncoder; this.metadataEncoder = metadataEncoder;
@@ -71,19 +67,17 @@ class MessagingManagerImpl implements MessagingManager, AddContactHook,
} }
@Override @Override
public void addingContact(ContactId c) { public void addingContact(Transaction txn, Contact c) throws DbException {
try { try {
// Create a group to share with the contact // Create a group to share with the contact
Group g = getContactGroup(db.getContact(c)); Group g = getContactGroup(c);
// Store the group and share it with the contact // Store the group and share it with the contact
db.addGroup(g); db.addGroup(txn, g);
db.setVisibility(g.getId(), Collections.singletonList(c)); db.setVisibleToContact(txn, c.getId(), g.getId(), true);
// Attach the contact ID to the group // Attach the contact ID to the group
BdfDictionary d = new BdfDictionary(); BdfDictionary d = new BdfDictionary();
d.put("contactId", c.getInt()); d.put("contactId", c.getId().getInt());
db.mergeGroupMetadata(g.getId(), metadataEncoder.encode(d)); db.mergeGroupMetadata(txn, g.getId(), metadataEncoder.encode(d));
} catch (DbException e) {
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
} catch (FormatException e) { } catch (FormatException e) {
throw new RuntimeException(e); throw new RuntimeException(e);
} }
@@ -94,12 +88,8 @@ class MessagingManagerImpl implements MessagingManager, AddContactHook,
} }
@Override @Override
public void removingContact(ContactId c) { public void removingContact(Transaction txn, Contact c) throws DbException {
try { db.removeGroup(txn, getContactGroup(c));
db.removeGroup(getContactGroup(db.getContact(c)));
} catch (DbException e) {
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
}
} }
@Override @Override
@@ -109,15 +99,22 @@ class MessagingManagerImpl implements MessagingManager, AddContactHook,
@Override @Override
public void addLocalMessage(PrivateMessage m) throws DbException { public void addLocalMessage(PrivateMessage m) throws DbException {
BdfDictionary d = new BdfDictionary();
d.put("timestamp", m.getMessage().getTimestamp());
if (m.getParent() != null) d.put("parent", m.getParent().getBytes());
d.put("contentType", m.getContentType());
d.put("local", true);
d.put("read", true);
try { try {
BdfDictionary d = new BdfDictionary();
d.put("timestamp", m.getMessage().getTimestamp());
if (m.getParent() != null)
d.put("parent", m.getParent().getBytes());
d.put("contentType", m.getContentType());
d.put("local", true);
d.put("read", true);
Metadata meta = metadataEncoder.encode(d); Metadata meta = metadataEncoder.encode(d);
db.addLocalMessage(m.getMessage(), CLIENT_ID, meta, true); Transaction txn = db.startTransaction();
try {
db.addLocalMessage(txn, m.getMessage(), CLIENT_ID, meta, true);
txn.setComplete();
} finally {
db.endTransaction(txn);
}
} catch (FormatException e) { } catch (FormatException e) {
throw new RuntimeException(e); throw new RuntimeException(e);
} }
@@ -126,26 +123,48 @@ class MessagingManagerImpl implements MessagingManager, AddContactHook,
@Override @Override
public ContactId getContactId(GroupId g) throws DbException { public ContactId getContactId(GroupId g) throws DbException {
try { try {
BdfDictionary d = metadataParser.parse(db.getGroupMetadata(g)); Metadata meta;
long id = d.getInteger("contactId"); Transaction txn = db.startTransaction();
return new ContactId((int) id); try {
meta = db.getGroupMetadata(txn, g);
txn.setComplete();
} finally {
db.endTransaction(txn);
}
BdfDictionary d = metadataParser.parse(meta);
return new ContactId(d.getInteger("contactId").intValue());
} catch (FormatException e) { } catch (FormatException e) {
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e); throw new DbException(e);
throw new NoSuchContactException();
} }
} }
@Override @Override
public GroupId getConversationId(ContactId c) throws DbException { public GroupId getConversationId(ContactId c) throws DbException {
return getContactGroup(contactManager.getContact(c)).getId(); Contact contact;
Transaction txn = db.startTransaction();
try {
contact = db.getContact(txn, c);
txn.setComplete();
} finally {
db.endTransaction(txn);
}
return getContactGroup(contact).getId();
} }
@Override @Override
public Collection<PrivateMessageHeader> getMessageHeaders(ContactId c) public Collection<PrivateMessageHeader> getMessageHeaders(ContactId c)
throws DbException { throws DbException {
GroupId groupId = getConversationId(c); Map<MessageId, Metadata> metadata;
Map<MessageId, Metadata> metadata = db.getMessageMetadata(groupId); Collection<MessageStatus> statuses;
Collection<MessageStatus> statuses = db.getMessageStatus(c, groupId); Transaction txn = db.startTransaction();
try {
GroupId g = getContactGroup(db.getContact(txn, c)).getId();
metadata = db.getMessageMetadata(txn, g);
statuses = db.getMessageStatus(txn, c, g);
txn.setComplete();
} finally {
db.endTransaction(txn);
}
Collection<PrivateMessageHeader> headers = Collection<PrivateMessageHeader> headers =
new ArrayList<PrivateMessageHeader>(); new ArrayList<PrivateMessageHeader>();
for (MessageStatus s : statuses) { for (MessageStatus s : statuses) {
@@ -169,7 +188,14 @@ class MessagingManagerImpl implements MessagingManager, AddContactHook,
@Override @Override
public byte[] getMessageBody(MessageId m) throws DbException { public byte[] getMessageBody(MessageId m) throws DbException {
byte[] raw = db.getRawMessage(m); byte[] raw;
Transaction txn = db.startTransaction();
try {
raw = db.getRawMessage(txn, m);
txn.setComplete();
} finally {
db.endTransaction(txn);
}
ByteArrayInputStream in = new ByteArrayInputStream(raw, ByteArrayInputStream in = new ByteArrayInputStream(raw,
MESSAGE_HEADER_LENGTH, raw.length - MESSAGE_HEADER_LENGTH); MESSAGE_HEADER_LENGTH, raw.length - MESSAGE_HEADER_LENGTH);
BdfReader r = bdfReaderFactory.createReader(in); BdfReader r = bdfReaderFactory.createReader(in);
@@ -192,10 +218,17 @@ class MessagingManagerImpl implements MessagingManager, AddContactHook,
@Override @Override
public void setReadFlag(MessageId m, boolean read) throws DbException { public void setReadFlag(MessageId m, boolean read) throws DbException {
BdfDictionary d = new BdfDictionary();
d.put("read", read);
try { try {
db.mergeMessageMetadata(m, metadataEncoder.encode(d)); BdfDictionary d = new BdfDictionary();
d.put("read", read);
Metadata meta = metadataEncoder.encode(d);
Transaction txn = db.startTransaction();
try {
db.mergeMessageMetadata(txn, m, meta);
txn.setComplete();
} finally {
db.endTransaction(txn);
}
} catch (FormatException e) { } catch (FormatException e) {
throw new RuntimeException(e); throw new RuntimeException(e);
} }

View File

@@ -4,6 +4,7 @@ import org.briarproject.api.TransportId;
import org.briarproject.api.contact.ContactId; import org.briarproject.api.contact.ContactId;
import org.briarproject.api.db.DatabaseComponent; import org.briarproject.api.db.DatabaseComponent;
import org.briarproject.api.db.DbException; import org.briarproject.api.db.DbException;
import org.briarproject.api.db.Transaction;
import org.briarproject.api.event.EventBus; import org.briarproject.api.event.EventBus;
import org.briarproject.api.event.TransportDisabledEvent; import org.briarproject.api.event.TransportDisabledEvent;
import org.briarproject.api.event.TransportEnabledEvent; import org.briarproject.api.event.TransportEnabledEvent;
@@ -27,6 +28,7 @@ import org.briarproject.api.plugins.simplex.SimplexPluginFactory;
import org.briarproject.api.properties.TransportProperties; import org.briarproject.api.properties.TransportProperties;
import org.briarproject.api.properties.TransportPropertyManager; import org.briarproject.api.properties.TransportPropertyManager;
import org.briarproject.api.settings.Settings; import org.briarproject.api.settings.Settings;
import org.briarproject.api.settings.SettingsManager;
import org.briarproject.api.system.Clock; import org.briarproject.api.system.Clock;
import org.briarproject.api.ui.UiCallback; import org.briarproject.api.ui.UiCallback;
@@ -60,6 +62,7 @@ class PluginManagerImpl implements PluginManager, Service {
private final DatabaseComponent db; private final DatabaseComponent db;
private final Poller poller; private final Poller poller;
private final ConnectionManager connectionManager; private final ConnectionManager connectionManager;
private final SettingsManager settingsManager;
private final TransportPropertyManager transportPropertyManager; private final TransportPropertyManager transportPropertyManager;
private final UiCallback uiCallback; private final UiCallback uiCallback;
private final Map<TransportId, Plugin> plugins; private final Map<TransportId, Plugin> plugins;
@@ -72,6 +75,7 @@ class PluginManagerImpl implements PluginManager, Service {
DuplexPluginConfig duplexPluginConfig, Clock clock, DuplexPluginConfig duplexPluginConfig, Clock clock,
DatabaseComponent db, Poller poller, DatabaseComponent db, Poller poller,
ConnectionManager connectionManager, ConnectionManager connectionManager,
SettingsManager settingsManager,
TransportPropertyManager transportPropertyManager, TransportPropertyManager transportPropertyManager,
UiCallback uiCallback) { UiCallback uiCallback) {
this.ioExecutor = ioExecutor; this.ioExecutor = ioExecutor;
@@ -82,6 +86,7 @@ class PluginManagerImpl implements PluginManager, Service {
this.db = db; this.db = db;
this.poller = poller; this.poller = poller;
this.connectionManager = connectionManager; this.connectionManager = connectionManager;
this.settingsManager = settingsManager;
this.transportPropertyManager = transportPropertyManager; this.transportPropertyManager = transportPropertyManager;
this.uiCallback = uiCallback; this.uiCallback = uiCallback;
plugins = new ConcurrentHashMap<TransportId, Plugin>(); plugins = new ConcurrentHashMap<TransportId, Plugin>();
@@ -181,7 +186,13 @@ class PluginManagerImpl implements PluginManager, Service {
} }
try { try {
long start = clock.currentTimeMillis(); long start = clock.currentTimeMillis();
db.addTransport(id, plugin.getMaxLatency()); Transaction txn = db.startTransaction();
try {
db.addTransport(txn, id, plugin.getMaxLatency());
txn.setComplete();
} finally {
db.endTransaction(txn);
}
long duration = clock.currentTimeMillis() - start; long duration = clock.currentTimeMillis() - start;
if (LOG.isLoggable(INFO)) if (LOG.isLoggable(INFO))
LOG.info("Adding transport took " + duration + " ms"); LOG.info("Adding transport took " + duration + " ms");
@@ -244,7 +255,13 @@ class PluginManagerImpl implements PluginManager, Service {
} }
try { try {
long start = clock.currentTimeMillis(); long start = clock.currentTimeMillis();
db.addTransport(id, plugin.getMaxLatency()); Transaction txn = db.startTransaction();
try {
db.addTransport(txn, id, plugin.getMaxLatency());
txn.setComplete();
} finally {
db.endTransaction(txn);
}
long duration = clock.currentTimeMillis() - start; long duration = clock.currentTimeMillis() - start;
if (LOG.isLoggable(INFO)) if (LOG.isLoggable(INFO))
LOG.info("Adding transport took " + duration + " ms"); LOG.info("Adding transport took " + duration + " ms");
@@ -319,7 +336,7 @@ class PluginManagerImpl implements PluginManager, Service {
public Settings getSettings() { public Settings getSettings() {
try { try {
return db.getSettings(id.getString()); return settingsManager.getSettings(id.getString());
} catch (DbException e) { } catch (DbException e) {
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e); if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
return new Settings(); return new Settings();
@@ -348,7 +365,7 @@ class PluginManagerImpl implements PluginManager, Service {
public void mergeSettings(Settings s) { public void mergeSettings(Settings s) {
try { try {
db.mergeSettings(s, id.getString()); settingsManager.mergeSettings(s, id.getString());
} catch (DbException e) { } catch (DbException e) {
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e); if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
} }

View File

@@ -7,7 +7,6 @@ import org.briarproject.api.FormatException;
import org.briarproject.api.TransportId; import org.briarproject.api.TransportId;
import org.briarproject.api.contact.Contact; import org.briarproject.api.contact.Contact;
import org.briarproject.api.contact.ContactId; import org.briarproject.api.contact.ContactId;
import org.briarproject.api.contact.ContactManager;
import org.briarproject.api.contact.ContactManager.AddContactHook; import org.briarproject.api.contact.ContactManager.AddContactHook;
import org.briarproject.api.contact.ContactManager.RemoveContactHook; import org.briarproject.api.contact.ContactManager.RemoveContactHook;
import org.briarproject.api.data.BdfDictionary; import org.briarproject.api.data.BdfDictionary;
@@ -21,6 +20,7 @@ import org.briarproject.api.db.DatabaseComponent;
import org.briarproject.api.db.DbException; import org.briarproject.api.db.DbException;
import org.briarproject.api.db.Metadata; import org.briarproject.api.db.Metadata;
import org.briarproject.api.db.NoSuchGroupException; import org.briarproject.api.db.NoSuchGroupException;
import org.briarproject.api.db.Transaction;
import org.briarproject.api.properties.TransportProperties; import org.briarproject.api.properties.TransportProperties;
import org.briarproject.api.properties.TransportPropertyManager; import org.briarproject.api.properties.TransportPropertyManager;
import org.briarproject.api.sync.ClientId; import org.briarproject.api.sync.ClientId;
@@ -41,10 +41,7 @@ import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import java.util.Map.Entry; import java.util.Map.Entry;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import java.util.logging.Logger;
import static java.util.logging.Level.WARNING;
import static org.briarproject.api.properties.TransportPropertyConstants.MAX_PROPERTY_LENGTH; import static org.briarproject.api.properties.TransportPropertyConstants.MAX_PROPERTY_LENGTH;
import static org.briarproject.api.sync.SyncConstants.MESSAGE_HEADER_LENGTH; import static org.briarproject.api.sync.SyncConstants.MESSAGE_HEADER_LENGTH;
@@ -57,11 +54,7 @@ class TransportPropertyManagerImpl implements TransportPropertyManager,
private static final byte[] LOCAL_GROUP_DESCRIPTOR = new byte[0]; private static final byte[] LOCAL_GROUP_DESCRIPTOR = new byte[0];
private static final Logger LOG =
Logger.getLogger(TransportPropertyManagerImpl.class.getName());
private final DatabaseComponent db; private final DatabaseComponent db;
private final ContactManager contactManager;
private final PrivateGroupFactory privateGroupFactory; private final PrivateGroupFactory privateGroupFactory;
private final MessageFactory messageFactory; private final MessageFactory messageFactory;
private final BdfReaderFactory bdfReaderFactory; private final BdfReaderFactory bdfReaderFactory;
@@ -71,18 +64,13 @@ class TransportPropertyManagerImpl implements TransportPropertyManager,
private final Clock clock; private final Clock clock;
private final Group localGroup; private final Group localGroup;
/** Ensures isolation between database reads and writes. */
private final ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
@Inject @Inject
TransportPropertyManagerImpl(DatabaseComponent db, TransportPropertyManagerImpl(DatabaseComponent db,
ContactManager contactManager, GroupFactory groupFactory, GroupFactory groupFactory, PrivateGroupFactory privateGroupFactory,
PrivateGroupFactory privateGroupFactory,
MessageFactory messageFactory, BdfReaderFactory bdfReaderFactory, MessageFactory messageFactory, BdfReaderFactory bdfReaderFactory,
BdfWriterFactory bdfWriterFactory, MetadataEncoder metadataEncoder, BdfWriterFactory bdfWriterFactory, MetadataEncoder metadataEncoder,
MetadataParser metadataParser, Clock clock) { MetadataParser metadataParser, Clock clock) {
this.db = db; this.db = db;
this.contactManager = contactManager;
this.privateGroupFactory = privateGroupFactory; this.privateGroupFactory = privateGroupFactory;
this.messageFactory = messageFactory; this.messageFactory = messageFactory;
this.bdfReaderFactory = bdfReaderFactory; this.bdfReaderFactory = bdfReaderFactory;
@@ -95,165 +83,156 @@ class TransportPropertyManagerImpl implements TransportPropertyManager,
} }
@Override @Override
public void addingContact(ContactId c) { public void addingContact(Transaction txn, Contact c) throws DbException {
lock.writeLock().lock(); // Create a group to share with the contact
try { Group g = getContactGroup(c);
// Create a group to share with the contact // Store the group and share it with the contact
Group g = getContactGroup(db.getContact(c)); db.addGroup(txn, g);
// Store the group and share it with the contact db.setVisibleToContact(txn, c.getId(), g.getId(), true);
db.addGroup(g); // Copy the latest local properties into the group
db.setVisibility(g.getId(), Collections.singletonList(c)); DeviceId dev = db.getDeviceId(txn);
// Copy the latest local properties into the group Map<TransportId, TransportProperties> local = getLocalProperties(txn);
DeviceId dev = db.getDeviceId(); for (Entry<TransportId, TransportProperties> e : local.entrySet()) {
Map<TransportId, TransportProperties> local = getLocalProperties(); storeMessage(txn, g.getId(), dev, e.getKey(), e.getValue(), 1,
for (Entry<TransportId, TransportProperties> e : local.entrySet()) { true, true);
storeMessage(g.getId(), dev, e.getKey(), e.getValue(), 1, true,
true);
}
} catch (DbException e) {
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
} catch (FormatException e) {
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
} finally {
lock.writeLock().unlock();
} }
} }
@Override @Override
public void removingContact(ContactId c) { public void removingContact(Transaction txn, Contact c) throws DbException {
lock.writeLock().lock(); db.removeGroup(txn, getContactGroup(c));
try {
db.removeGroup(getContactGroup(db.getContact(c)));
} catch (DbException e) {
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
} finally {
lock.writeLock().unlock();
}
} }
@Override @Override
public void addRemoteProperties(ContactId c, DeviceId dev, public void addRemoteProperties(ContactId c, DeviceId dev,
Map<TransportId, TransportProperties> props) throws DbException { Map<TransportId, TransportProperties> props) throws DbException {
lock.writeLock().lock(); Transaction txn = db.startTransaction();
try { try {
Group g = getContactGroup(db.getContact(c)); Group g = getContactGroup(db.getContact(txn, c));
for (Entry<TransportId, TransportProperties> e : props.entrySet()) { for (Entry<TransportId, TransportProperties> e : props.entrySet()) {
storeMessage(g.getId(), dev, e.getKey(), e.getValue(), 0, false, storeMessage(txn, g.getId(), dev, e.getKey(), e.getValue(), 0,
false); false, false);
} }
} catch (FormatException e) { txn.setComplete();
throw new DbException(e);
} finally { } finally {
lock.writeLock().unlock(); db.endTransaction(txn);
} }
} }
@Override @Override
public Map<TransportId, TransportProperties> getLocalProperties() public Map<TransportId, TransportProperties> getLocalProperties()
throws DbException { throws DbException {
lock.readLock().lock(); Map<TransportId, TransportProperties> local;
Transaction txn = db.startTransaction();
try { try {
// Find the latest local update for each transport local = getLocalProperties(txn);
Map<TransportId, LatestUpdate> latest = txn.setComplete();
findLatest(localGroup.getId(), true);
// Retrieve and parse the latest local properties
Map<TransportId, TransportProperties> local =
new HashMap<TransportId, TransportProperties>();
for (Entry<TransportId, LatestUpdate> e : latest.entrySet()) {
byte[] raw = db.getRawMessage(e.getValue().messageId);
local.put(e.getKey(), parseProperties(raw));
}
return Collections.unmodifiableMap(local);
} catch (NoSuchGroupException e) {
// Local group doesn't exist - there are no local properties
return Collections.emptyMap();
} catch (IOException e) {
throw new DbException(e);
} finally { } finally {
lock.readLock().unlock(); db.endTransaction(txn);
} }
return Collections.unmodifiableMap(local);
} }
@Override @Override
public TransportProperties getLocalProperties(TransportId t) public TransportProperties getLocalProperties(TransportId t)
throws DbException { throws DbException {
lock.readLock().lock();
try { try {
// Find the latest local update TransportProperties p = null;
LatestUpdate latest = findLatest(localGroup.getId(), t, true); Transaction txn = db.startTransaction();
if (latest == null) return null; try {
// Retrieve and parse the latest local properties // Find the latest local update
return parseProperties(db.getRawMessage(latest.messageId)); LatestUpdate latest = findLatest(txn, localGroup.getId(), t,
true);
if (latest != null) {
// Retrieve and parse the latest local properties
byte[] raw = db.getRawMessage(txn, latest.messageId);
p = parseProperties(raw);
}
txn.setComplete();
} finally {
db.endTransaction(txn);
}
return p;
} catch (NoSuchGroupException e) { } catch (NoSuchGroupException e) {
// Local group doesn't exist - there are no local properties // Local group doesn't exist - there are no local properties
return null; return null;
} catch (IOException e) { } catch (FormatException e) {
throw new DbException(e); throw new DbException(e);
} finally {
lock.readLock().unlock();
} }
} }
@Override @Override
public Map<ContactId, TransportProperties> getRemoteProperties( public Map<ContactId, TransportProperties> getRemoteProperties(
TransportId t) throws DbException { TransportId t) throws DbException {
lock.readLock().lock();
try { try {
Map<ContactId, TransportProperties> remote = Map<ContactId, TransportProperties> remote =
new HashMap<ContactId, TransportProperties>(); new HashMap<ContactId, TransportProperties>();
for (Contact c : contactManager.getContacts()) { Transaction txn = db.startTransaction();
Group g = getContactGroup(c); try {
// Find the latest remote update for (Contact c : db.getContacts(txn)) {
LatestUpdate latest = findLatest(g.getId(), t, false); Group g = getContactGroup(c);
if (latest != null) { // Find the latest remote update
// Retrieve and parse the latest remote properties LatestUpdate latest = findLatest(txn, g.getId(), t, false);
byte[] raw = db.getRawMessage(latest.messageId); if (latest != null) {
remote.put(c.getId(), parseProperties(raw)); // Retrieve and parse the latest remote properties
byte[] raw = db.getRawMessage(txn, latest.messageId);
remote.put(c.getId(), parseProperties(raw));
}
} }
txn.setComplete();
} finally {
db.endTransaction(txn);
} }
return Collections.unmodifiableMap(remote); return Collections.unmodifiableMap(remote);
} catch (IOException e) { } catch (FormatException e) {
throw new DbException(e); throw new DbException(e);
} finally {
lock.readLock().unlock();
} }
} }
@Override @Override
public void mergeLocalProperties(TransportId t, TransportProperties p) public void mergeLocalProperties(TransportId t, TransportProperties p)
throws DbException { throws DbException {
lock.writeLock().lock();
try { try {
// Create the local group if necessary Transaction txn = db.startTransaction();
db.addGroup(localGroup); try {
// Merge the new properties with any existing properties // Create the local group if necessary
TransportProperties merged; db.addGroup(txn, localGroup);
LatestUpdate latest = findLatest(localGroup.getId(), t, true); // Merge the new properties with any existing properties
if (latest == null) { TransportProperties merged;
merged = p; boolean changed;
} else { LatestUpdate latest = findLatest(txn, localGroup.getId(), t,
byte[] raw = db.getRawMessage(latest.messageId); true);
TransportProperties old = parseProperties(raw); if (latest == null) {
merged = new TransportProperties(old); merged = p;
merged.putAll(p); changed = true;
if (merged.equals(old)) return; // Unchanged } else {
byte[] raw = db.getRawMessage(txn, latest.messageId);
TransportProperties old = parseProperties(raw);
merged = new TransportProperties(old);
merged.putAll(p);
changed = !merged.equals(old);
}
if (changed) {
// Store the merged properties in the local group
DeviceId dev = db.getDeviceId(txn);
long version = latest == null ? 1 : latest.version + 1;
storeMessage(txn, localGroup.getId(), dev, t, merged,
version, true, false);
// Store the merged properties in each contact's group
for (Contact c : db.getContacts(txn)) {
Group g = getContactGroup(c);
latest = findLatest(txn, g.getId(), t, true);
version = latest == null ? 1 : latest.version + 1;
storeMessage(txn, g.getId(), dev, t, merged, version,
true, true);
}
}
txn.setComplete();
} finally {
db.endTransaction(txn);
} }
// Store the merged properties in the local group } catch (FormatException e) {
DeviceId dev = db.getDeviceId();
long version = latest == null ? 1 : latest.version + 1;
storeMessage(localGroup.getId(), dev, t, merged, version, true,
false);
// Store the merged properties in each contact's group
for (Contact c : contactManager.getContacts()) {
Group g = getContactGroup(c);
latest = findLatest(g.getId(), t, true);
version = latest == null ? 1 : latest.version + 1;
storeMessage(g.getId(), dev, t, merged, version, true, true);
}
} catch (IOException e) {
throw new DbException(e); throw new DbException(e);
} finally {
lock.writeLock().unlock();
} }
} }
@@ -261,18 +240,44 @@ class TransportPropertyManagerImpl implements TransportPropertyManager,
return privateGroupFactory.createPrivateGroup(CLIENT_ID, c); return privateGroupFactory.createPrivateGroup(CLIENT_ID, c);
} }
// Locking: lock.writeLock private Map<TransportId, TransportProperties> getLocalProperties(
private void storeMessage(GroupId g, DeviceId dev, TransportId t, Transaction txn) throws DbException {
TransportProperties p, long version, boolean local, boolean shared) try {
throws DbException, FormatException { Map<TransportId, TransportProperties> local =
byte[] body = encodeProperties(dev, t, p, version); new HashMap<TransportId, TransportProperties>();
long now = clock.currentTimeMillis(); // Find the latest local update for each transport
Message m = messageFactory.createMessage(g, now, body); Map<TransportId, LatestUpdate> latest = findLatest(txn,
BdfDictionary d = new BdfDictionary(); localGroup.getId(), true);
d.put("transportId", t.getString()); // Retrieve and parse the latest local properties
d.put("version", version); for (Entry<TransportId, LatestUpdate> e : latest.entrySet()) {
d.put("local", local); byte[] raw = db.getRawMessage(txn, e.getValue().messageId);
db.addLocalMessage(m, CLIENT_ID, metadataEncoder.encode(d), shared); local.put(e.getKey(), parseProperties(raw));
}
return local;
} catch (NoSuchGroupException e) {
// Local group doesn't exist - there are no local properties
return Collections.emptyMap();
} catch (FormatException e) {
throw new DbException(e);
}
}
private void storeMessage(Transaction txn, GroupId g, DeviceId dev,
TransportId t, TransportProperties p, long version, boolean local,
boolean shared) throws DbException {
try {
byte[] body = encodeProperties(dev, t, p, version);
long now = clock.currentTimeMillis();
Message m = messageFactory.createMessage(g, now, body);
BdfDictionary d = new BdfDictionary();
d.put("transportId", t.getString());
d.put("version", version);
d.put("local", local);
Metadata meta = metadataEncoder.encode(d);
db.addLocalMessage(txn, m, CLIENT_ID, meta, shared);
} catch (FormatException e) {
throw new RuntimeException(e);
}
} }
private byte[] encodeProperties(DeviceId dev, TransportId t, private byte[] encodeProperties(DeviceId dev, TransportId t,
@@ -293,12 +298,11 @@ class TransportPropertyManagerImpl implements TransportPropertyManager,
return out.toByteArray(); return out.toByteArray();
} }
// Locking: lock.readLock private Map<TransportId, LatestUpdate> findLatest(Transaction txn,
private Map<TransportId, LatestUpdate> findLatest(GroupId g, boolean local) GroupId g, boolean local) throws DbException, FormatException {
throws DbException, FormatException {
Map<TransportId, LatestUpdate> latestUpdates = Map<TransportId, LatestUpdate> latestUpdates =
new HashMap<TransportId, LatestUpdate>(); new HashMap<TransportId, LatestUpdate>();
Map<MessageId, Metadata> metadata = db.getMessageMetadata(g); Map<MessageId, Metadata> metadata = db.getMessageMetadata(txn, g);
for (Entry<MessageId, Metadata> e : metadata.entrySet()) { for (Entry<MessageId, Metadata> e : metadata.entrySet()) {
BdfDictionary d = metadataParser.parse(e.getValue()); BdfDictionary d = metadataParser.parse(e.getValue());
if (d.getBoolean("local") == local) { if (d.getBoolean("local") == local) {
@@ -312,11 +316,10 @@ class TransportPropertyManagerImpl implements TransportPropertyManager,
return latestUpdates; return latestUpdates;
} }
// Locking: lock.readLock private LatestUpdate findLatest(Transaction txn, GroupId g, TransportId t,
private LatestUpdate findLatest(GroupId g, TransportId t, boolean local) boolean local) throws DbException, FormatException {
throws DbException, FormatException {
LatestUpdate latest = null; LatestUpdate latest = null;
Map<MessageId, Metadata> metadata = db.getMessageMetadata(g); Map<MessageId, Metadata> metadata = db.getMessageMetadata(txn, g);
for (Entry<MessageId, Metadata> e : metadata.entrySet()) { for (Entry<MessageId, Metadata> e : metadata.entrySet()) {
BdfDictionary d = metadataParser.parse(e.getValue()); BdfDictionary d = metadataParser.parse(e.getValue());
if (d.getString("transportId").equals(t.getString()) if (d.getString("transportId").equals(t.getString())
@@ -330,25 +333,32 @@ class TransportPropertyManagerImpl implements TransportPropertyManager,
} }
private TransportProperties parseProperties(byte[] raw) private TransportProperties parseProperties(byte[] raw)
throws IOException { throws FormatException {
TransportProperties p = new TransportProperties(); TransportProperties p = new TransportProperties();
ByteArrayInputStream in = new ByteArrayInputStream(raw, ByteArrayInputStream in = new ByteArrayInputStream(raw,
MESSAGE_HEADER_LENGTH, raw.length - MESSAGE_HEADER_LENGTH); MESSAGE_HEADER_LENGTH, raw.length - MESSAGE_HEADER_LENGTH);
BdfReader r = bdfReaderFactory.createReader(in); BdfReader r = bdfReaderFactory.createReader(in);
r.readListStart(); try {
r.skipRaw(); // Device ID r.readListStart();
r.skipString(); // Transport ID r.skipRaw(); // Device ID
r.skipInteger(); // Version r.skipString(); // Transport ID
r.readDictionaryStart(); r.skipInteger(); // Version
while (!r.hasDictionaryEnd()) { r.readDictionaryStart();
String key = r.readString(MAX_PROPERTY_LENGTH); while (!r.hasDictionaryEnd()) {
String value = r.readString(MAX_PROPERTY_LENGTH); String key = r.readString(MAX_PROPERTY_LENGTH);
p.put(key, value); String value = r.readString(MAX_PROPERTY_LENGTH);
p.put(key, value);
}
r.readDictionaryEnd();
r.readListEnd();
if (!r.eof()) throw new FormatException();
return p;
} catch (FormatException e) {
throw e;
} catch (IOException e) {
// Shouldn't happen with ByteArrayInputStream
throw new RuntimeException(e);
} }
r.readDictionaryEnd();
r.readListEnd();
if (!r.eof()) throw new FormatException();
return p;
} }
private static class LatestUpdate { private static class LatestUpdate {

View File

@@ -4,6 +4,7 @@ import com.google.inject.Inject;
import org.briarproject.api.db.DatabaseComponent; import org.briarproject.api.db.DatabaseComponent;
import org.briarproject.api.db.DbException; import org.briarproject.api.db.DbException;
import org.briarproject.api.db.Transaction;
import org.briarproject.api.settings.Settings; import org.briarproject.api.settings.Settings;
import org.briarproject.api.settings.SettingsManager; import org.briarproject.api.settings.SettingsManager;
@@ -18,11 +19,25 @@ class SettingsManagerImpl implements SettingsManager {
@Override @Override
public Settings getSettings(String namespace) throws DbException { public Settings getSettings(String namespace) throws DbException {
return db.getSettings(namespace); Settings s;
Transaction txn = db.startTransaction();
try {
s = db.getSettings(txn, namespace);
txn.setComplete();
} finally {
db.endTransaction(txn);
}
return s;
} }
@Override @Override
public void mergeSettings(Settings s, String namespace) throws DbException { public void mergeSettings(Settings s, String namespace) throws DbException {
db.mergeSettings(s, namespace); Transaction txn = db.startTransaction();
try {
db.mergeSettings(txn, s, namespace);
txn.setComplete();
} finally {
db.endTransaction(txn);
}
} }
} }

View File

@@ -4,6 +4,7 @@ import org.briarproject.api.TransportId;
import org.briarproject.api.contact.ContactId; import org.briarproject.api.contact.ContactId;
import org.briarproject.api.db.DatabaseComponent; import org.briarproject.api.db.DatabaseComponent;
import org.briarproject.api.db.DbException; import org.briarproject.api.db.DbException;
import org.briarproject.api.db.Transaction;
import org.briarproject.api.event.ContactRemovedEvent; import org.briarproject.api.event.ContactRemovedEvent;
import org.briarproject.api.event.Event; import org.briarproject.api.event.Event;
import org.briarproject.api.event.EventBus; import org.briarproject.api.event.EventBus;
@@ -50,8 +51,8 @@ class DuplexOutgoingSession implements SyncSession, EventListener {
private static final ThrowingRunnable<IOException> CLOSE = private static final ThrowingRunnable<IOException> CLOSE =
new ThrowingRunnable<IOException>() { new ThrowingRunnable<IOException>() {
public void run() {} public void run() {}
}; };
private final DatabaseComponent db; private final DatabaseComponent db;
private final Executor dbExecutor; private final Executor dbExecutor;
@@ -178,7 +179,14 @@ class DuplexOutgoingSession implements SyncSession, EventListener {
public void run() { public void run() {
if (interrupted) return; if (interrupted) return;
try { try {
Ack a = db.generateAck(contactId, MAX_MESSAGE_IDS); Ack a;
Transaction txn = db.startTransaction();
try {
a = db.generateAck(txn, contactId, MAX_MESSAGE_IDS);
txn.setComplete();
} finally {
db.endTransaction(txn);
}
if (LOG.isLoggable(INFO)) if (LOG.isLoggable(INFO))
LOG.info("Generated ack: " + (a != null)); LOG.info("Generated ack: " + (a != null));
if (a != null) writerTasks.add(new WriteAck(a)); if (a != null) writerTasks.add(new WriteAck(a));
@@ -212,8 +220,15 @@ class DuplexOutgoingSession implements SyncSession, EventListener {
public void run() { public void run() {
if (interrupted) return; if (interrupted) return;
try { try {
Collection<byte[]> b = db.generateRequestedBatch(contactId, Collection<byte[]> b;
MAX_PACKET_PAYLOAD_LENGTH, maxLatency); Transaction txn = db.startTransaction();
try {
b = db.generateRequestedBatch(txn, contactId,
MAX_PACKET_PAYLOAD_LENGTH, maxLatency);
txn.setComplete();
} finally {
db.endTransaction(txn);
}
if (LOG.isLoggable(INFO)) if (LOG.isLoggable(INFO))
LOG.info("Generated batch: " + (b != null)); LOG.info("Generated batch: " + (b != null));
if (b != null) writerTasks.add(new WriteBatch(b)); if (b != null) writerTasks.add(new WriteBatch(b));
@@ -247,8 +262,15 @@ class DuplexOutgoingSession implements SyncSession, EventListener {
public void run() { public void run() {
if (interrupted) return; if (interrupted) return;
try { try {
Offer o = db.generateOffer(contactId, MAX_MESSAGE_IDS, Offer o;
maxLatency); Transaction txn = db.startTransaction();
try {
o = db.generateOffer(txn, contactId, MAX_MESSAGE_IDS,
maxLatency);
txn.setComplete();
} finally {
db.endTransaction(txn);
}
if (LOG.isLoggable(INFO)) if (LOG.isLoggable(INFO))
LOG.info("Generated offer: " + (o != null)); LOG.info("Generated offer: " + (o != null));
if (o != null) writerTasks.add(new WriteOffer(o)); if (o != null) writerTasks.add(new WriteOffer(o));
@@ -282,7 +304,14 @@ class DuplexOutgoingSession implements SyncSession, EventListener {
public void run() { public void run() {
if (interrupted) return; if (interrupted) return;
try { try {
Request r = db.generateRequest(contactId, MAX_MESSAGE_IDS); Request r;
Transaction txn = db.startTransaction();
try {
r = db.generateRequest(txn, contactId, MAX_MESSAGE_IDS);
txn.setComplete();
} finally {
db.endTransaction(txn);
}
if (LOG.isLoggable(INFO)) if (LOG.isLoggable(INFO))
LOG.info("Generated request: " + (r != null)); LOG.info("Generated request: " + (r != null));
if (r != null) writerTasks.add(new WriteRequest(r)); if (r != null) writerTasks.add(new WriteRequest(r));

View File

@@ -5,6 +5,7 @@ import org.briarproject.api.TransportId;
import org.briarproject.api.contact.ContactId; import org.briarproject.api.contact.ContactId;
import org.briarproject.api.db.DatabaseComponent; import org.briarproject.api.db.DatabaseComponent;
import org.briarproject.api.db.DbException; import org.briarproject.api.db.DbException;
import org.briarproject.api.db.Transaction;
import org.briarproject.api.event.ContactRemovedEvent; import org.briarproject.api.event.ContactRemovedEvent;
import org.briarproject.api.event.Event; import org.briarproject.api.event.Event;
import org.briarproject.api.event.EventBus; import org.briarproject.api.event.EventBus;
@@ -24,7 +25,9 @@ import java.util.logging.Logger;
import static java.util.logging.Level.WARNING; import static java.util.logging.Level.WARNING;
/** An incoming {@link org.briarproject.api.sync.SyncSession SyncSession}. */ /**
* An incoming {@link org.briarproject.api.sync.SyncSession SyncSession}.
*/
class IncomingSession implements SyncSession, EventListener { class IncomingSession implements SyncSession, EventListener {
private static final Logger LOG = private static final Logger LOG =
@@ -103,7 +106,13 @@ class IncomingSession implements SyncSession, EventListener {
public void run() { public void run() {
try { try {
db.receiveAck(contactId, ack); Transaction txn = db.startTransaction();
try {
db.receiveAck(txn, contactId, ack);
txn.setComplete();
} finally {
db.endTransaction(txn);
}
} catch (DbException e) { } catch (DbException e) {
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e); if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
interrupt(); interrupt();
@@ -121,7 +130,13 @@ class IncomingSession implements SyncSession, EventListener {
public void run() { public void run() {
try { try {
db.receiveMessage(contactId, message); Transaction txn = db.startTransaction();
try {
db.receiveMessage(txn, contactId, message);
txn.setComplete();
} finally {
db.endTransaction(txn);
}
} catch (DbException e) { } catch (DbException e) {
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e); if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
interrupt(); interrupt();
@@ -139,7 +154,13 @@ class IncomingSession implements SyncSession, EventListener {
public void run() { public void run() {
try { try {
db.receiveOffer(contactId, offer); Transaction txn = db.startTransaction();
try {
db.receiveOffer(txn, contactId, offer);
txn.setComplete();
} finally {
db.endTransaction(txn);
}
} catch (DbException e) { } catch (DbException e) {
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e); if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
interrupt(); interrupt();
@@ -157,7 +178,13 @@ class IncomingSession implements SyncSession, EventListener {
public void run() { public void run() {
try { try {
db.receiveRequest(contactId, request); Transaction txn = db.startTransaction();
try {
db.receiveRequest(txn, contactId, request);
txn.setComplete();
} finally {
db.endTransaction(txn);
}
} catch (DbException e) { } catch (DbException e) {
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e); if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
interrupt(); interrupt();

View File

@@ -4,6 +4,7 @@ import org.briarproject.api.TransportId;
import org.briarproject.api.contact.ContactId; import org.briarproject.api.contact.ContactId;
import org.briarproject.api.db.DatabaseComponent; import org.briarproject.api.db.DatabaseComponent;
import org.briarproject.api.db.DbException; import org.briarproject.api.db.DbException;
import org.briarproject.api.db.Transaction;
import org.briarproject.api.event.ContactRemovedEvent; import org.briarproject.api.event.ContactRemovedEvent;
import org.briarproject.api.event.Event; import org.briarproject.api.event.Event;
import org.briarproject.api.event.EventBus; import org.briarproject.api.event.EventBus;
@@ -40,8 +41,8 @@ class SimplexOutgoingSession implements SyncSession, EventListener {
private static final ThrowingRunnable<IOException> CLOSE = private static final ThrowingRunnable<IOException> CLOSE =
new ThrowingRunnable<IOException>() { new ThrowingRunnable<IOException>() {
public void run() {} public void run() {}
}; };
private final DatabaseComponent db; private final DatabaseComponent db;
private final Executor dbExecutor; private final Executor dbExecutor;
@@ -119,7 +120,14 @@ class SimplexOutgoingSession implements SyncSession, EventListener {
public void run() { public void run() {
if (interrupted) return; if (interrupted) return;
try { try {
Ack a = db.generateAck(contactId, MAX_MESSAGE_IDS); Ack a;
Transaction txn = db.startTransaction();
try {
a = db.generateAck(txn, contactId, MAX_MESSAGE_IDS);
txn.setComplete();
} finally {
db.endTransaction(txn);
}
if (LOG.isLoggable(INFO)) if (LOG.isLoggable(INFO))
LOG.info("Generated ack: " + (a != null)); LOG.info("Generated ack: " + (a != null));
if (a == null) decrementOutstandingQueries(); if (a == null) decrementOutstandingQueries();
@@ -154,8 +162,15 @@ class SimplexOutgoingSession implements SyncSession, EventListener {
public void run() { public void run() {
if (interrupted) return; if (interrupted) return;
try { try {
Collection<byte[]> b = db.generateBatch(contactId, Collection<byte[]> b;
MAX_PACKET_PAYLOAD_LENGTH, maxLatency); Transaction txn = db.startTransaction();
try {
b = db.generateBatch(txn, contactId,
MAX_PACKET_PAYLOAD_LENGTH, maxLatency);
txn.setComplete();
} finally {
db.endTransaction(txn);
}
if (LOG.isLoggable(INFO)) if (LOG.isLoggable(INFO))
LOG.info("Generated batch: " + (b != null)); LOG.info("Generated batch: " + (b != null));
if (b == null) decrementOutstandingQueries(); if (b == null) decrementOutstandingQueries();

View File

@@ -3,10 +3,7 @@ package org.briarproject.sync;
import com.google.inject.AbstractModule; import com.google.inject.AbstractModule;
import com.google.inject.Provides; import com.google.inject.Provides;
import org.briarproject.api.data.ObjectReader;
import org.briarproject.api.event.EventBus; import org.briarproject.api.event.EventBus;
import org.briarproject.api.identity.Author;
import org.briarproject.api.identity.AuthorFactory;
import org.briarproject.api.lifecycle.LifecycleManager; import org.briarproject.api.lifecycle.LifecycleManager;
import org.briarproject.api.sync.GroupFactory; import org.briarproject.api.sync.GroupFactory;
import org.briarproject.api.sync.MessageFactory; import org.briarproject.api.sync.MessageFactory;
@@ -22,7 +19,6 @@ public class SyncModule extends AbstractModule {
@Override @Override
protected void configure() { protected void configure() {
bind(AuthorFactory.class).to(AuthorFactoryImpl.class);
bind(GroupFactory.class).to(GroupFactoryImpl.class); bind(GroupFactory.class).to(GroupFactoryImpl.class);
bind(MessageFactory.class).to(MessageFactoryImpl.class); bind(MessageFactory.class).to(MessageFactoryImpl.class);
bind(PacketReaderFactory.class).to(PacketReaderFactoryImpl.class); bind(PacketReaderFactory.class).to(PacketReaderFactoryImpl.class);
@@ -32,11 +28,6 @@ public class SyncModule extends AbstractModule {
SyncSessionFactoryImpl.class).in(Singleton.class); SyncSessionFactoryImpl.class).in(Singleton.class);
} }
@Provides
ObjectReader<Author> getAuthorReader(AuthorFactory authorFactory) {
return new AuthorReader(authorFactory);
}
@Provides @Singleton @Provides @Singleton
ValidationManager getValidationManager(LifecycleManager lifecycleManager, ValidationManager getValidationManager(LifecycleManager lifecycleManager,
EventBus eventBus, ValidationManagerImpl validationManager) { EventBus eventBus, ValidationManagerImpl validationManager) {

View File

@@ -9,7 +9,7 @@ import org.briarproject.api.db.DatabaseExecutor;
import org.briarproject.api.db.DbException; import org.briarproject.api.db.DbException;
import org.briarproject.api.db.Metadata; import org.briarproject.api.db.Metadata;
import org.briarproject.api.db.NoSuchGroupException; import org.briarproject.api.db.NoSuchGroupException;
import org.briarproject.api.db.NoSuchMessageException; import org.briarproject.api.db.Transaction;
import org.briarproject.api.event.Event; import org.briarproject.api.event.Event;
import org.briarproject.api.event.EventListener; import org.briarproject.api.event.EventListener;
import org.briarproject.api.event.MessageAddedEvent; import org.briarproject.api.event.MessageAddedEvent;
@@ -82,14 +82,17 @@ class ValidationManagerImpl implements ValidationManager, Service,
public void run() { public void run() {
try { try {
// TODO: Don't do all of this in a single DB task // TODO: Don't do all of this in a single DB task
for (MessageId id : db.getMessagesToValidate(c)) { Transaction txn = db.startTransaction();
try { try {
Message m = parseMessage(id, db.getRawMessage(id)); for (MessageId id : db.getMessagesToValidate(txn, c)) {
Group g = db.getGroup(m.getGroupId()); byte[] raw = db.getRawMessage(txn, id);
Message m = parseMessage(id, raw);
Group g = db.getGroup(txn, m.getGroupId());
validateMessage(m, g); validateMessage(m, g);
} catch (NoSuchMessageException e) {
LOG.info("Message removed before validation");
} }
txn.setComplete();
} finally {
db.endTransaction(txn);
} }
} catch (DbException e) { } catch (DbException e) {
if (LOG.isLoggable(WARNING)) if (LOG.isLoggable(WARNING))
@@ -127,17 +130,21 @@ class ValidationManagerImpl implements ValidationManager, Service,
dbExecutor.execute(new Runnable() { dbExecutor.execute(new Runnable() {
public void run() { public void run() {
try { try {
if (meta == null) { Transaction txn = db.startTransaction();
db.setMessageValid(m, c, false); try {
} else { if (meta == null) {
for (ValidationHook hook : hooks) db.setMessageValid(txn, m, c, false);
hook.validatingMessage(m, c, meta); } else {
db.mergeMessageMetadata(m.getId(), meta); db.mergeMessageMetadata(txn, m.getId(), meta);
db.setMessageValid(m, c, true); db.setMessageValid(txn, m, c, true);
db.setMessageShared(m, true); db.setMessageShared(txn, m, true);
for (ValidationHook hook : hooks)
hook.validatingMessage(txn, m, c, meta);
}
txn.setComplete();
} finally {
db.endTransaction(txn);
} }
} catch (NoSuchMessageException e) {
LOG.info("Message removed during validation");
} catch (DbException e) { } catch (DbException e) {
if (LOG.isLoggable(WARNING)) if (LOG.isLoggable(WARNING))
LOG.log(WARNING, e.toString(), e); LOG.log(WARNING, e.toString(), e);
@@ -159,7 +166,13 @@ class ValidationManagerImpl implements ValidationManager, Service,
dbExecutor.execute(new Runnable() { dbExecutor.execute(new Runnable() {
public void run() { public void run() {
try { try {
validateMessage(m, db.getGroup(m.getGroupId())); Transaction txn = db.startTransaction();
try {
validateMessage(m, db.getGroup(txn, m.getGroupId()));
txn.setComplete();
} finally {
db.endTransaction(txn);
}
} catch (NoSuchGroupException e) { } catch (NoSuchGroupException e) {
LOG.info("Group removed before validation"); LOG.info("Group removed before validation");
} catch (DbException e) { } catch (DbException e) {

View File

@@ -7,6 +7,7 @@ import org.briarproject.api.crypto.SecretKey;
import org.briarproject.api.db.DatabaseComponent; import org.briarproject.api.db.DatabaseComponent;
import org.briarproject.api.db.DatabaseExecutor; import org.briarproject.api.db.DatabaseExecutor;
import org.briarproject.api.db.DbException; import org.briarproject.api.db.DbException;
import org.briarproject.api.db.Transaction;
import org.briarproject.api.event.ContactRemovedEvent; import org.briarproject.api.event.ContactRemovedEvent;
import org.briarproject.api.event.Event; import org.briarproject.api.event.Event;
import org.briarproject.api.event.EventListener; import org.briarproject.api.event.EventListener;
@@ -55,7 +56,14 @@ class KeyManagerImpl implements KeyManager, Service, EventListener {
@Override @Override
public boolean start() { public boolean start() {
try { try {
Map<TransportId, Integer> latencies = db.getTransportLatencies(); Map<TransportId, Integer> latencies;
Transaction txn = db.startTransaction();
try {
latencies = db.getTransportLatencies(txn);
txn.setComplete();
} finally {
db.endTransaction(txn);
}
for (Entry<TransportId, Integer> e : latencies.entrySet()) for (Entry<TransportId, Integer> e : latencies.entrySet())
addTransport(e.getKey(), e.getValue()); addTransport(e.getKey(), e.getValue());
} catch (DbException e) { } catch (DbException e) {

View File

@@ -7,6 +7,7 @@ import org.briarproject.api.crypto.CryptoComponent;
import org.briarproject.api.crypto.SecretKey; import org.briarproject.api.crypto.SecretKey;
import org.briarproject.api.db.DatabaseComponent; import org.briarproject.api.db.DatabaseComponent;
import org.briarproject.api.db.DbException; import org.briarproject.api.db.DbException;
import org.briarproject.api.db.Transaction;
import org.briarproject.api.system.Clock; import org.briarproject.api.system.Clock;
import org.briarproject.api.system.Timer; import org.briarproject.api.system.Timer;
import org.briarproject.api.transport.StreamContext; import org.briarproject.api.transport.StreamContext;
@@ -66,7 +67,13 @@ class TransportKeyManager extends TimerTask {
// Load the transport keys from the DB // Load the transport keys from the DB
Map<ContactId, TransportKeys> loaded; Map<ContactId, TransportKeys> loaded;
try { try {
loaded = db.getTransportKeys(transportId); Transaction txn = db.startTransaction();
try {
loaded = db.getTransportKeys(txn, transportId);
txn.setComplete();
} finally {
db.endTransaction(txn);
}
} catch (DbException e) { } catch (DbException e) {
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e); if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
return; return;
@@ -90,7 +97,13 @@ class TransportKeyManager extends TimerTask {
for (Entry<ContactId, TransportKeys> e : current.entrySet()) for (Entry<ContactId, TransportKeys> e : current.entrySet())
addKeys(e.getKey(), new MutableTransportKeys(e.getValue())); addKeys(e.getKey(), new MutableTransportKeys(e.getValue()));
// Write any rotated keys back to the DB // Write any rotated keys back to the DB
db.updateTransportKeys(rotated); Transaction txn = db.startTransaction();
try {
db.updateTransportKeys(txn, rotated);
txn.setComplete();
} finally {
db.endTransaction(txn);
}
} catch (DbException e) { } catch (DbException e) {
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e); if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
} finally { } finally {
@@ -135,7 +148,13 @@ class TransportKeyManager extends TimerTask {
// Initialise mutable state for the contact // Initialise mutable state for the contact
addKeys(c, new MutableTransportKeys(k)); addKeys(c, new MutableTransportKeys(k));
// Write the keys back to the DB // Write the keys back to the DB
db.addTransportKeys(c, k); Transaction txn = db.startTransaction();
try {
db.addTransportKeys(txn, c, k);
txn.setComplete();
} finally {
db.endTransaction(txn);
}
} catch (DbException e) { } catch (DbException e) {
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e); if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
} finally { } finally {
@@ -171,8 +190,14 @@ class TransportKeyManager extends TimerTask {
outKeys.getStreamCounter()); outKeys.getStreamCounter());
// Increment the stream counter and write it back to the DB // Increment the stream counter and write it back to the DB
outKeys.incrementStreamCounter(); outKeys.incrementStreamCounter();
db.incrementStreamCounter(c, transportId, Transaction txn = db.startTransaction();
outKeys.getRotationPeriod()); try {
db.incrementStreamCounter(txn, c, transportId,
outKeys.getRotationPeriod());
txn.setComplete();
} finally {
db.endTransaction(txn);
}
return ctx; return ctx;
} catch (DbException e) { } catch (DbException e) {
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e); if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
@@ -210,9 +235,15 @@ class TransportKeyManager extends TimerTask {
inContexts.remove(new Bytes(removeTag)); inContexts.remove(new Bytes(removeTag));
} }
// Write the window back to the DB // Write the window back to the DB
db.setReorderingWindow(tagCtx.contactId, transportId, Transaction txn = db.startTransaction();
inKeys.getRotationPeriod(), window.getBase(), try {
window.getBitmap()); db.setReorderingWindow(txn, tagCtx.contactId, transportId,
inKeys.getRotationPeriod(), window.getBase(),
window.getBitmap());
txn.setComplete();
} finally {
db.endTransaction(txn);
}
return ctx; return ctx;
} catch (DbException e) { } catch (DbException e) {
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e); if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
@@ -249,7 +280,13 @@ class TransportKeyManager extends TimerTask {
for (Entry<ContactId, TransportKeys> e : current.entrySet()) for (Entry<ContactId, TransportKeys> e : current.entrySet())
addKeys(e.getKey(), new MutableTransportKeys(e.getValue())); addKeys(e.getKey(), new MutableTransportKeys(e.getValue()));
// Write any rotated keys back to the DB // Write any rotated keys back to the DB
db.updateTransportKeys(rotated); Transaction txn = db.startTransaction();
try {
db.updateTransportKeys(txn, rotated);
txn.setComplete();
} finally {
db.endTransaction(txn);
}
} catch (DbException e) { } catch (DbException e) {
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e); if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
} finally { } finally {

View File

@@ -0,0 +1,14 @@
package org.briarproject.contact;
import org.briarproject.BriarTestCase;
import org.junit.Test;
import static org.junit.Assert.fail;
public class ContactManagerImplTest extends BriarTestCase {
@Test
public void testUnitTestsExist() {
fail(); // FIXME: Write tests
}
}

View File

@@ -8,7 +8,6 @@ import org.briarproject.api.contact.ContactId;
import org.briarproject.api.crypto.SecretKey; import org.briarproject.api.crypto.SecretKey;
import org.briarproject.api.db.DbException; import org.briarproject.api.db.DbException;
import org.briarproject.api.db.Metadata; import org.briarproject.api.db.Metadata;
import org.briarproject.api.db.StorageStatus;
import org.briarproject.api.identity.Author; import org.briarproject.api.identity.Author;
import org.briarproject.api.identity.AuthorId; import org.briarproject.api.identity.AuthorId;
import org.briarproject.api.identity.LocalAuthor; import org.briarproject.api.identity.LocalAuthor;
@@ -87,8 +86,7 @@ public class H2DatabaseTest extends BriarTestCase {
localAuthorId = new AuthorId(TestUtils.getRandomId()); localAuthorId = new AuthorId(TestUtils.getRandomId());
timestamp = System.currentTimeMillis(); timestamp = System.currentTimeMillis();
localAuthor = new LocalAuthor(localAuthorId, "Bob", localAuthor = new LocalAuthor(localAuthorId, "Bob",
new byte[MAX_PUBLIC_KEY_LENGTH], new byte[123], timestamp, new byte[MAX_PUBLIC_KEY_LENGTH], new byte[123], timestamp);
StorageStatus.ACTIVE);
messageId = new MessageId(TestUtils.getRandomId()); messageId = new MessageId(TestUtils.getRandomId());
size = 1234; size = 1234;
raw = new byte[size]; raw = new byte[size];
@@ -1060,8 +1058,7 @@ public class H2DatabaseTest extends BriarTestCase {
throws Exception { throws Exception {
AuthorId localAuthorId1 = new AuthorId(TestUtils.getRandomId()); AuthorId localAuthorId1 = new AuthorId(TestUtils.getRandomId());
LocalAuthor localAuthor1 = new LocalAuthor(localAuthorId1, "Carol", LocalAuthor localAuthor1 = new LocalAuthor(localAuthorId1, "Carol",
new byte[MAX_PUBLIC_KEY_LENGTH], new byte[123], timestamp, new byte[MAX_PUBLIC_KEY_LENGTH], new byte[123], timestamp);
StorageStatus.ACTIVE);
Database<Connection> db = open(false); Database<Connection> db = open(false);
Connection txn = db.startTransaction(); Connection txn = db.startTransaction();

View File

@@ -0,0 +1,14 @@
package org.briarproject.identity;
import org.briarproject.BriarTestCase;
import org.junit.Test;
import static org.junit.Assert.fail;
public class IdentityManagerImplTest extends BriarTestCase {
@Test
public void testUnitTestsExist() {
fail(); // FIXME: Write tests
}
}

View File

@@ -3,6 +3,7 @@ package org.briarproject.plugins;
import org.briarproject.BriarTestCase; import org.briarproject.BriarTestCase;
import org.briarproject.api.TransportId; import org.briarproject.api.TransportId;
import org.briarproject.api.db.DatabaseComponent; import org.briarproject.api.db.DatabaseComponent;
import org.briarproject.api.db.Transaction;
import org.briarproject.api.event.EventBus; import org.briarproject.api.event.EventBus;
import org.briarproject.api.plugins.ConnectionManager; import org.briarproject.api.plugins.ConnectionManager;
import org.briarproject.api.plugins.duplex.DuplexPlugin; import org.briarproject.api.plugins.duplex.DuplexPlugin;
@@ -14,6 +15,7 @@ import org.briarproject.api.plugins.simplex.SimplexPluginCallback;
import org.briarproject.api.plugins.simplex.SimplexPluginConfig; import org.briarproject.api.plugins.simplex.SimplexPluginConfig;
import org.briarproject.api.plugins.simplex.SimplexPluginFactory; import org.briarproject.api.plugins.simplex.SimplexPluginFactory;
import org.briarproject.api.properties.TransportPropertyManager; import org.briarproject.api.properties.TransportPropertyManager;
import org.briarproject.api.settings.SettingsManager;
import org.briarproject.api.system.Clock; import org.briarproject.api.system.Clock;
import org.briarproject.api.ui.UiCallback; import org.briarproject.api.ui.UiCallback;
import org.briarproject.system.SystemClock; import org.briarproject.system.SystemClock;
@@ -46,6 +48,8 @@ public class PluginManagerImplTest extends BriarTestCase {
final Poller poller = context.mock(Poller.class); final Poller poller = context.mock(Poller.class);
final ConnectionManager connectionManager = final ConnectionManager connectionManager =
context.mock(ConnectionManager.class); context.mock(ConnectionManager.class);
final SettingsManager settingsManager =
context.mock(SettingsManager.class);
final TransportPropertyManager transportPropertyManager = final TransportPropertyManager transportPropertyManager =
context.mock(TransportPropertyManager.class); context.mock(TransportPropertyManager.class);
final UiCallback uiCallback = context.mock(UiCallback.class); final UiCallback uiCallback = context.mock(UiCallback.class);
@@ -55,18 +59,21 @@ public class PluginManagerImplTest extends BriarTestCase {
final SimplexPlugin simplexPlugin = context.mock(SimplexPlugin.class); final SimplexPlugin simplexPlugin = context.mock(SimplexPlugin.class);
final TransportId simplexId = new TransportId("simplex"); final TransportId simplexId = new TransportId("simplex");
final int simplexLatency = 12345; final int simplexLatency = 12345;
final Transaction simplexTxn = new Transaction(null);
final SimplexPluginFactory simplexFailFactory = final SimplexPluginFactory simplexFailFactory =
context.mock(SimplexPluginFactory.class, "simplexFailFactory"); context.mock(SimplexPluginFactory.class, "simplexFailFactory");
final SimplexPlugin simplexFailPlugin = final SimplexPlugin simplexFailPlugin =
context.mock(SimplexPlugin.class, "simplexFailPlugin"); context.mock(SimplexPlugin.class, "simplexFailPlugin");
final TransportId simplexFailId = new TransportId("simplex1"); final TransportId simplexFailId = new TransportId("simplex1");
final int simplexFailLatency = 23456; final int simplexFailLatency = 23456;
final Transaction simplexFailTxn = new Transaction(null);
// Two duplex plugin factories: one creates a plugin, the other fails // Two duplex plugin factories: one creates a plugin, the other fails
final DuplexPluginFactory duplexFactory = final DuplexPluginFactory duplexFactory =
context.mock(DuplexPluginFactory.class); context.mock(DuplexPluginFactory.class);
final DuplexPlugin duplexPlugin = context.mock(DuplexPlugin.class); final DuplexPlugin duplexPlugin = context.mock(DuplexPlugin.class);
final TransportId duplexId = new TransportId("duplex"); final TransportId duplexId = new TransportId("duplex");
final int duplexLatency = 34567; final int duplexLatency = 34567;
final Transaction duplexTxn = new Transaction(null);
final DuplexPluginFactory duplexFailFactory = final DuplexPluginFactory duplexFailFactory =
context.mock(DuplexPluginFactory.class, "duplexFailFactory"); context.mock(DuplexPluginFactory.class, "duplexFailFactory");
final TransportId duplexFailId = new TransportId("duplex1"); final TransportId duplexFailId = new TransportId("duplex1");
@@ -82,7 +89,10 @@ public class PluginManagerImplTest extends BriarTestCase {
will(returnValue(simplexPlugin)); // Created will(returnValue(simplexPlugin)); // Created
oneOf(simplexPlugin).getMaxLatency(); oneOf(simplexPlugin).getMaxLatency();
will(returnValue(simplexLatency)); will(returnValue(simplexLatency));
oneOf(db).addTransport(simplexId, simplexLatency); oneOf(db).startTransaction();
will(returnValue(simplexTxn));
oneOf(db).addTransport(simplexTxn, simplexId, simplexLatency);
oneOf(db).endTransaction(simplexTxn);
oneOf(simplexPlugin).start(); oneOf(simplexPlugin).start();
will(returnValue(true)); // Started will(returnValue(true)); // Started
oneOf(simplexPlugin).shouldPoll(); oneOf(simplexPlugin).shouldPoll();
@@ -96,7 +106,11 @@ public class PluginManagerImplTest extends BriarTestCase {
will(returnValue(simplexFailPlugin)); // Created will(returnValue(simplexFailPlugin)); // Created
oneOf(simplexFailPlugin).getMaxLatency(); oneOf(simplexFailPlugin).getMaxLatency();
will(returnValue(simplexFailLatency)); will(returnValue(simplexFailLatency));
oneOf(db).addTransport(simplexFailId, simplexFailLatency); oneOf(db).startTransaction();
will(returnValue(simplexFailTxn));
oneOf(db).addTransport(simplexFailTxn, simplexFailId,
simplexFailLatency);
oneOf(db).endTransaction(simplexFailTxn);
oneOf(simplexFailPlugin).start(); oneOf(simplexFailPlugin).start();
will(returnValue(false)); // Failed to start will(returnValue(false)); // Failed to start
// First duplex plugin // First duplex plugin
@@ -109,7 +123,10 @@ public class PluginManagerImplTest extends BriarTestCase {
will(returnValue(duplexPlugin)); // Created will(returnValue(duplexPlugin)); // Created
oneOf(duplexPlugin).getMaxLatency(); oneOf(duplexPlugin).getMaxLatency();
will(returnValue(duplexLatency)); will(returnValue(duplexLatency));
oneOf(db).addTransport(duplexId, duplexLatency); oneOf(db).startTransaction();
will(returnValue(duplexTxn));
oneOf(db).addTransport(duplexTxn, duplexId, duplexLatency);
oneOf(db).endTransaction(duplexTxn);
oneOf(duplexPlugin).start(); oneOf(duplexPlugin).start();
will(returnValue(true)); // Started will(returnValue(true)); // Started
oneOf(duplexPlugin).shouldPoll(); oneOf(duplexPlugin).shouldPoll();
@@ -128,7 +145,8 @@ public class PluginManagerImplTest extends BriarTestCase {
}}); }});
PluginManagerImpl p = new PluginManagerImpl(ioExecutor, eventBus, PluginManagerImpl p = new PluginManagerImpl(ioExecutor, eventBus,
simplexPluginConfig, duplexPluginConfig, clock, db, poller, simplexPluginConfig, duplexPluginConfig, clock, db, poller,
connectionManager, transportPropertyManager, uiCallback); connectionManager, settingsManager, transportPropertyManager,
uiCallback);
// Two plugins should be started and stopped // Two plugins should be started and stopped
assertTrue(p.start()); assertTrue(p.start());

View File

@@ -12,7 +12,7 @@ import org.briarproject.api.contact.ContactId;
import org.briarproject.api.contact.ContactManager; import org.briarproject.api.contact.ContactManager;
import org.briarproject.api.crypto.SecretKey; import org.briarproject.api.crypto.SecretKey;
import org.briarproject.api.db.DatabaseComponent; import org.briarproject.api.db.DatabaseComponent;
import org.briarproject.api.db.StorageStatus; import org.briarproject.api.db.Transaction;
import org.briarproject.api.event.Event; import org.briarproject.api.event.Event;
import org.briarproject.api.event.EventBus; import org.briarproject.api.event.EventBus;
import org.briarproject.api.event.EventListener; import org.briarproject.api.event.EventListener;
@@ -120,11 +120,16 @@ public class SimplexMessagingIntegrationTest extends BriarTestCase {
lifecycleManager.startServices(); lifecycleManager.startServices();
lifecycleManager.waitForStartup(); lifecycleManager.waitForStartup();
// Add a transport // Add a transport
db.addTransport(transportId, MAX_LATENCY); Transaction txn = db.startTransaction();
try {
db.addTransport(txn, transportId, MAX_LATENCY);
txn.setComplete();
} finally {
db.endTransaction(txn);
}
// Add an identity for Alice // Add an identity for Alice
LocalAuthor aliceAuthor = new LocalAuthor(aliceId, "Alice", LocalAuthor aliceAuthor = new LocalAuthor(aliceId, "Alice",
new byte[MAX_PUBLIC_KEY_LENGTH], new byte[123], timestamp, new byte[MAX_PUBLIC_KEY_LENGTH], new byte[123], timestamp);
StorageStatus.ADDING);
identityManager.addLocalAuthor(aliceAuthor); identityManager.addLocalAuthor(aliceAuthor);
// Add Bob as a contact // Add Bob as a contact
Author bobAuthor = new Author(bobId, "Bob", Author bobAuthor = new Author(bobId, "Bob",
@@ -185,11 +190,16 @@ public class SimplexMessagingIntegrationTest extends BriarTestCase {
lifecycleManager.startServices(); lifecycleManager.startServices();
lifecycleManager.waitForStartup(); lifecycleManager.waitForStartup();
// Add a transport // Add a transport
db.addTransport(transportId, MAX_LATENCY); Transaction txn = db.startTransaction();
try {
db.addTransport(txn, transportId, MAX_LATENCY);
txn.setComplete();
} finally {
db.endTransaction(txn);
}
// Add an identity for Bob // Add an identity for Bob
LocalAuthor bobAuthor = new LocalAuthor(bobId, "Bob", LocalAuthor bobAuthor = new LocalAuthor(bobId, "Bob",
new byte[MAX_PUBLIC_KEY_LENGTH], new byte[123], timestamp, new byte[MAX_PUBLIC_KEY_LENGTH], new byte[123], timestamp);
StorageStatus.ADDING);
identityManager.addLocalAuthor(bobAuthor); identityManager.addLocalAuthor(bobAuthor);
// Add Alice as a contact // Add Alice as a contact
Author aliceAuthor = new Author(aliceId, "Alice", Author aliceAuthor = new Author(aliceId, "Alice",

View File

@@ -5,6 +5,7 @@ import org.briarproject.TestUtils;
import org.briarproject.api.TransportId; import org.briarproject.api.TransportId;
import org.briarproject.api.contact.ContactId; import org.briarproject.api.contact.ContactId;
import org.briarproject.api.db.DatabaseComponent; import org.briarproject.api.db.DatabaseComponent;
import org.briarproject.api.db.Transaction;
import org.briarproject.api.event.EventBus; import org.briarproject.api.event.EventBus;
import org.briarproject.api.sync.Ack; import org.briarproject.api.sync.Ack;
import org.briarproject.api.sync.MessageId; import org.briarproject.api.sync.MessageId;
@@ -49,16 +50,24 @@ public class SimplexOutgoingSessionTest extends BriarTestCase {
final SimplexOutgoingSession session = new SimplexOutgoingSession(db, final SimplexOutgoingSession session = new SimplexOutgoingSession(db,
dbExecutor, eventBus, contactId, transportId, maxLatency, dbExecutor, eventBus, contactId, transportId, maxLatency,
packetWriter); packetWriter);
final Transaction noAckTxn = new Transaction(null);
final Transaction noMsgTxn = new Transaction(null);
context.checking(new Expectations() {{ context.checking(new Expectations() {{
// Add listener // Add listener
oneOf(eventBus).addListener(session); oneOf(eventBus).addListener(session);
// No acks to send // No acks to send
oneOf(db).generateAck(contactId, MAX_MESSAGE_IDS); oneOf(db).startTransaction();
will(returnValue(noAckTxn));
oneOf(db).generateAck(noAckTxn, contactId, MAX_MESSAGE_IDS);
will(returnValue(null)); will(returnValue(null));
oneOf(db).endTransaction(noAckTxn);
// No messages to send // No messages to send
oneOf(db).generateBatch(with(contactId), with(any(int.class)), oneOf(db).startTransaction();
with(maxLatency)); will(returnValue(noMsgTxn));
oneOf(db).generateBatch(with(noMsgTxn), with(contactId),
with(any(int.class)), with(maxLatency));
will(returnValue(null)); will(returnValue(null));
oneOf(db).endTransaction(noMsgTxn);
// Flush the output stream // Flush the output stream
oneOf(packetWriter).flush(); oneOf(packetWriter).flush();
// Remove listener // Remove listener
@@ -75,25 +84,41 @@ public class SimplexOutgoingSessionTest extends BriarTestCase {
final SimplexOutgoingSession session = new SimplexOutgoingSession(db, final SimplexOutgoingSession session = new SimplexOutgoingSession(db,
dbExecutor, eventBus, contactId, transportId, maxLatency, dbExecutor, eventBus, contactId, transportId, maxLatency,
packetWriter); packetWriter);
final Transaction ackTxn = new Transaction(null);
final Transaction noAckTxn = new Transaction(null);
final Transaction msgTxn = new Transaction(null);
final Transaction noMsgTxn = new Transaction(null);
context.checking(new Expectations() {{ context.checking(new Expectations() {{
// Add listener // Add listener
oneOf(eventBus).addListener(session); oneOf(eventBus).addListener(session);
// One ack to send // One ack to send
oneOf(db).generateAck(contactId, MAX_MESSAGE_IDS); oneOf(db).startTransaction();
will(returnValue(ackTxn));
oneOf(db).generateAck(ackTxn, contactId, MAX_MESSAGE_IDS);
will(returnValue(ack)); will(returnValue(ack));
oneOf(db).endTransaction(ackTxn);
oneOf(packetWriter).writeAck(ack); oneOf(packetWriter).writeAck(ack);
// No more acks
oneOf(db).generateAck(contactId, MAX_MESSAGE_IDS);
will(returnValue(null));
// One message to send // One message to send
oneOf(db).generateBatch(with(contactId), with(any(int.class)), oneOf(db).startTransaction();
with(maxLatency)); will(returnValue(msgTxn));
oneOf(db).generateBatch(with(msgTxn), with(contactId),
with(any(int.class)), with(maxLatency));
will(returnValue(Arrays.asList(raw))); will(returnValue(Arrays.asList(raw)));
oneOf(db).endTransaction(msgTxn);
oneOf(packetWriter).writeMessage(raw); oneOf(packetWriter).writeMessage(raw);
// No more messages // No more acks
oneOf(db).generateBatch(with(contactId), with(any(int.class)), oneOf(db).startTransaction();
with(maxLatency)); will(returnValue(noAckTxn));
oneOf(db).generateAck(noAckTxn, contactId, MAX_MESSAGE_IDS);
will(returnValue(null)); will(returnValue(null));
oneOf(db).endTransaction(noAckTxn);
// No more messages
oneOf(db).startTransaction();
will(returnValue(noMsgTxn));
oneOf(db).generateBatch(with(noMsgTxn), with(contactId),
with(any(int.class)), with(maxLatency));
will(returnValue(null));
oneOf(db).endTransaction(noMsgTxn);
// Flush the output stream // Flush the output stream
oneOf(packetWriter).flush(); oneOf(packetWriter).flush();
// Remove listener // Remove listener