Merge branch '2228-mailbox-client-manager' into 'master'

Add mailbox client manager

Closes #2228

See merge request briar/briar!1696
This commit is contained in:
Torsten Grote
2022-08-16 14:20:42 +00:00
30 changed files with 1802 additions and 75 deletions

View File

@@ -0,0 +1,18 @@
package org.briarproject.bramble.io;
import dagger.Module;
import dagger.Provides;
import okhttp3.Dns;
/**
* This is a dedicated module, so it can be replaced for testing.
*/
@Module
public class DnsModule {
@Provides
Dns provideDns(NoDns noDns) {
return noDns;
}
}

View File

@@ -0,0 +1,31 @@
package org.briarproject.bramble.io;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.Collections;
import java.util.List;
import javax.inject.Inject;
import okhttp3.Dns;
@NotNullByDefault
class NoDns implements Dns {
private static final byte[] UNSPECIFIED_ADDRESS = new byte[4];
@Inject
public NoDns() {
}
@Override
public List<InetAddress> lookup(String hostname)
throws UnknownHostException {
InetAddress unspecified =
InetAddress.getByAddress(hostname, UNSPECIFIED_ADDRESS);
return Collections.singletonList(unspecified);
}
}

View File

@@ -0,0 +1,667 @@
package org.briarproject.bramble.mailbox;
import org.briarproject.bramble.api.contact.Contact;
import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.contact.ContactManager;
import org.briarproject.bramble.api.contact.event.ContactRemovedEvent;
import org.briarproject.bramble.api.db.DatabaseExecutor;
import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.db.TransactionManager;
import org.briarproject.bramble.api.event.Event;
import org.briarproject.bramble.api.event.EventExecutor;
import org.briarproject.bramble.api.event.EventListener;
import org.briarproject.bramble.api.lifecycle.Service;
import org.briarproject.bramble.api.lifecycle.ServiceException;
import org.briarproject.bramble.api.mailbox.MailboxProperties;
import org.briarproject.bramble.api.mailbox.MailboxSettingsManager;
import org.briarproject.bramble.api.mailbox.MailboxStatus;
import org.briarproject.bramble.api.mailbox.MailboxUpdate;
import org.briarproject.bramble.api.mailbox.MailboxUpdateManager;
import org.briarproject.bramble.api.mailbox.MailboxUpdateWithMailbox;
import org.briarproject.bramble.api.mailbox.MailboxVersion;
import org.briarproject.bramble.api.mailbox.event.MailboxPairedEvent;
import org.briarproject.bramble.api.mailbox.event.MailboxUnpairedEvent;
import org.briarproject.bramble.api.mailbox.event.MailboxUpdateSentToNewContactEvent;
import org.briarproject.bramble.api.mailbox.event.OwnMailboxConnectionStatusEvent;
import org.briarproject.bramble.api.mailbox.event.RemoteMailboxUpdateEvent;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.plugin.Plugin;
import org.briarproject.bramble.api.plugin.PluginManager;
import org.briarproject.bramble.api.plugin.event.TransportActiveEvent;
import org.briarproject.bramble.api.plugin.event.TransportInactiveEvent;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Executor;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.logging.Logger;
import javax.annotation.Nullable;
import javax.annotation.concurrent.ThreadSafe;
import javax.inject.Inject;
import static java.util.logging.Level.WARNING;
import static java.util.logging.Logger.getLogger;
import static org.briarproject.bramble.api.mailbox.MailboxConstants.CLIENT_SUPPORTS;
import static org.briarproject.bramble.api.mailbox.MailboxHelper.isClientCompatibleWithServer;
import static org.briarproject.bramble.api.nullsafety.NullSafety.requireNonNull;
import static org.briarproject.bramble.api.plugin.Plugin.State.ACTIVE;
import static org.briarproject.bramble.api.plugin.TorConstants.ID;
import static org.briarproject.bramble.util.LogUtils.logException;
/**
* This component manages a {@link MailboxClient} for each mailbox we know
* about and are able to use (our own mailbox and/or contacts' mailboxes).
* The clients are created when we come online (i.e. when the Tor plugin
* becomes {@link Plugin.State#ACTIVE active}) and destroyed when we go
* offline.
* <p/>
* The manager keeps track of the latest {@link MailboxUpdate} sent to and
* received from each contact. These updates are used to decide which
* mailboxes the manager needs clients for, and which mailbox (if any) should
* be used for uploading data to and/or downloading data from each contact.
* <p/>
* The assignments of contacts to mailboxes for upload and/or download may
* change when the following events happen:
* <ul>
* <li> A mailbox is paired or unpaired </li>
* <li> A contact is added or removed </li>
* <li> A {@link MailboxUpdate} is received from a contact </li>
* <li> We discover that our own mailbox's supported API versions have
* changed </li>
* </ul>
* <p/>
* The manager keeps its mutable state consistent with the state in the DB by
* using commit actions and events to update the manager's state on the event
* thread.
*/
@ThreadSafe
@NotNullByDefault
class MailboxClientManager implements Service, EventListener {
private static final Logger LOG =
getLogger(MailboxClientManager.class.getName());
private final Executor eventExecutor, dbExecutor;
private final TransactionManager db;
private final ContactManager contactManager;
private final PluginManager pluginManager;
private final MailboxSettingsManager mailboxSettingsManager;
private final MailboxUpdateManager mailboxUpdateManager;
private final MailboxClientFactory mailboxClientFactory;
private final TorReachabilityMonitor reachabilityMonitor;
private final AtomicBoolean used = new AtomicBoolean(false);
// All of the following mutable state must only be accessed on the
// event thread
private final Map<ContactId, Updates> contactUpdates = new HashMap<>();
private final Map<ContactId, MailboxClient> contactClients =
new HashMap<>();
@Nullable
private MailboxProperties ownProperties = null;
@Nullable
private MailboxClient ownClient = null;
private boolean online = false, handleEvents = false;
@Inject
MailboxClientManager(@EventExecutor Executor eventExecutor,
@DatabaseExecutor Executor dbExecutor,
TransactionManager db,
ContactManager contactManager,
PluginManager pluginManager,
MailboxSettingsManager mailboxSettingsManager,
MailboxUpdateManager mailboxUpdateManager,
MailboxClientFactory mailboxClientFactory,
TorReachabilityMonitor reachabilityMonitor) {
this.eventExecutor = eventExecutor;
this.dbExecutor = dbExecutor;
this.db = db;
this.contactManager = contactManager;
this.pluginManager = pluginManager;
this.mailboxSettingsManager = mailboxSettingsManager;
this.mailboxUpdateManager = mailboxUpdateManager;
this.mailboxClientFactory = mailboxClientFactory;
this.reachabilityMonitor = reachabilityMonitor;
}
@Override
public void startService() throws ServiceException {
if (used.getAndSet(true)) throw new IllegalStateException();
reachabilityMonitor.start();
dbExecutor.execute(this::loadMailboxProperties);
}
@DatabaseExecutor
private void loadMailboxProperties() {
LOG.info("Loading mailbox properties");
try {
db.transaction(true, txn -> {
Map<ContactId, Updates> updates = new HashMap<>();
for (Contact c : contactManager.getContacts(txn)) {
MailboxUpdate local = mailboxUpdateManager
.getLocalUpdate(txn, c.getId());
MailboxUpdate remote = mailboxUpdateManager
.getRemoteUpdate(txn, c.getId());
updates.put(c.getId(), new Updates(local, remote));
}
MailboxProperties ownProps =
mailboxSettingsManager.getOwnMailboxProperties(txn);
// Use a commit action so the state in memory remains
// consistent with the state in the DB
txn.attach(() -> initialiseState(updates, ownProps));
});
} catch (DbException e) {
logException(LOG, WARNING, e);
}
}
@EventExecutor
private void initialiseState(Map<ContactId, Updates> updates,
@Nullable MailboxProperties ownProps) {
contactUpdates.putAll(updates);
ownProperties = ownProps;
Plugin tor = pluginManager.getPlugin(ID);
if (tor != null && tor.getState() == ACTIVE) {
LOG.info("Online");
online = true;
createClients();
}
// Now that the mutable state has been initialised we can start
// handling events. This is done in a commit action so that we don't
// miss any changes to the DB state or handle events for any changes
// that were already reflected in the initial load
handleEvents = true;
}
@EventExecutor
private void createClients() {
LOG.info("Creating clients");
for (Entry<ContactId, Updates> e : contactUpdates.entrySet()) {
ContactId c = e.getKey();
Updates u = e.getValue();
if (isContactMailboxUsable(u.remote)) {
// Create and start a client for the contact's mailbox
MailboxClient contactClient = createAndStartClient(c);
// Assign the contact to the contact's mailbox for upload
assignContactToContactMailboxForUpload(c, contactClient, u);
if (!isOwnMailboxUsable(ownProperties, u.remote)) {
// We don't have a usable mailbox, so assign the contact to
// the contact's mailbox for download too
assignContactToContactMailboxForDownload(c,
contactClient, u);
}
}
}
if (ownProperties == null) return;
if (!isOwnMailboxUsable(ownProperties)) {
LOG.warning("We have a mailbox but we can't use it");
return;
}
// Create and start a client for our mailbox
createAndStartClientForOwnMailbox();
// Assign contacts to our mailbox for upload/download
for (Entry<ContactId, Updates> e : contactUpdates.entrySet()) {
ContactId c = e.getKey();
Updates u = e.getValue();
if (isOwnMailboxUsable(ownProperties, u.remote)) {
// Assign the contact to our mailbox for download
assignContactToOwnMailboxForDownload(c, u);
if (!isContactMailboxUsable(u.remote)) {
// The contact doesn't have a usable mailbox, so assign
// the contact to our mailbox for upload too
assignContactToOwnMailboxForUpload(c, u);
}
}
}
}
@Override
public void stopService() throws ServiceException {
CountDownLatch latch = new CountDownLatch(1);
eventExecutor.execute(() -> {
handleEvents = false;
if (online) destroyClients();
latch.countDown();
});
reachabilityMonitor.destroy();
try {
latch.await();
} catch (InterruptedException e) {
throw new ServiceException(e);
}
}
@EventExecutor
private void destroyClients() {
LOG.info("Destroying clients");
for (MailboxClient client : contactClients.values()) {
client.destroy();
}
contactClients.clear();
destroyOwnClient();
}
@EventExecutor
private void destroyOwnClient() {
if (ownClient != null) {
ownClient.destroy();
ownClient = null;
}
}
@Override
public void eventOccurred(Event e) {
if (!handleEvents) return;
if (e instanceof TransportActiveEvent) {
TransportActiveEvent t = (TransportActiveEvent) e;
if (t.getTransportId().equals(ID)) onTorActive();
} else if (e instanceof TransportInactiveEvent) {
TransportInactiveEvent t = (TransportInactiveEvent) e;
if (t.getTransportId().equals(ID)) onTorInactive();
} else if (e instanceof MailboxPairedEvent) {
LOG.info("Mailbox paired");
MailboxPairedEvent m = (MailboxPairedEvent) e;
onMailboxPaired(m.getProperties(), m.getLocalUpdates());
} else if (e instanceof MailboxUnpairedEvent) {
LOG.info("Mailbox unpaired");
MailboxUnpairedEvent m = (MailboxUnpairedEvent) e;
onMailboxUnpaired(m.getLocalUpdates());
} else if (e instanceof MailboxUpdateSentToNewContactEvent) {
LOG.info("Contact added");
MailboxUpdateSentToNewContactEvent
m = (MailboxUpdateSentToNewContactEvent) e;
onContactAdded(m.getContactId(), m.getMailboxUpdate());
} else if (e instanceof ContactRemovedEvent) {
LOG.info("Contact removed");
ContactRemovedEvent c = (ContactRemovedEvent) e;
onContactRemoved(c.getContactId());
} else if (e instanceof RemoteMailboxUpdateEvent) {
LOG.info("Remote mailbox update");
RemoteMailboxUpdateEvent r = (RemoteMailboxUpdateEvent) e;
onRemoteMailboxUpdate(r.getContact(), r.getMailboxUpdate());
} else if (e instanceof OwnMailboxConnectionStatusEvent) {
OwnMailboxConnectionStatusEvent o =
(OwnMailboxConnectionStatusEvent) e;
onOwnMailboxConnectionStatusChanged(o.getStatus());
}
}
@EventExecutor
private void onTorActive() {
// If we checked the plugin at startup concurrently with the plugin
// becoming active then `online` may already be true when we receive
// the first TransportActiveEvent, in which case ignore it
if (online) return;
LOG.info("Online");
online = true;
createClients();
}
@EventExecutor
private void onTorInactive() {
// If we checked the plugin at startup concurrently with the plugin
// becoming inactive then `online` may already be false when we
// receive the first TransportInactiveEvent, in which case ignore it
if (!online) return;
LOG.info("Offline");
online = false;
destroyClients();
}
@EventExecutor
private void onMailboxPaired(MailboxProperties ownProps,
Map<ContactId, MailboxUpdateWithMailbox> localUpdates) {
for (Entry<ContactId, MailboxUpdateWithMailbox> e :
localUpdates.entrySet()) {
ContactId c = e.getKey();
Updates u = contactUpdates.get(c);
contactUpdates.put(c, new Updates(e.getValue(), u.remote));
}
ownProperties = ownProps;
if (!online) return;
if (!isOwnMailboxUsable(ownProperties)) {
LOG.warning("We have a mailbox but we can't use it");
return;
}
// Create and start a client for our mailbox
createAndStartClientForOwnMailbox();
// Assign contacts to our mailbox for upload/download
for (Entry<ContactId, Updates> e : contactUpdates.entrySet()) {
ContactId c = e.getKey();
Updates u = e.getValue();
if (!isOwnMailboxUsable(ownProperties, u.remote)) {
// Our mailbox isn't usable for communicating with this
// contact, so don't assign/reassign this contact
continue;
}
if (isContactMailboxUsable(u.remote)) {
// The contact has a usable mailbox, so the contact should
// currently be assigned to the contact's mailbox for upload
// and download. Reassign the contact to our mailbox for
// download
MailboxClient contactClient =
requireNonNull(contactClients.get(c));
contactClient.deassignContactForDownload(c);
} else {
// The contact doesn't have a usable mailbox, so assign the
// contact to our mailbox for upload
assignContactToOwnMailboxForUpload(c, u);
}
assignContactToOwnMailboxForDownload(c, u);
}
}
@EventExecutor
private void onMailboxUnpaired(Map<ContactId, MailboxUpdate> localUpdates) {
for (Entry<ContactId, MailboxUpdate> e : localUpdates.entrySet()) {
ContactId c = e.getKey();
Updates updates = contactUpdates.get(c);
contactUpdates.put(c, new Updates(e.getValue(), updates.remote));
}
MailboxProperties oldOwnProperties = ownProperties;
ownProperties = null;
if (!online) return;
// Destroy the client for our own mailbox, if any
destroyOwnClient();
// Reassign contacts to their own mailboxes for download where possible
for (Entry<ContactId, Updates> e : contactUpdates.entrySet()) {
ContactId c = e.getKey();
Updates u = e.getValue();
if (isContactMailboxUsable(u.remote) &&
isOwnMailboxUsable(oldOwnProperties, u.remote)) {
// The contact should currently be assigned to our mailbox
// for download. Reassign the contact to the contact's
// mailbox for download
MailboxClient contactClient =
requireNonNull(contactClients.get(c));
assignContactToContactMailboxForDownload(c, contactClient, u);
}
}
}
@EventExecutor
private void onContactAdded(ContactId c, MailboxUpdate u) {
Updates old = contactUpdates.put(c, new Updates(u, null));
if (old != null) throw new IllegalStateException();
// We haven't yet received an update from the newly added contact,
// so at this stage we don't need to assign the contact to any
// mailboxes for upload or download
}
@EventExecutor
private void onContactRemoved(ContactId c) {
Updates updates = requireNonNull(contactUpdates.remove(c));
if (!online) return;
// Destroy the client for the contact's mailbox, if any
MailboxClient client = contactClients.remove(c);
if (client != null) client.destroy();
// If we have a mailbox and the contact is assigned to it for upload
// and/or download, deassign the contact
if (ownProperties == null) return;
if (isOwnMailboxUsable(ownProperties, updates.remote)) {
// We have a usable mailbox, so the contact should currently be
// assigned to our mailbox for download. Deassign the contact from
// our mailbox for download
requireNonNull(ownClient).deassignContactForDownload(c);
if (!isContactMailboxUsable(updates.remote)) {
// The contact doesn't have a usable mailbox, so the contact
// should currently be assigned to our mailbox for upload.
// Deassign the contact from our mailbox for upload
requireNonNull(ownClient).deassignContactForUpload(c);
}
}
}
@EventExecutor
private void onRemoteMailboxUpdate(ContactId c, MailboxUpdate remote) {
Updates old = contactUpdates.get(c);
MailboxUpdate oldRemote = old.remote;
Updates u = new Updates(old.local, remote);
contactUpdates.put(c, u);
if (!online) return;
// What may have changed?
// * Contact's mailbox may be usable now, was unusable before
// * Contact's mailbox may be unusable now, was usable before
// * Contact's mailbox may have been replaced
// * Contact's mailbox may have changed its API versions
// * Contact may be able to use our mailbox now, was unable before
// * Contact may be unable to use our mailbox now, was able before
boolean wasContactMailboxUsable = isContactMailboxUsable(oldRemote);
boolean isContactMailboxUsable = isContactMailboxUsable(remote);
boolean wasOwnMailboxUsable =
isOwnMailboxUsable(ownProperties, oldRemote);
boolean isOwnMailboxUsable = isOwnMailboxUsable(ownProperties, remote);
// Create/destroy/replace the client for the contact's mailbox if needed
MailboxClient contactClient = null;
boolean clientReplaced = false;
if (isContactMailboxUsable) {
if (wasContactMailboxUsable) {
MailboxProperties oldProps = getMailboxProperties(oldRemote);
MailboxProperties newProps = getMailboxProperties(remote);
if (oldProps.equals(newProps)) {
// The contact previously had a usable mailbox, now has
// a usable mailbox, it's the same mailbox, and its API
// versions haven't changed. Keep using the existing client
contactClient = requireNonNull(contactClients.get(c));
} else {
// The contact previously had a usable mailbox and now has
// a usable mailbox, but either it's a new mailbox or its
// API versions have changed. Replace the client
requireNonNull(contactClients.remove(c)).destroy();
contactClient = createAndStartClient(c);
clientReplaced = true;
}
} else {
// The client didn't previously have a usable mailbox but now
// has one. Create and start a client
contactClient = createAndStartClient(c);
}
} else if (wasContactMailboxUsable) {
// The client previously had a usable mailbox but no longer does.
// Destroy the existing client
requireNonNull(contactClients.remove(c)).destroy();
}
boolean wasAssignedToOwnMailboxForUpload =
wasOwnMailboxUsable && !wasContactMailboxUsable;
boolean willBeAssignedToOwnMailboxForUpload =
isOwnMailboxUsable && !isContactMailboxUsable;
boolean wasAssignedToContactMailboxForDownload =
!wasOwnMailboxUsable && wasContactMailboxUsable;
boolean willBeAssignedToContactMailboxForDownload =
!isOwnMailboxUsable && isContactMailboxUsable;
// Deassign the contact for upload/download if needed
if (wasAssignedToOwnMailboxForUpload &&
!willBeAssignedToOwnMailboxForUpload) {
requireNonNull(ownClient).deassignContactForUpload(c);
}
if (wasOwnMailboxUsable && !isOwnMailboxUsable) {
requireNonNull(ownClient).deassignContactForDownload(c);
}
// If the client for the contact's mailbox was replaced or destroyed
// above then we don't need to deassign the contact for download
if (wasAssignedToContactMailboxForDownload &&
!willBeAssignedToContactMailboxForDownload &&
!clientReplaced && isContactMailboxUsable) {
requireNonNull(contactClient).deassignContactForDownload(c);
}
// We never need to deassign the contact from the contact's mailbox for
// upload: this would only be needed if the contact's mailbox were no
// longer usable, in which case the client would already have been
// destroyed above. Thanks to the linter for spotting this
// Assign the contact for upload/download if needed
if (!wasAssignedToOwnMailboxForUpload &&
willBeAssignedToOwnMailboxForUpload) {
assignContactToOwnMailboxForUpload(c, u);
}
if (!wasOwnMailboxUsable && isOwnMailboxUsable) {
assignContactToOwnMailboxForDownload(c, u);
}
if ((!wasContactMailboxUsable || clientReplaced) &&
isContactMailboxUsable) {
assignContactToContactMailboxForUpload(c, contactClient, u);
}
if ((!wasAssignedToContactMailboxForDownload || clientReplaced) &&
willBeAssignedToContactMailboxForDownload) {
assignContactToContactMailboxForDownload(c, contactClient, u);
}
}
@EventExecutor
private void onOwnMailboxConnectionStatusChanged(MailboxStatus status) {
if (!online || ownProperties == null) return;
List<MailboxVersion> oldServerSupports =
ownProperties.getServerSupports();
List<MailboxVersion> newServerSupports = status.getServerSupports();
if (!oldServerSupports.equals(newServerSupports)) {
LOG.info("Our mailbox's supported API versions have changed");
// This potentially affects every assignment of contacts to
// mailboxes for upload and download, so just rebuild the clients
// and assignments from scratch
destroyClients();
createClients();
}
}
@EventExecutor
private void createAndStartClientForOwnMailbox() {
if (ownClient != null) throw new IllegalStateException();
ownClient = mailboxClientFactory.createOwnMailboxClient(
reachabilityMonitor, requireNonNull(ownProperties));
ownClient.start();
}
@EventExecutor
private MailboxClient createAndStartClient(ContactId c) {
MailboxClient client = mailboxClientFactory
.createContactMailboxClient(reachabilityMonitor);
MailboxClient old = contactClients.put(c, client);
if (old != null) throw new IllegalStateException();
client.start();
return client;
}
@EventExecutor
private void assignContactToOwnMailboxForDownload(ContactId c, Updates u) {
MailboxProperties localProps = getMailboxProperties(u.local);
requireNonNull(ownClient).assignContactForDownload(c,
requireNonNull(ownProperties),
requireNonNull(localProps.getOutboxId()));
}
@EventExecutor
private void assignContactToOwnMailboxForUpload(ContactId c, Updates u) {
MailboxProperties localProps = getMailboxProperties(u.local);
requireNonNull(ownClient).assignContactForUpload(c,
requireNonNull(ownProperties),
requireNonNull(localProps.getInboxId()));
}
@EventExecutor
private void assignContactToContactMailboxForDownload(ContactId c,
MailboxClient contactClient, Updates u) {
MailboxProperties remoteProps =
getMailboxProperties(requireNonNull(u.remote));
contactClient.assignContactForDownload(c, remoteProps,
requireNonNull(remoteProps.getInboxId()));
}
@EventExecutor
private void assignContactToContactMailboxForUpload(ContactId c,
MailboxClient contactClient, Updates u) {
MailboxProperties remoteProps =
getMailboxProperties(requireNonNull(u.remote));
contactClient.assignContactForUpload(c, remoteProps,
requireNonNull(remoteProps.getOutboxId()));
}
/**
* Returns the {@link MailboxProperties} included in the given update,
* which must be a {@link MailboxUpdateWithMailbox}.
*/
private MailboxProperties getMailboxProperties(MailboxUpdate update) {
if (!(update instanceof MailboxUpdateWithMailbox)) {
throw new IllegalArgumentException();
}
MailboxUpdateWithMailbox mailbox = (MailboxUpdateWithMailbox) update;
return mailbox.getMailboxProperties();
}
/**
* Returns true if we can use our own mailbox to communicate with the
* contact that sent the given update.
*/
private boolean isOwnMailboxUsable(
@Nullable MailboxProperties ownProperties,
@Nullable MailboxUpdate remote) {
if (ownProperties == null || remote == null) return false;
return isMailboxUsable(remote.getClientSupports(),
ownProperties.getServerSupports());
}
/**
* Returns true if we can use the contact's mailbox to communicate with
* the contact that sent the given update.
*/
private boolean isContactMailboxUsable(@Nullable MailboxUpdate remote) {
if (remote instanceof MailboxUpdateWithMailbox) {
MailboxUpdateWithMailbox remoteMailbox =
(MailboxUpdateWithMailbox) remote;
return isMailboxUsable(remoteMailbox.getClientSupports(),
remoteMailbox.getMailboxProperties().getServerSupports());
}
return false;
}
/**
* Returns true if we can communicate with a contact that has the given
* client-supported API versions via a mailbox with the given
* server-supported API versions.
*/
private boolean isMailboxUsable(List<MailboxVersion> contactClient,
List<MailboxVersion> server) {
return isClientCompatibleWithServer(contactClient, server)
&& isClientCompatibleWithServer(CLIENT_SUPPORTS, server);
}
/**
* Returns true if our client-supported API versions are compatible with
* our own mailbox's server-supported API versions.
*/
@SuppressWarnings("BooleanMethodIsAlwaysInverted")
private boolean isOwnMailboxUsable(MailboxProperties ownProperties) {
return isClientCompatibleWithServer(CLIENT_SUPPORTS,
ownProperties.getServerSupports());
}
/**
* A container for the latest {@link MailboxUpdate updates} sent to and
* received from a given contact.
*/
private static class Updates {
/**
* The latest update sent to the contact.
*/
private final MailboxUpdate local;
/**
* The latest update received from the contact, or null if no update
* has been received.
*/
@Nullable
private final MailboxUpdate remote;
private Updates(MailboxUpdate local, @Nullable MailboxUpdate remote) {
this.local = local;
this.remote = remote;
}
}
}

View File

@@ -4,17 +4,22 @@ import org.briarproject.bramble.api.FeatureFlags;
import org.briarproject.bramble.api.client.ClientHelper;
import org.briarproject.bramble.api.contact.ContactManager;
import org.briarproject.bramble.api.data.MetadataEncoder;
import org.briarproject.bramble.api.db.DatabaseExecutor;
import org.briarproject.bramble.api.db.TransactionManager;
import org.briarproject.bramble.api.event.EventBus;
import org.briarproject.bramble.api.event.EventExecutor;
import org.briarproject.bramble.api.lifecycle.LifecycleManager;
import org.briarproject.bramble.api.mailbox.MailboxManager;
import org.briarproject.bramble.api.mailbox.MailboxSettingsManager;
import org.briarproject.bramble.api.mailbox.MailboxUpdateManager;
import org.briarproject.bramble.api.mailbox.MailboxVersion;
import org.briarproject.bramble.api.plugin.PluginManager;
import org.briarproject.bramble.api.sync.validation.ValidationManager;
import org.briarproject.bramble.api.system.Clock;
import org.briarproject.bramble.api.versioning.ClientVersioningManager;
import java.util.List;
import java.util.concurrent.Executor;
import javax.inject.Inject;
import javax.inject.Singleton;
@@ -37,6 +42,8 @@ public class MailboxModule {
MailboxUpdateManager mailboxUpdateManager;
@Inject
MailboxFileManager mailboxFileManager;
@Inject
MailboxClientManager mailboxClientManager;
}
@Provides
@@ -126,4 +133,49 @@ public class MailboxModule {
MailboxWorkerFactoryImpl mailboxWorkerFactory) {
return mailboxWorkerFactory;
}
@Provides
MailboxClientFactory provideMailboxClientFactory(
MailboxClientFactoryImpl mailboxClientFactory) {
return mailboxClientFactory;
}
@Provides
MailboxApiCaller provideMailboxApiCaller(
MailboxApiCallerImpl mailboxApiCaller) {
return mailboxApiCaller;
}
@Provides
@Singleton
TorReachabilityMonitor provideTorReachabilityMonitor(
TorReachabilityMonitorImpl reachabilityMonitor) {
return reachabilityMonitor;
}
@Provides
@Singleton
MailboxClientManager provideMailboxClientManager(
@EventExecutor Executor eventExecutor,
@DatabaseExecutor Executor dbExecutor,
TransactionManager db,
ContactManager contactManager,
PluginManager pluginManager,
MailboxSettingsManager mailboxSettingsManager,
MailboxUpdateManager mailboxUpdateManager,
MailboxClientFactory mailboxClientFactory,
TorReachabilityMonitor reachabilityMonitor,
FeatureFlags featureFlags,
LifecycleManager lifecycleManager,
EventBus eventBus) {
MailboxClientManager manager = new MailboxClientManager(eventExecutor,
dbExecutor, db, contactManager, pluginManager,
mailboxSettingsManager, mailboxUpdateManager,
mailboxClientFactory, reachabilityMonitor);
if (featureFlags.shouldEnableMailbox()) {
lifecycleManager.registerService(manager);
eventBus.addListener(manager);
}
return manager;
}
}

View File

@@ -27,7 +27,7 @@ import org.briarproject.bramble.api.mailbox.MailboxUpdateWithMailbox;
import org.briarproject.bramble.api.mailbox.MailboxVersion;
import org.briarproject.bramble.api.mailbox.event.MailboxPairedEvent;
import org.briarproject.bramble.api.mailbox.event.MailboxUnpairedEvent;
import org.briarproject.bramble.api.mailbox.event.MailboxUpdateSentEvent;
import org.briarproject.bramble.api.mailbox.event.MailboxUpdateSentToNewContactEvent;
import org.briarproject.bramble.api.mailbox.event.RemoteMailboxUpdateEvent;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.sync.Group;
@@ -118,13 +118,12 @@ class MailboxUpdateManagerImpl implements MailboxUpdateManager,
}
Group g = getContactGroup(c);
storeMessageReplaceLatest(txn, g.getId(), updated);
txn.attach(new MailboxUpdateSentEvent(c.getId(), updated));
}
} else {
db.addGroup(txn, localGroup);
// Set things up for any pre-existing contacts
for (Contact c : db.getContacts(txn)) {
addingContact(txn, c);
addingContact(txn, c, false);
}
}
@@ -140,6 +139,17 @@ class MailboxUpdateManagerImpl implements MailboxUpdateManager,
@Override
public void addingContact(Transaction txn, Contact c) throws DbException {
addingContact(txn, c, true);
}
/**
* @param attachEvent True if a {@link MailboxUpdateSentToNewContactEvent}
* should be attached to the transaction. We should only do this when
* adding a new contact, not when setting up this client for an existing
* contact.
*/
private void addingContact(Transaction txn, Contact c, boolean attachEvent)
throws DbException {
// Create a group to share with the contact
Group g = getContactGroup(c);
db.addGroup(txn, g);
@@ -160,7 +170,9 @@ class MailboxUpdateManagerImpl implements MailboxUpdateManager,
// Not paired, but we still want to get our clientSupports sent
u = sendUpdateNoMailbox(txn, c);
}
txn.attach(new MailboxUpdateSentEvent(c.getId(), u));
if (attachEvent) {
txn.attach(new MailboxUpdateSentToNewContactEvent(c.getId(), u));
}
}
@Override